Search
SailfishOS Open Build Service
>
Projects
>
nemo
:
devel:hw
:
pine
:
dontbeevil
>
ofono
> _service:tar_git:0008-Add-the-sailfish-slot-manager-and-dbus-interface.patch
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File _service:tar_git:0008-Add-the-sailfish-slot-manager-and-dbus-interface.patch of Package ofono
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Adam Pigg <adam@piggz.co.uk> Date: Sun, 10 Mar 2024 12:24:08 +0000 Subject: [PATCH 1/1] Add the sailfish slot-manager and dbus interface --- Makefile.am | 12 +- include/slot.h | 168 +++++ src/ofono.h | 2 + src/slot-manager-dbus.c | 1152 +++++++++++++++++++++++++++++ src/slot-manager-dbus.h | 85 +++ src/slot-manager.c | 1551 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 2967 insertions(+), 3 deletions(-) create mode 100644 include/slot.h create mode 100644 src/slot-manager-dbus.c create mode 100644 src/slot-manager-dbus.h create mode 100644 src/slot-manager.c diff --git a/Makefile.am b/Makefile.am index cf13de97ea24a5dd7fda5918a8798b3ed6681ad3..636546189a50f338f77706ec465cded268a89210 100644 --- a/Makefile.am +++ b/Makefile.am @@ -179,7 +179,7 @@ pkginclude_HEADERS = include/log.h include/plugin.h include/history.h \ include/dbus-clients.h include/cell-info.h if SAILFISH_SLOT -pkginclude_HEADERS += include/sailfish_watch.h +pkginclude_HEADERS += include/sailfish_watch.h include/slot.h endif nodist_pkginclude_HEADERS = include/version.h @@ -700,12 +700,18 @@ src_ofonod_SOURCES = $(builtin_sources) $(gatchat_sources) src/ofono.ver \ src/hfp.h src/siri.c \ src/netmon.c src/lte.c src/ims.c \ src/netmonagent.c src/netmonagent.h \ - src/cell-info.c src/cell-info-dbus.c \ - src/cell-info-control.c \ src/module.c \ src/provisiondb.h src/provisiondb.c \ src/provision.c +if SAILFISH_SLOT +src_ofonod_SOURCES += src/cell-info.c src/cell-info-dbus.c \ + src/sim-info.c src/sim-info-dbus.c \ + src/cell-info-control.c \ + src/sailfish_watch.c \ + src/slot-manager.c src/slot-manager-dbus.c +endif + src_ofonod_LDADD = gdbus/libgdbus-internal.la $(builtin_libadd) $(ell_ldadd) \ @GLIB_LIBS@ @DBUS_LIBS@ -ldl diff --git a/include/slot.h b/include/slot.h new file mode 100644 index 0000000000000000000000000000000000000000..8fb8b730315df1a3750eb9a47bd462b358dcd509 --- /dev/null +++ b/include/slot.h @@ -0,0 +1,168 @@ +/* + * 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 __OFONO_SLOT_H +#define __OFONO_SLOT_H + +/* + * Slots are built-in non-removable modems. Which may or may not apprear + * in the list reported by org.ofono.Manager.GetModems D-Bus call. + * + * This API exists since mer/1.24+git2 + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ofono_modem; + +#include <ofono/types.h> +#include <ofono/radio-settings.h> + +enum ofono_slot_sim_presence { + OFONO_SLOT_SIM_UNKNOWN, + OFONO_SLOT_SIM_ABSENT, + OFONO_SLOT_SIM_PRESENT +}; + +/* Should be treated as a bitmask although currently it's not */ +enum ofono_slot_data_role { + OFONO_SLOT_DATA_NONE = 0, + OFONO_SLOT_DATA_MMS = 0x01, + OFONO_SLOT_DATA_INTERNET = 0x02 +}; + +enum ofono_slot_property { + OFONO_SLOT_PROPERTY_ANY, + OFONO_SLOT_PROPERTY_ENABLED, + OFONO_SLOT_PROPERTY_SIM_PRESENCE, + OFONO_SLOT_PROPERTY_DATA_ROLE +#define OFONO_SLOT_PROPERTY_LAST OFONO_SLOT_PROPERTY_DATA_ROLE +}; + +enum ofono_slot_manager_property { + OFONO_SLOT_MANAGER_PROPERTY_ANY, + OFONO_SLOT_MANAGER_PROPERTY_MMS_IMSI, + OFONO_SLOT_MANAGER_PROPERTY_MMS_PATH, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_VOICE_IMSI, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_DATA_IMSI, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_VOICE_PATH, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_DATA_PATH, + OFONO_SLOT_MANAGER_PROPERTY_READY +#define OFONO_SLOT_MANAGER_PROPERTY_LAST OFONO_SLOT_MANAGER_PROPERTY_READY +}; + +enum ofono_slot_flags { + OFONO_SLOT_NO_FLAGS = 0, + /* Normally we should be able to have two simultaneously active + * data contexts - one for mobile data and one for MMS. The flag + * below says that for whatever reason it's impossible and mobile + * data has to be disconnected before we can send or receive MMS. + * On such devices it may not be a good idea to automatically + * download MMS because that would kill active mobile data + * connections. */ + OFONO_SLOT_FLAG_SINGLE_CONTEXT = 0x01 +}; + +typedef struct ofono_slot { + const char *path; + const char *imei; + const char *imeisv; + ofono_bool_t enabled; + enum ofono_slot_sim_presence sim_presence; + enum ofono_slot_data_role data_role; +} const *ofono_slot_ptr; + +struct ofono_slot_manager { + const char *mms_imsi; + const char *mms_path; + const char *default_voice_imsi; + const char *default_data_imsi; + const char *default_voice_path; + const char *default_data_path; + const ofono_slot_ptr *slots; + ofono_bool_t ready; +}; + +#define OFONO_SLOT_API_VERSION (1) + +struct ofono_slot_driver { + const char *name; + int api_version; /* OFONO_SLOT_API_VERSION */ + + struct ofono_slot_driver_data *(*init)(struct ofono_slot_manager *m); + unsigned int (*start)(struct ofono_slot_driver_data *d); + void (*cancel)(struct ofono_slot_driver_data *d, unsigned int id); + void (*cleanup)(struct ofono_slot_driver_data *d); +}; + +typedef void (*ofono_slot_property_cb)(struct ofono_slot *slot, + enum ofono_slot_property property, void* user_data); +typedef void (*ofono_slot_manager_property_cb)(struct ofono_slot_manager *m, + enum ofono_slot_property property, void* user_data); + +struct ofono_slot_driver_data; +struct ofono_slot_driver_reg; +struct ofono_slot_driver_reg *ofono_slot_driver_register + (const struct ofono_slot_driver *driver); +struct ofono_slot_driver_data *ofono_slot_driver_get_data + (struct ofono_slot_driver_reg *reg); +void ofono_slot_driver_unregister(struct ofono_slot_driver_reg *reg); +void ofono_slot_driver_started(struct ofono_slot_driver_reg *reg); + +struct ofono_slot_manager *ofono_slot_manager_ref(struct ofono_slot_manager *m); +void ofono_slot_manager_unref(struct ofono_slot_manager *m); +void ofono_slot_manager_error(struct ofono_slot_manager *m, const char *key, + const char *message); +unsigned long ofono_slot_manager_add_property_handler + (struct ofono_slot_manager *m, enum ofono_slot_manager_property p, + ofono_slot_manager_property_cb cb, void* data); +void ofono_slot_manager_remove_handler(struct ofono_slot_manager *m, + unsigned long id); +void ofono_slot_manager_remove_handlers(struct ofono_slot_manager *m, + unsigned long *ids, unsigned int n); + +struct ofono_cell_info; +struct ofono_slot *ofono_slot_add(struct ofono_slot_manager *m, + const char *path, enum ofono_radio_access_mode techs, const char *imei, + const char *imeisv, enum ofono_slot_sim_presence sim_presence, + enum ofono_slot_flags flags); +struct ofono_slot *ofono_slot_ref(struct ofono_slot *s); +void ofono_slot_unref(struct ofono_slot *s); +void ofono_slot_error(struct ofono_slot *s, const char *key, const char *msg); +void ofono_slot_set_cell_info(struct ofono_slot *s, struct ofono_cell_info *ci); +unsigned long ofono_slot_add_property_handler(struct ofono_slot *s, + enum ofono_slot_property p, ofono_slot_property_cb cb, void* data); +void ofono_slot_remove_handler(struct ofono_slot *s, unsigned long id); +void ofono_slot_remove_handlers(struct ofono_slot *s, unsigned long *ids, + unsigned int n); +void ofono_slot_set_sim_presence(struct ofono_slot *s, + enum ofono_slot_sim_presence sim_presence); + +/* Since mer/1.25+git5 */ +#define ofono_slot_remove_all_handlers(s, ids) \ + ofono_slot_remove_handlers(s, ids, G_N_ELEMENTS(ids)) + +/* Since mer/1.25+git7 */ +void ofono_slot_set_cell_info_update_interval(struct ofono_slot *s, void* tag, + int interval_ms); +void ofono_slot_drop_cell_info_requests(struct ofono_slot *s, void* tag); + +#ifdef __cplusplus +} +#endif + +#endif /* __OFONO_SLOT_H */ diff --git a/src/ofono.h b/src/ofono.h index f7e01e004a00c2556607095a19d176fda30e3241..ad863738f4fb696e3078a39cad2528986f770c95 100644 --- a/src/ofono.h +++ b/src/ofono.h @@ -619,3 +619,5 @@ ofono_bool_t __ofono_private_network_request(ofono_private_network_cb_t cb, #include <ofono/netmon.h> #include <ofono/lte.h> #include <ofono/ims.h> +#include <ofono/storage.h> +#include <ofono/sailfish_watch.h> diff --git a/src/slot-manager-dbus.c b/src/slot-manager-dbus.c new file mode 100644 index 0000000000000000000000000000000000000000..a7a4351ada10970cd45e6db3c8532f7548274590 --- /dev/null +++ b/src/slot-manager-dbus.c @@ -0,0 +1,1152 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2015-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 "slot-manager-dbus.h" + +#include <ofono/log.h> +#include <ofono/dbus.h> + +#include <gutil_macros.h> +#include <gutil_strv.h> +#include <gutil_log.h> + +#include <gdbus.h> + +#include "ofono.h" + +typedef void (*slot_manager_dbus_append_fn)(DBusMessageIter *it, + struct slot_manager_dbus *dbus); +typedef gboolean (*slot_manager_dbus_slot_select_fn) + (const struct ofono_slot *slot); +typedef const char *(*slot_manager_dbus_slot_string_fn) + (const struct ofono_slot *slot); + +struct slot_manager_dbus_request { + DBusMessage *msg; + slot_manager_dbus_append_fn fn; + enum slot_manager_dbus_block block; +}; + +struct slot_manager_dbus { + struct ofono_slot_manager *manager; + const struct slot_manager_dbus_cb *cb; + DBusConnection *conn; + enum slot_manager_dbus_block block_mask; + GSList *blocked_req; + guint mms_watch; +}; + +#define SM_DBUS_PATH "/" +#define SM_DBUS_INTERFACE "org.nemomobile.ofono.ModemManager" +#define SM_DBUS_INTERFACE_VERSION (8) + +#define SM_DBUS_SIGNAL_ENABLED_MODEMS_CHANGED "EnabledModemsChanged" +#define SM_DBUS_SIGNAL_PRESENT_SIMS_CHANGED "PresentSimsChanged" +#define SM_DBUS_SIGNAL_DEFAULT_VOICE_SIM_CHANGED "DefaultVoiceSimChanged" +#define SM_DBUS_SIGNAL_DEFAULT_DATA_SIM_CHANGED "DefaultDataSimChanged" +#define SM_DBUS_SIGNAL_DEFAULT_VOICE_MODEM_CHANGED "DefaultVoiceModemChanged" +#define SM_DBUS_SIGNAL_DEFAULT_DATA_MODEM_CHANGED "DefaultDataModemChanged" +#define SM_DBUS_SIGNAL_MMS_SIM_CHANGED "MmsSimChanged" +#define SM_DBUS_SIGNAL_MMS_MODEM_CHANGED "MmsModemChanged" +#define SM_DBUS_SIGNAL_READY_CHANGED "ReadyChanged" +#define SM_DBUS_SIGNAL_MODEM_ERROR "ModemError" +#define SM_DBUS_IMSI_AUTO "auto" + +#define SM_DBUS_ERROR_SIGNATURE "si" + +static gboolean slot_manager_dbus_enabled(const struct ofono_slot *s) +{ + return s->enabled; +} + +static gboolean slot_manager_dbus_present(const struct ofono_slot *s) +{ + return s->sim_presence == OFONO_SLOT_SIM_PRESENT; +} + +static const char *slot_manager_dbus_imei(const struct ofono_slot *s) +{ + return s->imei; +} + +static const char *slot_manager_dbus_imeisv(const struct ofono_slot *s) +{ + return s->imeisv; +} + +static void slot_manager_dbus_append_path_array(DBusMessageIter *it, + struct slot_manager_dbus *dbus, slot_manager_dbus_slot_select_fn fn) +{ + DBusMessageIter array; + const ofono_slot_ptr *ptr = dbus->manager->slots; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array); + + if (ptr) { + while (*ptr) { + const struct ofono_slot *slot = *ptr++; + + if (!fn || fn(slot)) { + const char *path = slot->path; + dbus_message_iter_append_basic(&array, + DBUS_TYPE_OBJECT_PATH, &path); + } + } + } + + dbus_message_iter_close_container(it, &array); +} + +static void slot_manager_dbus_append_string_array(DBusMessageIter *it, + struct slot_manager_dbus *dbus, slot_manager_dbus_slot_string_fn fn) +{ + DBusMessageIter array; + const ofono_slot_ptr *ptr = dbus->manager->slots; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + if (ptr) { + while (*ptr) { + const struct ofono_slot *slot = *ptr++; + const char *str = fn(slot); + + if (!str) str = ""; + dbus_message_iter_append_basic(&array, + DBUS_TYPE_STRING, &str); + } + } + + dbus_message_iter_close_container(it, &array); +} + +static void slot_manager_dbus_append_boolean_array(DBusMessageIter *it, + struct slot_manager_dbus *dbus, slot_manager_dbus_slot_select_fn value) +{ + DBusMessageIter array; + const ofono_slot_ptr *ptr = dbus->manager->slots; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + DBUS_TYPE_BOOLEAN_AS_STRING, &array); + + if (ptr) { + while (*ptr) { + const struct ofono_slot *slot = *ptr++; + dbus_bool_t b = value(slot); + + dbus_message_iter_append_basic(&array, + DBUS_TYPE_BOOLEAN, &b); + } + } + + dbus_message_iter_close_container(it, &array); +} + +static void slot_manager_dbus_append_boolean(DBusMessageIter *it, dbus_bool_t b) +{ + dbus_message_iter_append_basic(it, DBUS_TYPE_BOOLEAN, &b); +} + +static void slot_manager_dbus_append_string(DBusMessageIter *it, const char *s) +{ + if (!s) s = ""; + dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &s); +} + +static void slot_manager_dbus_append_imsi(DBusMessageIter *it, const char *imsi) +{ + if (!imsi) imsi = SM_DBUS_IMSI_AUTO; + dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &imsi); +} + +static void slot_manager_dbus_append_path(DBusMessageIter *it, const char *path) +{ + if (!path) path = ""; + /* It's DBUS_TYPE_STRING since DBUS_TYPE_OBJECT_PATH can't be empty */ + dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &path); +} + +static void slot_manager_dbus_message_append_path_array(DBusMessage *msg, + struct slot_manager_dbus *dbus, slot_manager_dbus_slot_select_fn fn) +{ + DBusMessageIter iter; + + dbus_message_iter_init_append(msg, &iter); + slot_manager_dbus_append_path_array(&iter, dbus, fn); +} + +static void slot_manager_dbus_append_error_count(DBusMessageIter *it, + const char *id, dbus_uint32_t count) +{ + DBusMessageIter sub; + + dbus_message_iter_open_container(it, DBUS_TYPE_STRUCT, NULL, &sub); + dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id); + dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &count); + dbus_message_iter_close_container(it, &sub); +} + +static void slot_manager_dbus_append_error_counts(DBusMessageIter *it, + GHashTable *errors) +{ + DBusMessageIter counts; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + "(" SM_DBUS_ERROR_SIGNATURE ")", &counts); + + if (errors && g_hash_table_size(errors)) { + gpointer key, value; + GHashTableIter iter; + + g_hash_table_iter_init(&iter, errors); + while (g_hash_table_iter_next(&iter, &key, &value)) { + slot_manager_dbus_append_error_count(&counts, + key, GPOINTER_TO_INT(value)); + } + } + + dbus_message_iter_close_container(it, &counts); +} + +static void slot_manager_dbus_append_modem_errors(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + DBusMessageIter slots; + const ofono_slot_ptr *ptr = dbus->manager->slots; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + "a(" SM_DBUS_ERROR_SIGNATURE ")", &slots); + + if (ptr) { + while (*ptr) { + const struct ofono_slot *slot = *ptr++; + + slot_manager_dbus_append_error_counts(&slots, + dbus->cb->get_slot_errors(slot)); + } + } + + dbus_message_iter_close_container(it, &slots); +} + +static void slot_manager_dbus_append_errors(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_error_counts(it, + dbus->cb->get_errors(dbus->manager)); +} + +static void slot_manager_dbus_signal_path_array(struct slot_manager_dbus *dbus, + const char *name, slot_manager_dbus_slot_select_fn fn) +{ + DBusMessage *signal = dbus_message_new_signal(SM_DBUS_PATH, + SM_DBUS_INTERFACE, name); + + slot_manager_dbus_message_append_path_array(signal, dbus, fn); + g_dbus_send_message(dbus->conn, signal); +} + +static inline void slot_manager_dbus_signal_imsi(struct slot_manager_dbus *dbus, + const char *name, const char *imsi) +{ + if (!imsi) imsi = SM_DBUS_IMSI_AUTO; + DBG("%s %s", name, imsi); + g_dbus_emit_signal(dbus->conn, SM_DBUS_PATH, SM_DBUS_INTERFACE, + name, DBUS_TYPE_STRING, &imsi, DBUS_TYPE_INVALID); +} + +static inline void slot_manager_dbus_signal_string + (struct slot_manager_dbus *dbus, const char *name, const char *str) +{ + if (!str) str = ""; + DBG("%s %s", name, str); + g_dbus_emit_signal(dbus->conn, SM_DBUS_PATH, SM_DBUS_INTERFACE, + name, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID); +} + +static inline void slot_manager_dbus_signal_boolean + (struct slot_manager_dbus *dbus, const char *name, dbus_bool_t value) +{ + DBG("%s %d", name, value); + g_dbus_emit_signal(dbus->conn, SM_DBUS_PATH, SM_DBUS_INTERFACE, + name, DBUS_TYPE_BOOLEAN, &value, DBUS_TYPE_INVALID); +} + +void slot_manager_dbus_signal(struct slot_manager_dbus *dbus, + enum slot_manager_dbus_signal mask) +{ + if (dbus) { + const struct ofono_slot_manager *manager = dbus->manager; + + if (mask & SLOT_MANAGER_DBUS_SIGNAL_VOICE_IMSI) { + slot_manager_dbus_signal_imsi(dbus, + SM_DBUS_SIGNAL_DEFAULT_VOICE_SIM_CHANGED, + manager->default_voice_imsi); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_DATA_IMSI) { + slot_manager_dbus_signal_imsi(dbus, + SM_DBUS_SIGNAL_DEFAULT_DATA_SIM_CHANGED, + manager->default_data_imsi); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_MMS_IMSI) { + slot_manager_dbus_signal_string(dbus, + SM_DBUS_SIGNAL_MMS_SIM_CHANGED, + manager->mms_imsi); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_ENABLED_SLOTS) { + slot_manager_dbus_signal_path_array(dbus, + SM_DBUS_SIGNAL_ENABLED_MODEMS_CHANGED, + slot_manager_dbus_enabled); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_VOICE_PATH) { + slot_manager_dbus_signal_string(dbus, + SM_DBUS_SIGNAL_DEFAULT_VOICE_MODEM_CHANGED, + manager->default_voice_path); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_DATA_PATH) { + slot_manager_dbus_signal_string(dbus, + SM_DBUS_SIGNAL_DEFAULT_DATA_MODEM_CHANGED, + manager->default_data_path); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_MMS_PATH) { + slot_manager_dbus_signal_string(dbus, + SM_DBUS_SIGNAL_MMS_MODEM_CHANGED, + manager->mms_path); + } + if (mask & SLOT_MANAGER_DBUS_SIGNAL_READY) { + slot_manager_dbus_signal_boolean(dbus, + SM_DBUS_SIGNAL_READY_CHANGED, + manager->ready); + } + } +} + +void slot_manager_dbus_signal_sim(struct slot_manager_dbus *dbus, + int index, enum slot_manager_dbus_slot_signal mask) +{ + if (dbus) { + const struct ofono_slot *slot = dbus->manager->slots[index]; + + if (mask & SLOT_MANAGER_DBUS_SLOT_SIGNAL_PRESENT) { + dbus_bool_t value = slot_manager_dbus_present(slot); + + g_dbus_emit_signal(dbus->conn, + SM_DBUS_PATH, SM_DBUS_INTERFACE, + SM_DBUS_SIGNAL_PRESENT_SIMS_CHANGED, + DBUS_TYPE_INT32, &index, + DBUS_TYPE_BOOLEAN, &value, + DBUS_TYPE_INVALID); + } + } +} + +void slot_manager_dbus_emit_modem_error(struct slot_manager_dbus *dbus, + const char *path, const char *id, const char *message) +{ + if (!message) message = ""; + g_dbus_emit_signal(dbus->conn, SM_DBUS_PATH, SM_DBUS_INTERFACE, + SM_DBUS_SIGNAL_MODEM_ERROR, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &id, + DBUS_TYPE_STRING, &message, + DBUS_TYPE_INVALID); +} + +void slot_manager_dbus_signal_modem_error(struct slot_manager_dbus *dbus, + int index, const char *id, const char *msg) +{ + slot_manager_dbus_emit_modem_error(dbus, + dbus->manager->slots[index]->path, id, msg); +} + +void slot_manager_dbus_signal_error(struct slot_manager_dbus *dbus, + const char *id, const char *message) +{ + slot_manager_dbus_emit_modem_error(dbus, "/", id, message); +} + +static DBusMessage *slot_manager_dbus_reply(DBusMessage *msg, + struct slot_manager_dbus *dbus, + slot_manager_dbus_append_fn append) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + append(&iter, dbus); + return reply; +} + +static const char* slot_manager_dbus_block_name + (enum slot_manager_dbus_block block) +{ + return (block == SLOT_MANAGER_DBUS_BLOCK_IMEI) ? "IMEI" : + (block == SLOT_MANAGER_DBUS_BLOCK_MODEM) ? "MODEM" : + (block == SLOT_MANAGER_DBUS_BLOCK_ALL) ? "ALL" : + "???"; +} + +static void slot_manager_dbus_cancel_request(gpointer data) +{ + struct slot_manager_dbus_request *req = data; + + DBG("cancelling %s request %p", + slot_manager_dbus_block_name(req->block), req); + __ofono_dbus_pending_reply(&req->msg, + __ofono_error_canceled(req->msg)); + g_slice_free(struct slot_manager_dbus_request, req); +} + +void slot_manager_dbus_set_block(struct slot_manager_dbus *dbus, + enum slot_manager_dbus_block mask) +{ + enum slot_manager_dbus_block block = mask & ~dbus->block_mask; + enum slot_manager_dbus_block unblock = dbus->block_mask & ~mask; + + dbus->block_mask = mask; + if (block) { + DBG("blocking %s requests", + slot_manager_dbus_block_name(block)); + } + if (unblock) { + GSList *link = dbus->blocked_req, *prev = NULL; + + DBG("unblocking %s requests", + slot_manager_dbus_block_name(unblock)); + while (link) { + struct slot_manager_dbus_request *req = link->data; + GSList *next = link->next; + + if (req->block & dbus->block_mask) { + prev = link; + } else { + if (prev) { + prev->next = next; + } else { + dbus->blocked_req = next; + } + link->next = NULL; + __ofono_dbus_pending_reply(&req->msg, + slot_manager_dbus_reply(req->msg, + dbus, req->fn)); + gutil_slice_free(req); + g_slist_free1(link); + } + link = next; + } + } +} + +static DBusMessage *slot_manager_dbus_reply_or_block(DBusMessage *msg, + struct slot_manager_dbus *dbus, slot_manager_dbus_append_fn fn, + enum slot_manager_dbus_block block) +{ + if (dbus->block_mask & block) { + struct slot_manager_dbus_request *req = + g_slice_new(struct slot_manager_dbus_request); + + DBG("blocking %s request %s %p", + slot_manager_dbus_block_name(block), + dbus_message_get_member(msg), req); + req->msg = dbus_message_ref(msg); + req->fn = fn; + req->block = block; + dbus->blocked_req = g_slist_append(dbus->blocked_req, req); + return NULL; + } else { + return slot_manager_dbus_reply(msg, dbus, fn); + } +} + +static DBusMessage *slot_manager_dbus_modem_reply(DBusMessage *msg, + struct slot_manager_dbus *dbus, slot_manager_dbus_append_fn fn) +{ + return slot_manager_dbus_reply_or_block(msg, dbus, fn, + SLOT_MANAGER_DBUS_BLOCK_MODEM); +} + +static DBusMessage *slot_manager_dbus_imei_reply(DBusMessage *msg, + struct slot_manager_dbus *dbus, slot_manager_dbus_append_fn fn) +{ + return slot_manager_dbus_reply_or_block(msg, dbus, fn, + SLOT_MANAGER_DBUS_BLOCK_IMEI); +} + +static void slot_manager_dbus_append_version(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + dbus_int32_t version = SM_DBUS_INTERFACE_VERSION; + + dbus_message_iter_append_basic(it, DBUS_TYPE_INT32, &version); +} + +static void slot_manager_dbus_append_available_modems(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_path_array(it, dbus, NULL); +} + +static void slot_manager_dbus_append_enabled_modems(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_path_array(it, dbus, + slot_manager_dbus_enabled); +} + +static void slot_manager_dbus_append_present_sims(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_boolean_array(it, dbus, + slot_manager_dbus_present); +} + +static void slot_manager_dbus_append_imei_array(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_string_array(it, dbus, + slot_manager_dbus_imei); +} + +static void slot_manager_dbus_append_imeisv_array(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_string_array(it, dbus, + slot_manager_dbus_imeisv); +} + +static void slot_manager_dbus_append_all(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + struct ofono_slot_manager *manager = dbus->manager; + + slot_manager_dbus_append_version(it, dbus); + slot_manager_dbus_append_available_modems(it, dbus); + slot_manager_dbus_append_enabled_modems(it, dbus); + slot_manager_dbus_append_imsi(it, manager->default_data_imsi); + slot_manager_dbus_append_imsi(it, manager->default_voice_imsi); + slot_manager_dbus_append_path(it, manager->default_data_path); + slot_manager_dbus_append_path(it, manager->default_voice_path); +} + +static void slot_manager_dbus_append_all2(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all(it, dbus); + slot_manager_dbus_append_present_sims(it, dbus); +} + +static void slot_manager_dbus_append_all3(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all2(it, dbus); + slot_manager_dbus_append_imei_array(it, dbus); +} + +static void slot_manager_dbus_append_all4(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + struct ofono_slot_manager *manager = dbus->manager; + + slot_manager_dbus_append_all3(it, dbus); + slot_manager_dbus_append_string(it, manager->mms_imsi); + slot_manager_dbus_append_path(it, manager->mms_path); +} + +static void slot_manager_dbus_append_all5(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all4(it, dbus); + slot_manager_dbus_append_boolean(it, dbus->manager->ready); +} + +static void slot_manager_dbus_append_all6(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all5(it, dbus); + slot_manager_dbus_append_modem_errors(it, dbus); +} + +static void slot_manager_dbus_append_all7(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all6(it, dbus); + slot_manager_dbus_append_imeisv_array(it, dbus); +} + +static void slot_manager_dbus_append_all8(DBusMessageIter *it, + struct slot_manager_dbus *dbus) +{ + slot_manager_dbus_append_all7(it, dbus); + slot_manager_dbus_append_errors(it, dbus); +} + +static DBusMessage *slot_manager_dbus_get_all(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_modem_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all); +} + +static DBusMessage *slot_manager_dbus_get_all2(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_modem_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all2); +} + +static DBusMessage *slot_manager_dbus_get_all3(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all3); +} + +static DBusMessage *slot_manager_dbus_get_all4(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all4); +} + +static DBusMessage *slot_manager_dbus_get_all5(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all5); +} + +static DBusMessage *slot_manager_dbus_get_all6(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all6); +} + +static DBusMessage *slot_manager_dbus_get_all7(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all7); +} + +static DBusMessage *slot_manager_dbus_get_all8(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_all8); +} + +static DBusMessage *slot_manager_dbus_get_interface_version + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + return slot_manager_dbus_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_version); +} + +static DBusMessage *slot_manager_dbus_get_available_modems + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + return slot_manager_dbus_modem_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_available_modems); +} + +static DBusMessage *slot_manager_dbus_get_enabled_modems + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + return slot_manager_dbus_modem_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_enabled_modems); +} + +static DBusMessage *slot_manager_dbus_get_present_sims(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_present_sims); +} + +static DBusMessage *slot_manager_dbus_get_imei(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_imei_array); +} + +static DBusMessage *slot_manager_dbus_get_imeisv(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_imei_reply(msg, (struct slot_manager_dbus *) + data, slot_manager_dbus_append_imeisv_array); +} + +static DBusMessage *slot_manager_dbus_reply_with_string(DBusMessage *msg, + const char *str) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + slot_manager_dbus_append_string(&iter, str); + return reply; +} + +static DBusMessage *slot_manager_dbus_reply_with_imsi(DBusMessage *msg, + const char *imsi) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + slot_manager_dbus_append_imsi(&iter, imsi); + return reply; +} + +static DBusMessage *slot_manager_dbus_get_default_data_sim + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_imsi(msg, + dbus->manager->default_data_imsi); +} + +static DBusMessage *slot_manager_dbus_get_default_voice_sim + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_imsi(msg, + dbus->manager->default_voice_imsi); +} + +static DBusMessage *slot_manager_dbus_get_mms_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_string(msg, + dbus->manager->mms_imsi); +} + +static DBusMessage *slot_manager_dbus_reply_with_path(DBusMessage *msg, + const char *path) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + slot_manager_dbus_append_path(&iter, path); + return reply; +} + +static DBusMessage *slot_manager_dbus_get_default_data_modem + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_path(msg, + dbus->manager->default_data_path); +} + +static DBusMessage *slot_manager_dbus_get_default_voice_modem + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_path(msg, + dbus->manager->default_voice_path); +} + +static DBusMessage *slot_manager_dbus_get_mms_modem(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + return slot_manager_dbus_reply_with_path(msg, dbus->manager->mms_path); +} + +static DBusMessage *slot_manager_dbus_get_ready(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter it; + + dbus_message_iter_init_append(reply, &it); + slot_manager_dbus_append_boolean(&it, dbus->manager->ready); + return reply; +} + +static DBusMessage *slot_manager_dbus_get_modem_errors(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_reply(msg, (struct slot_manager_dbus *) data, + slot_manager_dbus_append_modem_errors); +} + +static DBusMessage *slot_manager_dbus_get_errors(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return slot_manager_dbus_reply(msg, (struct slot_manager_dbus *)data, + slot_manager_dbus_append_errors); +} + +static DBusMessage *slot_manager_dbus_set_enabled_modems(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + DBusMessageIter iter; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { + char **paths = NULL; + DBusMessageIter array; + + dbus_message_iter_recurse(&iter, &array); + while (dbus_message_iter_get_arg_type(&array) == + DBUS_TYPE_OBJECT_PATH) { + DBusBasicValue value; + + dbus_message_iter_get_basic(&array, &value); + paths = gutil_strv_add(paths, value.str); + dbus_message_iter_next(&array); + } + + dbus->cb->set_enabled_slots(dbus->manager, paths); + g_strfreev(paths); + return dbus_message_new_method_return(msg); + } else { + return __ofono_error_invalid_args(msg); + } +} + +static DBusMessage *slot_manager_dbus_set_imsi(struct slot_manager_dbus *dbus, + DBusMessage *msg, void (*apply)(struct ofono_slot_manager *manager, + const char *imsi)) +{ + DBusMessageIter iter; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + DBusBasicValue value; + const char *imsi; + + dbus_message_iter_get_basic(&iter, &value); + imsi = value.str; + if (!g_strcmp0(imsi, SM_DBUS_IMSI_AUTO)) imsi = NULL; + apply(dbus->manager, imsi); + return dbus_message_new_method_return(msg); + } else { + return __ofono_error_invalid_args(msg); + } +} + +static DBusMessage *slot_manager_dbus_set_default_voice_sim + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + GASSERT(conn == dbus->conn); + return slot_manager_dbus_set_imsi(dbus, msg, + dbus->cb->set_default_voice_imsi); +} + +static DBusMessage *slot_manager_dbus_set_default_data_sim + (DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct slot_manager_dbus *dbus = data; + + GASSERT(conn == dbus->conn); + return slot_manager_dbus_set_imsi(dbus, msg, + dbus->cb->set_default_data_imsi); +} + +static void slot_manager_dbus_mms_disconnect(DBusConnection *conn, void *data) +{ + struct slot_manager_dbus *dbus = data; + + dbus->mms_watch = 0; + if (dbus->manager->mms_imsi) { + DBG("MMS client is gone"); + dbus->cb->set_mms_imsi(dbus->manager, NULL); + } +} + +static DBusMessage *slot_manager_dbus_set_mms_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + struct slot_manager_dbus *dbus = data; + + GASSERT(conn == dbus->conn); + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + struct ofono_slot_manager *manager = dbus->manager; + DBusBasicValue value; + const char *imsi; + + dbus_message_iter_get_basic(&iter, &value); + imsi = value.str; + + /* + * MMS IMSI is not persistent and has to be eventually + * reset by the client or cleaned up if the client + * unexpectedly disappears. + */ + if (dbus->cb->set_mms_imsi(manager, imsi)) { + + /* + * Clear the previous MMS owner + */ + if (dbus->mms_watch) { + g_dbus_remove_watch(dbus->conn, + dbus->mms_watch); + dbus->mms_watch = 0; + } + + if (manager->mms_imsi && manager->mms_imsi[0]) { + /* + * This client becomes the owner + */ + const char* sender = + dbus_message_get_sender(msg); + + DBG("Owner: %s", sender); + dbus->mms_watch = g_dbus_add_disconnect_watch + (dbus->conn, sender, + slot_manager_dbus_mms_disconnect, + dbus, NULL); + } + + return slot_manager_dbus_reply_with_string(msg, + manager->mms_path); + } else { + return __ofono_error_not_available(msg); + } + } else { + return __ofono_error_invalid_args(msg); + } +} + +/* + * The client can call GetInterfaceVersion followed by the appropriate + * GetAllx call to get all settings in two steps. Alternatively, it can + * call GetAll followed by GetAllx based on the interface version returned + * by GetAll. In either case, two D-Bus calls are required, unless the + * client is willing to make the assumption about the ofono version it's + * talking to. + */ + +#define SM_DBUS_VERSION_ARG {"version", "i"} +#define SM_DBUS_AVAILABLE_MODEMS_ARG {"availableModems", "ao"} +#define SM_DBUS_ENABLED_MODEMS_ARG {"enabledModems", "ao" } +#define SM_DBUS_DEFAULT_DATA_SIM_ARG {"defaultDataSim", "s" } +#define SM_DBUS_DEFAULT_VOICE_SIM_ARG {"defaultVoiceSim", "s" } +#define SM_DBUS_DEFAULT_DATA_MODEM_ARG {"defaultDataModem", "s" } +#define SM_DBUS_DEFAULT_VOICE_MODEM_ARG {"defaultVoiceModem" , "s"} +#define SM_DBUS_PRESENT_SIMS_ARG {"presentSims" , "ab"} +#define SM_DBUS_IMEI_ARG {"imei" , "as"} +#define SM_DBUS_MMS_SIM_ARG {"mmsSim", "s"} +#define SM_DBUS_MMS_MODEM_ARG {"mmsModem" , "s"} +#define SM_DBUS_READY_ARG {"ready" , "b"} +#define SM_DBUS_MODEM_ERRORS_ARG {"modemErrors" , \ + "aa(" SM_DBUS_ERROR_SIGNATURE ")"} +#define SM_DBUS_IMEISV_ARG {"imeisv" , "as"} +#define SM_DBUS_ERRORS_ARG {"errors" , \ + "a(" SM_DBUS_ERROR_SIGNATURE ")"} +#define SM_DBUS_GET_ALL_ARGS \ + SM_DBUS_VERSION_ARG, \ + SM_DBUS_AVAILABLE_MODEMS_ARG, \ + SM_DBUS_ENABLED_MODEMS_ARG, \ + SM_DBUS_DEFAULT_DATA_SIM_ARG, \ + SM_DBUS_DEFAULT_VOICE_SIM_ARG, \ + SM_DBUS_DEFAULT_DATA_MODEM_ARG, \ + SM_DBUS_DEFAULT_VOICE_MODEM_ARG +#define SM_DBUS_GET_ALL2_ARGS \ + SM_DBUS_GET_ALL_ARGS, \ + SM_DBUS_PRESENT_SIMS_ARG +#define SM_DBUS_GET_ALL3_ARGS \ + SM_DBUS_GET_ALL2_ARGS, \ + SM_DBUS_IMEI_ARG +#define SM_DBUS_GET_ALL4_ARGS \ + SM_DBUS_GET_ALL3_ARGS, \ + SM_DBUS_MMS_SIM_ARG, \ + SM_DBUS_MMS_MODEM_ARG +#define SM_DBUS_GET_ALL5_ARGS \ + SM_DBUS_GET_ALL4_ARGS, \ + SM_DBUS_READY_ARG +#define SM_DBUS_GET_ALL6_ARGS \ + SM_DBUS_GET_ALL5_ARGS, \ + SM_DBUS_MODEM_ERRORS_ARG +#define SM_DBUS_GET_ALL7_ARGS \ + SM_DBUS_GET_ALL6_ARGS, \ + SM_DBUS_IMEISV_ARG +#define SM_DBUS_GET_ALL8_ARGS \ + SM_DBUS_GET_ALL7_ARGS, \ + SM_DBUS_ERRORS_ARG +static const GDBusMethodTable slot_manager_dbus_methods[] = { + { GDBUS_ASYNC_METHOD("GetAll", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL_ARGS), + slot_manager_dbus_get_all) }, + { GDBUS_ASYNC_METHOD("GetAll2", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL2_ARGS), + slot_manager_dbus_get_all2) }, + { GDBUS_ASYNC_METHOD("GetAll3", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL3_ARGS), + slot_manager_dbus_get_all3) }, + { GDBUS_ASYNC_METHOD("GetAll4", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL4_ARGS), + slot_manager_dbus_get_all4) }, + { GDBUS_ASYNC_METHOD("GetAll5", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL5_ARGS), + slot_manager_dbus_get_all5) }, + { GDBUS_ASYNC_METHOD("GetAll6", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL6_ARGS), + slot_manager_dbus_get_all6) }, + { GDBUS_ASYNC_METHOD("GetAll7", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL7_ARGS), + slot_manager_dbus_get_all7) }, + { GDBUS_ASYNC_METHOD("GetAll8", + NULL, GDBUS_ARGS(SM_DBUS_GET_ALL8_ARGS), + slot_manager_dbus_get_all8) }, + { GDBUS_ASYNC_METHOD("GetInterfaceVersion", + NULL, GDBUS_ARGS(SM_DBUS_VERSION_ARG), + slot_manager_dbus_get_interface_version) }, + { GDBUS_ASYNC_METHOD("GetAvailableModems", + NULL, GDBUS_ARGS(SM_DBUS_AVAILABLE_MODEMS_ARG), + slot_manager_dbus_get_available_modems) }, + { GDBUS_ASYNC_METHOD("GetEnabledModems", + NULL, GDBUS_ARGS(SM_DBUS_ENABLED_MODEMS_ARG), + slot_manager_dbus_get_enabled_modems) }, + { GDBUS_ASYNC_METHOD("GetPresentSims", + NULL, GDBUS_ARGS(SM_DBUS_PRESENT_SIMS_ARG), + slot_manager_dbus_get_present_sims) }, + { GDBUS_ASYNC_METHOD("GetIMEI", + NULL, GDBUS_ARGS(SM_DBUS_IMEI_ARG), + slot_manager_dbus_get_imei) }, + { GDBUS_ASYNC_METHOD("GetIMEISV", + NULL, GDBUS_ARGS(SM_DBUS_IMEISV_ARG), + slot_manager_dbus_get_imeisv) }, + { GDBUS_ASYNC_METHOD("GetDefaultDataSim", + NULL, GDBUS_ARGS(SM_DBUS_DEFAULT_DATA_SIM_ARG), + slot_manager_dbus_get_default_data_sim) }, + { GDBUS_ASYNC_METHOD("GetDefaultVoiceSim", + NULL, GDBUS_ARGS(SM_DBUS_DEFAULT_VOICE_SIM_ARG), + slot_manager_dbus_get_default_voice_sim) }, + { GDBUS_ASYNC_METHOD("GetMmsSim", + NULL, GDBUS_ARGS(SM_DBUS_MMS_SIM_ARG), + slot_manager_dbus_get_mms_sim) }, + { GDBUS_ASYNC_METHOD("GetDefaultDataModem", + NULL, GDBUS_ARGS(SM_DBUS_DEFAULT_DATA_MODEM_ARG), + slot_manager_dbus_get_default_data_modem) }, + { GDBUS_ASYNC_METHOD("GetDefaultVoiceModem", + NULL, GDBUS_ARGS(SM_DBUS_DEFAULT_VOICE_MODEM_ARG), + slot_manager_dbus_get_default_voice_modem) }, + { GDBUS_ASYNC_METHOD("GetMmsModem", + NULL, GDBUS_ARGS(SM_DBUS_MMS_MODEM_ARG), + slot_manager_dbus_get_mms_modem) }, + { GDBUS_ASYNC_METHOD("GetReady", + NULL, GDBUS_ARGS(SM_DBUS_READY_ARG), + slot_manager_dbus_get_ready) }, + { GDBUS_ASYNC_METHOD("GetModemErrors", + NULL, GDBUS_ARGS(SM_DBUS_MODEM_ERRORS_ARG), + slot_manager_dbus_get_modem_errors) }, + { GDBUS_ASYNC_METHOD("GetErrors", + NULL, GDBUS_ARGS(SM_DBUS_ERRORS_ARG), + slot_manager_dbus_get_errors) }, + { GDBUS_ASYNC_METHOD("SetEnabledModems", + GDBUS_ARGS({ "modems", "ao" }), NULL, + slot_manager_dbus_set_enabled_modems) }, + { GDBUS_ASYNC_METHOD("SetDefaultDataSim", + GDBUS_ARGS({ "imsi", "s" }), NULL, + slot_manager_dbus_set_default_data_sim) }, + { GDBUS_ASYNC_METHOD("SetDefaultVoiceSim", + GDBUS_ARGS({ "imsi", "s" }), NULL, + slot_manager_dbus_set_default_voice_sim) }, + { GDBUS_ASYNC_METHOD("SetMmsSim", + GDBUS_ARGS({ "imsi", "s" }), NULL, + slot_manager_dbus_set_mms_sim) }, + { } +}; + +static const GDBusSignalTable slot_manager_dbus_signals[] = { + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_ENABLED_MODEMS_CHANGED, + GDBUS_ARGS(SM_DBUS_ENABLED_MODEMS_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_PRESENT_SIMS_CHANGED, + GDBUS_ARGS({"index", "i" }, + {"present" , "b"})) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_DEFAULT_DATA_SIM_CHANGED, + GDBUS_ARGS(SM_DBUS_DEFAULT_DATA_SIM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_DEFAULT_VOICE_SIM_CHANGED, + GDBUS_ARGS(SM_DBUS_DEFAULT_VOICE_SIM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_DEFAULT_DATA_MODEM_CHANGED, + GDBUS_ARGS(SM_DBUS_DEFAULT_DATA_MODEM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_DEFAULT_VOICE_MODEM_CHANGED, + GDBUS_ARGS(SM_DBUS_DEFAULT_VOICE_MODEM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_MMS_SIM_CHANGED, + GDBUS_ARGS(SM_DBUS_MMS_SIM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_MMS_MODEM_CHANGED, + GDBUS_ARGS(SM_DBUS_MMS_MODEM_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_READY_CHANGED, + GDBUS_ARGS(SM_DBUS_READY_ARG)) }, + { GDBUS_SIGNAL(SM_DBUS_SIGNAL_MODEM_ERROR, + GDBUS_ARGS({"path","o"}, + {"error_id", "s"}, + {"message", "s"})) }, + { } +}; + +struct slot_manager_dbus *slot_manager_dbus_new(struct ofono_slot_manager *m, + const struct slot_manager_dbus_cb *cb) +{ + struct slot_manager_dbus *dbus = g_slice_new0(struct slot_manager_dbus); + + dbus->conn = dbus_connection_ref(ofono_dbus_get_connection()); + dbus->manager = m; + dbus->cb = cb; + if (g_dbus_register_interface(dbus->conn, SM_DBUS_PATH, + SM_DBUS_INTERFACE, slot_manager_dbus_methods, + slot_manager_dbus_signals, NULL, dbus, NULL)) { + return dbus; + } else { + ofono_error(SM_DBUS_INTERFACE " D-Bus register failed"); + slot_manager_dbus_free(dbus); + return NULL; + } +} + +void slot_manager_dbus_free(struct slot_manager_dbus *dbus) +{ + if (dbus) { + if (dbus->mms_watch) { + g_dbus_remove_watch(dbus->conn, dbus->mms_watch); + } + + g_slist_free_full(dbus->blocked_req, + slot_manager_dbus_cancel_request); + g_dbus_unregister_interface(dbus->conn, SM_DBUS_PATH, + SM_DBUS_INTERFACE); + dbus_connection_unref(dbus->conn); + gutil_slice_free(dbus); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/src/slot-manager-dbus.h b/src/slot-manager-dbus.h new file mode 100644 index 0000000000000000000000000000000000000000..cd05dd8bde842a7b0d2b39ba4a824fa4354bef7d --- /dev/null +++ b/src/slot-manager-dbus.h @@ -0,0 +1,85 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2016-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 SLOT_MANAGER_DBUS_H +#define SLOT_MANAGER_DBUS_H + +#include <ofono/slot.h> + +#include <glib.h> + +struct slot_manager_dbus; + +enum slot_manager_dbus_block { + SLOT_MANAGER_DBUS_BLOCK_NONE = 0, + SLOT_MANAGER_DBUS_BLOCK_MODEM = 0x01, + SLOT_MANAGER_DBUS_BLOCK_IMEI = 0x02, + SLOT_MANAGER_DBUS_BLOCK_ALL = 0x03 +}; + +enum slot_manager_dbus_signal { + SLOT_MANAGER_DBUS_SIGNAL_NONE = 0, + SLOT_MANAGER_DBUS_SIGNAL_VOICE_IMSI = 0x01, + SLOT_MANAGER_DBUS_SIGNAL_DATA_IMSI = 0x02, + SLOT_MANAGER_DBUS_SIGNAL_VOICE_PATH = 0x04, + SLOT_MANAGER_DBUS_SIGNAL_DATA_PATH = 0x08, + SLOT_MANAGER_DBUS_SIGNAL_ENABLED_SLOTS = 0x10, + SLOT_MANAGER_DBUS_SIGNAL_MMS_IMSI = 0x20, + SLOT_MANAGER_DBUS_SIGNAL_MMS_PATH = 0x40, + SLOT_MANAGER_DBUS_SIGNAL_READY = 0x80 +}; + +enum slot_manager_dbus_slot_signal { + SLOT_MANAGER_DBUS_SLOT_SIGNAL_NONE = 0, + SLOT_MANAGER_DBUS_SLOT_SIGNAL_PRESENT = 0x01 +}; + +/* Functionality provided by slot_manager to slot_manager_dbus */ +struct slot_manager_dbus_cb { + GHashTable *(*get_errors)(const struct ofono_slot_manager *mgr); + GHashTable *(*get_slot_errors)(const struct ofono_slot *slot); + void (*set_enabled_slots)(struct ofono_slot_manager *mgr, char **slots); + gboolean (*set_mms_imsi)(struct ofono_slot_manager *mgr, + const char *imsi); + void (*set_default_voice_imsi)(struct ofono_slot_manager *mgr, + const char *imsi); + void (*set_default_data_imsi)(struct ofono_slot_manager *mgr, + const char *imsi); +}; + +struct slot_manager_dbus *slot_manager_dbus_new + (struct ofono_slot_manager *m, + const struct slot_manager_dbus_cb *cb); +void slot_manager_dbus_free(struct slot_manager_dbus *d); +void slot_manager_dbus_set_block(struct slot_manager_dbus *d, + enum slot_manager_dbus_block b); +void slot_manager_dbus_signal(struct slot_manager_dbus *d, + enum slot_manager_dbus_signal mask); +void slot_manager_dbus_signal_sim(struct slot_manager_dbus *d, int index, + enum slot_manager_dbus_slot_signal mask); +void slot_manager_dbus_signal_error(struct slot_manager_dbus *d, + const char *id, const char *message); +void slot_manager_dbus_signal_modem_error(struct slot_manager_dbus *d, + int index, const char *id, const char *msg); + +#endif /* SLOT_MANAGER_DBUS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/src/slot-manager.c b/src/slot-manager.c new file mode 100644 index 0000000000000000000000000000000000000000..c0e5088b9bf035ce13e1e4ba46c07472fc2b3ac5 --- /dev/null +++ b/src/slot-manager.c @@ -0,0 +1,1551 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2017-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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ofono.h" +#include "sim-info.h" +#include "storage.h" +#include "slot-manager-dbus.h" +#include "cell-info-control.h" +#include "cell-info-dbus.h" +#include "sailfish_watch.h" + +#include <gutil_macros.h> +#include <gutil_misc.h> +#include <gutil_strv.h> +#include <gutil_log.h> + +#include <glib-object.h> + +enum slot_sim_auto_select { + SLOT_SIM_AUTO_SELECT_OFF, + SLOT_SIM_AUTO_SELECT_ON, + SLOT_SIM_AUTO_SELECT_ONCE +}; + +enum watch_events { + WATCH_EVENT_MODEM, + WATCH_EVENT_ONLINE, + WATCH_EVENT_IMSI, + WATCH_EVENT_COUNT +}; + +#define SM_LOOP_CONTINUE (FALSE) +#define SM_LOOP_DONE (TRUE) + +typedef struct ofono_slot_manager_object OfonoSlotManagerObject; +typedef struct ofono_slot_object OfonoSlotObject; +typedef struct ofono_slot_driver_reg OfonoSlotDriverReg; + +/* This code assumes that public part immediately follows OfonoSlotBase */ +typedef struct ofono_slot_base { + GObject object; + guint32 queued_signals; +} OfonoSlotBase; + +/* + * OfonoSlotManagerObject internally keeps a reference to each OfonoSlotObject + * created by ofono_slot_add() function. In addition to that, ofono_slot_add() + * returns another reference to the caller, which the caller must eventually + * release with ofono_slot_unref(). + */ +struct ofono_slot_object { + OfonoSlotBase base; + struct ofono_slot pub; + OfonoSlotManagerObject *manager; /* Not a ref, may be NULL */ + struct ofono_watch *watch; + struct sim_info *siminfo; + struct sim_info_dbus *siminfo_dbus; + struct cell_info_dbus *cellinfo_dbus; + CellInfoControl *cellinfo_ctl; + enum ofono_slot_flags flags; + gulong watch_event_id[WATCH_EVENT_COUNT]; + char *imei; + char *imeisv; + GHashTable *errors; + int index; +}; + +struct ofono_slot_manager_object { + OfonoSlotBase base; + struct ofono_slot_manager pub; + struct slot_manager_dbus *dbus; + GSList *drivers; /* OfonoSlotDriverReg* */ + GSList *slots; /* OfonoSlotObject* */ + ofono_slot_ptr *pslots; + OfonoSlotObject *voice_slot; + OfonoSlotObject *data_slot; + OfonoSlotObject *mms_slot; + enum slot_sim_auto_select auto_data_sim; + gboolean auto_data_sim_done; + guint init_countdown; + guint init_id; + char *default_voice_imsi; + char *default_data_imsi; + char *mms_imsi; + GKeyFile *storage; + GHashTable *errors; + guint start_id; +}; + +struct ofono_slot_driver_reg { + OfonoSlotManagerObject *manager; + const struct ofono_slot_driver *driver; + struct ofono_slot_driver_data *driver_data; + guint start_id; +}; + +/* Path always starts with a slash, skip it */ +#define slot_debug_prefix(s) ((s)->pub.path + 1) + +/* How long we wait for all drivers to register (number of idle loops) */ +#define SM_INIT_IDLE_COUNT (5) + +/* Read-only config */ +#define SM_CONFIG_FILE "main.conf" +#define SM_CONFIG_GROUP "ModemManager" +#define SM_CONFIG_KEY_AUTO_DATA_SIM "AutoSelectDataSim" + +/* "ril" is used for historical reasons */ +#define SM_STORE "ril" +#define SM_STORE_GROUP "Settings" +#define SM_STORE_ENABLED_SLOTS "EnabledSlots" +#define SM_STORE_DEFAULT_VOICE_SIM "DefaultVoiceSim" +#define SM_STORE_DEFAULT_DATA_SIM "DefaultDataSim" +#define SM_STORE_SLOTS_SEP "," +#define SM_STORE_AUTO_DATA_SIM_DONE "AutoSelectDataSimDone" + +/* The file where error statistics is stored. Again "rilerror" is historical */ +#define SM_ERROR_STORAGE "rilerror" /* File name */ +#define SM_ERROR_COMMON_SECTION "common" /* Modem independent section */ + +typedef GObjectClass OfonoSlotBaseClass; +typedef OfonoSlotBaseClass OfonoSlotObjectClass; +typedef OfonoSlotBaseClass OfonoSlotManagerObjectClass; + +#define OFONO_TYPE_BASE (ofono_slot_base_get_type()) +#define OFONO_TYPE_SLOT (ofono_slot_object_get_type()) +#define OFONO_TYPE_SLOT_MANAGER (ofono_slot_manager_object_get_type()) +#define OFONO_SLOT_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + OFONO_TYPE_BASE, OfonoSlotBase)) +#define OFONO_SLOT_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + OFONO_TYPE_SLOT, OfonoSlotObject)) +#define OFONO_SLOT_MANAGER_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + OFONO_TYPE_SLOT_MANAGER, OfonoSlotManagerObject)) +G_DEFINE_TYPE(OfonoSlotBase,ofono_slot_base,G_TYPE_OBJECT) +G_DEFINE_TYPE(OfonoSlotObject,ofono_slot_object,OFONO_TYPE_BASE) +G_DEFINE_TYPE(OfonoSlotManagerObject,ofono_slot_manager_object,OFONO_TYPE_BASE) + +typedef void (*slot_base_property_cb)(void *obj, int prop, void* data); + +typedef struct ofono_slot_base_closure { + GCClosure cclosure; + slot_base_property_cb cb; + gpointer user_data; +} OfonoSlotBaseClosure; + +#define slot_base_closure_new() ((OfonoSlotBaseClosure *) \ + g_closure_new_simple(sizeof(OfonoSlotBaseClosure), NULL)) + +#define SIGNAL_BIT(property) (1 << ((property) - 1)) +#define MAX_PROPERTIES \ + (MAX((int)OFONO_SLOT_PROPERTY_LAST, \ + (int)OFONO_SLOT_MANAGER_PROPERTY_LAST) + 1) + +#define SIGNAL_PROPERTY_CHANGED_NAME "ofono-slot-base-property-changed" +#define SIGNAL_PROPERTY_DETAIL "%x" +#define SIGNAL_PROPERTY_DETAIL_MAX_LEN (8) + +enum ofono_slot_base_signal { + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +}; + +static guint slot_base_signals[SIGNAL_COUNT]; +static GQuark slot_base_property_quarks[MAX_PROPERTIES]; + +static void slot_manager_reindex_slots(OfonoSlotManagerObject *mgr); +static void slot_manager_emit_all_queued_signals(OfonoSlotManagerObject *mgr); +static void slot_manager_update_ready(OfonoSlotManagerObject *mgr); +static enum slot_manager_dbus_signal slot_manager_update_modem_paths + (OfonoSlotManagerObject *mgr, gboolean imsi_change) + G_GNUC_WARN_UNUSED_RESULT; + +static inline OfonoSlotBase *slot_base_cast(gpointer p) +{ + return G_LIKELY(p) ? OFONO_SLOT_BASE((OfonoSlotBase*)p - 1) : NULL; +} + +static inline OfonoSlotObject *slot_object_cast(const struct ofono_slot *s) +{ + return G_LIKELY(s) ? OFONO_SLOT_OBJECT(G_CAST(s, + OfonoSlotObject, pub)) : NULL; +} + +static inline OfonoSlotManagerObject *slot_manager_object_cast + (const struct ofono_slot_manager *m) +{ + return G_LIKELY(m) ? OFONO_SLOT_MANAGER_OBJECT(G_CAST(m, + OfonoSlotManagerObject, pub)) : NULL; +} + +static GQuark slot_base_property_quark(guint p) +{ + /* For ANY property (zero) this function is expected to return zero */ + if (p > 0 && G_LIKELY(p < MAX_PROPERTIES)) { + const int i = p - 1; + + if (G_UNLIKELY(!slot_base_property_quarks[i])) { + char s[SIGNAL_PROPERTY_DETAIL_MAX_LEN + 1]; + + snprintf(s, sizeof(s), SIGNAL_PROPERTY_DETAIL, p); + s[sizeof(s) - 1] = 0; + slot_base_property_quarks[i] = g_quark_from_string(s); + } + return slot_base_property_quarks[i]; + } + return 0; +} + +static void slot_base_property_changed(OfonoSlotBase* base, + int property, OfonoSlotBaseClosure* closure) +{ + closure->cb(((void*)(base + 1)), property, closure->user_data); +} + +static void slot_base_emit_property_change(OfonoSlotBase* obj, int p) +{ + obj->queued_signals &= ~SIGNAL_BIT(p); + g_signal_emit(obj, slot_base_signals[SIGNAL_PROPERTY_CHANGED], + slot_base_property_quark(p), p); +} + +static void slot_base_emit_queued_signals(OfonoSlotBase* obj) +{ + if (obj->queued_signals) { + int p; + + /* Handlers could drop their references to us */ + g_object_ref(obj); + + /* Emit the signals */ + for (p = 0; obj->queued_signals && p < MAX_PROPERTIES; p++) { + if (obj->queued_signals & SIGNAL_BIT(p)) { + slot_base_emit_property_change(obj, p); + } + } + + /* And release the temporary reference */ + g_object_unref(obj); + } +} + +static gulong slot_base_add_property_handler(OfonoSlotBase* obj, + guint property, slot_base_property_cb cb, gpointer user_data) +{ + if (G_LIKELY(cb)) { + /* + * 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 OfonoSlotBase + * as the first parameter. slot_base_property_changed() + * will do the conversion. + */ + OfonoSlotBaseClosure* closure = slot_base_closure_new(); + GCClosure* cc = &closure->cclosure; + + cc->closure.data = closure; + cc->callback = G_CALLBACK(slot_base_property_changed); + closure->cb = cb; + closure->user_data = user_data; + + return g_signal_connect_closure_by_id(obj, + slot_base_signals[SIGNAL_PROPERTY_CHANGED], + slot_base_property_quark(property), &cc->closure, + FALSE); + } + return 0; +} + +static void slot_base_remove_handler(gpointer obj, gulong id) +{ + if (obj && id) { + g_signal_handler_disconnect(obj, id); + } +} + +static gboolean config_get_enum(GKeyFile *file, const char *group, + const char *key, int *result, + const char *name, int value, ...) +{ + char *str = g_key_file_get_string(file, group, key, NULL); + + if (str) { + /* + * Some people are thinking that # is a comment + * anywhere on the line, not just at the beginning + */ + char *comment = strchr(str, '#'); + + if (comment) *comment = 0; + g_strstrip(str); + if (strcasecmp(str, name)) { + va_list args; + va_start(args, value); + while ((name = va_arg(args, char*)) != NULL) { + value = va_arg(args, int); + if (!strcasecmp(str, name)) { + break; + } + } + va_end(args); + } + + if (!name) { + ofono_error("Invalid %s config value (%s)", key, str); + } + + g_free(str); + + if (name) { + if (result) { + *result = value; + } + return TRUE; + } + } + + return FALSE; +} + +static GHashTable *slot_manager_inc_error_count(GHashTable *errors, + const char *group, const char *key) +{ + GKeyFile *storage = storage_open(NULL, SM_ERROR_STORAGE); + int n; + + /* Update life-time statistics */ + if (storage) { + g_key_file_set_integer(storage, group, key, + g_key_file_get_integer(storage, group, key, NULL) + 1); + storage_close(NULL, SM_ERROR_STORAGE, storage, TRUE); + } + + /* Update run-time error counts. The key is the error id which + * is always a static string */ + if (!errors) { + errors = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + n = GPOINTER_TO_INT(g_hash_table_lookup(errors, key)); + g_hash_table_insert(errors, g_strdup(key), GINT_TO_POINTER(n + 1)); + return errors; +} + +/*==========================================================================* + * OfonoSlotObject + *==========================================================================*/ + +static inline void slot_queue_property_change(OfonoSlotObject* slot, + enum ofono_slot_property p) +{ + slot->base.queued_signals |= SIGNAL_BIT(p); +} + +static inline void slot_emit_queued_signals(OfonoSlotObject *slot) +{ + slot_base_emit_queued_signals(&slot->base); +} + +static inline void slot_manager_update_modem_paths_and_notify + (OfonoSlotManagerObject *mgr, enum slot_manager_dbus_signal extra) +{ + slot_manager_dbus_signal(mgr->dbus, extra | + slot_manager_update_modem_paths(mgr, FALSE)); +} + +static void slot_update_data_role(OfonoSlotObject *slot, + enum ofono_slot_data_role role) +{ + if (slot->pub.data_role != role) { + slot->pub.data_role = role; + slot_queue_property_change(slot, + OFONO_SLOT_PROPERTY_DATA_ROLE); + } +} + +static void slot_update_cell_info_dbus(OfonoSlotObject *slot) +{ + struct ofono_modem *modem = slot->watch->modem; + + if (modem && slot->cellinfo_ctl && slot->cellinfo_ctl->info) { + if (!slot->cellinfo_dbus) { + slot->cellinfo_dbus = cell_info_dbus_new(modem, + slot->cellinfo_ctl); + } + } else { + if (slot->cellinfo_dbus) { + cell_info_dbus_free(slot->cellinfo_dbus); + slot->cellinfo_dbus = NULL; + } + } +} + +static void slot_manager_slot_modem_changed(struct ofono_watch *w, void *data) +{ + OfonoSlotObject *slot = OFONO_SLOT_OBJECT(data); + OfonoSlotManagerObject *mgr = slot->manager; + + slot_update_cell_info_dbus(slot); + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_NONE); + slot_manager_update_ready(mgr); + slot_manager_emit_all_queued_signals(mgr); +} + +static void slot_manager_slot_imsi_changed(struct ofono_watch *w, void *data) +{ + OfonoSlotObject *slot = OFONO_SLOT_OBJECT(data); + OfonoSlotManagerObject *mgr = slot->manager; + + slot_manager_dbus_signal(mgr->dbus, + slot_manager_update_modem_paths(mgr, TRUE)); + slot_manager_emit_all_queued_signals(mgr); +} + +static gboolean slot_check_slot_name(OfonoSlotObject *s, void *path) +{ + return strcmp(s->pub.path, path) ? SM_LOOP_CONTINUE : SM_LOOP_DONE; +} + +static gint slot_compare_path(gconstpointer p1, gconstpointer p2) +{ + OfonoSlotObject *s1 = OFONO_SLOT_OBJECT(p1); + OfonoSlotObject *s2 = OFONO_SLOT_OBJECT(p2); + + return strcmp(s1->pub.path, s2->pub.path); +} + +static void slot_object_finalize(GObject* obj) +{ + OfonoSlotObject *s = OFONO_SLOT_OBJECT(obj); + + if (s->errors) { + g_hash_table_destroy(s->errors); + } + sim_info_unref(s->siminfo); + sim_info_dbus_free(s->siminfo_dbus); + cell_info_control_unref(s->cellinfo_ctl); + ofono_watch_remove_all_handlers(s->watch, s->watch_event_id); + ofono_watch_unref(s->watch); + g_free(s->imei); + g_free(s->imeisv); + G_OBJECT_CLASS(ofono_slot_object_parent_class)->finalize(obj); +} + +static struct ofono_slot *slot_add_internal(OfonoSlotManagerObject *mgr, + const char *path, enum ofono_radio_access_mode techs, const char *imei, + const char *imeisv, enum ofono_slot_sim_presence sim_presence, + enum ofono_slot_flags flags) +{ + char *enabled_slots; + /* Extra ref for the caller */ + OfonoSlotObject *s = g_object_ref(g_object_new(OFONO_TYPE_SLOT, NULL)); + struct ofono_slot *pub = &s->pub; + struct ofono_watch *w = ofono_watch_new(path); + + s->manager = mgr; /* Not a ref */ + s->flags = flags; + s->watch = w; + s->siminfo = sim_info_new(path); + s->siminfo_dbus = sim_info_dbus_new(s->siminfo); + s->cellinfo_ctl = cell_info_control_get(path); + pub->path = w->path; + pub->imei = s->imei = g_strdup(imei); + pub->imeisv = s->imeisv = g_strdup(imeisv); + pub->sim_presence = sim_presence; + DBG("%s", slot_debug_prefix(s)); + + /* Check if it's enabled */ + enabled_slots = g_key_file_get_string(mgr->storage, + SM_STORE_GROUP, SM_STORE_ENABLED_SLOTS, NULL); + if (enabled_slots) { + char **strv = g_strsplit(enabled_slots, SM_STORE_SLOTS_SEP, 0); + + DBG("Enabled slots: %s", enabled_slots); + pub->enabled = gutil_strv_contains(strv, path); + g_strfreev(strv); + g_free(enabled_slots); + } + + /* Add it to the list */ + mgr->slots = g_slist_insert_sorted(mgr->slots, s, slot_compare_path); + slot_manager_reindex_slots(mgr); + + /* Register for events */ + s->watch_event_id[WATCH_EVENT_MODEM] = + ofono_watch_add_modem_changed_handler(w, + slot_manager_slot_modem_changed, s); + s->watch_event_id[WATCH_EVENT_ONLINE] = + ofono_watch_add_online_changed_handler(w, + slot_manager_slot_modem_changed, s); + s->watch_event_id[WATCH_EVENT_IMSI] = + ofono_watch_add_imsi_changed_handler(w, + slot_manager_slot_imsi_changed, s); + + /* Clear queued signals */ + mgr->base.queued_signals = 0; + return pub; +} + +/*==========================================================================* + * OfonoSlotManagerObject + *==========================================================================*/ + +/* + * slot_manager_foreach_driver() and slot_manager_foreach_slot() terminate + * the loop and return TRUE if the callback returns TRUE. If all callbacks + * return FALSE, they returns FALSE. It there are no drivers/slots, they + * return FALSE too. + */ + +static gboolean slot_manager_foreach_driver(OfonoSlotManagerObject *mgr, + gboolean (*fn)(OfonoSlotDriverReg *reg, void *user_data), + gconstpointer user_data) +{ + GSList *l = mgr->drivers; + gboolean done = FALSE; + + while (l && !done) { + GSList *next = l->next; + + /* The callback returns TRUE to terminate the loop */ + done = fn((OfonoSlotDriverReg*)l->data, (void*) user_data); + l = next; + } + + return done; +} + +static gboolean slot_manager_foreach_slot(OfonoSlotManagerObject *mgr, + gboolean (*fn)(OfonoSlotObject *slot, void *user_data), + gconstpointer user_data) +{ + GSList *l = mgr->slots; + gboolean done = FALSE; + + while (l && !done) { + GSList *next = l->next; + OfonoSlotObject *s = OFONO_SLOT_OBJECT(l->data); + + /* The callback returns TRUE to terminate the loop */ + done = fn(s, (void*) user_data); + l = next; + } + + return done; +} + +static inline void slot_manager_queue_property_change + (OfonoSlotManagerObject* mgr, enum ofono_slot_manager_property p) +{ + mgr->base.queued_signals |= SIGNAL_BIT(p); +} + +static inline void slot_manager_emit_queued_signals(OfonoSlotManagerObject *mgr) +{ + slot_base_emit_queued_signals(&mgr->base); +} + +static gboolean slot_manager_emit_all_queued_signals_cb + (OfonoSlotObject *slot, void *unused) +{ + slot_emit_queued_signals(slot); + return SM_LOOP_CONTINUE; +} + +static void slot_manager_emit_all_queued_signals(OfonoSlotManagerObject *mgr) +{ + slot_manager_emit_queued_signals(mgr); + slot_manager_foreach_slot(mgr, slot_manager_emit_all_queued_signals_cb, + NULL); +} + +static void slot_manager_reindex_slots(OfonoSlotManagerObject *mgr) +{ + const int n = g_slist_length(mgr->slots); + ofono_slot_ptr *ptr = g_new0(ofono_slot_ptr, n + 1); + int i = 0; + GSList *l; + + g_free(mgr->pslots); + mgr->pub.slots = mgr->pslots = ptr; + + for (l = mgr->slots; l; l = l->next) { + OfonoSlotObject *slot = OFONO_SLOT_OBJECT(l->data); + + slot->index = i++; + *ptr++ = &slot->pub; + } + + *ptr = NULL; +} + +static void slot_manager_update_dbus_block(OfonoSlotManagerObject *mgr) +{ + slot_manager_dbus_set_block(mgr->dbus, mgr->pub.ready ? + SLOT_MANAGER_DBUS_BLOCK_NONE : + SLOT_MANAGER_DBUS_BLOCK_ALL); +} + +static void slot_manager_set_config_string(OfonoSlotManagerObject *mgr, + const char *key, const char *value) +{ + if (value) { + g_key_file_set_string(mgr->storage, SM_STORE_GROUP, key, value); + } else { + g_key_file_remove_key(mgr->storage, SM_STORE_GROUP, key, NULL); + } + storage_sync(NULL, SM_STORE, mgr->storage); +} + +struct slot_manager_imsi_slot_data { + OfonoSlotObject *slot; + const char *imsi; /* NULL if we are looking for any slot with IMSI */ +}; + +static gboolean slot_manager_find_slot_imsi_cb(OfonoSlotObject *slot, + void *user_data) +{ + struct slot_manager_imsi_slot_data *data = user_data; + const char *slot_imsi = slot->watch->imsi; + + if (slot_imsi && (!data->imsi || !strcmp(slot_imsi, data->imsi))) { + data->slot = slot; + return SM_LOOP_DONE; + } else { + return SM_LOOP_CONTINUE; + } +} + +static OfonoSlotObject *slot_manager_find_slot_imsi(OfonoSlotManagerObject *mgr, + const char *imsi) +{ + struct slot_manager_imsi_slot_data data; + + memset(&data, 0, sizeof(data)); + data.imsi = imsi; + slot_manager_foreach_slot(mgr, slot_manager_find_slot_imsi_cb, &data); + return data.slot; +} + +static gboolean slot_manager_all_sims_are_initialized_cb(OfonoSlotObject *slot, + void *result) +{ + /* Not initialized if present and enabled but no IMSI yet */ + if (slot->pub.sim_presence == OFONO_SLOT_SIM_PRESENT && + slot->pub.enabled && !slot->watch->imsi) { + *((gboolean*)result) = FALSE; + return SM_LOOP_DONE; + } else { + return SM_LOOP_CONTINUE; + } +} + +static gboolean slot_manager_all_sims_are_initialized + (OfonoSlotManagerObject *mgr) +{ + gboolean result = TRUE; + + slot_manager_foreach_slot(mgr, + slot_manager_all_sims_are_initialized_cb, &result); + return result; +} + +/* + * Returns the event mask to be passed to slot_manager_dbus_signal. + * The caller has a chance to OR it with other bits. Also updates the + * queued signals mask but doesn't actually emit any signals. + */ +static enum slot_manager_dbus_signal slot_manager_update_modem_paths + (OfonoSlotManagerObject *mgr, gboolean imsi_change) +{ + enum slot_manager_dbus_signal mask = SLOT_MANAGER_DBUS_SIGNAL_NONE; + OfonoSlotObject *slot = NULL; + OfonoSlotObject *mms_slot = NULL; + OfonoSlotObject *old_data_slot = NULL; + OfonoSlotObject *new_data_slot = NULL; + + /* Voice */ + if (mgr->default_voice_imsi) { + slot = slot_manager_find_slot_imsi(mgr, + mgr->default_voice_imsi); + } else if (mgr->voice_slot && !imsi_change) { + /* Make sure that the slot is enabled and SIM is in */ + slot = slot_manager_find_slot_imsi(mgr, + mgr->voice_slot->watch->imsi); + } + + /* + * If there's no default voice SIM, we will find any SIM instead. + * One should always be able to make and receive a phone call + * if there's a working SIM in the phone. However if the + * previously selected voice SIM is inserted, we will switch + * back to it. + * + * A similar behavior can be configured for data SIM too. + */ + if (!slot) { + slot = slot_manager_find_slot_imsi(mgr, NULL); + } + + if (mgr->voice_slot != slot) { + slot_manager_queue_property_change(mgr, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_VOICE_PATH); + mask |= SLOT_MANAGER_DBUS_SIGNAL_VOICE_PATH; + mgr->voice_slot = slot; + if (slot) { + const char *path = slot->pub.path; + DBG("Default voice SIM at %s", path); + mgr->pub.default_voice_path = path; + } else { + DBG("No default voice SIM"); + mgr->pub.default_voice_path = NULL; + } + } + + /* Data */ + if (mgr->default_data_imsi) { + slot = slot_manager_find_slot_imsi(mgr, + mgr->default_data_imsi); + } else if (g_slist_length(mgr->slots) < 2) { + if (mgr->data_slot) { + /* Make sure that the slot is enabled and SIM is in */ + slot = slot_manager_find_slot_imsi(mgr, + mgr->data_slot->watch->imsi); + } else { + /* Check if anything is available */ + slot = slot_manager_find_slot_imsi(mgr, NULL); + } + } else { + slot = NULL; + } + + /* Check if we need to auto-select data SIM (always or once) */ + if (!slot && (mgr->auto_data_sim == SLOT_SIM_AUTO_SELECT_ON || + (mgr->auto_data_sim == SLOT_SIM_AUTO_SELECT_ONCE && + !mgr->auto_data_sim_done))) { + /* + * To actually make a selection we need all present SIMs + * to be initialized. Otherwise we may end up endlessly + * switching data SIMs back and forth. + */ + if (slot_manager_all_sims_are_initialized(mgr)) { + slot = slot_manager_find_slot_imsi(mgr, NULL); + if (slot && slot->watch->imsi && slot->watch->online && + mgr->auto_data_sim == + SLOT_SIM_AUTO_SELECT_ONCE) { + const char *imsi = slot->watch->imsi; + + /* + * Data SIM only needs to be auto-selected + * once and it's done. Write that down. + */ + DBG("Default data sim set to %s once", imsi); + mgr->auto_data_sim_done = TRUE; + g_key_file_set_boolean(mgr->storage, + SM_STORE_GROUP, + SM_STORE_AUTO_DATA_SIM_DONE, + mgr->auto_data_sim_done); + + g_free(mgr->default_data_imsi); + mgr->pub.default_data_imsi = + mgr->default_data_imsi = g_strdup(imsi); + g_key_file_set_string(mgr->storage, + SM_STORE_GROUP, + SM_STORE_DEFAULT_DATA_SIM, + imsi); + + storage_sync(NULL, SM_STORE, mgr->storage); + slot_manager_queue_property_change(mgr, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_DATA_IMSI); + mask |= SLOT_MANAGER_DBUS_SIGNAL_DATA_IMSI; + } + } else { + DBG("Skipping auto-selection of data SIM"); + } + } + + if (slot && !slot->watch->online) { + slot = NULL; + } + + if (mgr->mms_imsi) { + mms_slot = slot_manager_find_slot_imsi(mgr, mgr->mms_imsi); + } + + if (mms_slot && (mms_slot != slot || + (slot->flags & OFONO_SLOT_FLAG_SINGLE_CONTEXT))) { + /* + * Reset default data SIM if + * a) another SIM is temporarily selected for MMS; or + * b) this slot can't have more than one context active. + */ + slot = NULL; + } + + /* Are we actually switching data SIMs? */ + old_data_slot = mgr->mms_slot ? mgr->mms_slot : mgr->data_slot; + new_data_slot = mms_slot ? mms_slot : slot; + + if (mgr->data_slot != slot) { + slot_manager_queue_property_change(mgr, + OFONO_SLOT_MANAGER_PROPERTY_DEFAULT_DATA_PATH); + mask |= SLOT_MANAGER_DBUS_SIGNAL_DATA_PATH; + mgr->data_slot = slot; + if (slot) { + const char *path = slot->pub.path; + DBG("Default data SIM at %s", path); + mgr->pub.default_data_path = path; + } else { + DBG("No default data SIM"); + mgr->pub.default_data_path = NULL; + } + } + + if (mgr->mms_slot != mms_slot) { + slot_manager_queue_property_change(mgr, + OFONO_SLOT_MANAGER_PROPERTY_MMS_PATH); + mask |= SLOT_MANAGER_DBUS_SIGNAL_MMS_PATH; + mgr->mms_slot = mms_slot; + if (mms_slot) { + const char *path = mms_slot->pub.path; + DBG("MMS data SIM at %s", path); + mgr->pub.mms_path = path; + } else { + DBG("No MMS data SIM"); + mgr->pub.mms_path = NULL; + } + } + + if (old_data_slot != new_data_slot) { + /* Yes we are switching data SIMs */ + if (old_data_slot) { + slot_update_data_role(old_data_slot, + OFONO_SLOT_DATA_NONE); + } + if (new_data_slot) { + slot_update_data_role(new_data_slot, + (new_data_slot == mgr->data_slot) ? + OFONO_SLOT_DATA_INTERNET : + OFONO_SLOT_DATA_MMS); + } + } + + return mask; +} + +static gboolean slot_manager_update_ready_driver_cb(OfonoSlotDriverReg *reg, + void *unused) +{ + return reg->start_id ? SM_LOOP_DONE : SM_LOOP_CONTINUE; +} + +static gboolean slot_manager_update_ready_slot_cb(OfonoSlotObject *slot, + void *unused) +{ + return (slot->pub.sim_presence == OFONO_SLOT_SIM_UNKNOWN) ? + SM_LOOP_DONE : SM_LOOP_CONTINUE; +} + +static void slot_manager_update_ready(OfonoSlotManagerObject *mgr) +{ + struct ofono_slot_manager *m = &mgr->pub; + + /* ready is a one-way flag */ + if (!m->ready && + /* + * slot_manager_foreach_xxx return FALSE if either all + * callbacks returned SM_LOOP_CONTINUE or there are no + * drivers/slots. In either case we are ready. + */ + !slot_manager_foreach_driver(mgr, + slot_manager_update_ready_driver_cb, NULL) && + !slot_manager_foreach_slot(mgr, + slot_manager_update_ready_slot_cb, NULL)) { + m->ready = TRUE; + DBG("ready"); + slot_manager_update_dbus_block(mgr); + slot_manager_queue_property_change(mgr, + OFONO_SLOT_MANAGER_PROPERTY_READY); + slot_manager_dbus_signal(mgr->dbus, + SLOT_MANAGER_DBUS_SIGNAL_READY); + } +} + +static gboolean slot_manager_start_driver_cb(OfonoSlotDriverReg *reg, + void *unused) +{ + const struct ofono_slot_driver *d = reg->driver; + OfonoSlotManagerObject *mgr = reg->manager; + + if (d->init) { + reg->driver_data = d->init(&mgr->pub); + } + if (d->start) { + reg->start_id = d->start(reg->driver_data); + } + return SM_LOOP_CONTINUE; +} + +static gboolean slot_manager_init_countdown_cb(gpointer user_data) +{ + OfonoSlotManagerObject *mgr = OFONO_SLOT_MANAGER_OBJECT(user_data); + + mgr->init_countdown--; + if (!mgr->init_countdown) { + mgr->init_id = 0; + DBG("done with registrations"); + slot_manager_foreach_driver(mgr, + slot_manager_start_driver_cb, NULL); + slot_manager_update_ready(mgr); + slot_manager_emit_queued_signals(mgr); + return G_SOURCE_REMOVE; + } else { + /* Keep on waiting */ + return G_SOURCE_CONTINUE; + } +} + +static void slot_driver_reg_free(OfonoSlotDriverReg *r) +{ + const struct ofono_slot_driver *d = r->driver; + + if (r->start_id && d->cancel) { + d->cancel(r->driver_data, r->start_id); + } + if (d->cleanup) { + d->cleanup(r->driver_data); + } + g_free(r); +} + +static OfonoSlotDriverReg *slot_manager_register_driver + (OfonoSlotManagerObject *mgr, const struct ofono_slot_driver *d) +{ + /* Only allow registrations at startup */ + if (mgr->init_countdown) { + OfonoSlotDriverReg *reg = g_new0(OfonoSlotDriverReg, 1); + + reg->manager = mgr; + reg->driver = d; + mgr->drivers = g_slist_append(mgr->drivers, reg); + return reg; + } else { + ofono_error("Refusing to register slot driver %s", d->name); + return NULL; + } +} + +static gboolean ofono_slot_manager_detach(OfonoSlotObject *s, void *p) +{ + s->manager = NULL; + return SM_LOOP_CONTINUE; +} + +static void ofono_slot_manager_object_finalize(GObject* obj) +{ + OfonoSlotManagerObject *mgr = OFONO_SLOT_MANAGER_OBJECT(obj); + + /* Drivers are unregistered by __ofono_slot_manager_cleanup */ + GASSERT(!mgr->drivers); + g_slist_free_full(mgr->slots, g_object_unref); + g_free(mgr->pslots); + slot_manager_dbus_free(mgr->dbus); + if (mgr->init_id) { + g_source_remove(mgr->init_id); + } + if (mgr->errors) { + g_hash_table_destroy(mgr->errors); + } + g_key_file_free(mgr->storage); + g_free(mgr->default_voice_imsi); + g_free(mgr->default_data_imsi); + g_free(mgr->mms_imsi); + G_OBJECT_CLASS(ofono_slot_manager_object_parent_class)->finalize(obj); +} + +static OfonoSlotManagerObject *ofono_slot_manager_new() +{ + return g_object_new(OFONO_TYPE_SLOT_MANAGER, NULL); +} + +/*==========================================================================* + * slot_manager_dbus callbacks + *==========================================================================*/ + +struct slot_manager_set_enabled_slots_data { + gchar * const * enabled; + gboolean all_enabled; + gboolean changed; +}; + +static gboolean slot_manager_set_enabled_slots_cb(OfonoSlotObject *slot, + void *user_data) +{ + struct slot_manager_set_enabled_slots_data *data = user_data; + struct ofono_slot *s = &slot->pub; + const gboolean was_enabled = s->enabled; + + s->enabled = gutil_strv_contains(data->enabled, s->path); + if ((was_enabled && !s->enabled) || (!was_enabled && s->enabled)) { + DBG("%s %s", slot_debug_prefix(slot), s->enabled ? + "enabled" : "disabled"); + slot_queue_property_change(slot, OFONO_SLOT_PROPERTY_ENABLED); + data->changed = TRUE; + } + + if (!s->enabled) { + data->all_enabled = FALSE; + } + + return SM_LOOP_CONTINUE; +} + +static gboolean slot_manager_enabled_slots_cb(OfonoSlotObject *slot, + void *user_data) +{ + struct ofono_slot *s = &slot->pub; + + if (s->enabled) { + char ***list = user_data; + + *list = gutil_strv_add(*list, s->path); + } + + return SM_LOOP_CONTINUE; +} + +static GHashTable *slot_manager_dbus_get_errors + (const struct ofono_slot_manager *m) +{ + return slot_manager_object_cast(m)->errors; +} + +static GHashTable *slot_manager_dbus_get_slot_errors + (const struct ofono_slot *s) +{ + return slot_object_cast(s)->errors; +} + +static void slot_manager_dbus_set_enabled_slots(struct ofono_slot_manager *m, + char **slots) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + struct slot_manager_set_enabled_slots_data cbd; + + cbd.enabled = slots; + cbd.changed = FALSE; + cbd.all_enabled = TRUE; + slot_manager_foreach_slot(mgr, slot_manager_set_enabled_slots_cb, &cbd); + if (cbd.changed) { + char **new_slots = NULL; + + slot_manager_foreach_slot(mgr,slot_manager_enabled_slots_cb, + &new_slots); + + /* + * Save the new config value. If it exactly matches the list + * of available modems, delete the setting because that's the + * default behavior. + */ + if (cbd.all_enabled) { + slot_manager_set_config_string(mgr, + SM_STORE_ENABLED_SLOTS, NULL); + } else { + const char *value; + char *tmp; + + if (new_slots) { + tmp = g_strjoinv(SM_STORE_SLOTS_SEP, new_slots); + value = tmp; + } else { + tmp = NULL; + value = ""; + } + + slot_manager_set_config_string(mgr, + SM_STORE_ENABLED_SLOTS, value); + g_free(tmp); + } + g_strfreev(new_slots); + + /* Update paths and emit signals */ + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_ENABLED_SLOTS); + slot_manager_emit_all_queued_signals(mgr); + } +} + +static gboolean slot_manager_dbus_set_mms_imsi(struct ofono_slot_manager *m, + const char *imsi) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + + if (imsi && imsi[0]) { + if (g_strcmp0(mgr->mms_imsi, imsi)) { + if (slot_manager_find_slot_imsi(mgr, imsi)) { + DBG("MMS sim %s", imsi); + g_free(mgr->mms_imsi); + m->mms_imsi = mgr->mms_imsi = g_strdup(imsi); + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_MMS_IMSI); + slot_manager_emit_all_queued_signals(mgr); + } else { + DBG("IMSI not found: %s", imsi); + return FALSE; + } + } + } else { + if (mgr->mms_imsi) { + DBG("No MMS sim"); + g_free(mgr->mms_imsi); + m->mms_imsi = mgr->mms_imsi = NULL; + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_MMS_IMSI); + slot_manager_emit_all_queued_signals(mgr); + } + } + + return TRUE; +} + +static void slot_manager_dbus_set_default_voice_imsi + (struct ofono_slot_manager *m, const char *imsi) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + + if (g_strcmp0(mgr->default_voice_imsi, imsi)) { + DBG("Default voice sim set to %s", imsi ? imsi : "(auto)"); + g_free(mgr->default_voice_imsi); + m->default_voice_imsi = + mgr->default_voice_imsi = g_strdup(imsi); + slot_manager_set_config_string(mgr, + SM_STORE_DEFAULT_VOICE_SIM, imsi); + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_VOICE_IMSI); + slot_manager_emit_all_queued_signals(mgr); + } +} + +static void slot_manager_dbus_set_default_data_imsi + (struct ofono_slot_manager *m, const char *imsi) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + + if (g_strcmp0(mgr->default_data_imsi, imsi)) { + DBG("Default data sim set to %s", imsi ? imsi : "(auto)"); + g_free(mgr->default_data_imsi); + m->default_data_imsi = + mgr->default_data_imsi = g_strdup(imsi); + slot_manager_set_config_string(mgr, + SM_STORE_DEFAULT_DATA_SIM, imsi); + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_DATA_IMSI); + slot_manager_emit_all_queued_signals(mgr); + } +} + +/*==========================================================================* + * API + *==========================================================================*/ + +struct ofono_slot_driver_data *ofono_slot_driver_get_data + (struct ofono_slot_driver_reg *reg) +{ + return reg ? reg->driver_data : NULL; +} + +struct ofono_slot_manager *ofono_slot_manager_ref(struct ofono_slot_manager *m) +{ + if (m) { + g_object_ref(slot_manager_object_cast(m)); + return m; + } + return NULL; +} + +void ofono_slot_manager_unref(struct ofono_slot_manager *m) +{ + if (m) { + g_object_unref(slot_manager_object_cast(m)); + } +} + +void ofono_slot_driver_started(OfonoSlotDriverReg *reg) +{ + if (reg) { + OfonoSlotManagerObject *mgr = reg->manager; + + reg->start_id = 0; + g_object_ref(mgr); + slot_manager_update_ready(mgr); + slot_manager_emit_all_queued_signals(mgr); + g_object_unref(mgr); + } +} + +unsigned long ofono_slot_manager_add_property_handler( + struct ofono_slot_manager *m, enum ofono_slot_manager_property p, + ofono_slot_manager_property_cb cb, void* data) +{ + return (p >= OFONO_SLOT_MANAGER_PROPERTY_ANY && + p <= OFONO_SLOT_MANAGER_PROPERTY_LAST) ? + slot_base_add_property_handler(slot_base_cast(m), p, + (slot_base_property_cb)cb, data) : 0; +} + +void ofono_slot_manager_remove_handler(struct ofono_slot_manager *m, + unsigned long id) +{ + slot_base_remove_handler(slot_manager_object_cast(m), id); +} + +void ofono_slot_manager_remove_handlers(struct ofono_slot_manager *m, + unsigned long *ids, unsigned int n) +{ + gutil_disconnect_handlers(slot_manager_object_cast(m), ids, n); +} + +void ofono_slot_manager_error(struct ofono_slot_manager *m, const char *key, + const char *message) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + + if (mgr) { + mgr->errors = slot_manager_inc_error_count(mgr->errors, + SM_ERROR_COMMON_SECTION, key); + slot_manager_dbus_signal_error(mgr->dbus, key, message); + } +} + +struct ofono_slot *ofono_slot_add(struct ofono_slot_manager *m, + const char *path, enum ofono_radio_access_mode techs, const char *imei, + const char *imeisv, enum ofono_slot_sim_presence sim_presence, + enum ofono_slot_flags flags) +{ + OfonoSlotManagerObject *mgr = slot_manager_object_cast(m); + + /* + * Only accept these calls when we are starting! We have been + * assuming all along that the number of slots is known right + * from startup. Perhaps it wasn't a super bright idea because + * there are USB modems which can appear (and disappear) pretty + * much at any time. This has to be dealt with somehow at some + * point but for now let's leave it as is. + */ + if (mgr && !mgr->pub.ready && path && + g_variant_is_object_path(path) && imei && + !slot_manager_foreach_slot(mgr, slot_check_slot_name, path)) { + return slot_add_internal(mgr, path, techs, imei, imeisv, + sim_presence, flags); + } else if (path) { + ofono_error("Refusing to register slot %s", path); + } + return NULL; +} + +struct ofono_slot *ofono_slot_ref(struct ofono_slot *s) +{ + if (s) { + g_object_ref(slot_object_cast(s)); + return s; + } + return NULL; +} + +void ofono_slot_unref(struct ofono_slot *s) +{ + if (s) { + g_object_unref(slot_object_cast(s)); + } +} + +void ofono_slot_error(struct ofono_slot *s, const char *key, const char *msg) +{ + OfonoSlotObject *slot = slot_object_cast(s); + + if (slot) { + /* slot->path always starts with a slash, skip it */ + const char *section = s->path + 1; + OfonoSlotManagerObject *mgr = slot->manager; + + slot->errors = slot_manager_inc_error_count(slot->errors, + section, key); + if (mgr) { + slot_manager_dbus_signal_modem_error(mgr->dbus, + slot->index, key, msg); + } + } +} + +void ofono_slot_set_cell_info(struct ofono_slot *s, struct ofono_cell_info *ci) +{ + OfonoSlotObject *slot = slot_object_cast(s); + + if (slot) { + CellInfoControl *ctl = slot->cellinfo_ctl; + + if (ctl->info != ci) { + cell_info_control_set_cell_info(ctl, ci); + cell_info_dbus_free(slot->cellinfo_dbus); + slot->cellinfo_dbus = NULL; + slot_update_cell_info_dbus(slot); + } + } +} + +void ofono_slot_set_cell_info_update_interval(struct ofono_slot *s, + void* tag, int interval_ms) +{ /* Since mer/1.25+git7 */ + OfonoSlotObject *slot = slot_object_cast(s); + + if (slot) { + cell_info_control_set_update_interval(slot->cellinfo_ctl, tag, + interval_ms); + } +} + +void ofono_slot_drop_cell_info_requests(struct ofono_slot *s, void* tag) +{ /* Since mer/1.25+git7 */ + OfonoSlotObject *slot = slot_object_cast(s); + + if (slot) { + cell_info_control_drop_requests(slot->cellinfo_ctl, tag); + } +} + +unsigned long ofono_slot_add_property_handler(struct ofono_slot *s, + enum ofono_slot_property p, ofono_slot_property_cb cb, void* data) +{ + return (p >= OFONO_SLOT_PROPERTY_ANY && p <= OFONO_SLOT_PROPERTY_LAST) ? + slot_base_add_property_handler(slot_base_cast(s), p, + (slot_base_property_cb)cb, data) : 0; +} + +void ofono_slot_remove_handler(struct ofono_slot *s, unsigned long id) +{ + slot_base_remove_handler(slot_object_cast(s), id); +} + +void ofono_slot_remove_handlers(struct ofono_slot *s, unsigned long *ids, + unsigned int n) +{ + gutil_disconnect_handlers(slot_object_cast(s), ids, n); +} + +void ofono_slot_set_sim_presence(struct ofono_slot *s, + enum ofono_slot_sim_presence sim_presence) +{ + if (s && s->sim_presence != sim_presence) { + OfonoSlotObject *slot = slot_object_cast(s); + OfonoSlotManagerObject *mgr = slot->manager; + + s->sim_presence = sim_presence; + slot_queue_property_change(slot, + OFONO_SLOT_PROPERTY_SIM_PRESENCE); + slot_manager_dbus_signal_sim(mgr->dbus, slot->index, + SLOT_MANAGER_DBUS_SLOT_SIGNAL_PRESENT); + slot_manager_update_modem_paths_and_notify(mgr, + SLOT_MANAGER_DBUS_SIGNAL_NONE); + slot_manager_update_ready(mgr); + slot_manager_emit_all_queued_signals(mgr); + } +} + +/*==========================================================================* + * GObject boilerplate + *==========================================================================*/ + +static void ofono_slot_base_init(OfonoSlotBase *base) +{ +} + +static void ofono_slot_object_init(OfonoSlotObject *slot) +{ + slot->pub.enabled = TRUE; /* Enabled by default */ +} + +static void ofono_slot_manager_object_init(OfonoSlotManagerObject *mgr) +{ + static const struct slot_manager_dbus_cb dbus_cb = { + slot_manager_dbus_get_errors, + slot_manager_dbus_get_slot_errors, + slot_manager_dbus_set_enabled_slots, + slot_manager_dbus_set_mms_imsi, + slot_manager_dbus_set_default_voice_imsi, + slot_manager_dbus_set_default_data_imsi + }; + + GKeyFile *conf = g_key_file_new(); + char* fn = g_build_filename(ofono_config_dir(), SM_CONFIG_FILE, NULL); + + /* Load config */ + if (g_key_file_load_from_file(conf, fn, 0, NULL)) { + int ival; + + DBG("Loading configuration file %s", fn); + if (config_get_enum(conf, SM_CONFIG_GROUP, + SM_CONFIG_KEY_AUTO_DATA_SIM, &ival, + "off", SLOT_SIM_AUTO_SELECT_OFF, + "once", SLOT_SIM_AUTO_SELECT_ONCE, + "always", SLOT_SIM_AUTO_SELECT_ON, + "on", SLOT_SIM_AUTO_SELECT_ON, NULL)) { + DBG("Automatic data SIM selection: %s", + ival == SLOT_SIM_AUTO_SELECT_ONCE ? "once": + ival == SLOT_SIM_AUTO_SELECT_ON ? "on": + "off"); + mgr->auto_data_sim = ival; + } + } + g_key_file_free(conf); + g_free(fn); + + /* Load settings */ + mgr->storage = storage_open(NULL, SM_STORE); + mgr->pub.default_voice_imsi = mgr->default_voice_imsi = + g_key_file_get_string(mgr->storage, SM_STORE_GROUP, + SM_STORE_DEFAULT_VOICE_SIM, NULL); + mgr->pub.default_data_imsi = mgr->default_data_imsi = + g_key_file_get_string(mgr->storage, SM_STORE_GROUP, + SM_STORE_DEFAULT_DATA_SIM, NULL); + mgr->auto_data_sim_done = g_key_file_get_boolean(mgr->storage, + SM_STORE_GROUP, SM_STORE_AUTO_DATA_SIM_DONE, NULL); + + DBG("Default voice sim is %s", mgr->default_voice_imsi ? + mgr->default_voice_imsi : "(auto)"); + DBG("Default data sim is %s", mgr->default_data_imsi ? + mgr->default_data_imsi : "(auto)"); + + /* Delay the initialization until after all drivers get registered */ + mgr->init_countdown = SM_INIT_IDLE_COUNT; + mgr->init_id = g_idle_add(slot_manager_init_countdown_cb, mgr); + + /* And block all requests until that happens */ + mgr->dbus = slot_manager_dbus_new(&mgr->pub, &dbus_cb); + slot_manager_dbus_set_block(mgr->dbus, SLOT_MANAGER_DBUS_BLOCK_ALL); +} + +static void ofono_slot_base_class_init(OfonoSlotBaseClass *c) +{ + GType type = G_OBJECT_CLASS_TYPE(c); + + slot_base_signals[SIGNAL_PROPERTY_CHANGED] = + g_signal_new(SIGNAL_PROPERTY_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, 0, + NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); +} + +static void ofono_slot_object_class_init(OfonoSlotObjectClass *c) +{ + G_OBJECT_CLASS(c)->finalize = slot_object_finalize; +} + +static void ofono_slot_manager_object_class_init(OfonoSlotManagerObjectClass *c) +{ + G_OBJECT_CLASS(c)->finalize = ofono_slot_manager_object_finalize; +} + +/*==========================================================================* + * Internal API + *==========================================================================*/ + +static OfonoSlotManagerObject *slot_manager = NULL; + +void __ofono_slot_manager_init(void) +{ + /* + * Let's not assume what's called first, ofono_slot_driver_register() + * or __ofono_slot_manager_init() + */ + if (!slot_manager) { + slot_manager = ofono_slot_manager_new(); + } +} + +void __ofono_slot_manager_cleanup(void) +{ + if (slot_manager) { + OfonoSlotManagerObject *mgr = slot_manager; + GSList *drivers = mgr->drivers; + + /* + * This cleanup needs to be done before dropping the ref. + * Drivers may keep a ref to slot_manager which would keep + * OfonoSlotManagerObject alive even after we drop our ref. + */ + slot_manager = NULL; + slot_manager_foreach_slot(mgr, ofono_slot_manager_detach, NULL); + mgr->drivers = NULL; + g_slist_free_full(drivers,(GDestroyNotify)slot_driver_reg_free); + g_object_unref(mgr); + } +} + +/*==========================================================================* + * Driver registration + * Requires access to slot_manager variable + *==========================================================================*/ + +OfonoSlotDriverReg *ofono_slot_driver_register + (const struct ofono_slot_driver *d) +{ + if (d) { + /* + * Let's not assume what's called first, + * ofono_slot_driver_register() or __ofono_slot_manager_init() + */ + if (!slot_manager) { + slot_manager = ofono_slot_manager_new(); + } + + return slot_manager_register_driver(slot_manager, d); + } + return NULL; +} + +void ofono_slot_driver_unregister(OfonoSlotDriverReg *reg) +{ + if (reg && slot_manager) { + GSList* l = g_slist_find(slot_manager->drivers, reg); + + if (l) { + slot_manager->drivers = g_slist_delete_link + (slot_manager->drivers, l); + slot_driver_reg_free(reg); + } + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */