你好!这里是风筝的博客,
欢迎和我一起交流。
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.
",
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.
",
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.
",
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.
",
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
#include
#include
#include
#include
#include
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;icontext;
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
");
/* 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
");
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