usb驱动开发16之设备生命线

回到struct usb_hcd,继续努力的往下看。

kref,usb主机控制器的引用计数。struct usb_hcd也有自己专用的引用计数函数,看hcd.c文件。

usb驱动开发16之设备生命线usb驱动开发16之设备生命线
static void hcd_release (struct kref *kref)
{
struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); kfree(hcd);
} struct usb_hcd *usb_get_hcd (struct usb_hcd *hcd)
{
if (hcd)
kref_get (&hcd->kref);
return hcd;
}
EXPORT_SYMBOL (usb_get_hcd); void usb_put_hcd (struct usb_hcd *hcd)
{
if (hcd)
kref_put (&hcd->kref, hcd_release);
}
EXPORT_SYMBOL (usb_put_hcd);

和struct urb差不多,如果不明白就回去看看聊struct urb吧。

product_desc,主机控制器的产品描述字符串,对于UHCI,它为“UHCI Host Controller”,对于EHCI,它为“EHCI Host Controller”。

irq_descr[24],这里边儿保存的是“ehci-hcd:usb1”之类的字符串,也就是驱动名加上总线编号。

CONFIG_PM,电源管理相关,飘过。

driver,每个主机控制器驱动都有一个struct hc_driver结构体。有兴趣的去看看它在hcd.h里的定义。和usb_driver、pci_driver一样,所有的xxx_driver都有一堆函数指针,这里只说下函数指针之外的东西,也就是开头儿的那三个成员。description直白点说就是驱动的大名,比如对于UHCI,它是“uhci_hcd”,对于EHCI,它就是“ehci_hcd”。product_desc和struct usb_hcd里的那个是一个样儿。hcd_priv_size还是有点儿意思的,前面喝那杯茶的时候提到过,每个主机控制器驱动都会有一个私有结构体,藏在struct usb_hcd最后的那个变长数组里,这个变也是相对的,在创建usb_hcd的时候也得知道它能变多长,不然谁知道要申请多少内存啊,这个长度就hcd_priv_size。

flags,属于HCD的一些标志,可用值就在接下来的HCD_FLAG_HW_ACCESSIBLE和HCD_FLAG_SAW_IRQ两行,至于什么作用,用到时候再聊。

rh_timer,status_urb,rh_registered ,poll_pending,poll_rh,poll_pending这几行都是专为root hub服务的。一个host controller对应一个root hub,即使某些嵌入式系统里,硬件上host controller没有集成root hub,软件上也需要虚拟一个出来,也就是所谓的virtual root hub。它位置是特殊的,但需要提供的功能和其它hub是没有什么差别的,仅仅是在和host controller的软硬件接口上有一些特别的规定。root hub再怎么特别也始终是一个hub,是一个usb设备,也不能脱离usb这个大家庭,也要向组织注册,也要有自己的设备生命线。关于root hub的生命线我们放到本节最后说,先看其他成员。

回到struct usb_hcd,看wireless,无线usb相关。

继续看irq、regs、rsrc_start、rsrc_len这几行,都是与PCI有关的。

power_budget,能够提供的电流。

*pool [HCD_BUFFER_POOLS],几个dma池。因为HCD_BUFFER_POOLS定义为4,所以这里就表示每个主机控制器可以有4个dma池。

我们知道主机控制器是可以进行DMA传输的,想想前面的struct urb,那里有两个成员transfer_dma和setup_dma,当时只是说你可以使用usb_buffer_alloc分配好dma缓冲区给它们,然后再告诉HCD你的urb已经有了,HCD就可以不用再进行复杂的DMA映射,并没有提到你这个获取的dma缓冲区是从哪里来的,实际上就是从这里所说的dma池子里来的。

一般来说DMA映射获得的都是以页为单位的内存,urb需要不了这么大,如果需要比较小的DMA缓冲区,就离不开DMA池了。还是看看主机控制器的这几个池子是怎么创建的,在buffer.c文件里主要有四个函数,hcd_buffer_create(),hcd_buffer_destroy(),hcd_buffer_alloc(),

