一、相关概念
中断按来源分为内部中断和外部中断
按中断入口的跳转方法可以分为向量中断和非向量中断。向量中断,不同的中断有不同的中断号,中断到来自动跳转;非向量中断,多个中断共享一个入口地址。进入中断后由软件判断是哪个中断。
linux中断处理机制:linux将中断分解为顶半部和底半部。顶半部完成尽可能少的比较紧急的功能,底半部完成相对来说并不是非常紧急的绝大多数任务。
系统中断的统计信息在/proc/interrupts文件中可以看到。
二、中断编程
n 申请IRQ
int request_irq(unsigned int irq,
void (*handler)(int irq,void *dev_id,struct pt_regs *regs),
unsigned long irqflags,
const char *devname,
void *dev_id);
irq,要申请的硬件中断号
handler,要安装的中断处理函数指针。
irqflags,中断处理的属性,若设置了SA_INTERRUPT,则表示中断处理程序是快速处理程序,调用时屏蔽所有中断,慢速处理程序不屏蔽。若设置了SA_SHIRQ,则表示多个设备共享中断。
devname,中断名称
dev_id,用于共享中断,非共享中断设置为NULL.
返回值:0成功,-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享释放IRQ
n 释放IRQ
void free_irq(unsigned int irq,void *dev_id);
n 使能和屏蔽中断
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
disable_irq_nosync()可以立即返回
disable_irq()等待目前中断处理完成后再返回。
n 屏蔽CPU内的所有中断
void local_irq_save(unsigned long flags)
void local_irq_disable(void);
n 恢复中断
void local_irq_restore(unsigned long falgs);
void local irq_enable(void);
三、底半部机制
linux的底半部机制有tasklet、工作队列和软中断
n tasklet
void my_tasklet_func(unsigned long);
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)
上述代码定义了名称为my_tasklet的tasklet并将其与my_tasklet_func()这个函数绑定,函数的参数为data
调度tasklet_schedule(&my_tasklet);
n 工作队列
struct work_struct my_wq;
void my_wq_func(unsigned long);
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);
调度工作队列
schedule_work(&my_wq);
n 使用模板
tasklet
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
void xxx_do_tasklet(unsigned long)
{
……
}
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
……
tasklet_schedule(&xxx_tasklet);
……
}
int __int xxx_init(void)
{
……
result = request_irq(xxx_irq,xxx_interrupt,
SA_INTERRUPT,”xxx”,NULL);
……
}
void __exit xxx_exit(void)
{
……
free_irq(xxx_irq,xxx_interrupt);
……
}
工作队列的模板的tasklet相仿,只是工作队列的函数绑定放在了模块初始化中。
n 软中断
硬中断、软中断和信号的区别:硬中断是外部设备对CPU的中断,软中断是硬中断服务程序对内核的中断,而信号则是由内核对某个进程的中断。
在linux内核中,用softirq_action结构体表征一个中断,open_softirq()函数可以注册软中断对应的处理函数,raise_softirq()函数触发一个软中断。
软中断和 tasklet仍然运行于中断上下文,而工作队列运行于进程上下文。因此,软中断和tasklet处理函数不能睡眠,而工作队列处理函数中允许睡眠。
四、共享中断
中断设置:request_irq()函数中,irqflags应设置为SA_SHIRQ,最后一个参数dev_id最好是设备结构体的指针。中断处理顶半部应根据handler()函数传递的dev_id和硬件寄存器中的信息比照确实中断属于哪一个设备。
五、LPC3250相关
中断编号:mach/include/irqs.h中可以看出
主中断控制器中断号:0~31
子中断控制器1中断号:32+(0~31)
子中断控制器2中断号:64+(0~31)
mach-lpc32xx/irq-lpc32xx.c中
static void get_controller(unsigned int irq, unsigned int *base, unsigned int *irqbit)
作用是根据传入的参数 irq号,可以确定irq控制器的基地址(使能寄存器)和相应控制器中的位。
static void lpc32xx_mask_irq(unsigned int irq)
作用是给相应的使能寄存器irq位置0,即禁能。
static void lpc32xx_unmask_irq(unsigned int irq)
与上面的函数作用相反。
static void lpc32xx_mask_ack_irq(unsigned int irq)
作用是向原始状态寄存器相应位写 1,即清除边沿中断源的中断状态。
static int lpc32xx_set_irq_type(unsigned int irq, unsigned int type)
作用是设定中断的极性和类型。这个函数里面调用了set_irq_handler(irq, handle_edge_irq);网上说是linux为支持共享中断而调用的。
void __init lpc32xx_set_default_mappings(unsigned int base, unsigned int apr, unsigned int atr, unsigned int offset)
作用是设定默认的中断极性和类型
寄存器设置的默认值
|
MIC |
SIC1 |
SIC2 |
ER |
0x0 |
0x0 |
0x0 |
APR |
0x3FF0EFF8 |
0xFBD27186 |
0x801810C0 |
ATR |
0x0 |
0x00026000 |
0x0 |
根据APR和ATR的值就可以确定最后中断的极性和类型。
ER(各使能寄存器)中,最后又修改为MIC使能寄存器的FIQ,IRQ中断。
LPC32XX中断例程
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <asm/arch/hardware.h>
#include <asm/arch/irq.h>
#include <mach/platform.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#define DEV_NAME "key"
#define KEY_IRQ IRQ_GPI_07
// IRQ_GPI_07已在include/mach/irqs.h中定义。
static int key_value = 0;
static int ready = 0;
static DECLARE_WAIT_QUEUE_HEAD(key_wait);
static int key_open(struct inode *inode, struct file *file)
{
printk(DEV_NAME" opened!\n");
return 0;
}
static int key_release(struct inode *inode, struct file *file)
{
printk(DEV_NAME" released!\n");
return 0;
}
static ssize_t key_read(struct file *flie, char *buf, size_t count, loff_t *f_pos)
{
static int value;
wait_event_interruptible(key_wait, ready == 1);
if(count != sizeof(key_value))
return -EINVAL;
value = key_value;
copy_to_user(buf, &value, sizeof(value));
ready = 0;
return sizeof(value);
}
/*
* SmartARM3250 key interrupt handler.
*/
static irqreturn_t key_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned int *sic2_rsr;
unsigned int *sic2_er;
sic2_er = io_p2v(SIC2_BASE + INTC_MASK);
sic2_rsr = io_p2v(SIC2_BASE + INTC_RAW_STAT);
//printk(DEV_NAME" Interrupted!\n");
key_value = 0x0D;
ready = 1;
//此处使用等待队列来做为中断处理程序顶半部和进程同步的一种机制。
wake_up_interruptible(&key_wait);
__raw_writel((1<<15), sic2_rsr); //clear interrupt flag
__raw_writel((1<<15), sic2_er); //re-enable GPIO_07 interrupt
return IRQ_HANDLED;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.release = key_release,
};
static struct miscdevice key_miscdev =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &key_fops
};
static int key_probe(struct platform_device *dev)
{
int ret;
printk("probing "DEV_NAME"!\n");
ret = misc_register(&key_miscdev);
if (ret)
printk("Failed to register miscdev for "DEV_NAME".\n");
return ret;
}
static int key_remove(struct platform_device *dev)
{
misc_deregister(&key_miscdev);
printk("\n"DEV_NAME " removed!\n");
return 0;
}
struct platform_device *key_device;
static struct platform_driver key_driver = {
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
},
.probe = key_probe,
.remove = key_remove,
};
static int __init key_init(void)
{
int rc;
unsigned int *sic2_er;
sic2_er = io_p2v(SIC2_BASE);
__raw_writel((1<<15), sic2_er);
printk("\n"DEV_NAME" init......\n");
key_device = platform_device_alloc(DEV_NAME, -1);
if (!key_device)
return -ENOMEM;
//set_irq_type(KEY_IRQ, IRQ_TYPE_EDGE_FALLING);
set_irq_type(KEY_IRQ, IRQ_TYPE_LEVEL_LOW);
rc = platform_device_add(key_device);
if (rc < 0) {
platform_device_put(key_device);
return rc;
}
rc = platform_driver_register(&key_driver);
if (rc < 0)
platform_driver_unregister(key_device);
if (request_irq(KEY_IRQ, key_interrupt, IRQF_DISABLED, "key", NULL))
printk(KERN_WARNING DEV_NAME": Can't get IRQ: %d!\n", KEY_IRQ);
return rc;
}
static void __exit key_exit(void)
{
platform_driver_unregister(&key_driver);
platform_device_unregister(key_device);
free_irq(KEY_IRQ, NULL);
printk(DEV_NAME" exit!\n");
}
module_init(key_init);
module_exit(key_exit);
MODULE_AUTHOR("Abing <Linux@zlgmcu.com>");
MODULE_DESCRIPTION("ZHIYUAN SmartARM3250 key Driver");
MODULE_LICENSE("GPL");
总结:如果但就中断来说,这个驱动显得有点冗余。像最后的注册平台设备与平台设备驱动完全是可以忽略的。增加这些是考虑到udev的使用。因为要使udev自动创建设备节点,就要使设备隶属于一种总线或类。LPC3250开发板的物理地址和虚拟地址的转换基本上都可以用io_p2v()来转换(它们在内核初始化时,已经被静态映射过)。最后,在学习过的中断底半部机制中,这个驱动程序没有使用其中的任何一种。而是在顶半部用了一个等待队列,那么唤醒的等待队列就可以完成中断后绪处理任务了。