linux 中input子系统介绍及使用案例

1. input概念

 linux下可以对输入设备使用普通字符设备驱动模型编写,如果每个设备都需要自己注册杂项设备 自己编写文件操作方法,这会造成代码重复,重复工作很多。比如:写一个程序是鼠标---USB,PS/2 ,触摸板。一个键盘,种类很多,不可能每一个都去写读写接口。内核专门为输入类设备编写一个子系统:Input子系统 (输入子系统)

其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
 


2. input子系统框架

 从底到顶:

硬件           --- 物理上的输入设备

设备驱动层     --- 直接驱动硬件,从硬件中获取事件

核心层         --- 在中间,起到连接设备驱动层和事件处理层的作用。就是提供关键数据结构,API接口函数给这两层使用。

事件处理层     ---- 接收设备驱动层传递过来的事件,传递用户空间(/dev/XXX 设备就属于这一层)

Linux 输入子系统包括三个层次, 由上到下别是事件处理层(Event Handler)、核心层(Input
Core)和驱动层(Input Driver)。
 

事件处理层:
负责与应用程序打交道,将硬件驱动层传来的事件报告给应用程序。
严格上说,每个输入设备都需要一个匹配事件处理者,需要驱动工作者编写。 但是,内核有实现一个通用的设备事件处理层代码,可以处理绝大多数输入设备,所以,不是很特殊的输入设备都可以处理,所以,大部分情况,编写基于 input 子系统的驱动,并不需要实现这一层代码。使用结构体 input_handler 表示事件处理器,是对事件处理的抽象。

核心层:
是链接其他两个层之间的纽带与桥梁, 向下提供驱动层的接口,向上提供事件处理层的接口。 属于内核源码的一部分,不需要做任何修改。提供一个结构体 handle, 是手柄的意思,结构体 input_handle 表示连接杆,连接底层硬件和上层事件
处理层。

设备驱动层:
负责操作具体的硬件设备,这层的代码是针对具体的设备驱动程序,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要在这层。 这一层就是需要驱动开发者编写。这一层使用结构体 input_dev 表示一个输入设备,是所有输入设备类型的抽象。
 

3. input子系统的关键数据结构

 核心结构:
Input.h    include\Linux

内核使用 struct input_dev  来描述一个输入设备。

struct input_dev {
	const char *name;  //名字,随便,在sys目录下的子目录出现
	const char *phys;  //物理路径,不用写,内核使用
	const char *uniq;  //内核使用,不用写
	struct input_id id;//匹配驱动,使用通用事件处理层可选。

    //以下是输入子系统的各种功能描述,一个位表示一个功能(事件类型)
	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
	
	//设置输入的设备支持事件类型:比如说鼠标有按键事件,相对事件 
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //必选

	//对应事件类型的数值,以下成员都有对应的api函数,根据evbit设置情况填充对应的数组,evbit每个事件都对应以下一个数组。
	//如:如果设置了按键事件,则要在 keybit数组设置支持哪一个按键。
	//如果设置了相对坐标事件,则要在 relbit 数组设置支持哪一个轴X,Y,Z等。
	//如果设置了绝对坐标事件,则要在 absbit 数组设置支持哪一个轴X,Y,Z等。
    //其他数组同理。常用就是 keybit,relbit,absbit
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//对应于按键键值
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//对应相对坐标的键值 x,y,z
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//对应绝对坐标事件的键值,x,y,z
	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_slot *mt;
	int mtsize;
	int slot;
	int trkid;

	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;

	bool sync;

	struct device dev;

	struct list_head	h_list;
	struct list_head	node;
};

这个结构一般需要填充:
name  -(可选)
evbit,
keybit,relbit,absbit  -- 根据evbit来填充。

怎么填充上面的evbit:只能使用内核定义好的宏

/* 
 * Event types
 */
#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)

 示例:

struct input_dev inputdev, *devp=&inputdev;

填充成员:
set_bit(EV_KEY, devp->evbit); //evbit是一个位表示一个事件。

怎么填充上面的 keybit,relbit,absbit:只能使用内核定义好的宏

input.h

 #define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
#define KEY_5			6
#define KEY_6			7
#define KEY_7			8
#define KEY_8			9
#define KEY_9			10
……
#define KEY_F			33
#define KEY_G			34
#define KEY_H			35
#define KEY_J			36
#define KEY_K			37
#define KEY_L			38
……

 上面设备 支持  EV_KEY ,所以要设置支持具体哪一个按键

    填充成员:
    set_bit(KEY_1, devp->keybit);  //数字1
    set_bit(KEY_ENTER,devp->keybit);//回车
    set_bit(KEY_L, devp->keybit);//L
    set_bit(KEY_S, devp->keybit);//S

 

