1. 程式人生 > >USB鍵盤驅動分析

USB鍵盤驅動分析

自動獲取 index poi ces 實現 內存 pan fill sca

簡介

本文介紹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鍵盤驅動分析