13.USB驱动

一、情景分析

现象:把USB设备接到PC

  1. 右下角弹出"发现android phone"
  2. 跳出一个对话框,提示你安装驱动程序

问1. 既然还没有"驱动程序",为何能知道是"android phone"
答1. windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone"
提示你安装的是"设备驱动程序"
USB总线驱动程序负责:识别USB设备, 给USB设备找到对应的驱动程序

问2. USB设备种类非常多,为什么一接入电脑,就能识别出来?
答2. PC和USB设备都得遵守一些规范。
比如:USB设备接入电脑后,PC机会发出"你是什么"?
USB设备就必须回答"我是xxx", 并且回答的语言必须是中文
USB总线驱动程序会发出某些命令想获取设备信息(描述符),
USB设备必须返回"描述符"给PC

问3. PC机上接有非常多的USB设备,怎么分辨它们?
USB接口只有4条线: 5V,GND,D-,D+
答3. 每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号
接在USB总线上的每一个USB设备都有自己的编号(地址)
PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)

问4. USB设备刚接入PC时,还没有编号;那么PC怎么把"分配的编号"告诉它?
答4. 新接入的USB设备的默认编号是0,在未分配新编号前,PC使用0编号和它通信。

问5. 为什么一接入USB设备,PC机就能发现它?
答5. PC的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时为低电平
USB设备的USB口内部,D-或D+接有1.5K的上拉电阻;它一接入PC,就会把PC USB口的D-或D+拉高,从硬件的角度通知PC有新设备接入

其他概念:
1.USB是主从结构的
所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。
例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。

2.USB的传输类型:
a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程
b. 批量传输: 可靠, 时间没有保证, 比如:U盘
c. 中断传输:可靠,实时,比如:USB鼠标 此中断非彼中断。没有中断能力。查询方式实现实时。
d. 实时传输:不可靠,实时,比如:USB摄像头

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

4.每一个端点都有传输类型,传输方向

5 术语里、程序里说的输入(IN)、输出(OUT) “都是” 基于USB主机的立场说的。
比如鼠标的数据是从鼠标传到PC机, 对应的端点称为"输入端点"

6.USB总线驱动程序的作用
a. 识别USB设备
b. 查找并安装对应的设备驱动程序
c. 提供USB读写函数
同样linux内核也自带了USB总线驱动程序,框架如下
在这里插入图片描述
USB总线驱动程序的作用
1.识别USB设备
1.1 分配地址
1.2 并告诉USB设备(set address)
1.3 发出命令获取描述符
描述符的信息可以在include\linux\usb\Ch9.h看到

2.查找并安装对应的设备驱动程序

3.提供USB读写函数
要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:

OHCI(Open Host Controller Inerface):微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的软件简单,硬件复杂

UHCI(Universal Host Controller Interface):Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的软件复杂,硬件简单

EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)

xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设针,同时也支持USB2.0、1.1等

二、分析USB总线驱动

1、drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里
它又是被谁调用的呢,如下图所示,我们搜索到它是通过hub_thread()函数调用的
在这里插入图片描述
hub_thread()函数如下:

static int hub_thread(void *__unused)
{
	do {
		hub_events();    //执行页次hub时间函数
		wait_event_interruptible(khubd_wait,
				!list_empty(&hub_event_list) ||
				kthread_should_stop());//每次执行一次hub事件,都会进入一次等待事件中断函数
		try_to_freeze();
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));
 
	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}

从上面函数中得到,要想执行hub_event(),都要等待khubd_wait这个中断唤醒才行
2、我们搜索“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);//唤醒khubd_wait这个中断
	}
	spin_unlock_irqrestore(&hub_event_lock, flags);
}

3、继续搜索kick_khubd,发现被hub_irq()函数中调用
显然,就是当USB设备插入后,D+ 或 D- 就会被拉高,然后USB主机控制器就会产生一个hub_irq中断

4、接下来我们直接分析hub_port_connect_chage()函数,如何连接端口的

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
	... ...
	udev = usb_alloc_dev(hdev, hdev->bus, port1);//(1)注册usb_device,然后会放在usb总线上
		
	usb_set_device_state(udev, USB_STATE_POWERED);//设置注册的USB设备的状态标识
	
	... ...
		
	choose_address(udev);//(2)给新的设备飞培一个地址编号
		
	status = hub_port_init(hub, udev, port1, i);//(3)初始化端口,与USB设备建立连接
	... ...
 
	status = usb_new_device(udev);//(4)创建USB设备,与USB设备驱动连接
}

