openharmony分布式硬件(三)
x86下openharmony移植
参考:
https://www.bilibili.com/opus/1008761824977354774/?from=readlist
仓库:https://gitee.com/angel---fish/openharmony_nnrt.git
openharmony5.1 release源码编译
下载源码后还需要下载设备源码
git clone -b OpenHarmony-5.1.0-Release https://gitee.com/ohos-porting-communities/vendor_opc.git vendor/opc
git clone -b OpenHarmony-5.1.0-Release https://gitee.com/ohos-porting-communities/device_board_opc.git device/board/opc
git clone -b OpenHarmony-5.1.0-Release https://gitee.com/ohos-porting-communities/device_soc_opc.git device/soc/opc
参考vendor/opc/README.md进行编译
制作启动设备
ubuntu下先安装gparted
sudo apt-get install gparted
sudo gparted
制作u盘启动盘

将out/x86_general/packages/phone/images下文件烧录进对应的分区
dd if=system.img of=/dev/sdb2 bs=4M
dd if=vendor.img of=/dev/sdb3 bs=4M
将device/board/opc/x86_general/loader下文件拷贝至boot分区
在开机的时候进入boot界面,选择u盘做为启动设备

system.img、vendor.img以及device/board/opc/x86_general/loader下文件放在文章开头仓库的image目录下。
hdf框架编译
x86_general产品使用的是linux内核,需要将hdf编译至内核中,跑一下oh-5/drivers/hdf_core/adapter/khdf/linux/patch_hdf.sh,这个脚本打的补丁就是复制HDF框架代码至linux内核源码目录下,同时修补好对应的Makefile,加入到内核的编译体系下;如果使用的是华为自己的liteos内核,则是用GN+ninja的构建系统。
cd drivers/hdf_core/adapter/khdf/linux/
# 最后一个参数无所谓,脚本会找到实际的补丁文件oh-5/kernel/linux/patches/linux-6.6/common_patch/hdf.patch
./patch_hdf.sh /home/zcm/Desktop/oh-5 /home/zcm/Desktop/oh-5/kernel/linux/linux-6.6 /home/zcm/Desktop/oh-5/kernel/linux/patches/linux-6.6 x86
打完补丁后需要检查一下

是否有对应的目录,如果有就是ok了。
注意一下oh-5/device/board/opc/x86_general/kernel/build_kernel.sh脚本这个位置参数加个L

