linux usb 驅動詳解 三

本部分的一些示例源碼來自drivers/usb/usb-skeleton.c,它是Linux內核爲我們提供的最基礎的USB驅動程序,USB骨架程序)
 
驅動程序把驅動對象註冊到 USB 子系統中,之後使用供應商(idVendor)和設備(idProduct)標識來判斷對應的硬件是否已經安裝.
 
驅動的設備支持列表
 
struct usb_device_id 結構提供了這個驅動支持的不同類型 USB 設備的列表. USB 核心通過此列表用來決定設備對應的驅動,熱插拔腳本也通過此列表來決定當特定設備被插入系統時,應該自動加載的驅動.
 

struct usb_device_id {
    /* 確定設備信息去和結構體中哪幾個字段匹配來判斷驅動的適用性 */
    __u16        match_flags;
    /* Used for product specific matches; range is inclusive */
    __u16        idVendor;    
//USB設備的製造商ID,須向www.usb.org申請
    __u16        idProduct;    
//USB設備的產品ID,有製造商自定
    __u16        bcdDevice_lo;    /* USB設備的產品版本號最低值*/
    __u16        bcdDevice_hi;    /* 和最高值,以BCD碼來表示。*/

    /* 分別定義設備的類,子類和協議,他們由 USB 論壇分配並定義在 USB 規範中. 這些值指定這個設備的行爲, 包括設備上所有的接口 */
    __u8        bDeviceClass;    
    __u8        bDeviceSubClass;
    __u8        bDeviceProtocol;

    /* 分別定義單個接口的類,子類和協議,他們由 USB 論壇分配並定義在 USB 規範中 */
    __u8        bInterfaceClass;
    __u8        bInterfaceSubClass;
    __u8        bInterfaceProtocol;

    /* 這個值不用來匹配驅動的, 驅動用它來在 USB 驅動的探測回調函數中區分不同的設備 */
    kernel_ulong_t    driver_info;
};


//以上結構體中__u16 match_flags;所使用的define:
//include/linux/mod_devicetable.h
/* Some useful macros to use to create struct usb_device_id */
#define USB_DEVICE_ID_MATCH_VENDOR        0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT        0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO        0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI        0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS        0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS    0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL    0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS        0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS    0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL    0x0200

//include/linux/usb.h
#define USB_DEVICE_ID_MATCH_DEVICE \
        (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE \
        (USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION \
        (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO \
        (USB_DEVICE_ID_MATCH_DEV_CLASS | \
        USB_DEVICE_ID_MATCH_DEV_SUBCLASS | \
        USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO \
        (USB_DEVICE_ID_MATCH_INT_CLASS | \
        USB_DEVICE_ID_MATCH_INT_SUBCLASS | \
        USB_DEVICE_ID_MATCH_INT_PROTOCOL)


//這個結構體一般不用手動賦值,以下的宏可以實現賦值:
/**
 * USB_DEVICE - macro used to describe a specific usb device
 * @vend: the 16 bit USB Vendor ID
 * @prod: the 16 bit USB Product ID
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific device.
 */

//僅和指定的製造商和產品ID匹配,用於需要特定驅動的設備
#define USB_DEVICE(vend,prod) \
    .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
    .idVendor = (vend), \
    .idProduct = (prod)
/**
 * USB_DEVICE_VER - describe a specific usb device with a version range
 * @vend: the 16 bit USB Vendor ID
 * @prod: the 16 bit USB Product ID
 * @lo: the bcdDevice_lo value
 * @hi: the bcdDevice_hi value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific device, with a version range.
 */

//僅和某版本範圍內的指定的製造商和產品ID匹配
#define USB_DEVICE_VER(vend, prod, lo, hi) \
    .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
    .idVendor = (vend), \
    .idProduct = (prod), \
    .bcdDevice_lo = (lo), \
    .bcdDevice_hi = (hi)

/**
 * USB_DEVICE_INTERFACE_PROTOCOL - describe a usb device with a specific interface protocol
 * @vend: the 16 bit USB Vendor ID
 * @prod: the 16 bit USB Product ID
 * @pr: bInterfaceProtocol value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific interface protocol of devices.
 */

//僅和指定的接口協議、製造商和產品ID匹配
#define USB_DEVICE_INTERFACE_PROTOCOL(vend, prod, pr) \
    .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
         USB_DEVICE_ID_MATCH_INT_PROTOCOL, \
    .idVendor = (vend), \
    .idProduct = (prod), \
    .bInterfaceProtocol = (pr)

/**
 * USB_DEVICE_INFO - macro used to describe a class of usb devices
 * @cl: bDeviceClass value
 * @sc: bDeviceSubClass value
 * @pr: bDeviceProtocol value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific class of devices.
 */

//僅和指定的設備類型相匹配
#define USB_DEVICE_INFO(cl, sc, pr) \
    .match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, \
    .bDeviceClass = (cl), \
    .bDeviceSubClass = (sc), \
    .bDeviceProtocol = (pr)

/**
 * USB_INTERFACE_INFO - macro used to describe a class of usb interfaces
 * @cl: bInterfaceClass value
 * @sc: bInterfaceSubClass value
 * @pr: bInterfaceProtocol value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific class of interfaces.
 */

//僅和指定的接口類型相匹配
#define USB_INTERFACE_INFO(cl, sc, pr) \
    .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
    .bInterfaceClass = (cl), \
    .bInterfaceSubClass = (sc), \
    .bInterfaceProtocol = (pr)

/**
 * USB_DEVICE_AND_INTERFACE_INFO - describe a specific usb device with a class of usb interfaces
 * @vend: the 16 bit USB Vendor ID
 * @prod: the 16 bit USB Product ID
 * @cl: bInterfaceClass value
 * @sc: bInterfaceSubClass value
 * @pr: bInterfaceProtocol value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific device with a specific class of interfaces.
 *
 * This is especially useful when explicitly matching devices that have
 * vendor specific bDeviceClass values, but standards-compliant interfaces.
 */

//僅和指定的製造商、產品ID和接口類型相匹配
#define USB_DEVICE_AND_INTERFACE_INFO(vend, prod, cl, sc, pr) \
    .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \
        | USB_DEVICE_ID_MATCH_DEVICE, \
    .idVendor = (vend), \
    .idProduct = (prod), \
    .bInterfaceClass = (cl), \
    .bInterfaceSubClass = (sc), \
    .bInterfaceProtocol = (pr)
/* ----------------------------------------------------------------------- */

 
對於一個只爲一個供應商的一個 USB 設備的簡單 USB 設備驅動, 其 struct usb_device_id 可定義如下:

/* Define these values to match your devices */
#define USB_SKEL_VENDOR_ID    0xfff0
#define USB_SKEL_PRODUCT_ID    0xfff0

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                    /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, skel_table);

 
MODULE_DEVICE_TABLE 宏是必需的,它允許用戶空間工具判斷該驅動可控制什麼設備. 對於 USB 驅動, 這個宏中的第一個值必須是 usb .
 
如果你需要這個驅動被系統中每個 USB 設備調用, 創建僅需設置 driver_info 成員:

static struct usb_device_id usb_ids[] = {
 {.driver_info = 42},
    {}
};

 
 
註冊USB驅動程序
  
所有 USB 驅動都必須創建的主要結構是 struct usb_driver. 這個結構必須被 USB 驅動程序手動填充並且包含多個回調函數和變量, 並向 USB 核心描述 USB 驅動程序:
 

struct usb_driver {
    const char *name;    
    /*指向驅動程序名字的指針. 它必須在內核所有的 USB 驅動中是唯一的(通常被設爲和驅動模塊名相同).當驅動在內核中運行時,會出現在/sys/bus/usb/drivers目錄中 */

    int (*probe) (struct usb_interface *intf,
         const struct usb_device_id *id);
    /* 指向 USB 驅動中探測函數指針. 當USB 核心認爲它有一個本驅動可處理的 struct usb_interface時此函數將被調用. USB 核心用來做判斷的 struct usb_device_id 指針也被傳遞給此函數.如果這個 USB 驅動確認傳遞給它的 struct usb_interface, 它應當正確地初始化設備並返回 0. 如果驅動沒有確認這個設備或發生錯誤,則返回負錯誤值 */

    void (*disconnect) (struct usb_interface *intf);
    /*指向 USB 驅動的斷開函數指針.當 struct usb_interface 從系統中清除或驅動 USB 核心卸載時,函數將被 USB 核心調用*/

    int (*ioctl) (struct usb_interface *intf, unsigned int code,
            void *buf);
    /*指向 USB 驅動的 ioctl 函數指針. 若此函數存在, 在用戶空間程序對usbfs 文件系統關聯的設備調用 ioctl 時,此函數將被調用. 實際上,當前只有 USB 集線器驅動使用這個 ioctl*/

    int (*suspend) (struct usb_interface *intf, pm_message_t message);
    /*指向 USB 驅動中掛起函數的指針*/
    int (*resume) (struct usb_interface *intf);
    /*指向 USB 驅動中恢複函數的指針*/
    int (*reset_resume)(struct usb_interface *intf);
    /*要復位一個已經被掛起的USB設備時調用此函數*/

    int (*pre_reset)(struct usb_interface *intf);
    /*在設備被複位之前由usb_reset_composite_device()調用*/
    int (*post_reset)(struct usb_interface *intf);
    /*在設備被複位之後由usb_reset_composite_device()調用*/

    const struct usb_device_id *id_table;
    /*指向 struct usb_device_id 表的指針*/

    struct usb_dynids dynids;
    struct usbdrv_wrap drvwrap;
    /*是struct device_driver driver的再包裝,struct device_driver 包含 struct module *owner;*/
    unsigned int no_dynamic_id:1;
    unsigned int supports_autosuspend:1;
    unsigned int soft_unbind:1;
};
#define    to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)

創建一個簡單的 struct usb_driver 結構, 只有 4 個成員需要初始化:

static struct usb_driver skel_driver = {
 .name = "skeleton",
 .id_table = skel_table,
 .probe = skel_probe,
 .disconnect = skel_disconnect,
};

//向 USB 核心註冊 struct usb_driver
static int __init usb_skel_init(void)
{
        int result;
        /* register this driver with the USB subsystem */
        result = usb_register(&skel_driver);
        if (result)
                err("usb_register failed. Error number %d", result);
        return result;
}

/*當 USB 驅動被卸載, struct usb_driver 需要從內核註銷(代碼如下). 當以下調用發生, 當前綁定到這個驅動的任何 USB 接口將會斷開, 並調用斷開函數*/
static void __exit usb_skel_exit(void)
{
        /* deregister this driver with the USB subsystem */
        usb_deregister(&skel_driver);
}

探測和斷開的細節

在 struct usb_driver 結構中, 有 2 個 USB 核心在適當的時候調用的函數:
(1)當設備安裝時, 如果 USB 核心認爲這個驅動可以處理,則調用探測(probe)函數,探測函數檢查傳遞給它的設備信息, 並判斷驅動是否真正合適這個設備.
(2)由於某些原因,設備被移除或驅動不再控制設備時,調用斷開(disconnect)函數,做適當清理.

探測和斷開回調函數都在 USB 集線器內核線程上下文中被調用, 因此它們休眠是合法的. 爲了縮短 USB 探測時間,大部分工作儘可能在設備打開時完成.這是因爲 USB 核心是在一個線程中處理 USB 設備的添加和移除, 因此任何慢設備驅動都可能使 USB 設備探測時間變長。

   探測函數分析
  
在探測回調函數中, USB 驅動應當初始化它可能用來管理 USB 設備的所有本地結構並保存所有需要的設備信息到本地結構, 因爲在此時做這些通常更容易.爲了和設備通訊,USB 驅動通常要探測設備的端點地址和緩衝大小. 以下是usb-skeleton的probe函數中的探測代碼:
 

    /* set up the endpoint information */
    /* use only the first bulk-in and bulk-out endpoints */
    iface_desc = interface->cur_altsetting;        //從輸入的interface中提取當前接口的端點總數
    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)) {    //如果是批量輸入端點,
            /* we found a 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;
            }
        }

        if (!dev->bulk_out_endpointAddr &&
         usb_endpoint_is_bulk_out(endpoint)) {    如果是批量輸出端點
            /* we found a 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_endpoint_num - get the endpoint's number
 * @epd: endpoint to be checked
 *
 * Returns @epd's number: 0 to 15.
 */

static inline int usb_endpoint_num(const struct usb_endpoint_descriptor *epd)
{
    return epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
}

/**
 * usb_endpoint_type - get the endpoint's transfer type
 * @epd: endpoint to be checked
 *
 * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according
 * to @epd's transfer type.
 */

static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd)
{
    return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
}

/**
 * usb_endpoint_dir_in - check if the endpoint has IN direction
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type IN, otherwise it returns false.
 */

static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN);
}

/**
 * usb_endpoint_dir_out - check if the endpoint has OUT direction
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type OUT, otherwise it returns false.
 */

static inline int usb_endpoint_dir_out(
                const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
}

/**
 * usb_endpoint_xfer_bulk - check if the endpoint has bulk transfer type
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type bulk, otherwise it returns false.
 */

static inline int usb_endpoint_xfer_bulk(
                const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
        USB_ENDPOINT_XFER_BULK);
}

/**
 * usb_endpoint_xfer_control - check if the endpoint has control transfer type
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type control, otherwise it returns false.
 */

static inline int usb_endpoint_xfer_control(
                const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
        USB_ENDPOINT_XFER_CONTROL);
}

/**
 * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type interrupt, otherwise it returns
 * false.
 */

static inline int usb_endpoint_xfer_int(
                const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
        USB_ENDPOINT_XFER_INT);
}

/**
 * usb_endpoint_xfer_isoc - check if the endpoint has isochronous transfer type
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint is of type isochronous, otherwise it returns
 * false.
 */

static inline int usb_endpoint_xfer_isoc(
                const struct usb_endpoint_descriptor *epd)
{
    return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
        USB_ENDPOINT_XFER_ISOC);
}

/**
 * usb_endpoint_is_bulk_in - check if the endpoint is bulk IN
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has bulk transfer type and IN direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_bulk_in(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_bulk(epd) && usb_endpoint_dir_in(epd));
}

/**
 * usb_endpoint_is_bulk_out - check if the endpoint is bulk OUT
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has bulk transfer type and OUT direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_bulk_out(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_bulk(epd) && usb_endpoint_dir_out(epd));
}

/**
 * usb_endpoint_is_int_in - check if the endpoint is interrupt IN
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has interrupt transfer type and IN direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_int_in(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_in(epd));
}

/**
 * usb_endpoint_is_int_out - check if the endpoint is interrupt OUT
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has interrupt transfer type and OUT direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_int_out(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_out(epd));
}

/**
 * usb_endpoint_is_isoc_in - check if the endpoint is isochronous IN
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has isochronous transfer type and IN direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_isoc_in(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_isoc(epd) && usb_endpoint_dir_in(epd));
}

/**
 * usb_endpoint_is_isoc_out - check if the endpoint is isochronous OUT
 * @epd: endpoint to be checked
 *
 * Returns true if the endpoint has isochronous transfer type and OUT direction,
 * otherwise it returns false.
 */