4. input子系统的API函数

 1)动态分配核心结构 input_dev 空间函数

input_allocate_device(void);

分配一个input_dev 堆空间,返回 首地址

2)注册核心结构(注册输入设备)函数

int input_register_device(struct input_dev * dev)

注册已经填充好的 input_dev 结构。

成功:0,失败:负数

3)注销核心结构函数

void input_unregister_device(struct input_dev *);

4)释放动态的分配核心结构 input_dev 空间函数

void input_free_device(struct input_dev *dev)

5)事件上报函数:分类,不同的事情,上报函数不同。

void input_report_key(struct input_dev *dev, 
                      unsigned int code, int value)

dev:核心结构指针
code:就是上面设备 keybit 支持的值。
value:按键对应的状态 ,0表示松开,1表示按键,2表示重复。

//报告相对位移事件,鼠标的X,y
void input_report_rel(struct input_dev *dev, unsigned int code, int value)

code:就是前面设置relbit设置的内容,如: 
    /*
 * Relative axes
 */

#define REL_X            0x00
#define REL_Y            0x01
#define REL_Z            0x02
#define REL_RX            0x03
#define REL_RY            0x04
#define REL_RZ            0x05
#define REL_HWHEEL        0x06
#define REL_DIAL        0x07
#define REL_WHEEL        0x08
#define REL_MISC        0x09
#define REL_MAX            0x0f
#define REL_CNT            (REL_MAX+1)REL_X

value: 对应坐标的值

//报告绝对位移事件,单点触摸屏,X,y
void input_report_abs(struct input_dev *dev, unsigned int code, int value)

和上面的类似,只是code不同:
/*
 * Absolute axes
 */
#define ABS_X            0x00  //X方向(单点设备)
#define ABS_Y            0x01  //Y方向(单点设备)
#define ABS_Z            0x02
#define ABS_RX            0x03
#define ABS_RY            0x04

。。。

报完后要报同步事件,告诉事件处理层,我已经把一组完整事件上报完毕,你可以去处理。

input_sync(struct input_dev * dev)  上报同步事件。

1. 分配一个 input_dev 结构空间
2. 填充 input_dev  evbit 成员,然后根据 evbit 填充的内容选择性填充  keybit,relbit,absbit,……
3. 注册输入设备(input_dev)
4. 在适当的地方上报事件 。 
5. 要相应的注销输入设备(如在模块卸载函数中)

5. input子系统的示例

该程序是 开发板上按键 1 的驱动,把按键 1 作为一个ESC 按键注册为一个输入设备。

#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/pci.h>   //iorepam
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/poll.h> // 为了添加poll接口函数
#include <linux/input.h>

/*用来实现宏名打印*/
#define    MACRO_(x)        #x
#define    SHOWMACOR(mac)   MACRO_(mac)
struct input_dev *devp;

/*定义设备名*/
#define DEV_NAME   "input_buttons"

/*
 *  rGPX3CON 0x11000C60
 */
#define GPX3BASE      0x11000C60  //GPM4CON物理地址  P361
#define GPX3SIZE      0x10        //GPM4占用空间大小
#define rGPX3CON      (*(volatile unsigned *)(base_addr + 0))
#define rGPX3DAT      (*(volatile unsigned *)(base_addr + 1))
#define rGPX3UP       (*(volatile unsigned *)(base_addr + 2))
#define rGPX3DRV      (*(volatile unsigned *)(base_addr + 3))
//#define rGPX3CONPDN   (*(volatile unsigned *)(base_addr + 4))
//#define rGPX3PUDPDN   (*(volatile unsigned *)(base_addr + 5))

/*  irq              ping     down value  up value  name  nr
 * IRQ_EINT8          0        0x01       0x00      "k1"   0
 * IRQ_EINT11         3        0x02       0x00      "k2"   1
 * IRQ_EINT13         5        0x03       0x00      "k3"   2
 * IRQ_EINT14         6        0x04       0x00      "k4"   3
 * IRQ_EINT15         7        0x05       0x00      "k5"   4
 * IRQ_EINT19         11       0x06       0x00      "k6"   5
 */
typedef struct {
    int irq;
    int gpio;
    int key_value;
    char *name;
    int nr;
} button_irq_desc;


