基于 rk3568 的 Dayu200 快速上手 (OpenHarmony 虚拟机)

本文档旨在为开发者详细介绍在 Dayu200 开发板上部署并启动 OpenHarmony 虚拟机的完整流程。

Dayu200 开发板介绍

alt text

alt text

上述需要用到的接口有:DC12V电源、Debug串口(用于Serial串口)、USB3.0接口(用于传输文件)、Ethernet接口(用于连接网线)

为进行设备调试与查看启动日志,需要通过开发板发货提供的数据线,通过电脑的USB接口连接到开发板的调试串口。

默认串口参数如下:

参数项参数值
波特率 (Baud Rate)1500000
数据位 (Data Bits)8
停止位 (Stop Bits)1
校验位 (Parity)None

OpenHarmony系统准备

准备Ubuntu20.04的VMware虚拟机环境(在WSL中无法编译)

sudo apt-get update; sudo apt-get install binutils; sudo apt-get install binutils-dev; sudo apt-get install git; sudo apt-get install git-lfs; sudo apt-get install gnupg; sudo apt-get install flex; sudo apt-get install bison; sudo apt-get install gperf; sudo apt-get install build-essential; sudo apt-get install zip; sudo apt-get install curl; sudo apt-get install zlib1g-dev; sudo apt-get install gcc-multilib; sudo apt-get install g++-multilib; sudo apt-get install libc6-dev-i386; sudo apt-get install libc6-dev-amd64; sudo apt-get install lib32ncurses5-dev; sudo apt-get install x11proto-core-dev; sudo apt-get install libx11-dev; sudo apt-get install lib32z1-dev; sudo apt-get install ccache; sudo apt-get install libgl1-mesa-dev; sudo apt-get install libxml2-utils; sudo apt-get install xsltproc; sudo apt-get install unzip; sudo apt-get install m4; sudo apt-get install bc; sudo apt-get install gnutls-bin; sudo apt-get install python3.9; sudo apt-get install python3-pip; sudo apt-get install ruby; sudo apt-get install genext2fs; sudo apt-get install device-tree-compiler; sudo apt-get install make; sudo apt-get install libffi-dev; sudo apt-get install e2fsprogs; sudo apt-get install pkg-config; sudo apt-get install perl; sudo apt-get install openssl; sudo apt-get install libssl-dev; sudo apt-get install libelf-dev; sudo apt-get install libdwarf-dev; sudo apt-get install u-boot-tools; sudo apt-get install mtd-utils; sudo apt-get install cpio; sudo apt-get install doxygen; sudo apt-get install liblz4-tool; sudo apt-get install openjdk-8-jre; sudo apt-get install gcc; sudo apt-get install g++; sudo apt-get install texinfo; sudo apt-get install dosfstools; sudo apt-get install mtools; sudo apt-get install default-jre; sudo apt-get install default-jdk; sudo apt-get install libncurses5; sudo apt-get install apt-utils; sudo apt-get install wget; sudo apt-get install scons; sudo apt-get install python3.9-distutils; sudo apt-get install tar; sudo apt-get install rsync; sudo apt-get install git-core; sudo apt-get install libxml2-dev; sudo apt-get install lib32z-dev; sudo apt-get install grsync; sudo apt-get install xxd; sudo apt-get install libglib2.0-dev; sudo apt-get install libpixman-1-dev; sudo apt-get install kmod; sudo apt-get install jfsutils; sudo apt-get install reiserfsprogs; sudo apt-get install xfsprogs; sudo apt-get install squashfs-tools; sudo apt-get install pcmciautils; sudo apt-get install quota; sudo apt-get install ppp; sudo apt-get install libtinfo-dev; sudo apt-get install libtinfo5; sudo apt-get install libncurses5-dev; sudo apt-get install libncursesw5; sudo apt-get install libstdc++6; sudo apt-get install gcc-arm-none-eabi; sudo apt-get install vim; sudo apt-get install ssh; sudo apt-get install locales; sudo apt-get install libxinerama-dev; sudo apt-get install libxcursor-dev; sudo apt-get install libxrandr-dev; sudo apt-get install libxi-dev

将python和python3软链接到3.8

sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.8 1
mkdir ohos5
cd ohos5
repo init -u git@gitee.com:openharmony/manifest.git -b refs/tags/OpenHarmony-v5.1.0-Release --no-repo-verify
repo sync -c
repo forall -c 'git lfs pull'

安装hb编译工具

python3 -m pip install --user build/hb
vim ~/.bashrc
export PATH=~/.local/bin:$PATH
source ~/.bashrc

在容器中源码目录执行

bash build/prebuilts_download.sh

使用命令行脚本编译:

./build.sh -p rk3568

如果期间存在

[OHOS INFO] [NINJA] [0/1] Regenerating ninja files
[OHOS INFO] [NINJA] [0/2] Regenerating ninja files
[OHOS INFO] [NINJA] [0/3] Regenerating ninja files
[OHOS INFO] [NINJA] [0/4] Regenerating ninja files

重复Regenerating ninja files的问题,修复所有文件的时间戳

find . -type f -exec touch {} +

然后重新编译成功,13900HX 花费3.5个小时编译OpenHarmony标准系统的rk3568

alt text

在这里HiHope_DAYU200/烧写工具及指南/windows/RKDevTool.exe · HiHope开源社区/Docs - 码云 - 开源中国下载烧录工具

安装对应的驱动程序,电脑连接开发板后,显示发现一个MASKROM设备 alt text

按住VOL-/RECOVERY 按键(图中标注的①号键) 和 RESET 按钮(图中标注的②号键)不松开, 烧录工具此时显示“没有发现设备” ;

alt text

松开 RESET 键, 烧录工具显示“发现一个 LOADER 设备”, 说明此时已经进入烧写模式,点击执行开始烧录 alt text

这样就能在开发板上运行原始的OpenHarmony系统了。 以下是OpenHarmony编译出来的产物 alt text

核心流程:U-Boot → 从存储设备加载 boot_linux.img → 解析并加载内核、设备树、ramdisk → 设置启动参数(bootargs)→ 启动 Linux 内核

在 RK3568 平台上,对应的启动镜像是 boot_linux.img,一个打包镜像,它把启动系统所需的多个组件打包在一起,方便 U-Boot 一次性加载。它包含以下三个核心部分:

文件作用来源
ImageLinux 内核镜像(未压缩的可执行镜像)由 kernel 源码编译生成,路径通常是 out/kernel/.../Image
toybrick.dtb设备树二进制文件(Device Tree Blob),描述硬件信息.dts 文件编译生成,如 rk3568-toybrick.dtstoybrick.dtb
ramdisk.img初始化内存盘镜像,包含最基础的根文件系统(如 /init 脚本)由 OpenHarmony 编译系统生成,使用 cpio 格式打包

生成boot_linux.img的脚本代码在device/soc/rockchip/common/sdk_linux/scripts/mkimg这个路径下,使用的是 mkbootimg 工具,生成的是 Android 格式镜像,略不同于mkimage工具。两种格式的镜像都能被uboot启动

u-boot 启动时,会设置 bootargs 字符串,类似这样

setenv bootargs "initrd=0x84000000,0x292e00 init=/init blkdevparts=mmcblk0:1M(boot),15M(kernel),200M(system)... hardware=Hi3516DV300 root=/dev/ram0 rootfstype=ext4 default_boot_device=soc/1010000.himci.eMMC"
名称示例说明
initrd0x84000000,0x292e00指定 initramfs 的地址和大小
init/init指定内核启动后运行的第一个程序
blkdevpartsmmcblk0:1M(boot),15M(kernel),
200M(system),200M(vendor),
2M(misc),20M(updater),-(userdata)
告诉内核 eMMC / SD 卡上的分区是怎么划分的
hardwareHi3516DV300, rk3568告诉内核当前硬件平台
root/dev/ram0, 或 root=PARTUUID=...告诉内核从哪个设备加载根文件系统
rootfstypeext4根文件系统的类型
default_boot_devicesoc/1010000.himci.eMMC默认启动设备路径,示例中这是一个设备树节点路径,指向 SoC 上的 eMMC 控制器
ohos.required_mount.xxx/dev/block/platform/soc/1010000.himci.eMMC/by-name/xxx@usr@ext4@ro,barrier=1@wait,requiredOpenHarmony 定义的一些必须挂载的分区,比如 system, vendor, userdata 等。

init 进程在系统启动初期,必须先找到并挂载 systemvendor 等关键分区,才能继续启动系统。