(usb_device设备结构体参考:https://mp.csdn.net/postedit/81025137)
所以最终流程图如下:
在这里插入图片描述
5、我们进入hub_port_connect_change()->usb_alloc_dev(),来看看它是怎么设置usb_device的

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);//分配一个usb_device设备
	... ...
 
	device_initialize(&dev->dev);//初始化usb_device
	dev->dev.bus = &usb_bus_type;//(1)设置usb_device的成员device->bus等于usb_bus_type总线
	dev->dev.type = &usb_device_type;//(2)设置usb_device的成员device->type等于usb_device_type
	...
	return dev;//返回一个usb_device结构体
}

(1)设置device成员bus,主要是二十小节,注册usb总线的device表上,

其中usb_bus_type是一个全局变量,它和我们之前学的platform平台总线相似,属于USB总线,是Linux中bus的一种。

如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB驱动联系起来。
在这里插入图片描述
usb_bus_type结构体如下:

struct bus_type usb_bus_type = {
	.name =		"usb",//总线名称,存在/sys/bus
	.match =	usb_device_match,//匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
	.uevent =	usb_uevent,//事件函数
	.suspend =	usb_suspend,//休眠函数
	.resume =	usb_resume,//唤醒函数
};

6、我们进入hub_port_connect_change()->choose_address(),来看看它是怎么分配地址编号的

static void choose_address(struct usb_device *udev)
{
	int		devnum;
	struct usb_bus	*bus = udev->bus;
 
	devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);//在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号
	if (devnum >= 128)//若编号大于等于128,说明没有找到空余的地址编号,从头开始找
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
 
	bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);//设置下次寻址的区间+1
 
	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);//设置位
		udev->devnum = devnum;
	}
}

从上面代码中分析每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,可以看出,如下图所示:
在这里插入图片描述
7、我们再来看看hub_port_connect_chage()->hub_port_init()函数是如何来实现连接USB设备的

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);//(1)设置地址,告诉USB设备新的地址编号
		if (retval >= 0)
			break;
		msleep(200);
	}
...
	retval = usb_get_device_descriptor(udev, 8);//(2)获得USB设备描述符前8个字节
...
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);//重新获取设备描述符信息
}

(1)hub_set_address()函数主要是用来告诉USB设备新的地址编号,
hub_set_address()函数如下:

static int hub_set_address(struct usb_device *udev)
{
	int retval;
 
	retval = usb_control_msg(udev, usb_sndaddr0pipe(),//(1)
		USB_REQ_SET_ADDRESS, 0, udev->devnum, 0,
		NULL, 0, USB_CTRL_SET_TIMEOUT);
 
 
	if (retval == 0) {//设置新的地址,传输完成,返回0
		usb_set_device_state(udev, USB_STATE_ADDRESS);//设置状态标志
		ep0_reinit(udev);
	}
	return retval;
}

usb_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0,其中参数udev表示目标设备;使用管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0;USB_REQ_SET_ADDRESS表示命令码,既设置地址;udev->devnum表示设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。

(2)usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方说支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构。

其中USB设备描述符结构体如下所示:

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;    //本描述符的size
	__u8  bDescriptorType;//描述符的类型,这里是设备描述符DEVICE
 
	__le16 bcdUSB;//指名usb的版本,比如usb2.0
	__u8  bDeviceClass;//类
	__u8  bDeviceSubClass;//子类
	__u8  bDeviceProtocol;//指定协议
	__u8  bMaxPacketSize0;//端点0对应的最大包大小
	__le16 idVendor;//厂家ID
	__le16 idProduct;//产品ID
	__le16 bcdDevice;//设备的发布号
	__u8  iManufacturer;//字符串描述符中厂家ID的索引
	__u8  iProduct;//字符串描述符中产品ID的索引
	__u8  iSerialNumber;//字符串描述符中设备序列号的索引
	__u8  bNumConfigurations;//可能的配置的树木
} __attribute__ ((packed));

8、我们来看看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的

int usb_new_device(struct usb_device *udev)
{
...
	err = usb_get_configuration(udev);//(1)获取配置描述块
...
	err = device_add(&udev->dev);//(2)把device放入bus的dev链表中,并寻找对应的设备驱动
...	
}

(1)其中usb_get_configuration()函数如下,就是获取各个配置

