/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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: * * Copyright (C) 2011 Ammonit Measurement GmbH * Author: Aleksander Morgado */ #include #include #include #include #include #include #include "mm-errors.h" #include "mm-modem-helpers.h" #include "mm-modem-iridium-gsm.h" #include "mm-log.h" /* NOTE: * We are simulating here the Iridium modem as if it were a pure GSM device * (even if it of course isn't). This is because the Iridium modems implement * GSM-like AT commands and therefore we can base the Iridium plugin on the * generic GSM implementation. So: * - We report Access Technology as pure GSM. * - We allow only 2G-related allowed modes. * */ static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); G_DEFINE_TYPE_EXTENDED (MMModemIridiumGsm, mm_modem_iridium_gsm, MM_TYPE_GENERIC_GSM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init)) #define MM_MODEM_IRIDIUM_GSM_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_IRIDIUM_GSM, MMModemIridiumGsmPrivate)) typedef struct { /* Current allowed mode */ MMModemGsmAllowedMode allowed_mode; } MMModemIridiumGsmPrivate; MMModem * mm_modem_iridium_gsm_new (const char *device, const char *driver, const char *plugin, guint32 vendor, guint32 product) { g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (driver != NULL, NULL); g_return_val_if_fail (plugin != NULL, NULL); return MM_MODEM (g_object_new (MM_TYPE_MODEM_IRIDIUM_GSM, MM_MODEM_MASTER_DEVICE, device, MM_MODEM_DRIVER, driver, MM_MODEM_PLUGIN, plugin, MM_MODEM_HW_VID, vendor, MM_MODEM_HW_PID, product, /* If up to 3 commands get timed out, mark * the modem as removed */ MM_MODEM_BASE_MAX_TIMEOUTS, 3, /* Allow up to 200s to setup the IP configuration * (used by NM when launching pppd) */ MM_MODEM_IP_TIMEOUT, 200, NULL)); } static void port_grabbed (MMGenericGsm *gsm, MMPort *port, MMAtPortFlags pflags, gpointer user_data) { if (MM_IS_AT_SERIAL_PORT (port)) { /* Set 9600 baudrate by default */ g_object_set (G_OBJECT (port), MM_SERIAL_PORT_BAUD, 9600, NULL); } } static gboolean after_atz_sleep_cb (gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMAtSerialPort *port; port = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (info->modem), MM_AT_PORT_FLAG_PRIMARY); g_assert (port); /* And send remaining initialization commands here, we do not care about the * responses. Note we also don't need a power-up command! */ mm_at_serial_port_queue_command (port, "E0 V1", 10, NULL, NULL); mm_at_serial_port_queue_command (port, "+CMEE=1", 10, NULL, NULL); /* Bearer service type set to 9600bps (V.110), which behaves better than the * default 9600bps (V.32). */ mm_at_serial_port_queue_command (port, "+CBST=71,0,1", 3, NULL, NULL); mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); return FALSE; } static void atz_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return; if (error) { mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); } else { /* Once ATZ reply is received, we need to wait a bit before going on, * otherwise, the next commands given will receive garbage as reply * (500ms should be enough) */ g_timeout_add (500, after_atz_sleep_cb, info); } } static void enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; if (error) mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); else { /* Just ATZ alone first */ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "Z", 3, atz_done, user_data); } } static void do_enable (MMGenericGsm *modem, MMModemFn callback, gpointer user_data) { MMCallbackInfo *info; MMAtSerialPort *primary; primary = mm_generic_gsm_get_at_port (modem, MM_AT_PORT_FLAG_PRIMARY); g_assert (primary); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); mm_serial_port_flash (MM_SERIAL_PORT (primary), 100, FALSE, enable_flash_done, info); } static void disconnect_flash_done (MMSerialPort *port, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return; if (error) { /* Ignore "NO CARRIER" response when modem disconnects and any flash * failures we might encounter. Other errors are hard errors. */ if ( !g_error_matches (error, MM_MODEM_CONNECT_ERROR, MM_MODEM_CONNECT_ERROR_NO_CARRIER) && !g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_FLASH_FAILED)) { info->error = g_error_copy (error); mm_callback_info_schedule (info); return; } } /* Send ATE0 after reopening, and continue */ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "E0", 3, NULL, NULL); mm_callback_info_schedule (info); } static gboolean after_disconnect_sleep_cb (gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *)user_data; MMAtSerialPort *primary; GError *error = NULL; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return FALSE; primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (info->modem), MM_AT_PORT_FLAG_PRIMARY); g_assert (primary); /* Propagate errors when reopening the port */ if (!mm_serial_port_open (MM_SERIAL_PORT (primary), &error)) { info->error = g_error_copy (error); mm_callback_info_schedule (info); } mm_serial_port_flash (MM_SERIAL_PORT (primary), 1000, TRUE, disconnect_flash_done, info); return FALSE; } static void do_disconnect (MMGenericGsm *gsm, gint cid, MMModemFn callback, gpointer user_data) { MMCallbackInfo *info; MMAtSerialPort *primary; info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); primary = mm_generic_gsm_get_at_port (gsm, MM_AT_PORT_FLAG_PRIMARY); g_assert (primary); /* Close the serial port and wait some seconds before reopening it */ mm_serial_port_close (MM_SERIAL_PORT (primary)); mm_dbg ("Waiting some seconds before reopening the port..."); g_timeout_add_seconds (5, after_disconnect_sleep_cb, info); } static void set_allowed_mode (MMGenericGsm *gsm, MMModemGsmAllowedMode mode, MMModemFn callback, gpointer user_data) { MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (gsm); MMCallbackInfo *info; info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); /* Allow only 2G-related allowed modes */ switch (mode) { case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: case MM_MODEM_GSM_ALLOWED_MODE_ANY: priv->allowed_mode = mode; break; default: info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Cannot set desired allowed mode, " "not supported"); break; } mm_callback_info_schedule (info); } static void get_allowed_mode (MMGenericGsm *gsm, MMModemUIntFn callback, gpointer user_data) { MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (gsm); MMCallbackInfo *info; /* Just return cached value */ info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->allowed_mode), NULL); mm_callback_info_schedule (info); } static void get_access_technology (MMGenericGsm *gsm, MMModemUIntFn callback, gpointer user_data) { MMCallbackInfo *info; info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); mm_callback_info_set_result (info, GUINT_TO_POINTER (MM_MODEM_GSM_ACCESS_TECH_GSM), NULL); mm_callback_info_schedule (info); } static void get_csqf_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; gboolean parsed = FALSE; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return; if (error) { info->error = g_error_copy (error); goto done; } if (response && strstr (response->str, "+CSQ:")) { /* Got valid reply */ const char *str; int quality; str = strstr (response->str, "+CSQ:") + 5; /* Skip possible whitespaces after '+CSQF:' and before the response */ while (*str == ' ') str++; if (sscanf (str, "%d", &quality)) { /* Normalize the quality. is NOT given in dBs, * given as a relative value between 0 and 5 */ quality = CLAMP (quality, 0, 5) * 100 / 5; mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality); mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); parsed = TRUE; } } if (!parsed && !info->error) { info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Could not parse signal quality results"); } done: mm_callback_info_schedule (info); } static void get_signal_quality (MMModemGsmNetwork *modem, MMModemUIntFn callback, gpointer user_data) { MMCallbackInfo *info; MMAtSerialPort *port; MMModemGsmNetwork *parent_iface; port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), NULL); if (!port) { /* Let the superclass handle it, will return cached value */ parent_iface = g_type_interface_peek_parent (MM_MODEM_GSM_NETWORK_GET_INTERFACE (modem)); parent_iface->get_signal_quality (modem, callback, user_data); return; } info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); /* If modem is not registered, don't even bother trying to get signal * quality */ if (mm_modem_get_state (MM_MODEM (modem)) < MM_MODEM_STATE_REGISTERED) { mm_dbg ("Not getting signal quality, not registered yet"); mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), 0); mm_callback_info_set_result (info, GUINT_TO_POINTER (0), NULL); mm_callback_info_schedule (info); return; } /* The iridium modem may have a huge delay to get signal quality if we pass * AT+CSQ, so we'll default to use AT+CSQF, which is a fast version that * returns right away the last signal quality value retrieved */ mm_at_serial_port_queue_command (port, "+CSQF", 3, get_csqf_done, info); } static void get_sim_iccid (MMGenericGsm *modem, MMModemStringFn callback, gpointer callback_data) { /* There seems to be no way of getting an ICCID/IMSI subscriber ID within * the Iridium AT command set, so we just skip this. */ callback (MM_MODEM (modem), "", NULL, callback_data); } static void get_operator_name (MMGenericGsm *modem, MMModemStringFn callback, gpointer callback_data) { /* Only "IRIDIUM" operator name is assumed */ callback (MM_MODEM (modem), "IRIDIUM", NULL, callback_data); } static void get_operator_code (MMGenericGsm *modem, MMModemStringFn callback, gpointer callback_data) { /* Only "90103" operator code is assumed */ callback (MM_MODEM (modem), "90103", NULL, callback_data); } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { /* Do nothing... see set_property() in parent, which also does nothing */ } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD: /* Enable RTS/CTS flow control. * Other available values: * AT&K0: Disable flow control * AT&K3: RTS/CTS * AT&K4: XOFF/XON * AT&K6: Both RTS/CTS and XOFF/XON */ g_value_set_string (value, "&K3"); break; case MM_GENERIC_GSM_PROP_SMS_INDICATION_ENABLE_CMD: /* AT+CNMO=,[[,[,[,]]]] * but can only be 0, * and can only be either 0 or 1 * * Note: Modem may return +CMS ERROR:322, which indicates Memory Full, * not a big deal */ g_value_set_string (value, "+CNMI=2,1,0,0,1"); break; case MM_GENERIC_GSM_PROP_SMS_STORAGE_LOCATION_CMD: /* AT=CPMS=[,[,]] * Only "SM" is allowed in all 3 message storages * * Note: Modem may return +CMS ERROR:322, which indicates Memory Full, * not a big deal */ g_value_set_string (value, "+CPMS=\"SM\",\"SM\",\"SM\""); break; case MM_GENERIC_GSM_PROP_PS_NETWORK_SUPPORTED: /* We do not support PS network in Iridium, only CS */ g_value_set_boolean (value, FALSE); break; default: break; } } /*****************************************************************************/ static void modem_gsm_network_init (MMModemGsmNetwork *network_class) { network_class->get_signal_quality = get_signal_quality; } static void mm_modem_iridium_gsm_init (MMModemIridiumGsm *self) { MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (self); /* Set defaults */ priv->allowed_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; } static void mm_modem_iridium_gsm_class_init (MMModemIridiumGsmClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); g_type_class_add_private (object_class, sizeof (MMModemIridiumGsmPrivate)); object_class->get_property = get_property; object_class->set_property = set_property; g_object_class_override_property (object_class, MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD, MM_GENERIC_GSM_FLOW_CONTROL_CMD); g_object_class_override_property (object_class, MM_GENERIC_GSM_PROP_SMS_INDICATION_ENABLE_CMD, MM_GENERIC_GSM_SMS_INDICATION_ENABLE_CMD); g_object_class_override_property (object_class, MM_GENERIC_GSM_PROP_SMS_STORAGE_LOCATION_CMD, MM_GENERIC_GSM_SMS_STORAGE_LOCATION_CMD); g_object_class_override_property (object_class, MM_GENERIC_GSM_PROP_PS_NETWORK_SUPPORTED, MM_GENERIC_GSM_PS_NETWORK_SUPPORTED); gsm_class->port_grabbed = port_grabbed; gsm_class->do_enable = do_enable; gsm_class->do_disconnect = do_disconnect; gsm_class->get_access_technology = get_access_technology; gsm_class->set_allowed_mode = set_allowed_mode; gsm_class->get_allowed_mode = get_allowed_mode; gsm_class->get_sim_iccid = get_sim_iccid; gsm_class->get_operator_name = get_operator_name; gsm_class->get_operator_code = get_operator_code; }