Linux之USB协议及相关必要结构体释义
USB:Universal Serial Bus,意为通用串行总线。
由来:USB最初是为了替代许多不同的低速总线(包括并行、串行和键盘连接)而设计的,它以单一类型的总线连接各种不同的类型 的设备。
发展:USB的发展已经超越了这些低速的连接方式,它现在可以支持几乎所有可以连接到PC上的设备。
USB设备通信,包括:配置(configuration)、接口 (interface)、端点(endpoint)、管道(pipe)。
USB传输类型,分为: 控制传输(control)、中断传输(interrupt)、等时传输(isochronous)、批量传输(bulk)。
其中控制传输和批量传输又称为非周期性传输方式(nonperiodic),而中断传输和等时传输称为周期性传输方式(periodic)。
批量传输:主要应用在数据大量数据传输和接受数据上同时又没有带宽和间隔时间要求的情况下。特点:要求保证传输。打印机和扫描仪属于这种类型这种类型的设备。
控制传输:USB系统软件用来主要进行查询配置和给USB设备发送通用的命令。特点:双向传输,数据量通常较小;数据传送是无损性的。
中断传输:主要用于定时查询设备是否有中断数据要传输。特点:设备的端点模式器的结构决定了它的查询频率从1到255ms之间,如鼠标键盘等。
同步传输:用于时间严格并具有较强容错性的流数据传输,或者用于要求恒定的数据传输率的即时应用中。特点:保证传输的同步性、保证每秒有固定的传输量,如网络电话。
概述:usb总线是一种轮询式总线,协议规定所有的数据传输都必须由主机发起,usb主机与设备之间是通过管道(pipe)传输的,管道两边分别对应主机中的数据缓冲区和设备侧的端点(endpoint),端点是通信的发送和接收点,要发送数据,只要把数据发到对应的端点就可以,而这个数据发送的动作由usb主机实现,驱动中只需确定接收端点,然后把数据提交给主机控制器,主机会把数据发送给接收端点,原理同i2c,uart类似。每个usb设备中都存在一个特殊端点endpoint0,在usb设备枚举过程里,就是通过endpoint0来获取usb设备信息。
端点(ENDPOINT):每一个USB设备在主机看来就是一个端点的结合,主机只能通过端点与设备进行通信,以使用设备的功能;每一个端点实际上就是一个一定大小的数据缓冲区。
管道(PIPE):一个USB管道是驱动程序的一个数据区缓冲与一个外设端点的连接,它代表了一种在两者之间移动数据的能力。一旦设备被配置,管道就存在了。
端点(endpoint): U8
总共8位,0,1,2,3四位表示端点号,第7位表示端点方向。
D7 表示传输方向 1 为输入
D6~D4 reserved
D3~D0 为端点号 端点号为 01
管道(pipe):int型
bit:31-30:为管道传输的流类型。 00: isochronous flow 01: interrupt flow 02: control flow 03: bulk flow
bit:29-20:暂未使用,为0
bit:19-16: endpoint 地址 ,endpoint共4位, bit3-0:endpoint编号
bit15:暂未使用,为0
bit:14-8:usb device number
bit7表示传输方向 为0,表示从host到device; 为1:表示从device到host
bit:6-0:暂未使用,为0
在urb发送到usb核心之前,这个变量必须被usb驱动初始化。
为了设置这个pipe,驱动有一些初始化函数,这里要注意,每个pipe只可能是其中一种类型:
unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint) //指定一个控制 OUT 端点给特定的带有特定端点号的 USB 设备.
unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint) //指定一个控制 IN 端点给带有特定端点号的特定 USB 设备.
unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint) //指定一个块 OUT 端点给带有特定端点号的特定 USB 设备
unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint) //指定一个块 IN 端点给带有特定端点号的特定 USB 设备
unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint) //指定一个中断 OUT 端点给带有特定端点号的特定 USB 设备
unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint) //指定一个中断 IN 端点给带有特定端点号的特定 USB 设备
unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint) //指定一个同步 OUT 端点给带有特定端点号的特定 USB 设备
unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint) //指定一个同步 IN 端点给带有特定端点号的特定 USB 设备
URB:USB request block, 用来描述与USB设备通信所用的基本载体和核心数据结构。
事实上,可以打一个这样的比喻,usb总线就像一条高速公路,货物、人流之类的可以看成是系统与设备交互的数据,而urb就可以看成是汽车。USB的endpoint有4种不同类型,也就是说能在这条高速公路上流动的数据就有四种。但是这对汽车是没有要求的,所以urb可以运载四种数据,不过你要先告诉司机你要运什么,目的地是什么。
路径:include/linux/usb.h
struct urb { /* 私有的:只能由USB核心和主机控制器访问的字段 */
struct kref kref; /*urb引用计数 */
spinlock_t lock; /* urb锁 */
void *hcpriv; /* 主机控制器私有数据 */
int bandwidth; /* INT/ISO请求的带宽 */
atomic_t use_count; /* 并发传输计数 */
u8 reject; /* 传输将失败*/
/* 公共的:可以被驱动使用的字段 */
struct list_head urb_list; /* 链表头*/
struct usb_device *dev; /* 关联的USB设备 */
unsigned int pipe; /* 管道信息 */
int status; /* URB的当前状态 */
unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ,这个变量可被设置为不同的位值,根据这个位,usb驱动可以设置urb传输的状态*/
void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 *,指向缓冲区的指针,这个指针可以是OUT urb 或者是In urb。主机控制器为了正确使用这个缓冲区,必须使用kmalloc调用来创建它,而不是堆栈或者静态数据区。对于控制端点,这个缓冲是给发送的数据/
dma_addr_t transfer_dma; /*用来以DMA方式向设备传输数据的缓冲区*/
int transfer_buffer_length;/*transfer_buffer或transfer_dma指向缓冲区的大小=wMaxPacketSize,USB2.0 全时速端点长度分别包括:8、16、32、64字节 */
int actual_length; /* URB结束后,发送或接收数据的实际长度 */
unsigned char *setup_packet; /* 指向控制URB的设置数据包的指针*/
dma_addr_t setup_dma; /*控制URB的设置数据包的DMA缓冲区*/
int start_frame; /*等时传输中用于设置或返回初始帧*/
int number_of_packets; /*等时传输中等时缓冲区数据 */
int interval; /* URB被轮询到的时间间隔(对中断和等时urb有效) */
int error_count; /* 等时传输错误数量 */
void *context; /* completion函数上下文 */
usb_complete_t complete; /* 当URB被完全传输或发生错误时,被调用 */
struct usb_iso_packet_descriptor iso_frame_desc[0];
/*单个URB一次可定义多个等时传输时,描述各个等时传输 */
};
当transfer_flags标志中的URB_NO_TRANSFER_DMA_MAP被置位时,USB核心将使用transfer_dma指向的缓冲区而非transfer_buffer指向的缓冲区,意味着即将传输DMA缓冲区。
当transfer_flags标志中的URB_NO_SETUP_DMA_MAP被置位时,对于有DMA缓冲区的控制urb而言,USB核心将使用setup_dma指向的缓冲区而非setup_packet指向的缓冲区。
这两个标志都是有关DMA 的,什么是DMA?就是外设,比如咱们的usb 摄像头,和内存之间直接进行数据交换,把CPU 给撇一边儿了。
一般来说,都是驱动里提供了kmalloc 等分配的缓冲区,HCD 做一定的DMA 映射处理,DMA 映射是干吗的?外设和内存之间进行数据交换,总要互相认识吧,外设是通过各种总线连到主机里边儿的,使用的是总线地址,而内存使用的是虚拟地址,它们之间本来就是两条互不相交的平行线,要让它们中间产生连接点,必须得将一个地址转化为另一个地址,这样才能找得到对方,才能互通有无,而DMA 映射就是干这个的。为了分担点HCD 的压力,于是就有了这里的两个标志,告诉HCD 不要再自己做DMA 映射了,驱动提供的urb 里已经提供有DMA 缓冲区地址,为领导分忧解难是咱们这些小百姓应该做的事情。具体提供了哪些DMA 缓冲区?就涉及到下面的transfer_buffer,transfer_dma,还有setup_packet,setup_dma 这两对儿了。
URB_ZERO_PACKET
这个标志表示批量的OUT 传输必须使用一个short packet 来结束。批量传输的数据大于批量端点的wMaxPacketSize 时,需要分成多个Data 包来传输,最后一个data payload 的长度可能等于wMaxPacketSize,也可能小于,当等于wMaxPacketSize 时,如果同时设置了URB_ZERO_PACKET 标志,就需要再发送一个长度为0 的数据包来结束这次传输,如果小于wMaxPacketSize 就没必要多此一举了。你要问,当批量传输的数据小于wMaxPacketSize 时那?也没必要再发送0 长的数据包,因为此时发送的这个数据包本身就是一个short packet。
一个urb 用来发送或接受数据到或者从一个特定 USB 设备上的特定的 USB 端点, 以一种异步的方式.一个 USB 设备驱动可能分配许多 urb 给一个端点或者可能重用单个 urb 给多个不同的端点, 根据驱动的需要. 设备中的每个端点都处理一个 urb 队列, 以至于多个 urb 可被发送到相同的端点, 在队列清空之前.
URB处理流程
1.在USB设备驱动中创建URB
struct urb *usb_alloc_urb(int iso_packets,int mem_flags);
2.初始化传输端点类型
void 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)
void 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)
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev, unsigned int pipe, void *transfer_buffer,intbuffer_length, usb_complete_t complete, void*context, int interval);
3.USB设备驱动将URB提交给USB核心
int usb_submit_urb(struct urb *urb, intmem_flags);
(1)GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current->state修改为非 TASK_ RUNNING时,应使用此标志。
(2)GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
(3)GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。
4.USB核心将URB提交给USB主机控制器驱动
5.USB主机控制器处理,进行一次数据传送
6.当一次URB传输完成后,USB主机控制器驱动再原路返回通知USB设备驱动
struct usb_driver {
/*驱动程序标识名称,应该与模块名称一致*/
const char *name;
/*探测函数指针 可选配置*/
int (*probe) (struct usb_interface *intf,const struct usb_device_id *id);
/*设备断开连接或者驱动程序没有加载的清理工作*/
void (*disconnect) (struct usb_interface *intf);
/*USBfs 配置函数,用于驱动程序与用户空间的通信*/
int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,void *buf);
/*USB核心设备抑制、唤醒、重置替代唤醒 处理函数回调*/
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
/*重置检测URB激活状态*/
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
/*USB外设列表*/
const struct usb_device_id *id_table;
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int soft_unbind:1;
};
设备描述符结构体:一个USB设备只有一个USB设备描述符
struct usb_device_descriptor {
__u8 bLength;//描述符的长度,设备描述符的长度为18个字节
__u8 bDescriptorType;//描述符的类型,设备描述符的类型为0x01
__le16 bcdUSB; //USB设备所遵循的协议版本号,例如2.0协议为0x0200
__u8 bDeviceClass;
//USB设备类代码,由USB-IF分配,如果该字段为0x00,表示由接口描述符来指定(有可能该USB设备是一个复合设备,USB设备的各个接口相互独立,分别属于不同的设备类)。
如果是0x01~0xfe,表示为USB-IF定义的设备类,例如0x03为HID设备,0x09为HUB设备。如果是0xff,表示由厂商自定义设备类型
__u8 bDeviceSubClass;//USB子类代码,由USB-IF分配,如果bDeviceClass为0x00,那么该字段也必须为 0x00,其它情况可以参考USB关于对于USB Device Class的定义
__u8 bDeviceProtocol; //协议代码,由USB-IF分配,如果bDeviceClass和bDeviceSubClass定义为0x00,那么该字段也必须为0x00
__u8 bMaxPacketSize0; //端点0最大数据包长度,必须为8、16、32和64字节
__le16 idVendor;//厂商ID
__le16 idProduct;//产品ID
__le16 bcdDevice; //设备序列号,由厂商自行设置
__u8 iManufacturer; //用于描述厂商的字符串描述符索引
__u8 iProduct; //用于描述产品的字符串描述符索引
__u8 iSerialNumber; //用于描述产品序列号的字符串描述符索引,注意,所有的字符串描述符是可选的,如果没有字符串描述符,指定这些索引为0x00
__u8 bNumConfigurations;//配置描述符数量
}
配置描述符: 一个USB设备可以有多个配置描述符
struct usb_config_descriptor {
__u8 bLength;//配置描述符长度,配置描述符长度为9字节大小
__u8 bDescriptorType;//描述符类型,配置描述符类型为0x02
__le16 wTotalLength;//配置描述符信息总的大小,包括接口描述符、端点描述符等等
__u8 bNumInterfaces;//USB接口数量
__u8 bConfigurationValue;//当使用SetConfiguration和GetConfiguration请求时所指定的配置索引值
__u8 iConfiguration; //描述配置的字符串描述符索引
__u8 bmAttributes;//供电配置,位详细定义如下:D7:保留,必须置1 D6:自供电模式 D5:远程唤醒 D4~D0 :保留
__u8 bMaxPower;//最大功耗,以2mA为单位,例如0x32为50*2=100mA
}
接口描述符: 一个配置中可以有一个或多个接口,一个接口中有0个或多个端点,接口描述符和端点描述符不能直接通过GetDescriptor请求返回,必须连同配置描述符一起返回,Linux中接口描述符定义如下。
struct usb_interface_descriptor {
__u8 bLength;//描述符长度,接口描述符长度为9个字节
__u8 bDescriptorType;//描述符类型,接口描述符的类型为0x04
__u8 bInterfaceNumber;//该接口编号,接口编号从0开始分配,当一个配置有多个接口时,就用该字段来区分不同的接口
__u8 bAlternateSetting;//
__u8 bNumEndpoints; //端点数量,不包括端点0
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;//和设备描述符中的bDeviceClass、bDeviceSubClass、bDeviceProtocol类似
__u8 iInterface;//描述该接口的字符串描述符索引
}
struct usb_interface {
struct usb_host_interface *altsetting; /*接口结构数组 用于记录可选的接口信息数组 无序*/
struct usb_host_interface *cur_altsetting; /* 当前接口激活的可选设置 */
unsigned num_altsetting; /* 可选设置的数量 */
struct usb_interface_assoc_descriptor *intf_assoc; /*组接口配置绑定*/
/*驱动程序包含了USB设备的主设备号,该选项表示为接口的次设备号 */
int minor;
enum usb_interface_condition condition; /* 接口绑定状态*/
/*操作配置功能参数位*/
unsigned sysfs_files_created:1; /* 文件系统已存在该设备文件*/
unsigned ep_devs_created:1; /* 终端设备已存在*/
unsigned unregistering:1; /* 进程未注册*/
unsigned needs_remote_wakeup:1; /* 驱动远程唤醒*/
unsigned needs_altsetting0:1; /* 开启抑制altsetting 0 */
unsigned needs_binding:1; /* 需要延迟绑定*/
unsigned reset_running:1;
unsigned resetting_device:1; /*重置后带宽重分配*/
struct device dev;
struct device *usb_dev;
atomic_t pm_usage_cnt; /*引用变量 用于确定接口是否正在使用*/
struct work_struct reset_ws; /*工作队列结构*/
};
端点描述符:端点0没有端点描述符,Linux中端点描述符结构定义如下。
struct usb_endpoint_descriptor {
__u8 bLength;//描述符长度,这里有两个值如果是audio设备的端点,那么端点描述符长度就为9个字节,对于其它设备端点,端点描述符长度就为7个字节
__u8 bDescriptorType;//描述符类型,端点描述符类型为0x05
__u8 bEndpointAddress;//端点地址,详细定义如下
__u8 bmAttributes;//端点类型,详细定义如下
__le16 wMaxPacketSize;//端点所支持最大数据包的长度
__u8 bInterval;//端点数据传输的访问时间间隔
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
}
HID描述符:
struct hid_descriptor {
__u8 bLength;//描述符长度
__u8 bDescriptorType;//描述符类型,HID描述符的类型为0x21
__le16 bcdHID;//所遵循的HID协议版本
__u8 bCountryCode;//国家代码
__u8 bNumDescriptors;//下级描述符数量,通常至少需要一个报告描述符
struct hid_class_descriptor desc[1];//下级描述符类型,例如报告描述符
}