blob: 7d0618b49cf22b930011ad5a0ac4ce02da28e8e0 [file] [log] [blame]
/*
* Copyright (C) 2019 - 2020 Intel Corporation
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stddef.h>
#include <stdint.h>
#include <usfstl/assert.h>
#include <usfstl/sched.h>
#include <usfstl/list.h>
#include "internal.h"
uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched)
{
uint64_t current_time;
if (!sched->external_sync_from || !sched->waiting)
return sched->current_time;
current_time = sched->external_sync_from(sched);
/* update current time after sync */
usfstl_sched_set_time(sched, current_time);
return current_time;
}
static bool usfstl_sched_external_request(struct usfstl_scheduler *sched,
uint64_t time)
{
if (!sched->external_request)
return false;
/*
* If we received a next_external_sync point, we don't need to ask for
* runtime for anything earlier than that point, we're allowed to run.
* However, note that this only applies if we're not currently waiting,
* if we are in fact waiting for permission, then of course we need to
* ask for any earlier time, regardless of the next_external_sync, as
* we won't schedule until we get called to run, and that usually won't
* happen if we don't ask for it.
*/
if (!sched->waiting && sched->next_external_sync_set &&
usfstl_time_cmp(time, <, sched->next_external_sync))
return false;
/* If we asked for this time slot already, don't ask again but wait. */
if (sched->prev_external_sync_set && time == sched->prev_external_sync)
return true;
sched->prev_external_sync = time;
sched->prev_external_sync_set = 1;
sched->external_request(sched, time);
return true;
}
static void usfstl_sched_external_wait(struct usfstl_scheduler *sched)
{
/*
* Once we wait for the external scheduler, we have to ask again
* even if for some reason we end up asking for the same time.
*/
sched->prev_external_sync_set = 0;
sched->waiting = 1;
sched->external_wait(sched);
sched->waiting = 0;
}
void usfstl_sched_add_job(struct usfstl_scheduler *sched, struct usfstl_job *job)
{
struct usfstl_job *tmp;
USFSTL_ASSERT_TIME_CMP(job->start, >=, sched->current_time);
USFSTL_ASSERT(!usfstl_job_scheduled(job),
"cannot add a job that's already scheduled");
USFSTL_ASSERT_CMP(job->group, <, 32, "%u");
if ((1 << job->group) & sched->blocked_groups &&
job != sched->allowed_job) {
job->start = 0;
usfstl_list_append(&sched->pending_jobs, &job->entry);
return;
}
usfstl_for_each_list_item(tmp, &sched->joblist, entry) {
if (usfstl_time_cmp(tmp->start, >, job->start))
break;
if (tmp->start == job->start &&
tmp->priority < job->priority)
break;
}
/* insert after previous entry */
if (!tmp)
usfstl_list_append(&sched->joblist, &job->entry);
else
usfstl_list_insert_before(&tmp->entry, &job->entry);
/*
* Request the new job's runtime from the external scheduler
* (if configured); if this job doesn't request any earlier
* runtime than previously requested, this does nothing. It
* may, however, request earlier runtime if this is due to
* an interrupt we got from outside while waiting for the
* external scheduler.
*/
usfstl_sched_external_request(sched, job->start);
if (sched->next_time_changed)
sched->next_time_changed(sched);
}
bool usfstl_job_scheduled(struct usfstl_job *job)
{
return job->entry.next != NULL;
}
void usfstl_sched_del_job(struct usfstl_job *job)
{
if (!usfstl_job_scheduled(job))
return;
usfstl_list_item_remove(&job->entry);
}
void _usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
{
uint64_t delta;
if (sched->current_time == time)
return;
// check that we at least don't move backwards
USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
delta = time - sched->current_time;
sched->current_time = time;
if (sched->time_advanced)
sched->time_advanced(sched, delta);
}
void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
{
/*
* also check that we're not getting set to something later than what
* we requested, that'd be a bug since we want to run something at an
* earlier time than what we just got set to; unless we have nothing
* to do and thus don't care at all.
*/
USFSTL_ASSERT(usfstl_list_empty(&sched->joblist) ||
usfstl_time_cmp(time, <=, sched->prev_external_sync),
"scheduler time moves further (to %" PRIu64 ") than requested (%" PRIu64 ")",
time, sched->prev_external_sync);
_usfstl_sched_set_time(sched, time);
}
static void usfstl_sched_forward(struct usfstl_scheduler *sched, uint64_t until)
{
USFSTL_ASSERT_TIME_CMP(until, >=, sched->current_time);
if (usfstl_sched_external_request(sched, until)) {
usfstl_sched_external_wait(sched);
/*
* The external_wait() method must call
* usfstl_sched_set_time() before returning,
* so we don't in this case.
*/
return;
}
_usfstl_sched_set_time(sched, until);
}
void usfstl_sched_start(struct usfstl_scheduler *sched)
{
if (usfstl_sched_external_request(sched, sched->current_time))
usfstl_sched_external_wait(sched);
}
struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched)
{
while (true) {
struct usfstl_job *job = usfstl_sched_next_pending(sched, NULL);
if (!job) {
/*
* If external scheduler is active, we might get here
* with nothing to do, so we just need to wait for an
* external input/job which will add a job to our
* scheduler.
*
* Due to the fact that we don't have any API for
* cancelling external time requests, we might have
* requested time from the external scheduler for a
* job that subsequently got removed, ending up here
* without a job, or one further in the future which
* would cause usfstl_sched_forward() to wait again.
*
* Additionally, we might only remove the job we just
* found during the usfstl_sched_forward() below, if
* that causes the main loop to run and we detect an
* event that causes a job removal (such as a client
* disconnecting from a server), so the job pointer we
* have might go stale. Hence, all of this needs to be
* checked in the overall loop.
*/
if (sched->external_request) {
usfstl_sched_external_wait(sched);
continue;
}
break;
}
/*
* Forward, but only if job isn't in the past - this
* can happen if some job was inserted while we
* were in fact waiting for the external scheduler, i.e.
* some sort of external job happened while we thought
* there was nothing to do.
*/
if (usfstl_time_cmp(job->start, >, sched->current_time))
usfstl_sched_forward(sched, job->start);
/*
* Some sort of external job might have come to us (while
* we were stuck waiting for the external scheduler), and
* might have inserted an earlier job into the timeline.
* If it's not this job's turn yet, reinsert it and check
* what's up next in the next loop iteration.
*
* Also, 'job' might now have been removed, see above.
*/
if (usfstl_sched_next_pending(sched, NULL) != job)
continue;
/*
* Otherwise we've actually reached this job, so remove
* and call it.
*/
usfstl_sched_del_job(job);
job->callback(job);
return job;
}
/*
* We must not get here, if there's no job whatsoever the
* simulation has basically ended in an undefined state, even
* the main thread can no longer make progress.
*/
USFSTL_ASSERT(0, "scheduling while there's nothing to do");
}
void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time)
{
USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
sched->next_external_sync = time;
sched->next_external_sync_set = 1;
}
static void usfstl_sched_block_job(struct usfstl_scheduler *sched,
struct usfstl_job *job)
{
usfstl_sched_del_job(job);
usfstl_list_append(&sched->pending_jobs, &job->entry);
}
struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched,
struct usfstl_job *job)
{
return job ? usfstl_next_item(&sched->joblist, job, struct usfstl_job, entry) :
usfstl_list_first_item(&sched->joblist, struct usfstl_job, entry);
}
static void usfstl_sched_remove_blocked_jobs(struct usfstl_scheduler *sched)
{
struct usfstl_job *job = NULL, *next;
usfstl_for_each_list_item_continue_safe(job, next, &sched->joblist,
entry) {
if (job == sched->allowed_job)
continue;
if ((1 << job->group) & sched->blocked_groups)
usfstl_sched_block_job(sched, job);
}
}
static void usfstl_sched_restore_job(struct usfstl_scheduler *sched,
struct usfstl_job *job)
{
usfstl_sched_del_job(job);
if (usfstl_time_cmp(job->start, <, sched->current_time))
job->start = sched->current_time;
usfstl_sched_add_job(sched, job);
}
static void usfstl_sched_restore_blocked_jobs(struct usfstl_scheduler *sched)
{
struct usfstl_job *job = NULL, *next;
usfstl_for_each_list_item_continue_safe(job, next, &sched->pending_jobs,
entry) {
if (job == sched->allowed_job ||
!((1 << job->group) & sched->blocked_groups))
usfstl_sched_restore_job(sched, job);
}
}
void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups,
struct usfstl_job *job,
struct usfstl_sched_block_data *save)
{
save->groups = sched->blocked_groups;
save->job = sched->allowed_job;
// it makes no sense to allow a job unless its group is blocked
USFSTL_ASSERT(!job || (1 << job->group) & groups,
"allowed job group %d must be part of blocked groups (0x%x\n)",
job->group, groups);
sched->blocked_groups |= groups;
sched->allowed_job = job;
usfstl_sched_remove_blocked_jobs(sched);
}
void usfstl_sched_restore_groups(struct usfstl_scheduler *sched,
struct usfstl_sched_block_data *restore)
{
sched->blocked_groups = restore->groups;
sched->allowed_job = restore->job;
usfstl_sched_restore_blocked_jobs(sched);
usfstl_sched_remove_blocked_jobs(sched);
}
static void usfstl_sched_link_job_callback(struct usfstl_job *job)
{
struct usfstl_scheduler *sched = job->data;
sched->link.waiting = false;
}
static uint64_t usfstl_sched_link_external_sync_from(struct usfstl_scheduler *sched)
{
uint64_t parent_time;
parent_time = usfstl_sched_current_time(sched->link.parent);
return DIV_ROUND_UP(parent_time - sched->link.offset,
sched->link.tick_ratio);
}
static void usfstl_sched_link_external_wait(struct usfstl_scheduler *sched)
{
sched->link.waiting = true;
while (sched->link.waiting)
usfstl_sched_next(sched->link.parent);
usfstl_sched_set_time(sched, usfstl_sched_current_time(sched));
}
static void usfstl_sched_link_external_request(struct usfstl_scheduler *sched,
uint64_t time)
{
uint64_t parent_time;
struct usfstl_job *job = &sched->link.job;
parent_time = sched->link.tick_ratio * time + sched->link.offset;
usfstl_sched_del_job(job);
job->start = parent_time;
usfstl_sched_add_job(sched->link.parent, job);
}
void usfstl_sched_link(struct usfstl_scheduler *sched,
struct usfstl_scheduler *parent,
uint32_t tick_ratio)
{
struct usfstl_job *job;
USFSTL_ASSERT(tick_ratio, "a ratio must be set");
USFSTL_ASSERT(!sched->link.parent, "must not be linked");
USFSTL_ASSERT_EQ(sched->external_request, NULL, "%p");
sched->external_request = usfstl_sched_link_external_request;
USFSTL_ASSERT_EQ(sched->external_wait, NULL, "%p");
sched->external_wait = usfstl_sched_link_external_wait;
USFSTL_ASSERT_EQ(sched->external_sync_from, NULL, "%p");
sched->external_sync_from = usfstl_sched_link_external_sync_from;
sched->link.tick_ratio = tick_ratio;
sched->link.parent = parent;
sched->link.job.callback = usfstl_sched_link_job_callback;
sched->link.job.data = sched;
/* current_time = (parent_time - offset) / tick_ratio */
sched->link.offset = sched->link.parent->current_time -
sched->current_time * sched->link.tick_ratio;
/* if we have a job already, request to run it */
job = usfstl_sched_next_pending(sched, NULL);
if (job)
usfstl_sched_external_request(sched, job->start);
}
void usfstl_sched_unlink(struct usfstl_scheduler *sched)
{
USFSTL_ASSERT(sched->link.parent, "must be linked");
sched->external_sync_from = NULL;
sched->external_wait = NULL;
sched->external_request = NULL;
usfstl_sched_del_job(&sched->link.job);
memset(&sched->link, 0, sizeof(sched->link));
}