赞
踩
摘要:本文介绍了USB设备驱动相关的基本知识结构,和编写驱动的基本步骤和流程。最后通过编写一个USB鼠标的驱动实力,讲述了简单字符型USB输入设备驱动的具体编写步骤,并给予了测试方法。文末附有完整程序代码和Makefile。
为什么一插上就会有提示信息?
是因为windows自带了USB总线驱动程序;
那USB总线驱动程序是干嘛用的?
首先,新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。然后USB总线驱动程序都会给它分配一个地址(编号)。PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)。
USB的结构是怎样的?
USB可以热拔插的硬件原理
USB的4大传输类型:
USB端点:
USB主机控制器类型
要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:
USB驱动整体框架:
一个 USB 设备是一个非常复杂的事物, 如同在官方的 USB 文档(可从 http://www.usb.org 中得到)中描述的. 幸运的是, Linux 提供了一个子系统 称为 USB 核, 来处理大部分复杂的工作. 《图USB设备驱动框架》 显示了 USB 设备如何包含配置、接口和端点,,以及 USB 驱动如何绑定到 USB 接口, 而不是整个 USB 设备.
一个设备描述符可以有多个配置描述符;
一个配置描述符可以有多个接口描述符(比如声卡驱动就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符;
USB设备描述符结构体如下所示:
struct usb_device_descriptor {
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //类
__u8 bDeviceSubClass; //子类
__u8 bDeviceProtocol; //指定协议
__u8 bMaxPacketSize0; //端点0对应的最大包大小
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice; //设备的发布号
__u8 iManufacturer; //字符串描述符中厂家ID的索引
__u8 iProduct; //字符串描述符中产品ID的索引
__u8 iSerialNumber; //字符串描述符中设备序列号的索引
__u8 bNumConfigurations; //配置描述符的个数,表示有多少个配置描述符
} __attribute__ ((packed));
USB设备描述符位于USB设备结构体usb_device中的成员descriptor中。同样地,配置、接口、端点描述符也是位于USB配置、接口、端点结构体中,不过这3个对于我们写驱动的不是很常用。
usb_device结构体如下所示:
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 设备可有多个配置并且可能在它们 之间转换以便改变设备的状态,但 一个配置只能在一个时间点上被使能。linux使用结构 struct usb_host_config
描述 USB 配置,USB 设备驱动通常不会需要读写这些结构。
struct usb_config_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__le16 wTotalLength; //配置所返回的所有数据的大小
__u8 bNumInterfaces; //配置所支持的接口个数, 表示有多少个接口描述符
__u8 bConfigurationValue; //Set_Configuration命令需要的参数值
__u8 iConfiguration; //描述该配置的字符串的索引值
__u8 bmAttributes; //供电模式的选择
__u8 bMaxPower; //设备从总线提取的最大电流
} __attribute__ ((packed));
USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动就有两个接口:录音接口和播放接口。这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
struct usb_interface_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__u8 bInterfaceNumber; //接口的编号
__u8 bAlternateSetting; //备用的接口描述符编号,提供不同质量的服务参数.
__u8 bNumEndpoints; //要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
__u8 bInterfaceClass; //接口类型,与驱动的id_table对应
__u8 bInterfaceSubClass; //接口子类型
__u8 bInterfaceProtocol; //接口所遵循的协议
__u8 iInterface; //描述该接口的字符串索引值
} __attribute__ ((packed)
它位于usb_interface->cur_altsetting->desc 这个成员结构体里,
usb_interface结构体如下所示:
struct usb_interface {
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting;
unsigned num_altsetting;
int minor;
...
}
altsetting:指向一个包含所有可用于该接口的可选设置的结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置)。这些接口结构没有特别的顺序。
num_altsetting:由 altsetting 指针指向的预设置的数量。
cur_altsetting:指向数组 altsetting 的一个指针, 表示这个接口当前被激活的设置.
**minor **:如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。
…:编写驱动不需要关心的其他成员
结构体usb_host_interface
的定义如下:
struct usb_host_interface {
struct usb_interface_descriptor desc; //当前被激活的接口描述符
struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
char *string; /* 接口描述字符串 */
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
一个 USB 设备驱动通常需要从给定的 struct usb_interface 结构得到 struct usb_device 结构,为此,linux提供有interface_to_usbdev()
函数来完成此目的。
USB 端点在内核中使用结构 struct usb_host_endpoint
来描述,但真实的端点信息(所有的USB设备特定的数据)却保存在该结构体指向的称为 struct usb_endpoint_descriptor
中。
struct usb_endpoint_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__u8 bEndpointAddress; //端点地址(编号)
__u8 bmAttributes; //端点属性, 比如中断传输类型,输入类型
__le16 wMaxPacketSize; //一个端点的最大包大小
__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));
USB_ENDPOINT_XFERTYPE_MASK
与其相与来判断端点是 USB_ENDPOINT_XFER_ISOC
, USB_ENDPOINT_XFER_BULK
或USB_ENDPOINT_XFER_INT
中的一种。bInterval :如果这个端点是中断类型的, 这个值是为这个端点设置的间隔(单位毫秒)。
endpoint的结构体为usb_host_endpoint
,如下所示:
usb_interface->cur_altsetting->desc->endpoint[0].desc
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //超快速端点描述符
struct list_head urb_list; //本端口对应的urb链表
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //使能的话urb才能被提交到此端口
};
## 2.2 USB在sysfs中的表示
由于单个 USB 物理设备的复杂性, 设备在 sysfs 中的表示也非常复杂. 物理 USB 设备(通过 struct usb_device 表示)和单个 USB 接口(由 struct usb_interface 表示)都作为单个设备出现在 sysfs . (这是因为这 2 个结构都 包含一个 struct device 结构)。
### 2.2.1 内核如何标识 USB 设备?
- 第一个 USB 设备是一个**根集线器**. 这也是 USB 控制器, 常常包含在一个 PCI 设 备中. 控制器的命名是由于它控制整个连接到它上面的 USB 总线. 控制器是一 个 PCI 总线和 USB 总线之间的桥, 同时是总线上的第一个设备。
- USB 核为所有的根集线器分配了一个唯一的号。例如usb1、usb2等等。可包含在单个系统中的根集线器的数目没有限制。
- USB设备在 sysfs 文件系统中的命名方法:**root_hub-hub_port:config.interface**。即采用根集线器的`序号`作为它的名子的第一个数字紧跟`-` 字符和设备插入的`端口号`,再跟`:` 字符和设备的`配置序号`紧跟`.` 字符和`接口序号`,例如`2-1:1.0`表示连接在系统第二个USB根集线器的第一个端口下的第一个配置中的0号接口。
- 随着设备在 USB 树中进一步拓展,设备名也将越来越长。对一个 2 层的树, 设备 名看来象: **root_hub-hub_port-hub_port:config.interface**
- 一个USB鼠标在sysfs中的目录结构为:
```c
/sys/devices/pci0000:00/0000:00:09.0/usb2/2-1 // 设备的usb_device结构
|-- 2-1:1.0 // 鼠标驱动被绑定到的接口
| |-- bAlternateSetting
| |-- bInterfaceClass
| |-- bInterfaceNumber
| |-- bInterfaceProtocol
| |-- bInterfaceSubClass
| |-- bNumEndpoints
| |-- detach_state
| |-- iInterface
| `-- power
| `-- state
|-- bConfigurationValue
|-- bDeviceClass
|-- bDeviceProtocol
|-- bDeviceSubClass
|-- bMaxPower
|-- bNumConfigurations
|-- bNumInterfaces
|-- bcdDevice
|-- bmAttributes
|-- detach_state
|-- devnum
|-- idProduct
|-- idVendor
|-- maxchild
|-- power
| `-- state
|-- speed
`-- version
/proc/bus/usb/devices
。linux 内核中的 USB核和所有的 USB 设备使用请求块( USB request block)进行通讯,这个请求块用 struct urb
结构描述并且可在 include/linux/usb.h
中找到。
一个 urb 用来从一个特定 USB 设备上的特定的 USB 端 点, 以一种异步的方式发送或接受数据。它非常像被用在文件系统异步 I/O 代码中的kiocb
结构,或用在网络代码中的 struct skbuff
结构。
根据驱动的需要,一个 USB 设备驱动可能分配多个 urb 给一个端点,或者可能重用单个 urb 给多个不同的端点。设备中的每个端点都维护一个 urb 队列,多个 urb 可被发送到相同的端点。
一个 urb 的典型生命循环如下:
提交 urb 的驱动可在任何时间取消提交, USB 核心也可在设备被拔出时取消它提交的urb。
urb 被动态创建并且包含一个内部引用计数,只有计数值为0时才会释放该urb。
urb 的使用主要是为了获得最高可能的数据传送速度,但若没有速度要求,例如只是想发送单独的块或者控制消息, 并且不关心数据吞吐率时,就不必使用urb了。也就是说USB传送可以不用urb。
struct usb_device *dev
:指向 urb 要发送到的USB设备。在 urb 被发送到 USB 核之前,它必须 被 USB 驱动初始化。
unsigned int pipe
:指向 urb 要发送到的USB设备的端点。在 urb 被发送到 USB 核之前,它必须 被 USB 驱动初始化。
//1.给带有特定端点号endpoint的特定USB设备dev指定一个控制OUT端点.
unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint);
//2.给带有特定端点号endpoint的特定USB设备dev指定一个控制IN端点.
unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint);
//3.给带有特定端点号endpoint的特定USB设备dev指定一个块OUT端点.
unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint);
//4.给带有特定端点号endpoint的特定USB设备dev指定一个块IN端点.
unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint);
//5.给带有特定端点号endpoint的特定USB设备dev指定一个中断OUT端点.
unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint);
//6.给带有特定端点号endpoint的特定USB设备dev指定一个中断IN端点.
unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint);
//7.给带有特定端点号endpoint的特定USB设备dev指定一个同步OUT端点.
unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint);
//8.给带有特定端点号endpoint的特定USB设备dev指定一个同步IN端点.
unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint);
unsigned int transfer_flags
:根据 USB 驱动想这个 urb 发生什 么,这个变量可被设置为以下不同的位值:
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 端点而言,在一个 urb 中提交一个大块数据(大于端点一次可传送的最大数据量),使 USB 主机控制器去划分为更小的块去传送,比以连续的顺序发送小的缓冲块要快。
unsigned char *setup_packet
:指向给一个控制 urb 的 setup 报文的指针. 它在位于传送缓冲中的数 据之前被传送. 这个变量只对控制 urb 有效.
dma_addr_t setup_dma
:给控制 urb 的 setupt 报文的 DMA 缓冲. 在位于正常传送缓冲的数据 之前被传送. 这个变量只对控制 urb 有效.
usb_complete_t complete
:当一个 urb 被完全传送或者当 urb 发生一个错误时, USB 核心调用该变量指向的完成处理者函数(类似于回调函数),在这个函数中, USB 驱动可检查这个 urb, 要么释放它, 要么重新提交另一次传送给它。
typedef void (*usb_complete_t)(struct urb *, struct pt_regs *);
void *context
:指向数据点的指针, 它可被 USB 驱动设置。当 urb 返回到驱动时,它可在完成处理者中使用。
int actual_length
:当urb 被完成时, 这个变量被设置为已传送数据的真实长度。特别对于 IN urb, 这个必须被用来替代 transfer_buffer_length 变量, 因为接收的数据可能比缓冲区小.
int status
:urb 的当前状态。 USB 驱动可在 urb 完成后调用处理者函数中安全的存取该值。对于同步 urb, 状态成功(0)只代表这个 urb 是否已被去链. 为获得同步 urb 的详细状态, 应当检查 iso_frame_desc 变量。status的值包含以下可能的取值:
0: urb 传送成功;
-ENOENT:这个 urb 被对 usb_kill_urb 的调用停止;
-ECONNRESET:urb 被对 usb_unlink_urb 的调用去链, 并且 transfer_flags 变量被 设置为 URB_ASYNC_UNLINK;
**-EINPROGRESS **:这个 urb 仍然在被 USB 主机控制器处理中;
-EPROTO:一个 bitstuff 错误在传送中发生,或者硬件没有及时收到响应帧。
-EILSEQ:这个 urb 传送中有一个 CRC 不匹配;
-EPIPE:这个端点现在被停止. 如果这个包含的端点不是一个控制端点, 这个错 误可被清除通过一个对函数 usb_clear_halt 的调用.
-ECOMM :在传送中数据接收快于能被写入系统内存. 这个错误值只对 IN urb.
-ENOSR :在传送中数据不能及时从系统内存中获取, 以便可跟上请求的 USB 数据速率. 这个错误只对 OUT urb.
-EOVERFLOW : 这个 urb 发生一个"babble"错误.(端点接受的数据多于端点的特定最大报文大小).
-EREMOTEIO :只发生在当 URB_SHORT_NOT_OK 标志被设置在 urb 的 transfer_flags 变量, 并且意味着 urb 请求的完整数量的数据没有收到.
-ENODEV :系统中没有这个 USB 设备(设备被拔出).
-EXDEV :只对同步 urb 发生, 并且意味着传送只部分完成. 为了决定传送什么, 驱动必须看单独的帧状态.
-EINVAL :这个 urb 发生了非常坏的事情. 例如一个参数在 urb 结构中被不正确地设置了, 或者如果 在提交这个 urb 给 USB 核心的 usb_submit_urb 调用中, 有一个不正 确的函数参数.
-ESHUTDOWN:这个 urb 被提交给的设备已掉线
通常, 错误值 -EPROTO, -EILSEQ, 和 -EOVERFLOW 指示设备的硬件问题。
int start_frame
:设置或返回同步传送要使用的初始帧号.
int interval
: urb被轮询的间隔,这只对中断或者同步 urb 有效。这个值的单位依据设备速度而不同,对于低速和高速的设备, 单位是帧(等同于毫秒),对于单位是宏帧的设备, 它等同于 1/8 微秒。 在这个 urb 被发送到 USB 核心之 前,这个值必须先被 USB 驱动设置给同步或者中断 urb。
int number_of_packets
:只对同步 urb 有效, 并且指定这个 urb 要处理的同步传送缓冲的编号. 在这个 urb 发送给 USB 核心之前,这个值必须先被 USB 驱动设置给同步 urb。
int error_count
:在同步 urb 完成之后,被 USB 核心设置。它指定同步传送发生的错误次数.
struct usb_iso_packet_descriptor iso_frame_desc[0]
:只对同步 urb 有效. 这个变量是组成这个 urb 的一个 struct usb_iso_packet_descriptor 结构数组. 这个结构允许单个 urb 来一次 定义多个同步传送. 它也用来收集每个单独传送的传送状态. 结构 usb_iso_packet_descriptor 由下列成员组成:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
void usb_free_urb(struct urb *urb);
void *usb_buffer_alloc(struct usb_device *, int count, int mem_flags, &urb->transfer_dma);
初始化为中断urb
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,\
void *context, int interval);
usb_sndintpipe()
或者 usb_rcvintpipe()
函数创建.初始化为块urb
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,\
void *context);
由于以上函数都不设置 urb 中的 transfer_flags 变量, 因此任何对这个成员的修改不得不由驱动自己完成
初始化为控制urb
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, void *context);
初始化为同步urb
同步 urb 没有像中断, 控制, 和块 urb 那样的初始化函数,在被提交给 USB 核心之前,必须在驱动中"手动"初始化。下面是一 个如何正确初始化这类 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;
}
一旦 urb 被正确地创建,并且被 USB 驱动初始化, 它已准备好被提交给 USB 核心来发送出到 USB 设备。以下函数完成该功能:
int usb_submit_urb(struct urb *urb, int mem_flags); //成功返回0,失败返回负值错误码
GFP_ATOMIC
:只要满足以下任一条件,就需要使用该宏
GFP_NOIO
:如果驱动在块 I/O 补丁中或所有存储类型的错误处理补丁中。GFP_KERNEL
:当用在所有其他的情况中。当 urb 符合以下任一情况时,在初始化时指定的回调函数complete将被调用一次:
void (*usb_complete_t)(struct urb *, struct pt_regs *);
一个回调函数的例子:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
/* sync/async unlink faults aren't errors */
if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
{
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
}
/* free up our allocated buffer */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,\
urb->transfer_buffer, urb->transfer_dma);
}
当需要停止一个已经提交给 USB 核心的 urb时,可采用以下函数:
int usb_kill_urb(struct urb *urb);
——常用在urb要送达的设备从系统被去除时使用,常用在去连接的回调函数中。int usb_unlink_urb(struct urb *urb);
——该函数是非阻塞的,可用在中断处理或者持有一个自旋锁时停止 urb,但这个函数要求urb的 URB_ASYNC_UNLINK 标志值被设置。当需要进行大量数据传送时,选择urb是对的。但若仅是传送一些简单的数据,我们有一些方便的函数可以达到此目的,而不必创建繁琐的urb。
该函数自动创建一个 USB 块 urb 并且发送它到特定的设备, 等待发送完成后返回到调用者。
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,\
void *data, int len, int *actual_length,\
int timeout);
usb_sndbulkpipe
或者 usb_rcvbulkpipe
函数创建pipe的值。应用举例:
/* 例子展示了一个简单的从一个 IN 端点的块读. 如果读取成功, 数据接着被拷贝到用户空间 */
retval = usb_bulk_msg(dev->udev,\
usb_rcvbulkpipe(dev->udev,dev->bulk_in_endpointAddr),\
dev->bulk_in_buffer,\
min(dev->bulk_in_size, count),\
&count, HZ*10);
/* if the read was successful, copy the data to user space */
if (!retval)
{
if (copy_to_user(buffer, dev->bulk_in_buffer, count))
retval = -EFAULT;
else
retval = count;
}
注意事项:
除了允许驱动发送结束 USB 控制信息外,它和usb_bulk_msg功能一样。
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);
注意事项:
前提条件:这些函 数不能从中断上下文或者持有自旋锁时调用.
当USB 驱动想从usb_device 结构中, 获取任何还没有在 usb_device 和 usb_interface 结构中出现的设备描述符时,可调用以下函数:
int usb_get_descriptor(struct usb_device *dev, unsigned char type,\
unsigned char index, void *buf, int size);
dev:指向应当从中获取描述符的 USB 设备;
type :描述符类型,取值是下列类型之一
USB_DT_DEVICE USB_DT_CONFIG USB_DT_INTERFACE
USB_DT_ENDPOINT USB_DT_DEVICE_QUALIFIER USB_DT_STRING
USB_DT_OTHER_SPEED_CONFIG USB_DT_INTERFACE_POWER USB_DT_OTG
USB_DT_DEBUG USB_DT_INTERFACE_ASSOCIATION USB_DT_CS_DEVICE
USB_DT_CS_CONFIG USB_DT_CS_STRING USB_DT_CS_INTERFACE
USB_DT_CS_ENDPOINT
index:希望从设备获取的描述符的数目;
buf:存储描述符信息的缓冲区指针;
size:由 buf 变量指向的内存的大小
usb_get_descripter 调用的一项最普遍的用法是从 USB 设备获取一个字符串,所以linux又专门为这个功能提供了两个函数:
usb_get_string
:
int usb_get_string(struct usb_device *dev, unsigned short langid,\
unsigned char index, void *buf, int size);
usb_string
:
int usb_string(struct usb_device *dev, unsigned short langid,\
void *buf, int size);
参数同上,其中langid是结构体usb_device_descriptor中的某一项
但它buf中存放的是一个已经转化为 ISO 8859-1格式编码的字符串,而该格式是USB设备的字符串典型格式。
使用举例:
static int af9015_probe(struct usb_interface *intf,\
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
char manufacturer[sizeof("ITE Technologies, Inc.")];
memset(manufacturer, 0, sizeof(manufacturer));
usb_string(udev, udev->descriptor.iManufacturer,\
manufacturer, sizeof(manufacturer));
/*
* There is two devices having same ID but different chipset. One uses
* AF9015 and the other IT9135 chipset. Only difference seen on lsusb
* is iManufacturer string.
*
* idVendor 0x0ccd TerraTec Electronic GmbH
* idProduct 0x0099
* bcdDevice 2.00
* iManufacturer 1 Afatech
* iProduct 2 DVB-T 2
*
* idVendor 0x0ccd TerraTec Electronic GmbH
* idProduct 0x0099
* bcdDevice 2.00
* iManufacturer 1 ITE Technologies, Inc.
* iProduct 2 DVB-T TV Stick
*/
if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VID_TERRATEC) &&\
(le16_to_cpu(udev->descriptor.idProduct) == 0x0099))
{
if (!strcmp("ITE Technologies, Inc.", manufacturer))
{
dev_dbg(&udev->dev, "%s: rejecting device\n", __func__);
return -ENODEV;
}
}
return dvb_usbv2_probe(intf, id);
}
static struct usb_driver skel_driver = {
.owner = THIS_MODULE,
.name = "skeleton",
.id_table = skel_table,
.probe = skel_probe,
.disconnect = skel_disconnect,
};
struct module *owner
:指向这个驱动的模块拥有者,USB 核心使用它正确地对这 个 USB 驱动进行引用计数。一般设置为THIS_MODULE宏.
const char *name
:指向驱动名。它在内核 USB 驱动中必须是唯一的,并且通常和驱动的模块名相同。当驱动被加载到内核中时,它出现在/sys/bus/usb/drivers/ 之下。
const struct usb_device_id *id_table
:包含这个驱动可接受的所有不同类型 USB 设备的列表. 如果这个变量没被设置, USB 驱动中的probe函数不会被调用。如果你想你的驱动可被系统中每个 USB 设备调用, 你只需创建一个只设置了 driver_info 成员的入口项:
static struct usb_device_id usb_ids[] = {
{.driver_info = 42},
{}
};
int (*probe) (struct usb_interface *intf, const struct usb_device_id *id)
:指向 USB 驱动中的探测函数,当USB核心发现系统中有一个USB设备(struct usb_interface)可被该驱动驱动时,它将调用probe指向的函数。成功返回0,失败返回负值错误码。
void (*disconnect) (struct usb_interface *intf)
:指向去连接函数。当 struct usb_interface 已被从系统中清除或者当驱动被从 USB 核心卸载时调用。
除了以上5个成员外,还有以下几个不常用的成员项:
int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf)
:只有 USB 集线器驱动使用这个 ioctl,在用户空间对一个关联到 USB 设备的usbfs文件系统做IO系统调用时,会触发该iocctl被调用。int (*suspend) (struct usb_interface *intf, u32 state)
: 当设备要被 USB 核心悬挂时被调 用int (*resume) (struct usb_interface *intf)
:当设备正被 USB 核心恢复时被调 用。一般在 USB 驱动的模块初始化代码中注册 struct usb_driver 到 USB 核心,使用的函数模板为:
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;
}
module_init(usb_skel_init);
一般在 USB 驱动的模块卸载代码中将 struct usb_driver 从USB 核心注销,此后任何当前绑定到这 个驱动的 USB 接口被去连接, 并且去连接函数被调用。使用的函数模板为:
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
module_exit(usb_skel_exit);
在usb_driver结构中,描述了两个USB 核心在合 适的时候调用的函数:
需要注意的是,以上2个函数都是在USB 集线器内核线程上下文中被调用,因此它们睡眠是合法的。但是, 为了保持 USB 探测时间为最小,建议大部分工作应当在设备被用户打开时完成。
在探测函数回调中, USB 驱动应当初始化任何它将来可能使用来管理 USB 设备的本地结构, 因为在此时做这些通常更容易。例如,驱动经常需要获取USB设备的端点地址和缓冲大小等信息。一个探测 BULK 类型的 IN 和 OUT 端点, 并且保存一些关于它们的信息在一个本地设备结构的代码块如下:
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;
//遍历接口中的所有端点
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i)
{
endpoint = &iface_desc->endpoint[i].desc;
//判断端点的方向是否是IN,端点类型是否是块
if (!dev->bulk_in_endpointAddr &&\
(endpoint->bEndpointAddress & USB_DIR_IN) &&\
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK))
{ /* we found a bulk in endpoint */
//保存关于端点的信息到本地结构中, 驱动后来将需要这些信息和它通讯
buffer_size = 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;
}
}
//判断端点的方向是否是OUT,端点类型是否是块
if (!dev->bulk_out_endpointAddr &&\
!(endpoint->bEndpointAddress & USB_DIR_OUT) &&\
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK))
{ /* 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 驱动需要获取一个和该设备关联的usb_interface数据结构,为此Linux提供了如下函数:
/* save our data pointer in this interface device */
usb_set_intfdata(struct usb_interface *interface, void *dev);
为了获取上面保存的数据,可以调用如下函数:
void *usb_get_intfdata(struct usb_interface *interface);
正是因为使用上面两个函数, USB 驱动不需要为系统中所有当前的设备保持一个静态指针数组来保存单个设备结构。这种对设备信息的非直接使得USB驱动支持无限数目的设备。
一个使用usb_get_intfdata的例子如下:
struct usb_skel *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&skel_driver, subminor);
if (!interface)
{
err ("%s - error, can't find device for minor %d", __FUNCTION__, subminor);
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface);
if (!dev)
{
retval = -ENODEV;
goto exit;
}
如果 USB 驱动没有和另一种处理用户和设备交互的子系统(例如 input, tty, video, 等待)关联,,为了使用传统的和用户空间之间的交互,驱动可使用 USB 主设备号来创建一个字符驱动接口。为此, USB 驱动必须在探测函数中调用 usb_register_dev 函 数。
int usb_register_dev(struct usb_interface *, struct usb_class_driver *);
usb_class_driver包含了许多参数:
char *name sysfs
:用来描述设备的名子。用在 devfs 文件系统中。 如果设备号需要显示在名子中, 字符 %d 应当在名子串中。例如要创建usb/foo1 和 sysfs 类名 foo1, 名子串应当设置为 usb/foo%d
。struct file_operations *fops
:驱动已定义用来注册为字符设备.mode_t mode
:若要在 devfs 文件系统中要被创建的模式,否则不使用。这个变量的典型设置是值 S_IRUSR 和 值 S_IWUSR 的结合。int minor_base
:驱动的起始次设备号。 只有 16 个设备被允许在任何时刻和这个驱动关联, 除非 CONFIG_USB_DYNAMIC_MINORS 配置选项被打开。此时可忽略这个变量,以后这个设备的所有的次设备号被以先来先服务的方式分配。建议打开了这个选项的系统,使用 udev 来关联系统中的设备节点。/* 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;
}
如果 usb_register_dev 已被在探测函数中调用来分配一个 USB 设备的次设备号,当 USB 设备断开, 所有的关联到这个设备的资源应当被清除(将次设备号返还给 USB 核心),Linux提供了一个函数来完成此任务:
usb_deregister_dev(struct usb_interface *, struct usb_class_driver *);
当然,在去连接函数中,还需要从接口获取之前调用 usb_set_intfdata 所设置的 数据. 接着设置数据指针在 struct us_interface 结构为 NULL 来阻止在不正 确存取数据中的任何进一步的错误:
static void skel_disconnect(struct usb_interface *interface)
{
struct usb_skel *dev;
int minor = interface->minor;
/* prevent skel_open() from racing skel_disconnect( ) */
lock_kernel();
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &skel_class);
unlock_kernel(); /* decrement our usage count */
kref_put(&dev->kref, skel_delete);
info("USB Skeleton #%d now disconnected", minor);
}
#include <linux/usb.h>
:所有和 USB 相关的头文件. 它必须被所有的 USB 设备驱动包含
struct usb_driver
:描述 USB 驱动的结构;
struct usb_device_id
: 描述这个驱动支持的 USB 设备的结构.
int usb_register(struct usb_driver *d)
: 用来从 USB 核心注册和注销一个 USB 驱动的函数.
struct usb_device *interface_to_usbdev(struct usb_interface *intf)
: 从 struct usb_interface 获取控制 struct usb_device *.
struct usb_device
:控制完整 USB 设备的结构.
struct usb_interface
:主 USB 设备结构, 用来和 USB 核心通讯的所有的 USB 驱动。
void usb_set_intfdata(struct usb_interface *intf, void *data)
:设置在 struct usb_interface 中的私有数据指针部分的函数.
void *usb_get_intfdata(struct usb_interface *intf)
:获取在 struct usb_interface 中的私有数据指针部分的函数.
struct usb_class_driver
:描述 USB 驱动的一个结构, 这个驱动要使用 USB 主设备号来和用户空间程序通讯.
int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver)
:用来注册一个特定 struct usb_interface * 结构到 struct usb_class_driver 结构的函数.
void usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver *class_driver)
:用来注销一个特定 struct usb_interface * 结构到 struct usb_class_driver 结构的函数.
struct urb
:描述一个 USB 数据传输的结构.
struct urb *usb_alloc_urb(int iso_packets, int mem_flags)
:用来创建一个 struct urb*的函数
void usb_free_urb(struct urb *urb)
:用来销毁一个 struct urb*的函数.
用来启动或停止一个 USB 数据传输的函数
int usb_submit_urb(struct urb *urb, int mem_flags);
int usb_kill_urb(struct urb *urb);
int usb_unlink_urb(struct urb *urb);
用来在被提交给 USB 核心之前初始化一个 struct urb 的函数
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,\
void *context, int interval)
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,\
void *context)
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, void *context)
用来发送和接受 USB 数据的函数(不使用 urb).
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,\
void *data, int len, int *actual_length, int timeout);
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);
由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息发现以下字段:
如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行:
这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口
它又是被谁调用的,如下图所示,我们搜索到它是通过hub_thread()函数调用的
hub_thread()函数如下:
static int hub_thread(void *__unused)
{
do {
hub_events(); //执行一次hub事件函数
wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop()); //(1).每次执行一次hub事件,都会进入一次等待事件中断函数
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
从上面函数中得到, 要想执行hub_events(),都要等待khubd_wait这个中断唤醒才行。
找到该中断在kick_khubd()函数中唤醒,代码如下:
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait); //唤醒khubd_wait这个中断
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.
static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{
... ...
udev = usb_alloc_dev(hdev, hdev->bus, port1); //(1)注册一个usb_device,然后会放在usb总线上
usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标志
... ...
choose_address(udev); //(2)给新的设备分配一个地址编号
status = hub_port_init(hub, udev, port1, i); //(3)初始化端口,与USB设备建立连接
... ...
status = usb_new_device(udev); //(4)创建USB设备,与USB设备驱动连接
... ...
}
所以最终流程图如下:
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一个usb_device设备结构体
... ...
device_initialize(&dev->dev); //初始化usb_device
dev->dev.bus = &usb_bus_type; //(1)设置usb_device的成员device->bus等于usb_bus总线
dev->dev.type = &usb_device_type; //设置usb_device的成员device->type等于usb_device_type
... ...
return dev; //返回一个usb_device结构体
}
在第17行上,设置device成员,主要是用来后面8.2小节,注册usb总线的device表上.
其中usb_bus_type是一个全局变量, 它和我们之前学的platform平台总线相似,属于USB总线, 是Linux中bus的一种.
如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.
usb_bus_type结构体如下:
struct bus_type usb_bus_type = {
.name = "usb", //总线名称,存在/sys/bus下
.match = usb_device_match, //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
.uevent = usb_uevent, //事件函数
.suspend = usb_suspend, //休眠函数
.resume = usb_resume, //唤醒函数
};
static void choose_address(struct usb_device *udev)
{
int devnum;
struct usb_bus *bus = udev->bus;
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
//在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号
if (devnum >= 128) //若编号大于等于128,说明没有找到空余的地址编号,从头开始找
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //设置下次寻址的区间+1
if (devnum < 128) {
set_bit(devnum, bus->devmap.devicemap); //设置位
udev->devnum = devnum;
}
}
从上面代码中分析到每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,也可以看出,如下图所示:
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{
... ...
for (j = 0; j < SET_ADDRESS_TRIES; ++j){
retval = hub_set_address(udev); //(1)设置地址,告诉USB设备新的地址编号
if (retval >= 0)
break;
msleep(200);
}
retval = usb_get_device_descriptor(udev, 8); //(2)获得USB设备描述符前8个字节
... ...
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新获取设备描述符信息
... ...
}
static int hub_set_address(struct usb_device *udev)
{
int retval;
... ...
retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT); //(1.1)等待传输完成
if (retval == 0) { //设置新的地址,传输完成,返回0
usb_set_device_state(udev, USB_STATE_ADDRESS); //设置状态标志
ep0_reinit(udev);
}
return retval;
}
其中USB设备描述符结构体如下所示:
struct usb_device_descriptor {
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //类
__u8 bDeviceSubClass; //子类
__u8 bDeviceProtocol; //指定协议
__u8 bMaxPacketSize0; //端点0对应的最大包大小
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice; //设备的发布号
__u8 iManufacturer; //字符串描述符中厂家ID的索引
__u8 iProduct; //字符串描述符中产品ID的索引
__u8 iSerialNumber; //字符串描述符中设备序列号的索引
__u8 bNumConfigurations; //可能的配置的数目
} __attribute__ ((packed));
int usb_new_device(struct usb_device *udev)
{
... ...
err = usb_get_configuration(udev); //(1)获取配置描述块
... ...
err = device_add(&udev->dev); // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
}
int usb_get_configuration(struct usb_device *dev)
{
... ...
/* USB_MAXCONFIG 定义为8,表示设备描述块下有最多不能超过8个配置描述块 */
/*ncfg表示 设备描述块下 有多少个配置描述块 */
if (ncfg > USB_MAXCONFIG) {
dev_warn(ddev, "too many configurations: %d, "
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
}
... ...
for (cfgno = 0; cfgno < ncfg; cfgno++){ //for循环,从USB设备里依次读入所有配置描述块
//每次先读取USB_DT_CONFIG_SIZE个字节,也就是9个字节,暂放到buffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
... ...
//通过wTotalLength,知道实际数据大小
length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);
bigbuffer = kmalloc(length, GFP_KERNEL); //然后再来分配足够大的空间
... ...
//在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bigbuffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
... ...
//再将bigbuffer地址放在rawdescriptors所指的指针数组中
dev->rawdescriptors[cfgno] = bigbuffer;
result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],
bigbuffer, length); //最后在解析每个配置块
}
... ...
}
int usb_get_configuration(struct usb_device *dev)
{
dev = get_device(dev); //使dev等于usb_device下的device成员
... ...
if ((error = bus_add_device(dev))) // 把这个设备添加到dev->bus的device表中
goto BusError;
... ...
bus_attach_device(dev); //来匹配对应的驱动程序
... ...
}
当bus_attach_device()函数匹配成功,就会调用驱动的probe函数
usb_device_match函数如下所示:
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
if (is_usb_device(dev)) { //判断是不是USB设备
if (!is_usb_device_driver(drv))
return 0;
return 1;
}
else
{ //否则就是USB驱动或者USB设备的接口
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
if (is_usb_device_driver(drv)) //如果是USB驱动,就不需要匹配,直接return
return 0;
intf = to_usb_interface(dev); //获取USB设备的接口
usb_drv = to_usb_driver(drv); //获取USB驱动
id = usb_match_id(intf, usb_drv->id_table); //匹配USB驱动的成员id_table
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
}
return 0;
}
显然就是匹配USB驱动的id_table
id_table的结构体为struct usb_device_id
,提供了这个驱动支持的 USB 设备列表。当特定设备被插入系统时,这个列表被 USB 核心用来决定给设备哪个驱动, 并且通过热插拔脚本来 决定哪个驱动自动加载。其定义如下所示:
struct usb_device_id {
__u16 match_flags;
//匹配哪种USB类型?这是一个位成员, 由include/linux/mod_devicetable.h文件中指定
//USB_DEVICE_ID_MATCH_INT_INFO : 用于匹配设备的接口描述符的3个成员
//USB_DEVICE_ID_MATCH_DEV_INFO: 用于匹配设备描述符的3个成员
//USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于匹配特定的USB设备的4个成员
//USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct)
/* 描述该驱动可匹配的特定的USB设备 */
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice_lo; //设备的低版本号(以 BCD 方式编码)
__u16 bcdDevice_hi; //设备的高版本号(以 BCD 方式编码)
/*以下3个就是用于比较“设备描述符”的*/
__u8 bDeviceClass; //设备类
__u8 bDeviceSubClass; //设备子类
__u8 bDeviceProtocol; //设备协议
/* 以下3个就是用于比较设备的“接口描述符”的 */
__u8 bInterfaceClass; //接口类型
__u8 bInterfaceSubClass; //接口子类型
__u8 bInterfaceProtocol; //接口所遵循的协议
/* 这个值不用来匹配,用来在probe函数中区分不同的设备. */
kernel_ulong_t driver_info;
};
有几个宏可用来初始化这个结构:
USB_DEVICE(vendor, product)
:用来只匹配特定供应商和产品 ID 值.USB_DEVICE_VER(vendor, product, lo, hi)
:用来在一个版本范围中只匹配特定供 应商和产品 ID 值.USB_DEVICE_INFO(class, subclass, protocol)
:用来只匹配一个特定类的 USB 设 备.USB_INTERFACE_INFO(class, subclass, protocol)
:用来只匹配一个特定类的 USB 接口.对于一个简单的只控制来自一个供应商的单一 USB 设备, struct usb_device_id 表可定义如:
/* 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);
参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),看它是如何使用的:
发现,它是通过**USB_INTERFACE_INFO()**这个宏定义的.该宏如下所示:
#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \ //设置id_table的.match_flags成员
.bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
//设置id_table的3个成员,用于与匹配USB设备的3个成员
然后将上图里的usb_mouse_id_table []里的3个值代入宏**USB_INTERFACE_INFO(cl,sc,pr)**中:
.bInterfaceClass =USB_INTERFACE_CLASS_HID;
//设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
//HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03
.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;
//设置匹配USB的接口子类型为启动设备
.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
//设置匹配USB的接口协议为USB鼠标的协议,等于2
//当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议
如下图,我们也可以通过windows上也可以找到鼠标的协议号,也是2:
其中VID:表示厂家(vendor)ID
PID:表示产品(Product) ID
总结:当我们插上USB设备时,系统就会获取USB设备的设备、配置、接口、端点的数据并创建新设备,所以我们的驱动就需要写id_table来匹配该USB设备。
讲了这么多,大家一定蒙圈了,现在以一个具体例子来具体分析一个USB驱动的编写方法!
预期目标:鼠标左键=按键L,鼠标右键=按键S,鼠标中键=回车键
.probe函数的结构:
怎么写USB设备驱动程序:
代码编写步骤如下:
0 定义全局变量 :usb_driver结构体、input_dev指针结构体 、虚拟地址缓存区、DMA地址缓存区
1 在入口函数中
2 在usb_driver的probe函数中
分配一个input_dev结构体
设置input_dev支持L、S、回车、3个按键事件
注册input_dev结构体
设置USB数据传输:
->4.1)通过usb_rcvintpipe()创建一个接收中断类型的端点管道pipe,用于端点和数据缓冲区之间的连接
->4.2)通过usb_buffer_alloc()申请USB缓冲区
->4.3)申请并初始化urb结构体(urb:用来传输数据)
->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址
->4.5)使用usb_submit_urb()提交urb
3 在鼠标中断函数中
1)判断缓存区数据是否改变,若改变则上传鼠标事件
2)使用usb_submit_urb()提交urb
4 在usb_driver的disconnect函数中
1)通过usb_kill_urb()杀掉提交到内核中的urb
2)释放urb
3)释放USB缓存区
4注销input_device
5 在出口函数中
1)通过usb_deregister ()函数注销usb_driver结构体
/*
* drivers\hid\usbhid\usbmouse.c
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int __init usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void __exit usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
static struct usb_device_id usbmouse_as_key_id_table[] = {
{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,\
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE)
},
//如果需要匹配USB设备的厂家和产品ID,可以添加{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf); //获得usb设备信息
/* 以下语句可以用来打印USB设备的厂家及产品id信息
*printk("Found usbmouse!\n");
*printk("bcdUSB = %x\n",dev->descriptor.bcdUSB);
*printk("VID = 0X%x\n",dev->descriptor.idVendor);
*printk("PID = 0X%x\n",dev->descriptor.idProduct);
*/
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
if (interface->desc.bNumEndpoints != 1) //除了端点0之外,鼠标的端点数如果不是1的话,则返回错误
return -ENODEV;
endpoint = &interface->endpoint[0].desc; //得到第一个非零endpoint的描述符
if (!usb_endpoint_is_int_in(endpoint)) //如果不是中断输入型端点的话,返回错误,更多内嵌函数见<linux/usb.h>
return -ENODEV;
/* a. 分配一个input_dev */
uk_dev = input_allocate_device(); //在文件开头先定义输入设备指针uk_dev
/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit); //能产生按键类事件
set_bit(EV_REP, uk_dev->evbit); //能产生按键的重复类事件
/* b.2 能产生哪些按键 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关操作 */
/* 数据传输“三要素”: 源,目的,长度 */
/* 1源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 2长度: 要在文件开头定义一个长度变量,int len*/
len = endpoint->wMaxPacketSize;
/*
* 3目的: (1.在文件开头定义一个字符串指针,char * usb_buf)
* (2.在文件开头定义一个usb_buf的物理地址,dma_addr_t * usb_buf_phys)
* 注意:在linux2.6.34及之前的代码中还可以使用usb_buffer_alloc 和 usb_buffer_free
* 来分配和释放缓存,但在之后的内核中这两个函数已不在使用了,可以用usb_alloc_coherent 和
* usb_free_coherent代替。原因是使用DMA传输数据时,由linux保证内存和硬件cache的一致性。
* 这也是为何新函数名字中含有coherent的原因。
*/
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 1分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL); //先要在文件开头定义一个urb指针,即urb * uk_urb
/* 2使用之前的三要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 3使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
USB主机周期性(间隔时间为endpoint->bInterval)的从设备获得数据,并存入到usb buffer中,同时USB主机控制器会向CPU产生中断,并调用usb中断处理函数:
static void usbmouse_as_key_irq(struct urb *urb)
{
//为了调试,先打印usb_buf中的数据
#if 0
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
//usb_buf[]里存放的是鼠标按键值、x坐标、y坐标和滚轮的值
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
#endif
//上报事件(USB总线驱动是不知道usb数据含义的,只有在USB设备驱动中加以解析)
#if 1
/* USB鼠标数据含义
* data[0]: bit0-左键(1-按下, 0-松开)
* bit1-右键(1-按下, 0-松开)
* bit2-中键(1-按下, 0-松开)
*/
static unsigned char pre_val;
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))) //如果上次数据的Bit0不等于现在的值
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
#endif
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
//printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb); //杀死urb
usb_free_urb(uk_urb); //释放urb
usb_free_coherent(dev, len, usb_buf, usb_buf_phys); //释放usb buffer
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
cd linux3.4.2
make menuconfig
->Device Drivers
->HID Devices
-> <>USB Human Interface Device (full HID) support
make uImage
tftp 30000000 uImage //将新的内核复制到tftp共享目录后,下载到开发板内核
//nfs 30000000 主机IP:/nfs_root/uImage //或者使用nfs命令下载新内核
bootm 30000000 //从内核的30000000地址处启动内核
mount -t nfs -o nolock 主机IP:/nfs_root /mnt //挂接网络文件系统
insmod usbmouse_as_key.ko //内核启动后,安装自己编译的USB设备驱动
# ls /dev/event* //观察当前系统有没有其它event设备
ls:/dev/event*:No such file or directory
接上USB鼠标
usb 1-1:configuration #1 chosen from 1 choice
input:Unspecified device as /class/input/input0
# ls /dev/event*
/dev/event0 //对应我们刚接上去的鼠标
# cat /dev/tty1 //将鼠标作为键盘,观察tty1口输出
l(按下左键)s(按下右键)
(按下回车)
llllll(按住左键)
/*
* drivers\hid\usbhid\usbmouse.c
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
static struct input_dev *uk_dev;//鼠标作为输入设备
static char *usb_buf; //使用虚拟地址的usb buffer
static dma_addr_t usb_buf_phys; //使用物理地址的DMA缓冲区
static int len; //usb buffer 的大小
static struct urb *uk_urb; //usb请求块
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
#if 0
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
#endif
/* USB鼠标数据含义
* data[0]: bit0-左键, 1-按下, 0-松开
* bit1-右键, 1-按下, 0-松开
* bit2-中键, 1-按下, 0-松开
*
*/
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/* a. 分配一个input_dev */
uk_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
//printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_free_coherent(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
KERN_DIR = /home/leon/linux-3.4.2
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += usbmouse_as_key.o
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。