1. 程式人生 > >linux usb 驅動詳解

linux 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 (= 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 s