OpenHarmony3.0的树莓派4B移植-学习记录

前言

以下是学习 OpenHarmony3.0 树莓派4B移植的详细流程记录,主要参考:官方移植指南官方树莓派3B移植社区大佬树莓派4B移植,LineageOS 树莓派移植项目:lineage-rpi,Android 树莓派移植项目:android-rpi

实现了触摸与显示,并添加了物理按键关机的功能。希望能给像我一样第一次接触移植的嵌入式小白以帮助。

以下步骤操作于 Ubuntu 20.04 LTS 。OHOS 3.0 LTS 源码解压自官方镜像(使用 repo下载得到的最新源码有较多变化,以下步骤不再适用)。设项目根目录名为 OpenHarmony,且控制台默认处于该目录。移植系统为OHOS标准版 (standard),内核为 Linux-5.10。

定义开发板

根据官方移植手册,对源码目录以及代码中常出现的几个概念进行解释,并在本流程背景下进行赋值:

productdevicevendorsocvendor
含义嵌入式产品名SOC名产品制造商SOC制造商
实例rpi4bcm2711raspberrybrcm

因此,我们首先定义 SOC。建立文件 OpenHarmony/productdefine/common/device/bcm2711.json

{
    "device_name": "bcm2711",
    "device_company": "brcm",
    "target_os": "ohos",
    "target_cpu": "arm",
    "kernel_version": "",
    "device_build_path": "device/brcm/build"
}

目前 OHOS 只支持 arm。

接着定义产品。建立文件 OpenHarmony/productdefine/common/products/rpi4.json

{
  "product_name": "rpi4",
  "product_company": "raspberry",
  "product_device": "bcm2711",
  "version": "2.0",
  "type": "standard",
  "product_build_path": "device/brcm/build",
  "parts": {
   ...
   "brcm_products:brcm_products":{},
   ...
  }
}

其中, 部分照搬同目录下的 Hi3516DV300.json,并将其中的 "hisilicon_products:hisilicon_products":{},改为 "brcm_products:brcm_products":{},

brcm_products 代表内核构建的子系统。我们需要在 OpenHarmony/build/subsystem_config.json 中定义它,加入键值对:

  "brcm_products":{
    "project": "hmf/brcm_products",
    "path": "device/brcm/bcm2711/build",
    "name": "brcm_products",
    "dir": "device/brcm"
  },  

建立编译配置组件

接着在 OpenHarmony/device 下仿照 ./hisilicon/hi3516dv300 建立编译配置组件。目录结构:

device
└── brcm
    ├── bcm2711
    │   ├── build
    │   │   └── rootfs
    │   │       ├── BUILD.gn
    │   │       └── init.rpi4.cfg
    │   └── BUILD.gn
    └── build
        ├── BUILD.gn
        └── ohos.build

OpenHarmony/device/brcm/bcm2711/build/rootfs/BUILD.gn

import("//build/ohos.gni")

ohos_prebuilt_etc("init.rpi4.cfg") {
  source = "init.rpi4.cfg"
  install_images = [ "system" ]
  part_name = "brcm_products"
}

group("init_configs") {
  deps = [
    ":init.rpi4.cfg"
  ]
}

OpenHarmony/device/brcm/bcm2711/build/rootfs/init.rpi4.cfg复制自OpenHarmony/device/hisilicon/hi3516dv300/build/rootfs/init.Hi3516DV300.cfg

OpenHarmony/device/brcm/bcm2711/BUILD.gn

import("//build/ohos.gni")

print("bcm2711_group in")
group("bcm2711_group") {
  deps = [
    "build/rootfs:init_configs",
    "//kernel/linux/build:linux_kernel"
  ]
}

OpenHarmony/device/brcm/build/BUILD.gn

import("//build/ohos.gni")

group("products_group") {
  deps = [
    "//device/brcm/bcm2711:bcm2711_group"
  ]
}

OpenHarmony/device/brcm/build/ohos.build

{
    "subsystem": "brcm_products",
    "parts": {
        "brcm_products": {
            "module_list": [
                "//device/brcm/build:products_group"
            ]
        }
    }
}

内核移植

内核编译流程

首先阅读 OpenHarmony/kernel/linux/build/kernel.mk

