OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(下)

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
  • OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案例
  • OpenHarmony(鸿蒙南向开发)——Combo解决方案之W800芯片移植案例
  • OpenHarmony(鸿蒙南向开发)——小型系统STM32MP1芯片移植案例
  • OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(上)
  • OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(下)
  • OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(上)
  • OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(下)
  • 持续更新中……

OpenHarmony Camera HDF驱动框架概述

OpenHarmony Camera驱动模型结构

  • HDI Implementation:对上实现HDI接口,向下调用框架层的接口,完成HDI接口任务的转发。
  • Buffer Manager:屏蔽不同内存管理的差异,为子系统提供统一的操作接口,同时提供buffer轮转的功能。
  • Pipeline Core:解析HCS配置完成pipeline的搭建,调度pipeline中的各个node完成流的处理
  • Device Manager:通过调用底层硬件适配层接口,实现查询控制底层设备、枚举监听底层设备的功能
  • Platform Adaption:屏蔽硬件差异,为Device Manager提供统一的操作底层硬件的能力

CameraService 进程

CameraService源码目录为:foundation/multimedia/camera_standard,camera app通过camera service与hal层进行交互

├── bundle.json
├── figures
├── frameworks                            camera frameworks部分,支持js和native转换
│   ├── js
│   └── native
├── hisysevent.yaml
├── interfaces                            CameraService接口
│   ├── inner_api
│   └── kits
├── LICENSE
├── OAT.xml
├── README.md
├── README_zh.md
├── sa_profile                            CameraService进程加载配置文件
│   ├── 3008.xml
│   └── BUILD.gn
└── services                            CameraService启动相关├── camera_service└── etc

CameraService启动入口在foundation/multimedia/camera_standard/services/etc/camera_service.cfg进行启动配置

"services" : [{"name" : "camera_service","path" : ["/system/bin/sa_main", "/system/profile/camera_service.xml"],"uid" : "cameraserver","gid" : ["system", "shell"],"secon" : "u:r:camera_service:s0"}
]

Camera驱动框架介绍

Camera驱动整体架构

camera驱动源码分布

Camera 驱动框架所在的仓为:drivers_peripheral,源码目录为:“drivers/peripheral/camera”。

├── bundle.json
├── figures
│   ├── Camera模块驱动模型.png
│   └── logic-view-of-modules-related-to-this-repository_zh.png
├── hal
│   ├── adapter                    #平台适配层,适配平台
│   ├── buffer_manager
│   ├── BUILD.gn                    #Camera驱动框架构建入口
│   ├── camera.gni                #定义组件所使用的全局变量
│   ├── device_manager
│   ├── hdi_impl
│   ├── include
│   ├── init                        #demo sample
│   ├── pipeline_core
│   ├── test                        #测试代码
│   └── utils
├── hal_c                        #为海思平台提供专用C接口
│   ├── BUILD.gn
│   ├── camera.gni
│   ├── hdi_cif
│   └── include
├── interfaces                    #HDI接口
│   ├── hdi_ipc
│   ├── hdi_passthrough
│   ├── include
│   └── metadata
└── README_zh.md
Camera Host HDF驱动

配置文件

Camera Host HDF配置相关在“vendor/kaihong/khdvk_3566b/hdf_config/uhdf/device_info.hcs”

    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";}}...}    

其中主要参数说明如下:

  • hostName = “camera_host”:camera host节点,该节点为一个独立进程,如果需要独立进程,新增属于自己的host节点
  • policy = 2:服务发布策略,Camera使用HDI服务,需设置为2
  • moduleName:camera host驱动实现库名
  • serviceName:服务名称,请保持全局唯一性,后面HDF Manager会根据这个名称拉起camera hdf

camera host服务启动 camera host 服务由hdf_devhost启动,配置文件存放于vendor/etc/init/hdf_devhost.cfg

    {"name" : "camera_host","path" : ["/vendor/bin/hdf_devhost", "8", "camera_host"],"uid" : "camera_host","gid" : ["camera_host"],"caps" : ["DAC_OVERRIDE", "DAC_READ_SEARCH"],"secon" : "u:r:camera_host:s0"}

Camera host驱动实现 代码路径:drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp

驱动入口结构体,后面将该结构体注册进HDF框架中

struct HdfDriverEntry g_cameraHostDriverEntry = {.moduleVersion = 1,.moduleName = "camera_service",.Bind = HdfCameraHostDriverBind,.Init = HdfCameraHostDriverInit,.Release = HdfCameraHostDriverRelease,
};

消息发布服务

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);
}

参数说明:

client:HdfDeviceIoClient设备句柄
cmdId:请求消息命令字
data:其他服务或者IO请求数据
reply:存储返回消息内容数据

绑定服务:初始化设备服务对象和资源对象

int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject)
{...hdfCameraService->ioservice.Dispatch = CameraServiceDispatch;hdfCameraService->ioservice.Open = nullptr;hdfCameraService->ioservice.Release = nullptr;hdfCameraService->instance = CameraHostStubInstance();deviceObject->service = &hdfCameraService->ioservice;return HDF_SUCCESS;
}

相关说明:

hdfCameraService->ioservice.Dispatch:注册消息分发服务接口
hdfCameraService->instance:创建camerahost实例

驱动初始化函数: 探测并初始化驱动程序

int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject)
{return HDF_SUCCESS;
}

驱动资源释放函数 : 如已经绑定的设备服务对象

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;}OsalMemFree(hdfCameraService);
}

设备创建不成功,关闭服务,释放相关资源

DeviceManager

创建SensorManager、FlashManager、ISPManager管理相应的设备。

SensorManager sensor Manager结构如下
class SensorManager : public IManager {
public:SensorManager();explicit SensorManager(ManagerId managerId);virtual ~SensorManager();RetCode CreateController(ControllerId controllerId, std::string hardwareName);RetCode DestroyController(ControllerId controllerId, std::string hardwareName);std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);void Configure(std::shared_ptr<CameraMetadata> meta);RetCode Start(std::string hardwareName, int buffCont, DeviceFormat& format);RetCode Stop(std::string hardwareName);RetCode PowerUp(std::string hardwareName);RetCode PowerDown(std::string hardwareName);std::shared_ptr<ISensor> GetSensor(std::string sensorName);RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer, std::string hardwareName);void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName);void SetNodeCallBack(const NodeBufferCb cb, std::string hardwareName);void SetMetaDataCallBack(const MetaDataCb cb, std::string hardwareName);private:bool CheckCameraIdList(std::string hardwareName);std::vector<std::shared_ptr<SensorController>> sensorList_;
};
} 

PowerUp为上电接口,OpenCamera时调用此接口进行设备上电操作

PowerDown为下电接口,CloseCamera时调用此接口进行设备下电操作

Configures为Metadata下发接口,如需设置metadata参数到硬件设备,可实现此接口进行解析及下发

Start为硬件模块使能接口,pipeline中的各个node进行使能的时候,会去调用,可根据需要定义实现,比如sensor的起流操作就可放在此处进行实现,Stop和Start为相反操作,可实现停流操作

SendFrameBuffer为每一帧buffer下发接口,所有和驱动进行buffer交互的操作,都是通过此接口进行的

SetNodeCallBack为pipeline,通过此接口将buffer回调函数设置到devicemanager

SetMetaDataCallBack为metadata回调接口,通过此接口将从底层获取的metadata数据上报给上层

BufferCallback上传每一帧已填充数据buffer的接口,通过此接口将buffer上报给pipeline

SetAbilityMetaDataTag设置需要从底层获取哪些类型的metadata数据,因为框架支持单独获取某一类型或多类型的硬件设备信息,所以可以通过此接口,获取想要的metadata数据

Camera Sensor Controller结构如下:

class SensorController : public IController {
public:SensorController();explicit SensorController(std::string hardwareName);virtual ~SensorController();RetCode Init();RetCode PowerUp();RetCode PowerDown();RetCode Configure(std::shared_ptr<CameraMetadata> meta);RetCode Start(int buffCont, DeviceFormat& format);RetCode Stop();...void SetMetaDataCallBack(MetaDataCb cb) override;void BufferCallback(std::shared_ptr<FrameSpec> buffer);void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta);RetCode Flush(int32_t streamId);...};

PowerUp下发命令给v4l2 dev去操作实际设备进行上电操作 PowerDown下发命令给v4l2 dev去操作实际设备进行下电操作 同理其他操作参考SensorManager. ####FlashManager Flash Manger结构如下:

class FlashManager : public IManager {
public:FlashManager();explicit FlashManager(ManagerId managerId);virtual ~FlashManager();RetCode CreateController(ControllerId controllerId, std::string hardwareName);std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);RetCode PowerUp(std::string hardwareName);RetCode PowerDown(std::string hardwareName);void Configure(std::shared_ptr<CameraMetadata> meta);void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName){(void)abilityMetaDataTag;(void)hardwareName;return;}RetCode SetFlashlight(FlashMode flashMode, bool enable, std::string hardwareName);
private:bool CheckCameraIdList(std::string hardwareName);std::vector<std::shared_ptr<FlashController>> flashList_;
}

Flash controller结构如下:

class FlashController : public IController {
public:FlashController();explicit FlashController(std::string hardwareName);virtual ~FlashController();RetCode Init();RetCode PowerUp();RetCode PowerDown();RetCode Configure(std::shared_ptr<CameraMetadata> meta){(void)meta;return RC_OK;}RetCode SetFlashlight(FlashMode flashMode, bool enable);void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta);
private:std::mutex startVolock_;bool startVoState_ = false;
}
ISPManager ISP Manager结构如下
class IspManager : public IManager {
public:IspManager();explicit IspManager(ManagerId managerId);virtual ~IspManager();RetCode CreateController(ControllerId controllerId, std::string hardwareName);std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);void Configure(std::shared_ptr<CameraMetadata> meta);RetCode Start(std::string hardwareName);RetCode Stop(std::string hardwareName);RetCode PowerUp(std::string hardwareName);RetCode PowerDown(std::string hardwareName);void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName){(void)abilityMetaDataTag;(void)hardwareName;return;}
private:bool CheckCameraIdList(std::string hardwareName);std::vector<std::shared_ptr<IspController>> ispList_;
};

ISP controller结构如下

class IspController : public IController {
public:IspController();explicit IspController(std::string hardwareName);virtual ~IspController();RetCode Init();RetCode Configure(std::shared_ptr<CameraMetadata> meta);RetCode PowerUp();RetCode PowerDown();RetCode Stop();RetCode Start();void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag){(void)abilityMetaDataTag;return;}RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta){(void)meta;return RC_OK;}
private:std::mutex startIsplock_;bool startIspState_ = false;
}

PlatForm Adapter

这部分通过V4l2框架对video设备进行管理,包括对相应设备的打开、启动/关闭数据流、设置/获取图像格式等等

源代码 V4l2 Adapter 源码位于driver/peripheral/camera/hal/adapter/platform/v4l2/src/driver_adapter 部分关键函数如下:
class HosV4L2Dev {
public:...RetCode start(const std::string& cameraID);RetCode stop(const std::string& cameraID);RetCode CreatBuffer(const std::string& cameraID, const std::shared_ptr<FrameSpec>& frameSpec);RetCode StartStream(const std::string& cameraID);RetCode QueueBuffer(const std::string& cameraID, const std::shared_ptr<FrameSpec>& frameSpec);RetCode ReleaseBuffers(const std::string& cameraID);RetCode StopStream(const std::string& cameraID);RetCode SetCallback(BufCallback cb);static RetCode Init(std::vector<std::string>& cameraIDs);static std::map<std::string, std::string> deviceMatch;private:std::shared_ptr<HosV4L2Buffers> myBuffers_ = nullptr;std::shared_ptr<HosV4L2Streams> myStreams_ = nullptr;std::shared_ptr<HosFileFormat> myFileFormat_ = nullptr;std::shared_ptr<HosV4L2Control> myControl_ = nullptr;...enum v4l2_memory memoryType_ = V4L2_MEMORY_USERPTR;enum v4l2_buf_type bufferType_ = V4L2_BUF_TYPE_PRIVATE;
};
PipeLineCore

这个模块解析HCS配置完成pipeline的搭建,调度pipeline中的各个node完成流的处理

IPP算法加载 IPP是pipeline 中的一个算法插件模块,由ippnode加载,对流数据进行算法处理,ippnode支持同时多路数据输入,只支持一路数据输出。

vendor/kaihong/khdvk_3566b/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:算法插件名称 description:描述算法插件的功能 path:算法插件所在路径 mode:算法插件所运行的模式

算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据需要进行扩展。

  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};

算法插件由device/board/kaihong/khdvk_3566b/camera/BUILD.gn文件进行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用:

typedef struct IppAlgoFunc {int (*Init)(IppAlgoMeta* meta);int (*Start)();int (*Flush)();int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta);int (*Stop)();
} IppAlgoFunc;

Init : 算法插件初始化接口,在起流前被ippnode调用,其中IppAlgoMeta定义在ipp_algo.h 中,为ippnode和算法插件提供非图像数据的传递通道,如当前运行的场景,算法处理后输出的人脸坐标等等,可根据实际需求进行扩展

Start:开始接口,起流时被ippnode调用

Flush:刷新数据的接口,停流之前被ippnode调用。此接口被调用时,算法插件需尽可能快地停止处理

Process: 数据处理接口,每帧数据都通过此接口输入至算法插件进行处理。inBuffer是一组输入buffer,inBufferCount是输入buffer的个数,outBuffer是输出buffer,meta是算法处理时产生的非图像数据,IppAlgoBuffer在ipp_algo.h中定义

Stop:停止处理接口,停流时被ippnode调用

下边代码中的id指的是和ippnode对应的port口id,比如inBuffer[0]的id为0,则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空,此时其中一个输入buffer 被ippnode作为输出buffer传递到下个node,inBuffer至少有一个buffer不为空。输入输出buffer 由pipeline配置决定。 比如在普通预览场景无算法处理且只有一路拍照数据传递到ippnode的情况下,输入buffer只有一个,输出buffer为空,即对于算法插件输入buffer 进行了透传; 比如算法插件进行两路预览图像数据进行合并的场景,第一路buffer需要预览送显示。把第二路图像拷贝到第一路的buffer即可,此时输入buffer有两个,输出buffer为空; 比如在算法插件中进行预览数据格式转换的场景,yuv转换为RGBA,那么只有一个yuv格式的输入buffer的情况下无法完成RGBA格式buffer的输出,此时需要一个新的buffer,那么ippnode的输出port口buffer作为outBuffer传递到算法插件。也即输入buffer只有一个,输出buffer也有一个。

typedef struct IppAlgoBuffer {void* addr;unsigned int width;unsigned int height;unsigned int stride;unsigned int size;int id;} IppAlgoBuffer;

camera HDF驱动适配 ###rk3566rp camera HDF驱动编译选项添加 camera HDF驱动的配置位于drivers/peripheral/camera/hal/camera.gni中,内容如下:

if (defined(ohos_lite)) {import("//build/lite/config/component/lite_component.gni")import("//device/board/hisilicon/hispark_taurus/device.gni")
} else {import("//build/ohos.gni")import("//vendor/$product_company/$product_name/product.gni")
}camera_path = "//drivers/peripheral/camera/hal"
current_path = "."
enable_camera_device_utest = falseuse_hitrace = false
if (use_hitrace) {defines += [ "HITRACE_LOG_ENABLED" ]
}if (defined(ohos_lite)) {defines += [ "CAMERA_BUILT_ON_OHOS_LITE" ]
}

根据编译配置可以找到对应的vendor/kaihong/khdvk_3566b/product.gni,从中获取到实际的文件是device/board/kaihong/khdvk_3566b/device.gni,后面修改入口基于这里

soc_company = "rockchip"
soc_name = "rk3566"import("//device/soc/${soc_company}/${soc_name}/soc.gni")import("//build/ohos.gni")
if (!defined(defines)) {defines = []
}
product_config_path = "//vendor/${product_company}/${device_name}"
board_camera_path = "//device/board/${product_company}/khdvk_3566b/camera"camera_product_name_path = "//vendor/${product_company}/${device_name}"
camera_device_name_path = "//device/board/${product_company}/khdvk_3566b"
is_support_v4l2 = true
if (is_support_v4l2) {is_support_mpi = falsedefines += [ "SUPPORT_V4L2" ]chipset_build_deps = "$camera_device_name_path/camera:chipset_build"camera_device_manager_deps = "$camera_device_name_path/camera/device_manager:camera_device_manager"camera_pipeline_core_deps = "$camera_device_name_path/camera/pipeline_core:camera_pipeline_core"
}

最终这里的配置文件里的参数将被drivers/peripheral/camera/hal/BUILD.gn使用。 ###HCS配置文件介绍 camera的配置文件位于vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/

目录结构如下:

├── hdi_impl
│   ├── camera_host_config.hcs
└── pipeline_core├── config.hcs├── ipp_algo_config.hcs└── params.hcs

Camera所有配置文件使用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS解析接口解析HCB文件,获取配置文件中的信息。

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_host_config.hcs:配置当前camera支持的能力集,物理/逻辑Camera配置、能力配置,此处的物理/逻辑Camera配置,需要在hal内部使用,逻辑Camera及能力配置需要上报给上层,这里需要根据设备实际支持的属性进行相应的修改。 这里的键值对参考文件drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h

   ability_01 :: ability {logicCameraId = "lcam001";physicsCameraIds = ["CAMERA_FIRST","CAMERA_SECOND"];metadata {aeAvailableAntiBandingModes = ["OHOS_CAMERA_AE_ANTIBANDING_MODE_OFF"];aeAvailableModes = ["OHOS_CAMERA_AE_MODE_OFF"];availableFpsRange = [30, 30];cameraPosition = "OHOS_CAMERA_POSITION_FRONT";cameraType = "OHOS_CAMERA_TYPE_WIDE_ANGLE";cameraConnectionType ="OHOS_CAMERA_CONNECTION_TYPE_BUILTIN";faceDetectMaxNum = "10";aeCompensationRange = [0, 0];aeCompensationSteps = [0, 0];availableAwbModes = ["OHOS_CAMERA_AWB_MODE_OFF"];...}

vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/pipeline_core/config.hcs为pipeline的连接方式,按场景划分每一路流由哪些Node组成,其连接方式是怎样的。

normal_preview :: pipeline_spec {name = "normal_preview";v4l2_source :: node_spec {name = "v4l2_source#0";status = "new";out_port_0 :: port_spec {name = "out0";peerPortName = "in0";peerPortNodeName = "sink#0";direction = 1;width = 0;height = 0;format = 0;}}sink :: node_spec {name = "sink#0";status = "new";streamType = "preview";in_port_0 :: port_spec {name = "in0";peerPortName = "out0";peerPortNodeName = "v4l2_source#0";direction = 0;}}
}

上面为preview场景的示例,normal_preview为该场景的名称,source和sink为Node,source为数据数据源端,sink为末端,source为第一个node,node的名称是source#0,status、in/out_port分别为Node状态及输入/输出口的配置。

以in_port_0为例,name = “in0”代表它的输入为“port0”,它的对端为source node的port口out0口,direction为它的源Node和对端Node是否为直连方式。如新添加芯片产品,必须按实际连接方式配置此文件。

新增功能node时需继承NodeBase类,且在cpp文件中注册该node。具体可参考//drivers/peripheral/camera/hal/pipeline_core/nodes/src下已经实现的node。

vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/pipeline_core/param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处需要添加定义。

 root {module = "";template stream_info {id = 0;name = "";}template scene_info {id = 0;name = "";}priview :: stream_info {id = 0;name = "preview";}video :: stream_info {id = 1;name = "video";}snapshot :: stream_info {id = 2;name = "snapshot";}normal :: scene_info {id = 0;name = "normal";}dual :: scene_info {id = 1;name = "dual";}
}

适配过程中遇到的问题 ###camera启动时无法出图排查方向

首先排查camera sensor有没有正常的上下电,初始化序列是否正确。 如果上述都正常,需要到HDF层面,看看设备配置是否正确,具体操作如下: 在ohos系统的上电启动过程中,camera host 服务进程调用InitSensors() -->SensorController::Init()–>HosV4L2Dev::Init()->HosFileFormat::V4L2MatchDevice()既ohos在初始化过程中就会去匹配camera实例与linux 驱动系统中的camera硬件,如果匹配则记录存下cameraId与/dev/videox的关系;所以在camera drive中一般需要修改的地方就是camera hardware的name与linux驱动的/dev/videox关系; 代码如下: cameraIDs向量组内是hdf支持的所以camera 的名称(string); ./drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/include/v4l2_device_manager.h定义的cameraId

std::vector<HardwareConfiguration> hardware = {{CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "bm2835 mmal"},{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"}
};

每个名称应与/dev/videox其中任意一个的capabilities中的driver name是一样的,只有一样的名称才能将hdf的camera name与/dev/videox绑定;

void HosFileFormat::V4L2MatchDevice(std::vector<std::string>& cameraIDs)
{struct stat st = {};char devName[16] = {0};std::string name = DEVICENAMEX;int fd = 0;int rc = 0;for (auto &it : cameraIDs) {for (int i = 0; i < MAXVIDEODEVICE; ++i) {if ((sprintf_s(devName, sizeof(devName), "%s%d", name.c_str(), i)) < 0) {CAMERA_LOGE("%s: sprintf devName failed", __func__);}...rc = V4L2GetCapability(fd, devName, it);if (rc == RC_ERROR) {close(fd);continue;}...}}
}

注意“(cameraId != std::string((char*)cap.driver)”比较cap中的名称是否相同。

RetCode HosFileFormat::V4L2GetCapability(int fd, const std::string& devName, std::string& cameraId)
{struct v4l2_capability cap = {};int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);if (rc < 0) {return RC_ERROR;}if (!(cap.capabilities & V4L2_CAP_STREAMING)) {return RC_ERROR;}if (!((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) || (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))) {return RC_ERROR;}if (cameraId != std::string((char*)cap.driver)) {return RC_ERROR;}std::lock_guard<std::mutex> l(HosV4L2Dev::deviceFdLock_);HosV4L2Dev::deviceMatch.insert(std::make_pair(std::string((char*)cap.driver), devName));...return RC_OK;
}

BT

HCI接口

蓝牙整体硬件架构上分为主机(计算机或MCU)和主机控制器(BT蓝牙模组)两部分;通信遵循主机控制器接口(HCI),通常使用串口进行通信,如下所示:

HCI定义了如何交换命令,事件,异步和同步数据包。异步数据包(ACL)用于数据传输,而同步数据包(SCO)用于带有耳机和免提配置文件的语音。

硬件连接

从RK3566芯片描述中看,该芯片并不没有集成WIFI/蓝牙功能,都需要外接蓝牙芯片才能支持蓝牙功能,这也符合上述逻辑架构。串口使用普通带流控串口即可,一般在原理图中可以看到对应的串口引脚:

可以看到使用的是UART1 M0,在设备树里就要使能对应的串口和pinctrl,同时还可以看到有几个管脚分别做电源和休眠控制。

     wireless_bluetooth: wireless-bluetooth {compatible = "bluetooth-platdata";clocks = <&rk817 1>;clock-names = "ext_clock";//wifi-bt-power-toggle;uart_rts_gpios = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>;pinctrl-names = "default", "rts_gpio";pinctrl-0 = <&uart1m0_rtsn &bt_host_wake_gpio &bt_poweren &bt_host_wake_irq>;pinctrl-1 = <&uart1_gpios>;BT,reset_gpio   = <&gpio0 RK_PC1 GPIO_ACTIVE_HIGH>;BT,wake_gpio   = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;BT,wake_host_irq = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;status = "okay";};wireless-bluetooth {uart1_gpios: uart1-gpios {rockchip,pins = <2 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;};bt_host_wake_irq: bt-host-wake-irq {rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_down>;};bt_host_wake_gpio: bt-host-wake-gpio {rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_down>;};bt_poweren: bt-poweren {rockchip,pins = <0 RK_PC1 RK_FUNC_GPIO &pcfg_pull_down>;};};&uart1 {status = "okay";pinctrl-names = "default";pinctrl-0 = <&uart1m0_xfer &uart1m0_ctsn>;};

蓝牙VENDORLIB适配

vendorlib是什么

vendorlib部署在主机侧,可以认为是主机侧对蓝牙芯片驱动层,屏蔽不同蓝牙芯片的技术细节。从代码层面解读,其主要功能有两个:

1、为协议栈提供蓝牙芯片之间的通道(串口的文件描述符)

2、提供特定芯片的具体控制方法

代码层面解读vendorlib

bt_vendor_lib.h 路径:

foundation/communication/bluetooth/services/bluetooth_standard/hardware/include

该文件定义了协议栈和vendor_lib交互接口,分为两组:

1、 vendorlib实现,协议栈调用

    typedef struct {/**\* Set to sizeof(bt_vndor_interface_t)*/size_t size;/**\* Caller will open the interface and pass in the callback routines\* to the implemenation 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;

协议栈启动时的基本流程如下:

1.1、协议栈动态打开libbt_vendor.z.so,并调用init函数,初始化vendorlib

1.2、协议栈调用op函数,分别调用BT_OP_POWER_ON、BT_OP_HCI_CHANNEL_OPEN、BT_OP_INIT三个opcode;原则上BT_OP_INIT成功后说明芯片初始化完成。

2、协议栈实现,vendorlib调用(回调函数)

    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在BT_OP_INIT完成后调用

alloc/dealloc用于发送HCI消息时申请/释放消息控件

xmit_cb发送HCI Commands

vendor_lib实现的几个重要函数

1、 init函数

   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);}

vendorlib被调用的第一个函数,vendorlib保存好协议栈的callback和mac地址即可。

2、 BT_OP_POWER_ON对应处理

观名知意,这个操作理论上需要拉高电源管脚电平;该函数中使用rfill设备来处理,并没有直接调用驱动拉高电平

   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对应处理

   case BT_OP_HCI_CHANNEL_OPEN: { // BT_VND_OP_USERIAL_OPENint(*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;

userial_vendor_open函数打开串口设备(UART)得到文件描述符(fd),通过op的参数param返回该fd

该串口设备在系统中的名字在vendor下的bluetooth相关目录中的bt_vendor_brcm.h文件定义了,本次开发板上设备为/dev/ttyS1

4、BT_OP_INIT对应处理

该操作码要求对蓝牙芯片进行初始化,具体要进行的处理和蓝牙芯片强相关。以本次调测的AP6xxx芯片为例,初始化过程中主要是下发蓝牙固件。

初始化结束后,必须调用init_cb回调函数(参见bt_vendor_callbacks_t)通知协议栈初始化结果,否则会阻塞协议栈线程导致蓝牙相关功能无法正常使用。协议栈的具体处理如下:

协议栈调用BT_OP_INIT后会等待信号量,该信号量由init_cb函数置位

   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移植问题
1、 vendorlib的so命名

vendorlib必须是libbt_vendor.z.so;因为协议栈打开动态链接库就是这个名字

2、 固件问题

开发时一定要关注芯片固件,有些蓝牙芯片可能无需升级固件,有些则必须升级固件, 不同型号的蓝牙对应固件也不一样;本次AP6xxx适配过程中最开始没有下发固件,导致蓝牙接收信号很差。固件下发时需要注意如下两点:

2.1、对于AP6xxx芯片,因为蓝牙芯片内并没有类似flash存储,要求芯片上下电后必须重新下发,固件要通过BUILD.gn把固件标记为prebuilt_etc

   ohos_prebuilt_etc("BCM43430A1.hcd") {source = "//vendor/kaihong/RK3566-xx/bluetooth/BCM43430A1.hcd"install_images = [ vendor_base_dir ]relative_install_dir = "firmware"part_name = "kaihong_products"install_enable = true}

然后在device/kaihong/build中把固件打包在镜像中

 "//vendor/kaihong/RK3566-xx/bluetooth:libbt_vendor","//vendor/kaihong/RK3566-xx/bluetooth:BCM43430A1.hcd",

2.2、按照芯片本身的要求处理,最好能找到厂商的参考代码;以Broadcom系列芯片为例,其固件下发过程比较复杂,通过一个状态机驱动;共如下9个状态

   / 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};

在收到BT_OP_INIT后初始化状态机,然后发送HCI_REST命令,切换状态为HW_CFG_START;

   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);}}}

收到芯片返回的HCI_RESET完成事件后,继续切换到下一个状态机并发送下一个COMMAND,一直到状态机完成固件下发。

详细实现请参见hw_config_cback函数。

3、 关注系统间接口差异

不同系统的接口可能有一些细微差异,需要重点关注;对比安卓和OHOS的接口,vendorlib调用xmit_cb发送HCI命令的函数定义略有差异

安卓:

   /* define callback of the cmd_xmit_cb*The callback function which HCI lib will call with the return of commandcomplete packet. Vendor lib is responsible for releasing the buffer passedin 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:/**hci command packet transmit callbackVendor lib calls cmd_xmit_cb function in order to send a HCI Commandpacket to BT Controller. *The opcode parameter gives the HCI OpCode (combination of OGF and OCF) ofHCI Command packet. For example, opcode = 0x0c03 for the HCI_RESET commandpacket. */typedef uint8_t (*cmd_xmit_callback)(uint16_t opcode, void* p_buf);

也就是说vendorlib中发送命令后,安卓会直接调用callback通知芯片返回的消息,OHOS则是通过BT_OP_EVENT_CALLBACK操作码(参见bt_opcode_t定义)通知芯片返回的消息;vendorlib需要解析报文中的消息码确认芯片是处理的哪个消息,然后调用对应的处理函数。

   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:\#endifcase 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;

另外,OHOS返回的是发送消息的字节数,<=0为发送失败,和安卓接口的返回值也不同

4、 btvendor日志

在vendor下的bluetooth/include相关目录里的Log.h中定义log文件的保存路径,我们代码里生成文件为/data/btvendor.log。也可以通过wireshark或其它报文分析工具可以看到Host和Controller之间的交互流程,有助于问题分析。

WIFI

整改思路及实现流程
整改思路

主要参考https://mp.weixin.qq.com/s/iiE97pqPtzWIZadcjrQtsw《OpenHarmony HDF WLAN驱动分析与使用》这篇文章,熟悉HDF WLAN的框架以及需要实现的主要接口,包括HDF驱动初始化接口、WLAN控制侧接口集、AP模式接口集、STA模式接口集、网络侧接口集、事件上报接口的实现。接下来熟悉HCS文件的格式以及"HDF WIFI”核心驱动框架的代码启动初始化过程,参考hi3881的代码进行改造。

HDF WiFi框架总体框架图

WLAN驱动架构组成:

ap6256驱动代码流程分析
驱动模块初始化流程分析

Ap6256 是一款SDIO设备WiFi模组驱动,使用标准Linux的SDIO设备驱动。内核模块初始化入口module_init()调用dhd_wifi_platform_load_sdio()函数进行初始化工作,这里调用wifi_platform_set_power()进行GPIO上电,调用dhd_wlan_set_carddetect()进行探测SDIO设备卡,最后调用sdio_register_driver(&bcmsdh_sdmmc_driver);进行SDIO设备驱动的注册,SDIO总线已经检测到WiFi模块设备,根据设备号和厂商号与该设备驱动匹配, 所以立即回调该驱动的bcmsdh_sdmmc_probe()函数,这里进行WiFi模组芯片的初始化工作,最后创建net_device网络接口wlan0,然后注册到Linux内核协议栈中。

下面对其中比较重要的函数进行举例分析:

(1) dhd_bus_register函数,主要实现sdio设备的注册,通过回调dhd_sdio中的相关函数,对wifi模块进行驱动注册等相关操作。

其中函数bcmsdh_register将静态结构体变量dhd_sdio赋值给静态结构体drvinfo,然后通过函数bcmsdh_register_client_driver调用函数sdio_register_driver向系统注册sdio接口驱动。

当sdio设备与sdio总线进行匹配后,会回调函数bcmsdh_sdmmc_probe,函数bcmsdh_sdmmc_probe会进一步回调dhd_sdio结构体中的成员函数dhdsdio_probe。

(2) dhdsdio_probe函数,主要实现net_device对象(wlan0)的创建,以及wireless_dev对象创建,并与net_device对象的成员ieee80211_ptr进行关联,给net_device对象的操作方法成员netdev_ops赋值,最后将net_device对象注册到协议栈中。

  • 创建net_device网络接口wlan0对象

dhd_allocate_if()会调用alloc_etherdev()创建net_device对象,即wlan0网络接口。wl_cfg80211_attach()会创建wireless_dev对象,并将wireless_dev对象赋值给net_device对象的成员ieee80211_ptr。

  • 将wlan0注册到内核协议栈

调用dhd_register_if()函数,这里将net_device_ops操作方法的实例dhd_ops_pri赋值给net_device对象的成员netdev_ops,然后调用register_netdev(net);将net_device对象wlan0网络接口注册到协议栈。

整改代码适配HDF WiFi框架

对于系统WiFi功能的使用,需要实现AP模式、STA模式、P2P三种主流模式,这里使用wpa_supplicant应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现STA模式和P2P模式的功能,使用hostapd应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现AP模式和P2P模式的功能。

Ap6256 WiFi6内核驱动依赖platform能力,主要包括SDIO总线的通讯能力;与用户态通信依赖HDF WiFi框架的能力,在确保上述能力功能正常后,即可开始本次WiFi驱动的HDF适配移植工作。本文档基于已经开源的rk3568开源版代码为基础版本,来进行此次移植。

适配移植ap6256 WiFi驱动涉及到的文件和目录如下:

CONFIG_DRIVERS_HDF_PLATFORM_SDIO=yCONFIG_DRIVERS_HDF_PLATFORM_MMC=yCONFIG_DRIVERS_HDF_WIFI=yCONFIG_DRIVERS_HDF_STORAGE=y

3.2 具体WiFi设备驱动编译控制宏

涉及到wifi设备驱动的编译控制宏位于drivers/adapter/khdf/linux/model/network/wifi/Kconfig中,其中主要涉及到编译控制宏如下:

CONFIG_DRIVERS_HDF_NETDEV_EXT=yCONFIG_AP6XXX_WIFI6_HDF=y

编译控制选项CONFIG_AP6XXX_WIFI6_HDF,内容如下:

config AP6XXX_WIFI6_HDFtristate "support ap6xxx wifi6(80211ax) HDF"depends on DRIVERS_HDF_WIFIselect CFG80211select MAC80211select DRIVERS_HDF_NETDEV_EXThelpThis driver supports wifi6 for ap6xxx HDF chipset.This driver uses the kernel's wireless extensions subsystem.If you choose to build a module, it'll be called dhd. Say M if unsure.

NOTE:此处为了保证框架侧与社区代码一致,不建议修改,设置CONFIG_AP6XXX_WIFI6_HDF的配置即可。

3.3 修改编译规则Makefile文件,添加ap6256驱动的源码位置

在drivers/adapter/khdf/linux/model/network/wifi/vendor/Makefile文件,添加如下内容:

ifneq ($(CONFIG_AP6XXX_WIFI6_HDF),)#RKWIFI_PATH := (HDFVENDORPREFIX)/device/
(product_company)/$(product_device)/wifiRKWIFI_PATH := $(HDF_VENDOR_PREFIX)/device/kaihong/rk3568-khdvk/wifi //修改添加部分obj-(CONFIGAP6XXXWIFI6HDF)+=
(RKWIFI_PATH)/endif

ap6256驱动源码就位于源码/device/kaihong/rk3568-khdvk/wifi中,另外再根据ap6256的编译规则,修改wifi中的Makefile。

NOTE:此处也不建议修改,源码就位于device/(productcompany)/
(product_device)/wifi中,但此处不能获取(productcompany)与
(product_device)的值,还需要社区进行完善

WiFi驱动源码目录
驱动代码编译规则修改

参考device/kaihong/rk3568-khdvk/wifi/Makefile文件,内容如下:

obj-$(CONFIG_AP6XXX_WIFI6_HDF) += bcmdhd_hdf/

NOTE:可以修改目标规则指向不同的wifi驱动代码。

原生驱动代码存放于:

device/kaihong/rk3568-khdvk/patches/kernel/drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/

在原生驱动上修改编译规则Makefile文件

由于驱动中添加了HDF框架代码,其中涉及到头文件位于drivers目录中,需要将相关路径加入编译规则中,主要是修改两点:

(1) 引用drivers/hdf/khdf/model/network/wifi/hdfwifi.mk中规则,在Makefile中添加语句如下:

include drivers/hdf/khdf/model/network/wifi/hdfwifi.mk

(2) 将hdfwifi.mk中涉及到的头文件定义添加到编译规则中,方便编译时引用,添加语句如下:

EXTRA_CFLAGS += $(HDF_FRAMEWORKS_INC) \$(HDF_WIFI_FRAMEWORKS_INC) \$(HDF_WIFI_ADAPTER_INC) \$(HDF_WIFI_VENDOR_INC) \$(SECURE_LIB_INC)

NOTE:如果有其他编译要求,可以修改Makefile中的相关规则

3.4.4 在原生驱动上增加以及修改的HDF驱动代码文件位于:

device/kaihong/rk3568-khdvk/wifi/bcmdhd_hdf/

目录结构:

./device/kaihong/rk3568-khdvk/wifi/bcmdhd_hdf/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_ap6256.h├── net_bdh_adpater.c├── net_bdh_adpater.h

其中hdf_bdh_mac80211.c主要对g_bdh6_baseOps所需函数的填充,hdf_mac80211_ap.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_sta.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_p2p.c主要对g_bdh6_p2pOps所需函数进行填充,在drivers/framework/include/wifi/wifi_mac80211_ops.h里有对wifi基本功能所需api的说明。

驱动文件编写

HDF WLAN驱动框架由Module、NetDevice、NetBuf、BUS、HAL、Client 和 Message 这七个部分组成。开发者在WiFi驱动HDF适配过程中主要实现以下几部分功能:

适配HDF WLAN框架的驱动模块初始化

代码流程框图如下:

HDF代码入口

HDF代码入口位于device/kaihong/rk3568-khdvk/wifi/bcmdhd_hdf/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);

3.5.2 HDF驱动的注册

在函数HDFWlanRegBDH6DriverFactory中完成HDF驱动的注册,相关代码如下:

static int32_t HDFWlanRegBDH6DriverFactory(void){static struct HdfChipDriverFactory BDH6Factory = { 0 }; // WiFi device chip driverstruct HdfChipDriverManager *driverMgr = NULL;driverMgr = HdfWlanGetChipDriverMgr();if (driverMgr == NULL) {HDF_LOGE("%s fail: driverMgr is NULL!", func);return HDF_FAILURE;}BDH6Factory.driverName = BDH6_DRIVER_NAME;BDH6Factory.GetMaxIFCount = GetBDH6GetMaxIFCount;BDH6Factory.InitChip = InitBDH6Chip;BDH6Factory.DeinitChip = DeinitBDH6Chip;BDH6Factory.Build = BuildBDH6Driver;BDH6Factory.Release = ReleaseBDH6Driver;BDH6Factory.ReleaseFactory = NULL;if (driverMgr->RegChipDriver(&BDH6Factory) != HDF_SUCCESS) {HDF_LOGE("%s fail: driverMgr is NULL!", func);return HDF_FAILURE;}return HDF_SUCCESS;}

在注册HDF驱动时,需要实现HDF的基本操作,对struct HdfChipDriverFactory结构体进行初始化,struct HdfChipDriverFactory结构体的内容如下:

struct HdfChipDriverFactory {const char *driverName; /**< Driver name */int32_t (*InitChip)(struct HdfWlanDevice *device);int32_t (*DeinitChip)(struct HdfWlanDevice *device);void (*ReleaseFactory)(struct HdfChipDriverFactory *factory);struct HdfChipDriver *(*Build)(struct HdfWlanDevice *device, uint8_t ifIndex);void (*Release)(struct HdfChipDriver *chipDriver);uint8_t (*GetMaxIFCount)(struct HdfChipDriverFactory *factory);};

相关函数接口说明:

函数功能
GetBDH6GetMaxIFCount无需实现具体操作
InitBDH6Chip芯片初始化
DeinitBDH6Chip芯片去初始化
BuildBDH6Driver实现芯片驱动侧绑定
ReleaseBDH6Driver释放WLAN芯片驱动
ReleaseFactory无需实现

3.5.3 芯片驱动初始化

芯片驱动初始化函数以及wifi相关的ap、sta、p2p操作函数的注册都在BuildBDH6Driver函数中实现,主要是实现struct HdfChipDriver结构体的初始化,struct HdfChipDriver结构体如下:

struct HdfChipDriver {uint16_t type; /**< Chip type */char name[MAX_WIFI_COMPONENT_NAME_LEN]; /**< Chip name */struct HdfMac80211BaseOps *ops; /**< MAC address for the basic feature */struct HdfMac80211STAOps *staOps; /**< MAC address for the STA feature */struct HdfMac80211APOps *apOps; /**< MAC address for the AP feature */struct HdfMac80211P2POps *p2pOps; /**< MAC address for the P2Pfeature */void *priv; /**< Private data of the chip driver */int32_t (*init)(struct HdfChipDriver *chipDriver, NetDevice *netDev);int32_t (*deinit)(struct HdfChipDriver *chipDriver, NetDevice *netDev);};

1)函数BuildBDH6Driver具体实现如下:

static struct HdfChipDriver *BuildBDH6Driver(struct HdfWlanDevice *device, uint8_t ifIndex){struct HdfChipDriver *specificDriver = NULL;if (device == NULL) {HDF_LOGE("%s fail : channel is NULL", func);return NULL;}(void)device;(void)ifIndex;specificDriver = (struct HdfChipDriver *)OsalMemCalloc(sizeof(struct HdfChipDriver)); //分配结构体地址空间if (specificDriver == NULL) {HDF_LOGE("%s fail: OsalMemCalloc fail!", func);return NULL;}if (memset_s(specificDriver, sizeof(struct HdfChipDriver), 0, sizeof(struct HdfChipDriver)) != EOK) {HDF_LOGE("%s fail: memset_s fail!", func);OsalMemFree(specificDriver);return NULL;}if (strcpy_s(specificDriver->name, MAX_WIFI_COMPONENT_NAME_LEN, BDH6_DRIVER_NAME) != EOK) {HDF_LOGE("%s fail : strcpy_s fail", func);OsalMemFree(specificDriver);return NULL;}specificDriver->init = BDH6Init;specificDriver->deinit = BDH6Deinit;HDF_LOGW("bdh6: call BuildBDH6Driver %p", specificDriver);BDH6Mac80211Init(specificDriver); //wifi相关的ap、sta、p2p操作接口初始化赋值return specificDriver;}

2)函数BDH6Mac80211Init实现wifi相关的ap、sta、p2p操作接口赋值到struct HdfChipDriver结构体中,具体实现如下

void BDH6Mac80211Init(struct HdfChipDriver *chipDriver){HDF_LOGE("%s: start...", func);if (chipDriver == NULL) {HDF_LOGE("%s: input is NULL", func);return;}chipDriver->ops = &g_bdh6_baseOps;chipDriver->staOps = &g_bdh6_staOps;chipDriver->apOps = &g_bdh6_apOps;chipDriver->p2pOps = &g_bdh6_p2pOps;}

3.5.4 Wifi芯片驱动初始化

Wifi芯片驱动初始化过程,由函数BDH6Init实现,主要涉及到wlan0网络节点的注册与p2p0网络节点的注册,以及芯片驱动的初始化过程。

整体流程如下:

下面对涉及的重要函数代码进行列举:

(1) 设置NetDevice对象的操作接口,函数主要通过全局结构体赋值给NetDevice对象的成员netDeviceIf指针来实现,具体代码如下:

(2) 给NetDevice对象分配私有数据空间,具体实现如下:

(3) 启动芯片初始化流程,请参考原生驱动的初始化流程,其中需要注意的是,需要进行wlan0的节点注册,代码在原生驱动函数dhd_register_if中进行实现,具体代码如下:

(4) 创建p2p0的NetDevice对象,具体代码实现如下:

(5) 重新设置p2p0的操作方法,并进行p2p0节点注册,具体代码实现如下:

3.5.5 HDF WlAN相关的控制接口

HDF WlAN相关的控制接口主要涉及到HdfMac80211BaseOps、HdfMac80211STAOps、HdfMac80211APOps、HdfMac80211P2POps结构体,通过将以上结构体的全局变量赋值给struct HdfChipDriver结构体的ops、staOps、apOps、p2pOps成员来实现。

1)HDF WLAN Base控制侧接口的实现

代码位于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,};

上述实现的接口供STA、AP、P2P三种模式中所调用。

2)HDF WLAN STA模式接口的实现

STA模式调用流程图如下:

代码位于hdf_mac80211_sta.c

struct HdfMac80211STAOps g_bdh6_staOps = {.Connect = HdfConnect,.Disconnect = HdfDisconnect,.StartScan = HdfStartScan,.AbortScan = HdfAbortScan,.SetScanningMacAddress = HdfSetScanningMacAddress,};

3) HDF WLAN AP模式接口的实现

AP模式调用流程图如下:

代码位于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};

4)HDF WLAN P2P模式接口的实现

P2P模式调用流程图如下:

struct HdfMac80211P2POps g_bdh6_p2pOps = {.RemainOnChannel = WalRemainOnChannel,.CancelRemainOnChannel = WalCancelRemainOnChannel,.ProbeReqReport = WalProbeReqReport,.AddIf = WalAddIf,.RemoveIf = WalRemoveIf,.SetApWpsP2pIe = WalSetApWpsP2pIe,.GetDriverFlag = WalGetDriverFlag,};

5) HDF WLAN框架事件上报接口的实现

WiFi驱动需要通过上报事件给wpa_supplicant和hostapd应用程序,比如扫描热点结果上报,新STA终端关联完成事件上报等等,HDF WLAN事件上报的所有接口请参考drivers/framework/include/wifi/hdf_wifi_event.h:

事件上报HDF WLAN接口主要有:

头文件接口名称功能描述
hdf_wifi_event.hHdfWifiEventNewSta()上报一个新的sta事件
HdfWifiEventDelSta()上报一个删除sta事件
HdfWifiEventInformBssFrame()上报扫描Bss事件
HdfWifiEventScanDone()上报扫描完成事件
HdfWifiEventConnectResult()上报连接结果事件
HdfWifiEventDisconnected()上报断开连接事件
HdfWifiEventMgmtTxStatus()上报发送状态事件
HdfWifiEventRxMgmt()上报接受状态事件
HdfWifiEventCsaChannelSwitch()上报Csa频段切换事件
HdfWifiEventTimeoutDisconnected()上报连接超时事件
HdfWifiEventEapolRecv()上报Eapol接收事件
HdfWifiEventResetResult()上报wlan驱动复位结果事件
HdfWifiEventRemainOnChannel()上报保持信道事件
HdfWifiEventCancelRemainOnChannel上报取消保持信道事件
所有关键问题总结
调试AP模块时,启动AP模式的方法

调试AP模块时,无法正常开启AP功能的解决方法

需要使用到busybox和hostapd配置ap功能,操作步骤如下:
1.ifconfig wlan0 up
2.ifconfig wlan0 192.168.12.1 netmask 255.255.255.0
3…/busybox udhcpd /data/l2tool/udhcpd.conf
4.hostapd -d /data/l2tool/hostapd.conf

调试STA模块时,启动STA模式的方法

NOTE:需要对busybox与dhcpc.sh设置成可执行权限

调试P2P模块时,启动P2P模式的方法

调试P2P模块时,模块可以作为GO模式或者GC模式,区别在于配置文件不同,操作步骤如下:

wpa_supplicant -i wlan0 -c /data/l2tool/p2p_supplicant.conf & 设置p2p模式wpa_cli -i wlan0 -p /data/l2tool/wlan0 p2p_find 启动p2p查找wpa_cli -i wlan0 -p /data/l2tool/wlan0 p2p_connect 06:86:29:e8:47:84 pbc 连接p2p设备./busybox udhcpc -ip2p-wlan0-0 -s /data/l2tool/dhcpc.sh 启动p2p-wlan0-0的dhcp获取地址

NOTE:在GO模式下,连接上设备后,应该立即获取IP地址,否则,连接会自动断开。

扫描热点事件无法上报到wap_supplicant的解决办法

wpa_supplicant 这个应用程序启动时不能加 -B参数后台启动,-B后台启动的话,调用poll()等待接收事件的线程会退出,所以无法接收上报事件,

wpa_supplicant -iwlan0 -c /data/wpa_supplicant.conf & 这样后台启动就可以了。

wpa2psk方式无法认证超时问题解决方法

分析流程发现 hostapd没有接收到WIFI_WPA_EVENT_EAPOL_RECV = 13这个事件,原来是驱动没有将接收到的EAPOL报文通过HDF WiFi框架发送给hostapd进程,在驱动接收报文后,调用netif_rx()触发软中断前将EAPOL报文发送给HDF WiFi框架,认证通过了。

P2P模式连接不成功问题定位分析

在调试P2P连接接口时,发现手机P2P直连界面总是处于已邀请提示,无法连接成功,通过抓取手机和WiFi模组正常连接成功报文和HDF适配后连接失败的报文进行比对,在失败的报文组中,发现手机侧多回复了一帧ACTION报文,提示无效参数,然后终止了P2P连接。

最后比对WiFi模组向手机发送的ACTION报文内容,发现填充的P2P Device Info的MAC地址值不对,如下:

正确帧内容:

错误帧内容:

最后经过分析MAC地址的填充部分代码,这个MAC地址是wpa_supplicant 根据p2p0的MAC地址填充的,所以将wdev对象(即p2p-dev-wlan0)的MAC地址更新给p2p0接口,二者保持一致即可,见代码wl_get_vif_macaddr(cfg, 7, p2p_hnetdev->macAddr);的调用。

