mbv: move a few things around

This commit is contained in:
2025-09-04 15:01:50 +02:00
parent dd51b5d610
commit 9edebe637b
58 changed files with 68 additions and 494 deletions

158
mbv/lib/async.cc Normal file
View File

@@ -0,0 +1,158 @@
#include "async.h"
#include <array>
#include <atomic>
#include <chrono>
#include <utility>
#include "lock.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<Stuff*> work = nullptr;
std::array<Notification, static_cast<size_t>(AwaitableType::kNumTypes)>
notifications = {};
} // namespace
void schedule(std::coroutine_handle<> h, int ms) {
InterruptLock lock;
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++;
stuff->h();
{
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<size_t>(type);
{
InterruptLock lock;
const bool was_notified =
std::exchange(notifications[ttype].pending, false);
if (was_notified) {
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<size_t>(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

193
mbv/lib/async.h Normal file
View File

@@ -0,0 +1,193 @@
#pragma once
#include <chrono>
#include <coroutine>
#include <utility>
namespace async {
struct continuation : std::suspend_always {
void await_suspend(std::coroutine_handle<>) noexcept {
if (parent) {
parent.resume();
}
}
std::coroutine_handle<> parent;
};
template <typename T = void>
struct task;
template <>
struct task<void> {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
~task() {
if (h) {
h.destroy();
}
}
struct promise_type {
task get_return_object() {
return {.h = handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
continuation final_suspend() noexcept { return {.parent = parent}; }
void return_void() {}
void unhandled_exception() {}
std::coroutine_handle<> parent;
};
// awaitable
bool await_ready() {
h.promise().parent = {};
h.resume();
if (h.done()) {
return true;
}
return false;
}
void await_suspend(std::coroutine_handle<> ha) { h.promise().parent = ha; }
void await_resume() {}
handle_type h;
};
template <typename T>
struct task {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
~task() {
if (h) {
h.destroy();
}
}
struct promise_type {
task get_return_object() {
return {.h = handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
continuation final_suspend() noexcept { return {.parent = parent}; }
void return_value(T&& value) { ret_value = std::move(value); }
void unhandled_exception() {}
template <std::convertible_to<T> From>
continuation yield_value(From&& value) {
ret_value = std::forward<From>(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 = {};
h.resume();
if (h.promise().result_ready || h.done()) {
return true;
}
return false;
}
void await_suspend(std::coroutine_handle<> ha) { h.promise().parent = ha; }
T await_resume() {
h.promise().result_ready = false;
return std::move(h.promise().ret_value);
}
std::coroutine_handle<promise_type> 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};
}
template <typename T>
struct gimme {
// child interface
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
ha = h;
waiting = true;
if (parent) {
schedule(parent);
}
}
T await_resume() {
waiting = false;
return std::move(stuff);
}
// parent interface
auto feed(T&& s) {
struct awaitable {
bool await_ready() {
g.parent = {};
g.ha.resume();
return g.waiting;
}
void await_suspend(std::coroutine_handle<> h) { g.parent = h; }
void await_resume() {}
gimme<T>& g;
};
if (!waiting) {
__builtin_trap();
}
if (!ha) {
__builtin_trap();
}
stuff = std::move(s);
return awaitable{.g = *this};
}
bool waiting = false;
std::coroutine_handle<> ha;
std::coroutine_handle<> parent;
T stuff;
};
} // namespace async

30
mbv/lib/buffer.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <span>
#include <utility>
struct buffer {
std::span<std::byte> data;
buffer() = default;
buffer(std::span<std::byte> 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();
};
}
};

13
mbv/lib/itoa.h Normal file
View File

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

5
mbv/lib/lock.cc Normal file
View File

@@ -0,0 +1,5 @@
#include "lock.h"
#ifdef __x86_64__
std::recursive_mutex InterruptLock::m;
#endif

21
mbv/lib/lock.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#ifndef __x86_64__
#include "hal/interrupts.h"
struct InterruptLock {
bool was_on;
InterruptLock() : was_on(EnableInterrupts(false)) {}
~InterruptLock() { EnableInterrupts(was_on); }
};
#else // __x86_64__
#include <mutex>
struct InterruptLock {
static std::recursive_mutex m;
InterruptLock() { m.lock(); }
~InterruptLock() { m.unlock(); }
};
#endif // __x86_64__

105
mbv/lib/ring_buffer.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include <span>
#include "lock.h"
struct RingBuffer {
std::span<std::byte> buffer;
size_t read_ptr = 0;
size_t write_ptr = 0;
size_t used = 0;
bool Store(std::span<const std::byte> 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<std::byte> 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();
used = used + amount;
return true;
}
bool Pop(size_t amount) {
InterruptLock lock;
if (amount > AvailableData()) {
return false;
}
read_ptr = (read_ptr + amount) % buffer.size();
used = used - amount;
return true;
}
size_t FreeSpace() const {
InterruptLock lock;
return buffer.size() - used;
}
size_t AvailableData() const {
InterruptLock lock;
return used;
}
uint8_t* RawReadPointer() const {
InterruptLock lock;
return reinterpret_cast<uint8_t*>(buffer.data() + read_ptr);
}
uint8_t* RawWritePointer() const {
InterruptLock lock;
return reinterpret_cast<uint8_t*>(buffer.data() + write_ptr);
}
size_t ContiguousFreeSpace() const {
InterruptLock lock;
return std::min(FreeSpace(), buffer.size() - write_ptr);
}
size_t ContiguousAvailableData() const {
InterruptLock lock;
return std::min(AvailableData(), buffer.size() - read_ptr);
}
};