本文从新手出发,一步步阐述如何编写一个初步的USB driver。该过程同样适用于其他设备驱动的开发。
我们初一看Linux的设备驱动,内容很多,好像很复杂。确实如此,但是Linux kernel里面已经做了很多工作,我们编写驱动只需要调用它们的函数与数据的接口。对于一个初学者来说,我们可以化繁为简,先从一个最精简的框架搭起,打造一个初步可演示的USB driver。
本文代码与实操全部基于Ubutu 20.04,kernel - 5.19.0-rc3+。
第一步,把一个USB设备连到Linux主机。任意有USB接口的产品都可以,我用的是一个蓝牙音箱。Linux内核已经含有市面上99.9%的USB设备驱动,所以连上后会自动识别。然后运行如下命令,查看USB设备信息。
$ lsusb
Bus 001 Device 002: ID 8087:8000 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 004: ID 10d6:1101 Actions Semiconductor Co., Ltd D-Wave 2GB MP4 Player / AK1025 MP3/MP4 Player
Bus 002 Device 003: ID 25a7:0701 Smart Smart Wireless Device
Bus 002 Device 002: ID 1a40:0101 Terminus Technology Inc. Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
其中第4条是该USB设备,这里要记住ID 10d6:1101。其中10d6是vendor id, 1101是product id。这是Linux内核用于识别该设备的唯一标识符,要记下来。
第二步,新建一个目录,创建一个驱动文件,我是usb_test_drv.c。文件内代码如下:
/*
* Linux Usb Device Driver
*/
#include
#include
#include
#define USB_VENDOR_ID 0x10d6
#define USB_PRODUCT_ID 0x1101
static int __init usb_test_init(void)
{
printk(KERN_INFO "Register the usb driver with the usb subsystem \n");
return usb_register(&usb_drv_struct);
}
static void __exit usb_test_exit(void)
{
printk(KERN_INFO "Deregister the usb driver with usb subsystem\n");
usb_deregister(&usb_drv_struct);
}
module_init(usb_test_init);
module_exit(usb_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jason Lee");
MODULE_DESCRIPTION("USB test Driver");
先定义了两个函数,init和exit,它们在load和unload这个USB模块的时候会被分别调用。在init函数里调用usb_register向Linux usb子系统进行注册,在exit函数里调用usb_deregister向Linux usb子系统注销。
这里面我们都用到了usb_drv_struct, 下面我们把它加进来
第三步,增加struct usb_driver变量初始化,代码如下:
static struct usb_driver usb_drv_struct={
.name = "Actions USB Driver",
.probe = NULL,
.disconnect = NULL,
.id_table = usb_drv_table
};
static struct usb_device_id usb_drv_table[]={
{
USB_DEVICE(USB_VENDOR_ID, USB_PRODUCT_ID)
},
{}
};
MODULE_DEVICE_TABLE(usb, usb_drv_table);
注意,这里因为先调用usb_drv_table, 再去定义它。由于C语言编译器只能按代码编写顺序编译,它会报错不认识这个变量。所以需要在文件前面加上这个变量的声明。
static struct usb_device_id usb_drv_table[];
上面一步用到的usb_drv_struct是struct usb_driver的变量。其中的id_table就是用第一步获取的vendor id和product id而生成的usb设备列表,只有一个设备。probe是函数指针,当该模块Load进内核后,当系统检测到该USB设备插入(通过查vendor id 和product id),probe对应函数被调用。disconnect也是函数指针,当系统检测到该设备拔出时被调用。先把这两个指针设为空。
第四步:在同一目录下编写Makefile
obj-m := usb_test_drv.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
install:
$(MAKE) -C $(KDIR) M=$(PWD) module_install
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
(1)obj-m是目标文件,注意前面名称必须和你前面几步创建的.c文件一样。
(2)KDIR这个目录是为了使用当前操作系统内核的makefile配置,必须保持一致,否则编译出来的模块会和当前kernel不匹配,无法load。这里注意,先运行如下命令查看所指路径是否正确。
$ ls -l /lib/modules/$(uname -r)/build
lrwxrwxrwx 1 root root 35 Jul 15 21:34 /lib/modules/5.19.0-rc3+/build -> /home/minipc/stable_rc/linux-5.19.0
命令返回显示,这个../build是一个symbolic link,指向该Linux机器当前kernel的源代码根目录。如果不是,要用$ln -vfns这个命令进行更新。
(3)$(MAKE) -C $(KDIR) M=$(PWD) modules, 这里-C的C是大写,表示转到后面那个目录(KDIR)的Makefile,其中的M参数为当前路径,即前面新建的,usb_test_drv.c所在路径。
第五步,在当前目录下运行make
$ make
make -C /lib/modules/5.19.0-rc3+/build M=/home/minipc/linux_usb_driver modules
make[1]: Entering directory '/home/minipc/stable_rc/linux-5.19.0'
CC [M] /home/minipc/linux_usb_driver/usb_test_drv.oMODPOST /home/minipc/linux_usb_driver/Module.symvers
CC [M] /home/minipc/linux_usb_driver/usb_test_drv.mod.o
LD [M] /home/minipc/linux_usb_driver/usb_test_drv.ko
BTF [M] /home/minipc/linux_usb_driver/usb_test_drv.ko
make[1]: Leaving directory '/home/minipc/stable_rc/linux-5.19.0'
如果你的运行出错,先检查(1)路径是否正确,make[1]是否enter了你当前运行内核的源代码根目录;(2)usb_test_drv.c 和 Makefile是否有拼写错误,包括大小写,标点符号等。
运行成功,查看当前目录下文件(按时间顺序排列)
$ls -lt
total 700
-rw-rw-r-- 1 minipc minipc 342304 Jul 18 19:54 usb_test_drv.ko
-rw-rw-r-- 1 minipc minipc 109856 Jul 18 19:54 usb_test_drv.mod.o
-rw-rw-r-- 1 minipc minipc 0 Jul 18 19:54 Module.symvers
-rw-rw-r-- 1 minipc minipc 984 Jul 18 19:54 usb_test_drv.mod.c
-rw-rw-r-- 1 minipc minipc 46 Jul 18 19:54 modules.order
-rw-rw-r-- 1 minipc minipc 45 Jul 18 19:54 usb_test_drv.mod
-rw-rw-r-- 1 minipc minipc 226128 Jul 18 19:54 usb_test_drv.o
-rw-rw-r-- 1 minipc minipc 2847 Jul 18 17:57 Makefile-rw-rw-r-- 1 minipc minipc 233 Jul 15 17:47 usb_test_drv.c
多出了usb_test_drv.ko,usb_test_drv.mod.o,Module.symvers,usb_test_drv.mod.c,modules.order,usb_test_drv.mod,usb_test_drv.o文件。
第六步,Load该模块。在Load之前,请把该USB设备从Linux主机上拔掉。
$ sudo insmod usb_test_drv.ko
如果成功,则立即返回,不报错。如果有如下报错
insmod: ERROR: could not insert module usb_test_drv.ko: Invalid module format
请参考我另一篇文章 insmod error could not insert module ... invalid module format [已解决] 。
查看dmesg信息:
$ dmesg | tail
[ 16.187459] wlp2s0: authenticated
[ 16.189235] wlp2s0: associate with b8:3a:08:95:e6:11 (try 1/3)
[ 16.213531] wlp2s0: RX AssocResp from b8:3a:08:95:e6:11 (capab=0x411 status=0 aid=6)
[ 16.233172] wlp2s0: associated
[ 17.313463] IPv6: ADDRCONF(NETDEV_CHANGE): wlp2s0: link becomes ready
[ 17.431564] rfkill: input handler disabled
[ 2680.857123] usb_test_drv: loading out-of-tree module taints kernel.
[ 2680.857170] usb_test_drv: module verification failed: signature and/or required key missing - tainting kernel
[ 2680.857569] Register the usb driver with the usb subsystem
[ 2680.857612] usbcore: registered new interface driver Actions USB Driver
最后面4行既是该模块加载成功的内核log消息,"Action USB Driver"即前面编写的usb_test_drv.c中usb_drv_struct.name。
再用lsmod查看
$ lsmod | grep usb
usb_test_drv 16384 0
usbhid 57344 0
hid 143360 2 usbhid,hid_generic
第一条就是该USB模块
第七步:Unload该模块
$sudo rmmod usb_test_drv.ko
成功的话会立即返回,再查看当前加载模块:
$ lsmod | grep usb
usbhid 57344 0
hid 143360 2 usbhid,hid_generic
该模块已移除
再查看dmesg里的消息,
$ dmesg | tail
[ 16.213531] wlp2s0: RX AssocResp from b8:3a:08:95:e6:11 (capab=0x411 status=0 aid=6)
[ 16.233172] wlp2s0: associated
[ 17.313463] IPv6: ADDRCONF(NETDEV_CHANGE): wlp2s0: link becomes ready
[ 17.431564] rfkill: input handler disabled
[ 2680.857123] usb_test_drv: loading out-of-tree module taints kernel.
[ 2680.857170] usb_test_drv: module verification failed: signature and/or required key missing - tainting kernel
[ 2680.857569] Register the usb driver with the usb subsystem
[ 2680.857612] usbcore: registered new interface driver Actions USB Driver
[ 3799.381146] Deregister the usb driver with usb subsystem
[ 3799.381151] usbcore: deregistering interface driver Actions USB Driver
后面两条log消息就是注销。
下面一篇文章Linux kernel: USB driver编写入门(二)将加入probe和disconnect函数,用于响应插入和拔出该USB设备。