/* -*- 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 2020 Google LLC */ #include #include #include #include "mm-utils.h" #include "mm-qrtr-bus-watcher.h" static void log_object_iface_init (MMLogObjectInterface *iface); G_DEFINE_TYPE_EXTENDED (MMQrtrBusWatcher, mm_qrtr_bus_watcher, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)) struct _MMQrtrBusWatcherPrivate { QrtrBus *qrtr_bus; guint node_added_id; guint node_removed_id; /* Map of NodeNumber -> QRTR nodes available */ GHashTable *nodes; }; enum { QRTR_DEVICE_ADDED, QRTR_DEVICE_REMOVED, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = { 0 }; /*****************************************************************************/ static gchar * log_object_build_id (MMLogObject *_self) { return g_strdup ("qrtr-bus-watcher"); } /*****************************************************************************/ typedef struct { MMQrtrBusWatcher *self; QrtrNode *node; } DeviceContext; static void device_context_free (DeviceContext *ctx) { g_object_unref (ctx->self); g_object_unref (ctx->node); g_slice_free (DeviceContext, ctx); } static void qrtr_node_services_ready (QrtrNode *node, GAsyncResult *res, DeviceContext *ctx) { guint32 node_id; node_id = qrtr_node_get_id (node); if (!qrtr_node_wait_for_services_finish (node, res, NULL)) { mm_obj_dbg (ctx->self, "qrtr node %u doesn't have required services to be considered a control node", node_id); g_hash_table_remove (ctx->self->priv->nodes, GUINT_TO_POINTER (node_id)); device_context_free (ctx); return; } mm_obj_dbg (ctx->self, "qrtr services ready for node %u", node_id); g_signal_emit (ctx->self, signals[QRTR_DEVICE_ADDED], 0, node_id); device_context_free (ctx); } static void handle_qrtr_node_added (QrtrBus *qrtr_bus, guint32 node_id, MMQrtrBusWatcher *self) { g_autoptr(QrtrNode) node = NULL; g_autoptr(GArray) services = NULL; DeviceContext *ctx; static const QmiService required_services[] = { QMI_SERVICE_WDS, QMI_SERVICE_NAS, QMI_SERVICE_DMS }; mm_obj_dbg (self, "qrtr node %u added", node_id); node = qrtr_bus_get_node (qrtr_bus, node_id); if (!node) { mm_obj_warn (self, "cannot find node %u", node_id); return; } if (g_hash_table_contains (self->priv->nodes, GUINT_TO_POINTER (node_id))) { mm_obj_warn (self, "qrtr node %u was previously added", node_id); return; } /* a full node reference now owned by the hash table */ g_hash_table_insert (self->priv->nodes, GUINT_TO_POINTER (node_id), g_object_ref (node)); mm_obj_dbg (self, "waiting for modem services on node %u", node_id); /* Check if the node provides services to be sure the node represents a * modem. */ services = g_array_sized_new (FALSE, FALSE, sizeof (QmiService), G_N_ELEMENTS (required_services)); g_array_append_vals (services, required_services, G_N_ELEMENTS (required_services)); /* Setup command context */ ctx = g_slice_new0 (DeviceContext); ctx->self = g_object_ref (self); ctx->node = g_object_ref (node); qrtr_node_wait_for_services (node, services, 4000, /* ms */ NULL, (GAsyncReadyCallback) qrtr_node_services_ready, ctx); } static void handle_qrtr_node_removed (QrtrBus *qrtr_bus, guint32 node_id, MMQrtrBusWatcher *self) { QrtrNode *node; node = qrtr_bus_get_node (qrtr_bus, node_id); if (!node) { mm_obj_warn (self, "cannot find node %u", node_id); return; } g_hash_table_remove (self->priv->nodes, GUINT_TO_POINTER (node_id)); mm_obj_dbg (self, "qrtr node %u removed", node_id); g_signal_emit (self, signals[QRTR_DEVICE_REMOVED], 0, node_id); } /*****************************************************************************/ QrtrNode * mm_qrtr_bus_watcher_peek_node (MMQrtrBusWatcher *self, guint32 node_id) { g_assert (MM_IS_QRTR_BUS_WATCHER (self)); return g_hash_table_lookup (self->priv->nodes, GUINT_TO_POINTER (node_id)); } /*****************************************************************************/ gboolean mm_qrtr_bus_watcher_start_finish (MMQrtrBusWatcher *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } typedef struct { MMQrtrBusWatcher *self; QrtrNode *node; } ProcessExistingNodes; static gboolean process_existing_nodes_idle (ProcessExistingNodes *ctx) { handle_qrtr_node_added ( ctx->self->priv->qrtr_bus, qrtr_node_get_id (ctx->node), ctx->self); g_object_unref (ctx->self); g_object_unref (ctx->node); g_slice_free (ProcessExistingNodes, ctx); return G_SOURCE_REMOVE; } static void process_existing_nodes (MMQrtrBusWatcher *self) { GList *nodes, *l; QrtrNode *node; ProcessExistingNodes *ctx; nodes = qrtr_bus_peek_nodes (self->priv->qrtr_bus); for (l = nodes; l; l = g_list_next (l)) { node = l->data; ctx = g_slice_new (ProcessExistingNodes); ctx->self = g_object_ref (self); ctx->node = g_object_ref (node); g_idle_add ((GSourceFunc) process_existing_nodes_idle, ctx); } } static void qrtr_bus_ready (GObject *source, GAsyncResult *res, GTask *task) { MMQrtrBusWatcher *self; GError *error = NULL; self = g_task_get_source_object (task); self->priv->qrtr_bus = qrtr_bus_new_finish (res, &error); if (!self->priv->qrtr_bus) { g_task_return_error (task, error); g_object_unref (task); return; } /* Listen for bus events */ self->priv->node_added_id = g_signal_connect (self->priv->qrtr_bus, QRTR_BUS_SIGNAL_NODE_ADDED, G_CALLBACK (handle_qrtr_node_added), self); self->priv->node_removed_id = g_signal_connect (self->priv->qrtr_bus, QRTR_BUS_SIGNAL_NODE_REMOVED, G_CALLBACK (handle_qrtr_node_removed), self); process_existing_nodes (self); g_task_return_boolean (task, TRUE); g_object_unref (task); } void mm_qrtr_bus_watcher_start (MMQrtrBusWatcher *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (self, NULL, callback, user_data); qrtr_bus_new (0, /* disable initial lookup wait */ NULL, (GAsyncReadyCallback)qrtr_bus_ready, task); } /*****************************************************************************/ MMQrtrBusWatcher * mm_qrtr_bus_watcher_new (void) { return MM_QRTR_BUS_WATCHER (g_object_new (MM_TYPE_QRTR_BUS_WATCHER, NULL)); } static void mm_qrtr_bus_watcher_init (MMQrtrBusWatcher *self) { /* Initialize opaque pointer to private data */ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_QRTR_BUS_WATCHER, MMQrtrBusWatcherPrivate); /* Setup internal lists of device and node objects */ self->priv->nodes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_object_unref); } static void finalize (GObject *object) { MMQrtrBusWatcher *self = MM_QRTR_BUS_WATCHER (object); g_hash_table_destroy (self->priv->nodes); if (self->priv->node_added_id) g_signal_handler_disconnect (self->priv->qrtr_bus, self->priv->node_added_id); if (self->priv->node_removed_id) g_signal_handler_disconnect (self->priv->qrtr_bus, self->priv->node_removed_id); g_clear_object (&self->priv->qrtr_bus); G_OBJECT_CLASS (mm_qrtr_bus_watcher_parent_class)->finalize (object); } static void log_object_iface_init (MMLogObjectInterface *iface) { iface->build_id = log_object_build_id; } static void mm_qrtr_bus_watcher_class_init (MMQrtrBusWatcherClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (object_class, sizeof (MMQrtrBusWatcherPrivate)); /* Virtual methods */ object_class->finalize = finalize; /** * QrtrBusWatcher::qrtr-device-added: * @self: the #QrtrBusWatcher * @node: the node ID of the modem that is added * * The ::qrtr-device-added signal is emitted when a new qrtr modem is * available on the QRTR bus. */ signals[QRTR_DEVICE_ADDED] = g_signal_new (MM_QRTR_BUS_WATCHER_DEVICE_ADDED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MMQrtrBusWatcherClass, qrtr_device_added), NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT); /** * QrtrBusWatcher::qrtr-device-removed: * @self: the #QrtrBusWatcher * @node: the node ID of the modem that is removed * * The ::qrtr-device-removed signal is emitted when a qrtr modem deregisters * all services from the QRTR bus. */ signals[QRTR_DEVICE_REMOVED] = g_signal_new (MM_QRTR_BUS_WATCHER_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MMQrtrBusWatcherClass, qrtr_device_removed), NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT); }