diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8393c66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +build +unit/coverage/*.gcov +unit/coverage/report diff --git a/LICENSE b/LICENSE index 0742fcd..2b0a5e9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2021 Jolla Ltd. +Copyright (C) 2021-2022 Jolla Ltd. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6204ad2 --- /dev/null +++ b/Makefile @@ -0,0 +1,222 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: clean all debug release test +.PHONY: debug_lib release_lib coverage_lib +.PHONY: print_debug_lib print_release_lib print_coverage_lib + +# +# Required packages +# +# ofono.pc adds -export-symbols-regex linker option which doesn't work +# on all platforms. +# + +LDPKGS = libgbinder-radio libgbinder libmce-glib libglibutil gobject-2.0 glib-2.0 +PKGS = ofono $(LDPKGS) + +# +# Default target +# + +all: debug release + +# +# Library name +# + +NAME = binderplugin +LIB_NAME = $(NAME) +LIB_SONAME = $(LIB_NAME).so +LIB = $(LIB_SONAME) +STATIC_LIB = $(NAME).a + +# +# Sources +# + +SRC = \ + binder_base.c \ + binder_call_barring.c \ + binder_call_forwarding.c \ + binder_call_settings.c \ + binder_call_volume.c \ + binder_cbs.c \ + binder_cell_info.c \ + binder_connman.c \ + binder_data.c \ + binder_devinfo.c \ + binder_devmon.c \ + binder_devmon_combine.c \ + binder_devmon_ds.c \ + binder_devmon_if.c \ + binder_gprs.c \ + binder_gprs_context.c \ + binder_logger.c \ + binder_modem.c \ + binder_netreg.c \ + binder_network.c \ + binder_radio.c \ + binder_radio_caps.c \ + binder_radio_settings.c \ + binder_sim.c \ + binder_sim_card.c \ + binder_sim_settings.c \ + binder_sms.c \ + binder_stk.c \ + binder_ussd.c \ + binder_util.c \ + binder_voicecall.c \ + binder_plugin.c + +# +# Directories +# + +SRC_DIR = src +BUILD_DIR = build +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release +COVERAGE_BUILD_DIR = $(BUILD_DIR)/coverage + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARNINGS = -Wall +BASE_FLAGS = -fPIC -fvisibility=hidden +FULL_CFLAGS = $(BASE_FLAGS) $(CFLAGS) $(DEFINES) $(WARNINGS) -MMD -MP \ + $(shell pkg-config --cflags $(PKGS)) +FULL_LDFLAGS = $(BASE_FLAGS) $(LDFLAGS) -shared \ + $(shell pkg-config --libs $(LDPKGS)) +DEBUG_FLAGS = -g +RELEASE_FLAGS = +COVERAGE_FLAGS = -g + +KEEP_SYMBOLS ?= 0 +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +endif + +DEBUG_LDFLAGS = $(FULL_LDFLAGS) $(DEBUG_FLAGS) +RELEASE_LDFLAGS = $(FULL_LDFLAGS) $(RELEASE_FLAGS) +DEBUG_CFLAGS = $(FULL_CFLAGS) $(DEBUG_FLAGS) -DDEBUG +RELEASE_CFLAGS = $(FULL_CFLAGS) $(RELEASE_FLAGS) -O2 +COVERAGE_CFLAGS = $(FULL_CFLAGS) $(COVERAGE_FLAGS) --coverage + +# +# Files +# + +DEBUG_OBJS = $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) +COVERAGE_OBJS = $(SRC:%.c=$(COVERAGE_BUILD_DIR)/%.o) + +DEBUG_SO = $(DEBUG_BUILD_DIR)/$(LIB) +RELEASE_SO = $(RELEASE_BUILD_DIR)/$(LIB) + +DEBUG_LIB = $(DEBUG_BUILD_DIR)/$(STATIC_LIB) +RELEASE_LIB = $(RELEASE_BUILD_DIR)/$(STATIC_LIB) +COVERAGE_LIB = $(COVERAGE_BUILD_DIR)/$(STATIC_LIB) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) $(COVERAGE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(DEBUG_OBJS) $(DEBUG_SO): | $(DEBUG_BUILD_DIR) +$(RELEASE_OBJS) $(RELEASE_SO): | $(RELEASE_BUILD_DIR) +$(COVERAGE_OBJS) $(COVERAGE_LIB): | $(COVERAGE_BUILD_DIR) + +# +# Rules +# + +debug: $(DEBUG_SO) + +release: $(RELEASE_SO) + +debug_lib: $(DEBUG_LIB) + +release_lib: $(RELEASE_LIB) + +coverage_lib: $(COVERAGE_LIB) + +print_debug_lib: + @echo $(DEBUG_LIB) + +print_release_lib: + @echo $(RELEASE_LIB) + +print_coverage_lib: + @echo $(COVERAGE_LIB) + +clean: + make -C unit clean + rm -f *~ $(SRC_DIR)/*~ rpm/*~ + rm -fr $(BUILD_DIR) + +test: + make -C unit test + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(COVERAGE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(COVERAGE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(COVERAGE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_SO): $(DEBUG_OBJS) + $(LD) $(DEBUG_OBJS) $(DEBUG_LDFLAGS) -o $@ + +$(RELEASE_SO): $(RELEASE_OBJS) + $(LD) $(RELEASE_OBJS) $(RELEASE_LDFLAGS) -o $@ +ifeq ($(KEEP_SYMBOLS),0) + $(STRIP) $@ +endif + +$(DEBUG_LIB): $(DEBUG_OBJS) + $(AR) rc $@ $? + ranlib $@ + +$(RELEASE_LIB): $(RELEASE_OBJS) + $(AR) rc $@ $? + ranlib $@ + +$(COVERAGE_LIB): $(COVERAGE_OBJS) + $(AR) rc $@ $? + ranlib $@ + +# +# Install +# + +PLUGINDIR ?= $$(pkg-config ofono --variable=plugindir) +ABS_PLUGINDIR := $(shell echo /$(PLUGINDIR) | sed -r 's|/+|/|g') + +INSTALL = install +INSTALL_PLUGIN_DIR = $(DESTDIR)$(ABS_PLUGINDIR) + +install: $(INSTALL_PLUGIN_DIR) + $(INSTALL) -m 755 $(RELEASE_SO) $(INSTALL_PLUGIN_DIR) + +$(INSTALL_PLUGIN_DIR): + $(INSTALL) -d $@ diff --git a/README b/README index 8d712ae..3639be0 100644 --- a/README +++ b/README @@ -2,3 +2,11 @@ Binder based ofono plugin. Integrates Sailfish OS fork of ofono with Android adaptations which support IRadio family of binder interfaces. + +This plugin is a replacement for ofono-ril-plugin. + +For reliable startup, /etc/ofono/binder.conf has to list all +expected slots, for example: + + [Settings] + ExpectSlots = slot1,slot2 diff --git a/binder.conf b/binder.conf new file mode 100644 index 0000000..3ddd9ea --- /dev/null +++ b/binder.conf @@ -0,0 +1,151 @@ +# This is a sample configuration file for ofono binder driver +# +# This file is expected to be installed in /etc/ofono +# +# The convention is that the keys which can only appear in the [Settings] +# section start with the upper case, those which may appear in the [slotX] +# i.e. slot specific section start with lower case. +# +# Slot specific settings may also appear in the [Settings] section in which +# case they apply to all modems. The exceptions are "path" and "slot" values +# which must be unique and therefore must appear in the section(s) for the +# respective slot(s). +# +# By default, the list of slots is fetched from hwservicemanager managing +# services at /dev/hwbinder +# + +[Settings] + +# +# Binder device to talk to. +# +# Default /dev/hwbinder +# +#Device=/dev/hwbinder + +# User and group for the ofono process. RIL clients are typically +# expected to run under radio:radio. +# +# Default radio:radio +# +#Identity=radio:radio + +# If the phone has more than one SIM slot, the 3G/LTE module may be +# shared by all modems, meaning that only one of the slots can use +# 3G/LTE. In order to "hand 4G over" to the other slot, the modem +# currently using 3G/LTE has to drop to GSM, release 3G/LTE module +# and only then 3G/LTE can be used by the other modem. This setting +# allows to disable this behaviour (say, if your phone has independent +# 3G/LTE modules for each slot or you don't need 4G for both slots). +# Obviously, it only has any effect if you have more than one SIM. +# +# Defaults to true (switch the current data modem to 2G when changing +# the data modems) +# +#3GLTEHandover=true + +# If this option is set, preferred technology is limited for non-data +# slots. If set to none, preferred technology doesn't depend on whether +# the slot is selected for data or not. +# +# Possible values are none, gsm and umts +# +# Default umts +# +#MaxNonDataMode=umts + +# RIL_REQUEST_SET_RADIO_CAPABILITY may or may not be supported by your RIL. +# This option allows you to forcibly enable or disable use of this request. +# It's involved in 3G/LTE handover between the modems, meaning that it only +# makes sense if you have more than one slot. +# +# Possible values are auto, on and off +# +# Default auto (enable if supported) +# +#SetRadioCapability=auto + +# Comma-separated list of slots to expect. These slots are added to the +# list the slots reported by hwservicemanager. Duplicates are ignored, i.e. +# the same slot doesn't get added twice. +# +# It's recommended that this list is specified, otherwise ofono may start +# before the modem adaptation and miss some or even all slots. +# +# Default empty +# +#ExpectSlots= + +# Comma-separated list of slots to ignore. Glob-style patterns are supported. +# Doesn't apply to the expected slots defined by ExpectSlots +# +# Default empty +# +#IgnoreSlots= + +# +# SLOT SPECIFIC ENTRIES +# +# Config groups are named after the slots, e.g. +# +# [slot1] + +# Since IRadio API doesn't provide a standard way of querying the number +# of remaining pin retries, some implementations (namely Qualcomm) allow +# to query the retry count by sending the empty pin. If your implementation +# actually does check the empty pin (and decrements the retry count) then +# you should turn this feature off. +# +# Default true +# +#emptyPinQuery=true + +# setDataAllowed request may or may not be supported by your modem. +# This option allows you to disable use of this request. +# Possible values are on and off +# +# Default on +# +#allowDataReq=on + +# Enables use of setDataProfile requests. +# +# Default true +# +#useDataProfiles=true + +# Comma-separated signal strength range, in dBm. +# +# These values are used for translating dBm values returned by the modem in +# LTE mode into signal strength percentage. If you are getting significantly +# different signal strength readings in GSM and LTE modes, you may need to +# tweak those. +# +# Default -100,-60 +# +#signalStrengthRange=-100,-60 + +# With some modems, network scan returns strange operator names, i.e. +# numeric MCC+MNC values or the same name for all operators (which is +# actually SPN fetched from the SIM). Such strange names can be replaced +# with operator names from MBPI database, based on the operator's MCC and +# MNC. That may not be 100% accurate, though. +# +# Default false (i.e. trust the modem to report the actual names) +# +#replaceStrangeOperatorNames=false + +# Configures device state tracking (basically, power saving strategy). +# Possible values are: +# +# ds = sendDeviceState mechanism +# if = setIndicationFilter mechanism +# all = All of the above +# none = Disable device state management +# +# Note that one can specify a combination of methods, e.g. ds+if +# +# Default all +# +#deviceStateTracking=all diff --git a/rpm/ofono-binder-plugin.spec b/rpm/ofono-binder-plugin.spec new file mode 100644 index 0000000..5b0cb13 --- /dev/null +++ b/rpm/ofono-binder-plugin.spec @@ -0,0 +1,77 @@ +Name: ofono-binder-plugin + +Version: 1.0.0 +Release: 1 +Summary: Binder based ofono plugin +License: GPLv2 +URL: https://github.com/mer-hybris/ofono-binder-plugin +Source: %{name}-%{version}.tar.bz2 + +%define libglibutil_version 1.0.61 +%define libgbinder_version 1.1.15 +%define libgbinder_radio_version 1.4.8 +%define libmce_version 1.0.6 +%define ofono_version 1.28+git2 + +BuildRequires: pkgconfig +BuildRequires: ofono-devel >= %{ofono_version} +BuildRequires: pkgconfig(libgbinder) >= %{libgbinder_version} +BuildRequires: pkgconfig(libgbinder-radio) >= %{libgbinder_radio_version} +BuildRequires: pkgconfig(libglibutil) >= %{libglibutil_version} +BuildRequires: pkgconfig(libmce-glib) >= %{libmce_version} + +# license macro requires rpm >= 4.11 +BuildRequires: pkgconfig(rpm) +%define license_support %(pkg-config --exists 'rpm >= 4.11'; echo $?) + +Requires: ofono >= %{ofono_version} +Requires: libgbinder >= %{libgbinder_version} +Requires: libgbinder-radio >= %{libgbinder_radio_version} +Requires: libglibutil >= %{libglibutil_version} +Requires: libmce-glib >= %{libmce_version} + +Conflicts: ofono-ril-plugin +Obsoletes: ofono-ril-plugin +Conflicts: ofono-ril-binder-plugin +Obsoletes: ofono-ril-binder-plugin + +%define plugin_dir %(pkg-config ofono --variable=plugindir) +%define config_dir /etc/ofono/ + +%description +Binder plugin for Sailfish OS fork of ofono + +%package -n ofono-configs-binder +Summary: Package to provide default binder configs for ofono +Provides: ofono-configs + +%description -n ofono-configs-binder +This package provides default configs for ofono + +%prep +%setup -q -n %{name}-%{version} + +%build +make %{_smp_mflags} PLUGINDIR=%{plugin_dir} KEEP_SYMBOLS=1 release + +%check +make test + +%install +rm -rf %{buildroot} +make DESTDIR=%{buildroot} install +mkdir -p %{buildroot}%{config_dir} +install -m 644 binder.conf %{buildroot}%{config_dir} + +%files +%dir %{plugin_dir} +%defattr(-,root,root,-) +%{plugin_dir}/binderplugin.so +%if %{license_support} == 0 +%license LICENSE +%endif + +%files -n ofono-configs-binder +%dir %{config_dir} +%defattr(-,root,root,-) +%config %{config_dir}/binder.conf diff --git a/src/binder_base.c b/src/binder_base.c new file mode 100644 index 0000000..1f41c3f --- /dev/null +++ b/src/binder_base.c @@ -0,0 +1,189 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_log.h" + +typedef +void +(*BinderBasePropertyFunc)( + gpointer source, + guint property, + gpointer user_data); + +typedef struct binder_base_closure { + GCClosure cclosure; + BinderBasePropertyFunc callback; + gpointer user_data; +} BinderBaseClosure; + +#define binder_base_closure_new() ((BinderBaseClosure*) \ + g_closure_new_simple(sizeof(BinderBaseClosure), NULL)) + +G_DEFINE_ABSTRACT_TYPE(BinderBase, binder_base, G_TYPE_OBJECT) +#define GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + BINDER_TYPE_BASE, BinderBaseClass) + +#define SIGNAL_PROPERTY_CHANGED_NAME "binder-base-property-changed" +#define SIGNAL_PROPERTY_DETAIL "%x" +#define SIGNAL_PROPERTY_DETAIL_MAX_LEN (8) + +enum binder_base_signal { + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +}; + +static guint binder_base_signals[SIGNAL_COUNT]; +static GQuark binder_base_property_quarks[BINDER_BASE_MAX_PROPERTIES]; + +static +GQuark +binder_base_property_quark( + guint property) +{ + GASSERT(property < BINDER_BASE_MAX_PROPERTIES); + /* For ANY property this function is expected to return zero */ + if (property > 0 && G_LIKELY(property < BINDER_BASE_MAX_PROPERTIES)) { + const int i = property - 1; + + if (G_UNLIKELY(!binder_base_property_quarks[i])) { + char buf[SIGNAL_PROPERTY_DETAIL_MAX_LEN + 1]; + + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_DETAIL, property); + buf[sizeof(buf) - 1] = 0; + binder_base_property_quarks[i] = g_quark_from_string(buf); + } + return binder_base_property_quarks[i]; + } + return 0; +} + +static +void +binder_base_property_changed( + BinderBase* self, + guint property, + BinderBaseClosure* closure) +{ + const BinderBaseClass* klass = GET_CLASS(self); + + closure->callback(((guint8*)self) + klass->public_offset, property, + closure->user_data); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +gulong +binder_base_add_property_handler( + BinderBase* self, + guint property, + GCallback callback, + gpointer user_data) +{ + if (G_LIKELY(callback)) { + /* + * We can't directly connect the provided callback because + * it expects the first parameter to point to public part + * of the object but glib will call it with BinderBase as + * the first parameter. binder_base_property_changed() will + * do the conversion. + */ + BinderBaseClosure* closure = binder_base_closure_new(); + GCClosure* cc = &closure->cclosure; + + cc->closure.data = closure; + cc->callback = G_CALLBACK(binder_base_property_changed); + closure->callback = (BinderBasePropertyFunc)callback; + closure->user_data = user_data; + + return g_signal_connect_closure_by_id(self, + binder_base_signals[SIGNAL_PROPERTY_CHANGED], + binder_base_property_quark(property), &cc->closure, FALSE); + } + return 0; +} + +void +binder_base_queue_property_change( + BinderBase* self, + guint property) +{ + self->queued_signals |= BINDER_BASE_PROPERTY_BIT(property); +} + +void +binder_base_emit_property_change( + BinderBase* self, + guint property) +{ + binder_base_queue_property_change(self, property); + binder_base_emit_queued_signals(self); +} + +void +binder_base_emit_queued_signals( + BinderBase* self) +{ + guint p; + + /* Signal handlers may release references to this object */ + g_object_ref(self); + + /* Emit the signals */ + for (p = 0; self->queued_signals && p < BINDER_BASE_MAX_PROPERTIES; p++) { + if (self->queued_signals & BINDER_BASE_PROPERTY_BIT(p)) { + self->queued_signals &= ~BINDER_BASE_PROPERTY_BIT(p); + g_signal_emit(self, binder_base_signals[SIGNAL_PROPERTY_CHANGED], + binder_base_property_quark(p), p); + } + } + + /* Release the temporary reference */ + g_object_unref(self); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_base_init( + BinderBase* self) +{ +} + +static +void +binder_base_class_init( + BinderBaseClass* klass) +{ + /* By default assume that public part immediately follows BinderBase */ + klass->public_offset = sizeof(BinderBase); + binder_base_signals[SIGNAL_PROPERTY_CHANGED] = + g_signal_new(SIGNAL_PROPERTY_CHANGED_NAME, G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_UINT); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_base.h b/src/binder_base.h new file mode 100644 index 0000000..31785fd --- /dev/null +++ b/src/binder_base.h @@ -0,0 +1,80 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_BASE_H +#define BINDER_BASE_H + +#include "binder_types.h" + +#include + +typedef struct binder_base_class { + GObjectClass object; + int public_offset; +} BinderBaseClass; + +typedef struct binder_base { + GObject object; + gsize queued_signals; +} BinderBase; + +BINDER_INTERNAL GType binder_base_get_type(void); +#define BINDER_TYPE_BASE (binder_base_get_type()) +#define BINDER_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + BINDER_TYPE_BASE, BinderBaseClass)) + +typedef enum binder_base_property { + BINDER_BASE_PROPERTY_ANY = 0, + BINDER_BASE_MAX_PROPERTIES = 8 +} BINDER_BASE_PROPERTY; + +#define BINDER_BASE_PROPERTY_BIT(property) (1 << (property - 1)) +#define BINDER_BASE_ASSERT_COUNT(count) \ + G_STATIC_ASSERT((int)count <= (int)BINDER_BASE_MAX_PROPERTIES) + +gulong +binder_base_add_property_handler( + BinderBase* base, + guint property, + GCallback callback, + gpointer user_data) + BINDER_INTERNAL; + +void +binder_base_queue_property_change( + BinderBase* base, + guint property) + BINDER_INTERNAL; + +void +binder_base_emit_property_change( + BinderBase* base, + guint property) + BINDER_INTERNAL; + +void +binder_base_emit_queued_signals( + BinderBase* base) + BINDER_INTERNAL; + +#endif /* BINDER_BASE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_barring.c b/src/binder_call_barring.c new file mode 100644 index 0000000..8543923 --- /dev/null +++ b/src/binder_call_barring.c @@ -0,0 +1,382 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_call_barring.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_sim_card.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include +#include + +typedef struct binder_call_barring { + struct ofono_call_barring* b; + BinderSimCard* card; + RadioRequestGroup* g; + char* log_prefix; + guint register_id; +} BinderCallBarring; + +typedef struct binder_call_barring_callback_data { + BinderCallBarring* self; + union call_barring_cb { + ofono_call_barring_query_cb_t query; + ofono_call_barring_set_cb_t set; + BinderCallback ptr; + } cb; + gpointer data; +} BinderCallBarringCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderCallBarring* +binder_call_barring_get_data(struct ofono_call_barring* b) + { return ofono_call_barring_get_data(b); } + +static +BinderCallBarringCbData* +binder_call_barring_callback_data_new( + BinderCallBarring* self, + BinderCallback cb, + void* data) +{ + BinderCallBarringCbData* cbd = g_slice_new0(BinderCallBarringCbData); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_call_barring_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderCallBarringCbData, cbd); +} + +static +gboolean +binder_call_barring_query_ok( + const BinderCallBarringCbData* cbd, + const GBinderReader* args) +{ + GBinderReader reader; + gint32 response; + + /* + * getFacilityLockForAppResponse(RadioResponseInfo, int32_t response); + * + * response - the TS 27.007 service class bit vector of services + * for which the specified barring facility is active. + * 0 means "disabled for all" + */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &response)) { + struct ofono_error err; + + DBG_(cbd->self, "Active services: %d", response); + cbd->cb.query(binder_error_ok(&err), response, cbd->data); + return TRUE; + } + return FALSE; +} + +static +void +binder_call_barring_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallBarringCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_FACILITY_LOCK_FOR_APP) { + if (error == RADIO_ERROR_NONE) { + if (binder_call_barring_query_ok(cbd, args)) { + return; + } + } else { + ofono_warn("Call Barring query error %d", error); + } + } else { + ofono_error("Unexpected getFacilityLockForApp response %d", resp); + } + } + cbd->cb.query(binder_error_failure(&err), 0, cbd->data); +} + +static +void +binder_call_barring_query( + struct ofono_call_barring* b, + const char* lock, + int cls, + ofono_call_barring_query_cb_t cb, + void* data) +{ + BinderCallBarring* self = ofono_call_barring_get_data(b); + + /* + * getFacilityLockForApp(int32_t serial, string facility, + * string password, int32_t serviceClass, string appId); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_FACILITY_LOCK_FOR_APP, &writer, + binder_call_barring_query_cb, + binder_call_barring_callback_data_free, + binder_call_barring_callback_data_new(self, BINDER_CB(cb), data)); + + DBG_(self, "lock: %s, services to query: 0x%02x", lock, cls); + binder_append_hidl_string(&writer, lock); /* facility */ + binder_append_hidl_string(&writer, ""); /* password */ + gbinder_writer_append_int32(&writer, cls); /* serviceClass */ + binder_append_hidl_string(&writer, binder_sim_card_app_aid(self->card)); + /* appId */ + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_call_barring_set_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallBarringCbData* cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb.set; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_FACILITY_LOCK_FOR_APP) { + /* + * setFacilityLockForAppResponse(RadioResponseInfo, int32_t retry); + * + * retry - the number of retries remaining, or -1 if unknown + */ + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_error("Call Barring Set error %d", error); + } + } else { + ofono_error("Unexpected setFacilityLockForApp response %d", resp); + } + } + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_call_barring_set( + struct ofono_call_barring* b, + const char* lock, + int enable, + const char* passwd, + int cls, + ofono_call_barring_set_cb_t cb, + void* data) +{ + BinderCallBarring* self = ofono_call_barring_get_data(b); + + /* + * setFacilityLockForApp(int32_t serial, string facility, bool lockState, + * string password, int32_t serviceClass, string appId); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_FACILITY_LOCK_FOR_APP, &writer, + binder_call_barring_set_cb, + binder_call_barring_callback_data_free, + binder_call_barring_callback_data_new(self, BINDER_CB(cb), data)); + + DBG_(self, "lock: %s, enable: %i, bearer class: %i", lock, enable, cls); + + binder_append_hidl_string(&writer, lock); /* facility */ + gbinder_writer_append_bool(&writer, enable); /* lockState */ + binder_append_hidl_string(&writer, passwd); /* password */ + gbinder_writer_append_int32(&writer, cls); /* serviceClass */ + binder_append_hidl_string(&writer, binder_sim_card_app_aid(self->card)); + /* appId */ + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_call_barring_set_passwd_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallBarringCbData* cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb.set; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_BARRING_PASSWORD) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_error("Call Barring Set PW error %d", error); + } + } else { + ofono_error("Unexpected setBarringPassword response %d", resp); + } + } + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_call_barring_set_passwd( + struct ofono_call_barring* b, + const char* lock, + const char* old_passwd, + const char* new_passwd, + ofono_call_barring_set_cb_t cb, + void* data) +{ + BinderCallBarring* self = ofono_call_barring_get_data(b); + + /* + * setBarringPassword(int32_t serial, string facility, + * string oldPassword, string newPassword); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_BARRING_PASSWORD, &writer, + binder_call_barring_set_passwd_cb, + binder_call_barring_callback_data_free, + binder_call_barring_callback_data_new(self, BINDER_CB(cb), data)); + + DBG_(self, ""); + binder_append_hidl_string(&writer, lock); /* facility */ + binder_append_hidl_string(&writer, old_passwd); /* oldPassword */ + binder_append_hidl_string(&writer, new_passwd); /* newPassword */ + + radio_request_submit(req); + radio_request_unref(req); +} + +static +gboolean +binder_call_barring_register( + gpointer user_data) +{ + BinderCallBarring* self = user_data; + + GASSERT(self->register_id); + self->register_id = 0; + ofono_call_barring_register(self->b); + return G_SOURCE_REMOVE; +} + +static +int +binder_call_barring_probe( + struct ofono_call_barring* b, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderCallBarring* self = g_new0(struct binder_call_barring, 1); + + self->b = b; + self->card = binder_sim_card_ref(modem->sim_card); + self->g = radio_request_group_new(modem->client); + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_call_barring_register, self); + + DBG_(self, ""); + ofono_call_barring_set_data(b, self); + return 0; +} + +static +void +binder_call_barring_remove( + struct ofono_call_barring* b) +{ + BinderCallBarring* self = binder_call_barring_get_data(b); + + DBG_(self, ""); + if (self->register_id) { + g_source_remove(self->register_id); + } + binder_sim_card_unref(self->card); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + g_free(self->log_prefix); + g_free(self); + + ofono_call_barring_set_data(b, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_call_barring_driver binder_call_barring_driver = { + .name = BINDER_DRIVER, + .probe = binder_call_barring_probe, + .remove = binder_call_barring_remove, + .query = binder_call_barring_query, + .set = binder_call_barring_set, + .set_passwd = binder_call_barring_set_passwd +}; + +void +binder_call_barring_init() +{ + ofono_call_barring_driver_register(&binder_call_barring_driver); +} + +void +binder_call_barring_cleanup() +{ + ofono_call_barring_driver_unregister(&binder_call_barring_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_barring.h b/src/binder_call_barring.h new file mode 100644 index 0000000..9063ba8 --- /dev/null +++ b/src/binder_call_barring.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CALL_BARRING_H +#define BINDER_CALL_BARRING_H + +#include "binder_types.h" + +void +binder_call_barring_init(void) + BINDER_INTERNAL; + +void +binder_call_barring_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_CALL_BARRING_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_forwarding.c b/src/binder_call_forwarding.c new file mode 100644 index 0000000..e387740 --- /dev/null +++ b/src/binder_call_forwarding.c @@ -0,0 +1,398 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_call_forwarding.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include +#include + +typedef struct binder_call_forwarding { + struct ofono_call_forwarding* f; + RadioRequestGroup* g; + char* log_prefix; + guint register_id; +} BinderCallForwarding; + +typedef struct binder_call_forwarding_cbd { + union call_forwarding_cb { + ofono_call_forwarding_query_cb_t query; + ofono_call_forwarding_set_cb_t set; + BinderCallback ptr; + } cb; + gpointer data; +} BinderCallForwardingCbData; + +#define CF_TIME_DEFAULT (0) + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderCallForwarding* +binder_call_forwarding_get_data(struct ofono_call_forwarding* f) + { return ofono_call_forwarding_get_data(f); } + +static +BinderCallForwardingCbData* +binder_call_forwarding_callback_data_new( + BinderCallback cb, + void* data) +{ + BinderCallForwardingCbData* cbd = g_slice_new0(BinderCallForwardingCbData); + + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_call_forwarding_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderCallForwardingCbData, cbd); +} + +static +void +binder_call_forwarding_call( + BinderCallForwarding* self, + RADIO_REQ code, + RADIO_CALL_FORWARD action, + int reason, + int cls, + const struct ofono_phone_number* number, + int time, + RadioRequestCompleteFunc complete, + BinderCallback cb, + void* data) +{ + /* + * getCallForwardStatus(int32_t serial, CallForwardInfo callInfo); + * setCallForward(int32_t serial, CallForwardInfo callInfo); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, code, &writer, complete, + binder_call_forwarding_callback_data_free, + binder_call_forwarding_callback_data_new(cb, data)); + RadioCallForwardInfo* info = gbinder_writer_new0(&writer, + RadioCallForwardInfo); + guint parent; + + info->status = action; + info->reason = reason; + info->serviceClass = cls; + info->timeSeconds = time; + if (number) { + info->toa = number->type; + binder_copy_hidl_string(&writer, &info->number, number->number); + } else { + info->toa = OFONO_NUMBER_TYPE_UNKNOWN; + binder_copy_hidl_string(&writer, &info->number, NULL); + } + parent = gbinder_writer_append_buffer_object(&writer, info, sizeof(*info)); + binder_append_hidl_string_data(&writer, info, number, parent); + + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_call_forwarding_set_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallForwardingCbData* cbd = user_data; + ofono_call_forwarding_set_cb_t cb = cbd->cb.set; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_CALL_FORWARD) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_error("CF error %d", error); + } + } else { + ofono_error("Unexpected setCallForward response %d", resp); + } + } + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_call_forwarding_set( + BinderCallForwarding* self, + RADIO_CALL_FORWARD action, + int reason, + int cls, + const struct ofono_phone_number* number, + int time, + ofono_call_forwarding_set_cb_t cb, + void* data) +{ + binder_call_forwarding_call(self, RADIO_REQ_SET_CALL_FORWARD, + action, reason, cls, number, time, binder_call_forwarding_set_cb, + BINDER_CB(cb), data); +} + +static +void +binder_call_forwarding_registration( + struct ofono_call_forwarding* f, + int type, + int cls, + const struct ofono_phone_number* number, + int time, + ofono_call_forwarding_set_cb_t cb, + void* data) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, "%d", type); + binder_call_forwarding_set(self, RADIO_CALL_FORWARD_REGISTRATION, + type, cls, number, time, cb, data); +} + +static +void +binder_call_forwarding_erasure( + struct ofono_call_forwarding* f, + int type, + int cls, + ofono_call_forwarding_set_cb_t cb, + void* data) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, "%d", type); + binder_call_forwarding_set(self, RADIO_CALL_FORWARD_ERASURE, + type, cls, NULL, CF_TIME_DEFAULT, cb, data); +} + +static +void +binder_call_forwarding_deactivate( + struct ofono_call_forwarding* f, + int type, + int cls, + ofono_call_forwarding_set_cb_t cb, + void* data) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, "%d", type); + binder_call_forwarding_set(self, RADIO_CALL_FORWARD_DISABLE, + type, cls, NULL, CF_TIME_DEFAULT, cb, data); +} + +static +void +binder_call_forwarding_activate( + struct ofono_call_forwarding* f, + int type, + int cls, + ofono_call_forwarding_set_cb_t cb, + void* data) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, "%d", type); + binder_call_forwarding_set(self, RADIO_CALL_FORWARD_ENABLE, + type, cls, NULL, CF_TIME_DEFAULT, cb, data); +} + +static +void +binder_call_forwarding_query_ok( + const BinderCallForwardingCbData* cbd, + const GBinderReader* args) +{ + struct ofono_error err; + const RadioCallForwardInfo* infos; + struct ofono_call_forwarding_condition* list = NULL; + GBinderReader reader; + gsize count = 0; + + /* getCallForwardStatusResponse(RadioResponseInfo, vec) */ + gbinder_reader_copy(&reader, args); + infos = gbinder_reader_read_hidl_type_vec(&reader, RadioCallForwardInfo, + &count); + if (count) { + gsize i; + + list = g_new0(struct ofono_call_forwarding_condition, count); + for (i = 0; i < count; i++) { + const RadioCallForwardInfo* info = infos + i; + struct ofono_call_forwarding_condition* fw = list + i; + + fw->status = info->status; + fw->cls = info->serviceClass; + fw->time = info->timeSeconds; + fw->phone_number.type = info->toa; + memcpy(fw->phone_number.number, info->number.data.str, + MIN(OFONO_MAX_PHONE_NUMBER_LENGTH, info->number.len)); + } + } + cbd->cb.query(binder_error_ok(&err), count, list, cbd->data); + g_free(list); +} + +static +void +binder_call_forwarding_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallForwardingCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_CALL_FORWARD) { + if (error == RADIO_ERROR_NONE) { + binder_call_forwarding_query_ok(cbd, args); + return; + } else { + ofono_error("CF query error %d", error); + } + } else { + ofono_error("Unexpected getCallForwardStatus response %d", resp); + } + } + cbd->cb.query(binder_error_failure(&err), 0, NULL, cbd->data); +} + +static +void +binder_call_forwarding_query( + struct ofono_call_forwarding* f, + int type, + int cls, + ofono_call_forwarding_query_cb_t cb, + void* data) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, "%d", type); + binder_call_forwarding_call(self, RADIO_REQ_GET_CALL_FORWARD_STATUS, + RADIO_CALL_FORWARD_INTERROGATE, type, cls, NULL, CF_TIME_DEFAULT, + binder_call_forwarding_query_cb, BINDER_CB(cb), data); +} + +static +gboolean +binder_call_forwarding_register( + gpointer user_data) +{ + BinderCallForwarding* self = user_data; + + GASSERT(self->register_id); + self->register_id = 0; + ofono_call_forwarding_register(self->f); + return G_SOURCE_REMOVE; +} + +static +int +binder_call_forwarding_probe( + struct ofono_call_forwarding* f, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderCallForwarding* self = g_new0(BinderCallForwarding, 1); + + self->f = f; + self->g = radio_request_group_new(modem->client); + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_call_forwarding_register, self); + + DBG_(self, ""); + ofono_call_forwarding_set_data(f, self); + return 0; +} + +static +void +binder_call_forwarding_remove( + struct ofono_call_forwarding* f) +{ + BinderCallForwarding* self = binder_call_forwarding_get_data(f); + + DBG_(self, ""); + if (self->register_id) { + g_source_remove(self->register_id); + } + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + g_free(self->log_prefix); + g_free(self); + + ofono_call_forwarding_set_data(f, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_call_forwarding_driver +binder_call_forwarding_driver = { + .name = BINDER_DRIVER, + .probe = binder_call_forwarding_probe, + .remove = binder_call_forwarding_remove, + .erasure = binder_call_forwarding_erasure, + .deactivation = binder_call_forwarding_deactivate, + .query = binder_call_forwarding_query, + .registration = binder_call_forwarding_registration, + .activation = binder_call_forwarding_activate +}; + +void +binder_call_forwarding_init() +{ + ofono_call_forwarding_driver_register(&binder_call_forwarding_driver); +} + +void +binder_call_forwarding_cleanup() +{ + ofono_call_forwarding_driver_unregister(&binder_call_forwarding_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_forwarding.h b/src/binder_call_forwarding.h new file mode 100644 index 0000000..789ad4e --- /dev/null +++ b/src/binder_call_forwarding.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CALL_FORWARDING_H +#define BINDER_CALL_FORWARDING_H + +#include "binder_types.h" + +void +binder_call_forwarding_init(void) + BINDER_INTERNAL; + +void +binder_call_forwarding_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_CALL_FORWARDING_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_settings.c b/src/binder_call_settings.c new file mode 100644 index 0000000..6529716 --- /dev/null +++ b/src/binder_call_settings.c @@ -0,0 +1,464 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_call_settings.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include +#include + +typedef struct binder_call_settings { + struct ofono_call_settings* s; + RadioRequestGroup* g; + char* log_prefix; + guint register_id; +} BinderCallSettings; + +typedef struct binder_call_settings_cbd { + BinderCallSettings* self; + union call_settings_cb { + ofono_call_settings_status_cb_t status; + ofono_call_settings_set_cb_t set; + ofono_call_settings_clir_cb_t clir; + BinderCallback ptr; + } cb; + gpointer data; +} BinderCallSettingsCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderCallSettings* +binder_call_settings_get_data(struct ofono_call_settings* s) + { return ofono_call_settings_get_data(s); } + +static +BinderCallSettingsCbData* +binder_call_settings_callback_data_new( + BinderCallSettings* self, + BinderCallback cb, + void* data) +{ + BinderCallSettingsCbData* cbd = g_slice_new0(BinderCallSettingsCbData); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_call_settings_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderCallSettingsCbData, cbd); +} + +static +void +binder_call_settings_call( + BinderCallSettings* self, + RADIO_REQ code, + RadioRequestCompleteFunc complete, + BinderCallback cb, + void* data) +{ + RadioRequest* req = radio_request_new2(self->g, code, NULL, complete, + binder_call_settings_callback_data_free, + binder_call_settings_callback_data_new(self, cb, data)); + + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_call_settings_set_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallSettingsCbData* cbd = user_data; + ofono_call_settings_set_cb_t cb = cbd->cb.set; + + if (status == RADIO_TX_STATUS_OK && error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + } else { + cb(binder_error_failure(&err), cbd->data); + } +} + +static +void +binder_call_settings_cw_set( + struct ofono_call_settings* s, + int mode, + int cls, + ofono_call_settings_set_cb_t cb, + void* data) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + /* setCallWaiting(int32_t serial, bool enable, int32_t serviceClass); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_CALL_WAITING, &writer, + binder_call_settings_set_cb, + binder_call_settings_callback_data_free, + binder_call_settings_callback_data_new(self, BINDER_CB(cb), data)); + + gbinder_writer_append_bool(&writer, mode); /* enable */ + gbinder_writer_append_int32(&writer, cls); /* serviceClass */ + + radio_request_submit(req); + radio_request_unref(req); +} + +static +gboolean +binder_call_settings_cw_query_ok( + const BinderCallSettingsCbData* cbd, + const GBinderReader* args) +{ + struct ofono_error err; + ofono_call_settings_status_cb_t cb = cbd->cb.status; + GBinderReader reader; + gboolean enable; + gint32 cls; + + /* + * getCallWaitingResponse(RadioResponseInfo, bool enable, + * int32_t serviceClass); + */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_bool(&reader, &enable) && + gbinder_reader_read_int32(&reader, &cls)) { + if (enable) { + DBG_(cbd->self, "CW enabled for %d", cls); + cb(binder_error_ok(&err), cls, cbd->data); + } else { + DBG_(cbd->self, "CW disabled"); + cb(binder_error_ok(&err), 0, cbd->data); + } + return TRUE; + } + ofono_warn("Unexpected getCallWaitingResponse payload"); + return FALSE; +} + +static +void +binder_call_settings_cw_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallSettingsCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_CALL_WAITING) { + if (error == RADIO_ERROR_NONE) { + if (binder_call_settings_cw_query_ok(cbd, args)) { + return; + } + } else { + ofono_warn("CW query error %d", error); + } + } else { + ofono_error("Unexpected getCallWaiting response %d", resp); + } + } + cbd->cb.status(binder_error_failure(&err), -1, cbd->data); +} + +static +void binder_call_settings_cw_query( + struct ofono_call_settings* s, + int cls, + ofono_call_settings_status_cb_t cb, + void* data) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + /* getCallWaiting(int32_t serial, int32_t serviceClass); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_CALL_WAITING, &writer, + binder_call_settings_cw_query_cb, + binder_call_settings_callback_data_free, + binder_call_settings_callback_data_new(self, BINDER_CB(cb), data)); + + gbinder_writer_append_int32(&writer, cls); /* serviceClass */ + + radio_request_submit(req); + radio_request_unref(req); +} + +static +gboolean +binder_call_settings_clip_query_ok( + const BinderCallSettingsCbData* cbd, + const GBinderReader* args) +{ + struct ofono_error err; + GBinderReader reader; + gint32 status; + + /* getClipResponse(RadioResponseInfo, ClipStatus status); */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &status)) { + cbd->cb.status(binder_error_ok(&err), status, cbd->data); + return TRUE; + } + return FALSE; +} + +static +void +binder_call_settings_clip_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallSettingsCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_CLIP) { + if (error == RADIO_ERROR_NONE) { + if (binder_call_settings_clip_query_ok(cbd, args)) { + return; + } + } else { + ofono_warn("CLIP query error %d", error); + } + } else { + ofono_error("Unexpected getClip response %d", resp); + } + } + cbd->cb.status(binder_error_failure(&err), -1, cbd->data); +} + +static +void +binder_call_settings_clip_query( + struct ofono_call_settings* s, + ofono_call_settings_status_cb_t cb, + void* data) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + DBG_(self, ""); + /* getClip(int32_t serial); */ + binder_call_settings_call(self, RADIO_REQ_GET_CLIP, + binder_call_settings_clip_query_cb, BINDER_CB(cb), data); +} + +static +gboolean +binder_call_settings_clir_ok( + const BinderCallSettingsCbData* cbd, + const GBinderReader* args) +{ + struct ofono_error err; + GBinderReader reader; + gint32 n, m; + + /* getClirResponse(RadioResponseInfo info, int32_t n, int32_t m); */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &n) && + gbinder_reader_read_int32(&reader, &m)) { + cbd->cb.clir(binder_error_ok(&err), n, m, cbd->data); + return TRUE; + } + ofono_warn("Unexpected getClirResponse payload"); + return FALSE; +} + +static +void +binder_call_settings_clir_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallSettingsCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_CLIR) { + if (error == RADIO_ERROR_NONE) { + if (binder_call_settings_clir_ok(cbd, args)) { + return; + } + } else { + ofono_warn("CW query error %d", error); + } + } else { + ofono_error("Unexpected getClir response %d", resp); + } + } + cbd->cb.clir(binder_error_failure(&err), -1, -1, cbd->data); +} + +static +void +binder_call_settings_clir_query( + struct ofono_call_settings* s, + ofono_call_settings_clir_cb_t cb, + void* data) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + DBG_(self, ""); + /* getClir(int32_t serial); */ + binder_call_settings_call(self, RADIO_REQ_GET_CLIR, + binder_call_settings_clir_cb , BINDER_CB(cb), data); +} + +static +void +binder_call_settings_clir_set( + struct ofono_call_settings* s, + int mode, + ofono_call_settings_set_cb_t cb, + void* data) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + /* setClir(int32_t serial, int32_t status); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_CLIR, &writer, + binder_call_settings_set_cb, + binder_call_settings_callback_data_free, + binder_call_settings_callback_data_new(self, BINDER_CB(cb), data)); + + DBG_(self, "%d", mode); + gbinder_writer_append_int32(&writer, mode); /* status */ + + radio_request_submit(req); + radio_request_unref(req); +} + +static +gboolean +binder_call_settings_register( + gpointer user_data) +{ + BinderCallSettings* self = user_data; + + DBG_(self, ""); + GASSERT(self->register_id); + self->register_id = 0; + ofono_call_settings_register(self->s); + return G_SOURCE_REMOVE; +} + +static +int +binder_call_settings_probe( + struct ofono_call_settings* s, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderCallSettings* self = g_new0(BinderCallSettings, 1); + + self->s = s; + self->g = radio_request_group_new(modem->client); + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_call_settings_register, self); + + DBG_(self, ""); + ofono_call_settings_set_data(s, self); + return 0; +} + +static +void +binder_call_settings_remove( + struct ofono_call_settings* s) +{ + BinderCallSettings* self = binder_call_settings_get_data(s); + + DBG_(self, ""); + if (self->register_id) { + g_source_remove(self->register_id); + } + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + g_free(self->log_prefix); + g_free(self); + + ofono_call_settings_set_data(s, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_call_settings_driver binder_call_settings_driver = { + .name = BINDER_DRIVER, + .probe = binder_call_settings_probe, + .remove = binder_call_settings_remove, + .clip_query = binder_call_settings_clip_query, + .clir_query = binder_call_settings_clir_query, + .clir_set = binder_call_settings_clir_set, + .cw_query = binder_call_settings_cw_query, + .cw_set = binder_call_settings_cw_set +}; + +void +binder_call_settings_init() +{ + ofono_call_settings_driver_register(&binder_call_settings_driver); +} + +void +binder_call_settings_cleanup() +{ + ofono_call_settings_driver_unregister(&binder_call_settings_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_settings.h b/src/binder_call_settings.h new file mode 100644 index 0000000..f6a6c6b --- /dev/null +++ b/src/binder_call_settings.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CALL_SETTINGS_H +#define BINDER_CALL_SETTINGS_H + +#include "binder_types.h" + +void +binder_call_settings_init(void) + BINDER_INTERNAL; + +void +binder_call_settings_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_CALL_SETTINGS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_volume.c b/src/binder_call_volume.c new file mode 100644 index 0000000..78e40bb --- /dev/null +++ b/src/binder_call_volume.c @@ -0,0 +1,243 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_call_volume.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include +#include + +typedef struct binder_call_volume { + struct ofono_call_volume* v; + RadioRequestGroup* g; + char* log_prefix; + guint register_id; +} BinderCallVolume; + +typedef struct binder_call_volume_req { + ofono_call_volume_cb_t cb; + gpointer data; +} BinderCallVolumeCbData; + +#define DBG_(cd,fmt,args...) DBG("%s" fmt, (cd)->log_prefix, ##args) + +static inline BinderCallVolume* +binder_call_volume_get_data(struct ofono_call_volume* v) + { return ofono_call_volume_get_data(v); } + +static +BinderCallVolumeCbData* +binder_call_volume_callback_data_new( + ofono_call_volume_cb_t cb, + void* data) +{ + BinderCallVolumeCbData* cbd = g_slice_new0(BinderCallVolumeCbData); + + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_call_volume_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderCallVolumeCbData, cbd); +} + +static +void +binder_call_volume_mute_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderCallVolumeCbData* cbd = user_data; + ofono_call_volume_cb_t cb = cbd->cb; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_MUTE) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_warn("Could not set the mute state, error %d", error); + } + } else { + ofono_error("Unexpected setMute response %d", resp); + } + } + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_call_volume_mute( + struct ofono_call_volume* v, + int muted, + ofono_call_volume_cb_t cb, + void* data) +{ + BinderCallVolume* self = binder_call_volume_get_data(v); + + /* setMute(int32_t serial, bool enable); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_MUTE, &writer, + binder_call_volume_mute_cb, + binder_call_volume_callback_data_free, + binder_call_volume_callback_data_new(cb, data)); + + DBG_(self, "%d", muted); + gbinder_writer_append_bool(&writer, muted); /* enabled */ + + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_call_volume_query_mute_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_MUTE) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + gboolean muted; + + /* getMuteResponse(RadioResponseInfo info, bool enable); */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_bool(&reader, &muted)) { + BinderCallVolume* self = user_data; + + DBG_(self, "%d", muted); + ofono_call_volume_set_muted(self->v, muted); + } + } else { + ofono_warn("Could not get the mute state, error %d", error); + } + } else { + ofono_error("Unexpected getMute response %d", resp); + } + } +} + +static +gboolean +binder_call_volume_register( + gpointer user_data) +{ + BinderCallVolume* self = user_data; + + DBG_(self, ""); + GASSERT(self->register_id); + self->register_id = 0; + ofono_call_volume_register(self->v); + + /* Probe the mute state */ + binder_submit_request2(self->g, RADIO_REQ_GET_MUTE, + binder_call_volume_query_mute_cb, NULL, self); + + return G_SOURCE_REMOVE; +} + +static +int +binder_call_volume_probe( + struct ofono_call_volume* v, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderCallVolume* self = g_new0(BinderCallVolume, 1); + + self->v = v; + self->g = radio_request_group_new(modem->client); + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_call_volume_register, self); + + DBG_(self, ""); + ofono_call_volume_set_data(v, self); + return 0; +} + +static +void +binder_call_volume_remove( + struct ofono_call_volume* v) +{ + BinderCallVolume* self = binder_call_volume_get_data(v); + + DBG_(self, ""); + if (self->register_id) { + g_source_remove(self->register_id); + } + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + g_free(self->log_prefix); + g_free(self); + + ofono_call_volume_set_data(v, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_call_volume_driver binder_call_volume_driver = { + .name = BINDER_DRIVER, + .probe = binder_call_volume_probe, + .remove = binder_call_volume_remove, + .mute = binder_call_volume_mute +}; + +void +binder_call_volume_init() +{ + ofono_call_volume_driver_register(&binder_call_volume_driver); +} + +void +binder_call_volume_cleanup() +{ + ofono_call_volume_driver_unregister(&binder_call_volume_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_call_volume.h b/src/binder_call_volume.h new file mode 100644 index 0000000..cd8fe2b --- /dev/null +++ b/src/binder_call_volume.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CALL_VOLUME_H +#define BINDER_CALL_VOLUME_H + +#include "binder_types.h" + +void +binder_call_volume_init(void) + BINDER_INTERNAL; + +void +binder_call_volume_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_CALL_VOLUME_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_cbs.c b/src/binder_cbs.c new file mode 100644 index 0000000..168f09b --- /dev/null +++ b/src/binder_cbs.c @@ -0,0 +1,388 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_cbs.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_util.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +typedef struct binder_cbs { + struct ofono_cbs* cbs; + RadioRequestGroup* g; + char* log_prefix; + guint register_id; + gulong event_id; +} BinderCbs; + +typedef struct binder_cbs_cbd { + BinderCbs* self; + ofono_cbs_set_cb_t cb; + gpointer data; +} BinderCbsCbData; + +#define CBS_CHECK_RETRY_MS 1000 +#define CBS_CHECK_RETRY_COUNT 30 + +#define DBG_(cd,fmt,args...) DBG("%s" fmt, (cd)->log_prefix, ##args) + +static inline BinderCbs* binder_cbs_get_data(struct ofono_cbs* cbs) + { return ofono_cbs_get_data(cbs); } + +static +BinderCbsCbData* +binder_cbs_callback_data_new( + BinderCbs* self, + ofono_cbs_set_cb_t cb, + void* data) +{ + BinderCbsCbData* cbd = g_slice_new0(BinderCbsCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_cbs_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderCbsCbData, cbd); +} + +static +gboolean +binder_cbs_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + return error == RADIO_ERROR_INVALID_STATE; +} + +static +void +binder_cbs_activate_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + struct ofono_error err; + BinderCbsCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_GSM_BROADCAST_ACTIVATION) { + if (error == RADIO_ERROR_NONE) { + cbd->cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_warn("Failed to configure broadcasts, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected setGsmBroadcastActivation response %d", + resp); + } + } + cbd->cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_cbs_activate( + BinderCbs* self, + gboolean activate, + ofono_cbs_set_cb_t cb, + void* data) +{ + /* setGsmBroadcastActivation(int32_t serial, bool activate); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_GSM_BROADCAST_ACTIVATION, &writer, + binder_cbs_activate_cb, + binder_cbs_callback_data_free, + binder_cbs_callback_data_new(self, cb, data)); + + gbinder_writer_append_bool(&writer, activate); /* activate */ + DBG_(self, "%sactivating CB", activate ? "" : "de"); + radio_request_set_retry_func(req, binder_cbs_retry); + radio_request_set_retry(req, CBS_CHECK_RETRY_MS, CBS_CHECK_RETRY_COUNT); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_cbs_set_config_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderCbsCbData* cbd = user_data; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_GSM_BROADCAST_CONFIG) { + if (error == RADIO_ERROR_NONE) { + binder_cbs_activate(cbd->self, TRUE, cbd->cb, cbd->data); + return; + } else { + ofono_warn("Failed to set broadcast config, error %d", error); + } + } else { + ofono_error("Unexpected setGsmBroadcastConfig response %d", + resp); + } + } + cbd->cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_cbs_set_config( + BinderCbs* self, + const char* topics, + ofono_cbs_set_cb_t cb, + void* data) +{ + /* setGsmBroadcastConfig(int32_t serial, vec); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_GSM_BROADCAST_CONFIG, &writer, + binder_cbs_set_config_cb, + binder_cbs_callback_data_free, + binder_cbs_callback_data_new(self, cb, data)); + + GBinderParent parent; + GBinderHidlVec* vec = gbinder_writer_new0(&writer, GBinderHidlVec); + RadioGsmBroadcastSmsConfig* configs = NULL; + char** list = topics ? g_strsplit(topics, ",", 0) : NULL; + const guint count = gutil_strv_length(list); + guint i; + + vec->count = count; + vec->owns_buffer = TRUE; + vec->data.ptr = configs = gbinder_writer_malloc0(&writer, + sizeof(RadioGsmBroadcastSmsConfig) * count); + + for (i = 0; i < count; i++) { + RadioGsmBroadcastSmsConfig* config = configs + i; + const char* entry = list[i]; + const char* delim = strchr(entry, '-'); + + config->selected = TRUE; + config->toCodeScheme = 0xff; + if (delim) { + char** range = g_strsplit(entry, "-", 0); + + config->fromServiceId = atoi(range[0]); + config->toServiceId = atoi(range[1]); + g_strfreev(range); + } else { + config->fromServiceId = config->toServiceId = atoi(entry); + } + } + + /* Every vector, even the one without data, requires two buffer objects */ + parent.offset = GBINDER_HIDL_VEC_BUFFER_OFFSET; + parent.index = gbinder_writer_append_buffer_object(&writer, vec, + sizeof(*vec)); + gbinder_writer_append_buffer_object_with_parent(&writer, configs, + sizeof(configs[0]) * count, &parent); + + DBG_(self, "configuring CB"); + radio_request_set_retry_func(req, binder_cbs_retry); + radio_request_set_retry(req, CBS_CHECK_RETRY_MS, CBS_CHECK_RETRY_COUNT); + radio_request_submit(req); + radio_request_unref(req); + g_strfreev(list); +} + +static +void +binder_cbs_set_topics( + struct ofono_cbs* cbs, + const char* topics, + ofono_cbs_set_cb_t cb, + void* data) +{ + BinderCbs* self = binder_cbs_get_data(cbs); + + DBG_(self, "%s", topics); + binder_cbs_set_config(self, topics, cb, data); +} + +static +void +binder_cbs_clear_topics( + struct ofono_cbs* cbs, + ofono_cbs_set_cb_t cb, + void *data) +{ + BinderCbs* self = binder_cbs_get_data(cbs); + + DBG_(self, ""); + binder_cbs_activate(self, FALSE, cb, data); +} + +static +void +binder_cbs_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderCbs* self = user_data; + GBinderReader reader; + const guchar* ptr; + gsize len; + + /* newBroadcastSms(RadioIndicationType type, vec data); */ + GASSERT(code == RADIO_IND_NEW_BROADCAST_SMS); + gbinder_reader_copy(&reader, args); + ptr = gbinder_reader_read_hidl_byte_vec(&reader, &len); + + /* By default assume that it's a length followed by the binary PDU data. */ + if (ptr) { + if (len > 4) { + const guint32 pdu_len = GUINT32_FROM_LE(*(guint32*)ptr); + + if (G_ALIGN4(pdu_len) == (len - 4)) { + DBG_(self, "%u bytes", pdu_len); + ofono_cbs_notify(self->cbs, ptr + 4, pdu_len); + return; + } + } + + /* + * But I've seen cell broadcasts arriving without the length, + * simply as a blob. + */ + ofono_cbs_notify(self->cbs, ptr, (guint) len); + } +} + +static +gboolean +binder_cbs_register( + gpointer user_data) +{ + BinderCbs* self = user_data; + RadioClient* client = self->g->client; + + GASSERT(self->register_id); + self->register_id = 0; + DBG_(self, "registering for CB"); + self->event_id = radio_client_add_indication_handler(client, + RADIO_IND_NEW_BROADCAST_SMS, binder_cbs_notify, self); + ofono_cbs_register(self->cbs); + return G_SOURCE_REMOVE; +} + +static +int +binder_cbs_probe( + struct ofono_cbs* cbs, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderCbs* self = g_new0(BinderCbs, 1); + + self->cbs = cbs; + self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_cbs_register, self); + + DBG_(self, ""); + ofono_cbs_set_data(cbs, self); + return 0; +} + +static +void +binder_cbs_remove( + struct ofono_cbs* cbs) +{ + BinderCbs* self = binder_cbs_get_data(cbs); + + DBG_(self, ""); + if (self->register_id) { + g_source_remove(self->register_id); + } + radio_client_remove_handler(self->g->client, self->event_id); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + g_free(self->log_prefix); + g_free(self); + + ofono_cbs_set_data(cbs, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_cbs_driver binder_cbs_driver = { + .name = BINDER_DRIVER, + .probe = binder_cbs_probe, + .remove = binder_cbs_remove, + .set_topics = binder_cbs_set_topics, + .clear_topics = binder_cbs_clear_topics +}; + +void +binder_cbs_init() +{ + ofono_cbs_driver_register(&binder_cbs_driver); +} + +void +binder_cbs_cleanup() +{ + ofono_cbs_driver_unregister(&binder_cbs_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_cbs.h b/src/binder_cbs.h new file mode 100644 index 0000000..50b57d3 --- /dev/null +++ b/src/binder_cbs.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CBS_H +#define BINDER_CBS_H + +#include "binder_types.h" + +void +binder_cbs_init(void) + BINDER_INTERNAL; + +void +binder_cbs_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_CBS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_cell_info.c b/src/binder_cell_info.c new file mode 100644 index 0000000..78a4f9e --- /dev/null +++ b/src/binder_cell_info.c @@ -0,0 +1,939 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_cell_info.h" +#include "binder_sim_card.h" +#include "binder_radio.h" +#include "binder_util.h" +#include "binder_log.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define DEFAULT_UPDATE_RATE_MS (10000) /* 10 sec */ +#define MAX_RETRIES (5) + +enum binder_cell_info_event { + CELL_INFO_EVENT_1_0, + CELL_INFO_EVENT_1_2, + CELL_INFO_EVENT_1_4, + CELL_INFO_EVENT_COUNT +}; + +typedef GObjectClass BinderCellInfoClass; +typedef struct binder_cell_info { + GObject object; + struct ofono_cell_info info; + struct ofono_cell **cells; + RadioClient* client; + BinderRadio* radio; + BinderSimCard* sim_card; + gulong radio_state_event_id; + gulong sim_status_event_id; + gboolean sim_card_ready; + int update_rate_ms; + char* log_prefix; + gulong event_id[CELL_INFO_EVENT_COUNT]; + RadioRequest* query_req; + RadioRequest* set_rate_req; + gboolean enabled; +} BinderCellInfo; + +enum binder_cell_info_signal { + SIGNAL_CELLS_CHANGED, + SIGNAL_COUNT +}; + +#define SIGNAL_CELLS_CHANGED_NAME "binder-cell-info-cells-changed" + +static guint binder_cell_info_signals[SIGNAL_COUNT] = { 0 }; + +G_DEFINE_TYPE(BinderCellInfo, binder_cell_info, G_TYPE_OBJECT) +#define THIS_TYPE (binder_cell_info_get_type()) +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), THIS_TYPE, BinderCellInfo)) +#define PARENT_CLASS binder_cell_info_parent_class + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +/* + * binder_cell_info_list_equal() assumes that zero-initialized + * struct ofono_cell gets allocated regardless of the cell type, + * even if a part of the structure remains unused. + */ + +#define binder_cell_new() g_new0(struct ofono_cell, 1) + +static +const char* +binder_cell_info_int_format( + int value, + const char* format) +{ + if (value == OFONO_CELL_INVALID_VALUE) { + return ""; + } else { + static GUtilIdlePool* binder_cell_info_pool = NULL; + GUtilIdlePool* pool = gutil_idle_pool_get(&binder_cell_info_pool); + char* str = g_strdup_printf(format, value); + + gutil_idle_pool_add(pool, str, g_free); + return str; + } +} + +static +gint +binder_cell_info_list_compare( + gconstpointer a, + gconstpointer b) +{ + return ofono_cell_compare_location(*(struct ofono_cell**)a, + *(struct ofono_cell**)b); +} + +static +gboolean +binder_cell_info_list_equal( + const ofono_cell_ptr* l1, + const ofono_cell_ptr* l2) +{ + if (l1 && l2) { + while (*l1 && *l2) { + if (memcmp(*l1, *l2, sizeof(struct ofono_cell))) { + return FALSE; + } + l1++; + l2++; + } + return !*l1 && !*l2; + } else { + return (!l1 || !*l1) && (!l2 || !*l2); + } +} + +static +void +binder_cell_info_clear( + BinderCellInfo* self) +{ + if (self->cells && self->cells[0]) { + gutil_ptrv_free((void**)self->cells); + self->info.cells = self->cells = g_new0(struct ofono_cell*, 1); + g_signal_emit(self, binder_cell_info_signals[SIGNAL_CELLS_CHANGED], 0); + } +} + +/* NULL-terminates and takes ownership of GPtrArray */ +static +void +binder_cell_info_update_cells( + BinderCellInfo* self, + GPtrArray* l) +{ + if (l) { + g_ptr_array_sort(l, binder_cell_info_list_compare); + g_ptr_array_add(l, NULL); + + DBG_(self, "%d cell(s)", (int)(l->len - 1)); + if (!binder_cell_info_list_equal(self->cells, + (struct ofono_cell**)l->pdata)) { + gutil_ptrv_free((void**)self->cells); + self->info.cells = self->cells = (struct ofono_cell **) + g_ptr_array_free(l, FALSE); + g_signal_emit(self, binder_cell_info_signals + [SIGNAL_CELLS_CHANGED], 0); + } else { + g_ptr_array_set_free_func(l, g_free); + g_ptr_array_free(l, TRUE); + } + } +} + +static +void +binder_cell_info_invalidate( + void* info, + gsize size) +{ + const int n = size/sizeof(int); + int* value = info; + int i; + + for (i = 0; i < n; i++) { + *value++ = OFONO_CELL_INVALID_VALUE; + } +} + +static +struct ofono_cell* +binder_cell_info_new_cell_gsm( + gboolean registered, + const RadioCellIdentityGsm* id, + const RadioSignalStrengthGsm* ss) +{ + struct ofono_cell* cell = binder_cell_new(); + struct ofono_cell_info_gsm* gsm = &cell->info.gsm; + + cell->type = OFONO_CELL_TYPE_GSM; + cell->registered = registered; + + binder_cell_info_invalidate(gsm, sizeof(*gsm)); + gutil_parse_int(id->mcc.data.str, 10, &gsm->mcc); + gutil_parse_int(id->mnc.data.str, 10, &gsm->mnc); + gsm->lac = id->lac; + gsm->cid = id->cid; + gsm->arfcn = id->arfcn; + gsm->bsic = id->bsic; + gsm->signalStrength = ss->signalStrength; + gsm->bitErrorRate = ss->bitErrorRate; + gsm->timingAdvance = ss->timingAdvance; + DBG("[gsm] reg=%d%s%s%s%s%s%s%s%s%s", registered, + binder_cell_info_int_format(gsm->mcc, ",mcc=%d"), + binder_cell_info_int_format(gsm->mnc, ",mnc=%d"), + binder_cell_info_int_format(gsm->lac, ",lac=%d"), + binder_cell_info_int_format(gsm->cid, ",cid=%d"), + binder_cell_info_int_format(gsm->arfcn, ",arfcn=%d"), + binder_cell_info_int_format(gsm->bsic, ",bsic=%d"), + binder_cell_info_int_format(gsm->signalStrength, ",strength=%d"), + binder_cell_info_int_format(gsm->bitErrorRate, ",err=%d"), + binder_cell_info_int_format(gsm->timingAdvance, ",t=%d")); + return cell; +} + +static +struct +ofono_cell* +binder_cell_info_new_cell_wcdma( + gboolean registered, + const RadioCellIdentityWcdma* id, + const RadioSignalStrengthWcdma* ss) +{ + struct ofono_cell* cell = binder_cell_new(); + struct ofono_cell_info_wcdma* wcdma = &cell->info.wcdma; + + cell->type = OFONO_CELL_TYPE_WCDMA; + cell->registered = registered; + + binder_cell_info_invalidate(wcdma, sizeof(*wcdma)); + gutil_parse_int(id->mcc.data.str, 10, &wcdma->mcc); + gutil_parse_int(id->mnc.data.str, 10, &wcdma->mnc); + wcdma->lac = id->lac; + wcdma->cid = id->cid; + wcdma->psc = id->psc; + wcdma->uarfcn = id->uarfcn; + wcdma->signalStrength = ss->signalStrength; + wcdma->bitErrorRate = ss->bitErrorRate; + DBG("[wcdma] reg=%d%s%s%s%s%s%s%s", registered, + binder_cell_info_int_format(wcdma->mcc, ",mcc=%d"), + binder_cell_info_int_format(wcdma->mnc, ",mnc=%d"), + binder_cell_info_int_format(wcdma->lac, ",lac=%d"), + binder_cell_info_int_format(wcdma->cid, ",cid=%d"), + binder_cell_info_int_format(wcdma->psc, ",psc=%d"), + binder_cell_info_int_format(wcdma->signalStrength, ",strength=%d"), + binder_cell_info_int_format(wcdma->bitErrorRate, ",err=%d")); + return cell; +} + +static +struct ofono_cell* +binder_cell_info_new_cell_lte( + gboolean registered, + const RadioCellIdentityLte* id, + const RadioSignalStrengthLte* ss) +{ + struct ofono_cell* cell = binder_cell_new(); + struct ofono_cell_info_lte* lte = &cell->info.lte; + + cell->type = OFONO_CELL_TYPE_LTE; + cell->registered = registered; + + binder_cell_info_invalidate(lte, sizeof(*lte)); + gutil_parse_int(id->mcc.data.str, 10, <e->mcc); + gutil_parse_int(id->mnc.data.str, 10, <e->mnc); + lte->ci = id->ci; + lte->pci = id->pci; + lte->tac = id->tac; + lte->earfcn = id->earfcn; + lte->signalStrength = ss->signalStrength; + lte->rsrp = ss->rsrp; + lte->rsrq = ss->rsrq; + lte->rssnr = ss->rssnr; + lte->cqi = ss->cqi; + lte->timingAdvance = ss->timingAdvance; + DBG("[lte] reg=%d%s%s%s%s%s%s%s%s%s%s%s", registered, + binder_cell_info_int_format(lte->mcc, ",mcc=%d"), + binder_cell_info_int_format(lte->mnc, ",mnc=%d"), + binder_cell_info_int_format(lte->ci, ",ci=%d"), + binder_cell_info_int_format(lte->pci, ",pci=%d"), + binder_cell_info_int_format(lte->tac, ",tac=%d"), + binder_cell_info_int_format(lte->signalStrength, ",strength=%d"), + binder_cell_info_int_format(lte->rsrp, ",rsrp=%d"), + binder_cell_info_int_format(lte->rsrq, ",rsrq=%d"), + binder_cell_info_int_format(lte->rssnr, ",rssnr=%d"), + binder_cell_info_int_format(lte->cqi, ",cqi=%d"), + binder_cell_info_int_format(lte->timingAdvance, ",t=%d")); + return cell; +} + +static +GPtrArray* +binder_cell_info_array_new_1_0( + const RadioCellInfo* cells, + gsize count) +{ + gsize i; + GPtrArray* l = g_ptr_array_sized_new(count + 1); + + for (i = 0; i < count; i++) { + const RadioCellInfo* cell = cells + i; + const gboolean reg = cell->registered; + const RadioCellInfoGsm* gsm; + const RadioCellInfoLte* lte; + const RadioCellInfoWcdma* wcdma; + guint j; + + switch (cell->cellInfoType) { + case RADIO_CELL_INFO_GSM: + gsm = cell->gsm.data.ptr; + for (j = 0; j < cell->gsm.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_gsm(reg, + &gsm[j].cellIdentityGsm, + &gsm[j].signalStrengthGsm)); + } + continue; + case RADIO_CELL_INFO_LTE: + lte = cell->lte.data.ptr; + for (j = 0; j < cell->lte.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_lte(reg, + <e[j].cellIdentityLte, + <e[j].signalStrengthLte)); + } + continue; + case RADIO_CELL_INFO_WCDMA: + wcdma = cell->wcdma.data.ptr; + for (j = 0; j < cell->wcdma.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_wcdma(reg, + &wcdma[j].cellIdentityWcdma, + &wcdma[j].signalStrengthWcdma)); + } + continue; + case RADIO_CELL_INFO_CDMA: + case RADIO_CELL_INFO_TD_SCDMA: + break; + } + DBG("unsupported cell type %d", cell->cellInfoType); + } + return l; +} + +static +GPtrArray* +binder_cell_info_array_new_1_2( + const RadioCellInfo_1_2* cells, + gsize count) +{ + gsize i; + GPtrArray* l = g_ptr_array_sized_new(count + 1); + + for (i = 0; i < count; i++) { + const RadioCellInfo_1_2* cell = cells + i; + const gboolean registered = cell->registered; + const RadioCellInfoGsm_1_2* gsm; + const RadioCellInfoLte_1_2* lte; + const RadioCellInfoWcdma_1_2* wcdma; + guint j; + + switch (cell->cellInfoType) { + case RADIO_CELL_INFO_GSM: + gsm = cell->gsm.data.ptr; + for (j = 0; j < cell->gsm.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_gsm(registered, + &gsm[j].cellIdentityGsm.base, + &gsm[j].signalStrengthGsm)); + } + continue; + case RADIO_CELL_INFO_LTE: + lte = cell->lte.data.ptr; + for (j = 0; j < cell->lte.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_lte(registered, + <e[j].cellIdentityLte.base, + <e[j].signalStrengthLte)); + } + continue; + case RADIO_CELL_INFO_WCDMA: + wcdma = cell->wcdma.data.ptr; + for (j = 0; j < cell->wcdma.count; j++) { + g_ptr_array_add(l, binder_cell_info_new_cell_wcdma(registered, + &wcdma[j].cellIdentityWcdma.base, + &wcdma[j].signalStrengthWcdma.base)); + } + continue; + case RADIO_CELL_INFO_CDMA: + case RADIO_CELL_INFO_TD_SCDMA: + break; + } + DBG("unsupported cell type %d", cell->cellInfoType); + } + return l; +} + +static +GPtrArray* +binder_cell_info_array_new_1_4( + const RadioCellInfo_1_4* cells, + gsize count) +{ + gsize i; + GPtrArray* l = g_ptr_array_sized_new(count + 1); + + for (i = 0; i < count; i++) { + const RadioCellInfo_1_4* cell = cells + i; + const gboolean registered = cell->registered; + + switch ((RADIO_CELL_INFO_TYPE_1_4)cell->cellInfoType) { + case RADIO_CELL_INFO_1_4_GSM: + g_ptr_array_add(l, binder_cell_info_new_cell_gsm(registered, + &cell->info.gsm.cellIdentityGsm.base, + &cell->info.gsm.signalStrengthGsm)); + continue; + case RADIO_CELL_INFO_1_4_LTE: + g_ptr_array_add(l, binder_cell_info_new_cell_lte(registered, + &cell->info.lte.base.cellIdentityLte.base, + &cell->info.lte.base.signalStrengthLte)); + continue; + case RADIO_CELL_INFO_1_4_WCDMA: + g_ptr_array_add(l, binder_cell_info_new_cell_wcdma(registered, + &cell->info.wcdma.cellIdentityWcdma.base, + &cell->info.wcdma.signalStrengthWcdma.base)); + continue; + case RADIO_CELL_INFO_1_4_TD_SCDMA: + case RADIO_CELL_INFO_1_4_CDMA: + case RADIO_CELL_INFO_1_4_NR: + break; + } + DBG("unsupported cell type %d", cell->cellInfoType); + } + return l; +} + +static +void +binder_cell_info_list_1_0( + BinderCellInfo* self, + GBinderReader* reader) +{ + gsize count; + const RadioCellInfo* cells = gbinder_reader_read_hidl_type_vec(reader, + RadioCellInfo, &count); + + if (cells) { + binder_cell_info_update_cells(self, + binder_cell_info_array_new_1_0(cells, count)); + } else { + ofono_warn("Failed to parse cellInfoList payload"); + } +} + +static +void +binder_cell_info_list_1_2( + BinderCellInfo* self, + GBinderReader* reader) +{ + gsize count; + const RadioCellInfo_1_2* cells = gbinder_reader_read_hidl_type_vec(reader, + RadioCellInfo_1_2, &count); + + if (cells) { + binder_cell_info_update_cells(self, + binder_cell_info_array_new_1_2(cells, count)); + } else { + ofono_warn("Failed to parse cellInfoList_1_2 payload"); + } +} + +static +void +binder_cell_info_list_1_4( + BinderCellInfo* self, + GBinderReader* reader) +{ + gsize count; + const RadioCellInfo_1_4* cells = gbinder_reader_read_hidl_type_vec(reader, + RadioCellInfo_1_4, &count); + + if (cells) { + binder_cell_info_update_cells(self, + binder_cell_info_array_new_1_4(cells, count)); + } else { + ofono_warn("Failed to parse cellInfoList_1_4 payload"); + } +} + +static +void +binder_cell_info_list_changed_1_0( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderCellInfo* self = THIS(user_data); + + GASSERT(code == RADIO_IND_CELL_INFO_LIST); + if (self->enabled) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + binder_cell_info_list_1_0(self, &reader); + } +} + +static +void +binder_cell_info_list_changed_1_2( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderCellInfo* self = THIS(user_data); + + GASSERT(code == RADIO_IND_CELL_INFO_LIST_1_2); + if (self->enabled) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + binder_cell_info_list_1_2(self, &reader); + } +} + +static +void +binder_cell_info_list_changed_1_4( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderCellInfo* self = THIS(user_data); + + GASSERT(code == RADIO_IND_CELL_INFO_LIST_1_4); + if (self->enabled) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + binder_cell_info_list_1_4(self, &reader); + } +} + +static +void +binder_cell_info_list_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderCellInfo* self = THIS(user_data); + + GASSERT(self->query_req == req); + radio_request_drop(self->query_req); + self->query_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (self->enabled) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + switch (resp) { + case RADIO_RESP_GET_CELL_INFO_LIST: + binder_cell_info_list_1_0(self, &reader); + break; + case RADIO_RESP_GET_CELL_INFO_LIST_1_2: + binder_cell_info_list_1_2(self, &reader); + break; + case RADIO_RESP_GET_CELL_INFO_LIST_1_4: + binder_cell_info_list_1_4(self, &reader); + break; + default: + ofono_warn("Unexpected getCellInfoList response %d", resp); + break; + } + } + } else { + DBG_(self, "%s error %d", radio_resp_name(resp), error); + } + } +} + +static +void +binder_cell_info_set_rate_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderCellInfo* self = THIS(user_data); + + DBG_(self, ""); + GASSERT(self->set_rate_req == req); + radio_request_drop(self->set_rate_req); + self->set_rate_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_CELL_INFO_LIST_RATE) { + if (error != RADIO_ERROR_NONE) { + DBG_(self, "Failed to set cell info rate, error %d", error); + } + } else { + ofono_error("Unexpected setCellInfoListRate response %d", resp); + } + } +} + +static +gboolean +binder_cell_info_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + BinderCellInfo* self = THIS(user_data); + + switch (error) { + case RADIO_ERROR_NONE: + case RADIO_ERROR_RADIO_NOT_AVAILABLE: + return FALSE; + default: + return self->enabled; + } +} + +static +void +binder_cell_info_query( + BinderCellInfo* self) +{ + radio_request_drop(self->query_req); + self->query_req = radio_request_new(self->client, + RADIO_REQ_GET_CELL_INFO_LIST, NULL, + binder_cell_info_list_cb, NULL, self); + radio_request_set_retry(self->query_req, BINDER_RETRY_MS, MAX_RETRIES); + radio_request_set_retry_func(self->query_req, binder_cell_info_retry); + radio_request_submit(self->query_req); +} + +static +void +binder_cell_info_set_rate( + BinderCellInfo* self) +{ + GBinderWriter writer; + + radio_request_drop(self->set_rate_req); + self->set_rate_req = radio_request_new(self->client, + RADIO_REQ_SET_CELL_INFO_LIST_RATE, &writer, + binder_cell_info_set_rate_cb, NULL, self); + + gbinder_writer_append_int32(&writer, + (self->update_rate_ms >= 0 && self->enabled) ? + self->update_rate_ms : INT_MAX); + + radio_request_set_retry(self->set_rate_req, BINDER_RETRY_MS, MAX_RETRIES); + radio_request_set_retry_func(self->set_rate_req, binder_cell_info_retry); + radio_request_submit(self->set_rate_req); +} + +static +void +binder_cell_info_refresh( + BinderCellInfo* self) +{ + /* getCellInfoList fails without SIM card */ + if (self->enabled && + self->radio->state == RADIO_STATE_ON && + self->sim_card_ready) { + binder_cell_info_query(self); + } else { + binder_cell_info_clear(self); + } +} + +static +void +binder_cell_info_radio_state_cb( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* user_data) +{ + BinderCellInfo* self = THIS(user_data); + + DBG_(self, "%s", binder_radio_state_string(radio->state)); + binder_cell_info_refresh(self); +} + +static +void +binder_cell_info_sim_status_cb( + BinderSimCard* sim, + void* user_data) +{ + BinderCellInfo* self = THIS(user_data); + + self->sim_card_ready = binder_sim_card_ready(sim); + DBG_(self, "%sready", self->sim_card_ready ? "" : "not "); + binder_cell_info_refresh(self); + if (self->sim_card_ready) { + binder_cell_info_set_rate(self); + } +} + +/*==========================================================================* + * ofono_cell_info interface + *==========================================================================*/ + +typedef struct binder_cell_info_closure { + GCClosure cclosure; + ofono_cell_info_cb_t cb; + void* user_data; +} BinderCellInfoClosure; + +static inline BinderCellInfo* binder_cell_info_cast(struct ofono_cell_info* info) + { return G_CAST(info, BinderCellInfo, info); } + +static +void +binder_cell_info_ref_proc( + struct ofono_cell_info* info) +{ + g_object_ref(binder_cell_info_cast(info)); +} + +static +void +binder_cell_info_unref_proc( + struct ofono_cell_info* info) +{ + g_object_unref(binder_cell_info_cast(info)); +} + +static +void +binder_cell_info_cells_changed_cb( + BinderCellInfo* self, + BinderCellInfoClosure* closure) +{ + closure->cb(&self->info, closure->user_data); +} + +static +gulong +binder_cell_info_add_cells_changed_handler_proc( + struct ofono_cell_info* info, + ofono_cell_info_cb_t cb, + void* user_data) +{ + if (cb) { + BinderCellInfoClosure* closure = (BinderCellInfoClosure *) + g_closure_new_simple(sizeof(BinderCellInfoClosure), NULL); + GCClosure* cc = &closure->cclosure; + + cc->closure.data = closure; + cc->callback = G_CALLBACK(binder_cell_info_cells_changed_cb); + closure->cb = cb; + closure->user_data = user_data; + return g_signal_connect_closure_by_id(binder_cell_info_cast(info), + binder_cell_info_signals[SIGNAL_CELLS_CHANGED], 0, + &cc->closure, FALSE); + } else { + return 0; + } +} + +static +void +binder_cell_info_remove_handler_proc( + struct ofono_cell_info* info, + gulong id) +{ + if (G_LIKELY(id)) { + g_signal_handler_disconnect(binder_cell_info_cast(info), id); + } +} + +static +void +binder_cell_info_set_update_interval_proc( + struct ofono_cell_info* info, + int ms) +{ + BinderCellInfo* self = binder_cell_info_cast(info); + + if (self->update_rate_ms != ms) { + self->update_rate_ms = ms; + DBG_(self, "%d ms", ms); + if (self->enabled && self->sim_card_ready) { + binder_cell_info_set_rate(self); + } + } +} + +static +void +binder_cell_info_set_enabled_proc( + struct ofono_cell_info* info, + gboolean enabled) +{ + BinderCellInfo* self = binder_cell_info_cast(info); + + if (self->enabled != enabled) { + self->enabled = enabled; + DBG_(self, "%d", enabled); + binder_cell_info_refresh(self); + if (self->sim_card_ready) { + binder_cell_info_set_rate(self); + } + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +struct ofono_cell_info* +binder_cell_info_new( + RadioClient* client, + const char* log_prefix, + BinderRadio* radio, + BinderSimCard* sim) +{ + BinderCellInfo* self = g_object_new(THIS_TYPE, 0); + + self->client = radio_client_ref(client); + self->radio = binder_radio_ref(radio); + self->sim_card = binder_sim_card_ref(sim); + self->log_prefix = binder_dup_prefix(log_prefix); + + DBG_(self, ""); + self->event_id[CELL_INFO_EVENT_1_0] = + radio_client_add_indication_handler(client, + RADIO_IND_CELL_INFO_LIST, + binder_cell_info_list_changed_1_0, self); + self->event_id[CELL_INFO_EVENT_1_2] = + radio_client_add_indication_handler(client, + RADIO_IND_CELL_INFO_LIST_1_2, + binder_cell_info_list_changed_1_2, self); + self->event_id[CELL_INFO_EVENT_1_4] = + radio_client_add_indication_handler(client, + RADIO_IND_CELL_INFO_LIST_1_4, + binder_cell_info_list_changed_1_4, self); + self->radio_state_event_id = + binder_radio_add_property_handler(radio, + BINDER_RADIO_PROPERTY_STATE, + binder_cell_info_radio_state_cb, self); + self->sim_status_event_id = + binder_sim_card_add_status_changed_handler(sim, + binder_cell_info_sim_status_cb, self); + self->sim_card_ready = binder_sim_card_ready(sim); + binder_cell_info_refresh(self); + + /* Disable updates by default */ + self->enabled = FALSE; + if (self->sim_card_ready) { + binder_cell_info_set_rate(self); + } + return &self->info; +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_cell_info_init( + BinderCellInfo* self) +{ + static const struct ofono_cell_info_proc binder_cell_info_proc = { + binder_cell_info_ref_proc, + binder_cell_info_unref_proc, + binder_cell_info_add_cells_changed_handler_proc, + binder_cell_info_remove_handler_proc, + binder_cell_info_set_update_interval_proc, + binder_cell_info_set_enabled_proc + }; + + self->update_rate_ms = DEFAULT_UPDATE_RATE_MS; + self->info.cells = self->cells = g_new0(struct ofono_cell*, 1); + self->info.proc = &binder_cell_info_proc; +} + +static +void +binder_cell_info_finalize( + GObject* object) +{ + BinderCellInfo* self = THIS(object); + + DBG_(self, ""); + radio_request_drop(self->query_req); + radio_request_drop(self->set_rate_req); + radio_client_remove_all_handlers(self->client, self->event_id); + radio_client_unref(self->client); + binder_radio_remove_handler(self->radio, self->radio_state_event_id); + binder_radio_unref(self->radio); + binder_sim_card_remove_handler(self->sim_card, self->sim_status_event_id); + binder_sim_card_unref(self->sim_card); + gutil_ptrv_free((void**)self->cells); + g_free(self->log_prefix); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_cell_info_class_init( + BinderCellInfoClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_cell_info_finalize; + binder_cell_info_signals[SIGNAL_CELLS_CHANGED] = + g_signal_new(SIGNAL_CELLS_CHANGED_NAME, G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_cell_info.h b/src/binder_cell_info.h new file mode 100644 index 0000000..d73e4c0 --- /dev/null +++ b/src/binder_cell_info.h @@ -0,0 +1,39 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CELL_INFO_H +#define BINDER_CELL_INFO_H + +#include "binder_types.h" + +#include + +struct ofono_cell_info* +binder_cell_info_new( + RadioClient* client, + const char* log_prefix, + BinderRadio* radio, + BinderSimCard* sim) + BINDER_INTERNAL; + +#endif /* BINDER_CELL_INFO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_connman.c b/src/binder_connman.c new file mode 100644 index 0000000..44570db --- /dev/null +++ b/src/binder_connman.c @@ -0,0 +1,627 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2019-2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_connman.h" + +#include +#include + +#include +#include + +#include + +BINDER_BASE_ASSERT_COUNT(BINDER_CONNMAN_PROPERTY_COUNT); + +#define CONNMAN_BUS DBUS_BUS_SYSTEM +#define CONNMAN_SERVICE "net.connman" +#define CONNMAN_PATH "/" + +#define CONNMAN_GET_PROPERTIES "GetProperties" +#define CONNMAN_GET_TECHNOLOGIES "GetTechnologies" +#define CONNMAN_PROPERTY_CHANGED "PropertyChanged" +#define CONNMAN_TECH_CONNECTED "Connected" +#define CONNMAN_TECH_TETHERING "Tethering" + +#define CONNMAN_INTERFACE_(name) "net.connman." name +#define CONNMAN_MANAGER_INTERFACE CONNMAN_INTERFACE_("Manager") +#define CONNMAN_TECH_INTERFACE CONNMAN_INTERFACE_("Technology") + +#define CONNMAN_TECH_PATH_(name) "/net/connman/technology/" name +#define CONNMAN_TECH_PATH_WIFI CONNMAN_TECH_PATH_("wifi") + +#define CONNMAN_TECH_CONNECTED_BIT (0x01) +#define CONNMAN_TECH_TETHERING_BIT (0x02) +#define CONNMAN_TECH_ALL_PROPERTY_BITS (\ + CONNMAN_TECH_CONNECTED_BIT | \ + CONNMAN_TECH_TETHERING_BIT) + +typedef BinderBaseClass ConnManObjectClass; + +typedef struct connman_tech ConnManTech; + +typedef struct connman_object { + BinderBase base; + BinderConnman pub; + DBusConnection* connection; + DBusPendingCall* call; + guint service_watch; + guint signal_watch; + GHashTable* techs; + ConnManTech* wifi; +} ConnManObject; + +GType connman_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(ConnManObject, connman_object, BINDER_TYPE_BASE) +#define PARENT_CLASS connman_object_parent_class +#define THIS_TYPE connman_object_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, ConnManObject) + +struct connman_tech { + ConnManObject* obj; + const char* path; + gboolean connected; + gboolean tethering; +}; + +#define SIGNAL_BIT_(name) \ + BINDER_BASE_PROPERTY_BIT(BINDER_CONNMAN_PROPERTY_##name) + +static inline +ConnManObject* +connman_object_cast( + BinderConnman* connman) +{ + return G_LIKELY(connman) ? + THIS(G_CAST(connman, ConnManObject, pub)) : + NULL; +} + +static inline +const char* +connman_iter_get_string( + DBusMessageIter* it) +{ + const char* str = NULL; + + dbus_message_iter_get_basic(it, &str); + return str; +} + +static +void +connman_object_emit_pending_signals( + ConnManObject* self) +{ + BinderBase* base = &self->base; + BinderConnman* connman = &self->pub; + gsize late_signals = 0; + + /* Handlers could drop their references to us */ + g_object_ref(self); + + /* + * PRESENT and VALID are the last signals to be emitted if the object + * BECOMES present and/or valid. + */ + if ((base->queued_signals & SIGNAL_BIT_(VALID)) && connman->valid) { + base->queued_signals &= ~SIGNAL_BIT_(VALID); + late_signals |= SIGNAL_BIT_(VALID); + } + if ((base->queued_signals & SIGNAL_BIT_(PRESENT)) && connman->present) { + base->queued_signals &= ~SIGNAL_BIT_(PRESENT); + late_signals |= SIGNAL_BIT_(PRESENT); + } + + /* + * Emit the signals. Not that in case if valid has become FALSE, + * then VALID is emitted first, otherwise it's emitted last. + * Same thing with PRESENT. + */ + binder_base_emit_queued_signals(base); + base->queued_signals |= late_signals; + binder_base_emit_queued_signals(base); + + /* And release the temporary reference */ + g_object_unref(self); +} + +static +void +connman_cancel_call( + ConnManObject* self) +{ + if (self->call) { + dbus_pending_call_cancel(self->call); + dbus_pending_call_unref(self->call); + self->call = NULL; + } +} + +static +ConnManTech* +connman_tech_new( + ConnManObject* self, + const char* path) +{ + ConnManTech* tech = g_new0(ConnManTech, 1); + char* key = g_strdup(path); + + tech->obj = self; + tech->path = key; + g_hash_table_replace(self->techs, key, tech); + return tech; +} + +static +void +connman_invalidate( + ConnManObject* self) +{ + BinderConnman* connman = &self->pub; + + if (connman->valid) { + connman->valid = FALSE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_VALID); + } +} + +static +void +connman_update_valid( + ConnManObject* self) +{ + BinderConnman* connman = &self->pub; + const gboolean valid = (connman->present && !self->call); + + if (connman->valid != valid) { + connman->valid = valid; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_VALID); + } +} + +static +gboolean +connman_update_tethering( + ConnManObject* self) +{ + BinderConnman* connman = &self->pub; + gboolean tethering = FALSE; + GHashTableIter it; + gpointer value; + + g_hash_table_iter_init(&it, self->techs); + while (g_hash_table_iter_next(&it, NULL, &value)) { + const ConnManTech* tech = value; + + if (tech->tethering) { + tethering = TRUE; + break; + } + } + + if (connman->tethering != tethering) { + connman->tethering = tethering; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_TETHERING); + return TRUE; + } else { + return FALSE; + } +} + +static +void +connman_set_tech_tethering( + ConnManTech* tech, + gboolean tethering) +{ + if (tech->tethering != tethering) { + ConnManObject* self = tech->obj; + + tech->tethering = tethering; + DBG(CONNMAN_TECH_TETHERING " %s for %s", tethering ? "on" : "off", + tech->path); + if (tethering) { + BinderConnman* connman = &self->pub; + + if (G_LIKELY(!connman->tethering)) { + /* Definitely tethering now */ + connman->tethering = TRUE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_TETHERING); + DBG("Tethering on"); + } + } else if (connman_update_tethering(self)) { + /* Not tethering anymore */ + DBG("Tethering off"); + } + } +} + +static +void +connman_set_tech_connected( + ConnManTech* tech, + gboolean connected) +{ + if (tech->connected != connected) { + ConnManObject* self = tech->obj; + + tech->connected = connected; + DBG(CONNMAN_TECH_CONNECTED " %s for %s", connected ? "on" : "off", + tech->path); + if (tech == self->wifi) { + BinderConnman* connman = &self->pub; + + connman->wifi_connected = connected; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_WIFI_CONNECTED); + DBG("WiFi %sconnected", connected ? "" : "dis"); + } + } +} + +static +int +connman_tech_set_property( + ConnManTech* tech, + DBusMessageIter* it) +{ + DBusMessageIter var; + DBusBasicValue value; + const char* key = connman_iter_get_string(it); + + dbus_message_iter_next(it); + dbus_message_iter_recurse(it, &var); + dbus_message_iter_get_basic(&var, &value); + if (!g_ascii_strcasecmp(key, CONNMAN_TECH_CONNECTED)) { + if (dbus_message_iter_get_arg_type(&var) == DBUS_TYPE_BOOLEAN) { + connman_set_tech_connected(tech, value.bool_val); + return CONNMAN_TECH_CONNECTED_BIT; + } + } else if (!g_ascii_strcasecmp(key, CONNMAN_TECH_TETHERING)) { + if (dbus_message_iter_get_arg_type(&var) == DBUS_TYPE_BOOLEAN) { + connman_set_tech_tethering(tech, value.bool_val); + return CONNMAN_TECH_TETHERING_BIT; + } + } + return 0; +} + +static +void +connman_tech_set_properties( + ConnManTech* tech, + DBusMessageIter* it) +{ + DBusMessageIter dict; + int handled = 0; + + dbus_message_iter_recurse(it, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + handled |= connman_tech_set_property(tech, &entry); + if (handled == CONNMAN_TECH_ALL_PROPERTY_BITS) { + /* Ignore the rest */ + break; + } + dbus_message_iter_next(&dict); + } +} + +static +gboolean +connman_tech_property_changed( + DBusConnection* conn, + DBusMessage* msg, + void* user_data) +{ + const char* path = dbus_message_get_path(msg); + ConnManObject* self = THIS(user_data); + ConnManTech* tech = g_hash_table_lookup(self->techs, path); + DBusMessageIter it; + + if (tech && dbus_message_has_signature(msg, "sv") && + dbus_message_iter_init(msg, &it)) { + const char* name = connman_iter_get_string(&it); + + if (!connman_tech_set_property(tech, &it)) { + DBG("%s changed for %s", name, path); + } + connman_object_emit_pending_signals(self); + } + return TRUE; +} + +static +void +connman_set_techs( + ConnManObject* self, + DBusMessageIter* it) +{ + DBusMessageIter list; + + dbus_message_iter_recurse(it, &list); + while (dbus_message_iter_get_arg_type(&list) == DBUS_TYPE_STRUCT) { + DBusMessageIter entry; + const char* path; + ConnManTech* tech; + + dbus_message_iter_recurse(&list, &entry); + path = connman_iter_get_string(&entry); + tech = connman_tech_new(self, path); + + DBG("%s", path); + if (!g_strcmp0(path, CONNMAN_TECH_PATH_WIFI)) { + /* WiFi is a special case */ + self->wifi = tech; + } + + dbus_message_iter_next(&entry); + connman_tech_set_properties(tech, &entry); + dbus_message_iter_next(&list); + } +} + +static +void +connman_techs_reply( + DBusPendingCall* call, + void* user_data) +{ + ConnManObject* self = THIS(user_data); + DBusMessage* reply = dbus_pending_call_steal_reply(call); + DBusError error; + DBusMessageIter array; + + dbus_error_init(&error); + if (dbus_set_error_from_message(&error, reply)) { + DBG("Failed to get technologies: %s", error.message); + dbus_error_free(&error); + } else if (dbus_message_has_signature(reply, "a(oa{sv})") && + dbus_message_iter_init(reply, &array)) { + connman_set_techs(self, &array); + } + + dbus_message_unref(reply); + dbus_pending_call_unref(self->call); + self->call = NULL; + connman_update_valid(self); + connman_object_emit_pending_signals(self); +} + +static +void +connman_get_techs( + ConnManObject* self) +{ + DBusMessage* msg = dbus_message_new_method_call(CONNMAN_SERVICE, + CONNMAN_PATH, CONNMAN_MANAGER_INTERFACE, CONNMAN_GET_TECHNOLOGIES); + + connman_cancel_call(self); + if (g_dbus_send_message_with_reply(self->connection, msg, &self->call, + DBUS_TIMEOUT_INFINITE)) { + /* Not valid while any request is pending */ + connman_invalidate(self); + dbus_pending_call_set_notify(self->call, connman_techs_reply, + self, NULL); + } + dbus_message_unref(msg); +} + +static +void +connman_appeared( + DBusConnection* conn, + void* user_data) +{ + ConnManObject* self = THIS(user_data); + BinderConnman* connman = &self->pub; + + if (!connman->present) { + DBG("connman is there"); + connman->present = TRUE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_PRESENT); + connman_get_techs(self); + connman_object_emit_pending_signals(self); + } +} + +static +void +connman_vanished( + DBusConnection* conn, + void* user_data) +{ + ConnManObject* self = THIS(user_data); + BinderConnman* connman = &self->pub; + + if (connman->present) { + DBG("connman has disappeared"); + g_hash_table_remove_all(self->techs); + self->wifi = NULL; + connman->present = FALSE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_PRESENT); + if (connman->wifi_connected) { + connman->wifi_connected = FALSE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_WIFI_CONNECTED); + } + if (connman->tethering) { + connman->tethering = FALSE; + binder_base_queue_property_change(&self->base, + BINDER_CONNMAN_PROPERTY_TETHERING); + } + connman_object_emit_pending_signals(self); + } +} + +static +void +connman_init( + ConnManObject* self, + DBusConnection* connection) +{ + self->connection = dbus_connection_ref(connection); + self->service_watch = g_dbus_add_service_watch(self->connection, + CONNMAN_SERVICE, connman_appeared, connman_vanished, self, NULL); + self->signal_watch = g_dbus_add_signal_watch(self->connection, + CONNMAN_SERVICE, NULL, CONNMAN_TECH_INTERFACE, + CONNMAN_PROPERTY_CHANGED, connman_tech_property_changed, self, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderConnman* +binder_connman_new() +{ + static ConnManObject* instance = NULL; + + if (instance) { + g_object_ref(instance); + return &instance->pub; + } else { + DBusError error; + DBusConnection* connection; + + dbus_error_init(&error); + connection = dbus_bus_get(CONNMAN_BUS, NULL); + + if (connection) { + instance = g_object_new(THIS_TYPE, NULL); + connman_init(instance, connection); + dbus_connection_unref(connection); + g_object_add_weak_pointer(G_OBJECT(instance), + (gpointer*)(&instance)); + return &instance->pub; + } else { + ofono_error("Unable to attach to connman bus: %s", error.message); + dbus_error_free(&error); + return NULL; + } + } +} + +BinderConnman* +binder_connman_ref( + BinderConnman* connman) +{ + ConnManObject* self = connman_object_cast(connman); + + if (G_LIKELY(self)) { + g_object_ref(self); + } + return connman; +} + +void +binder_connman_unref( + BinderConnman* connman) +{ + ConnManObject* self = connman_object_cast(connman); + + if (G_LIKELY(self)) { + g_object_unref(self); + } +} + +gulong +binder_connman_add_property_changed_handler( + BinderConnman* connman, + BINDER_CONNMAN_PROPERTY property, + BinderConnmanPropertyFunc callback, + void* user_data) +{ + ConnManObject* self = connman_object_cast(connman); + + return G_LIKELY(self) ? binder_base_add_property_handler(&self->base, + property, G_CALLBACK(callback), user_data) : 0; +} + +void +binder_connman_remove_handler( + BinderConnman* connman, + gulong id) +{ + if (G_LIKELY(id)) { + ConnManObject* self = connman_object_cast(connman); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +void +binder_connman_remove_handlers( + BinderConnman* connman, + gulong* ids, + int n) +{ + gutil_disconnect_handlers(connman_object_cast(connman), ids, n); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +connman_object_init( + ConnManObject* self) +{ + self->techs = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); +} + +static +void +connman_object_finalize( + GObject *object) +{ + ConnManObject* self = THIS(object); + + connman_cancel_call(self); + g_hash_table_destroy(self->techs); + g_dbus_remove_watch(self->connection, self->service_watch); + g_dbus_remove_watch(self->connection, self->signal_watch); + dbus_connection_unref(self->connection); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static void connman_object_class_init(ConnManObjectClass *klass) +{ + G_OBJECT_CLASS(klass)->finalize = connman_object_finalize; + BINDER_BASE_CLASS(klass)->public_offset = + G_STRUCT_OFFSET(ConnManObject, pub); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_connman.h b/src/binder_connman.h new file mode 100644 index 0000000..c6a41ea --- /dev/null +++ b/src/binder_connman.h @@ -0,0 +1,91 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2019-2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_CONNMAN_H +#define BINDER_CONNMAN_H + +#include "binder_types.h" + +typedef struct binder_connman { + gboolean valid; /* TRUE if other fields are valid */ + gboolean present; /* ConnMan is present on D-Bus */ + gboolean tethering; /* At least one technology is tethering */ + gboolean wifi_connected; /* WiFi network is connected */ +} BinderConnman; + +typedef enum binder_connman_property { + BINDER_CONNMAN_PROPERTY_ANY, + BINDER_CONNMAN_PROPERTY_VALID, + BINDER_CONNMAN_PROPERTY_PRESENT, + BINDER_CONNMAN_PROPERTY_TETHERING, + BINDER_CONNMAN_PROPERTY_WIFI_CONNECTED, + BINDER_CONNMAN_PROPERTY_COUNT +} BINDER_CONNMAN_PROPERTY; + +typedef +void +(*BinderConnmanPropertyFunc)( + BinderConnman* connman, + BINDER_CONNMAN_PROPERTY property, + void* user_data); + +BinderConnman* +binder_connman_new( + void) + BINDER_INTERNAL; + +BinderConnman* +binder_connman_ref( + BinderConnman* connman) + BINDER_INTERNAL; + +void +binder_connman_unref( + BinderConnman* connman) + BINDER_INTERNAL; + +gulong +binder_connman_add_property_changed_handler( + BinderConnman* connman, + BINDER_CONNMAN_PROPERTY property, + BinderConnmanPropertyFunc fn, + void* user_data) + BINDER_INTERNAL; + +void +binder_connman_remove_handler( + BinderConnman* connman, + gulong id) + BINDER_INTERNAL; + +void +binder_connman_remove_handlers( + BinderConnman* connman, + gulong* ids, + int n) + BINDER_INTERNAL; + +#define binder_connman_remove_all_handlers(connman, ids) \ + binder_connman_remove_handlers(connman, ids, G_N_ELEMENTS(ids)) + +#endif /* BINDER_CONNMAN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_data.c b/src/binder_data.c new file mode 100644 index 0000000..3509477 --- /dev/null +++ b/src/binder_data.c @@ -0,0 +1,2452 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_data.h" +#include "binder_radio.h" +#include "binder_network.h" +#include "binder_sim_settings.h" +#include "binder_util.h" +#include "binder_log.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* Yes, it does sometimes take minutes in roaming */ +#define SETUP_DATA_CALL_TIMEOUT (300*1000) /* ms */ + +typedef enum binder_data_flags { + BINDER_DATA_FLAG_NONE = 0x00, + BINDER_DATA_FLAG_ALLOWED = 0x01, + BINDER_DATA_FLAG_MAX_SPEED = 0x02, + BINDER_DATA_FLAG_ON = 0x04 +} BINDER_DATA_FLAGS; + +/* + * How it works: + * + * This code implements "one data SIM at a time" model. It will have + * to be updated to support multiple data SIMs active simultanously. + * + * There's one binder_data per slot. + * + * BINDER_DATA_FLAG_ALLOWED is set for the last SIM for which + * binder_data_allow() was called with non-zero role. No more + * than one SIM at a time has this flag set. + * + * BINDER_DATA_FLAG_MAX_SPEED is set for the last SIM for which + * binder_data_allow() was called with OFONO_SLOT_DATA_INTERNET. + * No more than one SIM at a time has this flag set. + * + * BINDER_DATA_FLAG_ON is set for the active SIM after setDataAllowed + * has successfully completed. + * + * Each binder_data object has a request queue which serializes + * setDataAllowed, setupDataCall and deactivateDataCall requests + * for the associated SIM. + * + * setDataAllowed isn't sent to the selected data SIM until all + * requests are finished for the other SIM. + * + * Power on is requested with binder_radio_power_on while data is allowed or + * any requests are pending for the SIM. Once data is disallowed and all + * requests are finished, power is released with binder_radio_power_off. + */ + +enum binder_data_io_event_id { + IO_EVENT_RESTRICTED_STATE_CHANGED, + IO_EVENT_DATA_CALL_LIST_CHANGED_1_0, + IO_EVENT_DATA_CALL_LIST_CHANGED_1_4, + IO_EVENT_DEATH, + IO_EVENT_COUNT +}; + +enum binder_data_settings_event_id { + SETTINGS_EVENT_IMSI_CHANGED, + SETTINGS_EVENT_PREF_MODE, + SETTINGS_EVENT_COUNT +}; + +struct binder_data_manager { + gint refcount; + GSList* data_list; + enum binder_data_manager_flags flags; + RadioConfig* rc; + RadioRequest* phone_cap_req; + GUtilInts* modem_ids; + enum ofono_radio_access_mode non_data_mode; +}; + +typedef struct binder_data_object { + BinderBase base; + BinderData pub; + RadioRequestGroup* g; + BinderRadio* radio; + BinderNetwork* network; + BinderDataManager* dm; + + BINDER_DATA_FLAGS flags; + RADIO_RESTRICTED_STATE restricted_state; + + BinderDataRequest* req_queue; + BinderDataRequest* pending_req; + + BinderDataOptions options; + gboolean use_data_profiles; + guint mms_data_profile_id; + guint slot; + char* log_prefix; + RadioRequest* query_req; + gulong io_event_id[IO_EVENT_COUNT]; + gulong settings_event_id[SETTINGS_EVENT_COUNT]; + GHashTable* grab; + gboolean downgraded_tech; /* Status 55 workaround */ +} BinderDataObject; + +typedef BinderBaseClass BinderDataObjectClass; +GType binder_data_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderDataObject, binder_data_object, BINDER_TYPE_BASE) +#define PARENT_CLASS binder_data_object_parent_class +#define THIS_TYPE binder_data_object_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, BinderDataObject) +BINDER_BASE_ASSERT_COUNT(BINDER_DATA_PROPERTY_COUNT); + +typedef enum binder_data_request_flags { + DATA_REQUEST_FLAG_COMPLETED = 0x1, + DATA_REQUEST_FLAG_CANCEL_WHEN_ALLOWED = 0x2, + DATA_REQUEST_FLAG_CANCEL_WHEN_DISALLOWED = 0x4 +} BINDER_DATA_REQUEST_FLAGS; + +struct binder_data_request { + BinderDataRequest* next; + BinderDataObject* data; + union binder_data_request_cb { + BinderDataCallSetupFunc setup; + BinderDataCallDeactivateFunc deact; + void (*ptr)(); + } cb; + void* arg; + gboolean (*submit)(BinderDataRequest* dr); + void (*cancel)(BinderDataRequest* dr); + void (*free)(BinderDataRequest* dr); + RadioRequest* radio_req; + BINDER_DATA_REQUEST_FLAGS flags; + const char* name; +}; + +typedef struct binder_data_request_setup { + BinderDataRequest req; + guint profile_id; + char* apn; + char* username; + char* password; + enum ofono_gprs_proto proto; + enum ofono_gprs_auth_method auth_method; + guint retry_count; + guint retry_delay_id; +} BinderDataRequestSetup; + +typedef struct binder_data_request_deact { + BinderDataRequest req; + int cid; +} BinderDataRequestDeact; + +typedef struct binder_data_request_allow_data { + BinderDataRequest req; + gboolean allow; +} BinderDataRequestAllowData; + +static struct ofono_debug_desc binder_data_debug_desc OFONO_DEBUG_ATTR = { + .file = __FILE__, + .flags = OFONO_DEBUG_FLAG_DEFAULT, +}; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderDataObject* binder_data_cast(BinderData* net) + { return net ? THIS(G_CAST(net, BinderDataObject, pub)) : NULL; } +static inline void binder_data_object_ref(BinderDataObject* data) + { g_object_ref(data); } +static inline void binder_data_object_unref(BinderDataObject* data) + { g_object_unref(data); } + +static void binder_data_manager_check_network_mode(BinderDataManager* dm); +static void binder_data_call_deact_cid(BinderDataObject* data, int cid); +static void binder_data_cancel_all_requests(BinderDataObject* data); +static void binder_data_power_update(BinderDataObject* data); + +static +guint8 +binder_data_modem_id( + BinderDataObject* data) +{ + if (data) { + guint count = 0; + BinderDataManager* dm = data->dm; + const int* ids = gutil_ints_get_data(dm->modem_ids, &count); + + if (data->slot < count) { + return (guint8)ids[data->slot]; + } + return (guint8)data->slot; + } + return 0; +} + +/*==========================================================================* + * Request builders + *==========================================================================*/ + +RadioRequest* +binder_data_deactivate_data_call_request_new( + RadioRequestGroup* group, + int cid, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + RadioClient* client = group->client; + const RADIO_INTERFACE iface = radio_client_interface(client); + RadioRequest* req; + GBinderWriter args; + + if (iface >= RADIO_INTERFACE_1_2) { + req = radio_request_new(client, + RADIO_REQ_DEACTIVATE_DATA_CALL_1_2, &args, + complete, destroy, user_data); + + /* + * deactivateDataCall_1_2(int32 serial, int32 cid, + * DataRequestReason reason); + */ + gbinder_writer_append_int32(&args, cid); + gbinder_writer_append_int32(&args, RADIO_DATA_REQUEST_REASON_NORMAL); + } else { + req = radio_request_new(client, + RADIO_REQ_DEACTIVATE_DATA_CALL, &args, + complete, destroy, user_data); + + /* + * deactivateDataCall(int32 serial, int32 cid, + * bool reasonRadioShutDown); + */ + gbinder_writer_append_int32(&args, cid); + gbinder_writer_append_bool(&args, FALSE); + } + + return req; +} + +RadioRequest* +binder_data_set_data_allowed_request_new( + RadioRequestGroup* group, + gboolean allow, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + GBinderWriter args; + RadioRequest* req = radio_request_new2(group, + RADIO_REQ_SET_DATA_ALLOWED, &args, + complete, destroy, user_data); + + /* setDataAllowed(int32 serial, bool allow) */ + gbinder_writer_append_bool(&args, allow); + return req; +} + +/*==========================================================================* + * BinderDataCall + *==========================================================================*/ + +static +BinderDataCall* +binder_data_call_new() +{ + return g_new0(struct binder_data_call, 1); +} + +/* extern */ +BinderDataCall* +binder_data_call_dup( + const BinderDataCall* call) +{ + if (call) { + BinderDataCall* dc = binder_data_call_new(); + + dc->cid = call->cid; + dc->status = call->status; + dc->active = call->active; + dc->prot = call->prot; + dc->retry_time = call->retry_time; + dc->mtu = call->mtu; + dc->ifname = g_strdup(call->ifname); + dc->dnses = g_strdupv(call->dnses); + dc->gateways = g_strdupv(call->gateways); + dc->addresses = g_strdupv(call->addresses); + dc->pcscf = g_strdupv(call->pcscf); + return dc; + } + return NULL; +} + +static +void +binder_data_call_destroy( + BinderDataCall* call) +{ + g_free(call->ifname); + g_strfreev(call->dnses); + g_strfreev(call->gateways); + g_strfreev(call->addresses); + g_strfreev(call->pcscf); +} + +/* extern */ +void +binder_data_call_free( + BinderDataCall* call) +{ + if (call) { + binder_data_call_destroy(call); + g_free(call); + } +} + +static +void +binder_data_call_list_free( + GSList* calls) +{ + g_slist_free_full(calls, (GDestroyNotify) binder_data_call_free); +} + +static +gint +binder_data_call_compare( + gconstpointer a, + gconstpointer b) +{ + const BinderDataCall* ca = a; + const BinderDataCall* cb = b; + + return ca->cid - cb->cid; +} + +static +BinderDataCall* +binder_data_call_new_1_0( + const RadioDataCall* dc) +{ + BinderDataCall* call = binder_data_call_new(); + + call->cid = dc->cid; + call->status = dc->status; + call->active = dc->active; + call->prot = binder_ofono_proto_from_proto_str(dc->type.data.str); + call->retry_time = dc->suggestedRetryTime; + call->mtu = dc->mtu; + call->ifname = g_strdup(dc->ifname.data.str); + call->dnses = g_strsplit(dc->dnses.data.str, " ", -1); + call->gateways = g_strsplit(dc->gateways.data.str, " ", -1); + call->addresses = g_strsplit(dc->addresses.data.str, " ", -1); + call->pcscf = g_strsplit(dc->pcscf.data.str, " ", -1); + + DBG("[status=%d,retry=%d,cid=%d,active=%d,type=%s,ifname=%s," + "mtu=%d,address=%s,dns=%s,gateways=%s,pcscf=%s]", + call->status, call->retry_time, call->cid, call->active, + dc->type.data.str, call->ifname, call->mtu, dc->addresses.data.str, + dc->dnses.data.str, dc->gateways.data.str, dc->pcscf.data.str); + return call; +} + +static +GSList* +binder_data_call_list_1_0( + const RadioDataCall* calls, + gsize n) +{ + if (n) { + gsize i; + GSList* l = NULL; + + DBG("num=%u", (guint) n); + for (i = 0; i < n; i++) { + l = g_slist_insert_sorted(l, binder_data_call_new_1_0(calls + i), + binder_data_call_compare); + } + return l; + } else { + DBG("no data calls"); + return NULL; + } +} + +static +BinderDataCall* +binder_data_call_new_1_4( + const RadioDataCall_1_4* dc) +{ + BinderDataCall* call = binder_data_call_new(); + + call->cid = dc->cid; + call->status = dc->cause; + call->active = dc->active; + call->prot = dc->type; + call->retry_time = dc->suggestedRetryTime; + call->mtu = dc->mtu; + call->ifname = g_strdup(dc->ifname.data.str); + call->dnses = binder_strv_from_hidl_string_vec(&dc->dnses); + call->gateways = binder_strv_from_hidl_string_vec(&dc->gateways); + call->addresses = binder_strv_from_hidl_string_vec(&dc->addresses); + call->pcscf = binder_strv_from_hidl_string_vec(&dc->pcscf); + + DBG("[status=%d,retry=%d,cid=%d,active=%d,type=%d,ifname=%s," + "mtu=%d,address=%s,dns=%s,gateways=%s,pcscf=%s]", + call->status, call->retry_time, call->cid, call->active, + dc->type, call->ifname, call->mtu, + binder_print_strv(call->addresses, " "), + binder_print_strv(call->dnses, " "), + binder_print_strv(call->gateways, " "), + binder_print_strv(call->pcscf, " ")); + return call; +} + +static +GSList* +binder_data_call_list_1_4( + const RadioDataCall_1_4* calls, + gsize n) +{ + if (n) { + gsize i; + GSList* l = NULL; + + DBG("num=%u", (guint) n); + for (i = 0; i < n; i++) { + l = g_slist_insert_sorted(l, binder_data_call_new_1_4(calls + i), + binder_data_call_compare); + } + return l; + } else { + DBG("no data calls"); + return NULL; + } +} + +static +gboolean +binder_data_call_equal( + const BinderDataCall* c1, + const BinderDataCall* c2) +{ + if (c1 == c2) { + return TRUE; + } else if (c1 && c2) { + return c1->cid == c2->cid && + c1->status == c2->status && + c1->active == c2->active && + c1->prot == c2->prot && + c1->retry_time == c2->retry_time && + c1->mtu == c2->mtu && + !g_strcmp0(c1->ifname, c2->ifname) && + gutil_strv_equal(c1->dnses, c2->dnses) && + gutil_strv_equal(c1->gateways, c2->gateways) && + gutil_strv_equal(c1->addresses, c2->addresses) && + gutil_strv_equal(c1->pcscf, c2->pcscf); + } else { + return FALSE; + } +} + +static +gboolean +binder_data_call_list_equal( + GSList* l1, + GSList* l2) +{ + while (l1 && l2) { + if (!binder_data_call_equal(l1->data, l2->data)) { + return FALSE; + } + l1 = l1->next; + l2 = l2->next; + } + return (!l1 && !l2); +} + +static +gboolean +binder_data_call_list_contains( + GSList* l, + const BinderDataCall* call) +{ + while (l) { + if (binder_data_call_equal(l->data, call)) { + return TRUE; + } + l = l->next; + } + return FALSE; +} + +/* extern */ +BinderDataCall* +binder_data_call_find( + GSList* l, + int cid) +{ + while (l) { + BinderDataCall* call = l->data; + + if (call->cid == cid) { + return call; + } + l = l->next; + } + return NULL; +} + +static +void +binder_data_set_calls( + BinderDataObject* self, + GSList* list) +{ + BinderBase* base = &self->base; + BinderData* data = &self->pub; + GHashTableIter it; + gpointer key; + + if (binder_data_call_list_equal(data->calls, list)) { + binder_data_call_list_free(list); + } else { + DBG("data calls changed"); + binder_data_call_list_free(data->calls); + data->calls = list; + binder_base_queue_property_change(base, BINDER_DATA_PROPERTY_CALLS); + } + + /* Clean up the grab table */ + g_hash_table_iter_init(&it, self->grab); + while (g_hash_table_iter_next(&it, &key, NULL)) { + const int cid = GPOINTER_TO_INT(key); + + if (!binder_data_call_find(data->calls, cid)) { + g_hash_table_iter_remove(&it); + } + } + + if (data->calls) { + GSList* l; + + /* Disconnect stray calls (one at a time) */ + for (l = data->calls; l; l = l->next) { + const BinderDataCall* dc = l->data; + + key = GINT_TO_POINTER(dc->cid); + if (!g_hash_table_contains(self->grab, key)) { + DBG_(self, "stray call %u", dc->cid); + binder_data_call_deact_cid(self, dc->cid); + break; + } + } + } + + binder_base_emit_queued_signals(base); +} + +static +gboolean +binder_data_is_allowed( + BinderDataObject* data) +{ + return G_LIKELY(data) && + (data->restricted_state & RADIO_RESTRICTED_STATE_PS_ALL) == 0 && + (data->flags & (BINDER_DATA_FLAG_ALLOWED | BINDER_DATA_FLAG_ON)) == + (BINDER_DATA_FLAG_ALLOWED | BINDER_DATA_FLAG_ON); +} + +static +void +binder_data_check_allowed( + BinderDataObject* data, + gboolean was_allowed) +{ + if (binder_data_is_allowed(data) != was_allowed) { + binder_base_queue_property_change(&data->base, + BINDER_DATA_PROPERTY_ALLOWED); + } +} + +static +void +binder_data_restricted_state_changed( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataObject* data = THIS(user_data); + GBinderReader reader; + gint32 state; + + /* restrictedStateChanged(RadioIndicationType, PhoneRestrictedState); */ + GASSERT(code == RADIO_IND_RESTRICTED_STATE_CHANGED); + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &state)) { + if (data->restricted_state != state) { + const gboolean was_allowed = binder_data_is_allowed(data); + + DBG_(data, "restricted state 0x%02x", state); + data->restricted_state = state; + binder_data_check_allowed(data, was_allowed); + } + } +} + +static +void +binder_data_call_list_changed( + BinderDataObject* data, + GSList* list) +{ + if (data->query_req) { + /* We have received change event before query has completed */ + DBG_(data, "cancelling query"); + radio_request_drop(data->query_req); + data->query_req = NULL; + } + + binder_data_set_calls(data, list); +} + +static +void +binder_data_call_list_changed_1_0( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataObject* data = THIS(user_data); + GBinderReader reader; + const RadioDataCall* calls; + gsize n = 0; + + /* dataCallListChanged(RadioIndicationType, vec); */ + GASSERT(code == RADIO_IND_DATA_CALL_LIST_CHANGED); + gbinder_reader_copy(&reader, args); + calls = gbinder_reader_read_hidl_type_vec(&reader, RadioDataCall, &n); + binder_data_call_list_changed(data, binder_data_call_list_1_0(calls, n)); +} + +static +void +binder_data_call_list_changed_1_4( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataObject* data = THIS(user_data); + GBinderReader reader; + const RadioDataCall_1_4* calls; + gsize n = 0; + + /* dataCallListChanged_1_4(RadioIndicationType,vec) */ + GASSERT(code == RADIO_IND_DATA_CALL_LIST_CHANGED_1_4); + gbinder_reader_copy(&reader, args); + calls = gbinder_reader_read_hidl_type_vec(&reader, RadioDataCall_1_4, &n); + binder_data_call_list_changed(data, binder_data_call_list_1_4(calls, n)); +} + +static +void binder_data_query_data_calls_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataObject* data = THIS(user_data); + + GASSERT(data->query_req == req); + radio_request_unref(data->query_req); + data->query_req = NULL; + + /* + * Only RADIO_ERROR_NONE and RADIO_ERROR_RADIO_NOT_AVAILABLE are expected, + * all other errors are filtered out by binder_data_poll_call_state_retry() + */ + if (status == RADIO_TX_STATUS_OK) { + GSList* list = NULL; + + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + gsize count = 0; + + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_GET_DATA_CALL_LIST) { + /* + * getDataCallListResponse(RadioResponseInfo, + * vec dcResponse); + */ + const RadioDataCall* calls = + gbinder_reader_read_hidl_type_vec(&reader, + RadioDataCall, &count); + + list = binder_data_call_list_1_0(calls, count); + } else if (resp == RADIO_RESP_GET_DATA_CALL_LIST_1_4) { + /* + * getDataCallListResponse_1_4(RadioResponseInfo, + * vec dcResponse); + */ + const RadioDataCall_1_4* calls = + gbinder_reader_read_hidl_type_vec(&reader, + RadioDataCall_1_4, &count); + + list = binder_data_call_list_1_4(calls, count); + } else { + ofono_error("Unexpected getDataCallList response %d", resp); + } + } else { + DBG_(data, "setupDataCall error %s", + binder_radio_error_string(error)); + } + + binder_data_set_calls(data, list); + } +} + +/*==========================================================================* + * BinderDataRequest + *==========================================================================*/ + +static +void +binder_data_request_free( + BinderDataRequest* dr) +{ + if (dr->free) { + dr->free(dr); + } else { + g_free(dr); + } +} + +/* extern */ +void +binder_data_request_detach( + BinderDataRequest* dr) +{ + if (dr) { + dr->cb.ptr = NULL; + dr->arg = NULL; + } +} + +static +gboolean +binder_data_request_call( + BinderDataRequest* dr, + RadioRequest* req) +{ + GASSERT(!dr->radio_req); + radio_request_drop(dr->radio_req); + if (radio_request_submit(req)) { + dr->radio_req = req; /* Keep the ref */ + return TRUE; + } else { + radio_request_drop(req); + dr->radio_req = NULL; + return FALSE; + } +} + +static +void +binder_data_request_cancel_io( + BinderDataRequest* dr) +{ + if (dr->radio_req) { + radio_request_drop(dr->radio_req); + dr->radio_req = NULL; + } +} + +static +void +binder_data_request_submit_next( + BinderDataObject* data) +{ + if (!data->pending_req) { + binder_data_power_update(data); + while (data->req_queue) { + BinderDataRequest* dr = data->req_queue; + + GASSERT(dr->data == data); + data->req_queue = dr->next; + dr->next = NULL; + + data->pending_req = dr; + if (dr->submit(dr)) { + DBG_(data, "submitted %s request %p", dr->name, dr); + break; + } else { + DBG_(data, "%s request %p done (or failed)", dr->name, dr); + data->pending_req = NULL; + binder_data_request_free(dr); + } + } + + if (!data->pending_req) { + binder_data_manager_check_data(data->dm); + } + } + binder_data_power_update(data); +} + +static +gboolean +binder_data_request_do_cancel( + BinderDataRequest* dr) +{ + if (dr && !(dr->flags & DATA_REQUEST_FLAG_COMPLETED)) { + BinderDataObject* data = dr->data; + + DBG_(data, "canceling %s request %p", dr->name, dr); + if (dr->cancel) { + dr->cancel(dr); + } + if (data->pending_req == dr) { + /* Request has been submitted already */ + data->pending_req = NULL; + } else if (data->req_queue == dr) { + /* It's the first one in the queue */ + data->req_queue = dr->next; + } else { + /* It's somewhere in the queue */ + BinderDataRequest* prev = data->req_queue; + + while (prev->next && prev->next != dr) { + prev = prev->next; + } + + /* Assert that it's there */ + GASSERT(prev); + if (prev) { + prev->next = dr->next; + } + } + + binder_data_request_free(dr); + return TRUE; + } else { + return FALSE; + } +} + +/* extern */ +void +binder_data_request_cancel( + BinderDataRequest* dr) +{ + if (dr) { + BinderDataObject* data = dr->data; + + if (binder_data_request_do_cancel(dr)) { + binder_data_request_submit_next(data); + } + } +} + +static +void +binder_data_request_completed( + BinderDataRequest* dr) +{ + GASSERT(!(dr->flags & DATA_REQUEST_FLAG_COMPLETED)); + dr->flags |= DATA_REQUEST_FLAG_COMPLETED; +} + +static +void +binder_data_request_finish( + BinderDataRequest* dr) +{ + BinderDataObject* data = dr->data; + + GASSERT(dr == data->pending_req); + GASSERT(!dr->next); + data->pending_req = NULL; + + binder_data_request_free(dr); + binder_data_request_submit_next(data); +} + +static +void +binder_data_request_queue( + BinderDataRequest* dr) +{ + BinderDataObject* data = dr->data; + + dr->next = NULL; + + if (!data->req_queue) { + data->req_queue = dr; + } else { + BinderDataRequest* last = data->req_queue; + + while (last->next) { + last = last->next; + } + last->next = dr; + } + + DBG_(data, "queued %s request %p", dr->name, dr); + binder_data_request_submit_next(data); +} + +/*==========================================================================* + * BinderDataRequestSetup + *==========================================================================*/ + +static +void +binder_data_call_setup_cancel( + BinderDataRequest* dr) +{ + BinderDataRequestSetup* setup = G_CAST(dr, BinderDataRequestSetup, req); + + binder_data_request_cancel_io(dr); + if (setup->retry_delay_id) { + g_source_remove(setup->retry_delay_id); + setup->retry_delay_id = 0; + } + if (dr->cb.setup) { + BinderDataCallSetupFunc cb = dr->cb.setup; + + dr->cb.setup = NULL; + cb(&dr->data->pub, RADIO_ERROR_CANCELLED, NULL, dr->arg); + } +} + +static +gboolean +binder_data_call_setup_retry( + gpointer user_data) +{ + BinderDataRequestSetup* setup = user_data; + BinderDataRequest* dr = &setup->req; + + GASSERT(setup->retry_delay_id); + setup->retry_delay_id = 0; + setup->retry_count++; + DBG("silent retry %u out of %u", setup->retry_count, + dr->data->options.data_call_retry_limit); + dr->submit(dr); + return G_SOURCE_REMOVE; +} + +static +gboolean +binder_data_call_retry( + BinderDataRequestSetup* setup) +{ + BinderDataRequest* dr = &setup->req; + const BinderDataOptions* options = &dr->data->options; + + if (setup->retry_count < options->data_call_retry_limit) { + binder_data_request_cancel_io(dr); + GASSERT(!setup->retry_delay_id); + if (!setup->retry_count) { + /* No delay first time */ + setup->retry_count++; + DBG("silent retry %u out of %u", setup->retry_count, + options->data_call_retry_limit); + dr->submit(dr); + } else { + const guint ms = options->data_call_retry_delay_ms; + + DBG("silent retry scheduled in %u ms", ms); + setup->retry_delay_id = g_timeout_add(ms, + binder_data_call_setup_retry, setup); + } + return TRUE; + } + return FALSE; +} + +static +void +binder_data_call_setup_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataRequestSetup* setup = user_data; + BinderDataRequest* dr = &setup->req; + BinderDataObject* self = dr->data; + BinderDataCall* call = NULL; + BinderData* data = &self->pub; + BinderBase* base = &self->base; + BinderDataCall* free_call = NULL; + + GASSERT(dr->radio_req == req); + radio_request_unref(dr->radio_req); + dr->radio_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_SETUP_DATA_CALL) { + /* + * setupDataCallResponse(RadioResponseInfo, + * SetupDataCallResult dcResponse); + */ + const RadioDataCall* dc = + gbinder_reader_read_hidl_struct(&reader, RadioDataCall); + + if (dc) { + call = binder_data_call_new_1_0(dc); + } + } else if (resp == RADIO_RESP_SETUP_DATA_CALL_1_4) { + /* + * setupDataCallResponse_1_4(RadioResponseInfo, + * SetupDataCallResult dcResponse); + */ + const RadioDataCall_1_4* dc = + gbinder_reader_read_hidl_struct(&reader, RadioDataCall_1_4); + + if (dc) { + call = binder_data_call_new_1_4(dc); + } + } else { + ofono_error("Unexpected setupDataCall response %d", resp); + } + } else { + DBG_(self, "setupDataCall error %s", + binder_radio_error_string(error)); + } + } + + if (call) { + BinderNetwork* network = self->network; + + switch (call->status) { + case RADIO_DATA_CALL_FAIL_UNSPECIFIED: + /* + * First time we retry immediately and if that doesn't work, + * then after certain delay. + */ + if (binder_data_call_retry(setup)) { + binder_data_call_free(call); + return; + } + break; + case RADIO_DATA_CALL_FAIL_MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED: + /* + * With some networks we sometimes start getting error 55 + * (Multiple PDN connections for a given APN not allowed) + * when trying to setup an LTE data call and this error + * doesn't go away until we successfully establish a data + * call over 3G. Then we can switch back to LTE. + */ + if (network->data.access_tech == OFONO_ACCESS_TECHNOLOGY_EUTRAN && + !self->downgraded_tech) { + DBG("downgrading preferred technology"); + self->downgraded_tech = TRUE; + binder_data_manager_check_network_mode(self->dm); + /* And let this call fail */ + } + break; + default: + break; + } + } + + binder_data_request_completed(dr); + free_call = call; + + if (call && call->status == RADIO_DATA_CALL_FAIL_NONE) { + if (self->downgraded_tech) { + DBG("done with status 55 workaround"); + self->downgraded_tech = FALSE; + binder_data_manager_check_network_mode(self->dm); + } + + if (!binder_data_call_list_contains(data->calls, call)) { + data->calls = g_slist_insert_sorted(data->calls, call, + binder_data_call_compare); + DBG_(self, "new data call"); + binder_base_queue_property_change(base, BINDER_DATA_PROPERTY_CALLS); + free_call = NULL; + } + } + + if (dr->cb.setup) { + dr->cb.setup(data, error, call, dr->arg); + } + + binder_data_request_finish(dr); + binder_data_call_free(free_call); + binder_base_emit_queued_signals(base); +} + +static +gboolean +binder_data_call_setup_submit( + BinderDataRequest* dr) +{ + BinderDataRequestSetup* setup = G_CAST(dr, BinderDataRequestSetup, req); + BinderDataObject* data = dr->data; + BinderNetwork* network = data->network; + RadioRequestGroup* g = data->g; + const RADIO_INTERFACE iface = radio_client_interface(g->client); + RadioRequest* req; + GBinderWriter writer; + const char* nothing = NULL; + const RADIO_TECH tech = (setup->profile_id == RADIO_DATA_PROFILE_IMS) ? + RADIO_TECH_LTE : network->data.radio_tech; + const RADIO_APN_AUTH_TYPE auth =(setup->username && setup->username[0]) ? + binder_radio_auth_from_ofono_method(setup->auth_method) : + RADIO_APN_AUTH_NONE; + + if (iface >= RADIO_INTERFACE_1_4) { + RadioDataProfile_1_4* dp; + guint parent; + + req = radio_request_new2(g, RADIO_REQ_SETUP_DATA_CALL_1_4, + &writer, binder_data_call_setup_cb, NULL, setup); + + /* + * setupDataCall_1_4(int32_t serial, AccessNetwork accessNetwork, + * DataProfileInfo dataProfileInfo, bool roamingAllowed, + * DataRequestReason reason, vec addresses, + * vec dnses); + */ + dp = gbinder_writer_new0(&writer, RadioDataProfile_1_4); + dp->profileId = setup->profile_id; + binder_copy_hidl_string(&writer, &dp->apn, setup->apn); + dp->protocol = dp->roamingProtocol = + binder_proto_from_ofono_proto(setup->proto); + dp->authType = auth; + binder_copy_hidl_string(&writer, &dp->user, setup->username); + binder_copy_hidl_string(&writer, &dp->password, setup->password); + dp->enabled = TRUE; + + gbinder_writer_append_int32(&writer, + binder_radio_access_network_for_tech(tech)); /* accessNetwork */ + /* dataProfileInfo */ + parent = gbinder_writer_append_buffer_object(&writer, dp, sizeof(*dp)); + binder_append_hidl_string_data(&writer, dp, apn, parent); + binder_append_hidl_string_data(&writer, dp, user, parent); + binder_append_hidl_string_data(&writer, dp, password, parent); + gbinder_writer_append_bool(&writer, TRUE); /* roamingAllowed */ + gbinder_writer_append_int32(&writer, + RADIO_DATA_REQUEST_REASON_NORMAL); /* reason */ + gbinder_writer_append_hidl_string_vec(&writer, ¬hing, -1); + gbinder_writer_append_hidl_string_vec(&writer, ¬hing, -1); + } else { + RadioDataProfile* dp; + const char* proto_str = binder_proto_str_from_ofono_proto(setup->proto); + guint parent; + + req = radio_request_new2(g, (iface >= RADIO_INTERFACE_1_2) ? + RADIO_REQ_SETUP_DATA_CALL_1_2 : RADIO_REQ_SETUP_DATA_CALL, + &writer, binder_data_call_setup_cb, NULL, setup); + + dp = gbinder_writer_new0(&writer, RadioDataProfile); + dp->profileId = setup->profile_id; + binder_copy_hidl_string(&writer, &dp->apn, setup->apn); + binder_copy_hidl_string(&writer, &dp->protocol, proto_str); + dp->roamingProtocol = dp->protocol; + dp->authType = auth; + binder_copy_hidl_string(&writer, &dp->user, setup->username); + binder_copy_hidl_string(&writer, &dp->password, setup->password); + dp->enabled = TRUE; + dp->supportedApnTypesBitmap = + binder_radio_apn_types_for_profile(setup->profile_id); + binder_copy_hidl_string(&writer, &dp->mvnoMatchData, NULL); + + if (iface >= RADIO_INTERFACE_1_2) { + /* + * setupDataCall_1_2(int32_t serial, AccessNetwork accessNetwork, + * DataProfileInfo dataProfileInfo, bool modemCognitive, + * bool roamingAllowed, bool isRoaming, DataRequestReason reason, + * vec addresses, vec dnses); + */ + gbinder_writer_append_int32(&writer, + binder_radio_access_network_for_tech(tech)); /* accessNetwork */ + } else { + /* + * setupDataCall(int32 serial, RadioTechnology radioTechnology, + * DataProfileInfo dataProfileInfo, bool modemCognitive, + * bool roamingAllowed, bool isRoaming); + */ + gbinder_writer_append_int32(&writer, tech); /* radioTechnology */ + } + + /* dataProfileInfo */ + parent = gbinder_writer_append_buffer_object(&writer, dp, sizeof(*dp)); + binder_append_hidl_string_data(&writer, dp, apn, parent); + binder_append_hidl_string_data(&writer, dp, protocol, parent); + binder_append_hidl_string_data(&writer, dp, roamingProtocol, parent); + binder_append_hidl_string_data(&writer, dp, user, parent); + binder_append_hidl_string_data(&writer, dp, password, parent); + binder_append_hidl_string_data(&writer, dp, mvnoMatchData, parent); + gbinder_writer_append_bool(&writer, FALSE); /* modemCognitive */ + gbinder_writer_append_bool(&writer, TRUE); /* roamingAllowed */ + gbinder_writer_append_bool(&writer, FALSE); /* isRoaming */ + + if (iface >= RADIO_INTERFACE_1_2) { + gbinder_writer_append_int32(&writer, + RADIO_DATA_REQUEST_REASON_NORMAL); /* reason */ + gbinder_writer_append_hidl_string_vec(&writer, ¬hing, -1); + gbinder_writer_append_hidl_string_vec(&writer, ¬hing, -1); + } + } + + return binder_data_request_call(dr, req); +} + +static +void +binder_data_call_setup_free( + BinderDataRequest* dr) +{ + BinderDataRequestSetup* setup = G_CAST(dr, BinderDataRequestSetup, req); + + g_free(setup->apn); + g_free(setup->username); + g_free(setup->password); + g_free(setup); +} + +static +BinderDataRequest* +binder_data_call_setup_new( + BinderDataObject* data, + const struct ofono_gprs_primary_context* ctx, + enum ofono_gprs_context_type context_type, + BinderDataCallSetupFunc cb, + void* arg) +{ + BinderDataRequestSetup* setup = g_new0(BinderDataRequestSetup, 1); + BinderDataRequest* dr = &setup->req; + + setup->profile_id = RADIO_DATA_PROFILE_DEFAULT; + if (data->use_data_profiles) { + switch (context_type) { + case OFONO_GPRS_CONTEXT_TYPE_MMS: + setup->profile_id = data->mms_data_profile_id; + break; + case OFONO_GPRS_CONTEXT_TYPE_IMS: + setup->profile_id = RADIO_DATA_PROFILE_IMS; + break; + case OFONO_GPRS_CONTEXT_TYPE_ANY: + case OFONO_GPRS_CONTEXT_TYPE_INTERNET: + case OFONO_GPRS_CONTEXT_TYPE_WAP: + break; + } + } + + setup->apn = g_strdup(ctx->apn); + setup->username = g_strdup(ctx->username); + setup->password = g_strdup(ctx->password); + setup->proto = ctx->proto; + setup->auth_method = ctx->auth_method; + + dr->name = "CALL_SETUP"; + dr->cb.setup = cb; + dr->arg = arg; + dr->data = data; + dr->submit = binder_data_call_setup_submit; + dr->cancel = binder_data_call_setup_cancel; + dr->free = binder_data_call_setup_free; + dr->flags = DATA_REQUEST_FLAG_CANCEL_WHEN_DISALLOWED; + return dr; +} + +/*==========================================================================* + * BinderDataRequestDeact + *==========================================================================*/ + +static +void +binder_data_call_deact_cancel( + BinderDataRequest* dr) +{ + binder_data_request_cancel_io(dr); + if (dr->cb.deact) { + BinderDataCallDeactivateFunc cb = dr->cb.deact; + + dr->cb.deact = NULL; + cb(&dr->data->pub, RADIO_ERROR_CANCELLED, dr->arg); + } +} + +static +void +binder_data_call_deact_cb( + RadioRequest* ioreq, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataRequestDeact* deact = user_data; + BinderDataRequest* dr = &deact->req; + BinderDataObject* self = dr->data; + BinderData* data = &self->pub; + BinderBase* base = &self->base; + BinderDataCall* call = NULL; + + GASSERT(dr->radio_req == ioreq); + radio_request_unref(dr->radio_req); + dr->radio_req = NULL; + + binder_data_request_completed(dr); + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_RESP_DEACTIVATE_DATA_CALL) { + /* + * If RADIO_REQ_DEACTIVATE_DATA_CALL succeeds, some adaptations + * don't send dataCallListChanged even though the list of calls + * has changed. Update the list of calls to account for that. + */ + call = binder_data_call_find(data->calls, deact->cid); + if (call) { + DBG_(self, "removing call %d", deact->cid); + data->calls = g_slist_remove(data->calls, call); + } + } else { + ofono_error("Unexpected deactivateDataCall response %d", resp); + } + } else { + DBG_(self, "deactivateDataCall error %s", + binder_radio_error_string(error)); + } + } + + if (call) { + binder_data_call_free(call); + binder_base_emit_property_change(base, BINDER_DATA_PROPERTY_CALLS); + } else { + /* Something seems to be slightly broken, request the current state */ + binder_data_poll_call_state(data); + } + + if (dr->cb.deact) { + dr->cb.deact(data, error, dr->arg); + } + + binder_data_request_finish(dr); +} + +static +gboolean +binder_data_call_deact_submit( + BinderDataRequest* dr) +{ + BinderDataRequestDeact* deact = G_CAST(dr, BinderDataRequestDeact, req); + BinderDataObject* data = dr->data; + + return binder_data_request_call(dr, + binder_data_deactivate_data_call_request_new(data->g, deact->cid, + binder_data_call_deact_cb, NULL, deact)); +} + +static +BinderDataRequest* +binder_data_call_deact_new( + BinderDataObject* data, + int cid, + BinderDataCallDeactivateFunc cb, + void* arg) +{ + BinderDataRequestDeact* deact = g_new0(BinderDataRequestDeact, 1); + BinderDataRequest* dr = &deact->req; + + deact->cid = cid; + dr->cb.deact = cb; + dr->arg = arg; + dr->data = data; + dr->submit = binder_data_call_deact_submit; + dr->cancel = binder_data_call_deact_cancel; + dr->name = "DEACTIVATE"; + return dr; +} + +static +void +binder_data_call_deact_cid( + BinderDataObject* data, + int cid) +{ + binder_data_request_queue(binder_data_call_deact_new(data, cid, + NULL, NULL)); +} + +/*==========================================================================* + * setPreferredDataModem (IRadioConfig >= 1.1) + *==========================================================================*/ + +static +void +binder_data_set_preferred_data_modem_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_CONFIG_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataRequest* dr = user_data; + BinderDataObject* data = dr->data; + + GASSERT(dr->radio_req == req); + radio_request_unref(dr->radio_req); + dr->radio_req = NULL; + + binder_data_request_completed(dr); + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_CONFIG_RESP_SET_PREFERRED_DATA_MODEM) { + const gboolean was_allowed = binder_data_is_allowed(data); + + data->flags |= BINDER_DATA_FLAG_ON; + DBG_(data, "data on"); + binder_data_check_allowed(data, was_allowed); + } else { + ofono_error("Unexpected setPreferredDataModem response %d", + resp); + } + } else { + DBG("setPreferredDataModem error %s", + binder_radio_error_string(error)); + } + } + + binder_data_request_finish(dr); +} + +static +gboolean +binder_data_set_preferred_data_modem_submit( + BinderDataRequest* dr) +{ + GBinderWriter args; + BinderDataObject* data = dr->data; + BinderDataManager* dm = data->dm; + RadioRequest* req = radio_config_request_new(dm->rc, + RADIO_CONFIG_REQ_SET_PREFERRED_DATA_MODEM, &args, + binder_data_set_preferred_data_modem_cb, NULL, dr); + + /* setPreferredDataModem(serial, uint8 modemId) */ + if (req) { + const guint8 modem_id = binder_data_modem_id(data); + + DBG("setPreferredDataModem(%u)", modem_id); + gbinder_writer_append_int8(&args, modem_id); + radio_request_set_retry(req, BINDER_RETRY_SECS*1000, -1); + return binder_data_request_call(dr, req); + } + return FALSE; +} + +static +BinderDataRequest* +binder_data_set_preferred_data_modem_new( + BinderDataObject* data) +{ + BinderDataRequest* dr = g_new0(BinderDataRequest, 1); + + dr->name = "SET_PREFERRED_DATA_MODEM"; + dr->data = data; + dr->submit = binder_data_set_preferred_data_modem_submit; + dr->cancel = binder_data_request_cancel_io; + dr->flags = DATA_REQUEST_FLAG_CANCEL_WHEN_DISALLOWED; + return dr; +} + +/*==========================================================================* + * BinderDataRequestAllowData + *==========================================================================*/ + +static +void +binder_data_allow_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataRequestAllowData* ad = user_data; + BinderDataRequest* dr = &ad->req; + BinderDataObject* data = dr->data; + + GASSERT(dr->radio_req == req); + radio_request_unref(dr->radio_req); + dr->radio_req = NULL; + + binder_data_request_completed(dr); + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_RESP_SET_DATA_ALLOWED) { + const gboolean was_allowed = binder_data_is_allowed(data); + + if (ad->allow) { + data->flags |= BINDER_DATA_FLAG_ON; + DBG_(data, "data on"); + } else { + data->flags &= ~BINDER_DATA_FLAG_ON; + DBG_(data, "data off"); + } + + binder_data_check_allowed(data, was_allowed); + } else { + ofono_error("Unexpected setDataAllowed response %d", resp); + } + } else { + DBG_(data, "setDataAllowed error %s", + binder_radio_error_string(error)); + } + } + + binder_data_request_finish(dr); +} + +static +gboolean +binder_data_allow_submit( + BinderDataRequest* dr) +{ + BinderDataRequestAllowData* ad = + G_CAST(dr, BinderDataRequestAllowData, req); + BinderDataObject* data = dr->data; + RadioRequest* req = binder_data_set_data_allowed_request_new(data->g, + ad->allow, binder_data_allow_cb, NULL, ad); + + radio_request_set_retry(req, BINDER_RETRY_SECS*1000, -1); + radio_request_set_blocking(req, TRUE); + return binder_data_request_call(dr, req); +} + +static +BinderDataRequest* +binder_data_allow_new( + BinderDataObject* data, + gboolean allow) +{ + BinderDataRequestAllowData* ad = g_new0(BinderDataRequestAllowData, 1); + BinderDataRequest* dr = &ad->req; + + dr->name = "ALLOW_DATA"; + dr->data = data; + dr->submit = binder_data_allow_submit; + dr->cancel = binder_data_request_cancel_io; + dr->flags = DATA_REQUEST_FLAG_CANCEL_WHEN_DISALLOWED; + ad->allow = allow; + return dr; +} + +static +gboolean +binder_data_allow_can_submit( + BinderDataObject* data) +{ + if (data) { + switch (data->options.allow_data) { + case BINDER_ALLOW_DATA_ENABLED: + return TRUE; + case BINDER_ALLOW_DATA_DISABLED: + break; + } + } + return FALSE; +} + +static +gboolean +binder_data_allow_submit_request( + BinderDataObject* data, + gboolean allow) +{ + if (binder_data_manager_need_set_data_allowed(data->dm)) { + if (binder_data_allow_can_submit(data)) { + binder_data_request_queue(binder_data_allow_new(data, allow)); + return TRUE; + } + } else if (allow) { + /* IRadioConfig >= 1.1 */ + binder_data_request_queue + (binder_data_set_preferred_data_modem_new(data)); + } + return FALSE; +} + +/*==========================================================================* + * BinderDataObject + *==========================================================================*/ + +static +enum ofono_radio_access_mode +binder_data_max_allowed_modes( + BinderDataObject* data) +{ + return data->downgraded_tech ? + OFONO_RADIO_ACCESS_UMTS_MASK : + OFONO_RADIO_ACCESS_MODE_ALL; +} + +gulong +binder_data_add_property_handler( + BinderData* data, + BINDER_DATA_PROPERTY property, + BinderDataPropertyFunc callback, + void* user_data) +{ + BinderDataObject* self = binder_data_cast(data); + + return G_LIKELY(self) ? binder_base_add_property_handler(&self->base, + property, G_CALLBACK(callback), user_data) : 0; +} + +void +binder_data_remove_handler( + BinderData* data, + gulong id) +{ + if (G_LIKELY(id)) { + BinderDataObject* self = binder_data_cast(data); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +static +void +binder_data_imsi_changed( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + BinderDataObject* data = THIS(user_data); + + GASSERT(property == BINDER_SIM_SETTINGS_PROPERTY_IMSI); + if (!settings->imsi) { + /* + * Most likely, SIM removal. In any case, no data requests + * make sense when IMSI is unavailable. + */ + binder_data_cancel_all_requests(data); + } + binder_data_manager_check_network_mode(data->dm); +} + +static +void +binder_data_pref_changed( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + GASSERT(property == BINDER_SIM_SETTINGS_PROPERTY_PREF); + binder_data_manager_check_network_mode(THIS(user_data)->dm); +} + +static +void binder_data_client_dead_cb( + RadioClient* client, + void* user_data) +{ + BinderDataObject* data = THIS(user_data); + + DBG_(data, "disconnected"); + data->flags = BINDER_DATA_FLAG_NONE; + data->restricted_state = 0; + binder_data_cancel_all_requests(data); +} + +static +gint +binder_data_compare_cb( + gconstpointer a, + gconstpointer b) +{ + const BinderDataObject* d1 = THIS(a); + const BinderDataObject* d2 = THIS(b); + + return d1->slot < d2->slot ? (-1) : d1->slot > d2->slot ? 1 : 0; +} + +BinderData* +binder_data_new( + BinderDataManager* dm, + RadioClient* client, + const char* name, + BinderRadio* radio, + BinderNetwork* network, + const BinderDataOptions* options, + const BinderSlotConfig* config) +{ + GASSERT(dm); + if (G_LIKELY(dm)) { + BinderDataObject* self = g_object_new(THIS_TYPE, NULL); + BinderData* data = &self->pub; + BinderSimSettings* settings = network->settings; + + self->options = *options; + self->log_prefix = binder_dup_prefix(name); + self->use_data_profiles = config->use_data_profiles; + self->mms_data_profile_id = config->mms_data_profile_id; + self->slot = config->slot; + self->g = radio_request_group_new(client); /* Keeps ref to client */ + self->dm = binder_data_manager_ref(dm); + self->radio = binder_radio_ref(radio); + self->network = binder_network_ref(network); + + self->io_event_id[IO_EVENT_DATA_CALL_LIST_CHANGED_1_0] = + radio_client_add_indication_handler(client, + RADIO_IND_DATA_CALL_LIST_CHANGED, + binder_data_call_list_changed_1_0, self); + self->io_event_id[IO_EVENT_DATA_CALL_LIST_CHANGED_1_4] = + radio_client_add_indication_handler(client, + RADIO_IND_DATA_CALL_LIST_CHANGED_1_4, + binder_data_call_list_changed_1_4, self); + self->io_event_id[IO_EVENT_RESTRICTED_STATE_CHANGED] = + radio_client_add_indication_handler(client, + RADIO_IND_RESTRICTED_STATE_CHANGED, + binder_data_restricted_state_changed, self); + self->io_event_id[IO_EVENT_DEATH] = + radio_client_add_death_handler(client, + binder_data_client_dead_cb, self); + + self->settings_event_id[SETTINGS_EVENT_IMSI_CHANGED] = + binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_IMSI, + binder_data_imsi_changed, self); + self->settings_event_id[SETTINGS_EVENT_PREF_MODE] = + binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_PREF, + binder_data_pref_changed, self); + + /* Request the current state */ + binder_data_poll_call_state(data); + + /* Order data contexts according to slot numbers */ + dm->data_list = g_slist_insert_sorted(dm->data_list, self, + binder_data_compare_cb); + binder_data_manager_check_network_mode(dm); + return data; + } + return NULL; +} + +static +gboolean +binder_data_poll_call_state_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + switch (error) { + case RADIO_ERROR_NONE: + case RADIO_ERROR_RADIO_NOT_AVAILABLE: + return FALSE; + default: + return TRUE; + } +} + +void +binder_data_poll_call_state( + BinderData* data) +{ + BinderDataObject* self = binder_data_cast(data); + + if (G_LIKELY(self) && !self->query_req) { + RadioRequest* ioreq = radio_request_new2(self->g, + RADIO_REQ_GET_DATA_CALL_LIST, NULL, + binder_data_query_data_calls_cb, NULL, self); + + radio_request_set_retry(ioreq, BINDER_RETRY_SECS*1000, -1); + radio_request_set_retry_func(ioreq, binder_data_poll_call_state_retry); + self->query_req = ioreq; + if (!radio_request_submit(self->query_req)) { + radio_request_unref(self->query_req); + self->query_req = NULL; + } + } +} + +BinderData* +binder_data_ref( + BinderData* data) +{ + BinderDataObject* self = binder_data_cast(data); + + if (G_LIKELY(self)) { + binder_data_object_ref(self); + } + return data; +} + +void +binder_data_unref( + BinderData* data) +{ + BinderDataObject* self = binder_data_cast(data); + + if (G_LIKELY(self)) { + binder_data_object_unref(self); + } +} + +gboolean +binder_data_allowed( + BinderData* data) +{ + return binder_data_is_allowed(binder_data_cast(data)); +} + +static +void +binder_data_deactivate_all( + BinderDataObject* self) +{ + GSList* l; + + for (l = self->pub.calls; l; l = l->next) { + BinderDataCall* call = l->data; + + if (call->status == RADIO_DATA_CALL_FAIL_NONE) { + DBG_(self, "deactivating call %u", call->cid); + binder_data_call_deact_cid(self, call->cid); + } + } +} + +static +void +binder_data_power_update( + BinderDataObject* self) +{ + if (self->pending_req || self->req_queue) { + binder_radio_power_on(self->radio, self); + } else { + binder_radio_power_off(self->radio, self); + } +} + +static +void +binder_data_cancel_requests( + BinderDataObject* self, + BINDER_DATA_REQUEST_FLAGS flags) +{ + BinderDataRequest* dr = self->req_queue; + + while (dr) { + BinderDataRequest* next = dr->next; + + GASSERT(dr->data == self); + if (dr->flags & flags) { + binder_data_request_do_cancel(dr); + } + dr = next; + } + + if (self->pending_req && (self->pending_req->flags & flags)) { + binder_data_request_cancel(self->pending_req); + } +} + +static +void +binder_data_cancel_all_requests( + BinderDataObject* self) +{ + BinderDataRequest* dr = self->req_queue; + + binder_data_request_do_cancel(self->pending_req); + while (dr) { + BinderDataRequest* next = dr->next; + + binder_data_request_do_cancel(dr); + dr = next; + } +} + +static +void +binder_data_disallow( + BinderDataObject* self) +{ + const gboolean was_allowed = binder_data_is_allowed(self); + + DBG_(self, "disallowed"); + GASSERT(self->flags & BINDER_DATA_FLAG_ALLOWED); + self->flags &= ~BINDER_DATA_FLAG_ALLOWED; + + /* + * Cancel all requests that can be canceled. + */ + binder_data_cancel_requests(self, DATA_REQUEST_FLAG_CANCEL_WHEN_DISALLOWED); + + /* + * Then deactivate active contexts (Hmm... what if deactivate + * requests are already pending? That's quite unlikely though) + */ + binder_data_deactivate_all(self); + + /* Tell the modem that the data is now disabled */ + if (!binder_data_allow_submit_request(self, FALSE)) { + self->flags &= ~BINDER_DATA_FLAG_ON; + GASSERT(!binder_data_is_allowed(self)); + DBG_(self, "data off"); + binder_data_power_update(self); + } + + binder_data_check_allowed(self, was_allowed); +} + +static +void +binder_data_max_speed_cb( + gpointer data, + gpointer max_speed) +{ + if (data != max_speed) { + THIS(data)->flags &= ~BINDER_DATA_FLAG_MAX_SPEED; + } +} + +static +void +binder_data_disallow_cb( + gpointer data, + gpointer allowed) +{ + if (data != allowed) { + BinderDataObject* obj = THIS(data); + + if (obj->flags & BINDER_DATA_FLAG_ALLOWED) { + binder_data_disallow(obj); + } + } +} + +void +binder_data_allow( + BinderData* data, + enum ofono_slot_data_role role) +{ + BinderDataObject* self = binder_data_cast(data); + if (G_LIKELY(self)) { + BinderDataManager* dm = self->dm; + + DBG_(self, "%s", (role == OFONO_SLOT_DATA_NONE) ? "none" : + (role == OFONO_SLOT_DATA_MMS) ? "mms" : "internet"); + + if (role != OFONO_SLOT_DATA_NONE) { + gboolean speed_changed = FALSE; + + if (role == OFONO_SLOT_DATA_INTERNET && + !(self->flags & BINDER_DATA_FLAG_MAX_SPEED)) { + self->flags |= BINDER_DATA_FLAG_MAX_SPEED; + speed_changed = TRUE; + + /* Clear BINDER_DATA_FLAG_MAX_SPEED for all other slots */ + g_slist_foreach(dm->data_list, binder_data_max_speed_cb, self); + } + + if (self->flags & BINDER_DATA_FLAG_ALLOWED) { + /* + * Data is already allowed for this slot, just adjust + * the speed if necessary. + */ + if (speed_changed) { + binder_data_manager_check_network_mode(dm); + } + } else { + self->flags |= BINDER_DATA_FLAG_ALLOWED; + self->flags &= ~BINDER_DATA_FLAG_ON; + + /* Clear BINDER_DATA_FLAG_ALLOWED for all other slots */ + g_slist_foreach(dm->data_list, binder_data_disallow_cb, self); + binder_data_cancel_requests(self, + DATA_REQUEST_FLAG_CANCEL_WHEN_ALLOWED); + binder_data_manager_check_data(dm); + binder_data_power_update(self); + } + } else if (self->flags & BINDER_DATA_FLAG_ALLOWED) { + binder_data_disallow(self); + binder_data_manager_check_data(dm); + } + } +} + +BinderDataRequest* +binder_data_call_setup( + BinderData* data, + const struct ofono_gprs_primary_context* ctx, + enum ofono_gprs_context_type type, + BinderDataCallSetupFunc cb, + void* user_data) +{ + BinderDataObject* self = binder_data_cast(data); + BinderDataRequest* dr = NULL; + + if (self) { + dr = binder_data_call_setup_new(self, ctx, type, cb, user_data); + binder_data_request_queue(dr); + } + return dr; +} + +BinderDataRequest* +binder_data_call_deactivate( + BinderData* data, + int cid, + BinderDataCallDeactivateFunc cb, + void* user_data) +{ + BinderDataObject* self = binder_data_cast(data); + BinderDataRequest* dr = NULL; + + if (self) { + dr = binder_data_call_deact_new(self, cid, cb, user_data); + binder_data_request_queue(dr); + } + return dr; +} + +gboolean +binder_data_call_grab( + BinderData* data, + int cid, + void* cookie) +{ + BinderDataObject* self = binder_data_cast(data); + + if (self && cookie && binder_data_call_find(data->calls, cid)) { + gpointer key = GINT_TO_POINTER(cid); + void* prev = g_hash_table_lookup(self->grab, key); + + if (!prev) { + g_hash_table_insert(self->grab, key, cookie); + return TRUE; + } else { + return (prev == cookie); + } + } + return FALSE; +} + +void +binder_data_call_release( + BinderData* data, + int cid, + void* cookie) +{ + BinderDataObject* self = binder_data_cast(data); + + if (self && cookie) { + g_hash_table_remove(self->grab, GUINT_TO_POINTER(cid)); + } +} + +static +void +binder_data_object_init( + BinderDataObject* self) +{ + self->grab = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +static +void +binder_data_object_finalize( + GObject* object) +{ + BinderDataObject* self = THIS(object); + BinderData* data = &self->pub; + BinderNetwork* network = self->network; + BinderSimSettings* settings = network->settings; + BinderDataManager* dm = self->dm; + + binder_data_cancel_all_requests(self); + dm->data_list = g_slist_remove(dm->data_list, self); + binder_data_manager_check_data(dm); + + radio_client_remove_all_handlers(self->g->client, self->io_event_id); + radio_request_drop(self->query_req); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + binder_radio_power_off(self->radio, self); + binder_radio_unref(self->radio); + + binder_sim_settings_remove_all_handlers(settings, self->settings_event_id); + binder_network_unref(self->network); + binder_data_manager_unref(self->dm); + binder_data_call_list_free(data->calls); + + g_hash_table_destroy(self->grab); + g_free(self->log_prefix); + + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_data_object_class_init( + BinderDataObjectClass* klass) +{ + + G_OBJECT_CLASS(klass)->finalize = binder_data_object_finalize; +} + +/*==========================================================================* + * BinderDataManager + *==========================================================================*/ + +static +void +binder_data_manager_get_phone_capability_done( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_CONFIG_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderDataManager* dm = user_data; + + GASSERT(dm->phone_cap_req == req); + radio_request_drop(dm->phone_cap_req); + dm->phone_cap_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_CONFIG_RESP_GET_PHONE_CAPABILITY) { + if (error == RADIO_ERROR_NONE) { + /* + * getPhoneCapabilityResponse(RadioResponseInfo, + * PhoneCapability phoneCapability); + */ + const RadioPhoneCapability* pcap = + binder_read_hidl_struct(args, RadioPhoneCapability); + + GASSERT(pcap); + if (pcap) { + const GBinderHidlVec* modems = &pcap->logicalModemList; + const RadioModemInfo* modem = modems->data.ptr; + const guint n = modems->count; + GUtilIntArray* modem_ids = gutil_int_array_sized_new(n); + guint i; + + for (i = 0; i < n; i++) { + gutil_int_array_append(modem_ids, modem[i].modemId); + } + dm->modem_ids = gutil_int_array_free_to_ints(modem_ids); + + if (binder_data_debug_desc.flags & OFONO_DEBUG_FLAG_PRINT) { + GString* str = g_string_new(""); + + for (i = 0; i < n; i++) { + if (i > 0) { + g_string_append_c(str, ','); + } + g_string_append_printf(str, "%u", modem[i].modemId); + } + DBG("maxActiveData=%u, maxActiveInternetData=%u, " + "logicalModemList=[%s]", pcap->maxActiveData, + pcap->maxActiveInternetData, str->str); + g_string_free(str, TRUE); + } + } + } else { + DBG("%s error %s", radio_config_resp_name(dm->rc, resp), + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getPhoneCapability response %d", resp); + } + } else { + DBG("getPhoneCapability error %s", binder_radio_error_string(error)); + } +} + +BinderDataManager* +binder_data_manager_new( + RadioConfig* rc, + BINDER_DATA_MANAGER_FLAGS flags, + enum ofono_radio_access_mode non_data_mode) +{ + BinderDataManager* dm = g_new0(BinderDataManager, 1); + + g_atomic_int_set(&dm->refcount, 1); + dm->flags = flags; + dm->non_data_mode = ofono_radio_access_max_mode(non_data_mode); + dm->rc = radio_config_ref(rc); + if (radio_config_interface(dm->rc) >= RADIO_CONFIG_INTERFACE_1_1) { + RadioRequest* req = radio_config_request_new(rc, + RADIO_CONFIG_REQ_GET_PHONE_CAPABILITY, NULL, + binder_data_manager_get_phone_capability_done, NULL, dm); + + if (radio_request_submit(req)) { + dm->phone_cap_req = req; /* Keep the ref */ + } else { + radio_request_unref(req); + } + } + return dm; +} + +BinderDataManager* +binder_data_manager_ref( + BinderDataManager* dm) +{ + if (dm) { + GASSERT(dm->refcount > 0); + g_atomic_int_inc(&dm->refcount); + } + return dm; +} + +void +binder_data_manager_unref( + BinderDataManager* dm) +{ + if (dm) { + GASSERT(dm->refcount > 0); + if (g_atomic_int_dec_and_test(&dm->refcount)) { + GASSERT(!dm->data_list); + radio_request_drop(dm->phone_cap_req); + radio_config_unref(dm->rc); + gutil_ints_unref(dm->modem_ids); + g_free(dm); + } + } +} + +static +gboolean +binder_data_manager_handover( + BinderDataManager* dm) +{ + /* + * The 3G/LTE handover thing only makes sense if we are managing + * more than one SIM slot. Otherwise leave things where they are. + */ + return dm->data_list && dm->data_list->next && + (dm->flags & BINDER_DATA_MANAGER_3GLTE_HANDOVER); +} + +static +gboolean +binder_data_manager_requests_pending( + BinderDataManager* dm) +{ + GSList* l; + + for (l = dm->data_list; l; l = l->next) { + BinderDataObject* data = THIS(l->data); + + if (data->pending_req || data->req_queue) { + return TRUE; + } + } + + return FALSE; +} + +static +void +binder_data_manager_check_network_mode( + BinderDataManager* dm) +{ + GSList* l; + + if (dm->non_data_mode && binder_data_manager_handover(dm)) { + BinderNetwork* lte_network = NULL; + BinderNetwork* best_network = NULL; + enum ofono_radio_access_mode best_mode = OFONO_RADIO_ACCESS_MODE_ANY; + const enum ofono_radio_access_mode non_data_mask = + (dm->non_data_mode << 1) - 1; + + /* Find a SIM for internet access */ + for (l = dm->data_list; l; l = l->next) { + BinderDataObject* data = THIS(l->data); + BinderNetwork* network = data->network; + BinderSimSettings* sim = network->settings; + enum ofono_radio_access_mode mode; + + /* Select the first network with internet role */ + if ((sim->pref > OFONO_RADIO_ACCESS_MODE_GSM) && + (data->flags & BINDER_DATA_FLAG_MAX_SPEED)) { + lte_network = network; + break; + } + + /* At the same time, look for a suitable slot */ + mode = binder_network_max_supported_mode(network); + if (mode > best_mode) { + best_network = network; + best_mode = mode; + } + } + + /* + * If there's no SIM selected for internet access + * then use a slot with highest capabilities for LTE. + */ + if (!lte_network) { + lte_network = best_network; + } + + for (l = dm->data_list; l; l = l->next) { + BinderDataObject* data = THIS(l->data); + BinderNetwork* net = data->network; + + binder_network_set_allowed_modes(net, (net == lte_network) ? + binder_data_max_allowed_modes(data) : non_data_mask, FALSE); + } + } else { + /* Otherwise there's no reason to limit anything */ + for (l = dm->data_list; l; l = l->next) { + BinderDataObject* data = THIS(l->data); + + binder_network_set_allowed_modes(data->network, + binder_data_max_allowed_modes(data), FALSE); + } + } +} + +static +BinderDataObject* +binder_data_manager_allowed( + BinderDataManager* dm) +{ + if (dm) { + GSList* l; + + for (l = dm->data_list; l; l = l->next) { + BinderDataObject* data = THIS(l->data); + + if (data->flags & BINDER_DATA_FLAG_ALLOWED) { + return data; + } + } + } + return NULL; +} + +static +void +binder_data_manager_switch_data_on( + BinderDataManager* dm, + BinderDataObject* data) +{ + DBG_(data, "allowing data"); + GASSERT(!(data->flags & BINDER_DATA_FLAG_ON)); + + if (binder_data_manager_handover(dm)) { + binder_network_set_allowed_modes(data->network, + binder_data_max_allowed_modes(data), TRUE); + } + + if (!binder_data_allow_submit_request(data, TRUE)) { + data->flags |= BINDER_DATA_FLAG_ON; + GASSERT(binder_data_is_allowed(data)); + DBG_(data, "data on"); + binder_base_emit_property_change(&data->base, + BINDER_DATA_PROPERTY_ALLOWED); + } +} + +void +binder_data_manager_check_data( + BinderDataManager* dm) +{ + /* + * Don't do anything if there're any requests pending. + */ + if (!binder_data_manager_requests_pending(dm)) { + BinderDataObject* data = binder_data_manager_allowed(dm); + + binder_data_manager_check_network_mode(dm); + if (data && !(data->flags & BINDER_DATA_FLAG_ON)) { + binder_data_manager_switch_data_on(dm, data); + } + } +} + +void +binder_data_manager_assert_data_on( + BinderDataManager* dm) +{ + if (binder_data_manager_need_set_data_allowed(dm)) { + binder_data_allow_submit_request(binder_data_manager_allowed(dm), TRUE); + } +} + +gboolean +binder_data_manager_need_set_data_allowed( + BinderDataManager* dm) +{ + return dm && radio_config_interface(dm->rc) < RADIO_CONFIG_INTERFACE_1_1; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_data.h b/src/binder_data.h new file mode 100644 index 0000000..f4be98b --- /dev/null +++ b/src/binder_data.h @@ -0,0 +1,257 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_DATA_H +#define BINDER_DATA_H + +#include "binder_types.h" + +#include + +#include +#include + +typedef enum binder_data_property { + BINDER_DATA_PROPERTY_ANY, + BINDER_DATA_PROPERTY_CALLS, + BINDER_DATA_PROPERTY_ALLOWED, + BINDER_DATA_PROPERTY_COUNT +} BINDER_DATA_PROPERTY; + +typedef struct binder_data_call { + int cid; + RADIO_DATA_CALL_FAIL_CAUSE status; + RADIO_DATA_CALL_ACTIVE_STATUS active; + enum ofono_gprs_proto prot; + int retry_time; + int mtu; + char* ifname; + char** dnses; + char** gateways; + char** addresses; + char** pcscf; +} BinderDataCall; + +struct binder_data { + GSList* calls; +}; + +typedef enum binder_data_manager_flags { + BINDER_DATA_MANAGER_NO_FLAGS = 0x00, + BINDER_DATA_MANAGER_3GLTE_HANDOVER = 0x01 +} BINDER_DATA_MANAGER_FLAGS; + +typedef enum binder_data_allow_data { + BINDER_ALLOW_DATA_DISABLED, + BINDER_ALLOW_DATA_ENABLED +} BINDER_DATA_ALLOW_DATA; + +typedef struct binder_data_options { + BINDER_DATA_ALLOW_DATA allow_data; + unsigned int data_call_retry_limit; + unsigned int data_call_retry_delay_ms; +} BinderDataOptions; + +typedef struct binder_data_request BinderDataRequest; + +typedef +void +(*BinderDataPropertyFunc)( + BinderData* data, + BINDER_DATA_PROPERTY property, + void* user_data); + +typedef +void +(*BinderDataCallSetupFunc)( + BinderData* data, + RADIO_ERROR status, + const BinderDataCall* call, + void* user_data); + +typedef +void +(*BinderDataCallDeactivateFunc)( + BinderData* data, + RADIO_ERROR status, + void* user_data); + +BinderDataManager* +binder_data_manager_new( + RadioConfig* config, + BINDER_DATA_MANAGER_FLAGS flags, + enum ofono_radio_access_mode non_data_mode) + BINDER_INTERNAL; + +BinderDataManager* +binder_data_manager_ref( + BinderDataManager* dm) + BINDER_INTERNAL; + +void +binder_data_manager_unref( + BinderDataManager* dm) + BINDER_INTERNAL; + +void +binder_data_manager_check_data( + BinderDataManager* dm) + BINDER_INTERNAL; + +void +binder_data_manager_assert_data_on( + BinderDataManager* dm) + BINDER_INTERNAL; + +gboolean +binder_data_manager_need_set_data_allowed( + BinderDataManager* dm) + BINDER_INTERNAL; + +BinderData* +binder_data_new( + BinderDataManager* dm, + RadioClient* client, + const char* name, + BinderRadio* radio, + BinderNetwork* network, + const BinderDataOptions* options, + const BinderSlotConfig* config) + BINDER_INTERNAL; + +BinderData* +binder_data_ref( + BinderData* data) + BINDER_INTERNAL; + +void +binder_data_unref( + BinderData* data) + BINDER_INTERNAL; + +gboolean +binder_data_allowed( + BinderData* data) + BINDER_INTERNAL; + +void +binder_data_poll_call_state( + BinderData* data) + BINDER_INTERNAL; + +gulong +binder_data_add_property_handler( + BinderData* data, + BINDER_DATA_PROPERTY property, + BinderDataPropertyFunc cb, + void* user_data) + BINDER_INTERNAL; + +void +binder_data_remove_handler( + BinderData* data, + gulong id) + BINDER_INTERNAL; + +void +binder_data_allow( + BinderData* data, + enum ofono_slot_data_role role) + BINDER_INTERNAL; + +BinderDataRequest* +binder_data_call_setup( + BinderData* data, + const struct ofono_gprs_primary_context* ctx, + enum ofono_gprs_context_type context_type, + BinderDataCallSetupFunc cb, + void* user_data) + BINDER_INTERNAL; + +BinderDataRequest* +binder_data_call_deactivate( + BinderData* data, + int cid, + BinderDataCallDeactivateFunc cb, + void* user_data) + BINDER_INTERNAL; + +void +binder_data_request_detach( + BinderDataRequest* req) + BINDER_INTERNAL; + +void +binder_data_request_cancel( + BinderDataRequest* req) + BINDER_INTERNAL; + +gboolean +binder_data_call_grab( + BinderData* data, + int cid, + void* cookie) + BINDER_INTERNAL; + +void +binder_data_call_release( + BinderData* data, + int cid, + void* cookie) + BINDER_INTERNAL; + +void +binder_data_call_free( + BinderDataCall* call) + BINDER_INTERNAL; + +BinderDataCall* +binder_data_call_dup( + const BinderDataCall* call) + BINDER_INTERNAL; + +BinderDataCall* +binder_data_call_find( + GSList* list, + int cid) + BINDER_INTERNAL; + +RadioRequest* +binder_data_deactivate_data_call_request_new( + RadioRequestGroup* group, + int cid, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) + BINDER_INTERNAL; + +RadioRequest* +binder_data_set_data_allowed_request_new( + RadioRequestGroup* group, + gboolean allow, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) + BINDER_INTERNAL; + +#endif /* BINDER_DATA_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devinfo.c b/src/binder_devinfo.c new file mode 100644 index 0000000..509ba66 --- /dev/null +++ b/src/binder_devinfo.c @@ -0,0 +1,314 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_devinfo.h" +#include "binder_modem.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include + +#include +#include + +enum binder_devinfo_cb_tag { + DEVINFO_QUERY_SERIAL = 1, + DEVINFO_QUERY_SVN +}; + +typedef struct binder_devinfo { + struct ofono_devinfo* di; + RadioRequestGroup* g; + GUtilIdleQueue* iq; + char* log_prefix; + char* imeisv; + char* imei; +} BinderDevInfo; + +typedef struct binder_devinfo_callback_data { + BinderDevInfo* self; + ofono_devinfo_query_cb_t cb; + gpointer data; +} BinderDevInfoCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderDevInfo* binder_devinfo_get_data(struct ofono_devinfo* di) + { return ofono_devinfo_get_data(di); } + +static +BinderDevInfoCbData* +binder_devinfo_callback_data_new( + BinderDevInfo* self, + ofono_devinfo_query_cb_t cb, + void* data) +{ + BinderDevInfoCbData* cbd = g_slice_new0(BinderDevInfoCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_devinfo_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderDevInfoCbData, cbd); +} + +static +void +binder_devinfo_query_unsupported( + struct ofono_devinfo* di, + ofono_devinfo_query_cb_t cb, + void* data) +{ + struct ofono_error error; + + cb(binder_error_failure(&error), "", data); +} + +static +void +binder_devinfo_query_revision_ok( + const BinderDevInfoCbData* cbd, + const GBinderReader* args) +{ + struct ofono_error err; + GBinderReader reader; + const char* res; + + /* getBasebandVersionResponse(RadioResponseInfo, string version); */ + gbinder_reader_copy(&reader, args); + res = gbinder_reader_read_hidl_string_c(&reader); + DBG_(cbd->self, "%s", res); + cbd->cb(binder_error_ok(&err), res ? res : "", cbd->data); +} + +static +void +binder_devinfo_query_revision_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + struct ofono_error err; + const BinderDevInfoCbData* cbd = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_BASEBAND_VERSION) { + if (error == RADIO_ERROR_NONE) { + binder_devinfo_query_revision_ok(cbd, args); + return; + } else { + ofono_error("getBasebandVersion error %d", error); + } + } else { + ofono_error("Unexpected getBasebandVersion response %d", resp); + } + } + cbd->cb(binder_error_failure(&err), NULL, cbd->data); +} + +static +void +binder_devinfo_query_revision( + struct ofono_devinfo* di, + ofono_devinfo_query_cb_t cb, + void* data) +{ + BinderDevInfo* self = binder_devinfo_get_data(di); + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_BASEBAND_VERSION, NULL, + binder_devinfo_query_revision_cb, + binder_devinfo_callback_data_free, + binder_devinfo_callback_data_new(self, cb, data)); + + DBG_(self, ""); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_devinfo_query_serial_cb( + gpointer user_data) +{ + BinderDevInfoCbData* cbd = user_data; + BinderDevInfo* self = cbd->self; + struct ofono_error error; + + DBG_(self, "%s", self->imei); + cbd->cb(binder_error_ok(&error), self->imei, cbd->data); +} + +static +void +binder_devinfo_query_svn_cb( + gpointer user_data) +{ + BinderDevInfoCbData* cbd = user_data; + BinderDevInfo* self = cbd->self; + struct ofono_error error; + + DBG_(self, "%s", self->imeisv); + if (self->imeisv && self->imeisv[0]) { + cbd->cb(binder_error_ok(&error), self->imeisv, cbd->data); + } else { + cbd->cb(binder_error_failure(&error), "", cbd->data); + } +} + +static +void +binder_devinfo_query( + BinderDevInfo* self, + enum binder_devinfo_cb_tag tag, + GUtilIdleFunc fn, + ofono_devinfo_query_cb_t cb, + void* data) +{ + GVERIFY_FALSE(gutil_idle_queue_cancel_tag(self->iq, tag)); + gutil_idle_queue_add_tag_full(self->iq, tag, fn, + binder_devinfo_callback_data_new(self, cb, data), + binder_devinfo_callback_data_free); +} + +static +void +binder_devinfo_query_serial( + struct ofono_devinfo* devinfo, + ofono_devinfo_query_cb_t cb, + void* data) +{ + BinderDevInfo* self = binder_devinfo_get_data(devinfo); + + DBG_(self, ""); + binder_devinfo_query(self, DEVINFO_QUERY_SERIAL, + binder_devinfo_query_serial_cb, cb, data); +} + +static +void +binder_devinfo_query_svn( + struct ofono_devinfo* devinfo, + ofono_devinfo_query_cb_t cb, + void* data) +{ + BinderDevInfo* self = binder_devinfo_get_data(devinfo); + + DBG_(self, ""); + binder_devinfo_query(self, DEVINFO_QUERY_SVN, + binder_devinfo_query_svn_cb, cb, data); +} + +static +void +binder_devinfo_register( + gpointer user_data) +{ + BinderDevInfo* self = user_data; + + DBG_(self, ""); + ofono_devinfo_register(self->di); +} + +static +int +binder_devinfo_probe( + struct ofono_devinfo* di, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderDevInfo* self = g_new0(BinderDevInfo, 1); + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + + DBG_(self, "%s", modem->imei); + self->g = radio_request_group_new(modem->client); + self->di = di; + self->imeisv = g_strdup(modem->imeisv); + self->imei = g_strdup(modem->imei); + self->iq = gutil_idle_queue_new(); + gutil_idle_queue_add(self->iq, binder_devinfo_register, self); + ofono_devinfo_set_data(di, self); + return 0; +} + +static +void +binder_devinfo_remove( + struct ofono_devinfo* di) +{ + BinderDevInfo* self = binder_devinfo_get_data(di); + + DBG_(self, ""); + ofono_devinfo_set_data(di, NULL); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + gutil_idle_queue_cancel_all(self->iq); + gutil_idle_queue_unref(self->iq); + g_free(self->log_prefix); + g_free(self->imeisv); + g_free(self->imei); + g_free(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_devinfo_driver binder_devinfo_driver = { + .name = BINDER_DRIVER, + .probe = binder_devinfo_probe, + .remove = binder_devinfo_remove, + /* query_revision won't be called if query_model is missing */ + .query_model = binder_devinfo_query_unsupported, + .query_revision = binder_devinfo_query_revision, + .query_serial = binder_devinfo_query_serial, + .query_svn = binder_devinfo_query_svn +}; + +void +binder_devinfo_init() +{ + ofono_devinfo_driver_register(&binder_devinfo_driver); +} + +void +binder_devinfo_cleanup() +{ + ofono_devinfo_driver_unregister(&binder_devinfo_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devinfo.h b/src/binder_devinfo.h new file mode 100644 index 0000000..402a323 --- /dev/null +++ b/src/binder_devinfo.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_DEVINFO_H +#define BINDER_DEVINFO_H + +#include "binder_types.h" + +void +binder_devinfo_init(void) + BINDER_INTERNAL; + +void +binder_devinfo_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_DEVINFO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devmon.c b/src/binder_devmon.c new file mode 100644 index 0000000..5245351 --- /dev/null +++ b/src/binder_devmon.c @@ -0,0 +1,51 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_devmon.h" + +BinderDevmonIo* +binder_devmon_start_io( + BinderDevmon* devmon, + RadioClient* client, + struct ofono_slot* slot) +{ + return devmon ? devmon->start_io(devmon, client, slot) : NULL; +} + +void +binder_devmon_io_free( + BinderDevmonIo* io) +{ + if (io) { + io->free(io); + } +} + +void +binder_devmon_free( + BinderDevmon* devmon) +{ + if (devmon) { + devmon->free(devmon); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devmon.h b/src/binder_devmon.h new file mode 100644 index 0000000..8eff6f1 --- /dev/null +++ b/src/binder_devmon.h @@ -0,0 +1,95 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_DEVMON_H +#define BINDER_DEVMON_H + +#include "binder_types.h" + +#include + +/* + * Separate instance of BinderDevmon is created for each modem. + * Device monitor is started after connection to the modem has + * been established. + */ + +typedef struct binder_devmon_io BinderDevmonIo; +struct binder_devmon_io { + void (*free)(BinderDevmonIo* io); +}; + +struct binder_devmon { + void (*free)(BinderDevmon* devmon); + BinderDevmonIo* (*start_io)(BinderDevmon* devmon, RadioClient* client, + struct ofono_slot* slot); +}; + +/* + * This Device Monitor uses sendDeviceState() call to let the modem + * choose the right power saving strategy. It basically mirrors the + * logic of DeviceStateMonitor class in Android. + */ +BinderDevmon* +binder_devmon_ds_new( + const BinderSlotConfig* config) + BINDER_INTERNAL; + +/* + * This Device Monitor implementation controls network state updates + * by calling setIndicationFilter(). + */ +BinderDevmon* +binder_devmon_if_new( + const BinderSlotConfig* config) + BINDER_INTERNAL; + +/* + * This one combines several methods. Takes ownership of binder_devmon objects. + */ +BinderDevmon* +binder_devmon_combine( + BinderDevmon* devmon[], + guint n) + BINDER_INTERNAL; + +/* Utilities (NULL tolerant) */ + +BinderDevmonIo* +binder_devmon_start_io( + BinderDevmon* devmon, + RadioClient* client, + struct ofono_slot* slot) + BINDER_INTERNAL; + +void +binder_devmon_io_free( + BinderDevmonIo* io) + BINDER_INTERNAL; + +void +binder_devmon_free( + BinderDevmon* devmon) + BINDER_INTERNAL; + +#endif /* BINDER_DEVMON_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devmon_combine.c b/src/binder_devmon_combine.c new file mode 100644 index 0000000..a2ce104 --- /dev/null +++ b/src/binder_devmon_combine.c @@ -0,0 +1,117 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_devmon.h" + +#include + +#include + +typedef struct binder_devmon_combine { + BinderDevmon pub; + BinderDevmon** impl; + guint count; +} DevMon; + +typedef struct binder_devmon_combine_io { + BinderDevmonIo pub; + BinderDevmonIo** impl; + guint count; +} DevMonIo; + +static inline DevMon* binder_devmon_combine_cast(BinderDevmon* devmon) + { return G_CAST(devmon, DevMon, pub); } + +static inline DevMonIo* binder_devmon_combine_io_cast(BinderDevmonIo* io) + { return G_CAST(io, DevMonIo, pub); } + +static +void +binder_devmon_combine_io_free( + BinderDevmonIo* io) +{ + guint i; + DevMonIo* self = binder_devmon_combine_io_cast(io); + + for (i = 0; i < self->count; i++) { + binder_devmon_io_free(self->impl[i]); + } + g_free(self); +} + +static +BinderDevmonIo* +binder_devmon_combine_start_io( + BinderDevmon* devmon, + RadioClient* client, + struct ofono_slot* slot) +{ + guint i; + DevMon* self = binder_devmon_combine_cast(devmon); + DevMonIo* io = g_malloc0(sizeof(DevMonIo) + + sizeof(BinderDevmonIo*) * self->count); + + io->pub.free = binder_devmon_combine_io_free; + io->impl = (BinderDevmonIo**)(io + 1); + io->count = self->count; + for (i = 0; i < io->count; i++) { + io->impl[i] = binder_devmon_start_io(self->impl[i], client, slot); + } + return &io->pub; +} + +static +void +binder_devmon_combine_free( + BinderDevmon* dm) +{ + DevMon* self = binder_devmon_combine_cast(dm); + guint i; + + for (i = 0; i < self->count; i++) { + binder_devmon_free(self->impl[i]); + } + g_free(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderDevmon* +binder_devmon_combine( + BinderDevmon* dm[], + guint n) +{ + guint i; + DevMon* self = g_malloc0(sizeof(DevMon) + sizeof(BinderDevmon*) * n); + + self->pub.free = binder_devmon_combine_free; + self->pub.start_io = binder_devmon_combine_start_io; + self->impl = (BinderDevmon**)(self + 1); + self->count = n; + for (i = 0; i < n; i++) { + self->impl[i] = dm[i]; + } + return &self->pub; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devmon_ds.c b/src/binder_devmon_ds.c new file mode 100644 index 0000000..5e49809 --- /dev/null +++ b/src/binder_devmon_ds.c @@ -0,0 +1,417 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_devmon.h" +#include "binder_connman.h" +#include "binder_log.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +enum binder_devmon_ds_battery_event { + BATTERY_EVENT_VALID, + BATTERY_EVENT_STATUS, + BATTERY_EVENT_COUNT +}; + +enum binder_devmon_ds_charger_event { + CHARGER_EVENT_VALID, + CHARGER_EVENT_STATE, + CHARGER_EVENT_COUNT +}; + +enum binder_devmon_ds_display_event { + DISPLAY_EVENT_VALID, + DISPLAY_EVENT_STATE, + DISPLAY_EVENT_COUNT +}; + +enum binder_devmon_ds_connman_event { + CONNMAN_EVENT_VALID, + CONNMAN_EVENT_TETHERING, + CONNMAN_EVENT_COUNT +}; + +typedef struct binder_devmon_ds { + BinderDevmon pub; + BinderConnman* connman; + MceBattery* battery; + MceCharger* charger; + MceDisplay* display; + int cell_info_interval_short_ms; + int cell_info_interval_long_ms; +} DevMon; + +typedef struct binder_devmon_ds_io { + BinderDevmonIo pub; + BinderConnman* connman; + struct ofono_slot* slot; + MceBattery* battery; + MceCharger* charger; + MceDisplay* display; + RadioClient* client; + RadioRequest* low_data_req; + RadioRequest* charging_req; + gboolean low_data; + gboolean charging; + gboolean low_data_supported; + gboolean charging_supported; + gulong connman_event_id[CONNMAN_EVENT_COUNT]; + gulong battery_event_id[BATTERY_EVENT_COUNT]; + gulong charger_event_id[CHARGER_EVENT_COUNT]; + gulong display_event_id[DISPLAY_EVENT_COUNT]; + int cell_info_interval_short_ms; + int cell_info_interval_long_ms; +} DevMonIo; + +#define DBG_(self,fmt,args...) \ + DBG("%s: " fmt, radio_client_slot((self)->client), ##args) + +static inline DevMon* binder_devmon_ds_cast(BinderDevmon* pub) + { return G_CAST(pub, DevMon, pub); } + +static inline DevMonIo* binder_devmon_ds_io_cast(BinderDevmonIo* pub) + { return G_CAST(pub, DevMonIo, pub); } + +static inline gboolean binder_devmon_ds_tethering_on(BinderConnman* connman) + { return connman->valid && connman->tethering; } + +static inline gboolean binder_devmon_ds_battery_ok(MceBattery* battery) + { return battery->valid && battery->status >= MCE_BATTERY_OK; } + +static inline gboolean binder_devmon_ds_charging(MceCharger* charger) + { return charger->valid && charger->state == MCE_CHARGER_ON; } + +static inline gboolean binder_devmon_ds_display_on(MceDisplay* display) + { return display->valid && display->state != MCE_DISPLAY_STATE_OFF; } + +static +void +binder_devmon_ds_io_low_data_state_sent( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + DevMonIo* self = user_data; + + GASSERT(self->low_data_req == req); + radio_request_unref(self->low_data_req); + self->low_data_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_DEVICE_STATE) { + if (error == RADIO_ERROR_REQUEST_NOT_SUPPORTED) { + DBG_(self, "LOW_DATA_EXPECTED state is not supported"); + self->low_data_supported = FALSE; + } + } else { + ofono_error("Unexpected sendDeviceState response %d", resp); + self->low_data_supported = FALSE; + } + } +} + +static +void +binder_devmon_ds_io_charging_state_sent( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + DevMonIo* self = user_data; + + GASSERT(self->charging_req == req); + radio_request_unref(self->charging_req); + self->charging_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_DEVICE_STATE) { + if (error == RADIO_ERROR_REQUEST_NOT_SUPPORTED) { + DBG_(self, "CHARGING state is not supported"); + self->charging_supported = FALSE; + } + } else { + ofono_error("Unexpected sendDeviceState response %d", resp); + self->charging_supported = FALSE; + } + } +} + +static +RadioRequest* +binder_devmon_ds_io_send_device_state( + DevMonIo* self, + RADIO_DEVICE_STATE type, + gboolean state, + RadioRequestCompleteFunc callback) +{ + GBinderWriter writer; + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_SEND_DEVICE_STATE, &writer, callback, NULL, self); + + /* sendDeviceState(int32_t serial, DeviceStateType type, bool state); */ + gbinder_writer_append_int32(&writer, type); + gbinder_writer_append_bool(&writer, state); + if (radio_request_submit(req)) { + return req; + } else { + radio_request_unref(req); + return NULL; + } +} + +static +void +binder_devmon_ds_io_update_charging( + DevMonIo* self) +{ + const gboolean charging = binder_devmon_ds_charging(self->charger); + + if (self->charging != charging) { + self->charging = charging; + DBG_(self, "Charging %s", charging ? "on" : "off"); + if (self->charging_supported) { + radio_request_drop(self->charging_req); + self->charging_req = binder_devmon_ds_io_send_device_state(self, + RADIO_DEVICE_STATE_CHARGING_STATE, charging, + binder_devmon_ds_io_charging_state_sent); + } + } +} + +static +void +binder_devmon_ds_io_update_low_data( + DevMonIo* self) +{ + const gboolean low_data = + !binder_devmon_ds_tethering_on(self->connman) && + !binder_devmon_ds_charging(self->charger) && + !binder_devmon_ds_display_on(self->display); + + if (self->low_data != low_data) { + self->low_data = low_data; + DBG_(self, "Low data is%s expected", low_data ? "" : " not"); + if (self->low_data_supported) { + radio_request_drop(self->low_data_req); + self->low_data_req = binder_devmon_ds_io_send_device_state(self, + RADIO_DEVICE_STATE_LOW_DATA_EXPECTED, low_data, + binder_devmon_ds_io_low_data_state_sent); + } + } +} + +static +void +binder_devmon_ds_io_set_cell_info_update_interval( + DevMonIo* self) +{ + ofono_slot_set_cell_info_update_interval(self->slot, self, + (binder_devmon_ds_display_on(self->display) && + (binder_devmon_ds_charging(self->charger) || + binder_devmon_ds_battery_ok(self->battery))) ? + self->cell_info_interval_short_ms : + self->cell_info_interval_long_ms); +} + +static +void +binder_devmon_ds_io_connman_cb( + BinderConnman* connman, + BINDER_CONNMAN_PROPERTY property, + void* user_data) +{ + binder_devmon_ds_io_update_low_data((DevMonIo*)user_data); +} + +static +void +binder_devmon_ds_io_battery_cb( + MceBattery* battery, + void* user_data) +{ + binder_devmon_ds_io_set_cell_info_update_interval((DevMonIo*)user_data); +} + +static +void +binder_devmon_ds_io_display_cb( + MceDisplay* display, + void* user_data) +{ + DevMonIo* self = user_data; + + binder_devmon_ds_io_update_low_data(self); + binder_devmon_ds_io_set_cell_info_update_interval(self); +} + +static +void +binder_devmon_ds_io_charger_cb( + MceCharger* charger, + void* user_data) +{ + DevMonIo* self = user_data; + + binder_devmon_ds_io_update_low_data(self); + binder_devmon_ds_io_update_charging(self); + binder_devmon_ds_io_set_cell_info_update_interval(self); +} + +static +void +binder_devmon_ds_io_free( + BinderDevmonIo* io) +{ + DevMonIo* self = binder_devmon_ds_io_cast(io); + + binder_connman_remove_all_handlers(self->connman, self->connman_event_id); + binder_connman_unref(self->connman); + + mce_battery_remove_all_handlers(self->battery, self->battery_event_id); + mce_battery_unref(self->battery); + + mce_charger_remove_all_handlers(self->charger, self->charger_event_id); + mce_charger_unref(self->charger); + + mce_display_remove_all_handlers(self->display, self->display_event_id); + mce_display_unref(self->display); + + radio_request_drop(self->low_data_req); + radio_request_drop(self->charging_req); + radio_client_unref(self->client); + + ofono_slot_drop_cell_info_requests(self->slot, self); + ofono_slot_unref(self->slot); + g_free(self); +} + +static +BinderDevmonIo* +binder_devmon_ds_start_io( + BinderDevmon* devmon, + RadioClient* client, + struct ofono_slot* slot) +{ + DevMon* ds = binder_devmon_ds_cast(devmon); + DevMonIo* self = g_new0(DevMonIo, 1); + + self->pub.free = binder_devmon_ds_io_free; + self->low_data_supported = TRUE; + self->charging_supported = TRUE; + self->client = radio_client_ref(client); + self->slot = ofono_slot_ref(slot); + + self->connman = binder_connman_ref(ds->connman); + self->connman_event_id[CONNMAN_EVENT_VALID] = + binder_connman_add_property_changed_handler(self->connman, + BINDER_CONNMAN_PROPERTY_VALID, + binder_devmon_ds_io_connman_cb, self); + self->connman_event_id[CONNMAN_EVENT_TETHERING] = + binder_connman_add_property_changed_handler(self->connman, + BINDER_CONNMAN_PROPERTY_TETHERING, + binder_devmon_ds_io_connman_cb, self); + + self->battery = mce_battery_ref(ds->battery); + self->battery_event_id[BATTERY_EVENT_VALID] = + mce_battery_add_valid_changed_handler(self->battery, + binder_devmon_ds_io_battery_cb, self); + self->battery_event_id[BATTERY_EVENT_STATUS] = + mce_battery_add_status_changed_handler(self->battery, + binder_devmon_ds_io_battery_cb, self); + + self->charger = mce_charger_ref(ds->charger); + self->charger_event_id[CHARGER_EVENT_VALID] = + mce_charger_add_valid_changed_handler(self->charger, + binder_devmon_ds_io_charger_cb, self); + self->charger_event_id[CHARGER_EVENT_STATE] = + mce_charger_add_state_changed_handler(self->charger, + binder_devmon_ds_io_charger_cb, self); + + self->display = mce_display_ref(ds->display); + self->display_event_id[DISPLAY_EVENT_VALID] = + mce_display_add_valid_changed_handler(self->display, + binder_devmon_ds_io_display_cb, self); + self->display_event_id[DISPLAY_EVENT_STATE] = + mce_display_add_state_changed_handler(self->display, + binder_devmon_ds_io_display_cb, self); + + self->cell_info_interval_short_ms = ds->cell_info_interval_short_ms; + self->cell_info_interval_long_ms = ds->cell_info_interval_long_ms; + + binder_devmon_ds_io_update_low_data(self); + binder_devmon_ds_io_update_charging(self); + binder_devmon_ds_io_set_cell_info_update_interval(self); + return &self->pub; +} + +static +void +binder_devmon_ds_free( + BinderDevmon* devmon) +{ + DevMon* self = binder_devmon_ds_cast(devmon); + + binder_connman_unref(self->connman); + mce_battery_unref(self->battery); + mce_charger_unref(self->charger); + mce_display_unref(self->display); + g_free(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderDevmon* +binder_devmon_ds_new( + const BinderSlotConfig* config) +{ + DevMon* self = g_new0(DevMon, 1); + + self->pub.free = binder_devmon_ds_free; + self->pub.start_io = binder_devmon_ds_start_io; + self->connman = binder_connman_new(); + self->battery = mce_battery_new(); + self->charger = mce_charger_new(); + self->display = mce_display_new(); + self->cell_info_interval_short_ms = config->cell_info_interval_short_ms; + self->cell_info_interval_long_ms = config->cell_info_interval_long_ms; + return &self->pub; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_devmon_if.c b/src/binder_devmon_if.c new file mode 100644 index 0000000..e1c6de7 --- /dev/null +++ b/src/binder_devmon_if.c @@ -0,0 +1,322 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_devmon.h" +#include "binder_log.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +enum binder_devmon_if_battery_event { + BATTERY_EVENT_VALID, + BATTERY_EVENT_STATUS, + BATTERY_EVENT_COUNT +}; + +enum binder_devmon_if_charger_event { + CHARGER_EVENT_VALID, + CHARGER_EVENT_STATE, + CHARGER_EVENT_COUNT +}; + +enum binder_devmon_if_display_event { + DISPLAY_EVENT_VALID, + DISPLAY_EVENT_STATE, + DISPLAY_EVENT_COUNT +}; + +typedef struct binder_devmon_if { + BinderDevmon pub; + MceBattery* battery; + MceCharger* charger; + MceDisplay* display; + int cell_info_interval_short_ms; + int cell_info_interval_long_ms; +} DevMon; + +typedef struct binder_devmon_if_io { + BinderDevmonIo pub; + struct ofono_slot* slot; + MceBattery* battery; + MceCharger* charger; + MceDisplay* display; + RadioClient* client; + RadioRequest* req; + gboolean display_on; + gboolean ind_filter_supported; + gulong battery_event_id[BATTERY_EVENT_COUNT]; + gulong charger_event_id[CHARGER_EVENT_COUNT]; + gulong display_event_id[DISPLAY_EVENT_COUNT]; + int cell_info_interval_short_ms; + int cell_info_interval_long_ms; +} DevMonIo; + +#define DBG_(self,fmt,args...) \ + DBG("%s: " fmt, radio_client_slot((self)->client), ##args) + +inline static DevMon* binder_devmon_if_cast(BinderDevmon* pub) + { return G_CAST(pub, DevMon, pub); } + +inline static DevMonIo* binder_devmon_if_io_cast(BinderDevmonIo* pub) + { return G_CAST(pub, DevMonIo, pub); } + +static inline gboolean binder_devmon_if_battery_ok(MceBattery* battery) + { return battery->valid && battery->status >= MCE_BATTERY_OK; } + +static inline gboolean binder_devmon_if_charging(MceCharger* charger) + { return charger->valid && charger->state == MCE_CHARGER_ON; } + +static gboolean binder_devmon_if_display_on(MceDisplay* display) + { return display->valid && display->state != MCE_DISPLAY_STATE_OFF; } + +static +void +binder_devmon_if_io_indication_filter_sent( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + DevMonIo* self = user_data; + + GASSERT(self->req == req); + radio_request_unref(self->req); + self->req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_INDICATION_FILTER) { + if (error == RADIO_ERROR_REQUEST_NOT_SUPPORTED) { + /* This is a permanent failure */ + DBG_(self, "Indication response filter is not supported"); + self->ind_filter_supported = FALSE; + } + } else { + ofono_error("Unexpected setIndicationFilter response %d", resp); + } + } +} + +static +void +binder_devmon_if_io_set_indication_filter( + DevMonIo* self) +{ + if (self->ind_filter_supported) { + GBinderWriter args; + RADIO_REQ code; + gint32 value; + + /* + * Both requests take the same args: + * + * setIndicationFilter(serial, bitfield) + * setIndicationFilter_1_2(serial, bitfield) + * + * and both produce IRadioResponse.setIndicationFilterResponse() + * + * However setIndicationFilter_1_2 comments says "If unset, defaults + * to @1.2::IndicationFilter:ALL" and it's unclear what "unset" means + * wrt a bitmask. How is "unset" different from NONE which is zero. + * To be on the safe side, let's always set the most innocently + * looking bit which I think is DATA_CALL_DORMANCY. + */ + if (radio_client_interface(self->client) < RADIO_INTERFACE_1_2) { + code = RADIO_REQ_SET_INDICATION_FILTER; + value = self->display_on ? RADIO_IND_FILTER_ALL : + RADIO_IND_FILTER_DATA_CALL_DORMANCY; + } else { + code = RADIO_REQ_SET_INDICATION_FILTER_1_2; + value = self->display_on ? RADIO_IND_FILTER_ALL_1_2 : + RADIO_IND_FILTER_DATA_CALL_DORMANCY; + } + + radio_request_drop(self->req); + self->req = radio_request_new(self->client, code, &args, + binder_devmon_if_io_indication_filter_sent, NULL, self); + gbinder_writer_append_int32(&args, value); + DBG_(self, "Setting indication filter: 0x%02x", value); + radio_request_submit(self->req); + } +} + +static +void +binder_devmon_if_io_set_cell_info_update_interval( + DevMonIo* self) +{ + ofono_slot_set_cell_info_update_interval(self->slot, self, + (self->display_on && (binder_devmon_if_charging(self->charger) || + binder_devmon_if_battery_ok(self->battery))) ? + self->cell_info_interval_short_ms : + self->cell_info_interval_long_ms); +} + +static +void +binder_devmon_if_io_battery_cb( + MceBattery* battery, + void* user_data) +{ + binder_devmon_if_io_set_cell_info_update_interval((DevMonIo*)user_data); +} + +static +void binder_devmon_if_io_charger_cb( + MceCharger* charger, + void* user_data) +{ + binder_devmon_if_io_set_cell_info_update_interval((DevMonIo*)user_data); +} + +static +void +binder_devmon_if_io_display_cb( + MceDisplay* display, + void* user_data) +{ + DevMonIo* self = user_data; + const gboolean display_on = binder_devmon_if_display_on(display); + + if (self->display_on != display_on) { + self->display_on = display_on; + binder_devmon_if_io_set_indication_filter(self); + binder_devmon_if_io_set_cell_info_update_interval(self); + } +} + +static +void +binder_devmon_if_io_free( + BinderDevmonIo* io) +{ + DevMonIo* self = binder_devmon_if_io_cast(io); + + mce_battery_remove_all_handlers(self->battery, self->battery_event_id); + mce_battery_unref(self->battery); + + mce_charger_remove_all_handlers(self->charger, self->charger_event_id); + mce_charger_unref(self->charger); + + mce_display_remove_all_handlers(self->display, self->display_event_id); + mce_display_unref(self->display); + + radio_request_drop(self->req); + radio_client_unref(self->client); + + ofono_slot_drop_cell_info_requests(self->slot, self); + ofono_slot_unref(self->slot); + g_free(self); +} + +static +BinderDevmonIo* +binder_devmon_if_start_io( + BinderDevmon* devmon, + RadioClient* client, + struct ofono_slot* slot) +{ + DevMon* impl = binder_devmon_if_cast(devmon); + DevMonIo* self = g_new0(DevMonIo, 1); + + self->pub.free = binder_devmon_if_io_free; + self->ind_filter_supported = TRUE; + self->client = radio_client_ref(client); + self->slot = ofono_slot_ref(slot); + + self->battery = mce_battery_ref(impl->battery); + self->battery_event_id[BATTERY_EVENT_VALID] = + mce_battery_add_valid_changed_handler(self->battery, + binder_devmon_if_io_battery_cb, self); + self->battery_event_id[BATTERY_EVENT_STATUS] = + mce_battery_add_status_changed_handler(self->battery, + binder_devmon_if_io_battery_cb, self); + + self->charger = mce_charger_ref(impl->charger); + self->charger_event_id[CHARGER_EVENT_VALID] = + mce_charger_add_valid_changed_handler(self->charger, + binder_devmon_if_io_charger_cb, self); + self->charger_event_id[CHARGER_EVENT_STATE] = + mce_charger_add_state_changed_handler(self->charger, + binder_devmon_if_io_charger_cb, self); + + self->display = mce_display_ref(impl->display); + self->display_on = binder_devmon_if_display_on(self->display); + self->display_event_id[DISPLAY_EVENT_VALID] = + mce_display_add_valid_changed_handler(self->display, + binder_devmon_if_io_display_cb, self); + self->display_event_id[DISPLAY_EVENT_STATE] = + mce_display_add_state_changed_handler(self->display, + binder_devmon_if_io_display_cb, self); + + self->cell_info_interval_short_ms = impl->cell_info_interval_short_ms; + self->cell_info_interval_long_ms = impl->cell_info_interval_long_ms; + + binder_devmon_if_io_set_indication_filter(self); + binder_devmon_if_io_set_cell_info_update_interval(self); + return &self->pub; +} + +static +void +binder_devmon_if_free( + BinderDevmon* devmon) +{ + DevMon* self = binder_devmon_if_cast(devmon); + + mce_battery_unref(self->battery); + mce_charger_unref(self->charger); + mce_display_unref(self->display); + g_free(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderDevmon* +binder_devmon_if_new( + const BinderSlotConfig* config) +{ + DevMon* self = g_new0(DevMon, 1); + + self->pub.free = binder_devmon_if_free; + self->pub.start_io = binder_devmon_if_start_io; + self->battery = mce_battery_new(); + self->charger = mce_charger_new(); + self->display = mce_display_new(); + self->cell_info_interval_short_ms = config->cell_info_interval_short_ms; + self->cell_info_interval_long_ms = config->cell_info_interval_long_ms; + return &self->pub; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_gprs.c b/src/binder_gprs.c new file mode 100644 index 0000000..c007246 --- /dev/null +++ b/src/binder_gprs.c @@ -0,0 +1,350 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_data.h" +#include "binder_gprs.h" +#include "binder_modem.h" +#include "binder_log.h" +#include "binder_netreg.h" +#include "binder_network.h" +#include "binder_util.h" + +#include +#include +#include +#include +#include + +enum binder_gprs_network_events { + NETWORK_EVENT_DATA_STATE, + NETWORK_EVENT_MAX_DATA_CALLS, + NETWORK_EVENT_COUNT +}; + +typedef struct binder_gprs { + struct ofono_gprs* gprs; + struct ofono_watch* watch; + BinderData* data; + BinderNetwork* network; + enum ofono_netreg_status reg_status; + gboolean attached; + gulong network_event_id[NETWORK_EVENT_COUNT]; + gulong data_event_id; + guint set_attached_id; + guint init_id; + char* log_prefix; +} BinderGprs; + +typedef struct binder_gprs_cbd { + BinderGprs* self; + ofono_gprs_cb_t cb; + gpointer data; +} BinderGprsCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static BinderGprs* binder_gprs_get_data(struct ofono_gprs* ofono) + { return ofono ? ofono_gprs_get_data(ofono) : NULL; } + +static +BinderGprsCbData* +binder_gprs_cbd_new( + BinderGprs* self, + ofono_gprs_cb_t cb, + void* data) +{ + BinderGprsCbData* cbd = g_slice_new0(BinderGprsCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + return cbd; +} +static +void +binder_gprs_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderGprsCbData, cbd); +} + +static +enum ofono_netreg_status +binder_gprs_fix_registration_status( + BinderGprs* self, + enum ofono_netreg_status status) +{ + if (!binder_data_allowed(self->data)) { + return OFONO_NETREG_STATUS_NOT_REGISTERED; + } else { + /* + * TODO: need a way to make sure that SPDI information has + * already been read from the SIM (i.e. sim_spdi_read_cb in + * network.c has been called) + */ + return binder_netreg_check_if_really_roaming(self->watch->netreg, + status); + } +} + +static +void +binder_gprs_data_update_registration_state( + BinderGprs* self) +{ + const enum ofono_netreg_status status = binder_gprs_fix_registration_status + (self, self->network->data.status); + + if (self->reg_status != status) { + ofono_info("data reg changed %d -> %d (%s), attached %d", + self->reg_status, status, ofono_netreg_status_to_string(status), + self->attached); + self->reg_status = status; + ofono_gprs_status_notify(self->gprs, self->reg_status); + } +} + +static +void +binder_gprs_check_data_allowed( + BinderGprs* self) +{ + DBG_(self, "%d %d", binder_data_allowed(self->data), self->attached); + if (!binder_data_allowed(self->data) && self->attached) { + self->attached = FALSE; + if (self->gprs) { + ofono_gprs_detached_notify(self->gprs); + } + } + + binder_gprs_data_update_registration_state(self); +} + +static +gboolean +binder_gprs_set_attached_cb( + gpointer user_data) +{ + BinderGprsCbData* cbd = user_data; + BinderGprs* self = cbd->self; + struct ofono_error err; + + GASSERT(self->set_attached_id); + self->set_attached_id = 0; + binder_gprs_check_data_allowed(self); + cbd->cb(binder_error_ok(&err), cbd->data); + return G_SOURCE_REMOVE; +} + +static +void +binder_gprs_set_attached( + struct ofono_gprs* gprs, + int attached, + ofono_gprs_cb_t cb, + void* data) +{ + BinderGprs* self = binder_gprs_get_data(gprs); + struct ofono_error err; + + if (binder_data_allowed(self->data) || !attached) { + DBG_(self, "attached: %d", attached); + if (self->set_attached_id) { + g_source_remove(self->set_attached_id); + } + self->attached = attached; + self->set_attached_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + binder_gprs_set_attached_cb, binder_gprs_cbd_new(self, cb, data), + binder_gprs_cbd_free); + } else { + DBG_(self, "not allowed to attach"); + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_gprs_allow_data_changed( + BinderData* data, + BINDER_DATA_PROPERTY property, + void* user_data) +{ + BinderGprs* self = user_data; + + DBG_(self, "%d", binder_data_allowed(data)); + if (!self->set_attached_id) { + binder_gprs_check_data_allowed(self); + } +} + +static +void +binder_gprs_max_data_calls_changed( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + void* user_data) +{ + BinderGprs* self = user_data; + + if (net->max_data_calls > 0) { + DBG_(self, "setting max cids to %d", net->max_data_calls); + ofono_gprs_set_cid_range(self->gprs, 1, net->max_data_calls); + } +} + +static +void +binder_gprs_data_registration_state_changed( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + void* user_data) +{ + binder_gprs_data_update_registration_state((BinderGprs*)user_data); +} + +static +void +binder_gprs_registration_status( + struct ofono_gprs* gprs, + ofono_gprs_status_cb_t cb, + void* data) +{ + BinderGprs* self = binder_gprs_get_data(gprs); + struct ofono_error err; + const enum ofono_netreg_status status = self->attached ? + self->reg_status : OFONO_NETREG_STATUS_NOT_REGISTERED; + + DBG("%d (%s)", status, ofono_netreg_status_to_string(status)); + cb(binder_error_ok(&err), status, data); +} + +static +gboolean +binder_gprs_register( + gpointer user_data) +{ + BinderGprs* self = user_data; + BinderNetwork* network = self->network; + struct ofono_gprs* gprs = self->gprs; + + self->init_id = 0; + self->network_event_id[NETWORK_EVENT_DATA_STATE] = + binder_network_add_property_handler(network, + BINDER_NETWORK_PROPERTY_DATA_STATE, + binder_gprs_data_registration_state_changed, self); + self->network_event_id[NETWORK_EVENT_MAX_DATA_CALLS] = + binder_network_add_property_handler(network, + BINDER_NETWORK_PROPERTY_MAX_DATA_CALLS, + binder_gprs_max_data_calls_changed, self); + self->data_event_id = + binder_data_add_property_handler(self->data, + BINDER_DATA_PROPERTY_ALLOWED, + binder_gprs_allow_data_changed, self); + self->reg_status = binder_gprs_fix_registration_status(self, + network->data.status); + + if (network->max_data_calls > 0) { + DBG_(self, "setting max cids to %d", network->max_data_calls); + ofono_gprs_set_cid_range(gprs, 1,network->max_data_calls); + } + + ofono_gprs_register(gprs); + return G_SOURCE_REMOVE; +} + +static +int +binder_gprs_probe( + struct ofono_gprs* gprs, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderGprs* self = g_new0(BinderGprs, 1); + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + DBG_(self, ""); + + self->watch = ofono_watch_new(binder_modem_get_path(modem)); + self->data = binder_data_ref(modem->data); + self->network = binder_network_ref(modem->network); + self->gprs = gprs; + + ofono_gprs_set_data(gprs, self); + self->init_id = g_idle_add(binder_gprs_register, self); + return 0; +} + +static +void +binder_gprs_remove( + struct ofono_gprs* gprs) +{ + BinderGprs* self = binder_gprs_get_data(gprs); + + DBG_(self, ""); + + if (self->set_attached_id) { + g_source_remove(self->set_attached_id); + } + + if (self->init_id) { + g_source_remove(self->init_id); + } + + binder_network_remove_all_handlers(self->network, self->network_event_id); + binder_network_unref(self->network); + + binder_data_remove_handler(self->data, self->data_event_id); + binder_data_unref(self->data); + + ofono_watch_unref(self->watch); + g_free(self->log_prefix); + g_free(self); + + ofono_gprs_set_data(gprs, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_gprs_driver binder_gprs_driver = { + .name = BINDER_DRIVER, + .probe = binder_gprs_probe, + .remove = binder_gprs_remove, + .set_attached = binder_gprs_set_attached, + .attached_status = binder_gprs_registration_status +}; + +void +binder_gprs_init() +{ + ofono_gprs_driver_register(&binder_gprs_driver); +} + +void +binder_gprs_cleanup() +{ + ofono_gprs_driver_unregister(&binder_gprs_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_gprs.h b/src/binder_gprs.h new file mode 100644 index 0000000..edf014d --- /dev/null +++ b/src/binder_gprs.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_GPRS_H +#define BINDER_GPRS_H + +#include "binder_types.h" + +void +binder_gprs_init(void) + BINDER_INTERNAL; + +void +binder_gprs_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_GPRS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_gprs_context.c b/src/binder_gprs_context.c new file mode 100644 index 0000000..7a28cdf --- /dev/null +++ b/src/binder_gprs_context.c @@ -0,0 +1,735 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_data.h" +#include "binder_gprs_context.h" +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_network.h" +#include "binder_netreg.h" +#include "binder_util.h" + +#include +#include +#include + +#include +#include + +#include + +#define CTX_ID_NONE ((unsigned int)(-1)) + +#define MAX_MMS_MTU 1280 + +typedef struct binder_gprs_context_call { + BinderDataRequest* req; + ofono_gprs_context_cb_t cb; + gpointer data; + unsigned int cid; +} BinderGprsContextCall; + +typedef struct binder_gprs_context { + struct ofono_gprs_context* gc; + struct ofono_watch* watch; + struct ofono_mtu_limit* mtu_limit; + BinderNetwork* network; + BinderData* data; + char* log_prefix; + guint active_ctx_cid; + gulong calls_changed_id; + BinderDataCall* active_call; + BinderGprsContextCall activate; + BinderGprsContextCall deactivate; +} BinderGprsContext; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderGprsContext* +binder_gprs_context_get_data(struct ofono_gprs_context *gprs) + { return ofono_gprs_context_get_data(gprs); } + +static +char* +binder_gprs_context_netmask( + const char* bits) +{ + guint nbits; + + if (gutil_parse_uint(bits, 0, &nbits) && nbits < 33) { + const char* str; + struct in_addr in; + + in.s_addr = htonl((nbits == 32) ? 0xffffffff : + ((1u << nbits)-1) << (32-nbits)); + str = inet_ntoa(in); + if (str) { + return g_strdup(str); + } + } + return NULL; +} + +static +int +binder_gprs_context_address_family( + const char* addr) +{ + if (strchr(addr, ':')) { + return AF_INET6; + } else if (strchr(addr, '.')) { + return AF_INET; + } else { + return AF_UNSPEC; + } +} + +static +void +binder_gprs_context_free_active_call( + BinderGprsContext* self) +{ + if (self->active_call) { + binder_data_call_release(self->data, self->active_call->cid, self); + binder_data_call_free(self->active_call); + self->active_call = NULL; + } + if (self->calls_changed_id) { + binder_data_remove_handler(self->data, self->calls_changed_id); + self->calls_changed_id = 0; + } + if (self->mtu_limit) { + ofono_mtu_limit_free(self->mtu_limit); + self->mtu_limit = NULL; + } +} + +static +void +binder_gprs_context_set_active_call( + BinderGprsContext* self, + const BinderDataCall* call) +{ + if (call) { + binder_data_call_free(self->active_call); + self->active_call = binder_data_call_dup(call); + if (ofono_gprs_context_get_type(self->gc) == + OFONO_GPRS_CONTEXT_TYPE_MMS) { + /* + * Some MMS providers have a problem with MTU greater than 1280. + * Let's be safe. + */ + if (!self->mtu_limit) { + self->mtu_limit = ofono_mtu_limit_new(MAX_MMS_MTU); + } + } + ofono_mtu_limit_set_ifname(self->mtu_limit, call->ifname); + binder_data_call_grab(self->data, call->cid, self); + } else { + binder_gprs_context_free_active_call(self); + } +} + +static +void +binder_gprs_context_set_disconnected( + BinderGprsContext* self) +{ + if (self->active_call) { + binder_gprs_context_free_active_call(self); + if (self->deactivate.req) { + BinderGprsContextCall deact = self->deactivate; + + memset(&self->deactivate, 0, sizeof(self->deactivate)); + binder_data_request_cancel(deact.req); + if (deact.cb) { + struct ofono_error err; + + ofono_info("Deactivated data call"); + deact.cb(binder_error_ok(&err), deact.data); + } + } + } + + if (self->active_ctx_cid != CTX_ID_NONE) { + const guint id = self->active_ctx_cid; + + self->active_ctx_cid = CTX_ID_NONE; + DBG_(self, "ofono context %u deactivated", id); + ofono_gprs_context_deactivated(self->gc, id); + } +} + +static +void +binder_gprs_context_set_address( + struct ofono_gprs_context *gc, + const BinderDataCall* call) +{ + const char* ip_addr = NULL; + char* ip_mask = NULL; + const char* ipv6_addr = NULL; + unsigned char ipv6_prefix_length = 0; + char* tmp_ip_addr = NULL; + char* tmp_ipv6_addr = NULL; + char* const* list = call->addresses; + const int n = gutil_strv_length(list); + int i; + + for (i = 0; i < n && (!ipv6_addr || !ip_addr); i++) { + const char* addr = list[i]; + + switch (binder_gprs_context_address_family(addr)) { + case AF_INET: + if (!ip_addr) { + const char* s = strchr(addr, '/'); + + if (s) { + const gsize len = s - addr; + tmp_ip_addr = g_strndup(addr, len); + ip_addr = tmp_ip_addr; + ip_mask = binder_gprs_context_netmask(s+1); + } else { + ip_addr = addr; + } + if (!ip_mask) { + ip_mask = g_strdup("255.255.255.0"); + } + } + break; + case AF_INET6: + if (!ipv6_addr) { + const char* s = strchr(addr, '/'); + + if (s) { + const gsize len = s - addr; + const int prefix = atoi(s + 1); + + tmp_ipv6_addr = g_strndup(addr, len); + ipv6_addr = tmp_ipv6_addr; + if (prefix >= 0 && prefix <= 128) { + ipv6_prefix_length = prefix; + } + } else { + ipv6_addr = addr; + } + } + } + } + + ofono_gprs_context_set_ipv4_address(gc, ip_addr, TRUE); + ofono_gprs_context_set_ipv4_netmask(gc, ip_mask); + ofono_gprs_context_set_ipv6_address(gc, ipv6_addr); + ofono_gprs_context_set_ipv6_prefix_length(gc, ipv6_prefix_length); + + if (!ip_addr && !ipv6_addr) { + ofono_error("GPRS context: No IP address"); + } + + /* Allocate temporary strings */ + g_free(ip_mask); + g_free(tmp_ip_addr); + g_free(tmp_ipv6_addr); +} + +static +void +binder_gprs_context_set_gateway( + struct ofono_gprs_context *gc, + const BinderDataCall* call) +{ + const char* ip_gw = NULL; + const char* ipv6_gw = NULL; + char* const* list = call->gateways; + const int n = gutil_strv_length(list); + int i; + + /* Pick 1 gw for each protocol*/ + for (i = 0; i < n && (!ipv6_gw || !ip_gw); i++) { + const char* addr = list[i]; + switch (binder_gprs_context_address_family(addr)) { + case AF_INET: + if (!ip_gw) ip_gw = addr; + break; + case AF_INET6: + if (!ipv6_gw) ipv6_gw = addr; + break; + } + } + + ofono_gprs_context_set_ipv4_gateway(gc, ip_gw); + ofono_gprs_context_set_ipv6_gateway(gc, ipv6_gw); +} + +typedef +void +(*ofono_gprs_context_list_setter_t)( + struct ofono_gprs_context* gc, + const char** list); + +static +void +binder_gprs_context_set_servers( + struct ofono_gprs_context* gc, + char* const* list, + ofono_gprs_context_list_setter_t set_ipv4, + ofono_gprs_context_list_setter_t set_ipv6) +{ + int i; + const char** ip_list = NULL, ** ip_ptr = NULL; + const char** ipv6_list = NULL, ** ipv6_ptr = NULL; + const int n = gutil_strv_length(list); + + for (i = 0; i < n; i++) { + const char *addr = list[i]; + + switch (binder_gprs_context_address_family(addr)) { + case AF_INET: + if (!ip_ptr) { + ip_list = g_new0(const char *, n - i + 1); + ip_ptr = ip_list; + } + *ip_ptr++ = addr; + break; + case AF_INET6: + if (!ipv6_ptr) { + ipv6_list = g_new0(const char *, n - i + 1); + ipv6_ptr = ipv6_list; + } + *ipv6_ptr++ = addr; + break; + } + } + + set_ipv4(gc, ip_list); + set_ipv6(gc, ipv6_list); + + g_free(ip_list); + g_free(ipv6_list); +} + +static +void +binder_gprs_context_set_dns_servers( + struct ofono_gprs_context* gc, + const BinderDataCall* call) +{ + binder_gprs_context_set_servers(gc, call->dnses, + ofono_gprs_context_set_ipv4_dns_servers, + ofono_gprs_context_set_ipv6_dns_servers); +} + +static +void +binder_gprs_context_set_proxy_cscf( + struct ofono_gprs_context* gc, + const BinderDataCall* call) +{ + binder_gprs_context_set_servers(gc, call->pcscf, + ofono_gprs_context_set_ipv4_proxy_cscf, + ofono_gprs_context_set_ipv6_proxy_cscf); +} + +/* Only compares the stuff that's important to us */ + +#define DATA_CALL_IFNAME_CHANGED (0x01) +#define DATA_CALL_ADDRESS_CHANGED (0x02) +#define DATA_CALL_GATEWAY_CHANGED (0x04) +#define DATA_CALL_DNS_CHANGED (0x08) +#define DATA_CALL_PCSCF_CHANGED (0x10) +#define DATA_CALL_ALL_CHANGED (0x1f) + +static +int +binder_gprs_context_data_call_change( + const BinderDataCall* c1, + const BinderDataCall* c2) +{ + if (c1 == c2) { + return 0; + } else if (c1 && c2) { + int changes = 0; + + if (g_strcmp0(c1->ifname, c2->ifname)) { + changes |= DATA_CALL_IFNAME_CHANGED; + } + + if (!gutil_strv_equal(c1->addresses, c2->addresses)) { + changes |= DATA_CALL_ADDRESS_CHANGED; + } + + if (!gutil_strv_equal(c1->gateways, c2->gateways)) { + changes |= DATA_CALL_GATEWAY_CHANGED; + } + + if (!gutil_strv_equal(c1->dnses, c2->dnses)) { + changes |= DATA_CALL_DNS_CHANGED; + } + + if (!gutil_strv_equal(c1->pcscf, c2->pcscf)) { + changes |= DATA_CALL_PCSCF_CHANGED; + } + + return changes; + } else { + return DATA_CALL_ALL_CHANGED; + } +} + +static +void +binder_gprs_context_call_list_changed( + BinderData* data, + BINDER_DATA_PROPERTY property, + void* arg) +{ + BinderGprsContext* self = arg; + struct ofono_gprs_context* gc = self->gc; + + /* + * self->active_call can't be NULL here because this callback + * is only registered when we have the active call and released + * when active call is dropped. + */ + BinderDataCall* prev_call = self->active_call; + const BinderDataCall* call = binder_data_call_find(data->calls, + prev_call->cid); + int change = 0; + + if (call && call->active != RADIO_DATA_CALL_INACTIVE) { + /* Compare it against the last known state */ + change = binder_gprs_context_data_call_change(call, prev_call); + } else { + ofono_error("Clearing active context"); + binder_gprs_context_set_disconnected(self); + call = NULL; + } + + if (!call) { + /* We are not interested */ + return; + } else if (!change) { + DBG_(self, "call %u didn't change", call->cid); + return; + } else { + DBG_(self, "call %u changed", call->cid); + } + + /* + * prev_call points to the previous active call, and it will + * be deallocated at the end of the this function. Clear the + * self->active_call pointer so that we don't deallocate it twice. + */ + self->active_call = NULL; + binder_gprs_context_set_active_call(self, call); + + if (call->status != RADIO_DATA_CALL_FAIL_NONE) { + ofono_info("data call status: %d", call->status); + } + + if (change & DATA_CALL_IFNAME_CHANGED) { + DBG_(self, "interface changed"); + ofono_gprs_context_set_interface(gc, call->ifname); + } + + if (change & DATA_CALL_ADDRESS_CHANGED) { + DBG_(self, "address changed"); + binder_gprs_context_set_address(gc, call); + } + + if (change & DATA_CALL_GATEWAY_CHANGED) { + DBG_(self, "gateway changed"); + binder_gprs_context_set_gateway(gc, call); + } + + if (change & DATA_CALL_DNS_CHANGED) { + DBG_(self, "name server(s) changed"); + binder_gprs_context_set_dns_servers(gc, call); + } + + if (change & DATA_CALL_PCSCF_CHANGED) { + DBG_(self, "P-CSCF changed"); + binder_gprs_context_set_proxy_cscf(gc, call); + } + + ofono_gprs_context_signal_change(gc, self->active_ctx_cid); + binder_data_call_free(prev_call); +} + +static +void +binder_gprs_context_activate_primary_cb( + BinderData* data, + RADIO_ERROR radio_error, + const BinderDataCall* call, + void* user_data) +{ + BinderGprsContext* self = user_data; + struct ofono_gprs_context* gc = self->gc; + struct ofono_error error; + ofono_gprs_context_cb_t cb; + gpointer cb_data; + + binder_error_init_failure(&error); + if (radio_error != RADIO_ERROR_NONE) { + ofono_error("GPRS context: Reply failure: %s", + binder_radio_error_string(radio_error)); + } else if (!call) { + ofono_error("Unexpected data call failure"); + } else if (call->status != RADIO_DATA_CALL_FAIL_NONE) { + ofono_error("Unexpected data call status %d", call->status); + error.type = OFONO_ERROR_TYPE_CMS; + error.error = call->status; + } else if (!call->ifname) { + /* Must have interface */ + ofono_error("GPRS context: No interface"); + } else { + ofono_info("setting up data call"); + + GASSERT(!self->calls_changed_id); + binder_data_remove_handler(self->data, self->calls_changed_id); + self->calls_changed_id = binder_data_add_property_handler(self->data, + BINDER_DATA_PROPERTY_CALLS, binder_gprs_context_call_list_changed, + self); + + self->active_ctx_cid = self->activate.cid; + binder_gprs_context_set_active_call(self, call); + ofono_gprs_context_set_interface(gc, call->ifname); + binder_gprs_context_set_address(gc, call); + binder_gprs_context_set_gateway(gc, call); + binder_gprs_context_set_dns_servers(gc, call); + binder_gprs_context_set_proxy_cscf(gc, call); + binder_error_init_ok(&error); + } + + cb = self->activate.cb; + cb_data = self->activate.data; + GASSERT(self->activate.req); + memset(&self->activate, 0, sizeof(self->activate)); + + if (cb) { + cb(&error, cb_data); + } +} + +static +void +binder_gprs_context_activate_primary( + struct ofono_gprs_context* gc, + const struct ofono_gprs_primary_context* ctx, + ofono_gprs_context_cb_t cb, + void* data) +{ + BinderGprsContext* self = binder_gprs_context_get_data(gc); + struct ofono_watch* watch = self->watch; + struct ofono_netreg* netreg = watch->netreg; + const enum ofono_netreg_status rs = ofono_netreg_get_status(netreg); + + /* Let's make sure that we aren't connecting when roaming not allowed */ + if (rs == OFONO_NETREG_STATUS_ROAMING) { + struct ofono_gprs* gprs = watch->gprs; + + if (!ofono_gprs_get_roaming_allowed(gprs) && + binder_netreg_check_if_really_roaming(netreg, rs) == + OFONO_NETREG_STATUS_ROAMING) { + struct ofono_error error; + + ofono_info("Can't activate context %u (roaming)", ctx->cid); + cb(binder_error_failure(&error), data); + return; + } + } + + ofono_info("Activating context: %u", ctx->cid); + GASSERT(!self->activate.req); + GASSERT(ctx->cid != CTX_ID_NONE); + + self->activate.cb = cb; + self->activate.data = data; + self->activate.cid = ctx->cid; + self->activate.req = binder_data_call_setup(self->data, ctx, + ofono_gprs_context_get_assigned_type(gc), + binder_gprs_context_activate_primary_cb, self); +} + +static +void +binder_gprs_context_deactivate_primary_cb( + BinderData* data, + RADIO_ERROR radio_error, + void* user_data) +{ + BinderGprsContext* gcd = user_data; + + /* + * Data call list may change before the completion of the deactivate + * request, in that case binder_gprs_context_set_disconnected will be + * invoked and gcd->deactivate.req will be NULL. + */ + if (gcd->deactivate.req) { + ofono_gprs_context_cb_t cb = gcd->deactivate.cb; + gpointer cb_data = gcd->deactivate.data; + + if (radio_error == RADIO_ERROR_NONE) { + GASSERT(gcd->active_call); + ofono_info("Deactivated data call"); + } else { + ofono_error("Deactivate failure: %s", + binder_radio_error_string(radio_error)); + } + + memset(&gcd->deactivate, 0, sizeof(gcd->deactivate)); + if (cb) { + struct ofono_error error; + + binder_gprs_context_free_active_call(gcd); + cb(binder_error_ok(&error), cb_data); + return; + } + } + + /* Make sure we are in the disconnected state */ + binder_gprs_context_set_disconnected(gcd); +} + +static +void +binder_gprs_context_deactivate_primary( + struct ofono_gprs_context* gc, + unsigned int id, + ofono_gprs_context_cb_t cb, + void* data) +{ + BinderGprsContext* self = binder_gprs_context_get_data(gc); + + GASSERT(self->active_ctx_cid == id); + ofono_info("Deactivating context: %u", id); + + if (self->active_call && self->active_ctx_cid == id) { + self->deactivate.cb = cb; + self->deactivate.data = data; + self->deactivate.req = binder_data_call_deactivate(self->data, + self->active_call->cid, binder_gprs_context_deactivate_primary_cb, + self); + } else if (cb) { + struct ofono_error err; + + cb(binder_error_ok(&err), data); + } +} + +static +void +binder_gprs_context_detach_shutdown( + struct ofono_gprs_context* gc, + unsigned int id) +{ + binder_gprs_context_deactivate_primary(gc, id, NULL, NULL); +} + +static +int +binder_gprs_context_probe( + struct ofono_gprs_context* gc, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderGprsContext* self = g_new0(BinderGprsContext, 1); + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + DBG_(self, ""); + + self->gc = gc; + self->watch = ofono_watch_new(binder_modem_get_path(modem)); + self->network = binder_network_ref(modem->network); + self->data = binder_data_ref(modem->data); + self->active_ctx_cid = CTX_ID_NONE; + + ofono_gprs_context_set_data(gc, self); + return 0; +} + +static +void +binder_gprs_context_remove( + struct ofono_gprs_context* gc) +{ + BinderGprsContext* self = binder_gprs_context_get_data(gc); + + DBG_(self, ""); + if (self->activate.req) { + /* + * The core has already completed its pending D-Bus + * request, invoking the completion callback will + * cause libdbus to panic. + */ + binder_data_request_detach(self->activate.req); + binder_data_request_cancel(self->activate.req); + } + + if (self->deactivate.req) { + /* Let it complete but we won't be around to be notified. */ + binder_data_request_detach(self->deactivate.req); + } else if (self->active_call) { + binder_data_call_deactivate(self->data, self->active_call->cid, + NULL, NULL); + } + + binder_data_remove_handler(self->data, self->calls_changed_id); + binder_data_unref(self->data); + binder_network_unref(self->network); + binder_data_call_free(self->active_call); + ofono_mtu_limit_free(self->mtu_limit); + ofono_watch_unref(self->watch); + + g_free(self->log_prefix); + g_free(self); + + ofono_gprs_context_set_data(gc, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_gprs_context_driver binder_gprs_context_driver = { + .name = BINDER_DRIVER, + .probe = binder_gprs_context_probe, + .remove = binder_gprs_context_remove, + .activate_primary = binder_gprs_context_activate_primary, + .deactivate_primary = binder_gprs_context_deactivate_primary, + .detach_shutdown = binder_gprs_context_detach_shutdown +}; + +void +binder_gprs_context_init() +{ + ofono_gprs_context_driver_register(&binder_gprs_context_driver); +} + +void +binder_gprs_context_cleanup() +{ + ofono_gprs_context_driver_unregister(&binder_gprs_context_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_gprs_context.h b/src/binder_gprs_context.h new file mode 100644 index 0000000..efeeba5 --- /dev/null +++ b/src/binder_gprs_context.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_GPRS_CONTEXT_H +#define BINDER_GPRS_CONTEXT_H + +#include "binder_types.h" + +void +binder_gprs_context_init(void) + BINDER_INTERNAL; + +void +binder_gprs_context_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_GPRS_CONTEXT_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_log.h b/src/binder_log.h new file mode 100644 index 0000000..5a98386 --- /dev/null +++ b/src/binder_log.h @@ -0,0 +1,31 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_LOG_H +#define BINDER_LOG_H + +#define GLOG_MODULE_NAME binder_plugin_log +#include +#include + +#endif /* BINDER_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_logger.c b/src/binder_logger.c new file mode 100644 index 0000000..e1c4357 --- /dev/null +++ b/src/binder_logger.c @@ -0,0 +1,553 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_logger.h" +#include "binder_util.h" + +#include +#include +#include + +#include +#include +#include + +#include + +enum binder_logger_events { + EVENT_REQ, + EVENT_RESP, + EVENT_IND, + EVENT_ACK, + EVENT_COUNT +}; + +typedef struct binder_logger_callbacks { + const char* (*req_name)(guint32 code); + const char* (*resp_name)(guint32 code); + const char* (*ind_name)(guint32 code); + gsize (*rpc_header_size)(gpointer object, guint32 code); + void (*drop_object)(BinderLogger* logger); +} BinderLoggerCallbacks; + +struct binder_logger { + const BinderLoggerCallbacks* cb; + gpointer object; + gulong event_id[EVENT_COUNT]; + char* prefix; +}; + +#define CONFIG_PREFIX "config" + +GLogModule binder_logger_module = { + .max_level = GLOG_LEVEL_VERBOSE, + .level = GLOG_LEVEL_VERBOSE, + .flags = GLOG_FLAG_HIDE_NAME +}; + +static GLogModule binder_logger_dump_module = { + .parent = &binder_logger_module, + .max_level = GLOG_LEVEL_VERBOSE, + .level = GLOG_LEVEL_INHERIT, + .flags = GLOG_FLAG_HIDE_NAME +}; + +static +void +binder_logger_trace_req( + BinderLogger* logger, + guint code, + GBinderLocalRequest* args) +{ + const BinderLoggerCallbacks* cb = logger->cb; + static const GLogModule* log = &binder_logger_module; + const gsize header_size = cb->rpc_header_size(logger->object, code); + const char* name = cb->req_name(code); + GBinderWriter writer; + const guint8* data; + guint32 serial; + gsize size; + + /* Use writer API to fetch the raw data and extract the serial */ + gbinder_local_request_init_writer(args, &writer); + data = gbinder_writer_get_data(&writer, &size); + serial = (size >= header_size + 4) ? *(guint32*)(data + header_size) : 0; + + if (serial) { + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s< [%08x] %u %s", + logger->prefix, serial, code, name ? name : ""); + } else { + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s< %u %s", + logger->prefix, code, name ? name : ""); + } +} + +static +void +binder_logger_trace_resp( + BinderLogger* logger, + guint code, + const RadioResponseInfo* info, + const GBinderReader* args) +{ + static const GLogModule* log = &binder_logger_module; + const BinderLoggerCallbacks* cb = logger->cb; + const char* name = cb->resp_name(code); + const char* error = (info->error == RADIO_ERROR_NONE) ? NULL : + binder_radio_error_string(info->error); + const char* arg1 = name ? name : error; + const char* arg2 = name ? error : NULL; + + if (arg2) { + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s> [%08x] %u %s %s", + logger->prefix, info->serial, code, arg1, arg2); + } else if (arg1) { + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s> [%08x] %u %s", + logger->prefix, info->serial, code, arg1); + } else { + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s> [%08x] %u", + logger->prefix, info->serial, code); + } +} + +static +void +binder_logger_trace_ind( + BinderLogger* logger, + RADIO_IND code, + const GBinderReader* args) +{ + const BinderLoggerCallbacks* cb = logger->cb; + const char* name = cb->ind_name(code); + static const GLogModule* log = &binder_logger_module; + + gutil_log(log, GLOG_LEVEL_VERBOSE, "%s> %u %s", + logger->prefix, code, name ? name : ""); +} + +static +void +binder_logger_dump_req( + GBinderLocalRequest* args) +{ + GBinderWriter writer; + const guint8* data; + gsize size; + + /* Use writer API to fetch the raw data */ + gbinder_local_request_init_writer(args, &writer); + data = gbinder_writer_get_data(&writer, &size); + gutil_log_dump(&binder_logger_dump_module, GLOG_LEVEL_VERBOSE, " ", + data, size); +} + +static +void +binder_logger_dump_reader( + const GBinderReader* reader) +{ + gsize size; + const guint8* data = gbinder_reader_get_data(reader, &size); + + gutil_log_dump(&binder_logger_dump_module, GLOG_LEVEL_VERBOSE, " ", + data, size); +} + +/*==========================================================================* + * RadioInstance implementation + *==========================================================================*/ + +static +void +binder_logger_radio_trace_req_cb( + RadioInstance* radio, + RADIO_REQ code, + GBinderLocalRequest* args, + gpointer user_data) +{ + binder_logger_trace_req((BinderLogger*)user_data, code, args); +} + +static +void +binder_logger_radio_trace_resp_cb( + RadioInstance* radio, + RADIO_RESP code, + const RadioResponseInfo* info, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_trace_resp((BinderLogger*)user_data, code, info, args); +} + +static +void +binder_logger_radio_trace_ind_cb( + RadioInstance* radio, + RADIO_IND code, + RADIO_IND_TYPE type, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_trace_ind((BinderLogger*)user_data, code, args); +} + +static +void +binder_logger_radio_trace_ack_cb( + RadioInstance* radio, + guint32 serial, + gpointer user_data) +{ + BinderLogger* logger = user_data; + + gutil_log(&binder_logger_module, GLOG_LEVEL_VERBOSE, "%s> [%08x] " + "acknowledgeRequest", logger->prefix, serial); +} + +static +void +binder_logger_radio_dump_req_cb( + RadioInstance* radio, + RADIO_REQ code, + GBinderLocalRequest* args, + gpointer user_data) +{ + binder_logger_dump_req(args); +} + +static +void +binder_logger_radio_dump_resp_cb( + RadioInstance* radio, + RADIO_RESP code, + const RadioResponseInfo* info, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_dump_reader(args); +} + +static +void +binder_logger_radio_dump_ind_cb( + RadioInstance* radio, + RADIO_IND code, + RADIO_IND_TYPE type, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_dump_reader(args); +} + +static +const char* +binder_logger_radio_req_name( + guint32 code) +{ + return radio_req_name(code); +} + +static +const char* +binder_logger_radio_resp_name( + guint32 code) +{ + return radio_resp_name(code); +} + +static +const char* +binder_logger_radio_ind_name( + guint32 code) +{ + return radio_ind_name(code); +} + +static +gsize +binder_logger_radio_rpc_header_size( + gpointer object, + guint32 code) +{ + return radio_instance_rpc_header_size(object, code); +} + +static +void +binder_logger_radio_drop_object( + BinderLogger* logger) +{ + radio_instance_remove_all_handlers(logger->object, logger->event_id); + radio_instance_unref(logger->object); +} + +static +BinderLogger* +binder_logger_radio_new( + RadioInstance* radio, + const char* prefix, + RADIO_INSTANCE_PRIORITY pri, + RadioRequestObserverFunc req_cb, + RadioResponseObserverFunc resp_cb, + RadioIndicationObserverFunc ind_cb, + RadioAckFunc ack_cb) +{ + static BinderLoggerCallbacks binder_logger_radio_callbacks = { + .req_name = binder_logger_radio_req_name, + .resp_name = binder_logger_radio_resp_name, + .ind_name = binder_logger_radio_ind_name, + .rpc_header_size = binder_logger_radio_rpc_header_size, + .drop_object = binder_logger_radio_drop_object + }; + + if (radio) { + BinderLogger* logger = g_new0(BinderLogger, 1); + + logger->cb = &binder_logger_radio_callbacks; + logger->prefix = binder_dup_prefix(prefix); + logger->object = radio_instance_ref(radio); + logger->event_id[EVENT_REQ] = + radio_instance_add_request_observer_with_priority(radio, pri, + RADIO_REQ_ANY, req_cb, logger); + logger->event_id[EVENT_RESP] = + radio_instance_add_response_observer_with_priority(radio, pri, + RADIO_RESP_ANY, resp_cb, logger); + logger->event_id[EVENT_IND] = + radio_instance_add_indication_observer_with_priority(radio, pri, + RADIO_IND_ANY, ind_cb, logger); + logger->event_id[EVENT_ACK] = radio_instance_add_ack_handler(radio, + ack_cb, logger); + return logger; + } else { + return NULL; + } +} + +/*==========================================================================* + * RadioConfig implementation + *==========================================================================*/ + +static +void +binder_logger_config_trace_req_cb( + RadioConfig* config, + RADIO_CONFIG_REQ code, + GBinderLocalRequest* args, + gpointer user_data) +{ + binder_logger_trace_req((BinderLogger*)user_data, code, args); +} + +static +void +binder_logger_config_trace_resp_cb( + RadioConfig* config, + RADIO_CONFIG_RESP code, + const RadioResponseInfo* info, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_trace_resp((BinderLogger*)user_data, code, info, args); +} + +static +void +binder_logger_config_trace_ind_cb( + RadioConfig* config, + RADIO_CONFIG_IND code, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_trace_ind((BinderLogger*)user_data, code, args); +} + +static +void +binder_logger_config_dump_req_cb( + RadioConfig* config, + RADIO_CONFIG_REQ code, + GBinderLocalRequest* args, + gpointer user_data) +{ + binder_logger_dump_req(args); +} + +static +void +binder_logger_config_dump_resp_cb( + RadioConfig* config, + RADIO_CONFIG_RESP code, + const RadioResponseInfo* info, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_dump_reader(args); +} + +static +void +binder_logger_config_dump_ind_cb( + RadioConfig* config, + RADIO_CONFIG_IND code, + const GBinderReader* args, + gpointer user_data) +{ + binder_logger_dump_reader(args); +} + +static +const char* +binder_logger_config_req_name( + guint32 code) +{ + return radio_config_req_name(NULL, code); +} + +static +const char* +binder_logger_config_resp_name( + guint32 code) +{ + return radio_config_resp_name(NULL, code); +} + +static +const char* +binder_logger_config_ind_name( + guint32 code) +{ + return radio_config_ind_name(NULL, code); +} + +static +gsize +binder_logger_config_rpc_header_size( + gpointer object, + guint32 code) +{ + return radio_config_rpc_header_size(object, code); +} + +static +void +binder_logger_config_drop_object( + BinderLogger* logger) +{ + radio_config_remove_all_handlers(logger->object, logger->event_id); + radio_config_unref(logger->object); +} + +static +BinderLogger* +binder_logger_config_new( + RadioConfig* config, + const char* prefix, + RADIO_INSTANCE_PRIORITY pri, + RadioConfigRequestObserverFunc req_cb, + RadioConfigResponseObserverFunc resp_cb, + RadioConfigIndicationObserverFunc ind_cb) +{ + static BinderLoggerCallbacks binder_logger_config_callbacks = { + .req_name = binder_logger_config_req_name, + .resp_name = binder_logger_config_resp_name, + .ind_name = binder_logger_config_ind_name, + .rpc_header_size = binder_logger_config_rpc_header_size, + .drop_object = binder_logger_config_drop_object + }; + + if (config) { + BinderLogger* logger = g_new0(BinderLogger, 1); + + logger->cb = &binder_logger_config_callbacks; + logger->prefix = binder_dup_prefix(prefix); + logger->object = radio_config_ref(config); + logger->event_id[EVENT_REQ] = + radio_config_add_request_observer_with_priority(config, pri, + RADIO_REQ_ANY, req_cb, logger); + logger->event_id[EVENT_RESP] = + radio_config_add_response_observer_with_priority(config, pri, + RADIO_RESP_ANY, resp_cb, logger); + logger->event_id[EVENT_IND] = + radio_config_add_indication_observer_with_priority(config, pri, + RADIO_IND_ANY, ind_cb, logger); + return logger; + } else { + return NULL; + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderLogger* +binder_logger_new_radio_trace( + RadioInstance* radio, + const char* prefix) +{ + return binder_logger_radio_new(radio, prefix, + RADIO_INSTANCE_PRIORITY_HIGHEST, binder_logger_radio_trace_req_cb, + binder_logger_radio_trace_resp_cb, binder_logger_radio_trace_ind_cb, + binder_logger_radio_trace_ack_cb); +} + +BinderLogger* +binder_logger_new_radio_dump( + RadioInstance* radio, + const char* prefix) +{ + return binder_logger_radio_new(radio, prefix, + RADIO_INSTANCE_PRIORITY_HIGHEST - 1, binder_logger_radio_dump_req_cb, + binder_logger_radio_dump_resp_cb, binder_logger_radio_dump_ind_cb, + NULL); +} + +BinderLogger* +binder_logger_new_config_trace( + RadioConfig* config) +{ + return binder_logger_config_new(config, CONFIG_PREFIX, + RADIO_INSTANCE_PRIORITY_HIGHEST, binder_logger_config_trace_req_cb, + binder_logger_config_trace_resp_cb, binder_logger_config_trace_ind_cb); +} + +BinderLogger* +binder_logger_new_config_dump( + RadioConfig* config) +{ + return binder_logger_config_new(config, CONFIG_PREFIX, + RADIO_INSTANCE_PRIORITY_HIGHEST - 1, binder_logger_config_dump_req_cb, + binder_logger_config_dump_resp_cb, binder_logger_config_dump_ind_cb); +} + +void +binder_logger_free( + BinderLogger* logger) +{ + if (logger) { + logger->cb->drop_object(logger); + g_free(logger->prefix); + g_free(logger); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_logger.h b/src/binder_logger.h new file mode 100644 index 0000000..3cc9656 --- /dev/null +++ b/src/binder_logger.h @@ -0,0 +1,58 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_LOGGER_H +#define BINDER_LOGGER_H + +#include "binder_types.h" + +BinderLogger* +binder_logger_new_radio_trace( + RadioInstance* instance, + const char* prefix) + BINDER_INTERNAL; + +BinderLogger* +binder_logger_new_radio_dump( + RadioInstance* instance, + const char* prefix) + BINDER_INTERNAL; + +BinderLogger* +binder_logger_new_config_trace( + RadioConfig* config) + BINDER_INTERNAL; + +BinderLogger* +binder_logger_new_config_dump( + RadioConfig* config) + BINDER_INTERNAL; + +void +binder_logger_free( + BinderLogger* logger) + BINDER_INTERNAL; + +extern GLogModule binder_logger_module BINDER_INTERNAL; + +#endif /* BINDER_LOGGER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_modem.c b/src/binder_modem.c new file mode 100644 index 0000000..63fb671 --- /dev/null +++ b/src/binder_modem.c @@ -0,0 +1,646 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_modem.h" +#include "binder_network.h" +#include "binder_radio.h" +#include "binder_sim_card.h" +#include "binder_sim_settings.h" +#include "binder_cell_info.h" +#include "binder_data.h" +#include "binder_util.h" +#include "binder_log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#define ONLINE_TIMEOUT_SECS (15) /* 20 sec is hardcoded in ofono core */ + +typedef enum binder_modem_power_state { + POWERED_OFF, + POWERED_ON, + POWERING_OFF +} BINDER_MODEM_POWER_STATE; + +enum binder_modem_watch_event { + WATCH_IMSI, + WATCH_ICCID, + WATCH_SIM_STATE, + WATCH_EVENT_COUNT +}; + +typedef struct binder_modem_priv BinderModemPriv; + +typedef struct binder_modem_online_request { + const char* name; + BinderModemPriv* self; + ofono_modem_online_cb_t cb; + void* data; + guint timeout_id; +} BinderModemOnlineRequest; + +struct binder_modem_priv { + BinderModem pub; + RadioRequestGroup* g; + char* log_prefix; + char* imeisv; + char* imei; + + gulong watch_event_id[WATCH_EVENT_COUNT]; + char* last_known_iccid; + char* reset_iccid; + + guint online_check_id; + BINDER_MODEM_POWER_STATE power_state; + gulong radio_state_event_id; + + BinderModemOnlineRequest set_online; + BinderModemOnlineRequest set_offline; +}; + +#define RADIO_POWER_TAG(md) (md) + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static BinderModemPriv* binder_modem_cast(BinderModem* modem) + { return G_CAST(modem, BinderModemPriv, pub); } +static BinderModemPriv* binder_modem_get_priv(struct ofono_modem* ofono) + { return binder_modem_cast(binder_modem_get_data(ofono)); } + +static +void +binder_modem_online_request_done( + BinderModemOnlineRequest* req) +{ + if (req->cb) { + struct ofono_error error; + ofono_modem_online_cb_t cb = req->cb; + void* data = req->data; + + req->cb = NULL; + req->data = NULL; + DBG_(req->self, "%s", req->name); + cb(binder_error_ok(&error), data); + } +} + +static +void +binder_modem_online_request_ok( + BinderModemOnlineRequest* req) +{ + if (req->timeout_id) { + g_source_remove(req->timeout_id); + req->timeout_id = 0; + } + binder_modem_online_request_done(req); +} + +static +void +binder_modem_update_online_state( + BinderModemPriv* self) +{ + switch (self->pub.radio->state) { + case RADIO_STATE_ON: + DBG_(self, "online"); + binder_modem_online_request_ok(&self->set_online); + break; + + case RADIO_STATE_OFF: + case RADIO_STATE_UNAVAILABLE: + DBG_(self, "offline"); + binder_modem_online_request_ok(&self->set_offline); + break; + } + + if (!self->set_offline.timeout_id && + !self->set_online.timeout_id && + self->power_state == POWERING_OFF) { + self->power_state = POWERED_OFF; + struct ofono_modem* ofono = self->pub.ofono; + + if (ofono) { + ofono_modem_set_powered(ofono, FALSE); + } + } +} + +static +gboolean +binder_modem_online_request_timeout( + gpointer data) +{ + BinderModemOnlineRequest* req = data; + + GASSERT(req->timeout_id); + req->timeout_id = 0; + DBG_(req->self, "%s", req->name); + binder_modem_online_request_done(req); + binder_modem_update_online_state(req->self); + return G_SOURCE_REMOVE; +} + +static +gboolean +binder_modem_online_check( + gpointer data) +{ + BinderModemPriv* self = data; + + GASSERT(self->online_check_id); + self->online_check_id = 0; + binder_modem_update_online_state(self); + return G_SOURCE_REMOVE; +} + +static +void +binder_modem_schedule_online_check( + BinderModemPriv* self) +{ + if (!self->online_check_id) { + self->online_check_id = g_idle_add(binder_modem_online_check, self); + } +} + +static +void +binder_modem_update_radio_settings( + BinderModemPriv* self) +{ + BinderModem* modem = &self->pub; + struct ofono_watch* watch = modem->watch; + struct ofono_radio_settings* rs = + ofono_modem_get_radio_settings(modem->ofono); + + if (watch->imsi) { + /* radio-settings.c assumes that IMSI is available */ + if ((modem->config.features & BINDER_FEATURE_RADIO_SETTINGS) && !rs) { + DBG_(self, "initializing radio settings interface"); + ofono_radio_settings_create(modem->ofono, 0, BINDER_DRIVER, + modem->ofono); + } + } else if (rs) { + DBG_(self, "removing radio settings interface"); + ofono_radio_settings_remove(rs); + } +} + +static +void +binder_modem_radio_state_cb( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* data) +{ + binder_modem_update_online_state((BinderModemPriv*)data); +} + +static +void +binder_modem_imsi_cb( + struct ofono_watch* watch, + void* data) +{ + binder_modem_update_radio_settings((BinderModemPriv*)data); +} + +static +void +binder_modem_iccid_cb( + struct ofono_watch* watch, + void* data) +{ + BinderModemPriv* self = data; + + if (watch->iccid) { + g_free(self->last_known_iccid); + self->last_known_iccid = g_strdup(watch->iccid); + DBG_(self, "%s", self->last_known_iccid); + } +} + +static +void +binder_modem_sim_state_cb( + struct ofono_watch* watch, + void* data) +{ + BinderModemPriv* self = data; + const enum ofono_sim_state state = ofono_sim_get_state(watch->sim); + + if (state == OFONO_SIM_STATE_RESETTING) { + g_free(self->reset_iccid); + self->reset_iccid = self->last_known_iccid; + self->last_known_iccid = NULL; + DBG_(self, "%s is resetting", self->reset_iccid); + } +} + +static +void +binder_modem_pre_sim( + struct ofono_modem* ofono) +{ + BinderModem* modem = binder_modem_get_data(ofono); + BinderModemPriv* self = binder_modem_cast(modem); + const BINDER_FEATURE_MASK features = modem->config.features; + + DBG_(self, ""); + ofono_devinfo_create(ofono, 0, BINDER_DRIVER, ofono); + ofono_sim_create(ofono, 0, BINDER_DRIVER, ofono); + if (features & BINDER_FEATURE_VOICE) { + ofono_voicecall_create(ofono, 0, BINDER_DRIVER, ofono); + } + if (!self->radio_state_event_id) { + self->radio_state_event_id = + binder_radio_add_property_handler(modem->radio, + BINDER_RADIO_PROPERTY_STATE, + binder_modem_radio_state_cb, self); + } +} + +static +void +binder_modem_post_sim( + struct ofono_modem* ofono) +{ + BinderModem* modem = binder_modem_get_data(ofono); + BinderModemPriv* self = binder_modem_cast(modem); + const BINDER_FEATURE_MASK features = modem->config.features; + struct ofono_gprs* gprs; + + DBG_(self, ""); + ofono_call_forwarding_create(ofono, 0, BINDER_DRIVER, ofono); + ofono_call_barring_create(ofono, 0, BINDER_DRIVER, ofono); + ofono_message_waiting_register(ofono_message_waiting_create(ofono)); + if (features & BINDER_FEATURE_SMS) { + ofono_sms_create(ofono, 0, BINDER_DRIVER, ofono); + } + if (features & BINDER_FEATURE_DATA) { + gprs = ofono_gprs_create(ofono, 0, BINDER_DRIVER, ofono); + if (gprs) { + guint i; + static const enum ofono_gprs_context_type ap_types[] = { + OFONO_GPRS_CONTEXT_TYPE_INTERNET, + OFONO_GPRS_CONTEXT_TYPE_MMS, + OFONO_GPRS_CONTEXT_TYPE_IMS + }; + + /* Create a context for each type */ + for (i = 0; i < G_N_ELEMENTS(ap_types); i++) { + struct ofono_gprs_context* gc = + ofono_gprs_context_create(ofono, 0, BINDER_DRIVER, ofono); + + if (gc == NULL) { + break; + } else { + ofono_gprs_context_set_type(gc, ap_types[i]); + ofono_gprs_add_context(gprs, gc); + } + } + } + } + if (features & BINDER_FEATURE_PHONEBOOK) { + ofono_phonebook_create(ofono, 0, "generic", ofono); + } + if (features & BINDER_FEATURE_STK) { + struct ofono_watch* watch = modem->watch; + + if (!self->reset_iccid || g_strcmp0(self->reset_iccid, watch->iccid)) { + /* This SIM was never reset */ + ofono_stk_create(ofono, 0, BINDER_DRIVER, ofono); + } else { + ofono_warn("Disabling STK after SIM reset"); + } + } + if (features & BINDER_FEATURE_CBS) { + ofono_cbs_create(ofono, 0, BINDER_DRIVER, ofono); + } + if (features & BINDER_FEATURE_SIM_AUTH) { + ofono_sim_auth_create(ofono); + } +} + +static +void +binder_modem_post_online( + struct ofono_modem* ofono) +{ + BinderModem* modem = binder_modem_get_data(ofono); + const BINDER_FEATURE_MASK features = modem->config.features; + + DBG_(binder_modem_cast(modem),""); + ofono_call_volume_create(ofono, 0, BINDER_DRIVER, ofono); + ofono_call_settings_create(ofono, 0, BINDER_DRIVER, ofono); + if (features & BINDER_FEATURE_NETREG) { + ofono_netreg_create(ofono, 0, BINDER_DRIVER, ofono); + } + if (features & BINDER_FEATURE_USSD) { + ofono_ussd_create(ofono, 0, BINDER_DRIVER, ofono); + } + ofono_netmon_create(ofono, 0, "cellinfo", ofono); +} + +static +void +binder_modem_set_online( + struct ofono_modem* ofono, + ofono_bool_t online, + ofono_modem_online_cb_t cb, + void* data) +{ + BinderModem* modem = binder_modem_get_data(ofono); + BinderModemPriv* self = binder_modem_cast(modem); + struct binder_radio* radio = modem->radio; + BinderModemOnlineRequest* req; + + DBG_(self, "going %sline", online ? "on" : "off"); + + binder_radio_set_online(radio, online); + if (online) { + binder_radio_power_on(radio, RADIO_POWER_TAG(self)); + req = &self->set_online; + } else { + binder_radio_power_off(radio, RADIO_POWER_TAG(self)); + req = &self->set_offline; + } + + req->cb = cb; + req->data = data; + if (req->timeout_id) { + g_source_remove(req->timeout_id); + } + req->timeout_id = g_timeout_add_seconds(ONLINE_TIMEOUT_SECS, + binder_modem_online_request_timeout, req); + binder_modem_schedule_online_check(self); +} + +static +int +binder_modem_enable( + struct ofono_modem* ofono) +{ + BinderModemPriv* self = binder_modem_get_priv(ofono); + + DBG_(self, ""); + self->power_state = POWERED_ON; + return 0; +} + +static +int +binder_modem_disable( + struct ofono_modem* ofono) +{ + BinderModemPriv* self = binder_modem_get_priv(ofono); + + DBG_(self, ""); + if (self->set_online.timeout_id || self->set_offline.timeout_id) { + self->power_state = POWERING_OFF; + return -EINPROGRESS; + } else { + self->power_state = POWERED_OFF; + return 0; + } +} + +static +int +binder_modem_probe( + struct ofono_modem* ofono) +{ + DBG("%s", ofono_modem_get_path(ofono)); + return 0; +} + +static +void +binder_modem_remove( + struct ofono_modem* ofono) +{ + BinderModem* modem = binder_modem_get_data(ofono); + BinderModemPriv* self = binder_modem_cast(modem); + + DBG_(self, ""); + ofono_modem_set_data(ofono, NULL); + + binder_radio_remove_handler(modem->radio, self->radio_state_event_id); + binder_radio_set_online(modem->radio, FALSE); + binder_radio_power_off(modem->radio, RADIO_POWER_TAG(self)); + binder_radio_set_online(modem->radio, FALSE); + binder_radio_unref(modem->radio); + binder_sim_settings_unref(modem->sim_settings); + + ofono_watch_remove_all_handlers(modem->watch, self->watch_event_id); + ofono_watch_unref(modem->watch); + + if (self->online_check_id) { + g_source_remove(self->online_check_id); + } + if (self->set_online.timeout_id) { + g_source_remove(self->set_online.timeout_id); + } + if (self->set_offline.timeout_id) { + g_source_remove(self->set_offline.timeout_id); + } + + binder_network_unref(modem->network); + binder_sim_card_unref(modem->sim_card); + binder_data_unref(modem->data); + ofono_cell_info_unref(modem->cell_info); + + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + radio_client_unref(modem->client); + + g_free(self->last_known_iccid); + g_free(self->reset_iccid); + g_free(self->log_prefix); + g_free(self->imeisv); + g_free(self->imei); + g_free(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_modem_driver binder_modem_driver = { + .name = BINDER_DRIVER, + .probe = binder_modem_probe, + .remove = binder_modem_remove, + .enable = binder_modem_enable, + .disable = binder_modem_disable, + .pre_sim = binder_modem_pre_sim, + .post_sim = binder_modem_post_sim, + .post_online = binder_modem_post_online, + .set_online = binder_modem_set_online +}; + +void +binder_modem_init() +{ + ofono_modem_driver_register(&binder_modem_driver); +} + +void +binder_modem_cleanup() +{ + ofono_modem_driver_unregister(&binder_modem_driver); +} + +BinderModem* +binder_modem_create( + RadioClient* client, + const char* log_prefix, + const char* path, + const char* imei, + const char* imeisv, + const BinderSlotConfig* config, + BinderRadio* radio, + BinderNetwork* network, + BinderSimCard* card, + BinderData* data, + BinderSimSettings* settings, + struct ofono_cell_info* cell_info) +{ + /* Skip the slash from the path, it looks like "/ril_0" */ + struct ofono_modem* ofono = ofono_modem_create(path + 1, BINDER_DRIVER); + + if (ofono) { + int err; + BinderModemPriv* self = g_new0(BinderModemPriv, 1); + BinderModem* modem = &self->pub; + + /* + * binder_plugin.c must wait until IMEI becomes known before + * creating the modem + */ + GASSERT(imei); + + /* Copy config */ + modem->config = *config; + modem->imei = self->imei = g_strdup(imei); + modem->imeisv = self->imeisv = g_strdup(imeisv); + modem->log_prefix = self->log_prefix = binder_dup_prefix(log_prefix); + modem->ofono = ofono; + modem->radio = binder_radio_ref(radio); + modem->network = binder_network_ref(network); + modem->sim_card = binder_sim_card_ref(card); + modem->sim_settings = binder_sim_settings_ref(settings); + modem->cell_info = ofono_cell_info_ref(cell_info); + modem->data = binder_data_ref(data); + modem->watch = ofono_watch_new(path); + modem->client = radio_client_ref(client); + self->g = radio_request_group_new(client); + self->last_known_iccid = g_strdup(modem->watch->iccid); + + self->watch_event_id[WATCH_IMSI] = + ofono_watch_add_imsi_changed_handler(modem->watch, + binder_modem_imsi_cb, self); + self->watch_event_id[WATCH_ICCID] = + ofono_watch_add_iccid_changed_handler(modem->watch, + binder_modem_iccid_cb, self); + self->watch_event_id[WATCH_SIM_STATE] = + ofono_watch_add_sim_state_changed_handler(modem->watch, + binder_modem_sim_state_cb, self); + + self->set_online.name = "online"; + self->set_online.self = self; + self->set_offline.name = "offline"; + self->set_offline.self = self; + ofono_modem_set_data(ofono, self); + err = ofono_modem_register(ofono); + if (!err) { + if (config->radio_power_cycle) { + binder_radio_power_cycle(modem->radio); + } + + /* + * ofono_modem_reset() sets Powered to TRUE without + * issuing PropertyChange signal. + */ + ofono_modem_set_powered(modem->ofono, FALSE); + ofono_modem_set_powered(modem->ofono, TRUE); + self->power_state = POWERED_ON; + + /* + * With some implementations, querying available band modes + * causes some magic Android properties to appear. That's + * the only reason for making this call. + */ + if (config->query_available_band_mode) { + /* oneway getAvailableBandModes(int32 serial); */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_AVAILABLE_BAND_MODES, NULL, + NULL, NULL, NULL); + + radio_request_submit(req); + radio_request_unref(req); + } + + binder_modem_update_radio_settings(self); + return modem; + } else { + ofono_error("Error %d registering %s", err, BINDER_DRIVER); + + /* + * If ofono_modem_register() failed, then + * ofono_modem_remove() won't invoke + * binder_modem_remove() callback. + */ + binder_modem_remove(ofono); + } + + ofono_modem_remove(ofono); + } + + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_modem.h b/src/binder_modem.h new file mode 100644 index 0000000..334ce55 --- /dev/null +++ b/src/binder_modem.h @@ -0,0 +1,75 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_MODEM_H +#define BINDER_MODEM_H + +#include "binder_types.h" + +#include + +struct binder_modem { + RadioClient* client; + const char* path; + const char* log_prefix; + const char* imei; + const char* imeisv; + struct ofono_modem* ofono; + struct ofono_cell_info* cell_info; + struct ofono_watch* watch; + BinderData* data; + BinderNetwork* network; + BinderRadio* radio; + BinderSimCard* sim_card; + BinderSimSettings* sim_settings; + BinderSlotConfig config; +}; + +#define binder_modem_get_path(modem) ofono_modem_get_path((modem)->ofono) +#define binder_modem_get_data(modem) ((BinderModem*)ofono_modem_get_data(modem)) + +void +binder_modem_init(void) + BINDER_INTERNAL; + +void +binder_modem_cleanup(void) + BINDER_INTERNAL; + +BinderModem* +binder_modem_create( + RadioClient* client, + const char* name, + const char* path, + const char* imei, + const char* imeisv, + const BinderSlotConfig* config, + BinderRadio* radio, + BinderNetwork* network, + BinderSimCard* card, + BinderData* data, + BinderSimSettings* settings, + struct ofono_cell_info* cell_info) + BINDER_INTERNAL; + +#endif /* BINDER_MODEM_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_netreg.c b/src/binder_netreg.c new file mode 100644 index 0000000..ef926e9 --- /dev/null +++ b/src/binder_netreg.c @@ -0,0 +1,1099 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_modem.h" +#include "binder_netreg.h" +#include "binder_network.h" +#include "binder_util.h" +#include "binder_log.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define REGISTRATION_MAX_RETRIES (2) +#define OPERATOR_LIST_TIMEOUT_MS (300000) /* 5 min */ + +enum binder_netreg_radio_ind { + IND_NITZ_TIME_RECEIVED, + IND_SIGNAL_STRENGTH, + IND_SIGNAL_STRENGTH_1_2, + IND_SIGNAL_STRENGTH_1_4, + IND_COUNT +}; + +enum binder_netreg_network_events { + NETREG_NETWORK_EVENT_OPERATOR_CHANGED, + NETREG_NETWORK_EVENT_VOICE_STATE_CHANGED, + NETREG_NETWORK_EVENT_COUNT +}; + +typedef struct binder_netreg { + RadioClient* client; + struct ofono_watch* watch; + struct ofono_netreg* netreg; + BinderNetwork* network; + gboolean replace_strange_oper; + int signal_strength_dbm_weak; + int signal_strength_dbm_strong; + int network_selection_timeout_ms; + RadioRequest* list_req; + RadioRequest* register_req; + RadioRequest* strength_req; + char* log_prefix; + guint init_id; + guint notify_id; + guint current_operator_id; + gulong ind_id[IND_COUNT]; + gulong network_event_id[NETREG_NETWORK_EVENT_COUNT]; +} BinderNetReg; + +typedef struct binder_netreg_cbd { + BinderNetReg* self; + union { + ofono_netreg_status_cb_t status; + ofono_netreg_operator_cb_t operator; + ofono_netreg_operator_list_cb_t operator_list; + ofono_netreg_register_cb_t reg; + ofono_netreg_strength_cb_t strength; + BinderCallback f; + } cb; + gpointer data; +} BinderNetRegCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderNetReg* binder_netreg_get_data(struct ofono_netreg *ofono) + { return ofono ? ofono_netreg_get_data(ofono) : NULL; } + +static +BinderNetRegCbData* +binder_netreg_cbd_new( + BinderNetReg* self, + BinderCallback cb, + void* data) +{ + BinderNetRegCbData* cbd = g_slice_new0(BinderNetRegCbData); + + cbd->self = self; + cbd->cb.f = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_netreg_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderNetRegCbData, cbd); +} + +static +int +binder_netreg_check_status( + BinderNetReg* self, + int status) +{ + return (self && self->netreg) ? + binder_netreg_check_if_really_roaming(self->netreg, status) : + status; +} + +static +gboolean +binder_netreg_status_notify_cb( + gpointer user_data) +{ + BinderNetReg* self = user_data; + const BinderRegistrationState* reg = &self->network->voice; + + DBG_(self, ""); + GASSERT(self->notify_id); + self->notify_id = 0; + + ofono_netreg_status_notify(self->netreg, + binder_netreg_check_status(self, reg->status), + reg->lac, reg->ci, reg->access_tech); + return G_SOURCE_REMOVE; +} + +static +void +binder_netreg_status_notify( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + void* user_data) +{ + BinderNetReg* self = user_data; + + /* Coalesce multiple notifications into one */ + if (self->notify_id) { + DBG_(self, "notification aready queued"); + } else { + DBG_(self, "queuing notification"); + self->notify_id = g_idle_add(binder_netreg_status_notify_cb, self); + } +} + +static +void +binder_netreg_registration_status( + struct ofono_netreg* netreg, + ofono_netreg_status_cb_t cb, + void* data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + const BinderRegistrationState* reg = &self->network->voice; + struct ofono_error error; + + DBG_(self, ""); + cb(binder_error_ok(&error), binder_netreg_check_status(self, reg->status), + reg->lac, reg->ci, reg->access_tech, data); +} + +static +gboolean +binder_netreg_current_operator_cb( + gpointer user_data) +{ + BinderNetRegCbData* cbd = user_data; + BinderNetReg* self = cbd->self; + ofono_netreg_operator_cb_t cb = cbd->cb.operator; + struct ofono_error error; + + DBG_(self, ""); + GASSERT(self->current_operator_id); + self->current_operator_id = 0; + + cb(binder_error_ok(&error), self->network->operator, cbd->data); + return G_SOURCE_REMOVE; +} + +static +void +binder_netreg_current_operator( + struct ofono_netreg* netreg, + ofono_netreg_operator_cb_t cb, + void *data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + + /* + * Calling ofono_netreg_status_notify() may result in + * binder_netreg_current_operator() being invoked even if one + * is already pending. Since ofono core doesn't associate + * any context with individual calls, we can safely assume + * that such a call essentially cancels the previous one. + */ + if (self->current_operator_id) { + g_source_remove(self->current_operator_id); + } + + self->current_operator_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + binder_netreg_current_operator_cb, + binder_netreg_cbd_new(self, BINDER_CB(cb), data), + binder_netreg_cbd_free); +} + +static +gboolean +binder_netreg_strange( + const struct ofono_network_operator* op, + struct ofono_sim* sim) +{ + gsize mcclen; + + if (sim && op->status != OFONO_OPERATOR_STATUS_CURRENT) { + const char* spn = ofono_sim_get_spn(sim); + const char* mcc = ofono_sim_get_mcc(sim); + const char* mnc = ofono_sim_get_mnc(sim); + + if (spn && mcc && mnc && !strcmp(op->name, spn) && + (strcmp(op->mcc, mcc) || strcmp(op->mnc, mnc))) { + /* + * Status is not "current", SPN matches the SIM, but + * MCC and/or MNC don't (e.g. Sony Xperia X where all + * operators could be reported with the same name + * which equals SPN). + */ + DBG("%s %s%s (sim spn?)", op->name, op->mcc, op->mnc); + return TRUE; + } + } + + mcclen = strlen(op->mcc); + if (!strncmp(op->name, op->mcc, mcclen) && + !strcmp(op->name + mcclen, op->mnc)) { + /* Some MediaTek modems only report numeric operator name */ + DBG("%s %s%s (numeric?)", op->name, op->mcc, op->mnc); + return TRUE; + } + + return FALSE; +} + +static +void +binder_netreg_process_operators( + BinderNetReg* self, + struct ofono_network_operator* ops, + int nops) +{ + if (self->replace_strange_oper) { + int i; + + for (i = 0; i < nops; i++) { + struct ofono_network_operator* op = ops + i; + struct ofono_gprs_provision_data* prov = NULL; + int np = 0; + + if (binder_netreg_strange(op, self->watch->sim) && + ofono_gprs_provision_get_settings(op->mcc, op->mnc, + NULL, &prov, &np)) { + /* Use the first entry */ + if (np > 0 && prov->provider_name && prov->provider_name[0]) { + DBG("%s %s%s -> %s", op->name, op->mcc, op->mnc, + prov->provider_name); + g_strlcpy(op->name, prov->provider_name, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } + ofono_gprs_provision_free_settings(prov, np); + } + } + } +} + +static +struct ofono_network_operator* +binder_netreg_convert_oplist( + const RadioOperatorInfo* ops, + gsize count, + enum ofono_access_technology default_tech) +{ + gsize i; + struct ofono_network_operator* oplist = + g_new0(struct ofono_network_operator, count); + + for (i = 0; i < count; i++) { + const RadioOperatorInfo* src = ops + i; + struct ofono_network_operator* dest = oplist + i; + + /* Try to use long by default */ + if (src->alphaLong.len) { + g_strlcpy(dest->name, src->alphaLong.data.str, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } else if (src->alphaShort.len) { + g_strlcpy(dest->name, src->alphaShort.data.str, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } + + /* Set the proper status */ + dest->status = OFONO_OPERATOR_STATUS_UNKNOWN; + switch (src->status) { + case RADIO_OP_STATUS_UNKNOWN: + break; + case RADIO_OP_AVAILABLE: + dest->status = OFONO_OPERATOR_STATUS_AVAILABLE; + break; + case RADIO_OP_CURRENT: + dest->status = OFONO_OPERATOR_STATUS_CURRENT; + break; + case RADIO_OP_FORBIDDEN: + dest->status = OFONO_OPERATOR_STATUS_FORBIDDEN; + break; + } + + dest->tech = default_tech; + binder_parse_mcc_mnc(src->operatorNumeric.data.str, dest); + DBG("[operator=%s, %s, %s, %s, %s]", + dest->name, dest->mcc, dest->mnc, + binder_ofono_access_technology_string(dest->tech), + binder_radio_op_status_string(src->status)); + } + + return oplist; +} + +static +void +binder_netreg_list_operators_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetRegCbData* cbd = user_data; + BinderNetReg* self = cbd->self; + ofono_netreg_operator_list_cb_t cb = cbd->cb.operator_list; + struct ofono_error err; + + GASSERT(self->list_req == req); + radio_request_unref(self->list_req); + self->list_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_AVAILABLE_NETWORKS) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + const RadioOperatorInfo* ops; + gsize count = 0; + + /* + * getAvailableNetworksResponse(RadioResponseInfo, + * vec networkInfos); + */ + gbinder_reader_copy(&reader, args); + ops = gbinder_reader_read_hidl_type_vec(&reader, + RadioOperatorInfo, &count); + if (ops) { + struct ofono_network_operator* list = + binder_netreg_convert_oplist(ops, count, + self->network->voice.access_tech); + + /* Success */ + binder_netreg_process_operators(self, list, count); + cb(binder_error_ok(&err), count, list, cbd->data); + g_free(list); + return; + } + } else { + ofono_warn("Failed to retrive the list of operators: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getAvailableNetworks response %d", resp); + } + } + + /* Error path */ + cb(binder_error_failure(&err), 0, NULL, cbd->data); +} + +static +void +binder_netreg_list_operators( + struct ofono_netreg* netreg, + ofono_netreg_operator_list_cb_t cb, + void* data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_GET_AVAILABLE_NETWORKS, NULL, + binder_netreg_list_operators_cb, + binder_netreg_cbd_free, + binder_netreg_cbd_new(self, BINDER_CB(cb), data)); + + radio_request_set_timeout(req, OPERATOR_LIST_TIMEOUT_MS); + radio_request_drop(self->list_req); + if (radio_request_submit(req)) { + DBG_(self, "querying available networks"); + self->list_req = req; /* Keep the ref */ + } else { + struct ofono_error err; + + DBG_(self, "failed to query available networks"); + radio_request_unref(req); + self->list_req = NULL; + cb(binder_error_failure(&err), 0, NULL, data); + } +} + +static +void +binder_netreg_register_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetRegCbData* cbd = user_data; + BinderNetReg* self = cbd->self; + ofono_netreg_register_cb_t cb = cbd->cb.reg; + struct ofono_error err; + + GASSERT(self->register_req == req); + radio_request_unref(self->register_req); + self->register_req = NULL; + + /* + * Handles both: + * setNetworkSelectionModeAutomaticResponse(RadioResponseInfo); + * setNetworkSelectionModeManualResponse(RadioResponseInfo); + */ + if (status == RADIO_TX_STATUS_OK) { + GASSERT(resp == RADIO_RESP_SET_NETWORK_SELECTION_MODE_AUTOMATIC || + resp == RADIO_RESP_SET_NETWORK_SELECTION_MODE_MANUAL); + if (error == RADIO_ERROR_NONE) { + /* Success */ + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_error("registration failed, error %s", + binder_radio_error_string(error)); + } + } + + /* Error path */ + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_netreg_query_register_auto_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetRegCbData* cbd = user_data; + BinderNetReg* self = cbd->self; + ofono_netreg_register_cb_t cb = cbd->cb.reg; + struct ofono_error err; + + GASSERT(self->register_req == req); + radio_request_unref(self->register_req); + self->register_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_NETWORK_SELECTION_MODE) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + gboolean manual; + + /* + * getNetworkSelectionModeResponse(RadioResponseInfo, + * bool manual); + */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_bool(&reader, &manual) && !manual) { + ofono_info("nw selection is already auto"); + cb(binder_error_ok(&err), cbd->data); + return; + } + } + } else { + ofono_error("Unexpected getNetworkSelectionMode response %d", resp); + } + } + + /* + * Either selection is set to manual, or the query failed. + * In either case, let's give it a try. + */ + req = radio_request_new(self->client, + RADIO_REQ_SET_NETWORK_SELECTION_MODE_AUTOMATIC, NULL, + binder_netreg_register_cb, binder_netreg_cbd_free, + binder_netreg_cbd_new(self, cbd->cb.f, cbd->data)); + + /* setNetworkSelectionModeAutomatic(int32 serial); */ + radio_request_set_timeout(req, self->network_selection_timeout_ms); + radio_request_set_retry(req, 0, REGISTRATION_MAX_RETRIES); + if (radio_request_submit(req)) { + ofono_info("%snw select auto", self->log_prefix); + self->register_req = req; /* Keep the ref */ + } else { + ofono_warn("%sfailed to select auto nw", self->log_prefix); + radio_request_unref(req); + cb(binder_error_failure(&err), cbd->data); + } +} + +static +void +binder_netreg_register_auto( + struct ofono_netreg* netreg, + ofono_netreg_register_cb_t cb, + void* data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_GET_NETWORK_SELECTION_MODE, NULL, + binder_netreg_query_register_auto_cb, + binder_netreg_cbd_free, + binder_netreg_cbd_new(self, BINDER_CB(cb), data)); + + /* getNetworkSelectionMode(int32 serial); */ + radio_request_drop(self->register_req); + if (radio_request_submit(req)) { + self->register_req = req; /* Keep the ref */ + } else { + struct ofono_error err; + + DBG_(self, "failed to query bw selection mode"); + radio_request_unref(req); + self->register_req = NULL; + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_netreg_register_manual( + struct ofono_netreg* netreg, + const char* mcc, + const char* mnc, + ofono_netreg_register_cb_t cb, + void* data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + char* numeric = g_strconcat(mcc, mnc, NULL); + GBinderWriter writer; + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_SET_NETWORK_SELECTION_MODE_MANUAL, &writer, + binder_netreg_register_cb, binder_netreg_cbd_free, + binder_netreg_cbd_new(self, BINDER_CB(cb), data)); + + /* setNetworkSelectionModeManual(int32 serial, string operatorNumeric); */ + gbinder_writer_add_cleanup(&writer, g_free, numeric); + gbinder_writer_append_hidl_string(&writer, numeric); + radio_request_set_timeout(req, self->network_selection_timeout_ms); + + radio_request_drop(self->register_req); + if (radio_request_submit(req)) { + ofono_info("%snw select manual: %s", self->log_prefix, numeric); + self->register_req = req; /* Keep the ref */ + } else { + struct ofono_error err; + + DBG_(self, "failed to set nw select manual: %s", numeric); + radio_request_unref(req); + self->register_req = NULL; + cb(binder_error_failure(&err), data); + } +} + +/* + * RSSI + * Received Signal Strength Indication + * + * Reference: 3GPP TS 27.007 section 8.5 + * Range: -51..-113 dBm + * Valid values are (0-31, 99) + * + * 0 -113 dBm or less + * 1 -111 dBm + * 2...30 -109... -53 dBm + * 31 -51 dBm or greater + * 99 not known or not detectable + * + * INT_MAX denotes that the value is invalid/unreported. + */ + +#define RSSI_MIN 0 +#define RSSI_MAX 31 + +static +int +binder_netreg_dbm_from_rssi( + int rssi) +{ + return (rssi >= RSSI_MIN && rssi <= RSSI_MAX) ? + (-113 + 2 * (rssi - RSSI_MIN)) : -140; +} + +/* + * RSRP + * Reference Signal Received Power + * + * Reference: 3GPP TS 36.133 section 9.1.4 + * Range: -44..-140 dBm + * Value is dBm multipled by -1 + * + * INT_MAX denotes that the value is invalid/unreported. + */ + +#define RSRP_MIN 44 +#define RSRP_MAX 140 + +static +int +binder_netreg_dbm_from_rsrp( + int rsrp) +{ + return (rsrp >= RSRP_MIN && rsrp <= RSRP_MAX) ? -rsrp : -140; +} + +/* + * RSCP + * Received Signal Code Power + * + * Reference: 3GPP TS 27.007 section 8.69 + * Range: -24..-120 dBm + * Valid values are (0-96, 255) + * + * 0 -120 dBm or less + * 1 -119 dBm + * 2...95 -118...-25 dBm + * 96 -24 dBm or greater + * 255 not known or not detectable + * + * INT_MAX denotes that the value is invalid/unreported. + */ + +#define RSCP_MIN 0 +#define RSCP_MAX 96 + +static +int +binder_netreg_dbm_from_rscp( + int rscp) +{ + return (rscp >= RSCP_MIN && rscp <= RSCP_MAX) ? + (-120 + (rscp - RSCP_MIN)) : -140; +} + +static +int +binder_netreg_get_signal_strength_dbm( + const RadioSignalStrengthGsm* gsm, + const RadioSignalStrengthLte* lte, + const RadioSignalStrengthWcdma_1_2* wcdma, + const RadioSignalStrengthTdScdma_1_2* tdscdma) +{ + int rssi = -1, rscp = -1, rsrp = -1; + + if (gsm->signalStrength <= RSSI_MAX) { + rssi = gsm->signalStrength; + } + + if (lte->signalStrength <= RSSI_MAX && + (int)lte->signalStrength > rssi) { + rssi = lte->signalStrength; + } + + if (lte->rsrp >= RSRP_MIN && lte->rsrp <= RSRP_MAX) { + rsrp = lte->rsrp; + } + + if (wcdma) { + if (wcdma->base.signalStrength <= RSSI_MAX && + (int)wcdma->base.signalStrength > rssi) { + rssi = wcdma->base.signalStrength; + } + if (wcdma->rscp <= RSCP_MAX) { + rscp = wcdma->rscp; + } + } + + if (tdscdma) { + if (tdscdma->signalStrength <= RSSI_MAX && + (int)tdscdma->signalStrength > rssi) { + rssi = tdscdma->signalStrength; + } + if (tdscdma->rscp <= RSCP_MAX && + (int)tdscdma->rscp > rscp) { + rscp = tdscdma->rscp; + } + } + + if (rssi >= RSCP_MIN) { + return binder_netreg_dbm_from_rssi(rssi); + } else if (rscp >= RSCP_MIN) { + return binder_netreg_dbm_from_rscp(rssi); + } else if (rsrp >= RSRP_MIN) { + return binder_netreg_dbm_from_rsrp(rssi); + } else { + return -140; + } +} + +static +int +binder_netreg_percent_from_dbm( + BinderNetReg* self, + int dbm) +{ + const int min_dbm = self->signal_strength_dbm_weak; /* dBm */ + const int max_dbm = self->signal_strength_dbm_strong; /* dBm */ + + return (dbm <= min_dbm) ? 1 : + (dbm >= max_dbm) ? 100 : + (100 * (dbm - min_dbm) / (max_dbm - min_dbm)); +} + +static +void +binder_netreg_strength_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetReg* self = user_data; + GBinderReader reader; + int dbm = 0; + + gbinder_reader_copy(&reader, args); + if (code == RADIO_IND_CURRENT_SIGNAL_STRENGTH) { + const RadioSignalStrength* ss = gbinder_reader_read_hidl_struct + (&reader, RadioSignalStrength); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gw, &ss->lte, NULL, NULL); + } + } else if (code == RADIO_IND_CURRENT_SIGNAL_STRENGTH_1_2) { + const RadioSignalStrength_1_2* ss = gbinder_reader_read_hidl_struct + (&reader, RadioSignalStrength_1_2); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gw, &ss->lte, &ss->wcdma, NULL); + } + } else if (code == RADIO_IND_CURRENT_SIGNAL_STRENGTH_1_4) { + const RadioSignalStrength_1_4* ss = gbinder_reader_read_hidl_struct + (&reader, RadioSignalStrength_1_4); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gsm, &ss->lte, &ss->wcdma, &ss->tdscdma); + } + } + + if (dbm) { + const int percent = binder_netreg_percent_from_dbm(self, dbm); + + DBG_(self, "%d dBm (%d%%)", dbm, percent); + ofono_netreg_strength_notify(self->netreg, percent); + } +} + +static void binder_netreg_strength_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetRegCbData* cbd = user_data; + BinderNetReg* self = cbd->self; + ofono_netreg_strength_cb_t cb = cbd->cb.strength; + struct ofono_error err; + + GASSERT(self->strength_req == req); + radio_request_unref(self->strength_req); + self->strength_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + int dbm = 0; + + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_GET_SIGNAL_STRENGTH) { + const RadioSignalStrength* ss = + gbinder_reader_read_hidl_struct(&reader, + RadioSignalStrength); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gw, &ss->lte, NULL, NULL); + } + } else if (resp == RADIO_RESP_GET_SIGNAL_STRENGTH_1_2) { + const RadioSignalStrength_1_2* ss = + gbinder_reader_read_hidl_struct(&reader, + RadioSignalStrength_1_2); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gw, &ss->lte, &ss->wcdma, NULL); + } + } else if (resp == RADIO_RESP_GET_SIGNAL_STRENGTH_1_4) { + const RadioSignalStrength_1_4* ss = + gbinder_reader_read_hidl_struct(&reader, + RadioSignalStrength_1_4); + + if (ss) { + dbm = binder_netreg_get_signal_strength_dbm + (&ss->gsm, &ss->lte, &ss->wcdma, &ss->tdscdma); + } + } else { + ofono_error("Unexpected getSignalStrength response %d", resp); + } + + if (dbm) { + const int percent = binder_netreg_percent_from_dbm(self, dbm); + + /* Success */ + DBG_(self, "%d dBm (%d%%)", dbm, percent); + cb(binder_error_ok(&err), percent, cbd->data); + return; + } + } else { + ofono_warn("Failed to retrive the signal strength: %s", + binder_radio_error_string(status)); + } + } + + /* Error path */ + cb(binder_error_failure(&err), -1, cbd->data); +} + +static +void +binder_netreg_strength( + struct ofono_netreg* netreg, + ofono_netreg_strength_cb_t cb, + void* data) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + RadioRequest* req = radio_request_new(self->client, + (radio_client_interface(self->client) >= RADIO_INTERFACE_1_4) ? + RADIO_REQ_GET_SIGNAL_STRENGTH_1_4 : RADIO_REQ_GET_SIGNAL_STRENGTH, + NULL, binder_netreg_strength_cb, binder_netreg_cbd_free, + binder_netreg_cbd_new(self, BINDER_CB(cb), data)); + + radio_request_set_retry(req, BINDER_RETRY_MS, -1); + radio_request_drop(self->strength_req); + if (radio_request_submit(req)) { + self->strength_req = req; /* Keep the ref */ + } else { + struct ofono_error err; + + DBG_(self, "failed to query signal strength"); + radio_request_unref(req); + self->strength_req = NULL; + cb(binder_error_failure(&err), -1, data); + } +} + +static +void +binder_netreg_nitz_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetReg* self = user_data; + GBinderReader reader; + int year, mon, mday, hour, min, sec, tzi, dst = 0; + char tzs; + const char* nitz; + + /* + * nitzTimeReceived(RadioIndicationType, string nitzTime, + * uint64 receivedTime); + */ + GASSERT(code == RADIO_IND_NITZ_TIME_RECEIVED); + gbinder_reader_copy(&reader, args); + nitz = gbinder_reader_read_hidl_string_c(&reader); + + DBG_(self, "%s", nitz); + + /* + * Format: yy/mm/dd,hh:mm:ss(+/-)tz[,ds] + * The ds part is considered optional, initialized to zero. + */ + if (nitz && sscanf(nitz, "%u/%u/%u,%u:%u:%u%c%u,%u", &year, &mon, &mday, + &hour, &min, &sec, &tzs, &tzi, &dst) >= 8 && + (tzs == '+' || tzs == '-')) { + struct ofono_network_time time; + char tz[4]; + + snprintf(tz, sizeof(tz), "%c%d", tzs, tzi); + time.sec = sec; + time.min = min; + time.hour = hour; + time.mday = mday; + time.mon = mon; + time.year = 2000 + year; + time.dst = dst; + time.utcoff = atoi(tz) * 15 * 60; + + ofono_netreg_time_notify(self->netreg, &time); + } else { + ofono_warn("Failed to parse NITZ string \"%s\"", nitz); + } +} + +static +gboolean +binder_netreg_register( + gpointer user_data) +{ + BinderNetReg* self = user_data; + + GASSERT(self->init_id); + self->init_id = 0; + ofono_netreg_register(self->netreg); + + /* Register for network state changes */ + self->network_event_id[NETREG_NETWORK_EVENT_OPERATOR_CHANGED] = + binder_network_add_property_handler(self->network, + BINDER_NETWORK_PROPERTY_OPERATOR, + binder_netreg_status_notify, self); + self->network_event_id[NETREG_NETWORK_EVENT_VOICE_STATE_CHANGED] = + binder_network_add_property_handler(self->network, + BINDER_NETWORK_PROPERTY_VOICE_STATE, + binder_netreg_status_notify, self); + + /* Register for network time updates */ + self->ind_id[IND_NITZ_TIME_RECEIVED] = + radio_client_add_indication_handler(self->client, + RADIO_IND_NITZ_TIME_RECEIVED, + binder_netreg_nitz_notify, self); + + /* Register for signal strength changes */ + self->ind_id[IND_SIGNAL_STRENGTH] = + radio_client_add_indication_handler(self->client, + RADIO_IND_CURRENT_SIGNAL_STRENGTH, + binder_netreg_strength_notify, self); + self->ind_id[IND_SIGNAL_STRENGTH_1_2] = + radio_client_add_indication_handler(self->client, + RADIO_IND_CURRENT_SIGNAL_STRENGTH_1_2, + binder_netreg_strength_notify, self); + self->ind_id[IND_SIGNAL_STRENGTH_1_4] = + radio_client_add_indication_handler(self->client, + RADIO_IND_CURRENT_SIGNAL_STRENGTH_1_4, + binder_netreg_strength_notify, self); + + return G_SOURCE_REMOVE; +} + +static +int +binder_netreg_probe( + struct ofono_netreg* netreg, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderNetReg* self = g_new0(BinderNetReg, 1); + const BinderSlotConfig* config = &modem->config; + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + + DBG_(self, "%p", netreg); + self->client = radio_client_ref(modem->client); + self->watch = ofono_watch_new(binder_modem_get_path(modem)); + self->network = binder_network_ref(modem->network); + self->netreg = netreg; + self->replace_strange_oper = config->replace_strange_oper; + self->signal_strength_dbm_weak = config->signal_strength_dbm_weak; + self->signal_strength_dbm_strong = config->signal_strength_dbm_strong; + self->network_selection_timeout_ms = config->network_selection_timeout_ms; + + ofono_netreg_set_data(netreg, self); + self->init_id = g_idle_add(binder_netreg_register, self); + return 0; +} + +static +void +binder_netreg_remove( + struct ofono_netreg* netreg) +{ + BinderNetReg* self = binder_netreg_get_data(netreg); + + DBG_(self, "%p", netreg); + + if (self->init_id) { + g_source_remove(self->init_id); + } + + if (self->notify_id) { + g_source_remove(self->notify_id); + } + + if (self->current_operator_id) { + g_source_remove(self->current_operator_id); + } + + radio_request_drop(self->list_req); + radio_request_drop(self->register_req); + radio_request_drop(self->strength_req); + + ofono_watch_unref(self->watch); + binder_network_remove_all_handlers(self->network, self->network_event_id); + binder_network_unref(self->network); + + radio_client_remove_all_handlers(self->client, self->ind_id); + radio_client_unref(self->client); + + g_free(self->log_prefix); + g_free(self); + + ofono_netreg_set_data(netreg, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_netreg_driver binder_netreg_driver = { + .name = BINDER_DRIVER, + .probe = binder_netreg_probe, + .remove = binder_netreg_remove, + .registration_status = binder_netreg_registration_status, + .current_operator = binder_netreg_current_operator, + .list_operators = binder_netreg_list_operators, + .register_auto = binder_netreg_register_auto, + .register_manual = binder_netreg_register_manual, + .strength = binder_netreg_strength +}; + +void +binder_netreg_init() +{ + ofono_netreg_driver_register(&binder_netreg_driver); +} + +void +binder_netreg_cleanup() +{ + ofono_netreg_driver_unregister(&binder_netreg_driver); +} + +enum ofono_netreg_status +binder_netreg_check_if_really_roaming( + struct ofono_netreg* netreg, + enum ofono_netreg_status status) +{ + if (status == OFONO_NETREG_STATUS_ROAMING && netreg) { + /* These functions tolerate NULL argument */ + const char* net_mcc = ofono_netreg_get_mcc(netreg); + const char* net_mnc = ofono_netreg_get_mnc(netreg); + + if (ofono_netreg_spdi_lookup(netreg, net_mcc, net_mnc)) { + ofono_info("not roaming based on spdi"); + return OFONO_NETREG_STATUS_REGISTERED; + } + } + return status; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_netreg.h b/src/binder_netreg.h new file mode 100644 index 0000000..77ae6c8 --- /dev/null +++ b/src/binder_netreg.h @@ -0,0 +1,45 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_NETREG_H +#define BINDER_NETREG_H + +#include "binder_types.h" + +#include + +void +binder_netreg_init(void) + BINDER_INTERNAL; + +void +binder_netreg_cleanup(void) + BINDER_INTERNAL; + +enum ofono_netreg_status +binder_netreg_check_if_really_roaming( + struct ofono_netreg* reg, + enum ofono_netreg_status status) + BINDER_INTERNAL; + +#endif /* BINDER_NETREG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_network.c b/src/binder_network.c new file mode 100644 index 0000000..3a1db1b --- /dev/null +++ b/src/binder_network.c @@ -0,0 +1,2288 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_log.h" +#include "binder_network.h" +#include "binder_radio.h" +#include "binder_radio_caps.h" +#include "binder_sim_card.h" +#include "binder_sim_settings.h" +#include "binder_util.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define SET_PREF_MODE_HOLDOFF_SEC BINDER_RETRY_SECS +#define INTINITE_TIMEOUT UINT_MAX + +typedef enum binder_network_timer { + TIMER_SET_RAT_HOLDOFF, + TIMER_FORCE_CHECK_PREF_MODE, + TIMER_COUNT +} BINDER_NETWORK_TIMER; + +enum binder_network_radio_event { + RADIO_EVENT_STATE_CHANGED, + RADIO_EVENT_ONLINE_CHANGED, + RADIO_EVENT_COUNT +}; + +enum binder_network_radio_caps_mgr_events { + RADIO_CAPS_MGR_TX_DONE, + RADIO_CAPS_MGR_TX_ABORTED, + RADIO_CAPS_MGR_EVENT_COUNT +}; + +enum binder_network_sim_events { + SIM_EVENT_STATUS_CHANGED, + SIM_EVENT_IO_ACTIVE_CHANGED, + SIM_EVENT_COUNT +}; + +enum binder_network_ind_events { + IND_NETWORK_STATE, + IND_MODEM_RESET, + IND_COUNT +}; + +enum binder_network_watch_event { + WATCH_EVENT_GPRS, + WATCH_EVENT_GPRS_SETTINGS, + WATCH_EVENT_COUNT +}; + +typedef struct binder_network_location { + int lac; + int ci; +} BinderNetworkLocation; + +typedef struct binder_network_data_profile { + RADIO_DATA_PROFILE_ID id; + RADIO_DATA_PROFILE_TYPE type; + const char* apn; + const char* username; + const char* password; + enum ofono_gprs_auth_method auth_method; + enum ofono_gprs_proto proto; + int max_conns_time; + int max_conns; + int wait_time; + gboolean enabled; +} BinderNetworkDataProfile; + +typedef struct binder_network_object { + BinderBase base; + BinderNetwork pub; + RadioRequestGroup* g; + BinderRadio* radio; + BinderRadioCaps* caps; + BinderSimCard* simcard; + struct ofono_watch* watch; + RADIO_ACCESS_FAMILY raf; + RADIO_PREF_NET_TYPE rat; + RADIO_PREF_NET_TYPE lte_network_mode; + RADIO_PREF_NET_TYPE umts_network_mode; + int network_mode_timeout_ms; + char* log_prefix; + RadioRequest* operator_poll_req; + RadioRequest* voice_poll_req; + RadioRequest* data_poll_req; + RadioRequest* query_rat_req; + RadioRequest* set_rat_req; + RadioRequest* set_data_profiles_req; + RadioRequest* set_ia_apn_req; + guint timer[TIMER_COUNT]; + gulong ind_id[IND_COUNT]; + gulong settings_event_id; + gulong caps_raf_event_id; + gulong caps_mgr_event_id[RADIO_CAPS_MGR_EVENT_COUNT]; + gulong radio_event_id[RADIO_EVENT_COUNT]; + gulong simcard_event_id[SIM_EVENT_COUNT]; + gulong watch_ids[WATCH_EVENT_COUNT]; + gboolean need_initial_attach_apn; + gboolean set_initial_attach_apn; + struct ofono_network_operator operator; + gboolean assert_rat; + gboolean force_gsm_when_radio_off; + gboolean use_data_profiles; + int mms_data_profile_id; + GSList* data_profiles; +} BinderNetworkObject; + +typedef BinderBaseClass BinderNetworkObjectClass; +GType binder_network_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderNetworkObject, binder_network_object, BINDER_TYPE_BASE) +#define PARENT_CLASS binder_network_object_parent_class +#define THIS_TYPE binder_network_object_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj,THIS_TYPE,BinderNetworkObject) + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +/* Some assumptions: */ +BINDER_BASE_ASSERT_COUNT(BINDER_NETWORK_PROPERTY_COUNT); +G_STATIC_ASSERT(OFONO_RADIO_ACCESS_MODE_ANY == 0); +G_STATIC_ASSERT(OFONO_RADIO_ACCESS_MODE_GSM > OFONO_RADIO_ACCESS_MODE_ANY); +G_STATIC_ASSERT(OFONO_RADIO_ACCESS_MODE_UMTS > OFONO_RADIO_ACCESS_MODE_GSM); +G_STATIC_ASSERT(OFONO_RADIO_ACCESS_MODE_LTE > OFONO_RADIO_ACCESS_MODE_UMTS); + +static +void +binder_network_query_pref_mode( + BinderNetworkObject* self); + +static +void +binder_network_check_pref_mode( + BinderNetworkObject* self, + gboolean immediate); + +static +void +binder_network_check_initial_attach_apn( + BinderNetworkObject* self); + +static inline BinderNetworkObject* binder_network_cast(BinderNetwork* net) + { return net ? THIS(G_CAST(net, BinderNetworkObject, pub)) : NULL; } +static inline void binder_network_object_ref(BinderNetworkObject* self) + { g_object_ref(self); } +static inline void binder_network_object_unref(BinderNetworkObject* self) + { g_object_unref(self); } + +static +void +binder_network_stop_timer( + BinderNetworkObject* self, + BINDER_NETWORK_TIMER tid) +{ + if (self->timer[tid]) { + g_source_remove(self->timer[tid]); + self->timer[tid] = 0; + } +} + +static +void +binder_network_reset_state( + BinderRegistrationState* reg) +{ + memset(reg, 0, sizeof(*reg)); + reg->status = OFONO_NETREG_STATUS_NONE; + reg->access_tech = OFONO_ACCESS_TECHNOLOGY_NONE; + reg->radio_tech = RADIO_TECH_UNKNOWN; + reg->lac = -1; + reg->ci = -1; +} + +static +struct ofono_network_operator* +binder_network_op_copy( + struct ofono_network_operator* dest, + const struct ofono_network_operator* src) +{ + g_strlcpy(dest->mcc, src->mcc, sizeof(dest->mcc)); + g_strlcpy(dest->mnc, src->mnc, sizeof(dest->mnc)); + g_strlcpy(dest->name, src->name, sizeof(dest->name)); + dest->status = src->status; + dest->tech = src->tech; + return dest; +} + +static +gboolean +binder_network_op_equal( + const struct ofono_network_operator* op1, + const struct ofono_network_operator* op2) +{ + if (op1 == op2) { + return TRUE; + } else if (!op1 || !op2) { + return FALSE; + } else { + return op1->status == op2->status && + op1->tech == op2->tech && + !strncmp(op1->mcc, op2->mcc, sizeof(op2->mcc)) && + !strncmp(op1->mnc, op2->mnc, sizeof(op2->mnc)) && + !strncmp(op1->name, op2->name, sizeof(op2->name)); + } +} + +static +void +binder_network_poll_operator_ok( + BinderNetworkObject* self, + GBinderReader* reader) +{ + /* + * getOperatorResponse(RadioResponseInfo, string longName, + * string shortName, string numeric); + */ + const char* lalpha = gbinder_reader_read_hidl_string_c(reader); + const char* salpha = gbinder_reader_read_hidl_string_c(reader); + const char* numeric = gbinder_reader_read_hidl_string_c(reader); + BinderNetwork* net = &self->pub; + struct ofono_network_operator op; + gboolean changed = FALSE; + + memset(&op, 0, sizeof(op)); + op.tech = OFONO_ACCESS_TECHNOLOGY_NONE; + if (binder_parse_mcc_mnc(numeric, &op)) { + if (op.tech == OFONO_ACCESS_TECHNOLOGY_NONE) { + op.tech = net->voice.access_tech; + } + op.status = OFONO_OPERATOR_STATUS_CURRENT; + if (lalpha) { + g_strlcpy(op.name, lalpha, sizeof(op.name)); + } else if (salpha) { + g_strlcpy(op.name, salpha, sizeof(op.name)); + } else { + g_strlcpy(op.name, numeric, sizeof(op.name)); + } + if (!net->operator) { + net->operator = binder_network_op_copy(&self->operator, &op); + changed = TRUE; + } else if (!binder_network_op_equal(&op, &self->operator)) { + binder_network_op_copy(&self->operator, &op); + changed = TRUE; + } + } else if (net->operator) { + net->operator = NULL; + changed = TRUE; + } + + if (changed) { + if (net->operator) { + DBG_(self, "lalpha=%s, salpha=%s, numeric=%s, %s, mcc=%s, mnc=%s," + " %s", lalpha, salpha, numeric, op.name, op.mcc, op.mnc, + ofono_access_technology_to_string(op.tech)); + } else { + DBG_(self, "no operator"); + } + binder_base_emit_property_change(&self->base, + BINDER_NETWORK_PROPERTY_OPERATOR); + } +} + +static +void +binder_network_poll_operator_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->operator_poll_req == req); + radio_request_unref(self->operator_poll_req); + self->operator_poll_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_OPERATOR) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + binder_network_poll_operator_ok(self, &reader); + } else { + DBG_(self, "Failed get operator, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getOperator response %d", resp); + } + } +} + +static +void +binder_network_set_registration_state( + BinderRegistrationState* reg, + RADIO_REG_STATE reg_state, + RADIO_TECH rat, + int lac, + int ci) +{ + reg->status = OFONO_NETREG_STATUS_NONE; + reg->access_tech = binder_access_tech_from_radio_tech(rat); + reg->radio_tech = rat; + reg->em_enabled = FALSE; + reg->lac = lac; + reg->ci = ci; + + switch (reg_state) { + case RADIO_REG_STATE_REG_HOME: + reg->em_enabled = TRUE; + reg->status = OFONO_NETREG_STATUS_REGISTERED; + break; + + case RADIO_REG_STATE_REG_ROAMING: + reg->em_enabled = TRUE; + reg->status = OFONO_NETREG_STATUS_ROAMING; + break; + + case RADIO_REG_STATE_NOT_REG_MT_NOT_SEARCHING_EM: + reg->em_enabled = TRUE; + /* fallthrough */ + case RADIO_REG_STATE_NOT_REG_NOT_SEARCHING: + reg->status = OFONO_NETREG_STATUS_NOT_REGISTERED; + break; + + case RADIO_REG_STATE_NOT_REG_MT_SEARCHING_EM: + reg->em_enabled = TRUE; + /* fallthrough */ + case RADIO_REG_STATE_NOT_REG_MT_SEARCHING: + reg->status = OFONO_NETREG_STATUS_SEARCHING; + break; + + case RADIO_REG_STATE_REG_DENIED_EM: + reg->em_enabled = TRUE; + /* fallthrough */ + case RADIO_REG_STATE_REG_DENIED: + reg->status = OFONO_NETREG_STATUS_DENIED; + break; + + case RADIO_REG_STATE_UNKNOWN_EM: + reg->em_enabled = TRUE; + /* fallthrough */ + case RADIO_REG_STATE_UNKNOWN: + reg->status = OFONO_NETREG_STATUS_UNKNOWN; + break; + } +} + +static +void +binder_network_location_1_0( + const RadioCellIdentity* cell, + BinderNetworkLocation* l) +{ + switch (cell->cellInfoType) { + case RADIO_CELL_INFO_GSM: + if (cell->gsm.count > 0 && cell->gsm.data.ptr) { + const RadioCellIdentityGsm* gsm = cell->gsm.data.ptr; + + l->lac = gsm->lac; + l->ci = gsm->cid; + return; + } + break; + case RADIO_CELL_INFO_WCDMA: + if (cell->wcdma.count > 0 && cell->wcdma.data.ptr) { + const RadioCellIdentityWcdma* wcdma = cell->wcdma.data.ptr; + + l->lac = wcdma->lac; + l->ci = wcdma->cid; + return; + } + break; + case RADIO_CELL_INFO_TD_SCDMA: + if (cell->tdscdma.count > 0 && cell->tdscdma.data.ptr) { + const RadioCellIdentityTdscdma* tds = cell->tdscdma.data.ptr; + + l->lac = tds->lac; + l->ci = tds->cid; + return; + } + break; + case RADIO_CELL_INFO_LTE: + if (cell->lte.count > 0 && cell->lte.data.ptr) { + const RadioCellIdentityLte* lte = cell->lte.data.ptr; + + l->lac = -1; + l->ci = lte->ci; + return; + } + break; + default: + break; + } + + /* Unknown location */ + l->lac = l->ci = -1; +} + +static +void +binder_network_location_1_2( + const RadioCellIdentity_1_2* cell, + BinderNetworkLocation* l) +{ + switch (cell->cellInfoType) { + case RADIO_CELL_INFO_GSM: + if (cell->gsm.count > 0 && cell->gsm.data.ptr) { + const RadioCellIdentityGsm_1_2* gsm = cell->gsm.data.ptr; + + l->lac = gsm->base.lac; + l->ci = gsm->base.cid; + return; + } + break; + case RADIO_CELL_INFO_WCDMA: + if (cell->wcdma.count > 0 && cell->wcdma.data.ptr) { + const RadioCellIdentityWcdma_1_2* wcdma = cell->wcdma.data.ptr; + + l->lac = wcdma->base.lac; + l->ci = wcdma->base.cid; + return; + } + break; + case RADIO_CELL_INFO_TD_SCDMA: + if (cell->tdscdma.count > 0 && cell->tdscdma.data.ptr) { + const RadioCellIdentityTdscdma_1_2* tds = cell->tdscdma.data.ptr; + + l->lac = tds->base.lac; + l->ci = tds->base.cid; + return; + } + break; + case RADIO_CELL_INFO_LTE: + if (cell->lte.count > 0 && cell->lte.data.ptr) { + const RadioCellIdentityLte_1_2* lte = cell->lte.data.ptr; + + l->lac = -1; + l->ci = lte->base.ci; + return; + } + break; + default: + break; + } + + /* Unknown location */ + l->lac = l->ci = -1; +} + +static +void +binder_network_poll_voice_state_1_0( + BinderRegistrationState* state, + const RadioVoiceRegStateResult* result) +{ + BinderNetworkLocation l; + + binder_network_location_1_0(&result->cellIdentity, &l); + binder_network_set_registration_state(state, result->regState, + result->rat, l.lac, l.ci); +} + +static +void +binder_network_poll_voice_state_1_2( + BinderRegistrationState* state, + const RadioVoiceRegStateResult_1_2* result) +{ + BinderNetworkLocation l; + + binder_network_location_1_2(&result->cellIdentity, &l); + binder_network_set_registration_state(state, result->regState, + result->rat, l.lac, l.ci); +} + +static +void +binder_network_poll_voice_state_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->voice_poll_req == req); + radio_request_unref(self->voice_poll_req); + self->voice_poll_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + BinderRegistrationState state; + BinderRegistrationState* reg = NULL; + int reason = -1; + + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_GET_VOICE_REGISTRATION_STATE) { + const RadioVoiceRegStateResult* result = + gbinder_reader_read_hidl_struct(&reader, + RadioVoiceRegStateResult); + + if (result) { + reg = &state; + reason = result->reasonForDenial; + binder_network_poll_voice_state_1_0(reg, result); + } + } else if (resp == RADIO_RESP_GET_VOICE_REGISTRATION_STATE_1_2) { + const RadioVoiceRegStateResult_1_2* result = + gbinder_reader_read_hidl_struct(&reader, + RadioVoiceRegStateResult_1_2); + + if (result) { + reg = &state; + reason = result->reasonForDenial; + binder_network_poll_voice_state_1_2(reg, result); + } + } else { + ofono_error("Unexpected getVoiceRegistrationState response %d", + resp); + } + + if (reg) { + BinderNetwork* net = &self->pub; + + DBG_(self, "%s,%s,%d,%d,%d,%d", + ofono_netreg_status_to_string(reg->status), + ofono_access_technology_to_string(reg->access_tech), + reg->radio_tech, reg->lac, reg->ci, reason); + if (memcmp(&state, &net->voice, sizeof(state))) { + DBG_(self, "voice registration changed"); + net->voice = state; + binder_base_emit_property_change(&self->base, + BINDER_NETWORK_PROPERTY_VOICE_STATE); + } + } + } else { + DBG_(self, "Failed get voice reg state, error %d", error); + } + } +} + +static +void +binder_network_poll_data_state_1_0( + BinderRegistrationState* state, + const RadioDataRegStateResult* result) +{ + BinderNetworkLocation l; + + binder_network_location_1_0(&result->cellIdentity, &l); + binder_network_set_registration_state(state, result->regState, + result->rat, l.lac, l.ci); +} + +static +void +binder_network_poll_data_state_1_2( + BinderRegistrationState* state, + const RadioDataRegStateResult_1_2* result) +{ + BinderNetworkLocation l; + + binder_network_location_1_2(&result->cellIdentity, &l); + binder_network_set_registration_state(state, result->regState, + result->rat, l.lac, l.ci); +} + +static +void +binder_network_poll_data_state_1_4( + BinderRegistrationState* state, + const RadioDataRegStateResult_1_4* result) +{ + BinderNetworkLocation l; + + binder_network_location_1_2(&result->cellIdentity, &l); + binder_network_set_registration_state(state, result->regState, + result->rat, l.lac, l.ci); +} + +static +void +binder_network_poll_data_state_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->data_poll_req == req); + radio_request_unref(self->data_poll_req); + self->data_poll_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + BinderRegistrationState state; + BinderRegistrationState* reg = NULL; + int reason = -1, max_data_calls = -1; + + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_GET_DATA_REGISTRATION_STATE) { + const RadioDataRegStateResult* result = + gbinder_reader_read_hidl_struct(&reader, + RadioDataRegStateResult); + + if (result) { + reg = &state; + reason = result->reasonDataDenied; + max_data_calls = result->maxDataCalls; + binder_network_poll_data_state_1_0(reg, result); + } + } else if (resp == RADIO_RESP_GET_DATA_REGISTRATION_STATE_1_2) { + const RadioDataRegStateResult_1_2* result = + gbinder_reader_read_hidl_struct(&reader, + RadioDataRegStateResult_1_2); + + if (result) { + reg = &state; + reason = result->reasonDataDenied; + max_data_calls = result->maxDataCalls; + binder_network_poll_data_state_1_2(reg, result); + } + } else if (resp == RADIO_RESP_GET_DATA_REGISTRATION_STATE_1_4) { + const RadioDataRegStateResult_1_4* result = + gbinder_reader_read_hidl_struct(&reader, + RadioDataRegStateResult_1_4); + + if (result) { + reg = &state; + reason = result->reasonDataDenied; + max_data_calls = result->maxDataCalls; + binder_network_poll_data_state_1_4(reg, result); + } + } else { + ofono_error("Unexpected getDataRegistrationState response %d", + resp); + } + + if (reg) { + BinderBase* base = &self->base; + BinderNetwork* net = &self->pub; + + DBG_(self, "%s,%s,%d,%d,%d,%d,%d", + ofono_netreg_status_to_string(reg->status), + ofono_access_technology_to_string(reg->access_tech), + reg->radio_tech, reg->lac, reg->ci, reason, + max_data_calls); + if (memcmp(&state, &net->data, sizeof(state))) { + DBG_(self, "data registration changed"); + net->data = state; + binder_base_queue_property_change(base, + BINDER_NETWORK_PROPERTY_DATA_STATE); + } + if (net->max_data_calls != max_data_calls) { + net->max_data_calls = max_data_calls; + DBG_(self, "max data calls %d", max_data_calls); + binder_base_queue_property_change(base, + BINDER_NETWORK_PROPERTY_MAX_DATA_CALLS); + } + binder_base_emit_queued_signals(base); + } + } else { + DBG_(self, "Failed get data reg state, error %d", error); + } + } +} + +static +gboolean +binder_network_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + switch (error) { + case RADIO_ERROR_NONE: + case RADIO_ERROR_RADIO_NOT_AVAILABLE: + return FALSE; + default: + return TRUE; + } +} + +static +RadioRequest* +binder_network_poll_and_retry( + BinderNetworkObject* self, + RadioRequest* req, + RADIO_REQ code, + RadioRequestCompleteFunc complete) +{ + /* Don't wait for retry timeout to expire */ + if (!radio_request_retry(req)) { + radio_request_drop(req); + req = radio_request_new2(self->g, code, NULL, complete, NULL, self); + radio_request_set_retry_func(req, binder_network_retry); + radio_request_set_retry(req, BINDER_RETRY_MS * 1000, -1); + radio_request_set_timeout(req, INTINITE_TIMEOUT); + radio_request_submit(req); + } + return req; +} + +static +void +binder_network_poll_registration_state( + BinderNetworkObject* self) +{ + self->voice_poll_req = binder_network_poll_and_retry(self, + self->voice_poll_req, RADIO_REQ_GET_VOICE_REGISTRATION_STATE, + binder_network_poll_voice_state_cb); + self->data_poll_req = binder_network_poll_and_retry(self, + self->data_poll_req, RADIO_REQ_GET_DATA_REGISTRATION_STATE, + binder_network_poll_data_state_cb); +} + +static +void +binder_network_poll_state( + BinderNetworkObject* self) +{ + DBG_(self, ""); + self->operator_poll_req = binder_network_poll_and_retry(self, + self->operator_poll_req, RADIO_REQ_GET_OPERATOR, + binder_network_poll_operator_cb); + binder_network_poll_registration_state(self); +} + +static +RADIO_PREF_NET_TYPE +binder_network_mode_to_pref( + BinderNetworkObject* self, + enum ofono_radio_access_mode mode) +{ + BinderSimSettings* settings = self->pub.settings; + + switch (ofono_radio_access_max_mode(mode)) { + case OFONO_RADIO_ACCESS_MODE_ANY: + case OFONO_RADIO_ACCESS_MODE_LTE: + if (settings->techs & OFONO_RADIO_ACCESS_MODE_LTE) { + return self->lte_network_mode; + } + /* fallthrough */ + default: + case OFONO_RADIO_ACCESS_MODE_UMTS: + if (settings->techs & OFONO_RADIO_ACCESS_MODE_UMTS) { + return self->umts_network_mode; + } + /* fallthrough */ + case OFONO_RADIO_ACCESS_MODE_GSM: + break; + } + + return RADIO_PREF_NET_GSM_ONLY; +} + +static +enum ofono_radio_access_mode +binder_network_actual_pref_modes( + BinderNetworkObject* self) +{ + BinderNetwork* net = &self->pub; + BinderSimSettings* settings = net->settings; + BinderRadioCaps* caps = self->caps; + const enum ofono_radio_access_mode supported = caps ? + binder_access_modes_from_raf(caps->raf) : + OFONO_RADIO_ACCESS_MODE_ALL; + + /* + * On most dual-SIM phones only one slot at a time is allowed + * to use LTE. On some modems, even if the slot which has been + * using LTE gets powered off, we still need to explicitly set + * its preferred mode to GSM, to make LTE machinery available + * to the other slot. This behavior is configurable. + */ + const enum ofono_radio_access_mode really_allowed = + (self->radio->state == RADIO_STATE_ON) ? net->allowed_modes: + OFONO_RADIO_ACCESS_MODE_GSM; + + return settings->techs & settings->pref & supported & really_allowed; +} + +static +void +binder_network_data_profile_init( + BinderNetworkDataProfile* profile, + const struct ofono_gprs_primary_context* ctx, + RADIO_DATA_PROFILE_ID id) +{ + memset(profile, 0, sizeof(*profile)); + profile->id = id; + profile->type = RADIO_DATA_PROFILE_3GPP; + profile->proto = ctx->proto; + profile->enabled = TRUE; + profile->auth_method = + (ctx->username[0] || ctx->password[0]) ? + ctx->auth_method : OFONO_GPRS_AUTH_METHOD_NONE; + + profile->apn = ctx->apn; + if (profile->auth_method == OFONO_GPRS_AUTH_METHOD_NONE) { + profile->username = ""; + profile->password = ""; + } else { + profile->username = ctx->username; + profile->password = ctx->password; + } +} + +static +BinderNetworkDataProfile* +binder_network_data_profile_new( + const struct ofono_gprs_primary_context* ctx, + RADIO_DATA_PROFILE_ID id) +{ + /* Allocate the whole thing as a single memory block */ + BinderNetworkDataProfile profile; + BinderNetworkDataProfile* out; + gsize apn_size; + gsize username_size = 0; + gsize password_size = 0; + char* ptr; + + binder_network_data_profile_init(&profile, ctx, id); + + apn_size = profile.apn[0] ? (strlen(profile.apn) + 1) : 0; + username_size = profile.username[0] ? (strlen(profile.username) + 1) : 0; + password_size = profile.password[0] ? (strlen(profile.password) + 1) : 0; + ptr = g_malloc(G_ALIGN8(sizeof(profile)) + G_ALIGN8(apn_size) + + G_ALIGN8(username_size) + G_ALIGN8(password_size)); + + /* Copy the strucure */ + *(out = (BinderNetworkDataProfile*)ptr) = profile; + ptr += G_ALIGN8(sizeof(profile)); + + /* And the strings */ + if (profile.apn[0]) { + out->apn = ptr; + memcpy(ptr, profile.apn, apn_size); + ptr += G_ALIGN8(apn_size); + } else { + out->apn = ""; + } + + if (profile.username[0]) { + out->username = ptr; + memcpy(ptr, profile.username, username_size); + ptr += G_ALIGN8(username_size); + } else { + out->username = ""; + } + + if (profile.password[0]) { + out->password = ptr; + memcpy(ptr, profile.password, password_size); + ptr += G_ALIGN8(password_size); + } else { + out->password = ""; + } + + return out; +} + +static +RadioDataProfile* +binder_network_fill_radio_data_profile( + GBinderWriter* writer, + RadioDataProfile* dp, + const BinderNetworkDataProfile* src) +{ + const char* proto = binder_proto_str_from_ofono_proto(src->proto); + + binder_copy_hidl_string(writer, &dp->apn, src->apn); + binder_copy_hidl_string(writer, &dp->protocol, proto); + binder_copy_hidl_string(writer, &dp->roamingProtocol, proto); + binder_copy_hidl_string(writer, &dp->user, src->username); + binder_copy_hidl_string(writer, &dp->password, src->password); + binder_copy_hidl_string(writer, &dp->mvnoMatchData, NULL); + + dp->authType = binder_radio_auth_from_ofono_method(src->auth_method); + dp->supportedApnTypesBitmap = binder_radio_apn_types_for_profile(src->id); + dp->enabled = TRUE; + return dp; +} + +static +RadioDataProfile* +binder_network_new_radio_data_profile( + GBinderWriter* writer, + const BinderNetworkDataProfile* src) +{ + return binder_network_fill_radio_data_profile(writer, + gbinder_writer_new0(writer, RadioDataProfile), src); +} + +static +void +binder_network_write_data_profile_strings( + GBinderWriter* writer, + const RadioDataProfile* dp, + guint parent, + guint i) +{ + const guint off = sizeof(*dp) * i; + + /* Write the string data in the right order */ + binder_append_hidl_string_data2(writer, dp, apn, parent, off); + binder_append_hidl_string_data2(writer, dp, protocol, parent, off); + binder_append_hidl_string_data2(writer, dp, roamingProtocol, parent, off); + binder_append_hidl_string_data2(writer, dp, user, parent, off); + binder_append_hidl_string_data2(writer, dp, password, parent, off); + binder_append_hidl_string_data2(writer, dp, mvnoMatchData, parent, off); +} + +static +RadioDataProfile_1_4* +binder_network_fill_radio_data_profile_1_4( + GBinderWriter* writer, + RadioDataProfile_1_4* dp, + const BinderNetworkDataProfile* src) +{ + binder_copy_hidl_string(writer, &dp->apn, src->apn); + binder_copy_hidl_string(writer, &dp->user, src->username); + binder_copy_hidl_string(writer, &dp->password, src->password); + + dp->protocol = dp->roamingProtocol = + binder_proto_from_ofono_proto(src->proto); + dp->authType = binder_radio_auth_from_ofono_method(src->auth_method); + dp->supportedApnTypesBitmap = binder_radio_apn_types_for_profile(src->id); + dp->enabled = TRUE; + dp->preferred = TRUE; + return dp; +} + +static +RadioDataProfile_1_4* +binder_network_new_radio_data_profile_1_4( + GBinderWriter* writer, + const BinderNetworkDataProfile* src) +{ + return binder_network_fill_radio_data_profile_1_4(writer, + gbinder_writer_new0(writer, RadioDataProfile_1_4), src); +} + +static +void +binder_network_write_data_profile_strings_1_4( + GBinderWriter* writer, + const RadioDataProfile_1_4* dp, + guint parent, + guint i) +{ + const guint off = sizeof(*dp) * i; + + /* Write the string data in the right order */ + binder_append_hidl_string_data2(writer, dp, apn, parent, off); + binder_append_hidl_string_data2(writer, dp, user, parent, off); + binder_append_hidl_string_data2(writer, dp, password, parent, off); +} + + +static +gboolean +binder_network_data_profile_equal( + const BinderNetworkDataProfile* profile1, + const BinderNetworkDataProfile* profile2) +{ + if (profile1 == profile2) { + return TRUE; + } else if (!profile1 || !profile2) { + return FALSE; + } else { + return profile1->id == profile2->id && + profile1->type == profile2->type && + profile1->auth_method == profile2->auth_method && + profile1->proto == profile2->proto && + profile1->enabled == profile2->enabled && + !g_strcmp0(profile1->apn, profile2->apn) && + !g_strcmp0(profile1->username, profile2->username) && + !g_strcmp0(profile1->password, profile2->password); + } +} + +static +gboolean +binder_network_data_profiles_equal( + GSList* list1, + GSList* list2) +{ + if (g_slist_length(list1) != g_slist_length(list2)) { + return FALSE; + } else { + GSList* l1 = list1; + GSList* l2 = list2; + + while (l1 && l2) { + const BinderNetworkDataProfile* p1 = l1->data; + const BinderNetworkDataProfile* p2 = l2->data; + + if (!binder_network_data_profile_equal(p1, p2)) { + return FALSE; + } + l1 = l1->next; + l2 = l2->next; + } + + return TRUE; + } +} + +static inline +void +binder_network_data_profiles_free( + GSList* list) +{ + /* Profiles are allocated as single memory blocks */ + g_slist_free_full(list, g_free); +} + +static +void +binder_network_set_data_profiles_done( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->set_data_profiles_req == req); + radio_request_unref(self->set_data_profiles_req); + self->set_data_profiles_req = NULL; + + if (error != RADIO_ERROR_NONE) { + ofono_error("Error setting data profiles: %s", + binder_radio_error_string(error)); + } + + binder_network_check_initial_attach_apn(self); +} + +static +void +binder_network_set_data_profiles( + BinderNetworkObject* self) +{ + RadioClient* client = self->g->client; + const RADIO_INTERFACE iface = radio_client_interface(client); + const guint n = g_slist_length(self->data_profiles); + RadioRequest* req; + GBinderWriter writer; + GSList* l; + guint i, parent; + + if (iface >= RADIO_INTERFACE_1_4) { + RadioDataProfile_1_4* dp; + const gsize elem = sizeof(*dp); + + /* setDataProfile_1_4(int32 serial, vec); */ + req = radio_request_new(client, RADIO_REQ_SET_DATA_PROFILE_1_4, + &writer, binder_network_set_data_profiles_done, NULL, self); + + dp = gbinder_writer_malloc0(&writer, elem * n); + for (l = self->data_profiles, i = 0; i < n; l = l->next, i++) { + binder_network_fill_radio_data_profile_1_4(&writer, dp + i, + (BinderNetworkDataProfile*) l->data); + } + + parent = binder_append_vec_with_data(&writer, dp, elem, n, NULL); + for (i = 0; i < n; i++) { + binder_network_write_data_profile_strings_1_4(&writer, dp + i, + parent, i); + } + } else { + RadioDataProfile* dp; + const gsize elem = sizeof(*dp); + + /* setDataProfile(int32 serial, vec, bool roaming); */ + req = radio_request_new(client, RADIO_REQ_SET_DATA_PROFILE, + &writer, binder_network_set_data_profiles_done, NULL, self); + + dp = gbinder_writer_malloc0(&writer, elem * n); + for (l = self->data_profiles, i = 0; i < n; l = l->next, i++) { + binder_network_fill_radio_data_profile(&writer, dp + i, + (BinderNetworkDataProfile*) l->data); + } + + parent = binder_append_vec_with_data(&writer, dp, elem, n, NULL); + for (i = 0; i < n; i++) { + binder_network_write_data_profile_strings(&writer, dp + i, + parent, i); + } + gbinder_writer_append_bool(&writer, FALSE); /* isRoaming */ + } + + radio_request_drop(self->set_data_profiles_req); + self->set_data_profiles_req = req; /* Keep the ref */ + radio_request_submit(req); +} + +static +void +binder_network_check_data_profiles( + BinderNetworkObject* self) +{ + struct ofono_gprs* gprs = self->watch->gprs; + + if (gprs) { + GSList* l = NULL; + + const struct ofono_gprs_primary_context* internet = + ofono_gprs_context_settings_by_type(gprs, + OFONO_GPRS_CONTEXT_TYPE_INTERNET); + const struct ofono_gprs_primary_context* mms = + ofono_gprs_context_settings_by_type(gprs, + OFONO_GPRS_CONTEXT_TYPE_MMS); + const struct ofono_gprs_primary_context* ims = + ofono_gprs_context_settings_by_type(gprs, + OFONO_GPRS_CONTEXT_TYPE_IMS); + + if (internet) { + DBG_(self, "internet apn \"%s\"", internet->apn); + l = g_slist_append(l, binder_network_data_profile_new(internet, + RADIO_DATA_PROFILE_DEFAULT)); + } + + if (mms) { + DBG_(self, "mms apn \"%s\"", mms->apn); + l = g_slist_append(l, binder_network_data_profile_new(mms, + self->mms_data_profile_id)); + } + + if (ims) { + DBG_(self, "ims apn \"%s\"", ims->apn); + l = g_slist_append(l, binder_network_data_profile_new(ims, + RADIO_DATA_PROFILE_IMS)); + } + + if (binder_network_data_profiles_equal(self->data_profiles, l)) { + binder_network_data_profiles_free(l); + } else { + binder_network_data_profiles_free(self->data_profiles); + self->data_profiles = l; + binder_network_set_data_profiles(self); + } + } else { + binder_network_data_profiles_free(self->data_profiles); + self->data_profiles = NULL; + } +} + +static +gboolean +binder_network_need_initial_attach_apn( + BinderNetworkObject* self) +{ + return (binder_network_actual_pref_modes(self) & + OFONO_RADIO_ACCESS_MODE_LTE) ? TRUE : FALSE; +} + +static +gboolean +binder_network_can_set_initial_attach_apn( + BinderNetworkObject* self) +{ + struct ofono_watch* watch = self->watch; + BinderRadio* radio = self->radio; + + return binder_network_need_initial_attach_apn(self) && + watch->gprs && radio->state == RADIO_STATE_ON && + !self->set_data_profiles_req; +} + +static +void +binder_network_set_initial_attach_apn( + BinderNetworkObject* self, + const struct ofono_gprs_primary_context* ctx) +{ + const RADIO_INTERFACE iface = radio_client_interface(self->g->client); + BinderNetworkDataProfile profile; + RadioRequest* req; + GBinderWriter writer; + guint parent; + + binder_network_data_profile_init(&profile, ctx, RADIO_DATA_PROFILE_DEFAULT); + + if (iface >= RADIO_INTERFACE_1_4) { + RadioDataProfile_1_4* dp; + + /* setInitialAttachApn_1_4(int32 serial, DataProfileInfo profile); */ + req = radio_request_new2(self->g, RADIO_REQ_SET_INITIAL_ATTACH_APN_1_4, + &writer, NULL, NULL, NULL); + + dp = binder_network_new_radio_data_profile_1_4(&writer, &profile); + parent = gbinder_writer_append_buffer_object(&writer, dp, sizeof(*dp)); + binder_network_write_data_profile_strings_1_4(&writer, dp, parent, 0); + } else { + RadioDataProfile* dp; + + /* + * setInitialAttachApn(int32 serial, DataProfileInfo profile, + * bool modemCognitive, bool isRoaming); + */ + req = radio_request_new2(self->g, RADIO_REQ_SET_INITIAL_ATTACH_APN, + &writer, NULL, NULL, NULL); + + dp = binder_network_new_radio_data_profile(&writer, &profile); + parent = gbinder_writer_append_buffer_object(&writer, dp, sizeof(*dp)); + binder_network_write_data_profile_strings(&writer, dp, parent, 0); + gbinder_writer_append_bool(&writer, FALSE); /* modemCognitive */ + gbinder_writer_append_bool(&writer, FALSE); /* isRoaming */ + } + + DBG_(self, "\"%s\"", ctx->apn); + radio_request_set_retry(req, BINDER_RETRY_MS, -1); + radio_request_set_timeout(req, INTINITE_TIMEOUT); + radio_request_drop(self->set_ia_apn_req); + self->set_ia_apn_req = req; /* Keep the ref */ + radio_request_submit(req); +} + +static +void +binder_network_try_set_initial_attach_apn( + BinderNetworkObject* self) +{ + if (self->set_initial_attach_apn && + binder_network_can_set_initial_attach_apn(self)) { + struct ofono_gprs* gprs = self->watch->gprs; + const struct ofono_gprs_primary_context* ctx = + ofono_gprs_context_settings_by_type(gprs, + OFONO_GPRS_CONTEXT_TYPE_INTERNET); + + if (ctx) { + self->set_initial_attach_apn = FALSE; + binder_network_set_initial_attach_apn(self, ctx); + } + } +} + +static +void +binder_network_check_initial_attach_apn( + BinderNetworkObject* self) +{ + const gboolean need = binder_network_need_initial_attach_apn(self); + + if (self->need_initial_attach_apn != need) { + DBG_(self, "%sneed initial attach apn", need ? "" : "don't "); + self->need_initial_attach_apn = need; + if (need) { + /* We didn't need initial attach APN and now we do */ + self->set_initial_attach_apn = TRUE; + } + } + binder_network_try_set_initial_attach_apn(self); +} + +static +void +binder_network_reset_initial_attach_apn( + BinderNetworkObject* self) +{ + if (binder_network_need_initial_attach_apn(self) && + !self->set_initial_attach_apn) { + /* Will set initial attach APN when we have a chance */ + DBG_(self, "need to set initial attach apn"); + self->set_initial_attach_apn = TRUE; + binder_network_try_set_initial_attach_apn(self); + } +} + +static +gboolean +binder_network_can_set_pref_mode( + BinderNetworkObject* self) +{ + /* + * With some modems an attempt to set rat significantly slows + * down SIM I/O, let's avoid that. + */ + return self->radio->online && binder_sim_card_ready(self->simcard) && + !self->simcard->sim_io_active && !self->timer[TIMER_SET_RAT_HOLDOFF]; +} + +static +gboolean +binder_network_set_pref_holdoff_cb( + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->timer[TIMER_SET_RAT_HOLDOFF]); + self->timer[TIMER_SET_RAT_HOLDOFF] = 0; + + binder_network_check_pref_mode(self, FALSE); + return G_SOURCE_REMOVE; +} + +static +void +binder_network_set_pref_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->set_rat_req == req); + radio_request_unref(self->set_rat_req); + self->set_rat_req = NULL; + + if (error != RADIO_ERROR_NONE) { + ofono_error("Error %d setting pref mode", error); + } + + binder_network_query_pref_mode(self); +} + +static +void +binder_network_set_pref( + BinderNetworkObject* self, + RADIO_PREF_NET_TYPE rat) +{ + BinderSimCard* card = self->simcard; + + if (!self->set_rat_req && + self->radio->online && + binder_sim_card_ready(card) && + /* + * With some modems an attempt to set rat significantly + * slows down SIM I/O, let's avoid that. + */ + !card->sim_io_active && + !self->timer[TIMER_SET_RAT_HOLDOFF]) { + RadioClient* client = self->g->client; + const RADIO_INTERFACE iface = radio_client_interface(client); + GBinderWriter writer; + + if (iface >= RADIO_INTERFACE_1_4) { + BinderRadioCaps* caps = self->caps; + RADIO_ACCESS_FAMILY raf = binder_raf_from_pref(rat); + + /* + * setPreferredNetworkTypeBitmap(int32 serial, + * bitfield networkTypeBitmap); + */ + self->set_rat_req = radio_request_new(client, + RADIO_REQ_SET_PREFERRED_NETWORK_TYPE_BITMAP, &writer, + binder_network_set_pref_cb, NULL, self); + + if (caps) raf &= caps->raf; + gbinder_writer_append_int32(&writer, raf); + } else { + /* setPreferredNetworkType(int32 serial, PreferredNetworkType); */ + self->set_rat_req = radio_request_new(client, + RADIO_REQ_SET_PREFERRED_NETWORK_TYPE, &writer, + binder_network_set_pref_cb, NULL, self); + gbinder_writer_append_int32(&writer, rat); + } + + DBG_(self, "setting rat mode %d", rat); + radio_request_set_timeout(self->set_rat_req, + self->network_mode_timeout_ms); + if (radio_request_submit(self->set_rat_req)) { + /* We have submitted the request, clear the assertion flag */ + self->assert_rat = FALSE; + } + + /* And don't do it too often */ + self->timer[TIMER_SET_RAT_HOLDOFF] = + g_timeout_add_seconds(SET_PREF_MODE_HOLDOFF_SEC, + binder_network_set_pref_holdoff_cb, self); + } else { + DBG_(self, "need to set rat mode %d", rat); + } +} + +static +void +binder_network_update_pref_mode( + BinderNetworkObject* self, + RADIO_PREF_NET_TYPE rat) +{ + if (self->rat != rat || self->assert_rat) { + binder_network_set_pref(self, rat); + } +} + +static +void +binder_network_check_pref_mode( + BinderNetworkObject* self, + gboolean immediate) +{ + BinderRadio* radio = self->radio; + + /* + * On most dual-SIM phones only one slot at a time is allowed + * to use LTE. On some phones, even if the slot which has been + * using LTE gets powered off, we still need to explicitly set + * its preferred mode to GSM, to make LTE machinery available + * to the other slot. This behavior is configurable. + */ + if (radio->state == RADIO_STATE_ON || self->force_gsm_when_radio_off) { + const enum ofono_radio_access_mode expected = + binder_network_actual_pref_modes(self); + const enum ofono_radio_access_mode actual = + binder_access_modes_from_pref(self->rat); + + if (self->timer[TIMER_FORCE_CHECK_PREF_MODE]) { + binder_network_stop_timer(self, TIMER_FORCE_CHECK_PREF_MODE); + /* + * TIMER_FORCE_CHECK_PREF_MODE is scheduled by + * binder_network_settings_pref_changed_cb and is meant + * to force radio tech check right now. + */ + immediate = TRUE; + } + + if (self->raf && actual != expected) { + DBG_(self, "rat %d raf 0x%08x (%s), expected %s", + self->rat, self->raf, + ofono_radio_access_mode_to_string(actual), + ofono_radio_access_mode_to_string(expected)); + } + + if (immediate) { + binder_network_stop_timer(self, TIMER_SET_RAT_HOLDOFF); + } + + if (actual != expected || self->assert_rat) { + const RADIO_PREF_NET_TYPE pref = + binder_network_mode_to_pref(self, expected); + + if (!self->timer[TIMER_SET_RAT_HOLDOFF]) { + binder_network_update_pref_mode(self, pref); + } else { + /* OK, later */ + DBG_(self, "need to set rat mode %d", pref); + } + } + } +} + +static +void +binder_network_assert_pref_mode( + BinderNetworkObject* self) +{ + self->assert_rat = TRUE; + binder_network_check_pref_mode(self, FALSE); +} + +static +gboolean +binder_network_query_rat_done( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args) +{ + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_PREFERRED_NETWORK_TYPE) { + /* + * getPreferredNetworkTypeResponse(RadioResponseInfo, + * PreferredNetworkType nwType); + */ + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + guint32 rat; + + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_uint32(&reader, &rat)) { + BinderNetwork* net = &self->pub; + enum ofono_radio_access_mode modes; + + self->rat = rat; + self->raf = binder_raf_from_pref(rat); + modes = binder_access_modes_from_pref(self->rat); + DBG_(self, "rat %d => raf 0x%08x (%s)", rat, self->raf, + ofono_radio_access_mode_to_string(modes)); + + if (net->pref_modes != modes) { + net->pref_modes = modes; + binder_base_emit_property_change(&self->base, + BINDER_NETWORK_PROPERTY_PREF_MODES); + } + return TRUE; + } + } else { + ofono_warn("getPreferredNetworkType error %d", error); + } + } else { + ofono_error("Unexpected getPreferredNetworkType response %d", resp); + } + } + return FALSE; +} + +static +gboolean +binder_network_query_raf_done( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args) +{ + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_PREFERRED_NETWORK_TYPE_BITMAP) { + /* + * getPreferredNetworkTypeBitmapResponse(RadioResponseInfo, + * bitfield networkTypeBitmap); + */ + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + guint32 raf; + + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_uint32(&reader, &raf)) { + BinderNetwork* net = &self->pub; + enum ofono_radio_access_mode modes; + + self->raf = raf; + self->rat = binder_pref_from_raf(raf); + modes = binder_access_modes_from_raf(raf); + DBG_(self, "raf 0x%08x => rat %d (%s)", raf, self->rat, + ofono_radio_access_mode_to_string(modes)); + + if (net->pref_modes != modes) { + net->pref_modes = modes; + binder_base_emit_property_change(&self->base, + BINDER_NETWORK_PROPERTY_PREF_MODES); + } + return TRUE; + } + } else { + ofono_warn("getPreferredNetworkTypeBitmap error %d", error); + } + } else { + ofono_error("Unexpected getPreferredNetworkTypeBitmap response %d", + resp); + } + } + return FALSE; +} + +static +void +binder_network_initial_pref_query_done( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gboolean (*handle)( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args)) +{ + binder_network_object_ref(self); + if (handle(self, status, resp, error, args)) { + /* + * At startup, the device may have an inconsistency between + * voice and data network modes, so it needs to be asserted. + */ + binder_network_assert_pref_mode(self); + } + binder_network_object_unref(self); +} + +static +void +binder_network_initial_rat_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + binder_network_initial_pref_query_done(THIS(user_data), + status, resp, error, args, binder_network_query_rat_done); +} + +static +void +binder_network_initial_raf_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + binder_network_initial_pref_query_done(THIS(user_data), + status, resp, error, args, binder_network_query_raf_done); +} + +static +void +binder_network_initial_rat_query( + BinderNetworkObject* self) +{ + RadioClient* client = self->g->client; + const RADIO_INTERFACE iface = radio_client_interface(client); + RadioRequest* req; + + if (iface >= RADIO_INTERFACE_1_4) { + /* getPreferredNetworkTypeBitmap(int32 serial) */ + req = radio_request_new2(self->g, + RADIO_REQ_GET_PREFERRED_NETWORK_TYPE_BITMAP, NULL, + binder_network_initial_raf_query_cb, NULL, self); + } else { + /* getPreferredNetworkType(int32 serial) */ + req = radio_request_new2(self->g, + RADIO_REQ_GET_PREFERRED_NETWORK_TYPE, NULL, + binder_network_initial_rat_query_cb, NULL, self); + } + + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_network_pref_query_done( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gboolean (*handle)( + BinderNetworkObject* self, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args)) +{ + GASSERT(self->query_rat_req); + radio_request_unref(self->query_rat_req); + self->query_rat_req = NULL; + + binder_network_object_ref(self); + if (handle(self, status, resp, error, args) && + binder_network_can_set_pref_mode(self)) { + binder_network_check_pref_mode(self, FALSE); + } + binder_network_object_unref(self); +} + +static +void +binder_network_rat_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + binder_network_pref_query_done(THIS(user_data), + status, resp, error, args, binder_network_query_rat_done); +} + +static +void +binder_network_raf_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + binder_network_pref_query_done(THIS(user_data), + status, resp, error, args, binder_network_query_raf_done); +} + +static +void +binder_network_query_pref_mode( + BinderNetworkObject* self) +{ + RadioClient* client = self->g->client; + const RADIO_INTERFACE iface = radio_client_interface(client); + RadioRequest* req; + + if (iface >= RADIO_INTERFACE_1_4) { + /* getPreferredNetworkTypeBitmap(int32 serial); */ + req = radio_request_new(client, + RADIO_REQ_GET_PREFERRED_NETWORK_TYPE_BITMAP, NULL, + binder_network_raf_query_cb, NULL, self); + } else { + /* getPreferredNetworkType(int32 serial); */ + req = radio_request_new(client, + RADIO_REQ_GET_PREFERRED_NETWORK_TYPE, NULL, + binder_network_rat_query_cb, NULL, self); + } + + radio_request_set_retry_func(req, binder_network_retry); + radio_request_set_retry(req, BINDER_RETRY_MS, -1); + radio_request_set_timeout(req, INTINITE_TIMEOUT); + radio_request_drop(self->query_rat_req); + self->query_rat_req = req; /* Keep the ref */ + if (!radio_request_submit(req)) { + radio_request_drop(self->query_rat_req); + self->query_rat_req = NULL; + } +} + +enum ofono_radio_access_mode +binder_network_max_supported_mode( + BinderNetwork* net) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self)) { + BinderSimSettings* settings = net->settings; + BinderRadioCaps* caps = self->caps; + + if (caps) { + return ofono_radio_access_max_mode(settings->techs & + binder_access_modes_from_raf(caps->raf)); + } else { + return ofono_radio_access_max_mode(settings->techs); + } + } + return OFONO_RADIO_ACCESS_MODE_NONE; +} + +static +void +binder_network_caps_raf_handler( + BinderRadioCaps* caps, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + DBG_(self, "raf 0x%08x (%s)", caps->raf, ofono_radio_access_mode_to_string + (binder_access_modes_from_raf(caps->raf))); + binder_network_check_pref_mode(self, TRUE); +} + +static +void +binder_network_radio_capability_tx_done_cb( + BinderRadioCapsManager* mgr, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + DBG_(self, ""); + binder_network_assert_pref_mode(self); +} + +static +void +binder_network_release_radio_caps( + BinderNetworkObject* self) +{ + BinderRadioCaps* caps = self->caps; + + if (caps) { + binder_radio_caps_manager_remove_all_handlers(caps->mgr, + self->caps_mgr_event_id); + binder_radio_caps_remove_handler(caps, self->caps_raf_event_id); + binder_radio_caps_unref(caps); + + self->caps_raf_event_id = 0; + self->caps = NULL; + } +} + +static +void +binder_network_attach_radio_caps( + BinderNetworkObject* self, + BinderRadioCaps* caps) +{ + self->caps = binder_radio_caps_ref(caps); + self->caps_raf_event_id = binder_radio_caps_add_raf_handler(caps, + binder_network_caps_raf_handler, self); + self->caps_mgr_event_id[RADIO_CAPS_MGR_TX_DONE] = + binder_radio_caps_manager_add_tx_done_handler(caps->mgr, + binder_network_radio_capability_tx_done_cb, self); + self->caps_mgr_event_id[RADIO_CAPS_MGR_TX_ABORTED] = + binder_radio_caps_manager_add_tx_aborted_handler(caps->mgr, + binder_network_radio_capability_tx_done_cb, self); +} + +static +void +binder_network_state_changed_cb( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + DBG_(self, ""); + GASSERT(code == RADIO_IND_NETWORK_STATE_CHANGED); + binder_network_poll_state(self); +} + +static +void +binder_network_modem_reset_cb( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + DBG_(self, "%s", binder_read_hidl_string(args)); + GASSERT(code == RADIO_IND_MODEM_RESET); + + /* Drop all pending requests */ + radio_request_drop(self->operator_poll_req); + radio_request_drop(self->voice_poll_req); + radio_request_drop(self->data_poll_req); + radio_request_drop(self->query_rat_req); + radio_request_drop(self->set_rat_req); + radio_request_drop(self->set_data_profiles_req); + radio_request_drop(self->set_ia_apn_req); + self->operator_poll_req = NULL; + self->voice_poll_req = NULL; + self->data_poll_req = NULL; + self->query_rat_req = NULL; + self->set_rat_req = NULL; + self->set_data_profiles_req = NULL; + self->set_ia_apn_req = NULL; + + binder_network_initial_rat_query(self); + binder_network_reset_initial_attach_apn(self); +} + +static +void +binder_network_radio_state_cb( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + binder_network_check_pref_mode(self, FALSE); + if (radio->state == RADIO_STATE_ON) { + binder_network_poll_state(self); + binder_network_try_set_initial_attach_apn(self); + } +} + +static +void +binder_network_radio_online_cb( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + if (binder_network_can_set_pref_mode(self)) { + binder_network_check_pref_mode(self, TRUE); + } +} + +static +gboolean +binder_network_check_pref_mode_cb( + gpointer user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + GASSERT(self->timer[TIMER_FORCE_CHECK_PREF_MODE]); + self->timer[TIMER_FORCE_CHECK_PREF_MODE] = 0; + + DBG_(self, "checking pref mode"); + binder_network_check_pref_mode(self, TRUE); + binder_network_check_initial_attach_apn(self); + return G_SOURCE_REMOVE; +} + +static +void +binder_network_settings_pref_changed_cb( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + /* + * Postpone binder_network_check_pref_mode because other pref mode + * listeners (namely, binder_data) may want to tweak allowed_modes + */ + if (!self->timer[TIMER_FORCE_CHECK_PREF_MODE]) { + DBG_(self, "scheduling pref mode check"); + self->timer[TIMER_FORCE_CHECK_PREF_MODE] = + g_idle_add(binder_network_check_pref_mode_cb, self); + } else { + DBG_(self, "pref mode check already scheduled"); + } +} + +static +void +binder_network_sim_status_changed_cb( + BinderSimCard* card, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + const BinderSimCardStatus* status = card->status; + + if (!status || status->card_state != RADIO_CARD_STATE_PRESENT) { + binder_network_reset_initial_attach_apn(self); + } + if (binder_network_can_set_pref_mode(self)) { + binder_network_check_pref_mode(self, FALSE); + } +} + +static +void +binder_network_watch_gprs_cb( + struct ofono_watch* watch, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + DBG_(self, "gprs %s", watch->gprs ? "appeared" : "is gone"); + if (self->use_data_profiles) { + binder_network_check_data_profiles(self); + } + binder_network_check_initial_attach_apn(self); +} + +static +void +binder_network_watch_gprs_settings_cb( + struct ofono_watch* watch, + enum ofono_gprs_context_type type, + const struct ofono_gprs_primary_context* settings, + void* user_data) +{ + BinderNetworkObject* self = THIS(user_data); + + if (self->use_data_profiles) { + binder_network_check_data_profiles(self); + } + + if (type == OFONO_GPRS_CONTEXT_TYPE_INTERNET) { + binder_network_check_initial_attach_apn(self); + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderNetwork* +binder_network_new( + const char* path, + RadioClient* client, + const char* log_prefix, + BinderRadio* radio, + BinderSimCard* simcard, + BinderSimSettings* settings, + const BinderSlotConfig* config) +{ + BinderNetworkObject* self = g_object_new(THIS_TYPE, NULL); + BinderNetwork* net = &self->pub; + + net->settings = binder_sim_settings_ref(settings); + self->g = radio_request_group_new(client); /* Keeps ref to client */ + self->radio = binder_radio_ref(radio); + self->simcard = binder_sim_card_ref(simcard); + self->watch = ofono_watch_new(path); + self->log_prefix = binder_dup_prefix(log_prefix); + DBG_(self, ""); + + /* Copy relevant config values */ + self->lte_network_mode = config->lte_network_mode; + self->umts_network_mode = config->umts_network_mode; + self->network_mode_timeout_ms = config->network_mode_timeout_ms; + self->force_gsm_when_radio_off = config->force_gsm_when_radio_off; + self->use_data_profiles = config->use_data_profiles; + self->mms_data_profile_id = config->mms_data_profile_id; + + /* Register listeners */ + self->ind_id[IND_NETWORK_STATE] = + radio_client_add_indication_handler(client, + RADIO_IND_NETWORK_STATE_CHANGED, + binder_network_state_changed_cb, self); + self->ind_id[IND_MODEM_RESET] = + radio_client_add_indication_handler(client, + RADIO_IND_MODEM_RESET, + binder_network_modem_reset_cb, self); + + self->radio_event_id[RADIO_EVENT_STATE_CHANGED] = + binder_radio_add_property_handler(self->radio, + BINDER_RADIO_PROPERTY_STATE, + binder_network_radio_state_cb, self); + self->radio_event_id[RADIO_EVENT_ONLINE_CHANGED] = + binder_radio_add_property_handler(self->radio, + BINDER_RADIO_PROPERTY_ONLINE, + binder_network_radio_online_cb, self); + + self->simcard_event_id[SIM_EVENT_STATUS_CHANGED] = + binder_sim_card_add_status_changed_handler(self->simcard, + binder_network_sim_status_changed_cb, self); + self->simcard_event_id[SIM_EVENT_IO_ACTIVE_CHANGED] = + binder_sim_card_add_sim_io_active_changed_handler(self->simcard, + binder_network_sim_status_changed_cb, self); + self->settings_event_id = + binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_PREF, + binder_network_settings_pref_changed_cb, self); + + self->watch_ids[WATCH_EVENT_GPRS] = + ofono_watch_add_gprs_changed_handler(self->watch, + binder_network_watch_gprs_cb, self); + self->watch_ids[WATCH_EVENT_GPRS_SETTINGS] = + ofono_watch_add_gprs_settings_changed_handler(self->watch, + binder_network_watch_gprs_settings_cb, self); + + /* Query the initial state */ + binder_network_initial_rat_query(self); + + if (radio->state == RADIO_STATE_ON) { + binder_network_poll_state(self); + } + + self->set_initial_attach_apn = self->need_initial_attach_apn = + binder_network_need_initial_attach_apn(self); + + if (self->use_data_profiles) { + binder_network_check_data_profiles(self); + } + binder_network_try_set_initial_attach_apn(self); + return net; +} + +BinderNetwork* +binder_network_ref( + BinderNetwork* net) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self)) { + binder_network_object_ref(self); + return net; + } else { + return NULL; + } +} + +void +binder_network_unref( + BinderNetwork* net) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self)) { + binder_network_object_unref(self); + } +} + +void +binder_network_query_registration_state( + BinderNetwork* net) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (self) { + DBG_(self, ""); + binder_network_poll_registration_state(self); + } +} + +void +binder_network_set_allowed_modes( + BinderNetwork* net, + enum ofono_radio_access_mode modes, + gboolean force_check) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self)) { + const gboolean changed = (net->allowed_modes != modes); + + if (changed) { + net->allowed_modes = modes; + DBG_(self, "allowed modes 0x%02x (%s)", modes, + ofono_radio_access_mode_to_string(modes)); + binder_base_emit_property_change(&self->base, + BINDER_NETWORK_PROPERTY_ALLOWED_MODES); + } + + if (changed || force_check) { + binder_network_check_pref_mode(self, TRUE); + } + } +} + +void +binder_network_set_radio_caps( + BinderNetwork* net, + BinderRadioCaps* caps) +{ + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self) && self->caps != caps) { + binder_network_release_radio_caps(self); + if (caps) { + binder_network_attach_radio_caps(self, caps); + } + binder_network_check_pref_mode(self, TRUE); + } +} + +gulong +binder_network_add_property_handler( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + BinderNetworkPropertyFunc callback, + void* user_data) +{ + BinderNetworkObject* self = binder_network_cast(net); + + return G_LIKELY(self) ? binder_base_add_property_handler(&self->base, + property, G_CALLBACK(callback), user_data) : 0; +} + +void +binder_network_remove_handler( + BinderNetwork* net, + gulong id) +{ + if (G_LIKELY(id)) { + BinderNetworkObject* self = binder_network_cast(net); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +void +binder_network_remove_handlers( + BinderNetwork* net, + gulong* ids, + int count) +{ + gutil_disconnect_handlers(binder_network_cast(net), ids, count); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_network_object_init( + BinderNetworkObject* self) +{ + BinderNetwork* net = &self->pub; + + self->rat = RADIO_PREF_NET_INVALID; + binder_network_reset_state(&net->voice); + binder_network_reset_state(&net->data); +} + +static +void +binder_network_object_finalize( + GObject* object) +{ + BinderNetworkObject* self = THIS(object); + BinderNetwork* net = &self->pub; + BINDER_NETWORK_TIMER tid; + + DBG_(self, ""); + for (tid=0; tidoperator_poll_req); + radio_request_drop(self->voice_poll_req); + radio_request_drop(self->data_poll_req); + radio_request_drop(self->query_rat_req); + radio_request_drop(self->set_rat_req); + radio_request_drop(self->set_data_profiles_req); + radio_request_drop(self->set_ia_apn_req); + + ofono_watch_remove_all_handlers(self->watch, self->watch_ids); + ofono_watch_unref(self->watch); + + radio_client_remove_all_handlers(self->g->client, self->ind_id); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + binder_network_release_radio_caps(self); + binder_radio_remove_all_handlers(self->radio, self->radio_event_id); + binder_radio_unref(self->radio); + binder_sim_card_remove_all_handlers(self->simcard, self->simcard_event_id); + binder_sim_card_unref(self->simcard); + binder_sim_settings_remove_handler(net->settings, self->settings_event_id); + binder_sim_settings_unref(net->settings); + + g_slist_free_full(self->data_profiles, g_free); + g_free(self->log_prefix); + + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_network_object_class_init( + BinderNetworkObjectClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_network_object_finalize; + BINDER_BASE_CLASS(klass)->public_offset = + G_STRUCT_OFFSET(BinderNetworkObject, pub); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_network.h b/src/binder_network.h new file mode 100644 index 0000000..ed138df --- /dev/null +++ b/src/binder_network.h @@ -0,0 +1,137 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_NETWORK_H +#define BINDER_NETWORK_H + +#include "binder_types.h" + +#include +#include + +typedef enum binder_network_property { + BINDER_NETWORK_PROPERTY_ANY, + BINDER_NETWORK_PROPERTY_VOICE_STATE, + BINDER_NETWORK_PROPERTY_DATA_STATE, + BINDER_NETWORK_PROPERTY_MAX_DATA_CALLS, + BINDER_NETWORK_PROPERTY_OPERATOR, + BINDER_NETWORK_PROPERTY_PREF_MODES, + BINDER_NETWORK_PROPERTY_ALLOWED_MODES, + BINDER_NETWORK_PROPERTY_COUNT +} BINDER_NETWORK_PROPERTY; + +typedef struct binder_registration_state { + enum ofono_netreg_status status; + enum ofono_access_technology access_tech; + RADIO_TECH radio_tech; + gboolean em_enabled; /* TRUE is emergency calls are enabled */ + int lac; + int ci; +} BinderRegistrationState; + +struct binder_network { + BinderSimSettings* settings; + BinderRegistrationState voice; + BinderRegistrationState data; + int max_data_calls; + const struct ofono_network_operator* operator; + enum ofono_radio_access_mode pref_modes; /* Mask */ + enum ofono_radio_access_mode allowed_modes; /* Mask */ +}; + +typedef +void +(*BinderNetworkPropertyFunc)( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + void* user_data); + +BinderNetwork* +binder_network_new( + const char* path, + RadioClient* client, + const char* log_prefix, + BinderRadio* radio, + BinderSimCard* sim_card, + BinderSimSettings* settings, + const BinderSlotConfig* config) + BINDER_INTERNAL; + +BinderNetwork* +binder_network_ref( + BinderNetwork* net) + BINDER_INTERNAL; + +void +binder_network_unref( + BinderNetwork* net) + BINDER_INTERNAL; + +void +binder_network_set_radio_caps( + BinderNetwork* net, + BinderRadioCaps* caps) + BINDER_INTERNAL; + +void +binder_network_set_allowed_modes( + BinderNetwork* net, + enum ofono_radio_access_mode modes, + gboolean force_check) + BINDER_INTERNAL; + +enum ofono_radio_access_mode +binder_network_max_supported_mode( + BinderNetwork* self) + BINDER_INTERNAL; + +void +binder_network_query_registration_state( + BinderNetwork* net) + BINDER_INTERNAL; + +gulong +binder_network_add_property_handler( + BinderNetwork* net, + BINDER_NETWORK_PROPERTY property, + BinderNetworkPropertyFunc callback, + void* user_data) + BINDER_INTERNAL; + +void +binder_network_remove_handler( + BinderNetwork* net, + gulong id) + BINDER_INTERNAL; + +void +binder_network_remove_handlers( + BinderNetwork* net, + gulong* ids, + int count) + BINDER_INTERNAL; + +#define binder_network_remove_all_handlers(net, ids) \ + binder_network_remove_handlers(net, ids, G_N_ELEMENTS(ids)) + +#endif /* BINDER_NETWORK_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_plugin.c b/src/binder_plugin.c new file mode 100644 index 0000000..3211a2f --- /dev/null +++ b/src/binder_plugin.c @@ -0,0 +1,2184 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_call_barring.h" +#include "binder_call_forwarding.h" +#include "binder_call_settings.h" +#include "binder_call_volume.h" +#include "binder_cbs.h" +#include "binder_cell_info.h" +#include "binder_data.h" +#include "binder_devinfo.h" +#include "binder_devmon.h" +#include "binder_gprs.h" +#include "binder_gprs_context.h" +#include "binder_log.h" +#include "binder_logger.h" +#include "binder_modem.h" +#include "binder_netreg.h" +#include "binder_network.h" +#include "binder_radio.h" +#include "binder_radio_caps.h" +#include "binder_radio_settings.h" +#include "binder_sim.h" +#include "binder_sim_card.h" +#include "binder_sim_settings.h" +#include "binder_sms.h" +#include "binder_stk.h" +#include "binder_ussd.h" +#include "binder_util.h" +#include "binder_voicecall.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BINDER_SLOT_NUMBER_AUTOMATIC (0xffffffff) +#define BINDER_GET_DEVICE_IDENTITY_RETRIES_LAST 2 + +#define BINDER_CONF_FILE "binder.conf" +#define BINDER_CONF_LIST_DELIMITER ',' +#define BINDER_SLOT_RADIO_INTERFACE_1_0 "1.0" +#define BINDER_SLOT_RADIO_INTERFACE_1_1 "1.1" +#define BINDER_SLOT_RADIO_INTERFACE_1_2 "1.2" +#define BINDER_SLOT_RADIO_INTERFACE_1_3 "1.3" +#define BINDER_SLOT_RADIO_INTERFACE_1_4 "1.4" + +static const char* const binder_radio_ifaces[] = { + RADIO_1_0, /* android.hardware.radio@1.0::IRadio */ + RADIO_1_1, /* android.hardware.radio@1.1::IRadio */ + RADIO_1_2, /* android.hardware.radio@1.2::IRadio */ + RADIO_1_3, /* android.hardware.radio@1.3::IRadio */ + RADIO_1_4 /* android.hardware.radio@1.4::IRadio */ +}; + +/* + * The convention is that the keys which can only appear in the [Settings] + * section start with the upper case, those which may appear in the [slotX] + * i.e. slot specific section start with lower case. + * + * Slot specific settings may also appear in the [Settings] section in which + * case they apply to all modems. The exceptions are "path" and "slot" values + * which must be unique and therefore must appear in the section(s) for the + * respective slot(s). + */ +#define BINDER_CONF_PLUGIN_DEVICE "Device" +#define BINDER_CONF_PLUGIN_IDENTITY "Identity" +#define BINDER_CONF_PLUGIN_3GLTE_HANDOVER "3GLTEHandover" +#define BINDER_CONF_PLUGIN_MAX_NON_DATA_MODE "MaxNonDataMode" +#define BINDER_CONF_PLUGIN_SET_RADIO_CAP "SetRadioCapability" +#define BINDER_CONF_PLUGIN_EXPECT_SLOTS "ExpectSlots" +#define BINDER_CONF_PLUGIN_IGNORE_SLOTS "IgnoreSlots" + +#define BINDER_CONF_SLOT_PATH "path" /* Slot specific */ +#define BINDER_CONF_SLOT_NUMBER "slot" /* Slot specific */ +#define BINDER_CONF_SLOT_RADIO_INTERFACE "radioInterface" +#define BINDER_CONF_SLOT_START_TIMEOUT_MS "startTimeout" +#define BINDER_CONF_SLOT_REQUEST_TIMEOUT_MS "timeout" +#define BINDER_CONF_SLOT_DISABLE_FEATURES "disableFeatures" +#define BINDER_CONF_SLOT_EMPTY_PIN_QUERY "emptyPinQuery" +#define BINDER_CONF_SLOT_DEVMON "deviceStateTracking" +#define BINDER_CONF_SLOT_USE_DATA_PROFILES "useDataProfiles" +#define BINDER_CONF_SLOT_ALLOW_DATA_REQ "allowDataReq" +#define BINDER_CONF_SLOT_REPLACE_STRANGE_OPER "replaceStrangeOperatorNames" +#define BINDER_CONF_SLOT_SIGNAL_STRENGTH_RANGE "signalStrengthRange" + +/* Defaults */ +#define BINDER_DEFAULT_RADIO_INTERFACE RADIO_INTERFACE_1_2 +#define BINDER_DEFAULT_PLUGIN_DEVICE GBINDER_DEFAULT_HWBINDER +#define BINDER_DEFAULT_PLUGIN_IDENTITY "radio:radio" +#define BINDER_DEFAULT_PLUGIN_DM_FLAGS BINDER_DATA_MANAGER_3GLTE_HANDOVER +#define BINDER_DEFAULT_MAX_NON_DATA_MODE OFONO_RADIO_ACCESS_MODE_UMTS +#define BINDER_DEFAULT_SLOT_PATH_PREFIX "ril" +#define BINDER_DEFAULT_SLOT_TECHS OFONO_RADIO_ACCESS_MODE_ALL +#define BINDER_DEFAULT_SLOT_LTE_MODE RADIO_PREF_NET_LTE_GSM_WCDMA +#define BINDER_DEFAULT_SLOT_UMTS_MODE RADIO_PREF_NET_GSM_WCDMA_AUTO +#define BINDER_DEFAULT_SLOT_NETWORK_MODE_TIMEOUT_MS (20*1000) /* ms */ +#define BINDER_DEFAULT_SLOT_NETWORK_SELECTION_TIMEOUT_MS (100*1000) /* ms */ +#define BINDER_DEFAULT_SLOT_DBM_WEAK (-100) /* 0.0000000001 mW */ +#define BINDER_DEFAULT_SLOT_DBM_STRONG (-60) /* 0.000001 mW */ +#define BINDER_DEFAULT_SLOT_FEATURES BINDER_FEATURE_ALL +#define BINDER_DEFAULT_SLOT_EMPTY_PIN_QUERY TRUE +#define BINDER_DEFAULT_SLOT_RADIO_POWER_CYCLE FALSE +#define BINDER_DEFAULT_SLOT_CONFIRM_RADIO_POWER_ON FALSE +#define BINDER_DEFAULT_SLOT_QUERY_AVAILABLE_BAND_MODE TRUE +#define BINDER_DEFAULT_SLOT_REPLACE_STRANGE_OPER FALSE +#define BINDER_DEFAULT_SLOT_FORCE_GSM_WHEN_RADIO_OFF FALSE +#define BINDER_DEFAULT_SLOT_USE_DATA_PROFILES TRUE +#define BINDER_DEFAULT_SLOT_MMS_DATA_PROFILE_ID RADIO_DATA_PROFILE_DEFAULT +#define BINDER_DEFAULT_SLOT_FLAGS OFONO_SLOT_NO_FLAGS +#define BINDER_DEFAULT_SLOT_CELL_INFO_INTERVAL_SHORT_MS (2000) /* 2 sec */ +#define BINDER_DEFAULT_SLOT_CELL_INFO_INTERVAL_LONG_MS (30000) /* 30 sec */ +#define BINDER_DEFAULT_SLOT_REQ_TIMEOUT_MS 0 /* Use library default */ +#define BINDER_DEFAULT_SLOT_START_TIMEOUT_MS (30*1000) /* 30 sec */ +#define BINDER_DEFAULT_SLOT_DEVMON BINDER_DEVMON_ALL +#define BINDER_DEFAULT_SLOT_ALLOW_DATA BINDER_ALLOW_DATA_ENABLED +#define BINDER_DEFAULT_SLOT_DATA_CALL_RETRY_LIMIT 4 +#define BINDER_DEFAULT_SLOT_DATA_CALL_RETRY_DELAY_MS 200 /* ms */ + +/* Modem error ids */ +#define BINDER_ERROR_ID_DEATH "binder-death" +#define BINDER_ERROR_ID_CAPS_SWITCH_ABORTED "binder-caps-switch-aborted" + +enum binder_plugin_client_events { + CLIENT_EVENT_CONNECTED, + CLIENT_EVENT_DEATH, + CLIENT_EVENT_RADIO_STATE_CHANGED, + CLIENT_EVENT_COUNT +}; + +enum binder_plugin_watch_events { + WATCH_EVENT_MODEM, + WATCH_EVENT_COUNT +}; + +enum binder_slot_events { + SLOT_EVENT_ENABLED, + SLOT_EVENT_DATA_ROLE, + SLOT_EVENT_COUNT +}; + +typedef enum binder_set_radio_cap_opt { + BINDER_SET_RADIO_CAP_AUTO, + BINDER_SET_RADIO_CAP_ENABLED, + BINDER_SET_RADIO_CAP_DISABLED +} BINDER_SET_RADIO_CAP_OPT; + +typedef enum binder_devmon_opt { + BINDER_DEVMON_NONE = 0x01, + BINDER_DEVMON_DS = 0x02, + BINDER_DEVMON_IF = 0x04, + BINDER_DEVMON_ALL = BINDER_DEVMON_DS | BINDER_DEVMON_IF +} BINDER_DEVMON_OPT; + +typedef struct binder_plugin_identity { + uid_t uid; + gid_t gid; +} BinderPluginIdentity; + +typedef struct binder_plugin_settings { + BINDER_DATA_MANAGER_FLAGS dm_flags; + BINDER_SET_RADIO_CAP_OPT set_radio_cap; + BinderPluginIdentity identity; + enum ofono_radio_access_mode non_data_mode; +} BinderPluginSettings; + +typedef struct ofono_slot_driver_data { + struct ofono_slot_manager* slot_manager; + GDBusConnection* system_bus; + RadioConfig* radio_config; + BinderLogger* radio_config_trace; + BinderLogger* radio_config_dump; + BinderDataManager* data_manager; + BinderRadioCapsManager* caps_manager; + BinderPluginSettings settings; + gulong caps_manager_event_id; + guint start_timeout_id; + char* dev; + GSList* slots; +} BinderPlugin; + +typedef struct binder_slot { + GBinderServiceManager* sm; + RADIO_INTERFACE version; + RadioInstance* instance; + RadioClient* client; + BinderPlugin* plugin; + BinderLogger* log_trace; + BinderLogger* log_dump; + BinderData* data; + BinderDevmon* devmon; + BinderDevmonIo* devmon_io; + BinderRadio* radio; + BinderModem* modem; + BinderNetwork* network; + BinderRadioCaps* caps; + BinderRadioCapsRequest* caps_req; + BinderSimCard* sim_card; + BinderSimSettings* sim_settings; + BinderSlotConfig config; + BinderDataOptions data_opt; + struct ofono_slot* handle; + struct ofono_cell_info* cell_info; + struct ofono_watch* watch; + enum ofono_slot_flags slot_flags; + RadioRequest* imei_req; + RadioRequest* caps_check_req; + gulong name_watch_id; + gulong list_call_id; + gulong connected_id; + gulong client_event_id[CLIENT_EVENT_COUNT]; + gulong watch_event_id[WATCH_EVENT_COUNT]; + gulong slot_event_id[SLOT_EVENT_COUNT]; + gulong sim_card_state_event_id; + gboolean received_sim_status; + char* name; + char* path; + char* imei; + char* imeisv; + int req_timeout_ms; /* Request timeout, in milliseconds */ + guint start_timeout_ms; + guint start_timeout_id; +} BinderSlot; + +typedef struct binder_plugin_module { + void (*init)(void); + void (*cleanup)(void); +} BinderPluginModule; + +static const BinderPluginModule binder_plugin_modules[] = { + { binder_call_barring_init, binder_call_barring_cleanup }, + { binder_call_forwarding_init, binder_call_forwarding_cleanup }, + { binder_call_settings_init, binder_call_settings_cleanup }, + { binder_call_volume_init, binder_call_volume_cleanup }, + { binder_cbs_init, binder_cbs_cleanup }, + { binder_devinfo_init, binder_devinfo_cleanup }, + { binder_gprs_context_init, binder_gprs_context_cleanup }, + { binder_gprs_init, binder_gprs_cleanup }, + { binder_modem_init, binder_modem_cleanup }, + { binder_netreg_init, binder_netreg_cleanup }, + { binder_radio_settings_init, binder_radio_settings_cleanup }, + { binder_sim_init, binder_sim_cleanup }, + { binder_sms_init, binder_sms_cleanup }, + { binder_stk_init, binder_stk_cleanup }, + { binder_ussd_init, binder_ussd_cleanup }, + { binder_voicecall_init, binder_voicecall_cleanup } +}; + +GLOG_MODULE_DEFINE("binderplugin"); + +typedef +void +(*BinderPluginSlotFunc)( + BinderSlot* slot); + +typedef +void +(*BinderPluginSlotParamFunc)( + BinderSlot* slot, + void* param); + +static +void +binder_plugin_slot_check( + BinderSlot* slot); + +static +void +binder_plugin_slot_init_client( + BinderSlot* slot); + +static +void +binder_plugin_slot_retry_init_client( + BinderSlot* slot); + +static +void +binder_plugin_manager_started( + BinderPlugin* plugin); + +static +void +binder_logger_trace_notify( + struct ofono_debug_desc* desc); + +static +void +binder_logger_dump_notify( + struct ofono_debug_desc* desc); + +static struct ofono_debug_desc binder_logger_trace OFONO_DEBUG_ATTR = { + .name = "binder_trace", + .flags = OFONO_DEBUG_FLAG_DEFAULT | OFONO_DEBUG_FLAG_HIDE_NAME, + .notify = binder_logger_trace_notify +}; + +static struct ofono_debug_desc binder_logger_dump OFONO_DEBUG_ATTR = { + .name = "binder_dump", + .flags = OFONO_DEBUG_FLAG_DEFAULT | OFONO_DEBUG_FLAG_HIDE_NAME, + .notify = binder_logger_dump_notify +}; + +static inline gboolean binder_plugin_multisim(BinderPlugin* plugin) + { return plugin->slots && plugin->slots->next; } + +static +void +binder_radio_config_trace_update( + BinderPlugin* plugin) +{ + if (plugin) { + if (binder_logger_trace.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!plugin->radio_config_trace) { + plugin->radio_config_trace = + binder_logger_new_config_trace(plugin->radio_config); + } + } else if (plugin->radio_config_trace) { + binder_logger_free(plugin->radio_config_trace); + plugin->radio_config_trace = NULL; + } + } +} + +static +void +binder_radio_config_dump_update( + BinderPlugin* plugin) +{ + if (plugin) { + if (binder_logger_dump.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!plugin->radio_config_dump) { + plugin->radio_config_dump = + binder_logger_new_config_dump(plugin->radio_config); + } + } else if (plugin->radio_config_dump) { + binder_logger_free(plugin->radio_config_dump); + plugin->radio_config_dump = NULL; + } + } +} + +static +void +binder_plugin_foreach_slot_param( + BinderPlugin* plugin, + BinderPluginSlotParamFunc fn, + void* param) +{ + if (plugin) { + GSList* l = plugin->slots; + + while (l) { + GSList* next = l->next; + + fn((BinderSlot*)l->data, param); + l = next; + } + } +} + +static +void +binder_plugin_foreach_slot( + BinderPlugin* plugin, + BinderPluginSlotFunc fn) +{ + if (plugin) { + GSList* l = plugin->slots; + + while (l) { + GSList* next = l->next; + + fn((BinderSlot*)l->data); + l = next; + } + } +} + +static +void +binder_logger_dump_update_slot( + BinderSlot* slot) +{ + if (binder_logger_dump.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!slot->log_dump) { + slot->log_dump = binder_logger_new_radio_dump(slot->instance, + slot->name); + } + } else if (slot->log_dump) { + binder_logger_free(slot->log_dump); + slot->log_dump = NULL; + } +} + +static +void +binder_logger_trace_update_slot( + BinderSlot* slot) +{ + if (binder_logger_trace.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!slot->log_trace) { + slot->log_trace = binder_logger_new_radio_trace(slot->instance, + slot->name); + } + } else if (slot->log_trace) { + binder_logger_free(slot->log_trace); + slot->log_trace = NULL; + } +} + +static +void +binder_plugin_check_if_started( + BinderPlugin* plugin) +{ + if (plugin->start_timeout_id) { + GSList* l; + + for (l = plugin->slots; l; l = l->next) { + BinderSlot* slot = l->data; + + if (!slot->handle) { + break; + } + } + + if (!l) { + DBG("Startup done!"); + g_source_remove(plugin->start_timeout_id); + /* id is zeroed by binder_plugin_manager_start_done */ + GASSERT(!plugin->start_timeout_id); + binder_plugin_manager_started(plugin); + } + } +} + +static +void +binder_plugin_slot_shutdown( + BinderSlot* slot, + gboolean kill_io) +{ + if (slot->modem) { + binder_data_allow(slot->data, OFONO_SLOT_DATA_NONE); + ofono_modem_remove(slot->modem->ofono); + + /* + * The above call is expected to result in + * binder_plugin_slot_modem_changed getting + * called which will set slot->modem to NULL. + */ + GASSERT(!slot->modem); + } + + if (kill_io) { + if (slot->devmon_io) { + binder_devmon_io_free(slot->devmon_io); + slot->devmon_io = NULL; + } + + if (slot->cell_info) { + ofono_slot_set_cell_info(slot->handle, NULL); + ofono_cell_info_unref(slot->cell_info); + slot->cell_info = NULL; + } + + if (slot->caps) { + binder_network_set_radio_caps(slot->network, NULL); + binder_radio_caps_request_free(slot->caps_req); + binder_radio_caps_drop(slot->caps); + slot->caps_req = NULL; + slot->caps = NULL; + } + + if (slot->data) { + binder_data_allow(slot->data, OFONO_SLOT_DATA_NONE); + binder_data_unref(slot->data); + slot->data = NULL; + } + + if (slot->radio) { + binder_radio_unref(slot->radio); + slot->radio = NULL; + } + + if (slot->network) { + binder_network_unref(slot->network); + slot->network = NULL; + } + + if (slot->sim_card) { + binder_sim_card_remove_handler(slot->sim_card, + slot->sim_card_state_event_id); + binder_sim_card_unref(slot->sim_card); + slot->sim_card_state_event_id = 0; + slot->sim_card = NULL; + slot->received_sim_status = FALSE; + } + + if (slot->client) { + binder_logger_free(slot->log_trace); + binder_logger_free(slot->log_dump); + slot->log_trace = NULL; + slot->log_dump = NULL; + + radio_request_drop(slot->caps_check_req); + radio_request_drop(slot->imei_req); + slot->caps_check_req = NULL; + slot->imei_req = NULL; + +#pragma message("De-serialize requests?") + + radio_client_remove_all_handlers(slot->client, + slot->client_event_id); + + radio_instance_unref(slot->instance); + radio_client_unref(slot->client); + slot->instance = NULL; + slot->client = NULL; + } + } +} + +/* + * It seems to be necessary to kick (with RADIO_REQ_SET_RADIO_POWER) + * the modems with power on after one of the modems has been powered + * off. Which (depending on the device) may actually turn on the modems + * which are supposed to be powered off but it's better than powering + * off the modems which are supposed to be on. + */ +static +void +binder_plugin_power_check( + BinderSlot* slot) +{ + binder_radio_confirm_power_on(slot->radio); +} + +static +void +binder_plugin_radio_state_changed( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + GBinderReader reader; + RADIO_STATE radio_state = RADIO_STATE_UNAVAILABLE; + + /* radioStateChanged(RadioIndicationType, RadioState radioState); */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, (gint32*) &radio_state) && + radio_state == RADIO_STATE_OFF) { + BinderSlot* slot = user_data; + + DBG("power off for slot %u", slot->config.slot); + binder_plugin_foreach_slot(slot->plugin, binder_plugin_power_check); + } +} + +static +enum ofono_slot_sim_presence +binder_plugin_sim_presence(BinderSlot *slot) +{ + const BinderSimCardStatus* status = slot->sim_card->status; + + if (status) { + switch (status->card_state) { + case RADIO_CARD_STATE_ABSENT: + return OFONO_SLOT_SIM_ABSENT; + case RADIO_CARD_STATE_PRESENT: + return OFONO_SLOT_SIM_PRESENT; + case RADIO_CARD_STATE_ERROR: + case RADIO_CARD_STATE_RESTRICTED: + break; + } + } + + return OFONO_SLOT_SIM_UNKNOWN; +} + +static +void +binder_plugin_slot_data_role_changed( + struct ofono_slot* ofono_slot, + enum ofono_slot_property property, + void* user_data) +{ + BinderSlot* slot = user_data; + const enum ofono_slot_data_role role = ofono_slot->data_role; + + binder_data_allow(slot->data, role); + binder_radio_caps_request_free(slot->caps_req); + if (role == OFONO_SLOT_DATA_NONE) { + slot->caps_req = NULL; + } else { + /* Full speed connectivity isn't required for MMS. */ + slot->caps_req = binder_radio_caps_request_new(slot->caps, + (role == OFONO_SLOT_DATA_MMS) ? OFONO_RADIO_ACCESS_MODE_GSM : + slot->sim_settings->techs, role); + } +} + +static +void +binder_plugin_modem_check( + BinderSlot* slot) +{ + if (!slot->modem && slot->handle && slot->handle->enabled && + radio_client_connected(slot->client)) { + BinderModem* modem; + + DBG("%s registering modem", slot->name); + modem = binder_modem_create(slot->client, slot->name, slot->path, + slot->imei, slot->imeisv, &slot->config, slot->radio, slot->network, + slot->sim_card, slot->data, slot->sim_settings, slot->cell_info); + + if (modem) { + slot->modem = modem; + } else { + binder_plugin_slot_shutdown(slot, TRUE); + } + } +} + +static +void +binder_plugin_slot_enabled_changed( + struct ofono_slot* ofono_slot, + enum ofono_slot_property property, + void* user_data) +{ + BinderSlot* slot = user_data; + + if (ofono_slot->enabled) { + binder_plugin_modem_check(slot); + radio_instance_set_enabled(slot->instance, TRUE); + } else { + radio_instance_set_enabled(slot->instance, FALSE); + binder_plugin_slot_shutdown(slot, FALSE); + } +} + +static +void +binder_plugin_slot_startup_check( + BinderSlot* slot) +{ + BinderPlugin* plugin = slot->plugin; + + if (!slot->handle && radio_client_connected(slot->client) && + !slot->imei_req && slot->imei && slot->start_timeout_id) { + struct ofono_slot* ofono_slot; + + /* Looks like we have made it before the timeout expired */ + g_source_remove(slot->start_timeout_id); + slot->start_timeout_id = 0; + + /* Register this slot with the sailfish manager plugin */ + DBG("registering slot %s", slot->path); + ofono_slot = slot->handle = ofono_slot_add(plugin->slot_manager, + slot->path, slot->config.techs, slot->imei, + slot->imeisv, binder_plugin_sim_presence(slot), + slot->slot_flags); + + if (ofono_slot) { + radio_instance_set_enabled(slot->instance, ofono_slot->enabled); + ofono_slot_set_cell_info(ofono_slot, slot->cell_info); + slot->slot_event_id[SLOT_EVENT_DATA_ROLE] = + ofono_slot_add_property_handler(ofono_slot, + OFONO_SLOT_PROPERTY_DATA_ROLE, + binder_plugin_slot_data_role_changed, slot); + slot->slot_event_id[SLOT_EVENT_ENABLED] = + ofono_slot_add_property_handler(ofono_slot, + OFONO_SLOT_PROPERTY_ENABLED, + binder_plugin_slot_enabled_changed, slot); + } + } + + binder_plugin_modem_check(slot); + binder_plugin_check_if_started(plugin); +} + +static +void +binder_plugin_handle_error( + BinderSlot* slot, + const char* message) +{ + ofono_error("%s %s", slot->name, message); + ofono_slot_error(slot->handle, BINDER_ERROR_ID_DEATH, message); + binder_plugin_slot_shutdown(slot, TRUE); + + DBG("%s retrying", slot->name); + binder_plugin_slot_check(slot); +} + +static +void +binder_plugin_slot_death( + RadioClient* client, + void* user_data) +{ + binder_plugin_handle_error((BinderSlot*)user_data, "binder service died"); +} + +static +void +binder_plugin_caps_switch_aborted( + BinderRadioCapsManager* mgr, + void* user_data) +{ + BinderPlugin* plugin = user_data; + + DBG("radio caps switch aborted"); + ofono_slot_manager_error(plugin->slot_manager, + BINDER_ERROR_ID_CAPS_SWITCH_ABORTED, + "Capability switch transaction aborted"); +} + +static +void +binder_plugin_device_identity_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSlot *slot = user_data; + + GASSERT(slot->imei_req == req); + radio_request_unref(slot->imei_req); + slot->imei_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_DEVICE_IDENTITY) { + /* + * getDeviceIdentityResponse(RadioResponseInfo, string imei, + * string imeisv, string esn, string meid) + */ + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + const char* imei; + const char* imeisv; + + gbinder_reader_copy(&reader, args); + imei = gbinder_reader_read_hidl_string_c(&reader); + imeisv = gbinder_reader_read_hidl_string_c(&reader); + + /* + * slot->imei should be either NULL (when we get connected + * to the radio service the very first time) or match the + * already known IMEI (if radio service has crashed and we + * have reconnected) + */ + DBG("%s %s %s", slot->name, imei, imeisv); + if (slot->imei && imei && strcmp(slot->imei, imei)) { + ofono_warn("IMEI has changed \"%s\" -> \"%s\"", + slot->imei, imei); + } + + /* We assume that IMEI never changes */ + if (!slot->imei) { + slot->imei = imei ? g_strdup(imei) : + g_strdup_printf("%d", slot->config.slot); + } + + if (!slot->imeisv) { + slot->imeisv = g_strdup(imeisv ? imeisv : ""); + } + } else { + ofono_warn("getDeviceIdentity error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getDeviceIdentity response %d", resp); + } + } else { + ofono_error("getDeviceIdentity error %d", status); + } + + binder_plugin_slot_startup_check(slot); +} + +static +void +binder_plugin_slot_get_device_identity( + BinderSlot* slot, + gboolean blocking, + int retries) +{ + /* getDeviceIdentity(int32 serial) */ + RadioRequest* req = radio_request_new(slot->client, + RADIO_REQ_GET_DEVICE_IDENTITY, NULL, + binder_plugin_device_identity_cb, NULL, slot); + + radio_request_set_blocking(req, TRUE); + radio_request_set_retry(req, BINDER_RETRY_MS, retries); + radio_request_drop(slot->imei_req); + if (radio_request_submit(req)) { + DBG("%s submitted getDeviceIdentity", slot->name); + slot->imei_req = req; /* Keep the ref */ + } else { + ofono_error("Failed to submit getDeviceIdentity for %s", slot->name); + slot->imei_req = NULL; + radio_request_unref(req); + } +} + +static +void +binder_plugin_slot_sim_state_changed( + BinderSimCard* card, + void* data) +{ + BinderSlot* slot = data; + enum ofono_slot_sim_presence presence = binder_plugin_sim_presence(slot); + + if (card->status) { + switch (presence) { + case OFONO_SLOT_SIM_PRESENT: + DBG("SIM found in slot %u", slot->config.slot); + break; + case OFONO_SLOT_SIM_ABSENT: + DBG("No SIM in slot %u", slot->config.slot); + break; + default: + break; + } + + if (!slot->received_sim_status && slot->imei_req) { + /* + * We have received the SIM status but haven't yet got IMEI + * from the modem. Some modems behave this way if they don't + * have their IMEI initialized yet. Cancel the current request + * (which probably had unlimited number of retries) and give a + * few more tries (this time, limited number). + * + * Some adaptations fail RADIO_REQ_GET_DEVICE_IDENTITY until + * the modem has been properly initialized. + */ + DBG("giving slot %u last chance", slot->config.slot); + binder_plugin_slot_get_device_identity(slot, FALSE, + BINDER_GET_DEVICE_IDENTITY_RETRIES_LAST); + } + slot->received_sim_status = TRUE; + } + + ofono_slot_set_sim_presence(slot->handle, presence); +} + +static +void +binder_plugin_slot_radio_caps_cb( + const RadioCapability* cap, + void *user_data) +{ + BinderSlot* slot = user_data; + + DBG("radio caps %s", cap ? "ok" : "NOT supported"); + GASSERT(slot->caps_check_req); + radio_request_drop(slot->caps_check_req); + slot->caps_check_req = NULL; + + if (cap) { + BinderPlugin* plugin = slot->plugin; + + if (!plugin->caps_manager) { + plugin->caps_manager = + binder_radio_caps_manager_new(plugin->data_manager); + plugin->caps_manager_event_id = + binder_radio_caps_manager_add_tx_aborted_handler + (plugin->caps_manager, binder_plugin_caps_switch_aborted, + plugin); + } + + GASSERT(!slot->caps); + slot->caps = binder_radio_caps_new(plugin->caps_manager, slot->name, + slot->client, slot->watch, slot->data, slot->radio, slot->sim_card, + slot->sim_settings, &slot->config, cap); + binder_network_set_radio_caps(slot->network, slot->caps); + } +} + +static +void +binder_plugin_slot_connected( + BinderSlot* slot) +{ + BinderPlugin* plugin = slot->plugin; + const BinderPluginSettings* ps = &plugin->settings; + + GASSERT(radio_client_connected(slot->client)); + GASSERT(!slot->client_event_id[CLIENT_EVENT_CONNECTED]); + DBG("%s", slot->name); + + /* + * Ofono modem will be registered after getDeviceIdentity() call + * successfully completes. By the time ofono starts, modem may + * not be completely functional. Waiting until it responds to + * getDeviceIdentity() and retrying the request on failure + * (hopefully) gives modem and/or adaptation enough time to + * finish whatever is happening during initialization. + */ + binder_plugin_slot_get_device_identity(slot, TRUE, -1); + + GASSERT(!slot->radio); + slot->radio = binder_radio_new(slot->client, slot->name); + + /* Register RADIO_IND_RADIO_STATE_CHANGED handler only if we need one */ + GASSERT(!slot->client_event_id[CLIENT_EVENT_RADIO_STATE_CHANGED]); + if (slot->config.confirm_radio_power_on) { + slot->client_event_id[CLIENT_EVENT_RADIO_STATE_CHANGED] = + radio_client_add_indication_handler(slot->client, + RADIO_IND_RADIO_STATE_CHANGED, + binder_plugin_radio_state_changed, slot); + } + + GASSERT(!slot->sim_card); + slot->sim_card = binder_sim_card_new(slot->client, slot->config.slot); + slot->sim_card_state_event_id = + binder_sim_card_add_state_changed_handler(slot->sim_card, + binder_plugin_slot_sim_state_changed, slot); + + /* + * BinderSimCard is expected to perform getIccCardStatus() + * asynchronously and report back when request has completed: + */ + GASSERT(!slot->sim_card->status); + GASSERT(!slot->received_sim_status); + + GASSERT(!slot->network); + slot->network = binder_network_new(slot->path, slot->client, + slot->name, slot->radio, slot->sim_card, slot->sim_settings, + &slot->config); + + GASSERT(!slot->data); + slot->data = binder_data_new(plugin->data_manager, slot->client, + slot->name, slot->radio, slot->network, &slot->data_opt, + &slot->config); + + GASSERT(!slot->cell_info); + slot->cell_info = binder_cell_info_new(slot->client, + slot->name, slot->radio, slot->sim_card); + + GASSERT(!slot->caps); + GASSERT(!slot->caps_check_req); + if (binder_plugin_multisim(plugin) && + (ps->set_radio_cap == BINDER_SET_RADIO_CAP_ENABLED || + ps->set_radio_cap == BINDER_SET_RADIO_CAP_AUTO)) { + /* Check if the device really supports radio capability management */ + slot->caps_check_req = binder_radio_caps_check(slot->client, + binder_plugin_slot_radio_caps_cb, slot); + } + + GASSERT(!slot->devmon_io); + if (slot->devmon) { + slot->devmon_io = binder_devmon_start_io(slot->devmon, + slot->client, slot->handle); + } + + binder_plugin_slot_startup_check(slot); +} + +static +gboolean +binder_plugin_service_list_proc( + GBinderServiceManager* sm, + char** services, + void* data) +{ + BinderSlot* slot = data; + + slot->list_call_id = 0; + if (!slot->client) { + char* fqname = g_strconcat(binder_radio_ifaces[slot->version], "/", + slot->name, NULL); + + if (gutil_strv_contains(services, fqname)) { + DBG("found %s", fqname); + binder_plugin_slot_init_client(slot); + } else { + DBG("not found %s", fqname); + } + g_free(fqname); + } + + /* Return FALSE to free the service list */ + return FALSE; +} + +static +void +binder_plugin_slot_check( + BinderSlot* slot) +{ + gbinder_servicemanager_cancel(slot->sm, slot->list_call_id); + slot->list_call_id = gbinder_servicemanager_list(slot->sm, + binder_plugin_service_list_proc, slot); +} + +static +void +binder_plugin_service_registration_proc( + GBinderServiceManager* sm, + const char* name, + void* slot) +{ + DBG("%s is there", name); + binder_plugin_slot_check((BinderSlot*) slot); +} + +static +void +binder_plugin_slot_connected_cb( + RadioClient* client, + void* user_data) +{ + BinderSlot* slot = user_data; + + radio_client_remove_handlers(client, slot->client_event_id + + CLIENT_EVENT_CONNECTED, 1); /* Zeros the id */ + binder_plugin_slot_connected(slot); +} + +static +void +binder_plugin_slot_init_client( + BinderSlot* slot) +{ + if (!slot->client) { + const char* dev = gbinder_servicemanager_device(slot->sm); + + slot->instance = radio_instance_new_with_modem_slot_and_version(dev, + slot->name, slot->path, slot->config.slot, slot->version); + slot->client = radio_client_new(slot->instance); + if (slot->client) { + radio_client_set_default_timeout(slot->client, + slot->req_timeout_ms); + slot->client_event_id[CLIENT_EVENT_DEATH] = + radio_client_add_death_handler(slot->client, + binder_plugin_slot_death, slot); + + binder_logger_dump_update_slot(slot); + binder_logger_trace_update_slot(slot); + +#pragma message("Serialize requests at startup?") + + if (radio_client_connected(slot->client)) { + binder_plugin_slot_connected(slot); + } else { + slot->client_event_id[CLIENT_EVENT_CONNECTED] = + radio_client_add_connected_handler(slot->client, + binder_plugin_slot_connected_cb, slot); + } + } else { + radio_instance_unref(slot->instance); + slot->instance = NULL; + } + } +} + +static +GUtilInts* +binder_plugin_config_get_ints( + GKeyFile* file, + const char* group, + const char* key) +{ + char* value = ofono_conf_get_string(file, group, key); + + if (value) { + GUtilIntArray *array = gutil_int_array_new(); + char **values, **ptr; + + /* + * Some people are thinking that # is a comment + * anywhere on the line, not just at the beginning + */ + char *comment = strchr(value, '#'); + + if (comment) *comment = 0; + values = g_strsplit(value, ",", -1); + ptr = values; + + while (*ptr) { + int val; + + if (gutil_parse_int(*ptr++, 0, &val)) { + gutil_int_array_append(array, val); + } + } + + g_free(value); + g_strfreev(values); + return gutil_int_array_free_to_ints(array); + } + return NULL; +} + +static +const char* +binder_plugin_radio_interface_name( + RADIO_INTERFACE interface) +{ + switch (interface) { + case RADIO_INTERFACE_1_0: return BINDER_SLOT_RADIO_INTERFACE_1_0; + case RADIO_INTERFACE_1_1: return BINDER_SLOT_RADIO_INTERFACE_1_1; + case RADIO_INTERFACE_1_2: return BINDER_SLOT_RADIO_INTERFACE_1_2; + case RADIO_INTERFACE_1_3: return BINDER_SLOT_RADIO_INTERFACE_1_3; + case RADIO_INTERFACE_1_4: return BINDER_SLOT_RADIO_INTERFACE_1_4; + case RADIO_INTERFACE_NONE: + case RADIO_INTERFACE_COUNT: + break; + } + return NULL; +} + +static +RADIO_INTERFACE +binder_plugin_parse_radio_interface( + const char* name) +{ + if (name) { + RADIO_INTERFACE i; + + for (i = RADIO_INTERFACE_1_0; i < RADIO_INTERFACE_COUNT; i++ ) { + if (!g_strcmp0(name, binder_plugin_radio_interface_name(i))) { + return i; + } + } + } + return BINDER_DEFAULT_RADIO_INTERFACE; +} + +static +BinderSlot* +binder_plugin_create_slot( + GBinderServiceManager* sm, + const char* name, + GKeyFile* file) +{ + BinderSlot* slot = g_new0(BinderSlot, 1); + BinderSlotConfig* config = &slot->config; + BinderDataOptions* data_opt = &slot->data_opt; + GError* error = NULL; + const char* group = name; + GUtilInts* ints; + char* sval; + int ival; + + config->slot = BINDER_SLOT_NUMBER_AUTOMATIC; + config->techs = BINDER_DEFAULT_SLOT_TECHS; + config->lte_network_mode = BINDER_DEFAULT_SLOT_LTE_MODE; + config->umts_network_mode = BINDER_DEFAULT_SLOT_UMTS_MODE; + config->network_mode_timeout_ms = + BINDER_DEFAULT_SLOT_NETWORK_MODE_TIMEOUT_MS; + config->network_selection_timeout_ms = + BINDER_DEFAULT_SLOT_NETWORK_SELECTION_TIMEOUT_MS; + config->signal_strength_dbm_weak = BINDER_DEFAULT_SLOT_DBM_WEAK; + config->signal_strength_dbm_strong = BINDER_DEFAULT_SLOT_DBM_STRONG; + config->empty_pin_query = BINDER_DEFAULT_SLOT_EMPTY_PIN_QUERY; + config->radio_power_cycle = BINDER_DEFAULT_SLOT_RADIO_POWER_CYCLE; + config->confirm_radio_power_on = BINDER_DEFAULT_SLOT_CONFIRM_RADIO_POWER_ON; + config->features = BINDER_DEFAULT_SLOT_FEATURES; + config->query_available_band_mode = + BINDER_DEFAULT_SLOT_QUERY_AVAILABLE_BAND_MODE; + config->replace_strange_oper = BINDER_DEFAULT_SLOT_REPLACE_STRANGE_OPER; + config->force_gsm_when_radio_off = + BINDER_DEFAULT_SLOT_FORCE_GSM_WHEN_RADIO_OFF; + config->use_data_profiles = BINDER_DEFAULT_SLOT_USE_DATA_PROFILES; + config->mms_data_profile_id = BINDER_DEFAULT_SLOT_MMS_DATA_PROFILE_ID; + config->cell_info_interval_short_ms = + BINDER_DEFAULT_SLOT_CELL_INFO_INTERVAL_SHORT_MS; + config->cell_info_interval_long_ms = + BINDER_DEFAULT_SLOT_CELL_INFO_INTERVAL_LONG_MS; + + slot->name = g_strdup(name); + slot->sm = gbinder_servicemanager_ref(sm); + slot->version = BINDER_DEFAULT_RADIO_INTERFACE; + slot->req_timeout_ms = BINDER_DEFAULT_SLOT_REQ_TIMEOUT_MS; + slot->slot_flags = BINDER_DEFAULT_SLOT_FLAGS; + slot->start_timeout_ms = BINDER_DEFAULT_SLOT_START_TIMEOUT_MS; + data_opt->allow_data = BINDER_DEFAULT_SLOT_ALLOW_DATA; + data_opt->data_call_retry_limit = + BINDER_DEFAULT_SLOT_DATA_CALL_RETRY_LIMIT; + data_opt->data_call_retry_delay_ms = + BINDER_DEFAULT_SLOT_DATA_CALL_RETRY_DELAY_MS; + + /* These two must be in the slot group */ + + /* path */ + slot->path = g_key_file_get_string(file, group, + BINDER_CONF_SLOT_PATH, NULL); + if (slot->path) { + DBG("%s: " BINDER_CONF_SLOT_PATH " %s", group, slot->path); + } + + /* slot */ + ival = g_key_file_get_integer(file, group, + BINDER_CONF_SLOT_NUMBER, &error); + if (error) { + g_clear_error(&error); + } else if (ival >= 0) { + config->slot = ival; + DBG("%s: " BINDER_CONF_SLOT_NUMBER " %u", group, config->slot); + } + + /* Everything else may be in the [Settings] section */ + + /* radioInterface */ + sval = ofono_conf_get_string(file, group, + BINDER_CONF_SLOT_RADIO_INTERFACE); + if (sval) { + DBG("%s: " BINDER_CONF_SLOT_RADIO_INTERFACE " %s", group, sval); + slot->version = binder_plugin_parse_radio_interface(sval); + g_free(sval); + } + + /* startTimeout */ + if (ofono_conf_get_integer(file, group, + BINDER_CONF_SLOT_START_TIMEOUT_MS, &ival) && ival >= 0) { + DBG("%s: " BINDER_CONF_SLOT_START_TIMEOUT_MS " %d ms", group, ival); + slot->start_timeout_ms = ival; + } + + /* timeout */ + if (ofono_conf_get_integer(file, group, + BINDER_CONF_SLOT_REQUEST_TIMEOUT_MS, &ival) && ival >= 0) { + DBG("%s: " BINDER_CONF_SLOT_REQUEST_TIMEOUT_MS " %d ms", group, ival); + slot->req_timeout_ms = ival; + } + + /* disableFeatures */ + if (ofono_conf_get_mask(file, group, + BINDER_CONF_SLOT_DISABLE_FEATURES, &ival, + "cbs", BINDER_FEATURE_CBS, + "data", BINDER_FEATURE_DATA, + "netreg", BINDER_FEATURE_NETREG, + "pb", BINDER_FEATURE_PHONEBOOK, + "rat", BINDER_FEATURE_RADIO_SETTINGS, + "auth", BINDER_FEATURE_SIM_AUTH, + "sms", BINDER_FEATURE_SMS, + "stk", BINDER_FEATURE_STK, + "ussd", BINDER_FEATURE_USSD, + "voice", BINDER_FEATURE_VOICE, + "all", BINDER_FEATURE_ALL, NULL) && ival) { + config->features &= ~ival; + DBG("%s: " BINDER_CONF_SLOT_DISABLE_FEATURES " 0x%04x", group, ival); + } + + /* deviceStateTracking */ + if (ofono_conf_get_mask(file, group, + BINDER_CONF_SLOT_DEVMON, &ival, + "none", BINDER_DEVMON_NONE, + "all", BINDER_DEVMON_ALL, + "ds", BINDER_DEVMON_DS, + "if", BINDER_DEVMON_IF, NULL) && ival) { + DBG("%s: " BINDER_CONF_SLOT_DEVMON " 0x%04x", group, ival); + } else { + ival = BINDER_DEFAULT_SLOT_DEVMON; + } + + if (ival != BINDER_DEVMON_NONE) { + BinderDevmon* devmon[3]; + int n = 0; + + if (ival & BINDER_DEVMON_DS) { + devmon[n++] = binder_devmon_ds_new(config); + } + if (ival & BINDER_DEVMON_IF) { + devmon[n++] = binder_devmon_if_new(config); + } + slot->devmon = binder_devmon_combine(devmon, n); + } + + /* emptyPinQuery */ + if (ofono_conf_get_boolean(file, group, + BINDER_CONF_SLOT_EMPTY_PIN_QUERY, &config->empty_pin_query)) { + DBG("%s: " BINDER_CONF_SLOT_EMPTY_PIN_QUERY " %s", group, + config->empty_pin_query ? "yes" : "no"); + } + + /* useDataProfiles */ + if (ofono_conf_get_boolean(file, group, + BINDER_CONF_SLOT_USE_DATA_PROFILES, &config->use_data_profiles)) { + DBG("%s: " BINDER_CONF_SLOT_USE_DATA_PROFILES " %s", group, + config->use_data_profiles ? "yes" : "no"); + } + + /* allowDataReq */ + if (ofono_conf_get_enum(file, group, + BINDER_CONF_SLOT_ALLOW_DATA_REQ, &ival, + "on", BINDER_ALLOW_DATA_ENABLED, + "off", BINDER_ALLOW_DATA_DISABLED, NULL)) { + DBG("%s: " BINDER_CONF_SLOT_ALLOW_DATA_REQ " %s", group, + (ival == BINDER_ALLOW_DATA_ENABLED) ? "enabled": "disabled"); + slot->data_opt.allow_data = ival; + } + + /* replaceStrangeOperatorNames */ + if (ofono_conf_get_boolean(file, group, + BINDER_CONF_SLOT_REPLACE_STRANGE_OPER, + &config->replace_strange_oper)) { + DBG("%s: " BINDER_CONF_SLOT_REPLACE_STRANGE_OPER " %s", group, + config->replace_strange_oper ? "yes" : "no"); + } + + /* signalStrengthRange */ + ints = binder_plugin_config_get_ints(file, group, + BINDER_CONF_SLOT_SIGNAL_STRENGTH_RANGE); + if (gutil_ints_get_count(ints) == 2) { + const int* dbms = gutil_ints_get_data(ints, NULL); + + /* MIN,MAX */ + if (dbms[0] < dbms[1]) { + DBG("%s: " BINDER_CONF_SLOT_SIGNAL_STRENGTH_RANGE " [%d,%d]", + group, dbms[0], dbms[1]); + config->signal_strength_dbm_weak = dbms[0]; + config->signal_strength_dbm_strong = dbms[1]; + } + } + gutil_ints_unref(ints); + +#pragma message("Make more things configurable") + + return slot; +} + +static +void +binder_plugin_slot_free( + BinderSlot* slot) +{ + BinderPlugin* plugin = slot->plugin; + + DBG("%s", slot->name); + binder_plugin_slot_shutdown(slot, TRUE); + plugin->slots = g_slist_remove(plugin->slots, slot); + ofono_watch_remove_all_handlers(slot->watch, slot->watch_event_id); + ofono_watch_unref(slot->watch); + ofono_slot_remove_all_handlers(slot->handle, slot->slot_event_id); + ofono_slot_unref(slot->handle); + binder_devmon_free(slot->devmon); + binder_sim_settings_unref(slot->sim_settings); + gutil_ints_unref(slot->config.local_hangup_reasons); + gutil_ints_unref(slot->config.remote_hangup_reasons); + gbinder_servicemanager_remove_handler(slot->sm, slot->name_watch_id); + gbinder_servicemanager_cancel(slot->sm, slot->list_call_id); + gbinder_servicemanager_unref(slot->sm); + g_free(slot->name); + g_free(slot->path); + g_free(slot->imei); + g_free(slot->imeisv); + g_free(slot); +} + +static +GSList* +binder_plugin_add_slot( + GSList* slots, + BinderSlot* new_slot) +{ + GSList* link = slots; + + /* Slot numbers and paths must be unique */ + while (link) { + GSList* next = link->next; + BinderSlot* slot = link->data; + gboolean delete_this_slot = FALSE; + + if (slot->path && !strcmp(slot->path, new_slot->path)) { + ofono_error("Duplicate modem path '%s'", slot->path); + delete_this_slot = TRUE; + } else if (slot->config.slot != BINDER_SLOT_NUMBER_AUTOMATIC && + slot->config.slot == new_slot->config.slot) { + ofono_error("Duplicate slot %u", slot->config.slot); + delete_this_slot = TRUE; + } + + if (delete_this_slot) { + slots = g_slist_delete_link(slots, link); + binder_plugin_slot_free(slot); + } + + link = next; + } + + return g_slist_append(slots, new_slot); +} + +static +void +binder_plugin_parse_identity( + BinderPluginIdentity* id, + const char* value) +{ + char* sep = strchr(value, ':'); + const char* user = value; + const char* group = NULL; + char* tmp_user = NULL; + const struct passwd* pw = NULL; + const struct group* gr = NULL; + + if (sep) { + /* Group */ + group = sep + 1; + gr = getgrnam(group); + user = tmp_user = g_strndup(value, sep - value); + + if (!gr) { + int n; + + /* Try numeric */ + if (gutil_parse_int(group, 0, &n)) { + gr = getgrgid(n); + } + } + } + + /* User */ + pw = getpwnam(user); + if (!pw) { + int n; + + /* Try numeric */ + if (gutil_parse_int(user, 0, &n)) { + pw = getpwuid(n); + } + } + + if (pw) { + DBG("user %s -> %d", user, pw->pw_uid); + id->uid = pw->pw_uid; + } else { + ofono_warn("Invalid user '%s'", user); + } + + if (gr) { + DBG("group %s -> %d", group, gr->gr_gid); + id->gid = gr->gr_gid; + } else if (group) { + ofono_warn("Invalid group '%s'", group); + } + + g_free(tmp_user); +} + +static +char** +binder_plugin_find_slots( + GBinderServiceManager* sm) +{ + char** slots = NULL; + char** services = gbinder_servicemanager_list_sync(sm); + + if (services) { + char** ptr; + static const char prefix[] = RADIO_IFACE_PREFIX; + static const char suffix[] = "::" RADIO_IFACE "/"; + const gsize prefix_len = sizeof(prefix) - 1; + const gsize suffix_len = sizeof(suffix) - 1; + + for (ptr = services; *ptr; ptr++) { + const char* service = *ptr; + + if (!strncmp(service, prefix, prefix_len)) { + /* Skip the interface version */ + const char* slot = strstr(service + prefix_len, suffix); + + if (slot) { + slot += suffix_len; + if (*slot && !gutil_strv_contains(slots, slot)) { + DBG("found %s", slot); + slots = gutil_strv_add(slots, slot); + } + } + } + } + gutil_strv_sort(slots, TRUE); + g_strfreev(services); + } + + return slots; +} + +static +gboolean +binder_plugin_pattern_match( + GPatternSpec** patterns, + const char* str) +{ + const guint len = strlen(str); + + while (*patterns) { + GPatternSpec* pspec = *patterns++; + + if (g_pattern_match(pspec, len, str, NULL)) { + return TRUE; + } + } + return FALSE; +} + +static +GSList* +binder_plugin_parse_config_file( + GKeyFile* file, + BinderPluginSettings* ps) +{ + GSList* list = NULL; + int ival; + char* sval; + char** expect_slots; + char** ignore_slots; + gboolean ignore_all; + + ival = ps->dm_flags; + + /* 3GLTEHandover */ + if (ofono_conf_get_flag(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_3GLTE_HANDOVER, + BINDER_DATA_MANAGER_3GLTE_HANDOVER, &ival)) { + DBG(BINDER_CONF_PLUGIN_3GLTE_HANDOVER " %s", (ival & + BINDER_DATA_MANAGER_3GLTE_HANDOVER) ? "yes" : "no"); + ps->dm_flags = ival; + } + + /* MaxNonDataMode */ + ival = ps->non_data_mode; + if (ofono_conf_get_enum(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_MAX_NON_DATA_MODE, &ival, "none", + OFONO_RADIO_ACCESS_MODE_NONE, + ofono_radio_access_mode_to_string(OFONO_RADIO_ACCESS_MODE_GSM), + OFONO_RADIO_ACCESS_MODE_GSM, + ofono_radio_access_mode_to_string(OFONO_RADIO_ACCESS_MODE_UMTS), + OFONO_RADIO_ACCESS_MODE_UMTS, NULL)) { + DBG(BINDER_CONF_PLUGIN_MAX_NON_DATA_MODE " %s", + ofono_radio_access_mode_to_string(ival)); + ps->non_data_mode = ival; + } + + /* SetRadioCapability */ + if (ofono_conf_get_enum(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_SET_RADIO_CAP, &ival, + "auto", BINDER_SET_RADIO_CAP_AUTO, + "on", BINDER_SET_RADIO_CAP_ENABLED, + "off", BINDER_SET_RADIO_CAP_DISABLED, NULL)) { + DBG(BINDER_CONF_PLUGIN_SET_RADIO_CAP " %d", ival); + ps->set_radio_cap = ival; + } + + /* Identity */ + sval = g_key_file_get_string(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_IDENTITY, NULL); + if (sval) { + DBG(BINDER_CONF_PLUGIN_IDENTITY " %s", sval); + binder_plugin_parse_identity(&ps->identity, sval); + g_free(sval); + } + + /* ExpectSlots */ + expect_slots = ofono_conf_get_strings(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_EXPECT_SLOTS, BINDER_CONF_LIST_DELIMITER); + + /* IgnoreSlots */ + ignore_slots = ofono_conf_get_strings(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_IGNORE_SLOTS, BINDER_CONF_LIST_DELIMITER); + + /* + * The way to stop the plugin from even trying to find any slots is + * the IgnoreSlots entry containining '*' pattern in combination with + * an empty (or missing) ExpectSlots. + */ + ignore_all = gutil_strv_contains(ignore_slots, "*"); + if (gutil_strv_length(expect_slots) || !ignore_all) { + GBinderServiceManager* sm; + const char* dev; + char* cfg_dev; + + /* Device */ + cfg_dev = g_key_file_get_string(file, OFONO_COMMON_SETTINGS_GROUP, + BINDER_CONF_PLUGIN_DEVICE, NULL); + + /* If device is not configured, then try the default one */ + dev = cfg_dev ? cfg_dev : BINDER_DEFAULT_PLUGIN_DEVICE; + sm = gbinder_servicemanager_new(dev); + + if (sm) { + char** slots; + char** s; + + DBG("using %sbinder device %s", cfg_dev ? "" : "default ", dev); + slots = ignore_all ? NULL : binder_plugin_find_slots(sm); + + /* + * Create the expected slots and remove them from the list. + * The ignore list doesn't apply to the expected slots. + */ + if (expect_slots) { + for (s = expect_slots; *s; s++) { + const char* slot = *s; + + list = binder_plugin_add_slot(list, + binder_plugin_create_slot(sm, slot, file)); + slots = gutil_strv_remove_all(slots, slot); + } + } + + /* Then check the remaining auto-discovered slots */ + if (!ignore_all) { + const guint np = gutil_strv_length(ignore_slots); + GPatternSpec** ignore = g_new(GPatternSpec*, np + 1); + guint i; + + /* Compile ignore patterns */ + for (i = 0; i < np; i++) { + ignore[i] = g_pattern_spec_new(ignore_slots[i]); + } + ignore[i] = NULL; + + /* Run the remaining slots through the ignore patterns */ + if (slots) { + for (s = slots; *s; s++) { + const char* slot = *s; + + if (binder_plugin_pattern_match(ignore, slot)) { + DBG("skipping %s", slot); + } else { + list = binder_plugin_add_slot(list, + binder_plugin_create_slot(sm, slot, file)); + } + } + } + + for (i = 0; i < np; i++) { + g_pattern_spec_free(ignore[i]); + } + g_free(ignore); + g_strfreev(slots); + } + } else { + ofono_warn("Can't open %sbinder device %s", + cfg_dev ? "" : "default ", dev); + } + + gbinder_servicemanager_unref(sm); + g_free(cfg_dev); + } + + g_strfreev(expect_slots); + g_strfreev(ignore_slots); + return list; +} + +static +GSList* +binder_plugin_load_config( + const char* path, + BinderPluginSettings* ps) +{ + GSList* list; + GKeyFile* file = g_key_file_new(); + + ofono_conf_merge_files(file, path); + list = binder_plugin_parse_config_file(file, ps); + g_key_file_free(file); + return list; +} + +static +void +binder_plugin_set_perm( + const char* path, + mode_t mode, + const BinderPluginIdentity* id) +{ + if (chmod(path, mode)) { + ofono_error("chmod(%s,%o) failed: %s", path, mode, strerror(errno)); + } + if (chown(path, id->uid, id->gid)) { + ofono_error("chown(%s,%d,%d) failed: %s", path, id->uid, id->gid, + strerror(errno)); + } +} + +/* Recursively updates file and directory ownership and permissions */ +static +void +binder_plugin_set_storage_perm( + const char* path, + const BinderPluginIdentity* id) +{ + DIR* d; + const mode_t dir_mode = S_IRUSR | S_IWUSR | S_IXUSR; + const mode_t file_mode = S_IRUSR | S_IWUSR; + + binder_plugin_set_perm(path, dir_mode, id); + d = opendir(path); + if (d) { + const struct dirent* p; + + while ((p = readdir(d)) != NULL) { + if (strcmp(p->d_name, ".") && strcmp(p->d_name, "..")) { + char* buf = g_build_filename(path, p->d_name, NULL); + struct stat st; + + if (!stat(buf, &st)) { + mode_t mode; + + if (S_ISDIR(st.st_mode)) { + binder_plugin_set_storage_perm(buf, id); + mode = dir_mode; + } else { + mode = file_mode; + } + binder_plugin_set_perm(buf, mode, id); + } + g_free(buf); + } + } + closedir(d); + } +} + +static +void +binder_plugin_switch_identity( + const BinderPluginIdentity* id) +{ + DBG("%d:%d", id->uid, id->gid); + binder_plugin_set_storage_perm(ofono_storage_dir(), id); + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + ofono_error("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); + } else if (setgid(id->gid) < 0) { + ofono_error("setgid(%d) failed: %s", id->gid, strerror(errno)); + } else if (setuid(id->uid) < 0) { + ofono_error("setuid(%d) failed: %s", id->uid, strerror(errno)); + } else { + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + memset(&header, 0, sizeof(header)); + memset(&cap, 0, sizeof(cap)); + + header.version = _LINUX_CAPABILITY_VERSION; + cap.effective = + cap.permitted = (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); + + if (syscall(SYS_capset, &header, &cap) < 0) { + ofono_error("syscall(SYS_capset) failed: %s", strerror(errno)); + } + } +} + +static +void +binder_plugin_slot_modem_changed( + struct ofono_watch* watch, + void* user_data) +{ + BinderSlot *slot = user_data; + + DBG("%s", slot->path); + if (!watch->modem) { + GASSERT(slot->modem); + slot->modem = NULL; + binder_data_allow(slot->data, OFONO_SLOT_DATA_NONE); + binder_radio_caps_request_free(slot->caps_req); + slot->caps_req = NULL; + } +} + +static +gboolean +binder_plugin_manager_start_timeout( + gpointer user_data) +{ + BinderPlugin* plugin = user_data; + + DBG(""); + plugin->start_timeout_id = 0; + binder_plugin_manager_started(plugin); + return G_SOURCE_REMOVE; +} + +static +void +binder_plugin_drop_orphan_slots( + BinderPlugin* plugin) +{ + GSList* l = plugin->slots; + + while (l) { + GSList* next = l->next; + BinderSlot* slot = l->data; + + if (!slot->handle) { + plugin->slots = g_slist_delete_link(plugin->slots, l); + binder_plugin_slot_free(slot); + } + l = next; + } +} + +static +void +binder_plugin_manager_start_done( + gpointer user_data) +{ + BinderPlugin* plugin = user_data; + + DBG(""); + if (plugin->start_timeout_id) { + /* Startup was cancelled */ + plugin->start_timeout_id = 0; + binder_plugin_drop_orphan_slots(plugin); + } +} + +static +void +binder_plugin_slot_check_timeout_cb( + BinderSlot* slot, + void* param) +{ + guint* timeout = param; + + if ((*timeout) < slot->start_timeout_ms) { + (*timeout) = slot->start_timeout_ms; + } +} + +static +gboolean +binder_plugin_slot_start_timeout( + gpointer user_data) +{ + BinderSlot* slot = user_data; + BinderPlugin* plugin = slot->plugin; + + DBG("%s", slot->name); + plugin->slots = g_slist_remove(plugin->slots, slot); + slot->start_timeout_id = 0; + binder_plugin_slot_free(slot); + binder_plugin_check_if_started(plugin); + return G_SOURCE_REMOVE; +} + +static +void +binder_logger_slot_start( + BinderSlot* slot) +{ + /* Check if the service is there */ + slot->name_watch_id = + gbinder_servicemanager_add_registration_handler(slot->sm, + binder_radio_ifaces[slot->version], + binder_plugin_service_registration_proc, slot); + binder_plugin_slot_check(slot); +} + +static BinderPlugin* +binder_plugin_slot_driver_init( + struct ofono_slot_manager* sm) +{ + BinderPlugin* plugin = g_new0(BinderPlugin, 1); + BinderPluginSettings* ps = &plugin->settings; + char* config_file = g_build_filename(ofono_config_dir(), + BINDER_CONF_FILE, NULL); + GSList* l; + GError* error = NULL; + GHashTable* slot_paths; + GHashTable* slot_numbers; + guint i; + + DBG(""); + for (i = 0; i < G_N_ELEMENTS(binder_plugin_modules); i++) { + binder_plugin_modules[i].init(); + } + plugin->slot_manager = sm; + binder_plugin_parse_identity(&ps->identity, BINDER_DEFAULT_PLUGIN_IDENTITY); + ps->set_radio_cap = BINDER_SET_RADIO_CAP_AUTO; + ps->dm_flags = BINDER_DEFAULT_PLUGIN_DM_FLAGS; + ps->non_data_mode = BINDER_DEFAULT_MAX_NON_DATA_MODE; + plugin->slots = binder_plugin_load_config(config_file, ps); + + /* Connect to system bus before we switch the identity */ + plugin->system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!plugin->system_bus) { + ofono_error("Failed to connect system bus: %s", error->message); + g_error_free(error); + } + + /* + * Finish slot initialization. Some of them may not have path and + * slot index set up yet. + */ + slot_paths = g_hash_table_new(g_str_hash, g_str_equal); + slot_numbers = g_hash_table_new(g_direct_hash, g_direct_equal); + for (l = plugin->slots; l; l = l->next) { + BinderSlot* slot = l->data; + + if (slot->path) { + g_hash_table_add(slot_paths, (gpointer) slot->path); + } + if (slot->config.slot != BINDER_SLOT_NUMBER_AUTOMATIC) { + g_hash_table_insert(slot_numbers, + GUINT_TO_POINTER(slot->config.slot), + GUINT_TO_POINTER(slot->config.slot)); + } + } + + for (l = plugin->slots; l; l = l->next) { + BinderSlot* slot = l->data; + + /* Auto-assign paths */ + if (!slot->path) { + guint i = 0; + + do { + g_free(slot->path); + slot->path = g_strdup_printf("/%s_%u", + BINDER_DEFAULT_SLOT_PATH_PREFIX, i++); + } while (g_hash_table_contains(slot_paths, slot->path)); + DBG("assigned %s => %s", slot->name, slot->path); + g_hash_table_insert(slot_paths, + (gpointer) slot->path, + (gpointer) slot->path); + } + + /* Auto-assign slot numbers */ + if (slot->config.slot == BINDER_SLOT_NUMBER_AUTOMATIC) { + slot->config.slot = 0; + while (g_hash_table_contains(slot_numbers, + GUINT_TO_POINTER(slot->config.slot))) { + slot->config.slot++; + } + DBG("assigned %s => %u", slot->name, slot->config.slot); + g_hash_table_insert(slot_numbers, + GUINT_TO_POINTER(slot->config.slot), + GUINT_TO_POINTER(slot->config.slot)); + } + + slot->plugin = plugin; + slot->watch = ofono_watch_new(slot->path); + slot->watch_event_id[WATCH_EVENT_MODEM] = + ofono_watch_add_modem_changed_handler(slot->watch, + binder_plugin_slot_modem_changed, slot); + slot->sim_settings = binder_sim_settings_new(slot->path, + slot->config.techs); + + /* Start timeout for this slot */ + slot->start_timeout_id = g_timeout_add(slot->start_timeout_ms, + binder_plugin_slot_start_timeout, slot); + } + + g_hash_table_unref(slot_paths); + g_hash_table_unref(slot_numbers); + g_free(config_file); + return plugin; +} + +static +guint +binder_plugin_slot_driver_start( + BinderPlugin* plugin) +{ + BinderPluginSettings* ps = &plugin->settings; + guint start_timeout = 0; + + DBG(""); + + /* Switch the user to the one expected by the radio subsystem */ + binder_plugin_switch_identity(&ps->identity); + + /* IRadioConfig service may check the identity too */ + plugin->radio_config = radio_config_new_with_version + (RADIO_CONFIG_INTERFACE_1_1); + binder_radio_config_trace_update(plugin); + binder_radio_config_dump_update(plugin); + + /* Now we can create the data manager */ + plugin->data_manager = binder_data_manager_new(plugin->radio_config, + ps->dm_flags, ps->non_data_mode); + + /* Pick the shortest timeout */ + binder_plugin_foreach_slot_param(plugin, + binder_plugin_slot_check_timeout_cb, &start_timeout); + + /* Timeout remains zero if there are no slots */ + GASSERT(!plugin->start_timeout_id); + plugin->start_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT, + start_timeout, binder_plugin_manager_start_timeout, + plugin, binder_plugin_manager_start_done); + + DBG("start timeout %u ms id %u", start_timeout, plugin->start_timeout_id); + + /* Check if the service is there */ + binder_plugin_foreach_slot(plugin, binder_logger_slot_start); + return plugin->start_timeout_id; +} + +static +void +binder_plugin_slot_driver_cancel( + BinderPlugin* plugin, + guint id) +{ + DBG("%u", id); + GASSERT(plugin->start_timeout_id == id); + plugin->start_timeout_id = 0; + g_source_remove(id); +} + +static +void +binder_plugin_slot_driver_cleanup( + BinderPlugin* plugin) +{ + if (plugin) { + guint i; + + for (i = 0; i < G_N_ELEMENTS(binder_plugin_modules); i++) { + binder_plugin_modules[i].cleanup(); + } + GASSERT(!plugin->slots); + if (plugin->system_bus) { + g_object_unref(plugin->system_bus); + } + binder_data_manager_unref(plugin->data_manager); + binder_radio_caps_manager_remove_handler(plugin->caps_manager, + plugin->caps_manager_event_id); + binder_radio_caps_manager_unref(plugin->caps_manager); + binder_logger_free(plugin->radio_config_trace); + binder_logger_free(plugin->radio_config_dump); + radio_config_unref(plugin->radio_config); + g_free(plugin); + } +} + +void +binder_plugin_mce_log_notify( + struct ofono_debug_desc* desc) +{ + mce_log.level = (desc->flags & OFONO_DEBUG_FLAG_PRINT) ? + GLOG_LEVEL_VERBOSE : GLOG_LEVEL_INHERIT; +} + +/* Global part (that requires access to these global variables) */ + +static struct ofono_slot_driver_reg* binder_driver_reg = NULL; + +static +void +binder_logger_trace_notify( + struct ofono_debug_desc* desc) +{ + BinderPlugin* plugin = ofono_slot_driver_get_data(binder_driver_reg); + + binder_logger_module.level = (desc->flags & OFONO_DEBUG_FLAG_PRINT) ? + GLOG_LEVEL_VERBOSE : GLOG_LEVEL_INHERIT; + binder_radio_config_trace_update(plugin); + binder_plugin_foreach_slot(plugin, binder_logger_trace_update_slot); +} + +static +void +binder_logger_dump_notify( + struct ofono_debug_desc* desc) +{ + BinderPlugin* plugin = ofono_slot_driver_get_data(binder_driver_reg); + + binder_plugin_foreach_slot(plugin, binder_logger_dump_update_slot); + binder_radio_config_dump_update(plugin); +} + +static +void +binder_plugin_manager_started( + BinderPlugin* plugin) +{ + binder_plugin_drop_orphan_slots(plugin); + binder_data_manager_check_data(plugin->data_manager); + ofono_slot_driver_started(binder_driver_reg); +} + +static +int +binder_plugin_init(void) +{ + static const struct ofono_slot_driver binder_slot_driver = { + .name = BINDER_DRIVER, + .api_version = OFONO_SLOT_API_VERSION, + .init = binder_plugin_slot_driver_init, + .start = binder_plugin_slot_driver_start, + .cancel = binder_plugin_slot_driver_cancel, + .cleanup = binder_plugin_slot_driver_cleanup, + }; + + static struct ofono_debug_desc mce_debug OFONO_DEBUG_ATTR = { + .name = "mce", + .flags = OFONO_DEBUG_FLAG_DEFAULT, + .notify = binder_plugin_mce_log_notify + }; + + DBG(""); + + /* + * Log categories (accessible via D-Bus) are generated from + * ofono_debug_desc structures, while libglibutil based log + * functions receive the log module name. Those should match. + */ + mce_log.name = mce_debug.name; + + /* Register the slot driver */ + binder_driver_reg = ofono_slot_driver_register(&binder_slot_driver); + return 0; +} + +static +void +binder_plugin_exit(void) +{ + DBG(""); + binder_plugin_foreach_slot(ofono_slot_driver_get_data(binder_driver_reg), + binder_plugin_slot_free); + ofono_slot_driver_unregister(binder_driver_reg); + binder_driver_reg = NULL; +} + +OFONO_PLUGIN_DEFINE(binder, "Binder adaptation plugin", OFONO_VERSION, + OFONO_PLUGIN_PRIORITY_DEFAULT, binder_plugin_init, binder_plugin_exit) + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio.c b/src/binder_radio.c new file mode 100644 index 0000000..3885e95 --- /dev/null +++ b/src/binder_radio.c @@ -0,0 +1,556 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_log.h" +#include "binder_radio.h" +#include "binder_util.h" + +#include +#include +#include + +#include +#include + +#include +#include + +/* + * Object states: + * + * 1. Idle (!pending && !retry) + * 2. Power on/off request pending (pending) + * 3. Power on retry has been scheduled (retry) + */ +typedef struct binder_radio_object { + BinderBase base; + BinderRadio pub; + RadioClient* client; + RadioRequestGroup* g; + gulong state_event_id; + char* log_prefix; + GHashTable* req_table; + RadioRequest* pending_req; + guint retry_id; + guint state_changed_while_request_pending; + RADIO_STATE last_known_state; + gboolean power_cycle; + gboolean next_state_valid; + gboolean next_state; +} BinderRadioObject; + +#define POWER_RETRY_SECS (1) + +typedef BinderBaseClass BinderRadioObjectClass; +GType binder_radio_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderRadioObject, binder_radio_object, BINDER_TYPE_BASE) +#define PARENT_CLASS binder_radio_object_parent_class +#define THIS_TYPE binder_radio_object_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, BinderRadioObject) + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +/* Assumptions */ +BINDER_BASE_ASSERT_COUNT(BINDER_RADIO_PROPERTY_COUNT); + +static +void +binder_radio_submit_power_request( + BinderRadioObject* self, + gboolean on); + +static inline BinderRadioObject* binder_radio_object_cast(BinderRadio* net) + { return net ? THIS(G_CAST(net, BinderRadioObject, pub)) : NULL; } +static inline void binder_radio_object_ref(BinderRadioObject* self) + { g_object_ref(self); } +static inline void binder_radio_object_unref(BinderRadioObject* self) + { g_object_unref(self); } +static inline gboolean binder_radio_state_off(RADIO_STATE state) + { return state == RADIO_STATE_OFF; } +static inline gboolean binder_radio_state_on(RADIO_STATE state) + { return !binder_radio_state_off(state); } + +static +gboolean +binder_radio_power_should_be_on( + BinderRadioObject* self) +{ + BinderRadio* radio = &self->pub; + + return (radio->online || g_hash_table_size(self->req_table) > 0) && + !self->power_cycle; +} + +static +gboolean +binder_radio_power_request_retry_cb( + gpointer user_data) +{ + BinderRadioObject* self = THIS(user_data); + + DBG_(self, ""); + GASSERT(self->retry_id); + self->retry_id = 0; + binder_radio_submit_power_request(self, + binder_radio_power_should_be_on(self)); + + return G_SOURCE_REMOVE; +} + +static +void +binder_radio_cancel_retry( + BinderRadioObject* self) +{ + if (self->retry_id) { + DBG_(self, "retry cancelled"); + g_source_remove(self->retry_id); + self->retry_id = 0; + } +} + +static +void +binder_radio_check_state( + BinderRadioObject* self) +{ + BinderRadio* radio = &self->pub; + + if (!self->pending_req) { + const gboolean should_be_on = binder_radio_power_should_be_on(self); + + if (binder_radio_state_on(self->last_known_state) == should_be_on) { + /* All is good, cancel pending retry if there is one */ + binder_radio_cancel_retry(self); + } else if (self->state_changed_while_request_pending) { + /* Hmm... BINDER's reaction was inadequate, repeat */ + binder_radio_submit_power_request(self, should_be_on); + } else if (!self->retry_id) { + /* There has been no reaction so far, wait a bit */ + DBG_(self, "retry scheduled"); + self->retry_id = g_timeout_add_seconds(POWER_RETRY_SECS, + binder_radio_power_request_retry_cb, self); + } + } + + /* Don't update public state while something is pending */ + if (!self->pending_req && !self->retry_id && + radio->state != self->last_known_state) { + DBG_(self, "%s -> %s", binder_radio_state_string(radio->state), + binder_radio_state_string(self->last_known_state)); + radio->state = self->last_known_state; + binder_base_emit_property_change(&self->base, + BINDER_RADIO_PROPERTY_STATE); + } +} + +static +void +binder_radio_power_request_done( + BinderRadioObject* self) +{ + GASSERT(!self->pending_req); + if (self->next_state_valid) { + binder_radio_submit_power_request(self, self->next_state); + } else { + binder_radio_check_state(self); + } +} + +static +void +binder_radio_power_request_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + BinderRadioObject* self = THIS(user_data); + + GASSERT(self->pending_req == req); + radio_request_unref(self->pending_req); + self->pending_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp != RADIO_RESP_SET_RADIO_POWER) { + ofono_error("Unexpected setRadioPower response %d", resp); + } else if (error != RADIO_ERROR_NONE) { + ofono_error("Power request failed: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Power request failed"); + } + + binder_radio_power_request_done(self); +} + +static +void +binder_radio_submit_power_request( + BinderRadioObject* self, + gboolean on) +{ + /* setRadioPower(int32 serial, bool on) */ + GBinderWriter writer; + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_SET_RADIO_POWER, &writer, + binder_radio_power_request_cb, NULL, self); + + gbinder_writer_append_bool(&writer, on); + + self->next_state_valid = FALSE; + self->next_state = on; + self->state_changed_while_request_pending = 0; + binder_radio_cancel_retry(self); + + GASSERT(!self->pending_req); + radio_request_set_blocking(req, TRUE); + if (radio_request_submit(req)) { + self->pending_req = req; /* Keep the ref */ + } else { + radio_request_unref(req); + } +} + +static +void +binder_radio_power_request( + BinderRadioObject* self, + gboolean on, + gboolean allow_repeat) +{ + const char* on_off = on ? "on" : "off"; + + if (self->pending_req) { + if (allow_repeat || self->next_state != on) { + /* Wait for the pending request to complete */ + self->next_state_valid = TRUE; + self->next_state = on; + DBG_(self, "%s (queued)", on_off); + } else { + DBG_(self, "%s (ignored)", on_off); + } + } else { + if (binder_radio_state_on(self->last_known_state) == on) { + DBG_(self, "%s (already)", on_off); + binder_radio_check_state(self); + } else { + DBG_(self, "%s", on_off); + binder_radio_submit_power_request(self, on); + } + } +} + +static +void +binder_radio_state_changed( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioObject* self = THIS(user_data); + RADIO_STATE radio_state = RADIO_STATE_UNAVAILABLE; + GBinderReader reader; + gint32 tmp; + + /* radioStateChanged(RadioIndicationType, RadioState radioState); */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &tmp)) { + radio_state = tmp; + } else { + ofono_error("Failed to parse radioStateChanged payload"); + } + + if (radio_state != RADIO_STATE_UNAVAILABLE) { + DBG_(self, "%s", binder_radio_state_string(radio_state)); + + GASSERT(!self->pending_req || !self->retry_id); + if (self->power_cycle && binder_radio_state_off(radio_state)) { + DBG_(self, "switched off for power cycle"); + self->power_cycle = FALSE; + } + + self->last_known_state = radio_state; + + if (self->pending_req) { + if (binder_radio_state_on(radio_state) == + binder_radio_power_should_be_on(self)) { + DBG_(self, "dropping pending request"); + /* + * All right, the modem has switched to the + * desired state, drop the request. + */ + radio_request_drop(self->pending_req); + self->pending_req = NULL; + binder_radio_power_request_done(self); + + /* We are done */ + return; + } else { + /* Something weird is going on */ + self->state_changed_while_request_pending++; + } + } + + binder_radio_check_state(self); + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderRadio* +binder_radio_new( + RadioClient* client, + const char* log_prefix) +{ + BinderRadioObject* self = g_object_new(THIS_TYPE, NULL); + BinderRadio* radio = &self->pub; + + self->client = radio_client_ref(client); + self->g = radio_request_group_new(client); + self->log_prefix = binder_dup_prefix(log_prefix); + DBG_(self, ""); + + self->state_event_id = radio_client_add_indication_handler(client, + RADIO_IND_RADIO_STATE_CHANGED, binder_radio_state_changed, self); + + /* + * Some modem adaptations like to receive power off request at startup + * even if radio is already off. Make those happy. + */ + binder_radio_submit_power_request(self, FALSE); + return radio; +} + +BinderRadio* +binder_radio_ref( + BinderRadio* radio) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + binder_radio_object_ref(self); + return radio; + } else { + return NULL; + } +} + +void +binder_radio_unref( + BinderRadio* radio) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + binder_radio_object_unref(self); + } +} + +void +binder_radio_set_online( + BinderRadio* radio, + gboolean online) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self) && radio->online != online) { + gboolean on, was_on = binder_radio_power_should_be_on(self); + + radio->online = online; + on = binder_radio_power_should_be_on(self); + if (was_on != on) { + binder_radio_power_request(self, on, FALSE); + } + binder_base_emit_property_change(&self->base, + BINDER_RADIO_PROPERTY_ONLINE); + } +} + +void +binder_radio_confirm_power_on( + BinderRadio* radio) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self) && binder_radio_power_should_be_on(self)) { + if (self->pending_req) { + if (!self->next_state) { + /* Wait for the pending request to complete */ + self->next_state_valid = TRUE; + self->next_state = TRUE; + DBG_(self, "on (queued)"); + } + } else { + DBG_(self, "on"); + binder_radio_submit_power_request(self, TRUE); + } + } +} + +void +binder_radio_power_cycle( + BinderRadio* radio) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + if (binder_radio_state_off(self->last_known_state)) { + DBG_(self, "power is already off"); + GASSERT(!self->power_cycle); + } else if (self->power_cycle) { + DBG_(self, "already in progress"); + } else { + DBG_(self, "initiated"); + self->power_cycle = TRUE; + if (!self->pending_req) { + binder_radio_submit_power_request(self, FALSE); + } + } + } +} + +void +binder_radio_power_on( + BinderRadio* radio, + gpointer tag) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + if (!g_hash_table_contains(self->req_table, tag)) { + const gboolean was_on = binder_radio_power_should_be_on(self); + + DBG_(self, "%p", tag); + g_hash_table_insert(self->req_table, tag, tag); + if (!was_on && binder_radio_power_should_be_on(self)) { + binder_radio_power_request(self, TRUE, FALSE); + } + } + } +} + +void +binder_radio_power_off( + BinderRadio* radio, + gpointer tag) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + if (g_hash_table_remove(self->req_table, tag)) { + DBG_(self, "%p", tag); + if (!binder_radio_power_should_be_on(self)) { + /* The last one turns the lights off */ + binder_radio_power_request(self, FALSE, FALSE); + } + } + } +} + +gulong +binder_radio_add_property_handler( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + BinderRadioPropertyFunc callback, + void* user_data) +{ + BinderRadioObject* self = binder_radio_object_cast(radio); + + return G_LIKELY(self) ? binder_base_add_property_handler(&self->base, + property, G_CALLBACK(callback), user_data) : 0; +} + +void +binder_radio_remove_handler( + BinderRadio* radio, + gulong id) +{ + if (G_LIKELY(id)) { + BinderRadioObject* self = binder_radio_object_cast(radio); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +void +binder_radio_remove_handlers( + BinderRadio* radio, + gulong* ids, + int count) +{ + gutil_disconnect_handlers(binder_radio_object_cast(radio), ids, count); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_radio_object_init( + BinderRadioObject* self) +{ + self->req_table = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +static +void +binder_radio_object_finalize( + GObject* object) +{ + BinderRadioObject* self = THIS(object); + + DBG_(self, ""); + binder_radio_cancel_retry(self); + + radio_request_drop(self->pending_req); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + radio_client_remove_handler(self->client, self->state_event_id); + radio_client_unref(self->client); + + g_hash_table_unref(self->req_table); + g_free(self->log_prefix); + + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_radio_object_class_init( + BinderRadioObjectClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_radio_object_finalize; + BINDER_BASE_CLASS(klass)->public_offset = + G_STRUCT_OFFSET(BinderRadioObject, pub); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio.h b/src/binder_radio.h new file mode 100644 index 0000000..47a4679 --- /dev/null +++ b/src/binder_radio.h @@ -0,0 +1,116 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_RADIO_H +#define BINDER_RADIO_H + +#include "binder_types.h" + +typedef enum binder_radio_property { + BINDER_RADIO_PROPERTY_ANY, + BINDER_RADIO_PROPERTY_STATE, + BINDER_RADIO_PROPERTY_ONLINE, + BINDER_RADIO_PROPERTY_COUNT +} BINDER_RADIO_PROPERTY; + +struct binder_radio { + RADIO_STATE state; + gboolean online; +}; + +typedef +void +(*BinderRadioPropertyFunc)( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* user_data); + +BinderRadio* +binder_radio_new( + RadioClient* client, + const char* log_prefix) + BINDER_INTERNAL; + +BinderRadio* +binder_radio_ref( + BinderRadio* radio) + BINDER_INTERNAL; + +void +binder_radio_unref( + BinderRadio* radio) + BINDER_INTERNAL; + +void +binder_radio_power_on( + BinderRadio* radio, + gpointer tag) + BINDER_INTERNAL; + +void +binder_radio_power_off( + BinderRadio* radio, + gpointer tag) + BINDER_INTERNAL; + +void +binder_radio_power_cycle( + BinderRadio* radio) + BINDER_INTERNAL; + +void +binder_radio_confirm_power_on( + BinderRadio* radio) + BINDER_INTERNAL; + +void +binder_radio_set_online( + BinderRadio* radio, + gboolean online) + BINDER_INTERNAL; + +gulong +binder_radio_add_property_handler( + BinderRadio* net, + BINDER_RADIO_PROPERTY property, + BinderRadioPropertyFunc callback, + void* user_data) + BINDER_INTERNAL; + +void +binder_radio_remove_handler( + BinderRadio* radio, + gulong id) + BINDER_INTERNAL; + +void +binder_radio_remove_handlers( + BinderRadio* radio, + gulong* ids, + int count) + BINDER_INTERNAL; + +#define binder_radio_remove_all_handlers(r,ids) \ + binder_radio_remove_handlers(r, ids, G_N_ELEMENTS(ids)) + +#endif /* BINDER_RADIO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio_caps.c b/src/binder_radio_caps.c new file mode 100644 index 0000000..3d0c9df --- /dev/null +++ b/src/binder_radio_caps.c @@ -0,0 +1,2030 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_data.h" +#include "binder_radio_caps.h" +#include "binder_radio.h" +#include "binder_sim_card.h" +#include "binder_sim_settings.h" +#include "binder_util.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define SET_CAPS_TIMEOUT_MS (30*1000) +#define GET_CAPS_TIMEOUT_MS (5*1000) +#define DATA_OFF_TIMEOUT_MS (10*1000) +#define DEACTIVATE_TIMEOUT_MS (10*1000) +#define CHECK_LATER_TIMEOUT_SEC (5) + +#define GET_CAPS_RETRIES 60 + +/* + * This code is doing something similar to what + * com.android.internal.telephony.ProxyController + * is doing. + */ + +enum binder_radio_caps_watch_events { + WATCH_EVENT_IMSI, + WATCH_EVENT_MODEM, + WATCH_EVENT_COUNT +}; + +enum binder_radio_caps_sim_events { + SIM_EVENT_STATE_CHANGED, + SIM_EVENT_IO_ACTIVE_CHANGED, + SIM_EVENT_COUNT +}; + +enum binder_radio_caps_settings_events { + SETTINGS_EVENT_PREF_MODE, + SETTINGS_EVENT_COUNT +}; + +enum binder_radio_caps_client_events { + CLIENT_EVENT_IND_RADIO_CAPABILITY, + CLIENT_EVENT_OWNER, + CLIENT_EVENT_COUNT +}; + +enum binder_radio_events { + RADIO_EVENT_STATE, + RADIO_EVENT_ONLINE, + RADIO_EVENT_COUNT +}; + +typedef struct binder_radio_caps_object { + GObject object; + struct binder_radio_caps pub; + enum ofono_radio_access_mode requested_modes; + guint slot; + char* log_prefix; + RadioClient* client; + RadioRequestGroup* g; + GUtilIdlePool* idle_pool; + gulong watch_event_id[WATCH_EVENT_COUNT]; + gulong settings_event_id[SETTINGS_EVENT_COUNT]; + gulong simcard_event_id[SIM_EVENT_COUNT]; + gulong client_event_id[CLIENT_EVENT_COUNT]; + gulong radio_event_id[RADIO_EVENT_COUNT]; + int tx_id; + int tx_pending; + struct ofono_watch* watch; + BinderData* data; + BinderRadio* radio; + BinderSimSettings* settings; + BinderSimCard* simcard; + RadioCapability* cap; + RadioCapability* old_cap; + RadioCapability* new_cap; +} BinderRadioCapsObject; + +typedef struct binder_radio_caps_manager { + GObject object; + GUtilIdlePool* idle_pool; + GPtrArray* caps_list; + GPtrArray* order_list; + GPtrArray* requests; + guint check_id; + int tx_id; + int tx_phase_index; + gboolean tx_failed; + BinderDataManager* data_manager; +} BinderRadioCapsManager; + +typedef struct binder_radio_caps_closure { + GCClosure cclosure; + BinderRadioCapsFunc cb; + void* user_data; +} BinderRadioCapsClosure; + +#define binder_radio_caps_closure_new() ((BinderRadioCapsClosure*) \ + g_closure_new_simple(sizeof(BinderRadioCapsClosure), NULL)) + +struct binder_radio_caps_request { + BinderRadioCapsObject* caps; + enum ofono_radio_access_mode modes; + enum ofono_slot_data_role role; +}; + +typedef struct binder_radio_caps_check_data { + BinderRadioCapsCheckFunc cb; + void* cb_data; +} BinderRadioCapsCheckData; + +typedef struct binder_radio_caps_request_tx_phase { + const char* name; + RADIO_CAPABILITY_PHASE phase; + RADIO_CAPABILITY_STATUS status; + gboolean send_new_cap; +} BinderRadioCapsRequestTxPhase; + +typedef +void +(*BinderRadioCapsEnumFunc)( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps); + +typedef GObjectClass BinderRadioCapsObjectClass; +GType binder_radio_caps_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderRadioCapsObject, binder_radio_caps_object, G_TYPE_OBJECT) +#define RADIO_CAPS_TYPE binder_radio_caps_object_get_type() +#define RADIO_CAPS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, \ + RADIO_CAPS_TYPE, BinderRadioCapsObject) + +enum binder_radio_caps_signal { + CAPS_SIGNAL_RAF_CHANGED, + CAPS_SIGNAL_COUNT +}; + +#define CAPS_SIGNAL_RAF_CHANGED_NAME "binder-radio-raf-changed" +static guint binder_radio_caps_signals[CAPS_SIGNAL_COUNT] = { 0 }; + +typedef GObjectClass BinderRadioCapsManagerClass; +GType binder_radio_caps_manager_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderRadioCapsManager, binder_radio_caps_manager, G_TYPE_OBJECT) +#define RADIO_CAPS_MANAGER_TYPE binder_radio_caps_manager_get_type() +#define RADIO_CAPS_MANAGER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, \ + RADIO_CAPS_MANAGER_TYPE, BinderRadioCapsManager) + +enum binder_radio_caps_manager_signal { + CAPS_MANAGER_SIGNAL_ABORTED, + CAPS_MANAGER_SIGNAL_TX_DONE, + CAPS_MANAGER_SIGNAL_COUNT +}; + +#define CAPS_MANAGER_SIGNAL_ABORTED_NAME "binder-radio-capsmgr-aborted" +#define CAPS_MANAGER_SIGNAL_TX_DONE_NAME "binder-radio-capsmgr-tx-done" +static guint binder_radio_caps_manager_signals[CAPS_MANAGER_SIGNAL_COUNT] = {0}; + +static const struct binder_access_mode_raf { + enum ofono_radio_access_mode mode; + RADIO_ACCESS_FAMILY raf; +} binder_access_mode_raf_map[] = { + { OFONO_RADIO_ACCESS_MODE_GSM, RAF_EDGE | RAF_GPRS | RAF_GSM }, + { OFONO_RADIO_ACCESS_MODE_UMTS, RAF_UMTS }, + { OFONO_RADIO_ACCESS_MODE_LTE, RAF_LTE | RAF_LTE_CA } +}; + +static const BinderRadioCapsRequestTxPhase binder_radio_caps_tx_phase[] = { + { + "START", + RADIO_CAPABILITY_PHASE_START, + RADIO_CAPABILITY_STATUS_NONE, + FALSE + },{ + "APPLY", + RADIO_CAPABILITY_PHASE_APPLY, + RADIO_CAPABILITY_STATUS_NONE, + TRUE + },{ + "FINISH", + RADIO_CAPABILITY_PHASE_FINISH, + RADIO_CAPABILITY_STATUS_SUCCESS, + TRUE + } +}; + +static const BinderRadioCapsRequestTxPhase binder_radio_caps_fail_phase = { + "ABORT", + RADIO_CAPABILITY_PHASE_FINISH, + RADIO_CAPABILITY_STATUS_FAIL, + FALSE +}; + +static GUtilIdlePool* binder_radio_caps_shared_pool = NULL; + +#define DBG_(obj, fmt, args...) DBG("%s" fmt, (obj)->log_prefix, ##args) + +static +void +binder_radio_caps_manager_next_phase( + BinderRadioCapsManager* mgr); + +static +void +binder_radio_caps_manager_consider_requests( + BinderRadioCapsManager* mgr); + +static +void +binder_radio_caps_manager_schedule_check( + BinderRadioCapsManager* mgr); + +static +void +binder_radio_caps_manager_recheck_later( + BinderRadioCapsManager* mgr); + +static +void +binder_radio_caps_manager_add( + BinderRadioCapsManager* mgr, + BinderRadioCapsObject* caps); + +static +void +binder_radio_caps_manager_remove( + BinderRadioCapsManager* mgr, + BinderRadioCapsObject* caps); + +static +void +binder_radio_caps_permutate( + GPtrArray* list, + const guint* sample, + guint off, + guint n) +{ + if (off < n) { + guint i; + + binder_radio_caps_permutate(list, sample, off + 1, n); + for (i = off + 1; i < n; i++) { + guint* resample = gutil_memdup(sample, sizeof(guint) * n); + + resample[off] = sample[i]; + resample[i] = sample[off]; + g_ptr_array_add(list, resample); + binder_radio_caps_permutate(list, resample, off + 1, n); + } + } +} + +static +void +binder_radio_caps_generate_permutations( + GPtrArray* list, + guint n) +{ + g_ptr_array_set_size(list, 0); + + if (n > 0) { + guint i; + guint* order = g_new(guint, n); + + /* + * In a general case this gives n! of permutations (1, 2, + * 6, 24, ...) but typically no more than 2 + */ + for (i = 0; i < n; i++) order[i] = i; + g_ptr_array_set_free_func(list, g_free); + g_ptr_array_add(list, order); + binder_radio_caps_permutate(list, order, 0, n); + } +} + +static +RadioCapability* +binder_radio_caps_dup( + const RadioCapability* in) +{ + if (in) { + if (in->logicalModemUuid.data.str) { + /* Allocate the whole thing from a single memory block */ + RadioCapability* out = g_malloc(G_ALIGN8(sizeof(*in)) + + in->logicalModemUuid.len + 1); + char* uuid = ((char*)out) + G_ALIGN8(sizeof(*out)); + + memcpy(out, in, sizeof(*in)); + memcpy(uuid, in->logicalModemUuid.data.str, + in->logicalModemUuid.len + 1); + out->logicalModemUuid.data.str = uuid; + return out; + } else { + return gutil_memdup(in, sizeof(*in));; + } + } + return NULL; +} + +static +gboolean +binder_radio_caps_equal( + const RadioCapability* rc1, + const RadioCapability* rc2) +{ + if (rc1 == rc2) { + return TRUE; + } else if (rc1 && rc2) { + return rc1->session == rc2->session && + rc1->phase == rc2->phase && + rc1->raf == rc2->raf && + rc1->status == rc2->status && + rc1->logicalModemUuid.len == rc2->logicalModemUuid.len && + (!rc1->logicalModemUuid.len || + !memcmp(rc1->logicalModemUuid.data.str, + rc2->logicalModemUuid.data.str, + rc1->logicalModemUuid.len)); + } else { + /* One of the pointers is NULL (but not both) */ + return FALSE; + } +} + +static +void +binder_radio_caps_check_done( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + BinderRadioCapsCheckData* check = user_data; + const RadioCapability* result = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_RADIO_CAPABILITY) { + if (error == RADIO_ERROR_NONE) { + result = binder_read_hidl_struct(args, RadioCapability); + if (result) { + DBG("tx=%d,phase=%d,raf=0x%x,uuid=%s,status=%d", + result->session, result->phase, result->raf, + result->logicalModemUuid.data.str, result->status); + } + } else { + DBG("getRadioCapability error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getRadioCapability response %d", resp); + } + } + + check->cb(result, check->cb_data); +} + +static +gboolean +binder_radio_caps_check_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + if (status == RADIO_TX_STATUS_OK) { + switch (error) { + case RADIO_ERROR_NONE: + case RADIO_ERROR_REQUEST_NOT_SUPPORTED: + case RADIO_ERROR_OPERATION_NOT_ALLOWED: + return FALSE; + default: + return TRUE; + } + } + return TRUE; +} + +RadioRequest* +binder_radio_caps_check( + RadioClient* client, + BinderRadioCapsCheckFunc cb, + void* cb_data) +{ + BinderRadioCapsCheckData* check = g_new0(BinderRadioCapsCheckData, 1); + + /* getRadioCapability(int32 serial) */ + RadioRequest* req = radio_request_new(client, + RADIO_REQ_GET_RADIO_CAPABILITY, NULL, + binder_radio_caps_check_done, g_free, check); + + check->cb = cb; + check->cb_data = cb_data; + + /* + * Make is blocking because this is typically happening at startup + * when there are lots of things happening at the same time which + * makes some modems unhappy. Slow things down a bit by not letting + * to submit any other requests while this one is pending. + */ + radio_request_set_blocking(req, TRUE); + radio_request_set_retry(req, GET_CAPS_TIMEOUT_MS, GET_CAPS_RETRIES); + radio_request_set_retry_func(req, binder_radio_caps_check_retry); + if (radio_request_submit(req)) { + return req; + } else { + radio_request_unref(req); + return NULL; + } +} + +/*==========================================================================* + * binder_radio_caps + *==========================================================================*/ + +static inline BinderRadioCapsObject* +binder_radio_caps_cast(BinderRadioCaps* caps) + { return caps ? RADIO_CAPS(G_CAST(caps,BinderRadioCapsObject,pub)) : NULL; } +static inline void binder_radio_caps_object_ref(BinderRadioCapsObject* self) + { g_object_ref(self); } +static inline void binder_radio_caps_object_unref(BinderRadioCapsObject* self) + { g_object_unref(self); } + +static +enum ofono_radio_access_mode +binder_radio_caps_access_mode( + const RadioCapability* cap) +{ + if (cap) { + const RADIO_ACCESS_FAMILY raf = cap->raf; + int i; + + /* Return the highest matched mode */ + for (i = G_N_ELEMENTS(binder_access_mode_raf_map); i >= 0; i--) { + if (raf & binder_access_mode_raf_map[i].raf) { + return binder_access_mode_raf_map[i].mode; + } + } + } + return OFONO_RADIO_ACCESS_MODE_ANY; +} + +static +enum ofono_radio_access_mode +binder_radio_caps_modes( + const RadioCapability* cap) +{ + enum ofono_radio_access_mode modes = OFONO_RADIO_ACCESS_MODE_NONE; + + if (cap) { + const RADIO_ACCESS_FAMILY raf = cap->raf; + int i; + + /* Bitwise-OR all matched modes */ + for (i = 0; i < G_N_ELEMENTS(binder_access_mode_raf_map); i++) { + if (raf & binder_access_mode_raf_map[i].raf) { + modes |= binder_access_mode_raf_map[i].mode; + } + } + } + return modes; +} + +static +void +binder_radio_caps_update_raf( + BinderRadioCapsObject* self) +{ + BinderRadioCaps* caps = &self->pub; + const RadioCapability* cap = self->cap; + RADIO_ACCESS_FAMILY raf = cap ? cap->raf : RAF_NONE; + + if (caps->raf != raf) { + caps->raf = raf; + binder_radio_caps_manager_schedule_check(caps->mgr); + g_signal_emit(self, binder_radio_caps_signals + [CAPS_SIGNAL_RAF_CHANGED], 0); + } +} + +static +int +binder_radio_caps_score( + const BinderRadioCapsObject* self, + const RadioCapability* cap) +{ + if (!self->radio->online || !self->simcard->status || + self->simcard->status->card_state != RADIO_CARD_STATE_PRESENT) { + /* Unusable slot */ + return -(int)binder_radio_caps_modes(cap); + } else if (self->requested_modes) { + if (binder_radio_caps_modes(cap) >= self->requested_modes) { + /* Happy slot (upgrade not required) */ + return self->requested_modes; + } else { + /* Unhappy slot (wants upgrade) */ + return -(int)self->requested_modes; + } + } else { + /* Whatever */ + return 0; + } +} + +static +gint +binder_radio_caps_slot_compare( + gconstpointer a, + gconstpointer b) +{ + const BinderRadioCapsObject* c1 = *(void**)a; + const BinderRadioCapsObject* c2 = *(void**)b; + + return c1->slot < c2->slot ? (-1) : c1->slot > c2->slot ? 1 : 0; +} + +static +void +binder_radio_caps_radio_event( + BinderRadio* radio, + BINDER_RADIO_PROPERTY property, + void* user_data) +{ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + + DBG_(self, ""); + binder_radio_caps_manager_schedule_check(self->pub.mgr); +} + +static +void +binder_radio_caps_simcard_event( + BinderSimCard* sim, + void* user_data) +{ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + + DBG_(self, ""); + binder_radio_caps_manager_schedule_check(self->pub.mgr); +} + +static +void +binder_radio_caps_watch_event( + struct ofono_watch* watch, + void* user_data) +{ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + + DBG_(self, ""); + binder_radio_caps_manager_schedule_check(self->pub.mgr); +} + +static +void +binder_radio_caps_settings_event( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + BinderRadioCapsManager* mgr = self->pub.mgr; + + DBG_(self, ""); + binder_radio_caps_manager_consider_requests(mgr); + binder_radio_caps_manager_schedule_check(mgr); +} + +static +void +binder_radio_caps_changed_cb( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + /* radioCapabilityIndication(RadioIndicationType, RadioCapability rc) */ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + const RadioCapability* cap = binder_read_hidl_struct(args, RadioCapability); + + DBG_(self, ""); + if (cap) { + g_free(self->cap); + self->cap = binder_radio_caps_dup(cap); + binder_radio_caps_update_raf(self); + binder_radio_caps_manager_schedule_check(self->pub.mgr); + } else { + ofono_error("Failed to parse RadioCapability payload"); + } +} + +static +void +binder_radio_caps_finish_init( + BinderRadioCapsObject* self) +{ + /* Register for update notifications */ + self->client_event_id[CLIENT_EVENT_IND_RADIO_CAPABILITY] = + radio_client_add_indication_handler(self->client, + RADIO_IND_RADIO_CAPABILITY_INDICATION, + binder_radio_caps_changed_cb, self); + + /* Schedule capability check */ + binder_radio_caps_manager_schedule_check(self->pub.mgr); +} + +static +void +binder_radio_caps_initial_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioCapsObject* self = RADIO_CAPS(user_data); + const RadioCapability* cap = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_RADIO_CAPABILITY) { + /* getRadioCapabilityResponse(RadioResponseInfo, RadioCapability) */ + if (error == RADIO_ERROR_NONE) { + cap = binder_read_hidl_struct(args, RadioCapability); + } else { + DBG_(self, "Failed get radio caps, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getRadioCapability response %d", resp); + } + } + + if (cap) { + binder_radio_caps_update_raf(self); + binder_radio_caps_finish_init(self); + } +} + +BinderRadioCaps* +binder_radio_caps_new( + BinderRadioCapsManager* mgr, + const char* log_prefix, + RadioClient* client, + struct ofono_watch* watch, + BinderData* data, + BinderRadio* radio, + BinderSimCard* sim, + BinderSimSettings* settings, + const BinderSlotConfig* config, + const RadioCapability* cap) +{ + GASSERT(mgr); + if (G_LIKELY(mgr)) { + BinderRadioCapsObject* self = g_object_new(RADIO_CAPS_TYPE, 0); + BinderRadioCaps* caps = &self->pub; + + self->slot = config->slot; + self->log_prefix = binder_dup_prefix(log_prefix); + + self->g = radio_request_group_new(client); + self->radio = binder_radio_ref(radio); + self->data = binder_data_ref(data); + caps->mgr = binder_radio_caps_manager_ref(mgr); + + self->radio = binder_radio_ref(radio); + self->radio_event_id[RADIO_EVENT_STATE] = + binder_radio_add_property_handler(radio, + BINDER_RADIO_PROPERTY_STATE, + binder_radio_caps_radio_event, self); + self->radio_event_id[RADIO_EVENT_ONLINE] = + binder_radio_add_property_handler(radio, + BINDER_RADIO_PROPERTY_ONLINE, + binder_radio_caps_radio_event, self); + + self->simcard = binder_sim_card_ref(sim); + self->simcard_event_id[SIM_EVENT_STATE_CHANGED] = + binder_sim_card_add_state_changed_handler(sim, + binder_radio_caps_simcard_event, self); + self->simcard_event_id[SIM_EVENT_IO_ACTIVE_CHANGED] = + binder_sim_card_add_sim_io_active_changed_handler(sim, + binder_radio_caps_simcard_event, self); + + self->watch = ofono_watch_ref(watch); + self->watch_event_id[WATCH_EVENT_IMSI] = + ofono_watch_add_imsi_changed_handler(watch, + binder_radio_caps_watch_event, self); + self->watch_event_id[WATCH_EVENT_MODEM] = + ofono_watch_add_modem_changed_handler(watch, + binder_radio_caps_watch_event, self); + + self->settings = binder_sim_settings_ref(settings); + self->settings_event_id[SETTINGS_EVENT_PREF_MODE] = + binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_PREF, + binder_radio_caps_settings_event, self); + + binder_radio_caps_manager_add(mgr, self); + if (cap) { + /* Current capabilities are provided by the caller */ + self->cap = binder_radio_caps_dup(cap); + caps->raf = cap->raf; + binder_radio_caps_finish_init(self); + } else { + /* Need to query current capabilities */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_RADIO_CAPABILITY, NULL, + binder_radio_caps_initial_query_cb, NULL, self); + + radio_request_set_retry(req, GET_CAPS_TIMEOUT_MS, GET_CAPS_RETRIES); + radio_request_submit(req); + radio_request_unref(req); + } + return caps; + } + return NULL; +} + +BinderRadioCaps* +binder_radio_caps_ref( + BinderRadioCaps* caps) +{ + BinderRadioCapsObject* self = binder_radio_caps_cast(caps); + + if (G_LIKELY(self)) { + binder_radio_caps_object_ref(self); + } + return caps; +} + +void +binder_radio_caps_unref( + BinderRadioCaps* caps) +{ + BinderRadioCapsObject* self = binder_radio_caps_cast(caps); + + if (G_LIKELY(self)) { + binder_radio_caps_object_unref(self); + } +} + +void +binder_radio_caps_drop( + BinderRadioCaps* caps) +{ + BinderRadioCapsObject* self = binder_radio_caps_cast(caps); + + if (G_LIKELY(self)) { + binder_radio_caps_manager_remove(self->pub.mgr, self); + g_object_unref(self); + } +} + +static +void +binder_radio_caps_signal_cb( + BinderRadioCapsObject* caps, + BinderRadioCapsClosure* closure) +{ + closure->cb(&caps->pub, closure->user_data); +} + +gulong +binder_radio_caps_add_raf_handler( + BinderRadioCaps* caps, + BinderRadioCapsFunc cb, + void* arg) +{ + BinderRadioCapsObject* self = binder_radio_caps_cast(caps); + + if (G_LIKELY(self) && G_LIKELY(cb)) { + BinderRadioCapsClosure* closure = binder_radio_caps_closure_new(); + GCClosure* cc = &closure->cclosure; + + cc->closure.data = closure; + cc->callback = G_CALLBACK(binder_radio_caps_signal_cb); + closure->cb = cb; + closure->user_data = arg; + + return g_signal_connect_closure_by_id(self, + binder_radio_caps_signals[CAPS_SIGNAL_RAF_CHANGED], + 0, &cc->closure, FALSE); + } + return 0; +} + +void +binder_radio_caps_remove_handler( + BinderRadioCaps* caps, + gulong id) +{ + if (G_LIKELY(id)) { + BinderRadioCapsObject* self = binder_radio_caps_cast(caps); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +static +void +binder_radio_caps_object_finalize( + GObject* object) +{ + BinderRadioCapsObject* self = RADIO_CAPS(object); + BinderRadioCapsManager* mgr = self->pub.mgr; + + binder_radio_remove_all_handlers(self->radio, self->radio_event_id); + binder_sim_settings_remove_all_handlers(self->settings, + self->settings_event_id); + binder_sim_card_remove_all_handlers(self->simcard, self->simcard_event_id); + + ofono_watch_remove_all_handlers(self->watch, self->watch_event_id); + ofono_watch_unref(self->watch); + + binder_radio_caps_manager_remove(mgr, self); + binder_radio_caps_manager_unref(mgr); + + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + radio_client_remove_all_handlers(self->client, self->client_event_id); + radio_client_unref(self->client); + + binder_data_unref(self->data); + binder_radio_unref(self->radio); + binder_sim_card_unref(self->simcard); + binder_sim_settings_unref(self->settings); + gutil_idle_pool_unref(self->idle_pool); + g_free(self->log_prefix); + g_free(self->cap); + g_free(self->old_cap); + g_free(self->new_cap); + G_OBJECT_CLASS(binder_radio_caps_object_parent_class)->finalize(object); +} + +static +void +binder_radio_caps_object_init( + BinderRadioCapsObject* self) +{ + self->idle_pool = gutil_idle_pool_ref + (gutil_idle_pool_get(&binder_radio_caps_shared_pool)); +} + +static +void +binder_radio_caps_object_class_init(BinderRadioCapsObjectClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_radio_caps_object_finalize; + binder_radio_caps_signals[CAPS_SIGNAL_RAF_CHANGED] = + g_signal_new(CAPS_SIGNAL_RAF_CHANGED_NAME, + G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/*==========================================================================* + * binder_radio_caps_manager + *==========================================================================*/ + +static +const char* +binder_radio_caps_manager_order_str( + BinderRadioCapsManager* self, + const guint* order) +{ + const guint n = self->caps_list->len; + + if (n > 0) { + guint i; + char* str; + GString* buf = g_string_sized_new(2*n + 2 /* roughly */); + + g_string_append_printf(buf, "(%u", order[0]); + for (i = 1; i < n; i++) { + g_string_append_printf(buf, ",%u", order[i]); + } + g_string_append_c(buf, ')'); + str = g_string_free(buf, FALSE); + gutil_idle_pool_add(self->idle_pool, str, g_free); + return str; + } else { + return "()"; + } +} + +static +const char* +binder_radio_caps_manager_role_str( + BinderRadioCapsManager* self, + enum ofono_slot_data_role role) +{ + char* str; + + switch (role) { + case OFONO_SLOT_DATA_NONE: + return "none"; + case OFONO_SLOT_DATA_MMS: + return "mms"; + case OFONO_SLOT_DATA_INTERNET: + return "internet"; + } + + str = g_strdup_printf("%d", (int)role); + gutil_idle_pool_add(self->idle_pool, str, g_free); + return str; +} + +static +void +binder_radio_caps_manager_foreach( + BinderRadioCapsManager* self, + BinderRadioCapsEnumFunc cb) +{ + guint i; + const GPtrArray* list = self->caps_list; + + for (i = 0; i < list->len; i++) { + cb(self, (BinderRadioCapsObject*)(list->pdata[i])); + } +} + +static +void +binder_radio_caps_manager_foreach_tx( + BinderRadioCapsManager* self, + BinderRadioCapsEnumFunc cb) +{ + guint i; + const GPtrArray* list = self->caps_list; + + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + /* Ignore the modems not associated with this transaction */ + if (caps->tx_id == self->tx_id) { + cb(self, caps); + } + } +} + +static +gboolean +binder_radio_caps_manager_tx_pending( + BinderRadioCapsManager* self) +{ + guint i; + const GPtrArray* list = self->caps_list; + + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + /* Ignore the modems not associated with this transaction */ + if (caps->tx_id == self->tx_id && caps->tx_pending > 0) { + return TRUE; + } + } + + return FALSE; +} + +/** + * Checks that all radio caps have been initialized (i.e. all the initial + * GET_RADIO_CAPABILITY requests have completed) and there's no transaction + * in progress. + */ +static +gboolean +binder_radio_caps_manager_can_check( + BinderRadioCapsManager* self) +{ + if (self->caps_list && !binder_radio_caps_manager_tx_pending(self)) { + const GPtrArray* list = self->caps_list; + const BinderRadioCapsObject* prev_caps = NULL; + gboolean all_modes_equal = TRUE; + guint i; + + for (i = 0; i < list->len; i++) { + const BinderRadioCapsObject* caps = list->pdata[i]; + const BinderRadio* radio = caps->radio; + const BinderSimCardStatus* status = caps->simcard->status; + const gboolean slot_enabled = (caps->watch->modem != NULL); + const gboolean sim_present = status && + (status->card_state == RADIO_CARD_STATE_PRESENT); + + if (slot_enabled && ((radio->online && + (radio->state != RADIO_STATE_ON || !caps->cap)) || + (sim_present && !caps->settings->imsi))) { + DBG_(caps, "not ready"); + return FALSE; + } + + if (!prev_caps) { + prev_caps = caps; + } else if (binder_radio_caps_access_mode(prev_caps->cap) != + binder_radio_caps_access_mode(caps->cap)) { + all_modes_equal = FALSE; + } + + DBG_(caps, "enabled=%s,online=%s,sim=%s,imsi=%s," + "raf=0x%x(%s),uuid=%s,req=%s,score=%d", + slot_enabled ? "yes" : "no", radio->online ? "yes" : "no", + status ? (status->card_state == RADIO_CARD_STATE_PRESENT) ? + "yes" : "no" : "?", + caps->settings->imsi ? caps->settings->imsi : "", + caps->cap ? caps->cap->raf : 0, + ofono_radio_access_mode_to_string + (binder_radio_caps_access_mode(caps->cap)), + caps->cap ? caps->cap->logicalModemUuid.data.str : "(null)", + ofono_radio_access_mode_to_string(caps->requested_modes), + binder_radio_caps_score(caps, caps->cap)); + } + return !all_modes_equal; + } + return FALSE; +} + +static +void +binder_radio_caps_manager_issue_requests( + BinderRadioCapsManager* self, + const BinderRadioCapsRequestTxPhase* phase, + RadioRequestCompleteFunc handler) +{ + guint i; + const GPtrArray* list = self->caps_list; + + DBG("%s transaction %d", phase->name, self->tx_id); + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + /* Ignore the modems not associated with this transaction */ + if (caps->tx_id == self->tx_id) { + GBinderWriter args; + RadioRequest* req = radio_request_new2(caps->g, + RADIO_REQ_SET_RADIO_CAPABILITY, &args, + handler, NULL, caps); + const RadioCapability* cap = phase->send_new_cap ? + caps->new_cap : caps->old_cap; + RadioCapability* rc = gbinder_writer_new0(&args, RadioCapability); + guint index; + + /* setRadioCapability(int32 serial, RadioCapability rc) */ + rc->session = self->tx_id; + rc->phase = phase->phase; + rc->raf = cap->raf; + rc->status = phase->status; + rc->logicalModemUuid = cap->logicalModemUuid; + if (rc->logicalModemUuid.len) { + rc->logicalModemUuid.data.str = gbinder_writer_memdup(&args, + cap->logicalModemUuid.data.str, + cap->logicalModemUuid.len + 1); + } + + /* Write transaction arguments */ + index = gbinder_writer_append_buffer_object(&args, rc, sizeof(*rc)); + binder_append_hidl_string_data(&args, rc, logicalModemUuid, index); + + /* Submit the request */ + radio_request_set_timeout(req, SET_CAPS_TIMEOUT_MS); + if (radio_request_submit(req)) { + /* Count it */ + caps->tx_pending++; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + } else { + ofono_error("failed to set radio caps"); + } + radio_request_unref(req); + } + } +} + +static +void +binder_radio_caps_manager_next_transaction_cb( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + radio_request_group_cancel(caps->g); + radio_client_remove_handlers(caps->client, caps->client_event_id + + CLIENT_EVENT_OWNER, 1); + binder_sim_card_remove_handlers(caps->simcard, caps->simcard_event_id + + SIM_EVENT_IO_ACTIVE_CHANGED, 1); +} + +static +void +binder_radio_caps_manager_next_transaction( + BinderRadioCapsManager* self) +{ + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_next_transaction_cb); + self->tx_failed = FALSE; + self->tx_phase_index = -1; + self->tx_id++; + if (self->tx_id <= 0) self->tx_id = 1; +} + +static +void binder_radio_caps_manager_cancel_cb( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + GASSERT(!caps->client_event_id[CLIENT_EVENT_OWNER]); + radio_request_group_unblock(caps->g); +} + +static +void +binder_radio_caps_manager_transaction_done( + BinderRadioCapsManager* self) +{ + binder_radio_caps_manager_schedule_check(self); + binder_data_manager_assert_data_on(self->data_manager); + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_cancel_cb); +} + +static +void +binder_radio_caps_manager_abort_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioCapsObject* caps = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = caps->pub.mgr; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_RADIO_CAPABILITY) { + if (error != RADIO_ERROR_NONE) { + DBG_(caps, "Failed to abort radio caps switch, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected setRadioCapability response %d", resp); + } + } + + GASSERT(caps->tx_pending > 0); + caps->tx_pending--; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + + if (!binder_radio_caps_manager_tx_pending(self)) { + DBG("transaction aborted"); + binder_radio_caps_manager_transaction_done(self); + } +} + +static +void +binder_radio_caps_manager_abort_transaction( + BinderRadioCapsManager* self) +{ + guint i; + const GPtrArray* list = self->caps_list; + const int prev_tx_id = self->tx_id; + + /* Generate new transaction id */ + DBG("aborting transaction %d", prev_tx_id); + binder_radio_caps_manager_next_transaction(self); + + /* Re-associate the modems with the new transaction */ + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + if (caps->tx_id == prev_tx_id) { + caps->tx_id = self->tx_id; + } + } + + /* + * Issue a FINISH with RADIO_CAPABILITY_STATUS_FAIL. That's what + * com.android.internal.telephony.ProxyController does (or at least + * did last time when I checked) when something goes wrong. + */ + binder_radio_caps_manager_issue_requests(self, + &binder_radio_caps_fail_phase, binder_radio_caps_manager_abort_cb); + + /* Notify the listeners */ + g_signal_emit(self, binder_radio_caps_manager_signals + [CAPS_MANAGER_SIGNAL_ABORTED], 0); +} + +static +void binder_radio_caps_manager_next_phase_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioCapsObject* caps = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = caps->pub.mgr; + gboolean ok = FALSE; + + GASSERT(caps->tx_pending > 0); + if (status == RADIO_TX_STATUS_OK) { + /* getRadioCapabilityResponse(RadioResponseInfo, RadioCapability rc) */ + if (resp == RADIO_RESP_SET_RADIO_CAPABILITY) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + const RadioCapability* rc; + + gbinder_reader_copy(&reader, args); + rc = gbinder_reader_read_hidl_struct(&reader, RadioCapability); + if (rc && rc->status != RADIO_CAPABILITY_STATUS_FAIL) { + ok = TRUE; + } + } else { + DBG_(caps, "Failed to set radio caps, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected setRadioCapability response %d", resp); + } + } + + if (!ok) { + if (!self->tx_failed) { + self->tx_failed = TRUE; + DBG("transaction %d failed", self->tx_id); + } + } + + caps->tx_pending--; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + if (!binder_radio_caps_manager_tx_pending(self)) { + if (self->tx_failed) { + binder_radio_caps_manager_abort_transaction(self); + } else { + binder_radio_caps_manager_next_phase(self); + } + } +} + +static +void +binder_radio_caps_manager_next_phase( + BinderRadioCapsManager* self) +{ + /* Note: -1 > 2 if 2 is unsigned (which turns -1 into 4294967295) */ + const int max_index = G_N_ELEMENTS(binder_radio_caps_tx_phase) - 1; + + GASSERT(!binder_radio_caps_manager_tx_pending(self)); + if (self->tx_phase_index >= max_index) { + const GPtrArray* list = self->caps_list; + GSList* updated_caps = NULL; + GSList* l; + guint i; + + DBG("transaction %d is done", self->tx_id); + + /* Update all caps before emitting signals */ + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + if (caps->tx_id == self->tx_id) { + caps->cap = caps->new_cap; + /* Better bump refs to make sure BinderRadioCapsObject + * don't get freed by a signal handler */ + binder_radio_caps_object_ref(caps); + updated_caps = g_slist_append(updated_caps, caps); + } + } + /* binder_radio_caps_update_raf will emit signals if needed */ + for (l = updated_caps; l; l = l->next) { + binder_radio_caps_update_raf((BinderRadioCapsObject*)l->data); + } + + binder_radio_caps_manager_transaction_done(self); + /* Free temporary BinderRadioCapsObject references */ + g_slist_free_full(updated_caps, g_object_unref); + g_signal_emit(self, binder_radio_caps_manager_signals + [CAPS_MANAGER_SIGNAL_TX_DONE], 0); + } else { + const BinderRadioCapsRequestTxPhase* phase = + binder_radio_caps_tx_phase + (++self->tx_phase_index); + + binder_radio_caps_manager_issue_requests(self, phase, + binder_radio_caps_manager_next_phase_cb); + } +} + +static +void +binder_radio_caps_manager_data_off_done( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + if (!binder_radio_caps_manager_tx_pending(self)) { + if (self->tx_failed) { + DBG("failed to start the transaction"); + binder_data_manager_assert_data_on(self->data_manager); + binder_radio_caps_manager_recheck_later(self); + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_cancel_cb); + g_signal_emit(self, binder_radio_caps_manager_signals + [CAPS_MANAGER_SIGNAL_ABORTED], 0); + } else { + DBG("starting transaction"); + binder_radio_caps_manager_next_phase(self); + } + } +} + +static +void +binder_radio_caps_manager_data_disallowed( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioCapsObject* caps = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = caps->pub.mgr; + + GASSERT(caps->tx_pending > 0); + caps->tx_pending--; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + + if (status != RADIO_TX_STATUS_OK || error != RADIO_ERROR_NONE) { + self->tx_failed = TRUE; + } + + binder_radio_caps_manager_data_off_done(self, caps); +} + +static +void +binder_radio_caps_manager_data_off( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + if (binder_data_manager_need_set_data_allowed(self->data_manager)) { + RadioRequest* req = binder_data_set_data_allowed_request_new(caps->g, + FALSE, binder_radio_caps_manager_data_disallowed, NULL, caps); + + caps->tx_pending++; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + radio_request_set_timeout(req, DATA_OFF_TIMEOUT_MS); + radio_request_submit(req); + radio_request_unref(req); + } else { + binder_radio_caps_manager_data_off_done(self, caps); + } +} + +static +void +binder_radio_caps_manager_deactivate_data_call_done( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderRadioCapsObject* caps = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = caps->pub.mgr; + + GASSERT(caps->tx_pending > 0); + caps->tx_pending--; + DBG_(caps, "tx_pending=%d", caps->tx_pending); + + if (status != RADIO_TX_STATUS_OK || error != RADIO_ERROR_NONE) { + self->tx_failed = TRUE; + /* + * Something seems to be slightly broken, try requesting the + * current state (later, after we release the block). + */ + binder_data_poll_call_state(caps->data); + } + + if (!binder_radio_caps_manager_tx_pending(self)) { + if (self->tx_failed) { + DBG("failed to start the transaction"); + binder_radio_caps_manager_recheck_later(self); + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_cancel_cb); + } else { + binder_radio_caps_manager_foreach_tx(self, + binder_radio_caps_manager_data_off); + } + } +} + +static +void +binder_radio_caps_deactivate_data_call( + BinderRadioCapsObject* caps, + int cid) +{ + RadioRequest* req = binder_data_deactivate_data_call_request_new(caps->g, + cid, binder_radio_caps_manager_deactivate_data_call_done, + NULL, caps); + + caps->tx_pending++; + DBG_(caps, "cid=%u, tx_pending=%d", cid, caps->tx_pending); + radio_request_set_blocking(req, TRUE); + radio_request_set_timeout(req, DEACTIVATE_TIMEOUT_MS); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_radio_caps_deactivate_data_call_cb( + gpointer list_data, + gpointer user_data) +{ + BinderDataCall* call = list_data; + + if (call->status == RADIO_DATA_CALL_FAIL_NONE) { + binder_radio_caps_deactivate_data_call(RADIO_CAPS(user_data), + call->cid); + } +} + +static +void +binder_radio_caps_manager_deactivate_all_cb( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + BinderData* data = caps->data; + + if (data) { + g_slist_foreach(data->calls, binder_radio_caps_deactivate_data_call_cb, + caps); + } +} + +static +void +binder_radio_caps_manager_deactivate_all( + BinderRadioCapsManager* self) +{ + binder_radio_caps_manager_foreach_tx(self, + binder_radio_caps_manager_deactivate_all_cb); + if (!binder_radio_caps_manager_tx_pending(self)) { + /* No data calls, submit setDataAllowed requests right away */ + binder_radio_caps_manager_foreach_tx(self, + binder_radio_caps_manager_data_off); + GASSERT(binder_radio_caps_manager_tx_pending(self)); + } +} + +static +void +binder_radio_caps_tx_wait_cb( + RadioClient* client, + void* user_data) +{ + BinderRadioCapsObject* caps = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = caps->pub.mgr; + const GPtrArray* list = self->caps_list; + gboolean can_start = TRUE; + guint i; + + if (radio_request_group_block_status(caps->g) == RADIO_BLOCK_ACQUIRED) { + /* We no longer need owner notifications from this channel */ + radio_client_remove_handlers(caps->client, caps->client_event_id + + CLIENT_EVENT_OWNER, 1); + } + + /* Check if all channels are ours */ + for (i = 0; i < list->len && can_start; i++) { + const BinderRadioCapsObject* caps = list->pdata[i]; + + if (caps->tx_id == self->tx_id && + radio_request_group_block_status(caps->g) != RADIO_BLOCK_ACQUIRED) { + /* Still waiting for this one */ + DBG_(caps, "still waiting"); + can_start = FALSE; + } + } + + if (can_start) { + /* All modems are ready */ + binder_radio_caps_manager_deactivate_all(self); + } +} + +static +void +binder_radio_caps_manager_lock_io_for_transaction( + BinderRadioCapsManager* self) +{ + const GPtrArray* list = self->caps_list; + gboolean can_start = TRUE; + guint i; + + /* + * We want to actually start the transaction when all the involved + * modems stop doing other things. Otherwise some modems get confused + * and break. We have already checked that SIM I/O has stopped. The + * next synchronization point is the completion of all deactivateDataCall + * and setDataAllowed requests. Then we can really start the capability + * switch transaction. + */ + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + /* + * Reset the block to make sure that we get to the end of + * the block queue (to avoid deadlocks since we are going + * to wait for all request groups to become the owners + * before actually starting the transaction) + */ + radio_request_group_unblock(caps->g); + + /* + * Check if we need to wait for all requests to complete for + * this client before we can actually start the transaction. + */ + if (radio_request_group_block(caps->g) == RADIO_BLOCK_QUEUED) { + GASSERT(!caps->client_event_id[CLIENT_EVENT_OWNER]); + caps->client_event_id[CLIENT_EVENT_OWNER] = + radio_client_add_owner_changed_handler(caps->client, + binder_radio_caps_tx_wait_cb, caps); + can_start = FALSE; + } + } + + if (can_start) { + /* All modems are ready */ + binder_radio_caps_manager_deactivate_all(self); + } +} + +static +void +binder_radio_caps_manager_stop_sim_io_watch( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + /* binder_sim_card_remove_handlers zeros the id */ + binder_sim_card_remove_handlers(caps->simcard, caps->simcard_event_id + + SIM_EVENT_IO_ACTIVE_CHANGED, 1); +} + +static +void +binder_radio_caps_tx_wait_sim_io_cb( + BinderSimCard* simcard, + void* user_data) +{ + BinderRadioCapsObject* src = RADIO_CAPS(user_data); + BinderRadioCapsManager* self = src->pub.mgr; + const GPtrArray* list = self->caps_list; + guint i; + + for (i = 0; i < list->len; i++) { + const BinderRadioCapsObject* caps = list->pdata[i]; + + if (caps->simcard->sim_io_active) { + DBG_(caps, "still waiting for SIM I/O to calm down"); + return; + } + } + + /* We no longer need to be notified about SIM I/O activity */ + DBG("SIM I/O has calmed down"); + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_stop_sim_io_watch); + + /* Now this looks like a good moment to start the transaction */ + binder_radio_caps_manager_lock_io_for_transaction(self); +} + +static +void +binder_radio_caps_manager_start_sim_io_watch( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + caps->simcard_event_id[SIM_EVENT_IO_ACTIVE_CHANGED] = + binder_sim_card_add_sim_io_active_changed_handler(caps->simcard, + binder_radio_caps_tx_wait_sim_io_cb, caps); +} + +static +void +binder_radio_caps_manager_start_transaction( + BinderRadioCapsManager *self) +{ + const GPtrArray* list = self->caps_list; + gboolean sim_io_active = FALSE; + guint i, count = 0; + + /* Start the new request transaction */ + binder_radio_caps_manager_next_transaction(self); + DBG("transaction %d", self->tx_id); + + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + if (!binder_radio_caps_equal(caps->new_cap, caps->old_cap)) { + /* Mark it as taking part in this transaction */ + caps->tx_id = self->tx_id; + count++; + if (caps->simcard->sim_io_active) { + sim_io_active = TRUE; + } + } + } + + GASSERT(count); + if (!count) { + /* This is not supposed to happen */ + DBG("nothing to do!"); + } else if (sim_io_active) { + DBG("waiting for SIM I/O to calm down"); + binder_radio_caps_manager_foreach_tx(self, + binder_radio_caps_manager_start_sim_io_watch); + } else { + /* Make sure we don't get notified about SIM I/O activity */ + binder_radio_caps_manager_foreach(self, + binder_radio_caps_manager_stop_sim_io_watch); + + /* And continue with locking BINDER I/O for the transaction */ + binder_radio_caps_manager_lock_io_for_transaction(self); + } +} + +static +void +binder_radio_caps_manager_set_order( + BinderRadioCapsManager* self, + const guint* order) +{ + const GPtrArray* list = self->caps_list; + guint i; + + DBG("%s => %s", + binder_radio_caps_manager_order_str(self, self->order_list->pdata[0]), + binder_radio_caps_manager_order_str(self, order)); + + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* dest = list->pdata[i]; + const BinderRadioCapsObject* src = list->pdata[order[i]]; + + g_free(dest->old_cap); + g_free(dest->new_cap); + dest->old_cap = binder_radio_caps_dup(dest->cap); + dest->new_cap = binder_radio_caps_dup(src->cap); + } + binder_radio_caps_manager_start_transaction(self); +} + +static +void +binder_radio_caps_manager_check( + BinderRadioCapsManager *self) +{ + if (binder_radio_caps_manager_can_check(self)) { + guint i; + const GPtrArray* list = self->caps_list; + const GPtrArray* permutations = self->order_list; + int highest_score = -INT_MAX, best_index = -1; + + for (i = 0; i < permutations->len; i++) { + const guint* order = permutations->pdata[i]; + int score = 0; + guint k; + + for (k = 0; k < list->len; k++) { + const BinderRadioCapsObject *c1 = list->pdata[k]; + const BinderRadioCapsObject *c2 = list->pdata[order[k]]; + + score += binder_radio_caps_score(c1, c2->cap); + } + + DBG("%s %d", binder_radio_caps_manager_order_str(self, order), + score); + if (score > highest_score) { + highest_score = score; + best_index = i; + } + } + + if (best_index > 0) { + binder_radio_caps_manager_set_order(self, + permutations->pdata[best_index]); + } + } +} + +static +gboolean +binder_radio_caps_manager_check_cb( + gpointer user_data) +{ + BinderRadioCapsManager* self = RADIO_CAPS_MANAGER(user_data); + + GASSERT(self->check_id); + self->check_id = 0; + binder_radio_caps_manager_check(self); + return G_SOURCE_REMOVE; +} + +static +void +binder_radio_caps_manager_recheck_later( + BinderRadioCapsManager* self) +{ + if (!binder_radio_caps_manager_tx_pending(self)) { + if (self->check_id) { + g_source_remove(self->check_id); + self->check_id = 0; + } + self->check_id = g_timeout_add_seconds(CHECK_LATER_TIMEOUT_SEC, + binder_radio_caps_manager_check_cb, self); + } +} + +static +void +binder_radio_caps_manager_schedule_check( + BinderRadioCapsManager* self) +{ + if (!self->check_id && !binder_radio_caps_manager_tx_pending(self)) { + self->check_id = g_idle_add(binder_radio_caps_manager_check_cb, self); + } +} + +static +gint +binder_caps_manager_request_sort( + gconstpointer a, + gconstpointer b) +{ + const BinderRadioCapsRequest* r1 = *(void**)a; + const BinderRadioCapsRequest* r2 = *(void**)b; + + /* MMS requests have higher priority */ + if (r1->role == OFONO_SLOT_DATA_MMS) { + if (r2->role != OFONO_SLOT_DATA_MMS) { + return -1; + } + } else if (r2->role == OFONO_SLOT_DATA_MMS) { + return 1; + } + return (int)r2->role - (int)r1->role; +} + +static +void +binder_radio_caps_manager_consider_requests( + BinderRadioCapsManager* self) +{ + guint i; + gboolean changed = FALSE; + const GPtrArray* list = self->caps_list; + GPtrArray* requests = self->requests; + + if (requests->len) { + const BinderRadioCapsRequest* req; + + g_ptr_array_sort(requests, binder_caps_manager_request_sort); + req = self->requests->pdata[0]; + + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + BinderSimSettings* settings = caps->settings; + const enum ofono_radio_access_mode modes = (req->caps == caps) ? + (req->modes & settings->pref) : OFONO_RADIO_ACCESS_MODE_NONE; + + if (caps->requested_modes != modes) { + caps->requested_modes = modes; + changed = TRUE; + } + } + } else { + for (i = 0; i < list->len; i++) { + BinderRadioCapsObject* caps = list->pdata[i]; + + if (caps->requested_modes) { + caps->requested_modes = OFONO_RADIO_ACCESS_MODE_NONE; + changed = TRUE; + } + } + } + if (changed) { + binder_radio_caps_manager_schedule_check(self); + } +} + +static +void +binder_radio_caps_manager_list_changed( + BinderRadioCapsManager* self) +{ + /* Order list elements according to slot numbers */ + g_ptr_array_sort(self->caps_list, binder_radio_caps_slot_compare); + + /* Generate full list of available permutations */ + binder_radio_caps_generate_permutations(self->order_list, + self->caps_list->len); +} + +static +void +binder_radio_caps_manager_add( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + g_ptr_array_add(self->caps_list, caps); + binder_radio_caps_manager_list_changed(self); +} + +static +void binder_radio_caps_manager_remove( + BinderRadioCapsManager* self, + BinderRadioCapsObject* caps) +{ + if (g_ptr_array_remove(self->caps_list, caps)) { + binder_radio_caps_manager_list_changed(self); + } +} + +gulong +binder_radio_caps_manager_add_tx_aborted_handler( + BinderRadioCapsManager* self, + BinderRadioCapsManagerFunc cb, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(cb)) ? g_signal_connect(self, + CAPS_MANAGER_SIGNAL_ABORTED_NAME, G_CALLBACK(cb), user_data) : 0; +} + +gulong +binder_radio_caps_manager_add_tx_done_handler( + BinderRadioCapsManager* self, + BinderRadioCapsManagerFunc cb, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(cb)) ? g_signal_connect(self, + CAPS_MANAGER_SIGNAL_TX_DONE_NAME, G_CALLBACK(cb), user_data) : 0; +} + +void +binder_radio_caps_manager_remove_handler( + BinderRadioCapsManager* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +binder_radio_caps_manager_remove_handlers( + BinderRadioCapsManager* self, + gulong* ids, + int count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +BinderRadioCapsManager* +binder_radio_caps_manager_ref( + BinderRadioCapsManager *self) +{ + if (G_LIKELY(self)) { + g_object_ref(RADIO_CAPS_MANAGER(self)); + } + return self; +} + +void +binder_radio_caps_manager_unref( + BinderRadioCapsManager* self) +{ + if (G_LIKELY(self)) { + g_object_unref(RADIO_CAPS_MANAGER(self)); + } +} + +BinderRadioCapsManager* +binder_radio_caps_manager_new( + BinderDataManager* dm) +{ + BinderRadioCapsManager* self = g_object_new(RADIO_CAPS_MANAGER_TYPE, 0); + + self->data_manager = binder_data_manager_ref(dm); + return self; +} + +static +void +binder_radio_caps_manager_init( + BinderRadioCapsManager* self) +{ + self->caps_list = g_ptr_array_new(); + self->order_list = g_ptr_array_new(); + self->requests = g_ptr_array_new(); + self->tx_phase_index = -1; + self->idle_pool = gutil_idle_pool_ref + (gutil_idle_pool_get(&binder_radio_caps_shared_pool)); +} + +static +void +binder_radio_caps_manager_finalize( + GObject* object) +{ + BinderRadioCapsManager* self = RADIO_CAPS_MANAGER(object); + + GASSERT(!self->caps_list->len); + GASSERT(!self->order_list->len); + GASSERT(!self->requests->len); + g_ptr_array_free(self->caps_list, TRUE); + g_ptr_array_free(self->order_list, TRUE); + g_ptr_array_free(self->requests, TRUE); + if (self->check_id) { + g_source_remove(self->check_id); + } + binder_data_manager_unref(self->data_manager); + gutil_idle_pool_unref(self->idle_pool); + G_OBJECT_CLASS(binder_radio_caps_manager_parent_class)->finalize(object); +} + +static +void +binder_radio_caps_manager_class_init( + BinderRadioCapsManagerClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + G_OBJECT_CLASS(klass)->finalize = binder_radio_caps_manager_finalize; + binder_radio_caps_manager_signals[CAPS_MANAGER_SIGNAL_ABORTED] = + g_signal_new(CAPS_MANAGER_SIGNAL_ABORTED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + binder_radio_caps_manager_signals[CAPS_MANAGER_SIGNAL_TX_DONE] = + g_signal_new(CAPS_MANAGER_SIGNAL_TX_DONE_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/*==========================================================================* + * binder_radio_caps_request + *==========================================================================*/ + +BinderRadioCapsRequest* +binder_radio_caps_request_new( + BinderRadioCaps* pub, + enum ofono_radio_access_mode modes, + enum ofono_slot_data_role role) +{ + BinderRadioCapsRequest* req = NULL; + BinderRadioCapsObject* caps = binder_radio_caps_cast(pub); + + if (caps) { + BinderRadioCapsManager *mgr = pub->mgr; + + DBG_(caps, "%s %s (0x%02x)", + binder_radio_caps_manager_role_str(pub->mgr, role), + ofono_radio_access_mode_to_string(modes), modes); + req = g_slice_new(BinderRadioCapsRequest); + binder_radio_caps_object_ref(req->caps = caps); + req->modes = modes; + req->role = role; + g_ptr_array_add(mgr->requests, req); + binder_radio_caps_manager_consider_requests(mgr); + } + return req; +} + +void +binder_radio_caps_request_free( + BinderRadioCapsRequest* req) +{ + if (req) { + /* In case if g_object_unref frees the caps */ + BinderRadioCapsManager* mgr = + binder_radio_caps_manager_ref(req->caps->pub.mgr); + + DBG_(req->caps, "%s (%s)", + binder_radio_caps_manager_role_str(mgr, req->role), + ofono_radio_access_mode_to_string(req->modes)); + g_ptr_array_remove(mgr->requests, req); + binder_radio_caps_object_unref(req->caps); + gutil_slice_free(req); + binder_radio_caps_manager_consider_requests(mgr); + binder_radio_caps_manager_unref(mgr); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio_caps.h b/src/binder_radio_caps.h new file mode 100644 index 0000000..c22bdfb --- /dev/null +++ b/src/binder_radio_caps.h @@ -0,0 +1,171 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_RADIO_CAPS_H +#define BINDER_RADIO_CAPS_H + +#include "binder_types.h" + +#include +#include + +struct ofono_watch; + +typedef +void +(*BinderRadioCapsFunc)( + BinderRadioCaps* caps, + void* user_data); + +typedef +void +(*BinderRadioCapsManagerFunc)( + BinderRadioCapsManager* mgr, + void* user_data); + +/* RadioCapability pointer is NULL if functionality is unsupported */ +typedef +void +(*BinderRadioCapsCheckFunc)( + const RadioCapability* cap, + void* user_data); + +/* Caller must unref the request */ +RadioRequest* +binder_radio_caps_check( + RadioClient* client, + BinderRadioCapsCheckFunc cb, + void* user_data) + G_GNUC_WARN_UNUSED_RESULT + BINDER_INTERNAL; + +/* There must be a single BinderRadioCapsManager shared by all modems */ +BinderRadioCapsManager* +binder_radio_caps_manager_new( + BinderDataManager* data) + BINDER_INTERNAL; + +BinderRadioCapsManager* +binder_radio_caps_manager_ref( + BinderRadioCapsManager* mgr) + BINDER_INTERNAL; + +void +binder_radio_caps_manager_unref( + BinderRadioCapsManager* mgr) + BINDER_INTERNAL; + +gulong +binder_radio_caps_manager_add_tx_aborted_handler( + BinderRadioCapsManager* mgr, + BinderRadioCapsManagerFunc cb, + void* user_data) + BINDER_INTERNAL; + +gulong +binder_radio_caps_manager_add_tx_done_handler( + BinderRadioCapsManager* mgr, + BinderRadioCapsManagerFunc cb, + void* user_data) + BINDER_INTERNAL; + +void +binder_radio_caps_manager_remove_handler( + BinderRadioCapsManager* mgr, + gulong id) + BINDER_INTERNAL; + +void +binder_radio_caps_manager_remove_handlers( + BinderRadioCapsManager* mgr, + gulong* ids, + int count) + BINDER_INTERNAL; + +#define binder_radio_caps_manager_remove_all_handlers(mgr, ids) \ + binder_radio_caps_manager_remove_handlers(mgr, ids, G_N_ELEMENTS(ids)) + +/* And one BinderRadioCaps object per modem */ + +struct binder_radio_caps { + BinderRadioCapsManager* mgr; + RADIO_ACCESS_FAMILY raf; +}; + +BinderRadioCaps* +binder_radio_caps_new( + BinderRadioCapsManager* mgr, + const char* log_prefix, + RadioClient* client, + struct ofono_watch* watch, + BinderData* data, + BinderRadio* radio, + BinderSimCard* sim, + BinderSimSettings* settings, + const BinderSlotConfig* config, + const RadioCapability* cap) + BINDER_INTERNAL; + +BinderRadioCaps* +binder_radio_caps_ref( + BinderRadioCaps* caps) + BINDER_INTERNAL; + +void +binder_radio_caps_unref( + BinderRadioCaps* caps) + BINDER_INTERNAL; + +void +binder_radio_caps_drop( + BinderRadioCaps* caps) + BINDER_INTERNAL; + +gulong +binder_radio_caps_add_raf_handler( + BinderRadioCaps* caps, + BinderRadioCapsFunc cb, + void* user_data) + BINDER_INTERNAL; + +void +binder_radio_caps_remove_handler( + BinderRadioCaps* caps, + gulong id) + BINDER_INTERNAL; + +/* Data requests */ + +BinderRadioCapsRequest* +binder_radio_caps_request_new( + BinderRadioCaps* caps, + enum ofono_radio_access_mode modes, /* Mask */ + enum ofono_slot_data_role role) + BINDER_INTERNAL; + +void +binder_radio_caps_request_free( + BinderRadioCapsRequest* req) + BINDER_INTERNAL; + +#endif /* BINDER_RADIO_CAPS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio_settings.c b/src/binder_radio_settings.c new file mode 100644 index 0000000..4ef5705 --- /dev/null +++ b/src/binder_radio_settings.c @@ -0,0 +1,251 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_modem.h" +#include "binder_radio_settings.h" +#include "binder_sim_settings.h" +#include "binder_util.h" +#include "binder_log.h" + +#include + +typedef struct binder_radio_settings { + struct ofono_radio_settings* rs; + BinderSimSettings* settings; + char* log_prefix; + guint callback_id; +} BinderRadioSettings; + +typedef struct binder_radio_settings_cbd { + BinderRadioSettings* self; + union _ofono_radio_settings_cb { + ofono_radio_settings_rat_mode_set_cb_t rat_mode_set; + ofono_radio_settings_rat_mode_query_cb_t rat_mode_query; + ofono_radio_settings_available_rats_query_cb_t available_rats; + BinderCallback ptr; + } cb; + gpointer data; +} BinderRadioSettingsCbData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderRadioSettings* +binder_radio_settings_get_data(struct ofono_radio_settings* rs) + { return ofono_radio_settings_get_data(rs); } + +static +void +binder_radio_settings_callback_data_free( + gpointer cbd) +{ + g_slice_free(BinderRadioSettingsCbData, cbd); +} + +static +void +binder_radio_settings_later( + BinderRadioSettings* self, + GSourceFunc fn, + BinderCallback cb, + void* data) +{ + BinderRadioSettingsCbData* cbd = g_slice_new0(BinderRadioSettingsCbData); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + + GASSERT(!self->callback_id); + self->callback_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, fn, cbd, + binder_radio_settings_callback_data_free); +} + +static +gboolean +binder_radio_settings_set_rat_mode_cb( + gpointer user_data) +{ + BinderRadioSettingsCbData* cbd = user_data; + BinderRadioSettings* self = cbd->self; + struct ofono_error error; + + GASSERT(self->callback_id); + self->callback_id = 0; + cbd->cb.rat_mode_set(binder_error_ok(&error), cbd->data); + return G_SOURCE_REMOVE; +} + +static +void +binder_radio_settings_set_rat_mode( + struct ofono_radio_settings* rs, + enum ofono_radio_access_mode mode, + ofono_radio_settings_rat_mode_set_cb_t cb, + void* data) +{ + BinderRadioSettings* self = binder_radio_settings_get_data(rs); + + DBG_(self, "%s", ofono_radio_access_mode_to_string(mode)); + binder_sim_settings_set_pref(self->settings, + binder_access_modes_up_to(mode)); + binder_radio_settings_later(self, + binder_radio_settings_set_rat_mode_cb, BINDER_CB(cb), data); +} + +static +gboolean +binder_radio_settings_query_rat_mode_cb( + gpointer user_data) +{ + BinderRadioSettingsCbData* cbd = user_data; + BinderRadioSettings* self = cbd->self; + const enum ofono_radio_access_mode mode = + ofono_radio_access_max_mode(self->settings->pref); + struct ofono_error error; + + DBG_(self, "rat mode %s", ofono_radio_access_mode_to_string(mode)); + GASSERT(self->callback_id); + self->callback_id = 0; + cbd->cb.rat_mode_query(binder_error_ok(&error), mode, cbd->data); + return G_SOURCE_REMOVE; +} + +static +void +binder_radio_settings_query_rat_mode( + struct ofono_radio_settings* rs, + ofono_radio_settings_rat_mode_query_cb_t cb, + void* data) +{ + BinderRadioSettings* self = binder_radio_settings_get_data(rs); + + DBG_(self, ""); + binder_radio_settings_later(self, + binder_radio_settings_query_rat_mode_cb, BINDER_CB(cb), data); +} + +static +gboolean +binder_radio_settings_query_available_rats_cb( + gpointer data) +{ + BinderRadioSettingsCbData* cbd = data; + BinderRadioSettings* self = cbd->self; + struct ofono_error error; + + GASSERT(self->callback_id); + self->callback_id = 0; + cbd->cb.available_rats(binder_error_ok(&error), + self->settings->techs, cbd->data); + return G_SOURCE_REMOVE; +} + +static +void +binder_radio_settings_query_available_rats( + struct ofono_radio_settings* rs, + ofono_radio_settings_available_rats_query_cb_t cb, + void* data) +{ + BinderRadioSettings* self = binder_radio_settings_get_data(rs); + + DBG_(self, ""); + binder_radio_settings_later(self, + binder_radio_settings_query_available_rats_cb, BINDER_CB(cb), data); +} + +static +gboolean +binder_radio_settings_register( + gpointer user_data) +{ + BinderRadioSettings* self = user_data; + + GASSERT(self->callback_id); + self->callback_id = 0; + ofono_radio_settings_register(self->rs); + return G_SOURCE_REMOVE; +} + +static +int +binder_radio_settings_probe( + struct ofono_radio_settings* rs, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderRadioSettings* self = g_new0(struct binder_radio_settings, 1); + + self->rs = rs; + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->settings = binder_sim_settings_ref(modem->sim_settings); + self->callback_id = g_idle_add(binder_radio_settings_register, self); + + DBG_(self, ""); + ofono_radio_settings_set_data(rs, self); + return 0; +} + +static +void +binder_radio_settings_remove( + struct ofono_radio_settings* rs) +{ + BinderRadioSettings* self = binder_radio_settings_get_data(rs); + + DBG_(self, ""); + if (self->callback_id) { + g_source_remove(self->callback_id); + } + binder_sim_settings_unref(self->settings); + g_free(self->log_prefix); + g_free(self); + + ofono_radio_settings_set_data(rs, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_radio_settings_driver binder_radio_settings_driver = { + .name = BINDER_DRIVER, + .probe = binder_radio_settings_probe, + .remove = binder_radio_settings_remove, + .query_rat_mode = binder_radio_settings_query_rat_mode, + .set_rat_mode = binder_radio_settings_set_rat_mode, + .query_available_rats = binder_radio_settings_query_available_rats +}; + +void +binder_radio_settings_init() +{ + ofono_radio_settings_driver_register(&binder_radio_settings_driver); +} + +void +binder_radio_settings_cleanup() +{ + ofono_radio_settings_driver_unregister(&binder_radio_settings_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_radio_settings.h b/src/binder_radio_settings.h new file mode 100644 index 0000000..18355a7 --- /dev/null +++ b/src/binder_radio_settings.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_RADIO_SETTINGS_H +#define BINDER_RADIO_SETTINGS_H + +#include "binder_types.h" + +void +binder_radio_settings_init(void) + BINDER_INTERNAL; + +void +binder_radio_settings_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_RADIO_SETTINGS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim.c b/src/binder_sim.c new file mode 100644 index 0000000..fd6b116 --- /dev/null +++ b/src/binder_sim.c @@ -0,0 +1,2564 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_sim.h" +#include "binder_sim_card.h" +#include "binder_util.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define SIM_STATE_CHANGE_TIMEOUT_SECS (5) +#define FAC_LOCK_QUERY_TIMEOUT_SECS (10) +#define FAC_LOCK_QUERY_RETRIES (1) +#define SIM_IO_TIMEOUT_SECS (20) + +#define EF_STATUS_INVALIDATED 0 +#define EF_STATUS_VALID 1 + +/* Commands defined for TS 27.007 +CRSM */ +#define CMD_READ_BINARY 176 /* 0xB0 */ +#define CMD_READ_RECORD 178 /* 0xB2 */ +#define CMD_GET_RESPONSE 192 /* 0xC0 */ +#define CMD_UPDATE_BINARY 214 /* 0xD6 */ +#define CMD_UPDATE_RECORD 220 /* 0xDC */ +#define CMD_STATUS 242 /* 0xF2 */ +#define CMD_RETRIEVE_DATA 203 /* 0xCB */ +#define CMD_SET_DATA 219 /* 0xDB */ + +/* FID/path of SIM/USIM root directory */ +static const char ROOTMF[] = "3F00"; + +/* P2 coding (modes) for READ RECORD and UPDATE RECORD (see TS 102.221) */ +#define MODE_SELECTED (0x00) /* Currently selected EF */ +#define MODE_CURRENT (0x04) /* P1='00' denotes the current record */ +#define MODE_ABSOLUTE (0x04) /* The record number is given in P1 */ +#define MODE_NEXT (0x02) /* Next record */ +#define MODE_PREVIOUS (0x03) /* Previous record */ + +enum binder_sim_card_event { + SIM_CARD_STATUS_EVENT, + SIM_CARD_APP_EVENT, + SIM_CARD_EVENT_COUNT +}; + +enum binder_sim_io_event { + IO_EVENT_SIM_REFRESH, + IO_EVENT_COUNT +}; + +typedef struct binder_sim { + struct ofono_sim* sim; + struct ofono_watch* watch; + enum ofono_sim_password_type ofono_passwd_state; + BinderSimCard* card; + RadioRequestGroup* g; + RadioRequest* query_pin_retries_req; + GList* pin_cbd_list; + int retries[OFONO_SIM_PASSWORD_INVALID]; + gboolean empty_pin_query_allowed; + gboolean inserted; + guint idle_id; /* Used by register and SIM reset callbacks */ + guint list_apps_id; + gulong card_event_id[SIM_CARD_EVENT_COUNT]; + gulong io_event_id[IO_EVENT_COUNT]; + gulong sim_state_watch_id; + char *log_prefix; + + /* query_passwd_state context */ + ofono_sim_passwd_cb_t query_passwd_state_cb; + void* query_passwd_state_cb_data; + guint query_passwd_state_timeout_id; + gulong query_passwd_state_sim_status_refresh_id; +} BinderSim; + +typedef struct binder_sim_io_response { + guint sw1, sw2; + guint8* data; + guint data_len; +} BinderSimIoResponse; + +typedef struct binder_sim_cbd_io { + BinderSim* self; + BinderSimCard* card; + union _ofono_sim_cb { + ofono_sim_file_info_cb_t file_info; + ofono_sim_read_cb_t read; + ofono_sim_write_cb_t write; + ofono_sim_imsi_cb_t imsi; + ofono_query_facility_lock_cb_t query_facility_lock; + ofono_sim_open_channel_cb_t open_channel; + ofono_sim_close_channel_cb_t close_channel; + BinderCallback ptr; + } cb; + gpointer data; + gpointer req_id; /* Actually RadioRequest pointer (but not a ref) */ +} BinderSimCbdIo; + +typedef struct binder_sim_session_cbd { + BinderSim* self; + BinderSimCard* card; + ofono_sim_logical_access_cb_t cb; + gpointer data; + int ref_count; + int channel; + int cla; + gpointer req_id; /* Actually RadioRequest pointer (but not a ref) */ +} BinderSimSessionCbData; + +typedef struct binder_sim_pin_cbd { + BinderSim* self; + ofono_sim_lock_unlock_cb_t cb; + gpointer data; + BinderSimCard* card; + enum ofono_sim_password_type passwd_type; + RADIO_ERROR status; + guint state_event_count; + guint timeout_id; + gulong card_status_id; +} BinderSimPinCbData; + +typedef struct binder_sim_retry_query_cbd { + BinderSim* self; + ofono_sim_pin_retries_cb_t cb; + void* data; + guint query_index; +} BinderSimRetryQueryCbData; + +typedef struct binder_sim_retry_query { + const char* name; + enum ofono_sim_password_type passwd_type; + RADIO_REQ code; + RadioRequest* (*new_req)(BinderSim* self, RADIO_REQ code, + RadioRequestCompleteFunc complete, GDestroyNotify destroy, + void* user_data); +} BinderSimRetryQuery; + +/* TS 102.221 */ +#define APP_TEMPLATE_TAG 0x61 +#define APP_ID_TAG 0x4F + +typedef struct binder_sim_list_apps { + BinderSim* self; + ofono_sim_list_apps_cb_t cb; + void* data; +} BinderSimListApps; + +static +void +binder_sim_query_retry_count_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data); + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderSim* binder_sim_get_data(struct ofono_sim* sim) + { return ofono_sim_get_data(sim); } + +static +BinderSimCbdIo* +binder_sim_cbd_io_new( + BinderSim* self, + BinderCallback cb, + void* data) +{ + BinderSimCbdIo* cbd = g_slice_new0(BinderSimCbdIo); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + cbd->card = binder_sim_card_ref(self->card); + return cbd; +} + +static +void +binder_sim_cbd_io_free( + gpointer data) +{ + BinderSimCbdIo* cbd = data; + + binder_sim_card_sim_io_finished(cbd->card, cbd->req_id); + binder_sim_card_unref(cbd->card); + gutil_slice_free(cbd); +} + +static +gboolean +binder_sim_cbd_io_start( + BinderSimCbdIo* cbd, + RadioRequest* req) +{ + if (radio_request_submit(req)) { + binder_sim_card_sim_io_started(cbd->card, cbd->req_id = req); + return TRUE; + } + return FALSE; +} + +static +BinderSimSessionCbData* +binder_sim_session_cbd_new( + BinderSim* self, + int channel, + int cla, + ofono_sim_logical_access_cb_t cb, + void* data) +{ + BinderSimSessionCbData* cbd = g_slice_new0(BinderSimSessionCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + cbd->card = binder_sim_card_ref(self->card); + cbd->channel = channel; + cbd->cla = cla; + cbd->ref_count = 1; + return cbd; +} + +static +void +binder_sim_session_cbd_unref( + gpointer data) +{ + BinderSimSessionCbData* cbd = data; + + if (--(cbd->ref_count) < 1) { + binder_sim_card_sim_io_finished(cbd->card, cbd->req_id); + binder_sim_card_unref(cbd->card); + gutil_slice_free(cbd); + } +} + +static +gboolean +binder_sim_session_cbd_start( + BinderSimSessionCbData* cbd, + RadioRequest* req) +{ + const gpointer finished_req = cbd->req_id; + gboolean ok; + + cbd->ref_count++; + ok = radio_request_submit(req); + if (ok) { + binder_sim_card_sim_io_started(cbd->card, cbd->req_id = req); + } else { + cbd->req_id = 0; + } + binder_sim_card_sim_io_finished(cbd->card, finished_req); + radio_request_unref(req); + return ok; +} + +static +void +binder_sim_pin_cbd_state_event_count_cb( + BinderSimCard* sc, + void* user_data) +{ + BinderSimPinCbData* cbd = user_data; + + /* + * Cound the SIM status events received while request is pending + * so that binder_sim_pin_change_state_cb can decide whether to wait + * for the next event or not + */ + cbd->state_event_count++; +} + +static +BinderSimPinCbData* +binder_sim_pin_cbd_new( + BinderSim* self, + enum ofono_sim_password_type passwd_type, + gboolean state_change_expected, + ofono_sim_lock_unlock_cb_t cb, + void* data) +{ + BinderSimPinCbData* cbd = g_slice_new0(BinderSimPinCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + cbd->passwd_type = passwd_type; + cbd->card = binder_sim_card_ref(self->card); + if (state_change_expected) { + cbd->card_status_id = + binder_sim_card_add_status_received_handler(cbd->card, + binder_sim_pin_cbd_state_event_count_cb, cbd); + } + return cbd; +} + +static +void +binder_sim_pin_cbd_free( + BinderSimPinCbData* cbd) +{ + if (cbd->timeout_id) { + g_source_remove(cbd->timeout_id); + } + + binder_sim_card_remove_handler(cbd->card, cbd->card_status_id); + binder_sim_card_unref(cbd->card); + gutil_slice_free(cbd); +} + +static +void +binder_sim_pin_req_done( + gpointer ptr) +{ + BinderSimPinCbData* cbd = ptr; + + /* Only free if callback isn't waiting for something else to happen */ + if (!cbd->timeout_id) { + GASSERT(!cbd->card_status_id); + binder_sim_pin_cbd_free(cbd); + } +} + +static +const char* +binder_sim_append_path( + BinderSim* self, + GBinderWriter* writer, + int fileid, + const guchar* path, + guint path_len) +{ + const RADIO_APP_TYPE app_type = binder_sim_card_app_type(self->card); + guchar db_path[OFONO_EF_PATH_BUFFER_SIZE] = { 0x00 }; + char* hex_path = NULL; + int len; + + if (path_len > 0 && path_len < 7) { + memcpy(db_path, path, path_len); + len = path_len; + } else if (app_type == RADIO_APP_TYPE_USIM) { + len = ofono_get_ef_path_3g(fileid, db_path); + } else if (app_type == RADIO_APP_TYPE_SIM) { + len = ofono_get_ef_path_2g(fileid, db_path); + } else { + ofono_error("Unsupported app type %d", app_type); + len = 0; + } + + if (len > 0) { + hex_path = binder_encode_hex(db_path, len); + gbinder_writer_add_cleanup(writer, g_free, hex_path); + DBG_(self, "%s", hex_path); + return hex_path; + } else { + /* + * Catch-all for EF_ICCID, EF_PL and other files absent + * from ef_db table in src/simutil.c, hard-code ROOTMF. + */ + DBG_(self, "%s (default)", ROOTMF); + return ROOTMF; + } +} + +static +BinderSimIoResponse* +binder_sim_io_response_new( + const GBinderReader* args) +{ + const RadioIccIoResult* result; + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + result = gbinder_reader_read_hidl_struct(&reader, RadioIccIoResult); + if (result) { + BinderSimIoResponse* resp = g_slice_new0(BinderSimIoResponse); + const char* hex = result->response.data.str; + + DBG("sw1=0x%02X,sw2=0x%02X,%s", result->sw1, result->sw2, hex); + resp->sw1 = result->sw1; + resp->sw2 = result->sw2; + resp->data = binder_decode_hex(hex, -1, &resp->data_len); + return resp; + } + return NULL; +} + +static +void +binder_sim_io_response_free( + BinderSimIoResponse* res) +{ + if (res) { + g_free(res->data); + g_slice_free(BinderSimIoResponse, res); + } +} + +static +gboolean +binder_sim_io_response_ok( + const BinderSimIoResponse* res) +{ + if (res) { + static const struct binder_sim_io_error { + gint32 sw; + const char* msg; + } errmsg [] = { + /* TS 102.221 */ + { 0x6a80, "Incorrect parameters in the data field" }, + { 0x6a81, "Function not supported" }, + { 0x6a82, "File not found" }, + { 0x6a83, "Record not found" }, + { 0x6a84, "Not enough memory space" }, + { 0x6a86, "Incorrect parameters P1 to P2" }, + { 0x6a87, "Lc inconsistent with P1 to P2" }, + { 0x6a88, "Referenced data not found" }, + /* TS 51.011 */ + { 0x9240, "Memory problem" }, + { 0x9400, "No EF selected" }, + { 0x9402, "Out of range (invalid address)" }, + { 0x9404, "File id/pattern not found" }, + { 0x9408, "File is inconsistent with the command" } + }; + + gint32 low, high, sw; + + switch (res->sw1) { + case 0x90: + /* '90 00' is the normal completion */ + if (res->sw2 != 0x00) { + break; + } + /* fall through */ + case 0x91: + case 0x9e: + case 0x9f: + return TRUE; + case 0x92: + if (res->sw2 != 0x40) { + /* '92 40' is "memory problem" */ + return TRUE; + } + break; + default: + break; + } + + /* Find the error message */ + low = 0; + high = G_N_ELEMENTS(errmsg)-1; + sw = (res->sw1 << 8) | res->sw2; + + while (low <= high) { + const int mid = (low + high)/2; + const int val = errmsg[mid].sw; + + if (val < sw) { + low = mid + 1; + } else if (val > sw) { + high = mid - 1; + } else { + /* Message found */ + DBG("error: %s", errmsg[mid].msg); + return FALSE; + } + } + + /* No message */ + DBG("error %02x %02x", res->sw1, res->sw2); + } + return FALSE; +} + +static +void +binder_sim_file_info_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + BinderSim* self = cbd->self; + ofono_sim_file_info_cb_t cb = cbd->cb.file_info; + struct ofono_error err; + + DBG_(self, ""); + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ICC_IO_FOR_APP) { + BinderSimIoResponse* res = binder_sim_io_response_new(args); + + if (!self->inserted) { + DBG_(self, "No SIM card"); + } else if (binder_sim_io_response_ok(res) && + error == RADIO_ERROR_NONE) { + gboolean ok = FALSE; + guchar faccess[3] = { 0x00, 0x00, 0x00 }; + guchar fstatus = EF_STATUS_VALID; + unsigned int flen = 0, rlen = 0, str = 0; + + if (res->data_len) { + if (res->data[0] == 0x62) { + ok = ofono_parse_get_response_3g(res->data, + res->data_len, &flen, &rlen, &str, faccess, + NULL); + } else { + ok = ofono_parse_get_response_2g(res->data, + res->data_len, &flen, &rlen, &str, faccess, + &fstatus); + } + } + + if (ok) { + /* Success */ + cb(binder_error_ok(&err), flen, str, rlen, faccess, + fstatus, cbd->data); + binder_sim_io_response_free(res); + return; + } else { + ofono_error("file info parse error"); + } + } else if (res) { + binder_error_init_sim_error(&err, res->sw1, res->sw2); + } else if (error != RADIO_ERROR_NONE) { + ofono_error("SIM I/O error: %s", + binder_radio_error_string(error)); + } else { + ofono_error("Failed to parse iccIOForApp response"); + } + binder_sim_io_response_free(res); + } else { + ofono_error("Unexpected iccIOForApp response %d", resp); + } + } + /* Error path */ + cb(&err, -1, -1, -1, NULL, EF_STATUS_INVALIDATED, cbd->data); +} + +static +gboolean +binder_sim_request_io( + BinderSim* self, + guint cmd, + int fid, + guint p1, + guint p2, + guint p3, + const char* hex_data, + const guchar* path, + guint path_len, + RadioRequestCompleteFunc complete, + BinderCallback cb, + void* data) +{ + static const char empty[] = ""; + const char* aid = binder_sim_card_app_aid(self->card); + BinderSimCbdIo* cbd = binder_sim_cbd_io_new(self, cb, data); + guint parent; + gboolean ok; + + /* iccIOForApp(int32 serial, IccIo iccIo); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_ICC_IO_FOR_APP, + &writer, complete, binder_sim_cbd_io_free, cbd); + RadioIccIo* io = gbinder_writer_new0(&writer, RadioIccIo); + + DBG_(self, "cmd=0x%.2X,fid=0x%.4X,%d,%d,%d,%s,pin2=(null),aid=%s", + cmd, fid, p1, p2, p3, hex_data, aid); + + io->command = cmd; + io->fileId = fid; + io->path.data.str = binder_sim_append_path(self, &writer, fid, path, + path_len); + io->path.len = strlen(io->path.data.str); + io->p1 = p1; + io->p2 = p2; + io->p3 = p3; + binder_copy_hidl_string(&writer, &io->data, hex_data); + io->pin2.data.str = empty; + binder_copy_hidl_string(&writer, &io->aid, aid); + + /* Write the parent structure */ + parent = gbinder_writer_append_buffer_object(&writer, io, sizeof(*io)); + + /* Write the string data in the right order */ + binder_append_hidl_string_data(&writer, io, path, parent); + binder_append_hidl_string_data(&writer, io, data, parent); + binder_append_hidl_string_data(&writer, io, pin2, parent); + binder_append_hidl_string_data(&writer, io, aid, parent); + + radio_request_set_blocking(req, TRUE); + radio_request_set_timeout(req, SIM_IO_TIMEOUT_SECS * 1000); + ok = binder_sim_cbd_io_start(cbd, req); + radio_request_unref(req); + return ok; +} + +static +void +binder_sim_ofono_read_file_info( + struct ofono_sim* sim, + int fileid, + const unsigned char* path, + unsigned int len, + ofono_sim_file_info_cb_t cb, + void* data) +{ + if (!binder_sim_request_io(binder_sim_get_data(sim), CMD_GET_RESPONSE, + fileid, 0, 0, 15, NULL, path, len, binder_sim_file_info_cb, + BINDER_CB(cb), data)) { + struct ofono_error err; + + cb(binder_error_failure(&err), -1, -1, -1, NULL, + EF_STATUS_INVALIDATED, data); + } +} + +static +void +binder_sim_read_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + BinderSim* self = cbd->self; + ofono_sim_read_cb_t cb = cbd->cb.read; + struct ofono_error err; + + DBG_(self, ""); + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ICC_IO_FOR_APP) { + BinderSimIoResponse* res = binder_sim_io_response_new(args); + + if (!self->inserted) { + DBG_(self, "No SIM card"); + } else if (binder_sim_io_response_ok(res) && + error == RADIO_ERROR_NONE) { + /* Success */ + cb(binder_error_ok(&err), res->data, res->data_len, cbd->data); + binder_sim_io_response_free(res); + return; + } else if (res) { + binder_error_init_sim_error(&err, res->sw1, res->sw2); + } else if (error != RADIO_ERROR_NONE) { + ofono_error("SIM read error: %s", + binder_radio_error_string(error)); + } else { + ofono_error("Failed to parse iccIOForApp response"); + } + binder_sim_io_response_free(res); + } + } + /* Error */ + cb(&err, NULL, 0, cbd->data); +} + +static +void +binder_sim_read( + struct ofono_sim* sim, + guint cmd, + int fileid, + guint p1, + guint p2, + guint p3, + const guchar* path, + guint path_len, + ofono_sim_read_cb_t cb, + void* data) +{ + if (!binder_sim_request_io(binder_sim_get_data(sim), cmd, fileid, p1, p2, + p3, NULL, path, path_len, binder_sim_read_cb, BINDER_CB(cb), data)) { + struct ofono_error err; + + cb(binder_error_failure(&err), NULL, 0, data); + } +} + +static +void +binder_sim_ofono_read_file_transparent( + struct ofono_sim* sim, + int fileid, + int start, + int length, + const unsigned char* path, + unsigned int path_len, + ofono_sim_read_cb_t cb, + void* data) +{ + binder_sim_read(sim, CMD_READ_BINARY, fileid, (start >> 8), (start & 0xff), + length, path, path_len, cb, data); +} + +static +void +binder_sim_ofono_read_file_linear( + struct ofono_sim* sim, + int fileid, + int record, + int length, + const unsigned char* path, + unsigned int path_len, + ofono_sim_read_cb_t cb, + void *data) +{ + binder_sim_read(sim, CMD_READ_RECORD, fileid, record, MODE_ABSOLUTE, + length, path, path_len, cb, data); +} + +static +void +binder_sim_ofono_read_file_cyclic( + struct ofono_sim* sim, + int fileid, + int record, + int length, + const unsigned char* path, + unsigned int path_len, + ofono_sim_read_cb_t cb, + void* data) +{ + binder_sim_read(sim, CMD_READ_RECORD, fileid, record, MODE_ABSOLUTE, + length, path, path_len, cb, data); +} + +static +void binder_sim_write_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + BinderSim* self = cbd->self; + ofono_sim_write_cb_t cb = cbd->cb.write; + struct ofono_error err; + + DBG_(self, ""); + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ICC_IO_FOR_APP) { + BinderSimIoResponse* res = binder_sim_io_response_new(args); + + if (!self->inserted) { + DBG_(self, "No SIM card"); + } else if (binder_sim_io_response_ok(res) && + error == RADIO_ERROR_NONE) { + /* Success */ + cb(binder_error_ok(&err), cbd->data); + return; + } else if (res) { + binder_error_init_sim_error(&err, res->sw1, res->sw2); + } else if (error != RADIO_ERROR_NONE) { + ofono_error("SIM write error: %s", + binder_radio_error_string(error)); + } else { + ofono_error("Failed to parse iccIOForApp response"); + } + binder_sim_io_response_free(res); + } + } + /* Error */ + cb(&err, cbd->data); +} + +static +void +binder_sim_write( + struct ofono_sim* sim, + guint cmd, + int fileid, + guint p1, + guint p2, + guint length, + const void* value, + const guchar* path, + guint path_len, + ofono_sim_write_cb_t cb, + void* data) +{ + char* hex_data = binder_encode_hex(value, length); + + if (!binder_sim_request_io(binder_sim_get_data(sim), cmd, fileid, p1, p2, + length, hex_data, path, path_len, binder_sim_write_cb, + BINDER_CB(cb), data)) { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } + g_free(hex_data); +} + +static +void +binder_sim_write_file_transparent( + struct ofono_sim* sim, + int fileid, + int start, + int length, + const unsigned char* value, + const unsigned char* path, + unsigned int path_len, + ofono_sim_write_cb_t cb, + void* data) +{ + binder_sim_write(sim, CMD_UPDATE_BINARY, fileid, (start >> 8), + (start & 0xff), length, value, path, path_len, cb, data); +} + +static +void +binder_sim_write_file_linear( + struct ofono_sim* sim, + int fileid, + int record, + int length, + const unsigned char* value, + const unsigned char* path, + unsigned int path_len, + ofono_sim_write_cb_t cb, + void* data) +{ + binder_sim_write(sim, CMD_UPDATE_RECORD, fileid, record, MODE_ABSOLUTE, + length, value, path, path_len, cb, data); +} + +static +void +binder_sim_write_file_cyclic( + struct ofono_sim* sim, + int fileid, + int length, + const unsigned char* value, + const unsigned char* path, + unsigned int path_len, + ofono_sim_write_cb_t cb, + void* data) +{ + binder_sim_write(sim, CMD_UPDATE_RECORD, fileid, 0, MODE_PREVIOUS, + length, value, path, path_len, cb, data); +} + +static +void +binder_sim_get_imsi_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + ofono_sim_imsi_cb_t cb = cbd->cb.imsi; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + /* getIMSIForAppResponse(RadioResponseInfo, string imsi); */ + if (resp == RADIO_RESP_GET_IMSI_FOR_APP) { + if (error == RADIO_ERROR_NONE) { + const char* imsi = binder_read_hidl_string(args); + + DBG_(cbd->self, "%s", imsi); + if (imsi) { + /* Success */ + GASSERT(strlen(imsi) == 15); + cb(binder_error_ok(&err), imsi, cbd->data); + return; + } + } else { + ofono_warn("Failed to query IMSI, error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getIMSIForApp response %d", resp); + } + } + + /* Error */ + cb(binder_error_failure(&err), NULL, cbd->data); +} + +static +void +binder_sim_read_imsi( + struct ofono_sim* sim, + ofono_sim_imsi_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + BinderSimCbdIo* cbd = binder_sim_cbd_io_new(self, BINDER_CB(cb), data); + const char* aid = binder_sim_card_app_aid(self->card); + gboolean ok; + + /* getImsiForApp(int32 serial, string aid); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_IMSI_FOR_APP, &writer, + binder_sim_get_imsi_cb, binder_sim_cbd_io_free, cbd); + + DBG_(self, "%s", aid); + binder_append_hidl_string(&writer, aid); + + /* + * If we fail the .read_imsi call, ofono gets into "Unable to + * read IMSI, emergency calls only" state. Retry the request + * on failure. + */ + radio_request_set_retry(req, BINDER_RETRY_MS, -1); + radio_request_set_blocking(req, TRUE); + ok = binder_sim_cbd_io_start(cbd, req); + radio_request_unref(req); + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), NULL, cbd->data); + } +} + +static +enum ofono_sim_password_type +binder_sim_passwd_state( + BinderSim* self) +{ + const BinderSimCardApp *app = self->card->app; + + if (app) { + switch (app->app_state) { + case RADIO_APP_STATE_PIN: + return OFONO_SIM_PASSWORD_SIM_PIN; + case RADIO_APP_STATE_PUK: + return OFONO_SIM_PASSWORD_SIM_PUK; + case RADIO_APP_STATE_READY: + return OFONO_SIM_PASSWORD_NONE; + case RADIO_APP_STATE_SUBSCRIPTION_PERSO: + switch (app->perso_substate) { + case RADIO_PERSO_SUBSTATE_READY: + return OFONO_SIM_PASSWORD_NONE; + case RADIO_PERSO_SUBSTATE_SIM_NETWORK: + return OFONO_SIM_PASSWORD_PHNET_PIN; + case RADIO_PERSO_SUBSTATE_SIM_NETWORK_SUBSET: + return OFONO_SIM_PASSWORD_PHNETSUB_PIN; + case RADIO_PERSO_SUBSTATE_SIM_CORPORATE: + return OFONO_SIM_PASSWORD_PHCORP_PIN; + case RADIO_PERSO_SUBSTATE_SIM_SERVICE_PROVIDER: + return OFONO_SIM_PASSWORD_PHSP_PIN; + case RADIO_PERSO_SUBSTATE_SIM_SIM: + return OFONO_SIM_PASSWORD_PHSIM_PIN; + case RADIO_PERSO_SUBSTATE_SIM_NETWORK_PUK: + return OFONO_SIM_PASSWORD_PHNET_PUK; + case RADIO_PERSO_SUBSTATE_SIM_NETWORK_SUBSET_PUK: + return OFONO_SIM_PASSWORD_PHNETSUB_PUK; + case RADIO_PERSO_SUBSTATE_SIM_CORPORATE_PUK: + return OFONO_SIM_PASSWORD_PHCORP_PUK; + case RADIO_PERSO_SUBSTATE_SIM_SERVICE_PROVIDER_PUK: + return OFONO_SIM_PASSWORD_PHSP_PUK; + case RADIO_PERSO_SUBSTATE_SIM_SIM_PUK: + return OFONO_SIM_PASSWORD_PHFSIM_PUK; + default: + break; + } + default: + break; + } + } + return OFONO_SIM_PASSWORD_INVALID; +} + +static +gboolean +binder_sim_app_in_transient_state( + BinderSim* self) +{ + const BinderSimCardApp* app = self->card->app; + + if (app) { + switch (app->app_state) { + case RADIO_APP_STATE_DETECTED: + return TRUE; + case RADIO_APP_STATE_SUBSCRIPTION_PERSO: + switch (app->perso_substate) { + case RADIO_PERSO_SUBSTATE_UNKNOWN: + case RADIO_PERSO_SUBSTATE_IN_PROGRESS: + return TRUE; + default: + break; + } + default: + break; + } + } + return FALSE; +} + +static +void +binder_sim_finish_passwd_state_query( + BinderSim* self, + enum ofono_sim_password_type state) +{ + if (self->query_passwd_state_timeout_id) { + g_source_remove(self->query_passwd_state_timeout_id); + self->query_passwd_state_timeout_id = 0; + } + + if (self->query_passwd_state_sim_status_refresh_id) { + binder_sim_card_remove_handler(self->card, + self->query_passwd_state_sim_status_refresh_id); + self->query_passwd_state_sim_status_refresh_id = 0; + } + + if (self->query_passwd_state_cb) { + ofono_sim_passwd_cb_t cb = self->query_passwd_state_cb; + void* data = self->query_passwd_state_cb_data; + struct ofono_error err; + + self->query_passwd_state_cb = NULL; + self->query_passwd_state_cb_data = NULL; + self->ofono_passwd_state = state; + + if (state == OFONO_SIM_PASSWORD_INVALID) { + cb(binder_error_failure(&err), state, data); + } else { + cb(binder_error_ok(&err), state, data); + } + } +} + +static +void +binder_sim_check_perm_lock( + BinderSim* self) +{ + BinderSimCard* sim = self->card; + + /* + * Zero number of retries in the PUK state indicates to the ofono + * client that the card is permanently locked. This is different + * from the case when the number of retries is negative (which + * means that PUK is required but the number of remaining attempts + * is not available). + */ + if (sim->app && + sim->app->app_state == RADIO_APP_STATE_PUK && + sim->app->pin1_state == RADIO_PIN_STATE_ENABLED_PERM_BLOCKED) { + + /* + * It makes no sense to return non-zero number of remaining + * attempts in PERM_LOCKED state. So when we get here, the + * number of retries has to be negative (unknown) or zero. + * Otherwise, something must be broken. + */ + GASSERT(self->retries[OFONO_SIM_PASSWORD_SIM_PUK] <= 0); + if (self->retries[OFONO_SIM_PASSWORD_SIM_PUK] < 0) { + self->retries[OFONO_SIM_PASSWORD_SIM_PUK] = 0; + DBG_(self, "SIM card is locked"); + } + } +} + +static +void +binder_sim_invalidate_passwd_state( + BinderSim* self) +{ + guint i; + + self->ofono_passwd_state = OFONO_SIM_PASSWORD_INVALID; + for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) { + self->retries[i] = -1; + } + + binder_sim_check_perm_lock(self); + binder_sim_finish_passwd_state_query(self, OFONO_SIM_PASSWORD_INVALID); +} + +static +void +binder_sim_app_changed_cb( + BinderSimCard* sim, + void *user_data) +{ + binder_sim_check_perm_lock((BinderSim*)user_data); +} + +static +void +binder_sim_status_changed_cb( + BinderSimCard* sim, + void* user_data) +{ + BinderSim* self = user_data; + + GASSERT(self->card == sim); + if (sim->status && sim->status->card_state == RADIO_CARD_STATE_PRESENT) { + if (sim->app) { + enum ofono_sim_password_type ps; + + binder_sim_check_perm_lock(self); + if (!self->inserted) { + self->inserted = TRUE; + ofono_info("SIM card OK"); + ofono_sim_inserted_notify(self->sim, TRUE); + } + + ps = binder_sim_passwd_state(self); + if (ps != OFONO_SIM_PASSWORD_INVALID) { + binder_sim_finish_passwd_state_query(self, ps); + } + } else { + binder_sim_invalidate_passwd_state(self); + } + } else { + binder_sim_invalidate_passwd_state(self); + if (self->inserted) { + self->inserted = FALSE; + ofono_info("No SIM card"); + ofono_sim_inserted_notify(self->sim, FALSE); + } + } +} + +static +void +binder_sim_state_changed_cb( + struct ofono_watch* watch, + void* data) +{ + BinderSim* self = data; + const enum ofono_sim_state state = ofono_sim_get_state(watch->sim); + + DBG_(self, "%d %d", state, self->inserted); + if (state == OFONO_SIM_STATE_RESETTING && self->inserted) { + /* That will simulate SIM card removal: */ + binder_sim_card_reset(self->card); + } +} + +static +RadioRequest* +binder_sim_enter_sim_pin_req( + BinderSim* self, + RADIO_REQ code, + const char* pin, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + /* + * supplyIccPinForApp(int32 serial, string pin, string aid); + * supplyIccPin2ForApp(int32_t serial, string pin2, string aid); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, code, &writer, + complete, destroy, user_data); + + binder_append_hidl_string(&writer, pin); + binder_append_hidl_string(&writer, + binder_sim_card_app_aid(self->card)); + + radio_request_set_blocking(req, TRUE); + return req; +} + +static +RadioRequest* +binder_sim_enter_sim_puk_req( + BinderSim* self, + RADIO_REQ code, + const char* puk, + const char* pin, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + /* + * supplyIccPukForApp(int32 serial, string puk, string pin, string aid); + * supplyIccPuk2ForApp(int32 serial, string puk2, string pin2, string aid); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, code, &writer, + complete, destroy, user_data); + + binder_append_hidl_string(&writer, puk); + binder_append_hidl_string(&writer, pin); + binder_append_hidl_string(&writer, + binder_sim_card_app_aid(self->card)); + + radio_request_set_blocking(req, TRUE); + return req; +} + +/* + * Some IRadio implementations allow to query the retry count + * by sending the empty pin in any state. + */ + +static +RadioRequest* +binder_sim_empty_sim_pin_req( + BinderSim* self, + RADIO_REQ code, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + return binder_sim_enter_sim_pin_req(self, code, "", + complete, destroy, user_data); +} + +static +RadioRequest* +binder_sim_empty_sim_puk_req( + BinderSim* self, + RADIO_REQ code, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + return binder_sim_enter_sim_puk_req(self, code, "", "", + complete, destroy, user_data); +} + +static const BinderSimRetryQuery binder_sim_retry_query_types[] = { + { + "pin", + OFONO_SIM_PASSWORD_SIM_PIN, + RADIO_REQ_SUPPLY_ICC_PIN_FOR_APP, + binder_sim_empty_sim_pin_req + },{ + "pin2", + OFONO_SIM_PASSWORD_SIM_PIN2, + RADIO_REQ_SUPPLY_ICC_PIN2_FOR_APP, + binder_sim_empty_sim_pin_req + },{ + "puk", + OFONO_SIM_PASSWORD_SIM_PUK, + RADIO_REQ_SUPPLY_ICC_PUK_FOR_APP, + binder_sim_empty_sim_puk_req + },{ + "puk2", + OFONO_SIM_PASSWORD_SIM_PUK2, + RADIO_REQ_SUPPLY_ICC_PUK2_FOR_APP, + binder_sim_empty_sim_puk_req + } +}; + +static +BinderSimRetryQueryCbData* +binder_sim_retry_query_cbd_new( + BinderSim* self, + guint query_index, + ofono_sim_pin_retries_cb_t cb, + void* data) +{ + BinderSimRetryQueryCbData* cbd = g_slice_new(BinderSimRetryQueryCbData); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + cbd->query_index = query_index; + return cbd; +} + +static +void +binder_sim_retry_query_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderSimRetryQueryCbData, cbd); +} + +static +RadioRequest* +binder_sim_query_retry_count( + BinderSim* self, + guint start_index, + ofono_sim_pin_retries_cb_t cb, + void* data) +{ + if (self->empty_pin_query_allowed) { + guint i = start_index; + + /* Find the first unknown retry count that we can query. */ + while (i < G_N_ELEMENTS(binder_sim_retry_query_types)) { + const BinderSimRetryQuery* query = + binder_sim_retry_query_types + i; + + if (self->retries[query->passwd_type] < 0) { + RadioRequest* req = query->new_req(self, query->code, + binder_sim_query_retry_count_cb, + binder_sim_retry_query_cbd_free, + binder_sim_retry_query_cbd_new(self, i, cb, data)); + + DBG_(self, "querying %s retry count...", query->name); + if (radio_request_submit(req)) { + return req; + } else { + radio_request_unref(req); + } + break; + } + i++; + } + } + return NULL; +} + +static +void +binder_sim_query_retry_count_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimRetryQueryCbData* cbd = user_data; + BinderSim* self = cbd->self; + struct ofono_error err; + + GASSERT(self->query_pin_retries_req); + radio_request_unref(self->query_pin_retries_req); + self->query_pin_retries_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + gint32 retry_count; + + if (binder_read_int32(args, &retry_count)) { + const BinderSimRetryQuery* query = + binder_sim_retry_query_types + cbd->query_index; + + DBG_(self, "%s retry count=%d", query->name, retry_count); + self->retries[query->passwd_type] = retry_count; + + /* Submit the next request */ + if ((self->query_pin_retries_req = + binder_sim_query_retry_count(self, cbd->query_index + 1, + cbd->cb, cbd->data)) != NULL) { + /* The next request is pending */ + return; + } + } else { + ofono_error("pin retry query error %s", + binder_radio_error_string(error)); + self->empty_pin_query_allowed = FALSE; + } + } + } + + cbd->cb(binder_error_ok(&err), self->retries, cbd->data); +} + +static +void +binder_sim_query_pin_retries( + struct ofono_sim* sim, + ofono_sim_pin_retries_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + + DBG_(self, ""); + radio_request_drop(self->query_pin_retries_req); + if (!(self->query_pin_retries_req = + binder_sim_query_retry_count(self, 0, cb, data))) { + struct ofono_error err; + + /* Nothing to wait for */ + cb(binder_error_ok(&err), self->retries, data); + } +} + +static +void +binder_sim_query_passwd_state_complete_cb( + BinderSimCard* sim, + void* user_data) +{ + BinderSim* self = user_data; + + GASSERT(self->query_passwd_state_sim_status_refresh_id); + binder_sim_finish_passwd_state_query(self, binder_sim_passwd_state(self)); +} + +static +gboolean +binder_sim_query_passwd_state_timeout_cb( + gpointer user_data) +{ + BinderSim* self = user_data; + + GASSERT(self->query_passwd_state_cb); + self->query_passwd_state_timeout_id = 0; + binder_sim_finish_passwd_state_query(self, OFONO_SIM_PASSWORD_INVALID); + + return G_SOURCE_REMOVE; +} + +static +void +binder_sim_query_passwd_state( + struct ofono_sim* sim, + ofono_sim_passwd_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + + if (self->query_passwd_state_timeout_id) { + g_source_remove(self->query_passwd_state_timeout_id); + self->query_passwd_state_timeout_id = 0; + } + + if (!self->query_passwd_state_sim_status_refresh_id) { + binder_sim_card_remove_handler(self->card, + self->query_passwd_state_sim_status_refresh_id); + self->query_passwd_state_sim_status_refresh_id = 0; + } + + /* Always request fresh status, just in case. */ + binder_sim_card_request_status(self->card); + self->query_passwd_state_cb = cb; + self->query_passwd_state_cb_data = data; + + if (binder_sim_passwd_state(self) != OFONO_SIM_PASSWORD_INVALID) { + /* Just wait for GET_SIM_STATUS completion */ + DBG_(self, "waiting for SIM status query to complete"); + self->query_passwd_state_sim_status_refresh_id = + binder_sim_card_add_status_received_handler(self->card, + binder_sim_query_passwd_state_complete_cb, self); + } else { + /* Wait for the state to change */ + DBG_(self, "waiting for the SIM state to change"); + } + + /* + * We still need to complete the request somehow, even if + * GET_STATUS never completes or SIM status never changes. + */ + self->query_passwd_state_timeout_id = + g_timeout_add_seconds(SIM_STATE_CHANGE_TIMEOUT_SECS, + binder_sim_query_passwd_state_timeout_cb, self); +} + +static +gboolean +binder_sim_pin_change_state_timeout_cb( + gpointer user_data) +{ + BinderSimPinCbData* cbd = user_data; + BinderSim* self = cbd->self; + struct ofono_error err; + + DBG_(self, "oops..."); + cbd->timeout_id = 0; + self->pin_cbd_list = g_list_remove(self->pin_cbd_list, cbd); + cbd->cb(binder_error_failure(&err), cbd->data); + binder_sim_pin_cbd_free(cbd); + + return G_SOURCE_REMOVE; +} + +static +void +binder_sim_pin_change_state_status_cb( + BinderSimCard* sim, + void* user_data) +{ + BinderSimPinCbData* cbd = user_data; + BinderSim* self = cbd->self; + + if (!binder_sim_app_in_transient_state(self)) { + enum ofono_sim_password_type ps = binder_sim_passwd_state(self); + struct ofono_error err; + + if (ps == OFONO_SIM_PASSWORD_INVALID || + cbd->status != RADIO_ERROR_NONE) { + DBG_(self, "failure"); + cbd->cb(binder_error_failure(&err), cbd->data); + } else { + DBG_(self, "success, passwd_state=%d", ps); + cbd->cb(binder_error_ok(&err), cbd->data); + } + + ofono_sim_initialized_notify(self->sim); + self->pin_cbd_list = g_list_remove(self->pin_cbd_list, cbd); + binder_sim_pin_cbd_free(cbd); + } else { + DBG_(self, "will keep waiting"); + } +} + +static +void +binder_sim_pin_change_state_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimPinCbData* cbd = user_data; + BinderSim* self = cbd->self; + enum ofono_sim_password_type type = cbd->passwd_type; + gint32 retry_count = 0; + + if (status == RADIO_TX_STATUS_OK) { + if (!binder_read_int32(args, &retry_count)) { + ofono_error("Failed to parse PIN/PUK response %d", resp); + error = RADIO_ERROR_GENERIC_FAILURE; + } + } else { + error = RADIO_ERROR_GENERIC_FAILURE; + } + + DBG_(self, "result=%d type=%d retry_count=%d", error, type, retry_count); + if (error == RADIO_ERROR_NONE && retry_count == 0) { + enum ofono_sim_password_type pin_type = ofono_sim_puk2pin(type); + /* + * If PIN/PUK request has succeeded, zero retry count + * makes no sense, we have to assume that it's unknown. + * If it can be queried, it will be queried later. If + * it can't be queried it will remain unknown. + */ + self->retries[type] = -1; + if (pin_type != OFONO_SIM_PASSWORD_INVALID) { + /* Successful PUK requests affect PIN retry count */ + self->retries[pin_type] = -1; + } + } else { + self->retries[type] = retry_count; + } + + binder_sim_check_perm_lock(self); + cbd->status = error; + + /* + * RADIO_ERROR_PASSWORD_INCORRECT is the final result, + * no need to wait. + */ + if (error != RADIO_ERROR_PASSWORD_INCORRECT && cbd->card_status_id && + (!cbd->state_event_count || binder_sim_app_in_transient_state(self))) { + GASSERT(!g_list_find(self->pin_cbd_list, cbd)); + GASSERT(!cbd->timeout_id); + + /* Wait for the SIM to change state */ + DBG_(self, "waiting for SIM state change"); + self->pin_cbd_list = g_list_append(self->pin_cbd_list, cbd); + cbd->timeout_id = g_timeout_add_seconds(SIM_STATE_CHANGE_TIMEOUT_SECS, + binder_sim_pin_change_state_timeout_cb, cbd); + + /* + * We no longer need to maintain state_event_count, + * replace the SIM state event handler + */ + binder_sim_card_remove_handler(cbd->card, cbd->card_status_id); + cbd->card_status_id = + binder_sim_card_add_status_received_handler(self->card, + binder_sim_pin_change_state_status_cb, cbd); + } else { + struct ofono_error err; + + /* It's either already changed or not expected at all */ + if (error == RADIO_ERROR_NONE) { + cbd->cb(binder_error_ok(&err), cbd->data); + } else { + cbd->cb(binder_error_failure(&err), cbd->data); + } + + /* To avoid assert in binder_sim_pin_req_done: */ + if (cbd->card_status_id) { + binder_sim_card_remove_handler(cbd->card, cbd->card_status_id); + cbd->card_status_id = 0; + } + + /* Tell the core that we are ready to accept more requests */ + ofono_sim_initialized_notify(self->sim); + } +} + +static +void +binder_sim_pin_send( + struct ofono_sim* sim, + const char* passwd, + ofono_sim_lock_unlock_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + RadioRequest* req = binder_sim_enter_sim_pin_req(self, + RADIO_REQ_SUPPLY_ICC_PIN_FOR_APP, passwd, + binder_sim_pin_change_state_cb, binder_sim_pin_req_done, + binder_sim_pin_cbd_new(self, OFONO_SIM_PASSWORD_SIM_PIN, TRUE, + cb, data)); + + if (radio_request_submit(req)) { + DBG_(self, "%s,aid=%s", passwd, binder_sim_card_app_aid(self->card)); + } else { + struct ofono_error err; + + DBG_(self, "sorry"); + cb(binder_error_failure(&err), data); + } + radio_request_unref(req); +} + +static +gboolean +binder_perso_change_state( + struct ofono_sim* sim, + enum ofono_sim_password_type passwd_type, + int enable, + const char* passwd, + ofono_sim_lock_unlock_cb_t cb, + void *data) +{ + BinderSim* self = binder_sim_get_data(sim); + RADIO_REQ code = RADIO_REQ_NONE; + gboolean ok = FALSE; + + switch (passwd_type) { + case OFONO_SIM_PASSWORD_PHNET_PIN: + if (!enable) { + code = RADIO_REQ_SUPPLY_NETWORK_DEPERSONALIZATION; + } else { + DBG_(self, "Not supported, enable=%d", enable); + } + break; + default: + DBG_(self, "Not supported, type=%d", passwd_type); + break; + } + + if (code) { + /* supplyNetworkDepersonalization(int32 serial, string netPin); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, code, &writer, + binder_sim_pin_change_state_cb, binder_sim_pin_req_done, + binder_sim_pin_cbd_new(self, passwd_type, FALSE, cb, data)); + + binder_append_hidl_string(&writer, passwd); + ok = radio_request_submit(req); + radio_request_unref(req); + } + + return ok; +} + +static +const char* +binder_sim_facility_code( + enum ofono_sim_password_type type) +{ + switch (type) { + case OFONO_SIM_PASSWORD_SIM_PIN: + return "SC"; + case OFONO_SIM_PASSWORD_SIM_PIN2: + return "P2"; + case OFONO_SIM_PASSWORD_PHSIM_PIN: + return "PS"; + case OFONO_SIM_PASSWORD_PHFSIM_PIN: + return "PF"; + case OFONO_SIM_PASSWORD_PHNET_PIN: + return "PN"; + case OFONO_SIM_PASSWORD_PHNETSUB_PIN: + return "PU"; + case OFONO_SIM_PASSWORD_PHSP_PIN: + return "PP"; + case OFONO_SIM_PASSWORD_PHCORP_PIN: + return "PC"; + default: + return NULL; + } +} + +static +void +binder_sim_pin_change_state( + struct ofono_sim* sim, + enum ofono_sim_password_type pwtype, + int enable, + const char* passwd, + ofono_sim_lock_unlock_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + const char* aid = binder_sim_card_app_aid(self->card); + const char* fac = binder_sim_facility_code(pwtype); + gboolean ok = FALSE; + + DBG_(self, "%d,%s,%d,%s,0,aid=%s", pwtype, fac, enable, passwd, aid); + + if (pwtype == OFONO_SIM_PASSWORD_PHNET_PIN) { + ok = binder_perso_change_state(sim, pwtype, enable, passwd, cb, data); + } else if (fac) { + /* + * setFacilityLockForApp(int32 serial, string facility, bool lockState, + * string password, int32 serviceClass, string appId); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_FACILITY_LOCK_FOR_APP, &writer, + binder_sim_pin_change_state_cb, binder_sim_pin_req_done, + binder_sim_pin_cbd_new(self, pwtype, FALSE, cb, data)); + + gbinder_writer_append_hidl_string(&writer, fac); /* facility */ + gbinder_writer_append_bool(&writer, enable); /* lockState */ + binder_append_hidl_string(&writer, passwd); /* password */ + gbinder_writer_append_int32(&writer, /* serviceClass */ + RADIO_SERVICE_CLASS_NONE); + binder_append_hidl_string(&writer, aid); /* appId */ + + radio_request_set_blocking(req, TRUE); + ok = radio_request_submit(req); + radio_request_unref(req); + } + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_sim_pin_send_puk( + struct ofono_sim* sim, + const char* puk, + const char* pin, + ofono_sim_lock_unlock_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + RadioRequest* req = binder_sim_enter_sim_puk_req(self, + RADIO_REQ_SUPPLY_ICC_PUK_FOR_APP, puk, pin, + binder_sim_pin_change_state_cb, binder_sim_pin_req_done, + binder_sim_pin_cbd_new(self, OFONO_SIM_PASSWORD_SIM_PUK, TRUE, + cb, data)); + + if (radio_request_submit(req)) { + DBG_(self, "puk=%s,pin=%s,aid=%s", puk, pin, + binder_sim_card_app_aid(self->card)); + } else { + struct ofono_error err; + + DBG_(self, "sorry"); + cb(binder_error_failure(&err), data); + } + radio_request_unref(req); +} + +static +void +binder_sim_change_passwd( + struct ofono_sim* sim, + enum ofono_sim_password_type passwd_type, + const char* old_passwd, + const char* new_passwd, + ofono_sim_lock_unlock_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + RADIO_REQ code = RADIO_REQ_NONE; + gboolean ok = FALSE; + + switch (passwd_type) { + case OFONO_SIM_PASSWORD_SIM_PIN: + code = RADIO_REQ_CHANGE_ICC_PIN_FOR_APP; + break; + case OFONO_SIM_PASSWORD_SIM_PIN2: + code = RADIO_REQ_CHANGE_ICC_PIN2_FOR_APP; + break; + default: + break; + } + + if (code) { + /* + * changeIccPinForApp(int32 serial, string oldPin, string newPin, + * string aid); + * changeIccPin2ForApp(int32 serial, string oldPin2, string newPin2, + * string aid); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, code, &writer, + binder_sim_pin_change_state_cb, binder_sim_pin_req_done, + binder_sim_pin_cbd_new(self, passwd_type, FALSE, cb, data)); + const char* aid = binder_sim_card_app_aid(self->card); + + DBG_(self, "old=%s,new=%s,aid=%s", old_passwd, new_passwd, aid); + binder_append_hidl_string(&writer, old_passwd); + binder_append_hidl_string(&writer, new_passwd); + binder_append_hidl_string(&writer, aid); + radio_request_set_blocking(req, TRUE); + ok = radio_request_submit(req); + radio_request_unref(req); + } + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_sim_query_facility_lock_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + ofono_query_facility_lock_cb_t cb = cbd->cb.query_facility_lock; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + /* getFacilityLockForAppResponse(RadioResponseInfo, int32 response); */ + if (resp == RADIO_RESP_GET_FACILITY_LOCK_FOR_APP) { + if (error == RADIO_ERROR_NONE) { + gint32 locked; + + if (binder_read_int32(args, &locked)) { + DBG_(cbd->self, "%d", locked); + cb(binder_error_ok(&err), locked != 0, cbd->data); + return; + } else { + ofono_error("Broken getFacilityLockForApp response?"); + } + } else { + ofono_error("Facility lock query error: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getFacilityLockForApp response %d", resp); + } + } + /* Error */ + cb(binder_error_failure(&err), FALSE, cbd->data); +} + +static +gboolean +binder_sim_query_facility_lock_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + return (status == RADIO_TX_STATUS_TIMEOUT); +} + +static +void +binder_sim_query_facility_lock( + struct ofono_sim* sim, + enum ofono_sim_password_type type, + ofono_query_facility_lock_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + const char* fac = binder_sim_facility_code(type); + BinderSimCbdIo* cbd = binder_sim_cbd_io_new(self, BINDER_CB(cb), data); + gboolean ok; + + /* + * getFacilityLockForApp(int32 serial, string facility, string password, + * int32 serviceClass, string appId); + */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_FACILITY_LOCK_FOR_APP, &writer, + binder_sim_query_facility_lock_cb, binder_sim_cbd_io_free, cbd); + + binder_append_hidl_string(&writer, fac); /* facility */ + binder_append_hidl_string(&writer, ""); /* password */ + gbinder_writer_append_int32(&writer, /* serviceClass */ + RADIO_SERVICE_CLASS_NONE); + binder_append_hidl_string(&writer, /* appId */ + binder_sim_card_app_aid(self->card)); + + /* Make sure that this request gets completed sooner or later */ + radio_request_set_timeout(req, FAC_LOCK_QUERY_TIMEOUT_SECS * 1000); + radio_request_set_retry(req, BINDER_RETRY_MS, FAC_LOCK_QUERY_RETRIES); + radio_request_set_retry_func(req, binder_sim_query_facility_lock_retry); + + DBG_(self, "%s", fac); + ok = binder_sim_cbd_io_start(cbd, req); + radio_request_unref(req); + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), FALSE, data); + } +} + +static +gboolean +binder_sim_list_apps_cb( + gpointer data) +{ + BinderSimListApps* cbd = data; + BinderSim* self = cbd->self; + const BinderSimCardStatus* status = self->card->status; + struct ofono_error err; + + GASSERT(self->list_apps_id); + self->list_apps_id = 0; + + if (status) { + int i, n = status->num_apps; + GByteArray* tlv = g_byte_array_sized_new(n * 20); + + /* Reconstruct EFdir contents */ + for (i = 0; i < n; i++) { + const char* hex = status->apps[i].aid; + gsize hex_len = hex ? strlen(hex) : 0; + guint8 aid[16]; + + if (hex_len >= 2 && hex_len <= 2 * sizeof(aid) && + gutil_hex2bin(hex, hex_len, aid)) { + const guint8 aid_size = (guint8)hex_len/2; + guint8 buf[4]; + + /* + * TS 102.221 + * 13 Application independent files + * 13.1 EFdir + * + * Application template TLV object. + */ + buf[0] = APP_TEMPLATE_TAG; + buf[1] = aid_size + 2; + buf[2] = APP_ID_TAG; + buf[3] = aid_size; + g_byte_array_append(tlv, buf, sizeof(buf)); + g_byte_array_append(tlv, aid, aid_size); + } + } + DBG_(self, "reporting %u apps %u bytes", n, tlv->len); + cbd->cb(binder_error_ok(&err), tlv->data, tlv->len, cbd->data); + g_byte_array_unref(tlv); + } else { + DBG_(self, "no SIM card, no apps"); + cbd->cb(binder_error_failure(&err), NULL, 0, cbd->data); + } + + return G_SOURCE_REMOVE; +} + +static +void +binder_sim_list_apps( + struct ofono_sim* sim, + ofono_sim_list_apps_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + BinderSimListApps* cbd = g_new(BinderSimListApps, 1); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + if (self->list_apps_id) { + g_source_remove(self->list_apps_id); + } + self->list_apps_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + binder_sim_list_apps_cb, cbd, g_free); +} + +static +void +binder_sim_open_channel_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + ofono_sim_open_channel_cb_t cb = cbd->cb.open_channel; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + /* + * iccOpenLogicalChannelResponse(RadioResponseInfo info, + * int32 channelId, vec selectResponse); + */ + if (resp == RADIO_RESP_ICC_OPEN_LOGICAL_CHANNEL) { + if (error == RADIO_ERROR_NONE) { + gint32 channel; + + /* Ignore selectResponse */ + if (binder_read_int32(args, &channel)) { + /* Success */ + DBG_(cbd->self, "%u", channel); + cb(binder_error_ok(&err), channel, cbd->data); + return; + } else { + ofono_error("Broken iccOpenLogicalChannel response?"); + } + } else { + ofono_error("Open logical channel failure: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected iccOpenLogicalChannel response %d", resp); + } + } + /* Error */ + cb(binder_error_failure(&err), 0, cbd->data); +} + +static +void +binder_sim_open_channel( + struct ofono_sim* sim, + const unsigned char* aid, + unsigned int len, + ofono_sim_open_channel_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + BinderSimCbdIo* cbd = binder_sim_cbd_io_new(self, BINDER_CB(cb), data); + gboolean ok; + + /* iccOpenLogicalChannel(int32 serial, string aid, int32 p2); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_ICC_OPEN_LOGICAL_CHANNEL, &writer, + binder_sim_open_channel_cb, binder_sim_cbd_io_free, cbd); + char *aid_hex = binder_encode_hex(aid, len); + + DBG_(self, "%s", aid_hex); + gbinder_writer_add_cleanup(&writer, g_free, aid_hex); + gbinder_writer_append_hidl_string(&writer, aid_hex); /* aid */ + gbinder_writer_append_int32(&writer, 0); /* p2 */ + radio_request_set_timeout(req, SIM_IO_TIMEOUT_SECS * 1000); + ok = binder_sim_cbd_io_start(cbd, req); + radio_request_unref(req); + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), 0, data); + } +} + +static +void +binder_sim_close_channel_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCbdIo* cbd = user_data; + struct ofono_error err; + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ICC_CLOSE_LOGICAL_CHANNEL) { + if (error == RADIO_ERROR_NONE) { + binder_error_init_ok(&err); + } else { + ofono_error("Close logical channel failure: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected iccCloseLogicalChannel response %d", resp); + } + } + cbd->cb.close_channel(&err, cbd->data); +} + +static +void +binder_sim_close_channel( + struct ofono_sim* sim, + int channel, + ofono_sim_close_channel_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + BinderSimCbdIo* cbd = binder_sim_cbd_io_new(self, BINDER_CB(cb), data); + gboolean ok; + + /* iccCloseLogicalChannel(int32 serial, int32 channelId); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_ICC_CLOSE_LOGICAL_CHANNEL, &writer, + binder_sim_close_channel_cb, binder_sim_cbd_io_free, cbd); + + DBG_(self, "%u", channel); + gbinder_writer_append_int32(&writer, channel); /* channelId */ + radio_request_set_timeout(req, SIM_IO_TIMEOUT_SECS * 1000); + ok = binder_sim_cbd_io_start(cbd, req); + radio_request_unref(req); + + if (!ok) { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_sim_logical_access_get_results_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimSessionCbData* cbd = user_data; + ofono_sim_logical_access_cb_t cb = cbd->cb; + struct ofono_error err; + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + /* + * iccTransmitApduLogicalChannelResponse(RadioResponseInfo, + * IccIoResult result); + */ + if (resp == RADIO_RESP_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL) { + BinderSimIoResponse* res = binder_sim_io_response_new(args); + + if (binder_sim_io_response_ok(res) && error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), res->data, res->data_len, cbd->data); + binder_sim_io_response_free(res); + return; + } else if (res) { + binder_error_init_sim_error(&err, res->sw1, res->sw2); + } + binder_sim_io_response_free(res); + } else { + ofono_error("Unexpected iccTransmitApduLogicalChannel response %d", + resp); + } + } + cb(&err, NULL, 0, cbd->data); +} + +static +gboolean +binder_sim_logical_access_transmit( + BinderSimSessionCbData* cbd, + int ins, + int p1, + int p2, + int p3, + const char* hex_data, + RadioRequestCompleteFunc cb) +{ + /* iccTransmitApduLogicalChannel(int32 serial, SimApdu message); */ + BinderSim* self = cbd->self; + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL, &writer, + cb, binder_sim_session_cbd_unref, cbd); + RadioSimApdu* apdu = gbinder_writer_new0(&writer, RadioSimApdu); + gboolean ok; + guint parent; + + DBG_(self, "session=%u,cmd=%02X,%02X,%02X,%02X,%02X,%s", cbd->channel, + cbd->cla, ins, p1, p2, p3, hex_data ? hex_data : ""); + + apdu->sessionId = cbd->channel; + apdu->cla = cbd->cla; + apdu->instruction = ins; + apdu->p1 = p1; + apdu->p2 = p2; + apdu->p3 = p3; + + binder_copy_hidl_string(&writer, &apdu->data, hex_data); + parent = gbinder_writer_append_buffer_object(&writer, apdu, sizeof(*apdu)); + binder_append_hidl_string_data(&writer, apdu, data, parent); + + radio_request_set_timeout(req, SIM_IO_TIMEOUT_SECS * 1000); + ok = binder_sim_session_cbd_start(cbd, req); + radio_request_unref(req); + return ok; +} + +static +void +binder_sim_logical_access_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimSessionCbData* cbd = user_data; + ofono_sim_logical_access_cb_t cb = cbd->cb; + struct ofono_error err; + + DBG_(cbd->self, ""); + cbd->req_id = 0; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL) { + BinderSimIoResponse* res = binder_sim_io_response_new(args); + + if (res && error == RADIO_ERROR_NONE) { + /* + * TS 102 221 + * 7.3.1.1.5.2 Case 4 commands + * + * If the UICC receives a case 4 command, after processing + * the data sent with the C-APDU, it shall return: + * + * a) procedure bytes '61 xx' instructing the transport + * layer of the terminal to issue a GET RESPONSE command + * with a maximum length of 'xx'; or + * b) status indicating a warning or error condition (but + * not SW1 SW2 = '90 00'). + * + * The GET RESPONSE command so issued is then treated as + * described for case 2 commands. + */ + if (res->sw1 == 0x61) { + binder_sim_logical_access_transmit(cbd, + CMD_GET_RESPONSE, 0, 0, res->sw2, NULL, + binder_sim_logical_access_get_results_cb); + } else if (binder_sim_io_response_ok(res)) { + cb(binder_error_ok(&err), res->data, res->data_len, + cbd->data); + } else { + cb(binder_error_sim(&err, res->sw1, res->sw2), NULL, 0, + cbd->data); + } + binder_sim_io_response_free(res); + return; + } + binder_sim_io_response_free(res); + } else { + ofono_error("Unexpected iccTransmitApduLogicalChannel response %d", + resp); + } + } + cb(binder_error_failure(&err), NULL, 0, cbd->data); +} + + +static +void +binder_sim_logical_access( + struct ofono_sim* sim, + int channel, + const unsigned char* pdu, + unsigned int len, + ofono_sim_logical_access_cb_t cb, + void* data) +{ + BinderSim* self = binder_sim_get_data(sim); + const char* hex_data; + char* tmp; + /* SIM Command APDU: CLA INS P1 P2 P3 Data */ + BinderSimSessionCbData* cbd = binder_sim_session_cbd_new(self, channel, + pdu[0], cb, data); + + GASSERT(len >= 5); + if (len > 5) { + hex_data = tmp = binder_encode_hex(pdu + 5, len - 5); + } else { + tmp = NULL; + hex_data = ""; + } + + binder_sim_logical_access_transmit(cbd, pdu[1], pdu[2], pdu[3], pdu[4], + hex_data, binder_sim_logical_access_cb); + binder_sim_session_cbd_unref(cbd); + g_free(tmp); +} + +static +void +binder_sim_session_read_binary( + struct ofono_sim* sim, + int session, + int fileid, + int start, + int length, + const unsigned char* path, + unsigned int path_len, + ofono_sim_read_cb_t cb, + void* data) +{ + struct ofono_error error; + + ofono_error("session_read_binary not implemented"); + cb(binder_error_failure(&error), NULL, 0, data); +} + +static +void +binder_sim_session_read_record( + struct ofono_sim* sim, + int channel, + int fileid, + int record, + int length, + const unsigned char* path, + unsigned int path_len, + ofono_sim_read_cb_t cb, + void* data) +{ + struct ofono_error error; + + ofono_error("session_read_record not implemented"); + cb(binder_error_failure(&error), NULL, 0, data); +} + +static +void +binder_sim_session_read_info( + struct ofono_sim* sim, + int channel, + int fileid, + const unsigned char* path, + unsigned int path_len, + ofono_sim_file_info_cb_t cb, + void* data) +{ + struct ofono_error error; + + ofono_error("session_read_info not implemented"); + cb(binder_error_failure(&error), -1, -1, -1, NULL, 0, data); +} + +static +void +binder_sim_refresh_cb( + RadioClient* client, + RADIO_IND code, + const GBinderReader* reader, + gpointer user_data) +{ + BinderSim* self = user_data; + + /* + * BINDER_UNSOL_SIM_REFRESH may contain the EFID of the updated file, + * so we could be more descrete here. However I have't actually + * seen that in real life, let's just refresh everything for now. + */ + ofono_sim_refresh_full(self->sim); +} + +static +gboolean +binder_sim_register( + gpointer user) +{ + BinderSim* self = user; + RadioClient* client = self->g->client; + + DBG_(self, ""); + GASSERT(self->idle_id); + self->idle_id = 0; + + ofono_sim_register(self->sim); + + /* Register for change notifications */ + self->card_event_id[SIM_CARD_STATUS_EVENT] = + binder_sim_card_add_status_changed_handler(self->card, + binder_sim_status_changed_cb, self); + self->card_event_id[SIM_CARD_APP_EVENT] = + binder_sim_card_add_app_changed_handler(self->card, + binder_sim_app_changed_cb, self); + self->sim_state_watch_id = + ofono_watch_add_sim_state_changed_handler(self->watch, + binder_sim_state_changed_cb, self); + + /* And IRadio events */ + self->io_event_id[IO_EVENT_SIM_REFRESH] = + radio_client_add_indication_handler(client, + RADIO_IND_SIM_REFRESH, + binder_sim_refresh_cb, self); + + /* Check the current state */ + binder_sim_status_changed_cb(self->card, self); + return G_SOURCE_REMOVE; +} + +static +int +binder_sim_probe( + struct ofono_sim* sim, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderSim* self = g_new0(BinderSim, 1); + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->empty_pin_query_allowed = modem->config.empty_pin_query; + self->card = binder_sim_card_ref(modem->sim_card); + self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ + self->watch = ofono_watch_new(binder_modem_get_path(modem)); + self->sim = sim; + + DBG_(self, ""); + binder_sim_invalidate_passwd_state(self); + self->idle_id = g_idle_add(binder_sim_register, self); + ofono_sim_set_data(sim, self); + return 0; +} + +static void binder_sim_remove(struct ofono_sim *sim) +{ + BinderSim* self = binder_sim_get_data(sim); + + DBG_(self, ""); + + g_list_free_full(self->pin_cbd_list, (GDestroyNotify) + binder_sim_pin_cbd_free); + + radio_client_remove_all_handlers(self->g->client, self->io_event_id); + radio_request_drop(self->query_pin_retries_req); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + if (self->list_apps_id) { + g_source_remove(self->list_apps_id); + } + + if (self->idle_id) { + g_source_remove(self->idle_id); + } + + if (self->query_passwd_state_timeout_id) { + g_source_remove(self->query_passwd_state_timeout_id); + } + + if (self->query_passwd_state_sim_status_refresh_id) { + binder_sim_card_remove_handler(self->card, + self->query_passwd_state_sim_status_refresh_id); + } + + ofono_watch_remove_handler(self->watch, self->sim_state_watch_id); + ofono_watch_unref(self->watch); + + binder_sim_card_remove_all_handlers(self->card, self->card_event_id); + binder_sim_card_unref(self->card); + + g_free(self->log_prefix); + g_free(self); + + ofono_sim_set_data(sim, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_sim_driver binder_sim_driver = { + .name = BINDER_DRIVER, + .probe = binder_sim_probe, + .remove = binder_sim_remove, + .read_file_info = binder_sim_ofono_read_file_info, + .read_file_transparent = binder_sim_ofono_read_file_transparent, + .read_file_linear = binder_sim_ofono_read_file_linear, + .read_file_cyclic = binder_sim_ofono_read_file_cyclic, + .write_file_transparent = binder_sim_write_file_transparent, + .write_file_linear = binder_sim_write_file_linear, + .write_file_cyclic = binder_sim_write_file_cyclic, + .read_imsi = binder_sim_read_imsi, + .query_passwd_state = binder_sim_query_passwd_state, + .send_passwd = binder_sim_pin_send, + .lock = binder_sim_pin_change_state, + .reset_passwd = binder_sim_pin_send_puk, + .change_passwd = binder_sim_change_passwd, + .query_pin_retries = binder_sim_query_pin_retries, + .query_facility_lock = binder_sim_query_facility_lock, + .list_apps = binder_sim_list_apps, + .open_channel2 = binder_sim_open_channel, + .close_channel = binder_sim_close_channel, + .session_read_binary = binder_sim_session_read_binary, + .session_read_record = binder_sim_session_read_record, + .session_read_info = binder_sim_session_read_info, + .logical_access = binder_sim_logical_access +}; + +void +binder_sim_init() +{ + ofono_sim_driver_register(&binder_sim_driver); +} + +void +binder_sim_cleanup() +{ + ofono_sim_driver_unregister(&binder_sim_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim.h b/src/binder_sim.h new file mode 100644 index 0000000..fd5287b --- /dev/null +++ b/src/binder_sim.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_SIM_H +#define BINDER_SIM_H + +#include "binder_types.h" + +void +binder_sim_init(void) + BINDER_INTERNAL; + +void +binder_sim_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_SIM_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim_card.c b/src/binder_sim_card.c new file mode 100644 index 0000000..ac3d3d6 --- /dev/null +++ b/src/binder_sim_card.c @@ -0,0 +1,870 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_sim_card.h" +#include "binder_radio.h" +#include "binder_util.h" +#include "binder_log.h" + +#include +#include +#include + +#include +#include + +#include + +/* + * First we wait for USIM app to get activated by itself. If that + * doesn't happen within UICC_SUBSCRIPTION_START_MS we poke the SIM + * with SET_UICC_SUBSCRIPTION request, resubmitting it if it times out. + * If nothing happens within UICC_SUBSCRIPTION_TIMEOUT_MS we give up. + * + * Submitting SET_UICC_SUBSCRIPTION request when modem doesn't expect + * it sometimes breaks pretty much everything. Unfortunately, there no + * reliable way to find out when modem expects it and when it doesn't :/ + */ +#define UICC_SUBSCRIPTION_RETRY_MS (500) +#define UICC_SUBSCRIPTION_START_MS (5000) +#define UICC_SUBSCRIPTION_TIMEOUT_MS (30000) + +/* SIM I/O idle timeout is measured in the number of idle loops. + * When active SIM I/O is going on, the idle loop count very rarely + * exceeds 1 between the requests, so 10 is more than enough. Idle + * loop is actually more accurate criteria than a timeout because + * it doesn't depend that much on the system load. */ +#define SIM_IO_IDLE_LOOPS (10) + +enum binder_sim_card_event { + EVENT_SIM_STATUS_CHANGED, + EVENT_UICC_SUBSCRIPTION_STATUS_CHANGED, + EVENT_COUNT +}; + +typedef struct binder_sim_card_object { + BinderSimCard card; + RadioRequest* status_req; + RadioRequest* sub_req; + RadioRequestGroup* g; + guint sub_start_timer; + gulong event_id[EVENT_COUNT]; + guint sim_io_idle_id; + guint sim_io_idle_count; + GHashTable* sim_io_pending; +} BinderSimCardObject; + +enum binder_sim_card_signal { + SIGNAL_STATUS_RECEIVED, + SIGNAL_STATUS_CHANGED, + SIGNAL_STATE_CHANGED, + SIGNAL_APP_CHANGED, + SIGNAL_SIM_IO_ACTIVE_CHANGED, + SIGNAL_COUNT +}; + +#define SIGNAL_STATUS_RECEIVED_NAME "binder-simcard-status-received" +#define SIGNAL_STATUS_CHANGED_NAME "binder-simcard-status-changed" +#define SIGNAL_STATE_CHANGED_NAME "binder-simcard-state-changed" +#define SIGNAL_APP_CHANGED_NAME "binder-simcard-app-changed" +#define SIGNAL_SIM_IO_ACTIVE_CHANGED_NAME "binder-simcard-sim-io-active-changed" + +static guint binder_sim_card_signals[SIGNAL_COUNT] = { 0 }; + +typedef GObjectClass BinderSimCardObjectClass; +GType binder_sim_card_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderSimCardObject, binder_sim_card, G_TYPE_OBJECT) +#define PARENT_CLASS binder_sim_card_parent_class +#define THIS_TYPE binder_sim_card_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, \ + BinderSimCardObject) + +#define NEW_SIGNAL(klass,name) NEW_SIGNAL_(klass,name##_CHANGED) +#define NEW_SIGNAL_(klass,name) \ + binder_sim_card_signals[SIGNAL_##name] = \ + g_signal_new(SIGNAL_##name##_NAME, \ + G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_FIRST, \ + 0, NULL, NULL, NULL, G_TYPE_NONE, 0) + +#define BINDER_SIMCARD_STATE_CHANGED (0x01) +#define BINDER_SIMCARD_STATUS_CHANGED (0x02) + +static inline BinderSimCardObject* binder_sim_card_cast(BinderSimCard* card) + { return G_LIKELY(card) ? THIS(card) : NULL; } + +static +gboolean +binder_sim_card_app_equal( + const BinderSimCardApp* a1, + const BinderSimCardApp* a2) +{ + if (a1 == a2) { + return TRUE; + } else if (!a1 || !a2) { + return FALSE; + } else { + return a1->app_type == a2->app_type && + a1->app_state == a2->app_state && + a1->perso_substate == a2->perso_substate && + a1->pin_replaced == a2->pin_replaced && + a1->pin1_state == a2->pin1_state && + a1->pin2_state == a2->pin2_state && + !g_strcmp0(a1->aid, a2->aid) && + !g_strcmp0(a1->label, a2->label); + } +} + +static +int +binder_sim_card_status_compare( + const BinderSimCardStatus* s1, + const BinderSimCardStatus* s2) +{ + if (s1 == s2) { + return 0; + } else if (!s1 || !s2) { + return BINDER_SIMCARD_STATE_CHANGED | BINDER_SIMCARD_STATUS_CHANGED; + } else { + int diff = 0; + + if (s1->card_state != s2->card_state) { + diff |= BINDER_SIMCARD_STATE_CHANGED; + } + + if (s1->pin_state != s2->pin_state || + s1->gsm_umts_index != s2->gsm_umts_index || + s1->ims_index != s2->ims_index || + s1->num_apps != s2->num_apps) { + diff |= BINDER_SIMCARD_STATUS_CHANGED; + } else { + int i; + + for (i = 0; i < s1->num_apps; i++) { + if (!binder_sim_card_app_equal(s1->apps + i, s2->apps + i)) { + diff |= BINDER_SIMCARD_STATUS_CHANGED; + break; + } + } + } + + return diff; + } +} + +static +void +binder_sim_card_status_free( + BinderSimCardStatus* status) +{ + if (status) { + if (status->apps) { + int i; + + for (i = 0; i < status->num_apps; i++) { + g_free(status->apps[i].aid); + g_free(status->apps[i].label); + } + } + /* status->apps is allocated from the same memory block */ + g_free(status); + } +} + +static +void +binder_sim_card_tx_start( + BinderSimCardObject* self) +{ + RADIO_BLOCK block = radio_request_group_block_status(self->g); + + if (block == RADIO_BLOCK_NONE) { + block = radio_request_group_block(self->g); + DBG("status tx for slot %u %s", self->card.slot, + (block == RADIO_BLOCK_ACQUIRED) ? "started" : "starting"); + } +} + +static +void +binder_sim_card_tx_check( + BinderSimCardObject* self) +{ + if (radio_request_group_block_status(self->g) != RADIO_BLOCK_NONE) { + BinderSimCard* card = &self->card; + const BinderSimCardStatus* status = card->status; + + if (status && status->card_state == RADIO_CARD_STATE_PRESENT) { + /* + * Transaction (if there is any) is finished when + * both GET_SIM_STATUS and SET_UICC_SUBSCRIPTION + * complete or get dropped. + */ + if (!self->status_req && !self->sub_req && + status->gsm_umts_index >= 0 && + status->gsm_umts_index < status->num_apps) { + DBG("status tx for slot %u finished", card->slot); + radio_request_group_unblock(self->g); + } + } else { + DBG("status tx for slot %u cancelled", card->slot); + radio_request_group_unblock(self->g); + } + } +} + +static +void +binder_sim_card_subscription_done( + BinderSimCardObject* self) +{ + if (self->sub_start_timer) { + /* Don't need this timer anymore */ + g_source_remove(self->sub_start_timer); + self->sub_start_timer = 0; + } + if (self->sub_req) { + radio_request_drop(self->sub_req); + self->sub_req = NULL; + } + binder_sim_card_tx_check(self); +} + +static +void +binder_sim_card_subscribe_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCardObject* self = THIS(user_data); + + GASSERT(resp == RADIO_RESP_SET_UICC_SUBSCRIPTION); + GASSERT(status == RADIO_TX_STATUS_OK); + GASSERT(error == RADIO_ERROR_NONE); + GASSERT(self->sub_req == req); + + radio_request_unref(self->sub_req); + self->sub_req = NULL; + DBG("UICC subscription OK for slot %u", self->card.slot); + binder_sim_card_subscription_done(self); +} + +static +void +binder_sim_card_subscribe( + BinderSimCardObject* self, + int app_index) +{ + BinderSimCard* card = &self->card; + GBinderWriter args; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_UICC_SUBSCRIPTION, &args, + binder_sim_card_subscribe_cb, NULL, self); + RadioSelectUiccSub* sub = gbinder_writer_new0(&args, RadioSelectUiccSub); + + /* setUiccSubscription(serial, SelectUiccSub uiccSub) */ + DBG("%u,%d", card->slot, app_index); + sub->slot = card->slot; + sub->appIndex = app_index; + sub->actStatus = RADIO_UICC_SUB_ACTIVATE; + gbinder_writer_append_buffer_object(&args, sub, sizeof(*sub)); + + radio_request_set_retry(req, UICC_SUBSCRIPTION_RETRY_MS, -1); + radio_request_set_timeout(req, UICC_SUBSCRIPTION_TIMEOUT_MS); + + /* N.B. Some adaptations never reply to SET_UICC_SUBSCRIPTION request */ + radio_request_drop(self->sub_req); + self->sub_req = req; + + /* + * Don't allow any requests other that GET_SIM_STATUS until + * we are done with the subscription. + */ + binder_sim_card_tx_start(self); + radio_request_submit(self->sub_req); +} + +static +int +binder_sim_card_select_app( + const BinderSimCardStatus *status) +{ + int i, selected_app = -1; + + for (i = 0; i < status->num_apps; i++) { + const int type = status->apps[i].app_type; + + if (type == RADIO_APP_TYPE_USIM || type == RADIO_APP_TYPE_RUIM) { + selected_app = i; + break; + } else if (type != RADIO_APP_TYPE_UNKNOWN && selected_app == -1) { + selected_app = i; + } + } + + DBG("%d", selected_app); + return selected_app; +} + +static +void +binder_sim_card_update_app( + BinderSimCardObject* self) +{ + BinderSimCard* card = &self->card; + const BinderSimCardApp* old_app = card->app; + const BinderSimCardStatus* status = card->status; + int app_index; + + if (status->card_state == RADIO_CARD_STATE_PRESENT) { + if (status->gsm_umts_index >= 0 && + status->gsm_umts_index < status->num_apps) { + app_index = status->gsm_umts_index; + binder_sim_card_subscription_done(self); + } else { + app_index = binder_sim_card_select_app(status); + if (app_index >= 0 && !self->sub_start_timer) { + binder_sim_card_subscribe(self, app_index); + } + } + } else { + app_index = -1; + binder_sim_card_subscription_done(self); + } + + if (app_index >= 0 && + status->apps[app_index].app_type != RADIO_APP_TYPE_UNKNOWN) { + card->app = status->apps + app_index; + } else { + card->app = NULL; + } + + if (!binder_sim_card_app_equal(old_app, card->app)) { + g_signal_emit(self, binder_sim_card_signals[SIGNAL_APP_CHANGED], 0); + } +} + +static +gboolean +binder_sim_card_sub_start_timeout( + gpointer user_data) +{ + BinderSimCardObject* self = THIS(user_data); + + DBG("%u", self->card.slot); + GASSERT(self->sub_start_timer); + self->sub_start_timer = 0; + binder_sim_card_update_app(self); + return G_SOURCE_REMOVE; +} + +static +void +binder_sim_card_update_status( + BinderSimCardObject* self, + BinderSimCardStatus* status) +{ + BinderSimCard* card = &self->card; + const int diff = binder_sim_card_status_compare(card->status, status); + + if (diff) { + BinderSimCardStatus* old_status = card->status; + + card->status = status; + if (diff & BINDER_SIMCARD_STATE_CHANGED && + status->card_state == RADIO_CARD_STATE_PRESENT) { + + /* + * SIM card has just appeared, give it some time to + * activate the USIM app + */ + if (self->sub_start_timer) { + g_source_remove(self->sub_start_timer); + } + DBG("started subscription timeout for slot %u", card->slot); + self->sub_start_timer = g_timeout_add(UICC_SUBSCRIPTION_START_MS, + binder_sim_card_sub_start_timeout, self); + } + binder_sim_card_update_app(self); + + g_signal_emit(self, binder_sim_card_signals + [SIGNAL_STATUS_RECEIVED], 0); + + if (diff & BINDER_SIMCARD_STATUS_CHANGED) { + DBG("status changed"); + g_signal_emit(self, binder_sim_card_signals + [SIGNAL_STATUS_CHANGED], 0); + } + if (diff & BINDER_SIMCARD_STATE_CHANGED) { + DBG("state changed"); + g_signal_emit(self, binder_sim_card_signals + [SIGNAL_STATE_CHANGED], 0); + } + binder_sim_card_status_free(old_status); + } else { + binder_sim_card_update_app(self); + binder_sim_card_status_free(status); + g_signal_emit(self, binder_sim_card_signals + [SIGNAL_STATUS_RECEIVED], 0); + } +} + +static +BinderSimCardStatus* +binder_sim_card_status_new( + const RadioCardStatus* radio_status) +{ + const guint num_apps = radio_status->apps.count; + BinderSimCardStatus* status = g_malloc0(sizeof(BinderSimCardStatus) + + num_apps * sizeof(BinderSimCardApp)); + + DBG("card_state=%d, universal_pin_state=%d, gsm_umts_index=%d, " + "ims_index=%d, num_apps=%d", radio_status->cardState, + radio_status->universalPinState, + radio_status->gsmUmtsSubscriptionAppIndex, + radio_status->imsSubscriptionAppIndex, num_apps); + + status->card_state = radio_status->cardState; + status->pin_state = radio_status->universalPinState; + status->gsm_umts_index = radio_status->gsmUmtsSubscriptionAppIndex; + status->ims_index = radio_status->imsSubscriptionAppIndex; + + if ((status->num_apps = num_apps) > 0) { + const RadioAppStatus* radio_apps = radio_status->apps.data.ptr; + guint i; + + status->apps = (BinderSimCardApp*)(status + 1); + for (i = 0; i < num_apps; i++) { + const RadioAppStatus* radio_app = radio_apps + i; + BinderSimCardApp* app = status->apps + i; + + app->app_type = radio_app->appType; + app->app_state = radio_app->appState; + app->perso_substate = radio_app->persoSubstate; + app->pin_replaced = radio_app->pinReplaced; + app->pin1_state = radio_app->pin1; + app->pin2_state = radio_app->pin2; + app->aid = g_strdup(radio_app->aid.data.str); + app->label = g_strdup(radio_app->label.data.str); + + DBG("app[%d]: type=%d, state=%d, perso_substate=%d, aid_ptr=%s, " + "label=%s, pin1_replaced=%d, pin1=%d, pin2=%d", i, + app->app_type, app->app_state, app->perso_substate, + app->aid, app->label, app->pin_replaced, app->pin1_state, + app->pin2_state); + } + } + + return status; +} + +static +void +binder_sim_card_status_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSimCardObject* self = THIS(user_data); + + GASSERT(self->status_req); + radio_request_unref(self->status_req); + self->status_req = NULL; + + if (status == RADIO_TX_STATUS_OK && error == RADIO_ERROR_NONE) { + const RadioCardStatus* status_1_0; + const RadioCardStatus_1_2* status_1_2; + const RadioCardStatus_1_4* status_1_4; + BinderSimCardStatus* status = NULL; + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + switch (resp) { + case RADIO_RESP_GET_ICC_CARD_STATUS: + status_1_0 = gbinder_reader_read_hidl_struct(&reader, + RadioCardStatus); + if (status_1_0) { + status = binder_sim_card_status_new(status_1_0); + } + break; + case RADIO_RESP_GET_ICC_CARD_STATUS_1_2: + status_1_2 = gbinder_reader_read_hidl_struct(&reader, + RadioCardStatus_1_2); + if (status_1_2) { + status = binder_sim_card_status_new(&status_1_2->base); + } + break; + case RADIO_RESP_GET_ICC_CARD_STATUS_RESPONSE_1_4: + status_1_4 = gbinder_reader_read_hidl_struct(&reader, + RadioCardStatus_1_4); + if (status_1_4) { + status = binder_sim_card_status_new(&status_1_4->base); + } + break; + default: + ofono_warn("Unexpected getIccCardStatus response %u", resp); + } + + if (status) { + binder_sim_card_update_status(self, status); + } + } + binder_sim_card_tx_check(self); +} + +static +void +binder_sim_card_get_status( + BinderSimCardObject* self) +{ + if (self->status_req) { + /* Retry right away, don't wait for retry timeout to expire */ + radio_request_retry(self->status_req); + } else { + self->status_req = radio_request_new2(self->g, + RADIO_REQ_GET_ICC_CARD_STATUS, NULL, + binder_sim_card_status_cb, NULL, self); + + /* + * Start the transaction to not allow any other requests to + * interfere with SIM status query. + */ + binder_sim_card_tx_start(self); + radio_request_set_retry(self->status_req, BINDER_RETRY_MS, -1); + radio_request_submit(self->status_req); + } +} + +static +void +binder_sim_card_update_sim_io_active( + BinderSimCardObject* self) +{ + /* SIM I/O is considered active for certain period of time after + * the last request has completed. That's because SIM_IO requests + * are usually submitted in large quantities and quick succession. + * Some modems don't like being bothered while they are doing SIM I/O + * and some time after that too. That sucks but what else can we + * do about it? */ + BinderSimCard* card = &self->card; + const gboolean active = self->sim_io_idle_id || + g_hash_table_size(self->sim_io_pending); + + if (card->sim_io_active != active) { + card->sim_io_active = active; + DBG("SIM I/O for slot %u is %sactive", card->slot, active ? "" : "in"); + g_signal_emit(self, binder_sim_card_signals + [SIGNAL_SIM_IO_ACTIVE_CHANGED], 0); + } +} + +static +gboolean +binder_sim_card_sim_io_idle_cb( + gpointer user_data) +{ + BinderSimCardObject* self = THIS(user_data); + + if (++(self->sim_io_idle_count) >= SIM_IO_IDLE_LOOPS) { + self->sim_io_idle_id = 0; + self->sim_io_idle_count = 0; + binder_sim_card_update_sim_io_active(self); + return G_SOURCE_REMOVE; + } else { + return G_SOURCE_CONTINUE; + } +} + +static +void +binder_sim_card_status_changed( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + binder_sim_card_get_status(THIS(user_data)); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderSimCard* +binder_sim_card_new( + RadioClient* client, + guint slot) +{ + BinderSimCardObject* self = g_object_new(THIS_TYPE, NULL); + BinderSimCard *card = &self->card; + + DBG("%u", slot); + card->slot = slot; + self->g = radio_request_group_new(client); /* Keeps ref to client */ + + self->event_id[EVENT_SIM_STATUS_CHANGED] = + radio_client_add_indication_handler(client, + RADIO_IND_SIM_STATUS_CHANGED, + binder_sim_card_status_changed, self); + self->event_id[EVENT_UICC_SUBSCRIPTION_STATUS_CHANGED] = + radio_client_add_indication_handler(client, + RADIO_IND_SUBSCRIPTION_STATUS_CHANGED, + binder_sim_card_status_changed, self); + binder_sim_card_get_status(self); + return card; +} + +BinderSimCard* +binder_sim_card_ref( + BinderSimCard* card) +{ + if (G_LIKELY(card)) { + g_object_ref(THIS(card)); + } + return card; +} + +void +binder_sim_card_unref( + BinderSimCard* card) +{ + if (G_LIKELY(card)) { + g_object_unref(THIS(card)); + } +} + +void +binder_sim_card_reset( + BinderSimCard* card) +{ + if (G_LIKELY(card)) { + BinderSimCardObject* self = binder_sim_card_cast(card); + BinderSimCardStatus* status = g_new0(BinderSimCardStatus, 1); + + /* Simulate removal and re-submit the SIM status query */ + status->card_state = RADIO_CARD_STATE_ABSENT; + status->gsm_umts_index = -1; + status->ims_index = -1; + binder_sim_card_update_status(self, status); + binder_sim_card_get_status(self); + } +} + +void +binder_sim_card_request_status( + BinderSimCard* card) +{ + if (G_LIKELY(card)) { + binder_sim_card_get_status(binder_sim_card_cast(card)); + } +} + +void +binder_sim_card_sim_io_started( + BinderSimCard* card, + gpointer key) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + if (G_LIKELY(self) && G_LIKELY(key)) { + g_hash_table_insert(self->sim_io_pending, key, key); + if (self->sim_io_idle_id) { + g_source_remove(self->sim_io_idle_id); + self->sim_io_idle_id = 0; + self->sim_io_idle_count = 0; + } + binder_sim_card_update_sim_io_active(self); + } +} + +void +binder_sim_card_sim_io_finished( + BinderSimCard* card, + gpointer key) +{ + if (G_LIKELY(card) && G_LIKELY(key)) { + BinderSimCardObject* self = binder_sim_card_cast(card); + + if (g_hash_table_remove(self->sim_io_pending, key) && + g_hash_table_size(self->sim_io_pending) == 0) { + /* Reset the idle loop count */ + if (self->sim_io_idle_id) { + g_source_remove(self->sim_io_idle_id); + self->sim_io_idle_count = 0; + } + self->sim_io_idle_id = g_idle_add(binder_sim_card_sim_io_idle_cb, + self); + } + binder_sim_card_update_sim_io_active(self); + } +} + +gboolean +binder_sim_card_ready( + BinderSimCard* card) +{ + return card && card->app && + ((card->app->app_state == RADIO_APP_STATE_READY) || + (card->app->app_state == RADIO_APP_STATE_SUBSCRIPTION_PERSO && + card->app->perso_substate == RADIO_PERSO_SUBSTATE_READY)); +} + +gulong +binder_sim_card_add_status_received_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + return (G_LIKELY(self) && G_LIKELY(fn)) ? g_signal_connect(self, + SIGNAL_STATUS_RECEIVED_NAME, G_CALLBACK(fn), user_data) : 0; +} + +gulong +binder_sim_card_add_status_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + return (G_LIKELY(self) && G_LIKELY(fn)) ? g_signal_connect(self, + SIGNAL_STATUS_CHANGED_NAME, G_CALLBACK(fn), user_data) : 0; +} + +gulong +binder_sim_card_add_state_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + return (G_LIKELY(self) && G_LIKELY(fn)) ? g_signal_connect(self, + SIGNAL_STATE_CHANGED_NAME, G_CALLBACK(fn), user_data) : 0; +} + +gulong +binder_sim_card_add_app_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + return (G_LIKELY(self) && G_LIKELY(fn)) ? g_signal_connect(self, + SIGNAL_APP_CHANGED_NAME, G_CALLBACK(fn), user_data) : 0; +} + +gulong +binder_sim_card_add_sim_io_active_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + return (G_LIKELY(self) && G_LIKELY(fn)) ? g_signal_connect(self, + SIGNAL_SIM_IO_ACTIVE_CHANGED_NAME, G_CALLBACK(fn), user_data) : 0; +} + +void +binder_sim_card_remove_handler( + BinderSimCard* card, + gulong id) +{ + BinderSimCardObject* self = binder_sim_card_cast(card); + + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +binder_sim_card_remove_handlers( + BinderSimCard* card, + gulong* ids, + int n) +{ + gutil_disconnect_handlers(binder_sim_card_cast(card), ids, n); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_sim_card_init( + BinderSimCardObject* self) +{ + self->sim_io_pending = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +static +void +binder_sim_card_finalize( + GObject* object) +{ + BinderSimCardObject* self = THIS(object); + BinderSimCard* card = &self->card; + + if (self->sim_io_idle_id) { + g_source_remove(self->sim_io_idle_id); + } + if (self->sub_start_timer) { + g_source_remove(self->sub_start_timer); + } + g_hash_table_destroy(self->sim_io_pending); + + radio_request_drop(self->status_req); + radio_request_drop(self->sub_req); + + radio_client_remove_all_handlers(self->g->client, self->event_id); + radio_request_group_unblock(self->g); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + binder_sim_card_status_free(card->status); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_sim_card_class_init( + BinderSimCardObjectClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_sim_card_finalize; + NEW_SIGNAL_(klass,STATUS_RECEIVED); + NEW_SIGNAL(klass,STATUS); + NEW_SIGNAL(klass,STATE); + NEW_SIGNAL(klass,APP); + NEW_SIGNAL(klass,SIM_IO_ACTIVE); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim_card.h b/src/binder_sim_card.h new file mode 100644 index 0000000..7b7adb2 --- /dev/null +++ b/src/binder_sim_card.h @@ -0,0 +1,168 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_SIM_CARD_H +#define BINDER_SIM_CARD_H + +#include "binder_types.h" + +#include + +typedef struct binder_sim_card_app { + RADIO_APP_TYPE app_type; + RADIO_APP_STATE app_state; + RADIO_PERSO_SUBSTATE perso_substate; + char* aid; + char* label; + guint pin_replaced; + RADIO_PIN_STATE pin1_state; + RADIO_PIN_STATE pin2_state; +} BinderSimCardApp; + +typedef struct binder_sim_card_status { + RADIO_CARD_STATE card_state; + RADIO_PIN_STATE pin_state; + int gsm_umts_index; + int ims_index; + guint num_apps; + BinderSimCardApp* apps; +} BinderSimCardStatus; + +struct binder_sim_card { + GObject object; + BinderSimCardStatus* status; + const BinderSimCardApp* app; + gboolean sim_io_active; + guint slot; +}; + +typedef +void +(*BinderSimCardFunc)( + BinderSimCard* card, + void* user_data); + +BinderSimCard* +binder_sim_card_new( + RadioClient* client, + guint slot) + BINDER_INTERNAL; + +BinderSimCard* +binder_sim_card_ref( + BinderSimCard* card) + BINDER_INTERNAL; + +void +binder_sim_card_unref( + BinderSimCard* card) + BINDER_INTERNAL; + +void +binder_sim_card_reset( + BinderSimCard* card) + BINDER_INTERNAL; + +void +binder_sim_card_request_status( + BinderSimCard* card) + BINDER_INTERNAL; + +void +binder_sim_card_sim_io_started( + BinderSimCard* card, + gpointer key) + BINDER_INTERNAL; + +void +binder_sim_card_sim_io_finished( + BinderSimCard* card, + gpointer key) + BINDER_INTERNAL; + +gboolean +binder_sim_card_ready( + BinderSimCard* card) + BINDER_INTERNAL; + +gulong +binder_sim_card_add_status_received_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) + BINDER_INTERNAL; + +gulong +binder_sim_card_add_status_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) + BINDER_INTERNAL; + +gulong +binder_sim_card_add_state_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) + BINDER_INTERNAL; + +gulong +binder_sim_card_add_app_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) + BINDER_INTERNAL; + +gulong +binder_sim_card_add_sim_io_active_changed_handler( + BinderSimCard* card, + BinderSimCardFunc fn, + void* user_data) + BINDER_INTERNAL; + +void +binder_sim_card_remove_handler( + BinderSimCard* card, + gulong id) + BINDER_INTERNAL; + +void +binder_sim_card_remove_handlers( + BinderSimCard* card, + gulong* ids, + int n) + BINDER_INTERNAL; + +/* Inline wrappers */ + +static inline RADIO_APP_TYPE binder_sim_card_app_type(BinderSimCard* card) + { return (card && card->app) ? card->app->app_type : + RADIO_APP_TYPE_UNKNOWN; } + +static inline const char* binder_sim_card_app_aid(BinderSimCard* card) + { return (card && card->app) ? card->app->aid : NULL; } + +#define binder_sim_card_remove_all_handlers(net, ids) \ + binder_sim_card_remove_handlers(net, ids, G_N_ELEMENTS(ids)) + +#endif /* BINDER_SIM_CARD_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim_settings.c b/src/binder_sim_settings.c new file mode 100644 index 0000000..19d809e --- /dev/null +++ b/src/binder_sim_settings.c @@ -0,0 +1,214 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_log.h" +#include "binder_sim_settings.h" + +#include + +#include +#include + +BINDER_BASE_ASSERT_COUNT(BINDER_SIM_SETTINGS_PROPERTY_COUNT); + +enum ofono_watch_events { + WATCH_EVENT_IMSI, + WATCH_EVENT_COUNT +}; + +typedef struct binder_sim_settings_object { + BinderBase base; + BinderSimSettings pub; + gulong watch_event_id[WATCH_EVENT_COUNT]; + struct ofono_watch* watch; + char* imsi; +} BinderSimSettingsObject; + +typedef BinderBaseClass BinderSimSettingsObjectClass; +GType binder_sim_settings_object_get_type() BINDER_INTERNAL; +G_DEFINE_TYPE(BinderSimSettingsObject, binder_sim_settings_object, + BINDER_TYPE_BASE) +#define PARENT_CLASS binder_sim_settings_object_parent_class +#define THIS_TYPE binder_sim_settings_object_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, \ + BinderSimSettingsObject) + +static inline +BinderSimSettingsObject* +binder_sim_settings_cast( + BinderSimSettings* settings) +{ + return G_LIKELY(settings) ? + THIS(G_CAST(settings, BinderSimSettingsObject, pub)) : + NULL; +} + +static +void +binder_sim_settings_imsi_changed( + struct ofono_watch* watch, + void* user_data) +{ + BinderSimSettingsObject* self = THIS(user_data); + + if (g_strcmp0(self->imsi, watch->imsi)) { + g_object_ref(self); + g_free(self->imsi); + self->pub.imsi = self->imsi = g_strdup(watch->imsi); + binder_base_emit_property_change(&self->base, + BINDER_SIM_SETTINGS_PROPERTY_IMSI); + g_object_unref(self); + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderSimSettings* +binder_sim_settings_new( + const char* path, + enum ofono_radio_access_mode techs) +{ + BinderSimSettings* settings = NULL; + + if (G_LIKELY(path)) { + BinderSimSettingsObject* self = g_object_new(THIS_TYPE, NULL); + + settings = &self->pub; + settings->techs = techs; + settings->pref = techs; + self->watch = ofono_watch_new(path); + self->watch_event_id[WATCH_EVENT_IMSI] = + ofono_watch_add_imsi_changed_handler(self->watch, + binder_sim_settings_imsi_changed, self); + settings->imsi = self->imsi = g_strdup(self->watch->imsi); + } + return settings; +} + +BinderSimSettings* +binder_sim_settings_ref( + BinderSimSettings* settings) +{ + BinderSimSettingsObject* self = binder_sim_settings_cast(settings); + + if (G_LIKELY(self)) { + g_object_ref(self); + } + return settings; +} + +void +binder_sim_settings_unref( + BinderSimSettings* settings) +{ + BinderSimSettingsObject* self = binder_sim_settings_cast(settings); + + if (G_LIKELY(self)) { + g_object_unref(self); + } +} + +void +binder_sim_settings_set_pref( + BinderSimSettings* settings, + enum ofono_radio_access_mode pref) +{ + BinderSimSettingsObject* self = binder_sim_settings_cast(settings); + + if (G_LIKELY(self) && settings->pref != pref) { + settings->pref = pref; + binder_base_emit_property_change(&self->base, + BINDER_SIM_SETTINGS_PROPERTY_PREF); + } +} + +gulong +binder_sim_settings_add_property_handler( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + BinderSimSettingsPropertyFunc callback, + void* user_data) +{ + BinderSimSettingsObject* self = binder_sim_settings_cast(settings); + + return G_LIKELY(self) ? binder_base_add_property_handler(&self->base, + property, G_CALLBACK(callback), user_data) : 0; +} + +void +binder_sim_settings_remove_handler( + BinderSimSettings* settings, + gulong id) +{ + BinderSimSettingsObject* self = binder_sim_settings_cast(settings); + + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +binder_sim_settings_remove_handlers( + BinderSimSettings* settings, + gulong* ids, + int count) +{ + gutil_disconnect_handlers(binder_sim_settings_cast(settings), ids, count); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_sim_settings_object_init( + BinderSimSettingsObject* self) +{ +} + +static +void +binder_sim_settings_object_finalize( + GObject* object) +{ + BinderSimSettingsObject* self = THIS(object); + + ofono_watch_remove_all_handlers(self->watch, self->watch_event_id); + ofono_watch_unref(self->watch); + g_free(self->imsi); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +binder_sim_settings_object_class_init( + BinderSimSettingsObjectClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = binder_sim_settings_object_finalize; + BINDER_BASE_CLASS(klass)->public_offset = + G_STRUCT_OFFSET(BinderSimSettingsObject, pub); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sim_settings.h b/src/binder_sim_settings.h new file mode 100644 index 0000000..e43782d --- /dev/null +++ b/src/binder_sim_settings.h @@ -0,0 +1,97 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_SIM_SETTINGS_H +#define BINDER_SIM_SETTINGS_H + +#include "binder_types.h" + +#include + +typedef enum binder_sim_settings_property { + BINDER_SIM_SETTINGS_PROPERTY_ANY, + BINDER_SIM_SETTINGS_PROPERTY_IMSI, + BINDER_SIM_SETTINGS_PROPERTY_PREF, + BINDER_SIM_SETTINGS_PROPERTY_COUNT +} BINDER_SIM_SETTINGS_PROPERTY; + +struct binder_sim_settings { + const char* imsi; + enum ofono_radio_access_mode techs; /* Mask */ + enum ofono_radio_access_mode pref; /* Mask */ +}; + +typedef +void +(*BinderSimSettingsPropertyFunc)( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data); + +BinderSimSettings* +binder_sim_settings_new( + const char* path, + enum ofono_radio_access_mode techs) + BINDER_INTERNAL; + +BinderSimSettings* +binder_sim_settings_ref( + BinderSimSettings* settings) + BINDER_INTERNAL; + +void +binder_sim_settings_unref( + BinderSimSettings* settings) + BINDER_INTERNAL; + +void +binder_sim_settings_set_pref( + BinderSimSettings* settings, + enum ofono_radio_access_mode pref) + BINDER_INTERNAL; + +gulong +binder_sim_settings_add_property_handler( + BinderSimSettings* settings, + BINDER_SIM_SETTINGS_PROPERTY property, + BinderSimSettingsPropertyFunc callback, + void* user_data) + BINDER_INTERNAL; + +void +binder_sim_settings_remove_handler( + BinderSimSettings* settings, + gulong id) + BINDER_INTERNAL; + +void +binder_sim_settings_remove_handlers( + BinderSimSettings* settings, + gulong* ids, + int count) + BINDER_INTERNAL; + +#define binder_sim_settings_remove_all_handlers(settings,ids) \ + binder_sim_settings_remove_handlers(settings, ids, G_N_ELEMENTS(ids)) + +#endif /* BINDER_SIM_SETTINGS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sms.c b/src/binder_sms.c new file mode 100644 index 0000000..3bbe821 --- /dev/null +++ b/src/binder_sms.c @@ -0,0 +1,707 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_sms.h" +#include "binder_util.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define BINDER_SMS_ACK_RETRY_MS 1000 +#define BINDER_SMS_ACK_RETRY_COUNT 10 + +#define SIM_EFSMS_FILEID 0x6F3C +#define EFSMS_LENGTH 176 + +static unsigned char sim_path[4] = {0x3F, 0x00, 0x7F, 0x10}; + +enum binder_sms_events { + SMS_EVENT_NEW_SMS, + SMS_EVENT_NEW_STATUS_REPORT, + SMS_EVENT_NEW_SMS_ON_SIM, + SMS_EVENT_COUNT +}; + +typedef struct binder_sms { + struct ofono_sms* sms; + struct ofono_watch* watch; + struct ofono_sim_context* sim_context; + char* log_prefix; + RadioRequestGroup* g; + gulong event_id[SMS_EVENT_COUNT]; + guint register_id; +} BinderSms; + +typedef struct binder_sms_cbd { + BinderSms* self; + union _ofono_sms_cb { + ofono_sms_sca_set_cb_t sca_set; + ofono_sms_sca_query_cb_t sca_query; + ofono_sms_submit_cb_t submit; + BinderCallback ptr; + } cb; + gpointer data; +} BinderSmsCbData; + +typedef struct binder_sms_sim_read_data { + BinderSms* self; + int record; +} BinderSmsSimReadData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) + +static inline BinderSms* binder_sms_get_data(struct ofono_sms *sms) + { return ofono_sms_get_data(sms); } + +static +BinderSmsCbData* +binder_sms_cbd_new( + BinderSms* self, + BinderCallback cb, + void* data) +{ + BinderSmsCbData* cbd = g_slice_new0(BinderSmsCbData); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_sms_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderSmsCbData, cbd); +} + +static +BinderSmsSimReadData* +binder_sms_sim_read_data_new( + BinderSms* self, + int rec) +{ + BinderSmsSimReadData* rd = g_slice_new0(BinderSmsSimReadData); + + rd->self = self; + rd->record = rec; + return rd; +} + +static +void +binder_sms_sim_read_data_free( + BinderSmsSimReadData* rd) +{ + gutil_slice_free(rd); +} + +static +void +binder_sms_sca_set_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSmsCbData* cbd = user_data; + ofono_sms_sca_set_cb_t cb = cbd->cb.sca_set; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SET_SMSC_ADDRESS) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_warn("smsc setting error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected setSmscAddress response %d", resp); + } + } + + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_sms_sca_set( + struct ofono_sms* sms, + const struct ofono_phone_number* sca, + ofono_sms_sca_set_cb_t cb, + void* data) +{ + BinderSms* self = binder_sms_get_data(sms); + char* tmp = NULL; + GBinderWriter writer; + const char* number = (sca->type == OFONO_NUMBER_TYPE_INTERNATIONAL) ? + (tmp = g_strconcat("+", sca->number, NULL)) : sca->number; + + /* setSmscAddress(int32_t serial, string smsc); */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_SMSC_ADDRESS, &writer, binder_sms_sca_set_cb, + binder_sms_cbd_free, binder_sms_cbd_new(self, BINDER_CB(cb), data)); + + DBG_(self, "setting sca: %s", number); + binder_append_hidl_string(&writer, number); + + if (!radio_request_submit(req)) { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } + + radio_request_unref(req); + g_free(tmp); +} + +static +void +binder_sms_sca_query_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSmsCbData* cbd = user_data; + ofono_sms_sca_query_cb_t cb = cbd->cb.sca_query; + struct ofono_error err; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_GET_SMSC_ADDRESS) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + const char* smsc; + + gbinder_reader_copy(&reader, args); + smsc = gbinder_reader_read_hidl_string_c(&reader); + if (smsc) { + struct ofono_phone_number sca; + + if (smsc[0] == '+') { + smsc++; + sca.type = OFONO_NUMBER_TYPE_INTERNATIONAL; + } else { + sca.type = OFONO_NUMBER_TYPE_UNKNOWN; + } + g_strlcpy(sca.number, smsc, sizeof(sca.number)); + DBG("csca_query_cb: %s, %d", sca.number, sca.type); + cb(binder_error_ok(&err), &sca, cbd->data); + return; + } + } else { + ofono_warn("smsc query error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected getSmscAddress response %d", resp); + } + } + + /* Error path */ + cb(binder_error_failure(&err), NULL, cbd->data); +} + +static +void +binder_sms_sca_query( + struct ofono_sms* sms, + ofono_sms_sca_query_cb_t cb, + void* data) +{ + BinderSms* self = binder_sms_get_data(sms); + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_GET_SMSC_ADDRESS, NULL, binder_sms_sca_query_cb, + binder_sms_cbd_free, binder_sms_cbd_new(self, BINDER_CB(cb), data)); + + DBG_(self, "sending csca_query"); + if (!radio_request_submit(req)) { + struct ofono_error err; + + cb(binder_error_failure(&err), NULL, data); + } + radio_request_unref(req); +} + +static +void +binder_sms_submit_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderSmsCbData* cbd = user_data; + ofono_sms_submit_cb_t cb = cbd->cb.submit; + struct ofono_error err; + + binder_error_init_failure(&err); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_SMS || + resp == RADIO_RESP_SEND_SMS_EXPECT_MORE) { + /* + * sendSmsResponse(RadioResponseInfo, SendSmsResult sms); + * sendSMSExpectMoreResponse(RadioResponseInfo, SendSmsResult sms); + */ + if (error == RADIO_ERROR_NONE) { + const RadioSendSmsResult* res; + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + res = gbinder_reader_read_hidl_struct(&reader, + RadioSendSmsResult); + + if (res) { + DBG("sms msg ref: %d, ack: %s err: %d", res->messageRef, + res->ackPDU.data.str, res->errorCode); + + /* + * Error is -1 if unknown or not applicable, + * otherwise 3GPP 27.005, 3.2.5 + */ + if (res->errorCode > 0) { + err.type = OFONO_ERROR_TYPE_CMS; + err.error = res->errorCode; + } else { + /* Success */ + cb(binder_error_ok(&err), res->messageRef, cbd->data); + return; + } + } + } else { + ofono_error("sms send error %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected sendSms response %d", resp); + } + } + /* Error path */ + cb(&err, 0, cbd->data); +} + +static +void +binder_sms_submit( + struct ofono_sms* sms, + const unsigned char* pdu, + int pdu_len, + int tpdu_len, + int expect_more, + ofono_sms_submit_cb_t cb, + void* data) +{ + BinderSms* self = binder_sms_get_data(sms); + RadioGsmSmsMessage* msg; + GBinderWriter writer; + char* tpdu; + guint parent; + int smsc_len; + + /* sendSms(int32 serial, GsmSmsMessage message); */ + RadioRequest* req = radio_request_new2(self->g, expect_more ? + RADIO_REQ_SEND_SMS_EXPECT_MORE : RADIO_REQ_SEND_SMS, &writer, + binder_sms_submit_cb, binder_sms_cbd_free, + binder_sms_cbd_new(self, BINDER_CB(cb), data)); + + DBG("pdu_len: %d, tpdu_len: %d more: %d", pdu_len, tpdu_len, expect_more); + + msg = gbinder_writer_new0(&writer, RadioGsmSmsMessage); + + /* + * SMSC address: + * + * smsc_len == 1, then zero-length SMSC was specified but IRadio + * interface expects an empty string for default SMSC. + */ + smsc_len = pdu_len - tpdu_len; + if (smsc_len > 1) { + binder_copy_hidl_string_len(&writer, &msg->smscPdu, (char*) + pdu, smsc_len); + } else { + /* Default SMSC address */ + binder_copy_hidl_string(&writer, &msg->smscPdu, NULL); + } + + /* PDU is sent as an ASCII hex string */ + msg->pdu.len = tpdu_len * 2; + tpdu = gbinder_writer_malloc(&writer, msg->pdu.len + 1); + ofono_encode_hex(pdu + smsc_len, tpdu_len, tpdu); + msg->pdu.data.str = tpdu; + DBG_(self, "%s", tpdu); + + /* Write GsmSmsMessage and its strings */ + parent = gbinder_writer_append_buffer_object(&writer, msg, sizeof(*msg)); + binder_append_hidl_string_data(&writer, msg, smscPdu, parent); + binder_append_hidl_string_data(&writer, msg, pdu, parent); + + /* Submit the request */ + if (!radio_request_submit(req)) { + struct ofono_error err; + + cb(binder_error_failure(&err), 0, data); + } + radio_request_unref(req); +} + +static +void +binder_ack_delivery_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_ACKNOWLEDGE_LAST_INCOMING_GSM_SMS) { + if (error != RADIO_ERROR_NONE) { + ofono_error("SMS acknowledgement failed: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected acknowledgeLastIncomingGsmSms response %d", + resp); + } + } else { + ofono_error("SMS acknowledgement failed"); + } +} + +static +void +binder_ack_delivery( + BinderSms* self, + gboolean ok) +{ + GBinderWriter writer; + + /* + * acknowledgeLastIncomingGsmSms(int32 serial, bool success, + * SmsAcknowledgeFailCause cause); + */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_ACKNOWLEDGE_LAST_INCOMING_GSM_SMS, &writer, + binder_ack_delivery_cb, NULL, NULL); + + DBG_(self, "%s", ok ? "ok" : "fail"); + gbinder_writer_append_bool(&writer, ok); + gbinder_writer_append_int32(&writer, ok ? RADIO_SMS_ACK_FAIL_NONE : + RADIO_SMS_ACK_FAIL_UNSPECIFIED_ERROR); + + radio_request_set_retry(req, BINDER_SMS_ACK_RETRY_MS, + BINDER_SMS_ACK_RETRY_COUNT); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_sms_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderSms* self = user_data; + GBinderReader reader; + const guint8* pdu; + gsize len; + + /* + * newSms(RadioIndicationType, vec pdu); + * newSmsStatusReport(RadioIndicationType, vec pdu); + */ + gbinder_reader_copy(&reader, args); + pdu = gbinder_reader_read_hidl_byte_vec(&reader, &len); + if (pdu) { + const guint pdu_len = (guint) len; + const guint smsc_len = (guint) pdu[0] + 1; + + ofono_info("%s, %u bytes",(code == RADIO_IND_NEW_SMS) ? + "incoming sms" : "sms status", pdu_len); + if (pdu_len > smsc_len) { + /* The PDU starts with the SMSC address per TS 27.005 (+CMT:) */ + const guint tpdu_len = pdu_len - smsc_len; + + DBG_(self, "smsc: %s", binder_print_hex(pdu, smsc_len)); + DBG_(self, "tpdu: %s", binder_print_hex(pdu + smsc_len, tpdu_len)); + + switch (code) { + case RADIO_IND_NEW_SMS: + ofono_sms_deliver_notify(self->sms, pdu, pdu_len, tpdu_len); + binder_ack_delivery(self, TRUE); + break; + case RADIO_IND_NEW_SMS_STATUS_REPORT: + ofono_sms_status_notify(self->sms, pdu, pdu_len, tpdu_len); + binder_ack_delivery(self, TRUE); + break; + default: + binder_ack_delivery(self, FALSE); + break; + } + return; + } + } + ofono_error("Unable to parse SMS notification"); + binder_ack_delivery(self, FALSE); +} + +static +void +binder_sms_delete_on_sim_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_DELETE_SMS_ON_SIM) { + if (error == RADIO_ERROR_NONE) { + ofono_info("sms deleted from sim"); + } else { + ofono_warn("Failed to delete sms from sim: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected deleteSmsOnSim response %d", resp); + } + } else { + ofono_error("Deleting SMS from SIM failed"); + } +} + +static +void +binder_sms_on_sim_cb( + int ok, + int total_length, + int record, + const unsigned char* sdata, + int length, + void* userdata) +{ + BinderSmsSimReadData* cbd = userdata; + BinderSms* self = cbd->self; + + /* + * EFsms contains status byte followed by SMS PDU (including + * SMSC address) per TS 31.103 4.2.12 + */ + if (ok) { + if (length > 1) { + /* Skip status byte */ + guint pdu_len = length - 1; + const guint8* pdu = sdata + 1; + const guint smsc_len = (guint) pdu[0] + 1; + + if (pdu_len > smsc_len) { + RadioRequest* req; + GBinderWriter writer; + const guint tpdu_len = pdu_len - smsc_len; + + ofono_info("read sms from sim, %u bytes", pdu_len); + DBG_(self, "smsc: %s", binder_print_hex(pdu, smsc_len)); + DBG_(self, "tpdu: %s", binder_print_hex(pdu + smsc_len, + tpdu_len)); + + ofono_sms_deliver_notify(self->sms, pdu, pdu_len, tpdu_len); + + /* deleteSmsOnSim(int32 serial, int32 index); */ + DBG_(self, "deleting record: %d", cbd->record); + req = radio_request_new2(self->g, + RADIO_REQ_DELETE_SMS_ON_SIM, &writer, + binder_sms_delete_on_sim_cb, NULL, NULL); + gbinder_writer_append_int32(&writer, cbd->record); + radio_request_submit(req); + radio_request_unref(req); + } else { + ofono_warn("Failed to extract PDU from EFsms"); + } + } else { + ofono_warn("Empty EFsms?"); + } + } else { + ofono_error("Cannot read SMS from SIM"); + } + + binder_sms_sim_read_data_free(cbd); +} + +static +void +binder_sms_on_sim( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderSms* self = user_data; + GBinderReader reader; + gint32 rec; + + ofono_info("new sms on sim"); + + /* newSmsOnSim(RadioIndicationType type, int32 recordNumber); */ + GASSERT(code == RADIO_IND_NEW_SMS_ON_SIM); + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &rec)) { + DBG("rec %d", rec); + if (self->sim_context) { + ofono_sim_read_record(self->sim_context, SIM_EFSMS_FILEID, + OFONO_SIM_FILE_STRUCTURE_FIXED, rec, EFSMS_LENGTH, + sim_path, sizeof(sim_path), binder_sms_on_sim_cb, + binder_sms_sim_read_data_new(self, rec)); + } + } +} + +static +gboolean binder_sms_register( + gpointer user_data) +{ + BinderSms* self = user_data; + RadioClient* client = self->g->client; + + DBG(""); + GASSERT(self->register_id); + self->register_id = 0; + + ofono_sms_register(self->sms); + + /* Register event handlers */ + self->event_id[SMS_EVENT_NEW_SMS] = + radio_client_add_indication_handler(client, + RADIO_IND_NEW_SMS, binder_sms_notify, self); + self->event_id[SMS_EVENT_NEW_STATUS_REPORT] = + radio_client_add_indication_handler(client, + RADIO_IND_NEW_SMS_STATUS_REPORT, binder_sms_notify, self); + self->event_id[SMS_EVENT_NEW_SMS_ON_SIM] = + radio_client_add_indication_handler(client, + RADIO_IND_NEW_SMS_ON_SIM, binder_sms_on_sim, self); + + return G_SOURCE_REMOVE; +} + +static +int +binder_sms_probe( + struct ofono_sms* sms, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderSms* self = g_new0(BinderSms, 1); + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + DBG_(self, ""); + + self->watch = ofono_watch_new(binder_modem_get_path(modem)); + self->sim_context = ofono_sim_context_create(self->watch->sim); + self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ + self->sms = sms; + + GASSERT(self->sim_context); + self->register_id = g_idle_add(binder_sms_register, self); + ofono_sms_set_data(sms, self); + return 0; +} + +static +void +binder_sms_remove( + struct ofono_sms* sms) +{ + BinderSms* self = binder_sms_get_data(sms); + + DBG_(self, ""); + + if (self->sim_context) { + ofono_sim_context_free(self->sim_context); + } + + if (self->register_id) { + g_source_remove(self->register_id); + } + + radio_client_remove_all_handlers(self->g->client, self->event_id); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + g_free(self->log_prefix); + g_free(self); + + ofono_sms_set_data(sms, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_sms_driver binder_sms_driver = { + .name = BINDER_DRIVER, + .probe = binder_sms_probe, + .remove = binder_sms_remove, + .sca_query = binder_sms_sca_query, + .sca_set = binder_sms_sca_set, + .submit = binder_sms_submit +}; + +void +binder_sms_init() +{ + ofono_sms_driver_register(&binder_sms_driver); +} + +void +binder_sms_cleanup() +{ + ofono_sms_driver_unregister(&binder_sms_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_sms.h b/src/binder_sms.h new file mode 100644 index 0000000..fc5fb4f --- /dev/null +++ b/src/binder_sms.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_SMS_H +#define BINDER_SMS_H + +#include "binder_types.h" + +void +binder_sms_init(void) + BINDER_INTERNAL; + +void +binder_sms_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_SMS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_stk.c b/src/binder_stk.c new file mode 100644 index 0000000..3c676d8 --- /dev/null +++ b/src/binder_stk.c @@ -0,0 +1,425 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_stk.h" +#include "binder_util.h" + +#include +#include + +#include +#include +#include + +#include +#include + +enum binder_stk_events { + STK_EVENT_PROACTIVE_COMMAND, + STK_EVENT_SESSION_END, + STK_EVENT_NOTIFY, + STK_EVENT_COUNT +}; + +typedef struct binder_stk { + struct ofono_stk* stk; + char* log_prefix; + RadioRequestGroup* g; + gulong event_id[STK_EVENT_COUNT]; + guint register_id; +} BinderStk; + +typedef struct binder_stk_cbd { + BinderStk* self; + union _ofono_stk_cb { + ofono_stk_envelope_cb_t envelope; + ofono_stk_generic_cb_t generic; + BinderCallback ptr; + } cb; + gpointer data; +} BinderStkCbData; + +#define DBG_(cd,fmt,args...) DBG("%s" fmt, (cd)->log_prefix, ##args) + +static inline BinderStk* binder_stk_get_data(struct ofono_stk* stk) + { return ofono_stk_get_data(stk); } + +static +BinderStkCbData* +binder_stk_cbd_new( + BinderStk* self, + BinderCallback cb, + void* data) +{ + BinderStkCbData* cbd = g_slice_new0(BinderStkCbData); + + cbd->self = self; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_stk_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderStkCbData, cbd); +} + +static void binder_stk_envelope_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderStkCbData* cbd = user_data; + ofono_stk_envelope_cb_t cb = cbd->cb.envelope; + struct ofono_error err; + + DBG_(cbd->self, ""); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_ENVELOPE) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), NULL, 0, cbd->data); + return; + } else { + ofono_warn("Error sending envelope: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected sendEnvelope response %d", resp); + } + } + cb(binder_error_failure(&err), NULL, 0, cbd->data); +} + +static +void +binder_stk_envelope( + struct ofono_stk* stk, + int length, + const unsigned char* cmd, + ofono_stk_envelope_cb_t cb, + void* data) +{ + BinderStk* self = binder_stk_get_data(stk); + char* hex = binder_encode_hex(cmd, length); + GBinderWriter writer; + + /* sendEnvelope(int32 serial, string command); */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SEND_ENVELOPE, &writer, + binder_stk_envelope_cb, binder_stk_cbd_free, + binder_stk_cbd_new(self, BINDER_CB(cb), data)); + + DBG("envelope %s", hex); + gbinder_writer_add_cleanup(&writer, g_free, hex); + gbinder_writer_append_hidl_string(&writer, hex); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_stk_terminal_response_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderStkCbData* cbd = user_data; + ofono_stk_generic_cb_t cb = cbd->cb.generic; + struct ofono_error err; + + DBG_(cbd->self, ""); + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_TERMINAL_RESPONSE_TO_SIM) { + if (error == RADIO_ERROR_NONE) { + cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_warn("Error sending terminal response: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected sendTerminalResponseToSim response %d", + resp); + } + } + cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_stk_terminal_response( + struct ofono_stk* stk, + int length, + const unsigned char* resp, + ofono_stk_generic_cb_t cb, + void* data) +{ + BinderStk* self = binder_stk_get_data(stk); + char* hex = binder_encode_hex(resp, length); + GBinderWriter writer; + + /* sendTerminalResponseToSim(int32 serial, string commandResponse); */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SEND_TERMINAL_RESPONSE_TO_SIM, &writer, + binder_stk_terminal_response_cb, binder_stk_cbd_free, + binder_stk_cbd_new(self, BINDER_CB(cb), data)); + + DBG_(self, "terminal response: %s", hex); + gbinder_writer_add_cleanup(&writer, g_free, hex); + gbinder_writer_append_hidl_string(&writer, hex); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_stk_user_confirmation( + struct ofono_stk* stk, + ofono_bool_t confirm) +{ + BinderStk* self = binder_stk_get_data(stk); + GBinderWriter writer; + + /* handleStkCallSetupRequestFromSim(int32 serial, bool accept); */ + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_HANDLE_STK_CALL_SETUP_REQUEST_FROM_SIM, &writer, + NULL, NULL, NULL); + + DBG_(self, "%d", confirm); + gbinder_writer_append_bool(&writer, confirm); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_stk_proactive_command( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderStk* self = user_data; + GBinderReader reader; + const char* pcmd; + void* pdu; + guint len; + + /* + * stkProactiveCommand(RadioIndicationType, string cmd); + * + * cmd - SAT/USAT proactive represented as byte array starting with + * command tag. + * + * Refer to ETSI TS 102.223 section 9.4 for command types. + */ + gbinder_reader_copy(&reader, args); + pcmd = gbinder_reader_read_hidl_string_c(&reader); + pdu = binder_decode_hex(pcmd, -1, &len); + if (pdu) { + DBG_(self, "pcmd: %s", pcmd); + ofono_stk_proactive_command_notify(self->stk, len, pdu); + g_free(pdu); + } else { + ofono_warn("Failed to parse STK command %s", pcmd); + } +} + +static +void +binder_stk_event_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderStk* self = user_data; + GBinderReader reader; + const char* pcmd; + void* pdu; + guint len; + + /* + * stkEventNotify(RadioIndicationType, string cmd); + * + * cmd - SAT/USAT commands or responses sent by ME to SIM or commands + * handled by ME, represented as byte array starting with first byte + * of response data for command tag. + * + * Refer to ETSI TS 102.223 section 9.4 for command types. + */ + gbinder_reader_copy(&reader, args); + pcmd = gbinder_reader_read_hidl_string_c(&reader); + pdu = binder_decode_hex(pcmd, -1, &len); + if (pdu) { + DBG_(self, "pcmd: %s", pcmd); + ofono_stk_proactive_command_handled_notify(self->stk, len, pdu); + g_free(pdu); + } else { + ofono_warn("Failed to parse STK event %s", pcmd); + } +} + +static +void +binder_stk_session_end_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderStk* self = user_data; + + DBG_(self, ""); + /* stkSessionEnd(RadioIndicationType); */ + ofono_stk_proactive_session_end_notify(self->stk); +} + +static +void +binder_stk_agent_ready( + struct ofono_stk* stk) +{ + BinderStk* self = binder_stk_get_data(stk); + RadioClient* client = self->g->client; + + DBG_(self, ""); + + if (!self->event_id[STK_EVENT_PROACTIVE_COMMAND]) { + DBG_(self, "Subscribing for notifications"); + self->event_id[STK_EVENT_PROACTIVE_COMMAND] = + radio_client_add_indication_handler(client, + RADIO_IND_STK_PROACTIVE_COMMAND, + binder_stk_proactive_command, self); + + GASSERT(!self->event_id[STK_EVENT_SESSION_END]); + self->event_id[STK_EVENT_SESSION_END] = + radio_client_add_indication_handler(client, + RADIO_IND_STK_SESSION_END, + binder_stk_session_end_notify, self); + + GASSERT(!self->event_id[STK_EVENT_NOTIFY]); + self->event_id[STK_EVENT_NOTIFY] = + radio_client_add_indication_handler(client, + RADIO_IND_STK_EVENT_NOTIFY, + binder_stk_event_notify, self); + + /* reportStkServiceIsRunning(int32 serial); */ + binder_submit_request(self->g, RADIO_REQ_REPORT_STK_SERVICE_IS_RUNNING); + } +} + +static +gboolean binder_stk_register( + gpointer user_data) +{ + BinderStk* self = user_data; + + DBG(""); + GASSERT(self->register_id); + self->register_id = 0; + + ofono_stk_register(self->stk); + + return G_SOURCE_REMOVE; +} + +static +int +binder_stk_probe( + struct ofono_stk* stk, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderStk* self = g_new0(BinderStk, 1); + + self->stk = stk; + self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_stk_register, self); + + DBG_(self, ""); + ofono_stk_set_data(stk, self); + return 0; +} + +static +void +binder_stk_remove( + struct ofono_stk* stk) +{ + BinderStk* self = binder_stk_get_data(stk); + + DBG_(self, ""); + + if (self->register_id) { + g_source_remove(self->register_id); + } + + radio_client_remove_all_handlers(self->g->client, self->event_id); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + g_free(self->log_prefix); + g_free(self); + + ofono_stk_set_data(stk, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_stk_driver binder_stk_driver = { + .name = BINDER_DRIVER, + .probe = binder_stk_probe, + .remove = binder_stk_remove, + .envelope = binder_stk_envelope, + .terminal_response = binder_stk_terminal_response, + .user_confirmation = binder_stk_user_confirmation, + .ready = binder_stk_agent_ready +}; + +void +binder_stk_init() +{ + ofono_stk_driver_register(&binder_stk_driver); +} + +void +binder_stk_cleanup() +{ + ofono_stk_driver_unregister(&binder_stk_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_stk.h b/src/binder_stk.h new file mode 100644 index 0000000..6dce780 --- /dev/null +++ b/src/binder_stk.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_STK_H +#define BINDER_STK_H + +#include "binder_types.h" + +void +binder_stk_init(void) + BINDER_INTERNAL; + +void +binder_stk_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_STK_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_types.h b/src/binder_types.h new file mode 100644 index 0000000..ff0d8a8 --- /dev/null +++ b/src/binder_types.h @@ -0,0 +1,107 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_TYPES_H +#define BINDER_TYPES_H + +#include +#include +#include + +typedef struct binder_data BinderData; +typedef struct binder_data_manager BinderDataManager; +typedef struct binder_devmon BinderDevmon; +typedef struct binder_logger BinderLogger; +typedef struct binder_modem BinderModem; +typedef struct binder_network BinderNetwork; +typedef struct binder_radio_caps BinderRadioCaps; +typedef struct binder_radio_caps_manager BinderRadioCapsManager; +typedef struct binder_radio_caps_request BinderRadioCapsRequest; +typedef struct binder_radio BinderRadio; +typedef struct binder_sim_card BinderSimCard; +typedef struct binder_sim_settings BinderSimSettings; + +typedef enum binder_feature_mask { + BINDER_FEATURE_NONE = 0, + BINDER_FEATURE_CBS = 0x0001, /* cbs */ + BINDER_FEATURE_DATA = 0x0002, /* data */ + BINDER_FEATURE_NETREG = 0x0004, /* netreg */ + BINDER_FEATURE_PHONEBOOK = 0x0008, /* pb */ + BINDER_FEATURE_RADIO_SETTINGS = 0x0010, /* rat */ + BINDER_FEATURE_SIM_AUTH = 0x0020, /* auth */ + BINDER_FEATURE_SMS = 0x0040, /* sms */ + BINDER_FEATURE_STK = 0x0080, /* stk */ + BINDER_FEATURE_USSD = 0x0100, /* ussd */ + BINDER_FEATURE_VOICE = 0x0200, /* voice */ + BINDER_FEATURE_ALL = 0x03ff /* all */ +} BINDER_FEATURE_MASK; + +typedef struct binder_slot_config { + guint slot; + int cell_info_interval_short_ms; + int cell_info_interval_long_ms; + int network_mode_timeout_ms; + int network_selection_timeout_ms; + int signal_strength_dbm_weak; + int signal_strength_dbm_strong; + enum ofono_radio_access_mode techs; + RADIO_PREF_NET_TYPE lte_network_mode; + RADIO_PREF_NET_TYPE umts_network_mode; + BINDER_FEATURE_MASK features; + gboolean query_available_band_mode; + gboolean empty_pin_query; + gboolean radio_power_cycle; + gboolean confirm_radio_power_on; + gboolean replace_strange_oper; + gboolean force_gsm_when_radio_off; + gboolean use_data_profiles; + RADIO_DATA_PROFILE_ID mms_data_profile_id; + GUtilInts* local_hangup_reasons; + GUtilInts* remote_hangup_reasons; +} BinderSlotConfig; + +#define BINDER_DRIVER "binder" +#define BINDER_INTERNAL G_GNUC_INTERNAL + +#define BINDER_RETRY_SECS (2) +#define BINDER_RETRY_MS (BINDER_RETRY_SECS * 1000) + +typedef void (*BinderCallback)(void); +#define BINDER_CB(f) ((BinderCallback)(f)) + +#define OFONO_RADIO_ACCESS_MODE_ALL (\ + OFONO_RADIO_ACCESS_MODE_GSM |\ + OFONO_RADIO_ACCESS_MODE_UMTS |\ + OFONO_RADIO_ACCESS_MODE_LTE) + +#define OFONO_RADIO_ACCESS_MODE_NONE \ + ((enum ofono_radio_access_mode) 0) + +/* Masks */ +#define OFONO_RADIO_ACCESS_GSM_MASK OFONO_RADIO_ACCESS_MODE_GSM +#define OFONO_RADIO_ACCESS_UMTS_MASK \ + (OFONO_RADIO_ACCESS_MODE_UMTS | (OFONO_RADIO_ACCESS_MODE_UMTS - 1)) +#define OFONO_RADIO_ACCESS_LTE_MASK \ + (OFONO_RADIO_ACCESS_MODE_LTE | (OFONO_RADIO_ACCESS_MODE_LTE - 1)) + +#endif /* BINDER_TYPES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_ussd.c b/src/binder_ussd.c new file mode 100644 index 0000000..4da837b --- /dev/null +++ b/src/binder_ussd.c @@ -0,0 +1,360 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_ussd.h" +#include "binder_util.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +#define SEC (1000) +#define USSD_REQUEST_TIMEOUT_MS (30 * SEC) +#define USSD_CANCEL_TIMEOUT_MS (20 * SEC) + +typedef struct binder_ussd { + struct ofono_ussd *ussd; + char* log_prefix; + RadioClient* client; + RadioRequest* send_req; + RadioRequest* cancel_req; + gulong event_id; + guint register_id; +} BinderUssd; + +typedef struct binder_ussd_cbd { + BinderUssd* self; + ofono_ussd_cb_t cb; + gpointer data; +} BinderUssdCbData; + +#define DBG_(cd,fmt,args...) DBG("%s" fmt, (cd)->log_prefix, ##args) + +static inline BinderUssd* binder_ussd_get_data(struct ofono_ussd *ussd) + { return ofono_ussd_get_data(ussd); } + +static +BinderUssdCbData* +binder_ussd_cbd_new( + BinderUssd* self, + ofono_ussd_cb_t cb, + void* data) +{ + BinderUssdCbData* cbd = g_slice_new(struct binder_ussd_cbd); + + cbd->self = self; + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static +void +binder_ussd_cbd_free( + gpointer cbd) +{ + g_slice_free(BinderUssdCbData, cbd); +} + +static +void +binder_ussd_cancel_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderUssdCbData* cbd = user_data; + BinderUssd* self = cbd->self; + struct ofono_error err; + + GASSERT(self->cancel_req == req); + radio_request_unref(self->cancel_req); + self->cancel_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_CANCEL_PENDING_USSD) { + if (error != RADIO_ERROR_NONE) { + ofono_warn("Error cancelling USSD: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected cancelPendingUssd response %d", resp); + } + } else { + ofono_warn("Failed to cancel USSD"); + } + + /* + * Always report sucessful completion, otherwise ofono may get + * stuck in the USSD_STATE_ACTIVE state. + */ + cbd->cb(binder_error_ok(&err), cbd->data); +} + +static +void +binder_ussd_send_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderUssdCbData* cbd = user_data; + BinderUssd* self = cbd->self; + struct ofono_error err; + + GASSERT(self->send_req == req); + radio_request_unref(self->send_req); + self->send_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (resp == RADIO_RESP_SEND_USSD) { + if (error == RADIO_ERROR_NONE) { + cbd->cb(binder_error_ok(&err), cbd->data); + return; + } else { + ofono_warn("Error sending USSD: %s", + binder_radio_error_string(error)); + } + } else { + ofono_error("Unexpected sendUssd response %d", resp); + } + } else { + ofono_warn("Failed to send USSD"); + } + cbd->cb(binder_error_failure(&err), cbd->data); +} + +static +void +binder_ussd_request( + struct ofono_ussd* ussd, + int dcs, + const unsigned char* pdu, + int len, + ofono_ussd_cb_t cb, + void* data) +{ + BinderUssd* self = binder_ussd_get_data(ussd); + char* text = ofono_ussd_decode(dcs, pdu, len); + struct ofono_error err; + + DBG_(self, "ussd request: %s", text); + GASSERT(!self->send_req); + radio_request_drop(self->send_req); + self->send_req = NULL; + + if (text) { + /* sendUssd(int32 serial, string ussd); */ + GBinderWriter writer; + RadioRequest* req = radio_request_new(self->client, + RADIO_REQ_SEND_USSD, &writer, + binder_ussd_send_cb, binder_ussd_cbd_free, + binder_ussd_cbd_new(self, cb, data)); + + /* USSD text will be deallocated together with the request */ + gbinder_writer_append_hidl_string(&writer, text); + gbinder_writer_add_cleanup(&writer, (GDestroyNotify) + ofono_ussd_decode_free, text); + + radio_request_set_timeout(req, USSD_REQUEST_TIMEOUT_MS); + if (radio_request_submit(req)) { + /* Request was successfully submitted, keep the ref */ + self->send_req = req; + return; + } + + radio_request_unref(req); + } + + /* Something went wrong */ + cb(binder_error_failure(&err), data); +} + +static +void +binder_ussd_cancel( + struct ofono_ussd* ussd, + ofono_ussd_cb_t cb, + void* data) +{ + BinderUssd* self = binder_ussd_get_data(ussd); + + ofono_info("sending ussd cancel"); + GASSERT(!self->cancel_req); + radio_request_drop(self->cancel_req); + + /* cancelPendingUssd(int32 serial); */ + self->cancel_req = radio_request_new(self->client, + RADIO_REQ_CANCEL_PENDING_USSD, NULL, + binder_ussd_cancel_cb, binder_ussd_cbd_free, + binder_ussd_cbd_new(self, cb, data)); + + radio_request_set_timeout(self->cancel_req, USSD_CANCEL_TIMEOUT_MS); + if (!radio_request_submit(self->cancel_req)) { + struct ofono_error err; + + radio_request_unref(self->cancel_req); + self->cancel_req = NULL; + cb(binder_error_failure(&err), data); + } +} + +static +void +binder_ussd_notify( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderUssd* self = user_data; + GBinderReader reader; + gint32 type = 0; + + ofono_info("ussd received"); + + /* onUssd(RadioIndicationType, UssdModeType modeType, string msg); */ + GASSERT(code == RADIO_IND_ON_USSD); + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_int32(&reader, &type)) { + const char* msg = gbinder_reader_read_hidl_string_c(&reader); + + if (msg && msg[0]) { + const int len = (int) strlen(msg); + + DBG_(self, "ussd length %d", len); + + /* + * Message is freed by core if dcs is 0xff, we have to + * duplicate it. + */ + ofono_ussd_notify(self->ussd, type, 0xff, + gutil_memdup(msg, len + 1), len); + } else { + ofono_ussd_notify(self->ussd, type, 0, NULL, 0); + } + } +} + +static +gboolean +binder_ussd_register( + gpointer user_data) +{ + BinderUssd* self = user_data; + + DBG_(self, ""); + GASSERT(self->register_id); + self->register_id = 0; + + ofono_ussd_register(self->ussd); + + /* Register for USSD events */ + self->event_id = radio_client_add_indication_handler(self->client, + RADIO_IND_ON_USSD, binder_ussd_notify, self); + + return G_SOURCE_REMOVE; +} + +static +int +binder_ussd_probe( + struct ofono_ussd* ussd, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderUssd* self = g_new0(BinderUssd, 1); + + self->ussd = ussd; + self->client = radio_client_ref(modem->client); + self->log_prefix = binder_dup_prefix(modem->log_prefix); + self->register_id = g_idle_add(binder_ussd_register, self); + + DBG_(self, ""); + ofono_ussd_set_data(ussd, self); + return 0; +} + +static +void +binder_ussd_remove( + struct ofono_ussd* ussd) +{ + BinderUssd* self = binder_ussd_get_data(ussd); + + DBG_(self, ""); + + if (self->register_id) { + g_source_remove(self->register_id); + } + + radio_request_drop(self->send_req); + radio_request_drop(self->cancel_req); + radio_client_remove_handler(self->client, self->event_id); + radio_client_unref(self->client); + + g_free(self->log_prefix); + g_free(self); + + ofono_ussd_set_data(ussd, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_ussd_driver binder_ussd_driver = { + .name = BINDER_DRIVER, + .probe = binder_ussd_probe, + .remove = binder_ussd_remove, + .request = binder_ussd_request, + .cancel = binder_ussd_cancel +}; + +void +binder_ussd_init() +{ + ofono_ussd_driver_register(&binder_ussd_driver); +} + +void +binder_ussd_cleanup() +{ + ofono_ussd_driver_unregister(&binder_ussd_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_ussd.h b/src/binder_ussd.h new file mode 100644 index 0000000..164a98a --- /dev/null +++ b/src/binder_ussd.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_USSD_H +#define BINDER_USSD_H + +#include "binder_types.h" + +void +binder_ussd_init(void) + BINDER_INTERNAL; + +void +binder_ussd_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_USSD_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_util.c b/src/binder_util.c new file mode 100644 index 0000000..b77d0b7 --- /dev/null +++ b/src/binder_util.c @@ -0,0 +1,839 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_util.h" + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +static GUtilIdlePool* binder_util_pool = NULL; +static const char binder_empty_str[] = ""; +static const char PROTO_IP_STR[] = "IP"; +static const char PROTO_IPV6_STR[] = "IPV6"; +static const char PROTO_IPV4V6_STR[] = "IPV4V6"; + +#define RADIO_ACCESS_FAMILY_GSM \ + (RAF_GSM|RAF_GPRS|RAF_EDGE) +#define RADIO_ACCESS_FAMILY_UMTS \ + (RAF_UMTS|RAF_HSDPA|RAF_HSUPA|RAF_HSPA|RAF_HSPAP|RAF_TD_SCDMA|RAF_EHRPD) +#define RADIO_ACCESS_FAMILY_LTE \ + (RAF_LTE|RAF_LTE_CA|RAF_EHRPD) + +static +const char* +binder_pool_string( + char* str) +{ + GUtilIdlePool* pool = gutil_idle_pool_get(&binder_util_pool); + + gutil_idle_pool_add(pool, str, g_free); + return str; +} + +RADIO_ACCESS_NETWORK +binder_radio_access_network_for_tech( + RADIO_TECH tech) +{ + switch (tech) { + case RADIO_TECH_GPRS: + case RADIO_TECH_EDGE: + case RADIO_TECH_GSM: + return RADIO_ACCESS_NETWORK_GERAN; + case RADIO_TECH_UMTS: + case RADIO_TECH_HSDPA: + case RADIO_TECH_HSPAP: + case RADIO_TECH_HSUPA: + case RADIO_TECH_HSPA: + case RADIO_TECH_TD_SCDMA: + return RADIO_ACCESS_NETWORK_UTRAN; + case RADIO_TECH_IS95A: + case RADIO_TECH_IS95B: + case RADIO_TECH_ONE_X_RTT: + case RADIO_TECH_EVDO_0: + case RADIO_TECH_EVDO_A: + case RADIO_TECH_EVDO_B: + case RADIO_TECH_EHRPD: + return RADIO_ACCESS_NETWORK_CDMA2000; + case RADIO_TECH_LTE: + case RADIO_TECH_LTE_CA: + return RADIO_ACCESS_NETWORK_EUTRAN; + case RADIO_TECH_IWLAN: + return RADIO_ACCESS_NETWORK_IWLAN; + case RADIO_TECH_UNKNOWN: + break; + } + return RADIO_ACCESS_NETWORK_UNKNOWN; +} + +RADIO_APN_TYPES +binder_radio_apn_types_for_profile( + RADIO_DATA_PROFILE_ID profile_id) +{ + switch (profile_id) { + case RADIO_DATA_PROFILE_INVALID: + return RADIO_APN_TYPE_NONE; + case RADIO_DATA_PROFILE_IMS: + return RADIO_APN_TYPE_IMS; + case RADIO_DATA_PROFILE_CBS: + return RADIO_APN_TYPE_CBS; + case RADIO_DATA_PROFILE_FOTA: + return RADIO_APN_TYPE_FOTA; + case RADIO_DATA_PROFILE_DEFAULT: + return (RADIO_APN_TYPE_DEFAULT | + RADIO_APN_TYPE_SUPL | + RADIO_APN_TYPE_IA); + default: + /* + * There's no standard profile id for MMS, OEM-specific profile ids + * are used for that. + */ + return RADIO_APN_TYPE_MMS; + } +} + +RADIO_PDP_PROTOCOL_TYPE +binder_proto_from_ofono_proto( + enum ofono_gprs_proto proto) +{ + switch (proto) { + case OFONO_GPRS_PROTO_IP: + return RADIO_PDP_PROTOCOL_IP; + case OFONO_GPRS_PROTO_IPV6: + return RADIO_PDP_PROTOCOL_IPV6; + case OFONO_GPRS_PROTO_IPV4V6: + return RADIO_PDP_PROTOCOL_IPV4V6; + } + return RADIO_PDP_PROTOCOL_UNKNOWN; +} + +const char* +binder_proto_str_from_ofono_proto( + enum ofono_gprs_proto proto) +{ + switch (proto) { + case OFONO_GPRS_PROTO_IP: + return PROTO_IP_STR; + case OFONO_GPRS_PROTO_IPV6: + return PROTO_IPV6_STR; + case OFONO_GPRS_PROTO_IPV4V6: + return PROTO_IPV4V6_STR; + } + return NULL; +} + +enum ofono_gprs_proto +binder_ofono_proto_from_proto_type( + RADIO_PDP_PROTOCOL_TYPE type) +{ + switch (type) { + case RADIO_PDP_PROTOCOL_IP: + return OFONO_GPRS_PROTO_IP; + case RADIO_PDP_PROTOCOL_IPV6: + return OFONO_GPRS_PROTO_IPV6; + case RADIO_PDP_PROTOCOL_IPV4V6: + return OFONO_GPRS_PROTO_IPV4V6; + case RADIO_PDP_PROTOCOL_PPP: + case RADIO_PDP_PROTOCOL_NON_IP: + case RADIO_PDP_PROTOCOL_UNSTRUCTURED: + case RADIO_PDP_PROTOCOL_UNKNOWN: + break; + } + /* Need OFONO_GPRS_PROTO_UNKNOWN? */ + return OFONO_GPRS_PROTO_IP; +} + +enum ofono_gprs_proto +binder_ofono_proto_from_proto_str( + const char* type) +{ + if (type) { + if (!g_ascii_strcasecmp(type, PROTO_IP_STR)) { + return OFONO_GPRS_PROTO_IP; + } else if (!g_ascii_strcasecmp(type, PROTO_IPV6_STR)) { + return OFONO_GPRS_PROTO_IPV6; + } else if (!g_ascii_strcasecmp(type, PROTO_IPV4V6_STR)) { + return OFONO_GPRS_PROTO_IPV4V6; + } + } + /* Need OFONO_GPRS_PROTO_UNKNOWN? */ + return OFONO_GPRS_PROTO_IP; +} + +RADIO_APN_AUTH_TYPE +binder_radio_auth_from_ofono_method( + enum ofono_gprs_auth_method auth) +{ + switch (auth) { + case OFONO_GPRS_AUTH_METHOD_NONE: + return RADIO_APN_AUTH_NONE; + case OFONO_GPRS_AUTH_METHOD_CHAP: + return RADIO_APN_AUTH_CHAP; + case OFONO_GPRS_AUTH_METHOD_PAP: + return RADIO_APN_AUTH_PAP; + case OFONO_GPRS_AUTH_METHOD_ANY: + /* Use default */ + break; + } + /* Default */ + return RADIO_APN_AUTH_PAP_CHAP; +} + +RADIO_PREF_NET_TYPE +binder_pref_from_raf( + RADIO_ACCESS_FAMILY raf) +{ + if (raf & RADIO_ACCESS_FAMILY_GSM) { + if (raf & RADIO_ACCESS_FAMILY_UMTS) { + if (raf & RADIO_ACCESS_FAMILY_LTE) { + return RADIO_PREF_NET_LTE_GSM_WCDMA; + } + return RADIO_PREF_NET_GSM_WCDMA; + } + return RADIO_PREF_NET_GSM_ONLY; + } else if (raf & RADIO_ACCESS_FAMILY_UMTS) { + if (raf & RADIO_ACCESS_FAMILY_LTE) { + return RADIO_PREF_NET_LTE_WCDMA; + } + return RADIO_PREF_NET_WCDMA; + } else if (raf & RADIO_ACCESS_FAMILY_LTE) { + return RADIO_PREF_NET_LTE_ONLY; + } else { + return RADIO_PREF_NET_INVALID; + } +} + +static +int +binder_pref_mask( + RADIO_PREF_NET_TYPE pref, + int none, + int gsm_mask, + int umts_mask, + int lte_mask) +{ + switch (pref) { + case RADIO_PREF_NET_GSM_ONLY: + return gsm_mask; + + case RADIO_PREF_NET_WCDMA: + case RADIO_PREF_NET_TD_SCDMA_ONLY: + case RADIO_PREF_NET_TD_SCDMA_WCDMA: + return umts_mask; + + case RADIO_PREF_NET_LTE_ONLY: + case RADIO_PREF_NET_LTE_CDMA_EVDO: + return lte_mask; + + case RADIO_PREF_NET_TD_SCDMA_GSM: + case RADIO_PREF_NET_GSM_WCDMA: + case RADIO_PREF_NET_GSM_WCDMA_AUTO: + case RADIO_PREF_NET_GSM_WCDMA_CDMA_EVDO_AUTO: + case RADIO_PREF_NET_TD_SCDMA_GSM_WCDMA: + case RADIO_PREF_NET_TD_SCDMA_GSM_WCDMA_CDMA_EVDO_AUTO: + return gsm_mask | umts_mask; + + case RADIO_PREF_NET_LTE_WCDMA: + case RADIO_PREF_NET_TD_SCDMA_LTE: + case RADIO_PREF_NET_TD_SCDMA_WCDMA_LTE: + return umts_mask | lte_mask; + + case RADIO_PREF_NET_LTE_GSM_WCDMA: + case RADIO_PREF_NET_TD_SCDMA_GSM_LTE: + case RADIO_PREF_NET_LTE_CMDA_EVDO_GSM_WCDMA: + case RADIO_PREF_NET_TD_SCDMA_GSM_WCDMA_LTE: + case RADIO_PREF_NET_TD_SCDMA_LTE_CDMA_EVDO_GSM_WCDMA: + return gsm_mask | umts_mask | lte_mask; + + case RADIO_PREF_NET_CDMA_ONLY: + case RADIO_PREF_NET_EVDO_ONLY: + case RADIO_PREF_NET_CDMA_EVDO_AUTO: + case RADIO_PREF_NET_INVALID: + return none; + } + + DBG("unexpected pref mode %d", pref); + return none; +} + +RADIO_ACCESS_FAMILY +binder_raf_from_pref( + RADIO_PREF_NET_TYPE pref) +{ + return binder_pref_mask(pref, RAF_NONE, + RADIO_ACCESS_FAMILY_GSM, RADIO_ACCESS_FAMILY_UMTS, + RADIO_ACCESS_FAMILY_LTE); +} + +enum ofono_radio_access_mode +binder_access_modes_from_pref( + RADIO_PREF_NET_TYPE pref) +{ + return binder_pref_mask(pref, OFONO_RADIO_ACCESS_MODE_NONE, + OFONO_RADIO_ACCESS_MODE_GSM, OFONO_RADIO_ACCESS_MODE_UMTS, + OFONO_RADIO_ACCESS_MODE_LTE); +} + +enum ofono_radio_access_mode +binder_access_modes_from_raf( + RADIO_ACCESS_FAMILY raf) +{ + + if (raf == RAF_UNKNOWN) { + return OFONO_RADIO_ACCESS_MODE_ALL; + } else { + enum ofono_radio_access_mode modes = OFONO_RADIO_ACCESS_MODE_NONE; + + if (raf & RADIO_ACCESS_FAMILY_GSM) { + modes |= OFONO_RADIO_ACCESS_MODE_GSM; + } + if (raf & RADIO_ACCESS_FAMILY_UMTS) { + modes |= OFONO_RADIO_ACCESS_MODE_UMTS; + } + if (raf & RADIO_ACCESS_FAMILY_LTE) { + modes |= OFONO_RADIO_ACCESS_MODE_LTE; + } + return modes; + } +} + +enum ofono_radio_access_mode +binder_access_modes_up_to( + enum ofono_radio_access_mode mode) +{ + /* Make sure only one bit is set in max_mode */ + enum ofono_radio_access_mode max_mode = ofono_radio_access_max_mode(mode); + + /* Treat ANY (zero) as ALL, otherwise set all lower bits */ + return (max_mode == OFONO_RADIO_ACCESS_MODE_ANY) ? + OFONO_RADIO_ACCESS_MODE_ALL : (max_mode | (max_mode - 1)); +} + +enum ofono_access_technology +binder_access_tech_from_radio_tech( + RADIO_TECH radio_tech) +{ + switch (radio_tech) { + case RADIO_TECH_UNKNOWN: + return OFONO_ACCESS_TECHNOLOGY_NONE; + case RADIO_TECH_GPRS: + case RADIO_TECH_GSM: + return OFONO_ACCESS_TECHNOLOGY_GSM; + case RADIO_TECH_EDGE: + return OFONO_ACCESS_TECHNOLOGY_GSM_EGPRS; + case RADIO_TECH_UMTS: + return OFONO_ACCESS_TECHNOLOGY_UTRAN; + case RADIO_TECH_HSDPA: + return OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA; + case RADIO_TECH_HSUPA: + return OFONO_ACCESS_TECHNOLOGY_UTRAN_HSUPA; + case RADIO_TECH_HSPA: + case RADIO_TECH_HSPAP: + return OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; + case RADIO_TECH_LTE: + case RADIO_TECH_LTE_CA: + return OFONO_ACCESS_TECHNOLOGY_EUTRAN; + case RADIO_TECH_IWLAN: + case RADIO_TECH_IS95B: + case RADIO_TECH_ONE_X_RTT: + case RADIO_TECH_EVDO_0: + case RADIO_TECH_EVDO_A: + case RADIO_TECH_EVDO_B: + case RADIO_TECH_EHRPD: + case RADIO_TECH_TD_SCDMA: + case RADIO_TECH_IS95A: + break; + } + + DBG("Unknown radio tech %d", radio_tech); + return OFONO_ACCESS_TECHNOLOGY_NONE; +} + +const char* +binder_ofono_access_technology_string( + enum ofono_access_technology act) +{ + switch (act) { + case OFONO_ACCESS_TECHNOLOGY_NONE: + return "none"; + case OFONO_ACCESS_TECHNOLOGY_GSM: + return "gsm"; + case OFONO_ACCESS_TECHNOLOGY_GSM_COMPACT: + return "gsmc"; + case OFONO_ACCESS_TECHNOLOGY_UTRAN: + return "utran"; + case OFONO_ACCESS_TECHNOLOGY_GSM_EGPRS: + return "egprs"; + case OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA: + return "hsdpa"; + case OFONO_ACCESS_TECHNOLOGY_UTRAN_HSUPA: + return "hsupa"; + case OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA: + return "utran"; + case OFONO_ACCESS_TECHNOLOGY_EUTRAN: + return "eutran"; + } + return binder_pool_string(g_strdup_printf("%d (?)", act)); +} + +const char* +binder_radio_op_status_string( + RADIO_OP_STATUS status) +{ + switch (status) { + case RADIO_OP_AVAILABLE: + return "available"; + case RADIO_OP_CURRENT: + return "current"; + case RADIO_OP_FORBIDDEN: + return "forbidden"; + case RADIO_OP_STATUS_UNKNOWN: + break; + } + return "unknown"; +} + +const char* +binder_radio_state_string( + RADIO_STATE state) +{ +#define RADIO_STATE_(name) case RADIO_STATE_##name: return #name + switch (state) { + RADIO_STATE_(OFF); + RADIO_STATE_(UNAVAILABLE); + RADIO_STATE_(ON); + } + return binder_pool_string(g_strdup_printf("%d (?)", state)); +} + +const char* +binder_radio_error_string( + RADIO_ERROR error) +{ + switch (error) { +#define RADIO_ERROR_STR_(name) case RADIO_ERROR_##name: return #name + RADIO_ERROR_STR_(NONE); + RADIO_ERROR_STR_(RADIO_NOT_AVAILABLE); + RADIO_ERROR_STR_(GENERIC_FAILURE); + RADIO_ERROR_STR_(PASSWORD_INCORRECT); + RADIO_ERROR_STR_(SIM_PIN2); + RADIO_ERROR_STR_(SIM_PUK2); + RADIO_ERROR_STR_(REQUEST_NOT_SUPPORTED); + RADIO_ERROR_STR_(CANCELLED); + RADIO_ERROR_STR_(OP_NOT_ALLOWED_DURING_VOICE_CALL); + RADIO_ERROR_STR_(OP_NOT_ALLOWED_BEFORE_REG_TO_NW); + RADIO_ERROR_STR_(SMS_SEND_FAIL_RETRY); + RADIO_ERROR_STR_(SIM_ABSENT); + RADIO_ERROR_STR_(SUBSCRIPTION_NOT_AVAILABLE); + RADIO_ERROR_STR_(MODE_NOT_SUPPORTED); + RADIO_ERROR_STR_(FDN_CHECK_FAILURE); + RADIO_ERROR_STR_(ILLEGAL_SIM_OR_ME); + RADIO_ERROR_STR_(MISSING_RESOURCE); + RADIO_ERROR_STR_(NO_SUCH_ELEMENT); + RADIO_ERROR_STR_(DIAL_MODIFIED_TO_USSD); + RADIO_ERROR_STR_(DIAL_MODIFIED_TO_SS); + RADIO_ERROR_STR_(DIAL_MODIFIED_TO_DIAL); + RADIO_ERROR_STR_(USSD_MODIFIED_TO_DIAL); + RADIO_ERROR_STR_(USSD_MODIFIED_TO_SS); + RADIO_ERROR_STR_(USSD_MODIFIED_TO_USSD); + RADIO_ERROR_STR_(SS_MODIFIED_TO_DIAL); + RADIO_ERROR_STR_(SS_MODIFIED_TO_USSD); + RADIO_ERROR_STR_(SUBSCRIPTION_NOT_SUPPORTED); + RADIO_ERROR_STR_(SS_MODIFIED_TO_SS); + RADIO_ERROR_STR_(LCE_NOT_SUPPORTED); + RADIO_ERROR_STR_(NO_MEMORY); + RADIO_ERROR_STR_(INTERNAL_ERR); + RADIO_ERROR_STR_(SYSTEM_ERR); + RADIO_ERROR_STR_(MODEM_ERR); + RADIO_ERROR_STR_(INVALID_STATE); + RADIO_ERROR_STR_(NO_RESOURCES); + RADIO_ERROR_STR_(SIM_ERR); + RADIO_ERROR_STR_(INVALID_ARGUMENTS); + RADIO_ERROR_STR_(INVALID_SIM_STATE); + RADIO_ERROR_STR_(INVALID_MODEM_STATE); + RADIO_ERROR_STR_(INVALID_CALL_ID); + RADIO_ERROR_STR_(NO_SMS_TO_ACK); + RADIO_ERROR_STR_(NETWORK_ERR); + RADIO_ERROR_STR_(REQUEST_RATE_LIMITED); + RADIO_ERROR_STR_(SIM_BUSY); + RADIO_ERROR_STR_(SIM_FULL); + RADIO_ERROR_STR_(NETWORK_REJECT); + RADIO_ERROR_STR_(OPERATION_NOT_ALLOWED); + RADIO_ERROR_STR_(EMPTY_RECORD); + RADIO_ERROR_STR_(INVALID_SMS_FORMAT); + RADIO_ERROR_STR_(ENCODING_ERR); + RADIO_ERROR_STR_(INVALID_SMSC_ADDRESS); + RADIO_ERROR_STR_(NO_SUCH_ENTRY); + RADIO_ERROR_STR_(NETWORK_NOT_READY); + RADIO_ERROR_STR_(NOT_PROVISIONED); + RADIO_ERROR_STR_(NO_SUBSCRIPTION); + RADIO_ERROR_STR_(NO_NETWORK_FOUND); + RADIO_ERROR_STR_(DEVICE_IN_USE); + RADIO_ERROR_STR_(ABORTED); + RADIO_ERROR_STR_(INVALID_RESPONSE); + RADIO_ERROR_STR_(OEM_ERROR_1); + RADIO_ERROR_STR_(OEM_ERROR_2); + RADIO_ERROR_STR_(OEM_ERROR_3); + RADIO_ERROR_STR_(OEM_ERROR_4); + RADIO_ERROR_STR_(OEM_ERROR_5); + RADIO_ERROR_STR_(OEM_ERROR_6); + RADIO_ERROR_STR_(OEM_ERROR_7); + RADIO_ERROR_STR_(OEM_ERROR_8); + RADIO_ERROR_STR_(OEM_ERROR_9); + RADIO_ERROR_STR_(OEM_ERROR_10); + RADIO_ERROR_STR_(OEM_ERROR_11); + RADIO_ERROR_STR_(OEM_ERROR_12); + RADIO_ERROR_STR_(OEM_ERROR_13); + RADIO_ERROR_STR_(OEM_ERROR_14); + RADIO_ERROR_STR_(OEM_ERROR_15); + RADIO_ERROR_STR_(OEM_ERROR_16); + RADIO_ERROR_STR_(OEM_ERROR_17); + RADIO_ERROR_STR_(OEM_ERROR_18); + RADIO_ERROR_STR_(OEM_ERROR_19); + RADIO_ERROR_STR_(OEM_ERROR_20); + RADIO_ERROR_STR_(OEM_ERROR_21); + RADIO_ERROR_STR_(OEM_ERROR_22); + RADIO_ERROR_STR_(OEM_ERROR_23); + RADIO_ERROR_STR_(OEM_ERROR_24); + RADIO_ERROR_STR_(OEM_ERROR_25); + } + + return binder_pool_string(g_strdup_printf("%d", error)); +} + +enum ofono_access_technology +binder_parse_tech( + const char* stech, + RADIO_TECH* radio_tech) +{ + int rt = RADIO_TECH_UNKNOWN; + const enum ofono_access_technology at = gutil_parse_int(stech, 0, &rt) ? + binder_access_tech_from_radio_tech(rt) : OFONO_ACCESS_TECHNOLOGY_NONE; + + if (radio_tech) { + *radio_tech = rt; + } + return at; +} + +gboolean +binder_parse_mcc_mnc( + const char* str, + struct ofono_network_operator* op) +{ + if (str) { + int i; + const char* ptr = str; + + /* Three digit country code */ + for (i = 0; + i < OFONO_MAX_MCC_LENGTH && *ptr && g_ascii_isdigit(*ptr); + i++) { + op->mcc[i] = *ptr++; + } + op->mcc[i] = 0; + + if (i == OFONO_MAX_MCC_LENGTH) { + /* Usually 2 but sometimes 3 digit network code */ + for (i = 0; + i < OFONO_MAX_MNC_LENGTH && *ptr && g_ascii_isdigit(*ptr); + i++) { + op->mnc[i] = *ptr++; + } + op->mnc[i] = 0; + + if (i > 0) { + /* + * Sometimes MCC/MNC are followed by + and what looks + * like the technology code. This seems to be modem + * specific. + */ + if (*ptr == '+') { + const enum ofono_access_technology at = + binder_parse_tech(ptr + 1, NULL); + + if (at != OFONO_ACCESS_TECHNOLOGY_NONE) { + op->tech = at; + } + } + return TRUE; + } + } + } + return FALSE; +} + +char* +binder_encode_hex( + const void* in, + guint size) +{ + char *out = g_new(char, size * 2 + 1); + + ofono_encode_hex(in, size, out); + return out; +} + +void* +binder_decode_hex( + const char* hex, + int len, + guint* out_size) +{ + void* out = NULL; + guint size = 0; + + if (hex) { + if (len < 0) { + len = (int) strlen(hex); + } + if (len > 0 && !(len & 1)) { + size = len/2; + out = g_malloc(size); + if (!gutil_hex2bin(hex, len, out)) { + g_free(out); + out = NULL; + size = 0; + } + } + } + if (out_size) { + *out_size = size; + } + return out; +} + +const char* +binder_print_strv( + char** strv, + const char* sep) +{ + if (!strv) { + return NULL; + } else if (!strv[0]) { + return binder_empty_str; + } else { + GUtilIdlePool* pool = gutil_idle_pool_get(&binder_util_pool); + char* str = g_strjoinv(sep, strv); + + gutil_idle_pool_add(pool, str, g_free); + return str; + } +} + +const char* +binder_print_hex( + const void* data, + gsize size) +{ + if (data && size) { + const guint8* bytes = data; + GUtilIdlePool* pool = gutil_idle_pool_get(&binder_util_pool); + char* str = g_new(char, size * 2 + 1); + char* ptr = str; + gsize i; + + for (i = 0; i < size; i++) { + static const char hex[] = "0123456789abcdef"; + const guint8 b = bytes[i]; + + *ptr++ = hex[(b >> 4) & 0xf]; + *ptr++ = hex[b & 0xf]; + } + *ptr++ = 0; + gutil_idle_pool_add(pool, str, g_free); + return str; + } + return binder_empty_str; +} + +gboolean +binder_submit_request( + RadioRequestGroup* g, + RADIO_REQ code) +{ + RadioRequest* req = radio_request_new2(g, code, NULL, NULL, NULL, NULL); + gboolean ok = radio_request_submit(req); + + radio_request_unref(req); + return ok; +} + +gboolean +binder_submit_request2( + RadioRequestGroup* g, + RADIO_REQ code, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + RadioRequest* req = radio_request_new2(g, code, NULL, complete, destroy, + user_data); + gboolean ok = radio_request_submit(req); + + radio_request_unref(req); + return ok; +} + +const char* +binder_read_hidl_string( + const GBinderReader* args) +{ + GBinderReader reader; + + /* Read a single string arg */ + gbinder_reader_copy(&reader, args); + return gbinder_reader_read_hidl_string_c(&reader); +} + +gboolean +binder_read_int32( + const GBinderReader* args, + gint32* value) +{ + GBinderReader reader; + + /* Read a single int32 arg */ + gbinder_reader_copy(&reader, args); + return gbinder_reader_read_int32(&reader, value); +} + +const void* +binder_read_hidl_struct1( + const GBinderReader* args, + gsize size) +{ + GBinderReader reader; + + /* Read a single struct */ + gbinder_reader_copy(&reader, args); + return gbinder_reader_read_hidl_struct1(&reader, size); +} + +char** +binder_strv_from_hidl_string_vec( + const GBinderHidlVec* vec) +{ + if (vec) { + const GBinderHidlString* strings = vec->data.ptr; + char** out = g_new(char*, vec->count); + char** ptr = out; + guint i; + + for (i = 0; i < vec->count; i++, ptr++) { + const char* str = strings[i].data.str; + + *ptr = str ? gutil_memdup(str, strings[i].len + 1) : g_strdup(""); + } + *ptr = NULL; + return out; + } + return NULL; +} + +guint +binder_append_vec_with_data( + GBinderWriter* writer, + const void* data, + guint elemsize, + guint count, + const GBinderParent* parent) +{ + GBinderHidlVec* vec = gbinder_writer_new0(writer, GBinderHidlVec); + GBinderParent p; + + vec->data.ptr = data; + vec->count = count; + + p.index = gbinder_writer_append_buffer_object(writer, vec, sizeof(*vec)); + p.offset = GBINDER_HIDL_VEC_BUFFER_OFFSET; + + /* Return the index of the data buffer */ + return gbinder_writer_append_buffer_object_with_parent(writer, + data, count * elemsize, &p); +} + +static +void +binder_copy_hidl_string_impl( + GBinderWriter* writer, + GBinderHidlString* dest, + const char* src, + gssize len) +{ + dest->owns_buffer = TRUE; + if (len > 0) { + /* GBinderWriter takes ownership of the string contents */ + dest->len = (guint32) len; + dest->data.str = gbinder_writer_memdup(writer, src, len + 1); + } else { + /* Replace NULL strings with empty strings */ + dest->data.str = binder_empty_str; + dest->len = 0; + } +} + +void +binder_copy_hidl_string( + GBinderWriter* writer, + GBinderHidlString* dest, + const char* src) +{ + binder_copy_hidl_string_impl(writer, dest, src, src ? strlen(src) : 0); +} + +void +binder_copy_hidl_string_len( + GBinderWriter* writer, + GBinderHidlString* dest, + const char* src, + gssize len) +{ + binder_copy_hidl_string_impl(writer, dest, src, (src && len < 0) ? + strlen(src) : 0); +} + +void +binder_append_hidl_string_with_parent( + GBinderWriter* writer, + const GBinderHidlString* str, + guint32 index, + guint32 offset) +{ + GBinderParent parent; + + parent.index = index; + parent.offset = offset; + + /* Strings are NULL-terminated, hence len + 1 */ + gbinder_writer_append_buffer_object_with_parent(writer, str->data.str, + str->len + 1, &parent); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_util.h b/src/binder_util.h new file mode 100644 index 0000000..b36bc45 --- /dev/null +++ b/src/binder_util.h @@ -0,0 +1,256 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_UTIL_H +#define BINDER_UTIL_H + +#include "binder_types.h" + +#include +#include + +#include + +struct ofono_network_operator; + +#define binder_dup_prefix(prefix) \ + (((prefix) && *(prefix)) ? (g_str_has_suffix(prefix, " ") ? \ + g_strdup(prefix) : g_strconcat(prefix, " ", NULL)) : g_strdup("")) + +#define binder_error_init_ok(err) \ + ((err)->error = 0, (err)->type = OFONO_ERROR_TYPE_NO_ERROR) +#define binder_error_init_failure(err) \ + ((err)->error = 0, (err)->type = OFONO_ERROR_TYPE_FAILURE) +#define binder_error_init_sim_error(err,sw1,sw2) \ + ((err)->error = (((guint)(sw1)) << 8) | (sw2), \ + (err)->type = OFONO_ERROR_TYPE_SIM) + +#define binder_error_ok(err) \ + (binder_error_init_ok(err), err) +#define binder_error_failure(err) \ + (binder_error_init_failure(err), err) +#define binder_error_sim(err,sw1,sw2) \ + (binder_error_init_sim_error(err,sw1,sw2), err) + +RADIO_ACCESS_NETWORK +binder_radio_access_network_for_tech( + RADIO_TECH tech) + BINDER_INTERNAL; + +RADIO_APN_TYPES +binder_radio_apn_types_for_profile( + RADIO_DATA_PROFILE_ID profile_id) + BINDER_INTERNAL; + +RADIO_PDP_PROTOCOL_TYPE +binder_proto_from_ofono_proto( + enum ofono_gprs_proto proto) + BINDER_INTERNAL; + +const char* +binder_proto_str_from_ofono_proto( + enum ofono_gprs_proto proto) + BINDER_INTERNAL; + +enum ofono_gprs_proto +binder_ofono_proto_from_proto_type( + RADIO_PDP_PROTOCOL_TYPE type) + BINDER_INTERNAL; + +enum ofono_gprs_proto +binder_ofono_proto_from_proto_str( + const char* type) + BINDER_INTERNAL; + +RADIO_APN_AUTH_TYPE +binder_radio_auth_from_ofono_method( + enum ofono_gprs_auth_method auth) + BINDER_INTERNAL; + +RADIO_PREF_NET_TYPE +binder_pref_from_raf( + RADIO_ACCESS_FAMILY raf) + BINDER_INTERNAL; + +RADIO_ACCESS_FAMILY +binder_raf_from_pref( + RADIO_PREF_NET_TYPE pref) + BINDER_INTERNAL; + +enum ofono_radio_access_mode +binder_access_modes_from_pref( + RADIO_PREF_NET_TYPE pref) + BINDER_INTERNAL; + +enum ofono_radio_access_mode +binder_access_modes_from_raf( + RADIO_ACCESS_FAMILY raf) + BINDER_INTERNAL; + +enum ofono_radio_access_mode +binder_access_modes_up_to( + enum ofono_radio_access_mode mode) + BINDER_INTERNAL; + +enum ofono_access_technology +binder_access_tech_from_radio_tech( + RADIO_TECH radio_tech) + BINDER_INTERNAL; + +const char* +binder_ofono_access_technology_string( + enum ofono_access_technology tech) + BINDER_INTERNAL; + +const char* +binder_radio_op_status_string( + RADIO_OP_STATUS status) + BINDER_INTERNAL; + +const char* +binder_radio_state_string( + RADIO_STATE state) + BINDER_INTERNAL; + +const char* +binder_radio_error_string( + RADIO_ERROR error) + BINDER_INTERNAL; + +enum ofono_access_technology +binder_parse_tech( + const char* stech, + RADIO_TECH* radio_tech) + BINDER_INTERNAL; + +gboolean +binder_parse_mcc_mnc( + const char* str, + struct ofono_network_operator* op) + BINDER_INTERNAL; + +char* +binder_encode_hex( + const void* in, + guint size) + BINDER_INTERNAL; + +void* +binder_decode_hex( + const char* hex, + int len, + guint* out_size) + BINDER_INTERNAL; + +const char* +binder_print_strv( + char** strv, + const char* sep) + BINDER_INTERNAL; + +const char* +binder_print_hex( + const void* data, + gsize size) + BINDER_INTERNAL; + +gboolean +binder_submit_request( + RadioRequestGroup* g, + RADIO_REQ code) + BINDER_INTERNAL; + +gboolean +binder_submit_request2( + RadioRequestGroup* g, + RADIO_REQ code, + RadioRequestCompleteFunc complete, + GDestroyNotify destroy, + void* user_data) + BINDER_INTERNAL; + +const char* +binder_read_hidl_string( + const GBinderReader* args) + BINDER_INTERNAL; + +gboolean +binder_read_int32( + const GBinderReader* args, + gint32* value) + BINDER_INTERNAL; + +const void* +binder_read_hidl_struct1( + const GBinderReader* reader, + gsize size); + +#define binder_read_hidl_struct(reader,type) \ + ((const type*)binder_read_hidl_struct1(reader, sizeof(type))) + +char** +binder_strv_from_hidl_string_vec( + const GBinderHidlVec* vec) + BINDER_INTERNAL; + +guint +binder_append_vec_with_data( + GBinderWriter* writer, + const void* data, + guint elemsize, + guint count, + const GBinderParent* parent) + BINDER_INTERNAL; + +void +binder_copy_hidl_string( + GBinderWriter* writer, + GBinderHidlString* dest, + const char* src) + BINDER_INTERNAL; + +void +binder_copy_hidl_string_len( + GBinderWriter* writer, + GBinderHidlString* dest, + const char* src, + gssize len) + BINDER_INTERNAL; + +void +binder_append_hidl_string_with_parent( + GBinderWriter* writer, + const GBinderHidlString* str, + guint32 index, + guint32 offset) + BINDER_INTERNAL; + +#define binder_append_hidl_string(writer,str) \ + gbinder_writer_append_hidl_string_copy(writer,str) +#define binder_append_hidl_string_data(writer,ptr,field,index) \ + binder_append_hidl_string_data2(writer,ptr,field,index,0) +#define binder_append_hidl_string_data2(writer,ptr,field,index,off) \ + binder_append_hidl_string_with_parent(writer, &ptr->field, index, \ + (off) + ((guint8*)(&ptr->field) - (guint8*)ptr)) + +#endif /* BINDER_UTIL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_voicecall.c b/src/binder_voicecall.c new file mode 100644 index 0000000..57af4ec --- /dev/null +++ b/src/binder_voicecall.c @@ -0,0 +1,1199 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_log.h" +#include "binder_modem.h" +#include "binder_util.h" +#include "binder_voicecall.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define VOICECALL_BLOCK_TIMEOUT_MS (5*1000) + +enum binder_voicecall_events { + VOICECALL_EVENT_CALL_STATE_CHANGED, + VOICECALL_EVENT_SUPP_SVC_NOTIFICATION, + VOICECALL_EVENT_RINGBACK_TONE, + VOICECALL_EVENT_ECCLIST_CHANGED, /* IRadio@1.4 specific */ + VOICECALL_EVENT_COUNT +}; + +typedef struct binder_voicecall { + struct ofono_voicecall* vc; + char* log_prefix; + GSList* calls; + RadioRequestGroup* g; + ofono_voicecall_cb_t cb; + void* data; + GUtilIntArray* local_release_ids; + GUtilIdleQueue* idleq; + GUtilRing* dtmf_queue; + GUtilInts* local_hangup_reasons; + GUtilInts* remote_hangup_reasons; + RadioRequest* send_dtmf_req; + RadioRequest* clcc_poll_req; + gulong event_id[VOICECALL_EVENT_COUNT]; + gulong supp_svc_notification_id; + gulong ringback_tone_event_id; +} BinderVoiceCall; + +typedef struct binder_voicecall_request_data { + int ref_count; + int pending_call_count; + int success; + struct ofono_voicecall* vc; + ofono_voicecall_cb_t cb; + gpointer data; +} BinderVoiceCallCbData; + +typedef struct binder_voicecall_lastcause_data { + BinderVoiceCall* self; + guint cid; +} BinderVoiceCallLastCauseData; + +#define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) +#define DBG__(vc,fmt,args...) \ + DBG("%s" fmt, binder_voicecall_get_data(vc)->log_prefix, ##args) + +static +void +binder_voicecall_send_one_dtmf( + BinderVoiceCall* self); + +static +void +binder_voicecall_clear_dtmf_queue( + BinderVoiceCall* self); + +static inline BinderVoiceCall* +binder_voicecall_get_data(struct ofono_voicecall* vc) + { return ofono_voicecall_get_data(vc); } + +static +BinderVoiceCallCbData* +binder_voicecall_request_data_new( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCallCbData* req = g_slice_new0(BinderVoiceCallCbData); + + req->ref_count = 1; + req->vc = vc; + req->cb = cb; + req->data = data; + return req; +} + +static +void +binder_voicecall_request_data_unref( + BinderVoiceCallCbData* req) +{ + if (!--req->ref_count) { + gutil_slice_free(req); + } +} + +static +void +binder_voicecall_request_data_free( + gpointer data) +{ + binder_voicecall_request_data_unref(data); +} + +static +void +binder_voicecall_clear_dtmf_queue( + BinderVoiceCall* self) +{ + gutil_ring_clear(self->dtmf_queue); + radio_request_drop(self->send_dtmf_req); + self->send_dtmf_req = NULL; +} + +static +gint +binder_voicecall_compare( + gconstpointer a, + gconstpointer b) +{ + const struct ofono_call* ca = a; + const struct ofono_call* cb = b; + + return (ca->id < cb->id) ? -1 : + (ca->id > cb->id) ? 1 : 0; +} + +static +void +binder_voicecall_ofono_call_free( + gpointer data) +{ + g_slice_free(struct ofono_call, data); +} + +static +struct ofono_call* +binder_voicecall_ofono_call_new( + const RadioCall* rc) +{ + struct ofono_call* call = g_slice_new0(struct ofono_call); + + ofono_call_init(call); + + call->status = rc->state; + call->id = rc->index; + call->direction = rc->isMT ? + OFONO_CALL_DIRECTION_MOBILE_TERMINATED : + OFONO_CALL_DIRECTION_MOBILE_ORIGINATED; + call->type = rc->isVoice ? + OFONO_CALL_MODE_VOICE : + OFONO_CALL_MODE_UNKNOWN; + if (rc->name.len) { + g_strlcpy(call->name, rc->name.data.str, OFONO_MAX_CALLER_NAME_LENGTH); + } + call->phone_number.type = rc->toa; + if (rc->number.len) { + call->clip_validity = OFONO_CLIP_VALIDITY_VALID; + g_strlcpy(call->phone_number.number, rc->number.data.str, + OFONO_MAX_PHONE_NUMBER_LENGTH); + } else { + call->clip_validity = OFONO_CLIP_VALIDITY_NOT_AVAILABLE; + } + + DBG("[id=%d,status=%d,type=%d,number=%s,name=%s]", call->id, + call->status, call->type, call->phone_number.number, call->name); + + return call; +} + +static +enum ofono_call_status +binder_voicecall_status_with_id( + struct ofono_voicecall *vc, + unsigned int id) +{ + struct ofono_call *call = ofono_voicecall_find_call(vc, id); + + /* Valid call statuses have value >= 0 */ + return call ? call->status : -1; +} + +static +enum ofono_disconnect_reason +binder_voicecall_map_cause( + BinderVoiceCall* self, + guint cid, + RADIO_LAST_CALL_FAIL_CAUSE last_cause) +{ + if (gutil_ints_contains(self->remote_hangup_reasons, last_cause)) { + DBG_(self, "hangup cause %d => remote hangup", last_cause); + return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + } else if (gutil_ints_contains(self->local_hangup_reasons, last_cause)) { + DBG("hangup cause %d => local hangup", last_cause); + return OFONO_DISCONNECT_REASON_LOCAL_HANGUP; + } else { + enum ofono_call_status call_status; + + switch (last_cause) { + case RADIO_LAST_CALL_FAIL_UNOBTAINABLE_NUMBER: + case RADIO_LAST_CALL_FAIL_NORMAL: + case RADIO_LAST_CALL_FAIL_BUSY: + case RADIO_LAST_CALL_FAIL_NO_ROUTE_TO_DESTINATION: + case RADIO_LAST_CALL_FAIL_CHANNEL_UNACCEPTABLE: + case RADIO_LAST_CALL_FAIL_OPERATOR_DETERMINED_BARRING: + case RADIO_LAST_CALL_FAIL_NO_USER_RESPONDING: + case RADIO_LAST_CALL_FAIL_NO_ANSWER_FROM_USER: + case RADIO_LAST_CALL_FAIL_CALL_REJECTED: + case RADIO_LAST_CALL_FAIL_NUMBER_CHANGED: + case RADIO_LAST_CALL_FAIL_PREEMPTION: + case RADIO_LAST_CALL_FAIL_DESTINATION_OUT_OF_ORDER: + case RADIO_LAST_CALL_FAIL_INVALID_NUMBER_FORMAT: + case RADIO_LAST_CALL_FAIL_FACILITY_REJECTED: + return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + + case RADIO_LAST_CALL_FAIL_NORMAL_UNSPECIFIED: + call_status = binder_voicecall_status_with_id(self->vc, cid); + if (call_status == OFONO_CALL_STATUS_ACTIVE || + call_status == OFONO_CALL_STATUS_HELD || + call_status == OFONO_CALL_STATUS_DIALING || + call_status == OFONO_CALL_STATUS_ALERTING) { + return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + } else if (call_status == OFONO_CALL_STATUS_INCOMING) { + return OFONO_DISCONNECT_REASON_LOCAL_HANGUP; + } + break; + + case RADIO_LAST_CALL_FAIL_ERROR_UNSPECIFIED: + call_status = binder_voicecall_status_with_id(self->vc, cid); + if (call_status == OFONO_CALL_STATUS_DIALING || + call_status == OFONO_CALL_STATUS_ALERTING || + call_status == OFONO_CALL_STATUS_INCOMING) { + return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + } + break; + + default: + break; + } + } + return OFONO_DISCONNECT_REASON_ERROR; +} + +void +binder_voicecall_lastcause_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCallLastCauseData* data = user_data; + BinderVoiceCall* self = data->self; + struct ofono_voicecall* vc = self->vc; + const guint cid = data->cid; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_RESP_GET_LAST_CALL_FAIL_CAUSE) { + GBinderReader reader; + const RadioLastCallFailCauseInfo* info; + + /* + * getLastCallFailCauseResponse(RadioResponseInfo, + * LastCallFailCauseInfo failCauseinfo); + */ + gbinder_reader_copy(&reader, args); + info = gbinder_reader_read_hidl_struct(&reader, + RadioLastCallFailCauseInfo); + if (info) { + enum ofono_disconnect_reason reason = + binder_voicecall_map_cause(self, cid, info->causeCode); + + ofono_info("Call %d ended with cause %d -> ofono reason %d", + cid, info->causeCode, reason); + ofono_voicecall_disconnected(vc, cid, reason, NULL); + return; + } + } else { + ofono_error("Unexpected getLastCallFailCause response %d", + resp); + } + } else { + ofono_warn("Failed to retrive last call fail cause: %s", + binder_radio_error_string(error)); + } + } + + ofono_info("Call %d ended with unknown reason", cid); + ofono_voicecall_disconnected(vc, cid, OFONO_DISCONNECT_REASON_ERROR, NULL); +} + +static +void +binder_voicecall_clcc_poll_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + struct ofono_voicecall* vc = self->vc; + struct ofono_error err; + GSList* list = NULL; + GSList* n; + GSList* o; + + GASSERT(self->clcc_poll_req == req); + radio_request_unref(self->clcc_poll_req); + self->clcc_poll_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + GBinderReader reader; + gsize i, count = 0; + + /* getCurrentCallsResponse(RadioResponseInfo, vec calls); */ + gbinder_reader_copy(&reader, args); + if (resp == RADIO_RESP_GET_CURRENT_CALLS) { + const RadioCall* calls = + gbinder_reader_read_hidl_type_vec(&reader, + RadioCall, &count); + + if (calls) { + /* Build sorted list */ + for (i = 0; i < count; i++) { + list = g_slist_insert_sorted(list, + binder_voicecall_ofono_call_new(calls + i), + binder_voicecall_compare); + } + } + } else if (resp == RADIO_RESP_GET_CURRENT_CALLS_1_2) { + const RadioCall_1_2* calls = + gbinder_reader_read_hidl_type_vec(&reader, + RadioCall_1_2, &count); + + if (calls) { + /* Build sorted list */ + for (i = 0; i < count; i++) { + list = g_slist_insert_sorted(list, + binder_voicecall_ofono_call_new(&calls[i].base), + binder_voicecall_compare); + } + } + } else { + ofono_error("Unexpected getCurrentCalls response %d", resp); + } + } else { + /* + * Only RADIO_ERROR_NONE and RADIO_ERROR_RADIO_NOT_AVAILABLE + * are expected here, all other errors are filtered out by + * binder_voicecall_clcc_retry() + */ + GASSERT(error == RADIO_ERROR_RADIO_NOT_AVAILABLE); + } + } + + /* Note: the lists are sorted by id */ + + n = list; + o = self->calls; + + while (n || o) { + struct ofono_call* nc = n ? n->data : NULL; + struct ofono_call* oc = o ? o->data : NULL; + + if (oc && (!nc || (nc->id > oc->id))) { + /* old call is gone */ + if (gutil_int_array_remove_all_fast(self->local_release_ids, + oc->id)) { + ofono_voicecall_disconnected(vc, oc->id, + OFONO_DISCONNECT_REASON_LOCAL_HANGUP, NULL); + } else { + /* Get disconnect cause before informing oFono core */ + BinderVoiceCallLastCauseData* reqdata = + g_new0(BinderVoiceCallLastCauseData, 1); + /* getLastCallFailCause(int32 serial); */ + RadioRequest* req2 = radio_request_new2(self->g, + RADIO_REQ_GET_LAST_CALL_FAIL_CAUSE, NULL, + binder_voicecall_lastcause_cb, g_free, reqdata); + + reqdata->self = self; + reqdata->cid = oc->id; + radio_request_submit(req2); + radio_request_unref(req2); + } + + binder_voicecall_clear_dtmf_queue(self); + o = o->next; + + } else if (nc && (!oc || (nc->id < oc->id))) { + /* new call, signal it */ + if (nc->type == OFONO_CALL_MODE_VOICE) { + ofono_voicecall_notify(vc, nc); + if (self->cb) { + ofono_voicecall_cb_t cb = self->cb; + void* cbdata = self->data; + + self->cb = NULL; + self->data = NULL; + cb(binder_error_ok(&err), cbdata); + } + } + + n = n->next; + + } else { + /* Both old and new call exist */ + if (memcmp(nc, oc, sizeof(*nc))) { + ofono_voicecall_notify(vc, nc); + } + n = n->next; + o = o->next; + } + } + + g_slist_free_full(self->calls, binder_voicecall_ofono_call_free); + self->calls = list; +} + +static +gboolean +binder_voicecall_clcc_retry( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + void* user_data) +{ + if (status == RADIO_TX_STATUS_OK) { + switch (error) { + case RADIO_ERROR_NONE: + case RADIO_ERROR_RADIO_NOT_AVAILABLE: + return FALSE; + default: + return TRUE; + } + } + return FALSE; +} + +static +void +binder_voicecall_clcc_poll( + BinderVoiceCall* self) +{ + if (!self->clcc_poll_req) { + /* getCurrentCalls(int32 serial); */ + RadioRequest* req = radio_request_new(self->g->client, + RADIO_REQ_GET_CURRENT_CALLS, NULL, + binder_voicecall_clcc_poll_cb, NULL, self); + + radio_request_set_retry(req, BINDER_RETRY_MS, -1); + radio_request_set_retry_func(req, binder_voicecall_clcc_retry); + if (radio_request_submit(req)) { + self->clcc_poll_req = req; + } else { + radio_request_unref(req); + } + } +} + +static +void +binder_voicecall_request_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCallCbData* data = user_data; + BinderVoiceCall* self = binder_voicecall_get_data(data->vc); + + binder_voicecall_clcc_poll(self); + + /* + * The ofono API call is considered successful if at least one + * associated request succeeds. + */ + if (status == RADIO_TX_STATUS_OK && error == RADIO_ERROR_NONE) { + data->success++; + } + + /* + * Only invoke the callback if this is the last request associated + * with this ofono api call (pending call count becomes zero). + */ + GASSERT(data->pending_call_count > 0); + if (!--data->pending_call_count && data->cb) { + struct ofono_error error; + + if (data->success) { + binder_error_init_ok(&error); + } else { + binder_error_init_failure(&error); + } + + data->cb(&error, data->data); + } +} + +static +void +binder_voicecall_request( + struct ofono_voicecall* vc, + RADIO_REQ code, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = + binder_voicecall_request_data_new(vc, cb, data); + RadioRequest* req = radio_request_new2(self->g, code, NULL, + binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + + if (radio_request_submit(req)) { + cbd->pending_call_count++; + } else { + binder_voicecall_request_data_unref(cbd); + } + radio_request_unref(req); +} + +static +void +binder_voicecall_dial_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_RESP_DIAL) { + if (self->cb) { + /* + * CLCC will update the oFono call list with + * proper ids if it's not done yet. + */ + binder_voicecall_clcc_poll(self); + return; + } + } else { + ofono_error("Unexpected dial response %d", resp); + } + } else { + ofono_error("call failed: %s", binder_radio_error_string(error)); + } + } + + /* + * Even though this dial request may have already been completed + * successfully by binder_voicecall_clcc_poll_cb, RADIO_REQ_DIAL + * may still fail. + */ + if (self->cb) { + struct ofono_error err; + ofono_voicecall_cb_t cb = self->cb; + void* cbdata = self->data; + + self->cb = NULL; + self->data = NULL; + cb(binder_error_failure(&err), cbdata); + } +} + +static +void +binder_voicecall_dial( + struct ofono_voicecall* vc, + const struct ofono_phone_number* ph, + enum ofono_clir_option clir, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + char phbuf[OFONO_PHONE_NUMBER_BUFFER_SIZE]; + const char* phstr = ofono_phone_number_to_string(ph, phbuf); + GBinderParent parent; + RadioDial* dialInfo; + + /* dial(int32 serial, Dial dialInfo) */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_DIAL, &writer, + binder_voicecall_dial_cb, NULL, self); + + ofono_info("dialing \"%s\"", phstr); + DBG_(self, "%s,%d,0", phstr, clir); + GASSERT(!self->cb); + self->cb = cb; + self->data = data; + + /* Prepare the Dial structure */ + dialInfo = gbinder_writer_new0(&writer, RadioDial); + dialInfo->clir = clir; + binder_copy_hidl_string(&writer, &dialInfo->address, phstr); + + /* Write the parent structure */ + parent.index = gbinder_writer_append_buffer_object(&writer, dialInfo, + sizeof(*dialInfo)); + + /* Write the string data */ + binder_append_hidl_string_data(&writer, dialInfo, address, parent.index); + + /* UUS information is empty but we still need to write a buffer */ + parent.offset = G_STRUCT_OFFSET(RadioDial, uusInfo.data.ptr); + gbinder_writer_append_buffer_object_with_parent(&writer, NULL, 0, &parent); + + /* Submit the request */ + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_voicecall_submit_hangup_req( + struct ofono_voicecall* vc, + guint cid, + BinderVoiceCallCbData* cbd) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + + /* hangup(int32 serial, int32 index) */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_HANGUP, &writer, + binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + + gbinder_writer_append_int32(&writer, cid); + + /* Append the call id to the list of calls being released locally */ + GASSERT(!gutil_int_array_contains(self->local_release_ids, cid)); + gutil_int_array_append(self->local_release_ids, cid); + + /* binder_voicecall_request_data_free will unref the request data */ + if (radio_request_submit(req)) { + cbd->ref_count++; + cbd->pending_call_count++; + } + radio_request_unref(req); +} + +static +void +binder_voicecall_hangup( + struct ofono_voicecall* vc, + gboolean (*filter)(struct ofono_call* call), + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = NULL; + GSList *l; + + /* + * Here the idea is that we submit (potentially) multiple + * hangup requests to BINDER and invoke the callback after + * the last request has completed (pending call count + * becomes zero). + */ + for (l = self->calls; l; l = l->next) { + struct ofono_call* call = l->data; + + if (!filter || filter(call)) { + if (!cbd) { + cbd = binder_voicecall_request_data_new(vc, cb, data); + } + + /* Send request to BINDER */ + DBG("Hanging up call with id %d", call->id); + binder_voicecall_submit_hangup_req(vc, call->id, cbd); + } else { + DBG("Skipping call with id %d", call->id); + } + } + + if (cbd) { + /* Release our reference (if any) */ + binder_voicecall_request_data_unref(cbd); + } else { + /* No requests were submitted */ + struct ofono_error err; + + cb(binder_error_ok(&err), data); + } +} + +static +gboolean +binder_voicecall_hangup_active_filter( + struct ofono_call *call) +{ + switch (call->status) { + case OFONO_CALL_STATUS_ACTIVE: + case OFONO_CALL_STATUS_DIALING: + case OFONO_CALL_STATUS_ALERTING: + case OFONO_CALL_STATUS_INCOMING: + return TRUE; + case OFONO_CALL_STATUS_HELD: + case OFONO_CALL_STATUS_WAITING: + case OFONO_CALL_STATUS_DISCONNECTED: + break; + } + return FALSE; +} + +static +void +binder_voicecall_hangup_active( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + binder_voicecall_hangup(vc, + binder_voicecall_hangup_active_filter, cb, data); +} + +static +void +binder_voicecall_hangup_all( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + binder_voicecall_hangup(vc, NULL, cb, data); +} + +static +void +binder_voicecall_release_specific( + struct ofono_voicecall* vc, + int id, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCallCbData* req = + binder_voicecall_request_data_new(vc, cb, data); + + DBG("Hanging up call with id %d", id); + binder_voicecall_submit_hangup_req(vc, id, req); + binder_voicecall_request_data_unref(req); +} + +static +void +binder_voicecall_call_state_changed_event( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + + GASSERT(code == RADIO_IND_CALL_STATE_CHANGED); + + /* Just need to request the call list again */ + binder_voicecall_clcc_poll(self); +} + +static +void +binder_voicecall_supp_svc_notification_event( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + const RadioSuppSvcNotification* ntf; + GBinderReader reader; + + gbinder_reader_copy(&reader, args); + ntf = gbinder_reader_read_hidl_struct(&reader, RadioSuppSvcNotification); + if (ntf) { + DBG_(self, "MT/MO: %d, code: %d, index: %d", ntf->isMT, + ntf->code, ntf->index); + + if (ntf->isMT) { + struct ofono_phone_number phone; + + if (ntf->number.data.str) { + g_strlcpy(phone.number, ntf->number.data.str, + sizeof(phone.number)); + } else { + phone.number[0] = 0; + } + + /* MT unsolicited result code */ + ofono_voicecall_ssn_mt_notify(self->vc, 0, ntf->code, ntf->index, + &phone); + } else { + /* MO intermediate result code */ + ofono_voicecall_ssn_mo_notify(self->vc, 0, ntf->code, ntf->index); + } + } +} + +static +void +binder_voicecall_answer( + struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, "Answering current call"); + binder_voicecall_request(vc, RADIO_REQ_ACCEPT_CALL, cb, data); +} + +static +void +binder_voicecall_send_dtmf_cb( + RadioRequest* req, + RADIO_TX_STATUS status, + RADIO_RESP resp, + RADIO_ERROR error, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + + GASSERT(self->send_dtmf_req == req); + radio_request_unref(self->send_dtmf_req); + self->send_dtmf_req = NULL; + + if (status == RADIO_TX_STATUS_OK) { + if (error == RADIO_ERROR_NONE) { + if (resp == RADIO_RESP_SEND_DTMF) { + /* Send the next one */ + binder_voicecall_send_one_dtmf(self); + return; + } else { + ofono_error("Unexpected sendDtmf response %d", resp); + } + } else { + ofono_error("failed to senf dtmf: %s", + binder_radio_error_string(error)); + } + } + binder_voicecall_clear_dtmf_queue(self); +} + +static +void +binder_voicecall_send_one_dtmf( + BinderVoiceCall* self) +{ + if (!self->send_dtmf_req && gutil_ring_size(self->dtmf_queue) > 0) { + /* sendDtmf(int32 serial, string s) */ + GBinderWriter writer; + RadioRequest* req = radio_request_new(self->g->client, + RADIO_REQ_SEND_DTMF, &writer, + binder_voicecall_send_dtmf_cb, NULL, self); + char dtmf_str[2]; + + dtmf_str[0] = (char)GPOINTER_TO_UINT(gutil_ring_get(self->dtmf_queue)); + dtmf_str[1] = 0; + gbinder_writer_append_hidl_string_copy(&writer, dtmf_str); + + DBG_(self, "%s", dtmf_str); + if (radio_request_submit(req)) { + self->send_dtmf_req = req; + } else { + radio_request_unref(req); + binder_voicecall_clear_dtmf_queue(self); + } + } +} + +static +void +binder_voicecall_send_dtmf( + struct ofono_voicecall* vc, + const char* dtmf, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + struct ofono_error err; + + /* + * Queue any incoming DTMF, send them to BINDER one-by-one, + * immediately call back core with no error + */ + DBG("Queue '%s'", dtmf); + while (*dtmf) { + gutil_ring_put(self->dtmf_queue, GUINT_TO_POINTER(*dtmf)); + dtmf++; + } + + binder_voicecall_send_one_dtmf(self); + cb(binder_error_ok(&err), data); +} + +static +void +binder_voicecall_create_multiparty( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, RADIO_REQ_CONFERENCE, cb, data); +} + +static +void +binder_voicecall_transfer( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, RADIO_REQ_EXPLICIT_CALL_TRANSFER, cb, data); +} + +static +void +binder_voicecall_private_chat( + struct ofono_voicecall* vc, + int cid, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = + binder_voicecall_request_data_new(vc, cb, data); + + /* separateConnection(int32 serial, int32 index) */ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SEPARATE_CONNECTION, &writer, + binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + + DBG_(self, "Private chat with id %d", cid); + gbinder_writer_append_int32(&writer, cid); + if (radio_request_submit(req)) { + cbd->ref_count++; + cbd->pending_call_count++; + } + radio_request_unref(req); +} + +static +void +binder_voicecall_swap_without_accept( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, + RADIO_REQ_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, cb, data); +} + +static +void +binder_voicecall_release_all_held( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, + RADIO_REQ_HANGUP_WAITING_OR_BACKGROUND, cb, data); +} + +static +void +binder_voicecall_release_all_active( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, + RADIO_REQ_HANGUP_FOREGROUND_RESUME_BACKGROUND, cb, data); +} + +static +void +binder_voicecall_set_udub( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + DBG__(vc, ""); + binder_voicecall_request(vc, RADIO_REQ_REJECT_CALL, cb, data); +} + +static +void +binder_voicecall_enable_supp_svc( + BinderVoiceCall* self) +{ + GBinderWriter writer; + RadioRequest* req = radio_request_new2(self->g, + RADIO_REQ_SET_SUPP_SERVICE_NOTIFICATIONS, &writer, + NULL, NULL, NULL); + + /* setSuppServiceNotifications(int32 serial, bool enable); */ + gbinder_writer_append_bool(&writer, TRUE); + radio_request_set_timeout(req, VOICECALL_BLOCK_TIMEOUT_MS); + radio_request_set_blocking(req, TRUE); + radio_request_submit(req); + radio_request_unref(req); +} + +static +void +binder_voicecall_ringback_tone_event( + RadioClient* client, + RADIO_IND code, + const GBinderReader* args, + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + GBinderReader reader; + gboolean start; + + /* indicateRingbackTone(RadioIndicationType, bool start) */ + gbinder_reader_copy(&reader, args); + if (gbinder_reader_read_bool(&reader, &start)) { + DBG("play ringback tone: %d", start); + ofono_voicecall_ringback_tone_notify(self->vc, start); + } +} + +static +void +binder_voicecall_register( + gpointer user_data) +{ + BinderVoiceCall* self = user_data; + RadioClient* client = self->g->client; + + ofono_voicecall_register(self->vc); + + /* Initialize call list */ + binder_voicecall_clcc_poll(self); + + /* Request supplementary service notifications*/ + binder_voicecall_enable_supp_svc(self); + + /* Unsol when call state changes */ + self->event_id[VOICECALL_EVENT_CALL_STATE_CHANGED] = + radio_client_add_indication_handler(client, + RADIO_IND_CALL_STATE_CHANGED, + binder_voicecall_call_state_changed_event, self); + + /* Unsol when call set in hold */ + self->event_id[VOICECALL_EVENT_SUPP_SVC_NOTIFICATION] = + radio_client_add_indication_handler(client, + RADIO_IND_SUPP_SVC_NOTIFY, + binder_voicecall_supp_svc_notification_event, self); + + /* Register for ringback tone notifications */ + self->event_id[VOICECALL_EVENT_RINGBACK_TONE] = + radio_client_add_indication_handler(client, + RADIO_IND_INDICATE_RINGBACK_TONE, + binder_voicecall_ringback_tone_event, self); + +#pragma message("TODO: Set up ECC list watcher") +} + +static +int +binder_voicecall_probe( + struct ofono_voicecall* vc, + unsigned int vendor, + void* data) +{ + BinderModem* modem = binder_modem_get_data(data); + BinderVoiceCall* self = g_new0(BinderVoiceCall, 1); + const BinderSlotConfig* cfg = &modem->config; + + self->log_prefix = binder_dup_prefix(modem->log_prefix); + DBG_(self, ""); + + self->dtmf_queue = gutil_ring_new(); + self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ + self->local_hangup_reasons = gutil_ints_ref(cfg->local_hangup_reasons); + self->remote_hangup_reasons = gutil_ints_ref(cfg->remote_hangup_reasons); + self->local_release_ids = gutil_int_array_new(); + self->idleq = gutil_idle_queue_new(); + self->vc = vc; + + binder_voicecall_clear_dtmf_queue(self); + gutil_idle_queue_add(self->idleq, binder_voicecall_register, self); + ofono_voicecall_set_data(vc, self); + return 0; +} + +static +void +binder_voicecall_remove( + struct ofono_voicecall* vc) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + + DBG(""); + g_slist_free_full(self->calls, binder_voicecall_ofono_call_free); + + radio_request_drop(self->send_dtmf_req); + radio_request_drop(self->clcc_poll_req); + radio_client_remove_all_handlers(self->g->client, self->event_id); + radio_request_group_cancel(self->g); + radio_request_group_unref(self->g); + + gutil_ring_unref(self->dtmf_queue); + gutil_ints_unref(self->local_hangup_reasons); + gutil_ints_unref(self->remote_hangup_reasons); + gutil_int_array_free(self->local_release_ids, TRUE); + gutil_idle_queue_free(self->idleq); + + g_free(self->log_prefix); + g_free(self); + + ofono_voicecall_set_data(vc, NULL); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +static const struct ofono_voicecall_driver binder_voicecall_driver = { + .name = BINDER_DRIVER, + .probe = binder_voicecall_probe, + .remove = binder_voicecall_remove, + .dial = binder_voicecall_dial, + .answer = binder_voicecall_answer, + .hangup_active = binder_voicecall_hangup_active, + .hangup_all = binder_voicecall_hangup_all, + .release_specific = binder_voicecall_release_specific, + .send_tones = binder_voicecall_send_dtmf, + .create_multiparty = binder_voicecall_create_multiparty, + .transfer = binder_voicecall_transfer, + .private_chat = binder_voicecall_private_chat, + .swap_without_accept = binder_voicecall_swap_without_accept, + .release_all_held = binder_voicecall_release_all_held, + .set_udub = binder_voicecall_set_udub, + .release_all_active = binder_voicecall_release_all_active +}; + +void +binder_voicecall_init() +{ + ofono_voicecall_driver_register(&binder_voicecall_driver); +} + +void +binder_voicecall_cleanup() +{ + ofono_voicecall_driver_unregister(&binder_voicecall_driver); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_voicecall.h b/src/binder_voicecall.h new file mode 100644 index 0000000..ffac02e --- /dev/null +++ b/src/binder_voicecall.h @@ -0,0 +1,37 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021-2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BINDER_VOICECALL_H +#define BINDER_VOICECALL_H + +#include "binder_types.h" + +void +binder_voicecall_init(void) + BINDER_INTERNAL; + +void +binder_voicecall_cleanup(void) + BINDER_INTERNAL; + +#endif /* BINDER_VOICECALL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/Makefile b/unit/Makefile new file mode 100644 index 0000000..178caaf --- /dev/null +++ b/unit/Makefile @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: clean all + +all: +%: + @$(MAKE) -C unit_base $* + @$(MAKE) -C unit_sim_settings $* + +clean: unitclean + rm -f coverage/*.gcov + rm -fr coverage/report diff --git a/unit/common/Makefile b/unit/common/Makefile new file mode 100644 index 0000000..47e54f0 --- /dev/null +++ b/unit/common/Makefile @@ -0,0 +1,206 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: clean cleaner unitclean all debug release coverage valgrind +.PHONY: debug_lib release_lib coverage_lib +.PHONY: test test_banner + +# +# Real test makefile defines EXE (and possibly SRC) and includes this one. +# + +ifndef EXE +${error EXE not defined} +endif + +SRC ?= $(EXE).c +# COMMON_SRC += test_main.c + +# +# Required packages +# + +LINK_PKGS += libglibutil glib-2.0 gobject-2.0 +PKGS += $(LINK_PKGS) libgbinder libgbinder-radio + +# +# Default target +# + +all: debug release + +# +# Directories +# + +SRC_DIR = . +LIB_DIR = ../.. +COMMON_DIR = ../common +BUILD_DIR = build +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release +COVERAGE_BUILD_DIR = $(BUILD_DIR)/coverage +COMMON_BUILD_DIR = $(COMMON_DIR)/build +COMMON_DEBUG_BUILD_DIR = $(COMMON_BUILD_DIR)/debug +COMMON_RELEASE_BUILD_DIR = $(COMMON_BUILD_DIR)/release +COMMON_COVERAGE_BUILD_DIR = $(COMMON_BUILD_DIR)/coverage + +# +# Tools and flags +# + +CC ?= $(CROSS_COMPILE)gcc +LD = $(CC) +WARNINGS += -Wall -Wno-deprecated-declarations +INCLUDES += -I$(COMMON_DIR) -I$(LIB_DIR)/src -I$(LIB_DIR)/include +BASE_FLAGS = -fPIC +FULL_CFLAGS = $(BASE_FLAGS) $(CFLAGS) $(DEFINES) $(WARNINGS) $(INCLUDES) \ + -MMD -MP $(shell pkg-config --cflags $(PKGS)) +FULL_LDFLAGS = $(BASE_FLAGS) $(LDFLAGS) +LIBS = $(shell pkg-config --libs $(LINK_PKGS)) -lpthread +QUIET_MAKE = make --no-print-directory +DEBUG_FLAGS = -g +RELEASE_FLAGS = +COVERAGE_FLAGS = -g + +DEBUG_LDFLAGS = $(FULL_LDFLAGS) $(DEBUG_FLAGS) +RELEASE_LDFLAGS = $(FULL_LDFLAGS) $(RELEASE_FLAGS) +COVERAGE_LDFLAGS = $(FULL_LDFLAGS) $(COVERAGE_FLAGS) --coverage + +DEBUG_CFLAGS = $(FULL_CFLAGS) $(DEBUG_FLAGS) -DDEBUG +RELEASE_CFLAGS = $(FULL_CFLAGS) $(RELEASE_FLAGS) -O2 +COVERAGE_CFLAGS = $(FULL_CFLAGS) $(COVERAGE_FLAGS) --coverage + +DEBUG_LIB_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_debug_lib) +RELEASE_LIB_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_release_lib) +COVERAGE_LIB_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_coverage_lib) + +DEBUG_LIB = $(LIB_DIR)/$(DEBUG_LIB_FILE) +RELEASE_LIB = $(LIB_DIR)/$(RELEASE_LIB_FILE) +COVERAGE_LIB = $(LIB_DIR)/$(COVERAGE_LIB_FILE) + +DEBUG_LIBS = $(DEBUG_LIB) $(LIBS) +RELEASE_LIBS = $(RELEASE_LIB) $(LIBS) +COVERAGE_LIBS = $(COVERAGE_LIB) $(LIBS) + +# +# Files +# + +TEST_DEBUG_OBJS = $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +TEST_RELEASE_OBJS = $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) +TEST_COVERAGE_OBJS = $(SRC:%.c=$(COVERAGE_BUILD_DIR)/%.o) +COMMON_DEBUG_OBJS = $(COMMON_SRC:%.c=$(COMMON_DEBUG_BUILD_DIR)/%.o) +COMMON_RELEASE_OBJS = $(COMMON_SRC:%.c=$(COMMON_RELEASE_BUILD_DIR)/%.o) +COMMON_COVERAGE_OBJS = $(COMMON_SRC:%.c=$(COMMON_COVERAGE_BUILD_DIR)/%.o) +DEBUG_OBJS = $(COMMON_DEBUG_OBJS) $(TEST_DEBUG_OBJS) +RELEASE_OBJS = $(COMMON_RELEASE_OBJS) $(TEST_RELEASE_OBJS) +COVERAGE_OBJS = $(COMMON_COVERAGE_OBJS) $(TEST_COVERAGE_OBJS) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) $(COVERAGE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(DEBUG_LIB): | debug_lib +$(RELEASE_LIB): | release_lib +$(COVERAGE_LIB): | coverage_lib + +$(TEST_DEBUG_OBJS): | $(DEBUG_BUILD_DIR) +$(TEST_RELEASE_OBJS): | $(RELEASE_BUILD_DIR) +$(TEST_COVERAGE_OBJS): | $(COVERAGE_BUILD_DIR) + +$(COMMON_DEBUG_OBJS): | $(COMMON_DEBUG_BUILD_DIR) +$(COMMON_RELEASE_OBJS): | $(COMMON_RELEASE_BUILD_DIR) +$(COMMON_COVERAGE_OBJS): | $(COMMON_COVERAGE_BUILD_DIR) + +# +# Rules +# + +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) +COVERAGE_EXE = $(COVERAGE_BUILD_DIR)/$(EXE) + +debug: debug_lib $(DEBUG_EXE) + +release: release_lib $(RELEASE_EXE) + +coverage: coverage_lib $(COVERAGE_EXE) + +unitclean: + rm -f *~ + rm -fr $(BUILD_DIR) $(COMMON_BUILD_DIR) + +clean: unitclean + +cleaner: unitclean + @make -C $(LIB_DIR) clean + +test_banner: + @echo "===========" $(EXE) "=========== " + +test: test_banner debug + @$(DEBUG_EXE) + +valgrind: test_banner debug + @G_DEBUG=gc-friendly G_SLICE=always-malloc valgrind --tool=memcheck --leak-check=full --show-possibly-lost=no $(DEBUG_EXE) + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(COVERAGE_BUILD_DIR): + mkdir -p $@ + +$(COMMON_DEBUG_BUILD_DIR): + mkdir -p $@ + +$(COMMON_RELEASE_BUILD_DIR): + mkdir -p $@ + +$(COMMON_COVERAGE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(COVERAGE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(COVERAGE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(COMMON_DEBUG_BUILD_DIR)/%.o : $(COMMON_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(COMMON_RELEASE_BUILD_DIR)/%.o : $(COMMON_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(COMMON_COVERAGE_BUILD_DIR)/%.o : $(COMMON_DIR)/%.c + $(CC) -c $(COVERAGE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_LIB) $(DEBUG_OBJS) + $(LD) $(DEBUG_LDFLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_LIB) $(RELEASE_OBJS) + $(LD) $(RELEASE_LDFLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ + +$(COVERAGE_EXE): $(COVERAG_LIB) $(COVERAGE_OBJS) + $(LD) $(COVERAGE_LDFLAGS) $(COVERAGE_OBJS) $(COVERAGE_LIBS) -o $@ + +debug_lib: + $(MAKE) -C $(LIB_DIR) $@ + +release_lib: + $(MAKE) -C $(LIB_DIR) $@ + +coverage_lib: + $(MAKE) -C $(LIB_DIR) $@ diff --git a/unit/common/test_watch.c b/unit/common/test_watch.c new file mode 100644 index 0000000..8ac9581 --- /dev/null +++ b/unit/common/test_watch.c @@ -0,0 +1,426 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2017-2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "test_watch.h" + +#include +#include +#include + +#include + +typedef GObjectClass TestOfonoWatchClass; + +typedef struct test_ofono_watch { + GObject object; + OfonoWatch pub; + char* path; + char* iccid; + char* imsi; + char* spn; + int queued_signals; +} TestOfonoWatch; + +typedef struct test_ofono_watch_closure { + GCClosure cclosure; + ofono_watch_cb_t cb; + void* user_data; +} TestOfonoWatchClosure; + +#define SIGNAL_MODEM_CHANGED_NAME "ofono-watch-modem-changed" +#define SIGNAL_ONLINE_CHANGED_NAME "ofono-watch-online-changed" +#define SIGNAL_SIM_CHANGED_NAME "ofono-watch-sim-changed" +#define SIGNAL_SIM_STATE_CHANGED_NAME "ofono-watch-sim-state-changed" +#define SIGNAL_ICCID_CHANGED_NAME "ofono-watch-iccid-changed" +#define SIGNAL_IMSI_CHANGED_NAME "ofono-watch-imsi-changed" +#define SIGNAL_SPN_CHANGED_NAME "ofono-watch-spn-changed" +#define SIGNAL_NETREG_CHANGED_NAME "ofono-watch-netreg-changed" + +static guint test_ofono_watch_signals[TEST_WATCH_SIGNAL_COUNT] = { 0 }; +static GHashTable* test_ofono_watch_table = NULL; + +G_DEFINE_TYPE(TestOfonoWatch, test_ofono_watch, G_TYPE_OBJECT) +#define PARENT_CLASS test_ofono_watch_parent_class +#define THIS_TYPE test_ofono_watch_get_type() +#define THIS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, TestOfonoWatch) + +#define NEW_SIGNAL(klass,name) \ + test_ofono_watch_signals[TEST_WATCH_SIGNAL_##name##_CHANGED] = \ + g_signal_new(SIGNAL_##name##_CHANGED_NAME, \ + G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_FIRST, \ + 0, NULL, NULL, NULL, G_TYPE_NONE, 0) + +static inline TestOfonoWatch* test_ofono_watch_cast(struct ofono_watch* watch) + { return watch ? THIS(G_CAST(watch, TestOfonoWatch, pub)) : NULL; } + +static inline int test_ofono_watch_signal_bit(TEST_WATCH_SIGNAL id) + { return (1 << id); } + +static +void +test_ofono_watch_signal_emit( + TestOfonoWatch* self, + TEST_WATCH_SIGNAL id) +{ + self->queued_signals &= ~test_ofono_watch_signal_bit(id); + g_signal_emit(self, test_ofono_watch_signals[id], 0); +} + +static +void +test_ofono_watch_destroyed( + gpointer key, + GObject* obj) +{ + GASSERT(test_ofono_watch_table); + GDEBUG("TestOfonoWatch %s destroyed", (char*) key); + if (test_ofono_watch_table) { + GASSERT(g_hash_table_lookup(test_ofono_watch_table,key) == obj); + g_hash_table_remove(test_ofono_watch_table, key); + if (g_hash_table_size(test_ofono_watch_table) == 0) { + g_hash_table_unref(test_ofono_watch_table); + test_ofono_watch_table = NULL; + GDEBUG("Last TestOfonoWatch is gone"); + } + } +} + +static +void +test_watch_signal_cb( + TestOfonoWatch* source, + TestOfonoWatchClosure* closure) +{ + closure->cb(&source->pub, closure->user_data); +} + +static +unsigned long +test_watch_add_signal_handler( + OfonoWatch* watch, + TEST_WATCH_SIGNAL signal, + ofono_watch_cb_t cb, + void* user_data) +{ + if (watch && cb) { + TestOfonoWatch* self = test_ofono_watch_cast(watch); + TestOfonoWatchClosure* closure = (TestOfonoWatchClosure*) + g_closure_new_simple(sizeof(TestOfonoWatchClosure), NULL); + + closure->cclosure.closure.data = closure; + closure->cclosure.callback = G_CALLBACK(test_watch_signal_cb); + closure->cb = cb; + closure->user_data = user_data; + + return g_signal_connect_closure_by_id(self, test_ofono_watch_signals + [signal], 0, &closure->cclosure.closure, FALSE); + } + return 0; +} + +static +void +test_ofono_watch_init( + TestOfonoWatch* self) +{ +} + +static +void +test_ofono_watch_finalize( + GObject* object) +{ + TestOfonoWatch* self = THIS(object); + + g_free(self->path); + g_free(self->iccid); + g_free(self->imsi); + g_free(self->spn); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +test_ofono_watch_class_init( + TestOfonoWatchClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = test_ofono_watch_finalize; + NEW_SIGNAL(klass, MODEM); + NEW_SIGNAL(klass, ONLINE); + NEW_SIGNAL(klass, SIM); + NEW_SIGNAL(klass, SIM_STATE); + NEW_SIGNAL(klass, ICCID); + NEW_SIGNAL(klass, IMSI); + NEW_SIGNAL(klass, SPN); + NEW_SIGNAL(klass, NETREG); +} + +/*==========================================================================* + * Test API + *==========================================================================*/ + +void +test_watch_signal_queue( + OfonoWatch* watch, + TEST_WATCH_SIGNAL id) +{ + TestOfonoWatch* self = test_ofono_watch_cast(watch); + + self->queued_signals |= test_ofono_watch_signal_bit(id); +} + +void +test_watch_emit_queued_signals( + OfonoWatch* watch) +{ + TestOfonoWatch* self = test_ofono_watch_cast(watch); + int i; + + for (i = 0; self->queued_signals && i < TEST_WATCH_SIGNAL_COUNT; i++) { + if (self->queued_signals & test_ofono_watch_signal_bit(i)) { + test_ofono_watch_signal_emit(self, i); + } + } +} + +void +test_watch_set_ofono_iccid( + OfonoWatch* watch, + const char* iccid) +{ + TestOfonoWatch* self = test_ofono_watch_cast(watch); + + if (g_strcmp0(self->iccid, iccid)) { + g_free(self->iccid); + watch->iccid = self->iccid = g_strdup(iccid); + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_ICCID_CHANGED); + } +} + +void +test_watch_set_ofono_imsi( + OfonoWatch* watch, + const char* imsi) +{ + TestOfonoWatch* self = test_ofono_watch_cast(watch); + + if (g_strcmp0(self->imsi, imsi)) { + g_free(self->imsi); + watch->imsi = self->imsi = g_strdup(imsi); + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_IMSI_CHANGED); + } +} + +void +test_watch_set_ofono_spn( + OfonoWatch* watch, + const char* spn) +{ + TestOfonoWatch* self = test_ofono_watch_cast(watch); + + if (g_strcmp0(self->spn, spn)) { + g_free(self->spn); + watch->spn = self->spn = g_strdup(spn); + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_SPN_CHANGED); + } +} + +void +test_watch_set_ofono_sim( + OfonoWatch* watch, + struct ofono_sim* sim) +{ + if (watch->sim != sim) { + watch->sim = sim; + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_SIM_CHANGED); + if (!sim) { + test_watch_set_ofono_iccid(watch, NULL); + test_watch_set_ofono_imsi(watch, NULL); + test_watch_set_ofono_spn(watch, NULL); + } + } +} + +void +test_watch_set_ofono_netreg( + OfonoWatch* watch, + struct ofono_netreg* netreg) +{ + if (watch->netreg != netreg) { + watch->netreg = netreg; + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_NETREG_CHANGED); + } +} + +/*==========================================================================* + * Ofono API + *==========================================================================*/ + +OfonoWatch* +ofono_watch_new( + const char* path) +{ + if (path) { + TestOfonoWatch* self = NULL; + + if (test_ofono_watch_table) { + self = g_hash_table_lookup(test_ofono_watch_table, path); + } + if (self) { + g_object_ref(self); + } else { + char* key = g_strdup(path); + + self = g_object_new(THIS_TYPE, NULL); + self->pub.path = self->path = g_strdup(path); + if (!test_ofono_watch_table) { + /* Create the table on demand */ + test_ofono_watch_table = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, NULL); + } + g_hash_table_replace(test_ofono_watch_table, key, self); + g_object_weak_ref(G_OBJECT(self), test_ofono_watch_destroyed, key); + GDEBUG("TestOfonoWatch %s created", path); + } + return &self->pub; + } + return NULL; +} + +OfonoWatch* +ofono_watch_ref( + OfonoWatch* watch) +{ + if (watch) { + g_object_ref(test_ofono_watch_cast(watch)); + } + return watch; +} + +void +ofono_watch_unref( + OfonoWatch* watch) +{ + if (watch) { + g_object_unref(test_ofono_watch_cast(watch)); + } +} + +unsigned long +ofono_watch_add_modem_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_MODEM_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_online_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_ONLINE_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_sim_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_SIM_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_sim_state_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_SIM_STATE_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_iccid_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_ICCID_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_imsi_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_IMSI_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_spn_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_SPN_CHANGED, cb, user_data); +} + +unsigned long +ofono_watch_add_netreg_changed_handler( + OfonoWatch* watch, + ofono_watch_cb_t cb, + void* user_data) +{ + return test_watch_add_signal_handler(watch, + TEST_WATCH_SIGNAL_NETREG_CHANGED, cb, user_data); +} + +void +ofono_watch_remove_handler( + OfonoWatch* watch, + unsigned long id) +{ + if (watch && id) { + g_signal_handler_disconnect(test_ofono_watch_cast(watch), id); + } +} + +void +ofono_watch_remove_handlers( + OfonoWatch* watch, + unsigned long* ids, + unsigned int count) +{ + gutil_disconnect_handlers(test_ofono_watch_cast(watch), ids, count); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/common/test_watch.h b/unit/common/test_watch.h new file mode 100644 index 0000000..1983bfe --- /dev/null +++ b/unit/common/test_watch.h @@ -0,0 +1,77 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2017-2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TEST_WATCH_H +#define TEST_WATCH_H + +#include + +typedef enum test_watch_signal { + TEST_WATCH_SIGNAL_MODEM_CHANGED, + TEST_WATCH_SIGNAL_ONLINE_CHANGED, + TEST_WATCH_SIGNAL_SIM_CHANGED, + TEST_WATCH_SIGNAL_SIM_STATE_CHANGED, + TEST_WATCH_SIGNAL_ICCID_CHANGED, + TEST_WATCH_SIGNAL_IMSI_CHANGED, + TEST_WATCH_SIGNAL_SPN_CHANGED, + TEST_WATCH_SIGNAL_NETREG_CHANGED, + TEST_WATCH_SIGNAL_COUNT +} TEST_WATCH_SIGNAL; + +typedef struct ofono_watch OfonoWatch; + +void +test_watch_signal_queue( + OfonoWatch* watch, + TEST_WATCH_SIGNAL id); + +void +test_watch_emit_queued_signals( + OfonoWatch* watch); + +void +test_watch_set_ofono_sim( + OfonoWatch* watch, + struct ofono_sim* sim); + +void +test_watch_set_ofono_iccid( + OfonoWatch* watch, + const char* iccid); + +void +test_watch_set_ofono_imsi( + OfonoWatch* watch, + const char* imsi); + +void +test_watch_set_ofono_spn( + OfonoWatch* watch, + const char* spn); + +void +test_watch_set_ofono_netreg( + OfonoWatch* watch, + struct ofono_netreg* netreg); + +#endif /* TEST_WATCH_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/coverage/run b/unit/coverage/run new file mode 100755 index 0000000..449f8b0 --- /dev/null +++ b/unit/coverage/run @@ -0,0 +1,53 @@ +#!/bin/bash +# +# This script requires lcov, dirname +# + +TESTS="\ +unit_base \ +unit_sim_settings" + +function err() { + echo "*** ERROR!" $1 + exit 1 +} + +# Check the required tools +which lcov >> /dev/null || err "Please install lcov" +which dirname >> /dev/null || err "Please install dirname" + +# LCOV 1.10 has branch coverage disabled per default +# Previous versions didn't have the --rc option +if [ ! -z "$(lcov --help | grep ' --rc ')" ] ; then + LCOV_OPT="--rc lcov_branch_coverage=1" + GENHTML_OPT="--branch-coverage" +fi + +pushd `dirname $0` > /dev/null +COV_DIR="$PWD" +pushd .. > /dev/null +TEST_DIR="$PWD" +pushd .. > /dev/null +TOP_DIR="$PWD" +popd > /dev/null +popd > /dev/null +popd > /dev/null + +make -C "$TOP_DIR" clean +for t in $TESTS ; do + pushd "$TEST_DIR/$t" + make -C "$TEST_DIR/$t" clean coverage || exit 1 + build/coverage/$t || exit 1 + popd +done + +# Sometimes you need this, sometimes that :S +BASE_DIR="$TOP_DIR" +#BASE_DIR="$TOP_DIR/src" + +FULL_COV="$COV_DIR/full.gcov" +LIB_COV="$COV_DIR/lib.gcov" +rm -f "$FULL_COV" "$LIB_COV" +lcov $LCOV_OPT -c -d "$TOP_DIR/build/coverage" -b "$BASE_DIR" -o "$FULL_COV" || exit 1 +lcov $LCOV_OPT -e "$FULL_COV" "$BASE_DIR/*" -o "$LIB_COV" || exit 1 +genhtml $GENHTML_OPT "$LIB_COV" -t "ofono binder plugin" --output-directory "$COV_DIR/report" || exit 1 diff --git a/unit/unit_base/Makefile b/unit/unit_base/Makefile new file mode 100644 index 0000000..bf24a08 --- /dev/null +++ b/unit/unit_base/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = unit_base + +include ../common/Makefile diff --git a/unit/unit_base/unit_base.c b/unit/unit_base/unit_base.c new file mode 100644 index 0000000..6145cd4 --- /dev/null +++ b/unit/unit_base/unit_base.c @@ -0,0 +1,156 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "binder_base.h" +#include "binder_log.h" + +#include +#include + +GLOG_MODULE_DEFINE("unit_base"); + +/*==========================================================================* + * Test object + *==========================================================================*/ + +typedef enum test_property { + TEST_PROPERTY_ANY, + TEST_PROPERTY_ONE, + TEST_PROPERTY_TWO, + TEST_PROPERTY_COUNT +} TEST_PROPERTY; + +typedef BinderBaseClass TestObjectClass; +typedef struct test_object_data { + void* ptr; +} TestObjectData; +typedef struct test_object { + BinderBase base; + TestObjectData pub; +} TestObject; + +G_DEFINE_TYPE(TestObject, test_object, BINDER_TYPE_BASE) +#define PARENT_CLASS test_object_parent_class +#define TEST_TYPE test_object_get_type() +#define TEST(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, TEST_TYPE, TestObject) +BINDER_BASE_ASSERT_COUNT(TEST_PROPERTY_COUNT); + +static +void +test_object_init( + TestObject* self) +{ +} + +static +void +test_object_class_init( + TestObjectClass* klass) +{ + BINDER_BASE_CLASS(klass)->public_offset = G_STRUCT_OFFSET(TestObject, pub); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +typedef struct test_basic_data { + int total; + int count[TEST_PROPERTY_COUNT]; +} TestBasicData; + +static +void +test_basic_cb( + TestObjectData* data, + TEST_PROPERTY property, + void* user_data) +{ + TestBasicData* test = user_data; + + g_assert(data->ptr == user_data); + g_assert_cmpint(property, > ,TEST_PROPERTY_ANY); + g_assert_cmpint(property, < ,TEST_PROPERTY_COUNT); + test->count[property]++; + test->total++; + GDEBUG("%d %d", property, test->total); +} + +static +void +test_basic( + void) +{ + TestBasicData test; + TestObject* obj = g_object_new(TEST_TYPE, NULL); + BinderBase* base = &obj->base; + ulong id; + + obj->pub.ptr = &test; + memset(&test, 0, sizeof(test)); + id = binder_base_add_property_handler(base, TEST_PROPERTY_ANY, + G_CALLBACK(test_basic_cb), &test); + g_assert(id); + + /* NULL callback is tolerated */ + g_assert(!binder_base_add_property_handler(base, TEST_PROPERTY_ANY, + NULL, NULL)); + + /* Queue two events. Not signals is emitted yet */ + binder_base_queue_property_change(base, TEST_PROPERTY_ONE); + binder_base_queue_property_change(base, TEST_PROPERTY_TWO); + g_assert_cmpint(test.total, == ,0); + + /* Emit queued signals */ + binder_base_emit_queued_signals(base); + g_assert_cmpint(test.total, == ,2); + g_assert_cmpint(test.count[TEST_PROPERTY_ONE], == ,1); + g_assert_cmpint(test.count[TEST_PROPERTY_TWO], == ,1); + + /* And emit one more */ + binder_base_emit_property_change(base, TEST_PROPERTY_ONE); + g_assert_cmpint(test.total, == ,3); + g_assert_cmpint(test.count[TEST_PROPERTY_ONE], == ,2); + + g_signal_handler_disconnect(base, id); + g_object_unref(obj); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_PREFIX "/base/" +#define TEST_(t) TEST_PREFIX t + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("basic"), test_basic); + + gutil_log_default.level = g_test_verbose() ? + GLOG_LEVEL_VERBOSE : GLOG_LEVEL_NONE; + gutil_log_timestamp = FALSE; + + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/unit_sim_settings/Makefile b/unit/unit_sim_settings/Makefile new file mode 100644 index 0000000..1ca6f9e --- /dev/null +++ b/unit/unit_sim_settings/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +COMMON_SRC += test_watch.c + +EXE = unit_sim_settings + +include ../common/Makefile diff --git a/unit/unit_sim_settings/unit_sim_settings.c b/unit/unit_sim_settings/unit_sim_settings.c new file mode 100644 index 0000000..24784eb --- /dev/null +++ b/unit/unit_sim_settings/unit_sim_settings.c @@ -0,0 +1,169 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2021 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "test_watch.h" + +#include "binder_sim_settings.h" +#include "binder_log.h" + +#include +#include + +GLOG_MODULE_DEFINE("unit_sim_settings"); + +/*==========================================================================* + * null + *==========================================================================*/ +static +void +test_null( + void) +{ + g_assert(!binder_sim_settings_new(NULL, OFONO_RADIO_ACCESS_MODE_NONE)); + g_assert(!binder_sim_settings_add_property_handler(NULL, 0, NULL, NULL)); + g_assert(!binder_sim_settings_ref(NULL)); + binder_sim_settings_unref(NULL); + binder_sim_settings_set_pref(NULL, OFONO_RADIO_ACCESS_MODE_NONE); + binder_sim_settings_remove_handler(NULL, 0); + binder_sim_settings_remove_handlers(NULL, NULL, 0); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +typedef struct test_basic_data { + int count[BINDER_SIM_SETTINGS_PROPERTY_COUNT]; +} TestBasicData; + +static +void +test_basic_total_cb( + BinderSimSettings* data, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + TestBasicData* test = user_data; + + test->count[BINDER_SIM_SETTINGS_PROPERTY_ANY]++; + GDEBUG("%d %d", property, test->count[BINDER_SIM_SETTINGS_PROPERTY_ANY]); +} + +static +void +test_basic_property_cb( + BinderSimSettings* data, + BINDER_SIM_SETTINGS_PROPERTY property, + void* user_data) +{ + TestBasicData* test = user_data; + + g_assert_cmpint(property, < ,BINDER_SIM_SETTINGS_PROPERTY_COUNT); + g_assert_cmpint(property, > ,BINDER_SIM_SETTINGS_PROPERTY_ANY); + test->count[property]++; + GDEBUG("%d %d", property, test->count[property]); +} + +static +void +test_basic( + void) +{ + TestBasicData test; + const char* path = "/test_0"; + const char* iccid = "8888888888888888888"; + const char* imsi = "222222222222222"; + enum ofono_radio_access_mode pref = OFONO_RADIO_ACCESS_MODE_GSM; + OfonoWatch* watch = ofono_watch_new(path); + BinderSimSettings* settings = binder_sim_settings_new(path, + OFONO_RADIO_ACCESS_MODE_ALL); + ulong id[BINDER_SIM_SETTINGS_PROPERTY_COUNT]; + guint i; + + memset(&test, 0, sizeof(test)); + id[0] = binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_ANY, test_basic_total_cb, &test); + g_assert(id[0]); + for (i = 1; i < G_N_ELEMENTS(id); i++) { + id[i] = binder_sim_settings_add_property_handler(settings, i, + test_basic_property_cb, &test); + g_assert(id[i]); + } + + /* NULL callback and zero id are tolerated */ + binder_sim_settings_remove_handler(settings, 0); + g_assert(!binder_sim_settings_add_property_handler(settings, + BINDER_SIM_SETTINGS_PROPERTY_ANY, NULL, NULL)); + + /* Change some properties */ + test_watch_set_ofono_iccid(watch, iccid); /* Ignored */ + test_watch_emit_queued_signals(watch); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_ANY], == ,0); + + test_watch_set_ofono_imsi(watch, imsi); /* Handled */ + test_watch_emit_queued_signals(watch); + g_assert_cmpstr(settings->imsi, == ,imsi); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_ANY], == ,1); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_IMSI], == ,1); + + /* No signal is emitted if IMSI hasn't actually changed */ + test_watch_signal_queue(watch, TEST_WATCH_SIGNAL_IMSI_CHANGED); + test_watch_emit_queued_signals(watch); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_ANY], == ,1); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_IMSI], == ,1); + + /* Second time doesn't emit anything since the value doesn't change */ + binder_sim_settings_set_pref(settings, pref); + binder_sim_settings_set_pref(settings, pref); + g_assert_cmpint(settings->pref, == ,pref); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_ANY], == ,2); + g_assert_cmpint(test.count[BINDER_SIM_SETTINGS_PROPERTY_PREF], == ,1); + + binder_sim_settings_remove_handler(settings, id[0]); + id[0] = 0; /* binder_sim_settings_remove_handlers ignores zeros */ + binder_sim_settings_remove_all_handlers(settings, id); + g_assert(binder_sim_settings_ref(settings) == settings); + binder_sim_settings_unref(settings); + binder_sim_settings_unref(settings); + ofono_watch_unref(watch); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_PREFIX "/sim_settings/" +#define TEST_(t) TEST_PREFIX t + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + + gutil_log_default.level = g_test_verbose() ? + GLOG_LEVEL_VERBOSE : GLOG_LEVEL_NONE; + gutil_log_timestamp = FALSE; + + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */