Linux之USB设备驱动分析与编程

目录

一、概念介绍

二、分析USB总线驱动程序

三、编写USB设备驱动程序

3.1 怎么写USB设备驱动程序

3.2 分配usb_driver结构体并设置注册

3.3 小测试

3.4 完善probe函数

3.5 通过鼠标了解数据含义

3.6 根据含义完善驱动程序


一、概念介绍

例当USB设备接到PC时,右下角弹出"发现android phone",跳出一个对话框,提示安装驱动程序

提出问题:

  1. 既然还没有"驱动程序",为何能知道是"android phone"?答:windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone",提示安装的是"设备驱动程序"
  2. USB设备种类非常多,为什么一接入电脑,就能识别出来?答:PC和USB设备都得遵守一些规范,比如USB设备接入电脑后,PC机会发出"你是什么"?USB设备就必须回答"我是XXX",并且回答的语言必须是中文,USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回"描述符"给PC
  3. USB接口只有4条线:5V,GND,D-,D+,PC机上接有非常多的USB设备,怎么分辨它们?答:每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)
  4. USB设备刚接入PC时,还没有编号,那么PC怎么把"分配的编号"告诉它?答:新接入的USB设备的默认编号是0,在末分配新编号前,PC使用0编号和它通信
  5. 为什么一接入USB设备,PC机就能发现它?答:PC的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时平时为低电平,USB设备的USB口内部,D-D+接有1.5K的上拉电阻;它一接入PC,就会把PC USB口的D-D+拉高,从硬件的角度通知PC有新设备接入,如果USB设备上是在D-接上1.5K电阻则是全速设备(12Mbps),如果是在D+接上1.5K电阻则是高速设备(480Mbps)

其他概念:

  • USB是主从结构的,所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力,例:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等待PC机来读
  • USB的传输类型:

        a.控制传输:可靠,时间有保证,比如:USB设备的识别过程
        b.批量传输:可靠,时间没有保证,比如:U盘(把数据拷到U盘后,弹出U盘时快时慢,文件可靠)
        c.中断传输:可靠,实时,比如:USB鼠标(不希望几秒后鼠标才能反映,所以需要实时查询,借助中断概念,并没有中断能力,根据查询方式来实时查询)
        d.实时传输:不可靠,实时,比如:USB摄像头(数据不可靠,有时候出现花屏,但实时传输)

  • USB传输的对象:端点(endpoint)

我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据,除了端点0外,每个端点只支持一个方向的数据传输,端口0用于控制传输,既能输出也能输入

  • 每一个端点都有传输类型,传输方向
  • 术语里、程序里说的输入(IN)、输出(OUT)"都是"基于USB主机的立场说的,比如鼠标的数据是从鼠标传到PC机,对应的端点成为"输入端点"
  • USB总线驱动程序的作用

a.识别USB设备 b.查到并安装对应的设备驱动程序 c.提供USB读写函数(不了解数据含义)

  • libusb封装了接口函数,可跳过设备驱动程序来调用USB读写函数
  • USB驱动程序框架:
app
内核USB设备驱动程序(知道数据含义)
USB总线驱动程序(不知道数据含义)
硬件USB主机控制器
USB设备

 

  • 对于PC的USB设备是主机控制器,USB总线驱动程序就是来支持USB主机控制器,可认为USB总线驱动程序就是USB主机控制器的驱动程序,主机控制器上面接有USB设备,设备驱动程序来支持USB设备,设备驱动程序访问USB设备,需要USB总线驱动程序提供的函数发给USB主机控制器,由USB主机控制器产生信号发给USB设备

二、分析USB总线驱动程序

  • 对于USB主机控制器来说有三种规范

    1.UHCI:intel规范,适用于低速(USB1.1、1.5Mbps)/全速(USB2.0、12Mbps)对于USB2.0还要分为全速还是高速
    2.OHCI:microsoft规范,低速(USB1.1)/全速(USB2.0)  
    3.EHCI:高速(480Mbps)

  • 对于个人单板S3C2440用的规范为OHCI,在内核中有ohci-2410.cUSB总线驱动程序
  • USB总线驱动程序的作用中识别USB设备有三点:1.给USB设备分配地址 2.并告诉USB设备(set address) 3.发出命令获取描述符
  • 在开发板上接入一个USB设备,看输出信息:

usb 1-1: new full speed USB device using s3c2410-ohci and address 2
usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access     HTC      Android Phone    0100 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] Attached SCSI removable disk

 拔掉:

usb 1-1: USB disconnect, address 2

再接上:

usb 1-1: new full speed USB device using s3c2410-ohci and address 3
usb 1-1: configuration #1 chosen from 1 choice
scsi1 : SCSI emulation for USB Mass Storage devices
scsi 1:0:0:0: Direct-Access     HTC      Android Phone    0100 PQ: 0 ANSI: 2
sd 1:0:0:0: [sda] Attached SCSI removable disk

  • 新的全速设备接上,使用s3c2410-ohci和地址2,断开用地址2,重新接上又分配了地址3,根据这些信息来查看内核
  • 在内核drivers目录下搜grep "USB device using" * -nR,找到以下内容

drivers/usb/core/hub.c:2186:              "%s %s speed %sUSB device using %s and address %d\n",

  • 在开发板中在USB主机控制器有hub上面接USB设备,每一个USB主机控制器都自带hub,hub可认为是特殊的USB设备

分析hub.c

在hub.c中出现打印语句

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
...
	dev_info (&udev->dev,
		  "%s %s speed %sUSB device using %s and address %d\n",
		  (udev->config) ? "reset" : "new", speed, type,
		  udev->bus->controller->driver->name, udev->devnum);

...
}

 而hub_port_init在hub_port_connect_change被调用

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
...
		status = hub_port_init(hub, udev, port1, i);
...
}

而hub_port_connect_change在hub_events被调用,而hub_events又被hub_thread线程调用,hub_thread线程平时是休眠的,在khubd_wait队列中休眠

static int hub_thread(void *__unused)
{
	do {
		hub_events();
		wait_event_interruptible(khubd_wait,
				!list_empty(&hub_event_list) ||
				kthread_should_stop());
		try_to_freeze();
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));

	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}

 khubd_wait则在kick_khubd被唤醒

static void kick_khubd(struct usb_hub *hub)
{
	unsigned long	flags;

	/* Suppress autosuspend until khubd runs */
	to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;

	spin_lock_irqsave(&hub_event_lock, flags);
	if (list_empty(&hub->event_list)) {
		list_add_tail(&hub->event_list, &hub_event_list);
		wake_up(&khubd_wait);
	}
	spin_unlock_irqrestore(&hub_event_lock, flags);
}

 同时kick_khubd被hub_irq中断调用,这是中断是主机控制器注册的中断,不是USB设备的中断,当我们接上USB设备,由硬件感知USB设备接入,就会产生hub_irq中断,一路下来调用hub_port_init函数来打印出新设备

/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
	struct usb_hub *hub = urb->context;
	int status;
	int i;
	unsigned long bits;

	switch (urb->status) {
	case -ENOENT:		/* synchronous unlink */
	case -ECONNRESET:	/* async unlink */
	case -ESHUTDOWN:	/* hardware going away */
		return;

	default:		/* presumably an error */
		/* Cause a hub reset after 10 consecutive errors */
		dev_dbg (hub->intfdev, "transfer --> %d\n", urb->status);
		if ((++hub->nerrors < 10) || hub->error)
			goto resubmit;
		hub->error = urb->status;
		/* FALL THROUGH */

	/* let khubd handle things */
	case 0:			/* we got data:  port status changed */
		bits = 0;
		for (i = 0; i < urb->actual_length; ++i)
			bits |= ((unsigned long) ((*hub->buffer)[i]))
					<< (i*8);
		hub->event_bits[0] = bits;
		break;
	}

	hub->nerrors = 0;

	/* Something happened, let khubd figure it out */
	kick_khubd(hub);

resubmit:
	if (hub->quiescing)
		return;

	if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
			&& status != -ENODEV && status != -EPERM)
		dev_err (hub->intfdev, "resubmit --> %d\n", status);
}

在hub_port_connect_change函数中调用了choose_address(udev)函数,给新设备分配编号(地址),find_next_zero_bit,如果编号被分配了相应位就设置为1,因此devnum如果大于128就返回重新分配,所以在USB设备重插上后地址为adress3,一个USB主机控制器最多可以接1-127即127个设备

static void choose_address(struct usb_device *udev)
{
	int		devnum;
	struct usb_bus	*bus = udev->bus;

	/* If khubd ever becomes multithreaded, this will need a lock */

	/* Try to allocate the next devnum beginning at bus->devnum_next. */
	devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
			bus->devnum_next);
	if (devnum >= 128)
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);

	bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);

	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);
		udev->devnum = devnum;
	}
}

hub_port_init在hub_port_connect_change被调用,SET_ADDRESS_TRIES为设置地址的宏,在hub_port_init函数中调用hub_set_address函数,把编号(地址)告诉USB设备,USB设备以后就使用这个地址,并且调用了usb_get_device_descriptor函数获取设备描述符

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
...
		for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
			retval = hub_set_address(udev);


    	retval = usb_get_device_descriptor(udev, 8);
...
}

而在usb_get_device_descriptor中找到描述符的信息在ch9.h中定义,ch9为USB规范的第九章,描述符的信息可以在include\linux\usb\ch9.h看到,usb_device_descriptor为设备描述符,还有usb_config_descriptor配置描述符

struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__le16 bcdUSB; //USB版本号
	__u8  bDeviceClass;
	__u8  bDeviceSubClass;
	__u8  bDeviceProtocol;
	__u8  bMaxPacketSize0; //最大包大小,每个设备都有端点0,通过端点0来识别USB设备
	__le16 idVendor; //厂家ID
	__le16 idProduct; //产品ID
	__le16 bcdDevice;
	__u8  iManufacturer;
	__u8  iProduct;
	__u8  iSerialNumber;
	__u8  bNumConfigurations; //配置的个数,即设备有多少种配置
} __attribute__ ((packed));

struct usb_config_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__le16 wTotalLength; //本配置下其他所有信息的总长度
	__u8  bNumInterfaces; //接口的个数
	__u8  bConfigurationValue; 
	__u8  iConfiguration;
	__u8  bmAttributes;
	__u8  bMaxPower;
} __attribute__ ((packed));

每个USB设备都有设备描述符,一个设备可能有多种配置,还有配置描述符,每个配置下面可能有多个接口,也可能只有一个接口,接口是逻辑上的设备,比如USB声卡,硬件只有一个,但逻辑上可能有两个功能,录音和播放,就可分为两个接口,两个逻辑设备,用两个接口描述符来分别描述,接口描述符如下,有端点就有端点描述符,写驱动程序的时候是给逻辑上的设备写的,所以一个USB硬件有可能安装多个驱动程序,一个接口描述符里有多个端点,描述符详解

struct usb_interface_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__u8  bInterfaceNumber;
	__u8  bAlternateSetting;
	__u8  bNumEndpoints; //USB传输的对象,除了端点0,有多少个端点
	__u8  bInterfaceClass;
	__u8  bInterfaceSubClass;
	__u8  bInterfaceProtocol;
	__u8  iInterface;
} __attribute__ ((packed));

struct usb_endpoint_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__u8  bEndpointAddress; //端点地址,是端点1还是2,有编号(地址)
	__u8  bmAttributes; //属性,哪种类型的端点,是输入还是输出,是实时端点还是批量端点
	__le16 wMaxPacketSize; //最大包大小,一次性可以读多少数据
	__u8  bInterval; //对于鼠标,查询的频繁

	/* NOTE:  these two are _only_ in audio endpoints. */
	/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
	__u8  bRefresh;
	__u8  bSynchAddress;
} __attribute__ ((packed));

 在usb_get_device_descriptor只获得8个字节,是因为不知道端点0一次性能传输多少数据,在设备描述符中第8个字节就可以知道一个包最大可以读多少,后面再根据最大包来获得设备描述符

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
...
    	retval = usb_get_device_descriptor(udev, 8);
...
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
...
}
struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__le16 bcdUSB; //USB版本号
	__u8  bDeviceClass;
	__u8  bDeviceSubClass;
	__u8  bDeviceProtocol;
	__u8  bMaxPacketSize0; 
..
}

继续分析hub_port_connect_change函数调用了usb_new_device函数,而在usb_get_configuration函数把所有的描述符都读出来,并解析,并且调用了device_add函数,回到了总线设备驱动模型,把device放入usb_bus_type的dev链表, 从usb_bus_type的driver链表里取出usb_driver

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
...
        udev = usb_alloc_dev(hdev, hdev->bus, port1); //dev->dev.bus = &usb_bus_type;
		    
		/* Run it through the hoops (find a driver, etc) */
		if (!status) {
			status = usb_new_device(udev);
...
    err = device_add(&udev->dev);
...
}

