| CHERIoT RTOS Board Descriptions | |
| =============================== | |
| CHERIoT RTOS is intended to run on any core that implements the CHERIoT ISA. | |
| Our initial prototype was a modification of the Flute core, our initial production implementation is based on Ibex, and we also use a software simulator generated from the Sail formal model. | |
| The formal model is fairly standard but the cores can run in simulation, FPGA, or as ASICs with different on-chip peripherals, address space layouts, and so on. | |
| To allow software to be portable across these and other implementations, we use a board description file. | |
| This is a JSON document that contains a single object describing the board. | |
| The parser supports a small superset of JSON, in particular it permits hex numbers as well as decimal, which is particularly useful for memory addresses. | |
| The [`boards`](../sdk/boards) directory contains some existing examples. | |
| Memory layout | |
| ------------- | |
| Our security guarantees for the shared heap depend on the mechanism that allows the allocator to mark memory as quarantined. | |
| Any pointer to memory in this region is subject to a check (by the hardware) on load: if it points to deallocated memory then it will be invalidated on load. | |
| This mechanism is necessary only for memory that can be reused by different trust domains during a single boot. | |
| Memory used to hold globals and code does not require it and so an implementation may save some hardware and power costs by supporting these temporal safety features for only a subset of memory. | |
| As such, we require a range of memory that is used for static code and data ('instruction memory') that is not required to support this mechanism and an additional range that *must* support this for use as the shared heap ('heap memory'). | |
| Implementations may choose not to make this separation and provide a single memory region. | |
| At some point, we expect to further separate the mutable and immutable portions of instruction memory so that we can support execute in place. | |
| Instruction memory is described by the `instruction_memory` property. | |
| This must be an object with a `start` and `end` property, each of which is an address. | |
| The region available for the heap is described in the `heap` property. | |
| This must describe the region over which the load filter is defined. | |
| If its `start` property is omitted, then it is assumed to start in the same place as instruction memory. | |
| The Sail board description has a simple layout: | |
| ```json | |
| "instruction_memory": { | |
| "start": 0x80000000, | |
| "end": 0x80040000 | |
| }, | |
| "heap": { | |
| "end": 0x80040000 | |
| }, | |
| ``` | |
| This starts instruction memory at the default RISC-V memory address and has a single 256 KiB region that is used for both kinds of memory. | |
| MMIO Devices | |
| ------------ | |
| Each memory-mapped I/O device is listed as an object within the `devices` field. | |
| The name of the field is the name of the device and must be an object that contains a `start` and either a `length` or `end` property that, between them, describe the memory range for the device. | |
| Software can then use the `MMIO_CAPABILITY` macro with the name of the device to get a capability to that device's MMIO range and can use `#if DEVICE_EXISTS(device_name)` to conditionally compile code if that device exists. | |
| The Sail model is very simple and so provides only three devices: | |
| ```json | |
| "devices": { | |
| "clint": { | |
| "start": 0x2000000, | |
| "length": 0x10000 | |
| }, | |
| "uart": { | |
| "start": 0x10000000, | |
| "end": 0x10000100 | |
| }, | |
| "shadow" : { | |
| "start" : 0x83000000, | |
| "end" : 0x83001000 | |
| } | |
| }, | |
| ``` | |
| This describes the core-local interrupt controller (`clint`), a UART, and the shadow memory used for the temporal safety mechanism (`shadow`). | |
| The UART, for example, is referred to in source using `MMIO_CAPABILITY(struct Uart, uart)`, which evaluates to a `volatile struct Uart *`, giving a capability to this device. | |
| Interrupts | |
| ---------- | |
| External interrupts should be defined in an array in the `interrupts` property. | |
| Each element has a `name`, a `number` and a `priority`. | |
| The name is used to refer to this in software and must be a valid C identifier. | |
| The number is the interrupt number. | |
| The priority is the priority with which this interrupt will be configured in the interrupt controller. | |
| Interrupts may optionally have an `edge_triggered` property (if this is omitted, it is assumed to be false). | |
| If this exists and is set to true then the interrupt is assumed to fire when a condition first holds, rather than to remain raised as long as a condition holds. | |
| Interrupts that are edge triggered are automatically completed by the scheduler; they do not require a call to `interrupt_complete`. | |
| Hardware features | |
| ----------------- | |
| Some properties define base parts of hardware support. | |
| The `revoker` property is either absent (no temporal safety support), `"software"` (revocation is implemented via a software sweep) or `"hardware"` (there is a hardware revoker). | |
| We expect this to be `"hardware"` on all real implementations, the software revoker exists primarily for the Sail model and the no temporal safety mode only for benchmarking the overhead of revocation. | |
| If the `stack_high_water_mark` property is set to true, then we assume the CPU provides CSRs for tracking stack usage. | |
| This property is primarily present for benchmarking as all of our targets currently implement this feature. | |
| Clock configuration | |
| ------------------- | |
| The clock rate is configured by two properties. | |
| The `timer_hz` field is the number of timer increments per second, typically the clock speed of the chip (the RISC-V timer is defined in terms of cycles). | |
| The `tickrate_hz` specifies how many scheduler ticks should happen per second. | |
| See the [timeout documentation](Timeouts.md) for more discussion about ticks. | |
| Conditional compilation | |
| ----------------------- | |
| The `defines` property specifies any pre-defined macros that should be set when building for this board. | |
| The `driver_includes` property contains an array (in priority order) of include directories that should be added for this target. | |
| Each of the paths in `driver_includes` is, by default, relative to the location of the board file (which allows the board file and drivers to be distributed together). | |
| Optionally, it may include the string `$(sdk)`, which will be replaced by the full path of the SDK directory. | |
| For example, `"$(sdk)/include/platform/generic-riscv"` will expand to the generic RISC-V directory in the SDK. | |
| The driver headers use `#include_next` to include more generic files and so it is important to list the directories containing your overrides first. | |
| Simulation support | |
| ------------------ | |
| There are two properties for defining simulation platforms. | |
| If `simulation` is set to `true` then this board is assumed to be a simulation platform. | |
| This will make the `simulation_exit` function attempt to exit the simulator in case of catastrophic failures. | |
| In addition, the `simulator` property can be the name of a program (or script) that can simulate images compiled for this board. | |
| This will be run from the build directory and will be passed the absolute path of the firmware image when `xmake run` is used. | |
| The build system will look for the simulator in the SDK directory and, failing that, in the path. | |
| Exact paths can be provided by using `${sdk}` or `${board}` in the name of the simulator. | |
| These will be expanded to the full path of the SDK or the directory containing the board description file, respectively. |