From 849cf7b7a17b8fb7d208d981cf5909f2513c959d Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Tue, 24 Jun 2025 23:04:13 -0700 Subject: [PATCH] mbv: add async app, sorta works --- mbv/Makefile | 4 + mbv/apps/async/async.cc | 168 ++++++++++++++++++++++++++++ mbv/apps/async/async.h | 208 +++++++++++++++++++++++++++++++++++ mbv/apps/async/buffer.h | 30 +++++ mbv/apps/async/gpio.h | 27 +++++ mbv/apps/async/itoa.h | 13 +++ mbv/apps/async/lock.cc | 5 + mbv/apps/async/lock.h | 21 ++++ mbv/apps/async/main.cc | 141 ++++++++++++++++++++++++ mbv/apps/async/ring_buffer.h | 108 ++++++++++++++++++ mbv/apps/async/trace.cc | 71 ++++++++++++ mbv/apps/async/trace.h | 51 +++++++++ mbv/apps/async/uart.cc | 165 +++++++++++++++++++++++++++ mbv/apps/async/uart.h | 35 ++++++ mbv/apps/async/uart_async.h | 15 +++ mbv/configure | 13 ++- 16 files changed, 1073 insertions(+), 2 deletions(-) create mode 100644 mbv/apps/async/async.cc create mode 100644 mbv/apps/async/async.h create mode 100644 mbv/apps/async/buffer.h create mode 100644 mbv/apps/async/gpio.h create mode 100644 mbv/apps/async/itoa.h create mode 100644 mbv/apps/async/lock.cc create mode 100644 mbv/apps/async/lock.h create mode 100644 mbv/apps/async/main.cc create mode 100644 mbv/apps/async/ring_buffer.h create mode 100644 mbv/apps/async/trace.cc create mode 100644 mbv/apps/async/trace.h create mode 100644 mbv/apps/async/uart.cc create mode 100644 mbv/apps/async/uart.h create mode 100644 mbv/apps/async/uart_async.h diff --git a/mbv/Makefile b/mbv/Makefile index 2f670cf..83d9c8e 100644 --- a/mbv/Makefile +++ b/mbv/Makefile @@ -14,6 +14,10 @@ timer: ## Build the timer app in docker uart: ## Build the uart app in docker docker build -o . --target export --build-arg TARGET=uart.bin . +.PHONY: async +async: ## Build the async app in docker + docker build -o . --target export --build-arg TARGET=async.bin . + .PHONY: dev-image dev-image: docker build -t mbv-dev --target dev . diff --git a/mbv/apps/async/async.cc b/mbv/apps/async/async.cc new file mode 100644 index 0000000..647891d --- /dev/null +++ b/mbv/apps/async/async.cc @@ -0,0 +1,168 @@ +#include "async.h" + +#include +#include +#include +#include + +#include "lock.h" +#include "trace.h" + +namespace async { +namespace { + +using namespace std::literals::chrono_literals; + +struct Stuff { + std::coroutine_handle<> h; + std::chrono::system_clock::time_point expiration; + + Stuff* next; +}; + +struct Notification { + bool pending; // can only be true if stuff is nullptr + Stuff* stuff; +}; + +std::atomic work = nullptr; +std::array(AwaitableType::kNumTypes)> + notifications = {}; + +} // namespace + +void schedule(std::coroutine_handle<> h, int ms) { + InterruptLock lock; + TRACE(tracing::TraceEvent::kAsyncSchedule); + std::chrono::system_clock::time_point exp = + std::chrono::system_clock::now() + std::chrono::milliseconds(ms); + Stuff* news = new Stuff{ + .h = h, + .expiration = exp, + }; + + Stuff* stuff = work; + + if (!stuff || stuff->expiration > exp) { + news->next = stuff; + work = news; + return; + } + + Stuff* s = stuff; + while (s->next && s->next->expiration <= exp) { + s = s->next; + } + + news->next = s->next; + s->next = news; +} + +void step() { + Stuff* stuff; + // ensure all previous side effects are visible + { + InterruptLock lock; + stuff = work; + }; + + if (stuff == nullptr) { + return; + } + + auto now = std::chrono::system_clock::now(); + auto dt = stuff->expiration - now; + + if (dt > 0ms) { + return; + } + + int stuffinqueue = 0; + for (Stuff* s = stuff; s; s = s->next) stuffinqueue++; + + TRACE(tracing::TraceEvent::kAsyncTask); + stuff->h(); + TRACE(tracing::TraceEvent::kAsyncTaskDone); + + if (stuff->h.done()) { + stuff->h.destroy(); + } + + { + InterruptLock lock; + work = stuff->next; + } + delete stuff; +} + +void reset() { + Stuff* stuff = work; + while (stuff) { + Stuff* byebye = stuff; + stuff = stuff->next; + + delete byebye; + } + work = nullptr; +} + +void main_loop(bool (*idle_function)()) { + while (1) { + if (idle_function != nullptr) { + if (idle_function()) { + reset(); + break; + }; + } + + step(); + } +} + +void enqueue(std::coroutine_handle<> h, AwaitableType type) { + auto ttype = static_cast(type); + + { + InterruptLock lock; + TRACE(tracing::TraceEvent::kAsyncEnqueue); + + const bool was_notified = + std::exchange(notifications[ttype].pending, false); + if (was_notified) { + TRACE(tracing::TraceEvent::kAsyncAwaitWasNotified); + schedule(h); + return; + } + + Stuff* item = new Stuff{.h = h}; + Stuff* stuff = notifications[ttype].stuff; + if (stuff == nullptr) { + notifications[ttype].stuff = item; + return; + } + while (stuff->next != nullptr) { + stuff = stuff->next; + } + stuff->next = item; + } +} + +void resume(AwaitableType type) { + auto ttype = static_cast(type); + Stuff* stuff = nullptr; + { + InterruptLock lock; + + stuff = notifications[ttype].stuff; + if (stuff == nullptr) { + notifications[ttype].pending = true; + return; + } + + notifications[ttype].stuff = stuff->next; + schedule(stuff->h); + } + delete stuff; +} + +} // namespace async \ No newline at end of file diff --git a/mbv/apps/async/async.h b/mbv/apps/async/async.h new file mode 100644 index 0000000..aa68ac3 --- /dev/null +++ b/mbv/apps/async/async.h @@ -0,0 +1,208 @@ +#pragma once + +#include +#include +#include + +#include "trace.h" + +namespace async { + +struct task_final_suspend { + bool await_ready() noexcept(true) { return false; } + void await_suspend(std::coroutine_handle<> h) noexcept(true) { + if (parent) { + TRACE(tracing::TraceEvent::kAsyncCallParent); + parent(); + TRACE(tracing::TraceEvent::kAsyncCallParentDone); + + if (parent && parent.done()) { + TRACE(tracing::TraceEvent::kAsyncDestroy); + parent.destroy(); + } + } + } + void await_resume() noexcept(true) {} + + std::coroutine_handle<> parent; +}; + +template +struct gimme { + // child interface + bool await_ready() { return false; } + void await_suspend(std::coroutine_handle<> h) { + ha = h; + waiting = true; + TRACE(tracing::TraceEvent::kAsyncGimmeWaiting); + } + T await_resume() { + waiting = false; + TRACE(tracing::TraceEvent::kAsyncGimmeResume); + return std::move(stuff); + } + + // parent interface + void feed(T&& s) { + if (!waiting) { + __builtin_trap(); + } + if (!ha) { + __builtin_trap(); + } + stuff = s; + ha.resume(); + } + + bool waiting = false; + std::coroutine_handle<> ha; + T stuff; +}; + +template +struct task; + +template <> +struct task { + struct promise_type; + using handle_type = std::coroutine_handle; + + struct promise_type { + task get_return_object() { + return {.h = handle_type::from_promise(*this)}; + } + std::suspend_always initial_suspend() noexcept { return {}; } + task_final_suspend final_suspend() noexcept { + return {.parent = parent}; + } + void return_void() {} + void unhandled_exception() { + TRACE(tracing::TraceEvent::kAsyncException); + } + + std::coroutine_handle<> parent; + }; + + // awaitable + bool await_ready() { + TRACE(tracing::TraceEvent::kAsyncCoAwait); + h(); + if (h.done()) { + TRACE(tracing::TraceEvent::kAsyncDestroy); + h.destroy(); + return true; + } + return false; + } + void await_suspend(std::coroutine_handle<> ha) { + TRACE(tracing::TraceEvent::kAsyncSuspend); + h.promise().parent = ha; + } + void await_resume() {} + + std::coroutine_handle h; +}; + +template +struct task { + struct promise_type; + using handle_type = std::coroutine_handle; + + struct promise_type { + task get_return_object() { + return {.h = handle_type::from_promise(*this)}; + } + std::suspend_always initial_suspend() noexcept { return {}; } + task_final_suspend final_suspend() noexcept { + return {.parent = parent}; + } + void return_value(T&& value) { ret_value = std::move(value); } + void unhandled_exception() { + TRACE(tracing::TraceEvent::kAsyncException); + } + template From> + task_final_suspend yield_value(From&& value) { + ret_value = std::forward(value); + result_ready = true; + return {.parent = parent}; + } + + T ret_value; + bool result_ready = false; + std::coroutine_handle<> parent; + }; + + // awaitable + bool await_ready() { + h.promise().parent = {}; + TRACE(tracing::TraceEvent::kAsyncCoAwait); + h(); + if (h.promise().result_ready) { + return true; + } + if (h.done()) { + destroyed = true; + ret_value = std::move(h.promise().ret_value); + TRACE(tracing::TraceEvent::kAsyncDestroy); + h.destroy(); + return true; + } + return false; + } + void await_suspend(std::coroutine_handle<> ha) { + TRACE(tracing::TraceEvent::kAsyncSuspend); + h.promise().parent = ha; + } + T await_resume() { + if (!destroyed) { + h.promise().result_ready = false; + return std::move(h.promise().ret_value); + } + return std::move(ret_value); + } + + bool destroyed = false; + T ret_value; + std::coroutine_handle h; +}; + +enum class AwaitableType { + kUnknown = 0, + kUartRx = 1, + kUartTx = 2, + + kNumTypes +}; + +void schedule(std::coroutine_handle<> h, int ms = 0); +void enqueue(std::coroutine_handle<> h, AwaitableType type); +void resume(AwaitableType type); // typically called from an ISR + +void main_loop(bool (*idle_function)()); +void step(); + +inline auto await(AwaitableType type) { + struct awaitable { + AwaitableType type; + + bool await_ready() { return false; }; + void await_suspend(std::coroutine_handle<> h) { enqueue(h, type); } + void await_resume() {} + }; + + return awaitable{type}; +} + +inline auto delay(int ms) { + struct awaitable { + int ms; + + bool await_ready() { return false; }; + void await_suspend(std::coroutine_handle<> h) { schedule(h, ms); } + void await_resume() {} + }; + + return awaitable{ms}; +} + +} // namespace async diff --git a/mbv/apps/async/buffer.h b/mbv/apps/async/buffer.h new file mode 100644 index 0000000..3cb4971 --- /dev/null +++ b/mbv/apps/async/buffer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +struct buffer { + std::span data; + + buffer() = default; + buffer(std::span d) : data(d) {} + + static buffer make(size_t size) { + return buffer({new std::byte[size], size}); + } + + buffer(buffer& other) = delete; + buffer& operator=(buffer& other) = delete; + + buffer(buffer&& other) : data(std::exchange(other.data, {})) {} + buffer& operator=(buffer&& other) { + data = std::exchange(other.data, {}); + return *this; + } + + ~buffer() { + if (data.data()) { + delete[] data.data(); + }; + } +}; diff --git a/mbv/apps/async/gpio.h b/mbv/apps/async/gpio.h new file mode 100644 index 0000000..23e4a22 --- /dev/null +++ b/mbv/apps/async/gpio.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +struct Gpio { + volatile uint32_t data; +}; + +#define gpio0 ((Gpio*)0x40000000) + +inline void ToggleLed(int which) { + uint8_t data = gpio0->data; + data ^= (0x1 << which); + gpio0->data = data; +} + +inline void SetLed(int which) { + uint8_t data = gpio0->data; + data |= (0x1 << which); + gpio0->data = data; +} + +inline void ClearLed(int which) { + uint8_t data = gpio0->data; + data &= ~(0x1 << which); + gpio0->data = data; +} diff --git a/mbv/apps/async/itoa.h b/mbv/apps/async/itoa.h new file mode 100644 index 0000000..9b1b35f --- /dev/null +++ b/mbv/apps/async/itoa.h @@ -0,0 +1,13 @@ +#pragma once + +// out must be at least 8 bytes long +inline void itoa(int val, char* out) { + for (int i = 0; i < 8; i++) { + uint8_t digit = (val >> (28 - 4 * i)) & 0xf; + if (digit < 0xa) { + out[i] = '0' + digit; + } else { + out[i] = 'a' + digit - 0xa; + } + } +} diff --git a/mbv/apps/async/lock.cc b/mbv/apps/async/lock.cc new file mode 100644 index 0000000..87ea6d5 --- /dev/null +++ b/mbv/apps/async/lock.cc @@ -0,0 +1,5 @@ +#include "lock.h" + +#ifdef __x86_64__ +std::recursive_mutex InterruptLock::m; +#endif \ No newline at end of file diff --git a/mbv/apps/async/lock.h b/mbv/apps/async/lock.h new file mode 100644 index 0000000..e361a60 --- /dev/null +++ b/mbv/apps/async/lock.h @@ -0,0 +1,21 @@ +#pragma once + +#ifndef __x86_64__ +#include "interrupts.h" + +struct InterruptLock { + bool was_on; + + InterruptLock() : was_on(EnableInterrupts(false)) {} + + ~InterruptLock() { EnableInterrupts(was_on); } +}; +#else // __x86_64__ +#include + +struct InterruptLock { + static std::recursive_mutex m; + InterruptLock() { m.lock(); } + ~InterruptLock() { m.unlock(); } +}; +#endif // __x86_64__ diff --git a/mbv/apps/async/main.cc b/mbv/apps/async/main.cc new file mode 100644 index 0000000..a531eaf --- /dev/null +++ b/mbv/apps/async/main.cc @@ -0,0 +1,141 @@ +#include "async.h" +#include "buffer.h" +#include "gpio.h" +#include "intc.h" +#include "interrupts.h" +#include "pol0.h" +#include "timer.h" +#include "trace.h" +#include "uart.h" +#include "uart_async.h" + +namespace { + +using async::AwaitableType; +Timer* timer0; + +void Uart0Isr() { + ToggleLed(7); + HandleUartIsr(); +} + +void Timer0Isr() { + SetLed(6); + __builtin_trap(); +} + +void SetupUart() { + InitUarts(); + + intc::SetIsr(UART0_IRQN, Uart0Isr); + intc::SetIrqEnabled(UART0_IRQN, true); +} + +void SetupTimer() { + timer0 = Timer::Instance(TIMER0_BASE); + timer0->SetupAsWdt(100'000 * 1000); + timer0->EnableT1(); + + intc::SetIsr(TIMER0_IRQN, Timer0Isr); + intc::SetIrqEnabled(TIMER0_IRQN, true); +} + +void SetupInterrupts() { + intc::EnableInterrupts(); + SetExternalInterruptHandler(intc::InterruptHandler); + EnableExternalInterrupts(); + EnableInterrupts(true); +} + +async::task<> echo() { + async::task reader = UartReadLoop(); + async::gimme> feeder; + async::task<> writer = UartWriteLoop(feeder); + writer.h.resume(); // advance to first yield + while (1) { + SetLed(1); + uint8_t c = co_await reader; + ClearLed(1); + ToggleLed(2); + feeder.feed(std::as_bytes(std::span{&c, 1})); + } +} + +} // namespace + +#define XUL_SR_RX_FIFO_FULL 0x02 /* receive FIFO full */ +#define XUL_SR_RX_FIFO_VALID_DATA 0x01 /* data in receive FIFO */ + +int main() { + SetupUart(); + UartWriteCrash("uart setup done\r\n"); + SetupTimer(); + UartWriteCrash("timer setup done\r\n"); + + gpio0->data = 0; + SetupInterrupts(); + + async::schedule(echo().h); + + UartWriteCrash("init done. starting main loop\r\n"); + + async::main_loop([]() { + static int cnt = 0; + timer0->Pet(); + if ((cnt++ % 100000) == 0) { + ToggleLed(0); + } + return false; + }); + // should never get here +} + +/// stdlib stuff + +#include + +#include + +#include "itoa.h" +#include "lock.h" + +#ifndef SBRK_STATS +#define SBRK_STATS 0 +#endif + +extern unsigned char _heap_begin, _heap_end; + +extern "C" void* _sbrk(int increment) { + static unsigned char* heap = &_heap_begin; + unsigned char* prev_heap = heap; + if (heap + increment >= &_heap_end) { + UartWriteCrash("Heap overflow!\r\n"); + return reinterpret_cast(-1); + } + heap += increment; + return prev_heap; +} + +extern "C" int _gettimeofday(struct timeval* tv, void* tzvp) { + (void)tzvp; + uint32_t ticks = timer0->GetT1Ticks(); + tv->tv_sec = ticks / 100000000; + tv->tv_usec = (ticks % 100000000) / 100; + + return 0; +} + +extern "C" uint8_t __atomic_exchange_1(volatile void* ptr, uint8_t val, + int memorder) { + (void)memorder; + auto* dest = reinterpret_cast(ptr); + bool ret; + + { + InterruptLock lock; + ret = *dest; + *dest = val; + } + + return ret; +} diff --git a/mbv/apps/async/ring_buffer.h b/mbv/apps/async/ring_buffer.h new file mode 100644 index 0000000..56a496e --- /dev/null +++ b/mbv/apps/async/ring_buffer.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include + +#include "lock.h" + +struct RingBuffer { + std::span buffer; + + std::atomic read_ptr = 0; + std::atomic write_ptr = 0; + std::atomic full = 0; + + bool Store(std::span data) { + InterruptLock lock; + + if (data.size() > FreeSpace()) { + return false; + } + const size_t to_copy = std::min(buffer.size() - write_ptr, data.size()); + std::copy(data.begin(), data.begin() + to_copy, + buffer.begin() + write_ptr); + if (to_copy < data.size()) { + std::copy(data.begin() + to_copy, data.end(), buffer.begin()); + } + Push(data.size()); + + return true; + } + + bool Load(std::span out) { + InterruptLock lock; + + if (out.size() > AvailableData()) { + return false; + } + const size_t to_copy = std::min(buffer.size() - read_ptr, out.size()); + std::copy(buffer.begin() + read_ptr, + buffer.begin() + read_ptr + to_copy, out.begin()); + if (to_copy < out.size()) { + std::copy(buffer.begin(), buffer.begin() + out.size() - to_copy, + out.begin() + to_copy); + } + Pop(out.size()); + return true; + } + + bool Push(size_t amount) { + InterruptLock lock; + + if (amount > FreeSpace()) { + return false; + } + write_ptr = (write_ptr + amount) % buffer.size(); + if (read_ptr == write_ptr) { + full = true; + } + return true; + } + + bool Pop(size_t amount) { + InterruptLock lock; + + if (amount > AvailableData()) { + return false; + } + read_ptr = (read_ptr + amount) % buffer.size(); + if (amount > 0) { + full = false; + } + return true; + } + + size_t FreeSpace() const { + InterruptLock lock; + + return buffer.size() - AvailableData(); + } + + size_t AvailableData() const { + InterruptLock lock; + + if (read_ptr == write_ptr) { + return full ? buffer.size() : 0; + } + return (buffer.size() + write_ptr - read_ptr) % buffer.size(); + } + + uint8_t* RawReadPointer() const { + InterruptLock lock; + + return reinterpret_cast(buffer.data() + read_ptr); + } + + size_t ContiguousAvailableData() const { + InterruptLock lock; + + if (read_ptr < write_ptr) { + return AvailableData(); + } + if (full) { + return 0; + } + + return buffer.size() - read_ptr; + } +}; diff --git a/mbv/apps/async/trace.cc b/mbv/apps/async/trace.cc new file mode 100644 index 0000000..f2d2a08 --- /dev/null +++ b/mbv/apps/async/trace.cc @@ -0,0 +1,71 @@ +#include "trace.h" + +#include +#include + +#include "itoa.h" +#include "lock.h" +#include "uart.h" + +namespace tracing { +namespace { + +struct Event { + uint32_t timestamp; + TraceEvent event; +}; + +constexpr size_t kTraceBufferSize = 256; +std::array buffer; +size_t write_ptr = 0; +size_t size = 0; + +} // namespace + +void trace(int raw_event) { trace(static_cast(raw_event)); } + +void trace(TraceEvent event) { + const std::chrono::system_clock::time_point now = + std::chrono::system_clock::now(); + const uint32_t uptime_ticks = now.time_since_epoch().count(); + + { + InterruptLock lock; + + buffer[write_ptr] = {.timestamp = uptime_ticks, .event = event}; + + write_ptr = (write_ptr + 1) % buffer.size(); + size = std::min(size + 1, kTraceBufferSize); + +#if TRACE_DUMP_WHEN_FULL + if (size == kTraceBufferSize) { + dump(); + } +#endif // TRACE_DUMP_WHEN_FULL + } +} + +void dump() { + InterruptLock lock; + + if (size == kTraceBufferSize) { + std::rotate(buffer.begin(), buffer.begin() + write_ptr, buffer.end()); + } + + char number[] = "00000000"; + + UartWriteCrash("----\r\n"); + for (Event event : std::span{buffer}.subspan(0, size)) { + itoa(static_cast(event.timestamp), number); + UartWriteCrash(number); + UartWriteCrash(" "); + itoa(static_cast(event.event), number); + UartWriteCrash(number); + UartWriteCrash("\r\n"); + } + UartWriteCrash("----\r\n"); + + size = 0; + write_ptr = 0; +} +} // namespace tracing diff --git a/mbv/apps/async/trace.h b/mbv/apps/async/trace.h new file mode 100644 index 0000000..a3b4b96 --- /dev/null +++ b/mbv/apps/async/trace.h @@ -0,0 +1,51 @@ +#pragma once + +#define TRACE_DUMP_WHEN_FULL 0 + +#ifdef __x86_64__ +#include +#define TRACE(x) printf(#x "\n") +#else // __x86_64__ +#define TRACE(...) tracing::trace(__VA_ARGS__) +#endif // __x86_64__ + +#include + +namespace tracing { +enum class TraceEvent : uint8_t { + kUnknown = 0, + + kUartIsr = 1, + kUartRxCb = 2, + kUartTxCb = 3, + + kUartSend = 10, + kUartRecv = 11, + kUartTxBufferFull = 12, + kUartTxBufferNotFull = 13, + + kUartWriteDone = 20, + + kAsyncResume = 4, + kAsyncEnqueue = 5, + kAsyncTask = 6, + kAsyncResumeSetPending = 7, + kAsyncAwaitWasNotified = 8, + kAsyncSchedule = 9, + + kAsyncTaskDone = 14, + kAsyncException = 15, + kAsyncCallParent = 16, + kAsyncCallParentDone = 17, + kAsyncCoAwait = 18, + kAsyncSuspend = 19, + kAsyncDestroy = 21, + + kAsyncGimmeWaiting = 22, + kAsyncGimmeResume = 23, +}; + +void trace(TraceEvent event); +void trace(int raw_event); +void dump(); +} // namespace tracing diff --git a/mbv/apps/async/uart.cc b/mbv/apps/async/uart.cc new file mode 100644 index 0000000..fcfcf97 --- /dev/null +++ b/mbv/apps/async/uart.cc @@ -0,0 +1,165 @@ +#include "uart.h" + +#include "async.h" +#include "gpio.h" +#include "lock.h" +#include "pol0.h" +#include "ring_buffer.h" +#include "trace.h" +#include "uart_async.h" +#include "xuartlite.h" + +namespace { +using async::AwaitableType; + +constexpr uintptr_t kUart0BaseAddress = UART0_BASE; +XUartLite uart0_inst; +XUartLite_Config uart0_config = { + .DeviceId = 0, + .RegBaseAddr = kUart0BaseAddress, + .BaudRate = 115200, + .UseParity = false, + .DataBits = 8, +}; + +constexpr size_t kUartTxBufferSize = 256; +std::array tx_buffer = {}; +RingBuffer tx_ring_buffer{.buffer = tx_buffer}; + +XUartLite* uart0 = &uart0_inst; +} // namespace + +void InitUarts() { + XUartLite_CfgInitialize(uart0, &uart0_config, uart0_config.RegBaseAddr); + + XUartLite_SetSendHandler(uart0, HandleUartTxFromIsr, nullptr); + XUartLite_SetRecvHandler(uart0, HandleUartRxFromIsr, nullptr); + XUartLite_EnableInterrupt(uart0); +} + +void UartWriteCrash(std::span data) { + while (data.size() > 0) { + while (XUartLite_IsSending(uart0)) { + } + auto* dat = + reinterpret_cast(const_cast(data.data())); + uint8_t sent = XUartLite_Send(uart0, dat, data.size()); + data = data.subspan(sent); + } + while (XUartLite_IsSending(uart0)) { + } +} + +async::task<> UartWrite(std::span data) { + while (!tx_ring_buffer.Store(data)) { + tracing::trace(tracing::TraceEvent::kUartTxBufferFull); + co_await async::await(AwaitableType::kUartTx); + tracing::trace(tracing::TraceEvent::kUartTxBufferNotFull); + } + + { + InterruptLock lock; + if (!XUartLite_IsSending(uart0)) { + tracing::trace(tracing::TraceEvent::kUartSend); + XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + } +} + +async::task<> UartWriteLoop( + async::gimme>& data_gen) { + while (1) { + auto data = co_await data_gen; + while (!tx_ring_buffer.Store(data)) { + tracing::trace(tracing::TraceEvent::kUartTxBufferFull); + co_await async::await(AwaitableType::kUartTx); + tracing::trace(tracing::TraceEvent::kUartTxBufferNotFull); + } + + { + InterruptLock lock; + if (!XUartLite_IsSending(uart0)) { + tracing::trace(tracing::TraceEvent::kUartSend); + XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + } + } +} + +void UartReadBlocking(std::span data) { + size_t bytes_received = 0; + while (bytes_received < data.size()) { + auto* buffer = reinterpret_cast(data.data() + bytes_received); + tracing::trace(tracing::TraceEvent::kUartRecv); + bytes_received += + XUartLite_Recv(uart0, buffer, data.size() - bytes_received); + } +} + +void UartWriteBlocking(std::span data) { + while (!tx_ring_buffer.Store(data)) { + } + + { + InterruptLock lock; + if (!XUartLite_IsSending(uart0)) { + tracing::trace(tracing::TraceEvent::kUartSend); + XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + } +} + +async::task UartReadLoop() { + uint8_t c; + while (1) { + tracing::trace(tracing::TraceEvent::kUartRecv); + size_t received = XUartLite_Recv(uart0, &c, 1); + // some data may already be in the fifo, but if not, wait: + if (received < 1) { + co_await async::await(AwaitableType::kUartRx); + } + + co_yield c; + } +} + +async::task UartRead(int size) { + auto buff = buffer::make(size); + auto* data = reinterpret_cast(buff.data.data()); + tracing::trace(tracing::TraceEvent::kUartRecv); + size_t received = XUartLite_Recv(uart0, data, buff.data.size()); + // some data may already be in the fifo, but if not, wait: + if (received < buff.data.size()) { + co_await async::await(AwaitableType::kUartRx); + } + co_return buff; +} + +void HandleUartTxFromIsr(void*, unsigned int transmitted) { + tx_ring_buffer.Pop(transmitted); + if (tx_ring_buffer.AvailableData() > 0) { + tracing::trace(tracing::TraceEvent::kUartSend); + XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + async::resume(AwaitableType::kUartTx); +} + +void HandleUartRxFromIsr(void*, unsigned int) { + async::resume(AwaitableType::kUartRx); +} + +void HandleUartIsr() { XUartLite_InterruptHandler(uart0); } + +extern "C" uint8_t XUartLite_GetSR(XUartLite*); + +uint8_t UartStatus() { return XUartLite_GetSR(uart0); } + +void LogStuff() { + uint8_t data = gpio0->data; + data |= (uart0->ReceiveBuffer.RemainingBytes & 0xf) << 4; + gpio0->data = data; +} diff --git a/mbv/apps/async/uart.h b/mbv/apps/async/uart.h new file mode 100644 index 0000000..f6d3a37 --- /dev/null +++ b/mbv/apps/async/uart.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +void InitUarts(); + +// block until the provided buffer is full +void UartReadBlocking(std::span data); +inline uint8_t UartReadByteBlocking() { + std::byte byte; + UartReadBlocking(std::span{&byte, 1}); + return static_cast(byte); +} + +// send and poll the uart until transmitted +void UartWriteCrash(std::span data); +inline void UartWriteCrash(std::string_view s) { + return UartWriteCrash(std::as_bytes(std::span{s.data(), s.size()})); +} + +// block until room is available in tx fifo, then send +void UartWriteBlocking(std::span data); +inline void UartWriteBlocking(std::string_view s) { + return UartWriteBlocking(std::as_bytes(std::span{s.data(), s.size()})); +} + +void HandleUartTxFromIsr(void*, unsigned int transmitted); +void HandleUartRxFromIsr(void*, unsigned int); +void HandleUartIsr(); + +uint8_t UartStatus(); + +void LogStuff(); diff --git a/mbv/apps/async/uart_async.h b/mbv/apps/async/uart_async.h new file mode 100644 index 0000000..e6912aa --- /dev/null +++ b/mbv/apps/async/uart_async.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "async.h" +#include "buffer.h" + +async::task UartRead(int size); +async::task UartReadLoop(); +async::task<> UartWrite(std::span data); +inline async::task<> UartWrite(std::string_view s) { + co_await UartWrite(std::as_bytes(std::span{s.data(), s.size()})); +} +async::task<> UartWriteLoop(async::gimme>& data); diff --git a/mbv/configure b/mbv/configure index 0b50a8d..d32f559 100755 --- a/mbv/configure +++ b/mbv/configure @@ -283,9 +283,11 @@ bootloader_image = build_image( linker_script="bootloader/bootloader.ld", ) -def app_image(app): +def app_image(app, sources=None): + if sources is None: + sources = glob.glob(f"./apps/{app}/**/*.cc", recursive=True) return build_image( - source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)), + source_set(app, sources), linker_script="apps/app.ld", dependencies=[hal], elf_out=f"{app}.elf", @@ -299,6 +301,13 @@ all = [ app_image("helloworld"), app_image("timer"), app_image("uart"), + app_image("async", sources=[ + "apps/async/async.cc", + "apps/async/lock.cc", + "apps/async/main.cc", + "apps/async/trace.cc", + "apps/async/uart.cc", + ]), ]