1. Gadget层介绍
由于SylixOS中没有Platform的概念,所以在笔者眼中Gadget层兼顾了Platform的功能,实现了UDC设备层和驱动层的连接。
Gadget层结构以及函数调用过于复杂,无法对每个函数做一一介绍,在此会抽调一些重要的函数以及结构体简单介绍。具体绑定传输流程还需读者花时间精力慢慢理顺。
1.1 上层函数操作集
如图 1‑1所示,在上层函数操作集中包含了4个重要的结构,这些结构体中包含大量的回调函数来获取底层信息,建立连接(此层的主要代码在composite.c文件中)。这些函数中以下几个尤其重要:
1. composite_bind函数
Bind函数主要完成UDC驱动层Gadget设备和设备层composite设备关系的建立,分配控制端点0的请求数据结构,设置设备描述符,并调用功能层的bind函数分配功能层所需要的资源等工作。
2. composite_unbind函数
Unbind函数完成bind函数中分配的资源,并遍历所有配置和各个配置下的功能,调用其对应的unbind函数来释放资源。
3. usb_add_function函数
add_function在配置中增加一个功能,每个配置初始化后都必须至少有一个功能。这个函数会在多个地方调用,使用需特别注意。
4. composite_setup函数
setup函数完成了ep0所需要处理的而底层不能处理的功能,由于UDC层实现了这个函数,所以其他层就不需要关注于USB协议的这些事务性处理,而重点放在需要实现的功能上。
图 1‑1 Gadget层需获取的函数集合
1.2 底层函数操作集
如图 1‑1所示,在底层操作函数操作集中大部分都是提供给上层的回调函数接口。其中有两个结构体,几乎贯穿整个USB虚拟网卡,详细介绍如下:
1. usb_request结构体
struct usb_request { void *buf; //数据缓存区 unsigned length; //数据长度 dma_addr_t dma; //与buf关联的DMA地址,DMA传输时使用 unsigned no_interrupt:1; //当为true时,表示没有完成函数,则通过中断通知传输完成,这个由DMA控制器直接控制 unsigned zero:1; //当输出的最后的数据包不够长度是是否填充0 unsigned short_not_ok:1; //当接收的数据不够指定长度时,是否报错 void (*complete)(struct usb_ep *ep, struct usb_request *req);//请求完成函数 void *context; //被completion回调函数使用 struct list_head list; //被Gadget Driver使用,插入队列 int status; //返回完成结果,0表示成功 unsigned actual; //实际传输的数据长度 };
|
在USB传输的过程中所有的消息最终都是填充在一个buf中来进行传输的,USB为了更好的管理传输的消息定义了一个usb_request结构体来描述一帧消息的信息。
2. usb_ep_ops
struct usb_ep_ops { int (*enable) (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc); int (*disable) (struct usb_ep *ep); struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags); void (*free_request) (struct usb_ep *ep, struct usb_request *req); int (*queue) (struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags); int (*dequeue) (struct usb_ep *ep, struct usb_request *req); int (*set_halt) (struct usb_ep *ep, int value); int (*set_wedge) (struct usb_ep *ep); int (*fifo_status) (struct usb_ep *ep); void (*fifo_flush) (struct usb_ep *ep); };
|
结构体中有两个重要的回调函数,一个是alloc_request函数,另一个是queue函数。
在usb虚拟网卡中所有的消息都以队列的形式来进行传输的。调用alloc_request函数申请一个传输请求大小的空间,这个大小的值可根据实际传输时所需的最大空间进行配置。在每次消息传出之前都要提前调用alloc_request函数来申请内存。在消息接收完毕或者填充完毕之后都需要调用queue函数把需要传输的消息加入到消息队列中,进行传输。
1.3 注意事项
USB虚拟网卡移植的过程中,主要参考的是Linux代码,SylixOS和Linux有许多区别,比如SylixOS中没有dev的概念,也没有platform的概念。需要改许多的代码框架。在SylixOS中使用的是LWIP网络协议栈,而Linux中使用的是TCP/IP协议栈。在数据传输的过程中LWIP使用的是pbuf来封装数据,而Linux使用的是skbbuf,两者的使用机制完全不一样。类似的结构体不同还有很多,在这不一一说明。
移植USB驱动时需特别注意虚拟地址和物理地址相互转换以及cache同步问题。USB传输的数据量会很大,因此会使用DMA。DMA使用的是物理地址,而CPU却只能识别虚拟地址。在物理地址和虚拟地址转换过程中很有可能会出现物理地址未映射成虚拟地址的情况。就算有做物理地址和虚拟地址的转换也需要注意cache刷新时刷新的字节大小问题。如果需要刷新内存的大小和cache刷新大小字节不对齐,会出现cache刷新失败问题。这个问题十分严重,会直接导致读出的数据是脏数据,系统直接跑飞,需特别注意。按照常理来说使用cache的数据处理速度应该远大于未使用cache,但是在SylixOS的实测中,有无cache操作速度差距不大,甚至不使用cache速度会更快。目前的USB虚拟网卡使用的是无cache版本的操作。
实际使用过程中,需要根据使用USB协议的版本号和实际速度,来配置初始化接收缓冲区个数。如果速度快而缓冲区个数过少,会出现严重的丢包现象,影响USB速率。
由于USB驱动中代码回调过于复杂,本文也只是简单提炼了一下框架和重点,如果想要完全弄清还需耐下心花时间从头到尾按部就班追一下数据传输流程的代码才行。由于作者水平有限可能会出现理解错误或者描述不到位的情况,请大家谅解,也请大家及时指正,谢谢。