IC:

概述

处理器间通信(Inter-Processor Communication, IPC)是专为多核异构系统设计的硬件加速通信机制,通过共享SRAM实现跨核数据交互,确保多核间低延迟的信息传递。其核心目标是为多核之间提供高效协同的工作能力。

IPC 架构

硬件架构特性

IPC 结构框图如下所示:

../../_images/ipc_block_diagram_dplus.svg
  1. 对称结构

    • 每个CPU都有一套相同的IPC模块。

  2. 双工通信模型

    • 支持双向全双工通信。

  3. 通道隔离性

    • 通道间采用硬件级资源隔离设计,任意通道的数据传输互不阻塞。

    • 支持并行多通道操作(如CPUA可同时通过通道n发送传感器数据、通道m传输日志信息)。

  4. 信息池

    • 预分配的一块内存,每个IPC通道通过信息池中预设的固定地址来收发信息。

  5. 共享内存

    • 当收发消息大于4字节时,发送端动态分配一块共享内存,并将内存地址作为消息写入信息池,接收端在发送端释放共享内存前,将数据拷贝到本地。

    • 当收发消息小于等于4字节时,将消息写入信息池。

  6. NVIC中断生成器

    • 在IPC通道准备好数据之后,通过写IPC某块中的寄存器,在接收端产生接收中断, 同样在接收端完成接收后,也可以在接收端触发发送完成中断。

数据流模型

  • 发送端流程:[共享信息] → 选择可用通道 → 写入共享内存 → 更新通道寄存器 → 触发对端中断 → 对端读取内存

  • 接收端流程:中断上报 → 读取通道寄存器 → 定位共享内存 → 拷贝数据到本地 → 处理数据 → 清除中断标志

IPC 基本定义

通道

  • 概念:IPC通道是连接两个IPC实体之间的双向独立通信路径,每个方向包含多组通道。

  • 作用:用于在两个CPU之间传输发送和接收事件,实现核间通信。

组件

IPC 配置表(IPC_INIT_TABLE)

  • 作用:定义了整个传输链路的所有信息,包括数据类型、接收和发送回调函数、中断处理函数参数、传输方向和IPC通道号。

  • 使用说明:用户需要将此结构体实例存放到 IPC_TABLE_DATA_SECTION 中,SDK会根据结构体的成员信息开启相应通道的Tx/Rx中断,并注册中断处理函数。

  • 结构体定义

    IPC_TABLE_DATA_SECTION
    typedef struct _IPC_INIT_TABLE_ {
       u32 USER_MSG_TYPE;  /* 数据类型:IPC_USER_DATA(小于32bit数据)或IPC_USER_POINT(大于32bit数据的地址) */
       void (*Rxfunc)(void *Data, u32 IrqStatus, u32 ChanNum); /* 接收中断回调函数 */
       void *RxIrqData;    /* 接收回调函数参数 */
       void (*Txfunc)(void *Data, u32 IrqStatus, u32 ChanNum); /* 发送完成中断回调函数 */
       void *TxIrqData;    /* 发送回调函数参数 */
       u32 IPC_Direction;  /* 传输方向 */
       u32 IPC_Channel;    /* IPC通道号 */
    } IPC_INIT_TABLE, *PIPC_INIT_TABLE;
    

IPC 数据结构(IPC_MSG_STRUCT)

  • 作用:在消息池中,每个IPC通道拥有一个固定位置用于存放 IPC_MSG_STRUCT 结构体,用于存储和传输数据。

  • 结构体定义

    typedef struct ipc_msg_struct {
       u32 msg_type;   /* 数据类型,与USER_MSG_TYPE相同 */
       u32 msg;        /* 需要传递的32bit数据或数据地址(对于大于32bit的数据) */
       u32 msg_len;    /* 数据长度 */
       u32 rsvd;       /* 保留字段 */
    } IPC_MSG_STRUCT, *PIPC_MSG_STRUCT, ipc_msg_struct_t;
    

IPC 标准使用方法

  1. 通信协议栈注册

    1.1 选择通道

    1. 文件路径 : ameba_ipccfg.h

    2. 选择与定义通道:

      通过取消注释并修改通道定义来选择和自定义通道。例如:

      /* 取消通道n的注释,并添加功能描述 */
      #define IPC_N2A_Channeln  n  /*!< 用户自定义通道:KM0到KM4的传感器数据传输 */
      ...
      //#define IPC_A2N_Channeln  n  /*!< 用户自定义通道:KM4到KM0的传感器数据传输 */
      
    3. 确认传输方向:

      #define IPC_KM0_TO_KM4  0  // KM0为发送端,KM4为接收端
       #define IPC_KM4_TO_KM0  1  // 反向通信方向
      
    4. 注意事项:

      • 每个方向配置16组物理通道,故通道号需在0-15范围内。

      • 避免使用系统已预留的通道,这些通道可能用于WiFi/BT等系统组件。

    1.2 定义接收端回调函数

    接收端回调函数,从线程池中读取消息,若消息类型为指针,使用 _memcpy() 将共享信息拷贝到本地。中断处理函数退出后,SDK自动清除中断标志.

    uint32_t local_buffer[32] = {0};
    
    /* 接收处理函数(中断上下文)*/
    void Rx_Channeln_Handler(void) {
       PIPC_MSG_STRUCT msg = ipc_get_message(IPC_KM0_TO_KM4, IPC_N2A_Channeln);/*获取消息元数据*/
       uint32_t *sensor_data = (uint32_t*)msg->msg;  /*提取数据指针*/
       _memcpy(local_buffer, (void*)sensor_data, data_size);  /*数据拷贝到本地,数据长度双方提前约定好*/
       rtos_sema_give(ipc_sema);  /*触发任务级处理*/
    }
    

    1.3 注册IPC配置表

    在接收端为每个通道注册一个IPC配置表, SDK会自动开启通道的接收中断。

    /* 通道配置表*/
    IPC_TABLE_DATA_SECTION
    const IPC_INIT_TABLE ipc_chn_config = {
       .msg_type    = IPC_USER_POINT,     // 消息传递方式:指针传递(数据大于32bit)
       .rx_func     = Rx_Channeln_Handler,
       .rx_irq_data = NULL,               // 无附加参数
       .tx_func     = NULL,      // 单向传输无需TX回调
       .tx_irq_data = NULL,
       .direction   = IPC_KM0_TO_KM4,     // 方向标识
       .channel_num = IPC_N2A_Channeln    // 通道号宏定义
    };
    
  2. 数据收发实现

    2.1 发送端实现

    调用 ipc_send_message() 函数,并指定传输方向,通道号和消息。IPC通过通道n从KM0向KM4发送IPC请求。

    void send_sensor_data(void) {
    
       IPC_MSG_STRUCT ipc_msg_temp;
       sensor_buffer = read_sensor();  // 更新共享内存
    
       ipc_msg_temp.msg_type = IPC_USER_POINT; // 传递指针
       ipc_msg_temp.msg = (u32)&sensor_buffer;
       ipc_msg_temp.msg_len = 1;              // 数据长度(字节)
       ipc_msg_temp.rsvd =0;
    
       /* 发送共享数据(SDK标准API), 带超时处理 */
       ipc_send_message(IPC_KM0_TO_KM4, IPC_N2A_Channeln, & ipc_msg_temp);
    }
    

    2.2 接收端处理

    为了协同工作,接收端线程监控ipc_sema,收到数据后及时处理,并做下一步操作。

    /* 中断上下文:快速捕获数据 */
    void Rx_Channeln_Handler(void *data) {
    }
    
    /* 任务上下文:数据处理 */
    void ipc_task(void *param) {
       while (1) {
          /*等待数据到达*/
          rtos_sema_take(ipc_sema, RTOS_WAIT_FOREVER);
             /*处理数据(已通过_memcpy复制到local_buffer)*/
          data_pipeline_process(local_buffer);
       }
    }
    

