Linux设备驱动probe过程(三)


前两周整理了Linux设备驱动probe的一些流程,文章链接如下:

  1. Linux设备驱动probe过程(一)
  2. Linux设备驱动probe过程(二)
  3. Linux设备驱动probe过程(四)

本周把剩下的坑填上,分析下热插拔设备驱动是如何probe的。典型的热插拔设备有PCI和USB。鉴于USB流传度和通用性更广,本文选用USB HOST HID作为分析示例。

本文的章节设计如下:

  1. USB设备驱动概述。整理了USB驱动架构有哪些基本构成,HID位于架构哪个位置,具体负责什么功能,为接下来HID probe流程的展开做铺垫。
  2. USB HID设备驱动。讲述以USB鼠标为例,从设备插入到正常工作的过程。
  3. 多设备驱动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:

  1. UHCI:Intel主导,通用主机控制接口,USB1.0/1.1;
  2. OHCI:开放主机控制接口,USB1.1;
  3. EHCI:Intel主导,增强主机控制接口,USB2.0;
  4. 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_probedwc3_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_initsubsys_initcall声明,位于kernel init 4位置,位于在module_init之后。USB Core负责USB驱动管理和协议处理的主要工作,包括:

  1. 通过定义一些数据结构体、宏和功能函数,向上为设备驱动提供编程接口,向下为USB HCD提供编程接口;
  2. 维护整个系统的USB设备信息;
  3. 完成设备热插拔控制、总线数据传输控制等。

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的典型生命周期如下:

  1. 被一个USB设备驱动通过usb_alloc_urb创建。
  2. 初始化,通过usb_fill_int_urb被安排给一个特定USB设备的特定端点。
  3. 被USB设备驱动通过usb_submit_urb提交给USB core。
  4. 提交由USB Core指定到USB HCD。
  5. 被USB HCD处理,进行一次到USB设备的传送。
  6. 当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侧的模块划分,拆分为三个部分:

  1. USB鼠标插入连接到HCD root hub resume,对应USB HCD层处理;
  2. HCD resume到usb hid probe,对应USB Core层处理;
  3. 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子系统注册的过程,可以看到这个流程涉及的业务模块非常多。

按模块划分的话,可以分为:

  1. USB hcd,处理中断和输出传输;
  2. USB core,维护usb设备信息,提供接口;
  3. USB driver,主要为root hub和usb hid driver;
  4. HID device&driver,HID封装,实现各种形态(比如蓝牙)HID设备;
  5. 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)

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值