基于Linux的USB驱动研究与实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
图1 USB分层结构
2 Linux下的USB框架
Linux下的USB框架分为三层,如图2所示。
USB的驱
动可以分为三类:USB控制器的驱动、主机端USB设备的
驱动、设备上的USB Gadget驱动。
USB core管理上层驱动
查荣明(1980-),男,江苏苏州人,硕士研究生,工程师。
研究方向:嵌入式系统接口技术、底层驱动。
孔康(1989-),男,山东济宁人,硕士研究生,工程师。
研究方向:嵌入式系统接口技术。
石璐(1988-),男,河南商丘人,本科,工程师。
研究方向:嵌入式系统总体技术方案。
图2 Linux下的USB框架图
本文研究的是主机端USB设备的驱动。
由于
驱动本质上是基于Linux的USB core层提供的
结构进行的,因此,有必要介绍相关数据结构。
USB相关的数据结构
USB相关的数据结构前,需要
及的几个逻辑概念:Device、Configuration
Endpoint。
其中,Endpoint在前文中已介绍。
它们之间的关系可以用如图3[3]表示。
图3 逻辑关系图
从图3可以看出,一个USB设备(device)通常包含一个或者多个配置(config),一个配置(config)通常包含一个或者多个接口(interface),一个接口(interface)通常包含零个或者多个端点(Endpoint)。
在Linux内核中,分别usb_device、usb_host_config、usb_interface、usb_host_ endpoint四个结构体。
USB四个结构体除了驱动所需要的其他成员变量外,其他要素基本对应四个相应描述符(descriptor),这几个结构
图4 USB各结构体间的关系
4 USB骨架程序介绍
在Linux内核源码目录的drivers/usb/下,有一个usb-skeleton.c,提供了基于USB的骨架驱动程序,该程序针对的设备类型是海量存储设备。
本文针对的骨架程序是Linux V3.0.35版本下的程序。
骨架程序的流程以及函数相互间的关系如图5[6]所示。
从图5可以看出,骨架程序主要可以分为三部分进行。
(1)驱动注册相关操作:骨架程序的入口函数为usb_ skel_init(void),该函数在系统初始化时或者在动态加载模块时被调用。
该函数主要是将skel_driver结构体的各个成员变量初始化,然后通过usb_register函数向USB子系统注册。
Usb_driver包含的主要部分解释如下。
①name:驱动名称在USB子系统中需要具有唯一性,不能和其他驱动名称重名,同时,需要和该驱动对应的模块名保持一致。
②probe:当有USB设备插入时,USB子系统根据id_ table中的VendorID和ProductID的组合来识别设备,如果匹配,则调用相应的probe函数。
③disconnect:当有USB设备拔出时,USB子系统根据id_table中的VendorID和ProductID进行比对。
相匹配时,则调用相应的disconnect函数。
④id_table:该table用于热插拔。
(2)热插拔及I/O注册:这部分主要是在probe及disconnect函数中实现。
一旦设备插入,USB子系统便根据id_table中的VendorID和ProductID的组合来识别设备,如果匹配,则调用相应的probe函数。
该函数的步骤如下。
①为结构体dev申请内存[7]。
初始化kref,limit_sem、io_mutex等用于互斥或同步的锁及资源。
②probe函数被USB子系统调用时,会传入一个参数——接口(interface),包含了设备上的interface上的所有信息。
该函数循环查找该interface上的所有端点,判断其传输方向是否为IN同时为BULK类型,找到第一个就将该端点的地址以及端点支持的包支持的最大字节数赋给dev结构体。
查找OUT端点的步骤类似。
③将interface和dev通过usb_set_intfdata函数进行绑定,同时,向内核注册了dev结构体,放入内核链表中。
④通过usb_register_dev函数,向该interface绑定了skel_class 结构,该结构包含了设备名称、文件操作结构体、次设备号的基准值。
用户态对该设备的读写等操作,最终会调用文件操作结构体中的读写等IO操作。
次设备号被用来区分不同的设备。
当设备拔出时,USB子系统会根据VendorID和ProductID 的组合识别被拔出的设备,并调用相应的disconnect函数,该函数主要完成资源释放功能。
操作:I/O操作主要指读写操作。
函数:总的来说,该函数主要实现的功能是根据内核缓冲区的数据,拷贝适当的数据给应用层。
首先通过变量来判断是否有read操作在执行,如果有则等待该操作执行完毕。
接着通过dev->processed_urb判断是否正常处理,如果为零,代表还没有处理好,需要等待,否则,代表处理完毕。
判断dev->bulk_in_filled的值,如果不为零,则代表接收缓冲区里已有的数据量,然后根据这个值减去已拷贝的数据值(dev->bulk_in_copied)得到还未读available)。
如果available为零,则代表数据已拷贝完毕,则再次发起skel_do_read_io向设备请求数据。
比较和期望拷贝值(count)之间的大小,取其小的赋值
,然后将chunk大小的数据调用copy_to_user函数拷贝到用户空间的buffer。
如果可读取的值小于期望拷贝值),则再次发起skel_do_read_io向设备请求数据。
数:write函数相对于read函数来说,较为简单。
其主要功能为:申请urb结构体大小的内核空间用于参数,然后根据需要写的字节数申请用于DMA的,接着调用copy_from_user函数将用户空间的数据拷中,调用usb_fill_bulk_urb函数将buffer地址需要写的字节数,通过write回调函数等填充到urb中,最后usb_submit_urb函数向内核提交urb请求。
5 基于骨架驱动程序的修改
我们的设备是通信类设备,采用的是interrupt
[8],因此,需要对骨架程序进行如下修改。
5.1 id_table的修改
将USB_SKEL_VENDOR_ID以及PRODUCT_ID修改成设备相应的值。
5.2 probe函数的修改
该函数的修改包含两部分:变量processed_urb
类型相关的API的修改。
第一部分:对一个变量
urb赋值为1,代表刚开始没有urb在处理。
第二部分:在
循环中,判断端点类型是否为符合要求的端点类型,需要将bulk类型的判断修改为interrupt类型的端点判断。
5.3 write和read相关函数的修改
这两个函数的修改类似,write函数:对于
数而言,在组装urb时,将bulk类型的API
类型的API,相应的该函数的参数也要从bulk
interrupt类型。
对read相关函数skel_do_read_io
似修改。
另外,当接收缓冲区中的可用数据大于需要读取的数据量时,不再调用read io操作,让用户自行判断,多次调用read来达到读取所有buffer中数据的目的。
6 驱动测试与验证
在驱动程序编译完成后,利用insmod及
图5 USB框架程序关系。