(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
 

你可能感兴趣的:(cdev总结2018)