中断控制器及中断域

中断控制器

外围设备不是把中断请求直接发送给处理器,而是发给中断控制器,由中断控制器转发给处理器;

中断控制器 是用于管理和调度系统中各种中断请求的硬件模块。在多设备或多中断源的系统中,中断控制器起到仲裁和分发中断信号的作用,确保 CPU 能够按优先级高效处理中断请求。

中断控制器的功能

中断控制器的主要功能包括:

  • 中断管理:接收多个中断源的请求,按照优先级将中断请求传递给 CPU。
  • 中断屏蔽:可以屏蔽某些不需要响应的中断。
  • 优先级仲裁:根据中断的优先级确定中断处理的顺序。
  • 中断分发:在多核处理器系统中,将中断分发给特定的 CPU 核心。

通用中断控制器(GIC)

ARM公司提供了一种标准的中断控制器,称为通用中断控制器(Generic Interrupt Controller,GIC);GIC发展历程中有多个版本(v1~v4),每个版本都引入了不同的功能与改进,以适配更复杂的多核处理器系统;GIC各版本之间主要是支持的CPU核心数、中断类型不同;

GIC硬件的实现形态有两种:

1)厂商研发自己的ARM处理器,向ARM公司购买GIC的授权;

2)厂商直接向ARM公司购买处理器的授权,这些处理器包含了GIC;

GIC的结构组成

从软件的角度看,GIC有两个主要的功能块:

  • GIC Distributor(分发器)

系统中所有的中断源连接到分发器,分发器的寄存器用来控制单个中断的属性:优先级、状态、安全、转发信息(可以被发送到哪些处理器)和使能状态。分发器决定哪个中断应该通过处理器接口转发到哪个处理器。

  • GIC CPU Interface(CPU 接口)

处理器通过处理器接口接收中断。处理器接口提供的寄存器用来屏蔽和识别中断,控制中断的状态。每个处理器有一个单独的处理器接口。

GIC的中断类型

软件通过中断号识别中断,每个中断号唯一对应一个中断源;

ARM GIC(Generic Interrupt Controller,通用中断控制器) 的中断类型主要包括以下几类:


SGI(Software Generated Interrupt,软件触发中断)
  • 定义:由软件生成的中断,通常用于同一个处理器集群中的 CPU 核之间的通信(例如唤醒其他核)。
  • 特点
    • 只能在同一组 CPU Cluster 中使用。
    • 软件通过向 GIC Distributor 写寄存器触发 SGI。
    • 中断号范围:0-15(通常只有 16 个)。
    • 主要用于 核间通信(IPI,Inter-Processor Interrupt)
  • 使用场景:核之间的同步、任务调度、唤醒 CPU 核。

SGI和软中断的区别:

  • SGI 属于硬件中断的一部分,主要用于 CPU 核之间的通信。
  • 软中断 是 Linux 内核中提供的 软件机制,用于延迟处理硬中断后的任务。
  • SGI 和软中断的共同点是都可以由软件触发,但 SGI 属于硬中断,而 软中断纯粹是软件调度机制

PPI(Private Peripheral Interrupt,私有外设中断)
  • 定义:分配给每个 CPU 核的中断,是每个核私有的中断,不同的核可以有独立的 PPI。
  • 特点
    • 中断号范围:16-31(固定的中断号)。
    • 每个 CPU 核都有自己的一组 PPI。
    • 常用于管理处理器本地外设,如 定时器(Local Timer)看门狗
  • 使用场景:处理器本地中断,如定时器中断、核本地外设的中断。

SPI(Shared Peripheral Interrupt,共享外设中断)
  • 定义:被多个 CPU 核共享的外设中断,由 GIC 分发到合适的 CPU 核进行处理。
  • 特点
    • 中断号范围:32-1019
    • 通常由外部设备(如网络控制器、USB 控制器等)触发。
    • 通过 GIC Distributor 控制分发到指定的 CPU 核。
  • 使用场景:外设中断,如网络设备、USB 控制器、DMA 控制器等。

