USB 设备驱动

USB 设备驱动的整体结构

Linux 实现了以下几类通用的USB设备驱动,并且为它们分配了主次设备号:

  • 音频设备类
  • 通信设备类
  • HID(人机接口)设备类
  • 显示设备类
  • 海量存储设备类
  • 电源设备类
  • 打印设备类
  • 集线器设备类

在 sysfs (/sys/bus/usb) 和 debugfs (/sys/kernel/debug/usb) 下都可以查看USB设备信息 。

设备驱动的抽象

使用 usb_driver 结构体来描述一个 USB 设备驱动。

USB设备的抽象

使用 usb_device_id 结构体来表示驱动所支持的设备,由 usb_driver 的 id_table 成员指向这个结构体的一个实例化的数组。如:

/* drivers/hid/usbhid/usbkbd.c */
static struct usb_device_id usb_kbd_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_KEYBOARD) },
	{ }						/* Terminating entry */
};

MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);

使用 usb_interface 结构体来表示一个“USB接口设备”,它可以对USB设备进行具体数据传输的“端点”进行管理。一个物理上的USB设备通常包括多个配置-接口-端点这样的层级关系,USB协议中使用设备描述符、配置描述符、接口描述符、端点描述符这样的概念来进行描述。而接口描述符,提供了接口所使用的类、子类、协议以及该接口的端点数等信息。

使用 usb_device 结构体来表示一个USB设备,它通常被包含在具体设备的私有数据的结构体中;在驱动与USB核心层/USB总线的交互过程中,会使用到这个结构体的实例。

通常在驱动的probe函数中,通过 interface_to_usbdev() 函数来由 usb_interface 获得 usb_device。

/* linux/sub.h */
static inline struct usb_device *interface_to_usbdev(struct usb_interface *intf);

USB数据传输的抽象

URB(USB request block, USB 请求块)是 USB 设备驱动中用来描述与 USB 设备通信所用的基本载体和核心数据结构,在内核中使用 urb 结构体来表示。

核心数据结构:urb

struct urb 被定义在 linux/usb.h 中。

urb的生命周期

  1. 设备驱动:创建 urb
  2. 设备驱动:为 urb 指定一个端点
  3. 设备驱动:将 urb 提交给USB核心,并设置一个回调函数
  4. USB 核心:提交给设备对应的USB主机控制器驱动
  5. USB Host:完成一次 USB 传送,并通知设备驱动
  6. 设备驱动:回调函数被调用,回调是在中断上下文运行的

同时,应当注意,urb 可以被提交它的驱动在任意时刻取消,或者被USB核心从系统中移出。

urb 的创建和销毁

/* drivers/usb/core/urb.c */
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
void usb_free_urb(struct urb *urb);

4类urb 的初始化

/* linux/usb.h */
/* 中断 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,
				     void *context,
				     int interval);
/* 批量 urb */
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,
				      void *context);
/* 控制 urb */
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,
					 void *context);
/* 等时 urb */
/* USB 等时传输,并没有其它3种传输方式那样的辅助函数,而是手动实现初始化 */
/* 以下是 drivers/usb/media/uvc/uvc_video.c 中的一段实例 */
// uvc_init_video_isoc()
for (i = 0; i < UVC_URBS; ++i) {
		urb = usb_alloc_urb(npackets, gfp_flags);
		if (urb == NULL) {
			uvc_uninit_video(stream, 1);
			return -ENOMEM;
		}

		urb->dev = stream->dev->udev;
		urb->context = stream;
		urb->pipe = usb_rcvisocpipe(stream->dev->udev,
				ep->desc.bEndpointAddress);
#ifndef CONFIG_DMA_NONCOHERENT
		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
		urb->transfer_dma = stream->urb_dma[i];
#else
		urb->transfer_flags = URB_ISO_ASAP;
#endif
		urb->interval = ep->desc.bInterval;
		urb->transfer_buffer = stream->urb_buffer[i];
		urb->complete = uvc_video_complete;
		urb->number_of_packets = npackets;
		urb->transfer_buffer_length = size;

		for (j = 0; j < npackets; ++j) {
			urb->iso_frame_desc[j].offset = j * psize;
			urb->iso_frame_desc[j].length = psize;
		}

		stream->urb[i] = urb;
	}

urb 的提交

/* drivers/usb/core/urb.c */
int usb_submit_urb(struct urb *urb, int mem_flags);

urb 的取消

提交urb给USB核心之后,设备驱动是可以主动取消urb的。usb_unlink_urb() 是异步的,通常在urb 的complete函数中调用;usb_kill_urb() 则是终止 urb 的生命周期,它会在执行完成之后才返回,通常在设备的disconnect函数中调用。

/* drivers/usb/core/urb.c */
int usb_unlink_urb(struct urb *urb);
void usb_kill_urb(struct urb *urb);

urb 的完成

urb 结构体的 complete 成员指向其“完成时的回调函数”;当urb被完成时,这个函数被usb核心调用。

以下几种情况,此 complete 函数会被调用:

  1. urb 被成功发送给设备,即数据被成功发送到外部(OUT),或者请求的数据被成功接收(IN)
  2. 传输过程中发生错误,这时候 status 成员会记录对应的错误值
  3. 驱动程序主动取消urb的传输
  4. urb被提交,但是对应的设备已经不存在

在 complete 函数中判断 urb 的 status 就能知道,urb完成的时候是处于哪种具体的情况:

错误代码 原因
ECONNRESET 被 usb_unlink_urb() 杀死
ENOENT 被 usb_kill_urb() 杀死
EPROTO 发生bitstuff错误或者硬件超时
ENODEV USB设备已被移除
EXDEV 等时传输只完成一部分

驱动代码分析

init, exit

在设备的 init 函数中注册 usb 驱动;在设备的 exit 函数中注销 usb 驱动。

在驱动注册之前 usb_driver 结构体需要被正确地初始化,有5个成员要求必须被初始化:

static struct usb_driver xxx_driver = {
    .owner = THIS_MODULE,
    .name = "xxx",
    .id_table = xxx_table,
    .probe = xxx_probe,
    .disconnect = xxx_disconnect,
};

init 和 exit 函数:

static int __init xxx_init(void)
{
	int result = usb_register(&xxx_driver);
	if (result == 0)
		info(DRIVER_VERSION ":" DRIVER_DESC);
	return result;
}

