linux裝置驅動歸納總結(六):2.分享中斷号
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
上一節介紹的内容是,調用接口request_irq(),使中斷号與中斷處理函數對應。但是,有時候會有這樣的情況,如果開發闆上按鍵的中斷已經被另外的驅動程式注冊中斷了,而我現在又想再注冊一次這個中斷,這就出現了一個中斷号不止對應一個中斷函數的情況。注意,這裡與硬體上的共享中斷不一樣,這裡是指,當一個中斷信号來了,基于作業系統,一個中斷的到來可以調用多個中斷處理程式,與硬體無關。
接下來從錯誤的代碼開始講解。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、錯誤的産生
以下的代碼在“6th_irq_2/1st”中。
假設有這樣的情況,有一個人,加載了子產品test.ko,子產品注冊了中斷号EINT1。接着,我編寫代碼(我并不知道中斷号已經被使用了),加載子產品test1.c(在err目錄下),子產品同樣注冊了中斷号EINT1。但這樣的注冊不成功的。
看效果:
[root:
1st]# insmod test.ko
//某處加載第一個時成功
hello
irq
[root:
1st]# key down
key
down
[root:
1st]# insmod err/test1.ko
//假設我并不知道已經記載了一次,加載第二次時失敗
[test_init]request
irq failed!
insmod:
cannot insert 'err/test1.ko':Device
or resource busy
[root:
1st]# cat /proc/interrupts
//檢視proc時發現,原來早就有人注冊了EINT1中斷号
CPU0
17:
2 s3c-ext0 key INT_EINT1
30:
20429 s3c S3C2410 Timer Tick
32:
0 s3c s3c2410-lcd
51:
3032 s3c-ext eth0
70:
252 s3c-uart0 s3c2440-uart
71:
277 s3c-uart0 s3c2440-uart
79:
0 s3c-adc s3c2410_action
80:
0 s3c-adc adc, s3c2410_action
83:
0 - s3c2410-wdt
Err:
這個就是兩男争一妞的情況了,解決辦法有兩個:
1、動物界的規矩,幹掉其中一個,誰赢誰說了算。
2、邪惡做法,和平解決,實作共享。
第一個解決辦法很簡單,查閱核心代碼,找到注冊該中斷的子產品,并且想辦法解除安裝該子產品。但是,如果那個子產品實在是太重要的,不能解除安裝,那隻能共享了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、共享中斷号的标記
在上一節的内容,注冊中斷處理函數接口request_irq()的參數irqflags還沒完全介紹完畢,除了IRQ_TIRGGER_FALLING這類指明中斷觸發的條件的标志外,下面還介紹三個:
SA_INTERRUPT:這個标志表明該中斷處理程式是一個快速中斷處理程式。過去,linux系統會區分快速我慢速中斷,但現在這個标志隻有這樣的效果:當響應這個中斷時,禁止所有的中斷,是該中斷處理函數不會被其他中斷打斷,迅速執行。
SA_SAMPLE_RANDOM:這個标志表明産生的中斷會對核心的entropy
pool有貢獻。Entropy
pool負責産生随機數。
SA_SHIRQ:這個标志表明多個中斷處理程式可以共享一個中斷号。
相對其他兩個,SA_SHIRQ是常用的标記。也就是說,我的中斷注冊失敗,原因是我沒有共享标記。也就是說,我需要在我的注冊中斷函數添加共享标記。但再回想一下兩男争一妞的場景,需要共享前提是兩個男的都同意共享,是以,原來的中斷注冊函數也需要共享标記。需要修改原來的函數test.c和我新寫的test1.c,都加上共享标記SA_SHIRQ,表示它們兩都同意共享。
在ARM下SA_SHIRQ相當于标志IRQF_SHARED:
53
#define IRQF_DISABLED 0x00000020 //SA_INTERRUPT
54
#define IRQF_SAMPLE_RANDOM 0x00000040 //SA_SAMPLE_RANDOM
55
#define IRQF_SHARED 0x00000080 //SA_SHIRQ
看修改後的代碼:
30
ret = request_irq(IRQ_EINT1, irq_handler,
31
IRQF_TRIGGER_FALLING |IRQF_SHARED,
"key INT_EINT1", NULL);
32
if(ret){
33
P_DEBUG("request irq failed!\n");
34
return ret;
35
}
另外一個一模一樣
30
ret = request_irq(IRQ_EINT1, irq_handler,
31
IRQF_TRIGGER_FALLING |IRQF_SHARED,
"key INT_EINT1", NULL);
32
if(ret){
33
P_DEBUG("request irq failed!\n");
34
return ret;
35
}
再加載一次,發現還是不行。
[root:
/]# cd review_driver/6th_irq/6th_irq_2/2nd/
[root:
2nd]# insmod test.ko
//加載第一個時就已經不行了
[test_init]request
irq failed!
insmod:
cannot insert 'test.ko':invalid
parameter
//提示參數錯誤
[root:
2nd]# insmod err/test1.ko
[test_init]request
irq failed!
insmod:
cannot insert 'err/test1.ko': invalid parameter
那到底是哪個參數錯了?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、裝置号ID——dev_id
話說兩個難得已經同意共享,但為什麼還是隻能加載一個呢?女的說:“你們是同意了,但我不能分辨不你們吖!”
中斷函數同時一樣的道理,實作共享中斷号的情況下,在調用free_irq()時,通過對應的标記,核心才會知道該釋放哪個中斷處理函數。
此時,最有一個沒講的參數dev_id就有他的用處了——核心通過dev_id對應中斷處理函數handler。另外,也可以通過它來傳參給中斷處理函數。
再次修改兩個程式,給每個程式就加上一個不同的dev_id:
13
irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數
14
{
15
printk("key down, dev_id[%d]\n", *(int *)dev_id);
16
return IRQ_HANDLED;
17
}
18
19
int id = 321;
20
。。。。
32
ret = request_irq(IRQ_EINT1, irq_handler,
33
IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1",&id);
。。。。
44
free_irq(IRQ_EINT1,&id);
另外一個一模一樣
6th_irq_2/1st/err/test.c
13
irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數
14
{
15
printk("hello xiaobai!, dev_id[%d]\n", *(int *)dev_id);
16
return IRQ_HANDLED;
17
}
18
19
int id = 123;
20
。。。。
32
ret = request_irq(IRQ_EINT1, irq_handler,
33
IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1",&id);
。。。。
44
free_irq(IRQ_EINT1,&id);
驗證一下,共享成功!
[root: 3rd]# insmod test.ko
//加載第一個成功
hello irq
[root: 3rd]# key down, dev_id[321]
key down, dev_id[321]
key down, dev_id[321]
[root: 3rd]# insmod err/test1.ko
//加載第二個也成功
hello irq
[root: 3rd]# key down,
dev_id[321]
//當我按下按鍵時,兩個中斷處理函數都調用了。
hello xiaobai!, dev_id[123]
key down, dev_id[321]
hello xiaobai!, dev_id[123]
key down, dev_id[321]
hello xiaobai!, dev_id[123]
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、介紹完了中斷接口函數,下面簡單講一下一個中斷産生後的流程:
以下的圖是《linux核心設計與實作》上的插圖,基于x86體系的,是以有些函數我在ARM下找不到。
先看前三步,這三步是我在《linux裝置驅動歸納總結(六):1.中斷的實作》的“從硬體角度看中斷”有較長的描述,當硬體(Hardware)産生中斷,傳給中斷處理器(Interrupt
controller),經過中斷處理器的一輪篩選後,把中斷傳給處理器(Processer)。
接下來就要講解處理器接收到中斷之後幹什麼:
1、處理器中斷核心(processor
interrupts the kernel):這步驟如下:
1.1、處理器會立即停止它正在進行的事情。
1.2、處理器關閉中斷。
1.3、儲存原來寄存器的值(這些值屬于被中斷的任務),切換工作模式至IRQ(S3C2440有7種工作模式,晶片手冊有講解,切換工作模式之前需要儲存原來部分寄存器上的值,具體請看S3C2440晶片手冊)。
2、do_IRQ:(這個部分說得可能有錯,因為我把核心的源代碼仔細看過,這部分主要是為了引出下一個的函數handle_IRQ_event())
在ARM相關的核心代碼我沒找到do_IRQ函數,但我找到一個相關的——asm_do_IRQ。看看大概做了些什麼事情:
2.1、把中斷号和一個在核心中存放對應的相關資料的結構體(這個結構體就是存放處理器中寄存器的值)作為參數,傳參給asm_do_IRQ。
29
.macro irq_handler
30
get_irqnr_preamble r5, lr
31
1: get_irqnr_and_base r0, r6, r5, lr
32
movne r1, sp
33
@
34
@ routine called withr0 = irq number, r1 = struct pt_regs *
35
@//獲得中斷号和一個結構體,作為參數傳給asm_do_IRQ
36
adrne lr, 1b
37
bneasm_do_IRQ
2.2、asm_do_IRQ進行一系列的準備工作之後,調用函數generic_handle_irq():
112
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct
pt_regs *regs)
113
{
114
struct pt_regs *old_regs = set_irq_regs(regs);//儲存并設定寄存器上的值,還沒弄懂操作的原因
115
116
irq_enter(); //一系列準備操作,沒細看
117
118
122
if (irq >= NR_IRQS)
123
handle_bad_irq(irq, &bad_irq_desc);
124
else
125generic_handle_irq(irq);
//調用該函數,開始進行中斷。
126
127
128
irq_finish(irq);
129
130
irq_exit();
131
set_irq_regs(old_regs);
132
}
2.3、generic_handle_irq中調用函數_do_IRQ:
309 static inline void
generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
310 {
311 #ifdef
CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
312 desc->handle_irq(irq,
desc);
313 #else
314 if
(likely(desc->handle_irq))
315 desc->handle_irq(irq,
desc);
316 else
317
__do_IRQ(irq);
318 #endif
319 }
320
321 static inline void
generic_handle_irq(unsigned int irq)
322 {
323 generic_handle_irq_desc(irq,
irq_to_desc(irq));
324 }
2.4、在__do_IRQ中,會判斷該中斷号是否已經注冊了中斷處理函數,如果沒有則退出中斷,切換至原來的工作模式。如果有,__do_IRQ會調用函數handle_IRQ_event()。
3、irqreturn_t
handle_IRQ_event(unsigned int irq, struct irqaction *action)幹了些什麼:
上面說的内容可能都不太詳細,因為我也不太明白有些函數的作用和具體的核心代碼,但下面的函數大家應該都會看得懂:
326
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction
*action)
327
{
328
irqreturn_t ret, retval = IRQ_NONE;
329
unsigned int status = 0;
330
331
if (!(action->flags &IRQF_DISABLED))
//在切換工作模式時核心是禁止了中斷的,如果
332
local_irq_enable_in_hardirq(); //注冊時使用标記IRQF_DISABLED,則開啟中斷
333
334
do {
335
ret = action->handler(irq, action->dev_id);
//調用我們注冊的中斷處理函數
336
if (ret == IRQ_HANDLED)
337
status |= action->flags;
338
retval |= ret;
339
action = action->next;
340
} while (action); //這是個循環,那就說,如果我們使用的IRQF_SHARED辨別,
341
//它會輪流執行該中斷号對應的所有注冊的中斷處理函數
342
if (status & IRQF_SAMPLE_RANDOM)
//如果使用該标記時相應的操作
343
add_interrupt_randomness(irq);
344local_irq_disable();
//再次關上中斷
345
346
return retval;
347
}
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、總結
總結一下中斷注冊的幾個注意事項:
1、調用request_irq必須通過傳回值判斷是否成功。
2、共享中斷号時,所有共享這個中斷号的request_irq都必須添加标記IRQF_SHARED,另外還需要使用一個獨特的裝置好dev_id,讓核心能夠通過dev_id對應注冊時的中斷處理函數。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx