blob: 45e9ee58cc3acd86b8d722e044fa3eaa94efb14c [file] [log] [blame] [edit]
#include <arch/arch.h>
#include <stdbool.h>
#include <errno.h>
#include <vcore.h>
#include <mcs.h>
#include <sys/param.h>
#include <parlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdio.h>
#include <event.h>
#include <uthread.h>
#include <ucq.h>
#include <ros/arch/membar.h>
#include <printf-ext.h>
/* starting with 1 since we alloc vcore0's stacks and TLS in vcore_init(). */
static size_t _max_vcores_ever_wanted = 1;
atomic_t nr_new_vcores_wanted;
atomic_t vc_req_being_handled;
bool vc_initialized = FALSE;
__thread struct syscall __vcore_one_sysc = {.flags = (atomic_t)SC_DONE, 0};
/* Per vcore entery function used when reentering at the top of a vcore's stack */
static __thread void (*__vcore_reentry_func)(void) = NULL;
/* TODO: probably don't want to dealloc. Considering caching */
static void free_transition_tls(int id)
{
if (get_vcpd_tls_desc(id)) {
/* Note we briefly have no TLS desc in VCPD. This is fine so long as
* that vcore doesn't get started fresh before we put in a new desc */
free_tls(get_vcpd_tls_desc(id));
set_vcpd_tls_desc(id, NULL);
}
}
static int allocate_transition_tls(int id)
{
/* We want to free and then reallocate the tls rather than simply
* reinitializing it because its size may have changed. TODO: not sure if
* this is right. 0-ing is one thing, but freeing and reallocating can be
* expensive, esp if syscalls are involved. Check out glibc's
* allocatestack.c for what might work. */
free_transition_tls(id);
void *tcb = allocate_tls();
if (!tcb) {
errno = ENOMEM;
return -1;
}
set_vcpd_tls_desc(id, tcb);
return 0;
}
static void free_transition_stack(int id)
{
// don't actually free stacks
}
static int allocate_transition_stack(int id)
{
struct preempt_data *vcpd = vcpd_of(id);
if (vcpd->transition_stack)
return 0; // reuse old stack
void* stackbot = mmap(0, TRANSITION_STACK_SIZE,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_POPULATE|MAP_ANONYMOUS, -1, 0);
if(stackbot == MAP_FAILED)
return -1; // errno set by mmap
vcpd->transition_stack = (uintptr_t)stackbot + TRANSITION_STACK_SIZE;
return 0;
}
void vcore_init(void)
{
uintptr_t mmap_block;
/* Note this is racy, but okay. The first time through, we are _S */
init_once_racy(return);
/* Need to alloc vcore0's transition stuff here (technically, just the TLS)
* so that schedulers can use vcore0's transition TLS before it comes up in
* vcore_entry() */
if(allocate_transition_stack(0) || allocate_transition_tls(0))
goto vcore_init_fail;
/* Initialize our VCPD event queues' ucqs, two pages per ucq, 4 per vcore */
mmap_block = (uintptr_t)mmap(0, PGSIZE * 4 * max_vcores(),
PROT_WRITE | PROT_READ,
MAP_POPULATE | MAP_ANONYMOUS, -1, 0);
/* Yeah, this doesn't fit in the error-handling scheme, but this whole
* system doesn't really handle failure, and needs a rewrite involving less
* mmaps/munmaps. */
assert(mmap_block);
/* Note we may end up doing vcore 0's elsewhere, for _Ss, or else have a
* separate ev_q for that. */
for (int i = 0; i < max_vcores(); i++) {
/* four pages total for both ucqs from the big block (2 pages each) */
ucq_init_raw(&vcpd_of(i)->ev_mbox_public.ev_msgs,
mmap_block + (4 * i ) * PGSIZE,
mmap_block + (4 * i + 1) * PGSIZE);
ucq_init_raw(&vcpd_of(i)->ev_mbox_private.ev_msgs,
mmap_block + (4 * i + 2) * PGSIZE,
mmap_block + (4 * i + 3) * PGSIZE);
}
atomic_init(&vc_req_being_handled, 0);
assert(!in_vcore_context());
/* no longer need to enable notifs on vcore 0, it is set like that by
* default (so you drop into vcore context immediately on transtioning to
* _M) */
vc_initialized = TRUE;
return;
vcore_init_fail:
assert(0);
}
/* Helper functions used to reenter at the top of a vcore's stack for an
* arbitrary function */
static void __attribute__((noinline, noreturn))
__vcore_reenter()
{
__vcore_reentry_func();
assert(0);
}
void vcore_reenter(void (*entry_func)(void))
{
assert(in_vcore_context());
struct preempt_data *vcpd = vcpd_of(vcore_id());
__vcore_reentry_func = entry_func;
set_stack_pointer((void*)vcpd->transition_stack);
cmb();
__vcore_reenter();
}
/* This gets called in glibc before calling the programs 'main'. Need to set
* ourselves up so that thread0 is a uthread, and then register basic signals to
* go to vcore 0. */
void vcore_event_init(void)
{
register_printf_specifier('r', printf_errstr, printf_errstr_info);
/* set up our thread0 as a uthread */
uthread_slim_init();
/* TODO: register for other kevents/signals and whatnot (can probably reuse
* the simple ev_q). Could also do this via explicit functions from the
* program. */
}
/* Helper, picks some sane defaults and changes the process into an MCP */
void vcore_change_to_m(void)
{
int ret;
__procdata.res_req[RES_CORES].amt_wanted = 1;
__procdata.res_req[RES_CORES].amt_wanted_min = 1; /* whatever */
assert(!in_multi_mode());
assert(!in_vcore_context());
ret = sys_change_to_m();
assert(!ret);
assert(in_multi_mode());
assert(!in_vcore_context());
}
/* Returns -1 with errno set on error, or 0 on success. This does not return
* the number of cores actually granted (though some parts of the kernel do
* internally).
*
* This tries to get "more vcores", based on the number we currently have.
* We'll probably need smarter 2LSs in the future that just directly set
* amt_wanted. What happens is we can have a bunch of 2LS vcore contexts
* trying to get "another vcore", which currently means more than num_vcores().
* If you have someone ask for two more, and then someone else ask for one more,
* how many you ultimately ask for depends on if the kernel heard you and
* adjusted num_vcores in between the two calls. Or maybe your amt_wanted
* already was num_vcores + 5, so neither call is telling the kernel anything
* new. It comes down to "one more than I have" vs "one more than I've already
* asked for".
*
* So for now, this will keep the older behavior (one more than I have). It
* will try to accumulate any concurrent requests, and adjust amt_wanted up.
* Interleaving, repetitive calls (everyone asking for one more) may get
* ignored.
*
* Note the doesn't block or anything (despite the min number requested is
* 1), since the kernel won't block the call.
*
* There are a few concurrency concerns. We have _max_vcores_ever_wanted,
* initialization of new vcore stacks/TLSs, making sure we don't ask for too
* many (minor point), and most importantly not asking the kernel for too much
* or otherwise miscommunicating our desires to the kernel. Remember, the
* kernel wants just one answer from the process about what it wants, and it is
* up to the process to figure that out.
*
* So we basically have one thread do the submitting/prepping/bookkeeping, and
* other threads come in just update the number wanted and make sure someone
* is sorting things out. This will perform a bit better too, since only one
* vcore makes syscalls (which hammer the proc_lock). This essentially has
* cores submit work, and one core does the work (like Eric's old delta
* functions).
*
* There's a slight semantic change: this will return 0 (success) for the
* non-submitters, and 0 if we submitted. -1 only if the submitter had some
* non-kernel failure.
*
* Also, beware that this (like the old version) doesn't protect with races on
* num_vcores(). num_vcores() is how many you have now or very soon (accounting
* for messages in flight that will take your cores), not how many you told the
* kernel you want. */
int vcore_request(long nr_new_vcores)
{
long nr_to_prep_now, nr_vcores_wanted;
assert(vc_initialized);
/* Early sanity checks */
if ((nr_new_vcores < 0) || (nr_new_vcores + num_vcores() > max_vcores()))
return -1; /* consider ERRNO */
/* Post our desires (ROS atomic_add() conflicts with glibc) */
atomic_fetch_and_add(&nr_new_vcores_wanted, nr_new_vcores);
try_handle_it:
cmb(); /* inc before swap. the atomic is a CPU mb() */
if (atomic_swap(&vc_req_being_handled, 1)) {
/* We got a 1 back, so someone else is already working on it */
return 0;
}
/* So now we're the ones supposed to handle things. This does things in the
* "increment based on the number we have", vs "increment on the number we
* said we want".
*
* Figure out how many we have, though this is racy. Yields/preempts/grants
* will change this over time, and we may end up asking for less than we
* had. */
nr_vcores_wanted = num_vcores();
/* Pull all of the vcores wanted into our local variable, where we'll deal
* with prepping/requesting that many vcores. Keep doing this til we think
* no more are wanted. */
while ((nr_to_prep_now = atomic_swap(&nr_new_vcores_wanted, 0))) {
nr_vcores_wanted += nr_to_prep_now;
/* Don't bother prepping or asking for more than we can ever get */
nr_vcores_wanted = MIN(nr_vcores_wanted, max_vcores());
/* Make sure all we might ask for are prepped */
for (long i = _max_vcores_ever_wanted; i < nr_vcores_wanted; i++) {
if (allocate_transition_stack(i) || allocate_transition_tls(i)) {
atomic_set(&vc_req_being_handled, 0); /* unlock and bail out*/
return -1;
}
_max_vcores_ever_wanted++; /* done in the loop to handle failures*/
}
}
cmb(); /* force a reread of num_vcores() */
/* Update amt_wanted if we now want *more* than what the kernel already
* knows. See notes in the func doc. */
if (nr_vcores_wanted > __procdata.res_req[RES_CORES].amt_wanted)
__procdata.res_req[RES_CORES].amt_wanted = nr_vcores_wanted;
/* If num_vcores isn't what we want, we can poke the ksched. Due to some
* races with yield, our desires may be old. Not a big deal; any vcores
* that pop up will just end up yielding (or get preempt messages.) */
if (nr_vcores_wanted > num_vcores())
sys_poke_ksched(0, RES_CORES); /* 0 -> poke for ourselves */
/* Unlock, (which lets someone else work), and check to see if more work
* needs to be done. If so, we'll make sure it gets handled. */
atomic_set(&vc_req_being_handled, 0); /* unlock, to allow others to try */
wrmb();
/* check for any that might have come in while we were out */
if (atomic_read(&nr_new_vcores_wanted))
goto try_handle_it;
return 0;
}
/* This can return, if you failed to yield due to a concurrent event. Note
* we're atomicly setting the CAN_RCV flag, and aren't bothering with CASing
* (either with the kernel or uthread's handle_indirs()). We don't particularly
* care what other code does - we intend to set those flags no matter what. */
void vcore_yield(bool preempt_pending)
{
unsigned long old_nr;
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
__sync_fetch_and_and(&vcpd->flags, ~VC_CAN_RCV_MSG);
/* no wrmb() necessary, handle_events() has an mb() if it is checking */
/* Clears notif pending and tries to handle events. This is an optimization
* to avoid the yield syscall if we have an event pending. If there is one,
* we want to unwind and return to the 2LS loop, where we may not want to
* yield anymore.
* Note that the kernel only cares about CAN_RCV_MSG for the desired vcore,
* not for a FALLBACK. */
if (handle_events(vcoreid)) {
__sync_fetch_and_or(&vcpd->flags, VC_CAN_RCV_MSG);
return;
}
/* If we are yielding since we don't want the core, tell the kernel we want
* one less vcore (vc_yield assumes a dumb 2LS).
*
* If yield fails (slight race), we may end up having more vcores than
* amt_wanted for a while, and might lose one later on (after a
* preempt/timeslicing) - the 2LS will have to notice eventually if it
* actually needs more vcores (which it already needs to do). amt_wanted
* could even be 0.
*
* In general, any time userspace decrements or sets to 0, it could get
* preempted, so the kernel will still give us at least one, until the last
* vcore properly yields without missing a message (and becomes a WAITING
* proc, which the ksched will not give cores to).
*
* I think it's possible for userspace to do this (lock, read amt_wanted,
* check all message queues for all vcores, subtract amt_wanted (not set to
* 0), unlock) so long as every event handler +1s the amt wanted, but that's
* a huge pain, and we already have event handling code making sure a
* process can't sleep (transition to WAITING) if a message arrives (can't
* yield if notif_pending, can't go WAITING without yielding, and the event
* posting the notif_pending will find the online VC or be delayed by
* spinlock til the proc is WAITING). */
if (!preempt_pending) {
do {
old_nr = __procdata.res_req[RES_CORES].amt_wanted;
if (old_nr == 0)
break;
} while (!__sync_bool_compare_and_swap(
&__procdata.res_req[RES_CORES].amt_wanted,
old_nr, old_nr - 1));
}
/* We can probably yield. This may pop back up if notif_pending became set
* by the kernel after we cleared it and we lost the race. */
sys_yield(preempt_pending);
__sync_fetch_and_or(&vcpd->flags, VC_CAN_RCV_MSG);
}
/* Enables notifs, and deals with missed notifs by self notifying. This should
* be rare, so the syscall overhead isn't a big deal. The other alternative
* would be to uthread_yield(), which would require us to revert some uthread
* interface changes. */
void enable_notifs(uint32_t vcoreid)
{
__enable_notifs(vcoreid);
wrmb(); /* need to read after the write that enabled notifs */
/* Note we could get migrated before executing this. If that happens, our
* vcore had gone into vcore context (which is what we wanted), and this
* self_notify to our old vcore is spurious and harmless. */
if (vcpd_of(vcoreid)->notif_pending)
sys_self_notify(vcoreid, EV_NONE, 0, TRUE);
}
/* Helper to disable notifs. It simply checks to make sure we disabled uthread
* migration, which is a common mistake. */
void disable_notifs(uint32_t vcoreid)
{
if (!in_vcore_context() && current_uthread)
assert(current_uthread->flags & UTHREAD_DONT_MIGRATE);
__disable_notifs(vcoreid);
}
/* Like smp_idle(), this will put the core in a state that it can only be woken
* up by an IPI. In the future, we may halt or something. This will return if
* an event was pending (could be the one you were waiting for). */
void vcore_idle(void)
{
uint32_t vcoreid = vcore_id();
if (handle_events(vcoreid))
return;
enable_notifs(vcoreid);
while (1) {
cpu_relax();
}
}
/* Helper, that actually makes sure a vcore is running. Call this is you really
* want vcoreid. More often, you'll want to call the regular version. */
static void __ensure_vcore_runs(uint32_t vcoreid)
{
if (vcore_is_preempted(vcoreid)) {
printd("[vcore]: VC %d changing to VC %d\n", vcore_id(), vcoreid);
/* Note that at this moment, the vcore could still be mapped (we're
* racing with __preempt. If that happens, we'll just fail the
* sys_change_vcore(), and next time __ensure runs we'll get it. */
/* We want to recover them from preemption. Since we know they have
* notifs disabled, they will need to be directly restarted, so we can
* skip the other logic and cut straight to the sys_change_vcore() */
sys_change_vcore(vcoreid, FALSE);
}
}
/* Helper, looks for any preempted vcores, making sure each of them runs at some
* point. This is pretty heavy-weight, and should be used to help get out of
* weird deadlocks (spinning in vcore context, waiting on another vcore). If
* you might know which vcore you are waiting on, use ensure_vc_runs. */
static void __ensure_all_run(void)
{
for (int i = 0; i < max_vcores(); i++)
__ensure_vcore_runs(i);
}
/* Makes sure a vcore is running. If it is preempted, we'll switch to
* it. This will return, either immediately if the vcore is running, or later
* when someone preempt-recovers us.
*
* If you pass in your own vcoreid, this will make sure all other preempted
* vcores run. */
void ensure_vcore_runs(uint32_t vcoreid)
{
/* if the vcoreid is ourselves, make sure everyone else is running */
if (vcoreid == vcore_id()) {
__ensure_all_run();
return;
}
__ensure_vcore_runs(vcoreid);
}
#define NR_RELAX_SPINS 1000
/* If you are spinning in vcore context and it is likely that you don't know who
* you are waiting on, call this. It will spin for a bit before firing up the
* potentially expensive __ensure_all_run(). Don't call this from uthread
* context. sys_change_vcore will probably mess you up. */
void cpu_relax_vc(uint32_t vcoreid)
{
unsigned int spun = 0;
assert(in_vcore_context());
if (spun++ >= NR_RELAX_SPINS) {
/* if vcoreid == vcore_id(), this might be expensive */
ensure_vcore_runs(vcoreid);
spun = 0;
}
cpu_relax();
}
/* Check with the kernel to determine what vcore we are. Normally, you should
* never call this, since your vcoreid is stored in your TLS. Also, if you call
* it from a uthread, you could get migrated, so you should drop into some form
* of vcore context (DONT_MIGRATE on) */
uint32_t get_vcoreid(void)
{
if (!in_vcore_context()) {
assert(current_uthread);
assert(current_uthread->flags & UTHREAD_DONT_MIGRATE);
}
return __get_vcoreid();
}
/* Debugging helper. Pass in the string you want printed if your vcoreid is
* wrong, and pass in what vcoreid you think you are. Don't call from uthread
* context unless migrations are disabled. Will print some stuff and return
* FALSE if you were wrong. */
bool check_vcoreid(const char *str, uint32_t vcoreid)
{
uint32_t kvcoreid = get_vcoreid();
if (vcoreid != kvcoreid) {
ros_debug("%s: VC %d thought it was VC %d\n", str, kvcoreid, vcoreid);
return FALSE;
}
return TRUE;
}