Compare commits

...

10 Commits

Author SHA1 Message Date
8c25ce8d21 arm: cleanup dead async code 2023-06-07 09:59:54 +02:00
83c80f15be arm: cleaner make clean 2023-06-03 20:41:16 -07:00
3c21dbfba3 arm: builds for embedded 2023-06-03 15:44:28 +09:00
f274749050 arm: add host uart driver tests
All host tests currently pass
Some async refactors may not work well on device, will try later
2023-06-02 23:33:01 -07:00
1f2f08e525 arm: cleanup make clean 2022-06-19 09:57:04 +02:00
4651e8d562 alchitry-loader: add bit2bin script 2022-06-19 09:50:54 +02:00
9721ee69c5 arm: async debug wip 2022-06-19 09:50:22 +02:00
02fbb1c671 arm: move trace dump to crash handler 2022-06-19 09:48:28 +02:00
ec024adfff arm: interrupt lock fix 2022-06-19 09:46:30 +02:00
61b92f5faa arm: bool idle function return (fixup) 2022-06-19 09:46:04 +02:00
17 changed files with 626 additions and 167 deletions

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
import argparse
import struct
def flip32(data: bytes):
sl = struct.Struct('<I')
sb = struct.Struct('>I')
d = bytearray(len(data))
for offset in range(0, len(data), 4):
sb.pack_into(d, offset, sl.unpack_from(data, offset)[0])
return d
def parse_args():
parser = argparse.ArgumentParser(description='Convert FPGA bit files to raw bin format suitable for flashing')
parser.add_argument('-f', '--flip', dest='flip', action='store_true', default=False, help='Flip 32-bit endianess (needed for Zynq)')
parser.add_argument("bitfiletemplate", help="Input bit file name from which the header is extracted")
parser.add_argument("binfile", help="Input bin file name from which the bitstream is extracted")
parser.add_argument("bitfile", help="Output bit file name")
return parser.parse_args()
def main():
args = parse_args()
short = struct.Struct('>H')
ulong = struct.Struct('>I')
bitfile = open(args.bitfiletemplate, 'rb')
headerLength = 0
l = short.unpack(bitfile.read(2))[0]
headerLength = headerLength + 2
if l != 9:
raise Exception("Missing <0009> header (0x%x), not a bit file" % l)
bitfile.read(l)
headerLength = headerLength + l
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
headerLength = headerLength + l + 2
if d != b'a':
raise Exception("Missing <a> header, not a bit file")
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
headerLength = headerLength + l +2
print("Design name: %s" % d)
KEYNAMES = {b'b': "Partname", b'c': "Date", b'd': "Time"}
while 1:
k = bitfile.read(1)
headerLength = headerLength + 1
if not k:
bitfile.close()
raise Exception("unexpected EOF")
elif k == b'e':
l = ulong.unpack(bitfile.read(4))[0]
headerLength = headerLength + 4
print("Found header end: %d" % headerLength)
break
elif k in KEYNAMES:
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
headerLength = headerLength + l + 2
print(KEYNAMES[k], d)
else:
print("Unexpected key: %s" % k)
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
headerLength = headerLength + l + 2
outputfile = open(args.bitfile, 'wb+')
bitfile.seek(0)
outputfile.write(bitfile.read(headerLength))
bitfile.close()
binfile = open( args.binfile, 'rb')
d = binfile.read()
binfile.close()
if args.flip:
print("Flipping data...")
d = flip32(d)
outputfile.write(d)
outputfile.close()
if __name__ == "__main__":
main()

View File

