| /* |
| * Copyright (C) 2019 - 2020 Intel Corporation |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| #ifndef _USFSTL_SCHED_H_ |
| #define _USFSTL_SCHED_H_ |
| #include <stdint.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include "assert.h" |
| #include "list.h" |
| #include "loop.h" |
| |
| /* |
| * usfstl's simple scheduler |
| * |
| * usfstl's concept of time is just a free-running counter. You can use |
| * any units you like, but we recommend micro or nanoseconds, even |
| * with nanoseconds you can simulate ~580 years in a uint64_t time. |
| * |
| * The scheduler is just a really basic concept, you enter "jobs" |
| * and then call usfstl_sched_next() to run the next job. Usually, |
| * this job would schedule another job and call usfstl_sched_next() |
| * again, etc. |
| * |
| * The scheduler supports grouping jobs (currently into up to 32 |
| * groups) and then blocking certain groups from being executed in |
| * the next iteration. This can be used, for example, to separate |
| * in-SIM and out-of-SIM jobs, or threads from IRQs, etc. Note |
| * that groups and priorities are two entirely separate concepts. |
| * |
| * In many cases, you'll probably be looking for usfstltask.h instead |
| * as that allows you to simulate a cooperative multithreading system. |
| * However, raw jobs may in that case be useful for things like e.g. |
| * interrupts that come into the simulated system (if they're always |
| * run-to-completion i.e. cannot yield to other jobs.) |
| * |
| * Additionally, the scheduler has APIs to integrate with another, |
| * external, scheduler to synchronize multiple components. |
| */ |
| |
| /** |
| * struct usfstl_job - usfstl scheduler job |
| * @time: time this job fires |
| * @priority: priority of the job, in case of multiple happening |
| * at the same time; higher value means higher priority |
| * @group: group value, in range 0-31 |
| * @name: job name |
| * @data: job data |
| * @callback: called when the job occurs |
| */ |
| struct usfstl_job { |
| uint64_t start; |
| uint32_t priority; |
| uint8_t group; |
| const char *name; |
| |
| void *data; |
| void (*callback)(struct usfstl_job *job); |
| |
| /* private: */ |
| struct usfstl_list_entry entry; |
| }; |
| |
| /** |
| * struct usfstl_scheduler - usfstl scheduler structure |
| * @external_request: If external scheduler integration is required, |
| * set this function pointer appropriately to request the next |
| * run time from the external scheduler. |
| * @external_wait: For external scheduler integration, this must wait |
| * for the previously requested runtime being granted, and you |
| * must call usfstl_sched_set_time() before returning from this |
| * function. |
| * @external_sync_from: For external scheduler integration, return current |
| * time based on external time info. |
| * @time_advanced: Set this to have logging (or similar) when time |
| * advances. Note that the argument is relative to the previous |
| * time, if you need the current absolute time use |
| * usfstl_sched_current_time(), subtract @delta from that to |
| * obtain the time prior to the current advance. |
| * |
| * Use USFSTL_SCHEDULER() to declare (and initialize) a scheduler. |
| */ |
| struct usfstl_scheduler { |
| void (*external_request)(struct usfstl_scheduler *, uint64_t); |
| void (*external_wait)(struct usfstl_scheduler *); |
| uint64_t (*external_sync_from)(struct usfstl_scheduler *sched); |
| void (*time_advanced)(struct usfstl_scheduler *, uint64_t delta); |
| |
| /* private: */ |
| void (*next_time_changed)(struct usfstl_scheduler *); |
| uint64_t current_time; |
| uint64_t prev_external_sync, next_external_sync; |
| |
| struct usfstl_list joblist; |
| struct usfstl_list pending_jobs; |
| struct usfstl_job *allowed_job; |
| |
| uint32_t blocked_groups; |
| uint8_t next_external_sync_set:1, |
| prev_external_sync_set:1, |
| waiting:1; |
| |
| struct { |
| struct usfstl_loop_entry entry; |
| uint64_t start; |
| uint32_t nsec_per_tick; |
| uint8_t timer_triggered:1, |
| initialized:1; |
| } wallclock; |
| |
| struct { |
| struct usfstl_scheduler *parent; |
| int64_t offset; |
| uint32_t tick_ratio; |
| struct usfstl_job job; |
| bool waiting; |
| } link; |
| |
| struct { |
| void *ctrl; |
| } ext; |
| }; |
| |
| #define USFSTL_SCHEDULER(name) \ |
| struct usfstl_scheduler name = { \ |
| .joblist = USFSTL_LIST_INIT(name.joblist), \ |
| .pending_jobs = USFSTL_LIST_INIT(name.pending_jobs), \ |
| } |
| |
| #define usfstl_time_check(x) \ |
| ({ uint64_t __t; typeof(x) __x; (void)(&__t == &__x); 1; }) |
| |
| /** |
| * usfstl_time_cmp - compare time, wrap-around safe |
| * @a: first time |
| * @op: comparison operator (<, >, <=, >=) |
| * @b: second time |
| * |
| * Returns: (a op b), e.g. usfstl_time_cmp(a, >=, b) returns (a >= b) |
| * while accounting for wrap-around |
| */ |
| #define usfstl_time_cmp(a, op, b) \ |
| (usfstl_time_check(a) && usfstl_time_check(b) && \ |
| (0 op (int64_t)((b) - (a)))) |
| |
| /** |
| * USFSTL_ASSERT_TIME_CMP - assert that the time comparison holds |
| * @a: first time |
| * @op: comparison operator |
| * @b: second time |
| */ |
| #define USFSTL_ASSERT_TIME_CMP(a, op, b) do { \ |
| uint64_t _a = a; \ |
| uint64_t _b = b; \ |
| if (!usfstl_time_cmp(_a, op, _b)) \ |
| usfstl_abort(__FILE__, __LINE__, \ |
| "usfstl_time_cmp(" #a ", " #op ", " #b ")",\ |
| " " #a " = %" PRIu64 "\n" \ |
| " " #b " = %" PRIu64 "\n", \ |
| _a, _b); \ |
| } while (0) |
| |
| /** |
| * usfstl_sched_current_time - return current time |
| * @sched: the scheduler to operate with |
| */ |
| uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched); |
| |
| /** |
| * usfstl_sched_add_job - add job execution |
| * @sched: the scheduler to operate with |
| * @job: job to add |
| * |
| * Add an job to the execution queue, at the time noted |
| * inside the job. |
| */ |
| void usfstl_sched_add_job(struct usfstl_scheduler *sched, |
| struct usfstl_job *job); |
| |
| /** |
| * @usfstl_sched_del_job - remove an job |
| * @sched: the scheduler to operate with |
| * @job: job to remove |
| * |
| * Remove an job from the execution queue, if present. |
| */ |
| void usfstl_sched_del_job(struct usfstl_job *job); |
| |
| /** |
| * usfstl_sched_start - start the scheduler |
| * @sched: the scheduler to operate with |
| * |
| * Start the scheduler, which initializes the scheduler data |
| * and syncs with the external scheduler if necessary. |
| */ |
| void usfstl_sched_start(struct usfstl_scheduler *sched); |
| |
| /** |
| * usfstl_sched_next - call next job |
| * @sched: the scheduler to operate with |
| * |
| * Go into the scheduler, forward time to the next job, |
| * and call its callback. |
| * |
| * Returns the job that was run. |
| */ |
| struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched); |
| |
| /** |
| * usfstl_job_scheduled - check if an job is scheduled |
| * @job: the job to check |
| * |
| * Returns: %true if the job is on the schedule list, %false otherwise. |
| */ |
| bool usfstl_job_scheduled(struct usfstl_job *job); |
| |
| /** |
| * usfstl_sched_next_pending - get first/next pending job |
| * @sched: the scheduler to operate with |
| * @job: %NULL or previously returned job |
| * |
| * This is used to implement usfstl_sched_for_each_pending() |
| * and usfstl_sched_for_each_pending_safe(). |
| */ |
| struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched, |
| struct usfstl_job *job); |
| |
| #define usfstl_sched_for_each_pending(sched, job) \ |
| for (job = usfstl_sched_next_pending(sched, NULL); job; \ |
| job = usfstl_sched_next_pending(sched, job)) |
| |
| #define usfstl_sched_for_each_pending_safe(sched, job, tmp) \ |
| for (job = usfstl_sched_next_pending(sched, NULL), \ |
| tmp = usfstl_sched_next_pending(sched, job); \ |
| job; \ |
| job = tmp, tmp = usfstl_sched_next_pending(sched, tmp)) |
| |
| struct usfstl_sched_block_data { |
| uint32_t groups; |
| struct usfstl_job *job; |
| }; |
| |
| /** |
| * usfstl_sched_block_groups - block groups from executing |
| * @sched: the scheduler to operate with |
| * @groups: groups to block, ORed with the currently blocked groups |
| * @job: single job that's allowed anyway, e.g. if the caller is |
| * part of the blocked group and must be allowed to continue |
| * @save: save data, use with usfstl_sched_restore_groups() |
| */ |
| void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups, |
| struct usfstl_job *job, |
| struct usfstl_sched_block_data *save); |
| |
| /** |
| * usfstl_sched_restore_groups - restore blocked groups |
| * @sched: the scheduler to operate with |
| * @restore: data saved during usfstl_sched_block_groups() |
| */ |
| void usfstl_sched_restore_groups(struct usfstl_scheduler *sched, |
| struct usfstl_sched_block_data *restore); |
| |
| /** |
| * usfstl_sched_set_time - set time from external source |
| * @sched: the scheduler to operate with |
| * @time: time |
| * |
| * Set the scheduler time from the external source, use this |
| * before returning from the sched_external_wait() method but also when |
| * injecting any other kind of job like an interrupt from an |
| * external source (only applicable when running with external |
| * scheduling and other external interfaces.) |
| */ |
| void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time); |
| |
| /** |
| * usfstl_sched_set_sync_time - set next external sync time |
| * @sched: the scheduler to operate with |
| * @time: time |
| * |
| * When cooperating with an external scheduler, it may be good to |
| * avoid ping-pong all the time, if there's nothing to do. This |
| * function facilitates that. |
| * |
| * Call it before returning from the sched_external_wait() method and |
| * the scheduler will not sync for each internal job again, but |
| * only when the next internal job would be at or later than the |
| * time given as the argument here. |
| * |
| * Note that then you also have to coordinate with the external |
| * scheduler every time there's any interaction with any other |
| * component also driven by the external scheduler. |
| * |
| * To understand this, consider the following timeline, where the |
| * letters indicate scheduler jobs: |
| * - component 1: A C D E F |
| * - component 2: B G |
| * Without calling this function, component 1 will always sync |
| * with the external scheduler in component 2, for every job |
| * it has. However, after the sync for job/time C, component |
| * 2 knows that it will not have any job until G, so if it |
| * tells component 1 (whatever RPC there is calls this function) |
| * then component 1 will sync again only after F. |
| * |
| * However, this also necessitates that whenever the components |
| * interact, this function could be called again. If you imagine |
| * jobs C-F to just be empty ticks that do nothing then this |
| * might not happen. However, if one of them causes interaction |
| * with component 2 (say a network packet in the simulation) the |
| * interaction may cause a new job to be inserted into the |
| * scheduler timeline of component 2. Let's say, for example, |
| * the job D was a transmission from component 1 to 2, and in |
| * component 2 that causes an interrupt and a rescheduling. The |
| * above timeline will thus change to: |
| * - component 1: A C D E F |
| * - component 2: B N G |
| * inserting the job N. Thus, this very interaction needs to |
| * call this function again with the time of N which will be at |
| * or shortly after the time of D, rather than at G. |
| * |
| * This optimises things if jobs C-F don't cause interaction |
| * with other components, and if considered properly in the RPC |
| * protocol will not cause any degradation. |
| * |
| * If not supported, just never call this function and each and |
| * every job will require external synchronisation. |
| */ |
| void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time); |
| |
| /** |
| * g_usfstl_top_scheduler - top level scheduler |
| * |
| * There can be multiple schedulers in an usfstl binary, in particular |
| * when the multi-binary support code is used. In any case, this will |
| * will point to the top-level scheduler to facilitate another level |
| * of integration if needed. |
| */ |
| extern struct usfstl_scheduler *g_usfstl_top_scheduler; |
| |
| /** |
| * usfstl_sched_wallclock_init - initialize wall-clock integration |
| * @sched: the scheduler to initialize, it must not have external |
| * integration set up yet |
| * @ns_per_tick: nanoseconds per scheduler tick |
| * |
| * You can use this function to set up a scheduler to run at roughly |
| * wall clock speed (per the @ns_per_tick setting). |
| * |
| * This is compatible with the usfstlloop abstraction, so you can also |
| * add other things to the event loop and they'll be handled while |
| * the scheduler is waiting for time to pass. |
| * |
| * NOTE: This is currently Linux-only. |
| */ |
| void usfstl_sched_wallclock_init(struct usfstl_scheduler *sched, |
| unsigned int ns_per_tick); |
| |
| /** |
| * usfstl_sched_wallclock_exit - remove wall-clock integration |
| * @sched: scheduler to remove wall-clock integration from |
| * |
| * This releases any resources used for the wall-clock integration. |
| */ |
| void usfstl_sched_wallclock_exit(struct usfstl_scheduler *sched); |
| |
| /** |
| * usfstl_sched_wallclock_wait_and_handle - wait for external events |
| * @sched: scheduler that's integrated with the wallclock |
| * |
| * If no scheduler events are pending, this will wait for external |
| * events using usfstl_wait_and_handle() and synchronize the time it |
| * took for such an event to arrive into the given scheduler. |
| */ |
| void usfstl_sched_wallclock_wait_and_handle(struct usfstl_scheduler *sched); |
| |
| /** |
| * usfstl_sched_link - link a scheduler to another one |
| * @sched: the scheduler to link, must not already use the external |
| * request methods, of course. Should also not be running. |
| * @parent: the parent scheduler to link to |
| * @tick_ratio: "tick_ratio" parent ticks == 1 of our ticks; |
| * e.g. 1000 for if @sched should have microseconds, while @parent |
| * uses nanoseconds. |
| * |
| * This links two schedulers together, and requesting any runtime in the |
| * inner scheduler (@sched) depends on the parent scheduler (@parent) |
| * granting it. |
| * |
| * Time in the inner scheduler is adjusted in two ways: |
| * 1) there's a "tick_ratio" as described above |
| * 2) at the time of linking, neither scheduler changes its current |
| * time, instead an offset between the two is maintained, so the |
| * inner scheduler can be at e.g. zero and be linked to a parent |
| * that has already been running for a while. |
| */ |
| void usfstl_sched_link(struct usfstl_scheduler *sched, |
| struct usfstl_scheduler *parent, |
| uint32_t tick_ratio); |
| |
| /** |
| * usfstl_sched_unlink - unlink a scheduler again |
| * @sched: the scheduler to unlink, must be linked |
| */ |
| void usfstl_sched_unlink(struct usfstl_scheduler *sched); |
| |
| #endif // _USFSTL_SCHED_H_ |