一、中斷基礎概念
? ? ? ? 所謂中斷,指CPU在執行程序的過程中,出現了某些突發事件即待處理,CPU必須暫停當前的程序。轉去處理突發事件,處理完畢后CPU又返回原程序被中斷的位置并繼續執行。
1、中斷分類
a -- 內部中斷和外部中斷
? ? ? 根據中斷的的來源,中斷可以分為內部中斷和外部中斷:
內部中斷,其中斷源來自CPU內部(軟件中斷指令、溢出、除法錯誤等),例如,操作系統從用戶態切換到內核態需借助CPU內部的軟中斷;
外部中斷,其中斷源來自CPU外部,由外設提出請求;
b -- 可屏蔽中斷與不屏蔽中斷
? ? ? 根據中斷是否可以屏蔽分為可屏蔽中斷與不屏蔽中斷:
可屏蔽中斷,其可以通過屏蔽字被屏蔽,屏蔽后,該中斷不再得到響應;
不屏蔽中斷,其不能被屏蔽;
c -- 向量中斷和非向量中斷
? ? ?根據中斷入口跳轉方法的不同,分為向量中斷和非向量中斷:
向量中斷,采用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來后,就自動跳轉到與該中斷號對應的地址執行。不同的中斷號有不同的入口地址;
非向量中斷,其多個中斷共享一個入口地址,進入該入口地址后再通過軟件判斷中斷標志來標識具體是哪個中斷。
? ? 也就是說,向量中斷由硬件提供中斷服務程序入口地址,非向量中斷由軟件提供中斷服務入口地址。
2、中斷ID
a -- IRQ number
? ? ? cpu給中斷的一個編號,一個IRQ number是一個虛擬的interrupt ID,和硬件無關;
b -- HW interrupt ID
? ? ? ?對于中斷控制器而言,它收集了多個外設的irq request line,要向cpu傳遞,GIC要對外設進行編碼,GIC就用HW interrupt ID來標示外部中斷;
3、SMP情況下中斷兩種形態
1-Nmode :只有一個processor處理器
N-N :所有的processor都是獨立收到中斷的
? ? ? GIC:SPI使用1-Nmode ? PPI 和 sgi使用N-Nmode
二、中斷編程
1、 申請IRQ
? ? ? 在linux內核中用于申請中斷的函數是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)
相關參數:
a -- irq是要申請的硬件中斷號。另外,這里要思考的問題是,這個irq 是怎么得到的?這里我們在設備樹中獲取,具體解析見:
b -- handler是向系統注冊的中斷處理函數,是一個回調函數,中斷發生時,系統調用這個函數,dev_id參數將被傳遞給它。
c -- irqflags是中斷處理的屬性,若設置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版已經不支持了),則表示中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程序不屏蔽;若設置了IRQF_SHARED (老版本中的SA_SHIRQ),則表示多個設備共享中斷,若設置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示對系統熵有貢獻,對系統獲取隨機數有好處。(這幾個flag是可以通過或的方式同時使用的)
d -- devname設置中斷名稱,在cat /proc/interrupts中可以看到此名稱。
e -- dev_id在中斷共享時會用到,一般設置為這個設備的設備結構體或者NULL。
? ? ? request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數指針為NULL,返回-EBUSY表示中斷已經被占用且不能共享。
? ? ? 頂半部 handler 的類型 irq_handler_t 定義為:
typedef irqreturn_t (*irq_handler_t)(int, void *);
參數1:中斷號
參數2 :參數
在Interrupt.h (e:\linux-3.14-fs4412\include\linux) ? ?18323 ? ?2014/3/31中定義
IRQ_NONE ? ? ? 共享中斷,如果不是我的設備產生的中斷,就返回該值
IRQ_HANDLED ? ? 中斷處理函數正確執行了就返回該值
IRQ_WAKE_THREAD ? ? ? ?= (1 << 1)
2、釋放IRQ
? ? ? 與request_irq()相對應的函數為 free_irq(),free_irq()的原型為:
void free_irq(unsigned int irq, void *dev_id)
?free_irq()參數的定義與request_irq()相同。
三、中斷注冊過程分析
? ? ? ?我們每次用中斷的時候就是注冊一個中斷函數。request_irq首先生成一個irqaction結構,其次根據中斷號 找到irq_desc數組項(還記得吧,內核中irq_desc是一個數組,沒一項對應一個中斷號),然后將irqaction結構添加到 irq_desc中的action鏈表中。當然還做一些其他的工作,注冊完成后,中斷函數就可以發生并被處理了。
? ? ? irq_desc 內核中記錄一個irq_desc的數組,數組的每一項對應一個中斷或者一組中斷使用同一個中斷號,一句話irq_desc幾乎記錄所有中斷相關的東西,這個結構是中斷的核心。其中包括倆個重要的結構irq_chip 和irqaction 。
1、? irq_chip
? ? ? irq_chip ?里面基本上是一些回調函數,其中大多用于操作底層硬件,設置寄存器,其中包括設置GPIO為中斷輸入就是其中的一個回調函數,分析一些源代碼
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);? //開啟中斷
? ? ? ? void? ? ? ? ? (*eoi)(unsigned int irq);
? ? ? ? void? ? ? ? ? (*end)(unsigned int irq);
? ? ? ? int? ? ? ? ? ? (*set_affinity)(unsigned int irq,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? const struct cpumask *dest);
? ? ? ? int? ? ? ? ? ? (*retrigger)(unsigned int irq);
? ? ? ? int? ? ? ? ? ? (*set_type)(unsigned int irq, unsigned int flow_type); //設置中斷類型,其中包括設置GPIO口為中斷輸入
? ? ? ? int? ? ? ? ? ? (*set_wake)(unsigned int irq, unsigned int on);
? ? ? ? void? ? ? ? ? (*bus_lock)(unsigned int irq);? //上鎖函數
? ? ? ? void? ? ? ? ? (*bus_sync_unlock)(unsigned int irq); //解鎖
? ? ? ? /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
? ? ? ? void? ? ? ? ? (*release)(unsigned int irq, void *dev_id);
#endif
? ? ? ? /*
? ? ? ? * For compatibility, ->typename is copied into ->name.
? ? ? ? * Will disappear.
? ? ? ? */
? ? ? ? const char? ? *typename;
};
? ? ? ?我們可以看到這里實現的是一個框架,需要我們進一步的填充里面的函數。我們在分析另一個結構irqaction
2、irqaction
include/linux/interrupt.h
struct irqaction {
? ? ? ? irq_handler_t handler;? //用戶注冊的中斷處理函數
? ? ? ? unsigned long flags;? ? //中斷標識
? ? ? ? const char *name;? ? ? //用戶注冊的中斷名字,cat/proc/interrupts時可以看到
? ? ? ? void *dev_id;? ? ? ? ? //可以是用戶傳遞的參數或者用來區分共享中斷
? ? ? ? struct irqaction *next; //irqaction結構鏈,一個共享中斷可以有多個中斷處理函數
? ? ? ? int irq;? ? ? ? ? ? ? ? //中斷號
? ? ? ? struct proc_dir_entry *dir;
? ? ? ? irq_handler_t thread_fn;
? ? ? ? struct task_struct *thread;
? ? ? ? unsigned long thread_flags;
};
? ? ? 我們用irq_request函數注冊中斷時,主要做倆個事情,根據中斷號生成一個irqaction結構并添加到irq_desc中的 action結構鏈表,另一發面做一些初始化的工作,其中包括設置中斷觸發方式,設置一些irq_chip結構中沒有初始化的函數為默認,開啟中斷,設置 GPIO口為中斷輸入模式。
中斷發生之后處理流程
a -- 具體的CPU architecture相關模塊進行現場保護,然后調用machine driver執行對應的中斷處理handler;
b -- machine driver對應中斷處理handler會根據硬件的信息獲取HW interrupt ?id,然后通過irq domain模塊翻譯成irq number;
c -- 調用該IRQ number對應 hign level irq ?event handler,在這個hign level的handler中,會通過和中斷控制器交互,進行中斷處理的flow 控制(中斷的嵌套、搶占),最終會遍歷我們注冊的IRQ action list,調用對應處理函數;
d -- CPU architeccture相關模塊進行現場的恢復;
具體分析之前先看幾個基礎概念:
一、?中斷硬件框架
? ? ? ? 中斷的主動通知特性需要硬件設施支持。在數字邏輯電路層面,外部設備和處理器之間有一條專門的中斷信號線(Interrupt Line),用于連接外設與CPU的中斷引腳(Interrupt Pin)。當外部設備發生狀態改變時,可以通過這條信號線向處理器發出一個中斷請求(Interrupt Request,IRQ),其中外部設備通常被稱作中斷源(Interrupt Source)。
? ? ? ? 處理器一般只有兩根左右的中斷引腳(Cortex A9 中的 IRQ、FIQ),而管理的外設卻很多。為了解決這個問題,現代設備的中斷信號線并不是與處理器直接相連,而是與一個稱為中斷控制器的設備相連接,后者才跟處理器的中斷引腳連接。中斷控制器一般可以通過處理器進行編程配置,在Cortex A9 中稱為通用中斷控制器GIC(Gerneric Interrupt Controller)。下圖是一個典型的中斷硬件連接的系統框架圖:
? ? ? 這里的中斷控制器是可編程中斷控制器PIC(Programmable Interrupt Controller)。PIC的輸出中斷信號線連接到處理器的INT引腳上,這是處理器專門用來接收中斷信號的pin腳。外部設備的中斷線連接到PIC的pin引腳上,這是PIC用來接收外設中斷的pin腳。比如第一個設備的中斷線通過P0連到PIC上。在實際的硬件平臺上,PIC有的在CPU外部,比如x86平臺的8259中斷控制器;有的被封裝到CPU的內部,這廣泛見于嵌入式領域。一顆SoC芯片內部集成了處理器和各種外部設備的控制器,其中包括PIC。
? ? ?IRQ相關信息管理的關鍵點是一個全局數組,每個數組項對應一個IRQ編號,軟件中斷號irq就是這個數組的索引,irq將一對一或多對一(共享)映射到硬件中斷源編號。不同的操作系統相關數據結構的實現和映射策略實現可能有差別。
二、 中斷向量表
? ? ? ? ?中斷向量表其實是處理器內部的概念,因為處理器除了會被外部設備中斷外,其內部也可能產生異常等事件,例如在MIPS中,中斷只是異常的一種。當這些事件發生時,CPU必須暫停手頭上的工作,轉而去處理中斷或異常,因此處理器需要知道到哪里去獲得這些中斷或異常的處理函數的目標地址。中斷向量表就是用來解決這個問題,其中每一項都是一個中斷或異常處理函數的入口地址,具體來說4個字節的函數指針將指向一段匯編微碼(intConnectCode)執行跳轉。
? ? ?外部設備的中斷常常對應向量表中的某一項,這是通用框架的外部中斷處理函數入口,因此在進入通用的中斷處理函數之后,系統必須知道正在處理的中斷是哪一個設備產生的,而這正是由軟件中斷號irq定的決。中斷向量表的內容是由操作系統在初始化階段來填寫,對于外部中斷,操作系統負責實現一個通用的外部中斷處理函數,然后把這個函數的入口地址放到中斷向量表中的對應位置。用戶注冊設備驅動ISR,實際上就是掛接到中斷向量表中,覆蓋某一項的默認處理實現特化。
中斷向量表在linux\arch\arm\kernel\entry-armv.S
三、中斷流程分析
1、當中斷發生時會跳轉到中斷向量表處,就是上面那張表;
2、匯編處理部分不做分析:
table16個入口,只有2項有效,對應user mode、svc mode
Q:
1、handle_arch_irq 從哪來?
gic_init_bases ?—> set_handle_irq(gic_handle_irq)—>??handle_arch_irq = handle_irq;
handle_arch_irq 實際執行的是gic_handle_irq
gic_handle_irq?—>?handle_IRQ?—>?generic_handle_irq()—>?generic_handle_irq_desc—>desc?—>handle_irq(irq,?desc);
2、desc->handle_irq從哪來?
xxx_irq_type —>irq_set_handler(data->irq,?handle_level_irq); —>?__irq_set_handler —>
__irq_set_handler-----?desc->handle_irq?=?handle;
handle_level_irq()
handle_irq_event —>?handle_irq_event_percpu —>res?=?action->handler(irq,?action->dev_id);真正調用到我們注冊的中斷處理函數
參考鏈接:https://blog.csdn.net/zqixiao_09/article/details/50908780
蝸窩科技有幾篇文章寫中斷寫得挺不錯的 鏈接如下
1、linux kernel的中斷子系統之(一)一個大概的軟硬件框架。
2、linux kernel的中斷子系統之(二):irq domain介紹。主要描述如何將一個HW interrupt ID轉換成IRQ number。
3、linux kernel的中斷子系統之(三):IRQ number和中斷描述符。主要描述中斷描述符相關的數據結構和接口API。
4、linux kernel的中斷子系統之(四):high level irq event handler。
6、linux kernel的中斷子系統之(六):ARM中斷處理過程,這份文檔以ARM CPU為例,描述ARM相關的中斷處理過程
7、linux kernel的中斷子系統之(七):GIC代碼分析,這份文檔是以一個具體的interrupt controller為例,描述irq chip driver的代碼構成情況。