不然复制文件夹就只是建立了软链而已。编译内核的时候这个脚本会将内核拷贝至oh-5/out/x86_general/kernel/src_tmp/linux-6.6,软链会在复制过程中丢失(相对路径不对)。
执行编译
./build.sh --product-name x86_general --ccache
中间可能遇到和这个模块有关的报错
oh-5/kernel/linux/linux-6.6/bounds_checking_function
路径下有说明文件,以及对应的编译方法,我们把它从编译体系中摘出来单独编译,后面哪个模块需要再说
把oh-5/kernel/linux/linux-6.6/bounds_checking_function/bundle.json做个备份
mv oh-5/kernel/linux/linux-6.6/bounds_checking_function/bundle.json oh-5/kernel/linux/linux-6.6/bounds_checking_function/bundle.json.old
开启santinizer选项
oh内核编译使用oh-5/kernel/linux/config下的config文件,一个是oh-5/kernel/linux/config/linux-6.6/base_defconfig,是一些基本的配置,另一个是针对特定产品的比如oh-5/kernel/linux/config/linux-6.6/type/standard_defconfig,最后总的config文件在oh-5/out/x86_general/kernel/OBJ/linux-6.6/.config,最好在检查一下
补充qemu启动脚本
#!/bin/sh
set -ex
IMG=openharmony.img
# 1. 创建镜像
qemu-img create -f raw "$IMG" 20G
# 2. 映射为 loop 设备
#sudo losetup -f --show -P openharmony.img
LOOPDEV=$(sudo losetup -f --show -P "$IMG")
echo "Loop device: $LOOPDEV"
# 3. 分区
sudo parted "$LOOPDEV" mklabel gpt
sudo parted "$LOOPDEV" mkpart boot fat32 1M 500M
sudo parted "$LOOPDEV" mkpart system ext4 500M 4G
sudo parted "$LOOPDEV" mkpart vendor ext4 4G 5G
sudo parted "$LOOPDEV" mkpart data ext4 5G 20G
sudo parted "$LOOPDEV" set 1 boot on
sudo parted "$LOOPDEV" set 1 esp on
# 4. 格式化
sudo mkfs.fat -F32 -n boot "${LOOPDEV}p1"
sudo mkfs.ext4 -L SYSTEM "${LOOPDEV}p2"
sudo mkfs.ext4 -L VENDOR "${LOOPDEV}p3"
sudo mkfs.ext4 -L DATA "${LOOPDEV}p4"
sudo mkdir -p /mnt/src
sudo mkdir -p /mnt/dst
# 5. 挂载并烧录
#sudo mount ../out/x86_general/packages/phone/images/system.img /mnt/src
#sudo mount "${LOOPDEV}p2" /mnt/dst
#sudo cp -a /mnt/src/* /mnt/dst/
#sudo umount /mnt/src /mnt/dst
#sudo mount ../out/x86_general/packages/phone/images/vendor.img /mnt/src
#sudo mount "${LOOPDEV}p3" /mnt/dst
#sudo cp -a /mnt/src/* /mnt/dst/
#sudo umount /mnt/src /mnt/dst
sudo dd if=../out/x86_general/packages/phone/images/system.img of="${LOOPDEV}p2" bs=1M
sudo dd if=../out/x86_general/packages/phone/images/vendor.img of="${LOOPDEV}p3" bs=1M
sudo mount "${LOOPDEV}p1" /mnt/dst
sudo cp -r ../device/board/opc/x86_general/loader/* /mnt/dst/
sudo cp ../out/x86_general/packages/phone/images/bzImage /mnt/dst/
sudo cp ../out/x86_general/packages/phone/images/ramdisk.img /mnt/dst/
sudo umount /mnt/dst
# 6. 卸载
sudo losetup -d "$LOOPDEV"
sudo rm -r /mnt/src
sudo rm -r /mnt/dst
运行上述脚本,输出openharmony.img的镜像文件,然后需要修改镜像中的grub配置,我们先挂载上去,
sudo losetup -f --show -P openharmony.img
$/dev/loop12
sudo mkdir /mnt/src
sudo mount /dev/loop12p1 /mnt/src
sudo vim /mnt/src/grub/grub.cfg
将console=ttyUSB0,115200改为console=ttyS0,115200 ttyUSB0是openharmony用来输出调试信息的,我们把它转到ttyS0串口设备上,这样在我们主机的终端上就能显示所有的信息
启动参数在加上一条quiet,取消打印日志
启动命令
qemu-system-x86_64 -enable-kvm -m 2G -smp 4 -boot once=d -hda ./openharmony.img -bios /usr/share/ovmf/OVMF.fd -serial stdio

hilog使用,参考这篇https://blog.csdn.net/dengjin20104042056/article/details/146028142
先调大hilog的缓存
hilog -G 5M -t core
hilog -G 5M -t app
开启内核日志读取
hilog -k on
这样能正常看到一些调试信息。
尝试编写hdf框架下的驱动
这个参考https://docs.openharmony.cn/pages/v5.1/zh-cn/device-dev/driver/driver-hdf-manage.md
注意服务要发布到用户态的话hcs配置的policy字段要设置成2
typedef enum {
/* 驱动不提供服务 /
SERVICE_POLICY_NONE = 0,
/ 驱动对内核态发布服务 /
SERVICE_POLICY_PUBLIC = 1,
/ 驱动对内核态和用户态都发布服务 /
SERVICE_POLICY_CAPACITY = 2,
/ 驱动服务不对外发布服务,但可以被订阅 /
SERVICE_POLICY_FRIENDLY = 3,
/ 驱动私有服务不对外发布服务,也不能被订阅 /
SERVICE_POLICY_PRIVATE = 4,
/ 错误的服务策略 */
SERVICE_POLICY_INVALID
} ServicePolicy;

