diff --git a/apps/sel4test-tests/src/helpers.c b/apps/sel4test-tests/src/helpers.c index f3a82439..b3bb8cc5 100644 --- a/apps/sel4test-tests/src/helpers.c +++ b/apps/sel4test-tests/src/helpers.c @@ -376,16 +376,26 @@ void set_helper_mcp(env_t env, helper_thread_t *thread, seL4_Word mcp) assert(error == seL4_NoError); } -void set_helper_affinity(UNUSED env_t env, helper_thread_t *thread, seL4_Word affinity) +int set_helper_affinity_fallible(UNUSED env_t env, helper_thread_t *thread, seL4_Word affinity) { #ifdef CONFIG_KERNEL_MCS seL4_Time timeslice = CONFIG_BOOT_THREAD_TIME_SLICE * US_IN_S; - int error = seL4_SchedControl_Configure(simple_get_sched_ctrl(&env->simple, affinity), - thread->thread.sched_context.cptr, - timeslice, timeslice, 0, 0); - ZF_LOGF_IF(error, "Failed to configure scheduling context"); + return seL4_SchedControl_Configure(simple_get_sched_ctrl(&env->simple, affinity), + thread->thread.sched_context.cptr, + timeslice, timeslice, 0, 0); +#elif CONFIG_MAX_NUM_NODES > 1 + return seL4_TCB_SetAffinity(thread->thread.tcb.cptr, affinity); +#else + assert(affinity == 0); +#endif +} + +void set_helper_affinity(UNUSED env_t env, helper_thread_t *thread, seL4_Word affinity) +{ + int error = set_helper_affinity_fallible(env, thread, affinity); +#ifdef CONFIG_KERNEL_MCS + ZF_LOGF_IF(error, "Failed to configure scheduling context for affinity"); #elif CONFIG_MAX_NUM_NODES > 1 - int error = seL4_TCB_SetAffinity(thread->thread.tcb.cptr, affinity); ZF_LOGF_IF(error, "Failed to set tcb affinity"); #endif } diff --git a/apps/sel4test-tests/src/helpers.h b/apps/sel4test-tests/src/helpers.h index efb60cfa..e783715e 100644 --- a/apps/sel4test-tests/src/helpers.h +++ b/apps/sel4test-tests/src/helpers.h @@ -71,7 +71,12 @@ void set_helper_priority(env_t env, helper_thread_t *thread, seL4_Word prio); /* set a helper threads max control priority */ void set_helper_mcp(env_t env, helper_thread_t *thread, seL4_Word mcp); -/* set a helper threads core affinity. This will have no effect on passive threads. */ +/* set a helper threads core affinity. This will have no effect on passive threads (MCS). + * if it fails, return an error. */ +int set_helper_affinity_fallible(env_t env, helper_thread_t *thread, seL4_Word affinity); + +/* set a helper threads core affinity. This will have no effect on passive threads (MCS). + * if it fails, calls ZF_LOGF. */ void set_helper_affinity(env_t env, helper_thread_t *thread, seL4_Word affinity); /* if CONFIG_KERNEL_MCS is set, set the helpers scheduling parameters */ diff --git a/apps/sel4test-tests/src/tests/binding.c b/apps/sel4test-tests/src/tests/binding.c index bff64e1d..c879dcb3 100644 --- a/apps/sel4test-tests/src/tests/binding.c +++ b/apps/sel4test-tests/src/tests/binding.c @@ -292,3 +292,135 @@ test_notification_binding_with_sc(env_t env) } DEFINE_TEST(BIND006, "Test passing thread notification binding with a scheduling context", test_notification_binding_with_sc, config_set(CONFIG_KERNEL_MCS)) + +static int +test_active_notification_binding_with_sc(env_t env) +{ + seL4_CPtr endpoint, notification; + int error; + helper_thread_t helper; + volatile int state = 0; + + endpoint = vka_alloc_endpoint_leaky(&env->vka); + notification = vka_alloc_notification_leaky(&env->vka); + + create_helper_thread(env, &helper); + + /* set our prio lower so the helper thread runs when we start it */ + set_helper_priority(env, &helper, 10); + error = seL4_TCB_SetPriority(env->tcb, env->tcb, 9); + test_eq(error, seL4_NoError); + + error = seL4_TCB_BindNotification(helper.thread.tcb.cptr, notification); + test_eq(error, seL4_NoError); + + /* start the helper so it is waiting on the endpoint */ + start_helper(env, &helper, (helper_fn_t) bind0005_helper, endpoint, + (seL4_Word) &state, 0, 0); + test_eq(state, 1); + + /* clear its sc */ + error = api_sc_unbind(helper.thread.sched_context.cptr); + test_eq(error, seL4_NoError); + + /* signal it */ + seL4_Signal(notification); + /* it should not progress until it has an SC */ + test_eq(state, 1); + + error = api_sc_bind(helper.thread.sched_context.cptr, notification); + test_eq(error, seL4_NoError); + + /* now it should have got the signal. + * However: due to a kernel design issue https://github.com/seL4/seL4/issues/1617 + * instead it blocks forever. + */ + // test_eq(state, 2); + test_eq(state, 1); + + return sel4test_get_result(); +} +DEFINE_TEST(BIND007, "Test unbind TCB / rebind ntfn (passive server migration) with an active notification", + test_active_notification_binding_with_sc, config_set(CONFIG_KERNEL_MCS)); + +static void +bind0008_helper(seL4_CPtr endpoint, volatile int *state) +{ + *state = 1; + seL4_Wait(endpoint, NULL); + *state = 2; + seL4_Wait(endpoint, NULL); + *state = 3; +} + + +static int +test_active_notification_binding_with_sc_lazy_rebind(env_t env) +{ + seL4_CPtr endpoint, notification; + int error; + helper_thread_t helper; + volatile int state = 0; + + endpoint = vka_alloc_endpoint_leaky(&env->vka); + notification = vka_alloc_notification_leaky(&env->vka); + + create_helper_thread(env, &helper); + + /* set our prio lower so the helper thread runs when we start it */ + set_helper_priority(env, &helper, 10); + error = seL4_TCB_SetPriority(env->tcb, env->tcb, 9); + test_eq(error, seL4_NoError); + + /* set up a bound notification (so endpoint wait <-> notification) */ + error = seL4_TCB_BindNotification(helper.thread.tcb.cptr, notification); + test_eq(error, seL4_NoError); + + /* start the helper so it is waiting on the endpoint */ + start_helper(env, &helper, (helper_fn_t) bind0008_helper, endpoint, + (seL4_Word) &state, 0, 0); + test_eq(state, 1); + + /* lower the helper priority so it won't be running */ + set_helper_priority(env, &helper, 8); + + /* signal the notification - will progress to running on the TCB's SC */ + seL4_Signal(notification); + + /* it should not progress until it is higher priority than us */ + test_eq(state, 1); + + /* check SC still bound to TCB */ + error = api_sc_bind(helper.thread.sched_context.cptr, helper.thread.tcb.cptr); + test_eq(error, seL4_IllegalOperation); + + /* perform lazy rebind */ + error = api_sc_bind(helper.thread.sched_context.cptr, notification); + test_eq(error, seL4_NoError); + + /* allow helper to run by bumping its priority */ + set_helper_priority(env, &helper, 10); + + /* it should now be waiting on its endpoint and will have unbound its TCB SC */ + test_eq(state, 2); + + /* bind and unbind TCB SC to demonstrate that it had been unbound */ + error = api_sc_bind(helper.thread.sched_context.cptr, helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + error = api_sc_unbind_object(helper.thread.sched_context.cptr, helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + /* the notification should still be bound */ + error = api_sc_bind(helper.thread.sched_context.cptr, notification); + test_eq(error, seL4_IllegalOperation); + + /* signal so it should continue */ + seL4_Signal(notification); + + test_eq(state, 3); + + return sel4test_get_result(); +} +DEFINE_TEST(BIND008, "Test lazy SC rebind (passive server migration) with an active notification", + test_active_notification_binding_with_sc_lazy_rebind, config_set(CONFIG_KERNEL_MCS)); diff --git a/apps/sel4test-tests/src/tests/ipc.c b/apps/sel4test-tests/src/tests/ipc.c index 1de6b426..5054e35e 100644 --- a/apps/sel4test-tests/src/tests/ipc.c +++ b/apps/sel4test-tests/src/tests/ipc.c @@ -1356,8 +1356,6 @@ static int test_sched_donation_cross_core(env_t env) return sel4test_get_result(); } -/* This test currently fails. - See https://github.com/seL4/seL4/issues/941 and https://github.com/seL4/seL4/pull/986 */ -DEFINE_TEST(IPC0028, "Cross core sched donation", test_sched_donation_cross_core, false); -/* config_set(CONFIG_KERNEL_MCS) &&(CONFIG_MAX_NUM_NODES > 1)); */ +DEFINE_TEST(IPC0028, "Cross core sched donation", test_sched_donation_cross_core, + config_set(CONFIG_KERNEL_MCS) &&(CONFIG_MAX_NUM_NODES > 1)); #endif /* CONFIG_KERNEL_MCS */ diff --git a/apps/sel4test-tests/src/tests/schedcontext_smp.c b/apps/sel4test-tests/src/tests/schedcontext_smp.c new file mode 100644 index 00000000..13b7c86b --- /dev/null +++ b/apps/sel4test-tests/src/tests/schedcontext_smp.c @@ -0,0 +1,739 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include +#include +#include +#include +#include + +#include "../helpers.h" + +/* FIXME: this is a temporary hack and should be exported via libsel4. + * https://github.com/seL4/seL4/issues/1659 + */ +#ifndef MAX_PERIOD_US +/* Conservative, but long enough (~ half an hour) */ +#define MAX_PERIOD_US (1ULL << 31) +#endif + +#ifndef MIN_BUDGET_US +#ifdef CONFIG_PLAT_TK1 +#define MIN_BUDGET_US (2 * 100) +#else +#define MIN_BUDGET_US (2 * 10) +#endif +#endif + +#ifdef CONFIG_KERNEL_MCS + +static inline seL4_CPtr badge_endpoint(env_t env, seL4_Word badge, seL4_CPtr ep) +{ + seL4_CPtr slot = get_free_slot(env); + int error = cnode_mint(env, ep, slot, seL4_AllRights, badge); + test_error_eq(error, seL4_NoError); + return slot; +} + +void sched_context_smp_001_recipient_fn(seL4_CPtr ep, void *arg1, void *arg2, void *arg3) +{ + /* Basically, say yes we ran */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + seL4_Send(ep, tag); +} + +int test_smp_bind_tcb_other_core(env_t env) +{ + /** + * This is for testing when the target TCB has no SchedContext. + * + * Cases: + * - bind local SC to not running remote TCB + * - bind remote SC to not running remote TCB + * - bind remote SC to local thread (which necessarily is not running) + * + * The local:local case is tested elsewhere. + **/ + + helper_thread_t bind_recipient; + seL4_CPtr ep_recipient; + seL4_CPtr sc; + int error; + + seL4_CPtr sc_ctrl_local = simple_get_sched_ctrl(&env->simple, 0); + seL4_CPtr sc_ctrl_remote = simple_get_sched_ctrl(&env->simple, 1); + seL4_Time timeslice = CONFIG_BOOT_THREAD_TIME_SLICE * US_IN_S; + + ep_recipient = vka_alloc_endpoint_leaky(&env->vka); + + { + printf("Local SC to not running remote TCB\n"); + + /* Our SC is local */ + sc = vka_alloc_sched_context_leaky(&env->vka); + error = seL4_SchedControl_Configure(sc_ctrl_local, sc, timeslice, timeslice, 0, 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + create_helper_thread(env, &bind_recipient); + NAME_THREAD(get_helper_tcb(&bind_recipient), "SCHED_CONTEXT_SMP_001 Bind Recipient"); + + /* move the recipient to the other core so it is a remote TCB. */ + set_helper_affinity(env, &bind_recipient, /* core */ 1); + /* unbind recipient so we can bind to it, also so it doesn't run */ + error = api_sc_unbind(get_helper_sched_context(&bind_recipient)); + ZF_LOGF_IF(error, "unable to unbind"); + + start_helper(env, &bind_recipient, (helper_fn_t) sched_context_smp_001_recipient_fn, ep_recipient, 0, 0, 0); + + int error = seL4_SchedContext_Bind(sc, get_helper_tcb(&bind_recipient)); + test_eq(error, seL4_NoError); + + /* We know that this thread got to run if it signalled this */ + seL4_Wait(ep_recipient, NULL); + + cleanup_helper(env, &bind_recipient); + } + + { + printf("Remote SC to not running remote TCB\n"); + + /* Our SC is remote */ + sc = vka_alloc_sched_context_leaky(&env->vka); + error = seL4_SchedControl_Configure(sc_ctrl_remote, sc, timeslice, timeslice, 0, 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + create_helper_thread(env, &bind_recipient); + NAME_THREAD(get_helper_tcb(&bind_recipient), "SCHED_CONTEXT_SMP_001 Bind Recipient"); + + /* move the recipient to the other core so it is a remote TCB. */ + set_helper_affinity(env, &bind_recipient, /* core */ 1); + /* unbind recipient so we can bind to it, also so it doesn't run */ + error = api_sc_unbind(get_helper_sched_context(&bind_recipient)); + ZF_LOGF_IF(error, "unable to unbind"); + + start_helper(env, &bind_recipient, (helper_fn_t) sched_context_smp_001_recipient_fn, ep_recipient, 0, 0, 0); + + int error = seL4_SchedContext_Bind(sc, get_helper_tcb(&bind_recipient)); + test_eq(error, seL4_NoError); + + /* We know that this thread got to run if it signalled this */ + seL4_Wait(ep_recipient, NULL); + + cleanup_helper(env, &bind_recipient); + } + + { + printf("Remote SC to local thread\n"); + + /* Our SC is remote */ + sc = vka_alloc_sched_context_leaky(&env->vka); + error = seL4_SchedControl_Configure(sc_ctrl_remote, sc, timeslice, timeslice, 0, 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + create_helper_thread(env, &bind_recipient); + NAME_THREAD(get_helper_tcb(&bind_recipient), "SCHED_CONTEXT_SMP_001 Bind Recipient"); + + /* unbind recipient so we can bind to it, also so it doesn't run */ + error = api_sc_unbind(get_helper_sched_context(&bind_recipient)); + ZF_LOGF_IF(error, "unable to unbind"); + + start_helper(env, &bind_recipient, (helper_fn_t) sched_context_smp_001_recipient_fn, ep_recipient, 0, 0, 0); + + int error = seL4_SchedContext_Bind(sc, get_helper_tcb(&bind_recipient)); + test_eq(error, seL4_NoError); + + /* We know that this thread got to run if it signalled this */ + seL4_Wait(ep_recipient, NULL); + + cleanup_helper(env, &bind_recipient); + } + + return sel4test_get_result(); +} + +DEFINE_TEST(SCHED_CONTEXT_SMP_001, "Test SC bind to a TCB on another core", + test_smp_bind_tcb_other_core, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +int sched_context_smp_002_helper_fn(void) +{ + return 1; +} + +void sched_context_smp_002_lazy_fn(seL4_CPtr endpoint, seL4_CPtr notification) +{ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + (void)seL4_NBSendWait(endpoint, tag, notification, NULL); +} + +void sched_context_smp_002_high_priority_helper(seL4_CPtr endpoint_cross, seL4_CPtr ep_hi_to_low) +{ + (void)seL4_Wait(endpoint_cross, NULL); + + seL4_Send(ep_hi_to_low, seL4_MessageInfo_new(0, 0, 0, 0)); +} + +int test_passive_thread_start_smp(env_t env) +{ + helper_thread_t helper; + seL4_CPtr notification = vka_alloc_notification_leaky(&env->vka); + int error; + + seL4_Time timeslice = CONFIG_BOOT_THREAD_TIME_SLICE * US_IN_S; + + seL4_CPtr sc_remote = vka_alloc_sched_context_leaky(&env->vka); + error = seL4_SchedControl_Configure(simple_get_sched_ctrl(&env->simple, 1), sc_remote, timeslice, timeslice, 0, 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + create_helper_thread(env, &helper); + + /* unbind the default SC; + make it so that there is no SC. */ + error = api_sc_unbind(helper.thread.sched_context.cptr); + test_eq(error, seL4_NoError); + + /* ========== resume then bind ================ */ + start_helper(env, &helper, (helper_fn_t) sched_context_smp_002_helper_fn, 0, 0, 0, 0); + + error = api_sc_bind(sc_remote, helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + error = wait_for_helper(&helper); + test_eq(error, 1); + + /* cleanup */ + cleanup_helper(env, &helper); + + /* ============ bind then resume =============== */ + create_helper_thread(env, &helper); + + /* unbind the default SC; + make it so that there is no SC. */ + error = api_sc_unbind(helper.thread.sched_context.cptr); + test_eq(error, seL4_NoError); + + error = api_sc_bind(sc_remote, helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + start_helper(env, &helper, (helper_fn_t) sched_context_smp_002_helper_fn, 0, 0, 0, 0); + + error = seL4_TCB_Resume(helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + error = wait_for_helper(&helper); + test_eq(error, 1); + + /* cleanup */ + cleanup_helper(env, &helper); + + /* ============ lazy unbind =================== */ + create_helper_thread(env, &helper); + + /* unbind the default SC */ + error = api_sc_unbind(helper.thread.sched_context.cptr); + test_eq(error, seL4_NoError); + /* add back our desired SC */ + error = api_sc_bind(sc_remote, helper.thread.tcb.cptr); + test_eq(error, seL4_NoError); + + error = api_sc_bind(sc_remote, notification); + test_eq(error, seL4_NoError); + /* set helper to higher prio to make behaviour deterministic */ + set_helper_priority(env, &helper, env->priority + 1); + + /* double check that the tcb is still bound */ + error = api_sc_bind(sc_remote, helper.thread.tcb.cptr); + test_eq(error, seL4_IllegalOperation); + + /* To be able to reproduce the test in SCHED_CONTEXT_007 where we rebind the + * TCB SC to test that it has been returned away, it is difficult to do + * deterministically. The best solution I can think of is to start a higher + * priority thread on this core that waits on an endpoint; this test thread + * should then only continue once that is blocked. Then the other core thread + * can perform an NBSendWait which should result in returning the TCB SC. + * This will then allow us to in this thread wait for the higher priority + * local thread to signal us, and so we can guarantee at this point the + * remote thread will be blocked on Wait. + */ + + helper_thread_t hi_helper; + create_helper_thread(env, &hi_helper); + set_helper_priority(env, &hi_helper, env->priority + 1); + seL4_CPtr endpoint_cross = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr endpoint_hi_low = vka_alloc_endpoint_leaky(&env->vka); + /* this will run and receive first */ + start_helper(env, &hi_helper, (helper_fn_t)sched_context_smp_002_high_priority_helper, endpoint_cross, endpoint_hi_low, 0, 0); + + start_helper(env, &helper, (helper_fn_t)sched_context_smp_002_lazy_fn, endpoint_cross, notification, 0, 0); + + seL4_Wait(endpoint_hi_low, NULL); + + /* the tcb should have been unbound lazily when the helper called seL4_Wait */ + error = api_sc_bind(get_helper_sched_context(&helper), get_helper_tcb(&helper)); + test_eq(error, seL4_NoError); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_002, "test resuming a passive thread and binding scheduling context on another core", + test_passive_thread_start_smp, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +/* + * Once https://github.com/seL4/seL4/issues/1617 is fixed, an equivalent of + * BIND0007 should be added here as SCHED_CONTEXT_SMP_003. + */ + +void sched_context_smp_004_helper_fn(env_t env, seL4_CPtr ep) +{ + /* Make sure this has run for at least MIN_BUDGET_US by poll-waiting that long */ + uint64_t start_ns = sel4test_timestamp(env); + uint64_t end_ns = start_ns + MIN_BUDGET_US * NS_IN_US; + while (sel4test_timestamp(env) < end_ns) { + for (int i = 0; i < 1000; i++) { + asm volatile("nop" ::: "memory"); + } + } + + seL4_Send(ep, seL4_MessageInfo_new(0, 0, 0, 0)); +} + +int test_update_remote_sc_with_budget(env_t env) +{ + + /** + * Idea behind this: a remote thread might have run out of a budget. + * If we make a remote thread that has a tiny budget (the minimum) + * but a very long period (the maximum) then once it will runout it will + * basically never refill: so, if we reconfigure the SC to have full + * bandwidth (budget = period) then it should start running again. + **/ + + int error; + helper_thread_t helper; + seL4_CPtr ep_unbadged = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr ep = badge_endpoint(env, 0x100, ep_unbadged); + + create_helper_thread(env, &helper); + + /* make the helper run on the remote core with no budget */ + error = seL4_SchedControl_Configure( + /* schedcontrol */ simple_get_sched_ctrl(&env->simple, 1), + /* schedcontext */ get_helper_sched_context(&helper), + /* budget */ MIN_BUDGET_US, /* period */ MAX_PERIOD_US, /* refills */ 0, /* badge */ 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + start_helper(env, &helper, (helper_fn_t)sched_context_smp_004_helper_fn, (seL4_Word)env, ep, 0, 0); + + /* Mostly a hack: wait at least twice MIN_BUDGET_US */ + sel4test_sleep(env, 2 * MIN_BUDGET_US * NS_IN_US); + + seL4_Word sender_badge; + seL4_NBWait(ep, &sender_badge); + /* if the badge is zero then NBWait failed (see doNBRecvFailedTransfer) + * and so it must haven't run up to seL4_Send. but we've waited at least + * 2x as long as we needed for this remote helper to to have run if it + * did have the budget. basically this is testing that the remote thread got + * blocked due to not having enough budget. */ + test_eq(sender_badge, 0); + + /* give the helper on the remote core infinite budget, so it should run again */ + error = seL4_SchedControl_Configure( + /* schedcontrol */ simple_get_sched_ctrl(&env->simple, 1), + /* schedcontext */ get_helper_sched_context(&helper), + /* budget */ MAX_PERIOD_US, /* period */ MAX_PERIOD_US, /* refills */ 0, /* badge */ 0); + + seL4_Wait(ep, NULL); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_004, "update a remote task's SC to have budget to run immediately.", + test_update_remote_sc_with_budget, config_set(CONFIG_HAVE_TIMER) && config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +void sched_context_smp_005_helper_fn(volatile int *state, seL4_CPtr ntfn, seL4_CPtr ep) +{ + *state = 1; + /* wait on ntfn */ + seL4_Wait(ntfn, NULL); + *state = 2; + + /* wait on ep */ + seL4_Wait(ep, NULL); + *state = 3; + + /* perform a send so we generate a reply that server can use to wake us up */ + seL4_Call(ep, seL4_MessageInfo_new(0, 0, 0, 0)); + *state = 4; +} + +void wait_eq_timeout(volatile int *state, int value, int timeout) +{ + int count = 0; + while (true) { + if (*state == value) { + return; + } else if (count >= timeout) { + /* register the failure */ + test_lt(count, timeout); + return; + } + + count++; + } +} + +int test_blocking_remote_task_activation(env_t env) +{ + seL4_CPtr ep = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr ntfn = vka_alloc_notification_leaky(&env->vka); + seL4_CPtr reply = vka_alloc_reply_leaky(&env->vka); + + /* fine to use this cross core because of single-copy atomicity on an aligned (32-bit) variable */ + volatile int state = 0; + + /* make remote helper */ + helper_thread_t helper; + create_helper_thread(env, &helper); + set_helper_affinity(env, &helper, 1); + start_helper(env, &helper, (helper_fn_t)sched_context_smp_005_helper_fn, + (seL4_Word)&state, ntfn, ep, 0); + + /* wait like a whole second (at 1GHz) to start */ + wait_eq_timeout(&state, 1, 1 * NS_IN_S); + + /* test notification waking blocked remote */ + seL4_Signal(ntfn); + /* wait a bit for the next state */ + wait_eq_timeout(&state, 2, 1 * US_IN_S); + + /* test endpoint waking blocked remote */ + seL4_Send(ep, seL4_MessageInfo_new(0, 0, 0, 0)); + /* wait a bit for the next state */ + wait_eq_timeout(&state, 3, 1 * US_IN_S); + + (void)seL4_Recv(ep, NULL, reply); + + /* test reply waiting blocked remote */ + seL4_Send(reply, seL4_MessageInfo_new(0, 0, 0, 0)); + wait_eq_timeout(&state, 4, 1 * US_IN_S); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_005, "test activation of blocking remote tasks (call/reply/signal)", + test_blocking_remote_task_activation, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +#define SC_SMP_006_EP 1 +#define SC_SMP_006_NTFN 2 + +int sched_context_smp_006_helper_fn(seL4_CPtr ep, seL4_CPtr reply, seL4_CPtr ntfn, seL4_CPtr tcb) +{ + seL4_Word sender; + + /* This ReplyRecv will cause passive server-ification */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + tag = seL4_ReplyRecv(ep, tag, &sender, reply); + test_eq(sender, SC_SMP_006_EP); + + test_eq(0, seL4_DebugGetThreadAffinity(tcb)); + + /* Now running on the SC of our `seL4_Call`er we signal our bound ntfn before + * ReplyRecv'ing */ + seL4_Signal(ntfn); + + /* After this we will be running on the notification SC on a different core */ + tag = seL4_ReplyRecv(ep, tag, &sender, reply); + test_eq(sender, SC_SMP_006_NTFN); + + test_eq(1, seL4_DebugGetThreadAffinity(tcb)); + + return sel4test_get_result(); +} + +int test_passive_remote_task_signal_migration_bound(env_t env) +{ + int error; + seL4_CPtr unbadged_ep = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr unbadged_ntfn = vka_alloc_notification_leaky(&env->vka); + seL4_CPtr reply = vka_alloc_reply_leaky(&env->vka); + /* badge_endpoint does work for a ntfn too */ + seL4_CPtr ntfn = badge_endpoint(env, SC_SMP_006_NTFN, unbadged_ntfn); + seL4_CPtr ep = badge_endpoint(env, SC_SMP_006_EP, unbadged_ep); + + /* make helper */ + helper_thread_t helper; + create_helper_thread(env, &helper); + + /* make the helper (and the bound ntfn sc) run on the second core */ + set_helper_affinity(env, &helper, 1); + + /* setup a bound notification */ + error = seL4_TCB_BindNotification(get_helper_tcb(&helper), ntfn); + test_eq(error, seL4_NoError); + + /* do lazy rebind for passive */ + error = api_sc_bind(get_helper_sched_context(&helper), ntfn); + test_eq(error, seL4_NoError); + + /* set helper to higher prio so it runs until blocked above us. */ + set_helper_priority(env, &helper, env->priority + 1); + + start_helper(env, &helper, (helper_fn_t)sched_context_smp_006_helper_fn, + ep, reply, ntfn, get_helper_tcb(&helper)); + + /* Make it run on our core and then continue with migration via signal on self */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + tag = seL4_Call(ep, tag); + + error = wait_for_helper(&helper); + test_eq(error, seL4_NoError); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_006, "signal on bound notification causing core migration passive task (current task)", + test_passive_remote_task_signal_migration_bound, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +int sched_context_smp_007_helper_fn(seL4_CPtr ep, seL4_CPtr reply, seL4_CPtr ntfn, seL4_CPtr tcb) +{ + seL4_Word sender; + + /* we start running on the SC of the notification, i.e. core 1 */ + test_eq(1, seL4_DebugGetThreadAffinity(tcb)); + + /* This ReplyRecv will cause passive server-ification */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + tag = seL4_ReplyRecv(ep, tag, &sender, reply); + test_eq(sender, SC_SMP_006_EP); + + test_eq(0, seL4_DebugGetThreadAffinity(tcb)); + + seL4_Signal(ntfn); + + /* reply (to donate away our SC) and then wait on our notification */ + tag = seL4_NBSendWait(reply, tag, ntfn, NULL); + + test_eq(1, seL4_DebugGetThreadAffinity(tcb)); + + return sel4test_get_result(); +} + +int test_passive_remote_task_signal_migration_wait(env_t env) +{ + int error; + seL4_CPtr unbadged_ep = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr unbadged_ntfn = vka_alloc_notification_leaky(&env->vka); + seL4_CPtr reply = vka_alloc_reply_leaky(&env->vka); + /* badge_endpoint does work for a ntfn too */ + seL4_CPtr ntfn = badge_endpoint(env, SC_SMP_006_NTFN, unbadged_ntfn); + seL4_CPtr ep = badge_endpoint(env, SC_SMP_006_EP, unbadged_ep); + + /* make helper */ + helper_thread_t helper; + create_helper_thread(env, &helper); + + /* make the helper (and the bound ntfn sc) run on the second core */ + set_helper_affinity(env, &helper, 1); + + /* setup a bound notification */ + error = seL4_TCB_BindNotification(get_helper_tcb(&helper), ntfn); + test_eq(error, seL4_NoError); + + /* do lazy rebind for passive */ + error = api_sc_bind(get_helper_sched_context(&helper), ntfn); + test_eq(error, seL4_NoError); + + /* set helper to higher prio so it runs until blocked above us. */ + set_helper_priority(env, &helper, env->priority + 1); + + start_helper(env, &helper, (helper_fn_t)sched_context_smp_007_helper_fn, + ep, reply, ntfn, get_helper_tcb(&helper)); + + /* Make it run on our core and then continue with migration via signal on self */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + tag = seL4_Call(ep, tag); + + error = wait_for_helper(&helper); + test_eq(error, seL4_NoError); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_007, "signal causing (seL4_Wait) core migration current passive task", + test_passive_remote_task_signal_migration_wait, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +int sched_context_smp_008_helper_fn(seL4_CPtr ep, seL4_CPtr reply, seL4_CPtr ntfn_to_helper, seL4_CPtr tcb) +{ + seL4_Word sender; + + /* Tell helper 2 we're ready and also do passive server-ification */ + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + tag = seL4_NBSendRecv(ntfn_to_helper, tag, ep, NULL, reply); + + /* now running on core 2 (helper 2's core) */ + test_eq(2, seL4_DebugGetThreadAffinity(tcb)); + + /* respond to helper 2 and wait for the notification from core 0 */ + tag = seL4_ReplyRecv(ep, tag, &sender, reply); + test_eq(sender, SC_SMP_006_NTFN); + + test_eq(1, seL4_DebugGetThreadAffinity(tcb)); + + return sel4test_get_result(); +} + +int sched_context_smp_008_core2_helper_fn(seL4_CPtr ep, seL4_CPtr ntfn_from_helper) +{ + /* wait for the other helper to be ready - so that when it does the + * passive server Recv() it goes into a blocked state, instead of immediately + * progressing as this helper already made the ep into a send state */ + seL4_Wait(ntfn_from_helper, NULL); + + seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0); + /* Call helper 1 and make it migrate to core 2 */ + (void)seL4_Call(ep, tag); + /* die */ + return 0; +} + +int test_passive_remote_task_migration(env_t env) +{ + int error; + seL4_CPtr unbadged_ep = vka_alloc_endpoint_leaky(&env->vka); + seL4_CPtr unbadged_ntfn = vka_alloc_notification_leaky(&env->vka); + seL4_CPtr reply = vka_alloc_reply_leaky(&env->vka); + /* badge_endpoint does work for a ntfn too */ + seL4_CPtr ntfn = badge_endpoint(env, SC_SMP_006_NTFN, unbadged_ntfn); + seL4_CPtr ep = badge_endpoint(env, SC_SMP_006_EP, unbadged_ep); + seL4_CPtr ntfn_between_helpers = vka_alloc_notification_leaky(&env->vka); + + /* make helper */ + helper_thread_t helper_1, helper_2; + create_helper_thread(env, &helper_1); + create_helper_thread(env, &helper_2); + + /* make the bound ntfn sc (and initial server setup) run on the second core */ + set_helper_affinity(env, &helper_1, 1); + set_helper_affinity(env, &helper_2, 2); + + /* setup a bound notification */ + error = seL4_TCB_BindNotification(get_helper_tcb(&helper_1), ntfn); + test_eq(error, seL4_NoError); + + /* do lazy rebind for passive */ + error = api_sc_bind(get_helper_sched_context(&helper_1), ntfn); + test_eq(error, seL4_NoError); + + /** + * Situation at start: + * + * Core 0: Core 1: Core 2 + * test program helper 1 (blocked on signal) helper 2 (that calls 1) + * + * Helper 1/2 run; helper 2 blocks waiting for helper 1 to NBSendRecv. + * This means helper 2 passive-server-ifies before helper 2 runs. + * Helper 2 performs seL4_Call(helper 1). This causes helper 1 to run + * on core 2. Then helper 1 rplies to helper 2, which tells core 0 to signal + * helper 1 and cause a migration to core 1. + * + **/ + + start_helper(env, &helper_1, (helper_fn_t)sched_context_smp_008_helper_fn, + ep, reply, ntfn_between_helpers, get_helper_tcb(&helper_1)); + start_helper(env, &helper_2, (helper_fn_t)sched_context_smp_008_core2_helper_fn, + ep, ntfn_between_helpers, 0, 0); + + /* wait for helper 2 to start and die */ + error = wait_for_helper(&helper_2); + test_eq(error, seL4_NoError); + + seL4_Signal(ntfn); + + /* wait for helper to die */ + error = wait_for_helper(&helper_1); + test_eq(error, seL4_NoError); + + cleanup_helper(env, &helper_1); + cleanup_helper(env, &helper_2); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_008, "signal on bound notification remote task causing core migration (two remote cores)", + test_passive_remote_task_migration, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 2)); + +void sc_smp_009_helper_fn(seL4_CPtr ntfn) +{ + /* tell remote we are started now */ + seL4_Signal(ntfn); + + /* Infinitely loop taking up budget */ + while (true) { + asm volatile("nop" ::: "memory"); + }; +} + +int test_update_remote_sc_with_no_budget(env_t env) +{ + + /** + * Idea behind this: a remote thread might have run out of a budget. + * If we make a remote thread that has a tiny budget (the minimum) + * but a very long period (the maximum) then once it will runout it will + * basically never refill: so, if we reconfigure the SC to have full + * bandwidth (budget = period) then it should start running again. + **/ + + int error; + helper_thread_t helper; + seL4_CPtr ntfn; + seL4_CPtr timeout_ep; + seL4_CPtr timeout_reply; + seL4_MessageInfo_t tag; + + ntfn = vka_alloc_notification_leaky(&env->vka); + timeout_ep = vka_alloc_endpoint_leaky(&env->vka); + timeout_reply = vka_alloc_reply_leaky(&env->vka); + + create_helper_thread(env, &helper); + + /* give the helper infinite budget */ + error = seL4_SchedControl_Configure( + /* schedcontrol */ simple_get_sched_ctrl(&env->simple, 1), + /* schedcontext */ get_helper_sched_context(&helper), + /* budget */ MAX_PERIOD_US, /* period */ MAX_PERIOD_US, /* refills */ 0, /* badge */ 0); + ZF_LOGF_IF(error, "should be able to configure SC"); + + /* configure a timeout EP so we know when it's budget expire s*/ + set_helper_tfep(env, &helper, timeout_ep); + + start_helper(env, &helper, (helper_fn_t)sc_smp_009_helper_fn, ntfn, 0, 0, 0); + + /* wait for it to tell us it is ready */ + seL4_Wait(ntfn, NULL); + + /* remove the remote helper's budget. it should stop counting immediately. + specifically: since it has infinite budget if we don't make it stop rather + quickly then it means it kept going. the idea is that if we don't stall + the remote TCB it will continue to run and never need to drop back into + the kernel as no timer interrupts are scheduled for over an hour. */ + error = seL4_SchedControl_Configure( + /* schedcontrol */ simple_get_sched_ctrl(&env->simple, 1), + /* schedcontext */ get_helper_sched_context(&helper), + /* budget */ MIN_BUDGET_US, /* period */ MAX_PERIOD_US, /* refills */ 0, /* badge */ 0); + test_error_eq(error, seL4_NoError); + + tag = seL4_Recv(timeout_ep, NULL, timeout_reply); + seL4_Fault_t fault = seL4_getFault(tag); + test_eq(seL4_Fault_get_seL4_FaultType(fault), seL4_Fault_Timeout); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED_CONTEXT_SMP_009, "update a remote task's SC to remove budget to run immediately.", + test_update_remote_sc_with_no_budget, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); + +#endif /* CONFIG_KERNEL_MCS */ diff --git a/apps/sel4test-tests/src/tests/scheduler.c b/apps/sel4test-tests/src/tests/scheduler.c index 600842ac..168f9aed 100644 --- a/apps/sel4test-tests/src/tests/scheduler.c +++ b/apps/sel4test-tests/src/tests/scheduler.c @@ -1648,23 +1648,14 @@ int sched0022_to_fn(struct env *env, helper_thread_t *thread, seL4_CPtr ep) seL4_MessageInfo_t tag = {0}; seL4_MessageInfo_ptr_set_length(&tag, 2); - /* change to core 1 */ - seL4_Error error = api_sched_ctrl_configure(simple_get_sched_ctrl(&env->simple, 1), - thread->thread.sched_context.cptr, - 10000, - 10000, - 0, - 0); + /* change our affinity to core 1 then back to core 0 and report errors */ + + int error = set_helper_affinity_fallible(env, thread, /* core */ 1); seL4_SetMR(0, error); - /* and back to core 0 */ - error = api_sched_ctrl_configure(simple_get_sched_ctrl(&env->simple, 0), - thread->thread.sched_context.cptr, - 10000, - 10000, - 0, - 0); + error = set_helper_affinity_fallible(env, thread, /* core */ 0); seL4_SetMR(1, error); + seL4_Send(ep, tag); return 0; } @@ -1672,7 +1663,7 @@ int sched0022_to_fn(struct env *env, helper_thread_t *thread, seL4_CPtr ep) /* Test that a helper thread can move itself back from another core. * Save the return values and check them in test thread. */ -static int test_changing_affinity(struct env *env) +static int test_changing_affinity_self(struct env *env) { int error; helper_thread_t t0; @@ -1699,5 +1690,226 @@ static int test_changing_affinity(struct env *env) return sel4test_get_result(); } -DEFINE_TEST(SCHED0022, "test changing a helper threads core", test_changing_affinity, - (config_set(CONFIG_KERNEL_MCS) &&(CONFIG_MAX_NUM_NODES > 1))); +DEFINE_TEST(SCHED0022, "test helper thread changing its own core", test_changing_affinity_self, + (CONFIG_MAX_NUM_NODES > 1)); + +void sched_0023_helper_high_fn(void) +{ + /* Infinitely loop taking up budget */ + while (true) { + asm volatile("nop" ::: "memory"); + } +} + +void sched_0023_helper_low_fn(volatile int *state, seL4_CPtr ntfn) +{ + *state = 2; + seL4_Signal(ntfn); +} + +static int test_set_higher_prio_remote(struct env *env) +{ + helper_thread_t high_thread, low_thread; + seL4_CPtr ntfn = vka_alloc_notification_leaky(&env->vka); + + create_helper_thread(env, &high_thread); + create_helper_thread(env, &low_thread); + + assert(CONFIG_NUM_PRIORITIES > 3); + + set_helper_priority(env, &high_thread, 2); + set_helper_priority(env, &low_thread, 1); + + set_helper_affinity(env, &high_thread, 1); + set_helper_affinity(env, &low_thread, 1); + + /* fine to use this cross core because of single-copy atomicity on an aligned (32-bit) variable */ + volatile int state = 0; + + /* start our two remote helpers */ + start_helper(env, &high_thread, (helper_fn_t) sched_0023_helper_high_fn, 0, 0, 0, 0); + start_helper(env, &low_thread, (helper_fn_t) sched_0023_helper_low_fn, (seL4_Word)&state, ntfn, 0, 0); + + /* check that the low function hasn't run and update the state */ + test_eq(state, 0); + + /* raise the helper priority of low above high */ + set_helper_priority(env, &low_thread, 3); + + /* helper should run and set state to 2 */ + seL4_Wait(ntfn, NULL); + test_eq(state, 2); + + /* ==== part 2: lowering instead of raising */ + + /* reset the priorities and state */ + state = 0; + set_helper_priority(env, &high_thread, 3); + set_helper_priority(env, &low_thread, 2); + + /* restart the low function */ + start_helper(env, &low_thread, (helper_fn_t) sched_0023_helper_low_fn, (seL4_Word)&state, ntfn, 0, 0); + + /* check that the low function hasn't run and update the state */ + test_eq(state, 0); + + /* lower the prio of high below low*/ + set_helper_priority(env, &high_thread, 1); + + /* helper should run and set state to 2 */ + seL4_Wait(ntfn, NULL); + test_eq(state, 2); + + cleanup_helper(env, &high_thread); + cleanup_helper(env, &low_thread); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED0023, "test set prio to higher/lower on remote core", + test_set_higher_prio, (CONFIG_MAX_NUM_NODES > 1)); + +void sched_0024_helper_fn(volatile int *state, seL4_CPtr ntfn) +{ + /* tell the test that we're ready (non-blocking as cross-core) */ + seL4_Signal(ntfn); + + /* start spinning so we can be suspend+resume somewhere OK */ + while (*state != 1); + + /* tell the test that the resume worked */ + *state = 2; + seL4_Signal(ntfn); +} + +static int test_resume_suspended_remote_task(struct env *env) +{ + helper_thread_t thread; + int error; + seL4_CPtr ntfn = vka_alloc_notification_leaky(&env->vka); + + create_helper_thread(env, &thread); + set_helper_affinity(env, &thread, 1); + + /* fine to use this cross core because of single-copy atomicity on an aligned (32-bit) variable */ + volatile int state = 0; + + /* start our remote helper */ + start_helper(env, &thread, (helper_fn_t) sched_0024_helper_fn, (seL4_Word)&state, ntfn, 0, 0); + + /* wait for remote helper to tell us it is ready - it should then be spinning */ + seL4_Wait(ntfn, NULL); + + error = seL4_TCB_Suspend(get_helper_tcb(&thread)); + test_error_eq(error, seL4_NoError); + + /* resume the remote thread now */ + state = 1; + error = seL4_TCB_Resume(get_helper_tcb(&thread)); + test_error_eq(error, seL4_NoError); + + /* helper should run and set state to 2 */ + seL4_Wait(ntfn, NULL); + test_eq(state, 2); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED0024, "test resuming a suspended remote task", + test_resume_suspended_remote_task, (CONFIG_MAX_NUM_NODES > 1)); + +#ifdef CONFIG_KERNEL_MCS +int test_yieldTo_remote(env_t env) +{ + int error; + helper_thread_t to, from; + volatile seL4_SchedContext_YieldTo_t ret; + + create_helper_thread(env, &to); + create_helper_thread(env, &from); + + start_helper(env, &to, (helper_fn_t) sched0018_to_fn, 0, 0, 0, 0); + start_helper(env, &from, (helper_fn_t) sched0017_helper_fn, to.thread.sched_context.cptr, (seL4_Word) &ret, 0, 0); + + /* To on a different core than From */ + set_helper_affinity(env, &to, 1); + set_helper_affinity(env, &from, 0); + + set_helper_mcp(env, &to, seL4_MaxPrio); + set_helper_mcp(env, &from, seL4_MaxPrio); + error = set_helper_sched_params(env, &to, 500 * US_IN_MS, 500 * US_IN_MS, 0); + test_eq(error, seL4_NoError); + error = set_helper_sched_params(env, &from, 500 * US_IN_MS, 500 * US_IN_MS, 0); + test_eq(error, seL4_NoError); + + ZF_LOGD("Wait for from\n"); + wait_for_helper(&from); + test_eq(ret.error, seL4_NoError); + test_geq(ret.consumed, 0llu); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED0025, "Test seL4_SchedContext_YieldTo remote", + test_yieldTo_remote, config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 1)); +#endif /* CONFIG_KERNEL_MCS */ + +void sched_0026_helper_fn(volatile uint64_t *counter, seL4_CPtr ntfn) +{ + /* tell server we started */ + seL4_Signal(ntfn); + + /* Infinitely loop taking up budget */ + while (true) { + *counter += 1; + } +} + +/* Test moving a helper thread between remote cores */ +static int test_changing_affinity_remote(struct env *env) +{ + int error; + helper_thread_t helper; + seL4_CPtr ntfn; + volatile uint64_t counter = 0; + + ntfn = vka_alloc_notification_leaky(&env->vka); + create_helper_thread(env, &helper); + + /* make the budget/period such that only 1 timer interrupt per hour is required, + * so that the helper should never enter the kernel unnecessarily + * as we don't need round robin. */ + error = set_helper_sched_params(env, &helper, 3600 * US_IN_S, 3600 * US_IN_S, 0); + test_eq(error, seL4_NoError); + + /* start on core 1 */ + set_helper_affinity(env, &helper, 1); + + start_helper(env, &helper, (helper_fn_t)sched_0026_helper_fn, (seL4_Word)&counter, ntfn, 0, 0); + + /* wait for it to start helper */ + seL4_Wait(ntfn, NULL); + + for (volatile int i = 0; i < 100000; i++) { } + + uint64_t local_count_0 = counter; + /* it should have run some amount */ + test_gt(local_count_0, 0); + + /* move remote task to another remote core */ + set_helper_affinity(env, &helper, 2); + + uint64_t local_count_1 = counter; + /* should have incremented some more */ + test_gt(local_count_1, local_count_0); + + for (volatile int i = 0; i < 100000; i++) { } + + /* it should still be incrementing */ + uint64_t local_count_2 = counter; + /* should have incremented some more */ + test_gt(local_count_2, local_count_1); + + cleanup_helper(env, &helper); + + return sel4test_get_result(); +} +DEFINE_TEST(SCHED0026, "test migrating remote tasks", test_changing_affinity_remote, + config_set(CONFIG_KERNEL_MCS) && (CONFIG_MAX_NUM_NODES > 2));