初涉 linux 驱动开发,路漫漫其修远兮
1. 实例:usbmouse.c
1.1 下载 linux 源码包
从 linux 内核源码中学习个人认为会是比较快入门的一种方法。
第一步就是获取源码包,有两个途径:
官网上面提供了最新的 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 == 0)
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(3, 1, 2) },
{ } /* 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
确定设备和结体体中下列字段中的哪一个相匹配。 -
__u16 idVendor
设备的 USB 制造商 ID,该编号是由 USB 论坛指派给其成员的,不会由其他人指定。 -
__u16 idProduct
设备的 USB 产品 ID,所有指派了制造商 ID 的制造商都可以随意地赋予其产品 ID __u16 bcdDevice_lo
-
__u16 bcdDevice_hi
定义了制造商指派的产品的版本号范围的最低和最高值。 __u8 bDeviceClass
__u8 bDeviceSubClass
-
__u8 bDeviceProtocol
分别定义设备的类型、子类型和协议。这些编号由 USB 论坛指派,定义在 USB 规范中。 __u8 bInterfaceClass
__u8 bInterfaceSubClass
-
__u8 bInterfaceProtocol
和上述设备特定的值很类似,这些值分别定义类型、子类型和单个接口的协议。这些编号由 USB 论坛指派,定义在 USB 规范中。 -
kernel_ulong_t driver_info
该值不是用来比较是否匹配的,不过它包含了驱动程序在 USB 驱动程序的探测回调函数中可以用来区分不同设备的信息。
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(0x04ca, 0x2007), .driver_info = BTUSB_BCM_PATCHRAM },
{ USB_DEVICE(0x105b, 0xe065), .driver_info = BTUSB_BCM_PATCHRAM },
探测函数 _probe
参考资料:
[1] ThinkPad E430 蓝牙驱动 BCM43142A0
[2] Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试