usb-skeleton 代码分析
文章目录
- usb-skeleton 代码分析
-
- 驱动注册
- 主端探测函数 probe
- 设备操作集
-
- 打开设备
- 关闭设备
- 写操作
- 读操作
- flush
usb-skeleton.c
是USB Host端代码的一个骨架,如果想要编写自己的Host端
bulk
传输的代码,可以参考这个部分的代码进行编写,至于其他
isoc
的传输方式,可能还需要参考其他的驱动代码进行编写。
当前内核版本linux-4.9.37
。
驱动注册
使用module_usb_driver
注册HOST端驱动,声明匹配的gadget驱动列表(主要依赖VID与PID),完善相关的探测、断开连接等函数:
主端探测函数 probe
看一下skel_probe
的第一部分里面做了什么:遍历接口的当前配置的所有端点,找到bulk in
端点地址并申请urb
传输描述符。
一开始用到的usb_skel
结构体如下,成员变量大部分都有注释,主要的也增加了中文的注释。
再看一下第二部分的skel_probe
,主要是执行一个USB
设备注册,传入一个struct usb_class_driver
类型的skel_class
。
看一下skel_class
的内容,主要关注是设备的操作集合skel_fops
。
设备操作集
看一下skel_fops
的概况,主要关注打开、关闭、读写和flush
函数。
打开设备
skel_open()
根据传入的inode
节点,找到次设备号后,使用次设备号找到对应的设备接口,再从设备接口中找到私有的usb_skel
结构体指针,并保存在file
结构体中,方便后续read/write
等函数使用。
关闭设备
skel_release()
主要作用是释放资源。
看一下skel_delete()
,就是各种资源的释放。
写操作
函数分为三个部分截图,看第一部分,这里主要就是一些参数的检查,以及如果上一次传输错误的话直接报告出错。
看第二部分的工作就是申请urb
,申请内核态的内存,并将用户态的数据拷贝过来,填充urb
的回调函数等信息后提交给硬件控制器。copy_form_user/copy_to_user
是比较耗时的工作,如果想要提高传输的效率,可以考虑将这个拷贝部分去掉,比如使用mmap
方式;有些特殊情况可以更加方便,比如MMZ
的内存可以直接将码流的物理地址交给USB
驱动,然后也不需要进行DMA
映射,填充urb
直接交给USB
控制器,就可以进行传输,省略拷贝的过程速度提高是很可观的。
看第三部分主要是urb
的释放以及出错处理。
看一下填充urb
时的回调函数skel_write_bulk_callback()
,内容也比较简单,如果有错误,保存错误码;释放申请的一致性内存。
读操作
读操作和写操作有些不一样,我们一般的read
操作是阻塞的,而write
操作直接提交给USB控制器之后就可以了,而阻塞的read
需要等待数据的回来并报告应用层。
这里也分为两部分,看第一部分,进行检查相关的参数后,如果发现正在等待数据回来的过程,不是阻塞IO的则返回-EAGAIN
,阻塞IO的话则等待读等待队列的唤醒。
第二部分就是根据用户空间要读取的数据和已经读取到数据的数量,进行发起实际的IO操作。
看一下实际发起IO操作的函数skel_do_read_io()
,也是填充urb
并提交给控制器,以及私有的数据初始化。
以及读回调函数skel_read_bulk_callback()
,与写回掉函数的差异多了实际接收长度的记录以及唤醒等待队列。
flush
至于这个skel_flush()
函数,主要就是调用skel_draw_down()
进行停止相关的传输,如果有错误的话将错误返回。
skel_draw_down()
函数等待以及提交的写urb
返回,如果超时则停止;同时停止读的urb
。
至此整个代码就基本分析完成了,关于控制器驱动部分,比较复杂,有机会再写些文档分析一下,其实这个代码我进行修改来适配从端的驱动,当前从端的驱动也有修改,主要作用是将MMZ
码流从一端传输到另一端,中间的优化过程就是上面的省略拷贝的过程。