连接成功日志
STA模式连接成功日志
WPA: Key negotiation ccompleted with 50:eb:f6:02:8e6:d4 [PTK=CCMP GTK=CCMP]06 wlan0: State: GROUP_HANDSHAKEc -> COMPLETEDwlan0: CTRL-E4VENT-CONNECTED - Connection to 50:eb:f6:02:8e:d4 completed 3[id=0 id_str=]WifiWpaReceid eEapol done

AP模式连接成功日志

wlan0: STA 96:27:b3:95:b7:6e IEEE 802.1X: au:thorizing portwlan0: STA 96:27:b3:95:b7:6e WPA: pairwiseb key handshake completed (RSN)WifiWpaReceiveEapol done

P2P模式连接成功日志

P2P: cli_channels:EAPOL: External notification - portValid=1EAPOL: External notifica:tion - EAP success=1EAPOL: SUPP_PAE entering state AUTHENTIwCATINGEAPOL: SUPP_BE enterilng state SUCCESSEAP: EAP ent_ering state DISABLEDEAPOL: SUPP_PAE entering state AUTHENTICATEDEAPOL:n Supplicant port status: AuthoorizedEAPOL: SUPP_BE enteringtstate IDLEWifiWpaReceiveEapol donepleted - result=SUCCESS# ifconfiglo Link encap:Local Loopbackinet addr:127.0.0.1 Mask:255.0.0.0inet6 addr: ::1/128 Scope: HostUP LOOPBACK RUNNING MTU:65536 Metric:1RX packets:12 errors:0 dropped:0 overruns:0 frame:0TX packets:12 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:565 TX bytes:565wlan0 Link encap:Ethernet HWaddr 10:2c:6b:11:61:e0 Driver bcmsdh_sdmmcinet6 addr: fe80::122c:6bff:fe11:61e0/64 Scope: LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 TX bytes:0p2p0 Link encap:Ethernet HWaddr 12:2c:6b:11:61:e0inet6 addr: fe80::102c:6bff:fe11:61e0/64 Scope: LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 TX bytes:0p2p-p2p0-0 Link encap:Ethernet HWaddr 12:2c:6b:11:21:e0 Driver bcmsdh_sdmmcinet6 addr: fe80::102c:6bff:fe11:21e0/64 Scope: LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:9 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 TX bytes:0

4G

EC20模块

EC20模块是移远的一款比较经典的4G通信模组,MCU可以通过USB或者串口来和4G模块进行通信,我们rk3566使用的则是USB接口。

4G模块作为usb device,在加载对应的驱动后会生成ttyUSBx节点,框架层可以通过这些节点使用AT指令或者模块的状态和信息,通过ppp拨号注册一个网卡设备,拨号成功后在命令行可以通过ifconfig -a,可以看到有pppx网卡生成。

硬件连接

从原理图中我们看到我们的4G模块使用的PCIE接口,细心的同学会发现36和38引脚是USBDN和USBDP,也就是说我们使用的是PCIE转USB接口,最终的表现和直接使用USB接口是一样的。

因为4G模块使用的是USB接口,对应USB的host功能一定要工作正常,比如USB VBUS的使能,USB设备树的正确配置,kernel config的一些配置都要相应的打开,有的4G模块还有电源使能引脚,也需要在设备树中配置。

Kennel修改

配置VID PID

在drivers/usb/serial/option.c,添加对应的vid pid,当插入一个新的usb设备,option里相关的USB虚拟串口驱动会匹配vid pid,如果匹配成功,就会生成ttysUSBx节点,具体模块的修改方法在供应商提供的模块的资料里一般都会有,如Linux_USB_Driver_User_Guide

1、option.c增加EC20的pid vid如下,在option_ids结构体中增加:

static const struct usb_device_id option_ids[] = {{ USB_DEVICE(0x2c7c, 0x6002) }, /* Quectel EC20 */

测试

1、 在/dev/查看有无ttyUSBx节点,有类似如下节点表明模块配置没有问题。

#ls /dev/ttyUSB*/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3

2、 AT指令测试,使用microcom串口指令

#microcom /dev/ttyUSB2ATOK

Vibrator

Vibrator是振动器的意思,也可以被叫做马达,马达旋转或者做直线运动会产生振动。

驱动框架模型

Vibrator驱动模型

Vibrator驱动按HDF标准框架开发,整体的驱动框架openharmony 主线已经具备,只需要实现具体的器件驱动。Vibrator驱动提供HDI能力接口,支持静态HCS配置的时间序列和动态配置持续时间两种振动效果。调用StartOnce接口动态配置持续振动时间,调用StartEffect接口启动静态配置的振动效果。

HDF驱动适配

HCS配置

配置设备描述信息,在device_info.hcs中添加device_linear_vibrator:

   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";}}}

配置线性马达器件信息,在linear_vibrator_config.hcs和vibrator_config.hcs中添加器件的特性:

root{linearVibratorConfig {boardConfig {match_attr = "hdf_linear_vibrator_driver";vibratorChipConfig {busType = 1; // 0:i2c 1:gpiogpioNum = 154;startReg = 0;stopReg = 0;startMask = 0;}}}
}root {vibratorConfig {boardConfig {match_attr = "hdf_vibrator_driver";vibratorAttr {/* 0:rotor 1:linear */deviceType = 1;supportPreset = 1;}vibratorHapticConfig {haptic_clock_timer {effectName = "haptic.clock.timer";type = 1; // 0 means built-in, 1 time seriesseq = [600, 600, 200, 600]; // time seq}haptic_default_effect {effectName = "haptic.default.effect";type = 0;seq = [0, 3, 800, 1];}}}}
}

HDF适配

驱动入口函数实现:

struct VibratorOps {int32_t (*Start)(void);int32_t (*StartEffect)(uint32_t effectType);int32_t (*Stop)(void);
};int32_t InitLinearVibratorDriver(struct HdfDeviceObject *device)
{static struct VibratorOps ops;------ops.Start = StartLinearVibrator;ops.StartEffect = StartEffectLinearVibrator;ops.Stop = StopLinearVibrator;RegisterVibrator(&ops); ParserLinearConfig(device->property, drvData);GpioSetDir(drvData->gpioNum, GPIO_DIR_OUT);
}struct HdfDriverEntry g_linearVibratorDriverEntry = {.moduleVersion = 1,.moduleName = "HDF_LINEAR_VIBRATOR",.Bind = BindLinearVibratorDriver,.Init = InitLinearVibratorDriver,.Release = ReleaseLinearVibratorDriver,
};HDF_INIT(g_linearVibratorDriverEntry);

代码分布

./drivers/peripheral/misc/vibrator/chipset/vibrator_linear_driver.c
./vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/device_info.hcs ./vendor/kaihong/khdvk_3566b/hdf_config/khdf/vibrator/linear_vibrator_config.hcs ./vendor/kaihong/khdvk_3566b/hdf_config/khdf/vibrator/vibrator_config.hcs

UT测试

代码路径

./drivers/peripheral/misc/vibrator/test/unittest/common/hdf_vibrator_test.cpp ./drivers/peripheral/misc/vibrator/test/unittest/hdi/hdf_vibrator_hdi_test.cpp

编译UT代码命令

./build.sh --product-name khdvk_3566b --build-target hdf_test_vibrator

生成目标文件路径

./out/khdvk_3566b/tests/unittest/hdf/vibrator/hdf_unittest_vibrator
./out/khdvk_3566b/tests/unittest/hdf/vibrator/hdf_unittest_hdi_vibrator

将编译生成的bin文件 push到开发板上system/bin目录,修改执行权限,执行结果如下

./hdfunittest_hdi_vibrator

Load parameter_contexts succes: /system/etc/selinux/targeted/contexts/parameter_contexts
Running main() from ../../third_party/googletest/googletest/src/gtest_main.cc
[==========] Running 14 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 14 tests from HdfVibratorHdiTest
[ RUN ] HdfVibratorHdiTest.CheckVibratorInstanceIsEmpty
[ OK ] HdfVibratorHdiTest.CheckVibratorInstanceIsEmpty (0 ms)
[ RUN ] HdfVibratorHdiTest.PerformOneShotVibratorDuration_001
[ OK ] HdfVibratorHdiTest.PerformOneShotVibratorDuration_001 (2002 ms)
[ RUN ] HdfVibratorHdiTest.PerformOneShotVibratorDuration_002
[ OK ] HdfVibratorHdiTest.PerformOneShotVibratorDuration_002 (2 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_001
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_001 (5002 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_002
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_002 (2002 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_004
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_004 (5005 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_005
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_005 (5002 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_006
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_006 (5002 ms)
[ RUN ] HdfVibratorHdiTest.ExecuteVibratorEffect_007
[ OK ] HdfVibratorHdiTest.ExecuteVibratorEffect_007 (3 ms)

./hdf_unittest_vibrator

Load parameter_contexts succes: /system/etc/selinux/targeted/contexts/parameter_contexts
Running main() from ../../third_party/googletest/googletest/src/gtest_main.cc
[==========] Running 16 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 16 tests from HdfVibratorTest
[ RUN ] HdfVibratorTest.CheckVibratorInstanceIsEmpty
[ OK ] HdfVibratorTest.CheckVibratorInstanceIsEmpty (0 ms)
[ RUN ] HdfVibratorTest.PerformOneShotVibratorDuration_001
[ OK ] HdfVibratorTest.PerformOneShotVibratorDuration_001 (2001 ms)
[ RUN ] HdfVibratorTest.PerformOneShotVibratorDuration_002
[ OK ] HdfVibratorTest.PerformOneShotVibratorDuration_002 (0 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_001
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_001 (5000 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_002
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_002 (2001 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_003
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_003 (0 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_004
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_004 (5001 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_005
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_005 (5000 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_006
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_006 (5000 ms)
[ RUN ] HdfVibratorTest.ExecuteVibratorEffect_007
[ OK ] HdfVibratorTest.ExecuteVibratorEffect_007 (1 ms)

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyQt5-QCheckBox-开关按钮

效果预览 实现代码 from PyQt5.QtWidgets import QCheckBox, QApplication, QWidget, QVBoxLayout from PyQt5.QtCore import Qt, QRect, QPropertyAnimation, QEasingCurve, pyqtProperty from PyQt5.QtGui import QPainter, QColor, QPen, QFontclass CompactSwitchCheckbox…

《Google软件测试之道》笔记

介绍 GTAC&#xff1a;Google Test Automation Conference&#xff0c;Google测试自动化大会。 本书出版之前还有一本《微软测试之道》&#xff0c;值得阅读。 质量不是被测试出来的&#xff0c;但未经测试也不可能开发出有质量的软件。质量是开发过程的问题&#xff0c;而不…

股指期货的详细玩法功能与应用解析

股指期货作为一种重要的金融衍生工具&#xff0c;为投资者提供了多样化的投资和风险管理手段。本文将详细探讨股指期货的三大主要功能&#xff1a;风险规避、价格发现和资产配置。 第一&#xff0c;风险规避功能 1.套期保值&#xff1a;股指期货的风险规避功能主要通过套期保值…

HarmonyOS 速记

目录 装饰器Entry(入口)Component(组件)State(状态)Prop(属性)Preview(预览)PreviewerInspector 结构体structbuild自定义组件自定义 Custom 组件 容器Row(行) & Column(列)RelativeContainer(相对布局容器)marginpaddingSwiper(轮播图)Grid(网格容器)List(列表) 组件Image…

Java入门程序-HelloWorld

Java程序开发的三个步骤 1.编写代码得到 .java 源代码文件 2.使用javac编译得到 .class 字节码文件 3.使用java运行 注意事项 建议代码文件名全英文&#xff0c;首字母大写&#xff0c;满足驼峰命名法&#xff0c;源代码文件的后缀必须是.java 开发HelloWorld程序 &…

MATLAB十九种作图大全

一、二维曲线图 反应两个变量的因果关系 clear; %清除工作空间的所有变量 clc; %清除命令窗口的内容&#xff0c;对工作环境中的全部变量无任何影响 close all; %关闭所有的Figure窗口 x linspace(1,200,100); %均匀…

进程监控与管理详解

一、进程的定义: 进程process是正在运行的程序,包括: 分配的内存地址空间 安全属性、包括所有权和特权 一个或多个线程 进程状态 进程的环境包括: 本地和全局变量 当前调度上下文…

Mac清理其他文件:释放存储空间的高效指南

每个Mac用户都可能遇到存储空间不足的问题&#xff0c;尤其是当“其他”文件积累到一定体积时。在Mac上&#xff0c;“其他”文件通常包括各种系统文件、缓存、文档以及不被归类为应用程序、照片、电影或音乐的其他类型的文件。这些文件往往不易被注意&#xff0c;但逐渐占用了…

数组学习内容

动态初始化 只给长度&#xff0c;数据类型【】 数组名new 数据类型【数组长度】 内存图

Leetcode—1137. 第 N 个泰波那契数【简单】

2024每日刷题&#xff08;160&#xff09; Leetcode—1137. 第 N 个泰波那契数 记忆化搜索实现代码 class Solution { public:int tribonacci(int n) {int zero 0;int one 1;int two 1;if(n 0) {return zero;}if(n 1) {return one;}if(n 2) {return two;}int ans 0;fo…

vscode搭建ros开发环境问题记录(更新...)

文章目录 vscode 不能自动补全方法一&#xff1a;方法二&#xff1a; 开发环境&#xff1a; vmware 15.7 ubuntu 20.04 ros noetic vscode 不能自动补全 方法一&#xff1a; 这里将头文件已经正确包含到c_cpp_properties.json中代码中仍然不能自动补全&#xff0c; 将C_CPP插…

基于python+django+vue的农业管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的农…

57.【C语言】字符函数和字符串函数(strerror函数)

目录 11.strerror函数 *简单使用 *错误码打印 *实际的用法 *附:VS中errno.h对错误码的分类 11.strerror函数 *简单使用 strerror string error cpuscplus的介绍 点我跳转 翻译: 函数 strerror char * strerror ( int errnum ); 得到指向错误信息字符串(简称错误码)的指针…

配置环境-keil

配置keil -- 先将keil安装配置好&#xff0c;包括库 一、STM32 -- STM32是意法半导体&#xff08;意大利&#xff09;采用ARM公司设计的内核&#xff0c;设计一系列32位单片机芯片。 1、STM32开发的几种方式 2、STM32寄存器和库函数版本的工程创建 新建文件夹 复制相关文件…

web基础之RCE

简介&#xff1a;RCE称为远程代码执行漏洞&#xff1b;是互联网的一种安全漏洞&#xff1b;攻击者可以直接向后台服务器远程注入操作系统命令&#xff1b;从而操控后台系统&#xff1b;也是CTF比较常考的一个方面 1、eval执行 &#xff08;1&#xff09;分析后端代码&#xf…

Redis学习——数据不一致怎么办?更新缓存失败了又怎么办?

文章目录 引言正文读写缓存的数据一致性只读缓存的数据一致性删除和修改数据不一致问题操作执行失败导致数据不一致解决办法 多线程访问导致数据不一致问题总结 总结参考信息 引言 最近面试快手的时候被问到了缓存不一致怎么解决&#xff1f;一开始还是很懵的&#xff0c;因为…

线性代数之QR分解和SVD分解

文章目录 1.QR分解Schmidt正交化Householder变换QR分解的应用 2. 求矩阵特征值、特征向量的基本方法3.SVD分解SVD分解的应用 参考文献 1.QR分解 矩阵的正交分解又称为QR分解&#xff0c;是将矩阵分解为一个正交矩阵Q和一个上三角矩阵R的乘积的形式。 任意实数方阵A&#xff0c…

从用户数据到区块链:Facebook如何利用去中心化技术

在数字化时代&#xff0c;用户数据的管理和保护已成为科技公司面临的重大挑战。作为全球最大的社交网络平台之一&#xff0c;Facebook不仅在用户数据的处理上积累了丰富的经验&#xff0c;也在探索如何利用去中心化技术&#xff0c;如区块链&#xff0c;来改进其数据管理和用户…

健身房预约小程序定制搭建,数字化运营管理

目前&#xff0c;健身已经成为了大众日常生活中不可或缺的一部分&#xff0c;不管是健身跑步、打羽毛球等&#xff0c;都受到了大众的欢迎&#xff01;随着健身行业的快速发展&#xff0c;为了提高大众的健身体验&#xff0c;健身房预约系统得到了广泛发展。预约系统不仅解决了…

深入MySQL的索引实践及优化

文章目录 一、什么是索引二、数据结构——为什么是B树平衡二叉查找树红黑树B树&#xff08;多叉&#xff09;B树&#xff08;多叉&#xff09; 三、MySQL索引实战1.索引创建(1)自动创建索引(2)手动创建非聚簇索引(3)索引的代价 2.B树索引原则&#xff08;1&#xff09;等值匹配…