static inline int usb_endpoint_is_isoc_out(
                const struct usb_endpoint_descriptor *epd)
{
    return (usb_endpoint_xfer_isoc(epd) && usb_endpoint_dir_out(epd));
}

/*-------------------------------------------------------------------------*/

在設備註冊之後,USB 驅動的後續操作都是通過struct usb_interface 獲得設備的端點信息,所以要使用 usb_set_intfdata 將前面獲得的端點信息保存到struct usb_interface 下的struct device 中的void *driver_data;中,以方便以後的操作。在usb-skeleton的probe函數中的代碼:

    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev);

之後在USB的驅動程序中的打開函數和斷開函數中調用usb_get_intfdata來獲取端點數據。由於這 2 個函數, USB 驅動不需要爲系統中所有當前的設備各保持一個靜態指針數組來保存單個設備結構. 對設備信息的非直接引用使得任何 USB 驅動都支持不限數量的設備.

若這個 USB 驅動沒有和另一種處理用戶和設備交互的子系統(如 input, tty, video......)關聯, 驅動可使用 USB 主設備號,以便在用戶空間使用傳統的字符驅動接口. 爲此, USB 驅動必須在探測函數中調用 usb_register_dev 函數, 以註冊一個設備到 USB 核心. 在usb-skeleton的probe函數中的代碼:

    /* we can register the device now, as it is ready */
    retval = usb_register_dev(interface, &skel_class);
    if (retval) {
        /* something prevented us from registering this driver */
        err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }

//其中使用到的 struct usb_class_driver 結構體如下:
/**
 * struct usb_class_driver - identifies a USB driver that wants to use the USB major number
 * @name: the usb class device name for this driver. Will show up in sysfs.
 * @fops: pointer to the struct file_operations of this driver.
 * @minor_base: the start of the minor range for this driver.
 *
 * This structure is used for the usb_register_dev() and
 * usb_unregister_dev() functions, to consolidate a number of the
 * parameters used for them.
 */

struct usb_class_driver {
    char *name;    
//sysfs 用來描述設備的名字

    const struct file_operations *fops;    
// struct file_operations 結構指針, 驅動定義來註冊爲字符設備
    int minor_base;
    
/*給這個驅動安排的次設備號的起始. 所有和這個驅動相關的設備被創建爲從這個值開始的唯一的, 遞增的次設備號. 只有 16 個設備被允許在任何時刻和這個驅動關聯, 除非 CONFIG_USB_DYNAMIC_MINORS 配置選項被打開. 如

果這樣, 忽略這個變量, 並且這個設備的所有的次設備號會以先來先服務的方式分配. 建議打開了這個選項的系統使用類似 udev 的程序來產生系統中的設備節點, 因爲一個靜態的 /dev 樹不會正確工作.*/

};

//而在usb-skeleton的設置如下:
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,
};
/*
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */

static struct usb_class_driver skel_class = {
    .name =        "skel%d",
    .fops =        &skel_fops,
    .minor_base =    USB_SKEL_MINOR_BASE,
};

當 USB 設備斷開, 所有關聯到這個設備的資源都應被釋放,如果已在探測函數中調用 usb_register_dev 分配了 USB 設備的次設備號, 必須調用函數 usb_deregister_dev 來將次設備號還回 USB 核心.在斷開函數中, 需要從接口獲取之前調用 usb_set_intfdata 所設置的數據,然後設置struct usb_interface 結構指針爲 NULL,以防止錯誤的訪問.而在usb-skeleton的源碼如下:

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

    /* give back our minor */
    usb_deregister_dev(interface, &skel_class);

    /* prevent more I/O from starting */
    mutex_lock(&dev->io_mutex);
    dev->interface = NULL;
    mutex_unlock(&dev->io_mutex);

    usb_kill_anchored_urbs(&dev->submitted);

    /* decrement our usage count */
    kref_put(&dev->kref, skel_delete);

    info("USB Skeleton #%d now disconnected", minor);
}

當一個 USB 設備調用 disconnect 函數時, 所有當前正被傳送的 urb 可自動被 USB 核心取消, 不必顯式調用 usb_kill_urb. 在USB設備被斷開之後,如果驅動試圖調用 usb_submit_urb 提交urb , 將會失敗, 錯誤值爲-EPIPE.

提交和控制 urb 的過程

以usb-skeleton源碼中的寫函數爲例:

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 = (struct usb_skel *)file->private_data;

    /* verify that we actually have some data to write */
    if (count == 0)
        goto exit;

    /* limit the number of URBs in flight to stop a user from using up all RAM */
    if (down_interruptible(&dev->limit_sem)) {
        retval = -ERESTARTSYS;
        goto exit;
    }

    spin_lock_irq(&dev->err_lock);
    if ((retval = dev->errors) < 0) {
        /* any error is reported once */
        dev->errors = 0;
        /* to preserve notifications about reset */
        retval = (retval == -EPIPE) ? retval : -EIO;
    }
    spin_unlock_irq(&dev->err_lock);
    if (retval < 0)
        goto error;

    /* create a urb, and a buffer for it, and copy the data to the urb */
    /*當驅動有數據發送到 USB 設備,首先分配一個 urb */
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }

    /*以最有效的方式是創建一個 DMA 緩衝區來發送數據到設備, 並拷貝數據到緩衝區*/
    buf = usb_buffer_alloc(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;
    }

    /* this lock makes sure we don't submit URBs to gone devices */
    mutex_lock(&dev->io_mutex);
    if (!dev->interface) {        /* disconnect() was called */
        mutex_unlock(&dev->io_mutex);
        retval = -ENODEV;
        goto error;
    }

    /* initialize the urb properly */
    /*在將urb提交給 USB 核心之前,正確初始化 urb */
    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);

    /* send the data out the bulk port */
    /*提交 urb 給 USB 核心, 由它將 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;
    }

    /* release our reference to this urb, the USB core will eventually free it entirely */
    usb_free_urb(urb);


    return writesize;

