(6.3)USB驅動程序框架

/* AUTHOR: Pinus

* Creat on : 2018-11-5

* KERNEL : linux-4.4.145

* REFS : Linux USB驅動學習總結(二)---- USB設備驅動

               chenliang0224的專欄

               hub_thread

               usb hub驅動

               hub_probe()

               Linux USB 驅動開發(三)—— 編寫USB 驅動程序

*/

USB驅動程序框架:

app:

-------------------------------------------

USB設備驅動程序 // 知道數據含義

內核 --------------------------------------

USB總線驅動程序 // 1. 識別, 2. 找到匹配的設備驅動, 3. 提供USB讀寫函數 (它不知道數據含義)

-------------------------------------------

USB主機控制器

UHCI OHCI EHCI

硬件 -----------

USB設備

 

| UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)

| OHCI: microsoft 低速/全速

| EHCI: 高速(480Mbps)

USB總線驅動程序的作用

1. 識別USB設備

1.1 分配地址

1.2 並告訴USB設備(set address)

1.3 發出命令獲取描述符

2. 查找並安裝對應的設備驅動程序

3. 提供USB讀寫函數

===========================================================

         前面學習了USB驅動的一些基礎概念與重要的數據結構,那麼究竟如何編寫一個USB 驅動程序呢?編寫與一個USB設備驅動程序的方法和其他總線驅動方式類似,驅動程序把驅動程序對象註冊到USB子系統中,稍後再使用製造商和設備標識來判斷是否安裝了硬件。當然,這些製造商和設備標識需要我們編寫進USB 驅動程序中。

USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和其他總線設備驅動編寫一樣,所有的USB驅動程序都必須創建的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。但這是個外殼,只是實現設備和總線的掛接,具體的USB 設備是什麼樣的,如何實現的,比如一個字符設備,我們還需填寫相應的文件操作接口 ,下面我們從外到裏進行剖析,學習如何搭建這樣的一個USB驅動外殼框架:

實際驅動編寫

這是根據韋東山教程編寫的簡化的鼠標驅動,將鼠標看做三個按鍵(左鍵,滾輪按鍵,右鍵)

/* 目標:usb鼠標用作按鍵
 * 左:L
 * 右:S
 * 中:Enter
 */

static struct usb_device_id usbmouse_as_key_id_table [] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE) },
    //{USB_DEVICE(0x1234,0x5678)},
    { } /* Terminating entry */
};

/* 1. 分配/設置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
    .name = "usbmouse_as_key_",
    .probe = usbmouse_as_key_probe,
    .disconnect = usbmouse_as_key_disconnect,
    .id_table = usbmouse_as_key_id_table,
};

static __init int usbmouse_as_key_init(void)
{
    usb_register(&usbmouse_as_key_driver); // 註冊驅動
    return 0;
}

static __exit void usbmouse_as_key_exit(void)
{
    usb_deregister(&usbmouse_as_key_driver);
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");

接下來分析,這個驅動程序做了些什麼?這些操作又有什麼意義?

usb_register(&usbmouse_as_key_driver); // 註冊驅動

根據經驗,這就是一個註冊的函數,簡單追溯一下注冊流程

usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name)

    |

    driver_register(&new_driver->drvwrap.driver);

    usb_create_newid_files(new_driver)

driver_register()這是讓人熟悉的函數啊,在分析platform_driver_register(drv) 時也調用了這個函數,看來是內核的通用總線驅動函數啊,其操作主要是根據bus type將new drv添加進相應總線驅動鏈表,並進行一次driver_attach,嘗試與具體設備連接(usb採用id table的方式進行匹配),如果成功掛接則調用new drv->probe。具體可以參考(3.7)一個按鍵所能涉及的:設備驅動分層分離的概念(platform bus)

由此,接下來分析 usbmouse_as_key_probe() 函數

首先涉及了struct usb_device ,這是USB設備的內核表示

struct usb_device {
    int devnum; // USB設備號;在USB總線上的地址
    char devpath[16]; // 爲了在信息中使用的設備ID字符串 (e.g., /port/...)
    u32 route; //與XHCI一起使用的樹拓撲六進制字符串
    enum usb_device_state state; //設備的狀態,此時處於configured狀態而不是attached狀態
    enum usb_device_speed speed; //表示設備是高速/全速/低速 (or error)

    struct usb_tt *tt; //事務傳輸信息,用於低速/全速設備,以及高速hub
    int ttport; //usb設備在tt hub上的port

    unsigned int toggle[2]; // 0代表IN端點,1代表OUT端點

    struct usb_device *parent; //代表hub,除非你是root hub
    struct usb_bus *bus; //設備所屬的usb總線
    struct usb_host_endpoint ep0; //端點0(默認的控制pipe)
    struct device dev; //通用設備接口
    
    struct usb_device_descriptor descriptor; //usb設備描述符,對應usb協議
    struct usb_host_bos *bos; //USB設備BOS描述符集
    struct usb_host_config *config; //設備所對應的所有配置

    struct usb_host_config *actconfig; //當前活躍的配置
    struct usb_host_endpoint *ep_in[16]; //IN端點數組
    struct usb_host_endpoint *ep_out[16]; //OUT端點數組

    char **rawdescriptors; //每個配置的原始描述符

    unsigned short bus_mA; //目前可從總線獲得
    u8 portnum; // 父端口號(默認是1)
    u8 level; //級別:USB集線器祖先數 usb hub的數量

    unsigned can_submit:1; //urb可以被提交
    unsigned persist_enabled:1; // 設備持續啓用
    unsigned have_langid:1; // whether string_langid is valid
    unsigned authorized:1; //授權:(用戶空間)策略決定是否授權該設備使用或不使用。默認情況下,有線USB設備是授權的。無線USB設備不是,直到我們從用戶空間授權他們。
    unsigned wusb:1; //是無線usb設備
    unsigned lpm_capable:1; //設備支持lpm
    unsigned usb2_hw_lpm_capable:1; //設備可以執行USB2硬件LPM
    unsigned usb2_hw_lpm_besl_capable:1; // 設備可以執行USB2硬件BESL LPM
    unsigned usb2_hw_lpm_enabled:1; //使能執行USB2硬件LPM
    unsigned usb2_hw_lpm_allowed:1; //用戶空間允許USB 2.0 LPM被使能
    unsigned usb3_lpm_enabled:1; //使能執行USB3硬件LPM
    unsigned usb3_lpm_u1_enabled:1; //USB3硬件 U1 LPM使能
    unsigned usb3_lpm_u2_enabled:1; //USB3硬件 U2 LPM使能
    int string_langid; //字符串的語言ID

    /* static strings from the device 從設備獲得的固定字符串 */
    char *product; //產品ID字符串,如果存在
    char *manufacturer; //廠家ID字符串,如果存在
    char *serial; //串口號字符串,如果存在
    struct list_head filelist; //爲着被設備而打開的usb文件系統的文件列表

    int maxchild; //如果是hub的接口,總端口數
    u32 quirks; // 整個裝置的怪癖
    atomic_t urbnum; //對整個設備來說被提交的urb的個數
    unsigned long active_duration; //活躍時間,設備不被掛起的總時間

