Linux驱动开发十:按键中断之输入子系统



 一、Input子系统概述

Input子系统是对不同类型的输入设备进行统一处理的驱动程序。一个输入事件,如按键,是通过驱动层到系统核心层到事件处理层到用户空间的顺序到达用户空间并传给应用程序使用。

Input子系统由驱动层、输入子系统核心层和事件处理层三部分组成。此子系统主要包括两类驱动程序:事件驱动程序和设备驱动程序。事件驱动程序负责和应用程序的接口,而设备驱动程序负责和底层输入设备的通信。输入事件驱动程序包括mousedevevdevjoydev,键盘等。事件驱动程序是标准的,对所有的输入设备都是可用的,所以要实现的是设备驱动程序而不是事件驱动程序,设备驱动程序可用利用一个已经存在的、合适的事件驱动程序通过输入核心和用户应用程序接口。事件驱动程序和设备驱动程序都可以利用输入核心层为之服务。


二、下面是对具体代码的分析过程:


drivers/input/input.c:
 input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
 
static const struct file_operations input_fops = {
 .owner = THIS_MODULE,
 .open = input_open_file,
};

问:怎么读按键?

input_open_file
 struct input_handler *handler = input_table[iminor(inode) >> 5];
 new_fops = fops_get(handler->fops)  //  =>&evdev_fops
 file->f_op = new_fops;
 err = new_fops->open(inode, file);

app: read > ... > file->f_op->read 

input_table数组由谁构造?

input_register_handler


注册input_handler:
input_register_handler
 // 放入数组
 input_table[handler->minor >> 5] = handler;
 
 // 放入链表
 list_add_tail(&handler->node, &input_handler_list);

 // 对于每个input_dev,调用input_attach_handler
 list_for_each_entry(dev, &input_dev_list, node)
  input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
 
 


注册输入设备:
input_register_device
 // 放入链表
 list_add_tail(&dev->node, &input_dev_list);
 
 // 对于每一个input_handler,都调用input_attach_handler
 list_for_each_entry(handler, &input_handler_list, node)
  input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev


input_attach_handler
 id = input_match_device(handler->id_table, dev);
 
 error = handler->connect(handler, dev, id);


注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
如果能支持,则调用input_handler的connect函数建立"连接"

怎么建立连接?(从evdev.c中分析)
1. 分配一个input_handle结构体
2.
 input_handle.dev = input_dev;  // 指向左边的input_dev
 input_handle.handler = input_handler;  // 指向右边的input_handler
3. 注册:
   input_handler->h_list = &input_handle;
   inpu_dev->h_list      = &input_handle;


evdev_connect
 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
 
 // 设置
 evdev->handle.dev = dev;  // 指向左边的input_dev
 evdev->handle.name = evdev->name;
 evdev->handle.handler = handler;  // 指向右边的input_handler
 evdev->handle.private = evdev;
 
 // 注册
 error = input_register_handle(&evdev->handle);
 
怎么读按键?
app: read
--------------------------
   .......
     evdev_read
      // 无数据并且是非阻塞方式打开,则立刻返回
   if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
    return -EAGAIN;
   
   // 否则休眠
   retval = wait_event_interruptible(evdev->wait,
    client->head != client->tail || !evdev->exist);
      

谁来唤醒?
evdev_event
 wake_up_interruptible(&evdev->wait);


evdev_event被谁调用?
猜:应该是硬件相关的代码,input_dev那层调用的
在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数
gpio_keys_isr
 // 上报事件
 input_event(input, type, button->code, !!state);
 input_sync(input);
 
input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 struct input_handle *handle;

 list_for_each_entry(handle, &dev->h_list, d_node)
  if (handle->open)
   handle->handler->event(handle, type, code, value);


怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件
   


struct input_dev {

 void *private;

 const char *name;
 const char *phys;
 const char *uniq;
 struct input_id id;

 unsigned long evbit[NBITS(EV_MAX)];   // 表示能产生哪类事件
 unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键
 unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮
 unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
 unsigned long mscbit[NBITS(MSC_MAX)];
 unsigned long ledbit[NBITS(LED_MAX)];
 unsigned long sndbit[NBITS(SND_MAX)];
 unsigned long ffbit[NBITS(FF_MAX)];
 unsigned long swbit[NBITS(SW_MAX)];

测试:(会在/dev/目录下产生event1设备,如果目录下有event0设备 )
crw-rw----    1 0        0          13,  64 Jan  1 00:00 /dev/event0
crw-rw----    1 0        0          13,  65 Jan  1 00:00 /dev/event1
1.
hexdump /dev/event1  (open(/dev/event1), read(), )
           秒        微秒    类  code    value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

2. 如果没有启动QT:
cat /dev/tty1
按:s2,s3,s4
就可以得到ls

或者:
exec 0</dev/tty1
然后可以使用按键来输入


3. 如果已经启动了QT:
可以点开记事本
然后按:s2,s3,s4

三、下面是在QT6410上运行OK的代码:

buttons.c

/*
 * 参考driver/input/keyboard/gpio_keys.c
 */

#include <linux/module.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>

static struct input_dev *buttons_dev;

static struct timer_list buttons_timer;

struct pin_desc* irq_pd;

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
	{IRQ_EINT(0),"K1",S3C64XX_GPN(0), KEY_L},
	{IRQ_EINT(1),"K2",S3C64XX_GPN(1), KEY_S},
	{IRQ_EINT(2),"K3",S3C64XX_GPN(2), KEY_ENTER},
	{IRQ_EINT(3),"K4",S3C64XX_GPN(3), KEY_LEFTSHIFT},
};

