rt 0.20.1

A real-time operating system capable of full preemption
Documentation
#include <rt/arch/csr.h>
#include <rt/arch/mix.h>
#include <rt/arch/mpu.h>
#include <rt/arch/mstatus.h>
#include <rt/arch/semihosting.h>
#include <rt/arch/trap.h>

#include <rt/log.h>
#include <rt/panic.h>
#include <rt/tick.h>
#include <rt/trace.h>
#include <rt/trap.h>

#include "mtime.h"

#include <stdint.h>

__attribute__((used)) RT_STACK(stack, 1024);

// Timer frequency is 10 MHz on QEMU virt.
#define TIMER_FREQ 10000000UL
#define TIMER_INCREMENT (TIMER_FREQ / 1000)

__attribute__((noreturn)) void rt_panic(const char *msg)
{
    semihosting_write0(msg);
    semihosting_write0("\n");
    semihosting_exception(ADP_STOPPED_OS_SPECIFIC);
}

__attribute__((noreturn)) void rt_trap(void)
{
    semihosting_exit_success();
}

void default_trap_handler(void);
RT_TRAP_HANDLER(default_trap_handler)
{
    semihosting_write0("unhandled trap\n");
    semihosting_exception(ADP_STOPPED_RUNTIME_ERROR_UNKNOWN);
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverride-init"
void (*const rt_trap_vector[])(void) = {
    [0 ... 31] = default_trap_handler,
    [RT_TRAP_ECALL_U] = rt_ecall_handler,
    [RT_TRAP_ECALL_S] = rt_ecall_handler,
    [RT_TRAP_ECALL_M] = rt_ecall_handler,
};
#pragma GCC diagnostic pop

void mtime_handler(void);
RT_INTERRUPT_HANDLER(mtime_handler)
{
    rt_trace_interrupt_start(MIx_CAUSE_MTI);
    /* Save mepc and mie before re-enabling interrupts. mepc must be saved
     * because the hardware overwrites it on any trap, and we re-enable MIE
     * below to allow nesting. Save mie and disable MSI+MTI so the syscall
     * handler can run with interrupts re-enabled without being preempted by
     * these. We must restore the original mie value rather than unconditionally
     * re-enabling MSI, because the ecall handler intentionally disables MSI
     * during context switches. Re-enabling it here would allow MSI to fire on
     * the kernel stack, corrupting the saved volatile context. */
    uintptr_t saved_mepc = csrr(CSR_MEPC);
    uintptr_t saved_mie = csrrc(CSR_MIE, MIx_MSI | MIx_MTI);
    uintptr_t saved_mstatus = csrrs(CSR_MSTATUS, MSTATUS_MIE);
    mtimecmp_set(mtimecmp() + TIMER_INCREMENT);
    rt_tick_advance();
    /* Restore mstatus (including MIE=0), mepc, and mie. The mret in the
     * RT_INTERRUPT_HANDLER wrapper will restore MIE from MPIE. */
    csrw(CSR_MSTATUS, saved_mstatus);
    csrw(CSR_MEPC, saved_mepc);
    csrw(CSR_MIE, saved_mie);
    rt_trace_interrupt_end(MIx_CAUSE_MTI);
}

// Full unrolling is required so each region id is a constant.
#if __has_attribute(optimize)
__attribute__((optimize("O3")))
#endif
__attribute__((noinline)) static void lock_static_regions(void)
{
    for (uint32_t i = RT_MPU_TASK_REGION_START_ID + RT_MPU_NUM_TASK_REGIONS;
         i < RT_MPU_NUM_REGIONS; ++i)
    {
        rt_mpu_region_set(i, 0UL, 0UL, RT_MPU_ATTR_NO_ACCESS_LOCKED);
    }
}

void init(void);
void init(void)
{
    const uint32_t static_region_start =
        RT_MPU_TASK_REGION_START_ID + RT_MPU_NUM_TASK_REGIONS;

    /* To transition to MML while preserving execute permission, we first need
     * to enable RLB, which allows reconfiguration of a locked region. This is
     * necessary to reconfigure the x region to the MML shared execute-only
     * attribute, whose encoding is L|W, which is only legal with MML enabled.
     * Therefore we must first configure the x region with a different attribute
     * that provides execute under MML=0 and MML=1, the only options of which
     * are L|X and L|R|X. We choose the former, which corresponds to M_X_LOCKED
     * with MML=1. */
    csrs(CSR_MSECCFG, MSECCFG_RLB);

    // Lock all static entries so they can't be used once RLB is cleared.
    lock_static_regions();

    extern const uint32_t __x_region__[];
    extern const uint32_t __x_region_end__[];
    rt_mpu_region_set(static_region_start + 0, (uintptr_t)__x_region__,
                      (uintptr_t)__x_region_end__ - (uintptr_t)__x_region__,
                      RT_MPU_ATTR_M_X_LOCKED | RT_MPU_ATTR_NAPOT);

    csrs(CSR_MSECCFG, MSECCFG_MML);

    rt_mpu_region_set_attr(static_region_start + 0,
                           RT_MPU_ATTR_X_LOCKED | RT_MPU_ATTR_NAPOT);

    extern const uint32_t __ro_region__[];
    extern const uint32_t __ro_region_end__[];
    rt_mpu_region_set(static_region_start + 1, (uintptr_t)__ro_region__,
                      (uintptr_t)__ro_region_end__ - (uintptr_t)__ro_region__,
                      RT_MPU_ATTR_RO_LOCKED | RT_MPU_ATTR_NAPOT);

    extern const uint32_t __rw_region__[];
    extern const uint32_t __rw_region_end__[];
    rt_mpu_region_set(static_region_start + 2, (uintptr_t)__rw_region__,
                      (uintptr_t)__rw_region_end__ - (uintptr_t)__rw_region__,
                      RT_MPU_ATTR_RW | RT_MPU_ATTR_NAPOT);

#if !RT_MPU_TASK_REGIONS_ENABLE
    extern const uint32_t __task_stack_region__[];
    extern const uint32_t __task_stack_region_end__[];
    rt_mpu_region_set(static_region_start + 3, (uintptr_t)__task_stack_region__,
                      (uintptr_t)__task_stack_region_end__ -
                          (uintptr_t)__task_stack_region__,
                      RT_MPU_ATTR_STACK);
#endif

    extern const uint32_t __priv_rw_region__[];
    extern const uint32_t __priv_rw_region_end__[];
    rt_mpu_region_set(static_region_start + 4, (uintptr_t)__priv_rw_region__,
                      (uintptr_t)__priv_rw_region_end__ -
                          (uintptr_t)__priv_rw_region__,
                      RT_MPU_ATTR_M_RW_LOCKED | RT_MPU_ATTR_NAPOT);

    extern const uint32_t __priv_ro_region__[];
    extern const uint32_t __priv_ro_region_end__[];
    rt_mpu_region_set(static_region_start + 5, (uintptr_t)__priv_ro_region__,
                      (uintptr_t)__priv_ro_region_end__ -
                          (uintptr_t)__priv_ro_region__,
                      RT_MPU_ATTR_M_RO_LOCKED | RT_MPU_ATTR_NAPOT);

    rt_mpu_region_set(static_region_start + 6, CLINT_BASE, CLINT_SIZE,
                      RT_MPU_ATTR_M_RW_LOCKED | RT_MPU_ATTR_NAPOT);

    // Drop RLB and enable the machine mode whitelist policy.
    csrw(CSR_MSECCFG, MSECCFG_MML | MSECCFG_MMWP);
    __asm__("fence" ::: "memory");

    // Reset mtime and set the first tick to occur at TIMER_INCREMENT.
    mtime_set(0);
    mtimecmp_set(TIMER_INCREMENT);

    // Enable machine software and timer interrupts.
    csrs(CSR_MIE, MIx_MSI | MIx_MTI);
}

#if RT_LOG_ENABLE
void rt_logf(const char *format, ...)
{
    (void)format;
}

void rt_log_flush(void)
{
}
#endif // RT_LOG_ENABLE