当前位置: 首页 > news >正文

巴中做网站centos6.3 网站开发

巴中做网站,centos6.3 网站开发,佛山营销型网站设计,网络管理培训课程往期知识点记录#xff1a; 鸿蒙#xff08;HarmonyOS#xff09;应用层开发#xff08;北向#xff09;知识点汇总 鸿蒙#xff08;OpenHarmony#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 OpenHarmony Camera驱动模型结构 HDI Implementation#x…往期知识点记录 鸿蒙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_standardcamera 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└── etcCameraService启动入口在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.mdCamera 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服务需设置为2moduleNamecamera 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); }参数说明 clientHdfDeviceIoClient设备句柄 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_ptrIController GetController(ControllerId controllerId, std::string hardwareName);void Configure(std::shared_ptrCameraMetadata 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_ptrISensor GetSensor(std::string sensorName);RetCode SendFrameBuffer(std::shared_ptrFrameSpec buffer, std::string hardwareName);void SetAbilityMetaDataTag(std::vectorint32_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::vectorstd::shared_ptrSensorController 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_ptrCameraMetadata meta);RetCode Start(int buffCont, DeviceFormat format);RetCode Stop();...void SetMetaDataCallBack(MetaDataCb cb) override;void BufferCallback(std::shared_ptrFrameSpec buffer);void SetAbilityMetaDataTag(std::vectorint32_t abilityMetaDataTag);RetCode GetAbilityMetaData(std::shared_ptrCameraMetadata 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_ptrIController GetController(ControllerId controllerId, std::string hardwareName);RetCode PowerUp(std::string hardwareName);RetCode PowerDown(std::string hardwareName);void Configure(std::shared_ptrCameraMetadata meta);void SetAbilityMetaDataTag(std::vectorint32_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::vectorstd::shared_ptrFlashController 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_ptrCameraMetadata meta){(void)meta;return RC_OK;}RetCode SetFlashlight(FlashMode flashMode, bool enable);void SetAbilityMetaDataTag(std::vectorint32_t abilityMetaDataTag);RetCode GetAbilityMetaData(std::shared_ptrCameraMetadata 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_ptrIController GetController(ControllerId controllerId, std::string hardwareName);void Configure(std::shared_ptrCameraMetadata meta);RetCode Start(std::string hardwareName);RetCode Stop(std::string hardwareName);RetCode PowerUp(std::string hardwareName);RetCode PowerDown(std::string hardwareName);void SetAbilityMetaDataTag(std::vectorint32_t abilityMetaDataTag, std::string hardwareName){(void)abilityMetaDataTag;(void)hardwareName;return;} private:bool CheckCameraIdList(std::string hardwareName);std::vectorstd::shared_ptrIspController ispList_; };ISP controller结构如下 class IspController : public IController { public:IspController();explicit IspController(std::string hardwareName);virtual ~IspController();RetCode Init();RetCode Configure(std::shared_ptrCameraMetadata meta);RetCode PowerUp();RetCode PowerDown();RetCode Stop();RetCode Start();void SetAbilityMetaDataTag(std::vectorint32_t abilityMetaDataTag){(void)abilityMetaDataTag;return;}RetCode GetAbilityMetaData(std::shared_ptrCameraMetadata 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_ptrFrameSpec frameSpec);RetCode StartStream(const std::string cameraID);RetCode QueueBuffer(const std::string cameraID, const std::shared_ptrFrameSpec frameSpec);RetCode ReleaseBuffers(const std::string cameraID);RetCode StopStream(const std::string cameraID);RetCode SetCallback(BufCallback cb);static RetCode Init(std::vectorstd::string cameraIDs);static std::mapstd::string, std::string deviceMatch;private:std::shared_ptrHosV4L2Buffers myBuffers_ nullptr;std::shared_ptrHosV4L2Streams myStreams_ nullptr;std::shared_ptrHosFileFormat myFileFormat_ nullptr;std::shared_ptrHosV4L2Control 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 {modulesample;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是一组输入bufferinBufferCount是输入buffer的个数outBuffer是输出buffermeta是算法处理时产生的非图像数据IppAlgoBuffer在ipp_algo.h中定义 Stop停止处理接口停流时被ippnode调用 下边代码中的id指的是和ippnode对应的port口id比如inBuffer[0]的id为0则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空此时其中一个输入buffer 被ippnode作为输出buffer传递到下个nodeinBuffer至少有一个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 rk3566import(//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/cameracamera_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_buildcamera_device_manager_deps $camera_device_name_path/camera/device_manager:camera_device_managercamera_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.hcsCamera所有配置文件使用系统支持的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 hdfconfiginstall_images [ chipset_base_dir ]subsystem_name hdfpart_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为Nodesource为数据数据源端sink为末端source为第一个nodenode的名称是source#0status、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::vectorHardwareConfiguration 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::vectorstd::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_guardstd::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.hcdinstall_images [ vendor_base_dir ]relative_install_dir firmwarepart_name kaihong_productsinstall_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_SDIOyCONFIG_DRIVERS_HDF_PLATFORM_MMCyCONFIG_DRIVERS_HDF_WIFIyCONFIG_DRIVERS_HDF_STORAGEy3.2 具体WiFi设备驱动编译控制宏 涉及到wifi设备驱动的编译控制宏位于drivers/adapter/khdf/linux/model/network/wifi/Kconfig中其中主要涉及到编译控制宏如下 CONFIG_DRIVERS_HDF_NETDEV_EXTyCONFIG_AP6XXX_WIFI6_HDFy编译控制选项CONFIG_AP6XXX_WIFI6_HDF内容如下 config AP6XXX_WIFI6_HDFtristate support ap6xxx wifi6(80211ax) HDFdepends on DRIVERS_HDF_WIFIselect CFG80211select MAC80211select DRIVERS_HDF_NETDEV_EXThelpThis driver supports wifi6 for ap6xxx HDF chipset.This driver uses the kernels wireless extensions subsystem.If you choose to build a module, itll 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)/endifap6256驱动源码就位于源码/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成员来实现。 1HDF 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三种模式中所调用。 2HDF 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};4HDF 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 [PTKCCMP GTKCCMP]06 wlan0: State: GROUP_HANDSHAKEc - COMPLETEDwlan0: CTRL-E4VENT-CONNECTED - Connection to 50:eb:f6:02:8e:d4 completed 3[id0 id_str]WifiWpaReceid eEapol doneAP模式连接成功日志 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 doneP2P模式连接成功日志 P2P: cli_channels:EAPOL: External notification - portValid1EAPOL: External notifica:tion - EAP success1EAPOL: 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 - resultSUCCESS# 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:04G 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/ttyUSB32、 AT指令测试使用microcom串口指令 #microcom /dev/ttyUSB2ATOKVibrator 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.hcsUT测试 代码路径 ./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.hkea.cn/news/14289500/

相关文章:

  • 汶川县建设局网站塘厦三局医院
  • 运城网站建设兼职余姚物流做网站
  • 做菠菜网站判多久怎样低成本做网站推广
  • 站长推荐网址入口自动跳转重庆建设工程有限公司
  • 建设工程知识类网站跨境电商营销推广
  • 自适应 网站快速排名推荐
  • 物流公司网站建设系统规划建博会广州网站
  • 网站首页尺寸最新军事新闻事件今天
  • 破解asp网站后台地址网站管理模板
  • 菜鸟必读 网站被入侵后需做的检测 1项目管理软件开源
  • seo网站开发注意事项wordpress国际运费设置
  • 网站后台图片不显示易企秀网站怎么做轮播图
  • 做地方网站如何盈利wordpress浏览器标签插件
  • 网站页面设计原则瑞安网站建设优化
  • 银川网站建设redu建设部监理资质申报网站
  • 现在网站开发哪个语言好网站备案拍照点
  • 网站建设课程设计要求宝坻网站建设公司
  • 商丘做网站汉狮网络花关键词排名系统
  • 如何将数据写入wordpress文站宿州住房和城乡建设局网站
  • app网站建设教程视频教程网站分类有哪几类
  • 无锡企业网站公司成都专业做网站公司
  • 网站后台管理系统多少钱快速开发平台对比
  • 手做网站升级网页
  • 申请注册网站网站开发设计工程师
  • 建设个网站十堰为企业做网站的单位
  • 新手怎么做电商在哪个网站wordpress 写文章页面
  • 深圳住房和建设局网站业务主题广告设计软件免费下载
  • WordPress 网站小图标移动商城信息费
  • 常州做网站找哪家好微信公众号开发平台登录
  • 百度云可以做网站吗店铺设计包含哪些内容