1. 程式人生 > 實用技巧 >Linux USB滑鼠驅動程式編寫

Linux USB滑鼠驅動程式編寫

學習目的:

  • 編寫usb滑鼠驅動程式,模擬l、s、enter按鍵值按下

前面對Linux中USB層次進行了簡單分析,瞭解到核心中USB驅動分為兩類:USB主機控制器驅動程式(Host Controller Driver)、USB裝置驅動程式(USB device drivers)USB主機控制器驅動程式由核心實現,提供訪問USB裝置的介面,它是一個“資料通道”,至於這些資料有什麼作用,這要靠上層的USB裝置驅動程式來解釋。USB裝置驅動程式使用USB核心層提供的介面來訪問USB裝置,不需要關心主機控制器和裝置如何進行通訊,只需實現usb裝置功能使用程式,這部分就是驅動編寫者根據功能要求來完成的工作。

編寫USB裝置驅動程式主要可以概括為以下幾個步驟:

① 填充ubs_driver結構體

完成usb_driver結構體成員函式

③ 向核心註冊usb_driver

下面根據上述的幾個步驟來完成我們今天的usb滑鼠驅動程式的編寫,實現當滑鼠左鍵按下模擬鍵盤上L按鍵值,滑鼠右鍵按下模擬鍵盤S鍵值,滾輪鍵按下模擬鍵盤enter鍵值。這個驅動程式中不僅用到了usb裝置驅動編寫相關方面知識,還需要對驅動中輸入子系統模型有所瞭解

1、填充usb_driver結構體

static struct usb_driver usb_drv = {
    .name        = "usbmouse",
    .probe        
= usb_drv_probe, .disconnect = usb_drv_disconnect, .id_table = usb_drv_id_table, };

.probe函式:probe函式在插入的usb裝置匹配到支援的驅動程式時,被自動呼叫,一般用來分配所需要的資源

.disconnect函式:disconnect函式在插入usb裝置拔出時,被自動呼叫,一般用來釋放分配的資源

.id_table:usb裝置和裝置驅動通過id_table進行匹配,id_table中包含當前驅動所支援的裝置資訊

2、usb_driver結構體成員實現

2.1 usb_drv_probe函式

static int usb_drv_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(intf); 
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe;

    interface = intf->cur_altsetting;

    endpoint = &interface->endpoint[0].desc;

    /* 分配一個input_dev */
    usb_input_dev = input_allocate_device();--------------->①

    /* 能產生哪一類事件 */
    set_bit(EV_KEY, usb_input_dev->evbit);----------------->②
    set_bit(EV_REP, usb_input_dev->evbit);

    /* 能產生該類事件的那些事件 */
    set_bit(KEY_L, usb_input_dev->keybit);
    set_bit(KEY_S, usb_input_dev->keybit);
    set_bit(KEY_ENTER, usb_input_dev->keybit);

    /* 註冊一個input_dev */
    input_register_device(usb_input_dev);

    /* 資料傳輸3要素:源-目的-長度 */
    /* 源:USB裝置的某個端點 */
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);----->③

    /* 長度 */
    len = endpoint->wMaxPacketSize;----------------------------->

    /* 目的 */
    usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);------------>

    uk_urb = usb_alloc_urb(0, GFP_KERNEL);

    usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usb_drv_irq, NULL, endpoint->bInterval);------------->
    uk_urb->transfer_dma = usb_buf_phys;
    uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

    usb_submit_urb(uk_urb, GFP_KERNEL);------------------------>⑦
    
    return 0;
}

usb_drv_probe函式是這個驅動程式的核心,當usb裝置的介面和這個驅動程式的id_table匹配成功時,自動被呼叫。

函式內分配了input_dev結構體,用來支援產生輸入事件;通過usb核心層提供函式,獲取與該驅動匹配成功的裝置介面的端點資訊,並根據這些資訊設定了urb,並向主機控制器請求資料傳輸

① 分配了input_dev結構體

② 設定輸入裝置可以產生按鍵類和重複類事件,並支援上報按鍵類事件中的l、s、enter鍵狀態資訊

③ 獲取usb通訊所需要的地址資訊,裝置地址資訊+端點號,從端點讀取資料

⑤ 獲取端點一次資料通訊可以傳輸的最大資料長度

⑥ 填充urb,滑鼠和usb主機通訊使用的是中斷方式的資料傳輸。urb(usb request block)可以看成是裝置驅動對主機控制器如何進行資料傳輸的描述,包括資料傳輸的三要素,源、目的、輸出長度。這裡我們使用到的傳輸方式是週期性的檢測驅動匹配的裝置的介面的某個端點中是否有資料,如果有資料,就從中讀取一定長度的資料,將資料存放到我們指定的記憶體地址中

⑦ urb資料填充完成後,還需要呼叫usb_submit_urb函式告訴主機控制器自己的請求,此時控制權將交給主機控制器驅動程式,主機控制器根據提交的urb資訊進行資料傳輸。

注:usb中斷方式資料傳輸不是真正意義上的硬體中斷請求傳輸,而是通過endpoint->bInterval指定時間,在指定時間內主機控制器去查詢端點內是否有資料可供讀取

2.1.1 usb_drv_irq函式

static void usb_drv_irq(struct urb *urb)
{
    /*
     *bit[1]   滑鼠     按鍵值
     *  |--->1 左鍵----->s
     *  |--->2 右鍵----->l
     *bit[6]
     *  |--->1 中間鍵-->enter
     */        
    static char old_ls = 0, old_ent = 0;

    if(usb_buf[1] != old_ls)
    {
        if(usb_buf[1]&0x01 || old_ls&0x01)
        {
            input_report_key(usb_input_dev, KEY_L,   usb_buf[1] & 0x01);
            
        }
        else if(usb_buf[1]&0x02 || old_ls&0x02)
        {
            input_report_key(usb_input_dev, KEY_S,   usb_buf[1] & 0x02);
        }
        input_sync(usb_input_dev);
        old_ls = usb_buf[1];
    }
    else if(usb_buf[6] != old_ent)
    {
        input_report_key(usb_input_dev, KEY_ENTER, usb_buf[6] & 0x01);
        input_sync(usb_input_dev);
        old_ent = usb_buf[6];
    }
    usb_submit_urb(uk_urb, GFP_KERNEL);
}

usb_drv_irq函式是urb結構中設定的資料傳輸完成時呼叫的處理函式,這個函式是當資料從源到目的地址傳輸完成時,被主機控制器呼叫的

usb_drv_irq函式根據解析usb主機控制器獲取的資料,判斷滑鼠左鍵、右鍵、滾輪鍵是否被按下,如果其中的一個鍵被按下,將通過輸入子系統上報資料狀態資訊

注意:usb滑鼠傳輸的資料的資訊中,哪一位元組,哪一位代表滑鼠的那些動作狀態,需要根據自己滑鼠特性去設定。最簡單的是打印出從端點中一次讀取的資料資訊,多次嘗試,將動作狀態和資料資訊位進行匹配

2.2 usb_drv_id_table

static struct usb_device_id usb_drv_id_table [] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE) },
    { }    /* Terminating entry */
};

usb_device_id結構體型別陣列,用來存放匹配可支援裝置的相關資訊,此處使用USB_INTERFACE_INFO巨集對成員進行的初始化

USB_INTERFACE_CLASS_HID:匹配USB裝置介面型別須為人機互動類裝置

USB_INTERFACE_SUBCLASS_BOOT:匹配USB裝置介面子類須為boot

USB_INTERFACE_PROTOCOL_MOUSE:匹配USB裝置介面協議須為滑鼠

2.3 usb_drv_disconnect函式

static void usb_drv_disconnect(struct usb_interface *intf)
{
    struct usb_device *dev = interface_to_usbdev(intf); 
    
    usb_kill_urb(uk_urb);------------------------->①
    input_unregister_device(usb_input_dev);------->②
    usb_free_urb(uk_urb);------------------------->③
    usb_free_coherent(dev, len, NULL, usb_buf_phys);---->④
}

disconnect函式在拔出裝置時被自動呼叫,用來釋放驅動中分配的資料資訊

① 取消提交的urb請求

② 登出註冊的輸入子系統裝置

③ 釋放動態分配urb結構記憶體

④ 釋放動態分配用於存放usb傳輸資料的記憶體

3、註冊usb_driver

static int usb_drv_init(void)
{
    usb_register(&usb_drv);
    return 0;
}

