huawei: rework probing and detection

Long ago there were problems where certain Huawei devices would
stop responding on various ports, and sometimes would crash
randomly.  The theory at the time was that touching the secondary
ports made the device angry, thus the plugin simply opened the
ports and listened for unsolicited messages.  But if the device
didn't send any during that 7 second period, MM would not detect
and secondary ports at all.  Plus, it was always a hack.

Instead, the new theory is that the device crashes if unsolicited
messages are enabled (^CURC=1), the secondary port gets touched,
*and* then closed and left for a while.  Fix that by turning
unsolicited messages off at probe time, on when the device is
enabled, and off again when the device is disabled like happens
for other modems.  Thus when MM first detects the modem, it turns
off unsolicited messages and the serial buffer on the secondary
port doesn't fill up and crash the modem.

Second, this allows us to simplify the probing logic quite a bit
so that we can probe all ports we find, but we still wait to probe
the first port so we can turn off unsolicited messages and get
hints about what port is the secondary.
This commit is contained in:
Dan Williams
2011-04-27 10:50:32 -05:00
parent 1cf7a4da44
commit dc89c0a42d
2 changed files with 160 additions and 141 deletions

View File

@@ -687,6 +687,71 @@ handle_status_change (MMAtSerialPort *port,
/*****************************************************************************/ /*****************************************************************************/
static void
do_enable_power_up_done (MMGenericGsm *gsm,
GString *response,
GError *error,
MMCallbackInfo *info)
{
if (!error) {
MMAtSerialPort *primary;
/* Enable unsolicited result codes */
primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY);
g_assert (primary);
mm_at_serial_port_queue_command (primary, "^CURC=1", 5, NULL, NULL);
}
/* Chain up to parent */
MM_GENERIC_GSM_CLASS (mm_modem_huawei_gsm_parent_class)->do_enable_power_up_done (gsm, NULL, error, info);
}
/*****************************************************************************/
typedef struct {
MMModem *modem;
MMModemFn callback;
gpointer user_data;
} DisableInfo;
static void
disable_unsolicited_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMModem *parent_modem_iface;
DisableInfo *info = user_data;
parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (info->modem));
parent_modem_iface->disable (info->modem, info->callback, info->user_data);
g_free (info);
}
static void
disable (MMModem *modem,
MMModemFn callback,
gpointer user_data)
{
MMAtSerialPort *primary;
DisableInfo *info;
info = g_malloc0 (sizeof (DisableInfo));
info->callback = callback;
info->user_data = user_data;
info->modem = modem;
primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY);
g_assert (primary);
/* Turn off unsolicited responses */
mm_at_serial_port_queue_command (primary, "^CURC=0", 5, disable_unsolicited_done, info);
}
/*****************************************************************************/
static gboolean static gboolean
grab_port (MMModem *modem, grab_port (MMModem *modem,
const char *subsys, const char *subsys,
@@ -766,6 +831,7 @@ static void
modem_init (MMModem *modem_class) modem_init (MMModem *modem_class)
{ {
modem_class->grab_port = grab_port; modem_class->grab_port = grab_port;
modem_class->disable = disable;
} }
static void static void
@@ -798,5 +864,6 @@ mm_modem_huawei_gsm_class_init (MMModemHuaweiGsmClass *klass)
gsm_class->set_allowed_mode = set_allowed_mode; gsm_class->set_allowed_mode = set_allowed_mode;
gsm_class->get_allowed_mode = get_allowed_mode; gsm_class->get_allowed_mode = get_allowed_mode;
gsm_class->get_access_technology = get_access_technology; gsm_class->get_access_technology = get_access_technology;
gsm_class->do_enable_power_up_done = do_enable_power_up_done;
} }

View File

