AIA中断控制器IPI的Linux内核实现
AIA中断控制器IPI的Linux内核实现
文章目录
前言
在 RISCV Linux 中有 AIA 之后,一般不通过 SBI 发送 IPI。今天看一下 Linux 在 AIA 中,如何发送 IPI。
IPI 类型
内核中一共定义 8 种 IPI,分别代表:
枚举 | 含义 / 用途 |
---|---|
IPI_RESCHEDULE (0) |
请求对方 CPU 重新调度(抢占/负载均衡等场景踢远端核)。 |
IPI_CALL_FUNC (1) |
跨核执行函数(smp_call_function* / on_each_cpu* )。 |
IPI_CPU_STOP (2) |
让目标 CPU 立刻停机(关机/BUG 等收敛)。 |
IPI_CPU_CRASH_STOP (3) |
崩溃场景的停机,在 kdump/panic 时让其它核停住并保存寄存器。 |
IPI_IRQ_WORK (4) |
IRQ work 提醒(异步小任务在对方核上执行)。 |
IPI_TIMER (5) |
定时广播(无本地时钟的 CPU 由其它核广播 tick)。仅在 CONFIG_GENERIC_CLOCKEVENTS_BROADCAST 时使用。 |
IPI_CPU_BACKTRACE (6) |
打印对方 CPU 的回溯(调试/诊断用的 NMI 级回溯)。 |
IPI_KGDB_ROUNDUP (7) |
KGDB 汇总/唤醒其它核(调试器使所有 CPU 进入可调试态)。 |
enum ipi_message_type {
IPI_RESCHEDULE,
IPI_CALL_FUNC,
IPI_CPU_STOP,
IPI_CPU_CRASH_STOP,
IPI_IRQ_WORK,
IPI_TIMER,
IPI_CPU_BACKTRACE,
IPI_KGDB_ROUNDUP,
IPI_MAX
};
IPI 发送
send_ipi_mask
会通过 cpu
掩码向目标 cpu
发送 op
类型的 IPI
。
static struct irq_desc *ipi_desc[IPI_MAX] __read_mostly;
static void send_ipi_mask(const struct cpumask *mask, enum ipi_message_type op)
{
__ipi_send_mask(ipi_desc[op], mask);
}
主要发送函数通过 __ipi_send_mask
实现。
int __ipi_send_mask(struct irq_desc *desc, const struct cpumask *dest)
{
struct irq_data *data = irq_desc_get_irq_data(desc); /* 从 desc 取出 irq_data(携带 chip/domain 等) */
struct irq_chip *chip = irq_data_get_irq_chip(data); /* 取出中断控制器的 chip 回调 */
unsigned int cpu;
#ifdef DEBUG
/*
* 调试构建下做一次能力/参数检查:
* - chip 是否实现了 IPI 发送回调
* - dest 掩码是否合法
* 正常构建为减少开销则省略。
*/
if (WARN_ON_ONCE(ipi_send_verify(chip, data, dest, 0)))
return -EINVAL;
#endif
/* 优先使用控制器提供的“掩码批量发送”接口:一次调用对所有目标 CPU 生效 */
if (chip->ipi_send_mask) {
chip->ipi_send_mask(data, dest);
return 0;
}
......
}
这里主要从 op
对应的 desc
中拿到 data
的子属性 chip
,调用其 ipi_send_mask
回调函数。
那么我们重点关注这些属性何时设置的,设置的具体是谁。
IMSIC 早期初始化
当我们使用设备树启动系统时,会通过当设备树节点的 compatible
匹配 "riscv,imsics"
时,会在 早期 irqchip
框架阶段调用 imsic_early_dt_init()
。
static int __init imsic_early_dt_init(struct device_node *node, struct device_node *parent)
{
struct fwnode_handle *fwnode = &node->fwnode;
int rc;
/* 1) 基于 DT 节点建立 IMSIC 的全局/本地状态
* - 解析几何参数与 reg 列表
* - ioremap 各个 MMIO 窗口
* - 为每个 CPU 计算并保存其 MSI 门铃页的物理/虚拟地址
* - 初始化 per-CPU 本地状态等
*/
rc = imsic_setup_state(fwnode, NULL);
if (rc) {
pr_err("%pfwP: failed to setup state (error %d)\n", fwnode, rc);
return rc;
}
/* 2) 进行“早期 IPI” 的初始化
* - 创建 IPI 复用器(ipi-mux)/ 域
* - 为 8 类 IPI 分配一段连续 VIRQ,并绑定到 ipi-mux 的 irq_chip/handler
* - 保存 virq 基址,注册 handle_IPI(),使得后续可发 IPI
*/
rc = imsic_early_probe(fwnode);
......
}
/* 把上面的 early init 函数注册为一个“早期中断控制器”初始化入口:
* 当设备树节点的 compatible 匹配 "riscv,imsics" 时,
* 在 very early 的 irqchip 框架阶段调用 imsic_early_dt_init()。
*/
IRQCHIP_DECLARE(riscv_imsic, "riscv,imsics", imsic_early_dt_init);
imsic_early_probe
会进行早期 IPI
初始化工作。
static int __init imsic_early_probe(struct fwnode_handle *fwnode)
{
struct irq_domain *domain;
int rc;
/* 1) 找到父级中断域并为“外部中断(Sei)”建立映射
*
* - RISC-V 架构里 IMSIC 作为“来向 MSI 控制器”,其触发最终汇聚到
* CPU 本地控制器(INTC)上的 S-Mode External Interrupt 线上。
* - 这里先根据 RISC-V INTC 的 fwnode 找到对应的 irq_domain,
* 再把硬件号 RV_IRQ_EXT(S 外部中断)映射成一个 Linux IRQ,
* 作为 IMSIC 的“父中断”。
*/
domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY);
if (!domain) {
pr_err("%pfwP: Failed to find INTC domain\n", fwnode);
return -ENOENT;
}
imsic_parent_irq = irq_create_mapping(domain, RV_IRQ_EXT);
if (!imsic_parent_irq) {
pr_err("%pfwP: Failed to create INTC mapping\n", fwnode);
return -ENOENT;
}
/* 2) 初始化 IPI 域(ipi-mux)
*
* - 创建 IPI 复用器的 irq_domain,并为 8 类 IPI 分配一段连续 VIRQ;
* - 绑定 ipi_mux_chip / handle_percpu_devid_irq 等;
* - 设置 IPI 发送回调为 imsic_ipi_send()(对目标 CPU 的 IMSIC 门铃页写入)。
*/
rc = imsic_ipi_domain_init();
......
}
这里我们重点看 imsic_ipi_domain_init
函数。
static int __init imsic_ipi_domain_init(void)
{
int virq;
/* 1) 创建“IPI 复用器”并为 IMSIC 的 8 类 IPI 申请一段连续的 VIRQ 区间
*
* - IMSIC_NR_IPI 通常为 8(RESCHED/CALL_FUNC/.../KGDB)。
* - 第二个参数是发送回调:ipi-mux 最终会调用 imsic_ipi_send(cpu),
* 由它对目标 CPU 的 IMSIC 门铃页做 writel(IMSIC_IPI_ID, ...),
* 即所有 IPI 在硬件上复用同一个 IMSIC 本地 ID。
* - 返回值 virq 为该段虚拟中断号的基址(>= 0);失败返回负值。
*/
virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send);
if (virq <= 0)
/* 约定:<=0 视为失败;负值直接返回错误码,=0 则用 -ENOMEM 表示 */
return virq < 0 ? virq : -ENOMEM;
/* 2) 把这段 VIRQ 区间告知架构层(RISC-V)
*
* - 保存基址到 ipi_virq_base,并限制数量为 IPI_MAX;
* - 为每个 IPI virq 调用 request_percpu_irq() 绑定 handle_IPI();
* - 标记 IRQ_HIDDEN,并立即为 boot CPU 启用这些 per-CPU IRQ。
*/
riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI);
/* 3) 打印信息:说明 IMSIC 以固定的 IMSIC_IPI_ID 提供 IPI 功能
* (所有 IPI 类型在硬件层都写同一个 IMSIC 本地中断号)。
*/
pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID);
return 0;
}
上文我们知道,要发 IPI
,则需要通过 static struct irq_desc *ipi_desc[IPI_MAX] __read_mostly
拿到对应 desc
找到 data
属性对应的 chip
,调用其 ipi_send_mask
。
那么这里,ipi_mux_create
的调用则负责通过 maple_tree
的形式存储连续 VIRQ
以及对应 desc
。并设置 desc
的属性。
riscv_ipi_set_virq_range
则把 static struct irq_desc *ipi_desc[IPI_MAX] __read_mostly
和 对应 desc
建立联系。
接下来分别看一下具体实现。
ipi_mux_create
int ipi_mux_create(unsigned int nr_ipi, void (*mux_send)(unsigned int cpu))
{
struct fwnode_handle *fwnode;
struct irq_domain *domain;
int rc;
/* 1) 全局只允许创建一次 IPI-Mux 域(单例) */
if (ipi_mux_domain)
return -EEXIST;
/* 2) 基本参数校验:
* - BITS_PER_TYPE(int) < nr_ipi:防止 nr_ipi 超出 int 能表达的位宽
* - !mux_send:必须提供底层发送回调(例如 imsic_ipi_send)
*/
if (BITS_PER_TYPE(int) < nr_ipi || !mux_send)
return -EINVAL;
/* 3) 分配 per-CPU 状态(ipi_mux_pcpu)
* 用于复用器在发送/接收端维护每 CPU 的待处理位图等状态
*/
ipi_mux_pcpu = alloc_percpu(typeof(*ipi_mux_pcpu));
if (!ipi_mux_pcpu)
return -ENOMEM;
/* 4) 为 IPI-Mux 域创建一个命名 fwnode,作为 irq_domain 的身份标识 */
fwnode = irq_domain_alloc_named_fwnode("IPI-Mux");
if (!fwnode) {
pr_err("unable to create IPI Mux fwnode\n");
rc = -ENOMEM;
goto fail_free_cpu;
}
/* 5) 创建线性 irq_domain:
* - hwirq 空间为 0..nr_ipi-1(每一类 IPI 一个 hwirq 索引)
* - 采用 ipi_mux_domain_ops(.alloc/.free 实现见外部)
*/
domain = irq_domain_create_linear(fwnode, nr_ipi,
&ipi_mux_domain_ops, NULL);
if (!domain) {
pr_err("unable to add IPI Mux domain\n");
rc = -ENOMEM;
goto fail_free_fwnode;
}
/* 6) 域属性:
* - IRQ_DOMAIN_FLAG_IPI_SINGLE:表明这些 IPI 共享同一个父中断线
* (例如 IMSIC 的同一个本地 ID),由软件做二次解复用
* - DOMAIN_BUS_IPI:把该域标记为 IPI 类型,便于 genirq 做策略区分
*/
domain->flags |= IRQ_DOMAIN_FLAG_IPI_SINGLE;
irq_domain_update_bus_token(domain, DOMAIN_BUS_IPI);
/* 7) 从该域分配 nr_ipi 个 virq(连续区间):
* - 成功时返回 virq 基址(>0)
* - 失败返回负值;=0 也视为失败(0 不是有效 virq)
* - 在 .alloc 回调里会将每个 virq 绑定到 ipi_mux_chip、
* 设置 hwirq=i、handler 等
*/
rc = irq_domain_alloc_irqs(domain, nr_ipi, NUMA_NO_NODE, NULL);
if (rc <= 0) {
pr_err("unable to alloc IRQs from IPI Mux domain\n");
goto fail_free_domain;
}
/* 8) 记录全局域与发送回调:
* - ipi_mux_domain:后续发送/接收路径使用
* - ipi_mux_send :底层真正发 IPI 的函数指针(例如 imsic_ipi_send)
*/
ipi_mux_domain = domain;
ipi_mux_send = mux_send;
/* 返回 virq 基址(供架构层保存为 ipi_virq_base) */
return rc;
fail_free_domain:
irq_domain_remove(domain);
fail_free_fwnode:
irq_domain_free_fwnode(fwnode);
fail_free_cpu:
free_percpu(ipi_mux_pcpu);
return rc;
}
irq_domain_create_linear
会绑定 domain
的 ops
为 ipi_mux_domain_ops
。irq_domain_alloc_irqs
则做具体分配,同时设置 desc
的具体属性。我们简单过一下相关源码,不会很细致。
irq_domain_alloc_irqs
一路获取锁,调用函数到下面的实现:
int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,
unsigned int irq_base,
unsigned int nr_irqs, void *arg)
{
/* 若该 irq_domain 未实现分配回调(.alloc),则无法在该“分层域”上完成后续
* 的 hwirq 绑定、chip/handler 设置等,直接返回不支持。
*/
if (!domain->ops->alloc) {
pr_debug("domain->ops->alloc() is NULL\n");
return -ENOSYS;
}
/* 调用具体域的 .alloc():
* - 典型实现中会为 [irq_base, irq_base+nr_irqs) 每个 virq 绑定一个 hwirq,
* 设置 irq_chip、handler、chip_data,并建立到父域的层级关系。
* - 成功返回 0,失败返回负错码。
*/
return domain->ops->alloc(domain, irq_base, nr_irqs, arg);
}
static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
unsigned int nr_irqs, int node, void *arg,
bool realloc, const struct irq_affinity_desc *affinity)
{
int i, ret, virq;
/* 1) 确定 virq 起始号:
* - 若是“重新分配(realloc)”且调用方给定了固定起点(irq_base>=0),则直接沿用;
* - 否则从全局稀疏 IRQ 空间申请一段连续 virq(长度 nr_irqs)。
*/
if (realloc && irq_base >= 0) {
virq = irq_base;
} else {
/* irq_domain_alloc_descs():
* - 在 Maple Tree 管理的 sparse_irqs 中查找一段空洞;
* - 可根据 affinity 做亲和性分配;
* - 返回分配到的起始 virq(失败则返回负错码)。
*/
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
affinity);
if (virq < 0) {
pr_debug("cannot allocate IRQ(base %d, count %d)\n",
irq_base, nr_irqs);
return virq;
}
}
/* 2) 为该段 virq 预分配 irq_data(每个 virq 一个):
* - 建立 virq 与 domain 的关系;
* - 分配/初始化通用字段(common)等;此时还未设置具体 chip/handler。
* - 若失败,需要回滚已分配的 virq 区间。
*/
if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {
pr_debug("cannot allocate memory for IRQ%d\n", virq);
ret = -ENOMEM;
goto out_free_desc; /* 回滚 virq 描述符分配 */
}
/* 3) 进入“分层域分配”阶段:
* - 调用 domain->ops->alloc()(通过 irq_domain_alloc_irqs_hierarchy 封装);
* - 由具体域完成 hwirq 绑定、irq_chip/handler/chip_data 设置,
* 并建立到父域的层级映射(如线性/树形 revmap)。
* - 失败则回滚 irq_data 与 virq。
*/
ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
...... /* 后续:成功则修剪层级/插入全局映射;失败路径跳到 out_free_irq_data/out_free_desc */
}
这里不废话,irq_domain_alloc_descs
和 irq_domain_alloc_irq_data
分别设置 desc
和 desc
对应的 data
部分。 irq_domain_alloc_irqs_hierarchy
则会调用上文注册的 domain ops
的 alloc
函数做初始化。
具体绑定函数如下:
static const struct irq_chip ipi_mux_chip = {
.name = "IPI Mux",
.irq_mask = ipi_mux_mask,
.irq_unmask = ipi_mux_unmask,
.ipi_send_mask = ipi_mux_send_mask,
};
static int ipi_mux_domain_alloc(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
int i;
for (i = 0; i < nr_irqs; i++) {
irq_set_percpu_devid(virq + i);
irq_domain_set_info(d, virq + i, i, &ipi_mux_chip, NULL,
handle_percpu_devid_irq, NULL, NULL);
}
return 0;
}
即,我们会绑定 chip
为 ipi_mux_chip
这个 chip
。而它的 ipi_send_mask
我们会在 imsic_ipi_domain_init
初始化 IMSIC 时通过调用 ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send)
来指定为 imsic_ipi_send
。
riscv_ipi_set_virq_range
这个函数会把上文绑定好 chip
的 desc
和我们发送 IPI
时用到的 static struct irq_desc *ipi_desc[IPI_MAX] __read_mostly
和 对应 desc
建立联系。简单看一下:
void riscv_ipi_set_virq_range(int virq, int nr)
{
int i, err;
if (WARN_ON(ipi_virq_base))
return;
WARN_ON(nr < IPI_MAX);
nr_ipi = min(nr, IPI_MAX);
ipi_virq_base = virq;
/* Request IPIs */
for (i = 0; i < nr_ipi; i++) {
err = request_percpu_irq(ipi_virq_base + i, handle_IPI,
"IPI", &ipi_dummy_dev);
WARN_ON(err);
ipi_desc[i] = irq_to_desc(ipi_virq_base + i);
......
}
重点关注最后部分,设置指针指向。
IMSIC 的 MMIO 处理
我们看一下 IMSIC 设置的 IPI 触发函数:
static void imsic_ipi_send(unsigned int cpu)
{
struct imsic_local_config *local = per_cpu_ptr(imsic->global.local, cpu);
writel_relaxed(IMSIC_IPI_ID, local->msi_va);
}
所有 IPI
复用一个 IMSIC
的中断号 1。写向 PERCPU
数据结构的 IMSIC
的 Interrupt File
。
它的 VA 什么时候设置呢?我们看回早期设备树初始化函数:
static int __init imsic_early_dt_init(struct device_node *node, struct device_node *parent)
{
struct fwnode_handle *fwnode = &node->fwnode;
int rc;
/* 1) 基于 DT 节点建立 IMSIC 的全局/本地状态
* - 解析几何参数与 reg 列表
* - ioremap 各个 MMIO 窗口
* - 为每个 CPU 计算并保存其 MSI 门铃页的物理/虚拟地址
* - 初始化 per-CPU 本地状态等
*/
rc = imsic_setup_state(fwnode, NULL);
if (rc) {
pr_err("%pfwP: failed to setup state (error %d)\n", fwnode, rc);
return rc;
}
imsic_setup_state
负责初始化好这部分。具体如下注释:
int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
{
u32 i, j, index, nr_parent_irqs, nr_mmios, nr_handlers = 0;
struct imsic_global_config *global;
struct imsic_local_config *local;
void __iomem **mmios_va = NULL; /* 暂存每个 regset 的 ioremap 基址 */
struct resource *mmios = NULL; /* 暂存每个 regset 的物理范围 start/end */
unsigned long reloff, hartid; /* reloff: 逻辑串联空间内的字节偏移;hartid: 目标 HART ID */
phys_addr_t base_addr; /* 用于几何基址校验的临时变量 */
int rc, cpu;
/*
* 平台上只允许一个 IMSIC 实例,以简化 SMP 中断亲和与 per-CPU IPI 的实现。
* 多 socket/die 平台下同一个 IMSIC 实例可能对应多个 MMIO 窗口(regset)。
*/
if (imsic) {
pr_err("%pfwP: already initialized hence ignoring\n", fwnode);
return -EALREADY;
}
/* 检查当前 CPU 是否支持 AIA(SxAIA 扩展) */
if (!riscv_isa_extension_available(NULL, SxAIA)) {
pr_err("%pfwP: AIA support not available\n", fwnode);
return -ENODEV;
}
/* 分配全局私有结构并清零 */
imsic = kzalloc(sizeof(*imsic), GFP_KERNEL);
if (!imsic)
return -ENOMEM;
imsic->fwnode = fwnode;
global = &imsic->global;
/* 为每个 CPU 分配一份本地状态(per-CPU),保存各自的 MSI page 地址等 */
global->local = alloc_percpu(typeof(*global->local));
if (!global->local) {
rc = -ENOMEM;
goto out_free_priv;
}
/* 解析 IMSIC 的 fwnode:几何参数、父中断数、MMIO regset 数等 */
rc = imsic_parse_fwnode(fwnode, global, &nr_parent_irqs, &nr_mmios, opaque);
if (rc)
goto out_free_local;
/* 申请保存 regset 物理范围的数组(临时) */
mmios = kcalloc(nr_mmios, sizeof(*mmios), GFP_KERNEL);
if (!mmios) {
rc = -ENOMEM;
goto out_free_local;
}
/* 申请保存 regset ioremap 虚拟基址的数组(临时) */
mmios_va = kcalloc(nr_mmios, sizeof(*mmios_va), GFP_KERNEL);
if (!mmios_va) {
rc = -ENOMEM;
goto out_iounmap;
}
/* 逐个 regset 解析并 ioremap */
for (i = 0; i < nr_mmios; i++) {
/* 取第 i 个 regset 的 resource(物理 start/end) */
rc = imsic_get_mmio_resource(fwnode, i, &mmios[i]);
if (rc) {
pr_err("%pfwP: unable to parse MMIO regset %d\n", fwnode, i);
goto out_iounmap;
}
/* 归一化:清掉 guest/hart/group 索引位,仅保留几何基址高位 */
base_addr = mmios[i].start;
base_addr &= ~(BIT(global->guest_index_bits +
global->hart_index_bits +
IMSIC_MMIO_PAGE_SHIFT) - 1);
base_addr &= ~((BIT(global->group_index_bits) - 1) <<
global->group_index_shift);
/* 校验所有 regset 归一化后应属于同一几何基址 */
if (base_addr != global->base_addr) {
rc = -EINVAL;
pr_err("%pfwP: address mismatch for regset %d\n", fwnode, i);
goto out_iounmap;
}
/* 整段 regset 做 ioremap,便于后续通过偏移落位到具体 MSI page */
mmios_va[i] = ioremap(mmios[i].start, resource_size(&mmios[i]));
if (!mmios_va[i]) {
rc = -EIO;
pr_err("%pfwP: unable to map MMIO regset %d\n", fwnode, i);
goto out_iounmap;
}
}
/* 初始化 per-CPU 本地运行时状态(掩码/寄存器默认值等) */
rc = imsic_local_init();
if (rc) {
pr_err("%pfwP: failed to initialize local state\n",
fwnode);
goto out_iounmap;
}
/* 为每个“父中断”配置其目标 CPU 的 MSI page 地址(物理/虚拟) */
for (i = 0; i < nr_parent_irqs; i++) {
/* 由父中断索引查 HART ID(可能缺失则跳过) */
rc = imsic_get_parent_hartid(fwnode, i, &hartid);
if (rc) {
pr_warn("%pfwP: hart ID for parent irq%d not found\n", fwnode, i);
continue;
}
/* HART ID → cpuid(无效则跳过) */
cpu = riscv_hartid_to_cpuid(hartid);
if (cpu < 0) {
pr_warn("%pfwP: invalid cpuid for parent irq%d\n", fwnode, i);
continue;
}
/* 计算该 hart 的 MSI page 在“逻辑串联的 regset 空间”中的偏移:
* 每个 hart 占 stride = 2^(guest_bits) * IMSIC_MMIO_PAGE_SZ 字节
*/
index = nr_mmios; /* 默认设为越界,找不到则失败 */
reloff = i * BIT(global->guest_index_bits) *
IMSIC_MMIO_PAGE_SZ;
/* 在各 regset 中定位该偏移落在哪个窗口
* 注意:存在“洞”时,需要以 stride 对齐跨过窗口
*/
for (j = 0; nr_mmios; j++) { /* FIXME: 这里应为 j < nr_mmios */
if (reloff < resource_size(&mmios[j])) {
index = j;
break;
}
/*
* 若 regset 大小未按 stride 对齐(有洞),则按 stride 对齐后再跨过。
*/
reloff -= ALIGN(resource_size(&mmios[j]),
BIT(global->guest_index_bits) * IMSIC_MMIO_PAGE_SZ);
}
if (index >= nr_mmios) {
pr_warn("%pfwP: MMIO not found for parent irq%d\n", fwnode, i);
continue;
}
/* 写入该 CPU 的 per-CPU 本地状态:记录其 MSI page 物理/虚拟地址 */
local = per_cpu_ptr(global->local, cpu);
local->msi_pa = mmios[index].start + reloff;
local->msi_va = mmios_va[index] + reloff;
nr_handlers++;
}
/* 如果没有任何 CPU 配置成功,则无法处理中断 */
if (!nr_handlers) {
pr_err("%pfwP: No CPU handlers found\n", fwnode);
rc = -ENODEV;
goto out_local_cleanup;
}
/* 初始化矩阵分配器(本地中断 ID 分配/掩码管理等) */
rc = imsic_matrix_init();
if (rc) {
pr_err("%pfwP: failed to create matrix allocator\n", fwnode);
goto out_local_cleanup;
}
/* 临时数组已完成使命,释放之(每 CPU 的 msi_{pa,va} 已保存到 per-CPU 结构) */
kfree(mmios_va);
kfree(mmios);
return 0;
out_local_cleanup:
/* 失败回滚:清理 per-CPU 本地状态 */
imsic_local_cleanup();
out_iounmap:
/* 失败回滚:解除各 regset 的 ioremap 映射并释放临时数组 */
for (i = 0; i < nr_mmios; i++) {
if (mmios_va[i])
iounmap(mmios_va[i]);
}
kfree(mmios_va);
kfree(mmios);
out_free_local:
/* 失败回滚:释放 per-CPU 结构 */
free_percpu(imsic->global.local);
out_free_priv:
/* 失败回滚:释放全局私有并清空指针 */
kfree(imsic);
imsic = NULL;
return rc;
}
函数调用链总结
启动期(DT/early)初始化链
IRQCHIP_DECLARE(...,"riscv,imsics", imsic_early_dt_init)
→ imsic_early_dt_init()
→ imsic_setup_state(fwnode)
(解析 DT;ioremap 多个 IMSIC regset;为每 CPU 计算/保存 MSI 门铃页 msi_pa/msi_va;init 本地状态/矩阵)
→ imsic_early_probe(fwnode)
→ irq_find_matching_fwnode(...INTC...) / irq_create_mapping(RV_IRQ_EXT)
(找 RISC-V INTC 父域并把 S-mode 外部中断映射成 Linux IRQ,作为 IMSIC 的父中断)
→ imsic_ipi_domain_init()
→ ipi_mux_create(IMSIC_NR_IPI=8, imsic_ipi_send)
(建 IPI-Mux 线性 irq_domain;标记 IPI_SINGLE;分配一段连续 virq)
→ irq_domain_alloc_irqs(...)
→ irq_domain_alloc_descs(...) (用 Maple Tree 在全局 virq 空间找“连续空洞”,建 `irq_desc`)
→ irq_domain_alloc_irq_data(...) (为每个 virq 准备 irq_data)
→ irq_domain_alloc_irqs_hierarchy(...)
→ ipi_mux_domain_alloc(.alloc 回调)
(for i=0..7:设 per-cpu devid;`irq_domain_set_info(d, virq+i, hwirq=i, chip=&ipi_mux_chip, handler=handle_percpu_devid_irq)`)
→ riscv_ipi_set_virq_range(virq_base, 8)
(把 `virq_base+i` 记录到 `ipi_desc[i]`;`request_percpu_irq(..., handle_IPI)`;启用 boot CPU 的 IPI)
→ irq_set_chained_handler(imsic_parent_irq, imsic_handle_irq)
(把 IMSIC 顶层处理函数挂到父中断线上)
→ cpuhp_setup_state(..., imsic_starting_cpu, imsic_dying_cpu)
(注册 CPU 热插拔回调)
运行期:IPI 发送链
上层触发(如:arch_smp_send_reschedule / arch_send_call_function_* / tick_broadcast / smp_send_stop)
→ send_ipi_mask(mask, op)
→ __ipi_send_mask(ipi_desc[op], mask)
→ irq_data->chip = &ipi_mux_chip
· 若有 .ipi_send_mask:ipi_mux_chip.ipi_send_mask(data, mask)
(按 mask 置每 CPU 的待处理位,随后逐核发“门铃”)
→ ipi_mux_send(cpu) == imsic_ipi_send(cpu)
→ writel_relaxed(IMSIC_IPI_ID, per-cpu local->msi_va)
(对目标 CPU 的 IMSIC Interrupt File 写“固定本地 ID”(常为 1),触发 SEI)
运行期:IPI 接收链
IMSIC 硬件触发 S-mode External Interrupt(SEI)
→ chained handler: imsic_handle_irq()
(CSR 读/清 TOPEI 拿本地 ID;若等于 IMSIC_IPI_ID → 走软件解复用)
→ ipi_mux_process()
(从 per-CPU 待处理位图取出“哪一类 IPI = op(0..7)”;投递 virq = ipi_virq_base + op)
→ 进入通用层 → handle_IPI(virq)
(op = virq - ipi_virq_base;switch 分发)
· 0 IPI_RESCHEDULE → scheduler_ipi()
· 1 IPI_CALL_FUNC → generic_smp_call_function_interrupt()
· 2 IPI_CPU_STOP → ipi_stop()
· 3 IPI_CPU_CRASH_STOP → ipi_cpu_crash_stop(cpu, regs)
· 4 IPI_IRQ_WORK → irq_work_run()
· 5 IPI_TIMER → tick_receive_broadcast()
· 6 IPI_CPU_BACKTRACE → nmi_cpu_backtrace(get_irq_regs())
· 7 IPI_KGDB_ROUNDUP → kgdb_nmicallback(cpu, get_irq_regs())
总结
完结撒花!!!
更多推荐
所有评论(0)