int usb_get_configuration(struct usb_device *dev)
{
    //USB_MAXCONFIG定义为8,表示设备描述块下最多不能超过8个配置描述块
	if (ncfg > USB_MAXCONFIG) {//nfs表示 设备描述块下 有多少个配置描述块
		dev_warn(ddev, "too many configurations: %d, "
		    "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
		dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
	}
 
	for (cfgno = 0; cfgno < ncfg; cfgno++) //for循环,从USB设备里一次读入所有配置描述块
	{
	
		length = max((int) le16_to_cpu(desc->wTotalLength),
		    USB_DT_CONFIG_SIZE);//通过wTotalLength,知道实际数据大小
 
		bigbuffer = kmalloc(length, GFP_KERNEL);//然后分配足够大的空间
...
		result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		    bigbuffer, length);//在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bifbuffer中
...
		dev->rawdescriptors[cfgno] = bigbuffer;//再将bifbuffer地址放在ramdescriptor所指的指针数组中
 
		result = usb_parse_configuration(&dev->dev, cfgno,
		    &dev->config[cfgno], bigbuffer, length);//最后解析每个配置块
	}
}

(2)其中device_add()函数如下:

int device_add(struct device *dev)
{
...
	dev = get_device(dev);//使dev等于usb_device下的device成员
...
	if ((error = bus_add_device(dev)))//把这个设备添加带dev->bus的device表中
		goto BusError;
...
	bus_attach_device(dev);//来匹配对应的驱动程序
	
}

当bus_attach_device()函数匹配成功,就会调用驱动的probe函数
9、我们再来看看usb_bus_type这个的成员usb_device_match函数,看看是如何匹配的
在这里插入图片描述
usb_device_match函数如下所示:

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	if (is_usb_device(dev)) {//判断是不是USB设备
 
		if (!is_usb_device_driver(drv))
			return 0;
 
		return 1;
 
	} else {    //否则就是USB驱动或者USB设备的接口
		struct usb_interface *intf;
		struct usb_driver *usb_drv;
		const struct usb_device_id *id;
 
		if (is_usb_device_driver(drv))//如果是USB驱动,就不需要匹配,直接return
			return 0;
 
		intf = to_usb_interface(dev);//获取USB设备的接口
		usb_drv = to_usb_driver(drv);//获取USB驱动
 
		id = usb_match_id(intf, usb_drv->id_table);//匹配USB驱动的成员id_table
		if (id)
			return 1;
 
		id = usb_match_dynamic_id(intf, usb_drv);
		if (id)
			return 1;
	}
 
	return 0;
}

显然就是匹配USB驱动的id_table
10、那么USB驱动的id_table又该如何定义
id_table的结构体为usb_device_id,如下所示:

struct usb_device_id {
 
	__u16		match_flags;//与usb设备匹配哪种类型?比较类型的宏如下:
//USB_DEVICE_ID_MATCH_INT_INFO:用于匹配设备的接口描述符的3个成员
//USB_DEVICE_ID_MATCH_DEV_INFO:用于匹配设备描述符的3个成员
//USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION:用于匹配特定的USB设备的4个成员
//USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct)
 
 
	/* 以下4个用匹配描述特定的USB设备 */
	__u16		idVendor;	//厂家ID
	__u16		idProduct;	//产品ID
	__u16		bcdDevice_lo;	//设备的低版本号
	__u16		bcdDevice_hi;	//设备的高版本号
 
	/* 以下3个就是用于比较设备描述符 */
	__u8		bDeviceClass;	//设备类
	__u8		bDeviceSubClass;//设备子类
	__u8		bDeviceProtocol;//设备协议
 
	/* 以下3个就是用于比较设备的接口描述符的 */
	__u8		bInterfaceClass;	//接口类型
	__u8		bInterfaceSubClass;	//接口子类型
	__u8		bInterfaceProtocol;	//接口所遵循的协议
 
	/* not matched against */
	kernel_ulong_t	driver_info;
}

(设备描述符接口描述符结构体参考:https://mp.csdn.net/postedit/81025137)
我们参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),是如何使用的,如下图所示:
在这里插入图片描述
发现它是通过USB_INTERFACE_INFO这个宏定义的,该宏如下所示:

#define USB_INTERFACE_INFO(cl,sc,pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, //设置id_table的,match_flags成员
	.bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)//设置id_table的3个成员,用于匹配USB设备的3个成员

然后将上图的usb_mouse_id_table[]里的3个值代入宏
USB_INTERFACE_INFO(cl,sc,pr)中:

.bInterfaceClass =USB_INTERFACE_CLASS_HID;  
   //设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
   //HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03
 
.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;  
   //设置匹配USB的接口子类型为启动设备
 
.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
  //设置匹配USB的接口协议为USB鼠标的协议,等于2
  //当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议

如下图,我们也可以通过windows下也可以找到鼠标的协议号,是1(Col01):

在这里插入图片描述

其中VID:表示厂家(vendor)ID

PID:表示产品(Product)ID

总结:当我们插上USB设备时,系统就会获取USB设备的配置、接口、端点的数据,并创建设备,所以我们的驱动就需要写id_table来匹配该USB设备

三、ch9.h描述符分析

Ch9.h
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__le16 bcdUSB;/*usb版本号*/
	__u8  bDeviceClass;/*类*/
	__u8  bDeviceSubClass;/*子类*/
	__u8  bDeviceProtocol;/*协议*/
	__u8  bMaxPacketSize0;/*端点0  每个设备都有*/
	__le16 idVendor;/*厂家ID*/
	__le16 idProduct;/*产品ID*/
	__le16 bcdDevice;
	__u8  iManufacturer;
	__u8  iProduct;
	__u8  iSerialNumber;
	__u8  bNumConfigurations;/*改设备有多少种配置*/
} __attribute__ ((packed));

/*配置描述符*/
/* USB_DT_CONFIG: Configuration descriptor information.
 *
 * USB_DT_OTHER_SPEED_CONFIG is the same descriptor, except that the
 * descriptor type is different.  Highspeed-capable devices can look
 * different depending on what speed they're currently running.  Only
 * devices with a USB_DT_DEVICE_QUALIFIER have any OTHER_SPEED_CONFIG
 * descriptors.
 */
struct usb_config_descriptor {
	__u8  bLength;/*描述符本身长度*/
	__u8  bDescriptorType;/*类型*/

	__le16 wTotalLength;/*所有描述符如接口描述符,端点描述符等的大小,一次性读出*/
	__u8  bNumInterfaces;/*接口个数,什么设备,或者不同功能不同接口*/
	__u8  bConfigurationValue;
	__u8  iConfiguration;
	__u8  bmAttributes;
	__u8  bMaxPower;
} __attribute__ ((packed));
/*接口描述符  写驱动就是给逻辑上的设备写的。一个usb硬件可能有多个逻辑设备,有多个逻辑设备就会安装多个驱动程序,多个端点*/
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__u8  bInterfaceNumber;
	__u8  bAlternateSetting;
	__u8  bNumEndpoints;/*端点*/
	__u8  bInterfaceClass;/*类*/
	__u8  bInterfaceSubClass;/*子类*/
	__u8  bInterfaceProtocol;
	__u8  iInterface;
} __attribute__ ((packed));
/*端点描述符  一次性传输多大的数据,端点编号,方向,传输类型*/
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;

	__u8  bEndpointAddress;/*端点地址,端点1 2 3 4*/
	__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));

四、写程序

关键部分参考usbmouse.c
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


/*
 * drivers\hid\usbhid\usbmouse.c
 * 鼠标模拟键盘
 * 左键:L 右键:S 滚轮按下:ENTER
 */

#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;
/*INTERFACE_INFO接口信息:只要类HID, 子类BOOT,协议为MOUSE 就可以支持*/
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)}, (厂家ID,设备ID)这么写就支持特定厂家的驱动。
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;
#if 0	
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
	for (i = 0; i < len; i++)
	{
		printk("%02x ", usb_buf[i]);
	}
	printk("\n");
#endif
	/* USB鼠标数据含义
	 * data[0]: bit0-左键, 1-按下, 0-松开
	 *          bit1-右键, 1-按下, 0-松开
	 *          bit2-中键, 1-按下, 0-松开 
	 *
     */
	if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
	{
		/* 左键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

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

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

	/* 重新提交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. 硬件相关操作 根据USB总线提供的驱动程序收发USB数据*/
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//usb_rcvintpipe中包含设备地址和端点地址pipe 为整数  包含了 二者
															//或运算合成一个整数
	/* 长度: */
	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);//bInterval 主机查询频率 ,主机得到数据产生中断调用usbmouse_as_key_irq
	uk_urb->transfer_dma = usb_buf_phys;//USB控制器得到数据后要写的物理地址
	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);

	//printk("disconnect usbmouse!\n");
	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");

五、测试

  1. insmod usbmouse_as_key.ko
  2. ls /dev/event*
  3. 接上USB鼠标
  4. ls /dev/event*
  5. cat /dev/tty1 然后按鼠标键
  6. hexdump /dev/event0
发布了66 篇原创文章 · 获赞 1 · 访问量 1159

猜你喜欢

转载自blog.csdn.net/qq_16933601/article/details/103494692