热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux设备驱动子系统终极弹usb,Linuxusb子系统(二):USB设备驱动usbskeleton.c

usb驱动分为通过usbfs操作设备的用户空间驱动,内核空间的内核驱动。两者不能同时进行,否则容易引发对共享资源访问的问题,死锁ÿ

usb驱动分为通过usbfs操作设备的用户空间驱动,内核空间的内核驱动。两者不能同时进行,否则容易引发对共享资源访问的问题,死锁!使用了内核驱动,就不能在usbfs里驱动该设备。

下面转载的一篇分析usb-skeleton.c文章。

初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了。好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发。但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催货。当然,还会顺带提一下其他的驱动程序写法。

事实上,Linux的设备驱动都遵循一个惯例--表征驱动程序(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程序所需要的所有资源。用术语来说,就是这个驱动器对象所拥有的属性及成员。由于Linux的内核用c来编写,所以我们也按照这种结构化的思想来分析代码,但我还是希望从OO的角度来阐述这些细节。这个结构体的名字有驱动开发人员决定,比如说,鼠标可能有一个叫做mouse_dev的struct,键盘可能由一个keyboard_dev的struct(dev for device,我们做的只是设备驱动)。而这次我们来分析一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),自然,他定义的设备结构体就叫做usb-skel:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*Structure to hold all of our device specific stuff*/

structusb_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*/

struct usb_anchor submitted; /*in case we need to retract our submissions*/

struct urb *bulk_in_urb; /*the urb to read data with*/unsignedchar *bulk_in_buffer; /*the buffer to receive data*/size_t bulk_in_size;/*the size of the receive buffer*/size_t bulk_in_filled;/*number of bytes in the buffer*/size_t bulk_in_copied;/*already copied to user space*/__u8 bulk_in_endpointAddr;/*the address of the bulk in endpoint*/__u8 bulk_out_endpointAddr;/*the address of the bulk out endpoint*/

int errors; /*the last request tanked*/

int open_count; /*count the number of openers*/

bool ongoing_read; /*a read is going on*/

bool processed_urb; /*indicates we haven't processed the urb*/spinlock_t err_lock;/*lock for errors*/

structkref kref;struct mutex io_mutex; /*synchronize I/O with disconnect*/

struct completion bulk_in_completion; /*to wait for an ongoing read*/};

ad70e8a1c913e0b87405e3189ad31c7e.gif

这里我们得补充说明一下一些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结构体,当中包含该端点的信息以及设备自定义的各种信息,这些信息包括:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*USB_DT_ENDPOINT: Endpoint descriptor*/

structusb_endpoint_descriptor {/*结构体大小*/__u8 bLength;/*描述符类型 USB_DT_DEVICE、USB_DT_CONFIG、USB_DT_STRING、USB_DT_INTERFACE、USB_DT_ENDPOINT等等*/*/__u8 bDescriptorType;/*(8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定。*/__u8 bEndpointAddress/*端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK可以确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。*/__u8 bmAttributes;//端点一次处理的最大字节数。发送的BULK包可以大于这个数值,但会被分割传送。

__le16 wMaxPacketSize;/*如果端点是中断类型,该值是端点的间隔设置,以毫秒为单位。在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程序来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,以下是该结构体中比较重要的字段:*/__u8 bInterval;/*NOTE: these two are _only_ in audio endpoints.*/

/*use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof.*/__u8 bRefresh;

__u8 bSynchAddress;

} __attribute__ ((packed));

ad70e8a1c913e0b87405e3189ad31c7e.gif

下面分析一下usb-skeleton的源码。这个范例程序可以在linux-2.6.17/drivers/usb下找到,其他版本的内核程序源码可能有所不同,但相差不大。大家可以先找到源码看一看,先有个整体印象。

之前已经提到,模块先要向内核注册初始化跟销毁函数:

ad70e8a1c913e0b87405e3189ad31c7e.gif

static int __init usb_skel_init(void)

{intresult;/*register this driver with the USB subsystem*/result= usb_register(&skel_driver);if(result)

err("usb_register failed. Error number %d", result);returnresult;

}static void __exit usb_skel_exit(void)

{/*deregister this driver with the USB subsystem*/usb_deregister(&skel_driver);

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

从代码开来,这个init跟exit函数的作用只是用来注册驱动程序,这个描述驱动程序的结构体是系统定义的标准结构struct usb_driver,注册和注销的方法很简单,usb_register(struct *usb_driver), usb_deregister(struct *usb_driver)。

那这个结构体需要做些什么呢?他要向系统提供几个函数入口,跟驱动的名字:

ad70e8a1c913e0b87405e3189ad31c7e.gif

static struct usb_driver skel_driver ={

.name= "skeleton",

.probe=skel_probe,

.disconnect=skel_disconnect,

.suspend=skel_suspend,

.resume=skel_resume,

.pre_reset=skel_pre_reset,

.post_reset=skel_post_reset,

.id_table=skel_table,

.supports_autosuspend= 1,

};

ad70e8a1c913e0b87405e3189ad31c7e.gif

从代码看来,usb_driver需要初始化四个东西:模块的名字skeleton,probe函数skel_probe,disconnect函数skel_disconnect,以及id_table。

在解释skel_driver各个成员之前,我们先来看看另外一个结构体。这个结构体的名字有开发人员自定义,它描述的是该驱动拥有的所有资源及状态:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*Structure to hold all of our device specific stuff*/

structusb_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*/

struct usb_anchor submitted; /*in case we need to retract our submissions*/

struct urb *bulk_in_urb; /*the urb to read data with*/unsignedchar *bulk_in_buffer; /*the buffer to receive data*/size_t bulk_in_size;/*the size of the receive buffer*/size_t bulk_in_filled;/*number of bytes in the buffer*/size_t bulk_in_copied;/*already copied to user space*/__u8 bulk_in_endpointAddr;/*the address of the bulk in endpoint*/__u8 bulk_out_endpointAddr;/*the address of the bulk out endpoint*/

int errors; /*the last request tanked*/

int open_count; /*count the number of openers*/

bool ongoing_read; /*a read is going on*/

bool processed_urb; /*indicates we haven't processed the urb*/spinlock_t err_lock;/*lock for errors*/

structkref kref;struct mutex io_mutex; /*synchronize I/O with disconnect*/

struct completion bulk_in_completion; /*to wait for an ongoing read*/};

ad70e8a1c913e0b87405e3189ad31c7e.gif

我们先来对这个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到底是什么东西:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*table of devices that work with this driver*/

static const struct usb_device_id skel_table[] ={

{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

{ }/*Terminating entry*/};

MODULE_DEVICE_TABLE(usb, skel_table);

ad70e8a1c913e0b87405e3189ad31c7e.gif

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模块作为设备的驱动。

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。他从一个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的引用释放时被调用(如果我的理解不准确,请指正)。下面是内核源码中的一段注释及代码:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/**

* 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;

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

当我们执行打开操作时,我们要增加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中。

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*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[i].desc;if (!dev->bulk_in_endpointAddr &&usb_endpoint_is_bulk_in(endpoint)) {/*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");gotoerror;

}

dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);if (!dev->bulk_in_urb) {

err("Could not allocate bulk_in_urb");gotoerror;

}

}if (!dev->bulk_out_endpointAddr &&usb_endpoint_is_bulk_out(endpoint)) {/*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");gotoerror;

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

接下来的工作是向系统注册一些以后会用的的信息。

首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到。

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*save our data pointer in this interface device*/usb_set_intfdata(interface, dev);/*we can register the device now, as it is ready*/retval= usb_register_dev(interface, &skel_class);if(retval) {/*something prevented us from registering this driver*/err("Not able to get a minor for this device.");

usb_set_intfdata(interface, NULL);gotoerror;

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

然后我们向这个interface注册一个skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:

ad70e8a1c913e0b87405e3189ad31c7e.gif

/**

* struct usb_class_driver - identifies a USB driver that wants to use the USB major number

* @name: the usb class device name for this driver. Will show up in sysfs.

* @devnode: Callback to provide a naming hint for a possible

* device node to create.

* @fops: pointer to the struct file_operations of this driver.

* @minor_base: the start of the minor range for this driver.

*

* This structure is used for the usb_register_dev() and

* usb_unregister_dev() functions, to consolidate a number of the

* parameters used for them.*/

structusb_class_driver {char *name;char *(*devnode)(struct device *dev, mode_t *mode);const struct file_operations *fops;intminor_base;

};

ad70e8a1c913e0b87405e3189ad31c7e.gif

它其实是一个系统定义的结构,里面包含了一名字、一个文件操作结构体还有一个次设备号的基准值。事实上它才是定义 真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。

这里补充一些我个人的估计:因为usb设备可以有多个interface,每个interface所定义的IO操作可能不一样,所以向系统注册的usb_class_driver要求注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。通常情况下,linux系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备,驱动程序可以依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。

ad70e8a1c913e0b87405e3189ad31c7e.gif

static const struct file_operations skel_fops ={

.owner=THIS_MODULE,

.read=skel_read,

.write=skel_write,

.open=skel_open,

.release=skel_release,

.flush=skel_flush,

.llseek=noop_llseek,

};

ad70e8a1c913e0b87405e3189ad31c7e.gif

这个文件操作结构中定义了对设备的读写、打开、释放(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操作。

说到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

一个管道号码,该管道记录了目标设备的端点以及管道的类型。每个管道只有一种类型和一个方向,它与他的目标设备的端点相对应,我们可以通过以下几个函数来获得管道号并设置管道类型:

ad70e8a1c913e0b87405e3189ad31c7e.gif

unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个控制OUT端点。

unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个控制IN端点。

unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个批量OUT端点。

unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个批量OUT端点。

unsigned int usb_sndintpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个中断OUT端点。

unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个中断OUT端点。

unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个等时OUT端点。

unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned intendpoint)//把指定USB设备的指定端点设置为一个等时OUT端点。

ad70e8a1c913e0b87405e3189ad31c7e.gif

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,我们用

ad70e8a1c913e0b87405e3189ad31c7e.gif

static inline void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,

unsignedintpipe,void *transfer_buffer,intbuffer_length,

usb_complete_t complete_fn,void *context,int interval)

ad70e8a1c913e0b87405e3189ad31c7e.gif

这里要解释一下,transfer_buffer是一个要送/收的数据的缓冲,buffer_length是它的长度,complete是urb完成回调函数的入口,context由用户定义,可能会在回调函数中使用的数据,interval就是urb被调度的间隔。

对于批量urb和控制urb,我们用:

ad70e8a1c913e0b87405e3189ad31c7e.gif

//批量

static inline void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,

unsignedintpipe,void *transfer_buffer,intbuffer_length,

usb_complete_t complete_fn,void *context)//控制

