一、分析中斷運行過程
本節目標:
分析在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中:
需要先設置異常向量地址(參考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釋放中斷服務函數相關的東西
三、自己編寫中斷方式按鍵驅動程序
本節目標:
編寫雙邊沿中斷的按鍵驅動
3.1、確定需要配置的寄存器
看原理圖和2440手冊,確定按鍵0~3分別是GPF0,GPF2,GPG3,GPG11
所以需要設置4個按鍵的EINT0, EINT2, EINT11, EINT19的模式為雙邊沿,且設置按鍵引腳為中斷引腳。
這里我們只需要使用request_irq函數就行了, 在request_irq函數里會初始chip->set_type(設置引腳和中斷模式)
3.2、驅動程序編寫
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;
}
3.3、編譯,加載,測試
1)make后,然在開發板里insmod,並掛載好了buttons設備節點,如下圖:

2)通過 exec 5</dev/buttons 將/dev/buttons 設備節點掛載到-sh進程下描述符5:
如下圖,使用ps查看-sh進程為801,然后ls -l /proc/801/fd 找到描述符5指向/dev/buttons

如下圖,並申請中斷,當有按鍵按下時,就進入中斷服務函數buttons_irq()打印數據:

3)通過 exec 5<&-將描述符5卸載
會進入.release成員second_drv_class()函數釋放中斷,
然后cat /proc/interrupts會發現申請的中斷已經注銷掉了,在-sh進程fd文件里也沒有文件描述符5
3.4、改進中斷按鍵驅動程序-等待隊列
改進措施:
使用等待隊列,讓read函數沒有中斷時,進入休眠狀態,降低CPU.
使用dev_id來獲取不同按鍵的狀態,是上升沿還是下降沿觸發?
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)來喚醒中斷
2)驅動程序步驟
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},} ;
2.2)聲明等待隊列類型的中斷button_wait:
static DECLARE_WAIT_QUEUE_HEAD(button_ wait); //聲明等待隊列類型的中斷
2.3)定義全局變量even _press,用於中斷事件標志:
static volatile int even _press = 0;
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為真,有數據了,發送給用戶層
2.5)在中斷服務函數里,發生中斷時, 就將even _press置1,並喚醒中斷button_wait(.read函數里就會發送數據給用戶層):
even _press = 0;
wake_up_interruptible(&button_wait); //喚醒中斷
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;
}
4)運行結果
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");
