• OpenHarmony 3GPP协议开发深度剖析——一文读懂RIL


    (以下内容来自开发者分享,不代表 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 <stdlib.h>
    #include <libudev.h>
    #include <pthread.h>
    
    #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 16
    
    static 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 < sizeof(g_usbModemVendorInfo) / sizeof(UsbDeviceInfo); i++) {
            if (g_usbModemVendorInfo[i].idVendor == idVendor && g_usbModemVendorInfo[i].idProduct == idProduct) {
                TELEPHONY_LOGI("list index:%{public}d", i);
                uDevInfo = &g_usbModemVendorInfo[i];
                break;
            }
        }
        return uDevInfo;
    }
    
    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't create udev");
            return uDevInfo;
        }
        enumerate = udev_enumerate_new(udev);
        if (enumerate == NULL) {
            TELEPHONY_LOGE("Can'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 ril
        const HRilOps *(*rilInitOps)(const struct HRilReport *) = NULL;
        // functions returned by ril init function in vendor ril
        const 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<std::mutex> 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;
    }
    复制代码

     

     

  • 相关阅读:
    电脑如何查看是否支持虚拟化及如何开启虚拟化
    【Linux】Buffer和Cache区别和用途
    [精选] 多账号统一登录,你如何设计?
    应用启动加速-并发初始化spring bean
    网管实战⑼:配置华为S5720交换机
    瑜伽组织怎么运用智能机器人提高员工敬业度
    搭建云平台过程中的错误及解决方案1
    数字拼图游戏代码
    自动化工具Selenium IDE基本使用——脚本编辑
    2023年9月21日
  • 原文地址:https://www.cnblogs.com/openharmony/p/16228440.html