Linux USB驱动框架分析【转】
转自:http://blog.csdn.net/jeffade/article/details/7701431
Linux USB驱动框架分析(一)
初次接触和OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了。好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动研发。但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催货。当然,还会顺带提一下其他的驱动程式写法。
事实上,Linux的设备驱动都遵循一个惯例——表征驱动程式(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程式所需要的任何资源。用术语来说,就是这个驱动器对象所拥有的属性及成员。由于Linux的内核用c来编写,所以我们也按照这种结构化的思想来分析代码,但我还是希望从OO的角度来阐述这些细节。这个结构体的名字有驱动研发人员决定,比如说,鼠标可能有一个叫做mouse_dev的struct,键盘可能由一个keyboard_dev的struct(dev for device,我们做的只是设备驱动)。而这次我们来分析一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),自然,他定义的设备结构体就叫做usb-skel:
struct usb_skel {
struct usb_device * udev; /* the usb device for this device */
struct usb_interface * interface; /* the interface for this device */
struct semaphore limit_sem; /* limiting the number of writes in progress */
unsigned char * bulk_in_buffer; /* the buffer to receive data */
size_t bulk_in_size; /* the size of the receive buffer */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
struct kref kref;
};
这里我们得补充说明一下一些USB的协议规范细节。USB能够自动监测设备,并调用相应得驱动程式处理设备,所以其规范实际上是相当复杂的,幸好,我们不必理会大部分细节问题,因为Linux已提供相应的解决方案。就我现在的理解来说,USB的驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已做好了,我们能够不管,但我们至少要了解他的功能。形象得说,USB的bus驱动相当于铺出一条路来,让任何的信息都能够通过这条USB通道到达该到的地方,这部分工作由usb_core来完成。当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备。里面复杂细节我们不用管,我们要做的是另一块工作——usb的设备驱动。也就是说,我们就等着usb_core告诉我们要工作了,我们才工作。
从研发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又能够有多个接口(interface),每个接口又有多个配置(setting图中没有给出),而接口本身可能没有端点或多个端点(end point)。USB的数据交换通过端点来进行,主机和各个端点之间建立起单向的管道来传输数据。而这些接口能够分为四类:
控制(control)
用于配置设备、获取设备信息、发送命令或获取设备的状态报告
中断(interrupt)
当USB宿主需要设备传输数据时,中断端点会以一个固定的速率传送少量数据,还用于发送数据到USB设备以控制设备,一般不用于传送大量数据。
批量(bulk)
用于大量数据的可靠传输,假如总线上的空间不足以发送整个批量包,他会被分割成多个包传输。
等时(isochronous)
大量数据的不可靠传输,不确保数据的到达,但确保恒定的数据流,多用于数据采集。
Linux中用struct usb_host_endpoint来描述USB端点,每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息连同设备自定义的各种信息,这些信息包括:
bEndpointAddress(b for byte)
8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),能够用掩码USB_DIR_OUT和USB_DIR_IN来确定。
bmAttributes
端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK能够确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。
wMaxPacketSize
端点一次处理的最大字节数。发送的BULK包能够大于这个数值,但会被分割传送。
bInterval
假如端点是中断类型,该值是端点的间隔配置,以毫秒为单位。
在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程式来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,需要有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用
struct usb_interface来描述,以下是该结构体中比较重要的字段:
struct usb_host_interface *altsetting(注意不是usb_interface)
其实据我理解,他应该是每个接口的配置,虽然名字上有点奇怪。该字段是个配置的数组(一个接口能够有多个配置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。
unsigned num_altstting
可选配置的数量,即altsetting所指数组的元素个数。
struct usb_host_interface *cur_altsetting
当前活动的配置,指向altsetting数组中的一个。
int minor
当捆绑到该接口的USB驱动程式使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。
除了他能够用struct usb_host_config来描述之外,到现在为止,我对配置的了解不多。而整个USB设备则能够用struct usb_device来描述,但基本上只会用他来初始化函数的接口,真正用到的应该是我们之前所提到的自定义的一个结构体。
Linux USB驱动框架分析(二)
好,了解过USB一些规范细节之后,我们现在来看看Linux的驱动框架。事实上,Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。要写一个Linux的模块并不复杂,以一个helloworld为例:
#include
#include
MODULE_LICENSE(“GPL”);
static int hello_init(void)
{
printk(KERN_ALERT “Hello World!\n”);
return 0;
}
static int hello_exit(void)
{
printk(KERN_ALERT “GOODBYE!\n”);
}
module_init(hello_init);
module_exit(hello_exit);
这个简单的程式告诉大家应该怎么写一个模块,MODULE_LICENSE告诉内核该模块的版权信息,很多情况下,用GPL或BSD,或两个,因为一个私有模块一般很难得到社区的帮助。module_init和module_exit用于向内核注册模块的初始化函数和模块推出函数。如程式所示,初始化函数是hello_init,而退出函数是hello_exit。
另外,要编译一个模块通常还需要用到内核源码树中的makefile,所以模块的Makefile能够写成:
ifneq ($(KERNELRELEASE),)
obj-m:= hello.o#usb-dongle.o
else
KDIR:= /usr/src/linux-headers-$(shell uname -r)
BDIR:= $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
.PHONY: clean
clean:
make -C $(KDIR) M=$(BDIR) clean
endif
能够用insmod跟rmmod来验证模块的挂在跟卸载,但必须用root的身份登陆命令行,用普通用户加su或sudo在Ubuntu上的测试是不行的。
Linux USB驱动框架分析(三)
下面分析一下usb-skeleton的源码。这个范例程式能够在linux-2.6.17/drivers/usb下找到,其他版本的内核程式源码可能有所不同,但相差不大。大家能够先找到源码看一看,先有个整体印象。
之前已提到,模块先要向内核注册初始化跟销毁函数:
static int __init usb_skel_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&skel_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
module_init (usb_skel_init);
module_exit (usb_skel_exit);
MODULE_LICENSE("GPL");
从代码开来,这个init跟exit函数的作用只是用来注册驱动程式,这个描述驱动程式的结构体是系统定义的标准结构struct usb_driver,注册和注销的方法很简单,usb_register(struct *usb_driver), usb_deregister(struct *usb_driver)。那这个结构体需要做些什么呢?他要向系统提供几个函数入口,跟驱动的名字:
static struct usb_driver skel_driver = {
.name = "skeleton",
.probe = skel_probe,
.disconnect = skel_disconnect,
.id_table = skel_table,
};
从代码看来,usb_driver需要初始化四个东西:模块的名字skeleton,probe函数skel_probe,disconnect函数skel_disconnect,连同id_table。
在解释skel_driver各个成员之前,我们先来看看另外一个结构体。这个结构体的名字有研发人员自定义,他描述的是该驱动拥有的任何资源及状态:
struct usb_skel {
struct usb_device * udev; /* the usb device for this device */
struct usb_interface * interface; /* the interface for this device */
struct semaphore limit_sem; /* limiting the number of writes in progress */
unsigned char * bulk_in_buffer; /* the buffer to receive data */
size_t bulk_in_size; /* the size of the receive buffer */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
struct kref kref;
};
我们先来对这个usb_skel作个简单分析,他拥有一个描述usb设备的结构体udev,一个接口interface,用于并发访问控制的semaphore(信号量) limit_sem,用于接收数据的缓冲bulk_in_buffer及其尺寸bulk_in_size,然后是批量输入输出端口地址bulk_in_endpointAddr、bulk_out_endpointAddr,最后是个内核使用的引用计数器。他们的作用我们将在后面的代码中看到。
我们再回过头来看看skel_driver。
name用来告诉内核模块的名字是什么,这个注册之后有系统来使用,跟我们关系不大。
id_table用来告诉内核该模块支持的设备。usb子系统通过设备的production ID和vendor ID的组合或设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程式作处理。我们能够看看这个id_table到底是什么东西:
/* Define these values to match your devices */
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);
MODULE_DEVICE_TABLE的第一个参数是设备的类型,假如是USB设备,那自然是usb(假如是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备。这涉及PCI设备的驱动了,在此先不深究)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,假如他们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。
Linux USB驱动框架分析(四)
probe是usb子系统自动调用的一个函数,有USB设备接到硬件集线器时,usb子系统会根据production ID和vendor ID的组合或设备的class、subclass跟protocol的组合来识别设备调用相应驱动程式的probe(探测)函数,对于skeleton来说,就是skel_probe。系统会传递给探测函数一个usb_interface *跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认配置也是第0号配置)跟他的设备ID描述(包括Vendor ID、Production ID等)。probe函数比较长,我们分段来分析这个函数:
dev->udev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
在初始化了一些资源之后,能够看到第一个关键的函数调用——interface_to_usbdev。他同uo一个usb_interface来得到该接口所在设备的设备描述结构。本来,要得到一个usb_device只要用interface_to_usbdev就够了,但因为要增加对该usb_device的引用计数,我们应该在做一个usb_get_dev的操作,来增加引用计数,并在释放设备时用usb_put_dev来减少引用计数。这里要解释的是,该引用计数值是对该usb_device的计数,并不是对本模块的计数,本模块的计数要由kref来维护。所以,probe一开始就有初始化kref。事实上,kref_init操作不单只初始化kref,还将其置设成1。所以在出错处理代码中有kref_put,他把kref的计数减1,假如kref计数已为0,那么kref会被释放。kref_put的第二个参数是个函数指针,指向一个清理函数。注意,该指针不能为空,或kfree。该函数会在最后一个对kref的引用释放时被调用(假如我的理解不准确,请指正)。下面是内核源码中的一段注释及代码:
/**
* kref_put - decrement refcount for object.
* @kref: object.
* @release: pointer to the function that will clean up the object when the
* last reference to the object is released.
* This pointer is required, and it is not acceptable to pass kfree
* in as this function.
*
* Decrement the refcount, and if 0, call release().
* Return 1 if the object was removed, otherwise return 0. Beware, if this
* function returns 0, you still can not count on the kref from remaining in
* memory. Only use the return value if you want to see if the kref is now
* gone, not present.
*/
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
WARN_ON(release == (void (*)(struct kref *))kfree);
/*
* if current count is one, we are the last user and can release object
* right now, avoiding an atomic operation on 'refcount'
*/
if ((atomic_read(&kref->refcount) == 1) ||
(atomic_dec_and_test(&kref->refcount))) {
release(kref);
return 1;
}
return 0;
}
当我们执行打开操作时,我们要增加kref的计数,我们能够用kref_get,来完成。任何对struct kref的操作都有内核代码确保其原子性。
得到了该usb_device之后,我们要对我们自定义的usb_skel各个状态跟资源作初始化。这部分工作的任务主要是向usb_skel注册该usb设备的端点。这里可能要补充以下一些关于usb_interface_descriptor的知识,但因为内核源码对该结构体的注释不多,所以只能靠个人猜测。在一个usb_host_interface结构里面有一个usb_interface_descriptor叫做desc的成员,他应该是用于描述该interface的一些属性,其中bNumEndpoints是个8位(b for byte)的数字,他代表了该接口的端点数。probe然后遍历任何的端点,检查他们的类型跟方向,注册到usb_skel中。
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;
for (i = 0; i desc.bNumEndpoints; i) {
endpoint = &iface_desc->endpoint.desc;
if ( !dev->bulk_in_endpointAddr &&
((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {
/* we found a bulk in endpoint */
buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {
/* we found a bulk out endpoint */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
Linux USB驱动框架分析(五)
接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构能够是任意的,这段程式向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data能够在以后用usb_get_intfdata来得到。
usb_set_intfdata(interface, dev);
retval = usb_register_dev(interface, &skel_class);
然后我们向这个interface注册一个skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:
static struct usb_class_driver skel_class = {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};
他其实是个系统定义的结构,里面包含了一名字、一个文档操作结构体更有一个次设备号的基准值。事实上他才是定义 真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。这里补充一些我个人的估计:因为usb设备能够有多个interface,每个interface所定义的IO操作可能不相同,所以向系统注册的usb_class_driver需要注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。通常情况下,linux系统用主设备号来识别某类设备的驱动程式,用次设备号管理识别具体的设备,驱动程式能够依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。
static struct file_operations skel_fops = {
.owner = THIS_MODULE,
.read = skel_read,
.write = skel_write,
.open = skel_open,
.release = skel_release,
};
这个文档操作结构中定义了对设备的读写、打开、释放(USB设备通常使用这个术语release)。他们都是函数指针,分别指向skel_read、skel_write、skel_open、skel_release这四个函数,这四个函数应该有研发人员自己实现。
当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interface的data:
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &skel_class);
然后他会用kref_put(&dev->kref, skel_delete)进行清理,kref_put的细节参见前文。
到现在为止,我们已分析完usb子系统需要的各个主要操作,下一部分我们在讨论一下对USB设备的IO操作。
Linux USB驱动框架分析(六)
说到usb子系统的IO操作,不得不说usb request block,简称urb。事实上,能够打一个这样的比喻,usb总线就像一条高速公路,货物、人流之类的能够看成是系统和设备交互的数据,而urb就能够看成是汽车。在一开始对USB规范细节的介绍,我们就说过USB的endpoint有4种不同类型,也就是说能在这条高速公路上流动的数据就有四种。但是这对汽车是没有需要的,所以urb能够运载四种数据,但是您要先告诉司机您要运什么,目的地是什么。我们现在就看看struct urb的具体内容。他的内容很多,为了不让我的理解误导各位,大家最好还是看一看内核源码的注释,具体内容参见源码树下include/linux/usb.h。
在这里我们重点介绍程式中出现的几个关键字段:
struct usb_device *dev
urb所发送的目标设备。
unsigned int pipe
一个管道号码,该管道记录了目标设备的端点连同管道的类型。每个管道只有一种类型和一个方向,他和他的目标设备的端点相对应,我们能够通过以下几个函数来获得管道号并配置管道类型:
unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个控制OUT端点。
unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个控制IN端点。
unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个批量OUT端点。
unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个批量OUT端点。
unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个中断OUT端点。
unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个中断OUT端点。
unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个等时OUT端点。
unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点配置为一个等时OUT端点。
unsigned int transfer_flags
当不使用DMA时,应该transfer_flags |= URB_NO_TRANSFER_DMA_MAP(按照代码的理解,希望没有错)。
int status
当一个urb把数据送到设备时,这个urb会由系统返回给驱动程式,并调用驱动程式的urb完成回调函数处理。这时,status记录了这次数据传输的有关状态,例如传送成功和否。成功的话会是0。
要能够运货当然首先要有车,所以第一步当然要创建urb:
struct urb *usb_alloc_urb(int isoc_packets, int mem_flags);
第一个参数是等时包的数量,假如不是乘载等时包,应该为0,第二个参数和kmalloc的标志相同。
要释放一个urb能够用:
void usb_free_urb(struct urb *urb);
要承载数据,还要告诉司机目的地信息跟要运的货物,对于不同的数据,系统提供了不同的函数,对于中断urb,我们用
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
void *transfer_buffer, int buffer_length,
usb_complete_t complete, void *context, int interval);
这里要解释一下,transfer_buffer是个要送/收的数据的缓冲,buffer_length是他的长度,complete是urb完成回调函数的入口,context由用户定义,可能会在回调函数中使用的数据,interval就是urb被调度的间隔。
对于批量urb和控制urb,我们用:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
void *transfer_buffer, int buffer_length, usb_complete_t complete,
void *context);
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
unsigned char* setup_packet,void *transfer_buffer,
int buffer_length, usb_complete_t complete,void *context);
控制包有一个特别参数setup_packet,他指向即将被发送到端点的配置数据报的数据。
对于等时urb,系统没有专门的fill函数,只能对各urb字段显示赋值。
有了汽车,有了司机,下一步就是要开始运货了,我们能够用下面的函数来提交urb
int usb_submit_urb(struct urb *urb, int mem_flags);
mem_flags有几种:GFP_ATOMIC、GFP_NOIO、GFP_KERNEL,通常在中断上下文环境我们会用GFP_ATOMIC。
当我们的卡车运货之后,系统会把他调回来,并调用urb完成回调函数,并把这辆车作为函数传递给驱动程式。我们应该在回调函数里面检查status字段,以确定数据的成功传输和否。下面是用urb来传送数据的细节。
/* initialize the urb properly */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, writesize, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);
这里skel_write_bulk_callback就是个完成回调函数,而他做的主要事情就是检查数据传输状态和释放urb:
dev = (struct usb_skel *)urb->context;
/* sync/async unlink faults aren't errors */
if (urb->status && !(urb->status = = -ENOENT || urb->status == -ECONNRESET || urb->status = = -ESHUTDOWN)) {
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
}
/* free up our allocated buffer */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
事实上,假如数据的量不大,那么能够不一定用卡车来运货,系统还提供了一种不用urb的传输方式,而usb-skeleton的读操作正是采用这种方式实现:
/* do a blocking bulk read to get data from the device */
retval = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
&bytes_read, 10000);
/* if the read was successful, copy the data to userspace */
if (!retval) {
if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
retval = -EFAULT;
else
retval = bytes_read;
}
程式使用了usb_bulk_msg来传送数据,他的原型如下:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data,
int len, int *actual length, int timeout)
这个函数会阻塞等待数据传输完成或等到超时,data是输入/输出缓冲,len是他的大小,actual length是实际传送的数据大小,timeout是阻塞超时。
对于控制数据,系统提供了另外一个函数,他的原型是:
Int usb_contrl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout);
request是控制消息的USB请求值、requesttype是控制消息的USB请求类型,value是控制消息的USB消息值,index是控制消息的USB消息索引。具体是什么,暂时不是很清楚,希望大家提供说明。
至此,Linux下的USB驱动框架分析基本完成了。
Linux USB驱动框架分析【转】的更多相关文章
- Linux USB驱动框架分析 【转】
转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...
- Linux USB驱动框架分析(2)【转】
转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html 看了http://blog.chinaunix.net/uid-11848011 ...
- linux驱动基础系列--linux spi驱动框架分析
前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...
- linux驱动基础系列--linux spi驱动框架分析(续)
前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...
- 【原创】Linux PCI驱动框架分析(二)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- 【原创】Linux PCI驱动框架分析(三)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- Linux下USB驱动框架分析【转】
转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...
- 【原创】Linux PCI驱动框架分析(一)
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- linux设备驱动程序--串行通信驱动框架分析
linux 串行通信接口驱动框架 在学习linux内核驱动时,不论是看linux相关的书籍,又或者是直接看linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各 ...
随机推荐
- python-10多进程
1-多进程(multiprocessing), 1个父进程可以有多少子进程 1.1下面的例子演示了启动一个子进程并等待其结束 from multiprocessing import Process i ...
- 安装python 第三方库遇到的安装问题 microsoft visual studio c++ 10.0 is required,Could not find function xmlCheckVersion in library libxml2. Is libxml2 installed?
问题一: microsoft visual studio c++ 10.0 is required 安装scrapy时候出现需要vc c++ 10,有时安装其他也会有. 解决方法:安装vc 2010, ...
- 开启虚拟机所报的错误:VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the program, access all directories the program uses, and access all directories for temporary fil
当我们开启虚拟机时出现错误: VMware Workstation cannot connect to the virtual machine. Make sure you have rights t ...
- Maven学习 (五) Elipse中发布一个Maven项目到Tomcat
对于maven初学者的我,经常遇到一个问题就是,maven项目创建成功后,本来已经添加了jar的依赖,但是发布到Tomcat中就是没有jar包存在, 启动Tomcat总是报没有找到jar包,可项目结构 ...
- 剑指Offer - 九度1354 - 和为S的连续正数序列
剑指Offer - 九度1354 - 和为S的连续正数序列2013-11-23 02:02 题目描述: 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100. ...
- Lua语言中文手册 转载自网络
Programming in LuaCopyright ® 2005, Translation Team, www.luachina.net Programming in LuaProgramming ...
- 解决idea无法下载插件的问题
分析原因: 使用了 https 协议下载而导致的问题. 解决办法: 找到 File -> Settings -> Appearance & Behavior -> Syste ...
- Windows+Python 3.6环境下安装PyQt4
第一步:下载.whl,地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyqt4,这里可以下载不同的python版本对应的包. 第二步:选择一个目录,将下 ...
- Django学习笔记(二):使用Template让HTML、CSS参与网页建立
Django学习笔记(二):使用Template让HTML.CSS参与网页建立 通过本文章实现: 了解Django中Template的使用 让HTML.CSS等参与网页建立 利用静态文件应用网页样式 ...
- App自动化测试前期准备---android SDK配置
说明:就是配置android SDK 一.sdk下载 Windows(X64):立即下载 Linux(X64):立即下载 二.Windows配置 1.解压文件 直接解压到指定目录(演示目录:D:/) ...