NXP i.MX8系列平台开发讲解 - 3.17 Linux 之USB子系统(二)

专栏文章目录传送门返回专栏目录

Hi, 我是你们的老朋友,主要专注于嵌入式软件开发,有兴趣不要忘记点击关注【码思途远】


目录

1. HOST端的USB设备识别与管理

2. USB总线驱动

2.1 USB Core

2.2 USB HCD

3. USB设备驱动

4. 总结


根据上一章节讲解,对USB相关的基础知识有初步认识,对子系统的框架也有一些理解,本章节将对USB子系统内部的实现相关进行一步的说明讲解;

根据上一章节对USB子系统的架构分为HOST端与DEVICE端;

1. HOST端的USB设备识别与管理

在HSOT端USB设备检测过程如图

图1.1 USB设备检测过程

  • 设备检测: 当你插入USB设备时,在控制USB控制器上有电压的变化,USB检测到后将会发起 一个中断信号,中断信号送入到处理器上的USB控制器中断线上,告诉Linux内核有设备接入。

  • USB设备驱动加载: 一旦内核检测到新设备,将会调用USB子系统中的USB Core模块,它会尝试匹配适当的USB设备驱动程序。内核会根据设备的Vendor ID和Product ID等信息,查找并加载与设备匹配的驱动模块。这些驱动模块通常在Linux内核中提前定义。如果没有找到匹配的设备驱动程序,那么USB Core将会匹配一个通用的驱动程序。

  • 向USB子系统注册:根据上一步骤,如果找到正确的驱动程序,它会向USB子系统注册并告诉它自己可以处理哪些设备,比如一个输入设备,存储设备等等。

  • 设备节点创建: 内核会为新连接的设备创建一个设备节点,这个节点通常位于/dev/目录下。这个节点允许用户空间应用程序访问设备。设备节点的名称通常是由内核根据设备的厂商ID、产品ID和序列号等信息动态生成的。

  • 设备初始化: 一旦驱动程序加载并设备节点创建完毕,内核会初始化设备并将其准备好以供使用。这包括配置设备、分配资源、启用中断等。

  • 用户空间设备操作: 一旦设备被初始化并且设备节点已创建,用户空间应用程序可以通过访问相应的设备节点来与设备进行交互。这通常涉及到读取和写入设备文件以执行特定的操作。

在USB设备的识别过程中,Linux系统就可以自动识别并加载对应的设备驱动实现功能,所以在实际使用过程中发现,只需要插入某个USB设备,不需要进行手动安装驱动或者执行其他任何操作就可以直接打开设备,除非是一些比较特殊的设备需要手动去加载驱动设备。

2. USB总线驱动

根据前一章节内容,这里想提USB总线驱动,也就是作为HOST端相关的驱动代码功能实现,对于HOST端,主要涉及USB Core/USB HCD

2.1 USB Core

USB Core主要作为处理USB核心,它提供了USB设备管理和通信的基本功能。

关于USB Core代码文件:

ls ./drivers/usb/core/ -al
total 804
drwxrwxr-x  2 pub pub   4096 9月   5 15:32 .
drwxrwxr-x 27 pub pub   4096 9月   5 15:32 ..
-rw-rw-r--  1 pub pub   3734 8月  22 11:00 buffer.c
-rw-rw-r--  1 pub pub  31889 8月  22 11:00 config.c
-rw-rw-r--  1 pub pub  16532 8月  22 11:00 devices.c
-rw-rw-r--  1 pub pub  70122 8月  22 11:00 devio.c
-rw-rw-r--  1 pub pub  59827 8月  22 11:00 driver.c
-rw-rw-r--  1 pub pub   4391 8月  22 11:00 endpoint.c
-rw-rw-r--  1 pub pub   6349 8月  22 11:00 file.c
-rw-rw-r--  1 pub pub   9125 8月  22 11:00 generic.c
-rw-rw-r--  1 pub pub  91158 8月  22 11:00 hcd.c
-rw-rw-r--  1 pub pub  16935 8月  22 11:00 hcd-pci.c
-rw-rw-r--  1 pub pub 180602 8月  22 11:00 hub.c
-rw-rw-r--  1 pub pub   5018 8月  22 11:00 hub.h
-rw-rw-r--  1 pub pub   4368 8月  22 11:00 Kconfig
-rw-rw-r--  1 pub pub   9035 8月  22 11:00 ledtrig-usbport.c
-rw-rw-r--  1 pub pub    486 8月  22 11:00 Makefile
-rw-rw-r--  1 pub pub  73949 8月  22 11:00 message.c
-rw-rw-r--  1 pub pub   1768 8月  22 11:00 notify.c
-rw-rw-r--  1 pub pub   2741 8月  22 11:00 of.c
-rw-rw-r--  1 pub pub   4228 8月  22 11:00 otg_productlist.h
-rw-rw-r--  1 pub pub   5260 8月  22 11:00 phy.c
-rw-rw-r--  1 pub pub    979 8月  22 11:00 phy.h
-rw-rw-r--  1 pub pub  16004 8月  22 11:00 port.c
-rw-rw-r--  1 pub pub  19859 8月  22 11:00 quirks.c
-rw-rw-r--  1 pub pub  32247 8月  22 11:00 sysfs.c
-rw-rw-r--  1 pub pub  34094 8月  22 11:00 urb.c
-rw-rw-r--  1 pub pub   7471 8月  22 11:00 usb-acpi.c
-rw-rw-r--  1 pub pub  30224 8月  22 11:00 usb.c
-rw-rw-r--  1 pub pub   7606 8月  22 11:00 usb.h

入口分析:

drivers/usb/core/usb.c 这里将列出一些重点步骤:

static int __init usb_init(void)
     int bus_register(struct bus_type *bus) //注册USB总线
     int usb_hub_init(void) //USB hub 的初始化
         int usb_register(&hub_driver) //向USB总线添加一个HUB驱动,这里的hub_driver包含了probe disconnect suspend .resume .reset_resume .pre_reset等操作函数
         hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);//创建一个名为usb_hub_wq的工作队列,用于处理USB集线器事件.它负责异步执行与 USB 集线器端口事件相关的任务,以避免阻塞主线程。比如设备断开连接,USB集线器状态等等
     usb_register_device_driver(&usb_generic_driver, THIS_MODULE); // 注册一个通用usb设备程序
进一步分析usb_register_device_driver
struct usb_device_driver usb_generic_driver = {
        .name = "usb",
        .match = usb_generic_driver_match,
        .probe = usb_generic_driver_probe,
        .disconnect = usb_generic_driver_disconnect,
#ifdef  CONFIG_PM
        .suspend = usb_generic_driver_suspend,
        .resume = usb_generic_driver_resume,
#endif
        .supports_autosuspend = 1,
};
​
​
//...省略代码
​
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
        if (!retval)
                goto out;
接口usb_register_device_driver 注册了一个通用的USB设备驱动
int usb_register_device_driver(struct usb_device_driver *new_udriver,
                struct module *owner)
{
        int retval = 0;
​
        if (usb_disabled())
                return -ENODEV;
​
        new_udriver->drvwrap.for_devices = 1; //设置为 1,表示这是一个用于设备的驱动程序
        new_udriver->drvwrap.driver.name = new_udriver->name; //以下填充驱动的基本信息
        new_udriver->drvwrap.driver.bus = &usb_bus_type;
        new_udriver->drvwrap.driver.probe = usb_probe_device; //匹配成功将执行的probe
        new_udriver->drvwrap.driver.remove = usb_unbind_device;
        new_udriver->drvwrap.driver.owner = owner;
        new_udriver->drvwrap.driver.dev_groups = new_udriver->dev_groups;
​
        retval = driver_register(&new_udriver->drvwrap.driver);
​
        if (!retval) {
                pr_info("%s: registered new device driver %s\n",
                        usbcore_name, new_udriver->name);
                /*
                 * Check whether any device could be better served with
                 * this new driver
                 */
                bus_for_each_dev(&usb_bus_type, NULL, new_udriver,
                                 __usb_bus_reprobe_drivers);
        } else {
                pr_err("%s: error %d registering device driver %s\n",
                        usbcore_name, retval, new_udriver->name);
        }
​
        return retval;
}

