S3C2440 USB总线驱动分析(十九)

如下图所示,以windows为例,我们插上一个没有USB设备驱动的USB,就会提示你安装驱动程序

1 为什么一插上就会有提示信息?

是因为windows自带了USB总线驱动程序,

2 USB总线驱动程序负责:

识别USB设备;给USB设备找到并安装对应的驱动程序;提供USB的读写函数。

新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。

然后USB总线驱动程序都会给它分配一个地址(编号)

PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)

USB是一种主从结构。主机叫做Host,从机叫做Device,所有的USB传输,都是从USB主机这方发起;USB设备没有“主动”通知USB主机的能力。

例子:USB鼠标滑动一下立刻产生数据,但是它还没有能力通过OC机来读数据,只能被动地等待PC机来读。

USB可以热拔插的硬件原理

    在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。

    而在USB设备端,在D+或者D-上接了1.5k欧姆上拉电阻,对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5k的上拉电阻和15k的下拉电阻分呀,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到告诉模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。


USB的4大传输类型:

控制传输

是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。

中断传输

支持中断传输的典型设备有USB鼠标、USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性来查看有没有数据需要处理

批量处理

支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。

实时传输

USB摄像头就是实时传输设备的典型代表,它同样进行大量数据的传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高


USB端点:(相当于我们是通过“嘴”来说话)

USB设备与主机会有若干个通信的“端点”,每个端点都有个端点号,除了端点0外,每一个端点只能工作在一种传输类型(控制传输、中断传输、批量传输、实时传输)下,一个传输方向下

传输方向都是基于USB主机的立场说的,

比如:鼠标的数据是从鼠标传到PC机,对应的端点称为“中断输入端点”

其中端点0是设备的默认控制端点,既能输出也能输入,用于USB设备的识别过程


同样linux内核也自带了USB总线驱动程序,框架乳腺癌:


要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:

OHCI(Open Host Controller Inerface):微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的软件简单,硬件复杂

UHCI(Universal Host Controller Interface):Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的软件复杂,硬件简单

EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)

xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设针,同时也支持USB2.0、1.1等


接下来进入主题,开始分析USB总线驱动,如何识别USB设备

由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息

发现以下字段:


如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行


这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口

1、drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里

它又是被谁调用的呢,如下图所示,我们搜索到它是通过hub_thread()函数调用的


hub_thread()函数如下:

static int hub_thread(void *__unused)
{
	do {
		hub_events();    //执行页次hub时间函数
		wait_event_interruptible(khubd_wait,
				!list_empty(&hub_event_list) ||
				kthread_should_stop());//每次执行一次hub事件,都会进入一次等待事件中断函数
		try_to_freeze();
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));

	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}

从上面函数中得到,要想执行hub_event(),都要等待khubd_wait这个中断唤醒才行


2、我们搜索“khubd_wait”,看看是被谁唤醒

找到该中断在kick_khubd()函数中唤醒,代码如下:

static void kick_khubd(struct usb_hub *hub)
{
	unsigned long	flags;

	/* Suppress autosuspend until khubd runs */
	to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;

	spin_lock_irqsave(&hub_event_lock, flags);
	if (list_empty(&hub->event_list)) {
		list_add_tail(&hub->event_list, &hub_event_list);
		wake_up(&khubd_wait);//唤醒khubd_wait这个中断
	}
	spin_unlock_irqrestore(&hub_event_lock, flags);
}


3、继续搜索kick_khubd,发现被hub_irq()函数中调用

显然,就是当USB设备插入后,D+ 或 D- 就会被拉高,然后USB主机控制器就会产生一个hub_irq中断


4、接下来我们直接分析hub_port_connect_chage()函数,如何连接端口的

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
	... ...
	udev = usb_alloc_dev(hdev, hdev->bus, port1);//(1)注册usb_device,然后会放在usb总线上
		
	usb_set_device_state(udev, USB_STATE_POWERED);//设置注册的USB设备的状态标识
	
	... ...
		
	choose_address(udev);//(2)给新的设备飞培一个地址编号
		
	status = hub_port_init(hub, udev, port1, i);//(3)初始化端口,与USB设备建立连接
	... ...

	status = usb_new_device(udev);//(4)创建USB设备,与USB设备驱动连接
}

