此处将以zero.c为例进行讲解。

第一次接触zero.c驱动的时候,是因为某项目需要,提供一种usb字符设备,希望能够通过字符设备打开,读取和发送文件。当时能想到的就是zero.c文件,本打算按照zero驱动的编写方式,自己编写一个字符驱动,但后来为了节省工作时间,直接修改zero驱动,增加了字符设备的注册和操作函数。

zero.c驱动提供了丰富的功能接口,既包含自收自发的loopback(回环功能),又包含了主从通信的source link功能。该两功分别绑定各自的配置,配置在实际使用的过程中,是不同共存的,只能切换操作,当然,如果你足够厉害,将两功能汇总到一个配置里面,也不是不可能的事。该两种功能的具体实现会在下面详细介绍。

zero.c驱动功能上主要是实现了两个bulk端点,in和out,根据选定的配置,in和out针对性就不同了。但是如果你要是想和host端主机通信,source link功能必然是完美的选择,因为loopback顾明思意,就是采用的回环功能。

在介绍zero.c驱动过的实现之前,先介绍下zero中相关的变量和函数。

variable : //该驱动涉及到的变量和结构体。
|| autoresume
|| autoresume_interval_
|| autoresume_step_ms
|| autoresume_timer //表示zero设备与resume操作相关。|| dev_strings //设备字符串描述符
|| device_desc //设备描述符,唯一
|| func_inst_lb //loopback功能实例 此处特别的重要
|| func_inst_ss //source link功能实例,也特别的重要。
|| func_lb //loopback接口,又称功能
|| func_ss //source link接口,又称功能
|| gzero_options //涉及到zero设备中packet和bulk_buf长度等|| longname //产品名称|| loopdefault //是否支持loopback功能通过该变量判定,为0表示选择source link功能。
|| max_autoresume //最长的自动恢复时间,与定时器设置相关
|| otg_desc //关于otg功能的
|| otg_descriptor
|| serial /Serial 变量存储的是设备序列号,对于一个非正式设备,显得不是那么重要,随便填充一下
|| sourcesink_driver //基本上后缀命名的driver表示是usb config
|| strings_dev //字符串描述符
|| stringtab_dev //gadget字符串描述符,包括字符串描述符,以及支持的语种。默认是英文。
|| zero_driver //usb_composite_driver function
|| cleanup //卸载驱动函数。
|| init //驱动注册函数。
|| ss_config_setup //枚举响应函数,只有source link功能支持。
|| zero_autoresum //定时器调用函数
|| zero_bind //没有绑定,就没有各种配置和接口的实现,也就没有驱动和设备的绑定
|| zero_resume //个人认为主要是省电模式采用,设备处于空闲时,进入挂起状态,被唤醒后自动回复正常状态
    || zero_suspend           //设备挂起。

    || zero_unbind            //是否功能和功能实例。

之所以介绍zero驱动的相关变量、结构体和函数主要是为了对驱动整体框架有所了解。在分析该驱动的过程中,我们主要关注那些点,以及这些是怎么封装的,这样会对我们改zero驱动会有很大的帮助。

1=======模块的注册和卸载。此处不用多讲,我的另一篇文章linux usb gadget框架概述已经较为详细的介绍了驱动的注册过程。

 static __refdata struct usb_composite_driver zero_driver = {
.name = "zero",
.dev = &device_desc,
.strings = dev_strings,
.max_speed = USB_SPEED_SUPER, //此处还支持usb3.0?没有测试过。
.bind = zero_bind,
.unbind = zero_unbind,
.suspend = zero_suspend,
.resume = zero_resume,
}; MODULE_AUTHOR("David Brownell");
MODULE_LICENSE("GPL"); static int __init init(void)
{
return usb_composite_probe(&zero_driver);
}
   module_init(init);

   static void __exit cleanup(void)
{
usb_composite_unregister(&zero_driver);
}
module_exit(cleanup);

2=====填充设备描述符。

   static struct usb_device_descriptor device_desc = {
.bLength = sizeof device_desc,
.bDescriptorType = USB_DT_DEVICE,//描述符类型,此处表示设备描述符,非接口描述符 .bcdUSB = cpu_to_le16(0x0200),
.bDeviceClass = USB_CLASS_VENDOR_SPEC, //#define USB_CLASS_VENDOR_SPEC   0xff 表示产商自定义类设备 .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM),
.idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM),
.bNumConfigurations = , //介绍该设备有几个配置,此处写的两个,真好是loopback和source link两个配置。
};