int usb_new_device(struct usb_device *udev)
{
	int err;

	/* Determine quirks */
	usb_detect_quirks(udev);

	err = usb_get_configuration(udev);
...

int usb_get_configuration(struct usb_device *dev)
{
...
    result = usb_parse_configuration(&dev->dev, cfgno,
		    &dev->config[cfgno], bigbuffer, length);
...
}

在usb_alloc_dev函数分配了一个dever,在函数中dev->dev.bus = &usb_bus_type;出现了bus总线,在bus总线中match函数是根据id_table来比较usb_interface和usb_driver,如果能匹配,调用usb_driver的probe

struct usb_device *
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
	struct usb_device *dev;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return NULL;

	if (!usb_get_hcd(bus_to_hcd(bus))) {
		kfree(dev);
		return NULL;
	}

	device_initialize(&dev->dev);
	dev->dev.bus = &usb_bus_type;
	dev->dev.type = &usb_device_type;
	dev->dev.dma_mask = bus->controller->dma_mask;
	dev->state = USB_STATE_ATTACHED;
...
}

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	/* devices and interfaces are handled separately */
	if (is_usb_device(dev)) {

		/* interface drivers never match devices */
		if (!is_usb_device_driver(drv))
			return 0;

		/* TODO: Add real matching code */
		return 1;

	} else {
		struct usb_interface *intf;
		struct usb_driver *usb_drv;
		const struct usb_device_id *id;

		/* device drivers never match interfaces */
		if (is_usb_device_driver(drv))
			return 0;

		intf = to_usb_interface(dev);
		usb_drv = to_usb_driver(drv);

		id = usb_match_id(intf, usb_drv->id_table);
		if (id)
			return 1;

		id = usb_match_dynamic_id(intf, usb_drv);
		if (id)
			return 1;
	}

	return 0;
}

更深了解参考《LINUX内核源代码情景分析》书籍

三、编写USB设备驱动程序

3.1 怎么写USB设备驱动程序

usb_interface在usb_bus_type总线当接上设备后会被USB主机控制器发现,需要我们来完善usb_driver

目的:把USB鼠标当成按键,左键为L健,右键为S健,滚轮按下为Enter健(输入子系统实现),1.分配input_dev结构体;2.设置结构体; 3.注册结构体;  4.硬件相关的操作(使用总线驱动程序提供的函数来收发数据)

  • 写USB设备驱动程序过程如下,在probe函数中实现输入子系统

    1. 分配/设置usb_driver结构体
            .id_table
            .probe
            .disconnect
    2. 注册

参考drivers\hid\usbhid\usbmouse.c,真正鼠标设备驱动程序

3.2 分配usb_driver结构体并设置注册

static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};

 在usbmouse.c中id_table为一个USB_INTERFACE_INFO宏,该宏在usb.h中定义,只要USB设备接口描述符的类是USB_INTERFACE_CLASS_HID,子类是USB_INTERFACE_SUBCLASS_BOOT,协议是USB_INTERFACE_PROTOCOL_MOUSE,就能够支持,因此usbmouse_as_key_id_table函数就可以设置出

static struct usb_device_id usb_mouse_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};


#define USB_INTERFACE_INFO(cl,sc,pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

如果想支持某款设备,只要 厂家ID,设备ID匹配就可以支持是可以的,在usb.h中定义了USB_DEVICE(vend,prod),根据需求修改id_table

#define USB_DEVICE(vend,prod) \
	.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \
			.idProduct = (prod)


----------------------------
static struct usb_device_id usbmouse_as_key_id_table [] = {
	//{USB_DEVICE(0x1234,0x5678)},
	{ }	/* Terminating entry */
};

在probe函数中参数一,逻辑上的设备就是usb_interface表示的,通过这个接口可以得到一个usb_device结构体

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)

 在usb_device结构体中有着设备描述符,因此我们可以打印出厂家ID和设备ID,写出probe函数和disconnect函数,完整代码如下


/*
 * drivers\hid\usbhid\usbmouse.c
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	//{USB_DEVICE(0x1234,0x5678)},
	{ }	/* Terminating entry */
};


static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	printk("found usbmouse!\n");

	printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
	printk("VID    = 0x%x\n", dev->descriptor.idVendor);
	printk("PID    = 0x%x\n", dev->descriptor.idProduct);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	printk("disconnect usbmouse!\n");
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");

3.3 小测试

需要我们去掉原来的USB鼠标驱动,make menuconfig->Device Drivers->HID Devices-><> USB Human Interface Device (full HID) support ,并make uImage使用新的内核启动

 加载驱动后,把PC的鼠标接在开发板,可以看到厂家ID和设备ID,与windows上的信息一致

3.4 完善probe函数

  • 在usbmouse.c中probe前面是一些判断,判断端点是否为1个,是否是中断端点
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_mouse *mouse;
	struct input_dev *input_dev;
	int pipe, maxp;
	int error = -ENOMEM;

	interface = intf->cur_altsetting;

	if (interface->desc.bNumEndpoints != 1)
		return -ENODEV;

	endpoint = &interface->endpoint[0].desc;
	if (!usb_endpoint_is_int_in(endpoint))
		return -ENODEV;
...
  •  分配一个input_dev结构体,并设置注册

static struct input_dev *uk_dev; 

   uk_dev = input_allocate_device();
    set_bit(EV_KEY, uk_dev->evbit);
    set_bit(EV_REP, uk_dev->evbit);
    
    set_bit(KEY_L, uk_dev->keybit);
    set_bit(KEY_S, uk_dev->keybit);
    set_bit(KEY_ENTER, uk_dev->keybit);
    
    input_register_device(uk_dev);

  • 硬件相关的操作,使用总线驱动程序提供的函数来收发数据,数据传输三要素:源,目的,长度

源:USB设备的某个端点,usb_rcvintpipe宏包含了USB设备的地址还有端点的地址,pipe是个整数,含有端点的类型,端点的方向,还有设备地址端点地址

struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;

interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

#define usb_rcvintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

static inline unsigned int __create_pipe(struct usb_device *dev,
        unsigned int endpoint)
{
    return (dev->devnum << 8) | (endpoint << 15);
}

目的用usb_buffer_alloc函数来分配,参数三为一个物理地址,而长度在端点描述符中

static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;

len = endpoint->wMaxPacketSize;

usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

使用三要素,需要usb_alloc_urb函数来分配urb,即usb请求块,使用usb_fill_int_urb函数将三要素填充置urb,usbmouse_as_key_irq为查询函数,并不是CPU在查询,而是由主机控制器在查询,endpoint->bInterval为查询的频率,当主机控制器有数据后,就会存在usb_buf中从而usbmouse_as_key_irq函数被调用

static struct urb *uk_urb;
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);

USB主机控制器得到数据后需要往某个物理地址去写,因此我们需要告诉它物理地址以及标记

uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

使用urb

usb_submit_urb(uk_urb, GFP_KERNEL);

写出usbmouse_as_key_irq函数先将数据打印出来需要重新提交urb,并且完善disconnect函数

static void usbmouse_as_key_irq(struct urb *urb)
{
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
	for (i = 0; i < len; i++)
	{
		printk("%02x ", usb_buf[i]);
	}
	printk("\n");

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

3.5 通过鼠标了解数据含义

完整代码如下

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
	for (i = 0; i < len; i++)
	{
		printk("%02x ", usb_buf[i]);
	}
	printk("\n");

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能产生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 长度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 分配usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");

 加载驱动,接上鼠标,按下左键右键滚轮健,可看到当数据中bit[8]为1则左键按下0松开,bit[9]为1则右键按下0松开,bit[10]为1则滚轮键按下0松开

3.6 根据含义完善驱动程序

  • 将打印改成上报事件,根据上次的数据来和这次的数据比较,修改usbmouse_as_key_irq函数
static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;
	/* USB鼠标数据含义
	 * data[0]: bit8-左键, 1-按下, 0-松开
	 *          bit9-右键, 1-按下, 0-松开
	 *          bit10-中键, 1-按下, 0-松开 
	 *
     */
	if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))
	{
		/* 左键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))
	{
		/* 右键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))
	{
		/* 中键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[1];

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}
  • 加载驱动,并测试,当接上鼠标后才会出现新设备event0,查看tty1,按下左键后按滚轮健出现l,按下右键后按滚轮健出现s,按下左键一会再按滚轮健,可以看到连续的l,也可以输入命令hexdump /dev/event0查看输出信息

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值