error_unanchor:
    usb_unanchor_urb(urb);
error:
    if (urb) {
        usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);
        usb_free_urb(urb);
    }
    up(&dev->limit_sem);

exit:
    return retval;
}

//當urb被成功傳遞到 USB 設備(或者在傳輸中發生了錯誤), urb 回調函數將被 USB 核心調用.也就是上面初始化 urb 中的 skel_write_bulk_callback
static void skel_write_bulk_callback(struct urb *urb)
{
    struct usb_skel *dev;

    dev = urb->context;

    /* sync/async unlink faults aren't errors */
    /*檢查 urb 的狀態,判斷這個 urb 是否成功完成傳輸*/
    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);
    }

    /* free up our allocated buffer */
    /*釋放分配給這個 urb 的緩衝區.*/
    usb_buffer_free(urb->dev, urb->transfer_buffer_length,
            urb->transfer_buffer, urb->transfer_dma);
    up(&dev->limit_sem);
}

urb 回調函數是在中斷上下文運行, 因此它不應做任何內存分配, 持有任何信號量, 或任何可導致進程休眠的事情. 如果從回調中提交 urb 並需要分配新內存塊, 需使用 GFP_ATOMIC 標誌來告知 USB 核心不要休眠.

使用簡單的函數接口(urb函數的包裝)

有時只是要發送或接受一些簡單的 USB 數據,可以使用簡單的函數接口:

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
         void *data, int len, int *actual_length, int timeout)
/*創建批量 urb 併發送到指定的設備, 接着在返回之前等待完成.*/
//struct usb_device *usb_dev :目標 USB 設備指針
//unsigned int pipe :目標 USB 設備的特定端點. 必須使用特定的宏創建.
//void *data :如果是 OUT 端點, 指向要發送到設備的數據的指針. 如果是 IN 端點, 這是從設備讀取的數據的緩衝區指針.
//int len : data 參數指向的緩衝的長度
//int *actual_length :指向函數放置真實字節數的指針,根據端點方向,這些字節要麼是被髮送到設備的,要麼是從設備中讀取的.
//int timeout :時鐘嘀噠數, 應等待的時間. 如果爲 0, 函數永遠等待操作完成.
//成功返回0,actual_length 參數包含被傳送或從設備中讀取的字節數.否則返回負的錯誤值.

int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
         __u8 requesttype, __u16 value, __u16 index, void *data,
         __u16 size, int timeout)
/*創建控制 urb 併發送到指定的設備, 接着在返回之前等待完成.*/
//struct usb_device *usb_dev :目標 USB 設備指針
//unsigned int pipe :目標 USB 設備的特定端點. 必須使用特定的宏創建.
//__u8 request :控制消息的 USB 請求值.
//__u8 requesttype :控制消息的 USB 請求類型.
//__u16 value :控制消息的 USB 消息值.
//__u16 index :控制消息的 USB 消息索引值.
//void *data :如果是 OUT 端點, 指向要發送到設備的數據的指針. 如果是 IN 端點, 這是從設備讀取的數據的緩衝區指針.
//__u16 size : data 參數指向的緩衝的長度
//int timeout :時鐘嘀噠數, 應等待的時間. 如果爲 0, 函數永遠等待操作完成.
//成功返回被傳送到或從設備讀取的字節數.否則返回負的錯誤值.

int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)
/*創建中斷 urb 併發送到指定的設備, 接着在返回之前等待完成.其實就是usb_bulk_msg的包裝,所有參數和usb_bulk_msg一樣使用*/

以上的函數不能在中斷上下文或持有自旋鎖時調用. 這些函數不能被取消, 所以小心使用; 確保驅動的 disconnect 函數了解足夠的信息, 在允許它自己從內存被卸載之前等待調用結束.


 其他 USB 函數

USB 核心中的一些輔助函數用來從所有的 USB 設備中獲取標準信息. 這些函數不能在中斷上下文或者持有自旋鎖時調用,因爲他們內部都是使用上面介紹的簡單的接口函數.這裏就不一一介紹了,包括《LDD3》介紹的這些函數,在/drivers/usb/core/message.c都有。

發佈了119 篇原創文章 · 獲贊 11 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章