Linux之USB子系统(一):usb总线设备模型
时间:2022-09-27 14:00:00
USB子系统
USB体系介绍
USB( Universal Serial Bus )它是一种支持热拔插的高速串行传输总线,其设计目标是取代串行、并行等各种低速总线,用单一类型的总线连接各种设备。当前,USB几乎所有的连接都可以支持PC从最初开始USB1.0、USB1.1、USB2.0发展到USB3.0,USB3.1和USB3.2,USB2.0的高速模式(High-Speed)最高480Mbps,USB3.0的Super-Speed模式达到5Gbps,最新的USB3.能达到20Gbps。USB当有全速时,下兼容模式(USB1.1)或者低速(USB1.0)连接到高速(USB2.0)主机时,主机依然可以支持,不过速度只能是当前设备的USB版本的速度。USB当有全速时,下兼容模式(USB1.1)或者低速(USB1.0)连接到高速(USB2.0)主机仍然可以支持,但速度只能是当前设备USB版本的速度 USB 在总线上,最高传输速度等级由总线上最慢的设备决定。
USB 体系包括USB主机控制器,USB设备以及USB连接三个部分,用分层拓扑连接所有部分USB如下图所示:
- USB主机控制器:控制所有控制器USB设备通信。通常,计算机CPU并不直接和USB与控制器打交道理控制器,CPU通过控制器向设备发送指令,通过控制器接收数据。
- Hub:不止一台计算机USB允许同时使用多个接口USB这些接口实际上就是所谓的设备Hub口。通常一个USB控制器会集成一个Hub,这个Hub被称作Root Hub,其他的Hub它可以连接到这里,然后延伸到其他外部设备。当然,你也可以不使用它Hub,USB直接接到设备Root Hub上。
- USB设备:包括设备USB功能设备和USB Hub,主机控制器最多可以支持128个地址,地址0作为默认地址,只在设备枚举期间使用,不能分配给任何设备,所以一个USB Host如果一个设备占用一个地址,最多可以支持127个设备。
- 复合设备(Compound Device):所谓复合设备,其实就是通过内置多个功能设备。USB Hub组合设备,如带录音麦克风的设备USB摄像头等。
- USB连接:指连接USB 设备和主机(或Hub)四线电缆。电缆包括VBUS(电源线),GND(地线)和D 、D-两条信号线。主机连接USB每个设备提供电源供其使用,每个设备都提供电源USB设备也可以有自己的电源,如下图所示:
USB协议简述
USB总线是一种轮询总线,采用轮询广播机制传输数据。协议规定,所有传输均由主机启动,所有数据传输均由主机控制,并在任何时候传输USB只允许数据包在系统中传输。USB通信最基本的形式是通过USB设备中的一个叫Endpoint(端点),主机和端点之间的数据传输是通过的Pipe(管道)。
一个USB逻辑设备是一系列端点的集合,它与主机之间的通信发生在主机上缓冲区还有一个设备端点之间,通过管道传输数据。换句话说,管道的一端是主机上的缓冲区,另一端是设备上的端点。
-
数据采用令牌包-数据包-握手包传输,在令牌包中指定设备地址和端点,以确保只有一个设备响应广播数据包/令牌包。数据包是USB 最小数据传输的最小单位包括 SYNC、数据及 EOP 三个部分。数据格式对不同的包有不同的格式,但都是 8 位的 PID 开始。PID 指定数据包的类型(共) 16 种)。令牌包即指 PID 为 IN/OUT/SETUP 的包。握手包表示数据传输的成功。
-
EP:端点是USB可在设备中收发数据的最小单元,支持单向或双向数据传输。**端点有方向和确定,或者是in、要么是out,除了端点0,协议规定了所有USB设备必须有端点0in端点也作为out端点,usb控制设备在系统中使用端点0。**设备支持端点的数量有限,除默认端点外,低速设备最多支持 2 组端点(2 个输入,2 高速和全速设备最多支持一个输出) 15 组端点。
-
接口:usb逻辑设备,usb端点被绑定为接口,接口代表基本功能。有些设备有多个接口,比如usb扬声器包括键盘接口和音频流接口。接口应对应驱动程序,usb扬声器在Linux需要两个不同的驱动程序。
-
Pipe:主机和设备之间的数据传输模型有两种管道,没有格式流管道(Stream Pipe)和有格式的信息管道(Message Pipe)。任何USB一旦设备上电,就会有信息管,即默认控制管道,连接主机和默认端点,USB主机通过管道获取设备的描述、配置和状态,并配置设备。
-
传输类型:USB该系统定义了四种传输类型,如下:
- 控制传输:主要用于在设备连接时对设备进行枚举和其他因设备而具体操作。
- 中断传输:键盘、游戏手柄等可靠传输延迟要求严格、数据量小。
- 批量传输:用于可靠传输大量数据,如U盘 。
- 同步传输:用于摄像头、 USB 音响等。
注:中断传输并不意味在传输过程中,设备会先中断HOST,继而通知 HOST 启动传输。中断传输也是如此。HOST启动传输,通过轮询询问设备是否有数据发送,如果有,则进行数据传输。
-
描述符:usb描述符分为设备描述符、配置描述符、接口描述符和端点描述符。USB协议规定每一项usb接口设备必须支持这四种描述符, 存储在这些描述符中usb设备的EEPROM中,在usb当设备枚举时,主机控制器将获得设备上的描述符,以安装相应的驱动器。
四种描述符之间的关系:一个USB设备只有一个设备描述符。设备描述符决定了设备的配置,每个配置都有一个配置描述符;每个配置描述符定义了多少接口,每个接口都有一个接口描述符;界面描述符定义了界面的端点,每个端点都有端点描述符;端点描述符定义了端点的大小和类型。
Q&A
-
现象:未知usb设备接到PC,如USB转串口线
- 一个黄色感叹号会在设备管理器的下端口弹出。cp2102 usb to uart bridge controller“。
- 跳出对话框,提示您安装驱动程序或自动安装驱动程序。
-
Q1:既然还没有"驱动程序",为什么能知道是的usb转串口设备呢?
A1:windows里已经有了USB接入总线驱动程序USB设备后,"总线驱动程序"通过EP0获得设备描述符后,知道你是一个usb to uart提示您安装设备驱动程序的设备。
USB总线驱动程序负责识别USB设备识别,给USB找到相应的驱动程序。
-
Q2:USB设备种类很多,为什么一接入电脑就能识别出来?
A2:USB主机控制器和USB设备都遵循USB协议,USB总线驱动程序会发出一些命令来获取设备信息(描述符),USB必须返回设备"描述符"给主机。
-
Q3:PC有很多机器连接USB如何区分设备?
A3:每一个USB当设备接入主机时,USB总线驱动程序将分配一个编号并连接到它USB总线上的每一个USB所有设备都有自己的地址),主机想访问某个USB设备发出的命令包含相应的编号(地址)。
-
Q4:USB设备刚接入PC还没有编号,所以PC怎么把"分配的编号"告诉它?
A4:新接入的USB默认设备编号为0,主机在新编号分配前使用0编号与其通信。
-
Q5.为什么一接入?USB设备,PC机就能发现它?
A5:主机的USB口内部,D-和D 接有15K未连接的下拉电阻USB设备为低电平。USB设备的USB口内部,D-或D 接有1.5K上拉电阻。设备一进入主机,主机就会被移动USB口的D-或D 从硬件的角度通知主机有新的设备接入。
USB驱动程序框架
-
USB主机控制器类型:
- OHCI: 是支持USB1.1的标准, 相较UHCI,OHCI硬件复杂,硬件做的事情多,所以实现相应的软件驱动任务相对简单。主要用于非x86的USB,如扩展卡、嵌入式开发板等USB主控。
- UHCI: 是Intel主导的对USB1.0、1.1接口标准,和OHCI不兼容。UHCI软件驱动的任务很重,需要更复杂,但可以使用更便宜、更简单的硬件USB控制器。
- EHCI: 是Intel导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
- XHCI:最新的USB3.0接口标准, 在速度、节能、虚拟化等方面都比前面3中有了较大的提高,支持所有种类速度的USB设备,xHCI的目的是为了替换OHCI/UHCI/EHCI。
-
USB总线驱动程序的作用:
-
枚举USB设备:获取设备描述、地址分配、获取配置描述、设备配置。
描述符的信息见include\linux\usb\ch9.h,或参照USB SPEC的9.5章节
-
匹配设备驱动
-
提供USB读写函数(不知道数据含义)
-
-
USB设备驱动程序:具体功能接口的驱动程序,知道数据含义,实现功能
USB总线驱动程序
usb总线设备驱动模型
USB子系统在内核中是Linux平台总线设备驱动模型,总入口在linux-4.19.148\drivers\usb\core\usb.c中,内核初始化do_initcalls会调用到subsys_initcall,初始化usb子系统。
usb_init
模型中的总线落实在USB子系统里就是usb_bus_type
,它在usb_init
中通过bus_register
进行注册。usb_init中当然还有sysfs、hub的初始化,这个放到后面另开章节在讲!
/* linux-4.19.148\drivers\usb\core\usb.c */
static int __init usb_init(void)
{
int retval;
//注册总线
retval = bus_register(&usb_bus_type);
//...
//注册通用驱动
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out;
//...
out:
return retval;
}
subsys_initcall(usb_init);
/* linux-4.19.148\drivers\usb\core\driver.c */
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
.need_parent_lock = true,
};
/* linux-4.19.148\drivers\usb\core\generic.c */
struct usb_device_driver usb_generic_driver = {
.name = "usb",
.probe = generic_probe,
.disconnect = generic_disconnect,
#ifdef CONFIG_PM
.suspend = generic_suspend,
.resume = generic_resume,
#endif
.supports_autosuspend = 1,
};
usb_device_match
bus的name是usb,设备和驱动之间的匹配函数是usb_device_match
,match函数中的两个参数对应的就是总线两条链表里的设备和驱动。总线上有新设备或新的驱动添加时,都会调用到match函数,如果指定的驱动程序能够处理指定的设备,也就匹配成功,函数返回1。usb_device_match
中有两个分支,一个是给USB设备走,一个给USB接口走。
设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动。Linux设备模型中的device落实在USB子系统中,成了两个结构:一个是struct usb_device
,一个是usb_interface
。一个USB键盘,上面带有一个扬声器,因此有两个接口,那肯定得要有两个驱动程序,一个是键盘驱动程序,一个是音频驱动程序。
/* linux-4.19.148\drivers\usb\core\driver.c */
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
/* devices and interfaces are handled separately */
if (is_usb_device(dev)) {
/* interface drivers never match devices */
if (!is_usb_device_driver(drv))
return 0;
/* TODO: Add real matching code */
return 1;
} 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_register_device_driver
注册usb设备驱动。
usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
{
/* 表示该driver是usb_device_driver */
usb_generic_driver->drvwrap.for_devices = 1;
usb_generic_driver->drvwrap.driver.name = usb_generic_driver->name;
/* 绑定bus为usb_bus_type */
usb_generic_driver->drvwrap.driver.bus = &usb_bus_type;
/* probe函数为usb_probe_device */
usb_generic_driver->drvwrap.driver.probe = usb_probe_device;
usb_generic_driver->drvwrap.driver.owner = owner;
/* 注册平台驱动 */
retval = driver_register(&usb_generic_driver->drvwrap.driver);
return retval;
}
usb设备枚举
usb_init
在内核启动后已将完成了usb子系统的初始化,若不接任何usb设备,usb子系统的使命就完成了。只有在接入设备后,usb子系统才会进入到设备枚举的流程。
USB2.0 SPEC中介绍到,当设备连接到主机时,按照以下顺序进行枚举
-
连接了设备的 HUB 在 HOST 查询其状态改变端点时返回对应的bitmap,告知 HOST 某个 PORT 状态发生了改变。
-
主机向 HUB 查询该 PORT 的状态,得知有设备连接,并知道了该设备的基本特性。
-
主机等待(至少 100ms)设备上电稳定,然后向 HUB 发送请求,复位并使能该 PORT。
-
HUB 执行 PORT 复位操作,复位完成后该 PORT 就使能了。现在设备进入到 defalut 状态,可以从 Vbus 获取不超过 100mA 的电流。主机可以通过 0 地址与其通讯。
-
主机通过 0 地址向该设备发送 get_device_descriptor 标准请求,获取设备的描述符。
-
主机再次向 HUB 发送请求,复位该 PORT。
-
主机通过标准请求 set_address 给设备分配地址。
-
主机通过新地址向设备发送 get_device_descriptor 标准请求,获取设备的描述符。
-
主机通过新地址向设备发送其他 get_configuration 请求,获取设备的配置描述符。
-
根据配置信息,主机选择合适配置,通过 set_configuration 请求对设备而进行配置。这时设备方可正常使用。
当把USB设备接到设备上,可以看到输出信息:
[root@dvrdvs ] # [ 33.587882] usb 3-1: new low-speed USB device number 2 using xhci-hcd
拔掉USB设备,输出信息如下:
[root@dvrdvs ] # [ 48.614777] usb 3-1: USB disconnect, device number 2
再次接上设备:
[root@dvrdvs ] # [ 3173.397887] usb 3-1: new low-speed USB device number 3 using xhci-hcd
接下来就根据打印信息分析usb总线驱动程序是如何枚举usb设备的
/* linux-4.19.148\drivers\usb\core\hub.c */
static int hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
//...
if (udev->speed < USB_SPEED_SUPER)
dev_info(&udev->dev,
"%s %s USB device number %d using %s\n",
(udev->config) ? "reset" : "new", speed,
devnum, driver_name);
//...
if (udev->speed >= USB_SPEED_SUPER) {
devnum = udev->devnum;
dev_info(&udev->dev,
"%s SuperSpeed%s%s USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
(udev->speed == USB_SPEED_SUPER_PLUS) ?
"Plus Gen 2" : " Gen 1",
(udev->rx_lanes == 2 && udev->tx_lanes == 2) ?
"x2" : "",
devnum, driver_name);
}
//...
}
void usb_disconnect(struct usb_device **pdev)
{
//...
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
dev_info(&udev->dev, "USB disconnect, device number %d\n",
udev->devnum);
//...
}
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
u16 portchange)
{
struct usb_device *udev = port_dev->child;
/* Disconnect any existing devices under this port */
if (udev) {
/* 若udev存在,说明是设备下线动作 */
usb_disconnect(&port_dev->child);
}
/* ... */
/* 设备上线动作 */
/* reset (non-USB 3.0 devices) and get descriptor */
usb_lock_port(port_dev);
status = hub_port_init(hub, udev, port1, i);
usb_unlock_port(port_dev);
/* ... */
}
int usb_hub_init(void)
hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
hub_probe
INIT_WORK(&hub->events, hub_event);
hub_irq(struct urb *urb)
/* Something happened, let hub_wq figure it out */
kick_hub_wq(hub);
queue_work(hub_wq, &hub->events)
hub_event
port_event
hub_port_connect_change /* port口状态改变 */
hub_port_connect
/* 分配一个usb_device */
udev = usb_alloc_dev(hdev, hdev->bus, port1);
/* 给新设备分配编号 */
choose_devnum(udev);
/* port初始化 */
hub_port_init(hub, udev, port1, i)
/* 新设备初始化 */
usb_new_device(udev)
usb_alloc_dev
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
device_initialize(&dev->dev);
/* usb_device中的device的bus为usb_bus_type */
dev->dev.bus = &usb_bus_type;
/* usb_device中的device的bus为usb_device_type */
dev->dev.type = &usb_device_type;
//...
hub_port_init
port初始化
hub_port_init
/* 4.PORT 复位操作 */
hub_port_reset
/* 5. 发送get_device_descriptor标准请求,获取设备描述符 */
#define GET_DESCRIPTOR_BUFSIZE 64
usb_control_msg(udev, usb_rcvaddr0pipe(),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
USB_DT_DEVICE << 8, 0,
buf, GET_DESCRIPTOR_BUFSIZE,
initial_descriptor_timeout);
/* 6. 主机再次向 HUB 发送请求,复位该 PORT */
hub_port_reset
/* 7. 主机通过标准请求set_address给设备设置分配的地址 */
hub_set_address(udev, devnum);
/* 8. 主机通过新地址向设备发送get_device_descriptor标准请求,获取设备描述符 */
retval = usb_get_device_descriptor(udev, 8);
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
usb_new_device
新设备初始化
usb_new_device
/* 继续设备枚举流程 */
usb_enumerate_device(udev);
/* 9. 主机通过新地址向设备发送get_configuration请求,获取设备的配置描述符 */
usb_get_configuration(udev);
/* 解析配置 */
usb_parse_configuration
/* 解析接口 */
usb_parse_interface
/* 解析端点 */
usb_parse_endpoint
/* 向总线注册设备 */
device_add(&udev->dev);
usb设备match
在usb_new_device
中device_add(&udev->dev)
,此时usb子系统bus总线上添加了一个usb_device,进入到总线设备驱动模型的match函数中
问:dev
在device_add中添加到总线,drv
在哪里注册?
答:usb_init
中usb_register_device_driver(&usb_generic_driver, THIS_MODULE)
static int usb_device_match(struct device *dev, struct device_driver *drv)
if (is_usb_device(dev)) {
/* interface drivers never match devices */
if (!is_usb_device_driver(drv))
return 0;
/* TODO: Add real matching code */
return 1;
}
/* dev在usb_alloc_dev中指定了type为usb_device_type,所以走得是match函数中的usb设备分支 */
static inline int is_usb_device(const struct device *dev)
{
return dev->type == &usb_device_type;
}
/* 此时总线上的dirver为usb_generic_driver->drvwrap.driver,其for_devices成员在usb_register_device_driver指定为1 */
static inline int is_usb_device_driver(struct device_driver *drv)
{
return container_of(drv, struct usbdrv_wrap, driver)->
for_devices;
}
dev和drv匹配,执行drv中的probe函数,usb_register_device_driver
中指定probe函数为usb_probe_device,最终执行到usb_generic_driver
的probe成员generic_probe
static int usb_probe_device(struct device *dev)
struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);
struct usb_device *udev = to_usb_device(dev);
error = udriver->probe(udev);
static int generic_probe(struct usb_device *udev)
c = usb_choose_configuration(udev);
/* 10.根据配置信息,主机选择合适配置,通过 set_configuration 请求对设备而进行配置 */
usb_set_configuration(udev, c);
/* 分配需要的usb接口 */
struct usb_interface **new_interfaces = NULL;
nintf = cp->desc.bNumInterfaces;
new_interfaces = kmalloc_array(nintf, sizeof(*new_interfaces),
GFP_NOIO);
for (i = 0; i < nintf; ++i)
struct usb_interface *intf;
/* 指定接口的设备device总线类型为usb_bus_type */
intf->dev.bus = &usb_bus_type;
/* 指定接口的设备device类型为usb_if_device_type */
intf->dev.type = &usb_if_device_type;
/* 设备配置 */
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
for (i = 0; i < nintf; ++i)
/* 向总线注册设备 */
device_add(&intf->dev);
usb接口match
generic_probe
中device_add(&intf->dev)
向总线注册了接口的设备,此时再次进入到总线设备驱动模型的match函数中
static int usb_device_match(struct device *dev, struct device_driver *drv)
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,若drv是is_usb_device_driver,返回0 */
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
/* 匹配id_table后,match函数执行成功,继而执行drv的probe函数 */
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
}
/* generic_probe中指定设备的type为usb_if_device_type,所以走得是match函数中的usb接口分支 */
static inline int is_usb_interface(const struct device *dev)
{
return dev->type == &usb_if_device_type;
}
/* 上面有提到设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动,这边的usb_drv就是我们要编写的功能接口驱动,例如键盘驱动、鼠标驱动、音频驱动等 */
#define to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)
接口驱动的注册,参考内核中的usb鼠标的驱动例子
/* linux-4.19.148\drivers\hid\usbhid\usbmouse.c */ 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 */ module_usb_driver(usb_mouse_driver); /* linux-4.19.148\include\linux\usb.h */ #define module_usb_driver(__usb_driver) \ module_driver(__usb_driver, usb_register, \ usb_deregister) #define usb_register(driver) \ usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) /* linux-4.19.148\drivers\usb\core\driver.c */ int usb_register_driver(struct usb_driver *new_driver, struct module 元器件数据手册
、IC替代型号,打造电子元器件IC百科大全!