8.1 編寫USB鼠標驅動程序,並測試


學習目標:編寫USB鼠標驅動程序,並測試(將USB鼠標的左鍵當作L按鍵,將USB鼠標的右鍵當作S按鍵,中鍵當作回車按鍵).


一、怎么寫USB設備驅動程序?步驟如下:

1. 首先先定義全局變量usb_driver結構體,並在入口函數中通過usb_register()函數進行注冊

2. 分別寫usb_driver結構體的成員函數:myusb_mouseprobe、myusb_mousedisconnect、myusb_mouseid_table

--> 2.1 usb_driver的probe函數

  1) 分配一個input_dev結構體

  2) 設置input_dev結構體,使它支持L、S、回車3個按鍵事件;

  3) 注冊input_dev結構體

  4) 硬件相關的操作,即設置USB數據傳輸3要素和urb結構體:

   ->4.1) 通過usb_rcvintpipe()創建一個接收中斷類型的端點管道,用來端點和數據緩沖區之間的連接

   ->4.2) 通過usb_buffer_alloc()申請USB緩沖區

   ->4.3) 申請urb結構體,並利用usb_fill_int_urb()初始化,用來傳輸數據

   ->4.4) 因為我們2440支持DMA,所以要告訴urb結構體,使用DMA緩沖區地址

   ->4.5) 使用usb_submit_urb()提交urb.

--> 2.2 編寫probe函數調用的鼠標中斷函數

  1)判斷緩存區數據是否改變,若改變,則通過input_event上傳鼠標事件

  2)使用usb_submit_urb()提交urb

--> 2.3 usb_driver的disconnect函數中

  1) 通過usb_kill_urb()殺掉提交到內核中的urb

  2) 釋放urb結構體

  3 )釋放USB緩存區

  4)注銷並釋放input_device結構體

3. 出口函數通過usb_deregister ()函數注銷usb_driver結構體

二、程序源碼

  1 #include <linux/kernel.h>
  2 #include <linux/slab.h>
  3 #include <linux/module.h>
  4 #include <linux/init.h>
  5 #include <linux/usb/input.h>
  6 #include <linux/hid.h>
  7 
  8 static struct input_dev *uk_dev;        //input_dev
  9 static char *usb_buf;                   //虛擬地址緩存區
 10 static dma_addr_t usb_buf_phys;         //DMA緩存區
 11 static int len;                         //數據包長度
 12 static struct urb *uk_urb;              //urb數據傳輸所用結構體
 13 
 14 static struct usb_device_id myusb_mouseid_table [] = {
 15     { USB_INTERFACE_INFO(
 16                             USB_INTERFACE_CLASS_HID,         //接口類:hid類
 17                             USB_INTERFACE_SUBCLASS_BOOT,     //子類:啟動設備類
 18                             USB_INTERFACE_PROTOCOL_MOUSE) }, // USB協議:鼠標協議
 19 };
 20 
 21 static void myusb_mouseirq(struct urb *urb)
 22 {
 23     static unsigned char pre_val;
 25     /* USB鼠標數據含義* data[0]:   bit0-左鍵, 1-按下, 0-松開
 26      *                           bit1-右鍵, 1-按下, 0-松開
 27      *                           bit2-中鍵, 1-按下, 0-松開  29      */
 30     if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
 31     {
 32         /* 左鍵發生了變化 */
 33         input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
 34         input_sync(uk_dev);
 35     }
 37     if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
 38     {
 39         /* 右鍵發生了變化 */
 40         input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
 41         input_sync(uk_dev);
 42     }
 44     if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
 45     {
 46         /* 中鍵發生了變化 */
 47         input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
 48         input_sync(uk_dev);
 49     }
 51     pre_val = usb_buf[0];
 53     /* 重新提交urb */
 54     usb_submit_urb(uk_urb, GFP_KERNEL);
 55 }
 57 static int myusb_mouseprobe(struct usb_interface *intf, const struct usb_device_id *id)
 58 {
 59     struct usb_device *dev = interface_to_usbdev(intf); // 設備,通過usb_ interface接口獲取usb_device設備,為后面設置USB數據傳輸用
 60     struct usb_host_interface *interface;               // 當前接口
 61     struct usb_endpoint_descriptor *endpoint;
 62     int pipe;                                            // 端點管道
 63     
 64     interface = intf->cur_altsetting;
 65     endpoint = &interface->endpoint[0].desc;            // 當前接口下的端點描述符
 66 
 67     /* a. 分配一個input_dev */
 68     uk_dev = input_allocate_device(); 
 70     /* b. 設置 */
 71     /* b.1 能產生哪類事件 */ //鍵盤變量定義在:include/linux/input.h, 比如: KEY_L(按鍵L)
72 set_bit(EV_KEY, uk_dev->evbit);
 73     set_bit(EV_REP, uk_dev->evbit);
 74     
 75     /* b.2 能產生哪些事件 */
 76     set_bit(KEY_L, uk_dev->keybit);
 77     set_bit(KEY_S, uk_dev->keybit);
 78     set_bit(KEY_ENTER, uk_dev->keybit);
 79     
 80     /* c. 注冊 */
 81     input_register_device(uk_dev);
 82     
 83     /* d. 硬件相關操作 */
 84     /* 數據傳輸3要素: 源,目的,長度 */
 85     /* 源: USB設備的某個端點 */
 86     pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
 87 
 88     /* 長度: */
 89     len = endpoint->wMaxPacketSize;
 90 
 91     /* 目的: */
 92     usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
 93 
 94     /* 使用"3要素" */
 95     /* 分配usb request block */
 96     uk_urb = usb_alloc_urb(0, GFP_KERNEL);//usb傳輸素具的urb結構體
 97     /* 使用"3要素設置urb" */
 98     usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, myusb_mouseirq, NULL, endpoint->bInterval);
 99     uk_urb->transfer_dma = usb_buf_phys;   //設置DMA地址
100     uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //設置使用DMA地址
101 
102     /* 使用URB */
103     usb_submit_urb(uk_urb, GFP_KERNEL);
104     
105     return 0;
106 }
108 static void myusb_mousedisconnect(struct usb_interface *intf)
109 {
110     struct usb_device *dev = interface_to_usbdev(intf);
112     //printk("disconnect usbmouse!\n");
113     usb_kill_urb(uk_urb);
114     usb_free_urb(uk_urb);
115 
116     usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
117     input_unregister_device(uk_dev);
118     input_free_device(uk_dev);
119 }
121 /* 1. 分配/設置usb_driver */
122 static struct usb_driver myusb_mousedriver = {
123     .name        = "myusb_mouse",
124     .probe        = myusb_mouseprobe,
125     .disconnect    = myusb_mousedisconnect,
126     .id_table    = myusb_mouseid_table,
127 };
130 static int myusb_mouseinit(void)
131 {
132     /* 2. 注冊 */
133     usb_register(&myusb_mousedriver);
134     return 0;
135 }
137 static void myusb_mouseexit(void)
138 {
139     usb_deregister(&myusb_mousedriver);    
140 }
142 module_init(myusb_mouseinit);
143 module_exit(myusb_mouseexit);
145 MODULE_LICENSE("GPL");

 三、源碼分析

3.1 id_table

1 struct usb_device_id usbmouse_id_table []=USB_INTERFACE_INFO(cl,sc,pr);

USB_INTERFACE_INFO()設置usb_driver驅動的id_table成員

cl:接口類,我們USB鼠標為HID類,所以填入0X03,也就是USB_INTERFACE_CLASS_HID

sc:接口子類為啟動設備,填入USB_INTERFACE_SUBCLASS_BOOT

pr:接口協議為鼠標協議,填入USB_INTERFACE_PROTOCOL_MOUSE 

3.2 usb_rcvintpipe()函數

1 pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress);

創建一個接收(rcv)中斷(int)類型的端點管道(pipe),用來端點和數據緩沖區之間的連接, 鼠標為接收中斷型

dev: usb_device設備結構體

endpoint->bEndpointAddress:為端點描述符的成員,即端點地址

1)對於控制類型的端點管道使用: usb_sndctrlpipe()/usb_rcvctrlpipe()

2)對於實時類型的端點管道使用: usb_sndisocpipe()/usb_sndisocpipe()

3)對於批量類型的端點管道使用: usb_sndbulkpipe()/usb_rcvbulkpipe()

3.3 usb_driver注冊函數

1 usb_deregister(struct usb_driver *driver);

注意:注冊一個usb_driver驅動,然后內核會通過usb_driver的成員.id_table函數匹配一次USB設備,匹配成功就會調用usb_driver的成員.probe函數

3.4 usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

1 char *usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma);

作用:分配一個usb緩沖區,該緩存區的物理地址會與虛擬地址的數據一致,分配成功返回一個char型緩沖區虛擬地址

*dev: usb_device設備結構體

size: 分配的緩沖區大小,這里填端點描述符的成員endpoint->wMaxPacketSize   len  //端點最大包長

mem_flags: 分配內存的參數,這里填GFP_ATOMIC,表示從不睡眠

dma: DMA緩沖區物理地址

注銷/釋放函數:

void usb_buffer_free(struct usb_device *dev,size_t size,void *addr,dma_addr_t dma);

注銷分配的usb緩沖區,在usb_driver的disconnect成員函數中使用

addr: 要注銷的緩沖區虛擬地址

dma: 要注銷的DMA緩沖區虛擬地址

3.5 uk_urb = usb_alloc_urb(0, GFP_KERNEL);//usb傳輸數據的urb結構體

1 struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

分配一個urb數據結構體, 分配成功返回一個urb結構體

urb全稱為usb request block,USB傳輸數據時,就是打包成urb結構體來傳輸

iso_packets:表示iso類型的包個數,這里我們不是iso類型包,直接填0

mem_flags:分配內存的參數,這里填入GFP_KERNEL,正常分配

3.6 usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, myusb_mouseirq, NULL, endpoint->bInterval);

1 static inline void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,
2                       void *transfer_buffer,int buffer_length, 
3                       usb_complete_t complete_fn,void *context,int interval);

初始化中斷型端點的urb數據結構體

1)針對批量型端點的urb使用usb_fill_bulk_urb()

2)針對控制型端點的urb使用usb_fill_control_urb()

3)針對等時型端點的urb  需要手動初始化。

urb:指向要初始化的urb

dev:指向要傳輸的usb設備

pipe:要傳輸的端點管道, 本節的pipe通過usb_rcvintpipe()宏獲取

transfer_buffer:指向要傳輸數據的虛擬地址緩沖區

buffer_length:數據大小, 這里填端點描述符的成員endpoint->wMaxPacketS //端點最大包長

complete_fn:數據傳輸完成后產生的中斷函數

context:會放在urb->context結構成員中,用來給中斷函數用,本節不需要,填NULL即可

interval:間隔時間,表示間隔多少時間讀一次數據,填入endpoint-> bInterval即可

3.7 usb_submit_urb(uk_urb, GFP_KERNEL);

1 int usb_submit_urb(struct urb *urb,gfp_t mem_flags);

提交urb到內核,初始化urb和中斷函數退出時,都要重新提交一次,告訴內核初始化內存緩存等.

三、測試

1. 在源碼下去除原先的usb鼠標驅動,執行 make menuconfig

  --> Device Drivers
      --> HID Devices
        <> USB Human Interface Device (full HID) support

make uImage編譯內核,並燒寫,啟動。

2. ubuntu環境下編譯生成驅動模塊:myusbmouse.ko

3. 開發板掛載ubuntu的/work/nfs_root/first_fs目錄: # mount -t nfs -o nolock,vers=2 10.70.12.103:/work/nfs_root/ /mnt

4. 加載驅動:# insmod myusbmouse.ko

5. 插入USB鼠標,查看設備節點: ls /dev/event*

6.  cat /dev/tty1    點擊鼠標按鍵

(注意:出現亂碼,需要關掉QT,#vi /etc/init.d/rcs   屏蔽一行 #/bin/qpe.sh &)

執行命令 # exec 0</dev/tty0 可以當做鍵盤使用ls命令,(這里exec 0</dev/tty1 表示將/dev/tty1的輸入作為標准輸,0代表的是STDIN,標准輸入)

退出時輸入:# exec 0 即可。 

7.  hexdump /dev/event1 

 


參考:https://www.cnblogs.com/lifexy/p/7641602.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM