Make sure we cleanup the 'buffer-full' signal handler when moving from AT probing to QCDM probing, so that we don't try to remove a signal handler from a QCDM port in which we haven't set it. E.g. avoids: ModemManager[1493]: <debug> [1390731359.108502] [mm-port-probe.c:239] mm_port_probe_set_result_qcdm(): (tty/ttyHS2) port is QCDM-capable (ModemManager:1493): GLib-GObject-WARNING **: gsignal.c:2585: instance '0x7431b0' has no handler with id '16'
1756 lines
54 KiB
C
1756 lines
54 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details:
|
|
*
|
|
* Copyright (C) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2011 Red Hat, Inc.
|
|
* Copyright (C) 2011 Aleksander Morgado <aleksander@gnu.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <mm-errors-types.h>
|
|
|
|
#include "mm-port-probe.h"
|
|
#include "mm-log.h"
|
|
#include "mm-at-serial-port.h"
|
|
#include "mm-serial-port.h"
|
|
#include "mm-serial-parsers.h"
|
|
#include "mm-port-probe-at.h"
|
|
#include "libqcdm/src/commands.h"
|
|
#include "libqcdm/src/utils.h"
|
|
#include "libqcdm/src/errors.h"
|
|
#include "mm-qcdm-serial-port.h"
|
|
#include "mm-daemon-enums-types.h"
|
|
|
|
#if defined WITH_QMI
|
|
#include "mm-qmi-port.h"
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
#include "mm-mbim-port.h"
|
|
#endif
|
|
|
|
/*
|
|
* Steps and flow of the Probing process:
|
|
* ----> AT Serial Open
|
|
* |----> Custom Init
|
|
* |----> AT?
|
|
* |----> Vendor
|
|
* |----> Product
|
|
* |----> Is Icera?
|
|
* ----> QCDM Serial Open
|
|
* |----> QCDM?
|
|
* ----> QMI Device Open
|
|
* |----> QMI Version Info check
|
|
* ----> MBIM Device Open
|
|
* |----> MBIM capabilities check
|
|
*/
|
|
|
|
G_DEFINE_TYPE (MMPortProbe, mm_port_probe, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_DEVICE,
|
|
PROP_PORT,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
typedef struct {
|
|
/* ---- Generic task context ---- */
|
|
|
|
GSimpleAsyncResult *result;
|
|
GCancellable *cancellable;
|
|
guint32 flags;
|
|
guint source_id;
|
|
|
|
/* ---- Serial probing specific context ---- */
|
|
|
|
guint buffer_full_id;
|
|
MMSerialPort *serial;
|
|
|
|
/* ---- AT probing specific context ---- */
|
|
|
|
GCancellable *at_probing_cancellable;
|
|
/* Send delay for AT commands */
|
|
guint64 at_send_delay;
|
|
/* Flag to leave/remove echo in AT responses */
|
|
gboolean at_remove_echo;
|
|
/* Flag to send line-feed at the end of AT commands */
|
|
gboolean at_send_lf;
|
|
/* Number of times we tried to open the AT port */
|
|
guint at_open_tries;
|
|
/* Custom initialization setup */
|
|
gboolean at_custom_init_run;
|
|
MMPortProbeAtCustomInit at_custom_init;
|
|
MMPortProbeAtCustomInitFinish at_custom_init_finish;
|
|
/* Custom commands to look for AT support */
|
|
const MMPortProbeAtCommand *at_custom_probe;
|
|
/* Current group of AT commands to be sent */
|
|
const MMPortProbeAtCommand *at_commands;
|
|
/* Current AT Result processor */
|
|
void (* at_result_processor) (MMPortProbe *self,
|
|
GVariant *result);
|
|
|
|
#if defined WITH_QMI
|
|
/* ---- QMI probing specific context ---- */
|
|
MMQmiPort *qmi_port;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
/* ---- MBIM probing specific context ---- */
|
|
MMMbimPort *mbim_port;
|
|
#endif
|
|
|
|
} PortProbeRunTask;
|
|
|
|
struct _MMPortProbePrivate {
|
|
/* Properties */
|
|
MMDevice *device;
|
|
GUdevDevice *port;
|
|
|
|
/* Probing results */
|
|
guint32 flags;
|
|
gboolean is_at;
|
|
gboolean is_qcdm;
|
|
gchar *vendor;
|
|
gchar *product;
|
|
gboolean is_icera;
|
|
gboolean is_qmi;
|
|
gboolean is_mbim;
|
|
|
|
/* Current probing task. Only one can be available at a time */
|
|
PortProbeRunTask *task;
|
|
};
|
|
|
|
void
|
|
mm_port_probe_set_result_at (MMPortProbe *self,
|
|
gboolean at)
|
|
{
|
|
self->priv->is_at = at;
|
|
self->priv->flags |= MM_PORT_PROBE_AT;
|
|
|
|
if (self->priv->is_at) {
|
|
mm_dbg ("(%s/%s) port is AT-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* Also set as not a QCDM/QMI/MBIM port */
|
|
self->priv->is_qcdm = FALSE;
|
|
self->priv->is_qmi = FALSE;
|
|
self->priv->is_mbim = FALSE;
|
|
self->priv->flags |= (MM_PORT_PROBE_QCDM | MM_PORT_PROBE_QMI | MM_PORT_PROBE_MBIM);
|
|
} else {
|
|
mm_dbg ("(%s/%s) port is not AT-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->is_icera = FALSE;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT_VENDOR |
|
|
MM_PORT_PROBE_AT_PRODUCT |
|
|
MM_PORT_PROBE_AT_ICERA);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_at_vendor (MMPortProbe *self,
|
|
const gchar *at_vendor)
|
|
{
|
|
if (at_vendor) {
|
|
mm_dbg ("(%s/%s) vendor probing finished",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->vendor = g_utf8_casefold (at_vendor, -1);
|
|
self->priv->flags |= MM_PORT_PROBE_AT_VENDOR;
|
|
} else {
|
|
mm_dbg ("(%s/%s) couldn't probe for vendor string",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT_VENDOR | MM_PORT_PROBE_AT_PRODUCT);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_at_product (MMPortProbe *self,
|
|
const gchar *at_product)
|
|
{
|
|
if (at_product) {
|
|
mm_dbg ("(%s/%s) product probing finished",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->product = g_utf8_casefold (at_product, -1);
|
|
self->priv->flags |= MM_PORT_PROBE_AT_PRODUCT;
|
|
} else {
|
|
mm_dbg ("(%s/%s) couldn't probe for product string",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->product = NULL;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_PRODUCT;
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_at_icera (MMPortProbe *self,
|
|
gboolean is_icera)
|
|
{
|
|
if (is_icera) {
|
|
mm_dbg ("(%s/%s) Modem is Icera-based",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->is_icera = TRUE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_ICERA;
|
|
} else {
|
|
mm_dbg ("(%s/%s) Modem is probably not Icera-based",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
self->priv->is_icera = FALSE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_ICERA;
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_qcdm (MMPortProbe *self,
|
|
gboolean qcdm)
|
|
{
|
|
self->priv->is_qcdm = qcdm;
|
|
self->priv->flags |= MM_PORT_PROBE_QCDM;
|
|
|
|
if (self->priv->is_qcdm) {
|
|
mm_dbg ("(%s/%s) port is QCDM-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* Also set as not an AT/QMI/MBIM port */
|
|
self->priv->is_at = FALSE;
|
|
self->priv->is_qmi = FALSE;
|
|
self->priv->is_mbim = FALSE;
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->is_icera = FALSE;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT |
|
|
MM_PORT_PROBE_AT_VENDOR |
|
|
MM_PORT_PROBE_AT_PRODUCT |
|
|
MM_PORT_PROBE_AT_ICERA |
|
|
MM_PORT_PROBE_QMI |
|
|
MM_PORT_PROBE_MBIM);
|
|
} else
|
|
mm_dbg ("(%s/%s) port is not QCDM-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_qmi (MMPortProbe *self,
|
|
gboolean qmi)
|
|
{
|
|
self->priv->is_qmi = qmi;
|
|
self->priv->flags |= MM_PORT_PROBE_QMI;
|
|
|
|
if (self->priv->is_qmi) {
|
|
mm_dbg ("(%s/%s) port is QMI-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* Also set as not an AT/QCDM/MBIM port */
|
|
self->priv->is_at = FALSE;
|
|
self->priv->is_qcdm = FALSE;
|
|
self->priv->is_mbim = FALSE;
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT |
|
|
MM_PORT_PROBE_AT_VENDOR |
|
|
MM_PORT_PROBE_AT_PRODUCT |
|
|
MM_PORT_PROBE_AT_ICERA |
|
|
MM_PORT_PROBE_QCDM |
|
|
MM_PORT_PROBE_MBIM);
|
|
} else
|
|
mm_dbg ("(%s/%s) port is not QMI-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_mbim (MMPortProbe *self,
|
|
gboolean mbim)
|
|
{
|
|
self->priv->is_mbim = mbim;
|
|
self->priv->flags |= MM_PORT_PROBE_MBIM;
|
|
|
|
if (self->priv->is_mbim) {
|
|
mm_dbg ("(%s/%s) port is MBIM-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* Also set as not an AT/QCDM/QMI port */
|
|
self->priv->is_at = FALSE;
|
|
self->priv->is_qcdm = FALSE;
|
|
self->priv->is_qmi = FALSE;
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT |
|
|
MM_PORT_PROBE_AT_VENDOR |
|
|
MM_PORT_PROBE_AT_PRODUCT |
|
|
MM_PORT_PROBE_AT_ICERA |
|
|
MM_PORT_PROBE_QCDM |
|
|
MM_PORT_PROBE_QMI);
|
|
} else
|
|
mm_dbg ("(%s/%s) port is not MBIM-capable",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
}
|
|
|
|
static gboolean serial_probe_at (MMPortProbe *self);
|
|
static gboolean serial_probe_qcdm (MMPortProbe *self);
|
|
static void serial_probe_schedule (MMPortProbe *self);
|
|
|
|
static void
|
|
port_probe_run_task_free (PortProbeRunTask *task)
|
|
{
|
|
if (task->source_id)
|
|
g_source_remove (task->source_id);
|
|
|
|
if (task->serial) {
|
|
if (task->buffer_full_id) {
|
|
g_warn_if_fail (MM_IS_AT_SERIAL_PORT (task->serial));
|
|
g_signal_handler_disconnect (task->serial, task->buffer_full_id);
|
|
}
|
|
if (mm_serial_port_is_open (task->serial))
|
|
mm_serial_port_close (task->serial);
|
|
g_object_unref (task->serial);
|
|
}
|
|
|
|
#if defined WITH_QMI
|
|
if (task->qmi_port) {
|
|
if (mm_qmi_port_is_open (task->qmi_port))
|
|
mm_qmi_port_close (task->qmi_port);
|
|
g_object_unref (task->qmi_port);
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (task->mbim_port) {
|
|
/* We should have closed it cleanly before */
|
|
g_assert (!mm_mbim_port_is_open (task->mbim_port));
|
|
g_object_unref (task->mbim_port);
|
|
}
|
|
#endif
|
|
|
|
if (task->cancellable)
|
|
g_object_unref (task->cancellable);
|
|
if (task->at_probing_cancellable)
|
|
g_object_unref (task->at_probing_cancellable);
|
|
|
|
g_object_unref (task->result);
|
|
g_free (task);
|
|
}
|
|
|
|
static void
|
|
port_probe_run_task_complete (PortProbeRunTask *task,
|
|
gboolean result,
|
|
GError *error)
|
|
{
|
|
/* As soon as we have the task completed, disable the buffer-full signal
|
|
* handling, so that we do not get unwanted errors reported */
|
|
if (task->serial && task->buffer_full_id) {
|
|
g_signal_handler_disconnect (task->serial, task->buffer_full_id);
|
|
task->buffer_full_id = 0;
|
|
}
|
|
|
|
if (error)
|
|
g_simple_async_result_take_error (task->result, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (task->result, result);
|
|
|
|
/* Always complete in idle */
|
|
g_simple_async_result_complete_in_idle (task->result);
|
|
}
|
|
|
|
static gboolean
|
|
port_probe_run_is_cancelled (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
/* Manually check if cancelled.
|
|
* TODO: Make the serial port response wait cancellable,
|
|
* so that we can connect a callback to the cancellable and forget about
|
|
* manually checking it.
|
|
*/
|
|
if (g_cancellable_is_cancelled (task->cancellable)) {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_CANCELLED,
|
|
"(%s/%s) port probing cancelled",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port)));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* QMI & MBIM */
|
|
|
|
static gboolean wdm_probe (MMPortProbe *self);
|
|
|
|
#if defined WITH_QMI
|
|
|
|
static void
|
|
qmi_port_open_ready (MMQmiPort *qmi_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GError *error = NULL;
|
|
gboolean is_qmi;
|
|
|
|
is_qmi = mm_qmi_port_open_finish (qmi_port, res, &error);
|
|
if (!is_qmi) {
|
|
mm_dbg ("(%s/%s) error checking QMI support: '%s'",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* Set probing result */
|
|
mm_port_probe_set_result_qmi (self, is_qmi);
|
|
|
|
mm_qmi_port_close (qmi_port);
|
|
|
|
/* Keep on */
|
|
task->source_id = g_idle_add ((GSourceFunc)wdm_probe, self);
|
|
}
|
|
|
|
#endif /* WITH_QMI */
|
|
|
|
static void
|
|
wdm_probe_qmi (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
#if defined WITH_QMI
|
|
mm_dbg ("(%s/%s) probing QMI...",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* Create a port and try to open it */
|
|
task->qmi_port = mm_qmi_port_new (g_udev_device_get_name (self->priv->port));
|
|
mm_qmi_port_open (task->qmi_port,
|
|
FALSE,
|
|
NULL,
|
|
(GAsyncReadyCallback)qmi_port_open_ready,
|
|
self);
|
|
#else
|
|
/* If not compiled with QMI support, just assume we won't have any QMI port */
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
task->source_id = g_idle_add ((GSourceFunc)wdm_probe, self);
|
|
#endif /* WITH_QMI */
|
|
}
|
|
|
|
#if defined WITH_MBIM
|
|
|
|
static void
|
|
mbim_port_close_ready (MMMbimPort *mbim_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
mm_mbim_port_close_finish (mbim_port, res, NULL);
|
|
|
|
/* Keep on */
|
|
task->source_id = g_idle_add ((GSourceFunc)wdm_probe, self);
|
|
}
|
|
|
|
static void
|
|
mbim_port_open_ready (MMMbimPort *mbim_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GError *error = NULL;
|
|
gboolean is_mbim;
|
|
|
|
is_mbim = mm_mbim_port_open_finish (mbim_port, res, &error);
|
|
if (!is_mbim) {
|
|
mm_dbg ("(%s/%s) error checking MBIM support: '%s'",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* Set probing result */
|
|
mm_port_probe_set_result_mbim (self, is_mbim);
|
|
|
|
mm_mbim_port_close (task->mbim_port,
|
|
(GAsyncReadyCallback)mbim_port_close_ready,
|
|
self);
|
|
}
|
|
|
|
static gboolean
|
|
mbim_sleep_ready (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
task->source_id = 0;
|
|
|
|
/* Create a port and try to open it */
|
|
task->mbim_port = mm_mbim_port_new (g_udev_device_get_name (self->priv->port));
|
|
mm_mbim_port_open (task->mbim_port,
|
|
NULL,
|
|
(GAsyncReadyCallback)mbim_port_open_ready,
|
|
self);
|
|
return FALSE;
|
|
}
|
|
|
|
#endif /* WITH_MBIM */
|
|
|
|
static void
|
|
wdm_probe_mbim (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
#if defined WITH_MBIM
|
|
mm_dbg ("(%s/%s) probing MBIM...",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
task->source_id = g_timeout_add_seconds (3, (GSourceFunc)mbim_sleep_ready, self);
|
|
#else
|
|
/* If not compiled with MBIM support, just assume we won't have any MBIM port */
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
task->source_id = g_idle_add ((GSourceFunc)wdm_probe, self);
|
|
#endif /* WITH_MBIM */
|
|
}
|
|
|
|
static gboolean
|
|
wdm_probe (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
task->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return FALSE;
|
|
|
|
if ((task->flags & MM_PORT_PROBE_QMI) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_QMI))
|
|
/* QMI probing needed */
|
|
wdm_probe_qmi (self);
|
|
else if ((task->flags & MM_PORT_PROBE_MBIM) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_MBIM))
|
|
/* MBIM probing needed */
|
|
wdm_probe_mbim (self);
|
|
else
|
|
/* All done now */
|
|
port_probe_run_task_complete (task, TRUE, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* QCDM */
|
|
|
|
static void
|
|
serial_probe_qcdm_parse_response (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
MMPortProbe *self)
|
|
{
|
|
QcdmResult *result;
|
|
gint err = QCDM_SUCCESS;
|
|
gboolean is_qcdm = FALSE;
|
|
gboolean retry = FALSE;
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return;
|
|
|
|
if (!error) {
|
|
/* Parse the response */
|
|
result = qcdm_cmd_version_info_result ((const gchar *) response->data,
|
|
response->len,
|
|
&err);
|
|
if (!result) {
|
|
mm_warn ("(%s/%s) failed to parse QCDM version info command result: %d",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
err);
|
|
retry = TRUE;
|
|
} else {
|
|
/* yay, probably a QCDM port */
|
|
is_qcdm = TRUE;
|
|
|
|
qcdm_result_unref (result);
|
|
}
|
|
} else {
|
|
if (!g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT))
|
|
mm_dbg ("QCDM probe error: (%d) %s", error->code, error->message);
|
|
retry = TRUE;
|
|
}
|
|
|
|
if (retry) {
|
|
GByteArray *cmd2;
|
|
|
|
cmd2 = g_object_steal_data (G_OBJECT (self), "cmd2");
|
|
if (cmd2) {
|
|
/* second try */
|
|
mm_qcdm_serial_port_queue_command (MM_QCDM_SERIAL_PORT (task->serial),
|
|
cmd2,
|
|
3,
|
|
NULL,
|
|
(MMQcdmSerialResponseFn)serial_probe_qcdm_parse_response,
|
|
self);
|
|
return;
|
|
}
|
|
|
|
/* no more retries left */
|
|
}
|
|
|
|
/* Set probing result */
|
|
mm_port_probe_set_result_qcdm (self, is_qcdm);
|
|
|
|
/* Reschedule probing */
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
static gboolean
|
|
serial_probe_qcdm (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GError *error = NULL;
|
|
GByteArray *verinfo = NULL;
|
|
GByteArray *verinfo2;
|
|
gint len;
|
|
guint8 marker = 0x7E;
|
|
|
|
task->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return FALSE;
|
|
|
|
mm_dbg ("(%s/%s) probing QCDM...",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
|
|
/* If open, close the AT port */
|
|
if (task->serial) {
|
|
/* Explicitly clear the buffer full signal handler */
|
|
if (task->buffer_full_id) {
|
|
g_signal_handler_disconnect (task->serial, task->buffer_full_id);
|
|
task->buffer_full_id = 0;
|
|
}
|
|
mm_serial_port_close (task->serial);
|
|
g_object_unref (task->serial);
|
|
}
|
|
|
|
/* Open the QCDM port */
|
|
task->serial = MM_SERIAL_PORT (mm_qcdm_serial_port_new (g_udev_device_get_name (self->priv->port)));
|
|
if (!task->serial) {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) Couldn't create QCDM port",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port)));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Try to open the port */
|
|
if (!mm_serial_port_open (task->serial, &error)) {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) Failed to open QCDM port: %s",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
(error ? error->message : "unknown error")));
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Build up the probe command; 0x7E is the frame marker, so put one at the
|
|
* beginning of the buffer to ensure that the device discards any AT
|
|
* commands that probing might have sent earlier. Should help devices
|
|
* respond more quickly and speed up QCDM probing.
|
|
*/
|
|
verinfo = g_byte_array_sized_new (10);
|
|
g_byte_array_append (verinfo, &marker, 1);
|
|
len = qcdm_cmd_version_info_new ((char *) (verinfo->data + 1), 9);
|
|
if (len <= 0) {
|
|
g_byte_array_free (verinfo, TRUE);
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) Failed to create QCDM versin info command",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port)));
|
|
return FALSE;
|
|
}
|
|
verinfo->len = len + 1;
|
|
|
|
/* Queuing the command takes ownership over it; save it for the second try */
|
|
verinfo2 = g_byte_array_sized_new (verinfo->len);
|
|
g_byte_array_append (verinfo2, verinfo->data, verinfo->len);
|
|
g_object_set_data_full (G_OBJECT (self), "cmd2", verinfo2, (GDestroyNotify) g_byte_array_unref);
|
|
|
|
mm_qcdm_serial_port_queue_command (MM_QCDM_SERIAL_PORT (task->serial),
|
|
verinfo,
|
|
3,
|
|
NULL,
|
|
(MMQcdmSerialResponseFn)serial_probe_qcdm_parse_response,
|
|
self);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* AT */
|
|
|
|
static const gchar *non_at_strings[] = {
|
|
/* Option Icera-based devices */
|
|
"option/faema_",
|
|
"os_logids.h",
|
|
/* Sierra CnS port */
|
|
"NETWORK SERVICE CHANGE",
|
|
NULL
|
|
};
|
|
|
|
static const guint8 zerobuf[32] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
|
|
static gboolean
|
|
is_non_at_response (const guint8 *data, gsize len)
|
|
{
|
|
const gchar **iter;
|
|
gsize iter_len;
|
|
gsize i;
|
|
|
|
/* Some devices (observed on a ZTE branded "QUALCOMM INCORPORATED" model
|
|
* "154") spew NULLs from some ports.
|
|
*/
|
|
for (i = 0; (len >= sizeof (zerobuf)) && (i < len - sizeof (zerobuf)); i++) {
|
|
if (!memcmp (&data[i], zerobuf, sizeof (zerobuf)))
|
|
return TRUE;
|
|
}
|
|
|
|
/* Check for a well-known non-AT response. There are some ports (eg many
|
|
* Icera-based chipsets, Qualcomm Gobi devices before their firmware is
|
|
* loaded, Sierra CnS ports) that just shouldn't be probed for AT capability
|
|
* if we get a certain response since that response means they aren't AT
|
|
* ports. Also, kernel bugs (at least with 2.6.31 and 2.6.32) trigger port
|
|
* flow control kernel oopses if we read too much data for these ports.
|
|
*/
|
|
for (iter = &non_at_strings[0]; iter && *iter; iter++) {
|
|
/* Search in the response for the item; the response could have embedded
|
|
* nulls so we can't use memcmp() or strstr() on the whole response.
|
|
*/
|
|
iter_len = strlen (*iter);
|
|
for (i = 0; (len >= iter_len) && (i < len - iter_len); i++) {
|
|
if (!memcmp (&data[i], *iter, iter_len))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
serial_probe_at_icera_result_processor (MMPortProbe *self,
|
|
GVariant *result)
|
|
{
|
|
if (result) {
|
|
/* If any result given, it must be a string */
|
|
g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE_STRING));
|
|
if (strstr (g_variant_get_string (result, NULL), "%IPSYS:")) {
|
|
mm_port_probe_set_result_at_icera (self, TRUE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mm_port_probe_set_result_at_icera (self, FALSE);
|
|
}
|
|
|
|
static void
|
|
serial_probe_at_product_result_processor (MMPortProbe *self,
|
|
GVariant *result)
|
|
{
|
|
if (result) {
|
|
/* If any result given, it must be a string */
|
|
g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE_STRING));
|
|
mm_port_probe_set_result_at_product (self,
|
|
g_variant_get_string (result, NULL));
|
|
return;
|
|
}
|
|
|
|
mm_port_probe_set_result_at_product (self, NULL);
|
|
}
|
|
|
|
static void
|
|
serial_probe_at_vendor_result_processor (MMPortProbe *self,
|
|
GVariant *result)
|
|
{
|
|
if (result) {
|
|
/* If any result given, it must be a string */
|
|
g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE_STRING));
|
|
mm_port_probe_set_result_at_vendor (self,
|
|
g_variant_get_string (result, NULL));
|
|
return;
|
|
}
|
|
|
|
mm_port_probe_set_result_at_vendor (self, NULL);
|
|
}
|
|
|
|
static void
|
|
serial_probe_at_result_processor (MMPortProbe *self,
|
|
GVariant *result)
|
|
{
|
|
if (result) {
|
|
/* If any result given, it must be a boolean */
|
|
g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE_BOOLEAN));
|
|
|
|
if (g_variant_get_boolean (result)) {
|
|
mm_port_probe_set_result_at (self, TRUE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
}
|
|
|
|
static void
|
|
serial_probe_at_parse_response (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GVariant *result = NULL;
|
|
GError *result_error = NULL;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return;
|
|
|
|
/* If AT probing cancelled, end this partial probing */
|
|
if (g_cancellable_is_cancelled (task->at_probing_cancellable)) {
|
|
mm_dbg ("(%s/%s) no need to keep on probing the port for AT support",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
task->at_result_processor (self, NULL);
|
|
serial_probe_schedule (self);
|
|
return;
|
|
}
|
|
|
|
if (!task->at_commands->response_processor (task->at_commands->command,
|
|
response ? response->str : NULL,
|
|
!!task->at_commands[1].command,
|
|
error,
|
|
&result,
|
|
&result_error)) {
|
|
/* Were we told to abort the whole probing? */
|
|
if (result_error) {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"(%s/%s) error while probing AT features: %s",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
result_error->message));
|
|
g_error_free (result_error);
|
|
return;
|
|
}
|
|
|
|
/* Go on to next command */
|
|
task->at_commands++;
|
|
if (!task->at_commands->command) {
|
|
/* Was it the last command in the group? If so,
|
|
* end this partial probing */
|
|
task->at_result_processor (self, NULL);
|
|
/* Reschedule */
|
|
serial_probe_schedule (self);
|
|
return;
|
|
}
|
|
|
|
/* Schedule the next command in the probing group */
|
|
task->source_id = g_idle_add ((GSourceFunc)serial_probe_at, self);
|
|
return;
|
|
}
|
|
|
|
/* Run result processor.
|
|
* Note that custom init commands are allowed to not return anything */
|
|
task->at_result_processor (self, result);
|
|
if (result)
|
|
g_variant_unref (result);
|
|
|
|
/* Reschedule probing */
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
static gboolean
|
|
serial_probe_at (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
task->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return FALSE;
|
|
|
|
/* If AT probing cancelled, end this partial probing */
|
|
if (g_cancellable_is_cancelled (task->at_probing_cancellable)) {
|
|
mm_dbg ("(%s/%s) no need to launch probing for AT support",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
task->at_result_processor (self, NULL);
|
|
serial_probe_schedule (self);
|
|
return FALSE;
|
|
}
|
|
|
|
mm_at_serial_port_queue_command (
|
|
MM_AT_SERIAL_PORT (task->serial),
|
|
task->at_commands->command,
|
|
task->at_commands->timeout,
|
|
FALSE,
|
|
task->at_probing_cancellable,
|
|
(MMAtSerialResponseFn)serial_probe_at_parse_response,
|
|
self);
|
|
return FALSE;
|
|
}
|
|
|
|
static const MMPortProbeAtCommand at_probing[] = {
|
|
{ "AT", 3, mm_port_probe_response_processor_is_at },
|
|
{ "AT", 3, mm_port_probe_response_processor_is_at },
|
|
{ "AT", 3, mm_port_probe_response_processor_is_at },
|
|
{ NULL }
|
|
};
|
|
|
|
static const MMPortProbeAtCommand vendor_probing[] = {
|
|
{ "+CGMI", 3, mm_port_probe_response_processor_string },
|
|
{ "+GMI", 3, mm_port_probe_response_processor_string },
|
|
{ "I", 3, mm_port_probe_response_processor_string },
|
|
{ NULL }
|
|
};
|
|
|
|
static const MMPortProbeAtCommand product_probing[] = {
|
|
{ "+CGMM", 3, mm_port_probe_response_processor_string },
|
|
{ "+GMM", 3, mm_port_probe_response_processor_string },
|
|
{ "I", 3, mm_port_probe_response_processor_string },
|
|
{ NULL }
|
|
};
|
|
|
|
static const MMPortProbeAtCommand icera_probing[] = {
|
|
{ "%IPSYS?", 3, mm_port_probe_response_processor_string },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
at_custom_init_ready (MMPortProbe *self,
|
|
GAsyncResult *res)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GError *error = NULL;
|
|
|
|
if (!task->at_custom_init_finish (self, res, &error)) {
|
|
/* All errors propagated up end up forcing an UNSUPPORTED result */
|
|
port_probe_run_task_complete (task, FALSE, error);
|
|
return;
|
|
}
|
|
|
|
/* Keep on with remaining probings */
|
|
task->at_custom_init_run = TRUE;
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
/***************************************************************/
|
|
|
|
static void
|
|
serial_probe_schedule (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return;
|
|
|
|
/* If we got some custom initialization setup requested, go on with it
|
|
* first. */
|
|
if (!task->at_custom_init_run &&
|
|
task->at_custom_init &&
|
|
task->at_custom_init_finish) {
|
|
task->at_custom_init (self,
|
|
MM_AT_SERIAL_PORT (task->serial),
|
|
task->at_probing_cancellable,
|
|
(GAsyncReadyCallback)at_custom_init_ready,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
/* Cleanup */
|
|
task->at_result_processor = NULL;
|
|
task->at_commands = NULL;
|
|
|
|
/* AT check requested and not already probed? */
|
|
if ((task->flags & MM_PORT_PROBE_AT) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT)) {
|
|
/* Prepare AT probing */
|
|
if (task->at_custom_probe)
|
|
task->at_commands = task->at_custom_probe;
|
|
else
|
|
task->at_commands = at_probing;
|
|
task->at_result_processor = serial_probe_at_result_processor;
|
|
}
|
|
/* Vendor requested and not already probed? */
|
|
else if ((task->flags & MM_PORT_PROBE_AT_VENDOR) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_VENDOR)) {
|
|
/* Prepare AT vendor probing */
|
|
task->at_result_processor = serial_probe_at_vendor_result_processor;
|
|
task->at_commands = vendor_probing;
|
|
}
|
|
/* Product requested and not already probed? */
|
|
else if ((task->flags & MM_PORT_PROBE_AT_PRODUCT) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_PRODUCT)) {
|
|
/* Prepare AT product probing */
|
|
task->at_result_processor = serial_probe_at_product_result_processor;
|
|
task->at_commands = product_probing;
|
|
}
|
|
/* Icera support check requested and not already done? */
|
|
else if ((task->flags & MM_PORT_PROBE_AT_ICERA) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_ICERA)) {
|
|
/* Prepare AT product probing */
|
|
task->at_result_processor = serial_probe_at_icera_result_processor;
|
|
task->at_commands = icera_probing;
|
|
}
|
|
|
|
/* If a next AT group detected, go for it */
|
|
if (task->at_result_processor &&
|
|
task->at_commands) {
|
|
task->source_id = g_idle_add ((GSourceFunc)serial_probe_at, self);
|
|
return;
|
|
}
|
|
|
|
/* QCDM requested and not already probed? */
|
|
if ((task->flags & MM_PORT_PROBE_QCDM) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_QCDM)) {
|
|
task->source_id = g_idle_add ((GSourceFunc)serial_probe_qcdm, self);
|
|
return;
|
|
}
|
|
|
|
/* All done! Finish asynchronously */
|
|
port_probe_run_task_complete (task, TRUE, NULL);
|
|
}
|
|
|
|
static void
|
|
serial_flash_done (MMSerialPort *port,
|
|
GError *error,
|
|
MMPortProbe *self)
|
|
{
|
|
/* Schedule probing */
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
static void
|
|
serial_buffer_full (MMSerialPort *serial,
|
|
GByteArray *buffer,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
|
|
if (is_non_at_response (buffer->data, buffer->len)) {
|
|
mm_dbg ("(%s/%s) serial buffer full",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
/* Don't explicitly close the AT port, just end the AT probing
|
|
* (or custom init probing) */
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
g_cancellable_cancel (task->at_probing_cancellable);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
serial_parser_filter_cb (gpointer filter,
|
|
gpointer user_data,
|
|
GString *response,
|
|
GError **error)
|
|
{
|
|
if (is_non_at_response ((const guint8 *) response->str, response->len)) {
|
|
g_set_error (error,
|
|
MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_PARSE_FAILED,
|
|
"Not an AT response");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
serial_open_at (MMPortProbe *self)
|
|
{
|
|
PortProbeRunTask *task = self->priv->task;
|
|
GError *error = NULL;
|
|
|
|
task->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_run_is_cancelled (self))
|
|
return FALSE;
|
|
|
|
/* Create AT serial port if not done before */
|
|
if (!task->serial) {
|
|
gpointer parser;
|
|
|
|
task->serial = MM_SERIAL_PORT (mm_at_serial_port_new (g_udev_device_get_name (self->priv->port)));
|
|
if (!task->serial) {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) couldn't create AT port",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port)));
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_set (task->serial,
|
|
MM_SERIAL_PORT_SEND_DELAY, task->at_send_delay,
|
|
MM_AT_SERIAL_PORT_REMOVE_ECHO, task->at_remove_echo,
|
|
MM_AT_SERIAL_PORT_SEND_LF, task->at_send_lf,
|
|
MM_SERIAL_PORT_SPEW_CONTROL, TRUE,
|
|
NULL);
|
|
|
|
parser = mm_serial_parser_v1_new ();
|
|
mm_serial_parser_v1_add_filter (parser,
|
|
serial_parser_filter_cb,
|
|
NULL);
|
|
mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (task->serial),
|
|
mm_serial_parser_v1_parse,
|
|
parser,
|
|
mm_serial_parser_v1_destroy);
|
|
}
|
|
|
|
/* Try to open the port */
|
|
if (!mm_serial_port_open (task->serial, &error)) {
|
|
/* Abort if maximum number of open tries reached */
|
|
if (++task->at_open_tries > 4) {
|
|
/* took too long to open the port; give up */
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) failed to open port after 4 tries",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port)));
|
|
} else if (g_error_matches (error,
|
|
MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE)) {
|
|
/* this is nozomi being dumb; try again */
|
|
task->source_id = g_timeout_add_seconds (1,
|
|
(GSourceFunc)serial_open_at,
|
|
self);
|
|
} else {
|
|
port_probe_run_task_complete (
|
|
task,
|
|
FALSE,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) failed to open port: %s",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
(error ? error->message : "unknown error")));
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success, start probing */
|
|
task->buffer_full_id = g_signal_connect (task->serial,
|
|
"buffer-full",
|
|
G_CALLBACK (serial_buffer_full),
|
|
self);
|
|
|
|
mm_serial_port_flash (MM_SERIAL_PORT (task->serial),
|
|
100,
|
|
TRUE,
|
|
(MMSerialFlashFn)serial_flash_done,
|
|
self);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_run_cancel_at_probing (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
if (self->priv->task) {
|
|
mm_dbg ("(%s/%s) requested to cancel all AT probing",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
g_cancellable_cancel (self->priv->task->at_probing_cancellable);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_run_cancel (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
if (self->priv->task) {
|
|
mm_dbg ("(%s/%s) requested to cancel the probing",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port));
|
|
g_cancellable_cancel (self->priv->task->cancellable);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_run_finish (MMPortProbe *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
|
|
|
|
/* Propagate error, if any */
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
|
|
res = FALSE;
|
|
else
|
|
res = g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (result));
|
|
|
|
/* Cleanup probing task */
|
|
if (self->priv->task) {
|
|
port_probe_run_task_free (self->priv->task);
|
|
self->priv->task = NULL;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void
|
|
mm_port_probe_run (MMPortProbe *self,
|
|
MMPortProbeFlag flags,
|
|
guint64 at_send_delay,
|
|
gboolean at_remove_echo,
|
|
gboolean at_send_lf,
|
|
const MMPortProbeAtCommand *at_custom_probe,
|
|
const MMAsyncMethod *at_custom_init,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
PortProbeRunTask *task;
|
|
guint32 i;
|
|
gchar *probe_list_str;
|
|
|
|
g_return_if_fail (MM_IS_PORT_PROBE (self));
|
|
g_return_if_fail (flags != MM_PORT_PROBE_NONE);
|
|
g_return_if_fail (callback != NULL);
|
|
|
|
/* Shouldn't schedule more than one probing at a time */
|
|
g_assert (self->priv->task == NULL);
|
|
|
|
task = g_new0 (PortProbeRunTask, 1);
|
|
task->at_send_delay = at_send_delay;
|
|
task->at_remove_echo = at_remove_echo;
|
|
task->at_send_lf = at_send_lf;
|
|
task->flags = MM_PORT_PROBE_NONE;
|
|
task->at_custom_probe = at_custom_probe;
|
|
task->at_custom_init = at_custom_init ? (MMPortProbeAtCustomInit)at_custom_init->async : NULL;
|
|
task->at_custom_init_finish = at_custom_init ? (MMPortProbeAtCustomInitFinish)at_custom_init->finish : NULL;
|
|
|
|
task->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
mm_port_probe_run);
|
|
|
|
/* Check if we already have the requested probing results.
|
|
* We will fix here the 'task->flags' so that we only request probing
|
|
* for the missing things. */
|
|
for (i = MM_PORT_PROBE_AT; i <= MM_PORT_PROBE_MBIM; i = (i << 1)) {
|
|
if ((flags & i) && !(self->priv->flags & i)) {
|
|
task->flags += i;
|
|
}
|
|
}
|
|
|
|
/* Store as current task. We need to keep it internally, as it will be
|
|
* freed during _finish() when the operation is completed. */
|
|
self->priv->task = task;
|
|
|
|
/* All requested probings already available? If so, we're done */
|
|
if (!task->flags) {
|
|
port_probe_run_task_complete (task, TRUE, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Setup internal cancellable */
|
|
task->cancellable = g_cancellable_new ();
|
|
|
|
probe_list_str = mm_port_probe_flag_build_string_from_mask (task->flags);
|
|
mm_dbg ("(%s/%s) launching port probing: '%s'",
|
|
g_udev_device_get_subsystem (self->priv->port),
|
|
g_udev_device_get_name (self->priv->port),
|
|
probe_list_str);
|
|
g_free (probe_list_str);
|
|
|
|
/* If any AT probing is needed, start by opening as AT port */
|
|
if (task->flags & MM_PORT_PROBE_AT ||
|
|
task->flags & MM_PORT_PROBE_AT_VENDOR ||
|
|
task->flags & MM_PORT_PROBE_AT_PRODUCT ||
|
|
task->flags & MM_PORT_PROBE_AT_ICERA) {
|
|
task->at_probing_cancellable = g_cancellable_new ();
|
|
task->source_id = g_idle_add ((GSourceFunc)serial_open_at, self);
|
|
return;
|
|
}
|
|
|
|
/* If QCDM probing needed, start by opening as QCDM port */
|
|
if (task->flags & MM_PORT_PROBE_QCDM) {
|
|
task->source_id = g_idle_add ((GSourceFunc)serial_probe_qcdm, self);
|
|
return;
|
|
}
|
|
|
|
/* If QMI/MBIM probing needed, go on */
|
|
if (task->flags & MM_PORT_PROBE_QMI || task->flags & MM_PORT_PROBE_MBIM) {
|
|
task->source_id = g_idle_add ((GSourceFunc)wdm_probe, self);
|
|
return;
|
|
}
|
|
|
|
/* Shouldn't happen */
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_at (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (g_str_equal (subsys, "net") ||
|
|
(g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm")))
|
|
return FALSE;
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT ?
|
|
self->priv->is_at :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_has_at_port (GList *list)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = list; l; l = g_list_next (l)){
|
|
MMPortProbe *probe = MM_PORT_PROBE (l->data);
|
|
if (probe->priv->flags & MM_PORT_PROBE_AT &&
|
|
probe->priv->is_at)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_qcdm (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (g_str_equal (subsys, "net") ||
|
|
(g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm")))
|
|
return FALSE;
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_QCDM ?
|
|
self->priv->is_qcdm :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_qmi (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (!g_str_has_prefix (subsys, "usb") ||
|
|
!name ||
|
|
!g_str_has_prefix (name, "cdc-wdm"))
|
|
return FALSE;
|
|
|
|
return self->priv->is_qmi;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_has_qmi_port (GList *list)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = list; l; l = g_list_next (l)) {
|
|
if (mm_port_probe_is_qmi (MM_PORT_PROBE (l->data)))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_mbim (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (!g_str_has_prefix (subsys, "usb") ||
|
|
!name ||
|
|
!g_str_has_prefix (name, "cdc-wdm"))
|
|
return FALSE;
|
|
|
|
return self->priv->is_mbim;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_has_mbim_port (GList *list)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = list; l; l = g_list_next (l)) {
|
|
if (mm_port_probe_is_mbim (MM_PORT_PROBE (l->data)))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MMPortType
|
|
mm_port_probe_get_port_type (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
|
|
if (g_str_equal (subsys, "net"))
|
|
return MM_PORT_TYPE_NET;
|
|
|
|
#if defined WITH_QMI
|
|
if (g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm") &&
|
|
self->priv->is_qmi)
|
|
return MM_PORT_TYPE_QMI;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm") &&
|
|
self->priv->is_mbim)
|
|
return MM_PORT_TYPE_MBIM;
|
|
#endif
|
|
|
|
if (self->priv->flags & MM_PORT_PROBE_QCDM &&
|
|
self->priv->is_qcdm)
|
|
return MM_PORT_TYPE_QCDM;
|
|
|
|
if (self->priv->flags & MM_PORT_PROBE_AT &&
|
|
self->priv->is_at)
|
|
return MM_PORT_TYPE_AT;
|
|
|
|
return MM_PORT_TYPE_UNKNOWN;
|
|
}
|
|
|
|
MMDevice *
|
|
mm_port_probe_peek_device (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return self->priv->device;
|
|
}
|
|
|
|
MMDevice *
|
|
mm_port_probe_get_device (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return MM_DEVICE (g_object_ref (self->priv->device));
|
|
}
|
|
|
|
GUdevDevice *
|
|
mm_port_probe_peek_port (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return self->priv->port;
|
|
};
|
|
|
|
GUdevDevice *
|
|
mm_port_probe_get_port (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return G_UDEV_DEVICE (g_object_ref (self->priv->port));
|
|
};
|
|
|
|
const gchar *
|
|
mm_port_probe_get_vendor (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (g_str_equal (subsys, "net") ||
|
|
(g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm")))
|
|
return NULL;
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT_VENDOR ?
|
|
self->priv->vendor :
|
|
NULL);
|
|
}
|
|
|
|
const gchar *
|
|
mm_port_probe_get_product (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = g_udev_device_get_subsystem (self->priv->port);
|
|
name = g_udev_device_get_name (self->priv->port);
|
|
if (g_str_equal (subsys, "net") ||
|
|
(g_str_has_prefix (subsys, "usb") &&
|
|
g_str_has_prefix (name, "cdc-wdm")))
|
|
return NULL;
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT_PRODUCT ?
|
|
self->priv->product :
|
|
NULL);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_icera (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
if (g_str_equal (g_udev_device_get_subsystem (self->priv->port), "net"))
|
|
return FALSE;
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT_ICERA ?
|
|
self->priv->is_icera :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_is_icera (GList *probes)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = probes; l; l = g_list_next (l)) {
|
|
if (mm_port_probe_is_icera (MM_PORT_PROBE (l->data)))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
const gchar *
|
|
mm_port_probe_get_port_name (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return g_udev_device_get_name (self->priv->port);
|
|
}
|
|
|
|
const gchar *
|
|
mm_port_probe_get_port_subsys (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return g_udev_device_get_subsystem (self->priv->port);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMPortProbe *
|
|
mm_port_probe_new (MMDevice *device,
|
|
GUdevDevice *port)
|
|
{
|
|
return MM_PORT_PROBE (g_object_new (MM_TYPE_PORT_PROBE,
|
|
MM_PORT_PROBE_DEVICE, device,
|
|
MM_PORT_PROBE_PORT, port,
|
|
NULL));
|
|
}
|
|
|
|
static void
|
|
mm_port_probe_init (MMPortProbe *self)
|
|
{
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), \
|
|
MM_TYPE_PORT_PROBE, \
|
|
MMPortProbePrivate);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMPortProbe *self = MM_PORT_PROBE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE:
|
|
/* construct only, no new reference! */
|
|
self->priv->device = g_value_get_object (value);
|
|
break;
|
|
case PROP_PORT:
|
|
/* construct only */
|
|
self->priv->port = g_value_dup_object (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMPortProbe *self = MM_PORT_PROBE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE:
|
|
g_value_set_object (value, self->priv->device);
|
|
break;
|
|
case PROP_PORT:
|
|
g_value_set_object (value, self->priv->port);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMPortProbe *self = MM_PORT_PROBE (object);
|
|
|
|
/* We should never have a task here */
|
|
g_assert (self->priv->task == NULL);
|
|
|
|
g_free (self->priv->vendor);
|
|
g_free (self->priv->product);
|
|
|
|
G_OBJECT_CLASS (mm_port_probe_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMPortProbe *self = MM_PORT_PROBE (object);
|
|
|
|
/* We didn't get a reference to the device */
|
|
self->priv->device = NULL;
|
|
|
|
g_clear_object (&self->priv->port);
|
|
|
|
G_OBJECT_CLASS (mm_port_probe_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mm_port_probe_class_init (MMPortProbeClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMPortProbePrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->finalize = finalize;
|
|
object_class->dispose = dispose;
|
|
|
|
properties[PROP_DEVICE] =
|
|
g_param_spec_object (MM_PORT_PROBE_DEVICE,
|
|
"Device",
|
|
"Device owning this probe",
|
|
MM_TYPE_DEVICE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_DEVICE, properties[PROP_DEVICE]);
|
|
|
|
properties[PROP_PORT] =
|
|
g_param_spec_object (MM_PORT_PROBE_PORT,
|
|
"Port",
|
|
"UDev device object of the port",
|
|
G_UDEV_TYPE_DEVICE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_PORT, properties[PROP_PORT]);
|
|
}
|