• layer == 层次
  • /proc --> 记录系统信息

第一节

涉及函数与结构体: struct input_dev struct evdev struct evdev_client struct input_event of_find_node_by_path() irq_of_parse_and_map() input_event() input_sync() input_allocate_device() input_free_device() __set_bit() input_register_device() input_unregister_device() get_irqno_from_node() request_irq() ioremap() of_get_next_child() of_get_named_gpio() gpio_get_value() input_report_key() get_all_child_from_node() of_property_read_u32() of_property_read_string() free_irq() ======1,什么是输入设备====== 按键.鼠标,触摸屏,键盘.............. ======2,输入子系统的3层框架======= ------------------------------------------------------------------------------------------------------------------------------------ **APP 				level** [步骤1]>> fd = open("/dev/event1", O_RDWR); open打开/event1文件时会创建一个struct file 结构体,存放文件读写权限,阻塞/非阻塞,文件路径,主设备号,次设备号等文件信息,
       各个文件对应的struct file对象存放在fd_table数组中,数组内容存放file对象的入口地址,fd就是数组下标(比如:打开文件1返回的fd=10,
       表示fd_table数组中第10位存放的地址就是文件1的file对象) 为了访问evdev->fops,file对象中也有f_ops对象来对接evdev->fops接口,每个evdev存储了主设备号,次设备号,evdev->fops等,
       file也存储了主次设备号,evdev在注册的时候会加载到cdev设备链表,当应用层调用open函数时,会自动遍历cdev设备链表的所有设备的主次设备号,
       如果file对象的主次设备号与设备的主次设备号匹配,就会将file对象的f_ops指向evdev->fops接口,就能通过file对象直接调用到evdev->fops接口

	   filed对象中还继承evdev_client对象,该对象中存储了input_dev上传的数据,文件io接口能直接从file->evdev_client中取到上传的数据 ; 调用open函数 --> 调用到file->f_ops->xxx_open() ;在该函数中evdev_client对象记录到file中 [步骤2]>>read()write() 由于open函数获取到设备的io接口并且保存放file对象,所以read,write函数就能直接操作open返回的file对象直接获取到evdev->fops,
       那么如果获取到file对象呢,就是通过fd_table的下标fd,找到file对象,进而获取到file的fops,进而获得evdev->fops read()的流程 --> 调用read() -->调用file->f_ops->read() --> 先获取到evdev_client对象 --> 新建一个input_event数据包 --> 从evdev_client缓冲区获取数据,存放在 input_event数据包 --> 最后将数据上报给用户 --> 统计上报多少数据作为返回值 (如果文件io是阻塞模式,没有数据,就会休眠等待) ------------------------------------------------------------------------------------------------------------------------------------ ** input	 	handler    level ** [源码 --> evdev.c] [步骤 1 ] >> 调用input core level的return input_register_handler( )接口 //注册input_handler [步骤1.1] >> INIT_LIST_HEAD(&handler->h_list); //初始化input_handler的h_list,这个h_list后面介绍 [步骤1.2] >> list_add_tail(&handler->node, &input_handler_list); 把当前input_handler添加到input_handler_list链表,也就是注册 [步骤1.3] >> list_for_each_entry(dev, &input_dev_list, node) ;input_attach_handler(dev, handler); 将input_dev_list链表中的dev节点一一与当前input_handler进行比对,input_handle->id_table里面也有evbit【】,keybit【】,
		如果与input_dev里面的evbit【】,keybit【】比对成功,表示当前inputhandle能处理evbit表示的类型数据与keybit类型的键值,
		匹配成功后,调用当前input_handler->connect函数 [步骤1.4] >> handler->connect函数 == evdev_connect函数 [步骤1.4.1] >> evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); 创建evdev对象,evdev对象包含了device对象(用来创建设备节点),还包含了一个input_dev对象用于存储匹配的input_dev对象,
		也还包含一个input_handler对象也是保存匹配到的input_handler对象信息,还包含了一个h_list链表,还包含了evdev_client对象,用来存储input_dev上传的数据;并用kmalloc实例化evdev对象 [步骤1.4.2] >> minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); 因为前面创建了class类,但是还没创建设备节点,从6465开始为这个evdev找一个没有使用的子设备号minor,,
		创建设备节点/event1,/event2....,如果子设备号是65,那么设备节点就是65-64=“event1” [步骤1.4.3] >> list_add_tail_rcu(&handle->d_node, &dev->h_list);list_add_tail_rcu(&handle->h_node, &handler->h_list); evdev对象中的h_list链表就是2根双向链表,一根input_dev与evdev->input_handler连起来,一根input_handler与evdev->input_dev连起来,
		为什么要用h_list把两个原版input_dev,input_handler与复制版本evdev->input_dev,evdev->input_handler连接起来呢?
		方便下次input_dev直接顺着h_list找到input_handler,就不用第二次匹配 [步骤1.4.3] >> 最后呢会实现evdev->fops对象,对应的文件io操作
		evdev->fops等待应用层open接口创建的file对象匹配file对象与evdev的主次设备号,如果匹配成功就将evdev->fops指向file对象的fops,实现了应用层io接口调用内核层的evdev->fops [步骤1.4.4] >> evdev_open( ) //evdev_open( )指向file->f_ops中的open接口 < -- >应用层调用open( ) 当打开/event0节点文件时,该函数才会去创建evdev_client对象,该对象中有个缓冲区,存放input_dev上传的数据; 将evdev_client对象记录到evdev中 ; evdev_client对象记录到file中,方便read,write接口使用 [步骤1.5] >> input_wakeup_procfs_readers(); //将该input_handler对象添加到/porc/input/handler文件中 ------------------------------------------------------------------------------------------------------------------------------------ ** input 			core 			level ** [源码 --> input.c] [步骤1] >> class_register(&input_class); //创建name = "input"的class [步骤2] >> input_proc_init(); proc_mkdir在/proc目录下创建文件夹"bus/input" proc_create创建文件"devices"存放输入子系统创建了哪些devices  
		proc_create创建文件"handlers"存放输入子系统创建了那些handler [步骤3] >> register_chrdev(INPUT_MAJOR, "input", &input_fops); //注册设备号"input" ------------------------------------------------------------------------------------------------------------------------------------ ** input 		device 		level ** [驱动工程师编写] [步骤1] >> input_allocate_device(); //allocate memory for new input device [步骤2] >> 将input_dev对象注册(添加)到input_dev_list链表 [步骤3] >> 将注册的input_dev对象一一与input_handler_list链表进行比对,也是比对evbit【】,keybit【】,
		一致表示handler能处理这类数据,对比成功后也还是调用对比成功的handler->connect函数, [步骤4] >>/porc/bus/input/device文件添加注册的input_dev对象 [步骤5] >> input_report_key(inputdev, pdesc->key_code, 0); 上传数据 input_handle_event(dev, type, code, value); // input_dev获取到数据暂存到input_value --> 从input_value 中获取到input_handle --> input_handler拿到最终调用handler中events() --> 将数据封装成input event对象获取到缓冲evdev_client --> 如果调用 input_sync()则唤醒等待队列 [步骤6] >> input_sync(inputdev);上报数据结束,唤醒等待队列,表示输入设备上报数据完毕 ------------------------------------------------------------------------------------------------------------------------------------ ======3,如何打开内核的输入子系统的功能======= Device Drivers ---> Input device support ---> () Generic input layer (needed for keyboard, mouse, ...) // input.c () Event interface //input handler层--evdev.c ==================input_dev结构体=============== struct input_dev {  //name,phys,uniq,id都可以在sys/中查看 const char *name;//输入设备名字 const char *phys; const char *uniq; struct input_id id; //数组的每一位表示存储什么类型的数据 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //数组的每一位表示存储键值 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //表示能够产生哪种相对坐标数据 ; 相对坐标 == 鼠标坐标 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //表示能够产生哪种绝对坐标数据, 绝对坐标 == 屏幕坐标(x,y) unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 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; //链表形式 } ==================evdev 结构体=============== struct evdev {  int open; int minor; struct input_handle handle; wait_queue_head_t wait; struct evdev_client __rcu *grab; struct list_head client_list; spinlock_t client_lock; /* protects client_list */ struct mutex mutex; struct device dev; bool exist; }; ==================evdev_client 结构体=============== struct evdev_client {  unsigned int head; unsigned int tail; unsigned int packet_head; /* [future] position of the first element of next packet */ spinlock_t buffer_lock; /* protects access to buffer, head and tail */ struct wake_lock wake_lock; bool use_wake_lock; char name[28]; struct fasync_struct *fasync; struct evdev *evdev; struct list_head node; int clkid; unsigned int bufsize; struct input_event buffer[]; }; ==================input_event 结构体=============== struct input_event {  struct timeval time; __u16 type; __u16 code; __s32 value; }; 

