RK3576 与 Zynq DSMC 开发记录
项目概述
RK3576 TEST1 作为 DSMC Host,通过 LocalBus 与 Xilinx Zynq XC7Z020 PL 通信。 内核:Rockchip Linux 6.1 板型:RK3576 TEST1 V10 片选:CS0 位宽:x16 频率:25 MHz region0:Merged FIFO,0x10000000 region3:Register,0x12000000
设备树
板级配置文件:
/home/user/kernel-6.1/arch/arm64/boot/dts/rockchip/rk3576-test1.dtsi
配置:
&dsmc {
clock-frequency = <25000000>;
status = "okay";
};
&dsmc_slave {
rockchip,clk-mode = <0>;
status = "okay";
lb-slave {
rockchip,io-width = <16>;
};
};
&dsmc_lb_slave0 {
rockchip,mtr-timing = <1 0 0 0 0 0 2 2>;
rockchip,int-en = <0>;
status = "okay";
};
&dsmc_p0_region0 {
status = "okay";
};
&dsmc_p0_region2 {
status = "disabled";
};
&dsmc_p0_region3 {
status = "okay";
};
内核配置保持:
CONFIG_ROCKCHIP_DSMC=y
CONFIG_ROCKCHIP_DSMC_SLAVE is not set
DSMC不同模式的启停
开启/停止DSMC
/* 全局启用 DSMC */
&dsmc {
status = "okay";
};
&dsmc_slave {
status = "okay";
};
选择使用PSRAM CSx/ LocalBus CSx
/* 选择 PSRAM CS0 选择哪个就把哪个打开就好*/
&dsmc_psram0 { status = "okay"; };
&dsmc_psram1 { status = "disabled"; };
&dsmc_psram2 { status = "disabled"; };
&dsmc_psram3 { status = "disabled"; };
&dsmc_lb_slave0 { status = "disabled"; };
&dsmc_lb_slave1 { status = "disabled"; };
&dsmc_lb_slave2 { status = "disabled"; };
&dsmc_lb_slave3 { status = "disabled"; };
选择LocalBus 位宽
/* Local Bus 位宽 */
&dsmc_slave {
lb-slave {
rockchip,io-width = <8>; /* x8 */
/* rockchip,io-width = <16>; */ /* x16 */
};
};
选择Regionx 和 数据传输模式
/* CS0:region0,寄存器窗口 */
&dsmc_p0_region0 {
status = "okay";
rockchip,attribute = "Register";
};
/* CS0:region1,合并 FIFO 数据窗口 */
&dsmc_p0_region1 {
status = "okay";
rockchip,attribute = "Merged FIFO";
};
/* CS0:region2,不合并 FIFO 数据窗口 */
&dsmc_p0_region2 {
status = "okay";
rockchip,attribute = "No-Merge FIFO";
};
/* CS0:region3,双口 RAM 窗口 */
&dsmc_p0_region3 {
status = "okay";
rockchip,attribute = "DPRA";
};
不同region 传输数据
- “Register”:控制、状态、配置寄存器。
- “Merged FIFO”:连续数据 FIFO,允许合并访问,适合高吞吐数据流。
- “No-Merge FIFO”:不合并每次读写,适合读写有副作用的 FIFO。
- “DPRA”:双口 RAM,适合共享内存窗口。
选择原则:寄存器用 Register;普通连续数据通道优先 Merged FIFO;每一次访问都必须独立生效时用 No-Merge FIFO; FPGA 暴露共享 RAM 时用 DPRA。
Patch
3.1 RK3576 测试适配
/home/user/tools/linux/0002-WIP-add-dsmc-test-rk3576-adapt.patch
作用:添加 dsmc-test.c、dsmc-memtester.c 和 sysfs 测试入口,并移除原 patch 中仅适用于 RK3506、强制 x8 等修改。
3.2 DMA 修复
/home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
作用:修复 kernel 6.1 中 dma_interleaved_template.sgl[] 柔性数组没有分配空间导致的内核栈越界。运行 plc_test 前必须应用此 patch。
修改文件:
/home/user/kernel-6.1/drivers/memory/rockchip/dsmc-host.c
需要修改三个位置。
1. 头文件区域
位置约在文件第 5~16 行。
增加:
#include <linux/slab.h>
放在:
#include <linux/reset.h>
#include <linux/slab.h>
2. DMA发送函数
函数位置约在第629行:
static int rockchip_dsmc_lb_prepare_tx_dma(
struct device *dev,
struct rockchip_dsmc *dsmc,
uint32_t cs)
将:
struct dma_interleaved_template xt;
修改为:
struct dma_interleaved_template *xt;
将:
memset(&xt, 0, sizeof(xt));
修改:
xt = kzalloc(struct_size(xt, sgl, 1), GFP_KERNEL);
if (!xt)
return -ENOMEM;
将:
dmaengine_config_interleaved(xfer, &xt, DMA_MEM_TO_DEV, 1);
txdesc = dmaengine_prep_interleaved_dma(dsmc->dma_req[cs], &xt,
flags | DMA_PREP_INTERRUPT);
修改为:
dmaengine_config_interleaved(xfer, xt, DMA_MEM_TO_DEV, 1);
txdesc = dmaengine_prep_interleaved_dma(dsmc->dma_req[cs], xt,
flags | DMA_PREP_INTERRUPT);
kfree(xt);
3. DMA接收函数
函数位置约在第666行:
static int rockchip_dsmc_lb_prepare_rx_dma(
struct device *dev,
struct rockchip_dsmc *dsmc,
uint32_t cs)
同样将:
struct dma_interleaved_template xt;
修改为:
struct dma_interleaved_template *xt;
将:
memset(&xt, 0, sizeof(xt));
修改为:
xt = kzalloc(struct_size(xt, sgl, 1), GFP_KERNEL);
if (!xt)
return -ENOMEM;
将:
dmaengine_config_interleaved(xfer, &xt, DMA_DEV_TO_MEM, 1);
rxdesc = dmaengine_prep_interleaved_dma(dsmc->dma_req[cs], &xt,
flags | DMA_PREP_INTERRUPT);
修改为:
dmaengine_config_interleaved(xfer, xt, DMA_DEV_TO_MEM, 1);
rxdesc = dmaengine_prep_interleaved_dma(dsmc->dma_req[cs], xt,
flags | DMA_PREP_INTERRUPT);
kfree(xt);
完整修复 patch:
/home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
应用方法:
cd /home/user/kernel-6.1
git apply --check \
/home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
git apply \
/home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
从干净源码应用:
cd /home/user/kernel-6.1
git apply --check /home/user/tools/linux/0002-WIP-add-dsmc-test-rk3576-adapt.patch
git apply /home/user/tools/linux/0002-WIP-add-dsmc-test-rk3576-adapt.patch
git apply --check /home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
git apply /home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
git diff —check
编译
cd /home/user
./build.sh rockchip_rk3576_test1_v10_defconfig
./build.sh kernel
产物:
/home/user/kernel-6.1/boot.img
检查 DTB:
DTB=/home/user/kernel-6.1/arch/arm64/boot/dts/rockchip/rk3576-test1-v10-linux.dtb
fdtget "$DTB" /dsmc@2a280000 status
fdtget -t u "$DTB" /dsmc@2a280000 clock-frequency
fdtget "$DTB" /dsmc-slave/lb-slave/lb-slave0/region/region0 status
fdtget "$DTB" /dsmc-slave/lb-slave/lb-slave0/region/region3 status
预期为 okay、25000000、okay、okay。
板端验证
FPGA 必须在 RK3576 DSMC probe 前加载 bitstream 并 ready。
dmesg | grep -i dsmc
test -L /sys/bus/platform/drivers/dsmc/2a280000.dsmc \
&& echo "DSMC绑定成功" \
|| echo "DSMC绑定失败"
成功日志应包含:
cs0 byte0: DLL window [...]
cs0 byte1: DLL window [...]
CPU 回环
ADDR=0x10001000
OLD=$(devmem $ADDR 32)
devmem $ADDR 32 0x55aa1234
devmem $ADDR 32
devmem $ADDR 32 0xaa55abcd
devmem $ADDR 32
devmem $ADDR 32 “$OLD”
实测读回:
0x55AA1234
0xAA55ABCD
DMA 回环
确认已应用 0003 且 region0 允许覆盖至少 4 MiB 后:
cat /sys/kernel/rk_dsmc_test/plc_test
dmesg | tail -n 200
成功应包含:
cpu_compare_test test done
plc_simple_test test done

Debug
DSMC 未启用
现象:
-
dmesg 中没有 DSMC 日志
-
/sys/bus/platform/devices/ 中没有 2a280000.dsmc
检查命令:
tr '\0' '\n' < /proc/device-tree/dsmc@2a280000/status如果输出 disabled,说明当前 DTB 没有启用 DSMC。需要检查:
-
是否修改了 rk3576-test1.dtsi
-
是否重新编译了内核
-
是否烧写了最新的 boot.img
DLL 训练失败
DSMC: cs0 byte0 dll training failedDSMC dll training fail含义:
驱动扫描了 DLL 延迟值 0x00~0xff,但所有延迟值都无法正确完成数据写入和读回。
检查命令:
dmesg | grep -i dsmc
重点检查:
-
FPGA 是否已经加载 bitstream
-
FPGA LocalBus Slave 是否 ready
-
DQ[7:0] 和 DQS0 是否正常
-
DQ[15:8] 和 DQS1 是否正常
-
CSN0、CLKP/CLKN、RDYN 是否正常
-
ARM 与 FPGA 的 x8/x16 配置是否一致
-
FPGA 是否实现对应 region
-
region 前 0x100 字节是否支持写入并原样读回
-
clk-mode 和 mtr-timing 是否与 FPGA 一致
如果只报 byte0 失败,不一定只是 DQS0 问题。驱动遇到 byte0 失败后会直接停止,因此整个 FPGA 读返回异常也会表现为 byte0 失败。
DLL 训练成功
正常日志示例:
cs0 byte0: DLL window [0x20-0xff] length=0xe0, current=0x8f检查驱动是否绑定:
test -L /sys/bus/platform/drivers/dsmc/2a280000.dsmc && echo "DSMC绑定成功" || echo "DSMC绑定失败"必须同时满足:
-
byte0 DLL 训练成功
-
byte1 DLL 训练成功
-
DSMC platform driver 绑定成功
注意:
/dev/dsmc/cs0/region0 等节点存在,不代表驱动一定成功。旧驱动在 probe 失败后可能残留字符设备节点。
Asynchronous SError
错误日志:
Kernel panic - not syncing: Asynchronous SError Interrupt
含义:
ARM 访问 DSMC 外部地址时,FPGA 没有返回有效总线响应。
常见原因:
-
FPGA 尚未 ready
-
DSMC probe 已失败,控制器时钟被关闭
-
访问了错误的 region 地址
-
FPGA 没有实现对应 region
-
DQS 或 RDYN 没有正确返回
-
FPGA 在通信过程中复位或丢失 bitstream
发生后不要继续执行 devmem。
先检查:
dmesg | grep -i dsmctest -L /sys/bus/platform/drivers/dsmc/2a280000.dsmc && echo "DSMC成功" || echo "DSMC失败"冷启动阶段 external abort
异常调用路径:
rk_dsmc_probe
→ rockchip_dsmc_dll_training
→ dsmc_dll_training_method
→ dsmc_read
→ __raw_readl
含义:
RK3576 在启动约 2.5 秒时开始 DSMC DLL 训练,但 FPGA 可能还没有完成 bitstream 加载和 LocalBus 初始化。
判断方法:
-
保持 FPGA 上电并 ready
-
只重启 RK3576
-
如果热重启成功、冷启动失败,基本可以确认是启动时序问题
建议方案:
-
最可靠:增加 FPGA Ready GPIO
-
DSMC 驱动检测到 Ready 后再初始化
-
临时验证可以增加固定延时
-
固定延时不适合作为最终方案
DMA 测试导致内核栈破坏
错误日志:
stack-protector: Kernel stack is corrupted in: rockchip_dsmc_lb_prepare_tx_dma调用路径:
plc_test_show
→ plc_simple_test
→ dsmc_copy_to
→ rockchip_dsmc_lb_prepare_tx_dma
原因:
kernel 6.1 中 dma_interleaved_template 的最后一个成员是柔性数组:
struct data_chunk sgl[];
旧代码只在栈上定义结构本体,但继续访问 sgl[0],造成内核栈越界,最终触发 Kernel Panic。
修复 patch:
/home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patch
该 patch 同时修复:
-
DMA TX 模板
-
DMA RX 模板
-
柔性数组空间分配
-
使用完成后的内存释放
应用命令:
cd /home/user/kernel-6.1git apply --check /home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patchgit apply /home/user/tools/linux/0003-fix-rk3576-dsmc-dma-template.patchRCU Stall
错误日志:
rcu_sched detected stallsCPU7 ... rockchip_panic_notify含义:
RCU stall 通常是前面 Kernel Panic 或 external abort 的后续表现,不是独立的 DSMC 故障。
某个 CPU 已经卡在 panic 处理流程,其他 CPU 等待 RCU 同步,因此持续打印 stall。
排查时应向前查找最早出现的日志:
- Asynchronous SError
- external abort
- stack\-protector
- Kernel panic
不要从 RCU stall 本身开始排查。
CPU 回环测试
前提:
-
FPGA 已 ready
-
DLL 训练成功
-
DSMC 驱动绑定成功
-
测试地址允许覆盖
命令:
ADDR=0x10001000OLD=$(devmem $ADDR 32)devmem$ADDR 32 0x55aa1234 devmem $ADDR 32 0xaa55abcddevmem$ADDR 32 “$OLD"0x55AA12340xAA55ABCD结论:
-
ARM → FPGA 写通路正常
-
FPGA → ARM 读通路正常
-
region0 地址译码正常
-
DQ/DQS 基本通信正常
常用命令
检查内核版本:
uname -a
检查 DSMC 日志:
dmesg | grep -i dsmc
检查全部相关异常:
dmesg | grep -i -E 'dsmc|dll|serror|abort|stack|panic|rcu'
检查驱动绑定:
test -L /sys/bus/platform/drivers/dsmc/2a280000.dsmc && echo "DSMC绑定成功" || echo "DSMC绑定失败"
检查设备节点:
find /dev/dsmc -type c 2>/dev/null
检查运行时设备树:
tr '\0' '\n' < /proc/device-tree/dsmc@2a280000/status