Linux异常处理体系结构使用主要分成两步:
1、使用函数init_IRQ()初始化中断体系结构,源代码在arch/arm/kernel/irq.c中。
2、用户使用函数request_irq()向内核注册中断处理函数,也就是通过中断号找到irq_desc数组项,将中断函数添加到action链表中。
作者分析的内核版本为2.6.22.6。
Linux内核将所有中断统一编号,使用一个irq_desc结构数组描述,每个数组项对应一个中断,irq结构数据类型在include/linux/irq.h中定义,如下:
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; /* 用户提供的中断处理函数链表 */
unsigned int status; /* IRQ 状态 */
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_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name; /* 中断名称 */
} ____cacheline_internodealigned_in_smp;
在handle.c文件中(在/kernel/irq中定义),定义了irq_desc结构数组,如下:
struct irq_desc irq_desc[NR_IRQS]
#define NR_IRQS 128
irq_desc结构数组中成员hanle_irq结构,它包括操作底层硬件的函数,如清除、屏蔽或者重新使能中断,其类型在include/linux/irq.h中定义,如下:
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); /* 启动中断,如果不设置,缺省为enable */
void (*shutdown)(unsigned int irq); /* 关闭中断,如果不设置,缺省为disable */
void (*enable)(unsigned int irq); /* 使能中断,如果不设置,缺省为unmask */
void (*disable)(unsigned int irq); /* 禁止中断,如果不设置,缺省为disable */
void (*ack)(unsigned int irq); /* 响应中断,通常是清楚当前中断使得可以接受下一个中断 */
void (*mask)(unsigned int irq); /* 屏蔽中断源 */
void (*mask_ack)(unsigned int irq); /* 屏蔽和响应中断 */
void (*unmask)(unsigned int irq); /* 开启中断源 */
void (*eoi)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, cpumask_t dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
const char *typename;
};
irq_desc结构数组中成员irqaction结构类型,他用来表示用户注册的中断处理函数,若有多个处理函数,他们链接成一个链表,以action为表头。irqaction结构类型在include/linux/interrupt.h,如下:
struct irqaction {
irq_handler_t handler; /* 用户注册的中断处理函数 */
unsigned long flags; /* 中断标志,如是否共享中断,电平触发还是边沿触发等 */
cpumask_t mask; /* 用于SMP(对称多处理器系统) */
const char *name; /* 用户注册中断名字,cat /proc/interrupts 能看到 */
void *dev_id; /* 用户传给上面handler的参数,可以区分共享中断 */
struct irqaction *next; /**/
int irq; /* 中断号 */
struct proc_dir_entry *dir; /**/
};
irq_desc结构数组、struct irq_chip *chip、struct irqaction *action这三个数据结构构成中断体系的架构,其关系如下:
Linux内核启动后,进入start_kernel函数(源代码在init/main.c),该函数会调用函数init_IRQ()初始化中断处理体系结构(源代码在arch/arm/kernel/irq.c),也就是构造上面的中断体系的架构,init_IRQ()函数源代码如下:
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
init_arch_irq();
}
首先会初始化irq_desc结构数组的中断状态,然后调用函数init_arch_irq(),该函数由不同厂家的芯片决定,不同的芯片会执行不同的函数,作者使用的是开发板JZ2440V3,对应S3C2440芯片,会执行函数s3c24xx_init_irq()(在arch/arm/plat-s3c2440xx/irq.c)
函数s3c24xx_init_irq()会设置跟S3C2440芯片相关的数据结构,如设置irq_desc结构数组中处理函数入口,定义irq_chip 结构体等,并把他们挂接到irq_desc结构数组。
初始化完中断体系架构后,就需要用户注册中断处理函数。用户通过request_irq函数向内核注册中断处理函数,该函数根据中断号找到irq_desc数组项,然后将中断处理函数挂接到在action链表中。
request_irq()函数在kernel/irqmanage.c中定义,函数原型如下:
/* 函数:request_irq()
* 描述:向内核注册中断处理函数
* 参数: irq:中断号
* handler:中断处理函数句柄
* irqflags:中断触发方式
* devname:中断名称
* dev_id:用户可以自己指定,也可以为空
* 返回:
*/
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long irqflags,
const char *devname,
void *dev_id)
当删除一个中断时,也可以使用卸载中断处理函数,在kernel/irq/manage.c中定义,函数原型如下:
/* 函数:request_irq()
* 描述:向内核注册中断处理函数
* 参数: irq:中断号
* dev_id:用户可以自己指定,也可以为空
* 返回: 无
*/
void free_irq(unsigned int irq, void *dev_id)
ARM架构的异常向量基址可以为0x00000000,也可以为0xFFFF0000,Linux内核采用0xFFFF0000。当发生中断时,CPU会调到相应的异常向量,执行异常向量中的代码后,最终会调用总入口函数,例如图下的asm_do_IRQ、do_DataAbort函数等,异常处理体系结构如下:
假设进入c语言总入口函数,如函数asm_do_IRQ()(在arch/arm/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);
}
最终会执行函数desc_handle_irq(),该函数直接调用desc结构体中处理函数入口,对于电平触发中断,入口函数为handle_level_irq()(在kernel/irq/chip.c中定义);对于边沿触发中断,入口为handle_edge_irq()(在kernel/irq/chip.c中定义)。
不管函数handle_level_irq()还是函数handle_edge_irq(),最后都会调用函数hanle_IRQ_event(),依次执行action链表中用户注册的处理函数。
开发板:JZ2440V3
Linux内核版本:2.6.22.6
编译器:arm-linux-gcc-3.4.5-glibc-2.3.6
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "keys" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define LED_MAJOR 251 /* 主设备号 */
int major;
static struct class *keys_class;
static struct class_device *keys_class_dev;
volatile unsigned long *GPFCON;
volatile unsigned long *GPFDAT;
volatile unsigned long *GPGCON;
volatile unsigned long *GPGDAT;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,keys_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},
};
/* 函数:buttons_irq()
* 描述:中断函数
* 参数:irq:中断号
* dev_id:传入的参数dev_id,用户可以指向不同指针数据,也可以为空
* 该处传入按键引脚和按键值
* 返回:
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
/* 返回引脚的状态,若引脚为高,则返回1 */
pinval = s3c2410_gpio_getpin(pindesc->pin);
/* 引脚为低,怎说明按下按键 */
if(pinval) /* 松开 */
{
key_val = 0x80 | pindesc->key_val;
}
else /* 按下 */
{
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 函数:keys_open()
* 描述:应用程序执行open(...)时,就会调用该函数
* 参数:inode:传递给驱动的inode
* filp:设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* 返回:0 成功;其他 失败
*/
static int keys_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
/* 函数:keys_read()
* 描述:应用程序执行read(...)时,就会调用该函数,从设备读取数据
* 参数:filp:要打开的设备文件(文件描述符)
* buf:返回给用户空间的数据缓冲区
* cnt:要读取的数据长度
* offt:相对于文件首地址的偏移
* 返回:读取的字节数,如果为负值,表示读取失败
*/
ssize_t keys_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;
}
/* 函数:keys_close()
* 描述:应用程序执行close(...)时,就会调用该函数,关闭设备
* 参数: file:设备文件,表示关闭的文件描述符
* inode:传递给驱动的inode
* 返回:0 成功;其他 失败
*/
int keys_close(struct inode *inode, struct file *file)
{
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 keys_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = keys_open,
.read = keys_read,
.release = keys_close,
};
static int __init keys_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &keys_drv_fops);
/* 新建一个类 */
keys_class = class_create(THIS_MODULE, DEVICE_NAME);
/* 在类下创建一个设备 */
keys_class_dev = class_device_create(keys_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); /* /dev/keys */
GPFCON = (volatile unsigned long *)ioremap(0x56000050, 16);
GPFDAT = GPFCON + 1;
GPGCON = (volatile unsigned long *)ioremap(0x56000060, 16);
GPGDAT = GPGCON + 1;
return 0;
}
static void __exit keys_exit(void)
{
unregister_chrdev(major, DEVICE_NAME);
class_device_unregister(keys_class_dev);
class_destroy(keys_class);
/* 取消映射 */
iounmap(GPFCON);
iounmap(GPGCON);
return 0;
}
module_init(keys_init);
module_exit(keys_exit);
MODULE_LICENSE("GPL");
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("https://me.csdn.net/qq_31782183");
MODULE_VERSION("1.0.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 Keys Driver");
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
fd = open("/dev/keys", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += keys.o
make
编译完成,会生成chrleds.ko的驱动模块文件。
arm-linux-gcc keysApp.c -o keysApp
编译完成后,会生成keysApp应用程序。
mount -t nfs -o tcp,nolock 192.168.1.1:/home/book/linux/nfs/fs_mini_mdev /mnt
启动开发板后,执行该语句,将自己的nfs系统挂接,方便测试。
将keys.ko和keysApp两个文件拷贝到挂接系统的/lib/modules/2.6.22.6 目录下,输入以下命令:
insmod keys.ko
驱动加载成功后,会创建"/dev/keys"设备节点。
./keysApp
通过按开发板上面的按键,会打印相应的内容,如下:
key_val = 0x3
key_val = 0x3
key_val = 0x83
key_val = 0x3
key_val = 0x3
key_val = 0x83
key_val = 0x83
如果要卸载模块,输入如下命令:
rmmod chrleds.ko