作者 软通夏德旺 在此特别鸣谢!
市面上关于终端(手机)操作系统在 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 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;
}
在AI和物联网下的运营商SIM卡如何生存
苹果官方盖章确认了?无线充电设备 AirPower胎死腹中
OPPOK3玩游戏怎么样
国内车规级IGBT厂商比亚迪半导体上市进程加快
云化科技与华为将在人工智能领域展开紧密合作
一文读懂RIL - OpenHarmony 3GPP 协议开发解读
PCB走线镀锡:用这种方法,既简单又漂亮!
MIPS落幕,转身加入RISC-V阵营
搭建链路实现FPGA到PC的数据传输使用以太网实现信息传输
智慧用电物联网系统解决方案
嵌入式开发经典算法之栈逆序
TI智能家居和家庭健康医疗展区介绍
deovo公司介绍_deovo是什么牌子
华勤技术蝉联五年全球智能手机和平板ODM行业第一
2017年中国物联网行业细分市场总结报告,看好学好把握未来趋势
中国联通在第一视角直播领域展开探索
无线医疗设备-超低功耗射频技术详解
电线电缆拉力测试全解析:如何使用电子拉力试验机进行测试?原理流程解密!
苹果专利:探索力感知织物手套,支持手势控制输入
半导体光放大器SOA的电光转化效率