rt 0.19.1

A real-time operating system capable of full preemption
Documentation
#include "handlers.h"

#include <gic.h>

#include <rt/arch/coprocessor.h>
#include <rt/arch/mpu.h>
#include <rt/arch/semihosting.h>
#include <rt/arch/syscall.h>

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

/* QEMU's Cortex-R52 emulation doesn't support the PMU cycle counter, so use the
 * generic timer counter instead. */
void rt_cycle_init(void)
{
    // The generic timer is already initialized for tick interrupts.
}

uint32_t rt_cycle(void)
{
    return (uint32_t)cntpct();
}

__attribute__((used)) RT_STACK(svc_stack, 1024);
__attribute__((used)) RT_STACK(irq_stack, 1024);
__attribute__((used)) RT_STACK(fiq_stack, 1024);
__attribute__((used)) RT_STACK(udf_stack, 1024);
__attribute__((used)) RT_STACK(abt_stack, 1024);

__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 dcache_invalidate_all(void)
{
    uint32_t clidr_val = clidr();
    for (unsigned level = 0; level < 7; ++level)
    {
        uint32_t ctype = (clidr_val >> (level * 3)) & 0x7;
        if (ctype == 0)
        {
            break;
        }
        if (ctype == 1)
        {
            continue;
        }
        csselr_set(level << 1);
        __asm__("isb" ::: "memory");

        uint32_t ccsidr_val = ccsidr();
        uint32_t line_size = (ccsidr_val & 0x7) + 4;
        uint32_t assoc = ((ccsidr_val >> 3) & 0x3FF) + 1;
        uint32_t num_sets = ((ccsidr_val >> 13) & 0x7FFF) + 1;
        uint32_t way_shift = (uint32_t)__builtin_clz(assoc - 1);
        uint32_t set_shift = line_size;

        for (uint32_t way = 0; way < assoc; ++way)
        {
            for (uint32_t set = 0; set < num_sets; ++set)
            {
                uint32_t sw =
                    (way << way_shift) | (set << set_shift) | (level << 1);
                dcisw_set(sw);
            }
        }
    }
}

#define NUM_INTERRUPTS 1024

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverride-init"
void (*const gic_vector[NUM_INTERRUPTS])(void) = {
    [0 ... NUM_INTERRUPTS - 1] = default_irq_handler,
    [GIC_INTID_SYSCALL] = rt_syscall_irq_handler,
    [GIC_INTID_TICK] = tick_irq_handler,
    [GIC_INTID_GROUP0] = reserved_irq_handler,
    [GIC_INTID_RESERVED] = reserved_irq_handler,
    [GIC_INTID_NMI] = reserved_irq_handler,
    [GIC_INTID_SPURIOUS] = spurious_irq_handler,
};
#pragma GCC diagnostic pop

void init(void);
void init(void)
{
    // Invalidate caches and branch predictor.
    iciallu_set(0);
    dcache_invalidate_all();
    bpiall_set(0);
    __asm__("dsb; isb" ::: "memory");

    // Enable caches and branch prediction.
    uint32_t sctlr_flags = SCTLR_C | SCTLR_Z | SCTLR_I;
#ifdef __thumb__
    // Take exceptions in thumb mode if compiling for thumb.
    sctlr_flags |= SCTLR_TE;
#endif
    sctlr_oreq(sctlr_flags);
    __asm__("dsb; isb" ::: "memory");

    rt_mpu_attr_init();
    rt_mpu_attr_set(0, RT_MPU_ATTR_WB_RWALLOC);
    rt_mpu_attr_set(1, RT_MPU_ATTR_DEVICE_NGNRNE);

    extern const uint32_t vector[];
    extern const uint32_t __vector_size__[];
    rt_mpu_region_set(0, (uintptr_t)vector, (size_t)__vector_size__,
                      RT_MPU_ATTR_RO | RT_MPU_ATTR_INDEX(0) |
                          RT_MPU_ATTR_ENABLE);

    extern const uint32_t __ro_region__[];
    extern const uint32_t __ro_region_size__[];
    rt_mpu_region_set(1, (uintptr_t)__ro_region__, (size_t)__ro_region_size__,
                      RT_MPU_ATTR_RO | RT_MPU_ATTR_XN | RT_MPU_ATTR_INDEX(0) |
                          RT_MPU_ATTR_ENABLE);

    extern const uint32_t __rx_region__[];
    extern const uint32_t __rx_region_size__[];
    rt_mpu_region_set(2, (uintptr_t)__rx_region__, (size_t)__rx_region_size__,
                      RT_MPU_ATTR_RO | RT_MPU_ATTR_INDEX(0) |
                          RT_MPU_ATTR_ENABLE);

    extern const uint32_t __priv_rw_region__[];
    extern const uint32_t __priv_rw_region_size__[];
    rt_mpu_region_set(3, (uintptr_t)__priv_rw_region__,
                      (size_t)__priv_rw_region_size__,
                      RT_MPU_ATTR_RW_PRIV | RT_MPU_ATTR_XN |
                          RT_MPU_ATTR_INDEX(0) | RT_MPU_ATTR_ENABLE);

    extern const uint32_t __rw_region__[];
    extern const uint32_t __rw_region_size__[];
    rt_mpu_region_set(4, (uintptr_t)__rw_region__, (size_t)__rw_region_size__,
                      RT_MPU_ATTR_RW | RT_MPU_ATTR_XN | RT_MPU_ATTR_INDEX(0) |
                          RT_MPU_ATTR_ENABLE);

    /* If per-task MPU regions are not enabled, we need to give user rw
     * permissions to all task stacks so unprivileged tasks can access their own
     * stack. */
#if !RT_MPU_TASK_REGIONS_ENABLE
    extern const uint32_t __task_stack_region__[], __task_stack_region_size__[];
    rt_mpu_region_set(5, (uintptr_t)__task_stack_region__,
                      (size_t)__task_stack_region_size__,
                      RT_MPU_ATTR_INDEX(0) | task_stack_priv_attr |
                          RT_MPU_ATTR_XN | RT_MPU_ATTR_PXN |
                          RT_MPU_ATTR_ENABLE);
#endif

    /* Use privileged default region for peripherals and kernel access to task
     * stacks. The Armv8 MPU doesn't allow overlapping MPU regions, so priv_rw
     * must not contain task stacks. */
    rt_mpu_privileged_default_enable();
    rt_mpu_enable();

    GICD->ctlr = GICD_CTLR_ENABLE_GRP1;

    // Wake the redistributor so SGI/PPI interrupts are forwarded.
    GICR->rd.waker &= ~GICR_WAKER_PROCESSOR_SLEEP;
    while (GICR->rd.waker & GICR_WAKER_CHILDREN_ASLEEP)
    {
    }

    GICR->sgi.ipriorityr[GIC_INTID_SYSCALL] = GIC_PRIORITY_SYSCALL;
    GICR->sgi.ipriorityr[GIC_INTID_TICK] = GIC_PRIORITY_TICK;

    const uint32_t interrupts =
        (UINT32_C(1) << GIC_INTID_SYSCALL) | (UINT32_C(1) << GIC_INTID_TICK);

    // Set the syscall and tick interrupts as group 1 (IRQ).
    GICR->sgi.igroupr0 = interrupts;
    GICR->sgi.isenabler0 = interrupts;

    icc_pmr_set(GIC_PRIORITY_IDLE);

    // Enable group 1 (IRQ) interrupts at the CPU interface.
    icc_igrpen1_set(1);

    // Set the initial timer compare value 1ms in the future.
    cntp_cval_set(cntfrq() / UINT32_C(1000));
    cntp_ctl_set(CNT_CTL_ENABLE);
}

#if RT_LOG_ENABLE
// Provide rt/log.h stubs so the build succeeds.
void rt_logf(const char *format, ...)
{
    (void)format;
}

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