• Linux usb子系统(二) _usb-skeleton.c精析


    "./drivers/usb/usb-skeleton.c"是内核提供给usb设备驱动开发者的海量存储usb设备的模板程序, 程序不长, 通用性却很强,十分经典, 深入理解这个文件可以帮助我们更好的理解usb子系统以及usb设备驱动框架, 写出更好的usb海量存储设备驱动。

    匹配前

    既然是一个usb设备驱动的模板,那么就少不了构造一个usb_driver对象并将其注册到内核中,

    650 static struct usb_driver skel_driver = {
    651         .name =         "skeleton",
    652         .probe =        skel_probe,
    653         .disconnect =   skel_disconnect,
    654         .suspend =      skel_suspend,
    655         .resume =       skel_resume,
    656         .pre_reset =    skel_pre_reset,
    657         .post_reset =   skel_post_reset,
    658         .id_table =     skel_table,
    659         .supports_autosuspend = 1,
    660 };
    661 
    662 module_usb_driver(skel_driver);
    

    关于这个对象的域,在上一篇已经解释了,这里,我们主要关心的是skel_table,它决定了这个驱动匹配到哪个设备,从下面的定义可以看出,这个驱动是按照device进行匹配的,

     30 static const struct usb_device_id skel_table[] = {
     31         { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
     32         { }                                     /* Terminating entry */
     33 };
     34 MODULE_DEVICE_TABLE(usb, skel_table);
    

    匹配后

    资源类

    接下来,看一下这个驱动对于资源类的定义,这可是整个驱动程序的纽带,管理着整个驱动程序各个函数与接口共用的资源, 不得不说这个注释真的是内核中少有的详细, skeleton主要是针对海量存储设备的,所以其资源对象中封装了很多缓冲区的信息VS中断设备只要一个urb即可搞定数据传输问题

     49 struct usb_skel {
     50         struct usb_device       *udev;                  /* the usb device for this device */
     51         struct usb_interface    *interface;             /* the interface for this device */
     52         struct semaphore        limit_sem;              /* limiting the number of writes in progress
     53         struct usb_anchor       submitted;              /* in case we need to retract our submission
     54         struct urb              *bulk_in_urb;           /* the urb to read data with */
     55         unsigned char           *bulk_in_buffer;        /* the buffer to receive data */
     56         size_t                  bulk_in_size;           /* the size of the receive buffer */
     57         size_t                  bulk_in_filled;         /* number of bytes in the buffer */
     58         size_t                  bulk_in_copied;         /* already copied to user space */
     59         __u8                    bulk_in_endpointAddr;   /* the address of the bulk in endpoint */
     60         __u8                    bulk_out_endpointAddr;  /* the address of the bulk out endpoint */
     61         int                     errors;                 /* the last request tanked */
     62         bool                    ongoing_read;           /* a read is going on */
     63         spinlock_t              err_lock;               /* lock for errors */
     64         struct kref             kref;
     65         struct mutex            io_mutex;               /* synchronize I/O with disconnect */
     66         wait_queue_head_t       bulk_in_wait;           /* to wait for an ongoing read */
     67 };
    

    struct usb_skel
    --50-->驱动操作的usb_device对象
    --51-->驱动操作的usb_interface对象, 这两个都是设备信息, VS i2c-s3c2410.c 通过将设备信息在probe中拷贝出来以保存到驱动资源对象中, 这里也是同样的思路. struct usb_interface->dev域之于usb_skel以及其他接口函数, 相当于struct device域之于s3c24xx_i2c以及其他接口函数, 都是在各个接口函数中流动的
    --54-->使用的urb对象
    --55-->用于接收数据的buf指针
    --56-->标识要接收数据长度的域
    --57-->标识当前缓冲区有多少有效数据的域
    --58-->标识当前缓冲区已经被拷贝走多少数据的域,skeleton不会清空缓冲区,而是使用各种长度表示来决定已经占用了多少,超出长度的部分,是否被清零无所谓。他们之间的关系见下图
    --59-->bulk设备的输入端点
    --60-->bulk设备的输出端点
    --62-->设备可读标志位,0表示可读,1表示不可读
    --64-->kref供内核引用计数用

    usb_skeleton还参考内核中已有的to_platform_device等结构封装了一个to_skel_dev, 这种写法值得借鉴

     68 #define to_skel_dev(d) container_of(d, struct usb_skel, kref)
    

    probe

    匹配成功后,按照套路就该请probe上场了

    490 static int skel_probe(struct usb_interface *interface,
    491                       const struct usb_device_id *id)
    492 {
    493         struct usb_skel *dev;
    494         struct usb_host_interface *iface_desc;
    495         struct usb_endpoint_descriptor *endpoint;
    496         size_t buffer_size;
    497         int i;
    498         int retval = -ENOMEM;
    499 
    500         /* allocate memory for our device state and initialize it */
    501         dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    506         kref_init(&dev->kref);
    507         sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
    508         mutex_init(&dev->io_mutex);
    509         spin_lock_init(&dev->err_lock);
    510         init_usb_anchor(&dev->submitted);
    511         init_waitqueue_head(&dev->bulk_in_wait);
    512 
    513         dev->udev = usb_get_dev(interface_to_usbdev(interface));
    514         dev->interface = interface;
    515 
    516         /* set up the endpoint information */
    517         /* use only the first bulk-in and bulk-out endpoints */
    518         iface_desc = interface->cur_altsetting;
    519         for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
    520                 endpoint = &iface_desc->endpoint[i].desc;
    521 
    522                 if (!dev->bulk_in_endpointAddr &&
    523                     usb_endpoint_is_bulk_in(endpoint)) {
    524                         /* we found a bulk in endpoint */
    525                         buffer_size = usb_endpoint_maxp(endpoint);
    526                         dev->bulk_in_size = buffer_size;
    527                         dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
    528                         dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
    534                         dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
    540                 }
    542                 if (!dev->bulk_out_endpointAddr &&
    543                     usb_endpoint_is_bulk_out(endpoint)) {
    544                         /* we found a bulk out endpoint */
    545                         dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
    546                 }
    547         }
    553 
    554         /* save our data pointer in this interface device */
    555         usb_set_intfdata(interface, dev);
    556 
    557         /* we can register the device now, as it is ready */
    558         retval = usb_register_dev(interface, &skel_class);
    566 
    567         /* let the user know what node this device is now attached to */
    568         dev_info(&interface->dev,
    569                  "USB Skeleton device now attached to USBSkel-%d",
    570                  interface->minor);
    571         return 0;
    578 }
    579 
    

    skel_probe
    --501-->为资源对象申请空间, 注意这里的写法: dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    --506-->初始化usb_skel->kref
    --507-->初始化usb_skel->limit_sem
    --508-->初始化usb_skel->io_mutex);
    --509-->初始化usb_skel->err_lock);
    --510-->初始化usb_skel->submitted);
    --511-->初始化usb_skel->bulk_in_wait
    --513-->初始化usb_skel->udev,将匹配到的usb_device地址存储下来
    --514-519-->初始化usb_skel对象其他域.
    --555-->将我们的资源对象藏到interface->dev->p->driver_data中
    --558-->注册一个usb_device对象到内核、申请一个次设备号并创建设备文件, ==>intf->usb_dev = device_create(usb_class->class, &intf->dev,MKDEV(USB_MAJOR, minor), class_driver,"%s", temp);

    通过上面的分析, 我们发现了一个skeleton和usbmouse不一样的地方:skeleton构造了usb_class_driver对象并使用usb_register_dev注册一个usb设备, 而usbmouse作为input子系统, 仅需要input_register(input_dev)即可, 不用usb设备的注册问题, 产生这个差别的原因是skeleton是针对bulk urb设备的, 而usbmouse是针对interrupt urb设备的。对于bulk设备,我们会对设备进行读写操作,而不仅仅是读操作,所以在bulk urb设备驱动中要实现相应的操作方法集并绑定到设备文件一起注册到内核,这个工作就是由usb_register_dev来完成。为了使用这个函数,我们需要构造一个usb_class_driver对象,其中最重要的就是本节要讨论的skel_fops了。这个域也是struct file_operations类型的,所有的读写方法的实现都要注册到这个域中。
    既然提到fops,我们主要关心的就三个方法的实现:open, read和write,考虑到read和write在操作逻辑类似,所以本文只讨论open和read

    open

     83 static int skel_open(struct inode *inode, struct file *file)
     84 {
     85         struct usb_skel *dev;
     86         struct usb_interface *interface;
     87         int subminor;
     88         int retval = 0;
     89 
     90         subminor = iminor(inode);
     91 
     92         interface = usb_find_interface(&skel_driver, subminor);
    100         dev = usb_get_intfdata(interface);
    106         retval = usb_autopm_get_interface(interface);
    110         /* increment our usage count for the device */
    111         kref_get(&dev->kref);
    112 
    113         /* save our object in the file's private structure */
    114         file->private_data = dev;
    115 
    117         return retval;
    118 }
    

    skel_open()
    --90-->从inode中获取次设备号
    --92-->根据skel_driver对象和次设备号获取usb_interface对象,至此就找到了设备
    --100-->从interface->dev->p->driver_data中获取资源对象的地址,这个地址是在probe--555--中藏到这的
    --110-->引用计数加一
    --114-->关键,将之前获得的资源对象的地址藏在file->private_data中,这样在所有的cdev接口之间都可以使用资源对象了,和将资源对象地址藏到interface中以便在usb_driver的接口函数之间流动的思想是一样的。

    read

    打开了设备,接下来就可以读写了,skeleton中对于读操作的关键函数调用关系如下,我们依照这个调用树依次分析

    skel_read()
    skel_do_read_io(dev, count)
    usb_fill_bulk_urb(...);
    usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);

    首先是skel_read(),这个函数是应用层读设备时回调的函数,它试图实现这样一个功能: 如果内核缓冲区有数据就将适当的数据拷贝给应用层, 如果没有就调用skel_do_read_io来向设备请求数据

    226 static ssize_t skel_read(struct file *file, char *buffer, size_t count,
    227                          loff_t *ppos)
    228 {
    229         struct usb_skel *dev;
    230         int rv;
    231         bool ongoing_io;
    232 
    233         dev = file->private_data;
    255         if (ongoing_io) {
    256                 /* nonblocking IO shall not wait */
    257                 if (file->f_flags & O_NONBLOCK) {
    258                         rv = -EAGAIN;
    259                         goto exit;
    260                 }
    265                 rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read));
    266                 if (rv < 0)
    267                         goto exit;
    268         }
    269 
    270         /* errors must be reported */
    271         rv = dev->errors;
    272         if (rv < 0) {
    273                 /* any error is reported once */
    274                 dev->errors = 0;
    275                 /* to preserve notifications about reset */
    276                 rv = (rv == -EPIPE) ? rv : -EIO;
    277                 /* report it */
    278                 goto exit;
    279         }
    286         if (dev->bulk_in_filled) {
    287                 /* we had read data */
    288                 size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
    289                 size_t chunk = min(available, count);
    290 
    291                 if (!available) {
    296                         rv = skel_do_read_io(dev, count);
    297                         if (rv < 0)
    298                                 goto exit;
    299                         else
    300                                 goto retry;
    301                 }
    307                 if (copy_to_user(buffer,
    308                                  dev->bulk_in_buffer + dev->bulk_in_copied,
    309                                  chunk))
    310                         rv = -EFAULT;
    311                 else
    312                         rv = chunk;
    313 
    314                 dev->bulk_in_copied += chunk;
    320                 if (available < count)
    321                         skel_do_read_io(dev, count - chunk);
    322         } else {
    323                 /* no data in the buffer */
    324                 rv = skel_do_read_io(dev, count);
    325                 if (rv < 0)
    326                         goto exit;
    327                 else
    328                         goto retry;
    329         }
    330 exit:
    331         mutex_unlock(&dev->io_mutex);
    332         return rv;
    333 }
    

    skel_read()
    --233-->都是套路,先将藏在file_private_data中的资源对象拿出来
    --255-268-->资源对象中的可读标志位,不可读的时候,判断IO是否允许阻塞,如果不允许就直接返回,允许阻塞就使用资源对象中的等待队列头,将进程加入等待队列,使用的是interruptible版本的wait,如果睡眠中的进程是被中断唤醒的,那么rv==-1,函数直接返回。
    --286-->执行到这一行只有一个情况:设备可读了!如果缓冲区满执行第一个语句块,否则执行下面的语句块
    --288-->缓冲区满时, 获取可拷贝的数据的大小.
    --289-->在可拷贝的大小和期望拷贝的大小中取小者给chunk
    --291-->可拷贝的数据为0, 而usb_skel->bulk_in_filled被置位才能进入这里, 所以只有一种情况: 缓冲区的数据已经拷贝完了
    --292-->既然数据已经拷贝完毕, 调用skel_do_read_io发起请求
    --300-->请求了数据,设备也反馈了,但是什么数据都没有,重试
    307-->从内核缓冲区usb_skel->bulk_in_buffer + usb_skel->bulk_in_copied开始(就是剩余未拷贝数据的首地址)拷贝chunk byte的数据到应用层
    --314-->更新usb_skel->bulk_in_copied的值
    --320-->如果可拷贝数据的大小<期望拷贝的大小, 那么显然刚才chunk=availible, 已经将所有的数据拷贝到应用层, 但是还不能满足应用层的需求, 调用skel_do_read_io来继续向设备索取数据, 当然, 索取的大小是没满足的部分, 即count-chunk
    --324-->usb_skel->bulk_in_filled没有被置位, 表示内核缓冲区没有数据, 调用skel_do_read_io索取数据, 当然, 索取的大小是全部数据, 即count

    刚才也说了, 如果缓冲区不能满足应用层需求的时候, 就会调用下面这个函数向bulk usb设备请求数据, 得到数据后将数据放到缓冲区并将相应的标志位置1/置0

    189 static int skel_do_read_io(struct usb_skel *dev, size_t count)
    190 {
    191         int rv;
    193         /* prepare a read */
    194         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);
    204         dev->ongoing_read = 1;
    206 
    207         /* submit bulk in urb, which means no data to deliver */
    208         dev->bulk_in_filled = 0;
    209         dev->bulk_in_copied = 0;
    210 
    211         /* do it */
    212         rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
    223         return rv;
    224 }
    

    skel_do_read_io()
    --194-->向usb核心提交一个urb, 将资源对象dev藏在urb->context中随着urb实参传入回调函数, 和usb_fill_int_urb不同, usb_fill_bulk_urb注册的时候需要将缓冲区首地址和请求数据的大小和urb绑定到一起一同提交, 这样才知道向bulk设备请求的数据的大小, bulk设备有数据返回的时候才知道放哪.
    --204-->将usb_skel->ongoing_read置1, 表示没有数据可读
    --208-->将usb_skel->bulk_in_filled置0, 表示内核缓冲区没有数据可读
    --209-->将usb_skel->bulk_in_copied置0, 表示没有任何数据已被拷贝
    --212-->做好准备工作之后, 命令usb核心发送urb

    请求被发出后, usb总线就会静待设备的反馈, 设备有反馈后就会回调urb的注册函数, 我们看看这个回调函数都做了什么

    163 static void skel_read_bulk_callback(struct urb *urb)
    164 {
    165         struct usb_skel *dev;
    166 
    167         dev = urb->context;
    168 
    169         spin_lock(&dev->err_lock);
    170         /* sync/async unlink faults aren't errors */
    181                 dev->bulk_in_filled = urb->actual_length;
    183         dev->ongoing_read = 0;
    184         spin_unlock(&dev->err_lock);
    185 
    186         wake_up_interruptible(&dev->bulk_in_wait);
    187 }
    

    skel_read_bulk_callback
    --167-->套路, 先把资源对象拿出来
    --181-->将表示设备反馈的数据长度urb->actual_length赋值给usb_skel->bulk_in_filled, 表示缓冲区有数据了
    --183-->将usb_skel->ongoing_read置0, 表示可读了!
    --186-->唤醒因为没有数据可读而陷入睡眠的进程

    分析到这里, 应用层就可以通过usb_skeleton驱动从USB海量存储设备中获取数据了!!!写入数据的思路是一样的, 我这里就不罗嗦了.

    锁的使用

    除了对缓冲区管理的巧妙, usb_skeleton.c中对于并发控制技术的使用也值得学习, 在构造资源对象usb_skel的时候, 这个驱动使用了semaphore ,spinlock,mutex三种常用的并发控制锁机制, 接下来我们讨论一下内核大牛们是如何在不同应用场景中使用这些技术的.

    semaphore

    semaphore是以进程为单位的, 其典型特点就是当一个进程不能获取信号量的时候, 会进陷入睡眠让出CPU, 所以中断上下文不能使用semaphore。在usb_skeleton.c中,semaphore在如下场景中被使用

    335 static void skel_write_bulk_callback(struct urb *urb)
    336 {
    358         up(&dev->limit_sem);                                                                        
    359 }
    
    361 static ssize_t skel_write(struct file *file, const char *user_buffer,
    362                           size_t count, loff_t *ppos)
    363 {
    376         /*
    377          * limit the number of URBs in flight to stop a user from using up all
    378          * RAM
    379          */
    380         if (!(file->f_flags & O_NONBLOCK)) {
    381                 if (down_interruptible(&dev->limit_sem)) {
    382                         retval = -ERESTARTSYS;
    383                         goto exit;
    384                 }
    385         } else {
    386                 if (down_trylock(&dev->limit_sem)) {
    387                         retval = -EAGAIN;
    388                         goto exit;
    389                 }
    390         }
    467         return retval;
    468 }
    

    spinlock

    当不能获取临界资源时,使用spinlock的进程不会陷入睡眠, 而是忙等,所以spinlock可以用在中断上下文,但是如果不能获取资源又不出让CPU,会浪费系统资源,所以被spinlock保护的临界区不能太长。usb_skeleton主要在以下场景中使用了spinlock

    226 static ssize_t skel_read(struct file *file, char *buffer, size_t count,
    227                          loff_t *ppos)
    228 {
    250 retry:
    251         spin_lock_irq(&dev->err_lock);
    252         ongoing_io = dev->ongoing_read;
    253         spin_unlock_irq(&dev->err_lock);
    332         return rv;
    333 }
    
    

    mutex

    mutex只是用来保证互斥,在不使用trylock的时候,和semaphore一样会在得不到锁的时候进入睡眠。usb_skeleton在以下场景中使用mutex

    226 static ssize_t skel_read(struct file *file, char *buffer, size_t count,
    227                          loff_t *ppos)
    228 {
    239         /* no concurrent readers */
    240         rv = mutex_lock_interruptible(&dev->io_mutex);
    330 exit:
    331         mutex_unlock(&dev->io_mutex);
    332         return rv;
    333 }
    
  • 相关阅读:
    HDU 6106 Classes【水题】
    HDU 6106 Classes【水题】
    ACM常用解题技巧方法
    ACM常用解题技巧方法
    程序员语录
    数据库设计的三大范式
    IDEA中Java代码存入DB中为乱码
    IDEA中如何添加jar包
    Java的Protected
    Hibernate JPA 如何使用SQL文直接查询
  • 原文地址:https://www.cnblogs.com/xiaojiang1025/p/6506116.html
Copyright © 2020-2023  润新知