Linux下的USB程序分析_linux下的usb程序分析

其他范文 时间:2020-02-27 21:46:26 收藏本文下载本文
【www.daodoc.com - 其他范文】

Linux下的USB程序分析由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“linux下的usb程序分析”。

Linux下的USB程序分析

关键字:USB、LINUX、设备驱动、总线 参考文献:

LINUX 设备驱动程序 ALESSANDRO RUBINI & JONATHAN CORBET

著 魏拥明 骆刚 姜君 译

USB 2.0 原理与工程开发 王成儒、李英伟 编著 LINUX 内核源代码情景分析 毛德操 胡希明 著

一、USB概述 1.USB的优点

USB与计算机的接口采用的不是传统的I/O模式,它和传统的通信接口相比,存在以下优点:

(1)热插拔:用户可以把USB外设直接连接到一台正在运行的计算机上,操作系统能自动识别,不用时可将USB在操作系统中卸载,不会损伤计算机

(2)即插即用:用户将USB设备插入后可立即使用(3)共享式接口:不同的USB外设使用同样的接口

(4)节省系统资源:只有USB主控制器需要使用一根IRQ线和一些I/O地址空间,对USB外设来说,它需要的仅仅是为其分配一个唯一的地址

(5)传输速率高;USB2.0的传输速率可达25MB/S以上(6)提供电源(7)兼容性强

2.USB系统描述

USB系统由USB主机和USB设备构成。

USB主机内部含有USB主控制器,负责完成主机和USB设备之间的物理数据传输。USB主机中还应有设备驱动程序。USB的数据驱动是基于令牌的,其所有的通信都由USB主机启动。

二、Linux设备驱动概述 1.设备驱动程序的作用

Linux是“单块结构”的操作系统,设备驱动作为设备控制模块中的一部分,在整个操作系统中扮演着非常重要的角色。设备驱动使某个特定的硬件响应一个良好定义的内部编程接口,同时完全隐藏了设备工作的细节。用户操作通过一组标准化的调用完成,而这些调用是和特定的驱动程序无关的。将这些调用映射到作用于实际硬件的设备特定的操作上,则是设备驱动程序的任务。这个编程接口能够使得驱动程序独立于内核的其它部分而建立,在需要的时候,可在运行时“插入”内核。

如果从另一个角度来看驱动程序,那么它可以被看作是应用和实际设备之间的一个软件层。这种定位使驱动程序具有了“个性化”的特点:即对于相同的设备,不同的驱动程序也可以提供不同的功能。

2.Linux内核功能划分

Linux内核从功能上分可以分为以下几个部分;进程管理: 进程管理功能负责创建和撤销进程以及处理它们和外部世界的连接(输入和输出)。不同进程之间的通信(通过信号、管道或进程见通信原语)是整个模块的基本功能。除此之外,控制进程如何共享CPU的调度程序也是进程管理的一部分。概括地说,内核的进程管理活动就是在单个或多个CPU上实现多个进程的抽象。内存管理:

内存是计算机的主要资源之一,用来管理内存的策略是决定系统性能的一个关键因素。内核在有限的可用资源上为每一个进程都创建了一个虚拟寻址空间,内核的不同部分和在内存管理子系统交互时使用一套相同的系统调用,包括从简单的malloc/free到对其它一些不常用的系统调用。文件系统:

Linux中的每个对象几乎都可以被看作文件。内核在没有结构的硬件上构造结构化的文件系统,所构造的文件系统抽象在整个系统中广泛使用。另外,Linux支持多种文件系统类型,即在物理介质上组织数据的不同方式。设备控制:

除了处理器、内存以及其他很有限的几个实体外,所有设备控制操作都由与被控制设备相关的代码来完成。这段代码就是设备驱动程序,内核必须为系统中的每件外设嵌入相应的驱动程序、包括硬盘驱动器、键盘、鼠标等。

3.设备和模块分类

Linux系统将设备分成三种类型:字符设备、块设备和网络接口。每个模块通常实现其中一种类型,相应地,模块可分为字符模块(char modual)、块模块(block modual)和网络模块(network modual)三种。

