API GUIDE / PLIC / API OVERVIEW

PLIC API 概述

irq api

G32S10 IRQ 接口文档

1. 文档范围

本文档定义 G32S10 SDK IRQ 框架的公开接口和推荐用法,覆盖普通 PLIC 外设 IRQ、V0 ACC/V1 非 ACC 路径、handler 分发、优先级嵌套以及 V0/V1 owner 同步。

主要公开头文件:

code/include/drivers/imsis/im_irq.h

2. 构建配置

2.1 IRQ 框架

CONFIG_DRIVER_IRQ=y

启用后编译 IRQ 框架。

2.2 自定义分发模式

CONFIG_IRQ_SIMPLE_DISPATCH=y

默认值为 n。启用后不编译内置 IRQ 注册表,sys_irq_request() 和 sys_irq_release() 返回 IM_ERR_UNSUPPORTED,应用必须自行实现 sys_irq_dispatch(uint32_t irq_id)。依赖默认注册表的中间件通常不能与该配置同时使用。

2.3 V0 ACC 模式

CONFIG_IRQ_ACC_MODE=y

ACC 是 V0 的硬件辅助中断控制路径。它不是一套软件架构,而是改变“硬件如何选择 IRQ、建立中断上下文、进入向量和返回”的底层执行模型。

该配置只依赖并作用于 BUILD_CORE_V0。V1 没有 ACC:V1 不编译 ACC 专用 IRQ handler,也不使用 ACC 的 MINTCTRL/MSP/PSP 初始化路径。

2.3.1 ACC 与普通 machine external 路径的区别

非 ACC 模式下,普通 PLIC 外部中断的入口只有一个:

PLIC pending
    ↓
RISC-V machine external trap
    ↓
sys_irq_plic_dispatch()
    ↓
软件读取 PLIC claim ID
    ↓
sys_irq_dispatch(irq_id)
    ↓
软件 complete PLIC source

V0 ACC 模式下,PLIC/ACC 硬件在产生 machine interrupt 时已经完成 source 选择和向量索引,入口变成:

PLIC/ACC source
    ↓
ACC vector slot
    ↓
IRQ_xxx_handler
    ↓
sys_irq_dispatch(IRQ_xxx)

因此 ACC 普通 IRQ handler 不会调用 sys_irq_plic_dispatch(),也不会在软件中执行普通路径的 claim/complete。代码中的注释明确规定:PLIC claim/complete 由非 ACC external IRQ 路径使用,ACC 路径直接按 source 进入 per-source handler。

这并不意味着 PLIC source 配置被绕过。ACC 模式仍然需要:

  • 配置 source priority;
  • 使能对应 PLIC source;
  • 打开 CPU 的 machine external delivery;
  • 打开设备自身的中断产生位。

在默认 SDK 分发路径下,变化只在于“source 被选中后如何进入软件”,而不是取消 PLIC 的 source enable/priority 配置。若采用后文的 ACC 直达 handler 高级用法,则还可以绕过软件注册表,但不能绕过 PLIC 和设备本身的使能配置。

2.3.2 ACC 向量表布局

ACC 相关向量从 vector table 的 IRQ 32 基址之后开始:

向量槽/IRQ 含义
IRQ 32 ACC 外部中断总入口
source 0 保留槽,不作为普通 source 使用
source 1~63 映射到普通外设 IRQ handler

本地和虚拟 IRQ 的向量槽由 SDK、OS 或 IPC 内部使用,不作为普通应用 IRQ 接口说明。

2.3.3 ACC 直接覆盖 IRQ_xxx_handler(高级用法)

V0 ACC 的每个外设向量最终都会跳转到对应的 IRQ_xxx_handler。SDK 默认实现使用弱符号提供这些入口,默认逻辑只是把向量号转换成软件 IRQ 号,再调用 sys_irq_dispatch(IRQ_xxx):

ACC vector
    ↓
IRQ_xxx_handler(SDK 默认弱定义)
    ↓
sys_irq_dispatch(IRQ_xxx)
    ↓
sys_irq_request() 注册的业务 handler

对于对中断延迟特别敏感的 V0 ACC 外设,可以在应用中提供同名的强符号,覆盖 SDK 的弱定义,直接在向量入口处理业务:

/* 仅适用于 V0 + CONFIG_IRQ_ACC_MODE */
void IRQ_UART0_handler(void)
{
    /* 在此直接读取 UART0 状态、处理业务并清除 UART0 的 pending 条件 */
}

覆盖后路径变为:

ACC vector
    ↓
应用自定义 IRQ_UART0_handler()
    ↓
直接处理 UART0

该用法的特点和约束如下:

  • 函数名必须与 ACC 向量使用的符号完全一致,例如 IRQ_UART0_handler;
  • 入口是向量入口函数,不是 irq_handler_t 回调,通常定义为 void IRQ_xxx_handler(void),不能按 sys_irq_request() 的 (irq_id, arg) 形式实现;
  • 该路径不经过 sys_irq_dispatch(),也不使用 sys_irq_request() 注册的同一 IRQ 的软件 handler,因此可以省去软件注册表查找和二次函数调用;
  • 仍然必须配置 PLIC priority、使能 PLIC source、打开 CPU machine external,以及打开设备内部中断;
  • handler 必须自行读取并清除设备的 pending 条件,否则会重复进入中断;
  • 不要在该 handler 中再次调用 sys_irq_plic_dispatch(),ACC 路径不走普通 external IRQ 的 PLIC claim/complete 软件流程;
  • 裸机 ACC 下可以直接使用这种方式;
  • FreeRTOS ACC 下,SDK 默认弱 handler 还承担 MIE 保存/恢复和 ISR nesting 维护。直接覆盖会同时覆盖这些包装逻辑,只有在自定义入口自行保留等价的上下文、嵌套和返回约束时才可使用,通常应继续使用 SDK 默认 wrapper + sys_irq_dispatch() 路径。

该高级用法只改变“向量进入业务代码”的方式,不改变 IRQ 号、PLIC source 映射和硬件使能关系。V1 和 V0 非 ACC 不使用这套 per-source ACC handler 覆盖机制。

注意:绕过中断时的软件分发,不等于可以跳过公共 API 的注册检查。默认注册表模式下,sys_irq_enable() 仍要求该 IRQ 已通过 sys_irq_request() 注册,否则返回 IM_ERR_NOT_READY。可保留注册用于资源管理,同时由同名强符号让 ACC 中断直接进入业务处理。

完整的入口定义、初始化和停用示例见后文 15.2 V0 ACC 直达 handler 高级用法模板。

2.4 IRQ owner 同步

CONFIG_IRQ_OWNER_SYNC=y

该配置依赖 G32S10 V0 IPC(核间通信)。启用后,V0/V1 通过 IPC 同步普通 PLIC IRQ 的启用状态,用于记录 IRQ 使用核并检测双核冲突。它是状态同步和冲突告警机制,不是硬件互斥锁;当前实现发现双方同时启用同一 IRQ 时只打印 warning。

3. IRQ 资源模型

3.1 软件 IRQ 和 PLIC source 映射

IRQ 表定义在 code/include/soc/g32s10/irq_table.def:

software_irq_id = plic_source_id + IRQ_PLIC_START

当前 IRQ_PLIC_START 为 0,因此普通 PLIC source ID 和软件 IRQ ID 相同。

3.2 IRQ 编号范围

范围 类型 说明
0 保留 IRQ_PLIC_START,不能注册为普通软件 IRQ
1~63 普通 PLIC IRQ UART、GPIO、DMA、GMAC、I2C 等外设

本地和虚拟 IRQ 编号由 SDK、OS 或 IPC 内部使用,不作为普通应用 IRQ 接口说明。

普通 PLIC IRQ 使用 sys_irq_request()、sys_irq_enable() 等接口。本地和虚拟 IRQ 由 SDK、OS 或 IPC 内部管理,应用通常不需要直接操作。

4. 返回码约定

IRQ 接口使用 SDK 公共返回码 error_code_t。

返回值 说明
IM_OK 操作成功
IM_ERR_INVALID_PARAM IRQ ID、handler、优先级或其他参数非法
IM_ERR_NOT_READY handler 未注册、子系统未初始化或状态未准备好
IM_ERR_UNSUPPORTED 当前配置或当前 CPU 不支持该操作
IM_ERR_ALREADY_REGISTERED 指定 IRQ 已经有 handler
其他错误码 底层 PLIC、IPC 或硬件初始化失败

5. 初始化和生命周期

5.1 sys_irq_init

