blob: 7b0d1f6fd13d552a03f5c704add443bce6b73289 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include <cheri.hh>
#include <stdint.h>
#include <utils.hh>
/**
* Driver for using the (old) RISC-V Core-Local Interrupt (CLINT)
* controller (as opposed to the newer CLIC) to provide timer signals.
* Note that CLIC can run in CLINT-compatible mode, so we don't need to
* support CLIC for a while, it has a lot of features that are overkill for
* most embedded devices.
*/
class StandardClint : private utils::NoCopyNoMove
{
public:
/**
* Initialise the interface.
*/
static void init()
{
// This needs to be csr_read(mhartid) if we support multicore
// systems, but we don't plan to any time soon.
constexpr uint32_t HartId = 0;
auto setField = [&](auto &field, size_t offset, size_t size) {
CHERI::Capability capability{MMIO_CAPABILITY(uint32_t, clint)};
capability.address() += offset;
capability.bounds() = size;
field = capability;
};
setField(pmtimer, StandardClint::ClintMtime, 2 * sizeof(uint32_t));
setField(pmtimercmp,
StandardClint::ClintMtimecmp + HartId * 8,
2 * sizeof(uint32_t));
}
static uint64_t time()
{
// The timer is little endian, so the high 32 bits are after the low 32
// bits. We can't do atomic 64-bit loads and so we have to read these
// separately.
volatile uint32_t *timerHigh = pmtimer + 1;
uint32_t timeLow, timeHigh;
// Read the current time. Loop until the high 32 bits are stable.
do
{
timeHigh = *timerHigh;
timeLow = *pmtimer;
} while (timeHigh != *timerHigh);
return (uint64_t(timeHigh) << 32) | timeLow;
}
/**
* Set the timer up for the next timer interrupt. We need to:
* 1. read the current MTIME,
* 2. add tick amount of cycles to the current time,
* 3. write back the new value to MTIMECMP for the next interrupt.
* Because MTIMECMP is 64-bit and we are a 32-bit CPU, we need to:
* 1. ensure the high 32 bits are stable when we read the low 32 bits
* 2. ensure we write both halves in a way that will not produce
* spurious interrupts.
* Note that CLINT does not need any action to acknowledge the
* interrupt. Writing to any half is enough to clear the interrupt,
* which is also why 2. is important.
*/
static void setnext(uint64_t nextTime)
{
/// the high 32 bits of the 64-bit MTIME register
volatile uint32_t *pmtimercmphigh = pmtimercmp + 1;
uint32_t curmtimehigh, curmtime, curmtimenew;
// Write the new MTIMECMP value, at which the next interrupt fires.
*pmtimercmphigh = -1; // Prevent spurious interrupts.
*pmtimercmp = nextTime;
*pmtimercmphigh = nextTime >> 32;
}
static void clear()
{
volatile uint32_t *pmtimercmphigh = pmtimercmp + 1;
*pmtimercmphigh = -1; // Prevent spurious interrupts.
}
private:
#ifdef IBEX_SAFE
/**
* The Ibex-SAFE platform doesn't implement a complete CLINT, only the
* timer part (which is all that we use). Its offsets within the CLINT
* region are different.
*/
static constexpr size_t ClintMtime = 0x10;
static constexpr size_t ClintMtimecmp = 0x18;
#else
/**
* The standard RISC-V CLINT is a large memory-mapped device and places the
* timers a long way in.
*/
static constexpr size_t ClintMtimecmp = 0x4000U;
static constexpr size_t ClintMtime = 0xbff8U;
#endif
static inline volatile uint32_t *pmtimercmp;
static inline volatile uint32_t *pmtimer;
};
using TimerCore = StandardClint;