Linux & andriod 输入子系统

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

前面我们学习USB鼠标驱动的时候发现里面有个input_dev结构体,从名字上看可以知道这是个输入设备,所以今天来学习一下这个输入系统。


提示:以下是本篇文章正文内容,下面案例可供参考

一、输入系统是什么?

输入设备总类繁杂,包括按键,键盘,触摸屏,鼠标,摇杆等等,它们本身都是字符设备,不过内核为了能将这些设备的共性抽象出来,简化驱动的开发,建立了一个 Input 子系统。用户只需要根据内核提供的 input 子系统下提供的 API 函数接口,完成设备的注册即可

input 子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层

(1)硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来
编写。
(2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的
接口。
(3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序

在这里插入图片描述
左边就是最底层的具体设备,比如按键、USB 键盘/鼠标等,
中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,
最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用

二、相关知识

如何查看开发板的输入设备

在/sys/class 目录下有一个 input 子目录,如图 58.1.2.1 所示:
在这里插入图片描述
可以看到图中有even0~2,这个就是提供个用户使用的设备文件

查看输入设备的具体信息

在这里插入图片描述

如何查看输入打印消息

如果没有按键的话可以插入键盘

 hexdump /dev/input/eventx

在这里插入图片描述

在文件include\uapi\linux\input-event-codes.h中记录了有关码的信息

那么 tpye 等于 1,代表的就是按键事件,如下图所示:

在这里插入图片描述
code 等于 1c,我们把它换成 10 进制,就是 28,对应的就是回车按键,如下图所示:
在这里插入图片描述
value 等于 0,代表的就是按下,所以第三条信息代表的是按键按下

三、Linux下的输入驱动

前面说道,输入系统有三部分组成,输入驱动 、输入核心、事件,输入驱动一般是由驱动开发者编写,而核心层一般都是编写好的,所以这里我们先来了解一下核心层部分。

1.核心层

drivers/input/input.c 这个文件,input.c 就是 input 输入子系统的核心层,该文件有2513行:
在这里插入图片描述

input 核心层会向 Linux 内核注册一个字符设备,这个字符设备的主设备号是13,如下:
在这里插入图片描述
在这里插入图片描述

另外在里面也注册了一个Input类,我们可以在/sys/class目录下看到Input,

在这里插入图片描述
所以 input 子系统的所有设备主设备号都为 13,在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可

2.输入驱动

由input.c可知,我们只需要在我们的输入驱动中注册一个输入设备即可,剩下的事由核心层完成,即我们需要做的就是围绕input_dev结构体展开编写代码。

input_dev 结构体

在使用 input 子系统的时候我们只需要注册一个 input 设备即可,input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中

在这个结构体里面定义丙炔归纳了各种设备的信息,例如按键、相对设备、绝对设备、杂项设备、LED、声音设备、强制反馈设备、开关设备等

struct input_dev {
	const char *name;//设备名称
	const char *phys;//奢比在系统的物理路径
	const char *uniq;//统一的ID
	struct input_id id;//设备id

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//事件
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//按键
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//相对设备
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//绝对设备
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//杂项设备
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];//led
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//声音设备
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//强制反馈设备
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//开关设备

	unsigned int hint_events_per_packet;

	unsigned int keycodemax;//按键码的最大值
	unsigned int keycodesize;//按键码的大小
	void *keycode;//按键码

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	struct ff_device *ff;

	unsigned int repeat_key;
	struct timer_list timer;

	int rep[REP_CNT];

	struct input_mt *mt;

	struct input_absinfo *absinfo;

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];
	unsigned long led[BITS_TO_LONGS(LED_CNT)];
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];
//设备相关的操作
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

	struct input_handle __rcu *grab;

	spinlock_t event_lock;
	struct mutex mutex;

	unsigned int users;
	bool going_away;

	struct device dev;

	struct list_head	h_list;
	struct list_head	node;

	unsigned int num_vals;
	unsigned int max_vals;
	struct input_value *vals;

	bool devres_managed;
};

evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件
中,事件类型如下:

#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件 */
#define EV_ABS 0x03 /* 绝对坐标事件 */
#define EV_MSC 0x04 /* 杂项(其他)事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */

比如使用到按键,那么就需要注册 EV_KEY 事件,如果要使用连按功能的话还需要注册 EV_REP 事件

evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到 keybit,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些键值定义在 include/uapi/linux/input.h 文件中,按键值如下:
在这里插入图片描述

1.内核给我们提供了申请、取消申请结构体的函数:

struct input_dev *input_allocate_device(void) //申请 input_dev 结构体
void input_free_device(struct input_dev *dev) //注销 input_dev 结构体

申请函数里面没有参数,内核会直接分配一个input_dev 结构体空间回来,我们只需要再填充该结构体即可,比如:根据自己的设备来指定事件类型和事件值,比如按键设备的事件类型是 evbit,事件值是 keybit

三种设置事件和事件值具体设置方式如下:

10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
12 __set_bit(EV_REP, inputdev->repbit); /* 重复事件 */
13 __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
14 /************************************************/
15
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/

2.注册设备

input_dev 结构体初始化完成后,使用 input_register_device 函数向 Linux 内核注册input_dev 设备
在这里插入图片描述
在这里插入图片描述

注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的
input_dev,input_unregister_device 函数原型如下:
在这里插入图片描述

3.上报事件

在 input 设备驱动中申请、注册完成 input_dev 结构体后,还不能正常使用 input 子系统因为 input设备是输入一些信息,但是 Linux 内核还不清楚输入的信息表示什么意思,有什么作用,所以我们需要驱动获取到具体的输入值,或者说输入事件,然后将输入事件上报给Linux 内核
常用的 API 函数
input_event 函数:用于上报指定的事件以及对应的值
这个函数是一个通用的上报函数,由用户指定通道的类型
在这里插入图片描述

input_report_key上报按键事件
可以看出该函数就是对input_event函数进行了封装,指定为按键类型

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}

同样的还有一些其他事件对应的上报函数:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)//相对事件
void input_report_abs(struct input_dev *dev, unsigned int code, int value)//绝对事
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)//反馈事
void input_report_switch(struct input_dev *dev, unsigned int code, int value)//开关时间
void input_mt_sync(struct input_dev *dev)

4.同步事件

当我们上报完之后,还需要同步一次上报事件,告诉内核,上报全部完成。
input_sync 函数用来告诉 Linux 内核 input 子系统上报结束。input_sync 函数本质上是上报一个同步事件

void input_sync(struct input_dev *dev)

可以看出整个流程并不是特别难,流程图如下:
在这里插入图片描述

3.用户层

当我们编写好驱动之后,会出现 /dev/input/eventx设备文件提供给我们的应用层使用,我们应用程序只需要在程序重使用结构体input_event即可获得对应的typecodevalue
input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

struct input_event
{
	struct timeval time;
	 __u16 type; 
	 __u16 code;
	 __s32 value;
};

 type:事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
 code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0KEY_1 等等这些按键。此成员变量为 16 位。
 value:值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。


四、andriod下的输入系统

需要完成两个工作:

1.就是上面的Linux驱动的编写
2.修改kl和kcm文件

具体介绍如下:
在这里插入图片描述
在这里插入图片描述

下面针对这三个文件来详细讲解:

头文件:input.h

在第三小节我们介绍了了一个很重要的结构input_dev就是在这个头文件里面,除了这个结构体还有上报函数,比如事件上报函数input_event、按键上报函数等都是在这个头文件里面,这里就不过多介绍。

核心层文件:input.c

在第三小节的1点我们也稍微的介绍了一下这个文件,这里再具体展开讲解(建议自己找到对应驱动文件一起查看):

1.input_init函数和input_exit函数

这两个函数主要是实现input设备的初始化和注销工作,具体如下:
在这里插入图片描述
在这里插入图片描述

2.input_allocate_device函数

这个函数是不是觉得很眼熟,在如下目录中,这个就是我们用来申请input_dev结构体的函数,从这里也可以看出,我们写驱动就是调用别人写好的核心层的代码进行操作
在这里插入图片描述

在这里插入图片描述
有这个申请结构体的函数,自然在该文件中也就有回收结构体、注册结构体到内核的函数,这里就不一一列举。

event.c文件

这个文件是和事件有关的文件,

五、键盘驱动分析

在之前的章节中,我们学习了USB的相关知识,并且也分析了鼠标的驱动,这里我们就以键盘的驱动为准,分析一下里面的USB驱动和输入子系统的驱动:

