USB-Storage驱动 源码阅读笔记——从USB子系统开始
最近在研究U盘的驱动,遇到很多难以理解的问题,虽然之前也参考过一些很不错的书籍如:《USB那些事》,但最终还是觉得下载一份最新的源码,慢慢啃源码会有更具象的了解,在这里做一些笔记。本文基于linux-5.16.9源码描述。
我就USB-Storage的调用栈花了个思维导图,放在这了USB-Storage驱动调用思维导图(免费下载)
一、USB子系统
要想了解USB-Storage驱动,我们就要从USB子系统开始说起。
1.USB Core
USB core 是USB子系统的核心部分,在这里完成了USB子系统的初始化、注册USB总线、注册USB根集线器、初始化调试文件系统(usbfs)、注册通用驱动(generic_driver)以及定义了许多关键的数据结构。
1.1 从init开始
写过驱动的朋友们一定很熟悉,就像C++ 中的main函数一样,驱动是从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();
//用于调试USB的虚拟文件系统初始化
usb_debugfs_init();
//
usb_acpi_register();
//注册USB总线
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;
//注册字符设备,主设备号180
retval = usb_major_init();
if (retval)
goto major_init_failed;
//注册usbfs驱动
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;
//USb通用设备驱动的注册
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out;
usb_hub_cleanup();
//下面是一些异常处理 略
...
}
是不是很简洁,一个init下逐项排列着另外一些init,有的看名字就能大概知道它的作用是什么,好的,接下来一步一步看。接下来我们只介绍一些关键的内容,有的会和电源管理有关,而有的是为特殊设备做的适配,有的是为调试建立的文件系统,我就暂且舍“末” 逐 “本”了。
1.2 注册USB总线
一切总线都需要被注册,总线就是设备和驱动的桥梁,桥梁的两端被连接起来,设备才能正常地工作实现各种功能。
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
.need_parent_lock = true,
};
.name,就是这条总线的身份证——usb,用于和其他总线区分。
.match, 只要总线上出现新的设备,match函数就会尝试为其匹配合适的驱动,每一个设备都要感谢match函数
.uevent,用于发送总线相关的总线事件
关于UEvent,在Android中,负责U盘挂载的native进程 vold 就会利用netlinkmanager监听Subsystem为“block”的Uevent,进而挂载U盘,因此UEvent可以用来Debug。(这里立个Flag,要写一篇从UEvent角度看USB子系统的笔记)
(1)match函数
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
/* devices and interfaces are handled separately */
if (is_usb_device(dev)) {
struct usb_device *udev;
struct usb_device_driver *udrv;
/* interface drivers never match devices */
if (!is_usb_device_driver(drv))
return 0;
udev = to_usb_device(dev);
udrv = to_usb_device_driver(drv);
/* If the device driver under consideration does not have a
* id_table or a match function, then let the driver's probe
* function decide.
*/
if (!udrv->id_table && !udrv->match)
return 1;
return usb_driver_applicable(udev, udrv);
} else if (is_usb_interface(dev)) {
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
/* device drivers never match interfaces */
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
}
return 0;
}
可以看到这里有两条路可以走,一条路是给设备走的,另一条路是给接口走的。
在USB设备的逻辑组织中,包含设备、配置、接口和端点4个层次。
每个USB设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一个),配置由多个接口组成。
在USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口。每个配置中可以有多个接口,而设备接口是端点的汇集(collection)。例如USB扬声器可以包含一个音频接口以及对旋钮和按钮的接口。一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。
端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个端点都有惟一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB端点只能在一个方向承载数据,或者从主机到设备(称为输出端点),或者从设备到主机(称为输入端点),因此端点可看作一个单向的管道。端点0通常为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电端点0就可以被访问。端点1、2等一般用作数据端点,存放主机与设备间往来的数据。
这里match函数先按下不讲,待到有设备或者接口出现的时候我们再展开描述。
1.3 初始化根集线器
关于根集线器,其本身是一个物理硬件集成在USB主机控制器上一般位于我们的主板上。所有的USB设备都要连接在根集线器上。(但并不意味着,只有一个根集线器)因此,USB设备的拓扑结构是一个树状结构。
以我是用的电脑为例,下面列出了我的USB设备:
lil_crystal@lil-crystal-pc:~$ lsusb
Bus 002 Device 002: ID 05e3:0626 Genesys Logic, Inc. USB3.1 Hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 413c:301a Dell Computer Corp. Dell MS116 USB Optical Mouse
Bus 001 Device 002: ID 413c:2113 Dell Computer Corp. Dell KB216 Wired Keyboard
Bus 001 Device 008: ID 0781:5567 SanDisk Corp. Cruzer Blade
Bus 001 Device 004: ID 05e3:0610 Genesys Logic, Inc. 4-port hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
可以看到我就有两个根集线器,一个是USB2.0协议,另一个是3.0协议。一共有这么几个USB设备:一个鼠标、一个键盘、一个U盘以及两个Hub(一个是我自己的Hub 4-port hub那个,另一个是电脑前面板的几个接口,其本质也还是一个hub)
(1)注册集线器驱动 usb_register(&hub_driver)
hub_driver:
static struct usb_driver hub_driver = {
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = 1,
};
注册完驱动后,probe函数就会执行。但是这里就会有一个疑问:probe函数的调用是设备和驱动缺一不可。驱动已经注册完成了,但是设备呢?
按照我的理解,所有的USB设备都连接在USB主机控制器上的根集线器上,而USB主机控制器是连接在SCSI总线上,他应该是一个SCSI设备,是在SCSI子系统中完成注册的。
(2)hub_probe
hub_probe中设置了电源管理的相关信息,对根集线器和普通设备有区别处理,但这些并不是我主要关心的。
主要的内容就是为这个hub分配内存空间,初始化相关信息,在工作队列中添加hub事件的监听任务并配置hub。
在较早版本的kernel中,是利用内核线程去监控hub_event,而在5.16.9中是利用工作队列去实现的。
核心代码为:INIT_WORK(&hub->events, hub_event);
每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq, 在该函数中置位event_bits,运行工作队列。进入hub_event函数,该函数用来处理端口变化的事件。然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口
hub_event在之后有USB设备插入在展开描述。
(3)hub_configure
hub_configure是在probe里边被调用的,包括不同的hub的判断和配置,接口数量。
首先就是获取这个hub的描述符
ret = get_hub_descriptor(hdev, hub->descriptor);
struct usb_hub_descriptor {
__u8 bDescLength;
__u8 bDescriptorType;
__u8 bNbrPorts;
__le16 wHubCharacteristics;
__u8 bPwrOn2PwrGood;
__u8 bHubContrCurrent;
/* 2.0 and 3.0 hubs differ here */
union {for (i = 0; i < maxchild; i++) {
ret = usb_hub_create_port_device(hub, i + 1);
if (ret < 0) {
dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + 1);
break;
}
}
struct {
/* add 1 bit for hub status change; round to bytes */
__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
} __attribute__ ((packed)) hs;
struct {
__u8 bHubHdrDecLat;
__le16 wHubDelay;
__le16 DeviceRemovable;
} __attribute__ ((packed)) ss;
} u;
} __attribute__ ((packed));
接着读取hub的端口数量,linux对于一个hub,能够支持的最大端口数量是31个。读取供电数据,roothub和普通hub的供电是不同的。由于USB是由主板进行供电的,因此无法支撑功耗大的设备,故对每个端口有最大电流限制。按照USB协议,每个端口是5V1A供电。但是代码里看到的hub的是900mA,这里有些疑问,可以评论区讨论。
if (hdev == hdev->bus->root_hub) {
if (hcd->power_budget > 0)
hdev->bus_mA = hcd->power_budget;
else
hdev->bus_mA = full_load * maxchild;
if (hdev->bus_mA >= full_load)
hub->mA_per_port = full_load;
else {
hub->mA_per_port = hdev->bus_mA;
hub->limited_power = 1;
}
if (hub_is_superspeed(hdev)) {
unit_load = 150;
full_load = 900;
} else {
unit_load = 100;
full_load = 500;
}
之后填充urb并调用hub_irq发送控制信息,遍历hub上的各个端口,并为其创建设备(如果有设备),这里边的设备就是说在连接上hub的时候,hub上本来就有设备,比如开机前U盘就插在电脑上。而不是我们插入设备触发hub_event进而添加设备的。流程和插入设备一致。
for (i = 0; i < maxchild; i++) {
ret = usb_hub_create_port_device(hub, i + 1);//Here add device
if (ret < 0) {
dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + 1);
break;
}
}
接下来是hub_activate(hub, HUB_INIT)
这个函数没有深入看,大概就是对hub上电,消除抖动,使其状态稳定。之后如果有需要会再进行补充
1.4 注册通用USB驱动
//USb通用设备驱动的注册
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
一句话就完成了这个通用驱动的注册,这主要是因为,为了减轻USB接口驱动开发者的开发难度,USB Core首先获取一些信息并做一些设置,根据这些信息判断USB设备该匹配哪一种具体的接口设备。
下面是这个通用设备驱动的信息。
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,
};
usb_generic_driver_match:做一个判断,看看这个设备是不是一个USB通用设备。
usb_generic_driver_probe所做的工作:从设备可能的众多配置中选择一个合适的,然后去配置设备,从而让设备进入期待已久的Configured状态。(USB设备有多种状态,这些状态在USB协议中有明确规定)probe将在插入USB设备后展开描述。
小结
至此,USB子系统完成了初始化,但其实呢,我还什么都没说,因为总线只是注册,没有设备进入,但他已经准备好迎接设备的到来。也留了很多坑,match函数没有讲,hub_event加入了工作队列,但如何触发?USB设备的probe函数干了什么,状态怎么就变成configured了。这些都会在下一节描述。
下一节,就从插入一个U盘讲起,未完待续,,,,,,,
2.USB-Storage
USB-Storage 是linux 实现的USB存储类模块。其代码位于“/drivers/usb/storage”下。上一节,浏览了USB子系统初始化的过程,本节就从一个U盘插入开始,看看驱动是如何运作的。
2.1 hub_event
在上一节中注册根集线器时,在工作队列中加入了hub_event任务。当集线器的端口出现电位变化的时候,hub_event就会被唤醒。
hub_event 完整代码
static void hub_event(struct work_struct *work)
{
struct usb_device *hdev;
struct usb_interface *intf;
struct usb_hub *hub;
struct device *hub_dev;
u16 hubstatus;
u16 hubchange;
int i, ret;
hub = container_of(work, struct usb_hub, events);
hdev = hub->hdev;
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev);
kcov_remote_start_usb((u64)hdev->bus->busnum);
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
hdev->state, hdev->maxchild,
/* NOTE: expects max 15 ports... */
(u16) hub->change_bits[0],
(u16) hub->event_bits[0]);
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
usb_lock_device(hdev);
if (unlikely(hub->disconnected))
goto out_hdev_lock;
/* If the hub has died, clean up after it */
if (hdev->state == USB_STATE_NOTATTACHED) {
hub->error = -ENODEV;
hub_quiesce(hub, HUB_DISCONNECT);
goto out_hdev_lock;
}
/* Autoresume */
ret = usb_autopm_get_interface(intf);
if (ret) {
dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
goto out_hdev_lock;
}
/* If this is an inactive hub, do nothing */
if (hub->quiescing)
goto out_autopm;
if (hub->error) {
dev_dbg(hub_dev, "resetting for error %d\n", hub->error);
ret = usb_reset_device(hdev);
if (ret) {
dev_dbg(hub_dev, "error resetting hub: %d\n", ret);
goto out_autopm;
}
hub->nerrors = 0;
hub->error = 0;
}
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
struct usb_port *port_dev = hub->ports[i - 1];
if (test_bit(i, hub->event_bits)
|| test_bit(i, hub->change_bits)
|| test_bit(i, hub->wakeup_bits)) {
/*
* The get_noresume and barrier ensure that if
* the port was in the process of resuming, we
* flush that work and keep the port active for
* the duration of the port_event(). However,
* if the port is runtime pm suspended
* (powered-off), we leave it in that state, run
* an abbreviated port_event(), and move on.
*/
pm_runtime_get_noresume(&port_dev->dev);
pm_runtime_barrier(&port_dev->dev);
usb_lock_port(port_dev);
//端口事件的处理
port_event(hub, i);
usb_unlock_port(port_dev);
pm_runtime_put_sync(&port_dev->dev);
}
}
/* deal with hub status changes */
if (test_and_clear_bit(0, hub->event_bits) == 0)
; /* do nothing */
else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
dev_err(hub_dev, "get_hub_status failed\n");
else {
if (hubchange & HUB_CHANGE_LOCAL_POWER) {
dev_dbg(hub_dev, "power change\n");
clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
if (hubstatus & HUB_STATUS_LOCAL_POWER)
/* FIXME: Is this always true? */
hub->limited_power = 1;
else
hub->limited_power = 0;
}
if (hubchange & HUB_CHANGE_OVERCURRENT) {
u16 status = 0;
u16 unused;
dev_dbg(hub_dev, "over-current change\n");
clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
msleep(500); /* Cool down */
hub_power_on(hub, true);
hub_hub_status(hub, &status, &unused);
if (status & HUB_STATUS_OVERCURRENT)
dev_err(hub_dev, "over-current condition\n");
}
}
out_autopm:
/* Balance the usb_autopm_get_interface() above */
usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
usb_unlock_device(hdev);
/* Balance the stuff in kick_hub_wq() and allow autosuspend */
usb_autopm_put_interface(intf);
kref_put(&hub->kref, hub_release);
kcov_remote_stop();
}
其中的核心代码是遍历各个端口,并对出现变化的端口进行下一步处理。
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
struct usb_port *port_dev = hub->ports[i - 1];
if (test_bit(i, hub->event_bits)
|| test_bit(i, hub->change_bits)
|| test_bit(i, hub->wakeup_bits)) {
pm_runtime_get_noresume(&port_dev->dev);
pm_runtime_barrier(&port_dev->dev);
usb_lock_port(port_dev);
//端口事件的处理
port_event(hub, i);
usb_unlock_port(port_dev);
pm_runtime_put_sync(&port_dev->dev);
}
}
端口事件的处理