3=====填充字符描述符。基本上usb_gadget_strings都是这个德行,封装三层,最后填充到zero_driver的.strings = dev_strings,中去。

 static struct usb_string strings_dev[] = {
[USB_GADGET_MANUFACTURER_IDX].s = "",
[USB_GADGET_PRODUCT_IDX].s = longname,
[USB_GADGET_SERIAL_IDX].s = serial,
[USB_GZERO_SS_DESC].s = "source and sink data",
[USB_GZERO_LB_DESC].s = "loop input to output",
{ } /* end of list */
}; static struct usb_gadget_strings stringtab_dev = {
.language = 0x0409, /* en-us */
.strings = strings_dev,
}; static struct usb_gadget_strings *dev_strings[] = {
&stringtab_dev,
NULL,
};

 ==========填充配置描述符。loopback_driver&sourcesink_driver

   static struct usb_configuration sourcesink_driver = {
.label = "source/sink", //标签在主机枚举的时候获取,如果主机是window,可以在管理/设备管理器中找到
.setup = ss_config_setup, //setup主要是响应枚举过程中控制请求某些特效的操作。
.bConfigurationValue = , //当使用SetConfiguration和GetConfiguration请求时所指定的配置索引值。这个在响应枚举过程是十分重要的。
.bmAttributes = USB_CONFIG_ATT_SELFPOWER, //自供电
/* .iConfiguration = DYNAMIC */
};
static struct usb_configuration loopback_driver = {
.label = "loopback",
.bConfigurationValue = ,
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
/* .iConfiguration = DYNAMIC */
};

 ==========填充接口描述符。func_ss&func_lb

该接口的实现挺有技巧的,下面详细讲述之。

 static struct usb_function *func_ss;
static struct usb_function_instance *func_inst_ss; //视乎编写一个gadget驱动的通用方法需要填充一个功能实例。
static struct usb_function *func_lb;
static struct usb_function_instance *func_inst_lb; //首先,填充usb_function_instance 这个结构体。
 func_inst_ss = usb_get_function_instance("SourceSink");  
 func_inst_lb = usb_get_function_instance("Loopback");

38 struct usb_function_instance *usb_get_function_instance(const char *name) //此处是怎么通过字符串得到的功能实例呢?
39 {
40 struct usb_function_instance *fi;
41 int ret;
42
43 fi = try_get_usb_function_instance(name);
44 if (!IS_ERR(fi))
45 return fi;
46 ret = PTR_ERR(fi);
47 if (ret != -ENOENT)
48 return fi;
49 ret = request_module("usbfunc:%s", name);
50 if (ret < 0)
51 return ERR_PTR(ret);
52 return try_get_usb_function_instance(name); //关键在于这个函数。
53 }

11 static struct usb_function_instance *try_get_usb_function_instance(const char *name)
12 {
13 struct usb_function_driver *fd;
14 struct usb_function_instance *fi;
15
16 fi = ERR_PTR(-ENOENT);
17 mutex_lock(&func_lock);
18 list_for_each_entry(fd, &func_list, list) {  //便利所有的功能链表,通过功能链表获取功能驱动,那功能驱动从哪里来的?那就设计到每个功能驱动的注册了。
19
20 if (strcmp(name, fd->name))
21 continue;
22
23 if (!try_module_get(fd->mod)) {
24 fi = ERR_PTR(-EBUSY);
25 break;
26 }
27 fi = fd->alloc_inst();  //功能实例是在这里生成的。
28 if (IS_ERR(fi))
29 module_put(fd->mod);
30 else
31 fi->fd = fd;
32 break;
33 }
34 mutex_unlock(&func_lock);
35 return fi;
36 }

那现在问题的关键点是在于找到usb_function_driver这个结构体,驱动肯定是需要调用注册函数进行注册的,功能驱动肯定是在各自功能实现文件里。

f_sourcesink.c f_loopback.c

usb_function_register(&SourceSinkusb_func);
int usb_function_register(struct usb_function_driver *newf)
{
struct usb_function_driver *fd;
int ret; ret = -EEXIST; mutex_lock(&func_lock);
list_for_each_entry(fd, &func_list, list) {
if (!strcmp(fd->name, newf->name))
goto out;
}
ret = ;
list_add_tail(&newf->list, &func_list); //从该函数可以看出,该注册函数正好是讲功能实例结构体中的链表加入全局链表func_list中,这样通过list_for_entry就可以获取实例了
 out: 

 mutex_unlock(&func_lock); 

 return ret;  }

那SourceSinkusb_func这个变量从哪里来呢?用ag搜索了整个内核目录都没有找到,这个时候不得不好好分析linux中宏定义的厉害了。

 DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst,
source_sink_alloc_func); #define DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc) \
static struct usb_function_driver _name ## usb_func = { \ //这个地方就不用过多的介绍了吧。
.name = __stringify(_name), \
.mod = THIS_MODULE, \
.alloc_inst = _inst_alloc, \ //所以在try_get_usb_function_instance函数中其实才是真正的开始对功能实例进行初始化。
.alloc_func = _func_alloc, \ //usb_get_function其实也是调用的该函数实现得功能的初始化。
}; \
MODULE_ALIAS("usbfunc:"__stringify(_name));
下面在再来看一下alloc_func做了哪些操作。
 static struct usb_function *source_sink_alloc_func(
struct usb_function_instance *fi)
{
struct f_sourcesink *ss;
struct f_ss_opts *ss_opts; ss = kzalloc(sizeof(*ss), GFP_KERNEL);
if (!ss)
return NULL; ss_opts = container_of(fi, struct f_ss_opts, func_inst); mutex_lock(&ss_opts->lock);
ss_opts->refcnt++;
mutex_unlock(&ss_opts->lock); pattern = ss_opts->pattern;
isoc_interval = ss_opts->isoc_interval;
isoc_maxpacket = ss_opts->isoc_maxpacket;
isoc_mult = ss_opts->isoc_mult;
isoc_maxburst = ss_opts->isoc_maxburst;
buflen = ss_opts->bulk_buflen; ss->function.name = "source/sink"; //填充功能名称
ss->function.bind = sourcesink_bind;//主要实现了设备和驱动的绑定,已经端点的初始化的操作
ss->function.set_alt = sourcesink_set_alt;//这个会根据配置中功能的先后顺序,将某个功能配置为0
ss->function.get_alt = sourcesink_get_alt;
ss->function.disable = sourcesink_disable;
ss->function.setup = sourcesink_setup;
ss->function.strings = sourcesink_strings; ss->function.free_func = sourcesink_free_func; return &ss->function;
}

 ==========填充端点描述符。 //端点描述符有两种一个是支持全是一种是支持高速,主要针对host端是否支持来定,在bind函数中,将其加入function中。

 static struct usb_endpoint_descriptor fs_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
}; static struct usb_endpoint_descriptor fs_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_endpoint_descriptor fs_iso_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = cpu_to_le16(),
.bInterval = ,
}; static struct usb_endpoint_descriptor fs_iso_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = cpu_to_le16(),
.bInterval = ,
};
static struct usb_descriptor_header *fs_source_sink_descs[] = {
(struct usb_descriptor_header *) &source_sink_intf_alt0, //在枚举过程中,都是通过usb_descriptor_header指针获取的。
(struct usb_descriptor_header *) &fs_sink_desc,
(struct usb_descriptor_header *) &fs_source_desc,
(struct usb_descriptor_header *) &source_sink_intf_alt1,
#define FS_ALT_IFC_1_OFFSET 3
(struct usb_descriptor_header *) &fs_sink_desc,
(struct usb_descriptor_header *) &fs_source_desc,
(struct usb_descriptor_header *) &fs_iso_sink_desc,
(struct usb_descriptor_header *) &fs_iso_source_desc,
NULL,
};

 至此,从驱动注册,各种配置描述符的初始化和实现已经讲完,是不是感觉什么也没讲明白,那是因为你不熟悉gadaget驱动的注册和枚举响应过程,下面将花大量的篇幅介绍gadget设备的枚举过程。

枚举过程主要分为如下几个步骤:

要了解gadget驱动的枚举过程,就必须了解usb设备的中断响应。要了解usb的中断响应就必须知道usb控制的注册过程,因为中断的注册实在控制器的注册中完成的。

下面将花少量篇幅介绍下udc的注册过程,以omap_udc.c为例进行讲解。

194 static struct platform_device udc_device = {
195 .name = "omap_udc",
196 .id = -1,
197 .dev = {
198 .dma_mask = &udc_dmamask,
199 .coherent_dma_mask = 0xffffffff,
200 },
201 .num_resources = ARRAY_SIZE(udc_resources),
202 .resource = udc_resources,
203 };

  static const char driver_name[] = "omap_udc";//此处是平台设备能够注册成功的关键,平台设备一般在板级信息中注册。
