/*-1* Copyright (c) 2013 Hans Petter Selasky. 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* 2. Redistributions in binary form must reproduce the above copyright9* notice, this list of conditions and the following disclaimer in the10* documentation and/or other materials provided with the distribution.11*12* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND13* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE14* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE15* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE16* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL17* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS18* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)19* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT20* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY21* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF22* SUCH DAMAGE.23*/2425#include <bsd_global.h>2627#if USB_HAVE_BUSDMA28static void usb_pc_common_mem_cb(struct usb_page_cache *pc,29void *vaddr, uint32_t length);30#endif3132/*------------------------------------------------------------------------*33* usbd_get_page - lookup DMA-able memory for the given offset34*35* NOTE: Only call this function when the "page_cache" structure has36* been properly initialized !37*------------------------------------------------------------------------*/38void39usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset,40struct usb_page_search *res)41{42#if USB_HAVE_BUSDMA43struct usb_page *page;4445if (pc->page_start) {4647/* Case 1 - something has been loaded into DMA */4849if (pc->buffer) {5051/* Case 1a - Kernel Virtual Address */5253res->buffer = USB_ADD_BYTES(pc->buffer, offset);54}55offset += pc->page_offset_buf;5657/* compute destination page */5859page = pc->page_start;6061if (pc->ismultiseg) {6263page += (offset / USB_PAGE_SIZE);6465offset %= USB_PAGE_SIZE;6667res->length = USB_PAGE_SIZE - offset;68res->physaddr = page->physaddr + offset;69} else {70res->length = (usb_size_t)-1;71res->physaddr = page->physaddr + offset;72}73if (!pc->buffer) {7475/* Case 1b - Non Kernel Virtual Address */7677res->buffer = USB_ADD_BYTES(page->buffer, offset);78}79return;80}81#endif82/* Case 2 - Plain PIO */8384res->buffer = USB_ADD_BYTES(pc->buffer, offset);85res->length = (usb_size_t)-1;86#if USB_HAVE_BUSDMA87res->physaddr = 0;88#endif89}9091/*------------------------------------------------------------------------*92* usbd_copy_in - copy directly to DMA-able memory93*------------------------------------------------------------------------*/94void95usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset,96const void *ptr, usb_frlength_t len)97{98struct usb_page_search buf_res;99100while (len != 0) {101102usbd_get_page(cache, offset, &buf_res);103104if (buf_res.length > len) {105buf_res.length = len;106}107memcpy(buf_res.buffer, ptr, buf_res.length);108109offset += buf_res.length;110len -= buf_res.length;111ptr = USB_ADD_BYTES(ptr, buf_res.length);112}113}114115/*------------------------------------------------------------------------*116* usbd_copy_out - copy directly from DMA-able memory117*------------------------------------------------------------------------*/118void119usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset,120void *ptr, usb_frlength_t len)121{122struct usb_page_search res;123124while (len != 0) {125126usbd_get_page(cache, offset, &res);127128if (res.length > len) {129res.length = len;130}131memcpy(ptr, res.buffer, res.length);132133offset += res.length;134len -= res.length;135ptr = USB_ADD_BYTES(ptr, res.length);136}137}138139/*------------------------------------------------------------------------*140* usbd_frame_zero - zero DMA-able memory141*------------------------------------------------------------------------*/142void143usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset,144usb_frlength_t len)145{146struct usb_page_search res;147148while (len != 0) {149150usbd_get_page(cache, offset, &res);151152if (res.length > len) {153res.length = len;154}155memset(res.buffer, 0, res.length);156157offset += res.length;158len -= res.length;159}160}161162#if USB_HAVE_BUSDMA163164/*------------------------------------------------------------------------*165* usb_pc_common_mem_cb - BUS-DMA callback function166*------------------------------------------------------------------------*/167static void168usb_pc_common_mem_cb(struct usb_page_cache *pc,169void *vaddr, uint32_t length)170{171struct usb_page *pg;172usb_size_t rem;173bus_size_t off;174bus_addr_t phys = (uintptr_t)vaddr; /* XXX */175uint32_t nseg;176177if (length == 0)178nseg = 1;179else180nseg = ((length + USB_PAGE_SIZE - 1) / USB_PAGE_SIZE);181182pg = pc->page_start;183pg->physaddr = phys & ~(USB_PAGE_SIZE - 1);184rem = phys & (USB_PAGE_SIZE - 1);185pc->page_offset_buf = rem;186pc->page_offset_end += rem;187length += rem;188189for (off = USB_PAGE_SIZE; off < length; off += USB_PAGE_SIZE) {190pg++;191pg->physaddr = (phys + off) & ~(USB_PAGE_SIZE - 1);192}193}194195/*------------------------------------------------------------------------*196* usb_pc_alloc_mem - allocate DMA'able memory197*198* Returns:199* 0: Success200* Else: Failure201*------------------------------------------------------------------------*/202uint8_t203usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg,204usb_size_t size, usb_size_t align)205{206void *ptr;207uint32_t rem;208209/* allocate zeroed memory */210211if (align != 1) {212ptr = malloc(size + align, XXX, XXX);213if (ptr == NULL)214goto error;215216rem = (-((uintptr_t)ptr)) & (align - 1);217} else {218ptr = malloc(size, XXX, XXX);219if (ptr == NULL)220goto error;221rem = 0;222}223224/* setup page cache */225pc->buffer = ((uint8_t *)ptr) + rem;226pc->page_start = pg;227pc->page_offset_buf = 0;228pc->page_offset_end = size;229pc->map = NULL;230pc->tag = ptr;231pc->ismultiseg = (align == 1);232233/* compute physical address */234usb_pc_common_mem_cb(pc, pc->buffer, size);235236usb_pc_cpu_flush(pc);237return (0);238239error:240/* reset most of the page cache */241pc->buffer = NULL;242pc->page_start = NULL;243pc->page_offset_buf = 0;244pc->page_offset_end = 0;245pc->map = NULL;246pc->tag = NULL;247return (1);248}249250/*------------------------------------------------------------------------*251* usb_pc_free_mem - free DMA memory252*253* This function is NULL safe.254*------------------------------------------------------------------------*/255void256usb_pc_free_mem(struct usb_page_cache *pc)257{258if (pc != NULL && pc->buffer != NULL) {259free(pc->tag, XXX);260pc->buffer = NULL;261}262}263264/*------------------------------------------------------------------------*265* usb_pc_load_mem - load virtual memory into DMA266*267* Return values:268* 0: Success269* Else: Error270*------------------------------------------------------------------------*/271uint8_t272usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)273{274/* setup page cache */275pc->page_offset_buf = 0;276pc->page_offset_end = size;277pc->ismultiseg = 1;278279mtx_assert(pc->tag_parent->mtx, MA_OWNED);280281if (size > 0) {282/* compute physical address */283usb_pc_common_mem_cb(pc, pc->buffer, size);284}285if (sync == 0) {286/*287* Call callback so that refcount is decremented288* properly:289*/290pc->tag_parent->dma_error = 0;291(pc->tag_parent->func) (pc->tag_parent);292}293return (0);294}295296/*------------------------------------------------------------------------*297* usb_pc_cpu_invalidate - invalidate CPU cache298*------------------------------------------------------------------------*/299void300usb_pc_cpu_invalidate(struct usb_page_cache *pc)301{302if (pc->page_offset_end == pc->page_offset_buf) {303/* nothing has been loaded into this page cache! */304return;305}306/* NOP */307}308309/*------------------------------------------------------------------------*310* usb_pc_cpu_flush - flush CPU cache311*------------------------------------------------------------------------*/312void313usb_pc_cpu_flush(struct usb_page_cache *pc)314{315if (pc->page_offset_end == pc->page_offset_buf) {316/* nothing has been loaded into this page cache! */317return;318}319/* NOP */320}321322/*------------------------------------------------------------------------*323* usb_pc_dmamap_create - create a DMA map324*325* Returns:326* 0: Success327* Else: Failure328*------------------------------------------------------------------------*/329uint8_t330usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size)331{332return (0); /* NOP, success */333}334335/*------------------------------------------------------------------------*336* usb_pc_dmamap_destroy337*338* This function is NULL safe.339*------------------------------------------------------------------------*/340void341usb_pc_dmamap_destroy(struct usb_page_cache *pc)342{343/* NOP */344}345346/*------------------------------------------------------------------------*347* usb_dma_tag_setup - initialise USB DMA tags348*------------------------------------------------------------------------*/349void350usb_dma_tag_setup(struct usb_dma_parent_tag *udpt,351struct usb_dma_tag *udt, bus_dma_tag_t dmat,352struct mtx *mtx, usb_dma_callback_t *func,353uint8_t ndmabits, uint8_t nudt)354{355memset(udpt, 0, sizeof(*udpt));356357/* sanity checking */358if ((nudt == 0) ||359(ndmabits == 0) ||360(mtx == NULL)) {361/* something is corrupt */362return;363}364/* initialise condition variable */365cv_init(udpt->cv, "USB DMA CV");366367/* store some information */368udpt->mtx = mtx;369udpt->func = func;370udpt->tag = dmat;371udpt->utag_first = udt;372udpt->utag_max = nudt;373udpt->dma_bits = ndmabits;374375while (nudt--) {376memset(udt, 0, sizeof(*udt));377udt->tag_parent = udpt;378udt++;379}380}381382/*------------------------------------------------------------------------*383* usb_bus_tag_unsetup - factored out code384*------------------------------------------------------------------------*/385void386usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt)387{388struct usb_dma_tag *udt;389uint8_t nudt;390391udt = udpt->utag_first;392nudt = udpt->utag_max;393394while (nudt--) {395udt->align = 0;396udt++;397}398399if (udpt->utag_max) {400/* destroy the condition variable */401cv_destroy(udpt->cv);402}403}404405/*------------------------------------------------------------------------*406* usb_bdma_work_loop407*408* This function handles loading of virtual buffers into DMA and is409* only called when "dma_refcount" is zero.410*------------------------------------------------------------------------*/411void412usb_bdma_work_loop(struct usb_xfer_queue *pq)413{414struct usb_xfer_root *info;415struct usb_xfer *xfer;416usb_frcount_t nframes;417418xfer = pq->curr;419info = xfer->xroot;420421mtx_assert(info->xfer_mtx, MA_OWNED);422423if (xfer->error) {424/* some error happened */425USB_BUS_LOCK(info->bus);426usbd_transfer_done(xfer, 0);427USB_BUS_UNLOCK(info->bus);428return;429}430if (!xfer->flags_int.bdma_setup) {431struct usb_page *pg;432usb_frlength_t frlength_0;433uint8_t isread;434435xfer->flags_int.bdma_setup = 1;436437/* reset BUS-DMA load state */438439info->dma_error = 0;440441if (xfer->flags_int.isochronous_xfr) {442/* only one frame buffer */443nframes = 1;444frlength_0 = xfer->sumlen;445} else {446/* can be multiple frame buffers */447nframes = xfer->nframes;448frlength_0 = xfer->frlengths[0];449}450451/*452* Set DMA direction first. This is needed to453* select the correct cache invalidate and cache454* flush operations.455*/456isread = USB_GET_DATA_ISREAD(xfer);457pg = xfer->dma_page_ptr;458459if (xfer->flags_int.control_xfr &&460xfer->flags_int.control_hdr) {461/* special case */462if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {463/* The device controller writes to memory */464xfer->frbuffers[0].isread = 1;465} else {466/* The host controller reads from memory */467xfer->frbuffers[0].isread = 0;468}469} else {470/* default case */471xfer->frbuffers[0].isread = isread;472}473474/*475* Setup the "page_start" pointer which points to an array of476* USB pages where information about the physical address of a477* page will be stored. Also initialise the "isread" field of478* the USB page caches.479*/480xfer->frbuffers[0].page_start = pg;481482info->dma_nframes = nframes;483info->dma_currframe = 0;484info->dma_frlength_0 = frlength_0;485486pg += (frlength_0 / USB_PAGE_SIZE);487pg += 2;488489while (--nframes > 0) {490xfer->frbuffers[nframes].isread = isread;491xfer->frbuffers[nframes].page_start = pg;492493pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE);494pg += 2;495}496497}498if (info->dma_error) {499USB_BUS_LOCK(info->bus);500usbd_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED);501USB_BUS_UNLOCK(info->bus);502return;503}504if (info->dma_currframe != info->dma_nframes) {505506if (info->dma_currframe == 0) {507/* special case */508usb_pc_load_mem(xfer->frbuffers,509info->dma_frlength_0, 0);510} else {511/* default case */512nframes = info->dma_currframe;513usb_pc_load_mem(xfer->frbuffers + nframes,514xfer->frlengths[nframes], 0);515}516517/* advance frame index */518info->dma_currframe++;519520return;521}522/* go ahead */523usb_bdma_pre_sync(xfer);524525/* start loading next USB transfer, if any */526usb_command_wrapper(pq, NULL);527528/* finally start the hardware */529usbd_pipe_enter(xfer);530}531532/*------------------------------------------------------------------------*533* usb_bdma_done_event534*535* This function is called when the BUS-DMA has loaded virtual memory536* into DMA, if any.537*------------------------------------------------------------------------*/538void539usb_bdma_done_event(struct usb_dma_parent_tag *udpt)540{541struct usb_xfer_root *info;542543info = USB_DMATAG_TO_XROOT(udpt);544545mtx_assert(info->xfer_mtx, MA_OWNED);546547/* copy error */548info->dma_error = udpt->dma_error;549550/* enter workloop again */551usb_command_wrapper(&info->dma_q,552info->dma_q.curr);553}554555/*------------------------------------------------------------------------*556* usb_bdma_pre_sync557*558* This function handles DMA synchronisation that must be done before559* an USB transfer is started.560*------------------------------------------------------------------------*/561void562usb_bdma_pre_sync(struct usb_xfer *xfer)563{564struct usb_page_cache *pc;565usb_frcount_t nframes;566567if (xfer->flags_int.isochronous_xfr) {568/* only one frame buffer */569nframes = 1;570} else {571/* can be multiple frame buffers */572nframes = xfer->nframes;573}574575pc = xfer->frbuffers;576577while (nframes--) {578579if (pc->isread) {580usb_pc_cpu_invalidate(pc);581} else {582usb_pc_cpu_flush(pc);583}584pc++;585}586}587588/*------------------------------------------------------------------------*589* usb_bdma_post_sync590*591* This function handles DMA synchronisation that must be done after592* an USB transfer is complete.593*------------------------------------------------------------------------*/594void595usb_bdma_post_sync(struct usb_xfer *xfer)596{597struct usb_page_cache *pc;598usb_frcount_t nframes;599600if (xfer->flags_int.isochronous_xfr) {601/* only one frame buffer */602nframes = 1;603} else {604/* can be multiple frame buffers */605nframes = xfer->nframes;606}607608pc = xfer->frbuffers;609610while (nframes--) {611if (pc->isread) {612usb_pc_cpu_invalidate(pc);613}614pc++;615}616}617#endif618619620