這一章我們linux input 子系統
在linux中,input子系統有三部分組成:驅動層,輸入子系統核心,事件處理層.一個輸入時間,比如按鍵按下通過:驅動->input core->event handler->user space的順序到達用戶空間的應用程序
驅動層:將底層的硬件輸入轉化爲統一的事件形式,向輸入核心(input core)彙報
核心層:承上啓下,爲驅動層提供輸入設備接口與操作藉口;通知事件處理層進行時間處理;在/proc下產生相應的設備信息.
事件處理層:樹要是和用戶空間打交道,(Linux中一切皆是文件,由於input驅動設備有fops接口,在/dev下會產生相應的設備文件nod,訪問這些文件通過應用程序來完成).
實現input驅動的核心工作就是:向系統報告按鍵等輸入事件(event,通過input_event結構描述),不再關心文件操作接口(不用手動的完成file_operations結構體的實例化).報告事件通過核心層和事件處理層直接到達用戶.
一些input驅動相關的函數:
設備註冊
註冊 int input_regesiter_device(struct input_dev *dev)
註銷 void input_unregister_device(struct input_dev *dev)
設置事件
set_bit()告訴input輸入系統支持那些設備,哪些按鍵等
比如
set_bit(EV_KEY,button_dev.evbit)(其中button_dev是struct input_dev類型)
報告事件:
用於報告EV_KEY,EV_REL,EV_ABS事件的函數分別爲:
報告按鍵事件void input_report_key(struct input_dev *dev,unsigned int code,int value)
報告重複事件void input_report_rel (struct input_dev *dev, unsigned int code, int value)
報告絕對座標事件void input_report_abs (struct input_dev *dev, unsigned int code, int value)
報告結束後,同步事件
input_sync()同步用於告訴input core 子系統報告結束
實例 ,我們做一個按鍵input驅動,該驅動首先在設備樹文件裏查找按鍵的節點,獲取按鍵的屬性值,通過中斷裏開啓定時器定時10ms進行按鍵機械消抖,在定時器裏將按鍵事件彙報給input核,然後編寫應用程序實現按鍵事件的讀取
在設備樹根/下建立一個key節點,在iomux節點下建立一個pinctl節點,包括compatible屬性,按鍵對應的gpio口,中斷,名稱等等
設備樹文件:
key-myz {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myz-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_BOTH>;//both_edge
key-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
pinctrl_key: keygrp {
fsl,pins = <
//MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0xF080/* KEY0 */
MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0xF080/* KEY0 */
>;
};
驅動程序:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct key_input
{ //key屬性
struct input_dev *input_dev; //input設備
struct device_node *nd; //設備節點
struct timer_list timer; //定時器句柄
irqreturn_t (*handler)(int, void *); //定時器回調函數
int pin; //key的引腳編號
int irqnum; //key對應的中斷
int value; //key的值
};
static struct key_input key; //聲明一個實例
static irqreturn_t key_handler(int irq, void *dev_id)
{ //中斷回調函數
struct key_input *dev = (struct key_input *)dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); //主要實現了一個10ms的定時任務,用於按鍵機械防抖
return IRQ_RETVAL(IRQ_HANDLED);
}
static void timer_fun(unsigned long arg)
{ //定時器回調函數
unsigned char value = 0;
struct key_input *dev = (struct key_input *)arg;
value = gpio_get_value(dev->pin); //獲取按鍵值
if (value == 0)
{
input_report_key(dev->input_dev, dev->value, 1); //上報按鍵的值,如果value == 0的時候就上報1,否則上報0;
input_sync(dev->input_dev); //同步上報的信息
}
else
{
input_report_key(dev->input_dev, dev->value, 0);
input_sync(dev->input_dev);
}
}
static int __init mkey_init(void)
{ //insmod 的時候調用
int res = 0;
printk("key _init\n");
key.input_dev = input_allocate_device(); //申請一個input設備
key.input_dev->name = "key-myz";
key.nd = of_find_node_by_path("/key-myz"); //在設備樹根/下找key-myz節點
if (key.nd == NULL)
{
printk("key node is not find \n");
return -EINVAL;
}
else
{
printk("key node is success\n");
}
key.pin = of_get_named_gpio(key.nd, "key-gpio",0); //通過key-myz節點找出""key-gpio"的引腳編號
if (key.pin < 0)
{
printk("can not get key pin\n");
}
else
{
printk("get key pin success ! pin = %d \n",key.pin);
}
res = gpio_request(key.pin, "KEY0"); //通過按鍵引腳編號申請一個gpio
if(res){
printk("gpio_requested failed res = %d\n",res);
}else{
printk("gpio_requested successed !\n");
}
gpio_direction_input(key.pin); //設置按鍵引腳爲輸入
key.irqnum = irq_of_parse_and_map(key.nd, 0); //根據key-myz節點解析一箇中斷號
printk("irqnumber is %d\n",key.irqnum);
key.handler = key_handler; //中斷函數賦值
key.value = KEY_0;
//request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
res = request_irq(key.irqnum, key.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key-mayunzhi", &key); //根據中斷號申請中斷並且註冊中斷回調函數,上升下降沿產生中斷
if (res < 0)
{
printk("irq request failed ! res = %d\n",res);
gpio_free(key.pin );
return -EINVAL;
}
else
{
printk("irq request successed\n");
}
init_timer(&key.timer); //初始化定時器
key.timer.function = timer_fun; //???定時器中斷函數
key.input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); //按鍵事件和重複事件賦值
input_set_capability(key.input_dev, EV_KEY, KEY_0); //註冊按鍵事件和重複事件
res = input_register_device(key.input_dev); //註冊一個input設備
if (res)
{
printk("input device register failed\n");
return res;
}
else
{
printk("input device register successed\n");
}
return 0;
}
static void __exit mkey_exit(void)
{ //rmmod 的時候調用
printk("key _exit\n");
del_timer_sync(&key.timer);
free_irq(key.irqnum, &key);
input_unregister_device(key.input_dev); //卸載input設備
input_free_device(key.input_dev);
}
module_init(mkey_init);
module_exit(mkey_exit);
MODULE_LICENSE("GPL");
應用程序的編寫:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/ioctl.h>
static struct input_event inputevent;
int main(int argc,char *argv[]){
int fd;
int res;
char *filename;
if(argc != 2){
printf("err usage \n");
}
filename = argv[1];
fd = open(filename ,O_RDWR);
if(fd<0){
printf("can not open file :%s\n",filename);
return -1;
}
while(1){
res = read(fd ,&inputevent,sizeof(inputevent));
if(res >0){
switch(inputevent.type){
case EV_KEY:
if(inputevent.code <BTN_MISC){
printf("key %d is %s\n",inputevent.code,inputevent.value?"pressed":"released");
}else{
printf("key %d is %s\n",inputevent.code,inputevent.value?"pressed":"released");
}
break;
case EV_REL:
printf("it is a rel event\n");
break;
}
}else{
printf("read file %s failed\n",filename);
}
}
return 0;
}
makefile 文件內容
KERNELDIR :=/home/mayunzhi/linux/100ask_imx6ull-sdk/Linux-4.9.88
CURRENT_PATH :=$(shell pwd)
ARM-CC :=arm-linux-gnueabihf-gcc
APP :=app
DRV :=key
obj-m :=$(DRV).o
build:kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
rm -f ./app
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
app:
$(ARM-CC) $(APP).c -o $(APP)
mv:
sudo mv ./app *.ko /home/mayunzhi/linux/nfs/rootfs/lib/modules/4.1.15
在驅動文件夾下依次執行make ,make app ,make mv
在kernel根文件目錄下 執行 make dtbs
,並且拷貝生成的xxx.dtb文件到tftp文件夾,以便通過tftp啓動內核和設備樹可執行文件
重啓設備,加載設備樹,將key.ko app
文件放在開發板文件系統中/lib/module/4.1.15
目錄中
cd /lib/module/4.1.15
執行insmod key.ko沒有錯誤的話說就說明加載成功了,然後執行./app,通過應用程序進行解析/dev/input/event2
中的按鍵事件,也可以執行hexdump /dev/input/event2
來實現原始數據的讀取
實驗現象
卸載驅動 rmmod key.ko