usb总线是一种轮询式总线,协议规定所有的数据传输都必须由主机发起,usb主机与设备之间是通过管道(pipe)传输的,管道两边分别对应主机中的数据缓冲区和设备侧的端点(endpoint),端点是通信的发送和接收点,要发送数据,只要把数据发到对应的端点就可以,而这个数据发送的动作由usb主机实现,驱动中只需确定接收端点,然后把数据提交给主机控制器,主机会把数据发送给接收端点,原理同i2c,uart类似。每个usb设备中都存在一个特殊端点endpoint0,在usb设备枚举过程里,就是通过endpoint0来获取usb设备信息。
USB按传输类型分可以分为控制传输(control),中断传输(interrupt),等时传输(isochronous),批量传输(bulk),其中控制传输和批量传输又称为非周期性传输方式(nonperiodic),而中断传输和等时传输称为周期性传输方式(periodic);
USB控制器和设备之间的传输数据结构由urb表示,urb具体内容如下所示:
struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* reference count of the URB */
void *hcpriv; /* urb的私有数据内容,endpoint descriptor ED*/
atomic_t use_count; /* urb使用计数 */
atomic_t reject; /* submissions will fail */
int unlinked; /* unlink error code */
struct list_head urb_list; /* 用于连接endpoint queue的列表*/
struct list_head anchor_list; * the URB may be anchored */
struct usb_anchor *anchor;
struct usb_device *dev;
struct usb_host_endpoint *ep;/* 接收端点*/
unsigned int pipe; /* 传输数据管道 */
unsigned int stream_id;
int status; /* 用于存放urb状态*/
unsigned int transfer_flags; /* 传输过程种用到的标志位,如果URB_NO_TRANSFER_DMA_MAP*/
void *transfer_buffer; /* 收发缓冲区 */
dma_addr_t transfer_dma ;/* 用dma方式发缓冲 */
struct scatterlist *sg;
int num_sgs;
u32 transfer_buffer_length; /*发送数据长度*/
u32 actual_length; /* 实际发送的长度*/
unsigned char *setup_packet;/* 用于control类型发送setup包的缓存 */
dma_addr_t setup_dma; /* 通过dma方式 发送setup包缓存*/
int start_frame; /* 在等时传输时用来指定开始发送数据位置*/
int number_of_packets; /*等时传输包个数*/
int interval; /* 中断传输和等时传输发送间隔*/
int error_count; /* 用于统计发送出错个数 */
void *context;/* 回调函数complete函数参数 */
usb_complete_t complete; /* 提交完urb后会调用 的回调函数 */
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
usb 用urb发送数据时分三个步骤:申请urb, 填充urb,向usb控制器提交urb.
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
iso_paskets为等时传输时变长数组iso_frame_desc的元素个数,对于中断,控制和批量传输应该为0,mem_flags为申请内存空间时所需的标志位。
void usb_free_urb(struct urb *urb)
{
if (urb)
kref_put(&urb->kref, urb_destroy)
}
urb和端点不是一一对应的关系,一个urb可以被发向多个不同的端点,这里通过urb->kref来统计某个urb被使用次数,当使用urb时,kref加1,调用usb_free_urb时urb减1,当kref为0时,调用urb_destroy来释放urb中的transfer_buffer和urb。
对于usb的四种不同传输方式,usb驱动中已经实现control,bulk,interrupt三种传输类型传输填充函数。
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)
其中urb为要初始化的控制类型urb,dev为USB设备,pipe为管道,setup_packet专门用于发送control的usb_contrlrequest的setup包,transfer_buffer发送缓冲区, buffer_length传输长度, complete_fn为urb递交给hcd后将会运行的回调函数,comtext为回调函数参数。
与control填充函数相比,bulk传输方式的初始化少了setup_packet设置,bulk函数接口为:
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)
对于interrupt而言,还需设置定时查询时间间隔interval, 函数接口为:
void usb_fill_int_urb(struct urb*urb, struct usb_device *dev, unsinged int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn ,void *context, int interval)
对于等时传输,urb里是可以指定多次传输的,所以必须一个一个的对变长数组iso_frame_esc内容进行初始化。
经过前面的创建、初始化和填充数据后,urb需要提交给usb core,让它移交给主机控制器驱动进行处理,然后等待hcd的反馈结果,用于提交urb函数接口为:
int usb_submit_urb(struct urb*urb, gfp_t mem_flags)
其中urb即为提交给hcd的urb,mem_flags为申请urb中的hcprv,endpoint descripotr和 transfer descriptor时要用到的申请内存标志。
将urb提交给控制器后,由控制器进行处理,并通过回调函数返回urb发送结果。
如果想取消之前提交的urb,可以用usb_unlink_urb来实现:
int usb_unlink_urb(struct urb *urb);
用前面的方式提交urb或取消urb时,程序不会阻塞,属于异步方式。除了异步方式外,usb还可用同步方式来提交和取消urb。同样由于isochronous中发送数据包个数不确定性,驱动只实现了control,interrupt和bulk三种方式 的同步方式操作urb接口。
int 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_control_msg用于发送control类数据,对于control类型,除发送正常数据外,还要发送一个setup transaction, request, requesttype指定请求包的类型和属性,data为要发送的数据,size为发送数据长度,timeout为发送超时时间。
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)
上面三个接口函数都已经将之前提到过的申请urb,填充urb和提交urb过程封装在一起,在使用时只要指定对应该数据,数据长度及超时时间就可以。在使用上面三个接口提交urb时,程序阻塞,直到超时或urb提交成功并通过回调函数返回结果并唤醒等待队列。
在同步方式下对应的urb取消函数接口为:
void usb_kill_urb(struct urb *urb)
usb_kill_urb提交取消urb申请后,会一直等待urb取消完成才会退出,里面的等待也是通过等待队列实现的。