注意区分

  • usb_register :注册一个USB接口驱动,USB 接口驱动程序通常与特定的 USB 设备接口(例如 USB HID、USB Mass Storage、USB Ethernet 等)相关联,而不是与特定品牌或型号的设备相关联。它们用于处理一类具有相似接口特性的 USB 设备。

  • usb_register_device_driver:注册一个通用的设备驱动,通用的设备驱动程序通常用于处理没有特定驱动程序的 USB 设备,或者用于为一类 USB 设备注册通用的驱动程序。这些驱动程序是为了确保不特定的设备可以正常工作。

整体来说做的以下工作:

  • 注册 USB 总线

  • 注册了三个驱动,USB接口驱动,HUB驱动,USB设备驱动。

2.2 USB HCD

USB HCD 又称为USB 主机控制器驱动,用来处理主机控制器的初始化以及数据的传输,并监测外部设备插入、拔出,完成设备枚举。

其实在HCD中出现了很多种不同类型,在图中有展示到OHCI和UHCI,EHCI,和xHCI,不同USB控制器类型OHCI,UHCI,EHCI,xHCI的区别和联系,主要是在于功能版本速率不一样。

代码位置:

./drivers/usb/dwc3/core.c

static struct platform_driver dwc3_driver = {
        .probe          = dwc3_probe,
        .remove         = dwc3_remove,
        .driver         = {
                .name   = "dwc3",
                .of_match_table = of_match_ptr(of_dwc3_match),
                .acpi_match_table = ACPI_PTR(dwc3_acpi_match),
                .pm     = &dwc3_dev_pm_ops,
        },
};

首先是platform驱动的加载, 会通过of_dwc3_match配置dts,匹配成功后,会进入dwc3_probe函数。主要进行一些资源的分配,硬件的初始化等,probe内容较多,截取部分:

static int dwc3_probe(struct platform_device *pdev)
{
        // 省略代码
        dwc3_get_properties(dwc); //从dts获取 DWC3 控制器的属性,这些属性可能包括控制器的一些特性和配置信息,比如dr_mode 模式
        dwc3_cache_hwparams(dwc); //读取硬件参数
        // 省略代码
​
        ret = dwc3_get_dr_mode(dwc); //获取 DWC3 控制器的数据传输模式(例如,是主机模式还是设备模式)
        if (ret)
                goto err3;
​
        ret = dwc3_alloc_scratch_buffers(dwc);
        if (ret)
                goto err3;
​
        ret = dwc3_core_init(dwc); //初始化 DWC3 控制器的核心功能,一些硬件USB phy
        if (ret) {
                dev_err_probe(dev, ret, "failed to initialize core\n");
                goto err4;
        }
​
        dwc3_check_params(dwc);
        dwc3_debugfs_init(dwc);
​
        ret = dwc3_core_init_mode(dwc);//初始化 USB 控制器的工作模式,这可能涉及到配置控制器为主机模式或设备模式,具体取决于系统的需求。
        if (ret)
                goto err5;
​
        pm_runtime_put(dev);
​
        return 0;
​
​
        // 省略代码
}
 

查看dwc3_core_init_mode函数重要内容

static int dwc3_core_init_mode(struct dwc3 *dwc)
{
        struct dwc3_platform_data *dwc3_pdata;
        struct device *dev = dwc->dev;
        int ret;
​
        switch (dwc->dr_mode) {
        case USB_DR_MODE_PERIPHERAL:
                dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
​
                if (dwc->usb2_phy)
                        otg_set_vbus(dwc->usb2_phy->otg, false);
                phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE);
                phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_DEVICE);
​
                ret = dwc3_gadget_init(dwc);
                if (ret)
                        return dev_err_probe(dev, ret, "failed to initialize gadget\n");
                break;
        case USB_DR_MODE_HOST:
                dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
​
                if (dwc->usb2_phy)
                        otg_set_vbus(dwc->usb2_phy->otg, true);
                phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST);
                phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_HOST);