static inline void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,

unsignedintpipe,

unsignedchar *setup_packet,void *transfer_buffer,intbuffer_length,

usb_complete_t complete_fn,void *context)

ad70e8a1c913e0b87405e3189ad31c7e.gif

控制包有一个特殊参数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来传送数据的细节。

ad70e8a1c913e0b87405e3189ad31c7e.gif

static int skel_do_read_io(struct usb_skel *dev, size_t count)

{intrv;/*prepare a read*/usb_fill_bulk_urb(dev->bulk_in_urb,

dev->udev,

usb_rcvbulkpipe(dev->udev,dev->bulk_in_endpointAddr),

dev->bulk_in_buffer,

min(dev->bulk_in_size, count),

skel_read_bulk_callback,

dev);/*tell everybody to leave the URB alone*/spin_lock_irq(&dev->err_lock);

dev->ongoing_read = 1;

spin_unlock_irq(&dev->err_lock);/*do it*/rv&#61; usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);if (rv <0) {

err("%s - failed submitting read urb, error %d",

__func__, rv);

dev->bulk_in_filled &#61; 0;

rv&#61; (rv &#61;&#61; -ENOMEM) ? rv : -EIO;

spin_lock_irq(&dev->err_lock);

dev->ongoing_read &#61; 0;

spin_unlock_irq(&dev->err_lock);

}returnrv;

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

这里skel_write_bulk_callback就是一个完成回调函数&#xff0c;而他做的主要事情就是检查数据传输状态和释放urb&#xff1a;

ad70e8a1c913e0b87405e3189ad31c7e.gif

static void skel_read_bulk_callback(struct urb *urb)

