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};