1. 程式人生 > >Linux中斷 - IRQ number和中斷描述符

Linux中斷 - IRQ number和中斷描述符

else pla 中斷控制 詳細 打開 fig 好的 應該 啟動

一、前言

本文主要圍繞IRQ number和中斷描述符(interrupt descriptor)這兩個概念描述通用中斷處理過程。第二章主要描述基本概念,包括什麽是IRQ number,什麽是中斷描述符等。第三章描述中斷描述符數據結構的各個成員。第四章描述了初始化中斷描述符相關的接口API。第五章描述中斷描述符相關的接口API。

二、基本概念

1、通用中斷的代碼處理示意圖

一個關於通用中斷處理的示意圖如下:

技術分享圖片

在linux kernel中,對於每一個外設的IRQ都用struct irq_desc來描述,我們稱之中斷描述符(struct irq_desc)。linux kernel中會有一個數據結構保存了關於所有IRQ的中斷描述符信息,我們稱之中斷描述符DB(上圖中紅色框圖內)。當發生中斷後,首先獲取觸發中斷的HW interupt ID,然後通過irq domain翻譯成IRQ nuber,然後通過IRQ number就可以獲取對應的中斷描述符。調用中斷描述符中的highlevel irq-events handler來進行中斷處理就OK了。而highlevel irq-events handler主要進行下面兩個操作:

(1)調用中斷描述符的底層irq chip driver進行mask,ack等callback函數,進行interrupt flow control。

(2)調用該中斷描述符上的action list中的specific handler(我們用這個術語來區分具體中斷handler和high level的handler)。這個步驟不一定會執行,這是和中斷描述符的當前狀態相關,實際上,interrupt flow control是軟件(設定一些標誌位,軟件根據標誌位進行處理)和硬件(mask或者unmask interrupt controller等)一起控制完成的。

2、中斷的打開和關閉

我們再來看看整個通用中斷處理過程中的開關中斷情況,開關中斷有兩種:

(1)開關local CPU的中斷。對於UP,關閉CPU中斷就關閉了一切,永遠不會被搶占。對於SMP,實際上,沒有關全局中斷這一說,只能關閉local CPU(代碼運行的那個CPU)

(2)控制interrupt controller,關閉某個IRQ number對應的中斷。更準確的術語是mask或者unmask一個 IRQ。

本節主要描述的是第一種,也就是控制CPU的中斷。當進入high level handler的時候,CPU的中斷是關閉的(硬件在進入IRQ processor mode的時候設定的)。

對於外設的specific handler,舊的內核(2.6.35版本之前)認為有兩種:slow handler和fast handle。在request irq的時候,對於fast handler,需要傳遞IRQF_DISABLED的參數,確保其中斷處理過程中是關閉CPU的中斷,因為是fast handler,執行很快,即便是關閉CPU中斷不會影響系統的性能。但是,並不是每一種外設中斷的handler都是那麽快(例如磁盤),因此就有slow handler的概念,說明其在中斷處理過程中會耗時比較長。對於這種情況,如果在整個specific handler中關閉CPU中斷,對系統的performance會有影響。因此,對於slow handler,在從high level handler轉入specific handler中間會根據IRQF_DISABLED這個flag來決定是否打開中斷,具體代碼如下(來自2.6.23內核):

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
……

if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();

……
}

如果沒有設定IRQF_DISABLED(slow handler),則打開本CPU的中斷。然而,隨著軟硬件技術的發展:

(1)硬件方面,CPU越來越快,原來slow handler也可以很快執行完畢

(2)軟件方面,linux kernel提供了更多更好的bottom half的機制

因此,在新的內核中,比如3.14,IRQF_DISABLED被廢棄了。我們可以思考一下,為何要有slow handler?每一個handler不都是應該迅速執行完畢,返回中斷現場嗎?此外,任意中斷可以打斷slow handler執行,從而導致中斷嵌套加深,對內核棧也是考驗。因此,新的內核中在interrupt specific handler中是全程關閉CPU中斷的。


3、IRQ number

從CPU的角度看,無論外部的Interrupt controller的結構是多麽復雜,I do not care,我只關心發生了一個指定外設的中斷,需要調用相應的外設中斷的handler就OK了。更準確的說是通用中斷處理模塊不關心外部interrupt controller的組織細節(電源管理模塊當然要關註具體的設備(interrupt controller也是設備)的拓撲結構)。一言以蔽之,通用中斷處理模塊可以用一個線性的table來管理一個個的外部中斷,這個表的每個元素就是一個irq描述符,在kernel中定義如下:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};

系統中每一個連接外設的中斷線(irq request line)用一個中斷描述符來描述,每一個外設的interrupt request line分配一個中斷號(irq number),系統中有多少個中斷線(或者叫做中斷源)就有多少個中斷描述符(struct irq_desc)。NR_IRQS定義了該硬件平臺IRQ的最大數目。

總之,一個靜態定義的表格,irq number作為index,每個描述符都是緊密的排在一起,一切看起來很美好,但是現實很殘酷的。有些系統可能會定義一個很大的NR_IRQS,但是只是想用其中的若幹個,換句話說,這個靜態定義的表格不是每個entry都是有效的,有空洞,如果使用靜態定義的表格就會導致了內存很大的浪費。為什麽會有這種需求?我猜是和各個interrupt controller硬件的interrupt ID映射到irq number的算法有關。在這種情況下,靜態表格不適合了,我們改用一個radix tree來保存中斷描述符(HW interrupt作為索引)。這時候,每一個中斷描述符都是動態分配,然後插入到radix tree中。如果你的系統采用這種策略,那麽需要打開CONFIG_SPARSE_IRQ選項。上面的示意圖描述的是靜態表格的中斷描述符DB,如果打開CONFIG_SPARSE_IRQ選項,系統使用Radix tree來保存中斷描述符DB,不過概念和靜態表格是類似的。

此外,需要註意的是,在舊內核中,IRQ number和硬件的連接有一定的關系,但是,在引入irq domain後,IRQ number已經變成一個單純的number,和硬件沒有任何關系。

三、中斷描述符數據結構

1、底層irq chip相關的數據結構

中斷描述符中應該會包括底層irq chip相關的數據結構,linux kernel中把這些數據組織在一起,形成struct irq_data,具體代碼如下:

struct irq_data {
u32 mask;----------TODO
unsigned int irq;--------IRQ number
unsigned long hwirq;-------HW interrupt ID
unsigned int node;-------NUMA node index
unsigned int state_use_accessors;--------底層狀態,參考IRQD_xxxx
struct irq_chip *chip;----------該中斷描述符對應的irq chip數據結構
struct irq_domain *domain;--------該中斷描述符對應的irq domain數據結構
void *handler_data;--------和外設specific handler相關的私有數據
void *chip_data;---------和中斷控制器相關的私有數據
struct msi_desc *msi_desc;
cpumask_var_t affinity;-------和irq affinity相關
};

中斷有兩種形態,一種就是直接通過signal相連,用電平或者邊緣觸發。另外一種是基於消息的,被稱為MSI (Message Signaled Interrupts)。msi_desc是MSI類型的中斷相關,這裏不再描述。

node成員用來保存中斷描述符的內存位於哪一個memory node上。 對於支持NUMA(Non Uniform Memory Access Architecture)的系統,其內存空間並不是均一的,而是被劃分成不同的node,對於不同的memory node,CPU其訪問速度是不一樣的。如果一個IRQ大部分(或者固定)由某一個CPU處理,那麽在動態分配中斷描述符的時候,應該考慮將內存分配在該CPU訪問速度比較快的memory node上。

2、irq chip數據結構

Interrupt controller描述符(struct irq_chip)包括了若幹和具體Interrupt controller相關的callback函數,我們總結如下:

成員名字 描述
name 該中斷控制器的名字,用於/proc/interrupts中的顯示
irq_startup start up 指定的irq domain上的HW interrupt ID。如果不設定的話,default會被設定為enable函數
irq_shutdown shutdown 指定的irq domain上的HW interrupt ID。如果不設定的話,default會被設定為disable函數
irq_enable enable指定的irq domain上的HW interrupt ID。如果不設定的話,default會被設定為unmask函數
irq_disable disable指定的irq domain上的HW interrupt ID。
irq_ack 和具體的硬件相關,有些中斷控制器必須在Ack之後(清除pending的狀態)才能接受到新的中斷。
irq_mask mask指定的irq domain上的HW interrupt ID
irq_mask_ack mask並ack指定的irq domain上的HW interrupt ID。
irq_unmask mask指定的irq domain上的HW interrupt ID
irq_eoi 有些interrupt controler(例如GIC)提供了這樣的寄存器接口,讓CPU可以通知interrupt controller,它已經處理完一個中斷
irq_set_affinity 在SMP的情況下,可以通過該callback函數設定CPU affinity
irq_retrigger 重新觸發一次中斷,一般用在中斷丟失的場景下。如果硬件不支持retrigger,可以使用軟件的方法。
irq_set_type 設定指定的irq domain上的HW interrupt ID的觸發方式,電平觸發還是邊緣觸發
irq_set_wake 和電源管理相關,用來enable/disable指定的interrupt source作為喚醒的條件。
irq_bus_lock 有些interrupt controller是連接到慢速總線上(例如一個i2c接口的IO expander芯片),在訪問這些芯片的時候需要lock住那個慢速bus(只能有一個client在使用I2C bus)
irq_bus_sync_unlock unlock慢速總線
irq_suspend
irq_resume
irq_pm_shutdown
電源管理相關的callback函數
irq_calc_mask TODO
irq_print_chip /proc/interrupts中的信息顯示

3、中斷描述符

在linux kernel中,使用struct irq_desc來描述一個外設的中斷,我們稱之中斷描述符,具體代碼如下:

struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;------IRQ的統計信息
irq_flow_handler_t handle_irq;--------(1)
struct irqaction *action; -----------(2)
unsigned int status_use_accessors;-----中斷描述符的狀態,參考IRQ_xxxx
unsigned int core_internal_state__do_not_mess_with_it;----(3)
unsigned int depth;----------(4)
unsigned int wake_depth;--------(5)
unsigned int irq_count; ---------(6)
unsigned long last_unhandled;
unsigned int irqs_unhandled;
raw_spinlock_t lock;-----------(7)
struct cpumask *percpu_enabled;-------(8)
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;----和irq affinity相關,後續單獨文檔描述
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot; -----(9)
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;--------該IRQ對應的proc接口
#endif
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp

(1)handle_irq就是highlevel irq-events handler,何謂high level?站在高處自然看不到細節。我認為high level是和specific相對,specific handler處理具體的事務,例如處理一個按鍵中斷、處理一個磁盤中斷。而high level則是對處理各種中斷交互過程的一個抽象,根據下列硬件的不同:

(a)中斷控制器

(b)IRQ trigger type

highlevel irq-events handler可以分成:

(a)處理電平觸發類型的中斷handler(handle_level_irq)

(b)處理邊緣觸發類型的中斷handler(handle_edge_irq)

(c)處理簡單類型的中斷handler(handle_simple_irq)

(d)處理EOI類型的中斷handler(handle_fasteoi_irq)

會另外有一份文檔對high level handler進行更詳細的描述。

(2)action指向一個struct irqaction的鏈表。如果一個interrupt request line允許共享,那麽該鏈表中的成員可以是多個,否則,該鏈表只有一個節點。

(3)這個有著很長名字的符號core_internal_state__do_not_mess_with_it在具體使用的時候被被簡化成istate,表示internal state。就像這個名字定義的那樣,我們最好不要直接修改它。

#define istate core_internal_state__do_not_mess_with_it

(4)我們可以通過enable和disable一個指定的IRQ來控制內核的並發,從而保護臨界區的數據。對一個IRQ進行enable和disable的操作可以嵌套(當然一定要成對使用),depth是描述嵌套深度的信息。

(5)wake_depth是和電源管理中的wake up source相關。通過irq_set_irq_wake接口可以enable或者disable一個IRQ中斷是否可以把系統從suspend狀態喚醒。同樣的,對一個IRQ進行wakeup source的enable和disable的操作可以嵌套(當然一定要成對使用),wake_depth是描述嵌套深度的信息。

(6)irq_count、last_unhandled和irqs_unhandled用於處理broken IRQ 的處理。所謂broken IRQ就是由於種種原因(例如錯誤firmware),IRQ handler沒有定向到指定的IRQ上,當一個IRQ沒有被處理的時候,kernel可以為這個沒有被處理的handler啟動scan過程,讓系統中所有的handler來認領該IRQ。

(7)保護該中斷描述符的spin lock。

(8)一個中斷描述符可能會有兩種情況,一種是該IRQ是global,一旦disable了該irq,那麽對於所有的CPU而言都是disable的。還有一種情況,就是該IRQ是per CPU的,也就是說,在某個CPU上disable了該irq只是disable了本CPU的IRQ而已,其他的CPU仍然是enable的。percpu_enabled是一個描述該IRQ在各個CPU上是否enable成員。

(9)threads_oneshot、threads_active和wait_for_threads是和IRQ thread相關,後續文檔會專門描述。

四、初始化相關的中斷描述符的接口

1、靜態定義的中斷描述符初始化

int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;

init_irq_default_affinity();

desc = irq_desc;
count = ARRAY_SIZE(irq_desc);

for (i = 0; i < count; i++) {---遍歷整個lookup table,對每一個entry進行初始化
desc[i].kstat_irqs = alloc_percpu(unsigned int);---分配per cpu的irq統計信息需要的內存
alloc_masks(&desc[i], GFP_KERNEL, node);---分配中斷描述符中需要的cpu mask內存
raw_spin_lock_init(&desc[i].lock);---初始化spin lock
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
desc_set_defaults(i, &desc[i], node, NULL);---設定default值
}
return arch_early_irq_init();---調用arch相關的初始化函數
}

2、使用Radix tree的中斷描述符初始化

int __init early_irq_init(void)
{……

initcnt = arch_probe_nr_irqs();---體系結構相關的代碼來決定預先分配的中斷描述符的個數

if (initcnt > nr_irqs)---initcnt是需要在初始化的時候預分配的IRQ的個數
nr_irqs = initcnt; ---nr_irqs是當前系統中IRQ number的最大值

for (i = 0; i < initcnt; i++) {--------預先分配initcnt個中斷描述符
desc = alloc_desc(i, node, NULL);---分配中斷描述符
set_bit(i, allocated_irqs);---設定已經alloc的flag
irq_insert_desc(i, desc);-----插入radix tree
}
……
}

即便是配置了CONFIG_SPARSE_IRQ選項,在中斷描述符初始化的時候,也有機會預先分配一定數量的IRQ。這個數量由arch_probe_nr_irqs決定,對於ARM而言,其arch_probe_nr_irqs定義如下:

int __init arch_probe_nr_irqs(void)
{
nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
return nr_irqs;
}

3、分配和釋放中斷描述符

對於使用Radix tree來保存中斷描述符DB的linux kernel,其中斷描述符是動態分配的,可以使用irq_alloc_descs和irq_free_descs來分配和釋放中斷描述符。alloc_desc函數也會對中斷描述符進行初始化,初始化的內容和靜態定義的中斷描述符初始化過程是一樣的。最大可以分配的ID是IRQ_BITMAP_BITS,定義如下:

#ifdef CONFIG_SPARSE_IRQ
# define IRQ_BITMAP_BITS (NR_IRQS + 8196)---對於Radix tree,除了預分配的,還可以動態
#else 分配8196個中斷描述符
# define IRQ_BITMAP_BITS NR_IRQS---對於靜態定義的,IRQ最大值就是NR_IRQS
#endif

五、和中斷控制器相關的中斷描述符的接口

這部分的接口主要有兩類,irq_desc_get_xxx和irq_set_xxx,由於get接口API非常簡單,這裏不再描述,主要描述set類別的接口API。此外,還有一些locked版本的set接口API,定義為__irq_set_xxx,這些API的調用者應該已經持有保護irq desc的spinlock,因此,這些locked版本的接口沒有中斷描述符的spin lock進行操作。這些接口有自己特定的使用場合,這裏也不詳細描述了。

1、接口調用時機

kernel提供了若幹的接口API可以讓內核其他模塊可以操作指定IRQ number的描述符結構。中斷描述符中有很多的成員是和底層的中斷控制器相關,例如:

(1)該中斷描述符對應的irq chip

(2)該中斷描述符對應的irq trigger type

(3)high level handler

在過去,系統中各個IRQ number是固定分配的,各個IRQ對應的中斷控制器、觸發類型等也都是清楚的,因此,一般都是在machine driver初始化的時候一次性的進行設定。machine driver的初始化過程會包括中斷系統的初始化,在machine driver的中斷初始化函數中,會調用本文定義的這些接口對各個IRQ number對應的中斷描述符進行irq chip、觸發類型的設定。

在引入了device tree、動態分配IRQ number以及irq domain這些概念之後,這些接口的調用時機移到各個中斷控制器的初始化以及各個具體硬件驅動初始化過程中,具體如下:

(1)各個中斷控制器定義好自己的struct irq_domain_ops callback函數,主要是map和translate函數。

(2)在各個具體的硬件驅動初始化過程中,通過device tree系統可以知道自己的中斷信息(連接到哪一個interrupt controler、使用該interrupt controller的那個HW interrupt ID,trigger type為何),調用對應的irq domain的translate進行翻譯、解析。之後可以動態申請一個IRQ number並和該硬件外設的HW interrupt ID進行映射,調用irq domain對應的map函數。在map函數中,可以調用本節定義的接口進行中斷描述符底層interrupt controller相關信息的設定。

2、irq_set_chip

這個接口函數用來設定中斷描述符中desc->irq_data.chip成員,具體代碼如下:

int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0); ----(1)

desc->irq_data.chip = chip;
irq_put_desc_unlock(desc, flags);---------------(2)

irq_reserve_irq(irq);----------------------(3)
return 0;
}

(1)獲取irq number對應的中斷描述符。這裏用關閉中斷+spin lock來保護中斷描述符,flag中就是保存的關閉中斷之前的狀態flag,後面在(2)中會恢復中斷flag。

(3)前面我們說過,irq number有靜態表格定義的,也有radix tree類型的。對於靜態定義的中斷描述符,沒有alloc的概念。但是,對於radix tree類型,需要首先irq_alloc_desc或者irq_alloc_descs來分配一個或者一組IRQ number,在這些alloc函數值,就會set那些那些已經分配的IRQ。對於靜態表格而言,其IRQ沒有分配,因此,這裏通過irq_reserve_irq函數標識該IRQ已經分配,雖然對於CONFIG_SPARSE_IRQ(使用radix tree)的配置而言,這個操作重復了(在alloc的時候已經設定了)。

3、irq_set_irq_type

這個函數是用來設定該irq number的trigger type的。

int irq_set_irq_type(unsigned int irq, unsigned int type)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
int ret = 0;

type &= IRQ_TYPE_SENSE_MASK;
ret = __irq_set_trigger(desc, irq, type);
irq_put_desc_busunlock(desc, flags);
return ret;
}

來到這個接口函數,第一個問題就是:為何irq_set_chip接口函數使用irq_get_desc_lock來獲取中斷描述符,而irq_set_irq_type這個函數卻需要irq_get_desc_buslock呢?其實也很簡單,irq_set_chip不需要訪問底層的irq chip(也就是interrupt controller),但是irq_set_irq_type需要。設定一個IRQ的trigger type最終要調用desc->irq_data.chip->irq_set_type函數對底層的interrupt controller進行設定。這時候,問題來了,對於嵌入SOC內部的interrupt controller,當然沒有問題,因為訪問這些中斷控制器的寄存器memory map到了CPU的地址空間,訪問非常的快,因此,關閉中斷+spin lock來保護中斷描述符當然沒有問題,但是,如果該interrupt controller是一個I2C接口的IO expander芯片(這類芯片是擴展的IO,也可以提供中斷功能),這時,讓其他CPU進行spin操作太浪費CPU時間了(bus操作太慢了,會spin很久的)。腫麽辦?當然只能是用其他方法lock住bus了(例如mutex,具體實現是和irq chip中的irq_bus_lock實現相關)。一旦lock住了slow bus,然後就可以關閉中斷了(中斷狀態保存在flag中)。

解決了bus lock的疑問後,還有一個看起來奇奇怪怪的宏:IRQ_GET_DESC_CHECK_GLOBAL。為何在irq_set_chip函數中不設定檢查(check的參數是0),而在irq_set_irq_type接口函數中要設定global的check,到底是什麽意思呢?既然要檢查,那麽檢查什麽呢?和“global”對應的不是local而是“per CPU”,內核中的宏定義是:IRQ_GET_DESC_CHECK_PERCPU。SMP情況下,從系統角度看,中斷有兩種形態(或者叫mode):

(1)1-N mode。只有1個processor處理中斷

(2)N-N mode。所有的processor都是獨立的收到中斷,如果有N個processor收到中斷,那麽就有N個處理器來處理該中斷。

聽起來有些抽象,我們還是用GIC作為例子來具體描述。在GIC中,SPI使用1-N模型,而PPI和SGI使用N-N模型。對於SPI,由於采用了1-N模型,系統(硬件加上軟件)必須保證一個中斷被一個CPU處理。對於GIC,一個SPI的中斷可以trigger多個CPU的interrupt line(如果Distributor中的Interrupt Processor Targets Registers有多個bit被設定),但是,該interrupt source和CPU的接口寄存器(例如ack register)只有一套,也就是說,這些寄存器接口是全局的,是global的,一旦一個CPU ack(讀Interrupt Acknowledge Register,獲取interrupt ID)了該中斷,那麽其他的CPU看到的該interupt source的狀態也是已經ack的狀態。在這種情況下,如果第二個CPU ack該中斷的時候,將獲取一個spurious interrupt ID。

對於PPI或者SGI,使用N-N mode,其interrupt source的寄存器是per CPU的,也就是每個CPU都有自己的、針對該interrupt source的寄存器接口(這些寄存器叫做banked register)。一個CPU 清除了該interrupt source的pending狀態,其他的CPU感知不到這個變化,它們仍然認為該中斷是pending的。

對於irq_set_irq_type這個接口函數,它是for 1-N mode的interrupt source使用的。如果底層設定該interrupt是per CPU的,那麽irq_set_irq_type要返回錯誤。

4、irq_set_chip_data

每個irq chip總有自己私有的數據,我們稱之chip data。具體設定chip data的代碼如下:

int irq_set_chip_data(unsigned int irq, void *data)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);
desc->irq_data.chip_data = data;
irq_put_desc_unlock(desc, flags);
return 0;
}

多麽清晰、多麽明了,需要文字繼續描述嗎?

5、設定high level handler

這是中斷處理的核心內容,__irq_set_handler就是設定high level handler的接口函數,不過一般不會直接調用,而是通過irq_set_chip_and_handler_name或者irq_set_chip_and_handler來進行設定。具體代碼如下:

void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

……
desc->handle_irq = handle;
desc->name = name;

if (handle != handle_bad_irq && is_chained) {
irq_settings_set_noprobe(desc);
irq_settings_set_norequest(desc);
irq_settings_set_nothread(desc);
irq_startup(desc, true);
}
out:
irq_put_desc_busunlock(desc, flags);
}

理解這個函數的關鍵是在is_chained這個參數。這個參數是用在interrupt級聯的情況下。例如中斷控制器B級聯到中斷控制器A的第x個interrupt source上。那麽對於A上的x這個interrupt而言,在設定其IRQ handler參數的時候要設定is_chained參數等於1,由於這個interrupt source用於級聯,因此不能probe、不能被request(已經被中斷控制器B使用了),不能被threaded(具體中斷線程化的概念在其他文檔中描述)

Linux中斷 - IRQ number和中斷描述符