LPI(Locality-specific Peripheral Interrupt)
  • 功能:LPI 是为了解决大规模设备中断需求而设计的,尤其是通过 MSI(Message Signaled Interrupt) 的机制来动态分发中断。
  • 特点
    • 动态中断号分配:LPI 不使用固定的中断号,而是可以动态分配。
    • 高扩展性:中断号可以扩展到 2^24(最多 16M+ 个中断)。
    • ITS(Interrupt Translation Service)支持:LPI 需要通过 GIC ITS(Interrupt Translation Service)进行路由和管理。
    • 专为高性能场景设计:特别适合 PCIe 设备 使用的 MSI(Message Signaled Interrupt)

边沿触发和水平触发

中断可以是边沿触发(edge-triggered,ET),也可以是水平触发(level-triggered,LT)。ET是再电压变化的一瞬间触发,电压由高到低变化触发的中断称为下降沿触发,电压由低到高变化触发的中断称为上升沿触发;LT是再高电压或低电压保持的时间内触发,低电压触发的中断称为低电平触发,高电压触发的中断称为高电平触发;

中断状态

中断有以下4种状态:

(1)Inactive:中断源没有发送中断。

(2)Pending:中断源已经发送中断,等待处理器处理。

(3)Active:处理器已经确认中断,正在处理。

(4)Activeand pending:处理器正在处理中断,相同的中断源)又发送了一个中断

中断的状态转换过程如下。

(1)Inactive->Pending:外围设备发送了中断。

(2)Pending->Active:处理器确认了中断。

(3)Active->Inactive:处理器处理完中断。

中断控制器的工作流程

  1. 中断源发起请求:外设或软件触发一个中断请求。
  2. 中断控制器接收中断
    • GIC Distributor 接收中断请求。
    • 仲裁中断的优先级,并决定将中断分发给哪个 CPU 核。
  3. CPU 接收中断
    • GIC CPU Interface 将中断信息通知 CPU。
    • CPU 通过中断向量表找到对应的中断服务例程(ISR)并执行。
  4. 中断处理完成
    • CPU 执行完 ISR 后,向 GIC 发送 中断结束信号(EOI)
    • GIC 清除中断状态,等待下一次中断请求。

内核中GIC定义

不同种类的中断控制器的访问方法存在差异,为了屏蔽差异,内核定义了中断控制器描述符irq_chip,每种中断控制器自定义各种操作函数。GIC V2控制器的描述符如下:

static struct irq_chip gic_chip = {
    .irq_mask		= gic_mask_irq,
    .irq_unmask		= gic_unmask_irq,
    .irq_eoi		= gic_eoi_irq,
    .irq_set_type		= gic_set_type,
    .irq_get_irqchip_state	= gic_irq_get_irqchip_state,
    .irq_set_irqchip_state	= gic_irq_set_irqchip_state,
    .flags			= IRQCHIP_SET_TYPE_MASKED |
        IRQCHIP_SKIP_SET_WAKE |
        IRQCHIP_MASK_ON_SUSPEND,
};
1. irq_mask
  • 描述: 指向函数 gic_mask_irq,用于屏蔽指定的中断。
  • 功能: 当某个中断不需要被响应时,通过调用该函数屏蔽中断。

2. irq_unmask
  • 描述: 指向函数 gic_unmask_irq,用于取消屏蔽某个中断。
  • 功能: 重新启用被屏蔽的中断,使其能够被处理。

3. irq_eoi
  • 描述: 指向函数 gic_eoi_irq,用于发出 End of Interrupt (EOI) 信号。
  • 功能: 通知 GIC 当前中断处理完成,通常在中断服务程序(ISR)末尾调用。

4. irq_set_type
  • 描述: 指向函数 gic_set_type,用于设置中断触发类型。
  • 功能: 配置中断是边沿触发还是电平触发,以及上升沿、下降沿等模式。

5. irq_get_irqchip_state
  • 描述: 指向函数 gic_irq_get_irqchip_state,用于获取中断控制器的状态信息。
  • 功能: 允许查询中断的当前状态,例如是否处于挂起状态。

6. irq_set_irqchip_state
  • 描述: 指向函数 gic_irq_set_irqchip_state,用于设置中断控制器的状态。
  • 功能: 用于操作中断状态,例如设置中断为活动状态或挂起状态。

