blob: 91ad44648ac4b52f174c528352b35954f30e34a0 [file] [log] [blame]
#include "../timing.h"
#include <compartment.h>
#include <debug.hh>
#include <event.h>
#include <locks.hh>
#include <simulator.h>
#include <thread.h>
#include <timeout.h>
#if DEBUG_INTERRUPT_BENCH
# include <fail-simulator-on-error.h>
#endif
using Debug = ConditionalDebug<DEBUG_INTERRUPT_BENCH, "Interrupt benchmark">;
namespace
{
std::atomic<uint32_t> event;
int start;
} // namespace
/**
* N threads of equal priority will enter here with different stack sizes. They
* will all wait on a ticket lock so that only one of them runs at a time. They
* will then wait on a futex that will be set by the low-priority thread.
*
* All of these threads will be waiting for the futex.
*/
void __cheri_compartment("interrupt_bench") entry_high_priority()
{
Timeout t = {0, UnlimitedTimeout};
static TicketLock lock;
static bool headerWritten;
static _Atomic(uint8_t) threadCounter = 0;
threadCounter++;
uint16_t threadID = thread_id_get();
{
Debug::log("Thread {} entering ticket lock", threadID);
LockGuard g{lock};
Debug::log("Thread {} got ticket lock", threadID);
if (!headerWritten)
{
Debug::log("Thread {} creating event", threadID);
printf("#board\tstack size\ttotal\n");
headerWritten = true;
}
int end = CHERI::with_interrupts_disabled([&]() {
uint32_t bits = 0;
Debug::log("Thread {} releasing ticket lock", threadID);
g.unlock();
Debug::log("Thread {} waiting on event", threadID);
event.wait(0);
int time = rdcycle();
Debug::Invariant(event == 1, "Futex woke spuriously");
return time;
});
size_t stackSize = get_stack_size();
printf(__XSTRING(BOARD) "\t%d\t%d\n", stackSize, end - start);
}
// Last one out turns off the lights. This relies on all threads
// incrementing the counter before any thread reaches here. We can be sure
// of this because for any thread to reach here the low priority thread has
// to run which can only happen when all high priority threads are waiting
// and the first blocking event (LockGuard constructor) is after the counter
// increment.
if (--threadCounter == 0)
{
Debug::log("Thread {} exiting simulator", threadID);
simulation_exit(0);
}
else
{
// Other threads sleep forever. we could exit (return) instead but this
// seems to trigger a bug sometimes where the low priority thread
// doesn't wake up.
thread_sleep(&t);
}
}
/**
* This lower priority thread will run once all the higher priority threads are
* waiting (either on the ticket lock or the event). It sets the event without
* yielding, puts the starting cycle counter in a global then yields(), which
* does an 'ecall' simulating an interrupt waking the waiting thread, which
* reads the cycle counter again to calculate the interrupt latency. We repeat
* this until all the higher priority threads have run, with the last one
* calling exiting.
*/
void __cheri_compartment("interrupt_bench") entry_low_priority()
{
while (true)
{
CHERI::with_interrupts_disabled([]() {
uint32_t bits = 0;
Debug::log("Low thread setting event");
event = 1;
start = rdcycle();
event.notify_all();
});
}
}