OpenHarmony实战:瑞芯微RK3566移植案例(下)

news2024/11/26 13:26:35

OpenHarmony实战:瑞芯微RK3566移植案例(下)

OpenHarmony实战:瑞芯微RK3566移植案例(中)


WIFI

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

接下来熟悉HCS文件的格式以及"HDF WIFI”核心驱动框架的代码启动初始化过程,参考hi3881的代码进行改造。

HDF WiFi框架总体框架图

WLAN驱动架构组成:

img

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

img

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模块进行驱动注册等相关操作。

img

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

img

img

当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驱动涉及到的文件和目录如下:

3.1 WIFI相关的HDF框架编译控制宏

ap6256采用的是sdio总线,涉及到的通用编译控制宏如下:

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)+=(��������6�������6���)+=(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框架的驱动模块初始化

代码流程框图如下:

img

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网络节点的注册,以及芯片驱动的初始化过程。

整体流程如下:

img

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

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

img

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

img

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

img

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

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

img

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模式调用流程图如下:

img

代码位于hdf_mac80211_sta.c

struct HdfMac80211STAOps g_bdh6_staOps = {

.Connect = HdfConnect,

.Disconnect = HdfDisconnect,

.StartScan = HdfStartScan,

.AbortScan = HdfAbortScan,

.SetScanningMacAddress = HdfSetScanningMacAddress,

};

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

AP模式调用流程图如下:

img

代码位于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模式调用流程图如下:

img

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连接。

img

最后比对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接口是一样的。

img

因为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驱动模型

img

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)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

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

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

相关文章

Java入门基础知识第七课(超基础,超详细)——数组

前面二白讲了选择结构和循环结构&#xff0c;动手的同学会发现已经有了一定的难度&#xff0c;后面二白会专门收集一些经典的题目&#xff0c;训练多了才能让记忆更加深刻&#xff0c;这次咱们讲一下数组。 一、数组的定义 什么是数组呢&#xff0c;我们都知道变量是存储数据的…

第八讲 Sort Aggregate 算法

我们现在将讨论如何使用迄今为止讨论过的 DBMS 组件来执行查询。 1 查询计划【Query Plan】 我们首先来看当一个查询【Query】被解析【Parsed】后会发生什么&#xff1f; 当 SQL 查询被提供给数据库执行引擎&#xff0c;它将通过语法解析器进行检查&#xff0c;然后它会被转换…

新增长100人研讨会:20+上海医疗企业共探数字驱动下的目标管理与业绩增长策略

近日&#xff0c;纷享销客新增长100人系列活动之上海医疗专场&#xff0c;我们有幸邀请百趣生物一起&#xff0c;共同探讨医疗器械行业数字化增长的新理念、新方法和新实践。 活动聚集了百趣生物、汉维生物、松佰牙科器械、多宁生物、松佰医疗、瑞丰达医疗等20余位标杆医疗健康…

蓝桥杯简单模板

目录 最大公约数 两个数的最大公约数 多个数的最大公约数 最小公倍数 两个数的最小公倍数 多个数的最小公倍数 素数 ​编辑 位数分离 正写 ​编辑 反写 闰年 最大公约数 两个数的最大公约数 之前看见的是辗转相除法&#xff0c;例如现在让算一个49&#xff0c;21…

数码相框-LCD显示多行文字

显示几行文字: 从左显示&#xff1a;先描边再算出边框。居中显示&#xff1a;先算出边框&#xff0c;再确定坐标描画。 从左显示 第一行数据的起始位置是从(0,24)开始的。 要知道第二行数据从哪里开始&#xff0c;我们得知道画出来的矢量字体的边框是多少&#xff1a; 这个…

【C++】 详解 lower_bound 和 upper_bound 函数(看不懂来捶我!!!)

目录 一、前言 二、函数详解 &#x1f95d; lower_bound &#x1f34d;upper_bound 三、常考面试题 四、共勉 一、前言 这两个函数是我在 LeetCode 上做题见到&#xff0c;看到不熟悉的函数 lower_bound 和 upper_bound让我感觉很难受&#xff0c;于是在 C 官网去学习&…

2024HW --->反序列化漏洞!

对于反序列化&#xff0c;这个漏洞也是常用的&#xff0c;不过涉及到的方面非常非常广&#xff0c;比其他漏洞也难很多 于是本篇文章就分成PHP和JAVA的反序列化来讲讲 1.反序列化 想要理解反序列化&#xff0c;首先就要理解序列化 序列化&#xff1a;把对象转换为字节序列的过…

默克尔(Merkle)树 - 原理及用途

