rt 0.19.1

A real-time operating system capable of full preemption
Documentation
#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();
}

static void semihosting_write_hex(uint32_t value)
{
    static const char hex_chars[] = "0123456789abcdef";
    char buf[11];
    buf[0] = '0';
    buf[1] = 'x';
    for (int i = 7; i >= 0; --i)
    {
        buf[2 + (7 - i)] = hex_chars[(value >> (i * 4)) & 0xf];
    }
    buf[10] = '\0';
    semihosting_write0(buf);
}

void default_trap_handler(void);
RT_TRAP_HANDLER(default_trap_handler)
{
    uint32_t mcause;
    uint32_t mepc;
    uint32_t mtval;
    __asm__("csrr %0, mcause" : "=r"(mcause));
    __asm__("csrr %0, mepc" : "=r"(mepc));
    __asm__("csrr %0, mtval" : "=r"(mtval));
    semihosting_write0("unhandled trap\nmcause: ");
    semihosting_write_hex(mcause);
    semihosting_write0("\nmepc: ");
    semihosting_write_hex(mepc);
    semihosting_write0("\nmtval: ");
    semihosting_write_hex(mtval);
    semihosting_write0("\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. */
    uint32_t saved_mepc;
    uint32_t saved_mie;
    uint32_t saved_mstatus;
    __asm__ __volatile__("csrr %0, mepc" : "=r"(saved_mepc));
    __asm__ __volatile__("csrrc %0, mie, %1"
                         : "=r"(saved_mie)
                         : "r"(MIx_MSI | MIx_MTI));
    __asm__ __volatile__("csrrs %0, mstatus, %1"
                         : "=r"(saved_mstatus)
                         : "i"(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. */
    __asm__("csrw mstatus, %0" : : "r"(saved_mstatus));
    __asm__("csrw mepc, %0" : : "r"(saved_mepc));
    __asm__("csrw mie, %0" : : "r"(saved_mie));
    rt_trace_interrupt_end(MIx_CAUSE_MTI);
}

void init(void);
void init(void)
{
    /* Set up PMP regions. Per-task regions (0 through
     * RT_MPU_NUM_TASK_REGIONS-1) are configured by the context switch code.
     * Static regions start at RT_MPU_NUM_TASK_REGIONS. No L (lock) flag —
     * M-mode bypasses PMP without L, U-mode is restricted to these regions. */
    const uint32_t static_region_start =
        RT_MPU_TASK_REGION_START_ID + RT_MPU_NUM_TASK_REGIONS;

    extern const uint32_t __rx_region__[];
    extern const uint32_t __rx_region_size__;
    rt_mpu_region_set(static_region_start + 0, (uintptr_t)__rx_region__,
                      (size_t)&__rx_region_size__,
                      RT_MPU_PMPCFG_R | RT_MPU_PMPCFG_X | RT_MPU_PMPCFG_NAPOT);

    extern const uint32_t __ro_region__[];
    extern const uint32_t __ro_region_size__;
    rt_mpu_region_set(static_region_start + 1, (uintptr_t)__ro_region__,
                      (size_t)&__ro_region_size__,
                      RT_MPU_PMPCFG_R | RT_MPU_PMPCFG_NAPOT);

    extern const uint32_t __rw_region__[];
    extern const uint32_t __rw_region_size__;
    rt_mpu_region_set(static_region_start + 2, (uintptr_t)__rw_region__,
                      (size_t)&__rw_region_size__,
                      RT_MPU_PMPCFG_R | RT_MPU_PMPCFG_W | RT_MPU_PMPCFG_NAPOT);

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

    rt_mpu_enable();

    // 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.
    __asm__("csrs mie, %0" : : "r"(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