USB驱动——键盘驱动(控制传输)

本文以 usbkbd.c 为例,分析 usb 键盘驱动程序。

static int __init usb_kbd_init(void)
{
	int result = usb_register(&usb_kbd_driver);
	if (result == 0)
		printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
				DRIVER_DESC "\n");
	return result;
}
static struct usb_driver usb_kbd_driver = {
	.name =		"usbkbd",
	.probe =	usb_kbd_probe,
	.disconnect =	usb_kbd_disconnect,
	.id_table =	usb_kbd_id_table,
};

还是来看一下 id_table ,与鼠标相比,仅仅是协议不一样。

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

下面来看probe函数

static int usb_kbd_probe(struct usb_interface *iface,
			 const struct usb_device_id *id)
{
	/* 获得usb_device */
	struct usb_device *dev = interface_to_usbdev(iface);

	/* 接口的设置 */
	struct usb_host_interface *interface;

	/* 端点描述符 */
	struct usb_endpoint_descriptor *endpoint;

	/* 键盘结构体 */
	struct usb_kbd *kbd;

	/* 输入设备 */
	struct input_dev *input_dev;

	int i, pipe, maxp;
	int error = -ENOMEM;

	/* 获取该接口当前的设置 */
	interface = iface->cur_altsetting;

	/* 如果当前设置的端点数量不是1,那么错误,返回 */
	if (interface->desc.bNumEndpoints != 1)
		return -ENODEV;

	/* 如果当前设置第一个端点的类型不是中断端点,错误,返回 */
	endpoint = &interface->endpoint[0].desc;
	if (!usb_endpoint_is_int_in(endpoint))
		return -ENODEV;

	/* 第一个端点的管道 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 最大传输包大小 */
	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

	/* 为键盘结构体分配空间 */
	kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);

	/* 分配一个输入设备 */
	input_dev = input_allocate_device();

	/*
		kbd->irq = usb_alloc_urb(0, GFP_KERNEL)
		kbd->led = usb_alloc_urb(0, GFP_KERNEL)
		kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)
		kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)
		kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma)
	*/
	if (usb_kbd_alloc_mem(dev, kbd))
		goto fail2;

	/* 填充键盘结构体。以及一些字符串 */
	kbd->usbdev = dev;
	kbd->dev = input_dev;

	if (dev->manufacturer)
		strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));

	if (dev->product) {
		if (dev->manufacturer)
			strlcat(kbd->name, " ", sizeof(kbd->name));
		strlcat(kbd->name, dev->product, sizeof(kbd->name));
	}

	if (!strlen(kbd->name))
		snprintf(kbd->name, sizeof(kbd->name),
			 "USB HIDBP Keyboard %04x:%04x",
			 le16_to_cpu(dev->descriptor.idVendor),
			 le16_to_cpu(dev->descriptor.idProduct));

	usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
	strlcpy(kbd->phys, "/input0", sizeof(kbd->phys));

	/* 填充输入设备 */
	input_dev->name = kbd->name;
	input_dev->phys = kbd->phys;
	usb_to_input_id(dev, &input_dev->id);
	input_dev->dev.parent = &iface->dev;

	input_set_drvdata(input_dev, kbd);

	/* 设置它支持的事件类型和具体事件 1、按键类事件 2、LED灯(大小写灯等)3、重复上报*/
	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
		BIT_MASK(EV_REP);
	input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
		BIT_MASK(LED_KANA);

	for (i = 0; i < 255; i++)
		set_bit(usb_kbd_keycode[i], input_dev->keybit);
	clear_bit(0, input_dev->keybit);

	/* 对于LED类型的事件,首先会调用到dev->event 然后再调用事件处理层的event */
	input_dev->event = usb_kbd_event;
	input_dev->open = usb_kbd_open;
	input_dev->close = usb_kbd_close;

	/* 填充中断类型Urb */
	usb_fill_int_urb(kbd->irq, dev, pipe,
			 kbd->new, (maxp > 8 ? 8 : maxp),
			 usb_kbd_irq, kbd, endpoint->bInterval);
	kbd->irq->transfer_dma = kbd->new_dma;
	kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/*
	 * 	bit7 	控制传输 data 阶段的方向 0 主机到设备 1设备到主机,	这里 0 主机到设备
	 *	bit5-6	表示 request 类型,是标准的还是厂家定义的,			这里为hid class定义
	 *	bit0-4	表示这个请求针对的是设备、接口还是端点 ,			这里是接口
	 */
	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;	// 0x01 << 5 | 

	/*
		#define USB_REQ_GET_STATUS		0x00
		#define USB_REQ_CLEAR_FEATURE		0x01
		#define USB_REQ_SET_FEATURE		0x03
		#define USB_REQ_SET_ADDRESS		0x05
		#define USB_REQ_GET_DESCRIPTOR		0x06
		#define USB_REQ_SET_DESCRIPTOR		0x07
		#define USB_REQ_GET_CONFIGURATION	0x08
		#define USB_REQ_SET_CONFIGURATION	0x09
		#define USB_REQ_GET_INTERFACE		0x0A
		#define USB_REQ_SET_INTERFACE		0x0B
		#define USB_REQ_SYNCH_FRAME		0x0C
	*/
	kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION;

	/* request 的参数 */
	kbd->cr->wValue = cpu_to_le16(0x200);

	/* bRequestType 中,针对接口、端点时,它表示那个接口或端点 */
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);

	/* data阶段数据长度 */
	kbd->cr->wLength = cpu_to_le16(1);

	/*
	static inline void usb_fill_control_urb(
					struct urb *urb,
					struct usb_device *dev,
					unsigned int pipe,
					unsigned char *setup_packet,
					void *transfer_buffer,
					int buffer_length,
					usb_complete_t complete_fn,
					void *context)
	*/
	/* 这里使用的是默认端点0 */
	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),(void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd);
	kbd->led->setup_dma = kbd->cr_dma;
	kbd->led->transfer_dma = kbd->leds_dma;
	kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);

	error = input_register_device(kbd->dev);

	usb_set_intfdata(iface, kbd);
	return 0;
}

这里与鼠标驱动程序相比,多了一个控制传输过程,而且,这个控制传输的 bRequestType 中说明了这个传输不是标准的请求,而是Class,我们的键盘是HID类型,因此还要看USB HID协议中,关于这个请求是如何定义的,才能知道wValue 、wIndex等是什么意思(参考:http://blog.csdn.net/leo_wonty/article/details/6721214),这就是就将knd->leds
内的一字节数据发送给从设备。在鼠标驱动程序中,中断 urb 是在open函数中提交的,这里也不例外。

static int usb_kbd_open(struct input_dev *dev)
{
	struct usb_kbd *kbd = input_get_drvdata(dev);

	kbd->irq->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->irq, GFP_KERNEL))
		return -EIO;

	return 0;
}

中断传输完成之后会调用完成函数,usb_kbd_irq

static void usb_kbd_irq(struct urb *urb)
{
	struct usb_kbd *kbd = urb->context;
	int i;

	switch (urb->status) {
	case 0:			/* success */
		break;
	case -ECONNRESET:	/* unlink */
	case -ENOENT:
	case -ESHUTDOWN:
		return;
	/* -EPIPE:  should clear the halt */
	default:		/* error */
		goto resubmit;
	}
	//报告usb_kbd_keycode[224..231]8按键状态
	//KEY_LEFTCTRL,KEY_LEFTSHIFT,KEY_LEFTALT,KEY_LEFTMETA,
	//KEY_RIGHTCTRL,KEY_RIGHTSHIFT,KEY_RIGHTALT,KEY_RIGHTMETA
	for (i = 0; i < 8; i++)
		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
	//若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下
	for (i = 2; i < 8; i++) {
		//获取键盘离开的中断
			//同时没有该KEY的按下状态
		if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
			if (usb_kbd_keycode[kbd->old[i]])
				input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
			else
				hid_info(urb->dev,
					 "Unknown key (scancode %#x) released.\n",
					 kbd->old[i]);
		}
		//获取键盘按下的中断
			//同时没有该KEY的离开状态
		if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
			if (usb_kbd_keycode[kbd->new[i]])
				input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
			else
				hid_info(urb->dev,
					 "Unknown key (scancode %#x) released.\n",
					 kbd->new[i]);
		}
	}

	input_sync(kbd->dev);			//同步设备,告知事件的接收者驱动已经发出了一个完整的报告

	memcpy(kbd->old, kbd->new, 8);	//防止未松开时被当成新的按键处理