@ -5,21 +5,8 @@
#include <chrono> #include <chrono>
#include <utility> #include <utility>
#include "trace.h"
#ifdef __x86_64__
#include <mutex>
struct InterruptLock {
static std::mutex m;
InterruptLock() { m.lock(); }
~InterruptLock() { m.unlock(); }
};
std::mutex InterruptLock::m;
#else // __x86_64__
#include "lock.h" #include "lock.h"
#include "trace.h"
using tracing::trace;
#endif // __x86_64__
namespace async { namespace async {
namespace { namespace {
@ -38,13 +25,14 @@ struct Notification {
Stuff* stuff; Stuff* stuff;
}; };
std::atomic<Stuff*> work; std::atomic<Stuff*> work = nullptr;
std::array<Notification, static_cast<size_t>(AwaitableType::kNumTypes)> std::array<Notification, static_cast<size_t>(AwaitableType::kNumTypes)>
notifications = {}; notifications = {};
} // namespace } // namespace
void schedule(std::coroutine_handle<> h, int ms) { void schedule(std::coroutine_handle<> h, int ms) {
InterruptLock lock;
TRACE(tracing::TraceEvent::kAsyncSchedule); TRACE(tracing::TraceEvent::kAsyncSchedule);
std::chrono::system_clock::time_point exp = std::chrono::system_clock::time_point exp =
std::chrono::system_clock::now() + std::chrono::milliseconds(ms); std::chrono::system_clock::now() + std::chrono::milliseconds(ms);
@ -70,38 +58,64 @@ void schedule(std::coroutine_handle<> h, int ms) {
s->next = news; 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)()) { void main_loop(bool (*idle_function)()) {
while (1) { while (1) {
if (idle_function != nullptr) { if (idle_function != nullptr) {
if (idle_function()) { if (idle_function()) {
reset();
break; break;
}; };
} }
Stuff* stuff = work;
if (stuff == nullptr) {
continue; // busyloop
}
auto now = std::chrono::system_clock::now(); step();
auto dt = stuff->expiration - now;
if (dt > 0ms) {
continue;
}
TRACE(tracing::TraceEvent::kAsyncTask);
stuff->h();
TRACE(tracing::TraceEvent::kAsyncTaskDone);
if (stuff->h.done()) {
stuff->h.destroy();
}
{
InterruptLock lock;
work = stuff->next;
}
delete stuff;
} }
} }
@ -151,31 +165,4 @@ void resume(AwaitableType type) {
delete stuff; delete stuff;
} }
} // namespace async } // namespace async
#if 0
task<buffer> readline() {
int size = 0;
char c;
buffer buff = buffer::make(32);
// fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
while (true) {
int n = read(0, &c, 1);
if (n < 1) {
co_await co_waitio();
continue;
}
buff.data[size++] = static_cast<std::byte>(c);
if (c == '\n') {
buff.data = buff.data.subspan(0, size);
co_return buff;
}
}
}
#endif // 0

View File

@ -1,19 +1,11 @@
#pragma once #pragma once
#include <coroutine>
#include <chrono> #include <chrono>
#include <utility> #include <utility>
#include "trace.h" #include "trace.h"
#ifdef __clang__
#include <experimental/coroutine>
namespace std {
using namespace experimental;
}
#else // __clang__
#include <coroutine>
#endif // __clang__
namespace async { namespace async {
struct task_final_suspend { struct task_final_suspend {
@ -23,6 +15,11 @@ struct task_final_suspend {
TRACE(tracing::TraceEvent::kAsyncCallParent); TRACE(tracing::TraceEvent::kAsyncCallParent);
parent(); parent();
TRACE(tracing::TraceEvent::kAsyncCallParentDone); TRACE(tracing::TraceEvent::kAsyncCallParentDone);
if (parent && parent.done()) {
TRACE(tracing::TraceEvent::kAsyncDestroy);
parent.destroy();
}
} }
} }
void await_resume() noexcept(true) {} void await_resume() noexcept(true) {}
@ -38,12 +35,6 @@ struct gimme {
ha = h; ha = h;
waiting = true; waiting = true;
TRACE(tracing::TraceEvent::kAsyncGimmeWaiting); TRACE(tracing::TraceEvent::kAsyncGimmeWaiting);
if (parent) {
TRACE(tracing::TraceEvent::kAsyncCallParent);
parent.resume();
TRACE(tracing::TraceEvent::kAsyncCallParentDone);
}
} }
T await_resume() { T await_resume() {
waiting = false; waiting = false;
@ -52,23 +43,7 @@ struct gimme {
} }
// parent interface // parent interface
auto feed(T&& s) { void feed(T&& s) {
struct awaitable {
bool await_ready() {
if (g.waiting) {
return true;
}
// TODO: handle g.ha.done()
return false;
}
void await_suspend(std::coroutine_handle<> h) {
g.parent = h;
}
void await_resume() {}
gimme<T>& g;
};
if (!waiting) { if (!waiting) {
__builtin_trap(); __builtin_trap();
} }
@ -76,16 +51,12 @@ struct gimme {
__builtin_trap(); __builtin_trap();
} }
stuff = s; stuff = s;
parent = {};
ha.resume(); ha.resume();
return awaitable{.g = *this};
} }
bool waiting = false; bool waiting = false;
std::coroutine_handle<> ha; std::coroutine_handle<> ha;
T stuff; T stuff;
std::coroutine_handle<> parent;
}; };
template <typename T = void> template <typename T = void>
@ -117,7 +88,8 @@ struct task<void> {
TRACE(tracing::TraceEvent::kAsyncCoAwait); TRACE(tracing::TraceEvent::kAsyncCoAwait);
h(); h();
if (h.done()) { if (h.done()) {
TRACE(tracing::TraceEvent::kAsyncDestroy); h.destroy(); TRACE(tracing::TraceEvent::kAsyncDestroy);
h.destroy();
return true; return true;
} }
return false; return false;
@ -205,7 +177,8 @@ void schedule(std::coroutine_handle<> h, int ms = 0);
void enqueue(std::coroutine_handle<> h, AwaitableType type); void enqueue(std::coroutine_handle<> h, AwaitableType type);
void resume(AwaitableType type); // typically called from an ISR void resume(AwaitableType type); // typically called from an ISR
void main_loop(void (*idle_function)()); void main_loop(bool (*idle_function)());
void step();
inline auto await(AwaitableType type) { inline auto await(AwaitableType type) {
struct awaitable { struct awaitable {

View File

@ -7,6 +7,7 @@
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <cstdio> #include <cstdio>
#include <mutex>
#include <semaphore> #include <semaphore>
#include <thread> #include <thread>
@ -28,11 +29,14 @@ int uptime() {
template <typename... Args> template <typename... Args>
void log(int i, const char* fmt, Args... args) { void log(int i, const char* fmt, Args... args) {
if (i != 4) { if (i != 3) {
return; return;
} }
printf("[%10i] ", uptime()); printf("[%10i] ", uptime());
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
printf(fmt, args...); printf(fmt, args...);
#pragma clang diagnostic pop
printf("\n"); printf("\n");
} }
@ -124,6 +128,7 @@ TEST_F(LoopAsyncTest, ManyEnqueueResume) {
} }
EXPECT_EQ(done, kLoops); EXPECT_EQ(done, kLoops);
log(3, "got all of em, done");
terminate = true; terminate = true;
t.join(); t.join();
@ -196,10 +201,14 @@ std::binary_semaphore NestedAsync::next(0);
std::binary_semaphore NestedAsync::ready(0); std::binary_semaphore NestedAsync::ready(0);
TEST_F(NestedAsync, SimpleNestedTest) { TEST_F(NestedAsync, SimpleNestedTest) {
while (next.try_acquire());
while (ready.try_acquire());
async::schedule(nested().h); async::schedule(nested().h);
std::thread t([]() { std::thread t([]() {
async::main_loop([]() -> bool { async::main_loop([]() -> bool {
next.acquire(); // for some reason the bare `acquire` fails sometimes :/
while (!next.try_acquire_for(10ms));
return terminate; return terminate;
}); });
}); });
@ -222,39 +231,23 @@ TEST_F(NestedAsync, SimpleNestedTest) {
TEST_F(NestedAsync, LessSimpleNestedTest) { TEST_F(NestedAsync, LessSimpleNestedTest) {
async::schedule(othernested().h); async::schedule(othernested().h);
std::thread t([]() {
async::main_loop([]() -> bool {
ready.release();
log(3, "loop ready");
next.acquire();
return terminate;
});
});
ASSERT_FALSE(voiddone); ASSERT_FALSE(voiddone);
ASSERT_EQ(result, 0); ASSERT_EQ(result, 0);
log(3, "starting stuff"); log(3, "step");
async::step(); // should proceed until yield
ASSERT_TRUE(ready.try_acquire_for(1s));
next.release();
ASSERT_TRUE(ready.try_acquire_for(1s));
ASSERT_FALSE(voiddone); ASSERT_FALSE(voiddone);
ASSERT_EQ(result, 0); ASSERT_EQ(result, 0);
next.release(); log(3, "step");
ASSERT_TRUE(ready.try_acquire_for(1s)); async::step(); // should proceed until end
EXPECT_EQ(voiddone, true); EXPECT_EQ(voiddone, true);
ASSERT_EQ(result, 42); ASSERT_EQ(result, 42);
EXPECT_TRUE(done.try_acquire_for(1s)); EXPECT_TRUE(done.try_acquire_for(1s));
terminate = true;
next.release();
t.join();
} }
TEST_F(NestedAsync, GeneratorTest) { TEST_F(NestedAsync, GeneratorTest) {

View File

@ -3,6 +3,7 @@
#include "gpio.h" #include "gpio.h"
#include "itoa.h" #include "itoa.h"
#include "sleep.h" #include "sleep.h"
#include "trace.h"
#include "uart.h" #include "uart.h"
extern "C" uint32_t _initial_stack_pointer, _text_begin, _text_end; extern "C" uint32_t _initial_stack_pointer, _text_begin, _text_end;
@ -80,6 +81,8 @@ void CrashHandler(Armv6mRegs* regs) {
UartWriteCrash("- Stack trace:\r\n"); UartWriteCrash("- Stack trace:\r\n");
StackTrace(reinterpret_cast<uint32_t*>(regs->sp)); StackTrace(reinterpret_cast<uint32_t*>(regs->sp));
tracing::dump();
while (1) { while (1) {
gpio0->data = 0x55; gpio0->data = 0x55;
sleep(100); sleep(100);

5
arm/dev.dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM ubuntu
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y make clang libgmock-dev gdb && \
apt-get clean && rm -rf /var/lib/apt/lists

202
arm/fake_uart.cc Normal file
View File

@ -0,0 +1,202 @@
#include "uart.h"
#include "async.h"
#include "buffer.h"
#include "ring_buffer.h"
#include <array>
#include <mutex>
#include <vector>
namespace {
using async::AwaitableType;
constexpr size_t kUartTxBufferSize = 256;
std::array<std::byte, kUartTxBufferSize> tx_buffer = {};
RingBuffer tx_ring_buffer{.buffer = tx_buffer};
uint8_t* tx_buff = nullptr;
uint16_t tx_size = 0;
uint16_t tx_size_orig = 0;
uint8_t* rx_buff = nullptr;
uint16_t rx_size = 0;
uint16_t rx_size_orig = 0;
std::vector<std::byte> rx_overflow;
std::mutex interrupt_lock;
std::mutex fu_mutex;
} // namespace
//// Internal API
void FakeUart_Send(uint8_t* ptr, uint16_t size) {
std::scoped_lock lock(fu_mutex);
tx_buff = ptr;
tx_size = size;
tx_size_orig = size;
}
size_t FakeUart_Recv(uint8_t* ptr, uint16_t size) {
std::scoped_lock lock(fu_mutex);
if (size <= rx_overflow.size()) {
std::copy(rx_overflow.begin(), rx_overflow.begin() + size,
std::as_writable_bytes(std::span{ptr, size}).begin());
std::rotate(rx_overflow.begin(), rx_overflow.begin() + size, rx_overflow.end());
rx_overflow.resize(rx_overflow.size() - size);
return size;
}
rx_buff = ptr;
rx_size = size;
if (rx_overflow.empty()) {
return 0;
}
std::copy(rx_overflow.begin(), rx_overflow.end(),
std::as_writable_bytes(std::span{ptr, size}).begin());
size_t copied = rx_overflow.size();
rx_overflow.clear();
rx_buff += copied;
rx_size -= copied;
return copied;
}
uint8_t FakeUart_IsSending() {
std::scoped_lock lock(fu_mutex);
return tx_size > 0;
}
//// Backdoor access API
/// Those do trigger UART interrupts when tx/rx buffer state changes
buffer FakeUart_Drain(uint16_t size) {
std::scoped_lock isr_lock(interrupt_lock); // not really the same, but that'll do
std::scoped_lock lock(fu_mutex);
size_t drained = std::min(size, tx_size);
if (drained < 1) {
return buffer{};
}
buffer buff = buffer::make(drained);
auto txb = std::as_bytes(std::span{tx_buff, tx_size});
std::copy(txb.begin(), txb.begin() + drained, buff.data.begin());
tx_buff += drained;
tx_size -= drained;
if (tx_size < 1) {
fu_mutex.unlock();
HandleUartTxFromIsr(nullptr, tx_size_orig);
fu_mutex.lock();
}
return buff;
}
void FakeUart_Feed(std::span<const std::byte> data) {
std::scoped_lock lock(fu_mutex);
if (data.empty()) {
return;
}
auto rxb = std::as_writable_bytes(std::span{rx_buff, rx_size});
size_t fed = std::min(static_cast<size_t>(rx_size), data.size());
if (data.size() > fed) {
rx_overflow.insert(rx_overflow.end(), data.begin() + fed, data.end());
}
if (fed > 0) {
std::copy(data.begin(), data.begin() + fed, rxb.begin());
rx_buff += fed;
rx_size -= fed;
if (rx_size < 1) {
HandleUartRxFromIsr(nullptr, rx_size_orig);
}
}
}
void FakeUart_Reset() {
rx_overflow.clear();
rx_buff = nullptr;
rx_size = 0;
tx_buff = nullptr;
tx_size = 0;
}
//// Public API
void InitUarts() {}
void UartWriteCrash(std::span<const std::byte>) {}
void LogStuff() {}
void UartReadBlocking(std::span<std::byte>) {}
void UartWriteBlocking(std::span<const std::byte>) {}
void HandleUartIsr() {}
async::task<buffer> UartRead(int size) {
co_return buffer{};
}
void HandleUartTxFromIsr(void*, unsigned int transmitted) {
tx_ring_buffer.Pop(transmitted);
if (tx_ring_buffer.AvailableData() > 0 && !FakeUart_IsSending()) {
FakeUart_Send(tx_ring_buffer.RawReadPointer(),
tx_ring_buffer.ContiguousAvailableData());
}
async::resume(AwaitableType::kUartTx);
}
void HandleUartRxFromIsr(void*, unsigned int) {
async::resume(AwaitableType::kUartRx);
}
async::task<> UartWrite(std::span<const std::byte> data) {
while (!tx_ring_buffer.Store(data)) {
co_await async::await(AwaitableType::kUartTx);
}
{
std::scoped_lock lock(interrupt_lock);
if (!FakeUart_IsSending()) {
FakeUart_Send(tx_ring_buffer.RawReadPointer(),
tx_ring_buffer.ContiguousAvailableData());
}
}
}
async::task<> UartWriteLoop(async::gimme<std::span<const std::byte>>& data_gen) {
while (1) {
auto data = co_await data_gen;
while (!tx_ring_buffer.Store(data)) {
co_await async::await(AwaitableType::kUartTx);
}
{
std::scoped_lock lock(interrupt_lock);
if (tx_ring_buffer.AvailableData() && !FakeUart_IsSending()) {
FakeUart_Send(tx_ring_buffer.RawReadPointer(),
tx_ring_buffer.ContiguousAvailableData());
}
}
}
}
async::task<uint8_t> UartReadLoop() {
uint8_t c;
while (1) {
size_t received = FakeUart_Recv(&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;
}
}

10
arm/fake_uart.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
#include <span>
#include "buffer.h"
buffer FakeUart_Drain(uint16_t size);
void FakeUart_Feed(std::span<const std::byte> data);
void FakeUart_Reset();

5
arm/lock.cc Normal file
View File

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

View File

@ -1,15 +1,23 @@
#pragma once #pragma once
#ifndef __x86_64__
#include "aum1_cm1.h" #include "aum1_cm1.h"
struct InterruptLock { struct InterruptLock {
bool was_locked; uint32_t old_primask;
InterruptLock() : was_locked(__get_PRIMASK() != 0) { __disable_irq(); } InterruptLock() : old_primask(__get_PRIMASK()) { __disable_irq(); }
~InterruptLock() { ~InterruptLock() {
if (!was_locked) { __set_PRIMASK(old_primask);
__enable_irq();
}
} }
}; };
#else // __x86_64__
#include <mutex>
struct InterruptLock {
static std::recursive_mutex m;
InterruptLock() { m.lock(); }
~InterruptLock() { m.unlock(); }
};
#endif // __x86_64__

View File

@ -14,14 +14,12 @@ namespace {
using async::AwaitableType; using async::AwaitableType;
void Uart0Isr() { void Uart0Isr() {
tracing::trace(tracing::TraceEvent::kUartIsr); ToggleLed(7);
//tracing::trace(tracing::TraceEvent::kUartIsr);
HandleUartIsr(); HandleUartIsr();
} }
void Timer0Isr() { void Timer0Isr() { __builtin_trap(); }
tracing::dump();
__builtin_trap();
}
void SetupUart() { void SetupUart() {
InitUarts(); InitUarts();
@ -32,35 +30,81 @@ void SetupUart() {
} }
void SetupTimer() { void SetupTimer() {
timer0->SetupAsWdt(100000 * 4000); timer0->SetupAsWdt(100000 * 1); //4000);
timer0->EnableT1(); timer0->EnableT1();
_vector_table[16 + Timer0_IRQn] = reinterpret_cast<uint32_t>(Timer0Isr); _vector_table[16 + Timer0_IRQn] = reinterpret_cast<uint32_t>(Timer0Isr);
NVIC_EnableIRQ(Timer0_IRQn); NVIC_EnableIRQ(Timer0_IRQn);
} }
#if 1 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133
async::task<> echo() { async::task<> echo() {
async::task<uint8_t> reader = UartReadLoop();
async::gimme<std::span<const std::byte>> feeder;
async::task<> writer = UartWriteLoop(feeder);
writer.h.resume(); // advance to first yield
while (1) { while (1) {
buffer buff = co_await UartRead(1); SetLed(1);
co_await UartWrite(buff.data); uint8_t c = co_await reader;
ClearLed(1);
ToggleLed(2);
feeder.feed(std::as_bytes(std::span{&c, 1}));
}
}
#else
async::task<> echo() {
async::task<uint8_t> reader = UartReadLoop();
while (1) {
SetLed(1);
uint8_t c = co_await reader;
ClearLed(1);
ToggleLed(2);
co_await UartWrite(std::as_bytes(std::span{&c, 1}));
}
}
#endif
[[maybe_unused]]
async::task<> dump_trace() {
while (1) {
co_await async::delay(5000);
// tracing::dump();
} }
} }
} // namespace } // 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() { int main() {
_vector_table[16 + HardFault_IRQn] = _vector_table[16 + HardFault_IRQn] =
reinterpret_cast<uint32_t>(crash::HardFaultHandler); reinterpret_cast<uint32_t>(crash::HardFaultHandler);
SetupUart(); SetupUart();
UartWriteBlocking("uart setup done\r\n");
SetupTimer(); SetupTimer();
UartWriteBlocking("timer setup done\r\n");
async::schedule(echo().h); async::schedule(echo().h);
// async::schedule(dump_trace().h);
tracing::trace(tracing::TraceEvent::kUartIsr);
tracing::trace(tracing::TraceEvent::kUartIsr);
UartWriteBlocking("init done. starting main loop\r\n");
async::main_loop([]() { async::main_loop([]() {
static int cnt = 0; static int cnt = 0;
timer0->Pet(); timer0->Pet();
if ((cnt++ % 100000) == 0) { if ((cnt++ % 100000) == 0) {
ToggleLed(0); ToggleLed(0);
ClearLed(7);
ClearLed(2);
ClearLed(3);
ClearLed(4);
LogStuff();
} }
return false; return false;
}); });

View File

@ -4,7 +4,7 @@ CXX = arm-none-eabi-g++
OBJCOPY = arm-none-eabi-objcopy OBJCOPY = arm-none-eabi-objcopy
linker_script = bootloader.ld linker_script = bootloader.ld
includes = includes =
sources = sources =
CFLAGS = -march=armv6-m -g -ffunction-sections -fdata-sections -Os -Werror -Wall -flto CFLAGS = -march=armv6-m -g -ffunction-sections -fdata-sections -Os -Werror -Wall -flto
@ -43,10 +43,7 @@ app.elf: $(app_objects) app.ld
deps = $(app_objects:.o=.d) $(bootloader_objects:.o=.d) deps = $(app_objects:.o=.d) $(bootloader_objects:.o=.d)
clean: HOSTCXX = clang++
rm -rf *.elf *.bin $(tests) $(app_objects) $(bootloader_objects) $(deps)
HOSTCXX = /usr/local/opt/llvm/bin/clang++
HOSTLDFLAGS = -lgmock -lgtest -lgtest_main -L/usr/local/opt/llvm/lib -L/usr/local/lib HOSTLDFLAGS = -lgmock -lgtest -lgtest_main -L/usr/local/opt/llvm/lib -L/usr/local/lib
HOSTCFLAGS = -std=c++20 -g\ HOSTCFLAGS = -std=c++20 -g\
-I/usr/local/opt/llvm/include \ -I/usr/local/opt/llvm/include \
@ -57,31 +54,39 @@ HOSTCFLAGS = -std=c++20 -g\
TSAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=thread TSAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=thread
ASAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=address -fsanitize=leak ASAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=address -fsanitize=leak
tests = ring_buffer_test async_test_asan async_test_tsan tests = ring_buffer_test.run async_test_asan.run async_test_tsan.run uart_test_tsan.run
.PRECIOUS: $(tests)
test: $(tests) test: $(tests)
ring_buffer_test: ring_buffer_test.cc test/async_test_asan: async_test.cc async.host.o lock.host.o
mkdir test test/async_test_tsan: async_test.cc async.host.o lock.host.o
$(HOSTCXX) $(HOSTCFLAGS) -o test/$@ $< $(HOSTLDFLAGS) test/ring_buffer_test: ring_buffer_test.cc lock.host.o
./test/$@ test/uart_test_tsan: uart_test.cc fake_uart.host.o async.host.o lock.host.o
%.run: test/%
TSAN_OPTIONS='suppressions=tsan.suppressions' ASAN_OPTIONS=detect_leaks=1 ./$<
%.host.o: %.cc %.host.o: %.cc
$(HOSTCXX) $(HOSTCFLAGS) -c -o $@ $< $(HOSTCXX) $(HOSTCFLAGS) -c -o $@ $<
async_test_tsan: async_test.cc async.host.o test/%_test: | mktest
mkdir -p test $(HOSTCXX) $(HOSTCFLAGS) -o $@ $^ $(HOSTLDFLAGS)
$(HOSTCXX) $(TSAN_CFLAGS) -o test/$@ $^ $(HOSTLDFLAGS)
TSAN_OPTIONS='suppressions=tsan.suppressions' ./test/$@
async_test_asan: async_test.cc async.host.o test/%_asan: | mktest
mkdir -p test $(HOSTCXX) $(ASAN_CFLAGS) -o $@ $^ $(HOSTLDFLAGS)
$(HOSTCXX) $(ASAN_CFLAGS) -o test/$@ $^ $(HOSTLDFLAGS)
ASAN_OPTIONS=detect_leaks=1 ./test/$@
test_deps = async.host.d test/%_tsan: | mktest
$(HOSTCXX) $(TSAN_CFLAGS) -o $@ $^ $(HOSTLDFLAGS)
mktest:
mkdir -p test
test_deps = async.host.d lock.host.d fake_uart.host.d
clean:
rm -rf *.elf *.bin $(app_objects) $(bootloader_objects) $(deps)
rm -rf test/ *.dSYM $(test_deps) *.o
-include $(deps) -include $(deps)
-include $(test_deps) -include $(test_deps)

View File

@ -1,6 +1,7 @@
import serial import serial
import struct import struct
import sys import sys
import time
offset = 0x800 offset = 0x800
tty = 'tty' tty = 'tty'
@ -41,6 +42,17 @@ def main():
s = serial.Serial(tty, baud, timeout=1) s = serial.Serial(tty, baud, timeout=1)
write(s, offset, dat) write(s, offset, dat)
jump(s, offset) jump(s, offset)
last_dat = time.time()
while True:
dat = s.read()
if not dat:
if time.time() - last_dat > 1.0:
print('Data timeout')
break
continue
last_dat = time.time()
sys.stdout.buffer.write(dat)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -3,6 +3,8 @@
#include <atomic> #include <atomic>
#include <span> #include <span>
#include "lock.h"
struct RingBuffer { struct RingBuffer {
std::span<std::byte> buffer; std::span<std::byte> buffer;
@ -11,6 +13,8 @@ struct RingBuffer {
std::atomic<bool> full = 0; std::atomic<bool> full = 0;
bool Store(std::span<const std::byte> data) { bool Store(std::span<const std::byte> data) {
InterruptLock lock;
if (data.size() > FreeSpace()) { if (data.size() > FreeSpace()) {
return false; return false;
} }
@ -26,6 +30,8 @@ struct RingBuffer {
} }
bool Load(std::span<std::byte> out) { bool Load(std::span<std::byte> out) {
InterruptLock lock;
if (out.size() > AvailableData()) { if (out.size() > AvailableData()) {
return false; return false;
} }
@ -41,6 +47,8 @@ struct RingBuffer {
} }
bool Push(size_t amount) { bool Push(size_t amount) {
InterruptLock lock;
if (amount > FreeSpace()) { if (amount > FreeSpace()) {
return false; return false;
} }
@ -52,6 +60,8 @@ struct RingBuffer {
} }
bool Pop(size_t amount) { bool Pop(size_t amount) {
InterruptLock lock;
if (amount > AvailableData()) { if (amount > AvailableData()) {
return false; return false;
} }
@ -62,9 +72,15 @@ struct RingBuffer {
return true; return true;
} }
size_t FreeSpace() const { return buffer.size() - AvailableData(); } size_t FreeSpace() const {
InterruptLock lock;
return buffer.size() - AvailableData();
}
size_t AvailableData() const { size_t AvailableData() const {
InterruptLock lock;
if (read_ptr == write_ptr) { if (read_ptr == write_ptr) {
return full ? buffer.size() : 0; return full ? buffer.size() : 0;
} }
@ -72,10 +88,14 @@ struct RingBuffer {
} }
uint8_t* RawReadPointer() const { uint8_t* RawReadPointer() const {
InterruptLock lock;
return reinterpret_cast<uint8_t*>(buffer.data() + read_ptr); return reinterpret_cast<uint8_t*>(buffer.data() + read_ptr);
} }
size_t ContiguousAvailableData() const { size_t ContiguousAvailableData() const {
InterruptLock lock;
if (read_ptr < write_ptr) { if (read_ptr < write_ptr) {
return AvailableData(); return AvailableData();
} }

View File

@ -7,14 +7,19 @@
#include "timer.h" #include "timer.h"
#include "uart.h" #include "uart.h"
#ifndef SBRK_STATS
#define SBRK_STATS 0
#endif
extern unsigned char _heap_begin, _heap_end; extern unsigned char _heap_begin, _heap_end;
namespace { namespace {
[[maybe_unused]]
void LogStats(unsigned char* heap) { void LogStats(unsigned char* heap) {
char number[] = "00000000\r\n"; char number[] = "00000000\r\n";
UartWriteBlocking("Program break now at 0x"); UartWriteCrash("Program break now at 0x");
itoa(reinterpret_cast<int>(heap), number); itoa(reinterpret_cast<int>(heap), number);
UartWriteBlocking(number); UartWriteCrash(number);
} }
} // namespace } // namespace
@ -22,11 +27,13 @@ extern "C" void* _sbrk(int increment) {
static unsigned char* heap = &_heap_begin; static unsigned char* heap = &_heap_begin;
unsigned char* prev_heap = heap; unsigned char* prev_heap = heap;
if (heap + increment >= &_heap_end) { if (heap + increment >= &_heap_end) {
UartWriteBlocking("Heap overflow!\r\n"); UartWriteCrash("Heap overflow!\r\n");
return reinterpret_cast<void*>(-1); return reinterpret_cast<void*>(-1);
} }
heap += increment; heap += increment;
#if SBRK_STATS
LogStats(heap); LogStats(heap);
#endif
return prev_heap; return prev_heap;
} }

View File

@ -70,7 +70,7 @@ async::task<> UartWrite(std::span<const std::byte> data) {
} }
} }
#define GCC_HAS_BUG_101133 1 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133 #define GCC_HAS_BUG_101133 0 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133
#if !GCC_HAS_BUG_101133 #if !GCC_HAS_BUG_101133
async::task<> UartWriteLoop(async::gimme<std::span<const std::byte>>& data_gen) { async::task<> UartWriteLoop(async::gimme<std::span<const std::byte>>& data_gen) {
while (1) { while (1) {

95
arm/uart_test.cc Normal file
View File

@ -0,0 +1,95 @@
#include "uart.h"
#include "uart_async.h"
#include "fake_uart.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <semaphore>
#include <thread>
using namespace ::testing;
namespace {
std::atomic<bool> terminate;
std::counting_semaphore<12345> got_stuff(0);
buffer rx;
using namespace std::literals::chrono_literals;
}
std::string drain_n(size_t n, std::chrono::system_clock::duration timeout) {
std::string txtout;
auto begin = std::chrono::system_clock::now();
while (std::chrono::system_clock::now() < begin + timeout) {
auto out = FakeUart_Drain(n);
if (out.data.empty()) {
continue;
}
auto strout = std::string_view(reinterpret_cast<char*>(out.data.data()),
out.data.size());
n -= out.data.size();
txtout.append(strout);
if (n < 1) {
return txtout;
}
}
return "";
}
TEST(Uart, BasicSend) {
FakeUart_Reset();
terminate = false;
constexpr std::string_view kTestData = "blarg";
auto buff = std::as_bytes(std::span{kTestData});
async::schedule(UartWrite(buff).h);
std::thread t(
[]() { async::main_loop([]() -> bool { return terminate; }); });
std::string strout = drain_n(kTestData.size(), 1s);
EXPECT_EQ(strout, kTestData);
terminate = true;
t.join();
}
async::task<> test_echo() {
async::task<uint8_t> reader = UartReadLoop();
async::gimme<std::span<const std::byte>> feeder;
async::task<> writer = UartWriteLoop(feeder);
writer.h.resume(); // advance to first yield
while (1) {
uint8_t c = co_await reader;
feeder.feed(std::as_bytes(std::span{&c, 1}));
got_stuff.release();
}
}
TEST(Uart, Echo) {
FakeUart_Reset();
terminate = false;
constexpr std::string_view kTestData = "blargblaektrkblalasrjkh1!!";
auto buff = std::as_bytes(std::span{kTestData});
async::schedule(test_echo().h);
std::thread t(
[]() { async::main_loop([]() -> bool { return terminate; }); });
for (int j = 0; j < 100; j++) {
FakeUart_Feed(buff);
std::string txtout = drain_n(kTestData.size(), 1s);
ASSERT_EQ(txtout, kTestData);
}
terminate = true;
t.join();
}