未验证 提交 1ced4330 编写于 作者: O openharmony_ci 提交者: Gitee

!12934 翻译完成 12097+11629

Merge pull request !12934 from ester.zhou/TR-12097
......@@ -58,6 +58,14 @@ The download steps for other resources are the same as those in the mainline ver
## SoC Porting Cases
- Mini System SoC Porting Cases
- [Mini-System Devices with Screens — Bestechnic SoC Porting Case](porting-bes2600w-on-minisystem-display-demo.md)
- [Combo Solution – ASR Chip Porting Case](porting-asr582x-combo-demo.md)
- [Mini-System Devices with Screens – Bestechnic SoC Porting Case](porting-bes2600w-on-minisystem-display-demo.md)
- [Combo Solution – ASR Chip Porting Case](porting-asr582x-combo-demo.md)
- [IoT Solution - Chipsea CST85 Chip Porting Case](porting-cst85f01-combo-demo.md)
- [Mini System STM32F407 SoC Porting Case](porting-stm32f407-on-minisystem-eth.md)
- [Combo Solution – W800 Chip Porting Case](porting-w800-combo-demo.md)
- Small System SoC Porting Cases
- [Mini-System Devices – STM32MP1 SoC Porting Case](porting-stm32mp15xx-on-smallsystem.md)
- Standard System SoC Porting Cases
- [Standard System Solution – Rockchip RK3568 Porting Case](porting-dayu200-on_standard-demo.md)
- [Standard System Solution – Rockchip RK3566 Porting Case](https://gitee.com/openharmony/vendor_kaihong/blob/master/khdvk_3566b/porting-khdvk_3566b-on_standard-demo.md)
- [Standard System Solution – Yangfan Porting Case](porting-yangfan-on_standard-demo.md)
# Mini-System Devices with Screens — Bestechnic SoC Porting Case
# Mini-System Devices with Screens – Bestechnic SoC Porting Case
This document exemplifies the porting procedure for a development board on a mini-system device with a screen – an intelligent switch panel. It uses the BES multi-modal V200Z-R development board powered by the Bestechnic BES2600W SoC as an example. Components such as `ace_engine_lite`, `graphic_ui`, `aafwk_lite`, `appexecfwk_lite`, and `HDF` are adapted based on the OpenHarmony LiteOS-M kernel. This example uses the board-SoC separation solution as the porting architecture, the Newlib C or Musl C library as the toolchain, and GN and Kconfig graphical configuration for LiteOS-M kernel compilation.
......@@ -1395,7 +1393,7 @@ To adapt the `aafwk` subsystem, you need to add the `aafwk_lite` component in th
For details about the `aafwk_lite` use cases, see the `vendor/bestechnic/display_demo/tests/ability` directory, which includes the `launcher` and `js app` applications. The function invocation process of the applications is described as follows:
1. `launcher` application: Use `InstallLauncher` to install the `native ui` application, whose `BundleName` is `"com.example.launcher"`. After `AbilityMgrSliteFeature` is started, `AbilityMgrHandler::StartLauncher()` is invoked to start the `launcher` application.
2. `StartJSApp` application: Use `StartAbility` to start any `Want` and pass `want data` to `JS_APP_PATH`,
`SetWantData(&want, JS_APP_PATH, strlen(JS_APP_PATH) + 1)`.
......@@ -1422,7 +1420,7 @@ For details about product compatibility specifications, see [Introduction to Pro
### XTS Test Cases
For details about the `XTS` test cases, see [XTS](../subsystems/subsys-xts-guide.md). To adapt the `XTS` subsystem, you need to add the `xts_acts`/`xts_tools` component in the `config.json` file, as shown below:
For details about the `XTS` test cases, see [XTS](../device-test/xts.md). To adapt the `XTS` subsystem, you need to add the `xts_acts`/`xts_tools` component in the `config.json` file, as shown below:
{
"subsystem": "xts",
......
# IoT Solution - Chipsea CST85 Chip Porting Case
This document describes how to port the OpenHarmony LiteOS-M mini system on the cst85_wblink development board based on the Chipsea CST85 chip. In this document, Wi-Fi connection and XTS test samples are developed, and the adaptation of components such as wifi_lite, lwIP, startup, Utils, XTS, and HDF is implemented based on the OpenHarmony LiteOS-M kernel. The porting architecture uses the Board and SoC separation solution and the Newlib C library as the toolchain. The LiteOS-M kernel is compiled in gn+Kconfig graphical configuration mode.
## Compilation and Building Adaptation
### Directory Planning
This solution designs the directory structure using the [board and SoC decoupling idea](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md).
```
device
├── board --- Board vendor directory.
│ └── chipsea --- Board vendor name Chipsea.
│ └── cst85_wblink --- Board name cst85_wblink.
└── soc --- SoC vendor directory.
└── chipsea --- SoC vendor name Chipsea.
└── cst85 --- SoC series name CST85.
```
The planned product demo directory is as follows:
```
vendor
└── chipsea --- Vendor of the product demo, which is Chipsea here.
├── iotlink_demo --- Product name: Wi-Fi sample.
└── xts_demo --- Product name: XTS test sample.
```
### Product Definition
The vendor/chipsea/iotlink_demo describes the kernel, board, and subsystem information used by the product. The kernel, board model, and board vendor are required by the precompilation command and must be planned in advance. The information entered here corresponds to the planned directory. Example:
```
{
"product_name": "iotlink_demo", --- Product name.
"version": "3.0", --- OS version: 3.0
"device_company": "chipsea", --- Board vendor: Chipsea
"board": "cst85_wblink", --- Board name: cst85_wblink
"kernel_type": "liteos_m", --- Kernel type: liteos_m
"kernel_version": "3.0.0", --- Kernel version: 3.0.0
"subsystems": [] --- Subsystems
}
```
### Board Configuration
In the directory associated with the product definition, for example, **/device/board/chipsea/cst85_wblink**, you need to place the **config.gni** file in the **liteos_m** directory. This configuration file describes the board information, including the CPU, toolchain, kernel, and compile_flags. Example:
```
# Kernel type
kernel_type = "liteos_m"
# Kernel version
kernel_version = "3.0.0"
# Board CPU type
board_cpu = "cortex-m4"
# Toolchain: arm-none-eabi
board_toolchain = "arm-none-eabi"
# Path of the toolchain. You can use the system path '''' or customize the path.
board_toolchain_path = ""
# Compilation parameters about the board
board_cflags = []
# Link parameters about the board
board_ld_flags = []
# Header files about the board
board_include_dirs = []
# Board adapter dir for OHOS components.
board_adapter_dir = "${ohos_root_path}device/soc/chipsea"
```
### Precompilation
After the product directory, product definition, and board configuration are correctly configured, enter the precompilation command **hb set** in the root directory of the project. Then you can find the related product in the displayed list.
![ohos_config.json](figures/cst85_hb_set.png)
After you select a product and press **Enter**, a `ohos_config.json` file will be generated in the root directory. The file will list the product information to be compiled.
```
{
"root_path": "/home/openharmony",
"board": "cst85_wblink",
"kernel": "liteos_m",
"product": "iotlink_demo",
"product_path": "/home/openharmony/vendor/chipsea/iotlink_demo",
"device_path": "/home/openharmony/device/board/chipsea/cst85_wblink/liteos_m",
"device_company": "chipsea",
"os_level": "mini",
"version": "3.0",
"patch_cache": null,
"product_json": "/home/openharmony/vendor/chipsea/iotlink_demo/config.json",
"target_cpu": null,
"target_os": null,
"out_path": "/home/openharmony/out/cst85_wblink/iotlink_demo"
}
```
## Kernel Porting
### Kconfig Adaptation
During the compilation of **//kernel/liteos_m**, you need to use the `Kconfig` file for configurations in the corresponding board and SoC directories. Let's see the related configurations in the board and SoC directories.
The following uses `//device/board/chipsea` as an example for the `Kconfig` in the board directory:
```
device/board/chipsea
├── cst85_wblink --- Board configuration directory **cst85_wblink**.
│ ├── Kconfig.liteos_m.board --- Board configuration options.
│ ├── Kconfig.liteos_m.defconfig.board --- Default board configuration items.
│ └── liteos_m
│ └── config.gni --- Board configuration file.
├── Kconfig.liteos_m.boards --- Board configurations of the board vendor.
└── Kconfig.liteos_m.defconfig.boards --- Board configurations of the board vendor.
```
In `cst85_wblink/Kconfig.liteos_m.board`, configure that **BOARD_CST85_WBLINK** can be selected only when **SOC_CST85F01** is selected.
```
config BOARD_CST85_WBLINK
bool "select board cst85_wblink"
depends on SOC_CST85F01
```
The following uses `//device/soc/chipsea` as an example for the `Kconfig` in the SoC directory:
```
device/soc/chipsea/
├── cst85 --- CST85 series.
│ ├── Kconfig.liteos_m.defconfig.cst85f01 --- Default CST85F01 SoC configuration.
│ ├── Kconfig.liteos_m.defconfig.series --- Default configuration of the CST85 series.
│ ├── Kconfig.liteos_m.series --- Configuration of the CST85 series.
│ └── Kconfig.liteos_m.soc --- CST85 SoC configuration.
├── Kconfig.liteos_m.defconfig --- Default SoC configuration.
├── Kconfig.liteos_m.series --- Series configuration.
└── Kconfig.liteos_m.soc --- SoC configuration.
```
The `cst85/Kconfig.liteos_m.series` configuration is as follows:
```
config SOC_SERIES_CST85
bool "Chipsea CST85 Series"
select ARM
select SOC_COMPANY_CHIPSEA
select CPU_CORTEX_M4
help
Enable support for Chipsea CST85 series
```
**SOC_CST85F01** can be selected in **cst85/Kconfig.liteos_m.soc** only when **SOC_SERIES_CST85** is selected.
```
choice
prompt "Chipsea CST85 series SoC"
depends on SOC_SERIES_CST85
config SOC_CST85F01
bool "SoC CST85F01"
endchoice
```
To compile **BOARD_CST85_WBLINK**, select **SOC_COMPANY_CHIPSEA**, **SOC_SERIES_CST85** and **SOC_CST85F01**. You can run the `make menuconfig` command in `kernel/liteos_m` for configurations.
![cst85_kconfig.json](figures/cst85_kconfig.png)
The configured file is saved to `//vendor/chipsea/iotlink_demo/kernel_configs/debug.config` by default. You can also directly configure **debug.config**.
```
LOSCFG_SOC_SERIES_CST85=y
LOSCFG_KERNEL_BACKTRACE=y
LOSCFG_KERNEL_CPUP=y
LOSCFG_PLATFORM_EXC=y
```
### Modular Compilation
The compilation of `Board` and `SoC` adopts the modular compilation method, starting from **kernel/liteos_m/BUILD.gn** and increasing by level. The adaptation process of this solution is as follows:
1. Create the **BUILD.gn** file in `//device/board/chipsea` and add the following content to the file:
```
if (ohos_kernel_type == "liteos_m") {
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [
"cst85_wblink"
]
}
}
```
In the preceding **BUILD.gn** file, **cst85_wblink** is the module name organized by directory level.
2. Create the **BUILD.gn** file in `//device/soc/chipsea` in the same way and add the following content to the file by directory level:
```
if (ohos_kernel_type == "liteos_m") {
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [
"cst85",
"hals",
]
}
}
```
3. In the `//device/soc/chipsea` module at each level, add the **BUILD.gn** file and compile the module. The following uses `//device/soc/chipsea/cst85/liteos_m/sdk/bsp/arch/BUILD.gn` as an example:
```
import("//kernel/liteos_m/liteos.gni")
module_name = "sdk_bsp_arch"
kernel_module(module_name) {
sources = [
"boot/armgcc_4_8/boot_startup.S",
"boot/armgcc_4_8/exception.S",
"boot/fault_handler.c",
"cmsis/cmsis_nvic.c",
"ll/ll.c",
"main/arch_main.c",
]
include_dirs = [
"boot",
"boot/armgcc_4_8",
]
deps = [
"//base/startup/bootstrap_lite/services/source:bootstrap",
]
}
config("public") {
include_dirs = [
".",
"boot",
"compiler",
"cmsis",
"ll",
]
}
```
To organize links and some compilation options, set the following parameters in **config("public")**:
```
config("public") {
include_dirs = [] --- Common header file.
ldflags = [] --- Link parameters, including the Id file.
libs = [] --- Link library.
defines = [] --- Definitions.
}
```
![](../public_sys-resources/icon-note.gif) **NOTE**
It is recommended that common parameter options and header files not be repeatedly filled in each component.
### Kernel Startup Adaptation
The kernel startup adaptation file is stored in `//device/soc/chipsea/cst85/liteos_m/sdk/modules/rtos/src/rtos.c`.
The general idea of kernel startup adaptation is as follows:
1. Use `OsVectorInit();` to initialize the interrupt vector and initialize the interrupt processing function.
2. Use `osKernelInitialize` to initialize the kernel.
3. Create a thread for OS component platform initialization using `OHOS_SystemInit`.
4. Use `DeviceManagerStart()` for HDF initialization.
5. The kernel starts to schedule the `LOS_Start` thread.
This section describes step 3 in detail. Other steps are used to call kernel functions and are not described here.
Initialize necessary actions before **OHOS_SystemInit** is started in step 3, as shown below:
```
...
LOS_KernelInit();
DeviceManagerStart();
OHOS_SystemInit();
LOS_Start();
....
```
### Interrupt Adaptation
To ensure the normal running of LiteOS-M, two interrupt service routines must be redirected to the ISRs specified by LiteOS-M: HalPendSV and OsTickerHandler. This depends on whether LiteOS-M takes over the interrupt vector table when LiteOS-M is adapted.
```
/**
* @ingroup los_config
* Configuration item for using system defined vector base address and interrupt handlers.
* If LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT is set to 0, vector base address will not be
* modified by system. In arm, it should be noted that PendSV_Handler and SysTick_Handler should
* be redefined to HalPendSV and OsTickHandler respectably in this case, because system depends on
* these interrupt handlers to run normally. What's more, LOS_HwiCreate will not register handler.
*/
#ifndef LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT
#define LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT 1
#endif
```
#### Whether the OS Takes Over Interrupt Vectors
You can configure the **target_config.h** file to determine whether to take over LiteOS. The options are: **1**: yes; **0**: no.
```
#define LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT 0
```
If this parameter is set to **1**, LiteOS changes **SCB->VTOR** to **g_hwiForm**. Therefore, the ArchHwiCreate API of the LITEOS needs to be called to configure the original ISRs of the SoC to the new interrupt vector table **g_hwiForm** during startup, and the interrupt service routines of PendSV and SysTicke are redirected to HalPendSV and OsTickerHandler. Otherwise, the original ISRs of the SoC do not respond.
If this parameter is set to **0**, the original interrupt vector table of the SoC is used. For CST85F01, the interrupt vector table is **__vectors_start___** (**NVIC_Vectors_Init** copies the content of **__isr_vector** to this table). To adapt LiteOS, you must redirect the interrupt service routines of PendSV and SysTick to HalPendSV and OsTickHandler. Otherwise, the system cannot run.
In this example, LiteOS is not allowed to take over interrupt processing. Therefore, you need to redirect the interrupt service routines of PendSV and SysTick to HalPendSV and OsTickHandler during startup.
```
#ifdef CFG_LITEOS
static void OsVectorInit(void)
{
NVIC_SetVector(PendSV_IRQn, (uint32_t)HalPendSV);
NVIC_SetVector(SysTick_IRQn, (uint32_t)OsTickHandler);
}
#endif
```
#### Interrupt Vector Table Address Alignment
As described in the documents related to Cortex-M, the minimum address of the interrupt vector table is 32-byte aligned, that is, 0x80.
For example, if 21 more interrupts are required, a total of 37 interrupts are required because there are already 16 system interrupts. One 0x80 is insufficient for 37\*4 entries, and two 0x80s, that is, 0x100, are required.
In CST85F01 adaptation, the maximum number of interrupt vectors is 128 (defined in **target_config.h**).
```
#define LOSCFG_PLATFORM_HWI_LIMIT 128
```
Here, 128 interrupts and system interrupts are required, that is, a total of 144 (128 + 16) interrupts, and 144\*4 entries are required. These entries need to be covered by four 0x80s, that is, 0x200 alignment. Otherwise, the system restarts.
Therefore, the interrupt alignment needs to be overridden to 0x200.
```
#ifndef LOSCFG_ARCH_HWI_VECTOR_ALIGN
#define LOSCFG_ARCH_HWI_VECTOR_ALIGN 0x200
#endif
```
### LittleFS Adaptation
In the XTS test, the syspara test involves the read and write of files in the KV storage. Therefore, a file system needs to be adapted to store the KV to a certain position in the flash memory. Therefore, the LittleFS is adapted.
During the adaptation, an adaptation API needs to be added to `device/soc/chipsea/cst85/liteos_m/components/drivers/littlefs`.
```
#define LFS_DEFAULT_START_ADDR 0x081E3000 ---LittleFS start address
#define LFS_DEFAULT_BLOCK_SIZE 4096 --- Block size
#define LFS_DEFAULT_BLOCK_COUNT 25 ---Number of blocks
```
Finally, the LittleFS API of the kernel is implemented in `device/soc/chipsea/cst85/liteos_m/components/drivers/littlefs/hal_vfs.c`.
```
int32_t hal_vfs_init(void)
{
VfsOps = malloc(sizeof(struct lfs_manager));
if (VfsOps == NULL) {
printf("+++ hal_vfs_init: NO memory!!\n");
return -1;
} else {
memset(VfsOps, 0, sizeof(struct lfs_manager));
}
VfsOps->LfsOps.read = lfs_block_read; // Flash read API
VfsOps->LfsOps.prog = lfs_block_write; // Flash write API
VfsOps->LfsOps.erase = lfs_block_erase; // Flash erase API
VfsOps->LfsOps.sync = lfs_block_sync;
VfsOps->LfsOps.read_size = 256;
VfsOps->LfsOps.prog_size = 256;
VfsOps->LfsOps.cache_size = 256;
VfsOps->LfsOps.lookahead_size = 16;
VfsOps->LfsOps.block_cycles = 500;
VfsOps->start_addr = LFS_DEFAULT_START_ADDR;
VfsOps->LfsOps.block_size = LFS_DEFAULT_BLOCK_SIZE;
VfsOps->LfsOps.block_count = LFS_DEFAULT_BLOCK_COUNT;
SetDefaultMountPath(0,"/data");
if (LOS_FsMount(NULL, "/data", "littlefs", 0, VfsOps) != FS_SUCCESS) {
printf("+++ hal_vfs_init: Mount littlefs failed!\n");
free(VfsOps);
return -1;
}
if (LOS_Mkdir("/data", 0777) != 0 ) {
printf("+++ hal_vfs_init: Make dir failed!\n");
}
flash_user_data_addr_length_set(LFS_DEFAULT_START_ADDR,
LFS_DEFAULT_BLOCK_SIZE * LFS_DEFAULT_BLOCK_COUNT);
printf("+++ hal_vfs_init: Mount littlefs success!\n");
return 0;
}
```
### C Library Adaptation
In a mini system, the C library adaptation is complex. For details about the design idea, see [LiteOS-M Kernel Supports Smooth Switching Between musl and newlib](https://gitee.com/arvinzzz/ohos_kernel_design_specification/blob/master/liteos_m/%E6%94%AF%E6%8C%81newlib/%E5%86%85%E6%A0%B8%E9%80%82%E9%85%8Dnewlib%E6%96%B9%E6%A1%88%E6%80%9D%E8%B7%AF.md). The built-in `newlib` C library is used for system porting. Select **LOSCFG_LIBC_NEWLIB=y** in `vendor/chipsea/iotlink_demo/kernel_configs/debug.config`.
### printf Adaptation
To easily use standard functions in the C library to output information, implement adaptation to output the standard function information to the hardware (serial port). Therefore, the printf function is adapted.
Add the `wrap` link option of the printf function to `//device/board/chipsea/cst85_wblink/liteos_m/config.gni`.
```
board_ld_flags += [
"-Wl,--wrap=printf",
]
```
Implement *_wrap* printf in `device/soc/chipsea/cst85/liteos_m/sdk/bsp/wrapper/lite_sys.c`.
### HDF Adaptation of GPIO
To easily use the HDF to use GPIO functions, adapt the HDF for the GPIO.
1. The chip driver adaptation file is stored in the `//drivers/adapter/platform` directory. Add the **gpio_chipsea.c** and **gpio_chipsea.h** files to the **gpio** directory, and add compilation conditions of the new driver file to **BUILD.gn**.
```
if (defined(LOSCFG_SOC_COMPANY_CHIPSEA)) {
sources += [ "gpio_chipsea.c" ]
}
```
2. Describe the driver description file in **gpio_chipsea.c** as follows:
```
struct HdfDriverEntry g_gpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_PLATFORM_GPIO",
.Bind = GpioDriverBind,
.Init = GpioDriverInit,
.Release = GpioDriverRelease,
};
HDF_INIT(g_gpioDriverEntry);
```
3. Add the GPIO hardware description file **gpio.hcs** to **cst85/liteos_m/components/hdf_config/device_info.hcs**. The mapped gpio0 controls the programmable LED on the card. The hcs file content is as follows:
```
root {
platform :: host {
hostName = "platform_host";
priority = 50;
device_gpio :: device {
gpio0 :: deviceNode {
policy = 0;
priority = 100;
moduleName = "HDF_PLATFORM_GPIO";
serviceName = "HDF_PLATFORM_GPIO";
deviceMatchAttr = "gpio_config";
}
}
}
```
## OpenHarmony Subsystem Adaptation
### Communication Subsystem
In the communication subsystem, the wifi_lite component needs to be enabled and adapt related APIs.
The configuration items of the wifi_lite component are as follows:
```
"subsystem": "communication",
"components": [
{ "component": "wifi_lite", "features":[] }
]
```
The implementation related to Wi-Fi is under `//device/soc/chipsea/hals/communication/wifi_lite/wifiservice/wifi_device.c`.
```
......
WifiErrorCode Scan(void)
{
WIFI_STATE_INVALID_CHECK(WIFI_INACTIVE);
int testNum = MEMP_NUM_NETCONN;
dbg("testNum %d\r\n", testNum);
ChipseaWifiMsg msg = {
.eventId = WIFI_START_SCAN,
.payLoad = 0,
};
if (WifiCreateLock() != WIFI_SUCCESS) {
return ERROR_WIFI_NOT_AVAILABLE;
}
if (rtos_queue_write(g_wifiData.wifiQueue, &msg, 1, false) != 0) {
dbg("wifiDevice:rtos_queue_write err\r\n");
WifiUnlock();
return ERROR_WIFI_NOT_AVAILABLE;
}
WifiUnlock();
return WIFI_SUCCESS;
}
......
int GetSignalLevel(int rssi, int band)
{
if (band == HOTSPOT_BAND_TYPE_2G) {
if (rssi >= RSSI_LEVEL_4_2_G)
return RSSI_LEVEL_4;
if (rssi >= RSSI_LEVEL_3_2_G)
return RSSI_LEVEL_3;
if (rssi >= RSSI_LEVEL_2_2_G)
return RSSI_LEVEL_2;
if (rssi >= RSSI_LEVEL_1_2_G)
return RSSI_LEVEL_1;
}
if (band == HOTSPOT_BAND_TYPE_5G) {
if (rssi >= RSSI_LEVEL_4_5_G)
return RSSI_LEVEL_4;
if (rssi >= RSSI_LEVEL_3_5_G)
return RSSI_LEVEL_3;
if (rssi >= RSSI_LEVEL_2_5_G)
return RSSI_LEVEL_2;
if (rssi >= RSSI_LEVEL_1_5_G)
return RSSI_LEVEL_1;
}
return ERROR_WIFI_INVALID_ARGS;
}
```
### Kernel Subsystem
For the kernel subsystem, you need to configure the lwIP component closely related to Wi-Fi, use the third-party lwIP component of the community, and specify the directory for adapting the third-party lwIP and Wi-Fi systems.
By default, `lwip` is configured in the `LiteOS-M kernel` directory. Therefore, the compilation function is available. You can specify the `lwip` compilation directory in the `kernel` component. The sample code is as follows:
```
{
"subsystem": "kernel",
"components": [
{
"component": "liteos_m",
"features": [
"ohos_kernel_liteos_m_lwip_path = \"//device/soc/chipsea/cst85/liteos_m/sdk/modules/lwip-2.1\""
--- Specify that adaptation is performed in the chip vendor directory.
]
}
]
},
```
The `//device/soc/chipsea/cst85/liteos_m/sdk/modules/lwip-2.1/BUILD.gn` file describes the compilation of `lwip` as follows:
```
import("//kernel/liteos_m/liteos.gni")
import("$LITEOSTHIRDPARTY/lwip/lwip.gni")
import("$LITEOSTOPDIR/components/net/lwip-2.1/lwip_porting.gni")
module_switch = defined(LOSCFG_NET_LWIP_SACK)
module_name = "lwip"
kernel_module(module_name) {
sources = LWIP_PORTING_FILES + LWIPNOAPPSFILES -
[ "$LWIPDIR/api/sockets.c" ] + [ "porting/src/ethernetif.c" ] --- Add the **ethernetif.c** file for Ethernet network adapter initialization and adaptation.
defines = [ "LITEOS_LWIP=1" ]
defines += [ "CHECKSUM_BY_HARDWARE=1" ]
}
config("public") {
defines = [ "_BSD_SOURCE=1" ]
include_dirs =
[ "porting/include" ] + LWIP_PORTING_INCLUDE_DIRS + LWIP_INCLUDE_DIRS
}
```
In the `//device/soc/chipsea/cst85/liteos_m/sdk/modules/lwip-2.1/porting/include/lwip/lwipopts.h` file, the original `lwip` configuration items remain unchanged, the DSoftBus depends on these configuration items, and a hardware adaptation configuration item is added as follows:
```
#ifndef _PORTING_LWIPOPTS_H_
#define _PORTING_LWIPOPTS_H_
#include_next "lwip/lwipopts.h" --- Original configuration items remain unchanged.
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_CHECKSUM_ON_COPY 0
#define CHECKSUM_GEN_UDP 0 --- New hardware adaptation item.
#endif /* _PORTING_LWIPOPTS_H_ */
```
In the `//device/soc/chipsea/cst85/liteos_m/sdk/modules/lwip-2.1/porting/net_al.c` file, the adaptation to the initialization of the `Ethernet` network adapter is described as follows:
```
static err_t net_if_init(struct netif *net_if)
{
err_t status = ERR_OK;
struct fhost_vif_tag *vif = (struct fhost_vif_tag *)net_if->state;
#if LWIP_NETIF_HOSTNAME
{
/* Initialize interface hostname */
net_if->hostname = "CsWlan";
}
#endif /* LWIP_NETIF_HOSTNAME */
net_if->name[ 0 ] = 'w';
net_if->name[ 1 ] = 'l';
net_if->output = etharp_output;
net_if->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP;
net_if->hwaddr_len = ETHARP_HWADDR_LEN;
net_if->mtu = LLC_ETHER_MTU;
net_if->linkoutput = net_if_output;
memcpy(net_if->hwaddr, &vif->mac_addr, ETHARP_HWADDR_LEN);
return status;
}
```
### Startup Subsystem
To run application frameworks such as XTS or APP_FEATURE_INIT, the bootstrap_lite and syspara_lite components of the startup subsystem are adapted.
Add the corresponding configuration items to the `vendor/chipsea/wblink_demo/config.json` file:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite" --- bootstrap_lite component
},
{
"component": "syspara_lite", --- syspara_lite component
"features": [
"enable_ohos_startup_syspara_lite_use_posix_file_api = true"
]
}
]
},
```
When adapting the **bootstrap_lite** component, you need to manually add the following content to the link script file `//device/soc/chipsea/cst85/liteos_m/sdk/bsp/out/cst85f01/cst85f01.ld`:
```
__zinitcall_bsp_start = .;
KEEP (*(.zinitcall.bsp0.init))
KEEP (*(.zinitcall.bsp1.init))
KEEP (*(.zinitcall.bsp2.init))
KEEP (*(.zinitcall.bsp3.init))
KEEP (*(.zinitcall.bsp4.init))
__zinitcall_bsp_end = .;
__zinitcall_device_start = .;
KEEP (*(.zinitcall.device0.init))
KEEP (*(.zinitcall.device1.init))
KEEP (*(.zinitcall.device2.init))
KEEP (*(.zinitcall.device3.init))
KEEP (*(.zinitcall.device4.init))
__zinitcall_device_end = .;
__zinitcall_core_start = .;
KEEP (*(.zinitcall.core0.init))
KEEP (*(.zinitcall.core1.init))
KEEP (*(.zinitcall.core2.init))
KEEP (*(.zinitcall.core3.init))
KEEP (*(.zinitcall.core4.init))
__zinitcall_core_end = .;
__zinitcall_sys_service_start = .;
KEEP (*(.zinitcall.sys.service0.init))
KEEP (*(.zinitcall.sys.service1.init))
KEEP (*(.zinitcall.sys.service2.init))
KEEP (*(.zinitcall.sys.service3.init))
KEEP (*(.zinitcall.sys.service4.init))
__zinitcall_sys_service_end = .;
__zinitcall_sys_feature_start = .;
KEEP (*(.zinitcall.sys.feature0.init))
KEEP (*(.zinitcall.sys.feature1.init))
KEEP (*(.zinitcall.sys.feature2.init))
KEEP (*(.zinitcall.sys.feature3.init))
KEEP (*(.zinitcall.sys.feature4.init))
__zinitcall_sys_feature_end = .;
__zinitcall_run_start = .;
KEEP (*(.zinitcall.run0.init))
KEEP (*(.zinitcall.run1.init))
KEEP (*(.zinitcall.run2.init))
KEEP (*(.zinitcall.run3.init))
KEEP (*(.zinitcall.run4.init))
__zinitcall_run_end = .;
__zinitcall_app_service_start = .;
KEEP (*(.zinitcall.app.service0.init))
KEEP (*(.zinitcall.app.service1.init))
KEEP (*(.zinitcall.app.service2.init))
KEEP (*(.zinitcall.app.service3.init))
KEEP (*(.zinitcall.app.service4.init))
__zinitcall_app_service_end = .;
__zinitcall_app_feature_start = .;
KEEP (*(.zinitcall.app.feature0.init))
KEEP (*(.zinitcall.app.feature1.init))
KEEP (*(.zinitcall.app.feature2.init))
KEEP (*(.zinitcall.app.feature3.init))
KEEP (*(.zinitcall.app.feature4.init))
__zinitcall_app_feature_end = .;
__zinitcall_test_start = .;
KEEP (*(.zinitcall.test0.init))
KEEP (*(.zinitcall.test1.init))
KEEP (*(.zinitcall.test2.init))
KEEP (*(.zinitcall.test3.init))
KEEP (*(.zinitcall.test4.init))
__zinitcall_test_end = .;
__zinitcall_exit_start = .;
KEEP (*(.zinitcall.exit0.init))
KEEP (*(.zinitcall.exit1.init))
KEEP (*(.zinitcall.exit2.init))
KEEP (*(.zinitcall.exit3.init))
KEEP (*(.zinitcall.exit4.init))
__zinitcall_exit_end = .;
```
Adding the preceding content is because external APIs provided by `bootstrap_init` will be saved to the link segment. For details, see `//utils/native/lite/include/ohos_init.h`.
The following table lists the automatic initialization macros of bootstrap.
| API | Description |
| ---------------------- | -------------------------------- |
| SYS_SERVICE_INIT(func) | Entry for initializing and starting a core system service.|
| SYS_FEATURE_INIT(func) | Entry for initializing and starting a core system feature.|
| APP_SERVICE_INIT(func) | Entry for initializing and starting an application-layer service. |
| APP_FEATURE_INIT(func) | Entry for initializing and starting an application-layer feature. |
The **lib** file compiled using the loaded components needs to be manually add to the forcible link.
​If the `bootstrap_lite` component is configured in `vendor/chipsea/wblink_demo/config.json`:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite"
},
...
]
},
```
​The `bootstrap_lite` component compiles the `//base/startup/bootstrap_lite/services/source/bootstrap_service.c` file. In this file, `SYS_SERVICE_INIT` is used to inject the `Init` function symbol to `__zinitcall_sys_service_start` and `__zinitcall_sys_service_end`.
```
static void Init(void)
{
static Bootstrap bootstrap;
bootstrap.GetName = GetName;
bootstrap.Initialize = Initialize;
bootstrap.MessageHandle = MessageHandle;
bootstrap.GetTaskConfig = GetTaskConfig;
bootstrap.flag = FALSE;
SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
}
SYS_SERVICE_INIT(Init); --- Forcible link to the generated lib file is required if **SYS_INIT** is used for startup.
```
In the `//base/startup/bootstrap_lite/services/source/BUILD.gn` file, add the file to the compilation sources.
```
static_library("bootstrap") {
sources = [
"bootstrap_service.c",
"system_init.c",
]
....
```
Since the `Init` function does not support explicit calls, you need to forcibly link it to the final image. Configure **ld_flags** in the `device/board/chipsea/cst85_wblink/config.gni` file as follows:
```
board_ld_flags += [
"-Wl,--whole-archive",
"-lexample",
"-lhiview_lite",
"-lhilog_lite",
"-lhievent_lite",
"-lbroadcast",
"-lbootstrap",
"-Wl,--no-whole-archive",
]
```
### Utils Subsystem
To adapt the `utils` subsystem, you need to add the `kv_store`, `js_builtin`, `timer_task`, and `kal_timer` components to the `config.json` file.
```
{
"subsystem": "utils",
"components": [
{
"component": "kv_store",
"features": [
"enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true"
]
},
]
},
```
Similar to the adaptation of the `syspara_lite` component, when the `kv_store` component is adapted, the key-value pair is written to the file. In the mini system, file operation APIs include `POSIX` and `HalFiles`. For access to the file system in the kernel, use the `POSIX` API, which means you need to add `enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true` to the `features` field. If you are using the `HalFiles` API, no modification is required.
### XTS Subsystem
For the adaptation to the XTS subsystem, for example, `//vendor/chipsea/xts_demo/config.json`, you need to add the following component options:
```
"subsystem": "xts",
"components": [
{ "component": "xts_acts", "features":
[
"config_ohos_xts_acts_utils_lite_kv_store_data_path = \"/data\"",
"enable_ohos_test_xts_acts_use_thirdparty_lwip = true"
]
},
{ "component": "xts_tools", "features":[] }
]
```
The XTS lib needs to be forcibly linked in `device/board/chipsea/cst85_wblink/liteos_m/config.gni`.
```
board_ld_flags += [
"-Wl,--whole-archive",
"-lhctest",
"-lmodule_ActsParameterTest",
"-lmodule_ActsBootstrapTest",
"-lmodule_ActsDfxFuncTest",
"-lmodule_ActsKvStoreTest",
"-lmodule_ActsSamgrTest",
"-lmodule_ActsWifiServiceTest",
"-lmodule_ActsDsoftbusMgrTest",
]
```
# Standard System Solution – Rockchip RK3568 Porting Case
​This document describes how to port standard system functions based on the DAYU200 development board of the RK3568 chip from Rockchip. The porting processing mainly includes product configuration adding, kernel startup and upgrade, ADM-based conversion of audio, case summary of the camera, TP, LCD, Wi-Fi, BT, vibrator, sensor, and graphics display modules, as well as related function adaptation.
## Product Configuration and Directory Planning
### Product Configuration
Create a JSON file named after RK3568 in the `//productdefine/common/device` directory and specify the CPU architecture. The `//productdefine/common/device/rk3568.json` file is configured is as follows:
```
{
"device_name": "rk3568",
"device_company": "rockchip",
"target_os": "ohos",
"target_cpu": "arm",
"kernel_version": "",
"device_build_path": "device/board/hihope/rk3568",
"enable_ramdisk": true, // Specifies whether to support ramdisk secondary boot.
"build_selinux": true // Indicates whether SELinux permission management is supported.
}
```
Create a **rk3568.json** file in the **//productdefine/common/products** directory. This file is used to describe the SoC used by the product and the required subsystems. Configure the file as follows:
```
{
"product_name": "rk3568",
"product_company" : "hihope",
"product_device": "rk3568",
"version": "2.0",
"type": "standard",
"parts":{
"ace:ace_engine_standard":{},
"ace:napi":{},
...
"xts:phone_tests":{}
}
}
```
The main configurations are as follows:
1. **product_device**: SoC used by the product.
2. **type**: system type. In this example, set it to **standard**.
3. **parts**: subsystem to enable. A subsystem can be treated as an independently built functional block.
You can find predefined subsystems in **//build/subsystem_config.json**. You can also customize subsystems.
You are advised to copy the configuration file of Hi3516D V300 and delete the **hisilicon_products** subsystem, which is used to compile the kernel for Hi3516D V300.
### Directory Planning
This solution designs the directory structure using the [board and SoC decoupling idea](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md), and plans the SoC adaptation directory as follows:
```
device
├── board --- Board vendor directory
│ └── hihope --- Board vendor
│ └── rk3568 --- Board name, RK3568, which contains driver service code
└── soc --- SoC vendor directory
└── rockchip --- SoC vendor: Rockchip
└── rk3568 --- SoC series: RK3568, mainly solutions provided by the chip vendor and closed-source libraries
```
```
vendor
└── hihope
└── rk3568 --- Product name: product, HCS, and demo
```
## **Kernel Startup**
### Secondary Boot
Unlike traditional boot that directly mounts **system** and boots using **init** of **system**, secondary boot is to mount **ramdsik**, boot using **init** of **ramdsik**, perform necessary initialization operations (such as mounting the **system** and **vendor** partitions), and then switch to **init** of **system**.
RK3568 adaptation is to pack **ramdisk** compiled in the mainline version into **boot_linux.img**. The procedure is as follows:
1. Enable secondary boot.
Set **enable_ramdisk** in **productdefine/common/device/rk3568.json**.
```
{
"device_name": "rk3568",
"device_company": "hihope",
"target_os": "ohos",
"target_cpu": "arm",
"kernel_version": "",
"device_build_path": "device/hihope/build",
"enable_ramdisk": true,
"build_selinux": true
}
```
2. Pack the **ramdsik.img** file compiled in the mainline version to **boot_linux.img**.
View the configuration as follows:
RK supports **uboot** from **ramdisk**. You only need to add **ramdisk.img** to the configuration file of the packed **boot_linux.img**. Therefore, the **its** format of the mainline version is not used. Specifically, add the following content to the kernel compilation script **make-ohos.sh**:
```
function make_extlinux_conf()
{
dtb_path=$1
uart=$2
image=$3
echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF}
echo " kernel /extlinux/${image}" >> ${EXTLINUX_CONF}
echo " fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF}
if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then
echo " initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF}
fi
cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4"
echo " ${cmdline}" >> ${EXTLINUX_CONF}
}
```
### Packing
Add the **make-boot.sh** script for packing the boot image. This script can be called when the boot image is packed after **ramdisk** is compiled. The main content is as follows:
```
genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img
```
For details about modification for calling **make-boot.sh**, see the following:
https://gitee.com/openharmony/build/pulls/569/files
### INIT Configuration
For details about the init configuration, see the [specifications of the startup subsystem](https://gitee.com/openharmony/docs/blob/master/en/readme/startup.md).
## **Audio**
### Overall structure of the RK3568 audio module
![dayu200-audio-01.png](figures/dayu200/dayu200-audio-01.png)
### Introduction to ADM Adaptation Solution
#### ADM framework adaptation of the RK3568 platform
![](figures/dayu200/dayu200-audio-02.png)
1. ADM Drivers adapter
Register the Codec/DMA/I2S driver so that the ADM can load the driver node. Register the API functions for the interaction between the ADM and drivers.
2. ADM Drivers impl
Implement the ADM Drivers adapter API function, obtain configuration information such as **Codec_config.hcs/dai_config.hcs**, and register the information with the corresponding device.
3. Linux Drivers
You can use **ADM Drivers impl** to complete end-to-end driver configuration based on the hardware manual. It can also use the native Linux driver implementation and APIs to reduce your workload.
#### Directory Structure
```
./device/board/hihope/rk3568/audio_drivers
├── codec
│ └── rk809_codec
│ ├── include
│ │ ├── rk809_codec_impl.h
│ │ └── rk817_codec.h
│ └── src
│ ├── rk809_codec_adapter.c
│ ├── rk809_codec_linux_driver.c
│ └── rk809_codec_ops.c
├── dai
│ ├── include
│ │ ├── rk3568_dai_linux.h
│ │ └── rk3568_dai_ops.h
│ └── src
│ ├── rk3568_dai_adapter.c
│ ├── rk3568_dai_linux_driver.c
│ └── rk3568_dai_ops.c
├── dsp
│ ├── include
│ │ └── rk3568_dsp_ops.h
│ └── src
│ ├── rk3568_dsp_adapter.c
│ └── rk3568_dsp_ops.c
├── include
│ ├── audio_device_log.h
│ └── rk3568_audio_common.h
└── soc
├── include
│ └── rk3568_dma_ops.h
└── src
├── rk3568_dma_adapter.c
└── rk3568_dma_ops.c
```
### Detailed Process of Adapting RK3568 to ADM
#### Audio Framework Sorting
Sort out the audio structure of the target platform and specify the data stream and control stream path.
1. For the RK3568 platform, the audio structure is relatively simple. For details, see the overall audio structure of the RK3568 platform. The Codec functions as an independent device. The I2C controls the device, and the I2S implements the interaction between the codec device and the CPU.
2. Sort out the hardware information such as the I2S channel ID, corresponding pin ID, I2C channel ID, and address based on the schematic diagram.
3. Obtain the datasheet corresponding to the codec and the datasheet of the RK3568 platform (including the introduction to the registers such as the I2S and DMA channels).
#### ADM Structure
The following figure shows the ADM structure. Audio Peripheral Drivers and Platform Drivers are required for platform adaptation.
![dayu200-audio-03.png](figures/dayu200/dayu200-audio-03.png)
Based on the audio structure analysis in step 1, Audio Peripheral Drivers contain the RK809 driver, and Platform Drivers contain the DMA driver and I2S driver.
| Driver to Adapt| ADM Module| API File Path |
| -------------- | ----------- | ---------------------------------------------------- |
| RK809 driver | Accessory | drivers/framework/include/audio/audio_accessory_if.h |
| DMA driver | platform | drivers/framework/include/audio/audio_platform_if.h |
| I2S driver | DAI | drivers/framework/include/audio/audio_dai_if.h.h |
#### Driver Code Framework Setup
##### Configuring the HCS File
Register the driver node under **audio** in the **device_info.hcs** file.
```c
audio :: host {
hostName = "audio_host";
priority = 60;
device_dai0 :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DAI_RK3568";
serviceName = "dai_service";
deviceMatchAttr = "hdf_dai_driver";
}
}
device_codec :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_RK809";
serviceName = "codec_service_0";
deviceMatchAttr = "hdf_codec_driver";
}
}
device_codec_ex :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_RK817";
serviceName = "codec_service_1";
deviceMatchAttr = "hdf_codec_driver_ex";
}
}
device_dsp :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DSP_RK3568";
serviceName = "dsp_service_0";
deviceMatchAttr = "hdf_dsp_driver";
}
}
device_dma :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DMA_RK3568";
serviceName = "dma_service_0";
deviceMatchAttr = "hdf_dma_driver";
}
}
......
}
```
Select the Codec node or Accessory node based on the connected device, and configure the private attributes (including the start address of the register and the address of the related control register) corresponding to the device. **Codec_config.hcs** and **DAI_config.hcs** are involved.
For details about the configuration, see the HCS configuration section and **audio_parse** module code of the ADM framework in [Audio](https://gitee.com/openharmony/docs/blob/master/en/device-dev/driver/driver-peripherals-audio-des.md).
##### Codec/Accessory Module
1. Register the driver with the HDF framework. The code snippet is as follows. The **moduleName** is the same as that in the HCS file.
```
struct HdfDriverEntry g_codecDriverEntry = {
.moduleVersion = 1,
.moduleName = "CODEC_HI3516",
.Bind = CodecDriverBind,
.Init = CodecDriverInit,
.Release = CodecDriverRelease,
};
HDF_INIT(g_codecDriverEntry);
```
2. Fill the Codec module with:
**g_codecData**: operation function set and private data set of the codec device.
**g_codecDaiDeviceOps**: codec DAI device operation function set, including APIs for starting transmission and setting parameters.
**g_codecDaiData**: operation function set and private data set of the digital audio API of the codec.
3. Implement the bind, init, and release functions.
4. Verification
Add debug logs to the bind and init functions, compile the version, and obtain system logs.
```
[ 1.548624] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:258]: enter
[ 1.548635] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:260]: success
[ 1.548655] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:270]: enter
[ 1.549050] [E/"rk809_codec_adapter"] [GetServiceName][line:226]: enter
[ 1.549061] [E/"rk809_codec_adapter"] [GetServiceName][line:250]: success
[ 1.549072] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:316]: g_chip->accessory.drvAccessoryName = codec_service_1
[ 1.549085] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [accessory_dai] success.
[ 1.549096] [E/audio_core] [AudioRegisterAccessory][line:120]: Register [codec_service_1] success.
[ 1.549107] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:323]: success!
```
##### DAI Module
1. Register the I2S driver with the HDF framework. The code snippet is as follows. The **moduleName** is the same as that in the HCS file.
```c
struct HdfDriverEntry g_daiDriverEntry = {
.moduleVersion = 1,
.moduleName = "DAI_RK3568",
.Bind = DaiDriverBind,
.Init = DaiDriverInit,
.Release = DaiDriverRelease,
};
HDF_INIT(g_daiDriverEntry);
```
2. Fill the DAI module with:
```c
struct AudioDaiOps g_daiDeviceOps = {
.Startup = Rk3568DaiStartup,
.HwParams = Rk3568DaiHwParams,
.Trigger = Rk3568NormalTrigger,
};
struct DaiData g_daiData = {
.Read = Rk3568DeviceReadReg,
.Write = Rk3568DeviceWriteReg,
.DaiInit = Rk3568DaiDeviceInit,
.ops = &g_daiDeviceOps,
};
```
3. Implement the bind, init, and release functions.
4. Verification
Add debug logs to the bind and init functions, compile the version, and obtain system logs.
```
[ 1.549193] [I/device_node] launch devnode dai_service
[ 1.549204] [E/HDF_LOG_TAG] [DaiDriverBind][line:38]: entry!
[ 1.549216] [E/HDF_LOG_TAG] [DaiDriverBind][line:55]: success!
[ 1.549504] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [dai_service] success.
[ 1.549515] [E/HDF_LOG_TAG] [DaiDriverInit][line:116]: success.
```
##### Platform Module
1. Register the DMA driver with the HDF framework. The code snippet is as follows. The **moduleName** is the same as that in the HCS file.
```
struct HdfDriverEntry g_platformDriverEntry = {
.moduleVersion = 1,
.moduleName = "DMA_RK3568",
.Bind = PlatformDriverBind,
.Init = PlatformDriverInit,
.Release = PlatformDriverRelease,
};
HDF_INIT(g_platformDriverEntry);
```
2. Fill the DMA module with:
```c
struct AudioDmaOps g_dmaDeviceOps = {
.DmaBufAlloc = Rk3568DmaBufAlloc,
.DmaBufFree = Rk3568DmaBufFree,
.DmaRequestChannel = Rk3568DmaRequestChannel,
.DmaConfigChannel = Rk3568DmaConfigChannel,
.DmaPrep = Rk3568DmaPrep,
.DmaSubmit = Rk3568DmaSubmit,
.DmaPending = Rk3568DmaPending,
.DmaPause = Rk3568DmaPause,
.DmaResume = Rk3568DmaResume,
.DmaPointer = Rk3568PcmPointer,
};
struct PlatformData g_platformData = {
.PlatformInit = AudioDmaDeviceInit,
.ops = &g_dmaDeviceOps,
};
```
3. Implement the bind, init, and release functions.
4. Verification
Add debug logs to the bind and init functions, compile the version, and obtain system logs.
```
[ 1.548469] [E/rk3568_platform_adapter] [PlatformDriverBind][line:42]: entry!
[ 1.548481] [E/rk3568_platform_adapter] [PlatformDriverBind][line:58]: success!
[ 1.548492] [E/rk3568_platform_adapter] [PlatformDriverInit][line:100]: entry.
[ 1.548504] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:67]: entry!
[ 1.548515] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:91]: success!
[ 1.548528] [E/audio_core] [AudioSocRegisterPlatform][line:63]: Register [dma_service_0] success.
[ 1.548536] [E/rk3568_platform_adapter] [PlatformDriverInit][line:119]: success.
```
#### Driver Adaptation
##### Codec/Accessory Module
1. Read the DTS file to obtain the corresponding device node, and use the native driver registration function of Linux to obtain the corresponding device.
```
static int rk817_platform_probe(struct platform_device *pdev) {
rk817_pdev = pdev;
dev_info(&pdev->dev, "got rk817-codec platform_device");
return 0;
}
static struct platform_driver rk817_codec_driver = {
.driver = {
.name = "rk817-codec", // codec node in dts file
.of_match_table = rk817_codec_dt_ids,
},
.probe = rk817_platform_probe,
.remove = rk817_platform_remove,
};
```
2. Encapsulate the functions for reading and writing registers.
Use the **regmap** function of Linux based on the obtained device. You do not need to obtain the base address of the module.
Obtain the regmap code snippet of RK817.
```
g_chip = devm_kzalloc(&rk817_pdev->dev, sizeof(struct Rk809ChipData), GFP_KERNEL);
if (!g_chip) {
AUDIO_DEVICE_LOG_ERR("no memory");
return HDF_ERR_MALLOC_FAIL;
}
g_chip->pdev = rk817_pdev;
struct rk808 *rk808 = dev_get_drvdata(g_chip->pdev->dev.parent);
if (!rk808) {
AUDIO_DEVICE_LOG_ERR("%s: rk808 is NULL\n", __func__);
ret = HDF_FAILURE;
RK809ChipRelease();
return ret;
}
g_chip->regmap = devm_regmap_init_i2c(rk808->i2c,
&rk817_codec_regmap_config);
if (IS_ERR(g_chip->regmap)) {
AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap));
RK809ChipRelease();
return ret;
}
```
Code snippet of read and write functions of the register
```
int32_t Rk809DeviceRegRead(uint32_t reg, uint32_t *val)
{
if (regmap_read(g_chip->regmap, reg, val)) {
AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
int32_t Rk809DeviceRegWrite(uint32_t reg, uint32_t value) {
if (regmap_write(g_chip->regmap, reg, value)) {
AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
int32_t Rk809DeviceRegUpdatebits(uint32_t reg, uint32_t mask, uint32_t value) {
if (regmap_update_bits(g_chip->regmap, reg, mask, value)) {
AUDIO_DRIVER_LOG_ERR("update register bits fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
3. Define the register Initialization function.
The **regmap** function of Linux is used. Therefore, you need to define the **RegDefaultInit** function and read the initSeqConfig register and value in the HCS for configurations.
RK809RegDefaultInit code snippet
```c
int32_t RK809RegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup)
{
int32_t i;
struct AudioAddrConfig *regAttr = NULL;
if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL ||
regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) {
AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
return HDF_ERR_INVALID_PARAM;
}
regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem;
for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) {
Rk809DeviceRegWrite(regAttr[i].addr, regAttr[i].value);
}
return HDF_SUCCESS;
}
```
4. Encapsulate the read and write functions of the control API.
Set the control read/write functions to **RK809CodecReadReg** and **RK809CodecWriteReg**.
```c
struct CodecData g_rk809Data = {
.Init = Rk809DeviceInit,
.Read = RK809CodecReadReg,
.Write = RK809CodecWriteReg,
};
struct AudioDaiOps g_rk809DaiDeviceOps = {
.Startup = Rk809DaiStartup,
.HwParams = Rk809DaiHwParams,
.Trigger = RK809NormalTrigger,
};
struct DaiData g_rk809DaiData = {
.DaiInit = Rk809DaiDeviceInit,
.ops = &g_rk809DaiDeviceOps,
};
```
Encapsulate the read and write functions of the control API.
The original read/write prototype involves three parameters (**unsigned long virtualAddress**, **uint32_t reg**, and **uint32_t *val**). The virtual address is not required. Therefore, you only need to encapsulate one API as follows:
```c
int32_t RK809CodecReadReg(unsigned long virtualAddress,uint32_t reg, uint32_t *val)
{
if (val == NULL) {
AUDIO_DRIVER_LOG_ERR("param val is null.");
return HDF_FAILURE;
}
if (Rk809DeviceRegRead(reg, val)) {
AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
ADM_LOG_ERR("read reg 0x[%02x] = 0x[%02x]",reg,*val);
return HDF_SUCCESS;
}
int32_t RK809CodecWriteReg(unsigned long virtualAddress,uint32_t reg, uint32_t value)
{
if (Rk809DeviceRegWrite(reg, value)) {
AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
ADM_LOG_ERR("write reg 0x[%02x] = 0x[%02x]",reg,value);
return HDF_SUCCESS;
}
```
5. For other OPS functions:
- **Rk809DeviceInit**: Read the HCS file, initialize the codec register, and add the corresponding control configuration (/* reg, rreg, shift, rshift, min, max, mask, invert, value */) to kcontrol to facilitate dispatch control.
- **Rk809DaiStartup**: Read the HCS file, and configure the control register of the codec/accessory.
- **Rk809DaiHwParams**: Configure the corresponding register based on the audio attributes (such as the sampling rate, format, and channel) delivered by the HAL.
- **RK809NormalTrigger**: Operate the corresponding register based on the operation command code delivered by the HAL to start or stop the codec and switch between recording and playing.
##### DAI (i2s) Module
1. Read and write registers.
The idea is the same as that of the Codec module. Read the Linux DTS file and use the **regmap** function of Linux to read and write registers.
```c
int32_t Rk3568DeviceReadReg(unsigned long regBase, uint32_t reg, uint32_t *val)
{
AUDIO_DEVICE_LOG_ERR("entry");
(void)regBase;
struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
if(dmaOfNode == NULL) {
AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
}
struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
(void)regBase;
if (regmap_read(i2s_tdm->regmap, reg, val)) {
AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
int32_t Rk3568DeviceWriteReg(unsigned long regBase, uint32_t reg, uint32_t value)
{
AUDIO_DEVICE_LOG_ERR("entry");
(void)regBase;
struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
if(dmaOfNode == NULL) {
AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
}
struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
if (regmap_write(i2s_tdm->regmap, reg, value)) {
AUDIO_DEVICE_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
2. For other OPS functions:
- Rk3568DaiDeviceInit
Original framework, which reads the **DAI_config.hcs** parameter list and works with HwParams to set parameters.
- Rk3568DaiHwParams
Configure the I2S MCLK/BCLK/LRCLK clocks.
1. Calculate the MCLK based on different sampling rates.
```c
int32_t RK3568I2sTdmSetSysClk(struct rk3568_i2s_tdm_dev *i2s_tdm, const struct AudioPcmHwParams *param)
{
/* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */
uint32_t sampleRate = param->rate;
uint32_t mclk_parent_freq = 0;
switch (sampleRate) {
case AUDIO_DEVICE_SAMPLE_RATE_8000:
case AUDIO_DEVICE_SAMPLE_RATE_16000:
case AUDIO_DEVICE_SAMPLE_RATE_24000:
case AUDIO_DEVICE_SAMPLE_RATE_32000:
case AUDIO_DEVICE_SAMPLE_RATE_48000:
case AUDIO_DEVICE_SAMPLE_RATE_64000:
case AUDIO_DEVICE_SAMPLE_RATE_96000:
mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_192000;
break;
case AUDIO_DEVICE_SAMPLE_RATE_11025:
case AUDIO_DEVICE_SAMPLE_RATE_22050:
case AUDIO_DEVICE_SAMPLE_RATE_44100:
mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_176400;
break;
default:
AUDIO_DEVICE_LOG_ERR("Invalid LRCK freq: %u Hz\n", sampleRate);
return HDF_FAILURE;
}
i2s_tdm->mclk_tx_freq = mclk_parent_freq;
i2s_tdm->mclk_rx_freq = mclk_parent_freq;
return HDF_SUCCESS;
}
```
2. Calculate the BCLK/LRclk frequency division coefficient based on the obtained MCLK.
- Rk3568NormalTrigger
Complete a series of configurations based on the input and output types and commands (start/stop/pause/resume).
1. Start and stop the MCLK.
2. Start and stop DMA transfer.
3. Start and stop transmission.
See the code for detailed implementation, and refer to the API functions of the native Linux I2S driver.
```c
// Start or restore the process.
if (streamType == AUDIO_RENDER_STREAM) {
clk_prepare_enable(i2s_tdm->mclk_tx);
regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
I2S_DMACR_TDE_ENABLE,
I2S_DMACR_TDE_ENABLE);
} else {
clk_prepare_enable(i2s_tdm->mclk_rx);
regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
I2S_DMACR_RDE_ENABLE,
I2S_DMACR_RDE_ENABLE);
if (regmap_read(i2s_tdm->regmap, I2S_DMACR, &val)) {
AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_DMACR);
return ;
}
AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_DMACR, val);
}
if (atomic_inc_return(&i2s_tdm->refcount) == 1) {
regmap_update_bits(i2s_tdm->regmap, I2S_XFER,
I2S_XFER_TXS_START |
I2S_XFER_RXS_START,
I2S_XFER_TXS_START |
I2S_XFER_RXS_START);
if (regmap_read(i2s_tdm->regmap, I2S_XFER, &val)) {
AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_XFER);
return ;
}
AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_XFER, val);
}
```
##### Platform (DMA) Module
For other OPS functions:
1. Rk3568DmaBufAlloc/Rk3568DmaBufFree
Obtain the DMA device node. Use the system function **dma_alloc_wc** or **dma_free_wc** to apply for or release the DMA virtual memory and physical memory by referring to the method of obtaining the I2S device.
2. Rk3568DmaRequestChannel
Use the native Linux DMA API function to obtain the DMA transfer channel **dma_request_slave_channel**.
```
dmaRtd->dmaChn[streamType] = dma_request_slave_channel(dmaDevice, dmaChannelNames[streamType]);
```
3. Rk3568DmaConfigChannel
```
// Set channel parameters.
// Set voice playing channel parameters.
slave_config.direction = DMA_MEM_TO_DEV;
slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.dst_addr = I2S1_ADDR + I2S_TXDR;
slave_config.dst_maxburst = 8;
// Set recording channel parameters.
slave_config.direction = DMA_DEV_TO_MEM;
slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.src_addr = I2S1_ADDR + I2S_RXDR;
slave_config.src_maxburst = 8;
// Use the native Linux DMA API function to configure the DMA channel.
ret = dmaengine_slave_config(dmaChan, &slave_config);
if (ret != 0) {
AUDIO_DEVICE_LOG_ERR("dmaengine_slave_config failed");
return HDF_FAILURE;
}
```
4. Rk3568DmaSubmit/Rk3568DmaPending
Initialize a periodic DMA transfer descriptor by using the native Linux DMA API function **dmaengine_prep_dma_cyclic**. The **dmaengine_submit** API places the descriptor in the transfer queue, and then calls **dma_async_issue_pending** to start the transfer.
5. Rk3568PcmPointer
After step 4 is complete, the ADM framework calls Rk3568PcmPointer to cyclically write CirBuf and calculate the pointer.
```
dma_chn = dmaRtd->dmaChn[DMA_TX_CHANNEL];
buf_size = data->renderBufInfo.cirBufSize;
dmaengine_tx_status(dma_chn, dmaRtd->cookie[DMA_TX_CHANNEL], &dma_state);
if (dma_state.residue) {
currentPointer = buf_size - dma_state.residue;
*pointer = BytesToFrames(data->pcmInfo.frameSize, currentPointer);
} else {
*pointer = 0;
}
```
6. Rk3568DmaPause
Use the native Linux DMA API function **dmaengine_terminate_async** to stop DMA transfer.
```
dmaengine_terminate_async(dmaChan);
```
7. Rk3568DmaResume
Restart DMA transfer. You can perform operations related to **Rk3568DmaSubmit/Rk3568DmaPending**.
##### FAQs for Adaptation
1. After the audio plays for a period of time, the audio stops playing and there is a sharp and small sound.
Cause: After the playback stops, the components related to the codec are not powered off.
Solution: Register the **trigger** function of the codec. When the received command is **Stop**, power off the codec.
2. After the audio plays for a period of time and stops, no sound can be heard when the playback is resumed.
Cause: The **PAUSE** API function of the DMA driver does not stop DMA transfer.
Solution: When the playback stops, the **PAUSE** function of the DMA is not used. Instead, the DAM transfer stop API is used. Accordingly, the service logic of the resume function is equivalent to restarting the DMA transfer. You can perform operations related to **Rk3568DmaSubmit/Rk3568DmaPending**.
3. Noise occurs during playback.
Cause: The pointer position during DMA data transfer is incorrect.
Solution: The return value of the **Rk3568PcmPointer** function is the memory location for DMA transfer, which is calculated based on the difference between **buf** and **dma_state.residue**.
4. The audio can be played, but the MCLK pin does not have clock signals.
Cause: The pin-ctrl in the DTS file is not configured with the MCLK pin.
Solution: Modify the DTS file.
### Camera
**Basic Concepts**
The OpenHarmony camera driver model implements the HDI and the camera pipeline model to manage camera devices. The basic concepts of each layer are as follows:
1. HDI implementation layer: implements standard device APIs of OHOS cameras.
2. Framework layer: connects to the HDI implementation layer for control instruction and stream transfer, establishes data channels, and manages camera devices.
3. Adaptation layer: shields the differences between bottom-layer chips and OSs for multi-platform adaptation.
### Camera Driver Framework
#### Source Code Framework
The camera driver framework is stored in **drivers_peripheral**, and the source code directory is **drivers/peripheral/camera**.
```
|-- README_zh.md
|-- figures
| -- logic-view-of-modules-related-to-this-repository_zh.png
|-- hal
| |-- BUILD.gn # Entry for building the camera driver framework
| |-- adapter # Platform adaptation layer
| |-- buffer_manager
| |-- camera.gni # Global variables used by the component
| |-- device_manager
| |-- hdi_impl
| |-- include
| |-- init #demo sample
| |-- pipeline_core
| |-- test # Test code
| |-- utils
|-- hal_c # Dedicated C API for HiSilicon
| |-- BUILD.gn
| |-- camera.gni
| |-- hdi_cif
| |-- include
|-- interfaces # HDI APIs
|-- hdi_ipc
|-- hdi_passthrough
|-- include
```
The camera .hcs file is configurable for each chipset. Therefore, it is placed in the chipset-related repository. The following takes RK3568 as an example. The repository name is **vendor_hihope**, and the source code directory is **vendor/hihope/rk3568/hdf_config/uhdf/camera**.
├── hdi_impl
│ └── camera_host_config.hcs
└── pipeline_core
├── config.hcs
├── ipp_algo_config.hcs
└── params.hcs
The code repository related to the camera chipset of RK3568 is **device_hihope**. Path: device/board/hihope/rk3568/camera/
```
├── BUILD.gn
├── demo
│ └── include
│ └── project_camera_demo.h
├── device_manager
│ ├── BUILD.gn
│ ├── include
│ │ ├── imx600.h
│ │ ├── project_hardware.h
│ │ └── rkispv5.h
│ └── src
│ ├── imx600.cpp
│ └── rkispv5.cpp
├── driver_adapter
│ └── test
│ ├── BUILD.gn
│ ├── unittest
│ │ ├── include
│ │ │ └── utest_v4l2_dev.h
│ │ └── src
│ │ └── utest_v4l2_dev.cpp
│ └── v4l2_test
│ └── include
│ └── project_v4l2_main.h
└── pipeline_core
├── BUILD.gn
└── src
├── ipp_algo_example
│ └── ipp_algo_example.c
└── node
├── rk_codec_node.cpp
└── rk_codec_node.h
```
#### Camera Driver Framework Configuration
Path of the RK3568 configuration file:
"vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs". For other platforms, refer to the RK3568 adaptation.
```
hdi_server :: host {
hostName = "camera_host";
priority = 50;
caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];
camera_device :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
moduleName = "libcamera_hdi_impl.z.so";
serviceName = "camera_service";
}
}
...
}
```
Parameter description:
**Host**: A host node is an independent process. If an independent process is required, add a host node.
**Policy**: service publish policy. Set this parameter to **2** for the HDI service.
**moduleName**: name of the driver implementation library.
**serviceName**: service name, which must be globally unique.
Entry for implementing the Camera_host driver
File path: drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp
Dispatch device service messages.
**cmd Id:** command ID of the request.
**Data:** pointer to other services or I/O requests
**Reply:** pointer to the content of the returned message
```
static int32_t CameraServiceDispatch(struct HdfDeviceIoClient *client, int cmdId,
struct HdfSBuf *data, struct HdfSBuf *reply)
{
HdfCameraService *hdfCameraService = CONTAINER_OF(client->device->service, HdfCameraService, ioservice);
return CameraHostServiceOnRemoteRequest(hdfCameraService->instance, cmdId, data, reply);
}
```
Bind a device service: initializes the device service object and resource object.
```
int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject)
{
HDF_LOGI("HdfCameraHostDriverBind enter!");
if (deviceObject == nullptr) {
HDF_LOGE("HdfCameraHostDriverBind: HdfDeviceObject is NULL !");
return HDF_FAILURE;
}
```
Driver initialization function: detects and initializes the driver.
```
int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject)
{
return HDF_SUCCESS;
}
```
Driver resource release function: releases the bound device service object.
```
void HdfCameraHostDriverRelease(HdfDeviceObject *deviceObject)
{
if (deviceObject == nullptr || deviceObject->service == nullptr) {
HDF_LOGE("%{public}s deviceObject or deviceObject->service is NULL!", __FUNCTION__);
return;
}
HdfCameraService *hdfCameraService = CONTAINER_OF(deviceObject->service, HdfCameraService, ioservice);
if (hdfCameraService == nullptr) {
HDF_LOGE("%{public}s hdfCameraService is NULL!", __FUNCTION__);
return;
}
```
Define the driver descriptor: registers the driver code with the driver framework.
struct HdfDriverEntry g_cameraHostDriverEntry = {
.moduleVersion = 1,
.moduleName = "camera_service",
.Bind = HdfCameraHostDriverBind,
.Init = HdfCameraHostDriverInit,
.Release = HdfCameraHostDriverRelease,
};
#### Camera Configuration
In the camera module, all configuration files use the HCS configuration files supported by the system. The HCS configuration files are converted into HCB files during compilation. The configuration files burnt to the development board are in HCB format. In the code, the HCB files are parsed by using the HCS parsing API, to obtain the information in the configuration file.
hc_gen("build_camera_host_config") {
sources = [ rebase_path(
"$camera_product_name_path/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs") ]
}
ohos_prebuilt_etc("camera_host_config.hcb") {
deps = [ ":build_camera_host_config" ]
hcs_outputs = get_target_outputs(":build_camera_host_config")
source = hcs_outputs[0]
relative_install_dir = "hdfconfig"
install_images = [ chipset_base_dir ]
subsystem_name = "hdf"
part_name = "camera_device_driver"
}
### Camera Adaptation
#### New Product Platform Adaptation
In the **drivers/peripheral/camera/hal/camera.gni** file, call **product.gni** of different chipsets based on the input **product_company**, **product_name**, and **device_name** during compilation.
if (defined(ohos_lite)) {
import("//build/lite/config/component/lite_component.gni")
import(
"//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
} else {
import("//build/ohos.gni")
if ("${product_name}" == "ohos-arm64") {
import(
"//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
} else if ("${product_name}" == "Hi3516DV300") {
import(
"//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
} else if ("${product_name}" == "watchos") {
import(
"//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
} else {
import(
"//device/board/${product_company}/${device_name}/camera/product.gni")
}
}
The **product.gni** file in the following path specifies the path for compiling the code related to different chipsets:
```
device/${product_company}/${device_name}/camera/
```
The following is the **product.gni** file content of RK3568:
camera_device_name_path = "//device/board/${product_company}/${device_name}"
is_support_v4l2 = true
if (is_support_v4l2) {
is_support_mpi = false
defines += [ "SUPPORT_V4L2" ]
chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
camera_device_manager_deps =
"$camera_device_name_path/camera/src/device_manager:camera_device_manager"
camera_pipeline_core_deps =
"$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
}
Three code compilation paths **chipset_build_deps**, **camera_device_manager_deps**, and **camera_pipeline_core_deps** are specified in **product.gni**. The paths are used in **drivers/peripheral/camera/hal/BUILD.gn**.
#### Framework Adaptation
![dayu200-camera-01.png](figures/dayu200/dayu200-camera-01.png)
Take V4l2 as an example. The pipeline connection mode is to configure the connection in the HCS configuration file. The data source is called SourceNode, including hardware device control and data stream transfer.
You can determine whether to add the ISPNode as required because the ISPNode and SensorNode can be unified as the SourceNode in many operations. SinkNode is the key point of data transmission in the pipeline. Data is transmitted back to the buffer queue.
​ A node in the pipeline is the abstraction of the hardware/software module. Therefore, the hardware module node needs to control the hardware module. Before controlling the hardware module, you need to obtain the **deviceManager** of the corresponding hardware module and transmit the control command/data buffer through the **deviceManager**, therefore, the **deviceManager** has a v4l2 device manager abstract module, which is used to create the manager and controller of each hardware device, such as sensorManager, IspManager, and sensorController. Therefore, the v4l2 device manager is the general manager of each hardware device.
The controller in deviceManager directly interacts with the driver adaptation layer.
Based on the preceding description, to adapt a chip platform based on the Linux v4l2 framework, you only need to modify certain modules and HCS configuration file. If the standard v4l2 framework is used, the adapted code can be used. The following describes how to modify the modules.
The following directories are added:
**vendor/hihope/rk3568/hdf_config/uhdf/camera/**: HCS configuration file directory of the current chip product.
**device/hihope/rk3568/camera/**: code adaptation directory of the current chip product.
**drivers/peripheral/camera/hal/adapter/platform/v4l2**: common platform code.
#### HCS Configuration File Adaptation
```
├── hdi_impl
│ └── camera_host_config.hcs
└── pipeline_core
├── config.hcs
├── ipp_algo_config.hcs
└── params.hcs
```
Take the RK3568 development board as an example. The HCS file must be stored in the corresponding path.
```
vendor/${product_company}/${product_name}/ hdf_config/uhdf/camera/
```
```
template ability {
logicCameraId = "lcam001";
physicsCameraIds = [
"CAMERA_FIRST",
"CAMERA_SECOND"
];
metadata {
aeAvailableAntiBandingModes = [
"OHOS_CONTROL_AE_ANTIBANDING_MODE_OFF",
"OHOS_CONTROL_AE_ANTIBANDING_MODE_50HZ",
"OHOS_CONTROL_AE_ANTIBANDING_MODE_60HZ",
"OHOS_CONTROL_AE_ANTIBANDING_MODE_AUTO"
];
```
The **camera_host_config.hcs** file under **hdi_impl** contains the physical/logical camera configuration and capability configuration. The physical/logical camera configuration needs to be used in the HAL, and the logical camera and capability configuration need to be reported to the upper layer. Add the capability configuration based on the adapted chip product. The used capability values are key-value pairs, which are defined in **//drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h**.
```
normal_preview :: pipeline_spec {
name = "normal_preview";
v4l2_source :: node_spec {
name = "v4l2_source#0";
status = "new";
out_port_0 :: port_spec {
name = "out0";
peer_port_name = "in0";
peer_port_node_name = "sink#0";
direction = 1;
width = 0;
height = 0;
format = 0;
}
}
sink :: node_spec {
name = "sink#0";
status = "new";
stream_type = "preview";
in_port_0 :: port_spec {
name = "in0";
peer_port_name = "out0";
peer_port_node_name = "v4l2_source#0";
direction = 0;
}
}
}
```
The **config.hcs** file under **pipeline_core** uses the pipeline connection mode. Nodes of each flow and connection modes are classified by scenario.
The preceding is an example of the preview scenario. **normal_preview** is the scenario name, **source** and **sink** are nodes, **source** is the data source, and **sink** is the end. The source is the first node, and the node name is **source#0**. **status** and **in/out_port** indicate the node status and input/output port configurations, respectively.
Take **in_port_0** as an example. **name = "in0"** indicates that the input port is port0, the peer end is the out0 port of the source node, and **direction** indicates whether the source node and the peer node are directly connected. If a new chip is added, you must configure this file based on the actual connection mode.
When adding a function node, you need to inherit the **NodeBase** class and register the node in the .cpp file. For details, see the implemented nodes in **//drivers/peripheral/camera/hal/pipeline_core/nodes/src**.
root {
module = "";
template stream_info {
id = 0;
name = "";
}
template scene_info {
id = 0;
name = "";
}
preview :: stream_info {
id = 0;
name = "preview";
}
video :: stream_info {
id = 1;
name = "video";
}
The **param.hcs** file defines the scenario, flow type name, and flow ID. In the pipeline, the flow ID is used to identify the flow type. Therefore, you need to add the definition here.
#### Chipset and Platform Adaptation
The platform contains the platform common code, such as the Linux standard v4l2 adaptation API definition, common nodes adapted the v4l2 framework, and common **device_manager** adapted the v4l2 framework. The directory structure is as follows:
drivers/peripheral/camera/hal/adapter/platform
├── mpp
│ └── src
│ ├── device_manager
│ └── pipeline_core
└── v4l2
└── src
├── device_manager
├── driver_adapter
└── pipeline_core
The **v4l2** in the **platform** directory contains **src**. **driver_adapter** in **src** is the standard adaptation API of Linux v4l2. If custom functions are required, you can inherit **driver_adapter**, and implement the custom function APIs in the chipset. If there is no chip custom function, you can directly use the existing **driver_adapter**.
Nodes in the **platform** directory are hardware modules **v4l2_source_node** and **uvc_node** implemented based on the Linux v4l2 standard. The **uvc_node** is used for USB hot swap devices and is also a standard Linux API and can be directly used. The following shows the API declaration header file of **v4l2_source_node**.
namespace OHOS::Camera {
class V4L2SourceNode : public SourceNode {
public:
V4L2SourceNode(const std::string& name, const std::string& type);
~V4L2SourceNode() override;
RetCode Init(const int32_t streamId) override;
RetCode Start(const int32_t streamId) override;
RetCode Flush(const int32_t streamId) override;
RetCode Stop(const int32_t streamId) override;
RetCode GetDeviceController();
void SetBufferCallback() override;
RetCode ProvideBuffers(std::shared_ptr<FrameSpec> frameSpec) override;
private:
std::mutex requestLock_;
std::map<int32_t, std::list<int32_t>> captureRequests_ = {};
std::shared_ptr<SensorController> sensorController_ = nullptr;
std::shared_ptr<IDeviceManager> deviceManager_ = nullptr;
};
} // namespace OHOS::Camera
**Init**: initializes modules.
**Start**: enables functions, such as start stream.
**Stop**: disables functions.
**GetDeviceController** is the controller API for obtaining the deviceManager.
Chipset is the code related to a specific chip platform, for example, the RK3568 development board. The **device_manager** directory stores the configuration files of the sensors adapted the development board. The **pipeline_core** directory can store pipeline nodes added to meet specific requirements.
```
device/board/hihope/rk3568/camera
├── BUILD.gn
├── camera_demo
│ └── project_camera_demo.h
├── include
│ └── device_manager
├── product.gni
└── src
├── device_manager
├── driver_adapter
└── pipeline_core
```
The **device/board/hihope/rk3568/camera/** directory contains **include** and **src**. In **camera_demo** and **src**, **device ­manager** contains the sensor files adapted the chipset, works with the device management directory of **device manager** on the platform, and connects to the pipeline to implement platform-specific hardware processing APIs, data buffer delivery and reporting, and metadata interaction.
The following figure shows the implementation of **device_manager**. The pipeline controls and manages each hardware module. First, you need to obtain the manager of the corresponding device, and obtain the corresponding controller through the manager. The controller interacts with the corresponding driver.
![img](figures/dayu200/dayu200-camera-02.png)
Key APIs that need to be implemented in DeviceManager:
```
class SensorController : public IController {
public:
SensorController();
explicit SensorController(std::string hardwareName);
virtual ~SensorController();
RetCode Init();
RetCode PowerUp();
RetCode PowerDown();
RetCode Configure(std::shared_ptr<CameraStandard::CameraMetadata> meta);
RetCode Start(int buffCont, DeviceFormat& format);
RetCode Stop();
RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer);
void SetNodeCallBack(const NodeBufferCb cb);
void SetMetaDataCallBack(const MetaDataCb cb);
void BufferCallback(std::shared_ptr<FrameSpec> buffer);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);
}
```
**PowerUp** is used for power-on. You can call this API to power on a device for OpenCamera.
**PowerDown** is used for power-off. You can call this API to power off a device for CloseCamera.
The **Configures** API is used to deliver metadata. If metadata parameters need to be set on hardware devices, this API can be used to parse and deliver metadata parameters.
The **Start** API is used to enable the hardware module, and is called when each node in the pipeline is enabled. You can define the implementation as required. For example, the start operation of the sensor can be implemented here.
**Stop** and **Start** are reverse operations. You can call **Stop** for a stop operation.
**SendFrameBuffer** is an API for delivering the buffer of each frame. All buffer interaction operations with the driver are performed through this API.
SetNodeCallBack is a pipeline. This API is used to set the buffer callback function to DeviceManager.
SetMetaDataCallBack is a metadata callback. This API is used to report the metadata obtained from the bottom layer to the upper layer.
BufferCallback is used to upload the filled data buffer of each frame. This API is used to report the buffer to the pipeline.
**SetAbilityMetaDataTag** specifies the types of metadata to be obtained from the bottom layer. The framework can obtain information about one or more types of hardware devices separately. Therefore, you can use this API to obtain the required metadata.
For details about other APIs, see **drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/**.
#### IPP Adaptation
The IPP is an algorithm plugin module in the pipeline. It is loaded by the ippnode to perform algorithm processing on stream data. The ippnode supports simultaneous input of multiple channels of data and output of only one channel of data. The algorithm plugin loaded by the ippnode is specified in the following .hcs file:
vendor/${product_company}/${product_name}/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs
```
root {
module="sample";
ipp_algo_config {
algo1 {
name = "example";
description = "example algorithm";
path = "libcamera_ipp_algo_example.z.so";
mode = "IPP_ALGO_MODE_NORMAL";
}
}
}
```
**name**: algorithm plugin name.
**description**: functions of the algorithm plugin.
**path**: path of the algorithm plugin.
**mode**: running mode of the algorithm plugin.
The running modes of the algorithm plugin are provided by IppAlgoMode in **drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h** and can be extended as required.
```
enum IppAlgoMode {
IPP_ALGO_MODE_BEGIN,
IPP_ALGO_MODE_NORMAL = IPP_ALGO_MODE_BEGIN,
IPP_ALGO_MODE_BEAUTY,
IPP_ALGO_MODE_HDR,
IPP_ALGO_MODE_END
};
```
The algorithm plugin is compiled by the GN file **device/${product_company}/${device_name}/camera/BUILD.gn**. The algorithm plugin needs to implement the following APIs (specified by **ipp_algo.h**) for the ippnode to call:
typedef struct IppAlgoFunc {
int (*Init)(IppAlgoMeta* meta);
int (*Start)();
int (*Flush)();
int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta);
int (*Stop)();
} IppAlgoFunc;
1) **Init**: initializes the algorithm plugin. It is called by the ippnode before the start operation. **IppAlgoMeta** is defined in **ipp_algo.h** to provide a channel for transferring non-image data between the ippnode and algorithm plugin, such as the current running scenario and face coordinates output after algorithm processing, which can be extended as required.
2) **Start**: called by the ippnode for the start operation.
3) **Flush**: refreshes data. This API is called by the ippnode before the stop operation. When this API is called, the algorithm plugin needs to stop processing as quickly as possible.
4) **Process**: data processing API. Each frame of data is input to the algorithm plugin for processing through this API. **inBuffer** is a group of input buffers, **inBufferCount** is the number of input buffers, **outBuffer** is the output buffer, **meta** is the non-image data generated during algorithm processing, and **IppAlgoBuffer** is defined in **ipp_algo.h**.
5) **Stop**: called by the ippnode for the stop operation.
```
typedef struct IppAlgoBuffer {
void* addr;
unsigned int width;
unsigned int height;
unsigned int stride;
unsigned int size;
int id;
} IppAlgoBuffer;
```
In the preceding code, **id** indicates the ID of the port corresponding to the ippnode. For example, if the ID of **inBuffer[0]** is 0, **inBuffer[0]** corresponds to input port 0 of the ippnode. Note that **outBuffer** can be empty. In this case, one input buffer is transferred to the next node by the ippnode as the output buffer. At least one buffer in **inBuffer** is not empty. The input and output buffers are determined by the pipeline configuration.
For example, in the common preview scenario where there is no algorithm processing and only one channel of photographing data is transmitted to the ippnode, there is only one input buffer and the output buffer is empty. That is, the input buffer of the algorithm plugin is transparently transmitted.
For example, in the scenario where the algorithm plugin combines two channels of preview image data, the first channel of buffer needs to be previewed and sent for display. Copy the image of the second channel to the buffer of the first channel. In this case, there are two input buffers and the output buffer is empty.
For example, in the preview data format conversion scenario in the algorithm plugin, YUV data is converted into RGBA data. If there is only one YUV input buffer, the RGBA buffer cannot be output. In this case, a new buffer is required, and the output port buffer of the ippnode is transferred to the algorithm plugin as **outBuffer**. That is, there is only one input buffer and one output buffer.
For details about the port configuration of the ippnode, see the description of **config.hcs** in section 3.3.
#### V4L2 Driver Adaptation Instance
This section describes how to adapt the RK3568 development board in the v4l2 framework.
Distinguish the code related to the V4L2 platform and place it in the **drivers/peripheral/camera/hal/adapter/platform/v4l2** directory, which contains the **device_manager**, **driver_adapter**, and **pipeline_core** directories. The **driver_adapter** directory stores the code related to the v4l2 protocol. They can be used to interact with the v4l2 underlying driver. The **Pipeline_core** directory in this directory and the code in **drivers/peripheral/camera/hal/pipeline_core** form the pipeline framework. **v4l2_source_node** and **uvc_node** are dedicated nodes for v4l2. The **device_manager** directory stores the code for interaction between **device_manager** and **pipeline** and between the south and v4l2 adapter.
```
drivers/peripheral/camera/hal/adapter/platform/v4l2/src/
├── device_manager
│ ├── enumerator_manager.cpp
│ ├── flash_controller.cpp
│ ├── flash_manager.cpp
│ ├── idevice_manager.cpp
│ ├── include
│ ├── isp_controller.cpp
│ ├── isp_manager.cpp
│ ├── sensor_controller.cpp
│ ├── sensor_manager.cpp
│ └── v4l2_device_manager.cpp
├── driver_adapter
│ ├── BUILD.gn
│ ├── include
│ ├── main_test
│ └── src
└── pipeline_core
└── nodes
```
Distinguish the code related to the V4L2 chipset and place it in the **device/ ${product_company}/${device_name} /camera** directory.
```
├── BUILD.gn
├── camera_demo
│ └── project_camera_demo.h
├── include
│ └── device_manager
├── product.gni
└── src
├── device_manager
├── driver_adapter
└── pipeline_core
```
The **driver_adapter** directory contains the header files of the test cases related to the RK3568 driver adapter. The **Camera_demo** directory stores the chipset-related header files of the demo test cases in the camera HAL. **device_manager** stores the code for the camera sensor adapted RK3568 to read device capabilities. In the directory, **project_hardware.h** is critical and stores the list of devices supporting the chipset. The sample code is as follows:
```
namespace OHOS::Camera {
std::vector<HardwareConfiguration> hardware = {
{CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "rkisp_v5"},
{CAMERA_FIRST, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
{CAMERA_FIRST, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"},
{CAMERA_SECOND, DM_M_SENSOR, DM_C_SENSOR, (std::string) "Imx600"},
{CAMERA_SECOND, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
{CAMERA_SECOND, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"}
};
} // namespace OHOS::Camera
```
You can modify compilation options so that the compilation of V4L2 and other framework code can be distinguished based on different compilation chipsets. Add **device/${product_company}/${device_name}/camera/product.gni**.
```
camera_product_name_path = "//vendor/${product_company}/${product_name}"
camera_device_name_path = "//device/board/${product_company}/${device_name}"
is_support_v4l2 = true
if (is_support_v4l2) {
is_support_mpi = false
defines += [ "SUPPORT_V4L2" ]
chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
camera_device_manager_deps =
"$camera_device_name_path/camera/src/device_manager:camera_device_manager"
camera_pipeline_core_deps =
"$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
}
```
When **product.gni** is loaded by **//drivers/peripheral/camera/hal/camera.gni**, the V4L2 code needs to be compiled. In **//drivers/peripheral/camera/hal/camera.gni**, load the corresponding GNI file based on **product_name** and **device_name** input during compilation.
```
import("//build/ohos.gni")
if ("${product_name}" == "ohos-arm64") {
import(
"//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
} else if ("${product_name}" == "Hi3516DV300") {
import(
"//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
```
Different chipsets are compiled based on **chipset_build_deps**, **camera_device_manager_deps**, and **camera_pipeline_core_deps** in **drivers/peripheral/camera/hal/BUILD.gn**.
print("product_name : , ${product_name}")
group("camera_hal") {
if (is_standard_system) {
deps = [
"$camera_path/../interfaces/hdi_ipc/client:libcamera_client",
"buffer_manager:camera_buffer_manager",
"device_manager:camera_device_manager",
"hdi_impl:camera_hdi_impl",
"init:ohos_camera_demo",
"pipeline_core:camera_pipeline_core",
"utils:camera_utils",
]
deps += [ "${chipset_build_deps}" ]
}
The camera HAL layer shields the differences between the platform and chip and provides unified APIs for external systems (camera service or test program). The APIs are defined in **drivers/peripheral/camera/interfaces/include**.
├── icamera_device_callback.h
├── icamera_device.h
├── icamera_host_callback.h
├── icamera_host.h
├── ioffline_stream_operator.h
├── istream_operator_callback.h
├── istream_operator.h
During the test, you only need to test the provided external APIs to test the complete code of the camera HAL layer. For details about the APIs, see **README_zh.md** in **drivers/peripheral/camera/interfaces** and header file API definition. For details about the API call process, see the test demo **drivers/peripheral/camera/hal/init**.
### FAQs of Camera Adaptation
#### Modifying the SUBWINDOW_TYPE and Display Format
Change the RGBA888 display mode from **video** to **normal** (specified by **SUBWINDOW_TYPE**).
The OpenHarmony implements the camera of the Hi3516 platform earlier, which uses the PIXEL_FMT_YCRCB_420_SP format for display. However, the RK3568 needs to convert YUV420 preview streams into PIXEL_FMT_RGBA_8888 and send them to the screen for correct display. Modify the following content in the **foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp** file. This file is compiled in **libace.z.so**.
#ifdef PRODUCT_RK
previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_RGBA_8888));
previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
std::to_string(OHOS_CAMERA_FORMAT_RGBA_8888));
#else
previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_YCRCB_420_SP));
previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
std::to_string(OHOS_CAMERA_FORMAT_YCRCB_420_SP));
#endif
The following content in the **foundation/multimedia/camera_standard/services/camera_service/src/hstream_repeat.cpp** file is compiled in **libcamera_service.z.so**:
```
void HStreamRepeat::SetStreamInfo(std::shared_ptr<Camera::StreamInfo> streamInfo)
{
int32_t pixelFormat;
auto it = g_cameraToPixelFormat.find(format_);
if (it != g_cameraToPixelFormat.end()) {
pixelFormat = it->second;
} else {
#ifdef RK_CAMERA
pixelFormat = PIXEL_FMT_RGBA_8888;
#else
pixelFormat = PIXEL_FMT_YCRCB_420_SP;
#endif
```
In the preceding information, the Hi3516 platform uses the VO module driver to directly send display signals. Therefore, the subwindows mode configured in the ACE is **SUBWINDOW_TYPE_VIDEO**. Modify the **foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp** file. This file is compiled in **libace.z.so**.
#ifdef PRODUCT_RK
option->SetWindowType(SUBWINDOW_TYPE_NORMAL);
#else
option->SetWindowType(SUBWINDOW_TYPE_VIDEO);
#endif
#### Adding rk_codec_node
RGB conversion and H.264 and JPEG encoding and decoding are performed on this node. Each node in the pipeline model of the camera HAL is in camera data rotation. Currently, the camera HAL V4L2 adapter supports data rotation of only one stream. Therefore, the photographing and recording streams must be copied from a single preview stream. Currently, OpenHarmony does not have a dedicated server for codec and RGB conversion and JPEG compression. In this case, you can only create a dedicated node in the camera HAL, that is, **rk_codec_node**.
Add the **rk_codec_node** connection model to HCS.
Modify the **vendor/hihope/rk3568/hdf_config/uhdf/camera/pipeline_core/config.hcs** file.
normal_preview_snapshot :: pipeline_spec {
name = "normal_preview_snapshot";
v4l2_source :: node_spec {
name = "v4l2_source#0";
status = "new";
out_port_0 :: port_spec {
name = "out0";
peer_port_name = "in0";
peer_port_node_name = "fork#0";
direction = 1;
}
}
fork :: node_spec {
name = "fork#0";
status = "new";
in_port_0 :: port_spec {
name = "in0";
peer_port_name = "out0";
peer_port_node_name = "v4l2_source#0";
direction = 0;
}
out_port_0 :: port_spec {
name = "out0";
peer_port_name = "in0";
peer_port_node_name = "RKCodec#0";
direction = 1;
}
out_port_1 :: port_spec {
name = "out1";
peer_port_name = "in0";
peer_port_node_name = "RKCodec#1";
direction = 1;
}
}
RKCodec_1 :: node_spec {
name = "RKCodec#0";
status = "new";
in_port_0 :: port_spec {
name = "in0";
peer_port_name = "out0";
peer_port_node_name = "fork#0";
direction = 0;
}
out_port_0 :: port_spec {
name = "out0";
peer_port_name = "in0";
peer_port_node_name = "sink#0";
direction = 1;
}
}
RKCodec_2 :: node_spec {
name = "RKCodec#1";
The preview and photographing streams are used as an example. **v4l2_source_node** is the data source and flows to **fork_node**. **rork_node** directly sends the preview data to the RKCodec node and copies the photographing data stream to the RKCodec node for conversion. The converted data is sent to the sink node and then to the consumer of the buffer.
Add the compilation of **rk_codec_node.cpp** and related dependent libraries to **device/board/hihope/rk3568/camera/src/pipeline_core/BUILD.gn**. **librga** is the library for converting YUV images into RGB images, **libmpp** is the library for encoding and decoding YUV images into H.264 images, and **libjpeg** is the library for compressing YUV images into JPEG images.
ohos_shared_library("camera_pipeline_core") {
sources = [
"$camera_device_name_path/camera/src/pipeline_core/node/rk_codec_node.cpp",
"$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/uvc_node/uvc_node.cpp",
"$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/v4l2_source_node/v4l2_source_node.cpp",
deps = [
"$camera_path/buffer_manager:camera_buffer_manager",
"$camera_path/device_manager:camera_device_manager",
"//device/soc/rockchip/hardware/mpp:libmpp",
"//device/soc/rockchip/hardware/rga:librga",
"//foundation/multimedia/camera_standard/frameworks/native/metadata:metadata",
"//third_party/libjpeg:libjpeg_static",
Main APIs of the **openharmony/device/board/hihope/rk3568/camera/src/pipeline_core/node/rk_codec_node.cpp** file:
void RKCodecNode::DeliverBuffer(std::shared_ptr<IBuffer>& buffer)
{
if (buffer == nullptr) {
CAMERA_LOGE("RKCodecNode::DeliverBuffer frameSpec is null");
return;
}
int32_t id = buffer->GetStreamId();
CAMERA_LOGE("RKCodecNode::DeliverBuffer StreamId %{public}d", id);
if (buffer->GetEncodeType() == ENCODE_TYPE_JPEG) {
Yuv420ToJpeg(buffer);
} else if (buffer->GetEncodeType() == ENCODE_TYPE_H264) {
Yuv420ToH264(buffer);
} else {
Yuv420ToRGBA8888(buffer);
}
The data stream generated by **fork_node** is delivered to the **DeliverBuffer** API of the rk_codec_node. The API performs conversion based on the value of **EncodeType**. The converted buffers are delivered to the next-level node for processing until they are delivered to the buffer consumer.
#### Inconsistent H.264 Frame and Audio Timestamps
Issue: When creating a recorder, ACE obtains both audio and video data and combines them into an .mp4 file. However, during the actual synthesis, the timestamps in the audio and video information must be consistent. If they are inconsistent, the Recorder fails. After the recording button is clicked in the camera app, the recording cannot be stopped properly. After the recording is forcibly stopped, the MP4 file is empty.
Solution: Find the method of obtaining the audio timestamp of the audio module.
```
int32_t AudioCaptureAsImpl::GetSegmentInfo(uint64_t &start)
{
CHECK_AND_RETURN_RET(audioCapturer_ != nullptr, MSERR_INVALID_OPERATION);
AudioStandard::Timestamp timeStamp;
auto timestampBase = AudioStandard::Timestamp::Timestampbase::MONOTONIC;
CHECK_AND_RETURN_RET(audioCapturer_->GetAudioTime(timeStamp, timestampBase), MSERR_UNKNOWN);
CHECK_AND_RETURN_RET(timeStamp.time.tv_nsec >= 0 && timeStamp.time.tv_sec >= 0, MSERR_UNKNOWN);
if (((UINT64_MAX - timeStamp.time.tv_nsec) / SEC_TO_NANOSECOND) <= static_cast<uint64_t>(timeStamp.time.tv_sec)) {
MEDIA_LOGW("audio frame pts too long, this shouldn't happen");
}
start = timeStamp.time.tv_nsec + timeStamp.time.tv_sec * SEC_TO_NANOSECOND;
MEDIA_LOGI("timestamp from audioCapturer: %{public}" PRIu64 "", start);
return MSERR_OK;
}
```
In the **audio_capture_as_impl.cpp** file, and the camera module uses **CLOCK_REALTIME**, that is, the actual system time.
mppStatus_ = 1;
buf_size = ((MpiEncTestData *)halCtx_)->frame_size;
ret = hal_mpp_encode(halCtx_, dma_fd, (unsigned char *)buffer->GetVirAddress(), &buf_size);
SearchIFps((unsigned char *)buffer->GetVirAddress(), buf_size, buffer);
buffer->SetEsFrameSize(buf_size);
clock_gettime(CLOCK_MONOTONIC, &ts);
timestamp = ts.tv_nsec + ts.tv_sec * TIME_CONVERSION_NS_S;
buffer->SetEsTimestamp(timestamp);
CAMERA_LOGI("RKCodecNode::Yuv420ToH264 video capture on\n");
Solution: Change the time type in **rk_codec_node.cpp** of camera HAL to **CLOCK_MONOTONIC**.
#### Linux 4.19 Match Error After the Value of time_t Is Changed to 64-Bit
Background: When RK3568 encounters this problem, the upper-layer 32-bit system is running, and the bottom-layer kernel is 64-bit kernel of Linux 4.19. In a 32-bit system environment, the value of **time_t** is of the long type, that is, 32-bit. However, after the value of **time_t** is changed to the 64-bit, an error is reported by camera V4l2 in the IOCTL.
TYPEDEF _Int64 time_t;
TYPEDEF _Int64 suseconds_t;
The specific error and temporary solution are as follows:
1. When an error occurs, search for **camera_host** in Hilog. It is found that a **Not a tty** error is reported when the **VIDIOC_QUERYBUF** CMD is delivered in the **V4L2AllocBuffer** API. The sample code is as follows:
```
V4L2AllocBuffer error:ioctl VIDIOC_QUERYBUF failed: Not a tty
```
RetCode HosV4L2Buffers::V4L2AllocBuffer(int fd, const std::shared_ptr<FrameSpec>& frameSpec)
{
struct v4l2_buffer buf = {};
struct v4l2_plane planes[1] = {};
CAMERA_LOGD("V4L2AllocBuffer\n");
if (frameSpec == nullptr) {
CAMERA_LOGE("V4L2AllocBuffer frameSpec is NULL\n");
return RC_ERROR;
}
switch (memoryType_) {
case V4L2_MEMORY_MMAP:
// to do something
break;
case V4L2_MEMORY_USERPTR:
buf.type = bufferType_;
buf.memory = memoryType_;
buf.index = (uint32_t)frameSpec->buffer_->GetIndex();
if (bufferType_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
buf.m.planes = planes;
buf.length = 1;
}
CAMERA_LOGD("V4L2_MEMORY_USERPTR Print the cnt: %{public}d\n", buf.index);
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno));
return RC_ERROR;
2. Generally, the CMD called by the IOCTL system uses **sizeof** of the third parameter as the CMD value and transfers the value to the kernel to search for the corresponding switch case in the kernel. As shown below, **v4l2_buffer** is the main part of the **VIDIOC_QUERYBUF** macro value. If the size of **v4l2_buffer** changes, the value of **VIDIOC_QUERYBUF** also changes accordingly.
```
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
#define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer)
#define VIDIOC_G_FBUF _IOR('V', 10, struct v4l2_framebuffer)
```
3. When the **CONFIG_COMPAT** macro is enabled in the kernel, the 32-bit system is compatible with the 64-bit kernel. For the IOCTL delivered by the 32-bit system, the API in the following is used to convert the CMD value from 32-bit to 64-bit.
long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(file);
long ret = -ENOIOCTLCMD;
if (!file->f_op->unlocked_ioctl)
return ret;
if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
ret = do_video_ioctl(file, cmd, arg);
else if (vdev->fops->compat_ioctl32)
ret = vdev->fops->compat_ioctl32(file, cmd, arg);
4. In this case, a value of **VIDIOC_QUERYBUF** considered by the kernel is defined in the kernel.
#define VIDIOC_S_FMT32 _IOWR('V', 5, struct v4l2_format32)
#define VIDIOC_QUERYBUF32 _IOWR('V', 9, struct v4l2_buffer32)
#define VIDIOC_QUERYBUF32_TIME32 _IOWR('V', 9, struct v4l2_buffer32_time32)
5. As mentioned above, the value of **time_t** in the upper-layer MUSL has been changed from 32-bit to 64-bit, and **time_t** is used in struct timeval in the v4l2_buffer structure. In this case, the size of v4l2_buffer at the application layer is different from that at the kernel layer because **kernel_time_t** defined by the kernel in **time.h** is used during compilation in struct timeval of the kernel. As a result, the **sizeof** calculation of v4l2_buffer at the application layer is inconsistent with that at the driver layer, and CMD error cannot be found after the kernel mode is called.
```
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
```
6. The temporary solution is to change struct timeval in **videodev2.h** to a temporarily defined structure to ensure that the sizes of the upper and lower layers are the same. The sample code is as follows:
```
struct timeval1 {
long tv_sec;
long tv_usec;
}
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval1 timestamp;
struct v4l2_timecode timecode;
```
Solution:
There are only two ways to completely solve this problem. 1. Upgrade the system to 64-bit to ensure that the size of the **time_t** variable in user mode is the same as that in kernel mode. 2. Upgrade the kernel to a version later than 5.10.
This problem is solved in the **videodev2.h** file of kernel 5.10. As shown below, the 64-bit **time_t** value is considered during kernel compilation.
```
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
#ifdef __KERNEL__
struct __kernel_v4l2_timeval timestamp;
#else
struct timeval timestamp;
#endif
struct v4l2_timecode timecode;
}
struct __kernel_v4l2_timeval {
long long ._sec;
#if defined(__sparc__) && defined(__arch64__)
int tv_usec;
int __pad;
#else
long long tv_usec;
#endif
};
```
#### H.264 Key Frame Obtaining and Reporting
In addition to the encoded and decoded data, H.264 also needs to report the key frame information. So, how can we know whether a frame is a key frame? The information is required during MP4 encoding. You can determine a key frame as follows: Analyze the NALU header information. NALU type & 0x1f indicate the frame type. The NALU header starts with 0x00000001 or 0x000001. This figure shows the frame types when **nal_unit_type** is set to different values. Pay attention to the IDR frame information when **nal_unit_type** is set to **5**.
![1647875911244](figures/dayu200/dayu200-camera-03.png)
IDR frame analysis is coded in the **rk_cedec_node.cpp** file.
static constexpr uint32_t nalBit = 0x1F;
#define NAL_TYPE(value) ((value) & nalBit)
void RKCodecNode::SearchIFps(unsigned char* buf, size_t bufSize, std::shared_ptr<IBuffer>& buffer)
{
size_t nalType = 0;
size_t idx = 0;
size_t size = bufSize;
constexpr uint32_t nalTypeValue = 0x05;
if (buffer == nullptr || buf == nullptr) {
CAMERA_LOGI("RKCodecNode::SearchIFps parameter == nullptr");
return;
}
for (int i = 0; i < bufSize; i++) {
int ret = findStartCode(buf + idx, size);
if (ret == -1) {
idx += 1;
size -= 1;
} else {
nalType = NAL_TYPE(buf[idx + ret]);
CAMERA_LOGI("ForkNode::ForkBuffers nalu == 0x%{public}x buf == 0x%{public}x \n", nalType, buf[idx + ret]);
Each buffer converted by H.264 is transferred to the **SearchIFps** API to search for IDR frames. The **findStartCode()** API scans the content in the buffer byte by byte to find the NALU header.
```
int RKCodecNode::findStartCode(unsigned char *data, size_t dataSz)
{
constexpr uint32_t dataSize = 4;
constexpr uint32_t dataBit2 = 2;
constexpr uint32_t dataBit3 = 3;
if (data == nullptr) {
CAMERA_LOGI("RKCodecNode::findStartCode parameter == nullptr");
return -1;
}
if ((dataSz > dataSize) && (data[0] == 0) && (data[1] == 0) && \
(data[dataBit2] == 0) && (data[dataBit3] == 1)) {
return 4; // 4:start node
}
return -1;
}
```
After the NALU header is found, **nal_unit_type** is found for &0x1F. If the value of **nal_unit_type** is **5**, key frame information is marked and reported through the **buffer->SetEsKeyFrame(1)** API.
## TP
### TP Driver Model
This model mainly defines and implements the following types of HDIs of the input module, allowing upper-layer input services to perform operations for the input devices:
- **Input Manager**: manages input devices, including enabling and disabling input devices and obtaining the device list.
- **Input Reporter**: reports input events, including registering and unregistering data reporting callbacks.
- **Input Controller**: controls input devices, including obtaining the device information and device type, and setting power supply status.
**Figure 1** HDI architecture of the input module
![dayu200-tp-01.png](figures/dayu200/dayu200-tp-01.png)
The source code directory structure is as follows:
```
/drivers/peripheral/input
├── hal # HAL code
│ └── include # HAL header files
│ └── src # HAL code implementation
├── interfaces # Driver capability APIs provided for upper-layer services
│ └── include # APIs exposed externally
├── test # Test code
│ └── unittest # Unit test code
```
For details, see [README](https://gitee.com/openharmony/drivers_peripheral/blob/master/input/README_zh.md) of the input subsystem.
### TP HDF Driver Adaptation
#### Files and Directories Involved in the TP Driver
By default, the DAYU 200 platform supports the GT5688 TP IC.
Files and directories involved in porting the touch driver on the development board:
1. Makefile: drivers\adapter\khdf\linux\model\input\Makefile
2. vendor\hihope\rk3568\hdf_config\khdf\device_info\device_info.hcs
3. vendor\hihope\rk3568\hdf_config\khdf\input\input_config.hcs
4. drivers\framework\model\input\driver\touchscreen
The adaptation of the TP driver involves the TP driver and HCS configuration.
The adaptation of the TP driver depends on the HDF input model. The HDF input model supports device registration, management, data forwarding layer, and HCS parsing in TP, key, and HID scenarios. The input model of the HDF can be abstracted into three layers: driver management layer, common driver layer, and component driver layer.
The following figure shows the framework of the HDF input module from the perspective of functions.
![dayu200-tp-02.png](figures/dayu200/dayu200-tp-02.png)
Due to the high abstraction and integration of the HDF input model, the adapted driver of the TP driver mainly involves the adaptation of the component driver layer.
Before adaptation, you need to determine the resources required by the TP.
For hardware resources, the TP module requires the following resources on the host:
1. Interrupt pin
2. Reset pin
3. Used I2C group and address of the slave device
4. TP initialization firmware (usually provided by IC vendors)
5. Touchscreen resolution
For software resources, TP adaptation on the HDF depends on the following HDF basic modules:
1. HDF GPIO subsystem: sets GPIO pins and interrupt resources.
2. HDF I2C subsystem: used for I2C communication.
3. Input model
The component driver is used based on the following structures:
```
static struct TouchChipOps g_gt911ChipOps = {
.Init = ChipInit,
.Detect = ChipDetect,
.Resume = ChipResume,
.Suspend = ChipSuspend,
.DataHandle = ChipDataHandle,
.UpdateFirmware = UpdateFirmware,
.SetAbility = SetAbility,
};
```
**ChipInit** initializes the component driver.
**ChipDetect** checks the component validity after initialization.
**SetAbility** sets key attributes.
**ChipDataHandle** parses key values.
**UpdateFirmware** upgrades firmware.
**ChipSuspend** suspends components.
**ChipResume** resumes components.
Implement the preceding API callbacks based on the component features and register the structure with the input model.
#### HCS Configuration
Add a new component node to **device_info.hcs**.
```
device_touch_chip :: device {
device0 :: deviceNode {
policy = 0;
priority = 180;
preload = 0; // The driver is loaded by default.
permission = 0660;
moduleName = "HDF_TOUCH_GT911";// The value must be the same as that in the component driver.
serviceName = "hdf_touch_gt911_service";
deviceMatchAttr = "zsj_gt911_5p5";
}
}
```
Add component features to **input_config.hcs**.
```
chipConfig {
template touchChip {
match_attr = "";
chipName = "gt911";
vendorName = "zsj";
chipInfo = "AAAA11222"; // 4-ProjectName, 2-TP IC, 3-TP Module
/* 0:i2c 1:spi*/
busType = 0;
deviceAddr = 0x5D;
/* 0:None 1:Rising 2:Failing 4:High-level 8:Low-level */
irqFlag = 2;
maxSpeed = 400;
chipVersion = 0; //parse Coord TypeA
powerSequence {
/* [type, status, dir , delay]
<type> 0:none 1:vcc-1.8v 2:vci-3.3v 3:reset 4:int
<status> 0:off or low 1:on or high 2:no ops
<dir> 0:input 1:output 2:no ops
<delay> meanings delay xms, 20: delay 20ms
*/
powerOnSeq = [4, 0, 1, 5,
3, 0, 1, 10,
3, 1, 1, 60,
4, 2, 0, 50];
suspendSeq = [3, 0, 2, 10];
resumeSeq = [3, 1, 2, 10];
powerOffSeq = [3, 0, 2, 10,
1, 0, 2, 20];
}
}
```
## Display Adaptation
The following tasks need to be performed for display adaptation: graphics service HDI API adaptation, GPU adaptation, and LCD driver adaptation.
### Display HDI
[Display HDI](https://gitee.com/openharmony/drivers_peripheral/blob/master/display/README_zh.md) provides display driver capabilities for graphics services, including display layer management, display memory management, and hardware acceleration. Display HDI adaptation involves **gralloc** and **display_device**.
#### Gralloc Adaptation
The gralloc module provides the display memory management function. OpenHarmony provides the reference implementation of Hi3516D V300. Vendors can refer to and adapt the implementation based on the actual situation. The implementation is developed based on the DRM. [Source code link](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard)
The DRM device node is defined in the **//drivers_peripheral/display/hal/default_standard/srd/display_gralloc/display_gralloc_gbm.c** file. You can modify the file as required.
```
const char *g_drmFileNode = "/dev/dri/card0";
```
In this implementation, a HiSilicon private IOCTL command code **DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR** is defined in the **//drivers_peripheral/display/hal/default_standard/src/display_gralloc/hisilicon_drm.h** file and called in the **//drivers_peripheral/display/hal/default_standard/src/display_gralloc/display_gralloc_gbm.c** file. This function is a HiSilicon private function and can be modified as required during adaptation.
```
...
InitBufferHandle(bo, fd, info, priBuffer);
priBuffer->hdl.phyAddr = GetPhysicalAddr(grallocManager->drmFd, fd);
*buffer = &priBuffer->hdl;
...
```
#### Display Device Adaptation
The display device module provides functions such as display device management, layer management, and hardware acceleration.
OpenHarmony provides the [DRM-based Hi3516D V300 reference implementation](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard/src/display_device), which supports hardware composition by default.
If the development board does not support hardware composition, skip the initialization of the GFX in the **drm_display.cpp** file.
```
drivers_peripheral/blob/master/display/hal/default_standard/src/display_device/drm/drm_display.cpp
int32_t DrmDisplay::Init()
{
...
...
ret = HdiDisplay::Init();
DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("init failed"));
auto preComp = std::make_unique<HdiGfxComposition>();
DISPLAY_CHK_RETURN((preComp == nullptr), DISPLAY_FAILURE,
DISPLAY_LOGE("can not new HdiGfxComposition errno %{public}d", errno));
ret = preComp->Init(); // GFX initialization, which needs to be skipped.
DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not init HdiGfxComposition")); // Or do not check the return value.
...
}
```
In addition, modify the **set_layers** method in the **//drivers_peripheral/display/hal/default_standard/src/display_device/hdi_gfx_composition.cpp** file to use the CPU composition display.
```
int32_t HdiGfxComposition::SetLayers(std::vector<HdiLayer *> &layers, HdiLayer &clientLayer)
{
DISPLAY_LOGD("layers size %{public}zd", layers.size());
mClientLayer = &clientLayer;
mCompLayers.clear();
for (auto &layer : layers) {
if (CanHandle(*layer)) {
#if 0 // CPU composition.
layer->SetDeviceSelect(COMPOSITION_CLIENT);
#else
if ((layer->GetCompositionType() != COMPOSITION_VIDEO) &&
(layer->GetCompositionType() != COMPOSITION_CURSOR)) {
layer->SetDeviceSelect(COMPOSITION_DEVICE);
} else {
layer->SetDeviceSelect(layer->GetCompositionType());
}
#endif
mCompLayers.push_back(layer);
}
}
DISPLAY_LOGD("composer layers size %{public}zd", mCompLayers.size());
return DISPLAY_SUCCESS;
}
```
#### Test and Verification
hello_composer test module: It is a test program provided by the Rosen graphics framework and checks whether the functions such as the display process and HDI API are normal. By default, the module is compiled with the system.
Code path:
```
foundation/graphic/graphic/rosen/samples/composer/
├── BUILD.gn
├── hello_composer.cpp
├── hello_composer.h
├── layer_context.cpp
├── layer_context.h
└── main.cpp
```
The verification is as follows:
1. Disable the render service.
```
service_control stop render_service
```
2. Disable the foundation process.
```
service_control stop foundation
```
3. Run **hello_composer** to test related APIs.
```
./hello_composer
```
Test using devicetest: test module provided by the HDI display module. It is used to test the HDI API, display buffer, and driver capabilities. During the test, the render service and foundation processes also need to be stopped.
Code path: **/drivers/peripheral/display/test/unittest/standard**
```
├── BUILD.gn
├── common
│ ├── display_test.h
│ ├── display_test_utils.cpp
│ └── display_test_utils.h
├── display_device
│ ├── hdi_composition_check.cpp
│ ├── hdi_composition_check.h
│ ├── hdi_device_test.cpp
│ ├── hdi_device_test.h
│ ├── hdi_test_device_common.h
│ ├── hdi_test_device.cpp
│ ├── hdi_test_device.h
│ ├── hdi_test_display.cpp
│ ├── hdi_test_display.h
│ ├── hdi_test_layer.cpp
│ ├── hdi_test_layer.h
│ ├── hdi_test_render_utils.cpp
│ └── hdi_test_render_utils.h
└── display_gralloc
├── display_gralloc_test.cpp
└── display_gralloc_test.h
```
### GPU
Compiler Clang
```
prebuilts/clang/ohos/linux-x86_64/llvm
```
musl Library
```
./build.sh --product-name rk3568 --build-target musl_all
```
After the compilation is complete, the following header files and libraries are generated in the **out/{product_name}/obj/third_party/musl/usr/lib** directory:
```
32-bit: arm-linux-ohos
64-bit: aarch64-linux-ohos
```
Source code directory:
```
third_party/musl
```
GPU compilation parameter reference:
```
TARGET_CFLAGS=" -march=armv7-a -mfloat-abi=softfp -mtune=generic-armv7-a -mfpu=neon -mthumb --target=arm-linux-ohosmusl -fPIC -ftls-model=global-dynamic -mtls-direct-seg-refs -DUSE_MUSL"
```
## LCD
By default, the DAYU 200 platform supports an LCD screen with the MIPI API.
LCD adaptation mainly depends on the HDF display model. The display driver model is developed based on the HDF driver framework, platform API, and OSAL API. It can shield the differences between different kernel forms (LiteOS and Linux), apply to different chip platforms, and provide a unified driver platform for display components.
The following figure shows the hierarchy of the HDF display driver model.
![640](figures/dayu200/dayu200-lcd-01.png)
The current driver model is mainly deployed in kernel mode and connects to the display common HAL layer to assist HDI implementation. The display driver exposes the display driver capability to the graphics service through the display-HDI layer. The display driver connects to the display panel component downwards to drive the screen to work properly and streamlines the entire display process.
Therefore, the LCD adaptation mainly lies in the adaptation of the LCD panel component driver.
The adaptation of the component driver consists of two parts: panel driver and HCS configuration.
The following files are involved:
```
drivers/framework/model/display/driver/panel
vendor/hihope/rk3568/hdf_config/khdf/device_info
vendor/hihope/rk3568/hdf_config/khdf/input
```
### Panel Driver
The component driver is used based on the following APIs:
```
struct PanelData {
struct HdfDeviceObject *object;
int32_t (*init)(struct PanelData *panel);
int32_t (*on)(struct PanelData *panel);
int32_t (*off)(struct PanelData *panel);
int32_t (*prepare)(struct PanelData *panel);
int32_t (*unprepare)(struct PanelData *panel);
struct PanelInfo *info;
enum PowerStatus powerStatus;
struct PanelEsd *esd;
struct BacklightDev *blDev;
void *priv;
};
```
Instantiate the data structure in the initialization API of the driver.
panelSimpleDev->panel.init = PanelSimpleInit;
panelSimpleDev->panel.on = PanelSimpleOn;
panelSimpleDev->panel.off = PanelSimpleOff;
panelSimpleDev->panel.prepare = PanelSimplePrepare;
panelSimpleDev->panel.unprepare = PanelSimpleUnprepare;
PanelSimpleInit initializes panel software.
PanelSimpleOn turns on the screen.
PanelSimpleOff turns off the screen.
PanelSimplePrepare initializes the hardware time sequence for turning on the screen.
PanelSimpleUnprepare initializes the hardware time sequence for turning off the screen.
After instantiation, use the **RegisterPanel** API to register the panel driver with the display model.
Note that the LCD on DAYU 200 uses the DRM display framework.
### HCS Configuration
```
device4 :: deviceNode {
policy = 0;
priority = 100;
preload = 0;
moduleName = "LCD_PANEL_SIMPLE";
}
```
## Backlight
The backlight driver model is developed based on the HDF framework.
![dayu200-backlight-01.png](figures/dayu200/dayu200-backlight-01.png)
The RK3568 backlight is implemented by controlling the duty cycle by using PWM4.
Code path of the native backlight driver:
```c
linux-5.10/drivers/video/backlight/pwm_bl.c
linux-5.10/drivers/video/backlight/backlight.c
linux-5.10/drivers/pwm/pwm-rockchip.c
```
To use the backlight driver on the HDF framework, disable the native driver.
```c
# CONFIG_BACKLIGHT_PWM is not set
```
### HDF Implementation
Code Path
```c
drivers/framework/model/display/driver/backlight/hdf_bl.c
```
HDF BL Entry Function
```c
static int32_t BacklightInit(struct HdfDeviceObject *object)
{
if (object == NULL) {
HDF_LOGE("%s: object is null!", __func__);
return HDF_FAILURE;
}
HDF_LOGI("%s success", __func__);
return HDF_SUCCESS;
}
struct HdfDriverEntry g_blDevEntry = {
.moduleVersion = 1,
.moduleName = "HDF_BL",
.Init = BacklightInit,
.Bind = BacklightBind,
};
HDF_INIT(g_blDevEntry);
```
Code path:
```c
drivers/framework/model/display/driver/backlight/pwm_bl.c
```
HDF PWM Entry Function
```c
struct HdfDriverEntry g_pwmBlDevEntry = {
.moduleVersion = 1,
.moduleName = "PWM_BL",
.Init = BlPwmEntryInit,
};
HDF_INIT(g_pwmBlDevEntry);
```
The backlight control APIs are as follows:
```c
static int32_t BlPwmUpdateBrightness(struct BacklightDev *blDev, uint32_t brightness)
{
int32_t ret;
uint32_t duty;
struct BlPwmDev *blPwmDev = NULL;
blPwmDev = ToBlDevPriv(blDev);
if (blPwmDev == NULL) {
HDF_LOGE("%s blPwmDev is null", __func__);
return HDF_FAILURE;
}
if (blPwmDev->props.maxBrightness == 0) {
HDF_LOGE("%s maxBrightness is 0", __func__);
return HDF_FAILURE;
}
if (brightness == 0) {
return PwmDisable(blPwmDev->pwmHandle);
}
duty = (brightness * blPwmDev->config.period) / blPwmDev->props.maxBrightness;
ret = PwmSetDuty(blPwmDev->pwmHandle, duty);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: PwmSetDuty failed, ret %d", __func__, ret);
return HDF_FAILURE;
}
return PwmEnable(blPwmDev->pwmHandle);
}
static struct BacklightOps g_blDevOps = {
.updateBrightness = BlPwmUpdateBrightness,
};
```
Actually, the API implemented by the HDF PWM is used to connect to the kernel PWM.
![dayu200-backlight-02.png](figures/dayu200/dayu200-backlight-02.png)
Register the backlight with the LCD HDF driver.
Code Path
```c
drivers/framework/model/display/driver/panel/ili9881c_boe.c
```
```c
ili9881cBoeDev->panel.blDev = GetBacklightDev("hdf_pwm");
if (ili9881cBoeDev->panel.blDev == NULL) {
HDF_LOGE("%s GetBacklightDev fail", __func__);
goto FAIL;
}
```
### HCS Configuration
HCS configuration of the driver
```c
device_pwm_bl :: device {
device0 :: deviceNode {
policy = 0;
priority = 95;
preload = 0;
moduleName = "PWM_BL";
deviceMatchAttr = "pwm_bl_dev";
}
}
device_backlight :: device {
device0 :: deviceNode {
policy = 2;
priority = 90;
preload = 0;
permission = 0660;
moduleName = "HDF_BL";
serviceName = "hdf_bl";
}
}
```
HCS configuration for PWM backlight
```c
root {
backlightConfig {
pwmBacklightConfig {
match_attr = "pwm_bl_dev";
pwmDevNum = 1;
pwmMaxPeriod = 25000;
backlightDevName = "hdf_pwm";
minBrightness = 0;
defBrightness = 127;
maxBrightness = 255;
}
}
}
```
### Testing
Run **cat /sys/kernel/debug/pwm** to check whether the HDF PWM has applied for PWM4.
If the application is successful, the following information is displayed:
**requested**: The application is successful.
**enabled**: PWM4 is enabled successfully.
```c
# cat /sys/kernel/debug/pwm
platform/fe6e0000.pwm, 1 PWM device
pwm-0 ((null) ): requested enabled period: 25000 ns duty: 9705 ns polarity: normal
```
## **Wi-Fi**
### HDF-based Wi-Fi Approach
Get familiar with the HDF Wi-Fi framework and main APIs to be implemented by referring to [OpenHarmony HDF Wi-Fi Driver Analysis](https://mp.weixin.qq.com/s/iiE97pqPtzWIZadcjrQtsw), including the implementation of the HDF driver initialization API, Wi-Fi control-side API set, AP mode API set, STA mode API set, network-side API set, and event reporting API.
Next, let's get familiar with the HCS file format and the code startup initialization process of the HDF Wi-Fi core driver framework. For details, see the Hi3881 code.
HDF Wi-Fi framework
​ ![image-20220320160720306](figures/dayu200/dayu200-wifi-01.png)
### Code Process Analysis of the AP6275s Driver
#### Analysis of the Driver Module Initialization Process
![dayu200-wifi-02.png](figures/dayu200/dayu200-wifi-02.png)
The AP6275s is a Wi-Fi module driver of the SDIO device. It uses the standard Linux SDIO device driver. The kernel module initialization entry **module_init()** calls the **dhd_wifi_platform_load_sdio()** function for initialization. Here, **wifi_platform_set_power()** is called to power on the GPIO, **dhd_wlan_set_carddetect()** is called to detect the SDIO card, and **sdio_register_driver(&bcmsdh_sdmmc_driver)** is called to register the SDIO device driver. The SDIO bus has detected that the Wi-Fi module matches the device driver based on the device ID and vendor ID. Therefore, the **bcmsdh_sdmmc_probe()** function of the driver is called back immediately to initialize the Wi-Fi module chip. Finally, create the net_device network API wlan0 and register it with the Linux kernel protocol stack.
l Create the wlan0 object of the net_device network API.
**dhd_allocate_if()** calls **alloc_etherdev()** to create a **net_device** object, that is, the wlan0 network API.
l Register wlan0 with the kernel protocol stack.
Call the **dhd_register_if()** function. Here, call **register_netdev(net)** to register the wlan0 network API with the protocol stack.
### HDF Wi-Fi Framework Adaptation Through Code Modification
To use the Wi-Fi function of the system, the AP mode, STA mode, and P2P mode need to be implemented. The **wpa_supplicant** application interacts with the Wi-Fi driver through the HDF Wi-Fi framework to implement the STA mode and P2P mode, and the **hostapd** application interacts with the Wi-Fi driver through the HDF Wi-Fi framework to implement the AP mode and P2P mode.
The AP6275s Wi-Fi 6 kernel driver depends on the platform capability, including the SDIO bus communication capability. The communication with the user mode depends on the HDF Wi-Fi framework capability. After ensuring that the preceding capabilities are normal, you can start the HDF adaptation and porting of the Wi-Fi driver. This part uses the open-source RK3568 code as the basic version for porting.
The files and directories involved in the adaptation and porting of the AP6275s Wi-Fi 6 driver are as follows:
1). Compile configuration files.
drivers/adapter/khdf/linux/model/network/wifi/Kconfig
drivers/adapter/khdf/linux/model/network/wifi/vendor/Makefile
2). Access the Wi-Fi driver source code directory.
The native driver code is stored in:
linux-5.10/drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd_wifi6/
The code files added and modified on the native driver are stored in the following directory:
device/hihope/rk3568/wifi/bcmdhd_wifi6/
Directory structure:
```
./device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf
├── hdf_bdh_mac80211.c
├── hdf_driver_bdh_register.c
├── hdfinit_bdh.c
├── hdf_mac80211_ap.c
├── hdf_mac80211_sta.c
├── hdf_mac80211_sta.h
├── hdf_mac80211_sta_event.c
├── hdf_mac80211_sta_event.h
├── hdf_mac80211_p2p.c
├── hdf_public_ap6275s.h
├── net_bdh_adpater.c
├── net_bdh_adpater.h
```
**hdf_bdh_mac80211.c** is used to fill in the functions required by **g_bdh6_baseOps**, **hdf_mac80211_ap.c** is used to fill in the functions required by **g_bdh6_staOps**, **hdf_mac80211_sta.c** is used to fill in the functions required by **g_bdh6_staOps**, and **hdf_mac80211_p2p.c** is used to fill in the functions required by **g_bdh6_p2pOps**. For details about the APIs required for basic Wi-Fi functions, see **openharmony/drivers/framework/include/wifi/wifi_mac80211_ops.h**.
#### Driver File Compilation
The HDF WLAN driver framework consists of seven parts: Module, NetDevice, NetBuf, BUS, HAL, Client, and Message. You can implement the following functions during HDF adaptation of the Wi-Fi driver:
1) Initialize the driver module that adapts the HDF WLAN framework.
The following figure shows the code process.
![dayu200-wifi-03.png](figures/dayu200/dayu200-wifi-03.png)
The code is stored in **device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf_driver_bdh_register.c**.
```
struct HdfDriverEntry g_hdfBdh6ChipEntry = {
.moduleVersion = 1,
.Bind = HdfWlanBDH6DriverBind,
.Init = HdfWlanBDH6ChipDriverInit,
.Release = HdfWlanBDH6ChipRelease,
.moduleName = "HDF_WLAN_CHIPS"
};
HDF_INIT(g_hdfBdh6ChipEntry);
```
During driver initialization, the SDIO main control board scans and detects the card, initializes the Wi-Fi chip, and creates and initializes the main API.
2) Implement the HDF WLAN Base control-side APIs.
The code is stored in **hdf_bdh_mac80211.c**.
```
static struct HdfMac80211BaseOps g_bdh6_baseOps = {
.SetMode = BDH6WalSetMode,
.AddKey = BDH6WalAddKey,
.DelKey = BDH6WalDelKey,
.SetDefaultKey = BDH6WalSetDefaultKey,
.GetDeviceMacAddr = BDH6WalGetDeviceMacAddr,
.SetMacAddr = BDH6WalSetMacAddr,
.SetTxPower = BDH6WalSetTxPower,
.GetValidFreqsWithBand = BDH6WalGetValidFreqsWithBand,
.GetHwCapability = BDH6WalGetHwCapability,
.SendAction = BDH6WalSendAction,
.GetIftype = BDH6WalGetIftype,
};
```
The preceding APIs are called in STA, AP, and P2P modes.
3) Implement APIs in HDF WLAN STA mode.
The following figure shows the call process in STA mode.
​ ![image-20220320161412663](figures/dayu200/dayu200-wifi-04.png)
The code is stored in **hdf_mac80211_sta.c**.
```
struct HdfMac80211STAOps g_bdh6_staOps = {
.Connect = HdfConnect,
.Disconnect = HdfDisconnect,
.StartScan = HdfStartScan,
.AbortScan = HdfAbortScan,
.SetScanningMacAddress = HdfSetScanningMacAddress,
};
```
4) Implement APIs in HDF WLAN AP mode.
The following figure shows the call process in AP mode.
![image-20220320161432068](figures/dayu200/dayu200-wifi-05.png)
The code is stored in **hdf_mac80211_ap.c**.
```
struct HdfMac80211APOps g_bdh6_apOps = {
.ConfigAp = WalConfigAp,
.StartAp = WalStartAp,
.StopAp = WalStopAp,
.ConfigBeacon = WalChangeBeacon,
.DelStation = WalDelStation,
.SetCountryCode = WalSetCountryCode,
.GetAssociatedStasCount = WalGetAssociatedStasCount,
.GetAssociatedStasInfo = WalGetAssociatedStasInfo
};
```
5) Implement APIs in HDF WLAN P2P mode.
The following figure shows the call process in P2P mode.
![image-20220320161442845](figures/dayu200/dayu200-wifi-06.png)
```
struct HdfMac80211P2POps g_bdh6_p2pOps = {
.RemainOnChannel = WalRemainOnChannel,
.CancelRemainOnChannel = WalCancelRemainOnChannel,
.ProbeReqReport = WalProbeReqReport,
.AddIf = WalAddIf,
.RemoveIf = WalRemoveIf,
.SetApWpsP2pIe = WalSetApWpsP2pIe,
.GetDriverFlag = WalGetDriverFlag,
};
```
6) Implement the event reporting APIs of the HDF WLAN framework.
The Wi-Fi driver needs to report events to the **wpa_supplicant** and **hostapd** applications, such as hotspot scanning results and association completion events of new STAs. For details about all APIs for reporting HDF WLAN events, see **drivers/framework/include/wifi/hdf_wifi_event.h**.
The HDF WLAN APIs for reporting events are as follows.
| API in Header File hdf_wifi_event.h | Description |
| ----------------------------------- | ------------------------ |
| HdfWifiEventNewSta() | Reports a new STA event. |
| HdfWifiEventDelSta () | Reports an STA deletion event. |
| HdfWifiEventInformBssFrame () | Reports a BSS scanning event. |
| HdfWifiEventScanDone () | Reports a scanning completion event. |
| HdfWifiEventConnectResult () | Reports a connection result event. |
| HdfWifiEventDisconnected () | Reports a disconnection event. |
| HdfWifiEventMgmtTxStatus () | Reports sending status events. |
| HdfWifiEventRxMgmt () | Reports a receiving status event. |
| HdfWifiEventCsaChannelSwitch () | Reports a CSA frequency band switching event. |
| HdfWifiEventTimeoutDisconnected ()| Reports a connection timeout event. |
| HdfWifiEventEapolRecv () | Reports an EAPOL receive event. |
| HdfWifiEventResetResult () | Reports a WLAN driver reset result event.|
| HdfWifiEventRemainOnChannel () | Reports a channel holding event. |
| HdfWifiEventCancelRemainOnChannel | Reports a channel unholding event. |
### Summary of All Key Issues
#### Method of Enabling the AP Mode During AP Module Debugging
Use the BusyBox and hostapd to configure the AP function as follows:
```
ifconfig wlan0 up
ifconfig wlan0 192.168.12.1 netmask 255.255.255.0
busybox udhcpd /data/udhcpd.conf
./hostapd -d /data/hostapd.conf
```
#### Method of Enabling the ATA Mode During STA Module Debugging
```
wpa_supplicant -iwlan0 -c /data/l2tool/wpa_supplicant.conf -d &
./busybox udhcpc -i wlan0 -s /data/l2tool/dhcpc.sh
```
#### Solution to the Failure to Report the Hotspot Scanning Event to wap_supplicant
When the **wpa_supplicant** application is started, the **-B** parameter cannot be added to start the application in the background. If the **-B** parameter is added to start the application in the background, the thread that calls **poll()** to wait for receiving events exits. As a result, the reported events cannot be received.
In this case, use **wpa_supplicant -iwlan0 -c /data/wpa_supplicant.conf &** to start the application in the background.
#### WPA2PSK Authentication Failure and Timeout
According to the process analysis, the hostapd process does not receive the **WIFI_WPA_EVENT_EAPOL_RECV = 13** event. Originally, the driver does not send the received EAPOL packet to the hostapd process through the HDF Wi-Fi framework. After the driver receives the packet, the EAPOL packet is sent to the HDF Wi-Fi framework before **netif_rx()** is called to trigger a software interrupt. The authentication is successful.
#### Location and Analysis of Connection Failure in P2P Mode
During the debugging of the P2P connection API, it is found that the P2P direct connection page of the mobile phone shows the invited state and the connection fails. By capturing and comparing the packets indicating that the connection between the mobile phone and the Wi-Fi module is successful and the packets indicating that the connection fails after HDF adaptation, you can find that the mobile phone responds with an extra ACTION packet, indicating that the parameter is invalid. Then, the P2P connection is terminated.
![image-20220320161303057](figures/dayu200/dayu200-wifi-07.png)
Check the content of the ACTION packet sent by the Wi-Fi module to the mobile phone. It is found that the MAC address in P2P Device Info is incorrect, as shown below.
Correct frame content
![image-20220320161314006](figures/dayu200/dayu200-wifi-08.png)
Incorrect frame content
![image-20220320161318995](figures/dayu200/dayu200-wifi-09.png)
Analyze the padding code of the MAC address. The MAC address is filled by **wpa_supplicant** based on the MAC address of p2p0. Therefore, the MAC address of the wdev object (p2p-dev-wlan0) is updated to the p2p0 API, and the two values must be the same. For details, see the call process of **wl_get_vif_macaddr(cfg, 7, p2p_hnetdev->macAddr)**.
### Connection Success Log
#### Connection Success Log in STA Mode
```
WPA: Key negotiation completed with 50:eb:f6:02:8e6:d4 [PTK=CCMP GTK=CCMP]
06 wlan0: State: GROUP_HANDSHAKEc -> COMPLETED
wlan0: CTRL-E4VENT-CONNECTED - Connection to 50:eb:f6:02:8e:d4 completed 3[id=0 id_str=]
WifiWpaReceived eEapol done
```
#### Connection Success Log in AP Mode
```
wlan0: STA 96:27:b3:95:b7:6e IEEE 802.1X: au:thorizing port
wlan0: STA 96:27:b3:95:b7:6e WPA: pairwise key handshake completed (RSN)
WifiWpaReceiveEapol done
```
#### Connection Success Log in P2P Mode
```
P2P: cli_channels:
EAPOL: External notificationtion - portValid=1
EAPOL: External notification:tion - EAP success=1
EAPOL: SUPP_PAE entering state AUTHENTIwCATING
EAPOL: SUPP_BE enterilng state SUCCESS
EAP: EAP ent_ering state DISABLED
EAPOL: SUPP_PAE entering state AUTHENTICATED
EAPOL:n Supplicant port status: Authoorized
EAPOL: SUPP_BE entertaining IDLE
WifiWpaReceiveEapol donepleted - result=SUCCESS
\# ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope: Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:12 errors:0 dropped:0 overruns:0 frame:0
TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:565 TX bytes:565
wlan0 Link encap:Ethernet HWaddr 10:2c:6b:11:61:e0 Driver bcmsdh_sdmmc
inet6 addr: fe80::122c:6bff:fe11:61e0/64 Scope: Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 TX bytes:0
p2p0 Link encap:Ethernet HWaddr 12:2c:6b:11:61:e0
inet6 addr: fe80::102c:6bff:fe11:61e0/64 Scope: Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 TX bytes:0
p2p-p2p0-0 Link encap:Ethernet HWaddr 12:2c:6b:11:21:e0 Driver bcmsdh_sdmmc
inet6 addr: fe80::102c:6bff:fe11:21e0/64 Scope: Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:9 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 TX bytes:0
```
## Bluetooth
### HCI API
The overall Bluetooth hardware architecture consists of two parts: host (computer or MCU) and host controller (actual Bluetooth chipset). The communication between the host and controller complies with the HCI, as shown in the following figure.
![dayu200-bt-01.png](figures/dayu200/dayu200-bt-01.png)
HCI defines how to exchange commands, events, and asynchronous and synchronous packets. Asynchronous connectionless links (ACLs) are used for data transmission, while synchronous connection oriented links (SCOs) are used for voice with headsets and hands-free profiles.
### Hardware Connection
According to the description of the RK3568 chip, the RK3568 chip does not integrate the Wi-Fi/Bluetooth function. The RK3568 chip needs to connect to an external Bluetooth chip to support the Bluetooth function, which complies with the preceding logical architecture. How is the physical connection between the host and the controller? You can view the development board specifications more clearly.
![](figures/dayu200/dayu200-bt-02.png)
Pins 28-36 are UART (serial port). In addition, several pins are used for power supply and hibernation control.
### Bluetooth VENDORLIB Adaptation
#### What Is vendorlib?
The vendorlib is deployed on the host, and may be considered as a Bluetooth chip driver layer on the host, to shield technical details of different Bluetooth chips. From the perspective of code, the main functions are as follows:
1. Provide a channel (file descriptor of the serial port) between Bluetooth chips for the protocol stack.
2. Provide specific control methods for specific chips.
#### Interpretation of vendorlib at the Code Level
**bt_vendor_lib.h**:
```
foundation/communication/bluetooth/services/bluetooth_standard/hardware/include
```
This file defines the interaction APIs between the protocol stack and **vendor_lib**, which are divided into two groups:
1. Implemented by vendorlib and called by the protocol stack
```c
typedef struct {
/**
* Set to sizeof(bt_vendor_interface_t)
*/
size_t size;
/**
* Caller will open the interface and pass in the callback routines
* to the implementation of this interface.
*/
int (*init)(const bt_vendor_callbacks_t* p_cb, unsigned char* local_bdaddr);
/**
* Vendor specific operations
*/
int (*op)(bt_opcode_t opcode, void* param);
/**
* Closes the interface
*/
void (*close)(void);
} bt_vendor_interface_t;
```
The basic process of starting the protocol stack is as follows:
1.1. The protocol stack dynamically opens **libbt_vendor.z.so** and calls the **init** function to initialize **vendorlib**.
1.2. The protocol stack calls the OP function to call the OP codes **BT_OP_POWER_ON**, **BT_OP_HCI_CHANNEL_OPEN**, and **BT_OP_INIT**. In principle, if **BT_OP_INIT** is successful, the chip initialization is complete.
2. Implemented by the protocol stack and called by **vendorlib** (callback function)
```c
typedef struct {
/**
* set to sizeof(bt_vendor_callbacks_t)
*/
size_t size;
/* notifies caller result of init request */
init_callback init_cb;
/* buffer allocation request */
malloc_callback alloc;
/* buffer free request */
free_callback dealloc;
/* hci command packet transmit request */
cmd_xmit_callback xmit_cb;
} bt_vendor_callbacks_t;
```
**init_cb** is called after **BT_OP_INIT** is complete.
**alloc/dealloc** is used to apply for or release message controls when HCI messages are sent.
**xmit_cb** sends HCI commands.
Important functions implemented by **vendor_lib**:
1. **init** function
```c
static int init(const bt_vendor_callbacks_t *p_cb, unsigned char *local_bdaddr)
{
/* * ... */
userial_vendor_init();
upio_init();
vnd_load_conf(VENDOR_LIB_CONF_FILE);
/* store reference to user callbacks */
bt_vendor_cbacks = (bt_vendor_callbacks_t *)p_cb;
/* This is handed over from the stack */
return memcpy_s(vnd_local_bd_addr, BD_ADDR_LEN, local_bdaddr, BD_ADDR_LEN);
}
```
First function called by **vendorlib**. **vendorlib** only needs to save the callback and MAC addresses of the protocol stack.
2. **BT_OP_POWER_ON** processing
This operation needs to pull up the level of the power pin. In this function, the rfill device is used for processing, and the driver is not directly called to pull up the level.
```c
int upio_set_bluetooth_power(int on)
{
int sz;
int fd = -1;
int ret = -1;
char buffer = '0';
switch (on) {
case UPIO_BT_POWER_OFF:
buffer = '0';
break;
case UPIO_BT_POWER_ON:
buffer = '1';
break;
default:
return 0;
}
/* check if we have rfkill interface */
if (is_rfkill_disabled()) {
return 0;
}
if (rfkill_id == -1) {
if (init_rfkill()) {
return ret;
}
}
fd = open(rfkill_state_path, O_WRONLY);
if (fd < 0) {
return ret;
}
sz = write(fd, &buffer, 1);
/* ... */
return ret;
}
```
3. **BT_OP_HCI_CHANNEL_OPEN** processing
```c
case BT_OP_HCI_CHANNEL_OPEN: { // BT_VND_OP_USERIAL_OPEN
int(*fd_array)[] = (int(*)[])param;
int fd, idx;
fd = userial_vendor_open((tUSERIAL_CFG *)&userial_init_cfg);
if (fd != -1) {
for (idx = 0; idx < HCI_MAX_CHANNEL; idx++)
(*fd_array)[idx] = fd;
retval = 1;
}
/* retval contains numbers of open fd of HCI channels */
break;
```
The **userial_vendor_open** function opens the serial port device (UART) to obtain the file descriptor (FD). The FD is returned through the **param** parameter of OP.
The name of the serial port device in the system must have been predefined in the development board. In this version, the device on the development board is **/dev/ttyS8**.
4. **BT_OP_INIT** processing
The operation code requires initialization of the Bluetooth chip, and specific processing to be performed is closely related to the Bluetooth chip. The AP6257S chip used in this debugging is used as an example. During initialization, the Bluetooth firmware is delivered.
After the initialization is complete, the **init_cb** callback function (see **bt_vendor_callbacks_t**) must be called to notify the protocol stack of the initialization result. Otherwise, the protocol stack thread is blocked and Bluetooth functions cannot be used properly. The protocol stack performs the following operations:
After calling **BT_OP_INIT**, the protocol stack waits for the semaphore. The semaphore is set by the **init_cb** function.
```c
static int HciInitHal()
{
int result = BT_NO_ERROR;
g_waitHdiInit = SemaphoreCreate(0);
int ret = g_hdiLib->hdiInit(&g_hdiCallbacks);
if (ret == SUCCESS) {
SemaphoreWait(g_waitHdiInit);
}
}
```
### vendorlib Porting Problem
1. Name of the .so file of vendorlib
The .so file name of vendorlib must be **libbt_vendor.z.so** because it is used when the protocol stack opens the dynamic link library (DLL).
2. Firmware problem
Pay attention to the chip firmware during development. The firmware of some Bluetooth chips may not need to be upgraded, but it's a must for some chips. During the adaptation of the AP6257S, the firmware is not delivered at the beginning. As a result, the received Bluetooth signals are poor. Pay attention to the following points when delivering firmware:
2.1. For the AP6257S chip, the Bluetooth chip does not have a flash memory. Therefore, the firmware must be delivered again after the chip is powered on and off.
2.2. The firmware is processed based on the chip requirements. It is recommended that the reference code of the vendor be found. Take the Broadcom series chips as an example. The firmware delivery process is complex and is driven by a state machine. There are nine states in total:
```c
/ Hardware Configuration State */
enum {
HW_CFG_START = 1,
HW_CFG_SET_UART_CLOCK,
HW_CFG_SET_UART_BAUD_1,
HW_CFG_READ_LOCAL_NAME,
HW_CFG_DL_MINIDRIVER,
HW_CFG_DL_FW_PATCH,
HW_CFG_SET_UART_BAUD_2,
HW_CFG_SET_BD_ADDR,
HW_CFG_READ_BD_ADDR
};
```
Initialize the state machine after receiving the **BT_OP_INIT** message, send the **HCI_REST** command, and switch the state to **HW_CFG_START**.
```c
void hw_config_start(void)
{
HC_BT_HDR *p_buf = NULL;
uint8_t *p;
hw_cfg_cb.state = 0;
hw_cfg_cb.fw_fd = -1;
hw_cfg_cb.f_set_baud_2 = FALSE;
if (bt_vendor_cbacks) {
p_buf = (HC_BT_HDR *)bt_vendor_cbacks->alloc(BT_HC_HDR_SIZE +
HCI_CMD_PREAMBLE_SIZE);
}
if (p_buf) {
p_buf->event = MSG_STACK_TO_HC_HCI_CMD;
p_buf->offset = 0;
p_buf->layer_specific = 0;
p_buf->len = HCI_CMD_PREAMBLE_SIZE;
p = (uint8_t *)(p_buf + 1);
UINT16_TO_STREAM(p, HCI_RESET);
*p = 0;
hw_cfg_cb.state = HW_CFG_START;
bt_vendor_cbacks->xmit_cb(HCI_RESET, p_buf);
} else {
if (bt_vendor_cbacks) {
HILOGE("vendor lib fw conf aborted [no buffer]");
bt_vendor_cbacks->init_cb(BTC_OP_RESULT_FAIL);
}
}
}
```
After receiving the **HCI_RESET** completion event returned by the chip, the system switches to the next state machine and sends the next COMMAND until the state machine completes firmware delivery.
For details, see the **hw_config_cback** function.
3. Pay attention to inter-system API differences.
The APIs of various systems may be slightly different, which requires special attention. Compared with the APIs of Android and OHOS, the definitions of the functions for **vendorlib** to call **xmit_cb** to send HCI commands are slightly different.
Android:
```c
/* define callback of the cmd_xmit_cb
*
The callback function which HCI lib will call with the return of command
complete packet. Vendor lib is responsible for releasing the buffer passed
in at the p_mem parameter by calling dealloc callout function.
*/
typedef void (*tINT_CMD_CBACK)(void* p_mem);
typedef uint8_t (*cmd_xmit_cb)(uint16_t opcode, void* p_buf, tINT_CMD_CBACK p_cback);
```
OHOS:
```c
/**
hci command packet transmit callback
Vendor lib calls cmd_xmit_cb function in order to send a HCI Command
packet to BT Controller.
*
The opcode parameter gives the HCI OpCode (combination of OGF and OCF) of
HCI Command packet. For example, opcode = 0x0c03 for the HCI_RESET command
packet. */
typedef uint8_t (*cmd_xmit_callback)(uint16_t opcode, void* p_buf);
```
That is, after a command is sent in vendorlib, Android directly calls callback to notify the chip of the returned message, and OHOS notifies the chip of the returned message through the **BT_OP_EVENT_CALLBACK** operation code (see the definition of **bt_opcode_t**). The vendorlib needs to parse the message code in the packet to determine which message is processed by the chip, and then calls the corresponding processing function.
```c
void hw_process_event(HC_BT_HDR *p_buf)
{
uint16_t opcode;
uint8_t *p = (uint8_t *)(p_buf + 1) + HCI_EVT_CMD_CMPL_OPCODE;
STREAM_TO_UINT16(opcode, p);
switch (opcode) {
case HCI_VSC_WRITE_BD_ADDR:
#if (USE_CONTROLLER_BDADDR == TRUE)
case HCI_READ_LOCAL_BDADDR:
#endif
case HCI_READ_LOCAL_NAME:
case HCI_VSC_DOWNLOAD_MINIDRV:
case HCI_VSC_WRITE_FIRMWARE:
case HCI_VSC_LAUNCH_RAM:
case HCI_RESET:
case HCI_VSC_WRITE_UART_CLOCK_SETTING:
case HCI_VSC_UPDATE_BAUDRATE:
hw_config_cback(p_buf);
break;
```
In addition, the OHOS returns the number of bytes in the sent message. If the value is less than or equal to **0**, the message fails to be sent, which is different from the return value of the Android API.
4. snoop log
Both Android and OHOS record HCI interaction messages. The OHOS system generates the **/data/log/bluetooth/snoop.log** file. You can use Wireshark or other packet analysis tools to view the interaction process between the host and controller, facilitating fault analysis.
## Sensor
Sensor driver model developed based on the HDF driver framework
![dayu200-sensor-01.png](figures/dayu200/dayu200-sensor-01.png)
The RK3568 supports the accel sensor. TheOpenHarmony mainline version has the overall driver framework. You only need to implement the specific component driver.
### Implementation of the mcx5566xa HDF Driver
The RK3568 platform supports the acceleration sensor MXC6655XA. For details about the configuration, see the datasheet of the acceleration sensor. Before porting the HDF, ensure that the compilation of the sensor in the kernel is disabled.
The configuration file is stored in **kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig**.
```c
# CONFIG_GS_MXC6655XA is not set
```
Code path:
```c
drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.c
drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.h
```
Compile the macros.
```c
CONFIG_DRIVERS_HDF_SENSOR_ACCEL_MXC6655XA=y
```
Implementation of the MXC6655XA accelerometer driver entry function
```c
struct HdfDriverEntry g_accelMxc6655xaDevEntry = {
.moduleVersion = 1,
.moduleName = "HDF_SENSOR_ACCEL_MXC6655XA",
.Bind = Mxc6655xaBindDriver,
.Init = Mxc6655xaInitDriver,
.Release = Mxc6655xaReleaseDriver,
};
HDF_INIT(g_accelMxc6655xaDevEntry);
```
Next, let's look at the differentiated adaptation function.
```c
struct AccelOpsCall {
int32_t (*Init)(struct SensorCfgData *data);
int32_t (*ReadData)(struct SensorCfgData *data);
};
```
API for obtaining data on the X, Y, and Z axises
```c
int32_t ReadMxc6655xaData(struct SensorCfgData *cfg, struct SensorReportEvent *event)
{
int32_t ret;
struct AccelData rawData = { 0, 0, 0 };
static int32_t tmp[ACCEL_AXIS_NUM];
CHECK_NULL_PTR_RETURN_VALUE(cfg, HDF_ERR_INVALID_PARAM);
CHECK_NULL_PTR_RETURN_VALUE(event, HDF_ERR_INVALID_PARAM);
ret = ReadMxc6655xaRawData(cfg, &rawData, &event->timestamp);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: MXC6655XA read raw data failed", __func__);
return HDF_FAILURE;
}
event->sensorId = SENSOR_TAG_ACCELEROMETER;
event->option = 0;
event->mode = SENSOR_WORK_MODE_REALTIME;
rawData.x = rawData.x * MXC6655XA_ACC_SENSITIVITY_2G;
rawData.y = rawData.y * MXC6655XA_ACC_SENSITIVITY_2G;
rawData.z = rawData.z * MXC6655XA_ACC_SENSITIVITY_2G;
tmp[ACCEL_X_AXIS] = (rawData.x * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
tmp[ACCEL_Y_AXIS] = (rawData.y * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
tmp[ACCEL_Z_AXIS] = (rawData.z * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
ret = SensorRawDataToRemapData(cfg->direction, tmp, sizeof(tmp) / sizeof(tmp[0]));
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: MXC6655XA convert raw data failed", __func__);
return HDF_FAILURE;
}
event->dataLen = sizeof(tmp);
event->data = (uint8_t *)&tmp;
return ret;
}
```
Initialization
```c
static int32_t InitMxc6655xa(struct SensorCfgData *data)
{
int32_t ret;
CHECK_NULL_PTR_RETURN_VALUE(data, HDF_ERR_INVALID_PARAM);
ret = SetSensorRegCfgArray(&data->busCfg, data->regCfgGroup[SENSOR_INIT_GROUP]);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: MXC6655XA sensor init config failed", __func__);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
### HCS Configuration
HCS configuration of the MXC6655XA accel sensor driver
```c
device_sensor_mxc6655xa :: device {
device0 :: deviceNode {
policy = 1;
priority = 120;
preload = 0;
permission = 0664;
moduleName = "HDF_SENSOR_ACCEL_MXC6655XA";
serviceName = "hdf_accel_mxc6655xa";
deviceMatchAttr = "hdf_sensor_accel_mxc6655xa_driver";
}
}
```
Register group configuration information of the MXC6655XA accel sensor
```c
#include "../sensor_common.hcs"
root {
accel_mxc6655xa_chip_config : sensorConfig {
match_attr = "hdf_sensor_accel_mxc6655xa_driver";
sensorInfo :: sensorDeviceInfo {
sensorName = "accelerometer";
vendorName = "memsi_mxc6655xa"; // max string length is 16 bytes
sensorTypeId = 1; // enum SensorTypeTag
sensorId = 1; // user define sensor id
power = 230;
}
sensorBusConfig :: sensorBusInfo {
busType = 0; // 0:i2c 1:spi
busNum = 5;
busAddr = 0x15;
regWidth = 1; // 1byte
}
sensorIdAttr :: sensorIdInfo {
chipName = "mxc6655xa";
chipIdRegister = 0x0f;
chipIdValue = 0x05;
}
sensorDirection {
direction = 5; // chip direction range of value:0-7
/* <sign> 1:negative 0:positive
<map> 0:AXIS_X 1:AXIS_Y 2:AXIS_Z
*/
/* sign[AXIS_X], sign[AXIS_Y], sign[AXIS_Z], map[AXIS_X], map[AXIS_Y], map[AXIS_Z] */
convert = [
0, 0, 0, 0, 1, 2,
1, 0, 0, 1, 0, 2,
0, 0, 1, 0, 1, 2,
0, 1, 0, 1, 0, 2,
1, 0, 1, 0, 1, 2,
0, 0, 1, 1, 0, 2,
0, 1, 1, 0, 1, 2,
1, 1, 1, 1, 0, 2
];
}
sensorRegConfig {
/* regAddr: register address
value: config register value
len: size of value
mask: mask of value
delay: config register delay time (ms)
opsType: enum SensorOpsType 0-none 1-read 2-write 3-read_check 4-update_bit
calType: enum SensorBitCalType 0-none 1-set 2-revert 3-xor 4-left shift 5-right shift
shiftNum: shift bits
debug: 0-no debug 1-debug
save: 0-no save 1-save
*/
/* regAddr, value, mask, len, delay, opsType, calType, shiftNum, debug, save */
initSeqConfig = [
0x7e, 0xb6, 0xff, 1, 5, 2, 0, 0, 0, 0,
0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0
];
enableSeqConfig = [
0x7e, 0x11, 0xff, 1, 5, 2, 0, 0, 0, 0,
0x41, 0x03, 0xff, 1, 0, 2, 0, 0, 0, 0,
0x40, 0x08, 0xff, 1, 0, 2, 0, 0, 0, 0
];
disableSeqConfig = [
0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0
];
}
}
}
```
### Testing
The three-axis data of the sensor can be obtained during the UT test.
Test code path
```c
drivers/peripheral/sensor/test/unittest/common/hdf_sensor_test.cpp
```
Run the following command to compile the UT code:
```c
./build.sh --product-name rk3568 --build-target hdf_test_sensor
```
Push **hdf_test_sensor.bin** to the **system/bin** directory, add the execute permission to the file, and execute the file.
If the following information is displayed, the sensor test is successful.
```c
SensorTestDataCallback enter
sensor id :[1], data[1]: 0.001877
sensor id :[1], data[2]: 0.160823
sensor id :[1], data[3]: 0.046122
```
## Vibrator
### Vibrator Model
The vibrator driver model provides and implements vibrator-related HDIs. It supports the time sequence configuration in the static HCS and the duration configuration through dynamic parameters. The vibrator hardware service calls **StartOnce** to start one-shot vibration for a given duration and calls **StartEffect** to start vibration with a specified effect.
**Figure 1** Architecture of the vibrator driver module
![dayu200-vibrator-01.png](figures/dayu200/dayu200-vibrator-01.png)
The RK3568 supports the linear vibrator. The OpenHarmony mainline version has the overall driver framework. You only need to implement the specific component driver.
### HDF Driver Implementation
Code path:
```c
drivers/framework/model/misc/vibrator/driver/chipset/vibrator_linear_driver.c
```
Implementation of the linear vibrator accelerometer driver entry function
```c
struct HdfDriverEntry g_linearVibratorDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_LINEAR_VIBRATOR",
.Bind = BindLinearVibratorDriver,
.Init = InitLinearVibratorDriver,
.Release = ReleaseLinearVibratorDriver,
};
HDF_INIT(g_linearVibratorDriverEntry);
```
### HCS Configuration
HCS configuration of the driver
```c
vibrator :: host {
hostName = "vibrator_host";
device_vibrator :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "HDF_VIBRATOR";
serviceName = "hdf_misc_vibrator";
deviceMatchAttr = "hdf_vibrator_driver";
}
}
device_linear_vibrator :: device {
device0 :: deviceNode {
policy = 1;
priority = 105;
preload = 0;
permission = 0664;
moduleName = "HDF_LINEAR_VIBRATOR";
serviceName = "hdf_misc_linear_vibrator";
deviceMatchAttr = "hdf_linear_vibrator_driver";
}
}
}
```
HCS configuration of the linear vibrator
```c
root {
linearVibratorConfig {
boardConfig {
match_attr = "hdf_linear_vibrator_driver";
vibratorChipConfig {
busType = 1; // 0:i2c 1:gpio
gpioNum = 154;
startReg = 0;
stopReg = 0;
startMask = 0;
}
}
}
}
```
### UT Test
Test Code Path
```c
drivers/peripheral/misc/vibrator/test/unittest/common/hdf_vibrator_test.cpp
```
UT Code Compilation
```c
./build.sh --product-name rk3568 --build-target hdf_test_vibrator
```
Push **hdf_test_vibrator.bin** to the **system/bin** directory, add the execute permission to the file, and execute the file.
```
[ RUN ] HdfVibratorTest.CheckVibratorInstanceIsEmpty
[ OK ] HdfVibratorTest.CheckVibratorInstanceIsEmpty (0 ms)
[ RUN ] HdfVibratorTest.PerformOneShotVibratorDuration001
[ OK ] HdfVibratorTest.PerformOneShotVibratorDuration001 (2001 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect001
[ OK ] HdfVibratorTest.ExecuteVibratorEffect001 (5001 ms)
```
# Mini System STM32F407 SoC Porting Case
This document describes how to port the OpenHarmony LiteOS-M mini system on the Talkweb [Niobe407](https://gitee.com/openharmony-sig/device_board_talkweb) development board based on the `STM32F407IGT6` chip, to provide development board solutions for transportation and industrial fields. The porting architecture uses the `Board` and `SoC` separation solution and the `Newlib C` library of the `arm gcc` toolchain to implement adaptation of subsystems and components such as `lwip`, `littlefs`, and `hdf`. The matching application sample code is developed to support graphical configuration of compilation options through KConfig.
## Preparing for Adaptation
- Download the graphic tool [stm32cubemx](https://www.st.com/en/development-tools/stm32cubemx.html).
- Prepare the Ubuntu 20.04 system environment and install the [arm-none-eabi-gcc](https://gitee.com/openharmony/device_board_talkweb/blob/master/niobe407/docs/software/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E4%B8%8E%E5%9B%BA%E4%BB%B6%E7%BC%96%E8%AF%91.md#6%E5%AE%89%E8%A3%85%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E5%B7%A5%E5%85%B7%E9%93%BE) cross compilation toolchain.
### Generating an Available Project
Generate the Makefile project of the `STM32F407IGT6` chip by using the STM32CubeMX tool. You are advised to complete configurations as follows:
- Retain the default system settings.
- Set **SYSCLK** to 168 MHz in the clock configuration, to maximize the chip performance.
- Configure USART1 as the debugging serial port, to generate debugging information during adaptation.
- Set **Toolchain/IDE** to **Makefile** in STM32CubeMX configurations.
The generated project directory is as follows:
```
├── Core
│ ├── Inc
│ │ ├── main.h
│ │ ├── stm32f4xx_hal_conf.h
│ │ └── stm32f4xx_it.h
│ └── Src
│ ├── main.c --- Main function
│ ├── stm32f4xx_hal_msp.c --- Weak function configuration file in the HAL library
│ ├── stm32f4xx_it.c --- Interrupt callback function file
│ └── system_stm32f4xx.c --- System
├── Drivers
│ ├── CMSIS --- CMSIS API
│ └── STM32F4xx_HAL_Driver --- HAL library driver
├── Makefile --- Makefile compilation
├── STM32F407IGTx_FLASH.ld --- Link file
├── startup_stm32f407xx.s --- Startup file
└── stm32f407_output.ioc --- STM32CubeMX project file
```
### Verifying the Generated Project
Copy the generated project to Ubuntu, go to the project directory, and run the **make** command to compile the project. Ensure that the compilation is successful.
```
arm-none-eabi-gcc build/main.o build/stm32f4xx_it.o build/stm32f4xx_hal_msp.o build/stm32f4xx_hal_tim.o build/stm32f4xx_hal_tim_ex.o build/stm32f4xx_hal_uart.o build/stm32f4xx_hal_rcc.o build/stm32f4xx_hal_rcc_ex.o build/stm32f4xx_hal_flash.o build/stm32f4xx_hal_flash_ex.o build/stm32f4xx_hal_flash_ramfunc.o build/stm32f4xx_hal_gpio.o build/stm32f4xx_hal_dma_ex.o build/stm32f4xx_hal_dma.o build/stm32f4xx_hal_pwr.o build/stm32f4xx_hal_pwr_ex.o build/stm32f4xx_hal_cortex.o build/stm32f4xx_hal.o build/stm32f4xx_hal_exti.o build/system_stm32f4xx.o build/startup_stm32f407xx.o -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nano.specs -TSTM32F407IGTx_FLASH.ld -lc -lm -lnosys -Wl,-Map=build/stm32f407_output.map,--cref -Wl,--gc-sections -o build/stm32f407_output.elf
arm-none-eabi-size build/stm32f407_output.elf
text data bss dec hex filename
5000 20 1636 6656 1a00 build/stm32f407_output.elf
arm-none-eabi-objcopy -O ihex build/stm32f407_output.elf build/stm32f407_output.hex
arm-none-eabi-objcopy -O binary -S build/stm32f407_output.elf build/stm32f407_output.bin
```
After the compilation is complete, a .bin file is generated. To ensure that the program can run successfully on the development board, you need to initialize the serial port in the main function and output a string through the serial port. If the following information is generated during running, the development board is started successfully:
```
printf("hello world!!\r\n");
```
To generate logs using the **printf** function to the serial port, override the **_write** function. The sample code is as follows:
```c
#include <stdio.h>
int _write(int fd, char *ptr, int len)
{
return HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
}
```
Recompile the code and burn it to the development board for verification.
## Compilation and Building
### Directory Planning
The chip adaptation directory is planned as follows:
```
device
├── board --- Board vendor directory
│ └── talkweb --- Board vendor: Talkweb
│ └── niobe407 --- Board name, which is the same as the product name
└── soc --- SoC vendor directory
└── st --- SoC vendor
└── stm32f4xx --- SoC series: STM32F4xx, including SoC code
```
The planned product demo directory is as follows:
```
vendor
└── talkweb --- Vendor of the product sample
└── niobe407 --- Product name: niobe407
```
Obtain the [OpenHarmony source code](../get-code/sourcecode-acquire.md) and create directories as planned.
### Precompilation Adaptation
This part describes adaptation using the `hb set` command to set environment variables such as the root directory, board directory, product directory, and board vendor, for subsequent adaptation compilation.
To be specific:
1. Add the `config.json` file to the `vendor/talkweb/niobe407` directory to describe the board and kernel information used by the product sample. The sample code is as follows:
```
{
"product_name": "niobe407", --- Product name displayed when using the **hb set** command.
"type": "mini", --- System type, which can be mini, small, or standard.
"version": "3.0", --- Version of the system, which can be 1.0, 2.0, or 3.0.
"device_company": "talkweb", --- Board vendor name, which is used to find the **/device/board/talkweb** directory during compilation.
"board": "niobe407", --- Board name, which is used to find the **/device/board/talkweb/niobe407** directory during compilation.
"kernel_type": "liteos_m", --- Kernel type. The OpenHarmony supports multiple kernels, and one board may adapt multiple kernels. Therefore, you need to specify a kernel for compilation.
"kernel_version": "3.0.0", --- Kernel version. One board may adapt multiple Linux kernel versions. Therefore, you need to specify a kernel version for compilation.
"subsystems": [ ] --- Subsystem to be built.
}
```
2. Create the `board` directory under `//device/board/talkweb/niobe407` and add the `config.gni` file to the created directory to describe the compilation configuration information of the product.
```
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_m" --- Kernel type, which corresponds to **kernel_type** in **config.json**.
# Kernel version.
kernel_version = "3.0.0" --- Kernel version, which corresponds to **kernel_version** in **config.json**.
```
3. Check whether the `hb set` configuration is correct. If the following information is displayed after you enter `hb set`, the configuration is correct.
![hb set](figures/niobe407_hb_set.png)
4. Run the `hb env` command to view the selected precompilation environment variables.
![hb env](figures/niobe407_hb_env.png)
5. Introduction to **hb**
`hb` is a Python script tool provided by OpenHarmony to facilitate code building and compilation. The source code of `hb` is stored in the `//build/lite` repository directory. When the `hb set` command is executed, the script traverses `config.json` in the `//vendor/<product_company>/<product_name>` directory and provides product compilation options. In the **config.json** file, `product_name` indicates the product name, and `device_company` and `board` are used to associate the `//device/board/<device_company>/<board>` directory and match the `<any_dir_name>/config.gni` file in the directory. The `<any_dir_name>` directory name can be any name, however, you are advised to name it as the adapted kernel name (for example, **liteos_m**, **liteos_a**, or **linux**). If multiple `config.gni` files are matched, the **hb** command matches the `kernel_type` and `kernel_version` fields with the fields in the `config.json` file in `vendor/<device_company>` to determine the `config.gni` file to be compiled.
So far, the precompilation adaptation is complete. However, the project cannot be compiled by running `hb build`. You need to prepare for the subsequent `LiteOS-M` kernel porting.
## Kernel Porting
Kernel porting requires `LiteOS-M Kconfig` adaptation, `gn` compilation and building, and minimum kernel startup adaptation.
### Kconfig File Adaptation
1. Create the **kernel_configs** directory in `//vendor/talkweb/niobe407` and create an empty file named **debug.config**.
2. Open the `//kernel/liteos_m/Kconfig` file. Multiple Kconfig files in `//device/board` and `//device/soc` have been imported using the **orsource** command. You need to create and modify these files later.
```
orsource "../../device/board/*/Kconfig.liteos_m.shields"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.defconfig.boards"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.boards"
orsource "../../device/soc/*/Kconfig.liteos_m.defconfig"
orsource "../../device/soc/*/Kconfig.liteos_m.series"
orsource "../../device/soc/*/Kconfig.liteos_m.soc"
```
3. Create the Kconfig files in `//device/board/talkweb` by referring to the following directory structure:
```
.
├── Kconfig.liteos_m.boards
├── Kconfig.liteos_m.defconfig.boards
├── Kconfig.liteos_m.shields
└── niobe407
├── Kconfig.liteos_m.board --- Board configuration items
├── Kconfig.liteos_m.defconfig.board --- Default board configuration items
└── liteos_m
└── config.gni
```
4. Modify the `Kconfig` files.
- Add the following information to the `//device/board/talkweb/Kconfig.liteos_m.boards` file:
```
if SOC_STM32F407
orsource "niobe407/Kconfig.liteos_m.board" --- Load the definition of the specified board directory based on SoC definitions.
endif
```
- Add the following information to the `//device/board/talkweb/Kconfig.liteos_m.defconfig.boards` file:
```
orsource "*/Kconfig.liteos_m.defconfig.board"
```
- Add the following information to the `//device/board/talkweb/Kconfig.liteos_m.defconfig.boards` file:
```
orsource "shields/Kconfig.liteos_m.shields"
```
- Add the following information to the `//device/board/talkweb/niobe407/Kconfig.liteos_m.board` file:
```
menuconfig BOARD_NIOBE407
bool "select board niobe407"
depends on SOC_STM32F407 --- niobe407 uses the SoC of STM32F407. Only when the SoC is selected, the configuration items of niobe407 are available and can be selected.
```
- Add the following information to the `//device/board/talkweb/niobe407/Kconfig.liteos_m.defconfig.board` file:
```
if BOARD_NIOBE407
--- Used to add the default configuration of BOARD_NIOBE407.
endif #BOARD_NIOBE407
```
5. Create Kconfig files in the `//device/soc/st` directory by referring to the following directory structure, and copy the **Drivers** directory in the project generated by `STM32CubeMX` to the `stm32f4xx/sdk` directory.
```
.
├── Kconfig.liteos_m.defconfig
├── Kconfig.liteos_m.series
├── Kconfig.liteos_m.soc
└── stm32f4xx
├── Kconfig.liteos_m.defconfig.series
├── Kconfig.liteos_m.defconfig.stm32f4xx
├── Kconfig.liteos_m.series
├── Kconfig.liteos_m.soc
└── sdk
└── Drivers
├── CMSIS
└── STM32F4xx_HAL_Driver
```
6. Modify the Kconfig files.
- Add the following information to the `//device/soc/st/Kconfig.liteos_m.defconfig` file:
```
rsource "*/Kconfig.liteos_m.defconfig.series"
```
- Add the following information to the `//device/soc/st/Kconfig.liteos_m.series` file:
```
rsource "*/Kconfig.liteos_m.series"
```
- Add the following information to the `//device/soc/st/Kconfig.liteos_m.soc` file:
```
config SOC_COMPANY_STMICROELECTRONICS
bool
if SOC_COMPANY_STMICROELECTRONICS
config SOC_COMPANY
default "st"
rsource "*/Kconfig.liteos_m.soc"
endif # SOC_COMPANY_STMICROELECTRONICS
```
- Add the following information to the `//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.series` file:
```
if SOC_SERIES_STM32F4xx
rsource "Kconfig.liteos_m.defconfig.stm32f4xx"
config SOC_SERIES
string
default "stm32f4xx"
endif
```
- Add the following information to the `//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.stm32f4xx` file:
```
config SOC
string
default "stm32f4xx"
depends on SOC_STM32F4xx
```
- Add the following information to the `//device/soc/st/stm32f4xx/Kconfig.liteos_m.series` file:
```
config SOC_SERIES_STM32F4xx
bool "STMicroelectronics STM32F4xx series"
select ARCH_ARM
select SOC_COMPANY_STMICROELECTRONICS
select CPU_CORTEX_M4
help
Enable support for STMicroelectronics STM32F4xx series
```
- Add the following information to the `//device/soc/st/stm32f4xx/Kconfig.liteos_m.soc` file:
```
choice
prompt "STMicroelectronics STM32F4xx series SoC"
depends on SOC_SERIES_STM32F4xx
config SOC_STM32F407
bool "SoC STM32F407"
endchoice
```
7. Run the `make menuconfig` command in the `kernel/liteos_m` directory to select `SoC Series`.
![board make menuconfig](figures/niobe407_menuconfig.png)
The result is automatically saved in `$(PRODUCT_PATH)/kernel_configs/debug.config`. The saved result will be exported when you run the `make menuconfig` command next time.
### BUILD.gn File Adaptation
To quickly get familiar with **gn** compilation and adaptation, you are advised to read [LiteOS-M Kernel BUILD.gn Compilation Guide](https://gitee.com/caoruihong/kernel_liteos_m/wikis/LiteOS-M%E5%86%85%E6%A0%B8BUILD.gn%E7%BC%96%E5%86%99%E6%8C%87%E5%8D%97).
**(Note that no tab is allowed in the BUILD.gn file. All tabs are replaced with spaces.)**
1. In `kernel/liteos_m/BUILD.gn`, see that the compilation entries of `Board` and `SoC` are specified through `deps`.
```
deps += [ "//device/board/$device_company" ] --- Corresponds to **//device/board/talkweb**.
deps += [ "//device/soc/$LOSCFG_SOC_COMPANY" ] --- Corresponds to **//device/soc/st**.
```
2. Add the following information to the `//device/board/talkweb/BUILD.gn` file:
```
if (ohos_kernel_type == "liteos_m") {
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [ "niobe407" ]
}
}
```
3. Create a **BUILD.gn** file in the **niobe407** directory. To facilitate management, use the directory name as the module name.
```
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [
"liteos_m",
]
}
```
4. Copy the files in the **Core** directory of the sample project generated by STM32CubeMX, `startup_stm32f407xx.s`, and `STM32F407IGTx_FLASH.ld` link file to the `//device/board/talkweb/niobe407/liteos_m/` directory, create `BUILD.gn` in the directory, and add the following content:
```
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
kernel_module(module_name) {
sources = [
"startup_stm32f407xx.s",
"Src/main.c",
"Src/stm32f4xx_hal_msp.c",
"Src/stm32f4xx_it.c",
"Src/system_stm32f4xx.c",
]
include_dirs = [
"Inc",
]
}
config("public") {
ldflags = [
"-Wl,-T" + rebase_path("STM32F407IGTx_FLASH.ld"),
"-Wl,-u_printf_float",
]
libs = [
"c",
"m",
"nosys",
]
}
```
5. Configure `(Top) → Compat → Choose libc implementation` in **make menuconfig** and select `newlibc`.
6. The **\_write** function has the same name as the kernel file operation function. As a result, the compilation fails. Use another method to adapt the **printf** function. In this example, the **_write** function is deleted from the **main.c** file, and the **printf** function is used to perform the serial port printing test as follows:
```
uint8_t test[]={"hello niobe407!!\r\n"};
int len = strlen(test);
HAL_UART_Transmit(&huart1, (uint8_t *)test, len, 0xFFFF);
```
7. The same is true for `//device/soc/st/BUILD.gn`. The file is included layer by layer based on the directory structure. The file to be compiled and compilation parameters are specified in `//device/soc/st/stm32f4xx/sdk/BUILD.gn` using the `kernel_module` template. The following is an example:
```
import("//kernel/liteos_m/liteos.gni")
module_name = "stm32f4xx_sdk"
kernel_module(module_name) {
asmflags = board_asmflags
sources = [
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c",
"Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c",
]
}
#Specify the global header file search path.
config("public") {
include_dirs = [
"Drivers/STM32F4xx_HAL_Driver/Inc",
"Drivers/CMSIS/Device/ST/STM32F4xx/Include",
]
}
```
### config.gni File Adaptation
In the precompilation phase, the **config.gni** file is created in the `//device/board/talkweb/niobe407/liteos_m` directory. The **config.gni** file is the header file of the GN script and can be regarded as the global configuration file for project building. The file contains important information such as the CPU model, cross compilation toolchain, global compilation, and link parameters.
```
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_m"
# Kernel version.
kernel_version = "3.0.0"
# Board CPU type, e.g. "cortex-a7", "riscv32".
board_cpu = "cortex-m4"
# Board arch, e.g. "armv7-a", "rv32imac".
board_arch = ""
# Toolchain name used for system compiling.
# E.g. gcc-arm-none-eabi, arm-linux-harmonyeabi-gcc, ohos-clang, riscv32-unknown-elf.
# Note: The default toolchain is "ohos-clang". It's not mandatory if you use the default toolchain.
board_toolchain = "arm-none-eabi-gcc"
use_board_toolchain = true
# The toolchain path installed, it's not mandatory if you have added toolchain path to your ~/.bashrc.
board_toolchain_path = ""
# Compiler prefix.
board_toolchain_prefix = "arm-none-eabi-"
# Compiler type, "gcc" or "clang".
board_toolchain_type = "gcc"
#Debug compiler optimization level options
board_opt_flags = [
"-mcpu=cortex-m4",
"-mthumb",
"-mfpu=fpv4-sp-d16",
"-mfloat-abi=hard",
]
# Board related common compile flags.
board_cflags = [
"-Og",
"-Wall",
"-fdata-sections",
"-ffunction-sections",
"-DSTM32F407xx",
]
board_cflags += board_opt_flags
board_asmflags = [
"-Og",
"-Wall",
"-fdata-sections",
"-ffunction-sections",
]
board_asmflags += board_opt_flags
board_cxx_flags = board_cflags
board_ld_flags = board_opt_flags
# Board related headfiles search path.
board_include_dirs = [ "//utils/native/lite/include" ]
# Board adapter dir for OHOS components.
board_adapter_dir = ""
```
The settings of parameters such as **board_opt_flags**, **board_cflags**, and **board_asmflags** are defined as follows. You can extract the parameters from the `Makefile` file in the project generated by STM32CubeMX by referring to the following:
```
**board_opt_flags**: compiler-related options, such as the chip architecture, floating-point type, and compilation debugging optimization level.
**board_asmflags**: assembly compilation options, corresponding to the **ASFLAGS** variable in **Makefile**.
**board_cflags**: C code compilation options, corresponding to the **CFLAGS** variable in **Makefile**.
**board_cxx_flags**: C++ code compilation options, corresponding to the **CXXFLAGS** variable in **Makefile**.
**board_ld_flags**: link options, corresponding to the **LDFLAGS** variable in **Makefile**.
```
### Kernel Subsystem Adaptation
Add the kernel subsystem and related configurations to the `//vendor/talkweb/niobe407/config.json` file as follows:
```
{
"product_name": "niobe407",
"type": "mini",
"version": "3.0",
"device_company": "talkweb",
"board": "niobe407",
"kernel_type": "liteos_m",
"kernel_version": "3.0.0",
"subsystems": [
{
"subsystem": "kernel",
"components": [
{
"component": "liteos_m"
}
]
}
],
"product_adapter_dir": "",
"third_party_dir": "//third_party"
}
```
### config.gni File Adaptation
The `//kernel/liteos_m/kernel/include/los_config.h` file contains a header file named **target_config.h**. If this header file does not exist, a compilation error occurs.
This header file is used to define macros related to the SoC. You can create an empty header file and determine the macros to be defined based on the compilation error message. It has been verified that the kernel can be compiled successfully after the `LOSCFG_BASE_CORE_TICK_RESPONSE_MAX` macro is defined and the `stm32f4xx.h` header file is included for Cortex-M4 core adaptation.
If you do not know how to configure, see the `//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h` configuration in the VM QEMU example.
```
#ifndef _TARGET_CONFIG_H
#define _TARGET_CONFIG_H
#define LOSCFG_BASE_CORE_TICK_RESPONSE_MAX 0xFFFFFFUL
#include "stm32f4xx.h" // Contains a large number of macro definitions of the STM32f4.
#endif
```
The macro definition `LOSCFG_BASE_CORE_TICK_RESPONSE_MAX` is the configuration in the `//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h` file, and `//device/qemu/arm_mps2_an386` is the VM project of `cortex-m4`. These can be used as references.
### Kernel Startup Adaptation
The kernel subsystem is compiled successfully, and the **OHOS_Image.bin** file is generated in the **out** directory. Burn the generated **OHOS_Image.bin** file to the development board to check whether the board can start and run properly. If the correct information output by the serial port in the main function can be printed, start kernel startup adaptation.
1. Allocate memory for **liteos_m** and adapt the memory allocation function.
In the `//kernel/liteos_m/kernel/src/mm/los_memory.c` file, the `OsMemSystemInit` function initializes the memory through LOS_MemInit. Several key macros need to be specified. Add them to `target_config.h`.
```
extern unsigned int __los_heap_addr_start__;
extern unsigned int __los_heap_addr_end__;
#define LOSCFG_SYS_EXTERNAL_HEAP 1
#define LOSCFG_SYS_HEAP_ADDR ((void *)&__los_heap_addr_start__)
#define LOSCFG_SYS_HEAP_SIZE (((unsigned long)&__los_heap_addr_end__) - ((unsigned long)&__los_heap_addr_start__))
```
The `__los_heap_addr_start__` and `__los_heap_addr_end__` variables are defined in the `STM32F407IGTx_FLASH.ld` link file. Change the content in the braces of **_user_heap_stack** to the following:
```
._user_heap_stack :
{
. = ALIGN(0x40);
__los_heap_addr_start__ = .;
__los_heap_addr_end__ = ORIGIN(RAM) + LENGTH(RAM);
} >RAM
```
In addition, adapt the memory allocation function. The memory allocation functions such as **_malloc_r** have been implemented in the kernel, so you need to replace the memory allocation function in the standard library with that in the kernel. Modify the **board_ld_flags** variable in `//device/board/talkweb/niobe407/liteos_m/config.gni` as follows:
```
board_ld_flags = [
"-Wl,--wrap=_calloc_r",
"-Wl,--wrap=_malloc_r",
"-Wl,--wrap=_realloc_r",
"-Wl,--wrap=_reallocf_r",
"-Wl,--wrap=_free_r",
"-Wl,--wrap=_memalign_r",
"-Wl,--wrap=_malloc_usable_size_r",
]
board_ld_flags += board_opt_flags
```
2. printf Function Adaptation
To facilitate subsequent debugging, the **printf** function needs to be adapted first. Only simple adaptation is performed here. For details, see the source code of other development boards.
Create the **dprintf.c** file in the same directory as **main.c**. The file content is as follows:
```
#include <stdarg.h>
#include "los_interrupt.h"
#include <stdio.h>
extern UART_HandleTypeDef huart1;
INT32 UartPutc(INT32 ch, VOID *file)
{
char RL = '\r';
if (ch =='\n') {
HAL_UART_Transmit(&huart1, &RL, 1, 0xFFFF);
}
return HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
}
static void dputs(char const *s, int (*pFputc)(int n, FILE *cookie), void *cookie)
{
unsigned int intSave;
intSave = LOS_IntLock();
while (*s) {
pFputc(*s++, cookie);
}
LOS_IntRestore(intSave);
}
int printf(char const *fmt, ...)
{
char buf[1024] = { 0 };
va_list ap;
va_start(ap, fmt);
int len = vsnprintf_s(buf, sizeof(buf), 1024 - 1, fmt, ap);
va_end(ap);
if (len > 0) {
dputs(buf, UartPutc, 0);
} else {
dputs("printf error!\n", UartPutc, 0);
}
return len;
}
```
Add the **dprintf.c** file to the **BUILD.gn** script for compilation.
```
kernel_module(module_name) {
sources = [
"startup_stm32f407xx.s",
]
sources += [
"Src/main.c",
"Src/dprintf.c",
"Src/stm32f4xx_hal_msp.c",
"Src/stm32f4xx_it.c",
"Src/system_stm32f4xx.c",
]
}
```
After the serial port is initialized, use the **printf** function to generate information and test whether the adaptation is successful.
3. Call **LOS_KernelInit** to initialize the kernel and enter task scheduling.
After the serial port is initialized in the main function, call `LOS_KernelInit` for kernel initialization, create a task instance, and start task scheduling.
```
#include "los_task.h"
UINT32 ret;
ret = LOS_KernelInit(); // Initialize the kernel.
if (ret == LOS_OK) {
TaskSample(); // Sample task function, in which a thread task is created.
LOS_Start(); // Start task scheduling. Program execution is blocked here, and the kernel takes over the scheduling.
}
```
The content of the `TaskSample()` function is as follows:
```
VOID TaskSampleEntry2(VOID)
{
while (1) {
printf("TaskSampleEntry2 running...\n");
(VOID)LOS_TaskDelay(2000); /* 2000 millisecond */
}
}
VOID TaskSampleEntry1(VOID)
{
while (1) {
printf("TaskSampleEntry1 running...\n");
(VOID)LOS_TaskDelay(2000); /* 2000 millisecond */
}
}
VOID TaskSample(VOID)
{
UINT32 uwRet;
UINT32 taskID1;
UINT32 taskID2;
TSK_INIT_PARAM_S stTask = {0};
stTask.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry1;
stTask.uwStackSize = 0x1000;
stTask.pcName = "TaskSampleEntry1";
stTask.usTaskPrio = 6; /* Os task priority is 6 */
uwRet = LOS_TaskCreate(&taskID1, &stTask);
if (uwRet != LOS_OK) {
printf("Task1 create failed\n");
}
stTask.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry2;
stTask.uwStackSize = 0x1000;
stTask.pcName = "TaskSampleEntry2";
stTask.usTaskPrio = 7; /* Os task priority is 7 */
uwRet = LOS_TaskCreate(&taskID2, &stTask);
if (uwRet != LOS_OK) {
printf("Task2 create failed\n");
}
}
```
After the kernel startup is adapted, you can view the following information generated by serial port debugging:
![niobe407_boot](figures/niobe407_boot.png)
In the future, detailed adaptation verification needs to be performed on the entire basic kernel.
### Basic Kernel Function Adaptation
The adaptation items of basic kernel functions include [interrupt management](../kernel/kernel-mini-basic-interrupt.md), [task management](../kernel/kernel-mini-basic-task.md), [memory management](../kernel/kernel-mini-basic-memory.md), [kernel communication mechanism] (../kernel/kernel-mini-basic-ipc-event.md), [time management](../kernel/kernel-mini-basic-time.md), and [software timer](../kernel/kernel-mini-basic-soft.md). You can verify basic kernel functions by referring to the programming samples in the links. If any problem is found during the verification, perform specific adaptation.
According to the information generation interval in the previous section, the delay time of the `LOS_TaskDelay` function is inaccurate. You can define the following macros in `target_config.h` to adapt the kernel clock:
```
#define OS_SYS_CLOCK 168000000
#define LOSCFG_BASE_CORE_TICK_PER_SECOND (1000UL)
```
Most adaptation methods of other basic kernel functions are based on the macro definition in `target_config.h`. You need to explore the adaptation methods based on the source code in `//kernel/liteos_m`.
## Porting and Adaptation of LittleFS
The `Niobe407` development board is connected to an external 16 MB SPI flash. The Niobe407 performs LittleFS adaptation based on the SPI flash.
The kernel has adapted LittleFS. You only need to enable the configuration in Kconfig files and adapt the following LittleFS APIs:
```
int32_t LittlefsRead(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size)
{
W25x_BufferRead(buffer, cfg->context + cfg->block_size * block + off, size);
return LFS_ERR_OK;
}
int32_t LittlefsProg(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size)
{
W25x_BufferWrite((uint8_t *)buffer,cfg->context + cfg->block_size * block + off,size);
return LFS_ERR_OK;
}
int32_t LittlefsErase(const struct lfs_config *cfg, lfs_block_t block)
{
W25x_SectorErase(cfg->context + cfg->block_size * block);
return LFS_ERR_OK;
}
int32_t LittlefsSync(const struct lfs_config *cfg)
{
return LFS_ERR_OK;
}
```
Functions such as `W25x_BufferRead` are APIs for SPI flash reading and writing. The implementation varies according to the SPI flash model. For details about the implementation of the SPI flash operation of Niobe407, please refer to `//device/board/talkweb/niobe407/liteos_m/drivers/spi_flash/src/w25qxx.c`.
The SPI uses the HDF, and LittleFS depends on the SPI driver. To facilitate the configuration of the file system, you can add the LittleFS configuration to the .hcs file. For details, please refer to the `//device/board/talkweb/niobe407/liteos_m/hdf_config/hdf_littlefs.hcs` file.
```
misc {
littlefs_config {
match_attr = "littlefs_config";
mount_points = ["/talkweb"];
partitions = [0x800000];
block_size = [4096];
block_count = [256];
}
}
```
## Board-Level Driver Porting
Driver adaptation files are stored in `//drivers/adapter/platform`, including `gpio`, `i2c`, `pwm`, `spi`, `uart`, and `watchdog` drivers. These files are loaded using the `HDF` mechanism. This section uses `pwm` as an example.
### PWM Driver Adaptation
In the Hardware Driver Foundation (HDF), the PWM module uses the independent service mode for API adaptation. In this mode, each device independently publishes a service to process external access requests. When receiving an access request, the HDF DeviceManager extracts parameters from the request to call the internal APIs of the target device. In the independent service mode, the HDF DeviceManager provides service management capabilities. However, you need to configure a node for each device.
- API Description
```
1. pwm open initialization function: DevHandle PwmOpen(uint32_t num);
Parameters:
**num**: PWM device ID.
**return**: PWM device handle if the operation is successful; **NULL** otherwise.
2. pwm close deinitialization function: void PwmClose(DevHandle handle);
Parameters:
**handle**: PWM device handle.
**return**: none
3. PWM device parameter set function: int32_t PwmSetConfig(DevHandle handle, struct PwmConfig *config);
Parameters:
**handle**: PWM device handle.
***config**: parameter pointer.
**return**: **0** if the setting is successful; negative number otherwise.
```
- PWM HDF HCS Configuration File Parsing
The `device_info.hcs` file is stored in `//device/board/talkweb/niobe407/liteos_m/hdf_config/device_info.hcs`. The following example shows how to use the TIM2, TIM3, and TIM7 timers to output PWM signals:
```
device_pwm1 :: device {
pwm1 :: deviceNode {
policy = 2;
priority = 100;
moduleName = "ST_HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_1";
deviceMatchAttr = "config_pwm1";
}
}
device_pwm2 :: device {
pwm2 :: deviceNode {
policy = 2;
priority = 100;
moduleName = "ST_HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_2";
deviceMatchAttr = "config_pwm2";
}
}
device_pwm7 :: device {
pwm7 :: deviceNode {
policy = 2;
priority = 100;
moduleName = "ST_HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_7";
deviceMatchAttr = "config_pwm7";
}
}
```
The `hdf.hcs` file is stored in `//device/board/talkweb/niobe407/liteos_m/hdf_config/hdf.hcs`. You can configure the following information about the TIM timer in this file:
```
--- Note: The frequency of tim2-tim7 and tim12-tim14 is 84 MHz, and that of TIM1 and TIM8 to TIM11 is 168 MHz. tim6 and tim7 cannot output PWM signals.
--- tim1 to tim5 and tim8 have four channels, tim9 and tim12 have two channels, and tim10, tim11, tim13, and tim14 have only one channel.
pwm_config {
pwm1_config {
match_attr = "config_pwm1";
pwmTim = 1; --- timer ID. It means tim2. (0: tim1, 1: tim2, ... tim6 and tim7 are unavailable.)
pwmCh = 3; --- Number of channels. (0: ch1, 1: ch2, 2: ch3, 3: ch4)
prescaler = 4199; --- prescaler. For example, if the tim2 frequency is 84 MHz, 20 kHz (84 MHz/(4199 + 1)) is used as the reference.
}
pwm2_config {
match_attr = "config_pwm2";
pwmTim = 2;
pwmCh = 0;
prescaler = 8399;
}
pwm3_config {
match_attr = "config_pwm7";
pwmTim = 7;
pwmCh = 0;
prescaler = 8399;
}
}
```
For details about the `hdf pwm` adaptation code, see **//drivers/adapter/platform/pwm/pwm_stm32f4xx.c**.
For details about the `hdf pwm` usage example, see **//device/board/talkweb/niobe407/applications/206_hdf_pwm**.
## Subsystem Adaptation
The `OpenHarmony` subsystem adaptation consists of two parts:
- Add the related subsystem and component to `config.json`, so that they can be included in compilation by the compilation system.
- Perform hardware adaptation or optional software function adaptation for the `HAL` layer API of the component.
### lwIP Adaptation
The `LiteOS-M kernel` can use the Kconfig configuration to enable the lwIP to participate in compilation and specify the `lwip` compilation adaptation directory in the `kernel` component. The sample code is as follows:
```
{
"subsystem": "kernel",
"components": [
{
"component": "liteos_m",
"features": [
"ohos_kernel_liteos_m_lwip_path = \"//device/board/talkweb/niobe407/liteos_m/lwip_adapter\"" --- Specify the adaptation path.
]
}
]
}
```
In the specified compilation adaptation directory, intrude and modify the header file configuration of the lwIP third-party library in `#include_next "lwip/lwipopts.h"` mode. The detailed adaptation procedure for wired Ethernet lwIP adaptation will be supplemented later.
### Startup Subsystem Adaptation
For the startup subsystem, adapt the `bootstrap_lite` and `syspara_lite` components. Add the corresponding configuration items to the `//vendor/talkweb/niobe407/config.json` file, as shown below:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite",
"features": []
},
{
"component": "syspara_lite",
"features": []
}
]
}
```
When adapting the `bootstrap_lite` component, manually add the following information to the link file `//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld`:
```
__zinitcall_bsp_start = .;
KEEP (*(.zinitcall.bsp0.init))
KEEP (*(.zinitcall.bsp1.init))
KEEP (*(.zinitcall.bsp2.init))
KEEP (*(.zinitcall.bsp3.init))
KEEP (*(.zinitcall.bsp4.init))
__zinitcall_bsp_end = .;
__zinitcall_device_start = .;
KEEP (*(.zinitcall.device0.init))
KEEP (*(.zinitcall.device1.init))
KEEP (*(.zinitcall.device2.init))
KEEP (*(.zinitcall.device3.init))
KEEP (*(.zinitcall.device4.init))
__zinitcall_device_end = .;
__zinitcall_core_start = .;
KEEP (*(.zinitcall.core0.init))
KEEP (*(.zinitcall.core1.init))
KEEP (*(.zinitcall.core2.init))
KEEP (*(.zinitcall.core3.init))
KEEP (*(.zinitcall.core4.init))
__zinitcall_core_end = .;
__zinitcall_sys_service_start = .;
KEEP (*(.zinitcall.sys.service0.init))
KEEP (*(.zinitcall.sys.service1.init))
KEEP (*(.zinitcall.sys.service2.init))
KEEP (*(.zinitcall.sys.service3.init))
KEEP (*(.zinitcall.sys.service4.init))
__zinitcall_sys_service_end = .;
__zinitcall_sys_feature_start = .;
KEEP (*(.zinitcall.sys.feature0.init))
KEEP (*(.zinitcall.sys.feature1.init))
KEEP (*(.zinitcall.sys.feature2.init))
KEEP (*(.zinitcall.sys.feature3.init))
KEEP (*(.zinitcall.sys.feature4.init))
__zinitcall_sys_feature_end = .;
__zinitcall_run_start = .;
KEEP (*(.zinitcall.run0.init))
KEEP (*(.zinitcall.run1.init))
KEEP (*(.zinitcall.run2.init))
KEEP (*(.zinitcall.run3.init))
KEEP (*(.zinitcall.run4.init))
__zinitcall_run_end = .;
__zinitcall_app_service_start = .;
KEEP (*(.zinitcall.app.service0.init))
KEEP (*(.zinitcall.app.service1.init))
KEEP (*(.zinitcall.app.service2.init))
KEEP (*(.zinitcall.app.service3.init))
KEEP (*(.zinitcall.app.service4.init))
__zinitcall_app_service_end = .;
__zinitcall_app_feature_start = .;
KEEP (*(.zinitcall.app.feature0.init))
KEEP (*(.zinitcall.app.feature1.init))
KEEP (*(.zinitcall.app.feature2.init))
KEEP (*(.zinitcall.app.feature3.init))
KEEP (*(.zinitcall.app.feature4.init))
__zinitcall_app_feature_end = .;
__zinitcall_test_start = .;
KEEP (*(.zinitcall.test0.init))
KEEP (*(.zinitcall.test1.init))
KEEP (*(.zinitcall.test2.init))
KEEP (*(.zinitcall.test3.init))
KEEP (*(.zinitcall.test4.init))
__zinitcall_test_end = .;
__zinitcall_exit_start = .;
KEEP (*(.zinitcall.exit0.init))
KEEP (*(.zinitcall.exit1.init))
KEEP (*(.zinitcall.exit2.init))
KEEP (*(.zinitcall.exit3.init))
KEEP (*(.zinitcall.exit4.init))
__zinitcall_exit_end = .;
```
Adding the preceding content is because external APIs provided by `bootstrap_init` will be saved to the link segment. For details, see `//utils/native/lite/include/ohos_init.h`. The following table lists the automatic initialization macros of main services.
| API | Description |
| ---------------------- | -------------------------------- |
| SYS_SERVICE_INIT(func) | Entry for initializing and starting a core system service.|
| SYS_FEATURE_INIT(func) | Entry for initializing and starting a core system feature.|
| APP_SERVICE_INIT(func) | Entry for initializing and starting an application-layer service. |
| APP_FEATURE_INIT(func) | Entry for initializing and starting an application-layer feature. |
The **lib** file compiled using the loaded components needs to be manually add to the forcible link.
If the `bootstrap_lite` component is configured in the `//vendor/talkweb/niobe407/config.json` file:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite"
},
...
]
},
```
​ The `bootstrap_lite` component will compile the `//base/startup/bootstrap_lite/services/source/bootstrap_service.c` file. In this file, `SYS_SERVICE_INIT` is used to inject the `Init` function symbol to `__zinitcall_sys_service_start` and `__zinitcall_sys_service_end`. Since the `Init` function does not support explicit call, you need to forcibly link it to the final image. The sample code is as follows:
```
static void Init(void)
{
static Bootstrap bootstrap;
bootstrap.GetName = GetName;
bootstrap.Initialize = Initialize;
bootstrap.MessageHandle = MessageHandle;
bootstrap.GetTaskConfig = GetTaskConfig;
bootstrap.flag = FALSE;
SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
}
SYS_SERVICE_INIT(Init); --- Forcible link to the generated lib file is required if **SYS_INIT** is used for startup.
```
​ The `//base/startup/bootstrap_lite/services/source/BUILD.gn` file describes how to generate `libbootstrap.a` in `//out/niobe407/niobe407/libs` as follows:
```
static_library("bootstrap") {
sources = [
"bootstrap_service.c",
"system_init.c",
]
...
```
When the `syspara_lite` component is adapted, system parameters will be written into the file for persistent storage. In the mini system, file operation APIs include `POSIX` and `HalFiles`.
For access to the file system in the kernel, use the `POSIX` API, which means you need to add `enable_ohos_startup_syspara_lite_use_posix_file_api = true` to the `features` field.
If you are using the `HalFiles` API, no modification is required.
### DFX Subsystem Adaptation
To adapt the `DFX` subsystem, you need to add the `hilog_lite` and `hievent_lite` components to the `config.json` file.
```
{
"subsystem": "hiviewdfx",
"components": [
{
"component": "hilog_lite",
"features": []
},
{
"component": "hievent_lite",
"features": []
}
]
}
```
After the configuration is complete, you need to register the log output implementation function and add it for compilation.
```
bool HilogProc_Impl(const HiLogContent *hilogContent, uint32_t len)
{
char tempOutStr[LOG_FMT_MAX_LEN];
tempOutStr[0] = 0,tempOutStr[1] = 0;
if (LogContentFmt(tempOutStr, sizeof(tempOutStr), hilogContent) > 0) {
printf(tempOutStr);
}
return true;
}
HiviewRegisterHilogProc(HilogProc_Impl);
```
### System Service Management Subsystem Adaptation
To adapt the system service management subsystem, you need to add the `samgr_lite` component to the `config.json` file.
```
{
"subsystem": "systemabilitymgr",
"components": [
{
"component": "samgr_lite",
"features": []
}
]
}
```
In the mini system, the default size of the shared task stack configured for the `samgr_lite` is `2048`. During adaptation, you can use `config_ohos_systemabilitymgr_samgr_lite_shared_task_size` in features to reset the size of the shared task stack.
```
"config_ohos_systemabilitymgr_samgr_lite_shared_task_size = 4096"
```
### Security Subsystem Adaptation
To adapt the security subsystem, you need to add the `huks` component to the `config.json` file.
```
{
"subsystem": "security",
"components": [
{
"component": "huks",
"features": [
"huks_use_lite_storage = true",
"huks_use_hardware_root_key = true",
"huks_config_file = \"hks_config_lite.h\"",
"huks_key_store_path = \"storage\""
]
}
]
}
```
During `huks` adaptation, the `huks_key_store_path` configuration item specifies the path for storing the key, and `huks_config_file` indicates the name of the configuration header file.
### Utils Subsystem Adaptation
To adapt the utils subsystem, you need to add the `kv_store`, `file`, and `os_dump` components to the `config.json` file.
```
{
"subsystem": "utils",
"components": [
{
"component": "file",
"features": []
},
{
"component": "kv_store",
"features": [
"enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = false"
]
},
{
"component": "os_dump",
"features": []
}
]
},
```
Similar to the adaptation of the `syspara_lite` component, when the `kv_store` component is adapted, the key-value pair is written to the file. In the mini system, file operation APIs include `POSIX` and `HalFiles`. The `POSIX` API is used for access to the file system in the kernel, which means you need to add `enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true` to the `features` field. If you are using the `HalFiles` API, no modification is required.
### HDF Subsystem Adaptation
Similar to startup subsystem adaptation, you need to manually add the following information to the link file `//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld`:
```
_hdf_drivers_start = .;
KEEP(*(.hdf.driver))
_hdf_drivers_end = .;
```
After the kernel is initialized, call the **DeviceManagerStart** function. After the execution is complete, the HDF API can be called to control peripherals.
```
#include "devmgr_service_start.h" --- Note that this header file must be included.
#ifdef LOSCFG_DRIVERS_HDF
DeviceManagerStart();
#endif
```
The `devmgr_service_start.h` header file is stored in `//drivers/framework/core/common/include/manager`. To ensure that the header file can be found during compilation, add it to **include_dirs**.
### Adaptation of the XTS Compatibility Test Subsystem
#### Product Compatibility Specifications
For details about the product compatibility specifications, see [Introduction to Product Compatibility](https://gitee.com/openharmony-sig/compatibility/tree/master).
#### Adding an XTS Subsystem
For details about the `XTS` test reference, see [XTS Reference](../device-test/xts.md). To adapt the `XTS` subsystem, you need to add the `xts_acts` and `xts_tools` components to the `config.json` file. The configuration is as follows:
{
"subsystem": "xts",
"components": [
{
"component": "xts_acts",
"features": []
},
{
"component": "xts_tools",
"features": []
}
]
}
You can specify the following attributes in the features array of the **xts_acts** component:
- `config_ohos_xts_acts_utils_lite_kv_store_data_path`: name of the root directory of the mounted file system.
- `enable_ohos_test_xts_acts_use_thirdparty_lwip`: If the source code in the `thirdparty/lwip` directory is used for compilation, set this parameter to `true`. Otherwise, set this parameter to `false`.
#### Compiling the XTS
After the **config.json** file is configured, `hb build` does not compile the XTS. The XTS is compiled only when the debug version is compiled. In addition, the suite static library to be tested needs to be forcibly linked.
Add the following content to the **BUILD.gn** script that contains `kernel_module` in `//device/board/talkweb/liteos_m`:
```
config("public") {
if (build_xts) {
lib_dirs = [ "$root_out_dir/libs" ]
ldflags += [
"-Wl,--whole-archive", --- After whole-archive is enabled, functions and variables of the static library can be output to the dynamic library.
"-lbootstrap",
"-lbroadcast",
"-lhctest",
#Utils
# "-lmodule_ActsUtilsFileTest",
# "-lmodule_ActsKvStoreTest",
#DFX
"-lmodule_ActsDfxFuncTest",
"-lmodule_ActsHieventLiteTest",
#Startup
# "-lmodule_ActsBootstrapTest",
# "-lmodule_ActsParameterTest",
#Distributed scheduling
# "-lmodule_ActsSamgrTest",
"-Wl,--no-whole-archive", --- Disable the whole-archive feature.
]
}
}
```
The memory of the Niobe407 board is limited. Therefore, the XTS test needs to be performed by suite. Run the following compilation command to generate the firmware that contains the XTS test:
```
hb build -f -b debug --gn-args build_xts=true
```
In addition, you need to modify the `//vendor/talkweb/niobe407/hals/utils/sys_param/hal_sys_param.c` file to correctly define these character strings.
```
static const char OHOS_DEVICE_TYPE[] = {"Evaluation Board"};
static const char OHOS_DISPLAY_VERSION[] = {"OpenHarmony 3.1"};
static const char OHOS_MANUFACTURE[] = {"Talkweb"};
static const char OHOS_BRAND[] = {"Talkweb"};
static const char OHOS_MARKET_NAME[] = {"Niobe"};
static const char OHOS_PRODUCT_SERIES[] = {"Niobe"};
static const char OHOS_PRODUCT_MODEL[] = {"Niobe407"};
static const char OHOS_SOFTWARE_MODEL[] = {"1.0.0"};
static const char OHOS_HARDWARE_MODEL[] = {"2.0.0"};
static const char OHOS_HARDWARE_PROFILE[] = {"RAM:192K,ROM:1M,ETH:true"};
static const char OHOS_BOOTLOADER_VERSION[] = {"twboot-v2022.03"};
static const char OHOS_ABI_LIST[] = {"armm4_hard_fpv4-sp-d16-liteos"};
static const char OHOS_SERIAL[] = {"1234567890"}; // provided by OEM.
```
#### Verifying the XTS
After the compilation is complete, burn the firmware to the board. After the XTS is executed, information such as `xx Tests xx Failures xx Ignored` is displayed. The following uses the Utils test as an example:
```
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:590:testKvStoreClearCache002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:625:testKvStoreCacheSize001:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:653:testKvStoreCacheSize002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:681:testKvStoreCacheSize003:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:709:testKvStoreMaxSize001:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:737:testKvStoreMaxSize002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:765:testKvStoreMaxSize003:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:793:testKvStoreMaxSize004:PASS
+-------------------------------------------+
-----------------------
32 Tests 0 Failures 0 Ignored
OK
All the test suites finished!
```
# Small System STM32MP1 SoC Porting Case
This document describes how to port small development boards with screens based on the [BearPi-HM Micro development board](https://gitee.com/openharmony/device_board_bearpi) of the `STM32MP157` SoC from STMicroelectronics, so as to adapt components such as `ace_engine_lite`, `graphic_ui`, `aafwk_lite`, `appexecfwk_lite`, and `HDF` to the `OpenHarmony LiteOS-A` kernel. The porting architecture uses the solution where `Board` and `SoC` are separated.
## Compilation and Building
### Directory Planning
This solution designs the directory structure using the [board and SoC decoupling idea](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md), and plans the SoC adaptation directory as follows:
```
device
├── board --- Board vendor directory
│ └── bearpi --- Board vendor: BearPi
│ └── bearpi_hm_micro --- Board name: bearpi_hm_micro
└── soc --- SoC vendor directory
└── common --- Directory storing common HDF drivers.
└── st --- SoC vendor: STMicroelectronics
└── stm32mp1xx --- SoC series: stm32mp1xx
```
The planned product demo directory is as follows:
```
vendor
└── bearpi --- Vendor of the product demo, which is BearPi here
└── bearpi_hm_micro --- Product name: bearpi_hm_micro development board
```
### Precompilation Adaptation
Before porting, you need to perform precompilation adaptation.
The precompilation adaptation process mainly uses the `hb set` command to set environment variables such as the project root directory, board directory, product directory, and board vendor name, to make preparations for compilation.
The procedure is as follows:
1. Add the `config.json` file to the `vendor/bearpi/bearpi_hm_micro` directory to describe board and kernel information used by the product demo. The sample code is as follows:
```
{
"product_name": "bearpi_hm_micro", --- Product name displayed when the **hb set** command is used.
"version": "3.0", --- Version of the system, which can be 1.0, 2.0, or 3.0.
"type": "small", --- System type, which can be mini, small, or standard.
"ohos_version": "OpenHarmony 3.0", --- OpenHarmony system version.
"device_company": "bearpi", --- Board vendor name, which is used to find the /device/board/bearpi directory during compilation.
"board": "bearpi_hm_micro", --- Board name, which is used to find the /device/board/bearpi/bearpi_hm_micro directory during compilation.
"kernel_type": "liteos_a", --- Kernel type. OpenHarmony supports multiple kernels, and one board may adapt to multiple kernels. Therefore, you need to specify a kernel for compilation.
"kernel_version": "", --- Kernel version. One board may adapt to multiple Linux kernel versions. Therefore, you need to specify a kernel version for compilation.
"subsystems": [ ] --- Subsystem to be built.
}
```
2. Add the `config.gni` file to the `device/board/bearpi/bearpi_hm_micro/liteos_a` directory to describe board and kernel information used by the product demo. The sample code is as follows:
```
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_a"
# Kernel version.
kernel_version = ""
# Board CPU type, e.g. "cortex-a7", "riscv32".
board_cpu = "cortex-a7"
# Board arch, e.g. "armv7-a", "rv32imac".
board_arch = ""
# Toolchain name used for system compiling.
# E.g. gcc-arm-none-eabi, arm-linux-harmonyeabi-gcc, ohos-clang, riscv32-unknown-elf.
# Note: The default toolchain is "ohos-clang". It's not mandatory if you use the default toolchain.
board_toolchain = ""
# The toolchain path installed, it's not mandatory if you have added toolchain path to your ~/.bashrc.
board_toolchain_path = ""
# Compiler prefix.
board_toolchain_prefix = ""
# Compiler type, "gcc" or "clang".
board_toolchain_type = "clang"
# Board related common compile flags.
board_cflags = [
"-mfloat-abi=softfp",
"-mfpu=neon-vfpv4",
]
board_cxx_flags = [
"-mfloat-abi=softfp",
"-mfpu=neon-vfpv4",
]
board_ld_flags = []
# Board related headfiles search path.
board_include_dirs = []
# Board adapter dir for OHOS components.
board_adapter_dir = "//device/board/bearpi/bearpi_hm_micro/hal"
# Sysroot path.
board_configed_sysroot = ""
# Board storage type, it used for file system generation.
storage_type = "emmc"
```
3. Verify that the `hb set` configuration is correct. The `hb set` configuration is correct if the output information is as shown in the following figure.
Run `hb set`, enter the project root directory, and press **Enter**. The `hb` command will traverse all `config.json` files in the `//vendor/<product_company>/<product_name>` directory and list all available product compilation options. In `config.json`, `product_name` is used to display the product name, and `device_company` and `board` are used to associate the `//device/board/<device_company>/<board>` directory and match the `<any_dir_name>/config.gni` file. If multiple files are matched, the board adapts to multiple kernels. Then, `kernel_type` and `kernel_version` in `config.json` can be used to uniquely match `kernel_type` and `kernel_version` in `config.gni`, thus determining the board with a kernel to be compiled.
![hb set](figures/bearpi_hm_micro_hb_set.png)
After you select a product and press **Enter**, a **ohos_config.json** file will be generated in the root directory. The file will list the product information to be compiled. You can also run the `hb env` command to view the selected precompilation environment variables.
![hb env](figures/bearpi_hm_micro_hb_env.png)
## Kernel Porting
Kernel porting requires `LiteOS-A Kconfig` adaptation, `gn` compilation and building, and minimum kernel startup adaptation.
For details about the porting procedure, see [LiteOS-A kernel porting](porting-smallchip-kernel-a.md).
### Kconfig Adaptation
1. Add the chip, product name, and vendor name configurations to **//device/board/bearpi/bearpi_hm_micro/liteos_a/drivers/Kconfig**.
```
source "../../device/soc/st/common/platform/Kconfig"
config PLATFORM
string
default "stm32mp157" if PLATFORM_STM32MP157
config PRODUCT_NAME
string "product name"
default "bearpi_hm_micro" if PRODUCT_BEARPI_HM_MICRO
config DEVICE_COMPANY
string "vendor name"
default "st" if PLATFORM_STM32MP157
config TEE_ENABLE
bool "Enable TEE"
default n
depends on PLATFORM_STM32MP157
help
Enable teeos in platform
```
2. Add driver configurations to **//device/soc/st/common/platform/Kconfig**.
```
config DRIVERS_MMC
depends on DRIVERS
bool "Enable MMC"
default y
depends on DRIVERS && FS_VFS
help
Answer Y to enable LiteOS support MMC driver.
config DRIVERS_EMMC
depends on DRIVERS_MMC && PLATFORM_STM32MP157
bool "Enable MMC0 support eMMC type"
config DRIVERS_HI3881
bool "Enable Hi3881 Host driver"
default n
depends on DRIVERS_HDF_WIFI
help
Answer Y to enable Hi3881 Host driver.
config HW_RANDOM_ENABLE
depends on DRIVERS_RANDOM
bool "Select hw random"
default y
help
Answer Y to select hw random.
```
3. Enable related configurations in **//vendor/bearpi/bearpi_hm_micro/kernel_configs/debug_tee.config**.
```
...
LOSCFG_PLATFORM="stm32mp157"
LOSCFG_PRODUCT_NAME="bearpi_hm_micro"
LOSCFG_DEVICE_COMPANY="st"
# LOSCFG_PLATFORM_HI3516DV300 is not set
# LOSCFG_PLATFORM_HI3518EV300 is not set
# LOSCFG_PLATFORM_QEMU_ARM_VIRT_CA7 is not set
LOSCFG_PLATFORM_STM32MP157=y
LOSCFG_PRODUCT_BEARPI_HM_MICRO=y
LOSCFG_BOARD_CONFIG_PATH="device/board/bearpi/bearpi_hm_micro/liteos_a/board"
LOSCFG_TEE_ENABLE=y
...
```
### GN Build Adaptation
1. Create **BUILD.gn** in **//device/board/bearpi/bearpi_hm_micro/liteos_a** and add the following code. This module depends on **board**, **drivers**, and **hdf_config**.
```
cmd = "if [ -f $product_path/hdf_config/BUILD.gn ]; then echo true; else echo false; fi"
HAVE_PRODUCT_CONFIG =
exec_script("//build/lite/run_shell_cmd.py", [ cmd ], "value")
group("liteos_a") {
deps = [
"board",
"drivers",
]
if (HAVE_PRODUCT_CONFIG) {
deps += [ "$product_path/hdf_config" ]
} else {
deps += [ "hdf_config" ]
}
}
config("public") {
configs = [
"board:public",
"drivers:public",
]
}
```
2. Create **BUILD.gn** in **//device/board/bearpi/bearpi_hm_micro/liteos_a/board** and add the following code: Compile **os_adapt.c** kernel startup code into the system.
```
import("//kernel/liteos_a/liteos.gni")
module_name = "bsp_config"
kernel_module(module_name) {
sources = []
if (defined(LOSCFG_PLATFORM_ADAPT)) {
sources += [ "os_adapt/os_adapt.c" ]
}
}
config("public") {
include_dirs = [ "." ]
include_dirs += [ "include" ]
include_dirs += [ "$LITEOSTOPDIR/drivers/block/disk/include" ]
include_dirs +=
[ "$LITEOSTOPDIR/../../drivers/adapter/khdf/liteos/osal/include" ]
}
```
3. Create **BUILD.gn** in **//device/board/bearpi/bearpi_hm_micro/liteos_a/drivers**, add the following code, and compile the HDF driver under **device/soc/st/common/platform** into the system:
```
import("//drivers/adapter/khdf/liteos/hdf.gni")
group("drivers") {
public_deps = [ "//device/soc/st/common/platform:drivers" ]
}
config("public") {
configs = [ "//device/soc/st/common/platform:public" ]
}
```
4. Create **BUILD.gn** in **//vendor/bearpi/bearpi_hm_micro/hdf_config**, add the following code, and compile the HCS configuration file into the system:
```
module_switch = defined(LOSCFG_DRIVERS_HDF) && !defined(LOSCFG_DRIVERS_HDF_TEST)
module_name = "libhdf_config"
hdf_driver(module_name) {
hcs_sources = [ "hdf.hcs" ]
}
group("hdf_config") {
public_deps = [ ":$module_name" ]
deps = [
"hdf_test",
"hdf_test/hcs_macro_test",
]
}
```
### Kernel Startup Adaptation
1. Add the following kernel startup code to **//device/board/bearpi/bearpi_hm_micro/liteos_a/board/os_adapt.c**. For details, see [LiteOS-A kernel porting](porting-smallchip-kernel-a.md).
```
...
void SystemInit(void)
{
#ifdef LOSCFG_DRIVERS_RANDOM
dprintf("dev random init ...\n");
Mp1xxRngInit();
#endif
#ifdef LOSCFG_DRIVERS_MEM
dprintf("mem dev init ...\n");
extern int mem_dev_register(void);
mem_dev_register();
#endif
dprintf("Date:%s.\n", __DATE__);
dprintf("Time:%s.\n", __TIME__);
#ifdef LOSCFG_DRIVERS_HDF
dprintf("DeviceManagerStart start ...\n");
if (DeviceManagerStart()) {
PRINT_ERR("No drivers need load by hdf manager!");
}
dprintf("DeviceManagerStart end ...\n");
#endif
net_init();
#ifdef LOSCFG_PLATFORM_ROOTFS
dprintf("OsMountRootfs start ...\n");
if (LOS_GetCmdLine()) {
dprintf("get cmdline error!\n");
}
if (LOS_ParseBootargs()) {
dprintf("parse bootargs error!\n");
}
if (OsMountRootfs()) {
dprintf("mount rootfs error!\n");
}
dprintf("OsMountRootfs end ...\n");
#endif
dprintf("Before PLATFORM_UART ...\n");
#ifdef LOSCFG_DRIVERS_HDF_PLATFORM_UART
if (virtual_serial_init(TTY_DEVICE) != 0) {
PRINT_ERR("virtual_serial_init failed");
}
if (system_console_init(SERIAL) != 0) {
PRINT_ERR("system_console_init failed\n");
}
#endif
dprintf("After PLATFORM_UART ...\n");
if (OsUserInitProcess()) {
PRINT_ERR("Create user init process failed!\n");
return;
}
dprintf("cat log shell end\n");
return;
}
...
```
## Board-Level OS Porting
### Porting the HDF Driver for the SoC Platform
Driver adaptation files are stored in `device/soc/st/common/platform` and are loaded using the `HDF` mechanism. This section uses GPIO driver adaptation as an example.
1. Describe the building adaptation of the stm32mp1xx `gpio` driver in the `//device/soc/st/common/platform/gpio/BUILD.gn` file. The sample code is as follows:
```
module_switch = defined(LOSCFG_DRIVERS_HDF_PLATFORM_GPIO) --- If the GPIO configuration switch of the HDF is enabled, the following is built:
module_name = get_path_info(rebase_path("."), "name")
hdf_driver("hdf_gpio") {
sources = [ "stm32mp1_gpio.c" ] --- GPIO driver source file
include_dirs = [ --- Dependent .h path
"." ,
"../stm32mp1xx_hal/STM32MP1xx_HAL_Driver/Inc",
]
}
```
2. Describe the source code adaptation of the stm32mp1xx `gpio` driver in the `//device/soc/st/common/platform/gpio/stm32mp1_gpio.c` file.
First, load the basic driver adaptation framework based on the `HDF` driver framework of `OpenHarmony`, as shown below:
```
struct HdfDriverEntry g_GpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_PLATFORM_GPIO",
.Bind = GpioDriverBind,
.Init = GpioDriverInit,
.Release = GpioDriverRelease,
};
HDF_INIT(g_GpioDriverEntry); --- Load the GPIO driver using HDF_INIT.
```
3. Add the GPIO hardware description file **gpio_config.hcs** to **//device/soc/st/stm32mp1xx/sdk_liteos/hdf_config/gpio**, and add private configuration information of the driver to the file.
```
root {
platform {
gpio_config {
controller_0x50002000 {
match_attr = "st_stm32mp1_gpio";
groupNum = 11;
bitNum = 16;
gpioRegBase = 0x50002000;
gpioRegStep = 0x1000;
irqRegBase = 0x5000D000;
irqRegStep = 0x400;
}
}
}
}
```
4. Configure the product load driver. All device information of the product is defined in the source code file **//vendor/bearpi/bearpi_hm_micro/hdf_config/device_info/device_info.hcs**.
Add the platform driver to the host of the platform.
> **NOTE**
>
> **moduleName** must be the same as that defined in the driver file, and **deviceMatchAttr** must be the same as **match_attr** defined in the driver's private configuration information file **gpio_config.hcs**.
```
root {
...
platform :: host {
device_gpio :: device {
device0 :: deviceNode {
policy = 2;
priority = 10;
permission = 0644;
moduleName = "HDF_PLATFORM_GPIO_MANAGER";
serviceName = "HDF_PLATFORM_GPIO_MANAGER";
}
device1 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "HDF_PLATFORM_GPIO";
serviceName = "HDF_PLATFORM_GPIO";
deviceMatchAttr = "st_stm32mp1_gpio";
}
}
}
}
```
5. Complete driver code. Configuration information in **gpio_config.hcs** will be loaded in **GpioDriverInit**.
```
static int32_t GpioDriverInit(struct HdfDeviceObject *device)
{
int32_t ret;
struct Mp1xxGpioCntlr *stm32gpio = &g_Mp1xxGpioCntlr;
dprintf("%s: Enter", __func__);
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property NULL!", __func__);
return HDF_ERR_INVALID_OBJECT;
}
// Obtain property data.
ret = Mp1xxGpioReadDrs(stm32gpio, device->property);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get gpio device resource fail:%d", __func__, ret);
return ret;
}
...
}
```
### OpenHarmony Subsystem Adaptation
To adapt `OpenHarmony` subsystems, you only need to add related subsystems and components to `config.json`, so that the components can be included in the compilation by the compilation system.
#### Startup Subsystem Adaptation
For the startup subsystem, adapt the `bootstrap_lite`, `syspara_lite`, `appspawn_lite`, and `init_lite` components. Add the corresponding configuration items to the `vendor/bearpi/bearpi_hm_micro/config.json` file, as shown below:
```
{
"subsystem": "startup",
"components": [
{ "component": "syspara_lite", "features":[] },
{ "component": "bootstrap_lite", "features":[] },
{ "component": "appspawn_lite", "features":[] },
{ "component": "init_lite", "features":[] }
]
},
```
The system will be started according to startup configurations in the **//vendor/bearpi/bearpi_hm_micro/init_configs** file.
#### DFX Subsystem Adaptation
To adapt the `DFX` subsystem, you need to add the `hilog_featured_lite` and `hidumper_lite` components to the `config.json` file.
```
{
"subsystem": "hiviewdfx",
"components": [
{ "component": "hilog_featured_lite", "features":[] },
{ "component": "hidumper_lite", "features":[] }
]
},
```
#### System Service Management Subsystem Adaptation
To adapt the system service management subsystem, you need to add the `samgr_lite`, `safwk_lite`, and `dmsfwk_lite` components to the `config.json` file.
```
{
"subsystem": "distributed_schedule",
"components": [
{ "component": "samgr_lite", "features":[] },
{ "component": "safwk_lite", "features":[] },
{ "component": "dmsfwk_lite", "features":[] }
]
},
```
#### Security Subsystem Adaptation
To adapt the security subsystem, you need to add the `permission_lite`, `appverify`, `device_auth`, and `huks` components to the `config.json` file.
```
{
"subsystem": "security",
"components": [
{ "component": "permission_lite", "features":[] },
{ "component": "appverify", "features":[] },
{ "component": "device_auth", "features":[] },
{ "component": "huks", "features":
[
"huks_config_file = \"hks_config_small.h\""
]
}
]
},
```
#### utils Subsystem Adaptation
To adapt the utils subsystem, you need to add the `kv_store` and `os_dump` components to the `config.json` file.
```
{
"subsystem": "utils",
"components": [
{ "component": "kv_store", "features":[] },
{ "component": "os_dump", "features":[] }
]
},
```
#### Graphics Subsystem Adaptation
To adapt the graphics subsystem, you need to add the `graphic_utils` component to the `config.json` file.
```
{
"subsystem": "graphic",
"components": [
{ "component": "graphic_utils",
"features": [ "enable_ohos_graphic_utils_product_config = true"
]
},
{ "component": "graphic_hals", "features":[] },
{ "component": "ui", "features":[ "enable_graphic_font = true","enable_video_component=false"] },
{ "component": "surface", "features":[] },
{ "component": "wms", "features":[] }
]
},
```
For details about `graphic` configuration, see `//vendor/bearpi/bearpi_hm_micro/graphic_config/product_graphic_lite_config.h`.
#### ACE Subsystem Adaptation
To adapt the ACE subsystem, you need to add the `ace_engine_lite` component to the `config.json` file.
```
{
"subsystem": "ace",
"components": [
{
"component": "ace_engine_lite",
"features": [
"enable_ohos_ace_engine_lite_product_config = true"
]
}
]
},
```
For details about `ace_engine_lite` configuration, see `//vendor/bearpi/bearpi_hm_micro/ace_lite_config/product_acelite_config.h`.
#### aafwk Subsystem Adaptation
To adapt the aafwk subsystem, you need to add the `aafwk_lite` component to the `config.json` file.
```
{
"subsystem": "aafwk",
"components": [
{
"component": "aafwk_lite",
"features": [
"enable_ohos_appexecfwk_feature_ability = true" --- The FA feature is supported, that is, the graphics capability is included.
]
}
]
},
```
#### appexecfwk Subsystem Adaptation
To adapt the appexecfwk subsystem, you need to add the `appexecfwk_lite` component to the `config.json` file.
```
{
"subsystem": "appexecfwk",
"components": [
{
"component": "appexecfwk_lite"
}
]
},
```
# Combo Solution – W800 Chip Porting Case
The combo solution is developed based on the OpenHarmony LiteOS-M kernel. This document exemplifies how to port code of the [Neptune100 development board](https://gitee.com/openharmony-sig/device_board_hihope) powered by the W800 chip from Winner Micro. The porting architecture uses the solution where Board and SoC are separated. Compilation options can be graphically configured through KConfig. The porting of the `ck804ef` architecture is added to adapt subsystems and components such as `HDF` and `XTS`.
## Adaptation Preparation
Prepare the ubuntu20.04 system environment and install the cross compilation toolchain [csky-abiv2-elf-gcc](https://occ.t-head.cn/community/download?id=3885366095506644992).
## Compilation and Building
### Directory Planning
This solution designs the directory structure using the [board and SoC decoupling idea](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md).
The SoC adaptation directory is planned as follows:
```
device
├── board --- Board vendor directory
│ └── hihope --- Board vendor: HiHope
│ └── neptune100 --- Board name: Neptune100
└── soc --- SoC vendor directory
└── winnermicro --- SoC vendor: Winner Micro
└── wm800 --- SoC series: W800
```
The planned product demo directory is as follows:
```
vendor
└── hihope --- Vendor of the product demo.
├── neptune_iotlink_demo --- Product demo name: sample code of Neptune100
└── ...
```
### Product Definition
The `vendor/hihope/neptune_iotlink_demo/config.json` file describes the kernel, board, and subsystem information used by the product. The kernel, board model, and board vendor are required by the precompilation command `hb set` and must be planned. Example:
```
{
"product_name": "neptune_iotlink_demo", --- Product name
"ohos_version": "OpenHarmony 3.1", --- OS version in use
"type":"mini", --- OS type: mini
"version": "3.0", --- OS version: 3.0
"device_company": "hihope", --- Board vendor: hihope
"board": "neptune100", --- Board name: neptune100
"kernel_type": "liteos_m", --- Kernel type: liteos_m
"kernel_version": "3.0.0", --- Kernel version: 3.0.0
"subsystems": [] --- Subsystem
}
```
The filled information corresponds to the planned directory. In the information, `device_company` and `board` are used to associate the `device/board/<device_company>/` directory.
### Board Configuration
In the associated **\<board>** directory, place the `config.gni` file to the `device/board/hihope/neptune100/liteos_m` directory. This file is used to describe the board information, including the CPU model, cross compilation toolchain, global compilation, and link parameters.
```
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_m"
# Kernel version.
kernel_version = "3.0.0"
# Board CPU type, e.g. "cortex-a7", "riscv32".
board_cpu = "ck804ef"
# Board arch, e.g. "armv7-a", "rv32imac".
board_arch = "ck803"
# Toolchain name used for system compiling.
# E.g. gcc-arm-none-eabi, arm-linux-harmonyeabi-gcc, ohos-clang, riscv32-unknown-elf.
# Note: The default toolchain is "ohos-clang". It's not mandatory if you use the default toolchain.
board_toolchain = "csky-elfabiv2-gcc"
#use_board_toolchain = true
# The toolchain path installed, it's not mandatory if you have added toolchain path to your ~/.bashrc.
board_toolchain_path = ""
# Compiler prefix.
board_toolchain_prefix = "csky-elfabiv2-"
# Compiler type, "gcc" or "clang".
board_toolchain_type = "gcc"
# config.json parse
if (product_path != "") {
product_conf = read_file("${product_path}/config.json", "json")
product_name = product_conf.product_name
bin_list = product_conf.bin_list
}
# Board related common compile flags.
board_cflags = [
"-mcpu=ck804ef",
"-mhard-float",
"-DGCC_COMPILE=1",
"-DTLS_CONFIG_CPU_XT804=1",
"-DNIMBLE_FTR=1",
"-D__CSKY_V2__=1",
"-DCPU_CK804",
"-O2",
"-g3",
"-Wall",
"-ffunction-sections",
"-MMD",
"-MP",
]
board_cxx_flags = board_cflags
board_asmflags = [
"-mcpu=ck804ef",
"-DCPU_CK804",
]
board_ld_flags = []
# Board related headfiles search path.
board_include_dirs = []
# Board adapter dir for OHOS components.
board_adapter_dir = ""
# Sysroot path.
board_configed_sysroot = ""
# Board storage type, it used for file system generation.
storage_type = ""
```
### Precompilation
Run the precompilation command `hb set` in the project root directory to show relevant product information, as shown below:
```
hb set
OHOS Which product do you need? (Use arrow keys)
hihope
> neptune_iotlink_demo
OHOS Which product do you need? neptune_iotlink_demo
```
After `hb set` is executed, an `ohos_config.json` file will be automatically generated in the root directory. The file lists the product information to be compiled.
Run the `hb env` command to view the selected precompilation environment variables.
```
[OHOS INFO] root path: /home/xxxx/openharmony_w800
[OHOS INFO] board: neptune100
[OHOS INFO] kernel: liteos_m
[OHOS INFO] product: neptune_iotlink_demo
[OHOS INFO] product path: /home/xxxx/openharmony_w800/vendor/hihope/neptune_iotlink_demo
[OHOS INFO] device path: /home/xxxx/openharmony_w800/device/board/hihope/neptune100/liteos_m
[OHOS INFO] device company: hihope
```
So far, the precompilation adaptation is complete. However, the project cannot be compiled by running **hb build**. You also need to prepare for the subsequent LiteOS-M kernel porting.
## Kernel Porting
### Kconfig Adaptation
During the compilation of `kernel/liteos_m`, you need to use the `Kconfig` file for indexing in the corresponding board and SoC directory.
1. Create a **kernel_configs** directory in the `vendor/hihope/neptune_iotlink_demo` directory, and create an empty `debug.config` file.
2. Open the `kernel/liteos_m/Kconfig` file. Multiple `Kconfig` files in `device/board` and `device/soc` have been imported using the **orsource** command in this file. You need to create and modify these files later.
```
orsource "../../device/board/*/Kconfig.liteos_m.shields"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.defconfig.boards"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.boards"
orsource "../../device/soc/*/Kconfig.liteos_m.defconfig"
orsource "../../device/soc/*/Kconfig.liteos_m.series"
orsource "../../device/soc/*/Kconfig.liteos_m.soc"
```
3. Create corresponding `Kconfig` files in `device/board/hihope`.
```
├── neptune100 --- neptune100 board configuration directory.
│ ├── Kconfig.liteos_m.board --- Board configuration options.
│ ├── Kconfig.liteos_m.defconfig.board --- Default board configuration options.
│ └── liteos_m
│ └── config.gni --- Board configuration file.
├── Kconfig.liteos_m.boards --- Board configuration information of the board vendor.
└── Kconfig.liteos_m.defconfig.boards --- Default board configuration information of the board vendor.
```
4. Modify the `Kconfig` file in the `Board` directory.
Add the following content to `neptune100/Kconfig.liteos_m.board`:
```
config BOARD_NEPTUNE100
bool "select board neptune100"
depends on SOC_WM800
```
Configure that **BOARD_NEPTUNE100** can be selected only when **SOC_WM800** is selected.
Add the following content to `neptune100/Kconfig.liteos_m.defconfig.board`:
```
if BOARD_NEPTUNE100
endif #BOARD_NEPTUNE100
```
This content is used to add the default configuration of **BOARD_NEPTUNE100**.
5. Create corresponding `Kconfig` files in `device/soc/winnermicro`.
```
├── wm800 --- W800 series.
│ ├── Kconfig.liteos_m.defconfig.wm800 --- Default W800 SoC configuration.
│ ├── Kconfig.liteos_m.defconfig.series --- Default configuration of the W800 series.
│ ├── Kconfig.liteos_m.series --- Configuration of the W800 series.
│ └── Kconfig.liteos_m.soc --- W800 SoC configuration.
├── Kconfig.liteos_m.defconfig --- Default SoC configuration.
├── Kconfig.liteos_m.series --- Series configuration.
└── Kconfig.liteos_m.soc --- SoC configuration.
```
6. Modify the `Kconfig` file in the `Soc` directory.
Add the following content to `wm800/Kconfig.liteos_m.defconfig.wm800`:
```
config SOC
string
default "wm800"
depends on SOC_WM800
```
Add the following content to `wm800/Kconfig.liteos_m.defconfig.series`:
```
if SOC_SERIES_WM800
rsource "Kconfig.liteos_m.defconfig.wm800"
config SOC_SERIES
string
default "wm800"
endif
```
Add the following content to `wm800/Kconfig.liteos_m.series`:
```
config SOC_SERIES_WM800
bool "winnermicro 800 Series"
select ARM
select SOC_COMPANY_WINNERMICRO --- Select SOC_COMPANY_WINNERMICRO.
select CPU_XT804
help
Enable support for winnermicro 800 series
```
**SOC_WM800** in the `wm800/Kconfig.liteos_m.soc` file can be selected only after **SOC_SERIES_WM800** is selected.
```
choice
prompt "Winnermicro 800 series SoC"
depends on SOC_SERIES_WM800
config SOC_WM800 --- Select SOC_WM800.
bool "SoC WM800"
endchoice
```
In conclusion, to compile **BOARD_NEPTUNE100**, you need to select **SOC_COMPANY_WINNERMICRO**, **SOC_SERIES_WM800**, and **SOC_WM800**.
7. Run `make menuconfig` in `kernel/liteos_m` for configuration selection. The SoC series can be selected.
![w800_select.json](figures/w800_select.png)
The configured file is saved to `vendor/hihope/neptune_iotlink_demo/kernel_configs/debug.config` by default. You can also directly configure `debug.config`.
```
LOSCFG_PLATFORM_QEMU_CSKY_SMARTL=y
LOSCFG_SOC_SERIES_WM800=y
```
### Modular Compilation
The compilation of `Board` and `SoC` adopts the modular compilation method, starting from `kernel/liteos_m/BUILD.gn` and increasing by level. The adaptation process of this solution is as follows:
1. Create the `BUILD.gn` file in `device/board/hihope` and add the following content to the file:
```
if (ohos_kernel_type == "liteos_m") {
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [
"neptune100", --- Board module.
"shields",
]
}
}
```
In the preceding `BUILD.gn` file, **neptune100** and **shields** are the module names organized by directory level.
2. Create the `BUILD.gn` file in `device/soc/winnermicro` and add the following content to the file:
```
if (ohos_kernel_type == "liteos_m") {
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
module_group(module_name) {
modules = [
"hals",
"wm800",
]
}
}
```
3. In the `device/soc/winnermicro` module at each level, add the `BUILD.gn` file and compile the module. The following uses `device/soc/winnermicro/wm800/board/platform/sys/BUILD.gn` as an example:
```
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
kernel_module(module_name) { --- Compiled module.
sources = [ --- Compiled source file.
"wm_main.c",
]
include_dirs = [ --- Header file used in the module.
".",
]
}
```
4. To organize links and some compilation options, set the following parameters in `config("board_config")` in `device/soc/winnermicro/wm800/board/BUILD.gn`:
```
config("board_config") {
ldflags = [] --- Link parameters, including the Id file.
libs = [] --- Link library.
include_dirs = [] --- Common header file.
```
5. To organize some product applications, this solution adds a corresponding list to the `config.json` file of the vendor. The following uses `vendor/hihope/neptune_iotlink_demo/config.json` as an example to describe how to add a corresponding list to the `config.json` file:
```
"bin_list": [ --- demo list
{
"elf_name": "hihope",
"enable": "false", --- List switch.
"force_link_libs": [
"bootstrap",
"broadcast",
...
]
}
```
The demo is managed as a module. To enable or disable a demo, add or delete corresponding library files in **bin_list**. **bin_list** can be directly read in GN. You need to add the following content to `device/board/hihope/neptune100/liteos_m/config.gni`:
```
# config.json parse
if (product_path != "") {
product_conf = read_file("${product_path}/config.json", "json")
product_name = product_conf.product_name
bin_list = product_conf.bin_list
}
```
After reading the list, you can add related component libraries to the corresponding link options. Add the following content to `//device/soc/winnermicro/wm800/BUILD.gn`:
```
foreach(bin_file, bin_list) {
build_enable = bin_file.enable
...
if(build_enable == "true")
{
...
foreach(force_link_lib, bin_file.force_link_libs) {
ldflags += [ "-l${force_link_lib}" ]
}
...
}
}
```
### Kernel Subsystem Adaptation
Add the kernel subsystem and relevant configuration to `vendor/hihope/neptune_iotlink_demo/config.json`, as shown below:
```
"subsystems": [
{
"subsystem": "kernel",
"components": [
{
"component": "liteos_m", "features":[]
}
]
},
```
### Kernel Startup Adaptation
The Neptune100 development board uses the SoC architecture **ck804ef**, which is not supported by OpenHarmony. You need to port the architecture **ck804ef**. Adapt general files and function lists defined in `kernel\liteos_m\arch\include`, and place them to the `kernel\liteos_m\arch\csky\v2\ck804\gcc` directory.
The following is an example of kernel initialization:
```
osStatus_t ret = osKernelInitialize(); --- Kernel initialization.
if(ret == osOK)
{
threadId = osThreadNew((osThreadFunc_t)sys_init,NULL,&g_main_task); --- Create the init thread.
if(threadId!=NULL)
{
osKernelStart(); --- Thread scheduling.
}
}
```
Initialize necessary actions before **board_main** starts **OHOS_SystemInit**, as shown below:
```
...
UserMain(); --- Initialize the driver before starting OHOS_SystemInit of OpenHarmony.
...
OHOS_SystemInit(); --- Start OpenHarmony services and initialize components.
...
```
The **UserMain** function is in the `device/soc/winnermicro/wm800/board/app/main.c` file, as shown below:
```
...
if (DeviceManagerStart()) { --- HDF initialization.
printf("[%s] No drivers need load by hdf manager!",__func__);
}
...
```
### HDF Framework Adaptation
HDF provides a set of unified APIs for applications to access hardware, simplifying application development. To add the HDF component, you need to add it to `//vendor/hihope/neptune_iotlink_demo/kernel_configs`:
```
LOSCFG_DRIVERS_HDF=y
LOSCFG_DRIVERS_HDF_PLATFORM=y
```
Driver adaptation files are stored in `drivers/adapter/platform`, including the gpio, i2c, pwm, spi, uart, and watchdog drivers. These files are loaded using the HDF mechanism. This section uses GPIO and UART as an example.
#### GPIO Adaptation
1. The chip driver adaptation file is stored in the `drivers/adapter/platform` directory. Add the `gpio_wm.c` file to the **gpio** directory, and define the compilation adaptation of the W800 driver in `BUILD.gn`, as shown below:
```
...
if (defined(LOSCFG_SOC_COMPANY_WINNERMICRO)) {
sources += [ "gpio_wm.c" ]
}
...
```
2. Define the driver description file in `gpio_wm.c` as follows:
```
/* HdfDriverEntry definitions */
struct HdfDriverEntry g_GpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "WM_GPIO_MODULE_HDF",
.Bind = GpioDriverBind,
.Init = GpioDriverInit,
.Release = GpioDriverRelease,
};
HDF_INIT(g_GpioDriverEntry);
```
3. Add the GPIO hardware description information to `device/board/hihope/shields/neptune100/neptune100.hcs`.
```
root {
platform {
gpio_config {
match_attr = "gpio_config";
groupNum = 1;
pinNum = 48;
}
}
}
```
4. Obtain the **hcs** parameter from **GpioDriverInit** for initialization, as shown below:
```
...
gpioCntlr = GpioCntlrFromHdfDev(device); --- Obtain specific GPIO configurations through the **gpioCntlr** node variable.
if (gpioCntlr == NULL) {
HDF_LOGE("GpioCntlrFromHdfDev fail\r\n");
return HDF_DEV_ERR_NO_DEVICE_SERVICE;
}
...
```
#### UART Adaptation
1. The chip driver adaptation file is stored in the `drivers/adapter/platform` directory. Add the `uart_wm.c` file to the **uart** directory, and define the compilation adaptation of the W800 driver in `BUILD.gn`, as shown below:
```
...
if (defined(LOSCFG_SOC_COMPANY_WINNERMICRO)) {
sources += [ "uart_wm.c" ]
}
...
```
2. Define the driver description file in `uart_wm.c` as follows:
```
/* HdfDriverEntry definitions */
struct HdfDriverEntry g_UartDriverEntry = {
.moduleVersion = 1,
.moduleName = "W800_UART_MODULE_HDF",
.Bind = UartDriverBind,
.Init = UartDriverInit,
.Release = UartDriverRelease,
};
/* Initialize HdfDriverEntry */
HDF_INIT(g_UartDriverEntry);
```
3. Add the UART hardware description information to `device/board/hihope/shields/neptune100/neptune100.hcs`.
```
root {
platform {
uart_config {
/*
uart0 {
match_attr = "uart0_config";
num = 0;
baudrate = 115200;
parity = 0;
stopBit = 1;
data = 8;
}*/
uart1 {
match_attr = "uart1_config";
num = 1;
baudrate = 115200;
parity = 0;
stopBit = 1;
data = 8;
}
}
}
}
```
4. Obtain the **hcs** parameter from **UartDriverInit** for initialization, as shown below:
```
...
host = UartHostFromDevice(device);
if (host == NULL) {
HDF_LOGE("%s: host is NULL", __func__);
return HDF_ERR_INVALID_OBJECT;
}
...
```
## OpenHarmony Subsystem Adaptation
Subsystem compilation options are configured in the `config.json` file of the corresponding product, for example, `vendor/hihope/neptune_iotlink_demo/config.json`.
### wifi_lite Component
Add the `wifi_lite` component of the `communication` subsystem to the `config.json` file, as shown below:
```
{
"subsystem": "communication",
"components": [
{
"component": "wifi_lite",
"optional": "true"
}
]
},
```
The `wifi_lite` component is in the `build/lite/components/communication.json` file, which is described as follows:
```
{
"component": "wifi_lite",
"targets": [
"//foundation/communication/wifi_lite:wifi" --- Compilation target of the wifi_lite component.
]
},
```
In this case, the `wifi` adaptation source code can be checked in `device/soc/winnermicro/wm800/board/src/wifi/wm_wifi.c`, which is shown below:
```
int tls_wifi_netif_add_status_event(tls_wifi_netif_status_event_fn event_fn) --- Used to add the wifi event function.
{
u32 cpu_sr;
struct tls_wifi_netif_status_event *evt;
//if exist, remove from event list first.
tls_wifi_netif_remove_status_event(event_fn);
evt = tls_mem_alloc(sizeof(struct tls_wifi_netif_status_event));
if(evt==NULL)
return -1;
memset(evt, 0, sizeof(struct tls_wifi_netif_status_event));
evt->status_callback = event_fn;
cpu_sr = tls_os_set_critical();
dl_list_add_tail(&wifi_netif_status_event.list, &evt->list);
tls_os_release_critical(cpu_sr);
return 0;
}
```
### systemabilitymgr Subsystem Adaptation
To adapt the **systemabilitymgr** subsystem, you need to add the `samgr_lite` component to the `config.json` file, as shown below:
```
{
"subsystem": "systemabilitymgr",
"components": [
{
"component": "samgr_lite"
}
]
},
```
### utils Subsystem Adaptation
To adapt the utils subsystem, you need to add the `kv_store` and `file` components to the `config.json` file, as shown below:
```
{
"subsystem": "utils",
"components": [
{
"component": "kv_store",
"features": [
"enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true"
]
},
{ "component": "file", "features":[] }
]
},
```
When the `kv_store` component is adapted, key-value pairs will be written to the file. In the lite system, file operation APIs include `POSIX` and `HalFiles`.
The `POSIX` API is used for accessing the file system in the kernel. Therefore, you need to add `enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true` to `features`.
### Startup Subsystem Adaptation
To adapt the startup subsystem, you need to add the `bootstrap_lite` and `syspara_lite` components to the `config.json` file, as shown below:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite"
},
{
"component": "syspara_lite",
"features": [
"enable_ohos_startup_syspara_lite_use_posix_file_api = true",
"config_ohos_startup_syspara_lite_data_path = \"/data/\""
]
}
]
},
```
When adapting the **bootstrap_lite** component, you need to manually add the following content to the link script file `device/soc/winnermicro/wm800/board/ld/w800/gcc_csky.ld`:
```
.zinitcall_array :
{
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_core_start = .);
KEEP (*(SORT(.zinitcall.core*)))
KEEP (*(.zinitcall.core*))
PROVIDE_HIDDEN (__zinitcall_core_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_device_start = .);
KEEP (*(SORT(.zinitcall.device*)))
KEEP (*(.zinitcall.device*))
PROVIDE_HIDDEN (__zinitcall_device_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_bsp_start = .);
KEEP (*(SORT(.zinitcall.bsp*)))
KEEP (*(.zinitcall.bsp*))
PROVIDE_HIDDEN (__zinitcall_bsp_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_sys_service_start = .);
KEEP (*(SORT(.zinitcall.sys.service*)))
KEEP (*(.zinitcall.sys.service*))
PROVIDE_HIDDEN (__zinitcall_sys_service_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_app_service_start = .);
KEEP (*(SORT(.zinitcall.app.service*)))
KEEP (*(.zinitcall.app.service*))
PROVIDE_HIDDEN (__zinitcall_app_service_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_sys_feature_start = .);
KEEP (*(SORT(.zinitcall.sys.feature*)))
KEEP (*(.zinitcall.sys.feature*))
PROVIDE_HIDDEN (__zinitcall_sys_feature_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_app_feature_start = .);
KEEP (*(SORT(.zinitcall.app.feature*)))
KEEP (*(.zinitcall.app.feature*))
PROVIDE_HIDDEN (__zinitcall_app_feature_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_run_start = .);
KEEP (*(SORT(.zinitcall.run*)))
KEEP (*(.zinitcall.run*))
PROVIDE_HIDDEN (__zinitcall_run_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_test_start = .);
KEEP (*(SORT(.zinitcall.test*)))
KEEP (*(.zinitcall.test*))
PROVIDE_HIDDEN (__zinitcall_test_end = .);
. = ALIGN(0x4) ;
PROVIDE_HIDDEN (__zinitcall_exit_start = .);
KEEP (*(SORT(.zinitcall.exit*)))
KEEP (*(.zinitcall.exit*))
PROVIDE_HIDDEN (__zinitcall_exit_end = .);
} > REGION_RODATA
```
Adding the preceding content is because external APIs provided by `bootstrap_init` uses the segment injection mode and will be saved to the link segment. For details, see `utils/native/lite/include/ohos_init.h`. The following table lists the automatic initialization macros of main services.
| API | Description |
| ---------------------- | -------------------------------- |
| SYS_SERVICE_INIT(func) | Entry for initializing and starting a core system service.|
| SYS_FEATURE_INIT(func) | Entry for initializing and starting a core system feature.|
| APP_SERVICE_INIT(func) | Entry for initializing and starting an application-layer service. |
| APP_FEATURE_INIT(func) | Entry for initializing and starting an application-layer feature. |
The **lib** file compiled using the loaded components needs to be manually add to the forcible link.
If the `bootstrap_lite` component is configured in `vendor/hihope/neptune_iotlink_demo/config.json`:
```
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite"
},
...
]
},
```
The `bootstrap_lite` component will compile the `base/startup/bootstrap_lite/services/source/bootstrap_service.c` file. In this file, `SYS_SERVICE_INIT` is used to inject the `Init` function symbol to `__zinitcall_sys_service_start` and `__zinitcall_sys_service_end`. Since the `Init` function does not support explicit call, you need to forcibly link it to the final image, as shown below:
```
static void Init(void)
{
static Bootstrap bootstrap;
bootstrap.GetName = GetName;
bootstrap.Initialize = Initialize;
bootstrap.MessageHandle = MessageHandle;
bootstrap.GetTaskConfig = GetTaskConfig;
bootstrap.flag = FALSE;
SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
}
SYS_SERVICE_INIT(Init); --- Forcible link to the generated lib file is required if SYS_INIT is used for startup.
```
The `base/startup/bootstrap_lite/services/source/BUILD.gn` file describes `libbootstrap.a` generated in `out/neptune100/neptune_iotlink_demo/libs`, as shown below:
```
static_library("bootstrap") {
sources = [
"bootstrap_service.c",
"system_init.c",
]
...
```
When the `syspara_lite` component is adapted, system parameters will be written into the file for persistent storage. In the lite system, file operation APIs include **POSIX** and **HalFiles**.
The **POSIX** API is used for accessing the file system in the kernel. Therefore, you need to add `enable_ohos_startup_syspara_lite_use_posix_file_api = true` to the **features** field.
### XTS Subsystem Adaptation
To adapt the XTS subsystem, add the following component options to `config.json`:
```
{
"subsystem": "xts",
"components": [
{
"component": "xts_acts",
"features":
[
"config_ohos_xts_acts_utils_lite_kv_store_data_path = \"/data\"",
"enable_ohos_test_xts_acts_use_thirdparty_lwip = true"
]
},
{ "component": "xts_tools", "features":[] }
]
}
```
The XTS function is also organized using **list**. You can add or delete relevant modules in the `config.json` file.
```
"bin_list": [
{
"enable": "true",
"force_link_libs": [
"module_ActsParameterTest",
"module_ActsBootstrapTest",
"module_ActsDfxFuncTest",
"module_ActsHieventLiteTest",
"module_ActsSamgrTest",
"module_ActsUtilsFileTest",
"module_ActsKvStoreTest",
"module_ActsWifiServiceTest"
]
}
],
```
The adaptation process of other components is similar to that of other vendors.
# Standard System Solution – Yangfan Porting Case
This document describes how to port standard system functions based on the yangfan development board of the RK3399 chip from Rockchip. The porting processing mainly includes product configuration adding, kernel startup and upgrade, ADM-based conversion of audio, case summary of the camera, TP, LCD, Wi-Fi, BT, vibrator, sensor, and graphics display modules, as well as related function adaptation.
The development board system porting uses the solution where the board warehouse and SoC code are separated. The board warehouse stores adaptation code of on-board modules, such as audio, camera, TP, and Wi-Fi. The SoC warehouse stores adaptation codes of SoC driver modules, such as I2C, ISP, and RGA.
## Product Configuration and Directory Planning
### Product Configuration
Create a **config.json** file in the `//vendor/yangfan` directory of the product and specify the CPU architecture. Configuration of `//vendor/yangfan/rk3399.json` is as follows:
```
{
"product_name": "yangfan",--- Product name: yangfan
"device_company": "rockchip",--- Board vendor: Rockchip
"device_build_path": "device/board/isoftstone/yangfan",--- Device building path: device/board/isoftstone/yangfan
"target_cpu": "arm",--- Target CPU: arm
"type": "standard",--- OS type: standard
"version": "3.0",--- Version: 3.0
"board": "yangfan",--- Board name: yangfan
"enable_ramdisk": true,--- Enable memory virtual disk: true
"build_selinux": true,--- Build selinux: true
"inherit": [ "productdefine/common/inherit/rich.json", "productdefine/common/inherit/chipset_common.json" ],
"subsystems": [
{
"subsystem": "security",
"components": [
{
"component": "selinux",
"features": []
}
]
},
{
"subsystem": "communication",
"components": [
{
"component": "netmanager_ext",
"features": []
}
]
},
...
}
```
The main configurations are as follows:
1. "product_name": "yangfan",--- Product name: yangfan
2. "device_company": "rockchip",--- Board vendor: Rockchip
3. "device_build_path": "device/board/isoftstone/yangfan",--- Device building path: device/board/isoftstone/yangfan
4. "target_cpu": "arm",--- Target CPU: arm
5. "type": "standard",--- OS type: standard
6. "version": "3.0",--- Version: 3.0
7. "board": "yangfan",--- Board name: yangfan
8. "enable_ramdisk": true,--- Enable memory virtual disk: true
You can find defined subsystems in `//build/subsystem_config.json`. You can also customize subsystems.
You are advised to copy the configuration file of Hi3516DV300 and delete the **hisilicon_products** subsystem, which is used to compile the kernel for Hi3516DV300 and is not suitable for RK3568.
### Directory Planning
This solution designs the directory structure using the [board and SoC decoupling idea](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md), and plans the SoC adaptation directory as follows:
```
device
├── board --- Board vendor directory
│ └── isoftstone --- Board vendor
│ └── yangfan --- Board name: yangfan
└── soc --- SoC vendor directory
└── rockchip --- SoC vendor: Rockchip
└── rk3399 --- SoC series: RK3399, mainly solutions provided by the SoC manufacturer and closed-source libraries
```
The planned product demo directory is as follows:
```
vendor
└── isoftstone
└── yangfan --- Product name: product, HCS, and demo related
```
## **Kernel Startup**
### Secondary Boot
Unlike traditional boot that directly mounts **system** and boots using **init** of **system**, secondary boot is to mount **ramdsik**, boot using **init** of **ramdsik**, perform necessary initialization operations (such as mounting the **system** and **vendor** partitions, and then switch to **init** of **system**.
RK3399 adaptation is to pack **ramdisk** compiled in the mainline version into **boot_linux.img**. The procedure is as follows:
1. Enable secondary boot.
Enable **enable_ramdisk** in **//vendor/yangfan/rk3399.json**.
```
{
"product_name": "yangfan",
"device_company": "rockchip",
"device_build_path": "device/board/isoftstone/yangfan",
"target_cpu": "arm",
"type": "standard",
"version": "3.0",
"board": "yangfan",
"enable_ramdisk": true,
"build_selinux": true,
...
}
```
2. Pack the **ramdsik.img** file compiled in the mainline version to **boot_linux.img**.
Configuration:
RK supports **uboot** from **ramdisk**. You only need to add **ramdisk.img** to the configuration file of the packed **boot_linux.img**. Therefore, the **its** format of the mainline version is not used. Specifically, add the following content to the kernel compilation script **make-ohos.sh**:
```
function make_extlinux_conf()
{
dtb_path=$1
uart=$2
image=$3
echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF}
echo " kernel /extlinux/${image}" >> ${EXTLINUX_CONF}
echo " fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF}
if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then
echo " initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF}
fi
cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4"
echo " ${cmdline}" >> ${EXTLINUX_CONF}
}
```
### Packing
Add the **make-boot.sh** script for packing the boot image. This script can be called when the boot image is packed after **ramdisk** is compiled. The main content is as follows:
```
genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img
```
For details about modification for calling **make-boot.sh**, see [RK3568 adaptation to secondary boot]( https://gitee.com/openharmony/build/pulls/569/files).
### INIT Configuration
For details about the init configuration, see [Startup Subsystem](https://gitee.com/openharmony/docs/blob/master/zh-cn/readme/%E5%90%AF%E5%8A%A8%E6%81%A2%E5%A4%8D%E5%AD%90%E7%B3%BB%E7%BB%9F.md).
## **Audio**
### Introduction
This section describes how to develop the audio driver framework based on the Hardware Driver Foundation (HDF) in OpenHarmony 3.0, including detailed introduction to the composition of the audio driver framework, functional component implementation, and service nodes.
![Audio driver framework](figures/isoftstone/yangfan-Audio-ADM.png)
1. Audio Driver Model (ADM)
The ADM helps system developers to develop scenario-specific applications for the multimedia audio subsystem. With the ADM, codec and DSP device vendors can adapt their driver code based on the APIs provided by the ADM and implement quick development and easy adaptation to HarmonyOS.
2. Audio Control Dispatch
The Audio Control Dispatch dispatches the control instructions from the Audio Interface Lib to the driver layer.
3. Audio Stream Dispatch
The Audio Control Dispatch receives data streams from the Audio Interface Lib and distributes the data streams to the driver layer.
4. Card Manager
The Card Manager manages multiple audio adapters. Each audio adapter consists of the digital audio interface (DAI), platform, codec, accessory, DSP, and Smart Audio Power Manager (SAPM) modules.
5. Platform Driver
The Platform Driver is the driver adaptation layer.
6. Smart Audio Power Manager (SAPM)
The SAPM is the power manager module that optimizes the power consumption policies of the entire ADM power.
### Audio Driver Introduction
#### Code Directory
```
drivers
├── framework
│ └── model
│ │ └── audio # Framework code
│ │ ├─── common # Common implementation
│ │ ├─── core # Core
│ │ ├─── dispatch # Control stream and data stream implementation
│ │ └── sapm # Power manager
│ └── include
│ └── audio # External API
├── adapter
│ └──khdf
│ └── linux
│ └── model
│ └── audio # Compilation file
└── peripheral
└── audio
└── chipsets
└── rk3399 # Driver implementation
├── accessory #SmartPA driver
├── dai #I2S driver
└── soc #Dma driver
```
#### Audio Process Description
##### Startup Process
![](figures/isoftstone/yangfan-Audio-start.png)
1. When the system starts, the platform, codec, accessory, DSP, and DAI drivers of the audio module are loaded first. Each driver obtains the configuration information from its configuration file and saves the obtained information to the data structures.
2. Each driver module calls the ADM registration interface to add itself to the linked list of the driver module.
3. The ADM module reads configurations of hdf_audio_driver_0 (audio card_0) and hdf_audio_driver_1 (audio card_1), and loads specific devices of each module.
4. The ADM module initializes each module device by calling the initialization API of the respective module.
5. The initialized audio devices are added to the **cardManager** linked list.
##### Playback Process
![](figures/isoftstone/yangfan-Audio-play.png)
1. The Interface Lib dispatches the **Render Open** instruction through the service launched by the driver for handling the playback streaming (referred to as driver service hereinafter). Upon receiving the instruction, the Stream Dispatch service calls the API of each module to deliver the instruction.
2. The Audio Interface Lib sends a path selection instruction to the Control Dispatch service. The Control Dispatch service calls the DAI API to set the path.
3. The Interface Lib dispatches hardware parameters through the driver service. Upon receiving the parameters, the Stream Dispatch service calls the API of each module to set hardware parameters.
4. The Interface Lib dispatches the start playing instruction through the driver service. Upon receiving the instruction, the Stream Dispatch service calls the API of each module to perform related settings for each module.
5. The Interface Lib dispatches audio data through the driver service. Upon receiving the data, the Stream Dispatch service calls the **Platform AudioPcmWrite** API to send the audio data to direct memory access (DMA).
6. The Interface Lib dispatches the stop playing instruction through the driver service. Upon receiving the instruction, the Stream Dispatch service calls the stop API of each module to perform related settings for each module.
7. The Interface Lib dispatches the **Render Close** instruction through the driver service. Upon receiving the instruction, the Stream Dispatch service calls the **Platform AudioRenderClose** API to release resources.
##### Control Process
![](figures/isoftstone/yangfan-Audio-commond.png)
1. To adjust the volume, the Audio Interface Lib sends an instruction for obtaining the volume range to the Control Dispatch service. The Control Dispatch service parses the instruction and calls **get()** of the codec module to obtain the volume range.
2. The Audio Interface Lib sends an instruction for setting the volume to the Control Dispatch service. The Control Dispatch service parses the instruction and calls **Set()** of the codec module to set the volume.
#### Implementation Description
1. Driver registration
Take the registration function of codec as an example. When the codec driver is initialized, the following codec registration function is called to register codec with the **codecController** linked list.
```c
int32_t AudioRegisterCodec(struct HdfDeviceObject *device, struct CodecData *codecData, struct DaiData *daiData)
{
...
codec = (struct CodecDevice *)OsalMemCalloc(sizeof(*codec));
...
OsalMutexInit(&codec->mutex);
codec->devCodecName = codecData->drvCodecName;
codec->devData = codecData;
codec->device = device;
ret = AudioSocRegisterDai(device, daiData);
...
DListInsertHead(&codec->list, &codecController);
...
}
```
2. Data stream data distribution
During audio recording or playback, the Lib layer distributes or reads data using Dispatch. After receiving the request from the Lib layer, the API distributes or returns the data.
```c
static int32_t StreamDispatch(struct HdfDeviceIoClient *client, int cmdId,
struct HdfSBuf *data, struct HdfSBuf *reply)
{
unsigned int count = sizeof(g_streamDispCmdHandle) / sizeof(g_streamDispCmdHandle[0]);
for (unsigned int i = 0; i < count; ++i) {
if ((cmdId == (int)(g_streamDispCmdHandle[i].cmd)) && (g_streamDispCmdHandle[i].func != NULL)) {
return g_streamDispCmdHandle[i].func(client, data, reply);
}
}
ADM_LOG_ERR("invalid [cmdId=%d]", cmdId);
return HDF_FAILURE;
}
```
3. Control function registration API
Control functions such as volume control, gain control, and channel control are added to the audio adapter control list using this API.
```c
int32_t AudioAddControls(struct AudioCard *audioCard, const struct AudioKcontrol *controls, int32_t controlMaxNum)
{
...
for (i = 0; i < controlMaxNum; i++) {
control = AudioAddControl(audioCard, &controls[i]);
if (control == NULL) {
ADM_LOG_ERR("Add control fail!");
return HDF_FAILURE;
}
DListInsertHead(&control->list, &audioCard->controls);
}
ADM_LOG_DEBUG("Success.");
return HDF_SUCCESS;
}
```
4. Power management API
Add component implementation:
```c
int32_t AudioSapmNewComponents(struct AudioCard *audioCard,
const struct AudioSapmComponent *component, int32_t cptMaxNum)
{
...
for (i = 0; i < cptMaxNum; i++) {
ret = AudioSapmNewComponent(audioCard, component);
if (ret != HDF_SUCCESS) {
ADM_LOG_ERR("AudioSapmNewComponent fail!");
return HDF_FAILURE;
}
component++;
}
return HDF_SUCCESS;
}
```
Add channel implementation:
```c
int32_t AudioSapmAddRoutes(struct AudioCard *audioCard, const struct AudioSapmRoute *route, int32_t routeMaxNum)
{
...
for (i = 0; i < routeMaxNum; i++) {
ret = AudioSapmAddRoute(audioCard, route);
if (ret != HDF_SUCCESS) {
ADM_LOG_ERR("AudioSapmAddRoute failed!");
return HDF_FAILURE;
}
route++;
}
return HDF_SUCCESS;
}
```
Add control function implementation:
```c
int32_t AudioSapmNewControls(struct AudioCard *audioCard)
{
...
DLIST_FOR_EACH_ENTRY(sapmComponent, &audioCard->components, struct AudioSapmComponent, list) {
if (sapmComponent->newCpt) {
continue;
}
if (sapmComponent->kcontrolsNum > 0) {
sapmComponent->kcontrols = OsalMemCalloc(sizeof(struct AudioKcontrol*) * sapmComponent->kcontrolsNum);
if (sapmComponent->kcontrols == NULL) {
ADM_LOG_ERR("malloc kcontrols fail!");
return HDF_FAILURE;
}
}
switch (sapmComponent->sapmType) {
case AUDIO_SAPM_ANALOG_SWITCH:
case AUDIO_SAPM_MIXER:
case AUDIO_SAPM_MIXER_NAMED_CTRL:
case AUDIO_SAPM_SPK:
case AUDIO_SAPM_PGA:
ret = AudioSapmNewMixerControls(sapmComponent, audioCard);
break;
case AUDIO_SAPM_MUX:
case AUDIO_SAPM_VIRT_MUX:
case AUDIO_SAPM_VALUE_MUX:
ret = AudioSapmNewMuxControls(sapmComponent, audioCard);
break;
default:
ret = HDF_SUCCESS;
break;
}
...
ReadInitComponentPowerStatus(sapmComponent);
sapmComponent->newCpt = 1;
DListInsertTail(&sapmComponent->dirty, &audioCard->sapmDirty);
}
ret = AudioSapmPowerComponents(audioCard);
...
return HDF_SUCCESS;
}
```
5. Control stream data distribution
During audio recording or playback, the Lib layer delivers the control instruction using Dispatch. After receiving the instruction, the API distributes the instruction to each driver module.
```c
static int32_t ControlDispatch(struct HdfDeviceIoClient *client, int cmdId,
struct HdfSBuf *data, struct HdfSBuf *reply)
{
...
if (cmdId >= AUDIODRV_CTRL_IOCTRL_ELEM_BUTT || cmdId < 0) {
ADM_LOG_ERR("Invalid [cmdId=%d].", cmdId);
return HDF_FAILURE;
}
for (i = 0; i < HDF_ARRAY_SIZE(g_controlDispCmdHandle); ++i) {
if ((cmdId == (int)(g_controlDispCmdHandle[i].cmd)) && (g_controlDispCmdHandle[i].func != NULL)) {
return g_controlDispCmdHandle[i].func(client, data, reply);
}
}
return HDF_FAILURE;
}
```
### Audio Service Introduction
#### Service Node
Three services are provided for the HDI layer based on the audio driver of the ADM framework: hdf_audio_render, hdf_audio_capture, and hdf_audio_control.
The audio driver service nodes of the development board are as follows:
```
console:/dev # ls -al hdf_audio_*
crw------- 1 system system 249, 5 1970-01-01 00:21 hdf_audio_capture // Audio recording data stream service.
crw------- 1 system system 249, 3 1970-01-01 00:21 hdf_audio_codec_dev0 // Audio device name.
crw------- 1 system system 249, 4 1970-01-01 00:21 hdf_audio_control // Audio control stream service.
crw------- 1 system system 249, 6 1970-01-01 00:21 hdf_audio_render // Playback data stream service.
```
1. Audio control stream service
This service receives control instructions from the Lib layer, including the volume control, gain control, and channel control instructions. These control instructions are distributed to the driver through the control stream service.
2. Audio data playback service
This service receives audio data and playback parameters from the Lib layer, as well as instructions for starting, pausing, resuming, and stopping the playback. These instructions are distributed to the driver through the audio data and playback service.
3. Audio recording service
This service transmits audio data to the Lib layer and receives audio recording parameters from the Lib layer, as well as receive instructions for starting, pausing, resuming, and stopping audio recording. These instructions are distributed to the driver through the audio recording service.
#### Driver Service
Each audio device includes the following services.
| **hdf_audio_codec_dev0** | **Audio Device** |
| ------------------------ | ---------------------- |
| dma_service_0 | DMA driver service |
| dai_service | CPU DAI driver service |
| codec_service_0 | Codec driver service |
| dsp_service_0 | (Optional) DSP driver service|
| **hdf_audio_codec_dev1** | **Audio Device** |
| ------------------------ | --------------------------------- |
| dma_service_0 | DMA driver service |
| dai_service | CPU DAI driver service |
| codec_service_1 | Accessory driver service (SmartPA)|
| dsp_service_0 | (Optional) DSP driver service |
#### Code Path
```
vendor/rockchip/rk3399/hdf_config/khdf
├── audio # Audio private configuration file
├── device_info
| └── device_info.hcs # Device configuration file
└── hdf.hcs # Reference the HCS configuration file
```
#### Configuration Node Description
Take the codec driver as an example. Add the codec node information to the **audio host** node in the **device_info.hcs** file.
```
audio :: host {
hostName = "audio_host";
priority = 60;
...
device_codec :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_ES8316";
serviceName = "codec_service_0";
deviceMatchAttr = "hdf_codec_driver";
}
}
...
}
```
#### Driver Implementation
Modify the driver file to implement the driver logic that is the same as that of **moduleName** of **device_info.hcs**.
```
/* HdfDriverEntry implementations */
static int32_t Es8316DriverBind(struct HdfDeviceObject *device)
{
...
return HDF_SUCCESS;
}
static int32_t Es8316DriverInit(struct HdfDeviceObject *device)
{
...
return HDF_SUCCESS;
}
/* HdfDriverEntry definitions */
struct HdfDriverEntry g_es8316DriverEntry = {
.moduleVersion = 1,
.moduleName = "CODEC_ES8316",
.Bind = Es8316DriverBind,
.Init = Es8316DriverInit,
.Release = NULL,
};
HDF_INIT(g_es8316DriverEntry);
```
### Summary
A unified architecture is provided for audio development of OpenHarmony and unified APIs are provided for audio drivers of various platforms based on the ADM of the HDF framework. Audio drivers developed for one platform can be applied to multiple platforms, improving the development efficiency. This document briefly introduces the ADM to help developers develop their applications.
## **Camera**
### Introduction
This section describes how to develop the audio driver framework based on the Hardware Driver Foundation (HDF) in OpenHarmony 3.0, including detailed introduction to the composition of the camera driver framework, functional component implementation, and service nodes.
### Camera driver framework
OpenHarmony HDF camera driver module architecture
![img](https://gitee.com/openharmony/drivers_peripheral/raw/master/camera/figures/logic-view-of-modules-related-to-this-repository_zh.png)
The following uses the camera host as an example:
1. HDI implementation: implements standard device APIs for OpenHarmony cameras.
2. PipelineCorer: connects to the HDI implementation layer for control instruction and stream transfer, establishes data channels, and manages camera devices.
3. Platform Adaptation: shields the differences between bottom-layer chips and OSs for multi-platform adaptation.
According to analysis of the USB camera of RK3399E/T, Linux 4.19 is used as the kernel. The USB camera depends on the UVC of V4L2 under Linux. According to the architecture diagram, the HDF camera is compatible with the UVC of V4L2 under Linux. Therefore, you need to ensure that the USB and camera drivers required by the UVC are normal before debugging.
### Camera Driver Introduction
#### Configuration
arch/arm64/configs/rockchip_linux_defconfig
```c
CONFIG_VIDEO_V4L2_SUBDEV_API=y
CONFIG_MEDIA_USB_SUPPORT=y
CONFIG_USB_VIDEO_CLASS=y
```
#### Node Information
Before inserting the USB camera:
```
# ls -l dev/video*
crw-rw---- 1 root root 81, 0 2013-01-18 10:59 dev/video0
crw-rw---- 1 root root 81, 1 2013-01-18 10:59 dev/video1
crw-rw---- 1 root root 81, 2 2013-01-18 10:59 dev/video2
crw-rw---- 1 root root 81, 3 2013-01-18 10:59 dev/video3
crw-rw---- 1 root root 81, 4 2013-01-18 10:59 dev/video4
crw-rw---- 1 root root 81, 5 2013-01-18 10:59 dev/video5
crw-rw---- 1 root root 81, 6 2013-01-18 10:59 dev/video6
crw-rw---- 1 root root 81, 7 2013-01-18 10:59 dev/video7
crw-rw---- 1 root root 81, 8 2013-01-18 10:59 dev/video8
crw-rw---- 1 root root 81, 9 2013-01-18 10:59 dev/video9
#
```
After inserting the USB camera, add the **dev/video10** and **dev/video11** nodes.
```
# ls -l dev/video*
crw-rw---- 1 root root 81, 0 2013-01-18 10:59 dev/video0
crw-rw---- 1 root root 81, 1 2013-01-18 10:59 dev/video1
crw------- 1 root root 81, 10 2013-01-18 11:01 dev/video10
crw------- 1 root root 81, 11 2013-01-18 11:01 dev/video11
crw-rw---- 1 root root 81, 2 2013-01-18 10:59 dev/video2
crw-rw---- 1 root root 81, 3 2013-01-18 10:59 dev/video3
crw-rw---- 1 root root 81, 4 2013-01-18 10:59 dev/video4
crw-rw---- 1 root root 81, 5 2013-01-18 10:59 dev/video5
crw-rw---- 1 root root 81, 6 2013-01-18 10:59 dev/video6
crw-rw---- 1 root root 81, 7 2013-01-18 10:59 dev/video7
crw-rw---- 1 root root 81, 8 2013-01-18 10:59 dev/video8
crw-rw---- 1 root root 81, 9 2013-01-18 10:59 dev/video9
#
```
#### Opening a Device Node
In the code environment of OpenHarmony, compile the following code as an executable program and execute the program on the development board. The node is opened successfully if no error is reported.
```c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main(void)
{
// 1. Open the device.
int fd = open("/dev/video10", O_RDWR);
if (fd < 0) {
printf("open device fail\n");
return -1;
}
close(fd);
return 0;
}
```
#### Obtaining Parameters
```c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main(void)
{
// 1. Open the device.
int fd = open("/dev/video10", O_RDWR);
if (fd < 0) {
printf("open device fail\n");
return -1;
}
// 2. Obtain the format ioctl (file descriptor, command, and structure corresponding to the command) supported by the camera.
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int i = 0;
while(1) {
v4fmt.index = i++;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
if (ret < 0) {
printf("get fmt fail\n");
}
unsigned char *p = (unsigned char*)&v4fmt.pixelformat;
printf("index=%d\n", v4fmt.index);
printf("flags=%d\n", v4fmt.flags);
printf("description=%s\n", v4fmt.description);
printf("pixelformat=%c%c%c%c\n", p[0], p[1], p[2], p[3]);
printf("reserved=%d\n", v4fmt.reserved[0]);
}
close(fd);
return 0;
}
```
In the code environment of OpenHarmony, compile the preceding code as an executable program and execute the program on the development board. The result shows that the YUYV and MJPEG formats are supported.
index=0
flags=0
description=YUYV 4:2:2
pixelformat=YUYV
reserved=0
index=1
flags=1
description=Motion-JPEG
pixelformat=MJPG
reserved=0
#### Setting the Buffer Queue
```c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main(void)
{
// 1. Open the device.
int fd = open("/dev/video10", O_RDWR);
if (fd < 0) {
printf("open device fail\n");
return -1;
}
// 2. Set the collection format.
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width = 640;
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // Set the video capture format (which must be the same as the one obtained above).
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); // Set the format.
if (ret < 0) {
printf("set fmt fail\n");
return -1;
}
memset(&vfmt, 0, sizeof(vfmt));
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); // Obtain the format.
if (ret < 0) {
printf("set->get fmt fail\n");
return -1;
}
// 3. Apply for the kernel buffer queue.
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; // Apply for four buffers.
reqbuffer.memory = V4L2_MEMORY_MMAP; // Memory mapping method: MMAP/USERPTR
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer); // Allocate memory.
if (ret < 0) {
printf("req buffer fail\n");
return -1;
}
// 4. Stop the device.
close(fd);
return 0;
}
```
In the code environment of OpenHarmony, compile the preceding code as an executable program and execute the program on the development board.
Execution result: **req buffer fail**
Cause analysis: ioctl(fd, VIDIOC_REQBUFS, &reqbuffer); failure
Troubleshooting method 1: Add logs to the kernel to locate the position where **VIDIOC_REQBUFS** fails. After the **ioctl** command is delivered, the **video_usercopy** API in **drivers/media/v4l2-core/v4l2-ioctl.c** will be used. However, the specific failure cause is still not found.
Troubleshooting method 2: Contact Smart Device Tech to check whether the USB camera of the debian version of the Linux 4.19 kernel works properly. Result: After using the **gst-launch-1.0 v4l2src device=/dev/video10 ! image/jpeg, width= 1280, height=720, framerate=30/1 ! jpegparse ! mppjpegdec ! kmssink sync=false** command in the debian version, the HDMI screen can display the preview image properly.
Through the preceding operations, you can determine that the UVC driver of V4L2 under Linux and the external USB camera driver work properly. Next, you need to debug the HDF camera of OpenHarmony.
#### API Introduction
Check the camera-related executable programs in OpenHarmony: **ohos_camera_demo** and **v4l2_main**.
##### ohos_camera_demo
Execution result: After you enter **o**, no preview image is displayed and no error logs are recorded.
```
# ohos_camera_demo
GetUintParameter debug.bytrace.tags.enableflags error.
Options:
-h | --help Print this message
-o | --offline stream offline test
-c | --capture capture one picture
-w | --set WB Set white balance Cloudy
-v | --video capture Viedeo of 10s
-a | --Set AE Set Auto exposure
-f | --Set Flashlight Set flashlight ON 5s OFF
-q | --quit stop preview and quit this app
o
Options:
-h | --help Print this message
-o | --offline stream offline test
-c | --capture capture one picture
-w | --set WB Set white balance Cloudy
-v | --video capture Viedeo of 10s
-a | --Set AE Set Auto exposure
-f | --Set Flashlight Set flashlight ON 5s OFF
-q | --quit stop preview and quit this app
```
Cause analysis: Currently, **ohos_camera_demo** supports only MPP and does not support V4L2. Therefore, the debugging of this demo is canceled.
##### v4l2_main
Execution result: After you enter **u**, error message **ERROR:main test:cannot open framebuffer /dev/fb0 file node** is displayed.
```
Options:
-h | --help Print this message
-p | --preview start preview on platform sensor
-c | --capture capture one picture
-w | --set WB Set white balance Cloudy
-e | --Set AE Set exposure time
-v | --video capture Viedeo of 10s
-u | --uvc start preview on uvc preview
-a | --Set ATE Set Auto exposure
-q | --quit stop preview and quit this app
INFO:please input command(input -q exit this app)
u
ERROR:main test:cannot open framebuffer /dev/fb0 file node
INFO:V4L2OpenDevice /dev/video10
```
Cause analysis: Check whether the **dev/fb0** node exists. The **fb0** node does not exist. Check whether there are other **fb0** nodes in the root directory. The **dev/graphics/fb0** node exists.
```
# ls -l dev/fb0
ls: dev/fb0: No such file or directory
# find -name fb0
./dev/graphics/fb0
./sys/class/graphics/fb0
./sys/devices/platform/display-subsystem/graphics/fb0
```
You need to change **dev/fb0** in the executable program **v4l2_main** to **dev/graphics/fb0**. Here, **fb0** is **framebuffer**, which is used to display the preview image on the screen.
Modification: drivers/peripheral
```diff
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
index b351f49..d9c4cb3 100755
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
@@ -186,9 +186,9 @@ RetCode FBInit()
if (g_fbFd)
return RC_OK;
- g_fbFd = open("/dev/fb0", O_RDWR);
+ g_fbFd = open("/dev/graphics/fb0", O_RDWR);
if (g_fbFd < 0) {
- CAMERA_LOGE("main test:cannot open framebuffer %s file node\n", "/dev/fb0");
+ CAMERA_LOGE("main test:cannot open framebuffer %s file node\n", "/dev/graphics/fb0");
return RC_ERROR;
}
diff --git a/camera/hal/test/v4l2/src/test_display.cpp b/camera/hal/test/v4l2/src/test_display.cpp
index db908e7..7025deb 100644
--- a/camera/hal/test/v4l2/src/test_display.cpp
+++ b/camera/hal/test/v4l2/src/test_display.cpp
@@ -114,9 +114,9 @@ void TestDisplay::FBLog()
RetCode TestDisplay::FBInit()
{
- fbFd_ = open("/dev/fb0", O_RDWR);
+ fbFd_ = open("/dev/graphics/fb0", O_RDWR);
if (fbFd_ < 0) {
- CAMERA_LOGE("main test:cannot open framebuffer %s file node\n", "/dev/fb0");
+ CAMERA_LOGE("main test:cannot open framebuffer %s file node\n", "/dev/graphics/fb0");
return RC_ERROR;
}
@@ -439,4 +439,4 @@ void TestDisplay::StopStream(std::vector<int>& captureIds, std::vector<int>& str
std::cout << "==========[test log]check Capture: ReleaseStreams fail, rc = " << rc << std::endl;
}
}
-}
\ No newline at end of file
+}
diff --git a/display/hal/default/display_layer.c b/display/hal/default/display_layer.c
index ee7a825..e12a653 100644
--- a/display/hal/default/display_layer.c
+++ b/display/hal/default/display_layer.c
@@ -24,7 +24,7 @@
#define DEV_ID 0
#define LAYER_ID 0
-#define FB_PATH "/dev/fb0"
+#define FB_PATH "/dev/graphics/fb0"
#define DISP_WIDTH 800
#define DISP_HEIGHT 480
#define BITS_PER_PIXEL 32
```
Run the **./build.sh --product-name rk3399 --ccache --build-target v4l2_main** command to recompile **v4l2_main** after modification.
The executable program path after successful compilation is **./out/rk3399/hdf/hdf/v4l2_main**.
Push the newly compiled **v4l2_main** to the **system/bin** directory on the development board.
```
hdc shell "mount -o rw,remount /"
hdc file send D:\cyyanl\work\RockChip\bin\v4l2_main /system/bin
```
Continue to execute **v4l2_main**. If no **framebuffer** error is reported, the issue has been resolved. (Another idea is to analyze why **fb0** is in **/dev/graphics/fb0** instead of **/dev/fb0**, and then change **fb0** to **/dev/fb0**. This idea will be debugged later.)
New error: **ERROR:error: ioctl VIDIOC_QUERYBUF failed.**
```
Options:
-h | --help Print this message
-p | --preview start preview on platform sensor
-c | --capture capture one picture
-w | --set WB Set white balance Cloudy
-e | --Set AE Set exposure time
-v | --video capture Viedeo of 10s
-u | --uvc start preview on uvc preview
-a | --Set ATE Set Auto exposure
-q | --quit stop preview and quit this app
INFO:please input command(input -q exit this app)
u
INFO:the fixed information is as follow:
INFO:id=
INFO:sem_start=0
INFO:smem_len=2457600
...
INFO:V4L2AllocBuffer
INFO:V4L2AllocBuffer:memoryType_ = 2
INFO:V4L2AllocBuffer:V4L2_MEMORY_USERPTR = 2
INFO:V4L2AllocBuffer:VIDIOC_QUERYBUF = 3226490377
ERROR:error: ioctl VIDIOC_QUERYBUF failed.
ERROR:error: Creatbuffer: V4L2AllocBuffer error
ERROR:main test:V4L2PreviewThread CreatBuffer fail i = 0
```
Cause analysis: ioctl(fd, VIDIOC_QUERYBUF, &buf) failure. In the section for setting the format and applying for the buffer queue in the Linux L4V2 UVC driver chapter, the following error is reported:
ioctl(fd, VIDIOC_REQBUFS, &reqbuffer). Therefore, we can conclude that **ioctl VIDIOC_REQBUFS** on OpenHarmony will always report an error. The differences between the two failures are as follows:
The memory mapping methods are different: V4L2_MEMORY_MMAP and V4L2_MEMORY_USERPTR
According to issues of OpenHarmony, memory mapping method V4L2_MEMORY_MMAP is not supported. Next, we still use V4L2_MEMORY_USERPTR of **v4l2_main** for debugging and analysis.
For details, see [Method of Applying for Memory by Adding MMAP on V4L2 Devices](https://gitee.com/openharmony/drivers_peripheral/issues/I4EFWP).
Now, analyze the **ioctl(fd, VIDIOC_QUERYBUF, &buf)** failure. View the definition of VIDIOC_QUERYBUF: **videodev2.h**
```c
#define VIDIOC_QUERYBUF_IOWR('V', 9, struct v4l2_buffer)
```
Insert the **ioctl** definition **int ioctl(int fd, int cmd, …); VIDIOC_QUERYBUF** as the input parameter of **cmd**. The input parameter is of the **int** type. This is a digital command code. After the code is sent to the kernel using **ioctl**, the corresponding function operation will be performed. Therefore, the command code delivered in the user state must be consistent with that received by the kernel. Next, let's check the command code consistency.
**videodev2.h** contains 77 command codes for interaction with the kernel. Print the command codes in kernel state and compare them with those in the user state.
Printing in user state: drivers/peripheral
```diff
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
index d7dd15f..f7254b4 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
@@ -162,37 +162,119 @@ RetCode HosV4L2Buffers::V4L2DequeueBuffer(int fd)
return RC_OK;
}
+static void cyyanl_printf_cmd(void)
+{
+#if 1
+ CAMERA_LOGD("*************************************************************************************");
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYCAP ) = 0x%x\n", VIDIOC_QUERYCAP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FMT ) = 0x%x\n", VIDIOC_ENUM_FMT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_FMT ) = 0x%x\n", VIDIOC_G_FMT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_FMT ) = 0x%x\n", VIDIOC_S_FMT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_REQBUFS ) = 0x%x\n", VIDIOC_REQBUFS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYBUF ) = 0x%x\n", VIDIOC_QUERYBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_FBUF ) = 0x%x\n", VIDIOC_G_FBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_FBUF ) = 0x%x\n", VIDIOC_S_FBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_OVERLAY ) = 0x%x\n", VIDIOC_OVERLAY );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QBUF ) = 0x%x\n", VIDIOC_QBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_EXPBUF ) = 0x%x\n", VIDIOC_EXPBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_DQBUF ) = 0x%x\n", VIDIOC_DQBUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_STREAMON ) = 0x%x\n", VIDIOC_STREAMON );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_STREAMOFF ) = 0x%x\n", VIDIOC_STREAMOFF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_PARM ) = 0x%x\n", VIDIOC_G_PARM );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_PARM ) = 0x%x\n", VIDIOC_S_PARM );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_STD ) = 0x%x\n", VIDIOC_G_STD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_STD ) = 0x%x\n", VIDIOC_S_STD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMSTD ) = 0x%x\n", VIDIOC_ENUMSTD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMINPUT ) = 0x%x\n", VIDIOC_ENUMINPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_CTRL ) = 0x%x\n", VIDIOC_G_CTRL );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_CTRL ) = 0x%x\n", VIDIOC_S_CTRL );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_TUNER ) = 0x%x\n", VIDIOC_G_TUNER );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_TUNER ) = 0x%x\n", VIDIOC_S_TUNER );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_AUDIO ) = 0x%x\n", VIDIOC_G_AUDIO );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_AUDIO ) = 0x%x\n", VIDIOC_S_AUDIO );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYCTRL ) = 0x%x\n", VIDIOC_QUERYCTRL );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYMENU ) = 0x%x\n", VIDIOC_QUERYMENU );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_INPUT ) = 0x%x\n", VIDIOC_G_INPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_INPUT ) = 0x%x\n", VIDIOC_S_INPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_EDID ) = 0x%x\n", VIDIOC_G_EDID );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_EDID ) = 0x%x\n", VIDIOC_S_EDID );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_OUTPUT ) = 0x%x\n", VIDIOC_G_OUTPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_OUTPUT ) = 0x%x\n", VIDIOC_S_OUTPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMOUTPUT ) = 0x%x\n", VIDIOC_ENUMOUTPUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_AUDOUT ) = 0x%x\n", VIDIOC_G_AUDOUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_AUDOUT ) = 0x%x\n", VIDIOC_S_AUDOUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_MODULATOR ) = 0x%x\n", VIDIOC_G_MODULATOR );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_MODULATOR ) = 0x%x\n", VIDIOC_S_MODULATOR );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_FREQUENCY ) = 0x%x\n", VIDIOC_G_FREQUENCY );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_FREQUENCY ) = 0x%x\n", VIDIOC_S_FREQUENCY );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_CROPCAP ) = 0x%x\n", VIDIOC_CROPCAP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_CROP ) = 0x%x\n", VIDIOC_G_CROP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_CROP ) = 0x%x\n", VIDIOC_S_CROP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_JPEGCOMP ) = 0x%x\n", VIDIOC_G_JPEGCOMP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_JPEGCOMP ) = 0x%x\n", VIDIOC_S_JPEGCOMP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYSTD ) = 0x%x\n", VIDIOC_QUERYSTD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_FMT ) = 0x%x\n", VIDIOC_TRY_FMT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMAUDIO ) = 0x%x\n", VIDIOC_ENUMAUDIO );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMAUDOUT ) = 0x%x\n", VIDIOC_ENUMAUDOUT );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_PRIORITY ) = 0x%x\n", VIDIOC_G_PRIORITY );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_PRIORITY ) = 0x%x\n", VIDIOC_S_PRIORITY );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_SLICED_VBI_CAP ) = 0x%x\n", VIDIOC_G_SLICED_VBI_CAP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_LOG_STATUS ) = 0x%x\n", VIDIOC_LOG_STATUS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_EXT_CTRLS ) = 0x%x\n", VIDIOC_G_EXT_CTRLS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_EXT_CTRLS ) = 0x%x\n", VIDIOC_S_EXT_CTRLS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_EXT_CTRLS ) = 0x%x\n", VIDIOC_TRY_EXT_CTRLS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FRAMESIZES ) = 0x%x\n", VIDIOC_ENUM_FRAMESIZES );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FRAMEINTERVALS) = 0x%x\n", VIDIOC_ENUM_FRAMEINTERVALS);
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_ENC_INDEX ) = 0x%x\n", VIDIOC_G_ENC_INDEX );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENCODER_CMD ) = 0x%x\n", VIDIOC_ENCODER_CMD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_ENCODER_CMD ) = 0x%x\n", VIDIOC_TRY_ENCODER_CMD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_HW_FREQ_SEEK ) = 0x%x\n", VIDIOC_S_HW_FREQ_SEEK );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_CREATE_BUFS ) = 0x%x\n", VIDIOC_CREATE_BUFS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_PREPARE_BUF ) = 0x%x\n", VIDIOC_PREPARE_BUF );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_G_SELECTION ) = 0x%x\n", VIDIOC_G_SELECTION );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_S_SELECTION ) = 0x%x\n", VIDIOC_S_SELECTION );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_DECODER_CMD ) = 0x%x\n", VIDIOC_DECODER_CMD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_DECODER_CMD ) = 0x%x\n", VIDIOC_TRY_DECODER_CMD );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_DV_TIMINGS ) = 0x%x\n", VIDIOC_ENUM_DV_TIMINGS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERY_DV_TIMINGS ) = 0x%x\n", VIDIOC_QUERY_DV_TIMINGS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_DV_TIMINGS_CAP ) = 0x%x\n", VIDIOC_DV_TIMINGS_CAP );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FREQ_BANDS ) = 0x%x\n", VIDIOC_ENUM_FREQ_BANDS );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_DBG_G_CHIP_INFO ) = 0x%x\n", VIDIOC_DBG_G_CHIP_INFO );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(VIDIOC_QUERY_EXT_CTRL ) = 0x%x\n", VIDIOC_QUERY_EXT_CTRL );
+ CAMERA_LOGD("cyyanl v4l2 ioctl cmd(BASE_VIDIOC_PRIVATE ) = 0x%x\n", BASE_VIDIOC_PRIVATE );
+ CAMERA_LOGD("*************************************************************************************");
+#endif
+}
+
RetCode HosV4L2Buffers::V4L2AllocBuffer(int fd, const std::shared_ptr<FrameSpec>& frameSpec)
{
struct v4l2_buffer buf = {};
struct v4l2_plane planes[1] = {};
- CAMERA_LOGD("V4L2AllocBuffer\n");
+ CAMERA_LOGD("V4L2AllocBuffer enter\n");
+ cyyanl_printf_cmd();
if (frameSpec == nullptr) {
CAMERA_LOGE("V4L2AllocBuffer frameSpec is NULL\n");
return RC_ERROR;
}
-
switch (memoryType_) {
case V4L2_MEMORY_MMAP:
// to do something
break;
case V4L2_MEMORY_USERPTR:
+ CAMERA_LOGD("V4L2AllocBuffer:V4L2_MEMORY_USERPTR = %d\n", V4L2_MEMORY_USERPTR);
buf.type = bufferType_;
buf.memory = memoryType_;
buf.index = (uint32_t)frameSpec->buffer_->GetIndex();
-
if (bufferType_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ CAMERA_LOGD("V4L2AllocBuffer:V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = %d\n", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
buf.m.planes = planes;
buf.length = 1;
}
```
Printing in kernel state: kernel/linux/linux-4.19/
```diff
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 0842a47c6..8aa60407f 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -2902,10 +2902,93 @@ struct uvc_driver uvc_driver = {
},
};
+static void cyyanl_printk_cmd(void)
+{
+ printk("*************************************************************************************");
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYCAP ) = %ld\n", VIDIOC_QUERYCAP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FMT ) = %ld\n", VIDIOC_ENUM_FMT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_FMT ) = %ld\n", VIDIOC_G_FMT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_FMT ) = %ld\n", VIDIOC_S_FMT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_REQBUFS ) = %ld\n", VIDIOC_REQBUFS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYBUF ) = %ld\n", VIDIOC_QUERYBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_FBUF ) = %ld\n", VIDIOC_G_FBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_FBUF ) = %ld\n", VIDIOC_S_FBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_OVERLAY ) = %ld\n", VIDIOC_OVERLAY );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QBUF ) = %ld\n", VIDIOC_QBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_EXPBUF ) = %ld\n", VIDIOC_EXPBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_DQBUF ) = %ld\n", VIDIOC_DQBUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_STREAMON ) = %ld\n", VIDIOC_STREAMON );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_STREAMOFF ) = %ld\n", VIDIOC_STREAMOFF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_PARM ) = %ld\n", VIDIOC_G_PARM );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_PARM ) = %ld\n", VIDIOC_S_PARM );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_STD ) = %ld\n", VIDIOC_G_STD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_STD ) = %ld\n", VIDIOC_S_STD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMSTD ) = %ld\n", VIDIOC_ENUMSTD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMINPUT ) = %ld\n", VIDIOC_ENUMINPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_CTRL ) = %ld\n", VIDIOC_G_CTRL );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_CTRL ) = %ld\n", VIDIOC_S_CTRL );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_TUNER ) = %ld\n", VIDIOC_G_TUNER );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_TUNER ) = %ld\n", VIDIOC_S_TUNER );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_AUDIO ) = %ld\n", VIDIOC_G_AUDIO );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_AUDIO ) = %ld\n", VIDIOC_S_AUDIO );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYCTRL ) = %ld\n", VIDIOC_QUERYCTRL );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYMENU ) = %ld\n", VIDIOC_QUERYMENU );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_INPUT ) = %ld\n", VIDIOC_G_INPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_INPUT ) = %ld\n", VIDIOC_S_INPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_EDID ) = %ld\n", VIDIOC_G_EDID );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_EDID ) = %ld\n", VIDIOC_S_EDID );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_OUTPUT ) = %ld\n", VIDIOC_G_OUTPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_OUTPUT ) = %ld\n", VIDIOC_S_OUTPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMOUTPUT ) = %ld\n", VIDIOC_ENUMOUTPUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_AUDOUT ) = %ld\n", VIDIOC_G_AUDOUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_AUDOUT ) = %ld\n", VIDIOC_S_AUDOUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_MODULATOR ) = %ld\n", VIDIOC_G_MODULATOR );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_MODULATOR ) = %ld\n", VIDIOC_S_MODULATOR );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_FREQUENCY ) = %ld\n", VIDIOC_G_FREQUENCY );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_FREQUENCY ) = %ld\n", VIDIOC_S_FREQUENCY );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_CROPCAP ) = %ld\n", VIDIOC_CROPCAP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_CROP ) = %ld\n", VIDIOC_G_CROP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_CROP ) = %ld\n", VIDIOC_S_CROP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_JPEGCOMP ) = %ld\n", VIDIOC_G_JPEGCOMP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_JPEGCOMP ) = %ld\n", VIDIOC_S_JPEGCOMP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERYSTD ) = %ld\n", VIDIOC_QUERYSTD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_FMT ) = %ld\n", VIDIOC_TRY_FMT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMAUDIO ) = %ld\n", VIDIOC_ENUMAUDIO );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUMAUDOUT ) = %ld\n", VIDIOC_ENUMAUDOUT );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_PRIORITY ) = %ld\n", VIDIOC_G_PRIORITY );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_PRIORITY ) = %ld\n", VIDIOC_S_PRIORITY );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_SLICED_VBI_CAP ) = %ld\n", VIDIOC_G_SLICED_VBI_CAP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_LOG_STATUS ) = %ld\n", VIDIOC_LOG_STATUS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_EXT_CTRLS ) = %ld\n", VIDIOC_G_EXT_CTRLS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_EXT_CTRLS ) = %ld\n", VIDIOC_S_EXT_CTRLS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_EXT_CTRLS ) = %ld\n", VIDIOC_TRY_EXT_CTRLS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FRAMESIZES ) = %ld\n", VIDIOC_ENUM_FRAMESIZES );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FRAMEINTERVALS) = %ld\n", VIDIOC_ENUM_FRAMEINTERVALS);
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_ENC_INDEX ) = %ld\n", VIDIOC_G_ENC_INDEX );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENCODER_CMD ) = %ld\n", VIDIOC_ENCODER_CMD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_ENCODER_CMD ) = %ld\n", VIDIOC_TRY_ENCODER_CMD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_HW_FREQ_SEEK ) = %ld\n", VIDIOC_S_HW_FREQ_SEEK );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_CREATE_BUFS ) = %ld\n", VIDIOC_CREATE_BUFS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_PREPARE_BUF ) = %ld\n", VIDIOC_PREPARE_BUF );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_G_SELECTION ) = %ld\n", VIDIOC_G_SELECTION );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_S_SELECTION ) = %ld\n", VIDIOC_S_SELECTION );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_DECODER_CMD ) = %ld\n", VIDIOC_DECODER_CMD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_TRY_DECODER_CMD ) = %ld\n", VIDIOC_TRY_DECODER_CMD );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_DV_TIMINGS ) = %ld\n", VIDIOC_ENUM_DV_TIMINGS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERY_DV_TIMINGS ) = %ld\n", VIDIOC_QUERY_DV_TIMINGS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_DV_TIMINGS_CAP ) = %ld\n", VIDIOC_DV_TIMINGS_CAP );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_ENUM_FREQ_BANDS ) = %ld\n", VIDIOC_ENUM_FREQ_BANDS );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_DBG_G_CHIP_INFO ) = %ld\n", VIDIOC_DBG_G_CHIP_INFO );
+ printk("cyyanl v4l2 ioctl cmd(VIDIOC_QUERY_EXT_CTRL ) = %ld\n", VIDIOC_QUERY_EXT_CTRL );
+ printk("cyyanl v4l2 ioctl cmd(BASE_VIDIOC_PRIVATE ) = %ld\n", BASE_VIDIOC_PRIVATE );
+ printk("*************************************************************************************");
+}
+
static int __init uvc_init(void)
{
int ret;
-
+ printk("cyyanl enter uvc_init\n");
+ cyyanl_printk_cmd();
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
```
Printing result comparison: **VIDIOC_QUERYBUF** is **0xc0505609** in user state and **0xc0585609** in kernel state. There are several other inconsistent command codes.
![](figures/isoftstone/yangfan-print-01.png)
Analyze the cause for command code inconsistency. First, find the differences during compilation of **VIDIOC_QUERYBUF** in user state and kernel state.
User state: kernel/linux/patches/linux-5.10/prebuilts/usr/include/linux/videodev2.h +1358
Kernel state: kernel/linux/linux-4.19/include/uapi/linux/videodev2.h +2361
The possible cause is that **VIDIOC_QUERYBUF** inconsistency is caused by header file differences. (Currently, whether the issue can be solved by using linux-4.19 is not verified.) Seek help from the customer and check whether there are similar issues in OpenHarmony.
A similar issue is found: https://gitee.com/openharmony/drivers_peripheral/issues/I4NI4M?from=project-issue
Now, there are two debugging ideas:
1. Unify **VIDIOC_QUERYBUF** in user state with that in kernel state.
Solution: Change **0xc0505609** in user state to **0xc0585609** in kernel state.
Modification: drivers/peripheral
```diff
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
index d7dd15f..f7254b4 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp
buf.m.planes = planes;
buf.length = 1;
}
- CAMERA_LOGD("V4L2_MEMORY_USERPTR Print the cnt: %{public}d\n", buf.index);
-
- if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
- CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno));
+ if (ioctl(fd, /*VIDIOC_QUERYBUF*/0xc0585609, &buf) < 0) {
+ CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed.\n");
return RC_ERROR;
}
```
Run **v4l2_main** again. Result: Signal 4 reports an error.
Cause analysis: **ioctl(fd, /*****VIDIOC_QUERYBUF*****/0xc0585609, &buf)** is successful. According to Signal 4 analysis, the error is possibly still caused by the command codes of **cmd**.
This idea can solve the current issue, but it will cause a new Signal 4 error.
```
INFO:main test:allocating display buffer memory
INFO:main test:do_mmap: pmem mmap fd 5 ptr 0xf7508000 len 2457600
INFO:V4L2OpenDevice /dev/video10
INFO:V4L2ReqBuffers buffCont 4
INFO:Creatbuffer frameSpec->buffer index == 0
INFO:V4L2AllocBuffer
Signal 4
```
2. Analyze the root cause of command code inconsistency and make modifications.
Cause analysis: The size of the structure varies according to the 32-bit and 64-bit compilation, which affects the value of **VIDIOC_QUERYBUF**.
Modification: Modify the timestamp definition of the **v4l2_buffer** structure compiled in user state, and replace the header file **#include <linux/videodev2.h>** compiled in user state with
the modified **videodev2.h** file, that is, **#include "videodev2.h"**. (The actual operation is to copy the modified **videodev2.h** file to the **v4l2_main** compilation directory.)
kernel/linux/linux-4.19/
```diff
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index ba548d7f0..b0fb48f65 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -958,13 +958,20 @@ struct v4l2_plane {
* Contains data exchanged by application and driver using one of the Streaming
* I/O methods.
*/
+
+struct timeval_user {
+ long tv_sec;
+ long tv_usec;
+};
+
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
- struct timeval timestamp;
+ //struct timeval timestamp;
+ struct timeval_user timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
```
drivers/peripheral
```diff
diff --git a/camera/hal/adapter/chipset/rpi/rpi3/device/camera/src/driver_adapter/main_test/project_v4l2_main.h b/camera/hal/adapter/chipset/rpi/rpi3/device/camera/src/driver_adapter/main_test/project_v4l2_main.h
index 00ddea7..962ebc3 100755
--- a/camera/hal/adapter/chipset/rpi/rpi3/device/camera/src/driver_adapter/main_test/project_v4l2_main.h
+++ b/camera/hal/adapter/chipset/rpi/rpi3/device/camera/src/driver_adapter/main_test/project_v4l2_main.h
@@ -15,7 +15,8 @@
#ifndef HOS_CAMERA_PROJET_HARDWARE_H
#define HOS_CAMERA_PROJET_HARDWARE_H
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
namespace OHOS::Camera {
#define PREVIEW_PIXEL_FORMAT V4L2_PIX_FMT_YUV420
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_buffer.h b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_buffer.h
index 6f45882..a8d6819 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_buffer.h
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_buffer.h
@@ -19,7 +19,8 @@
#include <mutex>
#include <map>
#include <cstring>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <sys/ioctl.h>
#include "v4l2_common.h"
#if defined(V4L2_UTEST) || defined (V4L2_MAIN_TEST)
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_control.h b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_control.h
index 5b93f36..05191a7 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_control.h
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_control.h
@@ -16,7 +16,8 @@
#ifndef HOS_CAMERA_V4L2_CONTROL_H
#define HOS_CAMERA_V4L2_CONTROL_H
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <errno.h>
#include <sys/ioctl.h>
#include "v4l2_common.h"
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_dev.h b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_dev.h
index 10dc9b4..e3b3056 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_dev.h
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_dev.h
@@ -19,7 +19,8 @@
#include <mutex>
#include <thread>
#include <vector>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_fileformat.h b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_fileformat.h
index de892e9..44bb1b4 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_fileformat.h
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_fileformat.h
@@ -19,7 +19,8 @@
#include <vector>
#include <cstring>
#include <fcntl.h>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_uvc.h b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_uvc.h
index 1a62f37..96c70aa 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_uvc.h
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/include/v4l2_uvc.h
@@ -18,7 +18,8 @@
#include <thread>
#include <fcntl.h>
#include <linux/netlink.h>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/netlink.h>zz
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
index b351f49..5483b85 100755
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
@@ -22,7 +22,8 @@
#include <sys/mman.h>
#include <sys/time.h>
#include <linux/fb.h>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include "securec.h"
#include "v4l2_uvc.h"
#include "v4l2_dev.h"
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_stream.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_stream.cpp
index 778cf05..96618be 100644
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_stream.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/src/v4l2_stream.cpp
@@ -14,7 +14,8 @@
*/
#include <cstring>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <sys/ioctl.h>
#include "v4l2_stream.h"
diff --git a/camera/hal/test/v4l2/include/test_display.h b/camera/hal/test/v4l2/include/test_display.h
index d437e26..8e5205e 100644
--- a/camera/hal/test/v4l2/include/test_display.h
+++ b/camera/hal/test/v4l2/include/test_display.h
@@ -44,7 +44,8 @@
#include <errno.h>
#include <getopt.h>
#include <linux/fb.h>
-#include <linux/videodev2.h>
+//#include <linux/videodev2.h>
+#include "videodev2.h"
#include <mutex>
#include <pthread.h>
#include <stdlib.h>
@@ -138,4 +139,4 @@ public:
void StartCapture(int streamId, int captureId, bool shutterCallback, bool isStreaming);
float calTime(struct timeval start, struct timeval end);
};
-#endif
\ No newline at end of file
+#endif
```
Compile **v4l2_main** again and execute it. No error is recorded in logs. The HDMI screen displays the preview image.
According to the entire debugging process, the camera supports the YUYV and MJPEG formats. By default, preview in **v4l2_main** uses the YUYV format. Change the format to MJEPG and try again.
Modification:
```diff
diff --git a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
index b351f49..5483b85 100755
--- a/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
+++ b/camera/hal/adapter/platform/v4l2/src/driver_adapter/main_test/v4l2_main.cpp
@@ -394,7 +395,9 @@ void V4L2SetDeviceFormat(DeviceFormat& format, const std::string devname)
if (devname == "uvcvideo" || devname == "uvcvideo1") {
if (g_isPreviewOnUvc) {
- format.fmtdesc.pixelformat = V4L2_PIX_FMT_YUYV;
+ CAMERA_LOGD("cyyanl enter V4L2SetDeviceFormat : g_isPreviewOnUvc\n");
+ //format.fmtdesc.pixelformat = V4L2_PIX_FMT_YUYV;
+ format.fmtdesc.pixelformat = V4L2_PIX_FMT_MJPEG;
format.fmtdesc.width = width;
format.fmtdesc.height = height;
}
```
Execution result of **v4l2_main**: The display is abnormal. Currently, do not analyze the MJPEG format.
![](figures/isoftstone/yangfan-picture-v4l2_main.png)
So far, **v4l2_main** runs properly and images can be previewed. (The abnormal color of the preview image is related to the screen display format and does not need to be modified currently.) Next, debug photographing and video recording.
After **v4l2_main** is executed, enter **u** to preview the UVC, run **c** to take photos, and then run **v** to record videos. The photo and video files are generated in the current execution path.
```
# ls -l *.jpeg
-rwxrw-rw- 1 root 29034400 614400 2013-01-18 15:20 UVC0.jpeg
-rwxrw-rw- 1 root 29034400 614400 2013-01-18 15:20 UVC1.jpeg
-rwxrw-rw- 1 root 29034400 614400 2013-01-18 15:20 UVC2.jpeg
-rwxrw-rw- 1 root 29034400 614400 2013-01-18 15:20 UVC3.jpeg
#
# ls -l *.h264
-rwxrw-rw- 1 root 29034400 85401600 2013-01-18 15:20 uvc.h264
```
Export the preceding files to the PC and check them.
image **uvc0.jpeg**
![](figures/isoftstone/yangfan-picture-uvc0.png)
The video file **uvc.h264** can be viewed on the mobile phone and played properly.
### Appendixes
```c
/**
* struct v4l2_buffer - video buffer info
* @index: id number of the buffer
* @type: enum v4l2_buf_type; buffer type (type == *_MPLANE for
* multiplanar buffers);
* @bytesused: number of bytes occupied by data in the buffer (payload);
* unused (set to 0) for multiplanar buffers
* @flags: buffer informational flags
* @field: enum v4l2_field; field order of the image in the buffer
* @timestamp: frame timestamp
* @timecode: frame timecode
* @sequence: sequence count of this frame
* @memory: enum v4l2_memory; the method, in which the actual video data is
* passed
* @offset: for non-multiplanar buffers with memory == V4L2_MEMORY_MMAP;
* offset from the start of the device memory for this plane,
* (or a "cookie" that should be passed to mmap() as offset)
* @userptr: for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR;
* a userspace pointer pointing to this buffer
* @fd: for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF;
* a userspace file descriptor associated with this buffer
* @planes: for multiplanar buffers; userspace pointer to the array of plane
* info structs for this buffer
* @length: size in bytes of the buffer (NOT its payload) for single-plane
* buffers (when type != *_MPLANE); number of elements in the
* planes array for multi-plane buffers
*
* Contains data exchanged by application and driver using one of the Streaming
* I/O methods.
*/
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
__u32 reserved;
}
```
## TP
### TP Driver Model
This model mainly defines and implements the following types of Hardware Driver Interfaces (HDIs) of the input module, allowing upper-layer input services to perform operations for the input devices:
- **Input Manager**: manages input devices, including enabling and disabling input devices and obtaining the device list.
- **Input Reporter**: reports input events, including registering and unregistering data reporting callbacks.
- **Input Controller**: controls input devices, including obtaining the device information and device type, and setting power status.
**Figure 1** HDI architecture of the input module
![dayu200-tp-01.png](figures/dayu200/dayu200-tp-01.png)
The source code directory structure is as follows:
```
/drivers/peripheral/input
├── hal # HAL code
│ └── include # HAL header files
│ └── src # HAL code implementation
├── interfaces # Driver capability APIs provided for upper-layer services
│ └── include # APIs exposed
├── test # Test code
│ └── unittest # Unit test code
```
For details, see [README](https://gitee.com/openharmony/drivers_peripheral/blob/master/input/README_zh.md) of the input subsystem.
### TP HDF Driver Adaptation
#### Configuration Adaptation
Disable the Linux TP driver by disabling **TOUCHSCREEN_GT9XX** in the **<rockchip_linux_defconfig>** file. Specifically, change **CONFIG_TOUCHSCREEN_GT9XX=y** as follows:
```makefile
# CONFIG_TOUCHSCREEN_GT9XX is not set
```
#### Driver Adaptation
1. Modify content in **vendor/rockchip/rk3399/hdf_config/khdf/device_info/device_info.hcs** as follows or add the following content to the file:
```json
device_touch_chip :: device {
device0 :: deviceNode {
policy = 0;
priority = 130;
preload = 0;
permission = 0660;
moduleName = "HDF_TOUCH_GT911";
serviceName = "hdf_touch_gt911_service";
deviceMatchAttr = "zsj_gt911_5p5";
}
}
```
2. Modify content in **vendor/rockchip/rk3399/hdf_config/khdf/input/input_config.hcs** as follows or add the following content to the file:
```json
busConfig {
// 0:i2c 1:spi
busType = 0; // I2C communication method
busNum = 7; // I2C code of the touch chip corresponding to the CPU
clkGpio = 72; // I/O pin code corresponding to I2C clk
dataGpio = 71; // I/O pin code corresponding to I2C data
i2cClkIomux = [0xFF77E004, 0x2]; // I2C [reuse register corresponding to the clk pin, which is configured to the I2C7_clk function]
i2cDataIomux = [0xFF77E000, 0x8000]; // I2C [reuse register corresponding to the data pin, which is configured to the I2C7_DATA function]
}
pinConfig {
rstGpio = 150; // I/O pin code corresponding to rst of the touch chip
intGpio = 52; // I/O pin code corresponding to int of the touch chip
rstRegCfg = [0xFF77E028, 0x00000000]; // Reuse register corresponding to the rst pin, which is configured to the common I/O port
intRegCfg = [0xFF770018, 0x00000000]; // Reuse register corresponding to the int pin, which is configured to the common I/O port
}
```
#### FAQs
##### How to solve the problem that the portrait and landscape modes are reversed for the touchscreen?
Modify the **ParsePointData** function in **drivers/framework/model/input/driver/touchscreen/touch_gt911.c** to exchange the position of **x** and **y**.
##### Modify the **InputPinMuxCfg** function:
```c
static int32_t InputPinMuxCfg(uint32_t regAddr, int32_t regSize, uint32_t regValue)
{
uint8_t *base = NULL;
uint32_t data = 0;
if (regAddr == 0) {
HDF_LOGE("%s: regAddr invalid", __func__);
return HDF_FAILURE;
}
HDF_LOGE("regAddr = 0x%x, regSize = 0x%x", regAddr, regSize);
base = OsalIoRemap(regAddr, regSize);
if (base == NULL) {
HDF_LOGE("%s: ioremap failed", __func__);
return HDF_FAILURE;
}
// Read and save the original configurations before writing new configurations to the register. Modify the original other pin function configurations.
data = OSAL_READL(base);
data |= regValue;
OSAL_WRITEL(data, base);
OsalIoUnmap((void *)base);
return HDF_SUCCESS;
}
```
##### How to determine the pin GPIOn_KK code of RK3399?
```shell
num = 32n+(K-A)8+X
# GPIO1_B2 = 1 * 32 + (B-A)*8 + 2 = 32 + 8 + 1 = 42
```
| Function | I/O Name | I/O Code|
| ------ | -------- | ------ |
| TP_INT | GPIO1_C4 | 52 |
| TP_RST | GPIO4_C6 | 150 |
| TP_SDA | GPIO2_A7 | 71 |
| TP_SCL | GPIO2_B0 | 72 |
##### How to configure the pin GPIOn_KX reuse function of RK3399?
1. Find the base address of the GPIO reuse function register, which is **FF77_0000**.
2. Find the register offset of the GPIOn_K reuse function.
3. Determine the GPIOn_K reuse function register by **adding the base address and offset**.
4. Write the reuse function of the corresponding pin based on the reuse function register introduction.
| Function | I/O Name | Reuse Register Address| Control Bit| Value |
| ------ | -------- | -------------- | ------ | ------------------- |
| TP_INT | GPIO1_C4 | 0xFF770018 | 9:8 | 2'b00: GPIO |
| TP_RST | GPIO4_C6 | 0xFF77E028 | 13:12 | 2'b00: GPIO |
| TP_SDA | GPIO2_A7 | 0xFF77E000 | 15:14 | 2'b10: i2c7nfc_sda|
| TP_SCL | GPIO2_B0 | 0xFF77E004 | 1:0 | 2'b10: i2c7nfc_scl|
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册