static volatile button_irq_desc buttons[] = {
    [0] = {
        .gpio        = EXYNOS4_GPX3(2) ,
        .name       = "KEY1" ,
        .key_value  = KEY_1,
        .nr         = 0
    },

    [1] = {
        .gpio        = EXYNOS4_GPX3(3) ,
        .key_value  = KEY_ENTER,
        .name       = "KEY_ENTER" ,
        .nr         = 1
    },

    [2] = {
        .gpio        = EXYNOS4_GPX3(4) ,
        .key_value  = KEY_L,
        .name       = "KEY_L" ,
        .nr         = 2
    },

    [3] = {
        .gpio        = EXYNOS4_GPX3(5) ,
        .key_value  = KEY_S,
        .name       = "KEY_S" ,
        .nr         = 3
    },
};


static atomic_t open_lock;

static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    button_irq_desc *buttons = (button_irq_desc *)dev_id;
    int down = 0;

    down = !gpio_get_value(buttons->gpio);

    //printk("/**********************************************/\r\n");
    input_report_key(devp, buttons->key_value, down ? 1 : 0);
    	
    input_sync(devp);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}


static int exynos4412_key_setup_irq(void)
{
    int i;
    int err;
    for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++) {

        /*容错处理,过滤不合法的中断号*/
        if (buttons[i].gpio < 0) {
            continue;
        }

        buttons[i].irq = gpio_to_irq(buttons[i].gpio);
        err = request_irq(buttons[i].irq,
                          buttons_interrupt,
                          IRQ_TYPE_EDGE_BOTH,
                          buttons[i].name,
                          (void *)&buttons[i]);
        if (err)
            break;
    }

    if (err) {
        i--;
        for (; i >= 0; i--) {
            if (buttons[i].gpio < 0) {
                continue;
            }

            disable_irq(buttons[i].irq);

            free_irq(buttons[i].irq, (void *)&buttons[i]);
        }
        return -EBUSY;
    }


    return 0;
}



static void exynos4412_key_free_irq(void)
{
    int i;
    for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++) {

        if (buttons[i].gpio < 0) {
            continue;
        }

        free_irq(buttons[i].irq, (void *)&buttons[i]);
    }
}


static  int __init  button_chardev_init(void)
{

    int err = 0;

    atomic_set(&open_lock, 1);

    printk(KERN_EMERG"module init now!\r\n");
    
    //注册中断
    err = exynos4412_key_setup_irq();
    if(err)
        goto fail_iounmap;
    
    //分配一个输入设备结构空间
    devp = input_allocate_device();
    if (!devp) {
        printk(KERN_ERR "allocate input_dev fail!\n");
        err = -ENOMEM;
        goto fail_free_irq;
    }

    //开始填充输入设备结构空间
    devp->name = "exynos4_key_input";  //名字,随便

    //填充设备支持事件类型
    set_bit(EV_KEY, devp->evbit);
    //devp->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY);
    
    //设备支持的按键
    set_bit(KEY_1, devp->keybit);
    set_bit(KEY_ENTER,devp->keybit);
    set_bit(KEY_L, devp->keybit);
    set_bit(KEY_S, devp->keybit);

    //注册输入设备结构
    if ((err = input_register_device(devp))) {
        printk(KERN_ERR "register input_dev fail\n");

        goto fail_free_dev;
    }

    return 0;

fail_free_dev:
    input_free_device(devp);
fail_free_irq:
    exynos4412_key_free_irq();
fail_iounmap:
    return err;

}


static __exit void  button_chardev_exit(void)
{
    printk(KERN_EMERG"module remove now!!!\r\n");
    input_unregister_device(devp);
    input_free_device(devp);
    exynos4412_key_free_irq();
}



MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
module_init(button_chardev_init);
module_exit(button_chardev_exit);
MODULE_DESCRIPTION("This is input system buttom interrupt  driver test!\r\n");

APP代码:

 

//--app-key.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>
// 运行方法 : ./app /dev/input/event2

/*
 struct input_event {
	struct timeval time;
	__u16 type; //
	__u16 code; //状态
	__s32 value;//键值
};
*/
int main(int argc, char **argv)
{
	int count, i;
	int fd;

	//struct input_event 专门用来存放基于输入子系统上报的数据的结构
	struct input_event ev_key;

	if(argc != 2)
		printf("usge:%s /dev/eventX\r\n",argv[0]);

	fd = open(argv[1], 0);//根据实际情况 有可能不是
	if (fd < 0)
	{
		perror("open device key!");
		exit(1);
	}

	while (1)
	{
		count = read(fd, &ev_key, sizeof(struct input_event));

		if (EV_KEY == ev_key.type)
		{
			printf("type:%d,code:%d,value:%d\n", ev_key.type, ev_key.code, ev_key.value);
			if(ev_key.value==1)
			{
				printf("key down\r\n");
			}
			else
			{
				printf("key up\r\n");
			}
		}

		if (EV_SYN == ev_key.type)

			printf("syn event\n");

	}

	close(fd);

	return 0;

}


 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值