error_code_t sys_irq_init(void);

功能说明:

初始化 IRQ 框架、PLIC、ACC 相关状态以及可选的 owner/IPC 服务。

参数表:

参数 类型 方向 说明
无 — — 本接口无参数

返回值表:

返回值 说明
IM_OK 接口执行成功
其他错误码 PLIC、ACC、owner 表或 IPC 初始化失败

补充说明:

  • 本接口可重复调用;实际系统通常只需全局调用一次。
  • PLIC 初始化会关闭所有 source、清零优先级并将 threshold 设为 0。
  • 应在注册设备 IRQ 之前调用;本接口不会自动注册业务 handler,也不会自动打开 CPU 全局中断。

5.2 推荐生命周期

sys_irq_init()
    ↓
sys_irq_request()
    ↓
sys_irq_set_priority()       可选
    ↓
sys_irq_enable()
    ↓
im_cpu_irq_enable_mexternal()
    ↓
设备运行
    ↓
sys_irq_disable()
    ↓
sys_irq_release()

sys_irq_release() 不会自动关闭硬件 source,释放前必须先调用 sys_irq_disable()。

6. 软件 IRQ 注册接口

6.1 irq_handler_t

typedef void (*irq_handler_t)(uint32_t irq_id, void *arg);

irq_id 是当前 IRQ 编号,arg 是注册时传入的用户上下文。handler 运行在 IRQ/trap 上下文,必须短小、非阻塞。

6.2 sys_irq_request

error_code_t sys_irq_request(uint32_t irq_id,
                            irq_handler_t handler,
                            const char *name,
                            void *arg);

功能说明:

注册软件 IRQ handler,并为映射的 PLIC source 设置框架默认优先级。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 IRQ_xxx 枚举值,不能为保留值 0
handler irq_handler_t 输入 必须非 NULL
name const char * 输入 可选调试名字,可为 NULL
arg void * 输入 传给 handler 的上下文,可为 NULL

返回值表:

返回值 说明
IM_OK 接口执行成功
IM_ERR_INVALID_PARAM 参数非法
IM_ERR_ALREADY_REGISTERED 指定 IRQ 已经有 handler
IM_ERR_UNSUPPORTED 当前配置不支持软件注册
其他错误码 底层 PLIC 优先级设置失败

补充说明:

  • 注册不等于使能;source 仍可能关闭。
  • 普通情况下默认优先级为 HAL_PLIC_PRIORITY_1;ACC + FreeRTOS 默认优先级为 HAL_PLIC_PRIORITY_3。
  • name 不会被复制,调用方须保证字符串生命周期有效。

6.3 sys_irq_release

error_code_t sys_irq_release(uint32_t irq_id);

功能说明:

清除 handler、名字和参数,并将映射的 PLIC source 优先级恢复为 0。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 已注册的 IRQ_xxx 枚举值

返回值表:

返回值 说明
IM_OK 接口执行成功
IM_ERR_INVALID_PARAM IRQ ID 非法
IM_ERR_NOT_READY 指定 IRQ 未注册
其他错误码 底层 PLIC 操作失败

补充说明:

  • 正确顺序为 sys_irq_disable(irq_id) → sys_irq_release(irq_id)。
  • 本接口不会清除设备自身 pending 状态,也不会关闭外设内部的 IRQ 产生条件。

7. 普通 PLIC IRQ 控制

7.1 sys_irq_enable

error_code_t sys_irq_enable(uint32_t irq_id);

功能说明:

打开普通 PLIC source。默认注册表模式下,要求该 IRQ 已注册 handler。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 普通 PLIC source 对应的 IRQ ID

返回值表:

返回值 说明
IM_OK source 已打开;owner sync 失败可能只记录 warning
IM_ERR_INVALID_PARAM IRQ 不是普通 PLIC source
IM_ERR_NOT_READY handler 尚未注册
其他错误码 底层 PLIC 操作失败

补充说明:

  • 本接口不打开 CPU mie.MEIE,还需要 im_cpu_irq_enable_mexternal();也不直接打开全局 mstatus.MIE。
  • V1 + IRQ_OWNER_SYNC 会更新本地 owner 表;V0 + IRQ_OWNER_SYNC 会通过 IPC 向 V1 发送 enable 通知。

7.2 sys_irq_disable

error_code_t sys_irq_disable(uint32_t irq_id);