字符设备(character device)

字符设备是能够象字节流(比如文件)一样被访问地设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少需要实现open、close、read和write系统调用。字符终端(/dev/console)和串口(/dev/ttySO以及类似设备)就是字符设备的两个例子,他们能够用流很好地表示。字符设备可以通过文件系统节点(如/dev/tty1和/dev/lp0)来访问,它和普通文件之间的唯一差别在于,对普通文件的访问可以前后移动访问指针,而大多数字符设备是只能顺序访问的数据通道。然而,也存在和数据区特性类似的字符设备,访问它们时可前后移动访问指针。

块设备(block device)

和字符设备一样,块设备也是通过/dev目录下的文件系统节点被访问的。块设备(例如硬盘)上能够容纳文件系统。Linux允许应用程序象字符设备那样读写块设备,可以一次传递任意多字节的数据。因此,块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同。象字符设备一样,块设备也是通过文件系统节点被访问的,他们之间的差异对用户来说是透明的。块驱动程序除了给内核提供和字符驱动程序一样的接口以外,还提供了专门面向块设备的接口,不过这些接口对于那些从/dev目录下某个目录打开块设备的用户和应用程序都是不可见的。另外,块设备的接口必须支持挂装(mount)文件系统。

网络接口(network interface)

任何网络事务都要经过一个网络接口,即一个能够和其它主机交换数据的设备。通常接口是个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。网络接口由内核中的网络子系统驱动,负责发送和接收数据包,它无须了解每项事务是如何映射到实际传送的数据包的。尽管Telnet和FTP连接都是面向流的,它们都使用了同一个设备,但这个设备看到的只是数据包,而不是独立的流。

三、Linux下USB程序分析

USB总线的初始化和USB设备的枚举

首先,我们先看一下USB总线本身的初始化。USB控制器(连同根集中器)连接在PCI总线上,是一个PCI设备,在PCI总线的初始化过程中会受到枚举。PCI设备的初始化完成后,在PCI总线树中就游乐代表着具体USB总线控制器的PCI_DEV数据结构,并已为控制器的I/O区间和RAM区间分配和设置了总线地址。

在USB总线控制器的设备驱动程序方面,则要为其准备下一个PCI_DRIVER数据结构,其类型定义在include/linux/pci.h中: struct pci_driver { struct list_head node;char *name;const struct pci_device_id *id_table;int(*probe)(struct pci_dev *dev,const struct pci_device_id *id);void(*remove)(struct pci_dev *dev);void(*suspend)(struct pci_dev *dev);void(*resume)(struct pci_dev *dev);};

这个数据结构为通用的PCI设备管理机制提供了几个函数指针,特别是为一个通用的、一般化的PCI设备初始化过程提供了函数指针PROBE。供这个PCI设备的初始化过程叫“回叫”,以完成具体设备的初始化。对于遵循UHCI界面的USB控制器,其PCI_DRIVER数据结构为uhci_pci_driver,定义于drivers/usb/uhci.c:

