USB驱动初体验

前言

前一篇文章(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驱动的编写步骤。后面可能还需要完善,目前就写到这里吧。。。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值