[Linux] USB-Storage驱动 源码阅读笔记(一)

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等一般用作数据端点,存放主机与设备间往来的数据。

USB设备、配置、接口关系
这里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);
	}
}

端口事件的处理

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux系统下USB键盘驱动源码+使用文档说明 如何编写Linux下的USB键盘驱动 1. 指定USB键盘驱动所需的头文件: #include /*内核头文件,含有内核一些常用函数的原型定义*/ #include /*定义内存分配的一些函数*/ #include /*模块编译必须的头文件*/ #include /*输入设备相关函数的头文件*/ #include /*linux初始化模块函数定义*/ #include /*USB设备相关函数定义*/ 2. 定义键盘码表数组: /*使用第一套键盘扫描码表:A-1E;B-30;C-2E…*/ static unsigned char usb_kbd_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 150,158,159,128,136,177,178,176,142,152,173,140 }; 3. 编写设备ID表: static struct usb_device_id usb_kbd_id_table [] = { { USB_INTERFACE_INFO(3, 1, 1) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);/*指定设备ID表*/ 4. 定义USB键盘结构体: struct usb_kbd { struct input_dev *dev; /*定义一个输入设备*/ struct usb_device *usbdev;/*定义一个usb设备*/ unsigned char old[8]; /*按键离开时所用之数据缓冲区*/ struct urb *irq/*usb键盘之中断请求块*/, *led/*usb键盘之指示灯请求块*/; unsigned char newleds;/*目标指定灯状态*/ char name[128];/*存放厂商名字及产品名字*/ char phys[64]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值