(以下内容来自开发者分享,不代表 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 仓的源码结构:

  1. base/telephony/ril_adapter
  2. ├── figures # readme资源文件
  3. ├── frameworks
  4. ├── BUILD.gn
  5. └── src # 序列化文件
  6. ├── interfaces # 对应提供上层各业务内部接口
  7. └── innerkits
  8. ├── services # 服务
  9. ├── hril # hril层的各个业务模块接口实现
  10. ├── hril_hdf # HDF服务
  11. └── vendor # 厂商库文件
  12. └── test # 测试代码
  13. ├── BUILD.gn
  14. ├── mock
  15. └── unittest # 单元测试代码

核心业务逻辑梳理

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

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

  1. /*
  2. * Copyright (C) 2021 Huawei Device Co., Ltd.
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15.  
  16. #include "hril_hdf.h"
  17.  
  18. #include <stdlib.h>
  19. #include <libudev.h>
  20. #include <pthread.h>
  21.  
  22. #include "dfx_signal_handler.h"
  23. #include "parameter.h"
  24.  
  25. #include "modem_adapter.h"
  26. #include "telephony_log_c.h"
  27.  
  28. #define RIL_VENDOR_LIB_PATH "persist.sys.radio.vendorlib.path"
  29. #define BASE_HEX 16
  30.  
  31. static struct HRilReport g_reportOps = {
  32. OnCallReport,
  33. OnDataReport,
  34. OnModemReport,
  35. OnNetworkReport,
  36. OnSimReport,
  37. OnSmsReport,
  38. OnTimerCallback
  39. };
  40.  
  41. static int32_t GetVendorLibPath(char *path)
  42. {
  43. int32_t code = GetParameter(RIL_VENDOR_LIB_PATH, "", path, PARAMETER_SIZE);
  44. if (code <= 0) {
  45. TELEPHONY_LOGE("Failed to get vendor library path through system properties. err:%{public}d", code);
  46. return HDF_FAILURE;
  47. }
  48. return HDF_SUCCESS;
  49. }
  50.  
  51. static UsbDeviceInfo *GetPresetInformation(const char *vId, const char *pId)
  52. {
  53. char *out = NULL;
  54. UsbDeviceInfo *uDevInfo = NULL;
  55. int32_t idVendor = (int32_t)strtol(vId, &out, BASE_HEX);
  56. int32_t idProduct = (int32_t)strtol(pId, &out, BASE_HEX);
  57. for (uint32_t i = 0; i < sizeof(g_usbModemVendorInfo) / sizeof(UsbDeviceInfo); i++) {
  58. if (g_usbModemVendorInfo[i].idVendor == idVendor && g_usbModemVendorInfo[i].idProduct == idProduct) {
  59. TELEPHONY_LOGI("list index:%{public}d", i);
  60. uDevInfo = &g_usbModemVendorInfo[i];
  61. break;
  62. }
  63. }
  64. return uDevInfo;
  65. }
  66.  
  67. static UsbDeviceInfo *GetUsbDeviceInfo(void)
  68. {
  69. struct udev *udev;
  70. struct udev_enumerate *enumerate;
  71. struct udev_list_entry *devices, *dev_list_entry;
  72. struct udev_device *dev;
  73. UsbDeviceInfo *uDevInfo = NULL;
  74.  
  75. udev = udev_new();
  76. if (udev == NULL) {
  77. TELEPHONY_LOGE("Can't create udev");
  78. return uDevInfo;
  79. }
  80. enumerate = udev_enumerate_new(udev);
  81. if (enumerate == NULL) {
  82. TELEPHONY_LOGE("Can't create enumerate");
  83. return uDevInfo;
  84. }
  85. udev_enumerate_add_match_subsystem(enumerate, "tty");
  86. udev_enumerate_scan_devices(enumerate);
  87. devices = udev_enumerate_get_list_entry(enumerate);
  88. udev_list_entry_foreach(dev_list_entry, devices) {
  89. const char *path = udev_list_entry_get_name(dev_list_entry);
  90. if (path == NULL) {
  91. continue;
  92. }
  93. dev = udev_device_new_from_syspath(udev, path);
  94. if (dev == NULL) {
  95. continue;
  96. }
  97. dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
  98. if (!dev) {
  99. TELEPHONY_LOGE("Unable to find parent usb device.");
  100. return uDevInfo;
  101. }
  102. const char *cIdVendor = udev_device_get_sysattr_value(dev, "idVendor");
  103. const char *cIdProduct = udev_device_get_sysattr_value(dev, "idProduct");
  104. uDevInfo = GetPresetInformation(cIdVendor, cIdProduct);
  105. udev_device_unref(dev);
  106. if (uDevInfo != NULL) {
  107. break;
  108. }
  109. }
  110. udev_enumerate_unref(enumerate);
  111. udev_unref(udev);
  112. return uDevInfo;
  113. }
  114.  
  115. static void LoadVendor(void)
  116. {
  117. const char *rilLibPath = NULL;
  118. char vendorLibPath[PARAMETER_SIZE] = {0};
  119. // Pointer to ril init function in vendor ril
  120. const HRilOps *(*rilInitOps)(const struct HRilReport *) = NULL;
  121. // functions returned by ril init function in vendor ril
  122. const HRilOps *ops = NULL;
  123.  
  124. UsbDeviceInfo *uDevInfo = GetUsbDeviceInfo();
  125. if (GetVendorLibPath(vendorLibPath) == HDF_SUCCESS) {
  126. rilLibPath = vendorLibPath;
  127. } else if (uDevInfo != NULL) {
  128. rilLibPath = uDevInfo->libPath;
  129. } else {
  130. TELEPHONY_LOGI("use default vendor lib.");
  131. rilLibPath = g_usbModemVendorInfo[DEFAULT_MODE_INDEX].libPath;
  132. }
  133. if (rilLibPath == NULL) {
  134. TELEPHONY_LOGE("dynamic library path is empty");
  135. return;
  136. }
  137.  
  138. TELEPHONY_LOGI("RilInit LoadVendor start with rilLibPath:%{public}s", rilLibPath);
  139. g_dlHandle = dlopen(rilLibPath, RTLD_NOW);
  140. if (g_dlHandle == NULL) {
  141. TELEPHONY_LOGE("dlopen %{public}s is fail. %{public}s", rilLibPath, dlerror());
  142. return;
  143. }
  144. rilInitOps = (const HRilOps *(*)(const struct HRilReport *))dlsym(g_dlHandle, "RilInitOps");
  145. if (rilInitOps == NULL) {
  146. dlclose(g_dlHandle);
  147. TELEPHONY_LOGE("RilInit not defined or exported");
  148. return;
  149. }
  150. ops = rilInitOps(&g_reportOps);
  151. HRilRegOps(ops);
  152. TELEPHONY_LOGI("HRilRegOps completed");
  153. }
  154.  
  155. // 用来处理用户态发下来的消息
  156. static int32_t RilAdapterDispatch(
  157. struct HdfDeviceIoClient *client, int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *reply)
  158. {
  159. int32_t ret;
  160. static pthread_mutex_t dispatchMutex = PTHREAD_MUTEX_INITIALIZER;
  161. pthread_mutex_lock(&dispatchMutex);
  162. TELEPHONY_LOGI("RilAdapterDispatch cmd:%{public}d", cmd);
  163. ret = DispatchRequest(cmd, data);
  164. pthread_mutex_unlock(&dispatchMutex);
  165. return ret;
  166. }
  167.  
  168. static struct IDeviceIoService g_rilAdapterService = {
  169. .Dispatch = RilAdapterDispatch,
  170. .Open = NULL,
  171. .Release = NULL,
  172. };
  173.  
  174. //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
  175. static int32_t RilAdapterBind(struct HdfDeviceObject *device)
  176. {
  177. if (device == NULL) {
  178. return HDF_ERR_INVALID_OBJECT;
  179. }
  180. device->service = &g_rilAdapterService;
  181. return HDF_SUCCESS;
  182. }
  183.  
  184. // 驱动自身业务初始的接口
  185. static int32_t RilAdapterInit(struct HdfDeviceObject *device)
  186. {
  187. if (device == NULL) {
  188. return HDF_ERR_INVALID_OBJECT;
  189. }
  190. DFX_InstallSignalHandler();
  191. struct HdfSBuf *sbuf = HdfSbufTypedObtain(SBUF_IPC);
  192. if (sbuf == NULL) {
  193. TELEPHONY_LOGE("HdfSampleDriverBind, failed to obtain ipc sbuf");
  194. return HDF_ERR_INVALID_OBJECT;
  195. }
  196. if (!HdfSbufWriteString(sbuf, "string")) {
  197. TELEPHONY_LOGE("HdfSampleDriverBind, failed to write string to ipc sbuf");
  198. HdfSbufRecycle(sbuf);
  199. return HDF_FAILURE;
  200. }
  201. if (sbuf != NULL) {
  202. HdfSbufRecycle(sbuf);
  203. }
  204. TELEPHONY_LOGI("sbuf IPC obtain success!");
  205. LoadVendor();
  206. return HDF_SUCCESS;
  207. }
  208.  
  209. // 驱动资源释放的接口
  210. static void RilAdapterRelease(struct HdfDeviceObject *device)
  211. {
  212. if (device == NULL) {
  213. return;
  214. }
  215. dlclose(g_dlHandle);
  216. }
  217.  
  218. //驱动入口注册到HDF框架,这里配置的moduleName是找到Telephony模块与RIL进行通信的一个关键配置
  219. struct HdfDriverEntry g_rilAdapterDevEntry = {
  220. .moduleVersion = 1,
  221. .moduleName = "hril_hdf",
  222. .Bind = RilAdapterBind,
  223. .Init = RilAdapterInit,
  224. .Release = RilAdapterRelease,
  225. };
  226. // 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
  227. HDF_INIT(g_rilAdapterDevEntry);

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

  1. riladapter :: host {
  2. hostName = "riladapter_host";
  3. priority = 50;
  4. riladapter_device :: device {
  5. device0 :: deviceNode {
  6. policy = 2;
  7. priority = 100;
  8. moduleName = "libhril_hdf.z.so";
  9. serviceName = "cellular_radio1";
  10. }
  11. }
  12. }

这里可以发现该驱动对应的服务名称为 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 驱动对应的服务名称。

  1. bool TelRilManager::ConnectRilAdapterService()
  2. {
  3. std::lock_guard<std::mutex> lock_l(mutex_);
  4. rilAdapterRemoteObj_ = nullptr;
  5. auto servMgr_ = OHOS::HDI::ServiceManager::V1_0::IServiceManager::Get();
  6. if (servMgr_ == nullptr) {
  7. TELEPHONY_LOGI("Get service manager error!");
  8. return false;
  9. }
  10.  
  11. //通过HDF框架获取RIL_ADAPTER的服务,之前定义过RIL_ADAPTER_SERVICE_NAME常量为"cellular_radio1",它就是在 vendor_hisilicon/ XXXX / hdf_config / uhdf / device_info.hcs中配置的hril_hdf驱动对应的服务名称
  12. rilAdapterRemoteObj_ = servMgr_->GetService(RIL_ADAPTER_SERVICE_NAME.c_str());
  13. if (rilAdapterRemoteObj_ == nullptr) {
  14. TELEPHONY_LOGE("bind hdf error!");
  15. return false;
  16. }
  17. if (death_ == nullptr) {
  18. TELEPHONY_LOGE("create HdfDeathRecipient object failed!");
  19. rilAdapterRemoteObj_ = nullptr;
  20. return false;
  21. }
  22. if (!rilAdapterRemoteObj_->AddDeathRecipient(death_)) {
  23. TELEPHONY_LOGE("AddDeathRecipient hdf failed!");
  24. rilAdapterRemoteObj_ = nullptr;
  25. return false;
  26. }
  27.  
  28. int32_t ret = SetCellularRadioIndication();
  29. if (ret != CORE_SERVICE_SUCCESS) {
  30. TELEPHONY_LOGE("SetCellularRadioIndication error, ret:%{public}d", ret);
  31. return false;
  32. }
  33. ret = SetCellularRadioResponse();
  34. if (ret != CORE_SERVICE_SUCCESS) {
  35. TELEPHONY_LOGE("SetCellularRadioResponse error, ret:%{public}d", ret);
  36. return false;
  37. }
  38.  
  39. return true;
  40. }

搜索

复制

OpenHarmony 3GPP协议开发深度剖析——一文读懂RIL的更多相关文章

  1. 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...

  2. [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路   http://www.52im.net/thread-1709-1-2.html     本文原作者阮一峰,作者博客:r ...

  3. 一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm)

    一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm) 2017-12-25  16:29:19   对于 A3C 算法感觉自己总是一知半解,现将其梳理一下,记录在此,也 ...

  4. 即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?

    本文引用了“蔷薇Nina”的“Nginx 相关介绍(Nginx是什么?能干嘛?)”一文部分内容,感谢作者的无私分享. 1.引言   Nginx(及其衍生产品)是目前被大量使用的服务端反向代理和负载均衡 ...

  5. 一文读懂高性能网络编程中的I/O模型

    1.前言 随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力.本文(和下篇<高性能网络编程(六):一文读懂高性能网络编程中的线程模型>)旨在为大家提供有用的 ...

  6. [转帖]一文读懂 HTTP/2

    一文读懂 HTTP/2 http://support.upyun.com/hc/kb/article/1048799/ 又小拍 • 发表于:2017年05月18日 15:34:45 • 更新于:201 ...

  7. 一文读懂HTTP/2及HTTP/3特性

    摘要: 学习 HTTP/2 与 HTTP/3. 前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何 ...

  8. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  9. [转帖]MerkleDAG全面解析 一文读懂什么是默克尔有向无环图

    MerkleDAG全面解析 一文读懂什么是默克尔有向无环图 2018-08-16 15:58区块链/技术 MerkleDAG作为IPFS的核心数据结构,它融合了Merkle Tree和DAG的优点,今 ...

随机推荐

  1. Java 中 IO 流分为几种?

    按功能来分:输入流(input).输出流(output).按类型来分:字节流和字符流.字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数 ...

  2. Java 中怎么获取一份线程 dump 文件?

    在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件.在 Windows 下,你可以按下 Ctrl + Break 来获取 ...

  3. C++模板学习之优先队列实现

    转载:https://www.cnblogs.com/muzicangcang/p/10579250.html 今天将继续将强C++模板类的学习,同时为了巩固已经学习过的数据结构中有关优先队列的知识, ...

  4. 线程池提交任务的两种方式:execute与submit的区别

    Java中的线程池在进行任务提交时,有两种方式:execute和submit方法. 一.execute和submit的区别 execute只能提交Runnable类型的任务,无返回值.submit既可 ...

  5. 使用 Spring 访问 Hibernate 的方法有哪些?

    我们可以通过两种方式使用 Spring 访问 Hibernate: 1. 使用 Hibernate 模板和回调进行控制反转 2. 扩展 HibernateDAOSupport 并应用 AOP 拦截器节 ...

  6. 学习k8s(二)

    kubernetes-国内拉取gcr.io\quay.io镜像方法 方法1: https://hub.docker.com/r/ibmcom/ 例如: gcr.io/google_containers ...

  7. MM32F0020 UART1硬件自动波特率的使用

    目录: 1.MM32F0020简介 2.UART自动波特率校准应用场景 3.MM32F0020 UART自动波特率校准原理简介 4.MM32F0020 UART1 NVIC硬件自动波特率配置以及初始化 ...

  8. 【控制】模型预测控制 MPC 【合集】Model Predictive Control

    1.模型预测控制--运动学模型 2.模型预测控制--模型线性化 3.模型预测控制--模型离散化 4.模型预测控制--预测 5.模型预测控制--控制律优化二次型优化 6.模型预测控制--反馈控制 7.模 ...

  9. mybatis 实现分页和过滤模糊查询

    基于 mybatis 的分页和过滤查询 学习内容: 分页设计 1.分页需传递的参数 2.分页需展示的数据 3.分页需展示的数据的来源 3.1.结果总数与结果集(分页原理) 3.2.总页数.上一页和下一 ...

  10. 关于Symbol.iterator 学习笔记

    1.可以部署在对象上的一个遍历器 2. 遍历器是一个函数,需要返回一个含有一个next 方法的对象 const likeArray = {0:'a', 1: 'b', 2: 'c',3: 'd'. l ...