Merge remote-tracking branch 'spacebeaker/upstream_gh' into update This syncs with upstream cheriot-rtos. Bypass-Presubmit-Reason: no presubmit flows. Change-Id: I483a59d9bcb85743cd33c4933ce836aa5881a852
diff --git a/README.md b/README.md index e87571a..d5da6b3 100644 --- a/README.md +++ b/README.md
@@ -5,11 +5,11 @@ This is currently a *research project* that has been open sourced to enable wider collaboration. It is not yet in a state where it should be used in production: in particular, security issues will currently be fixed in the main branch of the repo with no coordinated disclosure. -To use this, you will also some dependencies. +To use this, you will also need to install some dependencies. The [getting started guide](docs/GettingStarted.md) describes in detail how to build these: - A [version of LLVM with CHERIoT support](https://github.com/CHERIoT-Platform/llvm-project/tree/cheriot) - - An implementation of the ISA (e.g. [CHERIoT-Ibex](https://github.com/Microsoft/cheriot-ibex) or the emulator generated from [the formal model](https://github.com/Microsoft/cheriot-sail))) + - An implementation of the ISA (e.g. [CHERIoT-Ibex](https://github.com/Microsoft/cheriot-ibex) or the emulator generated from [the formal model](https://github.com/Microsoft/cheriot-sail)) These dependencies are pre-installed in the dev container that will be automatically downloaded if you open this repository in Visual Studio Code or by hitting `.` to open it in GitHub Code Spaces. @@ -89,7 +89,7 @@ $ xmake ``` -This will create the output in `build/cheriot/cheriot/{release,debug}/{name of firmware target}. +This will create the output in `build/cheriot/cheriot/{release,debug}/{name of firmware target}`. It will also create a `.dump` file in the same location giving the objdump output of the same target. ## Contributing
diff --git a/compile_flags.txt b/compile_flags.txt index b598f57..ae0cf0a 100644 --- a/compile_flags.txt +++ b/compile_flags.txt
@@ -35,7 +35,7 @@ -DCONFIG_THREADS_NUM=3 -DREVOKABLE_MEMORY_START=0x80000000 -DCLANG_TIDY --DCHERIOT_INTERRUPT_NAMES=FakeInterrupt=4,RevokerInterrupt=5,EthernetReceiveInterrupt=3 +-DCHERIOT_INTERRUPT_NAMES=FakeInterrupt=4,RevokerInterrupt=5,EthernetReceiveInterrupt=3,EthernetInterrupt=47 -DCHERIOT_EXPOSE_FREERTOS_SEMAPHORE -DCHERIOT_EXPOSE_FREERTOS_MUTEX -DCHERIOT_EXPOSE_FREERTOS_RECURSIVE_MUTEX
diff --git a/docs/BoardDescriptions.md b/docs/BoardDescriptions.md index e8d032e..c383698 100644 --- a/docs/BoardDescriptions.md +++ b/docs/BoardDescriptions.md
@@ -101,7 +101,7 @@ The clock rate is configured by two properties. The `timer_hz` field is the number of timer increments per second, typically the clock speed of the chip (the RISC-V timer is defined in terms of cycles). The `tickrate_hz` specifies how many scheduler ticks should happen per second. -See the [timeout documentation](Timeout.md) for more discussion about ticks. +See the [timeout documentation](Timeouts.md) for more discussion about ticks. Conditional compilation -----------------------
diff --git a/examples/04.temporal_safety/README.md b/examples/04.temporal_safety/README.md index 1079d64..23911b3 100644 --- a/examples/04.temporal_safety/README.md +++ b/examples/04.temporal_safety/README.md
@@ -1,12 +1,14 @@ Temporal safety example ======================= -This example shows a trivial use-after-free bug. -The code simply allocates an object and frees it, printing the value of the pointer both before and after deallocation. +This example shows an number of use-after-free cases. + +The first case simply allocates an object and frees it, printing the value of the pointer both before and after deallocation. The output from this should be something roughly like this: ``` +Allocating compartment: -- Simple Case -- Allocating compartment: Allocated: 0x80004ce0 (v:1 0x80004ce0-0x80004d0a l:0x2a o:0x0 p: G RWcgm- -- ---) Allocating compartment: Use after free: 0x80004ce0 (v:0 0x80004ce0-0x80004d0a l:0x2a o:0x0 p: G RWcgm- -- ---) ``` @@ -43,3 +45,28 @@ For example, if you remove `W` and `M` permissions from a pointer that you pass as a parameter then you have a guarantee that nothing reachable from the pointer will be mutated. Similarly, if you remove `G` and `L` then you have the guarantee that nothing reachable from the pointer will be captured. If you remove `G` but not `L` then you have the weaker guarantee that the pointer that you passed will not be captured but pointers reachable from it might be. + +The next three use cases show the handling of a sub-object, a capability that references a sub-range of the allocation. + +In the first of these the sub-object is passed to free(). +As no claims have been made, free() would take action only if a cap to the entire object were passed in. +Thus, this call to free() has no effect on the heap or on the pointers held by the client. +(In particular, unlike many historical implementations of malloc, freeing a sub-object will not erroneously return this sub-object's memory to the free pool.) + +In the second use case a claim is made on the sub-object. +This charges the claimant's quota and ensures that the sub-object (and, indeed, the entire object) remains allocated for the duration of the claim, even when the enclosing allocation is passed to free(). +Thereafter, releasing the claim on the sub-object will cause the object to be freed, invalidating the pointers to the object and sub-object alike. + +The third use case shows the difference between a claim and a fast claim. +Claims are persistent, count against the compartment's quota, and last until they are explicitly released, but they are expensive as they require a cross compartment call to the allocator. +Fast claims are ephemeral and belong to the thread rather than the compartment. +Each thread may hold only at most one fast claim (on up to two objects). +They do not count against a quota, but they only last until the thread makes a cross compartment call or another fast claim. +In the example the claim on the sub-object is made with a fast claim, but when the enclosing object is now freed the fast claim is dropped (as free() is a cross compartment call) so both the enclosing and sub-objects become invalid. +_This is a poor use of a fast claim used to illustrate the behaviour; The normal use case is to establish a claim early in the entry to a compartment to prevent an object becoming invalid while the compartment processes it, which may include making its own persistent claim._ + +The final use case shows how an object initially allocated in one compartment may be claimed by (and counted against the quota) of a second compartment. +This allows, for example, a zero-copy data buffer pattern. +Even when the allocation is freed by (and removed from the quota of) the original compartment, it remains valid, because it is now claimed by the second compartment. +Note that the quota charge for a claim on an object is slightly larger than the size of the object itself, because a small amount of additional heap is required for the claim headers. +
diff --git a/examples/04.temporal_safety/allocate.cc b/examples/04.temporal_safety/allocate.cc index 7876969..b2cb79b 100644 --- a/examples/04.temporal_safety/allocate.cc +++ b/examples/04.temporal_safety/allocate.cc
@@ -5,17 +5,143 @@ #include <debug.hh> #include <fail-simulator-on-error.h> +#include "claimant.h" + /// Expose debugging features unconditionally for this compartment. using Debug = ConditionalDebug<true, "Allocating compartment">; /// Thread entry point. void __cheri_compartment("allocate") entry() { - void *x = malloc(42); - // Print the allocated value: - Debug::log("Allocated: {}", x); - free(x); - // Print the dangling pointer, note that it is no longer a valid pointer - // (v:0) - Debug::log("Use after free: {}", x); + // Simple case + { + Debug::log("----- Simple Case -----"); + void *x = malloc(42); + // Print the allocated value: + Debug::log("Allocated: {}", x); + free(x); + // Print the dangling pointer, note that it is no longer a valid pointer + // (v:0) + Debug::log("Use after free: {}", x); + } + + // Sub object + { + Debug::log("----- Sub object -----"); + void *x = malloc(100); + + CHERI::Capability y{x}; + y.address() += 25; + y.bounds() = 50; + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Free y - as it's a sub object of x both x & y remain valid + free(y); + Debug::log("After free of sub object"); + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Free x - both x & y become invalid + free(x); + Debug::log("After free of allocation"); + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + } + + // Sub object with a claim + { + Debug::log("----- Sub object with a claim -----"); + void *x = malloc(100); + + CHERI::Capability y{x}; + y.address() += 25; + y.bounds() = 50; + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Add a claim for y - the quota remaining is reduced + heap_claim(MALLOC_CAPABILITY, y); + Debug::log("heap quota after claim: {}", + heap_quota_remaining(MALLOC_CAPABILITY)); + + // free x. As we have a claim on y both x and y remain valid + free(x); + Debug::log("After free of allocation"); + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // free y - releases the claim and both x & y become invalid + free(y); + Debug::log("After free of sub object"); + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + } + + // Sub object with a fast claim + { + Debug::log("----- Sub object with a fast claim -----"); + void *x = malloc(100); + + CHERI::Capability y{x}; + y.address() += 25; + y.bounds() = 50; + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Add a fast claim for y + Timeout t{10}; + heap_claim_fast(&t, y); + + // In this freeing x will invalidate both x & y because free + // is a cross compartment call, which releases any fast claims. + free(x); + Debug::log("After free"); + Debug::log("Allocated : {}", x); + Debug::log("Sub Object: {}", y); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + } + + // Using a claim in another compartment. + // Note that a claim in the same compartment would also work, but this + // shows the more typical use case + { + Debug::log("----- Claim in another compartment -----"); + void *x = malloc(10); + + Debug::log("Allocated : {}", x); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Get the claimant compartment to make a fast claim + make_claim(x); + + // free x. We get out quota back but x remains valid as + // the claimant compartment has a claim on it + free(x); + Debug::log("After free: {}", x); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + // Get the claimant compartment to show its claim + show_claim(); + + // Give the claimant another ptr so it releases the first + void *y = malloc(10); + make_claim(y); + Debug::log("After make claim"); + Debug::log("x: {}", x); + Debug::log("y: {}", y); + + // Get the claimant compartment to show its new claim + show_claim(); + + // tidy up + free(y); + } }
diff --git a/examples/04.temporal_safety/claimant.cc b/examples/04.temporal_safety/claimant.cc new file mode 100644 index 0000000..c6e38ff --- /dev/null +++ b/examples/04.temporal_safety/claimant.cc
@@ -0,0 +1,34 @@ +#include <compartment.h> +#include <cstdlib> +#include <debug.hh> +#include <fail-simulator-on-error.h> + +/// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug<true, "Claimant compartment">; + +void *x; + +int __cheri_compartment("claimant") make_claim(void *ptr) +{ + Debug::log("Initial quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + + if (x != nullptr) + { + free(x); + } + + Timeout t{10}; + heap_claim(MALLOC_CAPABILITY, ptr); + x = ptr; + + Debug::log("Make Claim : {}", x); + Debug::log("heap quota: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + return 0; +}; + +int __cheri_compartment("claimant") show_claim() +{ + Debug::log("Show Claim : {}", x); + + return 0; +} \ No newline at end of file
diff --git a/examples/04.temporal_safety/claimant.h b/examples/04.temporal_safety/claimant.h new file mode 100644 index 0000000..4558808 --- /dev/null +++ b/examples/04.temporal_safety/claimant.h
@@ -0,0 +1,5 @@ +#include "compartment-macros.h" + +int __cheri_compartment("claimant") make_claim(void *ptr); + +int __cheri_compartment("claimant") show_claim();
diff --git a/examples/04.temporal_safety/xmake.lua b/examples/04.temporal_safety/xmake.lua index a5a9620..8cd8402 100644 --- a/examples/04.temporal_safety/xmake.lua +++ b/examples/04.temporal_safety/xmake.lua
@@ -17,9 +17,14 @@ add_deps("freestanding", "debug") add_files("allocate.cc") +compartment("claimant") + add_deps("debug") + add_files("claimant.cc") + -- Firmware image for the example. firmware("temporal_safety") add_deps("allocate") + add_deps("claimant") on_load(function(target) target:values_set("board", "$(board)") target:values_set("threads", { @@ -28,7 +33,7 @@ priority = 1, entry_point = "entry", stack_size = 0x400, - trusted_stack_frames = 2 + trusted_stack_frames = 4 } }, {expand = false}) end)
diff --git a/examples/08.memory_safety/README.md b/examples/08.memory_safety/README.md index 9c9151f..b7e0533 100644 --- a/examples/08.memory_safety/README.md +++ b/examples/08.memory_safety/README.md
@@ -33,8 +33,8 @@ * `Global` (G), which is initially set on pointers to the heap and globals, and unset on the stack pointer. * `StoreLocal`, which is only set on the stack pointer and means you're allowed to store pointers that don't have G set (as well as those that do). -The effect is that pointers to the stack can only be stored on the stack, and never in heap / globals. -There are no restrictions on where pointers to globals can be stored. -However it is possible to clear G on global pointers, effectively stopping them from being captured during a cross-compartment call. -This adds a huge value to concurrent safety, because it means "local" memory (stack allocations, etc.) can't be stored to memory accessible from different concurrent threads. +There are no restrictions on where pointers to globals can be stored, but if a pointer to the stack is stored in the heap or a global then it is invalidated so that any attempt to use it will trap. +The effect is that usable pointers to the stack can only be stored on the stack, and never in heap / globals. +It is also possible to clear G on global pointers, effectively stopping them from being captured during a cross-compartment call. +This adds a huge value to concurrent safety, because it means "local" memory (stack allocations, etc.) can't be used from memory accessible from different concurrent threads. A great example for that is [implemented](memory_safety_inner.cc:113) in the case for `StoreStackPtrToGlobal`, implemented in the inner compartment. \ No newline at end of file
diff --git a/examples/08.memory_safety/memory_safety_inner.cc b/examples/08.memory_safety/memory_safety_inner.cc index 44be565..ee2b47d 100644 --- a/examples/08.memory_safety/memory_safety_inner.cc +++ b/examples/08.memory_safety/memory_safety_inner.cc
@@ -12,7 +12,8 @@ using namespace CHERI; -char *allocation = NULL; +char *allocation; +static char *volatile volatilePointer; extern "C" ErrorRecoveryBehaviour compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval) @@ -126,7 +127,7 @@ case MemorySafetyBugClass::StoreStackPointerToGlobal: { /* - * It's illegal to store a stack pointer to a global variable. + * Storing a stack pointer to a global variable makes it invalid. * This is enforced by the Global (G) permission bit in the * capability. * This provides strong thread-isolation guarantees: data stored @@ -136,11 +137,12 @@ char buf[0x10]; Debug::log("Trigger storing a stack pointer {} into global", Capability{buf}); - allocation = buf; - - Debug::Assert(false, - "Code after storing stack pointer into global should " - "be unreachable"); + volatilePointer = buf; + Capability tmp = volatilePointer; + Debug::log("tmp: {}", tmp); + Debug::Assert(!tmp.is_valid(), + "Stack pointer stored into global should be invalid"); + return tmp[0]; } }
diff --git a/scripts/run_clang_tidy_format.sh b/scripts/run_clang_tidy_format.sh index 2934c90..6b17baf 100755 --- a/scripts/run_clang_tidy_format.sh +++ b/scripts/run_clang_tidy_format.sh
@@ -35,7 +35,7 @@ # 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 /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 /build/ | grep -v microvium | grep -v FreeRTOS-Compat) SOURCES=$(find ${DIRECTORIES} -name '*.cc' | grep -v /build/ | grep -v third_party | grep -v arith64.c) echo Headers: ${HEADERS}
diff --git a/sdk/boards/sonata.json b/sdk/boards/sonata-0.2.json similarity index 87% rename from sdk/boards/sonata.json rename to sdk/boards/sonata-0.2.json index ad5cc7e..6faa442 100644 --- a/sdk/boards/sonata.json +++ b/sdk/boards/sonata-0.2.json
@@ -59,13 +59,20 @@ "number": 2, "priority": 3, "edge_triggered": true + }, + { + "name": "EthernetInterrupt", + "number": 47, + "priority": 3 } ], "defines" : [ "IBEX", "SUNBURST", "SUNBURST_SHADOW_BASE=0x30000000", - "SUNBURST_SHADOW_SIZE=0x4000" + "SUNBURST_SHADOW_SIZE=0x4000", + "ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM=1", + "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1" ], "driver_includes" : [ "../include/platform/sunburst",
diff --git a/sdk/boards/sonata-prerelease.json b/sdk/boards/sonata-prerelease.json new file mode 100644 index 0000000..d82e122 --- /dev/null +++ b/sdk/boards/sonata-prerelease.json
@@ -0,0 +1,163 @@ +{ + "devices": { + "shadow" : { + "start" : 0x30000000, + "end" : 0x30004000 + }, + "gpio" : { + "start" : 0x80000000, + "end" : 0x80000020 + }, + "clint": { + "start" : 0x80040000, + "end" : 0x80050000 + }, + "uart": { + "start" : 0x80100000, + "end" : 0x80100034 + }, + "uart1": { + "start" : 0x80101000, + "end" : 0x80101034 + }, + "uart2": { + "start" : 0x80102000, + "end" : 0x80102034 + }, + "uart3": { + "start" : 0x80103000, + "end" : 0x80103034 + }, + "uart4": { + "start" : 0x80104000, + "end" : 0x80104034 + }, + "i2c0": { + "start" : 0x80200000, + "end" : 0x80200080 + }, + "i2c1": { + "start" : 0x80201000, + "end" : 0x80201080 + }, + "spi0": { + "start" : 0x80300000, + "end" : 0x80301000 + }, + "spi1": { + "start" : 0x80301000, + "end" : 0x80302000 + }, + "spi2": { + "start" : 0x80302000, + "end" : 0x80303000 + }, + "rgbled" : { + "start" : 0x80009000, + "end" : 0x80009020 + }, + "revoker": { + "start": 0x8000A000, + "length": 0x1000 + }, + "plic": { + "start" : 0x88000000, + "end" : 0x88400000 + }, + "pwm": { + "start" : 0x80001000, + "length": 0x00001000 + }, + "adc": { + "start" : 0x8000B000, + "length": 0x00001000 + } + }, + "instruction_memory": { + "start": 0x00101000, + "end": 0x00140000 + }, + "heap": { + "end": 0x00140000 + }, + "revokable_memory_start": 0x00100000, + "defines" : [ + "IBEX", + "SUNBURST", + "SUNBURST_SHADOW_BASE=0x30000000", + "SUNBURST_SHADOW_SIZE=0x4000", + "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" : 30000000, + "tickrate_hz" : 100, + "revoker" : "hardware", + "stack_high_water_mark" : true, + "simulator" : "${sdk}/../scripts/run-sonata.sh", + "simulation": false, + "interrupts": [ + { + "name": "Uart0TxWatermark", + "number": 1, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart0RxWatermark", + "number": 2, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart0TxEmpty", + "number": 3, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart0RxOverflow", + "number": 4, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart1TxWatermark", + "number": 9, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart1RxWatermark", + "number": 10, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart1TxEmpty", + "number": 11, + "priority": 3, + "edge_triggered": true + }, + { + "name": "Uart1RxOverflow", + "number": 12, + "priority": 3, + "edge_triggered": true + }, + { + "name": "EthernetInterrupt", + "number": 47, + "priority": 3 + }, + { + "name": "RevokerInterrupt", + "number": 72, + "priority": 2 + } + ] +}
diff --git a/sdk/boards/sonata.json b/sdk/boards/sonata.json new file mode 120000 index 0000000..c487a3d --- /dev/null +++ b/sdk/boards/sonata.json
@@ -0,0 +1 @@ +sonata-0.2.json \ No newline at end of file
diff --git a/sdk/core/allocator/alloc.h b/sdk/core/allocator/alloc.h index 20ba2da..1e890ea 100644 --- a/sdk/core/allocator/alloc.h +++ b/sdk/core/allocator/alloc.h
@@ -1030,7 +1030,7 @@ */ [[nodiscard]] __always_inline auto hazard_list_begin() { - auto *lockWord{MMIO_CAPABILITY(uint32_t, allocator_epoch)}; + auto *lockWord{SHARED_OBJECT(uint32_t, allocator_epoch)}; uint32_t epoch = *lockWord >> 16; Debug::Invariant( (epoch & 1) == 0, @@ -1280,8 +1280,8 @@ { // It is now safe to walk the hazard list. Capability<void *> hazards = - const_cast<void **>(MMIO_CAPABILITY_WITH_PERMISSIONS( - void *, hazard_pointers, true, true, true, false)); + const_cast<void **>(SHARED_OBJECT_WITH_PERMISSIONS( + void *, allocator_hazard_pointers, true, false, true, false)); size_t pointers = hazards.length() / sizeof(void *); for (size_t i = 0; i < pointers; i++) {
diff --git a/sdk/core/allocator/main.cc b/sdk/core/allocator/main.cc index 75743ba..8f5776f 100644 --- a/sdk/core/allocator/main.cc +++ b/sdk/core/allocator/main.cc
@@ -88,8 +88,9 @@ Capability m{tbase.cast<MState>()}; size_t hazardQuarantineSize = - Capability{MMIO_CAPABILITY_WITH_PERMISSIONS( - void *, hazard_pointers, true, true, true, false)} + Capability{ + SHARED_OBJECT_WITH_PERMISSIONS( + void *, allocator_hazard_pointers, true, false, true, false)} .length(); m.bounds() = sizeof(*m);
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc index 16a5ee6..11cb2c2 100644 --- a/sdk/core/loader/boot.cc +++ b/sdk/core/loader/boot.cc
@@ -471,22 +471,18 @@ Debug::Invariant( ((entry.address >= LA_ABS(__mmio_region_start)) && (entry.address + entry.size() <= LA_ABS(__mmio_region_end))) || - ((entry.address == LA_ABS(__export_mem_allocator_epoch)) && - (entry.address + entry.size() == - LA_ABS(__export_mem_allocator_epoch_end))) || - ((entry.address == LA_ABS(__export_mem_hazard_pointers)) && - (entry.address + entry.size() == - LA_ABS(__export_mem_hazard_pointers_end))), - "{}--{} is not in the MMIO range ({}--{}) or the hazard pointer " - "range ({}--{}) or the allocator epoch range ({}--{})", + ((entry.address >= LA_ABS(__shared_objects_start)) && + (entry.address + entry.size() <= + LA_ABS(__shared_objects_end))), + "{}--{} is not in the MMIO range ({}--{}) or the shared object " + "range ({}--{})", entry.address, entry.address + entry.size(), LA_ABS(__mmio_region_start), LA_ABS(__mmio_region_end), - LA_ABS(__export_mem_hazard_pointers), - LA_ABS(__export_mem_hazard_pointers_end), - LA_ABS(__export_mem_allocator_epoch), - LA_ABS(__export_mem_allocator_epoch_end)); + LA_ABS(__shared_objects_start), + LA_ABS(__shared_objects_end)); + auto ret = build(entry.address, entry.size()); // Remove any permissions that shouldn't be held here. ret.permissions() &= entry.permissions(); @@ -782,9 +778,9 @@ Root::Type::RWGlobal, PermissionSet{Permission::Store, Permission::LoadStoreCapability}>( - LA_ABS(__export_mem_hazard_pointers), - LA_ABS(__export_mem_hazard_pointers_end) - - LA_ABS(__export_mem_hazard_pointers)); + LA_ABS(__cheriot_shared_object_allocator_hazard_pointers), + LA_ABS(__cheriot_shared_object_allocator_hazard_pointers_end) - + LA_ABS(__cheriot_shared_object_allocator_hazard_pointers)); // Space per thread for hazard pointers. static constexpr size_t HazardPointerSpace = HazardPointersPerThread * sizeof(void *);
diff --git a/sdk/firmware.ldscript.in b/sdk/firmware.ldscript.in index 82d57b3..9fc9a29 100644 --- a/sdk/firmware.ldscript.in +++ b/sdk/firmware.ldscript.in
@@ -107,27 +107,23 @@ } .allocator_globals_end = .; - . = ALIGN(8); - __export_mem_hazard_pointers = .; - # Two hazard pointers per thread - . += @thread_count@ * 8 * 2; - __export_mem_hazard_pointers_end = .; - # 32-bit counter for allocator epochs. - __export_mem_allocator_epoch = .; - . += 4; - __export_mem_allocator_epoch_end = .; - @software_revoker_globals@ @gdc_ld@ + __compart_cgps_end = .; + .sealed_objects : { @sealed_objects@ } - __compart_cgps_end = ALIGN(64); + __shared_objects_start = .; + @shared_objects@ + __shared_objects_end = .; + + . = ALIGN(64); # Everything after this point can be discarded after the loader has # finished.
diff --git a/sdk/include/FreeRTOS-Compat/FreeRTOS_errno.h b/sdk/include/FreeRTOS-Compat/FreeRTOS_errno.h new file mode 100644 index 0000000..535050d --- /dev/null +++ b/sdk/include/FreeRTOS-Compat/FreeRTOS_errno.h
@@ -0,0 +1,63 @@ +// Copyright SCI Semiconductor and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include <errno.h> + +/** + * This header defines errno values used by FreeRTOS+TCP. + * + * In the FreeRTOS core tree, this is included in `projdefs.h`. + * + * We modified it here to use the same errno codes are the CHERIoT core when + * possible. + */ + +#define pdFREERTOS_ERRNO_NONE 0 // No errors +#define pdFREERTOS_ERRNO_ENOENT ENOENT // No such file or directory +#define pdFREERTOS_ERRNO_EINTR EINTR // Interrupted system call +#define pdFREERTOS_ERRNO_EIO EIO // I/O error +#define pdFREERTOS_ERRNO_ENXIO ENXIO // No such device or address +#define pdFREERTOS_ERRNO_EBADF EBADF // Bad file number +#define pdFREERTOS_ERRNO_EAGAIN EAGAIN // No more processes +#define pdFREERTOS_ERRNO_EWOULDBLOCK EWOULDBLOCK // Operation would block +#define pdFREERTOS_ERRNO_ENOMEM ENOMEM // Not enough memory +#define pdFREERTOS_ERRNO_EACCES EACCES // Permission denied +#define pdFREERTOS_ERRNO_EFAULT EFAULT // Bad address +#define pdFREERTOS_ERRNO_EBUSY EBUSY // Mount device busy +#define pdFREERTOS_ERRNO_EEXIST EEXIST // File exists +#define pdFREERTOS_ERRNO_EXDEV EXDEV // Cross-device link +#define pdFREERTOS_ERRNO_ENODEV ENODEV // No such device +#define pdFREERTOS_ERRNO_ENOTDIR ENOTDIR // Not a directory +#define pdFREERTOS_ERRNO_EISDIR EISDIR // Is a directory +#define pdFREERTOS_ERRNO_EINVAL EINVAL // Invalid argument +#define pdFREERTOS_ERRNO_ENOSPC ENOSPC // No space left on device +#define pdFREERTOS_ERRNO_ESPIPE ESPIPE // Illegal seek +#define pdFREERTOS_ERRNO_EROFS EROFS // Read only file system +#define pdFREERTOS_ERRNO_EUNATCH EUNATCH // Protocol driver not attached +#define pdFREERTOS_ERRNO_EBADE EBADE // Invalid exchange +#define pdFREERTOS_ERRNO_EFTYPE EFTYPE // Inappropriate file type or format +#define pdFREERTOS_ERRNO_ENOTEMPTY ENOTEMPTY // Directory not empty +#define pdFREERTOS_ERRNO_ENAMETOOLONG ENAMETOOLONG // File or path name too long +#define pdFREERTOS_ERRNO_EOPNOTSUPP EOPNOTSUPP // Operation not supported on transport endpoint +#define pdFREERTOS_ERRNO_EAFNOSUPPORT EAFNOSUPPORT // Address family not supported by protocol +#define pdFREERTOS_ERRNO_ENOBUFS ENOBUFS // No buffer space available +#define pdFREERTOS_ERRNO_ENOPROTOOPT ENOPROTOOPT // Protocol not available +#define pdFREERTOS_ERRNO_EADDRINUSE EADDRINUSE // Address already in use +#define pdFREERTOS_ERRNO_ETIMEDOUT ETIMEDOUT // Connection timed out +#define pdFREERTOS_ERRNO_EINPROGRESS EINPROGRESS // Connection already in progress +#define pdFREERTOS_ERRNO_EALREADY EALREADY // Socket already connected +#define pdFREERTOS_ERRNO_EADDRNOTAVAIL EADDRNOTAVAIL // Address not available +#define pdFREERTOS_ERRNO_EISCONN EISCONN // Socket is already connected +#define pdFREERTOS_ERRNO_ENOTCONN ENOTCONN // Socket is not connected +#define pdFREERTOS_ERRNO_ENOMEDIUM ENOMEDIUM // No medium inserted +#define pdFREERTOS_ERRNO_EILSEQ EILSEQ // An invalid UTF-16 sequence was encountered +#define pdFREERTOS_ERRNO_ECANCELED ECANCELED // Operation canceled + +/** + * These errno codes are non-standard, assign them a code outside our errno + * range. + */ + +#define pdFREERTOS_ERRNO_ENMFILE (__ELASTERROR + 1) // No more files
diff --git a/sdk/include/c++-config/atomic b/sdk/include/c++-config/atomic index 412f09e..3a91934 100644 --- a/sdk/include/c++-config/atomic +++ b/sdk/include/c++-config/atomic
@@ -461,7 +461,7 @@ * methods. */ template<typename T> - class pointer_atomic : primitive_atomic<T> + class pointer_atomic : public primitive_atomic<T> { public: using primitive_atomic<T>::primitive_atomic;
diff --git a/sdk/include/compartment-macros.h b/sdk/include/compartment-macros.h index 8f20940..ef0a024 100644 --- a/sdk/include/compartment-macros.h +++ b/sdk/include/compartment-macros.h
@@ -6,26 +6,27 @@ #include <stdbool.h> /** - * Helper macro, should not be used directly. + * Helper macro for MMIO and pre-shared object imports, should not be used + * directly. */ -#define MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \ - name, \ - mangledName, \ - permitLoad, \ - permitStore, \ - permitLoadStoreCapabilities, \ - permitLoadMutable) \ +#define IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \ + name, \ + prefix, \ + mangledName, \ + permitLoad, \ + permitStore, \ + permitLoadStoreCapabilities, \ + permitLoadMutable) \ ({ \ - volatile type *ret; /* NOLINT(bugprone-macro-parentheses) */ \ + type *ret; /* NOLINT(bugprone-macro-parentheses) */ \ __asm(".ifndef " mangledName "\n" \ " .type " mangledName ",@object\n" \ " .section .compartment_imports." #name \ ",\"awG\",@progbits," #name ",comdat\n" \ " .globl " mangledName "\n" \ " .p2align 3\n" mangledName ":\n" \ - " .word __export_mem_" #name "\n" \ - " .word __export_mem_" #name "_end - __export_mem_" #name \ - " + %c1\n" \ + " .word " #prefix #name "\n" \ + " .word " #prefix #name "_end - " #prefix #name " + %c1\n" \ " .size " mangledName ", 8\n" \ " .previous\n" \ ".endif\n" \ @@ -42,6 +43,25 @@ }) /** + * Helper macro, should not be used directly. + */ +#define MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \ + name, \ + mangledName, \ + permitLoad, \ + permitStore, \ + permitLoadStoreCapabilities, \ + permitLoadMutable) \ + IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \ + name, \ + __export_mem_, \ + mangledName, \ + permitLoad, \ + permitStore, \ + permitLoadStoreCapabilities, \ + permitLoadMutable) + +/** * Provide a capability of the type `volatile type *` referring to the MMIO * region exported in the linker script with `name` as its name. This macro * can be used only in code (it cannot be used to initialise a global). @@ -57,7 +77,7 @@ permitLoadStoreCapabilities, \ permitLoadMutable) \ MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER( \ - type, \ + volatile type, /* NOLINT(bugprone-macro-parentheses) */ \ name, \ "__import_mem_" #name "_" #permitLoad "_" #permitStore \ "_" #permitLoadStoreCapabilities "_" #permitLoadMutable, \ @@ -79,6 +99,44 @@ MMIO_CAPABILITY_WITH_PERMISSIONS(type, name, true, true, false, false) /** + * Provide a capability of the type `type *` referring to the pre-shared object + * with `name` as its name. This macro can be used only in code (it cannot be + * used to initialise a global). + * + * The last arguments specify the set of permissions that this capability + * holds. Pre-shared objects are always global and without store local. They + * may optionally omit additional permissions. + */ +#define SHARED_OBJECT_WITH_PERMISSIONS(type, \ + name, \ + permitLoad, \ + permitStore, \ + permitLoadStoreCapabilities, \ + permitLoadMutable) \ + IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER( \ + type, /* NOLINT(bugprone-macro-parentheses) */ \ + name, \ + __cheriot_shared_object_, \ + "__import_cheriot_shared_object_" #name "_" #permitLoad "_" #permitStore \ + "_" #permitLoadStoreCapabilities "_" #permitLoadMutable, \ + permitLoad, \ + permitStore, \ + permitLoadStoreCapabilities, \ + permitLoadMutable) + +/** + * Provide a capability of the type `type *` referring to the pre-shared object + * with `name` as its name. This macro can be used only in code (it cannot be + * used to initialise a global). + * + * Pre-shared object capabilities produced by this macro have load, store, + * load-mutable, and load/store-capability permissions. To define a reduced + * set of permissions use `SHARED_OBJECT_WITH_PERMISSIONS`. + */ +#define SHARED_OBJECT(type, name) \ + SHARED_OBJECT_WITH_PERMISSIONS(type, name, true, true, true, true) + +/** * Macro to test whether a device with a specific name exists in the board * definition for the current target. */
diff --git a/sdk/include/errno.h b/sdk/include/errno.h index df74785..07cf199 100644 --- a/sdk/include/errno.h +++ b/sdk/include/errno.h
@@ -46,6 +46,8 @@ #define ENOMSG 42 // No message of the desired type. #define EIDRM 43 // Identifier removed. #define EDEADLK 45 // Resource deadlock would occur. +#define EUNATCH 49 // Protocol driver not attached. +#define EBADE 52 // Invalid exchange. #define ENOSTR 60 // Not a STREAM. #define ENODATA 61 // No data available. #define ETIME 62 // Timer expired. @@ -54,6 +56,7 @@ #define EPROTO 71 // Protocol error. #define EMULTIHOP 72 // Reserved. #define EBADMSG 74 // Bad message. +#define EFTYPE 79 // Inappropriate file type or format. #define EILSEQ 84 // Illegal byte sequence. #define ENOTSOCK 88 // Not a socket. #define EDESTADDRREQ 89 // Destination address required. @@ -80,6 +83,7 @@ #define EINPROGRESS 115 // Operation in progress. #define ESTALE 116 // Reserved. #define EDQUOT 122 // Reserved. +#define ENOMEDIUM 123 // No medium inserted. #define ECANCELED 125 // Operation canceled. #define EOWNERDEAD 130 // Previous owner died. #define ENOTRECOVERABLE 131 // State not recoverable.
diff --git a/sdk/include/platform/concepts/ethernet.hh b/sdk/include/platform/concepts/ethernet.hh index 30ecc2f..700bb82 100644 --- a/sdk/include/platform/concepts/ethernet.hh +++ b/sdk/include/platform/concepts/ethernet.hh
@@ -57,6 +57,13 @@ {adaptor.mac_address_set()}; /** + * Check if PHY link is up. + */ + { + adaptor.phy_link_status() + } -> std::convertible_to<bool>; + + /** * Receive a frame. Returns an optional value (convertible to bool) that * has a length and a buffer. The return value owns the buffer for its * lifetime.
diff --git a/sdk/include/platform/sunburst/platform-adc.hh b/sdk/include/platform/sunburst/platform-adc.hh new file mode 100644 index 0000000..242576c --- /dev/null +++ b/sdk/include/platform/sunburst/platform-adc.hh
@@ -0,0 +1,353 @@ +#pragma once +#include <debug.hh> +#include <stdint.h> +#include <utils.hh> + +/** + * A simple driver for Sonata's XADC (Xilinx Analogue to Digital Converter). + * + * Documentation source can be found at: + * https://github.com/lowRISC/sonata-system/blob/97a525c48f7bf051b999d0178dba04859819bc5e/doc/ip/adc.md + * + * Rendered documentation is served from: + * https://lowrisc.github.io/sonata-system/doc/ip/adc.html + */ +class SonataAnalogueDigitalConverter : private utils::NoCopyNoMove +{ + /** + * Flag to set when debugging the driver for UART log messages. + */ + static constexpr bool DebugDriver = true; + + /** + * Helper for conditional debug logs and assertions. + */ + using Debug = ConditionalDebug<DebugDriver, "ADC">; + + /** + * Results of measurements / analogue conversions are stored in Dynamic + * Reconfiguration Port (DRP) status registers as most-significant-bit + * justified 12 bit values, represented by this mask. + */ + static constexpr uint16_t MeasurementMask = 0xFFF0; + + /** + * The location (offset) of the Xilinx Analogue-to-Digital Converter's + * Dynamic Reconfiguration Port (DRP) registers, which are 16-bit registers + * that are sequentially mapped to memory in 4-byte (word) intervals, in the + * lower 2 bytes of each word. This includes both status (read-only) and + * control (read/write) registers. + * + * https://docs.amd.com/r/en-US/ug480_7Series_XADC/XADC-Register-Interface + */ + enum class RegisterOffset : uint8_t + { + Temperature = 0x00, + VoltageInternalSupply = 0x01, + VoltageAuxiliarySupply = 0x02, + VoltageDedicated = 0x03, + VoltageInternalPositiveReference = 0x04, + VoltageInternalNegativeReference = 0x05, + VoltageBlockRamSupply = 0x06, + + /* Offset 0x07 is undefined. */ + + /* ADC A's calibration coefficient status registers omitted. */ + + /* Offsets 0x0B and 0x0C are undefined. */ + + /* Zynq-700 SoC-specific voltage status registers omitted. */ + + VoltageAuxiliary0 = 0x10, + VoltageAuxiliary1 = 0x11, + VoltageAuxiliary2 = 0x12, + VoltageAuxiliary3 = 0x13, + VoltageAuxiliary4 = 0x14, + VoltageAuxiliary5 = 0x15, + VoltageAuxiliary6 = 0x16, + VoltageAuxiliary7 = 0x17, + VoltageAuxiliary8 = 0x18, + VoltageAuxiliary9 = 0x19, + VoltageAuxiliary10 = 0x1A, + VoltageAuxiliary11 = 0x1B, + VoltageAuxiliary12 = 0x1C, + VoltageAuxiliary13 = 0x1D, + VoltageAuxiliary14 = 0x1E, + VoltageAuxiliary15 = 0x1F, + + /* Maximum & minimum sensor measurement status registers omitted. */ + + /* Offsets 0x2B and 0x2F are undefined. */ + + /* ADC B's calibration coefficient status registers omitted. */ + + /* Offsets 0x33 to 0x3E are undefined. */ + + FlagRegister = 0x3F, + ConfigRegister0 = 0x40, + ConfigRegister1 = 0x41, + ConfigRegister2 = 0x42, + + /* 0x43 to 0x47 are factory test registers and so are omitted. */ + + /* Sequence and Alarm control registers are emitted. */ + }; + + /** + * Definitions of fields (and their locations) within the Xilinx + * Analogue-to-digital Converter's Config Register 2 (offset 0x42). + * + * https://docs.amd.com/r/en-US/ug480_7Series_XADC/Control-Registers?section=XREF_53021_Configuration + */ + enum ConfigRegister2Field : uint16_t + { + /* Bits 0-3 are invalid and should not be interacted with. */ + + /** + * Power-down bits for the Analogue-to-Digital Converter. + */ + PowerDownMask = 0x3 << 4, + + /* Bits 6-7 are invalid and should not be interacted with. */ + + /** + * Bits used to select the division ratio between the Dynamic + * Reconfiguration Port clock (DCLK) and the lower frequency + * Analogue-to- Digital Converter Clock (ADCCLK). Values of 0 and 1 are + * mapped to a divider of 2 by the DCLK divider selection specification. + * All other values are mapped identically; the minimum division ratio + * is 2. + */ + ClockDividerMask = 0xFF << 8, + }; + + /** + * A helper that returns a pointer to the Analogue-to-Digital Converter + * (ADC)'s memory, which is mapped to the Dynamic Reconfiguration Port (DRP) + * registers used by the ADC. + */ + [[nodiscard, gnu::always_inline]] volatile uint32_t *registers() const + { + return MMIO_CAPABILITY(uint32_t, adc); + } + + /** + * Read the contents of a Dynamic Reconfiguration Port (DRP) register from + * memory. DRP registers are 16 bits wide but are mapped sequentially in + * memory, using 4 bytes per register, with the relevant data written in the + * lower 16 bits. + * + * The single argument is the register to read the value of. + */ + [[nodiscard]] uint16_t register_read(RegisterOffset reg) const + { + uint8_t registerOffset = static_cast<uint8_t>(reg); + return static_cast<uint16_t>(registers()[registerOffset]); + } + + /** + * Write to the contents of a Dynamic Reconfiguration Port (DRP) register in + * memory. DRP Registers are 16 bits wide but are mapped sequentially in + * memory, using 4 bytes per register, with the relevant data written to the + * lower 16 bits. + * + * The first argument is the register to write to. + * The second argument is the value to write to the register. + */ + void register_write(RegisterOffset reg, uint16_t value) const + { + uint8_t registerOffset = static_cast<uint8_t>(reg); + registers()[registerOffset] = static_cast<uint32_t>(value); + } + + /** + * Sets the relevant bits of a Dynamic Reconfiguration Port (DRP) register + * in memory. This acts like `register_write`, but additionally takes a + * mask so that it only overwrites specified bits of the register, and + * retains the value of all unselected bits. + * + * The first argument is the register to write to. + * The second argument is the mask of bits in the register to write to. + * The third argument is the values that will be written to the register + * according to the mask. + */ + void register_set_bits(RegisterOffset reg, + uint16_t bitMask, + uint16_t bitValues) const + { + uint16_t registerBits = register_read(reg); + registerBits &= ~bitMask; /* Clear bits in the mask. */ + registerBits |= bitMask & bitValues; /* Set values of masked bits. */ + return register_write(reg, registerBits); + } + + public: + /** + * Represents the offsets of the Xilinx Analogue-to-Digital Converter's + * (XADC) Dynamic Reconfiguration Port (DRP) status registers that are used + * to store values that are measured/sampled by the XADC, forming a mapping + * that provides a more comprehensible interface. + * + * https://lowrisc.github.io/sonata-system/doc/ip/adc.html + */ + enum class MeasurementRegister : uint8_t + { + ArduinoA0 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary4), + ArduinoA1 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary12), + ArduinoA2 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary5), + ArduinoA3 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary13), + ArduinoA4 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary6), + ArduinoA5 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary14), + Temperature = static_cast<uint8_t>(RegisterOffset::Temperature), + VoltageInternalSupply = + static_cast<uint8_t>(RegisterOffset::VoltageInternalSupply), + VoltageAuxiliarySupply = + static_cast<uint8_t>(RegisterOffset::VoltageAuxiliarySupply), + VoltageInternalReferencePositive = static_cast<uint8_t>( + RegisterOffset::VoltageInternalPositiveReference), + VoltageInternalReferenceNegative = static_cast<uint8_t>( + RegisterOffset::VoltageInternalNegativeReference), + VoltageBlockRamSupply = + static_cast<uint8_t>(RegisterOffset::VoltageBlockRamSupply), + }; + + /** + * Possible power down modes that can be set in Config Register 2. + * + * https://docs.amd.com/r/en-US/ug480_7Series_XADC/Control-Registers?section=XREF_93518_Power_Down + */ + enum class PowerDownMode : uint8_t + { + None = 0b00, /* Default */ + /* 0b01 is not valid, and should not be selected. */ + ConverterB = 0b10, + BothConverters = 0b11, + }; + + /** + * The Xilinx Analogue-to-Digtal Converter can sample at a maximum rate of 1 + * Megasample per second. It uses 16 bit Dynamic Reconfiguration Port (DRP) + * registers, and stores 12-bit measurements, which are stored most- + * significant-bit justified. + * + * https://docs.amd.com/r/en-US/ug480_7Series_XADC/XADC-Overview + */ + static constexpr size_t MaxSamples = 1 * 1000 * 1000; + static constexpr size_t RegisterSize = 16; + static constexpr size_t MeasurementBitWidth = 12; + + /** + * The minimum permissible Analogue-to-Digital Clock speed is 1 MHz, and the + * maximum is 26 MHz, as per the data sheet. + * + * See table 65 on page 57: + * https://docs.amd.com/v/u/en-US/ds181_Artix_7_Data_Sheet + */ + static constexpr size_t MinClockFrequencyHz = 1 * 1000 * 1000; + static constexpr size_t MaxClockFrequencyHz = 26 * 1000 * 1000; + + /** + * A clock divider value to be used by the Xilinx Analogue-to-Digital + * Converter (XADC), which divides its input clock (the system clock) + * by this divider to determine the clock of the XADC. This clock + * must fall between specified maximum and minimum frequency, and the + * divider must be an 8-bit integer, greater than 2. + */ + typedef uint8_t ClockDivider; + + /** + * Constructor - initialises the MMIO capability for the Analogue-to- + * Digital converter, and then sets its clock divider and power down + * mode. + * + * Takes the clock divider and power down modes to initialise. The + * clock divider should divide the system clock to create a signal + * between 1 and 26 MHz, and should be at least 2. + */ + SonataAnalogueDigitalConverter(ClockDivider divider, + PowerDownMode powerDown) + { + set_clock_divider(divider); + set_power_down(powerDown); + /* The analogue-to-digital converter starts up in independent ADC mode + by default, monitoring all channels, and so no further initialisation + logic is needed. */ + } + + /** + * Constructor, without any manual setting of the clock divider. Initialises + * the MMIO capability for the Analogue-to-Digtal converter, and then sets + * its power down mode. + * + * Takes the power down mode to initialise. + */ + SonataAnalogueDigitalConverter(PowerDownMode powerDown) + { + set_power_down(powerDown); + /* The Analogue-to-digital converter starts up in independent ADC mode + by default, monitoring all channels, and so no further initialisation + logic is needed. */ + } + + /** + * Sets the clock divider for the Sonata Analogue-to-Digital Converter + * (ADC), which is used to divide the system clock to create the ADC clock. + * + * The clock divider to use is provided as the single argument. It must be + * such that it creates a signal between 1 and 26 MHz, and should be at + * least 2. + */ + void set_clock_divider(ClockDivider divider) + { + Debug::Assert((divider >= 2), "The ADC divider must be at least 2"); + Debug::Assert( + (CPU_TIMER_HZ / divider >= MinClockFrequencyHz), + "The given divider causes the ADC clock to underclock its minimum."); + Debug::Assert( + (CPU_TIMER_HZ / divider <= MaxClockFrequencyHz), + "The given divider causes the ADC clock to overclock its maximum."); + register_set_bits( + RegisterOffset::ConfigRegister2, ClockDividerMask, (divider << 8)); + } + + /** + * Sets the power down configuration for the Sonata Analogue-to-Digital + * Converter (ADC). Calling this function allows either ADC B or the entire + * XADC to be **permanently** powered down. + * + * The power down mode to set is provided as an argument. + */ + void set_power_down(PowerDownMode powerdown) + { + register_set_bits(RegisterOffset::ConfigRegister2, + PowerDownMask, + (static_cast<uint16_t>(powerdown) << 4)); + } + + /** + * Reads the most recent output of the analogue-to-digital converter's + * measurements from a specified status register, corresponding to + * measurement of some channel. This currently assumes unipolar operation, + * which means all values are positive. + * + * The status register to read the last measurement of is given as the + * single argument. + * + * The output values can be translated to the relevant units being measured + * by using the transfer functions defined in documentation: + * https://docs.amd.com/r/en-US/ug480_7Series_XADC/ADC-Transfer-Functions + */ + [[nodiscard]] int16_t read_last_measurement(MeasurementRegister reg) const + { + uint16_t measurement = register_read(static_cast<RegisterOffset>(reg)); + measurement &= MeasurementMask; + measurement >>= (RegisterSize - MeasurementBitWidth); + + /* Currently just simple logic that assumes a unipolar analogue + measurement: to extend support to bipolar measurement, a mechanism for + tracking whether a measurement is bipolar or not must be introduced, and + if so, then the negative 12-bit two's complement values must be sign + extended first. */ + return static_cast<int16_t>(measurement); + } +};
diff --git a/sdk/include/platform/sunburst/platform-entropy.hh b/sdk/include/platform/sunburst/platform-entropy.hh new file mode 100644 index 0000000..85b97bc --- /dev/null +++ b/sdk/include/platform/sunburst/platform-entropy.hh
@@ -0,0 +1,65 @@ +#pragma once +#include <compartment-macros.h> +#include <ds/xoroshiro.h> +#include <interrupt.h> +#include <platform/concepts/entropy.h> +#include <riscvreg.h> + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(EthernetInterruptEntropy, + InterruptName::EthernetInterrupt, + true, + false) + +/** + * A simple entropy source. This wraps a few weak entropy sources to seed a + * PRNG. It is absolutely not secure and should not be used for anything that + * depends on cryptographically secure random numbers! Unfortunately, there is + * nothing on the Sonata instantiation of CHERIoT SAFE that can be used as a + * secure entropy source. + */ +class EntropySource +{ + ds::xoroshiro::P128R64 prng; + + public: + using ValueType = uint64_t; + + /// Definitely not secure! + static constexpr bool IsSecure = false; + + /// Constructor, tries to generate an independent sequence of random numbers + EntropySource() + { + reseed(); + } + + /// Reseed the PRNG + void reseed() + { + // Start from a not very random seed + uint64_t seed = rdcycle64(); + prng.set_state(seed, seed >> 24); + uint32_t interrupts = + *interrupt_futex_get(STATIC_SEALED_VALUE(EthernetInterruptEntropy)); + // Permute it with another not-very-random number + for (uint32_t i = 0; i < ((interrupts & 0xff00) >> 8); i++) + { + prng.long_jump(); + } + for (uint32_t i = 0; i < (interrupts & 0xff); i++) + { + prng.jump(); + } + // At this point, our random number is in a fairly predictable state, + // but with a fairly low probability of being the same predictable + // state as before. + } + + ValueType operator()() + { + return prng(); + } +}; + +static_assert(IsEntropySource<EntropySource>, + "EntropySource must be an entropy source");
diff --git a/sdk/include/platform/sunburst/platform-ethernet.hh b/sdk/include/platform/sunburst/platform-ethernet.hh new file mode 100644 index 0000000..0e9b3a7 --- /dev/null +++ b/sdk/include/platform/sunburst/platform-ethernet.hh
@@ -0,0 +1,824 @@ +#pragma once +#include <array> +#include <cheri.hh> +#include <cstddef> +#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/platform-spi.hh> +#include <thread.h> +#include <type_traits> + +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_fast(&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/platform-i2c.hh b/sdk/include/platform/sunburst/platform-i2c.hh index 9628a43..2332a8a 100644 --- a/sdk/include/platform/sunburst/platform-i2c.hh +++ b/sdk/include/platform/sunburst/platform-i2c.hh
@@ -394,7 +394,7 @@ /// Clears the given interrupt. void interrupt_clear(OpenTitanI2cInterrupt interrupt) volatile { - interruptState = interruptState & ~interrupt_bit(interrupt); + interruptState = interrupt_bit(interrupt); } /// Enables the given interrupt.
diff --git a/sdk/include/platform/sunburst/platform-pwm.hh b/sdk/include/platform/sunburst/platform-pwm.hh new file mode 100644 index 0000000..60f1704 --- /dev/null +++ b/sdk/include/platform/sunburst/platform-pwm.hh
@@ -0,0 +1,68 @@ +#pragma once +#include <debug.hh> +#include <stdint.h> + +/** + * A driver for Sonata's Pulse-Width Modulation (PWM). + * + * Documentation source can be found at: + * https://github.com/lowRISC/sonata-system/blob/97a525c48f7bf051b999d0178dba04859819bc5e/doc/ip/pwm.md + * + * Rendered documentation is served from: + * https://lowrisc.github.io/sonata-system/doc/ip/pwm.html + */ +struct SonataPulseWidthModulation +{ + /** + * 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, "PWM">; + + /** + * The number of pulse-width modulated outputs that are available. + */ + static constexpr size_t OutputCount = 1; + + /** + * The pulse-width modulation outputs available on Sonata. + */ + struct OutputRegisters + { + /** + * The duty cycle of the wave, represented as a width counter. That + * is, the number of clock cycles for which the signal will be on. The + * duty cycle as a percentage is (duty cycle / period) * 100. + */ + uint32_t dutyCycle; + + /** + * The period (width) of the output block wave, set with the number of + * clock cycles that one period should last. The maximum period is 255 + * as only an 8 bit counter is being used. + */ + uint32_t period; + } outputs[OutputCount]; + + /* + * Sets the output of a specified pulse-width modulated output. + * + * The first argument is the index of the output. The second argument is + * the period (length) of the output wave represented as a counter of + * system clock cycles. The third argument is the number of clock cycles + * for which a high pulse is sent within that period. + * + * So for example `output_set(0, 200, 31)` should set a 15.5% output. + */ + void output_set(uint32_t index, uint8_t period, uint8_t dutyCycle) volatile + { + Debug::Assert(index < OutputCount, "Specified PWM is out of range"); + Debug::Assert(dutyCycle <= period, "Duty cycle cannot exceed 100%"); + outputs[index].period = period; + outputs[index].dutyCycle = dutyCycle; + } +};
diff --git a/sdk/include/platform/sunburst/platform-uart.hh b/sdk/include/platform/sunburst/platform-uart.hh index 0f6d46d..a9acf85 100644 --- a/sdk/include/platform/sunburst/platform-uart.hh +++ b/sdk/include/platform/sunburst/platform-uart.hh
@@ -13,22 +13,20 @@ * Rendered register documentation is served at: * https://opentitan.org/book/hw/ip/uart/doc/registers.html */ -template<unsigned DefaultBaudRate = 115'200> -class OpenTitanUart +struct OpenTitanUart { - public: /** * Interrupt State Register. */ - uint32_t intrState; + uint32_t interruptState; /** * Interrupt Enable Register. */ - uint32_t intrEnable; + uint32_t interruptEnable; /** * Interrupt Test Register. */ - uint32_t intrTest; + uint32_t interruptTest; /** * Alert Test Register (unused). */ @@ -36,7 +34,7 @@ /** * Control Register. */ - uint32_t ctrl; + uint32_t control; /** * Status Register. */ @@ -44,11 +42,11 @@ /** * UART Read Data. */ - uint32_t rData; + uint32_t readData; /** * UART Write Data. */ - uint32_t wData; + uint32_t writeData; /** * UART FIFO Control Register. */ @@ -58,38 +56,197 @@ */ uint32_t fifoStatus; /** - * TX Pin Override Control. + * Transmit Pin Override Control. * - * Gives direct SW control over TX pin state. + * Gives direct software control over the transmit pin state. */ - uint32_t ovrd; + uint32_t override; /** * UART Oversampled Values. */ - uint32_t val; + uint32_t values; /** - * UART RX Timeout Control. + * UART Receive Timeout Control. */ - uint32_t timeoutCtrl; + uint32_t timeoutControl; - void init(unsigned baudRate = DefaultBaudRate) volatile + /// 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; + + /// FIFO Control Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Reset the transmit FIFO. + FifoControlTransmitReset = 1 << 1, + /// Reset the receive FIFO. + FifoControlReceiveReset = 1 << 0, + }; + + /// Control Register Fields + enum : uint32_t { - // NCO = 2^20 * baud rate / cpu frequency - const uint32_t NCO = + /// Sets the BAUD clock rate from the numerically controlled oscillator. + ControlNco = 0xff << 16, + /// Set the number of character times the line must be low + /// which will be interpreted as a break. + ControlReceiveBreakLevel = 0b11 << 8, + /// When set, odd parity is used, otherwise even parity is used. + ControlParityOdd = 1 << 7, + /// Enable party on both transmit and receive lines. + ControlParityEnable = 1 << 6, + /// When set, incoming received bits are forwarded to the transmit line. + ControlLineLoopback = 1 << 5, + /// When set, outgoing transmitted bits are routed back the receiving + /// line. + ControlSystemLoopback = 1 << 4, + /// Enable the noise filter on the receiving line. + ControlNoiseFilter = 1 << 2, + /// Enable receiving bits. + ControlReceiveEnable = 1 << 1, + /// Enable transmitting bits. + ControlTransmitEnable = 1 << 0, + }; + + /// The encoding for different transmit watermark levels. + enum class TransmitWatermark + { + Level1 = 0x0, + Level2 = 0x1, + Level4 = 0x2, + Level8 = 0x3, + Level16 = 0x4, + }; + + /// The encoding for different receive watermark levels. + enum class ReceiveWatermark + { + Level1 = 0x0, + Level2 = 0x1, + Level4 = 0x2, + Level8 = 0x3, + Level16 = 0x4, + Level32 = 0x5, + Level64 = 0x6, + }; + + /** + * Configure parity. + * + * When `enableParity` is set, parity will be enabled. + * When `oddParity` is set, the odd parity will be used. + */ + void parity(bool enableParity = true, bool oddParity = false) volatile + { + control = (control & ~(ControlParityEnable | ControlParityOdd)) | + (enableParity ? ControlParityEnable : 0) | + (oddParity ? ControlParityOdd : 0); + } + + /** + * Configure loopback. + * + * When `systemLoopback` is set, outgoing transmitted bits are routed back + * the receiving line. When `lineLoopback` is set, incoming received bits + * are forwarded to the transmit line. + */ + void loopback(bool systemLoopback = true, + bool lineLoopback = false) volatile + { + control = (control & ~(ControlSystemLoopback | ControlLineLoopback)) | + (systemLoopback ? ControlSystemLoopback : 0) | + (lineLoopback ? ControlLineLoopback : 0); + } + + /// Clears the contents of the receive and transmit FIFOs. + void fifos_clear() volatile + { + fifoCtrl = (fifoCtrl & ~0b11) | FifoControlTransmitReset | + FifoControlReceiveReset; + } + + /** + * Sets the level transmit watermark. + * + * When the number of bytes in the transmit FIFO reach this level, + * the transmit watermark interrupt will fire. + */ + void transmit_watermark(TransmitWatermark level) volatile + { + fifoCtrl = static_cast<uint32_t>(level) << 5 | (fifoCtrl & 0x1f); + } + + /** + * Sets the level receive watermark. + * + * When the number of bytes in the receive FIFO reach this level, + * the receive watermark interrupt will fire. + */ + void receive_watermark(ReceiveWatermark level) volatile + { + fifoCtrl = static_cast<uint32_t>(level) << 5 | (fifoCtrl & 0b11100011); + } + + /// Enable the given interrupt. + void interrupt_enable(OpenTitanUartInterrupt interrupt) volatile + { + interruptEnable = interruptEnable | interrupt; + } + + /// Disable the given interrupt. + void interrupt_disable(OpenTitanUartInterrupt interrupt) volatile + { + interruptEnable = interruptEnable & ~interrupt; + } + + void init(unsigned baudRate = 115'200) volatile + { + // Nco = 2^20 * baud rate / cpu frequency + const uint32_t Nco = ((static_cast<uint64_t>(baudRate) << 20) / CPU_TIMER_HZ); // Set the baud rate and enable transmit & receive - ctrl = (NCO << 16) | 0b11; - }; + control = (Nco << 16) | ControlTransmitEnable | ControlReceiveEnable; + } + + [[gnu::always_inline]] uint16_t transmit_fifo_level() volatile + { + return fifoStatus & 0xff; + } + + [[gnu::always_inline]] uint16_t receive_fifo_level() volatile + { + return ((fifoStatus >> 16) & 0xff); + } bool can_write() volatile { - return (fifoStatus & 0xff) < 32; - }; + return transmit_fifo_level() < 32; + } bool can_read() volatile { - return ((fifoStatus >> 16) & 0xff) > 0; - }; + return receive_fifo_level() > 0; + } /** * Write one byte, blocking until the byte is written. @@ -97,7 +254,7 @@ void blocking_write(uint8_t byte) volatile { while (!can_write()) {} - wData = byte; + writeData = byte; } /** @@ -106,11 +263,11 @@ uint8_t blocking_read() volatile { while (!can_read()) {} - return rData; + return readData; } }; #ifndef CHERIOT_PLATFORM_CUSTOM_UART -using Uart = OpenTitanUart<>; +using Uart = OpenTitanUart; static_assert(IsUart<Uart>); #endif
diff --git a/sdk/include/stdalign.h b/sdk/include/stdalign.h new file mode 100644 index 0000000..65b5191 --- /dev/null +++ b/sdk/include/stdalign.h
@@ -0,0 +1,27 @@ +#pragma once +// SPDX-License-Identifier: MIT +// Copyright CHERIoT Contributors + +/** + * This header is part of C11 (and supported for compatibility in older + * versions) but is gone in C23 because the C keywords matching their C++ + * equivalents were added. + */ +#ifdef __STDC_VERSION__ +# if __STDC_VERSION__ < 202311L + +/** + * C++-compatible spelling for `_Alignas`. + */ +# define alignas(__x) _Alignas(__x) + +/** + * C++-compatible spelling for `_Alignof`. + */ +# define alignof(__x) _Alignof(__x) + +# define __alignas_is_defined 1 +# define __alignof_is_defined 1 + +# endif +#endif
diff --git a/sdk/include/stdint.h b/sdk/include/stdint.h index 03c5af0..1b582f3 100644 --- a/sdk/include/stdint.h +++ b/sdk/include/stdint.h
@@ -13,6 +13,8 @@ typedef __UINT_FAST8_TYPE__ uint_fast8_t; #define UINT8_C(x) __constant_integer_suffix(x, __UINT8_C_SUFFIX__) #define UINT8_MAX __UINT8_MAX__ +#define UINT_LEAST8_MAX __UINT_LEAST8_MAX__ +#define UINT_FAST8_MAX __UINT_FAST8_MAX__ typedef __INT8_TYPE__ int8_t; typedef __INT_LEAST8_TYPE__ int_least8_t; @@ -20,12 +22,18 @@ #define INT8_C(x) __constant_integer_suffix(x, __INT8_C_SUFFIX__) #define INT8_MAX __INT8_MAX__ #define INT8_MIN ((-INT8_C(INT8_MAX)) - 1) +#define INT_LEAST8_MIN __INT_LEAST8_MIN__ +#define INT_FAST8_MIN __INT_FAST8_MIN__ +#define INT_LEAST8_MAX __INT_LEAST8_MAX__ +#define INT_FAST8_MAX __INT_FAST8_MAX__ typedef __UINT16_TYPE__ uint16_t; typedef __UINT_LEAST16_TYPE__ uint_least16_t; typedef __UINT_FAST16_TYPE__ uint_fast16_t; #define UINT16_C(x) __constant_integer_suffix(x, __UINT16_C_SUFFIX__) #define UINT16_MAX __UINT16_MAX__ +#define UINT_LEAST16_MAX __UINT_LEAST16_MAX__ +#define UINT_FAST16_MAX __UINT_FAST16_MAX__ typedef __INT16_TYPE__ int16_t; typedef __INT_LEAST16_TYPE__ int_least16_t; @@ -33,12 +41,18 @@ #define INT16_C(x) __constant_integer_suffix(x, __INT16_C_SUFFIX__) #define INT16_MAX __INT16_MAX__ #define INT16_MIN ((-INT16_C(INT16_MAX)) - 1) +#define INT_LEAST16_MIN __INT_LEAST16_MIN__ +#define INT_FAST16_MIN __INT_FAST16_MIN__ +#define INT_LEAST16_MAX __INT_LEAST16_MAX__ +#define INT_FAST16_MAX __INT_FAST16_MAX__ typedef __UINT32_TYPE__ uint32_t; typedef __UINT_LEAST32_TYPE__ uint_least32_t; typedef __UINT_FAST32_TYPE__ uint_fast32_t; #define UINT32_C(x) __constant_integer_suffix(x, __UINT32_C_SUFFIX__) #define UINT32_MAX __UINT32_MAX__ +#define UINT_LEAST32_MAX __UINT_LEAST32_MAX__ +#define UINT_FAST32_MAX __UINT_FAST32_MAX__ typedef __INT32_TYPE__ int32_t; typedef __INT_LEAST32_TYPE__ int_least32_t; @@ -46,12 +60,18 @@ #define INT32_C(x) __constant_integer_suffix(x, __INT32_C_SUFFIX__) #define INT32_MAX __INT32_MAX__ #define INT32_MIN ((-INT32_C(INT32_MAX)) - 1) +#define INT_LEAST32_MIN __INT_LEAST32_MIN__ +#define INT_FAST32_MIN __INT_FAST32_MIN__ +#define INT_LEAST32_MAX __INT_LEAST32_MAX__ +#define INT_FAST32_MAX __INT_FAST32_MAX__ typedef __UINT64_TYPE__ uint64_t; typedef __UINT_LEAST64_TYPE__ uint_least64_t; typedef __UINT_FAST64_TYPE__ uint_fast64_t; #define UINT64_C(x) __constant_integer_suffix(x, __UINT64_C_SUFFIX__) #define UINT64_MAX __UINT64_MAX__ +#define UINT_LEAST64_MAX __UINT_LEAST64_MAX__ +#define UINT_FAST64_MAX __UINT_FAST64_MAX__ typedef __INT64_TYPE__ int64_t; typedef __INT_LEAST64_TYPE__ int_least64_t; @@ -59,6 +79,10 @@ #define INT64_C(x) __constant_integer_suffix(x, __INT64_C_SUFFIX__) #define INT64_MAX __INT64_MAX__ #define INT64_MIN ((-INT64_C(INT64_MAX)) - 1) +#define INT_LEAST64_MIN __INT_LEAST64_MIN__ +#define INT_FAST64_MIN __INT_FAST64_MIN__ +#define INT_LEAST64_MAX __INT_LEAST64_MAX__ +#define INT_FAST64_MAX __INT_FAST64_MAX__ typedef __UINTMAX_TYPE__ uintmax_t; #define UINTMAX_C(x) __constant_integer_suffix(x, __UINTMAX_C_SUFFIX__)
diff --git a/sdk/include/stdnoreturn.h b/sdk/include/stdnoreturn.h new file mode 100644 index 0000000..7a372e9 --- /dev/null +++ b/sdk/include/stdnoreturn.h
@@ -0,0 +1,19 @@ +#pragma once +// SPDX-License-Identifier: MIT +// Copyright CHERIoT Contributors + +/** + * This header is part of C11 (and supported for compatibility in older + * versions) but is gone in C23 because the C keywords were moved out of the + * reserved-for-the-implementation namespace into the global one. + */ +#ifdef __STDC_VERSION__ +# if __STDC_VERSION__ < 202311L + +/** + * C++-compatible spelling for `_Noreturn`. + */ +# define noreturn _Noreturn + +# endif +#endif
diff --git a/sdk/include/string.h b/sdk/include/string.h index d5dac35..965a02b 100644 --- a/sdk/include/string.h +++ b/sdk/include/string.h
@@ -10,7 +10,8 @@ void *__cheri_libcall memcpy(void *dest, const void *src, size_t n); void *__cheri_libcall memset(void *, int, size_t); void *__cheri_libcall memmove(void *dest, const void *src, size_t n); -const void *__cheri_libcall memchr(const void *, int, size_t); +void *__cheri_libcall memchr(const void *, int, size_t); +void *__cheri_libcall memrchr(const void *, int, size_t); size_t __cheri_libcall strlen(const char *str); int __cheri_libcall strncmp(const char *s1, const char *s2, size_t n); char *__cheri_libcall strncpy(char *dest, const char *src, size_t n);
diff --git a/sdk/lib/compartment_helpers/claim_fast.cc b/sdk/lib/compartment_helpers/claim_fast.cc index 6181f3a..bf87ae4 100644 --- a/sdk/lib/compartment_helpers/claim_fast.cc +++ b/sdk/lib/compartment_helpers/claim_fast.cc
@@ -11,7 +11,7 @@ { void **hazards = switcher_thread_hazard_slots(); auto *epochCounter{const_cast< - cheriot::atomic<uint32_t> *>(MMIO_CAPABILITY_WITH_PERMISSIONS( + cheriot::atomic<uint32_t> *>(SHARED_OBJECT_WITH_PERMISSIONS( cheriot::atomic<uint32_t>, allocator_epoch, true, false, false, false))}; uint32_t epoch = epochCounter->load(); int values = 2;
diff --git a/sdk/lib/queue/queue.cc b/sdk/lib/queue/queue.cc index 5a1caf6..879f8c6 100644 --- a/sdk/lib/queue/queue.cc +++ b/sdk/lib/queue/queue.cc
@@ -509,19 +509,26 @@ if (LockGuard g{l, timeout}) { uint32_t producerCounter = counter_load(producer); - uint32_t consumerCounter = counter_load(consumer); + uint32_t consumerValue = consumer->load(); + uint32_t consumerCounter = + consumerValue & ~(HighBitFlagLock::reserved_bits()); Debug::log("Producer counter: {}, consumer counter: {}, Size: {}", producerCounter, consumerCounter, handle->queueSize); while (is_full(handle->queueSize, producerCounter, consumerCounter)) { - if (consumer->wait(timeout, consumerCounter) == -ETIMEDOUT) + // Wait on the value to change. If we hit this path while the + // consumer lock is held, then the high bits will be set. Make + // sure that we yield. + if (consumer->wait(timeout, consumerValue) == -ETIMEDOUT) { Debug::log("Timed out on futex"); return -ETIMEDOUT; } - consumerCounter = counter_load(consumer); + consumerValue = consumer->load(); + consumerCounter = + consumerValue & ~(HighBitFlagLock::reserved_bits()); } auto entry = buffer_at_counter(*handle, producerCounter); if (int claim = heap_claim_fast(timeout, handle->buffer, src); @@ -575,7 +582,9 @@ HighBitFlagLock l{*consumer}; if (LockGuard g{l, timeout}) { - uint32_t producerCounter = counter_load(producer); + uint32_t producerValue = producer->load(); + uint32_t producerCounter = + producerValue & ~(HighBitFlagLock::reserved_bits()); uint32_t consumerCounter = counter_load(consumer); Debug::log("Producer counter: {}, consumer counter: {}, Size: {}", producerCounter, @@ -583,11 +592,16 @@ handle->queueSize); while (is_empty(producerCounter, consumerCounter)) { - if (producer->wait(timeout, producerCounter) == -ETIMEDOUT) + // Wait on the value to change. If we hit this path while the + // producer lock is held, then the high bits will be set. Make + // sure that we yield. + if (producer->wait(timeout, producerValue) == -ETIMEDOUT) { return -ETIMEDOUT; } - producerCounter = counter_load(producer); + producerValue = producer->load(); + producerCounter = + producerValue & ~(HighBitFlagLock::reserved_bits()); } auto entry = buffer_at_counter(*handle, consumerCounter); if (int claim = heap_claim_fast(timeout, handle->buffer, dst);
diff --git a/sdk/lib/string/memchr.c b/sdk/lib/string/memchr.c index eb08f5e..7961976 100644 --- a/sdk/lib/string/memchr.c +++ b/sdk/lib/string/memchr.c
@@ -3,9 +3,9 @@ #include <string.h> -const void *__cheri_libcall memchr(const void *voidString, - int intChar, - size_t n) +void *__cheri_libcall memchr(const void *voidString, + int intChar, + size_t n) { const unsigned char c = (unsigned char)intChar; const unsigned char *s = (const unsigned char *)voidString; @@ -14,7 +14,7 @@ { if (*s == c) { - return (const void *)s; + return (void *)s; } s++; }
diff --git a/sdk/lib/string/memrchr.c b/sdk/lib/string/memrchr.c new file mode 100644 index 0000000..bf51298 --- /dev/null +++ b/sdk/lib/string/memrchr.c
@@ -0,0 +1,24 @@ +// Copyright SCI Semiconductor and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include <string.h> + +void *__cheri_libcall memrchr(const void *voidString, + int intChar, + size_t n) +{ + const unsigned char c = (unsigned char)intChar; + const unsigned char *s = (const unsigned char *)voidString; + + s += n; + for (size_t i = n; i > 0; --i) + { + --s; + if (*s == c) + { + return (void *)s; + } + } + + return NULL; +}
diff --git a/sdk/lib/string/xmake.lua b/sdk/lib/string/xmake.lua index ded4a00..002f5e3 100644 --- a/sdk/lib/string/xmake.lua +++ b/sdk/lib/string/xmake.lua
@@ -1,3 +1,3 @@ library("string") set_default(false) - add_files("strcmp.c", "strlen.c", "strncpy.c", "strstr.cc", "strchr.c", "strlcpy.c", "memchr.c") + add_files("strcmp.c", "strlen.c", "strncpy.c", "strstr.cc", "strchr.c", "strlcpy.c", "memchr.c", "memrchr.c")
diff --git a/sdk/xmake.lua b/sdk/xmake.lua index 96b03cd..bac906e 100644 --- a/sdk/xmake.lua +++ b/sdk/xmake.lua
@@ -282,7 +282,7 @@ simulator = string.gsub(simulator, "${(%w*)}", { sdk=scriptdir, board=boarddir }) local firmware = target:targetfile() local directory = path.directory(firmware) - firmware = path.basename(firmware) + firmware = path.filename(firmware) local run = function(simulator) os.execv(simulator, { firmware }, { curdir = directory }) end @@ -683,6 +683,42 @@ end end) + local shared_objects = { + -- 32-bit counter for the hazard-pointer epoch. + allocator_epoch = 4, + -- Two hazard pointers per thread. + allocator_hazard_pointers = #(threads) * 8 * 2 + } + visit_all_dependencies(function (target) + local globals = target:values("shared_objects") + if globals then + for name, size in pairs(globals) do + if not (name == "__wrap_locked__") then + if shared_objects[global] and (not (shared_objects[global] == size)) then + raise("Global " .. global .. " is declared with different sizes.") + end + shared_objects[name] = size + end + end + end + end) + -- TODO: We should sort pre-shared globals by size to minimise padding. + -- Each global is emitted as a separate section so that we can use + -- CAPALIGN and let the linker insert the required padding. + local shared_objects_template = + "\n\t\t. = ALIGN(MIN(${size}, 8));" .. + "\n\t\t__cheriot_shared_object_section_${global} : CAPALIGN" .. + "\n\t\t{" .. + "\n\t\t\t__cheriot_shared_object_${global} = .;" .. + "\n\t\t\t. += ${size};" .. + "\n\t\t\t__cheriot_shared_object_${global}_end = .;" .. + "\n\t\t}\n" + local shared_objects_section = "" + for global, size in table.orderpairs(shared_objects) do + shared_objects_section = shared_objects_section .. string.gsub(shared_objects_template, "${([_%w]*)}", {global=global, size=size}) + end + ldscript_substitutions.shared_objects = shared_objects_section + -- Add the counts of libraries and compartments to the substitution list. ldscript_substitutions.compartment_count = compartment_count ldscript_substitutions.library_count = library_count
diff --git a/tests/ccompile-test.c b/tests/ccompile-test.c index 0a6f4ba..7463b7c 100644 --- a/tests/ccompile-test.c +++ b/tests/ccompile-test.c
@@ -21,6 +21,7 @@ #include <multiwaiter.h> #include <queue.h> #include <riscvreg.h> +#include <stdalign.h> #include <stdarg.h> #include <stdatomic.h> #include <stdbool.h> @@ -28,6 +29,7 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <stdnoreturn.h> #include <string.h> #include <strings.h> #include <switcher.h>
diff --git a/tests/misc-test.cc b/tests/misc-test.cc index 3cbab21..28352b1 100644 --- a/tests/misc-test.cc +++ b/tests/misc-test.cc
@@ -3,10 +3,13 @@ #define TEST_NAME "Test misc APIs" #include "tests.hh" +#include <compartment-macros.h> #include <ds/pointer.h> #include <string.h> #include <timeout.h> +using namespace CHERI; + /** * Test timeouts. * @@ -87,6 +90,43 @@ } /** + * Test memrchr. + * + * This test checks the following: + * + * - memrchr finds the first occurrence of the character when it is present + * (test for different values, particularly the first and the last one). + * - memrchr returns NULL when the string does not contain the character (test + * for non-NULL terminated string). + * - memrchr does not stop at \0 characters. + * - memrchr returns NULL for 0-size pointers. + */ +void check_memrchr() +{ + debug_log("Test memrchr."); + + char string[] = {'C', 'H', 'E', 'R', 'R', 'I', 'O', 'T'}; + + TEST(memchr(string, 'C', sizeof(string)) == &string[0], + "memrchr must return the first occurence of the character."); + TEST(memrchr(string, 'R', sizeof(string)) == &string[4], + "memrchr must return the first occurence of the character."); + TEST(memrchr(string, 'T', sizeof(string)) == &string[7], + "memrchr must return the first occurence of the character."); + TEST(memrchr(string, 'X', sizeof(string)) == NULL, + "memrchr must return NULL when a character is not present."); + + char stringWithNull[] = {'F', 'U', '\0', 'B', 'A', 'R', '\0'}; + + TEST(memrchr(stringWithNull, 'F', sizeof(stringWithNull)) == + &stringWithNull[0], + "memrchr must not stop at NULL characters."); + + TEST(memrchr(stringWithNull, 'Y', 0) == NULL, + "memrchr must return NULL for zero-size pointers."); +} + +/** * Test pointer utilities. * * Not comprehensive, would benefit from being expanded at some point. @@ -112,9 +152,52 @@ "The pointer proxy `=` operator does not correctly set the pointer."); } +void check_shared_object(const char *name, + Capability<void> object, + size_t size, + PermissionSet permissions) +{ + debug_log("Checking shared object {}.", object); + TEST(object.length() == size, + "Object {} is {} bytes, expected {}", + name, + object.length(), + size); + TEST(object.permissions() == permissions, + "Object {} has permissions {}, expected {}", + name, + PermissionSet{object.permissions()}, + permissions); +} + void test_misc() { check_timeouts(); check_memchr(); + check_memrchr(); check_pointer_utilities(); + debug_log("Testing shared objects."); + check_shared_object("exampleK", + SHARED_OBJECT(void, exampleK), + 1024, + {Permission::Global, + Permission::Load, + Permission::Store, + Permission::LoadStoreCapability, + Permission::LoadMutable}); + check_shared_object( + "exampleK", + SHARED_OBJECT_WITH_PERMISSIONS(void, exampleK, true, true, false, false), + 1024, + {Permission::Global, Permission::Load, Permission::Store}); + check_shared_object( + "test_word", + SHARED_OBJECT_WITH_PERMISSIONS(void, test_word, true, false, true, false), + 4, + {Permission::Global, Permission::Load, Permission::LoadStoreCapability}); + check_shared_object("test_word", + SHARED_OBJECT_WITH_PERMISSIONS( + void, test_word, true, false, false, false), + 4, + {Permission::Global, Permission::Load}); }
diff --git a/tests/test-runner.cc b/tests/test-runner.cc index 2227633..dfc1d10 100644 --- a/tests/test-runner.cc +++ b/tests/test-runner.cc
@@ -92,8 +92,9 @@ size_t index = 0; for (auto permission : Permissions) { - TEST(permission == permissionArray[index++], - "Iterator of PermissionSet failed"); + TEST_EQUAL(permission, + permissionArray[index++], + "Iterator of PermissionSet failed"); } // These need to be checked visually debug_log("Trying to print 8-bit integer: {}", uint8_t(0x12));
diff --git a/tests/tests.hh b/tests/tests.hh index f5625bc..4193ed3 100644 --- a/tests/tests.hh +++ b/tests/tests.hh
@@ -42,6 +42,12 @@ } #define TEST(cond, msg, ...) Test::Invariant((cond), msg, ##__VA_ARGS__) +#define TEST_EQUAL(e1, e2, msg) \ + { \ + auto v1 = e1; \ + auto v2 = e2; \ + Test::Invariant(v1 == v2, "{}: {} != {}", msg, v1, v2); \ + } /** * Helper to sleep for a number of ticks and not report the sleep time.
diff --git a/tests/xmake.lua b/tests/xmake.lua index 267e791..bbc1d78 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua
@@ -85,6 +85,9 @@ test("check_pointer") -- Test various APIs that are too small to deserve their own test file test("misc") + on_load(function(target) + target:values_set("shared_objects", { exampleK = 1024, test_word = 4 }, {expand = false}) + end) includes(path.join(sdkdir, "lib"))