前面一篇文章Linux kernel: USB driver编写入门(一)介绍了一个最简单的USB驱动的最基本框架,本文将加入probe和disconnect函数,用于响应该设备插入和拔出。
继续在那个目录下$vim usb_test_drv.c,加入如下代码:
static int usb_drv_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_host_interface *interface_desc;
int ret;
interface_desc = interface->cur_altsetting;
printk(KERN_INFO "USB info %d now probed: (%04x:%04x)\n", interface_desc->desc.bInterfaceNumber, id->idVendor, id->idProduct);
printk(KERN_INFO "ID->bNumEndpoints:%02x\n", interface_desc->desc.bNumEndpoints);
printk(KERN_INFO "ID->bInterfaceClass:%02x\n", interface_desc->desc.bInterfaceClass);
ret = usb_register_dev(interface,&usb_cd);
if(ret)
{
printk(KERN_INFO "usb_register_dev erro: %d\n", ret);
}
else
{
printk(KERN_INFO "Minor number = %d\n", interface->minor);
}
return ret;
}
static void usb_drv_disconnect(struct usb_interface *interface)
{
printk(KERN_INFO "Disconneced and Release the MINOR number %d\n", interface->minor);
usb_deregister_dev(interface, &usb_cd);
}
static struct usb_driver usb_drv_struct={
.name = "Actions USB Driver",
.probe = usb_drv_probe,
.disconnect = usb_drv_disconnect,
.id_table = usb_drv_table
};
注意,这里引用了usb_cd,需要在文件前面加上该变量的声明。
static struct usb_class_driver usb_cd;
保存后,在当前目录下make. 如果没有出错,则加载该模块(命令同(一)),加载成功后,运行$lsmod | grep usb 和 $dmesg | tail来验证已经加载。
这时候,插入文(一)中的USB设备。注意,如果在Linux主机运行期间,已经插入过该设备。则需要重新启动Linux主机,因为Linux已有Kernel的驱动已经对该设备进行了一系列处理,会和我们新写的driver相冲突。
dmesg | tail -n 20
[ 241.357953] Register the usb driver with the usb subsystem
[ 241.358000] usbcore: registered new interface driver Actions USB Driver
[ 282.289547] usb 2-4: new high-speed USB device number 4 using xhci_hcd
[ 282.438387] usb 2-4: New USB device found, idVendor=10d6, idProduct=1101, bcdDevice= 1.00
[ 282.438400] usb 2-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 282.438406] usb 2-4: Product: USB CARDREADER
[ 282.438411] usb 2-4: Manufacturer: ACTIONS
[ 282.438415] usb 2-4: SerialNumber: ㉕捤稰眷㕳愳㤷湲
[ 282.441394] USB info 0 now probed: (10d6:1101)
[ 282.441406] ID->bNumEndpoints:02
[ 282.441411] ID->bInterfaceClass:08
[ 282.441416] usb_register_dev erro: -22
[ 282.441423] Actions USB Driver: probe of 2-4:1.0 failed with error -22
[ 282.519352] usb-storage 2-4:1.0: USB Mass Storage device detected
[ 282.519600] scsi host4: usb-storage 2-4:1.0
[ 282.519755] usbcore: registered new interface driver usb-storage
[ 283.533961] scsi 4:0:0:0: Direct-Access ACTIONS USB DISK FOB 2.0 0 PQ: 0 ANSI: 0 CCS
[ 283.534475] sd 4:0:0:0: Attached scsi generic sg1 type 0
[ 283.534848] sd 4:0:0:0: [sdb] Media removed, stopped polling
[ 283.535920] sd 4:0:0:0: [sdb] Attached SCSI removable disk
之所以这次用了tail -n 20是因为消息比较多,很多是Linux kernel自带驱动识别该设备后触发的消息。橙色背景的是probe函数被调用打印的消息。大家自己结合程序去查看。
usb_register_dev erro: -22返回错误信息,usb_register_dev这个函数没有在我们自己写的程序中调用,但是make通过没有报错,说明这个函数必然是在Linux kernel中定义了。从#include的头文件里,我们看到#include <linux/usb.h>。那么几乎可以肯定,usb_register_dev这个函数在这个头文件里声明了。
于是找到这个文件,
$ find ~/stable_rc/linux-5.19.0/ -wholename */linux/usb.h
/home/minipc/stable_rc/linux-5.19.0/include/linux/usb.h
这里选项用-wholename是因为有路径名称,因为linux目录不是直接在~/stable_rc/linux-5.19.0/(kernel源文件根目录)下,所以前面有*。在该文件中查找该函数声明:
$ grep usb_register_dev /home/minipc/stable_rc/linux-5.19.0/include/linux/usb.h
* number from the USB core by calling usb_register_dev().
* This structure is used for the usb_register_dev() and
extern int usb_register_device_driver(struct usb_device_driver *,
extern int usb_register_dev(struct usb_interface *intf,
最后一行即是:int usb_register_dev(struct usb_interface *intf,
之所以要找到函数原型,是为了后面查找定义更加方便,因为usb_register_dev的函数调用有很多。光有函数名称不够,还要有后面的参数定义就会更精准。
查找定义这个函数的文件。为减少搜索量,我们先在Linux kernel 源文件的根目录查找。
$find ~/stable_rc/linux-5.19.0/ -name usb
/home/minipc/stable_rc/linux-5.19.0/usr/include/linux/usb
/home/minipc/stable_rc/linux-5.19.0/tools/usb
/home/minipc/stable_rc/linux-5.19.0/tools/testing/selftests/drivers/usb
/home/minipc/stable_rc/linux-5.19.0/sound/usb
/home/minipc/stable_rc/linux-5.19.0/drivers/usb
/home/minipc/stable_rc/linux-5.19.0/drivers/media/usb
/home/minipc/stable_rc/linux-5.19.0/drivers/media/cec/usb
/home/minipc/stable_rc/linux-5.19.0/drivers/net/usb
/home/minipc/stable_rc/linux-5.19.0/drivers/net/can/usb
/home/minipc/stable_rc/linux-5.19.0/include/dt-bindings/usb
/home/minipc/stable_rc/linux-5.19.0/include/uapi/linux/usb
/home/minipc/stable_rc/linux-5.19.0/include/linux/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/devicetree/bindings/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/_sources/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/_sources/driver-api/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/.doctrees/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/.doctrees/driver-api/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/output/driver-api/usb
/home/minipc/stable_rc/linux-5.19.0/Documentation/driver-api/usb
从名称来看,我们先从 /home/minipc/stable_rc/linux-5.19.0/drivers/usb 文件夹里查找。
$ grep "usb_register_dev(struct" /home/minipc/stable_rc/linux-5.19.0/drivers/usb -r --include=*.c
/home/minipc/stable_rc/linux-5.19.0/drivers/usb/core/file.c:int usb_register_dev(struct usb_interface *intf,
这里为了更精确查找,字段选择"usb_register_dev(struct",其中struct是关键字,那么就排除了函数调用。其中--include=*.c,表示只从.c文件中选取。我们找到了函数定义文件usb/core/file.c.
打开该文件,可以找到该函数的定义:
int usb_register_dev(struct usb_interface *intf,
struct usb_class_driver *class_driver)
{
int retval;
int minor_base = class_driver->minor_base;
int minor;
char name[20];
#ifdef CONFIG_USB_DYNAMIC_MINORS
/*
* We don't care what the device tries to start at, we want to start
* at zero to pack the devices into the smallest available space with
* no holes in the minor range.
*/
minor_base = 0;
#endif
if (class_driver->fops == NULL)
return -EINVAL;
if (intf->minor >= 0)
return -EADDRINUSE;
mutex_lock(&init_usb_class_mutex);
retval = init_usb_class();
mutex_unlock(&init_usb_class_mutex);
if (retval)
return retval;
dev_dbg(&intf->dev, "looking for a minor, starting at %d\n", minor_base);
down_write(&minor_rwsem);
for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) {
if (usb_minors[minor])
continue;
usb_minors[minor] = class_driver->fops;
intf->minor = minor;
break;
}
if (intf->minor < 0) {
up_write(&minor_rwsem);
return -EXFULL;
}
/* create a usb class device for this usb interface */
snprintf(name, sizeof(name), class_driver->name, minor - minor_base);
intf->usb_dev = device_create(usb_class->class, &intf->dev,
MKDEV(USB_MAJOR, minor), class_driver,
"%s", kbasename(name));
if (IS_ERR(intf->usb_dev)) {
usb_minors[minor] = NULL;
intf->minor = -1;
retval = PTR_ERR(intf->usb_dev);
}
up_write(&minor_rwsem);
return retval;
}
EXPORT_SYMBOL_GPL(usb_register_dev);
从这个函数的定义里可以看到,
if (class_driver->fops == NULL)
return -EINVAL;
if (intf->minor >= 0)
return -EADDRINUSE;
这个EINVAL在include的头文件里<linux/errno.h>和<linux/usb.h>里都没有,所以要在原文件里查找它的定义
$ grep "^#define\s\+EINVAL" /home/minipc/stable_rc/linux-5.19.0/drivers/usb -r --include=*.h
这里使用了正则表达式,简单解释一下,^表示以这个开头,\s表示你空格,\+表示前面的字符有1个或多个。
结果输出为空,表明不在这个目录下,更换搜索目录为/home/minipc/stable_rc/linux-5.19.0/include/, 因为.h文件大部分都在这个目录下。
$ grep "^#define\s\+EINVAL" /home/minipc/stable_rc/linux-5.19.0/include/ -r --include=*.h
/home/minipc/stable_rc/linux-5.19.0/include/uapi/asm-generic/errno-base.h:#define EINVAL 22 /* Invalid argument */
发现-EINVAL正好是erro: -22。说明程序在下面一步就出错退出了:
if (class_driver->fops == NULL)
return -EINVAL;
这里的class_driver->fops就是在usb_drv_probe函数定义中的调用usb_register_dev
ret = usb_register_dev(interface,&usb_cd);
时的输入参数&usb_cd->fops,而在usb_test_drv.c文件中,usb_cd仅仅声明了
static struct usb_class_driver usb_cd;
没有定义。那么它内部的指针fops就缺省为空了。struct usb_class_driver的定义可以在<linux/usb.h>文件中找到。
struct usb_class_driver {
char *name;
char *(*devnode)(struct device *dev, umode_t *mode);
const struct file_operations *fops;
int minor_base;
};
下面一篇文章Linux kernel: USB driver编写入门(三),我们将讲到如何对usb_cd变量进行赋值。