base_modem_invalid_idle() needs to keep a reference to the MMBaseModem until its done. But if the modem is cancelled during removal right before we're trying to forget about it, keeping that reference can prevent everything getting cleaned up by MMBaseModem's dispose() until after the mainloop has run again and all our removal operations are supposedly complete. MMDevice uses an explicit dispose() trigger that mitigates most of this (and requires it for other reasons, like breaking reference cycles between the modem and it's various child classes) but it's somewhat error-prone to keep the modem around if we don't really need to. Signed-off-by: Dan Williams <dan@ioncontrol.co>
2751 lines
89 KiB
C
2751 lines
89 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 Google, Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <ModemManager-tags.h>
|
|
|
|
#include <mm-errors-types.h>
|
|
#include <mm-gdbus-modem.h>
|
|
|
|
#include "mm-context.h"
|
|
#include "mm-base-modem.h"
|
|
#if defined WITH_QRTR
|
|
#include "mm-kernel-device-qrtr.h"
|
|
#endif
|
|
|
|
#include "mm-log-object.h"
|
|
#include "mm-port-enums-types.h"
|
|
#include "mm-daemon-enums-types.h"
|
|
#include "mm-serial-parsers.h"
|
|
#include "mm-modem-helpers.h"
|
|
|
|
static void log_object_iface_init (MMLogObjectInterface *iface);
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MMBaseModem, mm_base_modem, MM_GDBUS_TYPE_OBJECT_SKELETON,
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
|
|
|
|
/* If we get 10 consecutive timeouts in a serial port, we consider the modem
|
|
* invalid and we request re-probing. */
|
|
#define DEFAULT_MAX_TIMEOUTS 10
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_VALID,
|
|
PROP_MAX_TIMEOUTS,
|
|
PROP_DEVICE,
|
|
PROP_PHYSDEV,
|
|
PROP_DRIVERS,
|
|
PROP_PLUGIN,
|
|
PROP_VENDOR_ID,
|
|
PROP_PRODUCT_ID,
|
|
PROP_SUBSYSTEM_VENDOR_ID,
|
|
PROP_SUBSYSTEM_DEVICE_ID,
|
|
|
|
PROP_CONNECTION,
|
|
PROP_REPROBE,
|
|
PROP_DATA_NET_SUPPORTED,
|
|
PROP_DATA_TTY_SUPPORTED,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_LINK_PORT_GRABBED,
|
|
SIGNAL_LINK_PORT_RELEASED,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
static guint signals[SIGNAL_LAST];
|
|
|
|
struct _MMBaseModemPrivate {
|
|
/* The connection to the system bus */
|
|
GDBusConnection *connection;
|
|
guint dbus_id;
|
|
|
|
/* Modem-wide cancellable. If it ever gets cancelled, no further operations
|
|
* should be done by the modem. */
|
|
GCancellable *cancellable;
|
|
gulong invalid_if_cancelled;
|
|
guint invalid_from_idle;
|
|
|
|
gchar *device;
|
|
gchar *physdev;
|
|
gchar **drivers;
|
|
gchar *plugin;
|
|
|
|
guint vendor_id;
|
|
guint product_id;
|
|
guint subsystem_vendor_id;
|
|
guint subsystem_device_id;
|
|
|
|
gboolean hotplugged;
|
|
gboolean valid;
|
|
gboolean reprobe;
|
|
gboolean torn_down;
|
|
|
|
guint max_timeouts;
|
|
|
|
/* The authorization provider */
|
|
MMAuthProvider *authp;
|
|
GCancellable *authp_cancellable;
|
|
|
|
GHashTable *ports;
|
|
MMPortSerialAt *primary;
|
|
MMPortSerialAt *secondary;
|
|
MMPortSerialQcdm *qcdm;
|
|
|
|
GList *data;
|
|
gboolean data_net_supported;
|
|
gboolean data_tty_supported;
|
|
|
|
/* GPS-enabled modems will have an AT port for control, and a raw serial
|
|
* port to receive all GPS traces */
|
|
MMPortSerialAt *gps_control;
|
|
MMPortSerialGps *gps;
|
|
|
|
/* Some audio-capable devices will have a port for audio specifically */
|
|
MMPortSerial *audio;
|
|
|
|
#if defined WITH_QMI
|
|
/* QMI ports */
|
|
GList *qmi;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
/* MBIM ports */
|
|
GList *mbim;
|
|
#endif
|
|
|
|
/* Additional port links grabbed after having
|
|
* organized ports */
|
|
GHashTable *link_ports;
|
|
|
|
/* Scheduled operations. The forbidden_forever flag will be set to TRUE
|
|
* if an "override" operation is requested, and will never be set to FALSE
|
|
* back, it is expected the modem object will eventually be removed after
|
|
* the operation has finished,
|
|
*/
|
|
GList *scheduled_operations;
|
|
gboolean scheduled_operations_forbidden_forever;
|
|
};
|
|
|
|
guint
|
|
mm_base_modem_get_dbus_id (MMBaseModem *self)
|
|
{
|
|
return self->priv->dbus_id;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void
|
|
port_removed_cb (MMPort *port,
|
|
MMBaseModem *self)
|
|
{
|
|
/* We have to do a full re-probe here because simply reopening the device
|
|
* and restarting proxy would leave us without proper notifications. */
|
|
mm_obj_msg (self, "port '%s' no longer controllable, reprobing",
|
|
mm_port_get_device (MM_PORT (port)));
|
|
self->priv->reprobe = TRUE;
|
|
g_cancellable_cancel (self->priv->cancellable);
|
|
}
|
|
|
|
static void
|
|
port_timed_out_cb (MMPort *port,
|
|
guint n_consecutive_timeouts,
|
|
MMBaseModem *self)
|
|
{
|
|
/* If reached the maximum number of timeouts, invalidate modem */
|
|
if (n_consecutive_timeouts >= self->priv->max_timeouts) {
|
|
mm_obj_err (self, "port %s timed out %u consecutive times, marking modem as invalid",
|
|
mm_port_get_device (MM_PORT (port)),
|
|
n_consecutive_timeouts);
|
|
g_cancellable_cancel (self->priv->cancellable);
|
|
return;
|
|
}
|
|
|
|
if (n_consecutive_timeouts > 1)
|
|
mm_obj_warn (self, "port %s timed out %u consecutive times",
|
|
mm_port_get_device (MM_PORT (port)),
|
|
n_consecutive_timeouts);
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_ignored_port (MMBaseModem *self,
|
|
const gchar *name)
|
|
{
|
|
return MM_PORT (g_object_new (MM_TYPE_PORT,
|
|
MM_PORT_DEVICE, name,
|
|
MM_PORT_TYPE, MM_PORT_TYPE_IGNORED,
|
|
NULL));
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_net_port (MMBaseModem *self,
|
|
const gchar *name)
|
|
{
|
|
return MM_PORT (mm_port_net_new (name));
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_tty_port (MMBaseModem *self,
|
|
const gchar *name,
|
|
MMKernelDevice *kernel_device,
|
|
MMPortType ptype)
|
|
{
|
|
MMPort *port = NULL;
|
|
const gchar *flow_control_tag;
|
|
|
|
if (ptype == MM_PORT_TYPE_QCDM)
|
|
port = MM_PORT (mm_port_serial_qcdm_new (name, MM_PORT_SUBSYS_TTY));
|
|
else if (ptype == MM_PORT_TYPE_GPS)
|
|
port = MM_PORT (mm_port_serial_gps_new (name));
|
|
else if (ptype == MM_PORT_TYPE_AUDIO)
|
|
port = MM_PORT (mm_port_serial_new (name, ptype));
|
|
else if (ptype == MM_PORT_TYPE_AT)
|
|
port = MM_PORT (mm_port_serial_at_new (name, MM_PORT_SUBSYS_TTY));
|
|
|
|
if (!port)
|
|
return NULL;
|
|
|
|
/* Optional user-provided baudrate */
|
|
if (mm_kernel_device_has_property (kernel_device, ID_MM_TTY_BAUDRATE))
|
|
g_object_set (port,
|
|
MM_PORT_SERIAL_BAUD, mm_kernel_device_get_property_as_int (kernel_device, ID_MM_TTY_BAUDRATE),
|
|
NULL);
|
|
|
|
/* Optional user-provided flow control */
|
|
flow_control_tag = mm_kernel_device_get_property (kernel_device, ID_MM_TTY_FLOW_CONTROL);
|
|
if (flow_control_tag) {
|
|
MMFlowControl flow_control;
|
|
g_autoptr(GError) inner_error = NULL;
|
|
|
|
flow_control = mm_flow_control_from_string (flow_control_tag, &inner_error);
|
|
if (flow_control != MM_FLOW_CONTROL_UNKNOWN)
|
|
g_object_set (port,
|
|
MM_PORT_SERIAL_FLOW_CONTROL, flow_control,
|
|
NULL);
|
|
else
|
|
mm_obj_warn (self, "unsupported flow control settings in port %s: %s",
|
|
name, inner_error->message);
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_usbmisc_port (MMBaseModem *self,
|
|
const gchar *name,
|
|
MMPortType ptype)
|
|
{
|
|
#if defined WITH_QMI
|
|
if (ptype == MM_PORT_TYPE_QMI)
|
|
return MM_PORT (mm_port_qmi_new (name, MM_PORT_SUBSYS_USBMISC));
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
if (ptype == MM_PORT_TYPE_MBIM)
|
|
return MM_PORT (mm_port_mbim_new (name, MM_PORT_SUBSYS_USBMISC));
|
|
#endif
|
|
if (ptype == MM_PORT_TYPE_AT)
|
|
return MM_PORT (mm_port_serial_at_new (name, MM_PORT_SUBSYS_USBMISC));
|
|
return NULL;
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_rpmsg_port (MMBaseModem *self,
|
|
const gchar *name,
|
|
MMPortType ptype)
|
|
{
|
|
#if defined WITH_QMI
|
|
if (ptype == MM_PORT_TYPE_QMI)
|
|
return MM_PORT (mm_port_qmi_new (name, MM_PORT_SUBSYS_RPMSG));
|
|
#endif
|
|
if (ptype == MM_PORT_TYPE_AT)
|
|
return MM_PORT (mm_port_serial_at_new (name, MM_PORT_SUBSYS_RPMSG));
|
|
return NULL;
|
|
}
|
|
|
|
#if defined WITH_QRTR
|
|
static MMPort *
|
|
base_modem_create_qrtr_port (MMBaseModem *self,
|
|
const gchar *name,
|
|
MMKernelDevice *kernel_device,
|
|
MMPortType ptype)
|
|
{
|
|
if (ptype == MM_PORT_TYPE_QMI) {
|
|
g_autoptr(QrtrNode) node = NULL;
|
|
|
|
g_assert (MM_IS_KERNEL_DEVICE_QRTR (kernel_device));
|
|
node = mm_kernel_device_qrtr_get_node (MM_KERNEL_DEVICE_QRTR (kernel_device));
|
|
return MM_PORT (mm_port_qmi_new_from_node (name, node));
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static MMPort *
|
|
base_modem_create_wwan_port (MMBaseModem *self,
|
|
const gchar *name,
|
|
MMPortType ptype)
|
|
{
|
|
#if defined WITH_QMI
|
|
if (ptype == MM_PORT_TYPE_QMI)
|
|
return MM_PORT (mm_port_qmi_new (name, MM_PORT_SUBSYS_WWAN));
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (ptype == MM_PORT_TYPE_MBIM)
|
|
return MM_PORT (mm_port_mbim_new (name, MM_PORT_SUBSYS_WWAN));
|
|
#endif
|
|
|
|
if (ptype == MM_PORT_TYPE_QCDM)
|
|
return MM_PORT (mm_port_serial_qcdm_new (name, MM_PORT_SUBSYS_WWAN));
|
|
|
|
if (ptype == MM_PORT_TYPE_AT)
|
|
return MM_PORT (mm_port_serial_at_new (name, MM_PORT_SUBSYS_WWAN));
|
|
|
|
if (ptype == MM_PORT_TYPE_XMMRPC)
|
|
return MM_PORT (g_object_new (MM_TYPE_PORT,
|
|
MM_PORT_DEVICE, name,
|
|
MM_PORT_SUBSYS, MM_PORT_SUBSYS_WWAN,
|
|
MM_PORT_TYPE, MM_PORT_TYPE_XMMRPC,
|
|
NULL));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_create_virtual_port (MMBaseModem *self,
|
|
const gchar *name)
|
|
{
|
|
return MM_PORT (mm_port_serial_at_new (name, MM_PORT_SUBSYS_UNIX));
|
|
}
|
|
|
|
static MMPort *
|
|
base_modem_internal_grab_port (MMBaseModem *self,
|
|
MMKernelDevice *kernel_device,
|
|
gboolean link_port,
|
|
MMPortType ptype,
|
|
MMPortSerialAtFlag at_pflags,
|
|
GError **error)
|
|
{
|
|
MMPort *port;
|
|
const gchar *subsys;
|
|
const gchar *name;
|
|
g_autofree gchar *key = NULL;
|
|
gboolean port_monitoring = FALSE;
|
|
|
|
subsys = mm_kernel_device_get_subsystem (kernel_device);
|
|
name = mm_kernel_device_get_name (kernel_device);
|
|
|
|
if (!self->priv->ports || (link_port && !self->priv->link_ports)) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Cannot add port '%s/%s', no ports table", subsys, name);
|
|
return NULL;
|
|
}
|
|
|
|
/* Check whether we already have it stored */
|
|
key = g_strdup_printf ("%s%s", subsys, name);
|
|
port = g_hash_table_lookup (self->priv->ports, key);
|
|
if (port) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot add port '%s/%s', already exists", subsys, name);
|
|
return NULL;
|
|
}
|
|
|
|
g_assert (MM_BASE_MODEM_GET_CLASS (self)->create_tty_port);
|
|
g_assert (MM_BASE_MODEM_GET_CLASS (self)->create_usbmisc_port);
|
|
g_assert (MM_BASE_MODEM_GET_CLASS (self)->create_wwan_port);
|
|
|
|
/* Explicitly ignored ports, grab them but explicitly flag them as ignored
|
|
* right away, all the same way (i.e. regardless of subsystem). */
|
|
if (ptype == MM_PORT_TYPE_IGNORED)
|
|
port = base_modem_create_ignored_port (self, name);
|
|
else if (g_str_equal (subsys, "net"))
|
|
port = base_modem_create_net_port (self, name);
|
|
else if (g_str_equal (subsys, "tty"))
|
|
port = MM_BASE_MODEM_GET_CLASS (self)->create_tty_port (self, name, kernel_device, ptype);
|
|
else if (g_str_equal (subsys, "usbmisc"))
|
|
port = MM_BASE_MODEM_GET_CLASS (self)->create_usbmisc_port (self, name, ptype);
|
|
else if (g_str_equal (subsys, "rpmsg"))
|
|
port = base_modem_create_rpmsg_port (self, name, ptype);
|
|
#if defined WITH_QRTR
|
|
else if (g_str_equal (subsys, "qrtr"))
|
|
port = base_modem_create_qrtr_port (self, name, kernel_device, ptype);
|
|
#endif
|
|
else if (g_str_equal (subsys, "virtual"))
|
|
port = base_modem_create_virtual_port (self, name);
|
|
else if (g_str_equal (subsys, "wwan"))
|
|
port = MM_BASE_MODEM_GET_CLASS (self)->create_wwan_port (self, name, ptype);
|
|
|
|
if (!port) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot add port '%s/%s', unhandled port type", subsys, name);
|
|
return NULL;
|
|
}
|
|
|
|
/* Setup consecutive ports and removal watchers in all control ports */
|
|
if (MM_IS_PORT_SERIAL_AT (port)) {
|
|
mm_obj_dbg (port, "port monitoring enabled in AT port");
|
|
port_monitoring = TRUE;
|
|
} else if (MM_IS_PORT_SERIAL_QCDM (port)) {
|
|
mm_obj_dbg (port, "port monitoring enabled in QCDM port");
|
|
port_monitoring = TRUE;
|
|
}
|
|
#if defined WITH_QMI
|
|
else if (MM_IS_PORT_QMI (port)) {
|
|
mm_obj_dbg (port, "port monitoring enabled in QMI port");
|
|
port_monitoring = TRUE;
|
|
}
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
else if (MM_IS_PORT_MBIM (port)) {
|
|
mm_obj_dbg (port, "port monitoring enabled in MBIM port");
|
|
port_monitoring = TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (port_monitoring) {
|
|
if (self->priv->max_timeouts > 0)
|
|
g_signal_connect (port,
|
|
MM_PORT_SIGNAL_TIMED_OUT,
|
|
G_CALLBACK (port_timed_out_cb),
|
|
self);
|
|
g_signal_connect (port,
|
|
MM_PORT_SIGNAL_REMOVED,
|
|
G_CALLBACK (port_removed_cb),
|
|
self);
|
|
}
|
|
|
|
/* Store kernel device */
|
|
g_object_set (port, MM_PORT_KERNEL_DEVICE, kernel_device, NULL);
|
|
|
|
/* Set owner ID */
|
|
mm_log_object_set_owner_id (MM_LOG_OBJECT (port), mm_log_object_get_id (MM_LOG_OBJECT (self)));
|
|
|
|
/* Common setup for all AT ports from all subsystems */
|
|
if (MM_IS_PORT_SERIAL_AT (port)) {
|
|
mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (port),
|
|
mm_serial_parser_v1_parse,
|
|
mm_serial_parser_v1_new (),
|
|
mm_serial_parser_v1_destroy);
|
|
/* Prefer plugin-provided flags to the generic ones */
|
|
if (at_pflags == MM_PORT_SERIAL_AT_FLAG_NONE) {
|
|
if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PRIMARY)) {
|
|
mm_obj_dbg (port, "AT port flagged as primary");
|
|
at_pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
|
|
} else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_SECONDARY)) {
|
|
mm_obj_dbg (port, "AT port flagged as secondary");
|
|
at_pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
|
|
} else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PPP)) {
|
|
mm_obj_dbg (port, "AT port flagged as PPP");
|
|
at_pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
|
|
}
|
|
|
|
/* Additionally, the ports may also be flagged as GPS control explicitly, if there is
|
|
* one specific port to be used for that purpose */
|
|
if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_GPS_CONTROL)) {
|
|
mm_obj_dbg (port, "AT port flagged as GPS control");
|
|
at_pflags = MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL;
|
|
}
|
|
}
|
|
/* The plugin may specify NONE_NO_GENERIC to avoid the generic
|
|
* port type hints from being applied. */
|
|
if (at_pflags == MM_PORT_SERIAL_AT_FLAG_NONE_NO_GENERIC)
|
|
at_pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
|
|
|
|
mm_port_serial_at_set_flags (MM_PORT_SERIAL_AT (port), at_pflags);
|
|
}
|
|
|
|
/* Add it to the tracking HT.
|
|
* Note: 'key' and 'port' now owned by the HT. */
|
|
if (link_port)
|
|
g_hash_table_insert (self->priv->link_ports, g_steal_pointer (&key), port);
|
|
else
|
|
g_hash_table_insert (self->priv->ports, g_steal_pointer (&key), port);
|
|
return port;
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_grab_port (MMBaseModem *self,
|
|
MMKernelDevice *kernel_device,
|
|
MMPortType ptype,
|
|
MMPortSerialAtFlag at_pflags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GError) inner_error = NULL;
|
|
|
|
if (!base_modem_internal_grab_port (self, kernel_device, FALSE, ptype, at_pflags, &inner_error)) {
|
|
/* If the port was REQUIRED via udev tags and we failed to grab it, we will report
|
|
* a fatal error. */
|
|
if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_REQUIRED)) {
|
|
mm_obj_err (self, "required port '%s/%s' failed to be grabbed",
|
|
mm_kernel_device_get_subsystem (kernel_device),
|
|
mm_kernel_device_get_name (kernel_device));
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Required port failed to be grabbed");
|
|
} else
|
|
g_propagate_error (error, g_steal_pointer (&inner_error));
|
|
return FALSE;
|
|
}
|
|
|
|
mm_obj_dbg (self, "port '%s/%s' grabbed",
|
|
mm_kernel_device_get_subsystem (kernel_device),
|
|
mm_kernel_device_get_name (kernel_device));
|
|
return TRUE;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
gboolean
|
|
mm_base_modem_grab_link_port (MMBaseModem *self,
|
|
MMKernelDevice *kernel_device,
|
|
GError **error)
|
|
{
|
|
const gchar *subsystem;
|
|
const gchar *name;
|
|
MMPort *port;
|
|
|
|
/* To simplify things, we only support NET link ports at this point */
|
|
subsystem = mm_kernel_device_get_subsystem (kernel_device);
|
|
name = mm_kernel_device_get_name (kernel_device);
|
|
|
|
if (!g_str_equal (subsystem, "net")) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot add port '%s/%s', unexpected link port subsystem", subsystem, name);
|
|
return FALSE;
|
|
}
|
|
|
|
/* all the newly added link ports will NOT be 'organized'; i.e. they won't
|
|
* be available as 'data ports' in the modem, but they can be looked up
|
|
* by name */
|
|
port = base_modem_internal_grab_port (self,
|
|
kernel_device,
|
|
TRUE,
|
|
MM_PORT_TYPE_NET,
|
|
MM_PORT_SERIAL_AT_FLAG_NONE,
|
|
error);
|
|
if (!port)
|
|
return FALSE;
|
|
|
|
|
|
mm_obj_dbg (self, "link port '%s/%s' grabbed", subsystem, name);
|
|
g_signal_emit (self, signals[SIGNAL_LINK_PORT_GRABBED], 0, port);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_release_link_port (MMBaseModem *self,
|
|
const gchar *subsystem,
|
|
const gchar *name,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *key = NULL;
|
|
MMPort *port;
|
|
|
|
if (!self->priv->link_ports) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Cannot release link port '%s/%s', no link ports table", subsystem, name);
|
|
return FALSE;
|
|
}
|
|
|
|
key = g_strdup_printf ("%s%s", subsystem, name);
|
|
port = g_hash_table_lookup (self->priv->link_ports, key);
|
|
if (!port) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot release link port '%s/%s', not grabbed", subsystem, name);
|
|
return FALSE;
|
|
}
|
|
|
|
/* make sure the port object is valid during the port release signal */
|
|
g_object_ref (port);
|
|
g_hash_table_remove (self->priv->link_ports, key);
|
|
mm_obj_dbg (self, "link port '%s/%s' released", subsystem, name);
|
|
g_signal_emit (self, signals[SIGNAL_LINK_PORT_RELEASED], 0, port);
|
|
g_object_unref (port);
|
|
return TRUE;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
typedef struct {
|
|
gchar *name;
|
|
gulong link_port_grabbed_id;
|
|
guint timeout_id;
|
|
} WaitLinkPortContext;
|
|
|
|
static void
|
|
wait_link_port_context_free (WaitLinkPortContext *ctx)
|
|
{
|
|
g_assert (!ctx->link_port_grabbed_id);
|
|
g_assert (!ctx->timeout_id);
|
|
g_free (ctx->name);
|
|
g_slice_free (WaitLinkPortContext, ctx);
|
|
}
|
|
|
|
MMPort *
|
|
mm_base_modem_wait_link_port_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static gboolean
|
|
wait_link_port_timeout_cb (GTask *task)
|
|
{
|
|
WaitLinkPortContext *ctx;
|
|
MMBaseModem *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->timeout_id = 0;
|
|
g_signal_handler_disconnect (self, ctx->link_port_grabbed_id);
|
|
ctx->link_port_grabbed_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
|
|
"Timed out waiting for link port 'net/%s'",
|
|
ctx->name);
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
wait_link_port_grabbed_cb (MMBaseModem *self,
|
|
MMPort *link_port,
|
|
GTask *task)
|
|
{
|
|
WaitLinkPortContext *ctx;
|
|
MMPortSubsys link_port_subsystem;
|
|
const gchar *link_port_name;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
link_port_subsystem = mm_port_get_subsys (link_port);
|
|
link_port_name = mm_port_get_device (link_port);
|
|
|
|
if (link_port_subsystem != MM_PORT_SUBSYS_NET) {
|
|
mm_obj_warn (self, "unexpected link port subsystem grabbed: %s/%s",
|
|
mm_port_subsys_get_string (link_port_subsystem),
|
|
link_port_name);
|
|
return;
|
|
}
|
|
|
|
if (g_strcmp0 (link_port_name, ctx->name) != 0)
|
|
return;
|
|
|
|
/* we got it! */
|
|
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
g_signal_handler_disconnect (self, ctx->link_port_grabbed_id);
|
|
ctx->link_port_grabbed_id = 0;
|
|
|
|
g_task_return_pointer (task, g_object_ref (link_port), g_object_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_wait_link_port (MMBaseModem *self,
|
|
const gchar *subsystem,
|
|
const gchar *name,
|
|
guint timeout_ms,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
WaitLinkPortContext *ctx;
|
|
GTask *task;
|
|
g_autofree gchar *key = NULL;
|
|
MMPort *port;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (!g_str_equal (subsystem, "net")) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot wait for port '%s/%s', unexpected link port subsystem", subsystem, name);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!self->priv->link_ports) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Cannot wait for port '%s/%s', no link ports table", subsystem, name);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
key = g_strdup_printf ("%s%s", subsystem, name);
|
|
port = g_hash_table_lookup (self->priv->link_ports, key);
|
|
if (port) {
|
|
mm_obj_dbg (self, "no need to wait for port '%s/%s': already grabbed", subsystem, name);
|
|
g_task_return_pointer (task, g_object_ref (port), g_object_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_slice_new0 (WaitLinkPortContext);
|
|
ctx->name = g_strdup (name);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)wait_link_port_context_free);
|
|
|
|
/* task ownership shared between timeout and signal handler */
|
|
ctx->timeout_id = g_timeout_add (timeout_ms,
|
|
(GSourceFunc) wait_link_port_timeout_cb,
|
|
task);
|
|
ctx->link_port_grabbed_id = g_signal_connect (self,
|
|
MM_BASE_MODEM_SIGNAL_LINK_PORT_GRABBED,
|
|
G_CALLBACK (wait_link_port_grabbed_cb),
|
|
task);
|
|
|
|
mm_obj_dbg (self, "waiting for port '%s/%s'...", subsystem, name);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* Common support to perform state update/sync operations with the base modem. */
|
|
|
|
typedef enum {
|
|
STATE_OPERATION_TYPE_INITIALIZE,
|
|
STATE_OPERATION_TYPE_ENABLE,
|
|
STATE_OPERATION_TYPE_DISABLE,
|
|
#if defined WITH_SUSPEND_RESUME
|
|
STATE_OPERATION_TYPE_SYNC,
|
|
#endif
|
|
} StateOperationType;
|
|
|
|
typedef struct {
|
|
StateOperation operation;
|
|
StateOperationFinish operation_finish;
|
|
gssize operation_id;
|
|
} StateOperationContext;
|
|
|
|
static void
|
|
state_operation_context_free (StateOperationContext *ctx)
|
|
{
|
|
g_assert (ctx->operation_id < 0);
|
|
g_slice_free (StateOperationContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
state_operation_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
state_operation_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
StateOperationContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->operation_id >= 0) {
|
|
mm_base_modem_operation_unlock (self, ctx->operation_id);
|
|
ctx->operation_id = (gssize) -1;
|
|
}
|
|
|
|
if (!ctx->operation_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
state_operation_run (GTask *task)
|
|
{
|
|
MMBaseModem *self;
|
|
StateOperationContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->operation (self,
|
|
self->priv->cancellable,
|
|
(GAsyncReadyCallback) state_operation_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
lock_before_state_operation_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
StateOperationContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->operation_id = mm_base_modem_operation_lock_finish (self, res, &error);
|
|
if (ctx->operation_id < 0) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
state_operation_run (task);
|
|
}
|
|
|
|
static void
|
|
state_operation (MMBaseModem *self,
|
|
StateOperationType operation_type,
|
|
MMBaseModemOperationLock operation_lock,
|
|
MMBaseModemOperationPriority operation_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
StateOperationContext *ctx;
|
|
gboolean optional;
|
|
const gchar *operation_description;
|
|
|
|
ctx = g_slice_new0 (StateOperationContext);
|
|
ctx->operation_id = (gssize) -1;
|
|
|
|
/* configure operation to run */
|
|
switch (operation_type) {
|
|
case STATE_OPERATION_TYPE_INITIALIZE:
|
|
operation_description = "initialization";
|
|
optional = FALSE;
|
|
ctx->operation = MM_BASE_MODEM_GET_CLASS (self)->initialize;
|
|
ctx->operation_finish = MM_BASE_MODEM_GET_CLASS (self)->initialize_finish;
|
|
break;
|
|
case STATE_OPERATION_TYPE_ENABLE:
|
|
operation_description = "enabling";
|
|
optional = FALSE;
|
|
ctx->operation = MM_BASE_MODEM_GET_CLASS (self)->enable;
|
|
ctx->operation_finish = MM_BASE_MODEM_GET_CLASS (self)->enable_finish;
|
|
break;
|
|
case STATE_OPERATION_TYPE_DISABLE:
|
|
operation_description = "disabling";
|
|
optional = FALSE;
|
|
ctx->operation = MM_BASE_MODEM_GET_CLASS (self)->disable;
|
|
ctx->operation_finish = MM_BASE_MODEM_GET_CLASS (self)->disable_finish;
|
|
break;
|
|
#if defined WITH_SUSPEND_RESUME
|
|
case STATE_OPERATION_TYPE_SYNC:
|
|
operation_description = "sync";
|
|
optional = TRUE;
|
|
ctx->operation = MM_BASE_MODEM_GET_CLASS (self)->sync;
|
|
ctx->operation_finish = MM_BASE_MODEM_GET_CLASS (self)->sync_finish;
|
|
break;
|
|
#endif
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) state_operation_context_free);
|
|
|
|
if (optional && (!ctx->operation || !ctx->operation_finish)) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Unsupported");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
g_assert (ctx->operation && ctx->operation_finish);
|
|
|
|
if (operation_lock == MM_BASE_MODEM_OPERATION_LOCK_ALREADY_ACQUIRED) {
|
|
state_operation_run (task);
|
|
return;
|
|
}
|
|
|
|
g_assert (operation_lock == MM_BASE_MODEM_OPERATION_LOCK_REQUIRED);
|
|
mm_base_modem_operation_lock (self,
|
|
operation_priority,
|
|
operation_description,
|
|
(GAsyncReadyCallback) lock_before_state_operation_ready,
|
|
task);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
#if defined WITH_SUSPEND_RESUME
|
|
|
|
gboolean
|
|
mm_base_modem_sync_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return state_operation_finish (self, res, error);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_sync (MMBaseModem *self,
|
|
MMBaseModemOperationLock operation_lock,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
state_operation (self,
|
|
STATE_OPERATION_TYPE_SYNC,
|
|
operation_lock,
|
|
MM_BASE_MODEM_OPERATION_PRIORITY_DEFAULT,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
#endif /* WITH_SUSPEND_RESUME */
|
|
|
|
/******************************************************************************/
|
|
|
|
gboolean
|
|
mm_base_modem_disable_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return state_operation_finish (self, res, error);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_disable (MMBaseModem *self,
|
|
MMBaseModemOperationLock operation_lock,
|
|
MMBaseModemOperationPriority operation_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
state_operation (self,
|
|
STATE_OPERATION_TYPE_DISABLE,
|
|
operation_lock,
|
|
operation_priority,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
gboolean
|
|
mm_base_modem_enable_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return state_operation_finish (self, res, error);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_enable (MMBaseModem *self,
|
|
MMBaseModemOperationLock operation_lock,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
state_operation (self,
|
|
STATE_OPERATION_TYPE_ENABLE,
|
|
operation_lock,
|
|
MM_BASE_MODEM_OPERATION_PRIORITY_DEFAULT,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
gboolean
|
|
mm_base_modem_initialize_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return state_operation_finish (self, res, error);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_initialize (MMBaseModem *self,
|
|
MMBaseModemOperationLock operation_lock,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
state_operation (self,
|
|
STATE_OPERATION_TYPE_INITIALIZE,
|
|
operation_lock,
|
|
MM_BASE_MODEM_OPERATION_PRIORITY_DEFAULT,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void
|
|
mm_base_modem_set_hotplugged (MMBaseModem *self,
|
|
gboolean hotplugged)
|
|
{
|
|
g_return_if_fail (MM_IS_BASE_MODEM (self));
|
|
|
|
self->priv->hotplugged = hotplugged;
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_get_hotplugged (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), FALSE);
|
|
|
|
return self->priv->hotplugged;
|
|
}
|
|
|
|
void
|
|
mm_base_modem_set_valid (MMBaseModem *self,
|
|
gboolean new_valid)
|
|
{
|
|
g_return_if_fail (MM_IS_BASE_MODEM (self));
|
|
|
|
/* If validity changed OR if both old and new were invalid, notify. This
|
|
* last case is to cover failures during initialization. */
|
|
if (self->priv->valid != new_valid ||
|
|
!new_valid) {
|
|
self->priv->valid = new_valid;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALID]);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_base_modem_set_reprobe (MMBaseModem *self,
|
|
gboolean reprobe)
|
|
{
|
|
g_return_if_fail (MM_IS_BASE_MODEM (self));
|
|
|
|
self->priv->reprobe = reprobe;
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_get_reprobe (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), FALSE);
|
|
|
|
return self->priv->reprobe;
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_get_valid (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), FALSE);
|
|
|
|
return self->priv->valid;
|
|
}
|
|
|
|
GCancellable *
|
|
mm_base_modem_peek_cancellable (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->cancellable;
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_get_port_primary (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->primary ? g_object_ref (self->priv->primary) : NULL);
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_peek_port_primary (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->primary;
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_get_port_secondary (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->secondary ? g_object_ref (self->priv->secondary) : NULL);
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_peek_port_secondary (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->secondary;
|
|
}
|
|
|
|
MMPortSerialQcdm *
|
|
mm_base_modem_get_port_qcdm (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->qcdm ? g_object_ref (self->priv->qcdm) : NULL);
|
|
}
|
|
|
|
MMPortSerialQcdm *
|
|
mm_base_modem_peek_port_qcdm (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->qcdm;
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_get_port_gps_control (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->gps_control ? g_object_ref (self->priv->gps_control) : NULL);
|
|
}
|
|
|
|
MMPortSerialAt *
|
|
mm_base_modem_peek_port_gps_control (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->gps_control;
|
|
}
|
|
|
|
MMPortSerialGps *
|
|
mm_base_modem_get_port_gps (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->gps ? g_object_ref (self->priv->gps) : NULL);
|
|
}
|
|
|
|
MMPortSerialGps *
|
|
mm_base_modem_peek_port_gps (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->gps;
|
|
}
|
|
|
|
MMPortSerial *
|
|
mm_base_modem_get_port_audio (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (self->priv->audio ? g_object_ref (self->priv->audio) : NULL);
|
|
}
|
|
|
|
MMPortSerial *
|
|
mm_base_modem_peek_port_audio (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->audio;
|
|
}
|
|
|
|
MMPort *
|
|
mm_base_modem_get_best_data_port (MMBaseModem *self,
|
|
MMPortType type)
|
|
{
|
|
MMPort *port;
|
|
|
|
port = mm_base_modem_peek_best_data_port (self, type);
|
|
return (port ? g_object_ref (port) : NULL);
|
|
}
|
|
|
|
MMPort *
|
|
mm_base_modem_peek_best_data_port (MMBaseModem *self,
|
|
MMPortType type)
|
|
{
|
|
GList *l;
|
|
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
/* Return first not-connected data port */
|
|
for (l = self->priv->data; l; l = g_list_next (l)) {
|
|
if (!mm_port_get_connected ((MMPort *)l->data) &&
|
|
(mm_port_get_port_type ((MMPort *)l->data) == type ||
|
|
type == MM_PORT_TYPE_UNKNOWN)) {
|
|
return (MMPort *)l->data;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GList *
|
|
mm_base_modem_get_data_ports (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return g_list_copy_deep (self->priv->data, (GCopyFunc)g_object_ref, NULL);
|
|
}
|
|
|
|
GList *
|
|
mm_base_modem_peek_data_ports (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->data;
|
|
}
|
|
|
|
MMIfacePortAt *
|
|
mm_base_modem_get_best_at_port (MMBaseModem *self,
|
|
GError **error)
|
|
{
|
|
MMIfacePortAt *best;
|
|
|
|
best = mm_base_modem_peek_best_at_port (self, error);
|
|
return (best ? g_object_ref (best) : NULL);
|
|
}
|
|
|
|
MMIfacePortAt *
|
|
mm_base_modem_peek_best_at_port (MMBaseModem *self,
|
|
GError **error)
|
|
{
|
|
gboolean supported;
|
|
|
|
#if defined WITH_MBIM
|
|
/* Prefer an AT-capable MBIM port instead of a serial port */
|
|
if (self->priv->mbim) {
|
|
GList *l;
|
|
|
|
for (l = self->priv->mbim; l; l = g_list_next (l)) {
|
|
if (MM_IS_IFACE_PORT_AT (l->data) &&
|
|
mm_iface_port_at_check_support (MM_IFACE_PORT_AT (l->data), &supported, NULL) &&
|
|
supported)
|
|
return MM_IFACE_PORT_AT (l->data);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_QMI
|
|
/* Prefer an AT-capable QMI port instead of a serial port */
|
|
if (self->priv->qmi) {
|
|
GList *l;
|
|
|
|
for (l = self->priv->qmi; l; l = g_list_next (l)) {
|
|
if (MM_IS_IFACE_PORT_AT (l->data) &&
|
|
mm_iface_port_at_check_support (MM_IFACE_PORT_AT (l->data), &supported, NULL) &&
|
|
supported)
|
|
return MM_IFACE_PORT_AT (l->data);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Decide which port to use */
|
|
if (self->priv->primary && !mm_port_get_connected (MM_PORT (self->priv->primary))) {
|
|
g_assert (MM_IS_IFACE_PORT_AT (self->priv->primary));
|
|
g_assert (mm_iface_port_at_check_support (MM_IFACE_PORT_AT (self->priv->primary), &supported, NULL) && supported);
|
|
return MM_IFACE_PORT_AT (self->priv->primary);
|
|
}
|
|
|
|
/* If primary port is connected, check if we can get the secondary
|
|
* port */
|
|
if (self->priv->secondary && !mm_port_get_connected (MM_PORT (self->priv->secondary))) {
|
|
g_assert (MM_IS_IFACE_PORT_AT (self->priv->secondary));
|
|
g_assert (mm_iface_port_at_check_support (MM_IFACE_PORT_AT (self->priv->secondary), &supported, NULL) && supported);
|
|
return MM_IFACE_PORT_AT (self->priv->secondary);
|
|
}
|
|
|
|
/* Otherwise, we cannot get any port */
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"No AT port available to run command");
|
|
return NULL;
|
|
}
|
|
|
|
static gint
|
|
port_info_cmp (const MMModemPortInfo *a,
|
|
const MMModemPortInfo *b)
|
|
{
|
|
/* default to alphabetical sorting on the port name */
|
|
return g_strcmp0 (a->name, b->name);
|
|
}
|
|
|
|
MMModemPortInfo *
|
|
mm_base_modem_get_port_infos (MMBaseModem *self,
|
|
guint *n_port_infos)
|
|
{
|
|
GHashTableIter iter;
|
|
GArray *port_infos;
|
|
MMPort *port;
|
|
|
|
if (!self->priv->ports) {
|
|
*n_port_infos = 0;
|
|
return NULL;
|
|
}
|
|
|
|
*n_port_infos = g_hash_table_size (self->priv->ports);
|
|
port_infos = g_array_sized_new (FALSE, FALSE, sizeof (MMModemPortInfo), *n_port_infos);
|
|
g_hash_table_iter_init (&iter, self->priv->ports);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer)&port)) {
|
|
MMModemPortInfo port_info;
|
|
|
|
port_info.name = g_strdup (mm_port_get_device (port));
|
|
switch (mm_port_get_port_type (port)) {
|
|
case MM_PORT_TYPE_NET:
|
|
port_info.type = MM_MODEM_PORT_TYPE_NET;
|
|
break;
|
|
case MM_PORT_TYPE_AT:
|
|
port_info.type = MM_MODEM_PORT_TYPE_AT;
|
|
break;
|
|
case MM_PORT_TYPE_QCDM:
|
|
port_info.type = MM_MODEM_PORT_TYPE_QCDM;
|
|
break;
|
|
case MM_PORT_TYPE_GPS:
|
|
port_info.type = MM_MODEM_PORT_TYPE_GPS;
|
|
break;
|
|
case MM_PORT_TYPE_AUDIO:
|
|
port_info.type = MM_MODEM_PORT_TYPE_AUDIO;
|
|
break;
|
|
case MM_PORT_TYPE_QMI:
|
|
port_info.type = MM_MODEM_PORT_TYPE_QMI;
|
|
break;
|
|
case MM_PORT_TYPE_MBIM:
|
|
port_info.type = MM_MODEM_PORT_TYPE_MBIM;
|
|
break;
|
|
case MM_PORT_TYPE_XMMRPC:
|
|
port_info.type = MM_MODEM_PORT_TYPE_XMMRPC;
|
|
break;
|
|
case MM_PORT_TYPE_IGNORED:
|
|
port_info.type = MM_MODEM_PORT_TYPE_IGNORED;
|
|
break;
|
|
case MM_PORT_TYPE_UNKNOWN:
|
|
default:
|
|
port_info.type = MM_MODEM_PORT_TYPE_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
g_array_append_val (port_infos, port_info);
|
|
}
|
|
|
|
g_assert (*n_port_infos == port_infos->len);
|
|
g_array_sort (port_infos, (GCompareFunc) port_info_cmp);
|
|
return (MMModemPortInfo *) g_array_free (port_infos, FALSE);
|
|
}
|
|
|
|
static gint
|
|
port_cmp (MMPort *a,
|
|
MMPort *b)
|
|
{
|
|
/* default to alphabetical sorting on the port name */
|
|
return g_strcmp0 (mm_port_get_device (a), mm_port_get_device (b));
|
|
}
|
|
|
|
GList *
|
|
mm_base_modem_find_ports (MMBaseModem *self,
|
|
MMPortSubsys subsys,
|
|
MMPortType type)
|
|
{
|
|
GList *out = NULL;
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
gpointer key;
|
|
|
|
if (!self->priv->ports)
|
|
return NULL;
|
|
|
|
g_hash_table_iter_init (&iter, self->priv->ports);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
MMPort *port = MM_PORT (value);
|
|
|
|
if (subsys != MM_PORT_SUBSYS_UNKNOWN && mm_port_get_subsys (port) != subsys)
|
|
continue;
|
|
|
|
if (type != MM_PORT_TYPE_UNKNOWN && mm_port_get_port_type (port) != type)
|
|
continue;
|
|
|
|
out = g_list_append (out, g_object_ref (port));
|
|
}
|
|
|
|
return g_list_sort (out, (GCompareFunc) port_cmp);
|
|
}
|
|
|
|
static MMPort *
|
|
peek_port_in_ht (GHashTable *ht,
|
|
const gchar *name)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
gpointer key;
|
|
|
|
if (!ht)
|
|
return NULL;
|
|
|
|
g_hash_table_iter_init (&iter, ht);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
MMPort *port = MM_PORT (value);
|
|
|
|
if (g_str_equal (mm_port_get_device (port), name))
|
|
return port;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MMPort *
|
|
mm_base_modem_peek_port (MMBaseModem *self,
|
|
const gchar *name)
|
|
{
|
|
MMPort *found;
|
|
|
|
found = peek_port_in_ht (self->priv->ports, name);
|
|
if (!found)
|
|
found = peek_port_in_ht (self->priv->link_ports, name);
|
|
|
|
return found;
|
|
}
|
|
|
|
MMPort *
|
|
mm_base_modem_get_port (MMBaseModem *self,
|
|
const gchar *name)
|
|
{
|
|
MMPort *port;
|
|
|
|
port = mm_base_modem_peek_port (self, name);
|
|
return (port ? g_object_ref (port) : NULL);
|
|
}
|
|
|
|
static inline void
|
|
log_port (MMBaseModem *self,
|
|
MMPort *port,
|
|
const gchar *desc)
|
|
{
|
|
if (!port)
|
|
return;
|
|
|
|
mm_obj_info (self, "%s/%s: %s",
|
|
mm_port_subsys_get_string (mm_port_get_subsys (port)),
|
|
mm_port_get_device (port),
|
|
desc);
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_organize_ports (MMBaseModem *self,
|
|
GError **error)
|
|
{
|
|
GHashTableIter iter;
|
|
MMPort *candidate;
|
|
MMPortSerialAtFlag flags;
|
|
MMPortSerialAt *backup_primary = NULL;
|
|
MMPortSerialAt *primary = NULL;
|
|
MMPortSerialAt *secondary = NULL;
|
|
MMPortSerialAt *backup_secondary = NULL;
|
|
MMPortSerialQcdm *qcdm = NULL;
|
|
MMPortSerialAt *gps_control = NULL;
|
|
MMPortSerialGps *gps = NULL;
|
|
MMPortSerial *audio = NULL;
|
|
MMPortSerialAt *data_at_primary = NULL;
|
|
MMPort *xmmrpc = NULL;
|
|
GList *l;
|
|
/* These lists don't keep full references, so they should be
|
|
* g_list_free()-ed on error exits */
|
|
g_autoptr(GList) data_at = NULL;
|
|
g_autoptr(GList) data_net = NULL;
|
|
#if defined WITH_QMI
|
|
g_autoptr(GList) qmi = NULL;
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
g_autoptr(GList) mbim = NULL;
|
|
#endif
|
|
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), FALSE);
|
|
|
|
/* If ports have already been organized, just return success */
|
|
if (self->priv->primary)
|
|
return TRUE;
|
|
|
|
/* Ports table is created on init and removed on dispose(), not on
|
|
* finalize(), so there is a chance this may happen */
|
|
if (!self->priv->ports) {
|
|
g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"No ports table");
|
|
return FALSE;
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, self->priv->ports);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &candidate)) {
|
|
switch (mm_port_get_port_type (candidate)) {
|
|
|
|
case MM_PORT_TYPE_AT:
|
|
g_assert (MM_IS_PORT_SERIAL_AT (candidate));
|
|
flags = mm_port_serial_at_get_flags (MM_PORT_SERIAL_AT (candidate));
|
|
|
|
if (flags & MM_PORT_SERIAL_AT_FLAG_PRIMARY) {
|
|
if (!primary)
|
|
primary = MM_PORT_SERIAL_AT (candidate);
|
|
else if (!backup_primary) {
|
|
/* Just in case the plugin gave us more than one primary
|
|
* and no secondaries, treat additional primary ports as
|
|
* secondary.
|
|
*/
|
|
backup_primary = MM_PORT_SERIAL_AT (candidate);
|
|
}
|
|
}
|
|
|
|
if (flags & MM_PORT_SERIAL_AT_FLAG_PPP) {
|
|
if (!data_at_primary)
|
|
data_at_primary = MM_PORT_SERIAL_AT (candidate);
|
|
else
|
|
data_at = g_list_append (data_at, candidate);
|
|
}
|
|
|
|
/* Explicitly flagged secondary ports trump NONE ports for secondary */
|
|
if (flags & MM_PORT_SERIAL_AT_FLAG_SECONDARY) {
|
|
if (!secondary || !(mm_port_serial_at_get_flags (secondary) & MM_PORT_SERIAL_AT_FLAG_SECONDARY))
|
|
secondary = MM_PORT_SERIAL_AT (candidate);
|
|
}
|
|
|
|
if (flags & MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL) {
|
|
if (!gps_control)
|
|
gps_control = MM_PORT_SERIAL_AT (candidate);
|
|
}
|
|
|
|
/* Fallback secondary */
|
|
if (flags == MM_PORT_SERIAL_AT_FLAG_NONE) {
|
|
if (!secondary)
|
|
secondary = MM_PORT_SERIAL_AT (candidate);
|
|
else if (!backup_secondary)
|
|
backup_secondary = MM_PORT_SERIAL_AT (candidate);
|
|
}
|
|
break;
|
|
|
|
case MM_PORT_TYPE_QCDM:
|
|
g_assert (MM_IS_PORT_SERIAL_QCDM (candidate));
|
|
if (!qcdm)
|
|
qcdm = MM_PORT_SERIAL_QCDM (candidate);
|
|
break;
|
|
|
|
case MM_PORT_TYPE_NET:
|
|
data_net = g_list_append (data_net, candidate);
|
|
break;
|
|
|
|
case MM_PORT_TYPE_GPS:
|
|
g_assert (MM_IS_PORT_SERIAL_GPS (candidate));
|
|
if (!gps)
|
|
gps = MM_PORT_SERIAL_GPS (candidate);
|
|
break;
|
|
|
|
case MM_PORT_TYPE_AUDIO:
|
|
g_assert (MM_IS_PORT_SERIAL (candidate));
|
|
if (!audio)
|
|
audio = MM_PORT_SERIAL (candidate);
|
|
break;
|
|
|
|
case MM_PORT_TYPE_XMMRPC:
|
|
g_assert (MM_IS_PORT (candidate));
|
|
if (!xmmrpc)
|
|
xmmrpc = MM_PORT (candidate);
|
|
break;
|
|
|
|
#if defined WITH_QMI
|
|
case MM_PORT_TYPE_QMI:
|
|
qmi = g_list_append (qmi, candidate);
|
|
break;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
case MM_PORT_TYPE_MBIM:
|
|
mbim = g_list_append (mbim, candidate);
|
|
break;
|
|
#endif
|
|
|
|
case MM_PORT_TYPE_UNKNOWN:
|
|
case MM_PORT_TYPE_IGNORED:
|
|
#if !defined WITH_MBIM
|
|
case MM_PORT_TYPE_MBIM:
|
|
#endif
|
|
#if !defined WITH_QMI
|
|
case MM_PORT_TYPE_QMI:
|
|
#endif
|
|
default:
|
|
/* Ignore port */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!primary) {
|
|
/* Fall back to a secondary port if we didn't find a primary port */
|
|
if (secondary) {
|
|
primary = secondary;
|
|
secondary = NULL;
|
|
}
|
|
/* Fallback to a data port if no primary or secondary */
|
|
else if (data_at_primary) {
|
|
primary = data_at_primary;
|
|
data_at_primary = NULL;
|
|
}
|
|
else {
|
|
gboolean allow_modem_without_at_port = FALSE;
|
|
|
|
#if defined WITH_QMI
|
|
if (qmi)
|
|
allow_modem_without_at_port = TRUE;
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (mbim)
|
|
allow_modem_without_at_port = TRUE;
|
|
#endif
|
|
|
|
if (!allow_modem_without_at_port) {
|
|
g_set_error_literal (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to find primary AT port");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the plugin didn't give us any secondary ports, use any additional
|
|
* primary ports or backup secondary ports as secondary.
|
|
*/
|
|
if (!secondary)
|
|
secondary = backup_primary ? backup_primary : backup_secondary;
|
|
|
|
#if defined WITH_QMI
|
|
/* On QMI-based modems, we need to have at least a net port */
|
|
if (qmi && !data_net) {
|
|
g_set_error_literal (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to find a net port in the QMI modem");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
/* On MBIM-based modems, we need to have at least a net port */
|
|
if (mbim && !data_net) {
|
|
g_set_error_literal (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to find a net port in the MBIM modem");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/* Data port defaults to primary AT port */
|
|
if (primary && !data_at_primary)
|
|
data_at_primary = primary;
|
|
|
|
/* Reset flags on all ports; clear data port first since it might also
|
|
* be the primary or secondary port.
|
|
*/
|
|
if (data_at_primary)
|
|
mm_port_serial_at_set_flags (data_at_primary, MM_PORT_SERIAL_AT_FLAG_NONE);
|
|
if (primary)
|
|
mm_port_serial_at_set_flags (primary, MM_PORT_SERIAL_AT_FLAG_PRIMARY);
|
|
if (secondary)
|
|
mm_port_serial_at_set_flags (secondary, MM_PORT_SERIAL_AT_FLAG_SECONDARY);
|
|
if (data_at_primary) {
|
|
flags = mm_port_serial_at_get_flags (data_at_primary);
|
|
mm_port_serial_at_set_flags (data_at_primary, flags | MM_PORT_SERIAL_AT_FLAG_PPP);
|
|
}
|
|
|
|
/* sort ports by name */
|
|
#if defined WITH_QMI
|
|
qmi = g_list_sort (qmi, (GCompareFunc) port_cmp);
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
mbim = g_list_sort (mbim, (GCompareFunc) port_cmp);
|
|
#endif
|
|
data_net = g_list_sort (data_net, (GCompareFunc) port_cmp);
|
|
data_at = g_list_sort (data_at, (GCompareFunc) port_cmp);
|
|
|
|
log_port (self, MM_PORT (primary), "at (primary)");
|
|
log_port (self, MM_PORT (secondary), "at (secondary)");
|
|
log_port (self, MM_PORT (data_at_primary), "at (data primary)");
|
|
for (l = data_at; l; l = g_list_next (l))
|
|
log_port (self, MM_PORT (l->data), "at (data secondary)");
|
|
for (l = data_net; l; l = g_list_next (l))
|
|
log_port (self, MM_PORT (l->data), "net (data)");
|
|
log_port (self, MM_PORT (qcdm), "qcdm");
|
|
log_port (self, MM_PORT (gps_control), "gps (control)");
|
|
log_port (self, MM_PORT (gps), "gps (nmea)");
|
|
log_port (self, MM_PORT (audio), "audio");
|
|
log_port (self, MM_PORT (xmmrpc), "xmmrpc");
|
|
#if defined WITH_QMI
|
|
for (l = qmi; l; l = g_list_next (l))
|
|
log_port (self, MM_PORT (l->data), "qmi");
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
for (l = mbim; l; l = g_list_next (l))
|
|
log_port (self, MM_PORT (l->data), "mbim");
|
|
#endif
|
|
|
|
/* We keep new refs to the objects here */
|
|
|
|
self->priv->primary = (primary ? g_object_ref (primary) : NULL);
|
|
self->priv->secondary = (secondary ? g_object_ref (secondary) : NULL);
|
|
self->priv->qcdm = (qcdm ? g_object_ref (qcdm) : NULL);
|
|
self->priv->gps_control = (gps_control ? g_object_ref (gps_control) : NULL);
|
|
self->priv->gps = (gps ? g_object_ref (gps) : NULL);
|
|
|
|
/* Append net ports to the final list of data ports, but only if the modem
|
|
* supports them */
|
|
if (data_net) {
|
|
if (self->priv->data_net_supported) {
|
|
g_list_foreach (data_net, (GFunc)g_object_ref, NULL);
|
|
self->priv->data = g_list_concat (self->priv->data, g_steal_pointer (&data_net));
|
|
} else
|
|
mm_obj_dbg (self, "net ports available but ignored");
|
|
}
|
|
|
|
/* Append tty ports to the final list of data ports, but only if the modem
|
|
* supports them */
|
|
if (data_at_primary || data_at) {
|
|
if (self->priv->data_tty_supported) {
|
|
if (data_at_primary)
|
|
self->priv->data = g_list_append (self->priv->data, g_object_ref (data_at_primary));
|
|
if (data_at) {
|
|
g_list_foreach (data_at, (GFunc)g_object_ref, NULL);
|
|
self->priv->data = g_list_concat (self->priv->data, g_steal_pointer (&data_at));
|
|
}
|
|
} else
|
|
mm_obj_dbg (self, "at data ports available but ignored");
|
|
}
|
|
|
|
/* Fail if we haven't added any single data port; this is probably a plugin
|
|
* misconfiguration */
|
|
if (!self->priv->data) {
|
|
g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Failed to find a data port in the modem");
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined WITH_QMI
|
|
if (qmi) {
|
|
/* The first item in the data list must be a net port, because
|
|
* QMI modems only expect net ports */
|
|
g_assert (MM_IS_PORT_NET (self->priv->data->data));
|
|
/* let the MMPortQmi know which net driver is being used, taken
|
|
* from the first item in the net port list */
|
|
g_list_foreach (qmi, (GFunc)mm_port_qmi_set_net_details, (gpointer) MM_PORT (self->priv->data->data));
|
|
|
|
g_list_foreach (qmi, (GFunc)g_object_ref, NULL);
|
|
self->priv->qmi = g_steal_pointer (&qmi);
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
if (mbim) {
|
|
g_list_foreach (mbim, (GFunc)g_object_ref, NULL);
|
|
self->priv->mbim = g_steal_pointer (&mbim);
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Authorization */
|
|
|
|
gboolean
|
|
mm_base_modem_authorize_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
authorize_ready (MMAuthProvider *authp,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_auth_provider_authorize_finish (authp, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_authorize (MMBaseModem *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *authorization,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, self->priv->authp_cancellable, callback, user_data);
|
|
|
|
/* When running in the session bus for tests, default to always allow */
|
|
if (mm_context_get_test_session ()) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_auth_provider_authorize (self->priv->authp,
|
|
invocation,
|
|
authorization,
|
|
self->priv->authp_cancellable,
|
|
(GAsyncReadyCallback)authorize_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Exclusive operation */
|
|
|
|
typedef struct {
|
|
gssize id;
|
|
MMBaseModemOperationPriority priority;
|
|
gchar *description;
|
|
GTask *wait_task;
|
|
} OperationInfo;
|
|
|
|
static void
|
|
operation_info_free (OperationInfo *info)
|
|
{
|
|
g_assert (!info->wait_task);
|
|
g_free (info->description);
|
|
g_slice_free (OperationInfo, info);
|
|
}
|
|
|
|
/* Exclusive operation lock */
|
|
|
|
gssize
|
|
mm_base_modem_operation_lock_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_int (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
base_modem_operation_run (MMBaseModem *self)
|
|
{
|
|
OperationInfo *info;
|
|
GTask *task;
|
|
|
|
if (!self->priv->scheduled_operations)
|
|
return;
|
|
|
|
/* Do nothing if head operation is already running */
|
|
info = (OperationInfo *)(self->priv->scheduled_operations->data);
|
|
if (!info->wait_task)
|
|
return;
|
|
|
|
/* Run the operation in head of the list */
|
|
mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: lock acquired",
|
|
info->id,
|
|
mm_base_modem_operation_priority_get_string (info->priority),
|
|
info->description);
|
|
task = g_steal_pointer (&info->wait_task);
|
|
g_task_return_int (task, info->id);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
abort_pending_operation_in_idle_cb (GTask *task)
|
|
{
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation aborted");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
abort_pending_operations (MMBaseModem *self)
|
|
{
|
|
GList *head = NULL;
|
|
GList *abort_operations;
|
|
|
|
/* Steal the whole list before iterating it */
|
|
abort_operations = g_steal_pointer (&self->priv->scheduled_operations);
|
|
|
|
while (abort_operations) {
|
|
OperationInfo *info;
|
|
GTask *task;
|
|
|
|
info = (OperationInfo *)(abort_operations->data);
|
|
|
|
/* Head operation may already be running, we should not abort that one */
|
|
if (!info->wait_task) {
|
|
/* Keep the head item as a single-element list */
|
|
g_assert (!head);
|
|
head = abort_operations;
|
|
abort_operations = g_list_remove_link (abort_operations, head);
|
|
continue;
|
|
}
|
|
|
|
g_assert (info->wait_task);
|
|
mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: aborted early",
|
|
info->id,
|
|
mm_base_modem_operation_priority_get_string (info->priority),
|
|
info->description);
|
|
|
|
task = g_steal_pointer (&info->wait_task);
|
|
g_idle_add ((GSourceFunc) abort_pending_operation_in_idle_cb, task);
|
|
abort_operations = g_list_delete_link (abort_operations, abort_operations);
|
|
operation_info_free (info);
|
|
}
|
|
|
|
/* Keep the running head, if any, in the list of scheduled operations */
|
|
self->priv->scheduled_operations = head;
|
|
}
|
|
|
|
void
|
|
mm_base_modem_operation_lock (MMBaseModem *self,
|
|
MMBaseModemOperationPriority priority,
|
|
const gchar *description,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
OperationInfo *info;
|
|
static gssize operation_id = 0;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
if (self->priv->scheduled_operations_forbidden_forever) {
|
|
mm_obj_dbg (self, "operation forbidden as override has already been requested");
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation aborted");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
info = g_slice_new0 (OperationInfo);
|
|
info->id = operation_id;
|
|
info->priority = priority;
|
|
info->description = g_strdup (description);
|
|
info->wait_task = task;
|
|
|
|
if (operation_id == G_MAXSSIZE) {
|
|
mm_obj_dbg (self, "operation id reset");
|
|
operation_id = 0;
|
|
} else
|
|
operation_id++;
|
|
|
|
if (info->priority == MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE) {
|
|
mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: override requested - no new operations will be allowed",
|
|
info->id,
|
|
mm_base_modem_operation_priority_get_string (info->priority),
|
|
info->description);
|
|
g_assert (!self->priv->scheduled_operations_forbidden_forever);
|
|
self->priv->scheduled_operations_forbidden_forever = TRUE;
|
|
abort_pending_operations (self);
|
|
self->priv->scheduled_operations = g_list_append (self->priv->scheduled_operations, info);
|
|
} else if (info->priority == MM_BASE_MODEM_OPERATION_PRIORITY_DEFAULT) {
|
|
mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: scheduled",
|
|
info->id,
|
|
mm_base_modem_operation_priority_get_string (info->priority),
|
|
info->description);
|
|
self->priv->scheduled_operations = g_list_append (self->priv->scheduled_operations, info);
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
base_modem_operation_run (self);
|
|
}
|
|
|
|
/* Exclusive operation unlock */
|
|
|
|
void
|
|
mm_base_modem_operation_unlock (MMBaseModem *self,
|
|
gssize operation_id)
|
|
{
|
|
OperationInfo *info;
|
|
|
|
g_assert (self->priv->scheduled_operations);
|
|
|
|
info = (OperationInfo *)(self->priv->scheduled_operations->data);
|
|
g_assert (!info->wait_task);
|
|
g_assert (info->id == operation_id);
|
|
|
|
mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: lock released",
|
|
info->id,
|
|
mm_base_modem_operation_priority_get_string (info->priority),
|
|
info->description);
|
|
|
|
/* Remove head list item and free its contents */
|
|
self->priv->scheduled_operations = g_list_delete_link (self->priv->scheduled_operations,
|
|
self->priv->scheduled_operations);
|
|
operation_info_free (info);
|
|
|
|
/* Run next, if any */
|
|
base_modem_operation_run (self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
GDBusMethodInvocation *invocation;
|
|
MMBaseModemOperationPriority operation_priority;
|
|
gchar *operation_description;
|
|
} AuthorizeAndOperationLockContext;
|
|
|
|
static void
|
|
authorize_and_operation_lock_context_free (AuthorizeAndOperationLockContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_free (ctx->operation_description);
|
|
g_slice_free (AuthorizeAndOperationLockContext, ctx);
|
|
}
|
|
|
|
gssize
|
|
mm_base_modem_authorize_and_operation_lock_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_int (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
lock_after_authorize_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
gssize operation_id;
|
|
|
|
operation_id = mm_base_modem_operation_lock_finish (self, res, &error);
|
|
if (operation_id < 0)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task, operation_id);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
authorize_before_lock_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
AuthorizeAndOperationLockContext *ctx;
|
|
|
|
if (!mm_base_modem_authorize_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
mm_base_modem_operation_lock (self,
|
|
ctx->operation_priority,
|
|
ctx->operation_description,
|
|
(GAsyncReadyCallback) lock_after_authorize_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_authorize_and_operation_lock (MMBaseModem *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *authorization,
|
|
MMBaseModemOperationPriority operation_priority,
|
|
const gchar *operation_description,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
AuthorizeAndOperationLockContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (AuthorizeAndOperationLockContext);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->operation_priority = operation_priority;
|
|
ctx->operation_description = g_strdup (operation_description);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)authorize_and_operation_lock_context_free);
|
|
|
|
mm_base_modem_authorize (self,
|
|
invocation,
|
|
authorization,
|
|
(GAsyncReadyCallback)authorize_before_lock_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
const gchar *
|
|
mm_base_modem_get_device (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->device;
|
|
}
|
|
|
|
const gchar *
|
|
mm_base_modem_get_physdev (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->physdev;
|
|
}
|
|
|
|
const gchar **
|
|
mm_base_modem_get_drivers (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return (const gchar **)self->priv->drivers;
|
|
}
|
|
|
|
const gchar *
|
|
mm_base_modem_get_plugin (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
|
|
|
|
return self->priv->plugin;
|
|
}
|
|
|
|
guint
|
|
mm_base_modem_get_vendor_id (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), 0);
|
|
|
|
return self->priv->vendor_id;
|
|
}
|
|
|
|
guint
|
|
mm_base_modem_get_product_id (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), 0);
|
|
|
|
return self->priv->product_id;
|
|
}
|
|
|
|
guint
|
|
mm_base_modem_get_subsystem_vendor_id (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), 0);
|
|
|
|
return self->priv->subsystem_vendor_id;
|
|
}
|
|
|
|
guint
|
|
mm_base_modem_get_subsystem_device_id (MMBaseModem *self)
|
|
{
|
|
g_return_val_if_fail (MM_IS_BASE_MODEM (self), 0);
|
|
|
|
return self->priv->subsystem_device_id;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
clear_invalid_from_idle (MMBaseModem *self)
|
|
{
|
|
if (self->priv->invalid_from_idle)
|
|
g_source_remove (self->priv->invalid_from_idle);
|
|
self->priv->invalid_from_idle = 0;
|
|
}
|
|
|
|
static gboolean
|
|
base_modem_invalid_idle (MMBaseModem *self)
|
|
{
|
|
clear_invalid_from_idle (self);
|
|
|
|
/* Ensure the modem is set invalid if we get the modem-wide cancellable
|
|
* cancelled */
|
|
mm_base_modem_set_valid (self, FALSE);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
base_modem_cancelled (GCancellable *cancellable,
|
|
MMBaseModem *self)
|
|
{
|
|
clear_invalid_from_idle (self);
|
|
|
|
/* NOTE: Don't call set_valid() directly here, do it in an idle, and ensure
|
|
* that we pass a valid reference of the modem object as context. */
|
|
self->priv->invalid_from_idle = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
|
|
(GSourceFunc)base_modem_invalid_idle,
|
|
g_object_ref (self),
|
|
(GDestroyNotify) g_object_unref);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
setup_ports_table (GHashTable **ht)
|
|
{
|
|
g_assert (ht && !*ht);
|
|
*ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
|
}
|
|
|
|
typedef struct {
|
|
volatile gint refcount;
|
|
GError *error;
|
|
/* The Context owns the GTask so that we can ensure the Task returns only
|
|
* when all ports are closed. The reference count tracks open ports and
|
|
* the context will return the Task when it reaches zero (indicating all
|
|
* outstanding operations are complete and ports are closed).
|
|
*/
|
|
GTask *task;
|
|
} TeardownContext;
|
|
|
|
static TeardownContext *
|
|
teardown_context_new (GObject *source_object,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
TeardownContext *ctx;
|
|
|
|
ctx = g_slice_new0 (TeardownContext);
|
|
ctx->refcount = 1;
|
|
ctx->task = g_task_new (source_object, NULL, callback, user_data);
|
|
return ctx;
|
|
}
|
|
|
|
static void
|
|
teardown_context_ref (TeardownContext *ctx)
|
|
{
|
|
g_atomic_int_inc (&ctx->refcount);
|
|
}
|
|
|
|
static void
|
|
teardown_context_unref (TeardownContext *ctx)
|
|
{
|
|
if (g_atomic_int_dec_and_test (&ctx->refcount)) {
|
|
if (ctx->error)
|
|
g_task_return_error (ctx->task, g_steal_pointer (&ctx->error));
|
|
else
|
|
g_task_return_boolean (ctx->task, TRUE);
|
|
g_clear_object (&ctx->task);
|
|
g_slice_free (TeardownContext, ctx);
|
|
}
|
|
}
|
|
|
|
#if defined (WITH_MBIM) || defined (WITH_QMI)
|
|
|
|
static void
|
|
teardown_context_add_error (TeardownContext *ctx,
|
|
MMPort *port,
|
|
GError *error)
|
|
{
|
|
if (!ctx->error) {
|
|
ctx->error = g_error_copy (error);
|
|
g_prefix_error (&ctx->error,
|
|
"[%s] teardown error: ",
|
|
mm_port_get_device (port));
|
|
} else {
|
|
g_prefix_error (&ctx->error,
|
|
"[%s] teardown error: %s; ",
|
|
mm_port_get_device (port),
|
|
error->message);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined WITH_MBIM
|
|
|
|
static void
|
|
mbim_port_close_ready (MMPortMbim *port,
|
|
GAsyncResult *res,
|
|
TeardownContext *ctx)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!mm_port_mbim_close_finish (port, res, &error))
|
|
teardown_context_add_error (ctx, MM_PORT (port), error);
|
|
teardown_context_unref (ctx);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined WITH_QMI
|
|
|
|
static void
|
|
qmi_port_close_ready (MMPortQmi *port,
|
|
GAsyncResult *res,
|
|
TeardownContext *ctx)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!mm_port_qmi_close_finish (port, res, &error))
|
|
teardown_context_add_error (ctx, MM_PORT (port), error);
|
|
teardown_context_unref (ctx);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void
|
|
cleanup_modem_port (MMBaseModem *self,
|
|
MMPort *port,
|
|
TeardownContext *ctx)
|
|
{
|
|
mm_obj_dbg (self, "cleaning up port '%s/%s'...",
|
|
mm_port_subsys_get_string (mm_port_get_subsys (MM_PORT (port))),
|
|
mm_port_get_device (MM_PORT (port)));
|
|
|
|
/* Cleanup on all control ports */
|
|
g_signal_handlers_disconnect_by_func (port, port_timed_out_cb, self);
|
|
g_signal_handlers_disconnect_by_func (port, port_removed_cb, self);
|
|
|
|
if (ctx)
|
|
teardown_context_ref (ctx);
|
|
|
|
/* No need to close serial ports here as they do not require a specific
|
|
* shutdown procedure with message exchanges and callbacks. They will be
|
|
* closed when the modem is invalidated or disposed.
|
|
*/
|
|
|
|
#if defined WITH_MBIM
|
|
/* We need to close the MBIM port cleanly when disposing the modem object */
|
|
if (MM_IS_PORT_MBIM (port)) {
|
|
mm_port_mbim_close (MM_PORT_MBIM (port),
|
|
ctx ? (GAsyncReadyCallback)mbim_port_close_ready : NULL,
|
|
ctx);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if defined WITH_QMI
|
|
/* We need to close the QMI port cleanly when disposing the modem object,
|
|
* otherwise the allocated CIDs will be kept allocated, and if we end up
|
|
* allocating too many newer allocations will fail with client-ids-exhausted
|
|
* errors. */
|
|
if (MM_IS_PORT_QMI (port)) {
|
|
mm_port_qmi_close (MM_PORT_QMI (port),
|
|
ctx ? (GAsyncReadyCallback)qmi_port_close_ready : NULL,
|
|
ctx);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (ctx)
|
|
teardown_context_unref (ctx);
|
|
}
|
|
|
|
static void
|
|
teardown_ports_tables (MMBaseModem *self,
|
|
TeardownContext *ctx)
|
|
{
|
|
GHashTable **tables[2] = {
|
|
&self->priv->link_ports,
|
|
&self->priv->ports,
|
|
};
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
gpointer key;
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (tables); i++) {
|
|
if (*tables[i]) {
|
|
g_hash_table_iter_init (&iter, *tables[i]);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
cleanup_modem_port (self, MM_PORT (value), ctx);
|
|
g_hash_table_destroy (*tables[i]);
|
|
*tables[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
mm_base_modem_teardown_ports_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
void
|
|
mm_base_modem_teardown_ports (MMBaseModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
TeardownContext *ctx;
|
|
|
|
self->priv->torn_down = TRUE;
|
|
|
|
ctx = teardown_context_new (G_OBJECT (self), callback, user_data);
|
|
teardown_ports_tables (self, ctx);
|
|
teardown_context_unref (ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gchar *
|
|
log_object_build_id (MMLogObject *_self)
|
|
{
|
|
MMBaseModem *self;
|
|
|
|
self = MM_BASE_MODEM (_self);
|
|
return g_strdup_printf ("modem%u", self->priv->dbus_id);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
mm_base_modem_init (MMBaseModem *self)
|
|
{
|
|
static guint id = 0;
|
|
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
|
|
MM_TYPE_BASE_MODEM,
|
|
MMBaseModemPrivate);
|
|
|
|
/* Each modem is given a unique id to build its own DBus path */
|
|
self->priv->dbus_id = id++;
|
|
|
|
/* Setup authorization provider */
|
|
self->priv->authp = mm_auth_provider_get ();
|
|
self->priv->authp_cancellable = g_cancellable_new ();
|
|
|
|
/* Setup modem-wide cancellable */
|
|
self->priv->cancellable = g_cancellable_new ();
|
|
self->priv->invalid_if_cancelled =
|
|
g_cancellable_connect (self->priv->cancellable,
|
|
G_CALLBACK (base_modem_cancelled),
|
|
self,
|
|
NULL);
|
|
|
|
self->priv->max_timeouts = DEFAULT_MAX_TIMEOUTS;
|
|
|
|
setup_ports_table (&self->priv->ports);
|
|
setup_ports_table (&self->priv->link_ports);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMBaseModem *self = MM_BASE_MODEM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_VALID:
|
|
mm_base_modem_set_valid (self, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_REPROBE:
|
|
mm_base_modem_set_reprobe (self, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_MAX_TIMEOUTS:
|
|
self->priv->max_timeouts = g_value_get_uint (value);
|
|
break;
|
|
case PROP_DEVICE:
|
|
g_free (self->priv->device);
|
|
self->priv->device = g_value_dup_string (value);
|
|
break;
|
|
case PROP_PHYSDEV:
|
|
g_free (self->priv->physdev);
|
|
self->priv->physdev = g_value_dup_string (value);
|
|
break;
|
|
case PROP_DRIVERS:
|
|
g_strfreev (self->priv->drivers);
|
|
self->priv->drivers = g_value_dup_boxed (value);
|
|
break;
|
|
case PROP_PLUGIN:
|
|
g_free (self->priv->plugin);
|
|
self->priv->plugin = g_value_dup_string (value);
|
|
break;
|
|
case PROP_VENDOR_ID:
|
|
self->priv->vendor_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_PRODUCT_ID:
|
|
self->priv->product_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_SUBSYSTEM_VENDOR_ID:
|
|
self->priv->subsystem_vendor_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_SUBSYSTEM_DEVICE_ID:
|
|
self->priv->subsystem_device_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_clear_object (&self->priv->connection);
|
|
self->priv->connection = g_value_dup_object (value);
|
|
break;
|
|
case PROP_DATA_NET_SUPPORTED:
|
|
self->priv->data_net_supported = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_DATA_TTY_SUPPORTED:
|
|
self->priv->data_tty_supported = g_value_get_boolean (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)
|
|
{
|
|
MMBaseModem *self = MM_BASE_MODEM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_VALID:
|
|
g_value_set_boolean (value, self->priv->valid);
|
|
break;
|
|
case PROP_REPROBE:
|
|
g_value_set_boolean (value, self->priv->reprobe);
|
|
break;
|
|
case PROP_MAX_TIMEOUTS:
|
|
g_value_set_uint (value, self->priv->max_timeouts);
|
|
break;
|
|
case PROP_DEVICE:
|
|
g_value_set_string (value, self->priv->device);
|
|
break;
|
|
case PROP_PHYSDEV:
|
|
g_value_set_string (value, self->priv->physdev);
|
|
break;
|
|
case PROP_DRIVERS:
|
|
g_value_set_boxed (value, self->priv->drivers);
|
|
break;
|
|
case PROP_PLUGIN:
|
|
g_value_set_string (value, self->priv->plugin);
|
|
break;
|
|
case PROP_VENDOR_ID:
|
|
g_value_set_uint (value, self->priv->vendor_id);
|
|
break;
|
|
case PROP_PRODUCT_ID:
|
|
g_value_set_uint (value, self->priv->product_id);
|
|
break;
|
|
case PROP_SUBSYSTEM_VENDOR_ID:
|
|
g_value_set_uint (value, self->priv->subsystem_vendor_id);
|
|
break;
|
|
case PROP_SUBSYSTEM_DEVICE_ID:
|
|
g_value_set_uint (value, self->priv->subsystem_device_id);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_value_set_object (value, self->priv->connection);
|
|
break;
|
|
case PROP_DATA_NET_SUPPORTED:
|
|
g_value_set_boolean (value, self->priv->data_net_supported);
|
|
break;
|
|
case PROP_DATA_TTY_SUPPORTED:
|
|
g_value_set_boolean (value, self->priv->data_tty_supported);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMBaseModem *self = MM_BASE_MODEM (object);
|
|
|
|
/* TODO
|
|
* mm_auth_provider_cancel_for_owner (self->priv->authp, object);
|
|
*/
|
|
|
|
g_assert (!self->priv->scheduled_operations);
|
|
|
|
mm_obj_dbg (self, "completely disposed");
|
|
|
|
g_free (self->priv->device);
|
|
g_free (self->priv->physdev);
|
|
g_strfreev (self->priv->drivers);
|
|
g_free (self->priv->plugin);
|
|
|
|
G_OBJECT_CLASS (mm_base_modem_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMBaseModem *self = MM_BASE_MODEM (object);
|
|
|
|
/* Cancel all ongoing auth requests */
|
|
g_cancellable_cancel (self->priv->authp_cancellable);
|
|
g_clear_object (&self->priv->authp_cancellable);
|
|
|
|
/* note: authp is a singleton, we don't keep a full reference */
|
|
|
|
/* Ensure we cancel any ongoing operation, but before
|
|
* disconnect our own signal handler, or we'll end up with
|
|
* another reference of the modem object around. */
|
|
g_cancellable_disconnect (self->priv->cancellable,
|
|
self->priv->invalid_if_cancelled);
|
|
g_cancellable_cancel (self->priv->cancellable);
|
|
g_clear_object (&self->priv->cancellable);
|
|
|
|
clear_invalid_from_idle (self);
|
|
|
|
g_clear_object (&self->priv->primary);
|
|
g_clear_object (&self->priv->secondary);
|
|
g_list_free_full (g_steal_pointer (&self->priv->data), g_object_unref);
|
|
g_clear_object (&self->priv->qcdm);
|
|
g_clear_object (&self->priv->gps_control);
|
|
g_clear_object (&self->priv->gps);
|
|
g_clear_object (&self->priv->audio);
|
|
#if defined WITH_QMI
|
|
g_list_free_full (g_steal_pointer (&self->priv->qmi), g_object_unref);
|
|
#endif
|
|
#if defined WITH_MBIM
|
|
g_list_free_full (g_steal_pointer (&self->priv->mbim), g_object_unref);
|
|
#endif
|
|
|
|
if (!self->priv->torn_down)
|
|
mm_obj_warn (self, "teardown not called before dispose");
|
|
teardown_ports_tables (self, NULL);
|
|
|
|
g_clear_object (&self->priv->connection);
|
|
|
|
G_OBJECT_CLASS (mm_base_modem_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
log_object_iface_init (MMLogObjectInterface *iface)
|
|
{
|
|
iface->build_id = log_object_build_id;
|
|
}
|
|
|
|
static void
|
|
mm_base_modem_class_init (MMBaseModemClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMBaseModemPrivate));
|
|
|
|
klass->create_tty_port = base_modem_create_tty_port;
|
|
klass->create_usbmisc_port = base_modem_create_usbmisc_port;
|
|
klass->create_wwan_port = base_modem_create_wwan_port;
|
|
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->finalize = finalize;
|
|
object_class->dispose = dispose;
|
|
|
|
properties[PROP_MAX_TIMEOUTS] =
|
|
g_param_spec_uint (MM_BASE_MODEM_MAX_TIMEOUTS,
|
|
"Max timeouts",
|
|
"Maximum number of consecutive timed out commands sent to "
|
|
"the modem before disabling it. If 0, this feature is disabled.",
|
|
0, G_MAXUINT, DEFAULT_MAX_TIMEOUTS,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_MAX_TIMEOUTS, properties[PROP_MAX_TIMEOUTS]);
|
|
|
|
properties[PROP_VALID] =
|
|
g_param_spec_boolean (MM_BASE_MODEM_VALID,
|
|
"Valid",
|
|
"Whether the modem is to be considered valid or not.",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_VALID, properties[PROP_VALID]);
|
|
|
|
properties[PROP_DEVICE] =
|
|
g_param_spec_string (MM_BASE_MODEM_DEVICE,
|
|
"Device",
|
|
"Main modem parent device of all the modem's ports",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_DEVICE, properties[PROP_DEVICE]);
|
|
|
|
properties[PROP_PHYSDEV] =
|
|
g_param_spec_string (MM_BASE_MODEM_PHYSDEV,
|
|
"Physdev path",
|
|
"Main modem parent physical device path",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_PHYSDEV, properties[PROP_PHYSDEV]);
|
|
|
|
properties[PROP_DRIVERS] =
|
|
g_param_spec_boxed (MM_BASE_MODEM_DRIVERS,
|
|
"Drivers",
|
|
"Kernel drivers",
|
|
G_TYPE_STRV,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_DRIVERS, properties[PROP_DRIVERS]);
|
|
|
|
properties[PROP_PLUGIN] =
|
|
g_param_spec_string (MM_BASE_MODEM_PLUGIN,
|
|
"Plugin",
|
|
"Name of the plugin managing this modem",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_PLUGIN, properties[PROP_PLUGIN]);
|
|
|
|
properties[PROP_VENDOR_ID] =
|
|
g_param_spec_uint (MM_BASE_MODEM_VENDOR_ID,
|
|
"Hardware vendor ID",
|
|
"Hardware vendor ID. May be unknown for serial devices.",
|
|
0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_VENDOR_ID, properties[PROP_VENDOR_ID]);
|
|
|
|
properties[PROP_PRODUCT_ID] =
|
|
g_param_spec_uint (MM_BASE_MODEM_PRODUCT_ID,
|
|
"Hardware product ID",
|
|
"Hardware product ID. May be unknown for serial devices.",
|
|
0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_PRODUCT_ID, properties[PROP_PRODUCT_ID]);
|
|
|
|
properties[PROP_SUBSYSTEM_VENDOR_ID] =
|
|
g_param_spec_uint (MM_BASE_MODEM_SUBSYSTEM_VENDOR_ID,
|
|
"Hardware subsystem vendor ID",
|
|
"Hardware subsystem vendor ID. Available for pci devices.",
|
|
0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_SUBSYSTEM_VENDOR_ID, properties[PROP_SUBSYSTEM_VENDOR_ID]);
|
|
|
|
properties[PROP_SUBSYSTEM_DEVICE_ID] =
|
|
g_param_spec_uint (MM_BASE_MODEM_SUBSYSTEM_DEVICE_ID,
|
|
"Hardware subsystem device ID",
|
|
"Hardware subsystem device ID. Available for pci devices.",
|
|
0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_SUBSYSTEM_DEVICE_ID, properties[PROP_SUBSYSTEM_DEVICE_ID]);
|
|
|
|
properties[PROP_CONNECTION] =
|
|
g_param_spec_object (MM_BASE_MODEM_CONNECTION,
|
|
"Connection",
|
|
"GDBus connection to the system bus.",
|
|
G_TYPE_DBUS_CONNECTION,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
|
|
|
|
properties[PROP_REPROBE] =
|
|
g_param_spec_boolean (MM_BASE_MODEM_REPROBE,
|
|
"Reprobe",
|
|
"Whether the modem needs to be reprobed or not.",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_REPROBE, properties[PROP_REPROBE]);
|
|
|
|
properties[PROP_DATA_NET_SUPPORTED] =
|
|
g_param_spec_boolean (MM_BASE_MODEM_DATA_NET_SUPPORTED,
|
|
"Data NET supported",
|
|
"Whether the modem supports connection via a NET port.",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_DATA_NET_SUPPORTED, properties[PROP_DATA_NET_SUPPORTED]);
|
|
|
|
properties[PROP_DATA_TTY_SUPPORTED] =
|
|
g_param_spec_boolean (MM_BASE_MODEM_DATA_TTY_SUPPORTED,
|
|
"Data TTY supported",
|
|
"Whether the modem supports connection via a TTY port.",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_DATA_TTY_SUPPORTED, properties[PROP_DATA_TTY_SUPPORTED]);
|
|
|
|
signals[SIGNAL_LINK_PORT_GRABBED] =
|
|
g_signal_new (MM_BASE_MODEM_SIGNAL_LINK_PORT_GRABBED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (MMBaseModemClass, link_port_grabbed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 1, MM_TYPE_PORT);
|
|
|
|
signals[SIGNAL_LINK_PORT_RELEASED] =
|
|
g_signal_new (MM_BASE_MODEM_SIGNAL_LINK_PORT_RELEASED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (MMBaseModemClass, link_port_released),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 1, MM_TYPE_PORT);
|
|
}
|