中断实践(一)
中断基础知识见:https://blog.csdn.net/DXflighting/article/details/111600973
实验内容:
自己虚拟出来一个设备,将该设备注册到系统中,执行自己写的中断服务例程。
我们要写一个中断内核模块,申请一个中断线,一个中断线对应一个IRQ号,虚拟出来的设备可以与其他中断线共用一条。
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/interrupt.h>
static int irq; //irq号
static char * devname; //设备名称
//命令行中传入参数
module_param(irq,int,0644);
module_param(devname,charp,0644);
//用于共享IRQ中
struct myirq
{
int devid;
};
struct myirq mydev={1119};
//中断处理函数,每执行一次count++
static irqreturn_t myirq_handler(int irq,void * dev)
{
struct myirq mydev;
static int count=1;
mydev = *(struct myirq*)dev;
printk("key: %d..\n",count);
printk("devid:%d ISR is working..\n",mydev.devid);
printk("ISR is leaving......\n");
count++;
return IRQ_HANDLED;//代表接收到了准确的中断信号,并且做出了相应的正确的处理
}
//内核模块初始化函数
static int __init myirq_init(void)
{
printk("Module is working...\n");
//中断注册:request_irq(),flag是IRQF_SHARED表示共享
if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
{
printk("%s request IRQ:%d failed..\n",devname,irq);
return -1;
}
printk("%s request IRQ:%d success...\n",devname,irq);
return 0;
}
//内核模块退出函数
static void __exit myirq_exit(void)
{
printk("Module is leaving...\n");
free_irq(irq,&mydev);
printk("Free the irq:%d..\n",irq);
}
MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);
插入内核,查看/proc/interrupt文件信息,是否将虚拟设备注册成功,并查看日志消息:
程序分析(从内核分析):
程序的内核模块初始化函数中调用的内核中断注册函数:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_irq函数的参数说明:
第1个参数irq:每一个IRQ中断线所对应的中断号。
第2个参数handler:是一个指针,指向处理这个中断实际中断处理程序,只要操作系统接收到中断,该函数就会被调用。该函数的返回值是irq_handler_t,该返回值的定义在/include/linux/irqreturn.h中:
enum irqreturn {
IRQ_NONE,
IRQ_HANDLED,
IRQ_WAKE_THREAD,
};
typedef enum irqreturn irqreturn_t;
第3个参数flag:指定了快速中断或者中断共享中中断的处理属性,定义在inlcude/interrupt.h:
#define IRQF_DISABLED 0x00000020 //内核在处理中断程序期间,禁止所有的其他中断
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080 //可以在多个中断处理程序之间共享中断线
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200 //特别为系统定时器的中断处理而准备的
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
第4个参数neme:与中断相关的设备的ASCII文本表示,例如PC机上键盘中断对应的这个值为“keyboard”,这些名字会被/proc/irq和/proc/interrupt文件使用。
第5个参数dev:用于共享中断线,当一个处理程序需要释放时,dev将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。如果没有这个参数,那么内核不知道在给定的中断线上到底删除哪一个处理程序。
request_threaded_irq():
request_irq是函数是对request_threaded_irq函数的一个封装,该函数定义在/kernel/irq/manage.c
首先了解一下该函数涉及的两个个重要的结构体:
irqaction结构体主要用来存用户注册的中断处理函数,一个中断可以有多个处理函数,当一个中断有多个处理函数,说名这个是共享中断,所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断。
struct irqaction {
irq_handler_t handler; //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
unsigned long flags; //中断标志,注册时设置,比如上升沿中断,下降沿中断等
cpumask_t mask; //中断掩码
const char *name; //中断名称,产生中断的硬件的名字
void *dev_id; //设备id
struct irqaction *next; //指向下一个成员
int irq; //中断号,
struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/
};
irq_desc结构体中构造了很多项handle_irq函数,
struct irq_desc {
irq_flow_handler_t handle_irq; //指向中断函数, 中断产生后,就会执行这个handle_irq
struct irq_chip *chip; //指向irq_chip结构体,用于底层的硬件访问,下面会介绍
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */ //action链表,用于中断处理函数
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;
... ...
const char *name; //产生中断的硬件名字
} ;
下面是request_threaded_irq()函数
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action; //主要是用来存用户注册的中断处理函数,
struct irq_desc *desc;//在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断
int retval;
/*
* handle_IRQ_event() always ignores IRQF_DISABLED except for
* the _first_ irqaction (sigh). That can cause oopsing, but
* the behavior is classified as "will not fix" so we need to
* start nudging drivers away from using that idiom.
*/
if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) ==
(IRQF_SHARED|IRQF_DISABLED)) {
pr_warning(
"IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQs\n",
irq, devname);
}
#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;
desc = irq_to_desc(irq);//根据中断号,在irq_desc数组中返回一个具体的irq_desc
if (!desc)
return -EINVAL;
if (desc->status & IRQ_NOREQUEST)
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
//注册主要将我们传入的参数生成一个action,再添加到irq_desc上
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(irq, desc);
retval = __setup_irq(irq, desc, action); //将生成的action添加到irq_desc上
chip_bus_sync_unlock(irq, desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ
if (!retval && (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 disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
EXPORT_SYMBOL(request_threaded_irq);
程序的内核模块退出函数中调用的内核中断注销函数:
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return;
chip_bus_lock(irq, desc);
kfree(__free_irq(irq, dev_id));
chip_bus_sync_unlock(irq, desc);
}