OpenHarmony通过TFTP裸启动

首先在电脑上配置好TFTP的运行环境,接下来尝试按照TFTP和定制设备树直接启动OpenHarmony

setenv serverip 192.168.1.10;
setenv ipaddr 192.168.1.20;
setenv loadaddr 0x61000000;
setenv fdt_addr 0x60000000;
setenv ramdisk_addr 0x0A200000;
tftp ${fdt_addr} zone0.dtb;
tftp ${loadaddr} Image;
tftp ${ramdisk_addr} uInitrd;
booti ${loadaddr} ${ramdisk_addr}:0x400000 ${fdt_addr}

其中zone0.dtb在hvisor的dayu200 platform配置路径下。 Image为OpenHarmony的系统在Obj/linux6.6下的编译镜像产物。 uInitrd为ramdisk(OpenHarmony的编译产物)经过了改装,U-Boot 在解析 ramdisk 时会先查找镜像头:

  • 如果有 U-Boot 镜像头,它认为是一个合法的 ramdisk。
  • 如果只是 gzip 压缩的 cpio(没有 U-Boot header),就会报错

ramdisk.img只是标准的 initramfs 文件,没有 U-Boot 镜像头, U-Boot 的 booti 不认,所以还需要执行 mkimage -A arm64 -O linux -T ramdisk -C gzip -d ramdisk.img uInitrd

ramdisk镜像大小只有2.14MB,而ramdisk的扇区大小是4MB,OpenHarmony在运行过程中需要sync一些数据到扇区的后面,因此booti命令还需要指定扇区的长度为0x400000

OpenHarmony 前置工作准备

编译Hvisor-tool

OpenHarmony内核会强制验证模块签名,内核开启了 CONFIG_MODULE_SIG 并且强制 (CONFIG_MODULE_SIG_FORCE=y),而 hvisor.ko 没有用内核信任的私钥签名。

每次编译都会生成不同的密钥,即使命令完全一样。 alt text 修改编译配置,使用脚本将下面的编译选项禁用: alt text

#module signature verification
${KERNEL_SOURCE}/scripts/config --file ${KERNEL_SRC_TMP_PATH}/arch/arm64/configs/rockchip_linux_defconfig -d MODULE_SIG -d MODULE_SIG_FORCE -d MODULE_SIG_ALL

还需要修改代码,如果不存在signing_key.pem就跳过 alt text

SIGNING_KEY="${PROJECT_DIR}/out/kernel/OBJ/linux-6.6/certs/signing_key.pem"
if [ -f "${SIGNING_KEY}" ]; then
    cp ${SIGNING_KEY} .
fi
export PATH=${PROJECT_DIR}/out/kernel/OBJ/linux-6.6/scripts/:$PATH

make PROJECTDIR=${PROJECT_DIR} DEVICENAME=${DEVICE_NAME} DEVICEARCH=${DEVICE_ARCH} TARGETKONAME=${TARGET_KO_NAME} OBJLIST=${obj_list}
if [ -f "signing_key.pem" ]; then
    sign-file sha512 signing_key.pem signing_key.pem *.ko
fi

此外,Hvisor使用了mmap /dev/mem,用于把 kernel/DTB 镜像写入物理内存。而OpenHarmony内核开启了 CONFIG_STRICT_DEVMEM,它会阻止通过 /dev/mem 访问 RAM 区域,mmap 直接返回 EPERM。因此需要再将此选项禁用。否则会出现如下错误: alt text

另外,还需要修改OpenHarmony的编译脚本,加上如下命令modules_prepare,这样才能针对OpenHarmony编译自定义模块也就是hvisor.ko: alt text 否则会出现如下报错: alt text

OpenHarmony使用LLVM编译,并且需要使用OpenHarmony提供的LLVM编译器,并且指定KDIR路径,下面是编译Hvisor-tool的命令:

export PATH=/home/stone/ohos5/prebuilts/clang/ohos/linux-x86_64/llvm/bin/:$PATH
export PATH=/home/stone/ohos5/prebuilts/develop_tools/pahole/bin/:$PATH
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 LOG=LOG_INFO KDIR=/home/stone/ohos5/out/kernel/OBJ/linux-6.6 VIRTIO_GPU=n driver
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 LOG=LOG_INFO KDIR=/home/stone/ohos5/out/kernel/OBJ/linux-6.6 VIRTIO_GPU=n tools

请将home/stone/ohos5替换为OpenHarmony的项目路径。

Openharmony SD卡配置

目前zone1的文件系统存放在SD卡上,对应的设备树节点为

dwmmc@fe2b0000 {
    compatible = "rockchip,rk3568-dw-mshc\0rockchip,rk3288-dw-mshc";
    reg = <0x00 0xfe2b0000 0x00 0x4000>;
    interrupts = <0x00 0x62 0x04>;
    clocks = <0x24 0xb0 0x24 0xb1 0x24 0x18a 0x24 0x18b>;
    clock-names = "biu\0ciu\0ciu-drive\0ciu-sample";
    fifo-depth = <0x100>;
    max-frequency = <0x8f0d180>;
    resets = <0x24 0xd4>;
    reset-names = "reset";
    status = "okay";
    supports-sd;
    bus-width = <0x04>;
    cap-mmc-highspeed;
    cap-sd-highspeed;
    disable-wp;
    no-1-8-v;
    pinctrl-names = "default";
    pinctrl-0 = <0xc5 0xc6 0xc7 0xc8>;
};

准备一张SD卡,接入Linux系统中,然后清除现有的分区表sudo sgdisk -Z /dev/sdb

创建SD卡分区

sudo sgdisk \
  -n 1:8192:16383       -c 1:uboot        -t 1:8300 \
  -n 2:16384:24575      -c 2:misc         -t 2:8300 \
  -n 3:24576:28671      -c 3:bootctrl     -t 3:8300 \
  -n 4:28672:40959      -c 4:resource     -t 4:8300 \
  -n 5:40960:237567     -c 5:boot_linux   -t 5:8300 -u 5:a2d37d82-51e0-420d-83f5-470db993dd35 \
  -n 6:237568:245759    -c 6:ramdisk      -t 6:8300 \
  -n 7:245760:4440063   -c 7:system       -t 7:8300 -u 7:614e0000-0000-4b53-8000-1d28000054a9 \
  -n 8:4440064:6537215  -c 8:vendor       -t 8:8300 \
  -n 9:6537216:6639615  -c 9:sys-prod     -t 9:8300 \
  -n 10:6639616:6742015 -c 10:chip-prod   -t 10:8300 \
  -n 11:6742016:6807551 -c 11:updater     -t 11:8300 \
  -n 12:6807552:6840319 -c 12:eng_system  -t 12:8300 \
  -n 13:6840320:6873087 -c 13:eng_chipset -t 13:8300 \
  -n 14:6938624:7069695 -c 14:chip_ckm    -t 14:8300 \
  -n 15:19955712:0      -c 15:userdata    -t 15:8300 \
  /dev/sdb

把bootable标志位设置为1。

sudo sgdisk -A 5:set:2 /dev/sdb
sudo partprobe /dev/sdb
sudo sgdisk -i 5 /dev/sdb

SD卡烧录,其中数据源为 OpenHarmony 的编译产物(执行前千万注意核对 /dev/sdb 是否为你的 SD 卡设备):

sudo dd if=MiniLoaderAll.bin of=/dev/sdb seek=64 conv=notrunc status=progress
sudo dd if=uboot.img of=/dev/sdb1 status=progress
sudo dd if=misc.img of=/dev/sdb2 status=progress
sudo dd if=bootctrl.img of=/dev/sdb3 status=progress
sudo dd if=resource.img of=/dev/sdb4 status=progress
sudo dd if=boot_linux.img of=/dev/sdb5 status=progress
sudo dd if=ramdisk.img of=/dev/sdb6 status=progress
sudo dd if=system.img of=/dev/sdb7 status=progress
sudo dd if=vendor.img of=/dev/sdb8 status=progress
sudo dd if=sys_prod.img of=/dev/sdb9 status=progress
sudo dd if=chip_prod.img of=/dev/sdb10 status=progress
sudo dd if=updater.img of=/dev/sdb11 status=progress
sudo dd if=eng_system.img of=/dev/sdb12 status=progress
sudo dd if=eng_chipset.img of=/dev/sdb13 status=progress
sudo dd if=chip_ckm.img of=/dev/sdb14 status=progress
sudo dd if=userdata.img of=/dev/sdb15 status=progress

弹出SD卡:

sudo eject /dev/sdb

