第五章 linux-输入子系统一

前言

今后分享将不再按照本人学习路线。输入子系统仅仅只是学习了一遍,仅此而已,已经忘的差不多了,日后再抽时间重新学习一遍,再将本博文改正和完善。将就看看。

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

什么是输入设备
1.按键/keyboard
2.mouce
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,truct input event):struct input even当作统一的数据包
框架:驱动分三层

应用层
input handler层:数据处理者
完成fop:实现read,open
将数据交给用户:数据从input device层

input 核心层:管理者
input device设备层
抽象出一个对象,描述输入设备信息
初始化输入设备硬件,获取到数据
知道具体数据是什么,但不知道数据如何给用户

硬件层:mouce:ts,keyboard,joystick

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

前提:input核心层代码和input handler、层需要在内核中必须有

drivers/input/event.c  // event handler
	drivers/input/input.c
make menuconfig
	Device Driver --->
	Input device support -->
		-*-Generic input layer(needed for keyboard,mouce,..)//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;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
//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...
//表示能够产生哪种相对坐标
	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_Z			0x02
#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) */

3.mouse:产生坐标,相对坐标,坐标值是相对与之前一个点坐标

#define REL_X			0x00
#define REL_Y			0x01
#define REL_Z			0x02
#define REL_RX			0x03
#define REL_RY			0x04
如何表示不同数据类型
#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		//声音数据
#define EV_REP			0x14
#define EV_FF				0x15
#define EV_PWR			0x16
#define EV_FF_STATUS		0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

另一种设置bit的方式

inputdev—>keybit [BIT WORD ( KEY_POWER )|= BIT_MASK (KEY_POWER) ; / / 116 % 3 2
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:封装

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);//确保上报的要么0要么1
}

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

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);
|
//将当前的handler和input dev进行匹配  event handler能够匹配所有的input dev
	id = input_match_device(handler, dev);
	if (!id)
		return -ENODEV;

	//匹配成功,之后要调用handler中connect方法
	//实际上就是event handler
	error = handler->connect(handler, dev, id);
	if (error && error != -ENODEV)
		pr_err("failed to attach handler %s to device %s, error: %d\n",
		       handler->name, kobject_name(&dev->dev.kobj), error);

	//将当前的handler加入到/proc/bus/input/handlers文件中
	input_wakeup_procfs_readers();

总结:
1.注册了event_handler
2.遍历input dev list,进行匹配,恒匹配成功,自动会调用handler中connect方法

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

input_init()
//注册类,类似于class_create()
	err = class_register(&input_class);
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}

	//在/pro目录下创建  bus/input/device/ handlers
	err = input_proc_init();
	if (err)
		goto fail1;

	//申请设备号
	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				     INPUT_MAX_CHAR_DEVICES, "input");
	if (err) {
		pr_err("unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

总结:
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
	error = handler->connect(handler, dev, id);

分析:evdev.c中evdev_connect()------属于input handler层
evdev_connect()

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




	//找到一个没有被使用的次设备号,从64开始
	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
	if (minor < 0) {
		error = minor;
		pr_err("failed to reserve new minor: %d\n", error);
		return error;
	}

	//实例化一个evdev对象
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev) {
		error = -ENOMEM;
		goto err_free_minor;
	}

	//初始化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;
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;

	
	//创建设备节点
	dev_set_name(&evdev->dev, "event%d", dev_no);

	//利用handleriluinput 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;

	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

	//将儿子关联到父亲(input_handler)母亲(input_dev)
	error = input_register_handle(&evdev->handle);
	if (error)
		goto err_free_evdev;

	//初始化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);
	if (error)
		goto err_unregister_handle;

总结:
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, ...)
|
device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
|
device_create_groups_vargs(class, parent, devt, drvdata, NULL,
					  fmt, args);
|
	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);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);//设置名字
	retval = device_add(dev);//注册到系统

设备驱动层:输入子系统
input handler层:edev.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,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,

实际上最终调用了evdev_open();

static int evdev_open(struct inode *inode, struct file *file)
{
	//实际上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;
	int error;

	//分配一个client对象,描述一个缓冲队列,存放的对象input event
	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
	if (!client)
		client = vzalloc(size);
	if (!client)
		return -ENOMEM;

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

	
	error = evdev_open_device(evdev);
	if (error)
		goto err_free_client;

	//将client记录到file,方便其他接口使用
	file->private_data = client;
	nonseekable_open(inode, file);

	return 0;

 err_free_client:
	evdev_detach_client(evdev, client);
	kfree(client);
	return error;
}

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

posted @ 2022-11-14 23:34  Paranoid-up  阅读(190)  评论(0)    收藏  举报