最近想找个 RISC-V 小板子玩一玩,搜寻一番发现大多都是搭载了多核异构 SoC 的嵌入式开发板,入手了一块 Milk-V Duo S 开发板。
其实 SpacemiT K1 的开发板更具吸引力,但是价格小贵
板子的基本使用方法官方文档 中写的比较详细,我这里主要记录一下使用 SDK 自行构建 SD 卡镜像遇到的一些问题;以及 Linux 启动后,如何扩展根分区和增加可用内存。
1 SDK(V2)编译
官方 SDK 基于 buildroot 构建,Dou S 推荐使用 V2 版本,链接 。
整个 SDK 工程十分复杂,塞进了 OpenSBI,U-Boot,Linux kernel,Buildroot 以及一些定制化硬件的驱动模块。同时给这个复杂的工程设计了一套复杂的构建流程,由各种 shell 脚本、python 脚本完成最终镜像的构建。
启动流程看起来是典型 FW_DYNAMIC 流程,ROM 固件跳转 OpenSBI,OpenSBI 引导 U-Boot(进入 S-mode),U-Boot 引导 Linux kernel,Buildroot 作为根文件系统。代码中也用了一些 ARM ATF 的术语,还没有细看。
总之,初步感觉以这套 SDK 的复杂(混乱)程度,对希望搞定制化开发的朋友算不上友好,初看起来有些“牵一发而动全身”的样子。
以下记录构建 Milk-V Duo S 无 WiFi/BT 版 SD 卡镜像遇到的一些小问题和解决方法。
1.1 编译过程记录
1.1.1 SDK 版本信息
SDK链接: https://github.com/milkv-duo/duo-buildroot-sdk-v2
main 分支:
1 2 3 4 5 6 commit 6f8962c394dd0a05729abb089f0feb7d5cc4aa5e (HEAD -> main, origin/main, origin/HEAD) Merge: 0545e9dc6 e7b0c7933 Author: carbon <[email protected] > Date: Tue Nov 4 13:38:24 2025 +0800 Merge branch 'develop'
develop 分支:
1 2 3 4 5 6 7 8 9 10 11 commit ad920f839277e566d9068989ca8d737483a4677d (HEAD -> develop, origin/develop) Author: carbon <[email protected] > Date: Tue Dec 30 17:35:00 2025 +0800 cvi_mpi: support st7701sn 2 lane lcd test command : sample_panel --panel=ST7701SN devmem 0x0a088094 32 0x0701000a Signed-off-by: carbon <[email protected] >
develop 分支看起来更新一点,也测试了一下,以下编译问题同时存在于 main 和 develop 分支。
其他问题参考文档: https://milkv.io/zh/docs/duo/getting-started/buildroot-sdk
值得一提的是 Milk-V 开发板的文档比较详细,论坛社区讨论也比较活跃。能在里面找到不少有参考价值的讨论。
1.1.2 主机环境
我使用 Debian 13 作为 Host 编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 ubuntu@debian ~ $ uname -a Linux debian 6.12.57+deb13-amd64 ubuntu@debian ~ $ cat /etc/os-release PRETTY_NAME="Debian GNU/Linux 13 (trixie)" NAME="Debian GNU/Linux" VERSION_ID="13" VERSION="13 (trixie)" VERSION_CODENAME=trixie DEBIAN_VERSION_FULL=13.2 ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/"
初次编译时,./build.sh 编译脚本会自动下载工具链和 Buildroot 软件包,网络要顺畅。
工具链:https://github.com/milkv-duo/host-tools.git
若工具链自动下载失败可以手动 git clone 至 SDK 根目录。Buildroot 软件包官方文档中也有离线下载方式可供参考。
官方文档中给出的安装命令:
1 2 3 4 5 6 sudo apt install -y pkg-config build-essential ninja-build automake autoconf libtool \wget curl git gcc libssl-dev bc slib squashfs-tools android-sdk-libsparse-utils \ jq python3-distutils scons parallel tree python3-dev python3-pip device-tree-compiler \ ssh cpio fakeroot libncurses5 flex bison libncurses5-dev genext2fs rsync unzip \ dosfstools mtools tcl openssh-client cmake expect python-is-python3 sudo pip install jinja2
在我的环境上需要略作调整,删去 libncurses5 和 python3-distutils,jinja2 使用 apt 而不是 pip 安装,还需要额外安装工具 xxd。修改过后的安装命令为:
1 2 3 4 5 sudo apt install -y pkg-config build-essential ninja-build automake autoconf libtool \wget curl git gcc libssl-dev bc slib squashfs-tools android-sdk-libsparse-utils jq \ scons parallel tree python3-dev python3-pip device-tree-compiler ssh cpio fakeroot \ flex bison libncurses5-dev genext2fs rsync unzip dosfstools mtools tcl openssh-client \ cmake expect python-is-python3 xxd python3-jinja2
至此准备工作结束,可以在 SDK 目录下直接使用命令 ./build.sh milkv-duos-musl-riscv64-sd 构建 SD 卡镜像,也可以用命令 ./build lunch 交互式地选择要使用的工具链和目标开发板。一切顺利的话 SD 镜像将会生成在 out/ 目录中。
1.1.3 错误解决
目前大部分编译报错都可以在 SDK github issues 中找到线索。
实在无法解决就使用 Docker 方式编译。
本节最后附有解决所有问题的 patch。
问题 1
报错信息(linux/cvi_type.h: No such file or directory):
1 2 3 4 5 6 7 8 9 ake[2]: Entering directory '/home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/modules/sys' ov_ov5647/ov5647_cmos.c:8:10: fatal error: linux/cvi_type.h: No such file or directory 8 | | ^~~~~~~~~~~~~~~~~~ compilation terminated. gcore_gc2083/gc2083_cmos.c:7:10: fatal error: linux/cvi_type.h: No such file or directory 7 | | ^~~~~~~~~~~~~~~~~~ compilation terminated.
这个问题有些奇怪,原本想改为单线程编译看下到底哪里出了问题,结果单线程编译反而能正常生成镜像,猜测应该是脚本编排的流程存在一些问题,导致并行编译时某些依赖文件尚未就绪。后来发现 issues#20 中也有人提到了这个现象。具体的修改位置在 build/envsetup_milkv.sh 文件中的 make all -j$(nproc),改为 make all -j1 即可。
问题 2
报错信息(undefined reference to `pcm_read’):
1 2 3 4 5 6 Run build_pqtool_server() function [riscv64-unknown-linux-musl-g++] cvi_streamer.o [riscv64-unknown-linux-musl-g++] main.o [riscv64-unknown-linux-musl-gcc] stream_porting.o /home/ronnie/code/duo-buildroot-sdk-v2/host-tools/gcc/riscv64-linux-musl-x86_64/bin/../lib/gcc/riscv64-unknown-linux-musl/10.2.0/../../../../riscv64-unknown-linux-musl/bin/ld: /home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/lib/libcvi_audio.so: undefined reference to `pcm_read' /home/ronnie/code/duo-buildroot-sdk-v2/host-tools/gcc/riscv64-linux-musl-x86_64/bin/../lib/gcc/riscv64-unknown-linux-musl/10.2.0/../../../../riscv64-unknown-linux-musl/bin/ld: /home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/lib/libcvi_audio.so: undefined reference to `pcm_open'
参考 issues#36 ,添加一个库解决。或者,参考 issues#6 ,直接在 build/envsetup_milkv.sh 中注释这个部分的编译,即注释这一行 build_pqtool_server || return $?
两种方法均测试可用,目前使用添加库的方法解决。
1.1.4 关闭 WiFi/BT 驱动加载
我买的是不带 WiFi 功能的板子,WiFi 芯片焊盘是空焊的。SDK 中的启动脚本默认会加载驱动,内核日志中会有一条找不到设备的报错,此处修改启动脚本直接不加载驱动。
修改 device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh 文件,注释掉文件末尾处的加载aic8800_*.ko模块的命令。
其实也尝试过修改内核 defconfig 不编译对应的驱动,没有生效,原因待查。
1.1.5 小结
以上修改总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 diff --git a/build/envsetup_milkv.sh b/build/envsetup_milkv.sh index d9a2e104f..b774c2f88 100644 --- a/build/envsetup_milkv.sh +++ b/build/envsetup_milkv.sh @@ -239,7 +239,7 @@ function build_middleware() make "$ROOTFS_DIR" || return "$?" pushd "$MW_PATH" - make all -j$(nproc) + make all -j1 test $? -ne 0 && print_notice "build middleware failed !!" && popd && return 1 make install DESTDIR="$SYSTEM_OUT_DIR" || return "$?" popd diff --git a/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile b/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile index 62e95b178..528d9414a 100644 --- a/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile +++ b/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile @@ -64,6 +64,7 @@ LIBS += -lcvi_ispd2 LIBS += -lraw_dump LIBS += -lcvi_dnvqe -lcvi_ssp2 -lteaisp LIBS += -lcviruntime -lcvikernel +LIBS += -ltinyalsa LOCAL_CFLAGS = $(DEFS) $(INCS) LOCAL_CPPFLAGS = -fno-use-cxa-atexit diff --git a/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh b/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh index e155c6e37..e19465950 100755 --- a/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh +++ b/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh @@ -22,14 +22,13 @@ gpio_b17=465 set_gpio ${gpio_b17} 0 # Host Wake BT -host_wake_bt=362 -set_gpio ${host_wake_bt} 1 +# host_wake_bt=362 +# set_gpio ${host_wake_bt} 1 # WIFI/BT Module -insmod /mnt/system/ko/aic8800_bsp.ko -sleep 0.5 -insmod /mnt/system/ko/aic8800_fdrv.ko +# insmod /mnt/system/ko/aic8800_bsp.ko +# sleep 0.5 +# insmod /mnt/system/ko/aic8800_fdrv.ko # Insmod PWM Module insmod /mnt/system/ko/cv181x_pwm.ko
2 根分区扩容
Duo S 镜像文件写入到 SD 卡后,根分区 / 只使用了 768M 空间,这与 SD 卡的容量大小无关,是 SDK 中生成镜像文件时设置的(device/milkv-duos-musl-riscv64-sd/genimage.cfg)。将根分区扩容至占满 SD 卡容量后,板上系统能使用更大的容量,SD 卡的空间也不至于白白浪费。
以下方法选择其一即可。
2.1 使用 parted 命令扩容
官方 SDK (duo-buildroot-sdk-v2)中默认编译了 parted 工具,扩容操作方便得多,按以下命令操作:
1 2 3 4 5 6 7 8 9 lsblk df -hTparted /dev/mmcblk0 resizepart 3 100% resize2fs /dev/mmcblk0p3 df -hT
设置过程图示:
2.2 使用 fdisk 命令扩容
如果 parted 工具不可用,还可选择 fdisk 工具“古法”扩容。扩容的原理是删除根分区,在原位置 上新建一个占满 SD 卡容量的新分区。操作的关键在于一定要原地新建,删除的分区和新建的分区的起始扇区号必须一致 。这样只修改了分区的信息,磁盘上的内容并没有改动。按以下命令操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 lsblk df -hTfdisk /dev/mmcblk0 p d 3 n p 3 266241 <回车> n <回车> w resize2fs /dev/mmcblk0p3 df -hT
设置过程图示:
注意图中只展示了扩容分区的步骤,后续仍需要使用 resize2fs 命令扩展文件系统。
3 释放 ION 内存
Duo S 开发板上 Linux 成功启动后,发现内存只有 316M 可用(板载内存 512M),搜索了一下发现 SDK 中预留了一部分内存(170M)用作 ION 内存,用于 Multimedia buffer,应该是给 SoC 内部的多媒体处理模块用的。我对这部分功能不感兴趣,尝试修改配置将这部分内存释放出来。
在 SDK 中搜索到 build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py 文件中定义了各部分内存的分配,直接将 ION 部分设置为 0,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 diff --git a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py index d18d314ca..10c1b4cfc 100644 --- a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py +++ b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py @@ -40,10 +40,10 @@ class MemoryMap: - ION_SIZE = 170 * SIZE_1M - H26X_BITSTREAM_SIZE = 2 * SIZE_1M + ION_SIZE = 0 * SIZE_1M + H26X_BITSTREAM_SIZE = 0 * SIZE_1M H26X_ENC_BUFF_SIZE = 0 - ISP_MEM_BASE_SIZE = 20 * SIZE_1M + ISP_MEM_BASE_SIZE = 0 * SIZE_1M FREERTOS_RESERVED_ION_SIZE = H26X_BITSTREAM_SIZE + H26X_ENC_BUFF_SIZE + ISP_MEM_BASE_SIZE
但是这样修改会导致启动时出现 OOPS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 [ 3.438039] cv181x-cooling cv181x_cooling: Cooling device registered: cv181x_cooling [ 3.468018] [INFO] Register SBM IRQ ################################### [ 3.468045] [INFO] pvctx->s_sbm_irq = 37 [ 3.483018] jpu ctrl reg pa = 0xb030000, va = (____ptrval____), size = 256 [ 3.494692] end jpu_init result = 0x0 [ 3.616765] cvi_vc_drv_init result = 0x0 [ 3.651499] Summary: [ 3.653825] [0] carveout heap size:0 bytes, used:0 bytes [ 3.659700] usage rate:-1%, memory usage peak 0 bytes [ 3.665266] [ 3.665266] Details: [ 3.665266] heap_id alloc_buf_size phy_addr kmap_cnt buffer name [ 3.678606] [ 3.680166] ion allocated len=0x1000 failed [ 3.684911] Summary: [ 3.687184] [0] carveout heap size:0 bytes, used:0 bytes [ 3.693033] usage rate:-1%, memory usage peak 0 bytes [ 3.698511] [ 3.698511] Details: [ 3.698511] heap_id alloc_buf_size phy_addr kmap_cnt buffer name [ 3.711787] [ 3.713588] ion allocated len=0x1000 failed [ 3.718357] Summary: [ 3.720906] [0] carveout heap size:0 bytes, used:0 bytes [ 3.726650] usage rate:-1%, memory usage peak 0 bytes [ 3.732127] [ 3.732127] Details: [ 3.732127] heap_id alloc_buf_size phy_addr kmap_cnt buffer name [ 3.745401] [ 3.746960] ion allocated len=0x4010 failed [ 3.751691] pstStCandiCornerCtrl->stMem.u64PhyAddr can't be 0! [ 3.757986] sys_ctx_mem_get() can't find it [ 3.762567] dmabuf_fd get failed, addr:0x14 [ 3.767141] sys_ctx_mem_get() can't find it [ 3.771719] dmabuf_fd get failed, addr:0xffffffe002a28294 [ 3.777584] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000078 [ 3.786925] Oops [#1] [ 3.789281] Modules linked in: cv181x_ive(FO+) cvi_vc_driver(FO) cv181x_jpeg(FO) cv181x_vcodec(FO) cv181x_tpu(FO) cv181x_clock_cooling(FO) cv181x_rgn(FO) cv181x_mipi_tx(FO) cv181x_vo(FO) cv181x_dwa(FO) cv181x_vpss(FO) cv181x_vi(FO) snsr_i2c(FO) cvi_mipi_rx(FO) cv181x_fast_image(FO) cv181x_rtos_cmdqu(FO) cv181x_base(FO) cv181x_sys(FO) [ 3.819888] CPU: 0 PID: 219 Comm: insmod Tainted: GF O 5.10.4-tag- #1 [ 3.827798] epc: ffffffdf809c909a ra : ffffffdf809c9066 sp : ffffffe0027e3a80 [ 3.835167] gp : ffffffe00098f2d8 tp : ffffffe0015bdc40 t0 : 0000000000000000 [ 3.842625] t1 : ffffffdf809d4600 t2 : 0000000000000000 s0 : 0000000000000000 [ 3.850085] s1 : 0000000000000000 a0 : 0000000000000000 a1 : 0000000000000000 [ 3.857543] a2 : ffffffdf809d4608 a3 : 0000000000000000 a4 : ffffffffffffffd0 [ 3.865003] a5 : 0000000000000000 a6 : 0000000000000007 a7 : ffffffdf809d4608 [ 3.872462] s2 : ffffffe000b68400 s3 : ffffffe000990088 s4 : ffffffdf809cc800 [ 3.879921] s5 : 0000000000000015 s6 : ffffffe000991068 s7 : ffffffe00096fe08 [ 3.887381] s8 : 0000003fe43439e0 s9 : 0000003fffa38da0 s10: 0000000000000014 [ 3.894839] s11: 0000000000000001 t3 : 0000000000000000 t4 : 0000000038000000 [ 3.902297] t5 : 0000000030000000 t6 : 0000000066000000 [ 3.907785] status: 0000000200000120 badaddr: 0000000000000078 cause: 000000000000000d [ 3.915959] Call Trace: [ 3.918519] [<ffffffdf809c909a>] _sys_ion_free+0x68/0x196 [cv181x_sys] [ 3.925398] [<ffffffdf80d89b16>] stcandicorner_workaround+0xb0/0xce [cv181x_ive] [ 3.933060] [<ffffffe0002f5752>] proc_alloc_inum+0x1e/0x38 [ 3.938732] [<ffffffe0002f5854>] proc_register+0x14/0xe0 [ 3.944347] [<ffffffdf80d7b6de>] cvi_ive_probe+0x1e4/0x1ea [cv181x_ive] [ 3.951195] [<ffffffe000408dee>] platform_drv_probe+0x24/0x54 [ 3.957143] [<ffffffe000407a44>] really_probe+0x210/0x33e [ 3.962728] [<ffffffe000407f22>] device_driver_attach+0x2c/0x48 [ 3.968848] [<ffffffe000408036>] __driver_attach+0xf8/0xfe [ 3.974519] [<ffffffe000407f3a>] device_driver_attach+0x44/0x48 [ 3.980639] [<ffffffe00040603a>] bus_for_each_dev+0x46/0x76 [ 3.986402] [<ffffffe000406e70>] bus_add_driver+0x15a/0x186 [ 3.992170] [<ffffffe000408494>] driver_register+0x7c/0xa2 [ 3.997852] [<ffffffe000227104>] do_one_initcall+0x58/0xf2 [ 4.003525] [<ffffffe000269734>] do_init_module+0x3e/0x174 [ 4.009195] [<ffffffe00026b1da>] __do_sys_finit_module+0x92/0xb6 [ 4.015407] [<ffffffe00026b1fc>] __do_sys_finit_module+0xb4/0xb6 [ 4.021613] [<ffffffe00022805e>] check_syscall_nr+0x1e/0x22 [ 4.031228] ---[ end trace b61bbf8cb217d7b4 ]--- Segmentation fault [ 4.049898] sh (194): drop_caches: 3 Starting app... [root@milkv-duo]~#
看起来应该是在初始化这些 SoC 内部模块时要分配 ION 内存,但已经没有 ION 内存可用,分配失败导致的错误。
按道理应当把这些模块的初始化代码删除或禁用,不过我暂时不清楚这些模块具体是做什么用的,甚至不知道名字,暂时还是保留一部分 ION 内存作为 workaround 消除这个错误吧。
从错误日志中可以看到,类似 ion allocated len=0x1000 failed 的日志打印了三次,猜测共做了三次分配,总计需要内存约 24K(0x1000 + 0x1000 + 0x4010 = 0x6010 = 24592B),预留 32K 内存给 ION,应当可以满足初始化时的分配。
做如下修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 diff --git a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py index d18d314ca..19fb26370 100644 --- a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py +++ b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py @@ -40,10 +40,10 @@ class MemoryMap: - ION_SIZE = 170 * SIZE_1M - H26X_BITSTREAM_SIZE = 2 * SIZE_1M + ION_SIZE = 32 * SIZE_1K + H26X_BITSTREAM_SIZE = 0 * SIZE_1M H26X_ENC_BUFF_SIZE = 0 - ISP_MEM_BASE_SIZE = 20 * SIZE_1M + ISP_MEM_BASE_SIZE = 0 * SIZE_1M FREERTOS_RESERVED_ION_SIZE = H26X_BITSTREAM_SIZE + H26X_ENC_BUFF_SIZE + ISP_MEM_BASE_SIZE
修改后,启动时不再有 OOPS 错误日志。
编译镜像过程中会生成各阶段内存分配表:build/output/sg2000_milkv_duos_musl_riscv64_sd/cvi_board_memmap.txt,查看该表可以看到各部分内存分配起始地址、结束地址以及大小等明细。
1 2 3 4 5 6 7 kernel stage: | Name | KERNEL_MEMORY | MONITOR | OPENSBI_FDT | FRAMEBUFFER | H26X_BITSTREAM | H26X_ENC_BUFF | ION | ISP_MEM_BASE | FREERTOS | | Start Address | 0x80000000 | 0x80000000 | 0x80080000 | 0x9f628000 | 0x9fdf8000 | 0x9fdf8000 | 0x9fdf8000 | 0x9fdf8000 | 0x9fe00000 | | End Address | 0x9fe00000 | 0x80000000 | 0x80080000 | 0x9fdf8000 | 0x9fdf8000 | 0x9fdf8000 | 0x9fe00000 | 0x9fdf8000 | 0xa0000000 | | Size | 0x1fe00000 | 0x0 | 0x0 | 0x7d0000 | 0x0 | 0x0 | 0x8000 | 0x0 | 0x200000 | | Size(M/K/B) | 510M | 0M | 0M | 8000K | 0M | 0M | 32K | 0M | 2M | ----------------------------------------------------------------------------------------------------------------------------------------------------
释放 ION 内存后,Linux 下使用 free -mh 命令查看可用内存达到 485M,根据分配表内核拥有的内存应该是 510M,有 2M 内存划给了 FreeRTOS。这中间约 25M 的差值应该都是由内核使用,这篇帖子 里有一些讨论可供参考。
后续或许可以精细的分析一下,内核到底把内存用在哪里了。 TODO