当前位置: 首页 > ai >正文

AIA中断控制器IPI的Linux内核实现

文章目录

  • 前言
  • IPI 类型
  • IPI 发送
  • IMSIC 早期初始化
    • ipi_mux_create
      • irq_domain_alloc_irqs
    • riscv_ipi_set_virq_range
  • IMSIC 的 MMIO 处理
  • 函数调用链总结
    • 启动期(DT/early)初始化链
    • 运行期:IPI 发送链
    • 运行期:IPI 接收链
  • 总结


前言

在 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())

总结

完结撒花!!!

http://www.xdnf.cn/news/19320.html

相关文章:

  • Qt-Advanced-Docking-System: 一个基于 Qt 框架的高级停靠窗口系统
  • Spring boot注解介绍
  • Python 2025:AI代理、Rust与异步编程的新时代
  • BigDecimal账户分布式原子操作
  • IOT安全学习之IoT_Sec_Tutorial
  • 历史数据分析——寒武纪
  • Wi-Fi技术——MAC特性
  • 【人工智能99问】Qwen3中的QK归一化是什么?(34/99)
  • LeetCode 3459.最长 V 形对角线段的长度:记忆化搜索——就一步步试
  • 备份压缩存储优化方案:提升效率与节省空间的完整指南
  • 鸿蒙开发入门:ArkTS 运算符与分支循环全解析(含实战案例 + 避坑指南)
  • ES6 面试题及详细答案 80题 (13-21)-- 数组与字符串扩展
  • Zynq开发实践(FPGA之平台免费IP)
  • GitHub Spark深度体验:是革命前夜,还是又一个“大厂玩具”?
  • 浅层与深层语义分析的NLP进化论
  • libmodbus移植
  • spi总线
  • Python 实战:内网渗透中的信息收集自动化脚本(6)
  • 【Unity3D实例-功能-切换武器】切换武器(一)动画配置
  • FPGA CIC抽取滤波器设计
  • HarmonyOS 应用开发:基于API 12及以上的新特性与实践
  • TensorFlow 面试题及详细答案 120道(81-90)-- 其他框架/工具
  • 内核Sched调度关于find_idlest_cpu选核逻辑
  • OpenCV 图像处理实战与命令行参数配置:从轮廓检测到模板匹配
  • AI 重构内容创作:从文案生成到视频剪辑,创作者该如何与 AI 协同共生?
  • 一个投骰子赌大小的游戏
  • H264几个参数说明
  • Maya基础:烘焙动画
  • 网络爬虫是自动从互联网上采集数据的程序
  • VSCode的launch.json配置文件在C++项目调试中的全面应用