本文从新手出发,一步步阐述如何编写一个初步的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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>

#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.o

 MODPOST /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设备。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