RIL适配方法介绍

在OpenHarmony中,适配不同modem芯片,主要工作是适配OpenHarmony中的telephony中的RIL模块。

1. telephony架构图

在OpenHarmony中,telephony子系统架构图如下

在telephony架构中,分为应用层,框架层,系统服务层,RIL适配层。

  • 应用层

上层应用接口支持,对上层应用提供功能接口

  • 框架层

提供电话业务相关功能,包含通话管理,蜂窝通话,蜂窝数据,短彩信等功能。

  • 系统服务层

电话服务核心层,包含SIM卡管理和搜网,以及底层接口支持。

  • RIL适配层

ril功能适配,提供电话服务和modem硬件之间的通信桥梁。实现包括芯片适配,厂商库加载,时间调度管理,业务抽象接口,接口实现等功能。

telephony框架图

在telephony框架中,分为应用层,框架层,电话服务层,RIL适配层。

 

各组件功能

部件名称 组件职责
call_manager 通话管理 主要管理CS(Circuit Switch,电路交换)、IMS(IP Multimedia Subsystem,IP多媒体子系统)和OTT(over the top,OTT解决方案)三种类型的通话,负责申请通话所需要的音视频资源,并处理多路通话时产生的各种冲突。通话管理主要分为UI交互、服务管理、系统通话管理、通话音频管理、通话视频管理和蓝牙通话管理六大模块。
cellular_call 蜂窝通话 提供基于运营商网络的基础通话实现,包含基于2G/3G的CS(Circuit Switch,电路交换)通话和基于4G/5G的IMS(IP Multimedia Subsystem,IP多媒体子系统)通话,包含VoLTE/ VoWIFI/ VoNR语音、视频、会议,支持CS和IMS通话之间的域选控制和切换,支持紧急通话。
cellular_data 蜂窝数据 提供蜂窝数据联网能力,包括蜂窝数据开启和关闭、蜂窝数据异常检测与恢复、蜂窝数据状态管理、蜂窝数据漫游管理、APN管理、网络管理交互等功能。蜂窝数据模块作为电话子系统可裁剪部件,依赖于core_service核心服务、ril_adapter。
sms_mms 短彩信 提供短信收发和彩信编解码功能,主要功能有GSM/CDMA短信收发、短信PDU(Protocol data unit,协议数据单元)编解码、Wap Push接收处理 、小区广播接收、彩信通知、 彩信编解码和SIM卡短信记录增删改查等。
state_registry 状态注册 提供各种消息事件的订阅以及取消订阅的能力。事件类型包括网络状态变化、信号强度变化、小区信息变化、蜂窝数据连接状态变化、通话状态变化等等。
telephony_data 蜂窝数据存储 提供电话服务子系统中的SIM卡、短彩信、APN、SIM卡账户等模块持久化数据存储,提供DataAbility访问接口。
core_service 蜂窝通信核心服务 提供SIM卡服务、搜网服务和RIL管理,以及获取RIL Adapter服务。 通过HDI实现与RIL Adapter进行通信;通过发布订阅,来实现与各功能模块的通信。
ril_adapter RIL适配 提供厂商库加载,业务接口实现以及事件调度管理。主要用于屏蔽不同Modem厂商硬件差异,为上层提供统一的接口,通过注册HDF服务与上层接口通讯。

 

 

2. RIL架构图

在RIL适配层中,RIl Adapter使用分层模块化设计,分为hril_hdf层,hril层(接口实现)和vendorlib层(RIL适配库)3个模块,其中vendorlib层(RIL适配库)文件是我们需要实现的部分。

hril_hdf将动态加载vendorlib库

hril_hdf可以通过vendorlib来控制modem

vendorlib从hril_hdf获取上层的请求和上报的函数,并在vendorlib实现这些函数。

hril层

对请求的业务进行具体业务识别,并将具体的业务分配到对应的业务模块中。

包含6个业务处理能力(network搜网,call蜂窝通话,data蜂窝数据,sms短彩信,simSIM卡信息,modem设备管理)和业务分发/整合能力(HRilManager),该模块为业务接口实现功能。上层下发请求时通过HRilManager分发到对应的业务处理能力通道里。响应时通过HRilManager整合业务处理能力通道里的数据,并将数据传给上层。

vendorlib层

对具体的业务请求进行实现,对下发到modem设备以及modem上报的事件进行处理。

包含8个业务处理能力(network搜网,call蜂窝通话,data蜂窝数据,sms短彩信,simSIM卡信息,modem设备管理),数据通信能力(support)和mdoem存活与事件监听能力(readLoop),共8个子模块组成。

 

RIL库加载流程

vendor库的加载流程如下

1.HDF加载ril_driver库

2.ril_driver调用hril_hdf的init()接口

3.hril_hdf调用LoadVendor(),加载RIL适配库

RIL库加载函数调用流程

1.HDF加载ril_driver。 HDF--->ril_driver

2.ril_driver被加载时,调用初始化函数DriverInit()函数。

3.在DriveInit()中,调用hril_hdf的InitRilAdapter()函数。 ril_driver--->hril_hdf

4.在InitRilAdapter()中,调用LoadVendor()函数,加载RIL适配库。 hril_hdf--->vendorlib

 

hril_hdf的作用

  • 加载厂商库

 

hril的作用

  • 对HDI提供接口,供上层调用

  • 对vendor提供上报接口,把vendor的信息上报给HDI

 

vendor的作用

  • RIL适配库,连接不同modem硬件和OpenHarmony系统

  • 函数接口实现,给modem下发请求,把mdoem的信息上报给系统。

 

3. RIL库加载

3.1 配置host节点

OpenHarmony默认没有riladapter_host节点,需要开发者把ril节点配置出来。

配置riladapter_host节点后,HDF就会加载ril_driver库,进而加载RIL适配库,整个加载过程就此完成。

下面以rk3568为例,在device_info.hcs文件中配置riladapter_host节点。用户根据实际情况,修改对应device_info.hcs文件,配置出riladapter_host节点。

rk3568的device_info.hcs路径如下

vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs

riladapter :: host {
            hostName = "riladapter_host";  //进程名称
            priority = 50;
            riladapter_device :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 100;
                    moduleName = "libril_driver.z.so";
                    serviceName = "ril_service";
                }
            }
        }

配置好后,重新编译镜像,烧录镜像,HDF就会拉起riladapter_host进程,libril_driver.z.so库会被加载。

设备启动后,查看riladapter_host进程是否正确启动。

riladapter_host进程是否启动如下图

如果riladapter_host进程没有启动,需要排查原因,可能是device_info.hcs文件配置有问题,或者编译时修改没有生效等。

3.2 RIL入口注册

入口函数在libril_driver.z.so库中,如下

static struct HdfDriverEntry g_RilDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "ril_service",
    .Bind = HdfRilDriverBind,
    .Init = HdfRilDriverInit,    //HDF初始化RIL函数
    .Release = HdfRilDriverRelease,   //HDF释放RIL函数
};

libril_driver.z.so库的源码路径如下

/drivers/peripheral/ril/interfaces/hdi_service/src/ril_driver.cpp

其中Init是驱动初始化函数,Release是驱动释放函数

 

3.3 RIL库加载

在驱动初始化函数中,会调用HdfRilDriverInit()函数,在函数中会加载RIL适配库。

调用hril_hdf的InitRilAdapter()函数加载RIL适配库

static int32_t HdfRilDriverInit(struct HdfDeviceObject *deviceObject)
{
    InitRilAdapter();   //调用hril_hdf的Init()函数
    return HDF_SUCCESS;
}

 

4. hril_hdf

在InitRilAdapter()函数中,调用LoadVendor()来加载RIL适配库。

void InitRilAdapter(void)
{
    LoadVendor();
}

在LoadVendor()函数中,先获取RIL适配库的路径,然后再加载RIL适配库。

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) {  //获取RIL适配库的路径
        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 || rilLibPath[0] == '\0') {
        TELEPHONY_LOGE("dynamic library path is empty");
        return;
    }
​
    TELEPHONY_LOGI("RilInit LoadVendor start with rilLibPath:%{public}s", rilLibPath);
    g_dlHandle = dlopen(rilLibPath, RTLD_NOW);  //加载RIL适配库
    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;
    }
    HRilInit();
    ops = rilInitOps(&g_reportOps);
    HRilRegOps(ops);
    TELEPHONY_LOGI("HRilRegOps completed");
}

在GetVendorLibPath()函数中,获取RIL适配库路径。

先读取VENDOR_LIB_PATH系统参数,来获取RIL适配库的路径。

如果失败,再读取RIL_VENDOR_LIB_PATH系统参数,来获取RIL适配库的路径。

static int32_t GetVendorLibPath(char *path)
{
    int32_t code = GetParameter(VENDOR_LIB_PATH, "", path, PARAMETER_SIZE);
    if (code > 0) {
        return HDF_SUCCESS;
    } else {
        TELEPHONY_LOGE("Failed to get const vendor library path through system properties. err:%{public}d", code);
    }
    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;
}

 

5. vendor

OpenHarmony中的RIL适配需要适配的OpenHarmony的接口,大概有100多个接口函数。开发者需要在RIL库把这100多个接口实现出来。

