【起航】OpenHarmony远征04小型系统移植
openharmony小型系统内核移植信息表
内核 | 支持的arch | ROM | 文件系统 | Flash类型 |
---|---|---|---|---|
Liteos-A | ARMV7 | >2M | VFAT、JFFS2 | SPI、NOR、NAND |
Linux-4.19 | ARM、ARM64、MIPS、X86 | >5M | VFAT、JFFS2、EXT2 | NOR、NAND、EMMC |
编译构建系统介绍
编译框架与编译环境的搭建和之前的轻量系统移植类似
Liteos-A内核移植
Liteos-A支持ARM V7的指令集架构,若第三方芯片本身就是ARMV7-a的架构,就没有必要去修改arch目录下的芯片架构支持,正常都是支持的
Liteos-A的初始化流程
- 新增target_config.h文件,编写与内存相关的适配宏DDR_MEM_ADDR和DDR_MEM_SIZE,分别来表示内存的起始地址和内存的长度,预链接脚本board.ld.S会根据这两个宏进行展开,生成连接脚本board.ld
- 新增定义MMU映射全局数组(g_archMmuInitMapping),指定各个内存段属性及虚实映射关系,内核启动阶段根据该表建立内存映射关系
- 如果是多核,需要新增定义从核操作函数句柄(struct SmpOps),其中SmpOps->SmpCpuOn函数需要实现唤醒从核的功能;接着定义SmpRegFunc函数,调用LOS_SmpOpsSet接口进行句柄注册;最后通过启动框架完成注册过程,即LOS_MODULE_INIT(SmpRegFunc, LOS_INIT_LEVEL_EARLIEST)
- 链接阶段根据链接脚本board.ld生成内核镜像
- 单核CPU镜像运行入口为汇编文件reset_vector_up.S,多核CPU的入口为reset_vector_mp.S,在汇编文件中进行中断向量表初始化、MMU页表初始化等操作
- reset_vector.S汇编代码最终会跳转到C语言的main函数,进行硬件时钟、软件定时器、内存和任务等初始化,这个过程会依赖target_config.h的特性宏配置,最后会创建SystemInit任务,并且开启任务调度OsSchedStart()
- SystemInit任务在单板代码中实现,其中调用DeviceManagerStart函数进行HDF驱动初始化,这个过程会调用单板代码中的驱动配置文件hdf.hcs以及drivers源码实现
整体流程如下:
target_config.h的配置项说明
配置项 | 说明 |
---|---|
OS_SYS_CLOCK | 系统cycle频率 |
DDR_MEM_ADDR | 系统内存起始地址 |
DDR_MEM_SIZE | 系统内存大小 |
OS_HWI_MIN | 系统中断最小值 |
OS_HWI_MAX | 系统中断最大值 |
GIC_BASE_ADDR | gic中断寄存器基址 |
PERIPH_PMM_BASE | 外设寄存器起始地址 |
PERIPH_PMM_SIZE | 外设寄存器长度大小 |
启动框架层级
层级 | 说明 |
---|---|
LOS_INIT_LEVEL_EARLIEST | 最早起初始化,不依赖架构的纯软件初始化 |
LOS_INIT_LEVEL_ARCH_EARLY | 架构相关,后续的模块初始化过程会依赖,非必要的建议放在LOS_INIT_LEVEL_ARCH |
LOS_INIT_LEVEL_PLATFORM_EARLY | 单板相关,驱动相关非必要的功能建议放在LOS_INIT_LEVEL_PLATFORM |
LOS_INIT_LEVEL_KMOD_PREVM | 内存初始化前需要依赖的内核模块进行初始化 |
LOS_INIT_LEVEL_VM_COMPLETE | 内存初始化 |
LOS_INIT_LEVEL_ARCH | 架构后期初始化 |
LOS_INIT_LEVEL_PLATFORM | 驱动初始化 |
LOS_INIT_LEVEL_KMOD_BASIC | 内核基础模块初始化,基础模块的加载 |
LOS_INIT_LEVEL_KMOD_EXTENDED | 内核扩展模块初始化 |
LOS_INIT_LEVEL_KMOD_TASK | 内核任务创建 |
进行单板移植时,若arch使用已经适配的架构,可以只需要关注LOS_INIT_LEVEL_PLATFORM到LOS_INIT_LEVEL_KMOD_TASK之间的层级
驱动移植
驱动主要包含两部分,平台驱动和器件驱动。平台驱动主要包括通常在SOC内的GPIO、I2C、SPI等;器件驱动则主要包含通常在SOC外的器件,如 LCD、TP、WLAN
需要注意HDF驱动框架可以跨OS,为了增加软件的灵活性,驱动开发过程中尽量去使用HDF框架中提供的API,否则会使驱动丧失跨OS使用的特性。
常见的驱动目录名称如下:
device├── vendor_name│ ├── drivers│ │ │ ├── common│ │ │ ├── Kconfig # 厂商驱动内核菜单入口│ │ │ └── lite.mk # 构建的入口│ ├── soc_name│ │ ├── drivers│ │ │ ├── dmac│ │ │ ├── gpio│ │ │ ├── i2c│ │ │ ├── LICENSE│ │ │ ├── mipi_dsi│ │ │ ├── mmc│ │ │ ├── pwm│ │ │ ├── README.md # docs 如果需要的话│ │ │ ├── README_zh.md│ │ │ ├── rtc│ │ │ ├── spi│ │ │ ├── uart│ │ │ └── watchdog│ ├── board_name
open harmony官方给出的platform驱动实例为GPIO驱动:
- 创建GPIO驱动,在源码目录//device/vendor/soc/drivers/gpio中创建文件soc_name_gpio.c
#官方示例code#include "gpio_core.h"// 定义GPIO结构体,如果需要的话struct SocNameGpioCntlr { struct GpioCntlr cntlr; // 这是HDF GPIO驱动框架需要的结构体 int myData; // 以下是当前驱动自身需要的};// Bind 方法在HDF驱动中主要用户对外发布服务,这里我们不需要,直接返回成功即可static int32_t GpioBind(struct HdfDeviceObject *device){ (void)device; return HDF_SUCCESS;}// Init方法时驱动初始化的入口,我们需要在Init方法中完成模型实例的注册static int32_t GpioInit(struct HdfDeviceObject *device){ SocNameGpioCntlr *impl = CreateGpio(); // 你的创建代码 ret = GpioCntlrAdd(&impl->cntlr); // 注册GPIO模型实例 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: err add controller:%d", __func__, ret); return ret; } return HDF_SUCCESS;}// Release方法会在驱动卸载时被调用,这里主要完成资源回收static void GpioRelease(struct HdfDeviceObject *device){ // GpioCntlrFromDevice 方法能从抽象的设备对象中获得init方法注册进去的模型实例。 struct GpioCntlr *cntlr = GpioCntlrFromDevice(device); //资源释放...}struct HdfDriverEntry g_gpioDriverEntry = { .moduleVersion = 1, .Bind = GpioBind, .Init = GpioInit, .Release = GpioRelease, .moduleName = "SOC_NAME_gpio_driver", // 这个名字我们稍后会在配置文件中用到,用来加载驱动。};HDF_INIT(g_gpioDriverEntry); // 注册一个GPIO的驱动入口
- 创建厂商驱动构建入口
//device/vendor/drivers/lite.mk是厂商驱动的构建入口,从该入口开始进行构建
#文件device/vendor_name/drivers/lite.mkSOC_VENDOR_NAME := $(subst $/",,$(LOSCFG_DEVICE_COMPANY))SOC_NAME := $(subst $/",,$(LOSCFG_PLATFORM))BOARD_NAME := $(subst $/",,$(LOSCFG_PRODUCT_NAME))# 指定SOC进行构建LIB_SUBDIRS += $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/
- 创建soc驱动构建入口
#文件device/vendor_name/soc_name/drivers/lite.mkSOC_DRIVER_ROOT := $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/# 判断如果打开了GPIO的内核编译开关ifeq ($(LOSCFG_DRIVERS_HDF_PLATFORM_GPIO), y) # 构建完成要链接一个叫hdf_gpio的对象 LITEOS_BASELIB += -lhdf_gpio # 增加构建目录gpio LIB_SUBDIRS += $(SOC_DRIVER_ROOT)/gpio endif# 后续其他驱动在此基础上追加
- GPIO构建入口
include $(LITEOSTOPDIR)/config.mkinclude $(LITEOSTOPDIR)/../../drivers/adapter/kndf/liteos/lite.mk#指定输出对象的名称,这里的名称与soc驱动构建里边的BASELIB保持一致MODULE_NAME := hdf_gpio#增加hdf框架的includeLOCAL_CFLAGS += $(HDG_INCLUDE)#需要编译的文件LOCAL_SRCS += soc_name_gpio.c#编译参数LOCAL_CFLAGS += -fstack-protector-strong -Wextera -Wallinclude $(HDF_DRIVER)
- 指定对应的产品去加载驱动
产品的设备信息被定义在源码文件中//vendor/product/config/device_info/device_info.hcs
还是以gpio驱动为例子,platform的驱动需要加载到platform的host中
root { ... platform :: host { device_gpio :: device { device0 :: deviceNode { policy = 0; priority = 10; permission = 0644; moduleName = "SOC_NAME_gpio_driver"; } } }}
open harmony官方给出的器件驱动实例为LCD驱动
器件驱动的相关代码没有放在device目录下,例如LCD的驱动放在源码目录//drivers/framework/model/display/driver/panel中
1.创建Panel驱动
创建HDF驱动,在驱动初始化过程中调用RegisterPanel接口去注册模型实例
int32_t LCDxxEntryInit(struct HdfDeviceObject *object){ struct PanelData *panel = CreateYourPanel(); // 注册模型实例 if (RegisterPanel(panel) != HDF_SUCCESS) { HDF_LOGE("%s: RegisterPanel failed", __func__); return HDF_FAILURE; } return HDF_SUCCESS;}struct HdfDriverEntry g_xxxxDevEntry = { .moduleVersion = 1, .moduleName = "LCD_XXXX", .Init = LCDxxEntryInit,};HDF_INIT(g_xxxxDevEntry);
- 配置加载Panel驱动
和platform driver一样,产品相关的设备信息被定义在//vendor/vendor_name/product_name/config/device_info/device_info.hcs,唯一不同的是module定义的位置不同,lcd driver放在display的host中
root { ... display :: host { device_lcd :: device { deviceN :: deviceNode { policy = 0; priority = 100; preload = 2; moduleName = "LCD_XXXX"; } } }}