锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Linux之USB子系统(一):usb总线设备模型

时间:2022-09-27 14:00:00 连接电缆用于cp

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总线是一种轮询总线,采用轮询广播机制传输数据。协议规定,所有传输均由主机启动,所有数据传输均由主机控制,并在任何时候传输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转串口线

    1. 一个黄色感叹号会在设备管理器的下端口弹出。cp2102 usb to uart bridge controller“。
    2. 跳出对话框,提示您安装驱动程序或自动安装驱动程序。
  • 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总线驱动程序

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中介绍到,当设备连接到主机时,按照以下顺序进行枚举

  1. 连接了设备的 HUB 在 HOST 查询其状态改变端点时返回对应的bitmap,告知 HOST 某个 PORT 状态发生了改变。

  2. 主机向 HUB 查询该 PORT 的状态,得知有设备连接,并知道了该设备的基本特性。

  3. 主机等待(至少 100ms)设备上电稳定,然后向 HUB 发送请求,复位并使能该 PORT。

  4. HUB 执行 PORT 复位操作,复位完成后该 PORT 就使能了。现在设备进入到 defalut 状态,可以从 Vbus 获取不超过 100mA 的电流。主机可以通过 0 地址与其通讯。

  5. 主机通过 0 地址向该设备发送 get_device_descriptor 标准请求,获取设备的描述符。

  6. 主机再次向 HUB 发送请求,复位该 PORT。

  7. 主机通过标准请求 set_address 给设备分配地址。

  8. 主机通过新地址向设备发送 get_device_descriptor 标准请求,获取设备的描述符。

  9. 主机通过新地址向设备发送其他 get_configuration 请求,获取设备的配置描述符。

  10. 根据配置信息,主机选择合适配置,通过 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_devicedevice_add(&udev->dev),此时usb子系统bus总线上添加了一个usb_device,进入到总线设备驱动模型的match函数中

问:dev在device_add中添加到总线,drv在哪里注册?

答:usb_initusb_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_probedevice_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百科大全!
          

相关文章