Linux usb子系統(一) _寫一個usb鼠標驅動

USB總線是一種典型的熱插拔的總線標準,因爲其優異的性能幾乎成爲了當下大小設備中的標配。
USB的驅動能夠分爲3類:SoC的USB控制器的驅動,主機端USB設備的驅動,設備上的USB Gadget驅動,一般,對於USB這種標準化的設備,內核已經將主機控制器的驅動編寫好了,設備上的Gadget驅動一般只運行固件程序而不是基於Linux, 因此驅動工程師的主要工做就是編寫主機端的USB設備驅動。linux

USB子系統框架

下圖表示了Linux中USB子系統的框架結構,和i2c同樣,USB子系統也可分爲三層:**設備驅動層--USB核心--控制器驅動層*api

做爲熱插拔總線, USB和非熱插拔總線最大的區別就是總線沒法事前獲知設備的信息以及設備什麼時候被插入或拔出,因此也就不能使用任意一種形式將設備信息事前寫入內核。
爲了解決因爲熱插拔引發的設備識別問題,USB總線經過枚舉的方式來獲取一個接入總線的USB設備的設備信息——一個由device->config->interface->endpoint逐級描述的設備,基於分離的思想,USB子系統中設計了一組結構來描述這幾個維度的設備信息,相比之下,i2c總線只要一個i2c_client便可描述一個設備.數組

USB總線上的全部通訊都是由主機發起的,因此本質上,USB都是採用輪詢的方式進行的。USB總線會使用輪詢的方式不斷檢測總線上是否有設備接入,若是有設備接入相應的D+D-就會有電平變化。而後總線就會按照USB規定的協議與設備進行通訊,設備將存儲在自身的設備信息依次交給主機,主機將這些信息按照4層模型組織起來。上報到內核,內核中的USB子系統再去匹配相應的驅動,USB設備驅動是面向interface這一層次的信息的緩存

做爲一種高度標準化的設備, 雖然USB自己十分複雜, 可是內核已經爲咱們完成了至關多的工做, 下述的經常使用設備驅動在內核中已經實現了。不少時候, 驅動的難度不是看設備的複雜程度, 而是看標準化程度網絡

  • 音頻設備類
  • 通訊設備類
  • HID設備類
  • 顯示設備類
  • 海量存儲設備類
  • 電源設備類
  • 打印設備類
  • 集線器設備類

核心結構和方法簡述

核心結構

基於分離的思想,USB子系統也提供了描述一個USB設備的結構,只不過基於USB協議,完整描述一個USB設備信息須要9個結構,這些結構中,前4個用來描述一個USB設備的硬件信息,即設備自己的信息,這些信息是寫入到設備的eeprom的,在任何USB主機中看到的都同樣,這些信息可使用lsusb -v命令來查看; 後5個描述一個USB設備的軟件信息,即除了硬件信息以外,Linux爲了管理一個USB設備還要封裝一些信息,是OS-specific的信息; USB設備硬件信息和軟件信息的關係相似於中斷子系統中的硬件中斷和內核中斷,只不過更復雜一點。框架

  • usb_device_descriptor來描述一個USB設備的device信息
  • usb_config_descriptor來描述一個device的config信息
  • usb_interface_descriptor來描述一個config的interface信息
  • usb_endpoint_descriptor來描述一個interface的endpoint信息函數

  • usb_device描述一個USB的device的軟件信息,包括usb_device_descriptor
  • urb_host_config描述一個USB設備config的軟件信息,包括usb_config_descriptor
  • usb_interface描述一個接口信息
  • usb_host_interface描述一個interface的設置信息,包括usb_interface_descriptor,咱們編寫驅動就是針對這一層次的
  • usb_host_endpoint描述一個interdace的endpoint信息,包括usb_endpoint_descriptor,這是USB通訊的最小單位,咱們讀寫一個設備就是針對一個endpointpost

  • usb_driver描述一個usb設備驅動, 也就是USB設備驅動開發的核心結構
  • usb_driver_id用來標識一個usb設備, 其實例id_table就是usb_driver中的一個域, 因爲usb總線中描述一個設備的複雜性, 構造這樣一個對象的方法也多種多樣
  • urb (usb request block)是在USB通訊過程當中的數據載體, 至關於i2c子系統中的i2c_msg, 網絡設備驅動中的sk_buff
  • usb_hcd描述一個SoC中的USB控制器驅動性能

核心方法

  • usb_fill_int_urb是註冊urb的API, 是整個USB通訊的核心數據封裝

核心結構和方法詳述

首先說的是那9個描述設備信息的結構, 其中的硬件信息是相互獨立的, 分別使用 這些結構在內核"include/uapi/linux/usbch9.h"有定義, 我就不貼代碼了ui

usb_device_descriptor

//include/uapi/linux/usbch9.h
258 struct usb_device_descriptor { 
259         __u8  bLength;
260         __u8  bDescriptorType;
262         __le16 bcdUSB;
263         __u8  bDeviceClass;
264         __u8  bDeviceSubClass;
265         __u8  bDeviceProtocol;
266         __u8  bMaxPacketSize0;
267         __le16 idVendor;
268         __le16 idProduct;
269         __le16 bcdDevice;
270         __u8  iManufacturer;
271         __u8  iProduct;
272         __u8  iSerialNumber;
273         __u8  bNumConfigurations;
274 } __attribute__ ((packed));

struct usb_device_descriptor
--263-->設備類別
--264-->設備子類
--265-->通訊協議
--267-->銷售商
--268-->產品ID
--272-->序列號

usb_config_descriptor

//include/uapi/linux/usbch9.h
314 struct usb_config_descriptor {
315         __u8  bLength;
316         __u8  bDescriptorType;
317 
318         __le16 wTotalLength;
319         __u8  bNumInterfaces;
320         __u8  bConfigurationValue;
321         __u8  iConfiguration;
322         __u8  bmAttributes;
323         __u8  bMaxPower;
324 } __attribute__ ((packed));

usb_interface_descriptor

//include/uapi/linux/usbch9.h
351 struct usb_interface_descriptor {
352         __u8  bLength;
353         __u8  bDescriptorType;
354 
355         __u8  bInterfaceNumber;
356         __u8  bAlternateSetting;
357         __u8  bNumEndpoints;
358         __u8  bInterfaceClass;
359         __u8  bInterfaceSubClass;
360         __u8  bInterfaceProtocol;
361         __u8  iInterface;
362 } __attribute__ ((packed));

usb_endpoint_descriptor

//include/uapi/linux/usbch9.h
369 struct usb_endpoint_descriptor {
370         __u8  bLength;
371         __u8  bDescriptorType;
372 
373         __u8  bEndpointAddress;
374         __u8  bmAttributes;
375         __le16 wMaxPacketSize;
376         __u8  bInterval;
377 
378         /* NOTE:  these two are _only_ in audio endpoints. */
379         /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
380         __u8  bRefresh;
381         __u8  bSynchAddress;
382 } __attribute__ ((packed));

usb_device

//include/linux/usb.h
 510 struct usb_device {
 511         int             devnum;
 512         char            devpath[16];
 513         u32             route;
 522         struct usb_device *parent;
 523         struct usb_bus *bus;
 524         struct usb_host_endpoint ep0;
 526         struct device dev;
 528         struct usb_device_descriptor descriptor;
 529         struct usb_host_bos *bos;
 530         struct usb_host_config *config;
 532         struct usb_host_config *actconfig;
 557         char *product;
 558         char *manufacturer;
 559         char *serial;
 561         struct list_head filelist;
 563         int maxchild;
 568         unsigned long active_duration;
 569 
 584 };

struct usb_device
--522-->這個設備的父設備, 一般就是usb塔形結構的上一個節點設備
--523-->所屬的總線是usb總線
--526-->這是一個device, 會掛接到相應的鏈表
--528-->這個軟件device結構包含的硬件device對象
--530-->軟件device擁有的全部軟件config對象, 對應硬件device擁有的全部硬件config
--531-->當下, 這個device正在使用的config
--557-->產品名
--558-->產品製造商
--559-->產品序列號
--561-->在這個設備上打開的usbfs文件的鏈表節點
--563-->子設備的最大數量

urb_host_config

//include/linux/usb.h
 275 struct usb_host_config {
 276         struct usb_config_descriptor    desc;
 278         char *string;           /* iConfiguration string, if present */
 282         struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];
 286         struct usb_interface *interface[USB_MAXINTERFACES];
 290         struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
 292         unsigned char *extra;   /* Extra descriptors */
 293         int extralen;
 294 };

struct usb_host_config
--276-->軟件config對象包含的硬件config對象
--278-->config的名稱
--282-->這個config上關聯的Interface Association Descriptor
--283-->這個config上關聯的下一級的軟件interface數組,

usb_interface

下面這個就是與驅動直接匹配的描述

160 struct usb_interface {
 163         struct usb_host_interface *altsetting;
 165         struct usb_host_interface *cur_altsetting;    
 167         unsigned num_altsetting;        /* number of alternate settings */
 171         struct usb_interface_assoc_descriptor *intf_assoc;
 173         int minor;           
 175         enum usb_interface_condition condition;         /* state of binding */
 176         unsigned sysfs_files_created:1; /* the sysfs attributes exist */
 177         unsigned ep_devs_created:1;     /* endpoint "devices" exist */
 178         unsigned unregistering:1;       /* unregistration is in progress */
 179         unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
 180         unsigned needs_altsetting0:1;   /* switch to altsetting 0 is pending */
 181         unsigned needs_binding:1;       /* needs delayed unbind/rebind */
 182         unsigned reset_running:1;
 183         unsigned resetting_device:1;    /* true: bandwidth alloc after reset */
 185         struct device dev;              /* interface specific device info */
 186         struct device *usb_dev;
 187         atomic_t pm_usage_cnt;          /* usage counter for autosuspend */
 188         struct work_struct reset_ws;    /* for resets in atomic context */
 189 };

struct usb_interface
--163-->這個interface包含的全部的setting
--164-->這個interface當前正在使用的setting
--165-->若是這個interface與一個使用了主設備號的驅動綁定了, 這個域就是interface的次設備號; 反之則沒用. 驅動應該在probe中設置這個參數

usb_host_interface

77 struct usb_host_interface {
  78         struct usb_interface_descriptor desc;
  80         int extralen;
  81         unsigned char *extra;   /* Extra descriptors */
  86         struct usb_host_endpoint *endpoint;
  88         char *string;           /* iInterface string, if present */
  89 };

struct usb_host_interface
--78-->這個interface對應的硬件interface對象
--86-->擁有的描述軟件endpoint信息的usb_host_endpoint數組
--88-->interface名稱

usb_host_endpoint

endpoint是USB設備IO的基本單元

64 struct usb_host_endpoint {
  65         struct usb_endpoint_descriptor          desc;
  66         struct usb_ss_ep_comp_descriptor        ss_ep_comp;
  67         struct list_head                urb_list;
  68         void                            *hcpriv;
  69         struct ep_device                *ep_dev;        /* For sysfs info */
  71         unsigned char *extra;   /* Extra descriptors */
  72         int extralen;
  73         int enabled;
  74 };

struct usb_host_endpoint
--65-->這個usb_host_endpoint對應的硬件endpoint信息
--67-->讀寫這個endpoint的usb鏈表, 由usb核心層(drivers/usb/core/file.c)維護
--73-->這個endpoint是否被使能了

每個硬件信息對象都包含在一個軟件信息對象中, 而軟件信息對象是層層包含的, 因此雖然驅動是基於interface描述的, 可是咱們可使用"list_entry()"很容易的向上找到config和device描述, 使用其中的域很容易的找到endpoint描述, 這9個描述設備的結構關係以下圖所示:

urb

與platform或i2c總線不一樣的是,usb總線不容許設備發起通訊,因此做爲設備驅動, 只有將須要的"材料"準備好經由核心層提交到usb控制器驅動,讓控制器驅動帶着這些"材料"去輪詢設備並將應答帶回。這些"材料"就是urb和相應的註冊參數。當usb_driver和usb設備匹配上後,咱們準備一個urb對象經過usb_fill_int_urb()/_bulk_/_control_註冊到總線,並在合適的時機經過usb_submit_urb向控制器驅動發出發送這個urb對象的命令,總線的控制器驅動收到咱們的發送命令以後,會根據咱們註冊時設置的週期不斷向匹配的設備發送請求,若是子設備響應了咱們的請求,控制器驅動就會將咱們註冊的urb對象填充好,並回調咱們的註冊函數。
對於海量存儲USB設備,若是要讓設備正常工做, 除了這些流程,還須要加上對緩存區的管理,這部分咱們下篇說。

usb_fill_int_urb

初始化並註冊一箇中斷urb。函數原型以下:

static inline void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,
                                    void *transfer_buffer,int buffer_length,usb_complete_t complete_fn,void *context,int interval)

這個函數參數比較多, urb表示咱們要註冊的urb對象; dev表示這個urb對象的目的設備; pipe表示讀寫管道, 使用usb_sndintpipe()和usb_rcvintpipe()獲取; transfer_buffer表示傳遞數據的緩衝區首地址;buffer_length表示緩衝區長度;complete_fn表示若是咱們發出的urb有了迴應, 就回調這個函數; context是回調函數的參數, 由用戶定義, 至關於request_irq中的void *dev; interval就是發送週期, 核心層會以這個參數爲週期經過usb控制器驅動輪詢設備,

usb_alloc()

urb和xxx同樣,要用內核分配函數,其中會作一些初始化的工做

usb_fill_bulk_urb

初始化並註冊一個海量存儲urb

usb_fill_control_urb

初始化並註冊一個控制urb

usb_submit_urb

通知內核發送urb對象

usb_driver

1048 struct usb_driver {
1049         const char *name;
1051         int (*probe) (struct usb_interface *intf,
1052                       const struct usb_device_id *id);
1054         void (*disconnect) (struct usb_interface *intf);
1056         int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
1057                         void *buf);
1059         int (*suspend) (struct usb_interface *intf, pm_message_t message);
1060         int (*resume) (struct usb_interface *intf);
1061         int (*reset_resume)(struct usb_interface *intf);
1063         int (*pre_reset)(struct usb_interface *intf);
1064         int (*post_reset)(struct usb_interface *intf);
1066         const struct usb_device_id *id_table;
1068         struct usb_dynids dynids;
1069         struct usbdrv_wrap drvwrap;
1070         unsigned int no_dynamic_id:1;
1071         unsigned int supports_autosuspend:1;
1072         unsigned int disable_hub_initiated_lpm:1;
1073         unsigned int soft_unbind:1;
1074 };

struct usb_driver
--1049-->usb設備的名字
--1051-->探測函數, 當usb_driver的id_table和usb設備信息匹配的時候會執行, 主要的工做是申請資源, 初始化, 提供接口
--1054-->當驅動模塊被卸載時或設備被拔出時會被執行
--1066-->功能依然是匹配同樣, 只是usb的設備信息由4個維度描述, 因此id_table能夠填充的內容也多種多樣

usb_register

註冊一個usb_driver到內核

usb_deregister

註銷一個usb_driver

id_table

內核提供了以下的宏來構造一個usb_device_id對象, 其實也就是對usb_device_id中的不一樣域進行了填充, 因爲設備的差別性, 不一樣的USB設備會上報不一樣的設備信息, 但不管上報哪些信息, 必定屬於下面這些宏的一種封裝. 能夠先使用lsusb -v查看設備的硬件信息, 再根據其提供的硬件信息肯定id_table編寫相應的驅動

