前言

在 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 会绑定 domainopsipi_mux_domain_opsirq_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_descsirq_domain_alloc_irq_data 分别设置 descdesc 对应的 data 部分。 irq_domain_alloc_irqs_hierarchy 则会调用上文注册的 domain opsalloc 函数做初始化。
具体绑定函数如下:

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;
}

即,我们会绑定 chipipi_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

这个函数会把上文绑定好 chipdesc 和我们发送 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 数据结构的 IMSICInterrupt 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())

总结

完结撒花!!!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