S3C2440 开发板实战(8):中断驱动
中断驱动
- 一、查询方式
- 1、驱动程序
- 2、应用程序
- 3、结果测试
- 二、中断方式
- 1、中断内部函数实现方式(理论框架)
- 1.1. 异常处理结构
- (1)设置异常向量表
- (2)异常服务函数调用
- 1.2. 中断处理体系结构
- (1)中断处理体系结构的初始化: s3c24xx_init_irq
- (i)底层硬件函数的结构体:chip
- (ii)入口函数:handle_irq
- (①)desc->chip->ack(irq)
- (②)handle_IRQ_event()
- (2)用户注册中断处理函数:request_irq()
- (i)action
- (ii)setup_irq
- (①)action
- (②)set_type
- (③)enable/start
- (3)用户释放中断处理函数:free_irq
- (4)中断处理函数:asm_do_irq()
- (i)irq_desc
- (ii)desc_handle_irq
- 2、中断操作实现方式(驱动部分)
- (1)定义引脚描述结构体
- (2)用户注册中断
- (3)用户释放中断
- (4)中断服务函数
- (5)加入休眠
- 总(驱动代码
- 3、中断操作实现方式(应用程序)
- 三、poll机制
CPU对于按键的查询方式有以下三种:
- main函数中进行while大循环
- 中断方式
- poll机制
一、查询方式
在之前LED驱动基础上进行修改,通过裸机的学习可以知道只是将IO口属性修改为输入。
1、驱动程序
则以文件类的read()函数对输入的按键值进行读取。进入文件类的定义源代码可以查找到以下关于read()的原函数定义代码:
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
所以根据要求将read()函数编写为以下形式,其中实现了通过查找寄存器DATVAL来判断是哪个按键按下执行。
ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{// buf is level of four button pin unsigned char button_val[4];int datval;if(size != sizeof(button_val))return -EINVAL; datval = *gpfdat;button_val[0] = (datval & (1<<0)) ? 1 : 0;button_val[1] = (datval & (1<<2)) ? 1 : 0;datval = *gpgdat;button_val[2] = (datval & (1<<3)) ? 1 : 0;button_val[3] = (datval & (1<<11)) ? 1 : 0;copy_to_user(buf, button_val, sizeof(button_val));return 4;
}
则通过上一篇的LED驱动程序,再添加读取函数read()作为按键读取函数,并且以open()函数为按键的初始化函数。则驱动函数如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/compiler.h>
#include <linux/err.h>#define DEV_NAME "button_drv" // device namestatic struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
volatile unsigned long *gpgcon = NULL;
volatile unsigned long *gpgdat = NULL;static int button_drv_open(struct inode *inode, struct file *file)
{*gpfcon &= ~((3<<8) | (3<<10) | (3<<12)); // input// *gpfcon |= ((1<<8) | (1<<10) | (1<<12)); *gpgcon &= ~((3<<8) | (3<<10) | (3<<12)); // input// *gpgcon |= ((1<<8) | (1<<10) | (1<<12));return 0;
}ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{// buf is level of four button pin unsigned char button_val[4];int datval;if(size != sizeof(button_val))return -EINVAL; datval = *gpfdat;button_val[0] = (datval & (1<<0)) ? 1 : 0;button_val[1] = (datval & (1<<2)) ? 1 : 0;datval = *gpgdat;button_val[2] = (datval & (1<<3)) ? 1 : 0;button_val[3] = (datval & (1<<11)) ? 1 : 0;copy_to_user(buf, button_val, sizeof(button_val));return 4;
}static struct file_operations button_drv_fops = {.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open = button_drv_open, .read = button_drv_read,
};
int major;
static int button_drv_init(void)
{major = register_chrdev(0, DEV_NAME, &button_drv_fops); // 注册, 告诉内核// register chrdev. to major (major, "drv name", file operations);buttondrv_class = class_create(THIS_MODULE, "buttondrv");if(IS_ERR(buttondrv_class)){printk( "ERROR creat key class");}buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/xyz */if(buttondrv_class_dev == NULL){printk("ERROR creat dev");}gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //remap button GPFCPNgpfdat = gpfcon + 1; //remap button GPFDATgpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //remap button GPGCPNgpgdat = gpgcon + 1; //remap button GPGDATreturn 0;
}static void button_drv_exit(void)
{unregister_chrdev(major, DEV_NAME); // 卸载class_device_unregister(buttondrv_class_dev);class_destroy(buttondrv_class);iounmap(gpfcon); // unmap virtual address
}module_init(button_drv_init); //When install drv, system will find init function
module_exit(button_drv_exit); // Uinstall drvMODULE_LICENSE("GPL");
2、应用程序
想必都看烂了,闭着眼睛都能写,所以就直接上程序吧
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>// button_drv int main(int argc, char **argv)
{int fd;int count = 0;unsigned char button_val[4];fd = open("/dev/button", O_RDWR);if (fd < 0){printf("can't open!\n");}if(argc != 1){printf("error parterner\n");printf("%s\n",argv[0]);return 0;}while(1){read(fd, button_val, sizeof(button_val));if(!button_val[0] || !button_val[1] ||!button_val[2] ||!button_val[3]){count++;printf("count: %d key state: %d %d %d %d\n",count, button_val[0], button_val[1],button_val[2], button_val[3]);}}return 0;
}
3、结果测试
运行应用程序,在按下对应的按键按下之后会直接出现很多查询出的值。
缺点:由于不断的查询方式读按键值。占用CPU资源非常多,通过top命令可以看到CPU的资源在该应用程序消耗绝大部分。
改进方式:中断方式查询按键值
二、中断方式
1、中断内部函数实现方式(理论框架)
比起裸机中的中断服务,linux内核中中断的实现方式是通过函数进行封装的。所以在使用的时候只需要调用接口函数即可,在这里对其内部函数进行分析。
1.1. 异常处理结构
中断驱动和之前裸跑中断的实现方式道理是一样的。复习下中断的步骤,一共三个操作:
- 按键按下
- 出现异常,跳到异常向量入口
- 完成中断操作,其中保存现场、执行中断函数(asm_do_irq)、恢复现场
(1)设置异常向量表
由于在驱动中使用的地址为虚拟地址,所以在程序初始化的时候应该把中断向量表在虚拟地址中建立起来,内核中利用trap_init()函数对其进行构建,对于ARM架构异常向量表的虚拟地址可以为:0x00000000或者0xffff0000,在Linux中一般使用0xffff0000。
所以该函数的作用就是将异常向量表的内容拷贝至0xffff0000处,这位子如果不确定我们可以通过查看内核中的 .config 文件,其中CONFIG_VECTORS_BASE = 0xffff0000,表示了异常向量表的地址。
在内核中的异常向量表实现的方式和裸机中是一模一样的,通过跳转指令实现。
(2)异常服务函数调用
这里以中断服务函数为例子
- trap_init() 设置异常向量表,发生中断时跳至vector_irq (汇编中实现,和裸机中相同)
- vector_irq 中保存现场
- vector_irq 中调用中断总入口函数asm_do_irq()(C函数,中断服务函数)
- vector_irq 恢复现场
则异常处理结构可以由下图表示:
1.2. 中断处理体系结构
断处理体系结构:
(1)中断处理体系结构的初始化: s3c24xx_init_irq
内核中断处理体系结构的搭建任务主要落在init_IRQ()函数,他直接由srart_kernel()函数来调用,定义于arch/arm/kernel/irq.c。定义如下:
void __init init_IRQ(void)
{int irq; //中断号for(irq = 0; irq < NR_IRQS; irq++)irq_desc[irq].status|= IRQ_NOREQUEST | IRQ_NOPROBE; //通过把每一个中断号对应结构体成员“status”状态字段设置成未请求和未探测状态init_arch_irq();
}
其中,最后一步init_arch_irq实际上是一个函数指针,最初由setup_arch()函数来设置这个指针
void __init setup_arch(char **cmdline_p)
{...init_arch_irq= mdesc->init_irq; //将void型指针init_arch_irq强制指向struct machine_desc结构体的init_irq成员...
}
接着一级一级递进,mdesc->init_irq的初始化在如下操作中定义:
MACHINE_START(S3C2440, "TQ2440").phys_io = S3C2410_PA_UART,.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) &0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100,.init_irq = s3c24xx_init_irq, //在这.map_io = tq2440_map_io,.init_machine = tq2440_machine_init, .timer = &s3c24xx_timer,
MACHINE_END
在这里将mdesc->init_irq 赋值了s3c24xx_init_irq函数,所以我们的init_arch_irq()函数即是s3c24xx_init_irq()
在递进一步来分析s3c24xx_init_irq()
在这个函数里主要初始化中断处理体系结构中的handle_irq和chip*
void _init s3c24xx_init_irq()
{...set_irq_chip(irqno, &s3c_irqext_chip);set_irq_handler(irq, handle_edge_irq);...
}
PS: 这里调用的函数不一定是handle_edge_irq,但是大体结构相同
(i)底层硬件函数的结构体:chip
在这里chip = s3c24xx系列底层访问函数。在handle_irq中调用。
在例子里使用的chip结构体定义如下:
static struct irq_chip s3c_irqext_chip = {.name = "s3c-ext",.mask = s3c_irqext_mask, //屏蔽中断源.unmask = s3c_irqext_unmask, //开启接收中断源.ack = s3c_irqext_ack, //响应中断:通常是清除当前中断使得可以接收下一个中断.set_type = s3c_irqext_type, //触发方式.set_wake = s3c_irqext_wake //唤醒相关
};
(ii)入口函数:handle_irq
handle_irq的设置是通过set_handle_irq实现的,即有handle_irq = handle_edge_irq,并且通过分析源码可以得到handle_edge_irq由以下两部分组成:
handle_edge_irq()
{...desc->chip->ack(irq);...handle_IRQ_event();...
}
(①)desc->chip->ack(irq)
作用:清中断
(②)handle_IRQ_event()
作用:中断处理
方式:取出action链表中的成员,然后指向action中的handler中断服务函数
(2)用户注册中断处理函数:request_irq()
这一步主要设置:action链表
以下是用户注册中断处理函数的源码:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
{struct irqaction *action;int retval;#ifdef CONFIG_LOCKDEP/** Lockdep wants atomic interrupt handlers:*/irqflags |= IRQF_DISABLED;
#endif/** Sanity-check: shared interrupts must pass in a real dev-ID,* otherwise we'll have trouble later trying to figure out* which interrupt is which (messes up the interrupt freeing* logic etc).*/if ((irqflags & IRQF_SHARED) && !dev_id)return -EINVAL;if (irq >= NR_IRQS)return -EINVAL;if (irq_desc[irq].status & IRQ_NOREQUEST)return -EINVAL;if (!handler)return -EINVAL;action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);if (!action)return -ENOMEM;action->handler = handler;action->flags = irqflags;cpus_clear(action->mask);action->name = devname;action->next = NULL;action->dev_id = dev_id;select_smp_affinity(irq);#ifdef CONFIG_DEBUG_SHIRQif (irqflags & IRQF_SHARED) {/** It's a shared IRQ -- the driver ought to be prepared for it* to happen immediately, so let's make sure....* We do this before actually registering it, to make sure that* a 'real' IRQ doesn't run in parallel with our fake*/if (irqflags & IRQF_DISABLED) {unsigned long flags;local_irq_save(flags);handler(irq, dev_id);local_irq_restore(flags);} elsehandler(irq, dev_id);}
#endifretval = setup_irq(irq, action);if (retval)kfree(action);return retval;
}
传入参数分析:
- irq:注册中断函数对应的中断号
- handler:中断服务函数
- irqflags:中断触发方式(第一个字节代表电平触发方式,第二个字节代表声明)
- devname:自定义的中断名
- dev_id:传入中断服务函数的参数,可以为任意形式(大多数是一个结构体指针)
(i)action
在这个数据结构里面保存着中断初始化中的所有的成员
(ii)setup_irq
这个函数有三个作用:
(①)action
把在request_irq中设置的irq_action数据结构,添加至链表action中。
如果该中断号对应的action链表里面有值,则判断以下条件:
- 是否irq_falgs声明为可共享的(IRQF_SHARE)
- 是否有相同的irq_flags电平触发方式
如果满足该条件,则将新建的数据结构irq_action链入至action中
(②)set_type
把引脚设置为中断引脚,由传入的irqflags来设置
(③)enable/start
这个时候初始就注册完毕,中断使能了。除非将irq_desc[irq].status = IRQ_NOAUTOEN(表示注册时不使能中断)。
(3)用户释放中断处理函数:free_irq
其分为两部对中断进行卸载
- 使用irq判断是在哪个irq_desc
- 使用dev_id判断是哪个action结构,并删除该action结构
- 如果删除后链表空,则屏蔽该中断
内核源码定义如下:
void free_irq(unsigned int irq, void *dev_id)
{struct irq_desc *desc;struct irqaction **p;unsigned long flags;irqreturn_t (*handler)(int, void *) = NULL;WARN_ON(in_interrupt());if (irq >= NR_IRQS)return;desc = irq_desc + irq;spin_lock_irqsave(&desc->lock, flags);p = &desc->action;for (;;) {struct irqaction *action = *p;if (action) {struct irqaction **pp = p;p = &action->next;if (action->dev_id != dev_id)continue;/* Found it - now remove it from the list of entries */*pp = action->next;/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHODif (desc->chip->release)desc->chip->release(irq, dev_id); //释放对应的action
#endifif (!desc->action) {desc->status |= IRQ_DISABLED;if (desc->chip->shutdown)desc->chip->shutdown(irq);elsedesc->chip->disable(irq); //屏蔽中断}spin_unlock_irqrestore(&desc->lock, flags);unregister_handler_proc(irq, action);/* Make sure it's not being used on another CPU */synchronize_irq(irq);if (action->flags & IRQF_SHARED)handler = action->handler;kfree(action);return;}printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);spin_unlock_irqrestore(&desc->lock, flags);return;}
#ifdef CONFIG_DEBUG_SHIRQif (handler) {/** It's a shared IRQ -- the driver ought to be prepared for it* to happen even now it's being freed, so let's make sure....* We do this after actually deregistering it, to make sure that* a 'real' IRQ doesn't run in parallel with our fake*/handler(irq, dev_id);}
#endif
}
综上,request_irq()所做的事情有:
5. 将irq_desc[irq]中的action链表已近链入了用户注册中断函数中的irq_action
6. 设置中断方式
7. 使能中断
(4)中断处理函数:asm_do_irq()
中断处理体系结构就是asm_do_irq()中中断服务的实现方式。在裸机中的中断总入口函数的处理步骤为:
分辨是哪个中断
调用处理函数
清中断
在中断总入口函数asm_do_irq()进行中断服务的步骤为
根据中断号调用irq_desc数组项中的handle_irq
handle_irq会用chip的成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等
handle_irq逐个调用action链表中的中断服务函数
以下是中断总入口函数的源码:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);struct irq_desc *desc = irq_desc + irq;/** Some hardware gives randomly wrong interrupts. Rather* than crashing, do something sensible.*/if (irq >= NR_IRQS)desc = &bad_irq_desc;irq_enter();desc_handle_irq(irq, desc);/* AT91 specific workaround */irq_finish(irq);irq_exit();set_irq_regs(old_regs);
}
接着对应着中断服务的步骤对其代码进行分析
(i)irq_desc
这是一个中断描述的数组irq_desc[irq],他以中断号为下标。即实现分辨是哪个中断。
*desc = irq_desc + irq;代表着对应的中断的结构体数组项地址赋值给desc。
这个结构体的成员如下所示
struct irq_desc {irq_flow_handler_t handle_irq;struct irq_chip *chip;struct msi_desc *msi_desc;void *handler_data;void *chip_data;struct irqaction *action; /* IRQ action list */unsigned int status; /* IRQ status */unsigned int depth; /* nested irq disables */unsigned int wake_depth; /* nested wake enables */unsigned int irq_count; /* For detecting broken IRQs */unsigned int irqs_unhandled;spinlock_t lock;
#ifdef CONFIG_SMPcpumask_t affinity;unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FSstruct proc_dir_entry *dir;
#endifconst char *name;
}
在执行中断服务函数的时候,irq_desc在初始化的时候就已经被设置好,所以能够直接调用。其中包括了主要的成员为:
- chip:
- handle_irq:
- action:
(ii)desc_handle_irq
其中源码如下所示:
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{desc->handle_irq(irq, desc);
}
可以看出这是利用之前设置的desc来调用中断号对应的中断处理函数。其中desc->handle_irq()在之前初始化中体现,其中调用的是注册函数中的中断服务函数。最终实现中断服务函数的调用。
2、中断操作实现方式(驱动部分)
(1)定义引脚描述结构体
为了完成我们实现的功能,引脚结构体需要有的成员为:
- 查询按键是否按下对应的寄存器(gpfdat、gpgdat的虚拟地址)
- 按键对应的引脚
- 按键对应的号码
即设置引脚描述结构体数组为:
struct pin_desc{volatile unsigned long *key_Register;int key_pin;int key_num;
};struct pin_desc pins_desc[4] = {{0x01, 0, 1},{0x02, 2, 2},{0x03, 3, 3},{0x04, 11, 4},
};
其中寄存器为随便定义一个,具体操作在open函数中定义。
(2)用户注册中断
实现注册中断操作在open()函数中完成。在open()函数neural主要完成两个任务:
- 引脚描述结构体数组初始化
- 注册中断
由register_irq()函数的定义:
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
open()函数实现代码为:
static int button_drv_open(struct inode *inode, struct file *file)
{pins_desc[0].key_Register = gpfdat;pins_desc[1].key_Register = gpfdat;pins_desc[2].key_Register = gpgdat;pins_desc[3].key_Register = gpgdat;printk("%d\n",&pins_desc[0]);request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);return 0;
}
其中button_irq为中断服务函数
(3)用户释放中断
有注册就有释放,首先在file_operations声明需要使用释放函数。
通过查找定义可以知道release的定义如下:
int (*release) (struct inode *, struct file *);
故我们在函数EINTkey_close()中释放中断,其代码如下:
static int EINTkey_close(struct inode *inode, struct file *file)
{// FREE IRQ free_irq(IRQ_EINT0, &pins_desc[0]);free_irq(IRQ_EINT2, &pins_desc[1]);free_irq(IRQ_EINT11, &pins_desc[2]);free_irq(IRQ_EINT19, &pins_desc[3]);return 0;
}
所以file_operation如下所示为:
struct file_operations button_drv_fops = {.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open = button_drv_open, .read = button_drv_read,.release = EINTkey_close,
};
(4)中断服务函数
思想:通过注册中断中传入的结构体来判断那哪一个引脚应该动作
static irqreturn_t button_irq(int irq, void *dev_id)
{struct pin_desc *pindesc = (struct pin_desc *)dev_id;key_val = ((*(pindesc -> key_Register)) & (1<<(pindesc -> key_pin))) ? 0 : 1;if(key_val){// printk("%d", pindesc -> key_num);}printk("%d ",key_val);printk("%d\n",pindesc->key_num);return IRQ_HANDLED;
}
(5)加入休眠
由于咱们使用中断的初衷是为了减少对CPU的使用,照原来的在主函数中大while循环肯定是不行的,所以加入休眠程序。
使用方法:
- DECLARE_WAIT_QUEUE_HEAD(name)
-----生成一个等待队列头wait_queue_head_t,名字为name - wait_event_interruptible ( queue , condition )
-----当 condition ( 一个布尔表达式 ) 为真,立即返回
-----否则让进程进入TASK_INTERRUPTIBLE 模式,并挂在 queue 参数所指定的等待队列上,进入休眠 - wake_up_interruptible ( wait_queue_t *q )
-----从等待队列 q 中唤醒状态为 TASK_INTERRUPTIBLE 的进程
所以思路为在read()中对主程序进行休眠操作,并在中断程序中唤醒,如下流程图表示
所以改写的button_irq和read函数如下所示:
static irqreturn_t button_irq(int irq, void *dev_id)
{struct pin_desc *pindesc = (struct pin_desc *)dev_id;key_val = ((*(pindesc -> key_Register)) & (1<<(pindesc -> key_pin))) ? 0 : 1;if(key_val){// printk("%d", pindesc -> key_num);}printk("%d ",key_val);printk("%d\n",pindesc->key_num);ev_press = 1; /* 表示中断发生了 */wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */return IRQ_HANDLED;
}
ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{if(size != -1)return -EINVAL; /* 如果没有按键动作, 休眠 */wait_event_interruptible(button_waitq, ev_press);/* 如果有按键动作, 返回键值 */copy_to_user(buf, &key_val, 1);ev_press = 0;return 1;
}
总(驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/compiler.h>
#include <linux/err.h>
#include <linux/irqreturn.h>
#include <linux/irq.h>
#include <asm/arch-s3c2410/regs-gpio.h>
#define DEV_NAME "button_drv" // device namestatic struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
volatile unsigned long *gpgcon = NULL;
volatile unsigned long *gpgdat = NULL;struct pin_desc{volatile unsigned long *key_Register;int key_pin;int key_num;
};struct pin_desc pins_desc[4] = {{0x01, 0, 1},{0x02, 2, 2},{0x03, 3, 3},{0x04, 11, 4},
};static irqreturn_t button_irq(int irq, void *dev_id)
{struct pin_desc *pindesc = (struct pin_desc *)dev_id;key_val = ((*(pindesc -> key_Register)) & (1<<(pindesc -> key_pin))) ? 0 : 1;if(key_val){// printk("%d", pindesc -> key_num);}printk("%d ",key_val);printk("%d\n",pindesc->key_num);ev_press = 1; /* 表示中断发生了 */wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */return IRQ_HANDLED;
}static int button_drv_open(struct inode *inode, struct file *file)
{pins_desc[0].key_Register = gpfdat;pins_desc[1].key_Register = gpfdat;pins_desc[2].key_Register = gpgdat;pins_desc[3].key_Register = gpgdat;printk("%d\n",&pins_desc[0]);request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);return 0;
}
ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{if(size == -1)return -EINVAL; /* 如果没有按键动作, 休眠 */wait_event_interruptible(button_waitq, ev_press);/* 如果有按键动作, 返回键值 */copy_to_user(buf, &key_val, 1);ev_press = 0;return 1;
}static int EINTkey_close(struct inode *inode, struct file *file)
{// FREE IRQ free_irq(IRQ_EINT0, &pins_desc[0]);free_irq(IRQ_EINT2, &pins_desc[1]);free_irq(IRQ_EINT11, &pins_desc[2]);free_irq(IRQ_EINT19, &pins_desc[3]);return 0;
}static struct file_operations button_drv_fops = {.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open = button_drv_open, .read = button_drv_read,.release = EINTkey_close,
};
int major;
static int button_drv_init(void)
{major = register_chrdev(0, DEV_NAME, &button_drv_fops); // 注册, 告诉内核// register chrdev. to major (major, "drv name", file operations);buttondrv_class = class_create(THIS_MODULE, "buttondrv");if(IS_ERR(buttondrv_class)){printk( "ERROR creat key class");}buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/xyz */if(buttondrv_class_dev == NULL){printk("ERROR creat dev");}gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //remap button GPFCPNgpfdat = gpfcon + 1; //remap button GPFDATgpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //remap button GPGCPNgpgdat = gpgcon + 1; //remap button GPGDATreturn 0;
}static void button_drv_exit(void)
{unregister_chrdev(major, DEV_NAME); // 卸载class_device_unregister(buttondrv_class_dev);class_destroy(buttondrv_class);iounmap(gpgcon);iounmap(gpfcon); // unmap virtual address
}module_init(button_drv_init); //When install drv, system will find init function
module_exit(button_drv_exit); // Uinstall drvMODULE_LICENSE("GPL");
3、中断操作实现方式(应用程序)
按照休眠的思路只要while read()函数就行了,在基础上在加上一个输出按键值操作,其代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>// button_drv int main(int argc, char **argv){int fd;int count = 0;unsigned char button_val[4];fd = open("/dev/button", O_RDWR);if (fd < 0) printf("can't open!\n");if(argc != 1) {printf("error parterner\n");printf("%s\n",argv[0]);return 0;}while(1)read(fd, button_val, sizeof(button_val)); return 0;
}
三、poll机制
本篇太长了所以接着下一节博文内容,连接如下:
S3C2440 开发板实战(9):poll机制
S3C2440 开发板实战(8):中断驱动相关推荐
- 【Verilog】基于Nexys4DDR开发板实现数字钟
功能: 基于Nexys4DDR开发板实现的数字钟,六位数码管显示时分秒,可切换24时制/12时制,有整点报时功能(led灯闪烁). Verilog代码: `timescale 1ns / 1ps//数 ...
- 基于PYNQ-Z2开发板实现矩阵乘法加速详细流程
基于PYNQ-Z2开发板实现矩阵乘法加速 主要内容 1.在Vivado HLS中生成矩阵乘法加速的IP核. 2.在Vivado中完成Block Design. 3.在Jupyter Notebook上 ...
- 移植根文件系统到linux内核 s3c2440,u-boot-2011.06在基于s3c2440开发板的移植之引导内核与加载根文件系统...
三.根文件系统的制作 我们利用busybox来制作根文件系统 1.在下列网站下载busybox-1.15.0.tar.bz2 在当前目录下解压busybox tar -jxvf busybox-1.1 ...
- 利用粤嵌LinuxGEC6818开发板实现电子相册
实验目的 利用粤嵌LinuxGEC6818开发板实现电子相册,要求如下: 实验操作必须在Linux操作系统下完成 源代码模块化设计 实现水平或者垂直滑动切换图片 实验步骤 因为操作需要在Linux下运 ...
- 【微信小程序控制硬件16 】 安信可 ESP32-S 开发板实现移植腾讯物联开发平台蓝牙 llsync 协议,实现一键蓝牙快速配网+远程控制。(附带源码)
文章目录 一.前言 二.源码目录说明 三.编译指导 四.常见问题 五.开源微信物联网控制一览表 另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联 ...
- Mixly(米思齐)的安装以及基于Arduino开发板实现电容触摸控制灯
Mixly(米思齐)的安装以及基于Arduino开发板实现电容触摸控制灯 1.Mixly下载 http://mixly.org/bnu-maker/mixly-arduino-win Mixly软件安 ...
- GEC6818开发板JPG图像显示,科大讯飞离线语音识别包Linux_aitalk_exp1227_1398d7c6运行demo程序,开发板实现录音
GEC6818开发板JPG图像显示 | 开发板实现录音 一.GEC6818开发板JPG图像显示 1.jpg图片特性 2.如何解压缩jpg图片 1.对jpegsrc.v8c.tar.gz进行arm移植 ...
- CC2540 CC2541 蓝牙4.0BLE开发板实现 空中飞鼠、体感游戏手柄【多图】
蓝牙4.0BLE开发板实现空中飞鼠.体感游戏手柄 近年来开始流行空中飞鼠与体感游戏手柄,正好我们的开发板上设计有插入mpu6050六轴传感器的适配接口,我们中秋节期间兴趣使然,实现了这么一个方案. ...
- 基于STM32开发板实现传感数据采集-DHT11温湿度采集
基于STM32开发板实现传感数据采集-DHT11温湿度采集 一.项目简介 本次项目是基于STM32开发板实现传感数据采集-DHT11温湿度采集.采用ARM结构中最为代表的Cortex-M4系列的芯片, ...
最新文章
- 技术 | Bengio终结Theano不是偶然,其性能早在Keras支持的四大框架中垫底
- LeetCode 1470. 重新排列数组
- 那些被大数据时代抛弃的人
- java线程池执行器_Java线程池ThreadPoolExecutor的使用
- 蓝色版去水印小程序源码+接口
- SLAM GMapping(5)运动模型
- React与ES6(一)开篇介绍
- 【WPF】设置DataGrid表头内容居中显示
- oracle存储过程id递增,oracle存储过程——按id更新相关信息
- Vova and Train (codeforces 1066A)
- Linux 终端快捷键
- 消费者人群画像-信用智能评分(金融风控模型经典案例)
- ubuntu更改文件夹权限
- 软件测试行业中ta表示什么意思,软件测试架构师(TA)的职位特点
- 获取手机唯一标识插件_H5能获取到手机设备ID或者手机浏览器唯一识别码吗
- 软硬件学习记录5—— Verilog语言中的数据类型
- 几种方法判断平面点在三角形内
- PHP之两个日期之间相差天数
- salesforce lightning 入门(一)
- 运筹说 第85期 | 只有初中学历的数学家
热门文章
- 用vs2017写php,windows下开发php7.2拓展,vs2017
- 与传统招聘方式相比,小程序招聘都有哪些优势?
- python 键盘记录_记录键盘敲击次数 python实现
- 用一个uchar 类型表示八个通道的状态
- VMWare虚拟机无法开启,显示模块“Disk”启动失败
- MySQL安全登录策略
- android usb wifi驱动下载,android 平台USB wifi驱动移植及使用
- 论文笔记1 | 使用CTC对湍流工业火焰进行瞬时三维重建
- 【xlrd读取Excel日期】使用xlrd读取Excel日期格式单元格的方法
- 关于Sun公司的SCJP认证