Linux kernel: USB driver编写入门(二)

前面一篇文章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变量进行赋值。 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值