​
                ret = dwc3_host_init(dwc);
                if (ret)
                        return dev_err_probe(dev, ret, "failed to initialize host\n");
                break;
        case USB_DR_MODE_OTG:
                INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
                ret = dwc3_drd_init(dwc);
                if (ret)
                        return dev_err_probe(dev, ret, "failed to initialize dual-role\n");
                break;
        default:
                dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
                return -EINVAL;
        }
​
        dwc3_pdata = (struct dwc3_platform_data *)dev_get_platdata(dwc->dev);
        if (dwc3_pdata && dwc3_pdata->set_role_post)
                dwc3_pdata->set_role_post(dwc, dwc->current_dr_role);
​
        return 0;
}

其实dwc3_core_init_mode根据不同模式进行初始化,OTG/HOST/Periphjeral,OTG模式应该使用比较多,使用比较灵活。

3. USB设备驱动

USB设备有各种不一样的功能,这里以USB Mouse作为例子分析

vim ./drivers/hid/usbhid/usbmouse.c

static const struct usb_device_id usb_mouse_id_table[] = {
        { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
                USB_INTERFACE_PROTOCOL_MOUSE) },
        { }     /* Terminating entry */
};
​
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
​
static struct usb_driver usb_mouse_driver = {
        .name           = "usbmouse",
        .probe          = usb_mouse_probe,
        .disconnect     = usb_mouse_disconnect,
        .id_table       = usb_mouse_id_table,
};

构建usb_driver驱动接口

USB Mouse驱动程序需要做的事情是在USB系统里面注册,提供相关的信息,包括驱动支持哪些设备,当被支持的设备从总线插入或者拔出,需要做哪些操作,所有的信息都是通过usb_driver中。

usbmouse作为客户端驱动的字符串名称,这个是用于避免驱动程序的重复安装和卸载;

usb_mose_prob 提供给USB内核函数,用于判断驱动程序是否能够对于设备的某个接口进行驱动;

usb_mouse_disconnect 当设备移除或者卸载时候,将运行该函数;

usb_mouse_id_table 包含该驱动程序可以支持所有不同类型的USB设备,如果不在该列表中,则驱动程序的安策回调将不会被调用。

usb_device_id 是一个在Linux内核中用于USB设备驱动程序匹配的结构体。它允许开发人员定义一系列USB设备描述符,以便内核可以将这些描述符与实际连接的USB设备进行匹配,从而加载适当的设备驱动程序。

具体匹配哪些标志可以查看结构体usb_device_id大概就是USB设备制造商ID,产品ID,设备类别等等

综上所述mouse驱动来看:

USB设备驱动程序通常可以分为两部分:

USB总线驱动程序:这部分驱动程序负责与USB总线通信,识别连接到USB总线上的设备,并将其传递给适当的设备驱动程序。USB总线驱动程序使用usb_driver结构体中的函数来管理USB设备的连接和断开,以及执行与USB总线通信相关的任务。这些函数包括probedisconnect等,它们负责管理USB总线上的设备。

设备类型特定的驱动程序:这部分驱动程序是针对特定类型的USB设备的,例如字符设备、tty设备、块设备、输入设备等。它们是USB设备的真正主体驱动程序,负责处理USB设备的数据传输、设备状态等特定任务。这些驱动程序通常不直接与USB总线通信,而是与特定设备进行通信,并执行设备类型相关的操作。

综合起来,USB设备驱动程序通常是一个组合,包括了USB总线驱动程序和设备类型特定的驱动程序。USB总线驱动程序用于管理USB总线上的设备连接和断开,以及与USB总线的通信,而设备类型特定的驱动程序用于处理实际的设备功能。这种分层结构允许系统支持多种类型的USB设备,并根据需要加载适当的设备类型特定的驱动程序。

4. 总结

根据本章节对于USB子系统的实现原理做了一些介绍,其实USB在驱动里面算是比较复杂驱动,若需要深究还有很多知识需要罗列,这里把重点提出,了解HOST,DEVICE端的重要部分。对于在开发过程中,若使用USB驱动修改不会很大,只会做一些很小的改动,做一些适配。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值