编译适配OpenHarmony的busybox

OpenHarmony自带的shell功能过于简陋,而busybox提供了很多诸如网络、串口相关的配置功能,因此需要使用 OpenHarmony 仓库内已经准备好的 ARM64 交叉编译工具链,生成一个可在 OpenHarmony ARM64 环境中直接使用的静态链接 BusyBox。

git clone https://github.com/mirror/busybox.git
export TOOLCHAIN_PREFIX=/home/stone/ohos5/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
make CROSS_COMPILE="$TOOLCHAIN_PREFIX" menuconfig

该命令可以打开配置界面,将下列config配置设为y

CONFIG_STATIC=y
CONFIG_STATIC_LIBGCC=y
CONFIG_PREFIX="./_install"
CONFIG_FEATURE_INSTALLER=y
CONFIG_INSTALL_APPLET_SYMLINKS=y
CONFIG_SH_IS_ASH=y
CONFIG_ASH=y
CONFIG_FEATURE_EDITING=y
CONFIG_FEATURE_TAB_COMPLETION=y
# 网络相关配置:
CONFIG_IFCONFIG=y
CONFIG_ROUTE=y
CONFIG_IP=y
CONFIG_PING=y
CONFIG_NETSTAT=y
CONFIG_UDHCPC=y
CONFIG_TFTP=y
CONFIG_TFTPD=y
CONFIG_WGET=y
CONFIG_NC=y
CONFIG_TELNET=y
CONFIG_TELNETD=y
CONFIG_TUNCTL=y

编译命令:

cd /home/stone/ohos5/busybox

export TOOLCHAIN_PREFIX=/home/stone/ohos5/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-

make CROSS_COMPILE="$TOOLCHAIN_PREFIX"
make CROSS_COMPILE="$TOOLCHAIN_PREFIX" install

得到的编译结果里busybox/busybox 是 ARM64 静态链接可执行文件,将其传输到OpenHarmony的文件系统中。

编译针对OpenHarmony的picocom串口工具

OpenHarmony的zone0系统通过picocom与zone1进行连接,这里针对OpenHarmony环境自己写了一个脚本程序

// SPDX-License-Identifier: MIT
/*
 * A tiny picocom-compatible terminal connector for OpenHarmony board bring-up.
 *
 * This is not a full upstream picocom replacement. It implements the small
 * subset needed to connect zone0 to hvisor-tool's virtio-console PTY:
 *
 *   picocom --nolock --noinit --noreset -b 115200 /dev/pts/0
 *
 * Exit with Ctrl-a Ctrl-x.
 */
#define _GNU_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static volatile sig_atomic_t g_stop;

struct options {
    const char *device;
    int baud;
    bool noinit;
    bool noreset;
};

struct baud_entry {
    int value;
    speed_t speed;
};

static const struct baud_entry g_baud_table[] = {
    {50, B50},         {75, B75},         {110, B110},
    {134, B134},       {150, B150},       {200, B200},
    {300, B300},       {600, B600},       {1200, B1200},
    {1800, B1800},     {2400, B2400},     {4800, B4800},
    {9600, B9600},     {19200, B19200},   {38400, B38400},
#ifdef B57600
    {57600, B57600},
#endif
#ifdef B115200
    {115200, B115200},
#endif
#ifdef B230400
    {230400, B230400},
#endif
#ifdef B460800
    {460800, B460800},
#endif
#ifdef B921600
    {921600, B921600},
#endif
#ifdef B1000000
    {1000000, B1000000},
#endif
#ifdef B1500000
    {1500000, B1500000},
#endif
};

static void on_signal(int sig)
{
    (void)sig;
    g_stop = 1;
}

static void usage(FILE *out, const char *argv0)
{
    fprintf(out,
            "usage: %s [--nolock] [--noinit] [--noreset] [-b BAUD] DEVICE\n"
            "\n"
            "Tiny picocom-compatible connector for /dev/pts/N or a serial TTY.\n"
            "Exit with Ctrl-a Ctrl-x.\n",
            argv0);
}

static int parse_baud(const char *text)
{
    char *end = NULL;
    long value;

    errno = 0;
    value = strtol(text, &end, 10);
    if (errno != 0 || end == text || *end != '\0' || value <= 0 ||
        value > 4000000) {
        fprintf(stderr, "invalid baud rate: %s\n", text);
        return -1;
    }
    return (int)value;
}

