简介之简介
Linux-USB Gadget 驱动框架(以下简称 Gadget)实现了USB 协议定义的设备端的软件功能。相对于 Linux USB主机端(Host) 驱动而言, Gadget 驱动出现较晚,它出现在2.4.23 以后, (作者的第一次 announce:或 ), 如果需要在老的 2.4 内核上支持 Gadget, 可以移植 2.4.31 代码,工作量较小。另外,2.6 内核标准版本里的可能比最新版本要老一些。 “mm” 补丁中的版本会比较新。
Gadget 框架提出了一套标准 API, 在底层, USB 设备控制器(USB Device Controller, UDC)驱动则实现这一套 API,不同的 UDC (通常是 SOC 的一部分) 需要不同的驱动, 甚至基于同样的 UDC 的不同板子也需要进行代码修改。这一层我们可以称之为平台相关层。
基于 API, Gadget 驱动实现了一套硬件无关的功能,这基本上可以对应到 USB 协议里 的各种 USB Class, 也有比如 USB Gadget Generic Serial 驱动,没有对应的 Class。当然,Gadget 驱动还是受限于底层提供的功能的。比如某些 Class 需要 USB Isochronous 端点,这时我们就不能支持该 Class。
普通的 Gadget 驱动只实现一个功能(比如, u 盘,usb 网卡)。复合设备可以支持多个功能,后面将仔细研究一下复合设备的实现。像智能手机, PDA这样的设备,硬件支持较丰富的端点、DMA Buffer, 给软件提了支持复合功能的基础。
有两点值得注意,第一是 usb gaget 驱动框架不象 usb 主机端有 usb core 的概念,usb 主机可能支持成百类型的外设,把通用功能抽象出来很有意义。Usb device 端则通常没有这个需求,一些通用功能抽象在一些 Helper 函数里就可以了。第二是 usb 2.0 里提出了 OTG 的概念,可以在同一接口上支持 host 以及 device 功能。OTG 是一个比较复杂的规范,以后有时间再研究。
控制器驱动
常见的 usb device 有 U 盘, usb 鼠标、键盘,usb 蓝牙模块,usb 读卡器,等等。这些设备比较简单,通常不会运行Linux。运行Linux Gadget 的通常是一些集成 CPU 以及很多外设接口的 SOC (System-on-Chip), 其中 CPU通常为 32 bit 的 CPU, 并且 udc 也是该 SOC 的一部分( 顺带还有 DMA 通道,FIFO)。
Linux 标准内核里支持各主流 SOC 的 udc 驱动,make menuconfig 一下可以看到具体列表,其中值得一提的是 dummy_hcd, 它是一个软件模拟的 udc, 在开发新的 gadget 驱动时很有帮助。
控制器驱动处理很少的 USB 控制请求(主要由硬件负责的部分)。其它所有的控制请求,比如返回描述符,设置当前配置,由 Gadget Driver 完成。控制器驱动一个主要责任就是负责管理各个端点的 I/O 队列,在 Gadget Driver 的 buffer 和硬件buffer 之间传输数据(通常是通过 DMA)。
我们前面提过,上层 Gadget 驱动能够实现什么功能要依赖底层提供的硬件条件。比如一个复合设备需要至少5 个端点,这些硬件特性通过一组 gadget_is_*()函数实现。
Gadget 驱动
基于底层提供的资源, Gadget 驱动可以运行在各种硬件平台上。重要的驱动有:
Ø Gadget Zero, 类似于 dummy hcd, 该驱动用于测试 udc 驱动。它会帮助您通过 USB-IF 测试。
Ø Ethernet over USB, 该驱动模拟以太网网口,它支持多种运行方式:
² CDC Ethernet: usb 规范规定的 Communications Device Class “Ethernet Model” protocol。
² CDC Subset: 对硬件要求最低的一种方式,主要是 Linux 主机支持该方式。
² RNDIS: 微软公司对 CDC Ethernet 的变种实现。
Ø File-backed Storage Gadget最常见的 U 盘功能实现。
Ø Serial Gadget 实现,包括:
² Generic Serial 实现(只需要Bulk-in/Bulk-out端点+ep0)
² CDC ACM 规范实现。
Ø Gadget Filesystem, 将 Gadget API 接口暴露给应用层,以便在应用层实现user mode driver。
Ø MIDI: 暴露ALSA接口,提供 recording 以及 playback 功能。
前面讲过,gadget api 提供了usb device controller 驱动和上层gadget驱动交互的接口。 UDC 驱动是服务提供者,而各种 gadget 驱动则是服务的使用者。其实还有一些通用代码,因为功能比较简单,我们称之为 helpe 函数。在阅读了 Gadget API 文档后,让我们开始阅读代码, udb 驱动代码比较复杂,我们先从 gadget 驱动看起。各种gadget 驱动中, 最简单的要数 g_zero 驱动。
g_zero 驱动简介
作为最简单的 gadget 驱动,g_zero 的功能基于两个 BULK 端点实现了简单的输入输出功能, 它可以用作写新的gadget 驱动的一个实例。 g_zero 驱动还有一个重要角色, 即配合 host 端的 usbtest (内核模块及用户层代码),用于测试底层 udc 驱动。当然,也可以是测试主机的控制器驱动。
两个 BULK 端点为一个 IN 端点, 一个 OUT端点。基于这两个(由底层提供的)端点,g_zero 驱动实现了两个configuration。 第一个 configuration 提供了 sink/source功能:两个端点一个负责输入,一个负责输出,其中输出的内容根据设置可以是全0,也可以是按照某种算法生成的数据。另一个 configuration 提供了 loopback 接口, IN端点负责把从 OUT 端点收到的数据反馈给 Host.
根据系统配置,g_zero 驱动提供了全速及高速功能,从而稍微增加了代码复杂度。另外,它还支持 otg 接口,从usb2.0 协议我们知道, otg 其实是usb device实现的一个补充功能。它增加了一套接口,使得同一设备可以在设备角色以及有限主机角色之切换。上层gadget驱动主要是在描述符方面提供配合支持。下面我们开始看代码。
模块初始化
static int (void)
{
/* a real value would likely come through some id prom
* or module option. this one takes at least two packets.
*/
(, "0123456789.0123456789.0123456789", sizeof );
return (&);
}
();
static void (void)
{
(&);
}
();
Serial 变量存储的是设备序列号,我们是一个非正式设备,随便填充一下。模块初始化函数调用 来向udc driver 注册一个 gadget 驱动, 我们这里是 。而退出函数则会做相反的操作:调用 取消原来的注册。像所有的模块初始化、退出函数一样,现在还看不出什么花头。我们再看一下 的定义:
static struct = {
#ifdef CONFIG_USB_GADGET_DUALSPEED
. = USB_SPEED_HIGH,
#else
. = USB_SPEED_FULL,
#endif
.function = (char *) ,
. = ,
.unbind = (),
. = ,
.disconnect = ,
.suspend = ,
.resume = ,
. = {
. = (char *) ,
.owner = ,
},
};
根据 CONFIG_USB_GADGET_DUALSPEED,代码选择是支持高速还是全速,我们使用的 PXA 平台支持高速传输,所以我们假定该配置变量为真。根据 Gadget API 文档(以及下面的代码调用图),在初始化阶段 函数会调用 bind 函数,而 setup 函数是用于处理 udc 驱动没有处理的控制传输部分。这两个函数是整个 zero gadget 驱动的精华部分。其它函数则只是为了完整性而提供,有兴趣者可以对照 Gadget API 文档及代码,自行研究。至于 .driver 成员变量,那主要是为LDM(linux device module)服务的。现在关于 LDM 的文档满天飞,这里就不多说了。
简单起见,我们目前不去深究 udc 驱动代码(那比我们的 g_zero 驱动要复杂很多, 而且很多代码硬件相关,需要阅读硬件 spec 才能理解),而是使用 kft及graphviz(见参考,colorant 大侠提供的文档)工具得到函数调用关系图:(我们这里使用 pxa udc驱动,如果使用 dummy_hcd 会得到类似但更简单的关系图)
从上图中,我们可以看到在初始化阶段, udc 驱动会调用 zero 驱动的 bind 函数,也会调用 zero 驱动的 setup 函数 (主要是得到一些描述符), setup 函数主要是在后面我们的 device 和主机连接后用于处理控制传输的响应(大部分)。在初始阶段只是顺便帮忙提供点信息,进行的是假传输,真提供信息给 udc 驱动。下面我们重点分析 bind 函数。
函数
static int
(struct *gadget)
{
struct *;
struct *ep;
int gcnum;
首先映入眼帘的是zero_bind 函数的参数,根据 Gadget API,一个 gadget 代表一个 usb slave设备。这个数据结构是在底层控制器驱动中静态分配的。Udc 驱动在调用 gadget 驱动各接口函数时都会提供这个数据结构。
/* FIXME this can't yet work right with SH ... it has only
* one configuration, numbered one.
*/
if ((gadget))
return -;
注意我们以前说过 gadget_is_* 系列函数提供了查询硬件能力的接口,这里用于判断是否是 SH 平台的 udc, 如果是, 直接出错返回:g_zero 驱动目前还不支持该平台。
/* Bulk-only drivers like this one SHOULD be able to
* autoconfigure on any sane usb controller driver,
* but there may also be important quirks to address.
*/
(gadget);
注意函数 不是由底层 udc 驱动实现,而是我们以前提过的 helper 函数的一部分。该函数功能很简单:用于清空 gadget 的 端点列表。
ep = (gadget, &);
if (!ep) {
autoconf_fail:
( "%s: can't autoconfigure on %s/n",
, gadget->);
return -;
}
= ep->;
ep-> = ep; /* claim */
ep = (gadget, &);
if (!ep)
goto autoconf_fail;
= ep->;
ep-> = ep; /* claim */
函数 根据第二个参数所描述的限制条件,自动寻找适合条件的端点,并插入 gadget的端点列表。这里 ep 是普通的数据端点,它的 driver_data 不需要存放特殊数据,那就保存一下自己的地址吧。(后面我们将看到 ep0 的 driver_data 放的是zero_driver 的特殊数据)。我们看一下 fs_source_desc:
static struct
= {
.bLength = ,
.bDescriptorType = ,
.bEndpointAddress = ,
.bmAttributes = ,
};
可见该描述符描述的是一个类型为 BULK,方向为 IN 的端点。 的定义类似,描述一个类型为 BULK, 方向为 OUT 的端点。下面继续看 zero_bind 的代码。
gcnum = (gadget);
if (gcnum >= 0)
. = (0x0200 + gcnum);
else {
/* gadget zero is so simple (for now, no altsettings) that
* it SHOULD NOT have problems with bulk-capable hardware.
* so warn about unrcognized controllers, don't panic.
*
* things like configuration and altsetting numbering
* can need hardware-specific attention though.
*/
( "%s: controller '%s' not recognized/n",
, gadget->);
. = (0x9999);
}
每一个 udc 驱动被分配了一个编号,用作该设备描述符里的 bcd 码。 如果没有分配,没办法,就将就着用 0x9999 吧。
/* ok, we made sense of the hardware ... */
= (sizeof(*), );
if (!)
return -;
(&->);
->gadget = gadget;
(gadget, );
stuct gadget 维护所有 gadget 驱动共性的内容,个性的数据则由各 gadget 驱动各自定义,对于 zero, 它定义了 zero_dev. 分配后存放在 gadget 结构的某个角落里: gadget.dev.driver_data。 zero_dev 定义如下:
struct {
;
struct *gadget;
struct *req; /* for control responses */
/* when configured, we have one of two configs:
* - source data (in to host) and sink it (out from host)
* - or loop it back (out from host back in to host)
*/
;
struct *in_ep, *out_ep;
/* autoresume timer */
struct resume;
};
这里 resume是用于唤醒 host 的 timer 的列表, config 表示我们当前使用第几个 configuration. 其它含义自明。下面继续看 zero bind 代码。
/* preallocate control response and buffer */
->req = (gadget->ep0, );
if (!->req)
goto enomem;
->req-> = (gadget->ep0, ,
&->req->, );
if (!->req->)
goto enomem;
->req-> = ;
这几行代码分配用于控制传输的请求/数据缓冲以及结束函数。控制传输是每个 gadget 驱动要使用的传输方式,这里及早分配。结束函数 只是打印一下状态,我们就不贴出了。
.bMaxPacketSize0 = gadget->ep0->maxpacket;
这里根据底层的数据初始化设备描述符里端点 0 (控制端点)的最大包大小。
#ifdef CONFIG_USB_GADGET_DUALSPEED
/* assume ep0 uses the same value for both speeds ... */
.bMaxPacketSize0 = .bMaxPacketSize0;
/* and that all endpoints are dual-speed */
.bEndpointAddress = .bEndpointAddress;
.bEndpointAddress = .bEndpointAddress;
#endif
高速设备需要的额外的描述符,我们对某些字段进行初始化。
if (gadget->is_otg) {
.bmAttributes |= ,
.bmAttributes |= ;
.bmAttributes |= ;
}
如果是 otg 设备,则需要在描述符里设置相关特性。
(gadget);
能运行 Linux Gadget 驱动的设备一般电池供电,也就是 selfpowered。
(&->resume);
->resume.function = ;
->resume. = (unsigned long) ;
if () {
.bmAttributes |= ;
.bmAttributes |= ;
}
这段代码跟自动唤醒 host 有关, 不深究。
gadget->ep0-> = ;
多记一份 zero_dev 的地址, 方便使用。
(, "%s, version: " "/n", );
(, "using %s, OUT %s IN %s/n", gadget->,
, );
(, sizeof , "%s %s with %s",
()->sysname, ()->,
gadget->);
return 0;
enomem:
(gadget);
return -;
}
自此 zero_bind 分析完毕。它主要是为 gadget 驱动找到了合适的端点,并且初始化了设备相关结构: zero_dev.从而把 gadget 驱动和 udc 驱动仅仅地绑定在一起。 看到现在,我们还没有感受到 gadget 驱动的真正意义, 前面的函数就像一座座桥梁,走过这些桥梁,我们终于来到美丽的湖心小岛:zero_setup。
函数 zero_setup
zero_setup 完成控制传输的大部分功能。比如获取各种描述符、设置配置等。Host 首先通过控制传输和设备进行通信,告诉设备它底下要干什么。 Zero gadget驱动比较简单,在主机进行set configuration后,就会在 IN/OUT 端点上准备好数据,供主机去用。并且通过call函数,在主机使用完前面准备好的数据后,继续插入请求,这样,主机就可以源源不断的对我们这个设备进行读写操作。以下开始看代码。
static int
(struct *gadget, const struct *)
{
照例,我们得到 usb_gadget 结构,同时,我们的第二个参数是 usb_ctrlrequest 结构:
struct {
bRequestType;
bRequest;
wValue;
wIndex;
wLength;
} ((packed));
具体含义请参考 Usb spec Ch9。 这里结构名字有点误导, 代表的是主机传过来的控制请求。和后面的 有较大区别。 代表放到端点的队列里等待主机过来读写的一个个数据包。下面我们继续看 zero_setup 函数代码。
struct * = (gadget);
struct *req = ->req;
int = -;
w_index = (->wIndex);
w_value = (->wValue);
w_length = (->wLength);
获得我们在 bind 函数分配的 zero_dev, usb_request, 以及由主机传过来的“请求”的各字段。
/* usually this stores reply data in the pre-allocated ep0 buffer,
* but config change events will reconfigure hardware.*/
req-> = 0;
switch (->bRequest) {
case :
if (->bRequestType != )
goto ;
请求各种描述符,当然需要是 IN 类型的请求。
switch (w_value >> 8) {
case :
= (w_length, () sizeof );
(req->, &, );
break;
#ifdef CONFIG_USB_GADGET_DUALSPEED
case :
if (!gadget->is_dualspeed)
break;
= (w_length, () sizeof );
(req->, &, );
break;
对应 USB 2.0 Spec CH9, 以上代码很容易理解。 每一个描述符使用 struct 描述,比如, 设备描述符:
static struct
= {
.bLength = sizeof ,
.bDescriptorType = ,
.bcdUSB = (0x0200),
.bDeviceClass = , 0xff
. = (),
. = (),
. = , 25, 厂商描述符
. = , 42,厂品描述符
. = , 101, 序列号
.bNumConfigurations = 2,
};
case :
if (!gadget->is_dualspeed)
break;
// FALLTHROUGH
#endif /* CONFIG_USB_GADGET_DUALSPEED */
case :
= (gadget, req->,
w_value >> 8,
w_value & 0xff);
if ( >= 0)
= (w_length, () );
break;
配置描述符比较复杂,会返回该配置里的接口,端点等信息。配置描述符由:struct [] 表达, 而且高速/全速设备的配置描述符是不一样。比如,高速 loopback 配置的配置描述符为:
static const struct * [] = {
(struct *) &,
(struct *) &,
(struct *) &,
(struct *) &,
,
};
可见,本质上,配置描述符是返回一组描述符。下面看一下配置描述符是如何生成的。
static int
(struct *gadget,
*, , unsigned )
{
int is_source_sink;
int ;
const struct **function;
#ifdef CONFIG_USB_GADGET_DUALSPEED
int hs = (gadget-> == USB_SPEED_HIGH);
#endif
/* two configurations will always be index 0 and index 1 */
if ( > 1)
return -;
is_source_sink = ? ( == 1) : ( == 0);
#ifdef CONFIG_USB_GADGET_DUALSPEED
if ( == )
hs = !hs;
if (hs)
function = is_source_sink
?
: ;
else
#endif
function = is_source_sink
?
: ;
/* for now, don't advertise srp-only devices */
if (!gadget->is_otg)
function++;
= (is_source_sink
? &
: &,
, , function);
if ( < 0)
return ;
((struct *) )->bDescriptorType = ;
return ;
}
代码很简单, 函数根据当前是否是高速设备,以及是否是 otg 设备,选择合适的 configuration( souce sink config or loopback config), 调用 生成最终的配置描述符。可以想象 的实现非常简单: 根据传过来的 描述符列表( 以 NULL 指针结束),使用 memcpy 之类见每个描述符的内容拷贝到 buf 里。 下面我们继续看 zero_setup函数。
case :
/* wIndex == language code.
* this driver only handles one language, you can
* add string tables for other languages, using
* any UTF-8 characters
*/
= (&,
w_value & 0xff, req->);
if ( >= 0)
= (w_length, () );
break;
}
break;
根据 host 传递过来的索引,响应相应的字符串。Zero驱动的字符串描述符则只支持一种语言(0409, en-us):
static struct = {
.language = 0x0409, /* en-us */
. = ,
};
/* static strings, in UTF-8 */
static struct [] = {
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ } /* end of list */
};
有点像应用层(比如 vc)为了支持多语言而独立出来的字符串资源。事实上就是这样!我们可以很容易再增加一种语言。下面我们继续看 zero_setup 函数。
/* currently two configs, two speeds */
case :
if (->bRequestType != 0)
goto ;
if (gadget->a_hnp_support)
(, "HNP available/n");
else if (gadget->a_alt_hnp_support)
(, "HNP needs a different root port/n");
else
(, "HNP inactive/n");
(&->);
= (, w_value, );
(&->);
break;
设置设备的当前配置,到这里,才凌空一脚,将设备带入数据传输状态,我们先把zero_setup 看完,再仔细看函数。
case :
if (->bRequestType != )
goto ;
*( *)req-> = ->;
= (w_length, () 1);
break;
获取设备的当前配置
/* until we add altsetting support, or other interfaces,
* only 0/0 are possible. pxa2xx only supports 0/0 (poorly)
* and already killed pending endpoint I/O.
*/
case :
if (->bRequestType != )
goto ;
(&->);
if (-> && w_index == 0 && w_value == 0) {
= ->;
/* resets interface configuration, forgets about
* previous transaction state (queued bufs, etc)
* and re-inits endpoint state (toggle etc)
* no response queued, just zero status == success.
* if we had more than one interface we couldn't
* use this "reset the config" shortcut.
*/
();
(, , );
= 0;
}
(&->);
break;
设置接口,由于我们每个configuration只有一个接口,所以这里的效果跟前面设置配置类似。
由于 zero_set_config 函数会调用 zero_reset_config, 所以这里应该可以不调用 zero_reset_config.
case :
if (->bRequestType != (|))
goto ;
if (!->)
break;
if (w_index != 0) {
= -;
break;
}
*( *)req-> = 0;
= (w_length, () 1);
break;
获取设备的当前配置的当前接口。
/*
* These are the same vendor-specific requests supported by
* Intel's USB 2.0 compliance test devices. We exceed that
* device spec by allowing multiple-packet requests.
*/
case 0x5b: /* control WRITE test -- fill the buffer */
if (->bRequestType != (|))
goto ;
if (w_value || w_index)
break;
/* just read that many bytes into the buffer */
if (w_length > )
break;
= w_length;
break;
case 0x5c: /* control READ test -- return the buffer */
if (->bRequestType != (|))
goto ;
if (w_value || w_index)
break;
/* expect those bytes are still in the buffer; send back */
if (w_length >
|| w_length != req->)
break;
= w_length;
break;
根据协议,我们可以定制私有的类型,这里是 Intel 定义的测试类型,用于测试端点0的数据收发。端点0通常用于控制传输, 用它进行数据传输完全是为了测试目的。
default:
:
(,
"unknown control req%02x.%02x v%04x i%04x l%d/n",
->bRequestType, ->bRequest,
w_value, w_index, w_length);
}
/* respond with data transfer before status phase? */
if ( >= 0) {
req-> = ;
req-> = < w_length;
= (gadget->ep0, req, );
if ( < 0) {
(, "ep_queue --> %d/n", );
req-> = 0;
(gadget->ep0, req);
}
}
如果有数据需要传给 Host, 则将其放到端点0的传送队列。底层 udc 驱动会负责将其发给 host.
/* device either stalls (value < 0) or reports success */
return ;
}
函数zero_setup 完成了usb spec ch9 定义的很多功能。而我们前面介绍的 bulk-in/bulk-out 数据端点开始工作则是在 set configuration (或者 set interface)后,由函数触发。下面开始分析该函数。
函数
static int
(struct *, unsigned , gfp_flags)
{
int = 0;
struct *gadget = ->gadget;
if ( == ->)
return 0;
();
函数 zero_reset_config 把所有的 端点置于 disable 状态。
switch () {
case :
= (, gfp_flags);
break;
case :
= (, gfp_flags);
break;
default:
= -;
/* FALL THROUGH */
case 0:
return ;
}
根据当前的配置,设置两种不同的传送方式。我们假定 host 设置的是 loopback 方式。另一种方式是类似的(数据内容不同)。
if (! && (!->in_ep || !->out_ep))
= -;
if ()
();
else {
char *;
switch (gadget->) {
case USB_SPEED_LOW: = "low"; break;
case USB_SPEED_FULL: = "full"; break;
case USB_SPEED_HIGH: = "high"; break;
default: = "?"; break;
}
-> = ;
(, "%s speed config #%d: %s/n", , ,
( == )
? : );
}
return ;
}
一些善后处理。 下面我们看函数 set_loopback_config
函数
static int
(struct *, gfp_flags)
{
int = 0;
struct *ep;
struct *gadget = ->gadget;
(ep, gadget) {
针对 gadget 端点列表的每一个端点进行操作。
const struct *d;
/* one endpoint writes data back IN to the host */
if ( (ep->, ) == 0) {
d = (gadget, &, &);
= (ep, d);
if ( == 0) {
ep-> = ;
->in_ep = ep;
continue;
}
/* one endpoint just reads OUT packets */
} else if ( (ep->, ) == 0) {
d = (gadget, &, &);
= (ep, d);
if ( == 0) {
ep-> = ;
->out_ep = ep;
continue;
}
/* ignore any other endpoints */
} else
continue;
/* stop on error */
(, "can't enable %s, result %d/n", ep->, );
break;
}
激活端点。并设置速度(高速或者全速)。
/* 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.
*/
if ( == 0) {
struct *req;
unsigned ;
ep = ->out_ep;
for ( = 0; < && == 0; ++) {
req = (ep, );
if (req) {
req-> = ;
= (ep, req, );
if ()
(, "%s queue req --> %d/n",
ep->, );
} else
= -;
}
}
首先在 OUT 端点上挂一堆请求(usb_request), 等待主机向我们发送数据。等主机真正对我们进行OUT数据传输并且数据传完后,会调用 回调函数。
if ( == 0)
(, "qlen %d, buflen %d/n", , );
/* caller is responsible for cleanup on error */
return ;
}
下面看 函数
函数
static void (struct *ep, struct *req)
{
struct * = ep->;
int = req->;
switch () {
case 0: /* normal completion? */
if (ep == ->out_ep) {
/* loop this OUT packet back IN to the host */
req-> = (req->actual < req->);
req-> = req->actual;
= (->in_ep, req, );
if ( == 0)
return;
/* "should never get here" */
(, "can't loop %s to %s: %d/n",
ep->, ->in_ep->,
);
}
/* queue the buffer for some later OUT packet */
req-> = ;
= (->out_ep, req, );
if ( == 0)
return;
/* "should never get here" */
/* FALLTHROUGH */
default:
(, "%s loop complete --> %d, %d/%d/n", ep->,
, req->actual, req->);
/* 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 -: /* hardware forced ep reset */
case -: /* request dequeued */
case -: /* disconnect from host */
(ep, req);
return;
}
}
如果 OUT 传输正常结束,则会将其放到IN 端点的传输队列。
如果 IN 传输正常结束,则会将其放到 OUT 端点的传输队列。
这样,通过回调函数不断在两个队列(IN/OUT)之间切换这些请求(usb_request),就实现了在主机看来的 loopback 设备。
总结
Gadget 驱动的特殊性在于它是 host 端对等驱动的 slave, 而不是上层某个应用的 slave. 响应的,它是实现是很有意思的。我们没有看到 read/write 函数,也没有看到我们最常实现的 ioctl 函数, 而是把重点放在回调函数zero_setup 上。 g_zero gadget 驱动实现了一个最简单的 bulk-in/bulk-out 功能,向我们展示了 gadget 驱动如果利用 gadget API来完成数据传输功能。对于复杂的 gadget 驱动, setup 回调函数只是一个起点。