这些接口中按照功能可以分为“下发”和“上报”2种类型。

在初始化函数RilInitOps()中,把接口函数保存下来。

const HRilOps *RilInitOps(const struct HRilReport *reportOps)
{
    pthread_attr_t attr;
    SetReportOps(reportOps);   //设置上报函数
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    int32_t ret = pthread_create(&g_eventListeners, &attr, (void *(*)(void *))EventListeners, NULL);
    if (ret < 0) {
        TELEPHONY_LOGE("EventListeners create failed %d \n", ret);
    }
    return &g_hrilOps;  //返回RIL库实现的接口
}

 

5.1 上报函数

RIL库收到modem消息时,需要上报给上层,此时就调用上报函数,上报给上层。

static struct HRilReport g_reportOps = {
    OnCallReport,
    OnDataReport,
    OnModemReport,
    OnNetworkReport,
    OnSimReport,
    OnSmsReport,
    OnTimerCallback
};

调用SetReportOps()函数,把上报函数传给RIL适配库。

void SetReportOps(const struct HRilReport *reportOps)
{
    g_reportOps = reportOps;
}

当modem需要上报消息时,调用调用对应上报函数(以OnCallReport为例),把消息上报给上层。

以OnCallReport()为例

void OnCallReport(int32_t slotId, struct ReportInfo reportInfo, const uint8_t *response, size_t responseLen)
{
    if (g_reportOps != NULL) {
        g_reportOps->OnCallReport(slotId, reportInfo, response, responseLen);
    } else {
        TELEPHONY_LOGE("g_reportOps is NULL");
    }
}

上报分别2种类型

1.执行AT命令的返回结果。---被动上报

2.信号强度,网络制式等。---主动上报

 

5.2 下发函数

下发函数是上层下发请求时,需要RIL适配库把请求下发到modem芯片。

下发请求接口如下

vendor_adapter.c

HRilOps g_hrilOps = {
    .callOps = &g_callReqOps,
    .simOps = &g_simReqOps,
    .smsOps = &g_smsReqOps,
    .networkOps = &g_networkReqOps,
    .dataOps = &g_dataReqOps,
    .modemOps = &g_modemReqOps,
};

vendor_adapter.c文件中的下发接口函数需要开发者根据不同的modem芯片,来实现具体的功能。

以CallReq为例,CallReq需要实现下面这些接口,来实现通话相关的功能。

static const HRilCallReq g_callReqOps = {
    .GetCallList = ReqGetCallList,
    .Dial = ReqDial,
    .Hangup = ReqHangup,
    .Reject = ReqReject,
    .Answer = ReqAnswer,
    .GetClip = ReqGetClip,
    .SetClip = ReqSetClip,
    .HoldCall = ReqHoldCall,
    .UnHoldCall = ReqUnHoldCall,
    .SwitchCall = ReqSwitchCall,
    .CombineConference = ReqCombineConference,
    .SeparateConference = ReqSeparateConference,
    .CallSupplement = ReqCallSupplement,
    .GetCallWaiting = ReqGetCallWaiting,
    .SetCallWaiting = ReqSetCallWaiting,
    .GetCallTransferInfo = ReqGetCallTransferInfo,
    .SetCallTransferInfo = ReqSetCallTransferInfo,
    .GetCallRestriction = ReqGetCallRestriction,
    .SetCallRestriction = ReqSetCallRestriction,
    .GetClir = ReqGetClir,
    .SetClir = ReqSetClir,
    .StartDtmf = ReqStartDtmf,
    .SendDtmf = ReqSendDtmf,
    .StopDtmf = ReqStopDtmf,
    .GetCallPreferenceMode = ReqGetCallPreferenceMode,
    .SetCallPreferenceMode = ReqSetCallPreferenceMode,
    .SetUssd = ReqSetUssd,
    .GetUssd = ReqGetUssd,
    .GetMute = ReqGetMute,
    .SetMute = ReqSetMute,
    .GetEmergencyCallList = ReqGetEmergencyCallList,
    .GetCallFailReason = ReqGetCallFailReason,
    .SetEmergencyCallList = ReqSetEmergencyCallList,
    .SetBarringPassword = ReqSetBarringPassword,
    .CloseUnFinishedUssd = ReqCloseUnFinishedUssd,
    .SetVonrSwitch = ReqSetVonrSwitch,
};

 

把这些函数全部适配完成后,RIL库的适配工作就完成了。

开发者可以测试telephony相关的功能,来验证适配是否正确。比如拨打电话是否成功,开启蜂窝上网是否成功等。

 

 

 

 

 

 

 

 

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