Files
ModemManager/plugins/mm-modem-samsung-gsm.c
T
Eric Shienbrood e7b6b2dc1c core: keep track of all PIN retry counts
Added a PinRetryCounts property on org.freedesktop.ModemManager.Modem.
This is dictionary that records the number of PIN tries remaining
for each of the possible PIN code types for which the modem is
capable of reporting the count. Also, these counts are kept up
to date across ChangePin and EnablePin operations, not just when
an unlock is attempted.
2011-11-16 17:42:50 -06:00

695 lines
21 KiB
C
Executable File

/* -*- 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) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2011 Red Hat, Inc.
* Copyright (C) 2011 Samsung Electronics, Inc.,
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <dbus/dbus-glib.h>
#include "mm-modem-samsung-gsm.h"
#include "mm-modem-simple.h"
#include "mm-errors.h"
#include "mm-callback-info.h"
#include "mm-modem-gsm-card.h"
#include "mm-log.h"
#include "mm-modem-icera.h"
#include "mm-utils.h"
static void modem_init (MMModem *modem_class);
static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class);
static void modem_simple_init (MMModemSimple *class);
static void modem_gsm_card_init (MMModemGsmCard *class);
static void modem_icera_init (MMModemIcera *icera_class);
G_DEFINE_TYPE_EXTENDED (MMModemSamsungGsm, mm_modem_samsung_gsm, MM_TYPE_GENERIC_GSM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_ICERA, modem_icera_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init))
#define MM_MODEM_SAMSUNG_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_SAMSUNG_GSM, MMModemSamsungGsmPrivate))
typedef struct {
gboolean disposed;
MMModemIceraPrivate *icera;
char *band;
} MMModemSamsungGsmPrivate;
#define IPDPADDR_TAG "%IPDPADDR: "
MMModem *
mm_modem_samsung_gsm_new (const char *device,
const char *driver,
const char *plugin)
{
MMModem *modem;
g_return_val_if_fail (device != NULL, NULL);
g_return_val_if_fail (driver != NULL, NULL);
g_return_val_if_fail (plugin != NULL, NULL);
modem = MM_MODEM (g_object_new (MM_TYPE_MODEM_SAMSUNG_GSM,
MM_MODEM_MASTER_DEVICE, device,
MM_MODEM_DRIVER, driver,
MM_MODEM_PLUGIN, plugin,
MM_MODEM_IP_METHOD, MM_MODEM_IP_METHOD_DHCP,
NULL));
if (modem)
MM_MODEM_SAMSUNG_GSM_GET_PRIVATE (modem)->icera = mm_modem_icera_init_private ();
return modem;
}
typedef struct {
MMModemGsmBand mm;
char band[50];
} BandTable;
static BandTable bands[12] = {
/* Sort 3G first since it's preferred */
{ MM_MODEM_GSM_BAND_U2100, "FDD_BAND_I" },
{ MM_MODEM_GSM_BAND_U1900, "FDD_BAND_II" },
{ MM_MODEM_GSM_BAND_U1800, "FDD_BAND_III" },
{ MM_MODEM_GSM_BAND_U17IV, "FDD_BAND_IV" },
{ MM_MODEM_GSM_BAND_U850, "FDD_BAND_V" },
{ MM_MODEM_GSM_BAND_U800, "FDD_BAND_VI" },
{ MM_MODEM_GSM_BAND_U900, "FDD_BAND_VIII" },
{ MM_MODEM_GSM_BAND_G850, "G850" },
/* 2G second */
{ MM_MODEM_GSM_BAND_DCS, "DCS" },
{ MM_MODEM_GSM_BAND_EGSM, "EGSM" }, /* 0x100 = Extended GSM, 0x200 = Primary GSM */
{ MM_MODEM_GSM_BAND_PCS, "PCS" },
/* And ANY last since it's most inclusive */
{ MM_MODEM_GSM_BAND_ANY, "ANY" },
};
static gboolean
band_mm_to_samsung (MMModemGsmBand band, MMModemGsmNetwork *modem)
{
int i;
MMModemSamsungGsmPrivate *priv = MM_MODEM_SAMSUNG_GSM_GET_PRIVATE (modem);
for (i = 0; i < sizeof (bands) / sizeof (BandTable); i++) {
if (bands[i].mm == band) {
priv->band = bands[i].band;
return TRUE;
}
}
return FALSE;
}
static void
get_allowed_mode (MMGenericGsm *gsm,
MMModemUIntFn callback,
gpointer user_data)
{
mm_modem_icera_get_allowed_mode (MM_MODEM_ICERA (gsm), callback, user_data);
}
static void
set_allowed_mode (MMGenericGsm *gsm,
MMModemGsmAllowedMode mode,
MMModemFn callback,
gpointer user_data)
{
mm_modem_icera_set_allowed_mode (MM_MODEM_ICERA (gsm), mode, callback, user_data);
}
static void
set_band_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)
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
}
static void
set_band (MMModemGsmNetwork *modem,
MMModemGsmBand band,
MMModemFn callback,
gpointer user_data)
{
MMCallbackInfo *info;
MMAtSerialPort *port;
char *command;
MMModemSamsungGsmPrivate *priv = MM_MODEM_SAMSUNG_GSM_GET_PRIVATE (modem);
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
mm_callback_info_schedule (info);
return;
}
/* TODO: Check how to pass more than one band in the same AT%%IPBM command */
if (!utils_check_for_single_value (band)) {
info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Cannot set more than one band.");
mm_callback_info_schedule (info);
} else if (!band_mm_to_samsung (band, modem)) {
info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid band.");
mm_callback_info_schedule (info);
} else {
mm_callback_info_set_data (info, "band", g_strdup(priv->band), NULL);
command = g_strdup_printf ("AT%%IPBM=\"%s\",1", priv->band);
mm_at_serial_port_queue_command (port, command, 3, set_band_done, info);
g_free (command);
priv->band = NULL;
}
}
static gboolean
parse_ipbm (const char *reply, MMModemGsmBand *band)
{
int enable[12];
g_return_val_if_fail (band != NULL, FALSE);
g_return_val_if_fail (reply != NULL, FALSE);
if (sscanf (reply, "\"ANY\": %d\r\n\"EGSM\": %d\r\n\"DCS\": %d\r\n\"PCS\": %d\r\n\"G850\": %d\r\n\"FDD_BAND_I\": %d\r\n\"FDD_BAND_II\": %d\r\n\"FDD_BAND_III\": %d\r\n\"FDD_BAND_IV\": %d\r\n\"FDD_BAND_V\": %d\r\n\"FDD_BAND_VI\": %d\r\n\"FDD_BAND_VIII\": %d", &enable[0], &enable[1], &enable[2], &enable[3], &enable[4], &enable[5], &enable[6], &enable[7], &enable[8], &enable[9], &enable[10], &enable[11]) != 12)
return FALSE;
*band = 0;
if (enable[5] == 1)
*band |= MM_MODEM_GSM_BAND_U2100;
if (enable[6] == 1)
*band |= MM_MODEM_GSM_BAND_U1900;
if (enable[7] == 1)
*band |= MM_MODEM_GSM_BAND_U1800;
if (enable[8] == 1)
*band |= MM_MODEM_GSM_BAND_U17IV;
if (enable[9] == 1)
*band |= MM_MODEM_GSM_BAND_U850;
if (enable[10] == 1)
*band |= MM_MODEM_GSM_BAND_U800;
if (enable[11] == 1)
*band |= MM_MODEM_GSM_BAND_U900;
if (enable[1] == 1)
*band |= MM_MODEM_GSM_BAND_EGSM;
if (enable[2] == 1)
*band |= MM_MODEM_GSM_BAND_DCS;
if (enable[3] == 1)
*band |= MM_MODEM_GSM_BAND_PCS;
if (enable[4] == 1)
*band |= MM_MODEM_GSM_BAND_G850;
return (*band > 0 ? TRUE : FALSE);
}
static void
get_band_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemGsmBand mm_band = MM_MODEM_GSM_BAND_UNKNOWN;
/* 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);
else if (parse_ipbm (response->str, &mm_band))
mm_callback_info_set_result (info, GUINT_TO_POINTER (mm_band), NULL);
mm_callback_info_schedule (info);
}
static void
get_band (MMModemGsmNetwork *modem,
MMModemUIntFn callback,
gpointer user_data)
{
MMAtSerialPort *port;
MMCallbackInfo *info;
info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
/* Otherwise ask the modem */
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
mm_callback_info_schedule (info);
return;
}
mm_at_serial_port_queue_command (port, "AT%IPBM?", 3, get_band_done, info);
}
static void
send_samsung_pinnum_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
int matched;
GArray *retry_counts;
PinRetryCount ur[4] = {
{"sim-pin", 0}, {"sim-puk", 0}, {"sim-pin2", 0}, {"sim-puk2", 0}
};
/* 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;
}
matched = sscanf (response->str, "%%PINNUM: %d, %d, %d, %d",
&ur[0].count, &ur[1].count, &ur[2].count, &ur[3].count);
if (matched == 4) {
if (ur[0].count > 998) {
info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
"Invalid PIN attempts left %d", ur[0].count);
ur[0].count = 0;
}
retry_counts = g_array_sized_new (FALSE, TRUE, sizeof (PinRetryCount), 4);
g_array_append_vals (retry_counts, &ur, 4);
mm_callback_info_set_result (info, retry_counts, NULL);
} else {
info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
"Could not parse PIN retries results");
}
done:
mm_serial_port_close (MM_SERIAL_PORT (port));
mm_callback_info_schedule (info);
}
static void
reset (MMModem *modem,
MMModemFn callback,
gpointer user_data)
{
MMCallbackInfo *info;
MMAtSerialPort *port;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (port)
mm_at_serial_port_queue_command (port, "%IRESET", 3, NULL, NULL);
mm_callback_info_schedule (info);
}
static void
get_unlock_retries (MMModemGsmCard *modem,
MMModemArrayFn callback,
gpointer user_data)
{
MMAtSerialPort *port;
MMCallbackInfo *info = mm_callback_info_array_new (MM_MODEM (modem), callback, user_data);
mm_dbg ("get_unlock_retries");
/* Ensure we have a usable port to use for the command */
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
mm_callback_info_schedule (info);
return;
}
/* Modem may not be enabled yet, which sometimes can't be done until
* the device has been unlocked. In this case we have to open the port
* ourselves.
*/
if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) {
mm_callback_info_schedule (info);
return;
}
/* if the modem have not yet been enabled we need to make sure echoing is turned off */
mm_at_serial_port_queue_command (port, "E0", 3, NULL, NULL);
mm_at_serial_port_queue_command (port, "%PINNUM?", 3, send_samsung_pinnum_done, info);
}
static void
get_access_technology (MMGenericGsm *gsm,
MMModemUIntFn callback,
gpointer user_data)
{
mm_modem_icera_get_access_technology (MM_MODEM_ICERA (gsm), callback, user_data);
}
/*****************************************************************************/
static void
disable_unsolicited_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;
/* Ignore all errors */
mm_callback_info_schedule (info);
}
static void
invoke_call_parent_disable_fn (MMCallbackInfo *info)
{
/* Note: we won't call the parent disable if info->modem is no longer
* valid. The invoke is called always once the info gets scheduled, which
* may happen during removed modem detection. */
if (info->modem) {
MMModem *parent_modem_iface;
parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (info->modem));
parent_modem_iface->disable (info->modem, (MMModemFn)info->callback, info->user_data);
}
}
static void
disable (MMModem *modem,
MMModemFn callback,
gpointer user_data)
{
MMAtSerialPort *primary;
MMCallbackInfo *info;
mm_modem_icera_cleanup (MM_MODEM_ICERA (modem));
mm_modem_icera_change_unsolicited_messages (MM_MODEM_ICERA (modem), FALSE);
info = mm_callback_info_new_full (modem,
invoke_call_parent_disable_fn,
(GCallback)callback,
user_data);
primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY);
g_assert (primary);
/*
* Command to ensure unsolicited message disable completes.
* Turns the radios off, which seems like a reasonable
* think to do when disabling.
*/
mm_at_serial_port_queue_command (primary, "AT+CFUN=4", 5, disable_unsolicited_done, info);
}
static void
init_all_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemSamsungGsm *self;
/* If the modem has already been removed, return without
* scheduling callback */
if (mm_callback_info_check_modem_removed (info))
return;
self = MM_MODEM_SAMSUNG_GSM (info->modem);
if (!error)
mm_modem_icera_change_unsolicited_messages (MM_MODEM_ICERA (self), TRUE);
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (self), error, info);
}
static void
init2_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemSamsungGsm *self;
/* If the modem has already been removed, return without
* scheduling callback */
if (mm_callback_info_check_modem_removed (info))
return;
self = MM_MODEM_SAMSUNG_GSM (info->modem);
if (error)
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (self), error, info);
else {
/* Finish the initialization */
mm_at_serial_port_queue_command (port, "E0 V1 X4 &C1", 3, init_all_done, info);
}
}
static void
init_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemSamsungGsm *self;
/* If the modem has already been removed, return without
* scheduling callback */
if (mm_callback_info_check_modem_removed (info))
return;
self = MM_MODEM_SAMSUNG_GSM (info->modem);
if (error)
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (self), error, info);
else {
/* Power up the modem */
mm_at_serial_port_queue_command (port, "+CMEE=1", 2, NULL, NULL);
mm_at_serial_port_queue_command (port, "+CFUN=1", 10, init2_done, info);
}
}
static void
init_reset_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemSamsungGsm *self = MM_MODEM_SAMSUNG_GSM (info->modem);
if (error)
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (self), error, info);
else
mm_at_serial_port_queue_command (port, "E0 V1", 3, init_done, info);
}
static void
do_enable (MMGenericGsm *modem, MMModemFn callback, gpointer user_data)
{
MMCallbackInfo *info;
MMAtSerialPort *primary;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
primary = mm_generic_gsm_get_at_port (modem, MM_PORT_TYPE_PRIMARY);
g_assert (primary);
mm_at_serial_port_queue_command (primary, "Z", 3, init_reset_done, info);
}
static void
do_connect (MMModem *modem,
const char *number,
MMModemFn callback,
gpointer user_data)
{
mm_modem_icera_do_connect (MM_MODEM_ICERA (modem), number, callback, user_data);
}
static void
do_disconnect (MMGenericGsm *gsm,
gint cid,
MMModemFn callback,
gpointer user_data)
{
mm_modem_icera_do_disconnect (gsm, cid, callback, user_data);
}
static void
get_ip4_config (MMModem *modem,
MMModemIp4Fn callback,
gpointer user_data)
{
mm_modem_icera_get_ip4_config (MM_MODEM_ICERA (modem), callback, user_data);
}
static void
simple_connect (MMModemSimple *simple,
GHashTable *properties,
MMModemFn callback,
gpointer user_data)
{
MMModemSimple *parent_iface;
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
/* Let icera cache user & password */
mm_modem_icera_simple_connect (MM_MODEM_ICERA (simple), properties);
parent_iface = g_type_interface_peek_parent (MM_MODEM_SIMPLE_GET_INTERFACE (simple));
parent_iface->connect (MM_MODEM_SIMPLE (simple), properties, callback, info);
}
static gboolean
grab_port (MMModem *modem,
const char *subsys,
const char *name,
MMPortType suggested_type,
gpointer user_data,
GError **error)
{
MMGenericGsm *gsm = MM_GENERIC_GSM (modem);
MMPortType ptype = MM_PORT_TYPE_IGNORED;
MMPort *port = NULL;
if (suggested_type == MM_PORT_TYPE_UNKNOWN) {
if (!strcmp (subsys, "tty")) {
if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY))
ptype = MM_PORT_TYPE_PRIMARY;
else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY))
ptype = MM_PORT_TYPE_SECONDARY;
}
} else
ptype = suggested_type;
port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error);
if (port && MM_IS_AT_SERIAL_PORT (port)) {
g_object_set (port,
MM_PORT_CARRIER_DETECT, FALSE,
MM_SERIAL_PORT_SEND_DELAY, (guint64) 0,
NULL);
/* Add Icera-specific handlers */
mm_modem_icera_register_unsolicted_handlers (MM_MODEM_ICERA (gsm), MM_AT_SERIAL_PORT (port));
}
return !!port;
}
static MMModemIceraPrivate *
get_icera_private (MMModemIcera *icera)
{
return MM_MODEM_SAMSUNG_GSM_GET_PRIVATE (icera)->icera;
}
static void
modem_init (MMModem *modem_class)
{
modem_class->reset = reset;
modem_class->disable = disable;
modem_class->connect = do_connect;
modem_class->get_ip4_config = get_ip4_config;
modem_class->grab_port = grab_port;
}
static void
modem_icera_init (MMModemIcera *icera)
{
icera->get_private = get_icera_private;
}
static void
modem_simple_init (MMModemSimple *class)
{
class->connect = simple_connect;
}
static void
modem_gsm_network_init (MMModemGsmNetwork *class)
{
class->set_band = set_band;
class->get_band = get_band;
}
static void
modem_gsm_card_init (MMModemGsmCard *class)
{
class->get_unlock_retries = get_unlock_retries;
}
static void
mm_modem_samsung_gsm_init (MMModemSamsungGsm *self)
{
}
static void
dispose (GObject *object)
{
MMModemSamsungGsm *self = MM_MODEM_SAMSUNG_GSM (object);
MMModemSamsungGsmPrivate *priv = MM_MODEM_SAMSUNG_GSM_GET_PRIVATE (self);
if (priv->disposed == FALSE) {
priv->disposed = TRUE;
mm_modem_icera_dispose_private (MM_MODEM_ICERA (self));
}
G_OBJECT_CLASS (mm_modem_samsung_gsm_parent_class)->dispose (object);
}
static void
mm_modem_samsung_gsm_class_init (MMModemSamsungGsmClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass);
mm_modem_samsung_gsm_parent_class = g_type_class_peek_parent (klass);
g_type_class_add_private (object_class, sizeof (MMModemSamsungGsmPrivate));
object_class->dispose = dispose;
gsm_class->do_disconnect = do_disconnect;
gsm_class->do_enable = do_enable;
gsm_class->set_allowed_mode = set_allowed_mode;
gsm_class->get_allowed_mode = get_allowed_mode;
gsm_class->get_access_technology = get_access_technology;
}