Path: blob/main/sys/contrib/vchiq/interface/vchiq_arm/vchiq_2835_arm.c
110033 views
/**1* Copyright (c) 2010-2012 Broadcom. All rights reserved.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions, and the following disclaimer,8* without modification.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12* 3. The names of the above-listed copyright holders may not be used13* to endorse or promote products derived from this software without14* specific prior written permission.15*16* ALTERNATIVELY, this software may be distributed under the terms of the17* GNU General Public License ("GPL") version 2, as published by the Free18* Software Foundation.19*20* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR24* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,25* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,26* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR27* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF28* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING29* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS30* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31*/3233#include <interface/compat/vchi_bsd.h>3435#include <sys/malloc.h>36#include <sys/rwlock.h>3738#include <vm/vm.h>39#include <vm/pmap.h>40#include <vm/vm_extern.h>41#include <vm/vm_kern.h>42#include <vm/vm_map.h>43#include <vm/vm_object.h>44#include <vm/vm_page.h>45#include <vm/vm_pager.h>46#include <vm/vm_param.h>4748#include <machine/bus.h>49#include <machine/cpu.h>50#include <arm/broadcom/bcm2835/bcm2835_mbox.h>51#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>5253MALLOC_DEFINE(M_VCPAGELIST, "vcpagelist", "VideoCore pagelist memory");5455#define TOTAL_SLOTS (VCHIQ_SLOT_ZERO_SLOTS + 2 * 32)5657#define VCHIQ_DOORBELL_IRQ IRQ_ARM_DOORBELL_058#define VCHIQ_ARM_ADDRESS(x) ((void *)PHYS_TO_VCBUS(pmap_kextract((vm_offset_t)(x))))5960#include "vchiq_arm.h"61#include "vchiq_2835.h"62#include "vchiq_connected.h"63#include "vchiq_killable.h"6465#define MAX_FRAGMENTS (VCHIQ_NUM_CURRENT_BULKS * 2)6667/*68* XXXMDC69* Do this less ad-hoc-y -- e.g.70* https://github.com/raspberrypi/linux/commit/c683db8860a80562a2bb5b451d77b3e471d24f3671*/72#if defined(__aarch64__)73int g_cache_line_size = 64;74#else75int g_cache_line_size = 32;76#endif77static int g_fragment_size;7879unsigned int g_long_bulk_space = 0;80#define VM_PAGE_TO_VC_BULK_PAGE(x) (\81g_long_bulk_space ? VM_PAGE_TO_PHYS(x)\82: PHYS_TO_VCBUS(VM_PAGE_TO_PHYS(x))\83)8485typedef struct vchiq_2835_state_struct {86int inited;87VCHIQ_ARM_STATE_T arm_state;88} VCHIQ_2835_ARM_STATE_T;8990static char *g_slot_mem;91static int g_slot_mem_size;92vm_paddr_t g_slot_phys;93/* BSD DMA */94bus_dma_tag_t bcm_slots_dma_tag;95bus_dmamap_t bcm_slots_dma_map;9697static char *g_fragments_base;98static char *g_free_fragments;99struct semaphore g_free_fragments_sema;100101static DEFINE_SEMAPHORE(g_free_fragments_mutex);102103typedef struct bulkinfo_struct {104PAGELIST_T *pagelist;105bus_dma_tag_t pagelist_dma_tag;106bus_dmamap_t pagelist_dma_map;107void *buf;108size_t size;109} BULKINFO_T;110111static int112create_pagelist(char __user *buf, size_t count, unsigned short type,113struct proc *p, BULKINFO_T *bi);114115static void116free_pagelist(BULKINFO_T *bi, int actual);117118static void119vchiq_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)120{121bus_addr_t *addr;122123if (err)124return;125126addr = (bus_addr_t*)arg;127*addr = PHYS_TO_VCBUS(segs[0].ds_addr);128}129130#if defined(__aarch64__) /* See comment in free_pagelist */131static int132invalidate_cachelines_in_range_of_ppage(133vm_page_t p,134size_t offset,135size_t count136)137{138if(offset + count > PAGE_SIZE){ return EINVAL; }139uint8_t *dst = (uint8_t*)pmap_quick_enter_page(p);140if (!dst){141return ENOMEM;142}143cpu_dcache_inv_range((void *)((vm_offset_t)dst + offset), count);144pmap_quick_remove_page((vm_offset_t)dst);145return 0;146}147148/* XXXMDC bulk instead of loading and invalidating single pages? */149static void150invalidate_cachelines_in_range_of_ppage_seq(vm_page_t *p, size_t start,151size_t count)152{153if (start >= PAGE_SIZE)154goto invalid_input;155156#define _NEXT_AT(x,_m) (((x)+((_m)-1)) & ~((_m)-1)) /* for power of two m */157size_t offset = _NEXT_AT(start,g_cache_line_size);158#undef _NEXT_AT159count = (offset < start + count) ? count - (offset - start) : 0;160offset = offset & (PAGE_SIZE - 1);161for (size_t done = 0; count > done;162p++, done += PAGE_SIZE - offset, offset = 0) {163size_t in_page = PAGE_SIZE - offset;164size_t todo = (count-done > in_page) ? in_page : count-done;165int e = invalidate_cachelines_in_range_of_ppage(*p, offset, todo);166if (e != 0)167goto problem_in_loop;168}169return;170171problem_in_loop:172invalid_input:173WARN_ON(1);174return;175}176#endif177178static int179copyout_page(vm_page_t p, size_t offset, void *kaddr, size_t size)180{181uint8_t *dst;182183dst = (uint8_t*)pmap_quick_enter_page(p);184if (!dst)185return ENOMEM;186187memcpy(dst + offset, kaddr, size);188189pmap_quick_remove_page((vm_offset_t)dst);190191return 0;192}193194int __init195vchiq_platform_init(VCHIQ_STATE_T *state)196{197VCHIQ_SLOT_ZERO_T *vchiq_slot_zero;198int frag_mem_size;199int err;200int i;201202/* Allocate space for the channels in coherent memory */203g_slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE);204g_fragment_size = 2*g_cache_line_size;205frag_mem_size = PAGE_ALIGN(g_fragment_size * MAX_FRAGMENTS);206207err = bus_dma_tag_create(208NULL,209PAGE_SIZE, 0, /* alignment, boundary */210BUS_SPACE_MAXADDR_32BIT, /* lowaddr */211BUS_SPACE_MAXADDR, /* highaddr */212NULL, NULL, /* filter, filterarg */213g_slot_mem_size + frag_mem_size, 1, /* maxsize, nsegments */214g_slot_mem_size + frag_mem_size, 0, /* maxsegsize, flags */215NULL, NULL, /* lockfunc, lockarg */216&bcm_slots_dma_tag);217218err = bus_dmamem_alloc(bcm_slots_dma_tag, (void **)&g_slot_mem,219BUS_DMA_COHERENT | BUS_DMA_WAITOK, &bcm_slots_dma_map);220if (err) {221vchiq_log_error(vchiq_core_log_level, "Unable to allocate channel memory");222err = -ENOMEM;223goto failed_alloc;224}225226err = bus_dmamap_load(bcm_slots_dma_tag, bcm_slots_dma_map, g_slot_mem,227g_slot_mem_size + frag_mem_size, vchiq_dmamap_cb,228&g_slot_phys, 0);229230if (err) {231vchiq_log_error(vchiq_core_log_level, "cannot load DMA map");232err = -ENOMEM;233goto failed_load;234}235236WARN_ON(((size_t)g_slot_mem & (PAGE_SIZE - 1)) != 0);237238vchiq_slot_zero = vchiq_init_slots(g_slot_mem, g_slot_mem_size);239if (!vchiq_slot_zero) {240err = -EINVAL;241goto failed_init_slots;242}243244vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_OFFSET_IDX] =245(int)g_slot_phys + g_slot_mem_size;246vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_COUNT_IDX] =247MAX_FRAGMENTS;248249g_fragments_base = (char *)(g_slot_mem + g_slot_mem_size);250g_slot_mem_size += frag_mem_size;251252g_free_fragments = g_fragments_base;253for (i = 0; i < (MAX_FRAGMENTS - 1); i++) {254*(char **)&g_fragments_base[i*g_fragment_size] =255&g_fragments_base[(i + 1)*g_fragment_size];256}257*(char **)&g_fragments_base[i*g_fragment_size] = NULL;258_sema_init(&g_free_fragments_sema, MAX_FRAGMENTS);259260if (vchiq_init_state(state, vchiq_slot_zero, 0/*slave*/) !=261VCHIQ_SUCCESS) {262err = -EINVAL;263goto failed_vchiq_init;264}265266bcm_mbox_write(BCM2835_MBOX_CHAN_VCHIQ, (unsigned int)g_slot_phys);267268vchiq_log_info(vchiq_arm_log_level,269"vchiq_init - done (slots %zx, phys %zx)",270(size_t)vchiq_slot_zero, g_slot_phys);271272vchiq_call_connected_callbacks();273274return 0;275276failed_vchiq_init:277failed_init_slots:278bus_dmamap_unload(bcm_slots_dma_tag, bcm_slots_dma_map);279failed_load:280bus_dmamem_free(bcm_slots_dma_tag, g_slot_mem, bcm_slots_dma_map);281failed_alloc:282bus_dma_tag_destroy(bcm_slots_dma_tag);283284return err;285}286287void __exit288vchiq_platform_exit(VCHIQ_STATE_T *state)289{290291bus_dmamap_unload(bcm_slots_dma_tag, bcm_slots_dma_map);292bus_dmamem_free(bcm_slots_dma_tag, g_slot_mem, bcm_slots_dma_map);293bus_dma_tag_destroy(bcm_slots_dma_tag);294}295296VCHIQ_STATUS_T297vchiq_platform_init_state(VCHIQ_STATE_T *state)298{299VCHIQ_STATUS_T status = VCHIQ_SUCCESS;300state->platform_state = kzalloc(sizeof(VCHIQ_2835_ARM_STATE_T), GFP_KERNEL);301((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited = 1;302status = vchiq_arm_init_state(state, &((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->arm_state);303if(status != VCHIQ_SUCCESS)304{305((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited = 0;306}307return status;308}309310VCHIQ_ARM_STATE_T*311vchiq_platform_get_arm_state(VCHIQ_STATE_T *state)312{313if(!((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited)314{315BUG();316}317return &((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->arm_state;318}319320VCHIQ_STATUS_T321vchiq_copy_from_user(void *dst, const void *src, int size)322{323324if (((vm_offset_t)(src)) < VM_MIN_KERNEL_ADDRESS) {325int error = copyin(src, dst, size);326return error ? VCHIQ_ERROR : VCHIQ_SUCCESS;327}328else329bcopy(src, dst, size);330331return VCHIQ_SUCCESS;332}333334VCHIQ_STATUS_T335vchiq_prepare_bulk_data(VCHIQ_BULK_T *bulk, VCHI_MEM_HANDLE_T memhandle,336void *offset, int size, int dir)337{338BULKINFO_T *bi;339int ret;340341WARN_ON(memhandle != VCHI_MEM_HANDLE_INVALID);342bi = malloc(sizeof(*bi), M_VCPAGELIST, M_WAITOK | M_ZERO);343344ret = create_pagelist((char __user *)offset, size,345(dir == VCHIQ_BULK_RECEIVE)346? PAGELIST_READ347: PAGELIST_WRITE,348current,349bi);350if (ret != 0)351return VCHIQ_ERROR;352353bulk->handle = memhandle;354bulk->data = VCHIQ_ARM_ADDRESS(bi->pagelist);355356/* Store the pagelist address in remote_data, which isn't used by the357slave. */358bulk->remote_data = bi;359360return VCHIQ_SUCCESS;361}362363void364vchiq_complete_bulk(VCHIQ_BULK_T *bulk)365{366if (bulk && bulk->remote_data && bulk->actual)367free_pagelist((BULKINFO_T *)bulk->remote_data, bulk->actual);368}369370void371vchiq_transfer_bulk(VCHIQ_BULK_T *bulk)372{373/*374* This should only be called on the master (VideoCore) side, but375* provide an implementation to avoid the need for ifdefery.376*/377BUG();378}379380void381vchiq_dump_platform_state(void *dump_context)382{383char buf[80];384int len;385len = snprintf(buf, sizeof(buf),386" Platform: 2835 (VC master)");387vchiq_dump(dump_context, buf, len + 1);388}389390VCHIQ_STATUS_T391vchiq_platform_suspend(VCHIQ_STATE_T *state)392{393return VCHIQ_ERROR;394}395396VCHIQ_STATUS_T397vchiq_platform_resume(VCHIQ_STATE_T *state)398{399return VCHIQ_SUCCESS;400}401402void403vchiq_platform_paused(VCHIQ_STATE_T *state)404{405}406407void408vchiq_platform_resumed(VCHIQ_STATE_T *state)409{410}411412int413vchiq_platform_videocore_wanted(VCHIQ_STATE_T* state)414{415return 1; // autosuspend not supported - videocore always wanted416}417418int419vchiq_platform_use_suspend_timer(void)420{421return 0;422}423void424vchiq_dump_platform_use_state(VCHIQ_STATE_T *state)425{426vchiq_log_info(vchiq_arm_log_level, "Suspend timer not in use");427}428void429vchiq_platform_handle_timeout(VCHIQ_STATE_T *state)430{431(void)state;432}433/*434* Local functions435*/436437static void438pagelist_page_free(vm_page_t pp)439{440vm_page_unwire(pp, PQ_INACTIVE);441}442443/* There is a potential problem with partial cache lines (pages?)444** at the ends of the block when reading. If the CPU accessed anything in445** the same line (page?) then it may have pulled old data into the cache,446** obscuring the new data underneath. We can solve this by transferring the447** partial cache lines separately, and allowing the ARM to copy into the448** cached area.449450** N.B. This implementation plays slightly fast and loose with the Linux451** driver programming rules, e.g. its use of __virt_to_bus instead of452** dma_map_single, but it isn't a multi-platform driver and it benefits453** from increased speed as a result.454*/455456457static int458create_pagelist(char __user *buf, size_t count, unsigned short type,459struct proc *p, BULKINFO_T *bi)460{461PAGELIST_T *pagelist;462vm_page_t* pages;463uint32_t *addrs;464unsigned int num_pages, i;465vm_offset_t offset;466int pagelist_size;467char *addr, *base_addr, *next_addr;468int run, addridx, actual_pages;469int err;470vm_paddr_t pagelist_phys;471vm_paddr_t pa;472473offset = (vm_offset_t)buf & (PAGE_SIZE - 1);474num_pages = (count + offset + PAGE_SIZE - 1) / PAGE_SIZE;475476bi->pagelist = NULL;477bi->buf = buf;478bi->size = count;479480/* Allocate enough storage to hold the page pointers and the page481** list482*/483pagelist_size = sizeof(PAGELIST_T) +484(num_pages * sizeof(unsigned long)) +485(num_pages * sizeof(pages[0]));486487err = bus_dma_tag_create(488NULL,489PAGE_SIZE, 0, /* alignment, boundary */490BUS_SPACE_MAXADDR_32BIT, /* lowaddr */491BUS_SPACE_MAXADDR, /* highaddr */492NULL, NULL, /* filter, filterarg */493pagelist_size, 1, /* maxsize, nsegments */494pagelist_size, 0, /* maxsegsize, flags */495NULL, NULL, /* lockfunc, lockarg */496&bi->pagelist_dma_tag);497498err = bus_dmamem_alloc(bi->pagelist_dma_tag, (void **)&pagelist,499BUS_DMA_COHERENT | BUS_DMA_WAITOK, &bi->pagelist_dma_map);500if (err || !pagelist) {501vchiq_log_error(vchiq_core_log_level, "Unable to allocate pagelist memory");502err = -ENOMEM;503goto failed_alloc;504}505506err = bus_dmamap_load(bi->pagelist_dma_tag, bi->pagelist_dma_map, pagelist,507pagelist_size, vchiq_dmamap_cb,508&pagelist_phys, 0);509510if (err) {511vchiq_log_error(vchiq_core_log_level, "cannot load DMA map for pagelist memory");512err = -ENOMEM;513bi->pagelist = pagelist;514goto failed_load;515}516517vchiq_log_trace(vchiq_arm_log_level,518"create_pagelist - %zx (%zu bytes @%p)", (size_t)pagelist, count, buf);519520addrs = pagelist->addrs;521pages = (vm_page_t*)(addrs + num_pages);522523actual_pages = vm_fault_quick_hold_pages(&p->p_vmspace->vm_map,524(vm_offset_t)buf, count,525(type == PAGELIST_READ ? VM_PROT_WRITE : 0 ) | VM_PROT_READ, pages, num_pages);526527if (actual_pages != num_pages) {528if (actual_pages > 0)529vm_page_unhold_pages(pages, actual_pages);530err = -ENOMEM;531bi->pagelist = pagelist;532goto failed_hold;533}534535pagelist->length = count;536pagelist->type = type;537pagelist->offset = offset;538539/* Group the pages into runs of contiguous pages */540541size_t run_ceil = g_long_bulk_space ? 0x100 : PAGE_SIZE;542unsigned int pg_addr_rshift = g_long_bulk_space ? 4 : 0;543base_addr = (void *) VM_PAGE_TO_VC_BULK_PAGE(pages[0]);544next_addr = base_addr + PAGE_SIZE;545addridx = 0;546run = 0;547#define _PG_BLOCK(base,run) \548((((size_t) (base)) >> pg_addr_rshift) & ~(run_ceil-1)) + (run)549for (i = 1; i < num_pages; i++) {550addr = (void *)VM_PAGE_TO_VC_BULK_PAGE(pages[i]);551if ((addr == next_addr) && (run < run_ceil - 1)) {552next_addr += PAGE_SIZE;553run++;554} else {555addrs[addridx++] = (uint32_t) _PG_BLOCK(base_addr,run);556base_addr = addr;557next_addr = addr + PAGE_SIZE;558run = 0;559}560}561addrs[addridx++] = _PG_BLOCK(base_addr, run);562#undef _PG_BLOCK563564/* Partial cache lines (fragments) require special measures */565if ((type == PAGELIST_READ) &&566((pagelist->offset & (g_cache_line_size - 1)) ||567((pagelist->offset + pagelist->length) &568(g_cache_line_size - 1)))) {569char *fragments;570571if (down_interruptible(&g_free_fragments_sema) != 0) {572free(pagelist, M_VCPAGELIST);573return -EINTR;574}575576WARN_ON(g_free_fragments == NULL);577578down(&g_free_fragments_mutex);579fragments = g_free_fragments;580WARN_ON(fragments == NULL);581g_free_fragments = *(char **) g_free_fragments;582up(&g_free_fragments_mutex);583pagelist->type = PAGELIST_READ_WITH_FRAGMENTS584+ (fragments - g_fragments_base)/g_fragment_size;585#if defined(__aarch64__)586bus_dmamap_sync(bcm_slots_dma_tag, bcm_slots_dma_map,587BUS_DMASYNC_PREREAD);588#endif589}590591#if defined(__aarch64__)592if(type == PAGELIST_READ) {593cpu_dcache_wbinv_range(buf, count);594} else {595cpu_dcache_wb_range(buf, count);596}597dsb(sy);598#else599pa = pmap_extract(PCPU_GET(curpmap), (vm_offset_t)buf);600dcache_wbinv_poc((vm_offset_t)buf, pa, count);601#endif602603bus_dmamap_sync(bi->pagelist_dma_tag, bi->pagelist_dma_map,604BUS_DMASYNC_PREWRITE);605606bi->pagelist = pagelist;607608return 0;609610failed_hold:611bus_dmamap_unload(bi->pagelist_dma_tag,bi->pagelist_dma_map);612failed_load:613bus_dmamem_free(bi->pagelist_dma_tag, bi->pagelist, bi->pagelist_dma_map);614failed_alloc:615bus_dma_tag_destroy(bi->pagelist_dma_tag);616617return err;618}619620static void621free_pagelist(BULKINFO_T *bi, int actual)622{623vm_page_t*pages;624unsigned int num_pages, i;625PAGELIST_T *pagelist;626627pagelist = bi->pagelist;628629vchiq_log_trace(vchiq_arm_log_level,630"free_pagelist - %zx, %d (%lu bytes @%p)",631(size_t)pagelist, (int)actual, (unsigned long)pagelist->length,632bi->buf);633634num_pages =635(pagelist->length + pagelist->offset + PAGE_SIZE - 1) /636PAGE_SIZE;637638pages = (vm_page_t*)(pagelist->addrs + num_pages);639640#if defined(__aarch64__)641/*642* On arm64, even if the user keeps their end of the bargain643* -- do NOT touch the buffers sent to VC -- but reads around the644* pagelist after the invalidation above, the arm might preemptively645* load (and validate) cache lines for areas inside the page list,646* so we must invalidate them again.647*648* The functional test does it and without this it doesn't pass.649*650* XXXMDC might it be enough to invalidate a couple of pages at651* the ends of the page list?652*/653if(pagelist->type >= PAGELIST_READ && actual > 0)654invalidate_cachelines_in_range_of_ppage_seq(pages,655pagelist->offset, actual);656#endif657658/* Deal with any partial cache lines (fragments) */659if (pagelist->type >= PAGELIST_READ_WITH_FRAGMENTS) {660char *fragments = g_fragments_base +661(pagelist->type - PAGELIST_READ_WITH_FRAGMENTS)*g_fragment_size;662int head_bytes, tail_bytes;663head_bytes = (g_cache_line_size - pagelist->offset) &664(g_cache_line_size - 1);665tail_bytes = (pagelist->offset + actual) &666(g_cache_line_size - 1);667668if ((actual >= 0) && (head_bytes != 0)) {669if (head_bytes > actual)670head_bytes = actual;671672copyout_page(pages[0],673pagelist->offset,674fragments,675head_bytes);676}677678if ((actual >= 0) && (head_bytes < actual) &&679(tail_bytes != 0)) {680681copyout_page(pages[num_pages-1],682(((vm_offset_t)bi->buf + actual) % PAGE_SIZE) - tail_bytes,683fragments + g_cache_line_size,684tail_bytes);685}686687down(&g_free_fragments_mutex);688*(char **) fragments = g_free_fragments;689g_free_fragments = fragments;690up(&g_free_fragments_mutex);691up(&g_free_fragments_sema);692}693694if (pagelist->type != PAGELIST_WRITE) {695for (i = 0; i < num_pages; i++) {696vm_page_dirty(pages[i]);697pagelist_page_free(pages[i]);698}699}700701#if defined(__aarch64__)702/* XXXMDC necessary? */703dsb(sy);704#endif705706bus_dmamap_unload(bi->pagelist_dma_tag, bi->pagelist_dma_map);707bus_dmamem_free(bi->pagelist_dma_tag, bi->pagelist, bi->pagelist_dma_map);708bus_dma_tag_destroy(bi->pagelist_dma_tag);709710free(bi, M_VCPAGELIST);711}712713714