Files
ModemManager/src/mm-modem-base.c
Dan Williams 4dad94d500 core: rework port grabbing and organization
Make port roles more flexible.  We have modems that do PPP
on interfaces other than the primary interface, and that
wasn't possible with the old code.  So clean up all that
logic and move the port organization code into the core
so we can reduce code in the plugins.

In the new world order, the plugins say whether the port
is a QCDM port, an AT port, or ignored.  If it's an AT
port the plugins get to tag it as primary, secondary, or
PPP, or any combination of the 3.  This allows for modems
where PPP should really be done on the secondary port
(Huawei E220, Sierra devices) so that the primary port
stays open for command and status.

Modem subclasses no longer get asked to handle port grabbing
themselves.  Instead, that's now done by the generic classes
(MMGenericCdma and MMGenericGsm) and the plugins are notified
when a port is grabbed so they can add unsolicited response
handlers for it.  After all ports are grabbed by the generic
classes, they get "organized", which assigns various ports
to the roles of PRIMARY, SECONDARY, DATA, and QCDM based
on specific rules and hints that the plugin provided (which
are expressed as MMAtPortFlags).  The plugins then have
a chance to perform fixups on the primary port if they choose.

The plugin code is responsible for determining the port
hints (ie MMAtPortFlags) at probe time, instead of having
a combination of the plugin and the modem class do the
job.  This simplifies things greatly for the plugins at
the expense of more complicated logic in the core.
2012-02-28 10:06:04 -06:00

1324 lines
44 KiB
C

