天天看點

USB驅動及其源碼分析

一.USB理論部分

1.USB概述

    USB1.0版本速度1.5Mbps(低速USB)、 USB1.1版本速度12Mbps(全速USB)、 USB2.0版本速度480Mbps(高速USB)、USB3.0版本速度5.0GMbps(超高速USB)。

USB驅動由USB主機控制器驅動和USB裝置驅動組成。USB主機控制器是用來控制USB裝置和CPU之間通信的,USB主機控制器驅動主要用來驅動晶片上的主機控制器硬體。USB裝置驅動主要是指具體的例如USB滑鼠,USB鍵盤燈裝置的驅動。

一般的通用的Linux裝置,如U盤、USB滑鼠、USB鍵盤,都不需要工程師再編寫驅動,需要編寫的是特定廠商、特定晶片的驅動,而且往往也可以參考核心中已經提供的驅動模闆。 USB隻是一個總線,真正的USB裝置驅動的主體工作仍然是USB裝置本身所屬類型的驅動,如字元裝置、tty裝置、塊裝置、輸入裝置等。

2.USB主機控制器

    USB主機控制器屬于南橋晶片的一部分,通過PCI總線和處理器通信。USB主機控制器分為UHCI(英特爾提出)、OHCI(康柏和微軟提出)、 EHCI。其中OHCI驅動程式用來為非PC系統上以及帶有SiS和ALi晶片組的PC主辦上的USB晶片提供支援。UHCI驅動程式多用來為大多數其他PC主機闆(包括Intel和Via)上的USB晶片提供支援。ENCI相容OHCI和UHCI。UHCI的硬體線路比OHCI簡單,是以成本較低,但需要較複雜的驅動程式,CPU負荷稍重。主機控制器驅動程式完成的功能主要包括:解析和維護URB,根據不同的端點進行分類緩存URB;負責不同USB傳輸類型的排程工作;負責USB資料的實際傳輸工作;實作虛拟跟HUB的功能。

3.USB裝置與USB驅動的比對

    USB裝置與USB驅動怎麼比對的呢?實際上USB裝置中有一個子產品叫固件,是固件資訊和USB驅動進行的比對。固件是固化在內建電路内部的程式代碼,USB固件中包含了USB裝置的出廠資訊,辨別該裝置的廠商ID、産品ID、主版本号和次版本号等。另外固件中還包含一組程式,這組程式主要完成USB協定的處理和裝置的讀寫操作。USB裝置固件和USB驅動之間通信的規範是通過USB協定來完成的。

4.USB裝置的邏輯結構和端點的傳輸方式

    USB裝置的邏輯結構包括裝置、配置、接口和端點,分别用usb_device、usb_host_config、 usb_interface、usb_host_endpoint表示。

    端點的傳輸方式包括控制傳輸、中斷傳輸、批量傳輸、等時傳輸。

    控制傳輸主要用于向裝置發送配置資訊、擷取裝置資訊、發送指令道裝置,或者擷取裝置的狀态報告。控制傳輸一般發送的資料量較小,當USB裝置插入時,USB核心使用端點0對裝置進行配置,另外,端口0與其他端點不一樣,端點0可以雙向傳輸。

    中斷傳輸就是中斷端點以一個固定的速度來傳輸較少的資料, USB鍵盤和滑鼠就是使用這個傳輸方式。這裡說的中斷和硬體上下文中的中斷不一樣,它不是裝置主動發送一個中斷請求,而是主機控制器在保證不大于某個時間間隔内安排一次傳輸。中斷傳輸對時間要求比較嚴格,是以可以用中斷傳輸來不斷地檢測某個裝置,當條件滿足後再使用批量傳輸傳輸大量的資料。

    批量傳輸通常用在資料量大、對資料實時性要求不高的場合,例如 USB列印機、掃描器、大容量儲存設備、U盤等。

    等時傳輸同樣可以傳輸大批量資料,但是對資料是否到達沒有保證,它對實時性的要求很高,例如 音頻、視訊等裝置。

5.USB的URB請求塊

    USB請求塊(USB request block,urb)是USB主機控制器和裝置通信的主要資料結構,主機和裝置之間通過urb進行資料傳輸。當主機控制器需要與裝置互動時,隻需要填充一個urb結構,然後将其送出給USB核心,由USB核心負責對其進行處理。

   URB處理流程:

   Step1:建立一個URB結構體 usb_alloc_urb()

   Step2:初始化,被安排一個特定的USB裝置的特定端點。fill_int/bulk/control_urb()

   Step3:被USB裝置驅動送出給USB核心usb_submit_urb(),注意GPF_ATOMIC,GPF_NOIO,GPF_KERNEL的使用差別。

   Step4:送出由USB核心指定的USB主機控制器驅動,被主機控制器驅動處理,進行一次到USB裝置的傳輸,該過程由USB核心和主機控制器完成,不受USB裝置驅動控制

   Step5:當urb完成,USB主機控制器驅動通知USB裝置驅動。

    簡單的批量與控制URB:

    有時候USB驅動程式隻是從USB裝置上接收或發送一些簡單的資料,這時候可以使用usb_bulk/control_msg()完成,這兩個函數是同步的,是以不能在中斷上下文和持有自旋鎖的情況下使用。

6.USB的枚舉過程

    核心輔助線程khubd用來監視與該集線器連接配接的所有端口,通常情況下,該線程處于休眠狀态,當集線器驅動程式檢測到USB端口狀态變化後,該核心線程立馬喚醒。

    USB的枚舉過程:USB的枚舉過程是熱插拔USB裝置的起始步驟,該過程中,主機控制器擷取裝置的相關資訊并配置好裝置,集線器驅動程式負責該枚舉過程。枚舉過程主要分如下幾步:

    Step1:根集線器報告插入裝置導緻的端口電流變化,集線器驅動程式檢測到這一狀态變化後,喚醒khubd線程。

    Step2:khubd識别出電流變化的那個端口。

    Step3:khubd通過給控制端點0發送控制URB來實作從1-127中選出一個數作為插入裝置的批量端點。

    Step4:khubd利用端口0使用的控制URB從插入的裝置那裡獲得裝置描述符,然後獲得配置描述符,并選擇一個合适的。

    Step5:khubd請求USB核心把對應的客戶驅動程式和該USB裝置挂鈎。

二.USB驅動分析

    核心代碼分析包括USB驅動架構、滑鼠驅動、鍵盤驅動、U盤驅動:

USB驅動編寫的主要架構/drivers/usb/usb-skeleton.c

USB滑鼠驅動 /drivers/hid/usbhid/usbmouse.c

USB鍵盤驅動動 /drivers/hid/usbhid/usbkbd.c

USB Mass Storage是一類USB儲存設備, U盤便是其中之一,主要分析的驅動檔案是/drivers/usb/storage/usb.c

1.USB驅動架構usb-skeleton.c

    USB骨架程式可以看做一個最簡單的USB裝置驅動的執行個體,其分析流程大緻如下:

module_init(usb_skel_init)---->usb_skel_init()中的usb_register(&skel_driver)---->(驅動中的id_table和裝置配置能對上号)skel_driver中的 .probe = skel_probe

---->probe函數中usb_register_dev(interface, &skel_class)語句---->skel_class 中的.fops = &skel_fops---->skel_fops中就是系統調用對應的函數指針,對應的函數

實作了系統調用函數的具體功能,這些函數僅當進行系統調用的時候被執行。

    首先看看USB骨架程式的usb_driver的定義:

static struct usb_driver skel_driver = {

    .name =        "skeleton",

    .probe =    skel_probe,  //裝置探測

    .disconnect =    skel_disconnect,

    .suspend =    skel_suspend,

    .resume =    skel_resume,

    .pre_reset =    skel_pre_reset,

    .post_reset =    skel_post_reset,

    .id_table =    skel_table,  //裝置支援項

    .supports_autosuspend = 1,

};

#define USB_SKEL_VENDOR_ID    0xfff0

#define USB_SKEL_PRODUCT_ID    0xfff0

static const struct usb_device_id skel_table[] = {

    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

    { }                    

};

MODULE_DEVICE_TABLE(usb, skel_table);

由上面代碼可見,通過USB_DEVICE宏定義了裝置支援項。

對上面usb_driver的注冊和登出發送在USB骨架程式的子產品加載和解除安裝函數中。

static int __init usb_skel_init(void)

{

    int result;

    result = usb_register(&skel_driver);  //将該驅動挂在USB總線上

    if (result)

        err("usb_register failed. Error number %d", result);

    return result;

}

一個裝置被安裝或者有裝置插入後,當USB總線上經過match比對成功,就會調用裝置驅動程式中的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);  //初始化互斥鎖

    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)); //擷取usb_device結構體

    dev->interface = interface;  //擷取usb_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);  //配置設定urb空間

            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);  //注冊 USB裝置

    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_driver的probe函數中根據usb_interface的成員尋找第一個批量輸入和輸出的端點,将端點位址、緩沖區等資訊存入USB骨架程式定義的usb_skel結構體中,并将usb_skel通過usb_set_intfdata傳為USB接口的私有資料,最後注冊USB裝置。

我們來看看這個USB骨架程式定義的usb_skel結構體

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;    

};

好了看完了probe,我們再看看disconnect函數

static void skel_disconnect(struct usb_interface *interface)

{

    struct usb_skel *dev;

    int minor = interface->minor;  //獲得接口的次裝置号

    dev = usb_get_intfdata(interface);  //獲得接口的私有資料

    usb_set_intfdata(interface, NULL);  //設定接口的私有資料為空

    usb_deregister_dev(interface, &skel_class);  //登出USB裝置

    mutex_lock(&dev->io_mutex);

    dev->interface = NULL;

    mutex_unlock(&dev->io_mutex);

    usb_kill_anchored_urbs(&dev->submitted);

    kref_put(&dev->kref, skel_delete);

    dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor);

}

我們在skel_probe中最後執行了usb_register_dev(interface, &skel_class)來注冊了一個USB裝置,我們看看skel_class的定義

static struct usb_class_driver skel_class = {

    .name =        "skel%d",

    .fops =        &skel_fops,

    .minor_base =    USB_SKEL_MINOR_BASE,

};

static const struct file_operations skel_fops = {

    .owner =    THIS_MODULE,

    .read =        skel_read,

    .write =    skel_write,

    .open =        skel_open,

    .release =    skel_release,

    .flush =    skel_flush,

    .llseek =    noop_llseek,

};

根據上面代碼我們知道,其實我們在probe中注冊USB裝置的時候使用的skel_class是一個包含file_operations的結構體,而這個結構體正是字元裝置檔案操作結構體。

我們先來看看這個file_operations中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);  //根據 usb_driver和次裝置号擷取裝置的接口

    if (!interface) {

        err("%s - error, can't find device for minor %d",

             __func__, subminor);

        retval = -ENODEV;

        goto exit;

    }

    dev = usb_get_intfdata(interface);  //擷取接口的私有資料 usb_skel

    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;  //将 usb_skel設定為檔案的私有資料

    mutex_unlock(&dev->io_mutex);

exit:

    return retval;

}

這個open函數實作非常簡單,它根據usb_driver和次裝置号通過usb_find_interface擷取USB接口,然後通過usb_get_intfdata獲得接口的私有資料并指派給檔案。

好了,我們看看write函數,在write函數中,我們進行了urb的配置設定、初始化和送出的操作

static ssize_t skel_write(struct file *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 = 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);  //配置設定urb

    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

    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

    usb_anchor_urb(urb, &dev->submitted);  //送出urb

    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;

error_unanchor:

    usb_unanchor_urb(urb);

error:

    if (urb) {

        usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);

        usb_free_urb(urb);

    }

    up(&dev->limit_sem);

exit:

    return retval;

}

首先說明一個問題,填充urb後,設定了transfer_flags标志,當transfer_flags中的URB_NO_TRANSFER_DMA_MAP被設定,USB核心使用transfer_dma指向的緩沖區而不是使用transfer_buffer指向的緩沖區,這表明即将傳輸DMA緩沖區。當transfer_flags中的URB_NO_SETUP_DMA_MAP被設定,如果控制urb有DMA緩沖區,USB核心将使用setup_dma指向的緩沖區而不是使用setup_packet指向的緩沖區。

另外,通過上面這個write函數我們知道,當寫函數發起的urb結束後,其完成函數skel_write_bulk_callback會被調用,我們繼續跟蹤

static void skel_write_bulk_callback(struct urb *urb)

{

    struct usb_skel *dev;

    dev = urb->context;

    if (urb->status) {

        if (!(urb->status == -ENOENT ||

            urb->status == -ECONNRESET ||

            urb->status == -ESHUTDOWN))

            err("%s - nonzero write bulk status received: %d",

                __func__, urb->status);  //出錯顯示

        spin_lock(&dev->err_lock);

        dev->errors = urb->status;

        spin_unlock(&dev->err_lock);

    }

    usb_free_coherent(urb->dev, urb->transfer_buffer_length,

              urb->transfer_buffer, urb->transfer_dma);  //釋放urb空間

    up(&dev->limit_sem);

}

很明顯,skel_write_bulk_callback主要對urb->status進行判斷,根據錯誤提示顯示錯誤資訊,然後釋放urb空間。

接着,我們看看USB骨架程式的字元裝置的read函數

static ssize_t skel_read(struct file *file, char *buffer, size_t count,

             loff_t *ppos)

{

    struct usb_skel *dev;

    int rv;

    bool ongoing_io;

    dev = 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) {  //USB核正在讀取資料中,資料沒準備好

        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;  //目前已處理好 urb

    }

    if (!dev->processed_urb) {  //目前還沒已處理好 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);  //沒可讀資料則調用 IO操作

            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);  //調用 IO操作

    } else {

        rv = skel_do_read_io(dev, count);    //緩沖區沒資料則調用 IO操作

        if (rv < 0)

            goto exit;

        else if (!(file->f_flags & O_NONBLOCK))

            goto retry;

        rv = -EAGAIN;

    }

exit:

    mutex_unlock(&dev->io_mutex);

    return rv;

}

通過上面read函數,我們知道,在讀取資料時候,如果發現緩沖區沒有資料,或者緩沖區的資料小于使用者需要讀取的資料量時,則會調用IO操作,也就是skel_do_read_io函數。

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);  //填充 urb

    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);  //送出 urb

    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;

}

好了,其實skel_do_read_io隻是完成了urb的填充和送出,USB核心讀取到了資料後,會調用填充urb時設定的回調函數skel_read_bulk_callback。

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);  //喚醒 skel_read函數

}

好了,到目前為止,我們已經把USB驅動架構usb-skeleton.c分析完了,總結下,其實很簡單,在子產品加載裡面注冊usb_driver,然後在probe函數裡初始化一些參數,最重要的是注冊了USB裝置,這個USB裝置相當于一個字元裝置,提供file_operations接口。然後設計open,close,read,write函數,這個open裡基本沒做什麼事情,在write中,通過配置設定urb、填充urb和送出urb。注意讀的urb的配置設定在probe裡申請空間,寫的urb的配置設定在write裡申請空間。在這個驅動程式中,我們重點掌握usb_fill_bulk_urb的設計。

2.USB滑鼠驅動usbmouse.c

下面我們分析下USB滑鼠驅動,滑鼠輸入HID類型,其資料傳輸采用中斷URB,滑鼠端點類型為IN。好了,我們先看看這個驅動的子產品加載部分。

static int __init usb_mouse_init(void)

{

    int retval = usb_register(&usb_mouse_driver);

    if (retval == 0)

        printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"

                DRIVER_DESC "\n");

    return retval;

}

子產品加載部分仍然是調用usb_register注冊USB驅動,我們跟蹤看看被注冊的usb_mouse_drive。

static struct usb_driver usb_mouse_driver = {

    .name        = "usbmouse",

    .probe        = usb_mouse_probe,

    .disconnect    = usb_mouse_disconnect,

    .id_table    = usb_mouse_id_table,

};

關于裝置支援項我們前面已經讨論過了

static struct usb_device_id usb_mouse_id_table [] = {

    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,

        USB_INTERFACE_PROTOCOL_MOUSE) },

    { }    

};

再細細看看USB_INTERFACE_INFO宏的定義

#define USB_INTERFACE_INFO(cl, sc, pr) \

    .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \

    .bInterfaceClass = (cl), \

    .bInterfaceSubClass = (sc), \

    .bInterfaceProtocol = (pr)

根據宏,我們知道,我們設定的支援項包括接口類,接口子類,接口協定三個比對項。

好了,我們主要看看usb_driver中定義的probe函數

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

    struct usb_device *dev = interface_to_usbdev(intf);//由接口擷取 usb_device 

    struct usb_host_interface *interface; //設定

    struct usb_endpoint_descriptor *endpoint; //端點描述符

    struct usb_mouse *mouse; //本驅動私有結構體

    struct input_dev *input_dev; //輸入結構體

    int pipe, maxp;

    int error = -ENOMEM;

    interface = intf->cur_altsetting; //擷取設定

    if (interface->desc.bNumEndpoints != 1) //滑鼠端點隻有 1個

        return -ENODEV;

    endpoint = &interface->endpoint[0].desc; //獲得端點描述符

    if (!usb_endpoint_is_int_in(endpoint)) //檢查該端點是否是中斷輸入端點

        return -ENODEV;

    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //建立中斷輸入端點

    maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //傳回端點能傳輸的最大的資料包,滑鼠的傳回的最大資料包為 4個位元組

    mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); //配置設定 mouse結構體

    input_dev = input_allocate_device(); //配置設定 input裝置空間

    if (!mouse || !input_dev)

        goto fail1;

    mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma); //配置設定緩沖區

    if (!mouse->data)

        goto fail1;

    mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //配置設定 urb

    if (!mouse->irq)

        goto fail2;

    mouse->usbdev = dev; //填充 mouse的 usb_device結構體

    mouse->dev = input_dev; //填充 mouse的 input結構體

    if (dev->manufacturer) //拷貝廠商 ID

        strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

    if (dev->product) { //拷貝産品 ID

        if (dev->manufacturer)

            strlcat(mouse->name, " ", sizeof(mouse->name));

        strlcat(mouse->name, dev->product, sizeof(mouse->name));

    }

    if (!strlen(mouse->name)) //拷貝産品 ID

        snprintf(mouse->name, sizeof(mouse->name),

             "USB HIDBP Mouse %04x:%04x",

             le16_to_cpu(dev->descriptor.idVendor),

             le16_to_cpu(dev->descriptor.idProduct));

    usb_make_path(dev, mouse->phys, sizeof(mouse->phys));

    strlcat(mouse->phys, "/input0", sizeof(mouse->phys));

    input_dev->name = mouse->name; //将滑鼠名賦給内嵌 input結構體

    input_dev->phys = mouse->phys; //将滑鼠裝置節點名賦給内嵌 input結構體

    usb_to_input_id(dev, &input_dev->id); //将 usb_driver的支援項拷貝給 input

    input_dev->dev.parent = &intf->dev;

    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); //evbit表明支援按鍵事件 (EV_KEY)和相對坐标事件 (EV_REL)             

    input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |

    BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); //keybit表明按鍵值包括左鍵、右鍵和中鍵

    input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); //relbit表明相對坐标事件值包括 X坐标和 Y坐标

    input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |

        BIT_MASK(BTN_EXTRA); //keybit表明除了左鍵、右鍵和中鍵,還支援其他按鍵

    input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); //relbit表明除了 X坐标和 Y坐标,還支援中鍵滾輪的滾動值

    input_set_drvdata(input_dev, mouse); //将 mouse設定為 input的私有資料

    input_dev->open = usb_mouse_open; //input裝置的 open

    input_dev->close = usb_mouse_close;

    usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,

             (maxp > 8 ? 8 : maxp),

             usb_mouse_irq, mouse, endpoint->bInterval); //填充 urb

    mouse->irq->transfer_dma = mouse->data_dma;

    mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /使用 transfer_dma

    error = input_register_device(mouse->dev); //注冊 input裝置

    if (error)

        goto fail3;

    usb_set_intfdata(intf, mouse);

    return 0;

fail3:    

    usb_free_urb(mouse->irq);

fail2:    

    usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);

fail1:    

    input_free_device(input_dev);

    kfree(mouse);

    return error;

}

其實上面這個probe主要是初始化usb裝置和input裝置,終極目标是為了完成urb的送出和input裝置的注冊。由于注冊為input裝置類型,那麼當使用者層open打開裝置時候,最終會調用input中的open實作打開,我們看看input中open的實作

static int usb_mouse_open(struct input_dev *dev)

{

    struct usb_mouse *mouse = input_get_drvdata(dev); //擷取私有資料

    mouse->irq->dev = mouse->usbdev; //擷取 urb指針

    if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //送出 urb

        return -EIO;

    return 0;

}

好了,當使用者層open打開這個USB滑鼠後,我們就已經将urb送出給了USB核心,那麼根據USB資料處理流程知道,當處理完畢後,USB核心會通知USB裝置驅動程式,這裡我們是響應中斷服務程式,這就相當于該URB的回調函數。我們在送出urb時候定義了中斷服務程式usb_mouse_irq,我們跟蹤看看

static void usb_mouse_irq(struct urb *urb)

{

    struct usb_mouse *mouse = urb->context;

    signed char *data = mouse->data;

    struct input_dev *dev = mouse->dev;

    int status;

    switch (urb->status) {

    case 0:            

        break;

    case -ECONNRESET:    

    case -ENOENT:

    case -ESHUTDOWN:

        return;

    default:        

        goto resubmit;

    }

    input_report_key(dev, BTN_LEFT,   data[0] & 0x01);//滑鼠左鍵

    input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);

    input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);

    input_report_key(dev, BTN_SIDE,   data[0] & 0x08);

    input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);

    input_report_rel(dev, REL_X,     data[1]);

    input_report_rel(dev, REL_Y,     data[2]);

    input_report_rel(dev, REL_WHEEL, data[3]);

    input_sync(dev);

resubmit:

    status = usb_submit_urb (urb, GFP_ATOMIC);//再次送出urb,等待下次響應

    if (status)

        err ("can't resubmit intr, %s-%s/input0, status %d",

                mouse->usbdev->bus->bus_name,

                mouse->usbdev->devpath, status);

}

根據上面的中斷服務程式,我們應該知道,系統是周期性地擷取滑鼠的事件資訊,是以在URB回調函數的末尾再次送出URB請求塊,這樣又會調用新的回調函數,周而複始。在回調函數中送出URB隻能是GFP_ATOMIC優先級,因為URB回調函數運作于中斷上下文中禁止導緻睡眠的行為。而在送出URB過程中可能會需要申請記憶體、保持信号量,這些操作或許會導緻USB核心睡眠。

最後我們再看看這個驅動的私有資料mouse的定義

struct usb_mouse {

    char name[128];//名字

    char phys[64];//裝置節點

    struct usb_device *usbdev;//内嵌usb_device裝置

    struct input_dev *dev;//内嵌input_dev裝置

    struct urb *irq;//urb結構體

    signed char *data;//transfer_buffer緩沖區

    dma_addr_t data_dma;// transfer _dma緩沖區

};

在上面這個結構體中,每一個成員的作用都應該很清楚了,尤其最後兩個的使用差別和作用,前面也已經說過。

如果最終需要測試這個USB滑鼠驅動,需要在核心中配置USB支援、對HID接口的支援、對OHCI HCD驅動的支援。另外,将驅動移植到開發闆之後,由于采用的是input裝置模型,是以還需要開發闆帶LCD屏才能測試。

3.USB鍵盤驅動usbkbd.c

跟USB滑鼠類型,USB鍵盤也屬于HID類型,代碼在/dirver/hid/usbhid/usbkbd.c下。USB鍵盤除了送出中斷URB外,還需要送出控制URB。

static int __init usb_kbd_init(void)

{

    int result = usb_register(&usb_kbd_driver);

    if (result == 0)

        printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"

                DRIVER_DESC "\n");

    return result;

}

static struct usb_driver usb_kbd_driver = {

    .name =        "usbkbd",

    .probe =    usb_kbd_probe,

    .disconnect =    usb_kbd_disconnect,

    .id_table =    usb_kbd_id_table,

};

static int usb_kbd_probe(struct usb_interface *iface,

             const struct usb_device_id *id)

{

    struct usb_device *dev = interface_to_usbdev(iface);

    struct usb_host_interface *interface;

    struct usb_endpoint_descriptor *endpoint;

    struct usb_kbd *kbd;

    struct input_dev *input_dev;

    int i, pipe, maxp;

    int error = -ENOMEM;

    interface = iface->cur_altsetting;

    if (interface->desc.bNumEndpoints != 1)

        return -ENODEV;

    endpoint = &interface->endpoint[0].desc;

    if (!usb_endpoint_is_int_in(endpoint))

        return -ENODEV;

    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//建立中斷輸入端點

    maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));//擷取傳回位元組大小

    kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //配置設定私有資料空間

    input_dev = input_allocate_device(); //配置設定input裝置空間

    if (!kbd || !input_dev)

        goto fail1;

    if (usb_kbd_alloc_mem(dev, kbd))//配置設定urb空間和其他緩沖空間

        goto fail2;

    kbd->usbdev = dev;//給内嵌結構體指派

    kbd->dev = input_dev;//給内嵌結構體指派

    if (dev->manufacturer)

        strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));

    if (dev->product) {

        if (dev->manufacturer)

            strlcat(kbd->name, " ", sizeof(kbd->name));

        strlcat(kbd->name, dev->product, sizeof(kbd->name));

    }

    if (!strlen(kbd->name))

        snprintf(kbd->name, sizeof(kbd->name),

             "USB HIDBP Keyboard %04x:%04x",

             le16_to_cpu(dev->descriptor.idVendor),

             le16_to_cpu(dev->descriptor.idProduct));

    usb_make_path(dev, kbd->phys, sizeof(kbd->phys));

    strlcat(kbd->phys, "/input0", sizeof(kbd->phys));

    input_dev->name = kbd->name;

    input_dev->phys = kbd->phys;

    usb_to_input_id(dev, &input_dev->id);//複制usb_driver的支援項給input的支援項

    input_dev->dev.parent = &iface->dev;

    input_set_drvdata(input_dev, kbd);//将kbd設定為input的私有資料

    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |

        BIT_MASK(EV_REP);

    input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |

        BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |

        BIT_MASK(LED_KANA);

    for (i = 0; i < 255; i++)

        set_bit(usb_kbd_keycode[i], input_dev->keybit);

    clear_bit(0, input_dev->keybit);

    input_dev->event = usb_kbd_event;//定義event函數

    input_dev->open = usb_kbd_open;

    input_dev->close = usb_kbd_close;

    usb_fill_int_urb(kbd->irq, dev, pipe,

             kbd->new, (maxp > 8 ? 8 : maxp),

             usb_kbd_irq, kbd, endpoint->bInterval);//填充中斷urb

    kbd->irq->transfer_dma = kbd->new_dma;

    kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//dma方式傳輸

    kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;

    kbd->cr->bRequest = 0x09;//設定控制請求的格式

    kbd->cr->wValue = cpu_to_le16(0x200);

    kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);

    kbd->cr->wLength = cpu_to_le16(1);

    usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),

                 (void *) kbd->cr, kbd->leds, 1,

                 usb_kbd_led, kbd);//填充控制urb

    kbd->led->transfer_dma = kbd->leds_dma;

    kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //設定dma和setup_dma有效

    error = input_register_device(kbd->dev);//注冊input裝置

    if (error)

        goto fail2;

    usb_set_intfdata(iface, kbd);

    device_set_wakeup_enable(&dev->dev, 1);

    return 0;

fail2:    

    usb_kbd_free_mem(dev, kbd);

fail1:    

    input_free_device(input_dev);

    kfree(kbd);

    return error;

}

在上面的probe中,我們主要是初始化一些結構體,然後送出中斷urb和控制urb,并注冊input裝置。其中有幾個地方需要細看下,其一,usb_kbd_alloc_mem的實作。其二,設定控制請求的格式。

static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)

{

    if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))//配置設定中斷urb

        return -1;

    if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))//配置設定控制urb

        return -1;

    if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)))

        return -1;

    if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) //配置設定控制urb使用的控制請求描述符

        return -1;

    if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) //配置設定控制urb使用的緩沖區

        return -1;

    return 0;

}

這裡我們需要明白中斷urb和控制urb需要配置設定不同的urb結構體,同時在送出urb之前,需要填充的内容也不同,中斷urb填充的是緩沖區和中斷處理函數,控制urb填充的是控制請求描述符和回調函數。

好了,接着我們解決第二個問題,設定控制請求的格式。cr是struct usb_ctrlrequest結構的指針,USB協定中規定一個控制請求的格式為一個8個位元組的資料包,其定義如下

struct usb_ctrlrequest {

    __u8 bRequestType;//設定傳輸方向、請求類型等

    __u8 bRequest;//指定哪個請求,可以是規定的标準值也可以是廠家定義的值

    __le16 wValue;//即将寫到寄存器的資料

    __le16 wIndex;//接口數量,也就是寄存器的偏移位址

    __le16 wLength;//資料傳輸階段傳輸多少個位元組

} __attribute__ ((packed));

USB協定中規定,所有的USB裝置都會響應主機的一些請求,這些請求來自USB主機控制器,主機控制器通過裝置的預設控制管道發出這些請求。預設的管道為0号端口對應的那個管道。

同樣這個input裝置首先由使用者層調用open函數,是以先看看input中定義的open

static int usb_kbd_open(struct input_dev *dev)

{

    struct usb_kbd *kbd = input_get_drvdata(dev);

    kbd->irq->dev = kbd->usbdev;

    if (usb_submit_urb(kbd->irq, GFP_KERNEL))//送出中斷urb

        return -EIO;

    return 0;

}

因為這個驅動裡面有一個中斷urb一個控制urb,我們先看中斷urb的處理流程。中斷urb在input的open中被送出後,當USB核心處理完畢,會通知這個USB裝置驅動,然後執行回調函數,也就是中斷處理函數usb_kbd_irq

static void usb_kbd_irq(struct urb *urb)

{

    struct usb_kbd *kbd = urb->context;

    int i;

    switch (urb->status) {

    case 0:            

        break;

    case -ECONNRESET:    

    case -ENOENT:

    case -ESHUTDOWN:

        return;

    default:        

        goto resubmit;//出錯就再次送出中斷urb

    }

    for (i = 0; i < 8; i++)

        input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);//向input子系統報告

    for (i = 2; i < 8; i++) {

        if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {

            if (usb_kbd_keycode[kbd->old[i]])

                input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);

            else

                hid_info(urb->dev,

                     "Unknown key (scancode %#x) released.\n",

                     kbd->old[i]);

        }

        if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {

            if (usb_kbd_keycode[kbd->new[i]])

                input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);

            else

                hid_info(urb->dev,

                     "Unknown key (scancode %#x) released.\n",

                     kbd->new[i]);

        }

    }

    input_sync(kbd->dev);

    memcpy(kbd->old, kbd->new, 8);

resubmit:

    i = usb_submit_urb (urb, GFP_ATOMIC);//再次送出中斷urb

    if (i)

        hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",

            kbd->usbdev->bus->bus_name,

            kbd->usbdev->devpath, i);

}

這個就是中斷urb的處理流程,跟前面講的的USB滑鼠中斷處理流程類似。好了,我們再來看看剩下的控制urb處理流程吧。

我們有個疑問,我們知道在probe中,我們填充了中斷urb和控制urb,但是在input的open中,我們隻送出了中斷urb,那麼控制urb什麼時候送出呢?

我們知道對于input子系統,如果有事件被響應,我們會調用事件處理層的event函數,而該函數最終調用的是input下的event。是以,對于input裝置,我們在USB鍵盤驅動中隻設定了支援LED選項,也就是ledbit項,這是怎麼回事呢?剛才我們分析的那個中斷urb其實跟這個input基本沒啥關系,中斷urb并不是像講鍵盤input實作的那樣屬于input下的中斷。我們在USB鍵盤驅動中的input子系統中隻設計了LED選項,那麼當input子系統有按鍵選項的時候必然會使得核心調用事件處理層的event函數,最終調用input下的event。好了,那我們來看看input下的event幹了些什麼。

static int usb_kbd_event(struct input_dev *dev, unsigned int type,

             unsigned int code, int value)

{

    struct usb_kbd *kbd = input_get_drvdata(dev);

    if (type != EV_LED) //不是LED事件就傳回

        return -1;

    kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |

               (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |

               (!!test_bit(LED_NUML,    dev->led));//将目前的LED值儲存在kbd->newleds中

    if (kbd->led->status == -EINPROGRESS)

        return 0;

    if (*(kbd->leds) == kbd->newleds)

        return 0;

    *(kbd->leds) = kbd->newleds;

    kbd->led->dev = kbd->usbdev;

    if (usb_submit_urb(kbd->led, GFP_ATOMIC))//送出控制urb

        pr_err("usb_submit_urb(leds) failed\n");

    return 0;

}

當在input的event裡送出了控制urb後,經過URB處理流程,最後傳回給USB裝置驅動的回調函數,也就是在probe中定義的usb_kbd_led

static void usb_kbd_led(struct urb *urb)

{

    struct usb_kbd *kbd = urb->context;

    if (urb->status)//送出失敗顯示

        hid_warn(urb->dev, "led urb status %d received\n",

             urb->status);

    if (*(kbd->leds) == kbd->newleds)//比較kbd->leds和kbd->newleds,如果發生變化,則更新kbd->leds

        return;

    *(kbd->leds) = kbd->newleds;

    kbd->led->dev = kbd->usbdev;

    if (usb_submit_urb(kbd->led, GFP_ATOMIC))//再次送出控制urb

        hid_err(urb->dev, "usb_submit_urb(leds) failed\n");

}

總結下,我們的控制urb走的是先由input的event送出,觸發後由控制urb的回調函數再次送出。好了,通過USB滑鼠,我們已經知道了控制urb和中斷urb的設計和處理流程。

4.U盤驅動分析

USB Mass Storage是一類USB儲存設備,這些裝置包括USB磁盤、USB硬碟、USB錄音帶機、USB光驅、U盤、記憶棒、智能卡和一些USB攝像頭等,這類裝置由USB協定支援。

首先我想去看看/driver/usb/storage/Makefile

ccflags-y := -Idrivers/scsi

obj-$(CONFIG_USB_UAS)        += uas.o

obj-$(CONFIG_USB_STORAGE)    += usb-storage.o

usb-storage-y := scsiglue.o protocol.o transport.o usb.o

usb-storage-y += initializers.o sierra_ms.o option_ms.o

usb-storage-$(CONFIG_USB_STORAGE_DEBUG) += debug.o

這是Makefile中前幾行代碼,在此我進行一個說明。第一行,-I選項表示需要編譯的目錄。當本Makefile檔案被編譯器讀取時,會先判斷/driver/scsi目錄下的檔案是否已經被編譯,如果沒有被編譯,則先編譯該目錄下的檔案後,再轉到該Makefile檔案中。第三行就是USB Mass Storage選項,是總指揮。第四、五行說明了這個檔案夾也就是usb-storage子產品必須包含的檔案,這些檔案将是主要分析的對象。第六行是調試部分。目前我們分析USB驅動,是以重點去分析這些檔案中的usb.c

同樣,我們先看看usb.c中的子產品加載部分

static int __init usb_stor_init(void)

{

    int retval;

    pr_info("Initializing USB Mass Storage driver...\n");

    retval = usb_register(&usb_storage_driver);

    if (retval == 0) {

        pr_info("USB Mass Storage support registered.\n");

        usb_usual_set_present(USB_US_TYPE_STOR);

    }

    return retval;

}

static struct usb_driver usb_storage_driver = {

    .name =        "usb-storage",

    .probe =    storage_probe,

    .disconnect =    usb_stor_disconnect,

    .suspend =    usb_stor_suspend,

    .resume =    usb_stor_resume,

    .reset_resume =    usb_stor_reset_resume,

    .pre_reset =    usb_stor_pre_reset,

    .post_reset =    usb_stor_post_reset,

    .id_table =    usb_storage_usb_ids,

    .supports_autosuspend = 1,

    .soft_unbind =    1,

};

下面重點我們來看看這個probe函數

static int storage_probe(struct usb_interface *intf,

             const struct usb_device_id *id)

{

    struct us_data *us;

    int result;

    if (usb_usual_check_type(id, USB_US_TYPE_STOR) ||

            usb_usual_ignore_device(intf))//檢測比對

        return -ENXIO;

    result = usb_stor_probe1(&us, intf, id,

            (id - usb_storage_usb_ids) + us_unusual_dev_list);//探測的第一部分

    if (result)

        return result;

    result = usb_stor_probe2(us);//探測的第二部分

    return result;

}

我們發現U盤驅動的探測分為兩個部分,我們先來看看第一個部分usb_stor_probe1

int usb_stor_probe1(struct us_data **pus,

        struct usb_interface *intf,

        const struct usb_device_id *id,

        struct us_unusual_dev *unusual_dev)

{

    struct Scsi_Host *host;

    struct us_data *us;

    int result;

    US_DEBUGP("USB Mass Storage device detected\n");

    host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us));//配置設定Scsi_Host結構體

    if (!host) {

        dev_warn(&intf->dev,

                "Unable to allocate the scsi host\n");

        return -ENOMEM;

    }

    host->max_cmd_len = 16;

    host->sg_tablesize = usb_stor_sg_tablesize(intf);

    *pus = us = host_to_us(host); //從host結構體中提取出us_data結構體

    memset(us, 0, sizeof(struct us_data));

    mutex_init(&(us->dev_mutex));

    init_completion(&us->cmnd_ready);//初始化完成量

    init_completion(&(us->notify));

    init_waitqueue_head(&us->delay_wait); //初始化等待隊列頭

    init_completion(&us->scanning_done);

    result = associate_dev(us, intf);//将us_data與USB裝置相關聯

    if (result)

        goto BadDevice;

    result = get_device_info(us, id, unusual_dev);//擷取裝置資訊

    if (result)

        goto BadDevice;

    get_transport(us); //擷取傳輸方式

    get_protocol(us); //擷取傳輸協定

    return 0;

BadDevice:

    US_DEBUGP("storage_probe() failed\n");

    release_everything(us);

    return result;

}

我們再看看U盤驅動的探測的第二部分usb_stor_probe2

int usb_stor_probe2(struct us_data *us)

{

    struct task_struct *th;

    int result;

    struct device *dev = &us->pusb_intf->dev;

    if (!us->transport || !us->proto_handler) {

        result = -ENXIO;

        goto BadDevice;

    }

    US_DEBUGP("Transport: %s\n", us->transport_name);

    US_DEBUGP("Protocol: %s\n", us->protocol_name);

    if (us->fflags & US_FL_SINGLE_LUN)

        us->max_lun = 0;

    result = get_pipes(us);//獲得管道

    if (result)

        goto BadDevice;

    if (us->fflags & US_FL_INITIAL_READ10)

        set_bit(US_FLIDX_REDO_READ10, &us->dflags);

    result = usb_stor_acquire_resources(us); //擷取資源

    if (result)

        goto BadDevice;

    snprintf(us->scsi_name, sizeof(us->scsi_name), "usb-storage %s",

                    dev_name(&us->pusb_intf->dev));

    result = scsi_add_host(us_to_host(us), dev);//添加scsi

    if (result) {

        dev_warn(dev,

                "Unable to add the scsi host\n");

        goto BadDevice;

    }

    th = kthread_create(usb_stor_scan_thread, us, "usb-stor-scan");//建立線程

    if (IS_ERR(th)) {

        dev_warn(dev,

                "Unable to start the device-scanning thread\n");

        complete(&us->scanning_done);

        quiesce_and_remove_host(us);

        result = PTR_ERR(th);

        goto BadDevice;

    }

    usb_autopm_get_interface_no_resume(us->pusb_intf);

    wake_up_process(th);//喚醒usb_stor_scan_thread線程

    return 0;

BadDevice:

    US_DEBUGP("storage_probe() failed\n");

    release_everything(us);

    return result;

}

好了,我們已經把probe大緻閱讀了一下,主要通過assocaite_dev(),get_device_info(),get_transport(),get_protocol(),get_pipes()五個函數來為us結構體指派,然後調用usb_stor_acquire_resources()來得到裝置需要的動态資源。最後建立掃描線程usb_stor_scan_thread,讓使用者能通過cat /proc/scsi/scsi看到U盤裝置。現在我們一個個分析下這裡提到了每個函數。

首先我們看看來為us結構體指派的裝置關聯函數associate_dev的實作

static int associate_dev(struct us_data *us, struct usb_interface *intf)

{

    US_DEBUGP("-- %s\n", __func__);

    us->pusb_dev = interface_to_usbdev(intf);//由接口擷取裝置

    us->pusb_intf = intf;//接口指派

    us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber;//接口數量

    US_DEBUGP("Vendor: 0x%04x, Product: 0x%04x, Revision: 0x%04x\n",

            le16_to_cpu(us->pusb_dev->descriptor.idVendor),

            le16_to_cpu(us->pusb_dev->descriptor.idProduct),

            le16_to_cpu(us->pusb_dev->descriptor.bcdDevice));

    US_DEBUGP("Interface Subclass: 0x%02x, Protocol: 0x%02x\n",

            intf->cur_altsetting->desc.bInterfaceSubClass,

            intf->cur_altsetting->desc.bInterfaceProtocol);

    usb_set_intfdata(intf, us);//把us設定為接口的私有資料

    us->cr = kmalloc(sizeof(*us->cr), GFP_KERNEL);//配置設定控制urb的控制字元空間

    if (!us->cr) {

        US_DEBUGP("usb_ctrlrequest allocation failed\n");

        return -ENOMEM;

    }

    us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE,

            GFP_KERNEL, &us->iobuf_dma);//配置設定urb的緩沖區

    if (!us->iobuf) {

        US_DEBUGP("I/O buffer allocation failed\n");

        return -ENOMEM;

    }

    return 0;

}

然後我們繼續看獲得裝置資訊函數get_device_info的實作

static int get_device_info(struct us_data *us, const struct usb_device_id *id,

        struct us_unusual_dev *unusual_dev)

{

    struct usb_device *dev = us->pusb_dev;

    struct usb_interface_descriptor *idesc =

        &us->pusb_intf->cur_altsetting->desc;

    struct device *pdev = &us->pusb_intf->dev;

    us->unusual_dev = unusual_dev; //不常用的裝置

//找到USB裝置支援的子類和協定

    us->subclass = (unusual_dev->useProtocol == USB_SC_DEVICE) ?

            idesc->bInterfaceSubClass :

            unusual_dev->useProtocol;

    us->protocol = (unusual_dev->useTransport == USB_PR_DEVICE) ?

            idesc->bInterfaceProtocol :

            unusual_dev->useTransport;

    us->fflags = USB_US_ORIG_FLAGS(id->driver_info);

    adjust_quirks(us);

    if (us->fflags & US_FL_IGNORE_DEVICE) { //USB裝置不能被系統識别則退出

        dev_info(pdev, "device ignored\n");

        return -ENODEV;

    }

    if (dev->speed != USB_SPEED_HIGH) //USB裝置不支援高速則改為低速

        us->fflags &= ~US_FL_GO_SLOW;

    if (us->fflags)

        dev_info(pdev, "Quirks match for vid %04x pid %04x: %lx\n",

                le16_to_cpu(dev->descriptor.idVendor),

                le16_to_cpu(dev->descriptor.idProduct),

                us->fflags);

//根據生産廠商和産品号來設定協定、傳輸類型等參數

    if (id->idVendor || id->idProduct) {

        static const char *msgs[3] = {

            "an unneeded SubClass entry",

            "an unneeded Protocol entry",

            "unneeded SubClass and Protocol entries"};

        struct usb_device_descriptor *ddesc = &dev->descriptor;

        int msg = -1;

        if (unusual_dev->useProtocol != USB_SC_DEVICE &&

            us->subclass == idesc->bInterfaceSubClass)

            msg += 1;

        if (unusual_dev->useTransport != USB_PR_DEVICE &&

            us->protocol == idesc->bInterfaceProtocol)

            msg += 2;

        if (msg >= 0 && !(us->fflags & US_FL_NEED_OVERRIDE))

            dev_notice(pdev, "This device "

                    "(%04x,%04x,%04x S %02x P %02x)"

                    " has %s in unusual_devs.h (kernel"

                    " %s)\n"

                    "   Please send a copy of this message to "

                    "<[email protected]> and "

                    "<[email protected]>\n",

                    le16_to_cpu(ddesc->idVendor),

                    le16_to_cpu(ddesc->idProduct),

                    le16_to_cpu(ddesc->bcdDevice),

                    idesc->bInterfaceSubClass,

                    idesc->bInterfaceProtocol,

                    msgs[msg],

                    utsname()->release);

    }

    return 0;

}

我們繼續看得到傳輸方式函數get_transport,這個函數主要獲得USB裝置支援的通信協定,并設定USB驅動的傳輸類型。對于U盤,USB協定規定它屬于Bulk-only的傳輸方式,也就是它的us->protocot為US_PR_BULK

static void get_transport(struct us_data *us)

{

    switch (us->protocol) {

    case USB_PR_CB:

        us->transport_name = "Control/Bulk";

        us->transport = usb_stor_CB_transport;

        us->transport_reset = usb_stor_CB_reset;

        us->max_lun = 7;

        break;

    case USB_PR_CBI:

        us->transport_name = "Control/Bulk/Interrupt";

        us->transport = usb_stor_CB_transport;

        us->transport_reset = usb_stor_CB_reset;

        us->max_lun = 7;

        break;

    case USB_PR_BULK:

        us->transport_name = "Bulk";

        us->transport = usb_stor_Bulk_transport;//傳輸函數

        us->transport_reset = usb_stor_Bulk_reset;

        break;

    }

}

好了,接着我們看獲得協定資訊的get_protocol函數,該函數根據不同的協定,用來設定協定的傳輸函數。對于U盤,USB協定規定us->subclass為US_SC_SCSI

static void get_protocol(struct us_data *us)

{

    switch (us->subclass) {

    case USB_SC_RBC:

        us->protocol_name = "Reduced Block Commands (RBC)";

        us->proto_handler = usb_stor_transparent_scsi_command;//協定處理函數

        break;

    case USB_SC_8020:

        us->protocol_name = "8020i";

        us->proto_handler = usb_stor_pad12_command;

        us->max_lun = 0;

        break;

    case USB_SC_QIC:

        us->protocol_name = "QIC-157";

        us->proto_handler = usb_stor_pad12_command;

        us->max_lun = 0;

        break;

    case USB_SC_8070:

        us->protocol_name = "8070i";

        us->proto_handler = usb_stor_pad12_command;

        us->max_lun = 0;

        break;

    case USB_SC_SCSI:

        us->protocol_name = "Transparent SCSI";

        us->proto_handler = usb_stor_transparent_scsi_command;

        break;

    case USB_SC_UFI:

        us->protocol_name = "Uniform Floppy Interface (UFI)";

        us->proto_handler = usb_stor_ufi_command;

        break;

    }

}

最後一個初始化us的函數是獲得管道資訊的get_pipes函數。

static int get_pipes(struct us_data *us)

{

    struct usb_host_interface *altsetting =

        us->pusb_intf->cur_altsetting; //擷取設定

    int i;

    struct usb_endpoint_descriptor *ep; //定義端點描述符

    struct usb_endpoint_descriptor *ep_in = NULL;//定義輸入端點描述符

    struct usb_endpoint_descriptor *ep_out = NULL;//定義輸出端點描述符

    struct usb_endpoint_descriptor *ep_int = NULL;//定義中斷端點描述符

    for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {

        ep = &altsetting->endpoint[i].desc;//擷取端點描述符

        if (usb_endpoint_xfer_bulk(ep)) { //是否是批量傳輸端點

            if (usb_endpoint_dir_in(ep)) { //是否是輸入端點

                if (!ep_in)

                    ep_in = ep;//設定為批量傳輸輸入端點

            } else {

                if (!ep_out)

                    ep_out = ep;/設定為批量傳輸輸出端點

            }

        }

        else if (usb_endpoint_is_int_in(ep)) { //是否是中斷端點

            if (!ep_int)

                ep_int = ep;//設定為中斷端點

        }

    }

    if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) {

        US_DEBUGP("Endpoint sanity check failed! Rejecting dev.\n");

        return -EIO;

    }

    us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0);//建立輸出控制端點

    us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0);//建立輸入控制端點

    us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev,

        usb_endpoint_num(ep_out));//建立輸出批量傳輸端點

    us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev,

        usb_endpoint_num(ep_in));//建立輸入批量傳輸端點

    if (ep_int) {

        us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev,

            usb_endpoint_num(ep_int));//建立中斷傳輸端點

        us->ep_bInterval = ep_int->bInterval;//設定中斷間隔時間

    }

    return 0;

}

析完上面get_pipes的代碼,需要補充說明的是,在我們的U盤中隻有輸入批量傳輸和輸出批量傳輸兩個端點,不存在控制端點,如果出現控制端點,那麼裝置支援CBI協定,即Control/Bulk/Interrupt協定,另外U盤也沒有中斷端點。

分析完上面五個對cr初始化的函數後,我們接着需要看usb_stor_acquire_resources了,這個函數主要功能是初始化裝置,并建立資料傳輸的控制線程。

static int usb_stor_acquire_resources(struct us_data *us)

{

    int p;

    struct task_struct *th;

    us->current_urb = usb_alloc_urb(0, GFP_KERNEL); //申請urb

    if (!us->current_urb) {

        US_DEBUGP("URB allocation failed\n");

        return -ENOMEM;

    }

    if (us->unusual_dev->initFunction) { //特殊裝置的初始化函數

        p = us->unusual_dev->initFunction(us);

        if (p)

            return p;

    }

    th = kthread_run(usb_stor_control_thread, us, "usb-storage"); //建立并執行控制線程

    if (IS_ERR(th)) {

        dev_warn(&us->pusb_intf->dev,

                "Unable to start control thread\n");

        return PTR_ERR(th);

    }

    us->ctl_thread = th; //儲存線程号

    return 0;

}

在上面這個usb_stor_acquire_resources函數中,我們建立并執行了usb_stor_control_thread這個核心線程,這個控制線程用來完成資料的接收和發送,它會一直運作,直到驅動程式退出。

我們來看看這個控制線程。

static int usb_stor_control_thread(void * __us)

{

    struct us_data *us = (struct us_data *)__us;

    struct Scsi_Host *host = us_to_host(us);

    for(;;) {

        US_DEBUGP("*** thread sleeping.\n");

        if (wait_for_completion_interruptible(&us->cmnd_ready))//等待使用者層SCSI指令喚醒

            break;

        US_DEBUGP("*** thread awakened.\n");

        mutex_lock(&(us->dev_mutex));

        scsi_lock(host);

        if (us->srb == NULL) {//為循環中逾時後的退出

            scsi_unlock(host);

            mutex_unlock(&us->dev_mutex);

            US_DEBUGP("-- exiting\n");

            break;

        }

        if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {  //直接跳到逾時判斷去

            us->srb->result = DID_ABORT << 16;

            goto SkipForAbort;

        }

        scsi_unlock(host);

        if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) {//方向

            US_DEBUGP("UNKNOWN data direction\n");

            us->srb->result = DID_ERROR << 16;

        }

        else if (us->srb->device->id &&

                !(us->fflags & US_FL_SCM_MULT_TARG)) {

            US_DEBUGP("Bad target number (%d:%d)\n",

                  us->srb->device->id, us->srb->device->lun);

            us->srb->result = DID_BAD_TARGET << 16;

        }

        else if (us->srb->device->lun > us->max_lun) {

            US_DEBUGP("Bad LUN (%d:%d)\n",

                  us->srb->device->id, us->srb->device->lun);

            us->srb->result = DID_BAD_TARGET << 16;

        }

        else if ((us->srb->cmnd[0] == INQUIRY) &&

                (us->fflags & US_FL_FIX_INQUIRY)) {//如果SCSI是請求指令的處理

            unsigned char data_ptr[36] = {

                0x00, 0x80, 0x02, 0x02,

                0x1F, 0x00, 0x00, 0x00};

            US_DEBUGP("Faking INQUIRY command\n");

            fill_inquiry_response(us, data_ptr, 36); //填充一個請求指令

            us->srb->result = SAM_STAT_GOOD;

        }

        else {

            US_DEBUG(usb_stor_show_command(us->srb));

            us->proto_handler(us->srb, us); //資料傳輸

            usb_mark_last_busy(us->pusb_dev);

        }

        scsi_lock(host);

        if (us->srb->result != DID_ABORT << 16) {

            US_DEBUGP("scsi cmd done, result=0x%x\n",

                   us->srb->result);

            us->srb->scsi_done(us->srb);

        } else {

SkipForAbort:

            US_DEBUGP("scsi command aborted\n");

        }

        if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {//逾時處理

            complete(&(us->notify));

            clear_bit(US_FLIDX_ABORTING, &us->dflags);

            clear_bit(US_FLIDX_TIMED_OUT, &us->dflags);

        }

        us->srb = NULL;

        scsi_unlock(host);

        mutex_unlock(&us->dev_mutex);

    }

    for (;;) {

        set_current_state(TASK_INTERRUPTIBLE);

        if (kthread_should_stop())

            break;

        schedule();

    }

    __set_current_state(TASK_RUNNING);

    return 0;

}    

對于上面這個控制線程,首先該函數執行了一個for(;;),這是一個死循環,也就是這個函數作為一些線程用可以不停息的運作。同時,根據剛開始wait_for_completion_interruptible代碼,我們知道開始就進入睡眠狀态了,隻有喚醒us->cmnd_ready這個控制線程才能繼續執行下去,那我們要知道什麼時候釋放這把鎖來喚醒下面的程式呢?

其實有兩個地方,一個是子產品解除安裝的時候,另一個就是有SCSI指令發過來。每一次應用層發過來SCSI指令了,比如你去讀寫/dev/sda,最終SCSI核心層就會調用與該主機對應的queuecommand函數,這個函數是scsi_host_template結構體成員,在probe中scsi_host_alloc時候注冊的。下面是queuecommand函數的實作。

static int queuecommand(struct scsi_cmnd *srb,void (*done)(struct scsi_cmnd *))

{

  struct us_data *us = host_to_us(srb->device->host);

  US_DEBUGP("%s called\n", __func__);

  if (us->srb != NULL) {

      printk(KERN_ERR USB_STORAGE "Error in %s: us->srb = %p\n",__func__, us->srb);

      return SCSI_MLQUEUE_HOST_BUSY;

   }

  if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) {

  US_DEBUGP("Fail command during disconnect\n");

  srb->result = DID_NO_CONNECT << 16;

  done(srb);

  return 0;

  }

  srb->scsi_done = done;

  us->srb = srb;

  complete(&us->cmnd_ready); //釋放鎖,喚醒控制線程

  return 0;

}

好了,使用者層有了SCSI指令,就會執行我們驅動中這個控制線程。這個死循環首先會做一些判斷,然後一直進行資料通信。那麼這個死循環也是會退出的,什麼時候呢?當執行這個死循環的最後一個if語句,會進行逾時處理,如果逾時會将us->srb=NULL,而我們在這個控制線程的死循環中發現,當擷取us->cmnd_ready鎖後,第一個執行的代碼就是判斷us->srb是否為NULL,如果us->srb=NULL就會執行break語句,進而跳出第一個死循環。接下來進入第二個死循環,這個死循環首先判斷是否真的該結束了,如果真的結束了,那麼就break,徹底退出這個控制線程,如果不是應該徹底結束,那進行schedule重新排程控制子線程。

到目前為止,我們的控制線程就已經分析完了,不過我們發現,這個控制線程是在usb_stor_acquire_resources中定義的,在usb_stor_acquire_resources之後,我們還建立了usb_stor_scan_thread線程,這是一個掃描線程。

static int usb_stor_scan_thread(void * __us)

{

    struct us_data *us = (struct us_data *)__us;

    struct device *dev = &us->pusb_intf->dev;

    dev_dbg(dev, "device found\n");

    set_freezable();//裝置在一定時間内沒有響應,會挂起

    if (delay_use > 0) { // delay_use秒後如果U盤沒拔出則繼續執行,否則執行disconnect

        dev_dbg(dev, "waiting for device to settle "

                "before scanning\n");

        wait_event_freezable_timeout(us->delay_wait,

                test_bit(US_FLIDX_DONT_SCAN, &us->dflags),

                delay_use * HZ);

    }

    if (!test_bit(US_FLIDX_DONT_SCAN, &us->dflags)) {

        if (us->protocol == USB_PR_BULK &&

                !(us->fflags & US_FL_SINGLE_LUN)) {

            mutex_lock(&us->dev_mutex);

            us->max_lun = usb_stor_Bulk_max_lun(us);//詢問裝置支援多少個LUN

            mutex_unlock(&us->dev_mutex);

        }

        scsi_scan_host(us_to_host(us));

        dev_dbg(dev, "scan complete\n");

    }

    usb_autopm_put_interface(us->pusb_intf);

    complete_and_exit(&us->scanning_done, 0);//本程序結束,喚醒disconnect中的程序

}

對于上面這個掃描線程,裡面的usb_stor_Bulk_max_lun函數完成了主機控制器與裝置之間的第一次通信。USB驅動程式首先發送一個指令,然後裝置根據指令傳回一些資訊,這裡顯示的是一個表示LUN個數的數字,usb_stor_Bulk_max_lun完成的是一次控制傳輸。

int usb_stor_Bulk_max_lun(struct us_data *us)

{

    int result;

    us->iobuf[0] = 0; //預設隻有0個LUN

    result = usb_stor_control_msg(us, us->recv_ctrl_pipe,

                 US_BULK_GET_MAX_LUN,

                 USB_DIR_IN | USB_TYPE_CLASS |

                 USB_RECIP_INTERFACE,

                 0, us->ifnum, us->iobuf, 1, 10*HZ);//向裝置發送一個指令

    US_DEBUGP("GetMaxLUN command result is %d, data is %d\n",

          result, us->iobuf[0]);

    if (result > 0)

        return us->iobuf[0];

    return 0;

}

我們看看裡面usb_stor_control_msg的實作

int usb_stor_control_msg(struct us_data *us, unsigned int pipe,

         u8 request, u8 requesttype, u16 value, u16 index,

         void *data, u16 size, int timeout)

{

    int status;

    US_DEBUGP("%s: rq=%02x rqtype=%02x value=%04x index=%02x len=%u\n",

            __func__, request, requesttype,

            value, index, size);

    us->cr->bRequestType = requesttype; //初始化us->cr

    us->cr->bRequest = request;

    us->cr->wValue = cpu_to_le16(value);

    us->cr->wIndex = cpu_to_le16(index);

    us->cr->wLength = cpu_to_le16(size);

    usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe,

             (unsigned char*) us->cr, data, size,

             usb_stor_blocking_completion, NULL);//填充控制urb

    status = usb_stor_msg_common(us, timeout);//繼續填充控制urb并送出

    if (status == 0)

        status = us->current_urb->actual_length;

    return status;

}

繼續往下看usb_stor_msg_common的實作

static int usb_stor_msg_common(struct us_data *us, int timeout)

{

    struct completion urb_done;

    long timeleft;

    int status;

    if (test_bit(US_FLIDX_ABORTING, &us->dflags))//裝置處于放棄狀态則結束

        return -EIO;

    init_completion(&urb_done); //初始化完成量

    us->current_urb->context = &urb_done;

    us->current_urb->transfer_flags = 0;

    if (us->current_urb->transfer_buffer == us->iobuf)

        us->current_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

    us->current_urb->transfer_dma = us->iobuf_dma;

    status = usb_submit_urb(us->current_urb, GFP_NOIO);//送出控制urb

    if (status) {

        return status;

    }

    set_bit(US_FLIDX_URB_ACTIVE, &us->dflags);

    if (test_bit(US_FLIDX_ABORTING, &us->dflags)) {//目前還沒取消urb時,取消urb請求

        if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) {

            US_DEBUGP("-- cancelling URB\n");

            usb_unlink_urb(us->current_urb);

        }

    }

 //等待直到urb完成,如果1秒時間到程序沒有被信号喚醒,則自動喚醒

    timeleft = wait_for_completion_interruptible_timeout(

            &urb_done, timeout ? : MAX_SCHEDULE_TIMEOUT);

    clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags);

    if (timeleft <= 0) {

        US_DEBUGP("%s -- cancelling URB\n",

              timeleft == 0 ? "Timeout" : "Signal");

        usb_kill_urb(us->current_urb);

    }

    return us->current_urb->status;

}

  通過對上面這個usb_stor_msg_common函數的分析,我們現在已經把控制urb送出給USB核心了,當處理完,就會通知USB裝置驅動,調用其回調函數,該回調函數在填充控制urb時已經說明,也就是usb_stor_blocking_completion函數

static void usb_stor_blocking_completion(struct urb *urb)

{

    struct completion *urb_done_ptr = urb->context;

    complete(urb_done_ptr);

}

當一次通信完成,執行了回調函數後,就會釋放鎖,這樣stor_msg_common函數中的wait_for_completion_interruptible_timeout處就會被喚醒,至此一次通信完畢。

最後需要補充說明一個問題,在上面送出控制urb時,flag标志使用的是GFP_NOIO。GFP_NOIO标志的意思是不能在申請記憶體的時候進行I/O操作,原因是usb_submit_urb()送出之後,會讀取磁盤或者U盤中的資料,這種情況下,由于虛拟記憶體的原因,申請記憶體的函數還需要讀取磁盤。是以不允許在usb_submit_urb()送出urb時進行I/O操作。

總結下,USB裝置驅動主要圍繞URB請求塊,也就是控制URB、中斷URB、批量URB、等時URB。USB骨骼架構是批量URB的例子。USB滑鼠是一個中斷URB和input子系統結合的例子。USB鍵盤是一個控制URB和中斷URB結合input子系統的例子。USB的U盤是批量URB和控制URB結合的例子。不幸的是,等時URB沒有填充函數,是以等時URB在被送出給USB核心之前,需要手動進行初始化。

U盤驅動測試:

本Mini2440開發闆具有兩種USB 接口,一個是USB Host,它和普通PC的USB接口是一樣的,可以接USB 攝像頭、USB 鍵盤、USB 滑鼠、優盤等常見的USB外設,另外一種是USB Slave,我們一般使用它來下載下傳程式到目标闆。對于U盤的測試,要配置核心後才能進行測試。

實驗環境:核心linux2.6.32.2,arm-linux-gcc交叉編譯器,mini2440開發闆。

核心配置:(1)因為優盤用到了 SCSI 指令,是以我們先增加SCSI 支援。在 Device Drivers 菜單裡面,選擇SCSI device support。(2)選擇 USB support,按回車進入USB support 菜單,找到并選中USB Mass Storage support。(3)另外,現在的優盤等移動存儲器使用的大都是FAT/FAT32 格式的,是以我們還需要添加FAT32 檔案系統的支援,在核心配置主菜單下進入FAT32 檔案系統配置子菜單,為了支援中英文的編碼,在File systems菜單下選擇Native language support 。

接上面的步驟,在核心源代碼根目錄下執行:make zImage,把生成的新核心燒寫到開發闆中,先不要插入優盤(這樣做是為了看插入時的列印資訊),等系統啟動後,進入指令行控制台,此時優盤,可以看到其廠家ID和産品ID,以及儲存設備uba1資訊。

(1) 執行cat /proc/partitions檢視磁盤分區資訊

(2) 挂載U盤,在mnt目錄下建立usb目錄

執行mkdir /mnt/usb ;mount /dev/uba1 /mnt/usb/

(3) 檢視U盤資訊 ls /mnt/usb –l

(4) 檢視挂載後的分區資訊 df –h

繼續閱讀