diff options
author | Dan Williams <dcbw@redhat.com> | 2009-06-28 14:05:05 -0400 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2009-06-28 14:05:05 -0400 |
commit | 6077763d90b69cfc60b23f383c4529f966facaaf (patch) | |
tree | 81ae20133a6ea116d58ddc2ee86644f4e1c9eb0a /src/mm-plugin-base.c | |
parent | 112f2da19dbe8dcd8f32b998459298e7c1884c67 (diff) |
asynchronous and deferred port detection
Allow plugins to perform asynchronous port detection, and to defer port detection
until later. This moves the prober bits into MMPluginBase so that all plugins
can take adavantage of it only when needed; the probing is not done at udev time.
Furthermore, plugins like Novatel can flip the secondary ports over the AT mode
through deferred detection, by deferring the secondary ports until the main port
has been detected and AT$NWDMAT has been sent.
This commit also finishes the port of the rest of the plugins (except mbm) over
to the new port detection methods and plugin API.
Diffstat (limited to 'src/mm-plugin-base.c')
-rw-r--r-- | src/mm-plugin-base.c | 796 |
1 files changed, 760 insertions, 36 deletions
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 6daa5895..25456ca5 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -23,66 +23,529 @@ #include <gudev/gudev.h> #include "mm-plugin-base.h" +#include "mm-serial-port.h" +#include "mm-serial-parsers.h" +#include "mm-errors.h" +#include "mm-marshal.h" -G_DEFINE_TYPE (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT) +static void plugin_init (MMPlugin *plugin_class); + +G_DEFINE_TYPE_EXTENDED (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_PLUGIN, plugin_init)) #define MM_PLUGIN_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE, MMPluginBasePrivate)) +/* A hash table shared between all instances of the plugin base that + * caches the probed capabilities so that only one plugin has to actually + * probe a port. + */ +static GHashTable *cached_caps = NULL; + + typedef struct { + char *name; + GUdevClient *client; + GHashTable *modems; + GHashTable *tasks; } MMPluginBasePrivate; +enum { + PROP_0, + PROP_NAME, + LAST_PROP +}; + +enum { + PROBE_RESULT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + + +typedef enum { + PROBE_STATE_GCAP_TRY1 = 0, + PROBE_STATE_GCAP_TRY2, + PROBE_STATE_GCAP_TRY3, + PROBE_STATE_ATI, + PROBE_STATE_CGMM, +} ProbeState; + +/*****************************************************************************/ + +G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT) + +#define MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskPrivate)) typedef struct { - char *key; - gpointer modem; -} FindInfo; + MMPluginBase *plugin; + GUdevDevice *port; + GUdevDevice *physdev; + char *driver; + + MMSerialPort *probe_port; + guint32 probed_caps; + ProbeState probe_state; + guint probe_id; + char *probe_resp; + GError *probe_error; + + MMSupportsPortResultFunc callback; + gpointer callback_data; +} MMPluginBaseSupportsTaskPrivate; + +static MMPluginBaseSupportsTask * +supports_task_new (MMPluginBase *self, + GUdevDevice *port, + GUdevDevice *physdev, + const char *driver, + MMSupportsPortResultFunc callback, + gpointer callback_data) +{ + MMPluginBaseSupportsTask *task; + MMPluginBaseSupportsTaskPrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL); + g_return_val_if_fail (port != NULL, NULL); + g_return_val_if_fail (physdev != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + + task = (MMPluginBaseSupportsTask *) g_object_new (MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, NULL); + + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + priv->plugin = self; + priv->port = g_object_ref (port); + priv->physdev = g_object_ref (physdev); + priv->driver = g_strdup (driver); + priv->callback = callback; + priv->callback_data = callback_data; + + return task; +} + +MMPlugin * +mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, NULL); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); + + return MM_PLUGIN (MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->plugin); +} + +GUdevDevice * +mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, NULL); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); + + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->port; +} + +GUdevDevice * +mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, NULL); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); + + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev; +} + +const char * +mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, NULL); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); + + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->driver; +} + +guint32 +mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, 0); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), 0); + + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->probed_caps; +} + +void +mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, + guint32 level) +{ + MMPluginBaseSupportsTaskPrivate *priv; + const char *subsys, *name; + + g_return_if_fail (task != NULL); + g_return_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task)); + + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + g_return_if_fail (priv->callback != NULL); + + subsys = g_udev_device_get_subsystem (priv->port); + name = g_udev_device_get_name (priv->port); + + priv->callback (MM_PLUGIN (priv->plugin), subsys, name, level, priv->callback_data); + + /* Clear out the callback, it shouldn't be called more than once */ + priv->callback = NULL; + priv->callback_data = NULL; +} + +static void +mm_plugin_base_supports_task_init (MMPluginBaseSupportsTask *self) +{ +} static void -find_modem (gpointer key, gpointer data, gpointer user_data) +dispose (GObject *object) { - FindInfo *info = user_data; + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object); + + g_object_unref (priv->port); + g_object_unref (priv->physdev); + g_free (priv->driver); + g_free (priv->probe_resp); + g_clear_error (&(priv->probe_error)); - if (!info->key && data == info->modem) - info->key = g_strdup ((const char *) key); + if (priv->probe_id) + g_source_remove (priv->probe_id); + if (priv->probe_port) + g_object_unref (priv->probe_port); + + G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); } static void -modem_destroyed (gpointer data, GObject *modem) +mm_plugin_base_supports_task_class_init (MMPluginBaseSupportsTaskClass *klass) { - MMPluginBase *self = MM_PLUGIN_BASE (data); - MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - FindInfo info = { NULL, modem }; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMPluginBaseSupportsTaskPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; +} + +/*****************************************************************************/ + +#define TAG_PROBE_ID "mm-plugin-base-probe-id" +#define TAG_PROBE_PORT "mm-plugin-base-probe-port" +#define TAG_PROBE_STATE "mm-plugin-base-probe-state" + +#define MM_PLUGIN_BASE_PORT_CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \ + MM_PLUGIN_BASE_PORT_CAP_IS707_P | \ + MM_PLUGIN_BASE_PORT_CAP_IS856 | \ + MM_PLUGIN_BASE_PORT_CAP_IS856_A) + +#define CAP_GSM_OR_CDMA (MM_PLUGIN_BASE_PORT_CAP_CDMA | MM_PLUGIN_BASE_PORT_CAP_GSM) + +struct modem_caps { + char *name; + guint32 bits; +}; + +static struct modem_caps modem_caps[] = { + {"+CGSM", MM_PLUGIN_BASE_PORT_CAP_GSM}, + {"+CIS707-A", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, + {"+CIS707A", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Cmotech */ + {"+CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, + {"CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Qualcomm Gobi */ + {"+CIS707P", MM_PLUGIN_BASE_PORT_CAP_IS707_P}, + {"CIS-856", MM_PLUGIN_BASE_PORT_CAP_IS856}, + {"+IS-856", MM_PLUGIN_BASE_PORT_CAP_IS856}, /* Cmotech */ + {"CIS-856-A", MM_PLUGIN_BASE_PORT_CAP_IS856_A}, + {"CIS-856A", MM_PLUGIN_BASE_PORT_CAP_IS856_A}, /* Kyocera KPC680 */ + {"+DS", MM_PLUGIN_BASE_PORT_CAP_DS}, + {"+ES", MM_PLUGIN_BASE_PORT_CAP_ES}, + {"+MS", MM_PLUGIN_BASE_PORT_CAP_MS}, + {"+FCLASS", MM_PLUGIN_BASE_PORT_CAP_FCLASS}, + {NULL} +}; + +static guint32 +parse_gcap (const char *buf) +{ + struct modem_caps *cap = modem_caps; + guint32 ret = 0; + + while (cap->name) { + if (strstr (buf, cap->name)) + ret |= cap->bits; + cap++; + } + return ret; +} + +static guint32 +parse_cgmm (const char *buf) +{ + if (strstr (buf, "GSM900") || strstr (buf, "GSM1800") || + strstr (buf, "GSM1900") || strstr (buf, "GSM850")) + return MM_PLUGIN_BASE_PORT_CAP_GSM; + return 0; +} + +static gboolean +emit_probe_result (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPlugin *self = mm_plugin_base_supports_task_get_plugin (task); + + /* Close the serial port */ + g_object_unref (task_priv->probe_port); + task_priv->probe_port = NULL; + + task_priv->probe_id = 0; + g_signal_emit (self, signals[PROBE_RESULT], 0, task, task_priv->probed_caps); + return FALSE; +} + +static void +probe_complete (MMPluginBaseSupportsTask *task) +{ + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + g_hash_table_insert (cached_caps, + g_strdup (mm_port_get_device (MM_PORT (task_priv->probe_port))), + GUINT_TO_POINTER (task_priv->probed_caps)); + + task_priv->probe_id = g_idle_add (emit_probe_result, task); +} + +static void +parse_response (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static void +real_handle_probe_response (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + const char *cmd, + const char *response, + const GError *error) +{ + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMSerialPort *port = task_priv->probe_port; + + task_priv->probe_state++; + + if (error) { + if (error->code == MM_SERIAL_RESPONSE_TIMEOUT) { + /* Try GCAP again */ + if (task_priv->probe_state <= PROBE_STATE_GCAP_TRY3) { + mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + return; + } + + /* Otherwise, if all the GCAP tries timed out, ignore the port + * as it's probably not an AT-capable port. + */ + probe_complete (task); + } else if (task_priv->probe_state <= PROBE_STATE_GCAP_TRY3) { + mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + } else if (task_priv->probe_state == PROBE_STATE_ATI) { + /* Give ATI a try */ + mm_serial_port_queue_command (port, "I", 3, parse_response, task); + } else if (task_priv->probe_state == PROBE_STATE_CGMM) { + /* If CGMM failed, probably not a modem port */ + probe_complete (task); + } + return; + } + + if (response) { + /* Parse the response */ + task_priv->probed_caps = parse_gcap (response); + + /* Some models (BUSlink SCWi275u) stick stupid stuff in the GMM response */ + if ( (task_priv->probe_state == PROBE_STATE_CGMM) + && !(task_priv->probed_caps & CAP_GSM_OR_CDMA)) + task_priv->probed_caps = parse_cgmm (response); + + if (task_priv->probed_caps & CAP_GSM_OR_CDMA) { + probe_complete (task); + return; + } + } + + /* Try a different command */ + switch (task_priv->probe_state) { + case PROBE_STATE_GCAP_TRY1: + case PROBE_STATE_GCAP_TRY2: + case PROBE_STATE_GCAP_TRY3: + mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + break; + case PROBE_STATE_ATI: + /* After the last GCAP attempt, try ATI */ + mm_serial_port_queue_command (port, "I", 3, parse_response, task); + break; + case PROBE_STATE_CGMM: + /* After the ATI attempt, try CGMM */ + mm_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); + break; + default: + /* Probably not GSM or CDMA */ + probe_complete (task); + break; + } +} + +static gboolean +handle_probe_response (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPluginBase *self = MM_PLUGIN_BASE (mm_plugin_base_supports_task_get_plugin (task)); + const char *cmd = NULL; + + switch (task_priv->probe_state) { + case PROBE_STATE_GCAP_TRY1: + case PROBE_STATE_GCAP_TRY2: + case PROBE_STATE_GCAP_TRY3: + cmd = "+GCAP"; + break; + case PROBE_STATE_ATI: + cmd = "I"; + break; + case PROBE_STATE_CGMM: + default: + cmd = "+CGMM"; + break; + } + + MM_PLUGIN_BASE_GET_CLASS (self)->handle_probe_response (self, + task, + cmd, + task_priv->probe_resp, + task_priv->probe_error); + return FALSE; +} + +static void +parse_response (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + if (task_priv->probe_id) + g_source_remove (task_priv->probe_id); + g_free (task_priv->probe_resp); + task_priv->probe_resp = NULL; + g_clear_error (&(task_priv->probe_error)); + + if (response && response->len) + task_priv->probe_resp = g_strdup (response->str); + if (error) + task_priv->probe_error = g_error_copy (error); + + /* Schedule the response handler in an idle, since we can't emit the + * PROBE_RESULT signal from the serial port response handler without + * potentially destroying the serial port in the middle of its response + * handler, which it understandably doesn't like. + */ + task_priv->probe_id = g_idle_add (handle_probe_response, task); +} - g_hash_table_foreach (priv->modems, find_modem, &info); - if (info.key) - g_hash_table_remove (priv->modems, info.key); - g_free (info.key); +static void +flash_done (MMSerialPort *port, gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + task_priv->probe_id = 0; + mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); } gboolean -mm_plugin_base_add_modem (MMPluginBase *self, - MMModem *modem) +mm_plugin_base_probe_port (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + GError **error) { - MMPluginBasePrivate *priv; - const char *device; + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMSerialPort *serial; + const char *name; + GUdevDevice *port; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), FALSE); - g_return_val_if_fail (modem != NULL, FALSE); - g_return_val_if_fail (MM_IS_MODEM (modem), FALSE); - - priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - - device = mm_modem_get_device (modem); - if (g_hash_table_lookup (priv->modems, device)) + g_return_val_if_fail (task != NULL, FALSE); + + port = mm_plugin_base_supports_task_get_port (task); + g_assert (port); + name = g_udev_device_get_name (port); + g_assert (name); + + serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + g_object_set (serial, + MM_SERIAL_PORT_SEND_DELAY, 100000, + MM_PORT_CARRIER_DETECT, FALSE, + NULL); + + mm_serial_port_set_response_parser (serial, + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); + + if (!mm_serial_port_open (serial, error)) { + g_object_unref (serial); return FALSE; + } - g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self); - g_hash_table_insert (priv->modems, g_strdup (device), modem); + g_debug ("(%s): probe requested by plugin '%s'", name, priv->name); + task_priv->probe_port = serial; + task_priv->probe_id = mm_serial_port_flash (serial, 100, flash_done, task); return TRUE; } +gboolean +mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, + GUdevDevice *port, + guint32 *capabilities) +{ + return g_hash_table_lookup_extended (cached_caps, + g_udev_device_get_name (port), + NULL, + (gpointer) capabilities); +} + +/*****************************************************************************/ + +static void +modem_destroyed (gpointer data, GObject *modem) +{ + MMPluginBase *self = MM_PLUGIN_BASE (data); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + GHashTableIter iter; + gpointer key, value; + + /* Remove it from the modems info */ + g_hash_table_iter_init (&iter, priv->modems); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (value == modem) { + g_hash_table_iter_remove (&iter); + break; + } + } + + /* Since we don't track port cached capabilities on a per-modem basis, + * we just have to live with blowing away the cached capabilities whenever + * a modem gets removed. Could do better here by storing a structure + * in the cached capabilities table that includes { caps, modem device } + * or something and then only removing cached capabilities for ports + * that the modem that was just removed owned, but whatever. + */ + g_hash_table_remove_all (cached_caps); +} + MMModem * mm_plugin_base_find_modem (MMPluginBase *self, const char *master_device) @@ -132,9 +595,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, guint16 *vendor, guint16 *product) { - GUdevClient *client; + MMPluginBasePrivate *priv; GUdevDevice *device = NULL; - const char *tmp[] = { subsys, NULL }; const char *vid, *pid; gboolean success = FALSE; @@ -147,11 +609,9 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, if (product) g_return_val_if_fail (*product == 0, FALSE); - client = g_udev_client_new (tmp); - if (!client) - return FALSE; + priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - device = g_udev_client_query_by_subsystem_and_name (client, subsys, name); + device = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name); if (!device) goto out; @@ -180,18 +640,255 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, out: if (device) g_object_unref (device); - g_object_unref (client); return success; } +static char * +get_key (const char *subsys, const char *name) +{ + return g_strdup_printf ("%s%s", subsys, name); +} + +static const char * +get_name (MMPlugin *plugin) +{ + return MM_PLUGIN_BASE_GET_PRIVATE (plugin)->name; +} + +static char * +get_driver_name (GUdevDevice *device) +{ + GUdevDevice *parent = NULL; + const char *driver; + char *ret; + + driver = g_udev_device_get_driver (device); + if (!driver) { + parent = g_udev_device_get_parent (device); + if (parent) + driver = g_udev_device_get_driver (parent); + } + + if (driver) + ret = g_strdup (driver); + if (parent) + g_object_unref (parent); + + return ret; +} + +static GUdevDevice * +real_find_physical_device (MMPluginBase *plugin, GUdevDevice *child) +{ + GUdevDevice *iter, *old = NULL; + const char *bus, *type; + + g_return_val_if_fail (child != NULL, NULL); + + bus = g_udev_device_get_property (child, "ID_BUS"); + if (!bus) + return NULL; + + if (!strcmp (bus, "usb")) { + /* Walk the parents to find the 'usb_device' for this device. */ + iter = g_object_ref (child); + while (iter) { + type = g_udev_device_get_devtype (iter); + if (type && !strcmp (type, "usb_device")) + return iter; + + old = iter; + iter = g_udev_device_get_parent (old); + g_object_unref (old); + } + g_object_unref (child); + } else if (!strcmp (bus, "pci")) { + return g_udev_device_get_parent (child); + } + + // FIXME: pci and pcmcia/cardbus? (like Sierra 850/860) + return NULL; +} + +static MMPluginSupportsResult +supports_port (MMPlugin *plugin, + const char *subsys, + const char *name, + MMSupportsPortResultFunc callback, + gpointer callback_data) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + GUdevDevice *port = NULL, *physdev = NULL; + char *driver = NULL, *key = NULL; + MMPluginBaseSupportsTask *task; + MMPluginSupportsResult result = MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + MMModem *existing; + const char *master_path; + + key = get_key (subsys, name); + task = g_hash_table_lookup (priv->tasks, key); + if (task) { + g_free (key); + g_return_val_if_fail (task == NULL, MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED); + } + + port = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name); + if (!port) + goto out; + + physdev = MM_PLUGIN_BASE_GET_CLASS (self)->find_physical_device (self, port); + if (!physdev) + goto out; + + driver = get_driver_name (port); + if (!driver) + goto out; + + task = supports_task_new (self, port, physdev, driver, callback, callback_data); + g_assert (task); + g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task)); + + /* Help the plugin out a bit by finding an existing modem for this port */ + master_path = g_udev_device_get_sysfs_path (physdev); + existing = g_hash_table_lookup (priv->modems, master_path); + + result = MM_PLUGIN_BASE_GET_CLASS (self)->supports_port (self, existing, task); + if (result != MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS) { + /* If the plugin doesn't support the port at all, the supports task is + * not needed. + */ + g_hash_table_remove (priv->tasks, key); + } + g_object_unref (task); + +out: + if (physdev) + g_object_unref (physdev); + if (port) + g_object_unref (port); + g_free (key); + g_free (driver); + return result; +} + +static void +cancel_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + MMPluginBaseSupportsTask *task; + char *key; + + key = get_key (subsys, name); + task = g_hash_table_lookup (priv->tasks, key); + if (task) { + /* Let the plugin cancel any ongoing tasks */ + if (MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task) + MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task (self, task); + g_hash_table_remove (priv->tasks, key); + } + + g_free (key); +} + +static MMModem * +grab_port (MMPlugin *plugin, + const char *subsys, + const char *name, + GError **error) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + MMPluginBaseSupportsTask *task; + char *key; + MMModem *existing = NULL, *modem = NULL; + const char *master_path; + + key = get_key (subsys, name); + task = g_hash_table_lookup (priv->tasks, key); + if (!task) { + g_free (key); + g_return_val_if_fail (task != NULL, FALSE); + } + + /* Help the plugin out a bit by finding an existing modem for this port */ + master_path = g_udev_device_get_sysfs_path (mm_plugin_base_supports_task_get_physdev (task)); + existing = g_hash_table_lookup (priv->modems, master_path); + + /* Let the modem grab the port */ + modem = MM_PLUGIN_BASE_GET_CLASS (self)->grab_port (self, existing, task, error); + if (modem && !existing) { + g_hash_table_insert (priv->modems, g_strdup (master_path), modem); + g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self); + } + + g_hash_table_remove (priv->tasks, key); + g_free (key); + return modem; +} + /*****************************************************************************/ static void +plugin_init (MMPlugin *plugin_class) +{ + /* interface implementation */ + plugin_class->get_name = get_name; + plugin_class->supports_port = supports_port; + plugin_class->cancel_supports_port = cancel_supports_port; + plugin_class->grab_port = grab_port; +} + +static void mm_plugin_base_init (MMPluginBase *self) { MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + const char *subsys[] = { "tty", "net", NULL }; + + if (!cached_caps) + cached_caps = g_hash_table_new (g_str_hash, g_str_equal); + + priv->client = g_udev_client_new (subsys); priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_object_unref); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_NAME: + /* Construct only */ + priv->name = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } static void @@ -199,7 +896,12 @@ finalize (GObject *object) { MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + g_free (priv->name); + + g_object_unref (priv->client); + g_hash_table_destroy (priv->modems); + g_hash_table_destroy (priv->tasks); G_OBJECT_CLASS (mm_plugin_base_parent_class)->finalize (object); } @@ -211,6 +913,28 @@ mm_plugin_base_class_init (MMPluginBaseClass *klass) g_type_class_add_private (object_class, sizeof (MMPluginBasePrivate)); + klass->find_physical_device = real_find_physical_device; + klass->handle_probe_response = real_handle_probe_response; + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_NAME, + g_param_spec_string (MM_PLUGIN_BASE_NAME, + "Name", + "Name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[PROBE_RESULT] = + g_signal_new ("probe-result", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMPluginBaseClass, probe_result), + NULL, NULL, + mm_marshal_VOID__OBJECT_UINT, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT); } |