功能说明:

关闭普通 PLIC source。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 普通 PLIC source 对应的 IRQ ID

返回值表:

返回值 说明
IM_OK source 已关闭
IM_ERR_INVALID_PARAM IRQ 不是普通 PLIC source
其他错误码 底层 PLIC 操作失败

补充说明:

  • 关闭 source 不等于清除外设 pending flag;设备 handler 或设备 HAL 仍需清硬件状态。
  • owner sync 下会同步清除当前核的 owner bit。

7.3 sys_irq_set_priority

error_code_t sys_irq_set_priority(uint32_t irq_id,
                                 hal_plic_priority_t priority);

功能说明:

设置 PLIC source 优先级。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 PLIC source 对应的 IRQ ID
priority hal_plic_priority_t 输入 HAL_PLIC_PRIORITY_0 到 HAL_PLIC_PRIORITY_7

返回值表:

返回值 说明
IM_OK 优先级设置成功
IM_ERR_INVALID_PARAM IRQ ID 或 priority 非法
其他错误码 底层 PLIC 操作失败

补充说明:

  • 数值越大优先级越高;priority 和 source enable 是两个独立控制项。

8. CPU Machine External 接口

8.1 Machine External

error_code_t im_cpu_irq_enable_mexternal(void);
error_code_t im_cpu_irq_disable_mexternal(void);

功能说明:

使能或关闭 CPU Machine External delivery,只控制 CPU mie.MEIE,不控制具体 PLIC source。

参数表:

参数 类型 方向 说明
无 — — 这两个接口均无参数

返回值表:

返回值 说明
IM_OK 操作成功
其他错误码 CPU IRQ 操作失败

补充说明:

  • 普通外部 IRQ 通常需要同时满足 sys_irq_enable(irq_id) 和 im_cpu_irq_enable_mexternal()。
  • 本接口也不直接打开全局 mstatus.MIE。

9. 分发接口

9.1 sys_irq_dispatch

error_code_t sys_irq_dispatch(uint32_t irq_id);

功能说明:

按 irq_id 查找并调用已注册的软件 handler。

参数表:

参数 类型 方向 说明
irq_id uint32_t 输入 要分发的 IRQ ID

返回值表:

返回值 说明
IM_OK handler 调用完成
IM_ERR_NOT_READY 没有注册 handler
IM_ERR_INVALID_PARAM IRQ ID 非法
IM_ERR_UNSUPPORTED CONFIG_IRQ_SIMPLE_DISPATCH=y 且应用未提供实现

补充说明:

  • 默认实现调用 handler(irq_id, s_irq_args[irq_id])。
  • CONFIG_IRQ_SIMPLE_DISPATCH=y 时,应用必须自行实现该接口。
  • handler 运行在 IRQ/trap 上下文,必须短小、非阻塞。

9.2 sys_irq_plic_dispatch

error_code_t sys_irq_plic_dispatch(void);

功能说明:

处理一次普通 PLIC machine external IRQ,并将 pending source 分发到已注册的软件 handler。

参数表:

参数 类型 方向 说明
无 — — 本接口无参数

返回值表:

返回值 说明
IM_OK 已完成一次 PLIC IRQ 处理
IM_ERR_NOT_READY 没有可处理的 pending source 或 handler 未准备好
其他错误码 PLIC claim、分发或 complete 操作失败

补充说明:

  • 该接口仅供 machine external trap/ISR 路径使用。
  • 普通 ACC 路径不调用该接口;ACC handler 直接按向量进入对应入口。
  • handler 仍必须满足 ISR 非阻塞约束。

10. 调试接口

10.1 sys_irq_dump

void sys_irq_dump(void);

功能说明:

输出已注册 IRQ 的调试状态。

参数表:

参数 类型 方向 说明
无 — — 本接口无参数

返回值表:

返回值 说明
无 本接口无返回值

补充说明:

  • 只有同时启用 CONFIG_SDK_ENABLE_DEBUG_HELPERS=y 和 CONFIG_IRQ_ENABLE_DEBUG_HELPERS=y 时才输出内容。
  • 输出包括 IRQ ID、PLIC source ID、priority、enable 状态、pending 状态和注册名字。
  • 未启用调试配置时为空操作。

11. 具体示例

11.1 普通 PLIC 外设 IRQ