(usb_device设备结构体参考:https://mp.csdn.net/postedit/81025137

所以最终流程图如下:



5、我们进入hub_port_connect_change()->usb_alloc_dev(),来看看它是怎么设置usb_device的

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);//分配一个usb_device设备
	... ...

	device_initialize(&dev->dev);//初始化usb_device
	dev->dev.bus = &usb_bus_type;//(1)设置usb_device的成员device->bus等于usb_bus_type总线
	dev->dev.type = &usb_device_type;//(2)设置usb_device的成员device->type等于usb_device_type
	...
	return dev;//返回一个usb_device结构体
}

(1)设置device成员bus,主要是二十小节,注册usb总线的device表上,

其中usb_bus_type是一个全局变量,它和我们之前学的platform平台总线相似,属于USB总线,是Linux中bus的一种。

如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB驱动联系起来。


usb_bus_type结构体如下:

struct bus_type usb_bus_type = {
	.name =		"usb",//总线名称,存在/sys/bus
	.match =	usb_device_match,//匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
	.uevent =	usb_uevent,//事件函数
	.suspend =	usb_suspend,//休眠函数
	.resume =	usb_resume,//唤醒函数
};

6、我们进入hub_port_connect_change()->choose_address(),来看看它是怎么分配地址编号的

static void choose_address(struct usb_device *udev)
{
	int		devnum;
	struct usb_bus	*bus = udev->bus;

	devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);//在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号
	if (devnum >= 128)//若编号大于等于128,说明没有找到空余的地址编号,从头开始找
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);

	bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);//设置下次寻址的区间+1

	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);//设置位
		udev->devnum = devnum;
	}
}

从上面代码中分析每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,可以看出,如下图所示:



7、我们再来看看hub_port_connect_chage()->hub_port_init()函数是如何来实现连接USB设备的

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{...
	for (j = 0; j < SET_ADDRESS_TRIES; ++j) 
	{
		retval = hub_set_address(udev);//(1)设置地址,告诉USB设备新的地址编号
		if (retval >= 0)
			break;
		msleep(200);
	}
...
	retval = usb_get_device_descriptor(udev, 8);//(2)获得USB设备描述符前8个字节
...
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);//重新获取设备描述符信息
}

(1)hub_set_address()函数主要是用来告诉USB设备新的地址编号,

hub_set_address()函数如下:

static int hub_set_address(struct usb_device *udev)
{
	int retval;

	retval = usb_control_msg(udev, usb_sndaddr0pipe(),//(1)
		USB_REQ_SET_ADDRESS, 0, udev->devnum, 0,
		NULL, 0, USB_CTRL_SET_TIMEOUT);


	if (retval == 0) {//设置新的地址,传输完成,返回0
		usb_set_device_state(udev, USB_STATE_ADDRESS);//设置状态标志
		ep0_reinit(udev);
	}
	return retval;
}

usb_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0,其中参数udev表示目标设备;使用管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0;USB_REQ_SET_ADDRESS表示命令码,既设置地址;udev->devnum表示设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。

(2)usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方说支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构。

其中USB设备描述符结构体如下所示:

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;    //本描述符的size
	__u8  bDescriptorType;//描述符的类型,这里是设备描述符DEVICE

	__le16 bcdUSB;//指名usb的版本,比如usb2.0
	__u8  bDeviceClass;//类
	__u8  bDeviceSubClass;//子类
	__u8  bDeviceProtocol;//指定协议
	__u8  bMaxPacketSize0;//端点0对应的最大包大小
	__le16 idVendor;//厂家ID
	__le16 idProduct;//产品ID
	__le16 bcdDevice;//设备的发布号
	__u8  iManufacturer;//字符串描述符中厂家ID的索引
	__u8  iProduct;//字符串描述符中产品ID的索引
	__u8  iSerialNumber;//字符串描述符中设备序列号的索引
	__u8  bNumConfigurations;//可能的配置的树木
} __attribute__ ((packed));

8、我们来看看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的

int usb_new_device(struct usb_device *udev)
{
...
	err = usb_get_configuration(udev);//(1)获取配置描述块
...
	err = device_add(&udev->dev);//(2)把device放入bus的dev链表中,并寻找对应的设备驱动
...	
}

(1)其中usb_get_configuration()函数如下,就是获取各个配置

