往期知识点记录:
- 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
- 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
- OpenHarmony(鸿蒙南向开发)——轻量系统芯片移植指南(一)
- OpenHarmony(鸿蒙南向开发)——轻量系统芯片移植指南(二)
- OpenHarmony(鸿蒙南向开发)——轻量系统芯片移植指南(三)
- OpenHarmony(鸿蒙南向开发)——轻量系统芯片内核移植
- OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(一)
- OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(二)
- OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(三)
- 持续更新中……
移植概述
驱动主要包含两部分,平台驱动和器件驱动。平台驱动主要包括通常在SOC内的GPIO、I2C、SPI等;器件驱动则主要包含通常在SOC外的器件,如 LCD、TP、WLAN等
图1 OpenHarmony 驱动分类
HDF驱动被设计为可以跨OS使用的驱动程序,HDF驱动框架会为驱动达成这个目标提供有力的支撑。开发HDF驱动中,请尽可能只使用HDF驱动框架提供的接口,否则会导致驱动丧失跨OS使用的特性。在开始驱动开发前,建议先了解HDF驱动框架。
平台驱动移植
在这一步,我们会在源码目录//device/vendor_name/soc_name/drivers
目录下创建平台驱动,如果你要移植的SOC的厂商还没有创建仓库的话,请联系 sig_devboard 创建。
建议的目录结构:
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
HDF为所有的平台驱动都创建了驱动模型,移植平台驱动的主要工作是向模型注入实例。 这些模型你可以在源码目录//drivers/hdf_core/framework/support/platform/include
中找到定义。
本节我们会以GPIO为例,讲解如何移植平台驱动,移植过程包含以下步骤:
- 创建GPIO驱动
在源码目录//device/vendor_name/soc_name/drivers/gpio
中创建文件soc_name_gpio.c
。内容模板如下:
#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_name/drivers/lite.mk
是厂商驱动的构建的入口。我们需要从这个入口开始,进行构建。
#文件device/vendor_name/drivers/lite.mk
SOC_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.mk
SOC_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.mk
include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk
# 指定输出对象的名称,注意要与SOC驱动构建入口里的LITEOS_BASELIB 保持一致
MODULE_NAME := hdf_gpio
# 增加HDF框架的INCLUDE
LOCAL_CFLAGS += $(HDF_INCLUDE)
# 要编译的文件
LOCAL_SRCS += soc_name_gpio.c
# 编译参数
LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror -fsigned-char -fno-strict-aliasing -fno-common
include $(HDF_DRIVER)
- 配置产品加载驱动
产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs
中。
平台驱动请添加到platform的host中。
说明: moduleName要与驱动定义中的相同。
root {
...
platform :: host {
device_gpio :: device {
device0 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "SOC_NAME_gpio_driver";
}
}
}
}
平台驱动移植
在这一步,我们会在源码目录//device/vendor_name/soc_name/drivers
目录下创建平台驱动,如果你要移植的SOC的厂商还没有创建仓库的话,请联系 sig_devboard 创建。
建议的目录结构:
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
HDF为所有的平台驱动都创建了驱动模型,移植平台驱动的主要工作是向模型注入实例。 这些模型你可以在源码目录//drivers/hdf_core/framework/support/platform/include
中找到定义。
本节我们会以GPIO为例,讲解如何移植平台驱动,移植过程包含以下步骤:
- 创建GPIO驱动
在源码目录//device/vendor_name/soc_name/drivers/gpio
中创建文件soc_name_gpio.c
。内容模板如下:
#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_name/drivers/lite.mk
是厂商驱动的构建的入口。我们需要从这个入口开始,进行构建。
#文件device/vendor_name/drivers/lite.mk
SOC_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.mk
SOC_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.mk
include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk
# 指定输出对象的名称,注意要与SOC驱动构建入口里的LITEOS_BASELIB 保持一致
MODULE_NAME := hdf_gpio
# 增加HDF框架的INCLUDE
LOCAL_CFLAGS += $(HDF_INCLUDE)
# 要编译的文件
LOCAL_SRCS += soc_name_gpio.c
# 编译参数
LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror -fsigned-char -fno-strict-aliasing -fno-common
include $(HDF_DRIVER)
- 配置产品加载驱动
产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs
中。
平台驱动请添加到platform的host中。
说明: moduleName要与驱动定义中的相同。
root {
...
platform :: host {
device_gpio :: device {
device0 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "SOC_NAME_gpio_driver";
}
}
}
}
器件驱动移植
LCD驱动移植
移植LCD驱动的主要工作是编写一个驱动,在驱动中生成模型的实例,并完成注册。
这些LCD的驱动被放置在源码目录//drivers/hdf_core/framework/model/display/driver/panel
中。
- 创建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驱动
产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs
中。修改该文件,在display的host中,名为device_lcd的device中增加配置。
注意: moduleName 要与panel驱动中的moduleName相同。
root {
...
display :: host {
device_lcd :: device {
deviceN :: deviceNode {
policy = 0;
priority = 100;
preload = 2;
moduleName = "LCD_XXXX";
}
}
}
}
TP驱动移植
本节描述如何移植触摸屏驱动。触摸屏的器件驱动被放置在源码目录//drivers/hdf_core/framework/model/input/driver/touchscreen
中。 移植触摸屏驱动主要工作是向系统注册ChipDevice模型实例。
详细的驱动开发指导,请参考 TOUCHSCREEN开发指导。
- 创建触摸屏器件驱动
在上述touchscreen目录中创建名为touch_ic_name.c
的文件。编写如下内容
#include "hdf_touch.h"
static int32_t HdfXXXXChipInit(struct HdfDeviceObject *device)
{
ChipDevice *tpImpl = CreateXXXXTpImpl();
if(RegisterChipDevice(tpImpl) != HDF_SUCCESS) { // 注册ChipDevice模型
ReleaseXXXXTpImpl(tpImpl);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
struct HdfDriverEntry g_touchXXXXChipEntry = {
.moduleVersion = 1,
.moduleName = "HDF_TOUCH_XXXX", // 注意这里的moduleName要与后续的配置完全一致
.Init = HdfXXXXChipInit,
};
HDF_INIT(g_touchXXXXChipEntry);
其中ChipDevice中要实现如下方法:
方法 | 实现说明 |
---|---|
int32_t (*Init)(ChipDevice *device) | 实现器件初始化 |
int32_t (*Detect)(ChipDevice *device) | 实现器件探测 |
int32_t (*Suspend)(ChipDevice *device) | 实现器件休眠 |
int32_t (*Resume)(ChipDevice *device) | 实现器件唤醒 |
int32_t (*DataHandle)(ChipDevice *device) | 需要实现从器件读取数据,将触摸点数据填写入device->driver->frameData中 |
int32_t (*UpdateFirmware)(ChipDevice *device) | 实现固件升级 |
- 配置产品,加载器件驱动
产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs
中。修改该文件,在名为input的host中,名为device_touch_chip的device中增加配置。
说明: moduleName 要与触摸屏驱动中的moduleName相同。
deviceN :: deviceNode {
policy = 0;
priority = 130;
preload = 0;
permission = 0660;
moduleName = "HDF_TOUCH_XXXX";
deviceMatchAttr = "touch_XXXX_configs";
}
WLAN驱动移植
WLAN驱动分为两部分,一部分负责管理WLAN设备,另一个部分负责处理WLAN流量。
图1 OpenHarmony WLAN结构示意图
如图1,左半部分负责管理WLAN设备,右半部分负责WLAN流量。HDF WLAN分别为这两部分做了抽象,驱动的移植过程可以看做分别实现这两部分所需接口。这些接口有:
接口 | 定义头文件 | 接口说明 |
---|---|---|
HdfChipDriverFactory | drivers\hdf_core\framework\include\wifi\hdf_wlan_chipdriver_manager.h | ChipDriver的Factory,用于支持一个芯片多个WLAN端口 |
HdfChipDriver | drivers\hdf_core\framework\include\wifi\wifi_module.h | 每个WLAN端口对应一个HdfChipDriver,用来管理一个特定端口 |
NetDeviceInterFace | drivers\hdf_core\framework\include\wifi\net_device.h | 与协议栈之间的接口,如发送数据、设置网络接口状态等 |
说明: 详细的接口开发指导,请参考WLAN开发。
具体的移植步骤如下:
- 创建HDF WLAN芯片驱动
在目录/device/vendor_name/peripheral/wifi/chip_name/
创建文件hdf_wlan_chip_name.c
。内容模板如下:
static int32_t HdfWlanXXXChipDriverInit(struct HdfDeviceObject *device) {
static struct HdfChipDriverFactory factory = CreateChipDriverFactory(); // 需要移植者实现的方法
struct HdfChipDriverManager *driverMgr = HdfWlanGetChipDriverMgr();
if (driverMgr->RegChipDriver(&factory) != HDF_SUCCESS) { // 注册驱动工厂
HDF_LOGE("%s fail: driverMgr is NULL!", __func__);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
struct HdfDriverEntry g_hdfXXXChipEntry = {
.moduleVersion = 1,
.Init = HdfWlanXXXChipDriverInit,
.Release = HdfWlanXXXChipRelease,
.moduleName = "HDF_WIFI_CHIP_XXX" // 注意:这个名字要与配置一致
};
HDF_INIT(g_hdfXXXChipEntry);
在上述代码的CreateChipDriverFactory方法中,需要创建一个HdfChipDriverFactory类型的对象。该对象提供如下方法:
接口 | 说明 |
---|---|
const char *driverName | 当前driverName |
int32_t (*InitChip)(struct HdfWlanDevice *device) | 初始化芯片 |
int32_t (*DeinitChip)(struct HdfWlanDevice *device) | 去初始化芯片 |
void (*ReleaseFactory)(struct HdfChipDriverFactory *factory) | 释放HdfChipDriverFactory对象 |
struct HdfChipDriver *(*Build)(struct HdfWlanDevice *device, uint8_t ifIndex) | 创建一个HdfChipDriver;输入参数中,device是设备信息,ifIndex是当前创建的接口在这个芯片中的序号 |
void (*Release)(struct HdfChipDriver *chipDriver) | 释放chipDriver |
uint8_t (*GetMaxIFCount)(struct HdfChipDriverFactory *factory) | 获取当前芯片支持的最大接口数 |
其中Build方法负责创建一个管理指定网络接口的对象HdfChipDriver。该对象需要提供方法:
接口 | 说明 |
---|---|
int32_t (*init)(struct HdfChipDriver *chipDriver, NetDevice *netDev) | 初始化当前网络接口,这里需要向netDev提供接口NetDeviceInterFace |
int32_t (*deinit)(struct HdfChipDriver *chipDriver, NetDevice *netDev) | 去初始化当前网络接口 |
struct HdfMac80211BaseOps *ops | WLAN基础能力接口集 |
struct HdfMac80211STAOps *staOps | 支持STA模式所需的接口集 |
struct HdfMac80211APOps *apOps | 支持AP模式所需要的接口集 |
- 编写配置文件描述驱动支持的芯片
在产品配置目录下创建芯片的配置文件,保存至源码路径//vendor/vendor_name/product_name/config/wifi/wlan_chip_chip_name.hcs
该文件模板如下:
root {
wlan_config {
chip_name :& chipList {
chip_name :: chipInst {
match_attr = "hdf_wlan_chips_chip_name"; /* 这是配置匹配属性,用于提供驱动的配置根 */
driverName = "driverName"; /* 需要与HdfChipDriverFactory中的driverName相同*/
sdio {
vendorId = 0xXXXX; /* your vendor id */
deviceId = [0xXXXX]; /*your supported devices */
}
}
}
}
}
说明:
路径和文件中的vendor_name、product_name、chip_name请替换成实际名称。
vendorId 和 deviceId需要根据实际芯片的识别码进行填写。
- 编写配置文件,加载驱动
产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs
中。修改该文件,在名为network的host中,名为device_wlan_chips的device中增加配置。模板如下:
deviceN :: deviceNode {
policy = 0;
preload = 2;
moduleName = "HDF_WLAN_CHIPS";
deviceMatchAttr = "hdf_wlan_chips_chip_name";
serviceName = "driverName";
}
说明: moduleName 要与HDF WLAN 芯片驱动中的moduleName相同。
- 修改Kconfig文件,让移植的WLAN模组出现再内核配置中
在device/vendor_name/drivers/Kconfig
中增加配置菜单,模板如下
config DRIVERS_HDF_WIFI_chip_name
bool "Enable chip_name Host driver"
default n
depends on DRIVERS_HDF_WLAN help
Answer Y to enable chip_name Host driver.
说明: 请替换模板中的chip_name为实际的芯片名称。
- 修改构建脚本,让驱动参与内核构建
在源码文件//device/vendor_name/drivers/lite.mk
末尾追加如下内容。
ifeq ($(LOSCFG_DRIVERS_HDF_WIFI_chip_name), y)
# 构建完成要链接一个叫hdf_wlan_chipdriver_chip_name的对象,建议按这个命名,防止冲突
LITEOS_BASELIB += -lhdf_wlan_chipdriver_chip_name
# 增加构建目录gpio
LIB_SUBDIRS += ../peripheral/wifi/chip_name
endif
说明: 请替换模板中的chip_name为实际的芯片名称。
经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?
为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview
OpenHarmony 开发环境搭建
《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
- 系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……
OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview