title: usb输入子系统写程序
tags: linux
date: 2018/12/18/ 18:46:04
toc: true
usb输入子系统写程序
目标:usb鼠标模拟一个键盘,左键L
,右键S
,中键enter
参考: drivers/hid/usbhid/usbmouse.c
这是自带的USB鼠标驱动
入口函数
static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver);
if (retval == 0)
info(DRIVER_VERSION ":" DRIVER_DESC);
return retval;
}
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
小结
-
构造匹配的
id_table
以供usb总线驱动的match
匹配,然后执行驱动的probe
struct usb_device_id xxx[]={}
-
构造
probe
来达到初始化相关操作 -
usb传输数据使用
urb = usb request block
也就是usb 请求块来传输,数据传输三要素源+长度+目的
-
源=Usb设备的端点,也就是包含了usb设备地址+端点地址+端点类型
struct usb_host_interface *interface; interface = intf->cur_altsetting; //获得端点地址 endpoint = &interface->endpoint[0].desc; //usb_rcvintpipe 构造了设备地址+端点地址 pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //PIPE_INTERRUPT 表示端点的类型 #define usb_rcvintpipe(dev,endpoint) ((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN) //__create_pipe 包含了设备地址和端点的地址 static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint) { return (dev->devnum << 8) | (endpoint << 15); }
-
长度从usb的端点描述符中获取
len = endpoint->wMaxPacketSize;
-
目的缓存需要是一块连续的物理地址,需要分配
static char *usb_buf; //目的buf虚拟地址 static dma_addr_t usb_buf_phys; //目的buf实际获得的地址 usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
-
-
设置这个
URB
,首先分配一块空间,然后填充相关数据static struct urb *uk_urb; uk_urb = usb_alloc_urb(0, GFP_KERNEL); // urb 分配得到的urb // pipe usb设备端点 // transfer_buffer 传输的目的地址,虚拟地址 // buffer_length 传输的长度 // complete_fn 收到传输数据的回调函数 // context 可以为null // interval 轮询间隔,在端点描述符中查询 static inline void usb_fill_int_urb (struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval) // 需要设置物理地址和标志 uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
-
启动这个
urb
,也就是说每次传输前都要先操作一遍,每次数据获取后也需要重新启动usb_submit_urb(uk_urb, GFP_KERNEL);
-
如何获取数据上报? 在
urb
的完成回调函数中实现,usb_buf
是urb
中的虚拟地址,获取数据后需要重新启动urb
static void usbxxx_irq(struct urb *urb) { for (i = 0; i < len; i++) { printk("%02x ", usb_buf[i]); } usb_submit_urb(uk_urb, GFP_KERNEL); }
-
如何上报到按键?这里用到以前的输入子系统,也就是在
probe
注册input_dev
,然后在urb
中的回调函数中获取到函数后上报input_dev = input_allocate_device(); /* b. 设置 */ /* b.1 能产生哪类事件 */ input_dev->evbit[0]=0; set_bit(EV_KEY, input_dev->evbit); set_bit(EV_REP, input_dev->evbit); /* b.2 能产生哪些事件 */ set_bit(KEY_L, input_dev->keybit); set_bit(KEY_S, input_dev->keybit); set_bit(KEY_ENTER, input_dev->keybit); /* c. 注册 */ input_register_device(input_dev);
内核修改
去除内核原有的模块Device Drivers > HID Devices > USB Human Interface Device (full HID) support
怎么写代码
usb总线设备也是一种总线设备驱动,总体上可以分为两个部分,左边部分是usb总线程序做好了,我们需要编写右边的usb_driver
部分,也就是分配设置注册usb_driver
类型匹配
首先是usb子系统会调用match来匹配id_table
中的类型后会调用probe
。所以先来确定这个类型匹配,也就是设备描述符的匹配。例子如下:
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)
cl:接口类,我们USB鼠标为HID类,所以填入0X03,也就是USB_INTERFACE_CLASS_HID
sc:接口子类为启动设备,填入USB_INTERFACE_SUBCLASS_BOOT
pr:接口协议为鼠标协议,填入USB_INTERFACE_PROTOCOL_MOUSE
//ch9.h
/* 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));
也可以单独匹配某个厂家ID使用USB_DEVICE
#define USB_DEVICE(vend,prod)
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend),
.idProduct = (prod)
可以在usb.h
查看这个匹配的大类
#define USB_DEVICE_ID_MATCH_DEVICE
(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE
(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION
(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO
(USB_DEVICE_ID_MATCH_DEV_CLASS |
USB_DEVICE_ID_MATCH_DEV_SUBCLASS |
USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO
(USB_DEVICE_ID_MATCH_INT_CLASS |
USB_DEVICE_ID_MATCH_INT_SUBCLASS |
USB_DEVICE_ID_MATCH_INT_PROTOCOL)
#define USB_DEVICE(vend,prod)
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend),
.idProduct = (prod)
#define USB_DEVICE_VER(vend,prod,lo,hi)
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
.idVendor = (vend), .idProduct = (prod),
.bcdDevice_lo = (lo), .bcdDevice_hi = (hi)
#define USB_DEVICE_INFO(cl,sc,pr)
.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, .bDeviceClass = (cl),
.bDeviceSubClass = (sc), .bDeviceProtocol = (pr)
#define USB_INTERFACE_INFO(cl,sc,pr)
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl),
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
probe
参考usb_mouse_probe
可以看到有以下操作
- 分配一个
input_dev
结构体 - 设置事件
- 注册输入子系统
- 硬件相关操作
disconnect
拔掉USB会调用该函数,可以加入打印先
程序设计
1th匹配probe
验证id
匹配后执行probe
函数,这里并没有加入输入子系统
#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>
//struct input_dev *input_dev;
static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
//input_dev = input_allocate_device();
struct usb_device *dev = interface_to_usbdev(intf);
printk("found usbmouse!
");
printk("bcdUSB = %x
", dev->descriptor.bcdUSB);
printk("VID = 0x%x
", dev->descriptor.idVendor);
printk("PID = 0x%x
", dev->descriptor.idProduct);
return 0;
}
static void myusb_disconnect(struct usb_interface *intf)
{
printk("disconnect usbmouse!
");
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
};
static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
}
static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
}
module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");
测试
烧录去除Device Drivers > HID Devices > USB Human Interface Device (full HID) support
的内核,加载模块,显示如下
# insmod myusb_1th.ko
new id 0x6019 !
found usbmouse!
bcdUSB = 110
VID = 0x93a
PID = 0x2510
usbcore: registered new interface driver myusb
2th 获取usb数据
- 数据鼠标上报的数据,在这里其实并不需要输入子系统的参与,输入子系统是为了上报按键的数据,而不是为了获取鼠标的数据
- 可以进一步在probe中判断是否为鼠标设备,可以获取接口描述符,获取端点个数(除了端点0)
- 如何上报数据? usb这里使用
urb usb request block
也就是usb 请求块来传输数据
测试后可以看到打印的数据,可以根据这个判断转换为按键值左键L
,右键S
,中键ENTER
data cnt 187: 01 00 00 00
data cnt 188: 00 00 00 00
data cnt 189: 02 00 00 00
data cnt 190: 00 00 00 00
data cnt 191: 04 00 00 00
data cnt 192: 00 00 00 00
上报次数 | 指示了按键 | 其他值 |
---|---|---|
data cnt 187: | 01 左键 | 00 00 00 |
data cnt 188: | 00 | 00 00 00 |
data cnt 189: | 02 右键 | 00 00 00 |
data cnt 190: | 00 | 00 00 00 |
data cnt 191: | 04 中键 | 00 00 00 |
data cnt 192: | 00 | 00 00 00 |
完整的程序
#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 int len;
static char *usb_buf; //目的buf虚拟地址
static dma_addr_t usb_buf_phys; //目的buf实际获得的地址
struct input_dev *input_dev;
static struct urb *uk_urb;
static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x17EF,0x6019) } /* right id */
//{USB_DEVICE(0x15EF,0x6055) } /* err */
};
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("
");
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int pipe;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *interface;
printk("new id 2 !
");
printk("found usbmouse!
");
printk("bcdUSB = %x
", dev->descriptor.bcdUSB);
printk("VID = 0x%x
", dev->descriptor.idVendor);
printk("PID = 0x%x
", dev->descriptor.idProduct);
interface = intf->cur_altsetting;
//data translate for urb
//源地址构造
//获得端点地址
endpoint = &interface->endpoint[0].desc;
//usb_rcvintpipe 构造了设备地址+端点地址
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 myusb_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
printk("disconnect usbmouse!
");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
};
static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
}
static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
}
module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");
3th 输入子系统上报按键
加入输入子系统上报按键事件,测试如下,这里只是输出到显示,并没有执行命令,如果要执行命令需要定位到标准输入
# cat /dev/tty0
ls
llllllllllllllllllll
llllllllllll
或者使用hexdump
来显示
# hexdump /dev/event0
【按键值】26 左键 1f右键 1C中键
00001a0 16a7 0000 c426 000b 0001 0026 0001 0000
00001b0 16a7 0000 c430 000b 0000 0000 0000 0000
00001c0 16a7 0000 3b15 000d 0001 0026 0000 0000
00001d0 16a7 0000 3b1d 000d 0000 0000 0000 0000
00001e0 16a8 0000 e834 0005 0001 001f 0001 0000
00001f0 16a8 0000 e83e 0005 0000 0000 0000 0000
0000200 16a8 0000 fb66 0007 0001 001f 0000 0000
0000210 16a8 0000 fb6e 0007 0000 0000 0000 0000
0000220 16a8 0000 f0c1 000e 0001 001c 0001 0000
0000230 16a8 0000 f0cc 000e 0000 0000 0000 0000
0000240 16a9 0000 8319 0001 0001 001c 0000 0000
0000250 16a9 0000 8322 0001 0000 0000 0000 0000
//include/linux
#define KEY_L 38
#define KEY_S 31
#define KEY_ENTER 28
或者使用重定位按键
# exec 0</dev/tty1
# ls
Makefile myusb.ko myusb.o myusb_1th.mod.c
Module.symvers myusb.mod.c myusb_1th.c myusb_1th.mod.o
myusb.c myusb.mod.o myusb_1th.ko myusb_1th.o
完整的程序如下
#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 int len;
static char *usb_buf; //目的buf虚拟地址
static dma_addr_t usb_buf_phys; //目的buf实际获得的地址
struct input_dev *input_dev;
static struct urb *uk_urb;
static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x17EF,0x6019) } /* right id */
//{USB_DEVICE(0x15EF,0x6055) } /* err */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
#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("
");
#else
static unsigned char pre_val;
// bit0 left_button bit1=right_button bit2=mid_button
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左键发生了变化 */
input_event(input_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(input_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(input_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(input_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(input_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(input_dev);
}
pre_val = usb_buf[0];
#endif
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int pipe;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *interface;
printk("new id 2 !
");
printk("found usbmouse!
");
printk("bcdUSB = %x
", dev->descriptor.bcdUSB);
printk("VID = 0x%x
", dev->descriptor.idVendor);
printk("PID = 0x%x
", dev->descriptor.idProduct);
interface = intf->cur_altsetting;
input_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
input_dev->evbit[0]=0;
set_bit(EV_KEY, input_dev->evbit);
set_bit(EV_REP, input_dev->evbit);
/* b.2 能产生哪些事件 */
set_bit(KEY_L, input_dev->keybit);
set_bit(KEY_S, input_dev->keybit);
set_bit(KEY_ENTER, input_dev->keybit);
/* c. 注册 */
input_register_device(input_dev);
//data translate for urb
//源地址构造
//获得端点地址
endpoint = &interface->endpoint[0].desc;
//usb_rcvintpipe 构造了设备地址+端点地址
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 myusb_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
printk("disconnect usbmouse!
");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(input_dev);
input_free_device(input_dev);
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
};
static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
}
static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
}
module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");