在上一章分析完USB總線驅動程序后, 接下來開始寫一個USB驅動:
本節目的: 將USB鼠標的左鍵當作L按鍵,將USB鼠標的右鍵當作S按鍵,中鍵當作回車按鍵
參考/drivers/hid/usbhid/usbmouse.c(內核自帶的USB鼠標驅動)
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
struct usb_device *dev=interface_to_usbdev(intf);
通過usb_ interface接口獲取usb_device設備,為后面設置USB數據傳輸用
pipe=usb_rcvintpipe(dev,endpoint);
創建一個接收(rcv)中斷(int)類型的端點管道(pipe),用來端點和數據緩沖區之間的連接,鼠標為接收中斷型
dev: usb_device設備結構體
endpoint:為端點描述符的成員endpoint->bEndpointAddress //端點地址
- 對於控制類型的端點管道使用: usb_sndctrlpipe()/usb_rcvctrlpipe()
- 對於實時類型的端點管道使用: usb_sndisocpipe()/usb_sndisocpipe()
- 對於批量類型的端點管道使用: usb_sndbulkpipe()/usb_rcvbulkpipe()
2.本節需要用到的函數如下:
usb_deregister(struct usb_driver *driver);
注冊一個usb_driver驅動,然后內核會通過usb_driver的成員.id_table函數匹配一次USB設備,匹配成功就會調用usb_driver的成員.probe函數
usb_deregister(struct usb_driver *driver);
注銷一個usb_driver驅動,在出口函數中寫
*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 //端點最大包長
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緩沖區虛擬地址
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,正常分配
其中urb結構體如下所示:
struct urb { ... ... struct usb_device *dev; //指向usb設備 struct usb_host_endpoint *ep; //指向端點的數據結構 unsigned int pipe; //指向端點管道(pipe), 本節的pipe通過usb_rcvintpipe()宏獲取 int status; //狀態,當status==0,表示數據被成功地收到/發送 unsigned int transfer_flags; //傳輸狀態 ... ... /*以下兩個緩沖區通過usb_buffer_alloc ()函數獲取 */ //urb結構體默認的transfer_flags是URB_NO_SETUP_DMA_MAP ,也就是說沒有提供DMA的緩沖區 //就會使用transfer_buffer虛擬地址緩沖區來當緩沖區 //當支持DMA緩沖區時,就需要手動設置transfer_flags =URB_NO_TRANSFER_DMA_MAP,並手動設置transfer_dma等於獲取到的DMA物理地址 void *transfer_buffer; //虛擬緩沖區 dma_addr_t transfer_dma; //DMA物理緩沖區 ... ... };
void usb_free_urb(struct urb *urb);
釋放申請的urb,在usb_driver的disconnect成員函數中使用
static inline void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,
void *transfer_buffer,int buffer_length,
usb_complete_t complete_fn,void *context,int interval);
初始化中斷型端點的urb數據結構體
針對批量型端點的urb使用usb_fill_bulk_urb()
針對控制型端點的urb使用usb_fill_control_urb()
針對等時型端點的urb 需要手動初始化。
urb:指向要初始化的urb
dev:指向要傳輸的usb設備
pipe:要傳輸的端點管道, 本節的pipe通過usb_rcvintpipe()宏獲取
transfer_buffer:指向要傳輸數據的虛擬地址緩沖區
buffer_length:數據大小, 這里填端點描述符的成員endpoint->wMaxPacketS //端點最大包長
complete_fn:數據傳輸完成后產生的中斷函數
context:會放在urb->context結構成員中,用來給中斷函數用,本節不需要,填NULL即可
interval:間隔時間,表示間隔多少時間讀一次數據,填入endpoint-> bInterval即可
int usb_submit_urb(struct urb *urb,gfp_t mem_flags);
提交urb到內核,初始化urb和中斷函數退出時,都要重新提交一次,告訴內核初始化內存緩存等
void usb_kill_urb(struct urb *urb);
殺掉urb,在usb_driver的disconnect成員函數中使用
3.步驟如下:
首先先定義全局變量:usb_driver結構體,input_dev指針結構體 ,虛擬地址緩存區,DMA地址緩存區
3.1在入口函數中
1)通過usb_register()函數注冊usb_driver結構體
3.2在usb_driver的probe函數中
1)分配一個input_dev結構體
2)設置input_dev支持L、S、回車、3個按鍵事件
3)注冊input_dev結構體
4)設置USB數據傳輸:
->4.1)通過usb_rcvintpipe()創建一個接收中斷類型的端點管道,用來端點和數據緩沖區之間的連接
->4.2)通過usb_buffer_alloc()申請USB緩沖區
->4.3)申請並初始化urb結構體,urb:用來傳輸數據
->4.4) 因為我們2440支持DMA,所以要告訴urb結構體,使用DMA緩沖區地址
->4.5)使用usb_submit_urb()提交urb
3.3在鼠標中斷函數中
1)判斷緩存區數據是否改變,若改變則上傳鼠標事件
2)使用usb_submit_urb()提交urb
3.4.在usb_driver的disconnect函數中
1)通過usb_kill_urb()殺掉提交到內核中的urb
2)釋放urb
3)釋放USB緩存區
4)注銷input_device,釋放input_device
3.5在出口函數中
1)通過usb_deregister ()函數注銷usb_driver結構體
4.代碼如下:
#include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/init.h> #include <linux/usb/input.h> #include <linux/hid.h> static struct input_dev *myusb_mouse_dev; //input_dev
static char *myusb_mouse_buf; //虛擬地址緩存區 static dma_addr_t myusb_mouse_phyc; //DMA緩存區; static __le16 myusb_mouse_size; //數據包長度 static struct urb *myusb_mouse_urb; //urb static void myusb_mouse_irq(struct urb *urb) //鼠標中斷函數 { static char buf1=0; //for(i=0;i<myusb_mouse_size;i++) // printk("%02x ",myusb_mouse_buf[i]); // printk("\n"); /*bit 1-左右中鍵 0X01:左鍵 0X02:右鍵 0x04:中鍵 */ if((buf1&(0X01)) != (myusb_mouse_buf[1]&(0X01))) { input_report_key(myusb_mouse_dev, KEY_L, buf1&(0X01)? 1:0); input_sync(myusb_mouse_dev); } if((buf1&(0X02)) != (myusb_mouse_buf[1]&(0X02))) { input_report_key(myusb_mouse_dev, KEY_S, buf1&(0X02)? 1:0); input_sync(myusb_mouse_dev); } if((buf1&(0X04)) != (myusb_mouse_buf[1]&(0X04)) ) { input_report_key(myusb_mouse_dev, KEY_ENTER, buf1&(0X04)? 1:0); input_sync(myusb_mouse_dev); } buf1=myusb_mouse_buf[1]; //更新數據 usb_submit_urb(myusb_mouse_urb, GFP_KERNEL); } static int myusb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); //設備 struct usb_endpoint_descriptor *endpoint; struct usb_host_interface *interface; //當前接口 int pipe; //端點管道 interface=intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; //當前接口下的端點描述符 printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); //打印VID,PID /* 1)分配一個input_dev結構體 */ myusb_mouse_dev=input_allocate_device(); /* 2)設置input_dev支持L、S,回車、3個按鍵事件*/ set_bit(EV_KEY, myusb_mouse_dev->evbit); set_bit(EV_REP, myusb_mouse_dev->evbit); //支持重復按功能
set_bit(KEY_L, myusb_mouse_dev->keybit); set_bit(KEY_S, myusb_mouse_dev->keybit); set_bit(KEY_ENTER, myusb_mouse_dev->keybit); /* 3)注冊input_dev結構體*/ input_register_device(myusb_mouse_dev); /* 4)設置USB數據傳輸 */ /*->4.1)通過usb_rcvintpipe()創建一個端點管道*/ pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); /*->4.2)通過usb_buffer_alloc()申請USB緩沖區*/ myusb_mouse_size=endpoint->wMaxPacketSize; myusb_mouse_buf=usb_buffer_alloc(dev,myusb_mouse_size,GFP_ATOMIC,&myusb_mouse_phyc); /*->4.3)通過usb_alloc_urb()和usb_fill_int_urb()申請並初始化urb結構體 */ myusb_mouse_urb=usb_alloc_urb(0,GFP_KERNEL);
usb_fill_int_urb (myusb_mouse_urb, //urb結構體 dev, //usb設備 pipe, //端點管道 myusb_mouse_buf, //緩存區地址 myusb_mouse_size, //數據長度 myusb_mouse_irq, //中斷函數 0, endpoint->bInterval); //中斷間隔時間 /*->4.4) 因為我們2440支持DMA,所以要告訴urb結構體,使用DMA緩沖區地址*/ myusb_mouse_urb->transfer_dma =myusb_mouse_phyc; //設置DMA地址 myusb_mouse_urb->transfer_flags =URB_NO_TRANSFER_DMA_MAP; //設置使用DMA地址 /*->4.5)使用usb_submit_urb()提交urb*/ usb_submit_urb(myusb_mouse_urb, GFP_KERNEL); return 0; } static void myusb_mouse_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); //設備 usb_kill_urb(myusb_mouse_urb); usb_free_urb(myusb_mouse_urb); usb_buffer_free(dev, myusb_mouse_size, myusb_mouse_buf,myusb_mouse_phyc); input_unregister_device(myusb_mouse_dev); //注銷內核中的input_dev input_free_device(myusb_mouse_dev); //釋放input_dev } static struct usb_device_id myusb_mouse_id_table [] = { { USB_INTERFACE_INFO( USB_INTERFACE_CLASS_HID, //接口類:hid類 USB_INTERFACE_SUBCLASS_BOOT, //子類:啟動設備類 USB_INTERFACE_PROTOCOL_MOUSE) }, //USB協議:鼠標協議 }; static struct usb_driver myusb_mouse_drv = { .name = "myusb_mouse", .probe = myusb_mouse_probe, .disconnect = myusb_mouse_disconnect, .id_table = myusb_mouse_id_table, }; /*入口函數*/ static int myusb_mouse_init(void) { usb_register(&myusb_mouse_drv); return 0; } /*出口函數*/ static void myusb_mouse_exit(void) { usb_deregister(&myusb_mouse_drv); } module_init(myusb_mouse_init); module_exit(myusb_mouse_exit); MODULE_LICENSE("GPL");
5.測試運行
5.1 重新設置編譯內核(去掉默認的hid_USB驅動)
make menuconfig ,進入menu菜單重新設置內核參數:
進入-> Device Drivers -> HID Devices
<> USB Human Interface Device (full HID) support //hid:人機交互的USB驅動,比如鼠標,鍵盤等
然后make uImage 編譯內核
將新的觸摸屏驅動模塊放入nfs文件系統目錄中
5.2然后燒寫內核,裝載觸摸屏驅動模塊
如下圖,當我們插上USB鼠標時,可以看到該VID和PID,和電腦上的鼠標的參數一樣
5.3使用hexdump命令來調試
(hexdump命令調試代碼詳解地址:http://www.cnblogs.com/lifexy/p/7553550.html)
5.4 使用tty1進程測試
未完待續~~~~~~~~~~ 下節 依葫蘆畫瓢 來寫出 USB鍵盤驅動