usb_drv_init驅動入口函式,usb_register向核心註冊usb驅動

4、測試

1)使用insmod載入驅動程式

2)插入滑鼠裝置,執行exec 0</dev/tty1讓系統標準輸入來tty1裝置,這樣滑鼠按下後上報按鍵值可以顯示在裝置終端上

3) 依次按下滑鼠左鍵-》右鍵-》滾輪鍵,看終端上是否顯示ls,以及是否執行了ls命令

完整驅動程式

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/usb/hcd.h>
#include <linux/hid.h>


static struct input_dev *usb_input_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static struct urb *uk_urb;


static int len;


static void usb_drv_irq(struct urb *urb)
{
    /*
     *bit[1]   滑鼠     按鍵值
     *    |--->1 左鍵----->s
     *  |--->2 右鍵----->l
     *bit[6]
     *  |--->1 中間鍵-->enter
     */        
#if 0
    int i;
    static int cnt = 0;
    printk("data cnt %d: ", ++cnt);
    for(i = 0; i < len; i++)
    {
        printk("%02x", usb_buf[i]);
    }
    printk("\n");
    usb_submit_urb(uk_urb, GFP_KERNEL);
#else
    static char old_ls = 0, old_ent = 0;

    if(usb_buf[1] != old_ls)
    {
        if(usb_buf[1]&0x01 || old_ls&0x01)
        {
            input_report_key(usb_input_dev, KEY_L,   usb_buf[1] & 0x01);
            
        }
        else if(usb_buf[1]&0x02 || old_ls&0x02)
        {
            input_report_key(usb_input_dev, KEY_S,   usb_buf[1] & 0x02);
        }
        input_sync(usb_input_dev);
        old_ls = usb_buf[1];
    }
    else if(usb_buf[6] != old_ent)
    {
        input_report_key(usb_input_dev, KEY_ENTER, usb_buf[6] & 0x01);
        input_sync(usb_input_dev);
        old_ent = usb_buf[6];
    }
    usb_submit_urb(uk_urb, GFP_KERNEL);
#endif
}


static int usb_drv_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(intf); 
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe;

    interface = intf->cur_altsetting;

    endpoint = &interface->endpoint[0].desc;

    /* 分配一個input_dev */
    usb_input_dev = input_allocate_device();

    /* 能產生哪一類事件 */
    set_bit(EV_KEY, usb_input_dev->evbit);
    set_bit(EV_REP, usb_input_dev->evbit);

    /* 能產生該類事件的那些事件 */
    set_bit(KEY_L, usb_input_dev->keybit);
    set_bit(KEY_S, usb_input_dev->keybit);
    set_bit(KEY_ENTER, usb_input_dev->keybit);

    /* 註冊一個input_dev */
    input_register_device(usb_input_dev);

    /* 資料傳輸3要素:源-目的-長度 */
    /* 源:USB裝置的某個端點 */
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

    /* 長度 */
    len = endpoint->wMaxPacketSize;

    /* 目的 */
    usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

    uk_urb = usb_alloc_urb(0, GFP_KERNEL);

    usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usb_drv_irq, NULL, endpoint->bInterval);
    uk_urb->transfer_dma = usb_buf_phys;
    uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

    usb_submit_urb(uk_urb, GFP_KERNEL);
    
    return 0;
}

static void usb_drv_disconnect(struct usb_interface *intf)
{
    struct usb_device *dev = interface_to_usbdev(intf); 
    
    usb_kill_urb(uk_urb);
    input_unregister_device(usb_input_dev);
    usb_free_urb(uk_urb);
    usb_free_coherent(dev, len, NULL, usb_buf_phys);
}

static struct usb_device_id usb_drv_id_table [] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE) },
    { }    /* Terminating entry */
};


static struct usb_driver usb_drv = {
    .name        = "usbmouse",
    .probe        = usb_drv_probe,
    .disconnect    = usb_drv_disconnect,
    .id_table    = usb_drv_id_table,
};


static int usb_drv_init(void)
{
    usb_register(&usb_drv);
    return 0;
}

static void usb_drv_exit(void)
{
    usb_deregister(&usb_drv);
}

module_init(usb_drv_init);
module_exit(usb_drv_exit);

MODULE_LICENSE("GPL");
usb_drv.c