#include "im_irq.h"

static void uart_irq_handler(uint32_t irq_id, void *arg)
{
    uart_device_t *dev = (uart_device_t *)arg;

    (void)irq_id;
    uart_handle_irq(dev); /* 读取并清除UART硬件状态 */
}

error_code_t uart_irq_init(uart_device_t *dev)
{
    error_code_t ret;

    ret = sys_irq_init();
    if (ret != IM_OK) {
        return ret;
    }

    ret = sys_irq_request((uint32_t)IRQ_UART0,
                         uart_irq_handler,
                         "uart0",
                         dev);
    if (ret != IM_OK) {
        return ret;
    }

    ret = sys_irq_set_priority((uint32_t)IRQ_UART0,
                              HAL_PLIC_PRIORITY_2);
    if (ret != IM_OK) {
        sys_irq_release((uint32_t)IRQ_UART0);
        return ret;
    }

    ret = sys_irq_enable((uint32_t)IRQ_UART0);
    if (ret != IM_OK) {
        sys_irq_release((uint32_t)IRQ_UART0);
        return ret;
    }

    return im_cpu_irq_enable_mexternal();
}

void uart_irq_deinit(void)
{
    sys_irq_disable((uint32_t)IRQ_UART0);
    sys_irq_release((uint32_t)IRQ_UART0);
}

实际系统通常只需全局调用一次 sys_irq_init()。外设 source、CPU MEIE 和全局中断开关是独立层次。

11.2 V0/V1 IPC owner sync

启用 CONFIG_IRQ_OWNER_SYNC 时,应用一般不需要直接操作 owner API。正常调用:

sys_irq_enable((uint32_t)IRQ_GPIOA);

V0 会发送 enable 通知,V1 更新 owner 表;V1 则直接更新本地 owner 表并处理 V0 通知。应用仍应从架构上保证一个共享外设 IRQ 只由一个核负责。

12. ISR 使用约束

所有 IRQ handler 都在 trap/ISR 上下文运行:

  • 不阻塞,不等待互斥锁、信号量或长时间轮询;
  • 不执行耗时的协议栈、文件系统或大块内存操作;
  • 先读取并清除设备硬件 pending 状态;
  • 复杂处理通过队列、事件标志或 task notification 延后到任务上下文;
  • FreeRTOS 中只使用允许在 ISR 中调用的 FromISR 接口;
  • arg 指向对象的生命周期必须覆盖 IRQ 注册到 release 的整个期间;
  • sys_irq_request() / sys_irq_release() 建议只在初始化/反初始化阶段串行调用。

13. 常见问题和注意事项

13.1 只调用 sys_irq_enable() 但收不到中断

普通 PLIC IRQ 至少需要:

handler 已注册
PLIC source 已 enable
CPU mie.MEIE 已 enable
全局 mstatus.MIE 已 enable
设备本身的 IRQ 产生位已 enable

13.2 直接 release

sys_irq_release() 不会关闭 PLIC source,必须先调用:

sys_irq_disable(irq_id);
sys_irq_release(irq_id);

13.3 IRQ handler 被重复注册

默认注册表中一个 IRQ 只能有一个 handler;重复调用 sys_irq_request() 返回 IM_ERR_ALREADY_REGISTERED。

13.4 IRQ owner 冲突

V0/V1 同时打开同一个普通 PLIC IRQ 时,owner mask 会变为 IM_IRQ_OWNER_CORE_BOTH 并打印 warning。该机制不会替应用解决设备归属问题。

14. 文件组织

code/include/drivers/imsis/im_irq.h              公共IRQ API
code/drivers/imsis/irq/im_irq.c                  注册表、生命周期和分发
code/include/drivers/g32s10/plic/im_irq_port.h   PLIC/CPU适配接口
code/drivers/g32s10/plic/im_irq_port.c           PLIC/CSR实现
code/include/soc/g32s10/irq_table.def            软件IRQ编号表
code/drivers/g32s10/plic/im_irq_owner.c          V0/V1 owner同步
code/soc/g32s10/system/interrupt/                trap、向量和ACC入口
code/soc/g32s10/system/ipc/                     IPC doorbell和消息分发

15. 推荐调用模板

15.1 默认软件分发模板

ret = sys_irq_init();
if (ret != IM_OK) {
    return ret;
}

