嵌入式Linux驱动笔记(四)------USB键盘驱动程序

本文深入解析了Linux Kernel 4.4.17中USB键盘驱动的设计与实现过程,重点介绍了usb_kbd_probe和usb_kbd_irq函数的工作原理,并提供了一个简易的USB键盘驱动程序示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你好!这里是风筝的博客,

欢迎和我一起交流。



Kernel版本为4.4.17.


编写USB键盘的驱动,可以参考Kernel里的usbkbd.c这个文件.

我越发觉得驱动都是按套路来的.......流程都差不多一样.


在这个文件里,最主要就是看usb_kbd_probe函数和usb_kbd_irq函数了。

在文件最开头有个数组:

static const unsigned char usb_kbd_keycode[256] = {
	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
	150,158,159,128,136,177,178,176,142,152,173,140
};
这个是USB键盘的码表,上报按键事件的时候需要用到。
接下来就是usb_kbd_probe函数了,就一点点内容,我都用注释写好了,还是比较简单的(其实注释也是我抄来的,///////捂脸)

static int usb_kbd_probe(struct usb_interface *iface,
			 const struct usb_device_id *id)
{
	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;

	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();
	if (!kbd || !input_dev)
		goto fail1;

	if (usb_kbd_alloc_mem(dev, kbd))
		goto fail2;
/* 填充 usb 设备结构体和输入设备结构体 */
	kbd->usbdev = dev;
	kbd->dev = input_dev;
	spin_lock_init(&kbd->leds_lock);
/*以"厂商名字 产品名字"的格式将其写入kbd->name*/
	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));
	strlcat(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);

	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);

	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;

	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	kbd->cr->bRequest = 0x09;
	kbd->cr->wValue = cpu_to_le16(0x200);
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
	kbd->cr->wLength = cpu_to_le16(1);
/*初始化控制URB*/
	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
			     (void *) kbd->cr, kbd->leds, 1,
			     usb_kbd_led, kbd);
	kbd->led->transfer_dma = kbd->leds_dma;
	kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	error = input_register_device(kbd->dev);
	if (error)
		goto fail2;

	usb_set_intfdata(iface, kbd);
	device_set_wakeup_enable(&dev->dev, 1);
	return 0;

fail2:	
	usb_kbd_free_mem(dev, kbd);
fail1:	
	input_free_device(input_dev);
	kfree(kbd);
	return error;
}

感觉主要的也没什么,就是:

选择interface.

获取描述符.

将endpoint设置为中断IN端点.

分配kbd、input_dev空间.

填充 usb 设备结构体和输入设备结构体 .

以"厂商名字 产品名字"的格式将其写入kbd->name.

设置上报输入子系统的事件类型.

设置上报输入子系统的事件有什么.

设置注册事件的处理、打开、关闭函数入口.

初始化中断URB.

初始化控制URB.

感觉就差不多了。

主要的还是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;
	}

	for (i = 0; i < 8; i++)
		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
	for (i = 2; i < 8; i++) {
		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]);
		}

		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) pressed.\n",
					 kbd->new[i]);
		}
	}

	input_sync(kbd->dev);

	memcpy(kbd->old, kbd->new, 8);

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

其中,每次中断接收的值都是8个字节,是这样的:

data0 data1 data2 data3 data4 data5 data6 data7 

一般,只按下一个键时,键值会出现在data2上。

如按下键盘a时,数据为:00 00 04 00 00 00 00 00.

a键松开时,数据为:00 00 00 00 00 00 00 00.

同时按下两个键时,第二个键的键值会出现在data3上。

如按下a时再按下b键,数据为:00 00 04 05 00 00 00 00.

所以能同时按下6个按键。

接下来就很好分析中断函数了:

for (i = 0; i < 8; i++)
		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);

这一句是用来检测修饰按键的,虽然我也不造修饰按键是什么......

其中,kbd->new为urb的缓冲区,在probe函数里有一句:usb_kbd_alloc_mem(dev, kbd),函数中为kbd->new分配8字节内存:kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma), 
并且在中断urb初始化函数中usb_fill_int_urb(kbd->irq, dev, pipe, kbd->new, (maxp > 8 ? 8 : maxp), usb_kbd_irq, kbd, endpoint->bInterval); 
即从端点里取得的数据会放在new指向的内存里面,最大为8字节.
接下来的才是真正的常用按键检测:

for (i = 2; i < 8; i++) {

		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]);
		}

		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) pressed.\n",
					 kbd->new[i]);
		}
	}
因为键值是在data2及其之后存放的,所以for循环从i=2开始查询。

for里面有两个if,第一个if是判断按键是否是松开的,第二个if是判断按键是否被按下的。

其中memscan函数是判断键值是否相等的,kbd->old存放的是上一次按键的数据,kbd->new是这一次按键的数据,memscan函数在string.c里,如下:

