我是靠谱客的博主 苗条信封,最近开发中收集的这篇文章主要介绍第五章 linux-输入子系统一第五章 linux-输入子系统一1,输入子系统的作用和框架2,输入子系统的编程方式–学会最简单的输入子系统的开发方式3, 初始化input device4, 驱动多个按键5, 输入子系统的工作原理和代码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

第五章 linux-输入子系统一


文章目录

  • 第五章 linux-输入子系统一
  • 1,输入子系统的作用和框架
  • 2,输入子系统的编程方式--学会最简单的输入子系统的开发方式
  • 3, 初始化input device
  • 4, 驱动多个按键
  • 5, 输入子系统的工作原理和代码分析
    • 分层分析:
    • open("/dev/event1", O_RDWR);
    • vfs sys_open(); struct file file->f_ops = cdev->ops; file->f_ops->open();


1,输入子系统的作用和框架

什么是输入设备:
	1,按键/keyboard
	2, mouse
	3, touchscreen :gt811, ft56xx
	4, joystick 游戏杆

	有多个输入设备需要驱动的时候,假如不考虑输入子系统
		a, gt811
				设备号,创建文件,硬件初始化,实现fop,阻塞
				硬件初始化
		b, ft56xx
				设备号,创建文件,硬件初始化,实现fop,阻塞
				硬件初始化	
		
		多个输入设备有共同点:
				获取到数据(操作硬件),上报给用户(xxx_read, copy_to_user, 阻塞)
						差异化                           通用
	
		多个输入设备,有部分差异,也有部分通用
		内核就会考虑,将通用代码编写好,将差异化的代码留给驱动工程师

		设计成输入子系统:使得应用编程人员和驱动编程人员编程的时候变得简单统一
			1, 兼容所有的输入设备
			2, 统一的编程驱动方法(实现差异化硬件操作)
			3,	统一的应用操作接口:/dev/input/event0,event1
							open("/dev/input/event0"),
							read(fd, struct input_event): struct input_event buff可以认为是一个统一的数据包


框架:驱动分成三层
		
			应用层
	---------------------------------
	input handler层:数据处理者
		完成fop:实现xxx_read(), xxx_open
		将数据交给用户:数据从input device层
		不知道具体数据是什么,只知道把数据给用户
	----------------------------------------------------------
	input 核心层:管理层
	----------------------------------------------------------
	input device设备层:
		抽象出一个对象,描述输入设备信息
		初始化输入设备硬件,获取到数据
		知道具体的数据是什么,但是不知道数据如何给用户
	---------------------------------
	硬件层:mouse
			ts, keybaord,joystick

 编程: 主要在input device层

2,输入子系统的编程方式–学会最简单的输入子系统的开发方式

前提:input 核心层代码和input handler层需要在内核中必须有:
	drivers/input/evdev.c	//  event handler
	drivers/input/input.c  // 核心层

 make menuconfig
	Device Drivers  --->
		 Input device support  ---> 
				-*- Generic input layer (needed for keyboard, mouse, ...)  // input.c
				 <*>   Event interface   //input handler层--evdev.c

编写步骤:
	1,分配一个input device对象
	2, 初始化input  device对象
	3,注册input device对象


上报数据:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
		参数1:当前的input device上报数据
		参数2:上报的是那种数据类型 EV_KEY,EV_ABS
		参数3:具体数据是什么:KEY_POWER
		参数4:值是是什么
用户空间读到的数据:统一的数据包
struct input_event {
	struct timeval time; //时间戳
	__u16 type; //数据类型
	__u16 code;//具体数据是什么
	__s32 value;//值是是什么
};

3, 初始化input device

struct input_dev {//表示的是一个具体的输入设备,描述设备能够产生什么数据
	const char *name; // sysfs中给用户看的信息
	const char *phys;
	const char *uniq;
	struct input_id id;
	//evbit实际是一个位表,描述输入设备能够产生什么类型数据
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // EV_KEY,EV_ABS, EV_REL
	//表示能够产生哪种按键
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//KEY_POWER.. 能够表示768bit,直接用24个long来表示
							 // KEY_CNT == 768   BITS_TO_LONGS== nr/32 = 768/32==24
	//表示能够产生哪种相对坐标数据
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// REL_X
	//表示能够产生哪种绝对坐标数据
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //ABS_X
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

