diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2012-07-11 10:21:28 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-08-06 20:07:22 +0200 |
commit | efe2228515bcc702ac1f510b91629288d8876ed7 (patch) | |
tree | dd68d5d7ddd0528ab43eee2398944f64168f4027 /plugins | |
parent | f9a0aba08c928f3cfb45ba7d609c5446687196ed (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.c | 403 |
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)); } |