diff --git a/cheriot-rtos b/cheriot-rtos index 4819fd0..c45d8cf 160000 --- a/cheriot-rtos +++ b/cheriot-rtos @@ -1 +1 @@ -Subproject commit 4819fd0b5eb5c76640340713770a9b13542248f3 +Subproject commit c45d8cf717782ffdb1bba7c202e3131021dfb1a2 diff --git a/uart/at/controller/controller.cc b/uart/at/controller/controller.cc new file mode 100644 index 0000000..05f591e --- /dev/null +++ b/uart/at/controller/controller.cc @@ -0,0 +1,220 @@ +// Copyright 3bian Limited and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "interface.hh" +#include +#include +#include +#include +#include +#include +#include + +constexpr uint8_t bufferSize = 32; +constexpr uint8_t maxCommands = 10; + +class ATCommand +{ + public: + // Type alias for a command handler function. + using Handler = std::string (*)(std::string_view); + + // Constructor: takes the command string, its precomputed hash, and the handler. + ATCommand(std::string_view command, uint16_t hash, Handler handler) : command(command), hash(hash), handler(handler) + { + } + + // Default constructor (needed for array initialization). + ATCommand() + { + } + + // Execute the command by calling its handler, passing in any arguments. + std::string execute(std::string_view argument) const + { + if (handler) + { + return handler(argument); + } + return "ERROR"; + } + + // Compare a candidate command (with its precomputed hash) against this command. + bool matches_with_hash(std::string_view candidate, uint16_t candidateHash) const + { + return (hash == candidateHash && command == candidate); + } + + private: + std::string_view command; // The command text (e.g., "AT+RESET") + uint16_t hash = 0; // Precomputed hash for fast comparisons. + Handler handler = nullptr; // Function pointer for executing the command. +}; + +class ATParser +{ + public: + ATParser() : commandCount(0), bufferIndex(0) + { + } + + // Process one input byte from the UART. + std::string process_input(char byte) + { + if (byte == '\r') + { + if (bufferIndex > 0) + { + // Null-terminate the buffer so it can be treated as a C-string. + buffer[bufferIndex] = '\0'; + log(buffer.data()); + + // Process the complete command. + std::string result = process_command(); + log(result); + + // Reset the buffer for the next command. + bufferIndex = 0; + return result; + } + } + else if (bufferIndex < bufferSize - 1) + { + // Add the byte if there is room in the buffer (reserve space for '\0'). + buffer[bufferIndex++] = byte; + } + return ""; + } + + // Register a new AT command along with its handler. + bool register_command(std::string_view command, ATCommand::Handler handler) + { + if (commandCount >= maxCommands) + { + return false; + } + + // Compute the hash for the command string using the parser's compute_hash. + uint16_t hash = compute_hash(command); + commands[commandCount++] = ATCommand(command, hash, handler); + return true; + } + + private: + std::array buffer; // Buffer for incoming characters. + size_t bufferIndex; // Current position in the buffer. + std::array commands; // Array of registered commands. + uint8_t commandCount; // Number of commands registered. + + // Compute a hash for a given string using a djb2-like algorithm. + static uint16_t compute_hash(std::string_view input) + { + uint16_t computedHash = 5381; + for (char character : input) + { + computedHash = ((computedHash << 5) + computedHash) + character; + } + return computedHash; + } + + // Process the complete command stored in the buffer. + std::string process_command() + { + // Create a string_view from the null-terminated buffer. + std::string_view fullInput(buffer.data()); + std::string_view commandCandidate; + std::string_view argumentCandidate; + size_t equalSignIndex = std::string_view::npos; + + // Manually search for the '=' character. + for (size_t i = 0; i < fullInput.size(); i++) + { + if (fullInput[i] == '=') + { + equalSignIndex = i; + break; + } + } + + if (equalSignIndex != std::string_view::npos) + { + // The command is before '=', and the argument follows. + commandCandidate = fullInput.substr(0, equalSignIndex); + argumentCandidate = fullInput.substr(equalSignIndex + 1); + } + else + { + // No argument provided; the entire string is the command. + commandCandidate = fullInput; + argumentCandidate = ""; + } + + // Compute the hash for the candidate command once. + uint16_t candidateHash = compute_hash(commandCandidate); + + // Search for a registered command that matches. + for (uint8_t index = 0; index < commandCount; index++) + { + if (commands[index].matches_with_hash(commandCandidate, candidateHash)) + { + return commands[index].execute(argumentCandidate); + } + } + return "ERROR"; + } +}; + +std::string handle_at_command_info(std::string_view) +{ + return "OK"; +} + +std::string handle_at_command_reset(std::string_view) +{ + return "RESETTING..."; +} + +std::string handle_at_command_get(std::string_view) +{ + return "GET OK"; +} + +std::string handle_at_command_set(std::string_view argument) +{ + if (!argument.empty()) + { + return "SET: " + std::string(argument); + } + return "SET: NO ARGS"; +} + +[[noreturn]] void __cheri_compartment("controller") entry_point() +{ + // Get a handle to the UART interface. + auto uart = MMIO_CAPABILITY(OpenTitanUart, uart); + + // Create an ATParser instance. + ATParser atParser; + + // Register supported AT commands. + atParser.register_command("AT", handle_at_command_info); + atParser.register_command("AT+RESET", handle_at_command_reset); + atParser.register_command("AT+GET", handle_at_command_get); + atParser.register_command("AT+SET", handle_at_command_set); + + // Main loop: Read bytes from UART, process input, and send responses. + while (true) + { + char byte = uart->blocking_read(); + std::string output = atParser.process_input(byte); + + if (!output.empty()) + { + for (char character : output) + { + uart->blocking_write(character); + } + uart->blocking_write('\r'); + } + } +} diff --git a/uart/at/controller/display.cc b/uart/at/controller/display.cc new file mode 100644 index 0000000..6171270 --- /dev/null +++ b/uart/at/controller/display.cc @@ -0,0 +1,97 @@ +// Copyright 3bian Limited and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "../../../third_party/display_drivers/lcd.hh" +#include "interface.hh" +#include +#include +#include + +constexpr uint32_t LineHeight = 15; +constexpr uint32_t ScreenMargin = 4; +constexpr uint32_t ScreenWidth = 160; +constexpr uint32_t ScreenHeight = 128; + +class RotatableHistory +{ + public: + explicit RotatableHistory(uint8_t maxLines) : maxLines(maxLines), dirty(true) + { + } + + bool is_dirty() const + { + return dirty; + } + + const std::vector &lines() const + { + return innerLines; + } + + void mark_clean() + { + dirty = false; + } + + void push(const std::string &line) + { + if (innerLines.size() >= maxLines) + { + innerLines.erase(innerLines.begin()); // Remove the oldest line + } + innerLines.push_back(line); + mark_dirty(); + } + + private: + bool dirty; + std::vector innerLines; + uint8_t maxLines; + + void mark_dirty() + { + dirty = true; + } +}; + +RotatableHistory history(8); + +void render_rotatable_history(SonataLcd &lcd, RotatableHistory &history); + +[[noreturn]] void __cheri_compartment("display") entry_point() +{ + SonataLcd lcd; + + while (true) + { + Timeout loopWait{MS_TO_TICKS(500)}; + + render_rotatable_history(lcd, history); + thread_sleep(&loopWait, ThreadSleepFlags::ThreadSleepNoEarlyWake); + } +} + +void __cheri_compartment("display") log(const std::string &line) +{ + history.push(line); +} + +void render_rotatable_history(SonataLcd &lcd, RotatableHistory &history) +{ + if (!history.is_dirty()) + { + return; + } + + lcd.fill_rect(Rect::from_point_and_size({0, 0}, {ScreenWidth, ScreenHeight}), Color::Black); + + uint32_t y = ScreenMargin; + for (const auto &line : history.lines()) + { + lcd.draw_str({ScreenMargin, y}, line.data(), Color::Black, Color::White); + y += LineHeight; + } + + history.mark_clean(); +} \ No newline at end of file diff --git a/uart/at/controller/interface.hh b/uart/at/controller/interface.hh new file mode 100644 index 0000000..9853224 --- /dev/null +++ b/uart/at/controller/interface.hh @@ -0,0 +1,9 @@ +// Copyright 3bian Limited and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +void __cheri_compartment("display") log(const std::string &line); \ No newline at end of file diff --git a/uart/at/controller/main.cc b/uart/at/controller/main.cc new file mode 100644 index 0000000..689e6e2 --- /dev/null +++ b/uart/at/controller/main.cc @@ -0,0 +1,169 @@ +// Copyright 3bian Limited and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include + +constexpr uint8_t ArgumentSize = 16; +constexpr uint8_t BufferSize = 32; +constexpr uint8_t MaxCommands = 10; + +typedef void (*ATHandler)(volatile OpenTitanUart *uart, const char *argument); + +struct ATCommand +{ + const char *command; + uint16_t hash; + ATHandler handler; +}; + +struct ATState +{ + ATCommand commands[MaxCommands]; + uint8_t commandCount; + char buffer[BufferSize]; + char argument[ArgumentSize]; + uint8_t bufferIndex; +}; + +ATState atState; // Global AT state + +void at_state_initialize(ATState *state); +bool at_state_register(ATState *state, const char *command, ATHandler handler); + +void at_state_process_input(volatile OpenTitanUart *uart, ATState *state); +void at_state_process_command(volatile OpenTitanUart *uart, ATState *state); + +uint16_t hash_command(const char *command); +void uart_write(volatile OpenTitanUart *uart, const char *string); + +void handle_at_command_info(volatile OpenTitanUart *uart, const char *argument); +void handle_at_command_reset(volatile OpenTitanUart *uart, const char *argument); +void handle_at_command_get(volatile OpenTitanUart *uart, const char *argument); +void handle_at_command_set(volatile OpenTitanUart *uart, const char *argument); + +[[noreturn]] void __cheri_compartment("controller") entry_point() +{ + auto uart = MMIO_CAPABILITY(OpenTitanUart, uart); + at_state_initialize(&atState); + at_state_register(&atState, "AT", handle_at_command_info); + at_state_register(&atState, "AT+RESET", handle_at_command_reset); + at_state_register(&atState, "AT+GET", handle_at_command_get); + at_state_register(&atState, "AT+SET", handle_at_command_set); + + while (true) + { + at_state_process_input(uart, &atState); + } +} + +void at_state_initialize(ATState *state) +{ + state->bufferIndex = 0; + state->commandCount = 0; +} + +bool at_state_register(ATState *state, const char *command, ATHandler handler) +{ + if (state->commandCount >= MaxCommands) + { + return false; + } + + uint8_t lastIndex = state->commandCount; + state->commands[lastIndex].command = command; + state->commands[lastIndex].hash = hash_command(command); + state->commands[lastIndex].handler = handler; + state->commandCount++; + + return true; +} + +void at_state_process_input(volatile OpenTitanUart *uart, ATState *state) +{ + char byte = uart->blocking_read(); + if (byte == '\r' || byte == '\n') + { + if (state->bufferIndex > 0) + { + state->buffer[state->bufferIndex] = '\0'; + at_state_process_command(uart, state); + state->bufferIndex = 0; + } + } + else if (state->bufferIndex < BufferSize - 1) + { + state->buffer[state->bufferIndex++] = byte; + } +} + +void at_state_process_command(volatile OpenTitanUart *uart, ATState *state) +{ + char *command = state->buffer; + char *argument = nullptr; + + for (uint8_t i = 0; i < state->bufferIndex; i++) + { + if (state->buffer[i] == '=') + { + state->buffer[i] = '\0'; + argument = &state->buffer[i + 1]; + break; + } + } + + uint16_t receivedHash = hash_command(command); + for (uint8_t i = 0; i < state->commandCount; i++) + { + if (state->commands[i].hash == receivedHash && strcmp(state->commands[i].command, command) == 0) + { + state->commands[i].handler(uart, argument); + return; + } + } + uart_write(uart, "ERROR\n"); +} + +uint16_t hash_command(const char *command) +{ + uint16_t hash = 5381; + while (*command) + { + hash = ((hash << 5) + hash) + *command++; + } + return hash; +} + +void uart_write(volatile OpenTitanUart *uart, const char *string) +{ + while (*string) + { + uart->blocking_write(*string++); + } +} + +void handle_at_command_info(volatile OpenTitanUart *uart, const char *argument) +{ + uart_write(uart, "OK\n"); +} + +void handle_at_command_reset(volatile OpenTitanUart *uart, const char *argument) +{ + uart_write(uart, "RESETTING...\n"); +} + +void handle_at_command_get(volatile OpenTitanUart *uart, const char *argument) +{ + uart_write(uart, "GET OK\n"); +} + +void handle_at_command_set(volatile OpenTitanUart *uart, const char *argument) +{ + uart_write(uart, "SET OK: "); + uart_write(uart, argument ? argument : "(no args)"); + uart_write(uart, "\n"); +} \ No newline at end of file diff --git a/uart/at/controller/xmake.lua b/uart/at/controller/xmake.lua new file mode 100644 index 0000000..155ca48 --- /dev/null +++ b/uart/at/controller/xmake.lua @@ -0,0 +1,46 @@ +-- Copyright 3bian Limited and CHERIoT Contributors. +-- SPDX-License-Identifier: MIT + +set_project("CHERIoT AT controller demo") +sdkdir = "../../../cheriot-rtos/sdk" +includes(sdkdir) +set_toolchains("cheriot-clang") + +option("board") + set_default("sonata-1.1") + +compartment("controller") + add_deps("freestanding", "string", "debug") + add_files("controller.cc") + +compartment("display") + add_deps("freestanding", "string", "debug") + add_files("display.cc") + add_files("../../../third_party/display_drivers/core/lcd_base.c") + add_files("../../../third_party/display_drivers/core/lucida_console_12pt.c") + add_files("../../../third_party/display_drivers/st7735/lcd_st7735.c", {defines = "CHERIOT_NO_AMBIENT_MALLOC"}) + add_files("../../../third_party/display_drivers/lcd_sonata_0.2.cc") + add_files("../../../third_party/display_drivers/lcd_sonata_1.0.cc") + +-- Firmware image for the example. +firmware("controller-demo") + add_deps("controller", "display") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "controller", + priority = 1, + entry_point = "entry_point", + stack_size = 0x1200, + trusted_stack_frames = 4 + }, + { + compartment = "display", + priority = 2, + entry_point = "entry_point", + stack_size = 0x1000, + trusted_stack_frames = 4 + } + }, {expand = false}) + end) diff --git a/uart/README.md b/uart/echo/README.md similarity index 100% rename from uart/README.md rename to uart/echo/README.md diff --git a/uart/uart.cc b/uart/echo/uart.cc similarity index 100% rename from uart/uart.cc rename to uart/echo/uart.cc diff --git a/uart/xmake.lua b/uart/echo/xmake.lua similarity index 95% rename from uart/xmake.lua rename to uart/echo/xmake.lua index 9238fc0..ffe1119 100644 --- a/uart/xmake.lua +++ b/uart/echo/xmake.lua @@ -2,7 +2,7 @@ -- SPDX-License-Identifier: MIT set_project("CHERIoT UART demo") -sdkdir = "../cheriot-rtos/sdk" +sdkdir = "../../cheriot-rtos/sdk" includes(sdkdir) set_toolchains("cheriot-clang")