| /* |
| * Gadget Driver for Android DvC.Dfx Debug Capability |
| * |
| * Copyright (C) 2008-2010, Intel Corporation. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/poll.h> |
| #include <linux/delay.h> |
| #include <linux/wait.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/device.h> |
| #include <linux/miscdevice.h> |
| #include <linux/usb/debug.h> |
| #include <linux/sdm.h> |
| #include <asm/intel_soc_debug.h> |
| |
| #define DFX_RX_REQ_MAX 1 |
| #define DFX_TX_REQ_MAX 2 |
| #define DFX_BULK_REQ_SIZE 64 |
| |
| #define CONFIG_BOARD_MRFLD_VV |
| |
| struct dvc_dfx_dev { |
| struct usb_function function; |
| struct usb_composite_dev *cdev; |
| spinlock_t lock; |
| u8 ctrl_id, data_id; |
| |
| struct usb_ep *ep_in; |
| struct usb_ep *ep_out; |
| |
| int transfering; |
| int online; |
| int online_ctrl; |
| int online_data; |
| int error; |
| |
| atomic_t read_excl; |
| atomic_t write_excl; |
| atomic_t open_excl; |
| |
| wait_queue_head_t read_wq; |
| wait_queue_head_t write_wq; |
| |
| struct usb_request *rx_req[DFX_RX_REQ_MAX]; |
| |
| struct list_head tx_idle; |
| struct list_head tx_xfer; |
| }; |
| |
| static struct usb_interface_assoc_descriptor dfx_iad_desc = { |
| .bLength = sizeof(dfx_iad_desc), |
| .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, |
| /* .bFirstInterface = DYNAMIC, */ |
| .bInterfaceCount = 2, /* debug control + data */ |
| .bFunctionClass = USB_CLASS_DEBUG, |
| .bFunctionSubClass = USB_SUBCLASS_DVC_DFX, |
| /* .bFunctionProtocol = DC_PROTOCOL_VENDOR, */ |
| /* .iFunction = 0, */ |
| }; |
| |
| static struct usb_interface_descriptor dfx_interface_desc = { |
| .bLength = USB_DT_INTERFACE_SIZE, |
| .bDescriptorType = USB_DT_INTERFACE, |
| .bNumEndpoints = 0, |
| .bInterfaceClass = USB_CLASS_DEBUG, |
| .bInterfaceSubClass = USB_SUBCLASS_DEBUG_CONTROL, |
| /* .bInterfaceProtocol = DC_PROTOCOL_VENDOR, */ |
| }; |
| |
| #define DC_DBG_ATTRI_LENGTH DC_DBG_ATTRI_SIZE(2, 32) |
| /* 1 input terminal, 1 output terminal and 1 feature unit */ |
| #define DC_DBG_TOTAL_LENGTH (DC_DBG_ATTRI_LENGTH) |
| |
| DECLARE_DC_DEBUG_ATTR_DESCR(DVCD, 2, 32); |
| |
| static struct DC_DEBUG_ATTR_DESCR(DVCD) dfx_debug_attri_desc = { |
| .bLength = DC_DBG_ATTRI_LENGTH, |
| .bDescriptorType = USB_DT_CS_INTERFACE, |
| .bDescriptorSubtype = DC_DEBUG_ATTRIBUTES, |
| .bcdDC = __constant_cpu_to_le16(0x0100), |
| .wTotalLength = __constant_cpu_to_le16(DC_DBG_TOTAL_LENGTH), |
| .bmSupportedFeatures = 0, /* Debug Event Supported, per SAS */ |
| .bControlSize = 2, |
| .bmControl = { /* per SAS */ |
| [0] = 0xFF, |
| [1] = 0x3F, |
| }, |
| .wAuxDataSize = __constant_cpu_to_le16(0x20), |
| /* per SAS v0.3*/ |
| .dInputBufferSize = __constant_cpu_to_le32(0x40), |
| .dOutputBufferSize = __constant_cpu_to_le32(0x80), |
| .qBaseAddress = 0, /* revision */ |
| .hGlobalID = { /* revision */ |
| [0] = 0, |
| [1] = 0, |
| } |
| }; |
| |
| static struct usb_interface_descriptor dfx_data_interface_desc = { |
| .bLength = USB_DT_INTERFACE_SIZE, |
| .bDescriptorType = USB_DT_INTERFACE, |
| .bAlternateSetting = 0, |
| .bNumEndpoints = 2, |
| .bInterfaceClass = USB_CLASS_DEBUG, |
| .bInterfaceSubClass = USB_SUBCLASS_DVC_DFX, |
| /* .bInterfaceProtocol = DC_PROTOCOL_VENDOR, */ |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_fullspeed_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_fullspeed_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_highspeed_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(512), |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_highspeed_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(512), |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_superspeed_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(1024), |
| }; |
| |
| static struct usb_ss_ep_comp_descriptor dfx_superspeed_in_comp_desc = { |
| .bLength = USB_DT_SS_EP_COMP_SIZE, |
| .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, |
| .bMaxBurst = 0, |
| .bmAttributes = 0, |
| }; |
| |
| static struct usb_endpoint_descriptor dfx_superspeed_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(1024), |
| }; |
| |
| static struct usb_ss_ep_comp_descriptor dfx_superspeed_out_comp_desc = { |
| .bLength = USB_DT_SS_EP_COMP_SIZE, |
| .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, |
| .bMaxBurst = 0, |
| .bmAttributes = 0, |
| }; |
| |
| /* no INPUT/OUTPUT CONNECTION and UNIT descriptors for DvC.DFx */ |
| static struct usb_descriptor_header *fs_dfx_descs[] = { |
| (struct usb_descriptor_header *) &dfx_iad_desc, |
| (struct usb_descriptor_header *) &dfx_data_interface_desc, |
| (struct usb_descriptor_header *) &dfx_fullspeed_in_desc, |
| (struct usb_descriptor_header *) &dfx_fullspeed_out_desc, |
| |
| (struct usb_descriptor_header *) &dfx_interface_desc, |
| (struct usb_descriptor_header *) &dfx_debug_attri_desc, |
| NULL, |
| }; |
| |
| static struct usb_descriptor_header *hs_dfx_descs[] = { |
| (struct usb_descriptor_header *) &dfx_iad_desc, |
| (struct usb_descriptor_header *) &dfx_data_interface_desc, |
| (struct usb_descriptor_header *) &dfx_highspeed_in_desc, |
| (struct usb_descriptor_header *) &dfx_highspeed_out_desc, |
| |
| (struct usb_descriptor_header *) &dfx_interface_desc, |
| (struct usb_descriptor_header *) &dfx_debug_attri_desc, |
| NULL, |
| }; |
| |
| static struct usb_descriptor_header *ss_dfx_descs[] = { |
| (struct usb_descriptor_header *) &dfx_iad_desc, |
| (struct usb_descriptor_header *) &dfx_data_interface_desc, |
| (struct usb_descriptor_header *) &dfx_superspeed_in_desc, |
| (struct usb_descriptor_header *) &dfx_superspeed_in_comp_desc, |
| (struct usb_descriptor_header *) &dfx_superspeed_out_desc, |
| (struct usb_descriptor_header *) &dfx_superspeed_out_comp_desc, |
| |
| (struct usb_descriptor_header *) &dfx_interface_desc, |
| (struct usb_descriptor_header *) &dfx_debug_attri_desc, |
| NULL, |
| }; |
| |
| /* string descriptors: */ |
| |
| #define DVCDFX_CTRL_IDX 0 |
| #define DVCDFX_DATA_IDX 1 |
| #define DVCDFX_IAD_IDX 2 |
| |
| /* static strings, in UTF-8 */ |
| static struct usb_string dfx_string_defs[] = { |
| [DVCDFX_CTRL_IDX].s = "Debug Sub-Class DvC.DFx (Control)", |
| [DVCDFX_DATA_IDX].s = "Debug Sub-Class DvC.DFx (Data)", |
| [DVCDFX_IAD_IDX].s = "Debug Sub-Class DvC.DFx", |
| { /* ZEROES END LIST */ }, |
| }; |
| |
| static struct usb_gadget_strings dfx_string_table = { |
| .language = 0x0409, /* en-us */ |
| .strings = dfx_string_defs, |
| }; |
| |
| static struct usb_gadget_strings *dfx_strings[] = { |
| &dfx_string_table, |
| NULL, |
| }; |
| |
| /*-------------------------------------------------------------------------*/ |
| |
| /* temporary variable used between dvc_dfx_open() and dvc_dfx_gadget_bind() */ |
| static struct dvc_dfx_dev *_dvc_dfx_dev; |
| |
| static inline struct dvc_dfx_dev *func_to_dvc_dfx(struct usb_function *f) |
| { |
| return container_of(f, struct dvc_dfx_dev, function); |
| } |
| |
| static int dvc_dfx_is_enabled(void) |
| { |
| if ((!cpu_has_debug_feature(DEBUG_FEATURE_USB3DFX)) || |
| (!stm_is_enabled())) { |
| pr_info("%s STM and/or USB3DFX is not enabled\n", __func__); |
| return 0; |
| } |
| return 1; |
| } |
| |
| static struct usb_request *dvc_dfx_request_new(struct usb_ep *ep, |
| int buffer_size, dma_addr_t dma) |
| { |
| struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); |
| if (!req) |
| return NULL; |
| |
| |
| req->dma = dma; |
| /* now allocate buffers for the requests */ |
| req->buf = kmalloc(buffer_size, GFP_KERNEL); |
| if (!req->buf) { |
| usb_ep_free_request(ep, req); |
| return NULL; |
| } |
| |
| return req; |
| } |
| |
| static void dvc_dfx_request_free(struct usb_request *req, struct usb_ep *ep) |
| { |
| if (req) { |
| kfree(req->buf); |
| usb_ep_free_request(ep, req); |
| } |
| } |
| |
| /* add a request to the tail of a list */ |
| static void dvc_dfx_req_put(struct dvc_dfx_dev *dev, struct list_head *head, |
| struct usb_request *req) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| list_add_tail(&req->list, head); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| /* remove a request from the head of a list */ |
| static struct usb_request *dvc_dfx_req_get(struct dvc_dfx_dev *dev, |
| struct list_head *head) |
| { |
| unsigned long flags; |
| struct usb_request *req; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (list_empty(head)) { |
| req = 0; |
| } else { |
| req = list_first_entry(head, struct usb_request, list); |
| list_del(&req->list); |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return req; |
| } |
| |
| static void dvc_dfx_set_disconnected(struct dvc_dfx_dev *dev) |
| { |
| dev->transfering = 0; |
| } |
| |
| static void dvc_dfx_complete_in(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| |
| if (req->status != 0) |
| dvc_dfx_set_disconnected(dev); |
| |
| dvc_dfx_req_put(dev, &dev->tx_idle, req); |
| |
| wake_up(&dev->write_wq); |
| } |
| |
| static void dvc_dfx_complete_out(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| |
| if (req->status != 0) |
| dvc_dfx_set_disconnected(dev); |
| wake_up(&dev->read_wq); |
| } |
| |
| |
| static inline int dvc_dfx_lock(atomic_t *excl) |
| { |
| if (atomic_inc_return(excl) == 1) { |
| return 0; |
| } else { |
| atomic_dec(excl); |
| return -1; |
| } |
| } |
| |
| static inline void dvc_dfx_unlock(atomic_t *excl) |
| { |
| atomic_dec(excl); |
| } |
| |
| static int dfx_create_bulk_endpoints(struct dvc_dfx_dev *dev, |
| struct usb_endpoint_descriptor *in_desc, |
| struct usb_endpoint_descriptor *out_desc, |
| struct usb_ss_ep_comp_descriptor *in_comp_desc, |
| struct usb_ss_ep_comp_descriptor *out_comp_desc |
| ) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct usb_request *req; |
| struct usb_ep *ep; |
| int i; |
| |
| pr_debug("%s dev: %p\n", __func__, dev); |
| |
| in_desc->bEndpointAddress |= 0x8; |
| ep = usb_ep_autoconfig_ss(cdev->gadget, in_desc, in_comp_desc); |
| if (!ep) { |
| pr_debug("%s for ep_in failed\n", __func__); |
| return -ENODEV; |
| } |
| pr_debug("%s for ep_in got %s\n", __func__, ep->name); |
| |
| ep->driver_data = dev; /* claim the endpoint */ |
| dev->ep_in = ep; |
| |
| out_desc->bEndpointAddress |= 0x8; |
| ep = usb_ep_autoconfig_ss(cdev->gadget, out_desc, out_comp_desc); |
| if (!ep) { |
| pr_debug("%s for ep_out failed\n", __func__); |
| return -ENODEV; |
| } |
| pr_debug("%s for ep_out got %s\n", __func__, ep->name); |
| |
| ep->driver_data = dev; /* claim the endpoint */ |
| dev->ep_out = ep; |
| |
| /* now allocate requests for our endpoints */ |
| for (i = 0; i < DFX_TX_REQ_MAX; i++) { |
| if (!(i % 2)) |
| req = dvc_dfx_request_new(dev->ep_in, |
| DFX_BULK_BUFFER_SIZE, |
| (dma_addr_t)DFX_BULK_IN_BUFFER_ADDR); |
| else |
| req = dvc_dfx_request_new(dev->ep_in, |
| DFX_BULK_BUFFER_SIZE, |
| (dma_addr_t)DFX_BULK_IN_BUFFER_ADDR_2); |
| if (!req) |
| goto fail; |
| req->complete = dvc_dfx_complete_in; |
| dvc_dfx_req_put(dev, &dev->tx_idle, req); |
| } |
| for (i = 0; i < DFX_RX_REQ_MAX; i++) { |
| req = dvc_dfx_request_new(dev->ep_out, DFX_BULK_BUFFER_SIZE, |
| (dma_addr_t)DFX_BULK_OUT_BUFFER_ADDR); |
| if (!req) |
| goto fail; |
| req->complete = dvc_dfx_complete_out; |
| dev->rx_req[i] = req; |
| } |
| |
| return 0; |
| |
| fail: |
| pr_err("%s could not allocate requests\n", __func__); |
| while ((req = dvc_dfx_req_get(dev, &dev->tx_idle))) |
| dvc_dfx_request_free(req, dev->ep_out); |
| for (i = 0; i < DFX_RX_REQ_MAX; i++) |
| dvc_dfx_request_free(dev->rx_req[i], dev->ep_out); |
| return -1; |
| } |
| |
| static ssize_t dvc_dfx_start_transfer(size_t count) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| struct usb_request *req; |
| int r = count, xfer; |
| int ret = -ENODEV; |
| |
| |
| pr_info("%s start\n", __func__); |
| if (!_dvc_dfx_dev) |
| return ret; |
| |
| if (dvc_dfx_lock(&dev->read_excl) |
| && dvc_dfx_lock(&dev->write_excl)) |
| return -EBUSY; |
| |
| /* we will block until enumeration completes */ |
| while (!(dev->online || dev->error)) { |
| pr_debug("%s waiting for online state\n", __func__); |
| ret = wait_event_interruptible(dev->read_wq, |
| (dev->online || dev->error)); |
| |
| if (ret < 0) { |
| /* not at CONFIGURED state */ |
| pr_info("%s USB not at CONFIGURED\n", __func__); |
| dvc_dfx_unlock(&dev->read_excl); |
| dvc_dfx_unlock(&dev->write_excl); |
| return ret; |
| } |
| } |
| |
| /* queue a ep_in endless request */ |
| while (r > 0) { |
| if (dev->error) { |
| pr_debug("%s dev->error\n", __func__); |
| r = -EIO; |
| break; |
| } |
| |
| if (!dev->online) { |
| pr_debug("%s !dev->online issue\n", __func__); |
| r = -EIO; |
| break; |
| } |
| |
| /* get an idle tx request to use */ |
| req = 0; |
| ret = wait_event_interruptible(dev->write_wq, |
| dev->error || !dev->online || |
| (req = dvc_dfx_req_get(dev, &dev->tx_idle))); |
| |
| if (ret < 0) { |
| r = ret; |
| break; |
| } |
| |
| if (req != 0) { |
| if (count > DFX_BULK_BUFFER_SIZE) |
| xfer = DFX_BULK_BUFFER_SIZE; |
| else |
| xfer = count; |
| |
| req->no_interrupt = 1; |
| req->context = &dev->function; |
| req->length = xfer; |
| pr_debug("%s queue tx_idle list req to dev->ep_in\n", |
| __func__); |
| ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); |
| if (ret < 0) { |
| pr_err("%s xfer error %d\n", __func__, ret); |
| dev->error = 1; |
| r = -EIO; |
| break; |
| } |
| pr_debug("%s xfer=%d/%d queued req/%x\n", __func__, |
| xfer, r, (uint)req); |
| dvc_dfx_req_put(dev, &dev->tx_xfer, req); |
| r -= xfer; |
| |
| /* zero this so we don't try to free it on error exit */ |
| req = 0; |
| } |
| } |
| if (req) { |
| pr_debug("%s req re-added to tx_idle on error\n", __func__); |
| dvc_dfx_req_put(dev, &dev->tx_idle, req); |
| } |
| |
| pr_debug("%s rx_req to dev->ep_out\n", __func__); |
| /* queue a ep_out endless request */ |
| req = dev->rx_req[0]; |
| req->length = DFX_BULK_BUFFER_SIZE; |
| req->no_interrupt = 1; |
| req->context = &dev->function; |
| ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); |
| if (ret < 0) { |
| pr_err("%s failed to queue out req %p (%d)\n", |
| __func__, req, req->length); |
| r = -EIO; |
| } else { |
| dev->transfering = 1; |
| } |
| |
| dvc_dfx_unlock(&dev->read_excl); |
| dvc_dfx_unlock(&dev->write_excl); |
| pr_debug("%s returning\n", __func__); |
| return r; |
| } |
| |
| static int dvc_dfx_disable_transfer(void) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| struct usb_request *req; |
| int r = 1; |
| int ret; |
| |
| |
| pr_info("%s start\n", __func__); |
| if (!_dvc_dfx_dev) |
| return -ENODEV; |
| |
| if (dvc_dfx_lock(&dev->read_excl) |
| && dvc_dfx_lock(&dev->write_excl)) |
| return -EBUSY; |
| |
| if (dev->error) { |
| pr_debug("%s dev->error\n", __func__); |
| r = -EIO; |
| goto end; |
| } |
| |
| if ((!dev->online) || (!dev->transfering)) { |
| pr_debug("%s !dev->online OR !dev->transfering\n", __func__); |
| r = -EIO; |
| goto end; |
| } |
| |
| /* get an xfer tx request to use */ |
| while ((req = dvc_dfx_req_get(dev, &dev->tx_xfer))) { |
| ret = usb_ep_dequeue(dev->ep_in, req); |
| if (ret < 0) { |
| pr_err("%s dequeue error %d\n", __func__, ret); |
| dev->error = 1; |
| r = -EIO; |
| goto end; |
| } |
| pr_debug("%s dequeued tx req/%x\n", __func__, (uint)req); |
| } |
| ret = usb_ep_dequeue(dev->ep_out, dev->rx_req[0]); |
| if (ret < 0) { |
| pr_err("%s dequeue rx error %d\n", __func__, ret); |
| dev->error = 1; |
| r = -EIO; |
| goto end; |
| } |
| |
| end: |
| dvc_dfx_unlock(&dev->read_excl); |
| dvc_dfx_unlock(&dev->write_excl); |
| return r; |
| } |
| |
| static int dvc_dfx_open(struct inode *ip, struct file *fp) |
| { |
| pr_info("%s\n", __func__); |
| if (!_dvc_dfx_dev) |
| return -ENODEV; |
| |
| if (dvc_dfx_lock(&_dvc_dfx_dev->open_excl)) |
| return -EBUSY; |
| |
| fp->private_data = _dvc_dfx_dev; |
| |
| /* clear the error latch */ |
| _dvc_dfx_dev->error = 0; |
| _dvc_dfx_dev->transfering = 0; |
| |
| return 0; |
| } |
| |
| static int dvc_dfx_release(struct inode *ip, struct file *fp) |
| { |
| pr_info("%s\n", __func__); |
| |
| dvc_dfx_unlock(&_dvc_dfx_dev->open_excl); |
| return 0; |
| } |
| |
| /* file operations for DvC.Dfx device /dev/usb_dvc_dfx */ |
| static const struct file_operations dvc_dfx_fops = { |
| .owner = THIS_MODULE, |
| .open = dvc_dfx_open, |
| .release = dvc_dfx_release, |
| }; |
| |
| static struct miscdevice dvc_dfx_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "usb_dvc_dfx", |
| .fops = &dvc_dfx_fops, |
| }; |
| |
| static int dvc_dfx_ctrlrequest(struct usb_composite_dev *cdev, |
| const struct usb_ctrlrequest *ctrl) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| int value = -EOPNOTSUPP; |
| u16 w_index = le16_to_cpu(ctrl->wIndex); |
| u16 w_value = le16_to_cpu(ctrl->wValue); |
| u16 w_length = le16_to_cpu(ctrl->wLength); |
| |
| pr_debug("%s %02x.%02x v%04x i%04x l%u\n", __func__, |
| ctrl->bRequestType, ctrl->bRequest, |
| w_value, w_index, w_length); |
| |
| switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { |
| |
| /* DC_REQUEST_SET_RESET ... stop active transfer */ |
| case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
| | DC_REQUEST_SET_RESET: |
| if (w_index != dev->data_id) |
| goto invalid; |
| |
| pr_info("%s DC_REQUEST_SET_RESET v%04x i%04x l%u\n", __func__, |
| w_value, w_index, w_length); |
| |
| dvc_dfx_disable_transfer(); |
| value = 0; |
| break; |
| |
| default: |
| invalid: |
| pr_debug("unknown class-specific control req " |
| "%02x.%02x v%04x i%04x l%u\n", |
| ctrl->bRequestType, ctrl->bRequest, |
| w_value, w_index, w_length); |
| } |
| |
| /* respond with data transfer or status phase? */ |
| if (value >= 0) { |
| cdev->req->zero = 0; |
| cdev->req->length = value; |
| value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); |
| if (value < 0) |
| pr_err("%s setup response queue error\n", __func__); |
| } |
| |
| /* device either stalls (value < 0) or reports success */ |
| return value; |
| } |
| |
| static int |
| dvc_dfx_function_bind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct usb_composite_dev *cdev = c->cdev; |
| struct dvc_dfx_dev *dev = func_to_dvc_dfx(f); |
| int id; |
| int ret; |
| |
| dev->cdev = cdev; |
| pr_info("%s dev: %p\n", __func__, dev); |
| |
| /* allocate interface ID(s) */ |
| id = usb_interface_id(c, f); |
| if (id < 0) |
| return id; |
| dev->data_id = id; |
| dfx_data_interface_desc.bInterfaceNumber = id; |
| dfx_iad_desc.bFirstInterface = id; |
| |
| id = usb_interface_id(c, f); |
| if (id < 0) |
| return id; |
| dev->ctrl_id = id; |
| dfx_interface_desc.bInterfaceNumber = id; |
| |
| /* allocate endpoints */ |
| ret = dfx_create_bulk_endpoints(dev, &dfx_fullspeed_in_desc, |
| &dfx_fullspeed_out_desc, |
| &dfx_superspeed_in_comp_desc, |
| &dfx_superspeed_out_comp_desc |
| ); |
| if (ret) |
| return ret; |
| |
| /* support high speed hardware */ |
| if (gadget_is_dualspeed(c->cdev->gadget)) { |
| dfx_highspeed_in_desc.bEndpointAddress = |
| dfx_fullspeed_in_desc.bEndpointAddress; |
| dfx_highspeed_out_desc.bEndpointAddress = |
| dfx_fullspeed_out_desc.bEndpointAddress; |
| } |
| |
| if (gadget_is_superspeed(c->cdev->gadget)) { |
| dfx_superspeed_in_desc.bEndpointAddress = |
| dfx_fullspeed_in_desc.bEndpointAddress; |
| |
| dfx_superspeed_out_desc.bEndpointAddress = |
| dfx_fullspeed_out_desc.bEndpointAddress; |
| } |
| |
| pr_info("%s speed %s: IN/%s, OUT/%s\n", |
| gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", |
| f->name, dev->ep_in->name, dev->ep_out->name); |
| return 0; |
| } |
| |
| static void |
| dvc_dfx_function_unbind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct dvc_dfx_dev *dev = func_to_dvc_dfx(f); |
| struct usb_request *req; |
| int i; |
| |
| dev->online = 0; |
| dev->online_ctrl = 0; |
| dev->online_data = 0; |
| dev->transfering = 0; |
| dev->error = 0; |
| |
| dfx_string_defs[DVCDFX_CTRL_IDX].id = 0; |
| |
| wake_up(&dev->read_wq); |
| |
| for (i = 0; i < DFX_RX_REQ_MAX; i++) |
| dvc_dfx_request_free(dev->rx_req[i], dev->ep_out); |
| while ((req = dvc_dfx_req_get(dev, &dev->tx_idle))) |
| dvc_dfx_request_free(req, dev->ep_in); |
| |
| } |
| |
| static int dvc_dfx_function_set_alt(struct usb_function *f, |
| unsigned intf, unsigned alt) |
| { |
| struct dvc_dfx_dev *dev = func_to_dvc_dfx(f); |
| struct usb_composite_dev *cdev = f->config->cdev; |
| int ret; |
| |
| pr_info("%s intf: %d alt: %d\n", __func__, intf, alt); |
| if (intf == dfx_data_interface_desc.bInterfaceNumber) { |
| ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); |
| if (ret) { |
| pr_err("%s intf: %d alt: %d ep_by_speed in error %d\n", |
| __func__, intf, alt, ret); |
| return ret; |
| } |
| ret = usb_ep_enable(dev->ep_in); |
| if (ret) { |
| pr_err("%s intf: %d alt: %d ep_enable in err %d\n", |
| __func__, intf, alt, ret); |
| return ret; |
| } |
| |
| ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); |
| if (ret) { |
| pr_err("%s intf: %d alt: %d ep_enable out error %d\n", |
| __func__, intf, alt, ret); |
| return ret; |
| } |
| |
| ret = usb_ep_enable(dev->ep_out); |
| if (ret) { |
| pr_err("%s intf: %d alt: %d ep_enable out err %d\n", |
| __func__, intf, alt, ret); |
| usb_ep_disable(dev->ep_in); |
| return ret; |
| } |
| dev->online_data = 1; |
| } |
| if (intf == dfx_interface_desc.bInterfaceNumber) |
| dev->online_ctrl = 1; |
| |
| if (dev->online_data && dev->online_ctrl) { |
| dev->online = 1; |
| dev->error = 0; |
| } |
| |
| /* readers may be blocked waiting for us to go online */ |
| wake_up(&dev->read_wq); |
| return 0; |
| } |
| |
| static void dvc_dfx_function_disable(struct usb_function *f) |
| { |
| struct dvc_dfx_dev *dev = func_to_dvc_dfx(f); |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| pr_info("%s cdev %p\n", __func__, cdev); |
| |
| if (dev->transfering) |
| dvc_dfx_disable_transfer(); |
| |
| dev->online = 0; |
| dev->online_ctrl = 0; |
| dev->online_data = 0; |
| dev->error = 0; |
| usb_ep_disable(dev->ep_in); |
| usb_ep_disable(dev->ep_out); |
| |
| /* readers may be blocked waiting for us to go online */ |
| wake_up(&dev->read_wq); |
| |
| pr_debug("%s disabled\n", dev->function.name); |
| } |
| |
| static int dvc_dfx_bind_config(struct usb_configuration *c) |
| { |
| struct dvc_dfx_dev *dev = _dvc_dfx_dev; |
| int status; |
| |
| pr_info("%s\n", __func__); |
| |
| if (dfx_string_defs[DVCDFX_CTRL_IDX].id == 0) { |
| status = usb_string_id(c->cdev); |
| if (status < 0) |
| return status; |
| dfx_string_defs[DVCDFX_CTRL_IDX].id = status; |
| |
| dfx_interface_desc.iInterface = status; |
| |
| status = usb_string_id(c->cdev); |
| if (status < 0) |
| return status; |
| dfx_string_defs[DVCDFX_DATA_IDX].id = status; |
| |
| dfx_data_interface_desc.iInterface = status; |
| |
| status = usb_string_id(c->cdev); |
| if (status < 0) |
| return status; |
| dfx_string_defs[DVCDFX_IAD_IDX].id = status; |
| |
| dfx_iad_desc.iFunction = status; |
| } |
| |
| dev->cdev = c->cdev; |
| dev->function.name = "dvcdfx"; |
| dev->function.fs_descriptors = fs_dfx_descs; |
| dev->function.hs_descriptors = hs_dfx_descs; |
| dev->function.ss_descriptors = ss_dfx_descs; |
| dev->function.strings = dfx_strings; |
| dev->function.bind = dvc_dfx_function_bind; |
| dev->function.unbind = dvc_dfx_function_unbind; |
| dev->function.set_alt = dvc_dfx_function_set_alt; |
| dev->function.disable = dvc_dfx_function_disable; |
| |
| return usb_add_function(c, &dev->function); |
| } |
| |
| static int dvc_dfx_setup(void) |
| { |
| struct dvc_dfx_dev *dev; |
| int ret; |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| spin_lock_init(&dev->lock); |
| |
| init_waitqueue_head(&dev->read_wq); |
| init_waitqueue_head(&dev->write_wq); |
| |
| INIT_LIST_HEAD(&dev->tx_idle); |
| INIT_LIST_HEAD(&dev->tx_xfer); |
| |
| atomic_set(&dev->open_excl, 0); |
| atomic_set(&dev->read_excl, 0); |
| atomic_set(&dev->write_excl, 0); |
| |
| _dvc_dfx_dev = dev; |
| |
| ret = misc_register(&dvc_dfx_device); |
| if (ret) |
| goto err; |
| |
| return 0; |
| |
| err: |
| kfree(dev); |
| pr_err("DvC.Dfx gadget driver failed to initialize\n"); |
| return ret; |
| } |
| |
| static void dvc_dfx_cleanup(void) |
| { |
| misc_deregister(&dvc_dfx_device); |
| |
| kfree(_dvc_dfx_dev); |
| _dvc_dfx_dev = NULL; |
| } |