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("
");
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
總結
以上是生活随笔為你收集整理的Linux USB鼠标驱动程序编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梅森素数 判定总结
- 下一篇: 10.10 traceroute:追踪数