diff options
-rw-r--r-- | src/meson.build | 4 | ||||
-rw-r--r-- | src/mm-log-object.c | 13 | ||||
-rw-r--r-- | src/mm-log-object.h | 1 | ||||
-rw-r--r-- | src/mm-port-scheduler-rr.c | 393 | ||||
-rw-r--r-- | src/mm-port-scheduler-rr.h | 53 | ||||
-rw-r--r-- | src/mm-port-scheduler.c | 74 | ||||
-rw-r--r-- | src/mm-port-scheduler.h | 71 | ||||
-rw-r--r-- | src/mm-port-serial.c | 118 | ||||
-rw-r--r-- | src/mm-port-serial.h | 1 | ||||
-rw-r--r-- | src/mm-port.c | 3 | ||||
-rw-r--r-- | src/tests/meson.build | 1 | ||||
-rw-r--r-- | src/tests/test-port-scheduler.c | 640 |
12 files changed, 1361 insertions, 11 deletions
diff --git a/src/meson.build b/src/meson.build index 6e1aae89..10838cf1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -134,6 +134,8 @@ headers = files( 'mm-iface-port-at.h', 'mm-port.h', 'mm-port-serial-at.h', + 'mm-port-scheduler.h', + 'mm-port-scheduler-rr.h', ) sources = files( @@ -146,6 +148,8 @@ sources = files( 'mm-port-serial-gps.c', 'mm-port-serial-qcdm.c', 'mm-serial-parsers.c', + 'mm-port-scheduler.c', + 'mm-port-scheduler-rr.c', ) deps = [libkerneldevice_dep] diff --git a/src/mm-log-object.c b/src/mm-log-object.c index ed41355f..64e4b407 100644 --- a/src/mm-log-object.c +++ b/src/mm-log-object.c @@ -63,7 +63,7 @@ mm_log_object_get_id (MMLogObject *self) gchar *self_id; self_id = MM_LOG_OBJECT_GET_IFACE (self)->build_id (self); - if (priv->owner_id) { + if (self_id && priv->owner_id) { priv->id = g_strdup_printf ("%s/%s", priv->owner_id, self_id); g_free (self_id); } else @@ -81,6 +81,17 @@ mm_log_object_set_owner_id (MMLogObject *self, priv = get_private (self); g_free (priv->owner_id); priv->owner_id = g_strdup (owner_id); + + mm_log_object_reset_id (self); +} + +void +mm_log_object_reset_id (MMLogObject *self) +{ + Private *priv; + + priv = get_private (self); + g_clear_pointer (&priv->id, g_free); } static void diff --git a/src/mm-log-object.h b/src/mm-log-object.h index 21ffc05e..9ddc7832 100644 --- a/src/mm-log-object.h +++ b/src/mm-log-object.h @@ -34,5 +34,6 @@ struct _MMLogObjectInterface const gchar *mm_log_object_get_id (MMLogObject *self); void mm_log_object_set_owner_id (MMLogObject *self, const gchar *owner_id); +void mm_log_object_reset_id (MMLogObject *self); #endif /* MM_LOG_OBJECT_H */ diff --git a/src/mm-port-scheduler-rr.c b/src/mm-port-scheduler-rr.c new file mode 100644 index 00000000..cfa3a28a --- /dev/null +++ b/src/mm-port-scheduler-rr.c @@ -0,0 +1,393 @@ +/* -*- 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); +static void log_object_iface_init (MMLogObjectInterface *iface); + +struct _MMPortSchedulerRRPrivate { + guint instance_id; + 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; +static guint instance_id_last = 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) + G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, + log_object_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); + + g_assert_cmpint (self->priv->sources->len, <, UINT_MAX); + mm_obj_dbg (self, "[%s] source id %p registered", tag, source_id); + mm_log_object_reset_id (MM_LOG_OBJECT (self)); + } +} + +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) { + mm_obj_dbg (self, "[%s] source id %p unregistered", s->tag, s->id); + g_ptr_array_remove_index (self->priv->sources, idx); + mm_log_object_reset_id (MM_LOG_OBJECT (self)); + + /* 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, "[%s] notified command-done but not active source", s->tag); + 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 gchar * +log_object_build_id (MMLogObject *_self) +{ + MMPortSchedulerRR *self = MM_PORT_SCHEDULER_RR (_self); + g_autoptr(GString) str; + guint i; + + str = g_string_sized_new (16); + for (i = 0; i < self->priv->sources->len; i++) { + Source *s; + + s = g_ptr_array_index (self->priv->sources, i); + if (str->len) + g_string_append_c (str, ','); + g_string_append (str, s->tag); + } + + return g_strdup_printf ("scheduler-%u (%s)", self->priv->instance_id, str->str); +} + +static void +log_object_iface_init (MMLogObjectInterface *iface) +{ + iface->build_id = log_object_build_id; +} + +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; + self->priv->instance_id = instance_id_last++; +} + +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)); +} diff --git a/src/mm-port-scheduler-rr.h b/src/mm-port-scheduler-rr.h new file mode 100644 index 00000000..6e750872 --- /dev/null +++ b/src/mm-port-scheduler-rr.h @@ -0,0 +1,53 @@ +/* -*- 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> + */ + +#ifndef _MM_PORT_SCHEDULER_RR_H_ +#define _MM_PORT_SCHEDULER_RR_H_ + +#include <glib-object.h> +#include <gio/gio.h> + +#include "mm-port-scheduler.h" + +#define MM_TYPE_PORT_SCHEDULER_RR (mm_port_scheduler_rr_get_type ()) +#define MM_PORT_SCHEDULER_RR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PORT_SCHEDULER_RR, MMPortSchedulerRR)) +#define MM_PORT_SCHEDULER_RR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PORT_SCHEDULER_RR, MMPortSchedulerRRClass)) +#define MM_IS_PORT_SCHEDULER_RR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PORT_SCHEDULER_RR)) +#define MM_IS_PORT_SCHEDULER_RR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PORT_SCHEDULER_RR)) +#define MM_PORT_SCHEDULER_RR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PORT_SCHEDULER_RR, MMPortSchedulerRRClass)) + +#define MM_PORT_SCHEDULER_RR_INTER_PORT_DELAY "inter-port-delay" + +typedef struct _MMPortSchedulerRR MMPortSchedulerRR; +typedef struct _MMPortSchedulerRRClass MMPortSchedulerRRClass; +typedef struct _MMPortSchedulerRRPrivate MMPortSchedulerRRPrivate; + +struct _MMPortSchedulerRR { + /*< private >*/ + GObject parent; + MMPortSchedulerRRPrivate *priv; +}; + +struct _MMPortSchedulerRRClass { + /*< private >*/ + GObjectClass parent; +}; + +GType mm_port_scheduler_rr_get_type (void); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMPortSchedulerRR, g_object_unref) + +MMPortSchedulerRR *mm_port_scheduler_rr_new (void); + +#endif /* _MM_PORT_SCHEDULER_RR_H_ */ diff --git a/src/mm-port-scheduler.c b/src/mm-port-scheduler.c new file mode 100644 index 00000000..c1790a1c --- /dev/null +++ b/src/mm-port-scheduler.c @@ -0,0 +1,74 @@ +/* -*- 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.h" + +G_DEFINE_INTERFACE (MMPortScheduler, mm_port_scheduler, G_TYPE_OBJECT) + +enum { + SIGNAL_SEND_COMMAND, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = { 0 }; + +/*****************************************************************************/ + +void +mm_port_scheduler_register_source (MMPortScheduler *self, + gpointer source, + const gchar *tag) +{ + MM_PORT_SCHEDULER_GET_INTERFACE (self)->register_source (self, source, tag); +} + +void +mm_port_scheduler_unregister_source (MMPortScheduler *self, + gpointer source) +{ + MM_PORT_SCHEDULER_GET_INTERFACE (self)->unregister_source (self, source); +} + +void +mm_port_scheduler_notify_num_pending (MMPortScheduler *self, + gpointer source, + guint num_pending) +{ + MM_PORT_SCHEDULER_GET_INTERFACE (self)->notify_num_pending (self, source, num_pending); +} + +void +mm_port_scheduler_notify_command_done (MMPortScheduler *self, + gpointer source, + guint num_pending) +{ + MM_PORT_SCHEDULER_GET_INTERFACE (self)->notify_command_done (self, source, num_pending); +} + +/*****************************************************************************/ + +static void +mm_port_scheduler_default_init (MMPortSchedulerInterface *iface) +{ + signals[SIGNAL_SEND_COMMAND] = + g_signal_new (MM_PORT_SCHEDULER_SIGNAL_SEND_COMMAND, + MM_TYPE_PORT_SCHEDULER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MMPortSchedulerInterface, send_command), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_POINTER); +} diff --git a/src/mm-port-scheduler.h b/src/mm-port-scheduler.h new file mode 100644 index 00000000..d67ddc24 --- /dev/null +++ b/src/mm-port-scheduler.h @@ -0,0 +1,71 @@ +/* -*- 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> + */ + +#ifndef MM_PORT_SCHEDULER_H +#define MM_PORT_SCHEDULER_H + +#include <glib.h> +#include <glib-object.h> + +#define MM_TYPE_PORT_SCHEDULER mm_port_scheduler_get_type () +#define MM_PORT_SCHEDULER_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), MM_TYPE_PORT_SCHEDULER, MMPortSchedulerInterface)) + +G_DECLARE_INTERFACE (MMPortScheduler, mm_port_scheduler, MM, PORT_SCHEDULER, GObject) + +#define MM_PORT_SCHEDULER_SIGNAL_SEND_COMMAND "send-command" + +typedef struct _MMPortSchedulerInterface MMPortSchedulerInterface; + +struct _MMPortSchedulerInterface +{ + GTypeInterface g_iface; + + /* Signals */ + void (*send_command) (MMPortScheduler *self, + gpointer source); + + /* Methods */ + void (*register_source) (MMPortScheduler *self, + gpointer source, + const gchar *tag); + + void (*unregister_source) (MMPortScheduler *self, + gpointer source); + + void (*notify_num_pending) (MMPortScheduler *self, + gpointer source, + guint num_pending); + + void (*notify_command_done) (MMPortScheduler *self, + gpointer source, + guint num_pending); +}; + +void mm_port_scheduler_register_source (MMPortScheduler *self, + gpointer source, + const gchar *tag); + +void mm_port_scheduler_unregister_source (MMPortScheduler *self, + gpointer source); + +void mm_port_scheduler_notify_num_pending (MMPortScheduler *self, + gpointer source, + guint num_pending); + +void mm_port_scheduler_notify_command_done (MMPortScheduler *self, + gpointer source, + guint num_pending); + +#endif /* MM_PORT_SCHEDULER_H */ diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c index 8b9bef98..d1976972 100644 --- a/src/mm-port-serial.c +++ b/src/mm-port-serial.c @@ -36,6 +36,8 @@ #include "mm-port-serial.h" #include "mm-log-object.h" #include "mm-helper-enums-types.h" +#include "mm-port-scheduler.h" +#include "mm-port-scheduler-rr.h" static gboolean port_serial_queue_process (gpointer data); static void port_serial_schedule_queue_process (MMPortSerial *self, @@ -59,6 +61,7 @@ enum { PROP_FD, PROP_SPEW_CONTROL, PROP_FLASH_OK, + PROP_SCHEDULER, LAST_PROP }; @@ -81,6 +84,10 @@ struct _MMPortSerialPrivate { GQueue *queue; GByteArray *response; + /* Command scheduler */ + MMPortScheduler *scheduler; + guint scheduler_send_id; + /* For real ports, iochannel, and we implement the eagain limit */ GIOChannel *iochannel; guint iochannel_id; @@ -89,7 +96,6 @@ struct _MMPortSerialPrivate { GSocket *socket; GSource *socket_source; - guint baud; guint bits; char parity; @@ -194,8 +200,9 @@ mm_port_serial_command (MMPortSerial *self, else g_queue_push_tail (self->priv->queue, task); - if (g_queue_get_length (self->priv->queue) == 1) - port_serial_schedule_queue_process (self, 0); + mm_port_scheduler_notify_num_pending (self->priv->scheduler, + self, + g_queue_get_length (self->priv->queue)); } /*****************************************************************************/ @@ -737,10 +744,11 @@ port_serial_got_response (MMPortSerial *self, } g_object_unref (task); - } - if (!g_queue_is_empty (self->priv->queue)) - port_serial_schedule_queue_process (self, 0); + mm_port_scheduler_notify_command_done (self->priv->scheduler, + self, + g_queue_get_length (self->priv->queue)); + } } g_object_unref (self); g_clear_error (&error); @@ -805,6 +813,7 @@ port_serial_queue_process (gpointer data) GCancellable *cancellable; GError *error = NULL; + g_assert (self->priv->timeout_id == 0); self->priv->queue_id = 0; task = g_queue_peek_head (self->priv->queue); @@ -878,6 +887,21 @@ port_serial_queue_process (gpointer data) } static void +scheduler_send_command (MMPortScheduler *scheduler, + gpointer source, + gpointer user_data) +{ + MMPortSerial *self = MM_PORT_SERIAL (user_data); + + /* Must be for us */ + if (source == self) { + g_assert (self->priv->queue_id == 0); + g_assert (self->priv->timeout_id == 0); + port_serial_queue_process (self); + } +} + +static void parse_response_buffer (MMPortSerial *self) { GError *error = NULL; @@ -1444,6 +1468,8 @@ _close_internal (MMPortSerial *self, gboolean force) } g_queue_clear (self->priv->queue); + mm_port_scheduler_notify_num_pending (self->priv->scheduler, self, 0); + if (self->priv->timeout_id) { g_source_remove (self->priv->timeout_id); self->priv->timeout_id = 0; @@ -1889,6 +1915,35 @@ mm_port_serial_get_flow_control (MMPortSerial *self) /*****************************************************************************/ +static void +scheduler_setup (MMPortSerial *self, MMPortScheduler *scheduler) +{ + self->priv->scheduler = scheduler; + if (self->priv->scheduler) { + const gchar *port_name; + + port_name = mm_port_get_device (MM_PORT (self)); + mm_port_scheduler_register_source (self->priv->scheduler, self, port_name); + self->priv->scheduler_send_id = g_signal_connect (self->priv->scheduler, + MM_PORT_SCHEDULER_SIGNAL_SEND_COMMAND, + G_CALLBACK (scheduler_send_command), + self); + } +} + +static void +scheduler_cleanup (MMPortSerial *self) +{ + if (self->priv->scheduler) { + mm_port_scheduler_unregister_source (self->priv->scheduler, self); + g_signal_handler_disconnect (self->priv->scheduler, self->priv->scheduler_send_id); + self->priv->scheduler_send_id = 0; + } + g_clear_object (&self->priv->scheduler); +} + +/*****************************************************************************/ + MMPortSerial * mm_port_serial_new (const char *name, MMPortType ptype) { @@ -1962,6 +2017,16 @@ mm_port_serial_init (MMPortSerial *self) } static void +constructed (GObject *object) +{ + MMPortSerial *self = MM_PORT_SERIAL (object); + + /* Add a default scheduler if none was set via GObject properties */ + if (!self->priv->scheduler) + scheduler_setup (self, MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ())); +} + +static void set_property (GObject *object, guint prop_id, const GValue *value, @@ -1997,6 +2062,10 @@ set_property (GObject *object, case PROP_FLASH_OK: self->priv->flash_ok = g_value_get_boolean (value); break; + case PROP_SCHEDULER: + scheduler_cleanup (self); + scheduler_setup (self, g_value_dup_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2039,6 +2108,9 @@ get_property (GObject *object, case PROP_FLASH_OK: g_value_set_boolean (value, self->priv->flash_ok); break; + case PROP_SCHEDULER: + g_value_set_object (value, self->priv->scheduler); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2046,7 +2118,7 @@ get_property (GObject *object, } static void -finalize (GObject *object) +dispose (GObject *object) { MMPortSerial *self = MM_PORT_SERIAL (object); @@ -2059,14 +2131,30 @@ finalize (GObject *object) g_assert (self->priv->socket == NULL); g_assert (self->priv->socket_source == NULL); - if (self->priv->timeout_id) + if (self->priv->timeout_id) { g_source_remove (self->priv->timeout_id); + self->priv->timeout_id = 0; + } - if (self->priv->queue_id) + if (self->priv->queue_id) { g_source_remove (self->priv->queue_id); + self->priv->queue_id = 0; + } - g_hash_table_destroy (self->priv->reply_cache); g_byte_array_unref (self->priv->response); + self->priv->response = NULL; + + scheduler_cleanup (self); + + G_OBJECT_CLASS (mm_port_serial_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + MMPortSerial *self = MM_PORT_SERIAL (object); + + g_hash_table_destroy (self->priv->reply_cache); g_queue_free (self->priv->queue); G_OBJECT_CLASS (mm_port_serial_parent_class)->finalize (object); @@ -2080,8 +2168,10 @@ mm_port_serial_class_init (MMPortSerialClass *klass) g_type_class_add_private (object_class, sizeof (MMPortSerialPrivate)); /* Virtual methods */ + object_class->constructed = constructed; object_class->set_property = set_property; object_class->get_property = get_property; + object_class->dispose = dispose; object_class->finalize = finalize; klass->config_fd = real_config_fd; @@ -2161,6 +2251,14 @@ mm_port_serial_class_init (MMPortSerialClass *klass) TRUE, G_PARAM_READWRITE)); + g_object_class_install_property + (object_class, PROP_SCHEDULER, + g_param_spec_object (MM_PORT_SERIAL_SCHEDULER, + "Scheduler", + "Command scheduler object (optional)", + MM_TYPE_PORT_SCHEDULER, + G_PARAM_READWRITE)); + /* Signals */ signals[BUFFER_FULL] = g_signal_new ("buffer-full", diff --git a/src/mm-port-serial.h b/src/mm-port-serial.h index 59daadd4..f050bdc3 100644 --- a/src/mm-port-serial.h +++ b/src/mm-port-serial.h @@ -40,6 +40,7 @@ #define MM_PORT_SERIAL_FD "fd" /* Construct-only */ #define MM_PORT_SERIAL_SPEW_CONTROL "spew-control" #define MM_PORT_SERIAL_FLASH_OK "flash-ok" +#define MM_PORT_SERIAL_SCHEDULER "scheduler" typedef enum { MM_PORT_SERIAL_RESPONSE_NONE, diff --git a/src/mm-port.c b/src/mm-port.c index da37f902..7102770d 100644 --- a/src/mm-port.c +++ b/src/mm-port.c @@ -132,6 +132,9 @@ log_object_build_id (MMLogObject *_self) MMPort *self; self = MM_PORT (_self); + if (!self->priv->device || !self->priv->ptype) + return NULL; + return g_strdup_printf ("%s/%s", mm_port_get_device (self), mm_port_type_get_string (mm_port_get_port_type (self))); diff --git a/src/tests/meson.build b/src/tests/meson.build index ea9ea3a3..5c6764b9 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -8,6 +8,7 @@ test_units = { 'error-helpers': libhelpers_dep, 'kernel-device-helpers': libkerneldevice_dep, 'modem-helpers': libhelpers_dep, + 'port-scheduler': libport_dep, 'sms-part-3gpp': libhelpers_dep, 'sms-part-cdma': libhelpers_dep, 'sms-list': libsms_dep, diff --git a/src/tests/test-port-scheduler.c b/src/tests/test-port-scheduler.c new file mode 100644 index 00000000..8f8fbeaa --- /dev/null +++ b/src/tests/test-port-scheduler.c @@ -0,0 +1,640 @@ +/* -*- 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 <glib.h> +#include <string.h> +#include <locale.h> +#include <stdio.h> + +#include "mm-port-scheduler-rr.h" +#include "mm-log-test.h" + +static GMainLoop *loop; + +/*****************************************************************************/ + +typedef struct { + MMPortScheduler *sched; + guint sig_id; + gpointer source_id; + gint num_pending; + gboolean immediate; + guint idle_id; + + gpointer data; + gpointer data2; +} TestSourceCtx; + +static void +test_source_setup (TestSourceCtx *ctx, + MMPortScheduler *sched, + GCallback send_cmd_func) +{ + ctx->sched = g_object_ref (sched); + mm_port_scheduler_register_source (sched, ctx->source_id, "test"); + ctx->sig_id = g_signal_connect (sched, + MM_PORT_SCHEDULER_SIGNAL_SEND_COMMAND, + send_cmd_func, + ctx); + mm_port_scheduler_notify_num_pending (ctx->sched, ctx->source_id, ctx->num_pending); +} + +static void +test_source_cleanup (TestSourceCtx *ctx) +{ + g_assert_cmpint (ctx->num_pending, ==, 0); + g_signal_handler_disconnect (ctx->sched, ctx->sig_id); + mm_port_scheduler_unregister_source (ctx->sched, ctx->source_id); + g_object_unref (ctx->sched); +} + +/*****************************************************************************/ + +static gboolean +test_ss_command_done (TestSourceCtx *ctx) +{ + ctx->idle_id = 0; + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + if (ctx->num_pending == 0) + g_main_loop_quit (loop); + return G_SOURCE_REMOVE; +} + +static void +test_ss_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + g_assert (scheduler == ctx->sched); + g_assert (source == ctx->source_id); + ctx->num_pending--; + g_assert_cmpint (ctx->num_pending, >=, 0); + + if (ctx->immediate) { + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + if (ctx->num_pending == 0) + g_main_loop_quit (loop); + } else + ctx->idle_id = g_idle_add ((GSourceFunc)test_ss_command_done, ctx); +} + +static void +test_ss_done (gboolean immediate) +{ + MMPortScheduler *sched; + TestSourceCtx ctx = { + .source_id = GUINT_TO_POINTER (0x1), + .num_pending = 10, + .immediate = immediate, + }; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx, sched, G_CALLBACK (test_ss_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_ds_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + ctx->num_pending--; + g_assert_cmpuint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + /* assert that the scheduler alternates between the two sources */ + g_assert_cmpuint ((*counter) % 2, ==, ctx->idle_id); + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_ordering (void) +{ + MMPortScheduler *sched; + guint counter; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .num_pending = 10, + .data = &counter, + .idle_id = 0, /* expected value of (counter % 2) */ + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .num_pending = 10, + .data = &counter, + .idle_id = 1, /* expected value of (counter % 2) */ + }; + + counter = ctx1.num_pending + ctx2.num_pending; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_ds_uneven_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + ctx->num_pending--; + /* Test that the scheduler only calls each source for the number of pending + * commands it has notified the scheduler are in its queue. + */ + g_assert_cmpint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_uneven_num_pending (void) +{ + MMPortScheduler *sched; + guint counter; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .num_pending = 10, + .data = &counter, + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .num_pending = 5, + .data = &counter, + }; + + counter = ctx1.num_pending + ctx2.num_pending; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_uneven_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_uneven_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static gboolean +ds_later_notify_pending (TestSourceCtx *ctx) +{ + mm_port_scheduler_notify_num_pending (ctx->sched, ctx->source_id, ctx->num_pending); + return G_SOURCE_REMOVE; +} + +static void +test_ds_later_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + ctx->num_pending--; + g_assert_cmpint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + /* After we've reached zero pending commands wait a short time and then + * add more to make sure the scheduler wakes up and processes the new pending + * requests */ + if (ctx->num_pending == 0 && ctx->idle_id > 0) { + ctx->num_pending = ctx->idle_id; + ctx->idle_id = 0; + g_timeout_add_seconds (GPOINTER_TO_UINT (ctx->source_id), + (GSourceFunc)ds_later_notify_pending, + ctx); + } + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_num_pending_later (void) +{ + MMPortScheduler *sched; + guint counter; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .num_pending = 5, + .data = &counter, + .idle_id = 2, /* num pending to add after original num_pending reaches 0 */ + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .num_pending = 4, + .data = &counter, + .idle_id = 1, /* num pending to add after original num_pending reaches 0 */ + }; + + counter = ctx1.num_pending + ctx2.num_pending + ctx1.idle_id + ctx2.idle_id; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_later_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_later_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static gboolean +quit_loop (void) +{ + g_main_loop_quit (loop); + return G_SOURCE_REMOVE; +} + +static void +test_ds_bad_notify_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + g_assert (scheduler == ctx->sched); + + if (source != ctx->source_id) + return; + + if (GPOINTER_TO_UINT (source) == 0x2) { + /* Second source without any pending commands tries to call + * notify_command_done but this should have no effect; the scheduler + * should ignore the num_pending given here. + */ + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, 15); + return; + } + + g_assert_cmpuint (GPOINTER_TO_UINT (source), ==, 0x1); + + ctx->num_pending--; + g_assert_cmpint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + if (ctx->num_pending == 0) { + /* Schedule a timeout to quit the mainloop to give enough time for + * the scheduler to mess up (which we don't expect). + */ + g_timeout_add_seconds (1, (GSourceFunc)quit_loop, NULL); + } +} + +static gboolean +assert_not_reached (void) +{ + g_assert_not_reached (); + return G_SOURCE_REMOVE; +} + +static void +test_ds_bad_notify_done (void) +{ + MMPortScheduler *sched; + guint timeout_id; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .num_pending = 5, + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .num_pending = 0, + /* This source just hammers notify_done even though it never has any + * pending commands. + */ + }; + + timeout_id = g_timeout_add_seconds (3, (GSourceFunc)assert_not_reached, NULL); + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_bad_notify_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_bad_notify_send_command)); + + g_main_loop_run (loop); + + g_source_remove (timeout_id); + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_ds_delay_notify_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + gint64 *last_call = ctx->data2; + gint64 now; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + /* Ensure there was at least the inter-port delay time since the last call */ + now = g_get_monotonic_time (); + g_assert_cmpint (now - *last_call, >=, 500); + *last_call = now; + + ctx->num_pending--; + g_assert_cmpuint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_inter_port_delay (void) +{ + MMPortScheduler *sched; + guint counter; + gint64 last_call = 0; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .data = &counter, + .data2 = &last_call, + .num_pending = 5, + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .data = &counter, + .data2 = &last_call, + .num_pending = 5, + }; + + counter = ctx1.num_pending + ctx2.num_pending; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + g_object_set (sched, MM_PORT_SCHEDULER_RR_INTER_PORT_DELAY, 500, NULL); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_delay_notify_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_delay_notify_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_ds_no_delay_notify_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + gint64 *last_call = ctx->data2; + gint64 now; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + /* Since the second source has no pending commands, there should be + * no delay between calls since only one source is executing. + */ + now = g_get_monotonic_time (); + g_assert_cmpint (now - *last_call, <, 1000); + *last_call = now; + + ctx->num_pending--; + g_assert_cmpuint (ctx->num_pending, >=, 0); + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_inter_port_no_delay (void) +{ + MMPortScheduler *sched; + guint counter; + gint64 last_call; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .data = &counter, + .data2 = &last_call, + .num_pending = 5, + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .data = &counter, + .data2 = &last_call, + .num_pending = 0, + }; + + counter = ctx1.num_pending + ctx2.num_pending; + last_call = g_get_monotonic_time (); + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_no_delay_notify_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_no_delay_notify_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_ds_pending_during_done_notify_send_command (MMPortScheduler *scheduler, + gpointer source, + TestSourceCtx *ctx) +{ + guint *counter = ctx->data; + + g_assert (scheduler == ctx->sched); + if (source != ctx->source_id) + return; + + ctx->num_pending--; + g_assert_cmpuint (ctx->num_pending, >=, 0); + + /* Simulate command completion adding more pending commands before calling command-done */ + if (ctx->idle_id > 0) { + ctx->idle_id--; + ctx->num_pending++; /* increase length of fake source's command queue */ + mm_port_scheduler_notify_num_pending (ctx->sched, ctx->source_id, ctx->num_pending); + } + + mm_port_scheduler_notify_command_done (ctx->sched, ctx->source_id, ctx->num_pending); + + (*counter)--; + if (*counter == 0) + g_main_loop_quit (loop); +} + +static void +test_ds_pending_during_done (void) +{ + MMPortScheduler *sched; + guint counter; + + TestSourceCtx ctx1 = { + .source_id = GUINT_TO_POINTER (0x1), + .data = &counter, + .idle_id = 5, /* additional to add during notify-command-done */ + .num_pending = 5, + }; + + TestSourceCtx ctx2 = { + .source_id = GUINT_TO_POINTER (0x2), + .data = &counter, + .idle_id = 5, /* additional to add during notify-command-done */ + .num_pending = 5, + }; + + counter = ctx1.num_pending + ctx2.num_pending + ctx1.idle_id + ctx2.idle_id; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx1, sched, G_CALLBACK (test_ds_pending_during_done_notify_send_command)); + test_source_setup (&ctx2, sched, G_CALLBACK (test_ds_pending_during_done_notify_send_command)); + + g_main_loop_run (loop); + + test_source_cleanup (&ctx1); + test_source_cleanup (&ctx2); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_errors_bad_source_done (void) +{ + MMPortScheduler *sched; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + mm_port_scheduler_notify_command_done (sched, GUINT_TO_POINTER (0x1), 5); + g_object_unref (sched); +} + +/*****************************************************************************/ + +static void +test_errors_source_done_before_loop (void) +{ + MMPortScheduler *sched; + + TestSourceCtx ctx = { + .source_id = GUINT_TO_POINTER (0x1), + }; + + sched = MM_PORT_SCHEDULER (mm_port_scheduler_rr_new ()); + test_source_setup (&ctx, sched, G_CALLBACK (assert_not_reached)); + mm_port_scheduler_notify_command_done (sched, ctx.source_id, 5); + + test_source_cleanup (&ctx); + g_object_unref (sched); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + int ret; + + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + loop = g_main_loop_new (NULL, FALSE); + + g_test_add_data_func ("/MM/port-scheduler/single-source/done-immediate", GUINT_TO_POINTER (TRUE), (GTestDataFunc)test_ss_done); + g_test_add_data_func ("/MM/port-scheduler/single-source/done-idle", GUINT_TO_POINTER (FALSE), (GTestDataFunc)test_ss_done); + g_test_add_data_func ("/MM/port-scheduler/dual-source/ordering", NULL, (GTestDataFunc)test_ds_ordering); + g_test_add_data_func ("/MM/port-scheduler/dual-source/uneven-num-pending", NULL, (GTestDataFunc)test_ds_uneven_num_pending); + g_test_add_data_func ("/MM/port-scheduler/dual-source/num-pending-later", NULL, (GTestDataFunc)test_ds_num_pending_later); + g_test_add_data_func ("/MM/port-scheduler/dual-source/bad-notify-done", NULL, (GTestDataFunc)test_ds_bad_notify_done); + g_test_add_data_func ("/MM/port-scheduler/dual-source/inter-port-delay", NULL, (GTestDataFunc)test_ds_inter_port_delay); + g_test_add_data_func ("/MM/port-scheduler/dual-source/inter-port-no-delay", NULL, (GTestDataFunc)test_ds_inter_port_no_delay); + g_test_add_data_func ("/MM/port-scheduler/dual-source/pending-during-done", NULL, (GTestDataFunc)test_ds_pending_during_done); + g_test_add_data_func ("/MM/port-scheduler/errors/bad-source-done", NULL, (GTestDataFunc)test_errors_bad_source_done); + g_test_add_data_func ("/MM/port-scheduler/errors/source-done-before-loop", NULL, (GTestDataFunc)test_errors_source_done_before_loop); + + ret = g_test_run(); + + g_main_loop_unref (loop); + + return ret; +} |