7. flags
  • 描述: flags 定义了中断控制器的属性,以下是具体标志位的含义:
    • **IRQCHIP_SET_TYPE_MASKED**:
      • 配置中断触发类型时,必须先屏蔽中断。
    • **IRQCHIP_SKIP_SET_WAKE**:
      • 跳过设置唤醒功能(即不支持 set_wake 操作)。
    • **IRQCHIP_MASK_ON_SUSPEND**:
      • 系统挂起时自动屏蔽中断。

gic_chip 是 Linux 内核中用于描述 GIC 的 irq_chip 实现。它定义了 GIC 的基本操作(如屏蔽、解除屏蔽、EOI 等)和属性,内核通过调用这些函数实现对 GIC 的控制。该结构体最终会与具体的中断(如外部设备中断)绑定,以完成中断管理和调度。

中断域

一个大型系统可能有多个中断控制器,这些中断控制器可以级联,一个中断控制器作为中断源连接到另一个中断控制器,但只有一个中断控制器作为根控制器直接连接到处理器。为把每个中断控制器本地的硬件中断号映射到全局唯一的Linux中断号(也称为虚拟中断号),内核定义了中断域irq_domain,每个中断控制器有自己的中断域

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 fwnode_handle *fwnode;
	enum irq_domain_bus_token bus_token;
	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[];
};

创建中断域

中断控制器的驱动程序使用分配函数irq_domain_add_*()创建和注册中断域。每种映射方法提供不同的分配函数,调用者必须给分配函数提供irq_domain_ops结构体,分配函数在执行成功的时候返回irq_domain的指针。

中断域支持以下映射方法:

- 线性映射;
- 树映射;
- 不映射;

线性映射(linear map)

线性映射维护一个固定大小的表,索引是硬件中断号。如果硬件中断号的最大数量是固定的,并且比较小(小于256),那么线性映射是好的选择。对于线性映射,分配中断域的函数如下:

/**
 * irq_domain_add_linear() - Allocate and register a linear revmap irq_domain.
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: Number of interrupts in the domain.
 * @ops: map/unmap domain callbacks
 * @host_data: Controller private data pointer
 */
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
					 unsigned int size,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}

树映射(tree map)

树映射使用基数树(radix tree)保存硬件中断号到Linux中断号的映射。为什么不使用红黑树呢?如果硬件中断号可能非常大,那么树映射是好的选择,因为不需要根据最大硬件中断号分配一个很大的表。对于树映射,分配中断域的函数如下:

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}

不映射(no map)

有些中断控制器很强,硬件中断号是可以配置的,例如PowerPC架构使用的MPIC(Multi-Processor Interrupt Controller)。我们直接把Linux中断号写到硬件,硬件中断号就是Linux中断号,不需要映射。对于不映射,分配中断域的的函数如下:

static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
					 unsigned int max_irq,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}

分配函数把主要工作委托给函数_irq_domain_add()。函数_irq_domain_add()的执行过程是:分配一个irq_domain结构体,初始化成员,然后把中断域添加到全局链表irq_domain_list中。

创建映射

创建中断域以后,需要向中断域添加硬件中断号到Linux中断号的映射,内核提供了函数irq_create_mapping:

/**
 * irq_create_mapping() - Map a hardware interrupt into linux irq space
 * @domain: domain owning this hardware interrupt or NULL for default domain
 * @hwirq: hardware irq number in that domain space
 *
 * Only one mapping per hardware interrupt is permitted. Returns a linux
 * irq number.
 * If the sense/trigger is to be specified, set_irq_type() should be called
 * on the number returned from that call.
 */
unsigned int irq_create_mapping(struct irq_domain *domain,
				irq_hw_number_t hwirq)

输入参数是中断域和硬件中断号,返回Linux中断号。该函数首先分配Linux中断号,然后把硬件中断号到Linux中断号的映射添加到中断域。

查找映射

中断处理程序需要根据硬件中断号查找Linux中断号,内核提供了函数 irq_find_mapping:

/**
 * irq_find_mapping() - Find a linux irq from an hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 */
unsigned int irq_find_mapping(struct irq_domain *domain,
			      irq_hw_number_t hwirq)

输入参数是中断域和硬件中断号,返回Linux中断号。

参考文献

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值