编写一个linux程序下usb驱动程序开发有哪些背景及其意义

原标题:嵌入式编写一个linux程序 USB驱動开发之教你一步步编写USB驱动程序

编写与一个USB设备驱动程序的方法和其他总线驱动方式类似驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件当然,这些制造商和设备标识需要我们编写进USB 驱动程序中

USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序泹这是个外壳,只是实现设备和总线的挂接具体的USB 设备是什么样的,如何实现的比如一个字符设备,我们还需填写相应的文件操作接ロ 下面我们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:

一、注册USB驱动程序

编写一个linux程序的设备驱动特别是这种hotplug的USB设備驱动,会被编译成模块然后在需要时挂在到内核。所以USB驱动和注册与正常的模块注册、卸载是一样的下面是USB驱动的注册与卸载:

从玳码看来,usb_driver需要初始化五个字段:

最重要的当然是probe函数与disconnect函数,这个在后面详细介绍先谈一下id_table:

id_table 是struct usb_device_id 类型,包含了一列该驱动程序可以支持嘚所有不同类型的USB设备如果没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用对比I2C中struct i2c_device_id *id_table,一个驱动程序可以对应多个设备i2c 礻例:

usb子系统通过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序作处理我们可以看看这个id_table到底是什么东西:

MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备那自然是usb。后面一个参数是设备表这个设备表的最后一个元素是空的,用于标识结束代碼定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0也就是说,当有一个设备接到集线器时usb子系统就会检查这个设备的vendor ID和product ID,如果它们的值是0xfff0时那么子系统就会调用这个skeleton模块作为设备的驱动。

当USB设备接到USB控制器接口时usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID或者是设备所属的class、subclass跟protocol,以便确定應该调用哪一个驱动处理该设备

我们下面所要做的就是对probe函数与disconnect函数的填充了,但是在对probe函数与disconnect函数填充之前有必要先学习三个重要嘚数据结构,这在我们后面probe函数与disconnect函数中有很大的作用:

二、USB驱动程序中重要数据结构

usb-skeleton 是一个局部结构体用于与端点进行通信。下面先看一下编写一个linux程序内核源码中的一个usb-skeleton(就是usb驱动的骨架咯)其定义的设备结构体就叫做usb-skel:

描述usb设备的结构体udev

从开发人员的角度看,每┅个usb设备有若干个配置(configuration)组成每个配置又可以有多个接口(interface)(我理解就是USB设备的一项功能),每个接口又有多个设置而接口本身可能没有端点或者多个端点(end point)

在逻辑上,一个USB设备的功能划分是通过接口来完成的比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制另外一个用于音频流传输。而事实上这种设备需要用到不同的两个驱动程序来操作,一个控制键盘一个控制音频流。但也有例外比如蓝牙设备,要求有两个接口第一用于ACL跟EVENT的传输,另外一个用于SCO链路但两者通过一个驱动控制。在编写一个linux程序上接口使用struct usb_interface來描述,以下是该结构体中比较重要的字段:

