diff options
Diffstat (limited to 'drivers/usb/core/message.c')
| -rw-r--r-- | drivers/usb/core/message.c | 913 |
1 files changed, 691 insertions, 222 deletions
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index e7ee1e451660..6138468c67c4 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1,20 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 /* * message.c - synchronous message handling + * + * Released under the GPLv2 only. */ +#include <linux/acpi.h> #include <linux/pci.h> /* for scatterlist macros */ #include <linux/usb.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h> -#include <linux/init.h> #include <linux/mm.h> #include <linux/timer.h> #include <linux/ctype.h> #include <linux/nls.h> #include <linux/device.h> #include <linux/scatterlist.h> +#include <linux/usb/cdc.h> #include <linux/usb/quirks.h> #include <linux/usb/hcd.h> /* for usbcore internals */ +#include <linux/usb/of.h> #include <asm/byteorder.h> #include "usb.h" @@ -114,20 +120,19 @@ static int usb_internal_control_msg(struct usb_device *usb_dev, * @timeout: time in msecs to wait for the message to complete before timing * out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple control message to a specified endpoint and * waits for the message to complete, or timeout. * - * If successful, it returns the number of bytes transferred, otherwise a - * negative error number. + * Don't use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb(). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. * - * Don't use this function from within an interrupt context, like a bottom half - * handler. If you need an asynchronous message, or need to send a message - * from within interrupt context, use usb_submit_urb(). - * If a thread in your driver uses this call, make sure your disconnect() - * method can wait for it to complete. Since you don't have a handle on the - * URB used, you can't cancel the request. + * Return: If successful, the number of bytes transferred. Otherwise, a negative + * error number. */ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, @@ -148,6 +153,10 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); + /* Linger a bit, prior to the next control message. */ + if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) + msleep(200); + kfree(dr); return ret; @@ -155,6 +164,139 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, EXPORT_SYMBOL_GPL(usb_control_msg); /** + * usb_control_msg_send - Builds a control "send" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint to send the message to + * @request: USB message request value + * @requesttype: USB message request type value + * @value: USB message value + * @index: USB message index value + * @driver_data: pointer to the data to send + * @size: length in bytes of the data to send + * @timeout: time in msecs to wait for the message to complete before timing + * out (if 0 the wait is forever) + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is not + * expected to fill in a response (i.e. a "send message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb(). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. + * + * The data pointer can be made to a reference on the stack, or anywhere else, + * as it will not be modified at all. This does not have the restriction that + * usb_control_msg() has where the data pointer must be to dynamically allocated + * memory (i.e. memory that can be successfully DMAed to a device). + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + const void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_sndctrlpipe(dev, endpoint); + int ret; + u8 *data = NULL; + + if (size) { + data = kmemdup(driver_data, size, memflags); + if (!data) + return -ENOMEM; + } + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + kfree(data); + + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_control_msg_send); + +/** + * usb_control_msg_recv - Builds a control "receive" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint to send the message to + * @request: USB message request value + * @requesttype: USB message request type value + * @value: USB message value + * @index: USB message index value + * @driver_data: pointer to the data to be filled in by the message + * @size: length in bytes of the data to be received + * @timeout: time in msecs to wait for the message to complete before timing + * out (if 0 the wait is forever) + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is + * expected to fill in a response (i.e. a "receive message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb(). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. + * + * The data pointer can be made to a reference on the stack, or anywhere else + * that can be successfully written to. This function does not have the + * restriction that usb_control_msg() has where the data pointer must be to + * dynamically allocated memory (i.e. memory that can be successfully DMAed to a + * device). + * + * The "whole" message must be properly received from the device in order for + * this function to be successful. If a device returns less than the expected + * amount of data, then the function will fail. Do not use this for messages + * where a variable amount of data might be returned. + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_rcvctrlpipe(dev, endpoint); + int ret; + u8 *data; + + if (!size || !driver_data) + return -EINVAL; + + data = kmalloc(size, memflags); + if (!data) + return -ENOMEM; + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + + if (ret < 0) + goto exit; + + if (ret == size) { + memcpy(driver_data, data, size); + ret = 0; + } else { + ret = -EREMOTEIO; + } + +exit: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(usb_control_msg_recv); + +/** * usb_interrupt_msg - Builds an interrupt urb, sends it off and waits for completion * @usb_dev: pointer to the usb device to send the message to * @pipe: endpoint "pipe" to send the message to @@ -165,20 +307,20 @@ EXPORT_SYMBOL_GPL(usb_control_msg); * @timeout: time in msecs to wait for the message to complete before * timing out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple interrupt message to a specified endpoint and * waits for the message to complete, or timeout. * - * If successful, it returns 0, otherwise a negative error number. The number - * of actual bytes transferred will be stored in the actual_length paramater. + * Don't use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb() If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. * - * Don't use this function from within an interrupt context, like a bottom half - * handler. If you need an asynchronous message, or need to send a message - * from within interrupt context, use usb_submit_urb() If a thread in your - * driver uses this call, make sure your disconnect() method can wait for it to - * complete. Since you don't have a handle on the URB used, you can't cancel - * the request. + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. */ int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout) @@ -198,25 +340,26 @@ EXPORT_SYMBOL_GPL(usb_interrupt_msg); * @timeout: time in msecs to wait for the message to complete before * timing out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple bulk message to a specified endpoint * and waits for the message to complete, or timeout. * - * If successful, it returns 0, otherwise a negative error number. The number - * of actual bytes transferred will be stored in the actual_length paramater. - * - * Don't use this function from within an interrupt context, like a bottom half - * handler. If you need an asynchronous message, or need to send a message - * from within interrupt context, use usb_submit_urb() If a thread in your - * driver uses this call, make sure your disconnect() method can wait for it to - * complete. Since you don't have a handle on the URB used, you can't cancel - * the request. + * Don't use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb() If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. * * Because there is no usb_interrupt_msg() and no USBDEVFS_INTERRUPT ioctl, * users are forced to abuse this routine by using it to submit URBs for * interrupt endpoints. We will take the liberty of creating an interrupt URB * (with the default interval) if the target is an interrupt endpoint. + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. + * */ int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout) @@ -261,10 +404,11 @@ static void sg_clean(struct usb_sg_request *io) static void sg_complete(struct urb *urb) { + unsigned long flags; struct usb_sg_request *io = urb->context; int status = urb->status; - spin_lock(&io->lock); + spin_lock_irqsave(&io->lock, flags); /* In 2.5 we require hcds' endpoint queues not to progress after fault * reports, until the completion callback (this!) returns. That lets @@ -298,11 +442,12 @@ static void sg_complete(struct urb *urb) * unlink pending urbs so they won't rx/tx bad data. * careful: unlink can sometimes be synchronous... */ - spin_unlock(&io->lock); + spin_unlock_irqrestore(&io->lock, flags); for (i = 0, found = 0; i < io->entries; i++) { - if (!io->urbs[i] || !io->urbs[i]->dev) + if (!io->urbs[i]) continue; if (found) { + usb_block_urb(io->urbs[i]); retval = usb_unlink_urb(io->urbs[i]); if (retval != -EINPROGRESS && retval != -ENODEV && @@ -314,7 +459,7 @@ static void sg_complete(struct urb *urb) } else if (urb == io->urbs[i]) found = 1; } - spin_lock(&io->lock); + spin_lock_irqsave(&io->lock, flags); } /* on the last completion, signal usb_sg_wait() */ @@ -323,7 +468,7 @@ static void sg_complete(struct urb *urb) if (!io->count) complete(&io->complete); - spin_unlock(&io->lock); + spin_unlock_irqrestore(&io->lock, flags); } @@ -341,9 +486,9 @@ static void sg_complete(struct urb *urb) * send every byte identified in the list. * @mem_flags: SLAB_* flags affecting memory allocations in this call * - * Returns zero for success, else a negative errno value. This initializes a - * scatter/gather request, allocating resources such as I/O mappings and urb - * memory (except maybe memory used by USB controller drivers). + * This initializes a scatter/gather request, allocating resources such as + * I/O mappings and urb memory (except maybe memory used by USB controller + * drivers). * * The request must be issued using usb_sg_wait(), which waits for the I/O to * complete (or to be canceled) and then cleans up all resources allocated by @@ -351,6 +496,8 @@ static void sg_complete(struct urb *urb) * * The request may be canceled with usb_sg_cancel(), either before or after * usb_sg_wait() is called. + * + * Return: Zero for success, else a negative errno value. */ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, unsigned pipe, unsigned period, struct scatterlist *sg, @@ -379,7 +526,7 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, } /* initialize all the urbs we'll use */ - io->urbs = kmalloc(io->entries * sizeof(*io->urbs), mem_flags); + io->urbs = kmalloc_array(io->entries, sizeof(*io->urbs), mem_flags); if (!io->urbs) goto nomem; @@ -460,7 +607,8 @@ EXPORT_SYMBOL_GPL(usb_sg_init); * usb_sg_wait - synchronously execute scatter/gather request * @io: request block handle, as initialized with usb_sg_init(). * some fields become accessible when this call returns. - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This function blocks until the specified I/O operation completes. It * leverages the grouping of the related I/O requests to get good transfer @@ -468,6 +616,7 @@ EXPORT_SYMBOL_GPL(usb_sg_init); * significantly improve USB throughput. * * There are three kinds of completion for this function. + * * (1) success, where io->status is zero. The number of io->bytes * transferred is as requested. * (2) error, where io->status is a negative errno value. The number @@ -511,12 +660,10 @@ void usb_sg_wait(struct usb_sg_request *io) int retval; io->urbs[i]->dev = io->dev; - retval = usb_submit_urb(io->urbs[i], GFP_ATOMIC); - - /* after we submit, let completions or cancelations fire; - * we handshake using io->status. - */ spin_unlock_irq(&io->lock); + + retval = usb_submit_urb(io->urbs[i], GFP_NOIO); + switch (retval) { /* maybe we retrying will recover */ case -ENXIO: /* hc didn't queue this one */ @@ -574,30 +721,34 @@ EXPORT_SYMBOL_GPL(usb_sg_wait); void usb_sg_cancel(struct usb_sg_request *io) { unsigned long flags; + int i, retval; spin_lock_irqsave(&io->lock, flags); + if (io->status || io->count == 0) { + spin_unlock_irqrestore(&io->lock, flags); + return; + } + /* shut everything down */ + io->status = -ECONNRESET; + io->count++; /* Keep the request alive until we're done */ + spin_unlock_irqrestore(&io->lock, flags); - /* shut everything down, if it didn't already */ - if (!io->status) { - int i; - - io->status = -ECONNRESET; - spin_unlock(&io->lock); - for (i = 0; i < io->entries; i++) { - int retval; + for (i = io->entries - 1; i >= 0; --i) { + usb_block_urb(io->urbs[i]); - if (!io->urbs[i]->dev) - continue; - retval = usb_unlink_urb(io->urbs[i]); - if (retval != -EINPROGRESS - && retval != -ENODEV - && retval != -EBUSY - && retval != -EIDRM) - dev_warn(&io->dev->dev, "%s, unlink --> %d\n", - __func__, retval); - } - spin_lock(&io->lock); + retval = usb_unlink_urb(io->urbs[i]); + if (retval != -EINPROGRESS + && retval != -ENODEV + && retval != -EBUSY + && retval != -EIDRM) + dev_warn(&io->dev->dev, "%s, unlink --> %d\n", + __func__, retval); } + + spin_lock_irqsave(&io->lock, flags); + io->count--; + if (!io->count) + complete(&io->complete); spin_unlock_irqrestore(&io->lock, flags); } EXPORT_SYMBOL_GPL(usb_sg_cancel); @@ -611,7 +762,8 @@ EXPORT_SYMBOL_GPL(usb_sg_cancel); * @index: the number of the descriptor * @buf: where to put the descriptor * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Gets a USB descriptor. Convenience functions exist to simplify * getting some types of descriptors. Use @@ -623,7 +775,7 @@ EXPORT_SYMBOL_GPL(usb_sg_cancel); * * This call is synchronous, and may not be used in an interrupt context. * - * Returns the number of bytes received on success, or else the status code + * Return: The number of bytes received on success, or else the status code * returned by the underlying usb_control_msg() call. */ int usb_get_descriptor(struct usb_device *dev, unsigned char type, @@ -632,6 +784,9 @@ int usb_get_descriptor(struct usb_device *dev, unsigned char type, int i; int result; + if (size <= 0) /* No point in asking for no data */ + return -EINVAL; + memset(buf, 0, size); /* Make sure we parse really received data */ for (i = 0; i < 3; ++i) { @@ -659,7 +814,8 @@ EXPORT_SYMBOL_GPL(usb_get_descriptor); * @index: the number of the descriptor * @buf: where to put the string * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Retrieves a string, encoded using UTF-16LE (Unicode, 16 bits per character, * in little-endian byte order). @@ -671,7 +827,7 @@ EXPORT_SYMBOL_GPL(usb_get_descriptor); * * This call is synchronous, and may not be used in an interrupt context. * - * Returns the number of bytes received on success, or else the status code + * Return: The number of bytes received on success, or else the status code * returned by the underlying usb_control_msg() call. */ static int usb_get_string(struct usb_device *dev, unsigned short langid, @@ -680,6 +836,9 @@ static int usb_get_string(struct usb_device *dev, unsigned short langid, int i; int result; + if (size <= 0) /* No point in asking for no data */ + return -EINVAL; + for (i = 0; i < 3; ++i) { /* retry on length 0 or stall; some devices are flakey */ result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), @@ -766,9 +925,7 @@ static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) dev->string_langid = 0x0409; dev->have_langid = 1; dev_err(&dev->dev, - "string descriptor 0 malformed (err = %d), " - "defaulting to 0x%04x\n", - err, dev->string_langid); + "language id specifier not provided by device, defaulting to English\n"); return 0; } @@ -776,7 +933,7 @@ static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) * deal with strings at all. Set string_langid to -1 in order to * prevent any string to be retrieved from the device */ if (err < 0) { - dev_err(&dev->dev, "string descriptor 0 read error: %d\n", + dev_info(&dev->dev, "string descriptor 0 read error: %d\n", err); dev->string_langid = -1; return -EPIPE; @@ -796,7 +953,8 @@ static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) * @index: the number of the descriptor * @buf: where to put the string * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This converts the UTF-16LE encoded strings returned by devices, from * usb_get_string_descriptor(), to null-terminated UTF-8 encoded ones @@ -805,7 +963,7 @@ static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) * * This call is synchronous, and may not be used in an interrupt context. * - * Returns length of the string (>= 0) or usb_control_msg status (< 0). + * Return: length of the string (>= 0) or usb_control_msg status (< 0). */ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) { @@ -814,9 +972,11 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) if (dev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; - if (size <= 0 || !buf || !index) + if (size <= 0 || !buf) return -EINVAL; buf[0] = 0; + if (index <= 0 || index >= 256) + return -EINVAL; tbuf = kmalloc(256, GFP_NOIO); if (!tbuf) return -ENOMEM; @@ -853,8 +1013,8 @@ EXPORT_SYMBOL_GPL(usb_string); * @udev: the device whose string descriptor is being read * @index: the descriptor index * - * Returns a pointer to a kmalloc'ed buffer containing the descriptor string, - * or NULL if the index is 0 or the string could not be read. + * Return: A pointer to a kmalloc'ed buffer containing the descriptor string, + * or %NULL if the index is 0 or the string could not be read. */ char *usb_cache_string(struct usb_device *udev, int index) { @@ -878,50 +1038,74 @@ char *usb_cache_string(struct usb_device *udev, int index) } return smallbuf; } +EXPORT_SYMBOL_GPL(usb_cache_string); /* - * usb_get_device_descriptor - (re)reads the device descriptor (usbcore) - * @dev: the device whose device descriptor is being updated - * @size: how much of the descriptor to read - * Context: !in_interrupt () + * usb_get_device_descriptor - read the device descriptor + * @udev: the device whose device descriptor should be read * - * Updates the copy of the device descriptor stored in the device structure, - * which dedicates space for this purpose. + * Context: task context, might sleep. * * Not exported, only for use by the core. If drivers really want to read * the device descriptor directly, they can call usb_get_descriptor() with * type = USB_DT_DEVICE and index = 0. * - * This call is synchronous, and may not be used in an interrupt context. - * - * Returns the number of bytes received on success, or else the status code - * returned by the underlying usb_control_msg() call. + * Returns: a pointer to a dynamically allocated usb_device_descriptor + * structure (which the caller must deallocate), or an ERR_PTR value. */ -int usb_get_device_descriptor(struct usb_device *dev, unsigned int size) +struct usb_device_descriptor *usb_get_device_descriptor(struct usb_device *udev) { struct usb_device_descriptor *desc; int ret; - if (size > sizeof(*desc)) - return -EINVAL; desc = kmalloc(sizeof(*desc), GFP_NOIO); if (!desc) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + ret = usb_get_descriptor(udev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); + if (ret == sizeof(*desc)) + return desc; - ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size); if (ret >= 0) - memcpy(&dev->descriptor, desc, size); + ret = -EMSGSIZE; kfree(desc); - return ret; + return ERR_PTR(ret); +} + +/* + * usb_set_isoch_delay - informs the device of the packet transmit delay + * @dev: the device whose delay is to be informed + * Context: task context, might sleep + * + * Since this is an optional request, we don't bother if it fails. + */ +int usb_set_isoch_delay(struct usb_device *dev) +{ + /* skip hub devices */ + if (dev->descriptor.bDeviceClass == USB_CLASS_HUB) + return 0; + + /* skip non-SS/non-SSP devices */ + if (dev->speed < USB_SPEED_SUPER) + return 0; + + return usb_control_msg_send(dev, 0, + USB_REQ_SET_ISOCH_DELAY, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + dev->hub_delay, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT, + GFP_NOIO); } /** * usb_get_status - issues a GET_STATUS call * @dev: the device whose status is being checked - * @type: USB_RECIP_*; for device, interface, or endpoint + * @recip: USB_RECIP_*; for device, interface, or endpoint + * @type: USB_STATUS_TYPE_*; for standard or PTM status types * @target: zero (for device), else interface or endpoint number * @data: pointer to two bytes of bitmap data - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Returns device, interface, or endpoint status. Normally only of * interest to see if the device is self powered, or has enabled the @@ -934,22 +1118,61 @@ int usb_get_device_descriptor(struct usb_device *dev, unsigned int size) * * This call is synchronous, and may not be used in an interrupt context. * - * Returns the number of bytes received on success, or else the status code - * returned by the underlying usb_control_msg() call. + * Returns 0 and the status value in *@data (in host byte order) on success, + * or else the status code from the underlying usb_control_msg() call. */ -int usb_get_status(struct usb_device *dev, int type, int target, void *data) +int usb_get_status(struct usb_device *dev, int recip, int type, int target, + void *data) { int ret; - u16 *status = kmalloc(sizeof(*status), GFP_KERNEL); + void *status; + int length; + switch (type) { + case USB_STATUS_TYPE_STANDARD: + length = 2; + break; + case USB_STATUS_TYPE_PTM: + if (recip != USB_RECIP_DEVICE) + return -EINVAL; + + length = 4; + break; + default: + return -EINVAL; + } + + status = kmalloc(length, GFP_KERNEL); if (!status) return -ENOMEM; ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, status, - sizeof(*status), USB_CTRL_GET_TIMEOUT); + USB_REQ_GET_STATUS, USB_DIR_IN | recip, USB_STATUS_TYPE_STANDARD, + target, status, length, USB_CTRL_GET_TIMEOUT); + + switch (ret) { + case 4: + if (type != USB_STATUS_TYPE_PTM) { + ret = -EIO; + break; + } + + *(u32 *) data = le32_to_cpu(*(__le32 *) status); + ret = 0; + break; + case 2: + if (type != USB_STATUS_TYPE_STANDARD) { + ret = -EIO; + break; + } + + *(u16 *) data = le16_to_cpu(*(__le16 *) status); + ret = 0; + break; + default: + ret = -EIO; + } - *(u16 *)data = *status; kfree(status); return ret; } @@ -959,7 +1182,8 @@ EXPORT_SYMBOL_GPL(usb_get_status); * usb_clear_halt - tells device to clear endpoint halt/stall condition * @dev: device whose endpoint is halted * @pipe: endpoint "pipe" being cleared - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This is used to clear halt conditions for bulk and interrupt endpoints, * as reported by URB completion status. Endpoints that are halted are @@ -974,8 +1198,10 @@ EXPORT_SYMBOL_GPL(usb_get_status); * same status code used to report a true stall. * * This call is synchronous, and may not be used in an interrupt context. + * If a thread in your driver uses this call, make sure your disconnect() + * method can wait for it to complete. * - * Returns zero on success, or else the status code returned by the + * Return: Zero on success, or else the status code returned by the * underlying usb_control_msg() call. */ int usb_clear_halt(struct usb_device *dev, int pipe) @@ -990,13 +1216,13 @@ int usb_clear_halt(struct usb_device *dev, int pipe) * (like some ibmcam model 1 units) seem to expect hosts to make * this request for iso endpoints, which can't halt! */ - result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, - USB_ENDPOINT_HALT, endp, NULL, 0, - USB_CTRL_SET_TIMEOUT); + result = usb_control_msg_send(dev, 0, + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); /* don't un-halt or force to DATA0 except on success */ - if (result < 0) + if (result) return result; /* NOTE: seems like Microsoft and Apple don't bother verifying @@ -1064,11 +1290,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, if (usb_endpoint_out(epaddr)) { ep = dev->ep_out[epnum]; - if (reset_hardware) + if (reset_hardware && epnum != 0) dev->ep_out[epnum] = NULL; } else { ep = dev->ep_in[epnum]; - if (reset_hardware) + if (reset_hardware && epnum != 0) dev->ep_in[epnum] = NULL; } if (ep) { @@ -1125,6 +1351,34 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf, } } +/* + * usb_disable_device_endpoints -- Disable all endpoints for a device + * @dev: the device whose endpoints are being disabled + * @skip_ep0: 0 to disable endpoint 0, 1 to skip it. + */ +static void usb_disable_device_endpoints(struct usb_device *dev, int skip_ep0) +{ + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + int i; + + if (hcd->driver->check_bandwidth) { + /* First pass: Cancel URBs, leave endpoint pointers intact. */ + for (i = skip_ep0; i < 16; ++i) { + usb_disable_endpoint(dev, i, false); + usb_disable_endpoint(dev, i + USB_DIR_IN, false); + } + /* Remove endpoints from the host controller internal state */ + mutex_lock(hcd->bandwidth_mutex); + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + mutex_unlock(hcd->bandwidth_mutex); + } + /* Second pass: remove endpoint pointers */ + for (i = skip_ep0; i < 16; ++i) { + usb_disable_endpoint(dev, i, true); + usb_disable_endpoint(dev, i + USB_DIR_IN, true); + } +} + /** * usb_disable_device - Disable all the endpoints for a USB device * @dev: the device whose endpoints are being disabled @@ -1138,7 +1392,6 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf, void usb_disable_device(struct usb_device *dev, int skip_ep0) { int i; - struct usb_hcd *hcd = bus_to_hcd(dev->bus); /* getting rid of interfaces will disconnect * any drivers bound to them (a key side effect) @@ -1172,8 +1425,11 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0) put_device(&dev->actconfig->interface[i]->dev); dev->actconfig->interface[i] = NULL; } + + usb_disable_usb2_hardware_lpm(dev); usb_unlocked_disable_lpm(dev); usb_disable_ltm(dev); + dev->actconfig = NULL; if (dev->state == USB_STATE_CONFIGURED) usb_set_device_state(dev, USB_STATE_ADDRESS); @@ -1181,22 +1437,8 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0) dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__, skip_ep0 ? "non-ep0" : "all"); - if (hcd->driver->check_bandwidth) { - /* First pass: Cancel URBs, leave endpoint pointers intact. */ - for (i = skip_ep0; i < 16; ++i) { - usb_disable_endpoint(dev, i, false); - usb_disable_endpoint(dev, i + USB_DIR_IN, false); - } - /* Remove endpoints from the host controller internal state */ - mutex_lock(hcd->bandwidth_mutex); - usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); - mutex_unlock(hcd->bandwidth_mutex); - /* Second pass: remove endpoint pointers */ - } - for (i = skip_ep0; i < 16; ++i) { - usb_disable_endpoint(dev, i, true); - usb_disable_endpoint(dev, i + USB_DIR_IN, true); - } + + usb_disable_device_endpoints(dev, skip_ep0); } /** @@ -1247,7 +1489,8 @@ void usb_enable_interface(struct usb_device *dev, * @dev: the device whose interface is being updated * @interface: the interface being updated * @alternate: the setting being chosen. - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This is used to enable data transfers on interfaces that may not * be enabled by default. Not all devices support such configurability. @@ -1267,12 +1510,18 @@ void usb_enable_interface(struct usb_device *dev, * is submitted that needs that bandwidth. Some other operating systems * allocate bandwidth early, when a configuration is chosen. * + * xHCI reserves bandwidth and configures the alternate setting in + * usb_hcd_alloc_bandwidth(). If it fails the original interface altsetting + * may be disabled. Drivers cannot rely on any particular alternate + * setting being in effect after a failure. + * * This call is synchronous, and may not be used in an interrupt context. * Also, drivers must not change altsettings while urbs are scheduled for * endpoints in that interface; all such urbs must first be completed - * (perhaps forced by unlinking). + * (perhaps forced by unlinking). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. * - * Returns zero on success, or else the status code returned by the + * Return: Zero on success, or else the status code returned by the * underlying usb_control_msg() call. */ int usb_set_interface(struct usb_device *dev, int interface, int alternate) @@ -1280,8 +1529,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) struct usb_interface *iface; struct usb_host_interface *alt; struct usb_hcd *hcd = bus_to_hcd(dev->bus); - int ret; - int manual = 0; + int i, ret, manual = 0; unsigned int epaddr; unsigned int pipe; @@ -1303,6 +1551,12 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) alternate); return -EINVAL; } + /* + * usb3 hosts configure the interface in usb_hcd_alloc_bandwidth, + * including freeing dropped endpoint ring buffers. + * Make sure the interface endpoints are flushed before that + */ + usb_disable_interface(dev, iface, false); /* Make sure we have enough bandwidth for this alternate interface. * Remove the current alt setting and add the new alt setting. @@ -1312,10 +1566,14 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) * so that the xHCI driver can recalculate the U1/U2 timeouts. */ if (usb_disable_lpm(dev)) { - dev_err(&iface->dev, "%s Failed to disable LPM\n.", __func__); + dev_err(&iface->dev, "%s Failed to disable LPM\n", __func__); mutex_unlock(hcd->bandwidth_mutex); return -ENOMEM; } + /* Changing alt-setting also frees any allocated streams */ + for (i = 0; i < iface->cur_altsetting->desc.bNumEndpoints; i++) + iface->cur_altsetting->endpoint[i].streams = 0; + ret = usb_hcd_alloc_bandwidth(dev, NULL, iface->cur_altsetting, alt); if (ret < 0) { dev_info(&dev->dev, "Not enough bandwidth for altsetting %d\n", @@ -1328,9 +1586,11 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) if (dev->quirks & USB_QUIRK_NO_SET_INTF) ret = -EPIPE; else - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, - alternate, interface, NULL, 0, 5000); + ret = usb_control_msg_send(dev, 0, + USB_REQ_SET_INTERFACE, + USB_RECIP_INTERFACE, alternate, + interface, NULL, 0, 5000, + GFP_NOIO); /* 9.4.10 says devices don't need this and are free to STALL the * request if the interface only has one alternate setting. @@ -1340,7 +1600,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) "manual set_interface for iface %d, alt %d\n", interface, alternate); manual = 1; - } else if (ret < 0) { + } else if (ret) { /* Re-instate the old alt setting */ usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting); usb_enable_lpm(dev); @@ -1373,8 +1633,6 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) * new altsetting. */ if (manual) { - int i; - for (i = 0; i < alt->desc.bNumEndpoints; i++) { epaddr = alt->endpoint[i].desc.bEndpointAddress; pipe = __create_pipe(dev, @@ -1426,7 +1684,10 @@ EXPORT_SYMBOL_GPL(usb_set_interface); * * The caller must own the device lock. * - * Returns zero on success, else a negative error code. + * Return: Zero on success, else a negative error code. + * + * If this routine fails the device will probably be in an unusable state + * with endpoints disabled, and interfaces only partially enabled. */ int usb_reset_configuration(struct usb_device *dev) { @@ -1442,10 +1703,7 @@ int usb_reset_configuration(struct usb_device *dev) * calls during probe() are fine */ - for (i = 1; i < 16; ++i) { - usb_disable_endpoint(dev, i, true); - usb_disable_endpoint(dev, i + USB_DIR_IN, true); - } + usb_disable_device_endpoints(dev, 1); /* skip ep0*/ config = dev->actconfig; retval = 0; @@ -1454,48 +1712,28 @@ int usb_reset_configuration(struct usb_device *dev) * that the xHCI driver can recalculate the U1/U2 timeouts. */ if (usb_disable_lpm(dev)) { - dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + dev_err(&dev->dev, "%s Failed to disable LPM\n", __func__); mutex_unlock(hcd->bandwidth_mutex); return -ENOMEM; } - /* Make sure we have enough bandwidth for each alternate setting 0 */ - for (i = 0; i < config->desc.bNumInterfaces; i++) { - struct usb_interface *intf = config->interface[i]; - struct usb_host_interface *alt; - alt = usb_altnum_to_altsetting(intf, 0); - if (!alt) - alt = &intf->altsetting[0]; - if (alt != intf->cur_altsetting) - retval = usb_hcd_alloc_bandwidth(dev, NULL, - intf->cur_altsetting, alt); - if (retval < 0) - break; - } - /* If not, reinstate the old alternate settings */ + /* xHCI adds all endpoints in usb_hcd_alloc_bandwidth */ + retval = usb_hcd_alloc_bandwidth(dev, config, NULL, NULL); if (retval < 0) { -reset_old_alts: - for (i--; i >= 0; i--) { - struct usb_interface *intf = config->interface[i]; - struct usb_host_interface *alt; - - alt = usb_altnum_to_altsetting(intf, 0); - if (!alt) - alt = &intf->altsetting[0]; - if (alt != intf->cur_altsetting) - usb_hcd_alloc_bandwidth(dev, NULL, - alt, intf->cur_altsetting); - } usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); return retval; } - retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_CONFIGURATION, 0, - config->desc.bConfigurationValue, 0, - NULL, 0, USB_CTRL_SET_TIMEOUT); - if (retval < 0) - goto reset_old_alts; + retval = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + config->desc.bConfigurationValue, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT, + GFP_NOIO); + if (retval) { + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return retval; + } mutex_unlock(hcd->bandwidth_mutex); /* re-init hc/hcd interface/endpoint state */ @@ -1537,14 +1775,54 @@ static void usb_release_interface(struct device *dev) altsetting_to_usb_interface_cache(intf->altsetting); kref_put(&intfc->ref, usb_release_interface_cache); + usb_put_dev(interface_to_usbdev(intf)); + of_node_put(dev->of_node); kfree(intf); } -static int usb_if_uevent(struct device *dev, struct kobj_uevent_env *env) +/* + * usb_deauthorize_interface - deauthorize an USB interface + * + * @intf: USB interface structure + */ +void usb_deauthorize_interface(struct usb_interface *intf) { - struct usb_device *usb_dev; - struct usb_interface *intf; - struct usb_host_interface *alt; + struct device *dev = &intf->dev; + + device_lock(dev->parent); + + if (intf->authorized) { + device_lock(dev); + intf->authorized = 0; + device_unlock(dev); + + usb_forced_unbind_intf(intf); + } + + device_unlock(dev->parent); +} + +/* + * usb_authorize_interface - authorize an USB interface + * + * @intf: USB interface structure + */ +void usb_authorize_interface(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + + if (!intf->authorized) { + device_lock(dev); + intf->authorized = 1; /* authorize interface */ + device_unlock(dev); + } +} + +static int usb_if_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct usb_device *usb_dev; + const struct usb_interface *intf; + const struct usb_host_interface *alt; intf = to_usb_interface(dev); usb_dev = interface_to_usbdev(intf); @@ -1574,7 +1852,7 @@ static int usb_if_uevent(struct device *dev, struct kobj_uevent_env *env) return 0; } -struct device_type usb_if_device_type = { +const struct device_type usb_if_device_type = { .name = "usb_interface", .release = usb_release_interface, .uevent = usb_if_uevent, @@ -1612,24 +1890,6 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev, /* * Internal function to queue a device reset - * - * This is initialized into the workstruct in 'struct - * usb_device->reset_ws' that is launched by - * message.c:usb_set_configuration() when initializing each 'struct - * usb_interface'. - * - * It is safe to get the USB device without reference counts because - * the life cycle of @iface is bound to the life cycle of @udev. Then, - * this function will be ran only if @iface is alive (and before - * freeing it any scheduled instances of it will have been cancelled). - * - * We need to set a flag (usb_dev->reset_running) because when we call - * the reset, the interfaces might be unbound. The current interface - * cannot try to remove the queued work as it would cause a deadlock - * (you cannot remove your work from within your executing - * workqueue). This flag lets it know, so that - * usb_cancel_queued_reset() doesn't try to do it. - * * See usb_queue_reset_device() for more details */ static void __usb_queue_reset_device(struct work_struct *ws) @@ -1641,19 +1901,58 @@ static void __usb_queue_reset_device(struct work_struct *ws) rc = usb_lock_device_for_reset(udev, iface); if (rc >= 0) { - iface->reset_running = 1; usb_reset_device(udev); - iface->reset_running = 0; usb_unlock_device(udev); } + usb_put_intf(iface); /* Undo _get_ in usb_queue_reset_device() */ } +/* + * Internal function to set the wireless_status sysfs attribute + * See usb_set_wireless_status() for more details + */ +static void __usb_wireless_status_intf(struct work_struct *ws) +{ + struct usb_interface *iface = + container_of(ws, struct usb_interface, wireless_status_work); + + device_lock(iface->dev.parent); + if (iface->sysfs_files_created) + usb_update_wireless_status_attr(iface); + device_unlock(iface->dev.parent); + usb_put_intf(iface); /* Undo _get_ in usb_set_wireless_status() */ +} + +/** + * usb_set_wireless_status - sets the wireless_status struct member + * @iface: the interface to modify + * @status: the new wireless status + * + * Set the wireless_status struct member to the new value, and emit + * sysfs changes as necessary. + * + * Returns: 0 on success, -EALREADY if already set. + */ +int usb_set_wireless_status(struct usb_interface *iface, + enum usb_wireless_status status) +{ + if (iface->wireless_status == status) + return -EALREADY; + + usb_get_intf(iface); + iface->wireless_status = status; + schedule_work(&iface->wireless_status_work); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_set_wireless_status); /* * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated * @configuration: the configuration being chosen. - * Context: !in_interrupt(), caller owns the device lock + * + * Context: task context, might sleep. Caller holds device lock. * * This is used to enable non-default device modes. Not all devices * use this kind of configurability; many devices only have one @@ -1729,19 +2028,16 @@ int usb_set_configuration(struct usb_device *dev, int configuration) n = nintf = 0; if (cp) { nintf = cp->desc.bNumInterfaces; - new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), - GFP_NOIO); - if (!new_interfaces) { - dev_err(&dev->dev, "Out of memory\n"); + new_interfaces = kmalloc_array(nintf, sizeof(*new_interfaces), + GFP_NOIO); + if (!new_interfaces) return -ENOMEM; - } for (; n < nintf; ++n) { new_interfaces[n] = kzalloc( sizeof(struct usb_interface), GFP_NOIO); if (!new_interfaces[n]) { - dev_err(&dev->dev, "Out of memory\n"); ret = -ENOMEM; free_interfaces: while (--n >= 0) @@ -1784,7 +2080,7 @@ free_interfaces: * timeouts. */ if (dev->actconfig && usb_disable_lpm(dev)) { - dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + dev_err(&dev->dev, "%s Failed to disable LPM\n", __func__); mutex_unlock(hcd->bandwidth_mutex); ret = -ENOMEM; goto free_interfaces; @@ -1806,11 +2102,13 @@ free_interfaces: struct usb_interface_cache *intfc; struct usb_interface *intf; struct usb_host_interface *alt; + u8 ifnum; cp->interface[i] = intf = new_interfaces[i]; intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; + intf->authorized = !!HCD_INTF_AUTHORIZED(hcd); kref_get(&intfc->ref); alt = usb_altnum_to_altsetting(intf, 0); @@ -1823,30 +2121,37 @@ free_interfaces: if (!alt) alt = &intf->altsetting[0]; - intf->intf_assoc = - find_iad(dev, cp, alt->desc.bInterfaceNumber); + ifnum = alt->desc.bInterfaceNumber; + intf->intf_assoc = find_iad(dev, cp, ifnum); intf->cur_altsetting = alt; usb_enable_interface(dev, intf, true); intf->dev.parent = &dev->dev; + if (usb_of_has_combined_node(dev)) { + device_set_of_node_from_dev(&intf->dev, &dev->dev); + } else { + intf->dev.of_node = usb_of_get_interface_node(dev, + configuration, ifnum); + } + ACPI_COMPANION_SET(&intf->dev, ACPI_COMPANION(&dev->dev)); intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.type = &usb_if_device_type; intf->dev.groups = usb_interface_groups; - intf->dev.dma_mask = dev->dev.dma_mask; INIT_WORK(&intf->reset_ws, __usb_queue_reset_device); + INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf); intf->minor = -1; device_initialize(&intf->dev); pm_runtime_no_callbacks(&intf->dev); - dev_set_name(&intf->dev, "%d-%s:%d.%d", - dev->bus->busnum, dev->devpath, - configuration, alt->desc.bInterfaceNumber); + dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum, + dev->devpath, configuration, ifnum); + usb_get_dev(dev); } kfree(new_interfaces); - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_CONFIGURATION, 0, configuration, 0, - NULL, 0, USB_CTRL_SET_TIMEOUT); - if (ret < 0 && cp) { + ret = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + configuration, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); + if (ret && cp) { /* * All the old state is gone, so what else can we do? * The device is probably useless now anyway. @@ -1890,6 +2195,13 @@ free_interfaces: for (i = 0; i < nintf; ++i) { struct usb_interface *intf = cp->interface[i]; + if (intf->dev.of_node && + !of_device_is_available(intf->dev.of_node)) { + dev_info(&dev->dev, "skipping disabled interface %d\n", + intf->cur_altsetting->desc.bInterfaceNumber); + continue; + } + dev_dbg(&dev->dev, "adding %s (config #%d, interface %d)\n", dev_name(&intf->dev), configuration, @@ -1907,6 +2219,7 @@ free_interfaces: usb_autosuspend_device(dev); return 0; } +EXPORT_SYMBOL_GPL(usb_set_configuration); static LIST_HEAD(set_config_list); static DEFINE_SPINLOCK(set_config_lock); @@ -1968,7 +2281,7 @@ static void cancel_async_set_config(struct usb_device *udev) * routine gets around the normal restrictions by using a work thread to * submit the change-config request. * - * Returns 0 if the request was successfully queued, error code otherwise. + * Return: 0 if the request was successfully queued, error code otherwise. * The caller has no way to know whether the queued request will eventually * succeed. */ @@ -1992,3 +2305,159 @@ int usb_driver_set_configuration(struct usb_device *udev, int config) return 0; } EXPORT_SYMBOL_GPL(usb_driver_set_configuration); + +/** + * cdc_parse_cdc_header - parse the extra headers present in CDC devices + * @hdr: the place to put the results of the parsing + * @intf: the interface for which parsing is requested + * @buffer: pointer to the extra headers to be parsed + * @buflen: length of the extra headers + * + * This evaluates the extra headers present in CDC devices which + * bind the interfaces for data and control and provide details + * about the capabilities of the device. + * + * Return: number of descriptors parsed or -EINVAL + * if the header is contradictory beyond salvage + */ + +int cdc_parse_cdc_header(struct usb_cdc_parsed_header *hdr, + struct usb_interface *intf, + u8 *buffer, + int buflen) +{ + /* duplicates are ignored */ + struct usb_cdc_union_desc *union_header = NULL; + + /* duplicates are not tolerated */ + struct usb_cdc_header_desc *header = NULL; + struct usb_cdc_ether_desc *ether = NULL; + struct usb_cdc_mdlm_detail_desc *detail = NULL; + struct usb_cdc_mdlm_desc *desc = NULL; + + unsigned int elength; + int cnt = 0; + + memset(hdr, 0x00, sizeof(struct usb_cdc_parsed_header)); + hdr->phonet_magic_present = false; + while (buflen > 0) { + elength = buffer[0]; + if (!elength) { + dev_err(&intf->dev, "skipping garbage byte\n"); + elength = 1; + goto next_desc; + } + if ((buflen < elength) || (elength < 3)) { + dev_err(&intf->dev, "invalid descriptor buffer length\n"); + break; + } + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (elength < sizeof(struct usb_cdc_union_desc)) + goto next_desc; + if (union_header) { + dev_err(&intf->dev, "More than one union descriptor, skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + case USB_CDC_COUNTRY_TYPE: + if (elength < sizeof(struct usb_cdc_country_functional_desc)) + goto next_desc; + hdr->usb_cdc_country_functional_desc = + (struct usb_cdc_country_functional_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: + if (elength != sizeof(struct usb_cdc_header_desc)) + goto next_desc; + if (header) + return -EINVAL; + header = (struct usb_cdc_header_desc *)buffer; + break; + case USB_CDC_ACM_TYPE: + if (elength < sizeof(struct usb_cdc_acm_descriptor)) + goto next_desc; + hdr->usb_cdc_acm_descriptor = + (struct usb_cdc_acm_descriptor *)buffer; + break; + case USB_CDC_ETHERNET_TYPE: + if (elength != sizeof(struct usb_cdc_ether_desc)) + goto next_desc; + if (ether) + return -EINVAL; + ether = (struct usb_cdc_ether_desc *)buffer; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + if (elength < sizeof(struct usb_cdc_call_mgmt_descriptor)) + goto next_desc; + hdr->usb_cdc_call_mgmt_descriptor = + (struct usb_cdc_call_mgmt_descriptor *)buffer; + break; + case USB_CDC_DMM_TYPE: + if (elength < sizeof(struct usb_cdc_dmm_desc)) + goto next_desc; + hdr->usb_cdc_dmm_desc = + (struct usb_cdc_dmm_desc *)buffer; + break; + case USB_CDC_MDLM_TYPE: + if (elength < sizeof(struct usb_cdc_mdlm_desc)) + goto next_desc; + if (desc) + return -EINVAL; + desc = (struct usb_cdc_mdlm_desc *)buffer; + break; + case USB_CDC_MDLM_DETAIL_TYPE: + if (elength < sizeof(struct usb_cdc_mdlm_detail_desc)) + goto next_desc; + if (detail) + return -EINVAL; + detail = (struct usb_cdc_mdlm_detail_desc *)buffer; + break; + case USB_CDC_NCM_TYPE: + if (elength < sizeof(struct usb_cdc_ncm_desc)) + goto next_desc; + hdr->usb_cdc_ncm_desc = (struct usb_cdc_ncm_desc *)buffer; + break; + case USB_CDC_MBIM_TYPE: + if (elength < sizeof(struct usb_cdc_mbim_desc)) + goto next_desc; + + hdr->usb_cdc_mbim_desc = (struct usb_cdc_mbim_desc *)buffer; + break; + case USB_CDC_MBIM_EXTENDED_TYPE: + if (elength < sizeof(struct usb_cdc_mbim_extended_desc)) + goto next_desc; + hdr->usb_cdc_mbim_extended_desc = + (struct usb_cdc_mbim_extended_desc *)buffer; + break; + case CDC_PHONET_MAGIC_NUMBER: + hdr->phonet_magic_present = true; + break; + default: + /* + * there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: type %02x, length %ud\n", + buffer[2], elength); + goto next_desc; + } + cnt++; +next_desc: + buflen -= elength; + buffer += elength; + } + hdr->usb_cdc_union_desc = union_header; + hdr->usb_cdc_header_desc = header; + hdr->usb_cdc_mdlm_detail_desc = detail; + hdr->usb_cdc_mdlm_desc = desc; + hdr->usb_cdc_ether_desc = ether; + return cnt; +} + +EXPORT_SYMBOL(cdc_parse_cdc_header); |
