diff options
-rw-r--r-- | src/mm-plugin-base.c | 371 | ||||
-rw-r--r-- | src/mm-plugin-base.h | 12 |
2 files changed, 290 insertions, 93 deletions
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 8d033a72..09d72394 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -45,11 +45,18 @@ G_DEFINE_TYPE_EXTENDED (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT, #define MM_PLUGIN_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE, MMPluginBasePrivate)) +/* Struct to be stored in the cached hash table */ +typedef struct { + guint32 capabilities; + gchar *vendor; + gchar *product; +} MMPluginBaseProbedInfo; + /* 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. + * caches the probed capabilities and reported vendor/model (when available) + * so that only one plugin has to actually probe a port. */ -static GHashTable *cached_caps = NULL; +static GHashTable *probed_info = NULL; /* Virtual port corresponding to the embeded modem */ static gchar *virtual_port[] = {"smd0", NULL}; @@ -73,18 +80,65 @@ enum { 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_CPIN, - PROBE_STATE_CGMM, + /* Probing capabilities... */ + PROBE_STATE_CAPS_GCAP_TRY1 = 0, + PROBE_STATE_CAPS_GCAP_TRY2, + PROBE_STATE_CAPS_GCAP_TRY3, + PROBE_STATE_CAPS_ATI, + PROBE_STATE_CAPS_CPIN, + PROBE_STATE_CAPS_CGMM, + /* Probing vendor... */ + PROBE_STATE_VENDOR_CGMI, + PROBE_STATE_VENDOR_GMI, + PROBE_STATE_VENDOR_ATI, + /* Probing product... */ + PROBE_STATE_PRODUCT_CGMM, + PROBE_STATE_PRODUCT_GMM, + PROBE_STATE_PRODUCT_ATI, PROBE_STATE_LAST } ProbeState; +/* Additional helper IDs for the states, to be updated whenever new intermediate + * states are added in each probing group */ +#define PROBE_STATE_CAPS_FIRST PROBE_STATE_CAPS_GCAP_TRY1 +#define PROBE_STATE_CAPS_LAST PROBE_STATE_CAPS_CGMM +#define PROBE_STATE_VENDOR_FIRST PROBE_STATE_VENDOR_CGMI +#define PROBE_STATE_VENDOR_LAST PROBE_STATE_VENDOR_ATI +#define PROBE_STATE_PRODUCT_FIRST PROBE_STATE_PRODUCT_CGMM +#define PROBE_STATE_PRODUCT_LAST PROBE_STATE_PRODUCT_ATI + +typedef struct { + /* The command to send in this probing state */ + gchar *cmd; + /* Whether the command reply should be cached */ + gboolean cached; +} ProbeStateCmd; + +/* List of commands sent in each state */ +static const ProbeStateCmd state_commands[PROBE_STATE_LAST] = { + /* Probing capabilities... */ + { "+GCAP", FALSE }, /* PROBE_STATE_CAPS_GCAP_TRY1 */ + { "+GCAP", FALSE }, /* PROBE_STATE_CAPS_GCAP_TRY2 */ + { "+GCAP", FALSE }, /* PROBE_STATE_CAPS_GCAP_TRY3 */ + { "I", TRUE }, /* PROBE_STATE_CAPS_ATI */ + { "+CPIN?", FALSE }, /* PROBE_STATE_CAPS_CPIN */ + { "+CGMM", TRUE }, /* PROBE_STATE_CAPS_CGMM */ + /* Probing vendor... */ + { "+CGMI", TRUE }, /* PROBE_STATE_VENDOR_CGMI */ + { "+GMI", TRUE }, /* PROBE_STATE_VENDOR_GMI */ + { "I", TRUE }, /* PROBE_STATE_VENDOR_ATI */ + /* Probing product... */ + { "+CGMM", TRUE }, /* PROBE_STATE_PRODUCT_CGMM */ + { "+GMM", TRUE }, /* PROBE_STATE_PRODUCT_GMM */ + { "I", TRUE } /* PROBE_STATE_PRODUCT_ATI */ +}; + static void probe_complete (MMPluginBaseSupportsTask *task); +static void parse_response (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); /*****************************************************************************/ @@ -113,6 +167,8 @@ typedef struct { MMAtSerialPort *probe_port; MMQcdmSerialPort *qcdm_port; guint32 probed_caps; + gchar *probed_vendor; + gchar *probed_product; ProbeState probe_state; guint probe_id; char *probe_resp; @@ -202,6 +258,24 @@ mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask * return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->probed_caps; } +const gchar * +mm_plugin_base_supports_task_get_probed_vendor (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)->probed_vendor; +} + +const gchar * +mm_plugin_base_supports_task_get_probed_product (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)->probed_product; +} + void mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, guint32 level) @@ -299,6 +373,9 @@ supports_task_dispose (GObject *object) g_object_unref (priv->qcdm_port); } + g_free (priv->probed_vendor); + g_free (priv->probed_product); + G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); } @@ -346,7 +423,7 @@ static struct modem_caps modem_caps[] = { }; static guint32 -parse_gcap (const char *buf) +parse_caps_gcap (const char *buf) { struct modem_caps *cap = modem_caps; guint32 ret = 0; @@ -360,7 +437,7 @@ parse_gcap (const char *buf) } static guint32 -parse_cpin (const char *buf) +parse_caps_cpin (const char *buf) { if ( strcasestr (buf, "SIM PIN") || strcasestr (buf, "SIM PUK") @@ -384,7 +461,7 @@ parse_cpin (const char *buf) } static guint32 -parse_cgmm (const char *buf) +parse_caps_cgmm (const char *buf) { if (strstr (buf, "GSM900") || strstr (buf, "GSM1800") || strstr (buf, "GSM1900") || strstr (buf, "GSM850")) @@ -392,6 +469,18 @@ parse_cgmm (const char *buf) return 0; } +static gchar * +parse_vendor_cgmi (const gchar *buf) +{ + return g_strstrip (g_strdelimit (g_strdup (buf), "\r\n", ' ')); +} + +static gchar * +parse_product_cgmm (const gchar *buf) +{ + return g_strstrip (g_strdelimit (g_strdup (buf), "\r\n", ' ')); +} + static const char *dq_strings[] = { /* Option Icera-based devices */ "option/faema_", @@ -402,6 +491,17 @@ static const char *dq_strings[] = { }; static void +probed_info_free (MMPluginBaseProbedInfo *info) +{ + if (!info) + return; + + g_free (info->vendor); + g_free (info->product); + g_free (info); +} + +static void port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) { MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); @@ -427,6 +527,8 @@ port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) if (!memcmp (&buffer->data[i], *iter, iter_len)) { /* Immediately close the port and complete probing */ priv->probed_caps = 0; + priv->probed_vendor = NULL; + priv->probed_product = NULL; mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); probe_complete (task); return; @@ -461,13 +563,20 @@ static void probe_complete (MMPluginBaseSupportsTask *task) { MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPluginBaseProbedInfo *info; MMPort *port; port = priv->probe_port ? MM_PORT (priv->probe_port) : MM_PORT (priv->qcdm_port); g_assert (port); - g_hash_table_insert (cached_caps, + + /* Allocate new cached info struct and store it in the hash table */ + info = g_malloc0 (sizeof (*info)); + info->capabilities = priv->probed_caps; + info->vendor = g_strdup (priv->probed_vendor); + info->product = g_strdup (priv->probed_product); + g_hash_table_insert (probed_info, g_strdup (mm_port_get_device (port)), - GUINT_TO_POINTER (priv->probed_caps)); + info); priv->probe_id = g_idle_add (emit_probe_result, task); } @@ -509,7 +618,6 @@ qcdm_verinfo_cb (MMQcdmSerialPort *port, /* yay, probably a QCDM port */ qcdm_result_unref (result); priv->probed_caps |= MM_PLUGIN_BASE_PORT_CAP_QCDM; - done: probe_complete (task); } @@ -575,12 +683,6 @@ try_qcdm_probe (MMPluginBaseSupportsTask *task) } static void -parse_response (MMAtSerialPort *port, - GString *response, - GError *error, - gpointer user_data); - -static void real_handle_probe_response (MMPluginBase *self, MMPluginBaseSupportsTask *task, const char *cmd, @@ -598,83 +700,149 @@ real_handle_probe_response (MMPluginBase *self, ignore_error = TRUE; if (error && !ignore_error) { + /* Only allow timeout errors in the initial AT+GCAP queries. If all AT+GCAP + * get timed out, assume it's not an AT port. */ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { /* Try GCAP again */ - if (task_priv->probe_state < PROBE_STATE_GCAP_TRY3) { + if (task_priv->probe_state < PROBE_STATE_CAPS_GCAP_TRY3) { task_priv->probe_state++; - mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); - } else { - /* Otherwise, if all the GCAP tries timed out, ignore the port - * as it's probably not an AT-capable port. Try QCDM. - */ - try_qcdm_probe (task); + mm_at_serial_port_queue_command (port, + state_commands[task_priv->probe_state].cmd, + 3, + parse_response, + task); + return; } - return; + + /* If all the GCAP tries timed out, ignore the port as it's + * probably not an AT-capable port. Try QCDM. + */ + if (task_priv->probe_state == PROBE_STATE_CAPS_GCAP_TRY3) { + try_qcdm_probe (task); + return; + } + + /* Otherwise, it's a timeout in some other command being probed, so try the + * next one if any */ } - /* Otherwise proceed to the next command */ + /* Non-timeout error, so proceed to the next command if any */ } else if (response) { /* Parse the response */ switch (task_priv->probe_state) { - case PROBE_STATE_GCAP_TRY1: - case PROBE_STATE_GCAP_TRY2: - case PROBE_STATE_GCAP_TRY3: - case PROBE_STATE_ATI: + case PROBE_STATE_CAPS_GCAP_TRY1: + case PROBE_STATE_CAPS_GCAP_TRY2: + case PROBE_STATE_CAPS_GCAP_TRY3: + case PROBE_STATE_CAPS_ATI: /* Some modems don't respond to AT+GCAP, but often they put a * GCAP-style response as a line in the ATI response. */ - task_priv->probed_caps = parse_gcap (response); + task_priv->probed_caps = parse_caps_gcap (response); break; - case PROBE_STATE_CPIN: + case PROBE_STATE_CAPS_CPIN: /* Some devices (ZTE MF628/ONDA MT503HS for example) reply to * anything but AT+CPIN? with ERROR if the device has a PIN set. * Since no known CDMA modems support AT+CPIN? we can consider the * device a GSM device if it returns a non-error response to AT+CPIN?. */ - task_priv->probed_caps = parse_cpin (response); + task_priv->probed_caps = parse_caps_cpin (response); break; - case PROBE_STATE_CGMM: + case PROBE_STATE_CAPS_CGMM: /* Some models (BUSlink SCWi275u) stick stupid stuff in the CGMM * response but at least it allows us to identify them. */ - task_priv->probed_caps = parse_cgmm (response); + task_priv->probed_caps = parse_caps_cgmm (response); + break; + case PROBE_STATE_VENDOR_CGMI: + case PROBE_STATE_VENDOR_GMI: + case PROBE_STATE_VENDOR_ATI: + /* These replies parsed the same way, just removing EOLs */ + task_priv->probed_vendor = parse_vendor_cgmi (response); + break; + case PROBE_STATE_PRODUCT_CGMM: + case PROBE_STATE_PRODUCT_GMM: + case PROBE_STATE_PRODUCT_ATI: + /* These replies parsed the same way, just removing EOLs */ + task_priv->probed_product = parse_product_cgmm (response); break; default: break; } + } + + /* Now, choose the proper next state */ + if (task_priv->probe_state <= PROBE_STATE_CAPS_LAST) { + /* Probing capabilities */ if (task_priv->probed_caps & CAP_GSM_OR_CDMA) { + /* Got capabilities probed, go on with vendor probing */ + task_priv->probe_state = PROBE_STATE_VENDOR_FIRST; + } else if (task_priv->probe_state < PROBE_STATE_CAPS_LAST) { + /* Didn't get capabilities probed yet, go on to next caps + * probing command */ + task_priv->probe_state++; + } else { + /* Tried probing all commands for capabilities and didn't get + * any: just end probing here, it probably isn't a GSM or CDMA + * modem */ + mm_warn ("Couldn't probe for capabilities, probably not a GSM or CDMA modem"); + probe_complete (task); + return; + } + } else if (task_priv->probe_state <= PROBE_STATE_VENDOR_LAST) { + /* Probing vendors */ + if (task_priv->probed_vendor) { + /* Got vendor probed, go on with product probing. */ + task_priv->probe_state = PROBE_STATE_PRODUCT_FIRST; + } else if (task_priv->probe_state < PROBE_STATE_VENDOR_LAST) { + /* Didn't get vendor probed yet, go on to next vendor + * probing command */ + task_priv->probe_state++; + } else { + /* Tried probing all commands for vendor and didn't get + * any: just end probing here, product ID is useless without + * vendor ID */ + probe_complete (task); + return; + } + } else if (task_priv->probe_state <= PROBE_STATE_PRODUCT_LAST) { + /* Probing products */ + if (!task_priv->probed_product && + task_priv->probe_state < PROBE_STATE_PRODUCT_LAST) { + /* Didn't get probed product yet but more commands available, + * go on with the next one */ + task_priv->probe_state++; + } else { + /* Got product probed or no more commands to probe, so end. */ probe_complete (task); return; } + } else { + g_warn_if_reached (); } - task_priv->probe_state++; - - /* Try a different command */ - switch (task_priv->probe_state) { - case PROBE_STATE_GCAP_TRY2: - case PROBE_STATE_GCAP_TRY3: - mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); - break; - case PROBE_STATE_ATI: - /* After the last GCAP attempt, try ATI */ - mm_at_serial_port_queue_command (port, "I", 3, parse_response, task); - break; - case PROBE_STATE_CPIN: - /* After the ATI attempt, try CPIN */ - mm_at_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task); - break; - case PROBE_STATE_CGMM: - /* After the CPIN attempt, try CGMM */ - mm_at_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); - break; - default: - /* Probably not GSM or CDMA */ + /* Out of last state? */ + if (task_priv->probe_state >= PROBE_STATE_LAST) { + /* Probing ended */ probe_complete (task); - break; + return; } + + /* Go on with the command in next state */ + + if (state_commands[task_priv->probe_state].cached) + mm_at_serial_port_queue_command_cached (port, + state_commands[task_priv->probe_state].cmd, + 3, + parse_response, + task); + else + mm_at_serial_port_queue_command (port, + state_commands[task_priv->probe_state].cmd, + 3, + parse_response, + task); } static gboolean @@ -683,29 +851,10 @@ 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_CPIN: - cmd = "+CPIN?"; - break; - case PROBE_STATE_CGMM: - default: - cmd = "+CGMM"; - break; - } MM_PLUGIN_BASE_GET_CLASS (self)->handle_probe_response (self, task, - cmd, + state_commands[task_priv->probe_state].cmd, task_priv->probe_resp, task_priv->probe_error); return FALSE; @@ -902,12 +1051,44 @@ mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, GUdevDevice *port, guint32 *capabilities) { - gpointer tmp = NULL; - gboolean found; + MMPluginBaseProbedInfo *info; + + if (g_hash_table_lookup_extended (probed_info, + g_udev_device_get_name (port), + NULL, + (gpointer *)&info)) { + *capabilities = info->capabilities; + return TRUE; + } + + *capabilities = 0; + return FALSE; +} + +gboolean +mm_plugin_base_get_cached_product_info (MMPluginBase *self, + GUdevDevice *port, + gchar **vendor, + gchar **product) +{ + MMPluginBaseProbedInfo *info; + + if (g_hash_table_lookup_extended (probed_info, + g_udev_device_get_name (port), + NULL, + (gpointer *)&info)) { + if (vendor) + *vendor = (info->vendor ? g_strdup (info->vendor) : NULL); + if (product) + *product = (info->product ? g_strdup (info->product) : NULL); + return TRUE; + } - found = g_hash_table_lookup_extended (cached_caps, g_udev_device_get_name (port), NULL, tmp); - *capabilities = GPOINTER_TO_UINT (tmp); - return found; + if (vendor) + *vendor = NULL; + if (product) + *product = NULL; + return FALSE; } /*****************************************************************************/ @@ -922,7 +1103,7 @@ modem_destroyed (gpointer data, GObject *modem) * 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); + g_hash_table_remove_all (probed_info); } gboolean @@ -1217,12 +1398,18 @@ 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); + if (!probed_info) { + probed_info = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) probed_info_free); + } priv->client = g_udev_client_new (subsys); - priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + priv->tasks = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, (GDestroyNotify) g_object_unref); } diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h index 799f6813..91b3b8df 100644 --- a/src/mm-plugin-base.h +++ b/src/mm-plugin-base.h @@ -56,7 +56,7 @@ typedef struct { GType mm_plugin_base_supports_task_get_type (void); -/* +/* * response: the response string from the modem, if no error occurred * error: the error returned by the modem or serial stack, if any * tries: number of times the custom init command has been sent to the modem @@ -86,6 +86,10 @@ const char *mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *t guint32 mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task); +const gchar *mm_plugin_base_supports_task_get_probed_vendor (MMPluginBaseSupportsTask *task); + +const gchar *mm_plugin_base_supports_task_get_probed_product (MMPluginBaseSupportsTask *task); + void mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, guint32 level); @@ -159,5 +163,11 @@ gboolean mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, GUdevDevice *port, guint32 *capabilities); +/* Returns TRUE if the port was previously probed, FALSE if not */ +gboolean mm_plugin_base_get_cached_product_info (MMPluginBase *self, + GUdevDevice *port, + gchar **vendor, + gchar **product); + #endif /* MM_PLUGIN_BASE_H */ |