static speed_t baud_to_speed(int baud)
{
    size_t i;

    for (i = 0; i < ARRAY_SIZE(g_baud_table); i++) {
        if (g_baud_table[i].value == baud) {
            return g_baud_table[i].speed;
        }
    }
    return (speed_t)0;
}

static int parse_args(int argc, char **argv, struct options *opts)
{
    int i;

    opts->baud = 115200;

    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            usage(stdout, argv[0]);
            exit(0);
        } else if (strcmp(argv[i], "--nolock") == 0) {
            continue;
        } else if (strcmp(argv[i], "--noinit") == 0) {
            opts->noinit = true;
        } else if (strcmp(argv[i], "--noreset") == 0) {
            opts->noreset = true;
        } else if (strcmp(argv[i], "-b") == 0 ||
                   strcmp(argv[i], "--baud") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "%s requires an argument\n", argv[i]);
                return -1;
            }
            opts->baud = parse_baud(argv[++i]);
            if (opts->baud < 0) {
                return -1;
            }
        } else if (strncmp(argv[i], "-b", 2) == 0 && argv[i][2] != '\0') {
            opts->baud = parse_baud(argv[i] + 2);
            if (opts->baud < 0) {
                return -1;
            }
        } else if (strncmp(argv[i], "--baud=", 7) == 0) {
            opts->baud = parse_baud(argv[i] + 7);
            if (opts->baud < 0) {
                return -1;
            }
        } else if (argv[i][0] == '-') {
            fprintf(stderr, "unsupported option: %s\n", argv[i]);
            return -1;
        } else if (opts->device == NULL) {
            opts->device = argv[i];
        } else {
            fprintf(stderr, "unexpected argument: %s\n", argv[i]);
            return -1;
        }
    }

    if (opts->device == NULL) {
        usage(stderr, argv[0]);
        return -1;
    }
    return 0;
}

static int set_raw_stdin(struct termios *saved)
{
    struct termios raw;

    if (tcgetattr(STDIN_FILENO, saved) != 0) {
        perror("tcgetattr(stdin)");
        return -1;
    }

    raw = *saved;
    cfmakeraw(&raw);
    if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) != 0) {
        perror("tcsetattr(stdin)");
        return -1;
    }
    return 0;
}

static int set_raw_device(int fd, int baud, struct termios *saved)
{
    struct termios raw;
    speed_t speed;

    if (tcgetattr(fd, saved) != 0) {
        perror("tcgetattr(device)");
        return -1;
    }

    raw = *saved;
    cfmakeraw(&raw);
    raw.c_cflag |= CLOCAL | CREAD;
    raw.c_cflag &= ~CRTSCTS;

    speed = baud_to_speed(baud);
    if (speed == (speed_t)0) {
        fprintf(stderr, "unsupported baud rate: %d\n", baud);
        return -1;
    }
    cfsetispeed(&raw, speed);
    cfsetospeed(&raw, speed);

    if (tcsetattr(fd, TCSANOW, &raw) != 0) {
        perror("tcsetattr(device)");
        return -1;
    }
    return 0;
}

static int write_all(int fd, const unsigned char *buf, size_t len)
{
    size_t off = 0;

    while (off < len) {
        ssize_t n = write(fd, buf + off, len - off);
        if (n < 0) {
            if (errno == EINTR) {
                continue;
            }
            return -1;
        }
        if (n == 0) {
            return -1;
        }
        off += (size_t)n;
    }
    return 0;
}

