"./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 }