int usb_get_configuration(struct usb_device *dev)
{
    //USB_MAXCONFIG定义为8,表示设备描述块下最多不能超过8个配置描述块
	if (ncfg > USB_MAXCONFIG) {//nfs表示 设备描述块下 有多少个配置描述块
		dev_warn(ddev, "too many configurations: %d, "
		    "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
		dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
	}

	for (cfgno = 0; cfgno < ncfg; cfgno++) //for循环,从USB设备里一次读入所有配置描述块
	{
	
		length = max((int) le16_to_cpu(desc->wTotalLength),
		    USB_DT_CONFIG_SIZE);//通过wTotalLength,知道实际数据大小

		bigbuffer = kmalloc(length, GFP_KERNEL);//然后分配足够大的空间
...
		result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		    bigbuffer, length);//在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bifbuffer中
...
		dev->rawdescriptors[cfgno] = bigbuffer;//再将bifbuffer地址放在ramdescriptor所指的指针数组中

		result = usb_parse_configuration(&dev->dev, cfgno,
		    &dev->config[cfgno], bigbuffer, length);//最后解析每个配置块
	}
}

(2)其中device_add()函数如下:

int device_add(struct device *dev)
{
...
	dev = get_device(dev);//使dev等于usb_device下的device成员
...
	if ((error = bus_add_device(dev)))//把这个设备添加带dev->bus的device表中
		goto BusError;
...
	bus_attach_device(dev);//来匹配对应的驱动程序
	
}

当bus_attach_device()函数匹配成功,就会调用驱动的probe函数


9、我们再来看看usb_bus_type这个的成员usb_device_match函数,看看是如何匹配的


usb_device_match函数如下所示:

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	if (is_usb_device(dev)) {//判断是不是USB设备

		if (!is_usb_device_driver(drv))
			return 0;

		return 1;

	} else {    //否则就是USB驱动或者USB设备的接口
		struct usb_interface *intf;
		struct usb_driver *usb_drv;
		const struct usb_device_id *id;

		if (is_usb_device_driver(drv))//如果是USB驱动,就不需要匹配,直接return
			return 0;

		intf = to_usb_interface(dev);//获取USB设备的接口
		usb_drv = to_usb_driver(drv);//获取USB驱动

		id = usb_match_id(intf, usb_drv->id_table);//匹配USB驱动的成员id_table
		if (id)
			return 1;

		id = usb_match_dynamic_id(intf, usb_drv);
		if (id)
			return 1;
	}

	return 0;
}

显然就是匹配USB驱动的id_table


10、那么USB驱动的id_table又该如何定义?

id_table的结构体为usb_device_id,如下所示:

struct usb_device_id {

	__u16		match_flags;//与usb设备匹配哪种类型?比较类型的宏如下:
//USB_DEVICE_ID_MATCH_INT_INFO:用于匹配设备的接口描述符的3个成员
//USB_DEVICE_ID_MATCH_DEV_INFO:用于匹配设备描述符的3个成员
//USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION:用于匹配特定的USB设备的4个成员
//USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct)


	/* 以下4个用匹配描述特定的USB设备 */
	__u16		idVendor;	//厂家ID
	__u16		idProduct;	//产品ID
	__u16		bcdDevice_lo;	//设备的低版本号
	__u16		bcdDevice_hi;	//设备的高版本号

	/* 以下3个就是用于比较设备描述符 */
	__u8		bDeviceClass;	//设备类
	__u8		bDeviceSubClass;//设备子类
	__u8		bDeviceProtocol;//设备协议

	/* 以下3个就是用于比较设备的接口描述符的 */
	__u8		bInterfaceClass;	//接口类型
	__u8		bInterfaceSubClass;	//接口子类型
	__u8		bInterfaceProtocol;	//接口所遵循的协议

	/* not matched against */
	kernel_ulong_t	driver_info;
}

(设备描述符接口描述符结构体参考:https://mp.csdn.net/postedit/81025137

我们参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),是如何使用的,如下图所示:


发现它是通过USB_INTERFACE_INFO这个宏定义的,该宏如下所示:

#define USB_INTERFACE_INFO(cl,sc,pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, //设置id_table的,match_flags成员
	.bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)//设置id_table的3个成员,用于匹配USB设备的3个成员

然后将上图的usb_mouse_id_table[]里的3个值代入宏

USB_INTERFACE_INFO(cl,sc,pr)中:

.bInterfaceClass =USB_INTERFACE_CLASS_HID;  
   //设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
   //HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03

.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;  
   //设置匹配USB的接口子类型为启动设备

.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
  //设置匹配USB的接口协议为USB鼠标的协议,等于2
  //当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议

如下图,我们也可以通过windows下也可以找到鼠标的协议号,是1(Col01):


其中VID:表示厂家(vendor)ID

PID:表示产品(Product)ID


总结:当我们插上USB设备时,系统就会获取USB设备的配置、接口、端点的数据,并创建设备,所以我们的驱动就需要写id_table来匹配该USB设备

猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/80831666