blob: 4837bf18d9314b700a746426be5e8bb206f78526 [file] [log] [blame]
/*
* Gadget Driver for Android DvC.Trace 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>
#define TRACE_TX_REQ_MAX 3
#define CONFIG_BOARD_MRFLD_VV
struct dvc_trace_dev {
struct usb_function function;
struct usb_composite_dev *cdev;
spinlock_t lock;
u8 ctrl_id, data_id;
u8 class_id, subclass_id;
struct usb_ep *ep_in;
int online;
int online_data;
int online_ctrl;
int transfering;
int error;
atomic_t write_excl;
atomic_t open_excl;
wait_queue_head_t write_wq;
struct list_head tx_idle;
struct list_head tx_xfer;
};
static struct usb_interface_assoc_descriptor trace_iad_desc = {
.bLength = sizeof(trace_iad_desc),
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
/* .bFirstInterface = DYNAMIC, */
.bInterfaceCount = 2, /* debug control + data */
.bFunctionClass = USB_CLASS_DEBUG,
.bFunctionSubClass = USB_SUBCLASS_DVC_TRACE,
/* .bFunctionProtocol = 0, */
/* .iFunction = DYNAMIC, */
};
static struct usb_interface_descriptor trace_interface_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 0,
.bInterfaceClass = USB_CLASS_DEBUG,
.bInterfaceSubClass = USB_SUBCLASS_DEBUG_CONTROL,
/* .bInterfaceProtocol = 0, */
};
#define DC_DVCTRACE_ATTRI_LENGTH DC_DBG_ATTRI_SIZE(2, 32)
/* 1 input terminal, 1 output terminal and 1 feature unit */
#define DC_DVCTRACE_TOTAL_LENGTH (DC_DVCTRACE_ATTRI_LENGTH \
+ DC_OUTPUT_CONNECTION_SIZE \
+ DC_OUTPUT_CONNECTION_SIZE \
+ DC_DBG_UNIT_SIZE(STM_NB_IN_PINS, 2, 2, 24))
DECLARE_DC_DEBUG_ATTR_DESCR(DVCT, 2, 32);
static struct DC_DEBUG_ATTR_DESCR(DVCT) trace_debug_attri_desc = {
.bLength = DC_DVCTRACE_ATTRI_LENGTH,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = DC_DEBUG_ATTRIBUTES,
.bcdDC = __constant_cpu_to_le16(0x0100),
.wTotalLength = __constant_cpu_to_le16(DC_DVCTRACE_TOTAL_LENGTH),
.bmSupportedFeatures = 0, /* Debug Event Supported, per SAS */
.bControlSize = 2,
.bmControl = { /* per SAS */
[0] = 0xFF,
[1] = 0x3F,
},
.wAuxDataSize = __constant_cpu_to_le16(0x20),
.dInputBufferSize = __constant_cpu_to_le32(0x00), /* per SAS */
.dOutputBufferSize = __constant_cpu_to_le32(TRACE_BULK_BUFFER_SIZE),
.qBaseAddress = 0, /* revision */
.hGlobalID = { /* revision */
[0] = 0,
[1] = 0,
}
};
static struct dc_output_connection_descriptor trace_output_conn_usb_desc = {
.bLength = DC_OUTPUT_CONNECTION_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = DC_OUTPUT_CONNECTION,
.bConnectionID = 0x01, /* USB */
.bConnectionType = DC_CONNECTION_USB,
.bAssocConnection = 0, /* No related input-connection */
.wSourceID = __constant_cpu_to_le16(0x01),
/* .iConnection = DYNAMIC, */
};
static struct dc_output_connection_descriptor trace_output_conn_pti_desc = {
.bLength = DC_OUTPUT_CONNECTION_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = DC_OUTPUT_CONNECTION,
.bConnectionID = 0, /* PTI */
.bConnectionType = DC_CONNECTION_DEBUG_DATA,
.bAssocConnection = 0, /* No related input-connection */
.wSourceID = __constant_cpu_to_le16(0x01),
/* .iConnection = DYNAMIC, */
};
#define DC_DVCTRACE_UNIT_LENGTH DC_DBG_UNIT_SIZE(STM_NB_IN_PINS, 2, 2, 24)
DECLARE_DC_DEBUG_UNIT_DESCRIPTOR(STM_NB_IN_PINS, 2, 2, 24);
static struct DC_DEBUG_UNIT_DESCRIPTOR(STM_NB_IN_PINS, 2, 2, 24)
trace_debug_unit_stm_desc = {
.bLength = DC_DVCTRACE_UNIT_LENGTH,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = DC_DEBUG_UNIT,
.bUnitID = 0x01, /* per SAS */
/* STM Trace Unit processor: revision */
.bDebugUnitType = DC_UNIT_TYPE_TRACE_PROC,
/* STM: Trace compressor controller */
.bDebugSubUnitType = DC_UNIT_SUBTYPE_TRACEZIP,
.bAliasUnitID = 0, /* no associated debug unit */
.bNrInPins = STM_NB_IN_PINS, /* p */
/* wSourceID contains STM_NB_IN_PINS elements */
/* .wSourceID = {0}, */
.bNrOutPins = 0x02, /* q */
.dTraceFormat = {
[0] = __constant_cpu_to_le32(DC_TRACE_MIPI_FORMATED_STPV1),
[1] = __constant_cpu_to_le32(DC_TRACE_MIPI_FORMATED_STPV1),
},
.dStreamID = __constant_cpu_to_le32(0xFFFFFFFF),
.bControlSize = 0x02, /* n */
.bmControl = {
[0] = 0xFF,
[1] = 0x3F,
},
.wAuxDataSize = __constant_cpu_to_le16(24), /* m */
.qBaseAddress = 0, /* revision */
.hIPID = {
[0] = 0,
[1] = 0,
},
/* .iDebugUnitType = DYNAMIC, */
};
static struct usb_interface_descriptor trace_data_interface_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_DEBUG,
.bInterfaceSubClass = USB_SUBCLASS_DVC_TRACE,
/* .bInterfaceProtocol = 0, */
};
static struct usb_endpoint_descriptor trace_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 trace_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 trace_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 trace_superspeed_in_comp_desc = {
.bLength = USB_DT_SS_EP_COMP_SIZE,
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
};
static struct usb_descriptor_header *fs_trace_descs[] = {
(struct usb_descriptor_header *) &trace_iad_desc,
(struct usb_descriptor_header *) &trace_data_interface_desc,
(struct usb_descriptor_header *) &trace_fullspeed_in_desc,
(struct usb_descriptor_header *) &trace_interface_desc,
(struct usb_descriptor_header *) &trace_debug_attri_desc,
(struct usb_descriptor_header *) &trace_output_conn_pti_desc,
(struct usb_descriptor_header *) &trace_output_conn_usb_desc,
(struct usb_descriptor_header *) &trace_debug_unit_stm_desc,
NULL,
};
static struct usb_descriptor_header *hs_trace_descs[] = {
(struct usb_descriptor_header *) &trace_iad_desc,
(struct usb_descriptor_header *) &trace_data_interface_desc,
(struct usb_descriptor_header *) &trace_highspeed_in_desc,
(struct usb_descriptor_header *) &trace_interface_desc,
(struct usb_descriptor_header *) &trace_debug_attri_desc,
(struct usb_descriptor_header *) &trace_output_conn_pti_desc,
(struct usb_descriptor_header *) &trace_output_conn_usb_desc,
(struct usb_descriptor_header *) &trace_debug_unit_stm_desc,
NULL,
};
static struct usb_descriptor_header *ss_trace_descs[] = {
(struct usb_descriptor_header *) &trace_iad_desc,
(struct usb_descriptor_header *) &trace_data_interface_desc,
(struct usb_descriptor_header *) &trace_superspeed_in_desc,
(struct usb_descriptor_header *) &trace_superspeed_in_comp_desc,
(struct usb_descriptor_header *) &trace_interface_desc,
(struct usb_descriptor_header *) &trace_debug_attri_desc,
(struct usb_descriptor_header *) &trace_output_conn_pti_desc,
(struct usb_descriptor_header *) &trace_output_conn_usb_desc,
(struct usb_descriptor_header *) &trace_debug_unit_stm_desc,
NULL,
};
/* string descriptors: */
#define DVCTRACE_CTRL_IDX 0
#define DVCTRACE_DATA_IDX 1
#define DVCTRACE_IAD_IDX 2
#define DVCTRACE_CONN_PTI_IDX 3
#define DVCTRACE_CONN_USB_IDX 4
#define DVCTRACE_UNIT_STM_IDX 5
/* static strings, in UTF-8 */
static struct usb_string trace_string_defs[] = {
[DVCTRACE_CTRL_IDX].s = "Debug Sub-Class DvC.Trace (Control)",
[DVCTRACE_DATA_IDX].s = "Debug Sub-Class DvC.Trace (Data)",
[DVCTRACE_IAD_IDX].s = "Debug Sub-Class DvC.Trace",
[DVCTRACE_CONN_PTI_IDX].s = "MIPI PTIv1 output Connector ",
[DVCTRACE_CONN_USB_IDX].s = "USB Device output connector",
[DVCTRACE_UNIT_STM_IDX].s = "MIPI STM Debug Unit",
{ /* ZEROES END LIST */ },
};
static struct usb_gadget_strings trace_string_table = {
.language = 0x0409, /* en-us */
.strings = trace_string_defs,
};
static struct usb_gadget_strings *trace_strings[] = {
&trace_string_table,
NULL,
};
/* temporary var used between dvc_trace_open() and dvc_trace_gadget_bind() */
static struct dvc_trace_dev *_dvc_trace_dev;
static inline struct dvc_trace_dev *func_to_dvc_trace(struct usb_function *f)
{
return container_of(f, struct dvc_trace_dev, function);
}
static int dvc_trace_is_enabled(void)
{
if (!stm_is_enabled()) {
pr_info("%s STM/PTI block is not enabled\n", __func__);
return 0;
}
return 1;
}
static struct usb_request *dvc_trace_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_trace_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_trace_req_put(struct dvc_trace_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_trace_req_get(struct dvc_trace_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_trace_set_disconnected(struct dvc_trace_dev *dev)
{
dev->transfering = 0;
}
static void dvc_trace_complete_in(struct usb_ep *ep, struct usb_request *req)
{
struct dvc_trace_dev *dev = _dvc_trace_dev;
if (req->status != 0)
dvc_trace_set_disconnected(dev);
dvc_trace_req_put(dev, &dev->tx_idle, req);
wake_up(&dev->write_wq);
}
static inline int dvc_trace_lock(atomic_t *excl)
{
if (atomic_inc_return(excl) == 1) {
return 0;
} else {
atomic_dec(excl);
return -1;
}
}
static inline void dvc_trace_unlock(atomic_t *excl)
{
atomic_dec(excl);
}
static int trace_create_bulk_endpoints(struct dvc_trace_dev *dev,
struct usb_endpoint_descriptor *in_desc,
struct usb_ss_ep_comp_descriptor *in_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 |= 0x1;
ep = usb_ep_autoconfig_ss(cdev->gadget, in_desc, in_comp_desc);
if (!ep) {
pr_err("%s usb_ep_autoconfig for ep_in failed\n", __func__);
return -ENODEV;
}
pr_debug("%s usb_ep_autoconfig for ep_in got %s\n", __func__, ep->name);
ep->driver_data = dev; /* claim the endpoint */
dev->ep_in = ep;
for (i = 0; i < TRACE_TX_REQ_MAX; i++) {
req = dvc_trace_request_new(dev->ep_in, TRACE_BULK_BUFFER_SIZE,
(dma_addr_t)TRACE_BULK_IN_BUFFER_ADDR);
if (!req)
goto fail;
req->complete = dvc_trace_complete_in;
dvc_trace_req_put(dev, &dev->tx_idle, req);
pr_debug("%s req= %x : for %s predefined TRB\n", __func__,
(uint)req, ep->name);
}
return 0;
fail:
pr_err("%s could not allocate requests\n", __func__);
return -1;
}
static ssize_t dvc_trace_start_transfer(size_t count)
{
struct dvc_trace_dev *dev = _dvc_trace_dev;
struct usb_request *req = 0;
int r = count, xfer;
int ret;
pr_debug("%s\n", __func__);
if (!_dvc_trace_dev)
return -ENODEV;
if (dvc_trace_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->write_wq,
(dev->online || dev->error));
if (ret < 0) {
/* not at CONFIGURED state */
pr_info("%s !dev->online already\n", __func__);
dvc_trace_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\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_trace_req_get(dev, &dev->tx_idle)));
if (ret < 0) {
r = ret;
break;
}
if (req != 0) {
if (count > TRACE_BULK_BUFFER_SIZE)
xfer = TRACE_BULK_BUFFER_SIZE;
else
xfer = count;
pr_debug("%s queue tx_idle list req to dev->ep_in\n",
__func__);
req->no_interrupt = 1;
req->context = &dev->function;
req->length = xfer;
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;
dev->transfering = 0;
r = -EIO;
break;
}
pr_debug("%s: xfer=%d/%d queued req/%x\n", __func__,
xfer, r, (uint)req);
dvc_trace_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_trace_req_put(dev, &dev->tx_idle, req);
} else {
dev->transfering = 1;
}
dvc_trace_unlock(&dev->write_excl);
pr_debug("%s end\n", __func__);
return ret;
}
static int dvc_trace_disable_transfer(void)
{
struct dvc_trace_dev *dev = _dvc_trace_dev;
struct usb_request *req = 0;
int ret;
pr_debug("%s\n", __func__);
if (!_dvc_trace_dev)
return -ENODEV;
if (dvc_trace_lock(&dev->write_excl))
return -EBUSY;
if (dev->error) {
pr_debug("%s dev->error\n", __func__);
dvc_trace_unlock(&dev->write_excl);
return -EIO;
}
if ((!dev->online) || (!dev->transfering)) {
pr_debug("%s !dev->online OR !dev->transfering\n", __func__);
dvc_trace_unlock(&dev->write_excl);
return -EIO;
}
/* get an xfer tx request to use */
while ((req = dvc_trace_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;
dvc_trace_unlock(&dev->write_excl);
return -EIO;
}
pr_debug("%s: dequeued req/%x\n", __func__, (uint)req);
}
dvc_trace_unlock(&dev->write_excl);
return 1;
}
static int dvc_trace_open(struct inode *ip, struct file *fp)
{
pr_debug("%s\n", __func__);
if (!_dvc_trace_dev)
return -ENODEV;
if (dvc_trace_lock(&_dvc_trace_dev->open_excl))
return -EBUSY;
fp->private_data = _dvc_trace_dev;
/* clear the error latch */
_dvc_trace_dev->error = 0;
_dvc_trace_dev->transfering = 0;
return 0;
}
static int dvc_trace_release(struct inode *ip, struct file *fp)
{
pr_debug("%s\n", __func__);
dvc_trace_unlock(&_dvc_trace_dev->open_excl);
return 0;
}
/* file operations for DvC.Trace device /dev/usb_dvc_trace */
static const struct file_operations dvc_trace_fops = {
.owner = THIS_MODULE,
.open = dvc_trace_open,
.release = dvc_trace_release,
};
static struct miscdevice dvc_trace_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "usb_dvc_trace",
.fops = &dvc_trace_fops,
};
static int dvc_trace_ctrlrequest(struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
struct dvc_trace_dev *dev = _dvc_trace_dev;
int value = -EOPNOTSUPP;
int ret;
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_trace_disable_transfer();
value = 0;
break;
/* DC_REQUEST_SET_TRACE ... start trace transfer */
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| DC_REQUEST_SET_TRACE:
pr_info("%s DC_REQUEST_SET_TRACE v%04x i%04x l%u\n", __func__,
w_value, w_index, w_length);
if (!w_index)
ret = dvc_trace_disable_transfer();
else
ret = dvc_trace_start_transfer(4096);
if (ret < 0)
value = -EINVAL;
else
value = (int) w_index;
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_trace_function_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct dvc_trace_dev *dev = func_to_dvc_trace(f);
int id;
int ret;
int status;
dev->cdev = cdev;
pr_debug("%s dev: %p\n", __func__, dev);
/* maybe allocate device-global string IDs, and patch descriptors */
if (trace_string_defs[DVCTRACE_CTRL_IDX].id == 0) {
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_DATA_IDX].id = status;
trace_data_interface_desc.iInterface = status;
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_CTRL_IDX].id = status;
trace_interface_desc.iInterface = status;
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_IAD_IDX].id = status;
trace_iad_desc.iFunction = status;
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_CONN_PTI_IDX].id = status;
trace_output_conn_pti_desc.iConnection = status;
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_CONN_USB_IDX].id = status;
trace_output_conn_usb_desc.iConnection = status;
status = usb_string_id(cdev);
if (status < 0)
return status;
trace_string_defs[DVCTRACE_UNIT_STM_IDX].id = status;
trace_debug_unit_stm_desc.iDebugUnitType = status;
}
/* allocate interface ID(s) */
id = usb_interface_id(c, f);
if (id < 0)
return id;
dev->data_id = id;
trace_data_interface_desc.bInterfaceNumber = id;
trace_iad_desc.bFirstInterface = id;
id = usb_interface_id(c, f);
if (id < 0)
return id;
dev->ctrl_id = id;
trace_interface_desc.bInterfaceNumber = id;
/* allocate endpoints */
ret = trace_create_bulk_endpoints(dev, &trace_fullspeed_in_desc,
&trace_superspeed_in_comp_desc);
if (ret)
return ret;
/* support high speed hardware */
if (gadget_is_dualspeed(c->cdev->gadget)) {
trace_highspeed_in_desc.bEndpointAddress =
trace_fullspeed_in_desc.bEndpointAddress;
}
if (gadget_is_superspeed(c->cdev->gadget)) {
trace_superspeed_in_desc.bEndpointAddress =
trace_fullspeed_in_desc.bEndpointAddress;
}
pr_debug("%s speed %s: IN/%s\n",
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
f->name, dev->ep_in->name);
return 0;
}
static void
dvc_trace_function_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct dvc_trace_dev *dev = func_to_dvc_trace(f);
struct usb_request *req;
dev->online = 0;
dev->online_data = 0;
dev->online_ctrl = 0;
dev->error = 0;
trace_string_defs[DVCTRACE_CTRL_IDX].id = 0;
wake_up(&dev->write_wq);
while ((req = dvc_trace_req_get(dev, &dev->tx_idle)))
dvc_trace_request_free(req, dev->ep_in);
}
static int dvc_trace_function_set_alt(struct usb_function *f,
unsigned intf, unsigned alt)
{
struct dvc_trace_dev *dev = func_to_dvc_trace(f);
struct usb_composite_dev *cdev = f->config->cdev;
int ret;
pr_debug("%s intf: %d alt: %d\n", __func__, intf, alt);
if (intf == trace_interface_desc.bInterfaceNumber)
dev->online_ctrl = 1;
if (intf == trace_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 err %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;
}
dev->online_data = 1;
}
if (dev->online_data && dev->online_ctrl) {
dev->online = 1;
dev->transfering = 0;
}
/* readers may be blocked waiting for us to go online */
wake_up(&dev->write_wq);
return 0;
}
static void dvc_trace_function_disable(struct usb_function *f)
{
struct dvc_trace_dev *dev = func_to_dvc_trace(f);
struct usb_composite_dev *cdev = dev->cdev;
pr_debug("%s dev %p\n", __func__, cdev);
if (dev->transfering)
dvc_trace_disable_transfer();
dev->online = 0;
dev->online_data = 0;
dev->online_ctrl = 0;
dev->error = 0;
usb_ep_disable(dev->ep_in);
/* writer may be blocked waiting for us to go online */
wake_up(&dev->write_wq);
pr_debug("%s : %s disabled\n", __func__, dev->function.name);
}
static int dvc_trace_bind_config(struct usb_configuration *c)
{
struct dvc_trace_dev *dev = _dvc_trace_dev;
pr_debug("%s\n", __func__);
dev->cdev = c->cdev;
dev->function.name = "dvctrace";
dev->function.strings = trace_strings;
dev->function.fs_descriptors = fs_trace_descs;
dev->function.hs_descriptors = hs_trace_descs;
dev->function.ss_descriptors = ss_trace_descs;
dev->function.bind = dvc_trace_function_bind;
dev->function.unbind = dvc_trace_function_unbind;
dev->function.set_alt = dvc_trace_function_set_alt;
dev->function.disable = dvc_trace_function_disable;
return usb_add_function(c, &dev->function);
}
static int dvc_trace_setup(void)
{
struct dvc_trace_dev *dev;
int ret;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
spin_lock_init(&dev->lock);
INIT_LIST_HEAD(&dev->tx_idle);
INIT_LIST_HEAD(&dev->tx_xfer);
init_waitqueue_head(&dev->write_wq);
atomic_set(&dev->open_excl, 0);
atomic_set(&dev->write_excl, 0);
_dvc_trace_dev = dev;
ret = misc_register(&dvc_trace_device);
if (ret)
goto err;
return 0;
err:
kfree(dev);
pr_err("DvC.Trace gadget driver failed to initialize\n");
return ret;
}
static void dvc_trace_cleanup(void)
{
misc_deregister(&dvc_trace_device);
kfree(_dvc_trace_dev);
_dvc_trace_dev = NULL;
}