一,USB设备规范:
USB硬件层由USB设备,USB总线,USB主机控制器组成;
USB驱动层由USB设备侧驱动程序,USB主机控制器驱动程序,USB核心,USB宿主机上的设备驱动程序组成;
如下图(图片来自USB规范)图一为USB体系拓扑结构,图二为USB系统数据流模型:
图一:
图二:
二,Linux 中USB数据协议的实现:
数据结构:
<linux/usb.h>
端点: struct usb_host_endpoint;
端点描述符: struct usb_endpoint_descriptor;
接口(设备功能<->逻辑设备): struct usb_interface;
接口设置(设备交互设置): struct usb_host_interface;
接口描述符: struct usb_interface_descriptor;
配置: struct usb_host_config;
配置描述符: struct usb_config_descriptor;
设备: struct usb_device;
设备描述符: struct usb_device_descriptor;
数据结构之间的关系,如下图,用面向对象的方式来表达数据结构之间的关系更为容易理解:
三,Linux USB驱动程序实现:
驱动程序组成结构:
设备侧驱动程序(USB器件驱动程序):写在USB设备芯片中的固件程序;
Linux主机 USB主机控制器驱动程序:USB主机控制器驱动程序(UHCI,EHCI,OHCI,xHCI)UHCI芯片由intel开发对应的驱动程序模块为uhcd,USB主机控制器是一个PCI设备连接在系统PCI总线上,通常集成在南桥芯片中,USB主机控制器也称为根HUB,一个主机可以有一个或多个USB主机控制器,主机通过系统PCI总线和USB主机控制器通信,USB主机控制器通过USB总线与设备通信;一个USB总线系统只能有唯一的一个主设备即为USB主机控制器,其他设备皆为从设备,所有的从设备与主设备进行点对点通信,从设备只能通过主设备与另一个从设备通信;
Linux主机 USB总线驱动核心:处理所有复杂的USB协议相关的逻辑,为设备驱动程序层提供接口,并调用主机控制器驱动程序将处理过的数据传递给主机控制器驱动程序;
Linux主机 USB设备驱动程序:驱动设备上的某个功能(例如:有声卡和LED的扬声器,有两个设备驱动程序,声卡驱动和LED驱动),即USB设备驱动程序与USB设备接口绑定;
Linux USB设备驱动程序数据结构:
头文件:<linux/usb.h>
数据结构及数据结构的主要成员:
struct usb_driver{
char *name;
struct usb_device_id *id_table;
int (*probe)(struct usb_interface,struct usb_device_id *id);
void (*disconnect)(struct usb_interface);
... ...
}
URB:USB request block,USB驱动程序向USB核心发送的请求数据块,类似与网络客户端程序向Web服务器发送的request请求;
struct urb {
struct usb_device *dev; //设备;
unsigned int pipe; //端点,必须用内核提供的初始化api接口初始化;
unsigned int transfer_flags; //传输标志位,例如,URB_NO_TRANSFER_DMA_MAP标志位被设置表示用DMA缓冲区发送或接受数据,USB核心使用transfer_dma指向的缓冲区,而不是transfer_buffer指向的缓冲区;
void *transfer_buffer; //用来接受或发送数据的缓冲区,为了主机控制器能够正确的存取这个缓存,必须用kmalloc调用来创建该缓冲区;
int transer_buffer_length; //缓冲区的大小;
dma_addr_t transfer_dma; //当transer_flags标志位被设置时使用该dma缓冲区存取数据;
usb_complete_t complete; //当urb生命周期结束时(成功结束或错误结束)执行该函数,这个完成函数是在中断处理例程中执行,因此不能有调度和延时;
void *context; //urb请求的私有数据;
int actual_length; //实际传输的数据大小;
int status; //结束时的状态,成功结束则该状态值为0,负责是错误码;
}
urb的生命周期是Linux USB设备驱动程序的核心,也是USB设备驱动程序的逻辑:
1,驱动程序分配并初始化urb,将urb提交给usb核心,如果usb核心接受成功则返回0,否则返回错误码;
2,usb核心对urb进行协议相关的操作,然后调用主机控制器驱动并将数据传递给主机控制器驱动;
3,主机控制器驱动再对数据进行相应的操作之后,将数据传递给USB总线根HUB接口;
4,USB总线系统硬件将数据传送给USB设备;
5,USB设备将请求数据传送给主机,主机USB核心调用urb完成函数,并将控制权交给USB设备驱动程序;
6,USB设备驱动程序在urb完成函数中对接受到的数据进行最后的逻辑处理;
以简单的鼠标驱动程序(仅实现打印鼠标位置)为例来说明USB设备驱动程序核心逻辑——urb生命周期:
1,探测函数:
static int usb_probe(struct usb_interface *intf,struct usb_device_id *id){
//1,获取usb设备,接口设置,端点;
struct usb_mouse *umouse=null;//保存鼠标设备信息;
struct usb_device *udev=interface_to_udev(intf);
struct usb_host_inteface *interface=intf->cur_altsetting;
struct usb_host_endpoint *endpoint=interface->endpoint;
//2,判断端点数目和的端点类型;
if(interface->bNumEndpoints!=1){
return -ENODEV;
}
if((endpoint->desc.bmAttribute&USB_ENDPOINT_XFER_MASK)!=USB_ENDPOINT_XFER_INT){
return -ENODEV;
}
//3,获取端点信息;
umouse=(struct usb_mouse *)kmalloc(sizeof(struct usb_mouse),GFP_KERNEL);
umouse->size=endpoint->desc.wMaxPacketSize;
umouse->pipe=usb_rcvintpipe(intf,endpoint->desc.bEndpointAddress);
umouse->mInterval=endpoint->desc.mInterval;
//4,分配URB;
umouse->irq=usb_alloc_urb(0,GFP_KERNEL);
//5,初始化URB;
umouse->data=usb_buffer_coherent(intf,umouse->size,&umouse->dma_addr);
urb_fill_int_in(umouse->irq,udev,umouse->pipe,umouse->data,umouse->size,urb_complete_handle,umouse,umouse->mInterval);
umouse->irq->transfer_flags|=USB_NO_TRANSFER_DMA_MAP;
umouse->irq->transfer_dma=umouse->dma_addr;
umouse->usddev=udev;
//6,设置usb接口私有数据;
usb_set_intfdata(intf,umouse);
//1,输入设备分配;
//2,输入设备设置;
//3,输入设备注册;
//4,设置输入设备私有数据;
return 0;
}
static void usb_disconnect(struct usb_interface *intf){
//与探测函数probe相反的顺序解除所分配的所有资源;
}
输入设备的操作:
打开:
static int input_open(struct input_dev *dev){
struct usb_mouse *umouse=input_get_drvdata(dev);
if(usb_submit_urb(umouse->irq){
//提交失败;
}
return 0;
}
关闭:
static void input_close(struct input_dev *dev){
struct usb_mouse *umouse=input_get_drvdata(dev);
usb_kill_urb( umouse->irq);
}
urb请求结束处理函数:
static void urb_complete_handle(struct urb *urb){
struct usb_mouse *umouse=urb->context;
signed char *data=umouse->data;
int status=umouse->irq->status;
//根据状态判断是否成功完成操作,状态status为0则成功完成,否则status为错误码;
//如果成功则打印鼠标坐标数据:data数组;
usb_submit_urb(umouse->irq,GFP_ATOMIC);
}