Linux设备驱动probe过程(三)
前两周整理了Linux设备驱动probe的一些流程,文章链接如下:
本周把剩下的坑填上,分析下热插拔设备驱动是如何probe的。典型的热插拔设备有PCI和USB。鉴于USB流传度和通用性更广,本文选用USB HOST HID作为分析示例。
本文的章节设计如下:
- USB设备驱动概述。整理了USB驱动架构有哪些基本构成,HID位于架构哪个位置,具体负责什么功能,为接下来HID probe流程的展开做铺垫。
- USB HID设备驱动。讲述以USB鼠标为例,从设备插入到正常工作的过程。
- 多设备驱动probe。介绍DRM架构如何实现多设备依次按顺序完成probe的方法。
1. USB设备驱动概述
本章作为铺垫层,只进行一些和后文有关的概念科普,模型比较单一,不考虑存在集线器情况。
USB设备属于热插拔设备,probe发生在USB设备硬件连接后,当USB设备插入后,会根据usb_device_id
匹配,找到目标usb driver
后,再触发usb设备创建和probe。
USB驱动架构分为Host和Device侧,Host侧作为主机,可以接入多个Device设备(比如键盘、鼠标、U盘),通过发送心跳包维持连接状态。按照USB协议规定,所有USB事务和数据传输都由Host发起,Device没有主动通知Host的能力。
Device设备一般负责单一功能,不过也有些设备(比如USB网卡),可以模拟出多个设备出来(网卡和串口)。
USB驱动结构图如下:
从主机侧的角度来看,需要编写的USB驱动程序包括:主机控制器驱动(USB HCD)和设备驱动(USB Devices Driver)两类,而USB Core作为公共代码和桥梁一般不需要进行额外开发。USB HCD驱动程序控制插入其中的USB设备,而USB Devices驱动程序控制该设备如何作为从设备与主机通信。
1.1 USB HCD主机控制器驱动
从USB Host侧看,处于最底层的是USB HCD(Host Controller Driver)主机控制器驱动,负责控制物理连接usb端口的通信、数据传输、设备插入拔出、电源管理,按协议可分为OHCI/EHCI/UHCI/xHCI:
- UHCI:Intel主导,通用主机控制接口,USB1.0/1.1;
- OHCI:开放主机控制接口,USB1.1;
- EHCI:Intel主导,增强主机控制接口,USB2.0;
- xHCI:可扩展主机控制器接口,最新USB3.0接口,可以兼容USB 1.x/2.0/3.x;
usb_hcd
结构体描述USB主机控制器驱动,它包含USB控制器的"家务"信息、硬件资源、状态描述和用于操作主机控制器的hc_driver等。
1.1.1 dwc3示例(拓展阅读)
USB HCD集成在芯片SOC中,不属于热插拔范畴。USB HCD驱动probe位于kernel_init初始化阶段,dwc3公共代码(drivers/usb/dwc3/core.c)中并不涉及clock、reset pin和power的操作,很多方案会在dwc3外再封装一层driver,用于处理clock、reset pin、power的初始化,对应dwc3_xxx_probe
函数。在完成外部初始化之后,通过of_platform_populate
调用dwc3_probe
,完成phy,irq和工作模式的初始化。驱动调用参考下图:
在dwc3_probe
到dwc3_host_init
调用过程中涉及了1次进程切换,dwc3_host_init
函数完成xhci注册,匹配xhci_plat_probe
进行调用。
xhci_plat_probe
初始化usb总线和phy,调用usb_add_hcd
完成HCD结构体和USB bus/device的添加。
usb_register_bus
注册USB bus,链接到对应usb core中,以便于后续进行bus list扫描和发现设备。
usb_new_device
注册usb设备,并发起设备枚举。
hub_probe
函数初始化root hub,初始化urb和events。
1.2 USB Core驱动
usb core驱动位于drivers/usb/core/usb.c,处于协议中间层,初始化函数usb_init
由subsys_initcall
声明,位于kernel init 4
位置,位于在module_init
之后。USB Core负责USB驱动管理和协议处理的主要工作,包括:
- 通过定义一些数据结构体、宏和功能函数,向上为设备驱动提供编程接口,向下为USB HCD提供编程接口;
- 维护整个系统的USB设备信息;
- 完成设备热插拔控制、总线数据传输控制等。
HCD提供硬件抽象,它只对USB Core一个负责,USB Core将设备驱动的请求映射到相关的HCD,设备驱动不能直接访问HCD,USB Core是HCD与USB设备的唯一桥梁。
USB Core注册了USB总线,USB文件系统,USB Hub以及USB generic设备驱动。
/*
* Init
*/
static int __init usb_init(void)
{
...
retval = bus_register(&usb_bus_type); // 注册USB总线驱动usb_bus_type,创建devices和drivers
if (retval)
goto bus_register_failed;
retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb); // 创建usb bus通知回调usb_bus_nb
if (retval)
goto bus_notifier_failed;
retval = usb_major_init(); // 注册"usb" char设备节点
if (retval)
goto major_init_failed;
retval = usb_register(&usbfs_driver); // 注册USB接口驱动,用于新设备的发现和枚举
if (retval)
goto driver_register_failed;
retval = usb_devio_init(); // 注册"usb_device"设备节点,并注册用于设备发现的回调函数
if (retval)
goto usb_devio_init_failed;
retval = usb_hub_init(); // 注册hub_driver设备驱动和"ush_hub_wq"工作队列
if (retval)
goto hub_init_failed;
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); // 注册USB设备驱动
...
}
subsys_initcall(usb_init);
// module_init为3,优先于subsys_initcall
#define subsys_initcall(fn) __define_initcall(fn, 4)
这里有2个概念比较容易混淆:usb_driver和usb_device_dirver。
- usb_driver,很多介绍中是指的就是usb设备驱动,但更准确的应该叫做usb interface(接口)驱动,比如adb,u盘等。
- usb_device_driver,一般特指usb_generic_dirver,所有usb设备都要绑定到usb_generic_driver上,它的使命为:为usb设备选择一个合适的配置,让设备进入configured状态。
看代码的话,注释描述还蛮清晰的,有时看很多中译反而容易搞混。
1.2.1 URB(USB Request Block)
URB是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,类似网络设备的sk_buffer结构体。USB设备中的每个端点都处理一个URB队列,在队列被清空之前,一个URB的典型生命周期如下:
- 被一个USB设备驱动通过
usb_alloc_urb
创建。 - 初始化,通过
usb_fill_int_urb
被安排给一个特定USB设备的特定端点。 - 被USB设备驱动通过
usb_submit_urb
提交给USB core。 - 提交由USB Core指定到USB HCD。
- 被USB HCD处理,进行一次到USB设备的传送。
- 当URB完成,USB HCD通知USB设备驱动。
1.3 USB 设备驱动
USB设备驱动位于协议最上层,Linux系统实现了几类通用的USB设备驱动,划分为如下几个设备类:
- 音频设备类
- 通信设备类
- HID(人机接口)设备类
- 显示设备类
- 海量存储设备类
- 电源设备类
- 打印设备类
- 集线器设备类
一般的通用Linux设备(如U盘、USB鼠标、USB键盘等)都不需要再编写驱动、而需要编写的是特定厂商、特定芯片的驱动,往往也可参考内核中提供的驱动模板。在Linux内核中,使用usb_driver
结构体描述一个USB设备驱动(也称作USB接口驱动)。
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf, const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code, void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
const struct usb_device_id *id_table;
...
};
usb_driver的注册和注销函数为usb_register()
和usb_deregister()
。
probe()
和disconnect()
为探测和断开函数,分别在usb设备插入和拔出时候调用,用于初始化和释放软硬件资源。
id_table
成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id
数组,usb_device_id
结构体包含有USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息以及要匹配标志成员match_flags。可以借助USB_DEVICE
宏来生成:
USB_DEVICE(vendor, product)
USB_DEVICE_VER(vendor, product, lo, hi)
USB_DEVICE_INFO(class, subclass, protocol)
USB_INTERFACE_INFO(class, subclass, protocal)
当USB Core检测到某个设备的属性和某个驱动程序的usb_device_id
结构体所携带的信息一致时,这个驱动程序的probe()函数就被执行。usb_driver
本身只有找到USB设备、管理USB设备连接和断开的作用,类似公司的"打卡机",USB设备本身为员工,表现为tty,u盘等具体功能形态。
对于USB设备而言,它至少具备两重身份:“USB"和"自己”。"USB"是指它挂接在USB总线上,必须完成usb_driver的初始化和注册;"自己"是指本身可能可能是char、tty、网络设备等,会依赖其他driver框架。USB设备驱动自身读写操作具有特殊性,以URB来贯穿始终,一个URB的生命周期通常包括创建、初始化、提交和被USB Core及USB HCD传递、完成后通过回调函数被通知的过程。
2. USB HID设备驱动
USB HID(人机交互设备,Human Interface Devices)位于"drivers/hid/usbhid/hid-core.c"路径,常见的HID设备有鼠标、键盘、游戏手柄等,本次使用的实例为USB鼠标。USB HID初始化函数如下:
static struct usb_driver hid_driver = {
.name = "usbhid",
.probe = usbhid_probe,
.disconnect = usbhid_disconnect,
.pre_reset = hid_pre_reset,
.post_reset = hid_post_reset,
.id_table = hid_usb_ids,
.supports_autosuspend = 1,
};
static int __init hid_init(void)
{
int retval;
// quirks_param参数可通过设备节点进行配置,实现动态修改HID的USB vendor和product id
retval = hid_quirks_init(quirks_param, BUS_USB, MAX_USBHID_BOOT_QUIRKS);
if (retval)
goto usbhid_quirks_init_fail;
retval = usb_register(&hid_driver); // 注册USB设备驱动
if (retval)
goto usb_register_fail;
pr_info(KBUILD_MODNAME ": " DRIVER_DESC "\n");
...
}
module_init(hid_init);
2.1 USB HID设备驱动probe流程(示例:USB鼠标)
USB HID设备驱动完整probe过程非常复杂,这里按照第一章Host侧的模块划分,拆分为三个部分:
- USB鼠标插入连接到HCD root hub resume,对应USB HCD层处理;
- HCD resume到usb hid probe,对应USB Core层处理;
- usb hid probe到到input设备注册,对应USB Devices Driver处理。
2.1.1 USB HCD 调用关系
识别USB插入到USB HCD resume,这个阶段以硬件中断为起点,完成HCD数据和事件的解析后,将下半部root hub resume部分放到工作队列中进一步处理。如下图所示:
这个阶段最主要的结构体为struct usb hcd
,在其上完成了中断、root_hub、工作队列的注册。本次示例选用的是usb3.0的IP,所以协议部分关联到了xhci_plat
。
2.1.2 USB Core 调用关系
流程紧接上节,HCD resume到创建新的USB设备,这个阶段的入口为hcd wakeup_work
任务得到调度,对应⑥到⑨,如下图所示:
这个阶段涉及的结构体就比较多了,联动关系可以参考上图。补充下,第⑦步usb_autoresume_device
的过程中,存在很多层数据结构的遍历,过程大致为:先通过usb_hcd
逐级找到usb_interface
,再通过to_usb_driver
宏(其实就是常见的container_of
处理)找到struct usb_driver hub_driver
,最后调用到hub_resume
函数。
这个阶段也涉及了1次工作队列的调度,主要是实现usb设备的创建,创建新的USB设备到触发usb hid probe过程如下:
通过usb_generic_driver
的处理,最终来到usb hid设备的probe,对应usbhid_probe
。需要注意在usb_probe_interface
中,进行了usb driver的match,根据usb_device_id
选择不同的usb_driver
驱动。
2.1.3 USB HID 调用关系
从usb hid probe到input设备注册,对应14到18,如下:
usb hid probe首先会创建hid设备驱动(位于drivers/hid/目录),之后触发hid设备驱动probe。接下来调用hid_driver
完成hid_input
的创建和input子系统的注册。input子系统部分就不在此文赘述了,简单说就是实现了key值上报管理的驱动,支持鼠标、键盘和手柄等。
2.1.4 小结
至此USB HID设备probe流程就结束了,本章总结了从USB插入到鼠标完成input子系统注册的过程,可以看到这个流程涉及的业务模块非常多。
按模块划分的话,可以分为:
- USB hcd,处理中断和输出传输;
- USB core,维护usb设备信息,提供接口;
- USB driver,主要为root hub和usb hid driver;
- HID device&driver,HID封装,实现各种形态(比如蓝牙)HID设备;
- input device,input事件上报;
调用关系可以参考下图:
完整调用栈供参考,感兴趣可以自己对着捋一下:
start_kernel -> arch_call_rest_init -> rest_init -> cpu_startup_entry -> do_idle -> cpuidle_enter -> cpuidle_enter_state -> el1_irq -> gic_handle_irq -> __handle_domain_irq -> handle_fasteoi_irq-> handle_irq_event -> __handle_irq_event_percpu -> usb_hcd_irq -> xhci_irq -> xhci_handle_event -> usb_hcd_resume_root_hub -> queue_work(pm_wq, &hcd->wakeup_work)
(进程调度)
ret_from_fork -> kthread -> worker_thread -> process_one_work -> hcd_resume_work -> usb_remote_wakeup -> usb_autoresume_device -> pm_runtime_get_sync -> __pm_runtime_resume -> rpm_resume -> rpm_callback -> __rpm_callback -> cb(dev) -> usb_runtime_resume -> usb_resume_both -> usb_resume_interface -> driver->resume(intf) -> hub_resume -> hub_activate -> kick_hub_wq -> queue_work(hub_wq, &hub->events)
(进程调度)
ret_from_fork -> kthread -> worker_thread -> process_one_work -> hub_event -> port_event -> hub_port_connect_change -> hub_port_connect -> usb_new_device -> device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv -> __device_attach_driver -> driver_probe_device -> really_probe -> usb_probe_device -> usb_generic_driver_probe -> usb_set_configuration -> device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv -> __device_attach_driver -> driver_probe_device -> really_probe -> usb_probe_interface -> usbhid_probe -> hid_add_device -> device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv -> __device_attach_driver -> driver_probe_device -> really_probe -> hid_device_probe -> hdrv->probe(hdev, id) -> hid_generic_probe -> hid_hw_start -> hid_connect -> hidinput_connect -> input_register_device
2.2 USB鼠标事件上报(拓展阅读)
上个章节讲了usb设备probe的过程,再拓展下usb设备的业务部分。如下是鼠标移动或点击等事件的上报路径:
鼠标事件上报的函数调用栈为:
start_kernel -> arch_call_rest_init -> rest_init -> cpu_startup_entry -> do_idle -> cpuidle_enter -> cpuidle_enter_state -> el1_irq -> gic_handle_irq -> __handle_domain_irq -> irq_exit -> efi_header_end -> tasklet_hi_action -> tasklet_action_common -> usb_giveback_urb_bh -> __usb_hcd_giveback_urb -> hid_irq_in -> hid_input_report -> hid_report_raw_event -> hidinput_report_event -> input_sync
3. 多设备驱动probe(Component)
写到这里发现篇幅有点长,虽然已经完成一部分了,不过还是另起一篇文章吧。立个flag,下一篇就是驱动probe系列的最后一篇文章了。
(TBD)