This post was last edited by hazhuzhu on 2021-12-29 00:13
Preface
The following is a detailed process record of learning OpenHarmony3.0 Raspberry Pi 4B porting, with main references: Official Porting Guide , Official Raspberry Pi 3B Porting , Community Leader Raspberry Pi 4B Porting , LineageOS Raspberry Pi Porting Project: lineage-rpi , Android Raspberry Pi Porting Project: android-rpi .
The touch and display are realized, and the physical button shutdown function is added. I hope it can help embedded newbies like me who are first exposed to transplantation.
The following steps are performed on Ubuntu 20.04 LTS. The OHOS 3.0 LTS source code is extracted from the official mirror (the latest source code downloaded using repo has many changes, and the following steps are no longer applicable). Set the project root directory to OpenHarmony, and the console is in this directory by default. The ported system is OHOS standard, and the kernel is Linux-5.10.
Defining the development board
According to the official porting manual, the source code directory and several concepts that often appear in the code are explained and assigned in the context of this process:
|
product
|
device
|
vendor
|
socvendor
|
meaning
|
Embedded Product Name
|
SOC Name
|
Product Manufacturers
|
SOC Manufacturers
|
Examples
|
rpi4
|
bcm2711
|
raspberry
|
brcm
|
Therefore, we first define the SOC. Create the file 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"
}
Currently OHOS only supports arm.
Next, define the product. Create the file 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":{},
...
}
}
Among them, the ... part is copied from Hi3516DV300.json in the same directory, and the "hisilicon_products:hisilicon_products":{}, is changed to "brcm_products:brcm_products":{},.
brcm_products represents the subsystem of the kernel build. We need to define it in OpenHarmony/build/subsystem_config.json and add the key-value pair:
"brcm_products":{
"project": "hmf/brcm_products",
"path": "device/brcm/bcm2711/build",
"name": "brcm_products",
"dir": "device/brcm"
},
Create a compilation configuration component
Then create a compilation configuration component in OpenHarmony/device in the same way as ./hisilicon/hi3516dv300. Directory structure:
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"
]
}
}
}
Kernel porting
Kernel compilation process
First read 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
Understand the kernel compilation process:
Generate patch
So first prepare the kernel patch required by Raspberry Pi 4B:
Select the official Raspberry Pi kernel rpi-5.10.y to generate the kernel source patch. Assume that the console is in the project root directory OpenHarmony, and execute the following in sequence:
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 ./
Note the diff command:
The actual compilation may be interrupted due to conflicts in the patch process. The solution is:
Generate defconfig
Add and modify based on the official bcm2711_defconfig of Raspberry Pi (currently located in the OpenHarmony directory):
cp ../rpi-kernel/linux/arch/arm/configs/bcm2711_defconfig kernel/linux/config/linux-5.10/arch/arm/configs/bcm2711_standard_defconfig
The main changes are:
Generate method:
Compile script fixes
The source code compilation script generates uImage by default, which requires the Raspberry Pi 4B to boot with u-boot, which is more troublesome. Therefore, change uImage to zImage in kernel.mk, build_kernel.sh, BUILD.gn, and kernel_module_build.sh in the kernel/linux/build/ directory.
In fact, it doesn't matter if you don't modify it. Before finally generating uImage, the kernel will first generate zImage under boot when compiling. You can manually copy it to the image output directory:
cp out/KERNEL_OBJ/kernel/src_tmp/linux-5.10/arch/arm/boot/zImage out/ohos-arm-release/packages/phone/images
Display and touch configuration
show
vi third_party/weston/weston.ini
Finally add:
[output]
name=card0
It doesn't matter if you don't set it. It will be displayed by card0 node by default. If the output node is HDMI-A-1 and needs to be rotated, add:
[output]
name=HDMI-A-1
transform=rotate-90
touch
vi third_party/eudev/rules.d/touchscreen.rules
Modified to:
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"
The WaveShare WS170120 is the device name obtained by querying /sys/dev/char/xx\:xx/device/uevent after the WaveShare touch screen is plugged into USB. It supports hid-multitouch.
Other Fixes
init.cfg
Initialization configuration file, similar to init.rc in Android
vi /base/startup/init_lite/services/etc/init.cfg
To modify:
- "/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
Although the HDF driver has not been ported, the relevant files are necessary for the kernel compilation process. Create a new file under OpenHarmony/vendor, and the directory structure is as follows:
vendor
└── raspberry
└── rpi4
└── hdf_config
├── hdf.hcs
├── hdf_test
│ ├── hdf.hcs
│ └── Makefile
├── khdf
│ ├── hdf.hcs
│ └── Makefile
├── Makefile
└── uhdf
└── hdf.hcs
Among them, hdf.hcs are:
root {
module = "default";
}
Makefile is copied from OpenHarmony/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/Makefile
camera.rpi4.gni
Although the camera driver has not been ported, it is necessary for compilation, otherwise an error will be reported:
cp drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi3.gni drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi4.gni
Image size
The image size can be modified in xxx_image_conf.txt under OpenHarmony/build/ohos/images/mkimage/.
Compile
Execute in the root directory OpenHarmony:
bash build/prebuilts_download.sh
The downloaded third-party open source software compressed package is stored in OpenHarmony_2.0_canary_prebuilts in the same directory as OpenHarmony. When the script uses wget to download mingw-w64, it may trigger a Segmentation fault (core dumped) because the latter is too large. There is no better solution yet. You can only use other download tools to manually download clang-mingw.tar.g z from the Huawei mirror and then put it in.
Compile:
./build.sh --product-name rpi4 --ccache --jobs $(nproc)
Before compiling the kernel, the script will print the compilation command in the console, which is convenient for us to manually compile in the kernel source root directory after an error or after modifying the config. When compiling manually, an error may occur that the environment variable PRODUCT_PATH cannot be found. Manually set it:
export PRODUCT_PATH=vendor/raspberry/rpi4
Make an SD card and boot
Partitioning and formatting
Use fdisk to set up sd card partitions:
sudo fdisk /dev/mmcblk0
step:
Possible final effect (p command):
Device Boot Start End Sector Size Id Type
/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
The corresponding fdisk command execution sequence:
n p 1 +128M n p 2 +2G n p 3 +512M n p t 1 c a 1 w
format:
sudo mkfs.msdos /dev/mmcblk0p1 -n boot
sudo mkfs.ext4 /dev/mmcblk0p2
sudo mkfs.ext4 /dev/mmcblk0p3
sudo mkfs.ext4 /dev/mmcblk0p4
Preparation of boot
Based on the official firmware of Raspberry Pi . Assume that the boot partition /dev/mmcblk0p1 has been mounted to /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
Modify 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"
Modify 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
Flashing the image
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
start up
In order for the Weixue touch screen to display successfully, the following three conditions must be met:
Button shutdown
When configuring the kernel to compile defconfig, we enabled CONFIG_KEYBOARD_GPIO to compile gpio-key into the kernel, which is an architecture-independent GPIO key driver. You only need to add the required key child node to the gpio-key node in the device tree to convert the GPIO state change into a key event.
The official Raspberry Pi has already implemented it (see rpi-kernel/linux/arch/arm/boot/dts/overlays/README for details ). You only need to enable it in config.txt:
dtoverlay=gpio-key,gpio=3,keycode=116,label="POWER"
In this way, GPIO3 will be pulled high by default, and when it is grounded, gpio-key will convert it into a key event with a key code value of 116 and output it to /dev/input/event0.
On mainstream Linux distributions (such as Raspbian OS based on Debian), systemd comes with a key event monitoring process. You only need to uncomment HandlePowerKey = ignore in /etc/systemd/logind.conf to achieve physical key (or F5) shutdown.
Although Android and OpenHarmony do not use systemd, they can run services. Therefore, we write a simple daemon program ourselves:
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 is the restart/shutdown program that comes with OHOS, and its source code is located in OpenHarmony/base/startup/init_lite/services/cmds/reboot/init_cmd_reboot.c.
Compile with static link library to ensure the successful operation of the program. Assume that the system partition /dev/mmcblk0p2 has been mounted to /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
When OHOS is initialized, the kernel will traverse all initialization configurations under /system/etc/init/. We write a corresponding myservice.cfg in this directory:
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"]
}
]
}
When we start the kernel we observe the following information:
# 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
This means that myservice has been successfully started and can be shut down using the physical button. Wiring diagram:
poweroff_bb