aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@lanedo.com>2012-07-11 10:21:28 +0200
committerAleksander Morgado <aleksander@lanedo.com>2012-08-06 20:07:22 +0200
commitefe2228515bcc702ac1f510b91629288d8876ed7 (patch)
treedd68d5d7ddd0528ab43eee2398944f64168f4027 /plugins
parentf9a0aba08c928f3cfb45ba7d609c5446687196ed (diff)
huawei: try to gather port layout while probing
We will try to use usbif0 to gather the port layout with AT^GETPORTMODE.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/huawei/mm-plugin-huawei.c403
1 files changed, 401 insertions, 2 deletions
diff --git a/plugins/huawei/mm-plugin-huawei.c b/plugins/huawei/mm-plugin-huawei.c
index f9640f18..a224a119 100644
--- a/plugins/huawei/mm-plugin-huawei.c
+++ b/plugins/huawei/mm-plugin-huawei.c
@@ -15,11 +15,14 @@
* Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
*/
-#include <string.h>
#include <gmodule.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
#include <libmm-common.h>
+#include "mm-serial-enums-types.h"
#include "mm-log.h"
#include "mm-plugin-huawei.h"
#include "mm-broadband-modem.h"
@@ -30,6 +33,384 @@ int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
/*****************************************************************************/
+/* Custom init */
+
+#define TAG_FIRST_INTERFACE_CONTEXT "first-interface-context"
+
+/* Maximum time to wait for the first interface 0 to appear and get probed.
+ * If it doesn't appear in this time, we'll decide which will be considered the
+ * first interface. */
+#define MAX_WAIT_TIME 5
+
+typedef struct {
+ guint first_usbif;
+ guint timeout_id;
+ gboolean custom_init_run;
+} FirstInterfaceContext;
+
+static void
+first_interface_context_free (FirstInterfaceContext *ctx)
+{
+ if (ctx->timeout_id)
+ g_source_remove (ctx->timeout_id);
+ g_slice_free (FirstInterfaceContext, ctx);
+}
+
+#define TAG_HUAWEI_PCUI_PORT "huawei-pcui-port"
+#define TAG_HUAWEI_MODEM_PORT "huawei-modem-port"
+#define TAG_HUAWEI_DIAG_PORT "huawei-diag-port"
+#define TAG_GETPORTMODE_SUPPORTED "getportmode-supported"
+#define TAG_AT_PORT_FLAGS "at-port-flags"
+
+typedef struct {
+ MMPortProbe *probe;
+ MMAtSerialPort *port;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+ gboolean curc_done;
+ guint curc_retries;
+ gboolean getportmode_done;
+ guint getportmode_retries;
+} HuaweiCustomInitContext;
+
+static void
+huawei_custom_init_context_complete_and_free (HuaweiCustomInitContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+
+ if (ctx->cancellable)
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->port);
+ g_object_unref (ctx->probe);
+ g_object_unref (ctx->result);
+ g_slice_free (HuaweiCustomInitContext, ctx);
+}
+
+static gboolean
+huawei_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void huawei_custom_init_step (HuaweiCustomInitContext *ctx);
+
+static void
+cache_port_mode (MMPortProbe *probe,
+ const gchar *reply,
+ const gchar *type,
+ const gchar *tag)
+{
+ gchar *p;
+ glong i;
+
+ /* Get the USB interface number of the PCUI port */
+ p = strstr (reply, type);
+ if (p) {
+ errno = 0;
+ /* shift by 1 so NULL return from g_object_get_data() means no tag */
+ i = 1 + strtol (p + strlen (type), NULL, 10);
+ if (i > 0 && i < 256 && errno == 0)
+ g_object_set_data (G_OBJECT (probe), tag, GINT_TO_POINTER ((gint) i));
+ }
+}
+
+static void
+getportmode_ready (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ HuaweiCustomInitContext *ctx)
+{
+ if (error) {
+ mm_dbg ("(Huawei) couldn't get port mode: '%s'",
+ error->message);
+
+ /* If any error occurred that was not ERROR or COMMAND NOT SUPPORT then
+ * retry the command.
+ */
+ if (!g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
+ /* Retry */
+ huawei_custom_init_step (ctx);
+ return;
+ }
+
+ /* Port mode not supported */
+ } else {
+ mm_dbg ("(Huawei) port mode layout retrieved");
+
+ cache_port_mode (ctx->probe, response->str, "PCUI:", TAG_HUAWEI_PCUI_PORT);
+ cache_port_mode (ctx->probe, response->str, "MDM:", TAG_HUAWEI_MODEM_PORT);
+ cache_port_mode (ctx->probe, response->str, "DIAG:", TAG_HUAWEI_DIAG_PORT);
+ g_object_set_data (G_OBJECT (ctx->probe), TAG_GETPORTMODE_SUPPORTED, GUINT_TO_POINTER (TRUE));
+ }
+
+ ctx->getportmode_done = TRUE;
+ huawei_custom_init_step (ctx);
+}
+
+static void
+curc_ready (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ HuaweiCustomInitContext *ctx)
+{
+ if (error) {
+ /* Retry if we get a timeout error */
+ if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ huawei_custom_init_step (ctx);
+ return;
+ }
+
+ mm_dbg ("(Huawei) couldn't turn off unsolicited messages in"
+ "secondary ports: '%s'",
+ error->message);
+ }
+
+ mm_dbg ("(Huawei) unsolicited messages in secondary ports turned off");
+
+ ctx->curc_done = TRUE;
+ huawei_custom_init_step (ctx);
+}
+
+static void
+huawei_custom_init_step (HuaweiCustomInitContext *ctx)
+{
+ FirstInterfaceContext *fi_ctx;
+
+ /* If cancelled, end */
+ if (g_cancellable_is_cancelled (ctx->cancellable)) {
+ mm_dbg ("(Huawei) no need to keep on running custom init in (%s)",
+ mm_port_get_device (MM_PORT (ctx->port)));
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Custom initialization cancelled");
+ huawei_custom_init_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (!ctx->curc_done) {
+ if (ctx->curc_retries == 0) {
+ /* All retries consumed, probably not an AT port */
+ mm_port_probe_set_result_at (ctx->probe, FALSE);
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ huawei_custom_init_context_complete_and_free (ctx);
+ return;
+ }
+
+ ctx->curc_retries--;
+ /* Turn off unsolicited messages on secondary ports until needed */
+ mm_at_serial_port_queue_command (
+ ctx->port,
+ "AT^CURC=0",
+ 3,
+ ctx->cancellable,
+ (MMAtSerialResponseFn)curc_ready,
+ ctx);
+ return;
+ }
+
+ /* Try to get a port map from the modem */
+ if (!ctx->getportmode_done) {
+ if (ctx->getportmode_retries == 0) {
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ huawei_custom_init_context_complete_and_free (ctx);
+ return;
+ }
+
+ ctx->getportmode_retries--;
+ mm_at_serial_port_queue_command (
+ ctx->port,
+ "AT^GETPORTMODE",
+ 3,
+ ctx->cancellable,
+ (MMAtSerialResponseFn)getportmode_ready,
+ ctx);
+ return;
+ }
+
+ /* All done it seems */
+ fi_ctx = g_object_get_data (G_OBJECT (mm_port_probe_peek_device (ctx->probe)), TAG_FIRST_INTERFACE_CONTEXT);
+ g_assert (fi_ctx != NULL);
+ fi_ctx->custom_init_run = TRUE;
+
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ huawei_custom_init_context_complete_and_free (ctx);
+}
+
+static gboolean
+first_interface_missing_timeout_cb (MMDevice *device)
+{
+ FirstInterfaceContext *fi_ctx;
+ GList *l;
+ gint closest;
+
+ fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT);
+ g_assert (fi_ctx != NULL);
+
+ /* First interface didn't appear, look for the next closest one among the list of
+ * interfaces in the device, and enable that one as being first */
+ closest = G_MAXINT;
+ for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) {
+ gint usbif;
+
+ usbif = g_udev_device_get_property_as_int (mm_port_probe_peek_port (MM_PORT_PROBE (l->data)), "ID_USB_INTERFACE_NUM");
+ if (usbif == fi_ctx->first_usbif) {
+ g_warn_if_reached ();
+ } else if (usbif > fi_ctx->first_usbif &&
+ usbif < closest) {
+ closest = usbif;
+ }
+ }
+
+ if (closest == G_MAXINT) {
+ /* Retry with interface 0... */
+ closest = 0;
+ }
+
+ mm_dbg ("(Huawei) Couldn't find interface '%d' to start probing, will try with interface '%d' first instead",
+ fi_ctx->first_usbif, closest);
+
+ fi_ctx->first_usbif = closest;
+
+ /* Reload the timeout, just in case we end up not having the next interface to probe...
+ * which is anyway very unlikely as we got it by looking at the real probe list, but anyway... */
+ return TRUE;
+}
+
+static void
+huawei_custom_init (MMPortProbe *probe,
+ MMAtSerialPort *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMDevice *device;
+ FirstInterfaceContext *fi_ctx;
+ HuaweiCustomInitContext *ctx;
+
+ device = mm_port_probe_peek_device (probe);
+
+ /* The primary port (called the "modem" port in the Windows drivers) is
+ * always USB interface 0, and we need to detect that interface first for
+ * two reasons: (1) to disable unsolicited messages on other ports that
+ * may fill up the buffer and crash the device, and (2) to attempt to get
+ * the port layout for hints about what the secondary port is (called the
+ * "pcui" port in Windows). Thus we probe USB interface 0 first and defer
+ * probing other interfaces until we've got if0, at which point we allow
+ * the other ports to be probed too.
+ */
+ fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT);
+ if (!fi_ctx) {
+ /* This is the first time we ask for the context. Set it up. */
+ fi_ctx = g_slice_new0 (FirstInterfaceContext);
+ g_object_set_data_full (G_OBJECT (device),
+ TAG_FIRST_INTERFACE_CONTEXT,
+ fi_ctx,
+ (GDestroyNotify)first_interface_context_free);
+ /* The timeout is controlled in the data set in 'device', and therefore
+ * it should be safe to assume that the timeout will not get fired after
+ * having disposed 'device' */
+ fi_ctx->timeout_id = g_timeout_add_seconds (MAX_WAIT_TIME,
+ (GSourceFunc)first_interface_missing_timeout_cb,
+ device);
+
+ /* By default, we'll ask the Huawei plugin to start probing usbif 0 */
+ fi_ctx->first_usbif = 0;
+
+ /* Custom init of the Huawei plugin is to be run only in the first
+ * interface. We'll control here whether we did run it already or not. */
+ fi_ctx->custom_init_run = FALSE;
+ }
+
+ ctx = g_slice_new (HuaweiCustomInitContext);
+ ctx->result = g_simple_async_result_new (G_OBJECT (probe),
+ callback,
+ user_data,
+ huawei_custom_init);
+ ctx->probe = g_object_ref (probe);
+ ctx->port = g_object_ref (port);
+ ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ ctx->curc_done = FALSE;
+ ctx->curc_retries = 3;
+ ctx->getportmode_done = FALSE;
+ ctx->getportmode_retries = 3;
+
+ /* Custom init only to be run in the first interface */
+ if (g_udev_device_get_property_as_int (mm_port_probe_peek_port (probe),
+ "ID_USB_INTERFACE_NUM") != fi_ctx->first_usbif) {
+
+ if (fi_ctx->custom_init_run)
+ /* If custom init was run already, we can consider this as successfully run */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ else
+ /* Otherwise, we'll need to defer the probing a bit more */
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_RETRY,
+ "Defer needed");
+
+ huawei_custom_init_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We can run custom init in the first interface! clear the timeout as it is no longer needed */
+ g_source_remove (fi_ctx->timeout_id);
+ fi_ctx->timeout_id = 0;
+
+ huawei_custom_init_step (ctx);
+}
+
+/*****************************************************************************/
+
+static void
+propagate_port_mode_results (GList *probes)
+{
+ GList *l;
+ MMPortProbe *first_probe = NULL;
+
+ /* We first need to check which is the port probe which contains the
+ * port mode results, if any */
+ for (l = probes; l; l = g_list_next (l)) {
+ if (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), TAG_GETPORTMODE_SUPPORTED))) {
+ first_probe = MM_PORT_PROBE (l->data);
+ break;
+ }
+ }
+
+ /* Now we propagate the tags to the specific port probes */
+ for (l = probes; l; l = g_list_next (l)) {
+ MMAtPortFlag at_port_flags = MM_AT_PORT_FLAG_NONE;
+ gint usbif;
+
+ usbif = g_udev_device_get_property_as_int (mm_port_probe_peek_port (MM_PORT_PROBE (l->data)), "ID_USB_INTERFACE_NUM");
+
+ if (first_probe) {
+ if (usbif + 1 == GPOINTER_TO_INT (g_object_get_data (G_OBJECT (first_probe), TAG_HUAWEI_PCUI_PORT)))
+ at_port_flags = MM_AT_PORT_FLAG_PRIMARY;
+ else if (usbif + 1 == GPOINTER_TO_INT (g_object_get_data (G_OBJECT (first_probe), TAG_HUAWEI_MODEM_PORT)))
+ at_port_flags = MM_AT_PORT_FLAG_PPP;
+ } else if (usbif == 0 &&
+ mm_port_probe_is_at (MM_PORT_PROBE (l->data))) {
+ /* If GETPORTMODE is not supported, we assume usbif 0 is the modem port */
+ at_port_flags = MM_AT_PORT_FLAG_PPP;
+
+ /* /\* TODO. */
+ /* * For CDMA modems we assume usbif0 is both primary and PPP, since */
+ /* * they don't have problems with talking on secondary ports. */
+ /* *\/ */
+ /* if (caps & CAP_CDMA) */
+ /* pflags |= MM_AT_PORT_FLAG_PRIMARY; */
+ }
+
+ g_object_set_data (G_OBJECT (l->data), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags));
+ }
+}
static MMBaseModem *
create_modem (MMPlugin *self,
@@ -40,6 +421,8 @@ create_modem (MMPlugin *self,
GList *probes,
GError **error)
{
+ propagate_port_mode_results (probes);
+
return MM_BASE_MODEM (mm_broadband_modem_new (sysfs_path,
driver,
mm_plugin_get_name (self),
@@ -53,11 +436,22 @@ grab_port (MMPlugin *self,
MMPortProbe *probe,
GError **error)
{
+ gchar *str;
+ MMAtPortFlag pflags;
+
+ pflags = (MMAtPortFlag) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS));
+ str = mm_at_port_flag_build_string_from_mask (pflags);
+ mm_dbg ("(%s/%s) Port will have AT flags '%s'",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe),
+ str);
+ g_free (str);
+
return mm_base_modem_grab_port (modem,
mm_port_probe_get_port_subsys (probe),
mm_port_probe_get_port_name (probe),
mm_port_probe_get_port_type (probe),
- MM_AT_PORT_FLAG_NONE,
+ pflags,
error);
}
@@ -68,6 +462,10 @@ mm_plugin_create (void)
{
static const gchar *subsystems[] = { "tty", "net", NULL };
static const guint16 vendor_ids[] = { 0x12d1, 0 };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (huawei_custom_init),
+ .finish = G_CALLBACK (huawei_custom_init_finish),
+ };
return MM_PLUGIN (
g_object_new (MM_TYPE_PLUGIN_HUAWEI,
@@ -75,6 +473,7 @@ mm_plugin_create (void)
MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
NULL));
}