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

时间:2023-03-08 20:09:36

"./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供内核引用计数用

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

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 }