- 了解中斷描述符struct irq_desc 參考韋東山百問網
1.通用中斷代碼處理
通用中斷處理示意圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyRQpkLwUTM4QDM0kDMwMTOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
對于每一個外設的IRQ 都用struct irq_desc來描述,稱之為中斷描述符。該資料結構儲存了關于所有IRQ的中斷描述符資訊(上圖中紅色框圖内)。
當中斷發生時,首先擷取觸發中斷的HW interupt ID,然後通過irq domain譯碼成IRQ number,然後通過IRQ number擷取對應的中斷描述符。調用中斷描述符中的highlevel irq-events handler來進行中斷處理。而highlevel irq-events handler主要進行下面兩個操作:
- 調用中斷描述符的底層irq chip driver進行mask,ack等callback函數,進行interrupt flow control。
- 調用該中斷描述符上的action list中的specific handler(用這個術語來區分具體中斷handler和high level的handler)。這個步驟不一定會執行,這是和中斷描述符的目前狀态相關,實際上,interrupt flow control是軟體(設定一些标志位,軟體根據标志位進行處理)和硬體(mask或者unmask interrupt controller等)一起控制完成的。
2.irq_desc 組織方式
irq_desc在核心中有兩種組織方式,這是根據宏CONFIG_SPARSE_IRQ是否定義來決定的(詳見linux IRQ Management(一)- 綜述),這兩種方式分别是:
- radix-tree方式,這是以基數樹的方式來組織irq_desc;
- 數組的方式 ,在系統初始化的時候會定義一個全局數組。
kernel/irq/irqdesc.c:
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都是有效的,有空洞,如果使用靜态定義的表格就會導緻了記憶體很大的浪費。在這種情況下,靜态表格不适合了,改用一個radix tree來儲存中斷描述符(HW interrupt作為索引)。這時候,每一個中斷描述符都是動态配置設定,然後插入到radix tree中。如果采用這種政策,那麼需要打開CONFIG_SPARSE_IRQ選項。
最終的目标是建立hwirq和virq的映射,兩種方式代表兩種不同的映射方式。irq_desc建立完後,要做的就是初始化,在核心中有兩方面會涉及到irq_desc的初始化,一個是在申請中斷的時候(主要是初始化irq_desc->action),另一個是在驅動初始化的時候。
2.1.中斷相關結構體
- struct irq_desc:一個中斷描述符,一個中斷所需要的資源都集中在這個結構體中描述,如果沒有定義選項CONFIG_SPARSE_IRQ,irq_desc會在系統初始化的時候被配置設定到一個數組中存放,其中數組下标代表的就是virq(虛拟中斷,而不是硬中斷),如果定義了選項那麼irq_desc會被配置設定到radix tree中;
- struct irq_data:一個具體中斷控制器,包括兩個結構體,分别是irq_chip和irq_domain,其中irq_chip中的每個字段是描述了具體操作中斷控制器的功能,irq_domain主要的功能是對hwirq和virq建立映射,把某些中斷所具有的同樣功能抽象出來,然後組成一個domain,這樣就可以被其他中斷控制器所用;
- struct irqaction:一個中斷要做什麼,此結構體組成一個連結清單,因為一個中斷線上可能會挂載好幾個裝置,這幾個裝置會共用這個中斷,具體是哪個裝置需要判斷。
2.2.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;-----中斷描述符的狀态
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是和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)
- (2)action指向一個struct irqaction的連結清單。如果一個interrupt request line允許共享,那麼該連結清單中的成員可以是多個,否則,該連結清單隻有一個節點。
- (3)core_internal_state__do_not_mess_with_it在具體使用的時候被被簡化成istate,表示internal state。
- (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相關。
2.2.1.struct irq_data
中斷描述符中應該會包括底層irq chip相關的資料結構,linux kernel中把這些資料組織在一起,形成struct irq_data:
struct irq_data {
u32 mask;----------TODO
unsigned int irq;--------IRQ number
unsigned long hwirq;-------HW interrupt ID
struct irq_common_data *common;
struct irq_chip *chip;----------該中斷描述符對應的irq chip資料結構
struct irq_domain *domain;--------該中斷描述符對應的irq domain資料結構
void *chip_data;---------和中斷控制器相關的私有資料
};
2.2.2.struct irq_chip:用于通路底層硬體,非常重要結構體。
449 struct irq_chip {
450 struct device *parent_device;
451 const char *name;
452 unsigned int (*irq_startup)(struct irq_data *data);
453 void (*irq_shutdown)(struct irq_data *data);
454 void (*irq_enable)(struct irq_data *data);
455 void (*irq_disable)(struct irq_data *data);
456
457 void (*irq_ack)(struct irq_data *data);
458 void (*irq_mask)(struct irq_data *data);
459 void (*irq_mask_ack)(struct irq_data *data);
460 void (*irq_unmask)(struct irq_data *data);
461 void (*irq_eoi)(struct irq_data *data);
462
463 int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
464 int (*irq_retrigger)(struct irq_data *data);
465 int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
466 int (*irq_set_wake)(struct irq_data *data, unsigned int on);
467
468 void (*irq_bus_lock)(struct irq_data *data);
469 void (*irq_bus_sync_unlock)(struct irq_data *data);
470
471 void (*irq_cpu_online)(struct irq_data *data);
472 void (*irq_cpu_offline)(struct irq_data *data);
};
解釋如下:
- irq_enable:在中斷注冊或使能時調用,一般在使能中斷前需要清除中斷狀态。
- irq_disable:在中斷登出或關閉時調用,一般在關閉中斷前不清除中斷狀态。
- irq_mask_ack:在進入中斷處理函數前調用,一般在屏蔽中斷前需要清除中斷源信号。
- irq_unmask:在退出中斷處理函數後調用,一般在去屏蔽中斷前不清除清除中斷源信号。
- irq_enable/irq_unmask"用于中斷使能,"irq_disable/irq_mask"用于中斷屏蔽。
2.3.irq_desc的配置設定
#define irq_alloc_descs(irq, from, cnt, node) \
__irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)
#define irq_alloc_desc(node) \
irq_alloc_descs(-1, 0, 1, node)
#define irq_alloc_desc_at(at, node) \
irq_alloc_descs(at, at, 1, node)
#define irq_alloc_desc_from(from, node) \
irq_alloc_descs(-1, from, 1, node)
#define irq_alloc_descs_from(from, cnt, node) \
irq_alloc_descs(-1, from, cnt, node)
最終都是調用到函數__irq_alloc_descs,此函數最終會調用到函數alloc_descs,由于irq_desc組織方式有兩種,是以alloc_descs也有兩個接口:
2.3.1.線性數組方式
static inline int alloc_descs(unsigned int start, unsigned int cnt, int node,
struct module *owner)
{
u32 i;
for (i = 0; i < cnt; i++) {
struct irq_desc *desc = irq_to_desc(start + i);
desc->owner = owner;
}
return start;
}
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL; //直接傳回irq_desc[irq]
}
2.3.2.基數樹方式
static int alloc_descs(unsigned int start, unsigned int cnt, int node,
struct module *owner)
{
struct irq_desc *desc;
int i;
for (i = 0; i < cnt; i++) {
desc = alloc_desc(start + i, node, owner); //配置設定一個irq_desc
if (!desc)
goto err;
mutex_lock(&sparse_irq_lock);
irq_insert_desc(start + i, desc); //把irq_desc插入到radix tree
mutex_unlock(&sparse_irq_lock);
}
return start;
err:
for (i--; i >= 0; i--)
free_desc(start + i);
mutex_lock(&sparse_irq_lock);
bitmap_clear(allocated_irqs, start, cnt);
mutex_unlock(&sparse_irq_lock);
return -ENOMEM;
}
static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
{
struct irq_desc *desc;
gfp_t gfp = GFP_KERNEL;
desc = kzalloc_node(sizeof(*desc), gfp, node); //為irq_desc配置設定記憶體
if (!desc)
return NULL;
/* allocate based on nr_cpu_ids */
desc->kstat_irqs = alloc_percpu(unsigned int);
if (!desc->kstat_irqs)
goto err_desc;
if (alloc_masks(desc, gfp, node))
goto err_kstat;
raw_spin_lock_init(&desc->lock);
lockdep_set_class(&desc->lock, &irq_desc_lock_class);
init_rcu_head(&desc->rcu);
desc_set_defaults(irq, desc, node, owner);
return desc;
err_kstat:
free_percpu(desc->kstat_irqs);
err_desc:
kfree(desc);
return NULL;
}
2.4.irq_desc的初始化
各個驅動中斷的配置在DT中,DT初始化中斷:
走到of_irq_to_resource就脫離了DT過程,函數調用流程圖如下:
- A : 找到比對的irq_domain,irq_domain會在中斷控制器注冊的時候加入;
- B : 判斷irq_domain是否是hierarchy,不同的類型需要不同的操作,從圖中可以看出雖然調用函數不一樣,但是具體要執行的步驟是一樣的:
- 搜尋hwirq是否已經被映射到virq,如果是直接傳回;
- 如果沒有得到virq,那麼把hwirq映射到virq;
- 調用irq_domain的相關函數初始化irq_desc.