Linux4.x之Gpio分析(六)通用中断控制器的软件抽象

Linux通用中断子系统

前面我们介绍过引入 irq_domain 的概念目的是屏蔽中断控制器的细节(IRQ号与硬件连线的关系),但我们没有分析过中断控制器的细节是咋样的。
前面我们提到过:

  • ARM的中断控制器GIC,GPIO控制器也是一个中断控制器,还有一面之缘的GPC控制器。
  • 这么多不同的中断控制器,我们需要将一些特性抽象出来,才能统一的操作这些中断控制器。
    所以Linux引入了通用中断子系统(Generic irq).

这里通用中断子系统主要分成了三个层次:

  • 驱动程序
  • 中断逻辑层
  • 硬件封装层

enter description here

硬件封装层

它包含了体系架构相关的所有代码,包括中断控制器的抽象封装,arch相关的中断初始化,以及各个IRQ的相关数据结构的初始化工作,cpu的中断入口也会在arch相关的代码中实现。中断通用逻辑层通过标准的封装接口(实际上就是struct irq_chip定义的接口)访问并控制中断控制器的行为,
体系相关的中断入口函数在获取IRQ编号后,通过中断通用逻辑层提供的标准函数,把中断调用传递到中断流控层中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct irq_chip {  
const char *name;
unsigned int (*irq_startup)(struct irq_data *data); //第一次开启一个irq时使用。
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data); // 使能该irq
void (*irq_disable)(struct irq_data *data); //禁用改irq


void (*irq_mask)(struct irq_data *data); // 屏蔽该irq
void (*irq_unmask)(struct irq_data *data); // 取消屏蔽该irq
void (*irq_ack)(struct irq_data *data); //用于CPU对该irq的回应,通常表示cpu希望要清除该irq的pending状态,准备接受下一个irq请求。
void (*irq_mask_ack)(struct irq_data *data); //相当于irq_mask + irq_ack

void (*irq_eoi)(struct irq_data *data); //有些中断控制器需要在cpu处理完该irq后发出eoi信号,该回调就是用于这个目的

int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force); // 用于设置该irq和cpu之间的亲缘关系,就是通知中断控制器,该irq发生时,那些cpu有权响应该irq。当然,中断控制器会在软件的配合下,最终只会让一个cpu处理本次请求。
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); //设置irq的电气触发条件,例如IRQ_TYPE_LEVEL_HIGH或IRQ_TYPE_EDGE_RISING。
int (*irq_set_wake)(struct irq_data *data, unsigned int on); //通知电源管理子系统,该irq是否可以用作系统的唤醒源。
......
};

这里我们看得出 irq_chip 它实际上就是对中断控制器的接口抽象,我们实现每个硬件中断控制器我们只需实现irq_chip中的部分接口即可。

中断通用逻辑

enter description here

在之前我们已经介绍过这张图了,现在我们再详细的介绍解读一下。

首先我们需要先组织好irq_desc表格,我们称它是 中断描述符表。[历史原因]
实际实现有两种:

  1. 静态表格
  2. radix树
    我们知道数组的缺点需要连续,这样回造成浪费。所以我们引入了radix树来存储。

当发生中断后,首先获取触发中断的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等)一起控制完成的。

接下来我们看看相关的数据结构来再次加深对这段话的理解。

首先先看 中断描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct irq_desc {  
struct irq_data irq_data; //与中断相关的所有数据
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq; //highlevel irq-events handle
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; //specific handler:中断响应链表,当一个irq被触发时,内核会遍历该链表,主要为了实现中断的共享
unsigned int status_use_accessors;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */

raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
wait_queue_head_t wait_for_threads;

const char *name;
} ____cacheline_internodealigned_in_smp;

接着看看irq_data这个结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct irq_data {  
unsigned int irq; //中断号
unsigned long hwirq; //硬件中断号
unsigned int node; //通常用于hwirq和irq之间的映射操作。
unsigned int state_use_accessors;
struct irq_chip *chip; //中断控制器
struct irq_domain *domain; //中断域
void *handler_data; //每个irq的私有数据指针,该字段由硬件封转层使用,例如用作底层硬件的多路复用中断。
void *chip_data; //中断控制器的私有数据,该字段由硬件封转层使用
struct msi_desc *msi_desc; //于PCIe总线的MSI或MSI-X中断机制
#ifdef CONFIG_SMP
cpumask_var_t affinity; //记录该irq与cpu之间的亲缘关系
#endif
};

这里我们看到大量和中断的结构:
其中最重要的两个结构:

  1. struct irq_chip
  2. struct irq_domain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
void *host_data;
unsigned int flags;

/* Optional data */
struct device_node *of_node;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif

/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
unsigned int linear_revmap[];
};

struct irq_domain_chip_generic {
unsigned int irqs_per_chip;
unsigned int num_chips;
unsigned int irq_flags_to_clear;
unsigned int irq_flags_to_set;
enum irq_gc_flags gc_flags;
struct irq_chip_generic *gc[0];
};

struct irq_chip_generic {
raw_spinlock_t lock;
void __iomem *reg_base;
u32 (*reg_readl)(void __iomem *addr);
void (*reg_writel)(u32 val, void __iomem *addr);
unsigned int irq_base; //*
unsigned int irq_cnt;
u32 mask_cache;
u32 type_cache;
u32 polarity_cache;
u32 wake_enabled;
u32 wake_active;
unsigned int num_ct;
void *private;
unsigned long installed;
unsigned long unused;
struct irq_domain *domain;
struct list_head list;
struct irq_chip_type chip_types[0];
};


struct irq_chip_type {
struct irq_chip chip;
struct irq_chip_regs regs;
irq_flow_handler_t handler;
u32 type;
u32 mask_cache_priv;
u32 *mask_cache;
};

接着我们把所有irq_chip 和irq_domin有关的数据结构都列了出来,看起来逻辑关系还比较复杂。
我们只能大致的看到irq_domin存放了当前irq_chip的类型(通常是一种)和一些状态信息。
我们暂且不要管太多现在知道了几个数据结构就可以,后面在代码中在去理清这些联系。

IRQ中断流控

因为各种中断请求的电气特性会有所不同,又或者中断控制器的特性也不同,
这会导致以下这些处理也会有所不同:

  1. 何时对中断控制器发出ack回应;
  2. mask_irq和unmask_irq的处理;
  3. 中断控制器是否需要eoi回应?
  4. 何时打开cpu的本地irq中断?以便允许irq的嵌套;
  5. 中断数据结构的同步和保护;

目前的通用中断子系统实现了以下这些标准流控回调函数,这些函数都定义在:kernel/irq/chip.c中

  1. handle_simple_irq 用于简易流控处理;
  2. handle_level_irq 用于电平触发中断的流控处理;
  3. handle_edge_irq 用于边沿触发中断的流控处理;
  4. handle_fasteoi_irq 用于需要响应eoi的中断控制器;
  5. handle_percpu_irq 用于只在单一cpu响应的中断;
  6. handle_nested_irq 用于处理使用线程的嵌套中断;

我们在 中断描述符结构中已经看到 highlevel irq-events handle:
irq_flow_handler_t handle_irq;

typedef void (irq_flow_handler_t)(unsigned int irq, struct irq_desc desc);

这样与通用子系统的相关的内容就快速的介绍了一遍,我们先休息一下,跳进代码中。

客官,小的这记口碎大石值几个钱?