usb驱动程序分析
usb驱动是linux内核中比较复杂的驱动之一,因此,大多数usb教程建议从usb-skeleton开始学习usb驱动。个人认为这是相当正确的,usb-sekelton提供了一个usb驱动开发的模板,而且代码量较少,很适合初学者的学习。
- struct usb_device; //usb设备
- struct usb_host_config; //usb配置
- struct usb_host_interface; //usb接口
- struct usb_host_endpoit; //usb端口
上图描述的usb各组成部分的关系可以描述为:
(1)一个usb设备可以有多个usb配置,多个配置之间可以相互切换,某个时刻只能对应一个配置;
(2)一个usb配置可以有多个usb接口,一个usb接口代表了一个基本功能,对以一个usb客户端驱动程序;
(3)一个usb接口可以有多个usb端点;
就像我们平时程序设计经常使用的方法一样,一个对象由一个结构体来表示,但还会再用来一个结构体来描述这个对象的一些属性。usb驱动框架也采用了这样的设计思想,usb框架中每一个组成部分都用两个结构体来描述:一个结构体表示成员组成,另一个结构体表示属性组成。Linux-USB核心定义了4个usb描述符。
- struct usb_device_descriptor;<-->struct usb_device;
- struct usb_host_config;<-->struct usb_config_descriptor;
- struct usb_host_interface;<-->struct usb_interface_descriptor;
- struct usb_host_endpoint;<-->struct usb_endpoint_descriptor;
另外,还有一个比较特殊的数据结构是URB(USB Request Block),被USB协议栈使用,是USB数据传输机制的核心数据结构,具体的URB的类型,URB的使用步骤等读者可以参考《Linux设备驱动程序》一书,本文不做详细介绍。
二、USB设备的枚举过程
USB设备是一种典型的热插拔设备,与PCI设备类似,当总线检测到有设备插入的时候,总线驱动程序就会去遍历总线上已经挂载的所有的设备驱动程序,查看有没有驱动程序与刚插入的设备匹配,如果匹配成功,则去执行驱动程序中的probe函数。这是Linux内核中经典的驱动和设备挂钩的方式之一。
三、分析代码的执行过程
要理解代码的含义,最关键的是理清代码的执行路径,即代码中函数的调用关系。光靠source insight或eclipse代码分析工具有时候略显不够,对于那些没有直接调用关系的函数这些静态代码分析工具爱莫能助。最好方法是能看到代码一步一步的执行流程,那么,单步调试就是比较好的选择了。本文采用KVM+GDB的方式对usb-skeleton模块进行了单步调试。Linux内核调试环境的搭建参考文章《qemu+eclipse内核调试》,这里仅需要创建一个Makefile文件,编译usb-skeleton.c即可。KVM中usb设备的使用参考文章《KVM中使用usb设备》。需要注意的是,Linux内中默认的usb存储设备的驱动模块名称为usb-storage,在调试前,先卸载该模块。
四、usb-skeleton主要代码分析(Linux-2.6.35)
1 skel_probe函数
- static int skel_probe(struct usb_interface*interface,
- const struct usb_device_id *id)
- {
- struct usb_skel *dev;
- struct usb_host_interface *iface_desc;
- struct usb_endpoint_descriptor *endpoint;
- size_t buffer_size;
- int i;
- int retval = -ENOMEM;
- /* allocate memory for our device state and initialize it */
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev){
- err("Out of memory");
- goto error;
- }
- kref_init(&dev->kref);
- sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
- mutex_init(&dev->io_mutex);/IO操作互斥锁,在进行IO操作时,不允许进行其他操作,如数据拷贝,后面会提到/
- spin_lock_init(&dev->err_lock);
- init_usb_anchor(&dev->submitted);
- init_completion(&dev->bulk_in_completion);/*comletion同步机制,即IO操作和数据拷贝的同步*/
- /*数据结构之间的转换,这里是usb_skel和usb_host_interface两个结构体之间的转换,相当于container_of宏*/
- dev->udev= usb_get_dev(interface_to_usbdev(interface));
- dev->interface= interface;
- /* set up the endpoint information */
- /* use only the first bulk-in and bulk-out endpoints */
- /* 初始化dev设备的bulk-in和bulk-out相关的数据成员,两种数据类型只初始化一个*/
- iface_desc = interface->cur_altsetting;
- for (i= 0; i < iface_desc->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");
- goto error;
- }
- dev->bulk_in_urb= usb_alloc_urb(0, GFP_KERNEL);
- if (!dev->bulk_in_urb){
- err("Could not allocate bulk_in_urb");
- goto error;
- }
- }
- 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");
- goto error;
- }
- /* save our data pointer in this interface device */
- /*不同上下文之间结构体传递的一种方式,在probe和open等其他函数之间通过usb_set_intfdata和usb_get_intfdata两个函数来保存和获取局部变量de*/
- usb_set_intfdata(interface, dev);
- /* we can register the device now, as it is ready */
- /*执行完usb_register_dev之后,接口interface就会对应一个此设备号,至此与该接口对应的驱动注册完成,其他一些操作将会等到open时再进行*/
- 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);
- goto error;
- }
- /* let the user know what node this device is now attached to */
- dev_info(&interface->dev,
- "USB Skeleton device now attached to USBSkel-%d",
- interface->minor);
- return 0;
- error:
- if (dev)
- /* this frees allocated memory */
- kref_put(&dev->kref, skel_delete);
- return retval;
- }
此函数主要完成usb用户态驱动的注册即usb_class_driver的注册,即skel_class结构体的注册。另外,需要注意的是在驱动程序设计中probe()和open()两个函数的区别,即它们各自应该实现什么功能?个人认为主要理解以下几点:
(1)对每个驱动来讲probe函数只会执行一次,执行时机为驱动加载或设备枚举的时候,用来实现设备和驱动的匹配。从这方面来讲,probe函数的执行函数应该尽可能的短,因此,操作越少越好。
(2)open()函数是每次打开某设备时都要执行的函数,如果系统中有多个进程都在使用某个设备,那么,open()函数就有可能执行多次,从这个角度来讲,open()函数主要应该坐与可冲入相关的操作,即让每个进程看来都是像第一次打开设备一样,其他进程对设备的某些值的修改不应该被其他进程看到。即相当于每次都虚拟了一个实际的硬件设备。
从这两方面将,如果不考虑probe的执行时间,如果不会存在多个使用同一设备的进程,完全可以将probe和open合并。
2 skel_open()函数
- static int skel_open(struct inode*inode, struct file *file)
- {
- struct usb_skel *dev;
- struct usb_interface *interface;
- int subminor;
- int retval = 0;
- subminor = iminor(inode);
- /*通过驱动和此设备号去查找对应的设备,类此pci设备,根据驱动结构体,在总线的device链表上查找这个驱动对应的设备,在usb驱动架构中即指对应的借口。*/
- interface = usb_find_interface(&skel_driver, subminor);
- if (!interface){
- err("%s - error, can't find device for minor %d",
- __func__, subminor);
- retval = -ENODEV;
- goto exit;
- }
- /*获得在probe函数中保存的局部变量usb_skel dev*/
- dev = usb_get_intfdata(interface);
- if (!dev){
- retval = -ENODEV;
- goto exit;
- }
- /* increment our usage count for the device */
- kref_get(&dev->kref);
- /* lock the device to allow correctly handling errors
- * in resumption */
- mutex_lock(&dev->io_mutex);
- if (!dev->open_count++){/*与电源管理相关的代码,可以暂时不去分析*/
- retval = usb_autopm_get_interface(interface);
- if (retval) {
- dev->open_count--;
- mutex_unlock(&dev->io_mutex);
- kref_put(&dev->kref, skel_delete);
- goto exit;
- }
- } /* else { //uncomment this block if you want exclusive open
- retval = -EBUSY;
- dev->open_count--;
- mutex_unlock(&dev->io_mutex);
- kref_put(&dev->kref, skel_delete);
- goto exit;
- } */
- /* prevent the device from being autosuspended */
- /* save our object in the file's private structure */
- /*这里需要注意,前面讲过在probe和open等函数之间传递私有数据用的是两个函数usb_set_intfdata()和usb_get_intfdata, 那么,在open函数和其他函数如read/write之间传递私有数据就是通过file->private_data变量来实现的*/
- file->private_data= dev;
- mutex_unlock(&dev->io_mutex);
- exit:
- return retval;
- }
从上面的代码我们可以发现,open函数归根结底其实只做了一件事情:保存了私有变量usb_skel dev,是的其他文件操作函数可用。
3.skel_read()函数
- static ssize_t skel_read(structfile *file,char *buffer,size_t count,
- loff_t *ppos)
- {
- struct usb_skel *dev;
- int rv;
- bool ongoing_io;
- dev = (struct usb_skel*)file->private_data;
- /* if we cannot read at all, return EOF */
- if (!dev->bulk_in_urb|| !count)
- return 0;
- /* no concurrent readers */
- /*I/O操作的互斥锁,每次读操作前先去获得此锁,防止读操作和IO之间的并发进行。即如果读操作获得了此锁,IO操作就不能进行,同样的,如果该锁已经被IO操作获得,则当前执行流程睡眠,直到IO操作完成,释放此锁*/
- rv = mutex_lock_interruptible(&dev->io_mutex);
- if (rv < 0)
- return rv;
- if (!dev->interface){ /* disconnect() was called */
- rv = -ENODEV;
- goto exit;
- }
- /* if IO is under way, we must not touch things */
- retry:
- spin_lock_irq(&dev->err_lock);
- ongoing_io = dev->ongoing_read;/*获得当前的IO状态*/
- spin_unlock_irq(&dev->err_lock);
- if (ongoing_io){/*如果usb的I/O操作正在进行中,要等待I/O操作执行完成*/
- /* nonblocking IO shall not wait */
- if (file->f_flags& O_NONBLOCK){
- rv = -EAGAIN;
- goto exit;
- }
- /*
- * IO may take forever
- * hence wait in an interruptible state
- */
- /*睡眠等待IO操作完成*/
- rv = wait_for_completion_interruptible(&dev->bulk_in_completion);
- if (rv < 0)
- goto exit;
- /*
- * by waiting we also semiprocessed the urb
- * we must finish now
- */
- dev->bulk_in_copied= 0;/*如果正在进行IO操作,说明当前URB对应的缓冲区没有数据可用了,所以copied=0*/
- dev->processed_urb= 1;/*z执行到这的时候IO操作已经完成了,这里要标记下开始处理URB了*/
- }
- if (!dev->processed_urb){/*等于0时,说明这个URB还没被处理过,即第一次读取这个URB*/
- /*
- * the URB hasn't been processed
- * do it now
- */
- /*这里为什么还要等待,什么情况下需要等待?????*/
- wait_for_completion(&dev->bulk_in_completion);
- dev->bulk_in_copied= 0;
- dev->processed_urb= 1;
- }
- /* errors must be reported */
- rv = dev->errors;
- if (rv < 0) {
- /* any error is reported once */
- dev->errors= 0;
- /* to preserve notifications about reset */
- rv = (rv== -EPIPE) ? rv: -EIO;
- /* no data to deliver */
- dev->bulk_in_filled= 0;
- /* report it */
- goto exit;
- }
- /*
- * if the buffer is filled we may satisfy the read
- * else we need to start IO
- */
- if (dev->bulk_in_filled){/*不是第一次读,当前缓冲区中的数据的字节数*/
- /* we had read data */
- size_t available = dev->bulk_in_filled- dev->bulk_in_copied;/*当前URB还有多少数据没有拷贝*/
- size_t chunk =min(available,count);
- if (!available){/*当前URB对应的缓冲区中没有数据了,要执行IO操作读入*/
- /*
- * all data has been used
- * actual IO needs to be done
- */
- /*执行IO操作,主要包括初始化一个URB和向usb core提交这个URB两个操作,真正的IO操作是由usb core驱动代码来完成的*/
- rv = skel_do_read_io(dev,count);
- if (rv < 0)
- goto exit;
- else
- goto retry;
- }
- /*
- * data is available
- * chunk tells us how much shall be copied
- */
- /*从当前URB对应的缓冲区中拷贝chunk字节数据到用户空间*/
- if (copy_to_user(buffer,
- dev->bulk_in_buffer+ dev->bulk_in_copied,
- chunk))
- rv = -EFAULT;
- else
- rv = chunk;
- dev->bulk_in_copied+= chunk;/*增加已拷贝数据的字节数*/
- /*
- * if we are asked for more than we have,
- * we start IO but don't wait
- */
- /*当前缓冲区拷贝完成后,还没有完成用户指定的数据拷贝量,要继续执行I/O操作,填充URB*/
- if (available< count)
- skel_do_read_io(dev,count - chunk);
- } else {
- /* no data in the buffer */
- /*当前缓冲区已经没有数据了,要执行I/O操作来填充URB对应的缓冲区*/
- rv = skel_do_read_io(dev,count);
- if (rv < 0)
- goto exit;
- else if(!(file->f_flags& O_NONBLOCK))
- goto retry;
- rv = -EAGAIN;
- }
- exit:
- mutex_unlock(&dev->io_mutex);
- return rv;
- }
- /*执行完I/O操作后,要去唤醒usb用户驱动中正在睡眠等待的读拷贝操作过程*/
- static void skel_read_bulk_callback(struct urb*urb)
- {
- struct usb_skel *dev;
- dev = urb->context;
- spin_lock(&dev->err_lock);
- /* sync/async unlink faults aren't errors */
- if (urb->status){
- if (!(urb->status== -ENOENT ||
- urb->status== -ECONNRESET ||
- urb->status== -ESHUTDOWN))
- err("%s - nonzero write bulk status received: %d",
- __func__, urb->status);
- dev->errors= urb->status;
- } else {
- dev->bulk_in_filled= urb->actual_length;
- }
- dev->ongoing_read= 0; /*执行完IO操作通知usb客户端驱动*/
- spin_unlock(&dev->err_lock);
- /*执行唤醒操作*/
- complete(&dev->bulk_in_completion);
- }
- static int skel_do_read_io(struct usb_skel*dev, size_t count)
- {
- int rv;
- /* prepare a read */
- /*在执行I/O操作前,先准备号一个URB*/
- 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; /*标记现在要进行I/O操作了*/
- spin_unlock_irq(&dev->err_lock);
- /* do it */
- /*交由usb core模块去执行真正的IO操作*/
- rv = 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= 0;
- rv = (rv== -ENOMEM) ? rv: -EIO;
- spin_lock_irq(&dev->err_lock);
- dev->ongoing_read= 0;
- spin_unlock_irq(&dev->err_lock);
- }
- return rv;
- }
该函数比较容易理解,就是一个从usb设备读数据到用户程序的过程,这里涉及到一个主要的数据结构URB,对于usb客户驱动来讲,只需调用usb core提供的API即可,因此,usb驱动开发者只需了解这个API的功能和如何使用即可。详见代码注释。
4 skel_write()操作
- static ssize_t skel_write(structfile *file,const char *user_buffer,
- size_t count, loff_t *ppos)
- {
- struct usb_skel *dev;
- int retval = 0;
- struct urb *urb = NULL;
- char *buf= NULL;
- /*确定每次写操作可以写的数据量,最大值为PAGE_SIZE - 512 字节*/
- size_t writesize = min(count,(size_t)MAX_TRANSFER);
- dev = (struct usb_skel*)file->private_data;
- /* verify that we actually have some data to write */
- if (count== 0)
- goto exit;
- /*
- * limit the number of URBs in flight to stop a user from using up all
- * RAM
- */
- if (!(file->f_flags& O_NONBLOCK)){
- if (down_interruptible(&dev->limit_sem)){
- retval = -ERESTARTSYS;
- goto exit;
- }
- } else{
- if (down_trylock(&dev->limit_sem)){
- retval = -EAGAIN;
- goto exit;
- }
- }
- spin_lock_irq(&dev->err_lock);
- retval = dev->errors;
- if (retval< 0) {
- /* any error is reported once */
- dev->errors= 0;
- /* to preserve notifications about reset */
- retval = (retval ==-EPIPE) ? retval : -EIO;
- }
- spin_unlock_irq(&dev->err_lock);
- if (retval< 0)
- goto error;
- /* create a urb, and a buffer for it, and copy the data to the urb */
- /*在usb用户驱动的读程序中,并没有URB的分配,因为在usb_skel中包含了一个URB数据成员*/
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb){
- retval = -ENOMEM;
- goto error;
- }
- /*因为这里是由usb用户驱动负责分配的URB,因此,也应负责分配URB的成员指向的内存*/
- buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
- &urb->transfer_dma);
- if (!buf){
- retval = -ENOMEM;
- goto error;
- }
- /*把用户空间的数据拷贝到URB对应的DMA内存中*/
- if (copy_from_user(buf, user_buffer, writesize)){
- retval = -EFAULT;
- goto error;
- }
- /* this lock makes sure we don't submit URBs to gone devices */
- /*在URB没有准备好之前,不允许进行实际的I/O操作*/
- mutex_lock(&dev->io_mutex);
- if (!dev->interface){ /* disconnect() was called */
- mutex_unlock(&dev->io_mutex);
- retval = -ENODEV;
- goto error;
- }
- /* 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;
- /*把urb添加到另外一条链表上,以便在其他地方对其进行访问*/
- usb_anchor_urb(urb,&dev->submitted);
- /* send the data out the bulk port */
- retval = usb_submit_urb(urb, GFP_KERNEL);
- mutex_unlock(&dev->io_mutex);
- if (retval){
- err("%s - failed submitting write urb, error %d",__func__,
- retval);
- goto error_unanchor;
- }
- /*
- * release our reference to this urb, the USB core will eventually free
- * it entirely
- */
- usb_free_urb(urb);
- return writesize;
- }
在函数与skel_read非常类似,唯一不同的是在写操作过程中需要usb客户驱动程序来显示的分配和初始化用于写操作的URB,之所以在读操作过程中看不到这个过程,是因为,在struct usb_skel dev中已经为写操作内嵌了一个数据成员:
- /* Structure to hold all of our device specific stuff */
- 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 */
- struct usb_anchor submitted; /* in case we need to retract our submissions */
- struct urb *bulk_in_urb; /* the urb to read data with */
- unsigned char*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 */
- struct kref kref;
- struct mutex io_mutex; /* synchronize I/O with disconnect */
- struct completion bulk_in_completion; /* to wait for an ongoing read */
- };
而且这个结构体的很多数据成员都与读操作有关,具体为什么这么设计,暂时还没有搞明白,希望大家指教!
总结:
1. usb客户驱动还是比较简单的,主要是因为很多功能都由usb core驱动程序事先完成了,usb客户端驱动程序仅仅是调用一些API即可。
2. usb客户驱动程序的开发比较固定,直接套用usb-skeleton的代码基本就可以完成用户自定义驱动的编写
更多推荐
所有评论(0)