USB_DEVICE

811 #define USB_DEVICE(vend, prod) \
 812         .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
 813         .idVendor = (vend), \
 814         .idProduct = (prod)

USB_DEVICE_VER

825 #define USB_DEVICE_VER(vend, prod, lo, hi) \
 826         .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
 827         .idVendor = (vend), \
 828         .idProduct = (prod), \
 829         .bcdDevice_lo = (lo), \
 830         .bcdDevice_hi = (hi)

USB_DEVICE_INTERFACE_CLASS

841 #define USB_DEVICE_INTERFACE_CLASS(vend, prod, cl) \
 842         .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
 843                        USB_DEVICE_ID_MATCH_INT_CLASS, \
 844         .idVendor = (vend), \
 845         .idProduct = (prod), \
 846         .bInterfaceClass = (cl)

USB_DEVICE_INTERFACE_PROTOCOL

857 #define USB_DEVICE_INTERFACE_PROTOCOL(vend, prod, pr) \
 858         .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
 859                        USB_DEVICE_ID_MATCH_INT_PROTOCOL, \
 860         .idVendor = (vend), \
 861         .idProduct = (prod), \
 862         .bInterfaceProtocol = (pr)

USB_DEVICE_INTERFACE_NUMBER

873 #define USB_DEVICE_INTERFACE_NUMBER(vend, prod, num) \
 874         .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
 875                        USB_DEVICE_ID_MATCH_INT_NUMBER, \
 876         .idVendor = (vend), \
 877         .idProduct = (prod), \
 878         .bInterfaceNumber = (num)

USB_DEVICE_INFO

889 #define USB_DEVICE_INFO(cl, sc, pr) \
 890         .match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, \
 891         .bDeviceClass = (cl), \
 892         .bDeviceSubClass = (sc), \
 893         .bDeviceProtocol = (pr)
 894

USB_INTERFACE_INFO

904 #define USB_INTERFACE_INFO(cl, sc, pr) \   
 905         .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
 906         .bInterfaceClass = (cl), \
 907         .bInterfaceSubClass = (sc), \
 908         .bInterfaceProtocol = (pr)

USB_DEVICE_AND_INTERFACE_INFO

924 #define USB_DEVICE_AND_INTERFACE_INFO(vend, prod, cl, sc, pr) \
 925         .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \
 926                 | USB_DEVICE_ID_MATCH_DEVICE, \
 927         .idVendor = (vend), \
 928         .idProduct = (prod), \
 929         .bInterfaceClass = (cl), \
 930         .bInterfaceSubClass = (sc), \
 931         .bInterfaceProtocol = (pr)

USB_VENDOR_AND_INTERFACE_INFO

946 #define USB_VENDOR_AND_INTERFACE_INFO(vend, cl, sc, pr) \
 947         .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \
 948                 | USB_DEVICE_ID_MATCH_VENDOR, \
 949         .idVendor = (vend), \
 950         .bInterfaceClass = (cl), \
 951         .bInterfaceSubClass = (sc), \
 952         .bInterfaceProtocol = (pr)
 953

id_table實例

下面是內核"drdrivers/hid/usbhid/usbmouse.c"中的ib_table填寫方式, 能夠看出, 不只構造使用了宏, 因爲USB鼠標是標準設備, 它的屬性值也有標準的宏來標識

230 static struct usb_device_id usb_mouse_id_table [] = {
231         { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
232                 USB_INTERFACE_PROTOCOL_MOUSE) },
233         { }     /* Terminating entry */
234 };

USB鼠標實例

內核"drivers/hid/usbhid/usbmouse.c"就是一個usb鼠標的驅動, 這裏我根據本身的理解寫了一個, 採用的是"usb中斷設備驅動+input子系統"框架

#define BUF_SIZE 8
MODULE_LICENSE("GPL");

//面向對象, 根據需求封裝類
struct xj_mouse {
    char name[128];
    struct usb_device *dev;
    struct urb *msg;
    struct input_dev *input;
    signed char *buf;
};