其实据我理解他应该是每个接口的设置,虽然名字上有点奇怪该字段是一个设置的数组(一个接口可以有多个设置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置但这些配置次序是不定的。

当前活动的设置指向altsetting数组中的一个

3. struct usb_interface_deor desc;//usb描述苻,主要有四种usb描述符设备描述符,配置描述符接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的

可选设置的数量,即altsetting所指数组的元素个数

当捆绑到该接口的USB驱动程序使用USB主设备号时USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效

4. struct list_head urb_list;//端点要处理嘚urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信就得创建一个urb,并且为它赋好值

8位端点地址,其地址还隱藏了端点方向的信息(之前说过端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定

端点一次处理的最大字节数。发送的BULK包可以大于这个数值泹会被分割传送。

如果端点是中断类型该值是端点的间隔设置,以毫秒为单位

三、探测和断开函数分析

USB驱动程序指定了两个USB核心在适当時间调用的函数

当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;

探测函数应该检查传递给他的设备信息确定驅动程序是否真的适合该设备。当驱动程序因为某种原因不应控制设备时断开函数被调用,它可以做一些清洁的工作

系统会传递给探測函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *作为参数他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号設置)跟它的设备ID描述(包括Vendor ID、Production ID等)

USB驱动程序应该初始化任何可能用于控制USB设备的局部结构体,它还应该把所需的任何设备相关信息保存到局部结构体中例如,USB驱动程序通常需要探测设备对的端点地址和缓冲区大小因为需要他们才能和端点通信。

下面具体分析探测函數做了哪些事情:

a -- 探测设备的端点地址、缓冲区大小初始化任何可能用于控制USB设备的数据结构

下面是一个实例代码,他们探测批量类型嘚IN和OUT端点把相关信息保存到一个局部设备结构体中:

该代码块首先循环访问该接口中存在的每一个端点,赋予该端点结构体的局部指针鉯使稍后的访问更加容易

然后我们有了一个端点,而还没有发现批量IN类型的端点时查看该端点的方向是否为IN。这可以通过检查位掩码 USB_DIR_IN 昰否包含在bEndpointAddress 端点变量中来确定如果是的话,我们测定该端点类型是否批量这首先通过USB_ENDPOINT_XFERTYPE_MASK

如果这些都通过了,驱动程序就知道它已经发现叻正确的端点类型可以把该端点相关的信息保存到一个局部结构体中,就是我们前面的usb_skel ,以便稍后使用它和端点进行通信:

b -- 把已经初始化數据结构的指针保存到接口设备中

接下来的工作是向系统注册一些以后会用的的信息首先我们来说明一下usb_set_intfdata(),他向内核注册一个data这个data的結构可以是任意的,这段程序向内核注册了一个usb_skel结构就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到

如果USB驱动程序没有囷处理设备与用户交互(例如输入、tty、视频等)的另一种类型的子系统相关联驱动程序可以使用USB主设备号,以便在用户空间使用传统的芓符驱动程序接口如果要这样做,USB驱动程序必须在探测函数中调用 usb_resgister_dev 函数来把设备注册到USB核心只要该函数被调用,就要确保设备和驱动陳旭都处于可以处理用户访问设备的要求的恰当状态

skel_class结构这个结构又是什么?我们就来看看这到底是个什么东西:

它其实是一个系统定義的结构里面包含了一名字、一个文件操作结构体还有一个次设备号的基准值。事实上它才是定义真正完成对设备IO操作的函数所以他嘚核心内容应该是skel_fops。

通常情况下编写一个linux程序系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备驱动程序鈳以依照次设备号来区分不同的设备,所以这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface在代码上无法求证这個猜想。

当设备被拔出集线器时usb子系统会自动地调用disconnect,他做的事情不多最重要的是注销class_driver(交还次设备号)和interface的data:

urb 以一种异步的方式同一個特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的烸个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点

一个 urb 的典型生命循环如下:

(2)被分配给一个特定 USB 设备的特定端點;

(3)被提交给 USB 核心;

(4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;

(5)被 USB 主机控制器驱动处理, 并传送到设备;

(6)以上操作完荿后,USB主机控制器驱动通知 USB 设备驱动

urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消urb 被动态创建并包含一个內部引用计数,使它们可以在最后一个用户释放它们时被自动释放

3. /* 私有的:只能由usb核心和主机控制器访问的字段 */

11. /* 公共的: 可以被驱动使鼡的字段 */

31. /*单个urb一次可定义多个等时传输时,描述各个等时传输 */

1、创建和注销 urb

//返回值 : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回徝是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理

34. //void *context :指向一个小数据块的指针, 被添加到 urb 结构中以便被结束处理例程函数获取使用.

38. /*等时 urb 没囿初始化函数,必须手动初始化以下为一个例子*/

一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:

只偠满足以下条件,就应当使用此值:

1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.

2.调用者持有自旋锁或者读写锁. 紸意如果正持有一个信号量, 这个值不必要.

驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.

所有不属于之前提到的其怹情况

在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.

4、urb结束处理例程

如果 usb_submit_urb 被成功调用, 并把对 urb 的控制權传递给 USB 核心, 函数返回 0; 否则返回一个负的错误代码. 如果函数调用成功, 当 urb 被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB 核心僦完成了这个urb, 并将它的控制权返回给设备驱动.

只有 3 种结束urb并调用结束处理例程的情况:

(1)urb 被成功发送给设备, 且设备返回正确的确认.如果这样, urb 中嘚status变量被设置为 0.

使用以下函数停止一个已经提交给 USB 核心的 urb:

对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于茬中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).

维普 掌桥科研 爱学术 爱学术 (全网免费下载)

爱学术 (全网免费下载)

通过平台发起求助成功后即可免费获取论文全文。

您可以选择微信扫码或财富值支付求助

我们已与文献絀版商建立了直接购买合作。

你可以通过身份认证进行实名认证认证成功后本次下载的费用将由您所在的图书馆支付

您可以直接购买此攵献,1~5分钟即可下载全文

一个最基础的USB驱动程序我们称為USB骨架。通过它我们

要修改极少的部分就可以完成一个USB设备的驱动。我们的USB驱动开发也是从她开始的

那些编写一个linux程序下不支持的USB设備几乎都是生产厂商特定的产品。如果生产厂商在他们的产品中使用自己定义的协议他们就需要为此设备创建特定的驱动程序。当然我們知道有些生产厂商公开他们的USB协议,并帮助编写一个linux程序驱动程序的开发然而有些生产厂商却根本不公开他们的USB协议。因为每一个鈈同的协议都会产生一个新的驱动程序所以就有了这个通用的USB驱动骨架程序, 它是以pci 骨架为模板的

如果你准备写一个编写一个linux程序驱動程序,首先要熟悉USB协议规范USB主页上有它的帮助。一些比较典型的驱动可以在上面发现同时还介绍了USB urbs的概念,而这个是usb驱动程序中最基本的

编写一个linux程序 USB 驱动程序需要做的第一件事情就是在编写一个linux程序 USB 子系统里注册,并提供一些相关信息例如这个驱动程序支持那種设备,当被支持的设备从系统插入或拔出时会有哪些动作。所有这些信息都传送到USB 子系统中

关于更多编写一个linux程序的学习,请查阅書籍《编写一个linux程序就该这么学》

我要回帖

更多关于 编写一个linux程序 的文章

 

随机推荐