03 · 关键寄存器详解
APLIC 寄存器按功能分成几大区。日常配置中断只用到其中一小部分,其余是版本/安全/诊断寄存器。先看"必须重点关注的",再附完整映射表。
基地址:APLIC1 =
0xF0A00000,APLIC2 =0xF0A20000每个 APLIC 实例都有独立的 M/S0/S1 三个域,所以同一个寄存器在三个域各有三份。
3.1 必须重点关注的寄存器(Top 8)
① APLIC_CTL(偏移 0x0020,读/写,复位 0x0000_0000)
全局控制寄存器,初始化时配一次即可。
|---|-----------|----------------------------------------------------|
| 位 | 名称 | 说明 |
| 7 | PTY_M | 优先级仲裁模式:0=基于域优先级(默认);1=基于 Hart 公平轮询 |
| 1 | E_CPC | 使能 CPC 接口(基于 master ID 的寄存器访问保护):0=禁用(默认);1=启用 |
② APLIC_M/S0/S1_DOMAIN_CFG(偏移 0x8000 / 0xC000 / 0x10000,复位 0x8000_0004)
每个域的"总开关"。
|---|--------|--------------------------------|
| 位 | 名称 | 说明 |
| 8 | IE | 该域所有中断的全局使能 :1=使能,0=关闭 |
| 2 | DM | 中断传递模式:1=MSI(本芯片固定) |
| 0 | BE | 大小端访问:0=小端,1=大端 |
代码里初始化时先写 0 关掉所有域,配完 CTL 后统一写
IE=1使能。
③ APLIC_M/S0/S1_SOURCECFG_N(偏移 0x8000 + N*4,每个源一个)
配置中断源的核心寄存器,N 为中断源编号(APLIC1: 11023,APLIC2: 1256)。
|-----|--------|----------------------------------------|
| 位 | 名称 | 说明 |
| 10 | D | 委托位(只有 M 域有 ,S0/S1 域无此位):1=委托给子域 |
| 2:0 | SM | 见下表 |
SM 字段含义(D=0 时,设置触发方式):
|------|----------------------|
| SM 值 | 含义 |
| 0x0 | 中断无效(inactive) |
| 0x1 | 中断卸载(detached,不接任何域) |
| 0x4 | 上升沿触发(rising edge) |
| 0x5 | 下降沿触发(falling edge) |
| 0x6 | 高电平触发(high level) |
| 0x7 | 低电平触发(low level) |
SM 字段含义(D=1 时,设置委托目标):
|------|------------------------|
| SM 值 | 含义 |
| 0x0 | 委托到 Supervisor0 Domain |
| 0x1 | 委托到 Supervisor1 Domain |
⚠️ 手册位域说明里一句写"当 D 为 1 时,SM 比特位无效",另一句又写"D=1 时 SM 用于设置委托目标(0→S0、1→S1)",前后矛盾 。以代码为准:D=1 时 SM 的低位才是委托目标 (代码宏
APLIC_DELEGATE_M_TO_S0=0、APLIC_DELEGATE_M_TO_S1=1)。⚠️ 手册寄存器图把 SM 标成
SM[1:0](2 bit),但取值有 0x4~0x7,实为 3 bit 。以代码aplic_reg_riscv.h为准。
"委托"到底是什么意思?(结合 01 篇 1.4)
- 委托是单向 的:只有 M 域 的
SOURCECFG有 D 位,所以只能 M → S0 或 M → S1 。S0/S1 域的SOURCECFG没有 D 位,不能继续往下委托。
- 一个源被 M 域
D=1委托后,它就不再在 M 域产生中断 (M 域的 pending 不再置位),而是流转到指定的子域(S0 或 S1),由该子域的SOURCECFG.SM(触发方式)+TARGET(目标)+SETIENUM(使能)真正完成配置。
- 换句话说:S0/S1 域能处理的中断,其源都必须先在 M 域被委托下来。M 域是"总闸",由最高特权级的 M 软件决定每个源归谁。
④ APLIC_M/S0/S1_TARGET_N(偏移 0xB000 + N*4)
指定该中断源"投递给谁、以什么 EIID",也是配置中断的核心。
|-------|----------------|---------------------------------|
| 位 | 名称 | 说明 |
| 20:18 | Hart_Index | 目标 hart(有效 03,对应 Core0Core3) |
| 15:13 | Context | DMSI 上下文(Guest Index),普通场景填 0 |
| 7:0 | EIID | 投递的外部中断标识,同时决定该中断的优先级(越小越高) |
代码中只用到
HART_INDEX(3 bit)和EIID(6 bit,值 1~63)两个字段,Context 填 0。每个源都能投给任意 hart 吗? 能。每个源都有自己独立的
TARGET_N,Hart_Index由软件写 0~3,APLIC 机制上不把源固定绑定到某个核(手册第 7.3.7.2 节的中断源表也只标注"所属 APLIC"和"内部中断号",没有"绑定核"字段)。实际使用只需满足两点:① 目标 hart 处于可接收 MSI 的状态(有 credit、未掉电);② 该 hart 上已正确配置了对应 EIID 的核内使能位和向量表条目。
⑤ 挂起位组 SETIPx / SETIPNUM / CLRIPx / CLRIPNUM
|----------------------------------------|-----------------------|
| 寄存器 | 作用 |
| SETIPx(0x9C00 + x*4,APLIC1 x=0~31) | 位图方式置挂起(1 bit = 1 个源) |
| SETIPNUM(0x9CDC) | 写中断编号,置单个挂起位 |
| CLRIPx(0x9D00 + x*4) | 位图方式清挂起 |
| CLRIPNUM(0x9DDC) | 写中断编号,清单个挂起位 |
软件可用
SETIPNUM模拟一次"软件中断"。
⑥ 使能位组 SETIEx / SETIENUM / CLRIEx / CLRIENUM
|-------------------------|-----------------|
| 寄存器 | 作用 |
| SETIEx(0x9E00 + x*4) | 位图方式使能中断 |
| SETIENUM(0x9EDC) | 写中断编号,使能单个中断源 |
| CLRIEx(0x9F00 + x*4) | 位图方式关闭使能 |
| CLRIENUM(0x9FDC) | 写中断编号,关闭单个中断源使能 |
代码初始化时用
SETIENUM = 源编号使能;Suspend/Resume时用CLRIENUM/SETIENUM。
⑦ APLIC_M/S0/S1_GENMSI(偏移 0xB000)
软件主动生成一条 MSI(模拟向某个 hart 发中断)。
|-------|------------|-----------------|
| 位 | 名称 | 说明 |
| 20:18 | Hart_Index | 目标 hart |
| 15:13 | Context | Guest Index |
| 12 | BUSY | 写寄存器置 1,发送完成清 0 |
| 7:0 | EIID | 要发送的 EIID |
⑧ 安全寄存器块(偏移 0x4000 起,功能安全用)
|----------------|--------|-------------------------------------------------|
| 寄存器 | 偏移 | 说明 |
| SFTY_PASSWD | 0x4000 | 安全寄存器写保护口令 |
| SFTY_CTRL | 0x4004 | 锁步使能 LSE 等(代码初始化用 APLIC_SFTY_LOCKSTEP_ENABLE) |
| SFTY_DIAG | 0x4008 | 诊断错误注入控制 |
| SFTY_BUILD | 0x400C | 安全版本信息(只读) |
| SFTY_ERR_STA | 0x4040 | 安全错误状态(只读,粘性) |
| SFTY_LAT_ERR | 0x4044 | 潜在错误状态(只读) |
3.2 完整寄存器映射表(APLIC1 为例)
|----------------|----------------------|-----------------------------------------|-------------|
| 偏移 (Hex) | 寄存器 | 说明 | 复位值 |
| 0x0000 | APLIC_BUILD_1 | 版本(只读) | 0x0000_0001 |
| 0x0004 | APLIC_BUILD_2 | 能力:ND/MSTY/DCE/SFTE/CPCE/INTTP/NUM_IRQS | 0x03FF_10D2 |
| 0x0008 | APLIC_BUILD_3 | 能力:NUM_EIID/NUM_CNTXT/NUM_HART | 0x0020_4100 |
| 0x0020 | APLIC_CTL | PTY_M / E_CPC | 0x0000_0000 |
| 0x0030 | APLIC_NOC_ERR | NoC 接口错误状态 | 0x0000_0000 |
| 0x0060 | APLIC_DOMAIN_TDM | 域时间分配选择 | 0x0000_0000 |
| 0x0070 | APLIC_OVRRUN | 溢出状态(读/清) | 0x0000_0000 |
| 0x4000~0x4044 | 安全寄存器块 | 见 3.1 ⑧ | --- |
| M 域 | | | |
| 0x8000 | M_DOMAIN_CFG | 机器域配置 | 0x8000_0004 |
| 0x8000+N*4 | M_SOURCECFG_N | 源 N 配置(N=1~1023) | 0x0000_0000 |
| 0x9C00~ | M_SETIP0..31 | 置挂起位图 | 0x0000_0000 |
| 0x9CDC | M_SETIPNUM | 置挂起(编号) | 0x0000_0000 |
| 0x9D00~ | M_CLRIP0..31 | 清挂起位图 | 0x0000_0000 |
| 0x9DDC | M_CLRIPNUM | 清挂起(编号) | 0x0000_0000 |
| 0x9E00~ | M_SETIE0..31 | 使能位图 | 0x0000_0000 |
| 0x9EDC | M_SETIENUM | 使能(编号) | 0x0000_0000 |
| 0x9F00~ | M_CLRIE0..31 | 关使能位图 | 0x0000_0000 |
| 0x9FDC | M_CLRIENUM | 关使能(编号) | 0x0000_0000 |
| 0xB000 | M_GENMSI | 生成 MSI | 0x0000_0000 |
| 0xB000+N*4 | M_TARGET_N | 目标(N=1~1023) | 0x0000_0000 |
| S0 域 | (同 M 域结构,偏移 +0x4000) | | |
| 0xC000 | S0_DOMAIN_CFG | 监管者域0 配置 | 0x8000_0004 |
| 0xF000 | S0_GENMSI | 生成 MSI | 0x0000_0000 |
| S1 域 | (同 M 域结构,偏移 +0x8000) | | |
| 0x10000 | S1_DOMAIN_CFG | 监管者域1 配置 | 0x8000_0004 |
| 0x13000 | S1_GENMSI | 生成 MSI | 0x0000_0000 |
S0/S1 域的 SOURCECFG / SETIP / CLRIP / SETIE / CLRIE / TARGET 与 M 域同名同结构,仅偏移整体后移 0x4000 / 0x8000。完整三域结构见代码
aplic_reg_riscv.h的struct APLIC_tag。
3.3 寄存器结构在代码中的对应
寄存器定义文件:Source/mcal/eclipse/plugins/Base_ARCH3DXXM10I0RX/Hardware/include/module_riscv/aplic_reg_riscv.h
```c
struct APLIC_tag {
APLIC_BUILD_1_tag BUILD_1; /* 0x0000 */
...
APLIC_CTL_tag CTL; /* 0x0020 */
...
struct { /* Machine Domain 0x8000-0xBFFF */
APLIC_DOMAIN_CFG_tag DOMAIN_CFG; /* 0x8000 */
APLIC_SOURCECFG_N_tag SOURCECFG[1023]; /* 0x8004+... */
APLIC_SETIP_X_tag SETIP[17]; /* 0x9C00 */
APLIC_SETIPNUM_tag SETIPNUM; /* 0x9CDC */
APLIC_CLRIP_X_tag CLRIP[17]; /* 0x9D00 */
...
APLIC_GENMSI_tag GENMSI; /* 0xB000 */
APLIC_TARGET_N_tag TARGET[1023]; /* 0xB004+... */
} M_DOMAIN;
struct { /* S0 Domain 0xC000-0xFFFF */ ... } S0_DOMAIN;
struct { /* S1 Domain 0x10000-0x13FFF */ ... } S1_DOMAIN;
};
```
⚠️ 代码
SOURCECFG[1023]和TARGET[1023]是"上限 1023",但 4046S 的 APLIC2 实际只有路源(APLIC1 才是 1023 路),配置时注意不要越界。
04 · 代码配置与常用方式
本篇基于 AUTOSAR 4.4.0 MCAL 的 Irq 模块 (Source/mcal/eclipse/plugins/Irq_ARCH3DXXM10I0RX/),讲清楚"怎么配一个中断、初始化流程长什么样、有哪几种常用方式"。
4.1 代码文件地图
|---------------------------------------------------|------------------------------------|
| 文件 | 作用 |
| include/Irq.h | Irq 模块头:API、配置结构体、APLIC 实例/域索引、锁步宏 |
| source/Irq.c | APLIC 驱动实现:初始化、源配置、Suspend/Resume |
| source/intc_lld.c / include/intc_lld.h | 老式 INTC(PPC 风格)驱动(嵌套兼容用) |
| Hardware/include/module_riscv/aplic_reg_riscv.h | APLIC 寄存器结构体定义 |
| Hardware/include/module_riscv/intc_reg_riscv.h | 老式 INTC 寄存器定义 |
| config/Irq_APLIC_ccrc4045s.xdm | APLIC 配置项定义(EB tresos) |
| mcal_cfg/CCRC4045S/source/Irq_Cfg.c | 实际生成的中断配置(源→EIID→handler 映射) |
| common/vector_riscv/major_interrupt.c | 外部中断向量入口(读 topei + 跳转) |
4.2 配置一个中断需要哪几个信息
每个中断源在配置里就是一个 Irq_SourceListConfigType 结构体,需要 4 个字段:
|-----------------|--------------------|--------------------------------|
| 字段 | 取值 | 说明 |
| IrqEiid | M 模式 163,S 模式 1255 | 决定优先级,同一核内不能重复 |
| IrqSourceId | 1~1023 | 硬件中断源号,全芯片唯一,对应某个外设的一路中断请求 |
| IrqSourceMode | 见下 | 触发方式 |
| IrqHandler | 函数名 | 中断服务函数 |
IrqSourceId不是"引脚编号"。它是 APLIC 的中断源编号 ,对应的是"某外设产生的某一路中断请求"(例如手册第 7.3.7.2 节的中断源表里,源 256 对应 PIT0 的 CH0 中断请求,源 990 对应 ADC01 的 GRP0)。一个外设往往有多路中断源(PIT 有多个通道、ADC 有多个组),每路各占一个源号,这些源号是芯片设计时固定分配好的,不是可自由接到任意引脚的。
IrqSourceMode 枚举值:
```c
typedef enum {
IRQ_SM_INACTIVE = 0x0, /* 无效 */
IRQ_SM_DETACHED = 0x1, /* 卸载 */
IRQ_SM_RISING_EDGE = 0x4, /* 上升沿 */
IRQ_SM_FALLING_EDGE = 0x5, /* 下降沿 */
IRQ_SM_HIGH_LEVEL = 0x6, /* 高电平 */
IRQ_SM_LOW_LEVEL = 0x7 /* 低电平 */
} Irq_SourceModeType;
```
触发方式到底起什么作用? 它告诉 APLIC:这个外设的中断信号是什么形态 (电平还是边沿),APLIC 据此去采样并锁存 pending 位。配置必须与外设实际输出的中断信号形态匹配,否则会漏中断或反复触发:
|-----------|----------------------------------------|---------------------------------------------|
| 触发方式 | 适合的信号 | 行为 |
| 高电平 / 低电平 | 电平型中断(外设置标志位后,中断线一直保持有效电平,直到软件清标志) | 电平有效期间持续算"有中断";软件清 pending 后若电平仍有效会再次触发 |
| 上升沿 / 下降沿 | 脉冲/边沿型中断(一个短暂脉冲表示事件,脉冲很快消失) | 检测到跳变沿后锁存 pending,脉冲消失后 pending 仍保留,直到软件清 |
以 PIT 为例:PIT 计数到 0 时把中断标志位 TIF 置 1,这个标志保持高电平直到软件写 1 清除 (手册 40.4.2),所以 PIT 是电平型 中断。因此代码示例里 PIT 配的是 IRQ_SM_HIGH_LEVEL(高电平触发)------这是正确匹配。
如果给 PIT 配成"上升沿触发"会怎样? 边沿触发只认 0→1 的跳变瞬间:
- 若 APLIC 使能该源时
TIF已经是 1(比如中断早就产生了),就采不到这个上升沿,这次中断直接丢失;
- 之后只有等标志被清成 0 再重新置 1,才会产生新的跳变沿触发。
所以电平型中断配成边沿触发是错误的(漏中断)。反过来,短暂脉冲型中断配成电平触发也可能漏(脉冲太短、电平期间没被采样到)。
上升沿和下降沿响应时间不一样吗? 不会。边沿/电平的响应延迟都取决于 APLIC 的采样同步时钟(几个时钟周期内识别),不取决于沿的方向 。上升沿 vs 下降沿只是选择"关注哪个跳变极性",用来匹配外设信号的极性(比如低有效的中断线用下降沿/低电平)。两者的真正区别是语义:边沿 = 事件(脉冲过后仍记住),电平 = 状态(持续有效),而不是"谁更快"。
实际配置示例(Irq_Cfg.c)
```c
Irq_SourceListConfigType Irq_Core0MmodeSrcListCfgArray[] = {
{ .IrqEiid = EIID_1, .IrqSourceId = 256, .IrqSourceMode = IRQ_SM_HIGH_LEVEL, .IrqHandler = &PIT0_CH0_ISR },
{ .IrqEiid = EIID_52, .IrqSourceId = 990, .IrqSourceMode = IRQ_SM_HIGH_LEVEL, .IrqHandler = &ADC01_GRP0 },
{ .IrqEiid = EIID_53, .IrqSourceId = 992, .IrqSourceMode = IRQ_SM_HIGH_LEVEL, .IrqHandler = &ADC01_GRP2 },
...
};
```
含义:Core0 上,PIT0 通道 0 的中断源(硬件源 256)被赋予 EIID=1(最高优先级),高电平触发,ISR 是 PIT0_CH0_ISR。
4.3 初始化流程(Irq_Init → 内部逐层配置)
Irq_Init() 调用链:Irq_Init → Irq_sInit → Irq_sCoreModeInit → Irq_sInterruptSourceInit。
硬件实例初始化(主核执行)
- 关掉 M/S0/S1 三个域的全局使能(
DOMAIN_CFG = 0)。
- 配
APLIC_CTL:E_CPC(默认关)+PTY_M(默认域优先级)。
- 配
APLIC_SFTY_CTRL:锁步使能。
- 重新使能三个域(
DOMAIN_CFG.IE = 1)。
每个中断源的初始化(Irq_sInterruptSourceInit)
对每个配置的中断源,按以下顺序操作:
```c
/* 1. 设置向量表:把 ISR 地址填到 m_AplicIsrVectorTable[core][eiid] */
m_AplicIsrVectorTable[CoreId][eiidIdx] = (uint32)IrqSrcListCfgPtr->IrqHandler;
/* 2. 在核内使能该 EIID(写核内 IMSIC 的使能位) */
Hart_M_ExtInterruptEnable(eiidIdx);
/* 3. 配置源触发方式(SOURCECFG.SM) */
APLIC1_PTR->M_DOMAIN.SOURCECFG[IrqSrcIdIdx - 1].R = sourceCfgVal;
/* 4. 配置目标(TARGET:HART_INDEX + EIID) */
APLIC1_PTR->M_DOMAIN.TARGET[IrqSrcIdIdx - 1].R = targetCfgVal;
/* 5. 使能该中断源 */
APLIC1_PTR->M_DOMAIN.SETIENUM.R = IrqSrcIdIdx;
```
关键代码片段(Irq.c):
```c
sourceCfgVal = (uint32)((uint32)IrqSrcListCfgPtr->IrqSourceMode & APLIC_SOURCECFG_N_SM_MASK);
targetCfgVal = APLIC_TARGET_N_HART_INDEX(CoreId) | (eiidIdx & APLIC_TARGET_N_EIID_MASK);
...
if (IrqSrcIdIdx <= 512u) { /* 源 1~512 → APLIC1 */
APLIC1_PTR->M_DOMAIN.SOURCECFG[IrqSrcIdIdx - 1].R = sourceCfgVal;
APLIC1_PTR->M_DOMAIN.TARGET[IrqSrcIdIdx - 1].R = targetCfgVal;
APLIC1_PTR->M_DOMAIN.SETIENUM.R = IrqSrcIdIdx;
} else if (IrqSrcIdIdx < 1024u) { /* 源 513~1023 → APLIC2 */
APLIC2_PTR->M_DOMAIN.SOURCECFG[IrqSrcIdIdx - 512 - 1].R = sourceCfgVal;
APLIC2_PTR->M_DOMAIN.TARGET[IrqSrcIdIdx - 512 - 1].R = targetCfgVal;
APLIC2_PTR->M_DOMAIN.SETIENUM.R = IrqSrcIdIdx - 512u;
}
```
⚠️ 这里的 512 分界是代码(面向 4045S/4086S)写死的;4046S 手册是 APLIC1=1023 / APLIC2=256,需核对是否适配。
Suspend / Resume
```c
void Irq_SuspendInterruptSource(Irq_InterruptSrcIdType SourceId); /* 关闭某中断源 */
void Irq_ResumeInterruptSource (Irq_InterruptSrcIdType SourceId); /* 恢复某中断源 */
```
内部对应写 CLRIENUM(关)和 SETIENUM(开),并维护 Irq_InterruptSourceStatus[] 状态数组。
4.4 硬件实例配置项
```c
Irq_HwInstConfigType Irq_HwInstConfig = {
.IrqMasterCore = IRQ_MASTER_COREID, /* 主核:负责配 APLIC 全局寄存器 */
.IrqCpcEn = FALSE, /* CPC 访问保护:默认关 */
.IrqPriorityMode = IRQ_PRIORITY_MODE_DOMAIN_PRIORITY, /* 域优先级仲裁(默认) */
.IrqLockstepEn = TRUE /* 锁步比较:默认开 */
};
```
|-------------------|---------------------------------------------------|
| 配置项 | 含义 |
| IrqCpcEn | 是否启用 CPC(master ID 访问保护),普通场景 FALSE |
| IrqPriorityMode | DOMAIN_PRIORITY(域优先)或 HART_FAIR_RR(hart 公平轮询) |
| IrqLockstepEn | 是否开启锁步比较(功能安全) |
4.5 三种常用配置方式
方式一:M 模式直接处理(最常用,默认)
中断直接归属 M 域 ,ISR 在 M 模式运行。代码里 IrqTargetCoreMode = HART_M_MODE。
```c
/* 源 → M 域 SOURCECFG 直接配触发方式,TARGET 配 hart+EIID,SETIENUM 使能 */
APLIC1_PTR->M_DOMAIN.SOURCECFG[src-1].R = sourceCfgVal;
APLIC1_PTR->M_DOMAIN.TARGET[src-1].R = targetCfgVal;
APLIC1_PTR->M_DOMAIN.SETIENUM.R = src;
```
方式二:S 模式处理(需要委托 M→S0)
中断先归 M 域,再委托给 S0 域 ,让 S 模式(通常是 OS)接管。代码里 IrqTargetCoreMode = HART_S_MODE。
```c
/* M 域:置 D 位 + SM=0,表示"委托到 S0" */
APLIC1_PTR->M_DOMAIN.SOURCECFG[src-1].R = APLIC_SOURCECFG_N_D_MASK | APLIC_DELEGATE_M_TO_S0;
/* S0 域:真正配置触发方式、目标、使能 */
APLIC1_PTR->S0_DOMAIN.SOURCECFG[src-1].R = sourceCfgVal;
APLIC1_PTR->S0_DOMAIN.TARGET[src-1].R = targetCfgVal;
APLIC1_PTR->S0_DOMAIN.SETIENUM.R = src;
```
委托本质:M 域里把该源标记为"委派给子域"(D=1,SM 指定 S0 或 S1),真正的触发/目标/使能配置在 S0/S1 域完成。
委托后它还"不可屏蔽"吗? 不再"不可屏蔽"。手册里"不可屏蔽"指的是------对 S 模式软件而言,M 域的中断请求是不可屏蔽的 (因为 M 特权级更高)。一旦源被
D=1委托到 S0/S1,它就变成了普通的 S 模式外部中断(SEI) ,受 S 模式的sstatus.SIE、sie、eithreshold等控制,可以像普通中断一样被 S 模式屏蔽/降级。所以"委托"= 放弃 M 级的不可屏蔽属性,把处理权下放给 S 模式。
方式三:嵌套老式 INTC(兼容 NXP 软件)
芯片为了兼容 NXP PPC 老代码,在 APLIC 里"嵌套"了 10 个老式 INTC 实例(INTC_0~INTC_9)。这些 INTC 实例作为 APLIC 的中断源(源号 1、7、13、19、25、31、37、43、49、55)接入,各自有一个 handler:
```c
{ .IrqEiid = EIID_54, .IrqSourceId = 1, .IrqHandler = &Irq_AplicIntc0_Handler },
{ .IrqEiid = EIID_55, .IrqSourceId = 7, .IrqHandler = &Irq_AplicIntc1_Handler },
...
```
老式 INTC 的初始化走 Irq_AplicIntcInit(),内部写 PSR(优先级 + PRC_SELN)、CPR,响应时读 IACKR 得向量、写 EOIR 结束------这就是 NXP 5777M 那套机制。
代码注释明确说:"The design of nesting the INTC module within APLIC will be removed in the next design version."(下个版本会移除嵌套 INTC),新代码应直接走 APLIC(方式一/二)。
4.6 向量表与 ISR 的衔接
ISR 函数会被填入按"核 × EIID"组织的向量表:
```c
extern volatile uint32 m_AplicIsrVectorTable[RESOURCE_CHIP_CORE_NUM][HART_M_EIID_MAX];
```
外部中断入口(major_interrupt.c 的 MI11_Handler):
```asm
csrrw t1, mtopei, t0 ; 读 M 模式顶部 EIID,同时写 0
ANDI t1, t1, 0x00ff ; 取低 8 位(EIID)
slli t1, t1, 0x2 ; ×4(32 位表项)
la t2, m_AplicIsrVectorTable
ADD t2, t2, t4 ; + hart 偏移(每核 64×4=256 字节)
ADD t2, t2, t1 ; + EIID 偏移
lw t3, 0(t2) ; 取 ISR 地址
jalr t3 ; 跳转执行
```
05 · 与 NXP MPC5777M 的 INTC 模块对比
NXP MPC5777M 是 Power Architecture(e200z7 双核)MCU,其中断控制器是经典的 PPC INTC 。CCRC4046S 是 RISC-V(ARC-V 内核)MCU,中断用 RISC-V AIA(APLIC + IMSIC + CLINT)。两者理念完全不同,本篇逐项对比,并给出迁移要点。
5.1 一句话总结差异
|----------|-------------------------------|-------------------------------------------------|
| 维度 | NXP MPC5777M (INTC) | RISC-V (APLIC) |
| 架构 | Power Architecture | RISC-V AIA |
| 控制器构成 | 单个 INTC(1024 源) | APLIC1+APLIC2 级联 + 每核 IMSIC + CLINT |
| 优先级配置 | 每源独立 PRI 字段(PSR.PRIN) | 无独立优先级寄存器,EIID 越小优先级越高 |
| 中断响应握手 | 读 IACKR 拿向量 → 处理 → 写 EOIR | 读 mtopei/stopei 拿 EIID → 处理 → 写 0 到 topei |
| 向量机制 | 软件向量 / 硬件向量(IVPR + IACKR) | 向量表按 hart × EIID 索引 |
| 抢占 | CPR(当前优先级寄存器)+ 阈值 | eithreshold + 硬件阈值栈 |
| 投递方式 | 电平/引脚直连 | DMSI 消息总线投递 |
| 域(特权级隔离) | 无 | M / S0 / S1 三域 + 委托机制 |
| 功能安全 | 独立安全机制 | ASIL-D(锁步/ECC/EDC/E2E) |
5.2 逐项详细对比
5.2.1 中断源与规模
- MPC5777M INTC:单一 INTC,支持约 1024 个中断源(软件向量模式),每核一套 CPR/IACKR/EOIR。
- CCRC4046S APLIC:两个 APLIC 级联(1023 + 256 = 1279 路),投递给 4 个 hart(Core0~Core3)。
5.2.2 优先级机制(差异最大)
MPC5777M :每个中断源的 PSR.PRIN(6 bit,0~15 实际可用)单独配优先级,越大越高;CPR 记录当前服务中断的优先级,用于抢占判断。
CCRC4046S :没有"优先级寄存器"这个字段 。优先级直接等于 EIID(编号),越小越高。给中断"更高优先级"的唯一办法是给它分配更小的 EIID。
|-------|----------------------|----------------------|
| | MPC5777M | CCRC4046S |
| 优先级来源 | PSR.PRIN(0~15,大者优先) | EIID(1~63/255,小者优先) |
| 优先级范围 | 16 级(实际常 0~15) | M 63 级 / S 255 级 |
| 抢占依据 | CPR + 阈值 | eithreshold + 硬件阈值栈 |
5.2.3 中断响应握手
MPC5777M(三段式握手):
```c
// 1. 读 IACKR 得到中断向量号(硬件向量模式)或读当前向量
// 2. 执行 ISR
// 3. 写 EOIR 结束中断,恢复 CPR
```
CCRC4046S(两段式):
```c
// 1. 读 mtopei/stopei CSR,得到 EIID(读取本身可同时"完成")
// 2. 执行 ISR(结束时可再向 topei 写 0,弹阈值栈)
```
关键区别:NXP 用内存映射寄存器 IACKR/EOIR 做握手;CCRC4046 用CSR(mtopei/stopei),且"读即认领"。
5.2.4 投递与拓扑
- MPC5777M:外设中断引脚直接汇总到 INTC,INTC 内部仲裁后拉高核的中断请求线。
- CCRC4046S :APLIC 采样后打包成 DMSI 消息,经 DMSI 总线投递到每个核的 IMSIC 中断文件,再由核响应。多了一级"消息化"抽象。
5.2.5 特权级/域隔离
- MPC5777M:无域概念(单核/双核对称,各自处理)。
- CCRC4046S:M/S0/S1 三域,可把中断委托给 S 模式(OS)处理,天然支持 M/S 特权隔离和虚拟化(VS/guest)。
5.2.6 向量表
- MPC5777M :
IVPR指向向量基址,硬件向量模式下由 INTC 直接算出向量地址;软件向量模式由IACKR.INTVEC索引。
- CCRC4046S :
m_AplicIsrVectorTable[hart][EIID],外部中断入口读 topei 得 EIID 后直接查表跳转。
5.3 寄存器对照表(迁移速查)
|---------------------|----------|-------------------------------------|
| NXP 5777M INTC 寄存器 | 作用 | CCRC4046S 对应 |
| INTC_BCR | 硬件向量模式开关 | (无直接对应,由内核配置决定向量模式) |
| INTC_MPROT | 主保护 | APLIC_CTL.E_CPC(CPC 访问保护) |
| INTC_CPR | 当前优先级 | 核内 eithreshold + 硬件阈值栈 |
| INTC_IACKR | 读中断向量 | 读 mtopei/stopei CSR(得 EIID) |
| INTC_EOIR | 结束中断 | 写 0 到 mtopei/stopei |
| INTC_PSR.PRIN | 配置优先级 | 无对应(EIID 即优先级) |
| INTC_PSR.PRC_SELN | 选择核 | APLIC_TARGET.Hart_Index |
| INTC_SSCIR | 软件触发/清除 | APLIC_SETIPNUM / APLIC_CLRIPNUM |
| (无) | --- | APLIC_SOURCECFG(触发方式 + 委托) |
| (无) | --- | APLIC_TARGET.EIID(目标编号 + 优先级) |
| (无) | --- | APLIC_SETIENUM/CLRIENUM(源使能开关) |
5.4 迁移要点清单
- 改优先级配置方式 :原来"给源设 PRI=15",现在改成"给源分配一个较小的 EIID"。注意 EIID 越小优先级越高(与 NXP 的 PRI 越大越高相反)。
- 改中断结束方式:把"写 EOIR"改成"写 0 到 topei"。且 RISC-V AIA 里读 topei 时往往已经认领了中断。
- 改向量表组织 :原来是单一向量表 + IACKR 索引;现在是
hart × EIID二维表,且每个 EIID 独立向量。
- 改投递认知:不用再关心"中断引脚直连 INTC",理解"外设 → APLIC → DMSI → IMSIC → 核"这条消息链。
- 利用域的隔离:如果需要把中断交给 OS(S 模式),用委托机制(M 域 D 位 + S0 域配置),这是 NXP INTC 没有的能力。
- 短期兼容 :芯片在 APLIC 里嵌套了 10 个老式 INTC 实例(走
Irq_AplicIntcInit+ IACKR/EOIR),可以让老代码原样跑 ,但该设计下版本会移除,新代码应直接用 APLIC。
5.5 对比结论
|--------|----------------------------------------|
| 结论 | 说明 |
| 更灵活 | APLIC 支持 1279 路源、三域、委托、虚拟化,远超 NXP INTC |
| 更"反直觉" | 优先级"隐含"在编号里(越小越高),没有显式优先级寄存器 |
| 投递更抽象 | 消息化(DMSI)替代了电平直连,理解成本更高 |
| 迁移有桥 | 嵌套 INTC 提供兼容层,但只是过渡方案 |