@@ -15,6 +15,7 @@
*/ */
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <gmodule.h> #include <gmodule.h>
#define G_UDEV_API_IS_SUBJECT_TO_CHANGE #define G_UDEV_API_IS_SUBJECT_TO_CHANGE
@@ -28,6 +29,7 @@
#include "mm-serial-parsers.h" #include "mm-serial-parsers.h"
#include "mm-at-serial-port.h" #include "mm-at-serial-port.h"
#include "mm-log.h" #include "mm-log.h"
#include "mm-errors.h"
G_DEFINE_TYPE (MMPluginHuawei, mm_plugin_huawei, MM_TYPE_PLUGIN_BASE) G_DEFINE_TYPE (MMPluginHuawei, mm_plugin_huawei, MM_TYPE_PLUGIN_BASE)
@@ -44,6 +46,8 @@ mm_plugin_create (void)
/*****************************************************************************/ /*****************************************************************************/
#define TAG_HUAWEI_PCUI_PORT "huawei-pcui-port"
#define CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \ #define CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \
MM_PLUGIN_BASE_PORT_CAP_IS707_P | \ MM_PLUGIN_BASE_PORT_CAP_IS707_P | \
MM_PLUGIN_BASE_PORT_CAP_IS856 | \ MM_PLUGIN_BASE_PORT_CAP_IS856 | \
@@ -70,93 +74,57 @@ probe_result (MMPluginBase *base,
mm_plugin_base_supports_task_complete (task, get_level_for_capabilities (capabilities)); mm_plugin_base_supports_task_complete (task, get_level_for_capabilities (capabilities));
} }
#define TAG_SUPPORTS_INFO "huawei-supports-info" static gboolean
getportmode_response_cb (MMPluginBaseSupportsTask *task,
typedef struct { GString *response,
MMAtSerialPort *serial; GError *error,
guint id; guint32 tries,
MMPortType ptype; gboolean *out_fail,
/* Whether or not there's already a detected modem that "owns" this port, guint32 *out_level,
* in which case we'll claim it, but if no capabilities are detected it'll gpointer user_data)
* just be ignored. {
/* If any error occurred that was not ERROR or COMMAND NOT SUPPORT then
* retry the command.
*/ */
gboolean parent_modem; if (error) {
} HuaweiSupportsInfo; if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_UNKNOWN) == FALSE)
return tries <= 4 ? TRUE : FALSE;
} else {
MMPlugin *plugin;
char *p;
int i = 0;
static void /* Get the USB interface number of the PCUI port */
huawei_supports_info_destroy (gpointer user_data) p = strstr (response->str, "PCUI:");
{ if (p)
HuaweiSupportsInfo *info = user_data; i = atoi (p + strlen ("PCUI:"));
if (info->id) if (i) {
g_source_remove (info->id); /* Save they PCUI port number for later */
if (info->serial) plugin = mm_plugin_base_supports_task_get_plugin (task);
g_object_unref (info->serial); g_assert (plugin);
memset (info, 0, sizeof (HuaweiSupportsInfo)); g_object_set_data (G_OBJECT (plugin), TAG_HUAWEI_PCUI_PORT, GINT_TO_POINTER (i));
g_free (info); }
}
static gboolean
probe_secondary_supported (gpointer user_data)
{
MMPluginBaseSupportsTask *task = user_data;
HuaweiSupportsInfo *info;
info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO);
info->id = 0;
g_object_unref (info->serial);
info->serial = NULL;
/* Yay, supported, we got an unsolicited message */
info->ptype = MM_PORT_TYPE_SECONDARY;
mm_plugin_base_supports_task_complete (task, 10);
return FALSE;
}
static void
probe_secondary_handle_msg (MMAtSerialPort *port,
GMatchInfo *match_info,
gpointer user_data)
{
MMPluginBaseSupportsTask *task = user_data;
HuaweiSupportsInfo *info;
info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO);
g_source_remove (info->id);
info->id = g_idle_add (probe_secondary_supported, task);
}
static gboolean
probe_secondary_timeout (gpointer user_data)
{
MMPluginBaseSupportsTask *task = user_data;
HuaweiSupportsInfo *info;
guint level = 0;
info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO);
info->id = 0;
g_object_unref (info->serial);
info->serial = NULL;
/* Supported, but ignored if this port's parent device is already a modem */
if (info->parent_modem) {
info->ptype = MM_PORT_TYPE_IGNORED;
level = 10;
} }
mm_plugin_base_supports_task_complete (task, level); /* No error or if ^GETPORTMODE is not supported, assume success */
return FALSE; return FALSE;
} }
static void static gboolean
add_regex (MMAtSerialPort *port, const char *match, gpointer user_data) curc_response_cb (MMPluginBaseSupportsTask *task,
GString *response,
GError *error,
guint32 tries,
gboolean *out_fail,
guint32 *out_level,
gpointer user_data)
{ {
GRegex *regex; if (error)
return tries <= 4 ? TRUE : FALSE;
regex = g_regex_new (match, G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); /* No error, assume success */
mm_at_serial_port_add_unsolicited_msg_handler (port, regex, probe_secondary_handle_msg, user_data, NULL); return FALSE;
g_regex_unref (regex);
} }
static MMPluginSupportsResult static MMPluginSupportsResult
@@ -169,7 +137,6 @@ supports_port (MMPluginBase *base,
const char *subsys, *name; const char *subsys, *name;
int usbif; int usbif;
guint16 vendor = 0, product = 0; guint16 vendor = 0, product = 0;
guint32 existing_type = MM_MODEM_TYPE_UNKNOWN;
/* Can't do anything with non-serial ports */ /* Can't do anything with non-serial ports */
port = mm_plugin_base_supports_task_get_port (task); port = mm_plugin_base_supports_task_get_port (task);
@@ -189,73 +156,49 @@ supports_port (MMPluginBase *base,
if (usbif < 0) if (usbif < 0)
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
/* The secondary ports don't necessarily respond correctly to probing, so /* The primary port (called the "modem" port in the Windows drivers) is
* we need to use the first port that does respond to probing to create the * always USB interface 0, and we need to detect that interface first for
* right type of mode (GSM or CDMA), and then re-check the other interfaces. * 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.
*/ */
if (!existing && usbif != 0) if (!existing && usbif != 0)
return MM_PLUGIN_SUPPORTS_PORT_DEFER; return MM_PLUGIN_SUPPORTS_PORT_DEFER;
/* CDMA devices don't have problems with the secondary ports, so after if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) {
* ensuring we have a device by probing the first port, probe the secondary level = get_level_for_capabilities (cached);
* ports on CDMA devices too. if (level) {
*/ mm_plugin_base_supports_task_complete (task, level);
if (existing)
g_object_get (G_OBJECT (existing), MM_MODEM_TYPE, &existing_type, NULL);
if (usbif == 0 || (existing_type == MM_MODEM_TYPE_CDMA)) {
if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) {
level = get_level_for_capabilities (cached);
if (level) {
mm_plugin_base_supports_task_complete (task, level);
return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
}
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
}
/* Otherwise kick off a probe */
if (mm_plugin_base_probe_port (base, task, 100000, NULL))
return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS; return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
} else {
HuaweiSupportsInfo *info;
GError *error = NULL;
/* Listen for Huawei-specific unsolicited messages */
info = g_malloc0 (sizeof (HuaweiSupportsInfo));
info->parent_modem = !!existing;
info->serial = mm_at_serial_port_new (name, MM_PORT_TYPE_PRIMARY);
g_object_set (G_OBJECT (info->serial), MM_PORT_CARRIER_DETECT, FALSE, NULL);
mm_at_serial_port_set_response_parser (info->serial,
mm_serial_parser_v1_parse,
mm_serial_parser_v1_new (),
mm_serial_parser_v1_destroy);
add_regex (info->serial, "\\r\\n\\^RSSI:(\\d+)\\r\\n", task);
add_regex (info->serial, "\\r\\n\\^MODE:(\\d),(\\d)\\r\\n", task);
add_regex (info->serial, "\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", task);
add_regex (info->serial, "\\r\\n\\^BOOT:.+\\r\\n", task);
add_regex (info->serial, "\\r\\r\\^BOOT:.+\\r\\r", task);
info->id = g_timeout_add_seconds (7, probe_secondary_timeout, task);
if (!mm_serial_port_open (MM_SERIAL_PORT (info->serial), &error)) {
mm_warn ("(Huawei) %s: couldn't open serial port: (%d) %s",
name,
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
huawei_supports_info_destroy (info);
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
} }
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
g_object_set_data_full (G_OBJECT (task), TAG_SUPPORTS_INFO,
info, huawei_supports_info_destroy);
return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
} }
/* Turn off unsolicited messages on secondary ports until needed,
* and try to get a port map from the modem. The response will
* get handled in custom_init_response().
*/
if (usbif == 0) {
mm_plugin_base_supports_task_add_custom_init_command (task,
"AT^CURC=0",
3, /* delay */
curc_response_cb,
NULL);
mm_plugin_base_supports_task_add_custom_init_command (task,
"AT^GETPORTMODE",
3, /* delay */
getportmode_response_cb,
NULL);
}
/* Kick off a probe */
if (mm_plugin_base_probe_port (base, task, 100000, NULL))
return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
} }
@@ -314,12 +257,21 @@ grab_port (MMPluginBase *base,
} }
} }
} else { } else {
HuaweiSupportsInfo *info;
MMPortType ptype = MM_PORT_TYPE_UNKNOWN; MMPortType ptype = MM_PORT_TYPE_UNKNOWN;
int pcui_usbif, port_usbif;
info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO); /* Any additional AT ports can be secondary ports, but we want to ensure
if (info) * that the "pcui" port found from ^GETPORTMODE above is always set as
ptype = info->ptype; * a secondary port too.
*/
port_usbif = g_udev_device_get_property_as_int (port, "ID_USB_INTERFACE_NUM");
pcui_usbif = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (base), TAG_HUAWEI_PCUI_PORT));
if ( (port_usbif == pcui_usbif)
|| (caps & MM_PLUGIN_BASE_PORT_CAP_GSM)
|| (caps & CAP_CDMA))
ptype = MM_PORT_TYPE_SECONDARY;
else if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) else if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM)
ptype = MM_PORT_TYPE_QCDM; ptype = MM_PORT_TYPE_QCDM;