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:
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user