static struct platform_driver udc_driver = {
.probe = omap_udc_probe,
.remove = omap_udc_remove,
.suspend = omap_udc_suspend,
.resume = omap_udc_resume,
.driver = {
.owner = THIS_MODULE,
.name = (char *) driver_name,
},
};
一当内核查找到平台设备和驱动设备中名称匹配,就会加重驱动,调用驱动probe函数
 static int omap_udc_probe(struct platform_device *pdev)
{
int status = -ENODEV;
int hmc;
struct usb_phy *xceiv = NULL;
const char *type = NULL;
struct omap_usb_config *config = pdev->dev.platform_data;
struct clk *dc_clk = NULL;
struct clk *hhc_clk = NULL; if (cpu_is_omap7xx())
use_dma = ; /* NOTE: "knows" the order of the resources! */
if (!request_mem_region(pdev->resource[].start,
pdev->resource[].end - pdev->resource[].start + ,
driver_name)) {
DBG("request_mem_region failed\n");
return -EBUSY;
} 。。。。。。。。。。。
/* USB "non-iso" IRQ (PIO for all but ep0) */
2880 status = request_irq(pdev->resource[2].start, omap_udc_pio_irq, //最关键的函数,一当host发起任何从设备相关的操作,都会调用该函数。
, "omap_udc pio", udc);
if (status != ) {
ERR("can't get irq %d, err %d\n",
(int) pdev->resource[].start, status);
goto cleanup2;
}
 static irqreturn_t omap_udc_irq(int irq, void *_udc)
{
struct omap_udc *udc = _udc;
u16 irq_src;
irqreturn_t status = IRQ_NONE;
unsigned long flags; spin_lock_irqsave(&udc->lock, flags);
irq_src = omap_readw(UDC_IRQ_SRC); /* Device state change (usb ch9 stuff) */
if (irq_src & UDC_DS_CHG) {
devstate_irq(_udc, irq_src);
status = IRQ_HANDLED;
irq_src &= ~UDC_DS_CHG;
} /* EP0 control transfers */
1837 if (irq_src & (UDC_EP0_RX|UDC_SETUP|UDC_EP0_TX)) {//针对枚举过程,中断源肯定是控制端点0发送过来的。
ep0_irq(_udc, irq_src);//如果是控制端点,则调用端点0中断函数响应中断。
status = IRQ_HANDLED;
irq_src &= ~(UDC_EP0_RX|UDC_SETUP|UDC_EP0_TX);
}

//这个地方不对这个中断函数进行多讲,主要介绍其枚举响应分支。

           default:
delegate:
/* activate the ep0out fifo right away */
if (!udc->ep0_in && w_length) {
omap_writew(, UDC_EP_NUM);
omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
} /* gadget drivers see class/vendor specific requests,
1676 ┊* {SET,GET}_{INTERFACE,DESCRIPTOR,CONFIGURATION},
1677 ┊* and more
1678 ┊*/
VDBG("SETUP %02x.%02x v%04x i%04x l%04x\n",
u.r.bRequestType, u.r.bRequest,
w_value, w_index, w_length); #undef w_value
#undef w_index
#undef w_length /* The gadget driver may return an error here,
1688 ┊* causing an immediate protocol stall.
1689 ┊*
1690 ┊* Else it must issue a response, either queueing a
1691 ┊* response buffer for the DATA stage, or halting ep0
1692 ┊* (causing a protocol stall, not a real halt). A
1693 ┊* zero length buffer means no DATA stage.
1694 ┊*
1695 ┊* It's fine to issue that response after the setup()
1696 ┊* call returns, and this IRQ was handled.
1697 ┊*/
udc->ep0_setup = ;
spin_unlock(&udc->lock);
status = udc->driver->setup(&udc->gadget, &u.r); 这个函数可不是zero.c中那个setup函数,而是composite.c中的setup函数。下面我们具体分析之。

=======枚举过程的关键性函数。

在介绍该函数时,先介绍下至关重要的一个结构体,usb_request,该结构体就是数据发送和接受的载体,类似于网络中的skb.

   struct usb_request {
void *buf; //需要传输的数据都会在此填充。
unsigned length; //buf长度
dma_addr_t dma; //与dma操作相关的 struct scatterlist *sg; //视乎是分散聚集表鄙人不是很明白。
unsigned num_sgs; //如上
unsigned num_mapped_sgs; //如上 unsigned stream_id:; //The stream id, when USB3.0 bulk streams are being used
unsigned no_interrupt:; //If true, hints that no completion irq is needed. Helpful sometimes with deep request queues that are handled directly by DMA controllers.
unsigned zero:; //是否是0包。
unsigned short_not_ok:; //对于0包,判定该报是否允许其为错包 void (*complete)(struct usb_ep *ep, //包发送完成后,会调用该函数。
struct usb_request *req);
void *context; //很简单
struct list_head list; //同一类型的usb_request int status; //当前状态,Reports completion code, zero or a negative errno.
unsigned actual; //实际传输包的长度。
};
 composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
struct usb_request *req = cdev->req; //所有的数据传输都是靠usb_request函数完成的。
int value = -EOPNOTSUPP;
int status = ;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u8 intf = w_index & 0xFF;
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
struct usb_function *f = NULL;
u8 endp; /* partial re-init of the response message; the function or the
1241 ┊* gadget might need to intercept e.g. a control-OUT completion
1242 ┊* when we delegate to it.
1243 ┊*/
req->zero = ;
req->complete = composite_setup_complete;
req->length = ;
gadget->ep0->driver_data = cdev; switch (ctrl->bRequest) {
/* we handle all standard USB descriptors */
case USB_REQ_GET_DESCRIPTOR:
if (ctrl->bRequestType != USB_DIR_IN)
goto unknown;
switch (w_value >> ) { case USB_DT_DEVICE: //设备描述符
cdev->desc.bNumConfigurations =
count_configs(cdev, USB_DT_DEVICE);
cdev->desc.bMaxPacketSize0 =
cdev->gadget->ep0->maxpacket;
if (gadget_is_superspeed(gadget)) {
if (gadget->speed >= USB_SPEED_SUPER) {
cdev->desc.bcdUSB = cpu_to_le16(0x0300);
cdev->desc.bMaxPacketSize0 = ;
} else {
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} value = min(w_length, (u16) sizeof cdev->desc);
memcpy(req->buf, &cdev->desc, value);
break;
case USB_DT_DEVICE_QUALIFIER: //the structure is used by USB client drivers to retrieve a USB-defined device qualifier descriptor.
if (!gadget_is_dualspeed(gadget) ||
┊ gadget->speed >= USB_SPEED_SUPER)
break;
device_qual(cdev);
value = min_t(int, w_length,
sizeof(struct usb_qualifier_descriptor));
break;
case USB_DT_OTHER_SPEED_CONFIG:
if (!gadget_is_dualspeed(gadget) ||
┊ gadget->speed >= USB_SPEED_SUPER)
break;
/* FALLTHROUGH */
case USB_DT_CONFIG: //配置描述符
value = config_desc(cdev, w_value);
if (value >= )
value = min(w_length, (u16) value);
break;
case USB_DT_STRING: //字符串描述符
value = get_string(cdev, req->buf,
w_index, w_value & 0xff);
if (value >= )
value = min(w_length, (u16) value);
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget)) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
break;
}
break;
/* any number of configs can work */
case USB_REQ_SET_CONFIGURATION:
if (ctrl->bRequestType != )
goto unknown;
if (gadget_is_otg(gadget)) {
if (gadget->a_hnp_support)
DBG(cdev, "HNP available\n");
else if (gadget->a_alt_hnp_support)
DBG(cdev, "HNP on another port\n");
else
VDBG(cdev, "HNP inactive\n");
}
spin_lock(&cdev->lock);
value = set_config(cdev, ctrl, w_value);
spin_unlock(&cdev->lock);
break;
case USB_REQ_GET_CONFIGURATION:
if (ctrl->bRequestType != USB_DIR_IN)
goto unknown;
if (cdev->config)
*(u8 *)req->buf = cdev->config->bConfigurationValue;
else
*(u8 *)req->buf = ;
value = min(w_length, (u16) );
break;
/* function drivers must handle get/set altsetting; if there's
1334 ┊* no get() method, we know only altsetting zero works.
1335 ┊*/
case USB_REQ_SET_INTERFACE: //配置接口
if (ctrl->bRequestType != USB_RECIP_INTERFACE)
goto unknown;
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
if (!f)
break;
if (w_value && !f->set_alt)
break;
value = f->set_alt(f, w_index, w_value); //设置当前接口为第一个接口0,在上文中有提及该函数的作用
if (value == USB_GADGET_DELAYED_STATUS) {
DBG(cdev,
┊"%s: interface %d (%s) requested delayed status\n",
__func__, intf, f->name);
cdev->delayed_status++;
DBG(cdev, "delayed_status count %d\n",
cdev->delayed_status);
}
break;
case USB_REQ_GET_INTERFACE:
if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
goto unknown;
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
case USB_REQ_GET_INTERFACE:
if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
goto unknown;
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
if (!f)
break;
/* lots of interfaces only need altsetting zero... */
value = f->get_alt ? f->get_alt(f, w_index) : ;
if (value < )
break;
*((u8 *)req->buf) = value;
value = min(w_length, (u16) );
break; /*
1373 ┊* USB 3.0 additions:
1374 ┊* Function driver should handle get_status request. If such cb
1375 ┊* wasn't supplied we respond with default value = 0
1376 ┊* Note: function driver should supply such cb only for the first
1377 ┊* interface of the function
1378 ┊*/
case USB_REQ_GET_STATUS:
if (!gadget_is_superspeed(gadget))
goto unknown;
if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
goto unknown;
value = ; /* This is the length of the get_status reply */
value = ; /* This is the length of the get_status reply */
put_unaligned_le16(, req->buf);
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
if (!f)
break;
status = f->get_status ? f->get_status(f) : ;
if (status < )
break;
put_unaligned_le16(status & 0x0000ffff, req->buf);
break;
/*
1397 ┊* Function drivers should handle SetFeature/ClearFeature
1398 ┊* (FUNCTION_SUSPEND) request. function_suspend cb should be supplied
1399 ┊* only for the first interface of the function
1400 ┊*/
case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
if (!gadget_is_superspeed(gadget))
goto unknown;
if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
goto unknown;
switch (w_value) {
case USB_INTRF_FUNC_SUSPEND:
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
if (!f)
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
if (!f)
break;
value = ;
if (f->func_suspend)
value = f->func_suspend(f, w_index >> );
if (value < ) {
ERROR(cdev,
┊ ┊ "func_suspend() returned error %d\n",
┊ ┊ value);
value = ;
}
break;
}
break;
default:
unknown: //倘若枚举过程中发送了无法识别的请求。
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length); /* functions always handle their interfaces and endpoints...
1434 ┊* punt other recipients (other, WUSB, ...) to the current
1435 ┊* configuration code.
1436 ┊*
1437 ┊* REVISIT it could make sense to let the composite device
1435 ┊* configuration code.
1436 ┊*
1437 ┊* REVISIT it could make sense to let the composite device
1438 ┊* take such requests too, if that's ever needed: to work
1439 ┊* in config 0, etc.
1440 ┊*/
switch (ctrl->bRequestType & USB_RECIP_MASK) {
case USB_RECIP_INTERFACE:
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
break;
f = cdev->config->interface[intf];
break; case USB_RECIP_ENDPOINT:
endp = ((w_index & 0x80) >> ) | (w_index & 0x0f);
list_for_each_entry(f, &cdev->config->functions, list) {
if (test_bit(endp, f->endpoints))
break;
}
if (&f->list == &cdev->config->functions)
f = NULL;
break;
} if (f && f->setup)
value = f->setup(f, ctrl); //当时在分析该代码时候,很不明白function中为啥还设置setup函数,至少zero驱动中source sink只是简单的处理了下。
else {
struct usb_configuration *c; c = cdev->config;
if (!c)
goto done; /* try current config's setup */
if (c->setup) {
value = c->setup(c, ctrl);
goto done;
} /* try the only function in the current config */
if (!list_is_singular(&c->functions))
goto done;
f = list_first_entry(&c->functions, struct usb_function,
┊ ┊list);
if (f->setup)
value = f->setup(f, ctrl);
} goto done;
}
//其实在每一个case语句里面,都对req进行了填充。并调用发送函数usb_eq_queue,做好执行回馈函数。
/* respond with data transfer before status phase? */
if (value >= && value != USB_GADGET_DELAYED_STATUS) {
req->length = value;
req->zero = value < w_length;
value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);//将封装好的数据,发送到udc指定的fifo中返回给主机,并调用回调函数composite_setup_complete
if (value < ) {
DBG(cdev, "ep_queue --> %d\n", value);
req->status = ;
composite_setup_complete(gadget->ep0, req);//complete响应函数
}
} else if (value == USB_GADGET_DELAYED_STATUS && w_length != ) {
WARN(cdev,
"%s: Delayed status not supported for w_length != 0",
__func__);
} done:
/* device either stalls (value < 0) or reports success */
if (value < ) {
printk("control error %d req%02x.%02x v%04x i%04x l%d\n", value,
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
}
return value;
}
usb_ep_queue实际调用的是udc中实现的queue函数。
 static struct usb_ep_ops omap_ep_ops = {
.enable = omap_ep_enable,
.disable = omap_ep_disable, .alloc_request = omap_alloc_request,
.free_request = omap_free_request, 1123 .queue = omap_ep_queue,//该函数的作用主要是讲req写入
到对应的端点中的queue队列中去。
1002         list_add_tail(&req->queue, &ep->queue);

下面好好分析下填充好了req后,是怎样通过omap_ep_queue函数发送的。

omap_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
{
struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
struct omap_req *req = container_of(_req, struct omap_req, req);
struct omap_udc *udc;
unsigned long flags;
int is_iso = ; /* catch various bogus parameters */
if (!_req || !req->req.complete || !req->req.buf //毋庸置疑,为了代码的严谨性,对req进行相关判定是必然的。
|| !list_empty(&req->queue)) {
DBG("%s, bad params\n", __func__);
return -EINVAL;
} if (!_ep || (!ep->ep.desc && ep->bEndpointAddress)) {//这个时候端点描述符显得尤为重要。
DBG("%s, bad ep\n", __func__);
return -EINVAL;
}
if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
if (req->req.length > ep->ep.maxpacket)
return -EMSGSIZE;
is_iso = ;
} /* this isn't bogus, but OMAP DMA isn't the only hardware to
893 ┊* have a hard time with partial packet reads... reject it.
894 ┊*/
if (use_dma
&& ep->has_dma
&& ep->bEndpointAddress !=
&& (ep->bEndpointAddress & USB_DIR_IN) ==
&& (req->req.length % ep->ep.maxpacket) != ) {
DBG("%s, no partial packet OUT reads\n", __func__);
return -EMSGSIZE;
} udc = ep->udc; //获取ucd控制器实例,这个时候就是req和udc结合高潮来临的节奏。
if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN; if (use_dma && ep->has_dma)//dma支持吗?
usb_gadget_map_request(&udc->gadget, &req->req,
usb_gadget_map_request(&udc->gadget, &req->req,
(ep->bEndpointAddress & USB_DIR_IN)); VDBG("%s queue req %p, len %d buf %p\n",
ep->ep.name, _req, _req->length, _req->buf); spin_lock_irqsave(&udc->lock, flags); req->req.status = -EINPROGRESS;
req->req.actual = ; /* maybe kickstart non-iso i/o queues */
if (is_iso) {
u16 w; w = omap_readw(UDC_IRQ_EN);
w |= UDC_SOF_IE;
omap_writew(w, UDC_IRQ_EN);
} else if (list_empty(&ep->queue) && !ep->stopped && !ep->ackwait) {
int is_in; if (ep->bEndpointAddress == ) { //若是控制端点0
if (!udc->ep0_pending || !list_empty(&ep->queue)) {
spin_unlock_irqrestore(&udc->lock, flags);
return -EL2HLT;
} /* empty DATA stage? */
is_in = udc->ep0_in;
if (!req->req.length) { //对于端点0,并且或者req数据长度为0,显然,不用进行回复,直接调用done函数表示完成传输。 /* chip became CONFIGURED or ADDRESSED
941 ┊* earlier; drivers may already have queued
942 ┊* requests to non-control endpoints
943 ┊*/
if (udc->ep0_set_config) {
u16 irq_en = omap_readw(UDC_IRQ_EN); irq_en |= UDC_DS_CHG_IE | UDC_EP0_IE;
if (!udc->ep0_reset_config)
irq_en |= UDC_EPN_RX_IE
| UDC_EPN_TX_IE;
omap_writew(irq_en, UDC_IRQ_EN); //是能udc中断
} /* STATUS for zero length DATA stages is
955 ┊* always an IN ... even for IN transfers,
956 ┊* a weird case which seem to stall OMAP.
957 ┊*/
omap_writew(UDC_EP_SEL | UDC_EP_DIR,
UDC_EP_NUM);
omap_writew(UDC_CLR_EP, UDC_CTRL);
omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);//设置fifo使能
omap_writew(UDC_EP_DIR, UDC_EP_NUM);//配置端点放向和号 /* cleanup */
udc->ep0_pending = ; //将控制端点0设置为非挂起状态,即空闲状态。
done(ep, req, ); req = NULL; /* non-empty DATA stage */
} else if (is_in) {
omap_writew(UDC_EP_SEL | UDC_EP_DIR,
UDC_EP_NUM);
} else {
if (udc->ep0_setup) //若端点处理建立状态则说明该端点中数据还没发送完成,即需要发送请求。则将req加入控制器队列queue中。
goto irq_wait;
omap_writew(UDC_EP_SEL, UDC_EP_NUM);
}
} else {
is_in = ep->bEndpointAddress & USB_DIR_IN;
if (!ep->has_dma)
use_ep(ep, UDC_EP_SEL);
/* if ISO: SOF IRQs must be enabled/disabled! */
} if (ep->has_dma)
(is_in ? next_in_dma : next_out_dma)(ep, req);
else if (req) {
if ((is_in ? write_fifo : read_fifo)(ep, req) == ) //若是in则调用write_fifo,将req写入队列中,若是out,则调用read函数,并释放req
req = NULL;
deselect_ep();
if (!is_in) { omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
ep->ackwait = + ep->double_buf;
}
/* IN: 6 wait states before it'll tx */
}
} irq_wait:
/* irq handler advances the queue */
if (req != NULL)
list_add_tail(&req->queue, &ep->queue);//将usb加入队列中,等待中断处理,处理完后就调用中断回调函数,done
spin_unlock_irqrestore(&udc->lock, flags); return ;
} 自此,枚举过程已经讲完,如果成功的话,会返回0值,小于0值,那就得调用composite_setup_complete,表明枚举过程失败了。

