1、 Linux中的USB设备驱动
我们再看看下面的图,我们基本了解了一下EHCI和如何将EHCI驱动起来,上EHCI驱动上面是USB核心,这一块是USB中最复杂的一块了,所幸他是与硬件无关的,作为一个普普通通的驱动工程师,只需要知道他提供了什么样的接口,以及如何使用这些接口,我们甚至不需要知道USB是如何工作的,只要放心大胆的使用这些USB核心层的API,把USB当作通路来使用。
当然这只是理想的状态,所谓的理想就是永远也无法实现的现实,当调试USB时我们还是需要从头到尾的把USB协议研究一遍。
只有站在USB核心层上,我们才能清晰的看到一般USB书籍中提到的端点,接口,通道和四种传输内型(控制传输,中断传输,批量传输,等时传输)等这些逻辑上的概念。
在linux的架构中,有一种数据数据叫URB封装了所有了这些概念,作为USB设备驱动,要使用USB通路来传递数据,我们只要操控URB就可以了。
| typedef struct urb //USB Request Block,包含了建立任何 USB传输所需的所有信息,并贯穿于USB协议栈对数据处理的整个过程 { spinlock_t lock; & nbsp; // lock for the URB void *hcpriv; // private data for host controller与主机控制器相关数据,对USB内核层是透明 struct list_head urb_list; // list pointer to all active urbs双向指针,用于将此URB连接到处于活动的URB双向链表中 struct urb *next; // pointer to next URB 指向下一个URB的指针 struct usb_device *dev; // pointer to associated USB device 接受此URB的USB设备指针 unsigned int pipe;// pipe information表示设备的某个端点和客户端驱动程序之间的管道 int status; ; ; // returned status 返回状态 unsigned int transfer_flags; // USB_DISABLE_SPD | USB_ISO_ASAP | etc. USB_DISABLE_SPD //拒绝短数据包,即比最大传输包长度小的数据包 USB_ISO_ASAP //用于实时传输,告诉主机控制器立即进行此请求的数据传输。如果没有置位,则需要给start_frame赋值,用来通知主机控制器该在哪个帧上开始此请求的数据传输 USB_ASYNC_UNLINK //告诉USBD采用异步方式取消请求 USB_QUEUE_BULK //表示批量请求可以排队,一般一个设备的批量请求端点只有一个URB USB_NO_FSBR //表示全速传输站用的带宽不要回收 USB_ZERO_PACKET //表示批量传输的数据长度等于端点的包最大长度时,主机控制器在发送完数据后,再发送一个零长度的包表示数据结束 USB_TIMEOUT_KILLED //本位只被HCD设置,表示发生了超时。客户驱动可以给URB的处理设置一个超时时间,如果处理超时,则要求USBD结束对此URB的处理,URB的返回信息中会反映该此状态。 void *transfer_buffer ; ; // associated data buffer传输数据缓存区指针,接收或发送设备的数据,它必须是物理连续的,不可换页的内存块,用kmalloc(,GFP_KERNEL)分配 int actual_length; // actual data buffer length 实际数据长度 int bandwidth; //此请求每次占用一帧的带宽,只适用实时/中断传输 int start_frame; //此请求所开始传输的帧号,只适用实时/中断传输。中断传输时,表示返回启动此请求的第一次中断传输的帧号。实时传输时,指明处理第一个实时请求数据报包的帧号,如果设置了USB_ISO_ASAP,此变量表示返回启动第一次实时传输的帧号。 int number_of_packets; // number of packets in this request (iso)此请求所包含的数据包数,只适合实时传输 int interval; // polling interval (irq only) 中断传输的周期,1〈= interval〈=255 int error_count; // number of errors in this transfer (iso only)发生传输错误次数的累加值,只适用实时传输 int timeout; // timeout (in jiffies) void *context; // context for completion routine回调函数中的参数 usb_complete_t complete; // pointer to completion routine 指向回调函数的指针。当数据传输完成后,主机控制器驱动会回调该函数。 iso_packet_descriptor_t iso_frame_desc[0]; 要进行实时传输的结构数组,每个结构表示一次数据传输 } |
上表中就是URB的具体内容,URB对USB协议解析得己经很清楚了,但是还是很复杂,我们需要更要更有利的工具,内核己经提供了这类操作URB的工具:
| usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) | 初始化控制URB结构,pipe是端点通道,setup_packet指向setup_packet数据,transfer_buffer指向transfer数据, | |
| usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) | 初始化块传输的URB | |
| usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval) | 初始化中断传输的URB | |
| usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout); | ||
| usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); | ||
| usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); | ||
| extern void usb_init_urb(struct urb *urb); extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags); extern void usb_free_urb(struct urb *urb); extern struct urb *usb_get_urb(struct urb *urb); extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags); extern int usb_unlink_urb(struct urb *urb); extern void usb_kill_urb(struct urb *urb); extern void usb_poison_urb(struct urb *urb); extern void usb_unpoison_urb(struct urb *urb); extern void usb_kill_anchored_urbs(struct usb_anchor *anchor); usb_poison_anchored_urbs(struct usb_anchor *anchor); extern void usb_unpoison_anchored_urbs(struct usb_anchor *anchor); extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor); extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor); extern void usb_unanchor_urb(struct urb *urb); extern int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor, unsigned int timeout); extern struct urb *usb_get_from_anchor(struct usb_anchor *anchor); extern void usb_scuttle_anchored_urbs(struct usb_anchor *anchor); extern int usb_anchor_empty(struct usb_anchor *anchor); |
对于pipe的创建及操作,内核中也有定义:
| #define PIPE_ISOCHRONOUS 0 #define PIPE_INTERRUPT 1 #define PIPE_CONTROL 2 #define PIPE_BULK 3 #define usb_pipein(pipe) #define usb_pipeout(pipe) #define usb_pipedevice(pipe) #define usb_pipeendpoint(pipe) #define usb_pipetype(pipe) #define usb_pipeisoc(pipe) #define usb_pipeint(pipe) #define usb_pipecontrol(pipe) #define usb_pipebulk(pipe) #define usb_gettoggle(dev, ep, out) #define usb_dotoggle(dev, ep, out) #define usb_settoggle(dev, ep, out, bit) #define usb_sndctrlpipe(dev,endpoint) #define usb_rcvctrlpipe(dev,endpoint) #define usb_sndisocpipe(dev,endpoint) #define usb_rcvisocpipe(dev,endpoint) #define usb_sndbulkpipe(dev,endpoint) #define usb_rcvbulkpipe(dev,endpoint) #define usb_sndintpipe(dev,endpoint) #define usb_rcvintpipe(dev,endpoint) |
上面这些工具都是usb核心层提供给我们的,我们只需在逻辑层上把USB看作一个个的pipe就可以了,USB从设备中也会有这样的一些概念,我们其实不是与从设备的硬件直接打交道,而是和从设备中的USB固件(usb从控制器的驱动)打交道。
设备驱动想要使用usb总线和设备通信,一般先要初始化urb结构,把你所想要传送的数据用系统提供的urb操作工具填入urb中,然后用 usb_submit_urb向usb核心提交。
我们想要了解usb设备驱动层的数据是如何到达USB主控制器并发送到总线上去的,usb_submit_urb是一个很好的突破口。
usb_submit_urb中全调用usb_hcd_submit_urb,usb_hcd_submit_urb会找到预先指定的控制器驱动,即调用hcd->driver->urb_enqueue(),对ehci控制器而言, urb_enqueue就是ehci_hcd.c中的ehci_urb_enqueue(),数据走到ehci_urb_enqueue(),接下来的事情我们就能很清楚了,我们前介绍过itd/sitd/qh/qtd/fstn这几种在ehci sepc规范中定义的数据模型,也介绍了这几种数据模型在linux kernel中的表示,一句话,ehci_urb_enqueue()要作的事就是把设备驱动层交给urb的数据填充到itd/sitd/qh/qtd/fstn这几种数据结构中,并将其链成调度表。
我们来看看这个函数的代码:
| static int ehci_urb_enqueue ( struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags ) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct list_head qtd_list; INIT_LIST_HEAD (&qtd_list); switch (usb_pipetype (urb->pipe)) { case PIPE_CONTROL: /* qh_completions() code doesn't handle all the fault cases * in multi-TD control transfers. Even 1KB is rare anyway. */ if (urb->transfer_buffer_length > (16 * 1024)) return -EMSGSIZE; /* FALLTHROUGH */ /* case PIPE_BULK: */ default: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return submit_async(ehci, urb, &qtd_list, mem_flags); case PIPE_INTERRUPT: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return intr_submit(ehci, urb, &qtd_list, mem_flags); case PIPE_ISOCHRONOUS: if (urb->dev->speed == USB_SPEED_HIGH) return itd_submit (ehci, urb, mem_flags); else return sitd_submit (ehci, urb, mem_flags); } } |
接触usb的人都知道USB的传输分为中断传输,控制传输,批量传输,等时传输。基本上所有的人都知道这几种传输的概念上的区别,但却没几个人能了解这种区别的具体实现,以及形成区别的原因,等时传输和中断传输为什么会有时效性,批量传输和等时传输在实现在有什么区别呢。
USB设备驱动URB数据直到流到了 ehci_urb_enqueue才各回各家,各找各妈。
控制传输数据由submit_async处理进入了qtd队列;
中断传输数据由intr_submit处理被打包成qtd挂在itd队列上。
等时数据则由itd_submit处理,打包成itd队列。如果是支持低/全速设备,还有一个sitd_submit的处理,生成sitd队列。
列表准备好了后,乘下的就是要对寄存器操作了,以submit_async为例,这个关键的动作由qh_link_async()这个函数完成:
| static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { __hc32 dma = QH_NEXT(ehci, qh->qh_dma); struct ehci_qh *head; /* (re)start the async schedule? */ head = ehci->async; timer_action_done (ehci, TIMER_ASYNC_OFF); if (!head->qh_next.qh) { u32 cmd = ehci_readl(ehci, &ehci->regs->command); if (!(cmd & CMD_ASE)) { /* in case a clear of CMD_ASE didn't take yet */ (void)handshake(ehci, &ehci->regs->status, STS_ASS, 0, 150); cmd |= CMD_ASE | CMD_RUN; ehci_writel(ehci, cmd, &ehci->regs->command); ehci_to_hcd(ehci)->state = HC_STATE_RUNNING; /* posted write need not be known to HC yet ... */ } } /* clear halt and/or toggle; and maybe recover from silicon quirk */ if (qh->qh_state == QH_STATE_IDLE) qh_refresh (ehci, qh); /* splice right after start */ qh->qh_next = head->qh_next; qh->hw_next = head->hw_next; wmb (); head->qh_next.qh = qh; head->hw_next = dma; qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ } |
在组织完qtd队列后,我们就把ehci的控制COMMAND寄存器的 CMD_ASE 和 CMD_RUN字段使能,ehci就开始调度(运行)了。
这里我们并没有看见让ASYNCLISTADDR指向qh队列头,这件事其实早就做好了,看下面的函数:
| usb/host/ehci_hcd.c | |
| /* start HC running; it's halted, ehci_init() has been run (once) */ static int ehci_run (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); ...... if ((retval = ehci_reset(ehci)) != 0) { ehci_mem_cleanup(ehci); return retval; } ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); |
至此,看到对ASYNCLISTADDR和FRINDEX两个寄存器的操作,所有与EHCI控制器有关的疑问都应该解决了。我们可以放心的在设备驱动中使用usb总线了。
从设备中往往会有endpoint和pipe的概念,USB主机设备驱动层也使用endpoint和pipe的概念,这样主从之间就对上话了,通讯就没有问题了。
一般而言,数据通路被协议还可以理解,但USB规范中,设备驱动也被协议了。
我们看看前面那张表
| 1-audio:表示一个音频设 备。 | |
| 2-communication device:通讯设备,如电话,moden等等。 | |
| 3-HID:人机交互设备,如键盘,鼠标等。 | |
| 6-image图象设备,如扫描仪,摄像头等,有时数码相 机也可归到这一类。 | |
| 7-打印机类。如单向,双向打印机等。 | |
| 8-mass storage海量存储类。所有带有一定存储功能的都可以归到这一类。如数码相机大多数都归这一类。 | |
| 9-hub类。 | |
| 11-chip card/smart card。 | |
| 13 --Content Security | |
| 14--Video (Interface) | |
| 15--Personal Healthcare | |
| 220--Diagnostic Device | |
| 224--Wireless Controller (Interface) | |
| 239--Miscellaneous | |
| 254--Application Specific (Interface) | |
| 255-vendor specific.厂家的自定义类,主要用于一些特殊的设备。如接口转接卡等。 |
上表中的设备和接口在USB规范中都有规定,这可能是USB相关的东西调试起来很复杂吧,不像I2C/PCI那样好理解,所以干脆USB社区连设备的规范都定了下来,这样就可以生产出许多免驱动的设备了。
我们先看看linux中的usb设备的描述
| struct usb_device { ; //代表一个USB设备 enum { USB_SPEED_UNKNOWN = 0, /* enumerating */ USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ USB_SPEED_HIGH /* usb 2.0 */ } speed; //设备速度,低速/全速/高速 struct usb_device *tt; /* usb1.1 device on usb2.0 bus */,事务处理解释器 int ttport; ; /* device/hub port on that tt */设备所连接的具有事务处理解释器功能的集线器端口 atomic_t refcnt; ; /* Reference count */引用计数 struct semaphore serialize; //用于同步 unsigned int toggle[2] ; ; /* one bit for each endpoint ([0] = IN, [1] = OUT) */用于同步切换的位图,每个端点占用1位,[0]表示输入,[1]输出 unsigned int halted[2]; /* endpoint halts; one bit per endpoint # & direction; [0] = IN, [1] = OUT */表示端点是否处于停止状态的位图 int epmaxpacketin[16] ; ;/* INput endpoint specific maximums */输入端点的最大包长 int epmaxpacketout[16] ; ; /* OUTput endpoint specific maximums */输出端点的最大包长 struct usb_device *parent; //表示设备所连的上游集线器指针 struct usb_bus *bus; /* Bus we're part of */设备所属的USB总线系统 struct usb_device_descriptor descriptor;/* Descriptor */ 设备描述符 struct usb_config_descriptor *config; /* All of the configs */指向设备的配置描述符和其所包含的接口描述符,端点描述符的指针 struct usb_config_descriptor *actconfig;/* the active configuration */当前的配置描述符指针 char **rawdescriptors; ; ;/* Raw descriptors for each config */ int have_langid; /* whether string_langid is valid yet *// 是否有string_langid int string_langid; /* language ID for strings */和字符描述符相关的语言ID void *hcpriv; ; ; /* Host Controller private data */设备在HCD层占用的资源指针,对USB内核层是透明的 /* usbdevfs inode list */ 设备在usbdevfs中的inode列表 struct list_head inodes; struct list_head filelist; /* * Child devices - these can be either new devices * (if this is a hub device), or different instances * of this same device. * * Each instance needs its own set of data structures. */只对当前设备是集线器的情况有效 int maxchild; /* Number of ports if hub */ hub的下游端口数 struct usb_device *children[USB_MAXCHILDREN]; hub所连设备指针 }; struct usb_bus { //USB总线系统 int busnum; ; ; /* Bus number (in order of reg) */当前总线系统的序列号,Linux支持多总线系统并为它们编号 #ifdef DEVNUM_ROUND_ROBIN int devnum_next; /* Next open device number in round-robin allocation */ #endif /* DEVNUM_ROUND_ROBIN */给连接到子系统上的设备分配设备号的数据结构 struct usb_devmap devmap; /* Device map */给连接到子系统上的设备分配设备号的数据结构 struct usb_operations *op; /* Operations (specific to the HC) */HCD为USB内核提供的一系统函数集指针 struct usb_device *root_hub; /* Root hub */指向根Hub的指针 struct list_head bus_list; 双向链表指针,USB内核用一个双向链表来维护系统中所有USB总线系统 void *hcpriv; /* Host Controller private data */与主机控制器相关数据,对USB内核层是透明 int bandwidth_allocated; /* on this Host Controller; applies to Int. and Isoc. pipes; measured in microseconds/frame; range is 0..900, where 900 = 90% of a 1-millisecond frame */当前子系统的带宽使用情况,单位是微秒/帧,取值范围[0,900] int bandwidth_int_reqs; ; /* number of Interrupt requesters */子系统中当前的中断传输的数量 int bandwidth_isoc_reqs; /* number of Isoc. requesters */子系统中当前的实时传输的数量 /* usbdevfs inode list */ 在usbdevfs中的inode列表 struct list_head inodes; atomic_t refcnt; }; struct usb_driver { //客户端驱动程序为USB内核提供的调用接口 const char *name; //客户端驱动程序的字符串名称,用于避免重复安装和卸载 void *(*probe)(//给USB内核提供的函数,用于判断驱动程序是否能对设备的某个接口进行驱动,如能则分配资源 struct usb_device *dev, ; ;/* the device */ unsigned intf, ; ; /* what interface */ const struct usb_device_id *id /* from id_table */ ); void (*disconnect)(struct usb_device *, void *);//给USB内核提供的函数,用于释放设备的某个接口所占用的资源 struct list_head driver_list;//对应的双向指针,USB内核通过一个双向指针链表维护USB子系统中所用的客户端驱动程序 struct file_operations *fops; int minor; 驱动的次版本号 struct semaphore serialize; /* ioctl -- userspace apps can talk to drivers through usbdevfs */ int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf); /* support for "new-style" USB hotplugging * binding policy can be driven from user mode too */ const struct usb_device_id *id_table; /* suspend before the bus suspends; * disconnect or resume when the bus resumes */ // void (*suspend)(struct usb_device *dev); // void (*resume)(struct usb_device *dev); }; |
看来,USB设备驱动其实走的也是老套路,重点还是要放在与设备的通讯上。
与设备相关的,usb规范又定义了许多东西,协议中把这些叫描述符,我觉得应该叫配置更合理些。如下图,设备的配置,配置(Configuration)的配置,接口的配置,端点的配置。
![]() |
USB规范把设备分成了许多类,特定类(class)的设备又可划分成子类描述符(subclass),划分子类的后软件就可以搜索总线并选择所有它可以支持的设备,一个设备只有一个(Device)描述符,它指明了设备所属的类,
每个设备可以有一个或多个配置描述符(Configuration),配置描述符用于定义设备的功能。如果某个设备有几种不同的功能,则每个功能都需要一个配置描述符。配置描述符(configuration)是接口描述符(interface)的集合。接口指定设备中的哪些硬件与USB交换数据。每一个与USB交换数据的硬件就叫做一个端点描述符(endpoint)。因此,接口是端点的集合。USB的设备类别定义(USB Device Class Definitions)定义特定类或子类中的设备需要提供的缺省配置、接口和端点.
USB设备驱动中在打通数据通路后,就要理清各种配置了,根据这些配置再与设备作下一步的交流。
USB设备的识别过程
有了上面的准备知识,我们可以看一看当一个USB设备接入后是如何被识别的。
USB系统是一种主从结构,所有的通讯都是host发起的,从设备永远处于被动状态,但在主设备还是需要一个中断来唤醒。
1)如果是OTG设备,接入USB从设备时,USB-ID线被拉低,OTG中断触发,OTG将控制器切换到host状态。
2)当控制器处于host状态开处于待机时(otg的ADP,ARP完成),因为设备的D+和D-线上有特殊的处理,低速设备上的D-上有下拉电阻,高速和全速设备的D+上有上拉电阻,集线器检测到这个变化上报中断给探制器。
3)主机使用0地址通道来向总线上的新设备进查询,取得设备的一些基本的信息。
4)主机发取得设备的基本信息后发出复位指令,并给设备分配一个地址。设备复位并有了新的地址。
5)主机用新的地址继续查询设备的各种描述符(配置),主机通过设备的这些配置为设备在主机上寻找一个驱动。
6)一旦找到设备的驱动程序,枚举过程就完成了,控制权交到设备驱动的手上,设备在设驱动的控制下工作。
上面的交互过程其实很复杂,还可以细分。我们需要注意的是在主机给设备分配地址前设备使用的是0号地址,这是个公共地址,任何设备在刚接入的时该都会使用这个地址,并且一次只能有一个设备在使用0地址,主要发出复位指令时才会给设备分配一个新的地址。设备枚举的过程主要是对设备的各种描述符进行解析,最后找到设备驱动程序。
USB设备驱动和USB gadge驱动应该是我们接触得最多的与usb相关的驱动,这也是初学者很容易混淆的两个概念,再看下面这张图。
![]() |
从图中可以看出,USB设备驱动和USB gadge驱动处在平等对话的位置,usb主机侧的usb核心层和USB设备侧的gadge层都是为了给这两者能够对上话提供服务。我们完全可以把USB核 心层和gadge层以下的部份当作一个黑盒子,这样USB设备驱动就和其它的设备驱动没有什么区别了,只是数据通路走的是USB总线。
在linux中,使用usb总线作为数据通路最重要的一点就是控制urb,在驱动层,对usb的控制就是操控urb,控制urb的api前面己经列举过,有两点需要特别注意,一是等时传输的urb要手动初始化。二是中断传输的urb中有一个回调函数,这个回调函数会在有中断传输数据到达时被usb核心调用。内核中有一个drivers/usb/usb-skeleton.c的例子,是一个很好的参照。
usb-gadge就要稍稍复杂一点,除了通路的问题,你可能还要准备好各种配置(设备描述符,配置描述符,端点描述符等)。最后不要忘了,USB设备驱动和gadge驱动有本质的区别,USB设备驱动是运行在主机侧,向用户层提供设备接口,而usb-gadge驱动则是作为一个设备的固件运行在设备侧。这是最基本的,如果这一点不清楚最好先不要碰USB。
usb-gadge驱动的技术和usb设备固件技术有很多相同之处,这也是中国研发人员投入精力最多的地方,市面上的很多USB的资料都会讲这一块,也只会讲这一块,很多资料中提到的USB控制器其实都是从控制器,用来设计USB设备的,这一点要特别注意。
注:转载请注明出处 datangsoc@hotmail.com
本文深入探讨了 Linux 中 USB 设备驱动的工作原理,详细解释了 URB 的结构及其操作 API,介绍了 USB 传输类型及设备识别过程。此外,还对比了 USB 设备驱动与 USB gage 驱动的不同。



1872

被折叠的 条评论
为什么被折叠?



