[maixpy3 axpi] 关于爱芯 ax620a 移植 debian11 系统这件事
2022 年 maix3 axpi 出来了,如今在 sipeed 开源硬件的 maix 家族成员再添一位中高端的 linux ai soc ,是来自 爱芯 爸爸的芯片,这个芯片体验下来有几个可以吹的点,比如 sipeed 天生的性价比优势,产品详情请看 http://wiki.sipeed.com/m3axpi
maix 系列就是主推视觉、听觉、传感器类的 AI 应用开发。
- 全功能套餐(带屏幕带摄像头带外壳)产品的 549 价格有不少优势,比如地平线x3、rv1126 都在 599 ~ 899 附近。
- 低功耗设计的4核a7整板加上所有外设(wifi、摄像头、8寸屏)功耗个人实测不会超过 1.1a ,一根 usb3.0 就可以带起来。
- 使用 sd 卡 boot 分区启动 rootfs 的根文件系统,拥有主流的 linux 开发体验,也是本文的内容。
- 最差的摄像头模组也有2022年中低端手机端端相机成像效果,这在开源硬件领域里是非常难得的效果了,也成功让我知道了原来一个摄像头模组的镜头比硬件板子本身还贵(摊手)。
缺点也很明显,芯片开发过于商业,对开源极其不友好,比如资料要签了 nda 才能拿到,有点类似博通与树莓派的关系了,原厂的开发环境建立在 Windows 之上使用 linux 开发,bsp sdk 源码里制造了太多重复的代码碎片(例如是要用 app c++ 还是 msp/sample c ),而烧录工具要用 Windows 烧录,可开发又要 Linux ,也不支持 macos 和 linux 的编译烧录,难道原厂的开发人员都是在 Windows 远程一台 Linux 服务器上开发的吗?(摊手
除了芯片底层开发困难和文档难用以外,其他都还好,至少到了 Linux 应用层就没有这些蠢事了,比如 bsp sdk 提供的是 busybox 或 buildroot 这种原始根文件系统,虽然效率高性能好,但也是真的难用。
不过现在这些问题来到了 sipeed 开源都已经被解决掉了,听我一一道来:
移植 debian 系统的过程中,带来哪些好处呢?
- 烧录的问题不再需要原厂支持,所有平台都可以通过 dd 一个磁盘完成烧录。
- 当拥有了 apt 软件管理后,就能拥有绝大多数软件的直接开箱即用。
- 用户也能基于此发布镜像了,不再需要官方或原厂的支持,只需要将系统重新 dd 导出即可。
要完成这一切,需要做到两件事
- 芯片支持从 sd 卡启动系统
- 适配标准的 debian 源快照
一些废话
感觉这几年的嵌入式领域的中文开发资料非常的稀少,也非常的隐晦,大多都是内容都是我还在校时期留下的,如今我也投身于这个行业了,趁着有空写一篇移植 debian11 的记录,给后人留下一些资料参考吧。
其实不难,不要神化了软件工程的开发工作,它虽然是有一点基础门槛,但这并不是阻碍用户(开发者)认知这个事物的门槛,可能有的人会把这个当做一种生产资料的宝贝藏着掖着,但在我看来,本来就是取之开源的东西,何必刻意去给后人制造门槛呢?未免也太小家子气了,难道这么害怕别人超越自己吗?
我认为任何想要阻止事物发展的都是要被历史车轮碾翻的大恶人,没有人可以阻止开源技术的发展,也没有必要去阻止,更好的代码开发效率可以给我们节约大量的生命,学习技术的资料应该说越来越通熟易懂浅显可读的。
差不多就进入正题吧,要完成这个任务,需要了解一些嵌入式 linux 的基础知识,比如:
随便知道一种被主线化的 linux 板子的启动过程,比如全志早年的那些板子,虽然不完美,但比较容易便宜搞到的板子,而且代码也很容易就能获得,像树莓派、香橙派、荔枝派、香蕉派家的板子都挺好的,毕竟以前的板子,在现在2022年来说,相对便宜好获取用于学习。
你会发现大多都有一个 sd 卡槽,然后里面放了一些 boot / spl / u-boot / kernel / rootfs 的文件,这些文件都是在启动过程中被加载到内存中的,然后执行,这些文件都是在 sd 卡中的,所以你可以把 sd 卡拔出来,然后把它插到其他的板子上,然后启动,这个板子就能启动了,这就是 sd 卡启动的原理。
当然能不能启动还是得看这个芯片的 boot0 这个部分是源自于芯片内部写死的引导,通常会涉及一种方式,比如芯片要从地址的多少开始执行程序,这一切早就在原厂推出芯片的时候准备好了,所以我们要做的就是替换这个启动引导要去的下一个程序。
uboot 也好,gurb 也罢,都是一种根文件系统的引导程序,此时 U-Boot = BIOS + GRUB ,然后有得小机灵就会问能不能用 uboot 来启动 x86 呢?那肯定是可以的呀,官方文档的这里就有介绍如何引导 U-Boot on x86,不过大多数搞桌面系统软件的的,不是很理解底层硬件的初始化都需要做什么,所以也不会浪费生命去琢磨这些。
如今嵌入式 arm linux 也越来越具有性价比了,不妨看看如今的 Windows On arm 和 apple arm m1 ,可见 arm 在能效比上还有不少优势的。
准备工作(uboot)
不同厂家的芯片启动过程和源码略微不同,但目的肯定都是一样的,不过能不能拿到资料却也成为了一种门槛。
爱芯是一个比较封闭的芯片厂家,它开放的内容非常有限,但运气很好,它的文档有提到一些关于启动流程的事情:
也就是原厂已经支持 uboot 和 kernel 了,所以我们就可以不用从零开始移植 boot 啦,大部分芯片原厂都会提前做好启动引导和内核支持,能拿到资料的开发者基本不用去逆向开发,既然已经支持 uboot 则意味着我们只需要修改 uboot 的部分就可以让系统从 sd 卡启动了。
首先确认 sd 启动的流程是否存在,通常 uboot 里会有判断启动介质的基本情况,这会影响着启动的优先级,对于从 emmc 还是从 mmc 还是从 nfs 启动,至于启动优先级上如何决定的呢?像爱芯这里是直接硬件决定的,而全志是依次判断介质的。
其实代码已经很明显了,只要主体框架不变,小范围的代码修补即可。
现在我们关注 sd 的启动部分,可以看到:
它(bootm)想要从 mmc 1:1 这个磁盘的 fat 分区里得到 kernel.img 和 dtb.img 两个文件用来启动,具体地址我这里就不能截了,至于什么是 mmc 可以看这个了解一些基本操作命令嵌入式Linux--U-Boot(四)MMC命令使用,所以要确保 mmc 能够获取指定 sd 卡槽的卡里的分区以及内容。在我拿到的资料的时候,芯片已经支持从 sd 卡启动,同时代码也已经存在了,但运行判断 sd 卡分区和读取文件的驱动有一点问题,修复一下这个部分的 sd 卡驱动,让 mmc list 能够读取到卡里的文件即可,如果后来的代码已经修复了这部分,那你只需要按这里的代码的要求,去给 sd 卡创建 fat 分区供 uboot 读取即可。
如果某些芯片什么代码也没写,那你可以参考这个流程来实现,目的就是要从某个介质上获取我要启动的文件,然后离开 uboot 正式进入系统,当然并不是所有的芯片都这么完美,但这个流程大同小异,只要知道了要做什么就按这个流程去完成就行,这里顺便提一下 grub2 的用法 grub2各种手动命令引导教程(引导Ubuntu及安装镜像,arch Linux及安装镜像,Windows及winPE)。
在这时你会发现底层代码需要重写的情况不多,大多数情况下,只需要去移植去适配去修改已有的代码让它运行起来就行,除非有 bug 要修,一般都不需要写太多代码,这也需要你清楚的知道自己要做什么才能达到目的,某种意义上讲这可能也是一种经验。
移植系统(rootfs)
注意,这里强调的是移植不是开发,也不是逆向,如今 linux arm 平台是一个开发多年已经很成熟的系统环境了,不需要从头开始编译开发,只需要做系统适配即可。
那么要如何移植一个 debian 系统呢?或者说移植其他发行版 linux 系统呢?
可见原厂为我们提供了 busybox 的最小 rootfs 文件系统移植参考,一般来说有这个就已经足够了,因为这意味着原厂已经打通了 uboot 、 kernel 、rootfs 的全套启动流程。
那么移植 debian 我们需要做什么呢?
-
获取 debian 系统
-
启动 debian 系统。
获取 debian 系统
根文件系统从哪里来呢?
可以从 docker 虚拟机里来。
目前常用的 Linux 发行版主要包括 Debian/Ubuntu 系列和 CentOS/Fedora 系列。
前者以自带软件包版本较新而出名;后者则宣称运行更稳定一些。选择哪个操作系统取决于读者的具体需求。
使用 Docker,读者只需要一个命令就能快速获取一个 Linux 发行版镜像,这是以往包括各种虚拟化技术都难以实现的。这些镜像一般都很精简,但是可以支持完整 Linux 系统的大部分功能。
本章将介绍如何使用 Docker 安装和使用 Busybox、Alphine、Debian/Ubuntu、CentOS/Fedora 等操作系统。
从软件的世界来看,所谓的根文件系统,就像 Windows 电脑需要一个 C 盘系统文件和 BOIS + UEFI 启动引导文件。
在这里根文件系统就是你的 C 盘,那么 docker 虚拟机也可以是你的 C 盘,也就是,你可以把 docker 的文件直接原封不动的拷贝到板子上运行,当然这是可行的,但不是最佳实践,尤其是这里还需要使用 arm 的 docker 虚拟机。
但这是最快的验证硬件的方法,比如将 docker arm64 debian 系统的 ld 加载器放到板子上可以当 ldd 看程序依赖就说明验证通过了。
那么还可以从哪里来呢?
docker 都是别人配好放到网上的,原始的文件其实官网都有,主要分两类。
- 提供快照的
- 需要编译的
比如 debian 快照源 是有快照备份的,可以直接使用 debuerreotype 提供的一系列命令拉取某个架构已经编译好并归档的干净的 debian 根系统。
例如以下是我的一个操作命令示意,在 ubuntu20 下生成一个 rootfs 。
$ git clone https://github.com/debuerreotype/debuerreotype
$ cd debuerreotype
$ su root
$ wget https://ftp-master.debian.org/keys/release-11.asc
$ gpg --dearmor release-11.asc
$ mv release-11.asc.gpg /root/.gnupg/
$ rm rootfs/ -rf
$ debuerreotype-init --arch=armhf --keyring /root/.gnupg/release-11.asc.gpg rootfs bullseye 2022-06-15T03:14:43Z
拿下来的 rootfs 验证一下加载器,可以看到没问题,是 armhf 的快照,也就是拿到这个就完成了最基础的 debian 系统移植。
但这是建立在 armhf 有快照的基础上,如果没有 armhf(aarch64) 的分支呢,如果不希望使用 glibc 的 ld 呢?那这时候就需要编译生成了,具体怎么做呢?这里我推荐个人喜欢的 alpine 和 openwrt 吧。
Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,但功能上比 busybox 又完善的多,因此得到开源社区越来越多的青睐。在保持瘦身的同时,Alpine 还提供了自己的包管理工具 apk,可以通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息,也可以直接通过 apk 命令直接查询和安装各种软件。
Alpine 由非商业组织维护的,支持广泛场景的 Linux发行版,它特别为资深/重度Linux用户而优化,关注安全,性能和资源效能。Alpine 镜像可以适用于更多常用场景,并且是一个优秀的可以适用于生产的基础系统/环境。
简单来说,小巧精致的 Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,还提供了自己的包管理工具 apk 这两个优势就足以让人心动,同样的还有 debian 有 apt 和 openwrt 有 opkg 这些拥有软件源的 linux 系统。
如今已经有非常完整的使用说明了 https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package ,就简单说一下源码编译具体要做什么。
拿到工程目录 https://gitlab.alpinelinux.org/alpine/aports 这里是软件源的构建列表
如果我们想要更换编译链或者改变平台架构,那么我们就需要从工程目录开始编译,按要求准备好环境,如 apk abuild 命令,官方现在推荐采用 docker 进行进行构建。
拿一个构建 blueman 的软件包的编译规则为例:
https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/blueman/APKBUILD
# Contributor: knuxify <knuxify@gmail.com>
# Maintainer: knuxify <knuxify@gmail.com>
pkgname=blueman
pkgver=2.3.2
pkgrel=0
pkgdesc="GTK+ Bluetooth Manager"
url="https://github.com/blueman-project/blueman"
arch="all !s390x !riscv64" # blocked by networkmanager/polkit
license="GPL-3.0-or-later"
depends="bluez dbus gtk+3.0 python3 py3-cairo py3-gobject3"
makedepends="
bluez-dev
cython
glib-dev
libtool
polkit-dev
python3-dev
py3-gobject3-dev
"
checkdepends="networkmanager libpulse-mainloop-glib"
subpackages="$pkgname-dev $pkgname-doc $pkgname-lang"
source="https://github.com/blueman-project/blueman/releases/download/$pkgver/blueman-$pkgver.tar.xz
dont-rerun-plugin-tests.patch"
build() {
./configure \
--build=$CBUILD \
--host=$CHOST \
--prefix=/usr \
--sysconfdir=/etc \
--mandir=/usr/share/man \
--localstatedir=/var \
--disable-schemas-compile
make
}
check() {
PYTHONPATH=module/.libs python3 -m unittest
}
package() {
make DESTDIR="$pkgdir" install
rm -rf "$pkgdir"/usr/lib/systemd
}
sha512sums="
13760def19951bcb4582dbe142259256826a0f50e6c34e56c61c6f890f4e2e08085733f2480ad4c95ee52ec616cbb7ddd3a946634f042d80692cd37b57207cc0 blueman-2.3.2.tar.xz
a3ec5d05c0f32353c1eb933bf1acdcc1f8bede31c12132bd6b30adb46111ef88b7586f9f7c574a95b9dd4877f3977514389e2b565c1029835db920f30a01fb72 dont-rerun-plugin-tests.patch
"
从这里可以得知几个信息:
- 如何获取软件包 blueman-2.3.2.tar.xz 的源码
- 需不需要打补丁 dont-rerun-plugin-tests.patch
- 还不支持在 s390x 和 riscv64 下编译通过
- 如何编译(build)、如何打包(package)、有哪些依赖(makedepends)、如何测试
这些就是使用源码编译构建库的方法,官方提供的快照也是这么来的,如果是 openwrt 也是同理,不妨直接拿到源码设置编译链工具进行编译。
https://github.com/openwrt/openwrt
Quickstart
Run ./scripts/feeds update -a to obtain all the latest package definitions defined in feeds.conf / feeds.conf.default
Run ./scripts/feeds install -a to install symlinks for all obtained packages into package/feeds/
Run make menuconfig to select your preferred configuration for the toolchain, target system & firmware packages.
Run make to build your firmware. This will download all sources, build the cross-compile toolchain and then cross-compile the GNU/Linux kernel & all chosen applications for your target system.
而 openwrt 的软件源维护的不算很好,许多包都需要自己编译生成,至少没有上面 apt 和 apk 的软件数量多,但比较容易被自定义,类似的还有 buildroot 这种更原始的根文件系统,接近 busybox 只送你一个 shell 的程度了,剩下的全部靠自己。
比如说我在做 maixpy3 的时候,需要手写 openwrt 软件包的编译规则,关于这个可以参考学习这篇文章
openwrt 的软件编译规则示例:
#
# Copyright (C) 2015-2017 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=libuv
PKG_VERSION:=1.42.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=http://dist.libuv.org/dist/v$(PKG_VERSION)/
# PKG_HASH:=61a90db95bac00adec1cc5ddc767ebbcaabc70242bd1134a7a6b1fb1d498a194
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-v$(PKG_VERSION)
PKG_MAINTAINER:=Marko Ratkaj <marko.ratkaj@sartura.hr>
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_CPE_ID:=cpe:/a:libuv_project:libuv
CMAKE_INSTALL:=1
CMAKE_BINARY_SUBDIR:=out/cmake
PKG_BUILD_PARALLEL:=1
include $(BUILD_DIR)/package.mk
include $(BUILD_DIR)/cmake.mk
define Package/libuv
SECTION:=libs
CATEGORY:=Libraries
TITLE:=Cross-platform asychronous I/O library
URL:=https://libuv.org/
DEPENDS:=+libpthread +librt
ABI_VERSION:=1
endef
define Package/libuv/description
libuv is a multi-platform support library with a focus on asynchronous I/O. It
was primarily developed for use by Node.js, but it's also used by Luvit, Julia,
pyuv, and others.
endef
CMAKE_OPTIONS += -DBUILD_TESTING=OFF
define Build/InstallDev
$(call Build/InstallDev/cmake,$(1))
$(SED) 's,/usr/include,$$$${prefix}/include,g' $(1)/usr/lib/pkgconfig/libuv.pc
$(SED) 's,/usr/lib,$$$${prefix}/lib,g' $(1)/usr/lib/pkgconfig/libuv.pc
endef
define Package/libuv/install
$(INSTALL_DIR) $(1)/usr/lib/
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libuv.so* $(1)/usr/lib/
endef
$(eval $(call BuildPackage,libuv))
也是同样的方式生成一个 ipk 包给用户使用 opkg -i xxx.ipk 的方式安装,这和 deb 使用 dpkg 安装也是一样的结构。
使用这个方式编译出来后会得到一个类似于 rootfs 的目录,与 debian 的快照同理,取出来当做系统启动就行。
启动 debian 系统。
接下来问题就来到了,为什么得到一个 rootfs 系统就可以启动呢?
它需要解决两个问题:
- 根文件系统在哪?
这就要回到 uboot 在启动时提供的字符串了(BOOTAGRS_SD)。
/* bootargs for SD */
#define BOOTAGRS_SD "mem=1024M initcall_debug=0 loglevel=0 ax_boot_delay=0 vmalloc=768M console=ttyS0,115200n8 earlyprintk=dw_uart, init=/sbin/init noinitrd root=/dev/mmcblk2p2 rw rootdelay=3 rootfstype=ext4"
可以从这里读到两个信息:
根文件系统在 root=/dev/mmcblk2p2
文件系统为 rootfstype=ext4
要启动的程序 init=/sbin/init
当然其他的信息是给 uboot 配置用的,在本文这里并不重要,比如 vmalloc=768M 会影响系统里的 free 剩余内存,这也是 ddr 贴的是 2g ,但你用不了 2g 的原因,这些是与系统息息相关的问题了,通常是一些硬解设备需要连续的物理内存之类,故意不留给用户空间使用。
- 系统的入口在哪?
已知要启动的程序 init=/sbin/init
那么这个系统的入口程序,可以是 busybox 也可以是 systemd ,详细可以看这个了解 System V init启动与Busybox init启动对比,这里就不展开讲启动过程了,它主要做在各个运行级别中进行初始化工作,包括: 启动交换分区;检查磁盘;设置主机名;检查并挂载文件系统;加载并初始化硬件模块。
这两套显然是前者简单快速,但后者是目前桌面系统的主流,各有各的优劣吧,后者主要解决的重点是启动过程中各种程序的依赖和优先级问题(给无脑用户),前者只有简单的 rcS 控制启动过程(需要十分了解自己的系统)。
所以链接它到期望的 /sbin/init
就行,当然你也可以修改成自己喜欢的程序路径,这个不绝对,只是约定俗成变成这样了。
完成了这两件事,就可以顺利进入系统了,可喜可贺可喜可贺。
但是你以为结束了吗?
接下来你要开始修 systemed 启动服务,修 dev 设备,修时间同步,修依赖环境,修分区挂载,修软件apt源,修busybox命令。
剩下的问题就不展开细说了,反正启动系统以后,使用到哪就修哪,都是一些繁琐的体力活了,也不是本文的重点了,我觉得修各种软件的错误是挺浪费生命的。
后记
本文介绍了如何移植一个根文件系统,从整个流程看下来其实并不难,都是小修小改就可以完成的任务,一方面我们要感谢爱芯原厂做的大量工作,另一方面我们要感谢开源世界为我们带来了这么多开放开源的软件。
如果没有这些前人的工作,我们可能要花很长的时间才能完成这个任务,显然这在中国这是行不通的,摊手。
时间就是金钱,我的朋友。
附录:扩充 rootfs img 大小
如果 4G 渐渐不够用了,可以用下面这个命令拓一下空间,可以接受 只有 rootfs 或 boot + rootfs 的分区,会在尾部拓展未使用的空间。
- 给 img 文件追加空间
qemu-img resize sipeed_ax620a_debian11_*.img +3G
- 然后拓展分区和文件系统
e2fsck -f sipeed_ax620a_debian11_*.img
resize2fs sipeed_ax620a_debian11_*.img
也可以直接用 disk 分区工具图形调整。