Merge request !1290 from 'port-scheduler'
port-serial: add command scheduler infrastructure and use it https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1290
This commit is contained in:
@@ -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]
|
||||
|
||||
+12
-1
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
+108
-10
@@ -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);
|
||||
@@ -877,6 +886,21 @@ port_serial_queue_process (gpointer data)
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -1961,6 +2016,16 @@ mm_port_serial_init (MMPortSerial *self)
|
||||
self->priv->response = g_byte_array_sized_new (500);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -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_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_byte_array_unref (self->priv->response);
|
||||
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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user