无标题
• 结论先说:ipc_memory_pool 中的 0x00080000 是设备树使用的物理/总线地址,但它不是手册中的 0x011D0000,两者不是同一片地址。
- ipc_memory_pool@00080000 的地址含义
在 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/u-boot/arch/arm/dts/bcm8915x.dtsi:27 中:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
ipc_memory_pool: ipc_memory_pool@00080000 {
reg = <0x00 0x00080000 0x0 0x80000>;
};
};
按照 Device Tree 语义:
起始地址 = 0x0000000000080000
大小 = 0x00080000 = 512 KiB
范围 = [0x00080000, 0x00100000)
这里的 reg 是设备树中的物理/总线资源地址,不是虚拟地址。由于 reserved-memory 下使用了空的 ranges;,这里没有再经过父总线地址转换。
但是它不等于:
0x011D0000 - 0x011D7FFF
当前工程实际存在两套 SRAM 地址视图:
用途 地址
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
A55、全局地址、Device Tree 视图 0x00080000
───────────────────────────────── ────────────────────────────────
MCU1 本地 SRAM 视图 0x01080000
───────────────────────────────── ────────────────────────────────
手册中的 MCU 本地 SRAM IPC 区域 0x011D0000 - 0x011D7FFF
───────────────────────────────── ────────────────────────────────
上述 SRAM IPC 区域的全局别名 通常为 0x001D0000 - 0x001D7FFF
工程代码可以证明前两者是别名关系:
#define IPCH_MEM_POOL_BASE (0x01080000UL)
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/LidarRTOSapp_0_1/apps/lidar_rtos_app/src/ipc_helper.c:18。
同一个文件中又定义了:
0x01000000 - 0x011FFFFF // MCU local SRAM
0x00000000 - 0x001FFFFF // SRAM global view
并通过减去 0x01000000 做地址转换,见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/LidarRTOSapp_0_1/apps/lidar_rtos_app/src/ipc_helper.c:161。
因此:
MCU local 0x01080000 -> global 0x00080000
MCU local 0x011D3000 -> global 0x001D3000
所以不能把 DTS 中的 0x00080000 解释成 0x011D0000。
当前 BL 配置也印证了这一点:
IpcMemPoolBase : 0x01080000
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/BCM8915X_BL_REL_1.5_src/init/bcm8915x/scripts/m7_flash_img.yaml:46。
- 当前 IPC pool 的实际布局不是固定的 0x011D 区域
BL 脚本会根据 YAML 中各个 channel 的 MsgMaxSize 自动计算消息缓冲区偏移,见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/BCM8915X_BL_REL_1.5_src/oneui/scripts/m7_flash_img.py:713。
当前配置大致是:
Channel 0: MsgMaxSize = 0x10000
Channel 1: MsgMaxSize = 0x01000
Channel 2: MsgMaxSize = 0x01000
Channel 3: MsgMaxSize = 0x22000
Channel 4: MsgMaxSize = 0
Channel 5: MsgMaxSize = 0x01000
按当前 YAML 顺序和 4 KiB 对齐计算:
pool base = 0x00080000 global
channel config = pool + 0x00000000
channel 0 payload = pool + 0x00001000
channel 1 payload = pool + 0x00011000
channel 2 payload = pool + 0x00012000
channel 3 payload = pool + 0x00013000
channel 5 payload = pool + 0x00035000
Linux 驱动也不是直接假设 payload 地址,而是读取 IPC 配置中的 msg_start_offset,然后计算:
uio_mem->addr = res.start + chan_info.msg_start_offset;
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/linux/drivers/mailbox/bcm-rigel-mbox-client.c:119。
所以:
ipc_memory_pool = 0x00080000 - 0x000FFFFF
属于当前 IPC channel pool;它与:
SRAM IPC = 0x011D0000 - 0x011D7FFF
是两个不同的区域。
- 你规划的 0x011D3000 到 0x011D7FFF 需要特别小心
你的地址布局本身满足:
4 KiB 对齐
每个 buffer 不重叠
总大小 5 * 4 KiB = 20 KiB
但是工程中的 BL linker script 已经把该区域定义成:
SRAM_IPC_SCR:
0x011D0000 - 0x011D7FFF
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/BCM8915X_BL_REL_1.5_src/init/bcm8915x/chip/common/firmware.ld:53。
更关键的是:
jtag_ipc_scratch_size = 0x4000;
并且:
.sram_ipc_scr:
{
. = . + jtag_ipc_scratch_size;
} > SRAM_IPC_SCR
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/BCM8915X_BL_REL_1.5_src/init/bcm8915x/chip/common/firmware_common.ld:50。
这意味着 BL/JTAG scratch 至少占用了:
0x011D0000 - 0x011D3FFF
因此你提出的:
MCU1_LOG_ADDR = 0x011D3000
覆盖了:
0x011D3000 - 0x011D3FFF
它与这段 JTAG IPC scratch 发生重叠。
你规划的 Q8 区域:
Q8 Core0: 0x011D4000 - 0x011D4FFF
Q8 Core1: 0x011D5000 - 0x011D5FFF
Q8 Core2: 0x011D6000 - 0x011D6FFF
Q8 Core3: 0x011D7000 - 0x011D7FFF
没有和这 16 KiB JTAG scratch 重叠,但仍然需要确认:
- 当前启动模式是否使用 PCIe EP IPC;
- JTAG/flash utility 是否会使用这片区域;
- MCU1、Q8 linker script 是否会使用这片区域;
- BL、ROM、工具链是否在运行期间清零或访问这片区域;
- 是否会被其他 scratch、调试、测试代码使用。
因此,当前地址方案不能直接批准使用。尤其是 MCU1_LOG_ADDR = 0x011D3000,需要先处理与 JTAG scratch 的冲突。
另外,当前 MCU1 应用自己的 linker script 只定义了:
SRAM : ORIGIN = 0x01040000, LENGTH = 256K
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/LidarRTOSapp_0_1/apps/lidar_rtos_app/mcu1.ld:15。
它没有为 0x011Dxxxx 建立 log section,也没有对该区域做链接期保护。所以即使代码手工访问 0x011Dxxxx,也不代表这片内存已经被正式保留。
- 可以在 config.ini 中指定这些地址吗?
从配置格式角度可以,但当前代码中这些配置项不会生效。
你当前添加的:
Q8_Core3_LOG_ADDR = 0x011D7000
Q8_Core2_LOG_ADDR = 0x011D6000
Q8_Core1_LOG_ADDR = 0x011D5000
Q8_Core0_LOG_ADDR = 0x011D4000
MCU1_LOG_ADDR = 0x011D3000
在 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/yocto/rigel/recipes-apps/lidar_a55_app/files/config.ini:24 中存在,但:
- config_t 没有这些字段;
- config_apply_param() 没有解析这些 key;
- config.h 没有对应 getter;
- 当前 parser 对未知 key 静默忽略。
相关代码见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/yocto/rigel/recipes-apps/lidar_a55_app/files/src/config.c:96 和 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/yocto/rigel/recipes-apps/lidar_a55_app/files/src/config.c:203。
即使补上 A55 parser,config.ini 也只能改变 A55 的配置。它不会自动改变 MCU1/Q8 的日志写入地址。
要让这套机制真正生效,至少需要:
config.ini
-> A55 parser
-> A55 保存五个 log 地址
-> A55 启动前把地址发布到共享配置 ABI
-> MCU1/Q8 读取共享配置
-> MCU1/Q8 使用这些地址写 ring buffer
-> A55 映射并读取 ring buffer
建议不要把地址命名成含义不明确的 *_LOG_ADDR,而是区分地址视图,例如:
MCU1_LOG_ADDR_GLOBAL = 0x001D3000
Q8_CORE0_LOG_ADDR_GLOBAL = 0x001D4000
MCU1 侧再转换为 local address:
global 0x001D3000 -> MCU local 0x011D3000
如果地址固定,建议由 linker script 或统一的公共内存布局头文件作为唯一来源,A55 只读取这个固定布局。不要让 config.ini、MCU1 固件、Q8 固件各自维护三份可能不一致的地址。
- 日志应该复用 channel 3,还是使用独立 channel?
建议:
日志使用独立的 IPC notification channel。
日志内容放在 shared-memory ring buffer。
IPC channel 只发送“有日志可读”的通知。
不要每条日志都触发 DMA 或发送一次完整 IPC message。
当前 channel 分配是:
Channel 当前用途
━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2 A55 -> MCU0
───────── ────────────────────────────────────────────
3 MCU1 -> A55,传输 LiDAR 数据
───────── ────────────────────────────────────────────
4 Q8 Core0..3 -> MCU1,发送 buffer-done 通知
───────── ────────────────────────────────────────────
5 A55 -> MCU1
配置见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/BCM8915X_BL_REL_1.5_src/init/bcm8915x/scripts/m7_flash_img.yaml:89。
当前 channel 3 的主要业务是:
Q8 -> channel 4 -> MCU1
MCU1 -> channel 3 -> A55
channel 3 是高频 LiDAR 数据路径。它使用 pointer message,A55 每次收到后处理数据,然后清除 doorbell。
现有 A55 responder 对 command-form doorbell 还明确不支持:
if (doorbell & IPC_DOORBELL_CMD_MASK) {
LOG_ERROR(“IPC RESP: command not supported\n”);
return;
}
见 /home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/yocto/rigel/recipes-apps/lidar_a55_app/files/src/ipc_resp.c:68。
所以如果日志复用 channel 3,不能只增加一个 command ID。还必须同时处理:
- LiDAR data message 与 log message 的区分;
- pointer message 与 command message;
- 共享 channel 的 pending/ACK 状态;
- 日志和 LiDAR payload 的 ownership;
- 多个 Q8 core 同时通知;
- A55 端不同消息类型的解析;
- 日志不能阻塞 LiDAR data;
- channel 忙时的日志合并和丢弃策略。
这正是复用 channel 3 可能影响主业务效率的原因。
更合理的设计是使用一个专用 log channel,例如 channel 6,但必须确认 channel 6 在实际 BL 配置中可用,然后同时修改:
BL YAML IPC 配置
A55 U-Boot DTS
A55 Linux UIO/mapping
A55 log reader
MCU1/Q8 log producer
不一定要为五个 producer 分配五个 IPC channel。可以采用:
一个专用 log channel
五个独立 ring buffer
doorbell 内容携带 producer ID 或 pending bitmap
例如:
MCU1 -> MCU1 log ring ->+
Q8_0 -> Q8_0 log ring ->+
Q8_1 -> Q8_1 log ring ->+-> 专用 log channel -> A55
Q8_2 -> Q8_2 log ring ->+
Q8_3 -> Q8_3 log ring ->+
但因为一个 IPC channel 同时只能有一个未完成的 doorbell,多个 producer 共用时需要:
- ring 中保存每个 producer 自己的 head/tail;
- 共享一个 pending bitmap;
- channel 忙时只置位 pending,不重复发送通知;
- A55 收到通知后扫描五个 ring;
- A55 清除 doorbell 前确认已读取 pending;
- A55 周期性扫描,避免通知丢失导致日志永久滞留。
- 每个 4 KiB ring 不建议直接作为字符串缓冲区
每个 4 KiB 区域建议至少包含:
magic
version
producer_id
buffer_size
head
tail
sequence
dropped_count
records[]
还需要定义:
- record 格式;
- timestamp 来源;
- wrap-around 规则;
- buffer 满时覆盖旧日志还是丢弃新日志;
- A55 如何更新 tail;
- M7/Q8 写入后的 memory barrier;
- M7/Q8 D-cache clean;
- A55 读取前的 cache invalidate 或 non-cacheable mapping;
- reboot 后 ring 的初始化和 magic 检查。
日志写入路径应类似:
格式化一条日志
-> 写入对应 producer 的 ring
-> DSB/cache clean
-> 必要时发送一次 log notification
而不是:
-> IPC 发送完整消息
-> 等待 ACK
最终建议
- ipc_memory_pool 的 0x00080000 是 global/bus physical address,大小 512 KiB,不是 0x011D0000。
- 0x011D0000 - 0x011D7FFF 是另一片 SRAM IPC/scratch 区域。
- 你提出的 MCU1_LOG_ADDR = 0x011D3000 与工程中的前 16 KiB JTAG IPC scratch 重叠,不能直接使用。
- MCU1/Q8 如果使用 local address,应将对应的 global address 明确记录为 0x001Dxxxx。
- config.ini 当前会静默忽略这些 log 地址,必须增加 parser、字段和 getter。
- config.ini 不能单独改变 MCU1/Q8 的写入位置,还需要固件 linker/启动代码或共享配置 ABI。
- 日志建议使用独立 channel,最好是一个专用通知 channel 加五个独立 shared-memory ring。
- 不建议把日志 command 直接塞入当前高频 LiDAR channel 3。
已按“Channel 3 保持主业务、Channel 6 独立日志通知、共享内存 ring buffer、A55 直接读取、不使用 DMA”的方案完成 MCU1 侧实现,并准备好了 A55/YAML 待合入内容。
地址结论
设备树中的:
ipc_memory_pool: ipc_memory_pool@00080000 {
reg = <0x00 0x00080000 0x0 0x80000>;
};
在设备树语义上表示 A55/Linux 侧的 reserved-memory 物理区域:
起始地址: 0x00080000
结束地址: 0x000FFFFF
大小: 0x80000
它不是固定的:
0x011D0000 - 0x011D7FFF
也不能把 Channel 6 的 log 地址直接写成 0x011D3000、0x011D4000 等固定地址。
当前方案使用:
Channel 6 message base =
IPC memory pool base + bootloader 分配的 msgStartOffset
MCU1 从 bootloader 生成的 IPC channel table 中读取 msgStartOffset,A55 通过 Channel 6 对应的 UIO payload 映射获得同一片内存。这样即使 bootloader 重新排列 Channel 3、Channel 6 的内存位置,协议仍然有效。
已经完成的 MCU1 侧改动
核心协议定义位于:
shared/ipc_log_protocol.h:1
Channel 6 的布局为:
+0x0000 - +0x001F : 通用 IPC header 保留区
+0x0020 - +0x005F : log control block
+0x1000 - +0x1FFF : MCU1 ring
+0x2000 - +0x2FFF : Q8 Core 0 ring
+0x3000 - +0x3FFF : Q8 Core 1 ring
+0x4000 - +0x4FFF : Q8 Core 2 ring
+0x5000 - +0x5FFF : Q8 Core 3 ring
每个 ring:
ring 总空间: 4 KiB
ring header: 128 bytes
record 大小: 256 bytes
record 文本: 240 bytes
record 数量: 15
实际可用槽位: 14
MCU1 侧日志实现位于:
app/src/logger/log_ipc.c:1
实现内容包括:
- Channel 6 动态地址发现。
- MCU1 专属 ring 初始化。
- Q8 ring 接口保留,但不会清空或初始化 Q8 ring。
- producer-owned head。
- consumer-owned tail。
- dropped 丢弃计数。
- cache clean/invalidate。
- release/acquire 原子索引发布。
- ring 满时丢日志,不阻塞 LiDAR 主业务。
- 空 ring 转非空时发送一次 doorbell。
- MCU1 周期性扫描 Q8 ring,兼容未来 Q8 logger。
- 日志内容直接写入共享内存。
- 不使用 DMA 搬运日志。
动态 IPC helper 位于:
app/src/bcm/ipc_helper.c:1
新增和调整了:
IPCH_GetMsgPtr()
IPCH_GetMsgPayloadPtr()
IPCH_GetMsgSize()
IPCH_SendMsg()
其中:
IPCH_SendMsg(IPC_CHAN_LOG_TO_A55, 0UL);
会根据 Channel 6 当前的 msgStartOffset 动态计算 doorbell pointer,不再使用固定 log 地址。
mcu1.ld 中原先固定的 IPC_LOG linker region 已移除,避免把 Channel 6 错误地绑定到固定地址:
app/mcu1.ld:48
ELog 仍然保留,当前 MCU1 配置为 IPC-only:
app/src/params_cfg.c:28
elog_log_config_t elog_config =
{
.transport = ELOG_TRANSPORT_IPC,
};
同时调整了初始化顺序,使 MPU 和 D-cache 在 ELog/IPC logger 初始化前完成:
app/src/app_system.c:115
A55 侧待合入文件
由于当前工作区权限只允许写入:
/home/hjk/workspace/11.bcm8915x/BCM8915X_MCU_OS
不能直接修改外部的:
/home/hjk/workspace/11.bcm8915x/BCM8915X_LIDAR_REF_SW_REL_0_1/A55_system/…
所以 A55 修改已暂存到:
pending_a55_lidar_app
其中:
- pending_a55_lidar_app/files/src/ipc_log_resp.c:1
- pending_a55_lidar_app/files/include/ipc_log_resp.h:1
- pending_a55_lidar_app/files/include/ipc_log_protocol.h:1
- pending_a55_lidar_app/files/src/main.c:1
- pending_a55_lidar_app/files/Makefile:1
A55 实现行为:
- 初始化 Channel 6 responder。
- 使用 UIO payload 作为动态 Channel 6 base。
- doorbell 仅作为“有日志”的通知。
- 通过 uio_mbox_phy_to_virt() 验证 doorbell pointer。
- 直接读取 UIO 映射内存。
- 不使用 DMA。
- 每 10 ms 扫描一次所有 producer ring。
- 收取 MCU1、Q8_0、Q8_1、Q8_2、Q8_3 五种 producer。
- 保留 Q8 接口,但 Q8 当前没有 logger producer 实现。
- 消费完之后通过 uio_mbox_send_resp() 清除 doorbell 并重新使能中断。
- 不影响 cmd.c 中已有的 Channel 3 LiDAR responder 线程。
原有 Channel 3 responder 仍然由:
cmd.c -> start_proc() -> pthread_create(ipc_resp_thread)
创建。Channel 6 responder 由新的 main.c 独立创建,两者没有共用 doorbell 或 DMA 路径。
YAML
我检查了实际 YAML,当前 Channel 6 仍然是:
- ChannelId: 6
TargetCPU: A55
SrcCPU: [MCU_1]
MsgMaxSize: 0x8000
需要改为:
- ChannelId: 6
TargetCPU: A55
SrcCPU: [MCU_1, Q8_0, Q8_1, Q8_2, Q8_3]
MsgMaxSize: 0x8000
对应补丁位于:
pending_a55_lidar_app/m7_flash_img.yaml.patch:1
这里使用了实际 YAML 中的 MCU_1 命名,而不是 MCU1。
config.ini 不需要增加:
MCU1_LOG_ADDR
Q8_Core0_LOG_ADDR
Q8_Core1_LOG_ADDR
Q8_Core2_LOG_ADDR
Q8_Core3_LOG_ADDR
这些固定地址配置应该删除或不添加,因为 ring 地址相对于动态 Channel 6 message base 固定,而 message base 由 bootloader 分配。
DTS 检查
你在:
A55_system/u-boot/arch/arm/dts/bcm89158.dts
中增加的节点:
mbox_resp_log: mbox_resp_log {
compatible = “brcm,rigel-mbox-client”;
mboxes = <&mailbox (MBOX_ACC_NORMAL_MASK | MBOX_TYPE_RESP | MBOX_ID(6))>;
mbox-names = “mcu1-q8-log”;
memory-region = <&ipc_memory_pool>;
};
配置方向正确:
- MBOX_TYPE_RESP 与 A55 接收 MCU1/Q8 日志相符。
- MBOX_ID(6) 与 Channel 6 相符。
- memory-region = <&ipc_memory_pool> 使 UIO 映射 bootloader IPC pool。
- 不需要在 DTS 中指定 MCU1/Q8 各自的固定 log 地址。
使用 C preprocessor 和 dtc 检查后可以成功生成 DTB。输出中只有原有 DTS 的格式 warning,例如 ipc_memory_pool@00080000 的 leading zero warning,没有 Channel 6 节点语法错误。
另外,Yocto 的启动配置确实使用 U-Boot 生成的 bcm89158.dtb:
IMAGE_BOOT_FILES:rigel = “…,bcm89158.dtb”
UBOOT_DTB_BINARY = “bcm89158.dtb”
因此当前工程没有发现需要额外同步的 BCM8915X Linux 板级 DTS 文件。
验证结果
已通过:
make mcu1 V=1
生成:
build/out/mcu1/bin/bcm8915x_mcu1.elf
build/out/mcu1/bin/bcm8915x_mcu1.bin
build/out/mcu1/bin/bcm8915x_mcu1.img
内存使用:
DTCM_BSS: 1280 B / 16 KB
SRAM: 72188 B / 256 KB
还通过了:
gcc -std=c11 -Wall -Wextra -Werror -pedantic
共享协议头检查、A55 新增源码主机侧语法检查、ring ABI 运行时布局检查以及:
git diff –check
MCU1 构建中仍存在原有 linker warning:
cannot find entry symbol BCM_OS_RESET_HANDLER;
defaulting to 0x01040000
这不是本次 Channel 6 日志改动引入的错误。
A55 由于外部目录在当前权限下不可写,尚未执行最终 BitBake 构建;目前已完成主机侧严格语法检查。app/src/logger/log_uart.c 和 app/src/logger/dma_log.c 暂时保留,因为 MCU1 的 cli_uart.c 仍然依赖 LogUart_* 接口。运行配置已经是 IPC-only,因此它们不会作为日志输出后端初始化。
success
IPC Log (MCU1 → A55, Channel 6)
MCU1 (FreeRTOS, BCM8915X_MCU_OS) produces log lines into a shared-memory ring;
A55 (Linux, bcm8915x_app) consumes them and writes /tmp/log/mcu1.log. The
same layout reserves rings for four Q8 cores, but only the MCU1 producer is
implemented today.
- Shared ABI:
BCM8915X_MCU_OS/shared/ipc_log_protocol.h
==bcm8915x_app/include/logger/ipc_log_protocol.h(keep byte-identical). - MCU1 producer:
BCM8915X_MCU_OS/app/src/logger/log_ipc.c(+ EasyLogger portelog_port.c, async drainlog_task.c+elog_async.c). - A55 consumer:
bcm8915x_app/src/logger/ipc_log_resp.c,ipc_log_file.c; buffer publish inbcm8915x_app/src/system.c.
Memory layout (Channel 6 payload, 0x8000 bytes)
Channel 6 is allocated by the bootloader. Both sides discover its message base
and size from the IPC channel config (no fixed address baked in).
offset size contents |
Each ring: 128-byte header (magic/version/producer/record_size/record_count,
then cache-line-isolated head, tail, dropped) followed by 15 records of
256 bytes. A record is sequence(4) timestamp_ms(4) length(2) level(1) flags(1) reserved(4) text[240]. SPSC: producer owns head, consumer ownstail; head==tail means empty.
Startup handshake
- A55
ipc_log_resp_init()opens the Channel 6 UIO device, mmaps the payload
(MAP_SHARED,O_SYNC), and starts the receiver thread. - A55
configure_log_buffers()computes the five ring physical addresses
(payload_phys + ring_offset) and publishes them into the shared LiDAR
config (logBufferInfo[5], at phys0x00190000, magicLIDR). - MCU1
LogIpc_Init()gets the Channel 6 base/size, readslogBufferInfofor
its own ring (falling back to the fixed channel-base layout if the config
isn’t published yet), then initializes the control block and the MCU1 ring
(writes magic last, after all immutable fields). - Steady state: EasyLogger formats a line → async ring (
elog_async.c) →LogTaskdrains it →LogIpc_Send()appends a record and rings the Channel
6 doorbell only on empty→non-empty. A55 wakes on the doorbell (also polls
every 10 ms), copies records out, advancestail, writes the file.
Doorbell also carries command-form trace codes (0x6C67xxxx) used for
bring-up; A55 logs them as received MCU1 log trace 0x.....
Cache / coherency
MCU1 maps the shared SRAM window (MPU region 6, 0x01000000+2MB) as Normal
non-cacheable (app_system.c, TEX=1/C=0/B=0). The SCB_Clean/Invalidate
calls in log_ipc.c are therefore effectively no-ops but are kept so the code
stays correct if the region is ever made cacheable. Ordering is enforced with__atomic acquire/release on head/tail plus __DSB() before the doorbell.
Validation on both sides
Consumer treats a ring/control block as valid only if magic + version +record_size + record_count (+ producer_id, + channel_size/ring_size/producer_count/ring_offset[] for the control block) all match. Invalid
blocks are logged once (IPC log ring ... invalid, IPC log control invalid)
and skipped. A55 file output is configured via the IPC_LOG: section of the
YAML config (PATH, MCU1_FILE, Q8_FILE_PATTERN, ENABLED).
Pitfall log
1. MCU1 .data initializers were dropped from the image (root cause of empty logs)
Symptom: A55 received a growing stream of records with correctsequence/timestamp but length=240 and all-zero text
([seq=N][t=Nms] <<empty>>).
Why it was misleading: correct seq/timestamp proves the ring, addressing,
and cache path all work — those fields and text live in the same 256-byte
record copied as one unit. So the text was genuinely zero in memory; the bug
was upstream of the ring.
Actual cause — build/linker, not logic:
- The raw image is
objcopy --only-section=.text(Makefile:76);Startup.S
copies.datafrom__data_load__at boot. app/mcu1.ldhad placed.datain a separate section
(> DTCM_BSS AT > SRAM) whose LMA fell past the end of.text, so its
initializer bytes were never in the.bin. Startup then copied garbage →
every nonzero-initialized global booted wrong.- In the logger,
buf_is_empty(the only nonzero-init static inelog_async.c,= true) came upfalse, soelog_async_get_buf_used()
reported the whole 10 KB async ring as “used” zeros andLogTaskstreamed
empty records forever.
Fix: carry *(.data*) inside the .text output section in-place
(VMA==LMA) so it rides the loaded payload and the startup copy is a no-op. This
mirrors the contract already documented in app/firmware_common.ld (which also
cites FreeRTOS uxCriticalNesting = 0xaaaaaaaa as another victim of the same
trap). Verify after building: the buf_is_empty init byte (01) must be
present in the .bin at its symbol offset.
Any linker script feeding this
.text-only image must keep initialized data
inside.text. Checkmcu0.ld/ Q8 scripts for the same pattern.
2. Two copies of ipc_log_protocol.h
The MCU1 and A55 headers must stay byte-identical (only whitespace may differ).
A struct/offset drift shows up as garbled fields, not empty text — different
failure mode from #1. static_asserts in the header guard sizes/alignment.
3. Doorbell BUSY is normal
IPCH_SendMsg returns BCM_ERR_BUSY while A55 still owns the previous
notification. The producer does not retry-block; A55’s 10 ms poll and the next
log line’s doorbell cover it. Don’t add blocking retries in the log hot path
(IpcLogDoorbellTraceSend deliberately delays 20 ms and must stay off the hot
path).

