We can remove the port type hint udev tags for the wwan subsystem, as that logic is now incorporated in the port type hint processing logic in the daemon itself. This makes it clearer to know what exact hints are being used, as the logic is in a single place and it has proper logging of all possible cases.
2123 lines
70 KiB
C
2123 lines
70 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 - 2018 Red Hat, Inc.
|
|
* Copyright (C) 2011 - 2018 Aleksander Morgado <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <ModemManager-tags.h>
|
|
|
|
#include <mm-errors-types.h>
|
|
|
|
#include "mm-port-probe.h"
|
|
#include "mm-log-object.h"
|
|
#include "mm-port-serial-at.h"
|
|
#include "mm-port-serial.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-port-serial-qcdm.h"
|
|
#include "mm-daemon-enums-types.h"
|
|
|
|
#if defined WITH_QMI
|
|
#include "mm-port-qmi.h"
|
|
#endif
|
|
|
|
#if defined WITH_QRTR
|
|
#include "mm-kernel-device-qrtr.h"
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
#include "mm-port-mbim.h"
|
|
#endif
|
|
|
|
/*
|
|
* Steps and flow of the Probing process:
|
|
* ----> AT Serial Open
|
|
* |----> Custom Init
|
|
* |----> AT?
|
|
* |----> Vendor
|
|
* |----> Product
|
|
* |----> Is Icera?
|
|
* |----> Is Xmm?
|
|
* ----> QCDM Serial Open
|
|
* |----> QCDM?
|
|
* ----> QMI Device Open
|
|
* |----> QMI Version Info check
|
|
* ----> MBIM Device Open
|
|
* |----> MBIM capabilities check
|
|
*/
|
|
|
|
static void log_object_iface_init (MMLogObjectInterface *iface);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMPortProbe, mm_port_probe, G_TYPE_OBJECT, 0,
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_DEVICE,
|
|
PROP_PORT,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
struct _MMPortProbePrivate {
|
|
/* Properties */
|
|
MMDevice *device;
|
|
MMKernelDevice *port;
|
|
|
|
/* Probing results */
|
|
guint32 flags;
|
|
gboolean is_at;
|
|
gboolean is_qcdm;
|
|
gchar *vendor;
|
|
gchar *product;
|
|
gboolean is_icera;
|
|
gboolean is_xmm;
|
|
gboolean is_qmi;
|
|
gboolean is_mbim;
|
|
|
|
/* From udev tags */
|
|
gboolean is_ignored;
|
|
gboolean is_gps;
|
|
gboolean is_audio;
|
|
gboolean maybe_at;
|
|
gboolean maybe_qcdm;
|
|
gboolean maybe_qmi;
|
|
gboolean maybe_mbim;
|
|
|
|
/* Current probing task. Only one can be available at a time */
|
|
GTask *task;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* Probe task completions.
|
|
* Always make sure that the stored task is NULL when the task is completed.
|
|
*/
|
|
|
|
static gboolean
|
|
port_probe_task_return_error_if_cancelled (MMPortProbe *self)
|
|
{
|
|
GTask *task;
|
|
|
|
task = self->priv->task;
|
|
self->priv->task = NULL;
|
|
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return TRUE;
|
|
}
|
|
|
|
self->priv->task = task;
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
port_probe_task_return_error (MMPortProbe *self,
|
|
GError *error)
|
|
{
|
|
GTask *task;
|
|
|
|
task = self->priv->task;
|
|
self->priv->task = NULL;
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
port_probe_task_return_boolean (MMPortProbe *self,
|
|
gboolean result)
|
|
{
|
|
GTask *task;
|
|
|
|
task = self->priv->task;
|
|
self->priv->task = NULL;
|
|
g_task_return_boolean (task, result);
|
|
g_object_unref (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_obj_dbg (self, "port is AT-capable");
|
|
|
|
/* 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_obj_dbg (self, "port is not AT-capable");
|
|
self->priv->vendor = NULL;
|
|
self->priv->product = NULL;
|
|
self->priv->is_icera = FALSE;
|
|
self->priv->is_xmm = FALSE;
|
|
self->priv->flags |= (MM_PORT_PROBE_AT_VENDOR |
|
|
MM_PORT_PROBE_AT_PRODUCT |
|
|
MM_PORT_PROBE_AT_ICERA |
|
|
MM_PORT_PROBE_AT_XMM);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_at_vendor (MMPortProbe *self,
|
|
const gchar *at_vendor)
|
|
{
|
|
if (at_vendor) {
|
|
mm_obj_dbg (self, "vendor probing finished");
|
|
self->priv->vendor = g_utf8_casefold (at_vendor, -1);
|
|
self->priv->flags |= MM_PORT_PROBE_AT_VENDOR;
|
|
} else {
|
|
mm_obj_dbg (self, "couldn't probe for vendor string");
|
|
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_obj_dbg (self, "product probing finished");
|
|
self->priv->product = g_utf8_casefold (at_product, -1);
|
|
self->priv->flags |= MM_PORT_PROBE_AT_PRODUCT;
|
|
} else {
|
|
mm_obj_dbg (self, "couldn't probe for product string");
|
|
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_obj_dbg (self, "modem is Icera-based");
|
|
self->priv->is_icera = TRUE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_ICERA;
|
|
} else {
|
|
mm_obj_dbg (self, "modem is probably not Icera-based");
|
|
self->priv->is_icera = FALSE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_ICERA;
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_port_probe_set_result_at_xmm (MMPortProbe *self,
|
|
gboolean is_xmm)
|
|
{
|
|
if (is_xmm) {
|
|
mm_obj_dbg (self, "modem is XMM-based");
|
|
self->priv->is_xmm = TRUE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_XMM;
|
|
} else {
|
|
mm_obj_dbg (self, "modem is probably not XMM-based");
|
|
self->priv->is_xmm = FALSE;
|
|
self->priv->flags |= MM_PORT_PROBE_AT_XMM;
|
|
}
|
|
}
|
|
|
|
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_obj_dbg (self, "port is QCDM-capable");
|
|
|
|
/* 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->is_xmm = 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_AT_XMM |
|
|
MM_PORT_PROBE_QMI |
|
|
MM_PORT_PROBE_MBIM);
|
|
} else
|
|
mm_obj_dbg (self, "port is not QCDM-capable");
|
|
}
|
|
|
|
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_obj_dbg (self, "port is QMI-capable");
|
|
|
|
/* 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_AT_XMM |
|
|
MM_PORT_PROBE_QCDM |
|
|
MM_PORT_PROBE_MBIM);
|
|
} else
|
|
mm_obj_dbg (self, "port is not QMI-capable");
|
|
}
|
|
|
|
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_obj_dbg (self, "port is MBIM-capable");
|
|
|
|
/* 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_AT_XMM |
|
|
MM_PORT_PROBE_QCDM |
|
|
MM_PORT_PROBE_QMI);
|
|
} else
|
|
mm_obj_dbg (self, "port is not MBIM-capable");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
/* ---- Generic task context ---- */
|
|
guint32 flags;
|
|
guint source_id;
|
|
GCancellable *cancellable;
|
|
|
|
/* ---- Serial probing specific context ---- */
|
|
|
|
guint buffer_full_id;
|
|
MMPortSerial *serial;
|
|
|
|
/* ---- AT probing specific context ---- */
|
|
|
|
GCancellable *at_probing_cancellable;
|
|
gulong at_probing_cancellable_linked;
|
|
/* 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;
|
|
/* Seconds between each AT command sent in the group */
|
|
guint at_commands_wait_secs;
|
|
/* Current AT Result processor */
|
|
void (* at_result_processor) (MMPortProbe *self,
|
|
GVariant *result);
|
|
|
|
#if defined WITH_QMI
|
|
/* ---- QMI probing specific context ---- */
|
|
MMPortQmi *port_qmi;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
/* ---- MBIM probing specific context ---- */
|
|
MMPortMbim *mbim_port;
|
|
#endif
|
|
|
|
/* ---- QCDM probing specific context ---- */
|
|
gboolean qcdm_required;
|
|
} PortProbeRunContext;
|
|
|
|
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_context_free (PortProbeRunContext *ctx)
|
|
{
|
|
if (ctx->cancellable && ctx->at_probing_cancellable_linked) {
|
|
g_cancellable_disconnect (ctx->cancellable, ctx->at_probing_cancellable_linked);
|
|
ctx->at_probing_cancellable_linked = 0;
|
|
}
|
|
|
|
if (ctx->source_id) {
|
|
g_source_remove (ctx->source_id);
|
|
ctx->source_id = 0;
|
|
}
|
|
|
|
if (ctx->serial && ctx->buffer_full_id) {
|
|
g_signal_handler_disconnect (ctx->serial, ctx->buffer_full_id);
|
|
ctx->buffer_full_id = 0;
|
|
}
|
|
|
|
if (ctx->serial) {
|
|
if (mm_port_serial_is_open (ctx->serial))
|
|
mm_port_serial_close (ctx->serial);
|
|
g_object_unref (ctx->serial);
|
|
}
|
|
|
|
#if defined WITH_QMI
|
|
if (ctx->port_qmi) {
|
|
/* We should have closed it cleanly before */
|
|
g_assert (!mm_port_qmi_is_open (ctx->port_qmi));
|
|
g_object_unref (ctx->port_qmi);
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (ctx->mbim_port) {
|
|
/* We should have closed it cleanly before */
|
|
g_assert (!mm_port_mbim_is_open (ctx->mbim_port));
|
|
g_object_unref (ctx->mbim_port);
|
|
}
|
|
#endif
|
|
|
|
if (ctx->at_probing_cancellable)
|
|
g_object_unref (ctx->at_probing_cancellable);
|
|
if (ctx->cancellable)
|
|
g_object_unref (ctx->cancellable);
|
|
|
|
g_slice_free (PortProbeRunContext, ctx);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* QMI & MBIM */
|
|
|
|
static gboolean wdm_probe (MMPortProbe *self);
|
|
|
|
#if defined WITH_QMI
|
|
|
|
static void
|
|
qmi_port_close_ready (MMPortQmi *qmi_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
mm_port_qmi_close_finish (qmi_port, res, NULL);
|
|
|
|
/* Keep on */
|
|
ctx->source_id = g_idle_add ((GSourceFunc) wdm_probe, self);
|
|
}
|
|
|
|
static void
|
|
port_qmi_open_ready (MMPortQmi *port_qmi,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
GError *error = NULL;
|
|
PortProbeRunContext *ctx;
|
|
gboolean is_qmi;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
is_qmi = mm_port_qmi_open_finish (port_qmi, res, &error);
|
|
if (!is_qmi) {
|
|
mm_obj_dbg (self, "error checking QMI support: %s",
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* Set probing result */
|
|
mm_port_probe_set_result_qmi (self, is_qmi);
|
|
|
|
mm_port_qmi_close (ctx->port_qmi,
|
|
(GAsyncReadyCallback) qmi_port_close_ready,
|
|
self);
|
|
}
|
|
|
|
#endif /* WITH_QMI */
|
|
|
|
static void
|
|
wdm_probe_qmi (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
#if defined WITH_QMI
|
|
/* Create a port and try to open it */
|
|
mm_obj_dbg (self, "probing QMI...");
|
|
|
|
#if defined WITH_QRTR
|
|
if (MM_IS_KERNEL_DEVICE_QRTR (self->priv->port)) {
|
|
g_autoptr(QrtrNode) node = NULL;
|
|
|
|
node = mm_kernel_device_qrtr_get_node (MM_KERNEL_DEVICE_QRTR (self->priv->port));
|
|
|
|
/* Will set MM_PORT_SUBSYS_QRTR when creating the mm-port */
|
|
ctx->port_qmi = mm_port_qmi_new_from_node (mm_kernel_device_get_name (self->priv->port), node);
|
|
} else
|
|
#endif /* WITH_QRTR */
|
|
{
|
|
MMPortSubsys subsys = MM_PORT_SUBSYS_USBMISC;
|
|
|
|
if (g_str_equal (mm_kernel_device_get_subsystem (self->priv->port), "rpmsg"))
|
|
subsys = MM_PORT_SUBSYS_RPMSG;
|
|
|
|
ctx->port_qmi = mm_port_qmi_new (mm_kernel_device_get_name (self->priv->port), subsys);
|
|
}
|
|
|
|
mm_port_qmi_open (ctx->port_qmi,
|
|
FALSE,
|
|
NULL,
|
|
(GAsyncReadyCallback) port_qmi_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);
|
|
ctx->source_id = g_idle_add ((GSourceFunc) wdm_probe, self);
|
|
#endif /* WITH_QMI */
|
|
}
|
|
|
|
#if defined WITH_MBIM
|
|
|
|
static void
|
|
mbim_port_close_ready (MMPortMbim *mbim_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
mm_port_mbim_close_finish (mbim_port, res, NULL);
|
|
|
|
/* Keep on */
|
|
ctx->source_id = g_idle_add ((GSourceFunc) wdm_probe, self);
|
|
}
|
|
|
|
static void
|
|
mbim_port_open_ready (MMPortMbim *mbim_port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
GError *error = NULL;
|
|
PortProbeRunContext *ctx;
|
|
gboolean is_mbim;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
is_mbim = mm_port_mbim_open_finish (mbim_port, res, &error);
|
|
if (!is_mbim) {
|
|
mm_obj_dbg (self, "error checking MBIM support: %s",
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* Set probing result */
|
|
mm_port_probe_set_result_mbim (self, is_mbim);
|
|
|
|
mm_port_mbim_close (ctx->mbim_port,
|
|
(GAsyncReadyCallback) mbim_port_close_ready,
|
|
self);
|
|
}
|
|
|
|
#endif /* WITH_MBIM */
|
|
|
|
static void
|
|
wdm_probe_mbim (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
#if defined WITH_MBIM
|
|
mm_obj_dbg (self, "probing MBIM...");
|
|
|
|
/* Create a port and try to open it */
|
|
ctx->mbim_port = mm_port_mbim_new (mm_kernel_device_get_name (self->priv->port),
|
|
MM_PORT_SUBSYS_USBMISC);
|
|
mm_port_mbim_open (ctx->mbim_port,
|
|
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
|
|
FALSE, /* Don't check QMI over MBIM support at this stage */
|
|
#endif
|
|
NULL,
|
|
(GAsyncReadyCallback) mbim_port_open_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);
|
|
ctx->source_id = g_idle_add ((GSourceFunc) wdm_probe, self);
|
|
#endif /* WITH_MBIM */
|
|
}
|
|
|
|
static gboolean
|
|
wdm_probe (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
ctx->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return G_SOURCE_REMOVE;
|
|
|
|
/* QMI probing needed? */
|
|
if ((ctx->flags & MM_PORT_PROBE_QMI) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_QMI)) {
|
|
wdm_probe_qmi (self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* MBIM probing needed */
|
|
if ((ctx->flags & MM_PORT_PROBE_MBIM) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_MBIM)) {
|
|
wdm_probe_mbim (self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* All done now */
|
|
port_probe_task_return_boolean (self, TRUE);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
|
|
static void
|
|
common_serial_port_setup (MMPortProbe *self,
|
|
MMPortSerial *serial)
|
|
{
|
|
const gchar *flow_control_tag;
|
|
|
|
if (mm_kernel_device_has_property (self->priv->port, ID_MM_TTY_BAUDRATE))
|
|
g_object_set (serial,
|
|
MM_PORT_SERIAL_BAUD, mm_kernel_device_get_property_as_int (self->priv->port, ID_MM_TTY_BAUDRATE),
|
|
NULL);
|
|
|
|
flow_control_tag = mm_kernel_device_get_property (self->priv->port, ID_MM_TTY_FLOW_CONTROL);
|
|
if (flow_control_tag) {
|
|
MMFlowControl flow_control;
|
|
GError *error = NULL;
|
|
|
|
flow_control = mm_flow_control_from_string (flow_control_tag, &error);
|
|
if (flow_control == MM_FLOW_CONTROL_UNKNOWN) {
|
|
mm_obj_warn (self, "unsupported flow control settings in port: %s", error->message);
|
|
g_error_free (error);
|
|
} else {
|
|
g_object_set (serial,
|
|
MM_PORT_SERIAL_FLOW_CONTROL, flow_control,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* QCDM */
|
|
|
|
static void
|
|
serial_probe_qcdm_parse_response (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
QcdmResult *result;
|
|
gint err = QCDM_SUCCESS;
|
|
gboolean is_qcdm = FALSE;
|
|
gboolean retry = FALSE;
|
|
GError *error = NULL;
|
|
GByteArray *response;
|
|
PortProbeRunContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return;
|
|
|
|
response = mm_port_serial_qcdm_command_finish (port, res, &error);
|
|
if (!error) {
|
|
/* Parse the response */
|
|
result = qcdm_cmd_version_info_result ((const gchar *) response->data, response->len, &err);
|
|
if (!result) {
|
|
mm_obj_warn (self, "failed to parse QCDM version info command result: %d", err);
|
|
retry = TRUE;
|
|
} else {
|
|
/* yay, probably a QCDM port */
|
|
is_qcdm = TRUE;
|
|
qcdm_result_unref (result);
|
|
}
|
|
g_byte_array_unref (response);
|
|
} else if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_PARSE_FAILED)) {
|
|
/* Failed to unescape QCDM packet: don't retry */
|
|
mm_obj_dbg (self, "QCDM parsing error: %s", error->message);
|
|
g_error_free (error);
|
|
} else {
|
|
if (!g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT))
|
|
mm_obj_dbg (self, "QCDM probe error: (%d) %s", error->code, error->message);
|
|
g_error_free (error);
|
|
retry = TRUE;
|
|
}
|
|
|
|
if (retry) {
|
|
GByteArray *cmd2;
|
|
|
|
cmd2 = g_object_steal_data (G_OBJECT (self), "cmd2");
|
|
if (cmd2) {
|
|
/* second try */
|
|
mm_port_serial_qcdm_command (MM_PORT_SERIAL_QCDM (ctx->serial),
|
|
cmd2,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback) serial_probe_qcdm_parse_response,
|
|
self);
|
|
g_byte_array_unref (cmd2);
|
|
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)
|
|
{
|
|
GError *error = NULL;
|
|
GByteArray *verinfo = NULL;
|
|
GByteArray *verinfo2;
|
|
gint len;
|
|
guint8 marker = 0x7E;
|
|
MMPortSubsys subsys = MM_PORT_SUBSYS_TTY;
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
ctx->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return G_SOURCE_REMOVE;
|
|
|
|
/* If the plugin specifies QCDM is not required, we can right away complete the QCDM
|
|
* probing task. */
|
|
if (!ctx->qcdm_required) {
|
|
mm_obj_dbg (self, "Maybe a QCDM port, but plugin does not require probing and grabbing...");
|
|
/* If we had a port type hint, flag the port as QCDM capable but ignored. Otherwise,
|
|
* no QCDM capable and not ignored. The outcome is really the same, i.e. the port is not
|
|
* used any more, but the way it's reported in DBus will be different (i.e. "ignored" vs
|
|
"unknown" */
|
|
if (self->priv->maybe_qcdm) {
|
|
mm_port_probe_set_result_qcdm (self, TRUE);
|
|
self->priv->is_ignored = TRUE;
|
|
} else
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
/* Reschedule probing */
|
|
serial_probe_schedule (self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
mm_obj_dbg (self, "probing QCDM...");
|
|
|
|
/* If open, close the AT port */
|
|
if (ctx->serial) {
|
|
/* Explicitly clear the buffer full signal handler */
|
|
if (ctx->buffer_full_id) {
|
|
g_signal_handler_disconnect (ctx->serial, ctx->buffer_full_id);
|
|
ctx->buffer_full_id = 0;
|
|
}
|
|
mm_port_serial_close (ctx->serial);
|
|
g_object_unref (ctx->serial);
|
|
}
|
|
|
|
if (g_str_equal (mm_kernel_device_get_subsystem (self->priv->port), "wwan"))
|
|
subsys = MM_PORT_SUBSYS_WWAN;
|
|
|
|
/* Open the QCDM port */
|
|
ctx->serial = MM_PORT_SERIAL (mm_port_serial_qcdm_new (mm_kernel_device_get_name (self->priv->port), subsys));
|
|
if (!ctx->serial) {
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) Couldn't create QCDM port",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port)));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* Setup port if needed */
|
|
common_serial_port_setup (self, ctx->serial);
|
|
|
|
/* Try to open the port */
|
|
if (!mm_port_serial_open (ctx->serial, &error)) {
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) Failed to open QCDM port: %s",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port),
|
|
(error ? error->message : "unknown error")));
|
|
g_clear_error (&error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* 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_unref (verinfo);
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) Failed to create QCDM version info command",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port)));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
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_port_serial_qcdm_command (MM_PORT_SERIAL_QCDM (ctx->serial),
|
|
verinfo,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback) serial_probe_qcdm_parse_response,
|
|
self);
|
|
g_byte_array_unref (verinfo);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* 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_xmm_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), "XACT:")) {
|
|
mm_port_probe_set_result_at_xmm (self, TRUE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mm_port_probe_set_result_at_xmm (self, 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 (MMPortSerialAt *port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
GVariant *result = NULL;
|
|
GError *result_error = NULL;
|
|
g_autofree gchar *response = NULL;
|
|
GError *error = NULL;
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return;
|
|
|
|
/* If AT probing cancelled, end this partial probing */
|
|
if (g_cancellable_is_cancelled (ctx->at_probing_cancellable)) {
|
|
mm_obj_dbg (self, "no need to keep on probing the port for AT support");
|
|
ctx->at_result_processor (self, NULL);
|
|
serial_probe_schedule (self);
|
|
return;
|
|
}
|
|
|
|
response = mm_port_serial_at_command_finish (port, res, &error);
|
|
|
|
if (!ctx->at_commands->response_processor (ctx->at_commands->command,
|
|
response,
|
|
!!ctx->at_commands[1].command,
|
|
error,
|
|
&result,
|
|
&result_error)) {
|
|
/* Were we told to abort the whole probing? */
|
|
if (result_error) {
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"(%s/%s) error while probing AT features: %s",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port),
|
|
result_error->message));
|
|
goto out;
|
|
}
|
|
|
|
/* Go on to next command */
|
|
ctx->at_commands++;
|
|
if (!ctx->at_commands->command) {
|
|
/* Was it the last command in the group? If so,
|
|
* end this partial probing */
|
|
ctx->at_result_processor (self, NULL);
|
|
/* Reschedule */
|
|
serial_probe_schedule (self);
|
|
goto out;
|
|
}
|
|
|
|
/* Schedule the next command in the probing group */
|
|
if (ctx->at_commands_wait_secs == 0)
|
|
ctx->source_id = g_idle_add ((GSourceFunc) serial_probe_at, self);
|
|
else {
|
|
mm_obj_dbg (self, "re-scheduling next command in probing group in %u seconds...",
|
|
ctx->at_commands_wait_secs);
|
|
ctx->source_id = g_timeout_add_seconds (ctx->at_commands_wait_secs, (GSourceFunc) serial_probe_at, self);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/* Run result processor.
|
|
* Note that custom init commands are allowed to not return anything */
|
|
ctx->at_result_processor (self, result);
|
|
|
|
/* Reschedule probing */
|
|
serial_probe_schedule (self);
|
|
|
|
out:
|
|
g_clear_pointer (&result, g_variant_unref);
|
|
g_clear_error (&error);
|
|
g_clear_error (&result_error);
|
|
}
|
|
|
|
static gboolean
|
|
serial_probe_at (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
ctx->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return G_SOURCE_REMOVE;
|
|
|
|
/* If AT probing cancelled, end this partial probing */
|
|
if (g_cancellable_is_cancelled (ctx->at_probing_cancellable)) {
|
|
mm_obj_dbg (self, "no need to launch probing for AT support");
|
|
ctx->at_result_processor (self, NULL);
|
|
serial_probe_schedule (self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
mm_port_serial_at_command (
|
|
MM_PORT_SERIAL_AT (ctx->serial),
|
|
ctx->at_commands->command,
|
|
ctx->at_commands->timeout,
|
|
FALSE,
|
|
FALSE,
|
|
ctx->at_probing_cancellable,
|
|
(GAsyncReadyCallback)serial_probe_at_parse_response,
|
|
self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
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 },
|
|
{ "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 },
|
|
{ "%IPSYS?", 3, mm_port_probe_response_processor_string },
|
|
{ "%IPSYS?", 3, mm_port_probe_response_processor_string },
|
|
{ NULL }
|
|
};
|
|
|
|
static const MMPortProbeAtCommand xmm_probing[] = {
|
|
{ "+XACT=?", 3, mm_port_probe_response_processor_string },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
at_custom_init_ready (MMPortProbe *self,
|
|
GAsyncResult *res)
|
|
{
|
|
GError *error = NULL;
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
if (!ctx->at_custom_init_finish (self, res, &error)) {
|
|
/* All errors propagated up end up forcing an UNSUPPORTED result */
|
|
port_probe_task_return_error (self, error);
|
|
return;
|
|
}
|
|
|
|
/* Keep on with remaining probings */
|
|
ctx->at_custom_init_run = TRUE;
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
/***************************************************************/
|
|
|
|
static void
|
|
serial_probe_schedule (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return;
|
|
|
|
/* If we got some custom initialization setup requested, go on with it
|
|
* first. We completely ignore the custom initialization if the serial port
|
|
* that we receive in the context isn't an AT port (e.g. if it was flagged
|
|
* as not being an AT port early) */
|
|
if (!ctx->at_custom_init_run &&
|
|
ctx->at_custom_init &&
|
|
ctx->at_custom_init_finish &&
|
|
MM_IS_PORT_SERIAL_AT (ctx->serial)) {
|
|
ctx->at_custom_init (self,
|
|
MM_PORT_SERIAL_AT (ctx->serial),
|
|
ctx->at_probing_cancellable,
|
|
(GAsyncReadyCallback) at_custom_init_ready,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
/* Cleanup */
|
|
ctx->at_result_processor = NULL;
|
|
ctx->at_commands = NULL;
|
|
ctx->at_commands_wait_secs = 0;
|
|
|
|
/* AT check requested and not already probed? */
|
|
if ((ctx->flags & MM_PORT_PROBE_AT) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT)) {
|
|
/* Prepare AT probing */
|
|
if (ctx->at_custom_probe)
|
|
ctx->at_commands = ctx->at_custom_probe;
|
|
else
|
|
ctx->at_commands = at_probing;
|
|
ctx->at_result_processor = serial_probe_at_result_processor;
|
|
}
|
|
/* Vendor requested and not already probed? */
|
|
else if ((ctx->flags & MM_PORT_PROBE_AT_VENDOR) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_VENDOR)) {
|
|
/* Prepare AT vendor probing */
|
|
ctx->at_result_processor = serial_probe_at_vendor_result_processor;
|
|
ctx->at_commands = vendor_probing;
|
|
}
|
|
/* Product requested and not already probed? */
|
|
else if ((ctx->flags & MM_PORT_PROBE_AT_PRODUCT) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_PRODUCT)) {
|
|
/* Prepare AT product probing */
|
|
ctx->at_result_processor = serial_probe_at_product_result_processor;
|
|
ctx->at_commands = product_probing;
|
|
}
|
|
/* Icera support check requested and not already done? */
|
|
else if ((ctx->flags & MM_PORT_PROBE_AT_ICERA) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_ICERA)) {
|
|
/* Prepare AT product probing */
|
|
ctx->at_result_processor = serial_probe_at_icera_result_processor;
|
|
ctx->at_commands = icera_probing;
|
|
/* By default, wait 2 seconds between ICERA probing retries */
|
|
ctx->at_commands_wait_secs = 2;
|
|
}
|
|
/* XMM support check requested and not already done? */
|
|
else if ((ctx->flags & MM_PORT_PROBE_AT_XMM) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_AT_XMM)) {
|
|
/* Prepare AT product probing */
|
|
ctx->at_result_processor = serial_probe_at_xmm_result_processor;
|
|
ctx->at_commands = xmm_probing;
|
|
}
|
|
|
|
/* If a next AT group detected, go for it */
|
|
if (ctx->at_result_processor &&
|
|
ctx->at_commands) {
|
|
ctx->source_id = g_idle_add ((GSourceFunc) serial_probe_at, self);
|
|
return;
|
|
}
|
|
|
|
/* QCDM requested and not already probed? */
|
|
if ((ctx->flags & MM_PORT_PROBE_QCDM) &&
|
|
!(self->priv->flags & MM_PORT_PROBE_QCDM)) {
|
|
ctx->source_id = g_idle_add ((GSourceFunc) serial_probe_qcdm, self);
|
|
return;
|
|
}
|
|
|
|
/* All done! */
|
|
port_probe_task_return_boolean (self, TRUE);
|
|
}
|
|
|
|
static void
|
|
serial_flash_ready (MMPortSerial *port,
|
|
GAsyncResult *res,
|
|
MMPortProbe *self)
|
|
{
|
|
mm_port_serial_flash_finish (port, res, NULL);
|
|
|
|
/* Schedule probing */
|
|
serial_probe_schedule (self);
|
|
}
|
|
|
|
static void
|
|
serial_buffer_full (MMPortSerial *serial,
|
|
GByteArray *buffer,
|
|
MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
if (!is_non_at_response (buffer->data, buffer->len))
|
|
return;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
|
|
mm_obj_dbg (self, "serial buffer full");
|
|
/* 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 (ctx->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)
|
|
{
|
|
GError *error = NULL;
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_assert (self->priv->task);
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
ctx->source_id = 0;
|
|
|
|
/* If already cancelled, do nothing else */
|
|
if (port_probe_task_return_error_if_cancelled (self))
|
|
return G_SOURCE_REMOVE;
|
|
|
|
/* Create AT serial port if not done before */
|
|
if (!ctx->serial) {
|
|
gpointer parser;
|
|
MMPortSubsys subsys = MM_PORT_SUBSYS_TTY;
|
|
|
|
if (g_str_equal (mm_kernel_device_get_subsystem (self->priv->port), "usbmisc"))
|
|
subsys = MM_PORT_SUBSYS_USBMISC;
|
|
else if (g_str_equal (mm_kernel_device_get_subsystem (self->priv->port), "rpmsg"))
|
|
subsys = MM_PORT_SUBSYS_RPMSG;
|
|
else if (g_str_equal (mm_kernel_device_get_subsystem (self->priv->port), "wwan"))
|
|
subsys = MM_PORT_SUBSYS_WWAN;
|
|
|
|
ctx->serial = MM_PORT_SERIAL (mm_port_serial_at_new (mm_kernel_device_get_name (self->priv->port), subsys));
|
|
if (!ctx->serial) {
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) couldn't create AT port",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port)));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
g_object_set (ctx->serial,
|
|
MM_PORT_SERIAL_SPEW_CONTROL, TRUE,
|
|
MM_PORT_SERIAL_SEND_DELAY, (guint64)(subsys == MM_PORT_SUBSYS_TTY ? ctx->at_send_delay : 0),
|
|
MM_PORT_SERIAL_AT_REMOVE_ECHO, ctx->at_remove_echo,
|
|
MM_PORT_SERIAL_AT_SEND_LF, ctx->at_send_lf,
|
|
NULL);
|
|
|
|
common_serial_port_setup (self, ctx->serial);
|
|
|
|
parser = mm_serial_parser_v1_new ();
|
|
mm_serial_parser_v1_add_filter (parser,
|
|
serial_parser_filter_cb,
|
|
NULL);
|
|
mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (ctx->serial),
|
|
mm_serial_parser_v1_parse,
|
|
parser,
|
|
mm_serial_parser_v1_destroy);
|
|
}
|
|
|
|
/* Try to open the port */
|
|
if (!mm_port_serial_open (ctx->serial, &error)) {
|
|
/* Abort if maximum number of open tries reached */
|
|
if (++ctx->at_open_tries > 4) {
|
|
/* took too long to open the port; give up */
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"(%s/%s) failed to open port after 4 tries",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port)));
|
|
g_clear_error (&error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE)) {
|
|
/* this is nozomi being dumb; try again */
|
|
ctx->source_id = g_timeout_add_seconds (1, (GSourceFunc) serial_open_at, self);
|
|
g_clear_error (&error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
port_probe_task_return_error (self,
|
|
g_error_new (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_OPEN_FAILED,
|
|
"(%s/%s) failed to open port: %s",
|
|
mm_kernel_device_get_subsystem (self->priv->port),
|
|
mm_kernel_device_get_name (self->priv->port),
|
|
(error ? error->message : "unknown error")));
|
|
g_clear_error (&error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* success, start probing */
|
|
ctx->buffer_full_id = g_signal_connect (ctx->serial, "buffer-full",
|
|
G_CALLBACK (serial_buffer_full), self);
|
|
mm_port_serial_flash (MM_PORT_SERIAL (ctx->serial),
|
|
100,
|
|
TRUE,
|
|
(GAsyncReadyCallback) serial_flash_ready,
|
|
self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
at_cancellable_cancel (GCancellable *cancellable,
|
|
PortProbeRunContext *ctx)
|
|
{
|
|
/* Avoid trying to disconnect cancellable on the handler, or we'll deadlock */
|
|
ctx->at_probing_cancellable_linked = 0;
|
|
g_cancellable_cancel (ctx->at_probing_cancellable);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_run_cancel_at_probing (MMPortProbe *self)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
if (!self->priv->task)
|
|
return FALSE;
|
|
|
|
ctx = g_task_get_task_data (self->priv->task);
|
|
if (g_cancellable_is_cancelled (ctx->at_probing_cancellable))
|
|
return FALSE;
|
|
|
|
mm_obj_dbg (self, "requested to cancel all AT probing");
|
|
g_cancellable_cancel (ctx->at_probing_cancellable);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_run_finish (MMPortProbe *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
g_return_val_if_fail (G_IS_TASK (result), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
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,
|
|
gboolean qcdm_required,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
PortProbeRunContext *ctx;
|
|
gchar *probe_list_str;
|
|
guint32 i;
|
|
|
|
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);
|
|
self->priv->task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* Task context */
|
|
ctx = g_slice_new0 (PortProbeRunContext);
|
|
ctx->at_send_delay = at_send_delay;
|
|
ctx->at_remove_echo = at_remove_echo;
|
|
ctx->at_send_lf = at_send_lf;
|
|
ctx->flags = MM_PORT_PROBE_NONE;
|
|
ctx->at_custom_probe = at_custom_probe;
|
|
ctx->at_custom_init = at_custom_init ? (MMPortProbeAtCustomInit)at_custom_init->async : NULL;
|
|
ctx->at_custom_init_finish = at_custom_init ? (MMPortProbeAtCustomInitFinish)at_custom_init->finish : NULL;
|
|
ctx->qcdm_required = qcdm_required;
|
|
ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
|
|
|
|
/* The context will be owned by the task */
|
|
g_task_set_task_data (self->priv->task, ctx, (GDestroyNotify) port_probe_run_context_free);
|
|
|
|
/* If we're told to completely ignore the port, don't do any probing */
|
|
if (self->priv->is_ignored) {
|
|
mm_obj_dbg (self, "port probing finished: skipping for blacklisted port");
|
|
port_probe_task_return_boolean (self, TRUE);
|
|
return;
|
|
}
|
|
|
|
/* If this is a port flagged as a GPS port, don't do any other probing */
|
|
if (self->priv->is_gps) {
|
|
mm_obj_dbg (self, "GPS port detected");
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
}
|
|
|
|
/* If this is a port flagged as an audio port, don't do any other probing */
|
|
if (self->priv->is_audio) {
|
|
mm_obj_dbg (self, "audio port detected");
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
}
|
|
|
|
/* If this is a port flagged as being an AT port, don't do any other probing */
|
|
if (self->priv->maybe_at) {
|
|
mm_obj_dbg (self, "no QCDM/QMI/MBIM probing in possible AT port");
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
}
|
|
|
|
/* If this is a port flagged as being a QCDM port, don't do any other probing */
|
|
if (self->priv->maybe_qcdm) {
|
|
mm_obj_dbg (self, "no AT/QMI/MBIM probing in possible QCDM port");
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
}
|
|
|
|
/* If this is a port flagged as being a QMI port, don't do any other probing */
|
|
if (self->priv->maybe_qmi) {
|
|
mm_obj_dbg (self, "no AT/QCDM/MBIM probing in possible QMI port");
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
mm_port_probe_set_result_mbim (self, FALSE);
|
|
}
|
|
|
|
/* If this is a port flagged as being a MBIM port, don't do any other probing */
|
|
if (self->priv->maybe_mbim) {
|
|
mm_obj_dbg (self, "no AT/QCDM/QMI probing in possible MBIM port");
|
|
mm_port_probe_set_result_at (self, FALSE);
|
|
mm_port_probe_set_result_qcdm (self, FALSE);
|
|
mm_port_probe_set_result_qmi (self, FALSE);
|
|
}
|
|
|
|
/* Check if we already have the requested probing results.
|
|
* We will fix here the 'ctx->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))
|
|
ctx->flags += i;
|
|
}
|
|
|
|
/* All requested probings already available? If so, we're done */
|
|
if (!ctx->flags) {
|
|
mm_obj_dbg (self, "port probing finished: no more probings needed");
|
|
port_probe_task_return_boolean (self, TRUE);
|
|
return;
|
|
}
|
|
|
|
/* Log the probes scheduled to be run */
|
|
probe_list_str = mm_port_probe_flag_build_string_from_mask (ctx->flags);
|
|
mm_obj_dbg (self, "launching port probing: '%s'", probe_list_str);
|
|
g_free (probe_list_str);
|
|
|
|
/* If any AT probing is needed, start by opening as AT port */
|
|
if (ctx->flags & MM_PORT_PROBE_AT ||
|
|
ctx->flags & MM_PORT_PROBE_AT_VENDOR ||
|
|
ctx->flags & MM_PORT_PROBE_AT_PRODUCT ||
|
|
ctx->flags & MM_PORT_PROBE_AT_ICERA ||
|
|
ctx->flags & MM_PORT_PROBE_AT_XMM) {
|
|
ctx->at_probing_cancellable = g_cancellable_new ();
|
|
/* If the main cancellable is cancelled, so will be the at-probing one */
|
|
if (cancellable)
|
|
ctx->at_probing_cancellable_linked = g_cancellable_connect (cancellable,
|
|
(GCallback) at_cancellable_cancel,
|
|
ctx,
|
|
NULL);
|
|
ctx->source_id = g_idle_add ((GSourceFunc) serial_open_at, self);
|
|
return;
|
|
}
|
|
|
|
/* If QCDM probing needed, start by opening as QCDM port */
|
|
if (ctx->flags & MM_PORT_PROBE_QCDM) {
|
|
ctx->source_id = g_idle_add ((GSourceFunc) serial_probe_qcdm, self);
|
|
return;
|
|
}
|
|
|
|
/* If QMI/MBIM probing needed, go on */
|
|
if (ctx->flags & MM_PORT_PROBE_QMI || ctx->flags & MM_PORT_PROBE_MBIM) {
|
|
ctx->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;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = mm_kernel_device_get_subsystem (self->priv->port);
|
|
if (g_str_equal (subsys, "net"))
|
|
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->is_ignored &&
|
|
probe->priv->flags & MM_PORT_PROBE_AT &&
|
|
probe->priv->is_at)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_qcdm (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_QCDM ?
|
|
self->priv->is_qcdm :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_qmi (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_QMI ?
|
|
self->priv->is_qmi :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_has_qmi_port (GList *list)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = list; l; l = g_list_next (l)) {
|
|
MMPortProbe *probe = MM_PORT_PROBE (l->data);
|
|
|
|
if (!probe->priv->is_ignored &&
|
|
mm_port_probe_is_qmi (probe))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_mbim (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_MBIM ?
|
|
self->priv->is_mbim :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_has_mbim_port (GList *list)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = list; l; l = g_list_next (l)) {
|
|
MMPortProbe *probe = MM_PORT_PROBE (l->data);
|
|
|
|
if (!probe->priv->is_ignored &&
|
|
mm_port_probe_is_mbim (probe))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MMPortType
|
|
mm_port_probe_get_port_type (MMPortProbe *self)
|
|
{
|
|
const gchar *subsys;
|
|
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
subsys = mm_kernel_device_get_subsystem (self->priv->port);
|
|
|
|
if (g_str_equal (subsys, "net"))
|
|
return MM_PORT_TYPE_NET;
|
|
|
|
#if defined WITH_QMI
|
|
if (self->priv->flags & MM_PORT_PROBE_QMI &&
|
|
self->priv->is_qmi)
|
|
return MM_PORT_TYPE_QMI;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (self->priv->flags & MM_PORT_PROBE_MBIM &&
|
|
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;
|
|
|
|
if (self->priv->is_gps)
|
|
return MM_PORT_TYPE_GPS;
|
|
|
|
if (self->priv->is_audio)
|
|
return MM_PORT_TYPE_AUDIO;
|
|
|
|
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));
|
|
}
|
|
|
|
MMKernelDevice *
|
|
mm_port_probe_peek_port (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return self->priv->port;
|
|
};
|
|
|
|
MMKernelDevice *
|
|
mm_port_probe_get_port (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return MM_KERNEL_DEVICE (g_object_ref (self->priv->port));
|
|
};
|
|
|
|
const gchar *
|
|
mm_port_probe_get_vendor (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT_VENDOR ?
|
|
self->priv->vendor :
|
|
NULL);
|
|
}
|
|
|
|
const gchar *
|
|
mm_port_probe_get_product (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_xmm (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return (self->priv->flags & MM_PORT_PROBE_AT_XMM ?
|
|
self->priv->is_xmm :
|
|
FALSE);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_list_is_xmm (GList *probes)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = probes; l; l = g_list_next (l)) {
|
|
if (mm_port_probe_is_xmm (MM_PORT_PROBE (l->data)))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
mm_port_probe_is_ignored (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), FALSE);
|
|
|
|
return self->priv->is_ignored;
|
|
}
|
|
|
|
const gchar *
|
|
mm_port_probe_get_port_name (MMPortProbe *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_PORT_PROBE (self), NULL);
|
|
|
|
return mm_kernel_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 mm_kernel_device_get_subsystem (self->priv->port);
|
|
}
|
|
|
|
static void
|
|
initialize_port_type_hints (MMPortProbe *self)
|
|
{
|
|
g_autoptr(GString) udev_tags = NULL;
|
|
guint n_udev_hints = 0;
|
|
gboolean auto_maybe_qmi = FALSE;
|
|
gboolean auto_maybe_mbim = FALSE;
|
|
gboolean auto_maybe_at = FALSE;
|
|
gboolean auto_maybe_qcdm = FALSE;
|
|
gboolean auto_ignored = FALSE;
|
|
|
|
#define ADD_HINT_FROM_UDEV_TAG(TAG, FIELD) do { \
|
|
if (!self->priv->FIELD && \
|
|
mm_kernel_device_get_property_as_boolean (self->priv->port, TAG)) { \
|
|
mm_obj_dbg (self, "port type hint detected in udev tag: %s", TAG); \
|
|
self->priv->FIELD = TRUE; \
|
|
n_udev_hints++; \
|
|
if (!udev_tags) \
|
|
udev_tags = g_string_new (TAG); \
|
|
else \
|
|
g_string_append_printf (udev_tags, ", %s", TAG); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* Process udev-configured port type hints */
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_GPS, is_gps);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_AUDIO, is_audio);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_AT_PRIMARY, maybe_at);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_AT_SECONDARY, maybe_at);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_AT_PPP, maybe_at);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_QCDM, maybe_qcdm);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_QMI, maybe_qmi);
|
|
ADD_HINT_FROM_UDEV_TAG (ID_MM_PORT_TYPE_MBIM, maybe_mbim);
|
|
|
|
/* Warn if more than one given at the same time */
|
|
if (n_udev_hints > 1)
|
|
mm_obj_warn (self, "multiple incompatible port type hints configured via udev: %s", udev_tags->str);
|
|
|
|
/* Process automatic port type hints, and warn if the hint doesn't match the
|
|
* one provided via udev. The udev-provided hints are always preferred. */
|
|
if (!g_strcmp0 (mm_kernel_device_get_subsystem (self->priv->port), "usbmisc")) {
|
|
const gchar *driver;
|
|
|
|
driver = mm_kernel_device_get_driver (self->priv->port);
|
|
if (!g_strcmp0 (driver, "qmi_wwan")) {
|
|
mm_obj_dbg (self, "port may be QMI based on the driver in use");
|
|
auto_maybe_qmi = TRUE;
|
|
} else if (!g_strcmp0 (driver, "cdc_mbim")) {
|
|
mm_obj_dbg (self, "port may be MBIM based on the driver in use");
|
|
auto_maybe_mbim = TRUE;
|
|
} else {
|
|
mm_obj_dbg (self, "port may be AT based on the driver in use: %s", driver);
|
|
auto_maybe_at = TRUE;
|
|
}
|
|
} else if (!g_strcmp0 (mm_kernel_device_get_subsystem (self->priv->port), "wwan")) {
|
|
/* Linux >= 5.14 has at 'type' attribute specifying the type of port */
|
|
if (mm_kernel_device_has_attribute (self->priv->port, "type")) {
|
|
const gchar *type;
|
|
|
|
type = mm_kernel_device_get_attribute (self->priv->port, "type");
|
|
if (!g_strcmp0 (type, "AT")) {
|
|
mm_obj_dbg (self, "port may be AT based on the wwan type attribute");
|
|
auto_maybe_at = TRUE;
|
|
} else if (!g_strcmp0 (type, "MBIM")) {
|
|
mm_obj_dbg (self, "port may be MBIM based on the wwan type attribute");
|
|
auto_maybe_mbim = TRUE;
|
|
} else if (!g_strcmp0 (type, "QMI")) {
|
|
mm_obj_dbg (self, "port may be QMI based on the wwan type attribute");
|
|
auto_maybe_qmi = TRUE;
|
|
} else if (!g_strcmp0 (type, "QCDM")) {
|
|
mm_obj_dbg (self, "port may be QCDM based on the wwan type attribute");
|
|
auto_maybe_qcdm = TRUE;
|
|
} else if (!g_strcmp0 (type, "FIREHOSE")) {
|
|
mm_obj_dbg (self, "port may be FIREHOSE based on the wwan type attribute");
|
|
auto_ignored = TRUE;
|
|
}
|
|
}
|
|
/* Linux 5.13 does not have 'type' attribute yet, match kernel name instead */
|
|
else {
|
|
const gchar *name;
|
|
|
|
name = mm_kernel_device_get_name (self->priv->port);
|
|
if (g_str_has_suffix (name, "AT")) {
|
|
mm_obj_dbg (self, "port may be AT based on the wwan device name");
|
|
auto_maybe_at = TRUE;
|
|
} else if (g_str_has_suffix (name, "MBIM")) {
|
|
mm_obj_dbg (self, "port may be MBIM based on the wwan device name");
|
|
auto_maybe_mbim = TRUE;
|
|
} else if (g_str_has_suffix (name, "QMI")) {
|
|
mm_obj_dbg (self, "port may be QMI based on the wwan device name");
|
|
auto_maybe_qmi = TRUE;
|
|
} else if (g_str_has_suffix (name, "QCDM")) {
|
|
mm_obj_dbg (self, "port may be QCDM based on the wwan device name");
|
|
auto_maybe_qcdm = TRUE;
|
|
} else if (g_str_has_suffix (name, "FIREHOSE")) {
|
|
mm_obj_dbg (self, "port may be FIREHOSE based on the wwan device name");
|
|
auto_ignored = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_assert ((auto_maybe_qmi + auto_maybe_mbim + auto_maybe_at + auto_maybe_qcdm + auto_ignored) <= 1);
|
|
|
|
#define PROCESS_AUTO_HINTS(TYPE, FIELD) do { \
|
|
if (auto_##FIELD) { \
|
|
if (n_udev_hints > 0 && !self->priv->FIELD) \
|
|
mm_obj_warn (self, "overriding type in possible " TYPE " port with udev tag: %s", udev_tags->str); \
|
|
else \
|
|
self->priv->FIELD = TRUE; \
|
|
} \
|
|
} while (0)
|
|
|
|
PROCESS_AUTO_HINTS ("QMI", maybe_qmi);
|
|
PROCESS_AUTO_HINTS ("MBIM", maybe_mbim);
|
|
PROCESS_AUTO_HINTS ("AT", maybe_at);
|
|
PROCESS_AUTO_HINTS ("QCDM", maybe_qcdm);
|
|
|
|
#undef PROCESS_AUTO_HINTS
|
|
|
|
mm_obj_dbg (self, "port type hints loaded: AT %s, QMI %s, MBIM %s, QCDM %s, AUDIO %s, GPS %s",
|
|
self->priv->maybe_at ? "yes" : "no",
|
|
self->priv->maybe_qmi ? "yes" : "no",
|
|
self->priv->maybe_mbim ? "yes" : "no",
|
|
self->priv->maybe_qcdm ? "yes" : "no",
|
|
self->priv->is_audio ? "yes" : "no",
|
|
self->priv->is_gps ? "yes" : "no");
|
|
|
|
/* Regardless of the type, the port may be ignored */
|
|
if (mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_IGNORE)) {
|
|
mm_obj_dbg (self, "port is ignored via udev tag");
|
|
self->priv->is_ignored = TRUE;
|
|
} else if (auto_ignored) {
|
|
mm_obj_dbg (self, "port is ignored via automatic rules");
|
|
self->priv->is_ignored = TRUE;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gchar *
|
|
log_object_build_id (MMLogObject *_self)
|
|
{
|
|
MMPortProbe *self;
|
|
|
|
self = MM_PORT_PROBE (_self);
|
|
return g_strdup_printf ("%s/probe", mm_kernel_device_get_name (self->priv->port));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMPortProbe *
|
|
mm_port_probe_new (MMDevice *device,
|
|
MMKernelDevice *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);
|
|
initialize_port_type_hints (self);
|
|
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
|
|
log_object_iface_init (MMLogObjectInterface *iface)
|
|
{
|
|
iface->build_id = log_object_build_id;
|
|
}
|
|
|
|
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",
|
|
"kernel device object of the port",
|
|
MM_TYPE_KERNEL_DEVICE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_PORT, properties[PROP_PORT]);
|
|
}
|