基于Tiny4412的Linux按键输入子系统驱动的实现(一)

本文介绍了基于Tiny4412 Cortex-A9开发板的Linux按键输入子系统驱动实现。首先,文章阐述了前期开发环境的搭建,包括Ubuntu 12.04交叉开发环境、Linux 3.0.86内核及Qtopia2.2.0 GUI系统。接着,详细讲解了如何在内核中注释掉默认的按键平台设备以避免中断冲突。此外,对Linux输入子系统进行了简要介绍,强调其抽象通用的输入事件管理和VFS接口。最后,探讨了按键输入子系统驱动的编写,涉及input_dev结构体的分配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要包含的章节:
    一、前期的准备工作
    二、Linux输入子系统的简单介绍
    三、基于输入子系统的按键驱动的实现


一、前期的准备工作
    1、基本的开发环境

          交叉开发环境  : Ubuntu12.04
          Linux内核版本  : Linux-3.0.86
          GUI系统  : Qtopia2.2.0
          开发板  : 友善之臂的Tiny4412(Cortex-A9)
     2、内核的配置
         由于默认的内核在初始化时已经将按键对应的GPIO口添加到平台设备当中,每当内核启动的时候这几个按键都会自动去请求中断,导致中断被占用。所以在编写自己的按键输入子系统驱动之前,要注释掉这个平台设备。
         这个平台设备对应的代码在内核中位置是:arch\arm\mach-exynos\mach-tiny4412.c,对应的行数为 :2968,代码如下所示:

static struct platform_device tiny4412_input_device = {
	.name	= GPIO_EVENT_DEV_NAME,
	.id		= 0,
	.dev	= {
		.platform_data = &tiny4412_input_data,
	},
};
      对这个文件进行修改,将 mach-tiny4412.c的3315行注释掉即可。


二、Linux输入子系统的简单介绍
        1、输入子系统

        常见的输入设备有按键、键盘、鼠标、触摸屏等,它们的工作原理虽然都各不相同,但是工作机制却是相似的。一般来讲都是通过触发中断,然后让CPU来读取键值、坐标值等。显然,在这些工作中,只有中断、读键值、坐标值等是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的VFS(Virtual File System)接口对输入设备是通用的。所以,内核把这些输入事件相似的地方给抽象了出来,设计出了输入子系统。
        2、核心函数
       核心函数在内核的位置:drivers\input\input.c 和 include\linux\input.h
       2.1 分配、释放一个输入设备

struct input_dev *input_allocate_device(void)
void input_free_device(struct input_dev *dev)
       2.2 注册、注销输入设备

int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
       2.3 报告输入事件的接口

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
/* 报告键值 */
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
/* 报告相对坐标 */
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
/* 报告绝对坐标 */
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
/* 报告同步事件 */
static inline void input_sync(struct input_dev *dev)
        3、输入子系统驱动的一般编写步骤
           a、分配一个input_dev结构体
           b、设置input_dev结构体  :  支持哪类事件, 支持该类事件中的那些事件
           c、注册input_dev结构体
           d、硬件相关的操作  : 中断申请,定时器的设置等

三、基于输入子系统的按键驱动的实现
     3.1 Tiny4412开发板上的按键电路原理图


      通过对原理图进行分析,可以看出当按键松开的时候IO口对应的是高电平,当按键按下的时候对应的是低电平。按键对应的GPIO是 :        
       * XEINT26----GPX3_2      

       * XEINT27----GPX3_3   

       * XEINT28----GPX3_4    

       * XEINT29----GPX3_5
       如下图所示:

        3.2 按键输入子系统驱动的编写
        3.2.1 分配一个名为buttons_dev的input_dev结构体

buttons_dev = input_allocate_device();
if(!buttons_dev)
{
	printk("input_allocate_device error!\n");
	return -ENOMEM;
}
       3.2.2 设置buttons_dev结构体

/* 2.1、支持哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);

/* 2.2、支持该类事件中的那些事件 */
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
	set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
}
       3.2.3 注册这个结构体

input_register_device(buttons_dev);
       3.2.4 硬件相关的操作

/* 4、硬件相关的操作 
*	  为每个按键申请一个中断,共用中断处理函数
*	  按键触发方式为双边沿触发
*/
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
	irq = gpio_to_irq(buttons_desc[i].gpio);
	request_irq(irq, yl_buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, buttons_desc[i].name, (void*)&buttons_desc[i]);	
}
       在硬件操作的核心是请求中断,中断的核心是 yl_buttons_irq 这个函数,这个函数的代码如下所示:

/* 按键中断处理程序 */
static irqreturn_t yl_buttons_irq(int irq, void *devid)
{
	struct yl_buttons_desc *buttons_desc = (struct yl_buttons_desc *)devid;
	int pinval = gpio_get_value(buttons_desc->gpio);

	if(pinval == 1)	/* 判断按键是按下还是松开 */
	{
		/* 松开 */
		input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 0);
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 1);
		input_sync(buttons_dev);
	}
	
	return IRQ_HANDLED;
}
        当发生按键中断时将触发按键中断程序,根据获得的对应按键的值来确定按键是按下还是松开,然后分别进行事件上报和事件同步。


附录:本节实现的按键输入子系统的完整代码如下所示。
/* 包含的头文件 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>

#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>

/* 输入子系统需要的头文件 */
#include <linux/input.h>

/**	按键映射:
  *		XEINT26		----	GPX3_2
  *		XEINT27		----	GPX3_3
  *		XEINT28		----	GPX3_4
  *		XEINT29		----	GPX3_5
  */

/* 定义一个结构体用来对输入按键进行描述 */
struct yl_buttons_desc{
	int  gpio;		// 表示对于的按键的引脚
	char *name;		// 表示对应的按键请求中断时的中断名
	int  key_code;	// 表示按键在输入子系统中对应的键值
};

/* 定义一个描述按键的数组 */
static struct yl_buttons_desc buttons_desc[] = {
		{EXYNOS4_GPX3(2), "yl_buttons_L", 		  KEY_L},
		{EXYNOS4_GPX3(3), "yl_buttons_S",		  KEY_S},
		{EXYNOS4_GPX3(4), "yl_buttons_ENTER", 	  KEY_ENTER},
		{EXYNOS4_GPX3(5), "yl_buttons_LEFTSHIFT", KEY_LEFTSHIFT},
};

/* 定义一个输入子系统的结构体指针变量 */
static struct input_dev *buttons_dev;

/* 按键中断处理程序 */
static irqreturn_t yl_buttons_irq(int irq, void *devid)
{
	struct yl_buttons_desc *buttons_desc = (struct yl_buttons_desc *)devid;
	int pinval = gpio_get_value(buttons_desc->gpio);

	if(pinval == 1)	/* 判断按键是按下还是松开 */
	{
		/* 松开 */
		input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 0);
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 1);
		input_sync(buttons_dev);
	}
	
	return IRQ_HANDLED;
}

/* 入口函数 */
static int __init yl_buttons_init(void)
{
	int irq;
	int i;

	/* 1、分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();
	if(!buttons_dev)
	{
		printk("input_allocate_device error!\n");
		return -ENOMEM;
	}

	/* 2、设置input_dev结构体 */
	/* 2.1、支持哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);

	/* 2.2、支持该类事件中的那些事件 */
	for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
	{
		set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
	}

	/* 3、注册input_dev结构体 */
	input_register_device(buttons_dev);

	/* 4、硬件相关的操作 
	 *	  为每个按键申请一个中断,共用中断处理函数
	 *	  按键触发方式为双边沿触发
	 */
	for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
	{
		irq = gpio_to_irq(buttons_desc[i].gpio);
		request_irq(irq, yl_buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, buttons_desc[i].name, (void*)&buttons_desc[i]);	
	}
	
	return 0;
}

/* 出口函数 */
static void __exit yl_buttons_exit(void)
{
	int irq;
	int i;

	/* 释放申请的按键中断 */
	for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
	{
		irq = gpio_to_irq(buttons_desc[i].gpio);
		free_irq(irq, (void*)&buttons_desc[i]);	
	}

	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);
}

module_init(yl_buttons_init);
module_exit(yl_buttons_exit);

MODULE_LICENSE("GPL");








1 引言 当前,由于Linux资源完全公开,使得Linux的发展日益广泛快速。基于Linux的各种应用已逐渐深入日常生活的方方面面,尤其是在嵌入式领域,由于内核可裁减定制,因此可随意地根据用户需求进行整个系统的定制与重构。其中,我们可以通过对各种标准外部设备的驱动进行改造,从而实现用户对标准设备的特定需求,例如可以通过对键盘的模拟来实现操作的自动化,从而可以避免重复的键盘操作。 2 Linux内核支持的外部调用接口 由于Linux内核作为系统最深层次的核心,因此外部的开发人员并不能直接对内核进行操作。然而在些应用程序的开发过程中,又不得不使用内核的某些功能,因此就提供了些外部接口供开发人员直接与底层内核打交道。 2.1 中断 在Linux 下,硬件中断叫做IRQ(Interrupt Requests)。有两种IRQ,短类型和长类型。短IRQ需要很短的时间,在此期间机器的其他部分被锁定,而且没有其他中断被处理。个长IRQ需要较长的时间,在此期间可能发生其他中断(但不是发自同个设备)。如果可能的话,最好把个中段声明为长类型。如果CPU接到个中断,它就会停止切工作(除非它正在处理个更重要的中断,在这种情况下要等到更重要的中断处理结束后才会处理这个中断),把相关的参数存储到栈里,然后调用中断处理程序。这意味着在中断处理程序本身中有些事情是不允许的,因为这时系统处在个未知状态。解决这个问题的方法是让中断处理程序做需要马上做的事,通常是从硬件读取信息或给硬件发送信息,然后把对新信息的处理调度到以后去做。 实现的方法是在接到相关的IRQ(在Intel平台上有16个IRQ)时调用中断处理程序。这个函数接到IRQ号码、函数名、标志、个/proc/interrupts的名字和传给中断处理程序的个参数。标志中可以包括 SA_SHIRQ来表明你希望和其他处理程序共享此IRQ(通常很多设备公用个IRQ),或者个SA_INTERRUPT表明这是个紧急中断。这个函数仅在此IRQ没有其他处理程序或需要共享所有处理程序时才会成功运行。 2.2 系统调用 系统调用发生在用户进程,通过些特殊的函数来请求内核提供服务。这时,用户进程被挂起,内核验证用户请求,尝试执行并把结果反馈给用户进程,接着用户进程重新启动。般当前系统的系统调用作为张表sys_call_table进行定义的,是由指向实现各种系统调用的内核函数的函数指针组成的表。具体参数参见Linux内核源代码arch/i386/kernel/entry.S文件中: ENTRY(sys_call_table) l long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/ l long SYMBOL_NAME(sys_exit)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值