#ifdef CONFIG_PM
    unsigned long connect_time; //usb設備首次連接時間
    unsigned do_remote_wakeup:1; //遠程喚醒使能
    unsigned reset_resume:1; //復位代替重啓
    unsigned port_is_suspended:1; // 上游端口暫停
#endif
    struct wusb_dev *wusb_dev; // 如果這是一個無線USB設備,鏈接到WUSB特定設備的數據。
    int slot_id; // XHCI分配的時隙ID
    enum usb_device_removable removable; //設備可在物理上被移除
    struct usb2_lpm_parameters l1_params; //USF2 L1 LPM狀態和L1超時的最佳EFF服務等待時間。
    struct usb3_lpm_parameters u1_params; //退出USP3 U1 LPM狀態的延遲,以及輪轂啓動超時。
    struct usb3_lpm_parameters u2_params; //退出USP3U2LPM狀態的延遲,以及輪轂啓動超時。
    unsigned lpm_disable_count; // 由usb_disable_lpm()和usb_enable_lpm() 使用的REF計數,用於跟蹤需要爲此usb_設備禁用USB 3.0鏈路電源管理的函數的數量。這個計數應該只由這些函數來操縱,而帶寬是互斥的。
};

第二個結構體struct usb_host_interface

/* 主機側封裝,用於一個接口設置的解析描述符 */

struct usb_host_interface {
    struct usb_interface_descriptor desc; // 前文的設備描述符之一

    int extralen; //額外描述符的長度
    unsigned char *extra; /* Extra descriptors 額外的描述符*/

/* array of desc.bNumEndpoints endpoints associated with this
 * interface setting. these will be in no particular order.
 * 與此接口設置相關聯的desc.bNumEndpoints端點數組。這些將沒有特別的順序。
 */
    struct usb_host_endpoint *endpoint;

    char *string; /* iInterface string, if present 接口字符串 */
};

其中struct usb_host_endpoint

struct usb_host_endpoint {
    struct usb_endpoint_descriptor desc; // 端點描述符
    struct usb_ss_ep_comp_descriptor ss_ep_comp; //這個端點的超高速伴隨描述符
    struct list_head urb_list; //這個端點的urb隊列;有USB core維護
    void *hcpriv; // 由HCD使用;通常持有硬件DMA隊列頭(QH),每個URB具有一個或多個傳輸描述符(TDS)。
    struct ep_device *ep_dev; /* For sysfs info 爲sysfs提供信息*/

    unsigned char *extra; /* Extra descriptors */
    int extralen;
    int enabled; //URBs可以被提供給這個端點
    int streams; //端點上分配的UB-3流數
};

在接下來的幾步,

首先,採用了輸入子系統結構,設置註冊了input dev,以前做過很多次,看上面程序很清楚。

然後設置了urb,用於USB傳輸

/* d. 硬件相關操作 */

/* 數據傳輸3要素: 源,目的,長度 */

/* 源: USB設備的某個端點 */

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

