一、驅動類型
????????USB 驅動開發主要分為兩種:主機側的驅動程序和設備側的驅動程序。一般我們編寫的都是主機側的USB驅動程序。
????????主機側驅動程序用于控制插入到主機中的 USB 設備,而設備側驅動程序則負責控制 USB 設備如何與主機通信。由于設備側驅動程序通常與設備功能緊密相關,因此常常被稱為 USB gadget 驅動程序。USB gadget 驅動程序的作用是定義如何通過 USB 協議棧與主機端進行通信,確保設備能夠正確響應主機的請求。
????????在 USB 系統中,主機側和設備側有各自不同的控制器。主機側使用的是主機控制器(Host Controller),它負責在主機和設備之間建立數據傳輸連接,管理設備的連接和斷開。設備側則使用 USB 設備控制器(UDC),它用于控制設備如何響應主機的請求和進行數據傳輸。主機控制器和設備控制器分別在各自的系統中充當著至關重要的角色,確保 USB 設備和主機之間的有效通信。
????????這兩種驅動程序通過操作系統中的 USB 子系統進行協同工作,提供了廣泛的設備支持,從鍵盤、鼠標等簡單外設到復雜的存儲設備、音頻設備等多種類型的 USB 設備。
二、USB傳輸介質-URB
????????USB通信和IIC類似,都要先構建數據包,然后使用對應的API函數進行傳輸。URB就是USB傳輸的介質。URB(USB請求塊)是Linux內核中用于管理USB數據傳輸的結構體。在Linux中,USB數據傳輸的核心就是通過URB來進行的,它相當于I2C中的數據包封裝,承擔著數據傳輸的“容器”角色。URB用于描述一次USB傳輸的請求,包括傳輸方向、數據長度、目標端點等信息。
1. 根據數據傳輸的方式和協議類型,URB有不同的類型。
(1)控制傳輸URB:用于管理設備控制請求,如設備的初始化、配置等。使用usb_fill_control_urb()
來填充控制傳輸的URB。
(2)批量傳輸URB:用于較大數據量的傳輸,通常用于數據的讀取和寫入。使用usb_fill_bulk_urb()
來填充批量傳輸URB。
(3)等時傳輸URB:用于對實時性要求較高的傳輸,如音頻、視頻流等。使用usb_fill_int_urb()
來填充等時傳輸URB。
(4)中斷傳輸URB:用于短數據的周期性傳輸,如鍵盤、鼠標等設備。使用usb_fill_int_urb()
來填充中斷傳輸URB。
2. 結構體如下所示。
struct urb {struct list_head urb_list; // 用于管理URB隊列的鏈表unsigned int pipe; // 傳輸通道,即端點void *context; // 用戶定義的上下文,用于回調函數中傳遞信息unsigned char *transfer_buffer; // 數據緩沖區指針,指向傳輸數據的內存dma_addr_t transfer_dma; // 用于DMA傳輸的物理地址unsigned int transfer_flags; // 標志,指示URB的某些屬性(例如同步、異步等)unsigned int status; // 傳輸狀態,成功或失敗unsigned int actual_length; // 實際傳輸的字節數unsigned int number_of_packets; // 分包傳輸時的數據包數unsigned int timeout; // 傳輸超時時間(毫秒)unsigned int start_frame; // 傳輸開始的幀號unsigned int interval; // 傳輸間隔時間(僅對中斷傳輸有效)unsigned char *setup_packet; // 指向控制傳輸的請求包(如果是控制傳輸時)struct urb *next; // 下一個URB,供鏈表使用unsigned int transfer_buffer_length; // 緩沖區的長度(即傳輸數據的最大字節數)struct usb_device *dev; // USB設備指針,指向傳輸目標設備void (*complete)(struct urb *urb); // 傳輸完成后的回調函數struct mutex *lock; // 用于同步URB訪問的互斥鎖unsigned int pipe_flags; // 端點標志,描述端點的類型和特性
};
3. 相關API。
(1)構建URB對象。
????????返回一個指向分配的URB對象的指針。如果分配失敗,返回 NULL
。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
/*
iso_packets:表示如果URB是用于等時傳輸,這個參數指定URB包含的數據包數量。如果不是等時傳輸,此參數應為0。
mem_flags:內存分配標志,通常使用 GFP_KERNEL 來分配內存。
*/
(2)釋放一個URB對象。?
void usb_free_urb(struct urb *urb);
(3)填充控制傳輸URB。
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,unsigned int pipe, unsigned char *setup_packet,void *transfer_buffer, int buffer_length,usb_complete_t complete_fn, void *context);
/*urb:要填充的URB對象。dev:目標USB設備。pipe:USB管道(端點),可以通過usb_sndctrlpipe()和usb_rcvctrlpipe()等函數獲取。setup_packet:指向控制請求的setup包指針,包含請求的控制信息(如請求碼、值、索引等)。transfer_buffer:指向傳輸數據緩沖區的指針。如果是控制傳輸的輸入數據,數據會存儲在這 里;輸出數據也通過此緩沖區發送。buffer_length:傳輸緩沖區的長度。complete_fn:傳輸完成后的回調函數。context:用于回調函數的用戶定義的上下文數據。可以在回調函數中使用。
*/
?(4)填充批量傳輸URB。
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,unsigned int pipe, void *transfer_buffer,int buffer_length, usb_complete_t complete_fn,void *context);
/*urb:要填充的URB對象。dev:目標USB設備。pipe:USB管道(端點),可以通過usb_sndbulkpipe()和usb_rcvbulkpipe()等函數獲取。transfer_buffer:指向傳輸數據緩沖區的指針。buffer_length:傳輸緩沖區的長度。complete_fn:傳輸完成后的回調函數。context:用于回調函數的用戶定義的上下文數據。
*/
?(5)填充中斷傳輸URB。
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, unsigned int interval);
/*urb:要填充的URB對象。dev:目標USB設備。pipe:USB管道(端點),可以通過usb_sndintpipe()和usb_rcvintpipe()等函數獲取。transfer_buffer:指向傳輸數據緩沖區的指針。buffer_length:傳輸緩沖區的長度。complete_fn:傳輸完成后的回調函數。context:用于回調函數的用戶定義的上下文數據。interval:傳輸間隔,單位為幀(適用于中斷傳輸)。
*/
?(6)提交URB進行傳輸。
????????如果提交成功,返回 0
;如果失敗,返回一個負數錯誤碼。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
/*urb:要提交的URB對象。mem_flags:內存分配標志,通常使用 GFP_KERNEL。
*/
(7)取消一個URB的傳輸。
void usb_kill_urb(struct urb *urb);
三、主機側驅動開發框架
1. 操作流程。
(1)編寫USB驅動框架。
(2)完善probe函數。
(3)在退出函數中釋放掉內存以及相關注銷。
2. 編寫USB驅動框架。
①在函數入口函數中注冊usb驅動,在出口函數中注銷usb驅動。
②填充usb驅動信息,例如名稱、probe函數等。
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/init.h>
//設備連接時執行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {return 0;
}//設備斷開時執行
static void my_usb_disconnect(struct usb_interface *intf) {}static const struct usb_device_id my_usb_id_table[] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) },{ }, // 空結構體,表示數組結束
};static struct usb_driver my_usb_driver = {.name = "my_usb",.probe = my_usb_probe,.disconnect = my_usb_disconnect,.id_table = my_usb_id_table,
};static int my_usb_init(void) {int ret = usb_register(&my_usb_driver);return 0;
}static void my_usb_exit(void) {usb_deregister(&my_usb_driver);
}module_init(my_usb_init);
module_exit(my_usb_exit);
MODULE_LICENSE("GPL");
3. 完善probe函數。
①由于鍵盤屬于輸入設備,則在probe函數中先為輸入子系統開辟空間,并填充信息。
②設置鍵盤的按鍵事件以及鍵值。
③注冊輸入子系統到驅動。
④構建URB對象,開辟空間并填充中斷傳輸URB函數中的參數。
⑤提交URB對象,用于數據傳輸。
⑥在填充URB的回調函數中,上報事件。
⑦在退出函數中釋放內存。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/slab.h>struct input_dev *myusb_inputdev = NULL; // 輸入設備
struct urb *myusb_urb = NULL; // USB請求塊(URB)
unsigned char *myusb_buf = NULL; // 數據緩沖區
int myusb_size = 0; // 緩沖區大小
dma_addr_t myusb_dma; // DMA地址// 鍵盤的鍵碼表,包含標準鍵盤的按鍵映射
static const unsigned char usb_keyboardcode[256] = {0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 0, 50,49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 5,6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 4, 27,43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65,66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106, 105,108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72,73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, 191,192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113, 115,114, 0, 0, 0, 121, 0, 89, 93, 124, 92, 94, 95, 0, 0, 6, 0,122, 123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};//回調函數執行的條件是 URB傳輸完成,無論是成功還是失敗。
static void myusb_func(struct urb *urb) //填充中斷URB的回調函數
{if (urb->status) {pr_err("URB transfer failed with status %d\n", urb->status);} else {pr_info("URB transfer completed successfully\n");}
}// 設備連接時執行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {int i;int ret;struct usb_device *myusb_dev = interface_to_usbdev(intf); // 獲取USB設備指針struct usb_endpoint_descriptor *endpoint;// 1. 為輸入設備分配內存myusb_inputdev = input_allocate_device();if (!myusb_inputdev) {pr_err("Failed to allocate input device\n");return -ENOMEM;}myusb_inputdev->name = "myusb_input"; // 設置設備名稱// 2. 設置事件類型:按鍵事件和重復事件set_bit(EV_KEY, myusb_inputdev->evbit);set_bit(EV_REP, myusb_inputdev->evbit);for (i = 0; i < 255; i++) { // 設置按鍵位圖set_bit(usb_keyboardcode[i], myusb_inputdev->keybit);}clear_bit(0, myusb_inputdev->keybit); // 清除無效的按鍵位// 3. 注冊輸入設備ret = input_register_device(myusb_inputdev);if (ret) {input_free_device(myusb_inputdev);return ret;}
// 4. URB分配內存myusb_urb = usb_alloc_urb(0, GFP_KERNEL); // 分配一個URB,ISO數據包數為0,內存分配標志為GFP_KERNEL// 獲取端點描述符并獲取端點最大數據包大小endpoint = &intf->cur_altsetting->endpoint[0].desc;myusb_size = endpoint->wMaxPacketSize;// 分配一致性內存緩沖區用于數據傳輸myusb_buf = usb_alloc_coherent(myusb_dev, myusb_size, GFP_ATOMIC, &myusb_dma);// 獲取接收中斷管道unsigned int pipe = usb_rcvintpipe(myusb_dev, endpoint->bEndpointAddress);// 填充URBusb_fill_int_urb(myusb_urb, myusb_dev, pipe, myusb_buf, myusb_size, myusb_func, 0, endpoint->bInterval);// 5. 提交URB進行數據傳輸ret = usb_submit_urb(myusb_urb, GFP_KERNEL);mykbd_urb->transfer_dma = mykbd_dma; // 設置URB的DMA地址mykbd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; // 設置URB的標志:不使用DMA映射return 0;
}// 設備斷開時執行
void myusb_disconnect(struct usb_interface *intf) {usb_kill_urb(myusb_urb); // 取消URB傳輸usb_free_urb(myusb_urb); // 釋放URBusb_free_coherent(myusb_dev, myusb_size, myusb_buf, myusb_dma); // 釋放一致性內存input_unregister_device(myusb_inputdev); // 注銷輸入設備input_free_device(myusb_inputdev); // 釋放輸入設備
}