/* -*- 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 #include #include #include #include #include #include #include #include #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" #include "mm-bind.h" static void log_object_iface_init (MMLogObjectInterface *iface); static void auth_iface_init (MMIfaceAuthInterface *iface); static void op_lock_iface_init (MMIfaceOpLockInterface *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) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_AUTH, auth_iface_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_OP_LOCK, op_lock_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; }; static void mm_base_modem_operation_lock (MMBaseModem *self, MMOperationPriority priority, const gchar *description, GAsyncReadyCallback callback, gpointer user_data); static gssize mm_base_modem_operation_lock_finish (MMBaseModem *self, GAsyncResult *res, GError **error); 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, MMPortType ptype, const gchar *name) { return MM_PORT (g_object_new (MM_TYPE_PORT, MM_PORT_DEVICE, name, MM_PORT_GROUP, MM_PORT_GROUP_IGNORED, MM_PORT_TYPE, ptype, 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_GROUP, MM_PORT_GROUP_USED, 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, MMPortGroup pgroup, 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 (pgroup == MM_PORT_GROUP_IGNORED) port = base_modem_create_ignored_port (self, ptype, 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 (pgroup == MM_PORT_GROUP_USED) { 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, MMPortGroup pgroup, MMPortType ptype, MMPortSerialAtFlag at_pflags, GError **error) { g_autoptr(GError) inner_error = NULL; if (!base_modem_internal_grab_port (self, kernel_device, FALSE, pgroup, 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_GROUP_USED, 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_iface_op_lock_unlock (MM_IFACE_OP_LOCK (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, MMOperationLock operation_lock, MMOperationPriority 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_OPERATION_LOCK_ALREADY_ACQUIRED) { state_operation_run (task); return; } g_assert (operation_lock == MM_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, MMOperationLock operation_lock, GAsyncReadyCallback callback, gpointer user_data) { state_operation (self, STATE_OPERATION_TYPE_SYNC, operation_lock, MM_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, MMOperationLock operation_lock, MMOperationPriority 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, MMOperationLock operation_lock, GAsyncReadyCallback callback, gpointer user_data) { state_operation (self, STATE_OPERATION_TYPE_ENABLE, operation_lock, MM_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, MMOperationLock operation_lock, GAsyncReadyCallback callback, gpointer user_data) { state_operation (self, STATE_OPERATION_TYPE_INITIALIZE, operation_lock, MM_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); } static MMModemPortInfo * parse_port_infos (GHashTable *ports, guint *n_port_infos, MMPortGroup pgroup_filter) { GHashTableIter iter; GArray *port_infos; MMPort *port; if (!ports) { *n_port_infos = 0; return NULL; } port_infos = g_array_new (FALSE, FALSE, sizeof (MMModemPortInfo)); g_hash_table_iter_init (&iter, ports); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&port)) { MMModemPortInfo port_info; if (mm_port_get_port_group (port) != pgroup_filter) continue; 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); } *n_port_infos = port_infos->len; g_array_sort (port_infos, (GCompareFunc) port_info_cmp); return (MMModemPortInfo *) g_array_free (port_infos, FALSE); } MMModemPortInfo * mm_base_modem_get_port_infos (MMBaseModem *self, guint *n_port_infos) { return parse_port_infos (self->priv->ports, n_port_infos, MM_PORT_GROUP_USED); } MMModemPortInfo * mm_base_modem_get_ignored_port_infos (MMBaseModem *self, guint *n_port_infos) { return parse_port_infos (self->priv->ports, n_port_infos, MM_PORT_GROUP_IGNORED); } 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)) { /* Skip ports that should not be used */ if (mm_port_get_port_group (candidate) != MM_PORT_GROUP_USED) continue; 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 */ static gboolean mm_base_modem_authorize_finish (MMIfaceAuth *auth, 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); } static void mm_base_modem_authorize (MMIfaceAuth *auth, GDBusMethodInvocation *invocation, const gchar *authorization, GAsyncReadyCallback callback, gpointer user_data) { MMBaseModem *self = MM_BASE_MODEM (auth); GTask *task; task = g_task_new (self, self->priv->authp_cancellable, callback, user_data); mm_auth_provider_authorize (self->priv->authp, invocation, authorization, self->priv->authp_cancellable, (GAsyncReadyCallback)authorize_ready, task); } /*****************************************************************************/ /* Exclusive operation */ typedef struct { gssize id; MMOperationPriority 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 */ static 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_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_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; } static void mm_base_modem_operation_lock (MMBaseModem *self, MMOperationPriority 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_OPERATION_PRIORITY_OVERRIDE) { mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: override requested - no new operations will be allowed", info->id, mm_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_OPERATION_PRIORITY_DEFAULT) { mm_obj_dbg (self, "[operation %" G_GSSIZE_FORMAT "] %s - %s: scheduled", info->id, mm_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 */ static void mm_base_modem_operation_unlock (MMIfaceOpLock *_self, gssize operation_id) { MMBaseModem *self = MM_BASE_MODEM (_self); 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_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; MMOperationPriority 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); } static gssize mm_base_modem_authorize_and_operation_lock_finish (MMIfaceOpLock *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 (MMIfaceAuth *auth, GAsyncResult *res, GTask *task) { GError *error = NULL; AuthorizeAndOperationLockContext *ctx; if (!mm_iface_auth_authorize_finish (auth, 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 (MM_BASE_MODEM (auth), ctx->operation_priority, ctx->operation_description, (GAsyncReadyCallback) lock_after_authorize_ready, task); } static void mm_base_modem_authorize_and_operation_lock (MMIfaceOpLock *self, GDBusMethodInvocation *invocation, const gchar *authorization, MMOperationPriority 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_iface_auth_authorize (MM_IFACE_AUTH (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 auth_iface_init (MMIfaceAuthInterface *iface) { iface->authorize = mm_base_modem_authorize; iface->authorize_finish = mm_base_modem_authorize_finish; } static void op_lock_iface_init (MMIfaceOpLockInterface *iface) { iface->authorize_and_lock = mm_base_modem_authorize_and_operation_lock; iface->authorize_and_lock_finish = mm_base_modem_authorize_and_operation_lock_finish; iface->unlock = mm_base_modem_operation_unlock; } 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_BINDABLE_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); }