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

"./drivers/usb/usb-skeleton.c"是內核提供給usb設備驅動開發者的海量存儲usb設備的模板程序, 程序不長, 通用性卻很強,十分經典, 深刻理解這個文件能夠幫助咱們更好的理解usb子系統以及usb設備驅動框架, 寫出更好的usb海量存儲設備驅動。node

匹配前

既然是一個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便可搞定數據傳輸問題async

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, 這種寫法值得借鑑post

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);this

經過上面的分析, 咱們發現了一個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和readspa

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 }
相關文章
相關標籤/搜索