/* 長度: */

len = endpoint->wMaxPacketSize;

/* 目的: */

usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

 

/* 使用"3要素" */

/* 分配usb request block */

uk_urb = usb_alloc_urb(0, GFP_KERNEL);

/* 使用"3要素設置urb" */

usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);

uk_urb->transfer_dma = usb_buf_phys;

uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

 

/* 使用URB */

usb_submit_urb(uk_urb, GFP_KERNEL);

return 0;

        整個probe函數很明顯就是兩部分,一是註冊input設備,而是構建urb,用於信息傳輸,那麼正確理解urb明顯至關重要【思考一:urb的作用?】,再用函數usb_fill_int_urb設置urb時,設置了,urb完成函數函數usbmouse_as_key_irq(),其中無非就是根據上報input event,最後再次提交urb

==============================================================

drivers\usb\core\usb.c

subsys_initcall(usb_init); // 聲明子系統調用,會在內核啓動時初始化usb

/*
* Init
*/
static int __init usb_init(void)
{
    int retval;
    if (usb_disabled()) {
        pr_info("%s: USB support disabled\n", usbcore_name);
        return 0;
    }

    usb_init_pool_max();

    retval = usb_debugfs_init();
    if (retval)
        goto out;

    usb_acpi_register();

    retval = bus_register(&usb_bus_type);
    if (retval)
        goto bus_register_failed;

    retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
    if (retval)
        goto bus_notifier_failed;

    retval = usb_major_init();
    if (retval)
        goto major_init_failed;

    retval = usb_register(&usbfs_driver);
    if (retval)
        goto driver_register_failed;

    retval = usb_devio_init();
    if (retval)
        goto usb_devio_init_failed;

    retval = usb_hub_init();
    if (retval)
        goto hub_init_failed;

    retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
    if (!retval)
        goto out;


    usb_hub_cleanup();
    hub_init_failed:
    usb_devio_cleanup();
    usb_devio_init_failed:
    usb_deregister(&usbfs_driver);
    driver_register_failed:
    usb_major_cleanup();
    major_init_failed:
    bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
    bus_notifier_failed:
    bus_unregister(&usb_bus_type);
    bus_register_failed:
    usb_acpi_unregister();
    usb_debugfs_cleanup();

out:
    return retval;
}

其中現在我們只關心最需要的

int usb_hub_init(void)
{
    usb_register(&hub_driver); //註冊USB hub驅動
    ...

    /* 創建工作隊列,此函數在usb_hub_init中分配隊列,替代了以前的thrread_run的功能 */
    hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
    ...
}

【思考二:聊一聊Linux中的工作隊列2

hub_probe

    |

    hub_configure

        |

        usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, //hub 中斷

            hub, endpoint->bInterval);

當有中斷產生時將調用hub_irq

實際USB觸發

把USB設備接到開發板上,看輸出信息:

/ # usb 1-1: new full-speed USB device number 2 using s3c2410-ohci

拔掉:

/ # usb 1-1: USB disconnect, device number 2

再接上:

/ # usb 1-1: new full-speed USB device number 3 using s3c2410-ohci

拔掉:

/ # usb 1-1: USB disconnect, device number 3

 

在內核目錄drivers/下搜:

grep "USB device number" * -nR

搜到:

usb/core/hub.c:4365: "%s %s USB device number %d using %s\n",

usb/core/hub.c:4498: "%s SuperSpeed%s USB device number %d using %s\n",

 

由此可以知道,當某個usb設備插上,因爲硬件上的某些原理,會觸發某些操作,從hub.c開始分析,這也與上文分析一致

hub_irq

    kickkick_hub_wq

        hub_events

            port_event

                hub_port_connect_change

                    hub_port_connect

                        udev = usb_alloc_dev(hdev, hdev->bus, port1);

                        choose_devnum(udev); // 給新設備分配編號(對於usb2.0也是地址)

                        hub_port_init(hub, udev, port1, i); // 初始化,打印信息

                        usb_new_device(udev); //新建一個usb設備

                            device_add(&udev->dev); //添加設備

                                ...

                                device_attach // 嘗試匹配usb驅動

總結

怎麼寫USB設備驅動程序?

1. 分配/設置usb_driver結構體

.id_table

.probe

.disconnect

2. 註冊

測試

測試1th/2th:

1. make menuconfig去掉原來的USB鼠標驅動

-> Device Drivers

    -> HID Devices

        <> USB Human Interface Device (full HID) support

2. make uImage 並使用新的內核啓動

3. insmod usbmouse_as_key.ko

4. 在開發板上接入、拔出USB鼠標

 

測試3th:

1. insmod usbmouse_as_key.ko

2. ls /dev/event*

3. 接上USB鼠標

4. ls /dev/event*

5. 操作鼠標觀察數據

 

測試4th:

1. insmod usbmouse_as_key.ko

2. ls /dev/event*

3. 接上USB鼠標

4. ls /dev/event*

5. cat /dev/tty1 然後按鼠標鍵

6. hexdump /dev/event0

struct usb_device
struct usb_device
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章