xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什麼是中斷
中斷分兩種:
1)中斷,又叫外部中斷或異步中斷,它的產生是由於外設向處理器發出中斷請求。其中外部中斷也有兩種,這是由配置寄存器設定的:普通中斷請求(IRQ)和快速中斷請求(FIQ)。一般地,linux下很少使用快速中斷請求。
2)異常,又叫內部中斷或同步中斷,它的產生是由於處理器執行指令出錯。
在以下的內容我是要介紹由於外部設備產生的中斷。
這裏我還有兩個名詞要說清楚
1)中斷請求線:在後面也叫中斷號,每個中斷都會通過一個唯一的數值來標識,而這個值就稱做中斷請求線
2)在2440芯片中,有些中斷是需要共享一箇中斷寄存器中的一位,如EINT4——EINT7,它們是共享寄存器SRCPEND的第4位。具體可以查看芯片手冊。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、什麼是中斷處理函數
在相應一箇中斷是,內核會執行該信號對應的一個函數,該函數就叫做該中斷對應的中斷處理函數。一般來說,中斷的優先級是最高的,一但接收到中斷,內核就會調用對應的中斷處理函數。
中斷處理函數運行在中斷上下文中。中斷上下文與內核上下文有一點區別:
內核上下文是指應用層調用系統調用陷入內核執行,內核代表陷入的進程執行操作。函數中可以通過current查看當前進程(即應用層的進程)的信息,並且可以睡眠。
中斷上下文中,不能通過current查看調用它的應用層進程的信息,同時,處於中斷上下文時,不能睡眠。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、從硬件角度看中斷
中斷的產生到處理器獲得中斷這段過程中,還要通過中斷處理器來篩選信號。
先溫習一下S3C2440芯片手冊的知識:中斷是如何產生的,中斷處理器本身如何處理中斷。先看一下一幅經典的圖,這是介紹中斷控制器的工作流程:
從硬件上的分類,有兩種不同的中斷類型:
1)自己佔有SORCPND寄存器的一位(without sub-register)。
2)幾個中斷共同享用SRCPND寄存器的一位(with sub-register)。
其實兩種都差不多,只是多了兩步的檢測。我以自己佔用一位的中斷來舉例,如EINT1,在我的開發板,EINT1上接了一個按鍵。
1)當我按下按鍵產生電平變化,傳到S3C2440的中斷控制器上(即將要進入上面圖的流程圖)。
2)首先,信號要經過寄存器SRCPND,SRCPND是用來配置當前的處理器要接收什麼中斷,如果該寄存器配置成接收EINT1中斷(對應位置一),則允許繼續下一步。
3)然後,信號經過寄存器MASK,這是用來設置當前系統需要屏蔽的中斷。注意,這裏的屏蔽跟上一個寄存器的不接收中斷是不一樣的。這裏的屏蔽是指,中斷是接受了,但是由於某種原因,先暫時不屏蔽產生的中斷。
4)通過INTPND寄存器,查看當前是否有相同的中斷已經被請求(如果是,INTPND對應位置一)。
5)如果沒有相同的中斷在請求,中斷處理器纔會把這個信號傳給處理器,這時處理器纔會知道有EINT0的中斷真正來了,要對信號進行處理了。
注:如果設定了EINT0是快速中斷模式(FIQ),中斷通過SRCPND寄存器後就會通過MODE寄存器的判斷,確定是FIQ後,中斷控制器優先將該中斷傳給CPU處理。
6)對應傳來的中斷類型(IRQ或FIQ),通過CPSR寄存器切換到對應的工作模式(ARM有七種工作模式)。
7)切換工作模式後,進入指定的中斷處理入口執行中斷處理函數。
注意:第6、7步在linux下的實現相對複雜,不像在裸板程序,只需要切換一下工作模式,執行相應的函數就可以了。遲點會介紹linux如何實現。
來個流程圖比較只在,同時來個類比,處理器是老闆,中斷處理器是小祕:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、註冊和釋放中斷處理函數
上面的介紹只是講解了一個設備產生中斷後要經過怎麼樣的步驟才能讓處理器接收到中斷信號。傳入處理器後,接下來的工作就是由內核來實現了,那是一個複雜的機制,我們這裏先不說。但是,內核提供了相關的接口給我們,我們只要通過接口告訴內核,當來了指定中斷時,內核你該執行哪個中斷處理函數。
註冊中斷處理函數:
/*include <linux/interrupt.h>*/
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
使用:
將中斷號irq與中斷處理函數handler對應
參數:
irq:指定要分配的中斷號,中斷號的定義在“include/mach/irqs.h”中。注意,不管是單獨佔有中斷請求線的中斷,還是共享中斷請求線的每個中斷,都有一個對應的中斷號。,所以,調用該函數不需要考慮是哪種中斷(是否共享寄存器),你想哪種中斷響應,你就填對應的中斷號。
handler:中斷處理函數指針。
irqflags:中斷處理標記,待會介紹:
devname:該字符串將顯示在/proc/irq和/pro/interrupt中。
dev_id:ID 號,待會會介紹。
返回值:成功返回0,失敗返回非0。
註冊函數需要注意兩件事:
1)該函數會睡眠。
2)必須判斷返回值。
中斷處理標誌irqflags,這裏先介紹幾個待會要用的:
/*linux-2.6.29/include/linux/interrupt.h*/
29 #define IRQF_TRIGGER_NONE 0x00000000
30 #define IRQF_TRIGGER_RISING 0x00000001 //上升沿觸發中斷
31 #define IRQF_TRIGGER_FALLING 0x00000002 //下降沿觸發中斷
32 #define IRQF_TRIGGER_HIGH 0x00000004 //高電平觸發中斷
33 #define IRQF_TRIGGER_LOW 0x00000008 //低電平觸發中斷
34 #define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
35 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
36 #define IRQF_TRIGGER_PROBE 0x00000010
釋放中斷處理函數:
void free_irq(unsigned int irq, void *dev_id)
編寫中斷處理函數:
中斷處理函數聲明如下:
static irqreturn_t intr_handler(int irq, void *dev_id)
先看第一個參數irq,這是調用中斷處理函數時傳給它的中斷號,對於新版本的內核,這個參數已經用處不大,一般只用於打印。
第二個參數dev_id,這個參數與request_irq()的參數dev_id一致,由於待會的程序我並不需要用這個參數,所以先不介紹。
再看返回值,中斷處理函數的返回值有三個:
/*linux-2.6.29/include/linux/interrupt..h*/
21 #define IRQ_NONE (0) //如果產生的中斷並不會執行該中斷處理函數時返回該值
22 #define IRQ_HANDLED (1) //中斷處理函數正確調用會返回
23 #define IRQ_RETVAL(x) ((x) != 0) //指定返回的數值,如果非0,返回IRQ_HADLER,否則
26 #ifndef IRQ_NONE //返回IRQ_NONE。
接下來就要寫函數了,在我的開發板中,有一個按鍵是對應EINT1,我要實現的操作是,當我按下按鍵,終端打印”key down”。在這個程序中,我並沒有使用dev_id。這將在會以後的章節介紹。
/*6th_irq_1/1st/test.c*/
1 #include <linux/module.h>
2 #include <linux/init.h>
3
4 #include <linux/interrupt.h>
5
。。。省略。。。
13 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數
14 {
15 printk("key down\n");
16 return IRQ_HANDLED;
17 }
18
19 static int __init test_init(void) //模塊初始化函數
20 {
21 int ret;
22
23 /*註冊中斷處理函數,必須查看返回值
24 * IRQ_EINT1:中斷號,定義在"include/mach/irqs.h"中
25 * irq_handler:中斷處理函數
26 * IRQ_TIRGGER_FALLING:中斷類型標記,下降沿觸發中斷
27 * ker_INT_EINT1:中斷的名字,顯示在/proc/interrupts等文件中
28 * NULL;現在我不使用dev_id,所以這裏不傳參數
29 */
30 ret = request_irq(IRQ_EINT1, irq_handler, IRQF_TRIGGER_FALLING,
31 "key INT_EINT1", NULL);
32 if(ret){
33 P_DEBUG("request irq failed!\n");
34 return -1;
35 }
36 printk("hello irq\n");
37 return 0;
38 }
39
40 static void __exit test_exit(void) //模塊卸載函數
41 {
42 free_irq(IRQ_EINT1, NULL);
43 printk("good bye irq\n");
44 }
45
46 module_init(test_init);
47 module_exit(test_exit);
48
49 MODULE_LICENSE("GPL");
50 MODULE_AUTHOR("xoao bai");
51 MODULE_VERSION("v0.1");
接下來驗證一下:
[root: 1st]# insmod test.ko
hello irq
[root: 1st]# key down //按下按鍵顯示
key down
key down
key down
[root: 1st]# cat /proc/interrupts
CPU0
17: 11 s3c-ext0 key INT_EINT1 顯示我註冊和中斷名字
30: 423482 s3c S3C2410 Timer Tick
32: 0 s3c s3c2410-lcd
51: 2782 s3c-ext eth0
70: 49 s3c-uart0 s3c2440-uart
71: 69 s3c-uart0 s3c2440-uart
79: 0 s3c-adc s3c2410_action
80: 0 s3c-adc adc, s3c2410_action
83: 0 - s3c2410-wdt
Err: 0
[root: key INT_EINT1]# rmmod test //卸載
good bye irq
[root: key INT_EINT1]# cat /proc/interrupts //卸載後,我的中斷名字消失了
CPU0
30: 828977 s3c S3C2410 Timer Tick
32: 0 s3c s3c2410-lcd
51: 3202 s3c-ext eth0
70: 192 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: 0
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、proc/interrupt
接下來,稍稍介紹一下proc/interrupt
[root: 1st]# cat /proc/interrupts
CPU0
17: 11 s3c-ext0 key INT_EINT1 顯示我註冊和中斷名字
首先,第一列是中斷號,之前的程序應該有人會有疑問:
中斷號在哪裏找的?
在S3C2440中,這些中斷號定義在文件"include/mach/irqs.h"中,在這裏,可以找到對應的中斷:
25 /* main cpu interrupts */
26 #define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
27 #define IRQ_EINT1 S3C2410_IRQ(1)
28 #define IRQ_EINT2 S3C2410_IRQ(2)
29 #define IRQ_EINT3 S3C2410_IRQ(3)
30 #define IRQ_EINT4t7 S3C2410_IRQ(4) /* 20 */
在這裏我標了兩處紅筆:
第一處:可以看到,S3C2440所有的中斷號在原來的基值上加了16構成中斷號,但不同的芯片或許有不同的定義方法。
第二處:有些中斷號是共享的。在S3C2440中,EINT4---EINT7是共享寄存器SRCPND中的一位,所以,linux系統給這樣的中斷分配了一個共享的中斷號。那就是說,如果你使用IRQ_EINT4t7,當收到這些中斷時,都會調用對應的中斷處理函數。這就需要在中斷處理函數中通過第一個傳參irq來辨別中斷並執行相應的操作。如:
13 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數
14 {
15 switch(irqno){
16 。。。。}
17 }
那肯定有人會說,這太麻煩了吧,有沒有更好的辦法處理共享中斷號?
那當然是有,繼續看文件"include/mach/irqs.h":
61 /* interrupts generated from the external interrupts sources */
62 #define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
63 #define IRQ_EINT5 S3C2410_IRQ(33)
64 #define IRQ_EINT6 S3C2410_IRQ(34)
65 #define IRQ_EINT7 S3C2410_IRQ(35)
66 #define IRQ_EINT8 S3C2410_IRQ(36)
看到了吧?內核把共享的中斷分離出來,只要使用這些標記就可以了。
其實上面我只是想說明:無論在硬件上ARM是怎麼實現中斷的(是否共享),在內核看來所有的中斷都是一樣的,都可以獨自獲得一箇中斷號。
第二列“11”是對應處理器響應該中斷的次數。
第三列“s3c-ext0”是處理這個中斷的中斷控制器
第四列一看就知道調用irq_request()時定義的中斷名字。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
六、總結
其實要實現中斷,大部分的工作已經給內核包了,我們只需要做的就是告訴內核,當來了什麼中斷要執行怎麼樣的函數,這也是今天介紹的重點,其實步驟很簡單:
1)調用兩個函數:requesr_irq和free_irq。
2)實現中斷處理函數:irq_handler()。
還有沒講的知識:
1)還有幾個irqflag沒介紹。
2)沒有介紹dev_id。
可能有人會加載上面的模塊失敗,這也是我今天沒介紹的只是,共享中斷號。這裏說的共享和硬件的共享不一樣性質,下節會介紹。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx