OpenHarmony 3GPP協議開發深度剖析——一文讀懂RIL

来源:https://www.cnblogs.com/openharmony/archive/2022/05/06/16228440.html
-Advertisement-
Play Games

基於 OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)源碼寫點內容,幫助大家瞭解下協議開發領域,儘可能將 3gpp 協議內容與 OpenHarmony 電話子系統模塊進行結合講解。 ...


(以下內容來自開發者分享,不代表 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;
}

 

 

搜索

複製


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 Ubuntu 暫時不能解析功能變數名稱及解決辦法 可能的解決方案:重啟虛擬機網卡 前提:主機使用無線網,Win10;虛擬機採用NAT模式,Ubuntu20.04 最近移動過vmware的文件,導致虛擬機開機後使用sudo apt-get時會提示暫時不能解 ...
  • 在Linux環境下,使用Shell腳本自動備份資料庫,需要用到 crontab 定時任務,以下是使用 mysqldump 方式對資料庫備份 1、新建shell腳本,這裡命名為 dbbackup.sh /usr/bin/mysqldump -u用戶名 -p密碼 -h 資料庫IP -R --opt 要備 ...
  • 5、1 設定像素 設定像素一般用四個位元組,最高位省略不用。 // 5-1-PWCP_設定像素_顏色.cpp : 定義應用程式的入口點。 // #include "framework.h" #include "5-1-PWCP_設定像素_顏色.h" #define MAX_LOADSTRING 100 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 1 您需要瞭解 安裝源您可訪問 CentOS官網 / 阿裡雲鏡像站 等 進行下載 CentOS 7 系列其他版本安裝方法一致 為更好顯示文章層次結構,便於觀看,您可點擊左上角目錄按鈕進行瀏覽 2 安裝過程 2.1 啟動項 Install Cent ...
  • 本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 實現矩陣鍵盤掃描,當按下任意一個按鈕時,數位管立即顯示當前按下按鈕對應鍵值。 硬體設計 在第一節的基礎上,在Pr ...
  • 文章作者:阿裡零售通演算法團隊 出品社區:DataFun 導讀: 零售通作為阿裡巴巴新零售的八路大軍之一,肩負著“共建智能分銷平臺”和“讓百萬小店擁抱DT時代”的重要使命。一方面,我們通過線上平臺(零售通APP)將零售品牌商的貨品展現給小店的店主,並提供交易渠道讓店主進行批發進貨;另一方面,我們通過天 ...
  • 環境準備 客戶端 Windows 10 ArcCatalog 10.8.1 精簡版Oracle Client 12 - 32 bit 安裝包名稱:instantclient-basic-nt-12.1.0.2.0.zip Navicat Premium 15 服務端 Windows Server 2 ...
  • 線上服務的MongoDB中有一個很大的表,我查詢時使用了sort()根據某個欄位進行排序,結果報了下麵這個錯誤: [Error] Executor error during find command :: caused by :: Sort operation used more than the ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...