【鸿蒙设备开发】OpenHarmony 小型系统芯片移植指南(三)驱动移植_openharmony设备开发
📌往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)
①📖 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
②📖嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
③📖 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
④📖 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
⑤📖 记录一场鸿蒙开发岗位面试经历~
⑥📖 持续更新中……
移植概述
驱动主要包含两部分,平台驱动和器件驱动。平台驱动主要包括通常在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中要实现如下方法:
- 配置产品,加载器件驱动
产品的所有设备信息被定义在源码文件//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分别为这两部分做了抽象,驱动的移植过程可以看做分别实现这两部分所需接口。这些接口有:
说明: 详细的接口开发指导,请参考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类型的对象。该对象提供如下方法:
其中Build方法负责创建一个管理指定网络接口的对象HdfChipDriver。该对象需要提供方法:
- 编写配置文件描述驱动支持的芯片
在产品配置目录下创建芯片的配置文件,保存至源码路径//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为实际的芯片名称。