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

news2024/11/24 18:33:36

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
  • 持续更新中……

概述

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 = false

use_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 = false
  defines += [ "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_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;

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 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:
   /**

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

也就是说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:
     \#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;

另外,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=y

CONFIG_DRIVERS_HDF_PLATFORM_MMC=y

CONFIG_DRIVERS_HDF_WIFI=y

CONFIG_DRIVERS_HDF_STORAGE=y

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

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

CONFIG_DRIVERS_HDF_NETDEV_EXT=y

CONFIG_AP6XXX_WIFI6_HDF=y

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

config AP6XXX_WIFI6_HDF

tristate "support ap6xxx wifi6(80211ax) HDF"

depends on DRIVERS_HDF_WIFI

 select CFG80211

 select MAC80211

 select DRIVERS_HDF_NETDEV_EXT

help

This 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)/wifi

RKWIFI_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 driver

struct 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 -> COMPLETED

wlan0: 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 port

wlan0: STA 96:27:b3:95:b7:6e WPA: pairwiseb key handshake completed (RSN)

WifiWpaReceiveEapol done

P2P模式连接成功日志

P2P: cli_channels:

EAPOL: External notification - portValid=1

EAPOL: External notifica: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 enteringtstate 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

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/ttyUSB2

AT

OK

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:gpio
                gpioNum = 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 series
                    seq = [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.……

鸿蒙开发面试真题(含参考答案):

在这里插入图片描述

《OpenHarmony源码解析》:

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

图片

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

图片
在这里插入图片描述

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

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

相关文章

【ubuntu】ubuntu20.04安装显卡驱动

1.安装 点击右下角Apply Changes。 等安装好之后&#xff0c;重启。 现在的nvidia驱动已经很好安装了&#xff0c;比早期时安装出现黑屏等情况好了很多。 2.验证 nvidia-smi

Mybatis plus快速使用

文章目录 Mybatis plus快速使用1.ORM2.mybatis plus介绍3.mybatis plus使用1.添加依赖2.配置信息3.启动类加入 MapperScan&#xff08;“填入mapper包的位置”&#xff09;4.创建user接口&#xff0c;在mapper中加入UserMapper接口5.mybatis-plus crud注解启动springboot项目ma…

基于图像的3D动物重建与生成

一、背景与目标 3D-Fauna 是一款用于基于图像和视频进行四足动物3D重建与生成的开源方案。自然界展示了复杂的相似性与多样性,该方法通过学习来自网上图片的四足动物的3D形态,能够从单张图片生成可动画化的带有纹理的3D网格模型。其最终目标是通过大量扩展现有的解决方案,实…

Ajax面试题:(第一天)

目录 1.说一下网络模型 2.在浏览器地址栏键入URL&#xff0c;按下回车之后会经历以下流程&#xff1a; 3.什么是三次握手和四次挥手&#xff1f; 4.http协议和https协议的区别 1.说一下网络模型 注&#xff1a;各层含义按自己理解即可 2.在浏览器地址栏键入URL&#xff0c;…

mybatis自定义类型处理器

mybatis自定义类型处理器 其实使用MySQL或Oracle数据库很少会遇到自定义类型处理器的情况&#xff0c;之前是因为项目中使用了PGSQL才接触到这块的&#xff0c;这里简单做一下记录 要创建一个自定义的类型处理器&#xff0c;就需要继承BaseTypeHandler类或者实现TypeHandler接…

数据结构 ——— 相交链表(链表的共节点)

题目要求 两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单链表相交的起始节点&#xff0c;如果两个链表不存在相交节点&#xff0c;则返回 NULL 手搓两个相交简易链表 代码演示&#xff1a; struct ListNode* a1 (struct ListNode*)malloc(sizeof(struc…

Git 分支提交同步到主干的详细教程——(包含命令行和idea操作两种方式)

文章目录 Git 分支提交同步到主干的详细教程一、Git 命令行操作1. 确保分支上的代码已提交2. 切换到主干分支3. 拉取最新的主干分支代码4. 合并分支到主干方式一&#xff1a;使用 merge 进行合并方式二&#xff1a;使用 rebase 进行合并 5. 推送合并后的代码到远程主干分支命令…

github 搭建个人导航网

最近搭建了个 个人的导航网&#xff0c;具体内容见下图&#xff0c;欢迎大家访问吖&#xff0c;点击访问 具体实现是使用 vue3 编写&#xff0c;白嫖 github 的 page 部署 首先在 github上创建一个仓库&#xff1a;name.github.io # name是你 github 的名字 然后在本地创建一…

Linux安装部署MySQL8.0加遇着问题解决

1.首先我先给个URL下载MySQL官方网站https://downloads.mysql.com/archives/community/ 2.选择Linux的红帽系统 3.接着选择红帽系统的7版本,x86 4.接着选择MySQL版本,此时我选择8.4.0,下载rpm bundle这个,下载下面这个就好 5.Windows文件上传到Linux系统 rz上传文件命令,找到…

【D3.js in Action 3 精译_030】3.5 给 D3 条形图加注图表标签(下):Krisztina Szűcs 人物专访 + 3.6 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

【redis-05】redis保证和mysql数据一致性

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…

Qt+大恒相机回调图片刷新使用方式

一、前言 上篇文章介绍了如何调用大恒SDK获得回调图片&#xff0c;这篇介绍如何使用这些图片并刷新到界面上。考虑到相机的帧率很高&#xff0c;比如200fps是很高的回调频率。那么我们的刷新频率是做不到这么快&#xff0c;也没必要这么快。一般刷新在60帧左右就够了。 二、思路…

springboot kafka多数据源,通过配置动态加载发送者和消费者

前言 最近做项目&#xff0c;需要支持kafka多数据源&#xff0c;实际上我们也可以通过代码固定写死多套kafka集群逻辑&#xff0c;但是如果需要不修改代码扩展呢&#xff0c;因为kafka本身不处理额外逻辑&#xff0c;只是起到削峰&#xff0c;和数据的传递&#xff0c;那么就需…

Unity_Obfuscator Pro代码混淆工具_学习日志

Unity_Obfuscator Pro代码混淆工具_学习日志 切勿将密码或 API 密钥存储在您附带的应用程序内。 混淆后的热更新暂时没有想到怎么办 Obfuscator 文档 https://docs.guardingpearsoftware.com/manual/Obfuscator/Description.html商店链接Obfuscator Pro&#xff08;大约$70&a…

169.254.0.0/16是什么地址?

169.254.0.0/16是一个链路本地地址&#xff0c;也称为连结本地位址&#xff0c;主要用于局域网内的主机相互通信。‌ 这种地址仅供在网段或广播域中的主机相互通信使用&#xff0c;不需要外部互联网服务‌。 169.254.0.0/16地址段定义在RFC 3927中&#xff0c;当DHCP服务器无法…

永洪BI:企业数字化转型的得力助手

在当今快速变化的商业环境中&#xff0c;数据已成为企业决策的重要依据。随着大数据、云计算和人工智能技术的发展&#xff0c;企业对数据分析的需求日益增长。永洪BI&#xff08;Business Intelligence&#xff09;作为国内领先的商业智能解决方案提供商&#xff0c;以其强大的…

在mac中通过ip连接打印机并实现双面打印

首先需要找到电脑自带的打印。添加打印机。 填写好打印机的ip地址&#xff0c;然后添加。 填写好ip地址后&#xff0c;直接添加就行 添加完打印机后其实就可以打印了。但是有些功能可能实现不了&#xff0c;比如说双面打印。为了实现双面打印的功能&#xff0c;需要再进行设置…

从0到1:企事业单位知识竞赛答题小程序迭代开发笔记一

背景调研 企事业单位知识竞赛答题小程序&#xff0c;在信息技术迅猛发展的时代&#xff0c;企业和事业单位在提升员工素质和知识水平方面面临着新的挑战。为了增强员工的学习积极性、提高团队凝聚力和整体素质&#xff0c;越来越多的单位开始组织知识竞赛活动。传统的知识竞赛…

SQL第13课——创建高级联结

本课讲另外一些联结&#xff08;含义和使用方法&#xff09;&#xff0c;如何使用表别名&#xff0c;如何对被联结的表使用聚集函数。 13.1 使用表别名 第7课中使用别名引用被检索的表列&#xff0c;给列起别名的语法如下&#xff1a; SQL除了可以对列名和计算字段使用别名&a…

kafka和zookeeper单机部署

安装kafka需要jdk和zookeeper环境&#xff0c;因此先部署单机zk的测试环境。 zookeeper离线安装 下载地址&#xff1a; zookeeper下载地址&#xff1a;Index of /dist/zookeeper 这里下载安装 zookeeper-3.4.6.tar.gz 版本&#xff0c;测试环境单机部署 上传服务器后解压缩 …