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