Linux Device Driver ---USB
Linux设备驱动程序学习(16)-USB 驱动程序(一)
从此文档开始,内核使用2.6.29.4
很久没有写《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)
struct usb_device {
int devnum; //设备号,是在USB总线的地址
char devpath [16]; //用于消息的设备ID字符串
enum usb_device_state state; //设备状态:已配置、未连接等等
enum usb_device_speed speed; //设备速度:高速、全速、低速或错误
struct usb_tt *tt; //处理传输者信息;用于低速、全速设备和高速HUB
int ttport; //位于tt HUB的设备口
unsigned int toggle[2]; //每个端点的占一位,表明端点的方向([0] = IN, [1] = OUT)
struct usb_device *parent; //上一级HUB指针
struct usb_bus *bus; //总线指针
struct usb_host_endpoint ep0; //端点0数据
struct device dev; //一般的设备接口数据结构
struct usb_device_descriptor descriptor; //USB设备描述符
struct usb_host_config *config; //设备的所有配置
struct usb_host_config *actconfig; //被激活的设备配置
struct usb_host_endpoint *ep_in[16]; //输入端点数组
struct usb_host_endpoint *ep_out[16]; //输出端点数组
char **rawdescriptors; //每个配置的raw描述符
unsigned short bus_mA; //可使用的总线电流
u8 portnum; //父端口号
u8 level; //USB HUB的层数
unsigned can_submit:1; //URB可被提交标志
unsigned discon_suspended:1; //暂停时断开标志
unsigned persist_enabled:1; //USB_PERSIST使能标志
unsigned have_langid:1; //string_langid存在标志
unsigned authorized:1;
unsigned authenticated:1;
unsigned wusb:1; //无线USB标志
int string_langid; //字符串语言ID
/* static strings from the device */ //设备的静态字符串
char *product; //产品名
char *manufacturer; //厂商名
char *serial; //产品串号
struct list_head filelist; //此设备打开的usbfs文件
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev; //用户空间访问的为usbfs设备创建的USB类设备
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry; //设备的usbfs入口
#endif
int maxchild; //(若为HUB)接口数
struct usb_device *children[USB_MAXCHILDREN];//连接在这个HUB上的子设备
int pm_usage_cnt; //自动挂起的使用计数
u32 quirks;
atomic_t urbnum; //这个设备所提交的URB计数
unsigned long active_duration; //激活后使用计时
#ifdef CONFIG_PM //电源管理相关
struct delayed_work autosuspend; //自动挂起的延时
struct work_struct autoresume; //(中断的)自动唤醒需求
struct mutex pm_mutex; //PM的互斥锁
unsigned long last_busy; //最后使用的时间
int autosuspend_delay;
unsigned long connect_time; //第一次连接的时间
unsigned auto_pm:1; //自动挂起/唤醒
unsigned do_remote_wakeup:1; //远程唤醒
unsigned reset_resume:1; //使用复位替代唤醒
unsigned autosuspend_disabled:1; //挂起关闭
unsigned autoresume_disabled:1; //唤醒关闭
unsigned skip_sys_resume:1; //跳过下个系统唤醒
#endif
struct wusb_dev *wusb_dev; //(如果为无线USB)连接到WUSB特定的数据结构
};
配置
一个USB设备可以有多个配置,并可在它们之间转换以改变设备的状态。比如一个设备可以通过下载固件(firmware)的方式改变设备的使用状态(我感觉类似FPGA或CPLD),那么USB设备就要切换配置,来完成这个工作。一个时刻只能有一个配置可以被激活。Linux使用结构 structusb_host_config来描述USB配置。我们编写的USB设备驱动通常不需要读写这些结构的任何值。可在内核源码的文件include/linux/usb.h中找到对它们的描述。
struct usb_host_config {
struct usb_config_descriptor desc; //配置描述符
char *string; /* 配置的字符串指针(如果存在) */
struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; //配置的接口联合描述符链表
struct usb_interface *interface[USB_MAXINTERFACES]; //接口描述符链表
struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
接口
USB端点被绑为接口,USB接口只处理一种USB逻辑连接。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要一个以上的驱动程序。这可以在“晕到死 差屁”系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
USB 接口可以有其他的设置,它是对接口参数的不同选择. 接口的初始化的状态是第一个设置,编号为0。 其他的设置可以以不同方式控制独立的端点。
USB接口在内核中使用 struct usb_interface 来描述。USB 核心将其传递给USB驱动,并由USB驱动负责后续的控制。
struct usb_interface {
struct usb_host_interface *altsetting; /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/
struct usb_host_interface *cur_altsetting; /* 指向altsetting内部的指针,表示当前激活的接口配置*/
unsigned num_altsetting; /* 可选设置的数量*/
/* If there is an interface association descriptor then it will list the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor; /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/
/*以下的数据在我们写的驱动中基本不用考虑,系统会自动设置*/
enum usb_interface_condition condition; /* state of binding */
unsigned is_active:1; /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;
struct device dev; /* 接口特定的设备信息 */
struct device *usb_dev;
int pm_usage_cnt; /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};
struct usb_host_interface {
struct usb_interface_descriptor desc; //接口描述符
struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
char *string; /* 接口描述字符串 */
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
端点
USB 通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道。
一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:
控制CONTROL
控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.
中断INTERRUPT
每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.
批量BULK
批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USBMass Storage和USB网络设备上。
等时ISOCHRONOUS
等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。
控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。
端点在内核中使用结构 struct usb_host_endpoint 来描述,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端点描述符
struct list_head urb_list; //此端点的URB对列,由USB核心维护
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;
};
/*-------------------------------------------------------------------------*/
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress; /*这个特定端点的 USB 地址,这个8位数据包含端点的方向,结合位掩码 USB_DIR_OUT 和 USB_DIR_IN 使用, 确定这个端点的数据方向。*/
__u8 bmAttributes; //这是端点的类型,位掩码如下
__le16 wMaxPacketSize; /*端点可以一次处理的最大字节数。驱动可以发送比这个值大的数据量到端点, 但是当真正传送到设备时,数据会被分为 wMaxPakcetSize 大小的块。对于高速设备, 通过使用高位部分几个额外位,可用来支持端点的高带宽模式。*/
__u8 bInterval; //如果端点是中断类型,该值是端点的间隔设置,即端点的中断请求间的间隔时间,以毫秒为单位
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */
/*
* Endpoints
*/
#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress 端点的 USB 地址掩码 */
#define USB_ENDPOINT_DIR_MASK 0x80 /* in bEndpointAddress 数据方向掩码 */
#define USB_DIR_OUT 0 /* to device */
#define USB_DIR_IN 0x80 /* to host */
#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* bmAttributes 的位掩码*/
#define USB_ENDPOINT_XFER_CONTROL 0
#define USB_ENDPOINT_XFER_ISOC 1
#define USB_ENDPOINT_XFER_BULK 2
#define USB_ENDPOINT_XFER_INT 3
#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80
/*-------------------------------------------------------------------------*/
USB 和 sysfs
由于单个 USB 物理设备的复杂性,设备在 sysfs 中的表示也非常复杂。
物理 USB 设备(通过 struct usb_device 表示)和单个 USB 接口(由 struct usb_interface 表示)都作为单个设备出现在 sysfs 中,这是因为这两个结构都包含一个 struct device结构。以下内容是我的USB鼠标在 sysfs 中的目录树:
/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1 (表示 usb_device 结构)
.
|-- 3-1:1.0 (鼠标所对应的usb_interface)
| |-- 0003:046D:C018.0003
| | |-- driver -> ../../../../../../../bus/hid/drivers/generic-usb
| | |-- power
| | | `-- wakeup
| | |-- subsystem -> ../../../../../../../bus/hid
| | `-- uevent
| |-- bAlternateSetting
| |-- bInterfaceClass
| |-- bInterfaceNumber
| |-- bInterfaceProtocol
| |-- bInterfaceSubClass
| |-- bNumEndpoints
| |-- driver -> ../../../../../../bus/usb/drivers/usbhid
| |-- ep_81 -
内核使用2.6.29.4
USB设备其实很复杂,但是Linux内核提供了一个称为USB core的子系统来处理了大部分的复杂工作,所以这里所描述的是驱动程序和USB core之间的接口。
在USB设备组织结构中,从上到下分为设备(device)、配置(config)、接口(interface)和端点(endpoint)四个层次。
对于这四个层次的简单描述如下:
设备通常具有一个或多个的配置
配置经常具有一个或多个的接口
接口通常具有一个或多个的设置
接口没有或具有一个以上的端点
设备
很明显,地代表了一个插入的USB设备,在内核使用数据结构 struct usb_device来描述整个USB设备。(include/linux/usb.h)
struct usb_device {
int devnum; //设备号,是在USB总线的地址
char devpath [16]; //用于消息的设备ID字符串
enum usb_device_state state; //设备状态:已配置、未连接等等
enum usb_device_speed speed; //设备速度:高速、全速、低速或错误
struct usb_tt *tt; //处理传输者信息;用于低速、全速设备和高速HUB
int ttport; //位于tt HUB的设备口
unsigned int toggle[2]; //每个端点的占一位,表明端点的方向([0] = IN, [1] = OUT)
struct usb_device *parent; //上一级HUB指针
struct usb_bus *bus; //总线指针
struct usb_host_endpoint ep0; //端点0数据
struct device dev; //一般的设备接口数据结构
struct usb_device_descriptor descriptor; //USB设备描述符
struct usb_host_config *config; //设备的所有配置
struct usb_host_config *actconfig; //被激活的设备配置
struct usb_host_endpoint *ep_in[16]; //输入端点数组
struct usb_host_endpoint *ep_out[16]; //输出端点数组
char **rawdescriptors; //每个配置的raw描述符
unsigned short bus_mA; //可使用的总线电流
u8 portnum; //父端口号
u8 level; //USB HUB的层数
unsigned can_submit:1; //URB可被提交标志
unsigned discon_suspended:1; //暂停时断开标志
unsigned persist_enabled:1; //USB_PERSIST使能标志
unsigned have_langid:1; //string_langid存在标志
unsigned authorized:1;
unsigned authenticated:1;
unsigned wusb:1; //无线USB标志
int string_langid; //字符串语言ID
/* static strings from the device */ //设备的静态字符串
char *product; //产品名
char *manufacturer; //厂商名
char *serial; //产品串号
struct list_head filelist; //此设备打开的usbfs文件
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev; //用户空间访问的为usbfs设备创建的USB类设备
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry; //设备的usbfs入口
#endif
int maxchild; //(若为HUB)接口数
struct usb_device *children[USB_MAXCHILDREN];//连接在这个HUB上的子设备
int pm_usage_cnt; //自动挂起的使用计数
u32 quirks;
atomic_t urbnum; //这个设备所提交的URB计数
unsigned long active_duration; //激活后使用计时
#ifdef CONFIG_PM //电源管理相关
struct delayed_work autosuspend; //自动挂起的延时
struct work_struct autoresume; //(中断的)自动唤醒需求
struct mutex pm_mutex; //PM的互斥锁
unsigned long last_busy; //最后使用的时间
int autosuspend_delay;
unsigned long connect_time; //第一次连接的时间
unsigned auto_pm:1; //自动挂起/唤醒
unsigned do_remote_wakeup:1; //远程唤醒
unsigned reset_resume:1; //使用复位替代唤醒
unsigned autosuspend_disabled:1; //挂起关闭
unsigned autoresume_disabled:1; //唤醒关闭
unsigned skip_sys_resume:1; //跳过下个系统唤醒
#endif
struct wusb_dev *wusb_dev; //(如果为无线USB)连接到WUSB特定的数据结构
};
配置
一个USB设备可以有多个配置,并可在它们之间转换以改变设备的状态。比如一个设备可以通过下载固件(firmware)的方式改变设备的使用状态(我感觉类似FPGA或CPLD),那么USB设备就要切换配置,来完成这个工作。一个时刻只能有一个配置可以被激活。Linux使用结构 structusb_host_config来描述USB配置。我们编写的USB设备驱动通常不需要读写这些结构的任何值。可在内核源码的文件include/linux/usb.h中找到对它们的描述。
struct usb_host_config {
struct usb_config_descriptor desc; //配置描述符
char *string; /* 配置的字符串指针(如果存在) */
struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; //配置的接口联合描述符链表
struct usb_interface *interface[USB_MAXINTERFACES]; //接口描述符链表
struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
接口
USB端点被绑为接口,USB接口只处理一种USB逻辑连接。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要一个以上的驱动程序。这可以在“晕到死 差屁”系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
USB 接口可以有其他的设置,它是对接口参数的不同选择. 接口的初始化的状态是第一个设置,编号为0。 其他的设置可以以不同方式控制独立的端点。
USB接口在内核中使用 struct usb_interface 来描述。USB 核心将其传递给USB驱动,并由USB驱动负责后续的控制。
struct usb_interface {
struct usb_host_interface *altsetting; /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/
struct usb_host_interface *cur_altsetting; /* 指向altsetting内部的指针,表示当前激活的接口配置*/
unsigned num_altsetting; /* 可选设置的数量*/
/* If there is an interface association descriptor then it will list the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor; /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/
/*以下的数据在我们写的驱动中基本不用考虑,系统会自动设置*/
enum usb_interface_condition condition; /* state of binding */
unsigned is_active:1; /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;
struct device dev; /* 接口特定的设备信息 */
struct device *usb_dev;
int pm_usage_cnt; /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};
struct usb_host_interface {
struct usb_interface_descriptor desc; //接口描述符
struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
char *string; /* 接口描述字符串 */
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
端点
USB 通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道。
一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:
控制CONTROL
控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.
中断INTERRUPT
每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.
批量BULK
批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USBMass Storage和USB网络设备上。
等时ISOCHRONOUS
等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。
控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。
端点在内核中使用结构 struct usb_host_endpoint 来描述,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端点描述符
struct list_head urb_list; //此端点的URB对列,由USB核心维护
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;
};
/*-------------------------------------------------------------------------*/
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress; /*这个特定端点的 USB 地址,这个8位数据包含端点的方向,结合位掩码 USB_DIR_OUT 和 USB_DIR_IN 使用, 确定这个端点的数据方向。*/
__u8 bmAttributes; //这是端点的类型,位掩码如下
__le16 wMaxPacketSize; /*端点可以一次处理的最大字节数。驱动可以发送比这个值大的数据量到端点, 但是当真正传送到设备时,数据会被分为 wMaxPakcetSize 大小的块。对于高速设备, 通过使用高位部分几个额外位,可用来支持端点的高带宽模式。*/
__u8 bInterval; //如果端点是中断类型,该值是端点的间隔设置,即端点的中断请求间的间隔时间,以毫秒为单位
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */
/*
* Endpoints
*/
#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress 端点的 USB 地址掩码 */
#define USB_ENDPOINT_DIR_MASK 0x80 /* in bEndpointAddress 数据方向掩码 */
#define USB_DIR_OUT 0 /* to device */
#define USB_DIR_IN 0x80 /* to host */
#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* bmAttributes 的位掩码*/
#define USB_ENDPOINT_XFER_CONTROL 0
#define USB_ENDPOINT_XFER_ISOC 1
#define USB_ENDPOINT_XFER_BULK 2
#define USB_ENDPOINT_XFER_INT 3
#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80
/*-------------------------------------------------------------------------*/
USB 和 sysfs
由于单个 USB 物理设备的复杂性,设备在 sysfs 中的表示也非常复杂。
物理 USB 设备(通过 struct usb_device 表示)和单个 USB 接口(由 struct usb_interface 表示)都作为单个设备出现在 sysfs 中,这是因为这两个结构都包含一个 struct device结构。以下内容是我的USB鼠标在 sysfs 中的目录树:
/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1 (表示 usb_device 结构)
.
|-- 3-1:1.0 (鼠标所对应的usb_interface)
| |-- 0003:046D:C018.0003
| | |-- driver -> ../../../../../../../bus/hid/drivers/generic-usb
| | |-- power
| | | `-- wakeup
| | |-- subsystem -> ../../../../../../../bus/hid
| | `-- uevent
| |-- bAlternateSetting
| |-- bInterfaceClass
| |-- bInterfaceNumber
| |-- bInterfaceProtocol
| |-- bInterfaceSubClass
| |-- bNumEndpoints
| |-- driver -> ../../../../../../bus/usb/drivers/usbhid
| |-- ep_81 -> usb_endpoint/usbdev3.4_ep81
| |-- input
| | `-- input6
| | |-- capabilities
| | | |-- abs
| | | |-- ev
| | | |-- ff
| | | |-- key
| | | |-- led
| | | |-- msc
| | | |-- rel
| | | |-- snd
| | | `-- sw
| | |-- device -> ../../../3-1:1.0
| | |-- event3
| | | |-- dev
| | | |-- device -> ../../input6
| | | |-- power
| | | | `-- wakeup
| | | |-- subsystem -> ../../../../../../../../../class/input
| | | `-- uevent
| | |-- id
| | | |-- bustype
| | | |-- product
| | | |-- vendor
| | | `-- version
| | |-- modalias
| | |-- mouse1
| | | |-- dev
| | | |-- device -> ../../input6
| | | |-- power
| | | | `-- wakeup
| | | |-- subsystem -> ../../../../../../../../../class/input
| | | `-- uevent
| | |-- name
| | |-- phys
| | |-- power
| | | `-- wakeup
| | |-- subsystem -> ../../../../../../../../class/input
| | |-- uevent
| | `-- uniq
| |-- modalias
| |-- power
| | `-- wakeup
| |-- subsystem -> ../../../../../../bus/usb
| |-- supports_autosuspend
| |-- uevent
| `-- usb_endpoint
| `-- usbdev3.4_ep81
| |-- bEndpointAddress
| |-- bInterval
| |-- bLength
| |-- bmAttributes
| |-- dev
| |-- device -> ../../../3-1:1.0
| |-- direction
| |-- interval
| |-- power
| | `-- wakeup
| |-- subsystem -> ../../../../../../../../class/usb_endpoint
| |-- type
| |-- uevent
| `-- wMaxPacketSize
|-- authorized
|-- bConfigurationValue
|-- bDeviceClass
|-- bDeviceProtocol
|-- bDeviceSubClass
|-- bMaxPacketSize0
|-- bMaxPower
|-- bNumConfigurations
|-- bNumInterfaces
|-- bcdDevice
|-- bmAttributes
|-- busnum
|-- configuration
|-- descriptors
|-- dev
|-- devnum
|-- driver -> ../../../../../bus/usb/drivers/usb
|-- ep_00 -> usb_endpoint/usbdev3.4_ep00
|-- idProduct
|-- idVendor
|-- manufacturer
|-- maxchild
|-- power
| |-- active_duration
| |-- autosuspend
| |-- connected_duration
| |-- level
| |-- persist
| `-- wakeup
|-- product
|-- quirks
|-- speed
|-- subsystem -> ../../../../../bus/usb
|-- uevent
|-- urbnum
|-- usb_endpoint
| `-- usbdev3.4_ep00
| |-- bEndpointAddress
| |-- bInterval
| |-- bLength
| |-- bmAttributes
| |-- dev
| |-- device -> ../../../3-1
| |-- direction
| |-- interval
| |-- power
| | `-- wakeup
| |-- subsystem -> ../../../../../../../class/usb_endpoint
| |-- type
| |-- uevent
| `-- wMaxPacketSize
`-- version
38 directories, 91 files
USB sysfs 设备命名方法是: root_hub-hub_port:config.interface
随着USB集线器层次的增加, 集线器端口号被添加到字符串中紧随着链中之前的集线器端口号。对一个 2 层的树, 设备为:
root_hub-hub_port-hub_port:config.interface ,以此类推。
USB urb (USB request block)
内核使用2.6.29.4
USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。
urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。
一个 urb 的典型生命循环如下:
(1)被创建;
(2)被分配给一个特定 USB 设备的特定端点;
(3)被提交给 USB 核心;
(4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
(5)被 USB 主机控制器驱动处理, 并传送到设备;
(6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。
urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。
struct urb
struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* URB引用计数 */
void *hcpriv; /* host控制器的私有数据 */
atomic_t use_count; /* 当前提交计数 */
atomic_t reject; /* 提交失败计数 */
int unlinked; /* 连接失败代码 */
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list; /* list head for use by the urb's
* current owner */
struct list_head anchor_list; /* the URB may be anchored */
struct usb_anchor *anchor;
struct usb_device *dev; /* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.*/
struct usb_host_endpoint *ep; /* (internal) pointer to endpoint */
unsigned int pipe; /* 这个 urb 所要发送到的特定struct usb_device的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/
int status; /*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时urb 的详细状态, 应当检查 iso_frame_desc 变量. */
unsigned int transfer_flags; /* 传输设置*/
void *transfer_buffer; /* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。 对控制端点, 这个缓冲区用于数据中转*/
dma_addr_t transfer_dma; /* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/
int transfer_buffer_length; /*transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个 USB设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/
int actual_length; /*当这个 urb 完成后, 该变量被设置为这个 urb (对于 OUT urb)发送或(对于 IN urb)接受数据的真实长度.对于 INurb, 必须是用此变量而非 transfer_buffer_length , 因为接收的数据可能比整个缓冲小*/
unsigned char *setup_packet; /* 指向控制urb的设置数据包指针.它在传送缓冲中的数据之前被传送(用于控制 urb)*/
dma_addr_t setup_dma; /* 控制 urb 用于设置数据包的 DMA 缓冲区地址,它在传送普通缓冲区中的数据之前被传送(用于控制 urb)*/
int start_frame; /* 设置或返回初始的帧数量(用于等时urb) */
int number_of_packets; /* 指定urb所处理的等时传输缓冲区的数量(用于等时urb,在urb被发送到USB核心前,必须设置) */
int interval; /*urb被轮询的时间间隔. 仅对中断或等时 urb 有效. 这个值的单位依据设备速度而不同. 对于低速和高速的设备, 单位是帧, 它等同于毫秒.对于其他设备, 单位是微帧, 等同于 1/8 毫秒. 在 urb被发送到 USB 核心之前,此值必须设置.*/
int error_count; /* 等时urb的错误计数,由USB核心设置 */
void *context; /* 指向一个可以被USB驱动模块设置的数据块. 当 urb 被返回到驱动时,可在结束处理例程中使用. */
usb_complete_t complete; /* 结束处理例程函数指针, 当 urb 被完全传送或发生错误,它将被 USB 核心调用. 此函数检查这个 urb, 并决定释放它或重新提交给另一个传输中*/
struct usb_iso_packet_descriptor iso_frame_desc[0];
/* (仅用于等时urb)usb_iso_packet_descriptor结构体允许单个urb一次定义许多等时传输,它用于收集每个单独的传输状态*/
};
struct usb_iso_packet_descriptor {
unsigned int offset; /* 该数据包的数据在传输缓冲区中的偏移量(第一个字节为0) */
unsigned int length; /* 该数据包的传输缓冲区大小 */
unsigned int actual_length; /* 等时数据包接收到传输缓冲区中的数据长度 */
int status; /* 该数据包的单个等时传输状态。它可以把相同的返回值作为主struct urb 结构体的状态变量 */
};
typedef void (*usb_complete_t)(struct urb *);
上述结构体中unsigned int pipe;的生成函数(define):
static inline unsigned int __create_pipe(struct usb_device *dev,
unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint) /
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev,endpoint) /
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev,endpoint) /
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev,endpoint) /
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev,endpoint) /
((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev,endpoint) /
((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev,endpoint) /
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev,endpoint) /
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
//snd:OUT rcv:IN ctrl:控制 isoc:等时 bulk:批量 int:中断
上述结构体中unsigned int transfer_flags;的值域:
/*
* urb->transfer_flags:
*
* Note: URB_DIR_IN/OUT is automatically set in usb_submit_urb().
*/
#define URB_SHORT_NOT_OK 0x0001 /* 置位时,任何在 IN 端点上发生的简短读取, 被 USB 核心当作错误. 仅对从 USB 设备读取的 urb 有用 */
#define URB_ISO_ASAP 0x0002 /* 若为等时 urb , 驱动想调度这个 urb 时,可置位该位, 只要带宽允许且想在此时设置 urb 中的 start_frame 变量. 若没有置位,则驱动必须指定 start_frame 值,且传输如果不能在当
时启动的话,必须能够正确恢复 */
#define URB_NO_TRANSFER_DMA_MAP 0x0004 /* 当 urb 包含要被发送的 DMA 缓冲时,应被置位.USB 核心使用就会使用 transfer_dma 变量指向的缓冲, 而不是被 transfer_buffer 变量指向的缓冲. */
#define URB_NO_SETUP_DMA_MAP 0x0008 /* 和 URB_NO_TRANSFER_DMA_MAP 类似, 这个位用来控制 DMA 缓冲已经建立的 urb. 如果它被置位, USB 核心使用 setup_dma 变量而不是 setup_packet 变量指向的缓冲. */
#define URB_NO_FSBR 0x0020 /*仅 UHCI USB 主机控制器驱动使用, 并且告诉它不要试图使用前端总线回收( Front Side Bus Reclamation)逻辑. 这个位通常应不设置, 因为有 UHCI 主机控制器的机器会增加 CPU 负担, 且PCI 总线会忙于等待设置了这个位的 urb */
#define URB_ZERO_PACKET 0x0040 /* 如果置位, 批量 OUT urb 通过发送不包含数据的短包来结束, 这时数据对齐到一个端点数据包边界. 这被一些掉线的 USB 设备需要该位才能正确工作 */
#define URB_NO_INTERRUPT 0x0080 /* 如果置位, 当 urb 结束时硬件可能不产生一个中断. 该位应当小心使用并且只在多个 urb 排队到相同端点时才使用. USB 核心函数使用该位进行 DMA 缓冲传送. */
#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */
#define URB_DIR_IN 0x0200 /* Transfer from device to host */
#define URB_DIR_OUT 0
#define URB_DIR_MASK URB_DIR_IN
上述结构体中int status;的常用值(in include/asm-generic/errno.h and errno_base.h) :
// 0 表示 urb 传送成功*/
//以下各个定义在使用时为负值
#define ENOENT 2 /* urb 被 usb_kill_urb 停止 */
#define ECONNRESET 104 /* urb 被 usb_unlink_urb 去链, 且 transfer_flags 被设为 URB_ASYNC_UNLINK */
#define EINPROGRESS 115 /* urb 仍在 USB 主机控制器处理 */
#define EPROTO 71 /* urb 发生错误: 在传送中发生bitstuff 错误或硬件没有及时收到响应帧 */
#define EILSEQ 84 /* urb 传送中出现 CRC 较验错 */
#define EPIPE 32 /* 端点被停止. 若此端点不是控制端点, 则这个错误可通过函数 usb_clear_halt 清除 */
#define ECOMM 70 /* 数据传输时的接收速度快于写入系统内存的速度. 此错误仅出现在 IN urb */
#define ENOSR 63 /* 从系统内存中获取数据的速度赶不上USB 数据传送速度,此错误仅出现在 OUT urb. */
#define EOVERFLOW 75 /* urb 发生"babble"(串扰)错误:端点接受的数据大于端点的最大数据包大小 */
#define EREMOTEIO 181 /* 当 urb 的 transfer_flags 变量的 URB_SHORT_NOT_OK 标志被设置, urb 请求的数据没有完整地收到 */
#define ENODEV 19 /* USB 设备从系统中拔出 */
#define EXDEV 18 /* 仅发生在等时 urb 中, 表示传送部分完成. 为了确定所传输的内容, 驱动必须看单独的帧状态. */
#define EINVAL 22 /* 如果urb的一个参数设置错误或在提交 urb 给 USB 核心的 usb_submit_urb 调用中, 有不正确的参数,则可能发生次错误 */
#define ESHUTDOWN 108 /* USB 主机控制器驱动有严重错误,它已被禁止, 或者设备从系统中拔出。且这个urb 在设备被移除后被提交. 它也可能发生在 urb 被提交给设备时,设备的配置已被改变*/
创建和注销 urb
struct urb 结构不能静态创建,必须使用 usb_alloc_urb 函数创建. 函数原型:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等时数据包的数目。如果不使用等时urb,则为0
//gfp_t mem_flags : 与传递给 kmalloc 函数调用来从内核分配内存的标志类型相同
//返回值 : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回值是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理
如果驱动已经对 urb 使用完毕, 必须调用 usb_free_urb 函数,释放urb。函数原型:
void usb_free_urb(struct urb *urb);
//struct urb *urb : 要释放的 struct urb 指针
根据内核源码,可以通过自己kmalloc一个空间来创建urb,然后必须使用
void usb_init_urb(struct urb *urb);
进行初始化后才可以继续使用。
其实usb_alloc_urb函数就是这样实现的,所以我当然不推荐这种自找麻烦的做法。
初始化 urb
static inline void usb_fill_int_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval);
static inline void usb_fill_bulk_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context);
static inline void usb_fill_control_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
unsigned char *setup_packet,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context);
//struct urb *urb :指向要被初始化的 urb 的指针
//struct usb_device *dev :指向 urb 要发送到的 USB 设备.
//unsigned int pipe : urb 要被发送到的 USB 设备的特定端点. 必须使用前面提过的 usb_******pipe 函数创建
//void *transfer_buffer :指向外发数据或接收数据的缓冲区的指针.注意:不能是静态缓冲,必须使用 kmalloc 来创建.
//int buffer_length :transfer_buffer 指针指向的缓冲区的大小
//usb_complete_t complete :指向 urb 结束处理例程函数指针
//void *context :指向一个小数据块的指针, 被添加到 urb 结构中,以便被结束处理例程函数获取使用.
//int interval :中断 urb 被调度的间隔.
//函数不设置 urb 中的 transfer_flags 变量, 因此对这个成员的修改必须由驱动手动完成
/*等时 urb 没有初始化函数,必须手动初始化,以下为一个例子*/
urb->dev = dev;
urb->context = uvd;
urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
urb->interval = 1;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
for (j=0; j < FRAMES_PER_DESC; j++) {
urb->iso_frame_desc[j].offset = j;
urb->iso_frame_desc[j].length = 1;
}
其实那三个初始化函数只是简单的包装,是inline函数。所以其实和等时的urb手动初始化没什么大的区别。
提交 urb
一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指针
//gfp_t mem_flags :使用传递给 kmalloc 调用同样的参数, 用来告诉 USB 核心如何及时分配内存缓冲
/*因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量必须正确设置. 根据 usb_submit_urb 被调用的时间,只有 3 个有效值可用:
GFP_ATOMIC
只要满足以下条件,就应当使用此值:
1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.
2.调用者持有自旋锁或者读写锁. 注意如果正持有一个信号量, 这个值不必要.
3.current->state 不是 TASK_RUNNING. 除非驱动已自己改变 current 状态,否则状态应该一直是 TASK_RUNNING .
GFP_NOIO
驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.
GFP_KERNEL
所有不属于之前提到的其他情况
*/
在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.
urb结束处理例程
如果 usb_submit_urb 被成功调用, 并把对 urb 的控制权传递给 USB 核心, 函数返回 0;否则返回一个负的错误代码. 如果函数调用成功, 当 urb 被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB核心就完成了这个urb, 并将它的控制权返回给设备驱动.
只有 3 种结束urb并调用结束处理例程的情况:
(1)urb 被成功发送给设备, 且设备返回正确的确认.如果这样, urb 中的status变量被设置为 0.
(2)发生错误, 错误值记录在 urb 结构中的 status 变量.
(3)urb 从 USB 核心unlink. 这发生在要么当驱动通过调用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一个已提交的 urb,或者在一个 urb 已经被提交给它时设备从系统中去除.
取消 urb
使用以下函数停止一个已经提交给 USB 核心的 urb:
void usb_kill_urb(struct urb *urb)
int usb_unlink_urb(struct urb *urb);
如果调用usb_kill_urb函数,则 urb 的生命周期将被终止. 这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用.
对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于在中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).
编写 USB 驱动程序
(本部分的一些示例源码来自drivers/usb/usb-skeleton.c,它是Linux内核为我们提供的最基础的USB驱动程序,USB骨架程序)
驱动程序把驱动对象注册到 USB 子系统中,之后使用供应商(idVendor)和设备(idProduct)标识来判断对应的硬件是否已经安装.
驱动的设备支持列表
struct usb_device_id 结构提供了这个驱动支持的不同类型 USB 设备的列表. USB 核心通过此列表用来决定设备对应的驱动,热插拔脚本也通过此列表来决定当特定设备被插入系统时,应该自动加载的驱动.
struct usb_device_id {
/* 确定设备信息去和结构体中哪几个字段匹配来判断驱动的适用性 */
__u16 match_flags;
/* Used for product specific matches; range is inclusive */
__u16 idVendor; //USB设备的制造商ID,须向www.usb.org申请
__u16 idProduct; //USB设备的产品ID,有制造商自定
__u16 bcdDevice_lo; /* USB设备的产品版本号最低值*/
__u16 bcdDevice_hi; /* 和最高值,以BCD码来表示。*/
/* 分别定义设备的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中. 这些值指定这个设备的行为, 包括设备上所有的接口 */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* 分别定义单个接口的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中 */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* 这个值不用来匹配驱动的, 驱动用它来在 USB 驱动的探测回调函数中区分不同的设备 */
kernel_ulong_t driver_info;
};
//以上结构体中__u16 match_flags;所使用的define:
//include/linux/mod_devicetable.h
/* Some useful macros to use to create struct usb_device_id */
#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
//include/linux/usb.h
#define USB_DEVICE_ID_MATCH_DEVICE /
(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE /
(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION /
(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO /
(USB_DEVICE_ID_MATCH_DEV_CLASS | /
USB_DEVICE_ID_MATCH_DEV_SUBCLASS | /
USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO /
(USB_DEVICE_ID_MATCH_INT_CLASS | /
USB_DEVICE_ID_MATCH_INT_SUBCLASS | /
USB_DEVICE_ID_MATCH_INT_PROTOCOL)
//这个结构体一般不用手动赋值,以下的宏可以实现赋值:
/**
* USB_DEVICE - macro used to describe a specific usb device
* @vend: the 16 bit USB Vendor ID
* @prod: the 16 bit USB Product ID
*
* This macro is used to create a struct usb_device_id that matches a
* specific device.
*/
//仅和指定的制造商和产品ID匹配,用于需要特定驱动的设备
#define USB_DEVICE(vend,prod) /
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, /
.idVendor = (vend), /
.idProduct = (prod)
/**
* USB_DEVICE_VER - describe a specific usb device with a version range
* @vend: the 16 bit USB Vendor ID
* @prod: the 16 bit USB Product ID
* @lo: the bcdDevice_lo value
* @hi: the bcdDevice_hi value
*
* This macro is used to create a struct usb_device_id that matches a
* specific device, with a version range.
*/
//仅和某版本范围内的指定的制造商和产品ID匹配
#define USB_DEVICE_VER(vend, prod, lo, hi) /
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, /
.idVendor = (vend), /
.idProduct = (prod), /
.bcdDevice_lo = (lo), /
.bcdDevice_hi = (hi)
/**
* USB_DEVICE_INTERFACE_PROTOCOL - describe a usb device with a specific interface protocol
* @vend: the 16 bit USB Vendor ID
* @prod: the 16 bit USB Product ID
* @pr: bInterfaceProtocol value
*
* This macro is used to create a struct usb_device_id that matches a
* specific interface protocol of devices.
*/
//仅和指定的接口协议、制造商和产品ID匹配
#define USB_DEVICE_INTERFACE_PROTOCOL(vend, prod, pr) /
.match_flags = USB_DEVICE_ID_MATCH_DEVICE | /
USB_DEVICE_ID_MATCH_INT_PROTOCOL, /
.idVendor = (vend), /
.idProduct = (prod), /
.bInterfaceProtocol = (pr)
/**
* USB_DEVICE_INFO - macro used to describe a class of usb devices
* @cl: bDeviceClass value
* @sc: bDeviceSubClass value
* @pr: bDeviceProtocol value
*
* This macro is used to create a struct usb_device_id that matches a
* specific class of devices.
*/
//仅和指定的设备类型相匹配
#define USB_DEVICE_INFO(cl, sc, pr) /
.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, /
.bDeviceClass = (cl), /
.bDeviceSubClass = (sc), /
.bDeviceProtocol = (pr)
/**
* USB_INTERFACE_INFO - macro used to describe a class of usb interfaces
* @cl: bInterfaceClass value
* @sc: bInterfaceSubClass value
* @pr: bInterfaceProtocol value
*
* This macro is used to create a struct usb_device_id that matches a
* specific class of interfaces.
*/
//仅和指定的接口类型相匹配
#define USB_INTERFACE_INFO(cl, sc, pr) /
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, /
.bInterfaceClass = (cl), /
.bInterfaceSubClass = (sc), /
.bInterfaceProtocol = (pr)
/**
* USB_DEVICE_AND_INTERFACE_INFO - describe a specific usb device with a class of usb interfaces
* @vend: the 16 bit USB Vendor ID
* @prod: the 16 bit USB Product ID
* @cl: bInterfaceClass value
* @sc: bInterfaceSubClass value
* @pr: bInterfaceProtocol value
*
* This macro is used to create a struct usb_device_id that matches a
* specific device with a specific class of interfaces.
*
* This is especially useful when explicitly matching devices that have
* vendor specific bDeviceClass values, but standards-compliant interfaces.
*/
//仅和指定的制造商、产品ID和接口类型相匹配
#define USB_DEVICE_AND_INTERFACE_INFO(vend, prod, cl, sc, pr) /
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO /
| USB_DEVICE_ID_MATCH_DEVICE, /
.idVendor = (vend), /
.idProduct = (prod), /
.bInterfaceClass = (cl), /
.bInterfaceSubClass = (sc), /
.bInterfaceProtocol = (pr)
/* ----------------------------------------------------------------------- */
对于一个只为一个供应商的一个 USB 设备的简单 USB 设备驱动, 其 struct usb_device_id 可定义如下:
/* Define these values to match your devices */
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, skel_table);
MODULE_DEVICE_TABLE 宏是必需的,它允许用户空间工具判断该驱动可控制什么设备. 对于 USB 驱动, 这个宏中的第一个值必须是 usb .
如果你需要这个驱动被系统中每个 USB 设备调用, 创建仅需设置 driver_info 成员:
static struct usb_device_id usb_ids[] = {
{.driver_info = 42},
{}
};
注册USB驱动程序
所有 USB 驱动都必须创建的主要结构是 struct usb_driver. 这个结构必须被 USB 驱动程序手动填充并且包含多个回调函数和变量, 并向 USB 核心描述 USB 驱动程序:
struct usb_driver {
const char *name;
/*指向驱动程序名字的指针. 它必须在内核所有的 USB 驱动中是唯一的(通常被设为和驱动模块名相同).当驱动在内核中运行时,会出现在/sys/bus/usb/drivers目录中 */
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
/*指向 USB 驱动中探测函数指针. 当USB 核心认为它有一个本驱动可处理的 struct usb_interface时此函数将被调用.USB 核心用来做判断的 struct usb_device_id 指针也被传递给此函数.如果这个 USB 驱动确认传递给它的 structusb_interface, 它应当正确地初始化设备并返回 0. 如果驱动没有确认这个设备或发生错误,则返回负错误值 */
void (*disconnect) (struct usb_interface *intf);
/*指向 USB 驱动的断开函数指针.当 struct usb_interface 从系统中清除或驱动 USB 核心卸载时,函数将被 USB 核心调用*/
int (*ioctl) (struct usb_interface *intf, unsigned int code,
void *buf);
/*指向 USB 驱动的 ioctl 函数指针. 若此函数存在, 在用户空间程序对usbfs 文件系统关联的设备调用 ioctl 时,此函数将被调用. 实际上,当前只有 USB 集线器驱动使用这个 ioctl*/
int (*suspend) (struct usb_interface *intf, pm_message_t message);
/*指向 USB 驱动中挂起函数的指针*/
int (*resume) (struct usb_interface *intf);
/*指向 USB 驱动中恢复函数的指针*/
int (*reset_resume)(struct usb_interface *intf);
/*要复位一个已经被挂起的USB设备时调用此函数*/
int (*pre_reset)(struct usb_interface *intf);
/*在设备被复位之前由usb_reset_composite_device()调用*/
int (*post_reset)(struct usb_interface *intf);
/*在设备被复位之后由usb_reset_composite_device()调用*/
const struct usb_device_id *id_table;
/*指向 struct usb_device_id 表的指针*/
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
/*是struct device_driver driver的再包装,struct device_driver 包含 struct module *owner;*/
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int soft_unbind:1;
};
#define to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)
创建一个简单的 struct usb_driver 结构, 只有 4 个成员需要初始化:
static struct usb_driver skel_driver = {
.name = "skeleton",
.id_table = skel_table,
.probe = skel_probe,
.disconnect = skel_disconnect,
};
//向 USB 核心注册 struct usb_driver
static int __init usb_skel_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&skel_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
/*当 USB 驱动被卸载, struct usb_driver 需要从内核注销(代码如下). 当以下调用发生, 当前绑定到这个驱动的任何 USB 接口将会断开, 并调用断开函数*/
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
探测和断开的细节
在 struct usb_driver 结构中, 有 2 个 USB 核心在适当的时候调用的函数:
(1)当设备安装时, 如果 USB 核心认为这个驱动可以处理,则调用探测(probe)函数,探测函数检查传递给它的设备信息, 并判断驱动是否真正合适这个设备.
(2)由于某些原因,设备被移除或驱动不再控制设备时,调用断开(disconnect)函数,做适当清理.
探测和断开回调函数都在 USB 集线器内核线程上下文中被调用, 因此它们休眠是合法的. 为了缩短 USB探测时间,大部分工作尽可能在设备打开时完成.这是因为 USB 核心是在一个线程中处理 USB 设备的添加和移除, 因此任何慢设备驱动都可能使USB 设备探测时间变长。
探测函数分析
在探测回调函数中, USB 驱动应当初始化它可能用来管理 USB 设备的所有本地结构并保存所有需要的设备信息到本地结构,因为在此时做这些通常更容易.为了和设备通讯,USB 驱动通常要探测设备的端点地址和缓冲大小.以下是usb-skeleton的probe函数中的探测代码:
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting; //从输入的interface中提取当前接口的端点总数
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
/*轮询所有端点*/
endpoint = &iface_desc->endpoint[i].desc; //获得端点的数据结构指针
if (!dev->bulk_in_endpointAddr &&
usb_endpoint_is_bulk_in(endpoint)) { //如果是批量输入端点,
/* we found a bulk in endpoint */
buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
dev->bulk_in_size = buffer_size; //获得端点大小
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; //获得端点地址
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); //为此端点创建缓冲区
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
usb_endpoint_is_bulk_out(endpoint)) { 如果是批量输出端点
/* we found a bulk out endpoint */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; //获得端点地址
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { //如果不是这两种端点,报错
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
//这里端点判断的函数给我们的编程带来了方便:
/*-------------------------------------------------------------------------*/
/**
* usb_endpoint_num - get the endpoint's number
* @epd: endpoint to be checked
*
* Returns @epd's number: 0 to 15.
*/
static inline int usb_endpoint_num(const struct usb_endpoint_descriptor *epd)
{
return epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
}
/**
* usb_endpoint_type - get the endpoint's transfer type
* @epd: endpoint to be checked
*
* Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according
* to @epd's transfer type.
*/
static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd)
{
return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
}
/**
* usb_endpoint_dir_in - check if the endpoint has IN direction
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type IN, otherwise it returns false.
*/
static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd)
{
return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN);
}
/**
* usb_endpoint_dir_out - check if the endpoint has OUT direction
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type OUT, otherwise it returns false.
*/
static inline int usb_endpoint_dir_out(
const struct usb_endpoint_descriptor *epd)
{
return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
}
/**
* usb_endpoint_xfer_bulk - check if the endpoint has bulk transfer type
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type bulk, otherwise it returns false.
*/
static inline int usb_endpoint_xfer_bulk(
const struct usb_endpoint_descriptor *epd)
{
return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_BULK);
}
/**
* usb_endpoint_xfer_control - check if the endpoint has control transfer type
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type control, otherwise it returns false.
*/
static inline int usb_endpoint_xfer_control(
const struct usb_endpoint_descriptor *epd)
{
return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_CONTROL);
}
/**
* usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type interrupt, otherwise it returns
* false.
*/
static inline int usb_endpoint_xfer_int(
const struct usb_endpoint_descriptor *epd)
{
return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_INT);
}
/**
* usb_endpoint_xfer_isoc - check if the endpoint has isochronous transfer type
* @epd: endpoint to be checked
*
* Returns true if the endpoint is of type isochronous, otherwise it returns
* false.
*/
static inline int usb_endpoint_xfer_isoc(
const struct usb_endpoint_descriptor *epd)
{
return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_ISOC);
}
/**
* usb_endpoint_is_bulk_in - check if the endpoint is bulk IN
* @epd: endpoint to be checked
*
* Returns true if the endpoint has bulk transfer type and IN direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_bulk_in(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_bulk(epd) && usb_endpoint_dir_in(epd));
}
/**
* usb_endpoint_is_bulk_out - check if the endpoint is bulk OUT
* @epd: endpoint to be checked
*
* Returns true if the endpoint has bulk transfer type and OUT direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_bulk_out(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_bulk(epd) && usb_endpoint_dir_out(epd));
}
/**
* usb_endpoint_is_int_in - check if the endpoint is interrupt IN
* @epd: endpoint to be checked
*
* Returns true if the endpoint has interrupt transfer type and IN direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_int_in(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_in(epd));
}
/**
* usb_endpoint_is_int_out - check if the endpoint is interrupt OUT
* @epd: endpoint to be checked
*
* Returns true if the endpoint has interrupt transfer type and OUT direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_int_out(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_out(epd));
}
/**
* usb_endpoint_is_isoc_in - check if the endpoint is isochronous IN
* @epd: endpoint to be checked
*
* Returns true if the endpoint has isochronous transfer type and IN direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_isoc_in(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_isoc(epd) && usb_endpoint_dir_in(epd));
}
/**
* usb_endpoint_is_isoc_out - check if the endpoint is isochronous OUT
* @epd: endpoint to be checked
*
* Returns true if the endpoint has isochronous transfer type and OUT direction,
* otherwise it returns false.
*/
static inline int usb_endpoint_is_isoc_out(
const struct usb_endpoint_descriptor *epd)
{
return (usb_endpoint_xfer_isoc(epd) && usb_endpoint_dir_out(epd));
}
/*-------------------------------------------------------------------------*/
在设备注册之后,USB 驱动的后续操作都是通过struct usb_interface 获得设备的端点信息,所以要使用usb_set_intfdata 将前面获得的端点信息保存到struct usb_interface 下的struct device中的void *driver_data;中,以方便以后的操作。在usb-skeleton的probe函数中的代码:
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
之后在USB的驱动程序中的打开函数和断开函数中调用usb_get_intfdata来获取端点数据。由于这 2 个函数, USB驱动不需要为系统中所有当前的设备各保持一个静态指针数组来保存单个设备结构. 对设备信息的非直接引用使得任何 USB 驱动都支持不限数量的设备.
若这个 USB 驱动没有和另一种处理用户和设备交互的子系统(如 input, tty, video......)关联, 驱动可使用USB 主设备号,以便在用户空间使用传统的字符驱动接口. 为此, USB 驱动必须在探测函数中调用 usb_register_dev 函数,以注册一个设备到 USB 核心. 在usb-skeleton的probe函数中的代码:
/* we can register the device now, as it is ready */
retval = usb_register_dev(interface, &skel_class);
if (retval) {
/* something prevented us from registering this driver */
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}
//其中使用到的 struct usb_class_driver 结构体如下:
/**
* struct usb_class_driver - identifies a USB driver that wants to use the USB major number
* @name: the usb class device name for this driver. Will show up in sysfs.
* @fops: pointer to the struct file_operations of this driver.
* @minor_base: the start of the minor range for this driver.
*
* This structure is used for the usb_register_dev() and
* usb_unregister_dev() functions, to consolidate a number of the
* parameters used for them.
*/
struct usb_class_driver {
char *name; //sysfs 用来描述设备的名字
const struct file_operations *fops; // struct file_operations 结构指针, 驱动定义来注册为字符设备
int minor_base;
/*给这个驱动安排的次设备号的起始. 所有和这个驱动相关的设备被创建为从这个值开始的唯一的, 递增的次设备号. 只有 16 个设备被允许在任何时刻和这个驱动关联, 除非 CONFIG_USB_DYNAMIC_MINORS 配置选项被打开. 如
果这样, 忽略这个变量, 并且这个设备的所有的次设备号会以先来先服务的方式分配. 建议打开了这个选项的系统使用类似 udev 的程序来产生系统中的设备节点, 因为一个静态的 /dev 树不会正确工作.*/
};
//而在usb-skeleton的设置如下:
static const struct file_operations skel_fops = {
.owner = THIS_MODULE,
.read = skel_read,
.write = skel_write,
.open = skel_open,
.release = skel_release,
.flush = skel_flush,
};
/*
* usb class driver info in order to get a minor number from the usb core,
* and to have the device registered with the driver core
*/
static struct usb_class_driver skel_class = {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};
当 USB 设备断开, 所有关联到这个设备的资源都应被释放,如果已在探测函数中调用 usb_register_dev 分配了 USB设备的次设备号, 必须调用函数 usb_deregister_dev 来将次设备号还回 USB 核心.在断开函数中, 需要从接口获取之前调用usb_set_intfdata 所设置的数据,然后设置struct usb_interface 结构指针为NULL,以防止错误的访问.而在usb-skeleton的源码如下:
static void skel_disconnect(struct usb_interface *interface)
{
struct usb_skel *dev;
int minor = interface->minor;
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &skel_class);
/* prevent more I/O from starting */
mutex_lock(&dev->io_mutex);
dev->interface = NULL;
mutex_unlock(&dev->io_mutex);
usb_kill_anchored_urbs(&dev->submitted);
/* decrement our usage count */
kref_put(&dev->kref, skel_delete);
info("USB Skeleton #%d now disconnected", minor);
}
当一个 USB 设备调用 disconnect 函数时, 所有当前正被传送的 urb 可自动被 USB 核心取消, 不必显式调用usb_kill_urb. 在USB设备被断开之后,如果驱动试图调用 usb_submit_urb 提交urb , 将会失败,错误值为-EPIPE.
提交和控制 urb 的过程
以usb-skeleton源码中的写函数为例:
static ssize_t skel_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos)
{
struct usb_skel *dev;
int retval = 0;
struct urb *urb = NULL;
char *buf = NULL;
size_t writesize = min(count, (size_t)MAX_TRANSFER);
dev = (struct usb_skel *)file->private_data;
/* verify that we actually have some data to write */
if (count == 0)
goto exit;
/* limit the number of URBs in flight to stop a user from using up all RAM */
if (down_interruptible(&dev->limit_sem)) {
retval = -ERESTARTSYS;
goto exit;
}
spin_lock_irq(&dev->err_lock);
if ((retval = dev->errors) < 0) {
/* any error is reported once */
dev->errors = 0;
/* to preserve notifications about reset */
retval = (retval == -EPIPE) ? retval : -EIO;
}
spin_unlock_irq(&dev->err_lock);
if (retval < 0)
goto error;
/* create a urb, and a buffer for it, and copy the data to the urb */
/*当驱动有数据发送到 USB 设备,首先分配一个 urb */
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
/*以最有效的方式是创建一个 DMA 缓冲区来发送数据到设备, 并拷贝数据到缓冲区*/
buf = usb_buffer_alloc(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, writesize)) {
retval = -EFAULT;
goto error;
}
/* this lock makes sure we don't submit URBs to gone devices */
mutex_lock(&dev->io_mutex);
if (!dev->interface) { /* disconnect() was called */
mutex_unlock(&dev->io_mutex);
retval = -ENODEV;
goto error;
}
/* initialize the urb properly */
/*在将urb提交给 USB 核心之前,正确初始化 urb */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, writesize, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_anchor_urb(urb, &dev->submitted);
/* send the data out the bulk port */
/*提交 urb 给 USB 核心, 由它将 urb 传递给设备*/
retval = usb_submit_urb(urb, GFP_KERNEL);
mutex_unlock(&dev->io_mutex);
if (retval) {
err("%s - failed submitting write urb, error %d", __func__, retval);
goto error_unanchor;
}
/* release our reference to this urb, the USB core will eventually free it entirely */
usb_free_urb(urb);
return writesize;
error_unanchor:
usb_unanchor_urb(urb);
error:
if (urb) {
usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);
usb_free_urb(urb);
}
up(&dev->limit_sem);
exit:
return retval;
}
//当urb被成功传递到 USB 设备(或者在传输中发生了错误), urb 回调函数将被 USB 核心调用.也就是上面初始化 urb 中的 skel_write_bulk_callback
static void skel_write_bulk_callback(struct urb *urb)
{
struct usb_skel *dev;
dev = urb->context;
/* sync/async unlink faults aren't errors */
/*检查 urb 的状态,判断这个 urb 是否成功完成传输*/
if (urb->status) {
if(!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
err("%s - nonzero write bulk status received: %d",
__func__, urb->status);
spin_lock(&dev->err_lock);
dev->errors = urb->status;
spin_unlock(&dev->err_lock);
}
/* free up our allocated buffer */
/*释放分配给这个 urb 的缓冲区.*/
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
up(&dev->limit_sem);
}
urb 回调函数是在中断上下文运行, 因此它不应做任何内存分配, 持有任何信号量, 或任何可导致进程休眠的事情. 如果从回调中提交 urb 并需要分配新内存块, 需使用 GFP_ATOMIC 标志来告知 USB 核心不要休眠.
使用简单的函数接口(urb函数的包装)
有时只是要发送或接受一些简单的 USB 数据,可以使用简单的函数接口:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout)
/*创建批量 urb 并发送到指定的设备, 接着在返回之前等待完成.*/
//struct usb_device *usb_dev :目标 USB 设备指针
//unsigned int pipe :目标 USB 设备的特定端点. 必须使用特定的宏创建.
//void *data :如果是 OUT 端点, 指向要发送到设备的数据的指针. 如果是 IN 端点, 这是从设备读取的数据的缓冲区指针.
//int len : data 参数指向的缓冲的长度
//int *actual_length :指向函数放置真实字节数的指针,根据端点方向,这些字节要么是被发送到设备的,要么是从设备中读取的.
//int timeout :时钟嘀哒数, 应等待的时间. 如果为 0, 函数永远等待操作完成.
//成功返回0,actual_length 参数包含被传送或从设备中读取的字节数.否则返回负的错误值.
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)
/*创建控制 urb 并发送到指定的设备, 接着在返回之前等待完成.*/
//struct usb_device *usb_dev :目标 USB 设备指针
//unsigned int pipe :目标 USB 设备的特定端点. 必须使用特定的宏创建.
//__u8 request :控制消息的 USB 请求值.
//__u8 requesttype :控制消息的 USB 请求类型.
//__u16 value :控制消息的 USB 消息值.
//__u16 index :控制消息的 USB 消息索引值.
//void *data :如果是 OUT 端点, 指向要发送到设备的数据的指针. 如果是 IN 端点, 这是从设备读取的数据的缓冲区指针.
//__u16 size : data 参数指向的缓冲的长度
//int timeout :时钟嘀哒数, 应等待的时间. 如果为 0, 函数永远等待操作完成.
//成功返回被传送到或从设备读取的字节数.否则返回负的错误值.
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)
/*创建中断 urb 并发送到指定的设备, 接着在返回之前等待完成.其实就是usb_bulk_msg的包装,所有参数和usb_bulk_msg一样使用*/
以上的函数不能在中断上下文或持有自旋锁时调用. 这些函数不能被取消, 所以小心使用; 确保驱动的 disconnect 函数了解足够的信息, 在允许它自己从内存被卸载之前等待调用结束.
其他 USB 函数
USB 核心中的一些辅助函数用来从所有的 USB 设备中获取标准信息. 这些函数不能在中断上下文或者持有自旋锁时调用,因为他们内部都是使用上面介绍的简单的接口函数.这里就不一一介绍了,包括《LDD3》介绍的这些函数,在/drivers/usb/core/message.c都有。
很久没有写《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 //电源管理相关 |
配置
一个USB设备可以有多个配置,并可在它们之间转换以改变设备的状态。比如一个设备可以通过下载固件(firmware)的方式改变设备的使用状态(我感觉类似FPGA或CPLD),那么USB设备就要切换配置,来完成这个工作。一个时刻只能有一个配置可以被激活。Linux使用结构 structusb_host_config来描述USB配置。我们编写的USB设备驱动通常不需要读写这些结构的任何值。可在内核源码的文件include/linux/usb.h中找到对它们的描述。
|
接口
USB端点被绑为接口,USB接口只处理一种USB逻辑连接。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要一个以上的驱动程序。这可以在“晕到死 差屁”系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
USB 接口可以有其他的设置,它是对接口参数的不同选择. 接口的初始化的状态是第一个设置,编号为0。 其他的设置可以以不同方式控制独立的端点。
USB接口在内核中使用 struct usb_interface 来描述。USB 核心将其传递给USB驱动,并由USB驱动负责后续的控制。
|
端点
USB 通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道。
一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:
控制CONTROL
控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.
中断INTERRUPT
每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.
批量BULK
批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USBMass Storage和USB网络设备上。
等时ISOCHRONOUS
等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。
控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。
端点在内核中使用结构 struct usb_host_endpoint 来描述,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。
|
USB 和 sysfs
由于单个 USB 物理设备的复杂性,设备在 sysfs 中的表示也非常复杂。 物理 USB 设备(通过 struct usb_device 表示)和单个 USB 接口(由 struct usb_interface 表示)都作为单个设备出现在 sysfs 中,这是因为这两个结构都包含一个 struct device结构。以下内容是我的USB鼠标在 sysfs 中的目录树:
|
USB sysfs 设备命名方法是: root_hub-hub_port:config.interface
随着USB集线器层次的增加, 集线器端口号被添加到字符串中紧随着链中之前的集线器端口号。对一个 2 层的树, 设备为: root_hub-hub_port-hub_port:config.interface ,以此类推。
USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。
urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。
一个 urb 的典型生命循环如下:
(1)被创建;
(2)被分配给一个特定 USB 设备的特定端点;
(3)被提交给 USB 核心;
(4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
(5)被 USB 主机控制器驱动处理, 并传送到设备;
(6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。
|
上述结构体中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结束处理例程
(2)发生错误, 错误值记录在 urb 结构中的 status 变量.
(3)urb 从 USB 核心unlink. 这发生在要么当驱动通过调用 usb_unlink_urb 或者 usb_kill_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都有。
更多推荐
所有评论(0)