Linux USB 驱动开发(四)—— 热插拔那点事
学习USB热插拔之前,先学习一些USB的硬件知识:
一、USB基础概念
1、硬件知识(USB插座和插头)
在最初的标准里,USB接头有4条线:
电源,D-,D+,地线。我们暂且把这样的叫做标准的USB接头吧。后来OTG出现了,又增加了miniUSB接头。而miniUSB接头则有5条线,多了一条ID线,用来标识身份用的。标准USB口只有A型和B型。其中每一型又分为插头和插座,例如A型插头,A型插座等。我们平常电脑上用的那种插座叫做A型USB插座,而相应的插头,叫做A型插头,例如U盘上那种。而像打印机上面那个插座,则是B型插座(比较四方的,没电脑上面那种扁),相应的插头,就是B型插头。也许你见过一头方一头扁的USB延长线,没错了,扁的那头就叫做A型插头,而方的那头,就叫做B型插头,而相应的被插的那两个插座,就分别是A型插座和B型插座了。A型插头是插不进B型插座的,反之亦然。
miniUSB也分为A型,B型,但增加了一个AB型(不是血型呀,别搞错了,没有O型^_^)。既然它叫做miniUSB,那么当然它就是很小的了,主要是给便携式设备用的,例如MP3、手机、数码相机等。USB是一主多从结构,即一个时刻只能有一台主机。像PC机就是一个主机,其它的只能是设备,因而两个设备之间是无法直接进行通信的。而USB OTG(on the go)的出现,则解决了这个矛盾:一个设备可以在某种场合下,改变身份,以主机的形式出现。因而就出现了AB型的miniUSB插座,不管是A型miniUSB插头,还是B型miniUSB插头,都可以插进去,而靠里面多出的那条ID线来识别它的身份:是主机还是从机。这样两个USB设备就可以直接连接起来,进行数据传送了。 像我们MP3上用的那中miniUSB插座,就是B型的miniUSB插座(注意,有一类miniUSB插座,似乎不是USB规范里面的,因为miniUSB接头应该有5条线,而这种插座只有4条线)。由于USB是支持热插拔的,因此它在接头的设计上也有相应的措施。USB插头的地引脚和电源引脚比较长,而两个数据引脚则比较短,这样在插入到插座中时,首先接通电源和地,然后再接通两个数据线。这样就可以保证电源在数据线之前接通,防止闩锁发生。至于USB电缆,通常我们不怎么关心,买现成的就行了,除非你是生产USB线缆的。在全速模式下需要使用带屏蔽的双绞电缆线,而低速模式模式则可以不用屏蔽和双绞。此外,USB协议规定,USB低速电缆长度不得超过3米,而全速电缆长度不得超过5米。这是因为线缆传输有延迟,要保证能够正确响应,就不能延迟太多。USB标准规定了里面信号线的颜 {MOD},其中Vbus为红 {MOD},D-为白 {MOD},D+为绿 {MOD},GND为黑 {MOD}。然而,我见过很多USB线缆并没有遵循标准,所以大家在使用时要小心,用表测量一下比较可靠。
2、集线器把USB设备的连接报告给USB主控制器
首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,
当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。
二、什么是热插拔
1、基础概念
热插拔(hot-plugging或Hot Swap)即带电插拔,热插拔功能就是允许用户在不关闭系统,不切断电源的情况下取出和更换损坏的硬盘、电源或板卡等部件,从而提高了系统对灾难的及时恢复能力、扩展性和灵活性等,例如一些面向高端应用的磁盘镜像系统都可以提供磁盘的热插拔功能。具体用学术的说法就是:热替换(Hot replacement)、热添加(hot expansion)和热升级(hot upgrade)
2、热插拔好处
系统中加入热插拔的好处包括:
在系统开机情况下将损坏的模块移除,还可以在开机情况下做更新或扩容而不影响系统操作。
由于热插拔零件的可靠度提升,还可以将它们用做断电器,而且因为热插拔能够自动恢复,有很多热插拔芯片为系统提供线路供电情况的信号,以便系统做故障分析,因此减少了成本。
三、Linux 下USB热插拔处理
1、 Linux下USB HUB的驱动的实现和分析:
在系统初始化的时候在
usb_init函数中调用
usb_hub_init函数,就进入了
hub的初始化。
在usb_hub_init函数中完成了注册hub驱动,并且利用
函数kthread_run创建一个内核线程。该线程用来
管理监视hub的状态,所有的情况都通过该线程来报告。
USB设备是热插拔,这就和PCI设备不同,PCI设备是在系统启动的时候都固定了,因此PCI设备只需要初始化进行枚举就可以了,采用递归算法即可。而USB设备需要热插拔,因此在
hub_probe函数中调用
hub_configure函数来配置hub,在这个函数中主要是利用函数
usb_alloc_urb函数来分配一个urb,利用
usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。
每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq,在该函数中利用kick_khubd将hub结构通过event_list添加到khubd的队列hub_event_list,然后唤醒khubd。进入hub_events函数,该函数用来处理khubd事件队列,从khubd的hub_event_list中的每个usb_hub数据结构。该函数中首先判断hub是否出错,然后通过一个for循环来检测每个端口的状态信息。利用
usb_port_status获取端口信息,如果发生变化就调用
hub_port_connect_change函数来配置端口等。
2、软件层次分析
这里我们先讲讲USB热插拔事件的处理工作。---Khubd守护进程。
-Khubd守护进程它是一个守护进程,来检查usb port的事件通知HCD和usb core,然后做相应的处理。
驱动目录
drivers/usb/*
usb/serial usb 串行设备驱动 (例如usb 3G卡、蓝牙等)
usb/storage usb 大储量磁盘驱动(u盘)
usb/host usb host usb主机控制器驱动(嵌入式otg:dwc_otg)
usb/core usb 核心一些处理代码,所有的驱动相关处理都在这里,也都注册到它里面。
usb/usb-skeleton.c 经典的usb客户驱动框架,可以参考
当然还有其他这里不再说明。
下面贴出USB的整体驱动框架:
这里我们主要分析khub的工作原理: 硬件层次是hub的工作,如何和host及其设备间通信及相应事件
[usb/core/hub.c ]
-
int usb_hub_init(void)
-
{
-
if (usb_register(&hub_driver) < 0) {
-
printk(KERN_ERR "%s: can't register hub driver
",
-
usbcore_name);
-
return -1;
-
}
-
-
khubd_task = kthread_run(hub_thread, NULL, "khubd");
-
if (!IS_ERR(khubd_task))
-
return 0;
-
-
/* Fall through if kernel_thread failed */
-
usb_deregister(&hub_driver);
-
printk(KERN_ERR "%s: can't start khubd
", usbcore_name);
-
-
return -1;
-
}
这里我们只关心kthread_run(hub_thread, NULL, "khubd"); 然后我们看hub_thread函数
-
static int hub_thread(void *__unused)
-
{
-
do {
-
hub_events();
-
wait_event_interruptible(khubd_wait,
-
!list_empty(&hub_event_list) ||
-
kthread_should_stop());
-
try_to_freeze();
-
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
-
-
pr_debug("%s: khubd exiting
", usbcore_name);
-
return 0;
-
}
这里我们看到了hub_events()函数。然后设置运行状态,如果有事件就加入hub_event_list。自此khubd运行起来了。
这里我们同样贴出它的函数调用流程图(这里懒得自己画了,就剪切了个^^)
通过流程图我们可以清晰的明白,当usb设备插入usb接口后,khubd运行,它检测到port状态的变化,调用hub_port_connect_change(),如果是新设备那么usb_allco_dev,然后调用usb_new_device来进行配置使usb设备可以正常工作。详细流程请看源码。
四、USB的枚举过程
内核辅助线程khubd用来监视与该集线器连接的所有端口,通常情况下,
该线程处于休眠状态,当集线器驱动程序检测到USB端口状态变化后,该内核线程立马唤醒。
USB的枚举过程:USB的枚举过程是热插拔USB设备的起始步骤,该过程中,
主机控制器获取设备的相关信息并配置好设备,集线器驱动程序负责该枚举过程。
枚举过程主要分如下几步:
Step1:根集线器报告插入设备导致的端口电流变化,集线器驱动程序检测到这一状态变化后,唤醒khubd线程。
Step2:khubd识别出电流变化的那个端口
Step3:khubd通过给控制端点0发送控制URB来实现从1-127中选出一个数作为插入设备的批量端点
Step4:khubd利用端口0使用的控制URB从插入的设备那里获得设备描述符,然后获得配置描述符,并选择一个合适的。
Step5:
khubd请求USB核心把对应的客户驱动程序和该USB设备挂钩。