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
grab_port (MMModem *modem,
const char *subsys,
@@ -766,6 +831,7 @@ static void
modem_init (MMModem *modem_class)
{
modem_class->grab_port = grab_port;
modem_class->disable = disable;
}
static void
@@ -798,5 +864,6 @@ mm_modem_huawei_gsm_class_init (MMModemHuaweiGsmClass *klass)
gsm_class->set_allowed_mode = set_allowed_mode;
gsm_class->get_allowed_mode = get_allowed_mode;
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 <stdlib.h>
#include <gmodule.h>
#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
@@ -28,6 +29,7 @@
#include "mm-serial-parsers.h"
#include "mm-at-serial-port.h"
#include "mm-log.h"
#include "mm-errors.h"
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 | \
MM_PLUGIN_BASE_PORT_CAP_IS707_P | \
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));
}
#define TAG_SUPPORTS_INFO "huawei-supports-info"
typedef struct {
MMAtSerialPort *serial;
guint id;
MMPortType ptype;
/* Whether or not there's already a detected modem that "owns" this port,
* in which case we'll claim it, but if no capabilities are detected it'll
* just be ignored.
static gboolean
getportmode_response_cb (MMPluginBaseSupportsTask *task,
GString *response,
GError *error,
guint32 tries,
gboolean *out_fail,
guint32 *out_level,
gpointer user_data)
{
/* If any error occurred that was not ERROR or COMMAND NOT SUPPORT then
* retry the command.
*/
gboolean parent_modem;
} HuaweiSupportsInfo;
if (error) {
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
huawei_supports_info_destroy (gpointer user_data)
{
HuaweiSupportsInfo *info = user_data;
/* Get the USB interface number of the PCUI port */
p = strstr (response->str, "PCUI:");
if (p)
i = atoi (p + strlen ("PCUI:"));
if (info->id)
g_source_remove (info->id);
if (info->serial)
g_object_unref (info->serial);
memset (info, 0, sizeof (HuaweiSupportsInfo));
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;
if (i) {
/* Save they PCUI port number for later */
plugin = mm_plugin_base_supports_task_get_plugin (task);
g_assert (plugin);
g_object_set_data (G_OBJECT (plugin), TAG_HUAWEI_PCUI_PORT, GINT_TO_POINTER (i));
}
}
mm_plugin_base_supports_task_complete (task, level);
/* No error or if ^GETPORTMODE is not supported, assume success */
return FALSE;
}
static void
add_regex (MMAtSerialPort *port, const char *match, gpointer user_data)
static gboolean
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);
mm_at_serial_port_add_unsolicited_msg_handler (port, regex, probe_secondary_handle_msg, user_data, NULL);
g_regex_unref (regex);
/* No error, assume success */
return FALSE;
}
static MMPluginSupportsResult
@@ -169,7 +137,6 @@ supports_port (MMPluginBase *base,
const char *subsys, *name;
int usbif;
guint16 vendor = 0, product = 0;
guint32 existing_type = MM_MODEM_TYPE_UNKNOWN;
/* Can't do anything with non-serial ports */
port = mm_plugin_base_supports_task_get_port (task);
@@ -189,73 +156,49 @@ supports_port (MMPluginBase *base,
if (usbif < 0)
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
/* The secondary ports don't necessarily respond correctly to probing, so
* we need to use the first port that does respond to probing to create the
* right type of mode (GSM or CDMA), and then re-check the other interfaces.
/* 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.
*/
if (!existing && usbif != 0)
return MM_PLUGIN_SUPPORTS_PORT_DEFER;
/* CDMA devices don't have problems with the secondary ports, so after
* ensuring we have a device by probing the first port, probe the secondary
* ports on CDMA devices too.
*/
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))
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;
} 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;
}
g_object_set_data_full (G_OBJECT (task), TAG_SUPPORTS_INFO,
info, huawei_supports_info_destroy);
return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
}
/* 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;
}
@@ -314,12 +257,21 @@ grab_port (MMPluginBase *base,
}
}
} else {
HuaweiSupportsInfo *info;
MMPortType ptype = MM_PORT_TYPE_UNKNOWN;
int pcui_usbif, port_usbif;
info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO);
if (info)
ptype = info->ptype;
/* Any additional AT ports can be secondary ports, but we want to ensure
* that the "pcui" port found from ^GETPORTMODE above is always set as
* 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)
ptype = MM_PORT_TYPE_QCDM;