USB鍵盤驅動分析
簡介
本文介紹USB驅動程序編寫的流程,分析一個鍵盤的USB程序,基於linux-2.6.39
USB驅動概要
分層
主機層面的USB驅動的整體架構可以分成4層,自頂到下依次是
1、USB設備驅動:本文主要講述的內容,利用USB核心提供的編程接口編寫具體硬件設備與系統的交互邏輯
2、USB核心:linux內核實現,管理上層的USB設備驅動,並且對下面的USB通信機制做封裝,封裝的接口提供給上層做驅動編寫
3、USB主機控制器驅動:主要負責USB硬件和主機通信機制
3、USB控制器:實際的硬件部分
本文講述的鍵盤驅動程序是上面第一層,也就是usb核心之上的驅動程序
USB驅動的邏輯組織
為了方便USB設備驅動的編寫,USB核心將USB設備抽象成4個層次,自頂向下依次是
1、設備:代表整個設備,一個設備會有相對應一個文件等
2、配置:一個設備有一個或是多個配置,不同的配置使設備表現出不同的功能組合,在連接期間必須從中選擇一個。比如說帶話筒的耳機可以可以有3個配置,讓硬件有3中工作模式:耳機工作、話筒工作、耳機和話筒同時工作。
3、接口:一個配置包含多個接口。比如一個USB音響可以有音頻接口和開關按鈕的接口,一個配置可以使所有的接口同時有效,並且被不同的驅動同時連接
4、端點:一個接口可以有多個端點。端點是主機與硬件設備通信的最基本形式,每個端點有屬於自己的地址,並且數據的傳輸是單向的,每一個USB設備在主機看來就是多個端點的集合
在include/linux/usb/ch9.h中,定義了幾個結構體:usb_device_descriptor、usb_config_descriptor 、usb_interface_descriptor、usb_endpoint_descriptor。分別表示上面這些含義的描述符。每個設備均會分配一個或是多個。具體見源碼
linux中USB驅動整體結構
在linux1中,實現了幾個通用的USB設備驅動,劃分為如下幾個設備類
- 音頻設備類
- 通信設備類
- HID人機接口類
- 顯示設備類
- 海量存儲設備類
- 電源設備類
- 打印設備類
- 集線器設備類
所以說一般的統一設備是不需要編寫驅動,linux內核中已經包含了。本文的鍵盤驅動屬於HID人機接口類。
linux中提供了USB設備文件系統usbdevfs,同樣是內存文件系統,在/etc/fstab中添加一行
none /proc/bus/usb usbfs defaults
或者是
mount -t usbfs none /proc/bus/usb
然後查看proc/bus/usb/devices,可以得到完整的usb信息
USB驅動編寫模板
整體骨架
所有 USB 驅動必須創建的主要結構是 struct usb_driver. 這個結構必須被 USB 驅動填充並且包含多個函數回調和變量, 來向 USB 核心代碼描述 USB 驅動
位於linux/usb.h中的usb_driver結構定義
struct usb_driver { const char *name; int (*probe) (struct usb_interface *intf, const struct usb_device_id *id); void (*disconnect) (struct usb_interface *intf); int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code, void *buf); int (*suspend) (struct usb_interface *intf, pm_message_t message); int (*resume) (struct usb_interface *intf); int (*reset_resume)(struct usb_interface *intf); int (*pre_reset)(struct usb_interface *intf); int (*post_reset)(struct usb_interface *intf); const struct usb_device_id *id_table; struct usb_dynids dynids; struct usbdrv_wrap drvwrap; unsigned int no_dynamic_id:1; unsigned int supports_autosuspend:1; unsigned int soft_unbind:1; };
其中比較關鍵的有4個成員,創建一個簡單的usb_driver比如:
static struct usb_driver skel_driver = { .name = "skeleton", .id_table = skel_table, .probe = skel_probe, .disconnect = skel_disconnect, };
const char *name:指向驅動名子的指針. 它必須在內核 USB 驅動中是唯一的並且通常被設置為和驅動的模塊名相同. 它出現在 sysfs 中在 /sys/bus/usb/drivers/ 之下, 當驅動在內核中時.
const struct usb_device_id *id_table:指向 struct usb_device_id 表的指針, 包含這個驅動可接受的所有不同類型 USB 設備的列表. 如果這個變量沒被設置, USB 驅動中的探測回調函數不會被調用. 如果你需要你的驅動給系統中每個 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 核心調用當它認為它有一個這個驅動可處理的 struct usb_interface. 一個指向 USB 核心用來做決定的 struct usb_device_id 的指針也被傳遞到這個函數. 如果這個 USB 驅動主張傳遞給它的 struct usb_interface, 它應當正確地初始化設備並且返回 0. 如果驅動不想主張這個設備, 或者發生一個錯誤, 它應當返回一個負錯誤值.
void (*disconnect) (struct usb_interface *intf):指向 USB 驅動的去連接函數的指針. 這個函數(在"探測和去連接的細節"一節中描述)被 USB 核心調用, 當 struct usb_interface 已被從系統中清除或者當驅動被從 USB 核心卸載.
為註冊 struct usb_driver 到 USB 核心, 一個調用 usb_register_driver 帶一個指向 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; }
當 USB 驅動被卸載, struct usb_driver 需要從內核註銷. 使用對 usb_deregister_driver 的調用做這個. 當這個調用發生, 任何當前綁定到這個驅動的 USB 接口被去連接, 並且去連接函數為它們而被調用.
static void __exit usb_skel_exit(void) { /* deregister this driver with the USB subsystem */ usb_deregister(&skel_driver); }
幾個重要的結構體
USB請求塊
在include/linux/usb.h中,定義了一個urb結構,是USB驅動中用來描述與USB設備通信所有的核心結構,非常類似於網絡設備驅動程序中的sk_buff結構體。一個 USB 設備驅動可能分配許多 urb 給一個端點或者可能重用單個 urb 給多個不同的端點, 根據驅動的需要. 設備中的每個端點都處理一個 urb 隊列, 以至於多個 urb 在隊列清空之前可被發送到相同的端點,urb 的典型生命循環如下:
- 被一個 USB 設備驅動創建.
- 安排給一個特定 USB 設備的特定端點
- 被 USB 設備驅動提交給 USB 核心,
- 被USB 核心提交給特定設備的指定USB主機控制器驅動
- 被 USB 主機控制器處理, 它做一個 USB 傳送到設備.
- 當 urb 完成, USB 主機控制器驅動通知 USB 設備驅動
urb 也可被提交這個 urb 的驅動在任何時間取消, 或者被 USB 核心如果設備被從系統中移出. urb 被動態創建並且包含一個內部引用計數, 使它們在這個 urb 的最後一個用戶釋放它時被自動釋放.
USB請求塊的操作
同時在driver/usb/core/urb.c中定義了很多urb的操作函數
usb_alloc_urb用來創建一個urb,這個函數分配sizeof(struct urb) +iso_packets * sizeof(struct usb_iso_packet_descriptor)大小的內存,參數1表示等時數據包的大小,如果不需要等時傳輸,則應該為0,第二個參數表示內存分配的標誌
usb_alloc_urb(int iso_packets, gfp_t mem_flags)
usb_free_urb則用來釋放urb
usb_free_urb(struct urb *urb)
usb_device_id結構
usb_device_id結構體用來給驅動probing函數和熱插拔提供標誌符
該結構體定義在include/linux/mod_devicetable.c中
USB鍵盤驅動源碼分析
probe函數
static int usb_kbd_probe(struct usb_interface *iface, const struct usb_device_id *id) //usb_interface *iface:由內核自動獲取的接口,一個接口對應一種功能, struct usb_device_id *id:設備的標識符 { printk("Starting probe\n"); struct usb_device *dev = interface_to_usbdev(iface); //獲取usb接口結構體中的usb設備結構體,每個USB設備對應一個struct usb_device的變量,由usb core負責申請和賦值 struct usb_host_interface *interface; //連接到的接口的描述 struct usb_endpoint_descriptor *endpoint; //傳輸數據管道的端點 struct usb_kbd *kbd; //usb設備在用戶空間的描述 struct input_dev *input_dev; //表示輸入設備 int i, pipe, maxp; int error = -ENOMEM; interface = iface->cur_altsetting; //將連接到的接口的描述設置為當前的setting printk("1\n"); if (interface->desc.bNumEndpoints != 1) //判斷中斷IN端點的個數,鍵盤只有一個端點,如果不為1,則出錯,desc是設備描述符 return -ENODEV; endpoint = &interface->endpoint[0].desc; //取得鍵盤中斷IN端點的描述符,endpoint[0]表示中斷端點 printk("2\n"); if (!usb_endpoint_is_int_in(endpoint)) //查看所獲得的端點是否為中斷IN端點 return -ENODEV; printk("3\n"); pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //得到驅動程序的中斷OUT端點號,創建管道,用於連接驅動程序緩沖區和設備端口 maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //得到最大可以傳輸的數據包(字節) kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //為kbd結構體分配內存,GFP_KERNEL是內核內存分配時最常用的標誌位,無內存可用時可引起休眠 input_dev = input_allocate_device(); //為輸入設備的結構體分配內存,並初始化它 printk("inputdev-1:%s\n",input_dev->name); printk("4\n"); if (!kbd || !input_dev) //給kbd或input_dev分配內存失敗 goto fail1; printk("5\n"); if (usb_kbd_alloc_mem(dev, kbd)) //分配urb內存空間失敗,即創建urb失敗 goto fail2; printk("6\n"); kbd->usbdev = dev; //給kbd的usb設備結構體usbdev賦值 kbd->dev = input_dev; //給kbd的輸入設備結構體dev賦值,將所有內容統一用kbd封裝,input子系統只能處理input_dev類型的對象 printk("7\n"); if (dev->manufacturer) //將廠商名,產品名賦值給kbd的name成員 { printk("7.1\n"); strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); } printk("8\n"); if (dev->product) { printk("7.2\n"); if (dev->manufacturer) //有廠商名,就在產品名之前加入空格 strlcat(kbd->name, " ", sizeof(kbd->name)); strlcat(kbd->name, dev->product, sizeof(kbd->name)); } printk("9\n"); printk("10\n"); usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); //分配設備的物理路徑的地址,設備鏈接地址,不隨設備的拔出而改變 printk("10.1\n"); strlcpy(kbd->phys, "/input0", sizeof(kbd->phys)); printk("10.2\n"); input_dev->name = kbd->name; //給input_dev的name賦值 printk("inputdevname:%s\n",input_dev->name); printk("kbddevname:%s\n",kbd->dev->name); input_dev->phys = kbd->phys; //設備鏈接地址 usb_to_input_id(dev, &input_dev->id); //給輸入設備結構體input->的標識符結構賦值,主要設置bustype、vendo、product等 printk("10.3\n"); //input_dev->dev.parent = &iface->dev; //input_set_drvdata(input_dev, kbd); printk("10.4\n"); input_dev->evbit[0] = BIT_MASK(EV_KEY) /*鍵碼事件*/| BIT_MASK(EV_LED) | /*LED事件*/ BIT_MASK(EV_REP)/*自動重覆數值*/; //支持的事件類型 printk("10.5\n"); input_dev->ledbit[0] = BIT_MASK(LED_NUML) /*數字燈*/| BIT_MASK(LED_CAPSL) |/*大小寫燈*/ BIT_MASK(LED_SCROLLL)/*滾動燈*/ ; //EV_LED事件支持的事件碼 printk("10.6\n"); for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], input_dev->keybit); // 初始化,每個鍵盤掃描碼都可以出發鍵盤事件 printk("10.7\n"); clear_bit(0, input_dev->keybit); //為0的鍵盤掃描碼不能觸發鍵盤事件 printk("10.8\n"); input_dev->event = usb_kbd_event; //設置input設備的打開、關閉、寫入數據時的處理方法 input_dev->open = usb_kbd_open; input_dev->close = usb_kbd_close; //初始化中斷URB usb_fill_int_urb(kbd->irq/*初始化kbd->irq這個urb*/, dev/*這個urb要發送到dev這個設備*/, pipe/*這個urb要發送到pipe這個端點*/, kbd->new/*指向緩沖的指針*/, (maxp > 8 ? 8 : maxp)/*緩沖區長度(不超過8)*/, usb_kbd_irq/*這個urb完成時調用的處理函數*/, kbd/*指向數據塊的指針,被添加到這個urb結構可被完成處理函數獲取*/, endpoint->bInterval/*urb應當被調度的間隔*/); printk("10.9\n"); kbd->irq->transfer_dma = kbd->new_dma; //指定urb需要傳輸的DMA緩沖區 kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //本urb有一個DMA緩沖區需要傳輸,用DMA傳輸要設的標誌 kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; //操作的是USB類接口對象 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); //一次數據傳輸要傳的字節數 //初始化控制URB printk("10.10\n"); usb_fill_control_urb(kbd->led/*初始化kbd->led這個urb*/, dev/*這個urb要由dev這個設備發出*/, usb_sndctrlpipe(dev, 0)/*urb發送到的端點*/, (void *) kbd->cr/*發送的setup packet*/, kbd->leds/*待發送數據的緩沖區*/, 1/*發送數據長度*/, usb_kbd_led/*這個urb完成時調用的處理函數*/, kbd/*指向數據塊的指針,被添加到這個urb結構可被完成處理函數獲取*/); kbd->led->setup_dma = kbd->cr_dma; //指定urb需要傳輸的DMA緩沖區 kbd->led->transfer_dma = kbd->leds_dma; //本urb有一個DMA緩沖區需要傳輸,用DMA傳輸要設的標誌 kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP/*如果使用DMA傳輸則urb中setup_dma指針所指向的緩沖區是DMA緩沖區而不是setup_packet所指向的緩沖區*/); printk("10.11\n"); printk("kbddevname1:%s\n",kbd->dev->name); error = input_register_device(kbd->dev); //註冊輸入設備 printk("11\n"); if (error) //註冊失敗 goto fail2; usb_set_intfdata(iface, kbd); //設置接口私有數據,向內核註冊一個data,這個data的結構可以是任意的,這段程序向內核註冊了一個usb_kbd結構,這個data可以在以後用usb_get_intfdata來得到 printk("12\n"); return 0; fail2: usb_kbd_free_mem(dev, kbd); //釋放URB內存空間,銷毀URB fail1: input_free_device(input_dev); //釋放input_dev和kbd的空間 kfree(kbd); return error; }
disconnect函數
static void usb_kbd_disconnect(struct usb_interface *intf) { struct usb_kbd *kbd = usb_get_intfdata (intf); usb_set_intfdata(intf, NULL); if (kbd) { usb_kill_urb(kbd->irq); printk("disconnect1\n"); input_unregister_device(kbd->dev); printk("disconnect2\n"); usb_kbd_free_mem(interface_to_usbdev(intf), kbd); kfree(kbd); } }
中斷處理的函數
static void usb_kbd_irq(struct urb *urb) { printk("Starting irq\n"); struct usb_kbd *kbd = urb->context; int i; switch (urb->status) { //判斷URB的狀態 case 0: //URB被成功接收 break; case -ECONNRESET: //斷開連接錯誤,urb未終止就返回給了回調函數 case -ENOENT: //urb被kill了,生命周期徹底被終止 case -ESHUTDOWN: //USB主控制器驅動程序發生了嚴重的錯誤,或者提交完的一瞬間設備被拔出 return; default: //其它錯誤,均可以重新提交urb goto resubmit; } printk("irq1\n"); for (i = 0; i < 8; i++) input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);/*usb_kbd_keycode[224]-usb_kbd_keycode[231],8次的值依次是:29-42-56-125-97-54-100-126,判斷這8個鍵的狀態*/ printk("irq2\n"); //若同時只按下2個按鍵則另一個鍵在第[2]個字節,若同時有兩個按鍵則第二個在第[3]字節,類推最多可有6個按鍵同時按下 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 //不是鍵盤所用的按鍵 dev_info(&urb->dev->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 //不是鍵盤所用的按鍵 dev_info(&urb->dev->dev, "Unknown key (scancode %#x) released.\n", kbd->new[i]); } } input_sync(kbd->dev); //同步設備,告知事件的接收者驅動已經發出了一個完整的input子系統的報告 printk("irq2.1\n"); memcpy(kbd->old, kbd->new, 8); //將本次的按鍵狀態拷貝到kbd->old,用作下次urb處理時判斷按鍵狀態的改變 printk("irq3\n"); resubmit: i = usb_submit_urb (urb, GFP_ATOMIC); //重新發送urb請求塊 if (i) //發送urb請求塊失敗 { printk("irq4\n"); err_hid ("can‘t resubmit intr, %s-%s/input0, status %d", kbd->usbdev->bus->bus_name, kbd->usbdev->devpath, i); printk("irq5\n"); } printk("irq6\n"); }
USB鍵盤驅動分析