1.驱动路径:

如下:drivers\hid\usbhid\usbkbd.c

2.USB驱动部分

对于USB驱动,我们的一个关键点就是对URB的申请,提交。

驱动的入口和匹配,这里就不深入讲解了,驱动章节有说

static const 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 */
};

MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);

static struct usb_driver usb_kbd_driver = {
	.name =		"usbkbd",
	.probe =	usb_kbd_probe,
	.disconnect =	usb_kbd_disconnect,
	.id_table =	usb_kbd_id_table,
};

probe函数
这里主要是列举了和USB有关的内容,可以看出主要就是申请URB,和验证是否为需要的设备

	//usb_interface转usb_dev
	struct usb_device *dev = interface_to_usbdev(iface);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_kbd *kbd;
		//获取当前接口设置
	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);
	//分配URB
	if (usb_kbd_alloc_mem(dev, kbd))
		goto fail2;

//初始化中断URB
	usb_fill_int_urb(kbd->irq, dev, pipe,
			 kbd->new, (maxp > 8 ? 8 : maxp),
			 usb_kbd_irq, kbd, endpoint->bInterval);

//初始化控制URB
	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
			     (void *) kbd->cr, kbd->leds, 1,
			     usb_kbd_led, kbd);

usb_kbd_alloc_mem函数如下:

//分配两个URBURB  
static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
{
	//输入URB
	if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
		return -1;
	//输出URB
	if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))
		return -1;

	//分配缓冲区
	if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
		return -1;
	if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL)))
		return -1;
	if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))
		return -1;

	return 0;
}

两个提交URB给核心层处理完成后回调函数

static void usb_kbd_led(struct urb *urb)
{
	unsigned long flags;
	struct usb_kbd *kbd = urb->context;

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

	spin_lock_irqsave(&kbd->leds_lock, flags);

	if (*(kbd->leds) == kbd->newleds){
		kbd->led_urb_submitted = false;
		spin_unlock_irqrestore(&kbd->leds_lock, flags);
		return;
	}

	*(kbd->leds) = kbd->newleds;
	
	kbd->led->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->led, GFP_ATOMIC)){
		hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
		kbd->led_urb_submitted = false;
	}
	spin_unlock_irqrestore(&kbd->leds_lock, flags);
	
}





//当从URB中获取到缓存数据之后,就上报对应的按键数字给输入系统
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.\n",
					 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.\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);
}

3.输入系统部分

输入部分就是按照我们分析的那几步骤进行

在probe函数中
这里主要就是申请input_dev结构体,设置结构体成员,注册到内核

struct input_dev *input_dev;

	//申请一个input_dev空间
	input_dev = input_allocate_device();
	if (!kbd || !input_dev)
		goto fail1;
//设置input_dev结构体中成员
	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);//设置输入事件类型为:按键,led,重复(如果想支持连续按键)

	input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
		BIT_MASK(LED_KANA);//设置那些led可以使用

	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;//键盘拔掉之后执行



	error = input_register_device(kbd->dev);//注册输入系统到内核
	if (error)
		goto fail2;

上报输入
上报的函数是在处理完URB数据之后的回调函数中进行上报,因为之后等URB处理完成之后,驱动才能拿到键盘输入的按键数据。

static void usb_kbd_irq(struct urb *urb)
{
    printk("Starting irq\n");
    struct usb_kbd *kbd = urb->context;
    int i;

    switch (urb->status) {  //判断URB的状态
    case 0:            //URB被成功接收
        break;
    case -ECONNRESET:    //断开连接错误,urb未终止就返回给了回调函数
    case -ENOENT: //urb被kill了,生命周期彻底被终止
    case -ESHUTDOWN:  //USB主控制器驱动程序发生了严重的错误,或者提交完的一瞬间设备被拔出
        return;
    
    default:        //其它错误,均可以重新提交urb
        goto resubmit;
    }
    printk("irq1\n");
    for (i = 0; i < 8; i++)
        input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);/*usb_kbd_keycode[224]-usb_kbd_keycode[231],8次的值依次是:29-42-56-125-97-54-100-126,判断这8个键的状态*/
    printk("irq2\n");
    //若同时只按下2个按键则另一个键在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下
    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                   //不是键盘所用的按键
                dev_info(&urb->dev->dev,
                        "Unknown key (scancode %#x) released.\n", 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        //不是键盘所用的按键
                dev_info(&urb->dev->dev,
                        "Unknown key (scancode %#x) released.\n", kbd->new[i]);
        }
    }

    input_sync(kbd->dev);  //同步设备,告知事件的接收者驱动已经发出了一个完整的input子系统的报告
    
        printk("irq2.1\n");
    memcpy(kbd->old, kbd->new, 8);  //将本次的按键状态拷贝到kbd->old,用作下次urb处理时判断按键状态的改变
        printk("irq3\n");
resubmit:
    i = usb_submit_urb (urb, GFP_ATOMIC); //重新发送urb请求块
    if (i)  //发送urb请求块失败
    {
        printk("irq4\n");
        err_hid ("can't resubmit intr, %s-%s/input0, status %d",
                kbd->usbdev->bus->bus_name,
                kbd->usbdev->devpath, i);
        printk("irq5\n");
    }
    printk("irq6\n");
}

4.完整probe函数代码和注释

static int usb_kbd_probe(struct usb_interface *iface,
             const struct usb_device_id *id)  //usb_interface *iface:由内核自动获取的接口,一个接口对应一种功能, struct usb_device_id *id:设备的标识符
{
    printk("Starting probe\n");
    struct usb_device *dev = interface_to_usbdev(iface);  //获取usb接口结构体中的usb设备结构体,每个USB设备对应一个struct usb_device的变量,由usb core负责申请和赋值
    struct usb_host_interface *interface;  //连接到的接口的描述
    struct usb_endpoint_descriptor *endpoint;    //传输数据管道的端点
    struct usb_kbd *kbd;  //usb设备在用户空间的描述
    struct input_dev *input_dev;  //表示输入设备
    int i, pipe, maxp; 
    int error = -ENOMEM; 
    
    
    interface = iface->cur_altsetting; //将连接到的接口的描述设置为当前的setting
    printk("1\n");
    if (interface->desc.bNumEndpoints != 1) //判断中断IN端点的个数,键盘只有一个端点,如果不为1,则出错,desc是设备描述符
        return -ENODEV;

    endpoint = &interface->endpoint[0].desc; //取得键盘中断IN端点的描述符,endpoint[0]表示中断端点
    printk("2\n");
    if (!usb_endpoint_is_int_in(endpoint)) //查看所获得的端点是否为中断IN端点
        return -ENODEV;
  printk("3\n");
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //得到驱动程序的中断OUT端点号,创建管道,用于连接驱动程序缓冲区和设备端口
    maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //得到最大可以传输的数据包(字节)

    kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //为kbd结构体分配内存,GFP_KERNEL是内核内存分配时最常用的标志位,无内存可用时可引起休眠
    input_dev = input_allocate_device();   //为输入设备的结构体分配内存,并初始化它
    printk("inputdev-1:%s\n",input_dev->name);
    printk("4\n");
    if (!kbd || !input_dev) //给kbd或input_dev分配内存失败
        goto fail1;
        printk("5\n");
    if (usb_kbd_alloc_mem(dev, kbd)) //分配urb内存空间失败,即创建urb失败
        goto fail2;
    printk("6\n");
    kbd->usbdev = dev;    //给kbd的usb设备结构体usbdev赋值
    kbd->dev = input_dev; //给kbd的输入设备结构体dev赋值,将所有内容统一用kbd封装,input子系统只能处理input_dev类型的对象
    printk("7\n");
    if (dev->manufacturer) //将厂商名,产品名赋值给kbd的name成员    
    {
        printk("7.1\n");
        strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
    }
    printk("8\n");
    
    if (dev->product) {
        printk("7.2\n");
        if (dev->manufacturer) //有厂商名,就在产品名之前加入空格
            strlcat(kbd->name, " ", sizeof(kbd->name));
        strlcat(kbd->name, dev->product, sizeof(kbd->name));
    }

    printk("9\n");

  printk("10\n");
    usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); //分配设备的物理路径的地址,设备链接地址,不随设备的拔出而改变
    printk("10.1\n");
    strlcpy(kbd->phys, "/input0", sizeof(kbd->phys));
    printk("10.2\n");
    input_dev->name = kbd->name; //给input_dev的name赋值
    printk("inputdevname:%s\n",input_dev->name);
    printk("kbddevname:%s\n",kbd->dev->name);
    input_dev->phys = kbd->phys;  //设备链接地址
    usb_to_input_id(dev, &input_dev->id); //给输入设备结构体input->的标识符结构赋值,主要设置bustype、vendo、product等
    printk("10.3\n");
    //input_dev->dev.parent = &iface->dev;

    //input_set_drvdata(input_dev, kbd);
    printk("10.4\n");
    input_dev->evbit[0] = BIT_MASK(EV_KEY) /*键码事件*/| BIT_MASK(EV_LED) | /*LED事件*/
        BIT_MASK(EV_REP)/*自动重覆数值*/;  //支持的事件类型
    printk("10.5\n");
    input_dev->ledbit[0] = BIT_MASK(LED_NUML) /*数字灯*/| BIT_MASK(LED_CAPSL) |/*大小写灯*/
        BIT_MASK(LED_SCROLLL)/*滚动灯*/ ;   //EV_LED事件支持的事件码
    printk("10.6\n");

    for (i = 0; i < 255; i++)
        set_bit(usb_kbd_keycode[i], input_dev->keybit); // 初始化,每个键盘扫描码都可以出发键盘事件
    printk("10.7\n");
    clear_bit(0, input_dev->keybit); //为0的键盘扫描码不能触发键盘事件
    printk("10.8\n");
    input_dev->event = usb_kbd_event; //设置input设备的打开、关闭、写入数据时的处理方法
    input_dev->open = usb_kbd_open;
    input_dev->close = usb_kbd_close;
  //初始化中断URB
    usb_fill_int_urb(kbd->irq/*初始化kbd->irq这个urb*/, dev/*这个urb要发送到dev这个设备*/, pipe/*这个urb要发送到pipe这个端点*/,
             kbd->new/*指向缓冲的指针*/, (maxp > 8 ? 8 : maxp)/*缓冲区长度(不超过8)*/,
             usb_kbd_irq/*这个urb完成时调用的处理函数*/, kbd/*指向数据块的指针,被添加到这个urb结构可被完成处理函数获取*/, endpoint->bInterval/*urb应当被调度的间隔*/);
    printk("10.9\n");
    kbd->irq->transfer_dma = kbd->new_dma;  //指定urb需要传输的DMA缓冲区
    kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;  //本urb有一个DMA缓冲区需要传输,用DMA传输要设的标志

    kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;  //操作的是USB类接口对象
    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
    printk("10.10\n");
    usb_fill_control_urb(kbd->led/*初始化kbd->led这个urb*/, dev/*这个urb要由dev这个设备发出*/, usb_sndctrlpipe(dev, 0)/*urb发送到的端点*/,
                 (void *) kbd->cr/*发送的setup packet*/, kbd->leds/*待发送数据的缓冲区*/, 1/*发送数据长度*/,
                 usb_kbd_led/*这个urb完成时调用的处理函数*/, kbd/*指向数据块的指针,被添加到这个urb结构可被完成处理函数获取*/);
    kbd->led->setup_dma = kbd->cr_dma;  //指定urb需要传输的DMA缓冲区
    kbd->led->transfer_dma = kbd->leds_dma;  //本urb有一个DMA缓冲区需要传输,用DMA传输要设的标志
    kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP/*如果使用DMA传输则urb中setup_dma指针所指向的缓冲区是DMA缓冲区而不是setup_packet所指向的缓冲区*/);   
    
    printk("10.11\n");
    printk("kbddevname1:%s\n",kbd->dev->name);
    error = input_register_device(kbd->dev);  //注册输入设备
    printk("11\n");
    if (error)  //注册失败
        goto fail2;

    usb_set_intfdata(iface, kbd);  //设置接口私有数据,向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_kbd结构,这个data可以在以后用usb_get_intfdata来得到
    printk("12\n");
    return 0;

fail2:    
    usb_kbd_free_mem(dev, kbd);  //释放URB内存空间,销毁URB
fail1:    
    input_free_device(input_dev); //释放input_dev和kbd的空间
    kfree(kbd);
    return error;
}

总结

未完。。。。。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知道起个啥名“”

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值