前言
前一篇文章(USB驱动的概念介绍),我们对USB驱动有了一个大概的认识。现在就来编写程序加深一下印象。
正文
下面我们将要编写一个简单的USB驱动程序,接收USB鼠标的数据后,模拟键盘的输入。比如,左击鼠标为键盘输入"L",右键鼠标为键盘输入"S",按下鼠标的滚轮为键盘输入“ENTER”。
下面先给出代码再解释:
/*
* 参考drivers/hid/usbhid/usbmouse.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 struct input_dev *uk_dev = NULL;
static char *usb_buf = NULL;
static dma_addr_t usb_buf_phy;
static int len;
struct urb *uk_urb = NULL;
/*
只要我们的USB设备的接口描述符的下面三项
符合usbmouse_as_key_id_table中的信息,就能匹配
struct usb_interface_descriptor {
...
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
...
} __attribute__ ((packed));
*/
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(vend,prod)},//这个宏可以规定只支持某vend和prod的设备
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
unsigned char data;
#if 0
int i;
static int cnt = 0;
printk("data cnt %d\n", ++cnt);
for (i = 0; i < len; i++)
printk("%02x ", usb_buf[i]);
printk("\n");
#endif
/*USB鼠标数据含义:
*data[0]: byte0-固定为2
* bit9 :左键,1-按下,0-松开
* bit10:右键,1-按下,0-松开
* bit11:中键,1-按下,0-松开
*/
/*左键*/
//printk("pre_val = %02x\n", pre_val);
if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0))){
//printk("left\n");
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
/*右键*/
if ((pre_val & (1<<1)) != (usb_buf[1] & (1<<1))){
//printk("right\n");
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
/*中键*/
if ((pre_val & (1<<2)) != (usb_buf[1] & (1<<2))){
//printk("enter\n");
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[1];
/*重新提交urb*/
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int error, pipe;
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct usb_device *dev = interface_to_usbdev(intf);
interface = intf->cur_altsetting;
/* 除了0端点之外还有多少个端点 */
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
endpoint = &interface->endpoint[0].desc;
/* 判断是不是中断型的输入端点,输入是相对于主机而言,
*也就是我的鼠标相对于主机是输入设备
*/
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
printk("found usbmouse\n");
printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
printk("idProduct = 0x%x\n", dev->descriptor.idProduct);
printk("idVendor = 0x%x\n", dev->descriptor.idVendor);
/* a. 分配一个input_dev */
uk_dev = input_allocate_device();
if (!uk_dev)
return -ENOMEM;
/* b. 设置 */
/* b1. 产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
/* b2. 产生 哪些事件*/
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
error = input_register_device(uk_dev);
if (error){
printk("input device register failed\n");
goto fail0;
}
/* d. 硬件相关操作 */
/* 数据传输3要素 :源、目的、长度*/
/* 源:USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度*/
len = endpoint->wMaxPacketSize;
/* 目的*/
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phy);
if (!usb_buf)
goto fail1;
/* 使用上面设置好的3要素 */
/* urb是USB请求块的意思 :usb request block*/
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!uk_urb)
goto fail2;
/* 是用"3要素"设置urb */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len,
usbmouse_as_key_irq, /* USB控制器查询到数据后调用的函数 */
NULL,
endpoint->bInterval); /* USB控制器查询的时间间隔 */
uk_urb->transfer_dma = usb_buf_phy;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
fail2:
usb_buffer_free(dev, len, usb_buf, usb_buf_phy);
fail1:
input_unregister_device(uk_dev);
fail0:
input_free_device(uk_dev);
}
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_submit_urb*/
usb_free_urb(uk_urb); /*usb_alloc_urb*/
usb_buffer_free(dev, len, usb_buf, usb_buf_phy);
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)
{
/*3. 注销*/
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
(1)
我们在上一篇文章(USB驱动的概念介绍)的最后介绍过,USB设备总线也是Linux系统中一个重要的总线,而且,系统也为我们搭好了一个框架,我们只要为自己的USB硬件设备编写好驱动程序,然后注册进系统就可以了,所以init函数就是用usb_register函数将我们自己定义好的usb_driver结构体注册进系统:
/* 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;
}
(2)
一般来说,注册时系统会根据usb_driver和usb_interface的id_table判断是否匹配,匹配的话就调用probe函数,也就是我们这里的usbmouse_as_key_probe函数(usb_interface是在接入USB设备后,系统自动注册的)。我们程序的id_table就描述了支持哪些设备:“类”是“HID”类;“子类”是“BOOT”;“协议”是“MOUSE”。
/*
只要我们的USB设备的接口描述符的下面三项
符合usbmouse_as_key_id_table中的信息,就能匹配
设备描述符:
struct usb_interface_descriptor {
...
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
...
} __attribute__ ((packed));
展开我们的id_table如下,实际用了一个宏来赋值:
#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
上面表明了所支持的USB设备的“接口描述符”里面,所描述的“类”必须是是“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(vend,prod)},//这个宏可以规定只支持某vend和prod的设备
{ } /* Terminating entry */
};
(3)
匹配成功后,我们就会调用probe注册函数。因为我们是用鼠标模拟按键类事件,所以一开始我们是分配并注册一个input_dev结构体。可以参考我以前的一篇文章:input子系统的架构分析及应用。描述了怎么注册一个按键类事件。下面我们关注一下怎么获取USB的数据,其中有3要素:
/* d. 硬件相关操作 */
/* 数据传输3要素 :源、目的、长度*/
/* 源:USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度*/
len = endpoint->wMaxPacketSize;
/* 目的*/
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phy);
if (!usb_buf)
goto fail1;
/* 使用上面设置好的3要素 */
/* urb是USB请求块的意思 :usb request block*/
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!uk_urb)
goto fail2;
/* 是用"3要素"设置urb */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len,
usbmouse_as_key_irq, /* USB控制器查询到数据后调用的函数 */
NULL,
endpoint->bInterval); /* USB控制器查询的时间间隔 */
uk_urb->transfer_dma = usb_buf_phy;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
以前我们是通过按按键,产生中断事件来上报数据,这里我们由USB控制器通过不停的轮询来获取数据(bInterval定义了轮询的时间间隔)。而获取了数据后,我们就会调用usbmouse_as_key_irq函数去解析数据。
(4)
在usbmouse_as_key_irq函数中,我们是通过usb_buf(USB3要素中的:目的)中读出数据的。这里要说明一下,每个鼠标产生的数据是不一样的,比如我的数据含义就如下所示:
/*USB鼠标数据含义:
*data[0]: byte0-固定为2
* bit9 :左键,1-按下,0-松开
* bit10:右键,1-按下,0-松开
* bit11:中键,1-按下,0-松开
*/
结语
写到这里,就简单介绍了一个非常简单的USB驱动的编写步骤。后面可能还需要完善,目前就写到这里吧。。。