	struct device dev; // 继承device对象

	struct list_head	h_list;
	struct list_head	node; //表示节点
}

不同输入设备能够产生不同的数据:
1,按键/keyboard: 产生键值,实际是一个数字

#define KEY_VOLUMEDOWN		114
			#define KEY_VOLUMEUP		115
			#define KEY_POWER		116	/* SC System Power Down */
	2,ts/gsensor:产生坐标,绝对坐标, 有一个明确坐标系,并且原点(0,0),最大值(800,480)
#define ABS_X			0x00
				#define ABS_Y			0x01
				#define ABS_PRESSURE		0x18
				#define ABS_MT_TOUCH_MAJOR	0x30	/* Major axis of touching ellipse */
				#define ABS_MT_TOUCH_MINOR	0x31	/* Minor axis (omit if circular) */
				#define ABS_MT_WIDTH_MAJOR	0x32	/* Major axis of approaching ellipse */
				#define ABS_MT_WIDTH_MINOR	0x33	/* Minor axis (omit if circular) */
				#define ABS_MT_ORIENTATION	0x34	/* Ellipse orientation */
				#define ABS_MT_POSITION_X	0x35	/* Center X touch position */
				#define ABS_MT_POSITION_Y	0x36	/* Center Y touch position */
	3,mouse:产生坐标,相对坐标,坐标值是相对于之前一个点坐标	
#define REL_X			0x00
			#define REL_Y			0x01
			#define REL_WHEEL		0x08
	如何表示不同数据类型: 
#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 //声音数据
另外一种设置bit的方法
inputdev->keybit[BIT_WORD(KEY_POWER)] |= BIT_MASK(KEY_POWER);// 116%32
	inputdev->keybit[116/32] |= 1 << 116%32;// 116%32
上报数据的时候:
	方法1:通用方法
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
	方法2:封装:
input_report_key(struct input_dev * dev, unsigned int code, int value)
					|
					input_event(dev, EV_KEY, code, !!value); //上报按键的时候一定0或者1

4, 驱动多个按键

一个按键有多个与其相关的元素:
	a, 中断号码
	b, 按键的状态--gpio的数据寄存器获取到
	c, 按键的值--code

在设备树文件中设置这几个元素:
key_int_node{
                compatible = "test_key";
                #address-cells = <1>;
                #size-cells = <1>;

                key_int@0 {
                        key_name = "key2_power_eint";
                        key_code = <116>;
                        gpio = <&gpx1 1 0>;
                        reg = <0x11000C20 0x18>;
                        interrupt-parent = <&gpx1>;
                        interrupts = <1 0>;
                };

                key_int@1 {
                        key_name = "key3_vup_eint";
                        key_code = <115>;
                        gpio = <&gpx1 2 0>;
                        reg = <0x11000C20 0x18>;
                        interrupt-parent = <&gpx1>;
                        interrupts = <2 0>;
                };

                key_int@2 {
                        key_name = "key4_vdown_eint";
                        key_code = <114>;
                        gpio = <&gpx3 2 0>;
                        reg = <0x11000C60 0x18>;
                        interrupt-parent = <&gpx3>;
                        interrupts = <2 0>;
                };
		};	
make dtbs
		更新设备树文件:
		 cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
在代码中也会设计这几个元素
在代码中获取节点:
of_get_next_child(const struct device_node * node, struct device_node * prev)
	参数1:表示节点
	参数2:之前的节点,如果是第一个节点,设置成NULL
// 通过节点去获取到中断号码
			 irqno = irq_of_parse_and_map(cnp, 0);
			
			//获取key name
			of_property_read_string(cnp, "key_name",  &key_name);

			//获取key code
			of_property_read_u32(cnp, "key_code", &code);

			gpionum = of_get_named_gpio(cnp, "gpio", 0);

			printk("name = %s, code = %d, gpionum = %d,irqno = %dn",
					key_name, code, gpionum,irqno);	
