文章目录
单片机开发
- 有按键按下
- cpu中断,强制跳转到中断入口执行
- 在中断入口函数中
3.1 保存现场(各种寄存器的值)
3.2 执行中断处理函数
3.3 恢复现场
他人优秀总结 字符设备驱动三 中断框架
中断描述符表(Interrupt Descriptor Table,IDT)大佬的链接
整个流程:
Linux
设置异常入口,linux异常向量的基地址为0xFFFF0000(8字节),这个是虚拟地址,建立与物理地址的映射之后需要把异常向量表复制到0xFFFF0000对应的物理地址,
arch/arm/kernel/traps.c,
void __init trap_init(void)
{
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
}
其中,
vecotrs的值在include/linux/autoconf.h中定义#define CONFIG_VECTORS_BASE 0xffff0000
__vectors_start在arch/arm/kernel/entry-armv.S中表示如下,就是汇编指令跳转到某个地址,
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
vector_und也在entry-armv.S中,
vector_stub und, UND_MODE
...
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
展开,是一个宏定义的,
vector_und:
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
trap_init拷贝异常向量的表到0xffff0000,
跳转到具体的某个向量时,计算返回地址—保存寄存器值—调用处理函数(asm_do_IRQ[C函数])—恢复。流程如下图,完全开发手册第415页
解析asm_do_IRQ
在单片机开发中,发生中断后,cpu干什么
- 判断是哪个中断
- 执行对应中断函数
- 清中断
在linux内核中这三步都在asm_do_IRQ完成,它在arch/arm26/kernel/irq.c中,
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);
}
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
irq_desc在kernel/irq/handle.c中,irq_desc是中断描述数组,以中断号为下标。asm_do_IRQ首先根据中断号在irq_desc取到一项,然后执行desc_handle_irq里的desc->handle_irq(irq, desc);,
在kernel/irq/chip.c中__set_irq_handler函数中对其赋值desc->handle_irq = handle;
在arch/arm/plat-s3c24xx/irq.c中的void __init s3c24xx_init_irq(void)
desc就是那个全局数组的某一项,那三步都在asm_do_IRQ
中的desc->handle(irq, desc, regs);
实现,
听不懂照搬代码吧,跟之前比做了一点点修改
1. 简单irq函数 request_irq free_irq 用exec打开设备节点
只改了驱动,没有该user,这小部分没有用到user
// 发生中断之后会调用这个函数
static irqreturn_t keys_irq(int irq, void *dev_id)
{
printk("irq = %d\n", irq);
printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// GPF0/2 GPG3/11 配置成输入模式
static int keys_open (struct inode *inode, struct file *filep)
{
// GPFCON &= ~((0x3<<0*2) | (0x3<<2*2)); // 清零
// GPGCON &= ~((0x3<<3*2) | (0x3<<11*2)); // 清零
/*
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
*/
// irq:中断号
// handler:处理函数
// irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
// *devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟)
// 文件上,或内核发生中断错误时使用。
// dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。也用于卸载action
// 中断引脚,自动设备为中断引脚,
// IRQ_EINT0在include/asm-arm/arch-s3c2410/irqs.h中定义
// keys_irq
// IRQT_BOTHEDGE双边沿触发
// "EINT0_S2"
request_irq(IRQ_EINT0, keys_irq, IRQT_BOTHEDGE, "EINT0_S2", 1);
request_irq(IRQ_EINT2, keys_irq, IRQT_BOTHEDGE, "EINT0_S3", 1);
request_irq(IRQ_EINT11, keys_irq, IRQT_BOTHEDGE, "EINT0_S4", 1);
request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "EINT0_S5", 1);
return 0;
}
// 释放中断的配置
// 关闭中断,也就是取消中断处理函数,在全局数组irq_desc寻找到对应的action链表,
// 通过id将其从链表中删除.如果链表空了,则禁止中断
int key_close(struct inode *inode, struct file *filp)
{
free_irq(IRQ_EINT0, 1);
free_irq(IRQ_EINT2, 1);
free_irq(IRQ_EINT11,1);
free_irq(IRQ_EINT19,1);
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = keys_open,
.read = keys_read,
.release = key_close,
};
2. 插入/exec打开关闭设备节点
查看设备的主设备号和驱动名称,
查看设备节点信息,
打开设备,这里不是在用户代码中调用open,用命令 exec 5<dev/keys_intnode
,打开设备节点,按下按键,弹起,双边沿触发,不同按键对应不同的中断号,比如EINT0在代码中就是16,
#define S3C2410_CPUIRQ_OFFSET (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
/* main cpu interrupts */
#define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
执行cat proc/interrupts
查看中IRQ名称断设备节点,可以看到EINT0_S2等 中断次数等
当前的进程是shell,查看shell的id ps / top
, pid为769,ls -l proc/769/fd
可以看到那个5
就指向了keys_intnode设备节点,
关闭用exec打开的那个设备exec 5<&-
然后再查看ls proc/769/fd -l
,用cat proc/interrupts
查看那四个IRQ的名称中断设备节点也没有了,再调用free_irq释放了中断。
在open中加入休眠等待
read之后进入内核态的驱动程序里,如果没有按键中断,休眠;
有按键中断,进入ISR得到按键值,在read中继续执行,将按键值传递到用户空间
休眠函数如下,condition=0才休眠,定义在include/linux/wait.h,condition参数这里是ev_press:
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
1. 驱动代码 keys_int_wait.c 在irq中更新kay_val wake_up_interruptible(&button_waitq)
#include <linux/init.h> // module_init module_exit
#include <linux/module.h> // MODULE_LICENSE
#include <linux/fs.h> // file_operations
#include <linux/cdev.h> // cdev
#include <linux/kernel.h>
#include <linux/device.h> // class_device
#include <linux/irq.h> // IRQ_EINTx
#include <asm/uaccess.h> // copy_from_user copy_to_user
#include <asm/io.h> // ioremap iounmap
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h> // S3C2410_GPFx
#define DEVICE_NAME "k_int_wait"
int major;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
volatile unsigned long *gpgcon = NULL;
volatile unsigned long *gpgdat = NULL;
#define GPFCON (*gpfcon)
#define GPFDAT (*gpfdat)
#define GPGCON (*gpgcon)
#define GPGDAT (*gpgdat)
// 用于自动创建设备节点的结构class 和 class_device
static struct class *keys_class;
static struct class_device *keys_class_dev;
// 定义一个事件叫button_waitq 生成一个等待队列头wait_queue_head_t,名字为button_waitq
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
// 定义 中断事件标志,与read中的wait_event_interruptible(button_waitq, ev_press)配合 ISR将它置1, read 清0
static volatile int ev_press = 0;
// 自定义引脚描述的结构
struct pin_desc
{
unsigned int pin;
unsigned int key_val;
};
/*键值:按下,0x01,0x02,0x03,0x04
键值:松开,0x81,0x82,0x83,0x84*/
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04}
};
// ISR 发生中断之后会调用这个函数, irq就是对应的中断号
static irqreturn_t keys_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pin_status;
// 获取引脚状态 Linux内部有系统函数s3c2410_gpio_getpin能够读取GPIO的值
pin_status = s3c2410_gpio_getpin(pindesc->pin);
printk("irq = %d\n", irq);
// 判断引脚,给全局变量赋对应引脚的状态值 (状态--> 所属引脚)
if (pin_status) // 松开
{
key_val = 0x80 | pindesc->key_val; // 存入自定义全局变量,在read中to_user
}
else // 按下
{
key_val = 0x00 | pindesc->key_val; // 存入自定义全局变量,在read中to_user
}
ev_press = 1; // 中断事件标志,ISR将它置1, read 清0
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
// GPF0/2 GPG3/11 申请中断
static int keys_open (struct inode *inode, struct file *filep)
{
/*
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
*/
// 中断引脚,自动设备为中断引脚,
// IRQ_EINT0在include/asm-arm/arch-s3c2410/irqs.h中定义
// keys_irq
// IRQT_BOTHEDGE双边沿触发
// "EINT0_S2"
// IRQ_EINT0 &pins_desc[0]将被传入到中断服务程序
request_irq(IRQ_EINT0, keys_irq, IRQT_BOTHEDGE, "EINT0_S2", &pins_desc[0]);
request_irq(IRQ_EINT2, keys_irq, IRQT_BOTHEDGE, "EINT0_S3", &pins_desc[1]);
request_irq(IRQ_EINT11, keys_irq, IRQT_BOTHEDGE, "EINT0_S4", &pins_desc[2]);
request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "EINT0_S5", &pins_desc[3]);
return 0;
}
// read函数会被用户空间调用,可能while(1), 防止占用资源
ssize_t keys_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
// 判断缓冲区为1
if (size != 1)
return -EINVAL;
// 没有按键动作就休眠, 内部判断ev_press是否为0,为0则休眠,否则往下执行
// 有休眠就有唤醒,唤醒在ISR中
wait_event_interruptible(button_waitq, ev_press); // ev_press在这里跟wait_event_interruptible函数绑定在一起了,该函数会判断第二参数的值而确定是否等待
// 将键值返回到用户空间
copy_to_user(buf, &key_val, 1);
// 中断事件标志,ISR将它置1, read 清0
ev_press = 0;
return 1;
}
// 释放中断的配置
int key_close(struct inode *inode, struct file *filp)
{
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 fops = {
.owner = THIS_MODULE,
.open = keys_open,
.read = keys_read,
.release = key_close,
};
static int keys_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0)
{
printk(DEVICE_NAME " can't register major number.\n");
return major;
}
keys_class = class_create(THIS_MODULE, "keys_class");
if (IS_ERR(keys_class))
return PTR_ERR(keys_class);
// 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
// 在/dev目录下创建相应的设备节点,
keys_class_dev = class_device_create(keys_class, NULL, MKDEV(major, 0), NULL, "k_int_w_node");
if (unlikely(IS_ERR(keys_class_dev)))
return PTR_ERR(keys_class_dev);
gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long*)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
printk(DEVICE_NAME " device initialized successfully...\n\n");
return 0;
}
static void keys_exit(void)
{
// 对应卸载
unregister_chrdev(major, DEVICE_NAME);
class_device_unregister(keys_class_dev);
class_destroy(keys_class);
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(keys_init);
module_exit(keys_exit);
MODULE_LICENSE("GPL");
2. 用户代码 user_keys_int_wait.c read会睡眠 进程资源占用0%
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int cnt = 0;
unsigned char key_val;
if (argc != 1)
{
printf("No args required.\n\n");
return 0;
}
fd = open("/dev/k_int_w_node", O_RDWR);
if (fd < 0)
{
printf("can't open '/dev/k_int_w_node'.\n");
return 0;
}
printf("Waiting press the keys, will print xxxx.\n\n");
while(1)
{
// read之后进入内核态的驱动程序里,如果没有按键中断,休眠;
// 有按键中断,进入ISR得到按键值,在read中继续执行,将按键值传递到用户空间
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val); // 输出16进制
}
return 0;
}
3. 插入模块/查看设备节点/中断注册情况/资源占用情况
查看主设备号和驱动设备,
查看设备节点,
查看中断注册情况,cat proc/interrupts
后台运行,按下按键,查看资源占用情况,