static int pump(int dev_fd)
{
    bool escaped = false;
    unsigned char buf[512];

    while (!g_stop) {
        fd_set rfds;
        int maxfd = dev_fd > STDIN_FILENO ? dev_fd : STDIN_FILENO;
        int ret;

        FD_ZERO(&rfds);
        FD_SET(STDIN_FILENO, &rfds);
        FD_SET(dev_fd, &rfds);

        ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
        if (ret < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror("select");
            return -1;
        }

        if (FD_ISSET(dev_fd, &rfds)) {
            ssize_t n = read(dev_fd, buf, sizeof(buf));
            if (n < 0) {
                if (errno == EINTR || errno == EAGAIN) {
                    continue;
                }
                perror("read(device)");
                return -1;
            }
            if (n == 0) {
                fprintf(stderr, "\r\n[device closed]\r\n");
                return 0;
            }
            if (write_all(STDOUT_FILENO, buf, (size_t)n) != 0) {
                perror("write(stdout)");
                return -1;
            }
        }

        if (FD_ISSET(STDIN_FILENO, &rfds)) {
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
            ssize_t i;

            if (n < 0) {
                if (errno == EINTR || errno == EAGAIN) {
                    continue;
                }
                perror("read(stdin)");
                return -1;
            }
            if (n == 0) {
                return 0;
            }

            for (i = 0; i < n; i++) {
                unsigned char c = buf[i];

                if (escaped) {
                    escaped = false;
                    if (c == 0x18 || c == 'x' || c == 'X') {
                        fprintf(stderr, "\r\n[exiting]\r\n");
                        return 0;
                    }
                    if (c != 0x01 &&
                        write_all(dev_fd, (const unsigned char *)"\001", 1) !=
                            0) {
                        perror("write(device)");
                        return -1;
                    }
                } else if (c == 0x01) {
                    escaped = true;
                    continue;
                }

                if (write_all(dev_fd, &c, 1) != 0) {
                    perror("write(device)");
                    return -1;
                }
            }
        }
    }

    return 0;
}

int main(int argc, char **argv)
{
    struct options opts = {0};
    struct termios saved_stdin;
    struct termios saved_dev;
    bool stdin_saved = false;
    bool dev_saved = false;
    int fd;
    int ret;

    if (parse_args(argc, argv, &opts) != 0) {
        return 2;
    }

    signal(SIGINT, on_signal);
    signal(SIGTERM, on_signal);
    signal(SIGHUP, on_signal);

    fd = open(opts.device, O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror(opts.device);
        return 1;
    }

    if (!opts.noinit) {
        if (set_raw_device(fd, opts.baud, &saved_dev) != 0) {
            close(fd);
            return 1;
        }
        dev_saved = true;
    }

    if (set_raw_stdin(&saved_stdin) != 0) {
        if (dev_saved && !opts.noreset) {
            tcsetattr(fd, TCSANOW, &saved_dev);
        }
        close(fd);
        return 1;
    }
    stdin_saved = true;

    fprintf(stderr, "connected to %s, exit with Ctrl-a Ctrl-x\r\n",
            opts.device);
    ret = pump(fd);

    if (stdin_saved) {
        tcsetattr(STDIN_FILENO, TCSANOW, &saved_stdin);
    }
    if (dev_saved && !opts.noreset) {
        tcsetattr(fd, TCSANOW, &saved_dev);
    }
    close(fd);
    return ret == 0 ? 0 : 1;
}

使用如下命令对picocom进行编译

#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OHOS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
TOOLCHAIN_PREFIX="${TOOLCHAIN_PREFIX:-$OHOS_ROOT/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-}"

if [[ ! -x "${TOOLCHAIN_PREFIX}gcc" ]]; then
  echo "missing compiler: ${TOOLCHAIN_PREFIX}gcc" >&2
  exit 1
fi

cd "$SCRIPT_DIR"
make clean
make TOOLCHAIN_PREFIX="$TOOLCHAIN_PREFIX"
make TOOLCHAIN_PREFIX="$TOOLCHAIN_PREFIX" install

file output/picocom
ls -l output/picocom

将编译产物复制到OpenHarmony的/data/zone目录下。后续通过该脚本控制连接zone1的串口终端。

将下列文件通过OpenHarmony开发者工具hdc,从主机电脑拷贝到OpenHarmony的EMMC文件系统上(需要通过开发板上的蓝色调试线连接):

hdc file send "D:\rk3568\configs\zone1-ohos.dtb" /data/zone
hdc file send "D:\rk3568\configs\zone1-ohos.json" /data/zone
hdc file send "D:\rk3568\configs\zone1-ohos-virtio.json" /data/zone
hdc file send "D:\rk3568\configs\zone1-ohos.kernel" /data/zone
hdc file send "D:\rk3568\configs\zone1-ohos.ramdisk" /data/zone
hdc file send "D:\rk3568\configs\hvisor.ko" /data/zone
hdc file send "D:\rk3568\configs\busybox" /data/zone
hdc file send "D:\rk3568\configs\picocom" /data/zone

