16.4.2Chipidea(公司) USB UDC(USB设备控制器)驱动
Chipidea USB UDC驱动的主体代码路径:drivers/usb/chipidea/udc.c,代码清单16.33列出其初始化流程部分,并定义了usb_ep_ops、usb_gadget_ops,在最终进行usb_add_gadget_udc()之前填充好了UDC的端点列表。
代码清单16.33Chipidea USB UDC驱动实例
/**
* Endpoint-specific part of the API to the USB controller hardware
* Check "usb_gadget.h" for details
*/
static const struct usb_ep_ops usb_ep_ops = {
.enable = ep_enable,
.disable = ep_disable,
.alloc_request = ep_alloc_request,
.free_request = ep_free_request,
.queue = ep_queue,
.dequeue = ep_dequeue,
.set_halt = ep_set_halt,
.set_wedge = ep_set_wedge,
.fifo_flush = ep_fifo_flush,
};
/**
* Device operations part of the API to the USB controller hardware,
* which don't involve endpoints (or i/o)
* Check "usb_gadget.h" for details
*/
static const struct usb_gadget_ops usb_gadget_ops = {
.vbus_session = ci_udc_vbus_session,
.wakeup = ci_udc_wakeup,
.pullup = ci_udc_pullup,
.vbus_draw = ci_udc_vbus_draw,
.udc_start = ci_udc_start,
.udc_stop = ci_udc_stop,
};
static int init_eps(struct ci_hdrc *ci)
{
int retval = 0, i, j;
for (i = 0; i < ci->hw_ep_max/2; i++)
for (j = RX; j <= TX; j++) {
int k = i + j * ci->hw_ep_max/2;
struct ci_hw_ep *hwep = &ci->ci_hw_ep[k];
scnprintf(hwep->name, sizeof(hwep->name), "ep%i%s", i, (j == TX) ? "in" : "out");
hwep->ci= ci;
hwep->lock = &ci->lock;
hwep->td_pool = ci->td_pool;
hwep->ep.name = hwep->name;
hwep->ep.ops = &usb_ep_ops;
/*
* for ep0: maxP defined in desc, for other
* eps, maxP is set by epautoconfig() called
* by gadget layer
*/
usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0);
INIT_LIST_HEAD(&hwep->qh.queue);
hwep->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, &hwep->qh.dma);
if (hwep->qh.ptr == NULL)
retval = -ENOMEM;
else
memset(hwep->qh.ptr, 0, sizeof(*hwep->qh.ptr));
/*
* set up shorthands for ep0 out and in endpoints,
* don't add to gadget's ep_list
*/
if (i == 0) {
if (j == RX)
ci->ep0out = hwep;
else
ci->ep0in = hwep;
usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX);
continue;
}
list_add_tail(&hwep->ep.ep_list, &ci->gadget.ep_list);
}
return retval;
}
/**
* udc_start: initialize gadget role
* @ci: chipidea controller
*/
static int udc_start(struct ci_hdrc *ci)
{
struct device *dev = ci->dev;
int retval = 0;
spin_lock_init(&ci->lock);
ci->gadget.ops= &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
ci->gadget.is_otg = ci->is_otg ? 1 : 0;
ci->gadget.name = ci->platdata->name;
INIT_LIST_HEAD(&ci->gadget.ep_list);
/* alloc resources */
ci->qh_pool = dma_pool_create("ci_hw_qh", dev,sizeof(struct ci_hw_qh),
64, CI_HDRC_PAGE_SIZE);
if (ci->qh_pool == NULL)
return -ENOMEM;
ci->td_pool = dma_pool_create("ci_hw_td", dev,sizeof(struct ci_hw_td),
64, CI_HDRC_PAGE_SIZE);
if (ci->td_pool == NULL) {
retval = -ENOMEM;
goto free_qh_pool;
}
retval = init_eps(ci);
if (retval)
goto free_pools;
ci->gadget.ep0 = &ci->ep0in->ep;
retval = usb_add_gadget_udc(dev, &ci->gadget);
if (retval)
goto destroy_eps;
pm_runtime_no_callbacks(&ci->gadget.dev);
pm_runtime_enable(&ci->gadget.dev);
return retval;
destroy_eps:
destroy_eps(ci);
free_pools:
dma_pool_destroy(ci->td_pool);
free_qh_pool:
dma_pool_destroy(ci->qh_pool);
return retval;
}
16.4.3Loopback Function驱动
drivers/usb/gadget/function/f_loopback.c实现一个最简单的Loopback驱动,主要完成的工作如下:
1、实现usb_function实例及其中成员函数bind()、set_alt()、disable()、free_func()等成员函数
2、准备USB外设的配置描述符、接口描述符usb_interface_descriptor、端点描述符usb_endpoint_descriptor等
3、发起usb_request、处理usb_request的完成并回环
代码清单16.34是抽取drivers/usb/gadget/function/f_loopback.c文件中一个Function驱动主体结构的实例代码
代码清单16.34Loopback USB Gadget Function驱动实例
static struct usb_interface_descriptor loopback_intf = {
.bLength = sizeof loopback_intf,
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
/* .iInterface = DYNAMIC */
};
static struct usb_endpoint_descriptor fs_loop_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_descriptor_header *fs_loopback_descs[] = {
(struct usb_descriptor_header *) &loopback_intf,
(struct usb_descriptor_header *) &fs_loop_sink_desc,
(struct usb_descriptor_header *) &fs_loop_source_desc,
NULL,
};
/* function-specific strings: */
static struct usb_string strings_loopback[] = {
[0].s = "loop input to output",
{ } /* end of list */
};
static struct usb_gadget_strings stringtab_loop = {
.language = 0x0409, /* en-us */
.strings = strings_loopback,
};
static struct usb_gadget_strings *loopback_strings[] = {
&stringtab_loop,
NULL,
};
static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_loopback *loop = func_to_loop(f);
int id;
int ret;
/* allocate interface ID(s) */
id = usb_interface_id(c, f);
if (id < 0)
return id;
loopback_intf.bInterfaceNumber = id;
id = usb_string_id(cdev);
if (id < 0)
return id;
strings_loopback[0].id = id;
loopback_intf.iInterface = id;
/* allocate endpoints */
loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
if (!loop->in_ep) {
autoconf_fail:
ERROR(cdev, "%s: can't autoconfigure on %s\n",
f->name, cdev->gadget->name);
return -ENODEV;
}
loop->in_ep->driver_data = cdev; /* claim */
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
if (!loop->out_ep)
goto autoconf_fail;
loop->out_ep->driver_data = cdev; /* claim */
/* support high speed hardware */
hs_loop_source_desc.bEndpointAddress =
fs_loop_source_desc.bEndpointAddress;
hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
/* support super speed hardware */
ss_loop_source_desc.bEndpointAddress =
fs_loop_source_desc.bEndpointAddress;
ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs,
ss_loopback_descs);
if (ret)
return ret;
DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
(gadget_is_superspeed(c->cdev->gadget) ? "super" :
(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
f->name, loop->in_ep->name, loop->out_ep->name);
return 0;
}
static void lb_free_func(struct usb_function *f)
{
struct f_lb_opts *opts;
opts = container_of(f->fi, struct f_lb_opts, func_inst);
mutex_lock(&opts->lock);
opts->refcnt--; // 临界区
mutex_unlock(&opts->lock);
usb_free_all_descriptors(f);
kfree(func_to_loop(f));
}
static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
{
struct f_loopback *loop;
struct f_lb_opts *lb_opts;
loop = kzalloc(sizeof *loop, GFP_KERNEL);
if (!loop)
return ERR_PTR(-ENOMEM);
lb_opts = container_of(fi, struct f_lb_opts, func_inst);
mutex_lock(&lb_opts->lock);
lb_opts->refcnt++;
mutex_unlock(&lb_opts->lock);
buflen = lb_opts->bulk_buflen;
qlen = lb_opts->qlen;
if (!qlen)
qlen = 32;
loop->function.name = "loopback";
loop->function.bind = loopback_bind; // 绑定函数指针
loop->function.set_alt = loopback_set_alt;
loop->function.disable = loopback_disable;
loop->function.strings = loopback_strings;
loop->function.free_func = lb_free_func;
return &loop->function;
}
static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_loopback *loop = ep->driver_data;
struct usb_composite_dev *cdev = loop->function.config->cdev;
int status = req->status;
switch (status) {
case 0: /* normal completion? */
if (ep == loop->out_ep) {
req->zero = (req->actual < req->length);
req->length = req->actual;
}
/* queue the buffer for some later OUT packet */
req->length = buflen;
status = usb_ep_queue(ep, req, GFP_ATOMIC);
if (status == 0)
return;
/* "should never get here" */
/* FALLTHROUGH */
default:
ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
status, req->actual, req->length);
/* FALLTHROUGH */
/* NOTE: since this driver doesn't maintain an explicit record
* of requests it submitted (just maintains qlen count), we
* rely on the hardware driver to clean up on disconnect or
* endpoint disable.
*/
case -ECONNABORTED: /* hardware forced ep reset */
case -ECONNRESET: /* request dequeued */
case -ESHUTDOWN: /* disconnect from host */
free_ep_req(ep, req);
return;
}
}
static int enable_endpoint(struct usb_composite_dev *cdev, struct f_loopback *loop,
struct usb_ep *ep)
{
struct usb_request *req;
unsigned i;
int result;
/*
* one endpoint writes data back IN to the host while another endpoint
* just reads OUT packets
*/
result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
if (result)
goto fail0;
result = usb_ep_enable(ep);
if (result < 0)
goto fail0;
ep->driver_data = loop;
/*
* allocate a bunch of read buffers and queue them all at once.
* we buffer at most 'qlen' transfers; fewer if any need more
* than 'buflen' bytes each.
*/
for (i = 0; i < qlen && result == 0; i++) {
req = lb_alloc_ep_req(ep, 0);
if (!req)
goto fail1;
req->complete = loopback_complete;
result = usb_ep_queue(ep, req, GFP_ATOMIC);
if (result) {
ERROR(cdev, "%s queue req --> %d\n",
ep->name, result);
goto fail1;
}
}
return 0;
fail1:
usb_ep_disable(ep);
fail0:
return result;
}