/* -*- 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 Red Hat, Inc.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "mm-modem-base.h"
#include "mm-modem.h"
#include "mm-at-serial-port.h"
#include "mm-qcdm-serial-port.h"
#include "mm-errors.h"
#include "mm-log.h"
#include "mm-properties-changed-signal.h"
#include "mm-callback-info.h"
#include "mm-modem-helpers.h"
#include "mm-modem-time.h"
static void modem_init (MMModem *modem_class);
static void pc_init (MMPropertiesChanged *pc_class);
static void modem_time_init (MMModemTime *modem_time_class);
G_DEFINE_TYPE_EXTENDED (MMModemBase, mm_modem_base,
G_TYPE_OBJECT,
G_TYPE_FLAG_VALUE_ABSTRACT,
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_PROPERTIES_CHANGED, pc_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_TIME, modem_time_init))
#define MM_MODEM_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_BASE, MMModemBasePrivate))
enum {
PROP_0,
PROP_MAX_TIMEOUTS,
LAST_PROP
};
typedef struct {
char *driver;
char *plugin;
char *device;
char *equipment_ident;
char *device_ident;
char *unlock_required;
guint32 unlock_retries;
GArray *pin_retry_counts;
guint32 ip_method;
guint32 ip_timeout;
gboolean valid;
MMModemState state;
guint vid;
guint pid;
char *manf;
char *model;
char *revision;
char *ati;
char *ati1;
char *gsn;
guint max_timeouts;
guint set_invalid_unresponsive_modem_id;
MMAuthProvider *authp;
GHashTable *ports;
GHashTable *tz_data;
guint tz_poll_id;
guint tz_poll_count;
} MMModemBasePrivate;
static char *
get_hash_key (const char *subsys, const char *name)
{
return g_strdup_printf ("%s%s", subsys, name);
}
MMPort *
mm_modem_base_get_port (MMModemBase *self,
const char *subsys,
const char *name)
{
MMPort *port;
char *key;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
g_return_val_if_fail (name != NULL, NULL);
g_return_val_if_fail (subsys != NULL, NULL);
g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), NULL);
key = get_hash_key (subsys, name);
port = g_hash_table_lookup (MM_MODEM_BASE_GET_PRIVATE (self)->ports, key);
g_free (key);
return port;
}
GSList *
mm_modem_base_get_ports (MMModemBase *self)
{
GHashTableIter iter;
MMPort *port;
GSList *list = NULL;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
g_hash_table_iter_init (&iter, MM_MODEM_BASE_GET_PRIVATE (self)->ports);
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &port))
list = g_slist_append (list, port);
return list;
}
static gboolean
set_invalid_unresponsive_modem_cb (MMModemBase *self)
{
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
mm_modem_base_set_valid (self, FALSE);
priv->set_invalid_unresponsive_modem_id = 0;
return FALSE;
}
static void
serial_port_timed_out_cb (MMSerialPort *port,
guint n_consecutive_timeouts,
gpointer user_data)
{
MMModemBase *self = (MM_MODEM_BASE (user_data));
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
if (priv->max_timeouts > 0 &&
n_consecutive_timeouts >= priv->max_timeouts) {
const gchar *dbus_path;
dbus_path = (const gchar *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
mm_warn ("Modem %s: Port (%s/%s) timed out %u times, marking modem as disabled",
dbus_path,
mm_port_type_to_name (mm_port_get_port_type (MM_PORT (port))),
mm_port_get_device (MM_PORT (port)),
n_consecutive_timeouts);
/* Only set action to invalidate modem if not already done */
if (!priv->set_invalid_unresponsive_modem_id)
priv->set_invalid_unresponsive_modem_id =
g_idle_add ((GSourceFunc)set_invalid_unresponsive_modem_cb, self);
}
}
gboolean
mm_modem_base_remove_port (MMModemBase *self, MMPort *port)
{
MMModemBasePrivate *priv;
char *device, *key, *name;
const char *type_name, *subsys;
gboolean removed;
g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE);
g_return_val_if_fail (port != NULL, FALSE);
priv = MM_MODEM_BASE_GET_PRIVATE (self);
name = g_strdup (mm_port_get_device (port));
subsys = mm_port_subsys_to_name (mm_port_get_subsys (port));
type_name = mm_port_type_to_name (mm_port_get_port_type (port));
key = get_hash_key (subsys, name);
removed = g_hash_table_remove (priv->ports, key);
if (removed) {
/* Port may have already been destroyed by removal from the hash */
device = mm_modem_get_device (MM_MODEM (self));
mm_dbg ("(%s) type %s removed from %s", name, type_name, device);
g_free (device);
}
g_free (key);
g_free (name);
return removed;
}
static inline void
log_port (MMPort *port, const char *device, const char *desc)
{
if (port) {
mm_dbg ("(%s) %s/%s %s",
device,
mm_port_subsys_to_name (mm_port_get_subsys (port)),
mm_port_get_device (port),
desc);
}
}
gboolean
mm_modem_base_organize_ports (MMModemBase *self,
MMAtSerialPort **out_primary,
MMAtSerialPort **out_secondary,
MMPort **out_data,
MMQcdmSerialPort **out_qcdm,
GError **error)
{
GSList *ports, *iter;
MMAtPortFlags flags;
MMAtSerialPort *backup_primary = NULL;
MMAtSerialPort *primary = NULL;
MMAtSerialPort *secondary = NULL;
MMAtSerialPort *backup_secondary = NULL;
MMQcdmSerialPort *qcdm = NULL;
MMPort *data = NULL;
char *device;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (out_primary != NULL, FALSE);
g_return_val_if_fail (out_secondary != NULL, FALSE);
g_return_val_if_fail (out_data != NULL, FALSE);
ports = mm_modem_base_get_ports (self);
for (iter = ports; iter; iter = g_slist_next (iter)) {
MMPort *candidate = iter->data;
MMPortSubsys subsys = mm_port_get_subsys (candidate);
if (MM_IS_AT_SERIAL_PORT (candidate)) {
flags = mm_at_serial_port_get_flags (MM_AT_SERIAL_PORT (candidate));
if (flags & MM_AT_PORT_FLAG_PRIMARY) {
if (!primary)
primary = MM_AT_SERIAL_PORT (candidate);
else if (!backup_primary) {
/* Just in case the plugin gave us more than one primary
* and no secondaries, treat additional primary ports as
* secondary.
*/
backup_primary = MM_AT_SERIAL_PORT (candidate);
}
}
if (!data && (flags & MM_AT_PORT_FLAG_PPP))
data = candidate;
/* Explicitly flagged secondary ports trump NONE ports for secondary */
if (flags & MM_AT_PORT_FLAG_SECONDARY) {
if (!secondary || !(mm_at_serial_port_get_flags (secondary) & MM_AT_PORT_FLAG_SECONDARY))
secondary = MM_AT_SERIAL_PORT (candidate);
}
/* Fallback secondary */
if (flags == MM_AT_PORT_FLAG_NONE) {
if (!secondary)
secondary = MM_AT_SERIAL_PORT (candidate);
else if (!backup_secondary)
backup_secondary = MM_AT_SERIAL_PORT (candidate);
}
} else if (MM_IS_QCDM_SERIAL_PORT (candidate)) {
if (!qcdm)
qcdm = MM_QCDM_SERIAL_PORT (candidate);
} else if (subsys == MM_PORT_SUBSYS_NET) {
/* Net device (if any) is the preferred data port */
if (!data || MM_IS_AT_SERIAL_PORT (data))
data = candidate;
}
}
g_slist_free (ports);
/* Fall back to a secondary port if we didn't find a primary port */
if (!primary) {
if (!secondary) {
g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
"Failed to find primary port.");
return FALSE;
}
primary = secondary;
secondary = NULL;
}
g_assert (primary);
/* If the plugin didn't give us any secondary ports, use any additional
* primary ports or backup secondary ports as secondary.
*/
if (!secondary)
secondary = backup_primary ? backup_primary : backup_secondary;
/* Data port defaults to primary AT port */
if (!data)
data = MM_PORT (primary);
g_assert (data);
/* Reset flags on all ports; clear data port first since it might also
* be the primary or secondary port.
*/
if (MM_IS_AT_SERIAL_PORT (data))
mm_at_serial_port_set_flags (MM_AT_SERIAL_PORT (data), MM_AT_PORT_FLAG_NONE);
mm_at_serial_port_set_flags (primary, MM_AT_PORT_FLAG_PRIMARY);
if (secondary)
mm_at_serial_port_set_flags (secondary, MM_AT_PORT_FLAG_SECONDARY);
if (MM_IS_AT_SERIAL_PORT (data)) {
flags = mm_at_serial_port_get_flags (MM_AT_SERIAL_PORT (data));
mm_at_serial_port_set_flags (MM_AT_SERIAL_PORT (data), flags | MM_AT_PORT_FLAG_PPP);
}
device = mm_modem_get_device (MM_MODEM (self));
log_port (MM_PORT (primary), device, "primary");
log_port (MM_PORT (secondary), device, "secondary");
log_port (MM_PORT (data), device, "data");
log_port (MM_PORT (qcdm), device, "qcdm");
g_free (device);
*out_primary = primary;
*out_secondary = secondary;
*out_data = data;
if (out_qcdm)
*out_qcdm = qcdm;
return TRUE;
}
void
mm_modem_base_set_valid (MMModemBase *self, gboolean new_valid)
{
MMModemBasePrivate *priv;
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
if (priv->valid != new_valid) {
priv->valid = new_valid;
/* Modem starts off in disabled state, and jumps to disabled when
* it's no longer valid.
*/
mm_modem_set_state (MM_MODEM (self),
MM_MODEM_STATE_DISABLED,
MM_MODEM_STATE_REASON_NONE);
g_object_notify (G_OBJECT (self), MM_MODEM_VALID);
}
}
gboolean
mm_modem_base_get_valid (MMModemBase *self)
{
g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE);
return MM_MODEM_BASE_GET_PRIVATE (self)->valid;
}
const char *
mm_modem_base_get_equipment_identifier (MMModemBase *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
return MM_MODEM_BASE_GET_PRIVATE (self)->equipment_ident;
}
void
mm_modem_base_set_equipment_identifier (MMModemBase *self, const char *ident)
{
MMModemBasePrivate *priv;
const char *dbus_path;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
/* Only do something if the value changes */
if ( (priv->equipment_ident == ident)
|| (priv->equipment_ident && ident && !strcmp (priv->equipment_ident, ident)))
return;
g_free (priv->equipment_ident);
priv->equipment_ident = g_strdup (ident);
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
if (priv->equipment_ident)
mm_info ("Modem %s: Equipment identifier set (%s)", dbus_path, priv->equipment_ident);
else
mm_warn ("Modem %s: Equipment identifier not set", dbus_path);
}
g_object_notify (G_OBJECT (self), MM_MODEM_EQUIPMENT_IDENTIFIER);
}
const char *
mm_modem_base_get_unlock_required (MMModemBase *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
return MM_MODEM_BASE_GET_PRIVATE (self)->unlock_required;
}
void
mm_modem_base_set_unlock_required (MMModemBase *self, const char *unlock_required)
{
MMModemBasePrivate *priv;
const char *dbus_path;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
/* Only do something if the value changes */
if ( (priv->unlock_required == unlock_required)
|| ( priv->unlock_required
&& unlock_required
&& !strcmp (priv->unlock_required, unlock_required)))
return;
g_free (priv->unlock_required);
priv->unlock_required = g_strdup (unlock_required);
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
if (priv->unlock_required)
mm_info ("Modem %s: unlock required (%s)", dbus_path, priv->unlock_required);
else
mm_info ("Modem %s: unlock no longer required", dbus_path);
}
g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_REQUIRED);
}
void
mm_modem_base_set_pin_retry_counts (MMModemBase *self, GArray *pin_retries)
{
MMModemBasePrivate *priv;
GArray *old_retries;
gboolean same;
PinRetryCount *active = NULL;
guint i, j;
guint old_unlock_retries;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
if (!pin_retries) {
priv->unlock_retries = MM_MODEM_UNLOCK_RETRIES_NOT_SUPPORTED;
return;
}
old_retries = priv->pin_retry_counts;
old_unlock_retries = priv->unlock_retries;
/* Only do something if one of the values changes */
same = (old_retries != NULL) && (pin_retries->len == old_retries->len);
for (i = 0; i < pin_retries->len; ++i) {
PinRetryCount *newur = &g_array_index (pin_retries, PinRetryCount, i);
if (!g_strcmp0 (newur->name, priv->unlock_required))
active = newur;
if (old_retries) {
for (j = 0; j < old_retries->len; ++j) {
PinRetryCount *oldur = &g_array_index (old_retries, PinRetryCount, i);
if (!g_strcmp0 (oldur->name, newur->name)) {
if (oldur->count != newur->count)
same = FALSE;
break;
}
}
}
}
if (priv->pin_retry_counts)
g_array_unref (priv->pin_retry_counts);
priv->pin_retry_counts = pin_retries;
if (priv->unlock_required) {
g_assert (active);
priv->unlock_retries = active->count;
} else
priv->unlock_retries = 0;
if (old_unlock_retries != priv->unlock_retries)
g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_RETRIES);
if (!same)
g_object_notify (G_OBJECT (self), MM_MODEM_PIN_RETRY_COUNTS);
}
const char *
mm_modem_base_get_manf (MMModemBase *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
return MM_MODEM_BASE_GET_PRIVATE (self)->manf;
}
void
mm_modem_base_set_manf (MMModemBase *self, const char *manf)
{
MMModemBasePrivate *priv;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
g_free (priv->manf);
priv->manf = g_strdup (manf);
}
const char *
mm_modem_base_get_model (MMModemBase *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
return MM_MODEM_BASE_GET_PRIVATE (self)->model;
}
void
mm_modem_base_set_model (MMModemBase *self, const char *model)
{
MMModemBasePrivate *priv;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
g_free (priv->model);
priv->model = g_strdup (model);
}
const char *
mm_modem_base_get_revision (MMModemBase *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
return MM_MODEM_BASE_GET_PRIVATE (self)->revision;
}
void
mm_modem_base_set_revision (MMModemBase *self, const char *revision)
{
MMModemBasePrivate *priv;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
g_free (priv->revision);
priv->revision = g_strdup (revision);
}
static GValue *
int_to_gvalue (gint i)
{
GValue *v = g_slice_new0 (GValue);
g_value_init (v, G_TYPE_INT);
g_value_set_int (v, i);
return v;
}
static void
value_destroy (gpointer data)
{
GValue *v = (GValue *) data;
g_value_unset (v);
g_slice_free (GValue, v);
}
static void
timezone_poll_done (MMModem *modem, GError *error, gpointer user_data)
{
/* do nothing; modem will call mm_modem_base_set_network_timezone if
it has timezone data, and then we will stop nagging it. */
}
static gboolean
timezone_poll_callback (gpointer data)
{
MMModemBase *self = (MMModemBase *) data;
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
gboolean result;
if (priv->tz_poll_count == 0)
goto stop_polling;
priv->tz_poll_count--;
result = mm_modem_time_poll_network_timezone (MM_MODEM_TIME (self),
timezone_poll_done,
NULL);
if (!result)
goto stop_polling;
return TRUE;
stop_polling:
mm_modem_base_set_network_timezone (self, NULL, NULL, NULL);
priv->tz_poll_id = 0;
return FALSE;
}
#define TIMEZONE_POLL_INTERVAL_SEC 5
#define TIMEZONE_POLL_RETRIES 6
void
mm_modem_base_set_network_timezone_polling (MMModemBase *self,
gboolean should_poll)
{
MMModemBasePrivate *priv;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
if (should_poll == !!priv->tz_poll_id)
return;
if (should_poll) {
priv->tz_poll_count = TIMEZONE_POLL_RETRIES;
priv->tz_poll_id = g_timeout_add_seconds (TIMEZONE_POLL_INTERVAL_SEC,
timezone_poll_callback,
self);
} else {
g_source_remove (priv->tz_poll_id);
priv->tz_poll_id = 0;
}
}
static void
modem_state_changed (MMModemBase *self, GParamSpec *pspec, gpointer user_data)
{
MMModemState state;
gboolean registered;
state = mm_modem_get_state (MM_MODEM (self));
registered = (state >= MM_MODEM_STATE_REGISTERED);
mm_modem_base_set_network_timezone_polling (self, registered);
if (!registered)
mm_modem_base_set_network_timezone (self, NULL, NULL, NULL);
}
void
mm_modem_base_set_network_timezone (MMModemBase *self, gint *offset,
gint *dst_offset, gint *leap_seconds)
{
MMModemBasePrivate *priv;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
priv = MM_MODEM_BASE_GET_PRIVATE (self);
if (offset)
g_hash_table_replace (priv->tz_data, "offset", int_to_gvalue (*offset));
else
g_hash_table_remove (priv->tz_data, "offset");
if (dst_offset)
g_hash_table_replace (priv->tz_data, "dst_offset", int_to_gvalue (*dst_offset));
else
g_hash_table_remove (priv->tz_data, "dst_offset");
if (leap_seconds)
g_hash_table_replace (priv->tz_data, "leap_seconds", int_to_gvalue (*leap_seconds));
else
g_hash_table_remove (priv->tz_data, "leap_seconds");
g_object_notify (G_OBJECT (self), MM_MODEM_TIME_NETWORK_TIMEZONE);
mm_modem_base_set_network_timezone_polling (self, FALSE);
}
/*************************************************************************/
static void
card_info_simple_invoke (MMCallbackInfo *info)
{
MMModemBase *self = MM_MODEM_BASE (info->modem);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
MMModemInfoFn callback = (MMModemInfoFn) info->callback;
callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data);
}
static void
card_info_cache_invoke (MMCallbackInfo *info)
{
MMModemBase *self = MM_MODEM_BASE (info->modem);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
MMModemInfoFn callback = (MMModemInfoFn) info->callback;
const char *manf, *cmanf, *model, *cmodel, *rev, *crev, *ati, *ati1, *gsn, *cgsn;
manf = mm_callback_info_get_data (info, "card-info-manf");
cmanf = mm_callback_info_get_data (info, "card-info-c-manf");
model = mm_callback_info_get_data (info, "card-info-model");
cmodel = mm_callback_info_get_data (info, "card-info-c-model");
rev = mm_callback_info_get_data (info, "card-info-revision");
crev = mm_callback_info_get_data (info, "card-info-c-revision");
/* Prefer the 'C' responses over the plain responses */
g_free (priv->manf);
priv->manf = g_strdup (cmanf ? cmanf : manf);
g_free (priv->model);
priv->model = g_strdup (cmodel ? cmodel : model);
g_free (priv->revision);
priv->revision = g_strdup (crev ? crev : rev);
ati = mm_callback_info_get_data (info, "card-info-ati");
g_free (priv->ati);
priv->ati = g_strdup (ati);
ati1 = mm_callback_info_get_data (info, "card-info-ati1");
g_free (priv->ati1);
priv->ati1 = g_strdup (ati1);
gsn = mm_callback_info_get_data (info, "card-info-gsn");
cgsn = mm_callback_info_get_data (info, "card-info-c-gsn");
g_free (priv->gsn);
priv->gsn = g_strdup (cgsn ? cgsn : gsn);
/* Build up the device identifier */
g_free (priv->device_ident);
priv->device_ident = mm_create_device_identifier (priv->vid,
priv->pid,
priv->ati,
priv->ati1,
priv->gsn,
priv->revision,
priv->model,
priv->manf);
g_object_notify (G_OBJECT (self), MM_MODEM_DEVICE_IDENTIFIER);
callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data);
}
static void
info_item_done (MMCallbackInfo *info,
GString *response,
GError *error,
const char *tag,
const char *tag2,
const char *desc)
{
const char *p;
if (!error) {
p = response->str;
if (tag)
p = mm_strip_tag (p, tag);
if (tag2)
p = mm_strip_tag (p, tag2);
mm_callback_info_set_data (info, desc, strlen (p) ? g_strdup (p) : NULL, g_free);
}
mm_callback_info_chain_complete_one (info);
}
#define GET_INFO_RESP_FN(func_name, tag, tag2, desc) \
static void \
func_name (MMAtSerialPort *port, \
GString *response, \
GError *error, \
gpointer user_data) \
{ \
if (mm_callback_info_check_modem_removed ((MMCallbackInfo *) user_data)) \
return; \
info_item_done ((MMCallbackInfo *) user_data, response, error, tag, tag2, desc ); \
}
GET_INFO_RESP_FN(get_revision_done, "+GMR:", "AT+GMR", "card-info-revision")
GET_INFO_RESP_FN(get_model_done, "+GMM:", "AT+GMM", "card-info-model")
GET_INFO_RESP_FN(get_manf_done, "+GMI:", "AT+GMI", "card-info-manf")
GET_INFO_RESP_FN(get_c_revision_done, "+CGMR:", "AT+CGMR", "card-info-c-revision")
GET_INFO_RESP_FN(get_c_model_done, "+CGMM:", "AT+CGMM", "card-info-c-model")
GET_INFO_RESP_FN(get_c_manf_done, "+CGMI:", "AT+CGMI", "card-info-c-manf")
GET_INFO_RESP_FN(get_ati_done, NULL, "ATI", "card-info-ati")
GET_INFO_RESP_FN(get_ati1_done, NULL, "ATI1", "card-info-ati1")
GET_INFO_RESP_FN(get_gsn_done, "+GSN:", "AT+GSN", "card-info-gsn")
GET_INFO_RESP_FN(get_cgsn_done, "+CGSN:", "AT+CGSN", "card-info-c-gsn")
void
mm_modem_base_get_card_info (MMModemBase *self,
MMAtSerialPort *port,
GError *port_error,
MMModemInfoFn callback,
gpointer user_data)
{
MMModemBasePrivate *priv;
MMCallbackInfo *info;
gboolean cached = FALSE;
GError *error = NULL;
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_MODEM_BASE (self));
/* Either we get a proper AT port, or we get a port_error */
g_return_if_fail ((port != NULL && MM_IS_AT_SERIAL_PORT (port)) || port_error != NULL);
g_return_if_fail (callback != NULL);
priv = MM_MODEM_BASE_GET_PRIVATE (self);
/* Cached info and errors schedule the callback immediately and do
* not hit up the card for it's model information.
*/
if (priv->manf || priv->model || priv->revision)
cached = TRUE;
else if (port_error)
error = g_error_copy (port_error);
/* If we have cached info or an error, don't hit up the card */
if (cached || error) {
info = mm_callback_info_new_full (MM_MODEM (self),
card_info_simple_invoke,
G_CALLBACK (callback),
user_data);
info->error = error;
mm_callback_info_schedule (info);
return;
}
/* Otherwise, ask the card */
info = mm_callback_info_new_full (MM_MODEM (self),
card_info_cache_invoke,
G_CALLBACK (callback),
user_data);
mm_callback_info_chain_start (info, 10);
mm_at_serial_port_queue_command_cached (port, "+GMI", 3, get_manf_done, info);
mm_at_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info);
mm_at_serial_port_queue_command_cached (port, "+GMR", 3, get_revision_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMI", 3, get_c_manf_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMM", 3, get_c_model_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMR", 3, get_c_revision_done, info);
mm_at_serial_port_queue_command_cached (port, "I", 3, get_ati_done, info);
mm_at_serial_port_queue_command_cached (port, "I1", 3, get_ati1_done, info);
mm_at_serial_port_queue_command_cached (port, "+GSN", 3, get_gsn_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGSN", 3, get_cgsn_done, info);
}
/*****************************************************************************/
static gboolean
grab_port (MMModem *modem,
const char *subsys,
const char *name,
MMPortType ptype,
MMAtPortFlags at_pflags,
gpointer user_data,
GError **error)
{
MMModemBase *self = MM_MODEM_BASE (modem);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
MMPort *port = NULL;
char *key, *device;
g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE);
g_return_val_if_fail (subsys != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE);
key = get_hash_key (subsys, name);
port = g_hash_table_lookup (priv->ports, key);
g_free (key);
g_return_val_if_fail (port == NULL, FALSE);
if (!strcmp (subsys, "tty")) {
if (ptype == MM_PORT_TYPE_QCDM)
port = MM_PORT (mm_qcdm_serial_port_new (name));
else if (ptype == MM_PORT_TYPE_AT)
port = MM_PORT (mm_at_serial_port_new (name));
/* For serial ports, enable port timeout checks */
if (port) {
g_signal_connect (port,
"timed-out",
G_CALLBACK (serial_port_timed_out_cb),
self);
}
} else if (!strcmp (subsys, "net")) {
port = MM_PORT (g_object_new (MM_TYPE_PORT,
MM_PORT_DEVICE, name,
MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET,
NULL));
}
if (!port) {
g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
"Failed to grab port %s/%s: unknown type?",
subsys, name);
mm_dbg ("(%s/%s): failed to create %s port",
subsys, name, mm_port_type_to_name (ptype));
return FALSE;
}
device = mm_modem_get_device (MM_MODEM (self));
mm_dbg ("(%s) type %s claimed by %s",
name,
mm_port_type_to_name (ptype),
device);
g_free (device);
key = get_hash_key (subsys, name);
g_hash_table_insert (priv->ports, key, port);
/* Let subclasses know we've grabbed it */
if (MM_MODEM_BASE_GET_CLASS (self)->port_grabbed)
MM_MODEM_BASE_GET_CLASS (self)->port_grabbed (self, port, at_pflags, user_data);
return TRUE;
}
static gboolean
modem_auth_request (MMModem *modem,
const char *authorization,
DBusGMethodInvocation *context,
MMAuthRequestCb callback,
gpointer callback_data,
GDestroyNotify notify,
GError **error)
{
MMModemBase *self = MM_MODEM_BASE (modem);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
g_assert (priv->authp);
return !!mm_auth_provider_request_auth (priv->authp,
authorization,
G_OBJECT (self),
context,
callback,
callback_data,
notify,
error);
}
static gboolean
modem_auth_finish (MMModem *modem, MMAuthRequest *req, GError **error)
{
if (mm_auth_request_get_result (req) != MM_AUTH_RESULT_AUTHORIZED) {
g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_AUTHORIZATION_REQUIRED,
"This request requires the '%s' authorization",
mm_auth_request_get_authorization (req));
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static void
mm_modem_base_init (MMModemBase *self)
{
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
priv->authp = mm_auth_provider_get ();
priv->ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
priv->tz_data = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, value_destroy);
priv->tz_poll_id = 0;
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_ENABLED,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_EQUIPMENT_IDENTIFIER,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_DEVICE_IDENTIFIER,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_UNLOCK_REQUIRED,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_UNLOCK_RETRIES,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_PIN_RETRY_COUNTS,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_IP_METHOD,
NULL,
MM_DBUS_INTERFACE_MODEM);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_TIME_NETWORK_TIMEZONE,
NULL,
MM_DBUS_INTERFACE_MODEM_TIME);
g_signal_connect (self,
"notify::" MM_MODEM_STATE,
G_CALLBACK (modem_state_changed),
NULL);
}
static void
modem_init (MMModem *modem_class)
{
modem_class->grab_port = grab_port;
modem_class->auth_request = modem_auth_request;
modem_class->auth_finish = modem_auth_finish;
}
static void
pc_init (MMPropertiesChanged *pc_class)
{
}
static void
modem_time_init (MMModemTime *modem_time_class)
{
}
static gboolean
is_enabled (MMModemState state)
{
return (state >= MM_MODEM_STATE_ENABLED);
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object);
gboolean old_enabled;
switch (prop_id) {
case MM_MODEM_PROP_STATE:
/* Ensure we update the 'enabled' property when the state changes */
old_enabled = is_enabled (priv->state);
priv->state = g_value_get_uint (value);
if (old_enabled != is_enabled (priv->state))
g_object_notify (object, MM_MODEM_ENABLED);
break;
case MM_MODEM_PROP_DRIVER:
/* Construct only */
priv->driver = g_value_dup_string (value);
break;
case MM_MODEM_PROP_PLUGIN:
/* Construct only */
priv->plugin = g_value_dup_string (value);
break;
case MM_MODEM_PROP_MASTER_DEVICE:
/* Construct only */
priv->device = g_value_dup_string (value);
break;
case MM_MODEM_PROP_IP_METHOD:
priv->ip_method = g_value_get_uint (value);
break;
case MM_MODEM_PROP_IP_TIMEOUT:
priv->ip_timeout = g_value_get_uint (value);
break;
case MM_MODEM_PROP_VALID:
case MM_MODEM_PROP_TYPE:
case MM_MODEM_PROP_ENABLED:
case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER:
case MM_MODEM_PROP_DEVICE_IDENTIFIER:
case MM_MODEM_PROP_UNLOCK_REQUIRED:
case MM_MODEM_PROP_UNLOCK_RETRIES:
case MM_MODEM_PROP_PIN_RETRY_COUNTS:
case MM_MODEM_PROP_NETWORK_TIMEZONE:
break;
case MM_MODEM_PROP_HW_VID:
/* Construct only */
priv->vid = g_value_get_uint (value);
break;
case MM_MODEM_PROP_HW_PID:
/* Construct only */
priv->pid = g_value_get_uint (value);
break;
case PROP_MAX_TIMEOUTS:
priv->max_timeouts = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GHashTable *
get_retry_counts (GArray *counts)
{
GHashTable *map;
int i;
map = g_hash_table_new (g_str_hash, g_str_equal);
if (counts) {
for (i = 0; i < counts->len; ++i) {
PinRetryCount *ur = (PinRetryCount *) &g_array_index (counts, PinRetryCount, i);
g_hash_table_insert (map, (char *) ur->name, GUINT_TO_POINTER (ur->count));
}
}
return map;
}
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object);
switch (prop_id) {
case MM_MODEM_PROP_STATE:
g_value_set_uint (value, priv->state);
break;
case MM_MODEM_PROP_MASTER_DEVICE:
g_value_set_string (value, priv->device);
break;
case MM_MODEM_PROP_DATA_DEVICE:
g_value_set_string (value, NULL);
break;
case MM_MODEM_PROP_DRIVER:
g_value_set_string (value, priv->driver);
break;
case MM_MODEM_PROP_PLUGIN:
g_value_set_string (value, priv->plugin);
break;
case MM_MODEM_PROP_TYPE:
g_value_set_uint (value, MM_MODEM_TYPE_UNKNOWN);
break;
case MM_MODEM_PROP_IP_METHOD:
g_value_set_uint (value, priv->ip_method);
break;
case MM_MODEM_PROP_IP_TIMEOUT:
g_value_set_uint (value, priv->ip_timeout);
break;
case MM_MODEM_PROP_VALID:
g_value_set_boolean (value, priv->valid);
break;
case MM_MODEM_PROP_ENABLED:
g_value_set_boolean (value, is_enabled (priv->state));
break;
case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER:
g_value_set_string (value, priv->equipment_ident);
break;
case MM_MODEM_PROP_DEVICE_IDENTIFIER:
g_value_set_string (value, priv->device_ident);
break;
case MM_MODEM_PROP_UNLOCK_REQUIRED:
g_value_set_string (value, priv->unlock_required);
break;
case MM_MODEM_PROP_UNLOCK_RETRIES:
g_value_set_uint (value, priv->unlock_retries);
break;
case MM_MODEM_PROP_PIN_RETRY_COUNTS:
g_value_set_boxed (value, get_retry_counts (priv->pin_retry_counts));
break;
case MM_MODEM_PROP_HW_VID:
g_value_set_uint (value, priv->vid);
break;
case MM_MODEM_PROP_HW_PID:
g_value_set_uint (value, priv->pid);
break;
case MM_MODEM_PROP_NETWORK_TIMEZONE:
g_value_set_boxed (value, priv->tz_data);
break;
case PROP_MAX_TIMEOUTS:
g_value_set_uint (value, priv->max_timeouts);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
finalize (GObject *object)
{
MMModemBase *self = MM_MODEM_BASE (object);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
mm_auth_provider_cancel_for_owner (priv->authp, object);
if (priv->tz_poll_id)
g_source_remove (priv->tz_poll_id);
g_hash_table_destroy (priv->ports);
g_hash_table_destroy (priv->tz_data);
g_free (priv->driver);
g_free (priv->plugin);
g_free (priv->device);
g_free (priv->equipment_ident);
g_free (priv->device_ident);
g_free (priv->unlock_required);
g_free (priv->manf);
g_free (priv->model);
g_free (priv->revision);
g_free (priv->ati);
g_free (priv->ati1);
g_free (priv->gsn);
G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object);
}
static void
mm_modem_base_class_init (MMModemBaseClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMModemBasePrivate));
/* Virtual methods */
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
g_object_class_override_property (object_class,
MM_MODEM_PROP_STATE,
MM_MODEM_STATE);
g_object_class_override_property (object_class,
MM_MODEM_PROP_MASTER_DEVICE,
MM_MODEM_MASTER_DEVICE);
g_object_class_override_property (object_class,
MM_MODEM_PROP_DATA_DEVICE,
MM_MODEM_DATA_DEVICE);
g_object_class_override_property (object_class,
MM_MODEM_PROP_DRIVER,
MM_MODEM_DRIVER);
g_object_class_override_property (object_class,
MM_MODEM_PROP_PLUGIN,
MM_MODEM_PLUGIN);
g_object_class_override_property (object_class,
MM_MODEM_PROP_TYPE,
MM_MODEM_TYPE);
g_object_class_override_property (object_class,
MM_MODEM_PROP_IP_METHOD,
MM_MODEM_IP_METHOD);
g_object_class_override_property (object_class,
MM_MODEM_PROP_IP_TIMEOUT,
MM_MODEM_IP_TIMEOUT);
g_object_class_override_property (object_class,
MM_MODEM_PROP_VALID,
MM_MODEM_VALID);
g_object_class_override_property (object_class,
MM_MODEM_PROP_ENABLED,
MM_MODEM_ENABLED);
g_object_class_override_property (object_class,
MM_MODEM_PROP_EQUIPMENT_IDENTIFIER,
MM_MODEM_EQUIPMENT_IDENTIFIER);
g_object_class_override_property (object_class,
MM_MODEM_PROP_DEVICE_IDENTIFIER,
MM_MODEM_DEVICE_IDENTIFIER);
g_object_class_override_property (object_class,
MM_MODEM_PROP_UNLOCK_REQUIRED,
MM_MODEM_UNLOCK_REQUIRED);
g_object_class_override_property (object_class,
MM_MODEM_PROP_UNLOCK_RETRIES,
MM_MODEM_UNLOCK_RETRIES);
g_object_class_override_property (object_class,
MM_MODEM_PROP_PIN_RETRY_COUNTS,
MM_MODEM_PIN_RETRY_COUNTS);
g_object_class_override_property (object_class,
MM_MODEM_PROP_HW_VID,
MM_MODEM_HW_VID);
g_object_class_override_property (object_class,
MM_MODEM_PROP_HW_PID,
MM_MODEM_HW_PID);
g_object_class_override_property (object_class,
MM_MODEM_PROP_NETWORK_TIMEZONE,
MM_MODEM_TIME_NETWORK_TIMEZONE);
g_object_class_install_property
(object_class, PROP_MAX_TIMEOUTS,
g_param_spec_uint (MM_MODEM_BASE_MAX_TIMEOUTS,
"Max timeouts",
"Maximum number of consecutive timed out commands sent to "
"the modem before disabling it. If 0, this feature is disabled.",
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
mm_properties_changed_signal_enable (object_class);
}