其中zone1-ohos.kernel就是OpenHarmony编译出来的Image文件,zone1-ohos.ramdisk也是OpenHarmony的编译产物ramdisk。

OpenHarmony root zone 启动

在hvisor项目路径下执行make BID=aarch64/dayu200 all,编译适配dayu200开发板的hvisor。 编译的产物在cp target/aarch64-unknown-none/debug/hvisor.bin路径下

使用如下命令启动OpenHarmony root zone:

setenv serverip 192.168.1.10; 
setenv ipaddr 192.168.1.20; 
setenv loadaddr 0x40400000; 
setenv board_dtb_addr 0x08300000; 
setenv zone0_kernel_addr 0x61000000; 
setenv zone0_fdt_addr 0x60000000;
setenv ramdisk_addr 0x0A200000;
tftp ${loadaddr} ${serverip}:hvisor.bin;tftp ${board_dtb_addr} ${serverip}:zone0.dtb; tftp ${zone0_kernel_addr} ${serverip}:Image; tftp ${zone0_fdt_addr} ${serverip}:zone0.dtb;tftp ${ramdisk_addr} ramdisk_emmc.img; bootm ${loadaddr} - ${board_dtb_addr};

OpenHarmony guest zone 启动

当前有两个网段:

外侧物理网段:192.168.1.0/24 Windows 主机:192.168.1.10 zone0 物理网口 eth0:192.168.1.20

内侧虚拟网段:192.168.200.0/24 zone0 虚拟网卡 tap0:192.168.200.1 zone1 虚拟网卡 eth0:192.168.200.2

zone0 在这里扮演两个角色:

  1. 它是 root zone,负责启动 zone1。
  2. 它也是 zone1 出网的网关和 NAT 路由器。

zone1 并没有直接拿到物理网卡。zone1 看到的 eth0 是 virtio-net 虚拟网卡,不是 dayu200 的真实以太网硬件。

为了让zone1能够访问不是本地网段 192.168.200.0/24 的地址,也就是外侧网段如Windows主机地址,需要加上默认路由,把包发给网关 192.168.200.1,并且从 eth0 这个接口发出去。

对于zone1:eth0 = zone1 的 virtio-net 前端网卡,192.168.200.1 = zone0 的 tap0 接下来在zone0的终端输入如下命令:

# Start dayu200 zone1 OpenHarmony with virtio-net and virtio-console.

echo "4 4 1 7" > /proc/sys/kernel/printk
cd /data/zone
mount -t proc proc /proc
mount -t sysfs sysfs /sys

# zone0/root OpenHarmony: create tap0 and NAT it through the real NIC.
# U-Boot TFTP uses board 192.168.1.20 <-> host 192.168.1.10, but OpenHarmony
# should configure the runtime NIC address again after boot.
ifconfig -a
ifconfig eth0 192.168.1.20 netmask 255.255.255.0 up
ping 192.168.1.10
mkdir -p /dev/net
mknod /dev/net/tun c 10 200
/data/zone/busybox tunctl -d tap0
tunctl -d tap0
tunctl -d -T tap0
/data/zone/busybox tunctl -t tap0
cat /sys/class/net/tap0/type
/data/zone/busybox ifconfig tap0 192.168.200.1 netmask 255.255.255.0 up
sysctl -w net.ipv4.ip_forward=1
netstat -rn
iptables -t nat -D POSTROUTING -s 192.168.200.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -o eth0 -j MASQUERADE

chmod 755 hvisor && chmod 644 hvisor.ko
insmod hvisor.ko
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts

rm -f nohup.out
nohup ./hvisor virtio start zone1-ohos-virtio.json &
sleep 2
./hvisor zone start zone1-ohos.json

接着在zone1的命令行输入如下命令

# zone1/non-root OpenHarmony: configure the virtio-net interface.
echo "4 4 1 7" > /proc/sys/kernel/printk
mount -t proc proc /proc
mount -t sysfs sysfs /sys
ifconfig eth0 192.168.200.2 netmask 255.255.255.0 up
/data/zone/busybox route add default gw 192.168.200.1 dev eth0
ping 192.168.200.1
ping 192.168.1.10

可以看到zone1能成功通过virtio-net访问外网 alt text

并且在zone0中可以通过picocom建立与zone1的串口链接,在zone0中控制zone1的串口。 alt text