1.1 input_system_drv.c

功能:通过输入子系统实现按键1的识别 #include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/of.h> #include <linux/of_irq.h> #include <asm/io.h> #define GPXCON_REG 0x11000C20 //控制寄存器 struct input_dev *inputdev; //输入设备结构体 int irqno; //中断号 void *reg_base; //寄存器map地址 //从设备树节点获取中断 int get_irqno_from_node(void) {  //查找设备树节点 ---存储---> 设备节点对象 struct device_node *np = of_find_node_by_path("/key_int_node"); if(np){  printk("find node ok\n"); }else{  printk("find node failed\n"); } // 通过设备节点对象去获取到中断号码 int irqno = irq_of_parse_and_map(np, 0); printk("irqno = %d\n", irqno); return irqno; } //按键中断服务函数 irqreturn_t input_key_irq_handler(int irqno, void *devid) {  printk("-------%s-------------\n", __FUNCTION__); //读取数据寄存器 int value = readl(reg_base + 4) & (1<<2); if(value){ //抬起 /* 参数1:输入设备结构体对象 <--> (上传到inputdev对象) 参数2: 上报的是数据是什么设备类型 <--> (EV_KEY按键数据 ; EV_SW开关数据 ;EV_LED灯泡数据 ;EV_SND声音数据.....) 参数3: 具体哪个设备 <--> (KEY_Q按键Q , KEY1按键1 ;) 参数4:设备的值是什么 <--> (硬件层 ---0---> device层) */ input_event(inputdev, EV_KEY, KEY_POWER, 0); //硬件层向device层上报数据 input_sync(inputdev); //上报数据结束  }else{  input_event(inputdev, EV_KEY, KEY_POWER, 1);//硬件层向device层上报数据 input_sync(inputdev); //上报数据结束 } return IRQ_HANDLED; } //模块加载 static int __init simple_input_init(void) {  int ret; //1,分配一个输入设备对象 inputdev = input_allocate_device(); if(inputdev == NULL) {  printk(KERN_ERR "input_allocate_device error\n"); return -ENOMEM; } //2.1,初始化 将evbit数组的EV_KEY位置1 --> 当前设备能够产生EV_KEY类型的数据 __set_bit(EV_KEY, inputdev->evbit); //2.2,初始化 将keybit数组的KEY_POWER位置1 --> 当前的设备是KEY_POWER __set_bit(KEY_POWER, inputdev->keybit); //3,注册输入设备对象 ret = input_register_device(inputdev); if(ret != 0) {  printk(KERN_ERR "input_register_device error\n"); goto err_0; } //获取中断号与申请中断服务函数 irqno = get_irqno_from_node(); ret = request_irq(irqno, input_key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL); if(ret != 0) {  printk("request_irq error\n"); goto err_1; } //地址映射 reg_base = ioremap(GPXCON_REG, 8); return 0; err_1: input_unregister_device(inputdev); err_0: input_free_device(inputdev); return ret; } static void __exit simple_input_exit(void) {  iounmap(reg_base); //地址断开映射 free_irq(irqno, NULL); //释放中断号资源 input_unregister_device(inputdev); //注销输入设备 input_free_device(inputdev); //释放输入设备资源 } module_init(simple_input_init); module_exit(simple_input_exit); MODULE_LICENSE("GPL"); 