讲完枚举过程,这里还讲下usb_request的传输过程。当时开发项目时,主要用到loopback功能,主要是对ep_in和out的包进行拦截,后并进行处理。

looback功能很按照字面意思很简单,就是普通的回复,即收到的req转给发出的req。

host和gadge之间的通信主要依靠中断,在gadget端,进行数据通信时,收到bulk处理指令后都会调用各自端点实现的complete函数,对于loopback来说,关键的处理函数如下:

此处针对omap_udc.c控制器简单的介绍下:

omap_udc_pio_irq-->write_fifo/read_fifo--->done->req.complete; //看起来很简单,其实是花费笔者较长时间才弄明白的。

 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 : /* normal completion? */
if (ep == loop->out_ep) { //如果host端发送过来的out_ep,则需要将包转发给ep_in端点。
/* loop this OUT packet back IN to the host */
req->zero = (req->actual < req->length);
req->length = req->actual;
status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC);//转发很简单,直接把收到的转到另外一个端点即可,对于需要通过loopback功能
//将req->buf中的包进行某些处理的,可以增加字符操作接口,对buf进行处理后再发送
//给对应得端点。
if (status == )
return; /* "should never get here" */
ERROR(cdev, "can't loop %s to %s: %d\n",
ep->name, loop->in_ep->name,
status);
} /* queue the buffer for some later OUT packet */
req->length = buflen;
status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC); //很显然,如果是in_ep端点的话,需要将out_ep中包发出。
if (status == )
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
284 ┊* of requests it submitted (just maintains qlen count), we
285 ┊* rely on the hardware driver to clean up on disconnect or
286 ┊* endpoint disable.
287 ┊*/
case -ECONNABORTED: /* hardware forced ep reset */
case -ECONNRESET: /* request dequeued */
case -ESHUTDOWN: /* disconnect from host */
free_ep_req(ep, req);
return;
}
}

