一.USB理論部分
1.USB概述
USB1.0版本速度1.5Mbps(低速USB)、 USB1.1版本速度12Mbps(全速USB)、 USB2.0版本速度480Mbps(高速USB)、USB3.0版本速度5.0GMbps(超高速USB)。
USB驅動由USB主機控制器驅動和USB裝置驅動組成。USB主機控制器是用來控制USB裝置和CPU之間通信的,USB主機控制器驅動主要用來驅動晶片上的主機控制器硬體。USB裝置驅動主要是指具體的例如USB滑鼠,USB鍵盤燈裝置的驅動。
一般的通用的Linux裝置,如U盤、USB滑鼠、USB鍵盤,都不需要工程師再編寫驅動,需要編寫的是特定廠商、特定晶片的驅動,而且往往也可以參考核心中已經提供的驅動模闆。 USB隻是一個總線,真正的USB裝置驅動的主體工作仍然是USB裝置本身所屬類型的驅動,如字元裝置、tty裝置、塊裝置、輸入裝置等。
2.USB主機控制器
USB主機控制器屬于南橋晶片的一部分,通過PCI總線和處理器通信。USB主機控制器分為UHCI(英特爾提出)、OHCI(康柏和微軟提出)、 EHCI。其中OHCI驅動程式用來為非PC系統上以及帶有SiS和ALi晶片組的PC主辦上的USB晶片提供支援。UHCI驅動程式多用來為大多數其他PC主機闆(包括Intel和Via)上的USB晶片提供支援。ENCI相容OHCI和UHCI。UHCI的硬體線路比OHCI簡單,是以成本較低,但需要較複雜的驅動程式,CPU負荷稍重。主機控制器驅動程式完成的功能主要包括:解析和維護URB,根據不同的端點進行分類緩存URB;負責不同USB傳輸類型的排程工作;負責USB資料的實際傳輸工作;實作虛拟跟HUB的功能。
3.USB裝置與USB驅動的比對
USB裝置與USB驅動怎麼比對的呢?實際上USB裝置中有一個子產品叫固件,是固件資訊和USB驅動進行的比對。固件是固化在內建電路内部的程式代碼,USB固件中包含了USB裝置的出廠資訊,辨別該裝置的廠商ID、産品ID、主版本号和次版本号等。另外固件中還包含一組程式,這組程式主要完成USB協定的處理和裝置的讀寫操作。USB裝置固件和USB驅動之間通信的規範是通過USB協定來完成的。
4.USB裝置的邏輯結構和端點的傳輸方式
USB裝置的邏輯結構包括裝置、配置、接口和端點,分别用usb_device、usb_host_config、 usb_interface、usb_host_endpoint表示。
端點的傳輸方式包括控制傳輸、中斷傳輸、批量傳輸、等時傳輸。
控制傳輸主要用于向裝置發送配置資訊、擷取裝置資訊、發送指令道裝置,或者擷取裝置的狀态報告。控制傳輸一般發送的資料量較小,當USB裝置插入時,USB核心使用端點0對裝置進行配置,另外,端口0與其他端點不一樣,端點0可以雙向傳輸。
中斷傳輸就是中斷端點以一個固定的速度來傳輸較少的資料, USB鍵盤和滑鼠就是使用這個傳輸方式。這裡說的中斷和硬體上下文中的中斷不一樣,它不是裝置主動發送一個中斷請求,而是主機控制器在保證不大于某個時間間隔内安排一次傳輸。中斷傳輸對時間要求比較嚴格,是以可以用中斷傳輸來不斷地檢測某個裝置,當條件滿足後再使用批量傳輸傳輸大量的資料。
批量傳輸通常用在資料量大、對資料實時性要求不高的場合,例如 USB列印機、掃描器、大容量儲存設備、U盤等。
等時傳輸同樣可以傳輸大批量資料,但是對資料是否到達沒有保證,它對實時性的要求很高,例如 音頻、視訊等裝置。
5.USB的URB請求塊
USB請求塊(USB request block,urb)是USB主機控制器和裝置通信的主要資料結構,主機和裝置之間通過urb進行資料傳輸。當主機控制器需要與裝置互動時,隻需要填充一個urb結構,然後将其送出給USB核心,由USB核心負責對其進行處理。
URB處理流程:
Step1:建立一個URB結構體 usb_alloc_urb()
Step2:初始化,被安排一個特定的USB裝置的特定端點。fill_int/bulk/control_urb()
Step3:被USB裝置驅動送出給USB核心usb_submit_urb(),注意GPF_ATOMIC,GPF_NOIO,GPF_KERNEL的使用差別。
Step4:送出由USB核心指定的USB主機控制器驅動,被主機控制器驅動處理,進行一次到USB裝置的傳輸,該過程由USB核心和主機控制器完成,不受USB裝置驅動控制
Step5:當urb完成,USB主機控制器驅動通知USB裝置驅動。
簡單的批量與控制URB:
有時候USB驅動程式隻是從USB裝置上接收或發送一些簡單的資料,這時候可以使用usb_bulk/control_msg()完成,這兩個函數是同步的,是以不能在中斷上下文和持有自旋鎖的情況下使用。
6.USB的枚舉過程
核心輔助線程khubd用來監視與該集線器連接配接的所有端口,通常情況下,該線程處于休眠狀态,當集線器驅動程式檢測到USB端口狀态變化後,該核心線程立馬喚醒。
USB的枚舉過程:USB的枚舉過程是熱插拔USB裝置的起始步驟,該過程中,主機控制器擷取裝置的相關資訊并配置好裝置,集線器驅動程式負責該枚舉過程。枚舉過程主要分如下幾步:
Step1:根集線器報告插入裝置導緻的端口電流變化,集線器驅動程式檢測到這一狀态變化後,喚醒khubd線程。
Step2:khubd識别出電流變化的那個端口。
Step3:khubd通過給控制端點0發送控制URB來實作從1-127中選出一個數作為插入裝置的批量端點。
Step4:khubd利用端口0使用的控制URB從插入的裝置那裡獲得裝置描述符,然後獲得配置描述符,并選擇一個合适的。
Step5:khubd請求USB核心把對應的客戶驅動程式和該USB裝置挂鈎。
二.USB驅動分析
核心代碼分析包括USB驅動架構、滑鼠驅動、鍵盤驅動、U盤驅動:
USB驅動編寫的主要架構/drivers/usb/usb-skeleton.c
USB滑鼠驅動 /drivers/hid/usbhid/usbmouse.c
USB鍵盤驅動動 /drivers/hid/usbhid/usbkbd.c
USB Mass Storage是一類USB儲存設備, U盤便是其中之一,主要分析的驅動檔案是/drivers/usb/storage/usb.c
1.USB驅動架構usb-skeleton.c
USB骨架程式可以看做一個最簡單的USB裝置驅動的執行個體,其分析流程大緻如下:
module_init(usb_skel_init)---->usb_skel_init()中的usb_register(&skel_driver)---->(驅動中的id_table和裝置配置能對上号)skel_driver中的 .probe = skel_probe
---->probe函數中usb_register_dev(interface, &skel_class)語句---->skel_class 中的.fops = &skel_fops---->skel_fops中就是系統調用對應的函數指針,對應的函數
實作了系統調用函數的具體功能,這些函數僅當進行系統調用的時候被執行。
首先看看USB骨架程式的usb_driver的定義:
static struct usb_driver skel_driver = {
.name = "skeleton",
.probe = skel_probe, //裝置探測
.disconnect = skel_disconnect,
.suspend = skel_suspend,
.resume = skel_resume,
.pre_reset = skel_pre_reset,
.post_reset = skel_post_reset,
.id_table = skel_table, //裝置支援項
.supports_autosuspend = 1,
};
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
static const struct usb_device_id skel_table[] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, skel_table);
由上面代碼可見,通過USB_DEVICE宏定義了裝置支援項。
對上面usb_driver的注冊和登出發送在USB骨架程式的子產品加載和解除安裝函數中。
static int __init usb_skel_init(void)
{
int result;
result = usb_register(&skel_driver); //将該驅動挂在USB總線上
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
一個裝置被安裝或者有裝置插入後,當USB總線上經過match比對成功,就會調用裝置驅動程式中的probe探測函數,向探測函數傳遞裝置的資訊,以便确定驅動程式是否支援該裝置。
static int skel_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_skel *dev; //特定裝置結構體
struct usb_host_interface *iface_desc; //接口設定
struct usb_endpoint_descriptor *endpoint; //端點描述符
size_t buffer_size;
int i;
int retval = -ENOMEM;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //配置設定記憶體
if (!dev) {
err("Out of memory");
goto error;
}
kref_init(&dev->kref); //引用計數初始化
sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); //初始化信号量
mutex_init(&dev->io_mutex); //初始化互斥鎖
spin_lock_init(&dev->err_lock); //初始化自旋鎖
init_usb_anchor(&dev->submitted);
init_completion(&dev->bulk_in_completion); //初始化完成量
dev->udev = usb_get_dev(interface_to_usbdev(interface)); //擷取usb_device結構體
dev->interface = interface; //擷取usb_interface結構體
iface_desc = interface->cur_altsetting; //由接口擷取目前設定
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)) { //如果端點為批量輸入端點
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;
}
dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); //配置設定urb空間
if (!dev->bulk_in_urb) {
err("Could not allocate bulk_in_urb");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
usb_endpoint_is_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_set_intfdata(interface, dev); //将特定裝置結構體設定為接口的私有資料
retval = usb_register_dev(interface, &skel_class); //注冊 USB裝置
if (retval) {
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}
dev_info(&interface->dev,
"USB Skeleton device now attached to USBSkel-%d",
interface->minor);
return 0;
error:
if (dev)
kref_put(&dev->kref, skel_delete);
return retval;
}
通過上面分析,我們知道,usb_driver的probe函數中根據usb_interface的成員尋找第一個批量輸入和輸出的端點,将端點位址、緩沖區等資訊存入USB骨架程式定義的usb_skel結構體中,并将usb_skel通過usb_set_intfdata傳為USB接口的私有資料,最後注冊USB裝置。
我們來看看這個USB骨架程式定義的usb_skel結構體
struct usb_skel {
struct usb_device *udev;
struct usb_interface *interface;
struct semaphore limit_sem;
struct usb_anchor submitted;
struct urb *bulk_in_urb;
unsigned char *bulk_in_buffer;
size_t bulk_in_size;
size_t bulk_in_filled;
size_t bulk_in_copied;
__u8 bulk_in_endpointAddr;
__u8 bulk_out_endpointAddr;
int errors;
int open_count;
bool ongoing_read;
bool processed_urb;
spinlock_t err_lock;
struct kref kref;
struct mutex io_mutex;
struct completion bulk_in_completion;
};
好了看完了probe,我們再看看disconnect函數
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); //設定接口的私有資料為空
usb_deregister_dev(interface, &skel_class); //登出USB裝置
mutex_lock(&dev->io_mutex);
dev->interface = NULL;
mutex_unlock(&dev->io_mutex);
usb_kill_anchored_urbs(&dev->submitted);
kref_put(&dev->kref, skel_delete);
dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor);
}
我們在skel_probe中最後執行了usb_register_dev(interface, &skel_class)來注冊了一個USB裝置,我們看看skel_class的定義
static struct usb_class_driver skel_class = {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};
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,
.llseek = noop_llseek,
};
根據上面代碼我們知道,其實我們在probe中注冊USB裝置的時候使用的skel_class是一個包含file_operations的結構體,而這個結構體正是字元裝置檔案操作結構體。
我們先來看看這個file_operations中open函數的實作
static int skel_open(struct inode *inode, struct file *file)
{
struct usb_skel *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode); //獲得次裝置号
interface = usb_find_interface(&skel_driver, subminor); //根據 usb_driver和次裝置号擷取裝置的接口
if (!interface) {
err("%s - error, can't find device for minor %d",
__func__, subminor);
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface); //擷取接口的私有資料 usb_skel
if (!dev) {
retval = -ENODEV;
goto exit;
}
kref_get(&dev->kref);
mutex_lock(&dev->io_mutex);
if (!dev->open_count++) {
retval = usb_autopm_get_interface(interface);
if (retval) {
dev->open_count--;
mutex_unlock(&dev->io_mutex);
kref_put(&dev->kref, skel_delete);
goto exit;
}
}
file->private_data = dev; //将 usb_skel設定為檔案的私有資料
mutex_unlock(&dev->io_mutex);
exit:
return retval;
}
這個open函數實作非常簡單,它根據usb_driver和次裝置号通過usb_find_interface擷取USB接口,然後通過usb_get_intfdata獲得接口的私有資料并指派給檔案。
好了,我們看看write函數,在write函數中,我們進行了urb的配置設定、初始化和送出的操作
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 = file->private_data; //擷取檔案的私有資料
if (count == 0)
goto exit;
if (!(file->f_flags & O_NONBLOCK)) { //如果檔案采用非阻塞方式
if (down_interruptible(&dev->limit_sem)) { //擷取限制讀的次數的信号量
retval = -ERESTARTSYS;
goto exit;
}
} else {
if (down_trylock(&dev->limit_sem)) {
retval = -EAGAIN;
goto exit;
}
}
spin_lock_irq(&dev->err_lock); //關中斷
retval = dev->errors;
if (retval < 0) {
dev->errors = 0;
retval = (retval == -EPIPE) ? retval : -EIO;
}
spin_unlock_irq(&dev->err_lock); //開中斷
if (retval < 0)
goto error;
urb = usb_alloc_urb(0, GFP_KERNEL); //配置設定urb
if (!urb) {
retval = -ENOMEM;
goto error;
}
buf = usb_alloc_coherent(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;
}
mutex_lock(&dev->io_mutex);
if (!dev->interface) {
mutex_unlock(&dev->io_mutex);
retval = -ENODEV;
goto error;
}
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, writesize, skel_write_bulk_callback, dev); //填充urb
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_anchor_urb(urb, &dev->submitted); //送出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;
}
usb_free_urb(urb);
return writesize;
error_unanchor:
usb_unanchor_urb(urb);
error:
if (urb) {
usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
usb_free_urb(urb);
}
up(&dev->limit_sem);
exit:
return retval;
}
首先說明一個問題,填充urb後,設定了transfer_flags标志,當transfer_flags中的URB_NO_TRANSFER_DMA_MAP被設定,USB核心使用transfer_dma指向的緩沖區而不是使用transfer_buffer指向的緩沖區,這表明即将傳輸DMA緩沖區。當transfer_flags中的URB_NO_SETUP_DMA_MAP被設定,如果控制urb有DMA緩沖區,USB核心将使用setup_dma指向的緩沖區而不是使用setup_packet指向的緩沖區。
另外,通過上面這個write函數我們知道,當寫函數發起的urb結束後,其完成函數skel_write_bulk_callback會被調用,我們繼續跟蹤
static void skel_write_bulk_callback(struct urb *urb)
{
struct usb_skel *dev;
dev = urb->context;
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);
}
usb_free_coherent(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma); //釋放urb空間
up(&dev->limit_sem);
}
很明顯,skel_write_bulk_callback主要對urb->status進行判斷,根據錯誤提示顯示錯誤資訊,然後釋放urb空間。
接着,我們看看USB骨架程式的字元裝置的read函數
static ssize_t skel_read(struct file *file, char *buffer, size_t count,
loff_t *ppos)
{
struct usb_skel *dev;
int rv;
bool ongoing_io;
dev = file->private_data; //獲得檔案私有資料
if (!dev->bulk_in_urb || !count) //正在寫的時候禁止讀操作
return 0;
rv = mutex_lock_interruptible(&dev->io_mutex); //獲得鎖
if (rv < 0)
return rv;
if (!dev->interface) {
rv = -ENODEV;
goto exit;
}
retry:
spin_lock_irq(&dev->err_lock);
ongoing_io = dev->ongoing_read;
spin_unlock_irq(&dev->err_lock);
if (ongoing_io) { //USB核正在讀取資料中,資料沒準備好
if (file->f_flags & O_NONBLOCK) { //如果為非阻塞,則結束
rv = -EAGAIN;
goto exit;
}
rv = wait_for_completion_interruptible(&dev->bulk_in_completion); //等待
if (rv < 0)
goto exit;
dev->bulk_in_copied = 0; //拷貝到使用者空間操作已成功
dev->processed_urb = 1; //目前已處理好 urb
}
if (!dev->processed_urb) { //目前還沒已處理好 urb
wait_for_completion(&dev->bulk_in_completion); //等待完成
dev->bulk_in_copied = 0;
dev->processed_urb = 1;
}
rv = dev->errors;
if (rv < 0) {
dev->errors = 0;
rv = (rv == -EPIPE) ? rv : -EIO;
dev->bulk_in_filled = 0;
goto exit;
}
if (dev->bulk_in_filled) { //緩沖區有内容
size_t available = dev->bulk_in_filled - dev->bulk_in_copied; //可讀資料大小為緩沖區内容減去已經拷貝到使用者空間的資料大小
size_t chunk = min(available, count); //真正讀取資料大小
if (!available) {
rv = skel_do_read_io(dev, count); //沒可讀資料則調用 IO操作
if (rv < 0)
goto exit;
else
goto retry;
}
if (copy_to_user(buffer,
dev->bulk_in_buffer + dev->bulk_in_copied,
chunk)) //拷貝緩沖區資料到使用者空間
rv = -EFAULT;
else
rv = chunk;
dev->bulk_in_copied += chunk; //目前拷貝完成的資料大小
if (available < count) //剩下可用資料小于使用者需要的資料
skel_do_read_io(dev, count - chunk); //調用 IO操作
} else {
rv = skel_do_read_io(dev, count); //緩沖區沒資料則調用 IO操作
if (rv < 0)
goto exit;
else if (!(file->f_flags & O_NONBLOCK))
goto retry;
rv = -EAGAIN;
}
exit:
mutex_unlock(&dev->io_mutex);
return rv;
}
通過上面read函數,我們知道,在讀取資料時候,如果發現緩沖區沒有資料,或者緩沖區的資料小于使用者需要讀取的資料量時,則會調用IO操作,也就是skel_do_read_io函數。
static int skel_do_read_io(struct usb_skel *dev, size_t count)
{
int rv;
usb_fill_bulk_urb(dev->bulk_in_urb,
dev->udev,
usb_rcvbulkpipe(dev->udev,
dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
skel_read_bulk_callback,
dev); //填充 urb
spin_lock_irq(&dev->err_lock);
dev->ongoing_read = 1; //标志正在讀取資料中
spin_unlock_irq(&dev->err_lock);
rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); //送出 urb
if (rv < 0) {
err("%s - failed submitting read urb, error %d",
__func__, rv);
dev->bulk_in_filled = 0;
rv = (rv == -ENOMEM) ? rv : -EIO;
spin_lock_irq(&dev->err_lock);
dev->ongoing_read = 0;
spin_unlock_irq(&dev->err_lock);
}
return rv;
}
好了,其實skel_do_read_io隻是完成了urb的填充和送出,USB核心讀取到了資料後,會調用填充urb時設定的回調函數skel_read_bulk_callback。
static void skel_read_bulk_callback(struct urb *urb)
{
struct usb_skel *dev;
dev = urb->context;
spin_lock(&dev->err_lock);
if (urb->status) { //根據傳回狀态判斷是否出錯
if (!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
err("%s - nonzero write bulk status received: %d",
__func__, urb->status);
dev->errors = urb->status;
} else {
dev->bulk_in_filled = urb->actual_length; //記錄緩沖區的大小
}
dev->ongoing_read = 0; //已經讀取資料完畢
spin_unlock(&dev->err_lock);
complete(&dev->bulk_in_completion); //喚醒 skel_read函數
}
好了,到目前為止,我們已經把USB驅動架構usb-skeleton.c分析完了,總結下,其實很簡單,在子產品加載裡面注冊usb_driver,然後在probe函數裡初始化一些參數,最重要的是注冊了USB裝置,這個USB裝置相當于一個字元裝置,提供file_operations接口。然後設計open,close,read,write函數,這個open裡基本沒做什麼事情,在write中,通過配置設定urb、填充urb和送出urb。注意讀的urb的配置設定在probe裡申請空間,寫的urb的配置設定在write裡申請空間。在這個驅動程式中,我們重點掌握usb_fill_bulk_urb的設計。
2.USB滑鼠驅動usbmouse.c
下面我們分析下USB滑鼠驅動,滑鼠輸入HID類型,其資料傳輸采用中斷URB,滑鼠端點類型為IN。好了,我們先看看這個驅動的子產品加載部分。
static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver);
if (retval == 0)
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
DRIVER_DESC "\n");
return retval;
}
子產品加載部分仍然是調用usb_register注冊USB驅動,我們跟蹤看看被注冊的usb_mouse_drive。
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
關于裝置支援項我們前面已經讨論過了
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ }
};
再細細看看USB_INTERFACE_INFO宏的定義
#define USB_INTERFACE_INFO(cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
.bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), \
.bInterfaceProtocol = (pr)
根據宏,我們知道,我們設定的支援項包括接口類,接口子類,接口協定三個比對項。
好了,我們主要看看usb_driver中定義的probe函數
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);//由接口擷取 usb_device
struct usb_host_interface *interface; //設定
struct usb_endpoint_descriptor *endpoint; //端點描述符
struct usb_mouse *mouse; //本驅動私有結構體
struct input_dev *input_dev; //輸入結構體
int pipe, maxp;
int error = -ENOMEM;
interface = intf->cur_altsetting; //擷取設定
if (interface->desc.bNumEndpoints != 1) //滑鼠端點隻有 1個
return -ENODEV;
endpoint = &interface->endpoint[0].desc; //獲得端點描述符
if (!usb_endpoint_is_int_in(endpoint)) //檢查該端點是否是中斷輸入端點
return -ENODEV;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //建立中斷輸入端點
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //傳回端點能傳輸的最大的資料包,滑鼠的傳回的最大資料包為 4個位元組
mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); //配置設定 mouse結構體
input_dev = input_allocate_device(); //配置設定 input裝置空間
if (!mouse || !input_dev)
goto fail1;
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma); //配置設定緩沖區
if (!mouse->data)
goto fail1;
mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //配置設定 urb
if (!mouse->irq)
goto fail2;
mouse->usbdev = dev; //填充 mouse的 usb_device結構體
mouse->dev = input_dev; //填充 mouse的 input結構體
if (dev->manufacturer) //拷貝廠商 ID
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) { //拷貝産品 ID
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name)) //拷貝産品 ID
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
input_dev->name = mouse->name; //将滑鼠名賦給内嵌 input結構體
input_dev->phys = mouse->phys; //将滑鼠裝置節點名賦給内嵌 input結構體
usb_to_input_id(dev, &input_dev->id); //将 usb_driver的支援項拷貝給 input
input_dev->dev.parent = &intf->dev;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); //evbit表明支援按鍵事件 (EV_KEY)和相對坐标事件 (EV_REL)
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); //keybit表明按鍵值包括左鍵、右鍵和中鍵
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); //relbit表明相對坐标事件值包括 X坐标和 Y坐标
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA); //keybit表明除了左鍵、右鍵和中鍵,還支援其他按鍵
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); //relbit表明除了 X坐标和 Y坐标,還支援中鍵滾輪的滾動值
input_set_drvdata(input_dev, mouse); //将 mouse設定為 input的私有資料
input_dev->open = usb_mouse_open; //input裝置的 open
input_dev->close = usb_mouse_close;
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval); //填充 urb
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /使用 transfer_dma
error = input_register_device(mouse->dev); //注冊 input裝置
if (error)
goto fail3;
usb_set_intfdata(intf, mouse);
return 0;
fail3:
usb_free_urb(mouse->irq);
fail2:
usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
input_free_device(input_dev);
kfree(mouse);
return error;
}
其實上面這個probe主要是初始化usb裝置和input裝置,終極目标是為了完成urb的送出和input裝置的注冊。由于注冊為input裝置類型,那麼當使用者層open打開裝置時候,最終會調用input中的open實作打開,我們看看input中open的實作
static int usb_mouse_open(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev); //擷取私有資料
mouse->irq->dev = mouse->usbdev; //擷取 urb指針
if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //送出 urb
return -EIO;
return 0;
}
好了,當使用者層open打開這個USB滑鼠後,我們就已經将urb送出給了USB核心,那麼根據USB資料處理流程知道,當處理完畢後,USB核心會通知USB裝置驅動程式,這裡我們是響應中斷服務程式,這就相當于該URB的回調函數。我們在送出urb時候定義了中斷服務程式usb_mouse_irq,我們跟蹤看看
static void usb_mouse_irq(struct urb *urb)
{
struct usb_mouse *mouse = urb->context;
signed char *data = mouse->data;
struct input_dev *dev = mouse->dev;
int status;
switch (urb->status) {
case 0:
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
return;
default:
goto resubmit;
}
input_report_key(dev, BTN_LEFT, data[0] & 0x01);//滑鼠左鍵
input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(dev, REL_X, data[1]);
input_report_rel(dev, REL_Y, data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);
input_sync(dev);
resubmit:
status = usb_submit_urb (urb, GFP_ATOMIC);//再次送出urb,等待下次響應
if (status)
err ("can't resubmit intr, %s-%s/input0, status %d",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}
根據上面的中斷服務程式,我們應該知道,系統是周期性地擷取滑鼠的事件資訊,是以在URB回調函數的末尾再次送出URB請求塊,這樣又會調用新的回調函數,周而複始。在回調函數中送出URB隻能是GFP_ATOMIC優先級,因為URB回調函數運作于中斷上下文中禁止導緻睡眠的行為。而在送出URB過程中可能會需要申請記憶體、保持信号量,這些操作或許會導緻USB核心睡眠。
最後我們再看看這個驅動的私有資料mouse的定義
struct usb_mouse {
char name[128];//名字
char phys[64];//裝置節點
struct usb_device *usbdev;//内嵌usb_device裝置
struct input_dev *dev;//内嵌input_dev裝置
struct urb *irq;//urb結構體
signed char *data;//transfer_buffer緩沖區
dma_addr_t data_dma;// transfer _dma緩沖區
};
在上面這個結構體中,每一個成員的作用都應該很清楚了,尤其最後兩個的使用差別和作用,前面也已經說過。
如果最終需要測試這個USB滑鼠驅動,需要在核心中配置USB支援、對HID接口的支援、對OHCI HCD驅動的支援。另外,将驅動移植到開發闆之後,由于采用的是input裝置模型,是以還需要開發闆帶LCD屏才能測試。
3.USB鍵盤驅動usbkbd.c
跟USB滑鼠類型,USB鍵盤也屬于HID類型,代碼在/dirver/hid/usbhid/usbkbd.c下。USB鍵盤除了送出中斷URB外,還需要送出控制URB。
static int __init usb_kbd_init(void)
{
int result = usb_register(&usb_kbd_driver);
if (result == 0)
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
DRIVER_DESC "\n");
return result;
}
static struct usb_driver usb_kbd_driver = {
.name = "usbkbd",
.probe = usb_kbd_probe,
.disconnect = usb_kbd_disconnect,
.id_table = usb_kbd_id_table,
};
static int usb_kbd_probe(struct usb_interface *iface,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(iface);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_kbd *kbd;
struct input_dev *input_dev;
int i, pipe, maxp;
int error = -ENOMEM;
interface = iface->cur_altsetting;
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//建立中斷輸入端點
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));//擷取傳回位元組大小
kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //配置設定私有資料空間
input_dev = input_allocate_device(); //配置設定input裝置空間
if (!kbd || !input_dev)
goto fail1;
if (usb_kbd_alloc_mem(dev, kbd))//配置設定urb空間和其他緩沖空間
goto fail2;
kbd->usbdev = dev;//給内嵌結構體指派
kbd->dev = input_dev;//給内嵌結構體指派
if (dev->manufacturer)
strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(kbd->name, " ", sizeof(kbd->name));
strlcat(kbd->name, dev->product, sizeof(kbd->name));
}
if (!strlen(kbd->name))
snprintf(kbd->name, sizeof(kbd->name),
"USB HIDBP Keyboard %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
strlcat(kbd->phys, "/input0", sizeof(kbd->phys));
input_dev->name = kbd->name;
input_dev->phys = kbd->phys;
usb_to_input_id(dev, &input_dev->id);//複制usb_driver的支援項給input的支援項
input_dev->dev.parent = &iface->dev;
input_set_drvdata(input_dev, kbd);//将kbd設定為input的私有資料
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
BIT_MASK(EV_REP);
input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
BIT_MASK(LED_KANA);
for (i = 0; i < 255; i++)
set_bit(usb_kbd_keycode[i], input_dev->keybit);
clear_bit(0, input_dev->keybit);
input_dev->event = usb_kbd_event;//定義event函數
input_dev->open = usb_kbd_open;
input_dev->close = usb_kbd_close;
usb_fill_int_urb(kbd->irq, dev, pipe,
kbd->new, (maxp > 8 ? 8 : maxp),
usb_kbd_irq, kbd, endpoint->bInterval);//填充中斷urb
kbd->irq->transfer_dma = kbd->new_dma;
kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//dma方式傳輸
kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
kbd->cr->bRequest = 0x09;//設定控制請求的格式
kbd->cr->wValue = cpu_to_le16(0x200);
kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
kbd->cr->wLength = cpu_to_le16(1);
usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
(void *) kbd->cr, kbd->leds, 1,
usb_kbd_led, kbd);//填充控制urb
kbd->led->transfer_dma = kbd->leds_dma;
kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //設定dma和setup_dma有效
error = input_register_device(kbd->dev);//注冊input裝置
if (error)
goto fail2;
usb_set_intfdata(iface, kbd);
device_set_wakeup_enable(&dev->dev, 1);
return 0;
fail2:
usb_kbd_free_mem(dev, kbd);
fail1:
input_free_device(input_dev);
kfree(kbd);
return error;
}
在上面的probe中,我們主要是初始化一些結構體,然後送出中斷urb和控制urb,并注冊input裝置。其中有幾個地方需要細看下,其一,usb_kbd_alloc_mem的實作。其二,設定控制請求的格式。
static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
{
if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))//配置設定中斷urb
return -1;
if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))//配置設定控制urb
return -1;
if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
return -1;
if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) //配置設定控制urb使用的控制請求描述符
return -1;
if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) //配置設定控制urb使用的緩沖區
return -1;
return 0;
}
這裡我們需要明白中斷urb和控制urb需要配置設定不同的urb結構體,同時在送出urb之前,需要填充的内容也不同,中斷urb填充的是緩沖區和中斷處理函數,控制urb填充的是控制請求描述符和回調函數。
好了,接着我們解決第二個問題,設定控制請求的格式。cr是struct usb_ctrlrequest結構的指針,USB協定中規定一個控制請求的格式為一個8個位元組的資料包,其定義如下
struct usb_ctrlrequest {
__u8 bRequestType;//設定傳輸方向、請求類型等
__u8 bRequest;//指定哪個請求,可以是規定的标準值也可以是廠家定義的值
__le16 wValue;//即将寫到寄存器的資料
__le16 wIndex;//接口數量,也就是寄存器的偏移位址
__le16 wLength;//資料傳輸階段傳輸多少個位元組
} __attribute__ ((packed));
USB協定中規定,所有的USB裝置都會響應主機的一些請求,這些請求來自USB主機控制器,主機控制器通過裝置的預設控制管道發出這些請求。預設的管道為0号端口對應的那個管道。
同樣這個input裝置首先由使用者層調用open函數,是以先看看input中定義的open
static int usb_kbd_open(struct input_dev *dev)
{
struct usb_kbd *kbd = input_get_drvdata(dev);
kbd->irq->dev = kbd->usbdev;
if (usb_submit_urb(kbd->irq, GFP_KERNEL))//送出中斷urb
return -EIO;
return 0;
}
因為這個驅動裡面有一個中斷urb一個控制urb,我們先看中斷urb的處理流程。中斷urb在input的open中被送出後,當USB核心處理完畢,會通知這個USB裝置驅動,然後執行回調函數,也就是中斷處理函數usb_kbd_irq
static void usb_kbd_irq(struct urb *urb)
{
struct usb_kbd *kbd = urb->context;
int i;
switch (urb->status) {
case 0:
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
return;
default:
goto resubmit;//出錯就再次送出中斷urb
}
for (i = 0; i < 8; i++)
input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);//向input子系統報告
for (i = 2; i < 8; i++) {
if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
if (usb_kbd_keycode[kbd->old[i]])
input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
else
hid_info(urb->dev,
"Unknown key (scancode %#x) released.\n",
kbd->old[i]);
}
if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
if (usb_kbd_keycode[kbd->new[i]])
input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
else
hid_info(urb->dev,
"Unknown key (scancode %#x) released.\n",
kbd->new[i]);
}
}
input_sync(kbd->dev);
memcpy(kbd->old, kbd->new, 8);
resubmit:
i = usb_submit_urb (urb, GFP_ATOMIC);//再次送出中斷urb
if (i)
hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",
kbd->usbdev->bus->bus_name,
kbd->usbdev->devpath, i);
}
這個就是中斷urb的處理流程,跟前面講的的USB滑鼠中斷處理流程類似。好了,我們再來看看剩下的控制urb處理流程吧。
我們有個疑問,我們知道在probe中,我們填充了中斷urb和控制urb,但是在input的open中,我們隻送出了中斷urb,那麼控制urb什麼時候送出呢?
我們知道對于input子系統,如果有事件被響應,我們會調用事件處理層的event函數,而該函數最終調用的是input下的event。是以,對于input裝置,我們在USB鍵盤驅動中隻設定了支援LED選項,也就是ledbit項,這是怎麼回事呢?剛才我們分析的那個中斷urb其實跟這個input基本沒啥關系,中斷urb并不是像講鍵盤input實作的那樣屬于input下的中斷。我們在USB鍵盤驅動中的input子系統中隻設計了LED選項,那麼當input子系統有按鍵選項的時候必然會使得核心調用事件處理層的event函數,最終調用input下的event。好了,那我們來看看input下的event幹了些什麼。
static int usb_kbd_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
struct usb_kbd *kbd = input_get_drvdata(dev);
if (type != EV_LED) //不是LED事件就傳回
return -1;
kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
(!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) |
(!!test_bit(LED_NUML, dev->led));//将目前的LED值儲存在kbd->newleds中
if (kbd->led->status == -EINPROGRESS)
return 0;
if (*(kbd->leds) == kbd->newleds)
return 0;
*(kbd->leds) = kbd->newleds;
kbd->led->dev = kbd->usbdev;
if (usb_submit_urb(kbd->led, GFP_ATOMIC))//送出控制urb
pr_err("usb_submit_urb(leds) failed\n");
return 0;
}
當在input的event裡送出了控制urb後,經過URB處理流程,最後傳回給USB裝置驅動的回調函數,也就是在probe中定義的usb_kbd_led
static void usb_kbd_led(struct urb *urb)
{
struct usb_kbd *kbd = urb->context;
if (urb->status)//送出失敗顯示
hid_warn(urb->dev, "led urb status %d received\n",
urb->status);
if (*(kbd->leds) == kbd->newleds)//比較kbd->leds和kbd->newleds,如果發生變化,則更新kbd->leds
return;
*(kbd->leds) = kbd->newleds;
kbd->led->dev = kbd->usbdev;
if (usb_submit_urb(kbd->led, GFP_ATOMIC))//再次送出控制urb
hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
}
總結下,我們的控制urb走的是先由input的event送出,觸發後由控制urb的回調函數再次送出。好了,通過USB滑鼠,我們已經知道了控制urb和中斷urb的設計和處理流程。
4.U盤驅動分析
USB Mass Storage是一類USB儲存設備,這些裝置包括USB磁盤、USB硬碟、USB錄音帶機、USB光驅、U盤、記憶棒、智能卡和一些USB攝像頭等,這類裝置由USB協定支援。
首先我想去看看/driver/usb/storage/Makefile
ccflags-y := -Idrivers/scsi
obj-$(CONFIG_USB_UAS) += uas.o
obj-$(CONFIG_USB_STORAGE) += usb-storage.o
usb-storage-y := scsiglue.o protocol.o transport.o usb.o
usb-storage-y += initializers.o sierra_ms.o option_ms.o
usb-storage-$(CONFIG_USB_STORAGE_DEBUG) += debug.o
這是Makefile中前幾行代碼,在此我進行一個說明。第一行,-I選項表示需要編譯的目錄。當本Makefile檔案被編譯器讀取時,會先判斷/driver/scsi目錄下的檔案是否已經被編譯,如果沒有被編譯,則先編譯該目錄下的檔案後,再轉到該Makefile檔案中。第三行就是USB Mass Storage選項,是總指揮。第四、五行說明了這個檔案夾也就是usb-storage子產品必須包含的檔案,這些檔案将是主要分析的對象。第六行是調試部分。目前我們分析USB驅動,是以重點去分析這些檔案中的usb.c
同樣,我們先看看usb.c中的子產品加載部分
static int __init usb_stor_init(void)
{
int retval;
pr_info("Initializing USB Mass Storage driver...\n");
retval = usb_register(&usb_storage_driver);
if (retval == 0) {
pr_info("USB Mass Storage support registered.\n");
usb_usual_set_present(USB_US_TYPE_STOR);
}
return retval;
}
static struct usb_driver usb_storage_driver = {
.name = "usb-storage",
.probe = storage_probe,
.disconnect = usb_stor_disconnect,
.suspend = usb_stor_suspend,
.resume = usb_stor_resume,
.reset_resume = usb_stor_reset_resume,
.pre_reset = usb_stor_pre_reset,
.post_reset = usb_stor_post_reset,
.id_table = usb_storage_usb_ids,
.supports_autosuspend = 1,
.soft_unbind = 1,
};
下面重點我們來看看這個probe函數
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct us_data *us;
int result;
if (usb_usual_check_type(id, USB_US_TYPE_STOR) ||
usb_usual_ignore_device(intf))//檢測比對
return -ENXIO;
result = usb_stor_probe1(&us, intf, id,
(id - usb_storage_usb_ids) + us_unusual_dev_list);//探測的第一部分
if (result)
return result;
result = usb_stor_probe2(us);//探測的第二部分
return result;
}
我們發現U盤驅動的探測分為兩個部分,我們先來看看第一個部分usb_stor_probe1
int usb_stor_probe1(struct us_data **pus,
struct usb_interface *intf,
const struct usb_device_id *id,
struct us_unusual_dev *unusual_dev)
{
struct Scsi_Host *host;
struct us_data *us;
int result;
US_DEBUGP("USB Mass Storage device detected\n");
host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us));//配置設定Scsi_Host結構體
if (!host) {
dev_warn(&intf->dev,
"Unable to allocate the scsi host\n");
return -ENOMEM;
}
host->max_cmd_len = 16;
host->sg_tablesize = usb_stor_sg_tablesize(intf);
*pus = us = host_to_us(host); //從host結構體中提取出us_data結構體
memset(us, 0, sizeof(struct us_data));
mutex_init(&(us->dev_mutex));
init_completion(&us->cmnd_ready);//初始化完成量
init_completion(&(us->notify));
init_waitqueue_head(&us->delay_wait); //初始化等待隊列頭
init_completion(&us->scanning_done);
result = associate_dev(us, intf);//将us_data與USB裝置相關聯
if (result)
goto BadDevice;
result = get_device_info(us, id, unusual_dev);//擷取裝置資訊
if (result)
goto BadDevice;
get_transport(us); //擷取傳輸方式
get_protocol(us); //擷取傳輸協定
return 0;
BadDevice:
US_DEBUGP("storage_probe() failed\n");
release_everything(us);
return result;
}
我們再看看U盤驅動的探測的第二部分usb_stor_probe2
int usb_stor_probe2(struct us_data *us)
{
struct task_struct *th;
int result;
struct device *dev = &us->pusb_intf->dev;
if (!us->transport || !us->proto_handler) {
result = -ENXIO;
goto BadDevice;
}
US_DEBUGP("Transport: %s\n", us->transport_name);
US_DEBUGP("Protocol: %s\n", us->protocol_name);
if (us->fflags & US_FL_SINGLE_LUN)
us->max_lun = 0;
result = get_pipes(us);//獲得管道
if (result)
goto BadDevice;
if (us->fflags & US_FL_INITIAL_READ10)
set_bit(US_FLIDX_REDO_READ10, &us->dflags);
result = usb_stor_acquire_resources(us); //擷取資源
if (result)
goto BadDevice;
snprintf(us->scsi_name, sizeof(us->scsi_name), "usb-storage %s",
dev_name(&us->pusb_intf->dev));
result = scsi_add_host(us_to_host(us), dev);//添加scsi
if (result) {
dev_warn(dev,
"Unable to add the scsi host\n");
goto BadDevice;
}
th = kthread_create(usb_stor_scan_thread, us, "usb-stor-scan");//建立線程
if (IS_ERR(th)) {
dev_warn(dev,
"Unable to start the device-scanning thread\n");
complete(&us->scanning_done);
quiesce_and_remove_host(us);
result = PTR_ERR(th);
goto BadDevice;
}
usb_autopm_get_interface_no_resume(us->pusb_intf);
wake_up_process(th);//喚醒usb_stor_scan_thread線程
return 0;
BadDevice:
US_DEBUGP("storage_probe() failed\n");
release_everything(us);
return result;
}
好了,我們已經把probe大緻閱讀了一下,主要通過assocaite_dev(),get_device_info(),get_transport(),get_protocol(),get_pipes()五個函數來為us結構體指派,然後調用usb_stor_acquire_resources()來得到裝置需要的動态資源。最後建立掃描線程usb_stor_scan_thread,讓使用者能通過cat /proc/scsi/scsi看到U盤裝置。現在我們一個個分析下這裡提到了每個函數。
首先我們看看來為us結構體指派的裝置關聯函數associate_dev的實作
static int associate_dev(struct us_data *us, struct usb_interface *intf)
{
US_DEBUGP("-- %s\n", __func__);
us->pusb_dev = interface_to_usbdev(intf);//由接口擷取裝置
us->pusb_intf = intf;//接口指派
us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber;//接口數量
US_DEBUGP("Vendor: 0x%04x, Product: 0x%04x, Revision: 0x%04x\n",
le16_to_cpu(us->pusb_dev->descriptor.idVendor),
le16_to_cpu(us->pusb_dev->descriptor.idProduct),
le16_to_cpu(us->pusb_dev->descriptor.bcdDevice));
US_DEBUGP("Interface Subclass: 0x%02x, Protocol: 0x%02x\n",
intf->cur_altsetting->desc.bInterfaceSubClass,
intf->cur_altsetting->desc.bInterfaceProtocol);
usb_set_intfdata(intf, us);//把us設定為接口的私有資料
us->cr = kmalloc(sizeof(*us->cr), GFP_KERNEL);//配置設定控制urb的控制字元空間
if (!us->cr) {
US_DEBUGP("usb_ctrlrequest allocation failed\n");
return -ENOMEM;
}
us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE,
GFP_KERNEL, &us->iobuf_dma);//配置設定urb的緩沖區
if (!us->iobuf) {
US_DEBUGP("I/O buffer allocation failed\n");
return -ENOMEM;
}
return 0;
}
然後我們繼續看獲得裝置資訊函數get_device_info的實作
static int get_device_info(struct us_data *us, const struct usb_device_id *id,
struct us_unusual_dev *unusual_dev)
{
struct usb_device *dev = us->pusb_dev;
struct usb_interface_descriptor *idesc =
&us->pusb_intf->cur_altsetting->desc;
struct device *pdev = &us->pusb_intf->dev;
us->unusual_dev = unusual_dev; //不常用的裝置
//找到USB裝置支援的子類和協定
us->subclass = (unusual_dev->useProtocol == USB_SC_DEVICE) ?
idesc->bInterfaceSubClass :
unusual_dev->useProtocol;
us->protocol = (unusual_dev->useTransport == USB_PR_DEVICE) ?
idesc->bInterfaceProtocol :
unusual_dev->useTransport;
us->fflags = USB_US_ORIG_FLAGS(id->driver_info);
adjust_quirks(us);
if (us->fflags & US_FL_IGNORE_DEVICE) { //USB裝置不能被系統識别則退出
dev_info(pdev, "device ignored\n");
return -ENODEV;
}
if (dev->speed != USB_SPEED_HIGH) //USB裝置不支援高速則改為低速
us->fflags &= ~US_FL_GO_SLOW;
if (us->fflags)
dev_info(pdev, "Quirks match for vid %04x pid %04x: %lx\n",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct),
us->fflags);
//根據生産廠商和産品号來設定協定、傳輸類型等參數
if (id->idVendor || id->idProduct) {
static const char *msgs[3] = {
"an unneeded SubClass entry",
"an unneeded Protocol entry",
"unneeded SubClass and Protocol entries"};
struct usb_device_descriptor *ddesc = &dev->descriptor;
int msg = -1;
if (unusual_dev->useProtocol != USB_SC_DEVICE &&
us->subclass == idesc->bInterfaceSubClass)
msg += 1;
if (unusual_dev->useTransport != USB_PR_DEVICE &&
us->protocol == idesc->bInterfaceProtocol)
msg += 2;
if (msg >= 0 && !(us->fflags & US_FL_NEED_OVERRIDE))
dev_notice(pdev, "This device "
"(%04x,%04x,%04x S %02x P %02x)"
" has %s in unusual_devs.h (kernel"
" %s)\n"
" Please send a copy of this message to "
"<[email protected]> and "
"<[email protected]>\n",
le16_to_cpu(ddesc->idVendor),
le16_to_cpu(ddesc->idProduct),
le16_to_cpu(ddesc->bcdDevice),
idesc->bInterfaceSubClass,
idesc->bInterfaceProtocol,
msgs[msg],
utsname()->release);
}
return 0;
}
我們繼續看得到傳輸方式函數get_transport,這個函數主要獲得USB裝置支援的通信協定,并設定USB驅動的傳輸類型。對于U盤,USB協定規定它屬于Bulk-only的傳輸方式,也就是它的us->protocot為US_PR_BULK
static void get_transport(struct us_data *us)
{
switch (us->protocol) {
case USB_PR_CB:
us->transport_name = "Control/Bulk";
us->transport = usb_stor_CB_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 7;
break;
case USB_PR_CBI:
us->transport_name = "Control/Bulk/Interrupt";
us->transport = usb_stor_CB_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 7;
break;
case USB_PR_BULK:
us->transport_name = "Bulk";
us->transport = usb_stor_Bulk_transport;//傳輸函數
us->transport_reset = usb_stor_Bulk_reset;
break;
}
}
好了,接着我們看獲得協定資訊的get_protocol函數,該函數根據不同的協定,用來設定協定的傳輸函數。對于U盤,USB協定規定us->subclass為US_SC_SCSI
static void get_protocol(struct us_data *us)
{
switch (us->subclass) {
case USB_SC_RBC:
us->protocol_name = "Reduced Block Commands (RBC)";
us->proto_handler = usb_stor_transparent_scsi_command;//協定處理函數
break;
case USB_SC_8020:
us->protocol_name = "8020i";
us->proto_handler = usb_stor_pad12_command;
us->max_lun = 0;
break;
case USB_SC_QIC:
us->protocol_name = "QIC-157";
us->proto_handler = usb_stor_pad12_command;
us->max_lun = 0;
break;
case USB_SC_8070:
us->protocol_name = "8070i";
us->proto_handler = usb_stor_pad12_command;
us->max_lun = 0;
break;
case USB_SC_SCSI:
us->protocol_name = "Transparent SCSI";
us->proto_handler = usb_stor_transparent_scsi_command;
break;
case USB_SC_UFI:
us->protocol_name = "Uniform Floppy Interface (UFI)";
us->proto_handler = usb_stor_ufi_command;
break;
}
}
最後一個初始化us的函數是獲得管道資訊的get_pipes函數。
static int get_pipes(struct us_data *us)
{
struct usb_host_interface *altsetting =
us->pusb_intf->cur_altsetting; //擷取設定
int i;
struct usb_endpoint_descriptor *ep; //定義端點描述符
struct usb_endpoint_descriptor *ep_in = NULL;//定義輸入端點描述符
struct usb_endpoint_descriptor *ep_out = NULL;//定義輸出端點描述符
struct usb_endpoint_descriptor *ep_int = NULL;//定義中斷端點描述符
for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {
ep = &altsetting->endpoint[i].desc;//擷取端點描述符
if (usb_endpoint_xfer_bulk(ep)) { //是否是批量傳輸端點
if (usb_endpoint_dir_in(ep)) { //是否是輸入端點
if (!ep_in)
ep_in = ep;//設定為批量傳輸輸入端點
} else {
if (!ep_out)
ep_out = ep;/設定為批量傳輸輸出端點
}
}
else if (usb_endpoint_is_int_in(ep)) { //是否是中斷端點
if (!ep_int)
ep_int = ep;//設定為中斷端點
}
}
if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) {
US_DEBUGP("Endpoint sanity check failed! Rejecting dev.\n");
return -EIO;
}
us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0);//建立輸出控制端點
us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0);//建立輸入控制端點
us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev,
usb_endpoint_num(ep_out));//建立輸出批量傳輸端點
us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev,
usb_endpoint_num(ep_in));//建立輸入批量傳輸端點
if (ep_int) {
us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev,
usb_endpoint_num(ep_int));//建立中斷傳輸端點
us->ep_bInterval = ep_int->bInterval;//設定中斷間隔時間
}
return 0;
}
析完上面get_pipes的代碼,需要補充說明的是,在我們的U盤中隻有輸入批量傳輸和輸出批量傳輸兩個端點,不存在控制端點,如果出現控制端點,那麼裝置支援CBI協定,即Control/Bulk/Interrupt協定,另外U盤也沒有中斷端點。
分析完上面五個對cr初始化的函數後,我們接着需要看usb_stor_acquire_resources了,這個函數主要功能是初始化裝置,并建立資料傳輸的控制線程。
static int usb_stor_acquire_resources(struct us_data *us)
{
int p;
struct task_struct *th;
us->current_urb = usb_alloc_urb(0, GFP_KERNEL); //申請urb
if (!us->current_urb) {
US_DEBUGP("URB allocation failed\n");
return -ENOMEM;
}
if (us->unusual_dev->initFunction) { //特殊裝置的初始化函數
p = us->unusual_dev->initFunction(us);
if (p)
return p;
}
th = kthread_run(usb_stor_control_thread, us, "usb-storage"); //建立并執行控制線程
if (IS_ERR(th)) {
dev_warn(&us->pusb_intf->dev,
"Unable to start control thread\n");
return PTR_ERR(th);
}
us->ctl_thread = th; //儲存線程号
return 0;
}
在上面這個usb_stor_acquire_resources函數中,我們建立并執行了usb_stor_control_thread這個核心線程,這個控制線程用來完成資料的接收和發送,它會一直運作,直到驅動程式退出。
我們來看看這個控制線程。
static int usb_stor_control_thread(void * __us)
{
struct us_data *us = (struct us_data *)__us;
struct Scsi_Host *host = us_to_host(us);
for(;;) {
US_DEBUGP("*** thread sleeping.\n");
if (wait_for_completion_interruptible(&us->cmnd_ready))//等待使用者層SCSI指令喚醒
break;
US_DEBUGP("*** thread awakened.\n");
mutex_lock(&(us->dev_mutex));
scsi_lock(host);
if (us->srb == NULL) {//為循環中逾時後的退出
scsi_unlock(host);
mutex_unlock(&us->dev_mutex);
US_DEBUGP("-- exiting\n");
break;
}
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { //直接跳到逾時判斷去
us->srb->result = DID_ABORT << 16;
goto SkipForAbort;
}
scsi_unlock(host);
if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) {//方向
US_DEBUGP("UNKNOWN data direction\n");
us->srb->result = DID_ERROR << 16;
}
else if (us->srb->device->id &&
!(us->fflags & US_FL_SCM_MULT_TARG)) {
US_DEBUGP("Bad target number (%d:%d)\n",
us->srb->device->id, us->srb->device->lun);
us->srb->result = DID_BAD_TARGET << 16;
}
else if (us->srb->device->lun > us->max_lun) {
US_DEBUGP("Bad LUN (%d:%d)\n",
us->srb->device->id, us->srb->device->lun);
us->srb->result = DID_BAD_TARGET << 16;
}
else if ((us->srb->cmnd[0] == INQUIRY) &&
(us->fflags & US_FL_FIX_INQUIRY)) {//如果SCSI是請求指令的處理
unsigned char data_ptr[36] = {
0x00, 0x80, 0x02, 0x02,
0x1F, 0x00, 0x00, 0x00};
US_DEBUGP("Faking INQUIRY command\n");
fill_inquiry_response(us, data_ptr, 36); //填充一個請求指令
us->srb->result = SAM_STAT_GOOD;
}
else {
US_DEBUG(usb_stor_show_command(us->srb));
us->proto_handler(us->srb, us); //資料傳輸
usb_mark_last_busy(us->pusb_dev);
}
scsi_lock(host);
if (us->srb->result != DID_ABORT << 16) {
US_DEBUGP("scsi cmd done, result=0x%x\n",
us->srb->result);
us->srb->scsi_done(us->srb);
} else {
SkipForAbort:
US_DEBUGP("scsi command aborted\n");
}
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {//逾時處理
complete(&(us->notify));
clear_bit(US_FLIDX_ABORTING, &us->dflags);
clear_bit(US_FLIDX_TIMED_OUT, &us->dflags);
}
us->srb = NULL;
scsi_unlock(host);
mutex_unlock(&us->dev_mutex);
}
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (kthread_should_stop())
break;
schedule();
}
__set_current_state(TASK_RUNNING);
return 0;
}
對于上面這個控制線程,首先該函數執行了一個for(;;),這是一個死循環,也就是這個函數作為一些線程用可以不停息的運作。同時,根據剛開始wait_for_completion_interruptible代碼,我們知道開始就進入睡眠狀态了,隻有喚醒us->cmnd_ready這個控制線程才能繼續執行下去,那我們要知道什麼時候釋放這把鎖來喚醒下面的程式呢?
其實有兩個地方,一個是子產品解除安裝的時候,另一個就是有SCSI指令發過來。每一次應用層發過來SCSI指令了,比如你去讀寫/dev/sda,最終SCSI核心層就會調用與該主機對應的queuecommand函數,這個函數是scsi_host_template結構體成員,在probe中scsi_host_alloc時候注冊的。下面是queuecommand函數的實作。
static int queuecommand(struct scsi_cmnd *srb,void (*done)(struct scsi_cmnd *))
{
struct us_data *us = host_to_us(srb->device->host);
US_DEBUGP("%s called\n", __func__);
if (us->srb != NULL) {
printk(KERN_ERR USB_STORAGE "Error in %s: us->srb = %p\n",__func__, us->srb);
return SCSI_MLQUEUE_HOST_BUSY;
}
if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) {
US_DEBUGP("Fail command during disconnect\n");
srb->result = DID_NO_CONNECT << 16;
done(srb);
return 0;
}
srb->scsi_done = done;
us->srb = srb;
complete(&us->cmnd_ready); //釋放鎖,喚醒控制線程
return 0;
}
好了,使用者層有了SCSI指令,就會執行我們驅動中這個控制線程。這個死循環首先會做一些判斷,然後一直進行資料通信。那麼這個死循環也是會退出的,什麼時候呢?當執行這個死循環的最後一個if語句,會進行逾時處理,如果逾時會将us->srb=NULL,而我們在這個控制線程的死循環中發現,當擷取us->cmnd_ready鎖後,第一個執行的代碼就是判斷us->srb是否為NULL,如果us->srb=NULL就會執行break語句,進而跳出第一個死循環。接下來進入第二個死循環,這個死循環首先判斷是否真的該結束了,如果真的結束了,那麼就break,徹底退出這個控制線程,如果不是應該徹底結束,那進行schedule重新排程控制子線程。
到目前為止,我們的控制線程就已經分析完了,不過我們發現,這個控制線程是在usb_stor_acquire_resources中定義的,在usb_stor_acquire_resources之後,我們還建立了usb_stor_scan_thread線程,這是一個掃描線程。
static int usb_stor_scan_thread(void * __us)
{
struct us_data *us = (struct us_data *)__us;
struct device *dev = &us->pusb_intf->dev;
dev_dbg(dev, "device found\n");
set_freezable();//裝置在一定時間内沒有響應,會挂起
if (delay_use > 0) { // delay_use秒後如果U盤沒拔出則繼續執行,否則執行disconnect
dev_dbg(dev, "waiting for device to settle "
"before scanning\n");
wait_event_freezable_timeout(us->delay_wait,
test_bit(US_FLIDX_DONT_SCAN, &us->dflags),
delay_use * HZ);
}
if (!test_bit(US_FLIDX_DONT_SCAN, &us->dflags)) {
if (us->protocol == USB_PR_BULK &&
!(us->fflags & US_FL_SINGLE_LUN)) {
mutex_lock(&us->dev_mutex);
us->max_lun = usb_stor_Bulk_max_lun(us);//詢問裝置支援多少個LUN
mutex_unlock(&us->dev_mutex);
}
scsi_scan_host(us_to_host(us));
dev_dbg(dev, "scan complete\n");
}
usb_autopm_put_interface(us->pusb_intf);
complete_and_exit(&us->scanning_done, 0);//本程序結束,喚醒disconnect中的程序
}
對于上面這個掃描線程,裡面的usb_stor_Bulk_max_lun函數完成了主機控制器與裝置之間的第一次通信。USB驅動程式首先發送一個指令,然後裝置根據指令傳回一些資訊,這裡顯示的是一個表示LUN個數的數字,usb_stor_Bulk_max_lun完成的是一次控制傳輸。
int usb_stor_Bulk_max_lun(struct us_data *us)
{
int result;
us->iobuf[0] = 0; //預設隻有0個LUN
result = usb_stor_control_msg(us, us->recv_ctrl_pipe,
US_BULK_GET_MAX_LUN,
USB_DIR_IN | USB_TYPE_CLASS |
USB_RECIP_INTERFACE,
0, us->ifnum, us->iobuf, 1, 10*HZ);//向裝置發送一個指令
US_DEBUGP("GetMaxLUN command result is %d, data is %d\n",
result, us->iobuf[0]);
if (result > 0)
return us->iobuf[0];
return 0;
}
我們看看裡面usb_stor_control_msg的實作
int usb_stor_control_msg(struct us_data *us, unsigned int pipe,
u8 request, u8 requesttype, u16 value, u16 index,
void *data, u16 size, int timeout)
{
int status;
US_DEBUGP("%s: rq=%02x rqtype=%02x value=%04x index=%02x len=%u\n",
__func__, request, requesttype,
value, index, size);
us->cr->bRequestType = requesttype; //初始化us->cr
us->cr->bRequest = request;
us->cr->wValue = cpu_to_le16(value);
us->cr->wIndex = cpu_to_le16(index);
us->cr->wLength = cpu_to_le16(size);
usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe,
(unsigned char*) us->cr, data, size,
usb_stor_blocking_completion, NULL);//填充控制urb
status = usb_stor_msg_common(us, timeout);//繼續填充控制urb并送出
if (status == 0)
status = us->current_urb->actual_length;
return status;
}
繼續往下看usb_stor_msg_common的實作
static int usb_stor_msg_common(struct us_data *us, int timeout)
{
struct completion urb_done;
long timeleft;
int status;
if (test_bit(US_FLIDX_ABORTING, &us->dflags))//裝置處于放棄狀态則結束
return -EIO;
init_completion(&urb_done); //初始化完成量
us->current_urb->context = &urb_done;
us->current_urb->transfer_flags = 0;
if (us->current_urb->transfer_buffer == us->iobuf)
us->current_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
us->current_urb->transfer_dma = us->iobuf_dma;
status = usb_submit_urb(us->current_urb, GFP_NOIO);//送出控制urb
if (status) {
return status;
}
set_bit(US_FLIDX_URB_ACTIVE, &us->dflags);
if (test_bit(US_FLIDX_ABORTING, &us->dflags)) {//目前還沒取消urb時,取消urb請求
if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) {
US_DEBUGP("-- cancelling URB\n");
usb_unlink_urb(us->current_urb);
}
}
//等待直到urb完成,如果1秒時間到程序沒有被信号喚醒,則自動喚醒
timeleft = wait_for_completion_interruptible_timeout(
&urb_done, timeout ? : MAX_SCHEDULE_TIMEOUT);
clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags);
if (timeleft <= 0) {
US_DEBUGP("%s -- cancelling URB\n",
timeleft == 0 ? "Timeout" : "Signal");
usb_kill_urb(us->current_urb);
}
return us->current_urb->status;
}
通過對上面這個usb_stor_msg_common函數的分析,我們現在已經把控制urb送出給USB核心了,當處理完,就會通知USB裝置驅動,調用其回調函數,該回調函數在填充控制urb時已經說明,也就是usb_stor_blocking_completion函數
static void usb_stor_blocking_completion(struct urb *urb)
{
struct completion *urb_done_ptr = urb->context;
complete(urb_done_ptr);
}
當一次通信完成,執行了回調函數後,就會釋放鎖,這樣stor_msg_common函數中的wait_for_completion_interruptible_timeout處就會被喚醒,至此一次通信完畢。
最後需要補充說明一個問題,在上面送出控制urb時,flag标志使用的是GFP_NOIO。GFP_NOIO标志的意思是不能在申請記憶體的時候進行I/O操作,原因是usb_submit_urb()送出之後,會讀取磁盤或者U盤中的資料,這種情況下,由于虛拟記憶體的原因,申請記憶體的函數還需要讀取磁盤。是以不允許在usb_submit_urb()送出urb時進行I/O操作。
總結下,USB裝置驅動主要圍繞URB請求塊,也就是控制URB、中斷URB、批量URB、等時URB。USB骨骼架構是批量URB的例子。USB滑鼠是一個中斷URB和input子系統結合的例子。USB鍵盤是一個控制URB和中斷URB結合input子系統的例子。USB的U盤是批量URB和控制URB結合的例子。不幸的是,等時URB沒有填充函數,是以等時URB在被送出給USB核心之前,需要手動進行初始化。
U盤驅動測試:
本Mini2440開發闆具有兩種USB 接口,一個是USB Host,它和普通PC的USB接口是一樣的,可以接USB 攝像頭、USB 鍵盤、USB 滑鼠、優盤等常見的USB外設,另外一種是USB Slave,我們一般使用它來下載下傳程式到目标闆。對于U盤的測試,要配置核心後才能進行測試。
實驗環境:核心linux2.6.32.2,arm-linux-gcc交叉編譯器,mini2440開發闆。
核心配置:(1)因為優盤用到了 SCSI 指令,是以我們先增加SCSI 支援。在 Device Drivers 菜單裡面,選擇SCSI device support。(2)選擇 USB support,按回車進入USB support 菜單,找到并選中USB Mass Storage support。(3)另外,現在的優盤等移動存儲器使用的大都是FAT/FAT32 格式的,是以我們還需要添加FAT32 檔案系統的支援,在核心配置主菜單下進入FAT32 檔案系統配置子菜單,為了支援中英文的編碼,在File systems菜單下選擇Native language support 。
接上面的步驟,在核心源代碼根目錄下執行:make zImage,把生成的新核心燒寫到開發闆中,先不要插入優盤(這樣做是為了看插入時的列印資訊),等系統啟動後,進入指令行控制台,此時優盤,可以看到其廠家ID和産品ID,以及儲存設備uba1資訊。
(1) 執行cat /proc/partitions檢視磁盤分區資訊
(2) 挂載U盤,在mnt目錄下建立usb目錄
執行mkdir /mnt/usb ;mount /dev/uba1 /mnt/usb/
(3) 檢視U盤資訊 ls /mnt/usb –l
(4) 檢視挂載後的分區資訊 df –h