赞
踩
很久没有写《LDD3》的学习笔记了,趁着做项目的机会,学习一下USB的驱动程序,并写学习笔记。
。
如果刚开始接触USB,会感觉无从下手,这种感觉就像我第一次接触嵌入式Linux一样。所以要对USB的硬件原理、数据传输和在USB电缆上传输的数据格式有一定的了解。所以推荐一篇《实用USB术语详解》 。再去CEPARK ( China Electronics Park ) 电子园 看完上面的经典教程 和基础知识 。看了上面的文章,您会对USB有一定的认识(如果你再写一个简单的在51上的USB固件就更好了),在学习USB的Linux驱动您就会觉得很轻松了。
如果您想要更细的知识,你可以去老古开发网下一本《USB结构体系》 ,有需要的时候翻一下。您也可以买专门介绍USB的书籍。
拓扑结构上, 一个 USB 子系统并不是以总线的方式来分布; 它是一棵由几个点对点连接构成的树。这些连接是连接设备和集线器的4线电缆(地, 电源, 和 2 个差分信号线), 如同以太网的双绞线。USB主控制器负责询问每个USB设备是否有数据需要发送。
由 于这个拓扑结构,一个 USB 设备在没有主控制器要求的情况下不能发送数据. 也就是说:USB是单主方式的实现,主机轮询各外设。但是设备也可以要求一个固定的数据传输带宽,以保证可靠的音视频I/O。USB只作为数据传输通道, 对他所收发的数据格式没有特殊的内容和结构上的要求,也就是类似于透传。
Linux内核支持两种主要类型的USB驱动程序:Host系统上的驱动程序(USB device driver)和device上的驱动程序(USB gadget driver)(设备端驱动)。
USB驱动程序存在于不同的内核子系统和USB硬件控制器之中。USB核心为USB驱动程序提供了一个用于访问和控制USB硬件的接口,它隐藏了USB控制器的硬件细节。从这里我们要知道:《LDD3》所谓的USB驱动是针对USB核心提供的接口而写的,并不是真正去操纵USB硬件控制器中的寄存器。这样你必须保证你的板子上CPU的USB硬件控制器的驱动是可用的。否则您就得先搞定CPU的USB硬件控制器的驱动才行。
以下是Linux内核中USB驱动的软件构架:
如左下图所示, 从主机侧的观念去看,在Linux 驱动中,USB 驱动处于最底层的是USB 主机控制器硬件,在其之上运行的是USB 主机控制器驱动,主机控制器之上为USB 核心层,再上层为USB 设备驱动层(插入主机上的U 盘、鼠标、USB 转串口等设备驱动)。因此,在主机侧的层次结构中,要实现的USB 驱动包括两类:USB 主机控制器驱动和USB 设备驱动,前者控制插入其中的USB 设备,后者控制USB 设备如何与主机通信。Linux 内核USB 核心负责USB 驱动管理和协议处理的主要工作。主机控制器驱动和设备驱动之间的USB 核心非常重要,其功能包括:通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB 主机控制器驱动提供编程接口;通过全局变量维护整个系统的USB 设备信息;完成设备热插拔控制、总线数据传输控制等。 如右下图所示,Linux 内核中USB 设备侧驱动程序分为3 个层次:UDC 驱动程序、Gadget API 和Gadget 驱动程序。UDC 驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与硬件相关操作的回调函数。当前Gadget API 是UDC 驱动程序回调函数的简单包装。Gadget 驱动程序具体控制USB 设备功能的实现,使设备表现出“网络连接”、“打印机”或“USB Mass Storage ”等特性,它使用Gadget API 控制UDC 实现上述功能。Gadget API 把下层的UDC 驱动程序和上层的Gadget 驱动程序隔离开,使得在Linux 系统中编写USB 设备侧驱动程序时能够把功能的实现和底层通信分离。
以上的图和文字载自 华清远见的《Linux设备驱动开发详解 》 |
《LDD3》中的USB驱动的介绍分以下几块:
-----------基础知识部分--------------
(1)USB设备基础
端点
接口
配置
(2)USB和sysfs
(3)USB urb
struct urb
创建和销毁urb
中断urb
批量urb
控制urb
等时urb
提交urb
结束urb:结束回调处理例程
取消urb
-------------------------------
--------驱动编写部分(一)--------------
(4)编写USB驱动程序
驱动支持什么设备
注册USB驱动程序
探测和断开的细节
提交和控制urb
-------------------------------
--------驱动编写部分(二)--------------
(5)不使用urb的USB传输
usb_bulk_msg
usb_control_msg
(6)其他USB数据函数
-------------------------------
我这份笔记也基本按照这个顺序来写。之后会添加USB gadget的驱动知识。
Linux设备驱动程序学习(17)-USB 驱动程序(二) | ||||||
|
内核使用2.6.29.4
USB设备其实很复杂,但是Linux内核提供了一个称为USB core的子系统来处理了大部分的复杂工作,所以这里所描述的是驱动程序和USB core之间的接口。
在USB设备组织结构中,从上到下分为设备(device)、配置(config)、接口(interface)和端点(endpoint)四个层次。
对于这四个层次的简单描述如下:
设备通常具有一个或多个的配置
配置经常具有一个或多个的接口
接口通常具有一个或多个的设置
接口没有或具有一个以上的端点
设备
很明显,地代表了一个插入的USB设备,在内核使用数据结构 struct usb_device来描述整个USB设备。(include/linux/usb.h)
# ifdef CONFIG_PM //电源管理相关 |
|
|
|
|
|
上述结构体中unsigned int pipe;的生成函数(define):
|
上述结构体中unsigned int transfer_flags;的值域:
|
上述结构体中int status;的常用值(in include/asm-generic/errno.h and errno_base.h) :
|
创建和注销 urb
struct urb 结构不能静态创建,必须使用 usb_alloc_urb 函数创建. 函数原型:
|
如果驱动已经对 urb 使用完毕, 必须调用 usb_free_urb 函数,释放urb。函数原型:
|
根据内核源码,可以通过自己kmalloc一个空间来创建urb,然后必须使用
|
初始化 urb
|
其实那三个初始化函数只是简单的包装,是inline函数。所以其实和等时的urb手动初始化没什么大的区别。
提交 urb
一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:
|
在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.
urb结束处理例程
使用以下函数停止一个已经提交给 USB 核心的 urb:
|
如果调用usb_kill_urb函数,则 urb 的生命周期将被终止. 这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用.
对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于在中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).
|
|
|
|
创建一个简单的 struct usb_driver 结构, 只有 4 个成员需要初始化:
|
|
探测和断开的细节
在 struct usb_driver 结构中, 有 2 个 USB 核心在适当的时候调用的函数:
(1)当设备安装时, 如果 USB 核心认为这个驱动可以处理,则调用探测(probe)函数,探测函数检查传递给它的设备信息, 并判断驱动是否真正合适这个设备.
(2)由于某些原因,设备被移除或驱动不再控制设备时,调用断开(disconnect)函数,做适当清理.
探测和断开回调函数都在 USB 集线器内核线程上下文中被调用, 因此它们休眠是合法的. 为了缩短 USB 探测时间,大部分工作尽可能在设备打开时完成.这是因为 USB 核心是在一个线程中处理 USB 设备的添加和移除, 因此任何慢设备驱动都可能使 USB 设备探测时间变长。
|
在设备注册之后,USB 驱动的后续操作都是通过struct usb_interface 获得设备的端点信息,所以要使用 usb_set_intfdata 将前面获得的端点信息保存到struct usb_interface 下的struct device 中的void *driver_data;中,以方便以后的操作。在usb-skeleton的probe函数中的代码:
|
之后在USB的驱动程序中的打开函数和断开函数中调用usb_get_intfdata来获取端点数据。由于这 2 个函数, USB 驱动不需要为系统中所有当前的设备各保持一个静态指针数组来保存单个设备结构. 对设备信息的非直接引用使得任何 USB 驱动都支持不限数量的设备.
若这个 USB 驱动没有和另一种处理用户和设备交互的子系统(如 input, tty, video......)关联, 驱动可使用 USB 主设备号,以便在用户空间使用传统的字符驱动接口. 为此, USB 驱动必须在探测函数中调用 usb_register_dev 函数, 以注册一个设备到 USB 核心. 在usb-skeleton的probe函数中的代码:
|
当 USB 设备断开, 所有关联到这个设备的资源都应被释放,如果已在探测函数中调用 usb_register_dev 分配了 USB 设备的次设备号, 必须调用函数 usb_deregister_dev 来将次设备号还回 USB 核心.在断开函数中, 需要从接口获取之前调用 usb_set_intfdata 所设置的数据,然后设置struct usb_interface 结构指针为 NULL,以防止错误的访问.而在usb-skeleton的源码如下:
|
当一个 USB 设备调用 disconnect 函数时, 所有当前正被传送的 urb 可自动被 USB 核心取消, 不必显式调用 usb_kill_urb. 在USB设备被断开之后,如果驱动试图调用 usb_submit_urb 提交urb , 将会失败, 错误值为-EPIPE.
提交和控制 urb 的过程
以usb-skeleton源码中的写函数为例:
|
urb 回调函数是在中断上下文运行, 因此它不应做任何内存分配, 持有任何信号量, 或任何可导致进程休眠的事情. 如果从回调中提交 urb 并需要分配新内存块, 需使用 GFP_ATOMIC 标志来告知 USB 核心不要休眠.
使用简单的函数接口(urb函数的包装)
有时只是要发送或接受一些简单的 USB 数据,可以使用简单的函数接口:
|
其他 USB 函数
USB 核心中的一些辅助函数用来从所有的 USB 设备中获取标准信息. 这些函数不能在中断上下文或者持有自旋锁时调用,因为他们内部都是使用上面介绍的简单的接口函数.这里就不一一介绍了,包括《LDD3》介绍的这些函数,在/drivers/usb/core/message.c 都有。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。