一、poll机制
为什么我们需要poll机制呢。之前的测试程序是这样:
while (1)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
1.1、poll机制内核框架
如下图所示,在用户层上,使用poll或select函数时,和open、read那些函数一样,也要进入内核sys_poll函数里,接下来我们分析sys_poll函数来了解poll机制(位于/fs/select.c)
1)sys_poll代码如下:
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
{
if (timeout_msecs > 0) //参数timeout>0
{
timeout_jiffies = msecs_to_jiffies(timeout_msecs); //通过频率来计算timeout时间需要多少计数值
}
else
{
timeout_jiffies = timeout_msecs; //如果timeout时间为0,直接赋值
}
return do_sys_poll(ufds, nfds, &timeout_jiffies); //调用do_sys_poll。
}
2)然后进入do_sys_poll(位于fs/select.c):
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
...
/*初始化一个poll_wqueues变量table*/
poll_initwait(&table);
...
fdcount = do_poll(nfds, head, &table, timeout);
...
}
3)进入poll_initwait函数,发现主要实现以下一句,后面会分析这里:
table ->pt-> qproc=__pollwait; //__pollwait将在驱动的poll函数里的poll_wait函数用到
4)然后进入do_poll函数, (位于fs/select.c):
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, s64 *timeout)
{
...
for (;;)
{
...
set_current_state(TASK_INTERRUPTIBLE); //设置为等待队列状态
...
for (; pfd != pfd_end; pfd++) { //for循环运行多个poll机制
/*将pfd和pt参数代入我们驱动程序里注册的poll函数*/
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面并退出
{ count++;
pt = NULL; }
}
...
/*count非0(.poll函数返回非0),timeout超时计数到0,有信号在等待*/
if (count || !*timeout || signal_pending(current))
break;
...
/*进入休眠状态,只有当timeout超时计数到0,或者被中断唤醒才退出,*/
__timeout = schedule_timeout(__timeout);
...
}
__set_current_state(TASK_RUNNING); //开始运行
return count;
}
4.1)上面do_pollfd函数到底是怎么将pfd和pt参数代入的?代码如下(位于fs/select.c):
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
...
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, pwait);
...
return mask;
}
上面file->f_op 就是我们驱动里的file_oprations结构体,如下图所示:
所以do_pollfd(pfd, pt)就执行了我们驱动程序里的.poll(pfd, pt)函数(第2小节开始分析.poll函数)
4.2)当poll进入休眠状态后,又是谁来唤醒它?这就要分析我们的驱动程序.poll函数(第1.2小节开始分析.poll函数)
1.2、编写并分析.poll函数
在上一节驱动程序里添加以下代码:
#include <linux/poll.h> //添加头文件
/* .poll驱动函数: third_poll */
static unsigned int third_poll(struct file *fp, poll_table * wait) //fp:文件 wait:
{
unsigned int mask =0;
poll_wait(fp, &button_wait, wait);
if(even_press) //中断事件标志, 1:退出休眠状态 0:进入休眠状态
mask |= POLLIN | POLLRDNORM ;
return mask; //当超时,就返给应用层为0 ,被唤醒了就返回POLLIN | POLLRDNORM ;
}
static struct file_operations third_drv_fops={
.owner = THIS_MODULE,
.open = third_drv_open,
.read = third_drv_read,
.release=third_drv_class,
.poll = third_poll, //创建.poll函数
};
1)在我们1.1-4)小节do_poll函数有一段以下代码:
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面并退出
{
count++;
pt = NULL;
}
且在1.1-4.1)分析出: do_pollfd(pfd, pt)就是指向的驱动程序third_poll()函数,
所以当我们有按键按下时, 驱动函数third_poll()就会返回mask非0值,然后在内核函数do_poll里的count就++,poll机制并退出睡眠.
2)分析在内核中poll机制如何被驱动里的中断唤醒的
在驱动函数third_poll()里有以下一句:
poll_wait(fp, &button_wait, wait);
如上图所示,代入参数,poll_wait()就是执行了: p->qproc(filp, button_wait, p);
刚好对应了我们1.1-3)小节的:
table ->pt-> qproc=__pollwait;
所以poll_wait()函数就是调用了: __pollwait(filp, button_wait, p)
;
然后我们来分析__pollwait
函数,pollwait的代码如下:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,poll_table *p)
{
... ...
//把current进程挂载到&entry->wait下
init_waitqueue_entry(&entry->wait, current);
//再&entry->wait把添加到到button_wait中断下
add_wait_queue(wait_address, &entry->wait);
}
它是将poll进程添加到了button_wait中断队列里,这样,一有按键按下时,在中断服务函数里就会唤醒button_wait中断,同样也会唤醒poll机制,使poll机制重新进程休眠计数
3)驱动程序.poll函数返回值介绍
当中断休眠状态时,返回mask为0
当运行时返回:mask |= POLLIN | POLLRDNORM
其中参数意义如下图:
所以POLLIN | POLLRDNORM:普通数据可读|优先级带数据可读
mask就返回到应用层poll函数,
1.3、改进测试程序third_poll_test.c(添加poll函数)
在没有poll机制的情况下,大部分时间程序都处在read中休眠的那个位置。如果我们不想让程序停在这个位置,而是希望当有按键按下时,我们再去read,因此我们编写poll函数,测试程序调用poll函数根据返回值,来决定是否执行read函数。
poll机制作用:相当于定时器,设置一定时间使进程等待资源,如果时间到了中断还处于睡眠状态(等待队列),poll机制就会唤醒中断,获取一次资源
二、使用异步通知
三、互斥与阻塞机制
一、分析中断运行过程
目标:
分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction
1.1、裸板中断运行过程
在裸板程序中(参考stmdb和ldmia详解:https://www.cnblogs.com/lifexy/p/7363208.html):
1)按键按下,
2)cpu发生中断,
3)强制跳到异常向量入口执行(0x18中断地址处)
3.1)使用stmdb将寄存器值保存在栈顶(保护现场)
stmdb sp!, { r0-r12,lr }
3.2)执行中断服务函数
3.3)使用ldmia将栈顶处数据读出到寄存器中,并使pc=lr(恢复现场)
ldmia sp!, { r0-r12,pc }^
//^表示将spsr的值复制到cpsr,因为异常返回后需要恢复异常发生前的工作状态
1.2、linux中断运行过程
需要先设置异常向量地址(参考linux应用手册P412):
在ARM裸板中异常向量基地址是0x00000000,如下图:
而linux内核中异常向量基地址是0xffff0000(虚拟地址),位于代码arch/cam/kernel/traps.c,代码如下:
void __init trap_init(void)
{
/* CONFIG_VECTORS_BASE :内核配置项,在.config文件中,设置的是0Xffff0000*/
/* vectors =0xffff0000*/
unsigned long vectors = CONFIG_VECTORS_BASE;
...
/*将异常向量地址复制到0xffff0000处*/
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);
...
}
上面代码中主要是将__vectors_end - __vectors_start
之间的代码复制到vectors (0xffff0000)处,
__vectors_start
为什么是异常向量基地址?
通过搜索,找到它在arch/arm/kernel/entry_armv.S中定义:
__vectors_start:
swi SYS_ERROR0 //复位异常,复位时会执行
b vector_und + stubs_offset //undefine未定义指令异常
ldr pc, .LCvswi + stubs_offset //swi软件中断异常
b vector_pabt + stubs_offset //指令预取中止abort
b vector_dabt + stubs_offset //数据访问中止abort
b vector_addrexcptn + stubs_offset //没有用到
b vector_irq + stubs_offset //irq异常
b vector_fiq + stubs_offset //fig异常
其中stubs_offset是链接地址的偏移地址, vector_und、vector_pabt等表示要跳转去执行的代码
1)以vector_irq中断为例, vector_irq是个宏,它在哪里定义呢?
它还是在arch/arm/kernel/entry_armv.S中定义,如下所示:
vector_stub irq, IRQ_MODE, 4//irq:名字 IRQ_MODE:0X12 4:偏移量
上面的vector_stub 根据参数irq, IRQ_MODE, 4来定义” vector_ irq”这个宏(其它宏也是这样定义的)
2)vector_stub又是怎么实现出来的定义不同的宏呢?
我们找到vector_stub这个定义:
.macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数
.align 5
vector_\name: //定义不同的宏,比如vector_ irq
.if \correction //判断correction参数是否为0
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 //读出spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@ 进入管理模式
mrs r0, cpsr //读出cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
3)因此我们将上面__vectors_start
里的b vector_irq + stubs_offset 中断展开如下:
.macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数
.align 5
vector_stub irq, IRQ_MODE, 4 //这三个参数值代入 vector_stub中
vector_ irq: //定义 vector_ irq
/*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
sub lr, lr, #4
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@保存r0和lr和spsr
stmia sp, {r0, lr} //存入sp栈里
mrs lr, spsr //读出spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@ 进入管理模式
mrs r0, cpsr //读出cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位
mov r0, sp
ldr lr, [pc, lr, lsl #2] //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc
movs pc, lr //跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr.
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
从上面代码中的注释可以看出:
- 将发生异常前的各个寄存器值保存在SP栈里,若是中断异常,则PC=PC-4,也就是CPU下个要运行的位置处
- 然后根据进入中断前的工作模式不同,程序下一步将跳转到
_irq_usr
、或__irq_svc
等位置。
4)我们先选择__irq_usr
作为下一步跟踪的目标:
4.1)其中__irq_usr
的实现如下(arch\arm\kernel\entry-armv.S):
__irq_usr:
usr_entry //保存数据到栈里
get_thread_info tsk
irq_handler //调用irq_handler
b ret_to_user
4.2)irq_handler的实现过程,arch\arm\kernel\entry-armv.S
.macro irq_handler
get_irqnr_preamble r5, lr
get_irqnr_and_base r0, r6, r5, lr // get_irqnr_and_base:获取中断号,r0=中断号
movne r1, sp //r1等于sp (发生中断之前的各个寄存器的基地址)
adrne lr, 1b
bne asm_do_IRQ //调用asm_do_IRQ, irq=r0 regs=r1
irq_handler最终调用asm_do_IRQ
4.3)asm_do_IRQ实现过程,arch/arm/kernel/irq.c
该函数和裸板中断处理一样的,完成3件事情:
- 分辨是哪个中断;
- 通过desc_handle_irq(irq, desc)调用对应的中断处理函数;
- 清中断
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) //irq:中断号 *regs:发生中断前的各个寄存器基地址
{
struct pt_regs *old_regs = set_irq_regs(regs);
/*根据irq中断号,找到哪个中断, *desc =irq_desc[irq]*/
struct irq_desc *desc = irq_desc + irq; // irq_desc是个数组(位于kernel/irq/handle.c)
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc); // desc_handle_irq根据中断号和desc,调用函数指针,进入中断处理,
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
上面主要是执行desc_handle_irq函数进入中断处理
其中desc_handle_irq代码如下:
desc->handle_irq(irq, desc);//相当于执行irq_desc[irq]-> handle_irq(irq, irq_desc[irq]);
它会执行handle_irq成员函数,这个成员handle_irq又是在哪里被赋值的?
搜索handle_irq,找到它位于kernel/irq/chip.c,__set_irq_handler
函数下:
void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
{
... ...
desc = irq_desc + irq; //在irq_desc结构体数组中找到对应的中断
... ...
desc->handle_irq = handle; //使handle_irq成员指向handle参数函数
}
继续搜索__set_irq_handler
函数,它被set_irq_handler函数调用:
static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
__set_irq_handler(irq, handle, 0, NULL);
}
继续搜索set_irq_handler函数,如下图
发现它在s3c24xx_init_irq(void)函数中被多次使用,显然在中断初始化时,多次进入__set_irq_handler
函数,并在irq_desc数组中构造了很多项 handle_irq函数
我们来看看irq_desc中断描述结构体到底有什么内容:
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; //产生中断的硬件名字
} ;
其中的成员*chip的结构体,用于底层的硬件访问, irq_chip类型如下:
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); //启动中断
void (*shutdown)(unsigned int irq); //关闭中断
void (*enable)(unsigned int irq); //使能中断
void (*disable)(unsigned int irq); //禁止中断
void (*ack)(unsigned int irq); //响应中断,就是清除当前中断使得可以再接收下个中断
void (*mask)(unsigned int irq); //屏蔽中断源
void (*mask_ack)(unsigned int irq); //屏蔽和响应中断
void (*unmask)(unsigned int irq); //开启中断源
... ...
int (*set_type)(unsigned int irq, unsigned int flow_type); //将对应的引脚设置为中断类型的引脚
... ...
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id); //释放中断服务函数
#endif
};
其中的成员struct irqaction *action,主要是用来存用户注册的中断处理函数,
一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.
所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。
所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断
irqaction结构定义如下:
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/
};
上面3个结构体的关系如下图所示:
我们来看看s3c24xx_init_irq()函数是怎么初始化中断的,以外部中断0为例(位于s3c24xx_init_irq函数):
s3c24xx_init_irq()函数中部分代码如下:
/*其中IRQ_EINT0=16, 所以irqno=16 */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++)
{
irqdbf("registering irq %d (ext int)\n", irqno);
/*在set_irq_chip函数中会执行:
desc = irq_desc + irq;
desc->chip = chip;*/
set_irq_chip(irqno, &s3c_irq_eint0t4); //所以(irq_desc+16)->chip= &s3c_irq_eint0t4
/* set_irq_handler 会调用__set_irq_handler 函数*/
set_irq_handler(irqno, handle_edge_irq); //所以(irq_desc+16)-> handle_irq = handle_edge_irq
set_irq_flags(irqno, IRQF_VALID);
}
初始化了外部中断0后,当外部中断0触发,就会进入我们之前分析的asm_do_IRQ函数中,调用(irq_desc+16)-> handle_irq也就是handle_edge_irq函数。
我们来分析下handle_edge_irq函数是如何执行中断服务的:
void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
const unsigned int cpu = smp_processor_id();
spin_lock(&desc->lock);
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
/*判断这个中断是否正在运行(INPROGRESS)或者禁止(DISABLED)*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || !desc->action))
{
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq); //屏蔽中断
goto out_unlock;
}
kstat_cpu(cpu).irqs[irq]++; //计数中断次数
/* Start handling the irq */
desc->chip->ack(irq); //开始处理这个中断
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS; //标记当前中断正在运行
do {
struct irqaction *action = desc->action;
irqreturn_t action_ret;
if (unlikely(!action)) { //判断链表是否为空
desc->chip->mask(irq);
goto out_unlock;
}
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq);
desc->status &= ~IRQ_MASKED;
}
desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action); //真正的处理过程
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}
上面handle_edge_irq()函数主要执行了:
1)desc->chip->ack(irq); //开始处理这个中断
在s3c24xx_init_irq()函数中chip成员指向了s3c_irq_eint0t4(),
所以desc->chip->ack(irq)就是执行handle_edge_irq(irq)函数,handle_edge_irq函数如下:
s3c_irq_ack(unsigned int irqno)
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
__raw_writel(bitval, S3C2410_SRCPND); //向SRCPND寄存器写入bitval ,清SRCPND中断
__raw_writel(bitval, S3C2410_INTPND); //向INTPND寄存器位写入bitval ,清INTPND中断
}
所以desc->chip->ack(irq); 主要执行清中断之类的
2)handle_IRQ_event(irq, action); //真正的处理过程
handle_IRQ_event()代码如下:
handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
ret = action->handler(irq, action->dev_id); //执行action->handler
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next; //指向下个action成员
} while (action); //取出action所有成员
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
所以handle_IRQ_event()函数主要是取出action链表中的成员,然后执行irq_desc->action->handler(irq, action->dev_id);
action链表是irq_desc中断描述符结构体的 成员
1.3、本节常用函数总结
trap_init(): 初始化异常向量的虚拟基地址,一般为0XFFFF0000
s3c24xx_init_irq():初始化各个中断
set_irq_chip(irqno, &s3c_irq_eint0t4):设置irq_desc[irqno]->chip等于第二个参数
set_irq_handler(irqno, handle_edge_irq); 设置irq_desc[irqno]->handle_irq等于第二个参数
asm_do_IRQ():中断产生后,会进入这个函数,最终执行 desc->handle_irq(irq, desc);
handle_edge_irq(irq, desc):执行中断函数,主要是执行以下两步骤:
1) desc->chip->ack(irq):相应中断,也就是清中断,使能再次接受中断
2) handle_IRQ_event(irq, action):执行中断的服务函数,desc->action->handler
1.4、中断运行总结
当产生一个中断异常
1)进入异常向量vector,比如中断异常: vector_irq + stubs_offset
2)比如中断异常之前是用户模式(正常工作),则进入__irq_usr
,然后最终进入asm_do_IRQ函数,
3)然后执行irq_desc [irq]->handle_irq(irq, irq_desc [irq]);
通过刚才的分析,外部中断0(irq_desc[16])的handle_irq成员等于handle_edge_irq函数,所以就是执行handle_edge_irq(irq, irq_desc [irq]);
4)以外部中断0为例,在handle_edge_irq函数中主要执行两步:
->4.1 desc->chip->ack //使用chip成员中的ack函数来清中断
->4.2 执行action链表 irq_desc->action->handler
这4步都是系统给做好的(中断的框架),当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断
二、分析request_irq(free_irq)函数如何注册(注销)中断
目标:
分析request_irq()如何申请注册中断,free_irq()如何注销中断
当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断
2.1、request_irq()
request_irq()位于kernel/irq/ manage .c,函数原型如下:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
参数说明:
unsigned int irq:为要注册中断服务函数的中断号,比如外部中断0就是16,定义在mach/irqs.h
irq_handler_t handler:为要注册的中断服务函数,就是(irq_desc+ irq )->action->handler
unsigned long irqflags: 触发中断的参数,比如边沿触发, 定义在linux/interrupt.h。
const char devname:中断程序的名字,使用cat /proc/interrupt 可以查看中断程序名字
void dev_id:传入中断处理程序的参数,注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数
1)request_irq代码如下:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
{
struct irqaction *action;
...
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); //注册irqaction结构体类型的action
if (!action)
return -ENOMEM;
/* 将带进来的参数赋给action */
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);
...
retval = setup_irq(irq, action); // 进入setup_irq(irq, action),设置irq_ desc[irq]->action
if (retval)
kfree(action);
return retval;
}
从上面分析,request_irq()函数主要注册了一个irqaction型action,然后把参数都赋给这个action,最后进入setup_irq(irq, action)设置irq_ desc[irq]->action
2)看看setup_irq(irq, action)如何设置irq_ desc[irq]->action的:
int setup_irq(unsigned int irq, struct irqaction *new)
{
struct irq_desc *desc = irq_desc + irq; //根据中断号找到irq_ desc[irq]
...
p = &desc->action; //指向desc->action
old = *p;
if (old) { //判断action是否为空
/*判断这个中断是否支持共享 (IRQF_SHARED)*/
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch; //不支持,则跳转
}
#if defined(CONFIG_IRQ_PER_CPU)
/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
#endif
/*找到action链表尾处,后面用于添加 新的中断服务函数(*new) */
do {
p = &old->next;
old = *p;
} while (old);
shared = 1; //表示该中断支持共享,添加新的action,否则直接赋值新的action
}
*p = new; //指向新的action
...
if (!shared) { //若该中断不支持共享
irq_chip_set_defaults(desc->chip); //更新desc->chip,将为空的成员设置默认值
#if defined(CONFIG_IRQ_PER_CPU)
if (new->flags & IRQF_PERCPU)
desc->status |= IRQ_PER_CPU;
#endif
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
if (desc->chip && desc->chip->set_type) // desc->chip->set_type设置为中断引脚
desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
else
printk(KERN_WARNING "No IRQF_TRIGGER set_type "
"function for IRQ %d (%s)\n", irq,
desc->chip ? desc->chip->name :
"unknown");
} else
compat_irq_chip_set_default_handler(desc);
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);
if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = 0;
desc->status &= ~IRQ_DISABLED;
if (desc->chip->startup)
desc->chip->startup(irq); //开启中断
else
desc->chip->enable(irq); //使能中断
} else
/* Undo nested disables: */
desc->depth = 1;
}
从上面可以看出setup_irq(irq, action)主要是将action中断服务函数放在irq_ desc[irq]->action中,
然后设置中断引脚:
desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
最后[开启/使能]中断:
desc->chip->[startup(irq) /enable(irq)]; //[开启/使能]中断
我们以外部中断0的desc[16]->chip->set_type为例,来看看它是如何初始化中断引脚的:
s3c_irqext_type(unsigned int irq, unsigned int type)
{
void __iomem *extint_reg;
void __iomem *gpcon_reg;
unsigned long gpcon_offset, extint_offset;
unsigned long newvalue = 0, value;
if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3)) //找到寄存器
{
gpcon_reg = S3C2410_GPFCON;
extint_reg = S3C24XX_EXTINT0; // EXTINT0对应中断0~中断7
gpcon_offset = (irq - IRQ_EINT0) * 2; //找到gpcon寄存器的相应位偏移量
extint_offset = (irq - IRQ_EINT0) * 4; //找到extint寄存器的相应位偏移量
}
else if(... ...) //找到其它的EINT4~23的寄存器
/*将GPIO引脚设为中断引脚*/
value = __raw_readl(gpcon_reg);
value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset); //相应位设置0x02
switch (type) //设置EXTINT0中断模式
{
case IRQT_NOEDGE: //未指定的中断模式
printk(KERN_WARNING "No edge setting!\n");
break;
case IRQT_RISING: //上升沿触发,设置EXTINT0相应位为0x04
newvalue = S3C2410_EXTINT_RISEEDGE;
break;
case IRQT_FALLING: //下降沿触发,设置EXTINT0相应位为0x02
newvalue = S3C2410_EXTINT_FALLEDGE;
break;
case IRQT_BOTHEDGE: //双边沿触发,设置EXTINT0相应位为0x06
newvalue = S3C2410_EXTINT_BOTHEDGE;
break;
case IRQT_LOW: //低电平触发,设置EXTINT0相应位为0x00
newvalue = S3C2410_EXTINT_LOWLEV;
break;
case IRQT_HIGH: //高电平触发,设置EXTINT0相应位为0x01
newvalue = S3C2410_EXTINT_HILEV;
break;
default:
}
/*更新EXTINT0相应位*/
value = __raw_readl(extint_reg);
value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset); //相应位设置
__raw_writel(value, extint_reg); //向extint_reg写入value值
return 0;
}
通过上面分析,就是将action->flags带入到desc[16]->chip->set_type里面,根据不同的中断来设置寄存器模式
2.2、free_irq()
卸载中断的函数是free_irq(),也位于kernel/irq/ manage .c,函数原型如下:
free_irq(unsigned int irq, void *dev_id);
参数说明:
unsigned int irq:要卸载的中断号
void *dev_id:这个是要卸载的中断action下的哪个服务函数,
1)free_irq()代码如下:
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; //p指向中断里的action链表
for (;;) {
struct irqaction *action = *p;
if (action) { //在action链表中找到与参数dev_id相等的中断服务函数
struct irqaction **pp = p;
p = &action->next;
if (action->dev_id != dev_id) //直到找dev_id才执行下面,进行卸载
continue;
*pp = action->next; //指向下个action成员,将当前的action释放掉
#ifdef CONFIG_IRQ_RELEASE_METHOD
if (desc->chip->release) //执行chip->release释放中断服务函数相关的东西
desc->chip->release(irq, dev_id);
#endif
if (!desc->action) { //判断当前action成员是否为空,表示没有中断服务函数
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown) //执行chip->shutdown关闭中断
desc->chip->shutdown(irq);
else //执行chip-> disable禁止中断
desc->chip->disable(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
unregister_handler_proc(irq, action);
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);//没有找到要卸载的action成员
spin_unlock_irqrestore(&desc->lock, flags);
return;
}
#ifdef CONFIG_DEBUG_SHIRQ
if (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
}
从上面分析,free_irq()函数主要通过irq和dev_id来找要释放的中断action
若释放的中断action不是共享的中断(为空),则执行:
*pp = action->next; //指向下个action成员,将当前的action释放掉
desc->chip->release(irq, dev_id); //执行chip->release释放中断服务函数相关的东西
desc->status |= IRQ_DISABLED; //设置desc[irq]->status标志位
desc->chip->[shutdown(irq)/ desible(irq)]; //关闭/禁止中断
若释放的中断action是共享的中断(还有其它中断服务函数)的话就只执行:
*pp = action->next; //指向下个action成员,将当前的action释放掉
desc->chip->release(irq, dev_id); //执行chip->release释放中断服务函数相关的东西
三、自己编写中断方式按键驱动程序
目标:
编写双边沿中断的按键驱动
确定按键0~3分别是GPF0,GPF2,GPG3,GPG11
需要设置4个按键的EINT0, EINT2, EINT11, EINT19的模式为双边沿,且设置按键引脚为中断引脚
这里我们只需要使用request_irq函数就行了, 在request_irq函数里会初始chip->set_type(设置引脚和中断模式)
1)首先添加头文件
#include <linux/irq.h> //要用到IRQ_EINT0和IRQT_RISING这些变量
2)在second_drv_open函数中,申请4个中断:
/* IRQ_EINT0: 中断号, 定义在 asm/arch/irqs.h,被linux/irq.h调用
buttons_irq : 中断服务函数,
IRQT_ BOTHEDGE: 双边沿中断, 定义在 asm/irq.h,被linux/irq.h调用
“S1”: 保存文件到/proc/interrupt/S1,
1: dev_id,中断函数的参数, 被用来释放中断服务函数,中断时并会传入中断服务函数
*/
request_irq(IRQ_EINT0, buttons_irq,IRQT_BOTHEDGE, “S1”, 1);
request_irq(IRQ_EINT2, buttons_irq,IRQT_ BOTHEDGE, “S2”, 1);
request_irq(IRQ_EINT11, buttons_irq,IRQT_ BOTHEDGE, “S3”, 1);
request_irq(IRQ_EINT19, buttons_irq,IRQT_ BOTHEDGE, “S4”, 1);
3)在file_oprations结构体中添加.release成员函数,用来释放中断
static struct file_operations second_drv_fops={
.owner = THIS_MODULE,
.open = second_drv_open,
.read = second_drv_read,
.release=second_drv_class, //里面添加free_irq函数,来释放中断服务函数
};
然后写.release成员函数,释放中断:
int second_drv_class(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0,1);
free_irq(IRQ_EINT2,1);
free_irq(IRQ_EINT11,1);
free_irq(IRQ_EINT19,1);
return 0;
}
4)写action->handler中断服务函数,在第2小节里request_irq函数的中断服务函数是buttons_irq
static irqreturn_t buttons_irq (int irq, void *dev_id) //irq:中断号, void *:表示支持所有类型
{
printk(“irq=%d\n”);
return IRQ_HANDLED;
}
5)make后,然后放在开发板里insmod,并挂载好了buttons设备节点,如下图:
6)通过exec 5</dev/buttons
将/dev/buttons 设备节点挂载到-sh进程下描述符5:
如下图,使用ps查看-sh进程为801,然后ls -l /proc/801/fd 找到描述符5指向/dev/buttons
如下图,并申请中断,当有按键按下时,就进入中断服务函数buttons_irq()打印数据:
7)通过exec 5<&-
将描述符5卸载
会进入.release成员second_drv_class()函数释放中断,
然后cat /proc/interrupts会发现申请的中断已经注销掉了,在-sh进程fd文件里也没有文件描述符5
8)改进中断按键驱动程序
使用等待队列,让read函数没有中断时,进入休眠状态,降低CPU.
使用dev_id来获取不同按键的状态,是上升沿还是下降沿触发?
8.1)接下来要用到以下几个函数:
s3c2410_gpio_getpin(unsigned int pin); //获取引脚高低电平
//pin: 引脚名称,例如:S3C2410_GPA0,定义在<asm/arch/regs-gpio.h>
队列3个函数(声明队列,唤醒队列,等待队列):
static DECLARE_WAIT_QUEUE_HEAD(qname);
//声明一个新的等待队列类型的中断;qname:就是中断名字,被用来后面的唤醒中断和等待中断
wake_up_interruptible(*qname);
//唤醒一个中断,会将这个中断重新添加到runqueue队列(将中断置为TASK_RUNNING状态)qname:指向声明的等待队列类型中断名字
wait_event_interruptible(qname, condition);
//等待事件中断函数,用来将中断放回等待队列
//qname: (wait queue):为声明的等待队列的中断名字
//condition:状态,等于0时就是中断进入休眠, 1:退出休眠
使用此函数的前提是condition要为0,然后将这个中断从runqueue队列中删除(将中断置为TASK_INTERRUPTIBLE状态),然后会在函数里一直for(; ;)判断condition为真才退出
注意:此时的中断属于僵尸进程(既不在等待队列,也不在运行队列),当需要这个进程时,需要使用wake_up_interruptible(*qname)来唤醒中断
8.2)驱动程序步骤
8.2.1)定义引脚描述结构体数组,每个结构体都保存按键引脚和初始状态,然后在中断服务函数中通过s3c2410_gpio_getpin()来获取按键是松开还是按下(因为中断是双边沿触发),并保存在key_val里(它会在.read函数发送给用户层)
/*
*引脚描述结构体
*/
struct pin_desc{
unsigned int pin;
unsigned int pin_status;
};
/*
*key初始状态(没有按下): 0x01,0x02,0x03,0x04
*key状态(按下): 0x81,0x82,0x83,0x84
*/
struct pin_desc pins_desc[4]={
{S3C2410_GPF0,0x01 },
{S3C2410_GPF2, 0x02 },
{S3C2410_GPG3, 0x03 },
{S3C2410_GPG11,0x04},} ;
8.2.2)声明等待队列类型的中断button_wait:
static DECLARE_WAIT_QUEUE_HEAD(button_ wait); //声明等待队列类型的中断
8.2.3)定义全局变量even _press,用于中断事件标志:
static volatile int even _press = 0;
8.2.4)在.read函数里,将even _press置0放入等待事件中断函数中,判断even _press为真,才发送数据:
even_press = 0;
wait_event_interruptible(button_ wait, even _press); //当even _press为真,表示有按键按下,退出等待队列
copy_to_user(buf, &key_val, 1); //even _press为真,有数据了,发送给用户层
8.2.5)在中断服务函数里,发生中断时, 就将even _press置1,并唤醒中断button_wait(.read函数里就会发送数据给用户层):
even _press = 0;
wake_up_interruptible(&button_wait); //唤醒中断
8.3)更改测试程序second_interrupt_test.c
#include <sys/types.h> //调用sys目录下types.h文件
#include <sys/stat.h> //stat.h获取文件属性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*secondtext while一直获取按键信息 */
int main(int argc,char **argv)
{
int fd,ret;
unsigned int val=0;
fd=open("/dev/buttons",O_RDWR);
if(fd<0)
{
printf("can't open!!!\n");
return -1;
}
while(1)
{
ret=read(fd,&val,1); //读取一个值,(当在等待队列时,本进程就会进入休眠状态)
if(ret<0)
{
printf("read err!\n");
continue;
}
printf("key_val=0X%x\r\n",val);
}
return 0;
}
9)运行结果
insmod second_interrupt.ko //挂载驱动设备
./second_interrupt_test & //后台运行测试程序
创建了4个中断,如下图:
当没有按键按下时,这个进程就处于静止状态staitc,如下图所示:
在等待队列(休眠状态)下,该进程占用了CPU0%资源,如下图所示:
当有按键按下时,便打印数据,如下图所示:
本节驱动代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static struct class *seconddrv_class;
static struct class_device *seconddrv_class_devs;
/* 声明等待队列类型中断 button_wait */
static DECLARE_WAIT_QUEUE_HEAD(button_wait);
/*
* 定义中断事件标志
* 0:进入等待队列 1:退出等待队列
*/
static int even_press=0;
/*
* 定义全局变量key_val,保存key状态
*/
static int key_val=0;
/*
*引脚描述结构体
*/
struct pin_desc{
unsigned int pin;
unsigned int pin_status;
};
/*
*key初始状态(没有按下): 0x01,0x02,0x03,0x04
*key状态(按下): 0x81,0x82,0x83,0x84
*/
struct pin_desc pins_desc[4]={
{S3C2410_GPF0,0x01 },
{S3C2410_GPF2, 0x02 },
{S3C2410_GPG3, 0x03 },
{S3C2410_GPG11,0x04},} ;
int second_drv_class(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 irqreturn_t buttons_irq (int irq, void *dev_id) //中断服务函数
{
struct pin_desc *pindesc=(struct pin_desc *)dev_id; //获取引脚描述结构体
unsigned int pin_val=0;
pin_val=s3c2410_gpio_getpin(pindesc->pin);
if(pin_val)
{
/*没有按下 (下降沿),清除0x80*/
key_val=pindesc->pin_status&0xef;
}
else
{
/*按下(上升沿),加上0x80*/
key_val=pindesc->pin_status|0x80;
}
even_press=1; //退出等待队列
wake_up_interruptible(&button_wait); //唤醒 中断
return IRQ_HANDLED;
}
static int second_drv_open(struct inode *inode, struct file *file)
{
request_irq(IRQ_EINT0,buttons_irq,IRQT_BOTHEDGE,"S1",&pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq,IRQT_BOTHEDGE, "S2", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq,IRQT_BOTHEDGE, "S3", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq,IRQT_BOTHEDGE, "S4", &pins_desc[3]);
return 0;
}
static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{/*将中断 进入等待队列(休眠状态)*/
wait_event_interruptible(button_wait, even_press);
/*有按键按下,退出等待队列,上传key_val 给用户层*/
if(copy_to_user(buf,&key_val,sizeof(key_val)))
return EFAULT;
even_press=0; //数据发完后,立马设为休眠状态,避免误操作
return 0;
}
static struct file_operations second_drv_fops={
.owner = THIS_MODULE,
.open = second_drv_open,
.read = second_drv_read,
.release=second_drv_class, //里面添加free_irq函数,来释放中断服务函数
};
volatile int second_major;
static int second_drv_init(void)
{
second_major=register_chrdev(0,"second_drv",&second_drv_fops); //创建驱动
seconddrv_class=class_create(THIS_MODULE,"second_dev"); //创建类名
seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons");
return 0;
}
static int second_drv_exit(void)
{
unregister_chrdev(second_major,"second_drv"); //卸载驱动
class_device_unregister(seconddrv_class_devs); //卸载类设备
class_destroy(seconddrv_class); //卸载类
return 0;
}
module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL v2");