struct xj_mouse *mouse;
static int usb_mouse_open(struct input_dev *dev)
{
    struct xj_mouse *mouse = input_get_drvdata(dev);

    mouse->msg->dev = mouse->dev;
    if (usb_submit_urb(mouse->msg, GFP_KERNEL))
        return -EIO;
    return 0;
}

static void usb_mouse_close(struct input_dev *dev)
{
    struct xj_mouse *mouse = input_get_drvdata(dev);
    usb_kill_urb(mouse->msg);
}

static int init_input(struct usb_interface * intf)
{
    int err=0;
    struct usb_device *dev = mouse->dev;
    struct input_dev *input_dev = mouse->input;
    input_dev->name = mouse->name;
    usb_to_input_id(dev, &input_dev->id);
    input_dev->dev.parent = &intf->dev;

    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
    input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
    input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
    input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |BIT_MASK(BTN_EXTRA);
    input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);

    input_set_drvdata(input_dev, mouse);

    input_dev->open = usb_mouse_open;
    input_dev->close = usb_mouse_close;

    err = input_register_device(mouse->input);
    return 0;
}
static void completion(struct urb * msg)
{
    int status;
    signed char *buf = mouse->buf;
    struct input_dev *input = mouse->input;
    input_report_key(input, BTN_LEFT,   buf[0] & 0x01);
    input_report_key(input, BTN_RIGHT,  buf[0] & 0x02);
    input_report_key(input, BTN_MIDDLE, buf[0] & 0x04);
    input_report_key(input, BTN_SIDE,   buf[0] & 0x08);
    input_report_key(input, BTN_EXTRA,  buf[0] & 0x10);

    input_report_rel(input, REL_X,     buf[1]);
    input_report_rel(input, REL_Y,     buf[2]);
    input_report_rel(input, REL_WHEEL, buf[3]);

    input_sync(input);
    status = usb_submit_urb (msg, GFP_ATOMIC);
}
static int probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    int pipe;
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    //分配、初始化個性結構
    mouse = (struct xj_mouse *)kzalloc(sizeof(struct xj_mouse),GFP_KERNEL);
    mouse->dev=interface_to_usbdev(intf);
    mouse->msg=usb_alloc_urb(0,GFP_KERNEL);
    mouse->input=input_allocate_device();
    mouse->buf=(void *)kzalloc(BUF_SIZE,GFP_KERNEL);
    if (mouse->dev->manufacturer){
        strlcpy(mouse->name, mouse->dev->manufacturer, sizeof(mouse->name));
        mouse->input->name = mouse->name;
    }

    //初始化input設備
    init_input(intf);
    //獲取pipe
    interface=intf->cur_altsetting;
    endpoint=&interface->endpoint[0].desc;
    /* 使用dev和endpoint獲取端點地址 */
    pipe = usb_rcvintpipe(mouse->dev,endpoint->bEndpointAddress);
    //註冊usb驅動
    usb_fill_int_urb(mouse->msg,mouse->dev,pipe,mouse->buf,BUF_SIZE,completion,mouse->msg,endpoint->bInterval);
    return 0;

}

static void disconnect(struct usb_interface *intf)
{
    struct xj_mouse *tmp_mouse = usb_get_intfdata (intf);
    usb_set_intfdata(intf, NULL);
    if (tmp_mouse) {
        usb_kill_urb(tmp_mouse->msg);
        input_unregister_device(tmp_mouse->input);
        usb_free_urb(tmp_mouse->msg);
        kfree(tmp_mouse->buf);
        kfree(tmp_mouse);
    }
}

static struct usb_device_id id_table [] ={
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) },
    {},
};
struct usb_driver mouse_drv = {
    .name = "xj_mouse_drv",
    .probe = probe,
    .disconnect = disconnect,
    .id_table = id_table,
};

module_usb_driver(mouse_drv);
相關文章
相關標籤/搜索