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

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