基於 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; }
搜索
複製