$(KERNEL_IMAGE_FILE):
    $(hide) echo "build kernel..."
    $(hide) rm -rf $(KERNEL_SRC_TMP_PATH);mkdir -p $(KERNEL_SRC_TMP_PATH);cp -arfL $(KERNEL_SRC_PATH)/* $(KERNEL_SRC_TMP_PATH)/
    $(hide) cd $(KERNEL_SRC_TMP_PATH) && patch -p1 < $(HDF_PATCH_FILE) && patch -p1 -s -N < $(DEVICE_PATCH_FILE)
ifneq ($(findstring $(BUILD_TYPE), small),)
    $(hide) cd $(KERNEL_SRC_TMP_PATH) && patch -p1 < $(SMALL_PATCH_FILE)
endif
    $(hide) cp -rf $(KERNEL_CONFIG_PATH)/. $(KERNEL_SRC_TMP_PATH)/
    $(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) distclean
    $(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) $(DEFCONFIG_FILE)
ifeq ($(KERNEL_VERSION), linux-5.10)
    $(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) modules_prepare
endif
    $(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) -j64 uImage
endif

了解到内核编译的流程:

  1. 删除 out(编译输出目录)下之前编译过的内核源码,重新复制一份 Linux 内核源码OpenHarmony/kernel/linux/linux-5.10OpenHarmony/out/KERNEL_OBJ/kernel/src_tmp/linux-5.10 下。
  2. 对内核源码打补丁,包括内核源码补丁与 HDF 补丁。补丁需要预先准备。
  3. 复制 defconfig,需要预先准备。
  4. make distclean, xxx_defconfig, modules_prepare, uImage.

生成 patch

因此首先准备树莓派4B需要的内核补丁:

选择树莓派官方内核 rpi-5.10.y 来生成内核源码补丁。设控制台处在项目根目录 OpenHarmony 下,依次执行:

mkdir ../rpi-kernel && cd $_
git clone git://github.com/raspberrypi/linux -b rpi-5.10.y --depth=1
cd ../OpenHarmony/kernel/linux
diff -uNr linux-5.10/ /home/username/Project/RPI/rpi-kernel/linux/ > bcm2711.patch
mkdir patches/linux-5.10/bcm2711_patch && cp bcm2711.patch $_ && cd $_
cp ../hi3516dv300_patch/hdf.patch ./

注意 diff 命令:

  1. 由于 kernel.mk中用的是 patch -p1,即忽略掉路径的第一部分,因此 diff 需作用在 OHOS 自带的源码目录外。
  2. 树莓派官方内核源码需要用绝对路径,因人而异。

实际编译时很可能会因为 patch 过程中出现冲突而中断,解决方法:

  1. diff 时忽略源码目录下的 .git, .gitgitignore, 以及某设备树文件,保证打补丁时无冲突。
  2. 手动执行 kernel.mk 中的流程。

生成 defconfig

在树莓派官方 bcm2711_defconfig 基础上进行增改(当前位于 OpenHarmony 目录):

cp ../rpi-kernel/linux/arch/arm/configs/bcm2711_defconfig kernel/linux/config/linux-5.10/arch/arm/configs/bcm2711_standard_defconfig

增改主要有:

  1. OHOS上层系统所需:开启 SELinux, Binder, ALSA。
  2. 显示:开启 DRM, VC4。
  3. 触屏:开启 INPUT_TOUCHSCREEN, HID_MULTITOUCH, ADS7846, USB_COMPOSITE。因为我使用的微雪触屏控制器为 ADS7846,且是 USB 连线。
  4. 按键关机:开启 CONFIG_KEYBOARD_GPIO,以将 gpio-key 驱动编译进内核。

生成方法:

  1. 先进行一次内核编译:
   ./build.sh --product-name rpi4 --ccache --jobs $(nproc) --build-target brcm_products

再进入 out 下的内核源码根目录,利用 menuconfig 配置 .config,最后用 savedefconfig 保存:

   cd out/KERNEL_OBJ/kernel/src_tmp/linux-5.10
   ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make bcm2711_standard_defconfig
   ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig
   ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make savedefconfig
   cp savedefconfig ../../../../../kernel/linux/config/linux-5.10/arch/arm/configs/bcm2711_standard_defconfig
  1. 或者直接对 bcm2711_standard_defconfig 增改以下内容:
   CONFIG_KEYBOARD_GPIO=y
   CONFIG_TOUCHSCREEN_ADS7846=y
   CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=y
   CONFIG_TOUCHSCREEN_USB_COMPOSITE=y
   CONFIG_DRM=y
   CONFIG_DRM_V3D=y
   CONFIG_DRM_VC4=y
   CONFIG_SND=y
   CONFIG_SND_SOC=y
   CONFIG_HID_MULTITOUCH=y
   CONFIG_ANDROID=y
   CONFIG_ANDROID_BINDER_IPC=y
   # CONFIG_DRIVERS_HDF is not set
   CONFIG_SECURITY_SELINUX=y
   CONFIG_SECURITY_SELINUX_BOOTPARAM=y
   CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE=1

由于本流程没有移植 HDF,因此不开启 HDF 驱动。

编译脚本修正

源码编译脚本默认生成 uImage,这要求树莓派4B用 u-boot 引导,比较费事。因此将 kernel/linux/build/ 目录下的kernel.mk, build_kernel.sh, BUILD.gn ,kernel_module_build.sh 中的 uImage 都改为 zImage。

事实上不修改也没有关系。在最终生成 uImage 之前,内核编译时会在 boot 下首先生成 zImage,手动将其复制到镜像输出目录即可:

cp out/KERNEL_OBJ/kernel/src_tmp/linux-5.10/arch/arm/boot/zImage out/ohos-arm-release/packages/phone/images

显示与触摸配置

显示

vi third_party/weston/weston.ini

最后添加:

[output]
name=card0

不设置一般也没有关系,默认通过card0节点显示。如果输出节点为 HDMI-A-1 并需要旋转,则添加:

[output]
name=HDMI-A-1
transform=rotate-90

触摸

vi third_party/eudev/rules.d/touchscreen.rules

修改为:

ATTRS{name}=="WaveShare WS170120", ENV{ID_INPUT}="1", ENV{ID_INPUT_TOUCHSCREEN}="1"
ATTRS{name}=="VSoC keyboard", ENV{ID_INPUT}="1", ENV{ID_INPUT_KEYBOARD}="1"
DRIVERS=="hid-multitouch", ENV{ID_INPUT}="1", ENV{ID_INPUT_TOUCHSCREEN}="1"

其中 WaveShare WS170120 为微雪触摸屏插入 usb 后,在 /sys/dev/char/xx\:xx/device/uevent 查询得到的设备名。支持 hid-multitouch

其它修正

init.cfg

初始化配置文件,类似于 Android 里的 init.rc

vi /base/startup/init_lite/services/etc/init.cfg

进行修改:

-            "/etc/init.Hi3516DV300.cfg"
+            "/etc/init.rpi4.cfg"
-                "mount ext4 /dev/block/platform/soc/10100000.himci.eMMC/by-name/vendor /vendor wait rdonly barrier=1",
-                "mount ext4 /dev/block/platform/soc/10100000.himci.eMMC/by-name/userdata /data wait nosuid nodev noatime barrier=1,data=ordered,noauto_da_alloc"
+                "mount ext4 /dev/block/mmcblk0p3 /vendor wait rdonly barrier=1",
+                "mount ext4 /dev/block/mmcblk0p4 /data wait nosuid nodev noatime barrier=1,data=ordered,noauto_da_alloc"

vendor

虽然没有移植 HDF 驱动,但是相关文件对内核编译流程是必要的。在 OpenHarmony/vendor 下新建文件,目录结构如下所示:

vendor
└── raspberry
    └── rpi4
        └── hdf_config
            ├── hdf.hcs
            ├── hdf_test
            │   ├── hdf.hcs
            │   └── Makefile
            ├── khdf
            │   ├── hdf.hcs
            │   └── Makefile
            ├── Makefile
            └── uhdf
                └── hdf.hcs

其中 hdf.hcs 均为:

root {
    module = "default";
}

Makefile 均拷贝自 OpenHarmony/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/Makefile

camera.rpi4.gni

虽然没有移植 camera 驱动,但也对编译是必要的,否则报错:

cp drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi3.gni drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi4.gni

镜像大小

镜像大小可以在 OpenHarmony/build/ohos/images/mkimage/ 下的 xxx_image_conf.txt 里修改。

编译

在根目录 OpenHarmony 下执行:

bash build/prebuilts_download.sh

下载的第三方开源软件压缩包存放于 OpenHarmony 同目录下的 OpenHarmony_2.0_canary_prebuilts。其中脚本在用 wget 下载 mingw-w64 时,由于后者过大可能触发 Segmentation fault (core dumped) 。目前还没有找到比较好的解决方法,只有用其它下载工具手动从华为镜像下载 clang-mingw.tar.gz 后再放进去。

编译:

./build.sh --product-name rpi4 --ccache --jobs $(nproc)

编译内核前脚本会在控制台打印编译命令,这方便我们在出错后或是修改config后,到内核源码根目录手动编译。手动编译时可能会出现找不到环境变量 PRODUCT_PATH 的错误,手动设置:

export PRODUCT_PATH=vendor/raspberry/rpi4 

制作SD卡并启动

分区与格式化

利用 fdisk 设置 sd 卡分区:

sudo fdisk /dev/mmcblk0

步骤:

  1. 删除既有分区。(d 命令)
  2. 新建四个主分区,分别对应 boot, system, vendor 与 userdata。大小由对应镜像决定。(n 命令)
  3. 设置 p1 分区文件类型为 W95 FAT32 (LBA)。(t 命令后选择 c)
  4. 为 p1 分区添加可启动标志。(a 命令)

可能的最终效果(p 命令):

设备           启动    起点     末尾     扇区  大小 Id 类型
/dev/mmcblk0p1 *       2048   264191   262144  128M  c W95 FAT32 (LBA)
/dev/mmcblk0p2       264192  4458495  4194304    2G 83 Linux
/dev/mmcblk0p3      4458496  5507071  1048576  512M 83 Linux
/dev/mmcblk0p4      5507072 62333951 56826880 27.1G 83 Linux

对应的 fdisk 内命令执行序列:

n p 1 +128M n p 2 +2G n p 3 +512M n p t 1 c a 1 w

格式化:

sudo mkfs.msdos /dev/mmcblk0p1 -n boot
sudo mkfs.ext4 /dev/mmcblk0p2
sudo mkfs.ext4 /dev/mmcblk0p3
sudo mkfs.ext4 /dev/mmcblk0p4

制备boot

基于树莓派官方 firmware。设 boot 分区 /dev/mmcblk0p1 已经挂载到 /media/username/boot 下:

cd ../ && git clone git://github.com/raspberrypi/firmware --depth=1
cp firmware/boot/{overlays,bcm2711-rpi-4-b.dtb,bcm2711-rpi-400.dtb,bcm2711-rpi-cm4.dtb,cmdline.txt,config.txt,fixup4.dat,fixup4x.dat,start4.elf,start4x.elf} /media/username/boot -r
cp OpenHarmony/out/ohos-arm-release/packages/phone/images/zImage /media/username/boot

修改 config.txt :

vi /media/username/boot/config.txt
# Kernel
kernel=zImage

# waveshare touchscreen
max_usb_current=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt 800 480 60 6 0 0 0
hdmi_drive=1

# fake KMS
dtoverlay=vc4-fkms-v3d

enable_uart=1

# output diagnostic information
uart_2ndstage=1

# for hardware power button
dtoverlay=gpio-key,gpio=3,keycode=116,label="POWER"

修改 cmdline.txt :

vi /media/username/boot/cmdline.txt
console=serial0,115200 no_console_suspend root=/dev/mmcblk0p2 elevator=deadline rootwait spidev.bufsiz=65536 androidboot.hardware=rpi4 androidboot.selinux=permissive

刷写镜像

cd OpenHarmony/out/ohos-arm-release/packages/phone/images
sudo dd if=system.img of=/dev/mmcblk0p2 bs=1M
sudo dd if=vendor.img of=/dev/mmcblk0p3 bs=1M
sudo dd if=userdata.img of=/dev/mmcblk0p4 bs=1M

启动

为使微雪触摸屏成功显示,须满足以下三个条件:

  1. config.txt : 手动设置 HDMI 输出配置(来自官网要求:5inch_HDMI_LCD
   max_usb_current=1
   hdmi_group=2
   hdmi_mode=87
   hdmi_cvt 800 480 60 6 0 0 0
   hdmi_drive=1
  1. config.txt : dtoverlay=vc4-fkms-v3d 启用 fake KMS
  2. 至少要在显示器启动前插入树莓派,且是远离 Type-C 的 Micro HDMI 口。保证树莓派能识别到屏幕。

按键关机

在配置内核编译 defconfig 时,我们开启了 CONFIG_KEYBOARD_GPIO 以将 gpio-key 编译进内核——这是一个与体系结构无关的 GPIO 按键驱动。只需要在设备树 gpio-key 节点添加需要的按键子节点后即可将 GPIO 状态变化转换为按键事件。

而树莓派官方已经有相应实现(详见 rpi-kernel/linux/arch/arm/boot/dts/overlays/README),只需要在 config.txt 开启即可:

dtoverlay=gpio-key,gpio=3,keycode=116,label="POWER"

这样,GPIO3 会被默认拉高,当它被接地时 gpio-key 就会将其转化为键码值 116 的按键事件输出到 /dev/input/event0

在主流的 Linux 发行版上(如基于 Debian 的 Raspbian OS),systemd 自带按键事件监听进程,只需在 /etc/systemd/logind.conf 中取消注释 HandlePowerKey = ignore 即可实现物理按键(或按 F5)关机。

Android 与 OpenHarmony 虽然不用 systemd,但可以运行服务。因此我们自己写一个简单的守护程序:

mkdir ../event0reader && cd $_
vi event0reader.c
#include <stdio.h>
#include <linux/input.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> 

#define DEV_PATH "/dev/input/event0"

int main()
{
    int keys_fd;
    int ret=0;
    struct input_event t;
    keys_fd=open(DEV_PATH, O_RDONLY);
    if(keys_fd <= 0)
    {
        printf("open /dev/input/event0 device error!\n");
        return -1;
    }
    while(1)
    {
        if(read(keys_fd, &t, sizeof(t)) == sizeof(t))
        {
            if(t.type==EV_KEY && t.code==116 && t.value==1)
            {
                ret=system("/bin/reboot shutdown");
                if (ret == -1)
                {
                    printf("failed to shutdown!\n");
                    return -1;
                }
            }
        }
        usleep(100000);
    }
    close(keys_fd);
    return 0;
}

其中 /bin/reboot 是 OHOS 自带的重启/关机程序,源码位于 OpenHarmony/base/startup/init_lite/services/cmds/reboot/init_cmd_reboot.c

使用静态链接库编译以保证程序成功运行。设 system 分区 /dev/mmcblk0p2 已经挂载到 /media/username/_ 下:

arm-linux-gnueabihf-gcc event0reader.c -o myservice -static
sudo cp myservice /media/username/_/system/bin/
sudo chmod 777 /media/username/_/system/bin/myservice
sudo chgrp 2000 /media/username/_/system/bin/myservice

在 OHOS 初始化时,内核会遍历 /system/etc/init/ 下的所有初始化配置。我们在该目录下写一个对应的 myservice.cfg

sudo vi /media/username/_/system/etc/init/myservice.cfg
{
    "jobs" : [{
            "name" : "post-fs",
            "cmds" : [
                "start myservice_shutdown"
            ]
        }
    ],
    "services" : [{
            "name" : "myservice_shutdown",
            "path" : ["/system/bin/myservice"],
            "uid" :  "root",
            "gid" : ["system", "shell"]
        }
   ]
}

当我们在内核启动时观察到如下信息:

# dmesg |grep myservice
[    3.722287] [pid=1][init_read_cfg.c:110][Init][INFO] ReadCfgs :/system/etc/init/myservice.cfg from /system/etc/init success.
[    7.558109] [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.myservice_shutdown value: running
[    7.558618] [pid=116][init_service.c:200][Init][INFO] service->name is myservice_shutdown 
[    7.569011] [pid=1][trigger_processor.c:165][Init][INFO] PostParamTrigger init.svc.myservice_shutdown success

则意味着 myservice 成功启动了,可以利用物理按键关机。接线示意图:

评论

  1. likerollingston
    2 年前
    2022-8-31 15:17:41

    实际编译时很可能会因为 patch 过程中出现冲突而中断,这边中断之后手动操作步骤是什么样呢

    • 博主
      likerollingston
      1 年前
      2023-7-16 19:47:46

      年代有些久远了,补丁的变化应该也很大了,我觉得最稳妥的还是一条条手动执行kernel.mk那些命令吧

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
下一篇