{struct usb_skel *dev;

dev&#61; urb->context;

spin_lock(&dev->err_lock);/*sync/async unlink faults aren&#39;t errors*/

if (urb->status) {if (!(urb->status &#61;&#61; -ENOENT ||urb->status &#61;&#61; -ECONNRESET ||urb->status &#61;&#61; -ESHUTDOWN))

err("%s - nonzero write bulk status received: %d",

__func__, urb->status);

dev->errors &#61; urb->status;

}else{

dev->bulk_in_filled &#61; urb->actual_length;

}

dev->ongoing_read &#61; 0;

spin_unlock(&dev->err_lock);

complete(&dev->bulk_in_completion);

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

事实上&#xff0c;如果数据的量不大&#xff0c;那么可以不一定用卡车来运货&#xff0c;系统还提供了一种不用urb的传输方式&#xff0c;而usb-skeleton的读操作正是采用这种方式实现&#xff1a;

ad70e8a1c913e0b87405e3189ad31c7e.gif

/*do a blocking bulk read to get data from the device*/retval&#61; 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&#61; -EFAULT;elseretval&#61;bytes_read;

}

ad70e8a1c913e0b87405e3189ad31c7e.gif

程序使用了usb_bulk_msg来传送数据&#xff0c;它的原型如下&#xff1a;

ad70e8a1c913e0b87405e3189ad31c7e.gif

int usb_bulk_msg(struct usb_device *usb_dev, unsigned intpipe,void *data, int len, int *actual_length, inttimeout)

{struct urb *urb;struct usb_host_endpoint *ep;

ep&#61;usb_pipe_endpoint(usb_dev, pipe);if (!ep || len <0)return -EINVAL;

urb&#61; usb_alloc_urb(0, GFP_KERNEL);if (!urb)return -ENOMEM;if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) &#61;&#61;USB_ENDPOINT_XFER_INT) {

pipe&#61; (pipe & ~(3 <<30)) | (PIPE_INTERRUPT <<30);

usb_fill_int_urb(urb, usb_dev, pipe, data, len,

usb_api_blocking_completion, NULL,

ep->desc.bInterval);

}elseusb_fill_bulk_urb(urb, usb_dev, pipe, data, len,

usb_api_blocking_completion, NULL);returnusb_start_wait_urb(urb, timeout, actual_length);

}

EXPORT_SYMBOL_GPL(usb_bulk_msg);

ad70e8a1c913e0b87405e3189ad31c7e.gif

这个函数会阻塞等待数据传输完成或者等到超时&#xff0c;data是输入/输出缓冲&#xff0c;len是它的大小&#xff0c;actual length是实际传送的数据大小&#xff0c;timeout是阻塞超时。

对于控制数据&#xff0c;系统提供了另外一个函数&#xff0c;他的原型是&#xff1a;

ad70e8a1c913e0b87405e3189ad31c7e.gif

int usb_control_msg(struct usb_device *dev, unsigned intpipe, __u8 request,

__u8 requesttype, __u16 value, __u16 index,void *data,

__u16 size,inttimeout)

{struct usb_ctrlrequest *dr;intret;

dr&#61; kmalloc(sizeof(structusb_ctrlrequest), GFP_NOIO);if (!dr)return -ENOMEM;

dr->bRequestType &#61;requesttype;

dr->bRequest &#61;request;

dr->wValue &#61;cpu_to_le16(value);

dr->wIndex &#61;cpu_to_le16(index);

dr->wLength &#61;cpu_to_le16(size);/*dbg("usb_control_msg");*/ret&#61;usb_internal_control_msg(dev, pipe, dr, data, size, timeout);

kfree(dr);returnret;

}

EXPORT_SYMBOL_GPL(usb_control_msg);

ad70e8a1c913e0b87405e3189ad31c7e.gif

equest是控制消息的USB请求值、requesttype是控制消息的USB请求类型&#xff0c;value是控制消息的USB消息值&#xff0c;index是控制消息的USB消息索引。具体是什么&#xff0c;暂时不是很清楚&#xff0c;希望大家提供说明。

当然&#xff0c;对于中断传输&#xff0c;系统也提供了另外的函数。

ad70e8a1c913e0b87405e3189ad31c7e.gif

int usb_interrupt_msg(struct usb_device *usb_dev, unsigned intpipe,void *data, int len, int *actual_length, inttimeout)

{returnusb_bulk_msg(usb_dev, pipe, data, len, actual_length, timeout);

}

EXPORT_SYMBOL_GPL(usb_interrupt_msg);

ad70e8a1c913e0b87405e3189ad31c7e.gif

直接调用usb_bulk_msg()函数。

至此&#xff0c;Linux下的USB驱动框架分析基本完成了。



推荐阅读
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 本实验主要探讨了二叉排序树(BST)的基本操作,包括创建、查找和删除节点。通过具体实例和代码实现,详细介绍了如何使用递归和非递归方法进行关键字查找,并展示了删除特定节点后的树结构变化。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • MySQL缓存机制深度解析
    本文详细探讨了MySQL的缓存机制,包括主从复制、读写分离以及缓存同步策略等内容。通过理解这些概念和技术,读者可以更好地优化数据库性能。 ... [详细]
  • Hadoop入门与核心组件详解
    本文详细介绍了Hadoop的基础知识及其核心组件,包括HDFS、MapReduce和YARN。通过本文,读者可以全面了解Hadoop的生态系统及应用场景。 ... [详细]
  • 本文介绍了两种方法,用于检测 Android 设备是否开启了开发者模式。第一种方法通过检查 USB 调试模式的状态,第二种方法则直接判断开发者选项是否启用。这两种方法均提供了代码示例和详细解释。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
author-avatar
qyc_3830179
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有