static struct pci_driver uhci_pci_driver= { name: “usb-uhci”, id_table: &uhci_pci_ids [0],probe: uhci_pci_probe, remove: uhci_pci_remove, #ifdef CONFIG_PM suspend: uhci_pci_suspend, resume: uhci_pci_resume, #endif /*PM*/ };

结构中的指针id_table应该指向一个pci_device_id结构数组,表明由这个数据结构所确定的设备驱动程序适用于哪一些PCI设备。对此,drivers/usb/uhci.c中相应地定义了数组uhci_pci_ids[]: static const struct pci_device_id__devinitdata uhci_pci_ids []={{ /*handle any USBUHCI controller */ cla:((PCI_CLASS_SERIAL_USB

},{/* end:all zeroes*/} };从其定义可以看出,它适用于所有类型为PCI_CLASS_SERIAL_USB,子类型为0的PCI设备,即USB控制器,而不管是由哪一家厂商提供。

准备好这些数据结构以后,就可以通过inline函数pci_module_init()向系登记具体的设备驱动程序,并对设备进行初始化。其代码在include/linux/pci.h中。这里通过pci_register_driver()完成PCI设备驱动程序的登记和初始化,这就是前面所讲的通用、一般化的PCI设备初始化过程。这个函数返回一个计数,表示在PCI总线上找到了几个这样的设备。一般化的PCI设备初始化过程。这个函数返回一个计数,表示在PCI总线上找到了几个这样的设备。一般,如果这个函数返回0就要调用pci_unregister_driver()撤消登记,但是如果PCI总线允许“热插入”。即在加电后运行过程中带电插入设备,而驱动程序又并非通过可以安装模块实现,则不应该撤消登记;因为以后热插入此种设备仍需要执行这个驱动程序,应该保留着,以备插入此种设备之需。

如果一个设备的pci_dev结构尚未与任何驱动程序挂钩,并且其所有地址区都尚未启用,则pci_dev_driver()返回0。这样的pci_dev结构需要通过pci_announce_device()加以对比(drivers/pci/pci.c)。

可想而知,所谓比对是将具体设备的类型、厂家等等在PCI枚举阶段从设备收集的信息与USB驱动程序的数组uhci_pci_ids[]进行对比,具体由pci_match_device()完成的(drivers/pci/pci.c)如果比队结果相符,就找到了一个USB总线控制器,此时便通过驱动程序提供的函数指针probe对其进行初始化。对于遵循UHCI界面的USB总线控制器,这个函数是uhci_pci_probe(),其代码在drivers/usb/uhci.c中。

我们在前面曾经说USB设备没有向主机发出中断请求的能力,而只能等待,受主机的查询。但是这并不意味着USB控制器(在主机中)没有向CPU发出中断请求的能力,这是完全不同的两回事,不能混淆。事实上,USB控制器是有能力向CPU发出中断请求的,所以要接通它的中断请求线。

USB控制器本身带有微处理器,在USB总线上发送/接收的信息都由USB控制器通过DMA直接从内存读/写,主机的CPU只要提供缓冲区指针就可以了。而且,CPU也不需要逐次地为USB总线上的操作提供缓冲区指针,而只要把缓冲区指针纪录在相应的交互请求,或曰交互描述块就可以了。USB控制器自会顺着交互请求队列逐个地完成对这些缓冲区的操作。类似的DMA操作称为“智能化DMA”。其实,USB总线控制器的DMA操作甚至比一般的智能化DMA还要复杂,因为CPU为之准备的并不只是一个缓冲区队列,而是许多交互请求队列,称为一个“调度”。PCI设备(的接口)要进行DMA操作就得具有竞争成为“主总线”的能力。另一个方面,PCI设备的DMA功能还要服从CPU的统一管理,在PCI配置寄存器组的命令寄存器中有一个控制位PCI_COMMAND_MASTER,就是用来打开或关闭具体PCI设备竞争成为主总线主的能力。在完成PCI总线初始化时,所有PCI设备的DMA功能都是关闭的,所以这里要通过pci_set_master()启用USB控制器竞争成为主总线主的能力(drivers/pci/pci.c)。

数据结构中有几个特别重要的成分。首先是指针f1,指向一个uhci_framelist数据结构,这就是具体USB总线的“框架表”,这种数据结构也定义于drivers/usb/uhci.h: struct uhci_framelist{ __u32 frame[UHCI_NUMFRAMES];} __attribute__((aligned(4096)));

USB总线的框架表实际上是个指针数组,每个指针都指向一个等时交互队列。常数UHCI_NUMFRAME在同一个文件中定义为1024,所以整个数组代表着1024个框架。数组的起始地址必须与4K字节边界对齐,这样其起始地址的低12位就全都是0。USB控制器内部有个“框架表基地址寄存器”,用来记录这个基地址。同时,USB控制器内部还有个10位的“框架计数器”,这个计数器从0开始每过1毫秒(1/1024秒)就加一,直至0x3ff即1023,然后又变为0,如此周而复始。在框架计数的后面添上两位0,再与框架表的基地址连在一起,就成了指向框架表中某个表项的指针。在框架表中的每个表项都指向一个uhci_td结构的队列,每个uhci_td结构是对一个交互的描述,我们称之为“交互描述块”或“交互请求”。USB控制器在每个框架中首先就执行这个队列。每个等时交互队列的最后一个数据结构指向一个(实际上是一截)中断交互队列。中断交互队列与框架之间并不是一一对应的关系,uhci结构中uhci_td结构数组skeltd[],其中的每个元素都指向一截中断交互请求队列,常数UHCL_NUM_SKELTD定义为10,其中skeltd[0]是整个队列的终点,而skeltd[9]实际上不用,所以一共有8截这样的中断交互请求队列。这些中断交互请求队列又在链接在一起,成为一个总的中断交互请求队列。但是,链接在不同部位上的中断交互请求受到执行的频率是不一样的。当将一个代表着中断交互的uhci_td结构链入队列时,可以根据所要求的执行周期选择链入中断交互请求队列的不同部位。而skeltd[]中的各个元素,则起着链入点的作用,所以这个数组称为中断交互请求队列的“骨架”(skeleton)。这些链入点本身也是uhci_td结构,不过是空闲的uhci_td结构,USB控制器在执行时会自动跳过。此外,虽然中断交互请求队列并不是与框架一一对应,二者间还是有着某种静态的对应关系。

等时交互和中断交互都是周期性的,在每个框架中二者的流量加在一起不超过90%。这样,在每个框架中,USB控制器在执行完这两种交互请求以后总是还有一些时间(至少10%),可以用来执行控制交互以及成块交互。这两种交互都不是周期性的,其队列与框架没有静态的对应关系,USB控制器对这些队列的执行完全是动态的,有时间就执行,没有时间就不执行,时间多就多执行,时间少就少执行。在uhci结构中还有个uhci_qh结构数组skelqh[],数组中的每个元素都是一个队列头,用来维持一个“队列的队列”,或者说传输请求的队列。如前所述,每个传输请求是一个交互请求的队列。所以,这个数组是控制/成块传输请求队列的骨架,其大小是UHCI_NUM_SKELQH,在drivers/usb/uhci.h中定义为4。从逻辑上说,只要有两个链入点就够了,可是实际上USB设备有全速和低速之分,还有一个有着特殊的用途,所以共有4个。

因此,除还有其他一些成分以外,uhci数据结构实际上代表着主机CPU为一条USB总线排好的“日程表”,或者说执行程序,这就称为一个“调度”(schedule)。不言而喻,初始化时要为USB控制器分配、建立一个uhci数据结构。这是由alloc_uhci()完成的,其代码在drivers/usb/uhci.c中。

USB总线的根集中器总是与USB控制器集成在一起。对于UHCI界面的总线控制器,其I/O地址区间的前16个地址用于总线控制器本身,其余的就用于根集中器。根集中器的每个“端口”(port)占用两个地址。端口的数量则取决于具体的芯片,至少两个,最多八个。每个端口的状态寄存器中的bit7总是1,所以代码中通过一个循环试读,以确定根集中器中端口的数量。

前面讲过,uhci结构中的skeltd[]用于8截中断交互队列,是个uhci_td数据结构的数组。结构名中的“td”是“交互描述结构”(transaction descriptor)的意思。这种数据结构定义于drivers/usb/uhci.h: struct uhci_td{ /*Hardware fields*/ __u32 link;__u32 status;__32 info;__32 buffer;

/*Software fields*/ unsigned int *frameptr;struct uhci_td *prevtd, *nexttd;

struct usb_device *dev;struct urb *urb;

struct list_head list;}__attribute__((aligned(16)));

每个uhci_td数据结构代表着一个交互请求,就好象是对USB控制器的一条指令。结构中前4个32位长字的作用是由USB控制器的硬件结构所确定了的,因而不能改变;不过硬件只认开头这4个字段,其余的字段则由软件定义和使用。数据结构的起点必须与16字节的边界对齐,这也是USB总线控制器的硬件结构所要求的。这4个32位长字实际上相当于一组寄存器。我们把硬件使用的这一部分称为“交互描述块”,以区别于整个交互描述结构。结构头部的4个字段中,link是指向下一个uhci_td数据结构的连接指针,buffer则指向用于发送或接收的缓冲区,二者均为物理地址。此外,info就好象是命令寄存器,相当于指令中的操作码。内核在drivers/usb/uhci.c中提供了一个inline函数uhci_fill_td(),用于设置 uhci_td数据结构头部除link外的三个字段:

static void inline uhci_fill_td(struct uhci_td *td,__u32 status,__u32 info,__u32 buffer){ td->status = status;td->info = info;td->buffer = buffer;} 与目标设备建立连接的过程就是对目标设备的“枚举”。枚举过程的步骤如下:(1)为设备制订地址(2)从设备读入其usb_device_descriptor数据结构(3)从设备读入其所有的“配置”描述结构(4)枚举或改变设备的配置

USB设备的初始化

每个USB设备都有usb_driver数据结构,定义再include/linux/usb.h中: struct usb_driver{ const char * name;void *(*probe)(struct usb_device *dev, unsigned intf, const struct usb_device_id *id);void(*disconnect)(struct usb_device *,void *);struct list_head driver_list;struct file_operations *fops;int minor;struct semaphore serialize;int(*ioctl)(struct usb_device* dev,unsigned int code,void *buf);const struct usb_device_id *id_table;};

通过usb_scan_devices()扫描所有USB总线上的所有设备,让每个USB设备驱动模块都试着来“认领”与其对口的设备。

所谓“认领”就是使一个usb_interface_descriptor数据结构与相应的设备的usb_driver结构挂钩。这样,从具体设备的数据结构出发,就可以找到其设备驱动程序了。就这样,当usb_scan_devices()完成了对所有的USB设备的扫描时,对扫描器设备的登记和初始化就完成了。最后,以次设备号中的低4位为下标的指针数组p_scn_table[]记录着指向每个具体scn_usb_data结构的地址。

USB设备的驱动

所有的USB设备都有相同的主设备号USB_MAJOR,而根据次设备号划分具体的设备及其类型。所以,根据设备文件节点提供的主设备号,CPU首先找到USB总线的file_operations数据结构usb_fops,从中得到用于open操作的函数指针,这个指针指向usb_open()。

Static int usb_open(struct inode * inode,struct file *file){ int minor = MINOR(inode->i_rdev);struct usb_driver *c = usb_minors[minor/16];int err =-ENODEV;struct file_operations *old_fops,*new_fops = NULL;if(!c ||!(new_fops = fops_get(c->fops)))return err;old_fops = file->f_op;file->f_op = new_fops;if(file->f_op->open)err = file->f_op->open(inode,file);if(err){ fops_put(file->f_op);file->f_op = fops_get(old);} fops_put(old_fops);return err;} 然后,进一步根据次设备号从指针数组usb_minors[]中找到扫描器的usb_driver结构。

对于USB总线上的每个传输,需要为之创建一个“USB传输请求块”,即usb数据结构。发送控制报文的过程是:根据参数建立一个usb数据结构,把这个usb结构交给低层,让低层据以调度相应的控制传输,然后睡眠等待传输的完成。

对于采用UHCI界面的USB总线控制器,其usb_bus结构中的指针hcpriv指向一个uhci数据结构,而uhci结构中有个次层结构rh,里面是有关根集中器的信息,这是在根集中器初始化时设置好的。如果目标设备恰好就是根集中器,那么通信的过程可以简化,因为CPU直接就可以访问其各个寄存器,不需要通过USB总线就能进行通信,所以此时由rh_submit_urb()完成操作。

与其他所有USB设备的通信(传输)都要通过USB总线上的交互来完成,因而需要为具体的传输调度一个或几个交互。除等时传输之外,在调度中不允许同时存在对同一对象的两个同种传输。

下载Linux下的USB程序分析word格式文档
下载Linux下的USB程序分析.doc
将本文档下载到自己电脑,方便修改和收藏。
点此处下载文档

文档为doc格式

    热门文章
      整站推荐
        点击下载本文