默克尔&#xff08;Merkle&#xff09;树的原理以及用途 引言 在当今数字化时代&#xff0c;确保数据的完整性是至关重要的。默克尔树作为一种高效的数据结构&#xff0c;被广泛应用于网络安全、分布式系统以及加密货币等领域&#xff0c;用于验证大量数据的完整性和一致性 数…

代码随想录算法训练营Day48|LC198 打家劫舍LC213 打家劫舍IILC337 打家劫舍III

一句话总结&#xff1a;前两题白给&#xff0c;第三题树形DP有点难。 原题链接&#xff1a;198 打家劫舍 滚动数组直接秒了。 class Solution {public int rob(int[] nums) {int n nums.length;int first 0, second nums[0];for (int i 2; i < n; i) {int tmp Math.m…

mega2560读取sick位移传感器

本次的项目中&#xff0c;需要使用到mega2560来读取sick位移传感器的模拟量&#xff0c;再把模拟量进行转换&#xff0c;从而使得到的数据为位移传感器的示数。 下面是位移传感器的接线图&#xff1a;棕色线接&#xff0b;24v&#xff0c;蓝色线接0v&#xff0c;白色线为模拟量…

JS 表单验证

点击注册的时候&#xff0c;渲染出来&#xff0c;验证码是自动获取出来的 html&#xff1a; <div class"div1">用户名<input type"text" id"yhm"><span id"span1"></span><br>密码<input type"…

mysql 查询变量@i:=@i+1

学习完mysql的查询&#xff1a;基本查询&#xff0c;连接查询和子查询和mysql 正则表达式查询&#xff0c;接下来先学习下变量查询。 mysql中没有oracle序列号那一列。mysql可以使用查询变量的方式去处理。我们先了解下查询变量&#xff0c;后面应用起来就更清晰。 1&#xff0…

【科东软件】鸿道Intewell-Lin_V2.2.0 软件版本发布

鸿道操作系统 Intewell-Lin_V2.2.0 软件版本发布 Intewell-Lin_V2.2.0 版本号&#xff1a;V2.2.0 版本或修改说明 增加功能&#xff1a; 1、增加T3板级支持 支持硬件列表

深入探索实时音视频技术:RTC程序设计权威指南

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

2024年32款数据分析工具分五大类总览

数据分析工具在现代商业和科学中扮演着不可或缺的角色&#xff0c;为组织和个人提供了深入洞察和明智决策的能力。这些工具不仅能够处理大规模的数据集&#xff0c;还能通过强大的分析和可视化功能揭示隐藏在数据背后的模式和趋势。数据分析工具软件主要可以划分为以下五个类别…

网络与通信-路由协议及基础配置

网络协议之路由协议 静态路由&#xff1a; 明细静态 默认静态 动态路由&#xff1a;&#xff08;可以自动去环&#xff09; RIP 十几台或几十台 &#xff08;维护上一代人搭建的网络&#xff09; OSPF 300台 &#xff08;最短路径算法&#xff09; ISIS 1200台 BGP…

深度学习500问——Chapter06: 循环神经网络(RNN)(2)

文章目录 6.4 CNN和RNN的区别 6.5 RNNs与FNNs有什么区别 6.6 RNNs训练和传统ANN训练异同点 6.7 为什么RNN训练的时候Loss波动很大 6.8 标准RNN前向输出流程 6.9 BPTT算法推导 6.9 RNN中为什么会出现梯度消失 6.10 如何解决RNN中的梯度消失问题 6.4 CNN和RNN的区别 类别特点描述…

主流三种驱动器方案特点简介

三种执行器原理相似&#xff0c;但在结构和部件上略有区别&#xff0c;因此在精度、响应速度等指标上 呈现不同效果&#xff1a; &#xff08;1&#xff09;TSA&#xff08;刚性驱动器&#xff09;&#xff1a;常规高速电机高传动比减速机高刚度力矩传感器&#xff0c;减 速机…

【Spring Cloud】服务容错中间件Sentinel入门

文章目录 什么是 SentinelSentinel 具有以下特征&#xff1a;Sentinel分为两个部分: 安装 Sentinel 控制台下载jar包&#xff0c;解压到文件夹启动控制台访问了解控制台的使用原理 微服务集成 Sentinel添加依赖增加配置测试用例编写启动程序 实现接口限流总结 欢迎来到阿Q社区 …

【QingHub】QingHub Studio企业级应用开发管理

QingHub Studio企业级应用开发设计器是QingHub Studio的一个核心模块&#xff0c;它可以实现应用搭建、团队管理&#xff0c;共享开发&#xff0c;可以快速接入API接口&#xff0c;复杂功能可以通过自定义脚本快速实现业务逻辑。打通前端开发与后台业务逻辑一体化。通过可视化的…