Linux usb子系统(一) _写一个usb鼠标驱动

USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配。 USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动,通常,对于USB这种标准化的设备,内核已经将主机控制器的驱动编写好了,设备上的Gadget驱动通常只运行固件程序而不是基于Linux, 所以驱动工程师的主要工作就是编写主机端的USB设备驱动。

USB子系统框架

下图表示了Linux中USB子系统的框架结构,和i2c一样,USB子系统也可分为三层:**设备驱动层--USB核心--控制器驱动层*

[blockquote]

Linux usb子系统(一) _写一个usb鼠标驱动

[/blockquote]

作为热插拔总线, 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通信的最小单位,我们读写一个设备就是针对一个endpoint

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"有定义, 我就不贴代码了

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

[blockquote]

struct usb_device_descriptor --263-->设备类别 --264-->设备子类 --265-->通信协议 --267-->销售商 --268-->产品ID --272-->序列号

[/blockquote]

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

[blockquote]

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-->子设备的最大数量

[/blockquote]

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

[blockquote]

struct usb_host_config --276-->软件config对象包含的硬件config对象 --278-->config的名称 --282-->这个config上关联的Interface Association Descriptor --283-->这个config上关联的下一级的软件interface数组,

[/blockquote]

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

[blockquote]

struct usb_interface --163-->这个interface包含的所有的setting --164-->这个interface当前正在使用的setting --165-->如果这个interface与一个使用了主设备号的驱动绑定了, 这个域就是interface的次设备号; 反之则没用. 驱动应该在probe中设置这个参数

[/blockquote]

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

[blockquote]

struct usb_host_interface --78-->这个interface对应的硬件interface对象 --86-->拥有的描述软件endpoint信息的usb_host_endpoint数组 --88-->interface名称

[/blockquote]

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

[blockquote]

struct usb_host_endpoint --65-->这个usb_host_endpoint对应的硬件endpoint信息 --67-->读写这个endpoint的usb链表, 由usb核心层(drivers/usb/core/file.c)维护 --73-->这个endpoint是否被使能了

[/blockquote]

每一个硬件信息对象都包含在一个软件信息对象中, 而软件信息对象是层层包含的, 所以虽然驱动是基于interface描述的, 但是我们可以使用"list_entry()"很容易的向上找到config和device描述, 使用其中的域很容易的找到endpoint描述, 这9个描述设备的结构关系如下图所示:

[blockquote]

Linux usb子系统(一) _写一个usb鼠标驱动

[/blockquote]

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

[blockquote]

struct usb_driver --1049-->usb设备的名字 --1051-->探测函数, 当usb_driver的id_table和usb设备信息匹配的时候会执行, 主要的工作是申请资源, 初始化, 提供接口 --1054-->当驱动模块被卸载时或设备被拔出时会被执行 --1066-->功能依然是匹配一样, 只是usb的设备信息由4个维度描述, 所以id_table可以填充的内容也多种多样

[/blockquote]

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

相关推荐