diff --git a/composition_scripts/sched_test_double_hit.toml b/composition_scripts/sched_test_double_hit.toml new file mode 100644 index 000000000..3d17e1ef3 --- /dev/null +++ b/composition_scripts/sched_test_double_hit.toml @@ -0,0 +1,33 @@ +[system] +description = "Simplest system with both capability manager and scheduler, from unit_schedcomp.sh" + +[[components]] +name = "booter" +img = "no_interface.llbooter" +implements = [{interface = "init"}, {interface = "addr"}] +deps = [{srv = "kernel", interface = "init", variant = "kernel"}] +constructor = "kernel" + +[[components]] +name = "capmgr" +img = "capmgr.simple" +deps = [{srv = "booter", interface = "init"}, {srv = "booter", interface = "addr"}] +implements = [{interface = "capmgr"}, {interface = "init"}, {interface = "memmgr"}, {interface = "capmgr_create"}] +constructor = "booter" + +[[components]] +name = "sched" +img = "sched.deferrable_server" +deps = [{srv = "capmgr", interface = "init"}, {srv = "capmgr", interface = "capmgr"}, {srv = "capmgr", interface = "memmgr"}] +implements = [{interface = "sched"}, {interface = "init"}] +constructor = "booter" + +[[components]] +name = "schedtest" +img = "tests.threads" +deps = [{srv = "sched", interface = "init"}, {srv = "sched", interface = "sched"}, {srv = "capmgr", interface = "capmgr_create"}] +params = [{key = "1", value = "0,1,4000,1000,900,7000"}] +constructor = "booter" + +# To create test threads +# params = [{key = "1", value = "type,priority,period_us,budget_us,execution_us,block_us"}... diff --git a/composition_scripts/sched_test_periodicity.toml b/composition_scripts/sched_test_periodicity.toml new file mode 100644 index 000000000..ebe57319c --- /dev/null +++ b/composition_scripts/sched_test_periodicity.toml @@ -0,0 +1,33 @@ +[system] +description = "Simplest system with both capability manager and scheduler, from unit_schedcomp.sh" + +[[components]] +name = "booter" +img = "no_interface.llbooter" +implements = [{interface = "init"}, {interface = "addr"}] +deps = [{srv = "kernel", interface = "init", variant = "kernel"}] +constructor = "kernel" + +[[components]] +name = "capmgr" +img = "capmgr.simple" +deps = [{srv = "booter", interface = "init"}, {srv = "booter", interface = "addr"}] +implements = [{interface = "capmgr"}, {interface = "init"}, {interface = "memmgr"}, {interface = "capmgr_create"}] +constructor = "booter" + +[[components]] +name = "sched" +img = "sched.deferrable_server" +deps = [{srv = "capmgr", interface = "init"}, {srv = "capmgr", interface = "capmgr"}, {srv = "capmgr", interface = "memmgr"}] +implements = [{interface = "sched"}, {interface = "init"}] +constructor = "booter" + +[[components]] +name = "schedtest" +img = "tests.threads" +deps = [{srv = "sched", interface = "init"}, {srv = "sched", interface = "sched"}, {srv = "capmgr", interface = "capmgr_create"}] +params = [{key = "1", value = "0,1,4000,1000,0,0"}] +constructor = "booter" + +# To create test threads +# params = [{key = "1", value = "type,priority,period_us,budget_us,execution_us,block_us"}, ... diff --git a/composition_scripts/sched_test_priority.toml b/composition_scripts/sched_test_priority.toml new file mode 100644 index 000000000..29e747894 --- /dev/null +++ b/composition_scripts/sched_test_priority.toml @@ -0,0 +1,33 @@ +[system] +description = "Simplest system with both capability manager and scheduler, from unit_schedcomp.sh" + +[[components]] +name = "booter" +img = "no_interface.llbooter" +implements = [{interface = "init"}, {interface = "addr"}] +deps = [{srv = "kernel", interface = "init", variant = "kernel"}] +constructor = "kernel" + +[[components]] +name = "capmgr" +img = "capmgr.simple" +deps = [{srv = "booter", interface = "init"}, {srv = "booter", interface = "addr"}] +implements = [{interface = "capmgr"}, {interface = "init"}, {interface = "memmgr"}, {interface = "capmgr_create"}] +constructor = "booter" + +[[components]] +name = "sched" +img = "sched.deferrable_server" +deps = [{srv = "capmgr", interface = "init"}, {srv = "capmgr", interface = "capmgr"}, {srv = "capmgr", interface = "memmgr"}] +implements = [{interface = "sched"}, {interface = "init"}] +constructor = "booter" + +[[components]] +name = "schedtest" +img = "tests.threads" +deps = [{srv = "sched", interface = "init"}, {srv = "sched", interface = "sched"}, {srv = "capmgr", interface = "capmgr_create"}] +params = [{key = "1", value = "0,1,4000,2000,0,0"}, {key = "2", value = "0,2,4000,1000,0,0"}] +constructor = "booter" + +# To create test threads +# params = [{key = "1", value = "type,priority,period_us,budget_us,execution_us,block_us"}... \ No newline at end of file diff --git a/doc/baremetal.md b/doc/baremetal.md index 65f192e99..e909783af 100644 --- a/doc/baremetal.md +++ b/doc/baremetal.md @@ -73,14 +73,14 @@ Good news, we found some [Null-Modem RS232 Serial cables](https://www.amazon.com If you've a UNIX-based machine, you could use `minicom` to see the serial output from Composite running on baremetal on your host machine. To get minicom working with the RS232 Serial to USB cables, you'd need to change the serail port to: `/dev/ttyUSB0` ideally using `minicom -s`. (You should verify this device from the `dmesg` output to be sure you're using the right port). -Next, you'd need to set up the baudrate to match what composite uses (38400bps). +Next, you'd need to set up the baudrate to match what composite uses (115200bps). To see the logs correctly, you'd need to set the linefeed, carriage return, and linewrap to "Yes". The contents of my minicom config file `minirc.dfl`: ``` # Machine-generated file - use "minicom -s" to change parameters. pu port /dev/ttyUSB0 -pu baudrate 38400 +pu baudrate 115200 pu histlines 5000 pu addlinefeed Yes pu linewrap Yes diff --git a/src/components/implementation/sched/deferrable_server/Makefile b/src/components/implementation/sched/deferrable_server/Makefile new file mode 100644 index 000000000..d4c65c840 --- /dev/null +++ b/src/components/implementation/sched/deferrable_server/Makefile @@ -0,0 +1,23 @@ +# Required variables used to drive the compilation process. It is OK +# for many of these to be empty. +# +# The set of interfaces that this component exports for use by other +# components. This is a list of the interface names. +INTERFACE_EXPORTS = sched init syncipc +# The interfaces this component is dependent on for compilation (this +# is a list of directory names in interface/) +INTERFACE_DEPENDENCIES = init capmgr memmgr +# The library dependencies this component is reliant on for +# compilation/linking (this is a list of directory names in lib/) +LIBRARY_DEPENDENCIES = component slm ps util crt initargs ck +# Note: Both the interface and library dependencies should be +# *minimal*. That is to say that removing a dependency should cause +# the build to fail. The build system does not validate this +# minimality; that's on you! + +CFILES+=../shared/init.c +CFILES+=../shared/thd_alloc.c + +C_OBJS+=$(CFILES:%.c=%.o) + +include Makefile.subsubdir diff --git a/src/components/implementation/sched/deferrable_server/main.c b/src/components/implementation/sched/deferrable_server/main.c new file mode 100644 index 000000000..f936514d8 --- /dev/null +++ b/src/components/implementation/sched/deferrable_server/main.c @@ -0,0 +1,688 @@ +#include +#include +#include +#include +#include +#include +#include + +/*** + * A version of scheduling using a simple periodic timeout, + * preemptive, fixed priority, round-robin scheduling, and uses the + * capability manager to allocate threads, with local thread memory + * tracked in static (allocate-only, finite) memory. + */ + +#include +//#include +//#include +//#include +#include +#include +#include + +#include + +struct slm_resources_thd { + thdcap_t cap; + thdid_t tid; + compid_t comp; +}; + +struct slm_thd *slm_thd_static_cm_lookup(thdid_t id); + +SLM_MODULES_COMPOSE_DATA(); +//SLM_MODULES_COMPOSE_FNS(quantum, fprr, static_cm); +SLM_MODULES_COMPOSE_FNS(fpds, fpds, static_cm); + +struct crt_comp self; + +SS_STATIC_SLAB(thd, struct slm_thd_container, MAX_NUM_THREADS); + +/* Implementation for use by the other parts of the slm */ +struct slm_thd * +slm_thd_static_cm_lookup(thdid_t id) +{ + return &ss_thd_get(id)->thd; +} + +static inline struct slm_thd * +slm_thd_current(void) +{ + return &ss_thd_get(cos_thdid())->thd; +} + +struct slm_thd * +slm_thd_current_extern(void) +{ + return slm_thd_current(); +} + +struct slm_thd * +slm_thd_from_container(struct slm_thd_container *c) { + return &c->thd; +} + +struct slm_thd_container * +slm_thd_mem_alloc(thdcap_t _cap, thdid_t _tid, thdcap_t *thd, thdid_t *tid) +{ + struct slm_thd_container *t = NULL; + struct slm_thd_container *ret = NULL; + + ret = t = ss_thd_alloc_at_id(_tid); + if (!t) assert(0); + + assert(_cap != 0 && _tid != 0); + t->resources = (struct slm_resources_thd) { + .cap = _cap, + .tid = _tid, + .comp = cos_compid() + }; + + *thd = _cap; + *tid = _tid; + + return ret; +} + +void slm_thd_mem_activate(struct slm_thd_container *t) { ss_thd_activate(t); } +/* TODO */ +void slm_thd_mem_free(struct slm_thd_container *t) { return; } + +thdid_t +sched_thd_create_closure(thdclosure_index_t idx) +{ + sched_param_t p = 0; + struct slm_thd *t = thd_alloc_in(cos_inv_token(), idx, &p, 0); + + if (!t) return 0; + + return t->tid; +} + +int +sched_thd_param_set(thdid_t tid, sched_param_t p) +{ + struct slm_thd *t = slm_thd_lookup(tid); + sched_param_type_t type; + unsigned int value; + + sched_param_get(p, &type, &value); + + if (!t) return -1; + + return slm_sched_thd_update(t, type, value); +} + +int +sched_thd_delete(thdid_t tid) +{ + return 0; +} + +int +sched_thd_exit(void) +{ + struct slm_thd *current = slm_thd_current(); + int i; + + slm_cs_enter(current, SLM_CS_NONE); + slm_thd_deinit(current); + for (i = 0; slm_cs_exit_reschedule(current, SLM_CS_NONE) && i < 16; i++) ; + + /* If we got here, something went wrong */ + BUG(); + + return 0; +} + +int +sched_thd_yield_to(thdid_t t) +{ + struct slm_thd *current = slm_thd_current(); + struct slm_thd *to = slm_thd_lookup(t); + int ret; + + assert(to); + if (!to) return -1; + + slm_cs_enter(current, SLM_CS_NONE); + slm_sched_yield(current, to); + ret = slm_cs_exit_reschedule(current, SLM_CS_NONE); + + return ret; +} + + +void +sched_set_tls(void* tls_addr) +{ + struct slm_thd *current = slm_thd_current(); + thdcap_t thdcap = current->thd; + + capmgr_set_tls(thdcap, tls_addr); +} + +int +thd_block(void) +{ + struct slm_thd *current = slm_thd_current(); + int ret; + + slm_cs_enter(current, SLM_CS_NONE); + ret = slm_thd_block(current); + if (!ret) ret = slm_cs_exit_reschedule(current, SLM_CS_NONE); + else slm_cs_exit(NULL, SLM_CS_NONE); + + return ret; +} + +/* TODO: Added for testing purposes, remove it later! */ +int +thd_block_privilidge(thdid_t id) +{ + struct slm_thd *block = slm_thd_lookup(id); + int ret; + + slm_cs_enter(block, SLM_CS_NONE); + ret = slm_thd_block(block); + if (!ret) ret = slm_cs_exit_reschedule(block, SLM_CS_NONE); + else slm_cs_exit(NULL, SLM_CS_NONE); + + return ret; +} + +/* TODO: Added for testing purposes, remove it later! */ +int +sched_thd_block(thdid_t dep_id) +{ + if (dep_id) { + return thd_block_privilidge(dep_id); + } + + return thd_block(); +} + +int +thd_wakeup(struct slm_thd *t) +{ + int ret; + struct slm_thd *current = slm_thd_current(); + + slm_cs_enter(current, SLM_CS_NONE); + ret = slm_thd_wakeup(t, 0); + if (ret < 0) { + slm_cs_exit(NULL, SLM_CS_NONE); + return ret; + } + + return slm_cs_exit_reschedule(current, SLM_CS_NONE); +} + +int +sched_thd_wakeup(thdid_t tid) +{ + struct slm_thd *t = slm_thd_lookup(tid); + + if (!t) return -1; + + return thd_wakeup(t); +} + +int +sched_debug_thd_state(thdid_t tid) +{ + struct slm_thd *t = slm_thd_lookup(tid); + return t->state; +} + +static int +thd_block_until(cycles_t timeout) +{ + struct slm_thd *current = slm_thd_current(); + int ret = 0; + + while (cycles_greater_than(timeout, slm_now())) { + slm_cs_enter(current, SLM_CS_NONE); + /* Cancel the timers for the current thread (replenishments and activations) */ + slm_timer_cancel(current); + if (slm_timer_add(current, timeout)) goto done; + if (slm_thd_block(current)) { + slm_timer_cancel(current); + } +done: + ret = slm_cs_exit_reschedule(current, SLM_CS_NONE); + /* cleanup stale timeouts (e.g. if we were woken outside of the timer) */ + slm_timer_cancel(current); + } + + return ret; +} + +cycles_t +sched_thd_block_timeout(thdid_t dep_id, cycles_t abs_timeout) +{ + cycles_t now; + + if (dep_id) return 0; + if (thd_block_until(abs_timeout)) return 0; + + now = slm_now(); + assert(cycles_greater_than(now, abs_timeout)); + + return now; +} + +/* TODO: Added for test purposes */ +u64_t +sched_thd_get_param(thdid_t tid, slm_thd_params_t param) +{ + u64_t val = 0; + + slm_thd_get_param(tid, param, &val); + + return val; +} + +int +thd_sleep(cycles_t c) +{ + cycles_t timeout = c + slm_now(); + + return thd_block_until(timeout); +} + +sched_blkpt_id_t +sched_blkpt_alloc(void) +{ + struct slm_thd *current = slm_thd_current(); + + return slm_blkpt_alloc(current); +} + +int +sched_blkpt_free(sched_blkpt_id_t id) +{ + return slm_blkpt_free(id); +} + +int +sched_blkpt_trigger(sched_blkpt_id_t blkpt, sched_blkpt_epoch_t epoch, int single) +{ + struct slm_thd *current = slm_thd_current(); + + return slm_blkpt_trigger(blkpt, current, epoch, single); +} + +int +sched_blkpt_block(sched_blkpt_id_t blkpt, sched_blkpt_epoch_t epoch, thdid_t dependency) +{ + struct slm_thd *current = slm_thd_current(); + + return slm_blkpt_block(blkpt, current, epoch, dependency); +} + +struct ipc_retvals { + int ready; + word_t r0, r1; +}; + +struct ipc_ep { + struct slm_thd *client, *server; + word_t a0, a1; + struct ipc_retvals *retvals; +}; + +/* For now, mainly a testing feature, thus only one */ +#define IPC_EP_NUM 1 /* MAX_NUM_THREADS */ + +struct ipc_ep eps[IPC_EP_NUM]; + +enum { + CNT_C_CALL, + CNT_C_LOOP, + CNT_C_RET, + CNT_S_REPLY, + CNT_S_LOOP, + CNT_S_WAIT, + CNT_S_RET, + CNT_MAX +}; +unsigned long counts[CNT_MAX]; +static void +count_inc(int type) +{ + counts[type]++; +} + +cycles_t readings[4]; +struct total { + int cnt; + int prev_stage_cnt[4]; + cycles_t tot; +} totals[4]; + +static void +trace_add(int reading) +{ + struct total *t = &totals[reading]; + cycles_t prev_tsc; + cycles_t now = ps_tsc(); + cycles_t max = 0; + int i, max_idx; + + /* Avoid this overhead for now! */ + return; + + for (i = 0; i < 4; i++) { + if (readings[i] > max) { + max = readings[i]; + max_idx = i; + } + } + prev_tsc = max; + t->prev_stage_cnt[max_idx]++; + + t->cnt++; + t->tot += now - prev_tsc; + + if (t->cnt % 128 == 128 - 1) { + int i; + + for (i = 0; i < 4; i++) { + int cnt = totals[i].cnt; + int j; + + if (cnt == 0) cnt = 1; + printc("%d:%llu (", i, totals[i].tot / cnt); + for (j = 0; j < 4; j++) { + printc("%d:%d%s", j, totals[i].prev_stage_cnt[j], j == 3 ? "" : ", "); + } + printc(")\n"); + } + printc("Counts: "); + for (i = 0; i < CNT_MAX; i++) { + printc("%ld%s", counts[i], i == CNT_MAX - 1 ? "\n\n" : ", "); + } + + readings[reading] = ps_tsc(); + } else { + readings[reading] = now; + } +} + +int +syncipc_call(int ipc_ep, word_t arg0, word_t arg1, word_t *ret0, word_t *ret1) +{ + struct slm_thd *t = slm_thd_current(), *switchto, *client; + /* avoid the conditional for bounds checking, ala Nova */ + struct ipc_ep *ep = &eps[ipc_ep % IPC_EP_NUM]; + sched_tok_t tok; + int ret; + struct ipc_retvals retvals = { .ready = 0 }; + + count_inc(CNT_C_CALL); + /* No server thread yet? Nothing to do here. */ + if (ep->server == NULL) return -EAGAIN; + while (1) { + tok = cos_sched_sync(); + switchto = ps_load(&ep->server); + + + /* Lets try and set ourself as the client communicating with the server! */ + if (likely(ps_cas((unsigned long *)&ep->client, 0, (unsigned long)t))) { + /* We are the serviced client, pass arguments! */ + ep->a0 = arg0; + ep->a1 = arg1; + ep->retvals = &retvals; + assert(ps_load(&ep->client) == t); + } + /* + * If another client is already communicating with the + * server, and they haven't yet populated the + * arguments and retvals, let them finish setting up + * their communication. + */ + client = ps_load(&ep->client); + if (unlikely(ep->client != t && ep->retvals == NULL)) switchto = client; + + /* + * If we are the client, then we're activating the + * server here. If another client exists, we're + * generally just performing priority inheritance + * here. + */ + trace_add(0); + ret = slm_switch_to(t, switchto, tok, 1); + trace_add(3); + count_inc(CNT_C_LOOP); + /* + * Iterate while we are not the serviced client, we + * have a stale scheduling token... + */ + if (unlikely(ret)) { + if (ret != -EAGAIN) return ret; + if (ret == EAGAIN) continue; + } + + /* ...or the server hasn't provided a response. */ + if (likely(ps_load(&retvals.ready))) break; + } + + count_inc(CNT_C_RET); + *ret0 = ps_load(&retvals.r0); + *ret1 = ps_load(&retvals.r1); + + return 0; +} + +int +syncipc_reply_wait(int ipc_ep, word_t arg0, word_t arg1, word_t *ret0, word_t *ret1) +{ + struct slm_thd *t = slm_thd_current(), *client; + /* avoid the conditional for bounds checking, ala Nova */ + struct ipc_ep *ep = &eps[ipc_ep % IPC_EP_NUM]; + sched_tok_t tok; + int ret; + + /* + * Phase 1: An EP is associated with a specific server. This + * conditional is formulated to move the cas and awaiting the + * first client off the fast-path. + */ + if (unlikely(ep->server != t)) { + /* Another server claimed the endpoint */ + if (ep->server != NULL) return -1; + if (!ps_cas((unsigned long *)&ep->server, 0, (unsigned long)t)) return -1; + ret = slm_sched_thd_update(t, SCHEDP_INIT, 0); + assert(ret == 0); + assert(ep->server == t); + + /* Now await the first client spinning at idle prio! */ + while (ps_load(&ep->client) == NULL) ; + *ret0 = ep->a0; + *ret1 = ep->a1; + + return 0; + } + + count_inc(CNT_S_REPLY); + + /* + * Phase 2: Reply to the client we are currently servicing! + */ + client = ps_load(&ep->client); + ep->retvals->r0 = arg0; + ep->retvals->r1 = arg1; + /* Make sure to set this *last*. */ + ep->retvals->ready = 1; + /* + * Reset the endpoint, so that the next client can make a + * request. As the client variable is the sync point, we have + * to write it last. + */ + ep->retvals = NULL; + ep->client = NULL; + + do { + /* + * Switch back to the client! + * + * TODO: We should add a bit that designates if there + * is *contention* on the server thread, and if it is + * set, instead call `slm_exit_reschedule` here to + * instead run the highest-priority client. + */ + trace_add(2); + ret = slm_switch_to(t, client, cos_sched_sync(), 1); + trace_add(1); + if (unlikely(ret) && ret != -EAGAIN) return ret; + count_inc(CNT_S_LOOP); + } while (ret == -EAGAIN); + + count_inc(CNT_S_WAIT); + /* + * Phase 3: Now await the next call! We spin as we're idle + * priority. + */ + while (ps_load(&ep->client) == NULL) ; + /* + * FIXME: If we are repriorized at higher than the client, we + * might preempt it after it sets ->client, but before it sets + * the arguments. The *correct* design is to create a PASSIVE + * priority in which threads only execute using explicit + * switches from other, properly prioritized threads. Such + * threads could *not* reprioritize. + */ + *ret0 = ep->a0; + *ret1 = ep->a1; + + count_inc(CNT_S_RET); + + return 0; +} + +thdid_t +sched_aep_create_closure(thdclosure_index_t id, int owntc, cos_channelkey_t key, microsec_t ipiwin, u32_t ipimax, arcvcap_t *rcv) +{ + return -1; +} + +unsigned long +sched_get_cpu_freq(void) +{ + return slm_get_cycs_per_usec(); +} + +struct slm_thd * +slm_ipithd_create(thd_fn_t fn, void * data, crt_rcv_flags_t flags, thdcap_t *thdcap, thdid_t *tid) +{ + struct slm_ipi_percore *ipi_data = slm_ipi_percore_get(cos_cpuid()); + struct slm_ipi_thd *r = &ipi_data->ipi_thd; + struct slm_thd_container *t; + struct slm_global *g = slm_global(); + struct slm_thd *current = &g->sched_thd; + struct slm_thd *thd; + struct slm_thd *ret; + thdcap_t _thd; + thdid_t _tid; + + r->rcv = capmgr_rcv_alloc(fn, data, flags, &r->asnd, &_thd, &_tid); + r->cpuid = cos_cpuid(); + r->tid = _tid; + + t = slm_thd_mem_alloc(_thd, _tid, thdcap, tid); + if (!t) ERR_THROW(NULL, done); + thd = slm_thd_from_container(t); + + slm_cs_enter(current, SLM_CS_NONE); + if (slm_thd_init(thd, _thd, _tid)) ERR_THROW(NULL, free); + + slm_thd_mem_activate(t); + + slm_cs_exit(NULL, SLM_CS_NONE); + + ret = thd; +done: + return ret; +free: + slm_thd_mem_free(t); + ret = NULL; + goto done; +} + +void +slm_ipi_process(void *d) +{ + int rcvd, ret; + struct slm_ipi_percore *ipi_data = slm_ipi_percore_get(cos_cpuid()); + struct slm_ipi_thd *r = &ipi_data->ipi_thd; + struct slm_ipi_event event = { 0 }; + struct slm_thd *current = slm_thd_current(); + struct slm_thd *thd; + + while (1) { + cos_rcv(r->rcv, RCV_ALL_PENDING, &rcvd); + + while (!slm_ipi_event_empty(cos_cpuid())) { + slm_ipi_event_dequeue(&event, cos_cpuid()); + + thd = slm_thd_static_cm_lookup(event.tid); + slm_cs_enter(current, SLM_CS_NONE); + ret = slm_thd_wakeup(thd, 0); + /* + * Return "0" means the thread is woken up in this call. + * Return "1" means the thread is already `RUNNABLE`. + */ + assert(ret == 0 || ret == 1); + slm_cs_exit(current, SLM_CS_NONE); + } + } + return; +} + +static coreid_t _init_core_id = 0; + +void +parallel_main(coreid_t cid) +{ + if (cid == _init_core_id) printc("Starting scheduler loop...\n"); + slm_sched_loop_nonblock(); +} + +void +cos_parallel_init(coreid_t cid, int init_core, int ncores) +{ + struct slm_thd_container *t; + struct slm_thd *r; + thdcap_t thdcap, ipithdcap; + arcvcap_t rcvcap; + thdid_t tid, ipitid; + if (init_core) { + _init_core_id = cid; + } + struct slm_ipi_percore *ipi_data = slm_ipi_percore_get(cos_cpuid()); + + cos_defcompinfo_sched_init(); + + t = slm_thd_alloc(slm_idle, NULL, &thdcap, &tid); + if (!t) BUG(); + + slm_init(thdcap, tid); + + r = slm_ipithd_create(slm_ipi_process, NULL, 0, &ipithdcap, &ipitid); + if (!r) BUG(); + sched_thd_param_set(ipitid, sched_param_pack(SCHEDP_PRIO, SLM_IPI_THD_PRIO)); + ck_ring_init(&ipi_data->ring, PAGE_SIZE / sizeof(struct slm_ipi_event)); +} + +void +cos_init(void) +{ + struct cos_compinfo *boot_info = cos_compinfo_get(cos_defcompinfo_curr_get()); + + cos_meminfo_init(&(boot_info->mi), BOOT_MEM_KM_BASE, COS_MEM_KERN_PA_SZ, BOOT_CAPTBL_SELF_UNTYPED_PT); + extern void calculate_initialization_schedule(void); + calculate_initialization_schedule(); + cos_defcompinfo_init(); +} diff --git a/src/components/implementation/sched/pfprr_quantum_static/Makefile b/src/components/implementation/sched/pfprr_quantum_static/Makefile index bb5aa4e0a..d4c65c840 100644 --- a/src/components/implementation/sched/pfprr_quantum_static/Makefile +++ b/src/components/implementation/sched/pfprr_quantum_static/Makefile @@ -15,4 +15,9 @@ LIBRARY_DEPENDENCIES = component slm ps util crt initargs ck # the build to fail. The build system does not validate this # minimality; that's on you! +CFILES+=../shared/init.c +CFILES+=../shared/thd_alloc.c + +C_OBJS+=$(CFILES:%.c=%.o) + include Makefile.subsubdir diff --git a/src/components/implementation/sched/pfprr_quantum_static/init.c b/src/components/implementation/sched/shared/init.c similarity index 100% rename from src/components/implementation/sched/pfprr_quantum_static/init.c rename to src/components/implementation/sched/shared/init.c diff --git a/src/components/implementation/sched/pfprr_quantum_static/thd_alloc.c b/src/components/implementation/sched/shared/thd_alloc.c similarity index 100% rename from src/components/implementation/sched/pfprr_quantum_static/thd_alloc.c rename to src/components/implementation/sched/shared/thd_alloc.c diff --git a/src/components/implementation/sched/pfprr_quantum_static/slm_modules.h b/src/components/implementation/sched/slm_modules.h similarity index 100% rename from src/components/implementation/sched/pfprr_quantum_static/slm_modules.h rename to src/components/implementation/sched/slm_modules.h diff --git a/src/components/implementation/tests/threads/Makefile b/src/components/implementation/tests/threads/Makefile new file mode 100644 index 000000000..1a9bc35d0 --- /dev/null +++ b/src/components/implementation/tests/threads/Makefile @@ -0,0 +1,18 @@ +# Required variables used to drive the compilation process. It is OK +# for many of these to be empty. +# +# The set of interfaces that this component exports for use by other +# components. This is a list of the interface names. +INTERFACE_EXPORTS = +# The interfaces this component is dependent on for compilation (this +# is a list of directory names in interface/) +INTERFACE_DEPENDENCIES = init sched +# The library dependencies this component is reliant on for +# compilation/linking (this is a list of directory names in lib/) +LIBRARY_DEPENDENCIES = component time +# Note: Both the interface and library dependencies should be +# *minimal*. That is to say that removing a dependency should cause +# the build to fail. The build system does not validate this +# minimality; that's on you! + +include Makefile.subsubdir diff --git a/src/components/implementation/tests/threads/test_threads.c b/src/components/implementation/tests/threads/test_threads.c new file mode 100644 index 000000000..47298892e --- /dev/null +++ b/src/components/implementation/tests/threads/test_threads.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include +#include + +#define SL_FPRR_NPRIOS 32 + +#define LOWEST_PRIORITY (SL_FPRR_NPRIOS - 1) +#define HIGHEST_PRIORITY 0 + +static void +workload(unsigned long long loop_count) +{ + unsigned long long i, j; + + for (i = 0; i < loop_count; ++i) { + for (j = 0; j < 100; ++j) { + __asm__ volatile(""); // Prevents the compiler from optimizing the loop away + } + } +} + +static void +spinning_task() +{ + SPIN(); +} + +static void +blocking_task(cycles_t blocking_time) +{ + cycles_t blocking_period = blocking_time; + + while (1) { + // Hacked to find a good value, gonna change it later + for (cycles_t i = 0; i < 9964000; i++) { + sched_thd_block_timeout(0, time_now() + i); + workload(27000); + } + } +} + +cycles_t +measure_cycles(unsigned long long loop_count) +{ + unsigned cycles_high, cycles_low, cycles_high1, cycles_low1; + + __asm__ __volatile__("cpuid\n\t" + "rdtsc\n\t" + "mov %%edx, %0\n\t" + "mov %%eax, %1\n\t" : + "=r" (cycles_high), "=r" (cycles_low) :: "%rax", "%rbx", "%rcx", "%rdx"); + + workload(loop_count); + + __asm__ __volatile__("rdtscp\n\t" + "mov %%edx, %0\n\t" + "mov %%eax, %1\n\t" + "cpuid\n\t" : + "=r" (cycles_high1), "=r" (cycles_low1) :: "%rax", "%rbx", "%rcx", "%rdx"); + + + cycles_t start = (((cycles_t)cycles_high << 32) | cycles_low); + cycles_t end = (((cycles_t)cycles_high1 << 32) | cycles_low1); + + PRINTLOG(PRINT_DEBUG, "Loop Count: %llu Start: %llu, End: %llu, Diff: %llu\n",loop_count , start, end, end - start); + + return end - start; +} + +cycles_t +find_loop_count(cycles_t desired_execution_time) +{ + unsigned long long loop_count_min = 25000; // For 1 ms measured loop count is ~25900 + unsigned long long loop_count = 0; + + for (unsigned long long i = loop_count_min; i < desired_execution_time; i++) + { + cycles_t cost = 0; + for (int j = 0; j < 1000; j++) + { + cost += measure_cycles(i); + } + cost /= 1000; + + // If it is in error margin, break + //if (cost > desired_execution_time - 2000 && cost < desired_execution_time + 2000) + if (cost >= desired_execution_time) { + loop_count = i; + PRINTLOG(PRINT_DEBUG, "Loop count: %llu, Cycles: %llu\n", i, cost); + break; + } + + } + + // Measure it again with the new loop count + cycles_t avg2 = 0; + for (size_t i = 0; i < 1000; i++) + { + avg2 += measure_cycles(loop_count); + + } + avg2 /= 1000; + + PRINTLOG(PRINT_DEBUG, "Average cycles: %llu Desired cycles: %llu\n", avg2, desired_execution_time); + + return loop_count; +} + + +enum thd_type_t { + SPINNER = 0, + BLOCKING = 1, // TODO: Add more types +}; + +struct thread_props { + enum thd_type_t type; + thdid_t tid; + int priority; + int budget_us; + int period_us; + int execution_us; + int block_us; +}; + +thdid_t +create_thread(const char* args) +{ + char *token; + struct thread_props thd; + + int result = sscanf(args, "%d,%d,%d,%d,%d,%d", (int*)&thd.type, &thd.priority, &thd.period_us, &thd.budget_us, &thd.execution_us, &thd.block_us); + + if (result != 6) { + PRINTLOG(PRINT_DEBUG, "Parsing failed\n"); + return 1; + } + + assert (thd.priority >= HIGHEST_PRIORITY && thd.priority <= LOWEST_PRIORITY); + assert(thd.period_us > thd.budget_us); + + switch (thd.type) + { + case SPINNER: + { + thd.tid = sched_thd_create(spinning_task, NULL); + break; + } + case BLOCKING: + { + // TODO: blocking time is hardcoded for now + cycles_t blocking_time = time_usec2cyc(2 * thd.period_us - thd.budget_us - 50000); // Error ?? + // TODO: Loop count is hardcoded for now + cycles_t loop_count = find_loop_count(time_usec2cyc(thd.execution_us)); + thd.tid = sched_thd_create(blocking_task, &blocking_time); + break; + } + /* TODO + case MEASURED: + { + cycles_t loop_count = find_loop_count(time_usec2cyc(thd.execution_us)); + thd.tid = sched_thd_create(measured_task, &args); + break; + } + */ + default: + assert(0); + break; + } + + PRINTLOG(PRINT_DEBUG, "\t TID: %lu, Type: %d, Priority: %d, Period: %d, Budget: %d\n", thd.tid, thd.type, thd.priority, thd.period_us, thd.budget_us); + + sched_thd_param_set(thd.tid, sched_param_pack(SCHEDP_PRIO, thd.priority)); + sched_thd_param_set(thd.tid, sched_param_pack(SCHEDP_BUDGET, time_usec2cyc(thd.budget_us))); + sched_thd_param_set(thd.tid, sched_param_pack(SCHEDP_WINDOW, time_usec2cyc(thd.period_us))); + + return thd.tid; +} + +int +main(void) +{ + int num_of_threads = 5; // To prevent dynamic allocation, just + thdid_t thread_ids[num_of_threads]; + + struct initargs params, curr; + struct initargs_iter i; + char *token; + int ret = 0; + + ret = args_get_entry("param", ¶ms); + assert(!ret); + assert(args_len(¶ms) < num_of_threads); + + num_of_threads = args_len(¶ms); + + int num = 0; + for (ret = args_iter(¶ms, &i, &curr) ; ret ; ret = args_iter_next(&i, &curr)) { + int keylen; + char *thread_args = args_value(&curr); + assert(thread_args); + thread_ids[num++] = create_thread(thread_args); + } + + cycles_t time_before_wakeup = time_now(); + cycles_t sleep = time_usec2cyc(10000000); + + PRINTLOG(PRINT_DEBUG, "Starting %d threads\n", num_of_threads); + sched_thd_block_timeout(0, time_before_wakeup + sleep); + + cycles_t wakeup = time_now(); + cycles_t spent = wakeup - time_before_wakeup; + PRINTLOG(PRINT_DEBUG, "Time spent: %llu \n", time_cyc2usec(spent)); + + // Block test threads + /* TODO: Blockedded for testing purposes, remove it later! */ + for(int i = 0; i < num_of_threads; i++) { + // TODO: When it is used with blocking_task, it crashes fix it + sched_thd_block(thread_ids[i]); + PRINTLOG(PRINT_DEBUG, "\t TID: %lu blocked\n", thread_ids[i]); + } + + + /* + cycles_t sched_exec_time, idle_exec_time = 0; + sched_exec_time = (cycles_t)sched_thd_get_param(1, 0); + idle_exec_time = (cycles_t)sched_thd_get_param(0, 0); + */ + + // Print the total execution time of the tasks + u16_t switch_cnt = 0; + for(int i = 0; i < num_of_threads; i++) { + cycles_t exec_time = 0; + exec_time = (cycles_t)sched_thd_get_param(thread_ids[i], 0); + switch_cnt = (u16_t)sched_thd_get_param(thread_ids[i], 1); + PRINTLOG(PRINT_DEBUG, "Thdid: %lu Total exec time: %llu, Switch count: %u\n", thread_ids[i], exec_time, switch_cnt); + } + PRINTLOG(PRINT_DEBUG, "Test Finished\n"); + + cos_trace_print_buffer(); + //sched_thd_block(0); + + return 0; +} diff --git a/src/components/interface/sched/sched.h b/src/components/interface/sched/sched.h index a26f6cf28..55302d13e 100644 --- a/src/components/interface/sched/sched.h +++ b/src/components/interface/sched/sched.h @@ -20,6 +20,7 @@ #include typedef u32_t sched_blkpt_id_t; +typedef u32_t sched_thd_param_t; //TODO: Added for getting execution and switch count, remove after #define SCHED_BLKPT_NULL 0 typedef word_t sched_blkpt_epoch_t; @@ -33,6 +34,9 @@ int sched_thd_block(thdid_t dep_id); int COS_STUB_DECL(sched_thd_block)(thdid_t dep_id); cycles_t sched_thd_block_timeout(thdid_t dep_id, cycles_t abs_timeout); cycles_t COS_STUB_DECL(sched_thd_block_timeout)(thdid_t dep_id, cycles_t abs_timeout); +//TODO: Added for getting execution and switch count, remove after +u64_t sched_thd_get_param(thdid_t t, sched_thd_param_t p); +u64_t COS_STUB_DECL(sched_thd_get_param)(thdid_t t, sched_thd_param_t p); void sched_set_tls(void* tls_addr); unsigned long sched_get_cpu_freq(void); diff --git a/src/components/interface/sched/stubs/stubs.S b/src/components/interface/sched/stubs/stubs.S index 5717a0ee3..7c9926fa9 100644 --- a/src/components/interface/sched/stubs/stubs.S +++ b/src/components/interface/sched/stubs/stubs.S @@ -20,6 +20,7 @@ cos_asm_stub_indirect(sched_thd_block_timeout); cos_asm_stub(sched_thd_create_closure); cos_asm_stub_indirect(sched_aep_create_closure); cos_asm_stub(sched_thd_param_set); +cos_asm_stub(sched_thd_get_param); //TODO: Added for getting execution and switch count, remove after cos_asm_stub(sched_thd_exit); cos_asm_stub(sched_thd_delete); cos_asm_stub(sched_set_tls); diff --git a/src/components/lib/component/Makefile b/src/components/lib/component/Makefile index dfd7a377d..e454549ee 100644 --- a/src/components/lib/component/Makefile +++ b/src/components/lib/component/Makefile @@ -21,7 +21,7 @@ INCLUDE_PATHS = . INTERFACE_DEPENDENCIES = init # The library dependencies this component is reliant on for # compilation/linking (this is a list of directory names in lib/) -LIBRARY_DEPENDENCIES = util ps kernel +LIBRARY_DEPENDENCIES = util ps kernel ck # Note: Both the interface and library dependencies should be # *minimal*. That is to say that removing a dependency should cause # the build to fail. The build system does not validate this diff --git a/src/components/lib/component/cos_trace.c b/src/components/lib/component/cos_trace.c new file mode 100644 index 000000000..550d077f3 --- /dev/null +++ b/src/components/lib/component/cos_trace.c @@ -0,0 +1,78 @@ +#include + +struct trace { + const char *format; + cycles_t tsc; + long cpu; + thdid_t tid; + compid_t cid; + dword_t a, b, c; +}; + +struct trace_ring_buf { + struct trace traces[COS_TRACE_NEVENTS]; + struct ck_ring ring; +}; + +CK_RING_PROTOTYPE(trace_buffer, trace); + +struct trace_ring_buf trace_buffer; + +// TODO: Constructor seems to be not working, check it later cos_iniit +void CCTOR +cos_trace_init() +{ + struct trace_ring_buf *ring_buf = &trace_buffer; + ck_ring_init(&ring_buf->ring, COS_TRACE_NEVENTS); +} + +void +cos_trace(const char *format, cycles_t tsc, long cpu, thdid_t tid, compid_t cid, dword_t a, dword_t b, dword_t c) +{ + struct trace new_trace; + struct trace_ring_buf *ring_buf = &trace_buffer; + bool res = true; + + assert(&ring_buf->ring); + + new_trace.format = format; + new_trace.tsc = tsc; + new_trace.cpu = cpu; + new_trace.tid = tid; + new_trace.cid = cid; + new_trace.a = a; + new_trace.b = b; + new_trace.c = c; + + res = CK_RING_ENQUEUE_MPSC(trace_buffer, &ring_buf->ring, ring_buf->traces, &new_trace); + + if (!res) { + unsigned int number_of_traces = ck_ring_size(&ring_buf->ring); + printc("Trace buffer enqueue failed %d, # of traces: %d\n", res, number_of_traces); + // assert(res); + } + +} + +void +cos_trace_print_buffer() +{ + struct trace_ring_buf *ring_buf = &trace_buffer; + struct trace trace = {0}; + bool res = true; + unsigned int i, number_of_traces; + + assert(&ring_buf->ring); + + number_of_traces = ck_ring_size(&ring_buf->ring); + + for (i = 0; i < number_of_traces; i++) { + res = CK_RING_DEQUEUE_MPSC(trace_buffer, &ring_buf->ring, ring_buf->traces, &trace); + assert(res); + + if (res) { + printc("[%-11llu|CPU: %ld,TID:%lu,CID:%lu] ", trace.tsc, trace.cpu, trace.tid, trace.cid); + printc( (char*)trace.format, trace.a, trace.b, trace.c); + } + } +} diff --git a/src/components/lib/component/cos_trace.h b/src/components/lib/component/cos_trace.h new file mode 100644 index 000000000..c87da7129 --- /dev/null +++ b/src/components/lib/component/cos_trace.h @@ -0,0 +1,32 @@ +#ifndef COS_TRACE_H +#define COS_TRACE_H + +#include +#include +#include +#include +#include +#include +#include + + +#define COS_TRACE_NEVENTS 10000 + +#ifndef COS_TRACE_ENABLED + +void cos_trace(const char *format, cycles_t tsc, long cpu, thdid_t tid, compid_t cid, dword_t a, dword_t b, dword_t c); +void cos_trace_print_buffer(void); + +/* Attaches the current (cycle, cpuid, thdid, compid) */ +#define COS_TRACE(format, a, b, c) \ + { \ + cos_trace(format, ps_tsc(), cos_coreid(), cos_thdid(), cos_compid(),\ + a, b, c); \ + } + +#else + +#define COS_TRACE(format, a, b, c) + +#endif // COS_TRACE_ENABLED +#endif // COS_TRACE_H \ No newline at end of file diff --git a/src/components/lib/slm/fpds.c b/src/components/lib/slm/fpds.c new file mode 100644 index 000000000..6cd1c689b --- /dev/null +++ b/src/components/lib/slm/fpds.c @@ -0,0 +1,604 @@ +#include + +#define SLM_FPRR_NPRIOS 32 +#define SLM_FPRR_PRIO_HIGHEST TCAP_PRIO_MAX +#define SLM_FPRR_PRIO_LOWEST (SLM_FPRR_NPRIOS - 1) + +#define SLM_WINDOW_HIGHEST 1000000000000 //cycles +#define SLM_WINDOW_LOWEST 1000 //cycles + + +struct prioqueue { + struct ps_list_head prio[SLM_FPRR_NPRIOS]; +} CACHE_ALIGNED; +struct prioqueue run_queue[NUM_CPU]; + +struct timer_global { + struct heap h; // you need to extend the heap , ring buffer in the thread + void *data[MAX_NUM_THREADS]; + cycles_t current_timeout; +} CACHE_ALIGNED; +static struct timer_global __timer_globals[NUM_CPU]; + +static inline struct timer_global * +timer_global(void) { + return &__timer_globals[cos_coreid()]; +} + +/* The timer expired */ +void +slm_timer_fpds_expire(cycles_t now) +{ + struct timer_global *g = timer_global(); + g->current_timeout = now; + + /* Should we wake up the closest-timeout thread? */ + while (heap_size(&g->h) > 0) { + + struct slm_thd *tp, *th; + struct slm_timer_thd *tt; + struct slm_sched_thd *st; + /* Should we wake up the closest-timeout thread? */ + tp = heap_peek(&g->h); + assert(tp); + tt = slm_thd_timer_policy(tp); + st = slm_thd_sched_policy(tp); + assert(tt && tt->timeout_idx > 0); + + /* No more threads to wake! */ + if (cycles_greater_than(tt->abs_next_processing, now)) break; + + /* Dequeue thread with closest wakeup */ + th = heap_highest(&g->h); + assert(th == tp); + + tt->timeout_idx = -1; + + // Check the state + switch(st->state) { + case STATE_EXPENDED: + { + /* A thread, in its new period with no budget, wants to replenish */ + + COS_TRACE("replenish(): TID: %ld Now: %llu\n", th->tid, now, 0); + replenish(th, now); + + /* Thread can only be in expended state if it is budgeted */ + assert(tt->is_budgeted); + + break; + } + case STATE_READY: + { + /* A thread, waiting for the execution in ready state */ + + // There should not be any timer for the threads in ready state + assert(0); + + break; + } + case STATE_BLOCKED: + { + /* A thread, blocked by the user before, wants to wake up */ + slm_thd_wakeup(th, 1); + break; + } + case STATE_BLOCKED_PERIODIC: + { + /* A thread, blocked in the previous period wants to wake up in its next period */ + /* TODO: Not throughly tested or used, remove this comment after verification */ + slm_sched_fpds_wakeup_periodic(th, now); + break; + } + case STATE_RUNNING: + { + /* A thread in the runqueue, executed in its current period wants to replenish */ + + // Optimization: For the budgeted threads that still have budget(in the runqueue), + // We can replenish just before it is scheduled + + // replenish(th, now); + + break; + } + default: + break; + } + } + +} + +/* + * Timeout and wakeup functionality + * + * TODO: Replace the in-place heap with a rb-tree to avoid external, static allocation. + */ + +int +slm_timer_fpds_add(struct slm_thd *t, cycles_t absolute_timeout) +{ + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + struct timer_global *g = timer_global(); + + assert(tt && tt->timeout_idx == -1); + assert(heap_size(&g->h) < MAX_NUM_THREADS); + + tt->abs_next_processing = absolute_timeout; + heap_add(&g->h, t); + + return 0; +} + +int +slm_timer_fpds_cancel(struct slm_thd *t) +{ + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + struct timer_global *g = timer_global(); + + if (tt->timeout_idx == -1) return 0; + + assert(heap_size(&g->h)); + assert(tt->timeout_idx > 0); + + heap_remove(&g->h, tt->timeout_idx); + tt->timeout_idx = -1; + + return 0; +} + +int +slm_timer_fpds_thd_init(struct slm_thd *t) +{ + struct timer_global *g = timer_global(); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + *tt = (struct slm_timer_thd){ + .timeout_idx = -1, + .abs_next_processing = 0, + .abs_period_start = slm_now(), + .abs_period_end = tt->abs_period_start + SLM_WINDOW_HIGHEST, + .budget = 0, + .initial_budget = 0, + .is_budgeted = 0, + .period = SLM_WINDOW_HIGHEST, + }; + + COS_TRACE("slm_timer_fpds_thd_init(): TID: %ld Period Start: %llu\n", t->tid, tt->abs_period_start, 0); + + // TODO: Check if the thread has higher priority than the current thread? + // Add timer interrupt if necessary? + + return 0; +} + +void +slm_timer_fpds_thd_deinit(struct slm_thd *t) +{ + // Cancel the timers + slm_timer_fpds_cancel(t); + return; +} + +static int +__slm_timeout_compare_min(void *a, void *b) +{ + /* FIXME: logic for wraparound in either timeout_cycs */ + return slm_thd_timer_policy((struct slm_thd *)a)->abs_next_processing <= slm_thd_timer_policy((struct slm_thd *)b)->abs_next_processing; +} + +static void +__slm_timeout_update_idx(void *e, int pos) +{ slm_thd_timer_policy((struct slm_thd *)e)->timeout_idx = pos; } + +static void +slm_policy_timer_init(microsec_t period) +{ + struct timer_global *g = timer_global(); + cycles_t next_timeout; + + memset(g, 0, sizeof(struct timer_global)); + heap_init(&g->h, MAX_NUM_THREADS, __slm_timeout_compare_min, __slm_timeout_update_idx); + + next_timeout = slm_now(); + g->current_timeout = next_timeout; + slm_timeout_set(next_timeout); +} + +int +slm_timer_fpds_init(void) +{ + /* 10ms */ + slm_policy_timer_init(10000); + + return 0; +} + +void +slm_sched_fpds_execution(struct slm_thd *t, cycles_t cycles, cycles_t now) +{ + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + struct slm_sched_thd *st = slm_thd_sched_policy(t); + + if(tt->is_budgeted == 0) { + return; + } + + tt->budget -= cycles; + + // Are period_start and period_end correct? + assert(tt->abs_period_start <= now); + // Did we miss the deadline? + // assert(tt->abs_period_end >= now + remaining WCET); + + // Plan the next replenishment + // TODO: Temporary for deferrable server + + // if budget is 0, add timer and block + if (tt->budget <= 0) { + //COS_TRACE("expended(): TID: %ld Now: %llu\n", t->tid, now, 0); + expended(t); + } + + return; +} + +void +set_next_timer_interrupt(struct slm_thd *t, cycles_t now) +{ + struct timer_global *g = timer_global(); + cycles_t next_timeout = 0; + + /* Are there any thread in timer queue? */ + /* TODO: We dont pay attention to the priority now */ + if (heap_size(&g->h) > 0) { + + struct slm_thd *tp; + struct slm_timer_thd *tt; + /* What is the closest-timeout? */ + tp = heap_peek(&g->h); + assert(tp); + tt = slm_thd_timer_policy(tp); + assert(tt && tt->timeout_idx > 0); + + next_timeout = tt->abs_next_processing; + + } + + /* Check if the next timeout is further than the budget of the current thread */ + if(t != NULL) { + struct slm_timer_thd *curr = slm_thd_timer_policy(t); + if (curr->is_budgeted) { + assert(curr->budget >= 0); + //Check if the budget exceeds the abs_period_end + //If it does, set the curr_deadline to abs_period_end + cycles_t curr_deadline = (cycles_t)curr->budget > curr->abs_period_end ? curr->abs_period_end : (cycles_t)curr->budget; + //Take the minimum of the next_timeout and curr_deadline + next_timeout = (next_timeout > (curr_deadline + now)) ? (curr_deadline + now) : next_timeout; + } + } + + // TODO: Hacked because even clearing timeout, it continues to interrupt + // slm_timeout_clear(); + slm_timeout_set(9999999999999999); + + /* Set the next timeout */ + if (next_timeout != 0) { + g->current_timeout = next_timeout; + slm_timeout_set(next_timeout); + } +} + +struct slm_thd * +slm_sched_fpds_schedule(cycles_t now) +{ + int i; + struct slm_sched_thd *st; + struct slm_timer_thd *tt; + struct ps_list_head *prios = run_queue[cos_cpuid()].prio; + struct timer_global *g = timer_global(); + + for (i = 0 ; i < SLM_FPRR_NPRIOS ; i++) { + if (ps_list_head_empty(&prios[i])) continue; + st = ps_list_head_first_d(&prios[i], struct slm_sched_thd); + tt = slm_thd_timer_policy(slm_thd_from_sched(st)); + + /* + * We want to move the selected thread to the back of the list. + * Otherwise it won't be truly round robin + */ + + /* Threads with no budget should not be in the runqueue */ + assert(st->state != STATE_EXPENDED); + assert(!tt->is_budgeted || tt->budget > 0); + + ps_list_rem_d(st); + ps_list_head_append_d(&prios[i], st); + + /* Set the timer */ + set_next_timer_interrupt(slm_thd_from_sched(st), now); + + st->state = STATE_RUNNING; + //COS_TRACE("slm_sched_fpds_schedule(): TID: %ld\n", slm_thd_from_sched(st)->tid, 0, 0); + + return slm_thd_from_sched(st); + } + + set_next_timer_interrupt(NULL, now); + //COS_TRACE("slm_sched_fpds_schedule(): TID: %ld Next Timeout: %llu\n", 0, now + g->period, 0); + + return NULL; +} + +int +slm_sched_fpds_block(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + assert(st->state != STATE_BLOCKED); + assert(st->state != STATE_BLOCKED_PERIODIC); + + //COS_TRACE("slm_sched_fpds_block(): TID: %ld State: %d\n", t->tid, st->state, 0); + + /* Remove from runqueue */ + ps_list_rem_d(st); + st->state = STATE_BLOCKED; + + // TODO: Now cancelling the timer is in sched/main.c should we move it here? + + return 0; +} + +/* TODO: Not throughly tested or used, remove this comment after verification */ +int +slm_sched_fpds_block_periodic(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + + assert(tt->is_budgeted); + assert(tt->abs_next_processing >= tt->abs_period_end); + + assert(st->state != STATE_BLOCKED); + assert(st->state != STATE_BLOCKED_PERIODIC); + + /* Remove from runqueue */ + st->state = STATE_BLOCKED_PERIODIC; + ps_list_rem_d(st); + + /* Update abs_period_start, abs_period_end and abs_next_processing */ + tt->abs_period_start = tt->abs_period_end; + tt->abs_period_end = tt->abs_period_start + tt->period; + + // Optimization: Set the next processing time to the first replenishment time + // TODO: Temporary for deferrable server + assert(tt->abs_next_processing == tt->abs_period_start); + + return 0; +} + +void +expended(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + int ret = -1; + + // Remove from runqueue, note that slm_state is still RUNNING + ps_list_rem_d(st); + st->state = STATE_EXPENDED; + + // Update abs_period_start, abs_period_end + tt->abs_period_start = tt->abs_period_start + tt->period; + tt->abs_period_end = tt->abs_period_start + tt->period; + + // Add replenishment timer + ret = slm_timer_fpds_add(t, tt->abs_period_start); + + assert(ret == 0); +} + +void +replenish(struct slm_thd *t, cycles_t now) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + int ret = -1; + + tt->budget = tt->initial_budget; + + assert(st->state == STATE_EXPENDED); + st->state = STATE_READY; + + /* Add to the runqueue */ + ps_list_head_append_d(&run_queue[cos_cpuid()].prio[t->priority], st); +} + +int +slm_sched_fpds_wakeup(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + struct timer_global *g = timer_global(); + + assert(ps_list_singleton_d(st)); + assert(st->state == STATE_BLOCKED); + + if (tt->is_budgeted) { + /* Shift abs_period_start, abs_period_end and abs_next_processing */ + /* This prevents a thread from gaining advantage over other same priority */ + cycles_t offset_abs_next_processing = tt->abs_next_processing - tt->abs_period_start; + + int periods_passed = (g->current_timeout - tt->abs_period_start) / tt->period; + COS_TRACE("wakeup(): TID: %ld Periods Passed: %d\n", t->tid, periods_passed, 0); + tt->abs_period_start += (periods_passed * tt->period); + tt->abs_period_end = tt->abs_period_start + tt->period; + + /* TODO: Update replenisment window abs values */ + /* Add the cancelled timer in slm_sched_fpds_block() */ + // Recover last state + // tt->abs_next_processing = tt->abs_period_start + offset_abs_next_processing; + + // TODO: For only deferable server + // If there is no budget change state and add timer + if (tt->budget <= 0) { + slm_timer_fpds_add(t, tt->abs_period_end); + st->state = STATE_EXPENDED; + return 0; + } + } + /* Add to the runqueue */ + st->state = STATE_READY; + ps_list_head_append_d(&run_queue[cos_cpuid()].prio[t->priority - 1], st); + + return 0; +} + + +/* TODO: Not throughly tested or used, remove this comment after verification */ +int +slm_sched_fpds_wakeup_periodic(struct slm_thd *t, cycles_t now) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + assert(ps_list_singleton_d(st)); + assert(st->state == STATE_BLOCKED_PERIODIC); + + assert(now < tt->abs_period_end); + replenish(t, now); + + /* Add to the runqueue */ + st->state = STATE_READY; + ps_list_head_append_d(&run_queue[cos_cpuid()].prio[t->priority - 1], st); + + return 0; +} + +void +slm_sched_fpds_yield(struct slm_thd *t, struct slm_thd *yield_to) +{ + + // TODO: Not implemented yet + assert(0); + + struct slm_sched_thd *st = slm_thd_sched_policy(t); + + ps_list_rem_d(st); + ps_list_head_append_d(&run_queue[cos_cpuid()].prio[t->priority], slm_thd_sched_policy(yield_to)); +} + +int +slm_sched_fpds_thd_init(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + + t->priority = SLM_FPRR_PRIO_LOWEST; + st->state = STATE_READY; + + ps_list_init_d(st); + + return 0; +} + +void +slm_sched_fpds_thd_deinit(struct slm_thd *t) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + + // Remove from runqueue + st->state = STATE_DEINIT; + ps_list_rem_d(slm_thd_sched_policy(t)); +} + +static void +update_queue(struct slm_thd *t, tcap_prio_t prio) +{ + struct slm_sched_thd *st = slm_thd_sched_policy(t); + + t->priority = prio; + ps_list_rem_d(st); /* if we're already on a list, and we're updating priority */ + ps_list_head_append_d(&run_queue[cos_cpuid()].prio[prio], st); + + return; +} + +static void +update_period(struct slm_thd *t, cycles_t period) +{ + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + tt->period = period; + tt->abs_period_end = tt->abs_period_start + period; + + COS_TRACE("update_period(): TID: %lu Period: %llu\n", t->tid, tt->period, 0); + + return; +} + +static void +update_budget(struct slm_thd *t, cycles_t budget) +{ + struct slm_timer_thd *tt = slm_thd_timer_policy(t); + + tt->budget = budget; + tt->initial_budget = budget; + tt->is_budgeted = 1; + + COS_TRACE("update_budget(): TID: %lu Budget: %llu\n", t->tid, tt->budget, 0); + + return; +} + +int +slm_sched_fpds_thd_update(struct slm_thd *t, sched_param_type_t type, unsigned int v) +{ + + switch (type) { + case SCHEDP_INIT_PROTO: + { + update_queue(t, 0); + + return 0; + } + case SCHEDP_INIT: + { + update_queue(t, SLM_FPRR_PRIO_LOWEST); + + return 0; + } + case SCHEDP_PRIO: + { + assert(v >= SLM_FPRR_PRIO_HIGHEST && v <= SLM_FPRR_PRIO_LOWEST); + update_queue(t, v); + + return 0; + } + case SCHEDP_BUDGET: + { + update_budget(t, v); + + return 0; + } + case SCHEDP_WINDOW: + { + assert(v <= SLM_WINDOW_HIGHEST && v >= SLM_WINDOW_LOWEST); + update_period(t, v); + + return 0; + } + default: + return -1; + } +} + +void +slm_sched_fpds_init(void) +{ + int i; + + for (i = 0 ; i < SLM_FPRR_NPRIOS ; i++) { + ps_list_head_init(&run_queue[cos_cpuid()].prio[i]); + } +} diff --git a/src/components/lib/slm/fpds.h b/src/components/lib/slm/fpds.h new file mode 100644 index 000000000..37cd6108a --- /dev/null +++ b/src/components/lib/slm/fpds.h @@ -0,0 +1,47 @@ +#ifndef FPDS_H +#define FPDS_H + +#include +#include +#include +#include +#include +#include +#include + +SLM_MODULES_POLICY_PROTOTYPES(fpds) + +SLM_MODULES_TIMER_PROTOTYPES(fpds) + +#define SLM_FPRES_REPL_WINDOW_SIZE 5 + +enum state_t { + STATE_EXPENDED, + STATE_READY, + STATE_BLOCKED, + STATE_BLOCKED_PERIODIC, + STATE_RUNNING, + STATE_DEINIT, +}; + +struct slm_sched_thd { + struct ps_list list; + enum state_t state; /* thread state for the policy */ +}; + +struct slm_timer_thd { + int timeout_idx; /* where are we in the heap? */ + cycles_t abs_next_processing; + cycles_t abs_period_start; + cycles_t abs_period_end; + cycles_t period; + budget_t budget; /* budget can hold negative values */ + budget_t initial_budget; + int is_budgeted; +}; + +void set_next_timer_interrupt(struct slm_thd *t, cycles_t now); +void expended(struct slm_thd *t); +void replenish(struct slm_thd *t, cycles_t now); + +#endif /* FPRES_H */ diff --git a/src/components/lib/slm/fprr.c b/src/components/lib/slm/fprr.c index f9939ba7b..0db33df7d 100644 --- a/src/components/lib/slm/fprr.c +++ b/src/components/lib/slm/fprr.c @@ -19,7 +19,7 @@ struct runqueue threads[NUM_CPU]; /* No RR based on execution, yet */ void -slm_sched_fprr_execution(struct slm_thd *t, cycles_t cycles) +slm_sched_fprr_execution(struct slm_thd *t, cycles_t cycles, cycles_t now) { return; } /* simply dump a core's task queue for debug usage */ @@ -51,7 +51,7 @@ debug_dump_info(void) } struct slm_thd * -slm_sched_fprr_schedule(void) +slm_sched_fprr_schedule(cycles_t now) { int i; struct slm_sched_thd *t; diff --git a/src/components/lib/slm/slm.c b/src/components/lib/slm/slm.c index efc82ab2d..23f62c87e 100644 --- a/src/components/lib/slm/slm.c +++ b/src/components/lib/slm/slm.c @@ -460,6 +460,42 @@ slm_get_cycs_per_usec(void) return (unsigned long)g->cyc_per_usec; } +/* TODO: Added for test purposes */ +void +slm_thd_get_param(thdid_t tid, slm_thd_params_t param, void* val) +{ + struct slm_global *g = slm_global(); + struct slm_thd *t; + + if (tid == 0) { + t = &g->idle_thd; + } else if (tid == 1) { + t = &g->sched_thd; + } else { + t = slm_thd_lookup(tid); + } + + assert(t); + assert(val); + + switch (param) + { + case SLM_THD_EXEC_TIME: + { + *(cycles_t*)val = t->total_exec_time; + break; + } + case SLM_THD_SWITCH_CNT: + { + *(u16_t*)val = t->switch_cnt; + break; + } + default: + assert(0); + break; + } +} + static void slm_sched_loop_intern(int non_block) { @@ -471,6 +507,7 @@ slm_sched_loop_intern(int non_block) /* Only the scheduler thread should call this function. */ assert(cos_thdid() == us->tid); + printc("Scheduler thread id %ld\n", cos_thdid()); while (1) { int pending, ret; @@ -552,7 +589,7 @@ slm_sched_loop_intern(int non_block) if (unlikely(slm_state_is_dead(t->state))) continue; /* Notify the policy that some execution has happened. */ - slm_sched_execution(t, cycles); + slm_sched_execution(t, cycles, slm_now()); if (blocked) { assert(cycles); @@ -572,6 +609,12 @@ slm_sched_loop_intern(int non_block) } } +// TODO: Added for tracing, remove +void slm_idle_iteration(void) +{ + cos_trace_print_buffer(); +} + void slm_sched_loop(void) { @@ -603,9 +646,15 @@ slm_init(thdcap_t thd, thdid_t tid) .thd = sched_aep->thd, .tid = sched_aep->tid, .rcv = sched_aep->rcv, + .total_exec_time = 0, + .switch_cnt = 0, .cpuid = cos_cpuid(), .priority = TCAP_PRIO_MAX }; + + // Initialize scheduled_thd to scheh_thd + g->scheduled_thd = s; + ps_list_init(s, thd_list); ps_list_init(s, graveyard_list); assert(s->tid == cos_thdid()); @@ -617,6 +666,8 @@ slm_init(thdcap_t thd, thdid_t tid) .thd = thd, .tid = tid, .rcv = 0, + .total_exec_time = 0, + .switch_cnt = 0, .cpuid = cos_cpuid(), .priority = TCAP_PRIO_MIN }; @@ -627,6 +678,8 @@ slm_init(thdcap_t thd, thdid_t tid) ps_list_head_init(&g->graveyard_head); g->cyc_per_usec = cos_hw_cycles_per_usec(BOOT_CAPTBL_SELF_INITHW_BASE); + printc("Cycles per usec: %d\n", g->cyc_per_usec); + g->lock.owner_contention = 0; slm_sched_init(); diff --git a/src/components/lib/slm/slm.h b/src/components/lib/slm/slm.h index 3855b5771..a47c3156f 100644 --- a/src/components/lib/slm/slm.h +++ b/src/components/lib/slm/slm.h @@ -99,6 +99,9 @@ struct slm_thd { asndcap_t asnd; tcap_prio_t priority; cpuid_t cpuid; + //TODO: Added for getting execution and switch count, remove after + cycles_t total_exec_time; + u16_t switch_cnt; /* Execution information retrieved by the scheduler thread */ struct event_info event_info; @@ -122,6 +125,14 @@ slm_now(void) unsigned long slm_get_cycs_per_usec(void); +//TODO: Added for getting execution and switch count, remove after +typedef enum { + SLM_THD_EXEC_TIME = 0, + SLM_THD_SWITCH_CNT = 1, +} slm_thd_params_t; + +void slm_thd_get_param(thdid_t tid, slm_thd_params_t param, void *value); + #include /*** diff --git a/src/components/lib/slm/slm_api.h b/src/components/lib/slm/slm_api.h index 331e0f12b..f90515873 100644 --- a/src/components/lib/slm/slm_api.h +++ b/src/components/lib/slm/slm_api.h @@ -126,12 +126,12 @@ int slm_sched_wakeup(struct slm_thd *t); * Return the next thread (e.g. highest priority thread) that should * execute. Can return `NULL` if no thread is runnable. */ -struct slm_thd *slm_sched_schedule(void); +struct slm_thd *slm_sched_schedule(cycles_t cycles); /** * Called to notify the scheduler that some amount of execution for * thread t has elapsed. */ -void slm_sched_execution(struct slm_thd *t, cycles_t cycles); +void slm_sched_execution(struct slm_thd *t, cycles_t cycles, cycles_t now); /*** * Resource APIs. @@ -203,17 +203,17 @@ typedef void (*thd_fn_t)(void *); { return slm_sched_##schedpol##_wakeup(t); } \ void slm_sched_yield(struct slm_thd *t, struct slm_thd *yield_to) \ { return slm_sched_##schedpol##_yield(t, yield_to); } \ - struct slm_thd *slm_sched_schedule(void) \ - { return slm_sched_##schedpol##_schedule(); } \ - void slm_sched_execution(struct slm_thd *t, cycles_t c) \ - { slm_sched_##schedpol##_execution(t, c); } \ + struct slm_thd *slm_sched_schedule(cycles_t c) \ + { return slm_sched_##schedpol##_schedule(c); } \ + void slm_sched_execution(struct slm_thd *t, cycles_t c, cycles_t n) \ + { slm_sched_##schedpol##_execution(t, c, n); } \ \ struct slm_thd *slm_thd_lookup(thdid_t id) \ { return slm_thd_##respol##_lookup(id); } #define SLM_MODULES_POLICY_PROTOTYPES(schedpol) \ - void slm_sched_##schedpol##_execution(struct slm_thd *t, cycles_t cycles); \ - struct slm_thd *slm_sched_##schedpol##_schedule(void); \ + void slm_sched_##schedpol##_execution(struct slm_thd *t, cycles_t cycles, cycles_t now); \ + struct slm_thd *slm_sched_##schedpol##_schedule(cycles_t c); \ int slm_sched_##schedpol##_block(struct slm_thd *t); \ int slm_sched_##schedpol##_wakeup(struct slm_thd *t); \ void slm_sched_##schedpol##_yield(struct slm_thd *t, struct slm_thd *yield_to); \ diff --git a/src/components/lib/slm/slm_private.h b/src/components/lib/slm/slm_private.h index f7d9b55e7..e9f8331e5 100644 --- a/src/components/lib/slm/slm_private.h +++ b/src/components/lib/slm/slm_private.h @@ -4,6 +4,7 @@ #include #include #include +#include typedef unsigned long slm_cs_cached_t; /* Critical section (cs) API to protect scheduler data-structures */ @@ -63,6 +64,10 @@ struct slm_global { struct slm_thd sched_thd; struct slm_thd idle_thd; + /* TODO: Should be per core */ + struct slm_thd *scheduled_thd; + cycles_t scheduled_thd_start; + int cyc_per_usec; int timer_set; /* is the timer set? */ cycles_t timer_next; /* ...what is it set to? */ @@ -175,9 +180,24 @@ slm_cs_exit_reschedule(struct slm_thd *curr, slm_cs_flags_t flags) try_again: tok = cos_sched_sync(); - if (flags & SLM_CS_CHECK_TIMEOUT && g->timer_set) { - cycles_t now = slm_now(); + // --> Interrupt punishes the current thread + cycles_t now = slm_now(); + // --> Interrupt punishes the next thread + + /* Notify the policy that some execution has happened. */ + cycles_t execution_time = now - g->scheduled_thd_start; + + g->scheduled_thd->switch_cnt++; + g->scheduled_thd->total_exec_time += execution_time; + + COS_TRACE("<< [TID, Amount of execution] [\"%ld\", %llu],\n", g->scheduled_thd->tid, execution_time, now); + + if(!(g->scheduled_thd->properties & SLM_THD_PROPERTY_SPECIAL)) { + slm_sched_execution(g->scheduled_thd, execution_time, now); + } + + if (flags & SLM_CS_CHECK_TIMEOUT && g->timer_set) { /* Do we need to recompute the timer? */ if (!cycles_greater_than(g->timer_next, now)) { g->timer_set = 0; @@ -186,16 +206,24 @@ slm_cs_exit_reschedule(struct slm_thd *curr, slm_cs_flags_t flags) } } + // TODO: What about time lost in + // - processing kernel events? + // - processing slm_timer_expire(), slm_sched_schedule()? + /* Make a policy decision! */ - t = slm_sched_schedule(); + t = slm_sched_schedule(now); if (unlikely(!t)) t = &g->idle_thd; + g->scheduled_thd_start = now; + g->scheduled_thd = t; + assert(slm_state_is_runnable(t->state)); slm_cs_exit(NULL, flags); ret = slm_thd_activate(curr, t, tok, 0); if (unlikely(ret != 0)) { + // TODO: What about previous slm_sched_schedule()? We already dequeued the thread from the list! /* Assuming only the single tcap with infinite budget...should not get EPERM */ assert(ret != -EPERM); assert(ret != -EINVAL); diff --git a/src/kernel/include/shared/cos_types.h b/src/kernel/include/shared/cos_types.h index 8423981c8..d71372db4 100644 --- a/src/kernel/include/shared/cos_types.h +++ b/src/kernel/include/shared/cos_types.h @@ -40,6 +40,7 @@ typedef u64_t tcap_prio_t; typedef u64_t tcap_uid_t; typedef u32_t sched_tok_t; typedef u32_t asid_t; +typedef s64_t budget_t; /* * This is more complicated than a direct comparison due to @@ -666,4 +667,4 @@ enum { #define VM_EXIT_REASON_XSETBV 55 #define VM_EXIT_REASON_APIC_WRITE 56 -#endif /* TYPES_H */ \ No newline at end of file +#endif /* TYPES_H */