USB驱动——键盘驱动(控制传输)

25 篇文章 41 订阅
11 篇文章 11 订阅

    本文以 usbkbd.c 为例,分析 usb 键盘驱动程序。

static int __init usb_kbd_init(void)
{
	int result = usb_register(&usb_kbd_driver);
	if (result == 0)
		printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
				DRIVER_DESC "\n");
	return result;
}
static struct usb_driver usb_kbd_driver = {
	.name =		"usbkbd",
	.probe =	usb_kbd_probe,
	.disconnect =	usb_kbd_disconnect,
	.id_table =	usb_kbd_id_table,
};
    还是来看一下 id_table ,与鼠标相比,仅仅是协议不一样。

static struct usb_device_id usb_kbd_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_KEYBOARD) },
	{ }						/* Terminating entry */
};

    下面来看probe函数

static int usb_kbd_probe(struct usb_interface *iface,
			 const struct usb_device_id *id)
{
	/* 获得usb_device */
	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;
	
	/* 如果当前设置的端点数量不是1,那么错误,返回 */
	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();
	
	/* 
		kbd->irq = usb_alloc_urb(0, GFP_KERNEL)
		kbd->led = usb_alloc_urb(0, GFP_KERNEL)
		kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)
		kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)
		kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma)
	*/
	if (usb_kbd_alloc_mem(dev, kbd))
		goto fail2;

	/* 填充键盘结构体。以及一些字符串 */
	kbd->usbdev = dev;
	kbd->dev = input_dev;

	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));
	strlcpy(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);
	
	/* 设置它支持的事件类型和具体事件 1、按键类事件 2、LED灯(大小写灯等)3、重复上报*/
	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);

	/* 对于LED类型的事件,首先会调用到dev->event 然后再调用事件处理层的event */
	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;
	
	/* 
	 * 	bit7 	控制传输 data 阶段的方向 0 主机到设备 1设备到主机,	这里 0 主机到设备
	 *	bit5-6	表示 request 类型,是标准的还是厂家定义的,			这里为hid class定义
	 *	bit0-4	表示这个请求针对的是设备、接口还是端点 ,			这里是接口
	 */
	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;	// 0x01 << 5 | 
	
	/*
		#define USB_REQ_GET_STATUS		0x00
		#define USB_REQ_CLEAR_FEATURE		0x01
		#define USB_REQ_SET_FEATURE		0x03
		#define USB_REQ_SET_ADDRESS		0x05
		#define USB_REQ_GET_DESCRIPTOR		0x06
		#define USB_REQ_SET_DESCRIPTOR		0x07
		#define USB_REQ_GET_CONFIGURATION	0x08
		#define USB_REQ_SET_CONFIGURATION	0x09
		#define USB_REQ_GET_INTERFACE		0x0A
		#define USB_REQ_SET_INTERFACE		0x0B
		#define USB_REQ_SYNCH_FRAME		0x0C
	*/
	kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION;
	
	/* request 的参数 */
	kbd->cr->wValue = cpu_to_le16(0x200);
	
	/* bRequestType 中,针对接口、端点时,它表示那个接口或端点 */
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
	
	/* data阶段数据长度 */
	kbd->cr->wLength = cpu_to_le16(1);
	
	/* 
	static inline void usb_fill_control_urb(
					struct urb *urb,
					struct usb_device *dev,
					unsigned int pipe,
					unsigned char *setup_packet,
					void *transfer_buffer,
					int buffer_length,
					usb_complete_t complete_fn,
					void *context) 
	*/
	/* 这里使用的是默认端点0 */
	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),(void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd);
	kbd->led->setup_dma = kbd->cr_dma;
	kbd->led->transfer_dma = kbd->leds_dma;
	kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);

	error = input_register_device(kbd->dev);

	usb_set_intfdata(iface, kbd);
	return 0;
}
    这里与鼠标驱动程序相比,多了一个控制传输过程,而且,这个控制传输的 bRequestType 中说明了这个传输不是标准的请求,而是Class,我们的键盘是HID类型,因此还要看USB HID协议中,关于这个请求是如何定义的,才能知道wValue 、wIndex等是什么意思(参考:http://blog.csdn.net/leo_wonty/article/details/6721214),这就是就将knd->leds 内的一字节数据发送给从设备。在鼠标驱动程序中,中断 urb 是在open函数中提交的,这里也不例外。

static int usb_kbd_open(struct input_dev *dev)
{
	struct usb_kbd *kbd = input_get_drvdata(dev);

	kbd->irq->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->irq, GFP_KERNEL))
		return -EIO;

	return 0;
}
    中断传输完成之后会调用完成函数,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;
	}
	//报告usb_kbd_keycode[224..231]8按键状态
	//KEY_LEFTCTRL,KEY_LEFTSHIFT,KEY_LEFTALT,KEY_LEFTMETA,
	//KEY_RIGHTCTRL,KEY_RIGHTSHIFT,KEY_RIGHTALT,KEY_RIGHTMETA
	for (i = 0; i < 8; i++)
		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
	//若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下
	for (i = 2; i < 8; i++) {
		//获取键盘离开的中断
			//同时没有该KEY的按下状态
		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]);
		}
		//获取键盘按下的中断
			//同时没有该KEY的离开状态
		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) released.\n",
					 kbd->new[i]);
		}
	}

	input_sync(kbd->dev);			//同步设备,告知事件的接收者驱动已经发出了一个完整的报告

	memcpy(kbd->old, kbd->new, 8);	//防止未松开时被当成新的按键处理

resubmit:
	i = usb_submit_urb (urb, GFP_ATOMIC);
	if (i)
		hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",
			kbd->usbdev->bus->bus_name,
			kbd->usbdev->devpath, i);
}
    这里,都是上报的按键类事件,我们在前边的Probe函数中,设置了输入设备支持按键类事件,还有一个LED类事件,但是搜遍代码也没有找到,LED类事件是在哪里上报的。还有,probe函数中定义了一个dev->event函数,在浏览资料时发现,有些人说在input_event时,调用事件处理层的event函数的同时会调用dev->event,我认为这种说法是不正确的,看过input_event代码的同学应该不难发现,只有上报LED类等事件的时候才会触发dev->event,我们这里单纯上报的按键类事件并不会触发dev->event 。那么,probe 函数里定义的 dev->event 函数岂不是成了摆设么?确实,我暂时没发现它有什么用。手头没有usb键盘,这个后边实验证实。

static int usb_kbd_event(struct input_dev *dev, unsigned int type,
			 unsigned int code, int value)
{
	struct usb_kbd *kbd = input_get_drvdata(dev);

	if (type != EV_LED)
		return -1;

	kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
		       (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |
		       (!!test_bit(LED_NUML,    dev->led));

	if (kbd->led->status == -EINPROGRESS)
		return 0;

	if (*(kbd->leds) == kbd->newleds)
		return 0;

	*(kbd->leds) = kbd->newleds;
	kbd->led->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
		err_hid("usb_submit_urb(leds) failed");

	return 0;
}
    这里的 dev->led 记录的是led的状态,比如,我们上报一个LED事件时,input_event 中会将对应的事件记录在 dev->led 中。这里检测LED事件是否发生,然后通过控制传输将1字节数据传送给usb键盘,Usb键盘的灯相应做出改变。但是,说到底,代码里没有上报过LED类事件,一切都是白扯。
static void usb_kbd_led(struct urb *urb)
{
	struct usb_kbd *kbd = urb->context;

	if (urb->status)
		dev_warn(&urb->dev->dev, "led urb status %d received\n",
			 urb->status);

	if (*(kbd->leds) == kbd->newleds)
		return;

	*(kbd->leds) = kbd->newleds;
	kbd->led->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
		err_hid("usb_submit_urb(leds) failed");
}
    这里的控制传输完成函数也是个累赘?每次有LED事件上报的话,那么控制传输urb就自动提交了。那么,*(kbd->leds)== kbd->newleds 必然是相等的,除非又有新的事件上报了,但是新事件上报时,在usb_kbd_event 函数里urb不就自动提交了么? 会出现不相等的情况?


    此篇文章存在诸多疑问,如果有大神看到,还请解答一下。








  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值