热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

OpenHarmony3GPP协议开发深度剖析——一文读懂RIL

(以下内容来自开发者分享,不代表OpenHarmony项目群工作委员会观点)本文转载自:https:harmonyos.51

(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点)
本文转载自:https://harmonyos.51cto.com/posts/10608

夏德旺

软通动力信息技术(集团)股份有限公司


前言


市面上关于终端(手机)操作系统在 3GPP 协议开发的内容太少了,即使 Android 相关的资料都很少,Android 协议开发书籍我是没有见过的。可能是市场需求的缘故吧,现在市场上还是前后端软件开发从业人员最多,包括我自己。

基于我曾经也在某手机协议开发团队干过一段时间,协议的 AP 侧和 CP 侧开发都整过,于是想尝试下基于 OpenAtom OpenHarmony(以下简称“OpenHarmony”)源码写点内容,帮助大家了解下协议开发领域,尽可能将 3gpp 协议内容与 OpenHarmony 电话子系统模块进行结合讲解。据我所知,现在终端协议开发非常缺人。首先声明我不是协议专家,我也离开该领域有五六年了,如有错误,欢迎指正。


等我觉得自己整明白了,就会考虑出本《OpenHarmony 3GPP 协议开发深度剖析》书籍。


提到终端协议开发,我首先想到的就是 RIL 了。


专有名词


CP:Communication Processor(通信处理器),我一般就简单理解为 modem 侧,也可以理解为底层协议,这部分由各个 modem 芯片厂商完成(比如海思、高通)。

AP:Application Processor(应用处理器),通常就是指的手机终端,我一般就简单理解为上层协议,主要由操作系统 Telephony 服务来进行处理。

RIL: Radio Interface Layer(无线电接口层),我一般就简单理解为硬件抽象层,即 AP 侧将通信请求传给 CP 侧的中间层。

AT指令: AT 指令是应用于终端设备与 PC 应用之间的连接与通信的指令。


设计思想


常规的 Modem 开发与调试可以使用 AT 指令来进行操作,而各家的 Modem 芯片的 AT 指令都会有各自的差异。因此手机终端厂商为了能在各种不同型号的产品中集成不同 modem 芯片,需要进行解耦设计来屏蔽各家 AT 指令的差异。

于是 OpenHarmony 采用 RIL 对 Modem 进行 HAL(硬件抽象),作为系统与 Modem 之间的通信桥梁,为 AP 侧提供控制 Modem 的接口,各 Modem 厂商则负责提供对应于 AT 命令的 Vender RIL(这些一般为封装好的 so 库),从而实现操作系统与 Modem 间的解耦。


OpenHarmony RIL架构
 

框架层:Telephony Service,电话子系统核心服务模块,主要功能是初始化 RIL 管理、SIM 卡和搜网模块。对应 OpenHarmony 的源码仓库 OpenHarmony / telephony_core_service。这个模块也是非常重要的一个模块,后期单独再做详细解读。

硬件抽象层:即我们要讲的 RIL,对应 OpenHarmony 的源码仓库 OpenHarmony / telephony_ril_adapter。RIL Adapter 模块主要包括厂商库加载,业务接口实现以及事件调度管理。主要用于屏蔽不同 modem 厂商硬件差异,为上层提供统一的接口,通过注册 HDF 服务与上层接口通讯。

芯片层:Modem 芯片相关代码,即 CP 侧,这些代码各个 Modem 厂商是不开放的,不出现在 OpenHarmony 中。


硬件抽象层


硬件抽象层又被划分为了 hril_hdf 层、hril 层和 venderlib 层。

hril_hdf层:HDF 服务,基于 OpenHarmony HDF 框架,提供 hril 层与 Telephony Service 层进行通讯。

hril 层:hril 层的各个业务模块接口实现,比如通话、短彩信、数据业务等。

vendorlib层:各 Modem 厂商提供的对应于 AT 命令库,各个厂商可以出于代码闭源政策,在这里以 so 库形式提供。目前源码仓中已经提供了一套提供代码的 AT 命令操作,至于这个是针对哪个型号 modem 芯片的,我后续了解清楚再补充。

下面是 ril_adapter 仓的源码结构:

base/telephony/ril_adapter
├── figures # readme资源文件
├── frameworks
│ ├── BUILD.gn
│ └── src # 序列化文件
├── interfaces # 对应提供上层各业务内部接口
│ └── innerkits
├── services # 服务
│ ├── hril # hril层的各个业务模块接口实现
│ ├── hril_hdf # HDF服务
│ └── vendor # 厂商库文件
└── test # 测试代码├── BUILD.gn├── mock└── unittest # 单元测试代码


核心业务逻辑梳理


本文解读 RIL 层很小一部分代码,RIL 是如何通过 HDF 与 Telephony 连接上的,以后更加完整的逻辑梳理会配上时序图讲解,会更加清晰。首先我们要对 OpenHarmony 的 HDF(Hardware Driver Foundation)驱动框架做一定了解,最好是动手写一个 Demo 案例,具体的可以单独去官网查阅 HDF 资料。

首先,找到 hril_hdf.c 文件的代码,它承担的是驱动业务部分,源码中是不带中文注释的,为了梳理清楚流程,我给源码关键部分加上了中文注释。

/** Copyright (C) 2021 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#include "hril_hdf.h"#include
#include
#include

#include "dfx_signal_handler.h"
#include "parameter.h"#include "modem_adapter.h"
#include "telephony_log_c.h"#define RIL_VENDOR_LIB_PATH "persist.sys.radio.vendorlib.path"
#define BASE_HEX 16static struct HRilReport g_reportOps = {OnCallReport,OnDataReport,OnModemReport,OnNetworkReport,OnSimReport,OnSmsReport,OnTimerCallback
};static int32_t GetVendorLibPath(char *path)
{int32_t code = GetParameter(RIL_VENDOR_LIB_PATH, "", path, PARAMETER_SIZE);if (code <= 0) {TELEPHONY_LOGE("Failed to get vendor library path through system properties. err:%{public}d", code);return HDF_FAILURE;}return HDF_SUCCESS;
}static UsbDeviceInfo *GetPresetInformation(const char *vId, const char *pId)
{char *out = NULL;UsbDeviceInfo *uDevInfo = NULL;int32_t idVendor = (int32_t)strtol(vId, &out, BASE_HEX);int32_t idProduct = (int32_t)strtol(pId, &out, BASE_HEX);for (uint32_t i = 0; i }static UsbDeviceInfo *GetUsbDeviceInfo(void)
{struct udev *udev;struct udev_enumerate *enumerate;struct udev_list_entry *devices, *dev_list_entry;struct udev_device *dev;UsbDeviceInfo *uDevInfo = NULL;udev = udev_new();if (udev == NULL) {TELEPHONY_LOGE("Can&#39;t create udev");return uDevInfo;}enumerate = udev_enumerate_new(udev);if (enumerate == NULL) {TELEPHONY_LOGE("Can&#39;t create enumerate");return uDevInfo;}udev_enumerate_add_match_subsystem(enumerate, "tty");udev_enumerate_scan_devices(enumerate);devices = udev_enumerate_get_list_entry(enumerate);udev_list_entry_foreach(dev_list_entry, devices) {const char *path = udev_list_entry_get_name(dev_list_entry);if (path == NULL) {continue;}dev = udev_device_new_from_syspath(udev, path);if (dev == NULL) {continue;}dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");if (!dev) {TELEPHONY_LOGE("Unable to find parent usb device.");return uDevInfo;}const char *cIdVendor = udev_device_get_sysattr_value(dev, "idVendor");const char *cIdProduct = udev_device_get_sysattr_value(dev, "idProduct");uDevInfo = GetPresetInformation(cIdVendor, cIdProduct);udev_device_unref(dev);if (uDevInfo != NULL) {break;}}udev_enumerate_unref(enumerate);udev_unref(udev);return uDevInfo;
}static void LoadVendor(void)
{const char *rilLibPath = NULL;char vendorLibPath[PARAMETER_SIZE] = {0};// Pointer to ril init function in vendor rilconst HRilOps *(*rilInitOps)(const struct HRilReport *) = NULL;// functions returned by ril init function in vendor rilconst HRilOps *ops = NULL;UsbDeviceInfo *uDevInfo = GetUsbDeviceInfo();if (GetVendorLibPath(vendorLibPath) == HDF_SUCCESS) {rilLibPath = vendorLibPath;} else if (uDevInfo != NULL) {rilLibPath = uDevInfo->libPath;} else {TELEPHONY_LOGI("use default vendor lib.");rilLibPath = g_usbModemVendorInfo[DEFAULT_MODE_INDEX].libPath;}if (rilLibPath == NULL) {TELEPHONY_LOGE("dynamic library path is empty");return;}TELEPHONY_LOGI("RilInit LoadVendor start with rilLibPath:%{public}s", rilLibPath);g_dlHandle = dlopen(rilLibPath, RTLD_NOW);if (g_dlHandle == NULL) {TELEPHONY_LOGE("dlopen %{public}s is fail. %{public}s", rilLibPath, dlerror());return;}rilInitOps = (const HRilOps *(*)(const struct HRilReport *))dlsym(g_dlHandle, "RilInitOps");if (rilInitOps == NULL) {dlclose(g_dlHandle);TELEPHONY_LOGE("RilInit not defined or exported");return;}ops = rilInitOps(&g_reportOps);HRilRegOps(ops);TELEPHONY_LOGI("HRilRegOps completed");
}// 用来处理用户态发下来的消息
static int32_t RilAdapterDispatch(struct HdfDeviceIoClient *client, int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *reply)
{int32_t ret;static pthread_mutex_t dispatchMutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&dispatchMutex);TELEPHONY_LOGI("RilAdapterDispatch cmd:%{public}d", cmd);ret = DispatchRequest(cmd, data);pthread_mutex_unlock(&dispatchMutex);return ret;
}static struct IDeviceIoService g_rilAdapterService = {.Dispatch = RilAdapterDispatch,.Open = NULL,.Release = NULL,
};//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
static int32_t RilAdapterBind(struct HdfDeviceObject *device)
{if (device == NULL) {return HDF_ERR_INVALID_OBJECT;}device->service = &g_rilAdapterService;return HDF_SUCCESS;
}// 驱动自身业务初始的接口
static int32_t RilAdapterInit(struct HdfDeviceObject *device)
{if (device == NULL) {return HDF_ERR_INVALID_OBJECT;}DFX_InstallSignalHandler();struct HdfSBuf *sbuf = HdfSbufTypedObtain(SBUF_IPC);if (sbuf == NULL) {TELEPHONY_LOGE("HdfSampleDriverBind, failed to obtain ipc sbuf");return HDF_ERR_INVALID_OBJECT;}if (!HdfSbufWriteString(sbuf, "string")) {TELEPHONY_LOGE("HdfSampleDriverBind, failed to write string to ipc sbuf");HdfSbufRecycle(sbuf);return HDF_FAILURE;}if (sbuf != NULL) {HdfSbufRecycle(sbuf);}TELEPHONY_LOGI("sbuf IPC obtain success!");LoadVendor();return HDF_SUCCESS;
}// 驱动资源释放的接口
static void RilAdapterRelease(struct HdfDeviceObject *device)
{if (device == NULL) {return;}dlclose(g_dlHandle);
}//驱动入口注册到HDF框架,这里配置的moduleName是找到Telephony模块与RIL进行通信的一个关键配置
struct HdfDriverEntry g_rilAdapterDevEntry = {.moduleVersion = 1,.moduleName = "hril_hdf",.Bind = RilAdapterBind,.Init = RilAdapterInit,.Release = RilAdapterRelease,
};
// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
HDF_INIT(g_rilAdapterDevEntry);

上述代码中配置了对应该驱动的 moduleName 为"hril_hdf",因此我们需要去找到对应驱动的配置文件,以 Hi3516DV300 开发板为例,它的驱动配置在 vendor_hisilicon/ Hi3516DV300 / hdf_config / uhdf / device_info.hcs 代码中可以找到,如下:

riladapter :: host {hostName = "riladapter_host";priority = 50;riladapter_device :: device {device0 :: deviceNode {policy = 2;priority = 100;moduleName = "libhril_hdf.z.so";serviceName = "cellular_radio1";}}}

这里可以发现该驱动对应的服务名称为 cellular_radio1,那么 telephony_core_service 通过 HDF 与 RIL 进行通信肯定会调用到该服务名称,因此无查找 telephony_core_service 的相关代码,可以很快定位到 telephony_core_service/ services / tel_ril / src / tel_ril_manager.cpp 该代码,改代码中有一个关键类 TelRilManager,它用来负责管理 tel_ril。

看 tel_ril_manager.cpp 中的一个关键函数 ConnectRilAdapterService,它就是用来通过 HDF 框架获取RIL_ADAPTER 的服务,之前定义过 RIL_ADAPTER_SERVICE_NAME 常量为 "cellular_radio1",它就是在 vendor_hisilicon/ XXXX / hdf_config / uhdf / device_info.hcs 中配置的 hril_hdf 驱动对应的服务名称。

bool TelRilManager::ConnectRilAdapterService()
{std::lock_guard lock_l(mutex_);rilAdapterRemoteObj_ = nullptr;auto servMgr_ = OHOS::HDI::ServiceManager::V1_0::IServiceManager::Get();if (servMgr_ == nullptr) {TELEPHONY_LOGI("Get service manager error!");return false;}//通过HDF框架获取RIL_ADAPTER的服务,之前定义过RIL_ADAPTER_SERVICE_NAME常量为"cellular_radio1",它就是在 vendor_hisilicon/ XXXX / hdf_config / uhdf / device_info.hcs中配置的hril_hdf驱动对应的服务名称 rilAdapterRemoteObj_ = servMgr_->GetService(RIL_ADAPTER_SERVICE_NAME.c_str());if (rilAdapterRemoteObj_ == nullptr) {TELEPHONY_LOGE("bind hdf error!");return false;}if (death_ == nullptr) {TELEPHONY_LOGE("create HdfDeathRecipient object failed!");rilAdapterRemoteObj_ = nullptr;return false;}if (!rilAdapterRemoteObj_->AddDeathRecipient(death_)) {TELEPHONY_LOGE("AddDeathRecipient hdf failed!");rilAdapterRemoteObj_ = nullptr;return false;}int32_t ret = SetCellularRadioIndication();if (ret != CORE_SERVICE_SUCCESS) {TELEPHONY_LOGE("SetCellularRadioIndication error, ret:%{public}d", ret);return false;}ret = SetCellularRadioResponse();if (ret != CORE_SERVICE_SUCCESS) {TELEPHONY_LOGE("SetCellularRadioResponse error, ret:%{public}d", ret);return false;}return true;
}


推荐阅读
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • Android native层服务例子Bp和Bn
    转入android阵地,被各种权限所阻挠,app写个jni各种没有权限,只能开个native服务,本来android的服务& ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 微软评估和规划(MAP)的工具包介绍及应用实验手册
    本文介绍了微软评估和规划(MAP)的工具包,该工具包是一个无代理工具,旨在简化和精简通过网络范围内的自动发现和评估IT基础设施在多个方案规划进程。工具包支持库存和使用用于SQL Server和Windows Server迁移评估,以及评估服务器的信息最广泛使用微软的技术。此外,工具包还提供了服务器虚拟化方案,以帮助识别未被充分利用的资源和硬件需要成功巩固服务器使用微软的Hyper - V技术规格。 ... [详细]
  • 目录浏览漏洞与目录遍历漏洞的危害及修复方法
    本文讨论了目录浏览漏洞与目录遍历漏洞的危害,包括网站结构暴露、隐秘文件访问等。同时介绍了检测方法,如使用漏洞扫描器和搜索关键词。最后提供了针对常见中间件的修复方式,包括关闭目录浏览功能。对于保护网站安全具有一定的参考价值。 ... [详细]
  • 本文介绍了使用FormData对象上传文件同时附带其他参数的方法。通过创建一个表单,将文件和参数添加到FormData对象中,然后使用ajax发送POST请求进行文件上传。在发送请求时,需要设置processData为false,告诉jquery不要处理发送的数据;同时设置contentType为false,告诉jquery不要设置content-Type请求头。 ... [详细]
  • 本文介绍了在PostgreSQL中批量导入数据时的优化方法。包括使用unlogged表、删除重建索引、删除重建外键、禁用触发器、使用COPY方法、批量插入等。同时还提到了一些参数优化的注意事项,如设置effective_cache_size、shared_buffer等,并强调了在导入大量数据后使用analyze命令重新收集统计信息的重要性。 ... [详细]
author-avatar
墨镜小靖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有