diff options
Diffstat (limited to 'sys/dev/usb2/core')
46 files changed, 25224 insertions, 0 deletions
diff --git a/sys/dev/usb2/core/README.TXT b/sys/dev/usb2/core/README.TXT new file mode 100644 index 000000000000..736723049424 --- /dev/null +++ b/sys/dev/usb2/core/README.TXT @@ -0,0 +1,411 @@ + +$FreeBSD$ + +DESCRIPTION OF THE NEW USB API + +The new USB 2.0 API consists of 5 functions. All transfer types are +managed using these functions. There is no longer need for separate +functions to setup INTERRUPT- and ISOCHRONOUS- transfers. + ++--------------------------------------------------------------+ +| | +| "usb2_transfer_setup" - This function will allocate all | +| necessary DMA memory and might | +| sleep! | +| | +| "usb2_transfer_unsetup" - This function will stop the USB | +| transfer, if it is currently | +| active, release all DMA | +| memory and might sleep! | +| | +| "usb2_transfer_start" - This function will start an USB | +| transfer, if not already started.| +| This function is always | +| non-blocking. ** | +| | +| "usb2_transfer_stop" - This function will stop an USB | +| transfer, if not already stopped.| +| The callback function will be | +| called before this function | +| returns. This function is always | +| non-blocking. ** | +| | +| "usb2_transfer_drain" - This function will stop an USB | +| transfer, if not already stopped | +| and wait for any additional | +| DMA load operations to complete. | +| Buffers that are loaded into DMA | +| using "usb2_set_frame_data" can | +| safely be freed after that | +| this function has returned. This | +| function can block the caller. | +| | +| ** These functions must be called with the private driver's | +| lock locked. | +| | +| NOTE: These USB API functions are NULL safe, with regard | +| to the USB transfer structure pointer. | ++--------------------------------------------------------------+ + +Reference: /sys/dev/usb2/core/usb2_transfer.c + +/* + * A simple USB callback state-machine: + * + * +->-----------------------+ + * | | + * +-<-+-------[tr_setup]--------+-<-+-<-[start/restart] + * | | + * | | + * | | + * +------>-[tr_transferred]---------+ + * | | + * +--------->-[tr_error]------------+ + */ + +void +usb2_default_callback(struct usb2_xfer *xfer) +{ + /* + * NOTE: it is not allowed to return + * before "USB_CHECK_STATUS()", + * even if the system is tearing down! + */ + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + /* + * Setup xfer->frlengths[], xfer->nframes + * and write data to xfer->frbuffers[], if any + */ + + /**/ + usb2_start_hardware(xfer); + return; + + case USB_ST_TRANSFERRED: + /* + * Read data from xfer->frbuffers[], if any. + * "xfer->frlengths[]" should now have been + * updated to the actual length. + */ + return; + + default: /* Error */ + /* print error message and clear stall for example */ + return; + } +} + +=== Notes for USB control transfers === + +An USB control transfer has three parts. First the SETUP packet, then +DATA packet(s) and then a STATUS packet. The SETUP packet is always +pointed to by "xfer->frbuffers[0]" and the length is stored in +"xfer->frlengths[0]" also if there should not be sent any SETUP +packet! If an USB control transfer has no DATA stage, then +"xfer->nframes" should be set to 1. Else the default value is +"xfer->nframes" equal to 2. + +Example1: SETUP + STATUS + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +Example2: SETUP + DATA + STATUS + xfer->nframes = 2; + xfer->frlenghts[0] = 8; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example3: SETUP + DATA + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +2nd callback: + /* IMPORTANT: frbuffer[0] must still point at the setup packet! */ + xfer->nframes = 2; + xfer->frlenghts[0] = 0; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example4: SETUP + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + xfer->flags.manual_status = 1; + usb2_start_hardware(xfer); + +2nd callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 0; + xfer->flags.manual_status = 0; + usb2_start_hardware(xfer); + + +=== General USB transfer notes === + + 1) Something that one should be aware of is that all USB callbacks support +recursation. That means one can start/stop whatever transfer from the callback +of another transfer one desires. Also the transfer that is currently called +back. Recursion is handled like this that when the callback that wants to +recurse returns it is called one more time. + + 2) After that the "usb2_start_hardware()" function has been called in +the callback one can always depend on that "tr_error" or "tr_transferred" +will get jumped afterwards. Always! + + 3) Sleeping functions can only be called from the attach routine of the +driver. Else one should not use sleeping functions unless one has to. It is +very difficult with sleep, because one has to think that the device might have +detached when the thread returns from sleep. + + 4) Polling. + + use_polling + This flag can be used with any callback and will cause the + "usb2_transfer_start()" function to wait using "DELAY()", + without exiting any mutexes, until the transfer is finished or + has timed out. This flag can be changed during operation. + + NOTE: If polling is used the "timeout" field should be non-zero! + NOTE: USB_ERR_CANCELLED is returned in case of timeout + instead of USB_ERR_TIMEOUT! + + + +USB device driver examples: + +/sys/dev/usb2/ethernet/if_axe.c +/sys/dev/usb2/ethernet/if_aue.c + +QUICK REFERENCE +=============== + + +/*------------------------------------------------------------------------* + * usb2_error_t + * usb2_transfer_setup(udev, ifaces, pxfer, setup_start, + * n_setup, priv_sc, priv_mtx) + *------------------------------------------------------------------------*/ + +- "udev" is a pointer to "struct usb2_device". + +- "ifaces" array of interface index numbers to use. See "if_index". + +- "pxfer" is a pointer to an array of USB transfer pointers that are + initialized to NULL, and then pointed to allocated USB transfers. + +- "setup_start" is a pointer to an array of USB config structures. + +- "n_setup" is a number telling the USB system how many USB transfers + should be setup. + +- "priv_sc" is the private softc pointer, which will be used to + initialize "xfer->priv_sc". + +- "priv_mtx" is the private mutex protecting the transfer structure and + the softc. This pointer is used to initialize "xfer->priv_mtx". + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_unsetup(pxfer, n_setup) + *------------------------------------------------------------------------*/ + +- "pxfer" is a pointer to an array of USB transfer pointers, that may + be NULL, that should be freed by the USB system. + +- "n_setup" is a number telling the USB system how many USB transfers + should be unsetup + +NOTE: This function can sleep, waiting for active mutexes to become unlocked! +NOTE: It is not allowed to call "usb2_transfer_unsetup" from the callback + of a USB transfer. + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_start(xfer) + *------------------------------------------------------------------------*/ + +- "xfer" is pointer to a USB transfer that should be started + +NOTE: this function must be called with "priv_mtx" locked + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_stop(xfer) + *------------------------------------------------------------------------*/ + +- "xfer" is a pointer to a USB transfer that should be stopped + +NOTE: this function must be called with "priv_mtx" locked + +NOTE: if the transfer was in progress, the callback will called with + "xfer->error=USB_ERR_CANCELLED", before this function returns + +/*------------------------------------------------------------------------* + * struct usb2_config { + * type, endpoint, direction, interval, timeout, frames, index + * flags, bufsize, callback + * }; + *------------------------------------------------------------------------*/ + +- The "type" field selects the USB pipe type. Valid values are: + UE_INTERRUPT, UE_CONTROL, UE_BULK, UE_ISOCHRONOUS. The special + value UE_BULK_INTR will select BULK and INTERRUPT pipes. + This field is mandatory. + +- The "endpoint" field selects the USB endpoint number. A value of + 0xFF, "-1" or "UE_ADDR_ANY" will select the first matching endpoint. + This field is mandatory. + +- The "direction" field selects the USB endpoint direction. A value of + "UE_DIR_ANY" will select the first matching endpoint. Else valid + values are: "UE_DIR_IN" and "UE_DIR_OUT". "UE_DIR_IN" and + "UE_DIR_OUT" can be binary ORed by "UE_DIR_SID" which means that the + direction will be swapped in case of USB_MODE_DEVICE. Note that + "UE_DIR_IN" refers to the data transfer direction of the "IN" tokens + and "UE_DIR_OUT" refers to the data transfer direction of the "OUT" + tokens. This field is mandatory. + +- The "interval" field selects the interrupt interval. The value of this + field is given in milliseconds and is independent of device speed. Depending + on the endpoint type, this field has different meaning: + + UE_INTERRUPT) + "0" use the default interrupt interval based on endpoint descriptor. + "Else" use the given value for polling rate. + + UE_ISOCHRONOUS) + "0" use default. + "Else" the value is ignored. + + UE_BULK) + UE_CONTROL) + "0" no transfer pre-delay. + "Else" a delay as given by this field in milliseconds is + inserted before the hardware is started when + "usb2_start_hardware()" is called. + NOTE: The transfer timeout, if any, is started after that + the pre-delay has elapsed! + +- The "timeout" field, if non-zero, will set the transfer timeout in + milliseconds. If the "timeout" field is zero and the transfer type + is ISOCHRONOUS a timeout of 250ms will be used. + +- The "frames" field sets the maximum number of frames. If zero is + specified it will yield the following results: + + UE_BULK) + UE_INTERRUPT) + xfer->nframes = 1; + + UE_CONTROL) + xfer->nframes = 2; + + UE_ISOCHRONOUS) + Not allowed. Will cause an error. + +- The "ep_index" field allows you to give a number, in case more + endpoints match the description, that selects which matching + "ep_index" should be used. + +- The "if_index" field allows you to select which of the interface + numbers in the "ifaces" array parameter passed to "usb2_transfer_setup" + that should be used when setting up the given USB transfer. + +- The "flags" field has type "struct usb2_xfer_flags" and allows one + to set initial flags an USB transfer. Valid flags are: + + force_short_xfer + This flag forces the last transmitted USB packet to be short. + A short packet has a length of less than "xfer->max_packet_size", + which derives from "wMaxPacketSize". This flag can be changed + during operation. + + short_xfer_ok + This flag allows the received transfer length, "xfer->actlen" + to be less than "xfer->sumlen" upon completion of a transfer. + This flag can be changed during operation. + + pipe_bof + This flag causes a failing USB transfer to remain first + in the PIPE queue except in the case of "xfer->error" equal + to "USB_ERR_CANCELLED". No other USB transfers in the affected + PIPE queue will be started until either: + + 1) The failing USB transfer is stopped using "usb2_transfer_stop()". + 2) The failing USB transfer performs a successful transfer. + + The purpose of this flag is to avoid races when multiple + transfers are queued for execution on an USB endpoint, and the + first executing transfer fails leading to the need for + clearing of stall for example. In this case this flag is used + to prevent the following USB transfers from being executed at + the same time the clear-stall command is executed on the USB + control endpoint. This flag can be changed during operation. + + "BOF" is short for "Block On Failure" + + NOTE: This flag should be set on all BULK and INTERRUPT + USB transfers which use an endpoint that can be shared + between userland and kernel. + + proxy_buffer + Setting this flag will cause that the total buffer size will + be rounded up to the nearest atomic hardware transfer + size. The maximum data length of any USB transfer is always + stored in the "xfer->max_data_length". For control transfers + the USB kernel will allocate additional space for the 8-bytes + of SETUP header. These 8-bytes are not counted by the + "xfer->max_data_length" variable. This flag can not be changed + during operation. + + ext_buffer + Setting this flag will cause that no data buffer will be + allocated. Instead the USB client must supply a data buffer. + This flag can not be changed during operation. + + manual_status + Setting this flag prevents an USB STATUS stage to be appended + to the end of the USB control transfer. If no control data is + transferred this flag must be cleared. Else an error will be + returned to the USB callback. This flag is mostly useful for + the USB device side. This flag can be changed during + operation. + + no_pipe_ok + Setting this flag causes the USB_ERR_NO_PIPE error to be + ignored. This flag can not be changed during operation. + + stall_pipe + Setting this flag will cause STALL pids to be sent to the + endpoint belonging to this transfer before the transfer is + started. The transfer is started at the moment the host issues + a clear-stall command on the STALL'ed endpoint. This flag can + be changed during operation. This flag does only have effect + in USB device side mode except for control endpoints. This + flag is cleared when the stall command has been executed. This + flag can only be changed outside the callback function by + using the functions "usb2_transfer_set_stall()" and + "usb2_transfer_clear_stall()" ! + +- The "bufsize" field sets the total buffer size in bytes. If + this field is zero, "wMaxPacketSize" will be used, multiplied by the + "frames" field if the transfer type is ISOCHRONOUS. This is useful for + setting up interrupt pipes. This field is mandatory. + + NOTE: For control transfers "bufsize" includes + the length of the request structure. + +- The "callback" pointer sets the USB callback. This field is mandatory. + +MUTEX NOTE: +=========== + +When you create a mutex using "mtx_init()", don't forget to call +"mtx_destroy()" at detach, else you can get "freed memory accessed" +panics. + +--HPS diff --git a/sys/dev/usb2/core/usb2_busdma.c b/sys/dev/usb2/core/usb2_busdma.c new file mode 100644 index 000000000000..1b8c4a1fc3ee --- /dev/null +++ b/sys/dev/usb2/core/usb2_busdma.c @@ -0,0 +1,1401 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +static void usb2_dma_tag_create(struct usb2_dma_tag *udt, uint32_t size, uint32_t align); +static void usb2_dma_tag_destroy(struct usb2_dma_tag *udt); + +#ifdef __FreeBSD__ +static void usb2_dma_lock_cb(void *arg, bus_dma_lock_op_t op); +static int32_t usb2_m_copy_in_cb(void *arg, void *src, uint32_t count); +static void usb2_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static void usb2_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static void usb2_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error, uint8_t isload); + +#endif + +#ifdef __NetBSD__ +static int32_t usb2_m_copy_in_cb(void *arg, caddr_t src, uint32_t count); +static void usb2_pc_common_mem_cb(struct usb2_page_cache *pc, bus_dma_segment_t *segs, int nseg, int error, uint8_t isload); + +#endif + +/*------------------------------------------------------------------------* + * usb2_get_page - lookup DMA-able memory for the given offset + * + * NOTE: Only call this function when the "page_cache" structure has + * been properly initialized ! + *------------------------------------------------------------------------*/ +void +usb2_get_page(struct usb2_page_cache *pc, uint32_t offset, + struct usb2_page_search *res) +{ + struct usb2_page *page; + + if (pc->page_start) { + + /* Case 1 - something has been loaded into DMA */ + + if (pc->buffer) { + + /* Case 1a - Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + } + offset += pc->page_offset_buf; + + /* compute destination page */ + + page = pc->page_start; + + if (pc->ismultiseg) { + + page += (offset / USB_PAGE_SIZE); + + offset %= USB_PAGE_SIZE; + + res->length = USB_PAGE_SIZE - offset; + res->physaddr = page->physaddr + offset; + } else { + res->length = 0 - 1; + res->physaddr = page->physaddr + offset; + } + if (!pc->buffer) { + + /* Case 1b - Non Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(page->buffer, offset); + } + } else { + + /* Case 2 - Plain PIO */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + res->length = 0 - 1; + res->physaddr = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_in - copy directly to DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_copy_in(struct usb2_page_cache *cache, uint32_t offset, + const void *ptr, uint32_t len) +{ + struct usb2_page_search buf_res; + + while (len != 0) { + + usb2_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + bcopy(ptr, buf_res.buffer, buf_res.length); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_in_user - copy directly to DMA-able memory from userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_copy_in_user(struct usb2_page_cache *cache, uint32_t offset, + const void *ptr, uint32_t len) +{ + struct usb2_page_search buf_res; + int error; + + while (len != 0) { + + usb2_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + error = copyin(ptr, buf_res.buffer, buf_res.length); + if (error) + return (error); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_m_copy_in - copy a mbuf chain directly into DMA-able memory + *------------------------------------------------------------------------*/ +struct usb2_m_copy_in_arg { + struct usb2_page_cache *cache; + uint32_t dst_offset; +}; + +static int32_t +#ifdef __FreeBSD__ +usb2_m_copy_in_cb(void *arg, void *src, uint32_t count) +#else +usb2_m_copy_in_cb(void *arg, caddr_t src, uint32_t count) +#endif +{ + register struct usb2_m_copy_in_arg *ua = arg; + + usb2_copy_in(ua->cache, ua->dst_offset, src, count); + ua->dst_offset += count; + return (0); +} + +void +usb2_m_copy_in(struct usb2_page_cache *cache, uint32_t dst_offset, + struct mbuf *m, uint32_t src_offset, uint32_t src_len) +{ + struct usb2_m_copy_in_arg arg = {cache, dst_offset}; + register int error; + + error = m_apply(m, src_offset, src_len, &usb2_m_copy_in_cb, &arg); + return; +} + +/*------------------------------------------------------------------------* + * usb2_uiomove - factored out code + *------------------------------------------------------------------------*/ +int +usb2_uiomove(struct usb2_page_cache *pc, struct uio *uio, + uint32_t pc_offset, uint32_t len) +{ + struct usb2_page_search res; + int error = 0; + + while (len != 0) { + + usb2_get_page(pc, pc_offset, &res); + + if (res.length > len) { + res.length = len; + } + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = uiomove(res.buffer, res.length, uio); + + if (error) { + break; + } + pc_offset += res.length; + len -= res.length; + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb2_copy_out - copy directly from DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_copy_out(struct usb2_page_cache *cache, uint32_t offset, + void *ptr, uint32_t len) +{ + struct usb2_page_search res; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bcopy(res.buffer, ptr, res.length); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_out_user - copy directly from DMA-able memory to userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_copy_out_user(struct usb2_page_cache *cache, uint32_t offset, + void *ptr, uint32_t len) +{ + struct usb2_page_search res; + int error; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + error = copyout(res.buffer, ptr, res.length); + if (error) + return (error); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_bzero - zero DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_bzero(struct usb2_page_cache *cache, uint32_t offset, uint32_t len) +{ + struct usb2_page_search res; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bzero(res.buffer, res.length); + + offset += res.length; + len -= res.length; + } + return; +} + + +#ifdef __FreeBSD__ + +/*------------------------------------------------------------------------* + * usb2_dma_lock_cb - dummy callback + *------------------------------------------------------------------------*/ +static void +usb2_dma_lock_cb(void *arg, bus_dma_lock_op_t op) +{ + /* we use "mtx_owned()" instead of this function */ + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_create(struct usb2_dma_tag *udt, + uint32_t size, uint32_t align) +{ + bus_dma_tag_t tag; + + if (bus_dma_tag_create + ( /* parent */ udt->tag_parent->tag, + /* alignment */ align, + /* boundary */ USB_PAGE_SIZE, + /* lowaddr */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1, + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, + /* filterarg */ NULL, + /* maxsize */ size, + /* nsegments */ (align == 1) ? + (2 + (size / USB_PAGE_SIZE)) : 1, + /* maxsegsz */ (align == 1) ? + USB_PAGE_SIZE : size, + /* flags */ 0, + /* lockfn */ &usb2_dma_lock_cb, + /* lockarg */ NULL, + &tag)) { + tag = NULL; + } + udt->tag = tag; + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_destroy(struct usb2_dma_tag *udt) +{ + bus_dma_tag_destroy(udt->tag); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb2_pc_common_mem_cb(arg, segs, nseg, error, 0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb2_pc_common_mem_cb(arg, segs, nseg, error, 1); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_page_cache *pc; + struct usb2_page *pg; + uint32_t rem; + uint8_t owned; + + pc = arg; + uptag = pc->tag_parent; + + /* + * XXX There is sometimes recursive locking here. + * XXX We should try to find a better solution. + * XXX Until further the "owned" variable does + * XXX the trick. + */ + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + nseg--; + + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + +done: + owned = mtx_owned(uptag->mtx); + if (!owned) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } else { + usb2_cv_broadcast(uptag->cv); + } + if (!owned) + mtx_unlock(uptag->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, + uint32_t size, uint32_t align) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_dma_tag *utag; + bus_dmamap_t map; + void *ptr; + int err; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto error; + } + } +#if 1 + /* + * XXX BUS-DMA workaround - FIXME later: + * + * We assume that that the aligment at this point of + * the code is greater than or equal to the size and + * less than two times the size, so that if we double + * the size, the size will be greater than the + * alignment. + * + * The bus-dma system has a check for "alignment" + * being less than "size". If that check fails we end + * up using contigmalloc which is page based even for + * small allocations. Try to avoid that to save + * memory, hence we sometimes to a large number of + * small allocations! + */ + if (size <= (USB_PAGE_SIZE / 2)) { + size *= 2; + } +#endif + } + /* get the correct DMA tag */ + utag = usb2_dma_tag_find(uptag, size, align); + if (utag == NULL) { + goto error; + } + /* allocate memory */ + if (bus_dmamem_alloc( + utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) { + goto error; + } + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + mtx_lock(uptag->mtx); + + /* load memory into DMA */ + err = bus_dmamap_load( + utag->tag, map, ptr, size, &usb2_pc_alloc_mem_cb, + pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT)); + + if (err == EINPROGRESS) { + usb2_cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + mtx_unlock(uptag->mtx); + + if (err || uptag->dma_error) { + bus_dmamem_free(utag->tag, ptr, map); + goto error; + } + bzero(ptr, size); + + usb2_pc_cpu_flush(pc); + + return (0); + +error: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_free_mem(struct usb2_page_cache *pc) +{ + if (pc && pc->buffer) { + + bus_dmamap_unload(pc->tag, pc->map); + + bus_dmamem_free(pc->tag, pc->buffer, pc->map); + + pc->buffer = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync) +{ + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + mtx_assert(pc->tag_parent->mtx, MA_OWNED); + + if (size > 0) { + if (sync) { + struct usb2_dma_parent_tag *uptag; + int err; + + uptag = pc->tag_parent; + + /* + * Try to load memory into DMA. + */ + err = bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb2_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK); + if (err == EINPROGRESS) { + usb2_cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + if (err || uptag->dma_error) { + return (1); + } + } else { + + /* + * Try to load memory into DMA. The callback + * will be called in all cases: + */ + if (bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb2_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) { + } + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_invalidate(struct usb2_page_cache *pc) +{ + bus_dmamap_sync(pc->tag, pc->map, + BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_flush(struct usb2_page_cache *pc) +{ + bus_dmamap_sync(pc->tag, pc->map, + BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size) +{ + struct usb2_xfer_root *info; + struct usb2_dma_tag *utag; + + /* get info */ + info = pc->tag_parent->info; + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb2_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + /* create DMA map */ + if (bus_dmamap_create(utag->tag, 0, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_dmamap_destroy(struct usb2_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } + return; +} + +#endif + +#ifdef __NetBSD__ + +/*------------------------------------------------------------------------* + * usb2_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_create(struct usb2_dma_tag *udt, + uint32_t size, uint32_t align) +{ + uint32_t nseg; + + if (align == 1) { + nseg = (2 + (size / USB_PAGE_SIZE)); + } else { + nseg = 1; + } + + udt->p_seg = malloc(nseg * sizeof(*(udt->p_seg)), + M_USB, M_WAITOK | M_ZERO); + + if (udt->p_seg == NULL) { + return; + } + udt->tag = udt->tag_parent->tag; + udt->n_seg = nseg; + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_destroy(struct usb2_dma_tag *udt) +{ + free(udt->p_seg, M_USB); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_common_mem_cb(struct usb2_page_cache *pc, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload, uint8_t dolock) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_page *pg; + uint32_t rem; + uint8_t ext_seg; /* extend last segment */ + + uptag = pc->tag_parent; + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + if (nseg < ((pc->page_offset_end + + (USB_PAGE_SIZE - 1)) / USB_PAGE_SIZE)) { + ext_seg = 1; + } else { + ext_seg = 0; + } + nseg--; + + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + + /* + * XXX The segments we get from BUS-DMA are not aligned, + * XXX so we need to extend the last segment if we are + * XXX unaligned and cross the segment boundary! + */ + if (ext_seg && pc->ismultiseg) { + (pg + 1)->physaddr = pg->physaddr + USB_PAGE_SIZE; + } +done: + if (dolock) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } + if (dolock) + mtx_unlock(uptag->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, + uint32_t size, uint32_t align) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_dma_tag *utag; + caddr_t ptr = NULL; + bus_dmamap_t map; + int seg_count; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto done_5; + } + } + } + /* get the correct DMA tag */ + utag = usb2_dma_tag_find(pc->tag_parent, size, align); + if (utag == NULL) { + goto done_5; + } + if (bus_dmamem_alloc(utag->tag, size, align, 0, utag->p_seg, + utag->n_seg, &seg_count, BUS_DMA_WAITOK)) { + goto done_4; + } + if (bus_dmamem_map(utag->tag, utag->p_seg, seg_count, size, + &ptr, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) { + goto done_3; + } + if (bus_dmamap_create(utag->tag, size, utag->n_seg, (align == 1) ? + USB_PAGE_SIZE : size, 0, BUS_DMA_WAITOK, &map)) { + goto done_2; + } + if (bus_dmamap_load(utag->tag, map, ptr, size, NULL, + BUS_DMA_WAITOK)) { + goto done_1; + } + pc->p_seg = malloc(seg_count * sizeof(*(pc->p_seg)), + M_USB, M_WAITOK | M_ZERO); + if (pc->p_seg == NULL) { + goto done_0; + } + /* store number if actual segments used */ + pc->n_seg = seg_count; + + /* make a copy of the segments */ + bcopy(utag->p_seg, pc->p_seg, + seg_count * sizeof(*(pc->p_seg))); + + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + usb2_pc_common_mem_cb(pc, utag->p_seg, seg_count, 0, 0, 1); + + bzero(ptr, size); + + usb2_pc_cpu_flush(pc); + + return (0); + +done_0: + bus_dmamap_unload(utag->tag, map); +done_1: + bus_dmamap_destroy(utag->tag, map); +done_2: + bus_dmamem_unmap(utag->tag, ptr, size); +done_3: + bus_dmamem_free(utag->tag, utag->p_seg, seg_count); +done_4: + /* utag is destroyed later */ +done_5: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + pc->n_seg = 0; + pc->p_seg = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_free_mem(struct usb2_page_cache *pc) +{ + if (pc && pc->buffer) { + bus_dmamap_unload(pc->tag, pc->map); + bus_dmamap_destroy(pc->tag, pc->map); + bus_dmamem_unmap(pc->tag, pc->buffer, + pc->page_offset_end - pc->page_offset_buf); + bus_dmamem_free(pc->tag, pc->p_seg, pc->n_seg); + free(pc->p_seg, M_USB); + pc->buffer = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync) +{ + int error; + + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + if (size > 0) { + + /* try to load memory into DMA using using no wait option */ + if (bus_dmamap_load(pc->tag, pc->map, pc->buffer, + size, NULL, BUS_DMA_NOWAIT)) { + error = ENOMEM; + } else { + error = 0; + } + + usb2_pc_common_mem_cb(pc, pc->map->dm_segs, + pc->map->dm_nsegs, error, !sync); + + if (error) { + return (1); + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_invalidate(struct usb2_page_cache *pc) +{ + uint32_t len; + + len = pc->page_offset_end - pc->page_offset_buf; + + bus_dmamap_sync(pc->tag, pc->map, 0, len, + BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_flush(struct usb2_page_cache *pc) +{ + uint32_t len; + + len = pc->page_offset_end - pc->page_offset_buf; + + bus_dmamap_sync(pc->tag, pc->map, 0, len, + BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size) +{ + struct usb2_xfer_root *info; + struct usb2_dma_tag *utag; + + /* get info */ + info = pc->tag_parent->info; + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb2_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + if (bus_dmamap_create(utag->tag, size, utag->n_seg, + USB_PAGE_SIZE, 0, BUS_DMA_WAITOK, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + pc->p_seg = utag->p_seg; + pc->n_seg = utag->n_seg; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + pc->p_seg = NULL; + pc->n_seg = 0; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_dmamap_destroy(struct usb2_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } + return; +} + +#endif + +/*------------------------------------------------------------------------* + * usb2_dma_tag_find - factored out code + *------------------------------------------------------------------------*/ +struct usb2_dma_tag * +usb2_dma_tag_find(struct usb2_dma_parent_tag *udpt, + uint32_t size, uint32_t align) +{ + struct usb2_dma_tag *udt; + uint8_t nudt; + + USB_ASSERT(align > 0, ("Invalid parameter align = 0!\n")); + USB_ASSERT(size > 0, ("Invalid parameter size = 0!\n")); + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align == 0) { + usb2_dma_tag_create(udt, size, align); + if (udt->tag == NULL) { + return (NULL); + } + udt->align = align; + udt->size = size; + return (udt); + } + if ((udt->align == align) && (udt->size == size)) { + return (udt); + } + udt++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_setup - initialise USB DMA tags + *------------------------------------------------------------------------*/ +void +usb2_dma_tag_setup(struct usb2_dma_parent_tag *udpt, + struct usb2_dma_tag *udt, bus_dma_tag_t dmat, + struct mtx *mtx, usb2_dma_callback_t *func, + struct usb2_xfer_root *info, uint8_t ndmabits, + uint8_t nudt) +{ + bzero(udpt, sizeof(*udpt)); + + /* sanity checking */ + if ((nudt == 0) || + (ndmabits == 0) || + (mtx == NULL)) { + /* something is corrupt */ + return; + } +#ifdef __FreeBSD__ + /* initialise condition variable */ + usb2_cv_init(udpt->cv, "USB DMA CV"); +#endif + + /* store some information */ + udpt->mtx = mtx; + udpt->info = info; + udpt->func = func; + udpt->tag = dmat; + udpt->utag_first = udt; + udpt->utag_max = nudt; + udpt->dma_bits = ndmabits; + + while (nudt--) { + bzero(udt, sizeof(*udt)); + udt->tag_parent = udpt; + udt++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_tag_unsetup - factored out code + *------------------------------------------------------------------------*/ +void +usb2_dma_tag_unsetup(struct usb2_dma_parent_tag *udpt) +{ + struct usb2_dma_tag *udt; + uint8_t nudt; + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align) { + /* destroy the USB DMA tag */ + usb2_dma_tag_destroy(udt); + udt->align = 0; + } + udt++; + } + + if (udpt->utag_max) { +#ifdef __FreeBSD__ + /* destroy the condition variable */ + usb2_cv_destroy(udpt->cv); +#endif + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_work_loop + * + * This function handles loading of virtual buffers into DMA and is + * only called when "dma_refcount" is zero. + *------------------------------------------------------------------------*/ +void +usb2_bdma_work_loop(struct usb2_xfer_queue *pq) +{ + struct usb2_xfer_root *info; + struct usb2_xfer *xfer; + uint32_t nframes; + + xfer = pq->curr; + info = xfer->usb2_root; + + mtx_assert(info->priv_mtx, MA_OWNED); + + if (xfer->error) { + /* some error happened */ + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, 0); + mtx_unlock(xfer->usb2_mtx); + return; + } + if (!xfer->flags_int.bdma_setup) { + struct usb2_page *pg; + uint32_t frlength_0; + uint8_t isread; + + xfer->flags_int.bdma_setup = 1; + + /* reset BUS-DMA load state */ + + info->dma_error = 0; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + frlength_0 = xfer->sumlen; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + frlength_0 = xfer->frlengths[0]; + } + + /* + * Set DMA direction first. This is needed to + * select the correct cache invalidate and cache + * flush operations. + */ + isread = USB_GET_DATA_ISREAD(xfer); + pg = xfer->dma_page_ptr; + + if (xfer->flags_int.control_xfr && + xfer->flags_int.control_hdr) { + /* special case */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + /* The device controller writes to memory */ + xfer->frbuffers[0].isread = 1; + } else { + /* The host controller reads from memory */ + xfer->frbuffers[0].isread = 0; + } + } else { + /* default case */ + xfer->frbuffers[0].isread = isread; + } + + /* + * Setup the "page_start" pointer which points to an array of + * USB pages where information about the physical address of a + * page will be stored. Also initialise the "isread" field of + * the USB page caches. + */ + xfer->frbuffers[0].page_start = pg; + + info->dma_nframes = nframes; + info->dma_currframe = 0; + info->dma_frlength_0 = frlength_0; + + pg += (frlength_0 / USB_PAGE_SIZE); + pg += 2; + + while (--nframes > 0) { + xfer->frbuffers[nframes].isread = isread; + xfer->frbuffers[nframes].page_start = pg; + + pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE); + pg += 2; + } + + } + if (info->dma_error) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED); + mtx_unlock(xfer->usb2_mtx); + return; + } + if (info->dma_currframe != info->dma_nframes) { + + if (info->dma_currframe == 0) { + /* special case */ + usb2_pc_load_mem(xfer->frbuffers, + info->dma_frlength_0, 0); + } else { + /* default case */ + nframes = info->dma_currframe; + usb2_pc_load_mem(xfer->frbuffers + nframes, + xfer->frlengths[nframes], 0); + } + + /* advance frame index */ + info->dma_currframe++; + + return; + } + /* go ahead */ + usb2_bdma_pre_sync(xfer); + + /* start loading next USB transfer, if any */ + usb2_command_wrapper(pq, NULL); + + /* finally start the hardware */ + usb2_pipe_enter(xfer); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_done_event + * + * This function is called when the BUS-DMA has loaded virtual memory + * into DMA, if any. + *------------------------------------------------------------------------*/ +void +usb2_bdma_done_event(struct usb2_dma_parent_tag *udpt) +{ + struct usb2_xfer_root *info; + + info = udpt->info; + + mtx_assert(info->priv_mtx, MA_OWNED); + + /* copy error */ + info->dma_error = udpt->dma_error; + + /* enter workloop again */ + usb2_command_wrapper(&info->dma_q, + info->dma_q.curr); + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_pre_sync + * + * This function handles DMA synchronisation that must be done before + * an USB transfer is started. + *------------------------------------------------------------------------*/ +void +usb2_bdma_pre_sync(struct usb2_xfer *xfer) +{ + struct usb2_page_cache *pc; + uint32_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->page_offset_buf != pc->page_offset_end) { + if (pc->isread) { + usb2_pc_cpu_invalidate(pc); + } else { + usb2_pc_cpu_flush(pc); + } + } + pc++; + } + + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_post_sync + * + * This function handles DMA synchronisation that must be done after + * an USB transfer is complete. + *------------------------------------------------------------------------*/ +void +usb2_bdma_post_sync(struct usb2_xfer *xfer) +{ + struct usb2_page_cache *pc; + uint32_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->page_offset_buf != pc->page_offset_end) { + if (pc->isread) { + usb2_pc_cpu_invalidate(pc); + } + } + pc++; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_busdma.h b/sys/dev/usb2/core/usb2_busdma.h new file mode 100644 index 000000000000..0b035995e624 --- /dev/null +++ b/sys/dev/usb2/core/usb2_busdma.h @@ -0,0 +1,169 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_BUSDMA_H_ +#define _USB2_BUSDMA_H_ + +#include <sys/uio.h> +#include <sys/mbuf.h> + +#include <machine/bus.h> + +/* defines */ + +#define USB_PAGE_SIZE PAGE_SIZE /* use system PAGE_SIZE */ + +#ifdef __FreeBSD__ +#if (__FreeBSD_version >= 700020) +#define USB_GET_DMA_TAG(dev) bus_get_dma_tag(dev) +#else +#define USB_GET_DMA_TAG(dev) NULL /* XXX */ +#endif +#endif + +/* structure prototypes */ + +struct usb2_xfer_root; +struct usb2_dma_parent_tag; + +/* + * The following typedef defines the USB DMA load done callback. + */ + +typedef void (usb2_dma_callback_t)(struct usb2_dma_parent_tag *udpt); + +/* + * The following structure defines physical and non kernel virtual + * address of a memory page having size USB_PAGE_SIZE. + */ +struct usb2_page { + bus_size_t physaddr; + void *buffer; /* non Kernel Virtual Address */ +}; + +/* + * The following structure is used when needing the kernel virtual + * pointer and the physical address belonging to an offset in an USB + * page cache. + */ +struct usb2_page_search { + void *buffer; + bus_size_t physaddr; + uint32_t length; +}; + +/* + * The following structure is used to keep information about a DMA + * memory allocation. + */ +struct usb2_page_cache { + +#ifdef __FreeBSD__ + bus_dma_tag_t tag; + bus_dmamap_t map; +#endif +#ifdef __NetBSD__ + bus_dma_tag_t tag; + bus_dmamap_t map; + bus_dma_segment_t *p_seg; +#endif + struct usb2_page *page_start; + struct usb2_dma_parent_tag *tag_parent; /* always set */ + void *buffer; /* virtual buffer pointer */ +#ifdef __NetBSD__ + int n_seg; +#endif + uint32_t page_offset_buf; + uint32_t page_offset_end; + uint8_t isread:1; /* set if we are currently reading + * from the memory. Else write. */ + uint8_t ismultiseg:1; /* set if we can have multiple + * segments */ +}; + +/* + * The following structure describes the parent USB DMA tag. + */ +struct usb2_dma_parent_tag { +#ifdef __FreeBSD__ + struct cv cv[1]; /* internal condition variable */ +#endif + + bus_dma_tag_t tag; /* always set */ + + struct mtx *mtx; /* private mutex, always set */ + struct usb2_xfer_root *info; /* used by the callback function */ + usb2_dma_callback_t *func; /* load complete callback function */ + struct usb2_dma_tag *utag_first;/* pointer to first USB DMA tag */ + + uint8_t dma_error; /* set if DMA load operation failed */ + uint8_t dma_bits; /* number of DMA address lines */ + uint8_t utag_max; /* number of USB DMA tags */ +}; + +/* + * The following structure describes an USB DMA tag. + */ +struct usb2_dma_tag { +#ifdef __NetBSD__ + bus_dma_segment_t *p_seg; +#endif + struct usb2_dma_parent_tag *tag_parent; + bus_dma_tag_t tag; + + uint32_t align; + uint32_t size; +#ifdef __NetBSD__ + uint32_t n_seg; +#endif +}; + +/* function prototypes */ + +int usb2_uiomove(struct usb2_page_cache *pc, struct uio *uio, uint32_t pc_offset, uint32_t len); +struct usb2_dma_tag *usb2_dma_tag_find(struct usb2_dma_parent_tag *udpt, uint32_t size, uint32_t align); +uint8_t usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +uint8_t usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size); +uint8_t usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync); +void usb2_bdma_done_event(struct usb2_dma_parent_tag *udpt); +void usb2_bdma_post_sync(struct usb2_xfer *xfer); +void usb2_bdma_pre_sync(struct usb2_xfer *xfer); +void usb2_bdma_work_loop(struct usb2_xfer_queue *pq); +void usb2_bzero(struct usb2_page_cache *cache, uint32_t offset, uint32_t len); +void usb2_copy_in(struct usb2_page_cache *cache, uint32_t offset, const void *ptr, uint32_t len); +int usb2_copy_in_user(struct usb2_page_cache *cache, uint32_t offset, const void *ptr, uint32_t len); +void usb2_copy_out(struct usb2_page_cache *cache, uint32_t offset, void *ptr, uint32_t len); +int usb2_copy_out_user(struct usb2_page_cache *cache, uint32_t offset, void *ptr, uint32_t len); +void usb2_dma_tag_setup(struct usb2_dma_parent_tag *udpt, struct usb2_dma_tag *udt, bus_dma_tag_t dmat, struct mtx *mtx, usb2_dma_callback_t *func, struct usb2_xfer_root *info, uint8_t ndmabits, uint8_t nudt); +void usb2_dma_tag_unsetup(struct usb2_dma_parent_tag *udpt); +void usb2_get_page(struct usb2_page_cache *pc, uint32_t offset, struct usb2_page_search *res); +void usb2_m_copy_in(struct usb2_page_cache *cache, uint32_t dst_offset, struct mbuf *m, uint32_t src_offset, uint32_t src_len); +void usb2_pc_cpu_flush(struct usb2_page_cache *pc); +void usb2_pc_cpu_invalidate(struct usb2_page_cache *pc); +void usb2_pc_dmamap_destroy(struct usb2_page_cache *pc); +void usb2_pc_free_mem(struct usb2_page_cache *pc); + +#endif /* _USB2_BUSDMA_H_ */ diff --git a/sys/dev/usb2/core/usb2_compat_linux.c b/sys/dev/usb2/core/usb2_compat_linux.c new file mode 100644 index 000000000000..7b0212d11891 --- /dev/null +++ b/sys/dev/usb2/core/usb2_compat_linux.c @@ -0,0 +1,1659 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_ioctl.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_compat_linux.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_parse.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_debug.h> + +struct usb_linux_softc { + LIST_ENTRY(usb_linux_softc) sc_attached_list; + + device_t sc_fbsd_dev; + struct usb2_device *sc_fbsd_udev; + struct usb_interface *sc_ui; + struct usb_driver *sc_udrv; +}; + +/* prototypes */ +static device_probe_t usb_linux_probe; +static device_attach_t usb_linux_attach; +static device_detach_t usb_linux_detach; +static device_suspend_t usb_linux_suspend; +static device_resume_t usb_linux_resume; +static device_shutdown_t usb_linux_shutdown; + +static usb2_callback_t usb_linux_isoc_callback; +static usb2_callback_t usb_linux_non_isoc_callback; + +static usb_complete_t usb_linux_wait_complete; + +static uint16_t usb_max_isoc_frames(struct usb_device *dev); +static int usb_start_wait_urb(struct urb *urb, uint32_t timeout, uint16_t *p_actlen); +static const struct usb_device_id *usb_linux_lookup_id(const struct usb_device_id *id, struct usb2_attach_arg *uaa); +static struct usb_driver *usb_linux_get_usb_driver(struct usb_linux_softc *sc); +static struct usb_device *usb_linux_create_usb_device(struct usb2_device *udev, device_t dev); +static void usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface); +static void usb_linux_complete(struct usb2_xfer *xfer); +static int usb_unlink_urb_sub(struct urb *urb, uint8_t drain); + +/*------------------------------------------------------------------------* + * FreeBSD USB interface + *------------------------------------------------------------------------*/ + +static LIST_HEAD(, usb_linux_softc) usb_linux_attached_list; +static LIST_HEAD(, usb_driver) usb_linux_driver_list; + +static device_method_t usb_linux_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, usb_linux_probe), + DEVMETHOD(device_attach, usb_linux_attach), + DEVMETHOD(device_detach, usb_linux_detach), + DEVMETHOD(device_suspend, usb_linux_suspend), + DEVMETHOD(device_resume, usb_linux_resume), + DEVMETHOD(device_shutdown, usb_linux_shutdown), + + {0, 0} +}; + +static driver_t usb_linux_driver = { + .name = "usb_linux", + .methods = usb_linux_methods, + .size = sizeof(struct usb_linux_softc), +}; + +static devclass_t usb_linux_devclass; + +DRIVER_MODULE(usb_linux, ushub, usb_linux_driver, usb_linux_devclass, NULL, 0); + +/*------------------------------------------------------------------------* + * usb_linux_lookup_id + * + * This functions takes an array of "struct usb_device_id" and tries + * to match the entries with the information in "struct usb2_attach_arg". + * If it finds a match the matching entry will be returned. + * Else "NULL" will be returned. + *------------------------------------------------------------------------*/ +static const struct usb_device_id * +usb_linux_lookup_id(const struct usb_device_id *id, struct usb2_attach_arg *uaa) +{ + if (id == NULL) { + goto done; + } + /* + * Keep on matching array entries until we find one with + * "match_flags" equal to zero, which indicates the end of the + * array: + */ + for (; id->match_flags; id++) { + + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->idVendor != uaa->info.idVendor)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + (id->idProduct != uaa->info.idProduct)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != uaa->info.bDeviceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != uaa->info.bDeviceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != uaa->info.bDeviceProtocol)) { + continue; + } + if ((uaa->info.bDeviceClass == 0xFF) && + !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL))) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && + (id->bInterfaceClass != uaa->info.bInterfaceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && + (id->bInterfaceSubClass != uaa->info.bInterfaceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && + (id->bInterfaceProtocol != uaa->info.bInterfaceProtocol)) { + continue; + } + /* we found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_linux_probe + * + * This function is the FreeBSD probe callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb_driver *udrv; + int err = ENXIO; + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + if (usb_linux_lookup_id(udrv->id_table, uaa)) { + err = 0; + break; + } + } + mtx_unlock(&Giant); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb_linux_get_usb_driver + * + * This function returns the pointer to the "struct usb_driver" where + * the Linux USB device driver "struct usb_device_id" match was found. + * We apply a lock before reading out the pointer to avoid races. + *------------------------------------------------------------------------*/ +static struct usb_driver * +usb_linux_get_usb_driver(struct usb_linux_softc *sc) +{ + struct usb_driver *udrv; + + mtx_lock(&Giant); + udrv = sc->sc_udrv; + mtx_unlock(&Giant); + return (udrv); +} + +/*------------------------------------------------------------------------* + * usb_linux_attach + * + * This function is the FreeBSD attach callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + * This function is called when "usb_linux_probe()" returns zero. + *------------------------------------------------------------------------*/ +static int +usb_linux_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv; + struct usb_device *p_dev; + const struct usb_device_id *id = NULL; + + if (sc == NULL) { + return (ENOMEM); + } + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + id = usb_linux_lookup_id(udrv->id_table, uaa); + if (id) + break; + } + mtx_unlock(&Giant); + + if (id == NULL) { + return (ENXIO); + } + /* + * Save some memory and only create the Linux compat structure when + * needed: + */ + p_dev = uaa->device->linux_dev; + if (p_dev == NULL) { + p_dev = usb_linux_create_usb_device(uaa->device, dev); + if (p_dev == NULL) { + return (ENOMEM); + } + uaa->device->linux_dev = p_dev; + } + device_set_usb2_desc(dev); + + sc->sc_fbsd_udev = uaa->device; + sc->sc_fbsd_dev = dev; + sc->sc_udrv = udrv; + sc->sc_ui = usb_ifnum_to_if(p_dev, uaa->info.bIfaceNum); + if (sc->sc_ui == NULL) { + return (EINVAL); + } + if (udrv->probe) { + if ((udrv->probe) (sc->sc_ui, id)) { + return (ENXIO); + } + } + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_attached_list, sc, sc_attached_list); + mtx_unlock(&Giant); + + /* success */ + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_detach + * + * This function is the FreeBSD detach callback. It is called from the + * FreeBSD USB stack through the "device_detach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_detach(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = NULL; + + mtx_lock(&Giant); + if (sc->sc_attached_list.le_prev) { + LIST_REMOVE(sc, sc_attached_list); + sc->sc_attached_list.le_prev = NULL; + udrv = sc->sc_udrv; + sc->sc_udrv = NULL; + } + mtx_unlock(&Giant); + + if (udrv && udrv->disconnect) { + (udrv->disconnect) (sc->sc_ui); + } + /* + * Make sure that we free all FreeBSD USB transfers belonging to + * this Linux "usb_interface", hence they will most likely not be + * needed any more. + */ + usb_linux_cleanup_interface(sc->sc_fbsd_udev->linux_dev, sc->sc_ui); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_suspend + * + * This function is the FreeBSD suspend callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_suspend(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->suspend) { + err = (udrv->suspend) (sc->sc_ui, 0); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_resume + * + * This function is the FreeBSD resume callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_resume(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->resume) { + err = (udrv->resume) (sc->sc_ui); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_shutdown + * + * This function is the FreeBSD shutdown callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_shutdown(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + + if (udrv && udrv->shutdown) { + (udrv->shutdown) (sc->sc_ui); + } + return (0); +} + +/*------------------------------------------------------------------------* + * Linux emulation layer + *------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------* + * usb_max_isoc_frames + * + * The following function returns the maximum number of isochronous + * frames that we support per URB. It is not part of the Linux USB API. + *------------------------------------------------------------------------*/ +static uint16_t +usb_max_isoc_frames(struct usb_device *dev) +{ + return ((usb2_get_speed(dev->bsd_udev) == USB_SPEED_HIGH) ? + USB_MAX_HIGH_SPEED_ISOC_FRAMES : USB_MAX_FULL_SPEED_ISOC_FRAMES); +} + +/*------------------------------------------------------------------------* + * usb_submit_urb + * + * This function is used to queue an URB after that it has been + * initialized. If it returns non-zero, it means that the URB was not + * queued. + *------------------------------------------------------------------------*/ +int +usb_submit_urb(struct urb *urb, uint16_t mem_flags) +{ + struct usb_host_endpoint *uhe; + + if (urb == NULL) { + return (-EINVAL); + } + mtx_assert(&Giant, MA_OWNED); + + if (urb->pipe == NULL) { + return (-EINVAL); + } + uhe = urb->pipe; + + /* + * Check that we have got a FreeBSD USB transfer that will dequeue + * the URB structure and do the real transfer. If there are no USB + * transfers, then we return an error. + */ + if (uhe->bsd_xfer[0] || + uhe->bsd_xfer[1]) { + /* we are ready! */ + + TAILQ_INSERT_HEAD(&uhe->bsd_urb_list, urb, bsd_urb_list); + + urb->status = -EINPROGRESS; + + usb2_transfer_start(uhe->bsd_xfer[0]); + usb2_transfer_start(uhe->bsd_xfer[1]); + } else { + /* no pipes have been setup yet! */ + urb->status = -EINVAL; + return (-EINVAL); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_unlink_urb + * + * This function is used to stop an URB after that it is been + * submitted, but before the "complete" callback has been called. On + *------------------------------------------------------------------------*/ +int +usb_unlink_urb(struct urb *urb) +{ + return (usb_unlink_urb_sub(urb, 0)); +} + +static void +usb_unlink_bsd(struct usb2_xfer *xfer, + struct urb *urb, uint8_t drain) +{ + if (xfer && + usb2_transfer_pending(xfer) && + (xfer->priv_fifo == (void *)urb)) { + if (drain) { + mtx_unlock(&Giant); + usb2_transfer_drain(xfer); + mtx_lock(&Giant); + } else { + usb2_transfer_stop(xfer); + } + usb2_transfer_start(xfer); + } + return; +} + +static int +usb_unlink_urb_sub(struct urb *urb, uint8_t drain) +{ + struct usb_host_endpoint *uhe; + uint16_t x; + + if (urb == NULL) { + return (-EINVAL); + } + mtx_assert(&Giant, MA_OWNED); + + if (urb->pipe == NULL) { + return (-EINVAL); + } + uhe = urb->pipe; + + if (urb->bsd_urb_list.tqe_prev) { + + /* not started yet, just remove it from the queue */ + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + urb->status = -ECONNRESET; + urb->actual_length = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + } + + if (urb->complete) { + (urb->complete) (urb); + } + } else { + + /* + * If the URB is not on the URB list, then check if one of + * the FreeBSD USB transfer are processing the current URB. + * If so, re-start that transfer, which will lead to the + * termination of that URB: + */ + usb_unlink_bsd(uhe->bsd_xfer[0], urb, drain); + usb_unlink_bsd(uhe->bsd_xfer[1], urb, drain); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_clear_halt + * + * This function must always be used to clear the stall. Stall is when + * an USB endpoint returns a stall message to the USB host controller. + * Until the stall is cleared, no data can be transferred. + *------------------------------------------------------------------------*/ +int +usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe) +{ + struct usb2_config cfg[1]; + struct usb2_pipe *pipe; + uint8_t type; + uint8_t addr; + + if (uhe == NULL) + return (-EINVAL); + + type = uhe->desc.bmAttributes & UE_XFERTYPE; + addr = uhe->desc.bEndpointAddress; + + bzero(cfg, sizeof(cfg)); + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + + pipe = usb2_get_pipe(dev->bsd_udev, uhe->bsd_iface_index, cfg); + if (pipe == NULL) + return (-EINVAL); + + usb2_clear_data_toggle(dev->bsd_udev, pipe); + + return (usb_control_msg(dev, &dev->ep0, + UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT, + UF_ENDPOINT_HALT, addr, NULL, 0, 1000)); +} + +/*------------------------------------------------------------------------* + * usb_start_wait_urb + * + * This is an internal function that is used to perform synchronous + * Linux USB transfers. + *------------------------------------------------------------------------*/ +static int +usb_start_wait_urb(struct urb *urb, uint32_t timeout, uint16_t *p_actlen) +{ + int err; + + /* you must have a timeout! */ + if (timeout == 0) { + timeout = 1; + } + urb->complete = &usb_linux_wait_complete; + urb->timeout = timeout; + urb->transfer_flags |= URB_WAIT_WAKEUP; + urb->transfer_flags &= ~URB_IS_SLEEPING; + + err = usb_submit_urb(urb, 0); + if (err) + goto done; + + /* + * the URB might have completed before we get here, so check that by + * using some flags! + */ + while (urb->transfer_flags & URB_WAIT_WAKEUP) { + urb->transfer_flags |= URB_IS_SLEEPING; + usb2_cv_wait(&urb->cv_wait, &Giant); + urb->transfer_flags &= ~URB_IS_SLEEPING; + } + + err = urb->status; + +done: + if (err) { + *p_actlen = 0; + } else { + *p_actlen = urb->actual_length; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_control_msg + * + * The following function performs a control transfer sequence one any + * control, bulk or interrupt endpoint, specified by "uhe". A control + * transfer means that you transfer an 8-byte header first followed by + * a data-phase as indicated by the 8-byte header. The "timeout" is + * given in milliseconds. + * + * Return values: + * 0: Success + * < 0: Failure + * > 0: Acutal length + *------------------------------------------------------------------------*/ +int +usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *uhe, + uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, void *data, + uint16_t size, uint32_t timeout) +{ + struct usb2_device_request req; + struct urb *urb; + int err; + uint16_t actlen; + uint8_t type; + uint8_t addr; + + req.bmRequestType = requesttype; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, size); + + if (uhe == NULL) { + return (-EINVAL); + } + type = (uhe->desc.bmAttributes & UE_XFERTYPE); + addr = (uhe->desc.bEndpointAddress & UE_ADDR); + + if (type != UE_CONTROL) { + return (-EINVAL); + } + if (addr == 0) { + /* + * The FreeBSD USB stack supports standard control + * transfers on control endpoint zero: + */ + err = usb2_do_request_flags(dev->bsd_udev, + &Giant, &req, data, USB_SHORT_XFER_OK, + &actlen, timeout); + if (err) { + err = -EPIPE; + } else { + err = actlen; + } + return (err); + } + if (dev->bsd_udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return (-EINVAL); + } + err = usb_setup_endpoint(dev, uhe, 1 /* dummy */ ); + + /* + * NOTE: we need to allocate real memory here so that we don't + * transfer data to/from the stack! + * + * 0xFFFF is a FreeBSD specific magic value. + */ + urb = usb_alloc_urb(0xFFFF, size); + if (urb == NULL) + return (-ENOMEM); + + urb->dev = dev; + urb->pipe = uhe; + + bcopy(&req, urb->setup_packet, sizeof(req)); + + if (size && (!(req.bmRequestType & UT_READ))) { + /* move the data to a real buffer */ + bcopy(data, USB_ADD_BYTES(urb->setup_packet, + sizeof(req)), size); + } + err = usb_start_wait_urb(urb, timeout, &actlen); + + if (req.bmRequestType & UT_READ) { + if (actlen) { + bcopy(USB_ADD_BYTES(urb->setup_packet, + sizeof(req)), data, actlen); + } + } + usb_free_urb(urb); + + if (err == 0) { + err = actlen; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_set_interface + * + * The following function will select which alternate setting of an + * USB interface you plan to use. By default alternate setting with + * index zero is selected. Note that "iface_no" is not the interface + * index, but rather the value of "bInterfaceNumber". + *------------------------------------------------------------------------*/ +int +usb_set_interface(struct usb_device *dev, uint8_t iface_no, uint8_t alt_index) +{ + struct usb_interface *p_ui = usb_ifnum_to_if(dev, iface_no); + int err; + + if (p_ui == NULL) + return (-EINVAL); + if (alt_index >= p_ui->num_altsetting) + return (-EINVAL); + usb_linux_cleanup_interface(dev, p_ui); + err = -usb2_set_alt_interface_index(dev->bsd_udev, + p_ui->bsd_iface_index, alt_index); + if (err == 0) { + p_ui->cur_altsetting = p_ui->altsetting + alt_index; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_setup_endpoint + * + * The following function is an extension to the Linux USB API that + * allows you to set a maximum buffer size for a given USB endpoint. + * The maximum buffer size is per URB. If you don't call this function + * to set a maximum buffer size, the endpoint will not be functional. + * Note that for isochronous endpoints the maximum buffer size must be + * a non-zero dummy, hence this function will base the maximum buffer + * size on "wMaxPacketSize". + *------------------------------------------------------------------------*/ +int +usb_setup_endpoint(struct usb_device *dev, + struct usb_host_endpoint *uhe, uint32_t bufsize) +{ + struct usb2_config cfg[2]; + uint8_t type = uhe->desc.bmAttributes & UE_XFERTYPE; + uint8_t addr = uhe->desc.bEndpointAddress; + + if (uhe->fbsd_buf_size == bufsize) { + /* optimize */ + return (0); + } + usb2_transfer_unsetup(uhe->bsd_xfer, 2); + + uhe->fbsd_buf_size = bufsize; + + if (bufsize == 0) { + return (0); + } + bzero(cfg, sizeof(cfg)); + + if (type == UE_ISOCHRONOUS) { + + /* + * Isochronous transfers are special in that they don't fit + * into the BULK/INTR/CONTROL transfer model. + */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].mh.callback = &usb_linux_isoc_callback; + cfg[0].mh.bufsize = 0; /* use wMaxPacketSize */ + cfg[0].mh.frames = usb_max_isoc_frames(dev); + cfg[0].mh.flags.proxy_buffer = 1; +#if 0 + /* + * The Linux USB API allows non back-to-back + * isochronous frames which we do not support. If the + * isochronous frames are not back-to-back we need to + * do a copy, and then we need a buffer for + * that. Enable this at your own risk. + */ + cfg[0].mh.flags.ext_buffer = 1; +#endif + cfg[0].mh.flags.short_xfer_ok = 1; + + bcopy(cfg, cfg + 1, sizeof(*cfg)); + + /* Allocate and setup two generic FreeBSD USB transfers */ + + if (usb2_transfer_setup(dev->bsd_udev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 2, uhe, &Giant)) { + return (-EINVAL); + } + } else { + if (bufsize > (1 << 22)) { + /* limit buffer size */ + bufsize = (1 << 22); + } + /* Allocate and setup one generic FreeBSD USB transfer */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].mh.callback = &usb_linux_non_isoc_callback; + cfg[0].mh.bufsize = bufsize; + cfg[0].mh.flags.ext_buffer = 1; /* enable zero-copy */ + cfg[0].mh.flags.proxy_buffer = 1; + cfg[0].mh.flags.short_xfer_ok = 1; + + if (usb2_transfer_setup(dev->bsd_udev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 1, uhe, &Giant)) { + return (-EINVAL); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_create_usb_device + * + * The following function is used to build up a per USB device + * structure tree, that mimics the Linux one. The root structure + * is returned by this function. + *------------------------------------------------------------------------*/ +static struct usb_device * +usb_linux_create_usb_device(struct usb2_device *udev, device_t dev) +{ + struct usb2_config_descriptor *cd = usb2_get_config_descriptor(udev); + struct usb2_descriptor *desc; + struct usb2_interface_descriptor *id; + struct usb2_endpoint_descriptor *ed; + struct usb_device *p_ud = NULL; + struct usb_interface *p_ui = NULL; + struct usb_host_interface *p_uhi = NULL; + struct usb_host_endpoint *p_uhe = NULL; + uint32_t size; + uint16_t niface_total; + uint16_t nedesc; + uint16_t iface_no_curr; + uint16_t iface_index; + uint8_t pass; + uint8_t iface_no; + + /* + * We do two passes. One pass for computing necessary memory size + * and one pass to initialize all the allocated memory structures. + */ + for (pass = 0; pass < 2; pass++) { + + iface_no_curr = 0 - 1; + niface_total = 0; + iface_index = 0; + nedesc = 0; + desc = NULL; + + /* + * Iterate over all the USB descriptors. Use the USB config + * descriptor pointer provided by the FreeBSD USB stack. + */ + while ((desc = usb2_desc_foreach(cd, desc))) { + + /* + * Build up a tree according to the descriptors we + * find: + */ + switch (desc->bDescriptorType) { + case UDESC_DEVICE: + break; + + case UDESC_ENDPOINT: + ed = (void *)desc; + if ((ed->bLength < sizeof(*ed)) || + (iface_index == 0)) + break; + if (p_uhe) { + bcopy(ed, &p_uhe->desc, sizeof(p_uhe->desc)); + p_uhe->bsd_iface_index = iface_index - 1; + p_uhe++; + } + if (p_uhi) { + (p_uhi - 1)->desc.bNumEndpoints++; + } + nedesc++; + break; + + case UDESC_INTERFACE: + id = (void *)desc; + if (id->bLength < sizeof(*id)) + break; + if (p_uhi) { + bcopy(id, &p_uhi->desc, sizeof(p_uhi->desc)); + p_uhi->desc.bNumEndpoints = 0; + p_uhi->endpoint = p_uhe; + p_uhi->string = ""; + p_uhi->bsd_iface_index = iface_index; + p_uhi++; + } + iface_no = id->bInterfaceNumber; + niface_total++; + if (iface_no_curr != iface_no) { + if (p_ui) { + p_ui->altsetting = p_uhi - 1; + p_ui->cur_altsetting = p_uhi - 1; + p_ui->num_altsetting = 1; + p_ui->bsd_iface_index = iface_index; + p_ui->linux_udev = p_ud; + p_ui++; + } + iface_no_curr = iface_no; + iface_index++; + } else { + if (p_ui) { + (p_ui - 1)->num_altsetting++; + } + } + break; + + default: + break; + } + } + + if (pass == 0) { + + size = ((sizeof(*p_ud) * 1) + + (sizeof(*p_uhe) * nedesc) + + (sizeof(*p_ui) * iface_index) + + (sizeof(*p_uhi) * niface_total)); + + p_ud = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + if (p_ud == NULL) { + goto done; + } + p_uhe = (void *)(p_ud + 1); + p_ui = (void *)(p_uhe + nedesc); + p_uhi = (void *)(p_ui + iface_index); + + p_ud->product = ""; + p_ud->manufacturer = ""; + p_ud->serial = ""; + p_ud->speed = usb2_get_speed(udev); + p_ud->bsd_udev = udev; + p_ud->bsd_iface_start = p_ui; + p_ud->bsd_iface_end = p_ui + iface_index; + p_ud->bsd_endpoint_start = p_uhe; + p_ud->bsd_endpoint_end = p_uhe + nedesc; + p_ud->devnum = device_get_unit(dev); + bcopy(&udev->ddesc, &p_ud->descriptor, + sizeof(p_ud->descriptor)); + bcopy(udev->default_pipe.edesc, &p_ud->ep0.desc, + sizeof(p_ud->ep0.desc)); + } + } +done: + return (p_ud); +} + +/*------------------------------------------------------------------------* + * usb_alloc_urb + * + * This function should always be used when you allocate an URB for + * use with the USB Linux stack. In case of an isochronous transfer + * you must specifiy the maximum number of "iso_packets" which you + * plan to transfer per URB. This function is always blocking, and + * "mem_flags" are not regarded like on Linux. + *------------------------------------------------------------------------*/ +struct urb * +usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags) +{ + struct urb *urb; + uint32_t size; + + if (iso_packets == 0xFFFF) { + /* + * FreeBSD specific magic value to ask for control transfer + * memory allocation: + */ + size = sizeof(*urb) + sizeof(struct usb2_device_request) + mem_flags; + } else { + size = sizeof(*urb) + (iso_packets * sizeof(urb->iso_frame_desc[0])); + } + + urb = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + if (urb) { + + usb2_cv_init(&urb->cv_wait, "URBWAIT"); + if (iso_packets == 0xFFFF) { + urb->setup_packet = (void *)(urb + 1); + urb->transfer_buffer = (void *)(urb->setup_packet + + sizeof(struct usb2_device_request)); + } else { + urb->number_of_packets = iso_packets; + } + } + return (urb); +} + +/*------------------------------------------------------------------------* + * usb_find_host_endpoint + * + * The following function will return the Linux USB host endpoint + * structure that matches the given endpoint type and endpoint + * value. If no match is found, NULL is returned. This function is not + * part of the Linux USB API and is only used internally. + *------------------------------------------------------------------------*/ +struct usb_host_endpoint * +usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + struct usb_host_interface *uhi; + struct usb_interface *ui; + uint8_t ea; + uint8_t at; + uint8_t mask; + + if (dev == NULL) { + return (NULL); + } + if (type == UE_CONTROL) { + mask = UE_ADDR; + } else { + mask = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR); + } + + ep &= mask; + + /* + * Iterate over all the interfaces searching the selected alternate + * setting only, and all belonging endpoints. + */ + for (ui = dev->bsd_iface_start; + ui != dev->bsd_iface_end; + ui++) { + uhi = ui->cur_altsetting; + if (uhi) { + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + for (uhe = uhi->endpoint; + uhe != uhe_end; + uhe++) { + ea = uhe->desc.bEndpointAddress; + at = uhe->desc.bmAttributes; + + if (((ea & mask) == ep) && + ((at & UE_XFERTYPE) == type)) { + return (uhe); + } + } + } + } + + if ((type == UE_CONTROL) && ((ep & UE_ADDR) == 0)) { + return (&dev->ep0); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_altnum_to_altsetting + * + * The following function returns a pointer to an alternate setting by + * index given a "usb_interface" pointer. If the alternate setting by + * index does not exist, NULL is returned. And alternate setting is a + * variant of an interface, but usually with slightly different + * characteristics. + *------------------------------------------------------------------------*/ +struct usb_host_interface * +usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index) +{ + if (alt_index >= intf->num_altsetting) { + return (NULL); + } + return (intf->altsetting + alt_index); +} + +/*------------------------------------------------------------------------* + * usb_ifnum_to_if + * + * The following function searches up an USB interface by + * "bInterfaceNumber". If no match is found, NULL is returned. + *------------------------------------------------------------------------*/ +struct usb_interface * +usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no) +{ + struct usb_interface *p_ui; + + for (p_ui = dev->bsd_iface_start; + p_ui != dev->bsd_iface_end; + p_ui++) { + if ((p_ui->num_altsetting > 0) && + (p_ui->altsetting->desc.bInterfaceNumber == iface_no)) { + return (p_ui); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_buffer_alloc + *------------------------------------------------------------------------*/ +void * +usb_buffer_alloc(struct usb_device *dev, uint32_t size, uint16_t mem_flags, uint8_t *dma_addr) +{ + return (malloc(size, M_USBDEV, M_WAITOK | M_ZERO)); +} + +/*------------------------------------------------------------------------* + * usb_get_intfdata + *------------------------------------------------------------------------*/ +void * +usb_get_intfdata(struct usb_interface *intf) +{ + return (intf->bsd_priv_sc); +} + +/*------------------------------------------------------------------------* + * usb_linux_register + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to register a Linux USB driver, so that its + * "usb_device_id" structures gets searched a probe time. This + * function is not part of the Linux USB API, and is for internal use + * only. + *------------------------------------------------------------------------*/ +void +usb_linux_register(void *arg) +{ + struct usb_driver *drv = arg; + + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_driver_list, drv, linux_driver_list); + mtx_unlock(&Giant); + + usb2_needs_explore_all(); + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_deregister + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to deregister a Linux USB driver. This function will + * ensure that all driver instances belonging to the Linux USB device + * driver in question, gets detached before the driver is + * unloaded. This function is not part of the Linux USB API, and is + * for internal use only. + *------------------------------------------------------------------------*/ +void +usb_linux_deregister(void *arg) +{ + struct usb_driver *drv = arg; + struct usb_linux_softc *sc; + +repeat: + mtx_lock(&Giant); + LIST_FOREACH(sc, &usb_linux_attached_list, sc_attached_list) { + if (sc->sc_udrv == drv) { + mtx_unlock(&Giant); + device_detach(sc->sc_fbsd_dev); + goto repeat; + } + } + LIST_REMOVE(drv, linux_driver_list); + mtx_unlock(&Giant); + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_free_device + * + * The following function is only used by the FreeBSD USB stack, to + * cleanup and free memory after that a Linux USB device was attached. + *------------------------------------------------------------------------*/ +void +usb_linux_free_device(struct usb_device *dev) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhe = dev->bsd_endpoint_start; + uhe_end = dev->bsd_endpoint_end; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + err = usb_setup_endpoint(dev, &dev->ep0, 0); + free(dev, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_buffer_free + *------------------------------------------------------------------------*/ +void +usb_buffer_free(struct usb_device *dev, uint32_t size, + void *addr, uint8_t dma_addr) +{ + free(addr, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_free_urb + *------------------------------------------------------------------------*/ +void +usb_free_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + /* make sure that the current URB is not active */ + usb_kill_urb(urb); + + /* destroy condition variable */ + usb2_cv_destroy(&urb->cv_wait); + + /* just free it */ + free(urb, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_init_urb + * + * The following function can be used to initialize a custom URB. It + * is not recommended to use this function. Use "usb_alloc_urb()" + * instead. + *------------------------------------------------------------------------*/ +void +usb_init_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + bzero(urb, sizeof(*urb)); + return; +} + +/*------------------------------------------------------------------------* + * usb_kill_urb + *------------------------------------------------------------------------*/ +void +usb_kill_urb(struct urb *urb) +{ + if (usb_unlink_urb_sub(urb, 1)) { + /* ignore */ + } + return; +} + +/*------------------------------------------------------------------------* + * usb_set_intfdata + * + * The following function sets the per Linux USB interface private + * data pointer. It is used by most Linux USB device drivers. + *------------------------------------------------------------------------*/ +void +usb_set_intfdata(struct usb_interface *intf, void *data) +{ + intf->bsd_priv_sc = data; + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_cleanup_interface + * + * The following function will release all FreeBSD USB transfers + * associated with a Linux USB interface. It is for internal use only. + *------------------------------------------------------------------------*/ +static void +usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface) +{ + struct usb_host_interface *uhi; + struct usb_host_interface *uhi_end; + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhi = iface->altsetting; + uhi_end = iface->altsetting + iface->num_altsetting; + while (uhi != uhi_end) { + uhe = uhi->endpoint; + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + uhi++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_wait_complete + * + * The following function is used by "usb_start_wait_urb()" to wake it + * up, when an USB transfer has finished. + *------------------------------------------------------------------------*/ +static void +usb_linux_wait_complete(struct urb *urb) +{ + if (urb->transfer_flags & URB_IS_SLEEPING) { + usb2_cv_signal(&urb->cv_wait); + } + urb->transfer_flags &= ~URB_WAIT_WAKEUP; + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_complete + *------------------------------------------------------------------------*/ +static void +usb_linux_complete(struct usb2_xfer *xfer) +{ + struct urb *urb; + + urb = xfer->priv_fifo; + xfer->priv_fifo = NULL; + if (urb->complete) { + (urb->complete) (urb); + } + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_isoc_callback + * + * The following is the FreeBSD isochronous USB callback. Isochronous + * frames are USB packets transferred 1000 or 8000 times per second, + * depending on whether a full- or high- speed USB transfer is + * used. + *------------------------------------------------------------------------*/ +static void +usb_linux_isoc_callback(struct usb2_xfer *xfer) +{ + uint32_t max_frame = xfer->max_frame_size; + uint32_t offset; + uint16_t x; + struct urb *urb = xfer->priv_fifo; + struct usb_host_endpoint *uhe = xfer->priv_sc; + struct usb_iso_packet_descriptor *uipd; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (urb->bsd_isread) { + + /* copy in data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + uipd->actual_length = xfer->frlengths[x]; + uipd->status = 0; + if (!xfer->flags.ext_buffer) { + usb2_copy_out(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->actual_length); + } + offset += max_frame; + } + } else { + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + uipd->actual_length = xfer->frlengths[x]; + uipd->status = 0; + } + } + + urb->actual_length = xfer->actlen; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EPIPE; /* XXX should be + * EREMOTEIO */ + } else { + urb->status = 0; + } + } else { + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + + if (xfer->priv_fifo == NULL) { + + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + x = xfer->max_frame_count; + if (urb->number_of_packets > x) { + /* XXX simply truncate the transfer */ + urb->number_of_packets = x; + } + } else { + DPRINTF("Already got a transfer\n"); + + /* already got a transfer (should not happen) */ + urb = xfer->priv_fifo; + } + + urb->bsd_isread = (uhe->desc.bEndpointAddress & UE_DIR_IN) ? 1 : 0; + + if (!(urb->bsd_isread)) { + + /* copy out data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + xfer->frlengths[x] = uipd->length; + if (!xfer->flags.ext_buffer) { + usb2_copy_in(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->length); + } + offset += uipd->length; + } + } else { + + /* + * compute the transfer length into the "offset" + * variable + */ + + offset = urb->number_of_packets * max_frame; + + /* setup "frlengths" array */ + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + xfer->frlengths[x] = max_frame; + } + } + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usb2_set_frame_data(xfer, + urb->transfer_buffer, 0); + } + xfer->priv_fifo = urb; + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + xfer->nframes = urb->number_of_packets; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; /* stalled */ + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* Set zero for "actual_length" */ + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + + } +} + +/*------------------------------------------------------------------------* + * usb_linux_non_isoc_callback + * + * The following is the FreeBSD BULK/INTERRUPT and CONTROL USB + * callback. It dequeues Linux USB stack compatible URB's, transforms + * the URB fields into a FreeBSD USB transfer, and defragments the USB + * transfer as required. When the transfer is complete the "complete" + * callback is called. + *------------------------------------------------------------------------*/ +static void +usb_linux_non_isoc_callback(struct usb2_xfer *xfer) +{ + enum { + REQ_SIZE = sizeof(struct usb2_device_request) + }; + struct urb *urb = xfer->priv_fifo; + struct usb_host_endpoint *uhe = xfer->priv_sc; + uint8_t *ptr; + uint32_t max_bulk = xfer->max_data_length; + uint8_t data_frame = xfer->flags_int.control_xfr ? 1 : 0; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->flags_int.control_xfr) { + + /* don't transfer the setup packet again: */ + + xfer->frlengths[0] = 0; + } + if (urb->bsd_isread && (!xfer->flags.ext_buffer)) { + /* copy in data with regard to the URB */ + usb2_copy_out(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, xfer->frlengths[data_frame]); + } + urb->bsd_length_rem -= xfer->frlengths[data_frame]; + urb->bsd_data_ptr += xfer->frlengths[data_frame]; + urb->actual_length += xfer->frlengths[data_frame]; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + urb->bsd_length_rem = 0; + + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EPIPE; + } else { + urb->status = 0; + } + } else { + /* check remainder */ + if (urb->bsd_length_rem > 0) { + goto setup_bulk; + } + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + xfer->priv_fifo = urb; + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + + if (xfer->flags_int.control_xfr) { + + /* + * USB control transfers need special handling. + * First copy in the header, then copy in data! + */ + if (!xfer->flags.ext_buffer) { + usb2_copy_in(xfer->frbuffers, 0, + urb->setup_packet, REQ_SIZE); + } else { + /* set virtual address to load */ + usb2_set_frame_data(xfer, + urb->setup_packet, 0); + } + + xfer->frlengths[0] = REQ_SIZE; + + ptr = urb->setup_packet; + + /* setup data transfer direction and length */ + urb->bsd_isread = (ptr[0] & UT_READ) ? 1 : 0; + urb->bsd_length_rem = ptr[6] | (ptr[7] << 8); + + } else { + + /* setup data transfer direction */ + + urb->bsd_length_rem = urb->transfer_buffer_length; + urb->bsd_isread = (uhe->desc.bEndpointAddress & + UE_DIR_IN) ? 1 : 0; + } + + urb->bsd_data_ptr = urb->transfer_buffer; + urb->actual_length = 0; + +setup_bulk: + if (max_bulk > urb->bsd_length_rem) { + max_bulk = urb->bsd_length_rem; + } + /* check if we need to force a short transfer */ + + if ((max_bulk == urb->bsd_length_rem) && + (urb->transfer_flags & URB_ZERO_PACKET) && + (!xfer->flags_int.control_xfr)) { + xfer->flags.force_short_xfer = 1; + } + /* check if we need to copy in data */ + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usb2_set_frame_data(xfer, urb->bsd_data_ptr, + data_frame); + } else if (!urb->bsd_isread) { + /* copy out data with regard to the URB */ + usb2_copy_in(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, max_bulk); + } + xfer->frlengths[data_frame] = max_bulk; + if (xfer->flags_int.control_xfr) { + if (max_bulk > 0) { + xfer->nframes = 2; + } else { + xfer->nframes = 1; + } + } else { + xfer->nframes = 1; + } + usb2_start_hardware(xfer); + return; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + } +} diff --git a/sys/dev/usb2/core/usb2_compat_linux.h b/sys/dev/usb2/core/usb2_compat_linux.h new file mode 100644 index 000000000000..b8ddaf4fe93f --- /dev/null +++ b/sys/dev/usb2/core/usb2_compat_linux.h @@ -0,0 +1,465 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_COMPAT_LINUX_H +#define _USB_COMPAT_LINUX_H + +struct usb_device; +struct usb_interface; +struct usb_driver; +struct urb; + +typedef void *pm_message_t; +typedef void (usb_complete_t)(struct urb *); + +#define USB_MAX_FULL_SPEED_ISOC_FRAMES (60 * 1) +#define USB_MAX_HIGH_SPEED_ISOC_FRAMES (60 * 8) + +/* + * Linux compatible USB device drivers put their device information + * into the "usb_device_id" structure using the "USB_DEVICE()" macro. + * The "MODULE_DEVICE_TABLE()" macro can be used to export this + * information to userland. + */ +struct usb_device_id { + /* which fields to match against */ + uint16_t match_flags; +#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 +#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 +#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004 +#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008 +#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010 +#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020 +#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040 +#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080 +#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100 +#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200 + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Hook for driver specific information */ + unsigned long driver_info; +}; + +#define USB_DEVICE_ID_MATCH_DEVICE \ + (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT) + +#define USB_DEVICE(vend,prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \ + .idProduct = (prod) + +/* The "usb_driver" structure holds the Linux USB device driver + * callbacks, and a pointer to device ID's which this entry should + * match against. Usually this entry is exposed to the USB emulation + * layer using the "USB_DRIVER_EXPORT()" macro, which is defined + * below. + */ +struct usb_driver { + const char *name; + + int (*probe) (struct usb_interface *intf, + const struct usb_device_id *id); + + void (*disconnect) (struct usb_interface *intf); + + int (*ioctl) (struct usb_interface *intf, unsigned int code, + void *buf); + + int (*suspend) (struct usb_interface *intf, pm_message_t message); + int (*resume) (struct usb_interface *intf); + + const struct usb_device_id *id_table; + + void (*shutdown) (struct usb_interface *intf); + + LIST_ENTRY(usb_driver) linux_driver_list; +}; + +#define USB_DRIVER_EXPORT(id,p_usb_drv) \ + SYSINIT(id,SI_SUB_KLD,SI_ORDER_FIRST,usb_linux_register,p_usb_drv); \ + SYSUNINIT(id,SI_SUB_KLD,SI_ORDER_ANY,usb_linux_deregister,p_usb_drv) + +/* + * The following structure is the same as "usb_device_descriptor_t" + * except that 16-bit values are "uint16_t" and not an array of "uint8_t". + * It is used by Linux USB device drivers. + */ +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} __packed; + +/* + * The following structure is the same as + * "usb_interface_descriptor_t". It is used by + * Linux USB device drivers. + */ +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} __packed; + +/* + * The following structure is the same as "usb_endpoint_descriptor_t" + * except that 16-bit values are "uint16_t" and not an array of "uint8_t". + * It is used by Linux USB device drivers. + */ +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + + /* extension for audio endpoints only: */ + uint8_t bRefresh; + uint8_t bSynchAddress; +} __packed; + +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 + +/* + * Endpoints + */ +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +/* CONTROL REQUEST SUPPORT */ + +/* + * Definition of direction mask for + * "bEndpointAddress" and "bmRequestType": + */ +#define USB_DIR_MASK 0x80 +#define USB_DIR_OUT 0x00 /* write to USB device */ +#define USB_DIR_IN 0x80 /* read from USB device */ + +/* + * Definition of type mask for + * "bmRequestType": + */ +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +/* + * Definition of receiver mask for + * "bmRequestType": + */ +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +/* + * Definition of standard request values for + * "bRequest": + */ +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_REQ_SET_ENCRYPTION 0x0D /* Wireless USB */ +#define USB_REQ_GET_ENCRYPTION 0x0E +#define USB_REQ_SET_HANDSHAKE 0x0F +#define USB_REQ_GET_HANDSHAKE 0x10 +#define USB_REQ_SET_CONNECTION 0x11 +#define USB_REQ_SET_SECURITY_DATA 0x12 +#define USB_REQ_GET_SECURITY_DATA 0x13 +#define USB_REQ_SET_WUSB_DATA 0x14 +#define USB_REQ_LOOPBACK_DATA_WRITE 0x15 +#define USB_REQ_LOOPBACK_DATA_READ 0x16 +#define USB_REQ_SET_INTERFACE_DS 0x17 + +/* + * USB feature flags are written using USB_REQ_{CLEAR,SET}_FEATURE, and + * are read as a bit array returned by USB_REQ_GET_STATUS. (So there + * are at most sixteen features of each type.) + */ +#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ +#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ +#define USB_DEVICE_TEST_MODE 2 /* (wired high speed only) */ +#define USB_DEVICE_BATTERY 2 /* (wireless) */ +#define USB_DEVICE_B_HNP_ENABLE 3 /* (otg) dev may initiate HNP */ +#define USB_DEVICE_WUSB_DEVICE 3 /* (wireless) */ +#define USB_DEVICE_A_HNP_SUPPORT 4 /* (otg) RH port supports HNP */ +#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ +#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ + +#define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ + +#define PIPE_ISOCHRONOUS 0x01 /* UE_ISOCHRONOUS */ +#define PIPE_INTERRUPT 0x03 /* UE_INTERRUPT */ +#define PIPE_CONTROL 0x00 /* UE_CONTROL */ +#define PIPE_BULK 0x02 /* UE_BULK */ + +/* Whenever Linux references an USB endpoint: + * a) to initialize "urb->pipe" + * b) second argument passed to "usb_control_msg()" + * + * Then it uses one of the following macros. The "endpoint" argument + * is the physical endpoint value masked by 0xF. The "dev" argument + * is a pointer to "struct usb_device". + */ +#define usb_sndctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_OUT) + +#define usb_rcvctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_IN) + +#define usb_sndisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_OUT) + +#define usb_rcvisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_IN) + +#define usb_sndbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_OUT) + +#define usb_rcvbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_IN) + +#define usb_sndintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_OUT) + +#define usb_rcvintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_IN) + +/* The following four structures makes up a tree, where we have the + * leaf structure, "usb_host_endpoint", first, and the root structure, + * "usb_device", last. The four structures below mirror the structure + * of the USB descriptors belonging to an USB configuration. Please + * refer to the USB specification for a definition of "endpoints" and + * "interfaces". + */ +struct usb_host_endpoint { + struct usb_endpoint_descriptor desc; + + TAILQ_HEAD(, urb) bsd_urb_list; + + struct usb2_xfer *bsd_xfer[2]; + + uint8_t *extra; /* Extra descriptors */ + + uint32_t fbsd_buf_size; + + uint16_t extralen; + + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_host_interface { + struct usb_interface_descriptor desc; + + /* the following array has size "desc.bNumEndpoint" */ + struct usb_host_endpoint *endpoint; + + const char *string; /* iInterface string, if present */ + uint8_t *extra; /* Extra descriptors */ + + uint16_t extralen; + + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_interface { + /* array of alternate settings for this interface */ + struct usb_host_interface *altsetting; + struct usb_host_interface *cur_altsetting; + struct usb_device *linux_udev; + void *bsd_priv_sc; /* device specific information */ + + uint8_t num_altsetting; /* number of alternate settings */ + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_device { + struct usb_device_descriptor descriptor; + struct usb_host_endpoint ep0; + + struct usb2_device *bsd_udev; + struct usb_interface *bsd_iface_start; + struct usb_interface *bsd_iface_end; + struct usb_host_endpoint *bsd_endpoint_start; + struct usb_host_endpoint *bsd_endpoint_end; + + /* static strings from the device */ + const char *product; /* iProduct string, if present */ + const char *manufacturer; /* iManufacturer string, if present */ + const char *serial; /* iSerialNumber string, if present */ + + uint16_t devnum; + + uint8_t speed; /* USB_SPEED_XXX */ +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure is used to extend "struct urb" when we are + * dealing with an isochronous endpoint. It contains information about + * the data offset and data length of an isochronous packet. + * The "actual_length" field is updated before the "complete" + * callback in the "urb" structure is called. + */ +struct usb_iso_packet_descriptor { + uint32_t offset; /* depreciated buffer offset (the + * packets are usually back to back) */ + uint16_t length; /* expected length */ + uint16_t actual_length; + uint16_t status; +}; + +/* + * The following structure holds various information about an USB + * transfer. This structure is used for all kinds of USB transfers. + * + * URB is short for USB Request Block. + */ +struct urb { + TAILQ_ENTRY(urb) bsd_urb_list; + struct cv cv_wait; + + struct usb_device *dev; /* (in) pointer to associated device */ + struct usb_host_endpoint *pipe; /* (in) pipe pointer */ + uint8_t *setup_packet; /* (in) setup packet (control only) */ + uint8_t *bsd_data_ptr; + void *transfer_buffer; /* (in) associated data buffer */ + void *context; /* (in) context for completion */ + usb_complete_t *complete; /* (in) completion routine */ + + uint32_t transfer_buffer_length;/* (in) data buffer length */ + uint32_t actual_length; /* (return) actual transfer length */ + uint32_t bsd_length_rem; + uint32_t timeout; /* FreeBSD specific */ + + uint16_t transfer_flags; /* (in) */ +#define URB_SHORT_NOT_OK 0x0001 /* report short transfers like errors */ +#define URB_ISO_ASAP 0x0002 /* ignore "start_frame" field */ +#define URB_ZERO_PACKET 0x0004 /* the USB transfer ends with a short + * packet */ +#define URB_NO_TRANSFER_DMA_MAP 0x0008 /* "transfer_dma" is valid on submit */ +#define URB_WAIT_WAKEUP 0x0010 /* custom flags */ +#define URB_IS_SLEEPING 0x0020 /* custom flags */ + + uint16_t start_frame; /* (modify) start frame (ISO) */ + uint16_t number_of_packets; /* (in) number of ISO packets */ + uint16_t interval; /* (modify) transfer interval + * (INT/ISO) */ + uint16_t error_count; /* (return) number of ISO errors */ + int16_t status; /* (return) status */ + + uint8_t setup_dma; /* (in) not used on FreeBSD */ + uint8_t transfer_dma; /* (in) not used on FreeBSD */ + uint8_t bsd_isread; + + struct usb_iso_packet_descriptor iso_frame_desc[]; /* (in) ISO ONLY */ +}; + +/* various prototypes */ + +int usb_submit_urb(struct urb *urb, uint16_t mem_flags); +int usb_unlink_urb(struct urb *urb); +int usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe); +int usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *pipe, uint8_t request, uint8_t requesttype, uint16_t value, uint16_t index, void *data, uint16_t size, uint32_t timeout); +int usb_set_interface(struct usb_device *dev, uint8_t ifnum, uint8_t alternate); +int usb_setup_endpoint(struct usb_device *dev, struct usb_host_endpoint *uhe, uint32_t bufsize); + +struct usb_host_endpoint *usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep); +struct urb *usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags); +struct usb_host_interface *usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index); +struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no); + +void *usb_buffer_alloc(struct usb_device *dev, uint32_t size, uint16_t mem_flags, uint8_t *dma_addr); +void *usb_get_intfdata(struct usb_interface *intf); + +void usb_buffer_free(struct usb_device *dev, uint32_t size, void *addr, uint8_t dma_addr); +void usb_free_urb(struct urb *urb); +void usb_init_urb(struct urb *urb); +void usb_kill_urb(struct urb *urb); +void usb_set_intfdata(struct usb_interface *intf, void *data); +void usb_linux_register(void *arg); +void usb_linux_deregister(void *arg); + +#define interface_to_usbdev(intf) (intf)->linux_udev +#define interface_to_bsddev(intf) (intf)->linux_udev->bsd_udev + +#endif /* _USB_COMPAT_LINUX_H */ diff --git a/sys/dev/usb2/core/usb2_config_td.c b/sys/dev/usb2/core/usb2_config_td.c new file mode 100644 index 000000000000..ac652fd256db --- /dev/null +++ b/sys/dev/usb2/core/usb2_config_td.c @@ -0,0 +1,320 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_mfunc.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_debug.h> + +static void usb2_config_td_sync_cb(struct usb2_config_td_softc *sc, struct usb2_config_td_cc *cc, uint16_t ref); + +static void +usb2_config_td_dispatch(struct usb2_proc_msg *pm) +{ + struct usb2_config_td_item *pi = (void *)pm; + struct usb2_config_td *ctd = pi->p_ctd; + + DPRINTF("\n"); + + (pi->command_func) (ctd->p_softc, (void *)(pi + 1), pi->command_ref); + + if (TAILQ_NEXT(pm, pm_qentry) == NULL) { + /* last command */ + if (ctd->p_end_of_commands) { + (ctd->p_end_of_commands) (ctd->p_softc); + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_setup + * + * NOTE: the structure pointed to by "ctd" must be zeroed before calling + * this function! + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_setup(struct usb2_config_td *ctd, void *priv_sc, + struct mtx *priv_mtx, + usb2_config_td_end_of_commands_t *p_func_eoc, + uint16_t item_size, uint16_t item_count) +{ + struct usb2_config_td_item *pi; + uint16_t n; + + DPRINTF(" size=%u, count=%u \n", item_size, item_count); + + if (item_count >= 256) { + DPRINTFN(0, "too many items!\n"); + return (1); + } + ctd->p_softc = priv_sc; + ctd->p_end_of_commands = p_func_eoc; + ctd->msg_count = (2 * item_count); + ctd->msg_size = + (sizeof(struct usb2_config_td_item) + item_size); + ctd->p_msgs = + malloc(ctd->msg_size * ctd->msg_count, M_USBDEV, M_WAITOK | M_ZERO); + if (ctd->p_msgs == NULL) { + return (1); + } + if (usb2_proc_setup(&ctd->usb2_proc, priv_mtx, USB_PRI_MED)) { + free(ctd->p_msgs, M_USBDEV); + ctd->p_msgs = NULL; + return (1); + } + /* initialise messages */ + pi = USB_ADD_BYTES(ctd->p_msgs, 0); + for (n = 0; n != ctd->msg_count; n++) { + pi->hdr.pm_callback = &usb2_config_td_dispatch; + pi->p_ctd = ctd; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_drain + * + * This function will tear down an USB config thread, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "ctd" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_config_td_drain(struct usb2_config_td *ctd) +{ + DPRINTF("\n"); + if (ctd->p_msgs) { + usb2_proc_drain(&ctd->usb2_proc); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_unsetup + * + * NOTE: If the structure pointed to by "ctd" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_config_td_unsetup(struct usb2_config_td *ctd) +{ + DPRINTF("\n"); + + usb2_config_td_drain(ctd); + + if (ctd->p_msgs) { + usb2_proc_unsetup(&ctd->usb2_proc); + free(ctd->p_msgs, M_USBDEV); + ctd->p_msgs = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_queue_command + * + * This function will enter a command into the config thread queue for + * execution. The "command_sync" field was previously used to indicate + * the queue count which is now fixed at two elements. If the + * "command_sync" field is equal to "USB2_CONFIG_TD_SYNC" the command + * will be executed synchronously from the config thread. The + * "command_ref" argument is the reference count for the current + * command which is passed on to the "command_post_func" + * function. This parameter can be used to make a command + * unique. "command_pre_func" is called from this function when we + * have the final queue element. "command_post_func" is called from + * the USB config thread when the command reaches the beginning of the + * USB config thread queue. This function must be called locked. + *------------------------------------------------------------------------*/ +void +usb2_config_td_queue_command(struct usb2_config_td *ctd, + usb2_config_td_command_t *command_pre_func, + usb2_config_td_command_t *command_post_func, + uint16_t command_sync, + uint16_t command_ref) +{ + struct usb2_config_td_item *pi; + struct usb2_config_td_item *pi_0; + struct usb2_config_td_item *pi_1; + uint16_t n; + + if (usb2_config_td_is_gone(ctd)) { + DPRINTF("gone\n"); + /* nothing more to do */ + return; + } + DPRINTF("\n"); + + pi = USB_ADD_BYTES(ctd->p_msgs, 0); + for (n = 0;; n += 2) { + if (n == ctd->msg_count) { + /* should not happen */ + panic("%s:%d: out of memory!\n", + __FUNCTION__, __LINE__); + return; + } + if (pi->command_func == NULL) { + /* reserve our entry */ + pi->command_func = command_post_func; + pi->command_ref = command_ref; + pi_0 = pi; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + pi->command_func = command_post_func; + pi->command_ref = command_ref; + pi_1 = pi; + break; + } + if ((pi->command_func == command_post_func) && + (pi->command_ref == command_ref)) { + /* found an entry */ + pi_0 = pi; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + pi_1 = pi; + break; + } + pi = USB_ADD_BYTES(pi, (2 * ctd->msg_size)); + } + + /* + * We have two message structures. One of them will get + * queued: + */ + pi = usb2_proc_msignal(&ctd->usb2_proc, pi_0, pi_1); + + /* + * The job of the post-command function is to finish the command in + * a separate context to allow calls to sleeping functions + * basically. Queue the post command before calling the pre command. + * That way commands queued by the pre command will be queued after + * the current command. + */ + + /* + * The job of the pre-command function is to copy the needed + * configuration to the provided structure and to execute other + * commands that must happen immediately + */ + if (command_pre_func) { + (command_pre_func) (ctd->p_softc, (void *)(pi + 1), command_ref); + } + if (command_sync == USB2_CONFIG_TD_SYNC) { + usb2_proc_mwait(&ctd->usb2_proc, pi_0, pi_1); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_is_gone + * + * Return values: + * 0: config thread is running + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_is_gone(struct usb2_config_td *ctd) +{ + return (usb2_proc_is_gone(&ctd->usb2_proc)); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_sleep + * + * NOTE: this function can only be called from the config thread + * + * Return values: + * 0: normal delay + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_sleep(struct usb2_config_td *ctd, uint32_t timeout) +{ + uint8_t is_gone = usb2_config_td_is_gone(ctd); + + if (is_gone) { + goto done; + } + if (timeout == 0) { + /* + * Zero means no timeout, so avoid that by setting + * timeout to one: + */ + timeout = 1; + } + mtx_unlock(ctd->usb2_proc.up_mtx); + + if (pause("USBWAIT", timeout)) { + /* ignore */ + } + mtx_lock(ctd->usb2_proc.up_mtx); + + is_gone = usb2_config_td_is_gone(ctd); +done: + return (is_gone); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_sync + * + * This function will wait until all commands have been executed on + * the config thread. This function must be called locked and can + * sleep. + * + * Return values: + * 0: success + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_sync(struct usb2_config_td *ctd) +{ + if (usb2_config_td_is_gone(ctd)) { + return (1); + } + usb2_config_td_queue_command(ctd, NULL, + &usb2_config_td_sync_cb, USB2_CONFIG_TD_SYNC, 0); + + if (usb2_config_td_is_gone(ctd)) { + return (1); + } + return (0); +} + +static void +usb2_config_td_sync_cb(struct usb2_config_td_softc *sc, + struct usb2_config_td_cc *cc, uint16_t ref) +{ + return; +} diff --git a/sys/dev/usb2/core/usb2_config_td.h b/sys/dev/usb2/core/usb2_config_td.h new file mode 100644 index 000000000000..1f7d378b0bf0 --- /dev/null +++ b/sys/dev/usb2/core/usb2_config_td.h @@ -0,0 +1,71 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_CONFIG_TD_H_ +#define _USB2_CONFIG_TD_H_ + +struct usb2_config_td_softc; +struct usb2_config_td_cc; + +#define USB2_CONFIG_TD_SYNC 0xFFFF /* magic value */ + +typedef void (usb2_config_td_command_t)(struct usb2_config_td_softc *sc, struct usb2_config_td_cc *cc, uint16_t reference); +typedef void (usb2_config_td_end_of_commands_t)(struct usb2_config_td_softc *sc); + +/* + * The following structure defines a command that should be executed + * using the USB config thread system. + */ +struct usb2_config_td_item { + struct usb2_proc_msg hdr; + struct usb2_config_td *p_ctd; + usb2_config_td_command_t *command_func; + uint16_t command_ref; +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure defines an USB config thread. + */ +struct usb2_config_td { + struct usb2_process usb2_proc; + struct usb2_config_td_softc *p_softc; + usb2_config_td_end_of_commands_t *p_end_of_commands; + void *p_msgs; + uint16_t msg_size; + uint16_t msg_count; +}; + +/* prototypes */ + +uint8_t usb2_config_td_setup(struct usb2_config_td *ctd, void *priv_sc, struct mtx *priv_mtx, usb2_config_td_end_of_commands_t *p_func_eoc, uint16_t item_size, uint16_t item_count); +void usb2_config_td_drain(struct usb2_config_td *ctd); +void usb2_config_td_unsetup(struct usb2_config_td *ctd); +void usb2_config_td_queue_command(struct usb2_config_td *ctd, usb2_config_td_command_t *pre_func, usb2_config_td_command_t *post_func, uint16_t command_sync, uint16_t command_ref); +uint8_t usb2_config_td_is_gone(struct usb2_config_td *ctd); +uint8_t usb2_config_td_sleep(struct usb2_config_td *ctd, uint32_t timeout); +uint8_t usb2_config_td_sync(struct usb2_config_td *ctd); + +#endif /* _USB2_CONFIG_TD_H_ */ diff --git a/sys/dev/usb2/core/usb2_core.c b/sys/dev/usb2/core/usb2_core.c new file mode 100644 index 000000000000..b96f53c71225 --- /dev/null +++ b/sys/dev/usb2/core/usb2_core.c @@ -0,0 +1,40 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB specifications and other documentation can be found at + * http://www.usb.org/developers/docs/ and + * http://www.usb.org/developers/devclass_docs/ + */ + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_mbuf.h> + +MALLOC_DEFINE(M_USB, "USB", "USB"); +MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); +MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); + +MODULE_VERSION(usb2_core, 1); diff --git a/sys/dev/usb2/core/usb2_core.h b/sys/dev/usb2/core/usb2_core.h new file mode 100644 index 000000000000..c0cec7a1725e --- /dev/null +++ b/sys/dev/usb2/core/usb2_core.h @@ -0,0 +1,448 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Including this file is mandatory for all USB related c-files in the + * kernel. + */ + +#ifndef _USB2_CORE_H_ +#define _USB2_CORE_H_ + +/* Default USB configuration */ + +#ifndef USB_NO_POLL +#define USB_NO_POLL 0 +#endif + +#ifndef USB_USE_CONDVAR +#define USB_USE_CONDVAR 0 +#endif + +#ifndef USB_TD_GET_PROC +#define USB_TD_GET_PROC(td) (td)->td_proc +#endif + +#ifndef USB_PROC_GET_GID +#define USB_PROC_GET_GID(td) (td)->p_pgid +#endif + +#ifndef USB_VNOPS_FO_CLOSE +#define USB_VNOPS_FO_CLOSE(fp, td, perr) do { \ + (td)->td_fpop = (fp); \ + *(perr) = vnops.fo_close(fp, td); \ + (td)->td_fpop = NULL; \ +} while (0) +#endif + +#ifndef USB_VNOPS_FO_STAT +#define USB_VNOPS_FO_STAT(fp, sb, cred, td) \ + vnops.fo_stat(fp, sb, cred, td) +#endif + +#ifndef USB_VNOPS_FO_TRUNCATE +#define USB_VNOPS_FO_TRUNCATE(fp, length, cred, td) \ + vnops.fo_truncate(fp, length, cred, td) +#endif + +/* Include files */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/linker_set.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_revision.h> + +#include "usb2_if.h" +#include "opt_usb.h" +#include "opt_bus.h" + +#define USB_STACK_VERSION 2000 /* 2.0 */ + +#define USB_HOST_ALIGN 8 /* bytes, must be power of two */ + +#define USB_ROOT_HUB_ADDR 1 /* value */ + +#define USB_ISOC_TIME_MAX 128 /* ms */ +#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */ + +#if (USB_FS_ISOC_UFRAME_MAX > 6) +#error "USB_FS_ISOC_UFRAME_MAX cannot be set higher than 6" +#endif + +#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */ +#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */ + +#define USB_MAX_IPACKET 8 /* maximum size of the initial USB + * data packet */ +#ifndef USB_VERBOSE +#define USB_VERBOSE 1 +#endif + +#define USB_HUB_MAX_DEPTH 5 + +/* USB transfer states */ + +#define USB_ST_SETUP 0 +#define USB_ST_TRANSFERRED 1 +#define USB_ST_ERROR 2 + +/* + * The following macro will return the current state of an USB + * transfer like defined by the "USB_ST_XXX" enums. + */ +#define USB_GET_STATE(xfer) ((xfer)->usb2_state) + +/* + * The following macro will tell if an USB transfer is currently + * receiving or transferring data. + */ +#define USB_GET_DATA_ISREAD(xfer) (((xfer)->flags_int.usb2_mode == \ + USB_MODE_DEVICE) ? ((xfer->endpoint & UE_DIR_IN) ? 0 : 1) : \ + ((xfer->endpoint & UE_DIR_IN) ? 1 : 0)) + +/* + * The following macros are used used to convert milliseconds into + * HZ. We use 1024 instead of 1000 milliseconds per second to save a + * full division. + */ +#define USB_MS_HZ 1024 + +#define USB_MS_TO_TICKS(ms) \ + (((uint32_t)((((uint32_t)(ms)) * ((uint32_t)(hz))) + USB_MS_HZ - 1)) / USB_MS_HZ) + +/* macros */ + +#define usb2_callout_init_mtx(c,m,f) callout_init_mtx(&(c)->co,m,f) +#define usb2_callout_reset(c,t,f,d) callout_reset(&(c)->co,t,f,d) +#define usb2_callout_stop(c) callout_stop(&(c)->co) +#define usb2_callout_drain(c) callout_drain(&(c)->co) +#define usb2_callout_pending(c) callout_pending(&(c)->co) + +/* structure prototypes */ + +struct file; +struct usb2_bus; +struct usb2_device; +struct usb2_page; +struct usb2_page_cache; +struct usb2_xfer; +struct usb2_xfer_root; + +/* typedefs */ + +typedef uint8_t usb2_error_t; + +typedef void (usb2_callback_t)(struct usb2_xfer *); + +/* structures */ + +/* + * This structure contains permissions. + */ + +struct usb2_perm { + uint32_t uid; + uint32_t gid; + uint16_t mode; +}; + +/* + * Common queue structure for USB transfers. + */ +struct usb2_xfer_queue { + TAILQ_HEAD(, usb2_xfer) head; + struct usb2_xfer *curr; /* current USB transfer processed */ + void (*command) (struct usb2_xfer_queue *pq); + uint8_t recurse_1:1; + uint8_t recurse_2:1; +}; + +/* + * The following is a wrapper for the callout structure to ease + * porting the code to other platforms. + */ +struct usb2_callout { + struct callout co; +}; + +/* + * The following structure defines a set of USB transfer flags. + */ +struct usb2_xfer_flags { + uint8_t force_short_xfer:1; /* force a short transmit transfer + * last */ + uint8_t short_xfer_ok:1; /* allow short receive transfers */ + uint8_t short_frames_ok:1; /* allow short frames */ + uint8_t pipe_bof:1; /* block pipe on failure */ + uint8_t proxy_buffer:1; /* makes buffer size a factor of + * "max_frame_size" */ + uint8_t ext_buffer:1; /* uses external DMA buffer */ + uint8_t manual_status:1; /* non automatic status stage on + * control transfers */ + uint8_t no_pipe_ok:1; /* set if "USB_ERR_NO_PIPE" error can + * be ignored */ + uint8_t stall_pipe:1; /* set if the endpoint belonging to + * this USB transfer should be stalled + * before starting this transfer! */ +}; + +/* + * The following structure defines a set of internal USB transfer + * flags. + */ +struct usb2_xfer_flags_int { + uint16_t control_rem; /* remainder in bytes */ + + uint8_t open:1; /* set if USB pipe has been opened */ + uint8_t transferring:1; /* set if an USB transfer is in + * progress */ + uint8_t did_dma_delay:1; /* set if we waited for HW DMA */ + uint8_t did_close:1; /* set if we closed the USB transfer */ + uint8_t draining:1; /* set if we are draining an USB + * transfer */ + uint8_t started:1; /* keeps track of started or stopped */ + uint8_t bandwidth_reclaimed:1; + uint8_t control_xfr:1; /* set if control transfer */ + uint8_t control_hdr:1; /* set if control header should be + * sent */ + uint8_t control_act:1; /* set if control transfer is active */ + + uint8_t short_frames_ok:1; /* filtered version */ + uint8_t short_xfer_ok:1; /* filtered version */ + uint8_t bdma_enable:1; /* filtered version (only set if + * hardware supports DMA) */ + uint8_t bdma_no_post_sync:1; /* set if the USB callback wrapper + * should not do the BUS-DMA post sync + * operation */ + uint8_t bdma_setup:1; /* set if BUS-DMA has been setup */ + uint8_t isochronous_xfr:1; /* set if isochronous transfer */ + uint8_t usb2_mode:1; /* shadow copy of "udev->usb2_mode" */ + uint8_t curr_dma_set:1; /* used by USB HC/DC driver */ + uint8_t can_cancel_immed:1; /* set if USB transfer can be + * cancelled immediately */ +}; + +/* + * The following structure defines the symmetric part of an USB config + * structure. + */ +struct usb2_config_sub { + usb2_callback_t *callback; /* USB transfer callback */ + uint32_t bufsize; /* total pipe buffer size in bytes */ + uint32_t frames; /* maximum number of USB frames */ + uint16_t interval; /* interval in milliseconds */ +#define USB_DEFAULT_INTERVAL 0 + uint16_t timeout; /* transfer timeout in milliseconds */ + struct usb2_xfer_flags flags; /* transfer flags */ +}; + +/* + * The following structure define an USB configuration, that basically + * is used when setting up an USB transfer. + */ +struct usb2_config { + struct usb2_config_sub mh; /* parameters for USB_MODE_HOST */ + struct usb2_config_sub md; /* parameters for USB_MODE_DEVICE */ + uint8_t type; /* pipe type */ + uint8_t endpoint; /* pipe number */ + uint8_t direction; /* pipe direction */ + uint8_t ep_index; /* pipe index match to use */ + uint8_t if_index; /* "ifaces" index to use */ +}; + +/* + * The following structure defines an USB transfer. + */ +struct usb2_xfer { + struct usb2_callout timeout_handle; + TAILQ_ENTRY(usb2_xfer) wait_entry; /* used at various places */ + + struct usb2_page_cache *buf_fixup; /* fixup buffer(s) */ + struct usb2_xfer_queue *wait_queue; /* pointer to queue that we + * are waiting on */ + struct usb2_page *dma_page_ptr; + struct usb2_pipe *pipe; /* our USB pipe */ + struct usb2_device *udev; + struct mtx *priv_mtx; /* cannot be changed during operation */ + struct mtx *usb2_mtx; /* used by HC driver */ + struct usb2_xfer_root *usb2_root; /* used by HC driver */ + void *usb2_sc; /* used by HC driver */ + void *qh_start[2]; /* used by HC driver */ + void *td_start[2]; /* used by HC driver */ + void *td_transfer_first; /* used by HC driver */ + void *td_transfer_last; /* used by HC driver */ + void *td_transfer_cache; /* used by HC driver */ + void *priv_sc; /* device driver data pointer 1 */ + void *priv_fifo; /* device driver data pointer 2 */ + void *local_buffer; + uint32_t *frlengths; + struct usb2_page_cache *frbuffers; + usb2_callback_t *callback; + + uint32_t max_usb2_frame_size; + uint32_t max_data_length; + uint32_t sumlen; /* sum of all lengths in bytes */ + uint32_t actlen; /* actual length in bytes */ + uint32_t timeout; /* milliseconds */ +#define USB_NO_TIMEOUT 0 +#define USB_DEFAULT_TIMEOUT 5000 /* 5000 ms = 5 seconds */ + + uint32_t max_frame_count; /* initial value of "nframes" after + * setup */ + uint32_t nframes; /* number of USB frames to transfer */ + uint32_t aframes; /* actual number of USB frames + * transferred */ + + uint16_t max_packet_size; + uint16_t max_frame_size; + uint16_t qh_pos; + uint16_t isoc_time_complete; /* in ms */ + uint16_t interval; /* milliseconds */ + + uint8_t address; /* physical USB address */ + uint8_t endpoint; /* physical USB endpoint */ + uint8_t max_packet_count; + uint8_t usb2_smask; + uint8_t usb2_cmask; + uint8_t usb2_uframe; + uint8_t usb2_state; + + usb2_error_t error; + + struct usb2_xfer_flags flags; + struct usb2_xfer_flags_int flags_int; +}; + +/* + * The following structure keeps information that is used to match + * against an array of "usb2_device_id" elements. + */ +struct usb2_lookup_info { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t bIfaceIndex; + uint8_t bIfaceNum; + uint8_t bConfigIndex; + uint8_t bConfigNum; +}; + +/* Structure used by probe and attach */ + +struct usb2_attach_arg { + struct usb2_lookup_info info; + device_t temp_dev; /* for internal use */ + const void *driver_info; /* for internal use */ + struct usb2_device *device; /* current device */ + struct usb2_interface *iface; /* current interface */ + uint8_t usb2_mode; /* see USB_MODE_XXX */ + uint8_t port; + uint8_t use_generic; /* hint for generic drivers */ +}; + +/* Structure used when referring an USB device */ + +struct usb2_location { + struct usb2_bus *bus; + struct usb2_device *udev; + struct usb2_interface *iface; + struct usb2_fifo *rxfifo; + struct usb2_fifo *txfifo; + uint32_t devloc; /* original devloc */ + uint16_t bus_index; + uint8_t dev_index; + uint8_t iface_index; + uint8_t ep_index; + uint8_t is_read; + uint8_t is_write; + uint8_t is_uref; +}; + +/* external variables */ + +MALLOC_DECLARE(M_USB); +MALLOC_DECLARE(M_USBDEV); +MALLOC_DECLARE(M_USBHC); + +extern struct mtx usb2_ref_lock; + +/* typedefs */ + +typedef struct malloc_type *usb2_malloc_type; + +/* prototypes */ + +const char *usb2_errstr(usb2_error_t error); +struct usb2_config_descriptor *usb2_get_config_descriptor(struct usb2_device *udev); +struct usb2_device_descriptor *usb2_get_device_descriptor(struct usb2_device *udev); +struct usb2_interface *usb2_get_iface(struct usb2_device *udev, uint8_t iface_index); +struct usb2_interface_descriptor *usb2_get_interface_descriptor(struct usb2_interface *iface); +uint8_t usb2_clear_stall_callback(struct usb2_xfer *xfer1, struct usb2_xfer *xfer2); +uint8_t usb2_get_interface_altindex(struct usb2_interface *iface); +usb2_error_t usb2_set_alt_interface_index(struct usb2_device *udev, uint8_t iface_index, uint8_t alt_index); +uint8_t usb2_get_speed(struct usb2_device *udev); +usb2_error_t usb2_transfer_setup(struct usb2_device *udev, const uint8_t *ifaces, struct usb2_xfer **pxfer, const struct usb2_config *setup_start, uint16_t n_setup, void *priv_sc, struct mtx *priv_mtx); +void usb2_set_frame_data(struct usb2_xfer *xfer, void *ptr, uint32_t frindex); +void usb2_set_frame_offset(struct usb2_xfer *xfer, uint32_t offset, uint32_t frindex); +void usb2_start_hardware(struct usb2_xfer *xfer); +void usb2_transfer_clear_stall(struct usb2_xfer *xfer); +void usb2_transfer_drain(struct usb2_xfer *xfer); +void usb2_transfer_set_stall(struct usb2_xfer *xfer); +void usb2_transfer_start(struct usb2_xfer *xfer); +void usb2_transfer_stop(struct usb2_xfer *xfer); +void usb2_transfer_unsetup(struct usb2_xfer **pxfer, uint16_t n_setup); +usb2_error_t usb2_ref_device(struct file *fp, struct usb2_location *ploc, uint32_t devloc); +void usb2_unref_device(struct usb2_location *ploc); +void usb2_set_parent_iface(struct usb2_device *udev, uint8_t iface_index, uint8_t parent_index); +void usb2_set_iface_perm(struct usb2_device *udev, uint8_t iface_index, uint32_t uid, uint32_t gid, uint16_t mode); +uint8_t usb2_get_bus_index(struct usb2_device *udev); +uint8_t usb2_get_device_index(struct usb2_device *udev); + +#endif /* _USB2_CORE_H_ */ diff --git a/sys/dev/usb2/core/usb2_debug.c b/sys/dev/usb2/core/usb2_debug.c new file mode 100644 index 000000000000..7969662387c7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_debug.c @@ -0,0 +1,153 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_defs.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> + +/* + * Define this unconditionally in case a kernel module is loaded that + * has been compiled with debugging options. + */ +int usb2_debug = 0; + +SYSCTL_NODE(_hw, OID_AUTO, usb2, CTLFLAG_RW, 0, "USB debugging"); +SYSCTL_INT(_hw_usb2, OID_AUTO, debug, CTLFLAG_RW, + &usb2_debug, 0, "Debug level"); + +/*------------------------------------------------------------------------* + * usb2_dump_iface + * + * This function dumps information about an USB interface. + *------------------------------------------------------------------------*/ +void +usb2_dump_iface(struct usb2_interface *iface) +{ + printf("usb2_dump_iface: iface=%p\n", iface); + if (iface == NULL) { + return; + } + printf(" iface=%p idesc=%p altindex=%d\n", + iface, iface->idesc, iface->alt_index); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_device + * + * This function dumps information about an USB device. + *------------------------------------------------------------------------*/ +void +usb2_dump_device(struct usb2_device *udev) +{ + printf("usb2_dump_device: dev=%p\n", udev); + if (udev == NULL) { + return; + } + printf(" bus=%p \n" + " address=%d config=%d depth=%d speed=%d self_powered=%d\n" + " power=%d langid=%d\n", + udev->bus, + udev->address, udev->curr_config_no, udev->depth, udev->speed, + udev->flags.self_powered, udev->power, udev->langid); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_queue + * + * This function dumps the USB transfer that are queued up on an USB pipe. + *------------------------------------------------------------------------*/ +void +usb2_dump_queue(struct usb2_pipe *pipe) +{ + struct usb2_xfer *xfer; + + printf("usb2_dump_queue: pipe=%p xfer: ", pipe); + TAILQ_FOREACH(xfer, &pipe->pipe_q.head, wait_entry) { + printf(" %p", xfer); + } + printf("\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_pipe + * + * This function dumps information about an USB pipe. + *------------------------------------------------------------------------*/ +void +usb2_dump_pipe(struct usb2_pipe *pipe) +{ + if (pipe) { + printf("usb2_dump_pipe: pipe=%p", pipe); + + printf(" edesc=%p isoc_next=%d toggle_next=%d", + pipe->edesc, pipe->isoc_next, pipe->toggle_next); + + if (pipe->edesc) { + printf(" bEndpointAddress=0x%02x", + pipe->edesc->bEndpointAddress); + } + printf("\n"); + usb2_dump_queue(pipe); + } else { + printf("usb2_dump_pipe: pipe=NULL\n"); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_xfer + * + * This function dumps information about an USB transfer. + *------------------------------------------------------------------------*/ +void +usb2_dump_xfer(struct usb2_xfer *xfer) +{ + printf("usb2_dump_xfer: xfer=%p\n", xfer); + if (xfer == NULL) { + return; + } + if (xfer->pipe == NULL) { + printf("xfer %p: pipe=NULL\n", + xfer); + return; + } + printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d " + "pipe=%p ep=0x%02x attr=0x%02x\n", + xfer, xfer->udev, + UGETW(xfer->udev->ddesc.idVendor), + UGETW(xfer->udev->ddesc.idProduct), + xfer->udev->address, xfer->pipe, + xfer->pipe->edesc->bEndpointAddress, + xfer->pipe->edesc->bmAttributes); + return; +} diff --git a/sys/dev/usb2/core/usb2_debug.h b/sys/dev/usb2/core/usb2_debug.h new file mode 100644 index 000000000000..92dcbd5b9c2c --- /dev/null +++ b/sys/dev/usb2/core/usb2_debug.h @@ -0,0 +1,70 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This file contains various factored out debug macros. */ + +#ifndef _USB2_DEBUG_H_ +#define _USB2_DEBUG_H_ + +/* Declare parent SYSCTL USB node. */ +SYSCTL_DECL(_hw_usb2); + +/* Declare global USB debug variable. */ +extern int usb2_debug; + +/* Force debugging until further */ +#ifndef USB_DEBUG +#define USB_DEBUG 1 +#endif + +/* Check if USB debugging is enabled. */ +#ifdef USB_DEBUG_VAR +#if (USB_DEBUG != 0) +#define DPRINTFN(n,fmt,...) do { \ + if ((USB_DEBUG_VAR) >= (n)) { \ + printf("%s:%u: " fmt, \ + __FUNCTION__, __LINE__,## __VA_ARGS__); \ + } \ +} while (0) +#define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFN(...) do { } while (0) +#endif +#endif + +struct usb2_interface; +struct usb2_device; +struct usb2_pipe; +struct usb2_xfer; + +void usb2_dump_iface(struct usb2_interface *iface); +void usb2_dump_device(struct usb2_device *udev); +void usb2_dump_queue(struct usb2_pipe *pipe); +void usb2_dump_pipe(struct usb2_pipe *pipe); +void usb2_dump_xfer(struct usb2_xfer *xfer); + +#endif /* _USB2_DEBUG_H_ */ diff --git a/sys/dev/usb2/core/usb2_dev.c b/sys/dev/usb2/core/usb2_dev.c new file mode 100644 index 000000000000..1afe83e1f9ee --- /dev/null +++ b/sys/dev/usb2/core/usb2_dev.c @@ -0,0 +1,2786 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006-2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * usb2_dev.c - An abstraction layer for creating devices under /dev/... + */ + +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_ioctl.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> + +#define USB_DEBUG_VAR usb2_fifo_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_mbuf.h> +#include <dev/usb2/core/usb2_dev.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_generic.h> +#include <dev/usb2/core/usb2_dynamic.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +#include <sys/filio.h> +#include <sys/ttycom.h> +#include <sys/syscallsubr.h> + +#include <machine/stdarg.h> + +#if USB_DEBUG +static int usb2_fifo_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); +SYSCTL_INT(_hw_usb2_dev, OID_AUTO, debug, CTLFLAG_RW, + &usb2_fifo_debug, 0, "Debug Level"); +#endif + +#if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ + ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) +#define USB_UCRED struct ucred *ucred, +#else +#define USB_UCRED +#endif + +/* prototypes */ + +static uint32_t usb2_path_convert_one(const char **pp); +static uint32_t usb2_path_convert(const char *path); +static int usb2_check_access(int fflags, struct usb2_perm *puser); +static int usb2_fifo_open(struct usb2_fifo *f, struct file *fp, struct thread *td, int fflags); +static void usb2_fifo_close(struct usb2_fifo *f, struct thread *td, int fflags); +static void usb2_dev_init(void *arg); +static void usb2_dev_init_post(void *arg); +static void usb2_dev_uninit(void *arg); +static int usb2_fifo_uiomove(struct usb2_fifo *f, void *cp, int n, struct uio *uio); +static void usb2_fifo_check_methods(struct usb2_fifo_methods *pm); +static void usb2_clone(void *arg, USB_UCRED char *name, int namelen, struct cdev **dev); +static struct usb2_fifo *usb2_fifo_alloc(void); +static struct usb2_pipe *usb2_dev_get_pipe(struct usb2_device *udev, uint8_t iface_index, uint8_t ep_index, uint8_t dir); + +static d_fdopen_t usb2_fdopen; +static d_close_t usb2_close; +static d_ioctl_t usb2_ioctl; + +static fo_rdwr_t usb2_read_f; +static fo_rdwr_t usb2_write_f; + +#if __FreeBSD_version > 800009 +static fo_truncate_t usb2_truncate_f; + +#endif +static fo_ioctl_t usb2_ioctl_f; +static fo_poll_t usb2_poll_f; +static fo_kqfilter_t usb2_kqfilter_f; +static fo_stat_t usb2_stat_f; +static fo_close_t usb2_close_f; + +static usb2_fifo_open_t usb2_fifo_dummy_open; +static usb2_fifo_close_t usb2_fifo_dummy_close; +static usb2_fifo_ioctl_t usb2_fifo_dummy_ioctl; +static usb2_fifo_cmd_t usb2_fifo_dummy_cmd; + +static struct usb2_perm usb2_perm = { + .uid = UID_ROOT, + .gid = GID_OPERATOR, + .mode = 0660, +}; + +static struct cdevsw usb2_devsw = { + .d_version = D_VERSION, + .d_fdopen = usb2_fdopen, + .d_close = usb2_close, + .d_ioctl = usb2_ioctl, + .d_name = "usb", + .d_flags = D_TRACKCLOSE, +}; + +static struct fileops usb2_ops_f = { + .fo_read = usb2_read_f, + .fo_write = usb2_write_f, +#if __FreeBSD_version > 800009 + .fo_truncate = usb2_truncate_f, +#endif + .fo_ioctl = usb2_ioctl_f, + .fo_poll = usb2_poll_f, + .fo_kqfilter = usb2_kqfilter_f, + .fo_stat = usb2_stat_f, + .fo_close = usb2_close_f, + .fo_flags = DFLAG_PASSABLE | DFLAG_SEEKABLE +}; + +static const dev_clone_fn usb2_clone_ptr = &usb2_clone; +static struct cdev *usb2_dev; +static uint32_t usb2_last_devloc = 0 - 1; +static eventhandler_tag usb2_clone_tag; +static void *usb2_old_f_data; +static struct fileops *usb2_old_f_ops; +static TAILQ_HEAD(, usb2_symlink) usb2_sym_head; +static struct sx usb2_sym_lock; + +struct mtx usb2_ref_lock; + +static uint32_t +usb2_path_convert_one(const char **pp) +{ + const char *ptr; + uint32_t temp = 0; + + ptr = *pp; + + while ((*ptr >= '0') && (*ptr <= '9')) { + temp *= 10; + temp += (*ptr - '0'); + if (temp >= 1000000) { + /* catch overflow early */ + return (0 - 1); + } + ptr++; + } + + if (*ptr == '.') { + /* skip dot */ + ptr++; + } + *pp = ptr; + + return (temp); +} + +/*------------------------------------------------------------------------* + * usb2_path_convert + * + * Path format: "/dev/usb<bus>.<dev>.<iface>.<fifo>" + * + * Returns: Path converted into numerical format. + *------------------------------------------------------------------------*/ +static uint32_t +usb2_path_convert(const char *path) +{ + uint32_t temp; + uint32_t devloc; + + devloc = 0; + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_BUS_MAX) { + return (0 - 1); + } + devloc += temp; + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_DEV_MAX) { + return (0 - 1); + } + devloc += (temp * USB_BUS_MAX); + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_IFACE_MAX) { + return (0 - 1); + } + devloc += (temp * USB_DEV_MAX * USB_BUS_MAX); + + temp = usb2_path_convert_one(&path); + + if (temp >= ((USB_FIFO_MAX / 2) + (USB_EP_MAX / 2))) { + return (0 - 1); + } + devloc += (temp * USB_IFACE_MAX * USB_DEV_MAX * USB_BUS_MAX); + + return (devloc); +} + +/*------------------------------------------------------------------------* + * usb2_set_iface_perm + * + * This function will set the interface permissions. + *------------------------------------------------------------------------*/ +void +usb2_set_iface_perm(struct usb2_device *udev, uint8_t iface_index, + uint32_t uid, uint32_t gid, uint16_t mode) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(udev, iface_index); + if (iface && iface->idesc) { + mtx_lock(&usb2_ref_lock); + iface->perm.uid = uid; + iface->perm.gid = gid; + iface->perm.mode = mode; + mtx_unlock(&usb2_ref_lock); + + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_perm + * + * This function will set the permissions at the given level. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static int +usb2_set_perm(struct usb2_dev_perm *psrc, uint8_t level) +{ + struct usb2_location loc; + struct usb2_perm *pdst; + uint32_t devloc; + int error; + + /* check if the current thread can change USB permissions. */ + error = priv_check(curthread, PRIV_ROOT); + if (error) { + return (error); + } + /* range check device location */ + if ((psrc->bus_index >= USB_BUS_MAX) || + (psrc->dev_index >= USB_DEV_MAX) || + (psrc->iface_index >= USB_IFACE_MAX)) { + return (EINVAL); + } + if (level == 1) + devloc = USB_BUS_MAX; /* use root-HUB to access bus */ + else + devloc = 0; + switch (level) { + case 3: + devloc += psrc->iface_index * + USB_DEV_MAX * USB_BUS_MAX; + /* FALLTHROUGH */ + case 2: + devloc += psrc->dev_index * + USB_BUS_MAX; + /* FALLTHROUGH */ + case 1: + devloc += psrc->bus_index; + break; + default: + break; + } + + if ((level > 0) && (level < 4)) { + error = usb2_ref_device(NULL, &loc, devloc); + if (error) { + return (error); + } + } + switch (level) { + case 3: + if (loc.iface == NULL) { + usb2_unref_device(&loc); + return (EINVAL); + } + pdst = &loc.iface->perm; + break; + case 2: + pdst = &loc.udev->perm; + break; + case 1: + pdst = &loc.bus->perm; + break; + default: + pdst = &usb2_perm; + break; + } + + /* all permissions are protected by "usb2_ref_lock" */ + mtx_lock(&usb2_ref_lock); + pdst->uid = psrc->user_id; + pdst->gid = psrc->group_id; + pdst->mode = psrc->mode; + mtx_unlock(&usb2_ref_lock); + + if ((level > 0) && (level < 4)) { + usb2_unref_device(&loc); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_get_perm + * + * This function will get the permissions at the given level. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static int +usb2_get_perm(struct usb2_dev_perm *pdst, uint8_t level) +{ + struct usb2_location loc; + struct usb2_perm *psrc; + uint32_t devloc; + int error; + + if ((pdst->bus_index >= USB_BUS_MAX) || + (pdst->dev_index >= USB_DEV_MAX) || + (pdst->iface_index >= USB_IFACE_MAX)) { + return (EINVAL); + } + if (level == 1) + devloc = USB_BUS_MAX; /* use root-HUB to access bus */ + else + devloc = 0; + switch (level) { + case 3: + devloc += pdst->iface_index * + USB_DEV_MAX * USB_BUS_MAX; + /* FALLTHROUGH */ + case 2: + devloc += pdst->dev_index * + USB_BUS_MAX; + /* FALLTHROUGH */ + case 1: + devloc += pdst->bus_index; + break; + default: + break; + } + + if ((level > 0) && (level < 4)) { + error = usb2_ref_device(NULL, &loc, devloc); + if (error) { + return (error); + } + } + switch (level) { + case 3: + if (loc.iface == NULL) { + usb2_unref_device(&loc); + return (EINVAL); + } + psrc = &loc.iface->perm; + break; + case 2: + psrc = &loc.udev->perm; + break; + case 1: + psrc = &loc.bus->perm; + break; + default: + psrc = &usb2_perm; + break; + } + + /* all permissions are protected by "usb2_ref_lock" */ + mtx_lock(&usb2_ref_lock); + if (psrc->mode != 0) { + pdst->user_id = psrc->uid; + pdst->group_id = psrc->gid; + pdst->mode = psrc->mode; + } else { + /* access entry at this level and location is not active */ + pdst->user_id = 0; + pdst->group_id = 0; + pdst->mode = 0; + } + mtx_unlock(&usb2_ref_lock); + + if ((level > 0) && (level < 4)) { + usb2_unref_device(&loc); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_check_access + * + * This function will verify the given access information. + * + * Return values: + * 0: Access granted. + * Else: No access granted. + *------------------------------------------------------------------------*/ +static int +usb2_check_access(int fflags, struct usb2_perm *puser) +{ + mode_t accmode; + + if ((fflags & (FWRITE | FREAD)) && (puser->mode != 0)) { + /* continue */ + } else { + return (EPERM); /* no access */ + } + + accmode = 0; + if (fflags & FWRITE) + accmode |= VWRITE; + if (fflags & FREAD) + accmode |= VREAD; + + return (vaccess(VCHR, puser->mode, puser->uid, + puser->gid, accmode, curthread->td_ucred, NULL)); +} + +/*------------------------------------------------------------------------* + * usb2_ref_device + * + * This function is used to atomically refer an USB device by its + * device location. If this function returns success the USB device + * will not dissappear until the USB device is unreferenced. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_ref_device(struct file *fp, struct usb2_location *ploc, uint32_t devloc) +{ + struct usb2_fifo **ppf; + struct usb2_fifo *f; + int fflags; + uint8_t need_uref; + + if (fp) { + /* check if we need uref hint */ + need_uref = devloc ? 0 : 1; + /* get devloc - already verified */ + devloc = USB_P2U(fp->f_data); + /* get file flags */ + fflags = fp->f_flag; + /* only ref FIFO */ + ploc->is_uref = 0; + /* devloc should be valid */ + } else { + /* we need uref */ + need_uref = 1; + /* only ref device */ + fflags = 0; + /* search for FIFO */ + ploc->is_uref = 1; + /* check "devloc" */ + if (devloc >= (USB_BUS_MAX * USB_DEV_MAX * + USB_IFACE_MAX * ((USB_EP_MAX / 2) + (USB_FIFO_MAX / 2)))) { + return (USB_ERR_INVAL); + } + } + + /* store device location */ + ploc->devloc = devloc; + ploc->bus_index = devloc % USB_BUS_MAX; + ploc->dev_index = (devloc / USB_BUS_MAX) % USB_DEV_MAX; + ploc->iface_index = (devloc / (USB_BUS_MAX * + USB_DEV_MAX)) % USB_IFACE_MAX; + ploc->ep_index = (devloc / (USB_BUS_MAX * USB_DEV_MAX * + USB_IFACE_MAX)); + + mtx_lock(&usb2_ref_lock); + ploc->bus = devclass_get_softc(usb2_devclass_ptr, ploc->bus_index); + if (ploc->bus == NULL) { + DPRINTFN(2, "no bus at %u\n", ploc->bus_index); + goto error; + } + if (ploc->dev_index >= ploc->bus->devices_max) { + DPRINTFN(2, "invalid dev index, %u\n", ploc->dev_index); + goto error; + } + ploc->udev = ploc->bus->devices[ploc->dev_index]; + if (ploc->udev == NULL) { + DPRINTFN(2, "no device at %u\n", ploc->dev_index); + goto error; + } + if (ploc->udev->refcount == USB_DEV_REF_MAX) { + DPRINTFN(2, "no dev ref\n"); + goto error; + } + ploc->iface = usb2_get_iface(ploc->udev, ploc->iface_index); + if (ploc->ep_index != 0) { + /* non control endpoint - we need an interface */ + if (ploc->iface == NULL) { + DPRINTFN(2, "no iface\n"); + goto error; + } + if (ploc->iface->idesc == NULL) { + DPRINTFN(2, "no idesc\n"); + goto error; + } + } + /* check if we are doing an open */ + if (fp == NULL) { + /* set defaults */ + ploc->txfifo = NULL; + ploc->rxfifo = NULL; + ploc->is_write = 0; + ploc->is_read = 0; + } else { + /* check for write */ + if (fflags & FWRITE) { + ppf = ploc->udev->fifo; + f = ppf[ploc->ep_index + USB_FIFO_TX]; + ploc->txfifo = f; + ploc->is_write = 1; /* ref */ + if ((f == NULL) || + (f->refcount == USB_FIFO_REF_MAX) || + (f->curr_file != fp)) { + goto error; + } + } else { + ploc->txfifo = NULL; + ploc->is_write = 0; /* no ref */ + } + + /* check for read */ + if (fflags & FREAD) { + ppf = ploc->udev->fifo; + f = ppf[ploc->ep_index + USB_FIFO_RX]; + ploc->rxfifo = f; + ploc->is_read = 1; /* ref */ + if ((f == NULL) || + (f->refcount == USB_FIFO_REF_MAX) || + (f->curr_file != fp)) { + goto error; + } + } else { + ploc->rxfifo = NULL; + ploc->is_read = 0; /* no ref */ + } + } + + /* when everything is OK we increment the refcounts */ + if (ploc->is_write) { + DPRINTFN(2, "ref write\n"); + ploc->txfifo->refcount++; + if (ploc->txfifo->flag_no_uref == 0) { + /* we need extra locking */ + ploc->is_uref = 1; + } + } + if (ploc->is_read) { + DPRINTFN(2, "ref read\n"); + ploc->rxfifo->refcount++; + if (ploc->rxfifo->flag_no_uref == 0) { + /* we need extra locking */ + ploc->is_uref = 1; + } + } + if (ploc->is_uref) { + if (need_uref) { + DPRINTFN(2, "ref udev - needed\n"); + ploc->udev->refcount++; + } else { + DPRINTFN(2, "ref udev - not needed\n"); + ploc->is_uref = 0; + } + } + mtx_unlock(&usb2_ref_lock); + + if (ploc->is_uref) { + /* + * We are about to alter the bus-state. Apply the + * required locks. + */ + sx_xlock(ploc->udev->default_sx + 1); + mtx_lock(&Giant); /* XXX */ + } + return (0); + +error: + mtx_unlock(&usb2_ref_lock); + DPRINTFN(2, "fail\n"); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb2_unref_device + * + * This function will release the reference count by one unit for the + * given USB device. + *------------------------------------------------------------------------*/ +void +usb2_unref_device(struct usb2_location *ploc) +{ + if (ploc->is_uref) { + mtx_unlock(&Giant); /* XXX */ + sx_unlock(ploc->udev->default_sx + 1); + } + mtx_lock(&usb2_ref_lock); + if (ploc->is_read) { + if (--(ploc->rxfifo->refcount) == 0) { + usb2_cv_signal(&ploc->rxfifo->cv_drain); + } + } + if (ploc->is_write) { + if (--(ploc->txfifo->refcount) == 0) { + usb2_cv_signal(&ploc->txfifo->cv_drain); + } + } + if (ploc->is_uref) { + if (--(ploc->udev->refcount) == 0) { + usb2_cv_signal(ploc->udev->default_cv + 1); + } + } + mtx_unlock(&usb2_ref_lock); + return; +} + +static struct usb2_fifo * +usb2_fifo_alloc(void) +{ + struct usb2_fifo *f; + + f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); + if (f) { + usb2_cv_init(&f->cv_io, "FIFO-IO"); + usb2_cv_init(&f->cv_drain, "FIFO-DRAIN"); + f->refcount = 1; + } + return (f); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_create + *------------------------------------------------------------------------*/ +static int +usb2_fifo_create(struct usb2_location *ploc, uint32_t *pdevloc, int fflags) +{ + struct usb2_device *udev = ploc->udev; + struct usb2_fifo *f; + struct usb2_pipe *pipe; + uint8_t iface_index = ploc->iface_index; + uint8_t dev_ep_index = ploc->ep_index; + uint8_t n; + uint8_t is_tx; + uint8_t is_rx; + uint8_t no_null; + uint8_t is_busy; + + is_tx = (fflags & FWRITE) ? 1 : 0; + is_rx = (fflags & FREAD) ? 1 : 0; + no_null = 1; + is_busy = 0; + + /* search for a free FIFO slot */ + + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + if (no_null) { + no_null = 0; + n = 0; + } else { + /* end of FIFOs reached */ + return (ENOMEM); + } + } + /* Check for TX FIFO */ + if (is_tx) { + f = udev->fifo[n + USB_FIFO_TX]; + if (f != NULL) { + if (f->dev_ep_index != dev_ep_index) { + /* wrong endpoint index */ + continue; + } + if ((dev_ep_index != 0) && + (f->iface_index != iface_index)) { + /* wrong interface index */ + continue; + } + if (f->curr_file != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + /* Check for RX FIFO */ + if (is_rx) { + f = udev->fifo[n + USB_FIFO_RX]; + if (f != NULL) { + if (f->dev_ep_index != dev_ep_index) { + /* wrong endpoint index */ + continue; + } + if ((dev_ep_index != 0) && + (f->iface_index != iface_index)) { + /* wrong interface index */ + continue; + } + if (f->curr_file != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + break; + } + + if (no_null == 0) { + if (dev_ep_index >= (USB_EP_MAX / 2)) { + /* we don't create any endpoints in this range */ + return (is_busy ? EBUSY : EINVAL); + } + } + /* Check TX FIFO */ + if (is_tx && + (udev->fifo[n + USB_FIFO_TX] == NULL)) { + pipe = usb2_dev_get_pipe(udev, + iface_index, dev_ep_index, USB_FIFO_TX); + if (pipe == NULL) { + return (EINVAL); + } + f = usb2_fifo_alloc(); + if (f == NULL) { + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_TX; + f->dev_ep_index = dev_ep_index; + f->priv_mtx = udev->default_mtx; + f->priv_sc0 = pipe; + f->methods = &usb2_ugen_methods; + f->iface_index = iface_index; + f->udev = udev; + if (dev_ep_index != 0) { + f->flag_no_uref = 1; + } + mtx_lock(&usb2_ref_lock); + udev->fifo[n + USB_FIFO_TX] = f; + mtx_unlock(&usb2_ref_lock); + } + /* Check RX FIFO */ + if (is_rx && + (udev->fifo[n + USB_FIFO_RX] == NULL)) { + + pipe = usb2_dev_get_pipe(udev, + iface_index, dev_ep_index, USB_FIFO_RX); + if (pipe == NULL) { + return (EINVAL); + } + f = usb2_fifo_alloc(); + if (f == NULL) { + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_RX; + f->dev_ep_index = dev_ep_index; + f->priv_mtx = udev->default_mtx; + f->priv_sc0 = pipe; + f->methods = &usb2_ugen_methods; + f->iface_index = iface_index; + f->udev = udev; + if (dev_ep_index != 0) { + f->flag_no_uref = 1; + } + mtx_lock(&usb2_ref_lock); + udev->fifo[n + USB_FIFO_RX] = f; + mtx_unlock(&usb2_ref_lock); + } + if (is_tx) { + ploc->txfifo = udev->fifo[n + USB_FIFO_TX]; + } + if (is_rx) { + ploc->rxfifo = udev->fifo[n + USB_FIFO_RX]; + } + /* replace endpoint index by FIFO index */ + + (*pdevloc) %= (USB_BUS_MAX * USB_DEV_MAX * USB_IFACE_MAX); + (*pdevloc) += (USB_BUS_MAX * USB_DEV_MAX * USB_IFACE_MAX) * n; + + /* complete */ + + return (0); +} + +void +usb2_fifo_free(struct usb2_fifo *f) +{ + uint8_t n; + + if (f == NULL) { + /* be NULL safe */ + return; + } + /* destroy symlink devices, if any */ + for (n = 0; n != 2; n++) { + if (f->symlink[n]) { + usb2_free_symlink(f->symlink[n]); + f->symlink[n] = NULL; + } + } + mtx_lock(&usb2_ref_lock); + + /* delink ourselves to stop calls from userland */ + if ((f->fifo_index < USB_FIFO_MAX) && + (f->udev != NULL) && + (f->udev->fifo[f->fifo_index] == f)) { + f->udev->fifo[f->fifo_index] = NULL; + } else { + DPRINTFN(0, "USB FIFO %p has not been linked!\n", f); + } + + /* decrease refcount */ + f->refcount--; + /* prevent any write flush */ + f->flag_iserror = 1; + /* need to wait until all callers have exited */ + while (f->refcount != 0) { + mtx_unlock(&usb2_ref_lock); /* avoid LOR */ + mtx_lock(f->priv_mtx); + /* get I/O thread out of any sleep state */ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + usb2_cv_broadcast(&f->cv_io); + } + mtx_unlock(f->priv_mtx); + mtx_lock(&usb2_ref_lock); + + /* wait for sync */ + usb2_cv_wait(&f->cv_drain, &usb2_ref_lock); + } + mtx_unlock(&usb2_ref_lock); + + /* take care of closing the device here, if any */ + usb2_fifo_close(f, curthread, 0); + + usb2_cv_destroy(&f->cv_io); + usb2_cv_destroy(&f->cv_drain); + + free(f, M_USBDEV); + return; +} + +static struct usb2_pipe * +usb2_dev_get_pipe(struct usb2_device *udev, + uint8_t iface_index, uint8_t ep_index, uint8_t dir) +{ + struct usb2_pipe *pipe; + uint8_t ep_dir; + + if (ep_index == 0) { + pipe = &udev->default_pipe; + } else { + if (dir == USB_FIFO_RX) { + if (udev->flags.usb2_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_IN; + } else { + ep_dir = UE_DIR_OUT; + } + } else { + if (udev->flags.usb2_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_OUT; + } else { + ep_dir = UE_DIR_IN; + } + } + pipe = usb2_get_pipe_by_addr(udev, ep_index | ep_dir); + } + + if (pipe == NULL) { + /* if the pipe does not exist then return */ + return (NULL); + } + if (pipe->edesc == NULL) { + /* invalid pipe */ + return (NULL); + } + if (ep_index != 0) { + if (pipe->iface_index != iface_index) { + /* + * Permissions violation - trying to access a + * pipe that does not belong to the interface. + */ + return (NULL); + } + } + return (pipe); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_fifo_open + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_fifo_open(struct usb2_fifo *f, struct file *fp, struct thread *td, + int fflags) +{ + int err; + + if (f == NULL) { + /* no FIFO there */ + DPRINTFN(2, "no FIFO\n"); + return (ENXIO); + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* set correct file flags */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + fflags |= FWRITE; + } else { + fflags |= FREAD; + } + + /* check if we are already opened */ + /* we don't need any locks when checking this variable */ + if (f->curr_file) { + err = EBUSY; + goto done; + } + /* call open method */ + err = (f->methods->f_open) (f, fflags, td); + if (err) { + goto done; + } + mtx_lock(f->priv_mtx); + + /* reset sleep flag */ + f->flag_sleeping = 0; + + /* reset error flag */ + f->flag_iserror = 0; + + /* reset complete flag */ + f->flag_iscomplete = 0; + + /* reset select flag */ + f->flag_isselect = 0; + + /* reset flushing flag */ + f->flag_flushing = 0; + + /* reset ASYNC proc flag */ + f->async_p = NULL; + + /* set which file we belong to */ + mtx_lock(&usb2_ref_lock); + f->curr_file = fp; + mtx_unlock(&usb2_ref_lock); + + /* reset queue */ + usb2_fifo_reset(f); + + mtx_unlock(f->priv_mtx); +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_reset + *------------------------------------------------------------------------*/ +void +usb2_fifo_reset(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + + if (f == NULL) { + return; + } + while (1) { + USB_IF_DEQUEUE(&f->used_q, m); + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + } else { + break; + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_close + *------------------------------------------------------------------------*/ +static void +usb2_fifo_close(struct usb2_fifo *f, struct thread *td, int fflags) +{ + int err; + + /* check if we are not opened */ + if (!f->curr_file) { + /* nothing to do - already closed */ + return; + } + mtx_lock(f->priv_mtx); + + /* clear current file flag */ + f->curr_file = NULL; + + /* check if we are selected */ + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + /* check if a thread wants SIGIO */ + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + f->async_p = NULL; + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* flush written data, if any */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + + if (!f->flag_iserror) { + + /* set flushing flag */ + f->flag_flushing = 1; + + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + + /* check if flushed already */ + while (f->flag_flushing && + (!f->flag_iserror)) { + /* wait until all data has been written */ + f->flag_sleeping = 1; + err = usb2_cv_wait_sig(&f->cv_io, f->priv_mtx); + if (err) { + DPRINTF("signal received\n"); + break; + } + } + } + fflags |= FWRITE; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_write) (f); + } else { + fflags |= FREAD; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_read) (f); + } + + /* check if we are sleeping */ + if (f->flag_sleeping) { + DPRINTFN(2, "Sleeping at close!\n"); + } + mtx_unlock(f->priv_mtx); + + /* call close method */ + (f->methods->f_close) (f, fflags, td); + + DPRINTF("closed\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_check_thread_perm + * + * Returns: + * 0: Has permission. + * Else: No permission. + *------------------------------------------------------------------------*/ +int +usb2_check_thread_perm(struct usb2_device *udev, struct thread *td, + int fflags, uint8_t iface_index, uint8_t ep_index) +{ + struct usb2_interface *iface; + int err; + + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (EINVAL); + } + if (iface->idesc == NULL) { + return (EINVAL); + } + /* scan down the permissions tree */ + if ((ep_index != 0) && iface && + (usb2_check_access(fflags, &iface->perm) == 0)) { + /* we got access through the interface */ + err = 0; + } else if (udev && + (usb2_check_access(fflags, &udev->perm) == 0)) { + /* we got access through the device */ + err = 0; + } else if (udev->bus && + (usb2_check_access(fflags, &udev->bus->perm) == 0)) { + /* we got access through the USB bus */ + err = 0; + } else if (usb2_check_access(fflags, &usb2_perm) == 0) { + /* we got general access */ + err = 0; + } else { + /* no access */ + err = EPERM; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_fdopen - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_fdopen(struct cdev *dev, int xxx_oflags, struct thread *td, + struct file *fp) +{ + struct usb2_location loc; + uint32_t devloc; + int err; + int fflags; + + DPRINTFN(2, "oflags=0x%08x\n", xxx_oflags); + + devloc = usb2_last_devloc; + usb2_last_devloc = (0 - 1); /* reset "usb2_last_devloc" */ + + if (fp == NULL) { + DPRINTFN(2, "fp == NULL\n"); + return (ENXIO); + } + if (usb2_old_f_data != fp->f_data) { + if (usb2_old_f_data != NULL) { + DPRINTFN(0, "File data mismatch!\n"); + return (ENXIO); + } + usb2_old_f_data = fp->f_data; + } + if (usb2_old_f_ops != fp->f_ops) { + if (usb2_old_f_ops != NULL) { + DPRINTFN(0, "File ops mismatch!\n"); + return (ENXIO); + } + usb2_old_f_ops = fp->f_ops; + } + fflags = fp->f_flag; + DPRINTFN(2, "fflags=0x%08x\n", fflags); + + if (!(fflags & (FREAD | FWRITE))) { + /* should not happen */ + return (EPERM); + } + if (devloc == (uint32_t)(0 - 2)) { + /* tried to open "/dev/usb" */ + return (0); + } else if (devloc == (uint32_t)(0 - 1)) { + /* tried to open "/dev/usb " */ + DPRINTFN(2, "no devloc\n"); + return (ENXIO); + } + err = usb2_ref_device(NULL, &loc, devloc); + if (err) { + DPRINTFN(2, "cannot ref device\n"); + return (ENXIO); + } + err = usb2_check_thread_perm(loc.udev, td, fflags, + loc.iface_index, loc.ep_index); + + /* check for error */ + if (err) { + usb2_unref_device(&loc); + return (err); + } + /* create FIFOs, if any */ + err = usb2_fifo_create(&loc, &devloc, fflags); + /* check for error */ + if (err) { + usb2_unref_device(&loc); + return (err); + } + if (fflags & FREAD) { + err = usb2_fifo_open(loc.rxfifo, fp, td, fflags); + if (err) { + DPRINTFN(2, "read open failed\n"); + usb2_unref_device(&loc); + return (err); + } + } + if (fflags & FWRITE) { + err = usb2_fifo_open(loc.txfifo, fp, td, fflags); + if (err) { + DPRINTFN(2, "write open failed\n"); + if (fflags & FREAD) { + usb2_fifo_close(loc.rxfifo, td, + fflags); + } + usb2_unref_device(&loc); + return (err); + } + } + /* + * Take over the file so that we get all the callbacks + * directly and don't have to create another device: + */ + finit(fp, fp->f_flag, DTYPE_VNODE, + ((uint8_t *)0) + devloc, &usb2_ops_f); + + usb2_unref_device(&loc); + + DPRINTFN(2, "error=%d\n", err); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_close - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_close(struct cdev *dev, int flag, int mode, struct thread *p) +{ + DPRINTF("\n"); + return (0); /* nothing to do */ +} + +/*------------------------------------------------------------------------* + * usb2_close - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_ioctl(struct cdev *dev, u_long cmd, caddr_t data, + int fflag, struct thread *td) +{ + union { + struct usb2_read_dir *urd; + struct usb2_dev_perm *udp; + void *data; + } u; + int err; + + u.data = data; + + switch (cmd) { + case USB_READ_DIR: + err = usb2_read_symlink(u.urd->urd_data, + u.urd->urd_startentry, u.urd->urd_maxlen); + break; + case USB_SET_IFACE_PERM: + err = usb2_set_perm(u.udp, 3); + break; + case USB_SET_DEVICE_PERM: + err = usb2_set_perm(u.udp, 2); + break; + case USB_SET_BUS_PERM: + err = usb2_set_perm(u.udp, 1); + break; + case USB_SET_ROOT_PERM: + err = usb2_set_perm(u.udp, 0); + break; + case USB_GET_IFACE_PERM: + err = usb2_get_perm(u.udp, 3); + break; + case USB_GET_DEVICE_PERM: + err = usb2_get_perm(u.udp, 2); + break; + case USB_GET_BUS_PERM: + err = usb2_get_perm(u.udp, 1); + break; + case USB_GET_ROOT_PERM: + err = usb2_get_perm(u.udp, 0); + break; + case USB_DEV_QUIRK_GET: + case USB_QUIRK_NAME_GET: + case USB_DEV_QUIRK_ADD: + case USB_DEV_QUIRK_REMOVE: + err = usb2_quirk_ioctl_p(cmd, data, fflag, td); + break; + default: + err = ENOTTY; + break; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_clone - cdev callback + * + * This function is the kernel clone callback for "/dev/usbX.Y". + * + * NOTE: This function assumes that the clone and device open + * operation is atomic. + *------------------------------------------------------------------------*/ +static void +usb2_clone(void *arg, USB_UCRED char *name, int namelen, struct cdev **dev) +{ + enum { + USB_DNAME_LEN = sizeof(USB_DEVICE_NAME) - 1, + USB_GNAME_LEN = sizeof(USB_GENERIC_NAME) - 1, + }; + + if (*dev) { + /* someone else has created a device */ + return; + } + /* reset device location */ + usb2_last_devloc = (uint32_t)(0 - 1); + + /* + * Check if we are matching "usb", "ugen" or an internal + * symbolic link: + */ + if ((namelen >= USB_DNAME_LEN) && + (bcmp(name, USB_DEVICE_NAME, USB_DNAME_LEN) == 0)) { + if (namelen == USB_DNAME_LEN) { + /* USB management device location */ + usb2_last_devloc = (uint32_t)(0 - 2); + } else { + /* USB endpoint */ + usb2_last_devloc = + usb2_path_convert(name + USB_DNAME_LEN); + } + } else if ((namelen >= USB_GNAME_LEN) && + (bcmp(name, USB_GENERIC_NAME, USB_GNAME_LEN) == 0)) { + if (namelen == USB_GNAME_LEN) { + /* USB management device location */ + usb2_last_devloc = (uint32_t)(0 - 2); + } else { + /* USB endpoint */ + usb2_last_devloc = + usb2_path_convert(name + USB_GNAME_LEN); + } + } + if (usb2_last_devloc == (uint32_t)(0 - 1)) { + /* Search for symbolic link */ + usb2_last_devloc = + usb2_lookup_symlink(name, namelen); + } + if (usb2_last_devloc == (uint32_t)(0 - 1)) { + /* invalid location */ + return; + } + dev_ref(usb2_dev); + *dev = usb2_dev; + return; +} + +static void +usb2_dev_init(void *arg) +{ + mtx_init(&usb2_ref_lock, "USB ref mutex", NULL, MTX_DEF); + sx_init(&usb2_sym_lock, "USB sym mutex"); + TAILQ_INIT(&usb2_sym_head); + + /* check the UGEN methods */ + usb2_fifo_check_methods(&usb2_ugen_methods); + return; +} + +SYSINIT(usb2_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb2_dev_init, NULL); + +static void +usb2_dev_init_post(void *arg) +{ + /* + * Create a dummy device so that we are visible. This device + * should never be opened. Therefore a space character is + * appended after the USB device name. + * + * NOTE: The permissions of this device is 0777, because we + * check the permissions again in the open routine against the + * real USB permissions which are not 0777. Else USB access + * will be limited to one user and one group. + */ + usb2_dev = make_dev(&usb2_devsw, 0, UID_ROOT, GID_OPERATOR, + 0777, USB_DEVICE_NAME " "); + if (usb2_dev == NULL) { + DPRINTFN(0, "Could not create usb bus device!\n"); + } + usb2_clone_tag = EVENTHANDLER_REGISTER(dev_clone, usb2_clone_ptr, NULL, 1000); + if (usb2_clone_tag == NULL) { + DPRINTFN(0, "Registering clone handler failed!\n"); + } + return; +} + +SYSINIT(usb2_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb2_dev_init_post, NULL); + +static void +usb2_dev_uninit(void *arg) +{ + if (usb2_clone_tag) { + EVENTHANDLER_DEREGISTER(dev_clone, usb2_clone_tag); + usb2_clone_tag = NULL; + } + if (usb2_dev) { + destroy_dev(usb2_dev); + usb2_dev = NULL; + } + mtx_destroy(&usb2_ref_lock); + sx_destroy(&usb2_sym_lock); + return; +} + +SYSUNINIT(usb2_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb2_dev_uninit, NULL); + +static int +usb2_close_f(struct file *fp, struct thread *td) +{ + struct usb2_location loc; + int fflags; + int err; + + fflags = fp->f_flag; + + DPRINTFN(2, "fflags=%u\n", fflags); + + err = usb2_ref_device(fp, &loc, 0);; + + /* restore some file variables */ + fp->f_ops = usb2_old_f_ops; + fp->f_data = usb2_old_f_data; + + /* check for error */ + if (err) { + DPRINTFN(2, "could not ref\n"); + goto done; + } + if (fflags & FREAD) { + usb2_fifo_close(loc.rxfifo, td, fflags); + } + if (fflags & FWRITE) { + usb2_fifo_close(loc.txfifo, td, fflags); + } + usb2_unref_device(&loc); + +done: + /* call old close method */ + USB_VNOPS_FO_CLOSE(fp, td, &err); + + return (err); +} + +static int +usb2_ioctl_f_sub(struct usb2_fifo *f, u_long cmd, void *addr, + struct thread *td) +{ + int error = 0; + + switch (cmd) { + case FIODTYPE: + *(int *)addr = 0; /* character device */ + break; + + case FIONBIO: + /* handled by upper FS layer */ + break; + + case FIOASYNC: + if (*(int *)addr) { + if (f->async_p != NULL) { + error = EBUSY; + break; + } + f->async_p = USB_TD_GET_PROC(td); + } else { + f->async_p = NULL; + } + break; + + /* XXX this is not the most general solution */ + case TIOCSPGRP: + if (f->async_p == NULL) { + error = EINVAL; + break; + } + if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { + error = EPERM; + break; + } + break; + default: + return (ENOTTY); + } + return (error); +} + +static int +usb2_ioctl_f(struct file *fp, u_long cmd, void *addr, + struct ucred *cred, struct thread *td) +{ + struct usb2_location loc; + int fflags; + int err_rx; + int err_tx; + int err; + uint8_t is_common = 0; + + err = usb2_ref_device(fp, &loc, 0);; + if (err) { + return (ENXIO); + } + fflags = fp->f_flag; + + DPRINTFN(2, "fflags=%u, cmd=0x%lx\n", fflags, cmd); + + if (fflags & FREAD) { + if (fflags & FWRITE) { + /* + * Make sure that the IOCTL is not + * duplicated: + */ + is_common = 1; + } + err_rx = usb2_ioctl_f_sub(loc.rxfifo, cmd, addr, td); + if (err_rx == ENOTTY) { + err_rx = (loc.rxfifo->methods->f_ioctl) ( + loc.rxfifo, cmd, addr, + is_common ? fflags : (fflags & ~FWRITE), td); + } + } else { + err_rx = 0; + } + if (fflags & FWRITE) { + err_tx = usb2_ioctl_f_sub(loc.txfifo, cmd, addr, td); + if (err_tx == ENOTTY) { + if (is_common) + err_tx = 0; /* already handled this IOCTL */ + else + err_tx = (loc.txfifo->methods->f_ioctl) ( + loc.txfifo, cmd, addr, fflags & ~FREAD, td); + } + } else { + err_tx = 0; + } + + if (err_rx) { + err = err_rx; + } else if (err_tx) { + err = err_tx; + } else { + err = 0; /* no error */ + } + usb2_unref_device(&loc); + return (err); +} + +/* ARGSUSED */ +static int +usb2_kqfilter_f(struct file *fp, struct knote *kn) +{ + return (ENXIO); +} + +/* ARGSUSED */ +static int +usb2_poll_f(struct file *fp, int events, + struct ucred *cred, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int revents; + uint8_t usbfs_active = 0; + + revents = usb2_ref_device(fp, &loc, 1 /* no uref */ );; + if (revents) { + return (POLLHUP); + } + fflags = fp->f_flag; + + /* figure out if the USB File System is active */ + + if (fflags & FWRITE) { + f = loc.txfifo; + if (f->fs_ep_max != 0) { + usbfs_active = 1; + } + } + if (fflags & FREAD) { + f = loc.rxfifo; + if (f->fs_ep_max != 0) { + usbfs_active = 1; + } + } + /* Figure out who needs service */ + + if ((events & (POLLOUT | POLLWRNORM)) && + (fflags & FWRITE)) { + + f = loc.txfifo; + + mtx_lock(f->priv_mtx); + + if (!usbfs_active) { + if (f->flag_iserror) { + /* we got an error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start write transfer, if not + * already started + */ + (f->methods->f_start_write) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->free_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLOUT | POLLWRNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + } + + mtx_unlock(f->priv_mtx); + } + if ((events & (POLLIN | POLLRDNORM)) && + (fflags & FREAD)) { + + f = loc.rxfifo; + + mtx_lock(f->priv_mtx); + + if (!usbfs_active) { + if (f->flag_iserror) { + /* we have and error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start read transfer, if not + * already started + */ + (f->methods->f_start_read) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->used_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLIN | POLLRDNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + + /* start reading data */ + (f->methods->f_start_read) (f); + } + + mtx_unlock(f->priv_mtx); + } + usb2_unref_device(&loc); + return (revents); +} + +/* ARGSUSED */ +static int +usb2_read_f(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + fflags = fp->f_flag & (O_NONBLOCK | O_DIRECT | FREAD | FWRITE); + if (fflags & O_DIRECT) + fflags |= IO_DIRECT; + + err = usb2_ref_device(fp, &loc, 1 /* no uref */ ); + if (err) { + return (ENXIO); + } + f = loc.rxfifo; + if (f == NULL) { + /* should not happen */ + return (EPERM); + } + resid = uio->uio_resid; + + if ((flags & FOF_OFFSET) == 0) + uio->uio_offset = fp->f_offset; + + mtx_lock(f->priv_mtx); + + if (f->flag_iserror) { + err = EIO; + goto done; + } + while (uio->uio_resid > 0) { + + if (f->fs_ep_max == 0) { + USB_IF_DEQUEUE(&f->used_q, m); + } else { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" + * ioctl. + */ + m = NULL; + } + + if (m == NULL) { + + /* start read transfer, if not already started */ + + (f->methods->f_start_read) (f); + + if (fflags & O_NONBLOCK) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb2_fifo_wait(f); + if (err) { + break; + } + continue; + } else { + tr_data = 1; + } + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + DPRINTFN(2, "transfer %d bytes from %p\n", + io_len, m->cur_data_ptr); + + err = usb2_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + m->cur_data_len -= io_len; + m->cur_data_ptr += io_len; + + if (m->cur_data_len == 0) { + + uint8_t last_packet; + + last_packet = m->last_packet; + + USB_IF_ENQUEUE(&f->free_q, m); + + if (last_packet) { + /* keep framing */ + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + + if (err) { + break; + } + } +done: + mtx_unlock(f->priv_mtx); + + usb2_unref_device(&loc); + + if ((flags & FOF_OFFSET) == 0) + fp->f_offset = uio->uio_offset; + fp->f_nextoff = uio->uio_offset; + return (err); +} + +static int +usb2_stat_f(struct file *fp, struct stat *sb, struct ucred *cred, struct thread *td) +{ + return (USB_VNOPS_FO_STAT(fp, sb, cred, td)); +} + +#if __FreeBSD_version > 800009 +static int +usb2_truncate_f(struct file *fp, off_t length, struct ucred *cred, struct thread *td) +{ + return (USB_VNOPS_FO_TRUNCATE(fp, length, cred, td)); +} + +#endif + +/* ARGSUSED */ +static int +usb2_write_f(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + fflags = fp->f_flag & (O_NONBLOCK | O_DIRECT | + FREAD | FWRITE | O_FSYNC); + if (fflags & O_DIRECT) + fflags |= IO_DIRECT; + + err = usb2_ref_device(fp, &loc, 1 /* no uref */ ); + if (err) { + return (ENXIO); + } + f = loc.txfifo; + if (f == NULL) { + /* should not happen */ + usb2_unref_device(&loc); + return (EPERM); + } + resid = uio->uio_resid; + + if ((flags & FOF_OFFSET) == 0) + uio->uio_offset = fp->f_offset; + + mtx_lock(f->priv_mtx); + + if (f->flag_iserror) { + err = EIO; + goto done; + } + if ((f->queue_data == NULL) && (f->fs_ep_max == 0)) { + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + } + /* we allow writing zero length data */ + do { + if (f->fs_ep_max == 0) { + USB_IF_DEQUEUE(&f->free_q, m); + } else { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" + * ioctl. + */ + m = NULL; + } + + if (m == NULL) { + + if (fflags & O_NONBLOCK) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb2_fifo_wait(f); + if (err) { + break; + } + continue; + } else { + tr_data = 1; + } + + USB_MBUF_RESET(m); + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + m->cur_data_len = io_len; + + DPRINTFN(2, "transfer %d bytes to %p\n", + io_len, m->cur_data_ptr); + + err = usb2_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + if (err) { + USB_IF_ENQUEUE(&f->free_q, m); + break; + } else { + USB_IF_ENQUEUE(&f->used_q, m); + (f->methods->f_start_write) (f); + } + } while (uio->uio_resid > 0); +done: + mtx_unlock(f->priv_mtx); + + usb2_unref_device(&loc); + + if ((flags & FOF_OFFSET) == 0) + fp->f_offset = uio->uio_offset; + fp->f_nextoff = uio->uio_offset; + + return (err); +} + +static int +usb2_fifo_uiomove(struct usb2_fifo *f, void *cp, + int n, struct uio *uio) +{ + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things: + */ + error = uiomove(cp, n, uio); + + mtx_lock(f->priv_mtx); + + return (error); +} + +int +usb2_fifo_wait(struct usb2_fifo *f) +{ + int err; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->flag_iserror) { + /* we are gone */ + return (EIO); + } + f->flag_sleeping = 1; + + err = usb2_cv_wait_sig(&f->cv_io, f->priv_mtx); + + if (f->flag_iserror) { + /* we are gone */ + err = EIO; + } + return (err); +} + +void +usb2_fifo_signal(struct usb2_fifo *f) +{ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + usb2_cv_broadcast(&f->cv_io); + } + return; +} + +void +usb2_fifo_wakeup(struct usb2_fifo *f) +{ + usb2_fifo_signal(f); + + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_opened + * + * Returns: + * 0: FIFO not opened. + * Else: FIFO is opened. + *------------------------------------------------------------------------*/ +uint8_t +usb2_fifo_opened(struct usb2_fifo *f) +{ + uint8_t temp; + uint8_t do_unlock; + + if (f == NULL) { + return (0); /* be NULL safe */ + } + if (mtx_owned(f->priv_mtx)) { + do_unlock = 0; + } else { + do_unlock = 1; + mtx_lock(f->priv_mtx); + } + temp = f->curr_file ? 1 : 0; + if (do_unlock) { + mtx_unlock(f->priv_mtx); + } + return (temp); +} + + +static int +usb2_fifo_dummy_open(struct usb2_fifo *fifo, + int fflags, struct thread *td) +{ + return (0); +} + +static void +usb2_fifo_dummy_close(struct usb2_fifo *fifo, + int fflags, struct thread *td) +{ + return; +} + +static int +usb2_fifo_dummy_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + return (ENOTTY); +} + +static void +usb2_fifo_dummy_cmd(struct usb2_fifo *fifo) +{ + fifo->flag_flushing = 0; /* not flushing */ + return; +} + +static void +usb2_fifo_check_methods(struct usb2_fifo_methods *pm) +{ + /* check that all callback functions are OK */ + + if (pm->f_open == NULL) + pm->f_open = &usb2_fifo_dummy_open; + + if (pm->f_close == NULL) + pm->f_close = &usb2_fifo_dummy_close; + + if (pm->f_ioctl == NULL) + pm->f_ioctl = &usb2_fifo_dummy_ioctl; + + if (pm->f_start_read == NULL) + pm->f_start_read = &usb2_fifo_dummy_cmd; + + if (pm->f_stop_read == NULL) + pm->f_stop_read = &usb2_fifo_dummy_cmd; + + if (pm->f_start_write == NULL) + pm->f_start_write = &usb2_fifo_dummy_cmd; + + if (pm->f_stop_write == NULL) + pm->f_stop_write = &usb2_fifo_dummy_cmd; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_attach + * + * The following function will create a duplex FIFO. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +int +usb2_fifo_attach(struct usb2_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb2_fifo_methods *pm, + struct usb2_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index) +{ + struct usb2_fifo *f_tx; + struct usb2_fifo *f_rx; + char buf[32]; + char src[32]; + uint8_t n; + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + if (pm == NULL) + return (EINVAL); + + /* check the methods */ + usb2_fifo_check_methods(pm); + + if (priv_mtx == NULL) + priv_mtx = &Giant; + + /* search for a free FIFO slot */ + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + /* end of FIFOs reached */ + return (ENOMEM); + } + /* Check for TX FIFO */ + if (udev->fifo[n + USB_FIFO_TX] != NULL) { + continue; + } + /* Check for RX FIFO */ + if (udev->fifo[n + USB_FIFO_RX] != NULL) { + continue; + } + break; + } + + f_tx = usb2_fifo_alloc(); + f_rx = usb2_fifo_alloc(); + + if ((f_tx == NULL) || (f_rx == NULL)) { + usb2_fifo_free(f_tx); + usb2_fifo_free(f_rx); + return (ENOMEM); + } + /* initialise FIFO structures */ + + f_tx->fifo_index = n + USB_FIFO_TX; + f_tx->dev_ep_index = (n / 2) + (USB_EP_MAX / 2); + f_tx->priv_mtx = priv_mtx; + f_tx->priv_sc0 = priv_sc; + f_tx->methods = pm; + f_tx->iface_index = iface_index; + f_tx->udev = udev; + f_tx->flag_no_uref = 1; + + f_rx->fifo_index = n + USB_FIFO_RX; + f_rx->dev_ep_index = (n / 2) + (USB_EP_MAX / 2); + f_rx->priv_mtx = priv_mtx; + f_rx->priv_sc0 = priv_sc; + f_rx->methods = pm; + f_rx->iface_index = iface_index; + f_rx->udev = udev; + f_rx->flag_no_uref = 1; + + f_sc->fp[USB_FIFO_TX] = f_tx; + f_sc->fp[USB_FIFO_RX] = f_rx; + + mtx_lock(&usb2_ref_lock); + udev->fifo[f_tx->fifo_index] = f_tx; + udev->fifo[f_rx->fifo_index] = f_rx; + mtx_unlock(&usb2_ref_lock); + + if (snprintf(src, sizeof(src), + USB_DEVICE_NAME "%u.%u.%u.%u", + device_get_unit(udev->bus->bdev), + udev->device_index, + iface_index, + f_tx->dev_ep_index)) { + /* ignore */ + } + for (n = 0; n != 4; n++) { + + if (pm->basename[n] == NULL) { + continue; + } + if (subunit == 0xFFFF) { + if (snprintf(buf, sizeof(buf), + "%s%u%s", pm->basename[n], + unit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } else { + if (snprintf(buf, sizeof(buf), + "%s%u.%u%s", pm->basename[n], + unit, subunit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } + + /* + * Distribute the symbolic links into two FIFO structures: + */ + if (n & 1) { + f_rx->symlink[n / 2] = + usb2_alloc_symlink(src, "%s", buf); + } else { + f_tx->symlink[n / 2] = + usb2_alloc_symlink(src, "%s", buf); + } + printf("Symlink: %s -> %s\n", buf, src); + } + + DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_alloc_buffer + * + * Return values: + * 0: Success + * Else failure + *------------------------------------------------------------------------*/ +int +usb2_fifo_alloc_buffer(struct usb2_fifo *f, uint32_t bufsize, + uint16_t nbuf) +{ + usb2_fifo_free_buffer(f); + + /* allocate an endpoint */ + f->free_q.ifq_maxlen = nbuf; + f->used_q.ifq_maxlen = nbuf; + + f->queue_data = usb2_alloc_mbufs( + M_USBDEV, &f->free_q, bufsize, nbuf); + + if ((f->queue_data == NULL) && bufsize && nbuf) { + return (ENOMEM); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_fifo_free_buffer + * + * This function will free the buffers associated with a FIFO. This + * function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_fifo_free_buffer(struct usb2_fifo *f) +{ + if (f->queue_data) { + /* free old buffer */ + free(f->queue_data, M_USBDEV); + f->queue_data = NULL; + } + /* reset queues */ + + bzero(&f->free_q, sizeof(f->free_q)); + bzero(&f->used_q, sizeof(f->used_q)); + return; +} + +void +usb2_fifo_detach(struct usb2_fifo_sc *f_sc) +{ + if (f_sc == NULL) { + return; + } + usb2_fifo_free(f_sc->fp[USB_FIFO_TX]); + usb2_fifo_free(f_sc->fp[USB_FIFO_RX]); + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + DPRINTFN(2, "detached %p\n", f_sc); + + return; +} + +uint32_t +usb2_fifo_put_bytes_max(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + uint32_t len; + + USB_IF_POLL(&f->free_q, m); + + if (m) { + len = m->max_data_len; + } else { + len = 0; + } + return (len); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_put_data + * + * what: + * 0 - normal operation + * 1 - set last packet flag to enforce framing + *------------------------------------------------------------------------*/ +void +usb2_fifo_put_data(struct usb2_fifo *f, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + usb2_copy_out(pc, offset, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + offset += io_len; + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb2_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } + return; +} + +void +usb2_fifo_put_data_linear(struct usb2_fifo *f, void *ptr, + uint32_t len, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + bcopy(ptr, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb2_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } + return; +} + +uint8_t +usb2_fifo_put_data_buffer(struct usb2_fifo *f, void *ptr, uint32_t len) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + m->cur_data_len = len; + m->cur_data_ptr = ptr; + USB_IF_ENQUEUE(&f->used_q, m); + usb2_fifo_wakeup(f); + return (1); + } + return (0); +} + +void +usb2_fifo_put_data_error(struct usb2_fifo *f) +{ + f->flag_iserror = 1; + usb2_fifo_wakeup(f); + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_get_data + * + * what: + * 0 - normal operation + * 1 - only get one "usb2_mbuf" + * + * returns: + * 0 - no more data + * 1 - data in buffer + *------------------------------------------------------------------------*/ +uint8_t +usb2_fifo_get_data(struct usb2_fifo *f, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen, + uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + usb2_copy_in(pc, offset, m->cur_data_ptr, io_len); + + len -= io_len; + offset += io_len; + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb2_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + f->flag_flushing = 0; + usb2_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb2_fifo_get_data_linear(struct usb2_fifo *f, void *ptr, + uint32_t len, uint32_t *actlen, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + bcopy(m->cur_data_ptr, ptr, io_len); + + len -= io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb2_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + f->flag_flushing = 0; + usb2_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb2_fifo_get_data_buffer(struct usb2_fifo *f, void **pptr, uint32_t *plen) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *plen = m->cur_data_len; + *pptr = m->cur_data_ptr; + + USB_IF_PREPEND(&f->used_q, m); + return (1); + } + return (0); +} + +void +usb2_fifo_get_data_next(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + usb2_fifo_wakeup(f); + } + return; +} + +void +usb2_fifo_get_data_error(struct usb2_fifo *f) +{ + f->flag_iserror = 1; + usb2_fifo_wakeup(f); + return; +} + +/*------------------------------------------------------------------------* + * usb2_alloc_symlink + * + * Return values: + * NULL: Failure + * Else: Pointer to symlink entry + *------------------------------------------------------------------------*/ +struct usb2_symlink * +usb2_alloc_symlink(const char *target, const char *fmt,...) +{ + struct usb2_symlink *ps; + va_list ap; + + ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); + if (ps == NULL) { + return (ps); + } + strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); + ps->dst_len = strlen(ps->dst_path); + + va_start(ap, fmt); + vsnrprintf(ps->src_path, + sizeof(ps->src_path), 32, fmt, ap); + va_end(ap); + ps->src_len = strlen(ps->src_path); + + sx_xlock(&usb2_sym_lock); + TAILQ_INSERT_TAIL(&usb2_sym_head, ps, sym_entry); + sx_unlock(&usb2_sym_lock); + return (ps); +} + +/*------------------------------------------------------------------------* + * usb2_free_symlink + *------------------------------------------------------------------------*/ +void +usb2_free_symlink(struct usb2_symlink *ps) +{ + if (ps == NULL) { + return; + } + sx_xlock(&usb2_sym_lock); + TAILQ_REMOVE(&usb2_sym_head, ps, sym_entry); + sx_unlock(&usb2_sym_lock); + + free(ps, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb2_lookup_symlink + * + * Return value: + * Numerical device location + *------------------------------------------------------------------------*/ +uint32_t +usb2_lookup_symlink(const char *src_ptr, uint8_t src_len) +{ + enum { + USB_DNAME_LEN = sizeof(USB_DEVICE_NAME) - 1, + }; + struct usb2_symlink *ps; + uint32_t temp; + + sx_xlock(&usb2_sym_lock); + + TAILQ_FOREACH(ps, &usb2_sym_head, sym_entry) { + + if (src_len != ps->src_len) + continue; + + if (memcmp(ps->src_path, src_ptr, src_len)) + continue; + + if (USB_DNAME_LEN > ps->dst_len) + continue; + + if (memcmp(ps->dst_path, USB_DEVICE_NAME, USB_DNAME_LEN)) + continue; + + temp = usb2_path_convert(ps->dst_path + USB_DNAME_LEN); + sx_unlock(&usb2_sym_lock); + + return (temp); + } + sx_unlock(&usb2_sym_lock); + return (0 - 1); +} + +/*------------------------------------------------------------------------* + * usb2_read_symlink + * + * Return value: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) +{ + struct usb2_symlink *ps; + uint32_t temp; + uint32_t delta = 0; + uint8_t len; + int error = 0; + + sx_xlock(&usb2_sym_lock); + + TAILQ_FOREACH(ps, &usb2_sym_head, sym_entry) { + + /* + * Compute total length of source and destination symlink + * strings pluss one length byte and two NUL bytes: + */ + temp = ps->src_len + ps->dst_len + 3; + + if (temp > 255) { + /* + * Skip entry because this length cannot fit + * into one byte: + */ + continue; + } + if (startentry != 0) { + /* decrement read offset */ + startentry--; + continue; + } + if (temp > user_len) { + /* out of buffer space */ + break; + } + len = temp; + + /* copy out total length */ + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out source string */ + + error = copyout(ps->src_path, + USB_ADD_BYTES(user_ptr, delta), ps->src_len); + if (error) { + break; + } + len = 0; + delta += ps->src_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out destination string */ + + error = copyout(ps->dst_path, + USB_ADD_BYTES(user_ptr, delta), ps->dst_len); + if (error) { + break; + } + len = 0; + delta += ps->dst_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + user_len -= temp; + } + + /* a zero length entry indicates the end */ + + if ((user_len != 0) && (error == 0)) { + + len = 0; + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + } + sx_unlock(&usb2_sym_lock); + return (error); +} diff --git a/sys/dev/usb2/core/usb2_dev.h b/sys/dev/usb2/core/usb2_dev.h new file mode 100644 index 000000000000..859dd4ebb728 --- /dev/null +++ b/sys/dev/usb2/core/usb2_dev.h @@ -0,0 +1,149 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_DEV_H_ +#define _USB2_DEV_H_ + +#include <sys/file.h> +#include <sys/vnode.h> +#include <sys/poll.h> +#include <sys/signalvar.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/proc.h> + +#define USB_FIFO_TX 0 +#define USB_FIFO_RX 1 + +struct usb2_fifo; + +typedef int (usb2_fifo_open_t)(struct usb2_fifo *fifo, int fflags, struct thread *td); +typedef void (usb2_fifo_close_t)(struct usb2_fifo *fifo, int fflags, struct thread *td); +typedef int (usb2_fifo_ioctl_t)(struct usb2_fifo *fifo, u_long cmd, void *addr, int fflags, struct thread *td); +typedef void (usb2_fifo_cmd_t)(struct usb2_fifo *fifo); + +struct usb2_symlink { + TAILQ_ENTRY(usb2_symlink) sym_entry; + char src_path[32]; /* Source path - including terminating + * zero */ + char dst_path[32]; /* Destination path - including + * terminating zero */ + uint8_t src_len; /* String length */ + uint8_t dst_len; /* String length */ +}; + +/* + * Locking note for the following functions. All the + * "usb2_fifo_cmd_t" functions are called locked. The others are + * called unlocked. + */ +struct usb2_fifo_methods { + usb2_fifo_open_t *f_open; + usb2_fifo_close_t *f_close; + usb2_fifo_ioctl_t *f_ioctl; + usb2_fifo_cmd_t *f_start_read; + usb2_fifo_cmd_t *f_stop_read; + usb2_fifo_cmd_t *f_start_write; + usb2_fifo_cmd_t *f_stop_write; + const char *basename[4]; + const char *postfix[4]; +}; + +/* + * Most of the fields in the "usb2_fifo" structure are used by the + * generic USB access layer. + */ +struct usb2_fifo { + struct usb2_ifqueue free_q; + struct usb2_ifqueue used_q; + struct selinfo selinfo; + struct cv cv_io; + struct cv cv_drain; + struct usb2_fifo_methods *methods; + struct usb2_symlink *symlink[2];/* our symlinks */ + struct proc *async_p; /* process that wants SIGIO */ + struct usb2_fs_endpoint *fs_ep_ptr; + struct usb2_device *udev; + struct usb2_xfer *xfer[2]; + struct usb2_xfer **fs_xfer; + struct mtx *priv_mtx; /* client data */ + struct file *curr_file; /* set if FIFO is opened by a FILE */ + void *priv_sc0; /* client data */ + void *priv_sc1; /* client data */ + void *queue_data; + uint32_t timeout; /* timeout in milliseconds */ + uint32_t bufsize; /* BULK and INTERRUPT buffer size */ + uint16_t nframes; /* for isochronous mode */ + uint16_t dev_ep_index; /* our device endpoint index */ + uint8_t flag_no_uref; /* set if FIFO is not control endpoint */ + uint8_t flag_sleeping; /* set if FIFO is sleeping */ + uint8_t flag_iscomplete; /* set if a USB transfer is complete */ + uint8_t flag_iserror; /* set if FIFO error happened */ + uint8_t flag_isselect; /* set if FIFO is selected */ + uint8_t flag_flushing; /* set if FIFO is flushing data */ + uint8_t flag_short; /* set if short_ok or force_short + * transfer flags should be set */ + uint8_t flag_stall; /* set if clear stall should be run */ + uint8_t iface_index; /* set to the interface we belong to */ + uint8_t fifo_index; /* set to the FIFO index in "struct + * usb2_device" */ + uint8_t fs_ep_max; + uint8_t fifo_zlp; /* zero length packet count */ + uint8_t refcount; +#define USB_FIFO_REF_MAX 0xFF +}; + +struct usb2_fifo_sc { + struct usb2_fifo *fp[2]; +}; + +int usb2_fifo_wait(struct usb2_fifo *fifo); +void usb2_fifo_signal(struct usb2_fifo *fifo); +int usb2_fifo_alloc_buffer(struct usb2_fifo *f, uint32_t bufsize, uint16_t nbuf); +void usb2_fifo_free_buffer(struct usb2_fifo *f); +int usb2_fifo_attach(struct usb2_device *udev, void *priv_sc, struct mtx *priv_mtx, struct usb2_fifo_methods *pm, struct usb2_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, uint8_t iface_index); +void usb2_fifo_detach(struct usb2_fifo_sc *f_sc); +uint32_t usb2_fifo_put_bytes_max(struct usb2_fifo *fifo); +void usb2_fifo_put_data(struct usb2_fifo *fifo, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint8_t what); +void usb2_fifo_put_data_linear(struct usb2_fifo *fifo, void *ptr, uint32_t len, uint8_t what); +uint8_t usb2_fifo_put_data_buffer(struct usb2_fifo *f, void *ptr, uint32_t len); +void usb2_fifo_put_data_error(struct usb2_fifo *fifo); +uint8_t usb2_fifo_get_data(struct usb2_fifo *fifo, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen, uint8_t what); +uint8_t usb2_fifo_get_data_linear(struct usb2_fifo *fifo, void *ptr, uint32_t len, uint32_t *actlen, uint8_t what); +uint8_t usb2_fifo_get_data_buffer(struct usb2_fifo *f, void **pptr, uint32_t *plen); +void usb2_fifo_get_data_next(struct usb2_fifo *f); +void usb2_fifo_get_data_error(struct usb2_fifo *fifo); +uint8_t usb2_fifo_opened(struct usb2_fifo *fifo); +void usb2_fifo_free(struct usb2_fifo *f); +void usb2_fifo_reset(struct usb2_fifo *f); +int usb2_check_thread_perm(struct usb2_device *udev, struct thread *td, int fflags, uint8_t iface_index, uint8_t ep_index); +void usb2_fifo_wakeup(struct usb2_fifo *f); +struct usb2_symlink *usb2_alloc_symlink(const char *target, const char *fmt,...); +void usb2_free_symlink(struct usb2_symlink *ps); +uint32_t usb2_lookup_symlink(const char *src_ptr, uint8_t src_len); +int usb2_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len); + +#endif /* _USB2_DEV_H_ */ diff --git a/sys/dev/usb2/core/usb2_device.c b/sys/dev/usb2/core/usb2_device.c new file mode 100644 index 000000000000..934886525f55 --- /dev/null +++ b/sys/dev/usb2/core/usb2_device.c @@ -0,0 +1,2110 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_ioctl.h> +#include <dev/usb2/include/usb2_devid.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_parse.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_dynamic.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_mbuf.h> +#include <dev/usb2/core/usb2_dev.h> +#include <dev/usb2/core/usb2_msctest.h> + +#include <dev/usb2/quirk/usb2_quirk.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +/* function prototypes */ + +static void usb2_fill_pipe_data(struct usb2_device *udev, uint8_t iface_index, struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe); +static void usb2_free_pipe_data(struct usb2_device *udev, uint8_t iface_index, uint8_t iface_mask); +static void usb2_free_iface_data(struct usb2_device *udev); +static void usb2_detach_device_sub(struct usb2_device *udev, device_t *ppdev, uint8_t free_subdev); +static uint8_t usb2_probe_and_attach_sub(struct usb2_device *udev, struct usb2_attach_arg *uaa); +static void usb2_init_attach_arg(struct usb2_device *udev, struct usb2_attach_arg *uaa); +static void usb2_suspend_resume_sub(struct usb2_device *udev, device_t dev, uint8_t do_suspend); +static void usb2_clear_stall_proc(struct usb2_proc_msg *_pm); +static void usb2_check_strings(struct usb2_device *udev); +static usb2_error_t usb2_fill_iface_data(struct usb2_device *udev, uint8_t iface_index, uint8_t alt_index); +static void usb2_notify_addq(const char *type, struct usb2_device *udev); +static void usb2_fifo_free_wrap(struct usb2_device *udev, uint8_t iface_index, uint8_t free_all); + +/* static structures */ + +static const uint8_t usb2_hub_speed_combs[USB_SPEED_MAX][USB_SPEED_MAX] = { + /* HUB *//* subdevice */ + [USB_SPEED_HIGH][USB_SPEED_HIGH] = 1, + [USB_SPEED_HIGH][USB_SPEED_FULL] = 1, + [USB_SPEED_HIGH][USB_SPEED_LOW] = 1, + [USB_SPEED_FULL][USB_SPEED_FULL] = 1, + [USB_SPEED_FULL][USB_SPEED_LOW] = 1, + [USB_SPEED_LOW][USB_SPEED_LOW] = 1, +}; + +/* This variable is global to allow easy access to it: */ + +int usb2_template = 0; + +SYSCTL_INT(_hw_usb2, OID_AUTO, template, CTLFLAG_RW, + &usb2_template, 0, "Selected USB device side template"); + + +/*------------------------------------------------------------------------* + * usb2_get_pipe_by_addr + * + * This function searches for an USB pipe by endpoint address and + * direction. + * + * Returns: + * NULL: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb2_pipe * +usb2_get_pipe_by_addr(struct usb2_device *udev, uint8_t ea_val) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + enum { + EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), + }; + + /* + * According to the USB specification not all bits are used + * for the endpoint address. Keep defined bits only: + */ + ea_val &= EA_MASK; + + /* + * Iterate accross all the USB pipes searching for a match + * based on the endpoint address: + */ + for (; pipe != pipe_end; pipe++) { + + if (pipe->edesc == NULL) { + continue; + } + /* do the mask and check the value */ + if ((pipe->edesc->bEndpointAddress & EA_MASK) == ea_val) { + goto found; + } + } + + /* + * The default pipe is always present and is checked separately: + */ + if ((udev->default_pipe.edesc) && + ((udev->default_pipe.edesc->bEndpointAddress & EA_MASK) == ea_val)) { + pipe = &udev->default_pipe; + goto found; + } + return (NULL); + +found: + return (pipe); +} + +/*------------------------------------------------------------------------* + * usb2_get_pipe + * + * This function searches for an USB pipe based on the information + * given by the passed "struct usb2_config" pointer. + * + * Return values: + * NULL: No match. + * Else: Pointer to "struct usb2_pipe". + *------------------------------------------------------------------------*/ +struct usb2_pipe * +usb2_get_pipe(struct usb2_device *udev, uint8_t iface_index, + const struct usb2_config *setup) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + uint8_t index = setup->ep_index; + uint8_t ea_mask; + uint8_t ea_val; + uint8_t type_mask; + uint8_t type_val; + + DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " + "type=0x%x dir=0x%x index=%d\n", + udev, iface_index, setup->endpoint, + setup->type, setup->direction, setup->ep_index); + + /* setup expected endpoint direction mask and value */ + + if (setup->direction == UE_DIR_ANY) { + /* match any endpoint direction */ + ea_mask = 0; + ea_val = 0; + } else { + /* match the given endpoint direction */ + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); + } + + /* setup expected endpoint address */ + + if (setup->endpoint == UE_ADDR_ANY) { + /* match any endpoint address */ + } else { + /* match the given endpoint address */ + ea_mask |= UE_ADDR; + ea_val |= (setup->endpoint & UE_ADDR); + } + + /* setup expected endpoint type */ + + if (setup->type == UE_BULK_INTR) { + /* this will match BULK and INTERRUPT endpoints */ + type_mask = 2; + type_val = 2; + } else if (setup->type == UE_TYPE_ANY) { + /* match any endpoint type */ + type_mask = 0; + type_val = 0; + } else { + /* match the given endpoint type */ + type_mask = UE_XFERTYPE; + type_val = (setup->type & UE_XFERTYPE); + } + + /* + * Iterate accross all the USB pipes searching for a match + * based on the endpoint address. Note that we are searching + * the pipes from the beginning of the "udev->pipes" array. + */ + for (; pipe != pipe_end; pipe++) { + + if ((pipe->edesc == NULL) || + (pipe->iface_index != iface_index)) { + continue; + } + /* do the masks and check the values */ + + if (((pipe->edesc->bEndpointAddress & ea_mask) == ea_val) && + ((pipe->edesc->bmAttributes & type_mask) == type_val)) { + if (!index--) { + goto found; + } + } + } + + /* + * Match against default pipe last, so that "any pipe", "any + * address" and "any direction" returns the first pipe of the + * interface. "iface_index" and "direction" is ignored: + */ + if ((udev->default_pipe.edesc) && + ((udev->default_pipe.edesc->bEndpointAddress & ea_mask) == ea_val) && + ((udev->default_pipe.edesc->bmAttributes & type_mask) == type_val) && + (!index)) { + pipe = &udev->default_pipe; + goto found; + } + return (NULL); + +found: + return (pipe); +} + +/*------------------------------------------------------------------------* + * usb2_interface_count + * + * This function stores the number of USB interfaces excluding + * alternate settings, which the USB config descriptor reports into + * the unsigned 8-bit integer pointed to by "count". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_interface_count(struct usb2_device *udev, uint8_t *count) +{ + if (udev->cdesc == NULL) { + *count = 0; + return (USB_ERR_NOT_CONFIGURED); + } + *count = udev->cdesc->bNumInterface; + return (USB_ERR_NORMAL_COMPLETION); +} + + +/*------------------------------------------------------------------------* + * usb2_fill_pipe_data + * + * This function will initialise the USB pipe structure pointed to by + * the "pipe" argument. + *------------------------------------------------------------------------*/ +static void +usb2_fill_pipe_data(struct usb2_device *udev, uint8_t iface_index, + struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe) +{ + bzero(pipe, sizeof(*pipe)); + + (udev->bus->methods->pipe_init) (udev, edesc, pipe); + + if (pipe->methods == NULL) { + /* the pipe is invalid: just return */ + return; + } + /* initialise USB pipe structure */ + pipe->edesc = edesc; + pipe->iface_index = iface_index; + TAILQ_INIT(&pipe->pipe_q.head); + pipe->pipe_q.command = &usb2_pipe_start; + + /* clear stall, if any */ + if (udev->bus->methods->clear_stall) { + mtx_lock(&udev->bus->mtx); + (udev->bus->methods->clear_stall) (udev, pipe); + mtx_unlock(&udev->bus->mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_free_pipe_data + * + * This function will free USB pipe data for the given interface + * index. Hence we do not have any dynamic allocations we simply clear + * "pipe->edesc" to indicate that the USB pipe structure can be + * reused. The pipes belonging to the given interface should not be in + * use when this function is called and no check is performed to + * prevent this. + *------------------------------------------------------------------------*/ +static void +usb2_free_pipe_data(struct usb2_device *udev, + uint8_t iface_index, uint8_t iface_mask) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + + while (pipe != pipe_end) { + if ((pipe->iface_index & iface_mask) == iface_index) { + /* free pipe */ + pipe->edesc = NULL; + } + pipe++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fill_iface_data + * + * This function will fill in interface data and allocate USB pipes + * for all the endpoints that belong to the given interface. This + * function is typically called when setting the configuration or when + * setting an alternate interface. + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_fill_iface_data(struct usb2_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + struct usb2_interface_descriptor *id; + struct usb2_endpoint_descriptor *ed = NULL; + struct usb2_descriptor *desc; + uint8_t nendpt; + + if (iface == NULL) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface_index=%d alt_index=%d\n", + iface_index, alt_index); + + sx_assert(udev->default_sx + 1, SA_LOCKED); + + pipe = udev->pipes; + pipe_end = udev->pipes + USB_EP_MAX; + + /* + * Check if any USB pipes on the given USB interface are in + * use: + */ + while (pipe != pipe_end) { + if ((pipe->edesc != NULL) && + (pipe->iface_index == iface_index) && + (pipe->refcount != 0)) { + return (USB_ERR_IN_USE); + } + pipe++; + } + + pipe = &udev->pipes[0]; + + id = usb2_find_idesc(udev->cdesc, iface_index, alt_index); + if (id == NULL) { + return (USB_ERR_INVAL); + } + /* + * Free old pipes after we know that an interface descriptor exists, + * if any. + */ + usb2_free_pipe_data(udev, iface_index, 0 - 1); + + /* Setup USB interface structure */ + iface->idesc = id; + iface->alt_index = alt_index; + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + + nendpt = id->bNumEndpoints; + DPRINTFN(5, "found idesc nendpt=%d\n", nendpt); + + desc = (void *)id; + + while (nendpt--) { + DPRINTFN(11, "endpt=%d\n", nendpt); + + while ((desc = usb2_desc_foreach(udev->cdesc, desc))) { + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + goto found; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + goto error; + +found: + ed = (void *)desc; + + /* find a free pipe */ + while (pipe != pipe_end) { + if (pipe->edesc == NULL) { + /* pipe is free */ + usb2_fill_pipe_data(udev, iface_index, ed, pipe); + break; + } + pipe++; + } + } + return (USB_ERR_NORMAL_COMPLETION); + +error: + /* passed end, or bad desc */ + DPRINTFN(0, "%s: bad descriptor(s), addr=%d!\n", + __FUNCTION__, udev->address); + + /* free old pipes if any */ + usb2_free_pipe_data(udev, iface_index, 0 - 1); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb2_free_iface_data + * + * This function will free all USB interfaces and USB pipes belonging + * to an USB device. + *------------------------------------------------------------------------*/ +static void +usb2_free_iface_data(struct usb2_device *udev) +{ + struct usb2_interface *iface = udev->ifaces; + struct usb2_interface *iface_end = udev->ifaces + USB_IFACE_MAX; + + /* mtx_assert() */ + + /* free Linux compat device, if any */ + if (udev->linux_dev) { + usb_linux_free_device(udev->linux_dev); + udev->linux_dev = NULL; + } + /* free all pipes, if any */ + usb2_free_pipe_data(udev, 0, 0); + + /* free all interfaces, if any */ + while (iface != iface_end) { + iface->idesc = NULL; + iface->alt_index = 0; + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + iface->perm.mode = 0; /* disable permissions */ + iface++; + } + + /* free "cdesc" after "ifaces", if any */ + if (udev->cdesc) { + free(udev->cdesc, M_USB); + udev->cdesc = NULL; + } + /* set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_config_index + * + * This function selects configuration by index, independent of the + * actual configuration number. This function should not be used by + * USB drivers. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_config_index(struct usb2_device *udev, uint8_t index) +{ + struct usb2_status ds; + struct usb2_hub_descriptor hd; + struct usb2_config_descriptor *cdp; + uint16_t power; + uint16_t max_power; + uint8_t nifc; + uint8_t selfpowered; + uint8_t do_unlock; + usb2_error_t err; + + DPRINTFN(6, "udev=%p index=%d\n", udev, index); + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + /* detach all interface drivers */ + usb2_detach_device(udev, USB_IFACE_INDEX_ANY, 1); + + /* free all FIFOs except control endpoint FIFOs */ + usb2_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, 0); + + /* free all configuration data structures */ + usb2_free_iface_data(udev); + + if (index == USB_UNCONFIG_INDEX) { + /* + * Leave unallocated when unconfiguring the + * device. "usb2_free_iface_data()" will also reset + * the current config number and index. + */ + err = usb2_req_set_config(udev, &Giant, USB_UNCONFIG_NO); + goto done; + } + /* get the full config descriptor */ + err = usb2_req_get_config_desc_full(udev, + &Giant, &cdp, M_USB, index); + if (err) { + goto done; + } + /* set the new config descriptor */ + + udev->cdesc = cdp; + + if (cdp->bNumInterface > USB_IFACE_MAX) { + DPRINTFN(0, "too many interfaces: %d\n", cdp->bNumInterface); + cdp->bNumInterface = USB_IFACE_MAX; + } + /* Figure out if the device is self or bus powered. */ + selfpowered = 0; + if ((!udev->flags.uq_bus_powered) && + (cdp->bmAttributes & UC_SELF_POWERED) && + (udev->flags.usb2_mode == USB_MODE_HOST)) { + /* May be self powered. */ + if (cdp->bmAttributes & UC_BUS_POWERED) { + /* Must ask device. */ + if (udev->flags.uq_power_claim) { + /* + * HUB claims to be self powered, but isn't. + * It seems that the power status can be + * determined by the HUB characteristics. + */ + err = usb2_req_get_hub_descriptor + (udev, &Giant, &hd, 1); + if (err) { + DPRINTFN(0, "could not read " + "HUB descriptor: %s\n", + usb2_errstr(err)); + + } else if (UGETW(hd.wHubCharacteristics) & + UHD_PWR_INDIVIDUAL) { + selfpowered = 1; + } + DPRINTF("characteristics=0x%04x\n", + UGETW(hd.wHubCharacteristics)); + } else { + err = usb2_req_get_device_status + (udev, &Giant, &ds); + if (err) { + DPRINTFN(0, "could not read " + "device status: %s\n", + usb2_errstr(err)); + } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { + selfpowered = 1; + } + DPRINTF("status=0x%04x \n", + UGETW(ds.wStatus)); + } + } else + selfpowered = 1; + } + DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " + "selfpowered=%d, power=%d\n", + udev, cdp, + cdp->bConfigurationValue, udev->address, cdp->bmAttributes, + selfpowered, cdp->bMaxPower * 2); + + /* Check if we have enough power. */ + power = cdp->bMaxPower * 2; + + if (udev->parent_hub) { + max_power = udev->parent_hub->hub->portpower; + } else { + max_power = USB_MAX_POWER; + } + + if (power > max_power) { + DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); + err = USB_ERR_NO_POWER; + goto done; + } + /* Only update "self_powered" in USB Host Mode */ + if (udev->flags.usb2_mode == USB_MODE_HOST) { + udev->flags.self_powered = selfpowered; + } + udev->power = power; + udev->curr_config_no = cdp->bConfigurationValue; + udev->curr_config_index = index; + + /* Set the actual configuration value. */ + err = usb2_req_set_config(udev, &Giant, cdp->bConfigurationValue); + if (err) { + goto done; + } + /* Allocate and fill interface data. */ + nifc = cdp->bNumInterface; + while (nifc--) { + err = usb2_fill_iface_data(udev, nifc, 0); + if (err) { + goto done; + } + } + +done: + DPRINTF("error=%s\n", usb2_errstr(err)); + if (err) { + usb2_free_iface_data(udev); + } + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_set_alt_interface_index + * + * This function will select an alternate interface index for the + * given interface index. The interface should not be in use when this + * function is called. That means there should be no open USB + * transfers. Else an error is returned. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_alt_interface_index(struct usb2_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + usb2_error_t err; + uint8_t do_unlock; + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + if (iface == NULL) { + err = USB_ERR_INVAL; + goto done; + } + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + usb2_detach_device(udev, iface_index, 1); + } + /* free all FIFOs for this interface */ + usb2_fifo_free_wrap(udev, iface_index, 0); + + err = usb2_fill_iface_data(udev, iface_index, alt_index); + if (err) { + goto done; + } + err = usb2_req_set_alt_interface_no + (udev, &Giant, iface_index, + iface->idesc->bAlternateSetting); + +done: + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_set_endpoint_stall + * + * This function is used to make a BULK or INTERRUPT endpoint + * send STALL tokens. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_endpoint_stall(struct usb2_device *udev, struct usb2_pipe *pipe, + uint8_t do_stall) +{ + struct usb2_xfer *xfer; + uint8_t et; + uint8_t was_stalled; + + if (pipe == NULL) { + /* nothing to do */ + DPRINTF("Cannot find endpoint\n"); + /* + * Pretend that the clear or set stall request is + * successful else some USB host stacks can do + * strange things, especially when a control endpoint + * stalls. + */ + return (0); + } + et = (pipe->edesc->bmAttributes & UE_XFERTYPE); + + if ((et != UE_BULK) && + (et != UE_INTERRUPT)) { + /* + * Should not stall control + * nor isochronous endpoints. + */ + DPRINTF("Invalid endpoint\n"); + return (0); + } + mtx_lock(&udev->bus->mtx); + + /* store current stall state */ + was_stalled = pipe->is_stalled; + + /* check for no change */ + if (was_stalled && do_stall) { + /* if the pipe is already stalled do nothing */ + mtx_unlock(&udev->bus->mtx); + DPRINTF("No change\n"); + return (0); + } + /* set stalled state */ + pipe->is_stalled = 1; + + if (do_stall || (!was_stalled)) { + if (!was_stalled) { + /* lookup the current USB transfer, if any */ + xfer = pipe->pipe_q.curr; + } else { + xfer = NULL; + } + + /* + * If "xfer" is non-NULL the "set_stall" method will + * complete the USB transfer like in case of a timeout + * setting the error code "USB_ERR_STALLED". + */ + (udev->bus->methods->set_stall) (udev, xfer, pipe); + } + if (!do_stall) { + pipe->toggle_next = 0; /* reset data toggle */ + pipe->is_stalled = 0; /* clear stalled state */ + + (udev->bus->methods->clear_stall) (udev, pipe); + + /* start up the current or next transfer, if any */ + usb2_command_wrapper(&pipe->pipe_q, pipe->pipe_q.curr); + } + mtx_unlock(&udev->bus->mtx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_reset_iface_endpoints - used in USB device side mode + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_reset_iface_endpoints(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + usb2_error_t err; + + pipe = udev->pipes; + pipe_end = udev->pipes + USB_EP_MAX; + + for (; pipe != pipe_end; pipe++) { + + if ((pipe->edesc == NULL) || + (pipe->iface_index != iface_index)) { + continue; + } + /* simulate a clear stall from the peer */ + err = usb2_set_endpoint_stall(udev, pipe, 0); + if (err) { + /* just ignore */ + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_detach_device_sub + * + * This function will try to detach an USB device. If it fails a panic + * will result. + *------------------------------------------------------------------------*/ +static void +usb2_detach_device_sub(struct usb2_device *udev, device_t *ppdev, + uint8_t free_subdev) +{ + device_t dev; + int err; + + if (!free_subdev) { + + *ppdev = NULL; + + } else if (*ppdev) { + + /* + * NOTE: It is important to clear "*ppdev" before deleting + * the child due to some device methods being called late + * during the delete process ! + */ + dev = *ppdev; + *ppdev = NULL; + + device_printf(dev, "at %s, port %d, addr %d " + "(disconnected)\n", + device_get_nameunit(udev->parent_dev), + udev->port_no, udev->address); + + if (device_is_attached(dev)) { + if (udev->flags.suspended) { + err = DEVICE_RESUME(dev); + if (err) { + device_printf(dev, "Resume failed!\n"); + } + } + if (device_detach(dev)) { + goto error; + } + } + if (device_delete_child(udev->parent_dev, dev)) { + goto error; + } + } + return; + +error: + /* Detach is not allowed to fail in the USB world */ + panic("An USB driver would not detach!\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_detach_device + * + * The following function will detach the matching interfaces. + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_detach_device(struct usb2_device *udev, uint8_t iface_index, + uint8_t free_subdev) +{ + struct usb2_interface *iface; + uint8_t i; + uint8_t do_unlock; + + if (udev == NULL) { + /* nothing to do */ + return; + } + DPRINTFN(4, "udev=%p\n", udev); + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + /* + * First detach the child to give the child's detach routine a + * chance to detach the sub-devices in the correct order. + * Then delete the child using "device_delete_child()" which + * will detach all sub-devices from the bottom and upwards! + */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + iface_index = i + 1; + } else { + i = 0; + iface_index = USB_IFACE_MAX; + } + + /* do the detach */ + + for (; i != iface_index; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb2_detach_device_sub(udev, &iface->subdev, free_subdev); + } + + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_probe_and_attach_sub + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_probe_and_attach_sub(struct usb2_device *udev, + struct usb2_attach_arg *uaa) +{ + struct usb2_interface *iface; + device_t dev; + int err; + + iface = uaa->iface; + if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { + /* leave interface alone */ + return (0); + } + dev = iface->subdev; + if (dev) { + + /* clean up after module unload */ + + if (device_is_attached(dev)) { + /* already a device there */ + return (0); + } + /* clear "iface->subdev" as early as possible */ + + iface->subdev = NULL; + + if (device_delete_child(udev->parent_dev, dev)) { + + /* + * Panic here, else one can get a double call + * to device_detach(). USB devices should + * never fail on detach! + */ + panic("device_delete_child() failed!\n"); + } + } + if (uaa->temp_dev == NULL) { + + /* create a new child */ + uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); + if (uaa->temp_dev == NULL) { + device_printf(udev->parent_dev, + "Device creation failed!\n"); + return (1); /* failure */ + } + device_set_ivars(uaa->temp_dev, uaa); + device_quiet(uaa->temp_dev); + } + /* + * Set "subdev" before probe and attach so that "devd" gets + * the information it needs. + */ + iface->subdev = uaa->temp_dev; + + if (device_probe_and_attach(iface->subdev) == 0) { + /* + * The USB attach arguments are only available during probe + * and attach ! + */ + uaa->temp_dev = NULL; + device_set_ivars(iface->subdev, NULL); + + if (udev->flags.suspended) { + err = DEVICE_SUSPEND(iface->subdev); + device_printf(iface->subdev, "Suspend failed\n"); + } + return (0); /* success */ + } else { + /* No USB driver found */ + iface->subdev = NULL; + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_set_parent_iface + * + * Using this function will lock the alternate interface setting on an + * interface. It is typically used for multi interface drivers. In USB + * device side mode it is assumed that the alternate interfaces all + * have the same endpoint descriptors. The default parent index value + * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not + * locked. + *------------------------------------------------------------------------*/ +void +usb2_set_parent_iface(struct usb2_device *udev, uint8_t iface_index, + uint8_t parent_index) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(udev, iface_index); + if (iface) { + iface->parent_iface_index = parent_index; + } + return; +} + +static void +usb2_init_attach_arg(struct usb2_device *udev, + struct usb2_attach_arg *uaa) +{ + bzero(uaa, sizeof(*uaa)); + + uaa->device = udev; + uaa->usb2_mode = udev->flags.usb2_mode; + uaa->port = udev->port_no; + + uaa->info.idVendor = UGETW(udev->ddesc.idVendor); + uaa->info.idProduct = UGETW(udev->ddesc.idProduct); + uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); + uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; + uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; + uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; + uaa->info.bConfigIndex = udev->curr_config_index; + uaa->info.bConfigNum = udev->curr_config_no; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_probe_and_attach + * + * This function is called from "uhub_explore_sub()", + * "usb2_handle_set_config()" and "usb2_handle_request()". + * + * Returns: + * 0: Success + * Else: A control transfer failed + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_probe_and_attach(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_attach_arg uaa; + struct usb2_interface *iface; + uint8_t i; + uint8_t j; + uint8_t do_unlock; + + if (udev == NULL) { + DPRINTF("udev == NULL\n"); + return (USB_ERR_INVAL); + } + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + if (udev->curr_config_index == USB_UNCONFIG_INDEX) { + /* do nothing - no configuration has been set */ + goto done; + } + /* setup USB attach arguments */ + + usb2_init_attach_arg(udev, &uaa); + + /* Check if only one interface should be probed: */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + j = i + 1; + } else { + i = 0; + j = USB_IFACE_MAX; + } + + /* Do the probe and attach */ + for (; i != j; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* + * Looks like the end of the USB + * interfaces ! + */ + DPRINTFN(2, "end of interfaces " + "at %u\n", i); + break; + } + if (iface->idesc == NULL) { + /* no interface descriptor */ + continue; + } + uaa.iface = iface; + + uaa.info.bInterfaceClass = + iface->idesc->bInterfaceClass; + uaa.info.bInterfaceSubClass = + iface->idesc->bInterfaceSubClass; + uaa.info.bInterfaceProtocol = + iface->idesc->bInterfaceProtocol; + uaa.info.bIfaceIndex = i; + uaa.info.bIfaceNum = + iface->idesc->bInterfaceNumber; + uaa.use_generic = 0; + + DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", + uaa.info.bInterfaceClass, + uaa.info.bInterfaceSubClass, + uaa.info.bInterfaceProtocol, + uaa.info.bIfaceIndex, + uaa.info.bIfaceNum); + + /* try specific interface drivers first */ + + if (usb2_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + /* try generic interface drivers last */ + + uaa.use_generic = 1; + + if (usb2_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + } + + if (uaa.temp_dev) { + /* remove the last created child; it is unused */ + + if (device_delete_child(udev->parent_dev, uaa.temp_dev)) { + DPRINTFN(0, "device delete child failed!\n"); + } + } +done: + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_suspend_resume_sub + * + * This function is called when the suspend or resume methods should + * be executed on an USB device. + *------------------------------------------------------------------------*/ +static void +usb2_suspend_resume_sub(struct usb2_device *udev, device_t dev, uint8_t do_suspend) +{ + int err; + + if (dev == NULL) { + return; + } + if (!device_is_attached(dev)) { + return; + } + if (do_suspend) { + err = DEVICE_SUSPEND(dev); + } else { + err = DEVICE_RESUME(dev); + } + if (err) { + device_printf(dev, "%s failed!\n", + do_suspend ? "Suspend" : "Resume"); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_suspend_resume_device + * + * The following function will suspend or resume the USB device. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_suspend_resume(struct usb2_device *udev, uint8_t do_suspend) +{ + struct usb2_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return (0); + } + DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); + + sx_assert(udev->default_sx + 1, SA_LOCKED); + + mtx_lock(&udev->bus->mtx); + /* filter the suspend events */ + if (udev->flags.suspended == do_suspend) { + mtx_unlock(&udev->bus->mtx); + /* nothing to do */ + return (0); + } + udev->flags.suspended = do_suspend; + mtx_unlock(&udev->bus->mtx); + + /* do the suspend or resume */ + + for (i = 0; i != USB_IFACE_MAX; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb2_suspend_resume_sub(udev, iface->subdev, do_suspend); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_clear_stall_proc + * + * This function performs generic USB clear stall operations. + *------------------------------------------------------------------------*/ +static void +usb2_clear_stall_proc(struct usb2_proc_msg *_pm) +{ + struct usb2_clear_stall_msg *pm = (void *)_pm; + struct usb2_device *udev = pm->udev; + + /* Change lock */ + mtx_unlock(&udev->bus->mtx); + mtx_lock(udev->default_mtx); + + /* Start clear stall callback */ + usb2_transfer_start(udev->default_xfer[1]); + + /* Change lock */ + mtx_unlock(udev->default_mtx); + mtx_lock(&udev->bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_alloc_device + * + * This function allocates a new USB device. This function is called + * when a new device has been put in the powered state, but not yet in + * the addressed state. Get initial descriptor, set the address, get + * full descriptor and get strings. + * + * Return values: + * 0: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb2_device * +usb2_alloc_device(device_t parent_dev, struct usb2_bus *bus, + struct usb2_device *parent_hub, uint8_t depth, + uint8_t port_index, uint8_t port_no, uint8_t speed, uint8_t usb2_mode) +{ + struct usb2_attach_arg uaa; + struct usb2_device *udev; + struct usb2_device *adev; + struct usb2_device *hub; + uint8_t *scratch_ptr; + uint32_t scratch_size; + usb2_error_t err; + uint8_t device_index; + + DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " + "port_index=%u, port_no=%u, speed=%u, usb2_mode=%u\n", + parent_dev, bus, parent_hub, depth, port_index, port_no, + speed, usb2_mode); + + /* + * Find an unused device index. In USB Host mode this is the + * same as the device address. + * + * NOTE: Index 1 is reserved for the Root HUB. + */ + for (device_index = USB_ROOT_HUB_ADDR; device_index != + USB_MAX_DEVICES; device_index++) { + if (bus->devices[device_index] == NULL) + break; + } + + if (device_index == USB_MAX_DEVICES) { + device_printf(bus->bdev, + "No free USB device index for new device!\n"); + return (NULL); + } + if (depth > 0x10) { + device_printf(bus->bdev, + "Invalid device depth!\n"); + return (NULL); + } + udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); + if (udev == NULL) { + return (NULL); + } + /* initialise our SX-lock */ + sx_init(udev->default_sx, "0123456789ABCDEF - USB device SX lock" + depth); + + /* initialise our SX-lock */ + sx_init(udev->default_sx + 1, "0123456789ABCDEF - USB config SX lock" + depth); + + usb2_cv_init(udev->default_cv, "WCTRL"); + usb2_cv_init(udev->default_cv + 1, "UGONE"); + + /* initialise our mutex */ + mtx_init(udev->default_mtx, "USB device mutex", NULL, MTX_DEF); + + /* initialise generic clear stall */ + udev->cs_msg[0].hdr.pm_callback = &usb2_clear_stall_proc; + udev->cs_msg[0].udev = udev; + udev->cs_msg[1].hdr.pm_callback = &usb2_clear_stall_proc; + udev->cs_msg[1].udev = udev; + + /* initialise some USB device fields */ + udev->parent_hub = parent_hub; + udev->parent_dev = parent_dev; + udev->port_index = port_index; + udev->port_no = port_no; + udev->depth = depth; + udev->bus = bus; + udev->address = USB_START_ADDR; /* default value */ + udev->plugtime = (uint32_t)ticks; + udev->power_mode = USB_POWER_MODE_ON; + + /* we are not ready yet */ + udev->refcount = 1; + + /* set up default endpoint descriptor */ + udev->default_ep_desc.bLength = sizeof(udev->default_ep_desc); + udev->default_ep_desc.bDescriptorType = UDESC_ENDPOINT; + udev->default_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + udev->default_ep_desc.bmAttributes = UE_CONTROL; + udev->default_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; + udev->default_ep_desc.wMaxPacketSize[1] = 0; + udev->default_ep_desc.bInterval = 0; + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + udev->speed = speed; + udev->flags.usb2_mode = usb2_mode; + + /* check speed combination */ + + hub = udev->parent_hub; + if (hub) { + if (usb2_hub_speed_combs[hub->speed][speed] == 0) { +#if USB_DEBUG + printf("%s: the selected subdevice and HUB speed " + "combination is not supported %d/%d.\n", + __FUNCTION__, speed, hub->speed); +#endif + /* reject this combination */ + err = USB_ERR_INVAL; + goto done; + } + } + /* search for our High Speed USB HUB, if any */ + + adev = udev; + hub = udev->parent_hub; + + while (hub) { + if (hub->speed == USB_SPEED_HIGH) { + udev->hs_hub_addr = hub->address; + udev->hs_port_no = adev->port_no; + break; + } + adev = hub; + hub = hub->parent_hub; + } + + /* init the default pipe */ + usb2_fill_pipe_data(udev, 0, + &udev->default_ep_desc, + &udev->default_pipe); + + /* set device index */ + udev->device_index = device_index; + + if (udev->flags.usb2_mode == USB_MODE_HOST) { + + err = usb2_req_set_address(udev, &Giant, device_index); + + /* This is the new USB device address from now on */ + + udev->address = device_index; + + /* + * We ignore any set-address errors, hence there are + * buggy USB devices out there that actually receive + * the SETUP PID, but manage to set the address before + * the STATUS stage is ACK'ed. If the device responds + * to the subsequent get-descriptor at the new + * address, then we know that the set-address command + * was successful. + */ + if (err) { + DPRINTFN(0, "set address %d failed " + "(ignored)\n", udev->address); + } + /* allow device time to set new address */ + usb2_pause_mtx(&Giant, USB_SET_ADDRESS_SETTLE); + } else { + /* We are not self powered */ + udev->flags.self_powered = 0; + + /* Set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + /* Setup USB descriptors */ + err = (usb2_temp_setup_by_index_p) (udev, usb2_template); + if (err) { + DPRINTFN(0, "setting up USB template failed maybe the USB " + "template module has not been loaded\n"); + goto done; + } + } + + /* + * Get the first 8 bytes of the device descriptor ! + * + * NOTE: "usb2_do_request" will check the device descriptor + * next time we do a request to see if the maximum packet size + * changed! The 8 first bytes of the device descriptor + * contains the maximum packet size to use on control endpoint + * 0. If this value is different from "USB_MAX_IPACKET" a new + * USB control request will be setup! + */ + err = usb2_req_get_desc(udev, &Giant, &udev->ddesc, + USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0); + if (err) { + DPRINTFN(0, "getting device descriptor " + "at addr %d failed!\n", udev->address); + goto done; + } + DPRINTF("adding unit addr=%d, rev=%02x, class=%d, " + "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", + udev->address, UGETW(udev->ddesc.bcdUSB), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->ddesc.bDeviceProtocol, + udev->ddesc.bMaxPacketSize, + udev->ddesc.bLength, + udev->speed); + + /* get the full device descriptor */ + err = usb2_req_get_device_desc(udev, &Giant, &udev->ddesc); + if (err) { + DPRINTF("addr=%d, getting full desc failed\n", + udev->address); + goto done; + } + /* + * Setup temporary USB attach args so that we can figure out some + * basic quirks for this device. + */ + usb2_init_attach_arg(udev, &uaa); + + if (usb2_test_quirk(&uaa, UQ_BUS_POWERED)) { + udev->flags.uq_bus_powered = 1; + } + if (usb2_test_quirk(&uaa, UQ_POWER_CLAIM)) { + udev->flags.uq_power_claim = 1; + } + if (usb2_test_quirk(&uaa, UQ_NO_STRINGS)) { + udev->flags.no_strings = 1; + } + /* + * Workaround for buggy USB devices. + * + * It appears that some string-less USB chips will crash and + * disappear if any attempts are made to read any string + * descriptors. + * + * Try to detect such chips by checking the strings in the USB + * device descriptor. If no strings are present there we + * simply disable all USB strings. + */ + scratch_ptr = udev->bus->scratch[0].data; + scratch_size = sizeof(udev->bus->scratch[0].data); + + if (udev->ddesc.iManufacturer || + udev->ddesc.iProduct || + udev->ddesc.iSerialNumber) { + /* read out the language ID string */ + err = usb2_req_get_string_desc(udev, &Giant, + (char *)scratch_ptr, 4, scratch_size, + USB_LANGUAGE_TABLE); + } else { + err = USB_ERR_INVAL; + } + + if (err || (scratch_ptr[0] < 4)) { + udev->flags.no_strings = 1; + } else { + /* pick the first language as the default */ + udev->langid = UGETW(scratch_ptr + 2); + } + + /* assume 100mA bus powered for now. Changed when configured. */ + udev->power = USB_MIN_POWER; + + /* get serial number string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iSerialNumber); + + strlcpy(udev->serial, (char *)scratch_ptr, sizeof(udev->serial)); + + /* get manufacturer string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iManufacturer); + + strlcpy(udev->manufacturer, (char *)scratch_ptr, sizeof(udev->manufacturer)); + + /* get product string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iProduct); + + strlcpy(udev->product, (char *)scratch_ptr, sizeof(udev->product)); + + /* finish up all the strings */ + usb2_check_strings(udev); + + if (udev->flags.usb2_mode == USB_MODE_HOST) { + uint8_t config_index; + uint8_t config_quirk; + + /* + * Most USB devices should attach to config index 0 by + * default + */ + if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_0)) { + config_index = 0; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_1)) { + config_index = 1; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_2)) { + config_index = 2; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_3)) { + config_index = 3; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_4)) { + config_index = 4; + config_quirk = 1; + } else { + config_index = 0; + config_quirk = 0; + } + +repeat_set_config: + + DPRINTF("setting config %u\n", config_index); + + /* get the USB device configured */ + sx_xlock(udev->default_sx + 1); + err = usb2_set_config_index(udev, config_index); + sx_unlock(udev->default_sx + 1); + if (err) { + DPRINTFN(0, "Failure selecting " + "configuration index %u: %s, port %u, addr %u\n", + config_index, usb2_errstr(err), udev->port_no, + udev->address); + + } else if (config_quirk) { + /* user quirk selects configuration index */ + } else if ((config_index + 1) < udev->ddesc.bNumConfigurations) { + + if ((udev->cdesc->bNumInterface < 2) && + (usb2_get_no_endpoints(udev->cdesc) == 0)) { + DPRINTFN(0, "Found no endpoints " + "(trying next config)!\n"); + config_index++; + goto repeat_set_config; + } + if (config_index == 0) { + /* + * Try to figure out if we have an + * auto-install disk there: + */ + if (usb2_test_autoinstall(udev, 0) == 0) { + DPRINTFN(0, "Found possible auto-install " + "disk (trying next config)\n"); + config_index++; + goto repeat_set_config; + } + } + } else if (UGETW(udev->ddesc.idVendor) == USB_VENDOR_HUAWEI) { + if (usb2_test_huawei(udev, 0) == 0) { + DPRINTFN(0, "Found Huawei auto-install disk!\n"); + err = USB_ERR_STALLED; /* fake an error */ + } + } + } else { + err = 0; /* set success */ + } + + DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", + udev->address, udev, udev->parent_hub); + + /* register our device - we are ready */ + usb2_bus_port_set_device(bus, parent_hub ? + parent_hub->hub->ports + port_index : NULL, udev, device_index); + + /* make a symlink for UGEN */ + if (snprintf((char *)scratch_ptr, scratch_size, + USB_DEVICE_NAME "%u.%u.0.0", + device_get_unit(udev->bus->bdev), + udev->device_index)) { + /* ignore */ + } + udev->ugen_symlink = + usb2_alloc_symlink((char *)scratch_ptr, "ugen%u.%u", + device_get_unit(udev->bus->bdev), + udev->device_index); + + printf("ugen%u.%u: <%s> at %s\n", + device_get_unit(udev->bus->bdev), + udev->device_index, udev->manufacturer, + device_get_nameunit(udev->bus->bdev)); + + usb2_notify_addq("+", udev); +done: + if (err) { + /* free device */ + usb2_free_device(udev); + udev = NULL; + } + return (udev); +} + +/*------------------------------------------------------------------------* + * usb2_free_device + * + * This function is NULL safe and will free an USB device. + *------------------------------------------------------------------------*/ +void +usb2_free_device(struct usb2_device *udev) +{ + struct usb2_bus *bus; + + if (udev == NULL) { + /* already freed */ + return; + } + DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); + + usb2_notify_addq("-", udev); + + bus = udev->bus; + + /* + * Destroy UGEN symlink, if any + */ + if (udev->ugen_symlink) { + usb2_free_symlink(udev->ugen_symlink); + udev->ugen_symlink = NULL; + } + /* + * Unregister our device first which will prevent any further + * references: + */ + usb2_bus_port_set_device(bus, udev->parent_hub ? + udev->parent_hub->hub->ports + udev->port_index : NULL, + NULL, USB_ROOT_HUB_ADDR); + + /* wait for all pending references to go away: */ + + mtx_lock(&usb2_ref_lock); + udev->refcount--; + while (udev->refcount != 0) { + usb2_cv_wait(udev->default_cv + 1, &usb2_ref_lock); + } + mtx_unlock(&usb2_ref_lock); + + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + /* stop receiving any control transfers (Device Side Mode) */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + } + /* free all FIFOs */ + usb2_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, 1); + + /* + * Free all interface related data and FIFOs, if any. + */ + usb2_free_iface_data(udev); + + /* unsetup any leftover default USB transfers */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + + (usb2_temp_unsetup_p) (udev); + + sx_destroy(udev->default_sx); + sx_destroy(udev->default_sx + 1); + + usb2_cv_destroy(udev->default_cv); + usb2_cv_destroy(udev->default_cv + 1); + + mtx_destroy(udev->default_mtx); + + /* free device */ + free(udev, M_USB); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_iface + * + * This function is the safe way to get the USB interface structure + * pointer by interface index. + * + * Return values: + * NULL: Interface not present. + * Else: Pointer to USB interface structure. + *------------------------------------------------------------------------*/ +struct usb2_interface * +usb2_get_iface(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_interface *iface = udev->ifaces + iface_index; + + if ((iface < udev->ifaces) || + (iface_index >= USB_IFACE_MAX) || + (udev->cdesc == NULL) || + (iface_index >= udev->cdesc->bNumInterface)) { + return (NULL); + } + return (iface); +} + +/*------------------------------------------------------------------------* + * usb2_find_descriptor + * + * This function will lookup the first descriptor that matches the + * criteria given by the arguments "type" and "subtype". Descriptors + * will only be searched within the interface having the index + * "iface_index". If the "id" argument points to an USB descriptor, + * it will be skipped before the search is started. This allows + * searching for multiple descriptors using the same criteria. Else + * the search is started after the interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A descriptor matching the criteria + *------------------------------------------------------------------------*/ +void * +usb2_find_descriptor(struct usb2_device *udev, void *id, uint8_t iface_index, + uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask) +{ + struct usb2_descriptor *desc; + struct usb2_config_descriptor *cd; + struct usb2_interface *iface; + + cd = usb2_get_config_descriptor(udev); + if (cd == NULL) { + return (NULL); + } + if (id == NULL) { + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (NULL); + } + id = usb2_get_interface_descriptor(iface); + if (id == NULL) { + return (NULL); + } + } + desc = (void *)id; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (((desc->bDescriptorType & type_mask) == type) && + ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { + return (desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_devinfo + * + * This function will dump information from the device descriptor + * belonging to the USB device pointed to by "udev", to the string + * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb2_devinfo(struct usb2_device *udev, char *dst_ptr, uint16_t dst_len) +{ + struct usb2_device_descriptor *udd = &udev->ddesc; + uint16_t bcdDevice; + uint16_t bcdUSB; + + bcdUSB = UGETW(udd->bcdUSB); + bcdDevice = UGETW(udd->bcdDevice); + + if (udd->bDeviceClass != 0xFF) { + snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" + "%x.%02x, addr %d", udev->manufacturer, udev->product, + udd->bDeviceClass, udd->bDeviceSubClass, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } else { + snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" + "%x.%02x, addr %d", udev->manufacturer, udev->product, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } + return; +} + +#if USB_VERBOSE +/* + * Descriptions of of known vendors and devices ("products"). + */ +struct usb_knowndev { + uint16_t vendor; + uint16_t product; + uint32_t flags; + const char *vendorname; + const char *productname; +}; + +#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ + +#include <dev/usb2/include/usb2_devid.h> +#include <dev/usb2/include/usb2_devtable.h> +#endif /* USB_VERBOSE */ + +/*------------------------------------------------------------------------* + * usb2_check_strings + * + * This function checks the manufacturer and product strings and will + * fill in defaults for missing strings. + *------------------------------------------------------------------------*/ +static void +usb2_check_strings(struct usb2_device *udev) +{ + struct usb2_device_descriptor *udd = &udev->ddesc; + const char *vendor; + const char *product; + +#if USB_VERBOSE + const struct usb_knowndev *kdp; + +#endif + uint16_t vendor_id; + uint16_t product_id; + + usb2_trim_spaces(udev->manufacturer); + usb2_trim_spaces(udev->product); + + if (udev->manufacturer[0]) { + vendor = udev->manufacturer; + } else { + vendor = NULL; + } + + if (udev->product[0]) { + product = udev->product; + } else { + product = NULL; + } + + vendor_id = UGETW(udd->idVendor); + product_id = UGETW(udd->idProduct); + +#if USB_VERBOSE + if (vendor == NULL || product == NULL) { + + for (kdp = usb_knowndevs; + kdp->vendorname != NULL; + kdp++) { + if (kdp->vendor == vendor_id && + (kdp->product == product_id || + (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) + break; + } + if (kdp->vendorname != NULL) { + if (vendor == NULL) + vendor = kdp->vendorname; + if (product == NULL) + product = (kdp->flags & USB_KNOWNDEV_NOPROD) == 0 ? + kdp->productname : NULL; + } + } +#endif + if (vendor && *vendor) { + if (udev->manufacturer != vendor) { + strlcpy(udev->manufacturer, vendor, + sizeof(udev->manufacturer)); + } + } else { + snprintf(udev->manufacturer, + sizeof(udev->manufacturer), "vendor 0x%04x", vendor_id); + } + + if (product && *product) { + if (udev->product != product) { + strlcpy(udev->product, product, + sizeof(udev->product)); + } + } else { + snprintf(udev->product, + sizeof(udev->product), "product 0x%04x", product_id); + } + return; +} + +uint8_t +usb2_get_speed(struct usb2_device *udev) +{ + return (udev->speed); +} + +struct usb2_device_descriptor * +usb2_get_device_descriptor(struct usb2_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (&udev->ddesc); +} + +struct usb2_config_descriptor * +usb2_get_config_descriptor(struct usb2_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (udev->cdesc); +} + +/*------------------------------------------------------------------------* + * usb2_test_quirk - test a device for a given quirk + * + * Return values: + * 0: The USB device does not have the given quirk. + * Else: The USB device has the given quirk. + *------------------------------------------------------------------------*/ +uint8_t +usb2_test_quirk(const struct usb2_attach_arg *uaa, uint16_t quirk) +{ + uint8_t found; + + found = (usb2_test_quirk_p) (&uaa->info, quirk); + return (found); +} + +struct usb2_interface_descriptor * +usb2_get_interface_descriptor(struct usb2_interface *iface) +{ + if (iface == NULL) + return (NULL); /* be NULL safe */ + return (iface->idesc); +} + +uint8_t +usb2_get_interface_altindex(struct usb2_interface *iface) +{ + return (iface->alt_index); +} + +uint8_t +usb2_get_bus_index(struct usb2_device *udev) +{ + return ((uint8_t)device_get_unit(udev->bus->bdev)); +} + +uint8_t +usb2_get_device_index(struct usb2_device *udev) +{ + return (udev->device_index); +} + +/*------------------------------------------------------------------------* + * usb2_notify_addq + * + * This function will generate events for dev. + *------------------------------------------------------------------------*/ +static void +usb2_notify_addq(const char *type, struct usb2_device *udev) +{ + char *data = NULL; + struct malloc_type *mt; + + mtx_lock(&malloc_mtx); + mt = malloc_desc2type("bus"); /* XXX M_BUS */ + mtx_unlock(&malloc_mtx); + if (mt == NULL) + return; + + data = malloc(512, mt, M_NOWAIT); + if (data == NULL) + return; + + /* String it all together. */ + if (udev->parent_hub) { + snprintf(data, 1024, + "%s" + "ugen%u.%u " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "at " + "port=%u " + "on " + "ugen%u.%u\n", + type, + device_get_unit(udev->bus->bdev), + udev->device_index, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->serial, + udev->port_no, + device_get_unit(udev->bus->bdev), + udev->parent_hub->device_index); + } else { + snprintf(data, 1024, + "%s" + "ugen%u.%u " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "at port=%u " + "on " + "%s\n", + type, + device_get_unit(udev->bus->bdev), + udev->device_index, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->serial, + udev->port_no, + device_get_nameunit(device_get_parent(udev->bus->bdev))); + } + devctl_queue_data(data); + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_free_wrap + * + * The function will free the FIFOs. + *------------------------------------------------------------------------*/ +static void +usb2_fifo_free_wrap(struct usb2_device *udev, + uint8_t iface_index, uint8_t free_all) +{ + struct usb2_fifo *f; + struct usb2_pipe *pipe; + uint16_t i; + + /* + * Free any USB FIFOs on the given interface: + */ + for (i = 0; i != USB_FIFO_MAX; i++) { + f = udev->fifo[i]; + if (f == NULL) { + continue; + } + pipe = f->priv_sc0; + if ((pipe == &udev->default_pipe) && (free_all == 0)) { + /* don't free UGEN control endpoint yet */ + continue; + } + /* Check if the interface index matches */ + if ((iface_index == f->iface_index) || + (iface_index == USB_IFACE_INDEX_ANY)) { + usb2_fifo_free(f); + } + } + return; +} diff --git a/sys/dev/usb2/core/usb2_device.h b/sys/dev/usb2/core/usb2_device.h new file mode 100644 index 000000000000..3c5225c226d3 --- /dev/null +++ b/sys/dev/usb2/core/usb2_device.h @@ -0,0 +1,162 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_DEVICE_H_ +#define _USB2_DEVICE_H_ + +struct usb2_symlink; + +#define USB_DEFAULT_XFER_MAX 2 + +struct usb2_clear_stall_msg { + struct usb2_proc_msg hdr; + struct usb2_device *udev; +}; + +/* + * The following structure defines an USB pipe which is equal to an + * USB endpoint. + */ +struct usb2_pipe { + struct usb2_xfer_queue pipe_q; /* queue of USB transfers */ + + struct usb2_xfer *xfer_block; /* blocking USB transfer */ + struct usb2_endpoint_descriptor *edesc; + struct usb2_pipe_methods *methods; /* set by HC driver */ + + uint16_t isoc_next; + uint16_t refcount; + + uint8_t toggle_next:1; /* next data toggle value */ + uint8_t is_stalled:1; /* set if pipe is stalled */ + uint8_t is_synced:1; /* set if we a synchronised */ + uint8_t unused:5; + uint8_t iface_index; /* not used by "default pipe" */ +}; + +/* + * The following structure defines an USB interface. + */ +struct usb2_interface { + struct usb2_perm perm; /* interface permissions */ + struct usb2_interface_descriptor *idesc; + device_t subdev; + uint8_t alt_index; + uint8_t parent_iface_index; +}; + +/* + * The following structure defines the USB device flags. + */ +struct usb2_device_flags { + uint8_t usb2_mode:1; /* USB mode (see USB_MODE_XXX) */ + uint8_t self_powered:1; /* set if USB device is self powered */ + uint8_t suspended:1; /* set if USB device is suspended */ + uint8_t no_strings:1; /* set if USB device does not support + * strings */ + uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */ + uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */ + uint8_t uq_power_claim:1; /* set if power claim quirk is present */ +}; + +/* + * The following structure defines an USB device. There exists one of + * these structures for every USB device. + */ +struct usb2_device { + struct usb2_clear_stall_msg cs_msg[2]; /* generic clear stall + * messages */ + struct usb2_perm perm; + struct sx default_sx[2]; + struct mtx default_mtx[1]; + struct cv default_cv[2]; + struct usb2_interface ifaces[USB_IFACE_MAX]; + struct usb2_pipe default_pipe; /* Control Endpoint 0 */ + struct usb2_pipe pipes[USB_EP_MAX]; + + struct usb2_bus *bus; /* our USB BUS */ + device_t parent_dev; /* parent device */ + struct usb2_device *parent_hub; + struct usb2_config_descriptor *cdesc; /* full config descr */ + struct usb2_hub *hub; /* only if this is a hub */ + struct usb_device *linux_dev; + struct usb2_xfer *default_xfer[USB_DEFAULT_XFER_MAX]; + struct usb2_temp_data *usb2_template_ptr; + struct usb2_pipe *pipe_curr; /* current clear stall pipe */ + struct usb2_fifo *fifo[USB_FIFO_MAX]; + struct usb2_symlink *ugen_symlink; /* our generic symlink */ + + uint32_t plugtime; /* copy of "ticks" */ + + uint16_t refcount; +#define USB_DEV_REF_MAX 0xffff + + uint16_t power; /* mA the device uses */ + uint16_t langid; /* language for strings */ + + uint8_t address; /* device addess */ + uint8_t device_index; /* device index in "bus->devices" */ + uint8_t curr_config_index; /* current configuration index */ + uint8_t curr_config_no; /* current configuration number */ + uint8_t depth; /* distance from root HUB */ + uint8_t speed; /* low/full/high speed */ + uint8_t port_index; /* parent HUB port index */ + uint8_t port_no; /* parent HUB port number */ + uint8_t hs_hub_addr; /* high-speed HUB address */ + uint8_t hs_port_no; /* high-speed HUB port number */ + uint8_t driver_added_refcount; /* our driver added generation count */ + uint8_t power_mode; /* see USB_POWER_XXX */ + + /* the "flags" field is write-protected by "bus->mtx" */ + + struct usb2_device_flags flags; + + struct usb2_endpoint_descriptor default_ep_desc; /* for pipe 0 */ + struct usb2_device_descriptor ddesc; /* device descriptor */ + + char serial[64]; /* serial number */ + char manufacturer[64]; /* manufacturer string */ + char product[64]; /* product string */ +}; + +/* function prototypes */ + +struct usb2_device *usb2_alloc_device(device_t parent_dev, struct usb2_bus *bus, struct usb2_device *parent_hub, uint8_t depth, uint8_t port_index, uint8_t port_no, uint8_t speed, uint8_t usb2_mode); +struct usb2_pipe *usb2_get_pipe(struct usb2_device *udev, uint8_t iface_index, const struct usb2_config *setup); +struct usb2_pipe *usb2_get_pipe_by_addr(struct usb2_device *udev, uint8_t ea_val); +usb2_error_t usb2_interface_count(struct usb2_device *udev, uint8_t *count); +usb2_error_t usb2_probe_and_attach(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_reset_iface_endpoints(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_set_config_index(struct usb2_device *udev, uint8_t index); +usb2_error_t usb2_set_endpoint_stall(struct usb2_device *udev, struct usb2_pipe *pipe, uint8_t do_stall); +usb2_error_t usb2_suspend_resume(struct usb2_device *udev, uint8_t do_suspend); +void usb2_detach_device(struct usb2_device *udev, uint8_t iface_index, uint8_t free_subdev); +void usb2_devinfo(struct usb2_device *udev, char *dst_ptr, uint16_t dst_len); +void usb2_free_device(struct usb2_device *udev); +void *usb2_find_descriptor(struct usb2_device *udev, void *id, uint8_t iface_index, uint8_t type, uint8_t type_mask, uint8_t subtype, uint8_t subtype_mask); +void usb_linux_free_device(struct usb_device *dev); + +#endif /* _USB2_DEVICE_H_ */ diff --git a/sys/dev/usb2/core/usb2_dynamic.c b/sys/dev/usb2/core/usb2_dynamic.c new file mode 100644 index 000000000000..2bd4b01331ce --- /dev/null +++ b/sys/dev/usb2/core/usb2_dynamic.c @@ -0,0 +1,140 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_dynamic.h> + +/* function prototypes */ +static usb2_temp_get_desc_t usb2_temp_get_desc_w; +static usb2_temp_setup_by_index_t usb2_temp_setup_by_index_w; +static usb2_temp_unsetup_t usb2_temp_unsetup_w; +static usb2_test_quirk_t usb2_test_quirk_w; +static usb2_quirk_ioctl_t usb2_quirk_ioctl_w; + +/* global variables */ +usb2_temp_get_desc_t *usb2_temp_get_desc_p = &usb2_temp_get_desc_w; +usb2_temp_setup_by_index_t *usb2_temp_setup_by_index_p = &usb2_temp_setup_by_index_w; +usb2_temp_unsetup_t *usb2_temp_unsetup_p = &usb2_temp_unsetup_w; +usb2_test_quirk_t *usb2_test_quirk_p = &usb2_test_quirk_w; +usb2_quirk_ioctl_t *usb2_quirk_ioctl_p = &usb2_quirk_ioctl_w; +devclass_t usb2_devclass_ptr = NULL; + +static usb2_error_t +usb2_temp_setup_by_index_w(struct usb2_device *udev, uint16_t index) +{ + return (USB_ERR_INVAL); +} + +static uint8_t +usb2_test_quirk_w(const struct usb2_lookup_info *info, uint16_t quirk) +{ + return (0); /* no match */ +} + +static int +usb2_quirk_ioctl_w(unsigned long cmd, caddr_t data, int fflag, struct thread *td) +{ + return (ENOIOCTL); +} + +static void +usb2_temp_get_desc_w(struct usb2_device *udev, struct usb2_device_request *req, const void **pPtr, uint16_t *pLength) +{ + /* stall */ + *pPtr = NULL; + *pLength = 0; + return; +} + +static void +usb2_temp_unsetup_w(struct usb2_device *udev) +{ + if (udev->usb2_template_ptr) { + + free(udev->usb2_template_ptr, M_USB); + + udev->usb2_template_ptr = NULL; + } + return; +} + +void +usb2_quirk_unload(void *arg) +{ + /* reset function pointers */ + + usb2_test_quirk_p = &usb2_test_quirk_w; + usb2_quirk_ioctl_p = &usb2_quirk_ioctl_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} + +void +usb2_temp_unload(void *arg) +{ + /* reset function pointers */ + + usb2_temp_get_desc_p = &usb2_temp_get_desc_w; + usb2_temp_setup_by_index_p = &usb2_temp_setup_by_index_w; + usb2_temp_unsetup_p = &usb2_temp_unsetup_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} + +void +usb2_bus_unload(void *arg) +{ + /* reset function pointers */ + + usb2_devclass_ptr = NULL; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} diff --git a/sys/dev/usb2/core/usb2_dynamic.h b/sys/dev/usb2/core/usb2_dynamic.h new file mode 100644 index 000000000000..ca59f96e2d00 --- /dev/null +++ b/sys/dev/usb2/core/usb2_dynamic.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_DYNAMIC_H_ +#define _USB2_DYNAMIC_H_ + +/* prototypes */ + +struct usb2_device; +struct usb2_lookup_info; +struct usb2_device_request; + +/* typedefs */ + +typedef usb2_error_t (usb2_temp_setup_by_index_t)(struct usb2_device *udev, uint16_t index); +typedef uint8_t (usb2_test_quirk_t)(const struct usb2_lookup_info *info, uint16_t quirk); +typedef int (usb2_quirk_ioctl_t)(unsigned long cmd, caddr_t data, int fflag, struct thread *td); +typedef void (usb2_temp_get_desc_t)(struct usb2_device *udev, struct usb2_device_request *req, const void **pPtr, uint16_t *pLength); +typedef void (usb2_temp_unsetup_t)(struct usb2_device *udev); + +/* global function pointers */ + +extern usb2_temp_get_desc_t *usb2_temp_get_desc_p; +extern usb2_temp_setup_by_index_t *usb2_temp_setup_by_index_p; +extern usb2_temp_unsetup_t *usb2_temp_unsetup_p; +extern usb2_test_quirk_t *usb2_test_quirk_p; +extern usb2_quirk_ioctl_t *usb2_quirk_ioctl_p; +extern devclass_t usb2_devclass_ptr; + +/* function prototypes */ + +void usb2_temp_unload(void *); +void usb2_quirk_unload(void *); +void usb2_bus_unload(void *); + +uint8_t usb2_test_quirk(const struct usb2_attach_arg *uaa, uint16_t quirk); + +#endif /* _USB2_DYNAMIC_H_ */ diff --git a/sys/dev/usb2/core/usb2_error.c b/sys/dev/usb2/core/usb2_error.c new file mode 100644 index 000000000000..51668599f2d8 --- /dev/null +++ b/sys/dev/usb2/core/usb2_error.c @@ -0,0 +1,44 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> + +#include <dev/usb2/core/usb2_core.h> + +USB_MAKE_DEBUG_TABLE(USB_ERR); + +/*------------------------------------------------------------------------* + * usb2_errstr + * + * This function converts an USB error code into a string. + *------------------------------------------------------------------------*/ +const char * +usb2_errstr(usb2_error_t err) +{ + return ((err < USB_ERR_MAX) ? + USB_ERR[err] : "USB_ERR_UNKNOWN"); +} diff --git a/sys/dev/usb2/core/usb2_generic.c b/sys/dev/usb2/core/usb2_generic.c new file mode 100644 index 000000000000..56ada335bad6 --- /dev/null +++ b/sys/dev/usb2/core/usb2_generic.c @@ -0,0 +1,2226 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_ioctl.h> +#include <dev/usb2/include/usb2_error.h> + +#define USB_DEBUG_VAR ugen_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_mbuf.h> +#include <dev/usb2/core/usb2_dev.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_generic.h> +#include <dev/usb2/core/usb2_transfer.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +/* defines */ + +#define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ +#define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ +#define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ + +/* function prototypes */ + +static usb2_callback_t ugen_read_clear_stall_callback; +static usb2_callback_t ugen_write_clear_stall_callback; +static usb2_callback_t ugen_default_read_callback; +static usb2_callback_t ugen_default_write_callback; +static usb2_callback_t ugen_isoc_read_callback; +static usb2_callback_t ugen_isoc_write_callback; +static usb2_callback_t ugen_default_fs_callback; + +static usb2_fifo_open_t ugen_open; +static usb2_fifo_close_t ugen_close; +static usb2_fifo_ioctl_t ugen_ioctl; +static usb2_fifo_cmd_t ugen_start_read; +static usb2_fifo_cmd_t ugen_start_write; +static usb2_fifo_cmd_t ugen_stop_io; + +static int ugen_transfer_setup(struct usb2_fifo *f, const struct usb2_config *setup, uint8_t n_setup); +static int ugen_open_pipe_write(struct usb2_fifo *f); +static int ugen_open_pipe_read(struct usb2_fifo *f); +static int ugen_set_config(struct usb2_fifo *f, uint8_t index); +static int ugen_set_interface(struct usb2_fifo *f, uint8_t iface_index, uint8_t alt_index); +static int ugen_get_cdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *pgd); +static int ugen_get_sdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd); +static int usb2_gen_fill_deviceinfo(struct usb2_fifo *f, struct usb2_device_info *di); +static int ugen_re_enumerate(struct usb2_fifo *f); +static int ugen_iface_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags); +static int ugen_ctrl_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags); +static int ugen_fs_uninit(struct usb2_fifo *f); +static uint8_t ugen_fs_get_complete(struct usb2_fifo *f, uint8_t *pindex); + + +/* structures */ + +struct usb2_fifo_methods usb2_ugen_methods = { + .f_open = &ugen_open, + .f_close = &ugen_close, + .f_ioctl = &ugen_ioctl, + .f_start_read = &ugen_start_read, + .f_stop_read = &ugen_stop_io, + .f_start_write = &ugen_start_write, + .f_stop_write = &ugen_stop_io, +}; + +#if USB_DEBUG +static int ugen_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic"); +SYSCTL_INT(_hw_usb2_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug, + 0, "Debug level"); +#endif + + +/* prototypes */ + +static int +ugen_transfer_setup(struct usb2_fifo *f, + const struct usb2_config *setup, uint8_t n_setup) +{ + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_device *udev = f->udev; + uint8_t iface_index = pipe->iface_index; + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "usb2_transfer_setup()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = usb2_transfer_setup(udev, &iface_index, f->xfer, + setup, n_setup, f, f->priv_mtx); + if (error == 0) { + + if (f->xfer[0]->nframes == 1) { + error = usb2_fifo_alloc_buffer(f, + f->xfer[0]->max_data_length, 2); + } else { + error = usb2_fifo_alloc_buffer(f, + f->xfer[0]->max_frame_size, + 2 * f->xfer[0]->nframes); + } + if (error) { + usb2_transfer_unsetup(f->xfer, n_setup); + } + } + mtx_lock(f->priv_mtx); + + return (error); +} + +static int +ugen_open(struct usb2_fifo *f, int fflags, struct thread *td) +{ + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + uint8_t type; + + DPRINTFN(6, "flag=0x%x\n", fflags); + + mtx_lock(f->priv_mtx); + if (usb2_get_speed(f->udev) == USB_SPEED_HIGH) { + f->nframes = UGEN_HW_FRAMES * 8; + f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; + } else { + f->nframes = UGEN_HW_FRAMES; + f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; + } + + type = ed->bmAttributes & UE_XFERTYPE; + if (type == UE_INTERRUPT) { + f->bufsize = 0; /* use "wMaxPacketSize" */ + } + f->timeout = USB_NO_TIMEOUT; + f->flag_short = 0; + f->fifo_zlp = 0; + mtx_unlock(f->priv_mtx); + + return (0); +} + +static void +ugen_close(struct usb2_fifo *f, int fflags, struct thread *td) +{ + DPRINTFN(6, "flag=0x%x\n", fflags); + + /* cleanup */ + + mtx_lock(f->priv_mtx); + usb2_transfer_stop(f->xfer[0]); + usb2_transfer_stop(f->xfer[1]); + mtx_unlock(f->priv_mtx); + + usb2_transfer_unsetup(f->xfer, 2); + usb2_fifo_free_buffer(f); + + if (ugen_fs_uninit(f)) { + /* ignore any errors - we are closing */ + } + return; +} + +static int +ugen_open_pipe_write(struct usb2_fifo *f) +{ + struct usb2_config usb2_config[2]; + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + if (f->fs_xfer) { + /* should not happen */ + return (EINVAL); + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[1].type = UE_CONTROL; + usb2_config[1].endpoint = 0; + usb2_config[1].direction = UE_DIR_ANY; + usb2_config[1].mh.timeout = 1000; /* 1 second */ + usb2_config[1].mh.interval = 50;/* 50 milliseconds */ + usb2_config[1].mh.bufsize = sizeof(struct usb2_device_request); + usb2_config[1].mh.callback = &ugen_write_clear_stall_callback; + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb2_config[0].mh.flags.force_short_xfer = 1; + } + usb2_config[0].mh.callback = &ugen_default_write_callback; + usb2_config[0].mh.timeout = f->timeout; + usb2_config[0].mh.frames = 1; + usb2_config[0].mh.bufsize = f->bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb2_config[0].mh.flags.short_xfer_ok = 1; + usb2_config[0].mh.bufsize = 0; /* use default */ + usb2_config[0].mh.frames = f->nframes; + usb2_config[0].mh.callback = &ugen_isoc_write_callback; + usb2_config[0].mh.timeout = 0; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + /* clone configuration */ + usb2_config[1] = usb2_config[0]; + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + break; + default: + return (EINVAL); + } + return (0); +} + +static int +ugen_open_pipe_read(struct usb2_fifo *f) +{ + struct usb2_config usb2_config[2]; + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + if (f->fs_xfer) { + /* should not happen */ + return (EINVAL); + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[1].type = UE_CONTROL; + usb2_config[1].endpoint = 0; + usb2_config[1].direction = UE_DIR_ANY; + usb2_config[1].mh.timeout = 1000; /* 1 second */ + usb2_config[1].mh.interval = 50;/* 50 milliseconds */ + usb2_config[1].mh.bufsize = sizeof(struct usb2_device_request); + usb2_config[1].mh.callback = &ugen_read_clear_stall_callback; + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = UE_DIR_IN; + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb2_config[0].mh.flags.short_xfer_ok = 1; + } + usb2_config[0].mh.timeout = f->timeout; + usb2_config[0].mh.frames = 1; + usb2_config[0].mh.callback = &ugen_default_read_callback; + usb2_config[0].mh.bufsize = f->bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb2_config[0].mh.flags.short_xfer_ok = 1; + usb2_config[0].mh.bufsize = 0; /* use default */ + usb2_config[0].mh.frames = f->nframes; + usb2_config[0].mh.callback = &ugen_isoc_read_callback; + usb2_config[0].mh.timeout = 0; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + /* clone configuration */ + usb2_config[1] = usb2_config[0]; + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + break; + + default: + return (EINVAL); + } + return (0); +} + +static void +ugen_start_read(struct usb2_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_read(f)) { + /* signal error */ + usb2_fifo_put_data_error(f); + } + /* start transfers */ + usb2_transfer_start(f->xfer[0]); + usb2_transfer_start(f->xfer[1]); + return; +} + +static void +ugen_start_write(struct usb2_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_write(f)) { + /* signal error */ + usb2_fifo_get_data_error(f); + } + /* start transfers */ + usb2_transfer_start(f->xfer[0]); + usb2_transfer_start(f->xfer[1]); + return; +} + +static void +ugen_stop_io(struct usb2_fifo *f) +{ + /* stop transfers */ + usb2_transfer_stop(f->xfer[0]); + usb2_transfer_stop(f->xfer[1]); + return; +} + +static void +ugen_default_read_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_mbuf *m; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen == 0) { + if (f->fifo_zlp != 4) { + f->fifo_zlp++; + } else { + /* + * Throttle a little bit we have multiple ZLPs + * in a row! + */ + xfer->interval = 64; /* ms */ + } + } else { + /* clear throttle */ + xfer->interval = 0; + f->fifo_zlp = 0; + } + usb2_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + if (f->flag_stall) { + usb2_transfer_start(f->xfer[1]); + break; + } + USB_IF_POLL(&f->free_q, m); + if (m) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + f->fifo_zlp = 0; + usb2_transfer_start(f->xfer[1]); + } + break; + } + return; +} + +static void +ugen_default_write_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t actlen; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If writing is in stall, just jump to clear stall + * callback and solve the situation. + */ + if (f->flag_stall) { + usb2_transfer_start(f->xfer[1]); + break; + } + /* + * Write data, setup and perform hardware transfer. + */ + if (usb2_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + usb2_transfer_start(f->xfer[1]); + } + break; + } + return; +} + +static void +ugen_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugen_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugen_isoc_read_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t offset; + uint16_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(6, "actlen=%d\n", xfer->actlen); + + offset = 0; + + for (n = 0; n != xfer->aframes; n++) { + usb2_fifo_put_data(f, xfer->frbuffers, offset, + xfer->frlengths[n], 1); + offset += xfer->max_frame_size; + } + + case USB_ST_SETUP: +tr_setup: + for (n = 0; n != xfer->nframes; n++) { + /* setup size for next transfer */ + xfer->frlengths[n] = xfer->max_frame_size; + } + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + return; +} + +static void +ugen_isoc_write_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t actlen; + uint32_t offset; + uint16_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + offset = 0; + for (n = 0; n != xfer->nframes; n++) { + if (usb2_fifo_get_data(f, xfer->frbuffers, offset, + xfer->max_frame_size, &actlen, 1)) { + xfer->frlengths[n] = actlen; + offset += actlen; + } else { + break; + } + } + + for (; n != xfer->nframes; n++) { + /* fill in zero frames */ + xfer->frlengths[n] = 0; + } + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + return; +} + +static int +ugen_set_config(struct usb2_fifo *f, uint8_t index) +{ + DPRINTFN(2, "index %u\n", index); + + if (f->flag_no_uref) { + /* not the control endpoint - just forget it */ + return (EINVAL); + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + if (f->udev->curr_config_index == index) { + /* no change needed */ + return (0); + } + /* change setting - will free generic FIFOs, if any */ + if (usb2_set_config_index(f->udev, index)) { + return (EIO); + } + /* probe and attach */ + if (usb2_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) { + return (EIO); + } + return (0); +} + +static int +ugen_set_interface(struct usb2_fifo *f, + uint8_t iface_index, uint8_t alt_index) +{ + DPRINTFN(2, "%u, %u\n", iface_index, alt_index); + + if (f->flag_no_uref) { + /* not the control endpoint - just forget it */ + return (EINVAL); + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + /* change setting - will free generic FIFOs, if any */ + if (usb2_set_alt_interface_index(f->udev, iface_index, alt_index)) { + return (EIO); + } + /* probe and attach */ + if (usb2_probe_and_attach(f->udev, iface_index)) { + return (EIO); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_get_cdesc + * + * This function will retrieve the complete configuration descriptor + * at the given index. + *------------------------------------------------------------------------*/ +static int +ugen_get_cdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd) +{ + struct usb2_config_descriptor *cdesc; + struct usb2_device *udev = f->udev; + int error; + uint16_t len; + uint8_t free_data; + + DPRINTFN(6, "\n"); + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (ugd->ugd_data == NULL) { + /* userland pointer should not be zero */ + return (EINVAL); + } + if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || + (ugd->ugd_config_index == udev->curr_config_index)) { + cdesc = usb2_get_config_descriptor(udev); + if (cdesc == NULL) { + return (ENXIO); + } + free_data = 0; + + } else { + if (usb2_req_get_config_desc_full(udev, + &Giant, &cdesc, M_USBDEV, + ugd->ugd_config_index)) { + return (ENXIO); + } + free_data = 1; + } + + len = UGETW(cdesc->wTotalLength); + if (len > ugd->ugd_maxlen) { + len = ugd->ugd_maxlen; + } + DPRINTFN(6, "len=%u\n", len); + + ugd->ugd_actlen = len; + ugd->ugd_offset = 0; + + error = copyout(cdesc, ugd->ugd_data, len); + + if (free_data) { + free(cdesc, M_USBDEV); + } + return (error); +} + +static int +ugen_get_sdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd) +{ + void *ptr = f->udev->bus->scratch[0].data; + uint16_t size = sizeof(f->udev->bus->scratch[0].data); + int error; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (usb2_req_get_string_desc(f->udev, &Giant, ptr, + size, ugd->ugd_lang_id, ugd->ugd_string_index)) { + error = EINVAL; + } else { + + if (size > ((uint8_t *)ptr)[0]) { + size = ((uint8_t *)ptr)[0]; + } + if (size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } + ugd->ugd_actlen = size; + ugd->ugd_offset = 0; + + error = copyout(ptr, ugd->ugd_data, size); + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb2_gen_fill_devicenames + * + * This function dumps information about an USB device names to + * userland. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_gen_fill_devicenames(struct usb2_fifo *f, struct usb2_device_names *dn) +{ + struct usb2_interface *iface; + const char *ptr; + char *dst; + char buf[32]; + int error = 0; + int len; + int max_len; + uint8_t i; + uint8_t first = 1; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + max_len = dn->udn_devnames_len; + dst = dn->udn_devnames_ptr; + + if (max_len == 0) { + return (EINVAL); + } + /* put a zero there */ + error = copyout("", dst, 1); + if (error) { + return (error); + } + for (i = 0;; i++) { + iface = usb2_get_iface(f->udev, i); + if (iface == NULL) { + break; + } + if ((iface->subdev != NULL) && + device_is_attached(iface->subdev)) { + ptr = device_get_nameunit(iface->subdev); + if (!first) { + strlcpy(buf, ", ", sizeof(buf)); + } else { + buf[0] = 0; + } + strlcat(buf, ptr, sizeof(buf)); + len = strlen(buf) + 1; + if (len > max_len) { + break; + } + error = copyout(buf, dst, len); + if (error) { + return (error); + } + len--; + dst += len; + max_len -= len; + first = 0; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_gen_fill_deviceinfo + * + * This function dumps information about an USB device to the + * structure pointed to by the "di" argument. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_gen_fill_deviceinfo(struct usb2_fifo *f, struct usb2_device_info *di) +{ + struct usb2_device *udev; + struct usb2_device *hub; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + udev = f->udev; + + bzero(di, sizeof(di[0])); + + di->udi_bus = device_get_unit(udev->bus->bdev); + di->udi_addr = udev->address; + di->udi_index = udev->device_index; + strlcpy(di->udi_serial, udev->serial, + sizeof(di->udi_serial)); + strlcpy(di->udi_vendor, udev->manufacturer, + sizeof(di->udi_vendor)); + strlcpy(di->udi_product, udev->product, + sizeof(di->udi_product)); + usb2_printBCD(di->udi_release, sizeof(di->udi_release), + UGETW(udev->ddesc.bcdDevice)); + di->udi_vendorNo = UGETW(udev->ddesc.idVendor); + di->udi_productNo = UGETW(udev->ddesc.idProduct); + di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); + di->udi_class = udev->ddesc.bDeviceClass; + di->udi_subclass = udev->ddesc.bDeviceSubClass; + di->udi_protocol = udev->ddesc.bDeviceProtocol; + di->udi_config_no = udev->curr_config_no; + di->udi_config_index = udev->curr_config_index; + di->udi_power = udev->flags.self_powered ? 0 : udev->power; + di->udi_speed = udev->speed; + di->udi_mode = udev->flags.usb2_mode; + di->udi_power_mode = udev->power_mode; + if (udev->flags.suspended) { + di->udi_suspended = 1; + } else { + di->udi_suspended = 0; + } + + hub = udev->parent_hub; + if (hub) { + di->udi_hubaddr = hub->address; + di->udi_hubindex = hub->device_index; + di->udi_hubport = udev->port_no; + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_check_request + * + * Return values: + * 0: Access allowed + * Else: No access + *------------------------------------------------------------------------*/ +static int +ugen_check_request(struct usb2_device *udev, struct usb2_device_request *req) +{ + struct usb2_pipe *pipe; + int error; + + /* + * Avoid requests that would damage the bus integrity: + */ + if (((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_ADDRESS)) || + ((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_CONFIG)) || + ((req->bmRequestType == UT_WRITE_INTERFACE) && + (req->bRequest == UR_SET_INTERFACE))) { + /* + * These requests can be useful for testing USB drivers. + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + } + /* + * Special case - handle clearing of stall + */ + if (req->bmRequestType == UT_WRITE_ENDPOINT) { + + pipe = usb2_get_pipe_by_addr(udev, req->wIndex[0]); + if (pipe == NULL) { + return (EINVAL); + } + if (usb2_check_thread_perm(udev, curthread, FREAD | FWRITE, + pipe->iface_index, req->wIndex[0] & UE_ADDR)) { + return (EPERM); + } + if ((req->bRequest == UR_CLEAR_FEATURE) && + (UGETW(req->wValue) == UF_ENDPOINT_HALT)) { + usb2_clear_data_toggle(udev, pipe); + } + } + /* TODO: add more checks to verify the interface index */ + + return (0); +} + +int +ugen_do_request(struct usb2_fifo *f, struct usb2_ctl_request *ur) +{ + int error; + uint16_t len; + uint16_t actlen; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (ugen_check_request(f->udev, &ur->ucr_request)) { + return (EPERM); + } + len = UGETW(ur->ucr_request.wLength); + + /* check if "ucr_data" is valid */ + if (len != 0) { + if (ur->ucr_data == NULL) { + return (EFAULT); + } + } + /* do the USB request */ + error = usb2_do_request_flags + (f->udev, NULL, &ur->ucr_request, ur->ucr_data, + (ur->ucr_flags & USB_SHORT_XFER_OK) | + USB_USER_DATA_PTR, &actlen, + USB_DEFAULT_TIMEOUT); + + ur->ucr_actlen = actlen; + + if (error) { + error = EIO; + } + return (error); +} + +/*------------------------------------------------------------------------ + * ugen_re_enumerate + *------------------------------------------------------------------------*/ +static int +ugen_re_enumerate(struct usb2_fifo *f) +{ + struct usb2_device *udev = f->udev; + int error; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + /* + * This request can be useful for testing USB drivers: + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + mtx_lock(f->priv_mtx); + error = usb2_req_re_enumerate(udev, f->priv_mtx); + mtx_unlock(f->priv_mtx); + + if (error) { + return (ENXIO); + } + return (0); +} + +static int +ugen_fs_uninit(struct usb2_fifo *f) +{ + if (f->fs_xfer == NULL) { + return (EINVAL); + } + usb2_transfer_unsetup(f->fs_xfer, f->fs_ep_max); + free(f->fs_xfer, M_USB); + f->fs_xfer = NULL; + f->fs_ep_max = 0; + f->fs_ep_ptr = NULL; + f->flag_iscomplete = 0; + f->flag_no_uref = 0; /* restore operation */ + usb2_fifo_free_buffer(f); + return (0); +} + +static uint8_t +ugen_fs_get_complete(struct usb2_fifo *f, uint8_t *pindex) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *pindex = *((uint8_t *)(m->cur_data_ptr)); + + USB_IF_ENQUEUE(&f->free_q, m); + + return (0); /* success */ + } else { + + *pindex = 0; /* fix compiler warning */ + + f->flag_iscomplete = 0; + } + return (1); /* failure */ +} + +static void +ugen_fs_set_complete(struct usb2_fifo *f, uint8_t index) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + /* can happen during close */ + DPRINTF("out of buffers\n"); + return; + } + USB_MBUF_RESET(m); + + *((uint8_t *)(m->cur_data_ptr)) = index; + + USB_IF_ENQUEUE(&f->used_q, m); + + f->flag_iscomplete = 1; + + usb2_fifo_wakeup(f); + + return; +} + +static int +ugen_fs_copy_in(struct usb2_fifo *f, uint8_t ep_index) +{ + struct usb2_device_request *req; + struct usb2_xfer *xfer; + struct usb2_fs_endpoint fs_ep; + void *uaddr; /* userland pointer */ + void *kaddr; + uint32_t offset; + uint32_t length; + uint32_t n; + uint32_t rem; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usb2_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + error = copyin(f->fs_ep_ptr + + ep_index, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + /* security checks */ + + if (fs_ep.nFrames > xfer->max_frame_count) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (fs_ep.nFrames == 0) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + error = copyin(fs_ep.ppBuffer, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + /* reset first frame */ + usb2_set_frame_offset(xfer, 0, 0); + + if (xfer->flags_int.control_xfr) { + + req = xfer->frbuffers[0].buffer; + + error = copyin(fs_ep.pLength, + &length, sizeof(length)); + if (error) { + return (error); + } + if (length >= sizeof(*req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (length != 0) { + error = copyin(uaddr, req, length); + if (error) { + return (error); + } + } + if (ugen_check_request(f->udev, req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + xfer->frlengths[0] = length; + + /* Host mode only ! */ + if ((req->bmRequestType & + (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + n = 1; + offset = sizeof(*req); + + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + offset = 0; + } + + rem = xfer->max_data_length; + xfer->nframes = fs_ep.nFrames; + xfer->timeout = fs_ep.timeout; + if (xfer->timeout > 65535) { + xfer->timeout = 65535; + } + if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) + xfer->flags.short_frames_ok = 1; + else + xfer->flags.short_frames_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) + xfer->flags.force_short_xfer = 1; + else + xfer->flags.force_short_xfer = 0; + + if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) + xfer->flags.stall_pipe = 1; + else + xfer->flags.stall_pipe = 0; + + for (; n != xfer->nframes; n++) { + + error = copyin(fs_ep.pLength + n, + &length, sizeof(length)); + if (error) { + break; + } + xfer->frlengths[n] = length; + + if (length > rem) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + rem -= length; + + if (!isread) { + + /* we need to know the source buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + break; + } + if (xfer->flags_int.isochronous_xfr) { + /* get kernel buffer address */ + kaddr = xfer->frbuffers[0].buffer; + kaddr = USB_ADD_BYTES(kaddr, offset); + } else { + /* set current frame offset */ + usb2_set_frame_offset(xfer, offset, n); + + /* get kernel buffer address */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyin(uaddr, kaddr, length); + if (error) { + break; + } + } + offset += length; + } + return (error); + +complete: + mtx_lock(f->priv_mtx); + ugen_fs_set_complete(f, ep_index); + mtx_unlock(f->priv_mtx); + return (0); +} + +static int +ugen_fs_copy_out(struct usb2_fifo *f, uint8_t ep_index) +{ + struct usb2_device_request *req; + struct usb2_xfer *xfer; + struct usb2_fs_endpoint fs_ep; + struct usb2_fs_endpoint *fs_ep_uptr; /* userland ptr */ + void *uaddr; /* userland ptr */ + void *kaddr; + uint32_t offset; + uint32_t length; + uint32_t temp; + uint32_t n; + uint32_t rem; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usb2_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + fs_ep_uptr = f->fs_ep_ptr + ep_index; + error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + fs_ep.status = xfer->error; + fs_ep.aFrames = xfer->aframes; + fs_ep.isoc_time_complete = xfer->isoc_time_complete; + if (xfer->error) { + goto complete; + } + if (xfer->flags_int.control_xfr) { + req = xfer->frbuffers[0].buffer; + + /* Host mode only ! */ + if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + if (xfer->nframes == 0) + n = 0; /* should never happen */ + else + n = 1; + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + } + + /* Update lengths and copy out data */ + + rem = xfer->max_data_length; + offset = 0; + + for (; n != xfer->nframes; n++) { + + /* get initial length into "temp" */ + error = copyin(fs_ep.pLength + n, + &temp, sizeof(temp)); + if (error) { + return (error); + } + if (temp > rem) { + /* the userland length has been corrupted */ + DPRINTF("corrupt userland length " + "%u > %u\n", temp, rem); + fs_ep.status = USB_ERR_INVAL; + goto complete; + } + rem -= temp; + + /* get actual transfer length */ + length = xfer->frlengths[n]; + if (length > temp) { + /* data overflow */ + fs_ep.status = USB_ERR_INVAL; + DPRINTF("data overflow %u > %u\n", + length, temp); + goto complete; + } + if (isread) { + + /* we need to know the destination buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + kaddr = USB_ADD_BYTES( + xfer->frbuffers[0].buffer, offset); + } else { + /* multiple frame buffers */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyout(kaddr, uaddr, length); + if (error) { + return (error); + } + } + /* + * Update offset according to initial length, which is + * needed by isochronous transfers! + */ + offset += temp; + + /* update length */ + error = copyout(&length, + fs_ep.pLength + n, sizeof(length)); + if (error) { + return (error); + } + } + +complete: + /* update "aFrames" */ + error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, + sizeof(fs_ep.aFrames)); + if (error) + goto done; + + /* update "isoc_time_complete" */ + error = copyout(&fs_ep.isoc_time_complete, + &fs_ep_uptr->isoc_time_complete, + sizeof(fs_ep.isoc_time_complete)); + if (error) + goto done; + /* update "status" */ + error = copyout(&fs_ep.status, &fs_ep_uptr->status, + sizeof(fs_ep.status)); +done: + return (error); +} + +static uint8_t +ugen_fifo_in_use(struct usb2_fifo *f, int fflags) +{ + struct usb2_fifo *f_rx; + struct usb2_fifo *f_tx; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + if ((fflags & FREAD) && f_rx && + (f_rx->xfer[0] || f_rx->xfer[1])) { + return (1); /* RX FIFO in use */ + } + if ((fflags & FWRITE) && f_tx && + (f_tx->xfer[0] || f_tx->xfer[1])) { + return (1); /* TX FIFO in use */ + } + return (0); /* not in use */ +} + +static int +ugen_fs_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb2_config usb2_config[1]; + struct usb2_device_request req; + union { + struct usb2_fs_complete *pcomp; + struct usb2_fs_start *pstart; + struct usb2_fs_stop *pstop; + struct usb2_fs_init *pinit; + struct usb2_fs_uninit *puninit; + struct usb2_fs_open *popen; + struct usb2_fs_close *pclose; + struct usb2_fs_clear_stall_sync *pstall; + void *addr; + } u; + struct usb2_pipe *pipe; + struct usb2_endpoint_descriptor *ed; + int error = 0; + uint8_t iface_index; + uint8_t isread; + uint8_t ep_index; + + u.addr = addr; + + switch (cmd) { + case USB_FS_COMPLETE: + mtx_lock(f->priv_mtx); + error = ugen_fs_get_complete(f, &ep_index); + mtx_unlock(f->priv_mtx); + + if (error) { + error = EBUSY; + break; + } + u.pcomp->ep_index = ep_index; + error = ugen_fs_copy_out(f, u.pcomp->ep_index); + break; + + case USB_FS_START: + error = ugen_fs_copy_in(f, u.pstart->ep_index); + if (error) { + break; + } + mtx_lock(f->priv_mtx); + usb2_transfer_start(f->fs_xfer[u.pstart->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_STOP: + if (u.pstop->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + usb2_transfer_stop(f->fs_xfer[u.pstop->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_INIT: + /* verify input parameters */ + if (u.pinit->pEndpoints == NULL) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max > 127) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max == 0) { + error = EINVAL; + break; + } + if (f->fs_xfer != NULL) { + error = EBUSY; + break; + } + if (f->flag_no_uref) { + error = EINVAL; + break; + } + if (f->dev_ep_index != 0) { + error = EINVAL; + break; + } + if (ugen_fifo_in_use(f, fflags)) { + error = EBUSY; + break; + } + error = usb2_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max); + if (error) { + break; + } + f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * + u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO); + if (f->fs_xfer == NULL) { + usb2_fifo_free_buffer(f); + error = ENOMEM; + break; + } + f->fs_ep_max = u.pinit->ep_index_max; + f->fs_ep_ptr = u.pinit->pEndpoints; + break; + + case USB_FS_UNINIT: + if (u.puninit->dummy != 0) { + error = EINVAL; + break; + } + error = ugen_fs_uninit(f); + break; + + case USB_FS_OPEN: + if (u.popen->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.popen->ep_index] != NULL) { + error = EBUSY; + break; + } + if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { + u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; + } + if (u.popen->max_frames > USB_FS_MAX_FRAMES) { + u.popen->max_frames = USB_FS_MAX_FRAMES; + break; + } + if (u.popen->max_frames == 0) { + error = EINVAL; + break; + } + pipe = usb2_get_pipe_by_addr(f->udev, u.popen->ep_no); + if (pipe == NULL) { + error = EINVAL; + break; + } + ed = pipe->edesc; + if (ed == NULL) { + error = ENXIO; + break; + } + iface_index = pipe->iface_index; + + error = usb2_check_thread_perm(f->udev, curthread, fflags, + iface_index, u.popen->ep_no); + if (error) { + break; + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + usb2_config[0].mh.callback = &ugen_default_fs_callback; + usb2_config[0].mh.timeout = 0; /* no timeout */ + usb2_config[0].mh.frames = u.popen->max_frames; + usb2_config[0].mh.bufsize = u.popen->max_bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + if (usb2_config[0].type == UE_CONTROL) { + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + } else { + + isread = ((usb2_config[0].endpoint & + (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); + + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + isread = !isread; + } + /* check permissions */ + if (isread) { + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + } else { + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + } + } + error = usb2_transfer_setup(f->udev, &iface_index, + f->fs_xfer + u.popen->ep_index, usb2_config, 1, + f, f->priv_mtx); + if (error == 0) { + /* update maximums */ + u.popen->max_packet_length = + f->fs_xfer[u.popen->ep_index]->max_frame_size; + u.popen->max_bufsize = + f->fs_xfer[u.popen->ep_index]->max_data_length; + f->fs_xfer[u.popen->ep_index]->priv_fifo = + ((uint8_t *)0) + u.popen->ep_index; + /* + * Increase performance by dropping locks we + * don't need: + */ + f->flag_no_uref = 1; + } else { + error = ENOMEM; + } + break; + + case USB_FS_CLOSE: + if (u.pclose->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pclose->ep_index] == NULL) { + error = EINVAL; + break; + } + usb2_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); + break; + + case USB_FS_CLEAR_STALL_SYNC: + if (u.pstall->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pstall->ep_index] == NULL) { + error = EINVAL; + break; + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + error = usb2_transfer_pending(f->fs_xfer[u.pstall->ep_index]); + mtx_unlock(f->priv_mtx); + + if (error) { + return (EBUSY); + } + pipe = f->fs_xfer[u.pstall->ep_index]->pipe; + + /* setup a clear-stall packet */ + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + error = usb2_do_request(f->udev, NULL, &req, NULL); + if (error == 0) { + usb2_clear_data_toggle(f->udev, pipe); + } else { + error = ENXIO; + } + break; + + default: + error = ENOTTY; + break; + } + return (error); +} + +static int +ugen_set_short_xfer(struct usb2_fifo *f, void *addr) +{ + uint8_t t; + + if (*(int *)addr) + t = 1; + else + t = 0; + + if (f->flag_short == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->flag_short = t; + return (0); +} + +static int +ugen_set_timeout(struct usb2_fifo *f, void *addr) +{ + f->timeout = *(int *)addr; + if (f->timeout > 65535) { + /* limit user input */ + f->timeout = 65535; + } + return (0); +} + +static int +ugen_get_frame_size(struct usb2_fifo *f, void *addr) +{ + if (f->xfer[0]) { + *(int *)addr = f->xfer[0]->max_frame_size; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_buffer_size(struct usb2_fifo *f, void *addr) +{ + uint32_t t; + + if (*(int *)addr < 1024) + t = 1024; + else if (*(int *)addr < (256 * 1024)) + t = *(int *)addr; + else + t = 256 * 1024; + + if (f->bufsize == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->bufsize = t; + return (0); +} + +static int +ugen_get_buffer_size(struct usb2_fifo *f, void *addr) +{ + *(int *)addr = f->bufsize; + return (0); +} + +static int +ugen_get_iface_desc(struct usb2_fifo *f, + struct usb2_interface_descriptor *idesc) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(f->udev, f->iface_index); + if (iface && iface->idesc) { + *idesc = *(iface->idesc); + } else { + return (EIO); + } + return (0); +} + +static int +ugen_get_endpoint_desc(struct usb2_fifo *f, + struct usb2_endpoint_descriptor *ed) +{ + struct usb2_pipe *pipe; + + pipe = f->priv_sc0; + + if (pipe && pipe->edesc) { + *ed = *pipe->edesc; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_power_mode(struct usb2_fifo *f, int mode) +{ + struct usb2_device *udev = f->udev; + int err; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (EINVAL); + } + err = priv_check(curthread, PRIV_ROOT); + if (err) { + return (err); + } + switch (mode) { + case USB_POWER_MODE_OFF: + /* clear suspend */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) + break; + + /* clear port enable */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + break; + + case USB_POWER_MODE_ON: + /* enable port */ + err = usb2_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + + /* FALLTHROUGH */ + + case USB_POWER_MODE_SAVE: + case USB_POWER_MODE_RESUME: + /* TODO: implement USB power save */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + break; + + case USB_POWER_MODE_SUSPEND: + /* TODO: implement USB power save */ + err = usb2_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + break; + default: + return (EINVAL); + } + + if (err) + return (ENXIO); /* I/O failure */ + + udev->power_mode = mode; /* update copy of power mode */ + + return (0); /* success */ +} + +static int +ugen_get_power_mode(struct usb2_fifo *f) +{ + struct usb2_device *udev = f->udev; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (USB_POWER_MODE_ON); + } + return (udev->power_mode); +} + +static int +ugen_do_port_feature(struct usb2_fifo *f, uint8_t port_no, + uint8_t set, uint16_t feature) +{ + struct usb2_device *udev = f->udev; + struct usb2_hub *hub; + int err; + + err = priv_check(curthread, PRIV_ROOT); + if (err) { + return (err); + } + if (port_no == 0) { + return (EINVAL); + } + if ((udev == NULL) || + (udev->hub == NULL)) { + return (EINVAL); + } + hub = udev->hub; + + if (port_no > hub->nports) { + return (EINVAL); + } + if (set) + err = usb2_req_set_port_feature(udev, + NULL, port_no, feature); + else + err = usb2_req_clear_port_feature(udev, + NULL, port_no, feature); + + if (err) + return (ENXIO); /* failure */ + + return (0); /* success */ +} + +static int +ugen_iface_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb2_fifo *f_rx; + struct usb2_fifo *f_tx; + int error = 0; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + switch (cmd) { + case USB_SET_RX_SHORT_XFER: + if (fflags & FREAD) { + error = ugen_set_short_xfer(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_FORCE_SHORT: + if (fflags & FWRITE) { + error = ugen_set_short_xfer(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_TIMEOUT: + if (fflags & FREAD) { + error = ugen_set_timeout(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_TIMEOUT: + if (fflags & FWRITE) { + error = ugen_set_timeout(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_FRAME_SIZE: + if (fflags & FREAD) { + error = ugen_get_frame_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_FRAME_SIZE: + if (fflags & FWRITE) { + error = ugen_get_frame_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_set_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_set_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_get_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_get_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_INTERFACE_DESC: + if (fflags & FREAD) { + error = ugen_get_iface_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_INTERFACE_DESC: + if (fflags & FWRITE) { + error = ugen_get_iface_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_ENDPOINT_DESC: + if (fflags & FREAD) { + error = ugen_get_endpoint_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_ENDPOINT_DESC: + if (fflags & FWRITE) { + error = ugen_get_endpoint_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_STALL_FLAG: + if ((fflags & FREAD) && (*(int *)addr)) { + f_rx->flag_stall = 1; + } + break; + + case USB_SET_TX_STALL_FLAG: + if ((fflags & FWRITE) && (*(int *)addr)) { + f_tx->flag_stall = 1; + } + break; + + default: + error = ENOTTY; + break; + } + return (error); +} + +static int +ugen_ctrl_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + union { + struct usb2_interface_descriptor *idesc; + struct usb2_alt_interface *ai; + struct usb2_device_descriptor *ddesc; + struct usb2_config_descriptor *cdesc; + struct usb2_device_stats *stat; + uint32_t *ptime; + void *addr; + int *pint; + } u; + struct usb2_device_descriptor *dtemp; + struct usb2_config_descriptor *ctemp; + struct usb2_interface *iface; + int error = 0; + uint8_t n; + + u.addr = addr; + + switch (cmd) { + case USB_DISCOVER: + usb2_needs_explore_all(); + break; + + case USB_SETDEBUG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + usb2_debug = *(int *)addr; + break; + + case USB_GET_CONFIG: + *(int *)addr = f->udev->curr_config_index; + break; + + case USB_SET_CONFIG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_config(f, *(int *)addr); + break; + + case USB_GET_ALTINTERFACE: + iface = usb2_get_iface(f->udev, + u.ai->uai_interface_index); + if (iface && iface->idesc) { + u.ai->uai_alt_index = iface->alt_index; + } else { + error = EINVAL; + } + break; + + case USB_SET_ALTINTERFACE: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_interface(f, + u.ai->uai_interface_index, u.ai->uai_alt_index); + break; + + case USB_GET_DEVICE_DESC: + dtemp = usb2_get_device_descriptor(f->udev); + if (!dtemp) { + error = EIO; + break; + } + *u.ddesc = *dtemp; + break; + + case USB_GET_CONFIG_DESC: + ctemp = usb2_get_config_descriptor(f->udev); + if (!ctemp) { + error = EIO; + break; + } + *u.cdesc = *ctemp; + break; + + case USB_GET_FULL_DESC: + error = ugen_get_cdesc(f, addr); + break; + + case USB_GET_STRING_DESC: + error = ugen_get_sdesc(f, addr); + break; + + case USB_REQUEST: + case USB_DO_REQUEST: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_do_request(f, addr); + break; + + case USB_DEVICEINFO: + case USB_GET_DEVICEINFO: + error = usb2_gen_fill_deviceinfo(f, addr); + break; + + case USB_GET_DEVICENAMES: + error = usb2_gen_fill_devicenames(f, addr); + break; + + case USB_DEVICESTATS: + for (n = 0; n != 4; n++) { + + u.stat->uds_requests_fail[n] = + f->udev->bus->stats_err.uds_requests[n]; + + u.stat->uds_requests_ok[n] = + f->udev->bus->stats_ok.uds_requests[n]; + } + break; + + case USB_DEVICEENUMERATE: + error = ugen_re_enumerate(f); + break; + + case USB_GET_PLUGTIME: + *u.ptime = f->udev->plugtime; + break; + + case USB_CLAIM_INTERFACE: + case USB_RELEASE_INTERFACE: + /* TODO */ + break; + + case USB_IFACE_DRIVER_ACTIVE: + /* TODO */ + *u.pint = 0; + break; + + case USB_IFACE_DRIVER_DETACH: + /* TODO */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + break; + } + error = EINVAL; + break; + + case USB_SET_POWER_MODE: + error = ugen_set_power_mode(f, *u.pint); + break; + + case USB_GET_POWER_MODE: + *u.pint = ugen_get_power_mode(f); + break; + + case USB_SET_PORT_ENABLE: + error = ugen_do_port_feature(f, + *u.pint, 1, UHF_PORT_ENABLE); + break; + + case USB_SET_PORT_DISABLE: + error = ugen_do_port_feature(f, + *u.pint, 0, UHF_PORT_ENABLE); + break; + + default: + error = EINVAL; + break; + } + return (error); +} + +static int +ugen_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags, + struct thread *td) +{ + int error; + + DPRINTFN(6, "cmd=%08lx\n", cmd); + error = ugen_fs_ioctl(f, cmd, addr, fflags); + if (error == ENOTTY) { + if (f->flag_no_uref) { + mtx_lock(f->priv_mtx); + error = ugen_iface_ioctl(f, cmd, addr, fflags); + mtx_unlock(f->priv_mtx); + } else { + error = ugen_ctrl_ioctl(f, cmd, addr, fflags); + } + } + DPRINTFN(6, "error=%d\n", error); + return (error); +} + +static void +ugen_default_fs_callback(struct usb2_xfer *xfer) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u alen=%u aframes=%u\n", + USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usb2_start_hardware(xfer); + break; + default: + ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); + break; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_generic.h b/sys/dev/usb2/core/usb2_generic.h new file mode 100644 index 000000000000..3a4e7c940ea4 --- /dev/null +++ b/sys/dev/usb2/core/usb2_generic.h @@ -0,0 +1,33 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_GENERIC_H_ +#define _USB2_GENERIC_H_ + +extern struct usb2_fifo_methods usb2_ugen_methods; +int ugen_do_request(struct usb2_fifo *f, struct usb2_ctl_request *ur); + +#endif /* _USB2_GENERIC_H_ */ diff --git a/sys/dev/usb2/core/usb2_handle_request.c b/sys/dev/usb2/core/usb2_handle_request.c new file mode 100644 index 000000000000..7dc82c072699 --- /dev/null +++ b/sys/dev/usb2/core/usb2_handle_request.c @@ -0,0 +1,750 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_dynamic.h> +#include <dev/usb2/core/usb2_hub.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +/* enum */ + +enum { + ST_DATA, + ST_POST_STATUS, +}; + +/* function prototypes */ + +static uint8_t usb2_handle_get_stall(struct usb2_device *udev, uint8_t ea_val); +static usb2_error_t usb2_handle_remote_wakeup(struct usb2_xfer *xfer, uint8_t is_on); +static usb2_error_t usb2_handle_request(struct usb2_xfer *xfer); +static usb2_error_t usb2_handle_set_config(struct usb2_xfer *xfer, uint8_t conf_no); +static usb2_error_t usb2_handle_set_stall(struct usb2_xfer *xfer, uint8_t ep, uint8_t do_stall); +static usb2_error_t usb2_handle_iface_request(struct usb2_xfer *xfer, void **ppdata, uint16_t *plen, struct usb2_device_request req, uint16_t off, uint8_t state); + +/*------------------------------------------------------------------------* + * usb2_handle_request_callback + * + * This function is the USB callback for generic USB Device control + * transfers. + *------------------------------------------------------------------------*/ +void +usb2_handle_request_callback(struct usb2_xfer *xfer) +{ + usb2_error_t err; + + /* check the current transfer state */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + + /* handle the request */ + err = usb2_handle_request(xfer); + + if (err) { + + if (err == USB_ERR_BAD_CONTEXT) { + /* we need to re-setup the control transfer */ + usb2_needs_explore(xfer->udev->bus, 0); + break; + } + /* + * If no control transfer is active, + * receive the next SETUP message: + */ + goto tr_restart; + } + usb2_start_hardware(xfer); + break; + + default: + if (xfer->error != USB_ERR_CANCELLED) { + /* should not happen - try stalling */ + goto tr_restart; + } + break; + } + return; + +tr_restart: + xfer->frlengths[0] = sizeof(struct usb2_device_request); + xfer->nframes = 1; + xfer->flags.manual_status = 1; + xfer->flags.force_short_xfer = 0; + xfer->flags.stall_pipe = 1; /* cancel previous transfer, if any */ + usb2_start_hardware(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_handle_set_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_set_config(struct usb2_xfer *xfer, uint8_t conf_no) +{ + usb2_error_t err = 0; + + /* + * We need to protect against other threads doing probe and + * attach: + */ + mtx_unlock(xfer->priv_mtx); + mtx_lock(&Giant); /* XXX */ + sx_xlock(xfer->udev->default_sx + 1); + + if (conf_no == USB_UNCONFIG_NO) { + conf_no = USB_UNCONFIG_INDEX; + } else { + /* + * The relationship between config number and config index + * is very simple in our case: + */ + conf_no--; + } + + if (usb2_set_config_index(xfer->udev, conf_no)) { + DPRINTF("set config %d failed\n", conf_no); + err = USB_ERR_STALLED; + goto done; + } + if (usb2_probe_and_attach(xfer->udev, USB_IFACE_INDEX_ANY)) { + DPRINTF("probe and attach failed\n"); + err = USB_ERR_STALLED; + goto done; + } +done: + mtx_unlock(&Giant); /* XXX */ + sx_unlock(xfer->udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_handle_iface_request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_iface_request(struct usb2_xfer *xfer, + void **ppdata, uint16_t *plen, + struct usb2_device_request req, uint16_t off, uint8_t state) +{ + struct usb2_interface *iface; + struct usb2_interface *iface_parent; /* parent interface */ + struct usb2_device *udev = xfer->udev; + int error; + uint8_t iface_index; + + if ((req.bmRequestType & 0x1F) == UT_INTERFACE) { + iface_index = req.wIndex[0]; /* unicast */ + } else { + iface_index = 0; /* broadcast */ + } + + /* + * We need to protect against other threads doing probe and + * attach: + */ + mtx_unlock(xfer->priv_mtx); + mtx_lock(&Giant); /* XXX */ + sx_xlock(udev->default_sx + 1); + + error = ENXIO; + +tr_repeat: + iface = usb2_get_iface(udev, iface_index); + if ((iface == NULL) || + (iface->idesc == NULL)) { + /* end of interfaces non-existing interface */ + goto tr_stalled; + } + /* forward request to interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface->subdev != NULL) && + device_is_attached(iface->subdev)) { +#if 0 + DEVMETHOD(usb2_handle_request, NULL); /* dummy */ +#endif + error = USB2_HANDLE_REQUEST(iface->subdev, + &req, ppdata, plen, + off, (state == ST_POST_STATUS)); + } + iface_parent = usb2_get_iface(udev, iface->parent_iface_index); + + if ((iface_parent == NULL) || + (iface_parent->idesc == NULL)) { + /* non-existing interface */ + iface_parent = NULL; + } + /* forward request to parent interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface_parent != NULL) && + (iface_parent->subdev != NULL) && + ((req.bmRequestType & 0x1F) == UT_INTERFACE) && + (iface_parent->subdev != iface->subdev) && + device_is_attached(iface_parent->subdev)) { + error = USB2_HANDLE_REQUEST(iface_parent->subdev, + &req, ppdata, plen, off, + (state == ST_POST_STATUS)); + } + if (error == 0) { + /* negativly adjust pointer and length */ + *ppdata = ((uint8_t *)(*ppdata)) - off; + *plen += off; + goto tr_valid; + } else if (error == ENOTTY) { + goto tr_stalled; + } + if ((req.bmRequestType & 0x1F) != UT_INTERFACE) { + iface_index++; /* iterate */ + goto tr_repeat; + } + if (state == ST_POST_STATUS) { + /* we are complete */ + goto tr_valid; + } + switch (req.bmRequestType) { + case UT_WRITE_INTERFACE: + switch (req.bRequest) { + case UR_SET_INTERFACE: + /* + * Handle special case. If we have parent interface + * we just reset the endpoints, because this is a + * multi interface device and re-attaching only a + * part of the device is not possible. Also if the + * alternate setting is the same like before we just + * reset the interface endoints. + */ + if ((iface_parent != NULL) || + (iface->alt_index == req.wValue[0])) { + error = usb2_reset_iface_endpoints(udev, + iface_index); + if (error) { + DPRINTF("alt setting failed %s\n", + usb2_errstr(error)); + goto tr_stalled; + } + break; + } + error = usb2_set_alt_interface_index(udev, + iface_index, req.wValue[0]); + if (error) { + DPRINTF("alt setting failed %s\n", + usb2_errstr(error)); + goto tr_stalled; + } + error = usb2_probe_and_attach(udev, + iface_index); + if (error) { + DPRINTF("alt setting probe failed\n"); + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req.bRequest) { + case UR_GET_INTERFACE: + *ppdata = &iface->alt_index; + *plen = 1; + break; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } +tr_valid: + mtx_unlock(&Giant); + sx_unlock(udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (0); + +tr_stalled: + mtx_unlock(&Giant); + sx_unlock(udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (USB_ERR_STALLED); +} + +/*------------------------------------------------------------------------* + * usb2_handle_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_set_stall(struct usb2_xfer *xfer, uint8_t ep, uint8_t do_stall) +{ + usb2_error_t err; + + mtx_unlock(xfer->priv_mtx); + err = usb2_set_endpoint_stall(xfer->udev, + usb2_get_pipe_by_addr(xfer->udev, ep), do_stall); + mtx_lock(xfer->priv_mtx); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_handle_get_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_handle_get_stall(struct usb2_device *udev, uint8_t ea_val) +{ + struct usb2_pipe *pipe; + uint8_t halted; + + pipe = usb2_get_pipe_by_addr(udev, ea_val); + if (pipe == NULL) { + /* nothing to do */ + return (0); + } + mtx_lock(&udev->bus->mtx); + halted = pipe->is_stalled; + mtx_unlock(&udev->bus->mtx); + + return (halted); +} + +/*------------------------------------------------------------------------* + * usb2_handle_remote_wakeup + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_remote_wakeup(struct usb2_xfer *xfer, uint8_t is_on) +{ + struct usb2_device *udev; + struct usb2_bus *bus; + + udev = xfer->udev; + bus = udev->bus; + + mtx_lock(&bus->mtx); + + if (is_on) { + udev->flags.remote_wakeup = 1; + } else { + udev->flags.remote_wakeup = 0; + } + + (bus->methods->rem_wakeup_set) (xfer->udev, is_on); + + mtx_unlock(&bus->mtx); + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_handle_request + * + * Internal state sequence: + * + * ST_DATA -> ST_POST_STATUS + * + * Returns: + * 0: Ready to start hardware + * Else: Stall current transfer, if any + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_request(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + struct usb2_device *udev; + const void *src_zcopy; /* zero-copy source pointer */ + const void *src_mcopy; /* non zero-copy source pointer */ + uint16_t off; /* data offset */ + uint16_t rem; /* data remainder */ + uint16_t max_len; /* max fragment length */ + uint16_t wValue; + uint16_t wIndex; + uint8_t state; + usb2_error_t err; + union { + uWord wStatus; + uint8_t buf[2]; + } temp; + + /* + * Filter the USB transfer state into + * something which we understand: + */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + state = ST_DATA; + + if (!xfer->flags_int.control_act) { + /* nothing to do */ + goto tr_stalled; + } + break; + + default: /* USB_ST_TRANSFERRED */ + if (!xfer->flags_int.control_act) { + state = ST_POST_STATUS; + } else { + state = ST_DATA; + } + break; + } + + /* reset frame stuff */ + + xfer->frlengths[0] = 0; + + usb2_set_frame_offset(xfer, 0, 0); + usb2_set_frame_offset(xfer, sizeof(req), 1); + + /* get the current request, if any */ + + usb2_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + if (xfer->flags_int.control_rem == 0xFFFF) { + /* first time - not initialised */ + rem = UGETW(req.wLength); + off = 0; + } else { + /* not first time - initialised */ + rem = xfer->flags_int.control_rem; + off = UGETW(req.wLength) - rem; + } + + /* set some defaults */ + + max_len = 0; + src_zcopy = NULL; + src_mcopy = NULL; + udev = xfer->udev; + + /* get some request fields decoded */ + + wValue = UGETW(req.wValue); + wIndex = UGETW(req.wIndex); + + DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x " + "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType, + req.bRequest, wValue, wIndex, off, rem, state); + + /* demultiplex the control request */ + + switch (req.bmRequestType) { + case UT_READ_DEVICE: + if (state != ST_DATA) { + break; + } + switch (req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req.bRequest) { + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + default: + /* we use "USB_ADD_BYTES" to de-const the src_zcopy */ + err = usb2_handle_iface_request(xfer, + USB_ADD_BYTES(&src_zcopy, 0), + &max_len, req, off, state); + if (err == 0) { + goto tr_valid; + } + /* + * Reset zero-copy pointer and max length + * variable in case they were unintentionally + * set: + */ + src_zcopy = NULL; + max_len = 0; + + /* + * Check if we have a vendor specific + * descriptor: + */ + goto tr_handle_get_descriptor; + } + goto tr_valid; + +tr_handle_get_descriptor: + (usb2_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len); + if (src_zcopy == NULL) { + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_config: + temp.buf[0] = udev->curr_config_no; + src_mcopy = temp.buf; + max_len = 1; + goto tr_valid; + +tr_handle_get_status: + + wValue = 0; + + mtx_lock(&udev->bus->mtx); + if (udev->flags.remote_wakeup) { + wValue |= UDS_REMOTE_WAKEUP; + } + if (udev->flags.self_powered) { + wValue |= UDS_SELF_POWERED; + } + mtx_unlock(&udev->bus->mtx); + + USETW(temp.wStatus, wValue); + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + goto tr_valid; + +tr_handle_set_address: + if (state == ST_DATA) { + if (wValue >= 0x80) { + /* invalid value */ + goto tr_stalled; + } else if (udev->curr_config_no != 0) { + /* we are configured ! */ + goto tr_stalled; + } + } else if (state == ST_POST_STATUS) { + udev->address = (wValue & 0x7F); + goto tr_bad_context; + } + goto tr_valid; + +tr_handle_set_config: + if (state == ST_DATA) { + if (usb2_handle_set_config(xfer, req.wValue[0])) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_halt: + if (state == ST_DATA) { + if (usb2_handle_set_stall(xfer, req.wIndex[0], 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_wakeup: + if (state == ST_DATA) { + if (usb2_handle_remote_wakeup(xfer, 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_halt: + if (state == ST_DATA) { + if (usb2_handle_set_stall(xfer, req.wIndex[0], 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_wakeup: + if (state == ST_DATA) { + if (usb2_handle_remote_wakeup(xfer, 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_get_ep_status: + if (state == ST_DATA) { + temp.wStatus[0] = + usb2_handle_get_stall(udev, req.wIndex[0]); + temp.wStatus[1] = 0; + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + } + goto tr_valid; + +tr_valid: + if (state == ST_POST_STATUS) { + goto tr_stalled; + } + /* subtract offset from length */ + + max_len -= off; + + /* Compute the real maximum data length */ + + if (max_len > xfer->max_data_length) { + max_len = xfer->max_data_length; + } + if (max_len > rem) { + max_len = rem; + } + /* + * If the remainder is greater than the maximum data length, + * we need to truncate the value for the sake of the + * comparison below: + */ + if (rem > xfer->max_data_length) { + rem = xfer->max_data_length; + } + if (rem != max_len) { + /* + * If we don't transfer the data we can transfer, then + * the transfer is short ! + */ + xfer->flags.force_short_xfer = 1; + xfer->nframes = 2; + } else { + /* + * Default case + */ + xfer->flags.force_short_xfer = 0; + xfer->nframes = max_len ? 2 : 1; + } + if (max_len > 0) { + if (src_mcopy) { + src_mcopy = USB_ADD_BYTES(src_mcopy, off); + usb2_copy_in(xfer->frbuffers + 1, 0, + src_mcopy, max_len); + } else { + usb2_set_frame_data(xfer, + USB_ADD_BYTES(src_zcopy, off), 1); + } + xfer->frlengths[1] = max_len; + } else { + /* the end is reached, send status */ + xfer->flags.manual_status = 0; + xfer->frlengths[1] = 0; + } + DPRINTF("success\n"); + return (0); /* success */ + +tr_stalled: + DPRINTF("%s\n", (state == ST_POST_STATUS) ? + "complete" : "stalled"); + return (USB_ERR_STALLED); + +tr_bad_context: + DPRINTF("bad context\n"); + return (USB_ERR_BAD_CONTEXT); +} diff --git a/sys/dev/usb2/core/usb2_handle_request.h b/sys/dev/usb2/core/usb2_handle_request.h new file mode 100644 index 000000000000..6cc050321371 --- /dev/null +++ b/sys/dev/usb2/core/usb2_handle_request.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_HANDLE_REQUEST_H_ +#define _USB2_HANDLE_REQUEST_H_ + +#endif /* _USB2_HANDLE_REQUEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_hid.c b/sys/dev/usb2/core/usb2_hid.c new file mode 100644 index 000000000000..04d7d4d0c0d7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hid.c @@ -0,0 +1,582 @@ +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ + + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_hid.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_parse.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_hid.h> + +static void hid_clear_local(struct hid_item *); + +#define MAXUSAGE 100 +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur; + int32_t usages[MAXUSAGE]; + int nu; + int minset; + int multi; + int multimax; + int kindset; +}; + +/*------------------------------------------------------------------------* + * hid_clear_local + *------------------------------------------------------------------------*/ +static void +hid_clear_local(struct hid_item *c) +{ + + c->usage = 0; + c->usage_minimum = 0; + c->usage_maximum = 0; + c->designator_index = 0; + c->designator_minimum = 0; + c->designator_maximum = 0; + c->string_index = 0; + c->string_minimum = 0; + c->string_maximum = 0; + c->set_delimiter = 0; +} + +/*------------------------------------------------------------------------* + * hid_start_parse + *------------------------------------------------------------------------*/ +struct hid_data * +hid_start_parse(const void *d, int len, int kindset) +{ + struct hid_data *s; + + s = malloc(sizeof *s, M_TEMP, M_WAITOK | M_ZERO); + s->start = s->p = d; + s->end = ((const uint8_t *)d) + len; + s->kindset = kindset; + return (s); +} + +/*------------------------------------------------------------------------* + * hid_end_parse + *------------------------------------------------------------------------*/ +void +hid_end_parse(struct hid_data *s) +{ + + while (s->cur.next != NULL) { + struct hid_item *hi = s->cur.next->next; + + free(s->cur.next, M_TEMP); + s->cur.next = hi; + } + free(s, M_TEMP); +} + +/*------------------------------------------------------------------------* + * hid_get_item + *------------------------------------------------------------------------*/ +int +hid_get_item(struct hid_data *s, struct hid_item *h) +{ + struct hid_item *c = &s->cur; + unsigned int bTag, bType, bSize; + uint32_t oldpos; + const uint8_t *data; + int32_t dval; + const uint8_t *p; + struct hid_item *hi; + int i; + +top: + if (s->multimax != 0) { + if (s->multi < s->multimax) { + c->usage = s->usages[MIN(s->multi, s->nu - 1)]; + s->multi++; + *h = *c; + c->loc.pos += c->loc.size; + h->next = 0; + return (1); + } else { + c->loc.count = s->multimax; + s->multimax = 0; + s->nu = 0; + hid_clear_local(c); + } + } + for (;;) { + p = s->p; + if (p >= s->end) + return (0); + + bSize = *p++; + if (bSize == 0xfe) { + /* long item */ + bSize = *p++; + bSize |= *p++ << 8; + bTag = *p++; + data = p; + p += bSize; + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) + bSize = 4; + data = p; + p += bSize; + } + s->p = p; + switch (bSize) { + case 0: + dval = 0; + break; + case 1: + dval = (int8_t)*data++; + break; + case 2: + dval = *data++; + dval |= *data++ << 8; + dval = (int16_t)dval; + break; + case 4: + dval = *data++; + dval |= *data++ << 8; + dval |= *data++ << 16; + dval |= *data++ << 24; + break; + default: + printf("BAD LENGTH %d\n", bSize); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + if (!(s->kindset & (1 << hid_input))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_input; + c->flags = dval; + ret: + if (c->flags & HIO_VARIABLE) { + s->multimax = c->loc.count; + s->multi = 0; + c->loc.count = 1; + if (s->minset) { + for (i = c->usage_minimum; + i <= c->usage_maximum; + i++) { + s->usages[s->nu] = i; + if (s->nu < MAXUSAGE - 1) + s->nu++; + } + s->minset = 0; + } + goto top; + } else { + *h = *c; + h->next = 0; + c->loc.pos += + c->loc.size * c->loc.count; + hid_clear_local(c); + s->minset = 0; + return (1); + } + case 9: /* Output */ + if (!(s->kindset & (1 << hid_output))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + case 11: /* Feature */ + if (!(s->kindset & (1 << hid_feature))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + c->collevel--; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + default: + printf("Main bTag=%d\n", bTag); + break; + } + break; + case 1: /* Global */ + switch (bTag) { + case 0: + c->_usage_page = dval << 16; + break; + case 1: + c->logical_minimum = dval; + break; + case 2: + c->logical_maximum = dval; + break; + case 3: + c->physical_minimum = dval; + break; + case 4: + c->physical_maximum = dval; + break; + case 5: + c->unit_exponent = dval; + break; + case 6: + c->unit = dval; + break; + case 7: + c->loc.size = dval; + break; + case 8: + c->report_ID = dval; + break; + case 9: + c->loc.count = dval; + break; + case 10: /* Push */ + hi = malloc(sizeof *hi, M_TEMP, M_WAITOK); + *hi = s->cur; + c->next = hi; + break; + case 11: /* Pop */ + hi = c->next; + oldpos = c->loc.pos; + s->cur = *hi; + c->loc.pos = oldpos; + free(hi, M_TEMP); + break; + default: + printf("Global bTag=%d\n", bTag); + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage = dval; + if (s->nu < MAXUSAGE) + s->usages[s->nu++] = dval; + /* else XXX */ + break; + case 1: + s->minset = 1; + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage_minimum = dval; + break; + case 2: + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage_maximum = dval; + break; + case 3: + c->designator_index = dval; + break; + case 4: + c->designator_minimum = dval; + break; + case 5: + c->designator_maximum = dval; + break; + case 7: + c->string_index = dval; + break; + case 8: + c->string_minimum = dval; + break; + case 9: + c->string_maximum = dval; + break; + case 10: + c->set_delimiter = dval; + break; + default: + printf("Local bTag=%d\n", bTag); + break; + } + break; + default: + printf("default bType=%d\n", bType); + break; + } + } +} + +/*------------------------------------------------------------------------* + * hid_report_size + *------------------------------------------------------------------------*/ +int +hid_report_size(const void *buf, int len, enum hid_kind k, uint8_t *idp) +{ + struct hid_data *d; + struct hid_item h; + int hi, lo, size, id; + + id = 0; + hi = lo = -1; + for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) + if (h.kind == k) { + if (h.report_ID != 0 && !id) + id = h.report_ID; + if (h.report_ID == id) { + if (lo < 0) + lo = h.loc.pos; + hi = h.loc.pos + h.loc.size * h.loc.count; + } + } + hid_end_parse(d); + size = hi - lo; + if (id != 0) { + size += 8; + *idp = id; /* XXX wrong */ + } else + *idp = 0; + return ((size + 7) / 8); +} + +/*------------------------------------------------------------------------* + * hid_locate + *------------------------------------------------------------------------*/ +int +hid_locate(const void *desc, int size, uint32_t u, enum hid_kind k, + struct hid_location *loc, uint32_t *flags) +{ + struct hid_data *d; + struct hid_item h; + + for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k && !(h.flags & HIO_CONST) && h.usage == u) { + if (loc != NULL) + *loc = h.loc; + if (flags != NULL) + *flags = h.flags; + hid_end_parse(d); + return (1); + } + } + hid_end_parse(d); + loc->size = 0; + return (0); +} + +/*------------------------------------------------------------------------* + * hid_get_data + *------------------------------------------------------------------------*/ +uint32_t +hid_get_data(const uint8_t *buf, uint32_t len, struct hid_location *loc) +{ + uint32_t hpos = loc->pos; + uint32_t hsize = loc->size; + uint32_t data; + int i, s, t; + + DPRINTFN(11, "hid_get_data: loc %d/%d\n", hpos, hsize); + + if (hsize == 0) + return (0); + + data = 0; + s = hpos / 8; + for (i = hpos; i < (hpos + hsize); i += 8) { + t = (i / 8); + if (t < len) { + data |= buf[t] << ((t - s) * 8); + } + } + data >>= hpos % 8; + data &= (1 << hsize) - 1; + hsize = 32 - hsize; + /* Sign extend */ + data = ((int32_t)data << hsize) >> hsize; + DPRINTFN(11, "hid_get_data: loc %d/%d = %lu\n", + loc->pos, loc->size, (long)data); + return (data); +} + +/*------------------------------------------------------------------------* + * hid_is_collection + *------------------------------------------------------------------------*/ +int +hid_is_collection(const void *desc, int size, uint32_t usage) +{ + struct hid_data *hd; + struct hid_item hi; + int err; + + hd = hid_start_parse(desc, size, hid_input); + if (hd == NULL) + return (0); + + err = hid_get_item(hd, &hi) && + hi.kind == hid_collection && + hi.usage == usage; + hid_end_parse(hd); + return (err); +} + +/*------------------------------------------------------------------------* + * hid_get_descriptor_from_usb + * + * This function will search for a HID descriptor between two USB + * interface descriptors. + * + * Return values: + * NULL: No more HID descriptors. + * Else: Pointer to HID descriptor. + *------------------------------------------------------------------------*/ +struct usb2_hid_descriptor * +hid_get_descriptor_from_usb(struct usb2_config_descriptor *cd, + struct usb2_interface_descriptor *id) +{ + struct usb2_descriptor *desc = (void *)id; + + if (desc == NULL) { + return (NULL); + } + while ((desc = usb2_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_HID) && + (desc->bLength >= USB_HID_DESCRIPTOR_SIZE(0))) { + return (void *)desc; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hid_desc + * + * This function will read out an USB report descriptor from the USB + * device. + * + * Return values: + * NULL: Failure. + * Else: Success. The pointer should eventually be passed to free(). + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hid_desc(struct usb2_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, + usb2_malloc_type mem, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_hid_descriptor *hid; + usb2_error_t err; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + hid = hid_get_descriptor_from_usb + (usb2_get_config_descriptor(udev), iface->idesc); + + if (hid == NULL) { + return (USB_ERR_IOERROR); + } + *sizep = UGETW(hid->descrs[0].wDescriptorLength); + if (*sizep == 0) { + return (USB_ERR_IOERROR); + } + if (mtx) + mtx_unlock(mtx); + + *descp = malloc(*sizep, mem, M_ZERO | M_WAITOK); + + if (mtx) + mtx_lock(mtx); + + if (*descp == NULL) { + return (USB_ERR_NOMEM); + } + err = usb2_req_get_report_descriptor + (udev, mtx, *descp, *sizep, iface_index); + + if (err) { + free(*descp, mem); + *descp = NULL; + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} diff --git a/sys/dev/usb2/core/usb2_hid.h b/sys/dev/usb2/core/usb2_hid.h new file mode 100644 index 000000000000..9809cde9bb3b --- /dev/null +++ b/sys/dev/usb2/core/usb2_hid.h @@ -0,0 +1,89 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_CORE_HID_H_ +#define _USB2_CORE_HID_H_ + +struct usb2_hid_descriptor; +struct usb2_config_descriptor; + +enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +}; + +struct hid_location { + uint32_t size; + uint32_t count; + uint32_t pos; +}; + +struct hid_item { + /* Global */ + int32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_ID; + /* Local */ + int32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; + /* Misc */ + int32_t collection; + int collevel; + enum hid_kind kind; + uint32_t flags; + /* Location */ + struct hid_location loc; + /* */ + struct hid_item *next; +}; + +/* prototypes from "usb2_hid.c" */ + +struct hid_data *hid_start_parse(const void *d, int len, int kindset); +void hid_end_parse(struct hid_data *s); +int hid_get_item(struct hid_data *s, struct hid_item *h); +int hid_report_size(const void *buf, int len, enum hid_kind k, uint8_t *id); +int hid_locate(const void *desc, int size, uint32_t usage, enum hid_kind kind, struct hid_location *loc, uint32_t *flags); +uint32_t hid_get_data(const uint8_t *buf, uint32_t len, struct hid_location *loc); +int hid_is_collection(const void *desc, int size, uint32_t usage); +struct usb2_hid_descriptor *hid_get_descriptor_from_usb(struct usb2_config_descriptor *cd, struct usb2_interface_descriptor *id); +usb2_error_t usb2_req_get_hid_desc(struct usb2_device *udev, struct mtx *mtx, void **descp, uint16_t *sizep, usb2_malloc_type mem, uint8_t iface_index); + +#endif /* _USB2_CORE_HID_H_ */ diff --git a/sys/dev/usb2/core/usb2_hub.c b/sys/dev/usb2/core/usb2_hub.c new file mode 100644 index 000000000000..b36085583a63 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hub.c @@ -0,0 +1,1330 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#define USB_DEBUG_VAR uhub_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_dynamic.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +#define UHUB_INTR_INTERVAL 250 /* ms */ + +#if USB_DEBUG +static int uhub_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); +SYSCTL_INT(_hw_usb2_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, + "Debug level"); +#endif + +struct uhub_current_state { + uint16_t port_change; + uint16_t port_status; +}; + +struct uhub_softc { + struct uhub_current_state sc_st;/* current state */ + device_t sc_dev; /* base device */ + struct usb2_device *sc_udev; /* USB device */ + struct usb2_xfer *sc_xfer[2]; /* interrupt xfer */ + uint8_t sc_flags; +#define UHUB_FLAG_INTR_STALL 0x02 + char sc_name[32]; +}; + +#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) +#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) +#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) + +/* prototypes for type checking: */ + +static device_probe_t uhub_probe; +static device_attach_t uhub_attach; +static device_detach_t uhub_detach; + +static bus_driver_added_t uhub_driver_added; +static bus_child_location_str_t uhub_child_location_string; +static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; + +static usb2_callback_t uhub_intr_callback; +static usb2_callback_t uhub_intr_clear_stall_callback; + +static const struct usb2_config uhub_config[2] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_ANY, + .mh.timeout = 0, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uhub_intr_callback, + .mh.interval = UHUB_INTR_INTERVAL, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0, + .direction = UE_DIR_ANY, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + .mh.flags = {}, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uhub_intr_clear_stall_callback, + }, +}; + +/* + * driver instance for "hub" connected to "usb" + * and "hub" connected to "hub" + */ +static devclass_t uhub_devclass; + +static driver_t uhub_driver = +{ + .name = "ushub", + .methods = (device_method_t[]){ + DEVMETHOD(device_probe, uhub_probe), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD(bus_child_location_str, uhub_child_location_string), + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string), + DEVMETHOD(bus_driver_added, uhub_driver_added), + {0, 0} + }, + .size = sizeof(struct uhub_softc) +}; + +DRIVER_MODULE(ushub, usbus, uhub_driver, uhub_devclass, 0, 0); +DRIVER_MODULE(ushub, ushub, uhub_driver, uhub_devclass, NULL, 0); + +static void +uhub_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uhub_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UHUB_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uhub_intr_callback(struct usb2_xfer *xfer) +{ + struct uhub_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(2, "\n"); + /* + * This is an indication that some port + * has changed status. Notify the bus + * event handler thread that we need + * to be explored again: + */ + usb2_needs_explore(sc->sc_udev->bus, 0); + + case USB_ST_SETUP: + if (sc->sc_flags & UHUB_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[1]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UHUB_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[1]); + } + return; + } +} + +/*------------------------------------------------------------------------* + * uhub_explore_sub - subroutine + * + * Return values: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_explore_sub(struct uhub_softc *sc, struct usb2_port *up) +{ + struct usb2_bus *bus; + struct usb2_device *child; + uint8_t refcount; + usb2_error_t err; + + bus = sc->sc_udev->bus; + err = 0; + + /* get driver added refcount from USB bus */ + refcount = bus->driver_added_refcount; + + /* get device assosiated with the given port */ + child = usb2_bus_port_get_device(bus, up); + if (child == NULL) { + /* nothing to do */ + goto done; + } + /* check if probe and attach should be done */ + + if (child->driver_added_refcount != refcount) { + child->driver_added_refcount = refcount; + err = usb2_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (err) { + goto done; + } + } + /* start control transfer, if device mode */ + + if (child->flags.usb2_mode == USB_MODE_DEVICE) { + usb2_default_transfer_setup(child); + } + /* if a HUB becomes present, do a recursive HUB explore */ + + if (child->hub) { + err = (child->hub->explore) (child); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_read_port_status - factored out code + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_port_status ps; + usb2_error_t err; + + err = usb2_req_get_port_status( + sc->sc_udev, &Giant, &ps, portno); + + /* update status regardless of error */ + + sc->sc_st.port_status = UGETW(ps.wPortStatus); + sc->sc_st.port_change = UGETW(ps.wPortChange); + + /* debugging print */ + + DPRINTFN(4, "port %d, wPortStatus=0x%04x, " + "wPortChange=0x%04x, err=%s\n", + portno, sc->sc_st.port_status, + sc->sc_st.port_change, usb2_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_reattach_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_device *child; + struct usb2_device *udev; + usb2_error_t err; + uint8_t timeout; + uint8_t speed; + uint8_t usb2_mode; + + DPRINTF("reattaching port %d\n", portno); + + err = 0; + timeout = 0; + udev = sc->sc_udev; + child = usb2_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + +repeat: + + /* first clear the port connection change bit */ + + err = usb2_req_clear_port_feature + (udev, &Giant, portno, UHF_C_PORT_CONNECTION); + + if (err) { + goto error; + } + /* detach any existing devices */ + + if (child) { + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 1); + usb2_free_device(child); + child = NULL; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if nothing is connected to the port */ + + if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) { + goto error; + } + /* check if there is no power on the port and print a warning */ + + if (!(sc->sc_st.port_status & UPS_PORT_POWER)) { + DPRINTF("WARNING: strange, connected port %d " + "has no power\n", portno); + } + /* check if the device is in Host Mode */ + + if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { + + DPRINTF("Port %d is in Host Mode\n", portno); + + /* USB Host Mode */ + + /* wait for maximum device power up time */ + + usb2_pause_mtx(&Giant, USB_PORT_POWERUP_DELAY); + + /* reset port, which implies enabling it */ + + err = usb2_req_reset_port + (udev, &Giant, portno); + + if (err) { + DPRINTFN(0, "port %d reset " + "failed, error=%s\n", + portno, usb2_errstr(err)); + goto error; + } + /* get port status again, it might have changed during reset */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if something changed during port reset */ + + if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) || + (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) { + if (timeout) { + DPRINTFN(0, "giving up port reset " + "- device vanished!\n"); + goto error; + } + timeout = 1; + goto repeat; + } + } else { + DPRINTF("Port %d is in Device Mode\n", portno); + } + + /* + * Figure out the device speed + */ + speed = + (sc->sc_st.port_status & UPS_HIGH_SPEED) ? USB_SPEED_HIGH : + (sc->sc_st.port_status & UPS_LOW_SPEED) ? USB_SPEED_LOW : USB_SPEED_FULL; + + /* + * Figure out the device mode + * + * NOTE: This part is currently FreeBSD specific. + */ + usb2_mode = + (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE) ? + USB_MODE_DEVICE : USB_MODE_HOST; + + /* need to create a new child */ + + child = usb2_alloc_device(sc->sc_dev, udev->bus, udev, + udev->depth + 1, portno - 1, portno, speed, usb2_mode); + if (child == NULL) { + DPRINTFN(0, "could not allocate new device!\n"); + goto error; + } + return (0); /* success */ + +error: + if (child) { + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 1); + usb2_free_device(child); + child = NULL; + } + if (err == 0) { + if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + err = usb2_req_clear_port_feature + (sc->sc_udev, &Giant, + portno, UHF_PORT_ENABLE); + } + } + if (err) { + DPRINTFN(0, "device problem (%s), " + "disabling port %d\n", usb2_errstr(err), portno); + } + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_suspend_resume_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_device *child; + struct usb2_device *udev; + uint8_t is_suspend; + usb2_error_t err; + + DPRINTF("port %d\n", portno); + + udev = sc->sc_udev; + child = usb2_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + + /* first clear the port suspend change bit */ + + err = usb2_req_clear_port_feature + (udev, &Giant, portno, UHF_C_PORT_SUSPEND); + + if (err) { + goto done; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto done; + } + /* get current state */ + + if (sc->sc_st.port_status & UPS_SUSPEND) { + is_suspend = 1; + } else { + is_suspend = 0; + } + /* do the suspend or resume */ + + if (child) { + sx_xlock(child->default_sx + 1); + err = usb2_suspend_resume(child, is_suspend); + sx_unlock(child->default_sx + 1); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_explore + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_explore(struct usb2_device *udev) +{ + struct usb2_hub *hub; + struct uhub_softc *sc; + struct usb2_port *up; + usb2_error_t err; + uint8_t portno; + uint8_t x; + + hub = udev->hub; + sc = hub->hubsoftc; + + DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address); + + /* ignore hubs that are too deep */ + if (udev->depth > USB_HUB_MAX_DEPTH) { + return (USB_ERR_TOO_DEEP); + } + for (x = 0; x != hub->nports; x++) { + up = hub->ports + x; + portno = x + 1; + + err = uhub_read_port_status(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) { + err = usb2_req_clear_port_feature( + udev, &Giant, portno, UHF_C_PORT_ENABLE); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + /* + * Ignore the port error if the device + * has vanished ! + */ + } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + DPRINTFN(0, "illegal enable change, " + "port %d\n", portno); + } else { + + if (up->restartcnt == USB_RESTART_MAX) { + /* XXX could try another speed ? */ + DPRINTFN(0, "port error, giving up " + "port %d\n", portno); + } else { + sc->sc_st.port_change |= UPS_C_CONNECT_STATUS; + up->restartcnt++; + } + } + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + err = uhub_reattach_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (sc->sc_st.port_change & UPS_C_SUSPEND) { + err = uhub_suspend_resume_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + err = uhub_explore_sub(sc, up); + if (err) { + /* no device(s) present */ + continue; + } + /* explore succeeded - reset restart counter */ + up->restartcnt = 0; + } + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uhub_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* + * The subclass for USB HUBs is ignored because it is 0 for + * some and 1 for others. + */ + if ((uaa->info.bConfigIndex == 0) && + (uaa->info.bDeviceClass == UDCLASS_HUB)) { + return (0); + } + return (ENXIO); +} + +static int +uhub_attach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_device *udev = uaa->device; + struct usb2_device *parent_hub = udev->parent_hub; + struct usb2_hub *hub; + struct usb2_hub_descriptor hubdesc; + uint16_t pwrdly; + uint8_t x; + uint8_t nports; + uint8_t portno; + uint8_t removable; + uint8_t iface_index; + usb2_error_t err; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = udev; + sc->sc_dev = dev; + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + device_set_usb2_desc(dev); + + DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, " + "parent->selfpowered=%d\n", + udev->depth, + udev->flags.self_powered, + parent_hub, + parent_hub ? + parent_hub->flags.self_powered : 0); + + if (udev->depth > USB_HUB_MAX_DEPTH) { + DPRINTFN(0, "hub depth, %d, exceeded. HUB ignored!\n", + USB_HUB_MAX_DEPTH); + goto error; + } + if (!udev->flags.self_powered && parent_hub && + (!parent_hub->flags.self_powered)) { + DPRINTFN(0, "bus powered HUB connected to " + "bus powered HUB. HUB ignored!\n"); + goto error; + } + /* get HUB descriptor */ + + DPRINTFN(2, "getting HUB descriptor\n"); + + /* assuming that there is one port */ + err = usb2_req_get_hub_descriptor(udev, &Giant, &hubdesc, 1); + + nports = hubdesc.bNbrPorts; + + if (!err && (nports >= 8)) { + /* get complete HUB descriptor */ + err = usb2_req_get_hub_descriptor(udev, &Giant, &hubdesc, nports); + } + if (err) { + DPRINTFN(0, "getting hub descriptor failed," + "error=%s\n", usb2_errstr(err)); + goto error; + } + if (hubdesc.bNbrPorts != nports) { + DPRINTFN(0, "number of ports changed!\n"); + goto error; + } + if (nports == 0) { + DPRINTFN(0, "portless HUB!\n"); + goto error; + } + hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports), + M_USBDEV, M_WAITOK | M_ZERO); + + if (hub == NULL) { + goto error; + } + udev->hub = hub; + + /* init FULL-speed ISOCHRONOUS schedule */ + usb2_fs_isoc_schedule_init_all(hub->fs_isoc_schedule); + + /* initialize HUB structure */ + hub->hubsoftc = sc; + hub->explore = &uhub_explore; + hub->nports = hubdesc.bNbrPorts; + hub->hubudev = udev; + + /* if self powered hub, give ports maximum current */ + if (udev->flags.self_powered) { + hub->portpower = USB_MAX_POWER; + } else { + hub->portpower = USB_MIN_POWER; + } + + /* set up interrupt pipe */ + iface_index = 0; + err = usb2_transfer_setup(udev, &iface_index, sc->sc_xfer, + uhub_config, 2, sc, &Giant); + if (err) { + DPRINTFN(0, "cannot setup interrupt transfer, " + "errstr=%s!\n", usb2_errstr(err)); + goto error; + } + /* wait with power off for a while */ + usb2_pause_mtx(&Giant, USB_POWER_DOWN_TIME); + + /* + * To have the best chance of success we do things in the exact same + * order as Windoze98. This should not be necessary, but some + * devices do not follow the USB specs to the letter. + * + * These are the events on the bus when a hub is attached: + * Get device and config descriptors (see attach code) + * Get hub descriptor (see above) + * For all ports + * turn on power + * wait for power to become stable + * (all below happens in explore code) + * For all ports + * clear C_PORT_CONNECTION + * For all ports + * get port status + * if device connected + * wait 100 ms + * turn on reset + * wait + * clear C_PORT_RESET + * get port status + * proceed with device attachment + */ + + /* XXX should check for none, individual, or ganged power? */ + + removable = 0; + pwrdly = ((hubdesc.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + for (x = 0; x != nports; x++) { + /* set up data structures */ + struct usb2_port *up = hub->ports + x; + + up->device_index = 0; + up->restartcnt = 0; + portno = x + 1; + + /* check if port is removable */ + if (!UHD_NOT_REMOV(&hubdesc, portno)) { + removable++; + } + if (!err) { + /* turn the power on */ + err = usb2_req_set_port_feature + (udev, &Giant, portno, UHF_PORT_POWER); + } + if (err) { + DPRINTFN(0, "port %d power on failed, %s\n", + portno, usb2_errstr(err)); + } + DPRINTF("turn on port %d power\n", + portno); + + /* wait for stable power */ + usb2_pause_mtx(&Giant, pwrdly); + } + + device_printf(dev, "%d port%s with %d " + "removable, %s powered\n", nports, (nports != 1) ? "s" : "", + removable, udev->flags.self_powered ? "self" : "bus"); + + /* start the interrupt endpoint */ + + mtx_lock(sc->sc_xfer[0]->priv_mtx); + usb2_transfer_start(sc->sc_xfer[0]); + mtx_unlock(sc->sc_xfer[0]->priv_mtx); + + return (0); + +error: + usb2_transfer_unsetup(sc->sc_xfer, 2); + + if (udev->hub) { + free(udev->hub, M_USBDEV); + udev->hub = NULL; + } + return (ENXIO); +} + +/* + * Called from process context when the hub is gone. + * Detach all devices on active ports. + */ +static int +uhub_detach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb2_hub *hub = sc->sc_udev->hub; + struct usb2_device *child; + uint8_t x; + + /* detach all children first */ + bus_generic_detach(dev); + + if (hub == NULL) { /* must be partially working */ + return (0); + } + for (x = 0; x != hub->nports; x++) { + + child = usb2_bus_port_get_device(sc->sc_udev->bus, hub->ports + x); + + if (child == NULL) { + continue; + } + /* + * Subdevices are not freed, because the caller of + * uhub_detach() will do that. + */ + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 0); + usb2_free_device(child); + child = NULL; + } + + usb2_transfer_unsetup(sc->sc_xfer, 2); + + free(hub, M_USBDEV); + sc->sc_udev->hub = NULL; + return (0); +} + +static void +uhub_driver_added(device_t dev, driver_t *driver) +{ + usb2_needs_explore_all(); + return; +} + +struct hub_result { + struct usb2_device *udev; + uint8_t portno; + uint8_t iface_index; +}; + +static void +uhub_find_iface_index(struct usb2_hub *hub, device_t child, + struct hub_result *res) +{ + struct usb2_interface *iface; + struct usb2_device *udev; + uint8_t nports; + uint8_t x; + uint8_t i; + + nports = hub->nports; + for (x = 0; x != nports; x++) { + udev = usb2_bus_port_get_device(hub->hubudev->bus, + hub->ports + x); + if (!udev) { + continue; + } + for (i = 0; i != USB_IFACE_MAX; i++) { + iface = usb2_get_iface(udev, i); + if (iface && + (iface->subdev == child)) { + res->iface_index = i; + res->udev = udev; + res->portno = x + 1; + return; + } + } + } + res->iface_index = 0; + res->udev = NULL; + res->portno = 0; + return; +} + +static int +uhub_child_location_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(parent); + struct usb2_hub *hub = sc->sc_udev->hub; + struct hub_result res; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + snprintf(buf, buflen, "port=%u interface=%u", + res.portno, res.iface_index); +done: + mtx_unlock(&Giant); + + return (0); +} + +static int +uhub_child_pnpinfo_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(parent); + struct usb2_hub *hub = sc->sc_udev->hub; + struct usb2_interface *iface; + struct hub_result res; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + iface = usb2_get_iface(res.udev, res.iface_index); + if (iface && iface->idesc) { + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "sernum=\"%s\" " + "intclass=0x%02x intsubclass=0x%02x", + UGETW(res.udev->ddesc.idVendor), + UGETW(res.udev->ddesc.idProduct), + res.udev->ddesc.bDeviceClass, + res.udev->ddesc.bDeviceSubClass, + res.udev->serial, + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass); + } else { + if (buflen) { + buf[0] = '\0'; + } + goto done; + } +done: + mtx_unlock(&Giant); + + return (0); +} + +/* + * The USB Transaction Translator: + * =============================== + * + * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed + * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT + * USB transfers. To utilize bandwidth dynamically the "scatter and + * gather" principle must be applied. This means that bandwidth must + * be divided into equal parts of bandwidth. With regard to USB all + * data is transferred in smaller packets with length + * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is + * not a constant! + * + * The bandwidth scheduler which I have implemented will simply pack + * the USB transfers back to back until there is no more space in the + * schedule. Out of the 8 microframes which the USB 2.0 standard + * provides, only 6 are available for non-HIGH-speed devices. I have + * reserved the first 4 microframes for ISOCHRONOUS transfers. The + * last 2 microframes I have reserved for INTERRUPT transfers. Without + * this division, it is very difficult to allocate and free bandwidth + * dynamically. + * + * NOTE about the Transaction Translator in USB HUBs: + * + * USB HUBs have a very simple Transaction Translator, that will + * simply pipeline all the SPLIT transactions. That means that the + * transactions will be executed in the order they are queued! + * + */ + +/*------------------------------------------------------------------------* + * usb2_intr_find_best_slot + * + * Return value: + * The best Transaction Translation slot for an interrupt endpoint. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_intr_find_best_slot(uint32_t *ptr, uint8_t start, uint8_t end) +{ + uint32_t max = 0xffffffff; + uint8_t x; + uint8_t y; + + y = 0; + + /* find the last slot with lesser used bandwidth */ + + for (x = start; x < end; x++) { + if (max >= ptr[x]) { + max = ptr[x]; + y = x; + } + } + return (y); +} + +/*------------------------------------------------------------------------* + * usb2_intr_schedule_adjust + * + * This function will update the bandwith usage for the microframe + * having index "slot" by "len" bytes. "len" can be negative. If the + * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX" + * the "slot" argument will be replaced by the slot having least used + * bandwidth. + * + * Returns: + * The slot on which the bandwidth update was done. + *------------------------------------------------------------------------*/ +uint8_t +usb2_intr_schedule_adjust(struct usb2_device *udev, int16_t len, uint8_t slot) +{ + struct usb2_bus *bus = udev->bus; + struct usb2_hub *hub; + + mtx_assert(&bus->mtx, MA_OWNED); + + if (usb2_get_speed(udev) == USB_SPEED_HIGH) { + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb2_intr_find_best_slot(bus->uframe_usage, 0, + USB_HS_MICRO_FRAMES_MAX); + } + bus->uframe_usage[slot] += len; + } else { + if (usb2_get_speed(udev) == USB_SPEED_LOW) { + len *= 8; + } + /* + * The Host Controller Driver should have + * performed checks so that the lookup + * below does not result in a NULL pointer + * access. + */ + + hub = bus->devices[udev->hs_hub_addr]->hub; + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb2_intr_find_best_slot(hub->uframe_usage, + USB_FS_ISOC_UFRAME_MAX, 6); + } + hub->uframe_usage[slot] += len; + bus->uframe_usage[slot] += len; + } + return (slot); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_init_sub + * + * This function initialises an USB FULL speed isochronous schedule + * entry. + *------------------------------------------------------------------------*/ +static void +usb2_fs_isoc_schedule_init_sub(struct usb2_fs_isoc_schedule *fss) +{ + fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX * + USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_slot = 0; + return; +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_init_all + * + * This function will reset the complete USB FULL speed isochronous + * bandwidth schedule. + *------------------------------------------------------------------------*/ +void +usb2_fs_isoc_schedule_init_all(struct usb2_fs_isoc_schedule *fss) +{ + struct usb2_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX; + + while (fss != fss_end) { + usb2_fs_isoc_schedule_init_sub(fss); + fss++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_isoc_time_expand + * + * This function will expand the time counter from 7-bit to 16-bit. + * + * Returns: + * 16-bit isochronous time counter. + *------------------------------------------------------------------------*/ +uint16_t +usb2_isoc_time_expand(struct usb2_bus *bus, uint16_t isoc_time_curr) +{ + uint16_t rem; + + mtx_assert(&bus->mtx, MA_OWNED); + + rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1); + + isoc_time_curr &= (USB_ISOC_TIME_MAX - 1); + + if (isoc_time_curr < rem) { + /* the time counter wrapped around */ + bus->isoc_time_last += USB_ISOC_TIME_MAX; + } + /* update the remainder */ + + bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1); + bus->isoc_time_last |= isoc_time_curr; + + return (bus->isoc_time_last); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_isoc_time_expand + * + * This function does multiple things. First of all it will expand the + * passed isochronous time, which is the return value. Then it will + * store where the current FULL speed isochronous schedule is + * positioned in time and where the end is. See "pp_start" and + * "pp_end" arguments. + * + * Returns: + * Expanded version of "isoc_time". + * + * NOTE: This function depends on being called regularly with + * intervals less than "USB_ISOC_TIME_MAX". + *------------------------------------------------------------------------*/ +uint16_t +usb2_fs_isoc_schedule_isoc_time_expand(struct usb2_device *udev, + struct usb2_fs_isoc_schedule **pp_start, + struct usb2_fs_isoc_schedule **pp_end, + uint16_t isoc_time) +{ + struct usb2_fs_isoc_schedule *fss_end; + struct usb2_fs_isoc_schedule *fss_a; + struct usb2_fs_isoc_schedule *fss_b; + struct usb2_hub *hs_hub; + + isoc_time = usb2_isoc_time_expand(udev->bus, isoc_time); + + hs_hub = udev->bus->devices[udev->hs_hub_addr]->hub; + + if (hs_hub != NULL) { + + fss_a = hs_hub->fs_isoc_schedule + + (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX); + + hs_hub->isoc_last_time = isoc_time; + + fss_b = hs_hub->fs_isoc_schedule + + (isoc_time % USB_ISOC_TIME_MAX); + + fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX; + + *pp_start = hs_hub->fs_isoc_schedule; + *pp_end = fss_end; + + while (fss_a != fss_b) { + if (fss_a == fss_end) { + fss_a = hs_hub->fs_isoc_schedule; + continue; + } + usb2_fs_isoc_schedule_init_sub(fss_a); + fss_a++; + } + + } else { + + *pp_start = NULL; + *pp_end = NULL; + } + return (isoc_time); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_alloc + * + * This function will allocate bandwidth for an isochronous FULL speed + * transaction in the FULL speed schedule. The microframe slot where + * the transaction should be started is stored in the byte pointed to + * by "pstart". The "len" argument specifies the length of the + * transaction in bytes. + * + * Returns: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_fs_isoc_schedule_alloc(struct usb2_fs_isoc_schedule *fss, + uint8_t *pstart, uint16_t len) +{ + uint8_t slot = fss->frame_slot; + + /* Compute overhead and bit-stuffing */ + + len += 8; + + len *= 7; + len /= 6; + + if (len > fss->total_bytes) { + *pstart = 0; /* set some dummy value */ + return (1); /* error */ + } + if (len > 0) { + + fss->total_bytes -= len; + + while (len >= fss->frame_bytes) { + len -= fss->frame_bytes; + fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME; + fss->frame_slot++; + } + + fss->frame_bytes -= len; + } + *pstart = slot; + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_bus_port_get_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +struct usb2_device * +usb2_bus_port_get_device(struct usb2_bus *bus, struct usb2_port *up) +{ + if ((bus == NULL) || (up == NULL)) { + /* be NULL safe */ + return (NULL); + } + if (up->device_index == 0) { + /* nothing to do */ + return (NULL); + } + return (bus->devices[up->device_index]); +} + +/*------------------------------------------------------------------------* + * usb2_bus_port_set_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_bus_port_set_device(struct usb2_bus *bus, struct usb2_port *up, + struct usb2_device *udev, uint8_t device_index) +{ + if (bus == NULL) { + /* be NULL safe */ + return; + } + /* + * There is only one case where we don't + * have an USB port, and that is the Root Hub! + */ + if (up) { + if (udev) { + up->device_index = device_index; + } else { + device_index = up->device_index; + up->device_index = 0; + } + } + /* + * Make relationships to our new device + */ + if (device_index != 0) { + mtx_lock(&usb2_ref_lock); + bus->devices[device_index] = udev; + mtx_unlock(&usb2_ref_lock); + } + /* + * Debug print + */ + DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_needs_explore + * + * This functions is called when the USB event thread needs to run. + *------------------------------------------------------------------------*/ +void +usb2_needs_explore(struct usb2_bus *bus, uint8_t do_probe) +{ + DPRINTF("\n"); + + if (bus == NULL) { + DPRINTF("No bus pointer!\n"); + return; + } + mtx_lock(&bus->mtx); + if (do_probe) { + bus->do_probe = 1; + } + if (usb2_proc_msignal(&bus->explore_proc, + &bus->explore_msg[0], &bus->explore_msg[1])) { + /* ignore */ + } + mtx_unlock(&bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_needs_explore_all + * + * This function is called whenever a new driver is loaded and will + * cause that all USB busses are re-explored. + *------------------------------------------------------------------------*/ +void +usb2_needs_explore_all(void) +{ + struct usb2_bus *bus; + devclass_t dc; + device_t dev; + int max; + + DPRINTFN(3, "\n"); + + dc = usb2_devclass_ptr; + if (dc == NULL) { + DPRINTFN(0, "no devclass\n"); + return; + } + /* + * Explore all USB busses in parallell. + */ + max = devclass_get_maxunit(dc); + while (max >= 0) { + dev = devclass_get_device(dc, max); + if (dev) { + bus = device_get_softc(dev); + if (bus) { + usb2_needs_explore(bus, 1); + } + } + max--; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_hub.h b/sys/dev/usb2/core/usb2_hub.h new file mode 100644 index 000000000000..183c9e02d854 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hub.h @@ -0,0 +1,75 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_HUB_H_ +#define _USB2_HUB_H_ + +/* + * The following structure defines an USB port. + */ +struct usb2_port { + uint8_t restartcnt; +#define USB_RESTART_MAX 5 + uint8_t device_index; /* zero means not valid */ + uint8_t usb2_mode:1; /* current USB mode */ + uint8_t unused:7; +}; + +/* + * The following structure defines how many bytes are + * left in an 1ms USB time slot. + */ +struct usb2_fs_isoc_schedule { + uint16_t total_bytes; + uint8_t frame_bytes; + uint8_t frame_slot; +}; + +/* + * The following structure defines an USB HUB. + */ +struct usb2_hub { + struct usb2_fs_isoc_schedule fs_isoc_schedule[USB_ISOC_TIME_MAX]; + struct usb2_device *hubudev; /* the HUB device */ + usb2_error_t (*explore) (struct usb2_device *hub); + void *hubsoftc; + uint32_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint16_t portpower; /* mA per USB port */ + uint8_t isoc_last_time; + uint8_t nports; + struct usb2_port ports[0]; +}; + +/* function prototypes */ + +uint8_t usb2_intr_schedule_adjust(struct usb2_device *udev, int16_t len, uint8_t slot); +void usb2_fs_isoc_schedule_init_all(struct usb2_fs_isoc_schedule *fss); +void usb2_bus_port_set_device(struct usb2_bus *bus, struct usb2_port *up, struct usb2_device *udev, uint8_t device_index); +struct usb2_device *usb2_bus_port_get_device(struct usb2_bus *bus, struct usb2_port *up); +void usb2_needs_explore(struct usb2_bus *bus, uint8_t do_probe); +void usb2_needs_explore_all(void); + +#endif /* _USB2_HUB_H_ */ diff --git a/sys/dev/usb2/core/usb2_if.m b/sys/dev/usb2/core/usb2_if.m new file mode 100644 index 000000000000..d0db8d4445f9 --- /dev/null +++ b/sys/dev/usb2/core/usb2_if.m @@ -0,0 +1,52 @@ +#- +# Copyright (c) 2008 Hans Petter Selasky. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer, +# without modification, immediately at the beginning of the file. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $FreeBSD$ +# + +# USB interface description +# + +#include <sys/bus.h> + +INTERFACE usb2; + +# The device received a control request +# +# Return values: +# 0: Success +# ENOTTY: Transaction stalled +# Else: Use builtin request handler +# +METHOD int handle_request { + device_t dev; + const void *req; /* pointer to the device request */ + void **pptr; /* data pointer */ + uint16_t *plen; /* maximum transfer length */ + uint16_t offset; /* data offset */ + uint8_t is_complete; /* set if transfer is complete */ +}; + diff --git a/sys/dev/usb2/core/usb2_lookup.c b/sys/dev/usb2/core/usb2_lookup.c new file mode 100644 index 000000000000..eaab8a3d6b12 --- /dev/null +++ b/sys/dev/usb2/core/usb2_lookup.c @@ -0,0 +1,134 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_lookup.h> + +/*------------------------------------------------------------------------* + * usb2_lookup_id_by_info + * + * This functions takes an array of "struct usb2_device_id" and tries + * to match the entries with the information in "struct usb2_lookup_info". + * + * NOTE: The "sizeof_id" parameter must be a multiple of the + * usb2_device_id structure size. Else the behaviour of this function + * is undefined. + * + * Return values: + * NULL: No match found. + * Else: Pointer to matching entry. + *------------------------------------------------------------------------*/ +const struct usb2_device_id * +usb2_lookup_id_by_info(const struct usb2_device_id *id, uint32_t sizeof_id, + const struct usb2_lookup_info *info) +{ + const struct usb2_device_id *id_end; + + if (id == NULL) { + goto done; + } + id_end = (const void *)(((const uint8_t *)id) + sizeof_id); + + /* + * Keep on matching array entries until we find a match or + * until we reach the end of the matching array: + */ + for (; id != id_end; id++) { + + if ((id->match_flag_vendor) && + (id->idVendor != info->idVendor)) { + continue; + } + if ((id->match_flag_product) && + (id->idProduct != info->idProduct)) { + continue; + } + if ((id->match_flag_dev_lo) && + (id->bcdDevice_lo > info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_hi) && + (id->bcdDevice_hi < info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_class) && + (id->bDeviceClass != info->bDeviceClass)) { + continue; + } + if ((id->match_flag_dev_subclass) && + (id->bDeviceSubClass != info->bDeviceSubClass)) { + continue; + } + if ((id->match_flag_dev_protocol) && + (id->bDeviceProtocol != info->bDeviceProtocol)) { + continue; + } + if ((info->bDeviceClass == 0xFF) && + (!(id->match_flag_vendor)) && + ((id->match_flag_int_class) || + (id->match_flag_int_subclass) || + (id->match_flag_int_protocol))) { + continue; + } + if ((id->match_flag_int_class) && + (id->bInterfaceClass != info->bInterfaceClass)) { + continue; + } + if ((id->match_flag_int_subclass) && + (id->bInterfaceSubClass != info->bInterfaceSubClass)) { + continue; + } + if ((id->match_flag_int_protocol) && + (id->bInterfaceProtocol != info->bInterfaceProtocol)) { + continue; + } + /* We found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_lookup_id_by_uaa - factored out code + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_lookup_id_by_uaa(const struct usb2_device_id *id, uint32_t sizeof_id, + struct usb2_attach_arg *uaa) +{ + id = usb2_lookup_id_by_info(id, sizeof_id, &uaa->info); + if (id) { + /* copy driver info */ + uaa->driver_info = id->driver_info; + return (0); + } + return (ENXIO); +} diff --git a/sys/dev/usb2/core/usb2_lookup.h b/sys/dev/usb2/core/usb2_lookup.h new file mode 100644 index 000000000000..d4e39fd53b6e --- /dev/null +++ b/sys/dev/usb2/core/usb2_lookup.h @@ -0,0 +1,119 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_LOOKUP_H_ +#define _USB2_LOOKUP_H_ + +struct usb2_attach_arg; + +/* + * The following structure is used when looking up an USB driver for + * an USB device. It is inspired by the Linux structure called + * "usb2_device_id". + */ +struct usb2_device_id { + + /* Hook for driver specific information */ + const void *driver_info; + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Select which fields to match against */ + uint8_t match_flag_vendor:1; + uint8_t match_flag_product:1; + uint8_t match_flag_dev_lo:1; + uint8_t match_flag_dev_hi:1; + uint8_t match_flag_dev_class:1; + uint8_t match_flag_dev_subclass:1; + uint8_t match_flag_dev_protocol:1; + uint8_t match_flag_int_class:1; + uint8_t match_flag_int_subclass:1; + uint8_t match_flag_int_protocol:1; +}; + +#define USB_VENDOR(vend) \ + .match_flag_vendor = 1, .idVendor = (vend) + +#define USB_PRODUCT(prod) \ + .match_flag_product = 1, .idProduct = (prod) + +#define USB_VP(vend,prod) \ + USB_VENDOR(vend), USB_PRODUCT(prod) + +#define USB_VPI(vend,prod,info) \ + USB_VENDOR(vend), USB_PRODUCT(prod), USB_DRIVER_INFO(info) + +#define USB_DEV_BCD_GTEQ(lo) /* greater than or equal */ \ + .match_flag_dev_lo = 1, .bcdDevice_lo = (lo) + +#define USB_DEV_BCD_LTEQ(hi) /* less than or equal */ \ + .match_flag_dev_hi = 1, .bcdDevice_hi = (hi) + +#define USB_DEV_CLASS(dc) \ + .match_flag_dev_class = 1, .bDeviceClass = (dc) + +#define USB_DEV_SUBCLASS(dsc) \ + .match_flag_dev_subclass = 1, .bDeviceSubClass = (dsc) + +#define USB_DEV_PROTOCOL(dp) \ + .match_flag_dev_protocol = 1, .bDeviceProtocol = (dp) + +#define USB_IFACE_CLASS(ic) \ + .match_flag_int_class = 1, .bInterfaceClass = (ic) + +#define USB_IFACE_SUBCLASS(isc) \ + .match_flag_int_subclass = 1, .bInterfaceSubClass = (isc) + +#define USB_IFACE_PROTOCOL(ip) \ + .match_flag_int_protocol = 1, .bInterfaceProtocol = (ip) + +#define USB_IF_CSI(class,subclass,info) \ + USB_IFACE_CLASS(class), USB_IFACE_SUBCLASS(subclass), USB_DRIVER_INFO(info) + +#define USB_DRIVER_INFO(ptr) \ + .driver_info = ((const void *)(ptr)) + +#define USB_GET_DRIVER_INFO(did) \ + (((const uint8_t *)((did)->driver_info)) - ((const uint8_t *)0)) + +const struct usb2_device_id *usb2_lookup_id_by_info(const struct usb2_device_id *id, uint32_t sizeof_id, const struct usb2_lookup_info *info); +int usb2_lookup_id_by_uaa(const struct usb2_device_id *id, uint32_t sizeof_id, struct usb2_attach_arg *uaa); + +#endif /* _USB2_LOOKUP_H_ */ diff --git a/sys/dev/usb2/core/usb2_mbuf.c b/sys/dev/usb2/core/usb2_mbuf.c new file mode 100644 index 000000000000..1f06f0f0f0dd --- /dev/null +++ b/sys/dev/usb2/core/usb2_mbuf.c @@ -0,0 +1,77 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_mbuf.h> + +/*------------------------------------------------------------------------* + * usb2_alloc_mbufs - allocate mbufs to an usbd interface queue + * + * Returns: + * A pointer that should be passed to "free()" when the buffer(s) + * should be released. + *------------------------------------------------------------------------*/ +void * +usb2_alloc_mbufs(struct malloc_type *type, struct usb2_ifqueue *ifq, + uint32_t block_size, uint16_t nblocks) +{ + struct usb2_mbuf *m_ptr; + uint8_t *data_ptr; + void *free_ptr = NULL; + uint32_t alloc_size; + + /* align data */ + block_size += ((-block_size) & (USB_HOST_ALIGN - 1)); + + if (nblocks && block_size) { + + alloc_size = (block_size + sizeof(struct usb2_mbuf)) * nblocks; + + free_ptr = malloc(alloc_size, type, M_WAITOK | M_ZERO); + + if (free_ptr == NULL) { + goto done; + } + m_ptr = free_ptr; + data_ptr = (void *)(m_ptr + nblocks); + + while (nblocks--) { + + m_ptr->cur_data_ptr = + m_ptr->min_data_ptr = data_ptr; + + m_ptr->cur_data_len = + m_ptr->max_data_len = block_size; + + USB_IF_ENQUEUE(ifq, m_ptr); + + m_ptr++; + data_ptr += block_size; + } + } +done: + return (free_ptr); +} diff --git a/sys/dev/usb2/core/usb2_mbuf.h b/sys/dev/usb2/core/usb2_mbuf.h new file mode 100644 index 000000000000..521263ecab02 --- /dev/null +++ b/sys/dev/usb2/core/usb2_mbuf.h @@ -0,0 +1,100 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_MBUF_H_ +#define _USB2_MBUF_H_ + +/* + * The following structure defines a minimum re-implementation of the + * mbuf system in the kernel. + */ +struct usb2_mbuf { + uint8_t *cur_data_ptr; + uint8_t *min_data_ptr; + struct usb2_mbuf *usb2_nextpkt; + struct usb2_mbuf *usb2_next; + + uint32_t cur_data_len; + uint32_t max_data_len:31; + uint32_t last_packet:1; +}; + +/* + * The following structure defines a minimum re-implementation of the + * ifqueue structure in the kernel. + */ +struct usb2_ifqueue { + struct usb2_mbuf *ifq_head; + struct usb2_mbuf *ifq_tail; + + uint32_t ifq_len; + uint32_t ifq_maxlen; +}; + +#define USB_IF_ENQUEUE(ifq, m) do { \ + (m)->usb2_nextpkt = NULL; \ + if ((ifq)->ifq_tail == NULL) \ + (ifq)->ifq_head = (m); \ + else \ + (ifq)->ifq_tail->usb2_nextpkt = (m); \ + (ifq)->ifq_tail = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_DEQUEUE(ifq, m) do { \ + (m) = (ifq)->ifq_head; \ + if (m) { \ + if (((ifq)->ifq_head = (m)->usb2_nextpkt) == NULL) { \ + (ifq)->ifq_tail = NULL; \ + } \ + (m)->usb2_nextpkt = NULL; \ + (ifq)->ifq_len--; \ + } \ + } while (0) + +#define USB_IF_PREPEND(ifq, m) do { \ + (m)->usb2_nextpkt = (ifq)->ifq_head; \ + if ((ifq)->ifq_tail == NULL) { \ + (ifq)->ifq_tail = (m); \ + } \ + (ifq)->ifq_head = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen) +#define USB_IF_QLEN(ifq) ((ifq)->ifq_len) +#define USB_IF_POLL(ifq, m) ((m) = (ifq)->ifq_head) + +#define USB_MBUF_RESET(m) do { \ + (m)->cur_data_ptr = (m)->min_data_ptr; \ + (m)->cur_data_len = (m)->max_data_len; \ + (m)->last_packet = 0; \ + } while (0) + +/* prototypes */ +void *usb2_alloc_mbufs(struct malloc_type *type, struct usb2_ifqueue *ifq, uint32_t block_size, uint16_t nblocks); + +#endif /* _USB2_MBUF_H_ */ diff --git a/sys/dev/usb2/core/usb2_msctest.c b/sys/dev/usb2/core/usb2_msctest.c new file mode 100644 index 000000000000..9db204a1523c --- /dev/null +++ b/sys/dev/usb2/core/usb2_msctest.c @@ -0,0 +1,612 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * The following file contains code that will detect USB autoinstall + * disks. + * + * TODO: Potentially we could add code to automatically detect USB + * mass storage quirks for not supported SCSI commands! + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_msctest.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +enum { + ST_COMMAND, + ST_DATA_RD, + ST_DATA_RD_CS, + ST_DATA_WR, + ST_DATA_WR_CS, + ST_STATUS, + ST_MAX, +}; + +enum { + DIR_IN, + DIR_OUT, + DIR_NONE, +}; + +#define BULK_SIZE 64 /* dummy */ + + +/* Command Block Wrapper */ +struct bbb_cbw { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed; + +/* Command Status Wrapper */ +struct bbb_csw { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed; + +struct bbb_transfer { + struct mtx mtx; + struct cv cv; + struct bbb_cbw cbw; + struct bbb_csw csw; + + struct usb2_xfer *xfer[ST_MAX]; + + uint8_t *data_ptr; + + uint32_t data_len; /* bytes */ + uint32_t data_rem; /* bytes */ + uint32_t data_timeout; /* ms */ + uint32_t actlen; /* bytes */ + + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + uint8_t state; + uint8_t error; + uint8_t status_try; + + uint8_t buffer[256]; +}; + +static usb2_callback_t bbb_command_callback; +static usb2_callback_t bbb_data_read_callback; +static usb2_callback_t bbb_data_rd_cs_callback; +static usb2_callback_t bbb_data_write_callback; +static usb2_callback_t bbb_data_wr_cs_callback; +static usb2_callback_t bbb_status_callback; + +static const struct usb2_config bbb_config[ST_MAX] = { + + [ST_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = sizeof(struct bbb_cbw), + .mh.flags = {}, + .mh.callback = &bbb_command_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &bbb_data_read_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &bbb_data_rd_cs_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_DATA_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &bbb_data_write_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &bbb_data_wr_cs_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = sizeof(struct bbb_csw), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &bbb_status_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, +}; + +static void +bbb_done(struct bbb_transfer *sc, uint8_t error) +{ + struct usb2_xfer *xfer; + + xfer = sc->xfer[sc->state]; + + /* verify the error code */ + + if (error) { + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + error = 1; + break; + default: + error = 2; + break; + } + } + sc->error = error; + sc->state = ST_COMMAND; + sc->status_try = 1; + usb2_cv_signal(&sc->cv); + return; +} + +static void +bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index) +{ + sc->state = xfer_index; + usb2_transfer_start(sc->xfer[xfer_index]); + return; +} + +static void +bbb_data_clear_stall_callback(struct usb2_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + + if (usb2_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + bbb_transfer_start(sc, next_xfer); + break; + default: + bbb_done(sc, 1); + break; + } + } + return; +} + +static void +bbb_command_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + bbb_transfer_start + (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD : + (sc->dir == DIR_OUT) ? ST_DATA_WR : + ST_STATUS)); + break; + + case USB_ST_SETUP: + sc->status_try = 0; + tag = UGETDW(sc->cbw.dCBWTag) + 1; + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + USETDW(sc->cbw.dCBWDataTransferLength, sc->data_len); + sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->lun; + if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) { + sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB); + DPRINTFN(0, "Truncating long command!\n"); + } + xfer->frlengths[0] = sizeof(sc->cbw); + + usb2_set_frame_data(xfer, &sc->cbw, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + bbb_done(sc, 1); + break; + } + return; +} + +static void +bbb_data_read_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= xfer->actlen; + sc->data_ptr += xfer->actlen; + sc->actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + break; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + xfer->timeout = sc->data_timeout; + xfer->frlengths[0] = max_bulk; + + usb2_set_frame_data(xfer, sc->data_ptr, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + bbb_done(sc, 1); + } else { + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } + return; +} + +static void +bbb_data_rd_cs_callback(struct usb2_xfer *xfer) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_RD); + return; +} + +static void +bbb_data_write_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= xfer->actlen; + sc->data_ptr += xfer->actlen; + sc->actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + return; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + xfer->timeout = sc->data_timeout; + xfer->frlengths[0] = max_bulk; + + usb2_set_frame_data(xfer, sc->data_ptr, 0); + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + bbb_done(sc, 1); + } else { + bbb_transfer_start(sc, ST_DATA_WR_CS); + } + return; + + } +} + +static void +bbb_data_wr_cs_callback(struct usb2_xfer *xfer) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_WR); + return; +} + +static void +bbb_status_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* very simple status check */ + + if (xfer->actlen < sizeof(sc->csw)) { + bbb_done(sc, 1);/* error */ + } else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) { + bbb_done(sc, 0);/* success */ + } else { + bbb_done(sc, 1);/* error */ + } + break; + + case USB_ST_SETUP: + xfer->frlengths[0] = sizeof(sc->csw); + + usb2_set_frame_data(xfer, &sc->csw, 0); + usb2_start_hardware(xfer); + break; + + default: + DPRINTFN(0, "Failed to read CSW: %s, try %d\n", + usb2_errstr(xfer->error), sc->status_try); + + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->status_try)) { + bbb_done(sc, 1); + } else { + sc->status_try = 1; + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } + return; +} + +/*------------------------------------------------------------------------* + * bbb_command_start - execute a SCSI command synchronously + * + * Return values + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun, + void *data_ptr, uint32_t data_len, uint8_t cmd_len, + uint32_t data_timeout) +{ + sc->lun = lun; + sc->dir = data_len ? dir : DIR_NONE; + sc->data_ptr = data_ptr; + sc->data_len = data_len; + sc->data_rem = data_len; + sc->data_timeout = (data_timeout + USB_MS_HZ); + sc->actlen = 0; + sc->cmd_len = cmd_len; + + usb2_transfer_start(sc->xfer[sc->state]); + + while (usb2_transfer_pending(sc->xfer[sc->state])) { + usb2_cv_wait(&sc->cv, &sc->mtx); + } + return (sc->error); +} + +/*------------------------------------------------------------------------* + * usb2_test_autoinstall + * + * Return values: + * 0: This interface is an auto install disk (CD-ROM) + * Else: Not an auto install disk. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_test_autoinstall(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + usb2_error_t err; + uint8_t timeout; + uint8_t sid_type; + struct bbb_transfer *sc; + + if (udev == NULL) { + return (USB_ERR_INVAL); + } + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (USB_ERR_INVAL); + } + id = iface->idesc; + if (id == NULL) { + return (USB_ERR_INVAL); + } + if (id->bInterfaceClass != UICLASS_MASS) { + return (USB_ERR_INVAL); + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + case UISUBCLASS_UFI: + break; + default: + return (USB_ERR_INVAL); + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + break; + default: + return (USB_ERR_INVAL); + } + + sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO); + if (sc == NULL) { + return (USB_ERR_NOMEM); + } + mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF); + usb2_cv_init(&sc->cv, "WBBB"); + + err = usb2_transfer_setup(udev, + &iface_index, sc->xfer, bbb_config, + ST_MAX, sc, &sc->mtx); + + if (err) { + goto done; + } + mtx_lock(&sc->mtx); + + timeout = 4; /* tries */ + +repeat_inquiry: + + sc->cbw.CBWCDB[0] = 0x12; /* INQUIRY */ + + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 6, USB_MS_HZ); + if (err) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 12, USB_MS_HZ); + if (err) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 16, USB_MS_HZ); + } + } + if ((sc->actlen != 0) && (err == 0)) { + sid_type = sc->buffer[0] & 0x1F; + if (sid_type == 0x05) { + /* CD-ROM */ + /* XXX could investigate more */ + return (0); + } + } else if (--timeout) { + usb2_pause_mtx(&sc->mtx, USB_MS_HZ); + goto repeat_inquiry; + } + err = USB_ERR_INVAL; + goto done; + +done: + mtx_unlock(&sc->mtx); + usb2_transfer_unsetup(sc->xfer, ST_MAX); + mtx_destroy(&sc->mtx); + usb2_cv_destroy(&sc->cv); + free(sc, M_USB); + return (err); +} + +/* + * Huawei Exxx radio devices have a built in flash disk which is their + * default power up configuration. This allows the device to carry its + * own installation software. + * + * Instead of following the USB spec, and create multiple + * configuration descriptors for this, the devices expects the driver + * to send UF_DEVICE_REMOTE_WAKEUP to endpoint 2 to reset the device, + * so it reprobes, now with the radio exposed. + */ + +usb2_error_t +usb2_test_huawei(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_device_request req; + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + usb2_error_t err; + + if (udev == NULL) { + return (USB_ERR_INVAL); + } + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (USB_ERR_INVAL); + } + id = iface->idesc; + if (id == NULL) { + return (USB_ERR_INVAL); + } + if (id->bInterfaceClass != UICLASS_MASS) { + return (USB_ERR_INVAL); + } + /* Bend it like Beckham */ + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, 2); + USETW(req.wLength, 0); + + /* We get error at return, but it works */ + err = usb2_do_request_flags(udev, NULL, &req, NULL, 0, NULL, 1 * USB_MS_HZ); + + return (0); /* success */ +} diff --git a/sys/dev/usb2/core/usb2_msctest.h b/sys/dev/usb2/core/usb2_msctest.h new file mode 100644 index 000000000000..19aac3697bd1 --- /dev/null +++ b/sys/dev/usb2/core/usb2_msctest.h @@ -0,0 +1,33 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_MSCTEST_H_ +#define _USB2_MSCTEST_H_ + +usb2_error_t usb2_test_autoinstall(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_test_huawei(struct usb2_device *udev, uint8_t iface_index); + +#endif /* _USB2_MSCTEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_parse.c b/sys/dev/usb2/core/usb2_parse.c new file mode 100644 index 000000000000..223b3c2634da --- /dev/null +++ b/sys/dev/usb2/core/usb2_parse.c @@ -0,0 +1,208 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_parse.h> + +/*------------------------------------------------------------------------* + * usb2_desc_foreach + * + * This function is the safe way to iterate across the USB config + * descriptor. It contains several checks against invalid + * descriptors. If the "desc" argument passed to this function is + * "NULL" the first descriptor, if any, will be returned. + * + * Return values: + * NULL: End of descriptors + * Else: Next descriptor after "desc" + *------------------------------------------------------------------------*/ +struct usb2_descriptor * +usb2_desc_foreach(struct usb2_config_descriptor *cd, struct usb2_descriptor *desc) +{ + void *end; + + if (cd == NULL) { + return (NULL); + } + end = USB_ADD_BYTES(cd, UGETW(cd->wTotalLength)); + + if (desc == NULL) { + desc = USB_ADD_BYTES(cd, 0); + } else { + desc = USB_ADD_BYTES(desc, desc->bLength); + } + return (((((void *)desc) >= ((void *)cd)) && + (((void *)desc) < end) && + (USB_ADD_BYTES(desc, desc->bLength) >= ((void *)cd)) && + (USB_ADD_BYTES(desc, desc->bLength) <= end) && + (desc->bLength >= sizeof(*desc))) ? desc : NULL); +} + +/*------------------------------------------------------------------------* + * usb2_find_idesc + * + * This function will return the interface descriptor, if any, that + * has index "iface_index" and alternate index "alt_index". + * + * Return values: + * NULL: End of descriptors + * Else: A valid interface descriptor + *------------------------------------------------------------------------*/ +struct usb2_interface_descriptor * +usb2_find_idesc(struct usb2_config_descriptor *cd, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *id; + uint8_t curidx = 0; + uint8_t lastidx = 0; + uint8_t curaidx = 0; + uint8_t first = 1; + + while ((desc = usb2_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (void *)desc; + + if (first) { + first = 0; + lastidx = id->bInterfaceNumber; + + } else if (id->bInterfaceNumber != lastidx) { + + lastidx = id->bInterfaceNumber; + curidx++; + curaidx = 0; + + } else { + curaidx++; + } + + if ((iface_index == curidx) && (alt_index == curaidx)) { + return (id); + } + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_find_edesc + * + * This function will return the endpoint descriptor for the passed + * interface index, alternate index and endpoint index. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint descriptor + *------------------------------------------------------------------------*/ +struct usb2_endpoint_descriptor * +usb2_find_edesc(struct usb2_config_descriptor *cd, + uint8_t iface_index, uint8_t alt_index, uint8_t ep_index) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *d; + uint8_t curidx = 0; + + d = usb2_find_idesc(cd, iface_index, alt_index); + if (d == NULL) + return (NULL); + + if (ep_index >= d->bNumEndpoints) /* quick exit */ + return (NULL); + + desc = ((void *)d); + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (desc->bDescriptorType == UDESC_ENDPOINT) { + if (curidx == ep_index) { + if (desc->bLength < + sizeof(struct usb2_endpoint_descriptor)) { + /* endpoint index is invalid */ + break; + } + return ((void *)desc); + } + curidx++; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_get_no_endpoints + * + * This function will count the total number of endpoints available. + *------------------------------------------------------------------------*/ +uint16_t +usb2_get_no_endpoints(struct usb2_config_descriptor *cd) +{ + struct usb2_descriptor *desc = NULL; + uint16_t count = 0; + + while ((desc = usb2_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_ENDPOINT) { + count++; + } + } + return (count); +} + +/*------------------------------------------------------------------------* + * usb2_get_no_alts + * + * Return value: + * Number of alternate settings for the given "ifaceno". + * + * NOTE: The returned can be larger than the actual number of + * alternate settings. + *------------------------------------------------------------------------*/ +uint16_t +usb2_get_no_alts(struct usb2_config_descriptor *cd, uint8_t ifaceno) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *id; + uint16_t n = 0; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (void *)desc; + if (id->bInterfaceNumber == ifaceno) { + n++; + } + } + } + return (n); +} diff --git a/sys/dev/usb2/core/usb2_parse.h b/sys/dev/usb2/core/usb2_parse.h new file mode 100644 index 000000000000..f367f1ad757b --- /dev/null +++ b/sys/dev/usb2/core/usb2_parse.h @@ -0,0 +1,36 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_PARSE_H_ +#define _USB2_PARSE_H_ + +struct usb2_descriptor *usb2_desc_foreach(struct usb2_config_descriptor *cd, struct usb2_descriptor *desc); +struct usb2_interface_descriptor *usb2_find_idesc(struct usb2_config_descriptor *cd, uint8_t iface_index, uint8_t alt_index); +struct usb2_endpoint_descriptor *usb2_find_edesc(struct usb2_config_descriptor *cd, uint8_t iface_index, uint8_t alt_index, uint8_t ep_index); +uint16_t usb2_get_no_endpoints(struct usb2_config_descriptor *cd); +uint16_t usb2_get_no_alts(struct usb2_config_descriptor *cd, uint8_t ifaceno); + +#endif /* _USB2_PARSE_H_ */ diff --git a/sys/dev/usb2/core/usb2_process.c b/sys/dev/usb2/core/usb2_process.c new file mode 100644 index 000000000000..97365722e269 --- /dev/null +++ b/sys/dev/usb2/core/usb2_process.c @@ -0,0 +1,480 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define USB_DEBUG_VAR usb2_proc_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_util.h> + +#include <sys/proc.h> +#include <sys/kthread.h> +#include <sys/sched.h> + +#if (__FreeBSD_version < 700000) +#define thread_lock(td) mtx_lock_spin(&sched_lock) +#define thread_unlock(td) mtx_unlock_spin(&sched_lock) +#endif + +#if (__FreeBSD_version >= 800000) +#define USB_THREAD_CREATE(f, s, p, ...) \ + kproc_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kproc_suspend(p,0) +#define USB_THREAD_EXIT(err) kproc_exit(err) +#else +#define USB_THREAD_CREATE(f, s, p, ...) \ + kthread_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit(err) +#endif + +#if USB_DEBUG +static int usb2_proc_debug; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, proc, CTLFLAG_RW, 0, "USB process"); +SYSCTL_INT(_hw_usb2_proc, OID_AUTO, debug, CTLFLAG_RW, &usb2_proc_debug, 0, + "Debug level"); +#endif + +/*------------------------------------------------------------------------* + * usb2_process + * + * This function is the USB process dispatcher. + *------------------------------------------------------------------------*/ +static void +usb2_process(void *arg) +{ + struct usb2_process *up = arg; + struct usb2_proc_msg *pm; + struct thread *td; + + /* adjust priority */ + td = curthread; + thread_lock(td); + sched_prio(td, up->up_prio); + thread_unlock(td); + + mtx_lock(up->up_mtx); + + up->up_curtd = td; + + while (1) { + + if (up->up_gone) { + break; + } + /* + * NOTE to reimplementors: dequeueing a command from the + * "used" queue and executing it must be atomic, with regard + * to the "up_mtx" mutex. That means any attempt to queue a + * command by another thread must be blocked until either: + * + * 1) the command sleeps + * + * 2) the command returns + * + * Here is a practical example that shows how this helps + * solving a problem: + * + * Assume that you want to set the baud rate on a USB serial + * device. During the programming of the device you don't + * want to receive nor transmit any data, because it will be + * garbage most likely anyway. The programming of our USB + * device takes 20 milliseconds and it needs to call + * functions that sleep. + * + * Non-working solution: Before we queue the programming + * command, we stop transmission and reception of data. Then + * we queue a programming command. At the end of the + * programming command we enable transmission and reception + * of data. + * + * Problem: If a second programming command is queued while the + * first one is sleeping, we end up enabling transmission + * and reception of data too early. + * + * Working solution: Before we queue the programming command, + * we stop transmission and reception of data. Then we queue + * a programming command. Then we queue a second command + * that only enables transmission and reception of data. + * + * Why it works: If a second programming command is queued + * while the first one is sleeping, then the queueing of a + * second command to enable the data transfers, will cause + * the previous one, which is still on the queue, to be + * removed from the queue, and re-inserted after the last + * baud rate programming command, which then gives the + * desired result. + */ + pm = TAILQ_FIRST(&up->up_qhead); + + if (pm) { + DPRINTF("Message pm=%p, cb=%p (enter)\n", + pm, pm->pm_callback); + + (pm->pm_callback) (pm); + + if (pm == TAILQ_FIRST(&up->up_qhead)) { + /* nothing changed */ + TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); + pm->pm_qentry.tqe_prev = NULL; + } + DPRINTF("Message pm=%p (leave)\n", pm); + + continue; + } + /* end if messages - check if anyone is waiting for sync */ + if (up->up_dsleep) { + up->up_dsleep = 0; + usb2_cv_broadcast(&up->up_drain); + } + up->up_msleep = 1; + usb2_cv_wait(&up->up_cv, up->up_mtx); + } + + up->up_ptr = NULL; + usb2_cv_signal(&up->up_cv); + mtx_unlock(up->up_mtx); + + USB_THREAD_EXIT(0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_setup + * + * This function will create a process using the given "prio" that can + * execute callbacks. The mutex pointed to by "p_mtx" will be applied + * before calling the callbacks and released after that the callback + * has returned. The structure pointed to by "up" is assumed to be + * zeroed before this function is called. + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_setup(struct usb2_process *up, struct mtx *p_mtx, uint8_t prio) +{ + up->up_mtx = p_mtx; + up->up_prio = prio; + + TAILQ_INIT(&up->up_qhead); + + usb2_cv_init(&up->up_cv, "WMSG"); + usb2_cv_init(&up->up_drain, "DMSG"); + + if (USB_THREAD_CREATE(&usb2_process, up, + &up->up_ptr, "USBPROC")) { + DPRINTFN(0, "Unable to create USB process."); + up->up_ptr = NULL; + goto error; + } + return (0); + +error: + usb2_proc_unsetup(up); + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_proc_unsetup + * + * NOTE: If the structure pointed to by "up" is all zero, this + * function does nothing. + * + * NOTE: Messages that are pending on the process queue will not be + * removed nor called. + *------------------------------------------------------------------------*/ +void +usb2_proc_unsetup(struct usb2_process *up) +{ + if (!(up->up_mtx)) { + /* not initialised */ + return; + } + usb2_proc_drain(up); + + usb2_cv_destroy(&up->up_cv); + usb2_cv_destroy(&up->up_drain); + + /* make sure that we do not enter here again */ + up->up_mtx = NULL; + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_msignal + * + * This function will queue one of the passed USB process messages on + * the USB process queue. The first message that is not already queued + * will get queued. If both messages are already queued the one queued + * last will be removed from the queue and queued in the end. The USB + * process mutex must be locked when calling this function. This + * function exploits the fact that a process can only do one callback + * at a time. The message that was queued is returned. + *------------------------------------------------------------------------*/ +void * +usb2_proc_msignal(struct usb2_process *up, void *_pm0, void *_pm1) +{ + struct usb2_proc_msg *pm0 = _pm0; + struct usb2_proc_msg *pm1 = _pm1; + struct usb2_proc_msg *pm2; + uint32_t d; + uint8_t t; + + mtx_assert(up->up_mtx, MA_OWNED); + + t = 0; + + if (pm0->pm_qentry.tqe_prev) { + t |= 1; + } + if (pm1->pm_qentry.tqe_prev) { + t |= 2; + } + if (t == 0) { + /* + * No entries are queued. Queue "pm0" and use the existing + * message number. + */ + pm2 = pm0; + } else if (t == 1) { + /* Check if we need to increment the message number. */ + if (pm0->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm1; + } else if (t == 2) { + /* Check if we need to increment the message number. */ + if (pm1->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm0; + } else if (t == 3) { + /* + * Both entries are queued. Re-queue the entry closest to + * the end. + */ + d = (pm1->pm_num - pm0->pm_num); + + /* Check sign after subtraction */ + if (d & 0x80000000) { + pm2 = pm0; + } else { + pm2 = pm1; + } + + TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); + } else { + pm2 = NULL; /* panic - should not happen */ + } + + DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num); + + /* Put message last on queue */ + + pm2->pm_num = up->up_msg_num; + TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); + + /* Check if we need to wakeup the USB process. */ + + if (up->up_msleep) { + up->up_msleep = 0; /* save "cv_signal()" calls */ + usb2_cv_signal(&up->up_cv); + } + return (pm2); +} + +/*------------------------------------------------------------------------* + * usb2_proc_is_gone + * + * Return values: + * 0: USB process is running + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_is_gone(struct usb2_process *up) +{ + mtx_assert(up->up_mtx, MA_OWNED); + + return (up->up_gone ? 1 : 0); +} + +/*------------------------------------------------------------------------* + * usb2_proc_mwait + * + * This function will return when the USB process message pointed to + * by "pm" is no longer on a queue. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb2_proc_mwait(struct usb2_process *up, void *_pm0, void *_pm1) +{ + struct usb2_proc_msg *pm0 = _pm0; + struct usb2_proc_msg *pm1 = _pm1; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_curtd == curthread) { + /* Just remove the messages from the queue. */ + if (pm0->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); + pm0->pm_qentry.tqe_prev = NULL; + } + if (pm1->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); + pm1->pm_qentry.tqe_prev = NULL; + } + } else + while (pm0->pm_qentry.tqe_prev || + pm1->pm_qentry.tqe_prev) { + /* check if config thread is gone */ + if (up->up_gone) + break; + up->up_dsleep = 1; + usb2_cv_wait(&up->up_drain, up->up_mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_drain + * + * This function will tear down an USB process, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "up" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_proc_drain(struct usb2_process *up) +{ + if (!(up->up_mtx)) { + /* not initialised */ + return; + } + if (up->up_mtx != &Giant) { + mtx_assert(up->up_mtx, MA_NOTOWNED); + } + mtx_lock(up->up_mtx); + + /* Set the gone flag */ + + up->up_gone = 1; + + while (up->up_ptr) { + + /* Check if we need to wakeup the USB process */ + + if (up->up_msleep || up->up_csleep) { + up->up_msleep = 0; + up->up_csleep = 0; + usb2_cv_signal(&up->up_cv); + } + /* Check if we are still cold booted */ + + if (cold) { + USB_THREAD_SUSPEND(up->up_ptr); + printf("WARNING: A USB process has been left suspended!\n"); + break; + } + usb2_cv_wait(&up->up_cv, up->up_mtx); + } + /* Check if someone is waiting - should not happen */ + + if (up->up_dsleep) { + up->up_dsleep = 0; + usb2_cv_broadcast(&up->up_drain); + DPRINTF("WARNING: Someone is waiting " + "for USB process drain!\n"); + } + mtx_unlock(up->up_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_cwait + * + * This function will suspend the current process until + * "usb2_proc_signal()" or "usb2_proc_drain()" is called. The + * "timeout" parameter defines the maximum wait time in system + * ticks. If "timeout" is zero that means no timeout. + * + * NOTE: This function can only be called from within an USB process. + * + * Return values: + * USB_PROC_WAIT_TIMEOUT: Timeout + * USB_PROC_WAIT_NORMAL: Success + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_cwait(struct usb2_process *up, int timeout) +{ + int error; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_gone) { + return (USB_PROC_WAIT_DRAIN); + } + up->up_csleep = 1; + + if (timeout == 0) { + usb2_cv_wait(&up->up_cv, up->up_mtx); + error = 0; + } else { + error = usb2_cv_timedwait(&up->up_cv, up->up_mtx, timeout); + } + + up->up_csleep = 0; + + if (up->up_gone) { + return (USB_PROC_WAIT_DRAIN); + } + if (error == EWOULDBLOCK) { + return (USB_PROC_WAIT_TIMEOUT); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_proc_csignal + * + * This function will wakeup the given USB process. + *------------------------------------------------------------------------*/ +void +usb2_proc_csignal(struct usb2_process *up) +{ + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_csleep) { + up->up_csleep = 0; + usb2_cv_signal(&up->up_cv); + } + return; +} diff --git a/sys/dev/usb2/core/usb2_process.h b/sys/dev/usb2/core/usb2_process.h new file mode 100644 index 000000000000..7019735f3c14 --- /dev/null +++ b/sys/dev/usb2/core/usb2_process.h @@ -0,0 +1,89 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_PROCESS_H_ +#define _USB2_PROCESS_H_ + +#include <sys/priority.h> + +/* defines */ +#define USB_PRI_HIGH PI_NET +#define USB_PRI_MED PI_DISK + +#define USB_PROC_WAIT_TIMEOUT 2 +#define USB_PROC_WAIT_DRAIN 1 +#define USB_PROC_WAIT_NORMAL 0 + +/* structure prototypes */ + +struct usb2_proc_msg; + +/* typedefs */ + +typedef void (usb2_proc_callback_t)(struct usb2_proc_msg *hdr); + +/* + * The following structure defines the USB process message header. + */ +struct usb2_proc_msg { + TAILQ_ENTRY(usb2_proc_msg) pm_qentry; + usb2_proc_callback_t *pm_callback; + uint32_t pm_num; +}; + +/* + * The following structure defines the USB process. + */ +struct usb2_process { + TAILQ_HEAD(, usb2_proc_msg) up_qhead; + struct cv up_cv; + struct cv up_drain; + + struct proc *up_ptr; + struct thread *up_curtd; + struct mtx *up_mtx; + + uint32_t up_msg_num; + + uint8_t up_prio; + uint8_t up_gone; + uint8_t up_msleep; + uint8_t up_csleep; + uint8_t up_dsleep; +}; + +/* prototypes */ + +uint8_t usb2_proc_cwait(struct usb2_process *up, int timeout); +uint8_t usb2_proc_is_gone(struct usb2_process *up); +uint8_t usb2_proc_setup(struct usb2_process *up, struct mtx *p_mtx, uint8_t prio); +void usb2_proc_csignal(struct usb2_process *up); +void usb2_proc_drain(struct usb2_process *up); +void usb2_proc_mwait(struct usb2_process *up, void *pm0, void *pm1); +void usb2_proc_unsetup(struct usb2_process *up); +void *usb2_proc_msignal(struct usb2_process *up, void *pm0, void *pm1); + +#endif /* _USB2_PROCESS_H_ */ diff --git a/sys/dev/usb2/core/usb2_request.c b/sys/dev/usb2/core/usb2_request.c new file mode 100644 index 000000000000..72d9f175e555 --- /dev/null +++ b/sys/dev/usb2/core/usb2_request.c @@ -0,0 +1,1373 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_ioctl.h> +#include <dev/usb2/include/usb2_hid.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_dynamic.h> + +#include <sys/ctype.h> + +#if USB_DEBUG +static int usb2_pr_poll_delay = USB_PORT_RESET_DELAY; +static int usb2_pr_recovery_delay = USB_PORT_RESET_RECOVERY; +static int usb2_ss_delay = 0; + +SYSCTL_INT(_hw_usb2, OID_AUTO, pr_poll_delay, CTLFLAG_RW, + &usb2_pr_poll_delay, 0, "USB port reset poll delay in ms"); +SYSCTL_INT(_hw_usb2, OID_AUTO, pr_recovery_delay, CTLFLAG_RW, + &usb2_pr_recovery_delay, 0, "USB port reset recovery delay in ms"); +SYSCTL_INT(_hw_usb2, OID_AUTO, ss_delay, CTLFLAG_RW, + &usb2_ss_delay, 0, "USB status stage delay in ms"); +#endif + +/*------------------------------------------------------------------------* + * usb2_do_request_callback + * + * This function is the USB callback for generic USB Host control + * transfers. + *------------------------------------------------------------------------*/ +void +usb2_do_request_callback(struct usb2_xfer *xfer) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usb2_start_hardware(xfer); + break; + default: + usb2_cv_signal(xfer->udev->default_cv); + break; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_do_clear_stall_callback + * + * This function is the USB callback for generic clear stall requests. + *------------------------------------------------------------------------*/ +void +usb2_do_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + struct usb2_pipe *pipe_first; + uint8_t to = USB_EP_MAX; + + mtx_lock(xfer->usb2_mtx); + + /* round robin pipe clear stall */ + + pipe = xfer->udev->pipe_curr; + pipe_end = xfer->udev->pipes + USB_EP_MAX; + pipe_first = xfer->udev->pipes; + if (pipe == NULL) { + pipe = pipe_first; + } + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (pipe->edesc && + pipe->is_stalled) { + pipe->toggle_next = 0; + pipe->is_stalled = 0; + /* start up the current or next transfer, if any */ + usb2_command_wrapper(&pipe->pipe_q, + pipe->pipe_q.curr); + } + pipe++; + + case USB_ST_SETUP: +tr_setup: + if (pipe == pipe_end) { + pipe = pipe_first; + } + if (pipe->edesc && + pipe->is_stalled) { + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* copy in the transfer */ + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer->frlengths[0] = sizeof(req); + xfer->nframes = 1; + mtx_unlock(xfer->usb2_mtx); + + usb2_start_hardware(xfer); + + mtx_lock(xfer->usb2_mtx); + break; + } + pipe++; + if (--to) + goto tr_setup; + break; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + + /* store current pipe */ + xfer->udev->pipe_curr = pipe; + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_do_request_flags and usb2_do_request + * + * Description of arguments passed to these functions: + * + * "udev" - this is the "usb2_device" structure pointer on which the + * request should be performed. It is possible to call this function + * in both Host Side mode and Device Side mode. + * + * "mtx" - if this argument is non-NULL the mutex pointed to by it + * will get dropped and picked up during the execution of this + * function, hence this function sometimes needs to sleep. If this + * argument is NULL it has no effect. + * + * "req" - this argument must always be non-NULL and points to an + * 8-byte structure holding the USB request to be done. The USB + * request structure has a bit telling the direction of the USB + * request, if it is a read or a write. + * + * "data" - if the "wLength" part of the structure pointed to by "req" + * is non-zero this argument must point to a valid kernel buffer which + * can hold at least "wLength" bytes. If "wLength" is zero "data" can + * be NULL. + * + * "flags" - here is a list of valid flags: + * + * o USB_SHORT_XFER_OK: allows the data transfer to be shorter than + * specified + * + * o USB_USE_POLLING: forces the transfer to complete from the + * current context by polling the interrupt handler. This flag can be + * used to perform USB transfers after that the kernel has crashed. + * + * o USB_DELAY_STATUS_STAGE: allows the status stage to be performed + * at a later point in time. This is tunable by the "hw.usb.ss_delay" + * sysctl. This flag is mostly useful for debugging. + * + * o USB_USER_DATA_PTR: treat the "data" pointer like a userland + * pointer. + * + * "actlen" - if non-NULL the actual transfer length will be stored in + * the 16-bit unsigned integer pointed to by "actlen". This + * information is mostly useful when the "USB_SHORT_XFER_OK" flag is + * used. + * + * "timeout" - gives the timeout for the control transfer in + * milliseconds. A "timeout" value less than 50 milliseconds is + * treated like a 50 millisecond timeout. A "timeout" value greater + * than 30 seconds is treated like a 30 second timeout. This USB stack + * does not allow control requests without a timeout. + * + * NOTE: This function is thread safe. All calls to + * "usb2_do_request_flags" will be serialised by the use of an + * internal "sx_lock". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_do_request_flags(struct usb2_device *udev, struct mtx *mtx, + struct usb2_device_request *req, void *data, uint32_t flags, + uint16_t *actlen, uint32_t timeout) +{ + struct usb2_xfer *xfer; + const void *desc; + int err = 0; + uint32_t start_ticks; + uint32_t delta_ticks; + uint32_t max_ticks; + uint16_t length; + uint16_t temp; + + if (timeout < 50) { + /* timeout is too small */ + timeout = 50; + } + if (timeout > 30000) { + /* timeout is too big */ + timeout = 30000; + } + length = UGETW(req->wLength); + + DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x " + "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n", + udev, req->bmRequestType, req->bRequest, + req->wValue[1], req->wValue[0], + req->wIndex[1], req->wIndex[0], + req->wLength[1], req->wLength[0]); + + /* + * Set "actlen" to a known value in case the caller does not + * check the return value: + */ + if (actlen) { + *actlen = 0; + } + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + DPRINTF("USB device mode\n"); + (usb2_temp_get_desc_p) (udev, req, &desc, &temp); + if (length > temp) { + if (!(flags & USB_SHORT_XFER_OK)) { + return (USB_ERR_SHORT_XFER); + } + length = temp; + } + if (actlen) { + *actlen = length; + } + if (length > 0) { + if (flags & USB_USER_DATA_PTR) { + if (copyout(desc, data, length)) { + return (USB_ERR_INVAL); + } + } else { + bcopy(desc, data, length); + } + } + return (0); /* success */ + } + if (mtx) { + mtx_unlock(mtx); + if (mtx != &Giant) { + mtx_assert(mtx, MA_NOTOWNED); + } + } + /* + * Grab the default sx-lock so that serialisation + * is achieved when multiple threads are involved: + */ + + sx_xlock(udev->default_sx); + + /* + * Setup a new USB transfer or use the existing one, if any: + */ + usb2_default_transfer_setup(udev); + + xfer = udev->default_xfer[0]; + if (xfer == NULL) { + /* most likely out of memory */ + err = USB_ERR_NOMEM; + goto done; + } + mtx_lock(xfer->priv_mtx); + + if (flags & USB_DELAY_STATUS_STAGE) { + xfer->flags.manual_status = 1; + } else { + xfer->flags.manual_status = 0; + } + + xfer->timeout = timeout; + + start_ticks = ticks; + + max_ticks = USB_MS_TO_TICKS(timeout); + + usb2_copy_in(xfer->frbuffers, 0, req, sizeof(*req)); + + xfer->frlengths[0] = sizeof(*req); + xfer->nframes = 2; + + while (1) { + temp = length; + if (temp > xfer->max_data_length) { + temp = xfer->max_data_length; + } + xfer->frlengths[1] = temp; + + if (temp > 0) { + if (!(req->bmRequestType & UT_READ)) { + if (flags & USB_USER_DATA_PTR) { + mtx_unlock(xfer->priv_mtx); + err = usb2_copy_in_user(xfer->frbuffers + 1, + 0, data, temp); + mtx_lock(xfer->priv_mtx); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else { + usb2_copy_in(xfer->frbuffers + 1, 0, data, temp); + } + } + xfer->nframes = 2; + } else { + if (xfer->frlengths[0] == 0) { + if (xfer->flags.manual_status) { +#if USB_DEBUG + int temp; + + temp = usb2_ss_delay; + if (temp > 5000) { + temp = 5000; + } + if (temp > 0) { + usb2_pause_mtx( + xfer->priv_mtx, temp); + } +#endif + xfer->flags.manual_status = 0; + } else { + break; + } + } + xfer->nframes = 1; + } + + usb2_transfer_start(xfer); + + while (usb2_transfer_pending(xfer)) { + if ((flags & USB_USE_POLLING) || cold) { + usb2_do_poll(udev->default_xfer, USB_DEFAULT_XFER_MAX); + } else { + usb2_cv_wait(xfer->udev->default_cv, xfer->priv_mtx); + } + } + + err = xfer->error; + + if (err) { + break; + } + /* subtract length of SETUP packet, if any */ + + if (xfer->aframes > 0) { + xfer->actlen -= xfer->frlengths[0]; + } else { + xfer->actlen = 0; + } + + /* check for short packet */ + + if (temp > xfer->actlen) { + temp = xfer->actlen; + if (!(flags & USB_SHORT_XFER_OK)) { + err = USB_ERR_SHORT_XFER; + } + length = temp; + } + if (temp > 0) { + if (req->bmRequestType & UT_READ) { + if (flags & USB_USER_DATA_PTR) { + mtx_unlock(xfer->priv_mtx); + err = usb2_copy_out_user(xfer->frbuffers + 1, + 0, data, temp); + mtx_lock(xfer->priv_mtx); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else { + usb2_copy_out(xfer->frbuffers + 1, + 0, data, temp); + } + } + } + /* + * Clear "frlengths[0]" so that we don't send the setup + * packet again: + */ + xfer->frlengths[0] = 0; + + /* update length and data pointer */ + length -= temp; + data = USB_ADD_BYTES(data, temp); + + if (actlen) { + (*actlen) += temp; + } + /* check for timeout */ + + delta_ticks = ticks - start_ticks; + if (delta_ticks > max_ticks) { + if (!err) { + err = USB_ERR_TIMEOUT; + } + } + if (err) { + break; + } + } + + if (err) { + /* + * Make sure that the control endpoint is no longer + * blocked in case of a non-transfer related error: + */ + usb2_transfer_stop(xfer); + } + mtx_unlock(xfer->priv_mtx); + +done: + sx_xunlock(udev->default_sx); + + if (mtx) { + mtx_lock(mtx); + } + return ((usb2_error_t)err); +} + +/*------------------------------------------------------------------------* + * usb2_req_reset_port + * + * This function will instruct an USB HUB to perform a reset sequence + * on the specified port number. + * + * Returns: + * 0: Success. The USB device should now be at address zero. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_reset_port(struct usb2_device *udev, struct mtx *mtx, uint8_t port) +{ + struct usb2_port_status ps; + usb2_error_t err; + uint16_t n; + +#if USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + err = usb2_req_set_port_feature(udev, mtx, port, UHF_PORT_RESET); + if (err) { + goto done; + } +#if USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb2_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb2_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#if USB_DEBUG + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, pr_poll_delay); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, USB_PORT_RESET_DELAY); + n += USB_PORT_RESET_DELAY; +#endif + err = usb2_req_get_port_status(udev, mtx, &ps, port); + if (err) { + goto done; + } + /* if the device disappeared, just give up */ + if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + goto done; + } + /* check if reset is complete */ + if (UGETW(ps.wPortChange) & UPS_C_PORT_RESET) { + break; + } + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usb2_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + if (err) { + goto done; + } + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#if USB_DEBUG + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, pr_recovery_delay); +#else + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, USB_PORT_RESET_RECOVERY); +#endif + +done: + DPRINTFN(2, "port %d reset returning error=%s\n", + port, usb2_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_desc + * + * This function can be used to retrieve USB descriptors. It contains + * some additional logic like zeroing of missing descriptor bytes and + * retrying an USB descriptor in case of failure. The "min_len" + * argument specifies the minimum descriptor length. The "max_len" + * argument specifies the maximum descriptor length. If the real + * descriptor length is less than the minimum length the missing + * byte(s) will be zeroed. The length field, first byte, of the USB + * descriptor will get overwritten in case it indicates a length that + * is too big. Also the type field, second byte, of the USB descriptor + * will get forced to the correct type. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_desc(struct usb2_device *udev, struct mtx *mtx, void *desc, + uint16_t min_len, uint16_t max_len, + uint16_t id, uint8_t type, uint8_t index, + uint8_t retries) +{ + struct usb2_device_request req; + uint8_t *buf; + usb2_error_t err; + + DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n", + id, type, index, max_len); + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, type, index); + USETW(req.wIndex, id); + + while (1) { + + if ((min_len < 2) || (max_len < 2)) { + err = USB_ERR_INVAL; + goto done; + } + USETW(req.wLength, min_len); + + err = usb2_do_request(udev, mtx, &req, desc); + + if (err) { + if (!retries) { + goto done; + } + retries--; + + usb2_pause_mtx(mtx, 200); + + continue; + } + buf = desc; + + if (min_len == max_len) { + + /* enforce correct type and length */ + + if (buf[0] > min_len) { + buf[0] = min_len; + } + buf[1] = type; + + goto done; + } + /* range check */ + + if (max_len > buf[0]) { + max_len = buf[0]; + } + /* zero minimum data */ + + while (min_len > max_len) { + min_len--; + buf[min_len] = 0; + } + + /* set new minimum length */ + + min_len = max_len; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_string_any + * + * This function will return the string given by "string_index" + * using the first language ID. The maximum length "len" includes + * the terminating zero. The "len" argument should be twice as + * big pluss 2 bytes, compared with the actual maximum string length ! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_string_any(struct usb2_device *udev, struct mtx *mtx, char *buf, + uint16_t len, uint8_t string_index) +{ + char *s; + uint8_t *temp; + uint16_t i; + uint16_t n; + uint16_t c; + uint8_t swap; + usb2_error_t err; + + if (len == 0) { + /* should not happen */ + return (USB_ERR_NORMAL_COMPLETION); + } + buf[0] = 0; + + if (string_index == 0) { + /* this is the language table */ + return (USB_ERR_INVAL); + } + if (udev->flags.no_strings) { + return (USB_ERR_STALLED); + } + err = usb2_req_get_string_desc + (udev, mtx, buf, len, udev->langid, string_index); + if (err) { + return (err); + } + temp = (uint8_t *)buf; + + if (temp[0] < 2) { + /* string length is too short */ + return (USB_ERR_INVAL); + } + /* reserve one byte for terminating zero */ + len--; + + /* find maximum length */ + s = buf; + n = (temp[0] / 2) - 1; + if (n > len) { + n = len; + } + /* skip descriptor header */ + temp += 2; + + /* reset swap state */ + swap = 3; + + /* convert and filter */ + for (i = 0; (i != n); i++) { + c = UGETW(temp + (2 * i)); + + /* convert from Unicode, handle buggy strings */ + if (((c & 0xff00) == 0) && (swap & 1)) { + /* Little Endian, default */ + *s = c; + swap = 1; + } else if (((c & 0x00ff) == 0) && (swap & 2)) { + /* Big Endian */ + *s = c >> 8; + swap = 2; + } else { + *s = '.'; + } + + /* + * Filter by default - we don't allow greater and less than + * signs because they might confuse the dmesg printouts! + */ + if ((*s == '<') || (*s == '>') || (!isprint(*s))) { + *s = '.'; + } + s++; + } + *s = 0; + return (USB_ERR_NORMAL_COMPLETION); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_string_desc + * + * If you don't know the language ID, consider using + * "usb2_req_get_string_any()". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_string_desc(struct usb2_device *udev, struct mtx *mtx, void *sdesc, + uint16_t max_len, uint16_t lang_id, + uint8_t string_index) +{ + return (usb2_req_get_desc(udev, mtx, sdesc, 2, max_len, lang_id, + UDESC_STRING, string_index, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config_desc(struct usb2_device *udev, struct mtx *mtx, + struct usb2_config_descriptor *d, uint8_t conf_index) +{ + usb2_error_t err; + + DPRINTFN(4, "confidx=%d\n", conf_index); + + err = usb2_req_get_desc(udev, mtx, d, sizeof(*d), + sizeof(*d), 0, UDESC_CONFIG, conf_index, 0); + if (err) { + goto done; + } + /* Extra sanity checking */ + if (UGETW(d->wTotalLength) < sizeof(*d)) { + err = USB_ERR_INVAL; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config_desc_full + * + * This function gets the complete USB configuration descriptor and + * ensures that "wTotalLength" is correct. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config_desc_full(struct usb2_device *udev, struct mtx *mtx, + struct usb2_config_descriptor **ppcd, struct malloc_type *mtype, + uint8_t index) +{ + struct usb2_config_descriptor cd; + struct usb2_config_descriptor *cdesc; + uint16_t len; + usb2_error_t err; + + DPRINTFN(4, "index=%d\n", index); + + *ppcd = NULL; + + err = usb2_req_get_config_desc(udev, mtx, &cd, index); + if (err) { + return (err); + } + /* get full descriptor */ + len = UGETW(cd.wTotalLength); + if (len < sizeof(*cdesc)) { + /* corrupt descriptor */ + return (USB_ERR_INVAL); + } + cdesc = malloc(len, mtype, M_WAITOK); + if (cdesc == NULL) { + return (USB_ERR_NOMEM); + } + err = usb2_req_get_desc(udev, mtx, cdesc, len, len, 0, + UDESC_CONFIG, index, 3); + if (err) { + free(cdesc, mtype); + return (err); + } + /* make sure that the device is not fooling us: */ + USETW(cdesc->wTotalLength, len); + + *ppcd = cdesc; + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_req_get_device_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_device_desc(struct usb2_device *udev, struct mtx *mtx, + struct usb2_device_descriptor *d) +{ + DPRINTFN(4, "\n"); + return (usb2_req_get_desc(udev, mtx, d, sizeof(*d), + sizeof(*d), 0, UDESC_DEVICE, 0, 3)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, + uint8_t *alt_iface_no, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + return (usb2_do_request(udev, mtx, &req, alt_iface_no)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t alt_no) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_WRITE_INTERFACE; + req.bRequest = UR_SET_INTERFACE; + req.wValue[0] = alt_no; + req.wValue[1] = 0; + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_device_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_device_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_status *st) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(*st)); + return (usb2_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hub_descriptor(struct usb2_device *udev, struct mtx *mtx, + struct usb2_hub_descriptor *hd, uint8_t nports) +{ + struct usb2_device_request req; + uint16_t len = (nports + 7 + (8 * 8)) / 8; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hub_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hub_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_hub_status *st) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(struct usb2_hub_status)); + return (usb2_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_address + * + * This function is used to set the address for an USB device. After + * port reset the USB device will respond at address zero. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_address(struct usb2_device *udev, struct mtx *mtx, uint16_t addr) +{ + struct usb2_device_request req; + + DPRINTFN(6, "setting device address=%d\n", addr); + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + /* Setting the address should not take more than 1 second ! */ + return (usb2_do_request_flags(udev, mtx, &req, NULL, + USB_DELAY_STATUS_STAGE, NULL, 1000)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_port_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_port_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_port_status *ps, uint8_t port) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_CLASS_OTHER; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof *ps); + return (usb2_do_request(udev, mtx, &req, ps)); +} + +/*------------------------------------------------------------------------* + * usb2_req_clear_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_clear_hub_feature(struct usb2_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_hub_feature(struct usb2_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_clear_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_clear_port_feature(struct usb2_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_port_feature(struct usb2_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_protocol + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_protocol(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n", + iface, report, iface->idesc->bInterfaceNumber); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_PROTOCOL; + USETW(req.wValue, report); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, + uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_report(struct usb2_device *udev, struct mtx *mtx, void *data, + uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL) || (id == 0)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_idle + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_idle(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "%d %d\n", duration, id); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_IDLE; + USETW2(req.wValue, duration, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_report_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_report_descriptor(struct usb2_device *udev, struct mtx *mtx, + void *d, uint16_t size, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, size); + return (usb2_do_request(udev, mtx, &req, d)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_config + * + * This function is used to select the current configuration number in + * both USB device side mode and USB host side mode. When setting the + * configuration the function of the interfaces can change. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_config(struct usb2_device *udev, struct mtx *mtx, uint8_t conf) +{ + struct usb2_device_request req; + + DPRINTF("setting config %d\n", conf); + + /* do "set configuration" request */ + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_CONFIG; + req.wValue[0] = conf; + req.wValue[1] = 0; + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config(struct usb2_device *udev, struct mtx *mtx, uint8_t *pconf) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + return (usb2_do_request(udev, mtx, &req, pconf)); +} + +/*------------------------------------------------------------------------* + * usb2_req_re_enumerate + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_re_enumerate(struct usb2_device *udev, struct mtx *mtx) +{ + struct usb2_device_descriptor ddesc; + struct usb2_device *parent_hub; + usb2_error_t err; + uint8_t old_addr; + + old_addr = udev->address; + parent_hub = udev->parent_hub; + if (parent_hub == NULL) { + err = USB_ERR_INVAL; + goto done; + } + err = usb2_req_reset_port(parent_hub, mtx, udev->port_no); + if (err) { + DPRINTFN(0, "addr=%d, port reset failed\n", old_addr); + goto done; + } + /* + * After that the port has been reset our device should be at + * address zero: + */ + udev->address = USB_START_ADDR; + + /* + * Restore device address: + */ + err = usb2_req_set_address(udev, mtx, old_addr); + if (err) { + /* XXX ignore any errors! */ + DPRINTFN(0, "addr=%d, set address failed\n", + old_addr); + err = 0; + } + /* restore device address */ + udev->address = old_addr; + + /* allow device time to set new address */ + usb2_pause_mtx(mtx, USB_SET_ADDRESS_SETTLE); + + /* get the device descriptor */ + err = usb2_req_get_device_desc(udev, mtx, &ddesc); + if (err) { + DPRINTFN(0, "addr=%d, getting device " + "descriptor failed!\n", old_addr); + goto done; + } +done: + /* restore address */ + udev->address = old_addr; + + if (err == 0) { + /* restore configuration */ + err = usb2_req_set_config(udev, mtx, udev->curr_config_no); + /* wait a little bit, just in case */ + usb2_pause_mtx(mtx, 10); + } + return (err); +} diff --git a/sys/dev/usb2/core/usb2_request.h b/sys/dev/usb2/core/usb2_request.h new file mode 100644 index 000000000000..f2b642291135 --- /dev/null +++ b/sys/dev/usb2/core/usb2_request.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_REQUEST_H_ +#define _USB2_REQUEST_H_ + +usb2_error_t usb2_do_request_flags(struct usb2_device *udev, struct mtx *mtx, struct usb2_device_request *req, void *data, uint32_t flags, uint16_t *actlen, uint32_t timeout); +usb2_error_t usb2_req_clear_hub_feature(struct usb2_device *udev, struct mtx *mtx, uint16_t sel); +usb2_error_t usb2_req_clear_port_feature(struct usb2_device *udev, struct mtx *mtx, uint8_t port, uint16_t sel); +usb2_error_t usb2_req_get_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, uint8_t *alt_iface_no, uint8_t iface_index); +usb2_error_t usb2_req_get_config(struct usb2_device *udev, struct mtx *mtx, uint8_t *pconf); +usb2_error_t usb2_req_get_config_desc(struct usb2_device *udev, struct mtx *mtx, struct usb2_config_descriptor *d, uint8_t conf_index); +usb2_error_t usb2_req_get_config_desc_full(struct usb2_device *udev, struct mtx *mtx, struct usb2_config_descriptor **ppcd, struct malloc_type *mtype, uint8_t conf_index); +usb2_error_t usb2_req_get_desc(struct usb2_device *udev, struct mtx *mtx, void *desc, uint16_t min_len, uint16_t max_len, uint16_t id, uint8_t type, uint8_t index, uint8_t retries); +usb2_error_t usb2_req_get_device_desc(struct usb2_device *udev, struct mtx *mtx, struct usb2_device_descriptor *d); +usb2_error_t usb2_req_get_device_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_status *st); +usb2_error_t usb2_req_get_hub_descriptor(struct usb2_device *udev, struct mtx *mtx, struct usb2_hub_descriptor *hd, uint8_t nports); +usb2_error_t usb2_req_get_hub_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_hub_status *st); +usb2_error_t usb2_req_get_port_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_port_status *ps, uint8_t port); +usb2_error_t usb2_req_get_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id); +usb2_error_t usb2_req_get_report_descriptor(struct usb2_device *udev, struct mtx *mtx, void *d, uint16_t size, uint8_t iface_index); +usb2_error_t usb2_req_get_string_any(struct usb2_device *udev, struct mtx *mtx, char *buf, uint16_t len, uint8_t string_index); +usb2_error_t usb2_req_get_string_desc(struct usb2_device *udev, struct mtx *mtx, void *sdesc, uint16_t max_len, uint16_t lang_id, uint8_t string_index); +usb2_error_t usb2_req_reset_port(struct usb2_device *udev, struct mtx *mtx, uint8_t port); +usb2_error_t usb2_req_set_address(struct usb2_device *udev, struct mtx *mtx, uint16_t addr); +usb2_error_t usb2_req_set_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint8_t alt_no); +usb2_error_t usb2_req_set_config(struct usb2_device *udev, struct mtx *mtx, uint8_t conf); +usb2_error_t usb2_req_set_hub_feature(struct usb2_device *udev, struct mtx *mtx, uint16_t sel); +usb2_error_t usb2_req_set_idle(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint8_t duration, uint8_t id); +usb2_error_t usb2_req_set_port_feature(struct usb2_device *udev, struct mtx *mtx, uint8_t port, uint16_t sel); +usb2_error_t usb2_req_set_protocol(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint16_t report); +usb2_error_t usb2_req_set_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id); +usb2_error_t usb2_req_re_enumerate(struct usb2_device *udev, struct mtx *mtx); + +#define usb2_do_request(u,m,r,d) \ + usb2_do_request_flags(u,m,r,d,0,NULL,USB_DEFAULT_TIMEOUT) + +#endif /* _USB2_REQUEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_sw_transfer.c b/sys/dev/usb2/core/usb2_sw_transfer.c new file mode 100644 index 000000000000..bb825028a907 --- /dev/null +++ b/sys/dev/usb2/core/usb2_sw_transfer.c @@ -0,0 +1,166 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_error.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_debug.h> + +/*------------------------------------------------------------------------* + * usb2_sw_transfer - factored out code + * + * This function is basically used for the Virtual Root HUB, and can + * emulate control, bulk and interrupt endpoints. Data is exchanged + * using the "std->ptr" and "std->len" fields, that allows kernel + * virtual memory to be transferred. All state is kept in the + * structure pointed to by the "std" argument passed to this + * function. The "func" argument points to a function that is called + * back in the various states, so that the application using this + * function can get a chance to select the outcome. The "func" + * function is allowed to sleep, exiting all mutexes. If this function + * will sleep the "enter" and "start" methods must be marked + * non-cancelable, hence there is no extra cancelled checking in this + * function. + *------------------------------------------------------------------------*/ +void +usb2_sw_transfer(struct usb2_sw_transfer *std, + usb2_sw_transfer_func_t *func) +{ + struct usb2_xfer *xfer; + uint32_t len; + uint8_t shortpkt = 0; + + xfer = std->xfer; + if (xfer == NULL) { + /* the transfer is gone */ + DPRINTF("xfer gone\n"); + return; + } + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + std->xfer = NULL; + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + /* check if we are transferring the SETUP packet */ + if (xfer->flags_int.control_hdr) { + + /* copy out the USB request */ + + if (xfer->frlengths[0] == sizeof(std->req)) { + usb2_copy_out(xfer->frbuffers, 0, + &std->req, sizeof(std->req)); + } else { + std->err = USB_ERR_INVAL; + goto done; + } + + xfer->aframes = 1; + + std->err = 0; + std->state = USB_SW_TR_SETUP; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + } else { + /* skip the first frame in this case */ + xfer->aframes = 1; + } + } + std->err = 0; + std->state = USB_SW_TR_PRE_DATA; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + /* Transfer data. Iterate accross all frames. */ + while (xfer->aframes != xfer->nframes) { + + len = xfer->frlengths[xfer->aframes]; + + if (len > std->len) { + len = std->len; + shortpkt = 1; + } + if (len > 0) { + if ((xfer->endpoint & (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN) { + usb2_copy_in(xfer->frbuffers + xfer->aframes, 0, + std->ptr, len); + } else { + usb2_copy_out(xfer->frbuffers + xfer->aframes, 0, + std->ptr, len); + } + } + std->ptr += len; + std->len -= len; + xfer->frlengths[xfer->aframes] = len; + xfer->aframes++; + + if (shortpkt) { + break; + } + } + + std->err = 0; + std->state = USB_SW_TR_POST_DATA; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + /* check if the control transfer is complete */ + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + std->err = 0; + std->state = USB_SW_TR_STATUS; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + } +done: + DPRINTF("done err=%s\n", usb2_errstr(std->err)); + std->state = USB_SW_TR_PRE_CALLBACK; + (func) (xfer, std); + return; +} diff --git a/sys/dev/usb2/core/usb2_sw_transfer.h b/sys/dev/usb2/core/usb2_sw_transfer.h new file mode 100644 index 000000000000..c90ba3663f77 --- /dev/null +++ b/sys/dev/usb2/core/usb2_sw_transfer.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_SW_TRANSFER_H_ +#define _USB2_SW_TRANSFER_H_ + +/* Software transfer function state argument values */ + +enum { + USB_SW_TR_SETUP, + USB_SW_TR_STATUS, + USB_SW_TR_PRE_DATA, + USB_SW_TR_POST_DATA, + USB_SW_TR_PRE_CALLBACK, +}; + +struct usb2_sw_transfer; + +typedef void (usb2_sw_transfer_func_t)(struct usb2_xfer *, struct usb2_sw_transfer *); + +/* + * The following structure is used to keep the state of a standard + * root transfer. + */ +struct usb2_sw_transfer { + struct usb2_device_request req; + struct usb2_xfer *xfer; + uint8_t *ptr; + uint16_t len; + uint8_t state; + usb2_error_t err; +}; + +/* prototypes */ + +void usb2_sw_transfer(struct usb2_sw_transfer *std, usb2_sw_transfer_func_t *func); + +#endif /* _USB2_SW_TRANSFER_H_ */ diff --git a/sys/dev/usb2/core/usb2_transfer.c b/sys/dev/usb2/core/usb2_transfer.c new file mode 100644 index 000000000000..21676243e6eb --- /dev/null +++ b/sys/dev/usb2/core/usb2_transfer.c @@ -0,0 +1,2833 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR usb2_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +struct usb2_std_packet_size { + struct { + uint16_t min; /* inclusive */ + uint16_t max; /* inclusive */ + } range; + + uint16_t fixed[4]; +}; + +/* + * This table stores the all the allowed packet sizes based on + * endpoint type and USB speed: + */ +static const struct usb2_std_packet_size + usb2_std_packet_size[4][USB_SPEED_MAX] = { + + [UE_INTERRUPT] = { + [USB_SPEED_LOW] = {.range = {0, 8}}, + [USB_SPEED_FULL] = {.range = {0, 64}}, + [USB_SPEED_HIGH] = {.range = {0, 1024}}, + [USB_SPEED_VARIABLE] = {.range = {0, 1024}}, + }, + + [UE_CONTROL] = { + [USB_SPEED_LOW] = {.fixed = {8, 8, 8, 8}}, + [USB_SPEED_FULL] = {.fixed = {8, 16, 32, 64}}, + [USB_SPEED_HIGH] = {.fixed = {64, 64, 64, 64}}, + [USB_SPEED_VARIABLE] = {.fixed = {512, 512, 512, 512}}, + }, + + [UE_BULK] = { + [USB_SPEED_LOW] = {.fixed = {0, 0, 0, 0}}, /* invalid */ + [USB_SPEED_FULL] = {.fixed = {8, 16, 32, 64}}, + [USB_SPEED_HIGH] = {.fixed = {512, 512, 512, 512}}, + [USB_SPEED_VARIABLE] = {.fixed = {512, 512, 1024, 1536}}, + }, + + [UE_ISOCHRONOUS] = { + [USB_SPEED_LOW] = {.fixed = {0, 0, 0, 0}}, /* invalid */ + [USB_SPEED_FULL] = {.range = {0, 1023}}, + [USB_SPEED_HIGH] = {.range = {0, 1024}}, + [USB_SPEED_VARIABLE] = {.range = {0, 3584}}, + }, +}; + +static const struct usb2_config usb2_control_ep_cfg[USB_DEFAULT_XFER_MAX] = { + + /* This transfer is used for generic control endpoint transfers */ + + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control endpoint */ + .direction = UE_DIR_ANY, + .mh.bufsize = 1024, /* bytes */ + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &usb2_do_request_callback, + .md.bufsize = 1024, /* bytes */ + .md.flags = {.proxy_buffer = 1,.short_xfer_ok = 0,}, + .md.callback = &usb2_handle_request_callback, + }, + + /* This transfer is used for generic clear stall only */ + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &usb2_do_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +/* function prototypes */ + +static void usb2_update_max_frame_size(struct usb2_xfer *xfer); +static uint32_t usb2_get_dma_delay(struct usb2_bus *bus); +static void usb2_transfer_unsetup_sub(struct usb2_xfer_root *info, uint8_t needs_delay); +static void usb2_control_transfer_init(struct usb2_xfer *xfer); +static uint8_t usb2_start_hardware_sub(struct usb2_xfer *xfer); +static void usb2_callback_proc(struct usb2_proc_msg *_pm); +static void usb2_callback_ss_done_defer(struct usb2_xfer *xfer); +static void usb2_callback_wrapper(struct usb2_xfer_queue *pq); +static void usb2_dma_delay_done_cb(void *arg); +static void usb2_transfer_start_cb(void *arg); +static uint8_t usb2_callback_wrapper_sub(struct usb2_xfer *xfer); + +/*------------------------------------------------------------------------* + * usb2_update_max_frame_size + * + * This function updates the maximum frame size, hence high speed USB + * can transfer multiple consecutive packets. + *------------------------------------------------------------------------*/ +static void +usb2_update_max_frame_size(struct usb2_xfer *xfer) +{ + /* compute maximum frame size */ + + if (xfer->max_packet_count == 2) { + xfer->max_frame_size = 2 * xfer->max_packet_size; + } else if (xfer->max_packet_count == 3) { + xfer->max_frame_size = 3 * xfer->max_packet_size; + } else { + xfer->max_frame_size = xfer->max_packet_size; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_dma_delay + * + * The following function is called when we need to + * synchronize with DMA hardware. + * + * Returns: + * 0: no DMA delay required + * Else: milliseconds of DMA delay + *------------------------------------------------------------------------*/ +static uint32_t +usb2_get_dma_delay(struct usb2_bus *bus) +{ + uint32_t temp = 0; + + if (bus->methods->get_dma_delay) { + (bus->methods->get_dma_delay) (bus, &temp); + /* + * Round up and convert to milliseconds. Note that we use + * 1024 milliseconds per second. to save a division. + */ + temp += 0x3FF; + temp /= 0x400; + } + return (temp); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup_sub_malloc + * + * This function will allocate one or more DMA'able memory chunks + * according to "size", "align" and "count" arguments. "ppc" is + * pointed to a linear array of USB page caches afterwards. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_transfer_setup_sub_malloc(struct usb2_setup_params *parm, + struct usb2_page_cache **ppc, uint32_t size, uint32_t align, + uint32_t count) +{ + struct usb2_page_cache *pc; + struct usb2_page *pg; + void *buf; + uint32_t n_dma_pc; + uint32_t n_obj; + uint32_t x; + uint32_t y; + uint32_t r; + uint32_t z; + + USB_ASSERT(align > 1, ("Invalid alignment, 0x%08x!\n", + align)); + USB_ASSERT(size > 0, ("Invalid size = 0!\n")); + + if (count == 0) { + return (0); /* nothing to allocate */ + } + /* + * Make sure that the size is aligned properly. + */ + size = -((-size) & (-align)); + + /* + * Try multi-allocation chunks to reduce the number of DMA + * allocations, hence DMA allocations are slow. + */ + if (size >= PAGE_SIZE) { + n_dma_pc = count; + n_obj = 1; + } else { + /* compute number of objects per page */ + n_obj = (PAGE_SIZE / size); + /* + * Compute number of DMA chunks, rounded up + * to nearest one: + */ + n_dma_pc = ((count + n_obj - 1) / n_obj); + } + + if (parm->buf == NULL) { + /* for the future */ + parm->dma_page_ptr += n_dma_pc; + parm->dma_page_cache_ptr += n_dma_pc; + parm->dma_page_ptr += count; + parm->xfer_page_cache_ptr += count; + return (0); + } + for (x = 0; x != n_dma_pc; x++) { + /* need to initialize the page cache */ + parm->dma_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->usb2_root->dma_parent_tag; + } + for (x = 0; x != count; x++) { + /* need to initialize the page cache */ + parm->xfer_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->usb2_root->dma_parent_tag; + } + + if (ppc) { + *ppc = parm->xfer_page_cache_ptr; + } + r = count; /* set remainder count */ + z = n_obj * size; /* set allocation size */ + pc = parm->xfer_page_cache_ptr; + pg = parm->dma_page_ptr; + + for (x = 0; x != n_dma_pc; x++) { + + if (r < n_obj) { + /* compute last remainder */ + z = r * size; + n_obj = r; + } + if (usb2_pc_alloc_mem(parm->dma_page_cache_ptr, + pg, z, align)) { + return (1); /* failure */ + } + /* Set beginning of current buffer */ + buf = parm->dma_page_cache_ptr->buffer; + /* Make room for one DMA page cache and one page */ + parm->dma_page_cache_ptr++; + pg++; + + for (y = 0; (y != n_obj); y++, r--, pc++, pg++) { + + /* Load sub-chunk into DMA */ + if (usb2_pc_dmamap_create(pc, size)) { + return (1); /* failure */ + } + pc->buffer = USB_ADD_BYTES(buf, y * size); + pc->page_start = pg; + + mtx_lock(pc->tag_parent->mtx); + if (usb2_pc_load_mem(pc, size, 1 /* synchronous */ )) { + mtx_unlock(pc->tag_parent->mtx); + return (1); /* failure */ + } + mtx_unlock(pc->tag_parent->mtx); + } + } + + parm->xfer_page_cache_ptr = pc; + parm->dma_page_ptr = pg; + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup_sub - transfer setup subroutine + * + * This function must be called from the "xfer_setup" callback of the + * USB Host or Device controller driver when setting up an USB + * transfer. This function will setup correct packet sizes, buffer + * sizes, flags and more, that are stored in the "usb2_xfer" + * structure. + *------------------------------------------------------------------------*/ +void +usb2_transfer_setup_sub(struct usb2_setup_params *parm) +{ + enum { + REQ_SIZE = 8, + MIN_PKT = 8, + }; + struct usb2_xfer *xfer = parm->curr_xfer; + const struct usb2_config_sub *setup_sub = parm->curr_setup_sub; + struct usb2_endpoint_descriptor *edesc; + struct usb2_std_packet_size std_size; + uint32_t n_frlengths; + uint32_t n_frbuffers; + uint32_t x; + uint8_t type; + uint8_t zmps; + + /* + * Sanity check. The following parameters must be initialized before + * calling this function. + */ + if ((parm->hc_max_packet_size == 0) || + (parm->hc_max_packet_count == 0) || + (parm->hc_max_frame_size == 0)) { + parm->err = USB_ERR_INVAL; + goto done; + } + edesc = xfer->pipe->edesc; + + type = (edesc->bmAttributes & UE_XFERTYPE); + + xfer->flags = setup_sub->flags; + xfer->nframes = setup_sub->frames; + xfer->timeout = setup_sub->timeout; + xfer->callback = setup_sub->callback; + xfer->interval = setup_sub->interval; + xfer->endpoint = edesc->bEndpointAddress; + xfer->max_packet_size = UGETW(edesc->wMaxPacketSize); + xfer->max_packet_count = 1; + /* make a shadow copy: */ + xfer->flags_int.usb2_mode = parm->udev->flags.usb2_mode; + + parm->bufsize = setup_sub->bufsize; + + if (parm->speed == USB_SPEED_HIGH) { + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + xfer->max_packet_size &= 0x7FF; + } + /* range check "max_packet_count" */ + + if (xfer->max_packet_count > parm->hc_max_packet_count) { + xfer->max_packet_count = parm->hc_max_packet_count; + } + /* filter "wMaxPacketSize" according to HC capabilities */ + + if ((xfer->max_packet_size > parm->hc_max_packet_size) || + (xfer->max_packet_size == 0)) { + xfer->max_packet_size = parm->hc_max_packet_size; + } + /* filter "wMaxPacketSize" according to standard sizes */ + + std_size = usb2_std_packet_size[type][parm->speed]; + + if (std_size.range.min || std_size.range.max) { + + if (xfer->max_packet_size < std_size.range.min) { + xfer->max_packet_size = std_size.range.min; + } + if (xfer->max_packet_size > std_size.range.max) { + xfer->max_packet_size = std_size.range.max; + } + } else { + + if (xfer->max_packet_size >= std_size.fixed[3]) { + xfer->max_packet_size = std_size.fixed[3]; + } else if (xfer->max_packet_size >= std_size.fixed[2]) { + xfer->max_packet_size = std_size.fixed[2]; + } else if (xfer->max_packet_size >= std_size.fixed[1]) { + xfer->max_packet_size = std_size.fixed[1]; + } else { + /* only one possibility left */ + xfer->max_packet_size = std_size.fixed[0]; + } + } + + /* compute "max_frame_size" */ + + usb2_update_max_frame_size(xfer); + + /* check interrupt interval and transfer pre-delay */ + + if (type == UE_ISOCHRONOUS) { + + uint32_t frame_limit; + + xfer->interval = 0; /* not used, must be zero */ + xfer->flags_int.isochronous_xfr = 1; /* set flag */ + + if (xfer->timeout == 0) { + /* + * set a default timeout in + * case something goes wrong! + */ + xfer->timeout = 1000 / 4; + } + if (parm->speed == USB_SPEED_HIGH) { + frame_limit = USB_MAX_HS_ISOC_FRAMES_PER_XFER; + } else { + frame_limit = USB_MAX_FS_ISOC_FRAMES_PER_XFER; + } + + if (xfer->nframes > frame_limit) { + /* + * this is not going to work + * cross hardware + */ + parm->err = USB_ERR_INVAL; + goto done; + } + if (xfer->nframes == 0) { + /* + * this is not a valid value + */ + parm->err = USB_ERR_ZERO_NFRAMES; + goto done; + } + } else { + + /* + * if a value is specified use that else check the endpoint + * descriptor + */ + if (xfer->interval == 0) { + + if (type == UE_INTERRUPT) { + + xfer->interval = edesc->bInterval; + + if (parm->speed == USB_SPEED_HIGH) { + xfer->interval /= 8; /* 125us -> 1ms */ + } + if (xfer->interval == 0) { + /* + * one millisecond is the smallest + * interval + */ + xfer->interval = 1; + } + } + } + } + + /* + * NOTE: we do not allow "max_packet_size" or "max_frame_size" + * to be equal to zero when setting up USB transfers, hence + * this leads to alot of extra code in the USB kernel. + */ + + if ((xfer->max_frame_size == 0) || + (xfer->max_packet_size == 0)) { + + zmps = 1; + + if ((parm->bufsize <= MIN_PKT) && + (type != UE_CONTROL) && + (type != UE_BULK)) { + + /* workaround */ + xfer->max_packet_size = MIN_PKT; + xfer->max_packet_count = 1; + parm->bufsize = 0; /* automatic setup length */ + usb2_update_max_frame_size(xfer); + + } else { + parm->err = USB_ERR_ZERO_MAXP; + goto done; + } + + } else { + zmps = 0; + } + + /* + * check if we should setup a default + * length: + */ + + if (parm->bufsize == 0) { + + parm->bufsize = xfer->max_frame_size; + + if (type == UE_ISOCHRONOUS) { + parm->bufsize *= xfer->nframes; + } + } + /* + * check if we are about to setup a proxy + * type of buffer: + */ + + if (xfer->flags.proxy_buffer) { + + /* round bufsize up */ + + parm->bufsize += (xfer->max_frame_size - 1); + + if (parm->bufsize < xfer->max_frame_size) { + /* length wrapped around */ + parm->err = USB_ERR_INVAL; + goto done; + } + /* subtract remainder */ + + parm->bufsize -= (parm->bufsize % xfer->max_frame_size); + + /* add length of USB device request structure, if any */ + + if (type == UE_CONTROL) { + parm->bufsize += REQ_SIZE; /* SETUP message */ + } + } + xfer->max_data_length = parm->bufsize; + + /* Setup "n_frlengths" and "n_frbuffers" */ + + if (type == UE_ISOCHRONOUS) { + n_frlengths = xfer->nframes; + n_frbuffers = 1; + } else { + + if (type == UE_CONTROL) { + xfer->flags_int.control_xfr = 1; + if (xfer->nframes == 0) { + if (parm->bufsize <= REQ_SIZE) { + /* + * there will never be any data + * stage + */ + xfer->nframes = 1; + } else { + xfer->nframes = 2; + } + } + } else { + if (xfer->nframes == 0) { + xfer->nframes = 1; + } + } + + n_frlengths = xfer->nframes; + n_frbuffers = xfer->nframes; + } + + /* + * check if we have room for the + * USB device request structure: + */ + + if (type == UE_CONTROL) { + + if (xfer->max_data_length < REQ_SIZE) { + /* length wrapped around or too small bufsize */ + parm->err = USB_ERR_INVAL; + goto done; + } + xfer->max_data_length -= REQ_SIZE; + } + /* setup "frlengths" */ + + xfer->frlengths = parm->xfer_length_ptr; + + parm->xfer_length_ptr += n_frlengths; + + /* setup "frbuffers" */ + + xfer->frbuffers = parm->xfer_page_cache_ptr; + + parm->xfer_page_cache_ptr += n_frbuffers; + + /* + * check if we need to setup + * a local buffer: + */ + + if (!xfer->flags.ext_buffer) { + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + if (parm->buf) { + + xfer->local_buffer = + USB_ADD_BYTES(parm->buf, parm->size[0]); + + usb2_set_frame_offset(xfer, 0, 0); + + if ((type == UE_CONTROL) && (n_frbuffers > 1)) { + usb2_set_frame_offset(xfer, REQ_SIZE, 1); + } + } + parm->size[0] += parm->bufsize; + + /* align data again */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + } + /* + * Compute maximum buffer size + */ + + if (parm->bufsize_max < parm->bufsize) { + parm->bufsize_max = parm->bufsize; + } + if (xfer->flags_int.bdma_enable) { + /* + * Setup "dma_page_ptr". + * + * Proof for formula below: + * + * Assume there are three USB frames having length "a", "b" and + * "c". These USB frames will at maximum need "z" + * "usb2_page" structures. "z" is given by: + * + * z = ((a / USB_PAGE_SIZE) + 2) + ((b / USB_PAGE_SIZE) + 2) + + * ((c / USB_PAGE_SIZE) + 2); + * + * Constraining "a", "b" and "c" like this: + * + * (a + b + c) <= parm->bufsize + * + * We know that: + * + * z <= ((parm->bufsize / USB_PAGE_SIZE) + (3*2)); + * + * Here is the general formula: + */ + xfer->dma_page_ptr = parm->dma_page_ptr; + parm->dma_page_ptr += (2 * n_frbuffers); + parm->dma_page_ptr += (parm->bufsize / USB_PAGE_SIZE); + } + if (zmps) { + /* correct maximum data length */ + xfer->max_data_length = 0; + } + /* subtract USB frame remainder from "hc_max_frame_size" */ + + xfer->max_usb2_frame_size = + (parm->hc_max_frame_size - + (parm->hc_max_frame_size % xfer->max_frame_size)); + + if (xfer->max_usb2_frame_size == 0) { + parm->err = USB_ERR_INVAL; + goto done; + } + /* initialize max frame count */ + + xfer->max_frame_count = xfer->nframes; + + /* initialize frame buffers */ + + if (parm->buf) { + for (x = 0; x != n_frbuffers; x++) { + xfer->frbuffers[x].tag_parent = + &xfer->usb2_root->dma_parent_tag; + + if (xfer->flags_int.bdma_enable && + (parm->bufsize_max > 0)) { + + if (usb2_pc_dmamap_create( + xfer->frbuffers + x, + parm->bufsize_max)) { + parm->err = USB_ERR_NOMEM; + goto done; + } + } + } + } +done: + if (parm->err) { + /* + * Set some dummy values so that we avoid division by zero: + */ + xfer->max_usb2_frame_size = 1; + xfer->max_frame_size = 1; + xfer->max_packet_size = 1; + xfer->max_data_length = 0; + xfer->nframes = 0; + xfer->max_frame_count = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup - setup an array of USB transfers + * + * NOTE: You must always call "usb2_transfer_unsetup" after calling + * "usb2_transfer_setup" if success was returned. + * + * The idea is that the USB device driver should pre-allocate all its + * transfers by one call to this function. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_transfer_setup(struct usb2_device *udev, + const uint8_t *ifaces, struct usb2_xfer **ppxfer, + const struct usb2_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *priv_mtx) +{ + struct usb2_xfer dummy; + struct usb2_setup_params parm; + const struct usb2_config *setup_end = setup_start + n_setup; + const struct usb2_config *setup; + struct usb2_pipe *pipe; + struct usb2_xfer_root *info; + struct usb2_xfer *xfer; + void *buf = NULL; + uint16_t n; + uint16_t refcount; + + parm.err = 0; + refcount = 0; + info = NULL; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_setup can sleep!"); + + /* do some checking first */ + + if (n_setup == 0) { + DPRINTFN(6, "setup array has zero length!\n"); + return (USB_ERR_INVAL); + } + if (ifaces == 0) { + DPRINTFN(6, "ifaces array is NULL!\n"); + return (USB_ERR_INVAL); + } + if (priv_mtx == NULL) { + DPRINTFN(6, "using global lock\n"); + priv_mtx = &Giant; + } + /* sanity checks */ + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + if ((setup->mh.bufsize == 0xffffffff) || + (setup->md.bufsize == 0xffffffff)) { + parm.err = USB_ERR_BAD_BUFSIZE; + DPRINTF("invalid bufsize\n"); + } + if ((setup->mh.callback == NULL) && + (setup->md.callback == NULL)) { + parm.err = USB_ERR_NO_CALLBACK; + DPRINTF("no callback\n"); + } + ppxfer[n] = NULL; + } + + if (parm.err) { + goto done; + } + bzero(&parm, sizeof(parm)); + + parm.udev = udev; + parm.speed = usb2_get_speed(udev); + parm.hc_max_packet_count = 1; + + if (parm.speed >= USB_SPEED_MAX) { + parm.err = USB_ERR_INVAL; + goto done; + } + /* setup all transfers */ + + while (1) { + + if (buf) { + /* + * Initialize the "usb2_xfer_root" structure, + * which is common for all our USB transfers. + */ + info = USB_ADD_BYTES(buf, 0); + + info->memory_base = buf; + info->memory_size = parm.size[0]; + + info->dma_page_cache_start = USB_ADD_BYTES(buf, parm.size[4]); + info->dma_page_cache_end = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_start = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_end = USB_ADD_BYTES(buf, parm.size[2]); + + usb2_cv_init(&info->cv_drain, "WDRAIN"); + + info->usb2_mtx = &udev->bus->mtx; + info->priv_mtx = priv_mtx; + + usb2_dma_tag_setup(&info->dma_parent_tag, + parm.dma_tag_p, udev->bus->dma_parent_tag[0].tag, + priv_mtx, &usb2_bdma_done_event, info, 32, parm.dma_tag_max); + + info->bus = udev->bus; + + TAILQ_INIT(&info->done_q.head); + info->done_q.command = &usb2_callback_wrapper; + + TAILQ_INIT(&info->dma_q.head); + info->dma_q.command = &usb2_bdma_work_loop; + + info->done_m[0].hdr.pm_callback = &usb2_callback_proc; + info->done_m[0].usb2_root = info; + info->done_m[1].hdr.pm_callback = &usb2_callback_proc; + info->done_m[1].usb2_root = info; + + /* create a callback thread */ + + if (usb2_proc_setup(&info->done_p, + &udev->bus->mtx, USB_PRI_HIGH)) { + parm.err = USB_ERR_NO_INTR_THREAD; + goto done; + } + } + /* reset sizes */ + + parm.size[0] = 0; + parm.buf = buf; + parm.size[0] += sizeof(info[0]); + + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + + /* select mode specific structure */ + if (udev->flags.usb2_mode == USB_MODE_HOST) { + parm.curr_setup_sub = &setup->mh; + } else { + parm.curr_setup_sub = &setup->md; + } + /* skip USB transfers without callbacks: */ + if (parm.curr_setup_sub->callback == NULL) { + continue; + } + /* see if there is a matching endpoint */ + pipe = usb2_get_pipe(udev, + ifaces[setup->if_index], setup); + + if (!pipe) { + if (parm.curr_setup_sub->flags.no_pipe_ok) { + continue; + } + parm.err = USB_ERR_NO_PIPE; + goto done; + } + /* store current setup pointer */ + parm.curr_setup = setup; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + if (buf) { + + /* + * Common initialization of the + * "usb2_xfer" structure. + */ + xfer = USB_ADD_BYTES(buf, parm.size[0]); + + ppxfer[n] = xfer; + xfer->udev = udev; + xfer->address = udev->address; + xfer->priv_sc = priv_sc; + xfer->priv_mtx = priv_mtx; + xfer->usb2_mtx = &udev->bus->mtx; + xfer->usb2_root = info; + info->setup_refcount++; + + usb2_callout_init_mtx(&xfer->timeout_handle, xfer->usb2_mtx, + CALLOUT_RETURNUNLOCKED); + } else { + /* + * Setup a dummy xfer, hence we are + * writing to the "usb2_xfer" + * structure pointed to by "xfer" + * before we have allocated any + * memory: + */ + xfer = &dummy; + bzero(&dummy, sizeof(dummy)); + refcount++; + } + + parm.size[0] += sizeof(xfer[0]); + + xfer->pipe = pipe; + + if (buf) { + /* + * Increment the pipe refcount. This + * basically prevents setting a new + * configuration and alternate setting + * when USB transfers are in use on + * the given interface. Search the USB + * code for "pipe->refcount" if you + * want more information. + */ + xfer->pipe->refcount++; + } + parm.methods = xfer->pipe->methods; + parm.curr_xfer = xfer; + + /* + * Call the Host or Device controller transfer setup + * routine: + */ + (udev->bus->methods->xfer_setup) (&parm); + + if (parm.err) { + goto done; + } + } + + if (buf || parm.err) { + goto done; + } + if (refcount == 0) { + /* no transfers - nothing to do ! */ + goto done; + } + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[1] = parm.size[0]; + + /* + * The number of DMA tags required depends on + * the number of endpoints. The current estimate + * for maximum number of DMA tags per endpoint + * is two. + */ + parm.dma_tag_max += 2 * MIN(n_setup, USB_EP_MAX); + + /* + * DMA tags for QH, TD, Data and more. + */ + parm.dma_tag_max += 8; + + parm.dma_tag_p += parm.dma_tag_max; + + parm.size[0] += ((uint8_t *)parm.dma_tag_p) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[3] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[4] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + parm.size[5] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + + parm.size[2] = parm.size[0]; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + parm.size[6] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_length_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* allocate zeroed memory */ + buf = malloc(parm.size[0], M_USB, M_WAITOK | M_ZERO); + + if (buf == NULL) { + parm.err = USB_ERR_NOMEM; + DPRINTFN(0, "cannot allocate memory block for " + "configuration (%d bytes)\n", + parm.size[0]); + goto done; + } + parm.dma_tag_p = USB_ADD_BYTES(buf, parm.size[1]); + parm.dma_page_ptr = USB_ADD_BYTES(buf, parm.size[3]); + parm.dma_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[4]); + parm.xfer_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[5]); + parm.xfer_length_ptr = USB_ADD_BYTES(buf, parm.size[6]); + } + +done: + if (buf) { + if (info->setup_refcount == 0) { + /* + * "usb2_transfer_unsetup_sub" will unlock + * "usb2_mtx" before returning ! + */ + mtx_lock(info->usb2_mtx); + + /* something went wrong */ + usb2_transfer_unsetup_sub(info, 0); + } + } + if (parm.err) { + usb2_transfer_unsetup(ppxfer, n_setup); + } + return (parm.err); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_unsetup_sub - factored out code + *------------------------------------------------------------------------*/ +static void +usb2_transfer_unsetup_sub(struct usb2_xfer_root *info, uint8_t needs_delay) +{ + struct usb2_page_cache *pc; + uint32_t temp; + + mtx_assert(info->usb2_mtx, MA_OWNED); + + /* wait for any outstanding DMA operations */ + + if (needs_delay) { + temp = usb2_get_dma_delay(info->bus); + usb2_pause_mtx(info->usb2_mtx, temp); + } + mtx_unlock(info->usb2_mtx); + + /* wait for interrupt thread to exit */ + usb2_proc_unsetup(&info->done_p); + + /* free DMA'able memory, if any */ + pc = info->dma_page_cache_start; + while (pc != info->dma_page_cache_end) { + usb2_pc_free_mem(pc); + pc++; + } + + /* free DMA maps in all "xfer->frbuffers" */ + pc = info->xfer_page_cache_start; + while (pc != info->xfer_page_cache_end) { + usb2_pc_dmamap_destroy(pc); + pc++; + } + + /* free all DMA tags */ + usb2_dma_tag_unsetup(&info->dma_parent_tag); + + usb2_cv_destroy(&info->cv_drain); + + /* + * free the "memory_base" last, hence the "info" structure is + * contained within the "memory_base"! + */ + free(info->memory_base, M_USB); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_unsetup - unsetup/free an array of USB transfers + * + * NOTE: All USB transfers in progress will get called back passing + * the error code "USB_ERR_CANCELLED" before this function + * returns. + *------------------------------------------------------------------------*/ +void +usb2_transfer_unsetup(struct usb2_xfer **pxfer, uint16_t n_setup) +{ + struct usb2_xfer *xfer; + struct usb2_xfer_root *info; + uint8_t needs_delay = 0; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_unsetup can sleep!"); + + while (n_setup--) { + xfer = pxfer[n_setup]; + + if (xfer) { + if (xfer->pipe) { + mtx_lock(xfer->priv_mtx); + mtx_lock(xfer->usb2_mtx); + + /* + * HINT: when you start/stop a transfer, it + * might be a good idea to directly use the + * "pxfer[]" structure: + * + * usb2_transfer_start(sc->pxfer[0]); + * usb2_transfer_stop(sc->pxfer[0]); + * + * That way, if your code has many parts that + * will not stop running under the same + * lock, in other words "priv_mtx", the + * usb2_transfer_start and + * usb2_transfer_stop functions will simply + * return when they detect a NULL pointer + * argument. + * + * To avoid any races we clear the "pxfer[]" + * pointer while holding the private mutex + * of the driver: + */ + pxfer[n_setup] = NULL; + + mtx_unlock(xfer->usb2_mtx); + mtx_unlock(xfer->priv_mtx); + + usb2_transfer_drain(xfer); + + if (xfer->flags_int.bdma_enable) { + needs_delay = 1; + } + /* + * NOTE: default pipe does not have an + * interface, even if pipe->iface_index == 0 + */ + xfer->pipe->refcount--; + + } else { + /* clear the transfer pointer */ + pxfer[n_setup] = NULL; + } + + usb2_callout_drain(&xfer->timeout_handle); + + if (xfer->usb2_root) { + info = xfer->usb2_root; + + mtx_lock(info->usb2_mtx); + + USB_ASSERT(info->setup_refcount != 0, + ("Invalid setup " + "reference count!\n")); + + info->setup_refcount--; + + if (info->setup_refcount == 0) { + usb2_transfer_unsetup_sub(info, + needs_delay); + } else { + mtx_unlock(info->usb2_mtx); + } + } + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_control_transfer_init - factored out code + * + * In USB Device Mode we have to wait for the SETUP packet which + * containst the "struct usb2_device_request" structure, before we can + * transfer any data. In USB Host Mode we already have the SETUP + * packet at the moment the USB transfer is started. This leads us to + * having to setup the USB transfer at two different places in + * time. This function just contains factored out control transfer + * initialisation code, so that we don't duplicate the code. + *------------------------------------------------------------------------*/ +static void +usb2_control_transfer_init(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + + /* copy out the USB request header */ + + usb2_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + /* setup remainder */ + + xfer->flags_int.control_rem = UGETW(req.wLength); + + /* copy direction to endpoint variable */ + + xfer->endpoint &= ~(UE_DIR_IN | UE_DIR_OUT); + xfer->endpoint |= + (req.bmRequestType & UT_READ) ? UE_DIR_IN : UE_DIR_OUT; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_start_hardware_sub + * + * This function handles initialisation of control transfers. Control + * transfers are special in that regard that they can both transmit + * and receive data. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_start_hardware_sub(struct usb2_xfer *xfer) +{ + uint32_t len; + + /* Check for control endpoint stall */ + if (xfer->flags.stall_pipe) { + /* no longer active */ + xfer->flags_int.control_act = 0; + } + /* + * Check if there is a control + * transfer in progress: + */ + if (xfer->flags_int.control_act) { + + if (xfer->flags_int.control_hdr) { + + /* clear send header flag */ + + xfer->flags_int.control_hdr = 0; + + /* setup control transfer */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + usb2_control_transfer_init(xfer); + } + } + /* get data length */ + + len = xfer->sumlen; + + } else { + + /* the size of the SETUP structure is hardcoded ! */ + + if (xfer->frlengths[0] != sizeof(struct usb2_device_request)) { + DPRINTFN(0, "Wrong framelength %u != %zu\n", + xfer->frlengths[0], sizeof(struct + usb2_device_request)); + goto error; + } + /* check USB mode */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + + /* check number of frames */ + if (xfer->nframes != 1) { + /* + * We need to receive the setup + * message first so that we know the + * data direction! + */ + DPRINTF("Misconfigured transfer\n"); + goto error; + } + /* + * Set a dummy "control_rem" value. This + * variable will be overwritten later by a + * call to "usb2_control_transfer_init()" ! + */ + xfer->flags_int.control_rem = 0xFFFF; + } else { + + /* setup "endpoint" and "control_rem" */ + + usb2_control_transfer_init(xfer); + } + + /* set transfer-header flag */ + + xfer->flags_int.control_hdr = 1; + + /* get data length */ + + len = (xfer->sumlen - sizeof(struct usb2_device_request)); + } + + /* check if there is a length mismatch */ + + if (len > xfer->flags_int.control_rem) { + DPRINTFN(0, "Length greater than remaining length!\n"); + goto error; + } + /* check if we are doing a short transfer */ + + if (xfer->flags.force_short_xfer) { + xfer->flags_int.control_rem = 0; + } else { + if ((len != xfer->max_data_length) && + (len != xfer->flags_int.control_rem) && + (xfer->nframes != 1)) { + DPRINTFN(0, "Short control transfer without " + "force_short_xfer set!\n"); + goto error; + } + xfer->flags_int.control_rem -= len; + } + + /* the status part is executed when "control_act" is 0 */ + + if ((xfer->flags_int.control_rem > 0) || + (xfer->flags.manual_status)) { + /* don't execute the STATUS stage yet */ + xfer->flags_int.control_act = 1; + + /* sanity check */ + if ((!xfer->flags_int.control_hdr) && + (xfer->nframes == 1)) { + /* + * This is not a valid operation! + */ + DPRINTFN(0, "Invalid parameter " + "combination\n"); + goto error; + } + } else { + /* time to execute the STATUS stage */ + xfer->flags_int.control_act = 0; + } + return (0); /* success */ + +error: + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_start_hardware - start USB hardware for the given transfer + * + * This function should only be called from the USB callback. + *------------------------------------------------------------------------*/ +void +usb2_start_hardware(struct usb2_xfer *xfer) +{ + uint32_t x; + + DPRINTF("xfer=%p, pipe=%p, nframes=%d, dir=%s\n", + xfer, xfer->pipe, xfer->nframes, USB_GET_DATA_ISREAD(xfer) ? + "read" : "write"); + +#if USB_DEBUG + if (USB_DEBUG_VAR > 0) { + mtx_lock(xfer->usb2_mtx); + + usb2_dump_pipe(xfer->pipe); + + mtx_unlock(xfer->usb2_mtx); + } +#endif + + mtx_assert(xfer->priv_mtx, MA_OWNED); + mtx_assert(xfer->usb2_mtx, MA_NOTOWNED); + + /* Only open the USB transfer once! */ + if (!xfer->flags_int.open) { + xfer->flags_int.open = 1; + + DPRINTF("open\n"); + + mtx_lock(xfer->usb2_mtx); + (xfer->pipe->methods->open) (xfer); + mtx_unlock(xfer->usb2_mtx); + } + /* set "transferring" flag */ + xfer->flags_int.transferring = 1; + + /* + * Check if the transfer is waiting on a queue, most + * frequently the "done_q": + */ + if (xfer->wait_queue) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_dequeue(xfer); + mtx_unlock(xfer->usb2_mtx); + } + /* clear "did_dma_delay" flag */ + xfer->flags_int.did_dma_delay = 0; + + /* clear "did_close" flag */ + xfer->flags_int.did_close = 0; + + /* clear "bdma_setup" flag */ + xfer->flags_int.bdma_setup = 0; + + /* by default we cannot cancel any USB transfer immediately */ + xfer->flags_int.can_cancel_immed = 0; + + /* clear lengths and frame counts by default */ + xfer->sumlen = 0; + xfer->actlen = 0; + xfer->aframes = 0; + + /* clear any previous errors */ + xfer->error = 0; + + /* sanity check */ + + if (xfer->nframes == 0) { + if (xfer->flags.stall_pipe) { + /* + * Special case - want to stall without transferring + * any data: + */ + DPRINTF("xfer=%p nframes=0: stall " + "or clear stall!\n", xfer); + mtx_lock(xfer->usb2_mtx); + xfer->flags_int.can_cancel_immed = 1; + /* start the transfer */ + usb2_command_wrapper(&xfer->pipe->pipe_q, xfer); + mtx_unlock(xfer->usb2_mtx); + return; + } + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_INVAL); + mtx_unlock(xfer->usb2_mtx); + return; + } + /* compute total transfer length */ + + for (x = 0; x != xfer->nframes; x++) { + xfer->sumlen += xfer->frlengths[x]; + if (xfer->sumlen < xfer->frlengths[x]) { + /* length wrapped around */ + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_INVAL); + mtx_unlock(xfer->usb2_mtx); + return; + } + } + + /* clear some internal flags */ + + xfer->flags_int.short_xfer_ok = 0; + xfer->flags_int.short_frames_ok = 0; + + /* check if this is a control transfer */ + + if (xfer->flags_int.control_xfr) { + + if (usb2_start_hardware_sub(xfer)) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_STALLED); + mtx_unlock(xfer->usb2_mtx); + return; + } + } + /* + * Setup filtered version of some transfer flags, + * in case of data read direction + */ + if (USB_GET_DATA_ISREAD(xfer)) { + + if (xfer->flags_int.control_xfr) { + + /* + * Control transfers do not support reception + * of multiple short USB frames ! + */ + + if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + } + } else { + + if (xfer->flags.short_frames_ok) { + xfer->flags_int.short_xfer_ok = 1; + xfer->flags_int.short_frames_ok = 1; + } else if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + } + } + } + /* + * Check if BUS-DMA support is enabled and try to load virtual + * buffers into DMA, if any: + */ + if (xfer->flags_int.bdma_enable) { + /* insert the USB transfer last in the BUS-DMA queue */ + usb2_command_wrapper(&xfer->usb2_root->dma_q, xfer); + return; + } + /* + * Enter the USB transfer into the Host Controller or + * Device Controller schedule: + */ + usb2_pipe_enter(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pipe_enter - factored out code + *------------------------------------------------------------------------*/ +void +usb2_pipe_enter(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + + mtx_assert(xfer->priv_mtx, MA_OWNED); + + mtx_lock(xfer->usb2_mtx); + + pipe = xfer->pipe; + + DPRINTF("enter\n"); + + /* enter the transfer */ + (pipe->methods->enter) (xfer); + + /* check cancelability */ + if (pipe->methods->enter_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + /* check for transfer error */ + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + mtx_unlock(xfer->usb2_mtx); + return; + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + + /* start the transfer */ + usb2_command_wrapper(&pipe->pipe_q, xfer); + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_start - start an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer start, until the USB transfer + * completes. + *------------------------------------------------------------------------*/ +void +usb2_transfer_start(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* transfer is gone */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* mark the USB transfer started */ + + if (!xfer->flags_int.started) { + xfer->flags_int.started = 1; + } + /* check if the USB transfer callback is already transferring */ + + if (xfer->flags_int.transferring) { + return; + } + mtx_lock(xfer->usb2_mtx); + /* call the USB transfer callback */ + usb2_callback_ss_done_defer(xfer); + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_stop - stop an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer stop. + * NOTE: When this function returns it is not safe to free nor + * reuse any DMA buffers. See "usb2_transfer_drain()". + *------------------------------------------------------------------------*/ +void +usb2_transfer_stop(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* check if the USB transfer was ever opened */ + + if (!xfer->flags_int.open) { + /* nothing to do except clearing the "started" flag */ + xfer->flags_int.started = 0; + return; + } + /* try to stop the current USB transfer */ + + mtx_lock(xfer->usb2_mtx); + xfer->error = USB_ERR_CANCELLED;/* override any previous error */ + /* + * Clear "open" and "started" when both private and USB lock + * is locked so that we don't get a race updating "flags_int" + */ + xfer->flags_int.open = 0; + xfer->flags_int.started = 0; + + /* + * Check if we can cancel the USB transfer immediately. + */ + if (xfer->flags_int.transferring) { + if (xfer->flags_int.can_cancel_immed && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + /* + * The following will lead to an USB_ERR_CANCELLED + * error code being passed to the USB callback. + */ + (xfer->pipe->methods->close) (xfer); + /* only close once */ + xfer->flags_int.did_close = 1; + } else { + /* need to wait for the next done callback */ + } + } else { + DPRINTF("close\n"); + + /* close here and now */ + (xfer->pipe->methods->close) (xfer); + + /* + * Any additional DMA delay is done by + * "usb2_transfer_unsetup()". + */ + + /* + * Special case. Check if we need to restart a blocked + * pipe. + */ + pipe = xfer->pipe; + + /* + * If the current USB transfer is completing we need + * to start the next one: + */ + if (pipe->pipe_q.curr == xfer) { + usb2_command_wrapper(&pipe->pipe_q, NULL); + } + } + + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_pending + * + * This function will check if an USB transfer is pending which is a + * little bit complicated! + * Return values: + * 0: Not pending + * 1: Pending: The USB transfer will receive a callback in the future. + *------------------------------------------------------------------------*/ +uint8_t +usb2_transfer_pending(struct usb2_xfer *xfer) +{ + struct usb2_xfer_root *info; + struct usb2_xfer_queue *pq; + + mtx_assert(xfer->priv_mtx, MA_OWNED); + + if (xfer->flags_int.transferring) { + /* trivial case */ + return (1); + } + mtx_lock(xfer->usb2_mtx); + if (xfer->wait_queue) { + /* we are waiting on a queue somewhere */ + mtx_unlock(xfer->usb2_mtx); + return (1); + } + info = xfer->usb2_root; + pq = &info->done_q; + + if (pq->curr == xfer) { + /* we are currently scheduled for callback */ + mtx_unlock(xfer->usb2_mtx); + return (1); + } + /* we are not pending */ + mtx_unlock(xfer->usb2_mtx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_drain + * + * This function will stop the USB transfer and wait for any + * additional BUS-DMA and HW-DMA operations to complete. Buffers that + * are loaded into DMA can safely be freed or reused after that this + * function has returned. + *------------------------------------------------------------------------*/ +void +usb2_transfer_drain(struct usb2_xfer *xfer) +{ + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_drain can sleep!"); + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + if (xfer->priv_mtx != &Giant) { + mtx_assert(xfer->priv_mtx, MA_NOTOWNED); + } + mtx_lock(xfer->priv_mtx); + + usb2_transfer_stop(xfer); + + while (usb2_transfer_pending(xfer)) { + xfer->flags_int.draining = 1; + /* + * Wait until the current outstanding USB + * transfer is complete ! + */ + usb2_cv_wait(&xfer->usb2_root->cv_drain, xfer->priv_mtx); + } + mtx_unlock(xfer->priv_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_frame_data + * + * This function sets the pointer of the buffer that should + * loaded directly into DMA for the given USB frame. Passing "ptr" + * equal to NULL while the corresponding "frlength" is greater + * than zero gives undefined results! + *------------------------------------------------------------------------*/ +void +usb2_set_frame_data(struct usb2_xfer *xfer, void *ptr, uint32_t frindex) +{ + /* set virtual address to load and length */ + xfer->frbuffers[frindex].buffer = ptr; + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_frame_offset + * + * This function sets the frame data buffer offset relative to the beginning + * of the USB DMA buffer allocated for this USB transfer. + *------------------------------------------------------------------------*/ +void +usb2_set_frame_offset(struct usb2_xfer *xfer, uint32_t offset, + uint32_t frindex) +{ + USB_ASSERT(!xfer->flags.ext_buffer, ("Cannot offset data frame " + "when the USB buffer is external!\n")); + + /* set virtual address to load */ + xfer->frbuffers[frindex].buffer = + USB_ADD_BYTES(xfer->local_buffer, offset); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_proc - factored out code + * + * This function performs USB callbacks. + *------------------------------------------------------------------------*/ +static void +usb2_callback_proc(struct usb2_proc_msg *_pm) +{ + struct usb2_done_msg *pm = (void *)_pm; + struct usb2_xfer_root *info = pm->usb2_root; + + /* Change locking order */ + mtx_unlock(info->usb2_mtx); + + /* + * We exploit the fact that the mutex is the same for all + * callbacks that will be called from this thread: + */ + mtx_lock(info->priv_mtx); + mtx_lock(info->usb2_mtx); + + /* Continue where we lost track */ + usb2_command_wrapper(&info->done_q, + info->done_q.curr); + + mtx_unlock(info->priv_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_ss_done_defer + * + * This function will defer the start, stop and done callback to the + * correct thread. + *------------------------------------------------------------------------*/ +static void +usb2_callback_ss_done_defer(struct usb2_xfer *xfer) +{ + struct usb2_xfer_root *info = xfer->usb2_root; + struct usb2_xfer_queue *pq = &info->done_q; + + if (!mtx_owned(xfer->usb2_mtx)) { + panic("%s: called unlocked!\n", __FUNCTION__); + } + if (pq->curr != xfer) { + usb2_transfer_enqueue(pq, xfer); + } + if (!pq->recurse_1) { + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb2_proc_msignal(&info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; + +} + +/*------------------------------------------------------------------------* + * usb2_callback_wrapper + * + * This is a wrapper for USB callbacks. This wrapper does some + * auto-magic things like figuring out if we can call the callback + * directly from the current context or if we need to wakeup the + * interrupt process. + *------------------------------------------------------------------------*/ +static void +usb2_callback_wrapper(struct usb2_xfer_queue *pq) +{ + struct usb2_xfer *xfer = pq->curr; + struct usb2_xfer_root *info = xfer->usb2_root; + + if (!mtx_owned(xfer->usb2_mtx)) { + panic("%s: called unlocked!\n", __FUNCTION__); + } + if (!mtx_owned(xfer->priv_mtx)) { + /* + * Cases that end up here: + * + * 5) HW interrupt done callback or other source. + */ + DPRINTFN(3, "case 5\n"); + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb2_proc_msignal(&info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + return; + } + /* + * Cases that end up here: + * + * 1) We are starting a transfer + * 2) We are prematurely calling back a transfer + * 3) We are stopping a transfer + * 4) We are doing an ordinary callback + */ + DPRINTFN(3, "case 1-4\n"); + /* get next USB transfer in the queue */ + info->done_q.curr = NULL; + + mtx_unlock(xfer->usb2_mtx); + mtx_assert(xfer->usb2_mtx, MA_NOTOWNED); + + /* set correct USB state for callback */ + if (!xfer->flags_int.transferring) { + xfer->usb2_state = USB_ST_SETUP; + if (!xfer->flags_int.started) { + /* we got stopped before we even got started */ + mtx_lock(xfer->usb2_mtx); + goto done; + } + } else { + + if (usb2_callback_wrapper_sub(xfer)) { + /* the callback has been deferred */ + mtx_lock(xfer->usb2_mtx); + goto done; + } + xfer->flags_int.transferring = 0; + + if (xfer->error) { + xfer->usb2_state = USB_ST_ERROR; + } else { + /* set transferred state */ + xfer->usb2_state = USB_ST_TRANSFERRED; + + /* sync DMA memory, if any */ + if (xfer->flags_int.bdma_enable && + (!xfer->flags_int.bdma_no_post_sync)) { + usb2_bdma_post_sync(xfer); + } + } + } + + /* call processing routine */ + (xfer->callback) (xfer); + + /* pickup the USB mutex again */ + mtx_lock(xfer->usb2_mtx); + + /* + * Check if we got started after that we got cancelled, but + * before we managed to do the callback. Check if we are + * draining. + */ + if ((!xfer->flags_int.open) && + (xfer->flags_int.started) && + (xfer->usb2_state == USB_ST_ERROR)) { + /* try to loop, but not recursivly */ + usb2_command_wrapper(&info->done_q, xfer); + return; + } else if (xfer->flags_int.draining && + (!xfer->flags_int.transferring)) { + /* "usb2_transfer_drain()" is waiting for end of transfer */ + xfer->flags_int.draining = 0; + usb2_cv_broadcast(&xfer->usb2_root->cv_drain); + } +done: + /* do the next callback, if any */ + usb2_command_wrapper(&info->done_q, + info->done_q.curr); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_delay_done_cb + * + * This function is called when the DMA delay has been exectuded, and + * will make sure that the callback is called to complete the USB + * transfer. This code path is ususally only used when there is an USB + * error like USB_ERR_CANCELLED. + *------------------------------------------------------------------------*/ +static void +usb2_dma_delay_done_cb(void *arg) +{ + struct usb2_xfer *xfer = arg; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTFN(3, "Completed %p\n", xfer); + + /* queue callback for execution, again */ + usb2_transfer_done(xfer, 0); + + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_dequeue + * + * - This function is used to remove an USB transfer from a USB + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_transfer_dequeue(struct usb2_xfer *xfer) +{ + struct usb2_xfer_queue *pq; + + pq = xfer->wait_queue; + if (pq) { + TAILQ_REMOVE(&pq->head, xfer, wait_entry); + xfer->wait_queue = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_enqueue + * + * - This function is used to insert an USB transfer into a USB * + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_transfer_enqueue(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer) +{ + /* + * Insert the USB transfer into the queue, if it is not + * already on a USB transfer queue: + */ + if (xfer->wait_queue == NULL) { + xfer->wait_queue = pq; + TAILQ_INSERT_TAIL(&pq->head, xfer, wait_entry); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_done + * + * - This function is used to remove an USB transfer from the busdma, + * pipe or interrupt queue. + * + * - This function is used to queue the USB transfer on the done + * queue. + * + * - This function is used to stop any USB transfer timeouts. + *------------------------------------------------------------------------*/ +void +usb2_transfer_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct usb2_xfer_queue *pq; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTF("err=%s\n", usb2_errstr(error)); + + /* + * If we are not transferring then just return. + * This can happen during transfer cancel. + */ + if (!xfer->flags_int.transferring) { + DPRINTF("not transferring\n"); + return; + } + /* only set transfer error if not already set */ + if (!xfer->error) { + xfer->error = error; + } + /* stop any callouts */ + usb2_callout_stop(&xfer->timeout_handle); + + /* + * If we are waiting on a queue, just remove the USB transfer + * from the queue, if any. We should have the required locks + * locked to do the remove when this function is called. + */ + usb2_transfer_dequeue(xfer); + + if (mtx_owned(xfer->priv_mtx)) { + /* + * If the private USB lock is not locked, then we assume + * that the BUS-DMA load stage has been passed: + */ + pq = &xfer->usb2_root->dma_q; + + if (pq->curr == xfer) { + /* start the next BUS-DMA load, if any */ + usb2_command_wrapper(pq, NULL); + } + } + /* keep some statistics */ + if (xfer->error) { + xfer->udev->bus->stats_err.uds_requests + [xfer->pipe->edesc->bmAttributes & UE_XFERTYPE]++; + } else { + xfer->udev->bus->stats_ok.uds_requests + [xfer->pipe->edesc->bmAttributes & UE_XFERTYPE]++; + } + + /* call the USB transfer callback */ + usb2_callback_ss_done_defer(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_start_cb + * + * This function is called to start the USB transfer when + * "xfer->interval" is greater than zero, and and the endpoint type is + * BULK or CONTROL. + *------------------------------------------------------------------------*/ +static void +usb2_transfer_start_cb(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct usb2_pipe *pipe = xfer->pipe; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTF("start\n"); + + /* start the transfer */ + (pipe->methods->start) (xfer); + + /* check cancelability */ + if (pipe->methods->start_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_set_stall + * + * This function is used to set the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_transfer_set_stall(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + mtx_lock(xfer->usb2_mtx); + + xfer->flags.stall_pipe = 1; + + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_clear_stall + * + * This function is used to clear the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_transfer_clear_stall(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + mtx_lock(xfer->usb2_mtx); + + xfer->flags.stall_pipe = 0; + + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_pipe_start + * + * This function is used to add an USB transfer to the pipe transfer list. + *------------------------------------------------------------------------*/ +void +usb2_pipe_start(struct usb2_xfer_queue *pq) +{ + struct usb2_pipe *pipe; + struct usb2_xfer *xfer; + uint8_t type; + + xfer = pq->curr; + pipe = xfer->pipe; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + /* + * If the pipe is already stalled we do nothing ! + */ + if (pipe->is_stalled) { + return; + } + /* + * Check if we are supposed to stall the pipe: + */ + if (xfer->flags.stall_pipe) { + /* clear stall command */ + xfer->flags.stall_pipe = 0; + + /* + * Only stall BULK and INTERRUPT endpoints. + */ + type = (pipe->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_INTERRUPT)) { + struct usb2_device *udev; + struct usb2_xfer_root *info; + + udev = xfer->udev; + pipe->is_stalled = 1; + + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + (udev->bus->methods->set_stall) ( + udev, NULL, pipe); + } else if (udev->default_xfer[1]) { + info = udev->default_xfer[1]->usb2_root; + if (usb2_proc_msignal(&info->done_p, + &udev->cs_msg[0], &udev->cs_msg[1])) { + /* ignore */ + } + } else { + /* should not happen */ + DPRINTFN(0, "No stall handler!\n"); + } + /* + * We get started again when the stall is cleared! + */ + return; + } + } + /* Set or clear stall complete - special case */ + if (xfer->nframes == 0) { + /* we are complete */ + xfer->aframes = 0; + usb2_transfer_done(xfer, 0); + return; + } + /* + * Handled cases: + * + * 1) Start the first transfer queued. + * + * 2) Re-start the current USB transfer. + */ + /* + * Check if there should be any + * pre transfer start delay: + */ + if (xfer->interval > 0) { + type = (pipe->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_CONTROL)) { + usb2_transfer_timeout_ms(xfer, + &usb2_transfer_start_cb, + xfer->interval); + return; + } + } + DPRINTF("start\n"); + + /* start USB transfer */ + (pipe->methods->start) (xfer); + + /* check cancelability */ + if (pipe->methods->start_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_timeout_ms + * + * This function is used to setup a timeout on the given USB + * transfer. If the timeout has been deferred the callback given by + * "cb" will get called after "ms" milliseconds. + *------------------------------------------------------------------------*/ +void +usb2_transfer_timeout_ms(struct usb2_xfer *xfer, + void (*cb) (void *arg), uint32_t ms) +{ + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + /* defer delay */ + usb2_callout_reset(&xfer->timeout_handle, + USB_MS_TO_TICKS(ms), cb, xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_wrapper_sub + * + * - This function will update variables in an USB transfer after + * that the USB transfer is complete. + * + * - This function is used to start the next USB transfer on the + * pipe transfer queue, if any. + * + * NOTE: In some special cases the USB transfer will not be removed from + * the pipe queue, but remain first. To enforce USB transfer removal call + * this function passing the error code "USB_ERR_CANCELLED". + * + * Return values: + * 0: Success. + * Else: The callback has been deferred. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_callback_wrapper_sub(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + uint32_t x; + + if ((!xfer->flags_int.open) && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + mtx_lock(xfer->usb2_mtx); + (xfer->pipe->methods->close) (xfer); + mtx_unlock(xfer->usb2_mtx); + /* only close once */ + xfer->flags_int.did_close = 1; + return (1); /* wait for new callback */ + } + /* + * If we have a non-hardware induced error we + * need to do the DMA delay! + */ + if (((xfer->error == USB_ERR_CANCELLED) || + (xfer->error == USB_ERR_TIMEOUT)) && + (!xfer->flags_int.did_dma_delay)) { + + uint32_t temp; + + /* only delay once */ + xfer->flags_int.did_dma_delay = 1; + + /* we can not cancel this delay */ + xfer->flags_int.can_cancel_immed = 0; + + temp = usb2_get_dma_delay(xfer->udev->bus); + + DPRINTFN(3, "DMA delay, %u ms, " + "on %p\n", temp, xfer); + + if (temp != 0) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_timeout_ms(xfer, + &usb2_dma_delay_done_cb, temp); + mtx_unlock(xfer->usb2_mtx); + return (1); /* wait for new callback */ + } + } + /* check actual number of frames */ + if (xfer->aframes > xfer->nframes) { + if (xfer->error == 0) { + panic("%s: actual number of frames, %d, is " + "greater than initial number of frames, %d!\n", + __FUNCTION__, xfer->aframes, xfer->nframes); + } else { + /* just set some valid value */ + xfer->aframes = xfer->nframes; + } + } + /* compute actual length */ + xfer->actlen = 0; + + for (x = 0; x != xfer->aframes; x++) { + xfer->actlen += xfer->frlengths[x]; + } + + /* + * Frames that were not transferred get zero actual length in + * case the USB device driver does not check the actual number + * of frames transferred, "xfer->aframes": + */ + for (; x < xfer->nframes; x++) { + xfer->frlengths[x] = 0; + } + + /* check actual length */ + if (xfer->actlen > xfer->sumlen) { + if (xfer->error == 0) { + panic("%s: actual length, %d, is greater than " + "initial length, %d!\n", + __FUNCTION__, xfer->actlen, xfer->sumlen); + } else { + /* just set some valid value */ + xfer->actlen = xfer->sumlen; + } + } + DPRINTFN(6, "xfer=%p pipe=%p sts=%d alen=%d, slen=%d, afrm=%d, nfrm=%d\n", + xfer, xfer->pipe, xfer->error, xfer->actlen, xfer->sumlen, + xfer->aframes, xfer->nframes); + + if (xfer->error) { + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + /* check if we should block the execution queue */ + if ((xfer->error != USB_ERR_CANCELLED) && + (xfer->flags.pipe_bof)) { + DPRINTFN(2, "xfer=%p: Block On Failure " + "on pipe=%p\n", xfer, xfer->pipe); + goto done; + } + } else { + /* check for short transfers */ + if (xfer->actlen < xfer->sumlen) { + + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + if (!xfer->flags_int.short_xfer_ok) { + xfer->error = USB_ERR_SHORT_XFER; + if (xfer->flags.pipe_bof) { + DPRINTFN(2, "xfer=%p: Block On Failure on " + "Short Transfer on pipe %p.\n", + xfer, xfer->pipe); + goto done; + } + } + } else { + /* + * Check if we are in the middle of a + * control transfer: + */ + if (xfer->flags_int.control_act) { + DPRINTFN(5, "xfer=%p: Control transfer " + "active on pipe=%p\n", xfer, xfer->pipe); + goto done; + } + } + } + + pipe = xfer->pipe; + + /* + * If the current USB transfer is completing we need to start the + * next one: + */ + mtx_lock(xfer->usb2_mtx); + if (pipe->pipe_q.curr == xfer) { + usb2_command_wrapper(&pipe->pipe_q, NULL); + + if (pipe->pipe_q.curr || TAILQ_FIRST(&pipe->pipe_q.head)) { + /* there is another USB transfer waiting */ + } else { + /* this is the last USB transfer */ + /* clear isochronous sync flag */ + xfer->pipe->is_synced = 0; + } + } + mtx_unlock(xfer->usb2_mtx); +done: + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_command_wrapper + * + * This function is used to execute commands non-recursivly on an USB + * transfer. + *------------------------------------------------------------------------*/ +void +usb2_command_wrapper(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer) +{ + if (xfer) { + /* + * If the transfer is not already processing, + * queue it! + */ + if (pq->curr != xfer) { + usb2_transfer_enqueue(pq, xfer); + if (pq->curr != NULL) { + /* something is already processing */ + DPRINTFN(6, "busy %p\n", pq->curr); + return; + } + } + } else { + /* Get next element in queue */ + pq->curr = NULL; + } + + if (!pq->recurse_1) { + + do { + + /* set both recurse flags */ + pq->recurse_1 = 1; + pq->recurse_2 = 1; + + if (pq->curr == NULL) { + xfer = TAILQ_FIRST(&pq->head); + if (xfer) { + TAILQ_REMOVE(&pq->head, xfer, + wait_entry); + xfer->wait_queue = NULL; + pq->curr = xfer; + } else { + break; + } + } + DPRINTFN(6, "cb %p (enter)\n", pq->curr); + (pq->command) (pq); + DPRINTFN(6, "cb %p (leave)\n", pq->curr); + + } while (!pq->recurse_2); + + /* clear first recurse flag */ + pq->recurse_1 = 0; + + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_default_transfer_setup + * + * This function is used to setup the default USB control endpoint + * transfer. + *------------------------------------------------------------------------*/ +void +usb2_default_transfer_setup(struct usb2_device *udev) +{ + struct usb2_xfer *xfer; + uint8_t no_resetup; + uint8_t iface_index; + +repeat: + + xfer = udev->default_xfer[0]; + if (xfer) { + mtx_lock(xfer->priv_mtx); + no_resetup = + ((xfer->address == udev->address) && + (udev->default_ep_desc.wMaxPacketSize[0] == + udev->ddesc.bMaxPacketSize)); + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + if (no_resetup) { + /* + * NOTE: checking "xfer->address" and + * starting the USB transfer must be + * atomic! + */ + usb2_transfer_start(xfer); + } + } + mtx_unlock(xfer->priv_mtx); + } else { + no_resetup = 0; + } + + if (no_resetup) { + /* + * All parameters are exactly the same like before. + * Just return. + */ + return; + } + /* + * Update wMaxPacketSize for the default control endpoint: + */ + udev->default_ep_desc.wMaxPacketSize[0] = + udev->ddesc.bMaxPacketSize; + + /* + * Unsetup any existing USB transfer: + */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + + /* + * Try to setup a new USB transfer for the + * default control endpoint: + */ + iface_index = 0; + if (usb2_transfer_setup(udev, &iface_index, + udev->default_xfer, usb2_control_ep_cfg, USB_DEFAULT_XFER_MAX, NULL, + udev->default_mtx)) { + DPRINTFN(0, "could not setup default " + "USB transfer!\n"); + } else { + goto repeat; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle. + *------------------------------------------------------------------------*/ +void +usb2_clear_data_toggle(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + DPRINTFN(5, "udev=%p pipe=%p\n", udev, pipe); + + mtx_lock(&udev->bus->mtx); + pipe->toggle_next = 0; + mtx_unlock(&udev->bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_clear_stall_callback - factored out clear stall callback + * + * Input parameters: + * xfer1: Clear Stall Control Transfer + * xfer2: Stalled USB Transfer + * + * This function is NULL safe. + * + * Return values: + * 0: In progress + * Else: Finished + * + * Clear stall config example: + * + * static const struct usb2_config my_clearstall = { + * .type = UE_CONTROL, + * .endpoint = 0, + * .direction = UE_DIR_ANY, + * .interval = 50, //50 milliseconds + * .bufsize = sizeof(struct usb2_device_request), + * .mh.timeout = 1000, //1.000 seconds + * .mh.flags = { }, + * .mh.callback = &my_clear_stall_callback, // ** + * }; + * + * ** "my_clear_stall_callback" calls "usb2_clear_stall_callback" + * passing the correct parameters. + *------------------------------------------------------------------------*/ +uint8_t +usb2_clear_stall_callback(struct usb2_xfer *xfer1, + struct usb2_xfer *xfer2) +{ + struct usb2_device_request req; + + if (xfer2 == NULL) { + /* looks like we are tearing down */ + DPRINTF("NULL input parameter\n"); + return (0); + } + mtx_assert(xfer1->priv_mtx, MA_OWNED); + mtx_assert(xfer2->priv_mtx, MA_OWNED); + + switch (USB_GET_STATE(xfer1)) { + case USB_ST_SETUP: + + /* + * pre-clear the data toggle to DATA0 ("umass.c" and + * "ata-usb.c" depends on this) + */ + + usb2_clear_data_toggle(xfer2->udev, xfer2->pipe); + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = xfer2->pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* + * "usb2_transfer_setup_sub()" will ensure that + * we have sufficient room in the buffer for + * the request structure! + */ + + /* copy in the transfer */ + + usb2_copy_in(xfer1->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer1->frlengths[0] = sizeof(req); + xfer1->nframes = 1; + + usb2_start_hardware(xfer1); + return (0); + + case USB_ST_TRANSFERRED: + break; + + default: /* Error */ + if (xfer1->error == USB_ERR_CANCELLED) { + return (0); + } + break; + } + return (1); /* Clear Stall Finished */ +} + +#if (USB_NO_POLL == 0) + +/*------------------------------------------------------------------------* + * usb2_callout_poll + *------------------------------------------------------------------------*/ +static void +usb2_callout_poll(struct usb2_xfer *xfer) +{ + struct usb2_callout *co; + void (*cb) (void *); + void *arg; + struct mtx *mtx; + uint32_t delta; + + if (xfer == NULL) { + return; + } + co = &xfer->timeout_handle; + +#if __FreeBSD_version >= 800000 + mtx = (void *)(co->co.c_lock); +#else + mtx = co->co.c_mtx; +#endif + mtx_lock(mtx); + + if (usb2_callout_pending(co)) { + delta = ticks - co->co.c_time; + if (!(delta & 0x80000000)) { + + cb = co->co.c_func; + arg = co->co.c_arg; + + /* timed out */ + usb2_callout_stop(co); + + (cb) (arg); + + /* the callback should drop the mutex */ + } else { + mtx_unlock(mtx); + } + } else { + mtx_unlock(mtx); + } + return; +} + + +/*------------------------------------------------------------------------* + * usb2_do_poll + * + * This function is called from keyboard driver when in polling + * mode. + *------------------------------------------------------------------------*/ +void +usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max) +{ + struct usb2_xfer *xfer; + struct usb2_xfer_root *usb2_root; + struct usb2_device *udev; + struct usb2_proc_msg *pm; + uint32_t to; + uint16_t n; + + /* compute system tick delay */ + to = ((uint32_t)(1000000)) / ((uint32_t)(hz)); + DELAY(to); + atomic_add_int((volatile int *)&ticks, 1); + + for (n = 0; n != max; n++) { + xfer = ppxfer[n]; + if (xfer) { + usb2_root = xfer->usb2_root; + udev = xfer->udev; + + /* + * Poll hardware - signal that we are polling by + * locking the private mutex: + */ + mtx_lock(xfer->priv_mtx); + (udev->bus->methods->do_poll) (udev->bus); + mtx_unlock(xfer->priv_mtx); + + /* poll clear stall start */ + mtx_lock(xfer->usb2_mtx); + pm = &udev->cs_msg[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + + if (udev->default_xfer[1]) { + + /* poll timeout */ + usb2_callout_poll(udev->default_xfer[1]); + + /* poll clear stall done thread */ + mtx_lock(xfer->usb2_mtx); + pm = &udev->default_xfer[1]-> + usb2_root->done_m[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + } + /* poll timeout */ + usb2_callout_poll(xfer); + + /* poll done thread */ + mtx_lock(xfer->usb2_mtx); + pm = &usb2_root->done_m[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + } + } + return; +} + +#else + +void +usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max) +{ + /* polling not supported */ + return; +} + +#endif diff --git a/sys/dev/usb2/core/usb2_transfer.h b/sys/dev/usb2/core/usb2_transfer.h new file mode 100644 index 000000000000..83920e5fc444 --- /dev/null +++ b/sys/dev/usb2/core/usb2_transfer.h @@ -0,0 +1,123 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_TRANSFER_H_ +#define _USB2_TRANSFER_H_ + +/* + * The following structure defines the messages that is used to signal + * the "done_p" USB process. + */ +struct usb2_done_msg { + struct usb2_proc_msg hdr; + struct usb2_xfer_root *usb2_root; +}; + +/* + * The following structure is used to keep information about memory + * that should be automatically freed at the moment all USB transfers + * have been freed. + */ +struct usb2_xfer_root { + struct usb2_xfer_queue dma_q; + struct usb2_xfer_queue done_q; + struct usb2_done_msg done_m[2]; + struct cv cv_drain; + + struct usb2_dma_parent_tag dma_parent_tag; + + struct usb2_process done_p; + void *memory_base; + struct mtx *priv_mtx; + struct mtx *usb2_mtx; + struct usb2_page_cache *dma_page_cache_start; + struct usb2_page_cache *dma_page_cache_end; + struct usb2_page_cache *xfer_page_cache_start; + struct usb2_page_cache *xfer_page_cache_end; + struct usb2_bus *bus; + + uint32_t memory_size; + uint32_t setup_refcount; + uint32_t page_size; + uint32_t dma_nframes; /* number of page caches to load */ + uint32_t dma_currframe; /* currect page cache number */ + uint32_t dma_frlength_0; /* length of page cache zero */ + uint8_t dma_error; /* set if virtual memory could not be + * loaded */ + uint8_t done_sleep; /* set if done thread is sleeping */ +}; + +/* + * The following structure is used when setting up an array of USB + * transfers. + */ +struct usb2_setup_params { + struct usb2_dma_tag *dma_tag_p; + struct usb2_page *dma_page_ptr; + struct usb2_page_cache *dma_page_cache_ptr; /* these will be + * auto-freed */ + struct usb2_page_cache *xfer_page_cache_ptr; /* these will not be + * auto-freed */ + struct usb2_device *udev; + struct usb2_xfer *curr_xfer; + const struct usb2_config *curr_setup; + const struct usb2_config_sub *curr_setup_sub; + const struct usb2_pipe_methods *methods; + void *buf; + uint32_t *xfer_length_ptr; + + uint32_t size[7]; + uint32_t bufsize; + uint32_t bufsize_max; + uint32_t hc_max_frame_size; + + uint16_t hc_max_packet_size; + uint8_t hc_max_packet_count; + uint8_t speed; + uint8_t dma_tag_max; + usb2_error_t err; +}; + +/* function prototypes */ + +uint8_t usb2_transfer_pending(struct usb2_xfer *xfer); +uint8_t usb2_transfer_setup_sub_malloc(struct usb2_setup_params *parm, struct usb2_page_cache **ppc, uint32_t size, uint32_t align, uint32_t count); +void usb2_command_wrapper(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer); +void usb2_pipe_enter(struct usb2_xfer *xfer); +void usb2_pipe_start(struct usb2_xfer_queue *pq); +void usb2_transfer_dequeue(struct usb2_xfer *xfer); +void usb2_transfer_done(struct usb2_xfer *xfer, usb2_error_t error); +void usb2_transfer_enqueue(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer); +void usb2_transfer_setup_sub(struct usb2_setup_params *parm); +void usb2_default_transfer_setup(struct usb2_device *udev); +void usb2_clear_data_toggle(struct usb2_device *udev, struct usb2_pipe *pipe); +void usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max); +usb2_callback_t usb2_do_request_callback; +usb2_callback_t usb2_handle_request_callback; +usb2_callback_t usb2_do_clear_stall_callback; +void usb2_transfer_timeout_ms(struct usb2_xfer *xfer, void (*cb) (void *arg), uint32_t ms); + +#endif /* _USB2_TRANSFER_H_ */ diff --git a/sys/dev/usb2/core/usb2_util.c b/sys/dev/usb2/core/usb2_util.c new file mode 100644 index 000000000000..d944e441abb7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_util.c @@ -0,0 +1,354 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_request.h> +#include <dev/usb2/core/usb2_busdma.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> + +/* function prototypes */ +#if (USB_USE_CONDVAR == 0) +static int usb2_msleep(void *chan, struct mtx *mtx, int priority, const char *wmesg, int timo); + +#endif + +/*------------------------------------------------------------------------* + * device_delete_all_children - delete all children of a device + *------------------------------------------------------------------------*/ +int +device_delete_all_children(device_t dev) +{ + device_t *devlist; + int devcount; + int error; + + error = device_get_children(dev, &devlist, &devcount); + if (error == 0) { + while (devcount-- > 0) { + error = device_delete_child(dev, devlist[devcount]); + if (error) { + break; + } + } + free(devlist, M_TEMP); + } + return (error); +} + +/*------------------------------------------------------------------------* + * device_set_usb2_desc + * + * This function can be called at probe or attach to set the USB + * device supplied textual description for the given device. + *------------------------------------------------------------------------*/ +void +device_set_usb2_desc(device_t dev) +{ + struct usb2_attach_arg *uaa; + struct usb2_device *udev; + struct usb2_interface *iface; + char *temp_p; + usb2_error_t err; + + if (dev == NULL) { + /* should not happen */ + return; + } + uaa = device_get_ivars(dev); + if (uaa == NULL) { + /* can happend if called at the wrong time */ + return; + } + udev = uaa->device; + iface = uaa->iface; + + if ((iface == NULL) || + (iface->idesc == NULL) || + (iface->idesc->iInterface == 0)) { + err = USB_ERR_INVAL; + } else { + err = 0; + } + + temp_p = (char *)udev->bus->scratch[0].data; + + if (!err) { + /* try to get the interface string ! */ + err = usb2_req_get_string_any + (udev, NULL, temp_p, + sizeof(udev->bus->scratch), iface->idesc->iInterface); + } + if (err) { + /* use default description */ + usb2_devinfo(udev, temp_p, + sizeof(udev->bus->scratch)); + } + device_set_desc_copy(dev, temp_p); + device_printf(dev, "<%s> on %s\n", temp_p, + device_get_nameunit(udev->bus->bdev)); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pause_mtx - factored out code + * + * This function will delay the code by the passed number of + * milliseconds. The passed mutex "mtx" will be dropped while waiting, + * if "mtx" is not NULL. The number of milliseconds per second is 1024 + * for sake of optimisation. + *------------------------------------------------------------------------*/ +void +usb2_pause_mtx(struct mtx *mtx, uint32_t ms) +{ + if (cold) { + ms = (ms + 1) * 1024; + DELAY(ms); + + } else { + + ms = USB_MS_TO_TICKS(ms); + /* + * Add one to the number of ticks so that we don't return + * too early! + */ + ms++; + + if (mtx != NULL) + mtx_unlock(mtx); + + if (pause("USBWAIT", ms)) { + /* ignore */ + } + if (mtx != NULL) + mtx_lock(mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_printBCD + * + * This function will print the version number "bcd" to the string + * pointed to by "p" having a maximum length of "p_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb2_printBCD(char *p, uint16_t p_len, uint16_t bcd) +{ + if (snprintf(p, p_len, "%x.%02x", bcd >> 8, bcd & 0xff)) { + /* ignore any errors */ + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_trim_spaces + * + * This function removes spaces at the beginning and the end of the string + * pointed to by the "p" argument. + *------------------------------------------------------------------------*/ +void +usb2_trim_spaces(char *p) +{ + char *q; + char *e; + + if (p == NULL) + return; + q = e = p; + while (*q == ' ') /* skip leading spaces */ + q++; + while ((*p = *q++)) /* copy string */ + if (*p++ != ' ') /* remember last non-space */ + e = p; + *e = 0; /* kill trailing spaces */ + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_devid + * + * This function returns the USB Vendor and Product ID like a 32-bit + * unsigned integer. + *------------------------------------------------------------------------*/ +uint32_t +usb2_get_devid(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + return ((uaa->info.idVendor << 16) | (uaa->info.idProduct)); +} + +/*------------------------------------------------------------------------* + * usb2_make_str_desc - convert an ASCII string into a UNICODE string + *------------------------------------------------------------------------*/ +uint8_t +usb2_make_str_desc(void *ptr, uint16_t max_len, const char *s) +{ + struct usb2_string_descriptor *p = ptr; + uint8_t totlen; + int j; + + if (max_len < 2) { + /* invalid length */ + return (0); + } + max_len = ((max_len / 2) - 1); + + j = strlen(s); + + if (j < 0) { + j = 0; + } + if (j > 126) { + j = 126; + } + if (max_len > j) { + max_len = j; + } + totlen = (max_len + 1) * 2; + + p->bLength = totlen; + p->bDescriptorType = UDESC_STRING; + + while (max_len--) { + USETW2(p->bString[max_len], 0, s[max_len]); + } + return (totlen); +} + +#if (USB_USE_CONDVAR == 0) + +/*------------------------------------------------------------------------* + * usb2_cv_init - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_init(struct cv *cv, const char *desc) +{ + cv_init(cv, desc); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_destroy - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_destroy(struct cv *cv) +{ + cv_destroy(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_wait - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_wait(struct cv *cv, struct mtx *mtx) +{ + int err; + + err = usb2_msleep(cv, mtx, 0, cv_wmesg(cv), 0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_wait_sig - wrapper function + *------------------------------------------------------------------------*/ +int +usb2_cv_wait_sig(struct cv *cv, struct mtx *mtx) +{ + int err; + + err = usb2_msleep(cv, mtx, PCATCH, cv_wmesg(cv), 0); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_cv_timedwait - wrapper function + *------------------------------------------------------------------------*/ +int +usb2_cv_timedwait(struct cv *cv, struct mtx *mtx, int timo) +{ + int err; + + if (timo == 0) + timo = 1; /* zero means no timeout */ + err = usb2_msleep(cv, mtx, 0, cv_wmesg(cv), timo); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_cv_signal - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_signal(struct cv *cv) +{ + wakeup_one(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_broadcast - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_broadcast(struct cv *cv) +{ + wakeup(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_msleep - wrapper function + *------------------------------------------------------------------------*/ +static int +usb2_msleep(void *chan, struct mtx *mtx, int priority, const char *wmesg, + int timo) +{ + int err; + + if (mtx == &Giant) { + err = tsleep(chan, priority, wmesg, timo); + } else { +#ifdef mtx_sleep + err = mtx_sleep(chan, mtx, priority, wmesg, timo); +#else + err = msleep(chan, mtx, priority, wmesg, timo); +#endif + } + return (err); +} + +#endif diff --git a/sys/dev/usb2/core/usb2_util.h b/sys/dev/usb2/core/usb2_util.h new file mode 100644 index 000000000000..8bbae8c5d32c --- /dev/null +++ b/sys/dev/usb2/core/usb2_util.h @@ -0,0 +1,57 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB2_UTIL_H_ +#define _USB2_UTIL_H_ + +int device_delete_all_children(device_t dev); +uint32_t usb2_get_devid(device_t dev); +uint8_t usb2_make_str_desc(void *ptr, uint16_t max_len, const char *s); +void device_set_usb2_desc(device_t dev); +void usb2_pause_mtx(struct mtx *mtx, uint32_t ms); +void usb2_printBCD(char *p, uint16_t p_len, uint16_t bcd); +void usb2_trim_spaces(char *p); + +#if (USB_USE_CONDVAR == 0) +void usb2_cv_init(struct cv *cv, const char *desc); +void usb2_cv_destroy(struct cv *cv); +void usb2_cv_wait(struct cv *cv, struct mtx *mtx); +int usb2_cv_wait_sig(struct cv *cv, struct mtx *mtx); +int usb2_cv_timedwait(struct cv *cv, struct mtx *mtx, int timo); +void usb2_cv_signal(struct cv *cv); +void usb2_cv_broadcast(struct cv *cv); + +#else +#define usb2_cv_init cv_init +#define usb2_cv_destroy cv_destroy +#define usb2_cv_wait cv_wait +#define usb2_cv_wait_sig cv_wait_sig +#define usb2_cv_timedwait cv_timedwait +#define usb2_cv_signal cv_signal +#define usb2_cv_broadcast cv_broadcast +#endif + +#endif /* _USB2_UTIL_H_ */ diff --git a/sys/dev/usb2/core/usbdevs b/sys/dev/usb2/core/usbdevs new file mode 100644 index 000000000000..8fa1cf1f14c2 --- /dev/null +++ b/sys/dev/usb2/core/usbdevs @@ -0,0 +1,2482 @@ +$FreeBSD$ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * List of known USB vendors + * + * USB.org publishes a VID list of USB-IF member companies at + * http://www.usb.org/developers/tools + * Note that it does not show companies that have obtained a Vendor ID + * without becoming full members. + * + * Please note that these IDs do not do anything. Adding an ID here and + * regenerating the usbdevs.h and usbdevs_data.h only makes a symbolic name + * available to the source code and does not change any functionality, nor + * does it make your device available to a specific driver. + * It will however make the descriptive string available if a device does not + * provide the string itself. + * + * After adding a vendor ID VNDR and a product ID PRDCT you will have the + * following extra defines: + * #define USB_VENDOR_VNDR 0x???? + * #define USB_PRODUCT_VNDR_PRDCT 0x???? + * + * You may have to add these defines to the respective probe routines to + * make the device recognised by the appropriate device driver. + */ + +vendor UNKNOWN1 0x0053 Unknown vendor +vendor UNKNOWN2 0x0105 Unknown vendor +vendor EGALAX2 0x0123 eGalax, Inc. +vendor HUMAX 0x02ad HUMAX +vendor LTS 0x0386 LTS +vendor BWCT 0x03da Bernd Walter Computer Technology +vendor AOX 0x03e8 AOX +vendor THESYS 0x03e9 Thesys +vendor DATABROADCAST 0x03ea Data Broadcasting +vendor ATMEL 0x03eb Atmel +vendor IWATSU 0x03ec Iwatsu America +vendor MITSUMI 0x03ee Mitsumi +vendor HP 0x03f0 Hewlett Packard +vendor GENOA 0x03f1 Genoa +vendor OAK 0x03f2 Oak +vendor ADAPTEC 0x03f3 Adaptec +vendor DIEBOLD 0x03f4 Diebold +vendor SIEMENSELECTRO 0x03f5 Siemens Electromechanical +vendor EPSONIMAGING 0x03f8 Epson Imaging +vendor KEYTRONIC 0x03f9 KeyTronic +vendor OPTI 0x03fb OPTi +vendor ELITEGROUP 0x03fc Elitegroup +vendor XILINX 0x03fd Xilinx +vendor FARALLON 0x03fe Farallon Communications +vendor NATIONAL 0x0400 National Semiconductor +vendor NATIONALREG 0x0401 National Registry +vendor ACERLABS 0x0402 Acer Labs +vendor FTDI 0x0403 Future Technology Devices +vendor NCR 0x0404 NCR +vendor SYNOPSYS2 0x0405 Synopsys +vendor FUJITSUICL 0x0406 Fujitsu-ICL +vendor FUJITSU2 0x0407 Fujitsu Personal Systems +vendor QUANTA 0x0408 Quanta +vendor NEC 0x0409 NEC +vendor KODAK 0x040a Eastman Kodak +vendor WELTREND 0x040b Weltrend +vendor VIA 0x040d VIA +vendor MCCI 0x040e MCCI +vendor MELCO 0x0411 Melco +vendor LEADTEK 0x0413 Leadtek +vendor WINBOND 0x0416 Winbond +vendor PHOENIX 0x041a Phoenix +vendor CREATIVE 0x041e Creative Labs +vendor NOKIA 0x0421 Nokia +vendor ADI 0x0422 ADI Systems +vendor CATC 0x0423 Computer Access Technology +vendor SMC2 0x0424 Standard Microsystems +vendor MOTOROLA_HK 0x0425 Motorola HK +vendor GRAVIS 0x0428 Advanced Gravis Computer +vendor CIRRUSLOGIC 0x0429 Cirrus Logic +vendor INNOVATIVE 0x042c Innovative Semiconductors +vendor MOLEX 0x042f Molex +vendor SUN 0x0430 Sun Microsystems +vendor UNISYS 0x0432 Unisys +vendor TAUGA 0x0436 Taugagreining HF +vendor AMD 0x0438 Advanced Micro Devices +vendor LEXMARK 0x043d Lexmark International +vendor LG 0x043e LG Electronics +vendor NANAO 0x0440 NANAO +vendor GATEWAY 0x0443 Gateway 2000 +vendor NMB 0x0446 NMB +vendor ALPS 0x044e Alps Electric +vendor THRUST 0x044f Thrustmaster +vendor TI 0x0451 Texas Instruments +vendor ANALOGDEVICES 0x0456 Analog Devices +vendor SIS 0x0457 Silicon Integrated Systems Corp. +vendor KYE 0x0458 KYE Systems +vendor DIAMOND2 0x045a Diamond (Supra) +vendor RENESAS 0x045b Renesas +vendor MICROSOFT 0x045e Microsoft +vendor PRIMAX 0x0461 Primax Electronics +vendor MGE 0x0463 MGE UPS Systems +vendor AMP 0x0464 AMP +vendor CHERRY 0x046a Cherry Mikroschalter +vendor MEGATRENDS 0x046b American Megatrends +vendor LOGITECH 0x046d Logitech +vendor BTC 0x046e Behavior Tech. Computer +vendor PHILIPS 0x0471 Philips +vendor SUN2 0x0472 Sun Microsystems (offical) +vendor SANYO 0x0474 Sanyo Electric +vendor SEAGATE 0x0477 Seagate +vendor CONNECTIX 0x0478 Connectix +vendor SEMTECH 0x047a Semtech +vendor KENSINGTON 0x047d Kensington +vendor LUCENT 0x047e Lucent +vendor PLANTRONICS 0x047f Plantronics +vendor KYOCERA 0x0482 Kyocera Wireless Corp. +vendor STMICRO 0x0483 STMicroelectronics +vendor FOXCONN 0x0489 Foxconn +vendor YAMAHA 0x0499 YAMAHA +vendor COMPAQ 0x049f Compaq +vendor HITACHI 0x04a4 Hitachi +vendor ACERP 0x04a5 Acer Peripherals +vendor DAVICOM 0x04a6 Davicom +vendor VISIONEER 0x04a7 Visioneer +vendor CANON 0x04a9 Canon +vendor NIKON 0x04b0 Nikon +vendor PAN 0x04b1 Pan International +vendor IBM 0x04b3 IBM +vendor CYPRESS 0x04b4 Cypress Semiconductor +vendor ROHM 0x04b5 ROHM +vendor COMPAL 0x04b7 Compal +vendor EPSON 0x04b8 Seiko Epson +vendor RAINBOW 0x04b9 Rainbow Technologies +vendor IODATA 0x04bb I-O Data +vendor TDK 0x04bf TDK +vendor 3COMUSR 0x04c1 U.S. Robotics +vendor METHODE 0x04c2 Methode Electronics Far East +vendor MAXISWITCH 0x04c3 Maxi Switch +vendor LOCKHEEDMER 0x04c4 Lockheed Martin Energy Research +vendor FUJITSU 0x04c5 Fujitsu +vendor TOSHIBAAM 0x04c6 Toshiba America +vendor MICROMACRO 0x04c7 Micro Macro Technologies +vendor KONICA 0x04c8 Konica +vendor LITEON 0x04ca Lite-On Technology +vendor FUJIPHOTO 0x04cb Fuji Photo Film +vendor PHILIPSSEMI 0x04cc Philips Semiconductors +vendor TATUNG 0x04cd Tatung Co. Of America +vendor SCANLOGIC 0x04ce ScanLogic +vendor MYSON 0x04cf Myson Technology +vendor DIGI2 0x04d0 Digi +vendor ITTCANON 0x04d1 ITT Canon +vendor ALTEC 0x04d2 Altec Lansing +vendor LSI 0x04d4 LSI +vendor MENTORGRAPHICS 0x04d6 Mentor Graphics +vendor ITUNERNET 0x04d8 I-Tuner Networks +vendor HOLTEK 0x04d9 Holtek Semiconductor, Inc. +vendor PANASONIC 0x04da Panasonic (Matsushita) +vendor HUANHSIN 0x04dc Huan Hsin +vendor SHARP 0x04dd Sharp +vendor IIYAMA 0x04e1 Iiyama +vendor SHUTTLE 0x04e6 Shuttle Technology +vendor ELO 0x04e7 Elo TouchSystems +vendor SAMSUNG 0x04e8 Samsung Electronics +vendor NORTHSTAR 0x04eb Northstar +vendor TOKYOELECTRON 0x04ec Tokyo Electron +vendor ANNABOOKS 0x04ed Annabooks +vendor JVC 0x04f1 JVC +vendor CHICONY 0x04f2 Chicony Electronics +vendor ELAN 0x04f3 Elan +vendor NEWNEX 0x04f7 Newnex +vendor BROTHER 0x04f9 Brother Industries +vendor DALLAS 0x04fa Dallas Semiconductor +vendor SUNPLUS 0x04fc Sunplus +vendor PFU 0x04fe PFU +vendor FUJIKURA 0x0501 Fujikura/DDK +vendor ACER 0x0502 Acer +vendor 3COM 0x0506 3Com +vendor HOSIDEN 0x0507 Hosiden Corporation +vendor AZTECH 0x0509 Aztech Systems +vendor BELKIN 0x050d Belkin Components +vendor KAWATSU 0x050f Kawatsu Semiconductor +vendor FCI 0x0514 FCI +vendor LONGWELL 0x0516 Longwell +vendor COMPOSITE 0x0518 Composite +vendor STAR 0x0519 Star Micronics +vendor APC 0x051d American Power Conversion +vendor SCIATLANTA 0x051e Scientific Atlanta +vendor TSM 0x0520 TSM +vendor CONNECTEK 0x0522 Advanced Connectek USA +vendor NETCHIP 0x0525 NetChip Technology +vendor ALTRA 0x0527 ALTRA +vendor ATI 0x0528 ATI Technologies +vendor AKS 0x0529 Aladdin Knowledge Systems +vendor TEKOM 0x052b Tekom +vendor CANONDEV 0x052c Canon +vendor WACOMTECH 0x0531 Wacom +vendor INVENTEC 0x0537 Inventec +vendor SHYHSHIUN 0x0539 Shyh Shiun Terminals +vendor PREHWERKE 0x053a Preh Werke Gmbh & Co. KG +vendor SYNOPSYS 0x053f Synopsys +vendor UNIACCESS 0x0540 Universal Access +vendor VIEWSONIC 0x0543 ViewSonic +vendor XIRLINK 0x0545 Xirlink +vendor ANCHOR 0x0547 Anchor Chips +vendor SONY 0x054c Sony +vendor FUJIXEROX 0x0550 Fuji Xerox +vendor VISION 0x0553 VLSI Vision +vendor ASAHIKASEI 0x0556 Asahi Kasei Microsystems +vendor ATEN 0x0557 ATEN International +vendor SAMSUNG2 0x055d Samsung Electronics +vendor MUSTEK 0x055f Mustek Systems +vendor TELEX 0x0562 Telex Communications +vendor CHINON 0x0564 Chinon +vendor PERACOM 0x0565 Peracom Networks +vendor ALCOR2 0x0566 Alcor Micro +vendor XYRATEX 0x0567 Xyratex +vendor WACOM 0x056a WACOM +vendor ETEK 0x056c e-TEK Labs +vendor EIZO 0x056d EIZO +vendor ELECOM 0x056e Elecom +vendor CONEXANT 0x0572 Conexant +vendor HAUPPAUGE 0x0573 Hauppauge Computer Works +vendor BAFO 0x0576 BAFO/Quality Computer Accessories +vendor YEDATA 0x057b Y-E Data +vendor AVM 0x057c AVM +vendor QUICKSHOT 0x057f Quickshot +vendor ROLAND 0x0582 Roland +vendor ROCKFIRE 0x0583 Rockfire +vendor RATOC 0x0584 RATOC Systems +vendor ZYXEL 0x0586 ZyXEL Communication +vendor INFINEON 0x058b Infineon +vendor MICREL 0x058d Micrel +vendor ALCOR 0x058f Alcor Micro +vendor OMRON 0x0590 OMRON +vendor ZORAN 0x0595 Zoran Microelectronics +vendor NIIGATA 0x0598 Niigata +vendor IOMEGA 0x059b Iomega +vendor ATREND 0x059c A-Trend Technology +vendor AID 0x059d Advanced Input Devices +vendor LACIE 0x059f LaCie +vendor FUJIFILM 0x05a2 Fuji Film +vendor ARC 0x05a3 ARC +vendor ORTEK 0x05a4 Ortek +vendor BOSE 0x05a7 Bose +vendor OMNIVISION 0x05a9 OmniVision +vendor INSYSTEM 0x05ab In-System Design +vendor APPLE 0x05ac Apple Computer +vendor YCCABLE 0x05ad Y.C. Cable +vendor DIGITALPERSONA 0x05ba DigitalPersona +vendor 3G 0x05bc 3G Green Green Globe +vendor RAFI 0x05bd RAFI +vendor TYCO 0x05be Tyco +vendor KAWASAKI 0x05c1 Kawasaki +vendor DIGI 0x05c5 Digi International +vendor QUALCOMM2 0x05c6 Qualcomm +vendor QTRONIX 0x05c7 Qtronix +vendor FOXLINK 0x05c8 Foxlink +vendor RICOH 0x05ca Ricoh +vendor ELSA 0x05cc ELSA +vendor SCIWORX 0x05ce sci-worx +vendor BRAINBOXES 0x05d1 Brainboxes Limited +vendor ULTIMA 0x05d8 Ultima +vendor AXIOHM 0x05d9 Axiohm Transaction Solutions +vendor MICROTEK 0x05da Microtek +vendor SUNTAC 0x05db SUN Corporation +vendor LEXAR 0x05dc Lexar Media +vendor ADDTRON 0x05dd Addtron +vendor SYMBOL 0x05e0 Symbol Technologies +vendor SYNTEK 0x05e1 Syntek +vendor GENESYS 0x05e3 Genesys Logic +vendor FUJI 0x05e5 Fuji Electric +vendor KEITHLEY 0x05e6 Keithley Instruments +vendor EIZONANAO 0x05e7 EIZO Nanao +vendor KLSI 0x05e9 Kawasaki LSI +vendor FFC 0x05eb FFC +vendor ANKO 0x05ef Anko Electronic +vendor PIENGINEERING 0x05f3 P.I. Engineering +vendor AOC 0x05f6 AOC International +vendor CHIC 0x05fe Chic Technology +vendor BARCO 0x0600 Barco Display Systems +vendor BRIDGE 0x0607 Bridge Information +vendor SOLIDYEAR 0x060b Solid Year +vendor BIORAD 0x0614 Bio-Rad Laboratories +vendor MACALLY 0x0618 Macally +vendor ACTLABS 0x061c Act Labs +vendor ALARIS 0x0620 Alaris +vendor APEX 0x0624 Apex +vendor CREATIVE3 0x062a Creative Labs +vendor VIVITAR 0x0636 Vivitar +vendor GUNZE 0x0637 Gunze Electronics USA +vendor AVISION 0x0638 Avision +vendor TEAC 0x0644 TEAC +vendor SGI 0x065e Silicon Graphics +vendor SANWASUPPLY 0x0663 Sanwa Supply +vendor LINKSYS 0x066b Linksys +vendor ACERSA 0x066e Acer Semiconductor America +vendor SIGMATEL 0x066f Sigmatel +vendor DRAYTEK 0x0675 DrayTek +vendor AIWA 0x0677 Aiwa +vendor ACARD 0x0678 ACARD Technology +vendor PROLIFIC 0x067b Prolific Technology +vendor SIEMENS 0x067c Siemens +vendor AVANCELOGIC 0x0680 Avance Logic +vendor SIEMENS2 0x0681 Siemens +vendor MINOLTA 0x0686 Minolta +vendor CHPRODUCTS 0x068e CH Products +vendor HAGIWARA 0x0693 Hagiwara Sys-Com +vendor CTX 0x0698 Chuntex +vendor ASKEY 0x069a Askey Computer +vendor SAITEK 0x06a3 Saitek +vendor ALCATELT 0x06b9 Alcatel Telecom +vendor AGFA 0x06bd AGFA-Gevaert +vendor ASIAMD 0x06be Asia Microelectronic Development +vendor BIZLINK 0x06c4 Bizlink International +vendor KEYSPAN 0x06cd Keyspan / InnoSys Inc. +vendor AASHIMA 0x06d6 Aashima Technology +vendor MULTITECH 0x06e0 MultiTech +vendor ADS 0x06e1 ADS Technologies +vendor ALCATELM 0x06e4 Alcatel Microelectronics +vendor SIRIUS 0x06ea Sirius Technologies +vendor GUILLEMOT 0x06f8 Guillemot +vendor BOSTON 0x06fd Boston Acoustics +vendor SMC 0x0707 Standard Microsystems +vendor PUTERCOM 0x0708 Putercom +vendor MCT 0x0711 MCT +vendor IMATION 0x0718 Imation +vendor SONYERICSSON 0x0731 Sony Ericsson +vendor EICON 0x0734 Eicon Networks +vendor SYNTECH 0x0745 Syntech Information +vendor DIGITALSTREAM 0x074e Digital Stream +vendor AUREAL 0x0755 Aureal Semiconductor +vendor MIDIMAN 0x0763 Midiman +vendor CYBERPOWER 0x0764 Cyber Power Systems, Inc. +vendor SURECOM 0x0769 Surecom Technology +vendor LINKSYS2 0x077b Linksys +vendor GRIFFIN 0x077d Griffin Technology +vendor SANDISK 0x0781 SanDisk +vendor JENOPTIK 0x0784 Jenoptik +vendor LOGITEC 0x0789 Logitec +vendor BRIMAX 0x078e Brimax +vendor AXIS 0x0792 Axis Communications +vendor ABL 0x0794 ABL Electronics +vendor SAGEM 0x079b Sagem +vendor SUNCOMM 0x079c Sun Communications, Inc. +vendor ALFADATA 0x079d Alfadata Computer +vendor NATIONALTECH 0x07a2 National Technical Systems +vendor ONNTO 0x07a3 Onnto +vendor BE 0x07a4 Be +vendor ADMTEK 0x07a6 ADMtek +vendor COREGA 0x07aa Corega +vendor FREECOM 0x07ab Freecom +vendor MICROTECH 0x07af Microtech +vendor GENERALINSTMNTS 0x07b2 General Instruments (Motorola) +vendor OLYMPUS 0x07b4 Olympus +vendor ABOCOM 0x07b8 AboCom Systems +vendor KEISOKUGIKEN 0x07c1 Keisokugiken +vendor ONSPEC 0x07c4 OnSpec +vendor APG 0x07c5 APG Cash Drawer +vendor BUG 0x07c8 B.U.G. +vendor ALLIEDTELESYN 0x07c9 Allied Telesyn International +vendor AVERMEDIA 0x07ca AVerMedia Technologies +vendor SIIG 0x07cc SIIG +vendor CASIO 0x07cf CASIO +vendor DLINK2 0x07d1 D-Link +vendor APTIO 0x07d2 Aptio Products +vendor ARASAN 0x07da Arasan Chip Systems +vendor ALLIEDCABLE 0x07e6 Allied Cable +vendor STSN 0x07ef STSN +vendor CENTURY 0x07f7 Century Corp +vendor ZOOM 0x0803 Zoom Telephonics +vendor PCS 0x0810 Personal Communication Systems +vendor BROADLOGIC 0x0827 BroadLogic +vendor HANDSPRING 0x082d Handspring +vendor PALM 0x0830 Palm Computing +vendor SOURCENEXT 0x0833 SOURCENEXT +vendor ACTIONSTAR 0x0835 Action Star Enterprise +vendor SAMSUNG_TECHWIN 0x0839 Samsung Techwin +vendor ACCTON 0x083a Accton Technology +vendor DIAMOND 0x0841 Diamond +vendor NETGEAR 0x0846 BayNETGEAR +vendor TOPRE 0x0853 Topre Corporation +vendor ACTIVEWIRE 0x0854 ActiveWire +vendor BBELECTRONICS 0x0856 B&B Electronics +vendor PORTGEAR 0x085a PortGear +vendor NETGEAR2 0x0864 Netgear +vendor SYSTEMTALKS 0x086e System Talks +vendor METRICOM 0x0870 Metricom +vendor ADESSOKBTEK 0x087c ADESSO/Kbtek America +vendor JATON 0x087d Jaton +vendor APT 0x0880 APT Technologies +vendor BOCARESEARCH 0x0885 Boca Research +vendor ANDREA 0x08a8 Andrea Electronics +vendor BURRBROWN 0x08bb Burr-Brown Japan +vendor 2WIRE 0x08c8 2Wire +vendor AIPTEK 0x08ca AIPTEK International +vendor SMARTBRIDGES 0x08d1 SmartBridges +vendor BILLIONTON 0x08dd Billionton Systems +vendor EXTENDED 0x08e9 Extended Systems +vendor MSYSTEMS 0x08ec M-Systems +vendor AUTHENTEC 0x08ff AuthenTec +vendor AUDIOTECHNICA 0x0909 Audio-Technica +vendor TRUMPION 0x090a Trumpion Microelectronics +vendor FEIYA 0x090c Feiya +vendor ALATION 0x0910 Alation Systems +vendor GLOBESPAN 0x0915 Globespan +vendor CONCORDCAMERA 0x0919 Concord Camera +vendor GARMIN 0x091e Garmin International +vendor GOHUBS 0x0921 GoHubs +vendor XEROX 0x0924 Xerox +vendor BIOMETRIC 0x0929 American Biometric Company +vendor TOSHIBA 0x0930 Toshiba +vendor PLEXTOR 0x093b Plextor +vendor INTREPIDCS 0x093c Intrepid +vendor YANO 0x094f Yano +vendor KINGSTON 0x0951 Kingston Technology +vendor BLUEWATER 0x0956 BlueWater Systems +vendor AGILENT 0x0957 Agilent Technologies +vendor GUDE 0x0959 Gude ADS +vendor PORTSMITH 0x095a Portsmith +vendor ACERW 0x0967 Acer +vendor ADIRONDACK 0x0976 Adirondack Wire & Cable +vendor BECKHOFF 0x0978 Beckhoff +vendor MINDSATWORK 0x097a Minds At Work +vendor POINTCHIPS 0x09a6 PointChips +vendor INTERSIL 0x09aa Intersil +vendor ALTIUS 0x09b3 Altius Solutions +vendor ARRIS 0x09c1 Arris Interactive +vendor ACTIVCARD 0x09c3 ACTIVCARD +vendor ACTISYS 0x09c4 ACTiSYS +vendor NOVATEL2 0x09d7 Novatel Wireless +vendor AFOURTECH 0x09da A-FOUR TECH +vendor AIMEX 0x09dc AIMEX +vendor ADDONICS 0x09df Addonics Technologies +vendor AKAI 0x09e8 AKAI professional M.I. +vendor ARESCOM 0x09f5 ARESCOM +vendor BAY 0x09f9 Bay Associates +vendor ALTERA 0x09fb Altera +vendor CSR 0x0a12 Cambridge Silicon Radio +vendor TREK 0x0a16 Trek Technology +vendor ASAHIOPTICAL 0x0a17 Asahi Optical +vendor BOCASYSTEMS 0x0a43 Boca Systems +vendor SHANTOU 0x0a46 ShanTou +vendor MEDIAGEAR 0x0a48 MediaGear +vendor BROADCOM 0x0a5c Broadcom +vendor GREENHOUSE 0x0a6b GREENHOUSE +vendor GEOCAST 0x0a79 Geocast Network Systems +vendor IDQUANTIQUE 0x0aba id Quantique +vendor ZYDAS 0x0ace Zydas Technology Corporation +vendor NEODIO 0x0aec Neodio +vendor OPTION 0x0af0 Option N.V: +vendor ASUS 0x0b05 ASUSTeK Computer +vendor TODOS 0x0b0c Todos Data System +vendor SIIG2 0x0b39 SIIG +vendor TEKRAM 0x0b3b Tekram Technology +vendor HAL 0x0b41 HAL Corporation +vendor EMS 0x0b43 EMS Production +vendor NEC2 0x0b62 NEC +vendor ATI2 0x0b6f ATI +vendor ZEEVO 0x0b7a Zeevo, Inc. +vendor KURUSUGAWA 0x0b7e Kurusugawa Electronics, Inc. +vendor ASIX 0x0b95 ASIX Electronics +vendor O2MICRO 0x0b97 O2 Micro, Inc. +vendor USR 0x0baf U.S. Robotics +vendor AMBIT 0x0bb2 Ambit Microsystems +vendor HTC 0x0bb4 HTC +vendor REALTEK 0x0bda Realtek +vendor ADDONICS2 0x0bf6 Addonics Technology +vendor FSC 0x0bf8 Fujitsu Siemens Computers +vendor AGATE 0x0c08 Agate Technologies +vendor DMI 0x0c0b DMI +vendor MICRODIA 0x0c45 Chicony +vendor SEALEVEL 0x0c52 Sealevel System +vendor LUWEN 0x0c76 Luwen +vendor KYOCERA2 0x0c88 Kyocera Wireless Corp. +vendor ZCOM 0x0cde Z-Com +vendor ATHEROS2 0x0cf3 Atheros Communications +vendor TANGTOP 0x0d3d Tangtop +vendor SMC3 0x0d5c Standard Microsystems +vendor ADDON 0x0d7d Add-on Technology +vendor ACDC 0x0d7e American Computer & Digital Components +vendor ABC 0x0d8c ABC +vendor CONCEPTRONIC 0x0d8e Conceptronic +vendor SKANHEX 0x0d96 Skanhex Technology, Inc. +vendor MSI 0x0db0 Micro Star International +vendor ELCON 0x0db7 ELCON Systemtechnik +vendor NETAC 0x0dd8 Netac +vendor SITECOMEU 0x0df6 Sitecom Europe +vendor MOBILEACTION 0x0df7 Mobile Action +vendor SPEEDDRAGON 0x0e55 Speed Dragon Multimedia +vendor HAWKING 0x0e66 Hawking +vendor FOSSIL 0x0e67 Fossil, Inc +vendor GMATE 0x0e7e G.Mate, Inc +vendor OTI 0x0ea0 Ours Technology +vendor PILOTECH 0x0eaf Pilotech +vendor NOVATECH 0x0eb0 NovaTech +vendor ITEGNO 0x0eba iTegno +vendor WINMAXGROUP 0x0ed1 WinMaxGroup +vendor TOD 0x0ede TOD +vendor EGALAX 0x0eef eGalax, Inc. +vendor AIRPRIME 0x0f3d AirPrime, Inc. +vendor MICROTUNE 0x0f4d Microtune +vendor VTECH 0x0f88 VTech +vendor FALCOM 0x0f94 Falcom Wireless Communications GmbH +vendor RIM 0x0fca Research In Motion +vendor DYNASTREAM 0x0fcf Dynastream Innovations +vendor QUALCOMM 0x1004 Qualcomm +vendor DESKNOTE 0x1019 Desknote +vendor GIGABYTE 0x1044 GIGABYTE +vendor WESTERN 0x1058 Western Digital +vendor MOTOROLA 0x1063 Motorola +vendor CCYU 0x1065 CCYU Technology +vendor CURITEL 0x106c Curitel Communications Inc +vendor SILABS2 0x10a6 SILABS2 +vendor USI 0x10ab USI +vendor PLX 0x10b5 PLX +vendor ASANTE 0x10bd Asante +vendor SILABS 0x10c4 Silicon Labs +vendor ANALOG 0x1110 Analog Devices +vendor TENX 0x1130 Ten X Technology, Inc. +vendor ISSC 0x1131 Integrated System Solution Corp. +vendor JRC 0x1145 Japan Radio Company +vendor SPHAIRON 0x114b Sphairon Access Systems GmbH +vendor DELORME 0x1163 DeLorme +vendor SERVERWORKS 0x1166 ServerWorks +vendor ACERCM 0x1189 Acer Communications & Multimedia +vendor SIERRA 0x1199 Sierra Wireless +vendor TOPFIELD 0x11db Topfield Co., Ltd +vendor SIEMENS3 0x11f5 Siemens +vendor PROLIFIC2 0x11f6 Prolific +vendor ALCATEL 0x11f7 Alcatel +vendor UNKNOWN3 0x1233 Unknown vendor +vendor TSUNAMI 0x1241 Tsunami +vendor PHEENET 0x124a Pheenet +vendor TARGUS 0x1267 Targus +vendor TWINMOS 0x126f TwinMOS +vendor TENDA 0x1286 Tenda +vendor CREATIVE2 0x1292 Creative Labs +vendor BELKIN2 0x1293 Belkin Components +vendor CYBERTAN 0x129b CyberTAN Technology +vendor HUAWEI 0x12d1 Huawei Technologies +vendor ARANEUS 0x12d8 Araneus Information Systems +vendor TAPWAVE 0x12ef Tapwave +vendor AINCOMM 0x12fd Aincomm +vendor MOBILITY 0x1342 Mobility +vendor DICKSMITH 0x1371 Dick Smith Electronics +vendor NETGEAR3 0x1385 Netgear +vendor BALTECH 0x13ad Baltech +vendor CISCOLINKSYS 0x13b1 Cisco-Linksys +vendor SHARK 0x13d2 Shark +vendor NOVATEL 0x1410 Novatel Wireless +vendor MERLIN 0x1416 Merlin +vendor WISTRONNEWEB 0x1435 Wistron NeWeb +vendor RADIOSHACK 0x1453 Radio Shack +vendor HUAWEI3COM 0x1472 Huawei-3Com +vendor SILICOM 0x1485 Silicom +vendor RALINK 0x148f Ralink Technology +vendor IMAGINATION 0x149a Imagination Technologies +vendor CONCEPTRONIC2 0x14b2 Conceptronic +vendor PLANEX3 0x14ea Planex Communications +vendor SILICONPORTALS 0x1527 Silicon Portals +vendor UBIQUAM 0x1529 UBIQUAM Co., Ltd. +vendor UBLOX 0x1546 U-blox +vendor PNY 0x154b PNY +vendor OQO 0x1557 OQO +vendor UMEDIA 0x157e U-MEDIA Communications +vendor FIBERLINE 0x1582 Fiberline +vendor SPARKLAN 0x15a9 SparkLAN +vendor SOHOWARE 0x15e8 SOHOware +vendor UMAX 0x1606 UMAX Data Systems +vendor INSIDEOUT 0x1608 Inside Out Networks +vendor GOODWAY 0x1631 Good Way Technology +vendor ENTREGA 0x1645 Entrega +vendor ACTIONTEC 0x1668 Actiontec Electronics +vendor ATHEROS 0x168c Atheros Communications +vendor GIGASET 0x1690 Gigaset +vendor GLOBALSUN 0x16ab Global Sun Technology +vendor ANYDATA 0x16d5 AnyDATA Corporation +vendor JABLOTRON 0x16d6 Jablotron +vendor CMOTECH 0x16d8 CMOTECH Co., Ltd. +vendor AXESSTEL 0x1726 Axesstel Co., Ltd. +vendor LINKSYS4 0x1737 Linksys +vendor SENAO 0x1740 Senao +vendor METAGEEK 0x1781 MetaGeek +vendor AMIT 0x18c5 AMIT +vendor QCOM 0x18e8 Qcom +vendor LINKSYS3 0x1915 Linksys +vendor QUALCOMMINC 0x19d2 Qualcomm, Incorporated +vendor DLINK 0x2001 D-Link +vendor PLANEX2 0x2019 Planex Communications +vendor ERICSSON 0x2282 Ericsson +vendor MOTOROLA2 0x22b8 Motorola +vendor TRIPPLITE 0x2478 Tripp-Lite +vendor HIROSE 0x2631 Hirose Electric +vendor NHJ 0x2770 NHJ +vendor PLANEX 0x2c02 Planex Communications +vendor VIDZMEDIA 0x3275 VidzMedia Pte Ltd +vendor AEI 0x3334 AEI +vendor HANK 0x3353 Hank Connection +vendor PQI 0x3538 PQI +vendor DAISY 0x3579 Daisy Technology +vendor NI 0x3923 National Instruments +vendor MICRONET 0x3980 Micronet Communications +vendor IODATA2 0x40bb I-O Data +vendor IRIVER 0x4102 iRiver +vendor DELL 0x413c Dell +vendor WCH 0x4348 QinHeng Electronics +vendor ACEECA 0x4766 Aceeca +vendor AVERATEC 0x50c2 Averatec +vendor SWEEX 0x5173 Sweex +vendor ONSPEC2 0x55aa OnSpec Electronic Inc. +vendor ZINWELL 0x5a57 Zinwell +vendor SITECOM 0x6189 Sitecom +vendor ARKMICRO 0x6547 Arkmicro Technologies Inc. +vendor 3COM2 0x6891 3Com +vendor INTEL 0x8086 Intel +vendor SITECOM2 0x9016 Sitecom +vendor MOSCHIP 0x9710 MosChip Semiconductor +vendor 3COM3 0xa727 3Com +vendor HP2 0xf003 Hewlett Packard +vendor USRP 0xfffe GNU Radio USRP + +/* + * List of known products. Grouped by vendor. + */ + +/* 3Com products */ +product 3COM HOMECONN 0x009d HomeConnect Camera +product 3COM 3CREB96 0x00a0 Bluetooth USB Adapter +product 3COM 3C19250 0x03e8 3C19250 Ethernet Adapter +product 3COM 3CRSHEW696 0x0a01 3CRSHEW696 Wireless Adapter +product 3COM 3C460 0x11f8 HomeConnect 3C460 +product 3COM USR56K 0x3021 U.S.Robotics 56000 Voice FaxModem Pro +product 3COM 3C460B 0x4601 HomeConnect 3C460B +product 3COM2 3CRUSB10075 0xa727 3CRUSB10075 +product 3COM3 AR5523_1 0x6893 AR5523 +product 3COM3 AR5523_2 0x6895 AR5523 +product 3COM3 AR5523_3 0x6897 AR5523 + +product 3COMUSR OFFICECONN 0x0082 3Com OfficeConnect Analog Modem +product 3COMUSR USRISDN 0x008f 3Com U.S. Robotics Pro ISDN TA +product 3COMUSR HOMECONN 0x009d 3Com HomeConnect Camera +product 3COMUSR USR56K 0x3021 U.S. Robotics 56000 Voice FaxModem Pro + +/* AboCom products */ +product ABOCOM XX1 0x110c XX1 +product ABOCOM XX2 0x200c XX2 +product ABOCOM URE450 0x4000 URE450 Ethernet Adapter +product ABOCOM UFE1000 0x4002 UFE1000 Fast Ethernet Adapter +product ABOCOM DSB650TX_PNA 0x4003 1/10/100 Ethernet Adapter +product ABOCOM XX4 0x4004 XX4 +product ABOCOM XX5 0x4007 XX5 +product ABOCOM XX6 0x400b XX6 +product ABOCOM XX7 0x400c XX7 +product ABOCOM RTL8151 0x401a RTL8151 +product ABOCOM XX8 0x4102 XX8 +product ABOCOM XX9 0x4104 XX9 +product ABOCOM UF200 0x420a UF200 Ethernet +product ABOCOM WL54 0x6001 WL54 +product ABOCOM XX10 0xabc1 XX10 +product ABOCOM BWU613 0xb000 BWU613 +product ABOCOM HWU54DM 0xb21b HWU54DM +product ABOCOM RT2573_2 0xb21c RT2573 +product ABOCOM RT2573_3 0xb21d RT2573 +product ABOCOM RT2573_4 0xb21e RT2573 +product ABOCOM WUG2700 0xb21f WUG2700 + +/* Accton products */ +product ACCTON USB320_EC 0x1046 USB320-EC Ethernet Adapter +product ACCTON 2664W 0x3501 2664W +product ACCTON 111 0x3503 T-Sinus 111 Wireless Adapter +product ACCTON SMCWUSBG 0x4505 SMCWUSB-G +product ACCTON PRISM_GT 0x4521 PrismGT USB 2.0 WLAN +product ACCTON SS1001 0x5046 SpeedStream Ethernet Adapter +product ACCTON ZD1211B 0xe501 ZD1211B + +/* Aceeca products */ +product ACEECA MEZ1000 0x0001 MEZ1000 RDA + +/* Acer Communications & Multimedia (oemd by Surecom) */ +product ACERCM EP1427X2 0x0893 EP-1427X-2 Ethernet Adapter + +/* Acer Labs products */ +product ACERLABS M5632 0x5632 USB 2.0 Data Link + +/* Acer Peripherals, Inc. products */ +product ACERP ACERSCAN_C310U 0x12a6 Acerscan C310U +product ACERP ACERSCAN_320U 0x2022 Acerscan 320U +product ACERP ACERSCAN_640U 0x2040 Acerscan 640U +product ACERP ACERSCAN_620U 0x2060 Acerscan 620U +product ACERP ACERSCAN_4300U 0x20b0 Benq 3300U/4300U +product ACERP ACERSCAN_640BT 0x20be Acerscan 640BT +product ACERP ACERSCAN_1240U 0x20c0 Acerscan 1240U +product ACERP ATAPI 0x6003 ATA/ATAPI Adapter +product ACERP AWL300 0x9000 AWL300 Wireless Adapter +product ACERP AWL400 0x9001 AWL400 Wireless Adapter + +/* Acer Warp products */ +product ACERW WARPLINK 0x0204 Warplink + +/* Actiontec, Inc. products */ +product ACTIONTEC PRISM_25 0x0408 Prism2.5 Wireless Adapter +product ACTIONTEC PRISM_25A 0x0421 Prism2.5 Wireless Adapter A +product ACTIONTEC FREELAN 0x6106 ROPEX FreeLan 802.11b +product ACTIONTEC UAT1 0x7605 UAT1 Wireless Ethernet Adapter + +/* ACTiSYS products */ +product ACTISYS IR2000U 0x0011 ACT-IR2000U FIR + +/* ActiveWire, Inc. products */ +product ACTIVEWIRE IOBOARD 0x0100 I/O Board +product ACTIVEWIRE IOBOARD_FW1 0x0101 I/O Board, rev. 1 firmware + +/* Adaptec products */ +product ADAPTEC AWN8020 0x0020 AWN-8020 WLAN + +/* Addtron products */ +product ADDTRON AWU120 0xff31 AWU-120 + +/* ADMtek products */ +product ADMTEK PEGASUSII_4 0x07c2 AN986A Ethernet +product ADMTEK PEGASUS 0x0986 AN986 Ethernet +product ADMTEK PEGASUSII 0x8511 AN8511 Ethernet +product ADMTEK PEGASUSII_2 0x8513 AN8513 Ethernet +product ADMTEK PEGASUSII_3 0x8515 AN8515 Ethernet + +/* ADDON products */ +/* PNY OEMs these */ +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON A256MB 0x1400 Attache 256MB USB 2.0 Flash Drive +product ADDON DISKPRO512 0x1420 USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive) + +/* Addonics products */ +product ADDONICS2 CABLE_205 0xa001 Cable 205 + +/* ADS products */ +product ADS UBS10BT 0x0008 UBS-10BT Ethernet +product ADS UBS10BTX 0x0009 UBS-10BT Ethernet + +/* AEI products */ +product AEI FASTETHERNET 0x1701 Fast Ethernet + +/* Agate Technologies products */ +product AGATE QDRIVE 0x0378 Q-Drive + +/* AGFA products */ +product AGFA SNAPSCAN1212U 0x0001 SnapScan 1212U +product AGFA SNAPSCAN1236U 0x0002 SnapScan 1236U +product AGFA SNAPSCANTOUCH 0x0100 SnapScan Touch +product AGFA SNAPSCAN1212U2 0x2061 SnapScan 1212U +product AGFA SNAPSCANE40 0x208d SnapScan e40 +product AGFA SNAPSCANE50 0x208f SnapScan e50 +product AGFA SNAPSCANE20 0x2091 SnapScan e20 +product AGFA SNAPSCANE25 0x2095 SnapScan e25 +product AGFA SNAPSCANE26 0x2097 SnapScan e26 +product AGFA SNAPSCANE52 0x20fd SnapScan e52 + +/* Ain Communication Technology products */ +product AINCOMM AWU2000B 0x1001 AWU2000B Wireless Adapter + +/* AIPTEK products */ +product AIPTEK POCKETCAM3M 0x2011 PocketCAM 3Mega +product SUNPLUS PENCAM_MEGA_1_3 0x504a PenCam Mega 1.3 + +/* AirPrime products */ +product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card + +/* AKS products */ +product AKS USBHASP 0x0001 USB-HASP 0.06 + +/* Alcor Micro, Inc. products */ +product ALCOR2 KBD_HUB 0x2802 Kbd Hub + +product ALCOR MA_KBD_HUB 0x9213 MacAlly Kbd Hub +product ALCOR AU9814 0x9215 AU9814 Hub +product ALCOR UMCR_9361 0x9361 USB Multimedia Card Reader +product ALCOR SM_KBD 0x9410 MicroConnectors/StrongMan Keyboard +product ALCOR NEC_KBD_HUB 0x9472 NEC Kbd Hub + +/* Altec Lansing products */ +product ALTEC ADA70 0x0070 ADA70 Speakers +product ALTEC ASC495 0xff05 ASC495 Speakers + +/* Allied Telesyn International products */ +product ALLIEDTELESYN ATUSB100 0xb100 AT-USB100 + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Ambit Microsystems products */ +product AMBIT WLAN 0x0302 WLAN +product AMBIT NTL_250 0x6098 NTL 250 cable modem + +/* AMIT products */ +product AMIT CGWLUSB2GO 0x0002 CG-WLUSB2GO + +/* Anchor products */ +product ANCHOR EZUSB 0x2131 EZUSB +product ANCHOR EZLINK 0x2720 EZLINK + +/* AnyData products */ +product ANYDATA ADU_E100X 0x6501 CDMA 2000 1xRTT/EV-DO USB Modem +product ANYDATA ADU_500A 0x6502 CDMA 2000 EV-DO USB Modem + +/* AOX, Inc. products */ +product AOX USB101 0x0008 Ethernet + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Apple Computer products */ +product APPLE EXT_KBD 0x020c Apple Extended USB Keyboard +product APPLE OPTMOUSE 0x0302 Optical mouse +product APPLE MIGHTYMOUSE 0x0304 Mighty Mouse +product APPLE EXT_KBD_HUB 0x1003 Hub in Apple Extended USB Keyboard +product APPLE SPEAKERS 0x1101 Speakers +product APPLE IPOD 0x1201 iPod +product APPLE IPOD2G 0x1202 iPod 2G +product APPLE IPOD3G 0x1203 iPod 3G +product APPLE IPOD_04 0x1204 iPod '04' +product APPLE IPODMINI 0x1205 iPod Mini +product APPLE IPOD_06 0x1206 iPod '06' +product APPLE IPOD_07 0x1207 iPod '07' +product APPLE IPOD_08 0x1208 iPod '08' +product APPLE IPODVIDEO 0x1209 iPod Video +product APPLE IPODNANO 0x120a iPod Nano +product APPLE IPHONE 0x1290 iPhone +product APPLE IPHONE_3G 0x1292 iPhone 3G +product APPLE ETHERNET 0x1402 Ethernet A1277 + +/* Arkmicro Technologies */ +product ARKMICRO ARK3116 0x0232 ARK3116 Serial + +/* Asahi Optical products */ +product ASAHIOPTICAL OPTIO230 0x0004 Digital camera +product ASAHIOPTICAL OPTIO330 0x0006 Digital camera + +/* Asante products */ +product ASANTE EA 0x1427 Ethernet + +/* ASIX Electronics products */ +product ASIX AX88172 0x1720 10/100 Ethernet +product ASIX AX88178 0x1780 AX88178 +product ASIX AX88772 0x7720 AX88772 + +/* ASUS products */ +product ASUS WL167G 0x1707 WL-167g Wireless Adapter +product ASUS WL159G 0x170c WL-159g +product ASUS A9T_WIFI 0x171b A9T wireless +product ASUS RT2573_1 0x1723 RT2573 +product ASUS RT2573_2 0x1724 RT2573 +product ASUS LCM 0x1726 LCM display +product ASUS P535 0x420f ASUS P535 PDA + +/* ATen products */ +product ATEN UC1284 0x2001 Parallel printer +product ATEN UC10T 0x2002 10Mbps Ethernet +product ATEN UC110T 0x2007 UC-110T Ethernet +product ATEN UC232A 0x2008 Serial +product ATEN UC210T 0x2009 UC-210T Ethernet +product ATEN DSB650C 0x4000 DSB-650C + +/* Atheros Communications products */ +product ATHEROS AR5523 0x0001 AR5523 +product ATHEROS AR5523_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_1 0x0001 AR5523 +product ATHEROS2 AR5523_1_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_2 0x0003 AR5523 +product ATHEROS2 AR5523_2_NF 0x0004 AR5523 (no firmware) +product ATHEROS2 AR5523_3 0x0005 AR5523 +product ATHEROS2 AR5523_3_NF 0x0006 AR5523 (no firmware) + +/* Atmel Comp. products */ +product ATMEL UHB124 0x3301 UHB124 hub +product ATMEL DWL120 0x7603 DWL-120 Wireless Adapter +product ATMEL BW002 0x7605 BW002 Wireless Adapter +product ATMEL WL1130USB 0x7613 WL-1130 USB +product ATMEL AT76C505A 0x7614 AT76c505a Wireless Adapter + +/* Avision products */ +product AVISION 1200U 0x0268 1200U scanner + +/* Axesstel products */ +product AXESSTEL DATAMODEM 0x1000 Data Modem + +/* Baltech products */ +product BALTECH CARDREADER 0x9999 Card reader + +/* B&B Electronics products */ +product BBELECTRONICS USOTL4 0xAC01 RS-422/485 + +/* Belkin products */ +/*product BELKIN F5U111 0x???? F5U111 Ethernet*/ +product BELKIN F5D6050 0x0050 F5D6050 802.11b Wireless Adapter +product BELKIN FBT001V 0x0081 FBT001v2 Bluetooth +product BELKIN FBT003V 0x0084 FBT003v2 Bluetooth +product BELKIN F5U103 0x0103 F5U103 Serial +product BELKIN F5U109 0x0109 F5U109 Serial +product BELKIN USB2SCSI 0x0115 USB to SCSI +product BELKIN USB2LAN 0x0121 USB to LAN +product BELKIN F5U208 0x0208 F5U208 VideoBus II +product BELKIN F5U237 0x0237 F5U237 USB 2.0 7-Port Hub +product BELKIN F5U257 0x0257 F5U257 Serial +product BELKIN F5U409 0x0409 F5U409 Serial +product BELKIN F6C550AVR 0x0551 F6C550-AVR UPS +product BELKIN F5U120 0x1203 F5U120-PC Hub +product BELKIN ZD1211B 0x4050 ZD1211B +product BELKIN F5D5055 0x5055 F5D5055 +product BELKIN F5D7050 0x7050 F5D7050 Wireless Adapter +product BELKIN F5D7051 0x7051 F5D7051 54g USB Network Adapter +product BELKIN F5D7050A 0x705a F5D7050A Wireless Adapter +product BELKIN F5D7050_V4000 0x705c F5D7050 v4000 Wireless Adapter +product BELKIN F5D9050V3 0x905b F5D9050 ver 3 Wireless Adapter +product BELKIN2 F5U002 0x0002 F5U002 Parallel printer + +/* Billionton products */ +product BILLIONTON USB100 0x0986 USB100N 10/100 FastEthernet +product BILLIONTON USBLP100 0x0987 USB100LP +product BILLIONTON USBEL100 0x0988 USB100EL +product BILLIONTON USBE100 0x8511 USBE100 +product BILLIONTON USB2AR 0x90ff USB2AR Ethernet + +/* Broadcom products */ +product BROADCOM BCM2033 0x2033 BCM2033 Bluetooth USB dongle + +/* Brother Industries products */ +product BROTHER HL1050 0x0002 HL-1050 laser printer + +/* Behavior Technology Computer products */ +product BTC BTC7932 0x6782 Keyboard with mouse port + +/* Canon, Inc. products */ +product CANON N656U 0x2206 CanoScan N656U +product CANON N1220U 0x2207 CanoScan N1220U +product CANON D660U 0x2208 CanoScan D660U +product CANON N676U 0x220d CanoScan N676U +product CANON N1240U 0x220e CanoScan N1240U +product CANON LIDE25 0x2220 CanoScan LIDE 25 +product CANON S10 0x3041 PowerShot S10 +product CANON S100 0x3045 PowerShot S100 +product CANON S200 0x3065 PowerShot S200 +product CANON REBELXT 0x30ef Digital Rebel XT + +/* CATC products */ +product CATC NETMATE 0x000a Netmate Ethernet +product CATC NETMATE2 0x000c Netmate2 Ethernet +product CATC CHIEF 0x000d USB Chief Bus & Protocol Analyzer +product CATC ANDROMEDA 0x1237 Andromeda hub + +/* CASIO products */ +product CASIO QV_DIGICAM 0x1001 QV DigiCam +product CASIO EXS880 0x1105 Exilim EX-S880 +product CASIO BE300 0x2002 BE-300 PDA +product CASIO NAMELAND 0x4001 CASIO Nameland EZ-USB + +/* CCYU products */ +product CCYU ED1064 0x2136 EasyDisk ED1064 + +/* Century products */ +product CENTURY EX35QUAT 0x011e Century USB Disk Enclosure + +/* Cherry products */ +product CHERRY MY3000KBD 0x0001 My3000 keyboard +product CHERRY MY3000HUB 0x0003 My3000 hub +product CHERRY CYBOARD 0x0004 CyBoard Keyboard + +/* Chic Technology products */ +product CHIC MOUSE1 0x0001 mouse +product CHIC CYPRESS 0x0003 Cypress USB Mouse + +/* Chicony products */ +product CHICONY KB8933 0x0001 KB-8933 keyboard +product MICRODIA TWINKLECAM 0x600d TwinkleCam USB camera + +/* CH Products */ +product CHPRODUCTS PROTHROTTLE 0x00f1 Pro Throttle +product CHPRODUCTS PROPEDALS 0x00f2 Pro Pedals +product CHPRODUCTS FIGHTERSTICK 0x00f3 Fighterstick +product CHPRODUCTS FLIGHTYOKE 0x00ff Flight Sim Yoke + +/* Cisco-Linksys products */ +product CISCOLINKSYS WUSB54G 0x000d WUSB54G Wireless Adapter +product CISCOLINKSYS WUSB54GP 0x0011 WUSB54GP Wireless Adapter +product CISCOLINKSYS USB200MV2 0x0018 USB200M v2 +product CISCOLINKSYS HU200TS 0x001a HU200TS Wireless Adapter +product CISCOLINKSYS WUSB54GC 0x0020 WUSB54GC +product CISCOLINKSYS WUSB54GR 0x0023 WUSB54GR +product CISCOLINKSYS WUSBF54G 0x0024 WUSBF54G + +/* CMOTECH products */ +product CMOTECH CNU510 0x5141 CMOTECH CDMA Technologies USB modem +product CMOTECH CNU550 0x5543 CDMA 2000 1xRTT/1xEVDO USB modem +product CMOTECH CDMA_MODEM1 0x6280 CMOTECH CDMA Technologies USB modem + +/* Compaq products */ +product COMPAQ IPAQPOCKETPC 0x0003 iPAQ PocketPC +product COMPAQ PJB100 0x504a Personal Jukebox PJB100 +product COMPAQ IPAQLINUX 0x505a iPAQ Linux + +/* Composite Corp products looks the same as "TANGTOP" */ +product COMPOSITE USBPS2 0x0001 USB to PS2 Adaptor + +/* Conceptronic products */ +product CONCEPTRONIC PRISM_GT 0x3762 PrismGT USB 2.0 WLAN +product CONCEPTRONIC C11U 0x7100 C11U +product CONCEPTRONIC WL210 0x7110 WL-210 +product CONCEPTRONIC AR5523_1 0x7801 AR5523 +product CONCEPTRONIC AR5523_1_NF 0x7802 AR5523 (no firmware) +product CONCEPTRONIC AR5523_2 0x7811 AR5523 +product CONCEPTRONIC AR5523_2_NF 0x7812 AR5523 (no firmware) +product CONCEPTRONIC2 C54RU 0x3c02 C54RU WLAN +product CONCEPTRONIC2 C54RU2 0x3c22 C54RU + +/* Connectix products */ +product CONNECTIX QUICKCAM 0x0001 QuickCam + +/* Corega products */ +product COREGA ETHER_USB_T 0x0001 Ether USB-T +product COREGA FETHER_USB_TX 0x0004 FEther USB-TX +product COREGA WLAN_USB_USB_11 0x000c WirelessLAN USB-11 +product COREGA FETHER_USB_TXS 0x000d FEther USB-TXS +product COREGA WLANUSB 0x0012 Wireless LAN Stick-11 +product COREGA FETHER_USB2_TX 0x0017 FEther USB2-TX +product COREGA WLUSB_11_KEY 0x001a ULUSB-11 Key +product COREGA CGWLUSB2GL 0x002d CG-WLUSB2GL +product COREGA CGWLUSB2GPX 0x002e CG-WLUSB2GPX +product COREGA WLUSB_11_STICK 0x7613 WLAN USB Stick 11 +product COREGA FETHER_USB_TXC 0x9601 FEther USB-TXC + +/* Creative products */ +product CREATIVE NOMAD_II 0x1002 Nomad II MP3 player +product CREATIVE NOMAD_IIMG 0x4004 Nomad II MG +product CREATIVE NOMAD 0x4106 Nomad +product CREATIVE2 VOIP_BLASTER 0x0258 Voip Blaster +product CREATIVE3 OPTICAL_MOUSE 0x0001 Notebook Optical Mouse + +/* Cambridge Silicon Radio Ltd. products */ +product CSR BT_DONGLE 0x0001 Bluetooth USB dongle +product CSR CSRDFU 0xffff USB Bluetooth Device in DFU State + +/* CTX products */ +product CTX EX1300 0x9999 Ex1300 hub + +/* Curitel products */ +product CURITEL HX550C 0x1101 CDMA 2000 1xRTT USB modem (HX-550C) +product CURITEL HX57XB 0x2101 CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600) +product CURITEL PC5740 0x3701 Broadband Wireless modem + +/* CyberPower products */ +product CYBERPOWER 1500CAVRLCD 0x0501 1500CAVRLCD + +/* CyberTAN Technology products */ +product CYBERTAN TG54USB 0x1666 TG54USB + +/* Cypress Semiconductor products */ +product CYPRESS MOUSE 0x0001 mouse +product CYPRESS THERMO 0x0002 thermometer +product CYPRESS WISPY1A 0x0bad MetaGeek Wi-Spy +product CYPRESS KBDHUB 0x0101 Keyboard/Hub +product CYPRESS FMRADIO 0x1002 FM Radio +product CYPRESS USBRS232 0x5500 USB-RS232 Interface +product CYPRESS SLIM_HUB 0x6560 Slim Hub + +/* Daisy Technology products */ +product DAISY DMC 0x6901 USB MultiMedia Reader + +/* Dallas Semiconductor products */ +product DALLAS J6502 0x4201 J-6502 speakers + +/* Dell products */ +product DELL PORT 0x0058 Port Replicator +product DELL AIO926 0x5115 Photo AIO Printer 926 +product DELL BC02 0x8000 BC02 Bluetooth USB Adapter +product DELL PRISM_GT_1 0x8102 PrismGT USB 2.0 WLAN +product DELL TM350 0x8103 TrueMobile 350 Bluetooth USB Adapter +product DELL PRISM_GT_2 0x8104 PrismGT USB 2.0 WLAN +product DELL U740 0x8135 Dell U740 CDMA + +/* Delorme Paublishing products */ +product DELORME EARTHMATE 0x0100 Earthmate GPS + +/* Desknote products */ +product DESKNOTE UCR_61S2B 0x0c55 UCR-61S2B + +/* Diamond products */ +product DIAMOND RIO500USB 0x0001 Rio 500 USB + +/* Dick Smith Electronics (really C-Net) products */ +product DICKSMITH RT2573 0x9022 RT2573 +product DICKSMITH CWD854F 0x9032 C-Net CWD-854 rev F + +/* Digi International products */ +product DIGI ACCELEPORT2 0x0002 AccelePort USB 2 +product DIGI ACCELEPORT4 0x0004 AccelePort USB 4 +product DIGI ACCELEPORT8 0x0008 AccelePort USB 8 + +/* D-Link products */ +/*product DLINK DSBS25 0x0100 DSB-S25 serial*/ +product DLINK DUBE100 0x1a00 10/100 Ethernet +product DLINK DSB650TX4 0x200c 10/100 Ethernet +product DLINK DWL120E 0x3200 DWL-120 rev E +product DLINK DWL122 0x3700 DWL-122 +product DLINK DWLG120 0x3701 DWL-G120 +product DLINK DWL120F 0x3702 DWL-120 rev F +product DLINK DWLAG132 0x3a00 DWL-AG132 +product DLINK DWLAG132_NF 0x3a01 DWL-AG132 (no firmware) +product DLINK DWLG132 0x3a02 DWL-G132 +product DLINK DWLG132_NF 0x3a03 DWL-G132 (no firmware) +product DLINK DWLAG122 0x3a04 DWL-AG122 +product DLINK DWLAG122_NF 0x3a05 DWL-AG122 (no firmware) +product DLINK DWLG122 0x3c00 DWL-G122 b1 Wireless Adapter +product DLINK DUBE100B1 0x3c05 DUB-E100 rev B1 +product DLINK DSB650C 0x4000 10Mbps Ethernet +product DLINK DSB650TX1 0x4001 10/100 Ethernet +product DLINK DSB650TX 0x4002 10/100 Ethernet +product DLINK DSB650TX_PNA 0x4003 1/10/100 Ethernet +product DLINK DSB650TX3 0x400b 10/100 Ethernet +product DLINK DSB650TX2 0x4102 10/100 Ethernet +product DLINK DSB650 0xabc1 10/100 Ethernet +product DLINK2 DWLG122C1 0x3c03 DWL-G122 c1 +product DLINK2 WUA1340 0x3c04 WUA-1340 +product DLINK2 DWA111 0x3c06 DWA-111 +product DLINK2 DWA110 0x3c07 DWA-110 + +/* DMI products */ +product DMI CFSM_RW 0xa109 CF/SM Reader/Writer + +/* DrayTek products */ +product DRAYTEK VIGOR550 0x0550 Vigor550 + +/* Dynastream Innovations */ +product DYNASTREAM ANTDEVBOARD 0x1003 ANT dev board + +/* EIZO products */ +product EIZO HUB 0x0000 hub +product EIZO MONITOR 0x0001 monitor + +/* ELCON Systemtechnik products */ +product ELCON PLAN 0x0002 Goldpfeil P-LAN + +/* Elecom products */ +product ELECOM MOUSE29UO 0x0002 mouse 29UO +product ELECOM LDUSBTX0 0x200c LD-USB/TX +product ELECOM LDUSBTX1 0x4002 LD-USB/TX +product ELECOM LDUSBLTX 0x4005 LD-USBL/TX +product ELECOM LDUSBTX2 0x400b LD-USB/TX +product ELECOM LDUSB20 0x4010 LD-USB20 +product ELECOM UCSGT 0x5003 UC-SGT +product ELECOM UCSGT0 0x5004 UC-SGT +product ELECOM LDUSBTX3 0xabc1 LD-USB/TX + +/* Elsa products */ +product ELSA MODEM1 0x2265 ELSA Modem Board +product ELSA USB2ETHERNET 0x3000 Microlink USB2Ethernet + +/* EMS products */ +product EMS DUAL_SHOOTER 0x0003 PSX gun controller converter + +/* Entrega products */ +product ENTREGA 1S 0x0001 1S serial +product ENTREGA 2S 0x0002 2S serial +product ENTREGA 1S25 0x0003 1S25 serial +product ENTREGA 4S 0x0004 4S serial +product ENTREGA E45 0x0005 E45 Ethernet +product ENTREGA CENTRONICS 0x0006 Parallel Port +product ENTREGA XX1 0x0008 Ethernet +product ENTREGA 1S9 0x0093 1S9 serial +product ENTREGA EZUSB 0x8000 EZ-USB +/*product ENTREGA SERIAL 0x8001 DB25 Serial*/ +product ENTREGA 2U4S 0x8004 2U4S serial/usb hub +product ENTREGA XX2 0x8005 Ethernet +/*product ENTREGA SERIAL_DB9 0x8093 DB9 Serial*/ + +/* Epson products */ +product EPSON PRINTER1 0x0001 USB Printer +product EPSON PRINTER2 0x0002 ISD USB Smart Cable for Mac +product EPSON PRINTER3 0x0003 ISD USB Smart Cable +product EPSON PRINTER5 0x0005 USB Printer +product EPSON 636 0x0101 Perfection 636U / 636Photo scanner +product EPSON 610 0x0103 Perfection 610 scanner +product EPSON 1200 0x0104 Perfection 1200U / 1200Photo scanner +product EPSON 1600 0x0107 Expression 1600 scanner +product EPSON 1640 0x010a Perfection 1640SU scanner +product EPSON 1240 0x010b Perfection 1240U / 1240Photo scanner +product EPSON 640U 0x010c Perfection 640U scanner +product EPSON 1250 0x010f Perfection 1250U / 1250Photo scanner +product EPSON 1650 0x0110 Perfection 1650 scanner +product EPSON GT9700F 0x0112 GT-9700F scanner +product EPSON GT9300UF 0x011b GT-9300UF scanner +product EPSON 3200 0x011c Perfection 3200 scanner +product EPSON 1260 0x011d Perfection 1260 scanner +product EPSON 1660 0x011e Perfection 1660 scanner +product EPSON 1670 0x011f Perfection 1670 scanner +product EPSON 1270 0x0120 Perfection 1270 scanner +product EPSON 2480 0x0121 Perfection 2480 scanner +product EPSON 3590 0x0122 Perfection 3590 scanner +product EPSON 4990 0x012a Perfection 4990 Photo scanner +product EPSON STYLUS_875DC 0x0601 Stylus Photo 875DC Card Reader +product EPSON STYLUS_895 0x0602 Stylus Photo 895 Card Reader +product EPSON CX5400 0x0808 CX5400 scanner +product EPSON 3500 0x080e CX-3500/3600/3650 MFP +product EPSON RX425 0x080f Stylus Photo RX425 scanner +product EPSON 4800 0x0819 CX4800 MP scanner +product EPSON 4200 0x0820 CX4200 MP scanner +product EPSON 5000 0x082b DX-50x0 MFP scanner +product EPSON 6000 0x082e DX-60x0 MFP scanner +product EPSON DX7400 0x0838 DX7400/CX7300 scanner +product EPSON DX8400 0x0839 DX8400 scanner + +/* e-TEK Labs products */ +product ETEK 1COM 0x8007 Serial + +/* Extended Systems products */ +product EXTENDED XTNDACCESS 0x0100 XTNDAccess IrDA + +/* FEIYA products */ +product FEIYA 5IN1 0x1132 5-in-1 Card Reader + +/* Fiberline */ +product FIBERLINE WL430U 0x6003 WL-430U + +/* Fossil, Inc products */ +product FOSSIL WRISTPDA 0x0002 Wrist PDA + +/* Freecom products */ +product FREECOM DVD 0xfc01 DVD drive + +/* Fujitsu Siemens Computers products */ +product FSC E5400 0x1009 PrismGT USB 2.0 WLAN + +/* Future Technology Devices products */ +product FTDI SERIAL_8U100AX 0x8372 8U100AX Serial +product FTDI SERIAL_8U232AM 0x6001 8U232AM Serial +product FTDI SERIAL_2232C 0x6010 FT2232C Dual port Serial +/* Gude Analog- und Digitalsysteme products also uses FTDI's id: */ +product FTDI TACTRIX_OPENPORT_13M 0xcc48 OpenPort 1.3 Mitsubishi +product FTDI TACTRIX_OPENPORT_13S 0xcc49 OpenPort 1.3 Subaru +product FTDI TACTRIX_OPENPORT_13U 0xcc4a OpenPort 1.3 Universal +product FTDI EISCOU 0xe888 Expert ISDN Control USB +product FTDI UOPTBR 0xe889 USB-RS232 OptoBridge +product FTDI EMCU2D 0xe88a Expert mouseCLOCK USB II +product FTDI PCMSFU 0xe88b Precision Clock MSF USB +product FTDI EMCU2H 0xe88c Expert mouseCLOCK USB II HBG +product FTDI USBSERIAL 0xfa00 Matrix Orbital USB Serial +product FTDI MX2_3 0xfa01 Matrix Orbital MX2 or MX3 +product FTDI MX4_5 0xfa02 Matrix Orbital MX4 or MX5 +product FTDI LK202 0xfa03 Matrix Orbital VK/LK202 Family +product FTDI LK204 0xfa04 Matrix Orbital VK/LK204 Family +product FTDI CFA_632 0xfc08 Crystalfontz CFA-632 USB LCD +product FTDI CFA_634 0xfc09 Crystalfontz CFA-634 USB LCD +product FTDI CFA_633 0xfc0b Crystalfontz CFA-633 USB LCD +product FTDI CFA_631 0xfc0c Crystalfontz CFA-631 USB LCD +product FTDI CFA_635 0xfc0d Crystalfontz CFA-635 USB LCD +product FTDI SEMC_DSS20 0xfc82 SEMC DSS-20 SyncStation + +/* Fuji photo products */ +product FUJIPHOTO MASS0100 0x0100 Mass Storage + +/* Fujitsu protducts */ +product FUJITSU AH_F401U 0x105b AH-F401U Air H device + +/* Garmin products */ +product GARMIN IQUE_3600 0x0004 iQue 3600 + +/* General Instruments (Motorola) products */ +product GENERALINSTMNTS SB5100 0x5100 SURFboard SB5100 Cable modem + +/* Genesys Logic products */ +product GENESYS GL620USB 0x0501 GL620USB Host-Host interface +product GENESYS GL650 0x0604 GL650 Hub +product GENESYS GL641USB 0x0700 GL641USB CompactFlash Card Reader +product GENESYS GL641USB2IDE_2 0x0701 GL641USB USB-IDE Bridge No 2 +product GENESYS GL641USB2IDE 0x0702 GL641USB USB-IDE Bridge +product GENESYS GL641USB_2 0x0760 GL641USB 6-in-1 Card Reader + +/* GIGABYTE products */ +product GIGABYTE GN54G 0x8001 GN-54G +product GIGABYTE GNBR402W 0x8002 GN-BR402W +product GIGABYTE GNWLBM101 0x8003 GN-WLBM101 +product GIGABYTE GNWBKG 0x8007 GN-WBKG +product GIGABYTE GNWB01GS 0x8008 GN-WB01GS +product GIGABYTE GNWI05GS 0x800a GN-WI05GS + +/* Gigaset products */ +product GIGASET WLAN 0x0701 WLAN +product GIGASET SMCWUSBTG 0x0710 SMCWUSBT-G +product GIGASET SMCWUSBTG_NF 0x0711 SMCWUSBT-G (no firmware) +product GIGASET AR5523 0x0712 AR5523 +product GIGASET AR5523_NF 0x0713 AR5523 (no firmware) +product GIGASET RT2573 0x0722 RT2573 + +/* Global Sun Technology product */ +product GLOBALSUN AR5523_1 0x7801 AR5523 +product GLOBALSUN AR5523_1_NF 0x7802 AR5523 (no firmware) +product GLOBALSUN AR5523_2 0x7811 AR5523 +product GLOBALSUN AR5523_2_NF 0x7812 AR5523 (no firmware) + +/* Globespan products */ +product GLOBESPAN PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product GLOBESPAN PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* G.Mate, Inc products */ +product GMATE YP3X00 0x1001 YP3X00 PDA + +/* GoHubs products */ +product GOHUBS GOCOM232 0x1001 GoCOM232 Serial + +/* Good Way Technology products */ +product GOODWAY GWUSB2E 0x6200 GWUSB2E +product GOODWAY RT2573 0xc019 RT2573 + +/* Gravis products */ +product GRAVIS GAMEPADPRO 0x4001 GamePad Pro + +/* GREENHOUSE products */ +product GREENHOUSE KANA21 0x0001 CF-writer with MP3 + +/* Griffin Technology */ +product GRIFFIN IMATE 0x0405 iMate, ADB Adapter + +/* Guillemot Corporation */ +product GUILLEMOT DALEADER 0xa300 DA Leader +product GUILLEMOT HWGUSB254 0xe000 HWGUSB2-54 WLAN +product GUILLEMOT HWGUSB254LB 0xe010 HWGUSB2-54-LB +product GUILLEMOT HWGUSB254V2AP 0xe020 HWGUSB2-54V2-AP + +/* Hagiwara products */ +product HAGIWARA FGSM 0x0002 FlashGate SmartMedia Card Reader +product HAGIWARA FGCF 0x0003 FlashGate CompactFlash Card Reader +product HAGIWARA FG 0x0005 FlashGate + +/* HAL Corporation products */ +product HAL IMR001 0x0011 Crossam2+USB IR commander + +/* Handspring, Inc. */ +product HANDSPRING VISOR 0x0100 Handspring Visor +product HANDSPRING TREO 0x0200 Handspring Treo +product HANDSPRING TREO600 0x0300 Handspring Treo 600 + +/* Hauppauge Computer Works */ +product HAUPPAUGE WINTV_USB_FM 0x4d12 WinTV USB FM + +/* Hawking Technologies products */ +product HAWKING UF100 0x400c 10/100 USB Ethernet + +/* Hitachi, Ltd. products */ +product HITACHI DVDCAM_DZ_MV100A 0x0004 DVD-CAM DZ-MV100A Camcorder +product HITACHI DVDCAM_USB 0x001e DVDCAM USB HS Interface + +/* HP products */ +product HP 895C 0x0004 DeskJet 895C +product HP 4100C 0x0101 Scanjet 4100C +product HP S20 0x0102 Photosmart S20 +product HP 880C 0x0104 DeskJet 880C +product HP 4200C 0x0105 ScanJet 4200C +product HP CDWRITERPLUS 0x0107 CD-Writer Plus +product HP KBDHUB 0x010c Multimedia Keyboard Hub +product HP G55XI 0x0111 OfficeJet G55xi +product HP HN210W 0x011c HN210W 802.11b WLAN +product HP 49GPLUS 0x0121 49g+ graphing calculator +product HP 6200C 0x0201 ScanJet 6200C +product HP S20b 0x0202 PhotoSmart S20 +product HP 815C 0x0204 DeskJet 815C +product HP 3300C 0x0205 ScanJet 3300C +product HP CDW8200 0x0207 CD-Writer Plus 8200e +product HP MMKEYB 0x020c Multimedia keyboard +product HP 1220C 0x0212 DeskJet 1220C +product HP 810C 0x0304 DeskJet 810C/812C +product HP 4300C 0x0305 Scanjet 4300C +product HP CDW4E 0x0307 CD-Writer+ CD-4e +product HP G85XI 0x0311 OfficeJet G85xi +product HP 1200 0x0317 LaserJet 1200 +product HP 5200C 0x0401 Scanjet 5200C +product HP 830C 0x0404 DeskJet 830C +product HP 3400CSE 0x0405 ScanJet 3400cse +product HP 6300C 0x0601 Scanjet 6300C +product HP 840C 0x0604 DeskJet 840c +product HP 2200C 0x0605 ScanJet 2200C +product HP 5300C 0x0701 Scanjet 5300C +product HP 4400C 0x0705 Scanjet 4400C +product HP 82x0C 0x0b01 Scanjet 82x0C +product HP 2300D 0x0b17 Laserjet 2300d +product HP 970CSE 0x1004 Deskjet 970Cse +product HP 5400C 0x1005 Scanjet 5400C +product HP 2215 0x1016 iPAQ 22xx/Jornada 548 +product HP 568J 0x1116 Jornada 568 +product HP 930C 0x1204 DeskJet 930c +product HP P2000U 0x1801 Inkjet P-2000U +product HP 640C 0x2004 DeskJet 640c +product HP 4670V 0x3005 ScanJet 4670v +product HP P1100 0x3102 Photosmart P1100 +product HP HN210E 0x811c Ethernet HN210E +product HP2 C500 0x6002 PhotoSmart C500 + +/* HTC products */ +product HTC WINMOBILE 0x00ce HTC USB Sync +product HTC PPC6700MODEM 0x00cf PPC6700 Modem +product HTC SMARTPHONE 0x0a51 SmartPhone USB Sync + +/* HUAWEI products */ +product HUAWEI MOBILE 0x1001 Huawei Mobile +product HUAWEI E270 0x1003 Huawei HSPA modem + +/* HUAWEI 3com products */ +product HUAWEI3COM WUB320G 0x0009 Aolynk WUB320g + +/* IBM Corporation */ +product IBM USBCDROMDRIVE 0x4427 USB CD-ROM Drive + +/* Imagination Technologies products */ +product IMAGINATION DBX1 0x2107 DBX1 DSP core + +/* Inside Out Networks products */ +product INSIDEOUT EDGEPORT4 0x0001 EdgePort/4 serial ports + +/* In-System products */ +product INSYSTEM F5U002 0x0002 Parallel printer +product INSYSTEM ATAPI 0x0031 ATAPI Adapter +product INSYSTEM ISD110 0x0200 IDE Adapter ISD110 +product INSYSTEM ISD105 0x0202 IDE Adapter ISD105 +product INSYSTEM USBCABLE 0x081a USB cable +product INSYSTEM STORAGE_V2 0x5701 USB Storage Adapter V2 + +/* Intel products */ +product INTEL EASYPC_CAMERA 0x0110 Easy PC Camera +product INTEL TESTBOARD 0x9890 82930 test board + +/* Intersil products */ +product INTERSIL PRISM_GT 0x1000 PrismGT USB 2.0 WLAN +product INTERSIL PRISM_2X 0x3642 Prism2.x or Atmel WLAN + +/* Interpid Control Systems products */ +product INTREPIDCS VALUECAN 0x0601 ValueCAN CAN bus interface +product INTREPIDCS NEOVI 0x0701 NeoVI Blue vehicle bus interface + +/* I/O DATA products */ +product IODATA IU_CD2 0x0204 DVD Multi-plus unit iU-CD2 +product IODATA DVR_UEH8 0x0206 DVD Multi-plus unit DVR-UEH8 +product IODATA USBSSMRW 0x0314 USB-SSMRW SD-card +product IODATA USBSDRW 0x031e USB-SDRW SD-card +product IODATA USBETT 0x0901 USB ETT +product IODATA USBETTX 0x0904 USB ETTX +product IODATA USBETTXS 0x0913 USB ETTX +product IODATA USBWNB11A 0x0919 USB WN-B11 +product IODATA USBWNB11 0x0922 USB Airport WN-B11 +product IODATA ETGUS2 0x0930 ETG-US2 +product IODATA USBRSAQ 0x0a03 Serial USB-RSAQ1 +product IODATA2 USB2SC 0x0a09 USB2.0-SCSI Bridge USB2-SC + +/* Iomega products */ +product IOMEGA ZIP100 0x0001 Zip 100 +product IOMEGA ZIP250 0x0030 Zip 250 + +/* Ituner networks products */ +product ITUNERNET USBLCD2X20 0x0002 USB-LCD 2x20 + +/* Jablotron products */ +product JABLOTRON PC60B 0x0001 PC-60B + +/* Jaton products */ +product JATON EDA 0x5704 Ethernet + +/* JVC products */ +product JVC GR_DX95 0x000a GR-DX95 +product JVC MP_PRX1 0x3008 MP-PRX1 Ethernet + +/* JRC products */ +product JRC AH_J3001V_J3002V 0x0001 AirH PHONE AH-J3001V/J3002V + +/* Kawatsu products */ +product KAWATSU MH4000P 0x0003 MiniHub 4000P + +/* Keisokugiken Corp. products */ +product KEISOKUGIKEN USBDAQ 0x0068 HKS-0200 USBDAQ + +/* Kensington products */ +product KENSINGTON ORBIT 0x1003 Orbit USB/PS2 trackball +product KENSINGTON TURBOBALL 0x1005 TurboBall + +/* Keyspan products */ +product KEYSPAN USA28_NF 0x0101 USA-28 serial Adapter (no firmware) +product KEYSPAN USA28X_NF 0x0102 USA-28X serial Adapter (no firmware) +product KEYSPAN USA19_NF 0x0103 USA-19 serial Adapter (no firmware) +product KEYSPAN USA18_NF 0x0104 USA-18 serial Adapter (no firmware) +product KEYSPAN USA18X_NF 0x0105 USA-18X serial Adapter (no firmware) +product KEYSPAN USA19W_NF 0x0106 USA-19W serial Adapter (no firmware) +product KEYSPAN USA19 0x0107 USA-19 serial Adapter +product KEYSPAN USA19W 0x0108 USA-19W serial Adapter +product KEYSPAN USA49W_NF 0x0109 USA-49W serial Adapter (no firmware) +product KEYSPAN USA49W 0x010a USA-49W serial Adapter +product KEYSPAN USA19QI_NF 0x010b USA-19QI serial Adapter (no firmware) +product KEYSPAN USA19QI 0x010c USA-19QI serial Adapter +product KEYSPAN USA19Q_NF 0x010d USA-19Q serial Adapter (no firmware) +product KEYSPAN USA19Q 0x010e USA-19Q serial Adapter +product KEYSPAN USA28 0x010f USA-28 serial Adapter +product KEYSPAN USA28XXB 0x0110 USA-28X/XB serial Adapter +product KEYSPAN USA18 0x0111 USA-18 serial Adapter +product KEYSPAN USA18X 0x0112 USA-18X serial Adapter +product KEYSPAN USA28XB_NF 0x0113 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA_NF 0x0114 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA 0x0115 USA-28XA serial Adapter +product KEYSPAN USA18XA_NF 0x0116 USA-18XA serial Adapter (no firmware) +product KEYSPAN USA18XA 0x0117 USA-18XA serial Adapter +product KEYSPAN USA19QW_NF 0x0118 USA-19WQ serial Adapter (no firmware) +product KEYSPAN USA19QW 0x0119 USA-19WQ serial Adapter +product KEYSPAN USA19HA 0x0121 USA-19HS serial Adapter +product KEYSPAN UIA10 0x0201 UIA-10 remote control +product KEYSPAN UIA11 0x0202 UIA-11 remote control + +/* Kingston products */ +product KINGSTON XX1 0x0008 Ethernet +product KINGSTON KNU101TX 0x000a KNU101TX USB Ethernet + +/* Kawasaki products */ +product KLSI DUH3E10BT 0x0008 USB Ethernet +product KLSI DUH3E10BTN 0x0009 USB Ethernet + +/* Kodak products */ +product KODAK DC220 0x0100 Digital Science DC220 +product KODAK DC260 0x0110 Digital Science DC260 +product KODAK DC265 0x0111 Digital Science DC265 +product KODAK DC290 0x0112 Digital Science DC290 +product KODAK DC240 0x0120 Digital Science DC240 +product KODAK DC280 0x0130 Digital Science DC280 + +/* Konica Corp. Products */ +product KONICA CAMERA 0x0720 Digital Color Camera + +/* KYE products */ +product KYE NICHE 0x0001 Niche mouse +product KYE NETSCROLL 0x0003 Genius NetScroll mouse +product KYE FLIGHT2000 0x1004 Flight 2000 joystick +product KYE VIVIDPRO 0x2001 ColorPage Vivid-Pro scanner + +/* Kyocera products */ +product KYOCERA FINECAM_S3X 0x0100 Finecam S3x +product KYOCERA FINECAM_S4 0x0101 Finecam S4 +product KYOCERA FINECAM_S5 0x0103 Finecam S5 +product KYOCERA FINECAM_L3 0x0105 Finecam L3 +product KYOCERA AHK3001V 0x0203 AH-K3001V +product KYOCERA2 CDMA_MSM_K 0x17da Qualcomm Kyocera CDMA Technologies MSM + +/* LaCie products */ +product LACIE HD 0xa601 Hard Disk +product LACIE CDRW 0xa602 CD R/W + +/* Lexar products */ +product LEXAR JUMPSHOT 0x0001 jumpSHOT CompactFlash Reader +product LEXAR CF_READER 0xb002 USB CF Reader + +/* Lexmark products */ +product LEXMARK S2450 0x0009 Optra S 2450 + +/* Linksys products */ +product LINKSYS MAUSB2 0x0105 Camedia MAUSB-2 +product LINKSYS USB10TX1 0x200c USB10TX +product LINKSYS USB10T 0x2202 USB10T Ethernet +product LINKSYS USB100TX 0x2203 USB100TX Ethernet +product LINKSYS USB100H1 0x2204 USB100H1 Ethernet/HPNA +product LINKSYS USB10TA 0x2206 USB10TA Ethernet +product LINKSYS USB10TX2 0x400b USB10TX +product LINKSYS2 WUSB11 0x2219 WUSB11 Wireless Adapter +product LINKSYS2 USB200M 0x2226 USB 2.0 10/100 Ethernet +product LINKSYS3 WUSB11v28 0x2233 WUSB11 v2.8 Wireless Adapter +product LINKSYS4 USB1000 0x0039 USB1000 + +/* Logitech products */ +product LOGITECH M2452 0x0203 M2452 keyboard +product LOGITECH M4848 0x0301 M4848 mouse +product LOGITECH PAGESCAN 0x040f PageScan +product LOGITECH QUICKCAMWEB 0x0801 QuickCam Web +product LOGITECH QUICKCAMPRO 0x0810 QuickCam Pro +product LOGITECH QUICKCAMEXP 0x0840 QuickCam Express +product LOGITECH QUICKCAM 0x0850 QuickCam +product LOGITECH N43 0xc000 N43 +product LOGITECH N48 0xc001 N48 mouse +product LOGITECH MBA47 0xc002 M-BA47 mouse +product LOGITECH WMMOUSE 0xc004 WingMan Gaming Mouse +product LOGITECH BD58 0xc00c BD58 mouse +product LOGITECH UN58A 0xc030 iFeel Mouse +product LOGITECH UN53B 0xc032 iFeel MouseMan +product LOGITECH WMPAD 0xc208 WingMan GamePad Extreme +product LOGITECH WMRPAD 0xc20a WingMan RumblePad +product LOGITECH WMJOY 0xc281 WingMan Force joystick +product LOGITECH BB13 0xc401 USB-PS/2 Trackball +product LOGITECH RK53 0xc501 Cordless mouse +product LOGITECH RB6 0xc503 Cordless keyboard +product LOGITECH MX700 0xc506 Cordless optical mouse +product LOGITECH QUICKCAMPRO2 0xd001 QuickCam Pro + +/* Logitec Corp. products */ +product LOGITEC LDR_H443SU2 0x0033 DVD Multi-plus unit LDR-H443SU2 +product LOGITEC LDR_H443U2 0x00b3 DVD Multi-plus unit LDR-H443U2 + +/* Lucent products */ +product LUCENT EVALKIT 0x1001 USS-720 evaluation kit + +/* Luwen products */ +product LUWEN EASYDISK 0x0005 EasyDisc + +/* Macally products */ +product MACALLY MOUSE1 0x0101 mouse + +/* MCT Corp. */ +product MCT HUB0100 0x0100 Hub +product MCT DU_H3SP_USB232 0x0200 D-Link DU-H3SP USB BAY Hub +product MCT USB232 0x0210 USB-232 Interface +product MCT SITECOM_USB232 0x0230 Sitecom USB-232 Products + +/* Melco, Inc products */ +product MELCO LUATX1 0x0001 LUA-TX Ethernet +product MELCO LUATX5 0x0005 LUA-TX Ethernet +product MELCO LUA2TX5 0x0009 LUA2-TX Ethernet +product MELCO LUAKTX 0x0012 LUA-KTX Ethernet +product MELCO DUBPXXG 0x001c USB-IDE Bridge: DUB-PxxG +product MELCO LUAU2KTX 0x003d LUA-U2-KTX Ethernet +product MELCO KG54YB 0x005e WLI-U2-KG54-YB WLAN +product MELCO KG54 0x0066 WLI-U2-KG54 WLAN +product MELCO KG54AI 0x0067 WLI-U2-KG54-AI WLAN +product MELCO NINWIFI 0x008b Nintendo Wi-Fi +product MELCO PCOPRS1 0x00b3 PC-OP-RS1 RemoteStation +product MELCO SG54HP 0x00d8 WLI-U2-SG54HP +product MELCO G54HP 0x00d9 WLI-U2-G54HP +product MELCO KG54L 0x00da WLI-U2-KG54L + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* MetaGeek products */ +product METAGEEK WISPY1B 0x083e MetaGeek Wi-Spy +product METAGEEK WISPY24X 0x083f MetaGeek Wi-Spy 2.4x + +/* Metricom products */ +product METRICOM RICOCHET_GS 0x0001 Ricochet GS + +/* MGE UPS Systems */ +product MGE UPS1 0x0001 MGE UPS SYSTEMS PROTECTIONCENTER 1 +product MGE UPS2 0xffff MGE UPS SYSTEMS PROTECTIONCENTER 2 + +/* Micro Star International products */ +product MSI BT_DONGLE 0x1967 Bluetooth USB dongle +product MSI UB11B 0x6823 UB11B +product MSI RT2570 0x6861 RT2570 +product MSI RT2570_2 0x6865 RT2570 +product MSI RT2570_3 0x6869 RT2570 +product MSI RT2573_1 0x6874 RT2573 +product MSI RT2573_2 0x6877 RT2573 +product MSI RT2573_3 0xa861 RT2573 +product MSI RT2573_4 0xa874 RT2573 + +/* Microdia products */ +product MICRODIA TWINKLECAM 0x600d TwinkleCam USB camera + +/* Microsoft products */ +product MICROSOFT SIDEPREC 0x0008 SideWinder Precision Pro +product MICROSOFT INTELLIMOUSE 0x0009 IntelliMouse +product MICROSOFT NATURALKBD 0x000b Natural Keyboard Elite +product MICROSOFT DDS80 0x0014 Digital Sound System 80 +product MICROSOFT SIDEWINDER 0x001a Sidewinder Precision Racing Wheel +product MICROSOFT INETPRO 0x001c Internet Keyboard Pro +product MICROSOFT TBEXPLORER 0x0024 Trackball Explorer +product MICROSOFT INTELLIEYE 0x0025 IntelliEye mouse +product MICROSOFT INETPRO2 0x002b Internet Keyboard Pro +product MICROSOFT MN510 0x006e MN510 Wireless +product MICROSOFT MN110 0x007a 10/100 USB NIC +product MICROSOFT WLINTELLIMOUSE 0x008c Wireless Optical IntelliMouse +product MICROSOFT WLNOTEBOOK 0x00b9 Wireless Optical Mouse (Model 1023) +product MICROSOFT WLNOTEBOOK2 0x00e1 Wireless Optical Mouse 3000 (Model 1056) +product MICROSOFT WLNOTEBOOK3 0x00d2 Wireless Optical Mouse 3000 (Model 1049) +product MICROSOFT WLUSBMOUSE 0x00b9 Wireless USB Mouse +product MICROSOFT XBOX360 0x0292 XBOX 360 WLAN + +/* Microtech products */ +product MICROTECH SCSIDB25 0x0004 USB-SCSI-DB25 +product MICROTECH SCSIHD50 0x0005 USB-SCSI-HD50 +product MICROTECH DPCM 0x0006 USB CameraMate +product MICROTECH FREECOM 0xfc01 Freecom USB-IDE + +/* Microtek products */ +product MICROTEK 336CX 0x0094 Phantom 336CX - C3 scanner +product MICROTEK X6U 0x0099 ScanMaker X6 - X6U +product MICROTEK C6 0x009a Phantom C6 scanner +product MICROTEK 336CX2 0x00a0 Phantom 336CX - C3 scanner +product MICROTEK V6USL 0x00a3 ScanMaker V6USL +product MICROTEK V6USL2 0x80a3 ScanMaker V6USL +product MICROTEK V6UL 0x80ac ScanMaker V6UL + +/* Microtune, Inc. products */ +product MICROTUNE BT_DONGLE 0x1000 Bluetooth USB dongle + +/* Midiman products */ +product MIDIMAN MIDISPORT2X2 0x1001 Midisport 2x2 + +/* MindsAtWork products */ +product MINDSATWORK WALLET 0x0001 Digital Wallet + +/* Minolta Co., Ltd. */ +product MINOLTA 2300 0x4001 Dimage 2300 +product MINOLTA S304 0x4007 Dimage S304 +product MINOLTA X 0x4009 Dimage X +product MINOLTA 5400 0x400e Dimage 5400 +product MINOLTA F300 0x4011 Dimage F300 +product MINOLTA E223 0x4017 Dimage E223 + +/* Mitsumi products */ +product MITSUMI CDRRW 0x0000 CD-R/RW Drive +product MITSUMI BT_DONGLE 0x641f Bluetooth USB dongle +product MITSUMI FDD 0x6901 USB FDD + +/* Mobility products */ +product MOBILITY EA 0x0204 Ethernet +product MOBILITY EASIDOCK 0x0304 EasiDock Ethernet + +/* MosChip products */ +product MOSCHIP MCS7703 0x7703 MCS7703 Serial Port Adapter +product MOSCHIP MCS7830 0x7830 MCS7830 Ethernet + +/* Motorola products */ +product MOTOROLA MC141555 0x1555 MC141555 hub controller +product MOTOROLA SB4100 0x4100 SB4100 USB Cable Modem +product MOTOROLA2 A41XV32X 0x2a22 A41x/V32x Mobile Phones +product MOTOROLA2 E398 0x4810 E398 Mobile Phone +product MOTOROLA2 USBLAN 0x600c USBLAN +product MOTOROLA2 USBLAN2 0x6027 USBLAN + +/* MultiTech products */ +product MULTITECH ATLAS 0xf101 MT5634ZBA-USB modem + +/* Mustek products */ +product MUSTEK 1200CU 0x0001 1200 CU scanner +product MUSTEK 600CU 0x0002 600 CU scanner +product MUSTEK 1200USB 0x0003 1200 USB scanner +product MUSTEK 1200UB 0x0006 1200 UB scanner +product MUSTEK 1200USBPLUS 0x0007 1200 USB Plus scanner +product MUSTEK 1200CUPLUS 0x0008 1200 CU Plus scanner +product MUSTEK BEARPAW1200F 0x0010 BearPaw 1200F scanner +product MUSTEK BEARPAW1200TA 0x021e BearPaw 1200TA scanner +product MUSTEK 600USB 0x0873 600 USB scanner +product MUSTEK MDC800 0xa800 MDC-800 digital camera + +/* M-Systems products */ +product MSYSTEMS DISKONKEY 0x0010 DiskOnKey +product MSYSTEMS DISKONKEY2 0x0011 DiskOnKey + +/* Myson products */ +product MYSON HEDEN 0x8818 USB-IDE + +/* National Semiconductor */ +product NATIONAL BEARPAW1200 0x1000 BearPaw 1200 +product NATIONAL BEARPAW2400 0x1001 BearPaw 2400 + +/* NEC products */ +product NEC HUB 0x55aa hub +product NEC HUB_B 0x55ab hub + +/* NEODIO products */ +product NEODIO ND3260 0x3260 8-in-1 Multi-format Flash Controller +product NEODIO ND5010 0x5010 Multi-format Flash Controller + +/* Netac products */ +product NETAC CF_CARD 0x1060 USB-CF-Card +product NETAC ONLYDISK 0x0003 OnlyDisk + +/* NetChip Technology Products */ +product NETCHIP TURBOCONNECT 0x1080 Turbo-Connect +product NETCHIP CLIK_40 0xa140 USB Clik! 40 +product NETCHIP ETHERNETGADGET 0xa4a2 Linux Ethernet/RNDIS gadget on pxa210/25x/26x + +/* Netgear products */ +product NETGEAR EA101 0x1001 Ethernet +product NETGEAR EA101X 0x1002 Ethernet +product NETGEAR FA101 0x1020 Ethernet 10/100, USB1.1 +product NETGEAR FA120 0x1040 USB 2.0 Ethernet +product NETGEAR WG111V2_2 0x4240 PrismGT USB 2.0 WLAN +product NETGEAR WG111U 0x4300 WG111U +product NETGEAR WG111U_NF 0x4301 WG111U (no firmware) +product NETGEAR2 MA101 0x4100 MA101 +product NETGEAR2 MA101B 0x4102 MA101 Rev B +product NETGEAR3 WG111T 0x4250 WG111T +product NETGEAR3 WG111T_NF 0x4251 WG111T (no firmware) +product NETGEAR3 WPN111 0x5f00 WPN111 +product NETGEAR3 WPN111_NF 0x5f01 WPN111 (no firmware) + +/* Nikon products */ +product NIKON E990 0x0102 Digital Camera E990 +product NIKON LS40 0x4000 CoolScan LS40 ED +product NIKON D300 0x041a Digital Camera D300 + +/* NovaTech Products */ +product NOVATECH NV902 0x9020 NovaTech NV-902W +product NOVATECH RT2573 0x9021 RT2573 + +/* Novatel Wireless products */ +product NOVATEL V640 0x1100 Merlin V620 +product NOVATEL CDMA_MODEM 0x1110 Novatel Wireless Merlin CDMA +product NOVATEL V620 0x1110 Merlin V620 +product NOVATEL V740 0x1120 Merlin V740 +product NOVATEL V720 0x1130 Merlin V720 +product NOVATEL U740 0x1400 Merlin U740 +product NOVATEL U740_2 0x1410 Merlin U740 +product NOVATEL U870 0x1420 Merlin U870 +product NOVATEL XU870 0x1430 Merlin XU870 +product NOVATEL X950D 0x1450 Merlin X950D +product NOVATEL ES620 0x2100 ES620 CDMA +product NOVATEL U720 0x2110 Merlin U720 +product NOVATEL U727 0x4100 Merlin U727 CDMA +product NOVATEL U950D 0x4400 Novatel MC950D HSUPA +product NOVATEL ZEROCD 0x5010 Novatel ZeroCD +product NOVATEL2 FLEXPACKGPS 0x0100 NovAtel FlexPack GPS receiver + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* Olympus products */ +product OLYMPUS C1 0x0102 C-1 Digital Camera +product OLYMPUS C700 0x0105 C-700 Ultra Zoom + +/* OmniVision Technologies, Inc. products */ +product OMNIVISION OV511 0x0511 OV511 Camera +product OMNIVISION OV511PLUS 0xa511 OV511+ Camera + +/* OnSpec Electronic, Inc. */ +product ONSPEC MDCFE_B_CF_READER 0xa000 MDCFE-B USB CF Reader +product ONSPEC CFMS_RW 0xa001 SIIG/Datafab Memory Stick+CF Reader/Writer +product ONSPEC READER 0xa003 Datafab-based Reader +product ONSPEC CFSM_READER 0xa005 PNY/Datafab CF+SM Reader +product ONSPEC CFSM_READER2 0xa006 Simple Tech/Datafab CF+SM Reader +product ONSPEC MDSM_B_READER 0xa103 MDSM-B reader +product ONSPEC CFSM_COMBO 0xa109 USB to CF + SM Combo (LC1) +product ONSPEC UCF100 0xa400 FlashLink UCF-100 CompactFlash Reader +product ONSPEC2 IMAGEMATE_SDDR55 0xa103 ImageMate SDDR55 + +/* Option products */ +product OPTION VODAFONEMC3G 0x5000 Vodafone Mobile Connect 3G datacard +product OPTION GT3G 0x6000 GlobeTrotter 3G datacard +product OPTION GT3GQUAD 0x6300 GlobeTrotter 3G QUAD datacard +product OPTION GT3GPLUS 0x6600 GlobeTrotter 3G+ datacard +product OPTION GTMAX36 0x6701 GlobeTrotter Max 3.6 Modem + +/* OQO */ +product OQO WIFI01 0x0002 model 01 WiFi interface +product OQO BT01 0x0003 model 01 Bluetooth interface +product OQO ETHER01PLUS 0x7720 model 01+ Ethernet +product OQO ETHER01 0x8150 model 01 Ethernet interface + +/* Palm Computing, Inc. product */ +product PALM SERIAL 0x0080 USB Serial +product PALM M500 0x0001 Palm m500 +product PALM M505 0x0002 Palm m505 +product PALM M515 0x0003 Palm m515 +product PALM I705 0x0020 Palm i705 +product PALM TUNGSTEN_Z 0x0031 Palm Tungsten Z +product PALM M125 0x0040 Palm m125 +product PALM M130 0x0050 Palm m130 +product PALM TUNGSTEN_T 0x0060 Palm Tungsten T +product PALM ZIRE31 0x0061 Palm Zire 31 +product PALM ZIRE 0x0070 Palm Zire + +/* Panasonic products */ +product PANASONIC LS120CAM 0x0901 LS-120 Camera +product PANASONIC KXL840AN 0x0d01 CD-R Drive KXL-840AN +product PANASONIC KXLRW32AN 0x0d09 CD-R Drive KXL-RW32AN +product PANASONIC KXLCB20AN 0x0d0a CD-R Drive KXL-CB20AN +product PANASONIC KXLCB35AN 0x0d0e DVD-ROM & CD-R/RW +product PANASONIC SDCAAE 0x1b00 MultiMediaCard + +/* Peracom products */ +product PERACOM SERIAL1 0x0001 Serial +product PERACOM ENET 0x0002 Ethernet +product PERACOM ENET3 0x0003 At Home Ethernet +product PERACOM ENET2 0x0005 Ethernet + +/* Philips products */ +product PHILIPS DSS350 0x0101 DSS 350 Digital Speaker System +product PHILIPS DSS 0x0104 DSS XXX Digital Speaker System +product PHILIPS HUB 0x0201 hub +product PHILIPS PCA646VC 0x0303 PCA646VC PC Camera +product PHILIPS PCVC680K 0x0308 PCVC680K Vesta Pro PC Camera +product PHILIPS DSS150 0x0471 DSS 150 Digital Speaker System +product PHILIPS SNU5600 0x1236 SNU5600 +product PHILIPS UM10016 0x1552 ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit +product PHILIPS DIVAUSB 0x1801 DIVA USB mp3 player + +/* Philips Semiconductor products */ +product PHILIPSSEMI HUB1122 0x1122 hub + +/* P.I. Engineering products */ +product PIENGINEERING PS2USB 0x020b PS2 to Mac USB Adapter + +/* Planex Communications products */ +product PLANEX GW_US11H 0x14ea GW-US11H WLAN +product PLANEX2 GW_US11S 0x3220 GW-US11S WLAN +product PLANEX2 GW_US54GXS 0x5303 GW-US54GXS WLAN +product PLANEX2 GWUS54HP 0xab01 GW-US54HP +product PLANEX2 GWUS54MINI2 0xab50 GW-US54Mini2 +product PLANEX2 GWUS54SG 0xc002 GW-US54SG +product PLANEX2 GWUS54GZL 0xc007 GW-US54GZL +product PLANEX2 GWUS54GD 0xed01 GW-US54GD +product PLANEX2 GWUSMM 0xed02 GW-USMM +product PLANEX3 GWUS54GZ 0xab10 GW-US54GZ +product PLANEX3 GU1000T 0xab11 GU-1000T +product PLANEX3 GWUS54MINI 0xab13 GW-US54Mini + +/* Plextor Corp. */ +product PLEXTOR 40_12_40U 0x0011 PlexWriter 40/12/40U + +/* PLX products */ +product PLX TESTBOARD 0x9060 test board + +/* PNY products */ +product PNY ATTACHE2 0x0010 USB 2.0 Flash Drive + +/* PortGear products */ +product PORTGEAR EA8 0x0008 Ethernet +product PORTGEAR EA9 0x0009 Ethernet + +/* Portsmith products */ +product PORTSMITH EEA 0x3003 Express Ethernet + +/* Primax products */ +product PRIMAX G2X300 0x0300 G2-200 scanner +product PRIMAX G2E300 0x0301 G2E-300 scanner +product PRIMAX G2300 0x0302 G2-300 scanner +product PRIMAX G2E3002 0x0303 G2E-300 scanner +product PRIMAX 9600 0x0340 Colorado USB 9600 scanner +product PRIMAX 600U 0x0341 Colorado 600u scanner +product PRIMAX 6200 0x0345 Visioneer 6200 scanner +product PRIMAX 19200 0x0360 Colorado USB 19200 scanner +product PRIMAX 1200U 0x0361 Colorado 1200u scanner +product PRIMAX G600 0x0380 G2-600 scanner +product PRIMAX 636I 0x0381 ReadyScan 636i +product PRIMAX G2600 0x0382 G2-600 scanner +product PRIMAX G2E600 0x0383 G2E-600 scanner +product PRIMAX COMFORT 0x4d01 Comfort +product PRIMAX MOUSEINABOX 0x4d02 Mouse-in-a-Box +product PRIMAX PCGAUMS1 0x4d04 Sony PCGA-UMS1 + +/* Prolific products */ +product PROLIFIC PL2301 0x0000 PL2301 Host-Host interface +product PROLIFIC PL2302 0x0001 PL2302 Host-Host interface +product PROLIFIC RSAQ2 0x04bb PL2303 Serial (IODATA USB-RSAQ2) +product PROLIFIC PL2303 0x2303 PL2303 Serial (ATEN/IOGEAR UC232A) +product PROLIFIC PL2305 0x2305 Parallel printer +product PROLIFIC ATAPI4 0x2307 ATAPI-4 Controller +product PROLIFIC PL2501 0x2501 PL2501 Host-Host interface +product PROLIFIC PHAROS 0xaaa0 Prolific Pharos +product PROLIFIC RSAQ3 0xaaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3) +product PROLIFIC2 WSIM 0x2001 Willcom WSIM + +/* Putercom products */ +product PUTERCOM UPA100 0x047e USB-1284 BRIDGE + +/* Qcom products */ +product QCOM RT2573 0x6196 RT2573 +product QCOM RT2573_2 0x6229 RT2573 + +/* Qualcomm products */ +product QUALCOMM CDMA_MSM 0x6000 CDMA Technologies MSM phone +product QUALCOMM2 RWT_FCT 0x3100 RWT FCT-CDMA 2000 1xRTT modem +product QUALCOMM2 CDMA_MSM 0x3196 CDMA Technologies MSM modem +product QUALCOMMINC CDMA_MSM 0x0001 CDMA Technologies MSM modem + +/* Qtronix products */ +product QTRONIX 980N 0x2011 Scorpion-980N keyboard + +/* Quickshot products */ +product QUICKSHOT STRIKEPAD 0x6238 USB StrikePad + +/* Radio Shack */ +product RADIOSHACK USBCABLE 0x4026 USB to Serial Cable + +/* Rainbow Technologies products */ +product RAINBOW IKEY2000 0x1200 i-Key 2000 + +/* Ralink Technology products */ +product RALINK RT2570 0x1706 RT2500USB Wireless Adapter +product RALINK RT2570_2 0x2570 RT2500USB Wireless Adapter +product RALINK RT2573 0x2573 RT2501USB Wireless Adapter +product RALINK RT2671 0x2671 RT2601USB Wireless Adapter +product RALINK RT2570_3 0x9020 RT2500USB Wireless Adapter +product RALINK RT2573_2 0x9021 RT2501USB Wireless Adapter + +/* ReakTek products */ +/* Green House and CompUSA OEM this part */ +product REALTEK USBKR100 0x8150 USBKR100 USB Ethernet + +/* Ricoh products */ +product RICOH VGPVCC2 0x1830 VGP-VCC2 Camera +product RICOH VGPVCC3 0x1832 VGP-VCC3 Camera +product RICOH VGPVCC2_2 0x1833 VGP-VCC2 Camera +product RICOH VGPVCC2_3 0x1834 VGP-VCC2 Camera +product RICOH VGPVCC7 0x183a VGP-VCC7 Camera +product RICOH VGPVCC8 0x183b VGP-VCC8 Camera + +/* Roland products */ +product ROLAND UM1 0x0009 UM-1 MIDI I/F +product ROLAND UM880N 0x0014 EDIROL UM-880 MIDI I/F (native) +product ROLAND UM880G 0x0015 EDIROL UM-880 MIDI I/F (generic) + +/* Rockfire products */ +product ROCKFIRE GAMEPAD 0x2033 gamepad 203USB + +/* RATOC Systems products */ +product RATOC REXUSB60 0xb000 REX-USB60 + +/* Sagem products */ +product SAGEM USBSERIAL 0x0027 USB-Serial Controller +product SAGEM XG760A 0x004a XG-760A +product SAGEM XG76NA 0x0062 XG-76NA + +/* Samsung products */ +product SAMSUNG ML6060 0x3008 ML-6060 laser printer +product SAMSUNG YP_U2 0x5050 YP-U2 MP3 Player +product SAMSUNG I500 0x6601 I500 Palm USB Phone + +/* Samsung Techwin products */ +product SAMSUNG_TECHWIN DIGIMAX_410 0x000a Digimax 410 + +/* SanDisk products */ +product SANDISK SDDR05A 0x0001 ImageMate SDDR-05a +product SANDISK SDDR31 0x0002 ImageMate SDDR-31 +product SANDISK SDDR05 0x0005 ImageMate SDDR-05 +product SANDISK SDDR12 0x0100 ImageMate SDDR-12 +product SANDISK SDDR09 0x0200 ImageMate SDDR-09 +product SANDISK SDDR75 0x0810 ImageMate SDDR-75 +product SANDISK SDCZ2_256 0x7104 Cruzer Mini 256MB +product SANDISK SDCZ4_128 0x7112 Cruzer Micro 128MB +product SANDISK SDCZ4_256 0x7113 Cruzer Micro 256MB + +/* Sanyo Electric products */ +product SANYO SCP4900 0x0701 Sanyo SCP-4900 USB Phone + +/* ScanLogic products */ +product SCANLOGIC SL11R 0x0002 SL11R IDE Adapter +product SCANLOGIC 336CX 0x0300 Phantom 336CX - C3 scanner + +/* Senao products */ +product SENAO NUB8301 0x2000 NUB-8301 + +/* ShanTou products */ +product SHANTOU ST268 0x0268 ST268 +product SHANTOU DM9601 0x9601 DM 9601 + +/* Shark products */ +product SHARK PA 0x0400 Pocket Adapter + +/* Sharp products */ +product SHARP SL5500 0x8004 Zaurus SL-5500 PDA +product SHARP SLA300 0x8005 Zaurus SL-A300 PDA +product SHARP SL5600 0x8006 Zaurus SL-5600 PDA +product SHARP SLC700 0x8007 Zaurus SL-C700 PDA +product SHARP SLC750 0x9031 Zaurus SL-C750 PDA +product SHARP WZERO3ES 0x9123 W-ZERO3 ES Smartphone + +/* Shuttle Technology products */ +product SHUTTLE EUSB 0x0001 E-USB Bridge +product SHUTTLE EUSCSI 0x0002 eUSCSI Bridge +product SHUTTLE SDDR09 0x0003 ImageMate SDDR09 +product SHUTTLE EUSBCFSM 0x0005 eUSB SmartMedia / CompactFlash Adapter +product SHUTTLE ZIOMMC 0x0006 eUSB MultiMediaCard Adapter +product SHUTTLE HIFD 0x0007 Sony Hifd +product SHUTTLE EUSBATAPI 0x0009 eUSB ATA/ATAPI Adapter +product SHUTTLE CF 0x000a eUSB CompactFlash Adapter +product SHUTTLE EUSCSI_B 0x000b eUSCSI Bridge +product SHUTTLE EUSCSI_C 0x000c eUSCSI Bridge +product SHUTTLE CDRW 0x0101 CD-RW Device +product SHUTTLE EUSBORCA 0x0325 eUSB ORCA Quad Reader + +/* Siemens products */ +product SIEMENS SPEEDSTREAM 0x1001 SpeedStream +product SIEMENS SPEEDSTREAM22 0x1022 SpeedStream 1022 +product SIEMENS2 WLL013 0x001b WLL013 +product SIEMENS2 ES75 0x0034 GSM module MC35 +product SIEMENS2 WL54G 0x3c06 54g USB Network Adapter +product SIEMENS3 SX1 0x0001 SX1 +product SIEMENS3 X65 0x0003 X65 +product SIEMENS3 X75 0x0004 X75 + +/* Sierra Wireless products */ +product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580 +product SIERRA AIRCARD595 0x0019 Sierra Wireless AirCard 595 +product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U +product SIERRA AC597E 0x0021 Sierra Wireless AirCard 597E +product SIERRA C597 0x0023 Sierra Wireless Compass 597 +product SIERRA AC880 0x6850 Sierra Wireless AirCard 880 +product SIERRA AC881 0x6851 Sierra Wireless AirCard 881 +product SIERRA AC880E 0x6852 Sierra Wireless AirCard 880E +product SIERRA AC881E 0x6853 Sierra Wireless AirCard 881E +product SIERRA AC880U 0x6855 Sierra Wireless AirCard 880U +product SIERRA AC881U 0x6856 Sierra Wireless AirCard 881U +product SIERRA EM5625 0x0017 EM5625 +product SIERRA MC5720 0x0218 MC5720 Wireless Modem +product SIERRA MC5720_2 0x0018 MC5720 +product SIERRA MC5725 0x0020 MC5725 +product SIERRA MINI5725 0x0220 Sierra Wireless miniPCI 5275 +product SIERRA MC8755_2 0x6802 MC8755 +product SIERRA MC8765 0x6803 MC8765 +product SIERRA MC8755 0x6804 MC8755 +product SIERRA AC875U 0x6812 AC875U HSDPA USB Modem +product SIERRA MC8755_3 0x6813 MC8755 HSDPA +product SIERRA MC8775_2 0x6815 MC8775 +product SIERRA AIRCARD875 0x6820 Aircard 875 HSDPA +product SIERRA MC8780 0x6832 MC8780 +product SIERRA MC8781 0x6833 MC8781 +product SIERRA TRUINSTALL 0x0fff Aircard Tru Installer + +/* Sigmatel products */ +product SIGMATEL I_BEAD100 0x8008 i-Bead 100 MP3 Player + +/* SIIG products */ +/* Also: Omnidirectional Control Technology products */ +product SIIG DIGIFILMREADER 0x0004 DigiFilm-Combo Reader +product SIIG WINTERREADER 0x0330 WINTERREADER Reader +product SIIG2 USBTOETHER 0x0109 USB TO Ethernet +product SIIG2 US2308 0x0421 Serial + +/* Silicom products */ +product SILICOM U2E 0x0001 U2E +product SILICOM GPE 0x0002 Psion Gold Port Ethernet + +/* SI Labs */ +product SILABS POLOLU 0x803b Pololu Serial +product SILABS ARGUSISP 0x8066 Argussoft ISP +product SILABS CRUMB128 0x807a Crumb128 board +product SILABS DEGREE 0x80ca Degree Controls Inc +product SILABS TRAQMATE 0x80ed Track Systems Traqmate +product SILABS SUUNTO 0x80f6 Suunto Sports Instrument +product SILABS BURNSIDE 0x813d Burnside Telecon Deskmobile +product SILABS HELICOM 0x815e Helicomm IP-Link 1220-DVM +product SILABS CP2102 0xea60 SILABS USB UART +product SILABS LIPOWSKY_JTAG 0x81c8 Lipowsky Baby-JTAG +product SILABS LIPOWSKY_LIN 0x81e2 Lipowsky Baby-LIN +product SILABS LIPOWSKY_HARP 0x8218 Lipowsky HARP-1 +product SILABS CP2102 0xea60 SILABS USB UARTa +product SILABS CP210X_2 0xea61 CP210x Serial +product SILABS2 DCU11CLONE 0xaa26 DCU-11 clone + +/* Silicon Portals Inc. */ +product SILICONPORTALS YAPPH_NF 0x0200 YAP Phone (no firmware) +product SILICONPORTALS YAPPHONE 0x0201 YAP Phone + +/* Sirius Technologies products */ +product SIRIUS ROADSTER 0x0001 NetComm Roadster II 56 USB + +/* Sitecom products */ +product SITECOM LN029 0x182d USB 2.0 Ethernet +product SITECOM SERIAL 0x2068 USB to serial cable (v2) +product SITECOM2 WL022 0x182d WL-022 + +/* Sitecom Europe products */ +product SITECOMEU LN028 0x061c LN-028 +product SITECOMEU WL113 0x9071 WL-113 +product SITECOMEU ZD1211B 0x9075 ZD1211B +product SITECOMEU WL172 0x90ac WL-172 +product SITECOMEU WL113R2 0x9712 WL-113 rev 2 + +/* Skanhex Technology products */ +product SKANHEX MD_7425 0x410a MD 7425 Camera +product SKANHEX SX_520Z 0x5200 SX 520z Camera + +/* SmartBridges products */ +product SMARTBRIDGES SMARTLINK 0x0001 SmartLink USB Ethernet +product SMARTBRIDGES SMARTNIC 0x0003 smartNIC 2 PnP Ethernet + +/* SMC products */ +product SMC 2102USB 0x0100 10Mbps Ethernet +product SMC 2202USB 0x0200 10/100 Ethernet +product SMC 2206USB 0x0201 EZ Connect USB Ethernet +product SMC 2862WG 0xee13 EZ Connect Wireless Adapter +product SMC2 2020HUB 0x2020 USB Hub +product SMC3 2662WUSB 0xa002 2662W-AR Wireless + +/* SOHOware products */ +product SOHOWARE NUB100 0x9100 10/100 USB Ethernet +product SOHOWARE NUB110 0x9110 10/100 USB Ethernet + +/* SOLID YEAR products */ +product SOLIDYEAR KEYBOARD 0x2101 Solid Year USB keyboard + +/* SONY products */ +product SONY DSC 0x0010 DSC cameras +product SONY MS_NW_MS7 0x0025 Memorystick NW-MS7 +product SONY PORTABLE_HDD_V2 0x002b Portable USB Harddrive V2 +product SONY MSACUS1 0x002d Memorystick MSAC-US1 +product SONY HANDYCAM 0x002e Handycam +product SONY MSC 0x0032 MSC memory stick slot +product SONY CLIE_35 0x0038 Sony Clie v3.5 +product SONY MS_PEG_N760C 0x0058 PEG N760c Memorystick +product SONY CLIE_40 0x0066 Sony Clie v4.0 +product SONY MS_MSC_U03 0x0069 Memorystick MSC-U03 +product SONY CLIE_40_MS 0x006d Sony Clie v4.0 Memory Stick slot +product SONY CLIE_S360 0x0095 Sony Clie s360 +product SONY CLIE_41_MS 0x0099 Sony Clie v4.1 Memory Stick slot +product SONY CLIE_41 0x009a Sony Clie v4.1 +product SONY CLIE_NX60 0x00da Sony Clie nx60 +product SONY CLIE_TH55 0x0144 Sony Clie th55 +product SONY CLIE_TJ37 0x0169 Sony Clie tj37 + +/* Sony Ericsson products */ +product SONYERICSSON DCU10 0x0528 USB Cable + +/* SOURCENEXT products */ +product SOURCENEXT KEIKAI8 0x039f KeikaiDenwa 8 +product SOURCENEXT KEIKAI8_CHG 0x012e KeikaiDenwa 8 with charger + +/* SparkLAN products */ +product SPARKLAN RT2573 0x0004 RT2573 + +/* Sphairon Access Systems GmbH products */ +product SPHAIRON UB801R 0x0110 UB801R + +/* STMicroelectronics products */ +product STMICRO BIOCPU 0x2016 Biometric Coprocessor +product STMICRO COMMUNICATOR 0x7554 USB Communicator + +/* STSN products */ +product STSN STSN0001 0x0001 Internet Access Device + +/* SUN Corporation products */ +product SUNTAC DS96L 0x0003 SUNTAC U-Cable type D2 +product SUNTAC PS64P1 0x0005 SUNTAC U-Cable type P1 +product SUNTAC VS10U 0x0009 SUNTAC Slipper U +product SUNTAC IS96U 0x000a SUNTAC Ir-Trinity +product SUNTAC AS64LX 0x000b SUNTAC U-Cable type A3 +product SUNTAC AS144L4 0x0011 SUNTAC U-Cable type A4 + +/* Sun Microsystems products */ +product SUN KEYBOARD 0x0005 Type 6 USB keyboard +/* XXX The above is a North American PC style keyboard possibly */ +product SUN MOUSE 0x0100 Type 6 USB mouse + +/* Supra products */ +product DIAMOND2 SUPRAEXPRESS56K 0x07da Supra Express 56K modem +product DIAMOND2 SUPRA2890 0x0b4a SupraMax 2890 56K Modem +product DIAMOND2 RIO600USB 0x5001 Rio 600 USB +product DIAMOND2 RIO800USB 0x5002 Rio 800 USB + +/* Surecom Technology products */ +product SURECOM RT2570 0x11f3 RT2570 +product SURECOM RT2573 0x31f3 RT2573 + +/* Sweex products */ +product SWEEX ZD1211 0x1809 ZD1211 + +/* System TALKS, Inc. */ +product SYSTEMTALKS SGCX2UL 0x1920 SGC-X2UL + +/* Tapwave products */ +product TAPWAVE ZODIAC 0x0100 Zodiac + +/* Taugagreining products */ +product TAUGA CAMERAMATE 0x0005 CameraMate (DPCM_USB) + +/* TDK products */ +product TDK UPA9664 0x0115 USB-PDC Adapter UPA9664 +product TDK UCA1464 0x0116 USB-cdmaOne Adapter UCA1464 +product TDK UHA6400 0x0117 USB-PHS Adapter UHA6400 +product TDK UPA6400 0x0118 USB-PHS Adapter UPA6400 +product TDK BT_DONGLE 0x0309 Bluetooth USB dongle + +/* TEAC products */ +product TEAC FD05PUB 0x0000 FD-05PUB floppy + +/* Tekram Technology products */ +product TEKRAM QUICKWLAN 0x1630 QuickWLAN +product TEKRAM ZD1211_1 0x5630 ZD1211 +product TEKRAM ZD1211_2 0x6630 ZD1211 + +/* Telex Communications products */ +product TELEX MIC1 0x0001 Enhanced USB Microphone + +/* Ten X Technology, Inc. */ +product TENX UAUDIO0 0xf211 USB audio headset + +/* Texas Intel products */ +product TI UTUSB41 0x1446 UT-USB41 hub +product TI TUSB2046 0x2046 TUSB2046 hub + +/* Thrustmaster products */ +product THRUST FUSION_PAD 0xa0a3 Fusion Digital Gamepad + +/* Topre Corporation products */ +product TOPRE HHKB 0x0100 HHKB Professional + +/* Toshiba Corporation products */ +product TOSHIBA POCKETPC_E740 0x0706 PocketPC e740 + +/* Trek Technology products */ +product TREK THUMBDRIVE 0x1111 ThumbDrive +product TREK MEMKEY 0x8888 IBM USB Memory Key +product TREK THUMBDRIVE_8MB 0x9988 ThumbDrive_8MB + +/* Tripp-Lite products */ +product TRIPPLITE U209 0x2008 Serial + +/* Trumpion products */ +product TRUMPION T33520 0x1001 T33520 USB Flash Card Controller +product TRUMPION C3310 0x1100 Comotron C3310 MP3 player +product TRUMPION MP3 0x1200 MP3 player + +/* TwinMOS */ +product TWINMOS G240 0xa006 G240 +product TWINMOS MDIV 0x1325 Memory Disk IV + +/* Ubiquam products */ +product UBIQUAM UALL 0x3100 CDMA 1xRTT USB Modem (U-100/105/200/300/520) + +/* Ultima products */ +product ULTIMA 1200UBPLUS 0x4002 1200 UB Plus scanner + +/* UMAX products */ +product UMAX ASTRA1236U 0x0002 Astra 1236U Scanner +product UMAX ASTRA1220U 0x0010 Astra 1220U Scanner +product UMAX ASTRA2000U 0x0030 Astra 2000U Scanner +product UMAX ASTRA2100U 0x0130 Astra 2100U Scanner +product UMAX ASTRA2200U 0x0230 Astra 2200U Scanner +product UMAX ASTRA3400 0x0060 Astra 3400 Scanner + +/* U-MEDIA Communications products */ +product UMEDIA TEW444UBEU 0x3006 TEW-444UB EU +product UMEDIA TEW444UBEU_NF 0x3007 TEW-444UB EU (no firmware) +product UMEDIA TEW429UB_A 0x300a TEW-429UB_A +product UMEDIA TEW429UB 0x300b TEW-429UB +product UMEDIA TEW429UBC1 0x300d TEW-429UB C1 +product UMEDIA ALL0298V2 0x3204 ALL0298 v2 +product UMEDIA AR5523_2 0x3205 AR5523 +product UMEDIA AR5523_2_NF 0x3206 AR5523 (no firmware) + +/* Universal Access products */ +product UNIACCESS PANACHE 0x0101 Panache Surf USB ISDN Adapter + +/* U.S. Robotics products */ +product USR USR5423 0x0121 USR5423 WLAN + +/* VIA Technologies products */ +product VIA USB2IDEBRIDGE 0x6204 USB 2.0 IDE Bridge + +/* USI products */ +product USI MC60 0x10c5 MC60 Serial + +/* VidzMedia products */ +product VIDZMEDIA MONSTERTV 0x4fb1 MonsterTV P2H + +/* Vision products */ +product VISION VC6452V002 0x0002 CPiA Camera + +/* Visioneer products */ +product VISIONEER 7600 0x0211 OneTouch 7600 +product VISIONEER 5300 0x0221 OneTouch 5300 +product VISIONEER 3000 0x0224 Scanport 3000 +product VISIONEER 6100 0x0231 OneTouch 6100 +product VISIONEER 6200 0x0311 OneTouch 6200 +product VISIONEER 8100 0x0321 OneTouch 8100 +product VISIONEER 8600 0x0331 OneTouch 8600 + +/* Vivitar products */ +product VIVITAR 35XX 0x0003 Vivicam 35Xx + +/* VTech products */ +product VTECH RT2570 0x3012 RT2570 +product VTECH ZD1211B 0x3014 ZD1211B + +/* Wacom products */ +product WACOM CT0405U 0x0000 CT-0405-U Tablet +product WACOM GRAPHIRE 0x0010 Graphire +product WACOM GRAPHIRE3_4X5 0x0013 Graphire 3 4x5 +product WACOM INTUOSA5 0x0021 Intuos A5 +product WACOM GD0912U 0x0022 Intuos 9x12 Graphics Tablet +/* WCH products*/ +product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge +/* Western Digital products */ +product WESTERN COMBO 0x0200 Firewire USB Combo +product WESTERN EXTHDD 0x0400 External HDD +product WESTERN HUB 0x0500 USB HUB +product WESTERN MYBOOK 0x0901 MyBook External HDD + +/* Windbond Electronics */ +product WINBOND UH104 0x5518 4-port USB Hub + +/* WinMaxGroup products */ +product WINMAXGROUP FLASH64MC 0x6660 USB Flash Disk 64M-C + +/* Wistron NeWeb products */ +product WISTRONNEWEB UR045G 0x0427 PrismGT USB 2.0 WLAN +product WISTRONNEWEB UR055G 0x0711 UR055G +product WISTRONNEWEB AR5523_1 0x0826 AR5523 +product WISTRONNEWEB AR5523_1_NF 0x0827 AR5523 (no firmware) +product WISTRONNEWEB AR5523_2 0x082a AR5523 +product WISTRONNEWEB AR5523_2_NF 0x0829 AR5523 (no firmware) + +/* Xerox products */ +product XEROX WCM15 0xffef WorkCenter M15 + +/* Xirlink products */ +product XIRLINK PCCAM 0x8080 IBM PC Camera + +/* Xyratex products */ +product XYRATEX PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product XYRATEX PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* Y-E Data products */ +product YEDATA FLASHBUSTERU 0x0000 Flashbuster-U + +/* Yamaha products */ +product YAMAHA UX256 0x1000 UX256 MIDI I/F +product YAMAHA UX96 0x1008 UX96 MIDI I/F +product YAMAHA RTA54I 0x4000 NetVolante RTA54i Broadband&ISDN Router +product YAMAHA RTA55I 0x4004 NetVolante RTA55i Broadband VoIP Router +product YAMAHA RTW65B 0x4001 NetVolante RTW65b Broadband Wireless Router +product YAMAHA RTW65I 0x4002 NetVolante RTW65i Broadband&ISDN Wireless Router + +/* Yano products */ +product YANO U640MO 0x0101 U640MO-03 +product YANO FW800HD 0x05fc METALWEAR-HDD + +/* Z-Com products */ +product ZCOM M4Y750 0x0001 M4Y-750 +product ZCOM XI725 0x0002 XI-725/726 +product ZCOM XI735 0x0005 XI-735 +product ZCOM XG703A 0x0008 PrismGT USB 2.0 WLAN +product ZCOM ZD1211 0x0011 ZD1211 +product ZCOM AR5523 0x0012 AR5523 +product ZCOM AR5523_NF 0x0013 AR5523 driver (no firmware) +product ZCOM ZD1211B 0x001a ZD1211B + +/* Zinwell products */ +product ZINWELL RT2570 0x0260 RT2570 + +/* Zoom Telephonics, Inc. products */ +product ZOOM 2986L 0x9700 2986L Fax modem + +/* Zoran Microelectronics products */ +product ZORAN EX20DSC 0x4343 Digital Camera EX-20 DSC + +/* Zydas Technology Corporation products */ +product ZYDAS ZD1211 0x1211 ZD1211 WLAN abg +product ZYDAS ZD1211B 0x1215 ZD1211B + +/* ZyXEL Communication Co. products */ +product ZYXEL OMNI56K 0x1500 Omni 56K Plus +product ZYXEL 980N 0x2011 Scorpion-980N keyboard +product ZYXEL ZYAIRG220 0x3401 ZyAIR G-220 +product ZYXEL G200V2 0x3407 G-200 v2 +product ZYXEL AG225H 0x3409 AG-225H +product ZYXEL M202 0x340a M-202 +product ZYXEL G220V2 0x340f G-220 v2 +product ZYXEL G202 0x3410 G-202 |