IPC 进阶使用方法

应用场景

通道繁忙时,发送线程及时让出调度,避免轮询占用CPU资源,使系统运行流畅。

传输回执配置

  1. 定义发送端回调函数

    • 如果 IPC_INIT_TABLE 初始化了发送端回调函数, SDK会自动开启通道n的传输完成中断,并在通道繁忙时初始化通道n的传输信号量(ipc_Semaphore)并等待释放, 以此让出调度,免长时间占用CPU。

    • 我们建议将此处理函数注册为 ameba_ipc_api.c 中的:func:IPC_TXHandler() 。在此函数中,会释放通道n的信号量(ipc_Semaphore)。

    • SDK会在IRQ退出后自动清除中断标志。

    void IPC_TXHandler(void *Data, u32 IrqStatus, u32 ChanNum)
    
      UNUSED(Data);
       UNUSED(IrqStatus);
      u32 CPUID = SYS_CPUID();
      IPC_TypeDef *IPCx = IPC_GetDevById(CPUID);
      /*关闭tx channeln 中断*/
       IPC_INTConfig(IPCx, ChanNum, DISABLE);
      if (ipc_Semaphore[ChanNum - IPC_TX_CHANNEL_SHIFT] != NULL) {
         /*释放信号量*/
         rtos_sema_give(ipc_Semaphore[ChanNum - IPC_TX_CHANNEL_SHIFT]);
      }
    }
    
  2. 注册IPC配置表

    在发送端为每个通道注册一个IPC配置表(可以将传输和接收配置于同一表中,需要双核都编译这个表)

    /* 通道配置表*/
    IPC_TABLE_DATA_SECTION
    const IPC_INIT_TABLE ipc_chn_config = {
       .msg_type    = IPC_USER_POINT,     // 消息传递方式:指针传递(数据大于32bit)
       .rx_func     = NULL,                // 接收函数置空
       .rx_irq_data = NULL,               // 无附加参数
       .tx_func     = IPC_TXHandlerL,      // 单向传输无需TX回调
       .tx_irq_data = NULL,
       .direction   = IPC_Direction,     // 方向标识
       .channel_num = IPC_Channel    // 通道号宏定义
    };
    

IPC 常见问题排查

通道冲突

现象

打印错误日志:Channel Conflict for CPU A Channel n ! Ignore If CPU Has Reset

原因

同一个通道注册了两个中断回调函数

解决方法

检查 IPC 配置表(IPC_INIT_TABLE),确认是否对同一通道重复注册

发送超时

现象

打印错误日志:IPC Request Timeout

原因

数据还未被接收,发送端试图再次通过同一通道发送数据

解决方法

  • 检查发送端的发送逻辑,降低发送频次,或者在多线程使用同一通道发送时引入握手机制,避免冲突

  • 检查接收端是否卡死,或者长时间陷入优先级更高的中断

获取信号量超时

现象

打印错误日志:IPC Get Semaphore Timeout

原因

发送端注册了发送完成回调函数,数据还未被接收,发送端试图再次通过同一通道发送数据,发送端等待信号量超时

解决方法

  • 检查发送端的发送逻辑,降低发送频次,或者在多线程使用同一通道发送时引入握手机制,避免冲突

  • 检查接收端是否卡死,或者长时间陷入优先级更高的中断

  • 避免大于2个线程同时使用同一个通道

接收端无响应

现象1

接收端某个IPC通道无响应

原因

接收端该通道中断被误关闭

解决方法

检查接收端是否误关中断,通过调用 IPC_INTConfig() 开启相应通道中断

现象2

接收端所有IPC通道无响应

原因

  • 接收端IPC中断被误关闭

  • 通道方向配置错误

解决方法

  • 检查接收端是否误关中断,通过调用 InterruptRegister() 为IPC开启中断

  • 检查 IPC_INIT_TABLEdirection 字段是否正确

IPC 应用层使用说明