ret = sys_irq_request(irq_id, handler, name, arg);
if (ret != IM_OK) {
    return ret;
}

ret = sys_irq_set_priority(irq_id, priority);  /* 可选 */
if (ret != IM_OK) {
    sys_irq_release(irq_id);
    return ret;
}

ret = sys_irq_enable(irq_id);
if (ret != IM_OK) {
    sys_irq_release(irq_id);
    return ret;
}

ret = im_cpu_irq_enable_mexternal();
if (ret != IM_OK) {
    sys_irq_disable(irq_id);
    sys_irq_release(irq_id);
    return ret;
}

/* ... device running ... */

sys_irq_disable(irq_id);
sys_irq_release(irq_id);

15.2 V0 ACC 直达 handler 高级用法模板

本模板仅适用于 V0 裸机 ACC 普通外设 IRQ:启用 CONFIG_IRQ_ACC_MODE=y,不启用 CONFIG_OS_BACKEND_FREERTOS,并保留默认注册表模式(CONFIG_IRQ_SIMPLE_DISPATCH=n)。FreeRTOS ACC 不能直接套用,须遵守 2.3.3 中的入口/退出约束。

默认模式的 sys_irq_enable() 会检查软件注册表,因此本模板保留 sys_irq_request() / sys_irq_release()。注册用于兼容公共 API 的资源管理;实际 ACC 中断仍直接进入应用定义的 IRQ_UART0_handler(),不经过 sys_irq_dispatch() 或注册回调。无需为单个直达 IRQ 切换整个工程的分发模式。

**入口定义:**以下代码放在应用 C 文件的文件作用域,确保编译并链接进 V0 固件。

#include <stddef.h>
#include "im_irq.h"

/* 应用提供同名强符号;不要声明为 static 或 weak。 */
void IRQ_UART0_handler(void)
{
    /* 在此直接读取 UART0 状态、处理业务并清除 UART0 的 pending 条件 */
}

/* 应用定义的注册适配回调,不是 SDK API;ACC 硬件入口不经过它。 */
static void uart0_irq_registry_callback(uint32_t irq_id, void *arg)
{
    (void)irq_id;
    (void)arg;
    IRQ_UART0_handler();
}

**初始化片段:**放在上述应用 C 文件中、返回 error_code_t 的初始化函数内。调用前应保持 UART0 设备中断关闭,并完成外设及业务状态准备。

error_code_t ret;

ret = sys_irq_init();
if (ret != IM_OK) {
    return ret;
}

/* 满足 sys_irq_enable() 的注册检查,不改变 ACC 直达入口。 */
ret = sys_irq_request(IRQ_UART0, uart0_irq_registry_callback,
                     "uart0-acc-direct", NULL);
if (ret != IM_OK) {
    return ret;
}

/* 可选:request 已设置默认优先级,此处显式选择优先级。 */
ret = sys_irq_set_priority(IRQ_UART0, HAL_PLIC_PRIORITY_1);
if (ret != IM_OK) {
    sys_irq_release(IRQ_UART0);
    return ret;
}

ret = sys_irq_enable(IRQ_UART0);
if (ret != IM_OK) {
    sys_irq_release(IRQ_UART0);
    return ret;
}

ret = im_cpu_irq_enable_mexternal();
if (ret != IM_OK) {
    sys_irq_disable(IRQ_UART0);
    sys_irq_release(IRQ_UART0);
    return ret;
}

/* 在此清理 UART0 遗留 pending,再打开 UART0 设备内部中断。 */
/* 系统初始化流程还必须确保全局 mstatus.MIE 已打开。 */
return IM_OK;

运行时,ACC 向量直接跳到 IRQ_UART0_handler();业务代码写在该函数内,无需另行调用分发接口。

**停用片段:**放在返回 error_code_t 的停用函数内,在不再使用 UART0 中断时执行,不要紧接初始化立即执行。

error_code_t ret;

/* 在此关闭 UART0 设备内部中断,并处理遗留 pending。 */
ret = sys_irq_disable(IRQ_UART0);
if (ret != IM_OK) {
    return ret;
}
return sys_irq_release(IRQ_UART0);

sys_irq_release() 只释放注册表条目,不会撤销链接时的强符号覆盖,必须先关闭设备中断和 PLIC source。不要为了停用 UART0 而关闭所有外设共享的 CPU machine external 入口。