aboutsummaryrefslogtreecommitdiff
path: root/src/mm-port-scheduler-rr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-port-scheduler-rr.c')
-rw-r--r--src/mm-port-scheduler-rr.c357
1 files changed, 357 insertions, 0 deletions
diff --git a/src/mm-port-scheduler-rr.c b/src/mm-port-scheduler-rr.c
new file mode 100644
index 00000000..8d6f8195
--- /dev/null
+++ b/src/mm-port-scheduler-rr.c
@@ -0,0 +1,357 @@
+/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
+ */
+
+#include "mm-port-scheduler-rr.h"
+#include "mm-log-object.h"
+
+/* Theory of operation:
+ *
+ * Sources (e.g. MMPort subclasses) register themselves with the scheduler.
+ *
+ * Each source notifies the scheduler whenever its command queue depth changes,
+ * for example when new commands are submitted, when commands are completed,
+ * or when commands are canceled.
+ *
+ * The scheduler will round-robin between all sources with pending commands,
+ * sleeping when there are no pending commands from any source.
+ *
+ * For each source with a pending command the scheduler will emit the
+ * 'send-command' signal with that source's ID. The given source should
+ * send the next command in its queue to the modem.
+ *
+ * When that command is finished (either successfully or with an error/timeout)
+ * the source must call mm_port_scheduler_notify_command_done() to notify the
+ * scheduler that it may advance to the next source with a pending command, if
+ * any. If the 'send-command' signal and the notify_command_done() call are not
+ * balanced the scheduler may stall.
+ */
+
+static void mm_port_scheduler_iface_init (MMPortSchedulerInterface *iface);
+
+struct _MMPortSchedulerRRPrivate {
+ GPtrArray *sources;
+ guint cur_source;
+ gboolean in_command;
+ guint next_pending_id;
+
+ /* Delay between allowing ports to send commands, in ms */
+ guint inter_port_delay;
+};
+
+enum {
+ PROP_0,
+ PROP_INTER_PORT_DELAY,
+
+ LAST_PROP
+};
+
+static guint send_command_signal = 0;
+
+G_DEFINE_TYPE_WITH_CODE (MMPortSchedulerRR, mm_port_scheduler_rr, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (MMPortSchedulerRR)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_PORT_SCHEDULER,
+ mm_port_scheduler_iface_init))
+
+/*****************************************************************************/
+
+typedef struct {
+ gpointer id;
+ gchar *tag; /* e.g. port name */
+ guint num_pending;
+} Source;
+
+static void
+source_free (Source *s)
+{
+ g_free (s->tag);
+ g_slice_free (Source, s);
+}
+
+static Source *
+find_source (MMPortSchedulerRR *self,
+ gpointer source_id,
+ guint *out_idx)
+{
+ guint i;
+
+ for (i = 0; i < self->priv->sources->len; i++) {
+ Source *s;
+
+ s = g_ptr_array_index (self->priv->sources, i);
+ if (s->id == source_id) {
+ if (out_idx)
+ *out_idx = i;
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+static Source *
+find_next_source (MMPortSchedulerRR *self,
+ guint *out_idx)
+{
+ guint i, idx;
+
+ /* Starting at the source *after* the current source, advance through
+ * the entire array and back to the current source (in case only the
+ * current source has pending commands) to find the next source with
+ * a pending command.
+ */
+ for (i = 0, idx = self->priv->cur_source + 1;
+ i < self->priv->sources->len;
+ i++, idx++) {
+ Source *s;
+
+ /* Wrap around */
+ if (idx >= self->priv->sources->len)
+ idx = 0;
+
+ s = g_ptr_array_index (self->priv->sources, idx);
+ if (s->num_pending > 0) {
+ if (out_idx)
+ *out_idx = idx;
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+static void schedule_next_command (MMPortSchedulerRR *self);
+
+static gboolean
+run_next_command (MMPortSchedulerRR *self)
+{
+ self->priv->next_pending_id = 0;
+
+ if (find_next_source (self, &self->priv->cur_source)) {
+ Source *s;
+
+ s = g_ptr_array_index (self->priv->sources, self->priv->cur_source);
+ /* If this source has a pending command, run it. */
+ self->priv->in_command = TRUE;
+ g_signal_emit (MM_PORT_SCHEDULER (self),
+ send_command_signal,
+ 0,
+ s->id);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_next_command (MMPortSchedulerRR *self)
+{
+ guint next_idx = 0;
+ guint delay = 0;
+
+ if (self->priv->next_pending_id || self->priv->in_command || !find_next_source (self, &next_idx))
+ return;
+
+ /* Only delay next command if we change sources and this isn't the
+ * first time we're running a command.
+ */
+ if (next_idx != self->priv->cur_source && self->priv->cur_source < self->priv->sources->len)
+ delay = self->priv->inter_port_delay;
+ self->priv->next_pending_id = g_timeout_add (delay, (GSourceFunc) run_next_command, self);
+}
+
+static void
+register_source (MMPortScheduler *scheduler,
+ gpointer source_id,
+ const gchar *tag)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (scheduler);
+ Source *s;
+
+ g_assert (source_id != NULL);
+
+ s = find_source (self, source_id, NULL);
+ if (!s) {
+ s = g_slice_new0 (Source);
+ s->id = source_id;
+ s->tag = g_strdup (tag);
+ g_ptr_array_add (self->priv->sources, s);
+ }
+}
+
+static void
+unregister_source (MMPortScheduler *scheduler, gpointer source_id)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (scheduler);
+ Source *s;
+ guint idx = 0;
+
+ g_assert (source_id != NULL);
+
+ s = find_source (self, source_id, &idx);
+ if (s) {
+ g_ptr_array_remove_index (self->priv->sources, idx);
+
+ /* If we just removed the current source, advance to the next one */
+ if (self->priv->cur_source == idx)
+ schedule_next_command (self);
+ }
+}
+
+static void
+notify_num_pending (MMPortScheduler *scheduler,
+ gpointer source_id,
+ guint num_pending)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (scheduler);
+ Source *s;
+
+ g_assert (source_id != NULL);
+
+ s = find_source (self, source_id, NULL);
+ if (s && s->num_pending != num_pending) {
+ s->num_pending = num_pending;
+ schedule_next_command (self);
+ }
+}
+
+static void
+notify_command_done (MMPortScheduler *scheduler,
+ gpointer source_id,
+ guint num_pending)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (scheduler);
+ Source *s;
+ guint idx = 0;
+
+ g_assert (source_id != NULL);
+
+ s = find_source (self, source_id, &idx);
+ if (!s) {
+ mm_obj_warn (self, "unknown source %p notified command-done", source_id);
+ return;
+ }
+
+ /* Only the current source gets to call this function */
+ if (self->priv->cur_source != idx) {
+ mm_obj_warn (self,
+ "source %p notified command-done but not active source",
+ source_id);
+ return;
+ }
+
+ self->priv->in_command = FALSE;
+ s->num_pending = num_pending;
+ schedule_next_command (self);
+}
+
+/*****************************************************************************/
+
+MMPortSchedulerRR *
+mm_port_scheduler_rr_new (void)
+{
+ return MM_PORT_SCHEDULER_RR (g_object_new (MM_TYPE_PORT_SCHEDULER_RR, NULL));
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (object);
+
+ switch (prop_id) {
+ case PROP_INTER_PORT_DELAY:
+ g_value_set_uint (value, self->priv->inter_port_delay);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (object);
+
+ switch (prop_id) {
+ case PROP_INTER_PORT_DELAY:
+ self->priv->inter_port_delay = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_port_scheduler_iface_init (MMPortSchedulerInterface *scheduler_iface)
+{
+ scheduler_iface->register_source = register_source;
+ scheduler_iface->unregister_source = unregister_source;
+ scheduler_iface->notify_num_pending = notify_num_pending;
+ scheduler_iface->notify_command_done = notify_command_done;
+
+ send_command_signal = g_signal_lookup (MM_PORT_SCHEDULER_SIGNAL_SEND_COMMAND,
+ MM_TYPE_PORT_SCHEDULER);
+}
+
+static void
+mm_port_scheduler_rr_init (MMPortSchedulerRR *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_PORT_SCHEDULER_RR,
+ MMPortSchedulerRRPrivate);
+ self->priv->sources = g_ptr_array_new_full (2, (GDestroyNotify) source_free);
+ self->priv->cur_source = G_MAXUINT32;
+}
+
+static void
+dispose (GObject *object)
+{
+ MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (object);
+
+ if (self->priv->next_pending_id) {
+ g_source_remove (self->priv->next_pending_id);
+ self->priv->next_pending_id = 0;
+ }
+
+ g_assert (self->priv->sources->len == 0);
+ g_ptr_array_free (self->priv->sources, TRUE);
+
+ G_OBJECT_CLASS (mm_port_scheduler_rr_parent_class)->dispose (object);
+}
+
+static void
+mm_port_scheduler_rr_class_init (MMPortSchedulerRRClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+
+ g_object_class_install_property
+ (object_class, PROP_INTER_PORT_DELAY,
+ g_param_spec_uint (MM_PORT_SCHEDULER_RR_INTER_PORT_DELAY,
+ "Inter-port Delay",
+ "Inter-port delay in ms",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+}