diff --git a/examples/10.dma_test/README.md b/examples/10.dma_test/README.md new file mode 100644 index 00000000..d44b51a5 --- /dev/null +++ b/examples/10.dma_test/README.md @@ -0,0 +1,4 @@ +DMA example +=================== + + diff --git a/examples/10.dma_test/dma-test-v1.cc b/examples/10.dma_test/dma-test-v1.cc new file mode 100644 index 00000000..8940a4b2 --- /dev/null +++ b/examples/10.dma_test/dma-test-v1.cc @@ -0,0 +1,52 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include + +#include <../../sdk/core/dma-v1/dma.h> + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Thread entry point. +void __cheri_compartment("dma_test") test_dma() +{ + Debug::log("DMA app entered, v1!"); + + // This is a dma process between two different memory addresses + uint32_t bytes = 1024; + uint32_t words = bytes / 4; + uint32_t byteSwap = 0; + + uint32_t *sourceAddress = (uint32_t *)malloc(bytes); + uint32_t *targetAddress = (uint32_t *)malloc(bytes); + + for (int i = 0; i < words; i++) + { + *(sourceAddress + i) = i + 100; + *(targetAddress + i) = 0; + } + + Debug::log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(sourceAddress), + *(sourceAddress + words - 1)); + Debug::log("M: Ind: 0 and last, Dest-n values BEFORE dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + static DMA::Device dmaDevice; + + int ret = dmaDevice.configure_and_launch( + sourceAddress, targetAddress, bytes, 0, 0, byteSwap); + + Debug::log("Main, ret: {}", ret); + + Debug::log("M: Ind: 0 and last, Dest-n values AFTER dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + Debug::log("M: End of test"); +} diff --git a/examples/10.dma_test/dma-test-v2.cc b/examples/10.dma_test/dma-test-v2.cc new file mode 100644 index 00000000..5848726f --- /dev/null +++ b/examples/10.dma_test/dma-test-v2.cc @@ -0,0 +1,54 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT +#define MALLOC_QUOTA 0x10000000 + +#include +#include +#include +#include +#include + +#include <../../sdk/core/dma-v2/dma.h> + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Thread entry point. +void __cheri_compartment("dma_test") test_dma() +{ + Debug::log("DMA app entered, v3.3!"); + + // This is a dma process between two different memory addresses + uint32_t bytes = 8096; + uint32_t words = bytes / 4; + uint32_t byteSwap = 0; + + uint32_t *sourceAddress = (uint32_t *)malloc(bytes); + uint32_t *targetAddress = (uint32_t *)malloc(bytes); + + for (int i = 0; i < words; i++) + { + *(sourceAddress + i) = i + 100; + *(targetAddress + i) = 0; + } + + Debug::log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(sourceAddress), + *(sourceAddress + words - 1)); + Debug::log("M: Ind: 0 and last, Dest-n values BEFORE dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + static DMA::Device dmaDevice; + + int ret = dmaDevice.configure_and_launch( + sourceAddress, targetAddress, bytes, 0, 0, byteSwap); + + Debug::log("Main, ret: {}", ret); + + Debug::log("M: Ind: 0 and last, Dest-n values AFTER dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + Debug::log("M: End of test"); +} diff --git a/examples/10.dma_test/dma-test-v3.cc b/examples/10.dma_test/dma-test-v3.cc new file mode 100644 index 00000000..7d05e177 --- /dev/null +++ b/examples/10.dma_test/dma-test-v3.cc @@ -0,0 +1,54 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT +#define MALLOC_QUOTA 0x10000000 + +#include +#include +#include +#include +#include + +#include <../../sdk/core/dma-v3/dma.h> + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Thread entry point. +void __cheri_compartment("dma_test") test_dma() +{ + Debug::log("DMA app entered, v3.0!"); + + // This is a dma process between two different memory addresses + uint32_t bytes = 8096; + uint32_t words = bytes / 4; + uint32_t byteSwap = 0; + + uint32_t *sourceAddress = (uint32_t *)malloc(bytes); + uint32_t *targetAddress = (uint32_t *)malloc(bytes); + + for (int i = 0; i < words; i++) + { + *(sourceAddress + i) = i + 100; + *(targetAddress + i) = 0; + } + + Debug::log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(sourceAddress), + *(sourceAddress + words - 1)); + Debug::log("M: Ind: 0 and last, Dest-n values BEFORE dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + static DMA::Device dmaDevice; + + int ret = dmaDevice.configure_and_launch( + sourceAddress, targetAddress, bytes, 0, 0, byteSwap); + + Debug::log("Main, ret: {}", ret); + + Debug::log("M: Ind: 0 and last, Dest-n values AFTER dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + Debug::log("M: End of test"); +} diff --git a/examples/10.dma_test/dma-test-v4.cc b/examples/10.dma_test/dma-test-v4.cc new file mode 100644 index 00000000..7b39379a --- /dev/null +++ b/examples/10.dma_test/dma-test-v4.cc @@ -0,0 +1,69 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT +#define MALLOC_QUOTA 0x10000000 + +#include <../../sdk/core/dma-v4/platform-dma.hh> +#include +#include +#include +#include +#include + +#include <../../sdk/core/dma-v4/dma.h> + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +using namespace Ibex; + +// Thread entry point. +void __cheri_compartment("dma_test") test_dma() +{ + Debug::log("DMA app entered, v4.0!"); + + // This is a dma process between two different memory addresses + uint32_t bytes = 8096; + uint32_t words = bytes / 4; + uint32_t byteSwap = 0; + + uint32_t *sourceAddress = (uint32_t *)malloc(bytes); + uint32_t *targetAddress = (uint32_t *)malloc(bytes); + + for (int i = 0; i < words; i++) + { + *(sourceAddress + i) = i + 100; + *(targetAddress + i) = 0; + } + + DMADescriptor *dmaDescriptorPointer = (DMADescriptor *) malloc(sizeof(DMADescriptor)); + + /** + * Set the configurations here, + * before sending the descriptor to the DMA + */ + dmaDescriptorPointer->sourceCapability = sourceAddress; + dmaDescriptorPointer->targetCapability = targetAddress; + dmaDescriptorPointer->lengthInBytes = bytes; + dmaDescriptorPointer->sourceStrides = 0; + dmaDescriptorPointer->targetStrides = 0; + dmaDescriptorPointer->byteSwaps = byteSwap; + + Debug::log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(sourceAddress), + *(sourceAddress + words - 1)); + Debug::log("M: Ind: 0 and last, Dest-n values BEFORE dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + static DMA::Device dmaDevice; + + int ret = dmaDevice.configure_and_launch(dmaDescriptorPointer); + + Debug::log("Main, ret: {}", ret); + + Debug::log("M: Ind: 0 and last, Dest-n values AFTER dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + Debug::log("M: End of test"); +} diff --git a/examples/10.dma_test/xmake.lua b/examples/10.dma_test/xmake.lua new file mode 100644 index 00000000..f91370bc --- /dev/null +++ b/examples/10.dma_test/xmake.lua @@ -0,0 +1,39 @@ +-- Copyright Microsoft and CHERIoT Contributors. +-- SPDX-License-Identifier: MIT + +set_project("CHERIoT Simple DMA App") +sdkdir = "../../sdk" +includes(sdkdir) +set_toolchains("cheriot-clang") + +-- Support libraries +includes(path.join(sdkdir, "lib/freestanding"), + path.join(sdkdir, "lib/cxxrt"), + path.join(sdkdir, "lib/atomic"), + path.join(sdkdir, "lib/crt")) + +option("board") + set_default("sail") + +compartment("dma") + add_files(path.join(sdkdir, "core/dma-v2/dma_compartment.cc")) + +compartment("dma_test") + add_files("dma-test-v2.cc") + +-- Firmware image for the example. +firmware("dma-test-v2") + add_deps("crt", "cxxrt", "freestanding", "atomic_fixed") + add_deps("dma", "dma_test") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "dma_test", + priority = 2, + entry_point = "test_dma", + stack_size = 0x1000, + trusted_stack_frames = 9 + } + }, {expand = false}) + end) diff --git a/ibex.json b/ibex.json new file mode 100644 index 00000000..288ba597 --- /dev/null +++ b/ibex.json @@ -0,0 +1,71 @@ +{ + "devices" : + { + "hspTimer" : { + "start" : 0x8f000800, + "length" : 0x20 + }, + "hspInteruptController" : { + "start" : 0x8f000600, + "length" : 0x14 + }, + "uart" : { + "start" : 0x8f00b000, + "length": 0x100 + }, + "shadow" : { + "start" : 0x200f0000, + "end" : 0x200f1800 + }, + "revoker" : { + "start" : 0x8f000000, + "length": 0xc + }, + "dmb" : { + "start" : 0x8f0f0000, + "length": 0x400 + }, + "ethernet" : { + "start" : 0x98000000, + "length": 0x204 + }, + "LEDs" : { + "start" : 0x8f00 f004, + "end": 0x2020_0040 + }, + "dma" : { + "start" : 0x2020_0000, + "end" : 0x2020_0040 + } + }, + "instruction_memory" : { + "start" : 0x20000080, + "end" : 0x20060000 + }, + "heap" : { + "end" : 0x20060000 + }, + "revokable_memory_start" : 0x20000000, + "interrupts": [ + { + "name": "Ethernet", + "number": 16, + "priority": 0 + } + ], + "defines" : [ + "IBEX", + "IBEX_SHADOW_BASE=0x200f0000U", + "IBEX_SHADOW_SIZE=0x1800U" + ], + "driver_includes" : [ + "../include/", + "${sdk}/include/platform/ibex", + "${sdk}/include/platform/generic-riscv" + ], + "timer_hz" : 20000000, + "tickrate_hz" : 100, + "stack_high_water_mark" : true, + "revoker" : "hardware", + "simulator" : "${board}/../scripts/deploy-ibex-fpga.sh" +} diff --git a/sdk/core/allocator/alloc.h b/sdk/core/allocator/alloc.h index 9ec81630..d180a4bd 100644 --- a/sdk/core/allocator/alloc.h +++ b/sdk/core/allocator/alloc.h @@ -3,6 +3,7 @@ #pragma once +#include "../dma-v2/platform-dma.hh" #include "alloc_config.h" #include "revoker.h" #include @@ -21,6 +22,8 @@ extern Revocation::Revoker revoker; +Ibex::PlatformDMA platformDma; + /// Do we have temporal safety support in hardware? constexpr bool HasTemporalSafety = !std::is_same_v; @@ -1174,6 +1177,13 @@ class MState * the user capability (or its progeny) that undid our work of zeroing! */ revoker.shadow_paint_range(mem.address(), chunk.cell_next()); + + /** + * notify the DMA, so it can stop the transfer if its the DMA's source + * and destination addresses that are freed. + */ + // platformDma.notify_the_dma(); + Debug::log("freed address: {}", mem.address()); /* * Shadow bits have been painted. From now on user caps to this chunk diff --git a/sdk/core/dma-v1/dma.h b/sdk/core/dma-v1/dma.h new file mode 100644 index 00000000..12f18eeb --- /dev/null +++ b/sdk/core/dma-v1/dma.h @@ -0,0 +1,89 @@ +#include "dma_compartment.hh" +#include "futex.h" +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace DMA +{ + + template + concept IsDmaDevice = requires(T device, + uint32_t *sourceAddress, + uint32_t *targetAddress, + size_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + { + device.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount) + } -> std::same_as; + { + device.reset_dma() + } -> std::same_as; + }; + + template + + requires IsDmaDevice + + class GenericDMA : public PlatformDMA + { + public: + int configure_and_launch(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Dma launch call: + * - checks for the dma ownership status, + * - for access rights, + * - creates claims for each source and destination addresses, + * - automatically resets the claims and the dma registers + * at the end of the transfer. + */ + Debug::log("before launch dma"); + + uint32_t dmaInterruptReturn = launch_dma(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + Debug::log("launch dma return: {}", dmaInterruptReturn); + + if (dmaInterruptReturn < 0) + { + return -EINVAL; + } + + wait_and_reset_dma(dmaInterruptReturn); + + return 0; + } + }; + + using Device = GenericDMA; +} // namespace DMA diff --git a/sdk/core/dma-v1/dma_compartment.cc b/sdk/core/dma-v1/dma_compartment.cc new file mode 100644 index 00000000..13a8cf0c --- /dev/null +++ b/sdk/core/dma-v1/dma_compartment.cc @@ -0,0 +1,214 @@ +#include "futex.h" +#define MALLOC_QUOTA 0x100000 + +#include "dma_compartment.hh" +#include +#include +#include + +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Import some useful things from the CHERI namespace. +using namespace CHERI; + +Ibex::PlatformDMA platformDma; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace +{ + /** + * Flag lock to control ownership + * over the dma controller + */ + FlagLock dmaOwnershipLock; + + uint32_t expectedValue = 0; + + /** + * Claims pointers so that the following + * addresses will not be freed ahead of time + */ + std::unique_ptr claimedSource; + std::unique_ptr claimedDestination; + +} // namespace + +void internal_wait_and_reset_dma(uint32_t interruptNumber); + +int launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) +{ + /** + * Lock this compartment via LockGuard, + * to prevent data race. + * + * This lock automatically unlocks at the end of this function. + */ + + LockGuard g{dmaOwnershipLock}; + Debug::log("dma v1"); + + /** + * If dma is already launched, we need to check for the interrupt status. + * No need for validity and permissions checks though for the scheduler + * once futex is created + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + uint32_t currentInterruptCounter = *dmaFutex; + + /** + * If dma is already running, check for the + * expected and current interrupt values. + * If they do not match, wait for the interrupt. + * + * Expected Value is expected to be incremented only per thread, + * assuming that every thread enters the launch_dma() + * only once per each transfer + */ + + if (expectedValue != currentInterruptCounter) + { + internal_wait_and_reset_dma(currentInterruptCounter); + } + + Debug::Assert( + expectedValue == *dmaFutex, + "ExpectedValue is not equal to the current interrupt counter!"); + + /** + * After acquiring a ownership over lock, + * now it is the time to launch dma. + * + * Here, we claim the memory with default malloc capability, + * as declaring another heap capability is an extra entry + * that leaves the default capability let unused. + * + * Unique pointers are used to avoid explicit heap_free() calls. + * when this pointer is not used anymore, it is automatically deleted + */ + + auto claim = [](void *ptr) -> std::unique_ptr { + if (heap_claim(MALLOC_CAPABILITY, ptr) == 0) + { + return {nullptr}; + } + + return std::unique_ptr{static_cast(ptr)}; + }; + + claimedSource = claim(sourceAddress); + claimedDestination = claim(targetAddress); + + if (!claimedSource || !claimedDestination) + { + return -EINVAL; + } + + /** + * return if sufficient permissions are not present + * and if not long enough + */ + + if (!check_pointer( + sourceAddress, lengthInBytes) || + !check_pointer( + targetAddress, lengthInBytes)) + { + return -EINVAL; + } + + platformDma.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + /** + * Increment the expected value only when + * dma has started to avoid the potential deadlock + * of this function is returned with failure earlier + */ + expectedValue++; + + /** + * return here, if all operations + * were successful. + */ + + return currentInterruptCounter; +} + +void internal_wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * Handle the interrupt here, once dmaFutex woke up via scheduler. + * DMA interrupt means that the dma operation is finished + * and it is time to reset and clear the dma configuration registers. + */ + + /** + * Claim registers are meant to be + * cleared by every DMA operation, + * that is why we are explicitely resetting + * them at this exit function. + */ + + /** + * However, clear only if the addresses are not reset yet. + * Because this function can be called from two different points + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + Timeout t{10}; + futex_timed_wait(&t, dmaFutex, interruptNumber); + + if (claimedSource || claimedDestination) + { + Debug::log("before dropping claims"); + claimedSource.reset(); + claimedDestination.reset(); + + /** + * Resetting the dma registers + */ + platformDma.reset_dma(); + + /** + * Acknowledging interrupt here irrespective of the reset status + */ + interrupt_complete(STATIC_SEALED_VALUE(dmaInterruptCapability)); + } +} + +void wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * This lock is to avoid the data race as well + */ + LockGuard g{dmaOwnershipLock}; + + internal_wait_and_reset_dma(interruptNumber); +} diff --git a/sdk/core/dma-v1/dma_compartment.hh b/sdk/core/dma-v1/dma_compartment.hh new file mode 100644 index 00000000..07f928c0 --- /dev/null +++ b/sdk/core/dma-v1/dma_compartment.hh @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +/** + * A function below claims the source and target addresses of the DMA interface. + * While, DMA is in progress, these addresses will be claimed by the DMA + * compartment and so the memory will not be freed. + */ + +int __cheri_compartment("dma") launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount); + +void __cheri_compartment("dma") wait_and_reset_dma(uint32_t interruptNumber); \ No newline at end of file diff --git a/sdk/core/dma-v1/platform-dma.hh b/sdk/core/dma-v1/platform-dma.hh new file mode 100644 index 00000000..e3992d9d --- /dev/null +++ b/sdk/core/dma-v1/platform-dma.hh @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Ibex +{ + class PlatformDMA + { + private: + struct DMAInterface + { + /** + * Below is DMA control register address: + * - includes start('b0), endianness conversion('b1 and 2) and + * reset bits('b3) + * - bit of index 1 and 2 are for enabling 2 and 4 byte swaps + * respectively + * - start bit is meant to be set at the end while programming + */ + uint32_t control; + /** + * Below is DMA status register address + * - first bit refers to halted status + * - 0 is for idle, and 1 for running + */ + uint32_t status; + /** + * Below is DMA source address register address: + * - here source address is where FROM the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + */ + uint32_t sourceAddress; + /** + * Below is DMA target address register address: + * - here target address is where TO the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + */ + uint32_t targetAddress; + /** + * Below is the amount of data IN BYTES that DMA should transfer + * between the source and a target + */ + uint32_t lengthInBytes; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a source + * address. Strides is the jump amount between each data retrieval + * address during the DMA operation. Minimum width of the stride is + * 4 byte words. + * - 0 stride is default and points to the address of the next word + * - 1 is for 1 word skippal, and 2 for 2 before the next address + * - todo: more fine grain stride configurability (in term of + * bytes) for the future? + */ + uint32_t sourceStrides; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a target + * address. The same as above but just for a target address. So that + * we can fetch and transfer the data at different stride rates + */ + uint32_t targetStrides; + /** + * Below is the capability for source and target addresses + */ + uint32_t sourceCapability; + uint32_t targetCapability; + /** + * Below is the MMIO interface to tell the DMA that free() + * call occurred at the allocator compartment + */ + uint32_t callFromMalloc; + }; + + __always_inline volatile DMAInterface &device() + { + return *MMIO_CAPABILITY(DMAInterface, dma); + } + + void write_strides(uint32_t sourceStrides, uint32_t targetStrides) + { + /** + * Setting source and target strides + */ + device().sourceStrides = sourceStrides; + device().targetStrides = targetStrides; + } + + int swap_bytes_and_start_dma(uint32_t swapAmount) + { + /** + * Setting byte swaps + * and start bit. + * + * Swap amount can be equal to + * only either 2 or 4. + */ + + if (swapAmount != 2) + { + if (swapAmount != 4) + { + swapAmount = 0; + } + } + + uint32_t controlConfiguration = swapAmount | 0x1; + + device().control = controlConfiguration; + + return 0; + } + + public: + uint32_t read_status() + { + /** + * this statement returns the less signifance bit + * to show the halted status + */ + return device().status & 0x1; + } + + void write_conf_and_start(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Setting source and target addresses, and length fields + */ + device().sourceAddress = CHERI::Capability{sourceAddress}.address(); + device().targetAddress = CHERI::Capability{targetAddress}.address(); + device().lengthInBytes = lengthInBytes; + device().sourceCapability = CHERI::Capability{sourceAddress}.base(); + device().targetCapability = CHERI::Capability{targetAddress}.base(); + + write_strides(sourceStrides, targetStrides); + + swap_bytes_and_start_dma(byteSwapAmount); + } + + void notify_the_dma() + { + device().callFromMalloc = 1; + } + + void reset_dma() + { + /** + * Setting a reset bit, which is bit 3. + * this clears all the registers, + * but do not transfer the current transfer status anywhere + */ + device().control = 0x8; + } + }; +} // namespace Ibex + +// template +using PlatformDMA = Ibex::PlatformDMA; \ No newline at end of file diff --git a/sdk/core/dma-v2/dma.h b/sdk/core/dma-v2/dma.h new file mode 100644 index 00000000..d5749584 --- /dev/null +++ b/sdk/core/dma-v2/dma.h @@ -0,0 +1,93 @@ +#include "dma_compartment.hh" +#include "futex.h" +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace DMA +{ + + template + concept IsDmaDevice = requires(T device, + uint32_t *sourceAddress, + uint32_t *targetAddress, + size_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + { + device.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount) + } -> std::same_as; + { + device.reset_dma() + } -> std::same_as; + }; + + template + + requires IsDmaDevice + + class GenericDMA : public PlatformDMA + { + public: + int configure_and_launch(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Dma launch call: + * - checks for the dma ownership status, + * - for access rights, + * - creates claims for each source and destination addresses, + * - automatically resets the claims and the dma registers + * at the end of the transfer. + */ + Debug::log("before launch"); + + uint32_t dmaInterruptReturn = launch_dma(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + Debug::log("after launch: {}", dmaInterruptReturn); + + int freeStatus; + freeStatus = free(sourceAddress); + Debug::log("driver, freeStatus: {}", freeStatus); + + if (dmaInterruptReturn < 0) + { + return -EINVAL; + } + + wait_and_reset_dma(dmaInterruptReturn); + + return 0; + } + }; + + using Device = GenericDMA; +} // namespace DMA diff --git a/sdk/core/dma-v2/dma_compartment.cc b/sdk/core/dma-v2/dma_compartment.cc new file mode 100644 index 00000000..bd7a322a --- /dev/null +++ b/sdk/core/dma-v2/dma_compartment.cc @@ -0,0 +1,181 @@ +#include "futex.h" + +#include "dma_compartment.hh" +#include +#include +#include + +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Import some useful things from the CHERI namespace. +using namespace CHERI; + +Ibex::PlatformDMA platformDma; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace +{ + /** + * Flag lock to control ownership + * over the dma controller + */ + FlagLock dmaOwnershipLock; + + uint32_t expectedValue = 0; + + bool alreadyReset = 0; + + +} // namespace + +void internal_wait_and_reset_dma(uint32_t interruptNumber); + +int launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) +{ + /** + * Lock this compartment via LockGuard, + * to prevent data race. + * + * This lock automatically unlocks at the end of this function. + */ + LockGuard g{dmaOwnershipLock}; + + /** + * If dma is already launched, we need to check for the interrupt status. + * No need for validity and permissions checks though for the scheduler + * once futex is created + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + uint32_t currentInterruptCounter = *dmaFutex; + + /** + * If dma is already running, check for the + * expected and current interrupt values. + * If they do not match, wait for the interrupt. + * + * Expected Value is expected to be incremented only per thread, + * assuming that every thread enters the launch_dma() + * only once per each transfer + */ + + if (expectedValue != currentInterruptCounter) + { + internal_wait_and_reset_dma(currentInterruptCounter); + } + + Debug::Assert( + expectedValue == *dmaFutex, + "ExpectedValue is not equal to the current interrupt counter!"); + + /** + * return if sufficient permissions are not present + * and if not long enough + */ + + if (!check_pointer( + sourceAddress, lengthInBytes) || + !check_pointer( + targetAddress, lengthInBytes)) + { + return -EINVAL; + } + + /** + * After passing all the checks, + * we can reset the alreadyReset value + */ + alreadyReset = 0; + + platformDma.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + /** + * Increment the expected value only when + * dma has started to avoid the potential deadlock + * of this function is returned with failure earlier + */ + expectedValue++; + + /** + * return here, if all operations + * were successful. + */ + + return currentInterruptCounter; +} + +void internal_wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * Handle the interrupt here, once dmaFutex woke up via scheduler. + * DMA interrupt means that the dma operation is finished + * and it is time to reset and clear the dma configuration registers. + */ + + /** + * However, clear only if the addresses are not reset yet. + * Because this function can be called from two different points + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + Debug::log("before futex wait"); + + Timeout t{10}; + futex_timed_wait(&t, dmaFutex, interruptNumber); + + if (!alreadyReset) + { + /** + * Resetting the dma registers + * todo: we need some check here to avoid double checks + */ + Debug::log("inside the reset condition"); + + platformDma.reset_dma(); + + alreadyReset = 1; + + /** + * Acknowledging interrupt here irrespective of the reset status + */ + interrupt_complete(STATIC_SEALED_VALUE(dmaInterruptCapability)); + } + +} + +void wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * This lock is to avoid the data race as well + */ + LockGuard g{dmaOwnershipLock}; + + internal_wait_and_reset_dma(interruptNumber); +} \ No newline at end of file diff --git a/sdk/core/dma-v2/dma_compartment.hh b/sdk/core/dma-v2/dma_compartment.hh new file mode 100644 index 00000000..07f928c0 --- /dev/null +++ b/sdk/core/dma-v2/dma_compartment.hh @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +/** + * A function below claims the source and target addresses of the DMA interface. + * While, DMA is in progress, these addresses will be claimed by the DMA + * compartment and so the memory will not be freed. + */ + +int __cheri_compartment("dma") launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount); + +void __cheri_compartment("dma") wait_and_reset_dma(uint32_t interruptNumber); \ No newline at end of file diff --git a/sdk/core/dma-v2/platform-dma.hh b/sdk/core/dma-v2/platform-dma.hh new file mode 100644 index 00000000..dd6ef201 --- /dev/null +++ b/sdk/core/dma-v2/platform-dma.hh @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Ibex +{ + class PlatformDMA + { + private: + struct DMAInterface + { + /** + * Below is DMA control register address: + * - includes start('b0), endianness conversion('b1 and 2) and + * reset bits('b3) + * - bit of index 1 and 2 are for enabling 2 and 4 byte swaps + * respectively + * - start bit is meant to be set at the end while programming + */ + uint32_t control; + /** + * Below is DMA status register address + * - first bit refers to halted status + * - 0 is for idle, and 1 for running + */ + uint32_t status; + /** + * Below is DMA source address register address: + * - here source address is where FROM the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + */ + uint32_t sourceAddress; + /** + * Below is DMA target address register address: + * - here target address is where TO the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + */ + uint32_t targetAddress; + /** + * Below is the amount of data IN BYTES that DMA should transfer + * between the source and a target + */ + uint32_t lengthInBytes; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a source + * address. Strides is the jump amount between each data retrieval + * address during the DMA operation. Minimum width of the stride is + * 4 byte words. + * - 0 stride is default and points to the address of the next word + * - 1 is for 1 word skippal, and 2 for 2 before the next address + * - todo: more fine grain stride configurability (in term of + * bytes) for the future? + */ + uint32_t sourceStrides; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a target + * address. The same as above but just for a target address. So that + * we can fetch and transfer the data at different stride rates + */ + uint32_t targetStrides; + /** + * Below is the capability for source and target addresses + */ + void* sourceCapability; + void* targetCapability; + /** + * Below is the MMIO interface to tell the DMA that free() + * call occurred at the allocator compartment + */ + uint32_t callFromMalloc; + }; + + __always_inline volatile DMAInterface &device() + { + return *MMIO_CAPABILITY(DMAInterface, dma); + } + + void write_strides(uint32_t sourceStrides, uint32_t targetStrides) + { + /** + * Setting source and target strides + */ + device().sourceStrides = sourceStrides; + device().targetStrides = targetStrides; + } + + int swap_bytes_and_start_dma(uint32_t swapAmount) + { + /** + * Setting byte swaps + * and start bit. + * + * Swap amount can be equal to + * only either 2 or 4. + */ + + if (swapAmount != 2) + { + if (swapAmount != 4) + { + swapAmount = 0; + } + } + + uint32_t controlConfiguration = swapAmount | 0x1; + + device().control = controlConfiguration; + + return 0; + } + + public: + uint32_t read_status() + { + /** + * this statement returns the less signifance bit + * to show the halted status + */ + return device().status & 0x1; + } + + void write_conf_and_start(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Setting source and target addresses, and length fields + */ + device().sourceAddress = CHERI::Capability{sourceAddress}.address(); + device().targetAddress = CHERI::Capability{targetAddress}.address(); + device().lengthInBytes = lengthInBytes; + device().sourceCapability = (void*) sourceAddress; + device().targetCapability = (void*) targetAddress; + + write_strides(sourceStrides, targetStrides); + + swap_bytes_and_start_dma(byteSwapAmount); + } + + void notify_the_dma() + { + device().callFromMalloc = 1; + } + + void reset_dma() + { + /** + * Setting a reset bit, which is bit 3. + * this clears all the registers, + * but do not transfer the current transfer status anywhere + */ + device().control = 0x8; + } + }; +} // namespace Ibex + +// template +using PlatformDMA = Ibex::PlatformDMA; \ No newline at end of file diff --git a/sdk/core/dma-v3/dma.h b/sdk/core/dma-v3/dma.h new file mode 100644 index 00000000..d5749584 --- /dev/null +++ b/sdk/core/dma-v3/dma.h @@ -0,0 +1,93 @@ +#include "dma_compartment.hh" +#include "futex.h" +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace DMA +{ + + template + concept IsDmaDevice = requires(T device, + uint32_t *sourceAddress, + uint32_t *targetAddress, + size_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + { + device.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount) + } -> std::same_as; + { + device.reset_dma() + } -> std::same_as; + }; + + template + + requires IsDmaDevice + + class GenericDMA : public PlatformDMA + { + public: + int configure_and_launch(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Dma launch call: + * - checks for the dma ownership status, + * - for access rights, + * - creates claims for each source and destination addresses, + * - automatically resets the claims and the dma registers + * at the end of the transfer. + */ + Debug::log("before launch"); + + uint32_t dmaInterruptReturn = launch_dma(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + Debug::log("after launch: {}", dmaInterruptReturn); + + int freeStatus; + freeStatus = free(sourceAddress); + Debug::log("driver, freeStatus: {}", freeStatus); + + if (dmaInterruptReturn < 0) + { + return -EINVAL; + } + + wait_and_reset_dma(dmaInterruptReturn); + + return 0; + } + }; + + using Device = GenericDMA; +} // namespace DMA diff --git a/sdk/core/dma-v3/dma_compartment.cc b/sdk/core/dma-v3/dma_compartment.cc new file mode 100644 index 00000000..5af64651 --- /dev/null +++ b/sdk/core/dma-v3/dma_compartment.cc @@ -0,0 +1,171 @@ +#include "futex.h" + +#include "dma_compartment.hh" +#include +#include +#include + +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Import some useful things from the CHERI namespace. +using namespace CHERI; + +Ibex::PlatformDMA platformDma; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +namespace +{ + /** + * Flag lock to control ownership + * over the dma controller + */ + FlagLock dmaOwnershipLock; + + uint32_t expectedValue = 0; + + bool alreadyReset = 0; + + +} // namespace + +void internal_wait_and_reset_dma(uint32_t interruptNumber); + +int launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) +{ + /** + * Lock this compartment via LockGuard, + * to prevent data race. + * + * This lock automatically unlocks at the end of this function. + */ + LockGuard g{dmaOwnershipLock}; + + /** + * If dma is already launched, we need to check for the interrupt status. + * No need for validity and permissions checks though for the scheduler + * once futex is created + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + uint32_t currentInterruptCounter = *dmaFutex; + + /** + * If dma is already running, check for the + * expected and current interrupt values. + * If they do not match, wait for the interrupt. + * + * Expected Value is expected to be incremented only per thread, + * assuming that every thread enters the launch_dma() + * only once per each transfer + */ + + if (expectedValue != currentInterruptCounter) + { + internal_wait_and_reset_dma(currentInterruptCounter); + } + + Debug::Assert( + expectedValue == *dmaFutex, + "ExpectedValue is not equal to the current interrupt counter!"); + + /** + * No checks at the driver at this version. + * + * So, once returned after interrupt and reset, if any, + * we write to the DMA and start the transfer + */ + + alreadyReset = 0; + + platformDma.write_conf_and_start(sourceAddress, + targetAddress, + lengthInBytes, + sourceStrides, + targetStrides, + byteSwapAmount); + + /** + * Increment the expected value only when + * dma has started to avoid the potential deadlock + * of this function is returned with failure earlier + */ + expectedValue++; + + /** + * return here, if all operations + * were successful. + */ + + return currentInterruptCounter; +} + +void internal_wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * Handle the interrupt here, once dmaFutex woke up via scheduler. + * DMA interrupt means that the dma operation is finished + * and it is time to reset and clear the dma configuration registers. + */ + + /** + * However, clear only if the addresses are not reset yet. + * Because this function can be called from two different points + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + Debug::log("before futex wait"); + + Timeout t{10}; + futex_timed_wait(&t, dmaFutex, interruptNumber); + + if (!alreadyReset) + { + /** + * Resetting the dma registers + * todo: we need some check here to avoid double checks + */ + Debug::log("inside the reset condition"); + + platformDma.reset_dma(); + + alreadyReset = 1; + + /** + * Acknowledging interrupt here irrespective of the reset status + */ + interrupt_complete(STATIC_SEALED_VALUE(dmaInterruptCapability)); + } + +} + +void wait_and_reset_dma(uint32_t interruptNumber) +{ + /** + * This lock is to avoid the data race as well + */ + LockGuard g{dmaOwnershipLock}; + + internal_wait_and_reset_dma(interruptNumber); +} \ No newline at end of file diff --git a/sdk/core/dma-v3/dma_compartment.hh b/sdk/core/dma-v3/dma_compartment.hh new file mode 100644 index 00000000..07f928c0 --- /dev/null +++ b/sdk/core/dma-v3/dma_compartment.hh @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +/** + * A function below claims the source and target addresses of the DMA interface. + * While, DMA is in progress, these addresses will be claimed by the DMA + * compartment and so the memory will not be freed. + */ + +int __cheri_compartment("dma") launch_dma(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount); + +void __cheri_compartment("dma") wait_and_reset_dma(uint32_t interruptNumber); \ No newline at end of file diff --git a/sdk/core/dma-v3/platform-dma.hh b/sdk/core/dma-v3/platform-dma.hh new file mode 100644 index 00000000..0019ab08 --- /dev/null +++ b/sdk/core/dma-v3/platform-dma.hh @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Ibex +{ + class PlatformDMA + { + private: + struct DMAInterface + { + /** + * Below is DMA source capability register address: + * - here source address is where FROM the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + * - this capability also includes the both address and the access bits + * - regs 0 and 1 at hw + */ + void* sourceCapability; + /** + * Below is DMA target capability register address: + * - here target address is where TO the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + * - this capability also includes the both address and the access bits + * - regs 2 and 3 at hw + */ + void* targetCapability; + /** + * Below is the amount of data IN BYTES that DMA should transfer + * between the source and a target + * - regs 4 at hw + */ + uint32_t lengthInBytes; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a source + * address. Strides is the jump amount between each data retrieval + * address during the DMA operation. Minimum width of the stride is + * 4 byte words. + * - 0 stride is default and points to the address of the next word + * - 1 is for 1 word skippal, and 2 for 2 before the next address + * - todo: more fine grain stride configurability (in term of + * bytes) for the future? + * - regs 5 at hw + */ + uint32_t sourceStrides; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a target + * address. The same as above but just for a target address. So that + * we can fetch and transfer the data at different stride rates + * - regs 6 at hw + */ + uint32_t targetStrides; + /** + * Below is DMA control register address: + * - includes start('b0), endianness conversion('b1 and 2) and + * reset bits('b3) + * - bit of index 1 and 2 are for enabling 2 and 4 byte swaps + * respectively + * - start bit is meant to be set at the end while programming + * - regs 7 at hw + */ + uint32_t control; + /** + * Below is DMA status register address + * - first bit refers to halted status + * - 0 is for idle, and 1 for running + * - regs 8 at hw + */ + uint32_t status; + /** + * Below is the MMIO interface to tell the DMA that free() + * call occurred at the allocator compartment + * - regs 9 at hw + */ + uint32_t callFromMalloc; + }; + + __always_inline volatile DMAInterface &device() + { + return *MMIO_CAPABILITY(DMAInterface, dma); + } + + void write_strides(uint32_t sourceStrides, uint32_t targetStrides) + { + /** + * Setting source and target strides + */ + device().sourceStrides = sourceStrides; + device().targetStrides = targetStrides; + } + + int swap_bytes_and_start_dma(uint32_t swapAmount) + { + /** + * Setting byte swaps + * and start bit. + * + * Swap amount can be equal to + * only either 2 or 4. + */ + + if (swapAmount != 2) + { + if (swapAmount != 4) + { + swapAmount = 0; + } + } + + uint32_t controlConfiguration = swapAmount | 0x1; + + device().control = controlConfiguration; + + return 0; + } + + public: + uint32_t read_status() + { + /** + * this statement returns the less signifance bit + * to show the halted status + */ + return device().status & 0x1; + } + + void write_conf_and_start(uint32_t *sourceAddress, + uint32_t *targetAddress, + uint32_t lengthInBytes, + uint32_t sourceStrides, + uint32_t targetStrides, + uint32_t byteSwapAmount) + { + /** + * Setting source and target addresses, and length fields + */ + device().sourceCapability = (void*) sourceAddress; + device().targetCapability = (void*) targetAddress; + device().lengthInBytes = lengthInBytes; + + write_strides(sourceStrides, targetStrides); + + swap_bytes_and_start_dma(byteSwapAmount); + } + + void notify_the_dma() + { + device().callFromMalloc = 1; + } + + void reset_dma() + { + /** + * Setting a reset bit, which is bit 3. + * this clears all the registers, + * but do not transfer the current transfer status anywhere + */ + device().control = 0x8; + } + }; +} // namespace Ibex + +// template +using PlatformDMA = Ibex::PlatformDMA; \ No newline at end of file diff --git a/sdk/core/dma-v4/dma.h b/sdk/core/dma-v4/dma.h new file mode 100644 index 00000000..d0fbf72d --- /dev/null +++ b/sdk/core/dma-v4/dma.h @@ -0,0 +1,101 @@ +#include "futex.h" +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +using namespace Ibex; + +Ibex::PlatformDMA platformDma; + +namespace DMA +{ + + template + concept IsDmaDevice = requires(T device, + DMADescriptor *dmaDescriptorPointer) + { + { + device.write_conf_and_start(dmaDescriptorPointer) + } -> std::same_as; + { + device.reset_dma(dmaDescriptorPointer) + } -> std::same_as; + }; + + template + + requires IsDmaDevice + + class GenericDMA : public PlatformDMA + { + public: + int configure_and_launch(DMADescriptor *dmaDescriptorPointer) + { + /** + * Dma launch call: + * - checks for the dma ownership status, + * - for access rights, + * - creates claims for each source and destination addresses, + * - automatically resets the claims and the dma registers + * at the end of the transfer. + */ + Debug::log("before launch"); + + platformDma.write_conf_and_start(dmaDescriptorPointer); + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + uint32_t currentInterruptCounter = *dmaFutex; + + Debug::log("after launch: {}", currentInterruptCounter); + + // int freeStatus; + // freeStatus = free(sourceAddress); + // Debug::log("driver, freeStatus: {}", freeStatus); + + // todo: implement a do-while loop later in case! + + Timeout t{10}; + futex_timed_wait(&t, dmaFutex, currentInterruptCounter); + + int startedStatus = dmaDescriptorPointer->status; + + if (startedStatus == 1) + { + /** + * Resetting the dma registers + * todo: we need some check here to avoid double checks + */ + Debug::log("inside the reset condition"); + + platformDma.reset_dma(dmaDescriptorPointer); + + /** + * Acknowledging interrupt here irrespective of the reset status + */ + interrupt_complete(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + return 0; + } + + Debug::log("after restart"); + + return 0; + } + }; + + using Device = GenericDMA; +} // namespace DMA diff --git a/sdk/core/dma-v4/dma_compartment.cc b/sdk/core/dma-v4/dma_compartment.cc new file mode 100644 index 00000000..5efe2ac2 --- /dev/null +++ b/sdk/core/dma-v4/dma_compartment.cc @@ -0,0 +1,123 @@ +#include "futex.h" + +#include "dma_compartment.hh" +#include +#include +#include + +#include "platform-dma.hh" +#include +#include +#include +#include +#include +#include + +// Expose debugging features unconditionally for this compartment. +using Debug = ConditionalDebug; + +// Import some useful things from the CHERI namespace. +using namespace CHERI; +using namespace Ibex; + +Ibex::PlatformDMA platformDma; + +DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(dmaInterruptCapability, + dma, + true, + true); + +int launch_dma(DMADescriptor *dmaDescriptorPointer) +{ + /** + * No lock necessary due to single write point + * and automatic reset + */ + + /** + * No checks at the driver at this version. + * + * So, once returned after interrupt and reset, if any, + * we write to the DMA and start the transfer + */ + + /** + * Control register are set here for the start bit. + * Even it is set before at the drive, it is reset here + */ + dmaDescriptorPointer->control = 1; + platformDma.write_conf_and_start(dmaDescriptorPointer); + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + uint32_t currentInterruptCounter = *dmaFutex; + + /** + * return the interrupt, + * so that initiator can wait for it + */ + + return currentInterruptCounter; +} + +int wait_and_reset_dma(uint32_t interruptNumber, + DMADescriptor *originalDescriptorPointer) +{ + /** + * Handle the interrupt here, once dmaFutex woke up via scheduler. + * DMA interrupt means that the dma operation is finished + * and it is time to reset and clear the dma configuration registers. + */ + + /** + * However, clear only if the addresses are not reset yet. + * Because this function can be called from two different points + */ + + static const uint32_t *dmaFutex = + interrupt_futex_get(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + Debug::log("before futex wait"); + + Timeout t{10}; + futex_timed_wait(&t, dmaFutex, interruptNumber); + + int startedStatus = originalDescriptorPointer->status; + + if (startedStatus == 1) + { + /** + * Resetting the dma registers + * todo: we need some check here to avoid double checks + */ + Debug::log("inside the reset condition"); + + originalDescriptorPointer->control = 8; + + platformDma.reset_dma(originalDescriptorPointer); + + /** + * Acknowledging interrupt here irrespective of the reset status + */ + interrupt_complete(STATIC_SEALED_VALUE(dmaInterruptCapability)); + + return 0; + } + + return 1; +} + +void force_stop_dma(DMADescriptor *originalDescriptorPointer) +{ + /** + * Calling the platform function to forcefully reset the DMA + * with updated ptr. + * + * However, DMA controller will still conduct checks to make sure + * that the right descriptor only can abort the transfer + */ + + originalDescriptorPointer->control = 8; + platformDma.reset_dma(originalDescriptorPointer); +} \ No newline at end of file diff --git a/sdk/core/dma-v4/dma_compartment.hh b/sdk/core/dma-v4/dma_compartment.hh new file mode 100644 index 00000000..a71d0a9f --- /dev/null +++ b/sdk/core/dma-v4/dma_compartment.hh @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "platform-dma.hh" + +using namespace Ibex; + +/** + * A function below claims the source and target addresses of the DMA interface. + * While, DMA is in progress, these addresses will be claimed by the DMA + * compartment and so the memory will not be freed. + */ + +int __cheri_compartment("dma") launch_dma(DMADescriptor *dmaDescriptorPointer); + +int __cheri_compartment("dma") + wait_and_reset_dma(uint32_t interruptNumber, + DMADescriptor *originalDescriptorPointer); + +void __cheri_compartment("dma") + force_stop_dma(DMADescriptor *originalDescriptorPointer); \ No newline at end of file diff --git a/sdk/core/dma-v4/platform-dma.hh b/sdk/core/dma-v4/platform-dma.hh new file mode 100644 index 00000000..7bab6cee --- /dev/null +++ b/sdk/core/dma-v4/platform-dma.hh @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Ibex +{ + /** + * Drivers should obey this DMADescriptor structure + * + * If they do not obey the right structure and the addresses, + * bounds and permissions are wrong, DMA does not start. + */ + struct DMADescriptor + { + /** + * Below is DMA source capability register address: + * - here source address is where FROM the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + * - this capability also includes the both address and the access bits + * - regs 0 and 1 at hw + */ + void* sourceCapability; + /** + * Below is DMA target capability register address: + * - here target address is where TO the DMA should transfer the + * data + * - it can be either memory buffer or MMAP-ed I/O device (i.e. any + * peripheral or accelerator) + * - this capability also includes the both address and the access bits + * - regs 2 and 3 at hw + */ + void* targetCapability; + /** + * Below is the amount of data IN BYTES that DMA should transfer + * between the source and a target + * - regs 4 at hw + */ + uint32_t lengthInBytes; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a source + * address. Strides is the jump amount between each data retrieval + * address during the DMA operation. Minimum width of the stride is + * 4 byte words. + * - 0 stride is default and points to the address of the next word + * - 1 is for 1 word skippal, and 2 for 2 before the next address + * - todo: more fine grain stride configurability (in term of + * bytes) for the future? + * - regs 5 at hw + */ + uint32_t sourceStrides; + /** + * Below is the amount of strides IN 4 BYTE WORDS for a target + * address. The same as above but just for a target address. So that + * we can fetch and transfer the data at different stride rates + * - regs 6 at hw + */ + uint32_t targetStrides; + /** + * Below is the register to show the number of byte swaps to be 2 or 4. + * This will be checked for the right value at the hw again. + * - regs 7 at hw + */ + uint32_t byteSwaps; + /** + * Ideally the following two registers should be set only + * at the compartment! + */ + + /** + * Below is DMA control register address: + * - includes start('b0), endianness conversion('b1 and 2) and + * reset bits('b3) + * - bit of index 1 and 2 are for enabling 2 and 4 byte swaps + * respectively + * - start bit is meant to be set at the end while programming + * - regs 7 at hw + */ + uint32_t control; + /** + * Below is DMA status register address + * - first bit refers to success status + * - regs 8 at hw + */ + uint32_t status; + }; + + class PlatformDMA + { + private: + + struct DMAInterface + { + /** + * All configurations would be written to the hw via + * setting the dmaDescriptorPointer to the right descriptor + * + * The structure of the descriptor is written here. + * This driver does not use tne reset register and resets + * automatically. + */ + + DMADescriptor *dmaDescriptorPointer; + /** + * Below is the MMIO interface to tell the DMA that free() + * call occurred at the allocator compartment + * - regs 9 at hw + */ + uint32_t callFromMalloc; + }; + + __always_inline volatile DMAInterface &device() + { + return *MMIO_CAPABILITY(DMAInterface, dma); + } + + public: + + void write_conf_and_start(DMADescriptor *dmaDescriptorPointer) + { + /** + * Setting configurations via pointing + * to a descriptor adddress. + * + * DMA controller should be able to fetch the data on its own + */ + dmaDescriptorPointer->control = 1; + device().dmaDescriptorPointer = dmaDescriptorPointer; + } + + void notify_the_dma() + { + device().callFromMalloc = 1; + } + + void reset_dma(DMADescriptor *originalDescriptorPointer) + { + /** + * Setting a reset bit, which is bit 3. + * this clears all the registers, + * but do not transfer the current transfer status anywhere. + * + * DMA driver should send the pointer with updated control + * field to enable this change. + * + * HW will check whether initiator and reset descriptor pointer + * are similar to guarantee the correctness + */ + originalDescriptorPointer->control = 8; + device().dmaDescriptorPointer = originalDescriptorPointer; + } + }; +} // namespace Ibex + +// template +using PlatformDMA = Ibex::PlatformDMA; \ No newline at end of file diff --git a/sdk/xmake.lua b/sdk/xmake.lua index c37de221..0300c060 100644 --- a/sdk/xmake.lua +++ b/sdk/xmake.lua @@ -78,6 +78,7 @@ toolchain("cheriot-clang") "-fno-builtin", "-fno-exceptions", "-fno-asynchronous-unwind-tables", + "-fno-c++-static-destructors", "-fno-rtti", "-Werror", "-I" .. path.join(include_directory, "c++-config"), diff --git a/tests/dma-test.cc b/tests/dma-test.cc new file mode 100644 index 00000000..31eac1e4 --- /dev/null +++ b/tests/dma-test.cc @@ -0,0 +1,96 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT +#define TEST_NAME "DMA" +#include "tests.hh" +#include + +#include +#include +#include +#include +#include + +#include <../../sdk/core/dma-v2/dma.h> + +#include +#include + +using namespace thread_pool; + +// Thread entry point. +void test_dma() +{ + debug_log("DMA app entered, v2.10!"); + + // This is a dma process between two different memory addresses + uint32_t bytes = 1024; + uint32_t words = bytes / 4; + uint32_t byteSwap = 0; + + uint32_t *sourceAddress = (uint32_t *)malloc(bytes); + uint32_t *targetAddress = (uint32_t *)malloc(bytes); + uint32_t *alternateAddress = (uint32_t *)malloc(bytes); + + for (int i = 0; i < words; i++) + { + *(sourceAddress + i) = i + 100; + *(alternateAddress + i) = i + 200; + *(targetAddress + i) = 0; + } + + debug_log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(sourceAddress), + *(sourceAddress + words - 1)); + + debug_log("M:Ind: 0 and last, Source values BEFORE dma: {}, {}", + *(alternateAddress), + *(alternateAddress + words - 1)); + + debug_log("M: Ind: 0 and last, Dest-n values BEFORE dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + static DMA::Device dmaDevice; + + async([=]() { + debug_log("Thread 1, start"); + + int ret = dmaDevice.configure_and_launch( + sourceAddress, targetAddress, bytes, 0, 0, byteSwap); + + debug_log("Thread 1, ret: {}", ret); + }); + + // async([=]() { + // debug_log("Thread Free, start"); + + // free(sourceAddress); + // }); + + // async([=]() { + // debug_log("Thread 2, start"); + + // int ret = dmaDevice.configure_and_launch( + // alternateAddress, targetAddress, bytes, 0, 0, 0); + + // debug_log("Thread 2, ret: {}", ret); + // }); + + // here, we are just forcing to sleep + // however, for experimental numbers + // we need to make sure to make a fare analysis + Timeout t{100}; + thread_sleep(&t); + + // debug_log("Ind: 0 and last, Source values AFTER dma: {}, {}", + // *(sourceAddress), + // *(sourceAddress + words - 1)); + // debug_log("Ind: 0 and last, Source values AFTER dma: {}, {}", + // *(alternateAddress), + // *(alternateAddress + words - 1)); + debug_log("M: Ind: 0 and last, Dest-n values AFTER dma: {}, {}", + *(targetAddress), + *(targetAddress + words - 1)); + + debug_log("M: End of test"); +} diff --git a/tests/test-runner.cc b/tests/test-runner.cc index 8f3e8be5..ea20b730 100644 --- a/tests/test-runner.cc +++ b/tests/test-runner.cc @@ -92,17 +92,18 @@ void __cheri_compartment("test_runner") run_tests() } run_timed("All tests", []() { - run_timed("Static sealing", test_static_sealing); - run_timed("Crash recovery", test_crash_recovery); - run_timed("Compartment calls", test_compartment_call); - run_timed("Stacks exhaustion in the switcher", test_stack); - run_timed("Thread pool", test_thread_pool); - run_timed("Global Constructors", test_global_constructors); - run_timed("Queue", test_queue); - run_timed("Futex", test_futex); - run_timed("Locks", test_locks); - run_timed("Multiwaiter", test_multiwaiter); - run_timed("Allocator", test_allocator); + // run_timed("Static sealing", test_static_sealing); + // run_timed("Crash recovery", test_crash_recovery); + // run_timed("Compartment calls", test_compartment_call); + // run_timed("Stacks exhaustion in the switcher", test_stack); + // run_timed("Thread pool", test_thread_pool); + // run_timed("Global Constructors", test_global_constructors); + // run_timed("Queue", test_queue); + // run_timed("Futex", test_futex); + // run_timed("Locks", test_locks); + // run_timed("Multiwaiter", test_multiwaiter); + // run_timed("Allocator", test_allocator); + run_timed("DMA", test_dma); }); TEST(crashDetected == false, "One or more tests failed"); diff --git a/tests/tests.hh b/tests/tests.hh index 9b9687fb..10441a5e 100644 --- a/tests/tests.hh +++ b/tests/tests.hh @@ -15,6 +15,7 @@ __cheri_compartment("multiwaiter_test") void test_multiwaiter(); __cheri_compartment("stack_test") void test_stack(); __cheri_compartment("compartment_calls_test") void test_compartment_call(); __cheri_compartment("static_sealing_test") void test_static_sealing(); +__cheri_compartment("dma_test") void test_dma(); // Simple tests don't need a separate compartment. void test_global_constructors(); diff --git a/tests/xmake.lua b/tests/xmake.lua index 605958bd..2963abac 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua @@ -23,38 +23,42 @@ function test_c(name) end -- Test the allocator and the revoker. -test("allocator") +-- test("allocator") -- Test the thread pool test("thread_pool") --- Test the futex implementation -test("futex") --- Test locks built on top of the futex -test("queue") --- Test queues -test("locks") --- Test the static sealing types -test("static_sealing") -compartment("static_sealing_inner") - add_files("static_sealing_inner.cc") --- Test crash recovery. -compartment("crash_recovery_inner") - add_files("crash_recovery_inner.cc") -compartment("crash_recovery_outer") - add_files("crash_recovery_outer.cc") -test("crash_recovery") --- Test the multiwaiter -test("multiwaiter") --- Test that C code can compile -test_c("ccompile") --- Test stacks -compartment("stack_integrity_thread") - add_files("stack_integrity_thread.cc") -test("stack") -compartment("compartment_calls_inner") - add_files("compartment_calls_inner.cc") -compartment("compartment_calls_inner_with_handler") - add_files("compartment_calls_inner_with_handler.cc") -test("compartment_calls") +-- -- Test the futex implementation +-- test("futex") +-- -- Test locks built on top of the futex +-- test("queue") +-- -- Test queues +-- test("locks") +-- -- Test the static sealing types +-- test("static_sealing") +-- compartment("static_sealing_inner") +-- add_files("static_sealing_inner.cc") +-- -- Test crash recovery. +-- compartment("crash_recovery_inner") +-- add_files("crash_recovery_inner.cc") +-- compartment("crash_recovery_outer") +-- add_files("crash_recovery_outer.cc") +-- test("crash_recovery") +-- -- Test the multiwaiter +-- test("multiwaiter") +-- -- Test that C code can compile +-- test_c("ccompile") +-- -- Test stacks +-- compartment("stack_integrity_thread") +-- add_files("stack_integrity_thread.cc") +-- test("stack") +-- compartment("compartment_calls_inner") +-- add_files("compartment_calls_inner.cc") +-- compartment("compartment_calls_inner_with_handler") +-- add_files("compartment_calls_inner_with_handler.cc") +-- test("compartment_calls") +compartment("dma") + add_files(path.join(sdkdir, "core/dma-v2/dma_compartment.cc")) + +test("dma") includes(path.join(sdkdir, "lib/atomic"), path.join(sdkdir, "lib/cxxrt"), @@ -74,17 +78,19 @@ firmware("test-suite") -- Helper libraries add_deps("freestanding", "string", "crt", "cxxrt", "atomic_fixed") -- Tests - add_deps("allocator_test") + -- add_deps("allocator_test") add_deps("thread_pool_test") - add_deps("futex_test") - add_deps("queue_test") - add_deps("locks_test") - add_deps("static_sealing_test", "static_sealing_inner") - add_deps("crash_recovery_test", "crash_recovery_inner", "crash_recovery_outer") - add_deps("multiwaiter_test") - add_deps("ccompile_test") - add_deps("stack_test", "stack_integrity_thread") - add_deps("compartment_calls_test", "compartment_calls_inner", "compartment_calls_inner_with_handler") + -- add_deps("futex_test") + -- add_deps("queue_test") + -- add_deps("locks_test") + -- add_deps("static_sealing_test", "static_sealing_inner") + -- add_deps("crash_recovery_test", "crash_recovery_inner", "crash_recovery_outer") + -- add_deps("multiwaiter_test") + -- add_deps("ccompile_test") + -- add_deps("stack_test", "stack_integrity_thread") + -- add_deps("compartment_calls_test", "compartment_calls_inner", "compartment_calls_inner_with_handler") + add_deps("dma_test") + add_deps("dma") -- Set the thread entry point to the test runner. on_load(function(target) target:values_set("board", "$(board)") @@ -98,13 +104,6 @@ firmware("test-suite") -- test to fail in the right compartment. trusted_stack_frames = 9 }, - { - compartment = "thread_pool", - priority = 1, - entry_point = "thread_pool_run", - stack_size = 0x600, - trusted_stack_frames = 8 - }, { compartment = "thread_pool", priority = 1, @@ -112,5 +111,12 @@ firmware("test-suite") stack_size = 0x600, trusted_stack_frames = 8 } + -- { + -- compartment = "thread_pool", + -- priority = 1, + -- entry_point = "thread_pool_run", + -- stack_size = 0x600, + -- trusted_stack_frames = 8 + -- } }, {expand = false}) end)