g_zero 代码分析
g_zero.ko
是USB Device端代码的一个驱动,具备两个功能:loopback
和sourcesink
,使用bulk
的端点进行传输,作者写这个驱动的目的是用来测试UDC驱动的,但我们也可以在这个驱动的基础进行修改,以实现一些我们自己的功能,比如生成字符设备,提供ioctl接口、mmap接口等,以实现与host端的主动发起交互。
当前内核版本linux-4.9.37
。
usb_f_ss_lb.ko模块
这个模块包含两个function
,分别是f_sourcesink
和f_loopback
,接下来就从初始化的部分分别看这两个代码。
f_sourcesink.c
初始化模块
先看一下模块初始化的部分,注册一个SourceSink
功能,如果成功,再调用lb_modinit()
进行loopback
功能的初始化。
注册的过程也相当简单,usb_function_register()
函数判断一下是否以及注册,没有注册的话就加到链表后,就表面注册成功了。
声明USB功能
初始化过程使用到的SourceSinkusb_func
是在下图所在的DECLARE_USB_FUNCTION()
定义的,同时指定了实例申请和功能申请的函数。
声明的过程也是比较简单,就是一个赋值的宏:
struce f_ss_opts
结构体值得关注的目前只有里面的功能实例:
看一下source_sink_free_instance()
的过程,就是释放申请的struct f_ss_opts
内存。
再回过头看一下source_sink_alloc_func()
函数,里面主要关注的是一个function
的赋值过程。
再看一下里面的struce f_sourcesink
结构体,里面其实主要注意的是IN/OUT
类型的端点描述。
IN类型传输即数据从 Device 传输到 Host,Host端发出应答。
OUT类型传输即数据从 Host 传输到 Device,Device 端发出应答。
如果需要实现自己的字符设备,那么就要注意了,对于Device端,Device端从用户空间写数据到内核空间,然后驱动通过IN端点发送出去给Host;内核通过OUT端点,从Host端读到数据后,再拷贝到用户空间,即用户空间的一个读过程。这个与Host代码是有区别的,只要搞清楚这个IN/OUT方向的描述即可。
如下图所示:
function之setup
其实setup
的内容大部分都由libcomposite.ko
模块进行了处理了,这里就剩下两个测试的请求,最后通过ep0
端点发起请求,高速Host端结果即可。关于setup
的过程还需要对比一下USB协议,这里就暂时先略过了。
function之bind
sourcesink_bind()
对两个配置的接口ID进行赋值,申请端点后,分别对全速、高速、超高速的端点参数进行修正,最后将端点描述符赋给function
。
看一下全速下的描述符:
高速描述符:与全速描述符有些差异,BULK端点限定了最多传输大小512字节,ISOC端点最大传输大小限定为1024字节,为什么端点没有了传输描述方向的,我猜想应该是bind过程中端点赋值了之后可以省略这个过程,而全速端点在申请时必须附带上方向。
超高速描述符:与高速与全速不同,除了BULK端点限制变为1024字节,每一个端点过后还多了一个usb_ss_ep_comp_descriptor
结构体的描述。
function之get_alt, set_alt, disable
这三个函数比较简短,放在一起介绍。sourcesink_get_alt()
函数返回当前的配置选择,上面bind
的时候以及端点描述符数组里面都可以看到有两个配置,但一般默认情况是配置0,而配置1是需要有isoc
相关端点的。sourcesink_set_alt()
自然就是用来选择不同的配置的。sourcesink_disable()
用于失能相关的功能,把端点disable
之后就无法进行传输了。
看一下disable_source_sink()
函数,就是禁用所有的端点。
disable_endpoints()
就是对传入的参数进行了一下判断,继续调用disable_ep()
。
disable_ep()
则是调用标准的udc
的core
里面提供的接口,禁用单个端点。
而udc
提供的标准接口,最后会执行对应的控制器的代码,当前控制器是dwc3
,暂不展开。
使能端点
然后看一下在set_alt
中用到的enable_source_sink()
函数,根据控制器最后协商的结果来配置IN/OUT端点,以及ISOC的两个端点(如果配置是1的话)的速度以及传输大小等数据,并调用source_sink_start_ep()
来申请端点描述符,发起传输等。
看一下source_sink_start_ep()
的过程,根据不同的端点类型申请设置不同的传输大小,并申请端点传输请求以及传输buffer大小,设置回调函数并发起请求。至于里面的pattern
,是里面用来测试的,自己修改代码或者编写代码时可以不用管这个。如果是想要实现自己的字符设备,这个source_sink_start_ep()
的过程可以省略掉。
ss_alloc_ep_req()
就是封装了一下udc
的标准接口。
alloc_ep_req()
申请端点请求以及传输的buffer,OUT端点传输长度需要进行对齐,否则Host端会报协议错误。usb_ep_alloc_request()
最后也是调用dwc3
里面的申请端点,这里就不展开了。如果是想在从端编写字符设备,并且将速度进行优化,那么申请内存的部分可以用mmap替代,这样也可以省略从用户空间拷贝的过程。
发起端点传输
可以看到从端的发起传输是通过usb_ep_queue()
函数来发起的,在使用dwc3
控制器时,执行dwc3_gadget_ep_queue()
函数,这个函数可以展开一下,里面有可以进行修改优化的过程。
这里就简单地放一张图来描述一下,因为申请的内存是通过kmalloc
申请过来的,所以直接线性映射后是可以得到物理地址的。而MMZ的内存不在内核管理,如果想要直接使用MMZ的物理内存,可以先使用ioremap
映射到高端地址,再进行操作;或者想直接用USB传输的,可以直接把MMZ的物理地址交给USB控制器,也可以正常完成传输。
回调函数
再看一下回调函数做了什么操作,主要就是一个完成之后里面再发起请求。
function之free
这个free
的过程就是减少对应的function
的实例引用,释放所有端点,以及申请的结构体内存。
f_loopback.c
经过上面的f_sourcesink.c
的分析,其实大部分大同小异,这里就不花太多篇幅去描述了。
初始化模块
注册一个Loopback
功能。
声明USB功能
初始化过程使用到的Loopback_func
是在下图所在的DECLARE_USB_FUNCTION()
定义的,同时指定了实例申请和功能申请的函数。
function的功能
功能使能、禁止以及选项配置:
申请请求:
完成回调:
g_zero.ko 模块
g_zero
模块是是属于Gadget 功能驱动层,本身具有两个function
,分别是LOOPBACK function
和SOURCESINK function
。
先看一些注册驱动的函数以及对应的zero_driver
驱动结构体内容:
驱动注册
看一下设备描述符:
usb_composite_driver
定义如下:
zero_driver
的zero_bind()
函数的被调用过程会经过如下步骤:
在composite_bind()
里面会申请一个usb_composite_dev
结构体,用于保存设备的相关信息:
设备绑定
在进到zero_bind()
函数里面仔细看一下做了哪些事情。
1- 设置定时器用于自动挂起,获取 SourceSink 功能实例,设置 SourceSink 参数,从 SourceSink 实例中申请 function。
2- 获取 Loopback 功能实例,设置 Loopback 参数,从Loopback 实例中申请 function。
3- 将配置增加到设备中,并将function添加到配置。
如果要想在从端生成字符设备,最好在bind()
这里生成,然后操作之前,需要进行一下判断端点是否使能,使能之后再进行操作。
zero_bind()
里面用到的usb_configuration
是保存设备的配置相关的信息:
解绑过程就是绑定过程的逆操作,释放相应的资源。