//设计一个对象出来
struct key_desc{
	char *name;
	int irqno;
	int key_code;
	int gpionum;
	void *reg_base;
	struct device_node *cnp;// 可以随时去获取节点各个信息
};	

5, 输入子系统的工作原理和代码分析

目的:
	a,学会如何分析内核中子系统的代码,从而可以举一反三
	b,整体把握框架思想,理解分层中各层的配合方式
	c,掌握子系统,增强排错能力

分层分析:


input handler层:evdev.c
module_init(evdev_init);
module_exit(evdev_exit);

static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = “evdev”,
.id_table = evdev_ids,
};

	evdev_init(void)
		|
		input_register_handler(&evdev_handler);
				|
				//初始化h_list
				INIT_LIST_HEAD(&handler->h_list);

				//将当前的handler加入到一个input_handler_list
				list_add_tail(&handler->node, &input_handler_list);

				//遍历链表input_dev_list
				list_for_each_entry(dev, &input_dev_list, node)
					input_attach_handler(dev, handler);
						|
						// 将当前的hanler和input dev进行匹配, event handler能够匹配所有的input dev
						id = input_match_device(handler, dev);

						//匹配成功,之后要调用handler中connect方法
						// 实际就是event handler,实际调用了evdev_connect
						error = handler->connect(handler, dev, id);

				//将当前的handler加入到/proc/bus/input/handlers文件中
				input_wakeup_procfs_readers();
	总结:
		1,注册了evdev_handler
		2, 遍历input dev list,并行匹配,恒匹配成功,自动会
			调用handler中connect方法--- evdev_connect

input核心层:input.c
subsys_initcall(input_init);
module_exit(input_exit);

input_init()
	|
	//注册类,类似于class_create();
	err = class_register(&input_class);
	//在/proc创建 bus/input/devices  handlers
	err = input_proc_init();
	//申请设备号
	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
			     INPUT_MAX_CHAR_DEVICES, "input");
	
	总结:
		1,注册了主设备号
		2,注册input class

simple_input.c
input_register_device(inputdev);
|
//将input dev加入到链表input_dev_list
list_add_tail(&dev->node, &input_dev_list);

	//遍历input handler链表,进行匹配
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);
				|
			//匹配成功,之后要调用handler中connect方法
			// 实际就是event handler,实际调用了evdev_connect
			error = handler->connect(handler, dev, id);

分析: evdev.c中evdev_connect()— 属于input handler层
evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
|
//找到一个没有被使用的次设备号, 从64开始, 65,66
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

// 实例化 一个evdev对象
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//初始化evdev对象
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
//等待队列是完成阻塞
init_waitqueue_head(&evdev->wait);
evdev->exist = true;

dev_no = minor;
dev_no -= EVDEV_MINOR_BASE; //减去了64

// 创建设备文件/dev/event0,1,2
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// 12, 64
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev)
device_add(&evdev->dev); 
//以上代码和device_create是一样

//利用handle记录input device和input handler
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
//你中有有我,我中有你
evdev->handle.private = evdev;


//将儿子关联到父亲(input handler)和母亲(input dev)
error = input_register_handle(&evdev->handle);
				|
			list_add_tail_rcu(&handle->d_node, &dev->h_list);
			list_add_tail_rcu(&handle->h_node, &handler->h_list);


//初始化了cdev,完成了fops, 为用户提供文件io
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);

总结:
	1,分配evdev,并初始化,记录input device和handler的关系
	2,创建设备节点/dev/event0
	3, 注册cdev,并实现fops

	4,关系:
		多个input device可以对应一个event handler
		一个input device对应一个 evdev,对应于一个设备节点/dev/event0,1,2
	5, 所有的设备节点调用open,read,write文件io的时候
		实际是调用cdev中fops中各个接口,最终都调用了
		static const struct file_operations evdev_fops = {
				.owner		= THIS_MODULE,
				.read		= evdev_read,
				.write		= evdev_write,
				.poll		= evdev_poll,
				.open		= evdev_open,
				.release	= evdev_release,
				.unlocked_ioctl	= evdev_ioctl,
			#ifdef CONFIG_COMPAT
				.compat_ioctl	= evdev_ioctl_compat,
			#endif
				.fasync		= evdev_fasync,
				.flush		= evdev_flush,
				.llseek		= no_llseek,
			};

device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, …)
|
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
|
device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, args);
|
struct device *dev = NULL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);

			device_initialize(dev);
			dev->devt = devt;
			dev->class = class;
			dev->parent = parent;
			dev->groups = groups;
			dev->release = device_create_release;
			dev_set_drvdata(dev, drvdata);
			kobject_set_name_vargs(&dev->kobj, fmt, args);//设置名字
			device_add(dev);//注册到系统

应用程序中调用了输入子系统的代码,数据是如何传递给用户层的?

open(“/dev/event1”, O_RDWR);

vfs
sys_open();
struct file file->f_ops = cdev->ops;
file->f_ops->open();

设备驱动层:输入子系统
input handler 层:evdev.c
cdev;
xxx_ops = {
.open = xxx_open,
.write = xxx_write,
}

 static const struct file_operations evdev_fops = {
				.owner		= THIS_MODULE,
				.read		= evdev_read,
				.write		= evdev_write,
				.poll		= evdev_poll,
				.open		= evdev_open,
 }

实际最终调用了evdev_open();
	|
	// 实际cdev是谁,就是evdev_connect注册的那个
	struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);

	// 通过儿子,找到老母input device
	unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);

	// size就包含了很多input_event
	unsigned int size = sizeof(struct evdev_client) +
					bufsize * sizeof(struct input_event);

	struct evdev_client *client;

	// 分配一个client对像,描述一个缓冲队列,存放的就是input_event
	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);

	// client中有一个缓冲区
	client->bufsize = bufsize;
	spin_lock_init(&client->buffer_lock);
	//在client中记录evdev
	client->evdev = evdev;
	// 将client加入到evdev中一个小链表
	evdev_attach_client(evdev, client);
			|
			list_add_tail_rcu(&client->node, &evdev->client_list);

	// 将client记录到file,方面其他的接口使用
	file->private_data = client;

	总结:
		1,为输入设备分配一个缓冲区evdev_client,用户存放input device层上报的数据
		2,evdev_client记录到evdev中
		3,evdev_client记录到file中,方面其他的接口使用

应用程序中read,是如何获取到数据的
read(fd, &event, sizeof(struct input_event));
---------------------------------------------
vfs
sys_read();
file->f_ops->read();
-------------------------------------------
evdev.c
static const struct file_operations evdev_fops = {
.read = evdev_read,
evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
|
// 获取到open中分配的缓冲区对象
struct evdev_client *client = file->private_data;
//获取到evdev
struct evdev *evdev = client->evdev;
//表示一个数据包,要给用户
struct input_event event;

	for (;;) {
		// 实现非阻塞
		if (client->packet_head == client->tail &&
			(file->f_flags & O_NONBLOCK))
			return -EAGAIN;

			while (read + input_event_size() <= count &&
			// 1从缓冲区获取数据,存放在 input_event数据包
			   evdev_fetch_next_event(client, &event)) {
						|
						*event = client->buffer[client->tail++];
			// 2, 将数据上报给用户
			if (input_event_to_user(buffer + read, &event))
						|
						copy_to_user(buffer, event, sizeof(struct input_event)

			// 3,统计上报多少数据
			read += input_event_size();
		}

		// 如果当前是阻塞模式
		if (!(file->f_flags & O_NONBLOCK)) {
			//等待---休眠,需要被唤醒,有数据时候唤醒
			error = wait_event_interruptible(evdev->wait,
					client->packet_head != client->tail ||
					!evdev->exist || client->revoked);
	}

	总结:
		1,如果没有数据,就会休眠等待
		2,如果有数据,就从缓冲区client->buffer[client->tail++]拿数据
			通过copy_to_user上报给用户
	
		疑问:
			数据到底是如何存放在缓冲区的
			等待队列是谁唤醒的
			input_report_key(inputdev, pdesc->key_code, 0);
			input_sync(inputdev);//上报数据结束


input_report_key(inputdev, pdesc->key_code, 0);
input_sync(inputdev);//上报数据结束
	|//input_event(dev, EV_KEY, code, !!value);
	input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);// 上报数据
			|
			input_handle_event(dev, type, code, value);
					|
					// 如果将数据交给input handler去处理
					if (disposition & INPUT_PASS_TO_HANDLERS) {
						struct input_value *v;
						//将input device获取到数据暂存一下input value
						v = &dev->vals[dev->num_vals++];
						v->type = type;
						v->code = code;
						v->value = value;


						input_pass_values(dev, dev->vals, dev->num_vals)
							|
							// 从input device中获取到input handle
						else {

						list_for_each_entry_rcu(handle, &dev->h_list, d_node)
							if (handle->open)
								count = input_to_handler(handle, vals, count);
										|
									// 通过handle儿子找到handler父亲
									struct input_handler *handler = handle->handler;
									// 如果有events函数指针,那就调用
									if (handler->events)
										handler->events(handle, vals, count);
									else if (handler->event)//否则就调用event();
										for (v = vals; v != end; v++)
											handler->event(handle, v->type, v->code, v->value);
									
									
					}

static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = “evdev”,
.id_table = evdev_ids,
};
总结: 如果将数据上报,最终是调用handler中events()或者event()
实际是evdev.c
.event = evdev_event,
.events = evdev_events,
|
// 拿到evdev,肯定要拿到缓冲区
struct evdev *evdev = handle->private;
struct evdev_client *client;

					// 获取到缓冲evdev_client
					else
					list_for_each_entry_rcu(client, &evdev->client_list, node)
						evdev_pass_values(client, vals, count,
								  time_mono, time_real);
							|
							// 通过client获取到evdev
							struct evdev *evdev = client->evdev;
							
							const struct input_value *v;
							struct input_event event;

							event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real);
							for (v = vals; v != vals + count; v++) {
								// 将input device上报的数据获取到,并且封装成input event对象
								event.type = v->type;
								event.code = v->code;
								event.value = v->value;
								// 将input event数据存放到缓冲区中
								__pass_event(client, &event);
										|
										client->buffer[client->head++] = *event;
										client->head &= client->bufsize - 1;
								for (v = vals; v != vals + count; v++) {
									// 将input device上报的数据获取到,并且封装成input event对象
									event.type = v->type;
									event.code = v->code;
									event.value = v->value;
									// 将input event数据存放到缓冲区中
									__pass_event(client, &event);
											client->buffer[client->head++] = *event;
											client->head &= client->bufsize - 1;
									// 如果调用 input_sync()
									if (v->type == EV_SYN && v->code == SYN_REPORT)
										wakeup = true;
								}

									// 唤醒等待队列
									if (wakeup)
										wake_up_interruptible(&evdev->wait);
		
		总结:
			input_report_key(inputdev, pdesc->key_code, 0);
				//将输入设备产生数据交给input handler,调用events();将数据存放在
				// 缓冲区client->buffer[client->head++] = *event;
			input_sync(inputdev);//上报数据结束
				// 唤醒等待队列,表示输入设备上报数据完毕

最后

以上就是苗条信封为你收集整理的第五章 linux-输入子系统一第五章 linux-输入子系统一1,输入子系统的作用和框架2,输入子系统的编程方式–学会最简单的输入子系统的开发方式3, 初始化input device4, 驱动多个按键5, 输入子系统的工作原理和代码分析的全部内容,希望文章能够帮你解决第五章 linux-输入子系统一第五章 linux-输入子系统一1,输入子系统的作用和框架2,输入子系统的编程方式–学会最简单的输入子系统的开发方式3, 初始化input device4, 驱动多个按键5, 输入子系统的工作原理和代码分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部