void *memscan(void *addr, int c, size_t size)
{
	unsigned char *p = addr;

	while (size) {
		if (*p == c)
			return (void *)p;
		p++;
		size--;
	}
  	return (void *)p;
}
很简单的一个c语言函数,主要就是判断是否相等而已。通过上一次数据和这一次数据的比较就知道按键是否被按下了。然后上报按键事件即可。
最后,memcpy(kbd->old, kbd->new, 8);就是把kbd->new的成员赋值给kbd->old,实现新旧数据存储。


好了。接下来写驱动程序就照葫芦画瓢就可以了:

/*参考usbkbd.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 const unsigned char usb_kbd_keycode[256] = {/*键值码表*/
	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
	150,158,159,128,136,177,178,176,142,152,173,140
};
static int len;
static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static struct urb *uk_urb;

static struct usb_device_id keyboard_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_KEYBOARD) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/
	{ }	/* Terminating entry */
};

void buffer_copy (char *usb_buf_source , char *usb_buf_target , int len)
{
	int i;
	for(i=0;i<len;i++)
	{
		usb_buf_target[i] = usb_buf_source[i];
	}
}

static void keyboard_irq(struct urb *urb)
{
	struct usb_kbd *kbd = urb->context;
	int i;
	static char usb_buf_pre_val[8];

	for (i = 2; i < 8; i++) {/*若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下*/ 
		if(usb_buf[i]!=usb_buf_pre_val[i])
		{
			if((usb_buf[i] ) ? 1 : 0)
				input_report_key(uk_dev, usb_kbd_keycode[usb_buf[i]],  1);
			else
				input_report_key(uk_dev, usb_kbd_keycode[usb_buf_pre_val[i]],  0);
		}		
	}
	
/*同步设备,告知事件的接收者驱动已经发出了一个完整的报告*/ 
	input_sync(uk_dev);

	buffer_copy(usb_buf , usb_buf_pre_val , 8);/*防止未松开时被当成新的按键处理*/
	
	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int keyboard_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*/
	interface = intf->cur_altsetting;
	/*获取端点描述符*/
	endpoint = &interface->endpoint[0].desc;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();    
	if ( !uk_dev)
		printk("allocate false \n");
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);/*键码事件*/
	set_bit(EV_REP, uk_dev->evbit);/*自动重覆数值*/;
	uk_dev->ledbit[0] = BIT(LED_NUML)/*数字灯*/ | BIT(LED_CAPSL)/*大小写灯*/ | BIT(LED_SCROLLL)/*滚动灯*/ | BIT(LED_COMPOSE) | BIT(LED_KANA);
	
	
	/* b.2 能产生哪些事件 */
	for (len = 0; len < 255; len++) 
		set_bit(usb_kbd_keycode[len], uk_dev->keybit);
	clear_bit(0, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);/*将endpoint设置为中断IN端点*/

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

	/* 目的: */
	//usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
	usb_buf = usb_alloc_coherent(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, keyboard_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);/*发送USB请求块*/
	
	return 0;
}

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

	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

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

/* 1. 分配/设置usb_driver */
static struct usb_driver keyboard_driver = {/*任何一个LINUX下的驱动都有个类似的驱动结构*/
	.name		= "my_keyboard",
	.probe		= keyboard_probe,/*驱动探测函数,加载时用到*/
	.disconnect	= keyboard_disconnect,
	.id_table	= keyboard_id_table,/*驱动设备ID表,用来指定设备或接口*/ 
};

static int keyboard_init(void)
{
	/* 2. 注册 */
	int result = usb_register(&keyboard_driver);
	if (result != 0) /*注册失败*/ 
		printk("register false \n");
	return 0;
}

static void keyboard_exit(void)
{
	usb_deregister(&keyboard_driver);	
}

module_init(keyboard_init);
module_exit(keyboard_exit);

MODULE_LICENSE("GPL");


其中kbd我没有用到,就不写了,实现了按键即可。

Makefile:

KERN_DIR = /work/system/linux-4.4.17

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= my_keyboard.o


最后make编译好,insmod my_keyboard.ko

插上键盘即可,usb键盘是热拔插的,随便插都没事。

cat /dev/tty1

即可看到在键盘按下的数据。


哦,忘记了,我是4.4.17的Kernel,usb键盘的驱动是没被编译进内核的,所以insmod自己写的驱动时不会出问题,其他的Kernel需要自己注意下,避免加载冲突了。

要是想用Kernel自带的驱动,在

Device Drivers  ---> HID support  ---> USB HID support  ---> < > USB HID transport layer

选中<*> USB HID transport layer即可。

估计以前的Kernel是

Device Drivers  ---> HID support  ---><*>  USB Human Interface Device (full HID) suppoort

默认是选中的,反正我现在的Kernel是 USB Human Interface Device (full HID) suppoort没有了的,估计就是变成了USB HID transport layer









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值