Linux-USB Gadget : Part 4: 最简单的 gadget驱动:g_zero
Linux-USB Gadget : Part 4: 最简单的 gadget驱动:g_zero
作者: zjujoe 转载请注明出处
Email : zjujoe@yahoo.com
BLOG : http://blog.csdn.net/zjujoe
前言
前面讲过, 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 驱动主要是在描述符方面提供配合支持。下面我们开始看代码。
模块初始化
1309 static int __init init (void)
1311 /* a real value would likely come through some id prom
1312 * or module option. this one takes at least two packets.
1314 strlcpy (serial , "0123456789.0123456789.0123456789" , sizeof serial );
1316 return usb_gadget_register_driver (&zero_driver );
1320 static void __exit cleanup (void)
1324 module_exit (cleanup ); usb_gadget_unregister_driver
1283 static struct usb_gadget_driver zero_driver = {
1284 #ifdef CONFIG_USB_GADGET_DUALSPEED
1289 .function = (char *) longname ,
1291 .unbind = __exit_p (zero_unbind ),
1294 .disconnect = zero_disconnect ,
1296 .suspend = zero_suspend ,
1300 .name = (char *) shortname ,
从上图中,我们可以看到在初始化阶段, udc 驱动会调用 zero 驱动的 bind 函数,也会调用 zero 驱动的 setup 函数 ( 主要是得到一些描述符 ) , setup 函数主要是在后面我们的 device 和主机连接后用于处理控制传输的响应(大部分)。在初始阶段只是顺便帮忙提供点信息,进行的是假传输,真提供信息给 udc 驱动。下面我们重点分析 bind 函数。
函数 zero_bind
1141 zero_bind (struct usb_gadget *gadget)
1147 /* FIXME this can't yet work right with SH ... it has only
1148 * one configuration, numbered one.
1150 if (gadget_is_sh (gadget))
注意我们以前说过 gadget_is_* 系列函数提供了查询硬件能力的接口,这里用于判断是否是 SH 平台的 udc, 如果是, 直接出错返回: g_zero 驱动目前还不支持该平台。
1153 /* Bulk-only drivers like this one SHOULD be able to
1154 * autoconfigure on any sane usb controller driver,
1155 * but there may also be important quirks to address.
1157 usb_ep_autoconfig_reset (gadget);
注意函数 usb_ep_autoconfig_reset 不是由底层 udc 驱动实现,而是我们以前提过的 helper 函数的一部分。该函数功能很简单:用于清空 gadget 的 端点列表。
1158 ep = usb_ep_autoconfig (gadget, &fs_source_desc );
1161 printk (KERN_ERR "%s: can't autoconfigure on %s/n" ,
1162 shortname , gadget->name );
1166 ep->driver_data = ep; /* claim */
1168 ep = usb_ep_autoconfig (gadget, &fs_sink_desc );
1172 ep->driver_data = ep; /* claim */
296 static struct usb_endpoint_descriptor
298 .bLength = USB_DT_ENDPOINT_SIZE ,
299 .bDescriptorType = USB_DT_ENDPOINT ,
301 .bEndpointAddress = USB_DIR_IN ,
302 .bmAttributes = USB_ENDPOINT_XFER_BULK ,
可见该描述符描述的是一个类型为 BULK, 方向为 IN 的端点。 fs_sink_desc 的定义类似,描述一个类型为 BULK, 方向为 OUT 的端点。下面继续看 zero_bind 的代码。
1174 gcnum = usb_gadget_controller_number (gadget);
1176 device_desc .bcdDevice = cpu_to_le16 (0x0200 + gcnum);
1178 /* gadget zero is so simple (for now, no altsettings) that
1179 * it SHOULD NOT have problems with bulk-capable hardware.
1180 * so warn about unrcognized controllers, don't panic.
1182 * things like configuration and altsetting numbering
1183 * can need hardware-specific attention though.
1185 printk (KERN_WARNING "%s: controller '%s' not recognized/n" ,
1186 shortname , gadget->name );
1187 device_desc .bcdDevice = __constant_cpu_to_le16 (0x9999);
每一个 udc 驱动被分配了一个编号,用作该设备描述符里的 bcd 码。 如果没有分配,没办法,就将就着用 0x9999 吧。
1191 /* ok, we made sense of the hardware ... */
1192 dev = kzalloc (sizeof(*dev ), GFP_KERNEL );
1195 spin_lock_init (&dev ->lock );
1197 set_gadget_data (gadget, dev );
121 struct usb_gadget *gadget;
122 struct usb_request *req; /* for control responses */
124 /* when configured, we have one of two configs:
125 * - source data (in to host) and sink it (out from host)
126 * - or loop it back (out from host back in to host)
129 struct usb_ep *in_ep, *out_ep;
这里 resume 是用于唤醒 host 的 timer 的列表, config 表示我们当前使用第几个 configuration. 其它含义自明。下面继续看 zero bind 代码。
1199 /* preallocate control response and buffer */
1200 dev ->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL );
1203 dev ->req->buf = usb_ep_alloc_buffer (gadget->ep0, USB_BUFSIZ ,
1204 &dev ->req->dma , GFP_KERNEL );
1208 dev ->req->complete = zero_setup_complete ;
1210 device_desc .bMaxPacketSize0 = gadget->ep0->maxpacket;
这里根据底层的数据初始化设备描述符里端点 0 (控制端点)的最大包大小。
1212 #ifdef CONFIG_USB_GADGET_DUALSPEED
1213 /* assume ep0 uses the same value for both speeds ... */
1214 dev_qualifier .bMaxPacketSize0 = device_desc .bMaxPacketSize0;
1216 /* and that all endpoints are dual-speed */
1217 hs_source_desc .bEndpointAddress = fs_source_desc .bEndpointAddress;
1218 hs_sink_desc .bEndpointAddress = fs_sink_desc .bEndpointAddress;
1222 otg_descriptor .bmAttributes |= USB_OTG_HNP ,
1223 source_sink_config .bmAttributes |= USB_CONFIG_ATT_WAKEUP ;
1224 loopback_config .bmAttributes |= USB_CONFIG_ATT_WAKEUP ;
1227 usb_gadget_set_selfpowered (gadget);
能运行 Linux Gadget 驱动的设备一般电池供电,也就是 selfpowered 。
1229 init_timer (&dev ->resume);
1230 dev ->resume.function = zero_autoresume ;
1231 dev ->resume.data = (unsigned long) dev ;
1233 source_sink_config .bmAttributes |= USB_CONFIG_ATT_WAKEUP ;
1234 loopback_config .bmAttributes |= USB_CONFIG_ATT_WAKEUP ;
这段代码跟自动唤醒 host 有关, 不深究。
1237 gadget->ep0->driver_data = dev ;
多记一份 zero_dev 的地址, 方便使用。
1239 INFO (dev , "%s, version: " DRIVER_VERSION "/n" , longname );
1240 INFO (dev , "using %s, OUT %s IN %s/n" , gadget->name ,
1241 EP_OUT_NAME , EP_IN_NAME );
1243 snprintf (manufacturer , sizeof manufacturer , "%s %s with %s" ,
1244 init_utsname ()->sysname, init_utsname ()->release ,
函数 zero_setup
zero_setup 完成控制传输的大部分功能。比如获取各种描述符、设置配置等。 Host 首先通过控制传输和设备进行通信,告诉设备它底下要干什么。 Zero gadget 驱动比较简单,在主机进行 set configuration 后,就会在 IN/OUT 端点上准备好数据,供主机去用。并且通过 call 函数,在主机使用完前面准备好的数据后,继续插入请求,这样,主机就可以源源不断的对我们这个设备进行读写操作。以下开始看代码。
918 zero_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl )
照例,我们得到 usb_gadget 结构,同时,我们的第二个参数是 usb_ctrlrequest 结构:
140 struct usb_ctrlrequest {
141 __u8 bRequestType;
142 __u8 bRequest;
143 __le16 wValue;
144 __le16 wIndex;
145 __le16 wLength;
146 } __attribute__ ((packed));
920 struct zero_dev *dev = get_gadget_data (gadget);
921 struct usb_request *req = dev ->req;
923 u16 w_index = le16_to_cpu (ctrl ->wIndex);
924 u16 w_value = le16_to_cpu (ctrl ->wValue);
925 u16 w_length = le16_to_cpu (ctrl ->wLength);
获得我们在 bind 函数分配的 zero_dev, usb_request , 以及由主机传过来的“请求”的各字段。
927 /* usually this stores reply data in the pre-allocated ep0 buffer,
928 * but config change events will reconfigure hardware.*/
931 switch (ctrl ->bRequest) {
933 case USB_REQ_GET_DESCRIPTOR :
934 if (ctrl ->bRequestType != USB_DIR_IN )
939 value = min (w_length, (u16 ) sizeof device_desc );
940 memcpy (req->buf , &device_desc , value );
942 #ifdef CONFIG_USB_GADGET_DUALSPEED
943 case USB_DT_DEVICE_QUALIFIER :
944 if (!gadget->is_dualspeed)
946 value = min (w_length, (u16 ) sizeof dev_qualifier );
947 memcpy (req->buf , &dev_qualifier , value );
对应 USB 2.0 Spec CH9, 以上代码很容易理解。 每一个描述符使用 struct usb_device_descriptor 描述,比如, 设备描述符:
222 static struct usb_device_descriptor
224 .bLength = sizeof device_desc ,
225 .bDescriptorType = USB_DT_DEVICE ,
227 .bcdUSB = __constant_cpu_to_le16 (0x0200),
228 .bDeviceClass = USB_CLASS_VENDOR_SPEC , 0xff
230 . idVendor = __constant_cpu_to_le16 ( DRIVER_VENDOR_NUM ),
231 . idProduct = __constant_cpu_to_le16 ( DRIVER_PRODUCT_NUM ),
232 . iManufacturer = STRING_MANUFACTURER , 25 , 厂商描述符
233 . iProduct = STRING_PRODUCT , 42 ,厂品描述符
234 . iSerialNumber = STRING_SERIAL , 101, 序列号
950 case USB_DT_OTHER_SPEED_CONFIG :
951 if (!gadget->is_dualspeed)
954 #endif /* CONFIG_USB_GADGET_DUALSPEED */
956 value = config_buf (gadget, req->buf ,
960 value = min (w_length, (u16 ) value );
378 static const struct usb_descriptor_header * hs_loopback_function [] = {
379 (struct usb_descriptor_header *) & otg_descriptor ,
380 (struct usb_descriptor_header *) & loopback_intf ,
381 (struct usb_descriptor_header *) & hs_source_desc ,
382 (struct usb_descriptor_header *) & hs_sink_desc ,
可见,本质上,配置描述符是返回一组描述符。下面看一下配置描述符是如何生成的。
433 config_buf (struct usb_gadget *gadget,
434 u8 * buf , u8 type , unsigned index )
438 const struct usb_descriptor_header **function;
439 #ifdef CONFIG_USB_GADGET_DUALSPEED
440 int hs = (gadget-> speed == USB_SPEED_HIGH);
443 /* two configurations will always be index 0 and index 1 */
446 is_source_sink = loopdefault ? ( index == 1) : ( index == 0);
448 #ifdef CONFIG_USB_GADGET_DUALSPEED
449 if ( type == USB_DT_OTHER_SPEED_CONFIG )
461 /* for now, don't advertise srp-only devices */
465 len = usb_gadget_config_buf (is_source_sink
468 buf , USB_BUFSIZ , function);
471 ((struct usb_config_descriptor *) buf )->bDescriptorType = type ;
964 /* wIndex == language code.
965 * this driver only handles one language, you can
966 * add string tables for other languages, using
969 value = usb_gadget_get_string (&stringtab ,
970 w_value & 0xff, req->buf );
972 value = min (w_length, (u16 ) value );
根据 host 传递过来的索引,响应相应的字符串。Zero 驱动的字符串描述符则只支持一种语言(0409, en-us ):
409 static struct usb_gadget_strings stringtab = {
410 .language = 0x0409, /* en-us */
399 /* static strings, in UTF-8 */
400 static struct usb_string strings [] = {
401 { STRING_MANUFACTURER , manufacturer , },
402 { STRING_PRODUCT , longname , },
403 { STRING_SERIAL , serial , },
404 { STRING_LOOPBACK , loopback , },
405 { STRING_SOURCE_SINK , source_sink , },
有点像应用层(比如 vc )为了支持多语言而独立出来的字符串资源。事实上就是这样!我们可以很容易再增加一种语言。下面我们继续看 zero_setup 函数。
977 /* currently two configs, two speeds */
978 case USB_REQ_SET_CONFIGURATION :
979 if (ctrl ->bRequestType != 0)
981 if (gadget->a_hnp_support)
982 DBG (dev , "HNP available/n" );
983 else if (gadget->a_alt_hnp_support)
984 DBG (dev , "HNP needs a different root port/n" );
986 VDBG (dev , "HNP inactive/n" );
988 value = zero_set_config (dev , w_value, GFP_ATOMIC );
989 spin_unlock (&dev ->lock );
设置设备的当前配置,到这里,才凌空一脚,将设备带入数据传输状态,我们先把zero_setup 看完,再仔细看函数 zero_set_config 。
991 case USB_REQ_GET_CONFIGURATION :
992 if (ctrl ->bRequestType != USB_DIR_IN )
994 *(u8 *)req->buf = dev ->config ;
995 value = min (w_length, (u16 ) 1);
998 /* until we add altsetting support, or other interfaces,
999 * only 0/0 are possible. pxa2xx only supports 0/0 (poorly)
1000 * and already killed pending endpoint I/O.
1002 case USB_REQ_SET_INTERFACE :
1003 if (ctrl ->bRequestType != USB_RECIP_INTERFACE )
1005 spin_lock (&dev ->lock );
1006 if (dev ->config && w_index == 0 && w_value == 0) {
1007 u8 config = dev ->config ;
1009 /* resets interface configuration, forgets about
1010 * previous transaction state (queued bufs, etc)
1011 * and re-inits endpoint state (toggle etc)
1012 * no response queued, just zero status == success.
1013 * if we had more than one interface we couldn't
1014 * use this "reset the config" shortcut.
1016 zero_reset_config (dev );
1017 zero_set_config (dev , config , GFP_ATOMIC );
1020 spin_unlock (&dev ->lock );
设置接口,由于我们每个configuration 只有一个接口,所以这里的效果跟前面设置配置类似。
由于 zero_set_config 函数会调用 zero_reset_config, 所以这里应该可以不调用 zero_reset_config.
1022 case USB_REQ_GET_INTERFACE :
1023 if (ctrl ->bRequestType != (USB_DIR_IN |USB_RECIP_INTERFACE ))
1032 value = min (w_length, (u16 ) 1);
1036 * These are the same vendor-specific requests supported by
1037 * Intel's USB 2.0 compliance test devices. We exceed that
1038 * device spec by allowing multiple-packet requests.
1040 case 0x5b: /* control WRITE test -- fill the buffer */
1041 if (ctrl ->bRequestType != (USB_DIR_OUT |USB_TYPE_VENDOR ))
1045 /* just read that many bytes into the buffer */
1046 if (w_length > USB_BUFSIZ )
1050 case 0x5c: /* control READ test -- return the buffer */
1051 if (ctrl ->bRequestType != (USB_DIR_IN |USB_TYPE_VENDOR ))
1055 /* expect those bytes are still in the buffer; send back */
1056 if (w_length > USB_BUFSIZ
1057 || w_length != req->length )
根据协议,我们可以定制私有的类型,这里是 Intel 定义的测试类型,用于测试端点0 的数据收发。端点0 通常用于控制传输, 用它进行数据传输完全是为了测试目的。
1065 "unknown control req%02x.%02x v%04x i%04x l%d/n" ,
1066 ctrl ->bRequestType, ctrl ->bRequest,
1067 w_value, w_index, w_length);
1070 /* respond with data transfer before status phase? */
1073 req->zero = value < w_length;
1074 value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC );
1076 DBG (dev , "ep_queue --> %d/n" , value );
1078 zero_setup_complete (gadget->ep0, req);
如果有数据需要传给 Host, 则将其放到端点0 的传送队列。底层 udc 驱动会负责将其发给 host.
1082 /* device either stalls (value < 0) or reports success */
函数zero_set_config
849 zero_set_config (struct zero_dev *dev , unsigned number , gfp_t gfp_flags)
852 struct usb_gadget *gadget = dev ->gadget;
854 if (number == dev ->config )
函数 zero_reset_config 把所有的 端点置于 disable 状态。
866 result = set_source_sink_config (dev , gfp_flags);
869 result = set_loopback_config (dev , gfp_flags);
根据当前的配置,设置两种不同的传送方式。我们假定 host 设置的是 loopback 方式。另一种方式是类似的 ( 数据内容不同 ) 。
878 if (!result && (!dev ->in_ep || !dev ->out_ep))
886 case USB_SPEED_LOW: speed = "low" ; break;
887 case USB_SPEED_FULL: speed = "full" ; break;
888 case USB_SPEED_HIGH: speed = "high" ; break;
889 default: speed = "?" ; break;
893 INFO (dev , "%s speed config #%d: %s/n" , speed , number ,
894 (number == CONFIG_SOURCE_SINK )
895 ? source_sink : loopback );
一些善后处理。 下面我们看函数 set_loopback_config
函数 set_loopback_config
748 set_loopback_config (struct zero_dev *dev , gfp_t gfp_flags)
752 struct usb_gadget *gadget = dev ->gadget;
754 gadget_for_each_ep (ep, gadget) {
755 const struct usb_endpoint_descriptor *d;
757 /* one endpoint writes data back IN to the host */
758 if (strcmp (ep->name , EP_IN_NAME ) == 0) {
759 d = ep_desc (gadget, &hs_source_desc , &fs_source_desc );
760 result = usb_ep_enable (ep, d);
767 /* one endpoint just reads OUT packets */
768 } else if (strcmp (ep->name , EP_OUT_NAME ) == 0) {
769 d = ep_desc (gadget, &hs_sink_desc , &fs_sink_desc );
770 result = usb_ep_enable (ep, d);
777 /* ignore any other endpoints */
782 ERROR (dev , "can't enable %s, result %d/n" , ep->name , result );
786 /* allocate a bunch of read buffers and queue them all at once.
787 * we buffer at most 'qlen' transfers; fewer if any need more
788 * than 'buflen' bytes each.
795 for (i = 0; i < qlen && result == 0; i ++) {
796 req = alloc_ep_req (ep, buflen );
798 req->complete = loopback_complete ;
799 result = usb_ep_queue (ep, req, GFP_ATOMIC );
801 DBG (dev , "%s queue req --> %d/n" ,
首先在 OUT 端点上挂一堆请求( usb_request ) , 等待主机向我们发送数据。等主机真正对我们进行 OUT 数据传输并且数据传完后,会调用 loopback_complete 回调函数。
808 DBG (dev , "qlen %d, buflen %d/n" , qlen , buflen );
810 /* caller is responsible for cleanup on error */
函数 loopback_complete
698 static void loopback_complete (struct usb_ep *ep, struct usb_request *req)
700 struct zero_dev *dev = ep->driver_data ;
701 int status = req->status ;
705 case 0: /* normal completion? */
707 /* loop this OUT packet back IN to the host */
708 req->zero = (req->actual < req->length );
709 req->length = req->actual;
710 status = usb_ep_queue (dev ->in_ep , req, GFP_ATOMIC );
714 /* "should never get here" */
715 ERROR (dev , "can't loop %s to %s: %d/n" ,
716 ep->name , dev ->in_ep->name ,
720 /* queue the buffer for some later OUT packet */
722 status = usb_ep_queue (dev ->out_ep , req, GFP_ATOMIC );
726 /* "should never get here" */
730 ERROR (dev , "%s loop complete --> %d, %d/%d/n" , ep->name ,
731 status , req->actual, req->length );
734 /* NOTE: since this driver doesn't maintain an explicit record
735 * of requests it submitted (just maintains qlen count), we
736 * rely on the hardware driver to clean up on disconnect or
739 case -ECONNABORTED : /* hardware forced ep reset */
740 case -ECONNRESET : /* request dequeued */
741 case -ESHUTDOWN : /* disconnect from host */
如果 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 回调函数只是一个起点。
参考
USB 2.0 Spec
http://www.usb.org/developers/docs/
用 KFI 和 Graphviz 跟踪 / 优化内核代码
http://blog.csdn.net/colorant/archive/2008/07/09/2627493.aspx
Linux-USB Gadget : Part 4: 最简单的 gadget驱动:g_zero相关推荐
- win7无法识别linux usb设备,win7无法识别U盘,驱动信息:该设备的驱动程序未被安装。 (代码 28)...
POJ 2452 Sticks Problem RMQ+二分....枚举 i ,找比 i 小的第一个元素,再找之间的第一个最大元素..... Sticks Pro ...
- Linux usb 6. HC/UDC 测试
文章目录 1. 背景介绍 2. Device (gadget zero) 2.1 `gadget zero` 创建 2.2 SourceSink Function 2.3 Loopback Funct ...
- 嵌入式Linux设备驱动程序开发指南20(Linux USB设备驱动)——读书笔记
Linux USB设备驱动 二十.Linux USB设备驱动 20.1 USB简介 20.1.1 USB2.0总线拓扑 20.1.2 USB总线枚举和设备布局 20.1.3 USB数据传输 20.1. ...
- linux usb gadget 日志
1,USB 协议入门 几种USB控制器类型:OHCI,UHCI,EHCI,XHCI 遇到过一些关于USB的东西(如下),一直没搞明白什么USB1.0/1.1/2.0/3.0之类的,当然我知道它们的各自 ...
- linux usb gadget驱动详解(三)
本文将对linux4.4.19版本usb gadget源码进行简单分析.鉴于前文反复测试U盘设备驱动,现从linux-4.4.19/drivers/usb/gadget/legacy/mass_sto ...
- linux下gadget复合设备,Linux USB Gadget--设备枚举
前面介绍了Linux USB Gadget的软件结构与各软件层的整合过程.经过各种注册函数,Gadget功能驱动层,USB设备层与UDC底层结合在了一起形成了一个完整的USB设备.而这个设备已经准备好 ...
- linux usb gadget驱动详解(二)
在上篇<linux usb gadget驱动详解(一)>中,我们了解到gadget的测试方法,但在最后,我们留下一个问题,就是怎样使用新的方法进行usb gadget驱动测试. 我们发现l ...
- Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...
- linux usb gadget驱动详解(一)
由于PC的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他的还有诸如以太网.PCIE总线和RS232串口等.这里我们主要讨论USB. USB是一个主从通信架构,但只能一主多从.其中us ...
最新文章
- android相机截取矩形框,Android自定义照相机实现只拍摄矩形区域(重传)
- 刀模图是什么意思_“吃鸡”光子公布神秘图,海岛图上有44个坐标,暗示信号值取消?...
- ajax中怎么验证data,我应该在jQuery的ajax成功处理程序中验证响应数据吗?
- android数据库文件是否加密存储,详解Android数据存储之SQLCipher数据库加密
- 成都睿铂盘点无人机航测三个极端恶劣环境的人员与设备防护指南
- 00.Maven简介
- 神奇的数学:牛津教授给青少年的讲座
- 密码生成 算法编程题
- 智慧校园人脸识别门禁系统设计方案
- linux pipe2函数,pipe()函数 Unix/Linux
- android 视频上传网络异常,App上传视频(或大文件)失败怎么办?
- 计算机笔记Excel,秦路天善智能EXCEL学习笔记1-文本清洗函数
- Gmail:如何撤回发出的邮件?
- 利用 perf4j 做服务监控
- ::ng-deep 与 :host ::ng-deep
- add_days oracle_oracle 日期时间函数使用总结
- 你们要的水性粘合剂乳胶漆消泡剂已经出来了
- 图片怎么在线转换成PDF格式
- 618战局天猫聚焦“商家体验”,创造确定性增长是核心目标
- 求解多元一次方程解的个数(参考内容)