/*
 *中断处理程序,并置标志位为1,唤醒等待队列上等待的进程,注册用户中断处理函数,设置触发方式为双边沿触发
 */
static irqreturn_t button_irq(int irq,void *dev_id)
{
	irq_pd = (struct pin_desc*)dev_id;
	
	/* 10ms后启动定时器 */
	mod_timer(&buttons_timer,jiffies + (HZ)/50);
	//jiffies为系统全局变量,用cat /proc/interrupts可以查看其值一直在变化“100:    1036564   s3c-timer  S3C2410 Timer Tick”
	//jiffies + (HZ)/100:表示当前时间起延时10ms后处理(HZ=1000)
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data)
{
	struct pin_desc * pindesc = irq_pd;
	
	if(!pindesc)	//如果没有中断发生就返回
		return ;
	unsigned int pinval;
	
	/* 读取中断引脚的状态 */
	pinval = gpio_get_value(pindesc->pin);

	/* 确定按键值 */
	if (pinval)
	{
		/* 松开: 最后一个参数:0-松开, 1-按下 */
		input_event(buttons_dev,EV_KEY,pindesc->key_val,0);		//上报事件
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev,EV_KEY,pindesc->key_val,1);		//上报事件
		input_sync(buttons_dev);
	}
}

static int buttons_init(void)
{
	int i;
	
	/* 1、分配input_dev结构体 */
	buttons_dev = input_allocate_device();
	if(!buttons_dev)
		return -ENOMEM;
	
	/* 2、设置  */
	/* 2.1、能产生哪类事件  */
	set_bit(EV_KEY,buttons_dev->evbit);		//产生按键类事件
	set_bit(EV_REP,buttons_dev->evbit);		//产生重复类事件,按下按键不松开时会一直有值
	
	/* 2.2、能产生这些操作里到那些事件:按键L、S、ENTER、左边到shift  */
	set_bit(KEY_L,buttons_dev->keybit);		//相当于按按键l
	set_bit(KEY_S,buttons_dev->keybit);		//相当于按按键S
	set_bit(KEY_ENTER,buttons_dev->keybit);		//相当于按按键RNTER
	set_bit(KEY_LEFTSHIFT,buttons_dev->keybit);		//相当于按按键SHIFT
	
	/* 3、注册  */
	input_register_device(buttons_dev);
	
	/* 4、硬件相关  */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for(i = 0; i < 4; i ++)
	{
		request_irq(pins_desc[i].irq,button_irq,IRQ_TYPE_EDGE_BOTH,pins_desc[i].name,&pins_desc[i]);
	}
	
	return 0;
}

static void buttons_exit(void)
{
	int i;
	for(i = 0; i < 4; i ++)
	{
		free_irq(pins_desc[i].irq,&pins_desc[i]);
	}
	
	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);
}

module_init(buttons_init);
module_exit(buttons_exit);

MODULE_LICENSE("GPL");


Makefile:

obj-m := buttons.o

KER_DIR := /work/work/linux/


all:
	$(MAKE) -C $(KER_DIR) M=`pwd` modules
#	cp first_drv.ko /work/tftpboot/
	cp buttons.ko /work/nfsroot/
	
clean:
	make -C $(KER_DIR) M=`pwd` modules clean

运行结果:

/ # hexdump /dev/event1
0000000 0052 0000 3546 000c 0001 0026 0001 0000
0000010 0052 0000 355d 000c 0000 0000 0000 0000
0000020 0053 0000 c38e 0000 0001 0026 0002 0000
0000030 0053 0000 c3a0 0000 0000 0000 0001 0000
0000040 0053 0000 11ac 0001 0001 0026 0000 0000
0000050 0053 0000 11bf 0001 0000 0000 0000 0000

/ # exec 0</dev/tty1
/ # s3cfb_blank: blank(mode=0, info=c049c4e4)
s3cfb_pan_display: s3c_fb_pan_display(var=c789f81c, info=c049c4e4)
ls
app             fs.tgz          sys
bin             lib             test
buttons.ko      linuxrc         tmp
buttons_drv.ko  mnt             usr
dev             proc
etc             sbin
/ #

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值