Merge remote-tracking branch 'upstream/main' into update
NB: this requires the 2025-01-21 toolchain (or later)
Change-Id: If26a2fae909bcc734de2a7f024eba27a244fdece
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 5a38f98..71defea 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -2,7 +2,7 @@
"image": "ghcr.io/cheriot-platform/devcontainer:latest",
"remoteUser": "cheriot",
"containerUser": "cheriot",
- "onCreateCommand": "git config --global --add safe.directory /workspaces/cheriot-rtos && git submodule init && git submodule update && cd tests && xmake f --sdk=/cheriot-tools/ && xmake project -k compile_commands .. && cd .. && for I in ex*/[[:digit:]]* ; do echo $I ; cd $I ; xmake f --sdk=/cheriot-tools/ && xmake project -k compile_commands . && cd ../.. ; done",
+ "onCreateCommand": "git config --global --add safe.directory /workspaces/cheriot-rtos && git submodule update --init --recursive && ./scripts/generate_compile_commands.sh",
"customizations": {
"vscode": {
"extensions": [
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1e29fe1..98ce086 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,6 +7,11 @@
branches: [ main ]
merge_group:
workflow_dispatch:
+ inputs:
+ devcontainer:
+ description: 'Set to override default build container'
+ type: string
+ required: false
jobs:
run-tests:
@@ -17,17 +22,17 @@
include:
- sonata: false
- build-type: debug
- build-flags: --debug-loader=y --debug-scheduler=y --debug-allocator=y -m debug
+ build-flags: --debug-loader=y --debug-scheduler=y --debug-allocator=y --allocator-rendering=y -m debug
- build-type: release
build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y
- board: sonata-simulator
build-type: release
- build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y --testing-model-output=y
+ build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y
sonata: true
fail-fast: false
runs-on: ubuntu-latest
container:
- image: ghcr.io/cheriot-platform/devcontainer:latest
+ image: ${{ inputs.devcontainer || 'ghcr.io/cheriot-platform/devcontainer:latest' }}
options: --user 1001
steps:
- name: Checkout repository and submodules
@@ -40,8 +45,6 @@
xmake f --board=${{ matrix.board }} --sdk=/cheriot-tools/ ${{ matrix.build-flags }}
xmake
- name: Run tests
- # Test suite needs HyperRAM support to be added to RTOS because SRAM is not big enough.
- if: ${{ !matrix.sonata }}
run: |
cd tests
xmake run
@@ -83,6 +86,8 @@
uses: actions/checkout@v4
with:
submodules: recursive
+ - name: Generate compiler_commands.json files
+ run: ./scripts/generate_compile_commands.sh
- name: Run clang-format and clang-tidy
run: ./scripts/run_clang_tidy_format.sh /cheriot-tools/bin
diff --git a/.github/workflows/test-new-xmake-nightly.yml b/.github/workflows/test-new-xmake-nightly.yml
index eac04ab..e4e9664 100644
--- a/.github/workflows/test-new-xmake-nightly.yml
+++ b/.github/workflows/test-new-xmake-nightly.yml
@@ -5,6 +5,15 @@
- cron: '0 0 * * *'
workflow_dispatch:
+defaults:
+ run:
+ # The `sh` used by default does not understand `source` which `xmake` uses
+ # in its profile script. (And POSIX requires only dot, `.`, leaving
+ # `source` unspecified. See
+ # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
+ # for far too much detai.)
+ shell: bash
+
jobs:
run-tests:
strategy:
@@ -18,24 +27,40 @@
uses: actions/checkout@v3
with:
submodules: recursive
- - name: Build latest xmake
+ # `xmake update` spawns `xmake` in the background, which is challenging.
+ # Work around this by using daemontools' fghack "anti-backgrounding tool";
+ # see https://cr.yp.to/daemontools/fghack.html and
+ # https://github.com/xmake-io/xmake/issues/6030 for discussion.
+ - name: "Install daemontools"
run: |
- xmake update dev
+ sudo apt install -y daemontools
+ - name: "Build latest xmake and prune upstream's"
+ run: |
+ mkdir -p ~/.local
+ fghack xmake update dev
sudo apt remove -y xmake
- echo ~/.xmake/profile
- . ~/.xmake/profile
+ - name: "Reasonableness check: have a look around ~/.local"
+ run: |
+ find ~/.local
+ ls -la ~/.local/bin/xmake
+ - name: "Integrate xmake, generating profile script"
+ run: |
+ ~/.local/bin/xmake update --integrate
+ - name: "Reasonableness check: dump the resulting profile script"
+ run: |
+ ls -l ~/.xmake/profile
+ cat ~/.xmake/profile
- name: Build tests
run: |
pwd
- echo ~/.xmake/profile
+ . ~/.xmake/profile
which xmake
xmake --version
cd tests
- xmake f --board=${{ matrix.board }} --sdk=/cheriot-tools/ ${{ matrix.build-flags }}
+ xmake f --board=sail --sdk=/cheriot-tools/ --mode=release
xmake
- name: Run tests
run: |
. ~/.xmake/profile
- xmake --version
cd tests
xmake run
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 9cd81d7..0000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,149 +0,0 @@
-trigger:
-- core
-
-resources:
- pipelines:
- - pipeline: LLVM
- project: CHERI-MCU
- source: LLVM
- - pipeline: Flute-TCM
- project: CHERI-MCU
- source: Flute-TCM
- - pipeline: sail-cheri-mcu
- project: CHERI-MCU
- source: sail-cheri-mcu
-
-jobs:
-############################################## Linux Builds
-- job:
- displayName: RTOS tests
- pool:
- vmImage: ubuntu-20.04
- timeoutInMinutes: 300
- strategy:
- matrix:
- HardwareRevokerRelease:
- board: flute
- flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n
- mode: release
- SoftwareRevokerRelease:
- board: flute-software-revoker
- flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n
- mode: release
- SailRelease:
- board: sail
- flags: --debug-loader=n --debug-scheduler=n --debug-allocator=n
- mode: release
- HardwareRevokerDebug:
- board: flute
- flags: --debug-loader=y --debug-scheduler=y --debug-allocator=y
- mode: debug
- SoftwareRevokerDebug:
- board: flute-software-revoker
- flags: --debug-loader=y --debug-scheduler=y --debug-allocator=y
- mode: debug
- SailDebug:
- board: sail
- flags: --debug-loader=y --debug-scheduler=y --debug-allocator=y
- mode: debug
- steps:
- - checkout: self
- submodules: recursive
- - download: LLVM
- - download: Flute-TCM
- - download: sail-cheri-mcu
- - script: |
- set -eo pipefail
- sudo add-apt-repository ppa:xmake-io/xmake
- sudo apt update
- sudo apt install xmake
- displayName: 'Installing dependencies'
- - script: |
- chmod +x $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/bin/* \
- $(Pipeline.Workspace)/$(resources.triggeringAlias)/Flute-TCM/FluteSimulator/* \
- $(Pipeline.Workspace)/$(resources.triggeringAlias)/sail-cheri-mcu/SailSimulator/*
- echo $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM
- echo $(Pipeline.Workspace)
- ls -R $(Pipeline.Workspace)
- displayName: 'See where anything is installed'
- - script: |
- ls $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/bin/
- echo xmake f -P . --board=$(board) --sdk=$(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/ $(flags) -m $(mode)
- xmake f -P . --board=$(board) --sdk=$(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/ $(flags) -m $(mode)
- workingDirectory: 'tests'
- displayName: 'Configure the build'
- - script: |
- xmake -P . -v
- workingDirectory: 'tests'
- displayName: 'Building the test suite'
- - script: |
- $(Pipeline.Workspace)/$(resources.triggeringAlias)/sail-cheri-mcu/SailSimulator/cheriot_sim -p --no-trace build/cheriot/cheriot/$(mode)/test-suite
- condition: startsWith(variables['board'],'sail')
- workingDirectory: 'tests'
- displayName: 'Running the test suite on Sail'
- - script: |
- export PATH=$(Pipeline.Workspace)/$(resources.triggeringAlias)/Flute-TCM/FluteSimulator:$PATH
- for I in `seq 32768` ; do echo 00000000 >> tail.hex ; done
- elf_to_hex build/cheriot/cheriot/$(mode)/test-suite Mem.hex
- hex_to_tcm_hex.sh
- cp tail.hex Mem-TCM-tags-0.hex
- exe_HW_sim +tohost | tee sim.log
- EXIT_CODE=$(expr $(printf '%d' $(grep -E -e 'tohost_value is 0x[0-9a-zA-Z]+' -o sim.log | awk '{print $3}')) / 2)
- echo "Exit code: $EXIT_CODE"
- exit $EXIT_CODE
- condition: startsWith(variables['board'],'flute')
- workingDirectory: 'tests'
- displayName: 'Running the test suite on Flute'
- - script: |
- set -eo pipefail
- for example_dir in $PWD/examples/*/; do
- cd $example_dir
- echo Building $example_dir
- xmake f --board=$(board) --sdk=$(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/ $(flags) -m $(mode)
- xmake
- done
- displayName: 'Building the examples'
- - script: |
- set -eo pipefail
- for example_dir in $PWD/examples/*/; do
- cd $example_dir
- echo Running $example_dir
- example_name=$(basename ${example_dir#*.})
- $(Pipeline.Workspace)/$(resources.triggeringAlias)/sail-cheri-mcu/SailSimulator/cheriot_sim \
- build/cheriot/cheriot/$(mode)/${example_name}
- done
- condition: startsWith(variables['board'],'sail')
- displayName: 'Running the examples'
-
-- job:
- displayName: Check coding style
- pool:
- vmImage: ubuntu-20.04
- timeoutInMinutes: 300
- steps:
- - checkout: self
- submodules: recursive
- - download: LLVM
- - script: |
- chmod +x $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/bin/*
- echo $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM
- echo $(Pipeline.Workspace)
- ls -R $(Pipeline.Workspace)
- displayName: 'See where anything is installed'
- - script: |
- ./scripts/run_clang_tidy_format.sh $(Pipeline.Workspace)/$(resources.triggeringAlias)/LLVM/LLVM/bin/
- displayName: 'Running clang-tidy and clang-format'
-
-- job:
- displayName: Compliance checks
- pool:
- vmImage: windows-latest
- steps:
- - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
- displayName: 'Run CredScan'
- inputs:
- debugMode: false
- - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
- displayName: 'Component Detection'
- - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@3
- displayName: 'Publish Security Analysis Logs'
diff --git a/benchmarks/timing.h b/benchmarks/timing.h
index 066323e..2d4bf04 100644
--- a/benchmarks/timing.h
+++ b/benchmarks/timing.h
@@ -12,7 +12,7 @@
// On Sail, report the number of instructions, the cycle count is
// meaningless.
__asm__ volatile("csrr %0, minstret" : "=r"(cycles));
-#elifdef IBEX
+#elif defined(IBEX)
// CHERIoT-Ibex does not yet implement rdcycle, so read the CSR
// directly.
__asm__ volatile("csrr %0, mcycle" : "=r"(cycles));
diff --git a/compile_flags.txt b/compile_flags.txt
index ae0cf0a..0ed75a0 100644
--- a/compile_flags.txt
+++ b/compile_flags.txt
@@ -22,6 +22,7 @@
-DDEBUG_LOADER=true
-DDEBUG_ALLOCATOR=true
-DDEBUG_SCHEDULER=true
+-DDEBUG_CXXRT=true
-DSAIL
-DCPU_TIMER_HZ=2000
-DTICK_RATE_HZ=10
diff --git a/docs/Allocator.md b/docs/Allocator.md
index 96f3c5f..fe8a0b2 100644
--- a/docs/Allocator.md
+++ b/docs/Allocator.md
@@ -29,10 +29,10 @@
"contents": "00001000 00000000 00000000 00000000 00000000 00000000",
"kind": "SealedObject",
"sealing_type": {
- "compartment": "alloc",
+ "compartment": "allocator",
"key": "MallocKey",
"provided_by": "build/cheriot/cheriot/release/cherimcu.allocator.compartment",
- "symbol": "__export.sealing_type.alloc.MallocKey"
+ "symbol": "__export.sealing_type.allocator.MallocKey"
}
},
```
diff --git a/docs/WritingADeviceDriver.md b/docs/WritingADeviceDriver.md
index 305a4df..f8ffeb7 100644
--- a/docs/WritingADeviceDriver.md
+++ b/docs/WritingADeviceDriver.md
@@ -118,10 +118,10 @@
"contents": "10000101",
"kind": "SealedObject",
"sealing_type": {
- "compartment": "sched",
+ "compartment": "scheduler",
"key": "InterruptKey",
"provided_by": "build/cheriot/cheriot/release/example-firmware.scheduler.compartment",
- "symbol": "__export.sealing_type.sched.InterruptKey"
+ "symbol": "__export.sealing_type.scheduler.InterruptKey"
}
```
@@ -194,11 +194,11 @@
### Platform includes
Each board description contains a set of include paths.
-For example, our Flute prototype platform has this:
+For example, our Ibex prototype platform has this:
```json
"driver_includes" : [
- "../include/platform/flute",
+ "../include/platform/ibex",
"../include/platform/generic-riscv"
],
```
diff --git a/examples/02.hello_compartment/hello.cc b/examples/02.hello_compartment/hello.cc
index 2a8421b..d71cfa1 100644
--- a/examples/02.hello_compartment/hello.cc
+++ b/examples/02.hello_compartment/hello.cc
@@ -4,7 +4,7 @@
#include "hello.h"
// This header adds an error handler that writes to the UART on error.
// Uncomment it and see that the compartmentalisation policy no longer passes.
-//#include <fail-simulator-on-error.h>
+// #include <fail-simulator-on-error.h>
/// Thread entry point.
void __cheri_compartment("hello") entry()
diff --git a/examples/04.temporal_safety/allocate.cc b/examples/04.temporal_safety/allocate.cc
index b2cb79b..71c736f 100644
--- a/examples/04.temporal_safety/allocate.cc
+++ b/examples/04.temporal_safety/allocate.cc
@@ -84,9 +84,9 @@
Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY));
}
- // Sub object with a fast claim
+ // Sub object with an ephemeral claim
{
- Debug::log("----- Sub object with a fast claim -----");
+ Debug::log("----- Sub object with an ephemeral claim -----");
void *x = malloc(100);
CHERI::Capability y{x};
@@ -96,12 +96,12 @@
Debug::log("Sub Object: {}", y);
Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY));
- // Add a fast claim for y
+ // Add an ephemeral claim for y
Timeout t{10};
- heap_claim_fast(&t, y);
+ heap_claim_ephemeral(&t, y);
// In this freeing x will invalidate both x & y because free
- // is a cross compartment call, which releases any fast claims.
+ // is a cross compartment call, which releases any ephemeral claims.
free(x);
Debug::log("After free");
Debug::log("Allocated : {}", x);
@@ -119,7 +119,7 @@
Debug::log("Allocated : {}", x);
Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY));
- // Get the claimant compartment to make a fast claim
+ // Get the claimant compartment to make a ephemeral claim
make_claim(x);
// free x. We get out quota back but x remains valid as
diff --git a/examples/05.sealing/identifier.cc b/examples/05.sealing/identifier.cc
index c66db96..08b2c5d 100644
--- a/examples/05.sealing/identifier.cc
+++ b/examples/05.sealing/identifier.cc
@@ -36,7 +36,7 @@
// capabilities.
auto [unsealed, sealed] =
blocking_forever<token_allocate<Identifier>>(MALLOC_CAPABILITY, key());
- if (sealed == nullptr)
+ if (!sealed.is_valid())
{
return nullptr;
}
diff --git a/scripts/common.sh b/scripts/common.sh
new file mode 100644
index 0000000..4026df4
--- /dev/null
+++ b/scripts/common.sh
@@ -0,0 +1,20 @@
+# Common functions to include in multiple scripts
+
+function error() {
+ echo "Error: $1"
+ exit 1
+}
+
+function ensure_cheriot_rtos_root () {
+ [ -d sdk ] || error "Please run this script from the root of the cheriot-rtos repository."
+}
+
+function find_sdk () {
+ if [ -n "$1" ]; then
+ SDK="$(readlink -f $1)"
+ elif [ -d "/cheriot-tools/bin" ]; then
+ SDK=/cheriot-tools
+ else
+ error "No SDK found, please provide as first argument."
+ fi
+}
diff --git a/scripts/generate_compile_commands.sh b/scripts/generate_compile_commands.sh
new file mode 100755
index 0000000..a05cbee
--- /dev/null
+++ b/scripts/generate_compile_commands.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+# Generate compile commands files for all known projects in this repo
+
+
+. "$(dirname $0)"/common.sh
+
+ensure_cheriot_rtos_root
+
+find_sdk $1
+
+echo "Using SDK=$SDK"
+
+# Generate compile_commands.json for all of the extra tests and examples.
+for dir in tests.extra/*/ ex*/[[:digit:]]* ; do
+ echo Generating compile_commands.json for $dir
+ (cd $dir && xmake f --sdk="${SDK}" && xmake project -k compile_commands)
+done
+
+# Generate the top-level compile-commands.json
+cd tests && xmake f --sdk="${SDK}" && xmake project -k compile_commands ..
diff --git a/scripts/includes/helper_find_llvm_install.sh b/scripts/includes/helper_find_llvm_install.sh
index f9b5bc1..f4cad6b 100644
--- a/scripts/includes/helper_find_llvm_install.sh
+++ b/scripts/includes/helper_find_llvm_install.sh
@@ -32,7 +32,7 @@
LLVM_TOOL=$(find_llvm_tool $1)
if [ ! -x ${LLVM_TOOL} ] ; then
- echo Unable to locate $1, please set TOOLS_PATH to the directory containing the LLVM toolchain.
+ echo Unable to locate $1, please set TOOLS_PATH to the directory containing the LLVM toolchain. >&2
exit 1
fi
diff --git a/scripts/model_output/sonata-simulator/examples/audit.txt b/scripts/model_output/sonata-simulator/examples/audit.txt
deleted file mode 100644
index f2abe0c..0000000
--- a/scripts/model_output/sonata-simulator/examples/audit.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[35mProducer[0m: Encrypting message 'Hello, World!'
-[35mEntry compartment[0m: Received encrypted message: 'Ifmmp-!Xpsme"' (13 bytes)
-[35mConsumer[0m: Decrypted message: 'Hello, World!'
diff --git a/scripts/model_output/sonata-simulator/examples/error_handling.txt b/scripts/model_output/sonata-simulator/examples/error_handling.txt
deleted file mode 100644
index ed3cf77..0000000
--- a/scripts/model_output/sonata-simulator/examples/error_handling.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-[35mUART compartment[0m: Message provided by caller: hello
-[35mUART compartment[0m: Detected BoundsViolation(0x1) trying to write to UART. Register CS0(0x8) contained invalid value: 0x101b10 (v:1 0x101b0b-0x101b10 l:0x5 o:0x0 p: - RWcgml -- ---)
-[35mUART compartment[0m: Message provided by caller:
-[35mUART compartment[0m: Detected PermitLoadViolation(0x12) trying to write to UART. Register CS0(0x8) contained invalid value: 0x101b0b (v:1 0x101b0b-0x101b10 l:0x5 o:0x0 p: - -W---- -- ---)
-[35mUART compartment[0m: Message provided by caller: Non-malicious string
diff --git a/scripts/model_output/sonata-simulator/examples/hello_compartment.txt b/scripts/model_output/sonata-simulator/examples/hello_compartment.txt
deleted file mode 100644
index 31e3a2a..0000000
--- a/scripts/model_output/sonata-simulator/examples/hello_compartment.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[35mUART compartment[0m: Hello world
-[35mUART compartment[0m: Hello from the stack
diff --git a/scripts/model_output/sonata-simulator/examples/hello_safe_compartment.txt b/scripts/model_output/sonata-simulator/examples/hello_safe_compartment.txt
deleted file mode 100644
index 0ec9819..0000000
--- a/scripts/model_output/sonata-simulator/examples/hello_safe_compartment.txt
+++ /dev/null
Binary files differ
diff --git a/scripts/model_output/sonata-simulator/examples/hello_world.txt b/scripts/model_output/sonata-simulator/examples/hello_world.txt
deleted file mode 100644
index bcb68d3..0000000
--- a/scripts/model_output/sonata-simulator/examples/hello_world.txt
+++ /dev/null
@@ -1 +0,0 @@
-[35mHello world compartment[0m: Hello world
diff --git a/scripts/model_output/sonata-simulator/examples/javascript.txt b/scripts/model_output/sonata-simulator/examples/javascript.txt
deleted file mode 100644
index 6fc8329..0000000
--- a/scripts/model_output/sonata-simulator/examples/javascript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Hello, World!
-array[0] = 2
-array[1] = 4
-array[2] = 6
-array[3] = 8
-array[4] = 10
-[35mJavaScript hello compartment[0m: Microvium is using 0x1a6 bytes of memory, including 0x60 bytes of heap
-[35mJavaScript hello compartment[0m: Running GC
-[35mJavaScript hello compartment[0m: Microvium is using 0x86 bytes of memory, including 0x14 bytes of heap
-[35mJavaScript hello compartment[0m: Peak heap used: 0x60 bytes, peak stack used: 0x32 bytes
diff --git a/scripts/model_output/sonata-simulator/examples/memory_safety.txt b/scripts/model_output/sonata-simulator/examples/memory_safety.txt
deleted file mode 100644
index 4ebed1f..0000000
--- a/scripts/model_output/sonata-simulator/examples/memory_safety.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-[35mMemory safety compartment[0m: Demonstrate memory safety
-[35mMemory safety compartment[0m: Trigger stack linear overflow
-[35mMemory safety compartment[0m: Detected error in instruction 0x107cac (v:0 0x107820-0x108820 l:0x1000 o:0x0 p: G R-cgm- X- ---)
-[35mMemory safety compartment[0m: Detected BoundsViolation(0x1): Register CA1(0xb) contained invalid value: 0x101ad0 (v:1 0x101ad0-0x101b10 l:0x40 o:0x0 p: - RWcgml -- ---)
-[35mMemory safety compartment[0m: Trigger heap linear overflow
-[35mMemory safety compartment[0m: Detected error in instruction 0x107ab2 (v:0 0x107820-0x108820 l:0x1000 o:0x0 p: G R-cgm- X- ---)
-[35mMemory safety compartment[0m: Detected BoundsViolation(0x1): Register CA0(0xa) contained invalid value: 0x109910 (v:1 0x109910-0x109a10 l:0x100 o:0x0 p: G RWcgm- -- ---)
-[35mMemory safety compartment[0m: Trigger heap nonlinear overflow
-[35mMemory safety compartment[0m: Detected error in instruction 0x107b0c (v:0 0x107820-0x108820 l:0x1000 o:0x0 p: G R-cgm- X- ---)
-[35mMemory safety compartment[0m: Detected BoundsViolation(0x1): Register CA0(0xa) contained invalid value: 0x109a18 (v:1 0x109a18-0x109b18 l:0x100 o:0x0 p: G RWcgm- -- ---)
-[35mMemory safety compartment[0m: Trigger heap use after free
-[35mMemory safety compartment[0m: Detected error in instruction 0x107a58 (v:0 0x107820-0x108820 l:0x1000 o:0x0 p: G R-cgm- X- ---)
-[35mMemory safety compartment[0m: Detected TagViolation(0x2): Register CA0(0xa) contained invalid value: 0x109b20 (v:0 0x109b20-0x109c20 l:0x100 o:0x0 p: G RWcgm- -- ---)
-[35mMemory safety compartment[0m: Trigger storing a stack pointer 0x101ad0 (v:1 0x101ad0-0x101ae0 l:0x10 o:0x0 p: - RWcgml -- ---) into global
-[35mMemory safety compartment[0m: tmp: 0x101ad0 (v:0 0x101ad0-0x101ae0 l:0x10 o:0x0 p: - RWcgml -- ---)
-[35mMemory safety compartment[0m: Detected error in instruction 0x107a28 (v:0 0x107820-0x108820 l:0x1000 o:0x0 p: G R-cgm- X- ---)
-[35mMemory safety compartment[0m: Detected TagViolation(0x2): Register CS0(0x8) contained invalid value: 0x101ad0 (v:0 0x101ad0-0x101ae0 l:0x10 o:0x0 p: - RWcgml -- ---)
diff --git a/scripts/model_output/sonata-simulator/examples/producer-consumer.txt b/scripts/model_output/sonata-simulator/examples/producer-consumer.txt
deleted file mode 100644
index 7a77111..0000000
--- a/scripts/model_output/sonata-simulator/examples/producer-consumer.txt
+++ /dev/null
@@ -1,203 +0,0 @@
-[35mConsumer[0m: Queue set to 0x10a720 (v:1 0x10a720-0x10a778 l:0x58 o:0xb p: G RWcgm- -- ---)
-[35mProducer[0m: Starting producer loop
-[35mConsumer[0m: Waiting for messages
-[35mConsumer[0m: Read 1 from queue
-[35mConsumer[0m: Read 2 from queue
-[35mConsumer[0m: Read 3 from queue
-[35mConsumer[0m: Read 4 from queue
-[35mConsumer[0m: Read 5 from queue
-[35mConsumer[0m: Read 6 from queue
-[35mConsumer[0m: Read 7 from queue
-[35mConsumer[0m: Read 8 from queue
-[35mConsumer[0m: Read 9 from queue
-[35mConsumer[0m: Read 10 from queue
-[35mConsumer[0m: Read 11 from queue
-[35mConsumer[0m: Read 12 from queue
-[35mConsumer[0m: Read 13 from queue
-[35mConsumer[0m: Read 14 from queue
-[35mConsumer[0m: Read 15 from queue
-[35mConsumer[0m: Read 16 from queue
-[35mConsumer[0m: Read 17 from queue
-[35mConsumer[0m: Read 18 from queue
-[35mConsumer[0m: Read 19 from queue
-[35mConsumer[0m: Read 20 from queue
-[35mConsumer[0m: Read 21 from queue
-[35mConsumer[0m: Read 22 from queue
-[35mConsumer[0m: Read 23 from queue
-[35mConsumer[0m: Read 24 from queue
-[35mConsumer[0m: Read 25 from queue
-[35mConsumer[0m: Read 26 from queue
-[35mConsumer[0m: Read 27 from queue
-[35mConsumer[0m: Read 28 from queue
-[35mConsumer[0m: Read 29 from queue
-[35mConsumer[0m: Read 30 from queue
-[35mConsumer[0m: Read 31 from queue
-[35mConsumer[0m: Read 32 from queue
-[35mConsumer[0m: Read 33 from queue
-[35mConsumer[0m: Read 34 from queue
-[35mConsumer[0m: Read 35 from queue
-[35mConsumer[0m: Read 36 from queue
-[35mConsumer[0m: Read 37 from queue
-[35mConsumer[0m: Read 38 from queue
-[35mConsumer[0m: Read 39 from queue
-[35mConsumer[0m: Read 40 from queue
-[35mConsumer[0m: Read 41 from queue
-[35mConsumer[0m: Read 42 from queue
-[35mConsumer[0m: Read 43 from queue
-[35mConsumer[0m: Read 44 from queue
-[35mConsumer[0m: Read 45 from queue
-[35mConsumer[0m: Read 46 from queue
-[35mConsumer[0m: Read 47 from queue
-[35mConsumer[0m: Read 48 from queue
-[35mConsumer[0m: Read 49 from queue
-[35mConsumer[0m: Read 50 from queue
-[35mConsumer[0m: Read 51 from queue
-[35mConsumer[0m: Read 52 from queue
-[35mConsumer[0m: Read 53 from queue
-[35mConsumer[0m: Read 54 from queue
-[35mConsumer[0m: Read 55 from queue
-[35mConsumer[0m: Read 56 from queue
-[35mConsumer[0m: Read 57 from queue
-[35mConsumer[0m: Read 58 from queue
-[35mConsumer[0m: Read 59 from queue
-[35mConsumer[0m: Read 60 from queue
-[35mConsumer[0m: Read 61 from queue
-[35mConsumer[0m: Read 62 from queue
-[35mConsumer[0m: Read 63 from queue
-[35mConsumer[0m: Read 64 from queue
-[35mConsumer[0m: Read 65 from queue
-[35mConsumer[0m: Read 66 from queue
-[35mConsumer[0m: Read 67 from queue
-[35mConsumer[0m: Read 68 from queue
-[35mConsumer[0m: Read 69 from queue
-[35mConsumer[0m: Read 70 from queue
-[35mConsumer[0m: Read 71 from queue
-[35mConsumer[0m: Read 72 from queue
-[35mConsumer[0m: Read 73 from queue
-[35mConsumer[0m: Read 74 from queue
-[35mConsumer[0m: Read 75 from queue
-[35mConsumer[0m: Read 76 from queue
-[35mConsumer[0m: Read 77 from queue
-[35mConsumer[0m: Read 78 from queue
-[35mConsumer[0m: Read 79 from queue
-[35mConsumer[0m: Read 80 from queue
-[35mConsumer[0m: Read 81 from queue
-[35mConsumer[0m: Read 82 from queue
-[35mConsumer[0m: Read 83 from queue
-[35mConsumer[0m: Read 84 from queue
-[35mConsumer[0m: Read 85 from queue
-[35mConsumer[0m: Read 86 from queue
-[35mConsumer[0m: Read 87 from queue
-[35mConsumer[0m: Read 88 from queue
-[35mConsumer[0m: Read 89 from queue
-[35mConsumer[0m: Read 90 from queue
-[35mConsumer[0m: Read 91 from queue
-[35mConsumer[0m: Read 92 from queue
-[35mConsumer[0m: Read 93 from queue
-[35mConsumer[0m: Read 94 from queue
-[35mConsumer[0m: Read 95 from queue
-[35mConsumer[0m: Read 96 from queue
-[35mConsumer[0m: Read 97 from queue
-[35mConsumer[0m: Read 98 from queue
-[35mConsumer[0m: Read 99 from queue
-[35mConsumer[0m: Read 100 from queue
-[35mConsumer[0m: Read 101 from queue
-[35mConsumer[0m: Read 102 from queue
-[35mConsumer[0m: Read 103 from queue
-[35mConsumer[0m: Read 104 from queue
-[35mConsumer[0m: Read 105 from queue
-[35mConsumer[0m: Read 106 from queue
-[35mConsumer[0m: Read 107 from queue
-[35mConsumer[0m: Read 108 from queue
-[35mConsumer[0m: Read 109 from queue
-[35mConsumer[0m: Read 110 from queue
-[35mConsumer[0m: Read 111 from queue
-[35mConsumer[0m: Read 112 from queue
-[35mConsumer[0m: Read 113 from queue
-[35mConsumer[0m: Read 114 from queue
-[35mConsumer[0m: Read 115 from queue
-[35mConsumer[0m: Read 116 from queue
-[35mConsumer[0m: Read 117 from queue
-[35mConsumer[0m: Read 118 from queue
-[35mConsumer[0m: Read 119 from queue
-[35mConsumer[0m: Read 120 from queue
-[35mConsumer[0m: Read 121 from queue
-[35mConsumer[0m: Read 122 from queue
-[35mConsumer[0m: Read 123 from queue
-[35mConsumer[0m: Read 124 from queue
-[35mConsumer[0m: Read 125 from queue
-[35mConsumer[0m: Read 126 from queue
-[35mConsumer[0m: Read 127 from queue
-[35mConsumer[0m: Read 128 from queue
-[35mConsumer[0m: Read 129 from queue
-[35mConsumer[0m: Read 130 from queue
-[35mConsumer[0m: Read 131 from queue
-[35mConsumer[0m: Read 132 from queue
-[35mConsumer[0m: Read 133 from queue
-[35mConsumer[0m: Read 134 from queue
-[35mConsumer[0m: Read 135 from queue
-[35mConsumer[0m: Read 136 from queue
-[35mConsumer[0m: Read 137 from queue
-[35mConsumer[0m: Read 138 from queue
-[35mConsumer[0m: Read 139 from queue
-[35mConsumer[0m: Read 140 from queue
-[35mConsumer[0m: Read 141 from queue
-[35mConsumer[0m: Read 142 from queue
-[35mConsumer[0m: Read 143 from queue
-[35mConsumer[0m: Read 144 from queue
-[35mConsumer[0m: Read 145 from queue
-[35mConsumer[0m: Read 146 from queue
-[35mConsumer[0m: Read 147 from queue
-[35mConsumer[0m: Read 148 from queue
-[35mConsumer[0m: Read 149 from queue
-[35mConsumer[0m: Read 150 from queue
-[35mConsumer[0m: Read 151 from queue
-[35mConsumer[0m: Read 152 from queue
-[35mConsumer[0m: Read 153 from queue
-[35mConsumer[0m: Read 154 from queue
-[35mConsumer[0m: Read 155 from queue
-[35mConsumer[0m: Read 156 from queue
-[35mConsumer[0m: Read 157 from queue
-[35mConsumer[0m: Read 158 from queue
-[35mConsumer[0m: Read 159 from queue
-[35mConsumer[0m: Read 160 from queue
-[35mConsumer[0m: Read 161 from queue
-[35mConsumer[0m: Read 162 from queue
-[35mConsumer[0m: Read 163 from queue
-[35mConsumer[0m: Read 164 from queue
-[35mConsumer[0m: Read 165 from queue
-[35mConsumer[0m: Read 166 from queue
-[35mConsumer[0m: Read 167 from queue
-[35mConsumer[0m: Read 168 from queue
-[35mConsumer[0m: Read 169 from queue
-[35mConsumer[0m: Read 170 from queue
-[35mConsumer[0m: Read 171 from queue
-[35mConsumer[0m: Read 172 from queue
-[35mConsumer[0m: Read 173 from queue
-[35mConsumer[0m: Read 174 from queue
-[35mConsumer[0m: Read 175 from queue
-[35mConsumer[0m: Read 176 from queue
-[35mConsumer[0m: Read 177 from queue
-[35mConsumer[0m: Read 178 from queue
-[35mConsumer[0m: Read 179 from queue
-[35mConsumer[0m: Read 180 from queue
-[35mConsumer[0m: Read 181 from queue
-[35mConsumer[0m: Read 182 from queue
-[35mProducer[0m: Producer sent all messages to consumer
-[35mConsumer[0m: Read 183 from queue
-[35mConsumer[0m: Read 184 from queue
-[35mConsumer[0m: Read 185 from queue
-[35mConsumer[0m: Read 186 from queue
-[35mConsumer[0m: Read 187 from queue
-[35mConsumer[0m: Read 188 from queue
-[35mConsumer[0m: Read 189 from queue
-[35mConsumer[0m: Read 190 from queue
-[35mConsumer[0m: Read 191 from queue
-[35mConsumer[0m: Read 192 from queue
-[35mConsumer[0m: Read 193 from queue
-[35mConsumer[0m: Read 194 from queue
-[35mConsumer[0m: Read 195 from queue
-[35mConsumer[0m: Read 196 from queue
-[35mConsumer[0m: Read 197 from queue
-[35mConsumer[0m: Read 198 from queue
-[35mConsumer[0m: Read 199 from queue
diff --git a/scripts/model_output/sonata-simulator/examples/sealing.txt b/scripts/model_output/sonata-simulator/examples/sealing.txt
deleted file mode 100644
index f6d7fd9..0000000
--- a/scripts/model_output/sonata-simulator/examples/sealing.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-[35mIdentifier service[0m: Allocated identifier, sealed capability: 0x109410 (v:1 0x109410-0x109420 l:0x10 o:0xb p: G RWcgm- -- ---)
-unsealed capability: 0x109418 (v:1 0x109418-0x109420 l:0x8 o:0x0 p: G RWcgm- -- ---)
-[35mCaller compartment[0m: Allocated identifier to hold the value 42: 0x109410 (v:1 0x109410-0x109420 l:0x10 o:0xb p: G RWcgm- -- ---)
-[35mCaller compartment[0m: Value is 42
-[35mCaller compartment[0m: Dangling pointer: 0x109410 (v:0 0x109410-0x109420 l:0x10 o:0xb p: G RWcgm- -- ---)
diff --git a/scripts/model_output/sonata-simulator/examples/temporal_safety.txt b/scripts/model_output/sonata-simulator/examples/temporal_safety.txt
deleted file mode 100644
index 37f990e..0000000
--- a/scripts/model_output/sonata-simulator/examples/temporal_safety.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-[35mAllocating compartment[0m: ----- Simple Case -----
-[35mAllocating compartment[0m: Allocated: 0x109910 (v:1 0x109910-0x109940 l:0x30 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Use after free: 0x109910 (v:0 0x109910-0x109940 l:0x30 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: ----- Sub object -----
-[35mAllocating compartment[0m: Allocated : 0x109948 (v:1 0x109948-0x1099b0 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x109961 (v:1 0x109961-0x109993 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 3984
-[35mAllocating compartment[0m: After free of sub object
-[35mAllocating compartment[0m: Allocated : 0x109948 (v:1 0x109948-0x1099b0 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x109961 (v:1 0x109961-0x109993 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 3984
-[35mAllocating compartment[0m: After free of allocation
-[35mAllocating compartment[0m: Allocated : 0x109948 (v:0 0x109948-0x1099b0 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x109961 (v:0 0x109961-0x109993 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 4096
-[35mAllocating compartment[0m: ----- Sub object with a claim -----
-[35mAllocating compartment[0m: Allocated : 0x1099b8 (v:1 0x1099b8-0x109a20 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x1099d1 (v:1 0x1099d1-0x109a03 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 3984
-[35mAllocating compartment[0m: heap quota after claim: 3968
-[35mAllocating compartment[0m: After free of allocation
-[35mAllocating compartment[0m: Allocated : 0x1099b8 (v:1 0x1099b8-0x109a20 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x1099d1 (v:1 0x1099d1-0x109a03 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 3968
-[35mAllocating compartment[0m: After free of sub object
-[35mAllocating compartment[0m: Allocated : 0x1099b8 (v:0 0x1099b8-0x109a20 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x1099d1 (v:0 0x1099d1-0x109a03 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 4096
-[35mAllocating compartment[0m: ----- Sub object with a fast claim -----
-[35mAllocating compartment[0m: Allocated : 0x109a38 (v:1 0x109a38-0x109aa0 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x109a51 (v:1 0x109a51-0x109a83 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 3984
-[35mAllocating compartment[0m: After free
-[35mAllocating compartment[0m: Allocated : 0x109a38 (v:0 0x109a38-0x109aa0 l:0x68 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: Sub Object: 0x109a51 (v:0 0x109a51-0x109a83 l:0x32 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 4096
-[35mAllocating compartment[0m: ----- Claim in another compartment -----
-[35mAllocating compartment[0m: Allocated : 0x109aa8 (v:1 0x109aa8-0x109ab8 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 4072
-[35mClaimant compartment[0m: Initial quota: 4096
-[35mClaimant compartment[0m: Make Claim : 0x109aa8 (v:1 0x109aa8-0x109ab8 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mClaimant compartment[0m: heap quota: 4056
-[35mAllocating compartment[0m: After free: 0x109aa8 (v:1 0x109aa8-0x109ab8 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: heap quota: 4096
-[35mClaimant compartment[0m: Show Claim : 0x109aa8 (v:1 0x109aa8-0x109ab8 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mClaimant compartment[0m: Initial quota: 4056
-[35mClaimant compartment[0m: Make Claim : 0x109ad0 (v:1 0x109ad0-0x109ae0 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mClaimant compartment[0m: heap quota: 4056
-[35mAllocating compartment[0m: After make claim
-[35mAllocating compartment[0m: x: 0x109aa8 (v:0 0x109aa8-0x109ab8 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mAllocating compartment[0m: y: 0x109ad0 (v:1 0x109ad0-0x109ae0 l:0x10 o:0x0 p: G RWcgm- -- ---)
-[35mClaimant compartment[0m: Show Claim : 0x109ad0 (v:1 0x109ad0-0x109ae0 l:0x10 o:0x0 p: G RWcgm- -- ---)
diff --git a/scripts/run-flute.sh b/scripts/run-flute.sh
deleted file mode 100755
index 107c9e3..0000000
--- a/scripts/run-flute.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-if [ -z "${FLUTE_BUILD}" ] ; then
- echo The FLUTE_BUILD environment variable should be set to the flute build directory
- exit 0
-fi
-# This script depends on non-portable GNU extensions, so prefer the g-prefixed
-# versions if they exist
-TAIL=tail
-if which gtail ; then TAIL=gtail ; fi
-HEAD=head
-if which ghead ; then HEAD=ghead ; fi
-PASTE=paste
-if which gpaste ; then PASTE=gpaste ; fi
-echo Using ${TAIL}, ${HEAD}, and ${PASTE}
-
-if [ ! -f tail.hex ] ; then
- for I in $(seq 0 32768) ; do
- echo 00000000 >> tail.hex
- done
-fi
-
-${FLUTE_BUILD}/../../Tests/elf_to_hex/elf_to_hex $1 Mem.hex
-
-awk '{print substr($0,33,8); print substr($0,0,8)}' Mem.hex > 1u-0.hex
-awk '{print substr($0,41,8); print substr($0,9,8)}' Mem.hex > 0u-0.hex
-awk '{print substr($0,49,8); print substr($0,17,8)}' Mem.hex > 1l-0.hex
-awk '{print substr($0,57,8); print substr($0,25,8)}' Mem.hex > 0l-0.hex
-
-${TAIL} -n +3 1u-0.hex > 1u-1.hex
-${TAIL} -n +3 0u-0.hex > 0u-1.hex
-${TAIL} -n +3 1l-0.hex > 1l-1.hex
-${TAIL} -n +3 0l-0.hex > 0l-1.hex
-
-${HEAD} -n -4 1u-1.hex > 1u-2.hex
-${HEAD} -n -4 0u-1.hex > 0u-2.hex
-${HEAD} -n -4 1l-1.hex > 1l-2.hex
-${HEAD} -n -4 0l-1.hex > 0l-2.hex
-
-${PASTE} -d \\n 1l-2.hex 1u-2.hex > 1-0.hex
-${PASTE} -d \\n 0l-2.hex 0u-2.hex > 0-0.hex
-
-cat 1-0.hex tail.hex | ${HEAD} -n 32768 > Mem-TCM-1.hex
-cat 0-0.hex tail.hex | ${HEAD} -n 32768 > Mem-TCM-0.hex
-
-${FLUTE_BUILD}/exe_HW_sim +tohost > /dev/null
diff --git a/scripts/run-sonata-1.0.sh b/scripts/run-sonata-1.0.sh
new file mode 100755
index 0000000..d54a780
--- /dev/null
+++ b/scripts/run-sonata-1.0.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+set -e
+
+FIRMWARE_ELF=$1
+
+SCRIPT_DIRECTORY="$(dirname "$(realpath "$0")")"
+. ${SCRIPT_DIRECTORY}/includes/helper_find_llvm_install.sh
+
+STRIP=$(find_llvm_tool_required llvm-strip)
+
+if ! command -v uf2conv > /dev/null ; then
+ echo "uf2conv not found. On macOS / Linux systems with Python3 installed, you can install it with:"
+ echo "python3 -m pip install --pre -U git+https://github.com/makerdiary/uf2utils.git@main"
+ exit 1
+fi
+
+# Strip the ELF file
+${STRIP} ${FIRMWARE_ELF} -o ${FIRMWARE_ELF}.strip
+# Convert the stripped elf to a UF2 (Microsoft USB Flashing Format) file
+uf2conv ${FIRMWARE_ELF}.strip -b0x00000000 -f0x6CE29E60 -co ${FIRMWARE_ELF}.slot1.uf2
+uf2conv ${FIRMWARE_ELF}.strip -b0x10000000 -f0x6CE29E60 -co ${FIRMWARE_ELF}.slot2.uf2
+uf2conv ${FIRMWARE_ELF}.strip -b0x20000000 -f0x6CE29E60 -co ${FIRMWARE_ELF}.slot3.uf2
+
+
+# Try to copy the firmware to the SONATA drive, if we can find one.
+try_copy()
+{
+ if [ -f $1/SONATA/OPTIONS.TXT ] ; then
+ cp ${FIRMWARE_ELF}.slot1.uf2 $1/SONATA/firmware.uf2
+ echo "Firmware copied to $1/SONATA/"
+ exit
+ fi
+}
+
+# Try some common mount points
+try_copy /Volumes/
+try_copy /run/media/$USER/
+try_copy /run/media/
+try_copy /mnt/
+
+cp ${FIRMWARE_ELF}.slot1.uf2 firmware.uf2
+
+echo "Please copy $(pwd)/firmware.uf2 to the SONATA drive to load."
diff --git a/scripts/run-sonata-sim.sh b/scripts/run-sonata-sim.sh
index 1b4c1a3..6367b69 100755
--- a/scripts/run-sonata-sim.sh
+++ b/scripts/run-sonata-sim.sh
@@ -4,7 +4,7 @@
# Specify the default environment variables if they haven't been already.
: "${SONATA_SIMULATOR:=/cheriot-tools/bin/sonata_simulator}"
-: "${SONATA_SIMULATOR_BOOT_STUB:=/cheriot-tools/elf/sonata_simulator_boot_stub}"
+: "${SONATA_SIMULATOR_BOOT_STUB:=/cheriot-tools/elf/sonata_simulator_hyperram_boot_stub}"
: "${SONATA_SIMULATOR_UART_LOG=uart0.log}"
if [ -z "$1" ] ; then
@@ -25,27 +25,14 @@
# Remove old uart log
rm -f "${SONATA_SIMULATOR_UART_LOG}"
-# If a second argument is provided, check content of UART log.
-if [ -n "$2" ] ; then
- # Run the simulator in the background.
- ${SONATA_SIMULATOR} -E "${SONATA_SIMULATOR_BOOT_STUB}" -E "$1" &
- LOOP_TRACKER=0
- while (( LOOP_TRACKER <= 60 ))
- do
- sleep 1s
- # Returns 0 if found and 1 if not.
- MATCH_FOUND=$(grep -q -F -f "$2" "${SONATA_SIMULATOR_UART_LOG}"; echo $?)
- if (( MATCH_FOUND == 0 )) ; then
- # Match was found so exit with success
- pkill -P $$
- exit 0
- fi
- LOOP_TRACKER=$((LOOP_TRACKER+1))
- done
- # Timeout was hit so no success.
- pkill -P $$
+if ! ${SONATA_SIMULATOR} -E "${SONATA_SIMULATOR_BOOT_STUB}" -E "$1"; then
+ echo "Simulator exited with failure! UART output:"
+ cat "${SONATA_SIMULATOR_UART_LOG}"
exit 4
-else
- # If there is no second argument, run simulator in foreground.
- ${SONATA_SIMULATOR} -E "${SONATA_SIMULATOR_BOOT_STUB}" -E "$1"
+fi
+
+# Check to see if the output indicates failure
+if grep -i failure "${SONATA_SIMULATOR_UART_LOG}"; then
+ echo "Log output contained 'failure'"
+ exit 5
fi
diff --git a/scripts/run-sonata.sh b/scripts/run-sonata.sh
index fc4238f..86a6236 100755
--- a/scripts/run-sonata.sh
+++ b/scripts/run-sonata.sh
@@ -9,10 +9,10 @@
OBJCOPY=$(find_llvm_tool_required llvm-objcopy)
-command -v uf2conv > /dev/null
-if [ ! $? ] ; then
+if ! command -v uf2conv > /dev/null ; then
echo "uf2conv not found. On macOS / Linux systems with Python3 installed, you can install it with:"
echo "python3 -m pip install --pre -U git+https://github.com/makerdiary/uf2utils.git@main"
+ exit 1
fi
# Convert the ELF file to a binary file
diff --git a/scripts/run_clang_tidy_format.sh b/scripts/run_clang_tidy_format.sh
index aecda51..2bce847 100755
--- a/scripts/run_clang_tidy_format.sh
+++ b/scripts/run_clang_tidy_format.sh
@@ -26,7 +26,7 @@
else
PARALLEL_JOBS=$(sysctl -n kern.smp.cpus)
fi
-DIRECTORIES="sdk tests examples"
+DIRECTORIES="sdk tests examples tests.extra"
# Standard headers should be included once we move to a clang-tidy that
# supports NOLINTBEGIN to disable specific checks over a whole file.
# In particular, modernize-redundant-void-arg should be disabled in any header
@@ -35,24 +35,23 @@
# FreeRTOS-Compat headers follow FreeRTOS naming conventions and should be
# excluded for now. Eventually they should be included for everything except
# the identifier naming checks.
-HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v setjmp.h | grep -v unwind.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat)
+HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v setjmp.h | grep -v unwind.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat | grep -v sunburst/v0.2)
SOURCES=$(find ${DIRECTORIES} -name '*.cc' | grep -v /build/ | grep -v third_party | grep -v arith64.c)
echo Headers: ${HEADERS}
echo Sources: ${SOURCES}
-rm -f tidy-*.fail
+${CLANG_FORMAT} -i ${HEADERS} ${SOURCES}
+if ! git diff --exit-code ${HEADERS} ${SOURCES} ; then
+ echo clang-format applied changes
+ exit 1
+fi
+
+rm -f tidy.fail-*
# sh syntax is -c "string" [name [args ...]], so "tidy" here is the name and not included in "$@"
-echo ${HEADERS} ${SOURCES} | xargs -P${PARALLEL_JOBS} -n5 sh -c "${CLANG_TIDY} -export-fixes=\$(mktemp -p. tidy.fail-XXXX) \$@" tidy
+echo ${HEADERS} ${SOURCES} | xargs -P${PARALLEL_JOBS} -n1 sh -c "${CLANG_TIDY} --extra-arg=-DCLANG_TIDY -export-fixes=\$(mktemp -p. tidy.fail-XXXX) \$@" tidy
if [ $(find . -maxdepth 1 -name 'tidy.fail-*' -size +0 | wc -l) -gt 0 ] ; then
# clang-tidy put non-empty output in one of the tidy-*.fail files
cat tidy.fail-*
exit 1
fi
-
-${CLANG_FORMAT} -i ${HEADERS} ${SOURCES}
-if git diff --exit-code ${HEADERS} ${SOURCES} ; then
- exit 0
-fi
-echo clang-format applied changes
-exit 1
diff --git a/sdk/boards/flute-debug-uart.json b/sdk/boards/flute-debug-uart.json
deleted file mode 100644
index b0dd865..0000000
--- a/sdk/boards/flute-debug-uart.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "devices" :
- {
- "clint" : {
- "start" : 0x2000000,
- "length" : 0x10000
- },
- "plic" : {
- "start" : 0xc000000,
- "length" : 0x400000
- },
- "uart" : {
- "start" : 0x10000100,
- "end" : 0x10000200
- },
- "ethernet" : {
- "start" : 0x10000100,
- "end" : 0x10000200
- },
- "shadow" : {
- "start" : 0x40000000,
- "end" : 0x40001000
- },
- "shadowctrl" : {
- "start" : 0x40001000,
- "end" : 0x40001028
- }
- },
- "instruction_memory" : {
- "start" : 0x80000000,
- "end" : 0x80040000
- },
- "heap" : {
- "end" : 0x80040000
- },
- "revoker" : "hardware",
- "stack_high_water_mark" : true,
- "driver_includes" : [
- "../include/platform/flute",
- "../include/platform/generic-riscv"
- ],
- "defines" : [
- "FLUTE",
- "FLUTE_SHADOW_BASE=0x40000000U",
- "FLUTE_SHADOW_SIZE=0x1000U"
- ],
- "timer_hz" : 40000,
- "tickrate_hz" : 10,
- "simulator" : "${sdk}/../scripts/run-flute.sh",
- "simulation" : true
-}
-
diff --git a/sdk/boards/flute-no-revoker.json b/sdk/boards/flute-no-revoker.json
deleted file mode 100644
index aca256b..0000000
--- a/sdk/boards/flute-no-revoker.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "devices" :
- {
- "clint" : {
- "start" : 0x2000000,
- "length" : 0x10000
- },
- "plic" : {
- "start" : 0xc000000,
- "length" : 0x400000
- },
- "uart" : {
- "start" : 0x10000100,
- "end" : 0x10000200
- },
- "shadow" : {
- "start" : 0x40000000,
- "end" : 0x40001000
- }
- },
- "instruction_memory" : {
- "start" : 0x80000000,
- "end" : 0x80040000
- },
- "heap" : {
- "end" : 0x80040000
- },
- "defines" : [
- "FLUTE",
- "FLUTE_SHADOW_BASE=0x40000000U",
- "FLUTE_SHADOW_SIZE=0x1000U"
- ],
- "stack_high_water_mark" : false,
- "driver_includes" : [
- "../include/platform/flute",
- "../include/platform/generic-riscv"
- ],
- "timer_hz" : 40000,
- "tickrate_hz" : 10,
- "simulator" : "${sdk}/../scripts/run-flute.sh",
- "simulation" : true
-}
-
diff --git a/sdk/boards/flute-software-revoker.json b/sdk/boards/flute-software-revoker.json
deleted file mode 100644
index 0968fd2..0000000
--- a/sdk/boards/flute-software-revoker.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "devices" :
- {
- "clint" : {
- "start" : 0x2000000,
- "length" : 0x10000
- },
- "plic" : {
- "start" : 0xc000000,
- "length" : 0x400000
- },
- "uart" : {
- "start" : 0x10000100,
- "end" : 0x10000200
- },
- "shadow" : {
- "start" : 0x40000000,
- "end" : 0x40001000
- }
- },
- "instruction_memory" : {
- "start" : 0x80000000,
- "end" : 0x80040000
- },
- "heap" : {
- "end" : 0x80040000
- },
- "stack_high_water_mark" : false,
- "driver_includes" : [
- "../include/platform/flute",
- "../include/platform/generic-riscv"
- ],
- "defines" : [
- "FLUTE",
- "FLUTE_SHADOW_BASE=0x40000000U",
- "FLUTE_SHADOW_SIZE=0x1000U"
- ],
- "timer_hz" : 40000,
- "tickrate_hz" : 10,
- "revoker" : "software",
- "simulator" : "${sdk}/../scripts/run-flute.sh",
- "simulation" : true
-}
-
diff --git a/sdk/boards/flute.json b/sdk/boards/flute.json
deleted file mode 100644
index 42586f5..0000000
--- a/sdk/boards/flute.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "devices" :
- {
- "clint" : {
- "start" : 0x2000000,
- "length" : 0x10000
- },
- "plic" : {
- "start" : 0xc000000,
- "length" : 0x400000
- },
- "uart" : {
- "start" : 0x10000000,
- "end" : 0x10000100
- },
- "ethernet" : {
- "start" : 0x10000100,
- "end" : 0x10000200
- },
- "shadow" : {
- "start" : 0x40000000,
- "end" : 0x40001000
- },
- "shadowctrl" : {
- "start" : 0x40001000,
- "end" : 0x40001028
- }
- },
- "instruction_memory" : {
- "start" : 0x80000000,
- "end" : 0x80040000
- },
- "heap" : {
- "end" : 0x80040000
- },
- "driver_includes" : [
- "../include/platform/flute",
- "../include/platform/generic-riscv"
- ],
- "defines" : [
- "FLUTE",
- "FLUTE_SHADOW_BASE=0x40000000U",
- "FLUTE_SHADOW_SIZE=0x1000U"
- ],
- "timer_hz" : 40000,
- "tickrate_hz" : 10,
- "revoker" : "hardware",
- "simulator" : "${sdk}/../scripts/run-flute.sh",
- "simulation" : true
-}
-
diff --git a/sdk/boards/ibex-arty-a7-100.json b/sdk/boards/ibex-arty-a7-100.json
deleted file mode 100644
index db54172..0000000
--- a/sdk/boards/ibex-arty-a7-100.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "devices": {
- "clint": {
- "start": 0x14001000,
- "length": 0x1000
- },
- "plic": {
- "start": 0x10000000,
- "end": 0x10400000
- },
- "revoker": {
- "start": 0x14000000,
- "length": 0x1000
- },
- "uart": {
- "start": 0x8f00b000,
- "end": 0x8f00b100
- },
- "shadow" : {
- "start": 0x200fe000,
- "length": 0x2000
- },
- "gpio_led0" : {
- "start": 0x8f00f000,
- "length": 0x800
- },
- "kunyan_ethernet": {
- "start": 0x14004000,
- "end": 0x14008000
- }
- },
- "instruction_memory": {
- "start": 0x20040000,
- "end": 0x20080000
- },
- "heap": {
- "end": 0x20080000
- },
- "interrupts": [
- {
- "name": "RevokerInterrupt",
- "number": 1,
- "priority": 2
- },
- {
- "name": "UARTInterrupt",
- "number": 2,
- "priority": 3,
- "edge_triggered": true
- },
- {
- "name": "EthernetTransmitInterrupt",
- "number": 3,
- "priority": 3
- },
- {
- "name": "EthernetReceiveInterrupt",
- "number": 4,
- "priority": 3
- }
- ],
- "defines" : [
- "IBEX",
- "IBEX_SAFE"
- ],
- "driver_includes" : [
- "${sdk}/include/platform/arty-a7",
- "${sdk}/include/platform/synopsis",
- "${sdk}/include/platform/ibex",
- "${sdk}/include/platform/generic-riscv"
- ],
- "timer_hz" : 33000000,
- "tickrate_hz" : 100,
- "revoker" : "hardware",
- "stack_high_water_mark" : true
-}
diff --git a/sdk/boards/ibex-arty-a7-100.patch b/sdk/boards/ibex-arty-a7-100.patch
new file mode 100644
index 0000000..e9ccd3d
--- /dev/null
+++ b/sdk/boards/ibex-arty-a7-100.patch
@@ -0,0 +1,77 @@
+{
+ "base": "ibex-safe-simulator",
+ "patch": [
+ {
+ "op": "add",
+ "path": "/devices/gpio_led0",
+ "value": {
+ "start": 0x8f00f000,
+ "length": 0x800
+ }
+ },
+ {
+ "op": "add",
+ "path": "/kunyan_ethernet",
+ "value": {
+ "start": 0x14004000,
+ "end": 0x14008000
+ }
+ },
+ {
+ "op": "add",
+ "path": "/interrupts/1",
+ "value": {
+ "name": "UARTInterrupt",
+ "number": 2,
+ "priority": 3,
+ "edge_triggered": true
+ }
+ },
+ {
+ "op": "add",
+ "path": "/interrupts/2",
+ "value": {
+ "name": "EthernetTransmitInterrupt",
+ "number": 3,
+ "priority": 3
+ }
+ },
+ {
+ "op": "add",
+ "path": "/interrupts/3",
+ "value": {
+ "name": "EthernetReceiveInterrupt",
+ "number": 4,
+ "priority": 3
+ }
+ },
+ {
+ "op": "add",
+ "path": "/driver_includes/0",
+ "value": "${sdk}/include/platform/synopsis"
+ },
+ {
+ "op": "add",
+ "path": "/driver_includes/0",
+ "value": "${sdk}/include/platform/arty-a7"
+ },
+ {
+ "op": "replace",
+ "path": "/timer_hz",
+ "value": 33000000
+ },
+ {
+ "op": "replace",
+ "path": "/tickrate_hz",
+ "value": 100
+ },
+ {
+ "op": "remove",
+ "path": "/simulation"
+ },
+ {
+ "op": "remove",
+ "path": "/run_command"
+ }
+ ]
+}
diff --git a/sdk/boards/ibex-safe-simulator.json b/sdk/boards/ibex-safe-simulator.json
index 87ac417..cdc1920 100644
--- a/sdk/boards/ibex-safe-simulator.json
+++ b/sdk/boards/ibex-safe-simulator.json
@@ -32,7 +32,8 @@
{
"name": "RevokerInterrupt",
"number": 1,
- "priority": 2
+ "priority": 2,
+ "edge_triggered": true
}
],
"defines" : [
@@ -48,5 +49,5 @@
"revoker" : "hardware",
"stack_high_water_mark" : true,
"simulation": true,
- "simulator" : "${sdk}/../scripts/run-ibex-safe-sim.sh"
+ "run_command" : "${sdk}/../scripts/run-ibex-safe-sim.sh"
}
diff --git a/sdk/boards/sail.json b/sdk/boards/sail.json
index 95e1689..fecd0b7 100644
--- a/sdk/boards/sail.json
+++ b/sdk/boards/sail.json
@@ -38,6 +38,6 @@
"tickrate_hz" : 10,
"revoker" : "software",
"stack_high_water_mark" : true,
- "simulator" : "cheriot_sim",
+ "run_command" : "cheriot_sim",
"simulation": true
}
diff --git a/sdk/boards/sonata-0.2.json b/sdk/boards/sonata-0.2.json
index 9747b93..3b78434 100644
--- a/sdk/boards/sonata-0.2.json
+++ b/sdk/boards/sonata-0.2.json
@@ -73,7 +73,8 @@
"SUNBURST_SHADOW_SIZE=0x4000",
"DEFAULT_UART_BAUD_RATE=115200",
"ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM=1",
- "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1"
+ "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1",
+ "CHERIOT_NO_SAIL_83"
],
"driver_includes" : [
"../include/platform/sunburst/v0.2",
@@ -84,6 +85,6 @@
"tickrate_hz" : 100,
"revoker" : "software",
"stack_high_water_mark" : true,
- "simulator" : "${sdk}/../scripts/run-sonata.sh",
+ "run_command" : "${sdk}/../scripts/run-sonata.sh",
"simulation": false
}
diff --git a/sdk/boards/sonata-1.0.patch b/sdk/boards/sonata-1.0.patch
new file mode 100644
index 0000000..8521866
--- /dev/null
+++ b/sdk/boards/sonata-1.0.patch
@@ -0,0 +1,15 @@
+{
+ "base": "sonata-1.1",
+ "patch": [
+ {
+ "op": "add",
+ "path": "/defines/0",
+ "value": "CHERIOT_NO_SAIL_83"
+ },
+ {
+ "op": "add",
+ "path": "/cxflags",
+ "value": "-mllvm -enable-machine-outliner=never"
+ }
+ ]
+}
diff --git a/sdk/boards/sonata-prerelease.json b/sdk/boards/sonata-1.1.json
similarity index 75%
rename from sdk/boards/sonata-prerelease.json
rename to sdk/boards/sonata-1.1.json
index bd07567..1dd81f2 100644
--- a/sdk/boards/sonata-prerelease.json
+++ b/sdk/boards/sonata-1.1.json
@@ -12,6 +12,14 @@
"start" : 0x80001030,
"end" : 0x80001038
},
+ "pinmux_pins_sinks": {
+ "start" : 0x80005000,
+ "length": 0x00000055
+ },
+ "pinmux_block_sinks": {
+ "start" : 0x80005800,
+ "length": 0x00000046
+ },
"rgbled" : {
"start" : 0x80009000,
"end" : 0x80009020
@@ -40,6 +48,26 @@
"start" : 0x80102000,
"end" : 0x80102034
},
+ "spi_lcd": {
+ "start" : 0x80300000,
+ "end" : 0x80301000
+ },
+ "spi_ethmac": {
+ "start" : 0x80301000,
+ "end" : 0x80302000
+ },
+ "spi0": {
+ "start" : 0x80302000,
+ "end" : 0x80303000
+ },
+ "spi1": {
+ "start" : 0x80303000,
+ "end" : 0x80304000
+ },
+ "spi2": {
+ "start" : 0x80304000,
+ "end" : 0x80305000
+ },
"usbdev": {
"start" : 0x80400000,
"end" : 0x80401000
@@ -49,9 +77,13 @@
"end" : 0x88400000
}
},
+ "data_memory": {
+ "start" : 0x00101000,
+ "end" : 0x00120000
+ },
"instruction_memory": {
- "start": 0x00101000,
- "end": 0x00120000
+ "start": 0x40000000,
+ "end": 0x40100000
},
"heap": {
"end": 0x00120000
@@ -63,7 +95,8 @@
"SUNBURST_SHADOW_BASE=0x30000000",
"SUNBURST_SHADOW_SIZE=0x800",
"ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM=1",
- "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1"
+ "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1",
+ "STDERR_TO_STDOUT=1"
],
"driver_includes" : [
"../include/platform/sunburst",
@@ -74,13 +107,14 @@
"tickrate_hz" : 100,
"revoker" : "hardware",
"stack_high_water_mark" : true,
- "simulator" : "${sdk}/../scripts/run-sonata.sh",
+ "run_command" : "${sdk}/../scripts/run-sonata-1.0.sh",
"simulation": false,
"interrupts": [
{
"name": "RevokerInterrupt",
"number": 1,
- "priority": 2
+ "priority": 2,
+ "edge_triggered": true
},
{
"name": "EthernetInterrupt",
diff --git a/sdk/boards/sonata-prerelease.patch b/sdk/boards/sonata-prerelease.patch
new file mode 100644
index 0000000..931cd7e
--- /dev/null
+++ b/sdk/boards/sonata-prerelease.patch
@@ -0,0 +1,4 @@
+{
+ "base": "sonata-1.1",
+ "patch": [ ]
+}
diff --git a/sdk/boards/sonata-simulator.json b/sdk/boards/sonata-simulator.json
deleted file mode 100644
index c2073f7..0000000
--- a/sdk/boards/sonata-simulator.json
+++ /dev/null
@@ -1,142 +0,0 @@
-{
- "devices": {
- "shadow" : {
- "start" : 0x30000000,
- "end" : 0x30000800
- },
- "pwm": {
- "start" : 0x80001000,
- "length": 0x00001000
- },
- "rgbled" : {
- "start" : 0x80009000,
- "end" : 0x80009020
- },
- "revoker": {
- "start" : 0x8000A000,
- "length": 0x00001000
- },
- "adc": {
- "start" : 0x8000B000,
- "length": 0x00001000
- },
- "clint": {
- "start" : 0x80040000,
- "end" : 0x80050000
- },
- "uart": {
- "start" : 0x80100000,
- "end" : 0x80100034
- },
- "uart1": {
- "start" : 0x80101000,
- "end" : 0x80101034
- },
- "uart2": {
- "start" : 0x80102000,
- "end" : 0x80102034
- },
- "usbdev": {
- "start" : 0x80400000,
- "end" : 0x80401000
- },
- "plic": {
- "start" : 0x88000000,
- "end" : 0x88400000
- }
- },
- "instruction_memory": {
- "start": 0x00101000,
- "end": 0x00120000
- },
- "heap": {
- "end": 0x00120000
- },
- "revokable_memory_start": 0x00100000,
- "defines" : [
- "IBEX",
- "SUNBURST",
- "SUNBURST_SHADOW_BASE=0x30000000",
- "SUNBURST_SHADOW_SIZE=0x800",
- "ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM=1",
- "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1"
- ],
- "driver_includes" : [
- "../include/platform/sunburst",
- "../include/platform/ibex",
- "../include/platform/generic-riscv"
- ],
- "timer_hz" : 40000000,
- "tickrate_hz" : 100,
- "revoker" : "hardware",
- "stack_high_water_mark" : true,
- "simulator" : "${sdk}/../scripts/run-sonata-sim.sh",
- "simulation": true,
- "interrupts": [
- {
- "name": "RevokerInterrupt",
- "number": 1,
- "priority": 2
- },
- {
- "name": "EthernetInterrupt",
- "number": 2,
- "priority": 3
- },
- {
- "name": "UsbDevInterrupt",
- "number": 3,
- "priority": 3
- },
- {
- "name": "Uart0Interrupt",
- "number": 8,
- "priority": 3
- },
- {
- "name": "Uart1Interrupt",
- "number": 9,
- "priority": 3
- },
- {
- "name": "Uart2Interrupt",
- "number": 10,
- "priority": 3
- },
- {
- "name": "I2c0Interrupt",
- "number": 16,
- "priority": 3
- },
- {
- "name": "I2c1Interrupt",
- "number": 17,
- "priority": 3
- },
- {
- "name": "SpiLcdInterrupt",
- "number": 24,
- "priority": 3
- },
- {
- "name": "SpiEthmacInterrupt",
- "number": 25,
- "priority": 3
- },
- {
- "name": "Spi0Interrupt",
- "number": 26,
- "priority": 3
- },
- {
- "name": "Spi1Interrupt",
- "number": 27,
- "priority": 3
- },
- {
- "name": "Spi2Interrupt",
- "number": 28,
- "priority": 3
- }
- ]
-}
diff --git a/sdk/boards/sonata-simulator.patch b/sdk/boards/sonata-simulator.patch
new file mode 100644
index 0000000..353edbd
--- /dev/null
+++ b/sdk/boards/sonata-simulator.patch
@@ -0,0 +1,15 @@
+{
+ "base": "sonata-1.1",
+ "patch": [
+ {
+ "op": "replace",
+ "path": "/simulation",
+ "value": true
+ },
+ {
+ "op": "replace",
+ "path": "/run_command",
+ "value": "${sdk}/../scripts/run-sonata-sim.sh"
+ }
+ ]
+}
diff --git a/sdk/core/allocator/alloc.h b/sdk/core/allocator/alloc.h
index 9560be0..4260b82 100644
--- a/sdk/core/allocator/alloc.h
+++ b/sdk/core/allocator/alloc.h
@@ -157,19 +157,17 @@
{
template<auto F, typename D>
- concept Decoder = requires(D d)
- {
+ concept Decoder = requires(D d) {
{
F(d)
- } -> std::same_as<size_t>;
+ } -> std::same_as<size_t>;
};
template<auto F, typename D>
- concept Encoder = requires(size_t s)
- {
+ concept Encoder = requires(size_t s) {
{
F(s)
- } -> std::same_as<D>;
+ } -> std::same_as<D>;
};
/**
@@ -177,7 +175,7 @@
* a proxy for a pointer.
*/
template<typename T, typename D, bool Positive, auto Decode, auto Encode>
- requires Decoder<Decode, D> && Encoder<Encode, D>
+ requires Decoder<Decode, D> && Encoder<Encode, D>
class Proxy
{
CHERI::Capability<void> ctx;
@@ -286,8 +284,7 @@
* - Collected in a treebin ring, using either/both the TChunk linkages
* or/and the MChunk::ring links present in body().
*/
-struct __packed __aligned(MallocAlignment)
-MChunkHeader
+struct __packed __aligned(MallocAlignment) MChunkHeader
{
/**
* Each chunk has a 16-bit metadata field that is used to store a small
@@ -589,8 +586,7 @@
* feed an unsafe_remove'd MChunk to such a function or to simply build a new
* MChunk header in the heap.
*/
-class __packed __aligned(MallocAlignment)
-MChunk
+class __packed __aligned(MallocAlignment) MChunk
{
friend class MChunkAssertions;
friend class TChunk;
@@ -710,8 +706,7 @@
* Since we have enough room (large/tree chunks are at least 65 bytes), we just
* put full capabilities here, and the format probably won't change, ever.
*/
-class __packed __aligned(MallocAlignment)
-TChunk
+class __packed __aligned(MallocAlignment) TChunk
{
friend class TChunkAssertions;
friend class MState;
@@ -1505,7 +1500,7 @@
*/
static bool __always_inline capaligned_range_do(void *start,
size_t size,
- bool (*fn)(void **))
+ bool (*fn)(void **))
{
Debug::Assert((size & (sizeof(void *) - 1)) == 0,
"Cap range is not aligned");
@@ -2811,4 +2806,163 @@
{
ABORT();
}
+
+#if HEAP_RENDER
+ public:
+ /**
+ * "Render" the heap for debugging.
+ */
+ template<bool Asserts = true, bool Chatty = true>
+ void render()
+ {
+ using RenderDebug = ConditionalDebug<Chatty, "Allocator heap">;
+
+ size_t measuredAllocated = 0, measuredFree = 0, measuredQuarantined = 0;
+
+ auto toAddr = [](void *p) {
+ return static_cast<ptraddr_t>(CHERI::Capability{p}.address());
+ };
+
+ auto header =
+ static_cast<MChunkHeader *>(static_cast<void *>(heapStart));
+
+ RenderDebug::log("Dumping MState={} start={} end={}",
+ toAddr(this),
+ toAddr(heapStart),
+ heapStart.top());
+
+ while (toAddr(header) != heapStart.top())
+ {
+ RenderDebug::log(" header {}: size={} inuse={} pinuse={}",
+ toAddr(header),
+ header->size_get(),
+ header->isCurrInUse,
+ header->isPrevInUse);
+
+ if (!header->is_in_use())
+ {
+ measuredFree += header->size_get();
+
+ auto chunk = MChunk::from_header(header);
+ if (!ds::linked_list::is_singleton(&chunk->ring))
+ {
+ RenderDebug::log(
+ " free ring <{},{}>",
+ toAddr(MChunk::from_ring(chunk->ring.cell_prev())),
+ toAddr(MChunk::from_ring(chunk->ring.cell_next())));
+ }
+ else
+ {
+ RenderDebug::log(" free ring empty");
+ }
+
+ if (!is_small(header->size_get()))
+ {
+ auto t = TChunk::from_mchunk(chunk);
+
+ if (t->is_tree_ring())
+ {
+ RenderDebug::log(" tree ring, index={}", t->index);
+ }
+ else
+ {
+ RenderDebug::log(
+ " tree, index={} parent={} children=[{},{}]",
+ t->index,
+ toAddr(t->parent),
+ toAddr(t->child[0]),
+ toAddr(t->child[1]));
+ }
+ }
+ }
+ else
+ {
+ measuredAllocated += header->size_get();
+ }
+
+ header = header->cell_next();
+ }
+
+ auto showQuarantineRing = [&](ChunkFreeLink *&p) {
+ auto header = MChunkHeader::from_body(MChunk::from_ring(p));
+ RenderDebug::log(
+ " quarantined {} size={}", toAddr(header), header->size_get());
+ measuredQuarantined += header->size_get();
+ if constexpr (Asserts)
+ {
+ RenderDebug::invariant(ds::linked_list::is_well_formed(p),
+ "Quarantine list node malformed");
+ }
+ return false;
+ };
+
+ {
+ decltype(quarantinePendingRing)::Ix head = 0, tail = 0;
+
+ quarantinePendingRing.head_get(head);
+ quarantinePendingRing.tail_get(tail);
+ RenderDebug::log(" Quarantine status: empty={} head={} tail={}",
+ quarantinePendingRing.is_empty(),
+ head,
+ tail);
+ }
+
+ if (quarantineFinishedSentinel.is_empty())
+ {
+ RenderDebug::log(" finished ring empty");
+ }
+ else
+ {
+ RenderDebug::log(" finished ring <{},{}>:",
+ toAddr(quarantineFinishedSentinel.last()),
+ toAddr(quarantineFinishedSentinel.first()));
+ quarantine_finished_get()->search(showQuarantineRing);
+ }
+
+ for (size_t ix = 0; ix < QuarantineRings; ix++)
+ {
+ if (quarantinePendingChunks[ix].is_empty())
+ {
+ RenderDebug::log(
+ " index={} epoch={} empty", ix, quarantinePendingEpoch[ix]);
+ }
+ else
+ {
+ RenderDebug::log(
+ " index={} epoch={}:", ix, quarantinePendingEpoch[ix]);
+ quarantine_pending_get(ix)->search(showQuarantineRing);
+ }
+ }
+
+ auto measuredTotal = measuredAllocated + measuredFree;
+
+ RenderDebug::log(
+ "Sizes: alloc={} free={} (expect {}) quar={} (expect {}) "
+ "total={} (expect {})",
+ measuredAllocated,
+ measuredFree,
+ heapFreeSize,
+ measuredQuarantined,
+ heapQuarantineSize,
+ measuredTotal,
+ heapTotalSize);
+
+ if constexpr (Asserts)
+ {
+ RenderDebug::invariant(measuredFree == heapFreeSize,
+ "Bad accounting in free size: {} {}",
+ std::make_tuple(measuredFree, heapFreeSize));
+
+ RenderDebug::invariant(
+ measuredQuarantined == heapQuarantineSize,
+ "Bad accounting in quarantine size: {} {}",
+ std::make_tuple(measuredQuarantined, heapQuarantineSize));
+
+ RenderDebug::invariant(
+ measuredTotal == heapTotalSize,
+ "Bad accounting in total size: {} {}",
+ std::make_tuple(measuredTotal, heapTotalSize));
+ }
+ }
+#endif
};
diff --git a/sdk/core/allocator/alloc_config.h b/sdk/core/allocator/alloc_config.h
index 522ed8a..65af7af 100644
--- a/sdk/core/allocator/alloc_config.h
+++ b/sdk/core/allocator/alloc_config.h
@@ -34,5 +34,15 @@
#endif
;
-#define STACK_CHECK(expected) \
- StackUsageCheck<StackMode, expected, __PRETTY_FUNCTION__> stackCheck
+#if defined(__CHERIOT__) && (__CHERIOT__ >= 20250108)
+# define STACK_CHECK(expected) \
+ static_assert((expected) == __cheriot_minimum_stack__, \
+ "Explicit stack check does not match annotation!"); \
+ StackUsageCheck<StackMode, \
+ __cheriot_minimum_stack__, \
+ __PRETTY_FUNCTION__> \
+ stackCheck
+#else
+# define STACK_CHECK(expected) \
+ StackUsageCheck<StackMode, expected, __PRETTY_FUNCTION__> stackCheck
+#endif
diff --git a/sdk/core/allocator/main.cc b/sdk/core/allocator/main.cc
index bb21962..ad838be 100644
--- a/sdk/core/allocator/main.cc
+++ b/sdk/core/allocator/main.cc
@@ -187,11 +187,11 @@
*
*/
template<typename T = Revocation::Revoker>
- bool wait_for_background_revoker(
- Timeout *timeout,
- uint32_t epoch,
- LockGuard<decltype(lock)> &g,
- T &r = revoker) requires(Revocation::SupportsInterruptNotification<T>)
+ bool wait_for_background_revoker(Timeout *timeout,
+ uint32_t epoch,
+ LockGuard<decltype(lock)> &g,
+ T &r = revoker)
+ requires(Revocation::SupportsInterruptNotification<T>)
{
// Release the lock before sleeping
g.unlock();
@@ -212,19 +212,22 @@
*
*/
template<typename T = Revocation::Revoker>
- bool wait_for_background_revoker(
- Timeout *timeout,
- uint32_t epoch,
- LockGuard<decltype(lock)> &g,
- T &r = revoker) requires(!Revocation::SupportsInterruptNotification<T>)
+ bool wait_for_background_revoker(Timeout *timeout,
+ uint32_t epoch,
+ LockGuard<decltype(lock)> &g,
+ T &r = revoker)
+ requires(!Revocation::SupportsInterruptNotification<T>)
{
// Yield while until a revocation pass has finished.
- while (!revoker.has_revocation_finished_for_epoch<true>(epoch))
+ while (!revoker.has_revocation_finished_for_epoch(epoch))
{
// Release the lock before sleeping
g.unlock();
Timeout smallSleep{1};
- thread_sleep(&smallSleep);
+ if (thread_sleep(&smallSleep) < 0)
+ {
+ return false;
+ }
if (!reacquire_lock(timeout, g, smallSleep.elapsed))
{
return false;
@@ -313,7 +316,11 @@
// Sleep for a single tick.
g.unlock();
Timeout smallSleep{1};
- thread_sleep(&smallSleep);
+ if (thread_sleep(&smallSleep) < 0)
+ {
+ /* Unable to sleep; bail out */
+ return nullptr;
+ }
if (!reacquire_lock(timeout, g, smallSleep.elapsed))
{
return nullptr;
@@ -852,7 +859,7 @@
return cap->quota;
}
-__cheriot_minimum_stack(0xc0) void heap_quarantine_empty()
+__cheriot_minimum_stack(0xc0) int heap_quarantine_empty()
{
STACK_CHECK(0xc0);
LockGuard g{lock};
@@ -866,14 +873,16 @@
yield();
g.lock();
}
+
+ return 0;
}
-__cheriot_minimum_stack(0x210) void *heap_allocate(Timeout *timeout,
+__cheriot_minimum_stack(0x220) void *heap_allocate(Timeout *timeout,
SObj heapCapability,
size_t bytes,
uint32_t flags)
{
- STACK_CHECK(0x210);
+ STACK_CHECK(0x220);
if (!check_timeout_pointer(timeout))
{
return nullptr;
@@ -884,11 +893,6 @@
{
return nullptr;
}
- if (!check_pointer<PermissionSet{Permission::Load, Permission::Store}>(
- timeout))
- {
- return nullptr;
- }
// Use the default memory space.
return malloc_internal(bytes, std::move(g), cap, timeout, false, flags);
}
@@ -935,11 +939,8 @@
return heap_free_internal(heapCapability, rawPointer, false);
}
-__cheriot_minimum_stack(0x260) int heap_free(SObj heapCapability,
- void *rawPointer)
+int heap_free_nostackcheck(SObj heapCapability, void *rawPointer)
{
- // If this value changes, update `heap_can_free` as well.
- STACK_CHECK(0x260);
LockGuard g{lock};
int ret = heap_free_internal(heapCapability, rawPointer, true);
if (ret != 0)
@@ -958,9 +959,17 @@
return 0;
}
-__cheriot_minimum_stack(0x190) ssize_t heap_free_all(SObj heapCapability)
+__cheriot_minimum_stack(0x260) int heap_free(SObj heapCapability,
+ void *rawPointer)
{
- STACK_CHECK(0x190);
+ // If this value changes, update `heap_can_free` as well.
+ STACK_CHECK(0x260);
+ return heap_free_nostackcheck(heapCapability, rawPointer);
+}
+
+__cheriot_minimum_stack(0x1a0) ssize_t heap_free_all(SObj heapCapability)
+{
+ STACK_CHECK(0x1a0);
LockGuard g{lock};
auto *capability = malloc_capability_unseal(heapCapability);
if (capability == nullptr)
@@ -997,13 +1006,13 @@
return freed;
}
-__cheriot_minimum_stack(0x210) void *heap_allocate_array(Timeout *timeout,
+__cheriot_minimum_stack(0x220) void *heap_allocate_array(Timeout *timeout,
SObj heapCapability,
size_t nElements,
size_t elemSize,
uint32_t flags)
{
- STACK_CHECK(0x210);
+ STACK_CHECK(0x220);
if (!check_timeout_pointer(timeout))
{
return nullptr;
@@ -1175,16 +1184,24 @@
auto [sealed, obj] = allocate_sealed_unsealed(
timeout, heapCapability, key, sz, {Permission::Seal, Permission::Unseal});
{
+ /*
+ * Write the unsealed capability through the out parameter, while
+ * holding the allocator lock. That's a little heavy-handed, but it
+ * suffices to ensure that it won't be freed out from under us, so
+ * if it passes `check_pointer`, then the store won't trap.
+ */
LockGuard g{lock};
if (check_pointer<PermissionSet{
Permission::Store, Permission::LoadStoreCapability}>(unsealed))
{
*unsealed = obj;
- return sealed;
}
}
- heap_free(heapCapability, obj);
- return INVALID_SOBJ;
+ /*
+ * Regardless of whether we were able to store the unsealed pointer, return
+ * the sealed object.
+ */
+ return sealed;
}
__cheriot_minimum_stack(0x260) SObj token_sealed_alloc(Timeout *timeout,
@@ -1239,7 +1256,7 @@
// The key can't be revoked and so there is no race with the key going
// away after the check.
}
- return heap_free(heapCapability, unsealed);
+ return heap_free_nostackcheck(heapCapability, unsealed);
}
__cheriot_minimum_stack(0xf0) int token_obj_can_destroy(SObj heapCapability,
@@ -1268,3 +1285,11 @@
{
return gm->heapFreeSize;
}
+
+[[cheri::interrupt_state(disabled)]] int heap_render()
+{
+#if HEAP_RENDER
+ gm->render();
+#endif
+ return 0;
+}
diff --git a/sdk/core/allocator/revoker.h b/sdk/core/allocator/revoker.h
index e484b4f..38c9698 100644
--- a/sdk/core/allocator/revoker.h
+++ b/sdk/core/allocator/revoker.h
@@ -23,21 +23,22 @@
* provided by the board search.
*/
template<typename T>
- concept IsHardwareRevokerDevice = requires(T v, uint32_t epoch)
- {
- {v.init()};
+ concept IsHardwareRevokerDevice = requires(T v, uint32_t epoch) {
+ {
+ v.init()
+ };
{
v.system_epoch_get()
- } -> std::same_as<uint32_t>;
+ } -> std::same_as<uint32_t>;
{
v.template has_revocation_finished_for_epoch<true>(epoch)
- } -> std::same_as<uint32_t>;
+ } -> std::same_as<uint32_t>;
{
v.template has_revocation_finished_for_epoch<false>(epoch)
- } -> std::same_as<uint32_t>;
+ } -> std::same_as<uint32_t>;
{
v.system_bg_revoker_kick()
- } -> std::same_as<void>;
+ } -> std::same_as<void>;
};
/**
@@ -47,14 +48,12 @@
* timeout expired.
*/
template<typename T>
- concept SupportsInterruptNotification = requires(T v,
- Timeout *timeout,
- uint32_t epoch)
- {
- {
- v.wait_for_completion(timeout, epoch)
- } -> std::same_as<bool>;
- };
+ concept SupportsInterruptNotification =
+ requires(T v, Timeout *timeout, uint32_t epoch) {
+ {
+ v.wait_for_completion(timeout, epoch)
+ } -> std::same_as<bool>;
+ };
/**
* Class for interacting with the shadow bitmap. This bitmap controls the
@@ -275,7 +274,7 @@
size_t TCMBaseAddr,
template<typename, size_t>
typename Revoker>
- requires IsHardwareRevokerDevice<Revoker<WordT, TCMBaseAddr>>
+ requires IsHardwareRevokerDevice<Revoker<WordT, TCMBaseAddr>>
class HardwareAccelerator : public Bitmap<WordT, TCMBaseAddr>,
public Revoker<WordT, TCMBaseAddr>
{
@@ -389,7 +388,7 @@
// time that it's queried.
if ((current & 1) == 1)
{
- revoker_tick();
+ (void)revoker_tick();
current = *epoch;
}
// We want to know if current is greater than epoch, but current
@@ -415,7 +414,7 @@
/// Start revocation running.
void system_bg_revoker_kick()
{
- revoker_tick();
+ (void)revoker_tick();
}
};
diff --git a/sdk/core/allocator/software_revoker.h b/sdk/core/allocator/software_revoker.h
index 561e021..11066ca 100644
--- a/sdk/core/allocator/software_revoker.h
+++ b/sdk/core/allocator/software_revoker.h
@@ -6,10 +6,14 @@
/**
* Prod the software revoker to do some work. This does not do a complete
- * revocation pass, it will scan a region of memory and then return.
+ * revocation pass; it will scan a region of memory and then return.
+ *
+ * Returns 0 on success, a compartment invocation failure indication
+ * (-ENOTENOUGHSTACK, -ENOTENOUGHTRUSTEDSTACK) if it cannot be invoked, or
+ * possibly -ECOMPARTMENTFAIL if the software revoker compartment is damaged.
*/
[[cheri::interrupt_state(disabled)]] __cheri_compartment(
- "software_revoker") void revoker_tick();
+ "software_revoker") int revoker_tick();
/**
* Returns a read-only capability to the current revocation epoch. If the low
diff --git a/sdk/core/loader/boot.S b/sdk/core/loader/boot.S
index ae915cb..953db9e 100644
--- a/sdk/core/loader/boot.S
+++ b/sdk/core/loader/boot.S
@@ -65,7 +65,7 @@
la_abs s0, loader_entry_point
csetaddr cra, cra, s0
// Base and size of the GP of loader
- // Flute doesn't support unaligned loads, so we have to load the base as
+ // Old sails don't support unaligned loads, so we have to load the base as
// bytes
clbu s0, IMAGE_HEADER_LOADER_DATA_START_OFFSET+3(ca1)
sll s0, s0, 8
@@ -157,14 +157,7 @@
ecall
// The idle thread sleeps and only waits for interrupts.
.Lidle_loop:
- // There is a bug in the Flute hardware revoker that means it stops during
- // wfi, but we want it to run here. Flute is simulation only at the
- // moment, so we don't care that the nop is power-inefficient.
-#if defined(TEMPORAL_SAFETY) && defined(FLUTE) && !defined(SOFTWARE_REVOKER)
- nop
-#else
wfi
-#endif
j .Lidle_loop
.Lfill_block:
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc
index 20ff56f..d5d5405 100644
--- a/sdk/core/loader/boot.cc
+++ b/sdk/core/loader/boot.cc
@@ -147,20 +147,21 @@
// The switcher assembly includes the types of import table entries and
// trusted stacks. This enumeration and the assembly must be kept in sync.
// This will fail if the enumeration value changes.
- static_assert(int(SealedImportTableEntries) == 9,
+ static_assert(static_cast<int>(SealedImportTableEntries) == 9,
"If this fails, update switcher/entry.S to the new value");
- static_assert(int(SealedTrustedStacks) == 10,
+ static_assert(static_cast<int>(SealedTrustedStacks) == 10,
"If this fails, update switcher/entry.S to the new value");
// The allocator and static sealing types must be contiguous so that the
// token library can hold a permit-unseal capability for both.
- static_assert(int(Allocator) + 1 == int(StaticToken),
+ static_assert(static_cast<int>(Allocator) + 1 ==
+ static_cast<int>(StaticToken),
"Allocator and StaticToken must be consecutive");
// The token library includes the types for allocator and statically sealed
// objects. This enumeration and the assembly must be kept in sync. This
// will fail if the enumeration value changes.
- static_assert(int(Allocator) == 11,
+ static_assert(static_cast<int>(Allocator) == 11,
"If this fails, update token_unseal.S to the new value");
// We currently have a 3-bit hardware otype, with different sealing spaces
@@ -248,7 +249,8 @@
Root::Type Type = Root::Type::RWGlobal,
PermissionSet Permissions = Root::Permissions<Type>,
bool Precise = true>
- Capability<T> build(auto &&range) requires(RawAddressRange<decltype(range)>)
+ Capability<T> build(auto &&range)
+ requires(RawAddressRange<decltype(range)>)
{
return build<T, Type, Permissions, Precise>(range.start(),
range.size());
@@ -261,9 +263,8 @@
template<typename T = void,
Root::Type Type = Root::Type::RWGlobal,
PermissionSet Permissions = Root::Permissions<Type>>
- Capability<T>
- build(auto &&range,
- ptraddr_t address) requires(RawAddressRange<decltype(range)>)
+ Capability<T> build(auto &&range, ptraddr_t address)
+ requires(RawAddressRange<decltype(range)>)
{
return build<T, Type, Permissions>(
range.start(), range.size(), address);
@@ -347,13 +348,14 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-designator"
constexpr SealingType Sentries[] = {
- [int(InterruptStatus::Enabled)] = SentryEnabling,
- [int(InterruptStatus::Disabled)] = SentryDisabling,
- [int(InterruptStatus::Inherited)] = SentryInheriting};
+ [static_cast<int>(InterruptStatus::Enabled)] = SentryEnabling,
+ [static_cast<int>(InterruptStatus::Disabled)] = SentryDisabling,
+ [static_cast<int>(InterruptStatus::Inherited)] = SentryInheriting};
#pragma clang diagnostic pop
- Debug::Invariant(
- unsigned(status) < 3, "Invalid interrupt status {}", int(status));
- size_t otype = size_t{Sentries[int(status)]};
+ Debug::Invariant(static_cast<unsigned>(status) < 3,
+ "Invalid interrupt status {}",
+ static_cast<int>(status));
+ size_t otype = size_t{Sentries[static_cast<int>(status)]};
void *key = build<void, Root::Type::Seal>(otype, 1);
return ptr.seal(key);
}
@@ -367,10 +369,11 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-designator"
constexpr SealingType Sentries[] = {
- [int(InterruptStatus::Enabled)] = ReturnSentryEnabling,
- [int(InterruptStatus::Disabled)] = ReturnSentryDisabling};
+ [static_cast<int>(InterruptStatus::Enabled)] = ReturnSentryEnabling,
+ [static_cast<int>(InterruptStatus::Disabled)] =
+ ReturnSentryDisabling};
#pragma clang diagnostic pop
- size_t otype = size_t{Sentries[int(Status)]};
+ size_t otype = size_t{Sentries[static_cast<int>(Status)]};
void *key = build<void, Root::Type::Seal>(otype, 1);
return ptr.seal(key);
}
@@ -379,9 +382,8 @@
* Helper to determine whether an object, given by a start address and size,
* is completely contained within a specified range.
*/
- bool contains(const auto &range,
- ptraddr_t addr,
- size_t size) requires(RawAddressRange<decltype(range)>)
+ bool contains(const auto &range, ptraddr_t addr, size_t size)
+ requires(RawAddressRange<decltype(range)>)
{
return (range.start() <= addr) &&
(range.start() + range.size() >= addr + size);
@@ -393,8 +395,8 @@
* object must be completely contained within the range.
*/
template<typename T = char>
- bool contains(const auto &range,
- ptraddr_t addr) requires(RawAddressRange<decltype(range)>)
+ bool contains(const auto &range, ptraddr_t addr)
+ requires(RawAddressRange<decltype(range)>)
{
return contains(range, addr, sizeof(T));
}
@@ -444,8 +446,8 @@
* of type `T` from a virtual address range.
*/
template<typename T, bool Precise = true>
- ContiguousPtrRange<T>
- build_range(const auto &range) requires(RawAddressRange<decltype(range)>)
+ ContiguousPtrRange<T> build_range(const auto &range)
+ requires(RawAddressRange<decltype(range)>)
{
Capability<T> start = build<T,
Root::Type::RWGlobal,
@@ -668,8 +670,8 @@
auto exportEntry = build<ExportEntry>(
compartment.exportTable, typeAddress);
Debug::Invariant(
- exportEntry->is_sealing_type(),
- "Sealed object points to invalid sealing type");
+ exportEntry->is_sealing_type(),
+ "Sealed object points to invalid sealing type");
*sealingType = exportEntry->functionStart;
return true;
}
@@ -863,8 +865,7 @@
for (size_t i = 0; const auto &config : image.threads())
{
Debug::log("Creating thread {}", i);
- auto findCompartment = [&]() -> auto &
- {
+ auto findCompartment = [&]() -> auto & {
for (auto &compartment : image.compartments())
{
Debug::log("Looking in export table {}+{}",
@@ -1000,8 +1001,7 @@
// Find the library compartment that contains an address in its code or
// data section.
- auto findCompartment = [&](ptraddr_t address) -> auto &
- {
+ auto findCompartment = [&](ptraddr_t address) -> auto & {
Debug::log("Capreloc address is {}", address);
for (auto &compartment : image.libraries_and_compartments())
{
@@ -1364,7 +1364,7 @@
build<ExportEntry>(
imgHdr.scheduler().exportTable,
LA_ABS(
- __export_sched__ZN5sched15exception_entryEP19TrustedStackGenericILj0EEjjj))
+ __export_scheduler__Z15exception_entryP19TrustedStackGenericILj0EEjjj))
->functionStart;
auto schedExceptionEntry = build_pcc(imgHdr.scheduler());
schedExceptionEntry.address() += exceptionEntryOffset;
@@ -1392,57 +1392,43 @@
csp);
#ifdef SOFTWARE_REVOKER
- // If we are using a software revoker then we need to provide it with three
+ // If we are using a software revoker then we need to provide it with some
// terrifyingly powerful capabilities. These break some of the rules that
- // we enforce for everything else, especially the last one, which is a
- // stack capability that is reachable from a global. The only code that
+ // we enforce for everything else (e.g. they may point to stacks
+ // but are reachable from a global). The only code that
// accesses these in the revoker is very small and amenable to auditing
// (the only memory accesses are a load and a store back at the same
// location, with interrupts disabled, to trigger the load barrier).
- //
- // We use imprecise set-bounds operations here because we need to ensure
- // that the regions are completely scanned and scanning slightly more is
+
+ // The scary capabilities are stored at the beginning of the software
+ // revoker compartment. At the moment we only need one.
+ auto scaryCapabilities = build<Capability<void>,
+ Root::Type::RWStoreL,
+ Root::Permissions<Root::Type::RWStoreL>,
+ /* Precise: */ true>(
+ imgHdr.privilegedCompartments.software_revoker().code.start(),
+ sizeof(void *));
+ Debug::log("Writing scary capabilities for software revoker to {}",
+ scaryCapabilities);
+ // Construct a capability to all RW globals, stacks and heap.
+ // We use an imprecise set-bounds operation here because we need to ensure
+ // that the region is completely scanned and scanning slightly more is
// not a problem unless the revoker is compromised. The software revoker
// already has a terrifying set of rights, so this doesn't really make
// things worse and is another good reason to use a hardware revoker.
// Given that hardware revokers are lower power, faster, and more secure,
// there's little reason for the software revoker to be used for anything
// other than testing.
- auto scaryCapabilities = build<Capability<void>,
- Root::Type::RWStoreL,
- Root::Permissions<Root::Type::RWStoreL>,
- /* Precise: */ false>(
- imgHdr.privilegedCompartments.software_revoker().code.start(),
- 3 * sizeof(void *));
- // Read-write capability to all globals. This is scary because a bug in
- // the revoker could violate compartment isolation.
- Debug::log("Writing scary capabilities for software revoker to {}",
- scaryCapabilities);
- scaryCapabilities[0] =
- build(LA_ABS(__compart_cgps),
- LA_ABS(__compart_cgps_end) - LA_ABS(__compart_cgps));
+ scaryCapabilities[0] = build<void,
+ Root::Type::RWStoreL,
+ Root::Permissions<Root::Type::RWStoreL>,
+ /* Precise: */ false>(
+ LA_ABS(__revoker_scan_start),
+ LA_ABS(__export_mem_heap_end) - LA_ABS(__revoker_scan_start));
scaryCapabilities[0].address() = scaryCapabilities[0].base();
- Debug::log("Wrote scary capability {}", scaryCapabilities[0]);
- // Read-write capability to the whole heap. This is scary because a bug in
- // the revoker could violate heap safety.
- scaryCapabilities[1] =
- build<void,
- Root::Type::RWGlobal,
- Root::Permissions<Root::Type::RWGlobal>,
- false>(LA_ABS(__export_mem_heap),
- LA_ABS(__export_mem_heap_end) - LA_ABS(__export_mem_heap));
- scaryCapabilities[1].address() = scaryCapabilities[1].base();
- Debug::log("Wrote scary capability {}", scaryCapabilities[1]);
- // Read-write capability to the entire stack. This is scary because a bug
- // in the revoker could violate thread isolation.
- scaryCapabilities[2] =
- build<void,
- Root::Type::RWStoreL,
- Root::Permissions<Root::Type::RWStoreL>,
- false>(LA_ABS(__stack_space_start),
- LA_ABS(__stack_space_end) - LA_ABS(__stack_space_start));
- scaryCapabilities[2].address() = scaryCapabilities[2].base();
- Debug::log("Wrote scary capability {}", scaryCapabilities[2]);
+ Debug::log("Wrote scary cap[0]={} requested_start={}",
+ scaryCapabilities[0],
+ LA_ABS(__revoker_scan_start));
#endif
// Set up the exception entry point
@@ -1460,7 +1446,7 @@
// invoke the exception entry point.
auto exportEntry = build<ExportEntry>(
imgHdr.scheduler().exportTable,
- LA_ABS(__export_sched__ZN5sched15scheduler_entryEPK16ThreadLoaderInfo));
+ LA_ABS(__export_scheduler__Z15scheduler_entryPK16ThreadLoaderInfo));
schedPCC.address() += exportEntry->functionStart;
Debug::log("Will return to scheduler entry point: {}", schedPCC);
diff --git a/sdk/core/loader/debug.hh b/sdk/core/loader/debug.hh
index a89ea7f..24af5ed 100644
--- a/sdk/core/loader/debug.hh
+++ b/sdk/core/loader/debug.hh
@@ -27,11 +27,10 @@
/// Concept for something that can be lazily called to produce a bool.
template<typename T>
- concept LazyAssertion = requires(T v)
- {
+ concept LazyAssertion = requires(T v) {
{
v()
- } -> IsBool;
+ } -> IsBool;
};
} // namespace DebugConcepts
@@ -151,7 +150,7 @@
}
std::array<char, 10> buf;
const char Digits[] = "0123456789";
- for (int i = int(buf.size() - 1); i >= 0; i--)
+ for (int i = static_cast<int>(buf.size() - 1); i >= 0; i--)
{
buf.at(static_cast<size_t>(i)) = Digits[s % 10];
s /= 10;
@@ -181,7 +180,7 @@
const char Hexdigits[] = "0123456789abcdef";
// Length of string including null terminator
static_assert(sizeof(Hexdigits) == 0x11);
- for (long i = long(buf.size() - 1); i >= 0; i--)
+ for (long i = static_cast<long>(buf.size() - 1); i >= 0; i--)
{
buf.at(static_cast<size_t>(i)) = Hexdigits[s & 0xf];
s >>= 4;
@@ -248,7 +247,7 @@
* Append an enumerated type value.
*/
template<typename T>
- requires DebugConcepts::IsEnum<T>
+ requires DebugConcepts::IsEnum<T>
void append(T e)
{
// `magic_enum::enum_name` requires cap relocs, so don't use it in
@@ -482,7 +481,8 @@
* Constructor, performs the assertion check.
*/
template<typename T>
- requires DebugConcepts::IsBool<T> __always_inline
+ requires DebugConcepts::IsBool<T>
+ __always_inline
Assert(T condition,
const char *fmt,
Args... args,
@@ -513,7 +513,8 @@
* where the assertion condition has side effects.
*/
template<typename T>
- requires DebugConcepts::LazyAssertion<T> __always_inline
+ requires DebugConcepts::LazyAssertion<T>
+ __always_inline
Assert(T &&condition,
const char *fmt,
Args... args,
diff --git a/sdk/core/loader/types.h b/sdk/core/loader/types.h
index 90e190e..830c041 100644
--- a/sdk/core/loader/types.h
+++ b/sdk/core/loader/types.h
@@ -273,21 +273,21 @@
* Helper concept for determining if something is an address.
*/
template<typename T>
- concept IsAddress = std::same_as<T, ptraddr_t> ||
- std::same_as<T, ptraddr_t &> || std::same_as<T, const ptraddr_t &>;
+ concept IsAddress =
+ std::same_as<T, ptraddr_t> || std::same_as<T, ptraddr_t &> ||
+ std::same_as<T, const ptraddr_t &>;
/**
* Concept for a raw address range. This exposes a range of addresses.
*/
template<typename T>
- concept RawAddressRange = requires(T range)
- {
+ concept RawAddressRange = requires(T range) {
{
range.size()
- } -> IsAddress;
+ } -> IsAddress;
{
range.start()
- } -> IsAddress;
+ } -> IsAddress;
};
/**
@@ -1067,11 +1067,11 @@
/**
* The mask to isolate the bits that describe interrupt status.
*/
- static constexpr uint8_t InterruptStatusMask = uint8_t(0b11)
- << InterruptStatusShift;
+ static constexpr uint8_t InterruptStatusMask =
+ static_cast<uint8_t>(0b11) << InterruptStatusShift;
static constexpr uint8_t InterruptStatusSwitcherMask =
- uint8_t(0b10) << InterruptStatusShift;
+ static_cast<uint8_t>(0b10) << InterruptStatusShift;
/*
* The switcher tests the high bit of the InterruptStatus word of
@@ -1080,11 +1080,13 @@
* that its understanding is correct.
*/
static_assert(
- ((int(InterruptStatus::Enabled) << InterruptStatusShift) &
+ ((static_cast<int>(InterruptStatus::Enabled)
+ << InterruptStatusShift) &
InterruptStatusSwitcherMask) == 0,
"Switcher interpretation of InterruptStatus no longer correct");
static_assert(
- ((int(InterruptStatus::Disabled) << InterruptStatusShift) &
+ ((static_cast<int>(InterruptStatus::Disabled)
+ << InterruptStatusShift) &
InterruptStatusSwitcherMask) != 0,
"Switcher interpretation of InterruptStatus no longer correct");
@@ -1096,7 +1098,8 @@
* their first word initialised to point to this, the loader will
* set them up to instead hold the value of the sealing key.
*/
- static constexpr uint8_t SealingTypeEntry = uint8_t(0b100000);
+ static constexpr uint8_t SealingTypeEntry =
+ static_cast<uint8_t>(0b100000);
static_assert((InterruptStatusMask & SealingTypeEntry) == 0);
@@ -1129,7 +1132,7 @@
{
uint8_t status =
(flags & InterruptStatusMask) >> InterruptStatusShift;
- return InterruptStatus(status);
+ return static_cast<InterruptStatus>(status);
}
/**
diff --git a/sdk/core/scheduler/main.cc b/sdk/core/scheduler/main.cc
index d4d36a8..2983289 100644
--- a/sdk/core/scheduler/main.cc
+++ b/sdk/core/scheduler/main.cc
@@ -14,7 +14,6 @@
#include <futex.h>
#include <interrupt.h>
#include <locks.hh>
-#include <new>
#include <priv/riscv.h>
#include <riscvreg.h>
#include <simulator.h>
@@ -31,9 +30,10 @@
/**
* Exit simulation, reporting the error code given as the argument.
*/
-void simulation_exit(uint32_t code)
+int scheduler_simulation_exit(uint32_t code)
{
platform_simulation_exit(code);
+ return -EPROTO;
}
#endif
@@ -121,6 +121,26 @@
*/
static constexpr auto UnboundedSleep = std::numeric_limits<uint32_t>::max();
+ enum FutexWakeKind
+ {
+ /**
+ * The futex wake did not make any threads runnable that would be
+ * scheduled preemptively.
+ */
+ NoYield,
+ /**
+ * The futex wake made a thread at the current priority level runnable,
+ * the caller should ensure that there is a timer interrupt scheduled
+ * to make the current thread yield later.
+ */
+ YieldLater,
+ /**
+ * The futex wake made a thread at a higher priority level runnable,
+ * the caller should yield to allow the other thread to run immediately.
+ */
+ YieldNow,
+ };
+
/**
* Helper that wakes a set of up to `count` threads waiting on the futex
* whose address is given by the `key` parameter.
@@ -133,11 +153,10 @@
* dropped back to the previous priority.
* - The number of sleeper that were awoken.
*/
- std::tuple<bool, bool, int>
+ std::tuple<bool, int>
futex_wake(ptraddr_t key,
uint32_t count = std::numeric_limits<uint32_t>::max())
{
- bool shouldYield = false;
bool shouldRecalculatePriorityBoost = false;
// The number of threads that we've woken, this is the return value on
// success.
@@ -149,7 +168,8 @@
{
shouldRecalculatePriorityBoost |=
thread->futexPriorityInheriting;
- shouldYield = thread->ready(Thread::WakeReason::Futex);
+ thread->ready(Thread::WakeReason::Futex);
+ Debug::log("futex_wake woke thread {}", thread->id_get());
count--;
woke++;
}
@@ -162,15 +182,12 @@
MultiWaiterInternal::wake_waiters(key, count);
count -= multiwaitersWoken;
woke += multiwaitersWoken;
- shouldYield |= (multiwaitersWoken > 0);
}
- return {shouldYield, shouldRecalculatePriorityBoost, woke};
+ Debug::log("futex_wake on {} woke {} waiters", key, woke);
+
+ return {shouldRecalculatePriorityBoost, woke};
}
-} // namespace
-
-namespace sched
-{
using namespace priv;
/**
@@ -192,30 +209,6 @@
return &(reinterpret_cast<Thread *>(threadSpaces))[threadId - 1];
}
- [[cheri::interrupt_state(disabled)]] void __cheri_compartment("sched")
- scheduler_entry(const ThreadLoaderInfo *info)
- {
- Debug::Invariant(Capability{info}.length() ==
- sizeof(*info) * CONFIG_THREADS_NUM,
- "Thread info is {} bytes, expected {} for {} threads",
- Capability{info}.length(),
- sizeof(*info) * CONFIG_THREADS_NUM,
- CONFIG_THREADS_NUM);
-
- for (size_t i = 0; auto *threadSpace : threadSpaces)
- {
- Debug::log("Created thread for trusted stack {}",
- info[i].trustedStack);
- Thread *th = new (threadSpace)
- Thread(info[i].trustedStack, i + 1, info[i].priority);
- th->ready(Thread::WakeReason::Timer);
- i++;
- }
-
- InterruptController::master_init();
- Timer::interrupt_setup();
- }
-
static void __dead2 sched_panic(size_t mcause, size_t mepc, size_t mtval)
{
size_t capcause = mtval & 0x1f;
@@ -228,8 +221,10 @@
static_cast<uint32_t>(capcause),
badcap);
+#ifdef SIMULATION
// If we're in simulation, exit here
- simulation_exit(1);
+ platform_simulation_exit(1);
+#endif
for (;;)
{
@@ -237,101 +232,6 @@
}
}
- [[cheri::interrupt_state(disabled)]] TrustedStack *
- __cheri_compartment("sched") exception_entry(TrustedStack *sealedTStack,
- size_t mcause,
- size_t mepc,
- size_t mtval)
- {
- if constexpr (DebugScheduler)
- {
- /* Ensure that we got here from an IRQ-s deferred context */
- Capability returnAddress{__builtin_return_address(0)};
- Debug::Assert(
- returnAddress.type() == CheriSealTypeReturnSentryDisabling,
- "Scheduler exception_entry called from IRQ-enabled context");
- }
-
- // The cycle count value the last time the scheduler returned.
- bool schedNeeded;
- if constexpr (Accounting)
- {
- uint64_t currentCycles = rdcycle64();
- auto *thread = Thread::current_get();
- uint64_t &threadCycleCounter =
- thread ? thread->cycles : Thread::idleThreadCycles;
- auto elapsedCycles = currentCycles - cyclesAtLastSchedulingEvent;
- threadCycleCounter += elapsedCycles;
- }
-
- ExceptionGuard g{[=]() { sched_panic(mcause, mepc, mtval); }};
-
- bool tick = false;
- switch (mcause)
- {
- // Explicit yield call
- case MCAUSE_ECALL_MACHINE:
- {
- schedNeeded = true;
- Thread *currentThread = Thread::current_get();
- tick = currentThread && currentThread->is_ready();
- break;
- }
- case MCAUSE_INTR | MCAUSE_MTIME:
- schedNeeded = true;
- tick = true;
- break;
- case MCAUSE_INTR | MCAUSE_MEXTERN:
- schedNeeded = false;
- InterruptController::master().do_external_interrupt().and_then(
- [&](uint32_t &word) {
- // Increment the futex word so that anyone preempted on
- // the way into the scheduler sleeping on its old value
- // will still see this update.
- word++;
- // Wake anyone sleeping on this futex. Interrupt futexes
- // are not priority inheriting.
- std::tie(schedNeeded, std::ignore, std::ignore) =
- futex_wake(Capability{&word}.address());
- });
- tick = schedNeeded;
- break;
- case MCAUSE_THREAD_EXIT:
- // Make the current thread non-runnable.
- if (Thread::exit())
- {
- // If we have no threads left (not counting the idle
- // thread), exit.
- simulation_exit(0);
- }
- // We cannot continue exiting this thread, make sure we will
- // pick a new one.
- schedNeeded = true;
- tick = true;
- sealedTStack = nullptr;
- break;
- default:
- sched_panic(mcause, mepc, mtval);
- }
- if (tick || !Thread::any_ready())
- {
- Timer::expiretimers();
- }
- auto newContext =
- schedNeeded ? Thread::schedule(sealedTStack) : sealedTStack;
-#if 0
- Debug::log("Thread: {}",
- Thread::current_get() ? Thread::current_get()->id_get() : 0);
-#endif
- Timer::update();
-
- if constexpr (Accounting)
- {
- cyclesAtLastSchedulingEvent = rdcycle64();
- }
- return newContext;
- }
-
/**
* Helper template to dispatch an operation to a typed value. The first
* argument is a sealed capability provided by the caller. The second is a
@@ -396,12 +296,135 @@
});
}
-} // namespace sched
+} // namespace
-using namespace sched;
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
+ scheduler_entry(const ThreadLoaderInfo *info)
+{
+ Debug::Invariant(Capability{info}.length() ==
+ sizeof(*info) * CONFIG_THREADS_NUM,
+ "Thread info is {} bytes, expected {} for {} threads",
+ Capability{info}.length(),
+ sizeof(*info) * CONFIG_THREADS_NUM,
+ CONFIG_THREADS_NUM);
+
+ for (size_t i = 0; auto *threadSpace : threadSpaces)
+ {
+ Debug::log("Created thread for trusted stack {}", info[i].trustedStack);
+ Thread *th = new (threadSpace)
+ Thread(info[i].trustedStack, i + 1, info[i].priority);
+ th->ready(Thread::WakeReason::Timer);
+ i++;
+ }
+
+ InterruptController::master_init();
+ Timer::interrupt_setup();
+
+ return 0;
+}
+
+[[cheri::interrupt_state(disabled)]] TrustedStack *
+ __cheri_compartment("scheduler") exception_entry(TrustedStack *sealedTStack,
+ size_t mcause,
+ size_t mepc,
+ size_t mtval)
+{
+ if constexpr (DebugScheduler)
+ {
+ /* Ensure that we got here from an IRQ-s deferred context */
+ Capability returnAddress{__builtin_return_address(0)};
+ Debug::Assert(
+ returnAddress.type() == CheriSealTypeReturnSentryDisabling,
+ "Scheduler exception_entry called from IRQ-enabled context");
+ }
+
+ // The cycle count value the last time the scheduler returned.
+ bool schedNeeded;
+ if constexpr (Accounting)
+ {
+ uint64_t currentCycles = rdcycle64();
+ auto *thread = Thread::current_get();
+ uint64_t &threadCycleCounter =
+ thread ? thread->cycles : Thread::idleThreadCycles;
+ auto elapsedCycles = currentCycles - cyclesAtLastSchedulingEvent;
+ threadCycleCounter += elapsedCycles;
+ }
+
+ ExceptionGuard g{[=]() { sched_panic(mcause, mepc, mtval); }};
+
+ bool tick = false;
+ switch (mcause)
+ {
+ // Explicit yield call
+ case MCAUSE_ECALL_MACHINE:
+ {
+ schedNeeded = true;
+ Thread *currentThread = Thread::current_get();
+ tick = currentThread && currentThread->is_ready();
+ break;
+ }
+ case MCAUSE_INTR | MCAUSE_MTIME:
+ schedNeeded = true;
+ tick = true;
+ break;
+ case MCAUSE_INTR | MCAUSE_MEXTERN:
+ schedNeeded = false;
+ InterruptController::master().do_external_interrupt().and_then(
+ [&](uint32_t &word) {
+ // Increment the futex word so that anyone preempted on
+ // the way into the scheduler sleeping on its old value
+ // will still see this update.
+ word++;
+ // Wake anyone sleeping on this futex. Interrupt futexes
+ // are not priority inheriting.
+ int woke;
+ Debug::log("Waking waiters on interrupt futex {}", &word);
+ std::tie(std::ignore, woke) =
+ futex_wake(Capability{&word}.address());
+ schedNeeded |= (woke > 0);
+ });
+ tick = schedNeeded;
+ break;
+ case MCAUSE_THREAD_EXIT:
+ // Make the current thread non-runnable.
+ if (Thread::exit())
+ {
+#ifdef SIMULATION
+ // If we have no threads left (not counting the idle
+ // thread), exit.
+ platform_simulation_exit(0);
+#endif
+ }
+ // We cannot continue exiting this thread, make sure we will
+ // pick a new one.
+ schedNeeded = true;
+ tick = true;
+ sealedTStack = nullptr;
+ break;
+ default:
+ sched_panic(mcause, mepc, mtval);
+ }
+ if (tick || !Thread::any_ready())
+ {
+ Timer::expiretimers();
+ }
+ auto newContext =
+ schedNeeded ? Thread::schedule(sealedTStack) : sealedTStack;
+#if 0
+ Debug::log("Thread: {}",
+ Thread::current_get() ? Thread::current_get()->id_get() : 0);
+#endif
+ Timer::update();
+
+ if constexpr (Accounting)
+ {
+ cyclesAtLastSchedulingEvent = rdcycle64();
+ }
+ return newContext;
+}
// thread APIs
-SystickReturn __cheri_compartment("sched") thread_systemtick_get()
+SystickReturn __cheri_compartment("scheduler") thread_systemtick_get()
{
uint64_t ticks = Thread::ticksSinceBoot;
uint32_t hi = ticks >> 32;
@@ -411,7 +434,7 @@
return ret;
}
-__cheriot_minimum_stack(0x90) int __cheri_compartment("sched")
+__cheriot_minimum_stack(0x90) int __cheri_compartment("scheduler")
thread_sleep(Timeout *timeout, uint32_t flags)
{
STACK_CHECK(0x90);
@@ -522,7 +545,24 @@
}
ptraddr_t key = Capability{address}.address();
- auto [shouldYield, shouldResetPrioirity, woke] = futex_wake(key, count);
+ auto [shouldResetPrioirity, woke] = futex_wake(key, count);
+
+ FutexWakeKind shouldYield = NoYield;
+
+ if (woke > 0)
+ {
+ auto *thread = Thread::current_get();
+ if (!thread->is_highest_priority())
+ {
+ shouldYield = YieldNow;
+ }
+ else if (thread->has_priority_peers())
+ {
+ shouldYield =
+ thread->has_run_for_full_tick() ? YieldNow : YieldLater;
+ }
+ Debug::log("futex_wake yielding? {}", shouldYield);
+ }
// If this futex wake is dropping a priority boost, reset the boost.
if (shouldResetPrioirity)
@@ -543,12 +583,18 @@
priority_boost_for_thread(currentThread->id_get()));
// If we have dropped priority below that of another runnable thread, we
// should yield now.
- shouldYield |= !currentThread->is_highest_priority();
}
- if (shouldYield)
+ switch (shouldYield)
{
- yield();
+ case YieldLater:
+ Timer::ensure_tick();
+ break;
+ case YieldNow:
+ yield();
+ break;
+ case NoYield:
+ break;
}
return woke;
diff --git a/sdk/core/scheduler/multiwait.h b/sdk/core/scheduler/multiwait.h
index e1fdb5f..7271b4a 100644
--- a/sdk/core/scheduler/multiwait.h
+++ b/sdk/core/scheduler/multiwait.h
@@ -196,11 +196,11 @@
}
void *memory = nullptr;
SObj sealed = token_sealed_unsealed_alloc(
- timeout,
- heapCapability,
- sealing_type(),
- sizeof(MultiWaiterInternal) + (length * sizeof(EventWaiter)),
- &memory);
+ timeout,
+ heapCapability,
+ sealing_type(),
+ sizeof(MultiWaiterInternal) + (length * sizeof(EventWaiter)),
+ &memory);
if (!memory)
{
error = -ENOMEM;
diff --git a/sdk/core/scheduler/plic.h b/sdk/core/scheduler/plic.h
index 99874d5..f147d32 100644
--- a/sdk/core/scheduler/plic.h
+++ b/sdk/core/scheduler/plic.h
@@ -24,21 +24,30 @@
using SourceID = uint32_t;
template<typename T, size_t MaxIntrID, typename SourceID, typename Priority>
- concept IsPlic = requires(T v, SourceID id, Priority p)
- {
- {v.interrupt_enable(id)};
- {v.interrupt_disable(id)};
- {v.interrupt_disable(id)};
- {v.priority_set(id, p)};
+ concept IsPlic = requires(T v, SourceID id, Priority p) {
+ {
+ v.interrupt_enable(id)
+ };
+ {
+ v.interrupt_disable(id)
+ };
+ {
+ v.interrupt_disable(id)
+ };
+ {
+ v.priority_set(id, p)
+ };
{
v.interrupt_claim()
- } -> std::same_as<std::optional<SourceID>>;
- {v.interrupt_complete(id)};
+ } -> std::same_as<std::optional<SourceID>>;
+ {
+ v.interrupt_complete(id)
+ };
};
/*
* FIXME: Sail doesn't have an interrupt controller at all, but we pretend
- * it does just like FLUTE build to let things compile. We need tons of
+ * it does just like other builds to let things compile. We need tons of
* #ifdefs or a big rewrite to make the entire external interrupt path
* optional.
*
@@ -142,7 +151,8 @@
{
for (size_t i = 0; i < NumberOfInterrupts; i++)
{
- if (ConfiguredInterrupts[i].number == uint32_t(source))
+ if (ConfiguredInterrupts[i].number ==
+ static_cast<uint32_t>(source))
{
if constexpr (CompleteInterruptIfEdgeTriggered)
{
diff --git a/sdk/core/scheduler/thread.h b/sdk/core/scheduler/thread.h
index ea88756..4a16a43 100644
--- a/sdk/core/scheduler/thread.h
+++ b/sdk/core/scheduler/thread.h
@@ -5,8 +5,10 @@
#include "common.h"
#include <cdefs.h>
+#include <platform-timer.hh>
#include <priv/riscv.h>
#include <strings.h>
+#include <tick_macros.h>
#include <utils.hh>
namespace
@@ -111,6 +113,15 @@
current->priority,
current->OriginalPriority);
}
+ if (current != th)
+ {
+ current->expiryTime = TimerCore::time();
+ }
+#ifdef CLANG_TIDY
+ // The static analyser thinks that `debug_log_message_write` may
+ // assign `nullptr` to `current`.
+ __builtin_assume(current != nullptr);
+#endif
return current->tStackPtr;
}
return schedTStack;
@@ -184,9 +195,9 @@
: threadId(threadid),
priority(priority),
OriginalPriority(priority),
- expiryTime(-1),
+
state(ThreadState::Suspended),
- isYielding(false),
+
sleepQueue(nullptr),
tStackPtr(tstack)
{
@@ -203,10 +214,9 @@
* the resource disappearing, do some clean-ups.
* @param the reason why this thread is now able to be scheduled
*/
- bool ready(WakeReason reason)
+ void ready(WakeReason reason)
{
int64_t ticksLeft;
- bool schedule = false;
// We must be suspended.
Debug::Assert(state == ThreadState::Suspended,
@@ -229,23 +239,14 @@
if (priority > highestPriority)
{
highestPriority = priority;
- schedule = true;
}
}
- // If this is the same priority as the current thread, we may need
- // to update the timer.
- if (priority >= highestPriority)
- {
- schedule = true;
- }
if (reason == WakeReason::Timer || reason == WakeReason::Delete)
{
multiWaiter = nullptr;
}
list_insert(&priorityList[priority]);
isYielding = false;
-
- return schedule;
}
/**
@@ -543,6 +544,17 @@
return next != this;
}
+ /**
+ * Returns true if the thread has run for a complete tick. This must
+ * be called only on the currently running thread.
+ */
+ bool has_run_for_full_tick()
+ {
+ Debug::Assert(this == current,
+ "Only the current thread is running");
+ return TimerCore::time() >= expiryTime + TIMERCYCLES_PER_TICK;
+ }
+
~ThreadImpl()
{
// We have static definition of threads. We only create threads in
@@ -564,9 +576,13 @@
///@}
/// Pointer to the list of the resource this thread is blocked on.
ThreadImpl **sleepQueue;
- /// If suspended, when will this thread expire. The maximum value is
- /// special-cased to mean blocked indefinitely.
- uint64_t expiryTime;
+ /**
+ * If suspended, when will this thread expire. The maximum value is
+ * special-cased to mean blocked indefinitely.
+ *
+ * When a thread is running, this the time at which it was scheduled.
+ */
+ uint64_t expiryTime{static_cast<uint64_t>(-1)};
/// The number of cycles that this thread has been scheduled for.
uint64_t cycles;
@@ -654,7 +670,7 @@
* expires, as long as no other threads are runnable or sleeping with
* shorter timeouts.
*/
- bool isYielding : 1;
+ bool isYielding : 1 {false};
};
using Thread = ThreadImpl<ThreadPrioNum>;
diff --git a/sdk/core/scheduler/timer.h b/sdk/core/scheduler/timer.h
index 45355b6..5a48924 100644
--- a/sdk/core/scheduler/timer.h
+++ b/sdk/core/scheduler/timer.h
@@ -3,6 +3,7 @@
#pragma once
+#include "common.h"
#include "plic.h"
#include "thread.h"
#include <platform-timer.hh>
@@ -16,10 +17,16 @@
* Concept for the interface to setting the system timer.
*/
template<typename T>
- concept IsTimer = requires(uint32_t cycles)
- {
- {T::init()};
- {T::setnext(cycles)};
+ concept IsTimer = requires(uint32_t cycles) {
+ {
+ T::init()
+ };
+ {
+ T::setnext(cycles)
+ };
+ {
+ T::next()
+ } -> std::same_as<uint64_t>;
};
static_assert(
@@ -103,6 +110,21 @@
}
/**
+ * Ensure that a timer tick is scheduled for the current thread.
+ */
+ static void ensure_tick()
+ {
+ auto *thread = Thread::current_get();
+ Debug::Assert(thread != nullptr,
+ "Ensure tick called with no running thread");
+ auto tickTime = thread->expiryTime + TIMERCYCLES_PER_TICK;
+ if (tickTime < TimerCore::next())
+ {
+ setnext(tickTime);
+ }
+ }
+
+ /**
* Wake any threads that were sleeping until a timeout before the
* current time. This also wakes yielded threads if there are no
* runnable threads.
@@ -156,7 +178,8 @@
{
Debug::log("Woke thread {} {} cycles early",
head->id_get(),
- int64_t(head->expiryTime) - now);
+ static_cast<int64_t>(head->expiryTime) -
+ now);
head->ready(Thread::WakeReason::Timer);
}
}
diff --git a/sdk/core/software_revoker/revoker.cc b/sdk/core/software_revoker/revoker.cc
index 445c778..05e001a 100644
--- a/sdk/core/software_revoker/revoker.cc
+++ b/sdk/core/software_revoker/revoker.cc
@@ -11,15 +11,14 @@
using CHERI::Permission;
/**
- * We need an array of the three allocations that provide the globals at the
+ * We need an array of the allocations that provide the globals at the
* start of our PCC, but the compiler doesn't currently provide a good way of
- * doing this, so do it with an assembly stub for loading the three
- * capabilities.
+ * doing this, so do it with an assembly stub for loading the capabilities.
*/
__asm__(" .section .text, \"ax\", @progbits\n"
" .p2align 3\n"
"globals:\n"
- " .zero 3*8\n"
+ " .zero 1*8\n"
" .globl get_globals\n"
"get_globals:\n"
" sll a0, a0, 3\n"
@@ -35,7 +34,7 @@
namespace
{
/**
- * The index of the current range to scan. Must be 0-2 or negative.
+ * The index of the current range to scan. Must be 0 or -1.
*/
int currentRange;
/**
@@ -62,18 +61,9 @@
*/
NotRunning,
/**
- * The revoker is scanning globals.
+ * The revoker is scanning.
*/
- ScanningGlobals,
- /**
- * The revoker is scanning the heap.
- */
- ScanningHeap,
- /**
- * The revoker is scanning stacks (including trusted stacks and the
- * register-save areas).
- */
- ScanningStacks
+ Scanning
} state;
/**
@@ -84,12 +74,8 @@
switch (state)
{
case State::NotRunning:
- return {0, State::ScanningGlobals};
- case State::ScanningGlobals:
- return {1, State::ScanningHeap};
- case State::ScanningHeap:
- return {2, State::ScanningStacks};
- case State::ScanningStacks:
+ return {0, State::Scanning};
+ case State::Scanning:
return {-1, State::NotRunning};
}
}
@@ -159,7 +145,7 @@
} // namespace
-void revoker_tick()
+int revoker_tick()
{
// If we've been asked to run, make sure that we're running.
if (state == State::NotRunning)
@@ -167,7 +153,9 @@
advance();
}
// Do some work.
- return scan_range();
+ scan_range();
+
+ return 0;
}
const uint32_t *revoker_epoch_get()
diff --git a/sdk/core/switcher/entry.S b/sdk/core/switcher/entry.S
index 6e6bcfc..3a400cf 100644
--- a/sdk/core/switcher/entry.S
+++ b/sdk/core/switcher/entry.S
@@ -218,7 +218,7 @@
/**
* Clear the hazard pointers associated with this thread. (See
- * include/stdlib.h:/heap_claim_fast, and its implementation in
+ * include/stdlib.h:/heap_claim_ephemeral, and its implementation in
* lib/compartment_helpers/claim_fast.cc for more about hazard pointers.) We
* don't care about leaks here (they're store-only from anywhere except the
* allocator), so just write a 32-bit zero over half of each one to clobber the
diff --git a/sdk/firmware.ldscript.in b/sdk/firmware.ldscript.in
index 0730217..a591c3a 100644
--- a/sdk/firmware.ldscript.in
+++ b/sdk/firmware.ldscript.in
@@ -5,298 +5,11 @@
SECTIONS
{
- . = @code_start@;
- _start = .;
-
- .loader_start :
- {
- *(.loader_start);
- }
-
- @thread_trusted_stacks@
-
- __stack_space_start = .;
- @thread_stacks@
- __stack_space_end = .;
-
- .compartment_export_tables : ALIGN(8)
- {
- # The scheduler and allocator's export tables are at the start.
- .scheduler_export_table = .;
- *.scheduler.compartment(.compartment_export_table);
- .scheduler_export_table_end = .;
-
- .allocator_export_table = ALIGN(8);
- */cheriot.allocator.compartment(.compartment_export_table);
- .allocator_export_table_end = .;
-
- @compartment_exports@
- }
-
-
- __compart_pccs = .;
-
- compartment_switcher_code : CAPALIGN
- {
- .compartment_switcher_start = .;
- */switcher/entry.S.o(.text);
- }
- .compartment_switcher_end = .;
-
- scheduler_code : CAPALIGN
- {
- .scheduler_start = .;
- *.scheduler.compartment(.compartment_sealing_keys);
- .scheduler_import_start = .;
- *.scheduler.compartment(.compartment_import_table);
- .scheduler_import_end = .;
- *.scheduler.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
- }
- .scheduler_end = .;
-
- allocator_code : CAPALIGN
- {
- .allocator_start = .;
- */cheriot.allocator.compartment(.compartment_sealing_keys);
- .allocator_import_start = .;
- */cheriot.allocator.compartment(.compartment_import_table);
- .allocator_import_end = .;
- allocator.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
- */cheriot.allocator.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
- }
- .allocator_end = .;
-
-
- token_library_code : CAPALIGN
- {
- .token_library_start = .;
- */cheriot.token_library.library(.compartment_sealing_keys);
- .token_library_import_start = .;
- */cheriot.token_library.library(.compartment_import_table);
- .token_library_import_end = .;
- token_library.library(.text .text.* .rodata .rodata.* .data.rel.ro);
- */cheriot.token_library.library(.text .text.* .rodata .rodata.* .data.rel.ro);
- }
- .token_library_end = .;
-
-
- @software_revoker_code@
-
- @pcc_ld@
-
- __compart_pccs_end = .;
-
- __compart_cgps = ALIGN(64);
-
- .scheduler_globals : CAPALIGN
- {
- .scheduler_globals = .;
- *.scheduler.compartment(.data .data.* .sdata .sdata.*);
- .scheduler_bss_start = .;
- *.scheduler.compartment(.sbss .sbss.* .bss .bss.*)
- }
- .scheduler_globals_end = .;
-
- .allocator_sealed_objects : CAPALIGN
- {
- .allocator_sealed_objects = .;
- */cheriot.allocator.compartment(.sealed_objects)
- }
- .allocator_sealed_objects_end = .;
-
- .allocator_globals : CAPALIGN
- {
- .allocator_globals = .;
- */cheriot.allocator.compartment(.data .data.* .sdata .sdata.*);
- .allocator_bss_start = .;
- */cheriot.allocator.compartment(.sbss .sbss.* .bss .bss.*);
- }
- .allocator_globals_end = .;
-
-
- @software_revoker_globals@
-
- @gdc_ld@
-
- __compart_cgps_end = .;
-
- .sealed_objects :
- {
- @sealed_objects@
- }
-
- __shared_objects_start = .;
- @shared_objects@
- __shared_objects_end = .;
-
- . = ALIGN(64);
-
- # Everything after this point can be discarded after the loader has
- # finished.
- __export_mem_heap = @heap_start@;
-
- __cap_relocs :
- {
- __cap_relocs = .;
- # FIXME: This currently doesn't do anything. The linker creates this
- # entire section. The linker script needs to be modified to create
- # separate caprelocs sections for each section.
- @cap_relocs@
- }
- __cap_relocs_end = .;
-
- # Collect all compartment headers
- .compartment_headers : ALIGN(4)
- {
- __compart_headers = .;
- # Loader code start
- LONG(.loader_code_start);
- # Loader code length
- SHORT(.loader_code_end - .loader_code_start);
- # Loader data start
- LONG(.loader_data_start);
- # Loader data length
- SHORT(.loader_data_end - .loader_data_start);
-
- # Compartment switcher start address
- LONG(.compartment_switcher_start);
- # Compartment switcher end
- SHORT(.compartment_switcher_end - .compartment_switcher_start);
- # Cross-compartment call return path
- SHORT(switcher_after_compartment_call - .compartment_switcher_start);
- # Compartment switcher sealing keys
- SHORT(compartment_switcher_sealing_key - .compartment_switcher_start);
- # Switcher's copy of the scheduler's PCC.
- SHORT(switcher_scheduler_entry_pcc - .compartment_switcher_start);
- # Switcher's copy of the scheduler's CGP
- SHORT(switcher_scheduler_entry_cgp - .compartment_switcher_start);
- # Switcher's copy of the scheduler's CSP
- SHORT(switcher_scheduler_entry_csp - .compartment_switcher_start);
- # Address of switcher export table
- LONG(.switcher_export_table);
- # Size of the switcher export table
- SHORT(.switcher_export_table_end - .switcher_export_table);
-
- # sdk/core/loader/types.h:/PrivilegedCompartment
- # Scheduler code start address
- LONG(.scheduler_start);
- # Scheduler code end
- SHORT(.scheduler_end - .scheduler_start);
- # Scheduler globals start address
- LONG(.scheduler_globals);
- # Scheduler globals end size
- SHORT(SIZEOF(.scheduler_globals));
- # Start of the scheduler's import table
- LONG(.scheduler_import_start);
- # Size of the scheduler import table
- SHORT(.scheduler_import_end - .scheduler_import_start);
- # Address of scheduler export table
- LONG(.scheduler_export_table);
- # Size of the scheduler export table
- SHORT(.scheduler_export_table_end - .scheduler_export_table);
- # No sealed objects
- LONG(0);
- SHORT(0);
-
- # sdk/core/loader/types.h:/PrivilegedCompartment
- # Allocator code start address
- LONG(.allocator_start);
- # Allocator code end
- SHORT(.allocator_end - .allocator_start);
- # Allocator globals start address
- LONG(.allocator_globals);
- # Allocator globals end
- SHORT(SIZEOF(.allocator_globals));
- # Start of the allocator's import table
- LONG(.allocator_import_start);
- # Size of the allocator import table
- SHORT(.allocator_import_end - .allocator_import_start);
- # Address of allocator export table
- LONG(.allocator_export_table);
- # Size of the allocator export table
- SHORT(.allocator_export_table_end - .allocator_export_table);
- # The allocator may have sealed objects
- LONG(.allocator_sealed_objects);
- SHORT(SIZEOF(.allocator_sealed_objects));
-
- # sdk/core/loader/types.h:/PrivilegedCompartment
- # Token server compartment header
- # Code
- LONG(.token_library_start);
- SHORT(.token_library_end - .token_library_start);
- # No data segment
- LONG(0);
- SHORT(0);
- # Start of the token_library's import table
- LONG(.token_library_import_start);
- # Size of the token server import table
- SHORT(.token_library_import_end - .token_library_import_start);
- # Address of the token server export table
- LONG(.token_library_export_table);
- # Size of the token server export table
- SHORT(.token_library_export_table_end - .token_library_export_table);
- # No sealed objects
- LONG(0);
- SHORT(0);
-
- @software_revoker_header@
-
- # sdk/core/loader/types.h:/is_magic_valid
- # Magic number, used to detect mismatches between linker script and
- # loader versions.
- # New versions of this can be generated with:
- # head /dev/random | shasum | cut -c 0-8
- LONG(0xca2b63de);
- # Number of library headers.
- SHORT(@library_count@);
- # Number of compartment headers.
- SHORT(@compartment_count@);
- @compartment_headers@
- }
-
- # Thread configuration. This follows the compartment headers but is in a
- # separate section to make auditing easier.
- # This section holds a `class ThreadInfo` (loader/types.h)
- .thread_config :
- {
- .thread_config_start = .;
- # Number of threads
- __thread_count = .;
- SHORT(@thread_count@);
- # The thread structures
- @thread_headers@
- __compart_headers_end = .;
- }
-
-
- .loader_code : CAPALIGN
- {
- .loader_code_start = .;
- */loader/boot.cc.o(.text .text.* .rodata .rodata.* .data.rel.ro);
- }
- .loader_code_end = .;
-
- .loader_data : CAPALIGN
- {
- .loader_data_start = .;
- */loader/boot.cc.o(.data .data.* .sdata .sdata.* .sbss .sbss.* .bss .bss.*);
- }
- .loader_data_end = .;
-
- .library_export_tables : ALIGN(8)
- {
- .token_library_export_table = ALIGN(8);
- */cheriot.token_library.library(.compartment_export_table);
- .token_library_export_table_end = .;
-
- .switcher_export_table = ALIGN(8);
- */switcher/entry.S.o(.compartment_export_table);
- .switcher_export_table_end = .;
-
- @library_exports@
- }
-
+ # We link either rwdata or rocode first depending on board config
+ INCLUDE @firmware_low_ldscript@
+ INCLUDE @firmware_high_ldscript@
}
+
# No symbols should be exported
VERSION {
VERSION_1 {
diff --git a/sdk/firmware.rocode.ldscript.in b/sdk/firmware.rocode.ldscript.in
new file mode 100644
index 0000000..6adecba
--- /dev/null
+++ b/sdk/firmware.rocode.ldscript.in
@@ -0,0 +1,73 @@
+. = @code_start@;
+_start = .;
+
+.loader_start :
+{
+ *(.loader_start);
+}
+
+.compartment_export_tables : ALIGN(8)
+{
+ # The scheduler and allocator's export tables are at the start.
+ .scheduler_export_table = .;
+ *.scheduler.compartment(.compartment_export_table);
+ .scheduler_export_table_end = .;
+
+ .allocator_export_table = ALIGN(8);
+ */cheriot.allocator.compartment(.compartment_export_table);
+ .allocator_export_table_end = .;
+
+ @compartment_exports@
+}
+
+
+__compart_pccs = .;
+
+compartment_switcher_code : CAPALIGN
+{
+ .compartment_switcher_start = .;
+ */switcher/entry.S.o(.text);
+}
+.compartment_switcher_end = .;
+
+scheduler_code : CAPALIGN
+{
+ .scheduler_start = .;
+ *.scheduler.compartment(.compartment_sealing_keys);
+ .scheduler_import_start = .;
+ *.scheduler.compartment(.compartment_import_table);
+ .scheduler_import_end = .;
+ *.scheduler.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
+}
+.scheduler_end = .;
+
+allocator_code : CAPALIGN
+{
+ .allocator_start = .;
+ */cheriot.allocator.compartment(.compartment_sealing_keys);
+ .allocator_import_start = .;
+ */cheriot.allocator.compartment(.compartment_import_table);
+ .allocator_import_end = .;
+ allocator.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
+ */cheriot.allocator.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);
+}
+.allocator_end = .;
+
+
+token_library_code : CAPALIGN
+{
+ .token_library_start = .;
+ */cheriot.token_library.library(.compartment_sealing_keys);
+ .token_library_import_start = .;
+ */cheriot.token_library.library(.compartment_import_table);
+ .token_library_import_end = .;
+ token_library.library(.text .text.* .rodata .rodata.* .data.rel.ro);
+ */cheriot.token_library.library(.text .text.* .rodata .rodata.* .data.rel.ro);
+}
+.token_library_end = .;
+
+
+@software_revoker_code@
+
+@pcc_ld@
+__compart_pccs_end = .;
diff --git a/sdk/firmware.rwdata.ldscript.in b/sdk/firmware.rwdata.ldscript.in
new file mode 100644
index 0000000..299921d
--- /dev/null
+++ b/sdk/firmware.rwdata.ldscript.in
@@ -0,0 +1,222 @@
+#data_start will either be . or some address depending on board config
+. = @data_start@;
+# Revoker scan region must be cap aligned
+. = ALIGN(8);
+__revoker_scan_start = .;
+__stack_space_start = .;
+
+@thread_trusted_stacks@
+
+@thread_stacks@
+
+__stack_space_end = .;
+
+__compart_cgps = ALIGN(64);
+
+.scheduler_globals : CAPALIGN
+{
+ .scheduler_globals = .;
+ *.scheduler.compartment(.data .data.* .sdata .sdata.*);
+ .scheduler_bss_start = .;
+ *.scheduler.compartment(.sbss .sbss.* .bss .bss.*)
+}
+.scheduler_globals_end = .;
+
+.allocator_sealed_objects : CAPALIGN
+{
+ .allocator_sealed_objects = .;
+ */cheriot.allocator.compartment(.sealed_objects)
+}
+.allocator_sealed_objects_end = .;
+
+.allocator_globals : CAPALIGN
+{
+ .allocator_globals = .;
+ */cheriot.allocator.compartment(.data .data.* .sdata .sdata.*);
+ .allocator_bss_start = .;
+ */cheriot.allocator.compartment(.sbss .sbss.* .bss .bss.*);
+}
+.allocator_globals_end = .;
+
+
+@software_revoker_globals@
+
+@gdc_ld@
+
+__compart_cgps_end = .;
+
+.sealed_objects :
+{
+ @sealed_objects@
+}
+
+__shared_objects_start = .;
+@shared_objects@
+__shared_objects_end = .;
+
+. = ALIGN(64);
+
+# Everything after this point can be discarded after the loader has
+# finished.
+__export_mem_heap = @heap_start@;
+
+__cap_relocs :
+{
+ __cap_relocs = .;
+ # FIXME: This currently doesn't do anything. The linker creates this
+ # entire section. The linker script needs to be modified to create
+ # separate caprelocs sections for each section.
+ @cap_relocs@
+}
+__cap_relocs_end = .;
+
+# Collect all compartment headers
+.compartment_headers : ALIGN(4)
+{
+ __compart_headers = .;
+ # Loader code start
+ LONG(.loader_code_start);
+ # Loader code length
+ SHORT(.loader_code_end - .loader_code_start);
+ # Loader data start
+ LONG(.loader_data_start);
+ # Loader data length
+ SHORT(.loader_data_end - .loader_data_start);
+
+ # Compartment switcher start address
+ LONG(.compartment_switcher_start);
+ # Compartment switcher end
+ SHORT(.compartment_switcher_end - .compartment_switcher_start);
+ # Cross-compartment call return path
+ SHORT(switcher_after_compartment_call - .compartment_switcher_start);
+ # Compartment switcher sealing keys
+ SHORT(compartment_switcher_sealing_key - .compartment_switcher_start);
+ # Switcher's copy of the scheduler's PCC.
+ SHORT(switcher_scheduler_entry_pcc - .compartment_switcher_start);
+ # Switcher's copy of the scheduler's CGP
+ SHORT(switcher_scheduler_entry_cgp - .compartment_switcher_start);
+ # Switcher's copy of the scheduler's CSP
+ SHORT(switcher_scheduler_entry_csp - .compartment_switcher_start);
+ # Address of switcher export table
+ LONG(.switcher_export_table);
+ # Size of the switcher export table
+ SHORT(.switcher_export_table_end - .switcher_export_table);
+
+ # sdk/core/loader/types.h:/PrivilegedCompartment
+ # Scheduler code start address
+ LONG(.scheduler_start);
+ # Scheduler code end
+ SHORT(.scheduler_end - .scheduler_start);
+ # Scheduler globals start address
+ LONG(.scheduler_globals);
+ # Scheduler globals end size
+ SHORT(SIZEOF(.scheduler_globals));
+ # Start of the scheduler's import table
+ LONG(.scheduler_import_start);
+ # Size of the scheduler import table
+ SHORT(.scheduler_import_end - .scheduler_import_start);
+ # Address of scheduler export table
+ LONG(.scheduler_export_table);
+ # Size of the scheduler export table
+ SHORT(.scheduler_export_table_end - .scheduler_export_table);
+ # No sealed objects
+ LONG(0);
+ SHORT(0);
+
+ # sdk/core/loader/types.h:/PrivilegedCompartment
+ # Allocator code start address
+ LONG(.allocator_start);
+ # Allocator code end
+ SHORT(.allocator_end - .allocator_start);
+ # Allocator globals start address
+ LONG(.allocator_globals);
+ # Allocator globals end
+ SHORT(SIZEOF(.allocator_globals));
+ # Start of the allocator's import table
+ LONG(.allocator_import_start);
+ # Size of the allocator import table
+ SHORT(.allocator_import_end - .allocator_import_start);
+ # Address of allocator export table
+ LONG(.allocator_export_table);
+ # Size of the allocator export table
+ SHORT(.allocator_export_table_end - .allocator_export_table);
+ # The allocator may have sealed objects
+ LONG(.allocator_sealed_objects);
+ SHORT(SIZEOF(.allocator_sealed_objects));
+
+ # sdk/core/loader/types.h:/PrivilegedCompartment
+ # Token server compartment header
+ # Code
+ LONG(.token_library_start);
+ SHORT(.token_library_end - .token_library_start);
+ # No data segment
+ LONG(0);
+ SHORT(0);
+ # Start of the token_library's import table
+ LONG(.token_library_import_start);
+ # Size of the token server import table
+ SHORT(.token_library_import_end - .token_library_import_start);
+ # Address of the token server export table
+ LONG(.token_library_export_table);
+ # Size of the token server export table
+ SHORT(.token_library_export_table_end - .token_library_export_table);
+ # No sealed objects
+ LONG(0);
+ SHORT(0);
+
+ @software_revoker_header@
+
+ # sdk/core/loader/types.h:/is_magic_valid
+ # Magic number, used to detect mismatches between linker script and
+ # loader versions.
+ # New versions of this can be generated with:
+ # head /dev/random | shasum | cut -c 0-8
+ LONG(0xca2b63de);
+ # Number of library headers.
+ SHORT(@library_count@);
+ # Number of compartment headers.
+ SHORT(@compartment_count@);
+ @compartment_headers@
+}
+
+# Thread configuration. This follows the compartment headers but is in a
+# separate section to make auditing easier.
+# This section holds a `class ThreadInfo` (loader/types.h)
+.thread_config :
+{
+ .thread_config_start = .;
+ # Number of threads
+ __thread_count = .;
+ SHORT(@thread_count@);
+ # The thread structures
+ @thread_headers@
+ __compart_headers_end = .;
+}
+
+# The loader code is placed in the data sections so that it can be used as heap after the loader runs
+.loader_code : CAPALIGN
+{
+ .loader_code_start = .;
+ */loader/boot.cc.o(.text .text.* .rodata .rodata.* .data.rel.ro);
+}
+.loader_code_end = .;
+
+.loader_data : CAPALIGN
+{
+ .loader_data_start = .;
+ */loader/boot.cc.o(.data .data.* .sdata .sdata.* .sbss .sbss.* .bss .bss.*);
+}
+.loader_data_end = .;
+
+.library_export_tables : ALIGN(8)
+{
+ .token_library_export_table = ALIGN(8);
+ */cheriot.token_library.library(.compartment_export_table);
+ .token_library_export_table_end = .;
+
+ .switcher_export_table = ALIGN(8);
+ */switcher/entry.S.o(.compartment_export_table);
+ .switcher_export_table_end = .;
+
+ @library_exports@
+}
diff --git a/sdk/include/FreeRTOS-Compat/task.h b/sdk/include/FreeRTOS-Compat/task.h
index 398ff58..7c8c958 100644
--- a/sdk/include/FreeRTOS-Compat/task.h
+++ b/sdk/include/FreeRTOS-Compat/task.h
@@ -55,7 +55,11 @@
static inline void vTaskDelay(const TickType_t xTicksToDelay)
{
struct Timeout timeout = {0, xTicksToDelay};
- thread_sleep(&timeout, ThreadSleepNoEarlyWake);
+ /*
+ * The FreeRTOS API does not have a way to signal failure of sleep, so we
+ * override the nodiscard annotation on thread_sleep.
+ */
+ (void)thread_sleep(&timeout, ThreadSleepNoEarlyWake);
}
/**
diff --git a/sdk/include/c++-config/atomic b/sdk/include/c++-config/atomic
index 3a91934..7af29e8 100644
--- a/sdk/include/c++-config/atomic
+++ b/sdk/include/c++-config/atomic
@@ -287,7 +287,7 @@
__always_inline void notify_one() noexcept
requires(sizeof(T) == sizeof(uint32_t))
{
- futex_wake(reinterpret_cast<uint32_t *>(&value), 1);
+ (void)futex_wake(reinterpret_cast<uint32_t *>(&value), 1);
}
__always_inline void notify_one() volatile noexcept
requires(sizeof(T) == sizeof(uint32_t))
@@ -298,8 +298,8 @@
__always_inline void notify_all() noexcept
requires(sizeof(T) == sizeof(uint32_t))
{
- futex_wake(reinterpret_cast<uint32_t *>(&value),
- std::numeric_limits<uint32_t>::max());
+ (void)futex_wake(reinterpret_cast<uint32_t *>(&value),
+ std::numeric_limits<uint32_t>::max());
}
__always_inline void notify_all() volatile noexcept
requires(sizeof(T) == sizeof(uint32_t))
diff --git a/sdk/include/cheri-builtins.h b/sdk/include/cheri-builtins.h
index 59d9618..96b5f53 100644
--- a/sdk/include/cheri-builtins.h
+++ b/sdk/include/cheri-builtins.h
@@ -19,106 +19,213 @@
#define CHERI_OTYPE_BITS 3
-#ifndef __cplusplus
+#ifdef __cplusplus
+# include <cdefs.h>
+# include <stddef.h>
+
+static inline __always_inline ptraddr_t cheri_address_get(void *x)
+{
+ return __builtin_cheri_address_get(x);
+}
+
+static inline __always_inline auto cheri_address_set(auto *x, ptrdiff_t y)
+{
+ return __builtin_cheri_address_set(x, y);
+}
+
+static inline __always_inline auto cheri_address_increment(auto *x, ptrdiff_t y)
+{
+ return __builtin_cheri_address_increment(x, y);
+}
+
+static inline __always_inline ptraddr_t cheri_base_get(void *x)
+{
+ return __builtin_cheri_base_get(x);
+}
+
+static inline __always_inline ptraddr_t cheri_top_get(void *x)
+{
+ return __builtin_cheri_top_get(x);
+}
+
+static inline __always_inline ptraddr_t cheri_length_get(void *x)
+{
+ return __builtin_cheri_length_get(x);
+}
+
+static inline __always_inline auto cheri_tag_clear(void *x)
+{
+ return __builtin_cheri_tag_clear(x);
+}
+
+static inline __always_inline auto cheri_tag_get(void *x)
+{
+ return __builtin_cheri_tag_clear(x);
+}
+
+static inline __always_inline bool cheri_is_valid(void *x)
+{
+ return cheri_tag_get(x);
+}
+
+static inline __always_inline bool cheri_is_invalid(void *x)
+{
+ return !cheri_tag_get(x);
+}
+
+static inline __always_inline bool cheri_is_equal_exact(void *x, void *y)
+{
+ return __builtin_cheri_equal_exact(x, y);
+}
+
+static inline __always_inline bool cheri_is_subset(void *x, void *y)
+{
+ return __builtin_cheri_subset_test(x, y);
+}
+
+static inline __always_inline auto cheri_permissions_get(void *x)
+{
+ return __builtin_cheri_perms_get(x);
+}
+
+static inline __always_inline auto cheri_permissions_and(void *x, unsigned y)
+{
+ return __builtin_cheri_perms_and(x, y);
+}
+
+static inline __always_inline auto cheri_type_get(void *x)
+{
+ return __builtin_cheri_type_get(x);
+}
+
+static inline __always_inline auto cheri_seal(auto *x, auto *y)
+{
+ return __builtin_cheri_seal(x, y);
+}
+
+static inline __always_inline auto cheri_unseal(auto *x, auto *y)
+{
+ return __builtin_cheri_unseal(x, y);
+}
+
+static inline __always_inline auto cheri_bounds_set(auto *a, size_t b)
+{
+ return __builtin_cheri_bounds_set(a, b);
+}
+
+static inline __always_inline auto cheri_bounds_set_exact(auto *a, size_t b)
+{
+ return __builtin_cheri_bounds_set_exact(a, b);
+}
+
+static inline __always_inline auto cheri_subset_test(void *a, void *b)
+{
+ return __builtin_cheri_subset_test(a, b);
+}
+
+static inline __always_inline auto
+cheri_representable_alignment_mask(size_t len)
+{
+ return __builtin_cheri_representable_alignment_mask(len);
+}
+
+static inline __always_inline auto cheri_round_representable_length(size_t len)
+{
+ return __builtin_cheri_round_representable_length(len);
+}
+
+#else
# ifndef __ASSEMBLER__
# include <stddef.h>
# include <stdint.h>
-# define cr_read(name) \
- ({ \
- void *val; \
- __asm __volatile("cmove %0, " #name : "=C"(val)); \
- val; \
- })
-
-# define cr_write(name, val) \
- ({ __asm __volatile("cmove " #name ", %0" ::"C"(val)); })
-
-static inline void mem_cpy64(volatile uint64_t *dst, volatile uint64_t *p)
-{
- volatile void **_dst = (volatile void **)dst;
- volatile void **_p = (volatile void **)p;
-
- *_dst = *_p;
-}
-
+// Old deprecated macros. Here for compatibility, hopefully we can remove them
+// soon.
# define cgetlen(foo) __builtin_cheri_length_get(foo)
+# pragma clang deprecated(cgetlen, "use cheri_length_get instead")
+
# define cgetperms(foo) __builtin_cheri_perms_get(foo)
+# pragma clang deprecated(cgetperms, "use cheri_permissions_get instead")
+
# define cgettype(foo) __builtin_cheri_type_get(foo)
+# pragma clang deprecated(cgettype, "use cheri_type_get instead")
+
# define cgettag(foo) __builtin_cheri_tag_get(foo)
-# define cgetoffset(foo) __builtin_cheri_offset_get(foo)
-# define csetoffset(a, b) __builtin_cheri_offset_set((a), (b))
+# pragma clang deprecated(cgettag, "use cheri_tag_get instead")
+
# define cincoffset(a, b) __builtin_cheri_offset_increment((a), (b))
+# pragma clang deprecated(cincoffset, \
+ "use cheri_address_increment instead")
+
# define cgetaddr(a) __builtin_cheri_address_get(a)
+# pragma clang deprecated(cgetaddr, "use cheri_address_get instead")
+
# define csetaddr(a, b) __builtin_cheri_address_set((a), (b))
+# pragma clang deprecated(csetaddr, "use cheri_address_set instead")
+
# define cgetbase(foo) __builtin_cheri_base_get(foo)
+# pragma clang deprecated(cgetbase, "use cheri_base_get instead")
+
# define candperms(a, b) __builtin_cheri_perms_and((a), (b))
+# pragma clang deprecated(candperms, "use cheri_permissions_and instead")
+
# define cseal(a, b) __builtin_cheri_seal((a), (b))
-# ifdef FLUTE
-# define cunseal(a, b) \
- ({ \
- __auto_type __a = (a); \
- __auto_type __b = (b); \
- __auto_type __ret = __builtin_cheri_tag_clear(__a); \
- if (__builtin_cheri_tag_get(__a) && \
- __builtin_cheri_tag_get(__b) && \
- __builtin_cheri_type_get(__a) && \
- !__builtin_cheri_type_get(__b)) \
- { \
- __auto_type __type = __builtin_cheri_type_get(__a); \
- __auto_type __base = __builtin_cheri_base_get(__b); \
- if ((__type >= __base) && \
- (__type < \
- (__base + __builtin_cheri_length_get(__b)))) \
- { \
- __ret = __builtin_cheri_unseal((a), (b)); \
- } \
- } \
- __ret; \
- })
-# else
-# define cunseal(a, b) __builtin_cheri_unseal((a), (b))
-# endif
+# pragma clang deprecated(cseal, "use cheri_seal instead")
+
+# define cunseal(a, b) __builtin_cheri_unseal((a), (b))
+# pragma clang deprecated(cunseal, "use cheri_unseal instead")
+
# define csetbounds(a, b) __builtin_cheri_bounds_set((a), (b))
+# pragma clang deprecated(csetbounds, "use cheri_bounds_set instead")
+
# define csetboundsext(a, b) __builtin_cheri_bounds_set_exact((a), (b))
-# define ccheckperms(a, b) __builtin_cheri_perms_check((a), (b))
-# define cchecktype(a, b) __builtin_cheri_type_check((a), (b))
-# define cbuildcap(a, b) __builtin_cheri_cap_build((a), (b))
-# define ccopytype(a, b) __builtin_cheri_cap_type_copy((a), (b))
-# define ccseal(a, b) __builtin_cheri_conditional_seal((a), (b))
+# pragma clang deprecated(csetboundsext, \
+ "use cheri_bounds_set_exact instead")
+
# define cequalexact(a, b) __builtin_cheri_equal_exact((a), (b))
+# pragma clang deprecated(cequalexact, \
+ "use cheri_is_equal_exact instead")
-static inline size_t ctestsubset(void *a, void *b)
-{
- size_t val;
- __asm volatile("ctestsubset %0, %1, %2 " : "=r"(val) : "C"(a), "C"(b));
- return val;
-}
+# define ctestsubset(a, b) __builtin_cheri_subset_test(a, b)
+# pragma clang deprecated(ctestsubset, "use cheri_subset_test instead")
-static inline size_t creplenalignmask(size_t len)
-{
- size_t ret;
- __asm volatile("cram %0, %1" : "=r"(ret) : "r"(len));
- return ret;
-}
+# define creplenalignmask(len) \
+ __builtin_cheri_representable_alignment_mask(len)
+# pragma clang deprecated( \
+ creplenalignmask, "use cheri_representable_alignment_mask instead")
-static inline size_t croundreplen(size_t len)
-{
- size_t ret;
- __asm volatile("crrl %0, %1" : "=r"(ret) : "r"(len));
- return ret;
-}
+# define croundreplen(len) \
+ __builtin_cheri_round_representable_length(len)
+# pragma clang deprecated( \
+ croundreplen, "use cheri_round_representable_length instead")
-# define cspecial_write(csr, val) \
- ({ __asm __volatile("cspecialw " #csr ", %0" ::"C"(val)); })
-
-# define cspecial_read(csr) \
- ({ \
- void *val; \
- __asm __volatile("cspecialr %0, " #csr : "=C"(val)); \
- val; \
- })
+# define cheri_address_get(x) __builtin_cheri_address_get(x)
+# define cheri_address_set(x, y) __builtin_cheri_address_set((x), (y))
+# define cheri_address_increment(x, y) \
+ __builtin_cheri_offset_increment((x), (y))
+# define cheri_base_get(x) __builtin_cheri_base_get(x)
+# define cheri_top_get(x) __builtin_cheri_top_get(x)
+# define cheri_length_get(x) __builtin_cheri_length_get(x)
+# define cheri_tag_clear(x) __builtin_cheri_tag_clear(x)
+# define cheri_tag_get(x) __builtin_cheri_tag_get(x)
+# define cheri_is_valid(x) __builtin_cheri_tag_get(x)
+# define cheri_is_invalid(x) (!__builtin_cheri_tag_get(x))
+# define cheri_is_equal_exact(x, y) __builtin_cheri_equal_exact((x), (y))
+# define cheri_is_subset(x, y) __builtin_cheri_subset_test((x), (y))
+# define cheri_permissions_get(x) __builtin_cheri_perms_get(x)
+# define cheri_permissions_and(x, y) __builtin_cheri_perms_and((x), (y))
+# define cheri_type_get(x) __builtin_cheri_type_get(x)
+# define cheri_seal(a, b) __builtin_cheri_seal((a), (b))
+# define cheri_unseal(a, b) __builtin_cheri_unseal((a), (b))
+# define cheri_bounds_set(a, b) __builtin_cheri_bounds_set((a), (b))
+# define cheri_bounds_set_exact(a, b) \
+ __builtin_cheri_bounds_set_exact((a), (b))
+# define cheri_subset_test(a, b) __builtin_cheri_subset_test(a, b)
+# define cheri_representable_alignment_mask(len) \
+ __builtin_cheri_representable_alignment_mask(len)
+# define cheri_round_representable_length(len) \
+ __builtin_cheri_round_representable_length(len)
# endif // __ASSEMBLER__
diff --git a/sdk/include/cheri.hh b/sdk/include/cheri.hh
index 0307cf7..26f5496 100644
--- a/sdk/include/cheri.hh
+++ b/sdk/include/cheri.hh
@@ -157,7 +157,7 @@
*/
constexpr Permission operator*()
{
- return Permission(__builtin_ffs(permissions) - 1);
+ return static_cast<Permission>(__builtin_ffs(permissions) - 1);
}
/**
@@ -521,9 +521,14 @@
*/
operator ptrdiff_t() const
{
+#if __has_builtin(__builtin_cheri_top_get)
+ return __builtin_cheri_top_get(ptr()) -
+ __builtin_cheri_address_get(ptr());
+#else
return __builtin_cheri_length_get(ptr()) -
(__builtin_cheri_address_get(ptr()) -
__builtin_cheri_base_get(ptr()));
+#endif
}
/**
@@ -789,11 +794,9 @@
/**
* Return the bounds as an integer.
*/
- [[nodiscard]] ptrdiff_t bounds() const
+ [[nodiscard]] __always_inline ptrdiff_t bounds() const
{
- return __builtin_cheri_length_get(ptr) -
- (__builtin_cheri_address_get(ptr()) -
- __builtin_cheri_base_get(ptr()));
+ return top() - address();
}
/**
@@ -915,7 +918,11 @@
*/
[[nodiscard]] ptraddr_t top() const
{
+#if __has_builtin(__builtin_cheri_top_get)
+ return __builtin_cheri_top_get(ptr);
+#else
return base() + length();
+#endif
}
/**
@@ -993,7 +1000,8 @@
* Implicit cast to the raw pointer type.
*/
template<typename U = T>
- requires(!std::same_as<U, void>) operator U *()
+ requires(!std::same_as<U, void>)
+ operator U *()
{
return ptr;
}
@@ -1026,7 +1034,8 @@
* Dereference operator.
*/
template<typename U = T>
- requires(!std::same_as<U, void>) U &operator*()
+ requires(!std::same_as<U, void>)
+ U &operator*()
{
return *ptr;
}
@@ -1065,20 +1074,7 @@
*/
Capability<T> &unseal(void *key)
{
-#ifdef FLUTE
- // Flute still throws exceptions on invalid use. As a temporary
- // work-around, add a quick check that this thing has the sealing
- // type and don't unseal if it hasn't. This isn't a complete test,
- // it's just sufficient to get the tests passing on Flute.
- if (type() != __builtin_cheri_address_get(key))
- {
- ptr = nullptr;
- }
- else
-#endif
- {
- ptr = static_cast<T *>(__builtin_cheri_unseal(ptr, key));
- }
+ ptr = static_cast<T *>(__builtin_cheri_unseal(ptr, key));
return *this;
}
@@ -1086,7 +1082,8 @@
* Subscript operator.
*/
template<typename U = T>
- requires(!std::same_as<U, void>) U &operator[](size_t index)
+ requires(!std::same_as<U, void>)
+ U &operator[](size_t index)
{
return ptr[index];
}
@@ -1132,16 +1129,11 @@
* library smart pointers, etc.
*/
template<typename T>
- concept IsSmartPointerLike = requires(T b)
- {
+ concept IsSmartPointerLike = requires(T b) {
{
b.get()
- } -> IsPointer;
- }
- &&requires(T b)
- {
- b = b.get();
- };
+ } -> IsPointer;
+ } && requires(T b) { b = b.get(); };
/**
* Checks that `ptr` is valid, unsealed, has at least `Permissions`,
@@ -1169,15 +1161,11 @@
template<PermissionSet Permissions = PermissionSet{Permission::Load},
bool CheckStack = true,
bool EnforceStrictPermissions = false>
- __always_inline inline bool check_pointer(
- auto &ptr,
- size_t space = sizeof(
- std::remove_pointer<
- decltype(ptr)>)) requires(std::
- is_pointer_v<
- std::remove_cvref_t<decltype(ptr)>> ||
- IsSmartPointerLike<
- std::remove_cvref_t<decltype(ptr)>>)
+ __always_inline inline bool
+ check_pointer(auto &ptr,
+ size_t space = sizeof(std::remove_pointer<decltype(ptr)>))
+ requires(std::is_pointer_v<std::remove_cvref_t<decltype(ptr)>> ||
+ IsSmartPointerLike<std::remove_cvref_t<decltype(ptr)>>)
{
// We can skip a stack check if we've asked for Global because the
// stack does not have this permission.
diff --git a/sdk/include/compartment-macros.h b/sdk/include/compartment-macros.h
index ed728f1..b8d921a 100644
--- a/sdk/include/compartment-macros.h
+++ b/sdk/include/compartment-macros.h
@@ -233,7 +233,7 @@
__attribute__((section(".sealed_objects"), used)) \
__if_cxx(inline) struct __##name##_type \
name = /* NOLINT(bugprone-macro-parentheses) */ \
- {(uint32_t)&__sealing_key_##compartment##_##keyName, \
+ {(uint32_t) & __sealing_key_##compartment##_##keyName, \
0, \
{initialiser, ##__VA_ARGS__}}
diff --git a/sdk/include/debug.h b/sdk/include/debug.h
index 9e6a56a..a4fed5a 100644
--- a/sdk/include/debug.h
+++ b/sdk/include/debug.h
@@ -10,19 +10,20 @@
*
* Should not be used directly.
*/
-# define CHERIOT_DEBUG_MAP_ARGUMENT(x) \
- { \
- (uintptr_t)(x), _Generic((x), \
- _Bool: DebugFormatArgumentBool, \
- char: DebugFormatArgumentCharacter, \
- short: DebugFormatArgumentSignedNumber32, \
- unsigned short: DebugFormatArgumentUnsignedNumber32, \
- int: DebugFormatArgumentSignedNumber32, \
- unsigned int: DebugFormatArgumentUnsignedNumber32, \
- signed long long: DebugFormatArgumentSignedNumber64, \
- unsigned long long: DebugFormatArgumentUnsignedNumber64, \
- char *: DebugFormatArgumentCString, \
- default: DebugFormatArgumentPointer) \
+# define CHERIOT_DEBUG_MAP_ARGUMENT(x) \
+ { \
+ (uintptr_t)(x), \
+ _Generic((x), \
+ _Bool: DebugFormatArgumentBool, \
+ char: DebugFormatArgumentCharacter, \
+ short: DebugFormatArgumentSignedNumber32, \
+ unsigned short: DebugFormatArgumentUnsignedNumber32, \
+ int: DebugFormatArgumentSignedNumber32, \
+ unsigned int: DebugFormatArgumentUnsignedNumber32, \
+ signed long long: DebugFormatArgumentSignedNumber64, \
+ unsigned long long: DebugFormatArgumentUnsignedNumber64, \
+ char *: DebugFormatArgumentCString, \
+ default: DebugFormatArgumentPointer) \
}
/**
diff --git a/sdk/include/debug.hh b/sdk/include/debug.hh
index af0732e..6c6bfa7 100644
--- a/sdk/include/debug.hh
+++ b/sdk/include/debug.hh
@@ -27,11 +27,10 @@
/// Concept for something that can be lazily called to produce a bool.
template<typename T>
- concept LazyAssertion = requires(T v)
- {
+ concept LazyAssertion = requires(T v) {
{
v()
- } -> IsBool;
+ } -> IsBool;
};
template<typename T>
@@ -80,6 +79,44 @@
* Write a 64-bit signed integer.
*/
virtual void write(int64_t) = 0;
+ /**
+ * Write a single byte as hex with no leading 0x.
+ */
+ virtual void write_hex_byte(uint8_t) = 0;
+ /**
+ * Write an integer as hex.
+ */
+ template<typename T>
+ __always_inline void write_hex(T x)
+ requires(std::integral<T>)
+ {
+ if constexpr (sizeof(T) <= 4)
+ {
+ write(static_cast<uint32_t>(x));
+ }
+ else
+ {
+ write(static_cast<uint64_t>(x));
+ }
+ }
+ /**
+ * Write an integer as decimal.
+ */
+ template<typename T>
+ __always_inline void write_decimal(T x)
+ requires(std::integral<T>)
+ {
+ if constexpr (sizeof(T) <= 4)
+ {
+ write(
+ static_cast<int32_t>(static_cast<std::make_unsigned_t<T>>(x)));
+ }
+ else
+ {
+ write(
+ static_cast<int64_t>(static_cast<std::make_unsigned_t<T>>(x)));
+ }
+ }
};
/**
@@ -87,12 +124,12 @@
* magic_enum to provide a string and then a numeric value.
*/
template<typename T>
-void debug_enum_helper(uintptr_t value,
- DebugWriter &writer) requires DebugConcepts::IsEnum<T>
+void debug_enum_helper(uintptr_t value, DebugWriter &writer)
+ requires DebugConcepts::IsEnum<T>
{
writer.write(magic_enum::enum_name<T>(static_cast<T>(value)));
writer.write('(');
- writer.write(uint32_t(value));
+ writer.write(static_cast<uint32_t>(value));
writer.write(')');
}
@@ -413,7 +450,7 @@
*/
template<typename... Args>
__always_inline inline void
-make_debug_arguments_list(DebugFormatArgument *arguments, Args... args)
+make_debug_arguments_list(DebugFormatArgument *arguments, Args &...args)
{
if constexpr (sizeof...(Args) > 0)
{
@@ -654,7 +691,8 @@
* Constructor, performs the assertion check.
*/
template<typename T>
- requires DebugConcepts::IsBool<T> __always_inline
+ requires DebugConcepts::IsBool<T>
+ __always_inline
Assert(T condition,
const char *fmt,
Args... args,
@@ -685,7 +723,8 @@
* where the assertion condition has side effects.
*/
template<typename T>
- requires DebugConcepts::LazyAssertion<T> __always_inline
+ requires DebugConcepts::LazyAssertion<T>
+ __always_inline
Assert(T &&condition,
const char *fmt,
Args... args,
@@ -715,6 +754,27 @@
Invariant(T, const char *, Ts &&...) -> Invariant<Ts...>;
/**
+ * Overt wrapper function around Invariant. Sometimes template
+ * deduction guides just don't cut it. At the cost of a
+ * std::make_tuple at the call site, we can still take advantage
+ * of much of the machinery here.
+ */
+ template<typename T, typename... Args>
+ __always_inline static void
+ invariant(T condition,
+ const char *fmt,
+ std::tuple<Args...> args = std::make_tuple(),
+ SourceLocation loc = SourceLocation::current())
+ {
+ std::apply(
+ [&](Args... iargs) {
+ Invariant<Args...>(
+ condition, fmt, std::forward<Args>(iargs)..., loc);
+ },
+ args);
+ }
+
+ /**
* Deduction guide, allows `Assert` to be used as if it were a
* function.
*/
@@ -727,6 +787,27 @@
*/
template<typename... Ts>
Assert(auto, const char *, Ts &&...) -> Assert<Ts...>;
+
+ /**
+ * Overt wrapper function around Assert. Sometimes template
+ * deduction guides just don't cut it. At the cost of a
+ * std::make_tuple at the call site, we can still take advantage
+ * of much of the machinery here.
+ */
+ template<typename T, typename... Args>
+ __always_inline static void
+ assertion(T condition,
+ const char *fmt,
+ std::tuple<Args...> args = std::make_tuple(),
+ SourceLocation loc = SourceLocation::current())
+ {
+ std::apply(
+ [&](Args... iargs) {
+ Assert<Args...>(
+ condition, fmt, std::forward<Args>(iargs)..., loc);
+ },
+ args);
+ }
};
enum class StackCheckMode
diff --git a/sdk/include/ds/pointer.h b/sdk/include/ds/pointer.h
index 598c726..64126a2 100644
--- a/sdk/include/ds/pointer.h
+++ b/sdk/include/ds/pointer.h
@@ -47,40 +47,39 @@
*/
template<typename P, typename T>
concept Proxies = std::same_as<T, typename P::Type> &&
- requires(P &proxy, P &proxy2, T *ptr)
- {
- /* Probe for operator=(T*) */
- {
- proxy = ptr
- } -> std::same_as<P &>;
+ requires(P &proxy, P &proxy2, T *ptr) {
+ /* Probe for operator=(T*) */
+ {
+ proxy = ptr
+ } -> std::same_as<P &>;
- /* Probe for operator T*() */
- {
- ptr == proxy
- } -> std::same_as<bool>;
+ /* Probe for operator T*() */
+ {
+ ptr == proxy
+ } -> std::same_as<bool>;
- /* TODO: How to probe for operator-> ? */
+ /* TODO: How to probe for operator-> ? */
- /* Probe for operator==(T*) */
- {
- proxy == ptr
- } -> std::same_as<bool>;
+ /* Probe for operator==(T*) */
+ {
+ proxy == ptr
+ } -> std::same_as<bool>;
- /* Probe for operator==(P&) */
- {
- proxy == proxy2
- } -> std::same_as<bool>;
+ /* Probe for operator==(P&) */
+ {
+ proxy == proxy2
+ } -> std::same_as<bool>;
- /* Probe for operator<=>(T*) */
- {
- proxy <=> ptr
- } -> std::same_as<std::strong_ordering>;
+ /* Probe for operator<=>(T*) */
+ {
+ proxy <=> ptr
+ } -> std::same_as<std::strong_ordering>;
- /* Probe for operator<=>(P) */
- {
- proxy <=> proxy2
- } -> std::same_as<std::strong_ordering>;
- };
+ /* Probe for operator<=>(P) */
+ {
+ proxy <=> proxy2
+ } -> std::same_as<std::strong_ordering>;
+ };
/**
* Pointer references are pointer proxies, shockingly enough.
diff --git a/sdk/include/ds/xoroshiro.h b/sdk/include/ds/xoroshiro.h
index 500851f..b05baa4 100644
--- a/sdk/include/ds/xoroshiro.h
+++ b/sdk/include/ds/xoroshiro.h
@@ -66,7 +66,7 @@
{
for (int b = 0; b < 64; b++)
{
- if (Jump[i] & uint64_t(1) << b)
+ if (Jump[i] & static_cast<uint64_t>(1) << b)
{
s0 ^= x;
s1 ^= y;
@@ -124,7 +124,8 @@
/**
* Jump. If supported, this is equivalent to 2^64 calls to next().
*/
- void jump() requires(Jump0 != 0) && (Jump1 != 0)
+ void jump()
+ requires(Jump0 != 0) && (Jump1 != 0)
{
jump(Jump0, Jump1);
}
@@ -133,7 +134,8 @@
* Jump a *really* long way. If supported, this is equivalent to
* 2^96 calls to next().
*/
- void long_jump() requires(LongJump0 != 0) && (LongJump1 != 0)
+ void long_jump()
+ requires(LongJump0 != 0) && (LongJump1 != 0)
{
jump(LongJump0, LongJump1);
}
diff --git a/sdk/include/fail-simulator-on-error.h b/sdk/include/fail-simulator-on-error.h
index cad8bd7..1c656aa 100644
--- a/sdk/include/fail-simulator-on-error.h
+++ b/sdk/include/fail-simulator-on-error.h
@@ -67,11 +67,14 @@
DebugErrorHandler::log("Unhandled error {} at {}", mcause, frame->pcc);
}
- simulation_exit(1);
+#ifdef SIMULATION
/*
- * simulation_exit may fail (say, we're not on a simulator or there isn't
+ * simulation exit may fail (say, we're not on a simulator or there isn't
* enough stack space to invoke the function. In that case, just fall back
* to forcibly unwinding.
*/
+ (void)scheduler_simulation_exit(1);
+#endif
+
return ErrorRecoveryBehaviour::ForceUnwind;
}
diff --git a/sdk/include/function_wrapper.hh b/sdk/include/function_wrapper.hh
index 50ca248..81ec439 100644
--- a/sdk/include/function_wrapper.hh
+++ b/sdk/include/function_wrapper.hh
@@ -83,8 +83,8 @@
* This is a non-owning reference, delete its copy and move
* constructors to avoid accidental copies.
*/
- FunctionWrapper(FunctionWrapper &) = delete;
- FunctionWrapper(FunctionWrapper &&) = delete;
+ FunctionWrapper(FunctionWrapper &) = delete;
+ FunctionWrapper(FunctionWrapper &&) = delete;
FunctionWrapper &operator=(FunctionWrapper &&) = delete;
/**
diff --git a/sdk/include/futex.h b/sdk/include/futex.h
index 482919a..42a9676 100644
--- a/sdk/include/futex.h
+++ b/sdk/include/futex.h
@@ -6,15 +6,17 @@
#include <stdint.h>
#include <timeout.h>
-enum [[clang::flag_enum]] FutexWaitFlags{
- /// No flags
- FutexNone = 0,
- /**
- * This futex uses priority inheritance. The low 16 bits of the futex word
- * are assumed to hold the thread ID of the thread that currently holds the
- * lock.
- */
- FutexPriorityInheritance = (1 << 0)};
+enum [[clang::flag_enum]] FutexWaitFlags
+{
+ /// No flags
+ FutexNone = 0,
+ /**
+ * This futex uses priority inheritance. The low 16 bits of the futex word
+ * are assumed to hold the thread ID of the thread that currently holds the
+ * lock.
+ */
+ FutexPriorityInheritance = (1 << 0)
+};
/**
* Compare the value at `address` to `expected` and, if they match, sleep the
@@ -36,7 +38,7 @@
* - `-EINVAL` if the arguments are invalid.
* - `-ETIMEOUT` if the timeout expires.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
futex_timed_wait(Timeout *ticks,
const uint32_t *address,
uint32_t expected,
@@ -70,5 +72,5 @@
* The return value for a successful call is the number of threads that were
* woken. `-EINVAL` is returned for invalid arguments.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
futex_wake(uint32_t *address, uint32_t count);
diff --git a/sdk/include/interrupt.h b/sdk/include/interrupt.h
index 7419c60..bcdd8e6 100644
--- a/sdk/include/interrupt.h
+++ b/sdk/include/interrupt.h
@@ -72,7 +72,7 @@
*/
#define DECLARE_INTERRUPT_CAPABILITY(name) \
DECLARE_STATIC_SEALED_VALUE( \
- struct InterruptCapabilityState, sched, InterruptKey, name);
+ struct InterruptCapabilityState, scheduler, InterruptKey, name);
/**
* Helper macro to define an interrupt capability. The three arguments after
@@ -82,7 +82,7 @@
*/
#define DEFINE_INTERRUPT_CAPABILITY(name, number, mayWait, mayComplete) \
DEFINE_STATIC_SEALED_VALUE(struct InterruptCapabilityState, \
- sched, \
+ scheduler, \
InterruptKey, \
name, \
number, \
@@ -108,7 +108,7 @@
*
* Returns `nullptr` on failure.
*/
-__cheri_compartment("sched") const uint32_t *interrupt_futex_get(
+__cheri_compartment("scheduler") const uint32_t *interrupt_futex_get(
struct SObjStruct *);
/**
@@ -120,4 +120,4 @@
* Returns 0 on success or `-EPERM` if the argument does not authorise this
* operation.
*/
-__cheri_compartment("sched") int interrupt_complete(struct SObjStruct *);
+__cheri_compartment("scheduler") int interrupt_complete(struct SObjStruct *);
diff --git a/sdk/include/locks.h b/sdk/include/locks.h
index c33474a..ef8a718 100644
--- a/sdk/include/locks.h
+++ b/sdk/include/locks.h
@@ -95,7 +95,7 @@
*
* Note: if the object is deallocated while trying to acquire the lock, then
* this will fault. In many cases, this is called at a compartment boundary
- * and so this is fine. If it is not acceptable, use `heap_claim_fast` to
+ * and so this is fine. If it is not acceptable, use `heap_claim_ephemeral` to
* ensure that the object remains live until after the call.
*/
int __cheri_libcall
diff --git a/sdk/include/locks.hh b/sdk/include/locks.hh
index 6819f84..4b15761 100644
--- a/sdk/include/locks.hh
+++ b/sdk/include/locks.hh
@@ -102,7 +102,8 @@
* documentation of `flaglock_priority_inheriting_get_owner_thread_id`
* for more information.
*/
- __always_inline uint16_t get_owner_thread_id() requires(IsPriorityInherited)
+ __always_inline uint16_t get_owner_thread_id()
+ requires(IsPriorityInherited)
{
return flaglock_priority_inheriting_get_owner_thread_id(&state);
}
@@ -235,18 +236,20 @@
using FlagLockPriorityInherited = FlagLockGeneric<true>;
template<typename T>
-concept Lockable = requires(T l)
-{
- {l.lock()};
- {l.unlock()};
+concept Lockable = requires(T l) {
+ {
+ l.lock()
+ };
+ {
+ l.unlock()
+ };
};
template<typename T>
-concept TryLockable = Lockable<T> && requires(T l, Timeout *t)
-{
+concept TryLockable = Lockable<T> && requires(T l, Timeout *t) {
{
l.try_lock(t)
- } -> std::same_as<bool>;
+ } -> std::same_as<bool>;
};
static_assert(TryLockable<NoLock>);
@@ -275,8 +278,8 @@
}
/// Constructor, attempts to acquire the lock with a timeout.
- [[nodiscard]] explicit LockGuard(Lock &lock, Timeout *timeout) requires(
- TryLockable<Lock>)
+ [[nodiscard]] explicit LockGuard(Lock &lock, Timeout *timeout)
+ requires(TryLockable<Lock>)
: wrappedLock(&lock), isOwned(false)
{
try_lock(timeout);
@@ -326,7 +329,8 @@
* it with the specified timeout. This must be called with the lock
* unlocked. Returns true if the lock has been acquired, false otherwise.
*/
- bool try_lock(Timeout *timeout) requires(TryLockable<Lock>)
+ bool try_lock(Timeout *timeout)
+ requires(TryLockable<Lock>)
{
LockDebug::Assert(!isOwned, "Trying to lock an already-locked lock");
isOwned = wrappedLock->try_lock(timeout);
diff --git a/sdk/include/multiwaiter.h b/sdk/include/multiwaiter.h
index 418228c..8b22817 100644
--- a/sdk/include/multiwaiter.h
+++ b/sdk/include/multiwaiter.h
@@ -34,52 +34,24 @@
#include <timeout.h>
/**
- * The kind of event source to wait for in a multiwaiter.
- */
-enum EventWaiterKind
-{
- /// Event source is an event channel.
- EventWaiterEventChannel,
- /// Event source is a futex.
- EventWaiterFutex
-};
-
-enum [[clang::flag_enum]] EventWaiterEventChannelFlags{
- /// Automatically clear the bits we waited on.
- EventWaiterEventChannelClearOnExit = (1 << 24),
- /// Notify when all bits were set.
- EventWaiterEventChannelWaitAll = (1 << 26)};
-
-/**
* Structure describing a change to the set of managed event sources for an
* event waiter.
*/
struct EventWaiterSource
{
/**
- * A pointer to the event source. For a futex, this should be the memory
- * address. For other sources, it should be a pointer to an object of the
- * corresponding type.
+ * A pointer to the event source. This is the futex that is monitored for
+ * the multiwaiter.
*/
void *eventSource;
/**
- * The kind of the event source. This must match the pointer type.
- */
- enum EventWaiterKind kind;
- /**
- * Event-specific configuration. This field is modified during the wait
- * call. The interpretation of this depends on `kind`:
+ * Event value. This field is modified during the wait
+ * call.
*
- * - `EventWaiterEventChannel`: The low 24 bits contain the bits to
- * monitor, the top bit indicates whether this event is triggered if all
- * of the bits are set (true) or some of them (false). On return, this
- * contains the bits that have been set during the call.
- * - `EventWaiterFutex`: This indicates the value to compare the futex word
- * against. If they mismatch, the event fires immediately.
+ * This indicates the value to compare the futex word against. If they
+ * mismatch, the event fires immediately.
*
- * If waiting for a futex, signal the event immediately if the value
- * does not match. On return, this is set to 1 if the futex is
- * signaled, 0 otherwise.
+ * On return, this is set to 1 if the futex is signaled, 0 otherwise.
*/
uint32_t value;
};
@@ -94,7 +66,7 @@
* Create a multiwaiter object. This is a stateful object that can wait on at
* most `maxItems` event sources.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
multiwaiter_create(Timeout *timeout,
struct SObjStruct *heapCapability,
struct MultiWaiter **ret,
@@ -103,7 +75,7 @@
/**
* Destroy a multiwaiter object.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
multiwaiter_delete(struct SObjStruct *heapCapability, struct MultiWaiter *mw);
/**
@@ -118,7 +90,7 @@
* - If the timeout is reached without any events being triggered then this
* returns -ETIMEOUT.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
multiwaiter_wait(Timeout *timeout,
struct MultiWaiter *waiter,
struct EventWaiterSource *events,
diff --git a/sdk/include/platform/arty-a7/platform-ethernet.hh b/sdk/include/platform/arty-a7/platform-ethernet.hh
index dc5392a..d568178 100644
--- a/sdk/include/platform/arty-a7/platform-ethernet.hh
+++ b/sdk/include/platform/arty-a7/platform-ethernet.hh
@@ -420,10 +420,10 @@
mdio_write(uint8_t phyAddress, PHYRegister registerAddress, uint16_t data)
{
mdio_wait_for_ready();
- auto &mdioAddress = mmio_register<RegisterOffset::MDIOAddress>();
- auto &mdioWrite = mmio_register<RegisterOffset::MDIODataWrite>();
- uint32_t writeCommand =
- (0 << 10) | (phyAddress << 5) | uint32_t(registerAddress);
+ auto &mdioAddress = mmio_register<RegisterOffset::MDIOAddress>();
+ auto &mdioWrite = mmio_register<RegisterOffset::MDIODataWrite>();
+ uint32_t writeCommand = (0 << 10) | (phyAddress << 5) |
+ static_cast<uint32_t>(registerAddress);
mdioAddress = writeCommand;
mdioWrite = data;
mdio_start_transaction();
@@ -437,7 +437,7 @@
mdio_wait_for_ready();
auto &mdioAddress = mmio_register<RegisterOffset::MDIOAddress>();
uint32_t readCommand =
- (1 << 10) | (phyAddress << 5) | uint8_t(registerAddress);
+ (1 << 10) | (phyAddress << 5) | static_cast<uint8_t>(registerAddress);
mdioAddress = readCommand;
mdio_start_transaction();
mdio_wait_for_ready();
@@ -755,7 +755,7 @@
// does not check the pointer which is coming from external
// untrusted components.
Timeout t{10};
- if ((heap_claim_fast(&t, buffer) < 0) ||
+ if ((heap_claim_ephemeral(&t, buffer) < 0) ||
(!CHERI::check_pointer<CHERI::PermissionSet{
CHERI::Permission::Load}>(buffer, length)))
{
diff --git a/sdk/include/platform/concepts/entropy.h b/sdk/include/platform/concepts/entropy.h
index e0933a7..ff6218c 100644
--- a/sdk/include/platform/concepts/entropy.h
+++ b/sdk/include/platform/concepts/entropy.h
@@ -7,15 +7,14 @@
* Concept for an Ethernet adaptor.
*/
template<typename T>
-concept IsEntropySource = requires(T source)
-{
+concept IsEntropySource = requires(T source) {
/**
* Must export a flag indicating whether this is a cryptographically
* secure random number.
*/
{
T::IsSecure
- } -> std::convertible_to<const bool>;
+ } -> std::convertible_to<const bool>;
/**
* Must return a random number. All bits of the value type are assumed to
@@ -24,7 +23,7 @@
*/
{
source()
- } -> std::same_as<typename T::ValueType>;
+ } -> std::same_as<typename T::ValueType>;
/**
* Must provide a method that reseeds the entropy source. If this is a
@@ -32,5 +31,7 @@
* may be an empty function. There are no constraints on the return type
* of this.
*/
- {source.reseed()};
+ {
+ source.reseed()
+ };
};
diff --git a/sdk/include/platform/concepts/ethernet.hh b/sdk/include/platform/concepts/ethernet.hh
index 700bb82..b5421bf 100644
--- a/sdk/include/platform/concepts/ethernet.hh
+++ b/sdk/include/platform/concepts/ethernet.hh
@@ -11,17 +11,16 @@
* This is not required to be copyable.
*/
template<typename T>
-concept ReceivedEthernetFrame = requires(T frame)
-{
+concept ReceivedEthernetFrame = requires(T frame) {
{
frame->length
- } -> std::convertible_to<uint16_t>;
+ } -> std::convertible_to<uint16_t>;
{
frame->buffer
- } -> std::convertible_to<const uint8_t *>;
+ } -> std::convertible_to<const uint8_t *>;
{
frame->buffer
- } -> std::convertible_to<bool>;
+ } -> std::convertible_to<bool>;
};
/**
@@ -31,14 +30,13 @@
concept EthernetAdaptor = requires(T adaptor,
const uint8_t *buffer,
uint16_t length,
- std::array<uint8_t, 6> macAddress)
-{
+ std::array<uint8_t, 6> macAddress) {
/**
* The default MAC address for this adaptor. Must return a 6-byte array.
*/
{
T::mac_address_default()
- } -> std::same_as<std::array<uint8_t, 6>>;
+ } -> std::same_as<std::array<uint8_t, 6>>;
/**
* Is the default MAC address unique? If the device (e.g. soft MAC)
* doesn't have its own hardware MAC address then callers may prefer to
@@ -46,22 +44,26 @@
*/
{
T::has_unique_mac_address()
- } -> std::convertible_to<bool>;
+ } -> std::convertible_to<bool>;
/**
* Set the MAC address of this adaptor.
*/
- {adaptor.mac_address_set(macAddress)};
+ {
+ adaptor.mac_address_set(macAddress)
+ };
/**
* Set the MAC address of this adaptor to the default value.
*/
- {adaptor.mac_address_set()};
+ {
+ adaptor.mac_address_set()
+ };
/**
* Check if PHY link is up.
*/
{
adaptor.phy_link_status()
- } -> std::convertible_to<bool>;
+ } -> std::convertible_to<bool>;
/**
* Receive a frame. Returns an optional value (convertible to bool) that
@@ -70,7 +72,7 @@
*/
{
adaptor.receive_frame()
- } -> ReceivedEthernetFrame;
+ } -> ReceivedEthernetFrame;
/**
* Send a frame identified by a base and length. Returns true if the frame
@@ -85,7 +87,7 @@
buffer, length, [](const uint8_t *buffer, uint16_t length) {
return true;
})
- } -> std::same_as<bool>;
+ } -> std::same_as<bool>;
/**
* Read the current interrupt counter for receive interrupts. If this
@@ -94,7 +96,7 @@
*/
{
adaptor.receive_interrupt_value()
- } -> std::same_as<uint32_t>;
+ } -> std::same_as<uint32_t>;
/**
* Called after `receive_frame` fails to return new frames to block until a
@@ -105,5 +107,5 @@
*/
{
adaptor.receive_interrupt_complete(nullptr, 0)
- } -> std::same_as<int>;
+ } -> std::same_as<int>;
};
diff --git a/sdk/include/platform/flute/platform-early_boot.inc b/sdk/include/platform/flute/platform-early_boot.inc
deleted file mode 100644
index f8532a5..0000000
--- a/sdk/include/platform/flute/platform-early_boot.inc
+++ /dev/null
@@ -1,12 +0,0 @@
- // Ugly hack. For some reason if I don't give this first load, subsequent mem
- // ops will trap on Flute-TCM.
- li a3, 0x80000000
- cspecialr ca4, mtdc
- csetaddr ca4, ca4, a3
- clc c0, 0(ca4)
- // The shadow memory may not be zeroed, ensure it is before we start or
- // random capability loads will fail.
- li a0, FLUTE_SHADOW_BASE
- csetaddr ca0, ca4, a0
- li a1, FLUTE_SHADOW_BASE + FLUTE_SHADOW_SIZE
- cjal .Lfill_block
diff --git a/sdk/include/platform/flute/platform-hardware_revoker.hh b/sdk/include/platform/flute/platform-hardware_revoker.hh
deleted file mode 100644
index 759db15..0000000
--- a/sdk/include/platform/flute/platform-hardware_revoker.hh
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright Microsoft and CHERIoT Contributors.
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <cdefs.h>
-#include <compartment-macros.h>
-#include <riscvreg.h>
-#include <stddef.h>
-#include <stdint.h>
-
-namespace Flute
-{
- template<typename WordT, size_t TCMBaseAddr>
- class HardwareRevoker
- {
- private:
- // layout of the shadow space control registers
- struct ShadowCtrl
- {
- uint32_t base;
- uint32_t pad0;
- uint32_t top;
- uint32_t pad1;
- uint32_t epoch;
- uint32_t pad2;
- uint32_t go;
- uint32_t pad4;
- };
- static_assert(offsetof(ShadowCtrl, epoch) == 16);
- static_assert(offsetof(ShadowCtrl, go) == 24);
-
- volatile ShadowCtrl *shadowCtrl;
-
- public:
- /**
- * Currently the only hardware revoker implementation is async which
- * sweeps memory in the background.
- */
- static constexpr bool IsAsynchronous = true;
-
- /**
- * Initialise a revoker instance.
- */
- void init()
- {
- /**
- * These two symbols mark the region that needs revocation. We
- * revoke capabilities everywhere from the start of compartment
- * globals to the end of the heap.
- */
- extern char __compart_cgps, __export_mem_heap_end;
-
- auto base = LA_ABS(__compart_cgps);
- auto top = LA_ABS(__export_mem_heap_end);
- shadowCtrl = MMIO_CAPABILITY(ShadowCtrl, shadowctrl);
- shadowCtrl->base = base;
- shadowCtrl->top = top;
- // Clang tidy is checking headers as stand-alone compilation units
- // and so doesn't know what Debug is defined to.
-#ifndef CLANG_TIDY
- Debug::Invariant(base < top,
- "Memory map has unexpected layout, base {} is "
- "expected to be below top {}",
- base,
- top);
-#endif
- }
-
- /**
- * Returns the revocation epoch. This is the number of revocations
- * that have started.
- */
- uint32_t system_epoch_get()
- {
- asm volatile("" ::: "memory");
- return shadowCtrl->epoch;
- }
-
- /**
- * Queries whether the specified revocation epoch has finished.
- */
- template<bool AllowPartial = false>
- uint32_t has_revocation_finished_for_epoch(uint32_t epoch)
- {
- asm volatile("" ::: "memory");
- if (AllowPartial)
- {
- return shadowCtrl->epoch > epoch;
- }
- return shadowCtrl->epoch - epoch >= (2 + (epoch & 1));
- }
-
- // Start a revocation.
- void system_bg_revoker_kick()
- {
- asm volatile("" ::: "memory");
- shadowCtrl->go = 1;
- asm volatile("" ::: "memory");
- }
- };
-} // namespace Flute
-
-template<typename WordT, size_t TCMBaseAddr>
-using HardwareRevoker = Flute::HardwareRevoker<WordT, TCMBaseAddr>;
diff --git a/sdk/include/platform/generic-riscv/platform-timer.hh b/sdk/include/platform/generic-riscv/platform-timer.hh
index 7b0d1f6..6fce600 100644
--- a/sdk/include/platform/generic-riscv/platform-timer.hh
+++ b/sdk/include/platform/generic-riscv/platform-timer.hh
@@ -52,7 +52,7 @@
timeHigh = *timerHigh;
timeLow = *pmtimer;
} while (timeHigh != *timerHigh);
- return (uint64_t(timeHigh) << 32) | timeLow;
+ return (static_cast<uint64_t>(timeHigh) << 32) | timeLow;
}
/**
@@ -80,6 +80,18 @@
*pmtimercmphigh = nextTime >> 32;
}
+ /**
+ * Returns the time at which the next timer is scheduled.
+ */
+ static uint64_t next()
+ {
+ volatile uint32_t *pmtimercmphigh = pmtimercmp + 1;
+ uint64_t nextTimer = *pmtimercmphigh;
+ nextTimer <<= 32;
+ nextTimer |= *pmtimercmp;
+ return nextTimer;
+ }
+
static void clear()
{
volatile uint32_t *pmtimercmphigh = pmtimercmp + 1;
diff --git a/sdk/include/platform/generic-riscv/platform-uart.hh b/sdk/include/platform/generic-riscv/platform-uart.hh
index 4b1c916..705a6fb 100644
--- a/sdk/include/platform/generic-riscv/platform-uart.hh
+++ b/sdk/include/platform/generic-riscv/platform-uart.hh
@@ -10,19 +10,22 @@
* Concept for checking that a UART driver exposes the right interface.
*/
template<typename T>
-concept IsUart = requires(volatile T *v, uint8_t byte)
-{
- {v->init()};
+concept IsUart = requires(volatile T *v, uint8_t byte) {
+ {
+ v->init()
+ };
{
v->can_write()
- } -> std::same_as<bool>;
+ } -> std::same_as<bool>;
{
v->can_read()
- } -> std::same_as<bool>;
+ } -> std::same_as<bool>;
{
v->blocking_read()
- } -> std::same_as<uint8_t>;
- {v->blocking_write(byte)};
+ } -> std::same_as<uint8_t>;
+ {
+ v->blocking_write(byte)
+ };
};
/**
diff --git a/sdk/include/platform/ibex/platform-hardware_revoker.hh b/sdk/include/platform/ibex/platform-hardware_revoker.hh
index c039cce..6de65c7 100644
--- a/sdk/include/platform/ibex/platform-hardware_revoker.hh
+++ b/sdk/include/platform/ibex/platform-hardware_revoker.hh
@@ -87,9 +87,9 @@
* revoke capabilities everywhere from the start of compartment
* globals to the end of the heap.
*/
- extern char __compart_cgps, __export_mem_heap_end;
+ extern char __revoker_scan_start, __export_mem_heap_end;
- auto base = LA_ABS(__compart_cgps);
+ auto base = LA_ABS(__revoker_scan_start);
auto top = LA_ABS(__export_mem_heap_end);
auto &device = revoker_device();
device.base = base;
@@ -121,7 +121,11 @@
}
/**
- * Queries whether the specified revocation epoch has finished.
+ * Queries whether the specified revocation epoch has finished, or,
+ * if `AllowPartial` is true, that it has (at least) started.
+ *
+ * `epoch` must be even, as memory leaves quarantine only when
+ * revocation is not in progress.
*/
template<bool AllowPartial = false>
uint32_t has_revocation_finished_for_epoch(uint32_t epoch)
@@ -177,7 +181,7 @@
// futex word with respect to the read of the revocation epoch.
__c11_atomic_signal_fence(__ATOMIC_SEQ_CST);
// If the requested epoch has finished, return success.
- if (has_revocation_finished_for_epoch<true>(epoch))
+ if (has_revocation_finished_for_epoch(epoch))
{
return true;
}
@@ -186,7 +190,7 @@
// There is a possible race: if the revocation pass finished
// before we requested the interrupt, we won't get the
// interrupt. Check again before we wait.
- if (has_revocation_finished_for_epoch<true>(epoch))
+ if (has_revocation_finished_for_epoch(epoch))
{
return true;
}
diff --git a/sdk/include/platform/sunburst/platform-ethernet.hh b/sdk/include/platform/sunburst/platform-ethernet.hh
index 0e9b3a7..8e69319 100644
--- a/sdk/include/platform/sunburst/platform-ethernet.hh
+++ b/sdk/include/platform/sunburst/platform-ethernet.hh
@@ -9,7 +9,6 @@
#include <locks.hh>
#include <optional>
#include <platform/concepts/ethernet.hh>
-#include <platform/sunburst/platform-gpio.hh>
#include <platform/sunburst/platform-spi.hh>
#include <thread.h>
#include <type_traits>
@@ -57,15 +56,6 @@
using Capability = CHERI::Capability<T>;
/**
- * GPIO output pins to be used
- */
- enum class GpioPin : uint8_t
- {
- EthernetChipSelect = 13,
- EthernetReset = 14,
- };
-
- /**
* SPI commands
*/
enum class SpiCommand : uint8_t
@@ -143,161 +133,171 @@
/**
* Flag bits of the TransmitControl register.
*/
- enum [[clang::flag_enum]] TransmitControl : uint16_t{
- TransmitEnable = 1 << 0,
- TransmitCrcEnable = 1 << 1,
- TransmitPaddingEnable = 1 << 2,
- TransmitFlowControlEnable = 1 << 3,
- FlushTransmitQueue = 1 << 4,
- TransmitChecksumGenerationIp = 1 << 5,
- TransmitChecksumGenerationTcp = 1 << 6,
- TransmitChecksumGenerationIcmp = 1 << 9,
+ enum [[clang::flag_enum]] TransmitControl : uint16_t
+ {
+ TransmitEnable = 1 << 0,
+ TransmitCrcEnable = 1 << 1,
+ TransmitPaddingEnable = 1 << 2,
+ TransmitFlowControlEnable = 1 << 3,
+ FlushTransmitQueue = 1 << 4,
+ TransmitChecksumGenerationIp = 1 << 5,
+ TransmitChecksumGenerationTcp = 1 << 6,
+ TransmitChecksumGenerationIcmp = 1 << 9,
};
/**
* Flag bits of the ReceiveControl1 register.
*/
- enum [[clang::flag_enum]] ReceiveControl1 : uint16_t{
- ReceiveEnable = 1 << 0,
- ReceiveInverseFilter = 1 << 1,
- ReceiveAllEnable = 1 << 4,
- ReceiveUnicastEnable = 1 << 5,
- ReceiveMulticastEnable = 1 << 6,
- ReceiveBroadcastEnable = 1 << 7,
- ReceiveMulticastAddressFilteringWithMacAddressEnable = 1 << 8,
- ReceiveErrorFrameEnable = 1 << 9,
- ReceiveFlowControlEnable = 1 << 10,
- ReceivePhysicalAddressFilteringWithMacAddressEnable = 1 << 11,
- ReceiveIpFrameChecksumCheckEnable = 1 << 12,
- ReceiveTcpFrameChecksumCheckEnable = 1 << 13,
- ReceiveUdpFrameChecksumCheckEnable = 1 << 14,
- FlushReceiveQueue = 1 << 15,
+ enum [[clang::flag_enum]] ReceiveControl1 : uint16_t
+ {
+ ReceiveEnable = 1 << 0,
+ ReceiveInverseFilter = 1 << 1,
+ ReceiveAllEnable = 1 << 4,
+ ReceiveUnicastEnable = 1 << 5,
+ ReceiveMulticastEnable = 1 << 6,
+ ReceiveBroadcastEnable = 1 << 7,
+ ReceiveMulticastAddressFilteringWithMacAddressEnable = 1 << 8,
+ ReceiveErrorFrameEnable = 1 << 9,
+ ReceiveFlowControlEnable = 1 << 10,
+ ReceivePhysicalAddressFilteringWithMacAddressEnable = 1 << 11,
+ ReceiveIpFrameChecksumCheckEnable = 1 << 12,
+ ReceiveTcpFrameChecksumCheckEnable = 1 << 13,
+ ReceiveUdpFrameChecksumCheckEnable = 1 << 14,
+ FlushReceiveQueue = 1 << 15,
};
/**
* Flag bits of the ReceiveControl2 register.
*/
- enum [[clang::flag_enum]] ReceiveControl2 : uint16_t{
- ReceiveSourceAddressFiltering = 1 << 0,
- ReceiveIcmpFrameChecksumEnable = 1 << 1,
- UdpLiteFrameEnable = 1 << 2,
- ReceiveIpv4Ipv6UdpFrameChecksumEqualZero = 1 << 3,
- ReceiveIpv4Ipv6FragmentFramePass = 1 << 4,
- DataBurst4Bytes = 0b000 << 5,
- DataBurst8Bytes = 0b001 << 5,
- DataBurst16Bytes = 0b010 << 5,
- DataBurst32Bytes = 0b011 << 5,
- DataBurstSingleFrame = 0b100 << 5,
+ enum [[clang::flag_enum]] ReceiveControl2 : uint16_t
+ {
+ ReceiveSourceAddressFiltering = 1 << 0,
+ ReceiveIcmpFrameChecksumEnable = 1 << 1,
+ UdpLiteFrameEnable = 1 << 2,
+ ReceiveIpv4Ipv6UdpFrameChecksumEqualZero = 1 << 3,
+ ReceiveIpv4Ipv6FragmentFramePass = 1 << 4,
+ DataBurst4Bytes = 0b000 << 5,
+ DataBurst8Bytes = 0b001 << 5,
+ DataBurst16Bytes = 0b010 << 5,
+ DataBurst32Bytes = 0b011 << 5,
+ DataBurstSingleFrame = 0b100 << 5,
};
/**
* Flag bits of the ReceiveFrameHeaderStatus register.
*/
- enum [[clang::flag_enum]] ReceiveFrameHeaderStatus : uint16_t{
- ReceiveCrcError = 1 << 0,
- ReceiveRuntFrame = 1 << 1,
- ReceiveFrameTooLong = 1 << 2,
- ReceiveFrameType = 1 << 3,
- ReceiveMiiError = 1 << 4,
- ReceiveUnicastFrame = 1 << 5,
- ReceiveMulticastFrame = 1 << 6,
- ReceiveBroadcastFrame = 1 << 7,
- ReceiveUdpFrameChecksumStatus = 1 << 10,
- ReceiveTcpFrameChecksumStatus = 1 << 11,
- ReceiveIpFrameChecksumStatus = 1 << 12,
- ReceiveIcmpFrameChecksumStatus = 1 << 13,
- ReceiveFrameValid = 1 << 15,
+ enum [[clang::flag_enum]] ReceiveFrameHeaderStatus : uint16_t
+ {
+ ReceiveCrcError = 1 << 0,
+ ReceiveRuntFrame = 1 << 1,
+ ReceiveFrameTooLong = 1 << 2,
+ ReceiveFrameType = 1 << 3,
+ ReceiveMiiError = 1 << 4,
+ ReceiveUnicastFrame = 1 << 5,
+ ReceiveMulticastFrame = 1 << 6,
+ ReceiveBroadcastFrame = 1 << 7,
+ ReceiveUdpFrameChecksumStatus = 1 << 10,
+ ReceiveTcpFrameChecksumStatus = 1 << 11,
+ ReceiveIpFrameChecksumStatus = 1 << 12,
+ ReceiveIcmpFrameChecksumStatus = 1 << 13,
+ ReceiveFrameValid = 1 << 15,
};
/**
* Flag bits of the ReceiveQueueCommand register.
*/
- enum [[clang::flag_enum]] ReceiveQueueCommand : uint16_t{
- ReleaseReceiveErrorFrame = 1 << 0,
- StartDmaAccess = 1 << 3,
- AutoDequeueReceiveQueueFrameEnable = 1 << 4,
- ReceiveFrameCountThresholdEnable = 1 << 5,
- ReceiveDataByteCountThresholdEnable = 1 << 6,
- ReceiveDurationTimerThresholdEnable = 1 << 7,
- ReceiveIpHeaderTwoByteOffsetEnable = 1 << 9,
- ReceiveFrameCountThresholdStatus = 1 << 10,
- ReceiveDataByteCountThresholdstatus = 1 << 11,
- ReceiveDurationTimerThresholdStatus = 1 << 12,
+ enum [[clang::flag_enum]] ReceiveQueueCommand : uint16_t
+ {
+ ReleaseReceiveErrorFrame = 1 << 0,
+ StartDmaAccess = 1 << 3,
+ AutoDequeueReceiveQueueFrameEnable = 1 << 4,
+ ReceiveFrameCountThresholdEnable = 1 << 5,
+ ReceiveDataByteCountThresholdEnable = 1 << 6,
+ ReceiveDurationTimerThresholdEnable = 1 << 7,
+ ReceiveIpHeaderTwoByteOffsetEnable = 1 << 9,
+ ReceiveFrameCountThresholdStatus = 1 << 10,
+ ReceiveDataByteCountThresholdstatus = 1 << 11,
+ ReceiveDurationTimerThresholdStatus = 1 << 12,
};
/**
* Flag bits of the TransmitQueueCommand register.
*/
- enum [[clang::flag_enum]] TransmitQueueCommand : uint16_t{
- ManualEnqueueTransmitQueueFrameEnable = 1 << 0,
- TransmitQueueMemoryAvailableMonitor = 1 << 1,
- AutoEnqueueTransmitQueueFrameEnable = 1 << 2,
+ enum [[clang::flag_enum]] TransmitQueueCommand : uint16_t
+ {
+ ManualEnqueueTransmitQueueFrameEnable = 1 << 0,
+ TransmitQueueMemoryAvailableMonitor = 1 << 1,
+ AutoEnqueueTransmitQueueFrameEnable = 1 << 2,
};
/**
* Flag bits of the TransmitFrameDataPointer and ReceiveFrameDataPointer
* register.
*/
- enum [[clang::flag_enum]] FrameDataPointer : uint16_t{
- /**
- * When this bit is set, the frame data pointer register increments
- * automatically on accesses to the data register.
- */
- FrameDataPointerAutoIncrement = 1 << 14,
+ enum [[clang::flag_enum]] FrameDataPointer : uint16_t
+ {
+ /**
+ * When this bit is set, the frame data pointer register increments
+ * automatically on accesses to the data register.
+ */
+ FrameDataPointerAutoIncrement = 1 << 14,
};
/**
* Flags bits of the InterruptStatus and InterruptEnable registers.
*/
- enum [[clang::flag_enum]] Interrupt : uint16_t{
- EnergyDetectInterrupt = 1 << 2,
- LinkupDetectInterrupt = 1 << 3,
- ReceiveMagicPacketDetectInterrupt = 1 << 4,
- ReceiveWakeupFrameDetectInterrupt = 1 << 5,
- TransmitSpaceAvailableInterrupt = 1 << 6,
- ReceiveProcessStoppedInterrupt = 1 << 7,
- TransmitProcessStoppedInterrupt = 1 << 8,
- ReceiveOverrunInterrupt = 1 << 11,
- ReceiveInterrupt = 1 << 13,
- TransmitInterrupt = 1 << 14,
- LinkChangeInterruptStatus = 1 << 15,
+ enum [[clang::flag_enum]] Interrupt : uint16_t
+ {
+ EnergyDetectInterrupt = 1 << 2,
+ LinkupDetectInterrupt = 1 << 3,
+ ReceiveMagicPacketDetectInterrupt = 1 << 4,
+ ReceiveWakeupFrameDetectInterrupt = 1 << 5,
+ TransmitSpaceAvailableInterrupt = 1 << 6,
+ ReceiveProcessStoppedInterrupt = 1 << 7,
+ TransmitProcessStoppedInterrupt = 1 << 8,
+ ReceiveOverrunInterrupt = 1 << 11,
+ ReceiveInterrupt = 1 << 13,
+ TransmitInterrupt = 1 << 14,
+ LinkChangeInterruptStatus = 1 << 15,
};
/**
* Flags bits of the Port1Control register.
*/
- enum [[clang::flag_enum]] Port1Control : uint16_t{
- Advertised10BTHalfDuplexCapability = 1 << 0,
- Advertised10BTFullDuplexCapability = 1 << 1,
- Advertised100BTHalfDuplexCapability = 1 << 2,
- Advertised100BTFullDuplexCapability = 1 << 3,
- AdvertisedFlowControlCapability = 1 << 4,
- ForceDuplex = 1 << 5,
- ForceSpeed = 1 << 6,
- AutoNegotiationEnable = 1 << 7,
- ForceMDIX = 1 << 9,
- DisableAutoMDIMDIX = 1 << 10,
- RestartAutoNegotiation = 1 << 13,
- TransmitterDisable = 1 << 14,
- LedOff = 1 << 15,
+ enum [[clang::flag_enum]] Port1Control : uint16_t
+ {
+ Advertised10BTHalfDuplexCapability = 1 << 0,
+ Advertised10BTFullDuplexCapability = 1 << 1,
+ Advertised100BTHalfDuplexCapability = 1 << 2,
+ Advertised100BTFullDuplexCapability = 1 << 3,
+ AdvertisedFlowControlCapability = 1 << 4,
+ ForceDuplex = 1 << 5,
+ ForceSpeed = 1 << 6,
+ AutoNegotiationEnable = 1 << 7,
+ ForceMDIX = 1 << 9,
+ DisableAutoMDIMDIX = 1 << 10,
+ RestartAutoNegotiation = 1 << 13,
+ TransmitterDisable = 1 << 14,
+ LedOff = 1 << 15,
};
/**
* Flags bits of the Port1Status register.
*/
- enum [[clang::flag_enum]] Port1Status : uint16_t{
- Partner10BTHalfDuplexCapability = 1 << 0,
- Partner10BTFullDuplexCapability = 1 << 1,
- Partner100BTHalfDuplexCapability = 1 << 2,
- Partner100BTFullDuplexCapability = 1 << 3,
- PartnerFlowControlCapability = 1 << 4,
- LinkGood = 1 << 5,
- AutoNegotiationDone = 1 << 6,
- MDIXStatus = 1 << 7,
- OperationDuplex = 1 << 9,
- OperationSpeed = 1 << 10,
- PolarityReverse = 1 << 13,
- HPMDIX = 1 << 15,
+ enum [[clang::flag_enum]] Port1Status : uint16_t
+ {
+ Partner10BTHalfDuplexCapability = 1 << 0,
+ Partner10BTFullDuplexCapability = 1 << 1,
+ Partner100BTHalfDuplexCapability = 1 << 2,
+ Partner100BTFullDuplexCapability = 1 << 3,
+ PartnerFlowControlCapability = 1 << 4,
+ LinkGood = 1 << 5,
+ AutoNegotiationDone = 1 << 6,
+ MDIXStatus = 1 << 7,
+ OperationDuplex = 1 << 9,
+ OperationSpeed = 1 << 10,
+ PolarityReverse = 1 << 13,
+ HPMDIX = 1 << 15,
};
/**
@@ -307,18 +307,6 @@
const uint32_t *receiveInterruptFutex;
/**
- * Set value of a GPIO output.
- */
- inline void set_gpio_output_bit(GpioPin pin, bool value) const
- {
- uint32_t shift = static_cast<uint8_t>(pin);
- uint32_t output = gpio()->output;
- output &= ~(1 << shift);
- output |= value << shift;
- gpio()->output = output;
- }
-
- /**
* Read a register from the KSZ8851.
*/
[[nodiscard]] uint16_t register_read(RegisterOffset reg) const
@@ -350,11 +338,11 @@
(byteEnable << 2) | (addr >> 6);
bytes[1] = (addr << 2) & 0b11110000;
- set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->chip_select_assert(true);
spi()->blocking_write(bytes, sizeof(bytes));
uint16_t val;
spi()->blocking_read(reinterpret_cast<uint8_t *>(&val), sizeof(val));
- set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ spi()->chip_select_assert(false);
return val;
}
@@ -371,11 +359,11 @@
(byteEnable << 2) | (addr >> 6);
bytes[1] = (addr << 2) & 0b11110000;
- set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->chip_select_assert(true);
spi()->blocking_write(bytes, sizeof(bytes));
spi()->blocking_write(reinterpret_cast<uint8_t *>(&val), sizeof(val));
spi()->wait_idle();
- set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ spi()->chip_select_assert(false);
}
/**
@@ -397,20 +385,13 @@
}
/**
- * Helper. Returns a pointer to the SPI device.
+ * Helper. Returns a pointer to the SPI device.
*/
- [[nodiscard, gnu::always_inline]] Capability<volatile SonataSpi> spi() const
+ [[nodiscard,
+ gnu::always_inline]] Capability<volatile SonataSpi::EthernetMac>
+ spi() const
{
- return MMIO_CAPABILITY(SonataSpi, spi2);
- }
-
- /**
- * Helper. Returns a pointer to the GPIO device.
- */
- [[nodiscard, gnu::always_inline]] Capability<volatile SonataGPIO>
- gpio() const
- {
- return MMIO_CAPABILITY(SonataGPIO, gpio);
+ return MMIO_CAPABILITY(SonataSpi::EthernetMac, spi_ethmac);
}
/**
@@ -435,10 +416,10 @@
RecursiveMutex receiveBufferMutex;
/**
- * Reads and writes of the GPIO space use the same bits of the MMIO region
- * and so need to be protected.
+ * Lock to protect reads/writes to the SPI Chip Selects, which use the
+ * same bits of the MMIO region and thus need to be protected.
*/
- FlagLockPriorityInherited gpioLock;
+ FlagLockPriorityInherited chipSelectLock;
/**
* Buffer used by receive_frame.
@@ -455,9 +436,9 @@
receiveBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
// Reset chip. It needs to be hold in reset for at least 10ms.
- set_gpio_output_bit(GpioPin::EthernetReset, false);
+ spi()->reset_assert(true);
thread_millisecond_wait(20);
- set_gpio_output_bit(GpioPin::EthernetReset, true);
+ spi()->reset_assert(false);
uint16_t chipId = register_read(RegisterOffset::ChipIdEnable);
Debug::log("Chip ID is {}", chipId);
@@ -626,7 +607,7 @@
std::optional<Frame> receive_frame()
{
- LockGuard g{gpioLock};
+ LockGuard g{chipSelectLock};
if (framesToProcess == 0)
{
uint16_t isr = register_read(RegisterOffset::InterruptStatus);
@@ -700,7 +681,7 @@
// Start receiving via SPI.
uint8_t cmd = static_cast<uint8_t>(SpiCommand::ReadDma) << 6;
- set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->chip_select_assert(true);
spi()->blocking_write(&cmd, 1);
// Initial words are ReceiveFrameHeaderStatus and
@@ -710,7 +691,7 @@
spi()->blocking_read(receiveBuffer.get(), paddedLength);
- set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ spi()->chip_select_assert(false);
register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
framesToProcess -= 1;
@@ -753,7 +734,7 @@
// does not check the pointer which is coming from external
// untrusted components.
Timeout t{10};
- if ((heap_claim_fast(&t, buffer) < 0) ||
+ if ((heap_claim_ephemeral(&t, buffer) < 0) ||
(!CHERI::check_pointer<CHERI::PermissionSet{
CHERI::Permission::Load}>(buffer, length)))
{
@@ -766,7 +747,7 @@
return false;
}
- LockGuard g{gpioLock};
+ LockGuard g{chipSelectLock};
// Wait for the transmit buffer to be available on the device side.
// This needs to include the header.
@@ -782,7 +763,7 @@
// Start sending via SPI.
uint8_t cmd = static_cast<uint8_t>(SpiCommand::WriteDma) << 6;
- set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->chip_select_assert(true);
spi()->blocking_write(&cmd, 1);
uint32_t header = static_cast<uint32_t>(length) << 16;
@@ -792,7 +773,7 @@
spi()->blocking_write(transmitBuffer.get(), paddedLength);
spi()->wait_idle();
- set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ spi()->chip_select_assert(false);
// Stop QMU DMA transfer operation.
register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
diff --git a/sdk/include/platform/sunburst/platform-i2c.hh b/sdk/include/platform/sunburst/platform-i2c.hh
index ef93eee..9d86acb 100644
--- a/sdk/include/platform/sunburst/platform-i2c.hh
+++ b/sdk/include/platform/sunburst/platform-i2c.hh
@@ -182,105 +182,113 @@
};
/// Control Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Enable Host I2C functionality
- ControlEnableHost = 1 << 0,
- /// Enable Target I2C functionality
- ControlEnableTarget = 1 << 1,
- /// Enable I2C line loopback test If line loopback is enabled, the
- /// internal design sees ACQ and RX data as "1"
- ControlLineLoopback = 1 << 2,
- /// Enable NACKing the address on a stretch timeout. This is a target
- /// mode feature. If enabled, a stretch timeout will cause the device to
- /// NACK the address byte. If disabled, it will ACK instead.
- ControlNackAddressAfterTimeout = 1 << 3,
- /// Enable ACK Control Mode, which works with the `targetAckControl`
- /// register to allow software to control upper-layer (N)ACKing.
- ControlAckControlEnable = 1 << 4,
- /// Enable the bus monitor in multi-controller mode.
- ControlMultiControllerMonitorEnable = 1 << 5,
- /// If set, causes a read transfer addressed to the this target to set
- /// the corresponding bit in the `targetEvents` register. While the
- /// `transmitPending` field is 1, subsequent read transactions will
- /// stretch the clock, even if there is data in the Transmit FIFO.
- ControlTransmitStretchEnable = 1 << 6,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Enable Host I2C functionality
+ ControlEnableHost = 1 << 0,
+ /// Enable Target I2C functionality
+ ControlEnableTarget = 1 << 1,
+ /// Enable I2C line loopback test If line loopback is enabled, the
+ /// internal design sees ACQ and RX data as "1"
+ ControlLineLoopback = 1 << 2,
+ /// Enable NACKing the address on a stretch timeout. This is a target
+ /// mode feature. If enabled, a stretch timeout will cause the device to
+ /// NACK the address byte. If disabled, it will ACK instead.
+ ControlNackAddressAfterTimeout = 1 << 3,
+ /// Enable ACK Control Mode, which works with the `targetAckControl`
+ /// register to allow software to control upper-layer (N)ACKing.
+ ControlAckControlEnable = 1 << 4,
+ /// Enable the bus monitor in multi-controller mode.
+ ControlMultiControllerMonitorEnable = 1 << 5,
+ /// If set, causes a read transfer addressed to the this target to set
+ /// the corresponding bit in the `targetEvents` register. While the
+ /// `transmitPending` field is 1, subsequent read transactions will
+ /// stretch the clock, even if there is data in the Transmit FIFO.
+ ControlTransmitStretchEnable = 1 << 6,
};
/// Status Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Host mode Format FIFO is full
- StatusFormatFull = 1 << 0,
- /// Host mode Receive FIFO is full
- StatusReceiveFull = 1 << 1,
- /// Host mode Format FIFO is empty
- StatusFormatEmpty = 1 << 2,
- /// Host functionality is idle. No Host transaction is in progress
- StatusHostIdle = 1 << 3,
- /// Target functionality is idle. No Target transaction is in progress
- StatusTargetIdle = 1 << 4,
- /// Host mode Receive FIFO is empty
- SmatusReceiveEmpty = 1 << 5,
- /// Target mode Transmit FIFO is full
- StatusTransmitFull = 1 << 6,
- /// Target mode Acquired FIFO is full
- StatusAcquiredFull = 1 << 7,
- /// Target mode Transmit FIFO is empty
- StatusTransmitEmpty = 1 << 8,
- /// Target mode Acquired FIFO is empty
- StatusAcquiredEmpty = 1 << 9,
- /// Target mode stretching at (N)ACK phase due to zero count
- /// in the `targetAckControl` register.
- StatusAckControlStretch = 1 << 10,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Host mode Format FIFO is full
+ StatusFormatFull = 1 << 0,
+ /// Host mode Receive FIFO is full
+ StatusReceiveFull = 1 << 1,
+ /// Host mode Format FIFO is empty
+ StatusFormatEmpty = 1 << 2,
+ /// Host functionality is idle. No Host transaction is in progress
+ StatusHostIdle = 1 << 3,
+ /// Target functionality is idle. No Target transaction is in progress
+ StatusTargetIdle = 1 << 4,
+ /// Host mode Receive FIFO is empty
+ SmatusReceiveEmpty = 1 << 5,
+ /// Target mode Transmit FIFO is full
+ StatusTransmitFull = 1 << 6,
+ /// Target mode Acquired FIFO is full
+ StatusAcquiredFull = 1 << 7,
+ /// Target mode Transmit FIFO is empty
+ StatusTransmitEmpty = 1 << 8,
+ /// Target mode Acquired FIFO is empty
+ StatusAcquiredEmpty = 1 << 9,
+ /// Target mode stretching at (N)ACK phase due to zero count
+ /// in the `targetAckControl` register.
+ StatusAckControlStretch = 1 << 10,
};
/// FormatData Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Issue a START condition before transmitting BYTE.
- FormatDataStart = 1 << 8,
- /// Issue a STOP condition after this operation
- FormatDataStop = 1 << 9,
- /// Read BYTE bytes from I2C. (256 if BYTE==0)
- FormatDataReadBytes = 1 << 10,
- /**
- * Do not NACK the last byte read, let the read
- * operation continue
- */
- FormatDataReadCount = 1 << 11,
- /// Do not signal an exception if the current byte is not ACK’d
- FormatDataNakOk = 1 << 12,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Issue a START condition before transmitting BYTE.
+ FormatDataStart = 1 << 8,
+ /// Issue a STOP condition after this operation
+ FormatDataStop = 1 << 9,
+ /// Read BYTE bytes from I2C. (256 if BYTE==0)
+ FormatDataReadBytes = 1 << 10,
+ /**
+ * Do not NACK the last byte read, let the read
+ * operation continue
+ */
+ FormatDataReadCount = 1 << 11,
+ /// Do not signal an exception if the current byte is not ACK’d
+ FormatDataNakOk = 1 << 12,
};
/// FifoControl Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Receive fifo reset. Write 1 to the register resets it. Read returns 0
- FifoControlReceiveReset = 1 << 0,
- /// Format fifo reset. Write 1 to the register resets it. Read returns 0
- FifoControlFormatReset = 1 << 1,
- /// Acquired FIFO reset. Write 1 to the register resets it. Read returns 0
- FifoControlAcquiredReset = 1 << 7,
- /// Transmit FIFO reset. Write 1 to the register resets it. Read returns 0
- FifoControlTransmitReset = 1 << 8,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Receive fifo reset. Write 1 to the register resets it. Read returns
+ /// 0
+ FifoControlReceiveReset = 1 << 0,
+ /// Format fifo reset. Write 1 to the register resets it. Read returns 0
+ FifoControlFormatReset = 1 << 1,
+ /// Acquired FIFO reset. Write 1 to the register resets it. Read returns
+ /// 0
+ FifoControlAcquiredReset = 1 << 7,
+ /// Transmit FIFO reset. Write 1 to the register resets it. Read returns
+ /// 0
+ FifoControlTransmitReset = 1 << 8,
};
/// ControllerEvents Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Controller FSM is halted due to receiving an unexpected NACK.
- ControllerEventsNack = 1 << 0,
- /**
- * Controller FSM is halted due to a Host-Mode active transaction being
- * ended by the `hostNackHandlerTimeout` mechanism.
- */
- ControllerEventsUnhandledNackTimeout = 1 << 1,
- /**
- * Controller FSM is halted due to a Host-Mode active transaction being
- * terminated because of a bus timeout activated by `timeoutControl`.
- */
- ControllerEventsBusTimeout = 1 << 2,
- /**
- * Controller FSM is halted due to a Host-Mode active transaction being
- * terminated because of lost arbitration.
- */
- ControllerEventsArbitrationLost = 1 << 3,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Controller FSM is halted due to receiving an unexpected NACK.
+ ControllerEventsNack = 1 << 0,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * ended by the `hostNackHandlerTimeout` mechanism.
+ */
+ ControllerEventsUnhandledNackTimeout = 1 << 1,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * terminated because of a bus timeout activated by `timeoutControl`.
+ */
+ ControllerEventsBusTimeout = 1 << 2,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * terminated because of lost arbitration.
+ */
+ ControllerEventsArbitrationLost = 1 << 3,
};
// Referred to as 'RX FIFO' in the documentation
diff --git a/sdk/include/platform/sunburst/platform-pinmux.hh b/sdk/include/platform/sunburst/platform-pinmux.hh
new file mode 100644
index 0000000..3a1017e
--- /dev/null
+++ b/sdk/include/platform/sunburst/platform-pinmux.hh
@@ -0,0 +1,421 @@
+/**
+ * SPDX-FileCopyrightText: lowRISC contributors
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * This file is generated by ./util/topgen.py
+ * in https://github.com/lowRISC/sonata-system
+ */
+
+#pragma once
+#include <cheri.hh>
+#include <debug.hh>
+#include <stdint.h>
+#include <utils.hh>
+
+namespace SonataPinmux
+{
+ /// The number of pin sinks (pin outputs)
+ static constexpr size_t NumPinSinks = 85;
+
+ /// The number of block sinks (block inputs)
+ static constexpr size_t NumBlockSinks = 70;
+
+ /// Flag to set when debugging the driver for UART log messages.
+ static constexpr bool DebugDriver = false;
+
+ /// Helper for conditional debug logs and assertions.
+ using Debug = ConditionalDebug<DebugDriver, "Pinmux">;
+
+ /// The disable bit that disables a sink
+ constexpr uint8_t SourceDisabled = 0;
+
+ /// The bit that resets a sink to it's default value
+ constexpr uint8_t SourceDefault = 1;
+
+ /**
+ * Each pin sink is configured by an 8-bit register. This enum maps pin sink
+ * names to the offset of their configuration registers. The offsets are
+ * relative to the first pin sink register.
+ *
+ * Documentation sources:
+ * 1. https://lowrisc.github.io/sonata-system/doc/ip/pinmux/
+ * 2.
+ * https://github.com/lowRISC/sonata-system/blob/v1.0/data/pins_sonata.xdc
+ * 3.
+ * https://github.com/newaetech/sonata-pcb/blob/649b11c2fb758f798966605a07a8b6b68dd434e9/sonata-schematics-r09.pdf
+ */
+ enum class PinSink : uint16_t
+ {
+ // Breaking enum naming convention to match cononical names used in PCB
+ // schematic documentation.
+ // When available, it would be neater to use NOLINTBEGIN and NOLINTEND.
+ ser0_tx = 0x000, // NOLINT(readability-identifier-naming)
+ ser1_tx = 0x001, // NOLINT(readability-identifier-naming)
+ rs232_tx = 0x002, // NOLINT(readability-identifier-naming)
+ rs485_tx = 0x003, // NOLINT(readability-identifier-naming)
+ scl0 = 0x004, // NOLINT(readability-identifier-naming)
+ sda0 = 0x005, // NOLINT(readability-identifier-naming)
+ scl1 = 0x006, // NOLINT(readability-identifier-naming)
+ sda1 = 0x007, // NOLINT(readability-identifier-naming)
+ rph_g0 = 0x008, // NOLINT(readability-identifier-naming)
+ rph_g1 = 0x009, // NOLINT(readability-identifier-naming)
+ rph_g2_sda = 0x00a, // NOLINT(readability-identifier-naming)
+ rph_g3_scl = 0x00b, // NOLINT(readability-identifier-naming)
+ rph_g4 = 0x00c, // NOLINT(readability-identifier-naming)
+ rph_g5 = 0x00d, // NOLINT(readability-identifier-naming)
+ rph_g6 = 0x00e, // NOLINT(readability-identifier-naming)
+ rph_g7 = 0x00f, // NOLINT(readability-identifier-naming)
+ rph_g8 = 0x010, // NOLINT(readability-identifier-naming)
+ rph_g9 = 0x011, // NOLINT(readability-identifier-naming)
+ rph_g10 = 0x012, // NOLINT(readability-identifier-naming)
+ rph_g11 = 0x013, // NOLINT(readability-identifier-naming)
+ rph_g12 = 0x014, // NOLINT(readability-identifier-naming)
+ rph_g13 = 0x015, // NOLINT(readability-identifier-naming)
+ rph_txd0 = 0x016, // NOLINT(readability-identifier-naming)
+ rph_rxd0 = 0x017, // NOLINT(readability-identifier-naming)
+ rph_g16 = 0x018, // NOLINT(readability-identifier-naming)
+ rph_g17 = 0x019, // NOLINT(readability-identifier-naming)
+ rph_g18 = 0x01a, // NOLINT(readability-identifier-naming)
+ rph_g19 = 0x01b, // NOLINT(readability-identifier-naming)
+ rph_g20 = 0x01c, // NOLINT(readability-identifier-naming)
+ rph_g21 = 0x01d, // NOLINT(readability-identifier-naming)
+ rph_g22 = 0x01e, // NOLINT(readability-identifier-naming)
+ rph_g23 = 0x01f, // NOLINT(readability-identifier-naming)
+ rph_g24 = 0x020, // NOLINT(readability-identifier-naming)
+ rph_g25 = 0x021, // NOLINT(readability-identifier-naming)
+ rph_g26 = 0x022, // NOLINT(readability-identifier-naming)
+ rph_g27 = 0x023, // NOLINT(readability-identifier-naming)
+ ah_tmpio0 = 0x024, // NOLINT(readability-identifier-naming)
+ ah_tmpio1 = 0x025, // NOLINT(readability-identifier-naming)
+ ah_tmpio2 = 0x026, // NOLINT(readability-identifier-naming)
+ ah_tmpio3 = 0x027, // NOLINT(readability-identifier-naming)
+ ah_tmpio4 = 0x028, // NOLINT(readability-identifier-naming)
+ ah_tmpio5 = 0x029, // NOLINT(readability-identifier-naming)
+ ah_tmpio6 = 0x02a, // NOLINT(readability-identifier-naming)
+ ah_tmpio7 = 0x02b, // NOLINT(readability-identifier-naming)
+ ah_tmpio8 = 0x02c, // NOLINT(readability-identifier-naming)
+ ah_tmpio9 = 0x02d, // NOLINT(readability-identifier-naming)
+ ah_tmpio10 = 0x02e, // NOLINT(readability-identifier-naming)
+ ah_tmpio11 = 0x02f, // NOLINT(readability-identifier-naming)
+ ah_tmpio12 = 0x030, // NOLINT(readability-identifier-naming)
+ ah_tmpio13 = 0x031, // NOLINT(readability-identifier-naming)
+ mb1 = 0x032, // NOLINT(readability-identifier-naming)
+ mb2 = 0x033, // NOLINT(readability-identifier-naming)
+ mb4 = 0x034, // NOLINT(readability-identifier-naming)
+ mb5 = 0x035, // NOLINT(readability-identifier-naming)
+ mb6 = 0x036, // NOLINT(readability-identifier-naming)
+ mb7 = 0x037, // NOLINT(readability-identifier-naming)
+ mb10 = 0x038, // NOLINT(readability-identifier-naming)
+ pmod0_1 = 0x039, // NOLINT(readability-identifier-naming)
+ pmod0_2 = 0x03a, // NOLINT(readability-identifier-naming)
+ pmod0_3 = 0x03b, // NOLINT(readability-identifier-naming)
+ pmod0_4 = 0x03c, // NOLINT(readability-identifier-naming)
+ pmod0_7 = 0x03d, // NOLINT(readability-identifier-naming)
+ pmod0_8 = 0x03e, // NOLINT(readability-identifier-naming)
+ pmod0_9 = 0x03f, // NOLINT(readability-identifier-naming)
+ pmod0_10 = 0x040, // NOLINT(readability-identifier-naming)
+ pmod1_1 = 0x041, // NOLINT(readability-identifier-naming)
+ pmod1_2 = 0x042, // NOLINT(readability-identifier-naming)
+ pmod1_3 = 0x043, // NOLINT(readability-identifier-naming)
+ pmod1_4 = 0x044, // NOLINT(readability-identifier-naming)
+ pmod1_7 = 0x045, // NOLINT(readability-identifier-naming)
+ pmod1_8 = 0x046, // NOLINT(readability-identifier-naming)
+ pmod1_9 = 0x047, // NOLINT(readability-identifier-naming)
+ pmod1_10 = 0x048, // NOLINT(readability-identifier-naming)
+ pmodc_1 = 0x049, // NOLINT(readability-identifier-naming)
+ pmodc_2 = 0x04a, // NOLINT(readability-identifier-naming)
+ pmodc_3 = 0x04b, // NOLINT(readability-identifier-naming)
+ pmodc_4 = 0x04c, // NOLINT(readability-identifier-naming)
+ pmodc_5 = 0x04d, // NOLINT(readability-identifier-naming)
+ pmodc_6 = 0x04e, // NOLINT(readability-identifier-naming)
+ appspi_d0 = 0x04f, // NOLINT(readability-identifier-naming)
+ appspi_clk = 0x050, // NOLINT(readability-identifier-naming)
+ appspi_cs = 0x051, // NOLINT(readability-identifier-naming)
+ microsd_cmd = 0x052, // NOLINT(readability-identifier-naming)
+ microsd_clk = 0x053, // NOLINT(readability-identifier-naming)
+ microsd_dat3 = 0x054, // NOLINT(readability-identifier-naming)
+ };
+
+ /**
+ * Each block sink is configured by an 8-bit register. This enum maps block
+ * sink names to the offset of their configuration registers. The offsets
+ * are relative to the first block sink register.
+ *
+ * For GPIO block reference:
+ * gpio_0 = Raspberry Pi Header Pins
+ * gpio_1 = Arduino Shield Header Pins
+ * gpio_2 = Pmod0 Pins
+ * gpio_3 = Pmod1 Pins
+ * gpio_4 = PmodC Pins
+ *
+ * Documentation source:
+ * https://lowrisc.github.io/sonata-system/doc/ip/pinmux/
+ */
+ enum class BlockSink : uint16_t
+ {
+ // Breaking enum naming convention to match cononical names used in
+ // documentation.
+ // When available, it would be neater to use NOLINTBEGIN and NOLINTEND.
+ gpio_0_ios_0 = 0x000, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_1 = 0x001, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_2 = 0x002, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_3 = 0x003, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_4 = 0x004, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_5 = 0x005, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_6 = 0x006, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_7 = 0x007, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_8 = 0x008, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_9 = 0x009, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_10 = 0x00a, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_11 = 0x00b, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_12 = 0x00c, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_13 = 0x00d, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_14 = 0x00e, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_15 = 0x00f, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_16 = 0x010, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_17 = 0x011, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_18 = 0x012, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_19 = 0x013, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_20 = 0x014, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_21 = 0x015, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_22 = 0x016, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_23 = 0x017, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_24 = 0x018, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_25 = 0x019, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_26 = 0x01a, // NOLINT(readability-identifier-naming)
+ gpio_0_ios_27 = 0x01b, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_0 = 0x01c, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_1 = 0x01d, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_2 = 0x01e, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_3 = 0x01f, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_4 = 0x020, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_5 = 0x021, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_6 = 0x022, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_7 = 0x023, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_8 = 0x024, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_9 = 0x025, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_10 = 0x026, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_11 = 0x027, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_12 = 0x028, // NOLINT(readability-identifier-naming)
+ gpio_1_ios_13 = 0x029, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_0 = 0x02a, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_1 = 0x02b, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_2 = 0x02c, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_3 = 0x02d, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_4 = 0x02e, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_5 = 0x02f, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_6 = 0x030, // NOLINT(readability-identifier-naming)
+ gpio_2_ios_7 = 0x031, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_0 = 0x032, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_1 = 0x033, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_2 = 0x034, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_3 = 0x035, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_4 = 0x036, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_5 = 0x037, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_6 = 0x038, // NOLINT(readability-identifier-naming)
+ gpio_3_ios_7 = 0x039, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_0 = 0x03a, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_1 = 0x03b, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_2 = 0x03c, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_3 = 0x03d, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_4 = 0x03e, // NOLINT(readability-identifier-naming)
+ gpio_4_ios_5 = 0x03f, // NOLINT(readability-identifier-naming)
+ uart_0_rx = 0x040, // NOLINT(readability-identifier-naming)
+ uart_1_rx = 0x041, // NOLINT(readability-identifier-naming)
+ uart_2_rx = 0x042, // NOLINT(readability-identifier-naming)
+ spi_0_cipo = 0x043, // NOLINT(readability-identifier-naming)
+ spi_1_cipo = 0x044, // NOLINT(readability-identifier-naming)
+ spi_2_cipo = 0x045, // NOLINT(readability-identifier-naming)
+ };
+
+ /**
+ * Returns the number of sources available for a pin sink (output pin).
+ *
+ * @param pin_sink The pin sink to query.
+ * @returns The number of sources available for the given sink.
+ */
+ static constexpr uint8_t sources_number(PinSink pinSink)
+ {
+ switch (pinSink)
+ {
+ case PinSink::pmod0_2:
+ case PinSink::pmod1_2:
+ return 5;
+ case PinSink::rph_g18:
+ case PinSink::rph_g20:
+ case PinSink::rph_g21:
+ case PinSink::ah_tmpio10:
+ case PinSink::ah_tmpio11:
+ case PinSink::pmod0_4:
+ case PinSink::pmod1_4:
+ return 4;
+ case PinSink::ser1_tx:
+ case PinSink::rph_g0:
+ case PinSink::rph_g1:
+ case PinSink::rph_g2_sda:
+ case PinSink::rph_g3_scl:
+ case PinSink::rph_g7:
+ case PinSink::rph_g8:
+ case PinSink::rph_g10:
+ case PinSink::rph_g11:
+ case PinSink::rph_g12:
+ case PinSink::rph_g13:
+ case PinSink::rph_txd0:
+ case PinSink::rph_g16:
+ case PinSink::rph_g17:
+ case PinSink::rph_g19:
+ case PinSink::ah_tmpio1:
+ case PinSink::ah_tmpio3:
+ case PinSink::ah_tmpio5:
+ case PinSink::ah_tmpio6:
+ case PinSink::ah_tmpio9:
+ case PinSink::ah_tmpio13:
+ case PinSink::pmod0_1:
+ case PinSink::pmod0_3:
+ case PinSink::pmod0_8:
+ case PinSink::pmod0_9:
+ case PinSink::pmod0_10:
+ case PinSink::pmod1_1:
+ case PinSink::pmod1_3:
+ case PinSink::pmod1_8:
+ case PinSink::pmod1_9:
+ case PinSink::pmod1_10:
+ return 3;
+ default:
+ return 2;
+ }
+ }
+
+ /**
+ * Returns the number of sources available for a block sink (block input).
+ *
+ * @param block_sink The block sink to query.
+ * @returns The number of sources available for the given sink.
+ */
+ static constexpr uint8_t sources_number(BlockSink blockSink)
+ {
+ switch (blockSink)
+ {
+ case BlockSink::uart_1_rx:
+ return 6;
+ case BlockSink::uart_2_rx:
+ return 5;
+ case BlockSink::spi_1_cipo:
+ case BlockSink::spi_2_cipo:
+ return 4;
+ case BlockSink::spi_0_cipo:
+ return 3;
+ default:
+ return 2;
+ }
+ }
+
+ /**
+ * A handle to a sink configuration register. This can be used to select
+ * the source of the handle's associated sink.
+ */
+ template<typename SinkEnum>
+ struct Sink
+ {
+ CHERI::Capability<volatile uint8_t> reg;
+ const SinkEnum SinkId;
+
+ /**
+ * Select a source to connect to the sink.
+ *
+ * To see the sources available for a given sink see the Sonata system
+ * documentation:
+ * https://lowrisc.github.io/sonata-system/doc/ip/pinmux/
+ *
+ * Note, source 0 disconnects the sink from any source disabling it,
+ * and source 1 is the default source for any given sink.
+ */
+ bool select(uint8_t source)
+ {
+ if (source >= sources_number(SinkId))
+ {
+ Debug::log("{} is outside the range of valid sources, [0-{}), "
+ "of pin {}.",
+ source,
+ sources_number(SinkId),
+ SinkId);
+ return false;
+ }
+ *reg = 1 << source;
+ return true;
+ }
+
+ /// Disconnect the sink from all available sources.
+ void disable()
+ {
+ *reg = 1 << SourceDisabled;
+ }
+
+ /// Reset the sink to it's default source.
+ void default_selection()
+ {
+ *reg = 1 << SourceDefault;
+ }
+ };
+
+ namespace
+ {
+ template<typename SinkEnum>
+ // This is used by `BlockSinks` and `PinSinks`
+ // to return a capability to a single sink's configuration register.
+ inline Sink<SinkEnum> get_sink(volatile uint8_t *baseRegister,
+ const SinkEnum SinkId)
+ {
+ CHERI::Capability reg = {baseRegister +
+ static_cast<ptrdiff_t>(SinkId)};
+ reg.bounds() = sizeof(uint8_t);
+ return Sink<SinkEnum>{reg, SinkId};
+ };
+ } // namespace
+
+ /**
+ * A driver for the Sonata system's pin multiplexed output pins.
+ *
+ * The Sonata system's Pin Multiplexer (pinmux) has two sets of registers:
+ * the pin sink registers and the block sink registers. This structure
+ * provides access to the pin sinks registers. Pin sinks are output onto the
+ * Sonata system's pins, which can be connected to a number of block outputs
+ * (their sources). The sources each sink can connect to are limited. See
+ * the documentation for the possible sources for a given pin:
+ *
+ * https://lowrisc.github.io/sonata-system/doc/ip/pinmux/
+ */
+ struct PinSinks : private utils::NoCopyNoMove
+ {
+ volatile uint8_t registers[NumPinSinks];
+
+ /// Returns a handle to a pin sink (an output pin).
+ Sink<PinSink> get(PinSink sink) volatile
+ {
+ return get_sink<PinSink>(registers, sink);
+ };
+ };
+
+ /**
+ * A driver for the Sonata system's pin multiplexed block inputs.
+ *
+ * The Sonata system's Pin Multiplexer (pinmux) has two sets of registers:
+ * the pin sink registers and the block sink registers. This structure
+ * provides access to the block sinks registers. Block sinks are inputs into
+ * the Sonata system's devices that can be connected to a number of system
+ * input pins (their sources). The sources each sink can connect to are
+ * limited. See the documentation for the possible sources for a given pin:
+ *
+ * https://lowrisc.github.io/sonata-system/doc/ip/pinmux
+ */
+ struct BlockSinks : private utils::NoCopyNoMove
+ {
+ volatile uint8_t registers[NumBlockSinks];
+
+ /// Returns a handle to a block sink (a block input).
+ Sink<BlockSink> get(BlockSink sink) volatile
+ {
+ return get_sink<BlockSink>(registers, sink);
+ };
+ };
+} // namespace SonataPinmux
diff --git a/sdk/include/platform/sunburst/platform-rgbctrl.hh b/sdk/include/platform/sunburst/platform-rgbctrl.hh
index 1e5a153..067d9e0 100644
--- a/sdk/include/platform/sunburst/platform-rgbctrl.hh
+++ b/sdk/include/platform/sunburst/platform-rgbctrl.hh
@@ -33,23 +33,25 @@
uint32_t status;
/// Control Register Fields
- enum [[clang::flag_enum]] ControlFields : uint32_t{
- /// Write 1 to set RGB LEDs to specified colours.
- ControlSet = 1 << 0,
- /**
- * Write 1 to turn off RGB LEDs.
- * Write to ControlSet to turn on again.
- */
- ControlOff = 1 << 1,
+ enum [[clang::flag_enum]] ControlFields : uint32_t
+ {
+ /// Write 1 to set RGB LEDs to specified colours.
+ ControlSet = 1 << 0,
+ /**
+ * Write 1 to turn off RGB LEDs.
+ * Write to ControlSet to turn on again.
+ */
+ ControlOff = 1 << 1,
};
/// Status Register Fields
- enum [[clang::flag_enum]] StatusFields : uint32_t{
- /**
- * When asserted controller is idle and new colours can be set,
- * otherwise writes to regLed0, regLed1, and control are ignored.
- */
- StatusIdle = 1 << 0,
+ enum [[clang::flag_enum]] StatusFields : uint32_t
+ {
+ /**
+ * When asserted controller is idle and new colours can be set,
+ * otherwise writes to regLed0, regLed1, and control are ignored.
+ */
+ StatusIdle = 1 << 0,
};
/**
diff --git a/sdk/include/platform/sunburst/platform-simulation_exit.hh b/sdk/include/platform/sunburst/platform-simulation_exit.hh
new file mode 100644
index 0000000..a158585
--- /dev/null
+++ b/sdk/include/platform/sunburst/platform-simulation_exit.hh
@@ -0,0 +1,30 @@
+// Copyright Microsoft and CHERIoT Contributors.
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#ifdef SIMULATION
+# include <stdint.h>
+# include <platform-uart.hh>
+# include <string_view>
+
+static void platform_simulation_exit(uint32_t code)
+{
+ auto uart =
+# if DEVICE_EXISTS(uart0)
+ MMIO_CAPABILITY(Uart, uart0);
+# elif DEVICE_EXISTS(uart)
+ MMIO_CAPABILITY(Uart, uart);
+# else
+# error No UART found in platform_simulation_exit
+# endif
+ // Writing the following magic string to the UART will cause the sonata
+ // simulator to exit.
+ const char *magicString =
+ "Safe to exit simulator.\xd8\xaf\xfb\xa0\xc7\xe1\xa9\xd7";
+ while (char ch = *magicString++)
+ {
+ uart->blocking_write(ch);
+ }
+}
+#endif
diff --git a/sdk/include/platform/sunburst/platform-spi.hh b/sdk/include/platform/sunburst/platform-spi.hh
index fd8aa19..e835640 100644
--- a/sdk/include/platform/sunburst/platform-spi.hh
+++ b/sdk/include/platform/sunburst/platform-spi.hh
@@ -2,53 +2,30 @@
#include <cdefs.h>
#include <debug.hh>
#include <stdint.h>
+#include <utils.hh>
-/**
- * A Simple Driver for the Sonata's SPI.
- *
- * Documentation source can be found at:
- * https://github.com/lowRISC/sonata-system/blob/1a59633d2515d4fe186a07d53e49ff95c18d9bbf/doc/ip/spi.md
- *
- * Rendered documentation is served from:
- * https://lowrisc.org/sonata-system/doc/ip/spi.html
- */
-struct SonataSpi
+namespace SonataSpi
{
- /**
- * The Sonata SPI block doesn't currently have support for interrupts.
- * The following registers are reserved for future use.
- */
- uint32_t interruptState;
- uint32_t interruptEnable;
- uint32_t interruptTest;
- /**
- * Configuration register. Controls how the SPI block transmits and
- * receives data. This register can be modified only whilst the SPI block
- * is idle.
- */
- uint32_t configuration;
- /**
- * Controls the operation of the SPI block. This register can
- * be modified only whilst the SPI block is idle.
- */
- uint32_t control;
- /// Status information about the SPI block
- uint32_t status;
- /**
- * Writes to this begin an SPI operation.
- * Writes are ignored when the SPI block is active.
- */
- uint32_t start;
- /**
- * Data from the receive FIFO. When read the data is popped from the FIFO.
- * If the FIFO is empty data read is undefined.
- */
- uint32_t receiveFifo;
- /**
- * Bytes written here are pushed to the transmit FIFO. If the FIFO is full
- * writes are ignored.
- */
- uint32_t transmitFifo;
+ /// Sonata SPI Interrupts
+ typedef enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Raised when a SPI operation completes and the block has become idle.
+ InterruptComplete = 1 << 4,
+ /*
+ * Asserted whilst the transmit FIFO level is at or below the
+ * transmit watermark.
+ */
+ InterruptTransmitWatermark = 1 << 3,
+ /// Asserted whilst the transmit FIFO is empty.
+ InterruptTransmitEmpty = 1 << 2,
+ /*
+ * Asserted whilst the receive FIFO level is at or above the receive
+ * watermark.
+ */
+ InterruptReceiveWatermark = 1 << 1,
+ /// Asserted whilst the receive FIFO is full.
+ InterruptReceiveFull = 1 << 0,
+ } Interrupt;
/// Configuration Register Fields
enum : uint32_t
@@ -110,6 +87,14 @@
* interrupt will trigger at different points
*/
ControlReceiveWatermarkMask = 0xf << 8,
+ /**
+ * Internal loopback function enabled when set to 1.
+ */
+ ControlInternalLoopback = 1 << 30,
+ /**
+ * Software reset performed when written as 1.
+ */
+ ControlSoftwareReset = 1u << 31,
};
/// Status Register Fields
@@ -140,96 +125,289 @@
StartByteCountMask = 0x7ffu,
};
- /// Flag set when we're debugging this driver.
- static constexpr bool DebugSonataSpi = false;
-
- /// Helper for conditional debug logs and assertions.
- using Debug = ConditionalDebug<DebugSonataSpi, "Sonata SPI">;
+ /// Info Register Fields
+ enum : uint32_t
+ {
+ /// Maximum number of items in the transmit FIFO.
+ InfoTxFifoDepth = 0xffu << 0,
+ /// Maximum number of items in the receive FIFO.
+ InfoRxFifoDepth = 0xffu << 8,
+ };
/**
- * Initialises the SPI block
+ * A driver for the Sonata's SPI device block.
*
- * @param ClockPolarity When false, the clock is low when idle and the
- * leading edge is positive. When true, the opposite behaviour is
- * set.
- * @param ClockPhase When false, data is sampled on the leading edge and
- * changes on the trailing edge. When true, the opposite behaviour is
- * set.
- * @param MsbFirst When true, the first bit of each byte sent is the most
- * significant bit, as oppose to the least significant bit.
- * @param HalfClockPeriod The length of a half period of the SPI clock,
- * measured in system clock cycles reduced by 1.
+ * Documentation source can be found at:
+ * https://github.com/lowRISC/sonata-system/blob/1a59633d2515d4fe186a07d53e49ff95c18d9bbf/doc/ip/spi.md
+ *
+ * Rendered documentation is served from:
+ * https://lowrisc.org/sonata-system/doc/ip/spi.html
*/
- void init(const bool ClockPolarity,
- const bool ClockPhase,
- const bool MsbFirst,
- const uint16_t HalfClockPeriod) volatile
+ template<size_t NumChipSelects = 4>
+ struct Generic : private utils::NoCopyNoMove
{
- configuration = (ClockPolarity ? ConfigurationClockPolarity : 0) |
- (ClockPhase ? ConfigurationClockPhase : 0) |
- (MsbFirst ? ConfigurationMSBFirst : 0) |
- (HalfClockPeriod & ConfigurationHalfClockPeriodMask);
- }
+ /// The current state of the SPI interrupts.
+ uint32_t interruptState;
+ /// Controls which interrupts are enabled.
+ uint32_t interruptEnable;
+ /// Allows one to manually trigger an interrupt for testing.
+ uint32_t interruptTest;
+ /**
+ * Configuration register. Controls how the SPI block transmits and
+ * receives data. This register can be modified only whilst the SPI
+ * block is idle.
+ */
+ uint32_t configuration;
+ /**
+ * Controls the operation of the SPI block. This register can
+ * be modified only whilst the SPI block is idle.
+ */
+ uint32_t control;
+ /// Status information about the SPI block
+ uint32_t status;
+ /**
+ * Writes to this begin an SPI operation.
+ * Writes are ignored when the SPI block is active.
+ */
+ uint32_t start;
+ /**
+ * Data from the receive FIFO. When read the data is popped from the
+ * FIFO. If the FIFO is empty data read is undefined.
+ */
+ uint32_t receiveFifo;
+ /**
+ * Bytes written here are pushed to the transmit FIFO. If the FIFO is
+ * full writes are ignored.
+ */
+ uint32_t transmitFifo;
+ /**
+ * Information about the SPI controller. This register reports the
+ * depths of the transmit and receive FIFOs within the controller.
+ */
+ uint32_t info;
+ /**
+ * Chip Select lines; each bit controls a chip select line.
+ * When a bit set to zero, a chip select line is pulled low.
+ * Multiple chip select lines can be pulled low at a time.
+ */
+ uint32_t chipSelects;
- /// Waits for the SPI device to become idle
- void wait_idle() volatile
- {
- // Wait whilst IDLE field in STATUS is low
- while ((status & StatusIdle) == 0) {}
- }
+ /// Flag set when we're debugging this driver.
+ static constexpr bool DebugSonataSpi = false;
- /**
- * Sends `len` bytes from the given `data` buffer,
- * where `len` is at most `0x7ff`.
- */
- void blocking_write(const uint8_t data[], uint16_t len) volatile
- {
- Debug::Assert(len <= 0x7ff,
- "You can't transfer more than 0x7ff bytes at a time.");
- len &= StartByteCountMask;
+ /// Helper for conditional debug logs and assertions.
+ using Debug = ConditionalDebug<DebugSonataSpi, "Sonata SPI">;
- wait_idle();
- control = ControlTransmitEnable;
- start = len;
-
- uint32_t transmitAvailable = 0;
- for (uint32_t i = 0; i < len; ++i)
+ /// Enable the given interrupt(s).
+ inline void interrupt_enable(Interrupt interrupt) volatile
{
- if (transmitAvailable == 0)
+ interruptEnable = interruptEnable | interrupt;
+ }
+
+ /// Disable the given interrupt(s).
+ inline void interrupt_disable(Interrupt interrupt) volatile
+ {
+ interruptEnable = interruptEnable & ~interrupt;
+ }
+
+ /**
+ * Initialises the SPI block
+ *
+ * @param ClockPolarity When false, the clock is low when idle and the
+ * leading edge is positive. When true, the opposite behaviour is
+ * set.
+ * @param ClockPhase When false, data is sampled on the leading edge and
+ * changes on the trailing edge. When true, the opposite
+ * behaviour is set.
+ * @param MsbFirst When true, the first bit of each byte sent is the
+ * most significant bit, as oppose to the least significant bit.
+ * @param HalfClockPeriod The length of a half period of the SPI clock,
+ * measured in system clock cycles reduced by 1.
+ */
+ void init(const bool ClockPolarity,
+ const bool ClockPhase,
+ const bool MsbFirst,
+ const uint16_t HalfClockPeriod) volatile
+ {
+ configuration =
+ (ClockPolarity ? ConfigurationClockPolarity : 0) |
+ (ClockPhase ? ConfigurationClockPhase : 0) |
+ (MsbFirst ? ConfigurationMSBFirst : 0) |
+ (HalfClockPeriod & ConfigurationHalfClockPeriodMask);
+
+ // Ensure that FIFOs are emptied of any stale data and the
+ // controller core has returned to Idle if it is presently active
+ // (eg. an incomplete previous operation, perhaps one that was
+ // interrupted/failed).
+ //
+ // Note: although, from a logical perspective, the three operations
+ // (i) tx clear, (ii) core reset and (iii) rx clear should be
+ // performed in that order, presently the implementation supports
+ // performing them as a single write.
+ control =
+ ControlTransmitClear | ControlSoftwareReset | ControlReceiveClear;
+ }
+
+ /// Waits for the SPI device to become idle
+ void wait_idle() volatile
+ {
+ // Wait whilst IDLE field in STATUS is low
+ while ((status & StatusIdle) == 0) {}
+ }
+
+ /**
+ * Sends `len` bytes from the given `data` buffer,
+ * where `len` is at most `0x7ff`.
+ */
+ void blocking_write(const uint8_t data[], uint16_t len) volatile
+ {
+ Debug::Assert(
+ len <= StartByteCountMask,
+ "You can't transfer more than 0x7ff bytes at a time.");
+ len &= StartByteCountMask;
+
+ wait_idle();
+ // Do not attempt a zero-byte transfer; not supported by the
+ // controller.
+ if (len)
{
- while (transmitAvailable < 64)
+ control = ControlTransmitEnable;
+ start = len;
+
+ uint32_t transmitAvailable = 0;
+ for (uint32_t i = 0; i < len; ++i)
{
- // Read number of bytes in TX FIFO to calculate space
- // available for more bytes
- transmitAvailable = 64 - (status & StatusTxFifoLevel);
+ while (!transmitAvailable)
+ {
+ // Read number of bytes in TX FIFO to calculate space
+ // available for more bytes
+ transmitAvailable = 8 - (status & StatusTxFifoLevel);
+ }
+ transmitFifo = data[i];
+ transmitAvailable--;
}
}
- transmitFifo = data[i];
- transmitAvailable--;
}
- }
- /*
- * Receives `len` bytes and puts them in the `data` buffer,
- * where `len` is at most `0x7ff`.
- *
- * This method will block until the requested number of bytes
- * has been seen. There is currently no timeout.
- */
- void blocking_read(uint8_t data[], uint16_t len) volatile
- {
- Debug::Assert(len <= 0x7ff,
- "You can't receive more than 0x7ff bytes at a time.");
- len &= StartByteCountMask;
- wait_idle();
- control = ControlReceiveEnable;
- start = len;
-
- for (uint32_t i = 0; i < len; ++i)
+ /*
+ * Receives `len` bytes and puts them in the `data` buffer,
+ * where `len` is at most `0x7ff`.
+ *
+ * This method will block until the requested number of bytes
+ * has been seen. There is currently no timeout.
+ */
+ void blocking_read(uint8_t data[], uint16_t len) volatile
{
- // Wait for at least one byte to be available in the RX FIFO
- while ((status & StatusRxFifoLevel) == 0) {}
- data[i] = static_cast<uint8_t>(receiveFifo);
+ Debug::Assert(len <= StartByteCountMask,
+ "You can't receive more than 0x7ff bytes at a time.");
+ len &= StartByteCountMask;
+ wait_idle();
+ // Do not attempt a zero-byte transfer; not supported by the
+ // controller.
+ if (len)
+ {
+ control = ControlReceiveEnable;
+ start = len;
+
+ for (uint32_t i = 0; i < len; ++i)
+ {
+ // Wait for at least one byte to be available in the RX FIFO
+ while ((status & StatusRxFifoLevel) == 0) {}
+ data[i] = static_cast<uint8_t>(receiveFifo);
+ }
+ }
}
- }
-};
+
+ /**
+ * Asserts/de-asserts a given chip select.
+ *
+ * Note, SPI chip selects are active low signals, so the register bit is
+ * zero when asserted and one when de-asserted.
+ *
+ * @tparam Index The index of the chip select to be set.
+ * @tparam DeassertOthers Whether to de-assert all other chip selects.
+ * @param Assert Whether to assert (true) or de-assert (false).
+ */
+ template<uint8_t Index, bool DeassertOthers = true>
+ inline void chip_select_assert(const bool Assert = true) volatile
+ {
+ static_assert(Index < NumChipSelects,
+ "SPI chip select index out of bounds");
+
+ const uint32_t State =
+ DeassertOthers ? (1 << NumChipSelects) - 1 : chipSelects;
+
+ const uint32_t Bit = (1 << Index);
+ chipSelects = Assert ? State & ~Bit : State | Bit;
+ }
+ };
+
+ /// A specialised driver for the SPI device connected to the Ethernet MAC.
+ class EthernetMac : public Generic<2>
+ {
+ enum : uint8_t
+ {
+ ChipSelectLine = 0,
+ ResetLine = 1,
+ };
+
+ public:
+ /**
+ * Assert the chip select line.
+ * @param Assert Whether to assert (true) or de-assert (false) the chip
+ * select line.
+ */
+ inline void chip_select_assert(const bool Assert) volatile
+ {
+ this->Generic<2>::chip_select_assert<ChipSelectLine, false>(Assert);
+ }
+ /**
+ * Assert the reset line.
+ * @param Assert Whether to assert (true) or de-assert (false) the reset
+ * line.
+ */
+ inline void reset_assert(const bool Assert = true) volatile
+ {
+ this->Generic<2>::chip_select_assert<ResetLine, false>(Assert);
+ }
+ };
+
+ /// A specialised driver for the SPI device connected to the LCD screen.
+ class Lcd : public Generic<3>
+ {
+ enum : uint8_t
+ {
+ ChipSelectLine = 0,
+ DataCommandLine = 1,
+ ResetLine = 2,
+ };
+
+ public:
+ /**
+ * Assert the chip select line.
+ * @param Assert Whether to assert (true) or de-assert (false) the chip
+ * select line.
+ */
+ inline void chip_select_assert(const bool Assert) volatile
+ {
+ this->Generic<3>::chip_select_assert<ChipSelectLine, false>(Assert);
+ }
+ /**
+ * Assert the chip select line.
+ * @param Assert Whether to assert (true) or de-assert (false) the reset
+ * line.
+ */
+ inline void reset_assert(const bool Assert = true) volatile
+ {
+ this->Generic<3>::chip_select_assert<ResetLine, false>(Assert);
+ }
+ /**
+ * Set the data/command line.
+ * @param high Whether to set high (true) or low (false).
+ */
+ inline void data_command_set(const bool High) volatile
+ {
+ this->Generic<3>::chip_select_assert<DataCommandLine, false>(!High);
+ }
+ };
+} // namespace SonataSpi
diff --git a/sdk/include/platform/sunburst/platform-uart.hh b/sdk/include/platform/sunburst/platform-uart.hh
index 09f217d..6513112 100644
--- a/sdk/include/platform/sunburst/platform-uart.hh
+++ b/sdk/include/platform/sunburst/platform-uart.hh
@@ -75,36 +75,37 @@
uint32_t timeoutControl;
/// OpenTitan UART Interrupts
- typedef enum [[clang::flag_enum]]
- : uint32_t{
- /// Raised if the transmit FIFO is empty.
- InterruptTransmitEmpty = 1 << 8,
- /// Raised if the receiver has detected a parity error.
- InterruptReceiveParityErr = 1 << 7,
- /// Raised if the receive FIFO has characters remaining in the FIFO
- /// without being
- /// retreived for the programmed time period.
- InterruptReceiveTimeout = 1 << 6,
- /// Raised if break condition has been detected on receive.
- InterruptReceiveBreakErr = 1 << 5,
- /// Raised if a framing error has been detected on receive.
- InterruptReceiveFrameErr = 1 << 4,
- /// Raised if the receive FIFO has overflowed.
- InterruptReceiveOverflow = 1 << 3,
- /// Raised if the transmit FIFO has emptied and no transmit is ongoing.
- InterruptTransmitDone = 1 << 2,
- /// Raised if the receive FIFO is past the high-water mark.
- InterruptReceiveWatermark = 1 << 1,
- /// Raised if the transmit FIFO is past the high-water mark.
- InterruptTransmitWatermark = 1 << 0,
- } OpenTitanUartInterrupt;
+ typedef enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Raised if the transmit FIFO is empty.
+ InterruptTransmitEmpty = 1 << 8,
+ /// Raised if the receiver has detected a parity error.
+ InterruptReceiveParityErr = 1 << 7,
+ /// Raised if the receive FIFO has characters remaining in the FIFO
+ /// without being
+ /// retreived for the programmed time period.
+ InterruptReceiveTimeout = 1 << 6,
+ /// Raised if break condition has been detected on receive.
+ InterruptReceiveBreakErr = 1 << 5,
+ /// Raised if a framing error has been detected on receive.
+ InterruptReceiveFrameErr = 1 << 4,
+ /// Raised if the receive FIFO has overflowed.
+ InterruptReceiveOverflow = 1 << 3,
+ /// Raised if the transmit FIFO has emptied and no transmit is ongoing.
+ InterruptTransmitDone = 1 << 2,
+ /// Raised if the receive FIFO is past the high-water mark.
+ InterruptReceiveWatermark = 1 << 1,
+ /// Raised if the transmit FIFO is past the high-water mark.
+ InterruptTransmitWatermark = 1 << 0,
+ } OpenTitanUartInterrupt;
/// FIFO Control Register Fields
- enum [[clang::flag_enum]] : uint32_t{
- /// Reset the transmit FIFO.
- FifoControlTransmitReset = 1 << 1,
- /// Reset the receive FIFO.
- FifoControlReceiveReset = 1 << 0,
+ enum [[clang::flag_enum]] : uint32_t
+ {
+ /// Reset the transmit FIFO.
+ FifoControlTransmitReset = 1 << 1,
+ /// Reset the receive FIFO.
+ FifoControlReceiveReset = 1 << 0,
};
/// Control Register Fields
diff --git a/sdk/include/platform/sunburst/platform-usbdev.hh b/sdk/include/platform/sunburst/platform-usbdev.hh
index 1a279bc..cc03ea5 100644
--- a/sdk/include/platform/sunburst/platform-usbdev.hh
+++ b/sdk/include/platform/sunburst/platform-usbdev.hh
@@ -250,9 +250,9 @@
[[nodiscard]] uint64_t supply_buffers(uint64_t bufferBitmap) volatile
{
constexpr uint32_t SetupFullBit =
- uint32_t(UsbStatusField::AvailableSetupFull);
+ static_cast<uint32_t>(UsbStatusField::AvailableSetupFull);
constexpr uint32_t OutFullBit =
- uint32_t(UsbStatusField::AvailableOutFull);
+ static_cast<uint32_t>(UsbStatusField::AvailableOutFull);
for (uint8_t index = 0; index < BufferCount; index++)
{
@@ -286,7 +286,7 @@
*/
void interrupt_enable(UsbdevInterrupt interrupt) volatile
{
- interruptEnable = interruptEnable | uint32_t(interrupt);
+ interruptEnable = interruptEnable | static_cast<uint32_t>(interrupt);
}
/**
@@ -294,7 +294,7 @@
*/
void interrupt_disable(UsbdevInterrupt interrupt) volatile
{
- interruptEnable = interruptEnable & ~uint32_t(interrupt);
+ interruptEnable = interruptEnable & ~static_cast<uint32_t>(interrupt);
}
/**
@@ -310,8 +310,10 @@
*/
[[nodiscard]] int init(uint64_t &bufferBitmap) volatile
{
- bufferBitmap = supply_buffers((uint64_t(1u) << BufferCount) - 1u);
- phyConfig = uint32_t(PhyConfigField::UseDifferentialReceiver);
+ bufferBitmap =
+ supply_buffers((static_cast<uint64_t>(1u) << BufferCount) - 1u);
+ phyConfig =
+ static_cast<uint32_t>(PhyConfigField::UseDifferentialReceiver);
return 0;
}
@@ -404,7 +406,8 @@
{
return -1;
}
- usbControl = usbControl | uint32_t(UsbControlField::Enable);
+ usbControl =
+ usbControl | static_cast<uint32_t>(UsbControlField::Enable);
return 0;
}
@@ -415,7 +418,8 @@
*/
void disconnect() volatile
{
- usbControl = usbControl & ~uint32_t(UsbControlField::Enable);
+ usbControl =
+ usbControl & ~static_cast<uint32_t>(UsbControlField::Enable);
}
/**
@@ -425,7 +429,7 @@
*/
[[nodiscard]] bool connected() volatile
{
- return (usbControl & uint32_t(UsbControlField::Enable));
+ return (usbControl & static_cast<uint32_t>(UsbControlField::Enable));
}
/**
@@ -443,8 +447,9 @@
{
return -1; // Device addresses are only 7 bits long.
}
- constexpr uint32_t Mask = uint32_t(UsbControlField::DeviceAddress);
- usbControl = (usbControl & ~Mask) | (address << 16);
+ constexpr uint32_t Mask =
+ static_cast<uint32_t>(UsbControlField::DeviceAddress);
+ usbControl = (usbControl & ~Mask) | (address << 16);
return 0;
}
@@ -463,8 +468,9 @@
[[nodiscard]] int retrieve_collected_packet(uint8_t &endpointId,
uint8_t &bufferId) volatile
{
- constexpr uint32_t BufferIdMask = uint32_t(ConfigInField::BufferId);
- uint32_t sent = inSent;
+ constexpr uint32_t BufferIdMask =
+ static_cast<uint32_t>(ConfigInField::BufferId);
+ uint32_t sent = inSent;
// Clear the first encountered packet sent indication.
for (endpointId = 0; endpointId < MaxEndpoints; endpointId++)
@@ -504,9 +510,10 @@
usbdev_transfer(buffer(bufferId), data, size, true);
}
- constexpr uint32_t ReadyBit = uint32_t(ConfigInField::Ready);
- configIn[endpointId] = bufferId | (size << 8);
- configIn[endpointId] = configIn[endpointId] | ReadyBit;
+ constexpr uint32_t ReadyBit =
+ static_cast<uint32_t>(ConfigInField::Ready);
+ configIn[endpointId] = bufferId | (size << 8);
+ configIn[endpointId] = configIn[endpointId] | ReadyBit;
}
/// The information associated with a received packet
@@ -516,22 +523,28 @@
/// The endpoint ID the received packet was received on
constexpr uint8_t endpoint_id()
{
- return (info & uint32_t(ReceiveBufferField::EndpointId)) >> 20;
+ return (info &
+ static_cast<uint32_t>(ReceiveBufferField::EndpointId)) >>
+ 20;
}
/// The size of the received packet
constexpr uint16_t size()
{
- return (info & uint32_t(ReceiveBufferField::Size)) >> 8;
+ return (info & static_cast<uint32_t>(ReceiveBufferField::Size)) >>
+ 8;
}
/// Whether the received packet was a setup packet
constexpr bool is_setup()
{
- return (info & uint32_t(ReceiveBufferField::Setup)) != 0;
+ return (info & static_cast<uint32_t>(ReceiveBufferField::Setup)) !=
+ 0;
}
/// The buffer ID used to store the received packet
constexpr uint8_t buffer_id()
{
- return (info & uint32_t(ReceiveBufferField::BufferId)) >> 0;
+ return (info &
+ static_cast<uint32_t>(ReceiveBufferField::BufferId)) >>
+ 0;
}
};
@@ -546,7 +559,7 @@
*/
[[nodiscard]] std::optional<ReceiveBufferInfo> packet_take() volatile
{
- if (!(usbStatus & uint32_t(UsbStatusField::ReceiveDepth)))
+ if (!(usbStatus & static_cast<uint32_t>(UsbStatusField::ReceiveDepth)))
{
return {}; // No packets received
}
diff --git a/sdk/include/platform/sunburst/v0.2/platform-ethernet.hh b/sdk/include/platform/sunburst/v0.2/platform-ethernet.hh
new file mode 100644
index 0000000..f4bf3e3
--- /dev/null
+++ b/sdk/include/platform/sunburst/v0.2/platform-ethernet.hh
@@ -0,0 +1,822 @@
+#pragma once
+#include <array>
+#include <cheri.hh>
+#include <cstdint>
+#include <debug.hh>
+#include <futex.h>
+#include <interrupt.h>
+#include <locks.hh>
+#include <optional>
+#include <platform/concepts/ethernet.hh>
+#include <platform/sunburst/platform-gpio.hh>
+#include <platform/sunburst/v0.2/platform-spi.hh>
+#include <thread.h>
+
+DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(EthernetInterruptCapability,
+ InterruptName::EthernetInterrupt,
+ true,
+ true);
+
+/**
+ * The driver for KSZ8851 SPI Ethernet MAC.
+ */
+class Ksz8851Ethernet
+{
+ /**
+ * Flag set when we're debugging this driver.
+ */
+ static constexpr bool DebugEthernet = false;
+
+ /**
+ * Flag set to log messages when frames are dropped.
+ */
+ static constexpr bool DebugDroppedFrames = true;
+
+ /**
+ * Maxmium size of a single Ethernet frame.
+ */
+ static constexpr uint16_t MaxFrameSize = 1500;
+
+ /**
+ * Helper for conditional debug logs and assertions.
+ */
+ using Debug = ConditionalDebug<DebugEthernet, "Ethernet driver">;
+
+ /**
+ * Helper for conditional debug logs and assertions for dropped frames.
+ */
+ using DebugFrameDrops =
+ ConditionalDebug<DebugDroppedFrames, "Ethernet driver">;
+
+ /**
+ * Import the Capability helper from the CHERI namespace.
+ */
+ template<typename T>
+ using Capability = CHERI::Capability<T>;
+
+ /**
+ * GPIO output pins to be used
+ */
+ enum class GpioPin : uint8_t
+ {
+ EthernetChipSelect = 13,
+ EthernetReset = 14,
+ };
+
+ /**
+ * SPI commands
+ */
+ enum class SpiCommand : uint8_t
+ {
+ ReadRegister = 0b00,
+ WriteRegister = 0b01,
+ // DMA in this context means that the Ethernet MAC is DMA directly
+ // from the SPI interface into its internal buffer, so it takes single
+ // SPI transaction for the entire frame. It is unrelated to whether
+ // SPI driver uses PIO or DMA for the SPI transaction.
+ ReadDma = 0b10,
+ WriteDma = 0b11,
+ };
+
+ /**
+ * The location of registers
+ */
+ enum class RegisterOffset : uint8_t
+ {
+ ChipConfiguration = 0x08,
+ MacAddressLow = 0x10,
+ MacAdressMiddle = 0x12,
+ MacAddressHigh = 0x14,
+ OnChipBusControl = 0x20,
+ EepromControl = 0x22,
+ MemoryBistInfo = 0x24,
+ GlobalReset = 0x26,
+
+ /* Wakeup frame registers omitted */
+
+ TransmitControl = 0x70,
+ TransmitStatus = 0x72,
+ ReceiveControl1 = 0x74,
+ ReceiveControl2 = 0x76,
+ TransmitQueueMemoryInfo = 0x78,
+ ReceiveFrameHeaderStatus = 0x7C,
+ ReceiveFrameHeaderByteCount = 0x7E,
+ TransmitQueueCommand = 0x80,
+ ReceiveQueueCommand = 0x82,
+ TransmitFrameDataPointer = 0x84,
+ ReceiveFrameDataPointer = 0x86,
+ ReceiveDurationTimerThreshold = 0x8C,
+ ReceiveDataByteCountThreshold = 0x8E,
+ InterruptEnable = 0x90,
+ InterruptStatus = 0x92,
+ ReceiveFrameCountThreshold = 0x9c,
+ TransmitNextTotalFrameSize = 0x9E,
+
+ /* MAC address hash table registers omitted */
+
+ FlowControlLowWatermark = 0xB0,
+ FlowControlHighWatermark = 0xB2,
+ FlowControlOverrunWatermark = 0xB4,
+ ChipIdEnable = 0xC0,
+ ChipGlobalControl = 0xC6,
+ IndirectAccessControl = 0xC8,
+ IndirectAccessDataLow = 0xD0,
+ IndirectAccessDataHigh = 0xD2,
+ PowerManagementEventControl = 0xD4,
+ GoSleepWakeUp = 0xD4,
+ PhyReset = 0xD4,
+ Phy1MiiBasicControl = 0xE4,
+ Phy1MiiBasicStatus = 0xE6,
+ Phy1IdLow = 0xE8,
+ Phy1High = 0xEA,
+ Phy1AutoNegotiationAdvertisement = 0xEC,
+ Phy1AutoNegotiationLinkPartnerAbility = 0xEE,
+ Phy1SpecialControlStatus = 0xF4,
+ Port1Control = 0xF6,
+ Port1Status = 0xF8,
+ };
+
+ using MACAddress = std::array<uint8_t, 6>;
+
+ /**
+ * Flag bits of the TransmitControl register.
+ */
+ enum [[clang::flag_enum]] TransmitControl : uint16_t{
+ TransmitEnable = 1 << 0,
+ TransmitCrcEnable = 1 << 1,
+ TransmitPaddingEnable = 1 << 2,
+ TransmitFlowControlEnable = 1 << 3,
+ FlushTransmitQueue = 1 << 4,
+ TransmitChecksumGenerationIp = 1 << 5,
+ TransmitChecksumGenerationTcp = 1 << 6,
+ TransmitChecksumGenerationIcmp = 1 << 9,
+ };
+
+ /**
+ * Flag bits of the ReceiveControl1 register.
+ */
+ enum [[clang::flag_enum]] ReceiveControl1 : uint16_t{
+ ReceiveEnable = 1 << 0,
+ ReceiveInverseFilter = 1 << 1,
+ ReceiveAllEnable = 1 << 4,
+ ReceiveUnicastEnable = 1 << 5,
+ ReceiveMulticastEnable = 1 << 6,
+ ReceiveBroadcastEnable = 1 << 7,
+ ReceiveMulticastAddressFilteringWithMacAddressEnable = 1 << 8,
+ ReceiveErrorFrameEnable = 1 << 9,
+ ReceiveFlowControlEnable = 1 << 10,
+ ReceivePhysicalAddressFilteringWithMacAddressEnable = 1 << 11,
+ ReceiveIpFrameChecksumCheckEnable = 1 << 12,
+ ReceiveTcpFrameChecksumCheckEnable = 1 << 13,
+ ReceiveUdpFrameChecksumCheckEnable = 1 << 14,
+ FlushReceiveQueue = 1 << 15,
+ };
+
+ /**
+ * Flag bits of the ReceiveControl2 register.
+ */
+ enum [[clang::flag_enum]] ReceiveControl2 : uint16_t{
+ ReceiveSourceAddressFiltering = 1 << 0,
+ ReceiveIcmpFrameChecksumEnable = 1 << 1,
+ UdpLiteFrameEnable = 1 << 2,
+ ReceiveIpv4Ipv6UdpFrameChecksumEqualZero = 1 << 3,
+ ReceiveIpv4Ipv6FragmentFramePass = 1 << 4,
+ DataBurst4Bytes = 0b000 << 5,
+ DataBurst8Bytes = 0b001 << 5,
+ DataBurst16Bytes = 0b010 << 5,
+ DataBurst32Bytes = 0b011 << 5,
+ DataBurstSingleFrame = 0b100 << 5,
+ };
+
+ /**
+ * Flag bits of the ReceiveFrameHeaderStatus register.
+ */
+ enum [[clang::flag_enum]] ReceiveFrameHeaderStatus : uint16_t{
+ ReceiveCrcError = 1 << 0,
+ ReceiveRuntFrame = 1 << 1,
+ ReceiveFrameTooLong = 1 << 2,
+ ReceiveFrameType = 1 << 3,
+ ReceiveMiiError = 1 << 4,
+ ReceiveUnicastFrame = 1 << 5,
+ ReceiveMulticastFrame = 1 << 6,
+ ReceiveBroadcastFrame = 1 << 7,
+ ReceiveUdpFrameChecksumStatus = 1 << 10,
+ ReceiveTcpFrameChecksumStatus = 1 << 11,
+ ReceiveIpFrameChecksumStatus = 1 << 12,
+ ReceiveIcmpFrameChecksumStatus = 1 << 13,
+ ReceiveFrameValid = 1 << 15,
+ };
+
+ /**
+ * Flag bits of the ReceiveQueueCommand register.
+ */
+ enum [[clang::flag_enum]] ReceiveQueueCommand : uint16_t{
+ ReleaseReceiveErrorFrame = 1 << 0,
+ StartDmaAccess = 1 << 3,
+ AutoDequeueReceiveQueueFrameEnable = 1 << 4,
+ ReceiveFrameCountThresholdEnable = 1 << 5,
+ ReceiveDataByteCountThresholdEnable = 1 << 6,
+ ReceiveDurationTimerThresholdEnable = 1 << 7,
+ ReceiveIpHeaderTwoByteOffsetEnable = 1 << 9,
+ ReceiveFrameCountThresholdStatus = 1 << 10,
+ ReceiveDataByteCountThresholdstatus = 1 << 11,
+ ReceiveDurationTimerThresholdStatus = 1 << 12,
+ };
+
+ /**
+ * Flag bits of the TransmitQueueCommand register.
+ */
+ enum [[clang::flag_enum]] TransmitQueueCommand : uint16_t{
+ ManualEnqueueTransmitQueueFrameEnable = 1 << 0,
+ TransmitQueueMemoryAvailableMonitor = 1 << 1,
+ AutoEnqueueTransmitQueueFrameEnable = 1 << 2,
+ };
+
+ /**
+ * Flag bits of the TransmitFrameDataPointer and ReceiveFrameDataPointer
+ * register.
+ */
+ enum [[clang::flag_enum]] FrameDataPointer : uint16_t{
+ /**
+ * When this bit is set, the frame data pointer register increments
+ * automatically on accesses to the data register.
+ */
+ FrameDataPointerAutoIncrement = 1 << 14,
+ };
+
+ /**
+ * Flags bits of the InterruptStatus and InterruptEnable registers.
+ */
+ enum [[clang::flag_enum]] Interrupt : uint16_t{
+ EnergyDetectInterrupt = 1 << 2,
+ LinkupDetectInterrupt = 1 << 3,
+ ReceiveMagicPacketDetectInterrupt = 1 << 4,
+ ReceiveWakeupFrameDetectInterrupt = 1 << 5,
+ TransmitSpaceAvailableInterrupt = 1 << 6,
+ ReceiveProcessStoppedInterrupt = 1 << 7,
+ TransmitProcessStoppedInterrupt = 1 << 8,
+ ReceiveOverrunInterrupt = 1 << 11,
+ ReceiveInterrupt = 1 << 13,
+ TransmitInterrupt = 1 << 14,
+ LinkChangeInterruptStatus = 1 << 15,
+ };
+
+ /**
+ * Flags bits of the Port1Control register.
+ */
+ enum [[clang::flag_enum]] Port1Control : uint16_t{
+ Advertised10BTHalfDuplexCapability = 1 << 0,
+ Advertised10BTFullDuplexCapability = 1 << 1,
+ Advertised100BTHalfDuplexCapability = 1 << 2,
+ Advertised100BTFullDuplexCapability = 1 << 3,
+ AdvertisedFlowControlCapability = 1 << 4,
+ ForceDuplex = 1 << 5,
+ ForceSpeed = 1 << 6,
+ AutoNegotiationEnable = 1 << 7,
+ ForceMDIX = 1 << 9,
+ DisableAutoMDIMDIX = 1 << 10,
+ RestartAutoNegotiation = 1 << 13,
+ TransmitterDisable = 1 << 14,
+ LedOff = 1 << 15,
+ };
+
+ /**
+ * Flags bits of the Port1Status register.
+ */
+ enum [[clang::flag_enum]] Port1Status : uint16_t{
+ Partner10BTHalfDuplexCapability = 1 << 0,
+ Partner10BTFullDuplexCapability = 1 << 1,
+ Partner100BTHalfDuplexCapability = 1 << 2,
+ Partner100BTFullDuplexCapability = 1 << 3,
+ PartnerFlowControlCapability = 1 << 4,
+ LinkGood = 1 << 5,
+ AutoNegotiationDone = 1 << 6,
+ MDIXStatus = 1 << 7,
+ OperationDuplex = 1 << 9,
+ OperationSpeed = 1 << 10,
+ PolarityReverse = 1 << 13,
+ HPMDIX = 1 << 15,
+ };
+
+ /**
+ * The futex used to wait for interrupts when packets are available to
+ * receive.
+ */
+ const uint32_t *receiveInterruptFutex;
+
+ /**
+ * Set value of a GPIO output.
+ */
+ inline void set_gpio_output_bit(GpioPin pin, bool value) const
+ {
+ uint32_t shift = static_cast<uint8_t>(pin);
+ uint32_t output = gpio()->output;
+ output &= ~(1 << shift);
+ output |= value << shift;
+ gpio()->output = output;
+ }
+
+ /**
+ * Read a register from the KSZ8851.
+ */
+ [[nodiscard]] uint16_t register_read(RegisterOffset reg) const
+ {
+ // KSZ8851 command have the following format:
+ //
+ // First byte:
+ // +---------+-------------+-------------------+
+ // | 7 6 | 5 2 | 1 0 |
+ // +---------+-------------+-------------------+
+ // | Command | Byte Enable | Address (bit 7-6) |
+ // +---------+-------------+-------------------+
+ //
+ // Second byte (for register read/write only):
+ // +-------------------+--------+
+ // | 7 4 | 3 0 |
+ // +-------------------+--------+
+ // | Address (bit 5-2) | Unused |
+ // +-------------------+--------+
+ //
+ // Note that the access is 32-bit since bit 1 & 0 of the address is not
+ // included. KSZ8851 have 16-bit registers so byte enable is used to
+ // determine which register to access from the 32 bits specified by the
+ // address.
+ uint8_t addr = static_cast<uint8_t>(reg);
+ uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
+ uint8_t bytes[2];
+ bytes[0] = (static_cast<uint8_t>(SpiCommand::ReadRegister) << 6) |
+ (byteEnable << 2) | (addr >> 6);
+ bytes[1] = (addr << 2) & 0b11110000;
+
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->blocking_write(bytes, sizeof(bytes));
+ uint16_t val;
+ spi()->blocking_read(reinterpret_cast<uint8_t *>(&val), sizeof(val));
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ return val;
+ }
+
+ /**
+ * Write a register to KSZ8851.
+ */
+ void register_write(RegisterOffset reg, uint16_t val) const
+ {
+ // See register_read for command format.
+ uint8_t addr = static_cast<uint8_t>(reg);
+ uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
+ uint8_t bytes[2];
+ bytes[0] = (static_cast<uint8_t>(SpiCommand::WriteRegister) << 6) |
+ (byteEnable << 2) | (addr >> 6);
+ bytes[1] = (addr << 2) & 0b11110000;
+
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->blocking_write(bytes, sizeof(bytes));
+ spi()->blocking_write(reinterpret_cast<uint8_t *>(&val), sizeof(val));
+ spi()->wait_idle();
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+ }
+
+ /**
+ * Set bits in a KSZ8851 register.
+ */
+ void register_set(RegisterOffset reg, uint16_t mask) const
+ {
+ uint16_t old = register_read(reg);
+ register_write(reg, old | mask);
+ }
+
+ /**
+ * Clear bits in a KSZ8851 register.
+ */
+ void register_clear(RegisterOffset reg, uint16_t mask) const
+ {
+ uint16_t old = register_read(reg);
+ register_write(reg, old & ~mask);
+ }
+
+ /**
+ * Helper. Returns a pointer to the SPI device.
+ */
+ [[nodiscard, gnu::always_inline]] Capability<volatile SonataSpi> spi() const
+ {
+ return MMIO_CAPABILITY(SonataSpi, spi2);
+ }
+
+ /**
+ * Helper. Returns a pointer to the GPIO device.
+ */
+ [[nodiscard, gnu::always_inline]] Capability<volatile SonataGPIO>
+ gpio() const
+ {
+ return MMIO_CAPABILITY(SonataGPIO, gpio);
+ }
+
+ /**
+ * Number of frames yet to be received since last interrupt acknowledgement.
+ */
+ uint16_t framesToProcess = 0;
+
+ /**
+ * Mutex protecting transmitBuffer if send_frame is reentered.
+ */
+ RecursiveMutex transmitBufferMutex;
+
+ /**
+ * Buffer used by send_frame.
+ */
+ std::unique_ptr<uint8_t[]> transmitBuffer;
+
+ /**
+ * Mutex protecting receiveBuffer if receive_frame is called before a
+ * previous returned frame is dropped.
+ */
+ RecursiveMutex receiveBufferMutex;
+
+ /**
+ * Reads and writes of the GPIO space use the same bits of the MMIO region
+ * and so need to be protected.
+ */
+ FlagLockPriorityInherited gpioLock;
+
+ /**
+ * Buffer used by receive_frame.
+ */
+ std::unique_ptr<uint8_t[]> receiveBuffer;
+
+ public:
+ /**
+ * Initialise a reference to the Ethernet device.
+ */
+ Ksz8851Ethernet()
+ {
+ transmitBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
+ receiveBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
+
+ // Reset chip. It needs to be hold in reset for at least 10ms.
+ set_gpio_output_bit(GpioPin::EthernetReset, false);
+ thread_millisecond_wait(20);
+ set_gpio_output_bit(GpioPin::EthernetReset, true);
+
+ uint16_t chipId = register_read(RegisterOffset::ChipIdEnable);
+ Debug::log("Chip ID is {}", chipId);
+
+ // Check the chip ID. The last nibble is revision ID and can be ignored.
+ Debug::Assert((chipId & 0xFFF0) == 0x8870, "Unexpected Chip ID");
+
+ // This is the initialisation sequence suggested by the programmer's
+ // guide.
+ register_write(RegisterOffset::TransmitFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ register_write(RegisterOffset::TransmitControl,
+ TransmitControl::TransmitCrcEnable |
+ TransmitControl::TransmitPaddingEnable |
+ TransmitControl::TransmitFlowControlEnable |
+ TransmitControl::TransmitChecksumGenerationIp |
+ TransmitControl::TransmitChecksumGenerationTcp |
+ TransmitControl::TransmitChecksumGenerationIcmp);
+ register_write(RegisterOffset::ReceiveFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ // Configure Receive Frame Threshold for one frame.
+ register_write(RegisterOffset::ReceiveFrameCountThreshold, 0x0001);
+ register_write(RegisterOffset::ReceiveControl1,
+ ReceiveControl1::ReceiveUnicastEnable |
+ ReceiveControl1::ReceiveMulticastEnable |
+ ReceiveControl1::ReceiveBroadcastEnable |
+ ReceiveControl1::ReceiveFlowControlEnable |
+ ReceiveControl1::
+ ReceivePhysicalAddressFilteringWithMacAddressEnable |
+ ReceiveControl1::ReceiveIpFrameChecksumCheckEnable |
+ ReceiveControl1::ReceiveTcpFrameChecksumCheckEnable |
+ ReceiveControl1::ReceiveUdpFrameChecksumCheckEnable);
+ // The frame data burst field in this register controls how many data
+ // from a frame is read per DMA operation. The programmer's guide has a
+ // 4 byte burst, but to reduce SPI transactions and improve performance
+ // we choose to use single-frame data burst which reads the entire
+ // Ethernet frame in a single SPI DMA.
+ register_write(
+ RegisterOffset::ReceiveControl2,
+ ReceiveControl2::UdpLiteFrameEnable |
+ ReceiveControl2::ReceiveIpv4Ipv6UdpFrameChecksumEqualZero |
+ ReceiveControl2::ReceiveIpv4Ipv6FragmentFramePass |
+ ReceiveControl2::DataBurstSingleFrame);
+ register_write(
+ RegisterOffset::ReceiveQueueCommand,
+ ReceiveQueueCommand::ReceiveFrameCountThresholdEnable |
+ ReceiveQueueCommand::AutoDequeueReceiveQueueFrameEnable);
+
+ // Programmer's guide have a step to set the chip in half-duplex when
+ // negotiation failed, but we omit the step since non-switching hubs and
+ // half-duplex Ethernet is rarely used these days.
+
+ register_set(RegisterOffset::Port1Control,
+ Port1Control::RestartAutoNegotiation);
+
+ // Configure Low Watermark to 6KByte available buffer space out of
+ // 12KByte (unit is 4 bytes).
+ register_write(RegisterOffset::FlowControlLowWatermark, 0x0600);
+ // Configure High Watermark to 4KByte available buffer space out of
+ // 12KByte (unit is 4 bytes).
+ register_write(RegisterOffset::FlowControlHighWatermark, 0x0400);
+
+ // Clear the interrupt status
+ register_write(RegisterOffset::InterruptStatus, 0xFFFF);
+ receiveInterruptFutex =
+ interrupt_futex_get(STATIC_SEALED_VALUE(EthernetInterruptCapability));
+ // Enable Receive interrupt
+ register_write(RegisterOffset::InterruptEnable, ReceiveInterrupt);
+
+ // Enable QMU Transmit.
+ register_set(RegisterOffset::TransmitControl,
+ TransmitControl::TransmitEnable);
+ // Enable QMU Receive.
+ register_set(RegisterOffset::ReceiveControl1,
+ ReceiveControl1::ReceiveEnable);
+ }
+
+ Ksz8851Ethernet(const Ksz8851Ethernet &) = delete;
+ Ksz8851Ethernet(Ksz8851Ethernet &&) = delete;
+
+ /**
+ * This device does not have a unique MAC address and so users must provide
+ * a locally administered MAC address if more than one device is present on
+ * the same network.
+ */
+ static constexpr bool has_unique_mac_address()
+ {
+ return false;
+ }
+
+ static constexpr MACAddress mac_address_default()
+ {
+ return {0x3a, 0x30, 0x25, 0x24, 0xfe, 0x7a};
+ }
+
+ void mac_address_set(MACAddress address = mac_address_default())
+ {
+ register_write(RegisterOffset::MacAddressHigh,
+ (address[0] << 8) | address[1]);
+ register_write(RegisterOffset::MacAdressMiddle,
+ (address[2] << 8) | address[3]);
+ register_write(RegisterOffset::MacAddressLow,
+ (address[4] << 8) | address[5]);
+ }
+
+ uint32_t receive_interrupt_value()
+ {
+ return *receiveInterruptFutex;
+ }
+
+ int receive_interrupt_complete(Timeout *timeout,
+ uint32_t lastInterruptValue)
+ {
+ // If there are frames to process, do not enter wait.
+ if (framesToProcess)
+ {
+ return 0;
+ }
+
+ // Our interrupt is level-triggered; if a frame happens to arrive
+ // between `receive_frame` call and we marking interrupt as received,
+ // it will trigger again immediately after we acknowledge it.
+
+ // Acknowledge the interrupt in the scheduler.
+ interrupt_complete(STATIC_SEALED_VALUE(EthernetInterruptCapability));
+ if (*receiveInterruptFutex == lastInterruptValue)
+ {
+ Debug::log("Acknowledged interrupt, sleeping on futex {}",
+ receiveInterruptFutex);
+ return futex_timed_wait(
+ timeout, receiveInterruptFutex, lastInterruptValue);
+ }
+ Debug::log("Scheduler announces interrupt has fired");
+ return 0;
+ }
+
+ /**
+ * Simple class representing a received Ethernet frame.
+ */
+ class Frame
+ {
+ public:
+ uint16_t length;
+ Capability<uint8_t> buffer;
+
+ private:
+ friend class Ksz8851Ethernet;
+ LockGuard<RecursiveMutex> guard;
+
+ Frame(LockGuard<RecursiveMutex> &&guard,
+ Capability<uint8_t> buffer,
+ uint16_t length)
+ : guard(std::move(guard)), buffer(buffer), length(length)
+ {
+ }
+ };
+
+ /**
+ * Check the link status of the PHY.
+ */
+ bool phy_link_status()
+ {
+ uint16_t status = register_read(RegisterOffset::Port1Status);
+ return (status & Port1Status::LinkGood) != 0;
+ }
+
+ std::optional<Frame> receive_frame()
+ {
+ LockGuard g{gpioLock};
+ if (framesToProcess == 0)
+ {
+ uint16_t isr = register_read(RegisterOffset::InterruptStatus);
+ if (!(isr & ReceiveInterrupt))
+ {
+ return std::nullopt;
+ }
+
+ // Acknowledge the interrupt
+ register_write(RegisterOffset::InterruptStatus, ReceiveInterrupt);
+
+ // Read number of frames pending.
+ // Note that this is only updated when we acknowledge the interrupt.
+ framesToProcess =
+ register_read(RegisterOffset::ReceiveFrameCountThreshold) >> 8;
+ }
+
+ // Get number of frames pending
+ for (; framesToProcess; framesToProcess--)
+ {
+ uint16_t status =
+ register_read(RegisterOffset::ReceiveFrameHeaderStatus);
+ uint16_t length =
+ register_read(RegisterOffset::ReceiveFrameHeaderByteCount) &
+ 0xFFF;
+ bool valid =
+ (status & ReceiveFrameValid) &&
+ !(status &
+ (ReceiveCrcError | ReceiveRuntFrame | ReceiveFrameTooLong |
+ ReceiveMiiError | ReceiveUdpFrameChecksumStatus |
+ ReceiveTcpFrameChecksumStatus | ReceiveIpFrameChecksumStatus |
+ ReceiveIcmpFrameChecksumStatus));
+
+ if (!valid)
+ {
+ DebugFrameDrops::log("Dropping frame with status: {}", status);
+
+ drop_error_frame();
+ continue;
+ }
+
+ if (length == 0)
+ {
+ DebugFrameDrops::log("Dropping frame with zero length");
+
+ drop_error_frame();
+ continue;
+ }
+
+ // The DMA transfer to the Ethernet MAC must be a multiple of 4
+ // bytes.
+ uint16_t paddedLength = (length + 3) & ~0x3;
+ if (paddedLength > MaxFrameSize)
+ {
+ DebugFrameDrops::log("Dropping frame that is too large: {}",
+ length);
+
+ drop_error_frame();
+ continue;
+ }
+
+ Debug::log("Receiving frame of length {}", length);
+
+ LockGuard guard{receiveBufferMutex};
+
+ // Reset receive frame pointer to zero and start DMA transfer
+ // operation.
+ register_write(RegisterOffset::ReceiveFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Start receiving via SPI.
+ uint8_t cmd = static_cast<uint8_t>(SpiCommand::ReadDma) << 6;
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->blocking_write(&cmd, 1);
+
+ // Initial words are ReceiveFrameHeaderStatus and
+ // ReceiveFrameHeaderByteCount which we have already know the value.
+ uint8_t dummy[8];
+ spi()->blocking_read(dummy, sizeof(dummy));
+
+ spi()->blocking_read(receiveBuffer.get(), paddedLength);
+
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+
+ register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+ framesToProcess -= 1;
+
+ Capability<uint8_t> boundedBuffer{receiveBuffer.get()};
+ boundedBuffer.bounds().set_inexact(length);
+ // Remove all permissions except load. This also removes global, so
+ // that this cannot be captured.
+ boundedBuffer.permissions() &=
+ CHERI::PermissionSet{CHERI::Permission::Load};
+
+ return Frame{std::move(guard), boundedBuffer, length};
+ }
+
+ return std::nullopt;
+ }
+
+ /**
+ * Send a packet. This will block if no buffer space is available on
+ * device.
+ *
+ * The third argument is a callback that allows the caller to check the
+ * frame before it's sent but after it's copied into memory that isn't
+ * shared with other compartments.
+ */
+ bool send_frame(const uint8_t *buffer, uint16_t length, auto &&check)
+ {
+ // The DMA transfer to the Ethernet MAC must be a multiple of 4 bytes.
+ uint16_t paddedLength = (length + 3) & ~0x3;
+ if (paddedLength > MaxFrameSize)
+ {
+ Debug::log("Frame size {} is larger than the maximum size", length);
+ return false;
+ }
+
+ LockGuard guard{transmitBufferMutex};
+
+ // We must check the frame pointer and its length. Although it
+ // is supplied by the firewall which is trusted, the firewall
+ // does not check the pointer which is coming from external
+ // untrusted components.
+ Timeout t{10};
+ if ((heap_claim_ephemeral(&t, buffer) < 0) ||
+ (!CHERI::check_pointer<CHERI::PermissionSet{
+ CHERI::Permission::Load}>(buffer, length)))
+ {
+ return false;
+ }
+
+ memcpy(transmitBuffer.get(), buffer, length);
+ if (!check(transmitBuffer.get(), length))
+ {
+ return false;
+ }
+
+ LockGuard g{gpioLock};
+
+ // Wait for the transmit buffer to be available on the device side.
+ // This needs to include the header.
+ while ((register_read(RegisterOffset::TransmitQueueMemoryInfo) &
+ 0xFFF) < length + 4)
+ {
+ }
+
+ Debug::log("Sending frame of length {}", length);
+
+ // Start DMA transfer operation.
+ register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Start sending via SPI.
+ uint8_t cmd = static_cast<uint8_t>(SpiCommand::WriteDma) << 6;
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, false);
+ spi()->blocking_write(&cmd, 1);
+
+ uint32_t header = static_cast<uint32_t>(length) << 16;
+ spi()->blocking_write(reinterpret_cast<uint8_t *>(&header),
+ sizeof(header));
+
+ spi()->blocking_write(transmitBuffer.get(), paddedLength);
+
+ spi()->wait_idle();
+ set_gpio_output_bit(GpioPin::EthernetChipSelect, true);
+
+ // Stop QMU DMA transfer operation.
+ register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Enqueue the frame for transmission.
+ register_set(
+ RegisterOffset::TransmitQueueCommand,
+ TransmitQueueCommand::ManualEnqueueTransmitQueueFrameEnable);
+
+ return true;
+ }
+
+ private:
+ void drop_error_frame()
+ {
+ register_set(RegisterOffset::ReceiveQueueCommand,
+ ReleaseReceiveErrorFrame);
+ // Wait for confirmation of frame release before attempting to process
+ // next frame.
+ while (register_read(RegisterOffset::ReceiveQueueCommand) &
+ ReleaseReceiveErrorFrame)
+ {
+ }
+ }
+};
+
+using EthernetDevice = Ksz8851Ethernet;
+
+static_assert(EthernetAdaptor<EthernetDevice>);
diff --git a/sdk/include/platform/sunburst/v0.2/platform-spi.hh b/sdk/include/platform/sunburst/v0.2/platform-spi.hh
new file mode 100644
index 0000000..fd8aa19
--- /dev/null
+++ b/sdk/include/platform/sunburst/v0.2/platform-spi.hh
@@ -0,0 +1,235 @@
+#pragma once
+#include <cdefs.h>
+#include <debug.hh>
+#include <stdint.h>
+
+/**
+ * A Simple Driver for the Sonata's SPI.
+ *
+ * Documentation source can be found at:
+ * https://github.com/lowRISC/sonata-system/blob/1a59633d2515d4fe186a07d53e49ff95c18d9bbf/doc/ip/spi.md
+ *
+ * Rendered documentation is served from:
+ * https://lowrisc.org/sonata-system/doc/ip/spi.html
+ */
+struct SonataSpi
+{
+ /**
+ * The Sonata SPI block doesn't currently have support for interrupts.
+ * The following registers are reserved for future use.
+ */
+ uint32_t interruptState;
+ uint32_t interruptEnable;
+ uint32_t interruptTest;
+ /**
+ * Configuration register. Controls how the SPI block transmits and
+ * receives data. This register can be modified only whilst the SPI block
+ * is idle.
+ */
+ uint32_t configuration;
+ /**
+ * Controls the operation of the SPI block. This register can
+ * be modified only whilst the SPI block is idle.
+ */
+ uint32_t control;
+ /// Status information about the SPI block
+ uint32_t status;
+ /**
+ * Writes to this begin an SPI operation.
+ * Writes are ignored when the SPI block is active.
+ */
+ uint32_t start;
+ /**
+ * Data from the receive FIFO. When read the data is popped from the FIFO.
+ * If the FIFO is empty data read is undefined.
+ */
+ uint32_t receiveFifo;
+ /**
+ * Bytes written here are pushed to the transmit FIFO. If the FIFO is full
+ * writes are ignored.
+ */
+ uint32_t transmitFifo;
+
+ /// Configuration Register Fields
+ enum : uint32_t
+ {
+ /**
+ * The length of a half period (i.e. positive edge to negative edge) of
+ * the SPI clock, measured in system clock cycles reduced by 1. For
+ * example, at a 50 MHz system clock, a value of 0 gives a 25 MHz SPI
+ * clock, a value of 1 gives a 12.5 MHz SPI clock, a value of 2 gives
+ * a 8.33 MHz SPI clock and so on.
+ */
+ ConfigurationHalfClockPeriodMask = 0xffu << 0,
+ /*
+ * When set the most significant bit (MSB) is the first bit sent and
+ * received with each byte
+ */
+ ConfigurationMSBFirst = 1u << 29,
+ /*
+ * The phase of the spi_clk signal. when clockphase is 0, data is
+ * sampled on the leading edge and changes on the trailing edge. The
+ * first data bit is immediately available before the first leading edge
+ * of the clock when transmission begins. When clockphase is 1, data is
+ * sampled on the trailing edge and change on the leading edge.
+ */
+ ConfigurationClockPhase = 1u << 30,
+ /*
+ * The polarity of the spi_clk signal. When ClockPolarity is 0, clock is
+ * low when idle and the leading edge is positive. When ClkPolarity is
+ * 1, clock is high when idle and the leading edge is negative
+ */
+ ConfigurationClockPolarity = 1u << 31,
+ };
+
+ /// Control Register Fields
+ enum : uint32_t
+ {
+ /// Write 1 to clear the transmit FIFO.
+ ControlTransmitClear = 1 << 0,
+ /// Write 1 to clear the receive FIFO.
+ ControlReceiveClear = 1 << 1,
+ /**
+ * When set bytes from the transmit FIFO are sent. When clear the state
+ * of the outgoing spi_cipo is undefined whilst the SPI clock is
+ * running.
+ */
+ ControlTransmitEnable = 1 << 2,
+ /**
+ * When set incoming bits are written to the receive FIFO. When clear
+ * incoming bits are ignored.
+ */
+ ControlReceiveEnable = 1 << 3,
+ /**
+ * The watermark level for the transmit FIFO, depending on the value
+ * the interrupt will trigger at different points
+ */
+ ControlTransmitWatermarkMask = 0xf << 4,
+ /**
+ * The watermark level for the receive FIFO, depending on the value the
+ * interrupt will trigger at different points
+ */
+ ControlReceiveWatermarkMask = 0xf << 8,
+ };
+
+ /// Status Register Fields
+ enum : uint32_t
+ {
+ /// Number of items in the transmit FIFO.
+ StatusTxFifoLevel = 0xffu << 0,
+ /// Number of items in the receive FIFO.
+ StatusRxFifoLevel = 0xffu << 8,
+ /**
+ * When set the transmit FIFO is full and any data written to it will
+ * be ignored.
+ */
+ StatusTxFifoFull = 1u << 16,
+ /**
+ * When set the receive FIFO is empty and any data read from it will be
+ * undefined.
+ */
+ StatusRxFifoEmpty = 1u << 17,
+ /// When set the SPI block is idle and can accept a new start command.
+ StatusIdle = 1u << 18,
+ };
+
+ /// Start Register Fields
+ enum : uint32_t
+ {
+ /// Number of bytes to receive/transmit in the SPI operation
+ StartByteCountMask = 0x7ffu,
+ };
+
+ /// Flag set when we're debugging this driver.
+ static constexpr bool DebugSonataSpi = false;
+
+ /// Helper for conditional debug logs and assertions.
+ using Debug = ConditionalDebug<DebugSonataSpi, "Sonata SPI">;
+
+ /**
+ * Initialises the SPI block
+ *
+ * @param ClockPolarity When false, the clock is low when idle and the
+ * leading edge is positive. When true, the opposite behaviour is
+ * set.
+ * @param ClockPhase When false, data is sampled on the leading edge and
+ * changes on the trailing edge. When true, the opposite behaviour is
+ * set.
+ * @param MsbFirst When true, the first bit of each byte sent is the most
+ * significant bit, as oppose to the least significant bit.
+ * @param HalfClockPeriod The length of a half period of the SPI clock,
+ * measured in system clock cycles reduced by 1.
+ */
+ void init(const bool ClockPolarity,
+ const bool ClockPhase,
+ const bool MsbFirst,
+ const uint16_t HalfClockPeriod) volatile
+ {
+ configuration = (ClockPolarity ? ConfigurationClockPolarity : 0) |
+ (ClockPhase ? ConfigurationClockPhase : 0) |
+ (MsbFirst ? ConfigurationMSBFirst : 0) |
+ (HalfClockPeriod & ConfigurationHalfClockPeriodMask);
+ }
+
+ /// Waits for the SPI device to become idle
+ void wait_idle() volatile
+ {
+ // Wait whilst IDLE field in STATUS is low
+ while ((status & StatusIdle) == 0) {}
+ }
+
+ /**
+ * Sends `len` bytes from the given `data` buffer,
+ * where `len` is at most `0x7ff`.
+ */
+ void blocking_write(const uint8_t data[], uint16_t len) volatile
+ {
+ Debug::Assert(len <= 0x7ff,
+ "You can't transfer more than 0x7ff bytes at a time.");
+ len &= StartByteCountMask;
+
+ wait_idle();
+ control = ControlTransmitEnable;
+ start = len;
+
+ uint32_t transmitAvailable = 0;
+ for (uint32_t i = 0; i < len; ++i)
+ {
+ if (transmitAvailable == 0)
+ {
+ while (transmitAvailable < 64)
+ {
+ // Read number of bytes in TX FIFO to calculate space
+ // available for more bytes
+ transmitAvailable = 64 - (status & StatusTxFifoLevel);
+ }
+ }
+ transmitFifo = data[i];
+ transmitAvailable--;
+ }
+ }
+
+ /*
+ * Receives `len` bytes and puts them in the `data` buffer,
+ * where `len` is at most `0x7ff`.
+ *
+ * This method will block until the requested number of bytes
+ * has been seen. There is currently no timeout.
+ */
+ void blocking_read(uint8_t data[], uint16_t len) volatile
+ {
+ Debug::Assert(len <= 0x7ff,
+ "You can't receive more than 0x7ff bytes at a time.");
+ len &= StartByteCountMask;
+ wait_idle();
+ control = ControlReceiveEnable;
+ start = len;
+
+ for (uint32_t i = 0; i < len; ++i)
+ {
+ // Wait for at least one byte to be available in the RX FIFO
+ while ((status & StatusRxFifoLevel) == 0) {}
+ data[i] = static_cast<uint8_t>(receiveFifo);
+ }
+ }
+};
diff --git a/sdk/include/simulator.h b/sdk/include/simulator.h
index 9de4a77..823faf2 100644
--- a/sdk/include/simulator.h
+++ b/sdk/include/simulator.h
@@ -3,14 +3,34 @@
#pragma once
#include <compartment.h>
+#include <errno.h>
#include <stdint.h>
#ifdef SIMULATION
/**
* Exit simulation, reporting the error code given as the argument.
*/
-[[cheri::interrupt_state(disabled)]] void __cheri_compartment("sched")
- simulation_exit(uint32_t code = 0);
-#else
-static inline void simulation_exit(uint32_t code){};
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
+ scheduler_simulation_exit(uint32_t code __if_cxx(= 0));
#endif
+
+/**
+ * Exit the simulation, if we can, or fall back to an infinite loop.
+ */
+static inline void __attribute__((noreturn))
+simulation_exit(uint32_t code __if_cxx(= 0))
+{
+#ifdef SIMULATION
+ /*
+ * This fails only if either we are out of (trusted) stack space for the
+ * cross-call or the platform is misconfigured. If either of those happen,
+ * fall back to infinite looping.
+ */
+ (void)scheduler_simulation_exit(code);
+#endif
+
+ while (true)
+ {
+ yield();
+ }
+};
diff --git a/sdk/include/stdio.h b/sdk/include/stdio.h
index 9448b5c..884924e 100644
--- a/sdk/include/stdio.h
+++ b/sdk/include/stdio.h
@@ -28,12 +28,16 @@
#elif DEVICE_EXISTS(uart)
# define stdout MMIO_CAPABILITY(void, uart)
# define stdin MMIO_CAPABILITY(void uart)
+#else
+#error No device found for stdout and stderr
#endif
-#if DEVICE_EXISTS(uart1)
+#if DEVICE_EXISTS(uart1) && !STDERR_TO_STDOUT
# define stderr MMIO_CAPABILITY(void, uart1)
#elif defined(stdout)
# define stderr stdout
+#else
+#error No device found for stderr
#endif
int __cheri_libcall vfprintf(FILE *stream, const char *fmt, va_list ap);
diff --git a/sdk/include/stdlib.h b/sdk/include/stdlib.h
index 54a9dc6..83a2da0 100644
--- a/sdk/include/stdlib.h
+++ b/sdk/include/stdlib.h
@@ -55,7 +55,7 @@
*/
#define DEFINE_ALLOCATOR_CAPABILITY(name, quota) \
DEFINE_STATIC_SEALED_VALUE(struct AllocatorCapabilityState, \
- alloc, \
+ allocator, \
MallocKey, \
name, \
(quota), \
@@ -113,32 +113,33 @@
}
}
-enum [[clang::flag_enum]] AllocateWaitFlags{
- /**
- * Non-blocking mode. This is equivalent to passing a timeout with no time
- * remaining.
- */
- AllocateWaitNone = 0,
- /**
- * If there is enough memory in the quarantine to fulfil the allocation, wait
- * for the revoker to free objects from the quarantine.
- */
- AllocateWaitRevocationNeeded = (1 << 0),
- /**
- * If the quota of the passed heap capability is exceeded, wait for other
- * threads to free allocations.
- */
- AllocateWaitQuotaExceeded = (1 << 1),
- /**
- * If the heap memory is exhausted, wait for any other thread of the system
- * to free allocations.
- */
- AllocateWaitHeapFull = (1 << 2),
- /**
- * Block on any of the above reasons. This is the default behavior.
- */
- AllocateWaitAny = (AllocateWaitRevocationNeeded | AllocateWaitQuotaExceeded |
- AllocateWaitHeapFull),
+enum [[clang::flag_enum]] AllocateWaitFlags
+{
+ /**
+ * Non-blocking mode. This is equivalent to passing a timeout with no time
+ * remaining.
+ */
+ AllocateWaitNone = 0,
+ /**
+ * If there is enough memory in the quarantine to fulfil the allocation,
+ * wait for the revoker to free objects from the quarantine.
+ */
+ AllocateWaitRevocationNeeded = (1 << 0),
+ /**
+ * If the quota of the passed heap capability is exceeded, wait for other
+ * threads to free allocations.
+ */
+ AllocateWaitQuotaExceeded = (1 << 1),
+ /**
+ * If the heap memory is exhausted, wait for any other thread of the system
+ * to free allocations.
+ */
+ AllocateWaitHeapFull = (1 << 2),
+ /**
+ * Block on any of the above reasons. This is the default behavior.
+ */
+ AllocateWaitAny = (AllocateWaitRevocationNeeded |
+ AllocateWaitQuotaExceeded | AllocateWaitHeapFull),
};
/**
@@ -175,7 +176,7 @@
*
* Memory returned from this interface is guaranteed to be zeroed.
*/
-void *__cheri_compartment("alloc")
+void *__cheri_compartment("allocator")
heap_allocate(Timeout *timeout,
struct SObjStruct *heapCapability,
size_t size,
@@ -197,7 +198,7 @@
*
* Memory returned from this interface is guaranteed to be zeroed.
*/
-void *__cheri_compartment("alloc")
+void *__cheri_compartment("allocator")
heap_allocate_array(Timeout *timeout,
struct SObjStruct *heapCapability,
size_t nmemb,
@@ -215,7 +216,7 @@
* `pointer` is not valid, etc.), or `-ENOTENOUGHSTACK` if the stack is
* insufficiently large to run the function.
*/
-ssize_t __cheri_compartment("alloc")
+ssize_t __cheri_compartment("allocator")
heap_claim(struct SObjStruct *heapCapability, void *pointer);
/**
@@ -236,9 +237,19 @@
* This function is provided by the compartment_helpers library, which must be
* linked for it to be available.
*/
-int __cheri_libcall heap_claim_fast(Timeout *timeout,
- const void *ptr,
- const void *ptr2 __if_cxx(= nullptr));
+int __cheri_libcall heap_claim_ephemeral(Timeout *timeout,
+ const void *ptr,
+ const void *ptr2 __if_cxx(= nullptr));
+
+__attribute__((deprecated("heap_claim_fast was a bad name. This function has "
+ "been renamed heap_claim_ephemeral")))
+__always_inline static int
+heap_claim_fast(Timeout *timeout,
+ const void *ptr,
+ const void *ptr2 __if_cxx(= nullptr))
+{
+ return heap_claim_ephemeral(timeout, ptr, ptr2);
+}
/**
* Free a heap allocation.
@@ -247,7 +258,7 @@
* of a live heap allocation, or `-ENOTENOUGHSTACK` if the stack size is
* insufficiently large to safely run the function.
*/
-int __cheri_compartment("alloc")
+int __cheri_compartment("allocator")
heap_free(struct SObjStruct *heapCapability, void *ptr);
/**
@@ -257,14 +268,14 @@
* capability, or `-ENOTENOUGHSTACK` if the stack size is insufficiently large
* to safely run the function.
*/
-ssize_t __cheri_compartment("alloc")
+ssize_t __cheri_compartment("allocator")
heap_free_all(struct SObjStruct *heapCapability);
/**
* Returns 0 if the allocation can be freed with the given capability, a
* negated errno value otherwise.
*/
-int __cheri_compartment("alloc")
+int __cheri_compartment("allocator")
heap_can_free(struct SObjStruct *heapCapability, void *ptr);
/**
@@ -272,7 +283,7 @@
* `heapCapability` is not valid or if the stack is insufficient to run the
* function.
*/
-ssize_t __cheri_compartment("alloc")
+ssize_t __cheri_compartment("allocator")
heap_quota_remaining(struct SObjStruct *heapCapability);
/**
@@ -281,8 +292,12 @@
* This should be used only in testing, to place the system in a quiesced
* state. It can block indefinitely if another thread is allocating and
* freeing memory while this runs.
+ *
+ * Returns 0 on success, a compartment invocation failure indication
+ * (-ENOTENOUGHSTACK, -ENOTENOUGHTRUSTEDSTACK) if it cannot be invoked, or
+ * possibly -ECOMPARTMENTFAIL if the allocator compartment is damaged.
*/
-void __cheri_compartment("alloc") heap_quarantine_empty(void);
+int __cheri_compartment("allocator") heap_quarantine_empty(void);
/**
* Returns true if `object` points to a valid heap address, false otherwise.
@@ -307,6 +322,15 @@
return (address >= heap_start) && (address < heap_end);
}
+/**
+ * Dump a textual rendering of the heap's structure to the debug console.
+ *
+ * If the RTOS is not built with --allocator-rendering=y, this is a no-op.
+ *
+ * Returns zero on success, non-zero on error (e.g. compartment call failure).
+ */
+int __cheri_compartment("allocator") heap_render();
+
static inline void __dead2 abort()
{
panic();
@@ -328,7 +352,7 @@
{
Timeout t = {0, MALLOC_WAIT_TICKS};
void *ptr = heap_allocate_array(
- &t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitRevocationNeeded);
+ &t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitRevocationNeeded);
if (!__builtin_cheri_tag_get(ptr))
{
ptr = NULL;
@@ -341,7 +365,7 @@
}
#endif
-size_t __cheri_compartment("alloc") heap_available(void);
+size_t __cheri_compartment("allocator") heap_available(void);
static inline void yield(void)
{
diff --git a/sdk/include/thread.h b/sdk/include/thread.h
index 2f156ce..dbc10f2 100644
--- a/sdk/include/thread.h
+++ b/sdk/include/thread.h
@@ -20,8 +20,8 @@
/// hi 32 bits
uint32_t hi;
} SystickReturn;
-[[cheri::interrupt_state(disabled)]] SystickReturn __cheri_compartment("sched")
- thread_systemtick_get(void);
+[[cheri::interrupt_state(disabled)]] SystickReturn
+ __cheri_compartment("scheduler") thread_systemtick_get(void);
enum ThreadSleepFlags : uint32_t
{
@@ -60,7 +60,7 @@
* If you are using `thread_sleep` to elapse real time, pass
* `ThreadSleepNoEarlyWake` as the flags argument to prevent early wakeups.
*/
-[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
+[[cheri::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
thread_sleep(struct Timeout *timeout, uint32_t flags __if_cxx(= 0));
/**
@@ -78,7 +78,7 @@
* This API is available only if the scheduler is built with accounting support
* enabled.
*/
-__cheri_compartment("sched") uint64_t thread_elapsed_cycles_idle(void);
+__cheri_compartment("scheduler") uint64_t thread_elapsed_cycles_idle(void);
/**
* Returns the number of cycles accounted to the current thread.
@@ -86,7 +86,7 @@
* This API is available only if the scheduler is built with accounting
* support enabled.
*/
-__cheri_compartment("sched") uint64_t thread_elapsed_cycles_current(void);
+__cheri_compartment("scheduler") uint64_t thread_elapsed_cycles_current(void);
/**
* Returns the number of threads, including threads that have exited.
@@ -97,7 +97,7 @@
*
* The result of this is safe to cache: it will never change over time.
*/
-__cheri_compartment("sched") uint16_t thread_count();
+__cheri_compartment("scheduler") uint16_t thread_count();
/**
* Wait for the specified number of microseconds. This is a busy-wait loop,
@@ -142,9 +142,9 @@
{
#ifdef SIMULATION
// In simulation builds, just yield once but don't bother trying to do
- // anything sensible with time.
+ // anything sensible with time. Ignore failures of attempts to sleep.
Timeout t = {0, 1};
- thread_sleep(&t, 0);
+ (void)thread_sleep(&t, 0);
return milliseconds;
#else
static const uint32_t CyclesPerMillisecond = CPU_TIMER_HZ / 1'000;
diff --git a/sdk/include/thread_pool.h b/sdk/include/thread_pool.h
index bbf7fbd..c8be4c2 100644
--- a/sdk/include/thread_pool.h
+++ b/sdk/include/thread_pool.h
@@ -45,10 +45,10 @@
thread_pool_async(ThreadPoolCallback fn, void *data);
/**
- * Run a thread pool. This does not return and can be used as a thread entry
- * point.
+ * Run a thread pool. This does not return, despite the claimed type, and can
+ * be used as a thread entry point.
*/
-void __cheri_compartment("thread_pool") thread_pool_run(void);
+int __cheri_compartment("thread_pool") thread_pool_run(void);
__END_DECLS
#ifdef __cplusplus
@@ -94,7 +94,14 @@
return;
}
(*fn)();
- token_obj_destroy(MALLOC_CAPABILITY, key, static_cast<SObj>(rawFn));
+ /*
+ * This fails only if the thread pool runner compartment can't make
+ * cross-compartment calls to the allocator at all, since we're
+ * in its initial trusted activation frame and near the beginning
+ * (highest address) of its stack.
+ */
+ (void)token_obj_destroy(
+ MALLOC_CAPABILITY, key, static_cast<SObj>(rawFn));
}
/**
@@ -131,15 +138,18 @@
* Asynchronously invoke a lambda. This moves the lambda to the heap and
* passes it to the thread pool's queue. If the lambda copies any stack
* objects by reference then the copy will fail.
+ *
+ * Returns 0 on success, or compartment-call failures (ENOTENOUGHSTACK,
+ * ENOTENOUGHTRUSTEDSTACK) if the thread pool cannot be invoked.
*/
template<typename T>
- void async(T &&lambda)
+ int async(T &&lambda)
{
// If this is a stateless function, just send a callback function
// pointer, don't copy zero bytes of state to the heap.
if constexpr (std::is_convertible_v<T, void (*)(void)>)
{
- thread_pool_async(
+ return thread_pool_async(
&detail::wrap_callback_function<std::remove_cvref_t<T>>, nullptr);
}
else
@@ -153,21 +163,26 @@
// type.
Timeout t{UnlimitedTimeout};
void *sealed = token_sealed_unsealed_alloc(
- &t,
- MALLOC_CAPABILITY,
- detail::sealing_key_for_type<LambdaType>(),
- sizeof(lambda),
- &buffer);
- // Copy the lambda into the new allocation.
- // Note: We silence a warning here because we *do* want to
- // explicitly move, not forward.
+ &t,
+ MALLOC_CAPABILITY,
+ detail::sealing_key_for_type<LambdaType>(),
+ sizeof(lambda),
+ &buffer);
+ /*
+ * Copy the lambda into the new allocation.
+ *
+ * If the above allocation has failed, this will trap.
+ *
+ * Note: We silence a warning here because we *do* want to
+ * explicitly move, not forward.
+ */
T *copy = new (buffer) T(
std::move(lambda)); // NOLINT(bugprone-move-forwarding-reference)
// Create the wrapper that will unseal and invoke the lambda.
ThreadPoolCallback invoke =
&detail::wrap_callback_lambda<LambdaType>;
// Dispatch it.
- thread_pool_async(invoke, sealed);
+ return thread_pool_async(invoke, sealed);
}
}
} // namespace thread_pool
diff --git a/sdk/include/token.h b/sdk/include/token.h
index c25c04b..1bb7ec6 100644
--- a/sdk/include/token.h
+++ b/sdk/include/token.h
@@ -36,20 +36,23 @@
* If the sealing keys have been exhausted then this will return
* `INVALID_SKEY`. This API is guaranteed never to block.
*/
-SKey __cheri_compartment("alloc") token_key_new(void);
+SKey __cheri_compartment("allocator") token_key_new(void);
/**
* Allocate a new object with size `sz`.
*
* An unsealed pointer to the newly allocated object is returned in
* `*unsealed`, the sealed pointer is returned as the return value.
+ * An invalid `unsealed` pointer does not constitute an error; the caller will
+ * still be given the sealed return value, assuming allocation was otherwise
+ * successful.
*
* The `key` parameter must have both the permit-seal and permit-unseal
* permissions.
*
* On error, this returns `INVALID_SOBJ`.
*/
-SObj __cheri_compartment("alloc")
+SObj __cheri_compartment("allocator")
token_sealed_unsealed_alloc(Timeout *timeout,
struct SObjStruct *heapCapability,
SKey key,
@@ -62,7 +65,7 @@
*
* The key must have the permit-seal permission.
*/
-SObj __cheri_compartment("alloc")
+SObj __cheri_compartment("allocator")
token_sealed_alloc(Timeout *timeout,
struct SObjStruct *heapCapability,
SKey,
@@ -121,7 +124,7 @@
* @return 0 if no errors. -EINVAL if key or obj not valid, or they don't
* match, or double destroy.
*/
-int __cheri_compartment("alloc")
+int __cheri_compartment("allocator")
token_obj_destroy(struct SObjStruct *heapCapability, SKey, SObj);
/**
@@ -131,7 +134,7 @@
* Returns 0 on success, `-EINVAL` if the key or object is not valid, or one of
* the errors from `heap_can_free` if the free would fail for other reasons.
*/
-int __cheri_compartment("alloc")
+int __cheri_compartment("allocator")
token_obj_can_destroy(SObj heapCapability, SKey key, SObj object);
__END_DECLS
@@ -175,19 +178,32 @@
{
return reinterpret_cast<T *>(sealedPointer);
}
+ /**
+ * Return the tag of the underlying pointer
+ */
+ bool is_valid()
+ {
+ return __builtin_cheri_tag_get(get());
+ }
};
/**
* Type-safe helper to allocate a sealed `T*`. Returns the sealed and unsealed
* pointers.
+ *
+ * Callers should check the sealed capability's tag to determine success.
*/
template<typename T>
__always_inline std::pair<T *, Sealed<T>>
token_allocate(Timeout *timeout, struct SObjStruct *heapCapability, SKey key)
{
- void *unsealed;
- SObj sealed = token_sealed_unsealed_alloc(
- timeout, heapCapability, key, sizeof(T), &unsealed);
+ /*
+ * Explicitly initialize unsealed, since callers like to check it, and not
+ * the sealed result, for validity.
+ */
+ void *unsealed = nullptr;
+ SObj sealed = token_sealed_unsealed_alloc(
+ timeout, heapCapability, key, sizeof(T), &unsealed);
return {static_cast<T *>(unsealed), Sealed<T>{sealed}};
}
diff --git a/sdk/include/utils.hh b/sdk/include/utils.hh
index 917fc40..704ede8 100644
--- a/sdk/include/utils.hh
+++ b/sdk/include/utils.hh
@@ -38,12 +38,12 @@
class NoCopyNoMove
{
public:
- NoCopyNoMove() = default;
- NoCopyNoMove(const NoCopyNoMove &) = delete;
+ NoCopyNoMove() = default;
+ NoCopyNoMove(const NoCopyNoMove &) = delete;
NoCopyNoMove &operator=(const NoCopyNoMove &) = delete;
NoCopyNoMove(NoCopyNoMove &&) = delete;
- NoCopyNoMove &operator=(NoCopyNoMove &&) = delete;
- ~NoCopyNoMove() = default;
+ NoCopyNoMove &operator=(NoCopyNoMove &&) = delete;
+ ~NoCopyNoMove() = default;
};
/**
diff --git a/sdk/lib/compartment_helpers/README.md b/sdk/lib/compartment_helpers/README.md
index 90857ae..6343379 100644
--- a/sdk/lib/compartment_helpers/README.md
+++ b/sdk/lib/compartment_helpers/README.md
@@ -3,5 +3,5 @@
This library includes functions that help securing compartment boundaries.
-- [`claim_fast.cc`] contains the `heap_claim_fast` function.
+- [`claim_fast.cc`] contains the `heap_claim_ephemeral` function.
- [`check_pointer.cc`] contains the `check_pointer` function.
diff --git a/sdk/lib/compartment_helpers/claim_fast.cc b/sdk/lib/compartment_helpers/claim_fast.cc
index bf87ae4..0c33205 100644
--- a/sdk/lib/compartment_helpers/claim_fast.cc
+++ b/sdk/lib/compartment_helpers/claim_fast.cc
@@ -7,12 +7,12 @@
#include <stdlib.h>
#include <switcher.h>
-int heap_claim_fast(Timeout *timeout, const void *ptr, const void *ptr2)
+int heap_claim_ephemeral(Timeout *timeout, const void *ptr, const void *ptr2)
{
void **hazards = switcher_thread_hazard_slots();
auto *epochCounter{const_cast<
cheriot::atomic<uint32_t> *>(SHARED_OBJECT_WITH_PERMISSIONS(
- cheriot::atomic<uint32_t>, allocator_epoch, true, false, false, false))};
+ cheriot::atomic<uint32_t>, allocator_epoch, true, false, false, false))};
uint32_t epoch = epochCounter->load();
int values = 2;
// Skip processing pointers that don't refer to heap memory.
@@ -43,10 +43,11 @@
if (timeout->may_block())
{
Timeout t{1};
- futex_timed_wait(&t,
- reinterpret_cast<uint32_t *>(epochCounter),
- epoch,
- FutexPriorityInheritance);
+ (void)futex_timed_wait(
+ &t,
+ reinterpret_cast<uint32_t *>(epochCounter),
+ epoch,
+ FutexPriorityInheritance);
timeout->elapse(t.elapsed);
}
else
diff --git a/sdk/lib/cxxrt/guard.cc b/sdk/lib/cxxrt/guard.cc
index a60f690..629a535 100644
--- a/sdk/lib/cxxrt/guard.cc
+++ b/sdk/lib/cxxrt/guard.cc
@@ -1,12 +1,14 @@
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
-#include <cassert>
#include <cdefs.h>
+#include <debug.hh>
#include <futex.h>
#include <limits>
#include <stdint.h>
+using Debug = ConditionalDebug<DEBUG_CXXRT, "cxxrt">;
+
/**
* The helper functions need to expose an unmangled name because the compiler
* inserts calls to them. Declare them using the asm label extension.
@@ -33,7 +35,7 @@
/// The high half (second on a little-endian system).
uint32_t high;
/// The bit used for the lock (the high bit on a little-endian system)
- static constexpr uint32_t LockBit = uint32_t(1) << 31;
+ static constexpr uint32_t LockBit = static_cast<uint32_t>(1) << 31;
public:
/**
@@ -54,6 +56,8 @@
/**
* Acquire the lock.
+ *
+ * This is safe only in IRQ-deferred context.
*/
void lock()
{
@@ -62,7 +66,7 @@
{
futex_wait(&high, LockBit);
}
- assert(high == 0);
+ Debug::Assert(high == 0, "Corrupt guard word at {}", this);
high = LockBit;
}
@@ -71,9 +75,12 @@
*/
void unlock()
{
- assert(high == LockBit);
- high = 0;
- futex_wake(&high, std::numeric_limits<uint32_t>::max());
+ Debug::Assert(high == LockBit, "Corrupt guard word at {}", this);
+ high = 0;
+ int res = futex_wake(&high, std::numeric_limits<uint32_t>::max());
+ Debug::Assert(res >= 0,
+ "futex_wake failed for guard {}; possible deadlock",
+ this);
}
/**
@@ -109,8 +116,8 @@
void __cxa_guard_release(uint64_t *guard)
{
auto *g = reinterpret_cast<GuardWord *>(guard);
- assert(!g->is_initialised());
- assert(g->is_locked());
+ Debug::Assert(!g->is_initialised(), "Releasing uninitialized guard {}", g);
+ Debug::Assert(g->is_locked(), "Releasing unlocked guard {}", g);
g->set_initialised();
g->unlock();
}
diff --git a/sdk/lib/debug/debug.cc b/sdk/lib/debug/debug.cc
index 2642d9f..b61eb61 100644
--- a/sdk/lib/debug/debug.cc
+++ b/sdk/lib/debug/debug.cc
@@ -113,7 +113,7 @@
}
std::array<char, 10> buf;
const char Digits[] = "0123456789";
- for (int i = int(buf.size() - 1); i >= 0; i--)
+ for (int i = static_cast<int>(buf.size() - 1); i >= 0; i--)
{
buf[static_cast<size_t>(i)] = Digits[s % 10];
s /= 10;
@@ -146,7 +146,7 @@
}
std::array<char, 20> buf;
const char Digits[] = "0123456789";
- for (int i = int(buf.size() - 1); i >= 0; i--)
+ for (int i = static_cast<int>(buf.size() - 1); i >= 0; i--)
{
buf[static_cast<size_t>(i)] = Digits[s % 10];
s /= 10;
@@ -168,6 +168,14 @@
}
/**
+ * Write a single byte with no prefix.
+ */
+ void write_hex_byte(uint8_t byte) override
+ {
+ append_hex_word(byte);
+ }
+
+ /**
* Write a 32-bit unsigned integer to the buffer as hex with no prefix.
*/
void append_hex_word(uint32_t s)
@@ -176,7 +184,7 @@
const char Hexdigits[] = "0123456789abcdef";
// Length of string including null terminator
static_assert(sizeof(Hexdigits) == 0x11);
- for (long i = long(buf.size() - 1); i >= 0; i--)
+ for (long i = static_cast<long>(buf.size() - 1); i >= 0; i--)
{
buf.at(static_cast<size_t>(i)) = Hexdigits[s & 0xf];
s >>= 4;
@@ -367,5 +375,5 @@
printer.write(function);
printer.write("\x1b[36m\n");
printer.format(format, arguments, argumentCount);
- printer.write("\n");
+ printer.write("\033[0m\n");
}
diff --git a/sdk/lib/event_group/event_group.cc b/sdk/lib/event_group/event_group.cc
index 04d0337..87f7ce7 100644
--- a/sdk/lib/event_group/event_group.cc
+++ b/sdk/lib/event_group/event_group.cc
@@ -38,7 +38,7 @@
EventGroup **outGroup)
{
auto threads = thread_count();
- if (threads == uint16_t(-1))
+ if (threads == static_cast<uint16_t>(-1))
{
return -ERANGE;
}
@@ -191,8 +191,7 @@
waiter.bitsSeen = bits;
waiter.bitsSeen.notify_one();
}
- heap_free(heapCapability, group);
- return 0;
+ return heap_free(heapCapability, group);
}
int eventgroup_destroy(SObjStruct *heapCapability, EventGroup *group)
diff --git a/sdk/lib/microvium/xmake.lua b/sdk/lib/microvium/xmake.lua
index 3b87d81..ed7b141 100644
--- a/sdk/lib/microvium/xmake.lua
+++ b/sdk/lib/microvium/xmake.lua
@@ -9,3 +9,4 @@
add_files("../../third_party/microvium/dist-c/microvium.c")
add_includedirs("../../include/microvium", ".")
add_defines("CHERIOT_NO_AMBIENT_MALLOC")
+ add_cflags("-Wno-constant-logical-operand")
diff --git a/sdk/lib/queue/queue_compartment.cc b/sdk/lib/queue/queue_compartment.cc
index 9aec00a..6ef2d50 100644
--- a/sdk/lib/queue/queue_compartment.cc
+++ b/sdk/lib/queue/queue_compartment.cc
@@ -90,18 +90,15 @@
{
if (token_obj_unseal(handle_key(), queueHandle) != nullptr)
{
- token_obj_destroy(heapCapability, handle_key(), queueHandle);
- return 0;
+ return token_obj_destroy(heapCapability, handle_key(), queueHandle);
}
if (token_obj_unseal(send_key(), queueHandle) != nullptr)
{
- token_obj_destroy(heapCapability, send_key(), queueHandle);
- return 0;
+ return token_obj_destroy(heapCapability, send_key(), queueHandle);
}
if (token_obj_unseal(receive_key(), queueHandle) != nullptr)
{
- token_obj_destroy(heapCapability, receive_key(), queueHandle);
- return 0;
+ return token_obj_destroy(heapCapability, receive_key(), queueHandle);
}
return -EINVAL;
}
@@ -186,7 +183,7 @@
}
auto [unsealed, sealed] = token_allocate<RestrictedEndpoint>(
timeout, heapCapability, receive_key());
- if (!unsealed)
+ if (!sealed.is_valid())
{
return -ENOMEM;
}
@@ -208,7 +205,7 @@
}
auto [unsealed, sealed] =
token_allocate<RestrictedEndpoint>(timeout, heapCapability, send_key());
- if (!unsealed)
+ if (!sealed.is_valid())
{
return -ENOMEM;
}
diff --git a/sdk/lib/thread_pool/thread_pool.cc b/sdk/lib/thread_pool/thread_pool.cc
index f58fcf9..5b8b305 100644
--- a/sdk/lib/thread_pool/thread_pool.cc
+++ b/sdk/lib/thread_pool/thread_pool.cc
@@ -47,7 +47,7 @@
return 0;
}
-void __cheri_compartment("thread_pool") thread_pool_run()
+int __cheri_compartment("thread_pool") thread_pool_run()
{
while (true)
{
diff --git a/sdk/xmake.lua b/sdk/xmake.lua
index 07a37c5..46d9032 100644
--- a/sdk/xmake.lua
+++ b/sdk/xmake.lua
@@ -25,6 +25,11 @@
set_description("Track per-thread cycle counts in the scheduler");
set_showmenu(true)
+option("allocator-rendering")
+ set_default(false)
+ set_description("Include heap_render() functionality in the allocator")
+ set_showmenu(true)
+
function debugOption(name)
option("debug-" .. name)
set_default(false)
@@ -227,8 +232,9 @@
add_deps("locks")
add_deps("compartment_helpers")
on_load(function (target)
- target:set("cheriot.compartment", "alloc")
+ target:set("cheriot.compartment", "allocator")
target:set('cheriot.debug-name', "allocator")
+ target:add('defines', "HEAP_RENDER=" .. tostring(get_config("allocator-rendering")))
end)
target("cheriot.token_library")
@@ -247,21 +253,143 @@
target:set("cheriot.ldscript", "software_revoker.ldscript")
end)
--- Helper to get the board file for a given target
-local board_file = function(target)
- local boardfile = target:values("board")
- if not boardfile then
- raise("target " .. target:name() .. " does not define a board name")
- end
+-- Helper to find a board file given either the name of a board file or a path.
+local function board_file_for_name(boardName)
+ local boardfile = boardName
-- The directory containing the board file.
local boarddir = path.directory(boardfile);
- if path.basename(boardfile) == boardfile then
+ -- If this isn't a path, look in the boards directory
+ if not os.isfile(boardfile) then
boarddir = path.join(scriptdir, "boards")
- boardfile = path.join(boarddir, boardfile .. '.json')
+ local fullBoardPath = path.join(boarddir, boardfile .. '.json')
+ if not os.isfile(fullBoardPath) then
+ fullBoardPath = path.join(boarddir, boardfile .. '.patch')
+ end
+ if not os.isfile(fullBoardPath) then
+ print("unable to find board file " .. boardfile .. ". Try specifying a full path")
+ return nil
+ end
+ boardfile = fullBoardPath
end
return boarddir, boardfile
end
+-- Helper to get the board file for a given target
+local function board_file_for_target(target)
+ local boardName = target:values("board")
+ if not boardName then
+ print("target " .. target:name() .. " does not define a board name")
+ return nil
+ end
+ return board_file_for_name(boardName)
+end
+
+-- Helper to load a board file. This must be passed the json object provided
+-- by import("core.base.json") because import does not work in helper
+-- functions at the top level.
+local function load_board_file(json, boardFile)
+ if path.extension(boardFile) == ".json" then
+ return json.loadfile(boardFile)
+ end
+ if path.extension(boardFile) ~= ".patch" then
+ print("unknown extension for board file: " .. boardFile)
+ return nil
+ end
+ local patch = json.loadfile(boardFile)
+ if not patch.base then
+ print("Board file " .. boardFile .. " does not specify a base")
+ return nil
+ end
+ local _, baseFile = board_file_for_name(patch.base)
+ local base = load_board_file(json, baseFile)
+
+ -- If a string value is a number, return it as number, otherwise return it
+ -- in its original form.
+ function asNumberIfNumber(value)
+ if tostring(tonumber(value)) == value then
+ return tonumber(value)
+ end
+ return value
+ end
+
+ -- Heuristic to tell a Lua table is probably an array in Lua
+ -- This is O(n), but n is usually very small, and this happens once per
+ -- build so this doesn't really matter.
+ function isarray(t)
+ local i = 1
+ for k, v in pairs(t) do
+ if k ~= i then
+ return false
+ end
+ i = i+1
+ end
+ return true
+ end
+
+ for _, p in ipairs(patch.patch) do
+ if not p.op then
+ print("missing op in "..json.encode(p))
+ return nil
+ end
+ if not p.path or (type(p.path) ~= "string") then
+ print("missing or invalid path in "..json.encode(p))
+ return nil
+ end
+
+ -- Parse the JSON Pointer into an array of filed names, converting
+ -- numbers into Lua numbers if we see them. This is not quite right,
+ -- because it doesn't handle field names with / in them, but we don't
+ -- currently use those for anything. It also assumes that we really do
+ -- mean array indexes when we say numbers. If we have an object with
+ -- "3" as the key and try to replace 3, it will currently not do the
+ -- right thing.
+ local objectPath = {}
+ for entry in string.gmatch(p.path, "/([^/]+)") do
+ table.insert(objectPath, asNumberIfNumber(entry))
+ end
+
+ if #objectPath < 1 then
+ print("invalid path in "..json.encode(p))
+ return nil
+ end
+
+ -- Last path object is the name of the key we're going to modify.
+ local nodeName = table.remove(objectPath)
+ -- Walk the path to find the object that we're going to modify.
+ local nodeToModify = base
+ for _, pathComponent in ipairs(objectPath) do
+ nodeToModify = nodeToModify[pathComponent]
+ end
+
+ -- JSON arrays are indexed from 0, Lua's are from 1. If someone says
+ -- array index 0, we need to map that to 1, and so on.
+ local isArrayOperation = false
+ if (type(nodeName) == "number") and isarray(nodeToModify) then
+ nodeName = nodeName + 1
+ isArrayOperation = true
+ end
+
+ -- Handle the operation
+ if (p.op == "replace") or (p.op == "add") then
+ if not p.value then
+ print(tostring(p.op).. " requires a value, missing in ", json.encode(p))
+ return nil
+ end
+ if isArrayOperation and p.op == "add" then
+ table.insert(nodeToModify, nodeName, p.value)
+ else
+ nodeToModify[nodeName] = p.value
+ end
+ elseif p.op == "remove" then
+ nodeToModify[nodeName] = nil
+ else
+ print(tostring(p.op) .. " is not a valid operation in ", json.encode(p))
+ return nil
+ end
+ end
+ return base
+end
+
-- Helper to visit all dependencies of a specified target exactly once and call
-- a callback.
local function visit_all_dependencies_of(target, callback)
@@ -278,34 +406,23 @@
visit(target)
end
-
-- Rule for defining a firmware image.
rule("firmware")
on_run(function (target)
import("core.base.json")
import("core.project.config")
- local boarddir, boardfile = board_file(target)
- local board = json.loadfile(boardfile)
- if not board.simulator then
+ local boarddir, boardfile = board_file_for_target(target)
+ local board = load_board_file(json, boardfile)
+ if (not board.run_command) and (not board.simulator) then
raise("board description " .. boardfile .. " does not define a run command")
end
- local simulator = board.simulator
+ local simulator = board.run_command or board.simulator
simulator = string.gsub(simulator, "${(%w*)}", { sdk=scriptdir, board=boarddir })
local firmware = target:targetfile()
local directory = path.directory(firmware)
firmware = path.filename(firmware)
local run = function(simulator)
local simargs = { firmware }
- if get_config("testing-model-output") then
- modeldir = path.join(scriptdir,
- "..",
- "scripts",
- "model_output",
- path.basename(target:values("board")),
- "examples")
- local modelout = path.join(modeldir, firmware .. ".txt")
- simargs[#simargs+1] = modelout
- end
os.execv(simulator, simargs, { curdir = directory })
end
-- Try executing the simulator from the sdk directory, if it's there.
@@ -328,14 +445,16 @@
-- This must be after load so that dependencies are resolved.
after_load(function (target)
import("core.base.json")
+ import("core.project.config")
local function visit_all_dependencies(callback)
visit_all_dependencies_of(target, callback)
end
- local boarddir, boardfile = board_file(target);
- local board = json.loadfile(boardfile)
-
+ local boarddir, boardfile = board_file_for_target(target);
+ local board = load_board_file(json, boardfile)
+ print("Board file saved as ", target:targetfile()..".board.json")
+ json.savefile(target:targetfile()..".board.json", board)
-- Add defines to all dependencies.
local add_defines = function (defines)
@@ -344,6 +463,13 @@
end)
end
+ -- Add cxflags to all dependencies.
+ local add_cxflags = function (cxflags)
+ visit_all_dependencies(function (target)
+ target:add('cxflags', cxflags, {force = true})
+ end)
+ end
+
local software_revoker = false
if board.revoker then
local temporal_defines = { "TEMPORAL_SAFETY" }
@@ -388,6 +514,11 @@
add_defines(board.defines)
end
+ -- If this board defines any cxflags, add them to all targets
+ if board.cxflags then
+ add_cxflags(board.cxflags)
+ end
+
add_defines("CPU_TIMER_HZ=" .. math.floor(board.timer_hz))
add_defines("TICK_RATE_HZ=" .. math.floor(board.tickrate_hz))
@@ -428,7 +559,22 @@
mmio = format("__mmio_region_start = 0x%x;\n%s__mmio_region_end = 0x%x;\n__export_mem_heap_end = 0x%x;\n",
mmio_start, mmio, mmio_end, board.heap["end"])
- local code_start = format("0x%x", board.instruction_memory.start)
+ local code_start = format("0x%x", board.instruction_memory.start);
+ -- Put the data either at the specified address if given, or directly after code
+ local data_start = board.data_memory and format("0x%x", board.data_memory.start) or '.';
+ local rwdata_ldscript = path.join(config.buildir(), target:name() .. "-firmware.rwdata.ldscript")
+ local rocode_ldscript = path.join(config.buildir(), target:name() .. "-firmware.rocode.ldscript")
+ if not board.data_memory or (board.instruction_memory.start < board.data_memory.start) then
+ -- If we're not explicilty given a data address or it's lower than the code address
+ -- then code needs to go first in the linker script.
+ firmware_low_ldscript = rocode_ldscript
+ firmware_high_ldscript = rwdata_ldscript
+ else
+ -- Otherwise the data is at a lower address than code (e.g. Sonata with SRAM and hyperram)
+ -- so it needs to go first.
+ firmware_low_ldscript = rwdata_ldscript;
+ firmware_high_ldscript = rocode_ldscript;
+ end
-- Set the start of memory that can be revoked.
-- By default, this is the start of code memory but it can be
@@ -622,8 +768,11 @@
software_revoker_header="",
sealed_objects="",
mmio=mmio,
+ data_start=data_start,
code_start=code_start,
heap_start=heap_start,
+ firmware_low_ldscript=firmware_low_ldscript,
+ firmware_high_ldscript=firmware_high_ldscript,
thread_count=#(threads),
thread_headers=thread_headers,
thread_trusted_stacks=thread_trusted_stacks,
@@ -754,7 +903,9 @@
import("core.project.config")
-- Get a specified linker script, or set the default to the compartment
-- linker script.
- local linkerscript = path.join(config.buildir(), target:name() .. "-firmware.ldscript")
+ local linkerscript1 = path.join(config.buildir(), target:name() .. "-firmware.ldscript")
+ local linkerscript2 = path.join(config.buildir(), target:name() .. "-firmware.rocode.ldscript")
+ local linkerscript3 = path.join(config.buildir(), target:name() .. "-firmware.rwdata.ldscript")
-- Link using the firmware's linker script.
batchcmds:show_progress(opt.progress, "linking firmware " .. target:targetfile())
batchcmds:mkdir(target:targetdir())
@@ -767,11 +918,11 @@
table.insert(objects, dep:targetfile())
end
end)
- batchcmds:vrunv(target:tool("ld"), table.join({"-n", "--script=" .. linkerscript, "--relax", "-o", target:targetfile(), "--compartment-report=" .. target:targetfile() .. ".json" }, objects), opt)
+ batchcmds:vrunv(target:tool("ld"), table.join({"-n", "--script=" .. linkerscript1, "--relax", "-o", target:targetfile(), "--compartment-report=" .. target:targetfile() .. ".json" }, objects), opt)
batchcmds:show_progress(opt.progress, "Creating firmware report " .. target:targetfile() .. ".json")
batchcmds:show_progress(opt.progress, "Creating firmware dump " .. target:targetfile() .. ".dump")
batchcmds:vexecv(target:tool("objdump"), {"-glxsdrS", "--demangle", target:targetfile()}, table.join(opt, {stdout = target:targetfile() .. ".dump"}))
- batchcmds:add_depfiles(linkerscript)
+ batchcmds:add_depfiles(linkerscript1, linkerscript2, linkerscript3)
batchcmds:add_depfiles(objects)
end)
@@ -823,7 +974,7 @@
add_deps("locks", "crt", "atomic1")
add_deps("compartment_helpers")
on_load(function (target)
- target:set("cheriot.compartment", "sched")
+ target:set("cheriot.compartment", "scheduler")
target:set('cheriot.debug-name', "scheduler")
target:add('defines', "SCHEDULER_ACCOUNTING=" .. tostring(get_config("scheduler-accounting")))
end)
@@ -840,6 +991,8 @@
-- The firmware linker script will be populated based on the set of
-- compartments.
add_configfiles(path.join(scriptdir, "firmware.ldscript.in"), {pattern = "@(.-)@", filename = name .. "-firmware.ldscript"})
+ add_configfiles(path.join(scriptdir, "firmware.rocode.ldscript.in"), {pattern = "@(.-)@", filename = name .. "-firmware.rocode.ldscript"})
+ add_configfiles(path.join(scriptdir, "firmware.rwdata.ldscript.in"), {pattern = "@(.-)@", filename = name .. "-firmware.rwdata.ldscript"})
end
-- Helper to create a library.
diff --git a/tests.extra/hardware_revoker_IRQs/README.md b/tests.extra/hardware_revoker_IRQs/README.md
new file mode 100644
index 0000000..bc8925c
--- /dev/null
+++ b/tests.extra/hardware_revoker_IRQs/README.md
@@ -0,0 +1 @@
+This is a minimal "the clock is ticking" test for a hardware revoker.
diff --git a/tests.extra/hardware_revoker_IRQs/top.cc b/tests.extra/hardware_revoker_IRQs/top.cc
new file mode 100644
index 0000000..7347934
--- /dev/null
+++ b/tests.extra/hardware_revoker_IRQs/top.cc
@@ -0,0 +1,71 @@
+#include <debug.hh>
+#include <fail-simulator-on-error.h>
+
+using Debug = ConditionalDebug<true, "top">;
+
+#if __has_include(<platform-hardware_revoker.hh>)
+# include <platform-hardware_revoker.hh>
+
+using Revoker = HardwareRevoker<uint32_t, REVOKABLE_MEMORY_START>;
+
+#elif defined(CLANG_TIDY)
+
+struct Revoker
+{
+ static constexpr bool IsAsynchronous = true;
+
+ uint32_t system_epoch_get()
+ {
+ return 0;
+ }
+ int wait_for_completion(Timeout *, uint32_t)
+ {
+ return 0;
+ }
+ void system_bg_revoker_kick() {}
+ void init() {}
+};
+
+#else
+# error No platform-hardware_revoker.hh found, are you building for the right platform?
+#endif
+
+static_assert(Revoker::IsAsynchronous, "This test is for async revokers");
+
+void __cheri_compartment("top") entry()
+{
+ Revoker r{};
+ r.init();
+
+ uint32_t epoch = r.system_epoch_get();
+ Debug::log("At startup, revocation epoch is {}; waiting...", epoch);
+
+ r.system_bg_revoker_kick();
+
+ for (int i = 0; i < 10; i++)
+ {
+ bool res;
+ uint32_t newepoch;
+ Timeout t{50};
+
+ res = r.wait_for_completion(&t, epoch & ~1);
+ newepoch = r.system_epoch_get();
+
+ Debug::log("After wait: for {}, result {}, epoch now is {}, "
+ "wait elapsed {} remaining {}",
+ epoch,
+ res,
+ newepoch,
+ t.elapsed,
+ t.remaining);
+
+ Debug::Assert(t.remaining > 0,
+ "Timed out waiting for revoker to advance");
+
+ if (res)
+ {
+ epoch = newepoch;
+ r.system_bg_revoker_kick();
+ }
+ }
+}
diff --git a/tests.extra/hardware_revoker_IRQs/xmake.lua b/tests.extra/hardware_revoker_IRQs/xmake.lua
new file mode 100644
index 0000000..a8c713f
--- /dev/null
+++ b/tests.extra/hardware_revoker_IRQs/xmake.lua
@@ -0,0 +1,26 @@
+set_project("Hardware Revoker IRQ Basic Functionality Test")
+sdkdir = "../../sdk"
+includes(sdkdir)
+set_toolchains("cheriot-clang")
+
+option("board")
+ set_default("ibex-safe-simulator")
+
+compartment("top")
+ add_files("top.cc")
+
+firmware("top_compartment")
+ add_deps("freestanding", "debug")
+ add_deps("top")
+ on_load(function(target)
+ target:values_set("board", "$(board)")
+ target:values_set("threads", {
+ {
+ compartment = "top",
+ priority = 1,
+ entry_point = "entry",
+ stack_size = 0x300,
+ trusted_stack_frames = 1
+ }
+ }, {expand = false})
+ end)
diff --git a/tests.extra/regress-thread_exit_IRQ/helper.cc b/tests.extra/regress-thread_exit_IRQ/helper.cc
index 023b4fe..0f065eb 100644
--- a/tests.extra/regress-thread_exit_IRQ/helper.cc
+++ b/tests.extra/regress-thread_exit_IRQ/helper.cc
@@ -1,5 +1,5 @@
#include "helper.h"
-[[cheri::interrupt_state(enabled)]] void* help(void)
+[[cheri::interrupt_state(enabled)]] void *help()
{
return __builtin_return_address(0);
}
diff --git a/tests.extra/regress-thread_exit_IRQ/helper.h b/tests.extra/regress-thread_exit_IRQ/helper.h
index 578659e..0eaf329 100644
--- a/tests.extra/regress-thread_exit_IRQ/helper.h
+++ b/tests.extra/regress-thread_exit_IRQ/helper.h
@@ -1,2 +1,2 @@
#include <compartment.h>
-void* __cheri_compartment("helper") help(void);
+void *__cheri_compartment("helper") help();
diff --git a/tests.extra/regress-thread_exit_IRQ/top.cc b/tests.extra/regress-thread_exit_IRQ/top.cc
index 57087ce..e1a87d7 100644
--- a/tests.extra/regress-thread_exit_IRQ/top.cc
+++ b/tests.extra/regress-thread_exit_IRQ/top.cc
@@ -1,5 +1,5 @@
#include "helper.h"
void __cheri_compartment("top") entry()
{
- asm volatile ("cmove cra, %0; cret" : : "C"(help()));
+ asm volatile("cmove cra, %0; cret" : : "C"(help()));
}
diff --git a/tests/allocator-test.cc b/tests/allocator-test.cc
index 05a4ac8..87af6c3 100644
--- a/tests/allocator-test.cc
+++ b/tests/allocator-test.cc
@@ -28,6 +28,17 @@
DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(emptyHeap, 0);
#define EMPTY_HEAP STATIC_SEALED_VALUE(emptyHeap)
+/* Used to test that the revoker sweeps static sealed capabilities */
+struct AllocatorTestStaticSealedType
+{
+ void *pointer;
+};
+DECLARE_AND_DEFINE_STATIC_SEALED_VALUE(struct AllocatorTestStaticSealedType,
+ allocator_test,
+ AllocatorTestCapabilityType,
+ allocatorTestStaticSealedValue,
+ 0);
+
namespace
{
/**
@@ -39,12 +50,6 @@
Timeout noWait{0};
- /**
- * Size of an allocation that is big enough that we'll exhaust memory before
- * we allocate `MaxAllocCount` of them.
- */
- constexpr size_t BigAllocSize = 1024 * 32;
- constexpr size_t AllocSize = 0xff0;
constexpr size_t MaxAllocCount = 16;
constexpr size_t TestIterations =
#ifdef NDEBUG
@@ -57,6 +62,27 @@
std::vector<void *> allocations;
/**
+ * Quick check of basic functionality before we get too carried away
+ *
+ * This is marked as noinline because otherwise the predict-false on the
+ * test failures causes all of the log-message-and-fail blocks to be moved
+ * to the end of the function and, if this is inlined, ends up with some
+ * branches that are more than 2 KiB away from their targets.
+ */
+ __noinline void test_preflight()
+ {
+ Timeout t{5};
+ void *volatile p = heap_allocate(&t, MALLOC_CAPABILITY, 16);
+ TEST(Capability{p}.is_valid(), "Unable to make first allocation");
+
+ int res = heap_free(MALLOC_CAPABILITY, p);
+ TEST_EQUAL(res, 0, "heap_free returned nonzero");
+ TEST(
+ !Capability{p}.is_valid(),
+ "Freed pointer still live; load barrier or revoker out of service?");
+ }
+
+ /**
* Test the revoker by constantly allocating and freeing batches of
* allocations. The total amount of allocations must greatly exceed the heap
* size to force a constant stream of allocation failures and revocations.
@@ -66,9 +92,98 @@
* This performance test should not fail. If it fails it's either the
* allocations in one iteration exceed the total heap size, or the revoker
* is buggy or too slow.
+ *
+ * This is marked as noinline because otherwise the predict-false on the
+ * test failures causes all of the log-message-and-fail blocks to be moved
+ * to the end of the function and, if this is inlined, ends up with some
+ * branches that are more than 2 KiB away from their targets.
*/
- void test_revoke()
+ __noinline void test_revoke(const size_t HeapSize)
{
+#ifdef TEMPORAL_SAFETY
+ {
+ static cheriot::atomic<int> state = 2;
+ int sleeps;
+
+ void *volatile pStack = malloc(16);
+ TEST(__builtin_cheri_tag_get(pStack),
+ "Failed to allocate for test");
+
+ static void *volatile pGlobal = malloc(16);
+ TEST(__builtin_cheri_tag_get(pGlobal),
+ "Failed to allocate for test");
+
+ auto unsealedToken = token_unseal<AllocatorTestStaticSealedType>(
+ STATIC_SEALING_TYPE(AllocatorTestCapabilityType),
+ STATIC_SEALED_VALUE(allocatorTestStaticSealedValue));
+ unsealedToken->pointer = pGlobal;
+
+ /*
+ * Check that trusted stacks are swept. This one is fun: we need to
+ * somehow ensure that a freed pointer is in the register file of an
+ * off-core thread. async() and inline asm it is!
+ */
+ async([=]() {
+ int ptag, scratch;
+
+ /*
+ * Release the main thread, then busy spin, ensuring that our
+ * test pointer is in a register throughout, then get its tag.
+ */
+ __asm__ volatile("csw zero, 0(%[state])\n"
+ "1:\n"
+ "clw %[scratch], 0(%[state])\n"
+ "beqz %[scratch], 1b\n"
+ "cgettag %[out], %[p]\n"
+ : [out] "+&r"(ptag), [scratch] "=&r"(scratch)
+ : [p] "C"(pGlobal), [state] "C"(&state));
+
+ TEST(ptag == 0, "Revoker failed to sweep trusted stack");
+
+ /* Release the main thread */
+ state = 3;
+ });
+
+ sleeps = 0;
+ while (state.load() != 0)
+ {
+ TEST(sleep(1) >= 0, "Failed to sleep");
+ TEST(sleeps++ < 100, "Background thread not ready");
+ }
+
+ free(pStack);
+ free(pGlobal);
+ heap_quarantine_empty();
+
+ state = 1;
+
+ /* Check that globals are swept */
+ TEST(!__builtin_cheri_tag_get_temporal(pGlobal),
+ "Revoker failed to sweep globals");
+
+ /* Check that the stack is swept */
+ TEST(!__builtin_cheri_tag_get_temporal(pStack),
+ "Revoker failed to sweep stack");
+
+ TEST(!__builtin_cheri_tag_get_temporal(unsealedToken->pointer),
+ "Revoker failed to sweep static sealed cap");
+
+ /* Wait for the async thread to have performed its test */
+ sleeps = 0;
+ while (state.load() != 3)
+ {
+ TEST(sleep(1) >= 0, "Failed to sleep");
+ TEST(sleeps++ < 100, "Background thread not finished");
+ }
+ }
+#else
+ debug_log("Skipping temporal safety checks");
+#endif
+
+ const size_t AllocSize = HeapSize / (MaxAllocCount + 2);
+ debug_log("test_revoke using {}-byte objects", AllocSize);
+
+ /* Repeatedly cycle quarantine */
allocations.resize(MaxAllocCount);
for (size_t i = 0; i < TestIterations; ++i)
{
@@ -101,8 +216,7 @@
"Checked that all allocations have been deallocated ({} of {})",
static_cast<int>(i),
static_cast<int>(TestIterations));
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
}
allocations.clear();
}
@@ -112,8 +226,18 @@
* Test that we can do a long-running blocking allocation in one thread and
* a free in another thread and make forward progress.
*/
- void test_blocking_allocator()
+ void test_blocking_allocator(const size_t HeapSize)
{
+ /**
+ * Size of an allocation that is big enough that we'll exhaust memory
+ * before we allocate `MaxAllocCount` of them.
+ */
+ const size_t BigAllocSize = HeapSize / (MaxAllocCount - 1);
+ TEST(BigAllocSize > 0,
+ "Cannot guestimate big allocation size for our heap of {} bytes",
+ HeapSize);
+ debug_log("BigAllocSize {} bytes", BigAllocSize);
+
allocations.resize(MaxAllocCount);
// Create the background worker before we try to exhaust memory.
async([]() {
@@ -122,8 +246,7 @@
freeStart.wait(0);
// One extra sleep to make sure that we're really in the blocking
// sleep.
- Timeout t{2};
- thread_sleep(&t);
+ TEST(sleep(2) >= 0, "Failed to sleep");
debug_log(
"Deallocation thread resuming, freeing pool of allocations");
// Free all of the allocations to make space.
@@ -131,7 +254,9 @@
{
if (allocation != nullptr)
{
- heap_free(MALLOC_CAPABILITY, allocation);
+ TEST_EQUAL(heap_free(MALLOC_CAPABILITY, allocation),
+ 0,
+ "Could not free allocation");
}
}
// Notify the parent thread that we're done.
@@ -139,6 +264,13 @@
freeStart.notify_one();
});
+ /*
+ * Empty the allocator's quarantine so that we're sure that the nullptr
+ * failure we see below isn't because we aren't allowing the revocation
+ * state machine to advance.
+ */
+ heap_quarantine_empty();
+
bool memoryExhausted = false;
for (auto &allocation : allocations)
{
@@ -154,6 +286,8 @@
}
}
TEST(memoryExhausted, "Failed to exhaust memory");
+ debug_log("Calling heap_render");
+ TEST_EQUAL(heap_render(), 0, "heap_render returned non-zero");
debug_log("Trying a non-blocking allocation");
// nullptr check because we explicitly want to check for OOM
TEST(heap_allocate(&noWait, MALLOC_CAPABILITY, BigAllocSize) == nullptr,
@@ -237,9 +371,14 @@
16, 64, 72, 96, 128, 256, 384, 1024};
static constexpr size_t NAllocSizes = std::size(AllocSizes);
+ static constexpr size_t NCachedFrees = 4 * MaxAllocCount;
+
ds::xoroshiro::P32R16 rand = {};
auto t = Timeout(0); /* don't sleep */
+ std::vector<void *> cachedFrees;
+ cachedFrees.resize(NCachedFrees);
+
auto doAlloc = [&](size_t sz) {
CHERI::Capability p{heap_allocate(&t, MALLOC_CAPABILITY, sz)};
@@ -261,6 +400,8 @@
TEST(CHERI::Capability{p}.is_valid(), "Double free {}", p);
+ cachedFrees[rand.next() % NCachedFrees] = p;
+
free(p);
};
@@ -295,8 +436,15 @@
doFree();
}
}
+
+ for (void *p : cachedFrees)
+ {
+ TEST(!Capability{p}.is_valid(), "Detected necromancy: {}", p);
+ }
}
+ cachedFrees.clear();
+
for (auto allocation : allocations)
{
free(allocation);
@@ -330,11 +478,11 @@
ssize_t claimSize = heap_claim(SECOND_HEAP, alloc);
claimCount++;
TEST((allocSize <= claimSize) &&
- (claimSize <= allocSize + CHERIOTHeapMinChunkSize),
- "{}-byte allocation claimed as {} bytes (claim number {})",
- allocSize,
- claimSize,
- claimCount);
+ (claimSize <= allocSize + CHERIOTHeapMinChunkSize),
+ "{}-byte allocation claimed as {} bytes (claim number {})",
+ allocSize,
+ claimSize,
+ claimCount);
};
claim();
int ret = heap_free(SECOND_HEAP, alloc);
@@ -410,6 +558,7 @@
void test_hazards()
{
+ int sleeps;
debug_log("Before allocating, quota left: {}",
heap_quota_remaining(SECOND_HEAP));
Timeout longTimeout{1000};
@@ -422,7 +571,7 @@
static cheriot::atomic<int> state = 0;
async([=]() {
Timeout t{1};
- int claimed = heap_claim_fast(&t, ptr, ptr2);
+ int claimed = heap_claim_ephemeral(&t, ptr, ptr2);
TEST(claimed == 0, "Heap claim failed: {}", claimed);
state = 1;
while (state.load() == 1) {}
@@ -430,21 +579,22 @@
// Exiting this task will cause this closure to be freed, which
// will collect dangling hazard pointers. Wait for long enough for
// the heap check to work.
- t = 1;
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
});
// Allow the async function to run and establish hazards
+ sleeps = 0;
while (state.load() != 1)
{
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
+ TEST(sleeps++ < 100,
+ "Background thread failed to establish hazards");
}
debug_log("Before freeing, quota left: {}",
heap_quota_remaining(SECOND_HEAP));
- heap_free(SECOND_HEAP, ptr);
+ TEST_EQUAL(heap_free(SECOND_HEAP, ptr), 0, "First free failed");
debug_log("After free 1, quota left: {}",
heap_quota_remaining(SECOND_HEAP));
- heap_free(SECOND_HEAP, ptr2);
+ TEST_EQUAL(heap_free(SECOND_HEAP, ptr2), 0, "Second free failed");
debug_log("After free 2, quota left: {}",
heap_quota_remaining(SECOND_HEAP));
TEST(Capability{ptr}.is_valid(),
@@ -455,21 +605,21 @@
ptr2);
state = 2;
// Yield to allow the hazards to be dropped.
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to yield to drop hazards");
// Try a double free. This may logically succeed, but should not affect
// our quota.
- heap_free(SECOND_HEAP, ptr);
+ TEST_EQUAL(heap_free(SECOND_HEAP, ptr),
+ -EPERM,
+ "Attempt to free freed but hazarded pointer not EPERM");
// Sleep again to make sure that the lambda from our async is gone.
// The logs may make it take more than one quantum in debug builds.
// The next test requires all memory allocated from the malloc
// capability to be freed before it starts.
- int sleeps = 0;
+ sleeps = 0;
while (heap_quota_remaining(MALLOC_CAPABILITY) < MALLOC_QUOTA &&
heap_quota_remaining(MALLOC_CAPABILITY) > 0)
{
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
TEST(sleeps++ < 100,
"Sleeping for too long waiting for async lambda to be freed");
}
@@ -485,8 +635,9 @@
{
void *unsealedCapability;
auto sealingCapability = STATIC_SEALING_TYPE(sealingTest);
+ Timeout t{AllocTimeout};
Capability sealedPointer =
- token_sealed_unsealed_alloc(&noWait,
+ token_sealed_unsealed_alloc(&t,
MALLOC_CAPABILITY,
sealingCapability,
tokenSize,
@@ -616,6 +767,21 @@
"{}, expected {}",
heap_quota_remaining(SECOND_HEAP),
SECOND_HEAP_QUOTA);
+
+ /*
+ * Test the bad-outparam failure path of token_sealed_unsealled_alloc.
+ * This is expected to still return the allocated object.
+ */
+ sealedPointer = token_sealed_unsealed_alloc(
+ &noWait, SECOND_HEAP, sealingCapability, 16, nullptr);
+ TEST(sealedPointer.is_valid(), "Invalid outparam case failed alloc");
+ TEST_EQUAL(
+ token_obj_destroy(SECOND_HEAP, sealingCapability, sealedPointer),
+ 0,
+ "Failed to free invalid-outparam sealed object");
+ TEST_EQUAL(heap_quota_remaining(SECOND_HEAP),
+ SECOND_HEAP_QUOTA,
+ "Invalid outparam path failed to restore quota");
}
} // namespace
@@ -631,12 +797,9 @@
const ptraddr_t HeapEnd = LA_ABS(__export_mem_heap_end);
const size_t HeapSize = HeapEnd - HeapStart;
- TEST(BigAllocSize < HeapSize,
- "Big allocation size is too large for our heap ({} >= {})",
- BigAllocSize,
- BigAllocSize);
debug_log("Heap size is {} bytes", HeapSize);
+ test_preflight();
test_token();
test_hazards();
@@ -682,9 +845,9 @@
ret = heap_free(MALLOC_CAPABILITY, array);
TEST(ret == 0, "Freeing array failed: {}", ret);
- test_blocking_allocator();
- heap_quarantine_empty();
- test_revoke();
+ test_blocking_allocator(HeapSize);
+ TEST_EQUAL(heap_quarantine_empty(), 0, "Could not flush quarantine");
+ test_revoke(HeapSize);
test_fuzz();
allocations.clear();
allocations.shrink_to_fit();
diff --git a/tests/compartment_calls-test.cc b/tests/compartment_calls-test.cc
index a8ed1ee..d8c8368 100644
--- a/tests/compartment_calls-test.cc
+++ b/tests/compartment_calls-test.cc
@@ -68,7 +68,10 @@
test_number_of_arguments();
- test_incorrect_export_table(nullptr, &outTestFailed);
+ TEST_EQUAL(
+ test_incorrect_export_table(nullptr, &outTestFailed),
+ 0,
+ "Test incorrect entry point without error handler bad return value");
TEST(outTestFailed == false,
"Test incorrect entry point without error handler failed");
return 0;
diff --git a/tests/compartment_calls.h b/tests/compartment_calls.h
index b740f31..c418218 100644
--- a/tests/compartment_calls.h
+++ b/tests/compartment_calls.h
@@ -38,11 +38,10 @@
const int *x4,
int x5,
int x6);
-__cheri_compartment("compartment_calls_inner") void test_incorrect_export_table(
+__cheri_compartment("compartment_calls_inner") int test_incorrect_export_table(
__cheri_callback void (*fn)(),
- bool *outTestFailed);
+ bool *outTestFailed);
__cheri_compartment(
"compartment_calls_inner_with_"
"handler") int test_incorrect_export_table_with_handler(__cheri_callback int (*fn)());
-__cheri_compartment("compartment_calls_outer") void compartment_call_outer();
constexpr int ConstantValue = 0x41414141;
diff --git a/tests/compartment_calls_inner.cc b/tests/compartment_calls_inner.cc
index 43fd9d0..f0d1c8f 100644
--- a/tests/compartment_calls_inner.cc
+++ b/tests/compartment_calls_inner.cc
@@ -97,8 +97,8 @@
return 0;
}
-void test_incorrect_export_table(__cheri_callback void (*fn)(),
- bool *outTestFailed)
+int test_incorrect_export_table(__cheri_callback void (*fn)(),
+ bool *outTestFailed)
{
/*
* Trigger a cross-compartment call with an invalid export entry.
@@ -111,4 +111,6 @@
fn();
*outTestFailed = false;
+
+ return 0;
}
diff --git a/tests/crash_recovery-test.cc b/tests/crash_recovery-test.cc
index cc67b24..1de2977 100644
--- a/tests/crash_recovery-test.cc
+++ b/tests/crash_recovery-test.cc
@@ -49,7 +49,7 @@
int test_crash_recovery()
{
debug_log("Calling crashy compartment indirectly");
- test_crash_recovery_outer(0);
+ TEST_EQUAL(test_crash_recovery_outer(0), 0, "Indirect crash failed");
check_stack();
TEST(crashes == 0, "Ran crash handler for outer compartment");
debug_log("Compartment with no error handler returned normally after "
diff --git a/tests/crash_recovery.h b/tests/crash_recovery.h
index 9c2c7bd..ebbdb56 100644
--- a/tests/crash_recovery.h
+++ b/tests/crash_recovery.h
@@ -6,7 +6,7 @@
__cheri_compartment("crash_recovery_inner") void *test_crash_recovery_inner(
int);
-__cheri_compartment("crash_recovery_outer") void test_crash_recovery_outer(int);
+__cheri_compartment("crash_recovery_outer") int test_crash_recovery_outer(int);
/**
* Checks that the stack is entirely full of zeroes below the current stack
diff --git a/tests/crash_recovery_outer.cc b/tests/crash_recovery_outer.cc
index 9d3513d..6b59b7e 100644
--- a/tests/crash_recovery_outer.cc
+++ b/tests/crash_recovery_outer.cc
@@ -6,7 +6,7 @@
#include <cheri.hh>
#include <errno.h>
-void test_crash_recovery_outer(int)
+int test_crash_recovery_outer(int)
{
debug_log(
"Calling crashy compartment from compartment with no error handler");
@@ -19,4 +19,5 @@
debug_log("Calling crashy compartment returned to compartment with no "
"error handler. Return value: {}",
ret);
+ return 0;
}
diff --git a/tests/futex-test.cc b/tests/futex-test.cc
index 4abfe6c..433f110 100644
--- a/tests/futex-test.cc
+++ b/tests/futex-test.cc
@@ -25,6 +25,7 @@
{
static uint32_t futex;
int ret;
+ int sleeps;
// Make sure that waking a futex with no sleepers doesn't crash!
ret = futex_wake(&futex, 1);
TEST(ret == 0, "Waking a futex with no sleepers should return 0");
@@ -32,7 +33,7 @@
// has been set to 1.
async([]() {
futex = 1;
- futex_wake(&futex, 1);
+ (void)futex_wake(&futex, 1);
});
debug_log("Calling blocking futex_wait");
ret = futex_wait(&futex, 0);
@@ -123,7 +124,7 @@
while (state != 1)
{
Timeout t{3};
- thread_sleep(&t);
+ (void)thread_sleep(&t);
}
debug_log("Consuming all CPU on medium-priority thread");
state = 2;
@@ -144,27 +145,31 @@
debug_log("Low-priority thread finished, unlocking");
state = 4;
futex = 0;
- futex_wake(&futex, 1);
+ (void)futex_wake(&futex, 1);
}
};
async(priorityBug);
async(priorityBug);
debug_log("Waiting for background threads to enter the right state");
- while (state != 2)
+ for (sleeps = 0; (sleeps < 100) && (state != 2); sleeps++)
{
- Timeout t{3};
- thread_sleep(&t);
+ TEST(sleep(3) >= 0, "Failed to sleep");
}
+ TEST(sleeps < 100, "Waited too long for background threads");
debug_log("High-priority thread attempting to acquire futex owned by "
"low-priority thread without priority propagation");
state = 3;
t.remaining = 1;
- futex_timed_wait(&t, &futex, futex, FutexNone);
+ TEST_EQUAL(futex_timed_wait(&t, &futex, futex, FutexNone),
+ -ETIMEDOUT,
+ "futex_timed_wait failed");
TEST(futex != 0, "Made progress surprisingly!");
debug_log("High-priority thread attempting to acquire futex owned by "
"low-priority thread with priority propagation");
t.remaining = 4;
- futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance);
+ TEST_EQUAL(futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance),
+ 0,
+ "futex_timed_wait failed");
TEST(futex == 0, "Failed to make progress!");
futex = 1234;
diff --git a/tests/list-test.cc b/tests/list-test.cc
index 5ea7700..bf6f66a 100644
--- a/tests/list-test.cc
+++ b/tests/list-test.cc
@@ -174,7 +174,10 @@
// we do not use here), not to the removed element.
LinkedObject::ObjectRing *removedCell = objects.first();
ds::linked_list::remove(objects.first());
- heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(removedCell));
+ TEST_EQUAL(
+ heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(removedCell)),
+ 0,
+ "Failed to free removed cell");
TEST(LinkedObject::from_ring(objects.first())->data == 1,
"First element of the list is incorrect after removing the first "
"element, expected {}, got {}",
@@ -191,7 +194,8 @@
{
struct LinkedObject *o = LinkedObject::from_ring(cell);
cell = cell->cell_next();
- heap_free(MALLOC_CAPABILITY, o);
+ TEST_EQUAL(
+ heap_free(MALLOC_CAPABILITY, o), 0, "Failed to free list object");
counter++;
}
@@ -213,7 +217,10 @@
// removed cell. This is great here because we will free the
// object anyways. We could also use `remove` here.
auto l = ds::linked_list::unsafe_remove(cell);
- heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(cell));
+ TEST_EQUAL(
+ heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(cell)),
+ 0,
+ "Failed to free searched object");
// `l` is the predecessor of `cell` in the residual ring, so
// this does exactly what we want when `::search` iterates.
cell = l;
@@ -221,7 +228,9 @@
return false;
});
// `::search` does not visit the element passed (`middle`)
- heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(middle));
+ TEST_EQUAL(heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(middle)),
+ 0,
+ "Failed to free middle object");
counter++;
TEST(counter == NumberOfListElements - 1,
diff --git a/tests/misc-test.cc b/tests/misc-test.cc
index e39b1e5..3df4689 100644
--- a/tests/misc-test.cc
+++ b/tests/misc-test.cc
@@ -268,6 +268,7 @@
o.permissions().without(Permission::Global),
"Loading global sealed cap through non-LoadGlobal bad perms");
+#ifndef CHERIOT_NO_SAIL_83
/*
* Use CAndPerm to shed Global from our o cap.
* Spell this a little oddly to make sure we get CAndPerm with a mask of
@@ -278,6 +279,10 @@
oLocal2.without_permissions(Permission::Global);
TEST_EQUAL(oLocal2, OLocal1, "CAndPerm ~GL gone wrong");
+#else
+ debug_log(
+ "Skipping test for cheriot-sail#83 because the ISA version is too old.");
+#endif
}
int test_misc()
diff --git a/tests/multiwaiter-test.cc b/tests/multiwaiter-test.cc
index b6b77dd..6cc0249 100644
--- a/tests/multiwaiter-test.cc
+++ b/tests/multiwaiter-test.cc
@@ -32,12 +32,12 @@
EventWaiterSource events[4];
debug_log("Testing error case: Invalid values");
- events[0] = {nullptr, static_cast<EventWaiterKind>(5), 0};
+ events[0] = {nullptr, 0};
ret = multiwaiter_wait(&t, mw, events, 1);
TEST(ret == -EINVAL, "multiwaiter returned {}, expected {}", ret, -EINVAL);
debug_log("Testing one futex, already ready");
- events[0] = {&futex, EventWaiterFutex, 1};
+ events[0] = {&futex, 1};
t.remaining = 5;
ret = multiwaiter_wait(&t, mw, events, 1);
TEST(ret == 0, "multiwaiter returned {}, expected 0", ret);
@@ -47,13 +47,13 @@
sleep(1);
debug_log("Waking futex from background thread");
*futexWord = value;
- futex_wake(futexWord, 1);
+ TEST(futex_wake(futexWord, 1) >= 0, "futex_wait failed");
});
};
debug_log("Testing one futex, not yet ready");
setFutex(&futex, 1);
- events[0] = {&futex, EventWaiterFutex, 0};
+ events[0] = {&futex, 0};
t.remaining = 6;
ret = multiwaiter_wait(&t, mw, events, 1);
TEST(ret == 0, "multiwaiter returned {}, expected 0", ret);
@@ -62,8 +62,8 @@
futex = 0;
futex2 = 2;
setFutex(&futex2, 3);
- events[0] = {&futex, EventWaiterFutex, 0};
- events[1] = {&futex2, EventWaiterFutex, 2};
+ events[0] = {&futex, 0};
+ events[1] = {&futex2, 2};
t.remaining = 6;
ret = multiwaiter_wait(&t, mw, events, 2);
TEST(ret == 0, "multiwaiter returned {}, expected 0", ret);
@@ -118,7 +118,7 @@
futex = 0;
setFutex(&futex, 1);
multiwaiter_queue_receive_init(&events[0], queue);
- events[1] = {&futex, EventWaiterFutex, 0};
+ events[1] = {&futex, 0};
t.remaining = 6;
ret = multiwaiter_wait(&t, mw, events, 2);
TEST(ret == 0, "multiwait on futex and queue returned {}", ret);
diff --git a/tests/stack-test.cc b/tests/stack-test.cc
index b3b894f..205a398 100644
--- a/tests/stack-test.cc
+++ b/tests/stack-test.cc
@@ -27,16 +27,17 @@
return ErrorRecoveryBehaviour::ForceUnwind;
}
-__cheri_callback void test_trusted_stack_exhaustion()
+__cheri_callback int test_trusted_stack_exhaustion()
{
- exhaust_trusted_stack(&test_trusted_stack_exhaustion,
- &threadStackTestFailed);
+ return exhaust_trusted_stack(&test_trusted_stack_exhaustion,
+ &threadStackTestFailed);
}
-__cheri_callback void cross_compartment_call()
+__cheri_callback int cross_compartment_call()
{
TEST(false,
"Cross compartment call with invalid CSP shouldn't be reachable");
+ return -EINVAL;
}
namespace
@@ -70,7 +71,10 @@
void expect_handler(bool handlerExpected)
{
debug_log("Expected to invoke the handler? {}", handlerExpected);
- set_expected_behaviour(&threadStackTestFailed, handlerExpected);
+ TEST_EQUAL(
+ set_expected_behaviour(&threadStackTestFailed, handlerExpected),
+ 0,
+ "Failed to set expectations");
}
__attribute__((used)) extern "C" int test_small_stack()
@@ -114,7 +118,7 @@
// Defeat the compiler optimisation that may turn our first call to this into a
// call. If the compiler does this then we will fail on an even number of
// cross-compartment calls not an odd number.
-__cheri_callback void (*volatile crossCompartmentCall)();
+__cheri_callback int (*volatile crossCompartmentCall)();
/*
* The stack tests should cover the edge-cases scenarios for both
@@ -139,7 +143,7 @@
TEST(ret == -ENOTENOUGHSTACK,
"test_with_small_stack failed, returned {} with 128-byte stack",
ret);
- __cheri_callback void (*callback)() = cross_compartment_call;
+ __cheri_callback int (*callback)() = cross_compartment_call;
crossCompartmentCall = test_trusted_stack_exhaustion;
debug_log("exhaust trusted stack, do self recursion with a cheri_callback");
@@ -148,12 +152,15 @@
debug_log("exhausting the compartment stack");
expect_handler(false);
- exhaust_thread_stack();
+ TEST_EQUAL(
+ exhaust_thread_stack(), -ECOMPARTMENTFAIL, "exhaust_thread_stack failed");
debug_log("exhausting the compartment stack during a switcher call");
expect_handler(false);
threadStackTestFailed = true;
- exhaust_thread_stack_spill(callback);
+ TEST_EQUAL(exhaust_thread_stack_spill(callback),
+ 0,
+ "exhaust_thread_stack_spill failed");
TEST(threadStackTestFailed == false, "switcher did not return error");
debug_log("modifying stack permissions on fault");
@@ -164,7 +171,9 @@
compartmentStackPermissions.without(permissionToRemove);
debug_log("Permissions: {}", permissions);
expect_handler(stack_is_mostly_valid(permissions));
- set_csp_permissions_on_fault(permissions);
+ TEST_EQUAL(set_csp_permissions_on_fault(permissions),
+ -ECOMPARTMENTFAIL,
+ "Unexpected success with restricted permissions");
}
debug_log("modifying stack permissions on cross compartment call");
@@ -173,16 +182,22 @@
auto permissions =
compartmentStackPermissions.without(permissionToRemove);
debug_log("Permissions: {}", permissions);
- set_csp_permissions_on_call(permissions, callback);
+ TEST_EQUAL(set_csp_permissions_on_call(permissions, callback),
+ -ECOMPARTMENTFAIL,
+ "Unexpected success with restricted permissions");
}
debug_log("invalid stack on fault");
expect_handler(false);
- test_stack_invalid_on_fault();
+ TEST_EQUAL(test_stack_invalid_on_fault(),
+ -ECOMPARTMENTFAIL,
+ "stack_invalid_on_fault failed");
debug_log("invalid stack on cross compartment call");
expect_handler(false);
+ TEST_EQUAL(test_stack_invalid_on_call(callback),
+ -ECOMPARTMENTFAIL,
+ "stack_invalid_on_call failed");
- test_stack_invalid_on_call(callback);
return 0;
}
diff --git a/tests/stack_integrity_thread.cc b/tests/stack_integrity_thread.cc
index c897432..bfeab94 100644
--- a/tests/stack_integrity_thread.cc
+++ b/tests/stack_integrity_thread.cc
@@ -64,14 +64,16 @@
* Set up the handler expectations. Takes the caller's error flag and
* whether the handler is expected as arguments.
*/
-void set_expected_behaviour(bool *outTestFailed, bool handlerExpected)
+int set_expected_behaviour(bool *outTestFailed, bool handlerExpected)
{
expectedHandler = handlerExpected;
threadStackTestFailed = outTestFailed;
*outTestFailed = handlerExpected;
+
+ return 0;
}
-void exhaust_thread_stack()
+int exhaust_thread_stack()
{
/* Move the compartment's stack near its end, in order to
* trigger stack exhaustion while the switcher handles
@@ -92,6 +94,8 @@
*threadStackTestFailed = true;
TEST(false, "Should be unreachable");
+
+ return 0;
}
/**
@@ -99,7 +103,7 @@
* callee-saved state. The result should simply be an error return, rather than
* a forced-unwind.
*/
-void exhaust_thread_stack_spill(__cheri_callback void (*fn)())
+int exhaust_thread_stack_spill(__cheri_callback int (*fn)())
{
register auto rfn asm("ct1") = fn;
register uintptr_t res asm("ca0") = 0;
@@ -126,52 +130,59 @@
*threadStackTestFailed = false;
TEST(res == -ENOTENOUGHSTACK, "Bad return {}", res);
+
+ return 0;
}
-void set_csp_permissions_on_fault(PermissionSet newPermissions)
+int set_csp_permissions_on_fault(PermissionSet newPermissions)
{
__asm__ volatile(
"candperm csp, csp, %0\n"
"csh zero, 0(cnull)\n" ::"r"(newPermissions.as_raw()));
TEST(false, "Should be unreachable");
+ return -EINVAL;
}
-void set_csp_permissions_on_call(PermissionSet newPermissions,
- __cheri_callback void (*fn)())
+int set_csp_permissions_on_call(PermissionSet newPermissions,
+ __cheri_callback int (*fn)())
{
CALL_CHERI_CALLBACK(fn, "candperm csp, csp, %1\n", newPermissions.as_raw());
TEST(false, "Should be unreachable");
+ return -EINVAL;
}
-void test_stack_invalid_on_fault()
+int test_stack_invalid_on_fault()
{
__asm__ volatile("ccleartag csp, csp\n"
"csh zero, 0(cnull)\n");
*threadStackTestFailed = true;
TEST(false, "Should be unreachable");
+ return -EINVAL;
}
-void test_stack_invalid_on_call(__cheri_callback void (*fn)())
+int test_stack_invalid_on_call(__cheri_callback int (*fn)())
{
// the `move zero, %1` is a no-op, just to have an operand
CALL_CHERI_CALLBACK(fn, "move zero, %1\nccleartag csp, csp\n", 0);
*threadStackTestFailed = true;
TEST(false, "Should be unreachable");
+ return -EINVAL;
}
-void self_recursion(__cheri_callback void (*fn)())
+int self_recursion(__cheri_callback int (*fn)())
{
(*fn)();
+ return 0;
}
-void exhaust_trusted_stack(__cheri_callback void (*fn)(),
- bool *outLeakedSwitcherCapability)
+int exhaust_trusted_stack(__cheri_callback int (*fn)(),
+ bool *outLeakedSwitcherCapability)
{
- self_recursion(fn);
+ return self_recursion(fn);
}
int test_stack_requirement()
diff --git a/tests/stack_tests.h b/tests/stack_tests.h
index 93ad94b..a209e2e 100644
--- a/tests/stack_tests.h
+++ b/tests/stack_tests.h
@@ -4,28 +4,27 @@
using namespace CHERI;
-__cheri_compartment("stack_integrity_thread") void exhaust_trusted_stack(
- __cheri_callback void (*fn)(),
- bool *outLeakedSwitcherCapability);
-__cheri_compartment("stack_integrity_thread") void exhaust_thread_stack();
-__cheri_compartment("stack_integrity_thread") void exhaust_thread_stack_spill(
- __cheri_callback void (*fn)());
-__cheri_compartment("stack_integrity_thread") void set_csp_permissions_on_fault(
+__cheri_compartment("stack_integrity_thread") int exhaust_trusted_stack(
+ __cheri_callback int (*fn)(),
+ bool *outLeakedSwitcherCapability);
+__cheri_compartment("stack_integrity_thread") int exhaust_thread_stack();
+__cheri_compartment("stack_integrity_thread") int exhaust_thread_stack_spill(
+ __cheri_callback int (*fn)());
+__cheri_compartment("stack_integrity_thread") int set_csp_permissions_on_fault(
PermissionSet newPermissions);
-__cheri_compartment("stack_integrity_thread") void set_csp_permissions_on_call(
- PermissionSet newPermissions,
- __cheri_callback void (*fn)());
-__cheri_compartment(
- "stack_integrity_thread") void test_stack_invalid_on_fault();
-__cheri_compartment("stack_integrity_thread") void test_stack_invalid_on_call(
- __cheri_callback void (*fn)());
+__cheri_compartment("stack_integrity_thread") int set_csp_permissions_on_call(
+ PermissionSet newPermissions,
+ __cheri_callback int (*fn)());
+__cheri_compartment("stack_integrity_thread") int test_stack_invalid_on_fault();
+__cheri_compartment("stack_integrity_thread") int test_stack_invalid_on_call(
+ __cheri_callback int (*fn)());
/**
* Sets what we expect to happen for this test. Is a fault expected to invoke
* the handler? The fault handler will set or clear `*outTestFailed` when a
* fault is received, depending on whether it was expected.
*/
-__cheri_compartment("stack_integrity_thread") void set_expected_behaviour(
+__cheri_compartment("stack_integrity_thread") int set_expected_behaviour(
bool *outTestFailed,
bool handlerExpected);
diff --git a/tests/test-runner.cc b/tests/test-runner.cc
index c9f3acb..5aaf023 100644
--- a/tests/test-runner.cc
+++ b/tests/test-runner.cc
@@ -66,10 +66,7 @@
if (mcause == 0x2)
{
debug_log("Test failure in test runner");
-#ifdef SIMULATION
simulation_exit(1);
-#endif
- return ErrorRecoveryBehaviour::ForceUnwind;
}
debug_log("mcause: {}, pcc: {}", mcause, frame->pcc);
auto [reg, cause] = CHERI::extract_cheri_mtval(mtval);
@@ -82,7 +79,7 @@
/**
* Test suite entry point. Runs all of the tests that we have defined.
*/
-void __cheri_compartment("test_runner") run_tests()
+int __cheri_compartment("test_runner") run_tests()
{
// magic_enum is a pretty powerful stress-test of various bits of linkage.
// In generating `enum_values`, it generates constant strings and pointers
@@ -114,8 +111,9 @@
"Iterator of PermissionSet failed");
}
// These need to be checked visually
- debug_log("Trying to print 8-bit integer: {}", uint8_t(0x12));
- debug_log("Trying to print unsigned 8-bit integer: {}", int8_t(34));
+ debug_log("Trying to print 8-bit integer: {}", static_cast<uint8_t>(0x12));
+ debug_log("Trying to print unsigned 8-bit integer: {}",
+ static_cast<int8_t>(34));
debug_log("Trying to print char: {}", 'c');
debug_log("Trying to print 32-bit integer: {}", 12345);
debug_log("Trying to print 64-bit integer: {}", 123456789012345LL);
@@ -162,13 +160,5 @@
TEST(crashDetected == false, "One or more tests failed");
- // Exit the simulator if we are running in simulation.
-#ifdef SIMULATION
simulation_exit();
-#endif
- // Infinite loop if we're not in simulation.
- while (true)
- {
- yield();
- }
}
diff --git a/tests/tests.hh b/tests/tests.hh
index 14780ed..79779b7 100644
--- a/tests/tests.hh
+++ b/tests/tests.hh
@@ -56,6 +56,6 @@
inline Ticks sleep(Ticks ticks)
{
Timeout t{ticks};
- thread_sleep(&t);
+ TEST(thread_sleep(&t) >= 0, "Failed to sleep");
return t.elapsed;
};
diff --git a/tests/thread_pool-test.cc b/tests/thread_pool-test.cc
index 52a08e0..9aaf861 100644
--- a/tests/thread_pool-test.cc
+++ b/tests/thread_pool-test.cc
@@ -75,8 +75,7 @@
int sleeps = 0;
while (counter < 2)
{
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
TEST(sleeps < 100, "Gave up after too many sleeps");
}
debug_log("Yielded {} times for the thread pool to run our jobs", sleeps);
@@ -86,13 +85,8 @@
free(heapInt);
async([]() {
- auto fast = thread_id_get();
- auto slow = thread_id_get();
- TEST(fast == slow,
- "Thread ID is different in fast ({}) and slow ({}) accessors",
- fast,
- slow);
- TEST(fast != 1, "Thread ID for thread pool thread should not be 1");
+ TEST(thread_id_get() != 1,
+ "Thread ID for thread pool thread should not be 1");
});
CHERI::Capability<void> mainThread{switcher_current_thread()};
@@ -125,8 +119,7 @@
{
if (!asyncThread)
{
- Timeout t{1};
- thread_sleep(&t);
+ TEST(sleep(1) >= 0, "Failed to sleep");
}
}
TEST(asyncThread, "Worker thread did not provide thread pointer");
@@ -134,8 +127,7 @@
bool ret = switcher_interrupt_thread(asyncThread);
interruptStarted = true;
TEST(ret, "Interrupting worker thread failed: {}", ret);
- Timeout t{3};
- thread_sleep(&t);
+ TEST(sleep(3) >= 0, "Failed to sleep");
TEST(interrupted, "Worker thread was not interrupted");
return 0;
static cheriot::atomic<uint32_t> barrier{3};