usb驅動是linux核心中比較複雜的驅動之一,是以,大多數usb教程建議從usb-skeleton開始學習usb驅動。個人認為這是相當正确的,usb-sekelton提供了一個usb驅動開發的模闆,而且代碼量較少,很适合初學者的學習。
記住,對于c語言的程式設計說,資料結構是整個程式的靈魂。是以,分析别人編寫的代碼的簡潔的入口點就是高清代碼中主要資料結構之間的關系。分析以usb-skeleton為例的完整的usb驅動架構,我們就從主要的幾個資料結構入手。 一、usb驅動架構的主要資料結構 usb驅動架構主要包括裝置,配置,接口和端點幾個組成部分,相對應的資料結構為:
- struct usb_device; //usb裝置
- struct usb_host_config; //usb配置
- struct usb_host_interface; //usb接口
- struct usb_host_endpoit; //usb端口
它們之間的關系可以形象的表示為圖1。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicGcq5ieHdXO3MDMwADNxAzMx8VN5ADM0kDMy8CX5IzLcNDMxEDMy8CX05WZth2YhRHdh9CX0VmbugXauVXYulGaj5yZvxmYvw1LcpDc0RHaiojIsJye.jpg)
上圖描述的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;
- 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);
- dev->udev= usb_get_dev(interface_to_usbdev(interface));
- dev->interface= interface;
- 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)){
- 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)){
- 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;
- }
- usb_set_intfdata(interface, dev);
- retval = usb_register_dev(interface,&skel_class);
- if (retval){
- err("Not able to get a minor for this device.");
- usb_set_intfdata(interface,NULL);
- goto error;
- }
- dev_info(&interface->dev,
- "USB Skeleton device now attached to USBSkel-%d",
- interface->minor);
- return 0;
- error:
- if (dev)
- 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);
- 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;
- }
- dev = usb_get_intfdata(interface);
- if (!dev){
- retval = -ENODEV;
- goto exit;
- }
- kref_get(&dev->kref);
- 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;
- }
- }
- 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 (!dev->bulk_in_urb|| !count)
- return 0;
- rv = mutex_lock_interruptible(&dev->io_mutex);
- if (rv < 0)
- return rv;
- if (!dev->interface){
- rv = -ENODEV;
- goto exit;
- }
- retry:
- spin_lock_irq(&dev->err_lock);
- ongoing_io = dev->ongoing_read;
- spin_unlock_irq(&dev->err_lock);
- if (ongoing_io){
- if (file->f_flags& O_NONBLOCK){
- rv = -EAGAIN;
- goto exit;
- }
- rv = wait_for_completion_interruptible(&dev->bulk_in_completion);
- if (rv < 0)
- goto exit;
- dev->bulk_in_copied= 0;
- dev->processed_urb= 1;
- }
- if (!dev->processed_urb){
- wait_for_completion(&dev->bulk_in_completion);
- dev->bulk_in_copied= 0;
- dev->processed_urb= 1;
- }
- rv = dev->errors;
- if (rv < 0) {
- dev->errors= 0;
- rv = (rv== -EPIPE) ? rv: -EIO;
- dev->bulk_in_filled= 0;
- goto exit;
- }
- if (dev->bulk_in_filled){
- size_t available = dev->bulk_in_filled- dev->bulk_in_copied;
- size_t chunk =min(available,count);
- if (!available){
- rv = skel_do_read_io(dev,count);
- if (rv < 0)
- goto exit;
- else
- goto retry;
- }
- 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 (available< count)
- skel_do_read_io(dev,count - chunk);
- } else {
- 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;
- }
-
- static void skel_read_bulk_callback(struct urb*urb)
- {
- struct usb_skel *dev;
- dev = urb->context;
- spin_lock(&dev->err_lock);
- 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;
- 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;
- 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);
- spin_lock_irq(&dev->err_lock);
- dev->ongoing_read= 1;
- spin_unlock_irq(&dev->err_lock);
- 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;
- size_t writesize = min(count,(size_t)MAX_TRANSFER);
- dev = (struct usb_skel*)file->private_data;
- if (count== 0)
- goto exit;
- 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) {
- dev->errors= 0;
- retval = (retval ==-EPIPE) ? retval : -EIO;
- }
- spin_unlock_irq(&dev->err_lock);
- if (retval< 0)
- goto error;
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb){
- retval = -ENOMEM;
- goto error;
- }
- buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
- &urb->transfer_dma);
- if (!buf){
- retval = -ENOMEM;
- goto error;
- }
- if (copy_from_user(buf, user_buffer, writesize)){
- retval = -EFAULT;
- goto error;
- }
- mutex_lock(&dev->io_mutex);
- if (!dev->interface){
- mutex_unlock(&dev->io_mutex);
- retval = -ENODEV;
- goto error;
- }
- 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;
- usb_anchor_urb(urb,&dev->submitted);
- 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;
- }
- usb_free_urb(urb);
- return writesize;
- }
在函數與skel_read非常類似,唯一不同的是在寫操作過程中需要usb客戶驅動程式來顯示的配置設定和初始化用于寫操作的URB,之是以在讀操作過程中看不到這個過程,是因為,在struct usb_skel dev中已經為寫操作内嵌了一個資料成員:
- struct usb_skel {
- struct usb_device *udev;
- struct usb_interface *interface;
- struct semaphore limit_sem;
- struct usb_anchor submitted;
- struct urb *bulk_in_urb;
- unsigned char*bulk_in_buffer;
- size_t bulk_in_size;
- size_t bulk_in_filled;
- size_t bulk_in_copied;
- __u8 bulk_in_endpointAddr;
- __u8 bulk_out_endpointAddr;
- int errors;
- int open_count;
- bool ongoing_read;
- bool processed_urb;
- spinlock_t err_lock;
- struct kref kref;
- struct mutex io_mutex;
- struct completion bulk_in_completion;
- };
而且這個結構體的很多資料成員都與讀操作有關,具體為什麼這麼設計,暫時還沒有搞明白,希望大家指教!
總結:
1. usb客戶驅動還是比較簡單的,主要是因為很多功能都由usb core驅動程式事先完成了,usb用戶端驅動程式僅僅是調用一些API即可。
2. usb客戶驅動程式的開發比較固定,直接套用usb-skeleton的代碼基本就可以完成使用者自定義驅動的編寫