1.2 input_system_text.c

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(void) {  int fd; int ret; struct input_event event; //输入子系统的handler level的数据包  fd = open("/dev/event1", O_RDWR); //打开输入子系统为我们申请的输入设备event1 if(fd < 0) {  perror("open"); exit(1); } while(1) {  ret = read(fd, &event, sizeof(struct input_event)); if(ret < 0) {  perror("read"); exit(1); } if(event.type == EV_KEY){  if(event.code == KEY_POWER){  if(event.value){  //按下 printf("__APP_USER__ : power pressed\n"); }else{  printf("__APP_USER__ : power up\n"); } } } } close(fd); return 0; } 

1.3 Makefile文件

ROOTFS_DIR = /opt/4412/rootfs  #开发板nsf目录

MODULE_NAME = simple_input
APP_NAME = simple_input_test

CROSS_COMPILE = /home/george/Linux_4412/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/george/Linux_4412/kernel/linux-3.14 CUR_DIR = $(shell pwd) all : make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules
	$(CC) $(APP_NAME).c -o $(APP_NAME) install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += $(MODULE_NAME).o
endif



第二节 设备树+输入子系统实现所有按键驱动

2.1设备树文件

key_int_node{  compatible = "test_key"; #address-cells = <1>; #size-cells = <1>; key_int@0 {  //子节点0 key_name = "key2_power_eint"; //自定义按键名 key_code = <116>; //键值 = int类型 gpio = <&gpx1 1 0>; reg = <0x11000C20 0x18>; //寄存器地址 interrupt-parent = <&gpx1>;//gpx1_1  interrupts = <1 0>; //0-->中断触发方式 }; key_int@1 {  //子节点1 key_name = "key3_vup_eint"; //自定义按键名 key_code = <115>; //键值 = int类型 gpio = <&gpx1 2 0>; reg = <0x11000C20 0x18>; //寄存器地址 interrupt-parent = <&gpx1>;//gpx1_2  interrupts = <2 0>;//0-->中断触发方式 }; key_int@2 {  //子节点2 key_name = "key4_vdown_eint";//自定义按键名 key_code = <114>; //键值 = int类型 gpio = <&gpx3 2 0>; reg = <0x11000C60 0x18>; //寄存器地址 interrupt-parent = <&gpx3>;//gpx3_2 interrupts = <2 0>;//0-->中断触发方式 }; }; 

2.2 驱动文件:input_system_drv.c

#include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/of_gpio.h> #include <asm/io.h> #define KEY_NUMS 3 //设计一个对象出来 --> 存储设备树信息 struct key_desc{  char *name; <-->:key_name = "key2_power_eint" int irqno; <--> 需要irq_of_parse_and_map()解析才能得到中断号 int key_code; <-->:key_code = <116>; //键值 int gpionum; <-->:gpio = <&gpx1 1 0>; void *reg_base; <-->:reg = <0x11000C20 0x18>; //寄存器地址 struct device_node *cnp; //子节点 }; struct key_desc all_key[KEY_NUMS]; //三个结构体对象 struct input_dev *inputdev; //获取设备树的所有按键节点 void get_all_child_from_node(void) {  //获取设备树按键的父亲节点 struct device_node *np = of_find_node_by_path("/key_int_node"); if(np){  printk("find node ok\n"); }else{  printk("find node failed\n"); } int i = 0; all_key[i].cnp= of_get_next_child(np, NULL);//由于父亲节点下的第一个子节点前面没有子节点 --> NULL i++; //i==1 do{  //父亲节点下轮询子节点(向定位父节点-->再定位前一个兄弟节点all_key[i-1].cnp --> 得到下一个子节点) all_key[i].cnp= of_get_next_child(np, all_key[i-1].cnp); if(all_key[i].cnp!= NULL){  i++; } }while(of_get_next_child(np, all_key[i-1].cnp) != NULL);//没有子节点了 --> 退出轮询 } //按键中断服务函数 irqreturn_t input_key_irq_handler(int irqno, void *devid) {  printk("-------%s-------------\n", __FUNCTION__); //区分不同的按键 struct key_desc *pdesc = (struct key_desc *)devid; //接收传递给中断服务函数的数据 int gpionum = of_get_named_gpio(pdesc->cnp, "gpio", 0); //直接通过gpio获取按键状态 int value = gpio_get_value(gpionum); if(value){ //抬起 input_report_key(inputdev, pdesc->key_code, 0); input_sync(inputdev);//上报数据结束 }else{  input_report_key(inputdev, pdesc->key_code, 1); input_sync(inputdev);//上报数据结束 } return IRQ_HANDLED; } static int __init simple_input_init(void) {  //编写输入子系统代码 /* 1,分配一个input device对象 2, 初始化input device对象 3,注册input device对象 */ int ret; inputdev = input_allocate_device(); if(inputdev == NULL) {  printk(KERN_ERR "input_allocate_device error\n"); return -ENOMEM; } get_all_child_from_node(); //添加设备信息--/sys/class/input/eventx/device/ inputdev->name = "simple input key"; inputdev->phys = "key/input/input0"; inputdev->uniq = "simple key0 for 4412"; inputdev->id.bustype = BUS_HOST; inputdev->id.vendor =0x1234 ; inputdev->id.product = 0x8888; inputdev->id.version = 0x0001; //当前设备能够产生按键数据--将某个bit置1 __set_bit(EV_KEY, inputdev->evbit); //表示当前设备能够产生power按键 //__set_bit(KEY_POWER, inputdev->keybit); //另外一种设置bit的方式 int i; for(i=0; i<KEY_NUMS; i++) {  //设置keybit,支持哪些按键 // 按键值从设备设备树 int code; struct device_node *cnp = all_key[i].cnp; of_property_read_u32(cnp,"key_code", &code); __set_bit(code, inputdev->keybit); all_key[i].key_code = code; //先记录下来 int irqno; irqno = irq_of_parse_and_map(cnp, 0); all_key[i].irqno = irqno;//先记录下来 char *key_name ; of_property_read_string(cnp, "key_name", &key_name); all_key[i].name = key_name; printk("name = %s, code = %d,irqno = %d\n", key_name, code,irqno); int irqflags = IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING; ret = request_irq(irqno, input_key_irq_handler, irqflags, key_name, &all_key[i]); if(ret != 0) {  printk("request_irq error\n"); goto err_0; } } ret = input_register_device(inputdev); if(ret != 0) {  printk(KERN_ERR "input_register_device error\n"); goto err_0; } return 0; err_1: input_unregister_device(inputdev); err_0: input_free_device(inputdev); return ret; } static void __exit simple_input_exit(void) {  int i; for(i=0; i<KEY_NUMS; i++) free_irq(all_key[i].irqno, &all_key[i]); input_unregister_device(inputdev); input_free_device(inputdev); } module_init(simple_input_init); module_exit(simple_input_exit); MODULE_LICENSE("GPL"); 

2.3 测试文件:input_system_text.c

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(void) {  int fd; int ret; struct input_event event; //输入子系统的handler level的数据包  fd = open("/dev/event1", O_RDWR); //打开输入子系统的event1文件 if(fd < 0) {  perror("open"); exit(1); } while(1) {  ret = read(fd, &event, sizeof(struct input_event)); if(ret < 0) {  perror("read"); exit(1); } if(event.type == EV_KEY){  switch(event.code){  case KEY_POWER: if(event.value){  //按下 printf("__APP_USER__ : power pressed\n"); }else{  printf("__APP_USER__ : power up\n"); } break; case KEY_VOLUMEDOWN: if(event.value){  //按下 printf("__APP_USER__ : KEY_VOLUMEDOWN pressed\n"); }else{  printf("__APP_USER__ : KEY_VOLUMEDOWN up\n"); } break; case KEY_VOLUMEUP: if(event.value){  //按下 printf("__APP_USER__ : KEY_VOLUMEUP pressed\n"); }else{  printf("__APP_USER__ : KEY_VOLUMEUP up\n"); } break; } } } close(fd); return 0; } 

2.4 Makefile文件

ROOTFS_DIR = /opt/4412/rootfs  #开发板nsf目录

MODULE_NAME = simple_input
APP_NAME = simple_input_test

CROSS_COMPILE = /home/george/Linux_4412/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/george/Linux_4412/kernel/linux-3.14 CUR_DIR = $(shell pwd) all : make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules
	$(CC) $(APP_NAME).c -o $(APP_NAME) install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += $(MODULE_NAME).o
endif