static void __exit xxx_exit(void)
{
	usb_deregister(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

也可以直接使用 module_usb_driver 宏来注册驱动

module_usb_driver(xxx_driver);

probe, disconnect

它们分别在一个USB设备被接入并且和驱动匹配,以及设备断开连接的时候被调用。设备和驱动匹配时,usb_interface 结构体被实例化,并传入驱动的 porbe 函数中。

probe 和 disconnect 函数的回调都在USB集线器内核线程的上下文中被调用,因此它们中睡眠是合法的。

USB 设备驱动的probe函数和disconnect函数的原型如下:

static int xxx_probe(struct usb_interface *iface, const struct usb_device_id *id);
static void xxx_disconnect(struct usb_interface *intf);

probe() 函数的主要工作

(1) 探测设备的端点地址、缓冲区大小,初始化与USB设备控制相关的结构体

(2) 把已经初始化的设备结构体保存到接口设备中

(3) 注册USB设备

disconnect() 函数的主要工作

(1) 释放所有为设备分配的资源

(2) 设置接口设备的数据指针为NULL

(3) 注销USB设备

相关API

/* linux/usb.h */
static inline void usb_set_intfdata(struct usb_interface *intf, void *data);
static inline void *usb_get_intfdata(struct usb_interface *intf);
/* drivers/usb/core/file.c */
int usb_register_dev(struct usb_interface *intf,
		     struct usb_class_driver *class_driver);
void usb_deregister_dev(struct usb_interface *intf,
			struct usb_class_driver *class_driver);
/* linux/usb.h */
struct usb_class_driver {
	char *name;
	char *(*devnode)(struct device *dev, umode_t *mode);
	const struct file_operations *fops;
	int minor_base;
};

usb_class_driver 用来指明 usb设备它本身的设备类型,如字符设备、输入设备、块设备、tty设备等;对于字符设备而言,usb_class_driver 结构体的 fops 成员中的 open, read, write 等函数,其地位完全与 file_operations结构体的成员相同;其它类型的设备,则调用其对应的注册函数。但是这些设备挂接在USB总线上,其数据的传输,都是通过 urb 来完成的。

usb_register_dev() 并非必须的,它在设备驱动使用USB主设备号的 时候才必须被调用;它会在 sysfs 文件系统下根据 usb_class_driver 创建相应的目录。

设备驱动中对 urb 的处理

由前面的“urb的生命周期”一节可以知道,在设备驱动中对urb的处理包括:创建和初始化urb,提交urb,取消urb,以及urb完成之后的处理。

以下通过一个例子来说明urb在设备驱动中被处理的过程:

【drivers/usb/usb-skeleton.c: skel_write()】

//分配urb
urb = usb_alloc_urb(0, GFP_KERNEL);
if(!urb) {
    retval = -ENOMEM;
    goto error;
}
//分配内存,并映射到一个DMA缓冲,这样传递到驱动的数据会被拷贝到缓冲
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if(!buf) {
    retval = -ENOMEM;
    goto error;
}
//从用户空间拷贝数据到内核空间
if(copy_from_user(buf, user_buffer, count)) {
    retval = -EFAULT;
    goto error;
}
//初始化urb,把对应USB端点、内核缓存指针、complete函数等信息提供给结构体
usb_fill_bulk_urb(urb, dev->udev,
                 usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
                 buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
//提交urb到usb核心
retval = usb_submit_urb(urb, GFP_KERNEL);
if(retval) {
    err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
    goto error;
}

urb完成回调函数。错误代码 -ENOENT, -ECONNRESET, -ESHUTDOWN 不是真正的传送错误, 只是报告伴随成功传送的情况。

static void skel_write_bulk_callback(struct urb *urb)
{
	struct usb_skel *dev;

	dev = urb->context;

	/* sync/async unlink faults aren't errors */
	if (urb->status) {
		if (!(urb->status == -ENOENT ||
		    urb->status == -ECONNRESET ||
		    urb->status == -ESHUTDOWN))
			dev_err(&dev->interface->dev,
				"%s - nonzero write bulk status received: %d\n",
				__func__, urb->status);

		spin_lock(&dev->err_lock);
		dev->errors = urb->status;
		spin_unlock(&dev->err_lock);
	}

	/* free up our allocated buffer */
	usb_free_coherent(urb->dev, urb->transfer_buffer_length,
			  urb->transfer_buffer, urb->transfer_dma);
	up(&dev->limit_sem);
}

简单的 bulk 和 contorl 消息

对于批量传输或者控制传输,如果只是发送一些简单的数据,可以使用两个快捷的API:usb_bulk_msg() 以及 usb_control_msg() ;这两个函数是同步的,会等待URB完成之后才返回,成功时返回完成传输(发送或接收)的字节数,失败时返回负值错误代码;因为等待返回的过程中会被挂起,这两个函数不能在中断上下文或者持有自旋锁的情况下使用。

另外,由于这两个函数不能被其它函数取消,所以,在设备的 disconnect 函数中应该要保证能够判断和等待 usb_bulk_msg() 或者 usb_control_msg() 的调用结束。

/* drivers/usb/core/message.c */
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);

参考源码

linux/usb.h

drivers/usb/core/urb.c

drivers/usb/core/file.c

drivers/usb/core/message.c

drivers/usb/media/uvc/uvc_video.c

drivers/usb/usb-skeleton.c

drivers/hid/usbhid/usbkbd.c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章