能看到这块是能正常工作的,样例代码可以在https://docs.openharmony.cn/pages/v5.1/zh-cn/device-dev/driver/driver-hdf-manage.md这里找到,下面也贴出来
用户层代码
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h> // 替换 <string>,使用 strcpy/strlen 等
#include <stdio.h>
#include "hdf_log.h"
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"
#define HDF_LOG_TAG sample_test
#define SAMPLE_SERVICE_NAME "sample_service"
#define SAMPLE_WRITE_READ 123
int g_replyFlag = 0;
// 回调函数:设备事件接收
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{
const char *string = HdfSbufReadString(data);
if (string == NULL) {
printf("distributed_ai_app: fail to read string in event data");
g_replyFlag = 1;
return HDF_FAILURE;
}
printf("distributed_ai_app: %s: dev event received: %u %s",
(char *)priv, id, string);
g_replyFlag = 1;
return HDF_SUCCESS;
}
// 发送事件函数
static int SendEvent(struct HdfIoService *serv, const char *eventData)
{
int ret = 0;
int replyData = 0;
struct HdfSBuf *data = HdfSbufObtainDefaultSize();
if (data == NULL) {
printf("distributed_ai_app: fail to obtain sbuf data");
return 1;
}
printf("distributed_ai_app: success to obtain sbuf data");
struct HdfSBuf *reply = HdfSbufObtainDefaultSize();
if (reply == NULL) {
printf("distributed_ai_app: fail to obtain sbuf reply");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
printf("distributed_ai_app: success to obtain sbuf reply");
if (!HdfSbufWriteString(data, eventData)) {
printf("distributed_ai_app: fail to write sbuf");
ret = HDF_FAILURE;
goto out;
}
printf("distributed_ai_app: success to write sbuf");
ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);
if (ret != HDF_SUCCESS) {
printf("distributed_ai_app: fail to send service call");
goto out;
}
printf("distributed_ai_app: success to send service call");
if (!HdfSbufReadInt32(reply, &replyData)) {
printf("distributed_ai_app: fail to get service call reply");
ret = HDF_ERR_INVALID_OBJECT;
goto out;
}
printf("distributed_ai_app: Get reply is: %d", replyData);
out:
HdfSbufRecycle(data);
HdfSbufRecycle(reply);
return ret;
}
// 主函数
int main(void)
{
// 使用 C 风格字符串
const char *sendData = "default event info";
printf("distributed_ai_app: enter\n");
struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME);
if (serv == NULL) {
printf("distributed_ai_app: fail to get service %s", SAMPLE_SERVICE_NAME);
return HDF_FAILURE;
}
printf("distributed_ai_app: success to get service %s", SAMPLE_SERVICE_NAME);
// 注册事件监听器
static struct HdfDevEventlistener listener = {
.callBack = OnDevEventReceived,
.priv = (void*)"Service0"
};
if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) {
printf("distributed_ai_app: fail to register event listener");
goto cleanup;
}
printf("distributed_ai_app: success to register event listener");
if (SendEvent(serv, sendData)) {
printf("distributed_ai_app: fail to send event");
goto unregister;
}
printf("distributed_ai_app: success to send event");
// 等待回复
while (g_replyFlag == 0) {
sleep(1);
}
// 注销监听器
if (HdfDeviceUnregisterEventListener(serv, &listener)) {
printf("distributed_ai_app: fail to unregister listener");
goto cleanup;
}
printf("distributed_ai_app: success to unregister listener");
HdfIoServiceRecycle(serv);
return HDF_SUCCESS;
unregister:
HdfDeviceUnregisterEventListener(serv, &listener);
cleanup:
HdfIoServiceRecycle(serv);
return HDF_FAILURE;
}
驱动层代码
#include <fcntl.h>
//#include <sys/stat.h>
//#include <sys/ioctl.h>
#include <string.h> // 替代 <string>,用于字符串操作(虽然本例未直接使用)
#include "hdf_log.h"
#include "hdf_base.h"
#include "hdf_device_desc.h"
#include "hdf_sbuf.h" // 添加:HdfSbufReadString 需要此头文件
#include <limits.h> // 添加:INT32_MAX 定义
#define HDF_LOG_TAG "sample_driver" // 注意:HDF_LOG_TAG 应为字符串字面量,不是宏展开
#define SAMPLE_WRITE_READ 123
#ifndef INT32_MAX
#define INT32_MAX 0x7FFFFFFF
#endif
// Dispatch 函数:处理来自客户端的请求
static int32_t HdfSampleDriverDispatch(
struct HdfDeviceIoClient *client, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
HDF_LOGI("%{public}s: received cmd %{public}d", __func__, id);
if (id == SAMPLE_WRITE_READ) {
const char *readData = HdfSbufReadString(data);
if (readData != NULL) {
HDF_LOGE("%{public}s: read data is: %{public}s", __func__, readData);
}
if (!HdfSbufWriteInt32(reply, INT32_MAX)) {
HDF_LOGE("%{public}s: reply int32 fail", __func__);
}
return HdfDeviceSendEvent(client->device, id, data);
}
return HDF_FAILURE;
}
// Release:设备释放时调用,用于释放资源
static void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
// 在这里释放分配的资源(本例无)
return;
}
// Bind:绑定服务接口
static int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("%{public}s: deviceObject is null", __func__);
return HDF_FAILURE;
}
// 定义服务接口
static struct IDeviceIoService testService = {
.Dispatch = HdfSampleDriverDispatch,
};
deviceObject->service = &testService;
return HDF_SUCCESS;
}
// Init:驱动初始化
static int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("%{public}s: deviceObject is null", __func__);
return HDF_FAILURE;
}
HDF_LOGI("Sample driver Init success");
return HDF_SUCCESS;
}
// 驱动入口结构体
static struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "sample_driver",
.Bind = HdfSampleDriverBind,
.Init = HdfSampleDriverInit,
.Release = HdfSampleDriverRelease,
};
// 注册驱动(HDF 会自动调用 Init)
HDF_INIT(g_sampleDriverEntry);
hcs配置
sample_host :: host{
hostName = "host0"; // host名称,host节点是用来存放某一类驱动的容器。
priority = 100; // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序。
caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; // 用户态进程Linux capabilities配置。
device_sample :: device { // sample设备节点
device0 :: deviceNode { // sample驱动的DeviceNode节点
policy = 2; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍。
priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序。
preload = 0; // 驱动按需加载字段。
permission = 0664; // 驱动创建设备节点权限
moduleName = "sample_driver"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。
serviceName = "sample_service"; // 驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等。
}
}
}

浙公网安备 33010602011771号