usb gadge驱动设计之我是zero的更多相关文章

  1. Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  2. linux设备驱动之USB主机控制器驱动分析 【转】

    转自:http://blog.chinaunix.net/uid-20543183-id-1930831.html   ---------------------------------------- ...

  3. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

  4. DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对?

    写在前面 阅读目录: 具体业务场景 业务需求变化 "愚蠢"的应对 消息列表实现 消息详情页实现 消息发送.回复.销毁等实现 回到原点的一些思考 业务需求变化,领域模型变化了吗? 对 ...

  5. DDD 领域驱动设计-两个实体的碰撞火花

    上一篇:<DDD 领域驱动设计-领域模型中的用户设计?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 在 ...

  6. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  7. 领域驱动设计实战—基于DDDLite的权限管理OpenAuth.net

    在园子里面,搜索一下“权限管理”至少能得到上千条的有效记录.记得刚开始工作的时候,写个通用的权限系统一直是自己的一个梦想.中间因为工作忙(其实就是懒!)等原因,被无限期搁置了.最近想想,自己写东西时, ...

  8. 我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践

    写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听你,清风吹送,田野短笛:第一次看你,半弯 ...

  9. 一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?

    写在前面 阅读目录: 问题根源是什么? <领域驱动设计-软件核心复杂性应对之道>分层概念 Repository(仓储)职责所在? Domain Model(领域模型)重新设计 Domain ...

随机推荐

  1. java多线程基础(二)--java多线程的基本使用

    java多线程的基本使用 在java中使用多线程,是通过继承Thread这个类或者实现Runnable这个接口或者实现Callable接口来完成多线程的. 下面是很简单的例子代码: package c ...

  2. launchctl

    Launchctl 系统启动时, 系统会以root用户的身份扫描/System/Library/LaunchDaemons和/Library/LaunchDaemons目录, 如果文件中有Disabl ...

  3. 性能测试学习第十天_controller

    集合点设置 controller虚拟多个用户执行脚本启动步骤不一定同步,集合点在脚本的某处设置一个标记,当有虚拟用户运行到这个标记的时候,停下等待所有用户都达到这个标记,再一同进行下面的步骤.这样可以 ...

  4. The program 'unzip' is currently not installed. You can install it by typing:

    linux解压遇到下面问题: The program 'unzip' is currently not installed. You can install it by typing: sudo ap ...

  5. .net 中的托管与非托管

    托管代码 托管代码就是Visual Basic .NET和C#编译器编译出来的代码.编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码.中间语言被封装在一个叫程序集(assemb ...

  6. 关于wav文件fft处理后x,y轴坐标数据的问题

    1.关于横坐标的频率的最大值是采样频率,那么每个点对应的频率值就很好算了:f(n) = [Fs/(N/2)]*n  (Fs是采样频率,常见的是44.1KHz(44100),N是采样点数,k表是第k个点 ...

  7. 从零开始的全栈工程师——js篇2.15(offsetLeft)

    元素的属性 Div.attributes 是所有标签属性构成的数据集合 Div.classList 是所有class名构成的数组集合 在classList的原型链上看以看到add()和remove() ...

  8. vue安装及环境搭建

    vue项目在pycharm里运行需要安装一个插件,打开settings,找到plugins,里面搜索vue.js,点击安装. vue安装 先安装node.js npm install -g @vue/ ...

  9. Browser History

    History 对象中包含用户(在浏览器窗口中)访问过的URL History 对象是window对象的一部分,可通过window.history属性对其进行访问. 注释:没有应用于History对象 ...

  10. Remote System Explorer Operation在eclipse后台一直跑 解决办法

    在用eclipse开发时,经常遇到卡死的情况,其中一种就是右下角出现:“Remote System Explorer Operation”,解决方案如下: 第一步:Eclipse -> Pref ...