/* * * oFono - Open Source Telephony * * Copyright (C) 2011 ST-Ericsson AB. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #define OFONO_API_SUBJECT_TO_CHANGE #include #include #include #include /* * ST-Ericsson's Modem Init Daemon is used for controlling the modem power * cycles and provides a dbus API for modem state and properties. */ #define MGR_SERVICE "com.stericsson.modeminit" #define MGR_INTERFACE MGR_SERVICE ".Manager" #define MGR_GET_MODEMS "GetModems" #define GET_MODEMS_TIMEOUT 5000 #define MGR_MODEM_INTERFACE MGR_SERVICE ".Modem" #define PROPERTY_CHANGED "PropertyChanged" enum ste_state { STE_STATE_OFF, STE_STATE_READY, STE_STATE_RESET }; enum ste_operation { STE_OP_STARTING, STE_OP_READY, STE_OP_RESTART, STE_OP_OFF }; struct ste_modem { char *path; struct ofono_modem *modem; enum ste_state state; char *serial; char *interface; }; static GHashTable *modem_list; static guint modem_daemon_watch; static guint property_changed_watch; static DBusConnection *connection; static void state_change(struct ste_modem *stemodem, enum ste_operation op) { switch (stemodem->state) { case STE_STATE_OFF: /* * The STE Modem is in state OFF and we're waiting for * the Modem Init Daemon to signal that modem is ready * in order to create and register the modem. */ switch (op) { case STE_OP_READY: stemodem->modem = ofono_modem_create(stemodem->serial, "ste"); if (stemodem->modem == NULL) { ofono_error("Could not create modem %s, %s", stemodem->path, stemodem->serial); return; } DBG("register modem %s, %s", stemodem->path, stemodem->serial); if (stemodem->interface != NULL) ofono_modem_set_string(stemodem->modem, "Interface", stemodem->interface); ofono_modem_register(stemodem->modem); stemodem->state = STE_STATE_READY; break; case STE_OP_STARTING: case STE_OP_RESTART: case STE_OP_OFF: break; } break; case STE_STATE_READY: /* * The STE Modem is ready and the modem has been created * and registered in oFono. In this state two things can * happen: Modem restarts or is turned off. Turning off * the modem is an exceptional situation e.g. high-temperature, * low battery or upgrade. In this scenario we remove the * STE modem from oFono. */ switch (op) { case STE_OP_READY: break; case STE_OP_STARTING: case STE_OP_RESTART: DBG("reset ongoing %s", stemodem->path); /* Note: Consider to power off modem here? */ stemodem->state = STE_STATE_RESET; break; case STE_OP_OFF: DBG("STE modem unregistering %s", stemodem->path); ofono_modem_remove(stemodem->modem); stemodem->modem = NULL; stemodem->state = STE_STATE_OFF; break; } break; case STE_STATE_RESET: /* * The STE Modem is resetting.In this state two things can * happen: Modem restarts succeeds, or modem is turned off. */ switch (op) { case STE_OP_STARTING: case STE_OP_RESTART: break; case STE_OP_READY: DBG("STE modem reset complete %s", stemodem->path); if (ofono_modem_get_powered(stemodem->modem)) ofono_modem_reset(stemodem->modem); stemodem->state = STE_STATE_READY; break; case STE_OP_OFF: DBG("STE modem unregistering %s", stemodem->path); ofono_modem_remove(stemodem->modem); stemodem->modem = NULL; stemodem->state = STE_STATE_OFF; break; } break; } } static void update_property(struct ste_modem *stemodem, const char *prop, DBusMessageIter *iter, enum ste_operation *op, gboolean *op_valid) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return; dbus_message_iter_get_basic(iter, &value); if (g_strcmp0(prop, "State") == 0) { *op_valid = TRUE; if (g_strcmp0(value, "booting") == 0) *op = STE_OP_STARTING; else if (g_strcmp0(value, "upgrading") == 0) *op = STE_OP_OFF; else if (g_strcmp0(value, "ready") == 0) *op = STE_OP_READY; else if (g_strcmp0(value, "off") == 0) *op = STE_OP_OFF; else if (g_strcmp0(value, "dumping") == 0) *op = STE_OP_RESTART; else *op_valid = FALSE; } else if (g_strcmp0(prop, "Interface") == 0) { g_free(stemodem->interface); stemodem->interface = g_strdup(value); } else if (g_strcmp0(prop, "Serial") == 0) { g_free(stemodem->serial); stemodem->serial = g_strdup(value); } } static void update_modem_properties(const char *path, DBusMessageIter *iter) { enum ste_operation operation; gboolean operation_valid; struct ste_modem *stemodem = g_hash_table_lookup(modem_list, path); if (stemodem == NULL) { stemodem = g_try_new0(struct ste_modem, 1); if (stemodem == NULL) return; stemodem->path = g_strdup(path); stemodem->state = STE_STATE_OFF; g_hash_table_insert(modem_list, stemodem->path, stemodem); } while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry, value; const char *key; dbus_message_iter_recurse(iter, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); update_property(stemodem, key, &value, &operation, &operation_valid); dbus_message_iter_next(iter); } if (operation_valid) state_change(stemodem, operation); } static void get_modems_reply(DBusPendingCall *call, void *user_data) { DBusMessageIter iter, list; DBusError err; DBusMessage *reply = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) { ofono_error("%s: %s\n", err.name, err.message); dbus_error_free(&err); goto done; } if (!dbus_message_has_signature(reply, "a(oa{sv})")) goto done; if (!dbus_message_iter_init(reply, &iter)) goto done; dbus_message_iter_recurse(&iter, &list); while (dbus_message_iter_get_arg_type(&list) == DBUS_TYPE_STRUCT) { DBusMessageIter entry, dict; const char *path; dbus_message_iter_recurse(&list, &entry); dbus_message_iter_get_basic(&entry, &path); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &dict); update_modem_properties(path, &dict); dbus_message_iter_next(&list); } done: dbus_message_unref(reply); } static void get_modems(void) { DBusMessage *message; DBusPendingCall *call; message = dbus_message_new_method_call(MGR_SERVICE, "/", MGR_INTERFACE, MGR_GET_MODEMS); if (message == NULL) { ofono_error("Unable to allocate new D-Bus message"); goto error; } dbus_message_set_auto_start(message, FALSE); if (!dbus_connection_send_with_reply(connection, message, &call, GET_MODEMS_TIMEOUT)) { ofono_error("Sending D-Bus message failed"); goto error; } if (call == NULL) { DBG("D-Bus connection not available"); goto error; } dbus_pending_call_set_notify(call, get_modems_reply, NULL, NULL); dbus_pending_call_unref(call); error: dbus_message_unref(message); } static gboolean property_changed(DBusConnection *conn, DBusMessage *message, void *user_data) { DBusMessageIter iter; struct ste_modem *stemodem; const char *key; enum ste_operation operation; gboolean operation_valid; stemodem = g_hash_table_lookup(modem_list, dbus_message_get_path(message)); if (stemodem == NULL) return TRUE; if (!dbus_message_iter_init(message, &iter)) return TRUE; dbus_message_iter_get_basic(&iter, &key); dbus_message_iter_next(&iter); update_property(stemodem, key, &iter, &operation, &operation_valid); if (operation_valid) state_change(stemodem, operation); return TRUE; } static void mgr_connect(DBusConnection *conn, void *user_data) { property_changed_watch = g_dbus_add_signal_watch(conn, MGR_SERVICE, NULL, MGR_MODEM_INTERFACE, PROPERTY_CHANGED, property_changed, NULL, NULL); get_modems(); } static void mgr_disconnect(DBusConnection *conn, void *user_data) { g_hash_table_remove_all(modem_list); g_dbus_remove_watch(conn, property_changed_watch); property_changed_watch = 0; } static void destroy_stemodem(gpointer data) { struct ste_modem *stemodem = data; ofono_modem_remove(stemodem->modem); g_free(stemodem->interface); g_free(stemodem->path); g_free(stemodem->serial); g_free(stemodem); } static int stemgr_init(void) { connection = ofono_dbus_get_connection(); modem_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, destroy_stemodem); modem_daemon_watch = g_dbus_add_service_watch(connection, MGR_SERVICE, mgr_connect, mgr_disconnect, NULL, NULL); return 0; } static void stemgr_exit(void) { g_hash_table_destroy(modem_list); g_dbus_remove_watch(connection, modem_daemon_watch); if (property_changed_watch > 0) g_dbus_remove_watch(connection, property_changed_watch); } OFONO_PLUGIN_DEFINE(stemgr, "ST-Ericsson Modem Init Daemon detection", VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT, stemgr_init, stemgr_exit)