hcd_buffer_free();需要说明两点,第一个是即使你的主机控制器不支持DMA,这几个函数也是可以用的,只不过创建的不是DMA池子,取的也不是DMA缓冲区,此时,DMA池子不存在,hcd_buffer_alloc获取的只是适用kmalloc申请的普通内存,当然相应的,你必须在它没有利用价值的时候使用hcd_buffer_free将它释放掉。第二个问题是size的问题,看到它们里面都有个pool_max吧,这是个同一个文件里定义的数组。这个数组里定义的就是四个池子中每个池子里保存的DMA缓冲区的size。注意这里虽说只定义了四种size,但是并不说明你使用hcd_buffer_alloc获取DMA缓冲区的时候不能指定更大的size,如果都满足不了要求,那就会使用dma_alloc_coherent为她建立一个新的DMA映射。不可能每次分配都会完全恰好和上面定义的四种size一致,这个问题不用担心,实际上使用这个size获取DMA缓冲区的时候,池子会选择一个略大一些的回馈过去。

回到struct usb_hcd中来, state,主机控制器的状态,紧挨着它的下面那些行就是相关的可用值和宏定义。

hcd_priv[0],主机控制器的私有数据被存储在这个结构体的末尾。

别忘了,我们还有一个重要的任务就是分析root hub的生命线,涉及usb_hcd结构体的主要成员有rh_timer,status_urb,rh_registered ,poll_pending,poll_rh,poll_pending这几行。还是要先声明一下,root hub的生命线只是咱们的设备生命线里的一个岔路口,不会沿着它去仔细的走,只会尽量的使用快镜头去展示一下。如果有哪里不明白,等走完了设备声明主线,你再回过头来看看,很多事情都是在我们蓦然回首的时候才明白的。

基于root hub和host controller这种极为特殊的关系,UHCI、EHCI等主机控制器驱动程序在自己的初始化代码里都有大量的篇幅花在root hub上面。不管是UHCI,还是EHCI,一般PCI都应该有个struct pci_driver结构体,都应该有一个属于自己的probe。在这个probe里,也都会调用usb_create_hcd()来创建一个属于自己的usb_hcd,也都会调用usb_add_hcd()将这个刚刚创建的usb_hcd注册到usb组织里。下面贴usb_add_hcd函数里的部分代码,主要是与root hub相关的代码。

if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {

dev_err(hcd->self.controller, "unable to allocate root hub\n");

retval = -ENOMEM;

goto err_allocate_root_hub;

}

rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :

USB_SPEED_FULL;

hcd->self.root_hub = rhdev;

在usb_add_hcd函数里,调用usb_alloc_dev()来为root hub准备一个struct usb_device结构体,这时root hub的设备类型被赋值为usb_device_type,所属总线类型赋值为usb_bus_type。然后根据各个host controller的实际情况,设定它的速度。struct usb_device结构体准备妥当之后,就要赋给struct usb_bus的root_hub元素。这些都只不过是准备工作,重头戏都在usb_add_hcd()最后调用的register_root_hub()里面。register_root_hub()非常肯定的将root hub的设备号devnum设置为1,于是就少了选择地址设置地址的过程,root hub直接跳跃式发展进入了Address状态,在这个状态我们应该知道此时可以很方便的获得root hub的设备描述符,可以调用usb_new_device将root hub送给无所不能的设备模型,去寻找它命中的驱动。

retval = usb_new_device (usb_dev);

if (retval) {

dev_err (parent_dev, "can't register root hub for %s, %d\n",

usb_dev->dev.bus_id, retval);

}

如果这些都一切顺利的话,register_root_hub()在最后就会将struct usb_hcd的rh_registered设置为1,表示root hub已经找到组织并加入到革命队伍里了。

Linux设备模型根据root hub所属的总线类型usb_bus_type将它添加到usb总线的那条有名的设备链表里,然后会轮询usb总线的另外一条有名的驱动链表,针对每个找到的驱动去调用usb总线的match函数,也就是咱们以前一再遇到以后不断遇到的usb_device_match(),为root hub牵线搭桥寻找另一个匹配的半圆。要注意,root hub的设备类型是usb_device_type,所以在usb_device_match()里走的设备那条路,匹配成功的是那个对所有usb_device_type类型的设备都来者不拒的花心大萝卜,usb世界里唯一的那个usb设备驱动(不是usb接口驱动)struct device_driver结构体对象usb_generic_driver。所以接下来要调用的就是usb_generic_driver的probe函数generic_probe(),去配置root hub,然后root hub就大步迈进了Configured状态。因为root hub只有一个配置,所以generic_probe()配置root hub时并没有多少选择的烦恼,如果有所疑问,可以look下hcd.c的开头就已经设定好的root hub设备描述符

/* usb 2.0 root hub device descriptor */

static const u8 usb2_rh_dev_descriptor [18] = {

0x12, /* __u8 bLength; */

0x01, /* __u8 bDescriptorType; Device */

0x00, 0x02, /* __le16 bcdUSB; v2.0 */

0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */

0x00, /* __u8 bDeviceSubClass; */

0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ]*/

0x40, /* __u8 bMaxPacketSize0; 64 Bytes */

0x00, 0x00, /* __le16 idVendor; */

0x00, 0x00, /* __le16 idProduct; */

KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */

0x03, /* __u8 iManufacturer; */

0x02, /* __u8 iProduct; */

0x01, /* __u8 iSerialNumber; */

0x01 /* __u8 bNumConfigurations; */

};

这张表是const的,也就是说在整个usb的世界里它都是只能去读不能去修改的,要用严谨科学的态度去对待root hub。明眼人一眼就能看到躲在它最后的那个0x01,这就是root hub支持的配置数量。在它下边儿还有针对usb 1.1的root hub设备描述符,大同小异就不贴了。

既然进入了Configured状态,就可以无拘无束的使用root hub提供的所有功能了,不过,对于咱们使用usb的劳苦大众来说,实际在起作用的还是设备里的接口,所以generic_probe()接下来就会根据root hub使用的配置,为它所有的接口准备struct usb_interface结构体对象,这时接口所属的总线类型仍然为usb_bus_type,设备类型就不一样了,为usb_if_device_type,早先说过的那句总线有总线的类型,设备有设备的类型到这里就应该再加上一句,接口有接口的类型。usb_if_device_type在message.c里定义

为root hub的接口准备struct usb_interface结构体也没费太大功夫,因为spec里规定了hub上除了端点0,就只有一个中断的IN端点,并不用准备太多的东西。root hub的配置描述符也已经在hcd.c的开头儿指定好了,确实只有一个接口,一个设置,一个端点,去look一下

/*-------------------------------------------------------------------------*/

/* Configuration descriptors for our root hubs */

static const u8 fs_rh_config_descriptor [] = {

/* one configuration */

0x09, /* __u8 bLength; */

0x02, /* __u8 bDescriptorType; Configuration */

0x19, 0x00, /* __le16 wTotalLength; */

0x01, /* __u8 bNumInterfaces; (1) */

0x01, /* __u8 bConfigurationValue; */

0x00, /* __u8 iConfiguration; */

0xc0, /* __u8 bmAttributes;

Bit 7: must be set,

6: Self-powered,

5: Remote wakeup,

4..0: resvd */

0x00, /* __u8 MaxPower; */

/* USB 1.1:

* USB 2.0, single TT organization (mandatory):

* one interface, protocol 0

*

* USB 2.0, multiple TT organization (optional):

* two interfaces, protocols 1 (like single TT)

* and 2 (multiple TT mode) ... config is

* sometimes settable

* NOT IMPLEMENTED

*/

/* one interface */

0x09, /* __u8 if_bLength; */

0x04, /* __u8 if_bDescriptorType; Interface */

0x00, /* __u8 if_bInterfaceNumber; */

0x00, /* __u8 if_bAlternateSetting; */

0x01, /* __u8 if_bNumEndpoints; */

0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */

0x00, /* __u8 if_bInterfaceSubClass; */

0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */

0x00, /* __u8 if_iInterface; */

/* one endpoint (status change endpoint) */

0x07, /* __u8 ep_bLength; */

0x05, /* __u8 ep_bDescriptorType; Endpoint */

0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */

0x03, /* __u8 ep_bmAttributes; Interrupt */

0x02, 0x00, /* __le16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */

0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */

};

bNumInterfaces,if_bAlternateSetting,if_bNumEndpoints,分别指定了接口数量,使用的设置,端点的数目。也就是说,如果加上端点0,root hub只有两个端点,而另外一个端点为IN端点,端点号为1,中断类型,每次可以处理的最大字节数为2,期望host controller访问自己的时间间隔为256ms。这里为什么说这个中断端点的wMaxPacketSize为2,wMaxPacketSize行不是明确写着0x02,0x00么?协议规定usb设备的各种描述符都是按照little-endian方式的字节序存储的,先是低字节然后是高字节。

为root hub的那个唯一接口准备好struct usb_interface结构体之后应该怎么做?一个接口一个驱动,接下来显然是应该将它送给设备模型去寻找它命中注定的驱动了,所以到了usb_device_match()。不过你说这个时候设备和接口两条路它应该走哪条?它的类型已经设置成usb_if_device_type了,设备那条路把门儿的根本就不会让它进,所以它必须得去走接口那条路。有关在接口这条路上它怎么去寻找另一半儿,日后再说。

回头再看root hub的设备描述符,它的bDeviceClass 被指定为0x09,在include/linux/usb/ch9.h里,它对应的是USB_CLASS_HUB,再看看root hub配置描述符的bInterfaceClass也被指定成0x09,也就是说同样为USB_CLASS_HUB。你再去hub驱动对应的hub.c文件里看看那里的hub_id_table,是不是应该明白点什么了? root hub里的那个接口所对应的驱动就是hub驱动,虽然它很特殊,可它也是个hub啊。root hub一边和host controller相生相依,一边和hub驱动眉来眼去花前月下,多么巨大的讽刺。

接下来要做的就是调用hub驱动里的hub_probe()。不管是root hub还是一般的hub,走到这一步都是必然的,不同的是对于root hub在host controller初始化的过程中就会去调用hub_probe()。hub_probe()通过hub_configure()创建并初始化了一个中断的urb,然后在hub_activate()里调用usb_submit_urb()将它提交给core,接着就又回到了HCD。

每个hub都需要进行中断传输来获得hub的状态变化,不然hub驱动里那个著名的hub_events就活不下去了,你连在hub上的usb设备系统也就察觉不到了,你也就不能用usb摄像头来泡mm了。我们都应该知道中断传输不会提交一次就完事儿了,需要在你的结束处理函数里提交再提交。中断传输都是有个间隔时间的,这个时间不由你决定,也不由我决定,咱们只能期待,决定权在host controller那里。host controller就决定了,对于root hub要每隔250ms去访问一次,你再看上面root hub的描述符,里面显示它期待的时间为0xff,也就是256ms,250与256也差不了多少,root hub也应该满意了。这个固定的访问频率host controller是怎么实现的?就是通过struct usb_hcd里的那个定时器rh_timer,定时器就是专门干这种事儿的,root hub每提交一次urb,在HCD里都会对它初始化一次,指定250ms之后去获得root hub的状态,然后就让urb返回给root hub,于是root hub再提交,再250ms后返回。

但是这种对root hub的轮询机制是早先版本里的,现在就不一样了。struct usb_hcd里出现了uses_new_polling,看名字就知道是一种新机制出现了。在新的机制里,root hub仍然是要提交一个中断urb的,不同的是这时它可以不用再请定时器来帮忙,在有设备插入时,host controller会在自己的中断处理函数里调用一个名叫usb_hcd_poll_rh_status的函数去获得root hub的状态并将urb的所有权归还给hub驱动。

那是不是就可以不需要定时器rh_timer了?事情没这么简单,原来的机制还必须得保留着,给不同的host controller选择使用。为了让新旧机制和谐的运作,usb_hcd_poll_rh_status多了一个职务,就是作为rh_timer的定时器函数,而且struct usb_hcd里除了uses_new_polling之外就又多了poll_rh等几个元素。如果uses_new_polling没有被设置,则一定会采用旧的轮询机制,如果uses_new_polling已经被设置了,但同时又设置了poll_rh,也是要采用旧机制,否则采用的就是新机制。

至于status_urb,其实就是root hub提交的那个urb。

root hub的生命线就还是说到这里吧,那么我们的usb_hcd结构体也分析完了。

上一篇:hdu 4223 Dynamic Programming?


下一篇:WITH+HInt MATERIALIZE 不见得有效