初涉 linux 驅動開發,路漫漫其修遠兮
1. 執行個體:usbmouse.c
1.1 下載下傳 linux 源碼包
從 linux 核心源碼中學習個人認為會是比較快入門的一種方法。
第一步就是擷取源碼包,有兩個途徑:
- 官網下載下傳:https://www.kernel.org/
- 阿裡雲鏡像:https://mirrors.aliyun.com/linux-kernel/
官網上面提供了最新的
linux
源碼,可以網頁下載下傳,或使用
git
工具擷取。鑒于國内網絡的特殊性,連接配接外網速度比較慢的,是以我推薦使用阿裡雲鏡像的方法來擷取
linux
源碼。Linux 核心版本命名規則偶數表示穩定版,為了研究友善我下載下傳的是 2.6.14 版本。下載下傳回來之後進行解壓:
$ tar -xvf linux-2.6.14.tar.xz
如果出現
bash: xz: command not found
提示,安裝
xz
工具即可解決:
$ sudo apt-get install -y xz-utils
解壓之後打開
linux-2.6.14/drivers/usb/input/usbmouse.c
檔案,接下來我們就一起來 hack 一下 linux 驅動開發。
延伸閱讀:xz 是一種壓縮檔案格式,采用 LZMA SDK 壓縮,目标檔案較 gzip 壓縮檔案 .gz 或 ·tgz 小 30%,較 ·bz2 小 15%。xz 官網
1.2 基本架構
一個 Linux USB 裝置驅動的基本架構包括了:初始化子產品、解除安裝子產品、probe 函數、disconnect 函數、裝置 ID 表。
初始化/解除安裝子產品
linux 驅動使用
module_init
函數來初始化一個裝置子產品,使用
module_exit
來解除安裝子產品,這兩個函數分别給
insmod
和
rmmod
指令所使用。
static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver);
if (retval == )
info(DRIVER_VERSION ":" DRIVER_DESC)
return retval;
}
static void __exit usb_mouse_exit(void)
{
usb_deregister(&usb_mouse_driver);
}
module_init(usb_mouse_init);
module_exit(usb_mouse_exit);
源碼中的 DRIVER_VERSION 與 DRIVER_DESC 為局部宏定義:
#define DRIVER_VERSION "v1.6"
#define DRIVER_DESC "USB HID Boot Protocol mouse driver"
usb_mouse_init
函數負責驅動裝載,
usb_mouse_exit
函數負責驅動解除安裝時的指令動作,核心函數:
-
usb_register
-
usb_deregister
以上兩個函數定義在
drivers/usb/core/usb.c
檔案中
/* drivers/usb/core/usb.c */
int usb_register(struct usb_driver *new_driver);
void usb_deregister(struct usb_driver *driver);
源碼中的
usb_mouse_driver
定義為:
static struct usb_driver usb_mouse_driver = {
.owner = THIS_MODULE,
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
從代碼看來,
usb_mouse_driver
需要初始化五個字段:
name | member | value |
---|---|---|
子產品的所有者 | .owner | THIS_MODULE |
子產品名稱 | .name | “usbmouse” |
probe 函數 | .probe | usb_mouse_probe |
disconnect 函數 | .disconnect | usb_mouse_disconnect |
id 表 | .id_table | usb_mouse_id_table |
結構體
struct usb_driver
定義參見
include/linux/usb.h
檔案:
/* include/linux/usb.h */
struct usb_driver {
struct module *owner;
const char *name;
int (*probe) (struct usb_interface *intf, const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
const struct usb_device_id *id_table;
struct device_driver driver;
};
此處可見
struct usb_driver
結構體中還包括了
.ioctl
,
.suspend
,
.resume
與
.driver
的成員。現在暫時放下
struct usb_driver
結構體不說,我們來看看
usb_mouse_driver
中的
.id_table
值。
id_table
在這裡面,代碼定義了
usb_mouse_id_table
ID 表,首先看下其定義:
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(, , ) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
struct usb_device_id
結構體提供了一列不同類型的該驅動程式能夠支援的 USB 裝置。USB 核心使用該清單來判斷對于一個裝置該使用哪一個驅動程式,熱插拔腳本使用它來确定當一個特定的裝置插入到系統時該自動裝載哪一個驅動程式。——《Linux 裝置驅動程式》
struct usb_device_id
結構體定義于
include/linux/mod_devicetable.h
檔案中,包括下列字段:
-
确定裝置和結體體中下列字段中的哪一個相比對。__u16 match_flags
-
裝置的 USB 制造商 ID,該編号是由 USB 論壇指派給其成員的,不會由其他人指定。__u16 idVendor
-
裝置的 USB 産品 ID,所有指派了制造商 ID 的制造商都可以随意地賦予其産品 ID__u16 idProduct
-
__u16 bcdDevice_lo
-
定義了制造商指派的産品的版本号範圍的最低和最高值。__u16 bcdDevice_hi
-
__u8 bDeviceClass
-
__u8 bDeviceSubClass
-
分别定義裝置的類型、子類型和協定。這些編号由 USB 論壇指派,定義在 USB 規範中。__u8 bDeviceProtocol
-
__u8 bInterfaceClass
-
__u8 bInterfaceSubClass
-
和上述裝置特定的值很類似,這些值分别定義類型、子類型和單個接口的協定。這些編号由 USB 論壇指派,定義在 USB 規範中。__u8 bInterfaceProtocol
-
該值不是用來比較是否比對的,不過它包含了驅動程式在 USB 驅動程式的探測回調函數中可以用來區分不同裝置的資訊。kernel_ulong_t driver_info
USB_INTERFACE_INFO 宏定義用于建立一個比對特定接口類的
struct usb_device_id
, 滑鼠裝置遵循 USB 人機接口裝置 (HID),在 HID 規範中規定滑鼠接口類碼為:
- 接口類 (InterfaceClass): 0x03
- 接口子類 (InterfaceSubClass): 0x01
- 接口協定 (InterfaceProtocol): 0x02
這樣分類的好處是裝置廠商可以直接利用規範中所定義的标準的驅動程式,而無需為每個裝置編寫特定的驅動程式。
對于 PC 驅動程式,
MODULE_DEVICE_TABLE
宏是必需的,以允許使用者空間的工具判斷出該驅動程式可以控制什麼裝置。但是對于 USB 驅動程式來說,字元串
usb
必須是該宏中的第一個值。
對于 PCI 裝置來說,
include/linux/usb.h
頭檔案中定義了四個用來初始化
struct usb_device_id
結構體的宏:
- USB_DEVICE (vid, pid)
- USB_DEVICE_VER (vid, pid, lo, hi)
- USB_DEVICE_INFO (class, subclass, protocol)
- USB_INTERFACE_INFO (class, subclass, protocol)
如果針對特定晶片的驅動程式程式設計,使用 USB_DEVICE 應該更頻繁一些,比如 ThinkPad E430 藍牙驅動 BCM43142A0 一文便使用了 USB_DEVICE 宏與
.driver_info
來區分不同的裝置資訊。源代碼可參考 linux 3.13 核心源碼
linux-3.13.0/drivers/bluetooth/btusb.c
檔案,也可直接從上面的連結處擷取具體細節資訊。
/* Broadcom BCM43142A0 */
{ USB_DEVICE(, ), .driver_info = BTUSB_BCM_PATCHRAM },
{ USB_DEVICE(, ), .driver_info = BTUSB_BCM_PATCHRAM },
探測函數 _probe
參考資料:
[1] ThinkPad E430 藍牙驅動 BCM43142A0
[2] Linux USB 驅動開發執行個體(二)—— USB 滑鼠驅動注解及測試