resubmit:
	i = usb_submit_urb (urb, GFP_ATOMIC);
	if (i)
		hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",
			kbd->usbdev->bus->bus_name,
			kbd->usbdev->devpath, i);
}

这里,都是上报的按键类事件,我们在前边的Probe函数中,设置了输入设备支持按键类事件,还有一个LED类事件,但是搜遍代码也没有找到,LED类事件是在哪里上报的。还有,probe函数中定义了一个dev->event函数,在浏览资料时发现,有些人说在input_event时,调用事件处理层的event函数的同时会调用dev->event,我认为这种说法是不正确的,看过input_event代码的同学应该不难发现,只有上报LED类等事件的时候才会触发dev->event,我们这里单纯上报的按键类事件并不会触发dev->event
。那么,probe 函数里定义的 dev->event 函数岂不是成了摆设么?确实,我暂时没发现它有什么用。手头没有usb键盘,这个后边实验证实。

static int usb_kbd_event(struct input_dev *dev, unsigned int type,
			 unsigned int code, int value)
{
	struct usb_kbd *kbd = input_get_drvdata(dev);

	if (type != EV_LED)
		return -1;

	kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
		       (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |
		       (!!test_bit(LED_NUML,    dev->led));

	if (kbd->led->status == -EINPROGRESS)
		return 0;

	if (*(kbd->leds) == kbd->newleds)
		return 0;

	*(kbd->leds) = kbd->newleds;
	kbd->led->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
		err_hid("usb_submit_urb(leds) failed");

	return 0;
}

这里的 dev->led 记录的是led的状态,比如,我们上报一个LED事件时,input_event 中会将对应的事件记录在 dev->led 中。这里检测LED事件是否发生,然后通过控制传输将1字节数据传送给usb键盘,Usb键盘的灯相应做出改变。但是,说到底,代码里没有上报过LED类事件,一切都是白扯。

static void usb_kbd_led(struct urb *urb)
{
	struct usb_kbd *kbd = urb->context;

	if (urb->status)
		dev_warn(&urb->dev->dev, "led urb status %d received\n",
			 urb->status);

	if (*(kbd->leds) == kbd->newleds)
		return;

	*(kbd->leds) = kbd->newleds;
	kbd->led->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
		err_hid("usb_submit_urb(leds) failed");
}

这里的控制传输完成函数也是个累赘?每次有LED事件上报的话,那么控制传输urb就自动提交了。那么,*(kbd->leds)== kbd->newleds 必然是相等的,除非又有新的事件上报了,但是新事件上报时,在usb_kbd_event 函数里urb不就自动提交了么? 会出现不相等的情况?

此篇文章存在诸多疑问,如果有大神看到,还请解答一下。

时间: 2024-10-05 20:35:55

USB驱动——键盘驱动(控制传输)的相关文章

【驱动】USB驱动实例&#183;串口驱动&#183;键盘驱动【转】

转自:http://www.cnblogs.com/lcw/p/3159370.html Preface USB体系支持多种类型的设备. 在 Linux内核,所有的USB设备都使用 usb_driver结构描述. 对于不同类型的 USB设备,内核使用传统的设备驱动模型建立设备驱动描述,然后映射到 USB设备驱动,最终完成特定类型的 USB设备驱动 USB驱动·入门:http://infohacker.blog.51cto.com/6751239/1226257 USB串口驱动 USB串口驱动关键

usb键鼠驱动分析【钻】

本文转载自:http://blog.csdn.net/orz415678659/article/details/9197859 一.鼠标 Linux下的usb鼠标驱动在/drivers/hid/usbhid/usbmouse.c中实现 1.加载初始化过程 1.1模块入口 [cpp] view plain copy module_init(usb_mouse_init); 1.2初始化函数 [cpp] view plain copy static int __init usb_mouse_init

第十八篇:融汇贯通--谈USB Video Class驱动

USB Video Class驱动是WINDOWS系统包含的一个针对于USB VIDEO 类的驱动程序. 好多工程师都做过USB VIDEO设备端的开发, 主要的工作内容为: 使用FIRMWARE,或者LIINUX GADGET驱动程序, 构建USB VIDEO设备的描述符, 通过这些描述符的TOPOLOGY关系, 让系统了解,设备所支持的某些控制, 支持的视频格式, 系统驱动通过对这些描述符的解释, 构建自己的KS, FILTER, NODE, PIN网络, 属性, 方法, 最终, 又由这些F

MSM8909+Android5.1.1键盘驱动------概述

采用SN7326带智能指扫描的键盘扩展芯片,通过I2C接口来读取其状态寄存器的值就可知道是单按键还是多按键按下,可知道具体是哪个按键按下.然后键盘驱动调用input_event()上报linux的扫描码,比如KEY_RIGHT,然后传递给android框架层,流程如下图: 图1 下面介绍要实现键盘驱动所涉及的主要方方面面 1.     Input子系统 Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简

Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析

转: http://blog.csdn.net/zqixiao_09/article/details/51146149 前面学习了SDIO接口的WiFi驱动,现在我们来学习一下USB接口的WiFi驱动,二者的区别在于接口不同.而USB接口的设备驱动,我们前面也有学习,比如USB摄像头驱动.USB鼠标驱动,同样都符合LinuxUSB驱动结构: USB设备驱动(字符设备.块设备.网络设备) | USB 核心 | USB主机控制器驱动 不同之处只是在于USB摄像头驱动是字符设备,而我们今天要学习的Wi

通过修改i8042prt端口驱动中类驱动Kbdclass的回调函数地址,达到过滤键盘操作的例子

同样也是寒江独钓的例子,但只给了思路,现贴出实现代码 原理是通过改变端口驱动中本该调用类驱动回调函数的地方下手 //替换分发函数 来实现过滤 #include <wdm.h> #include <ntddk.h> #include <Ntddkbd.h> #include <windef.h> // Kbdclass驱动的名字 #define KBD_DRIVER_NAME L"\\Driver\\Kbdclass" //ps2的端口驱动

Linux GPIO键盘驱动开发记录_OMAPL138

Linux GPIO键盘驱动开发记录_OMAPL138 Linux基本配置完毕了,这几天开始着手Linux驱动的开发,从一个最简单的键盘驱动开始,逐步的了解开发驱动的过程有哪些.看了一下Linux3.3内核文件下的driver目录,点开里面的C文件,感觉底层的Linux驱动机制还是很复杂的,还需要一段漫长时间的学习.现在开发的也不能说是叫做驱动,也只能说是驱动的应用,我们学习驱动也从应用逐步开始,往里面深入吧. 0.开发准备 内核源文件(当时我们编译内核时候的目录,很重要,编译驱动的时候需要依赖

Linux USB 鼠标输入驱动详解

平台:mini2440 内核:linux 2.6.32.2 USB设备插入时,内核会读取设备信息,接着就把id_table里的信息与读取到的信息做比较,看是否匹配,如果匹配,就调用probe函数.USB设备拔出时会调用disconnect函数.URB在USB设备驱动程序中用来描述与USB设备通信时用到的基本载体和核心数据结构. URB(usb request block)处理流程: ①USB设备驱动程序创建并初始化一个访问特定USB设备特定端点的urb并提交给USB core. ②USB cor

编写NDIS驱动,完全控制网卡收发报文

在windows上面,利用网卡做自定义报文的收发,Winpcap是唯一选择,目前自己编写驱动来实现相关功能的基本找不到. Winpcap对于接收的报文只是复制,并不阻断报文向操作系统提交,因此还是影响了操作系统,导致操作系统时不时的发出一些报文来,而这些报文又可能会干扰我们的测试过程,为了突破这个限制,我重新实现了相关的驱动,做到了以下几个功能: 1  和Winpcap一样出色的报文接收和发送功能.函数接口类似,原来的代码不用大修改. 2  接收后的报文不会转交到操作系统. 3  操作系统发出的