summaryrefslogtreecommitdiff
path: root/tools/src/uartproxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/src/uartproxy.c')
-rw-r--r--tools/src/uartproxy.c317
1 files changed, 317 insertions, 0 deletions
diff --git a/tools/src/uartproxy.c b/tools/src/uartproxy.c
new file mode 100644
index 0000000..fed9cc5
--- /dev/null
+++ b/tools/src/uartproxy.c
@@ -0,0 +1,317 @@
+/* SPDX-License-Identifier: MIT */
+
+#include "uartproxy.h"
+#include "assert.h"
+#include "exception.h"
+#include "iodev.h"
+#include "proxy.h"
+#include "string.h"
+#include "types.h"
+#include "utils.h"
+
+#define REQ_SIZE 64
+
+typedef struct {
+ u32 _pad;
+ u32 type;
+ union {
+ ProxyRequest prequest;
+ struct {
+ u64 addr;
+ u64 size;
+ u32 dchecksum;
+ } mrequest;
+ u64 features;
+ };
+ u32 checksum;
+} UartRequest;
+
+#define REPLY_SIZE 36
+
+typedef struct {
+ u32 type;
+ s32 status;
+ union {
+ ProxyReply preply;
+ struct {
+ u32 dchecksum;
+ } mreply;
+ struct uartproxy_msg_start start;
+ u64 features;
+ };
+ u32 checksum;
+ u32 _dummy; // Not transferred
+} UartReply;
+
+typedef struct {
+ u32 type;
+ u16 len;
+ u16 event_type;
+} UartEventHdr;
+
+static_assert(sizeof(UartReply) == (REPLY_SIZE + 4), "Invalid UartReply size");
+
+#define REQ_NOP 0x00AA55FF
+#define REQ_PROXY 0x01AA55FF
+#define REQ_MEMREAD 0x02AA55FF
+#define REQ_MEMWRITE 0x03AA55FF
+#define REQ_BOOT 0x04AA55FF
+#define REQ_EVENT 0x05AA55FF
+
+#define ST_OK 0
+#define ST_BADCMD -1
+#define ST_INVAL -2
+#define ST_XFRERR -3
+#define ST_CSUMERR -4
+
+#define PROXY_FEAT_DISABLE_DATA_CSUMS 0x01
+#define PROXY_FEAT_ALL (PROXY_FEAT_DISABLE_DATA_CSUMS)
+
+static u32 iodev_proxy_buffer[IODEV_MAX];
+
+#define CHECKSUM_INIT 0xDEADBEEF
+#define CHECKSUM_FINAL 0xADDEDBAD
+#define CHECKSUM_SENTINEL 0xD0DECADE
+#define DATA_END_SENTINEL 0xB0CACC10
+
+static bool disable_data_csums = false;
+
+// I just totally pulled this out of my arse
+// Noinline so that this can be bailed out by exc_guard = EXC_RETURN
+// We assume this function does not use the stack
+static u32 __attribute__((noinline)) checksum_block(void *start, u32 length, u32 init)
+{
+ u32 sum = init;
+ u8 *d = (u8 *)start;
+
+ while (length--) {
+ sum *= 31337;
+ sum += (*d++) ^ 0x5A;
+ }
+ return sum;
+}
+
+static inline u32 checksum_start(void *start, u32 length)
+{
+ return checksum_block(start, length, CHECKSUM_INIT);
+}
+
+static inline u32 checksum_add(void *start, u32 length, u32 sum)
+{
+ return checksum_block(start, length, sum);
+}
+
+static inline u32 checksum_finish(u32 sum)
+{
+ return sum ^ CHECKSUM_FINAL;
+}
+
+static inline u32 checksum(void *start, u32 length)
+{
+ return checksum_finish(checksum_start(start, length));
+}
+
+static u64 data_checksum(void *start, u32 length)
+{
+ if (disable_data_csums) {
+ return CHECKSUM_SENTINEL;
+ }
+
+ return checksum(start, length);
+}
+
+iodev_id_t uartproxy_iodev;
+
+int uartproxy_run(struct uartproxy_msg_start *start)
+{
+ int ret;
+ int running = 1;
+ size_t bytes;
+ u64 checksum_val;
+ u64 enabled_features = 0;
+
+ iodev_id_t iodev = IODEV_MAX;
+
+ UartRequest request;
+ UartReply reply = {REQ_BOOT};
+ if (!start) {
+ // Startup notification only goes out via UART
+ reply.checksum = checksum(&reply, REPLY_SIZE - 4);
+ iodev_write(IODEV_UART, &reply, REPLY_SIZE);
+ } else {
+ // Exceptions / hooks keep the current iodev
+ iodev = uartproxy_iodev;
+ reply.start = *start;
+ reply.checksum = checksum(&reply, REPLY_SIZE - 4);
+ iodev_write(iodev, &reply, REPLY_SIZE);
+ }
+
+ while (running) {
+ if (!start) {
+ // Look for commands from any iodev on startup
+ for (iodev = 0; iodev < IODEV_MAX;) {
+ u8 b;
+ if ((iodev_get_usage(iodev) & USAGE_UARTPROXY)) {
+ iodev_handle_events(iodev);
+ if (iodev_can_read(iodev) && iodev_read(iodev, &b, 1) == 1) {
+ iodev_proxy_buffer[iodev] >>= 8;
+ iodev_proxy_buffer[iodev] |= b << 24;
+ if ((iodev_proxy_buffer[iodev] & 0xffffff) == 0xAA55FF)
+ break;
+ }
+ }
+ iodev++;
+ if (iodev == IODEV_MAX)
+ iodev = 0;
+ }
+ } else {
+ // Stick to the current iodev for exceptions
+ do {
+ u8 b;
+ iodev_handle_events(iodev);
+ if (iodev_read(iodev, &b, 1) != 1) {
+ printf("Proxy: iodev read failed, exiting.\n");
+ return -1;
+ }
+ iodev_proxy_buffer[iodev] >>= 8;
+ iodev_proxy_buffer[iodev] |= b << 24;
+ } while ((iodev_proxy_buffer[iodev] & 0xffffff) != 0xAA55FF);
+ }
+
+ memset(&request, 0, sizeof(request));
+ request.type = iodev_proxy_buffer[iodev];
+ bytes = iodev_read(iodev, (&request.type) + 1, REQ_SIZE - 4);
+ if (bytes != REQ_SIZE - 4)
+ continue;
+
+ if (checksum(&(request.type), REQ_SIZE - 4) != request.checksum) {
+ memset(&reply, 0, sizeof(reply));
+ reply.type = request.type;
+ reply.status = ST_CSUMERR;
+ reply.checksum = checksum(&reply, REPLY_SIZE - 4);
+ iodev_write(iodev, &reply, REPLY_SIZE);
+ continue;
+ }
+
+ memset(&reply, 0, sizeof(reply));
+ reply.type = request.type;
+ reply.status = ST_OK;
+
+ uartproxy_iodev = iodev;
+
+ switch (request.type) {
+ case REQ_NOP:
+ enabled_features = request.features & PROXY_FEAT_ALL;
+ if (iodev == IODEV_UART) {
+ // Don't allow disabling checksums on UART
+ enabled_features &= ~PROXY_FEAT_DISABLE_DATA_CSUMS;
+ }
+
+ disable_data_csums = enabled_features & PROXY_FEAT_DISABLE_DATA_CSUMS;
+ reply.features = enabled_features;
+ break;
+ case REQ_PROXY:
+ ret = proxy_process(&request.prequest, &reply.preply);
+ if (ret != 0)
+ running = 0;
+ if (ret < 0)
+ printf("Proxy req error: %d\n", ret);
+ break;
+ case REQ_MEMREAD:
+ if (request.mrequest.size == 0)
+ break;
+ exc_count = 0;
+ exc_guard = GUARD_RETURN;
+ checksum_val = data_checksum((void *)request.mrequest.addr, request.mrequest.size);
+ exc_guard = GUARD_OFF;
+ if (exc_count)
+ reply.status = ST_XFRERR;
+ reply.mreply.dchecksum = checksum_val;
+ break;
+ case REQ_MEMWRITE:
+ exc_count = 0;
+ exc_guard = GUARD_SKIP;
+ if (request.mrequest.size != 0) {
+ // Probe for exception guard
+ // We can't do the whole buffer easily, because we'd drop UART data
+ write8(request.mrequest.addr, 0);
+ write8(request.mrequest.addr + request.mrequest.size - 1, 0);
+ }
+ exc_guard = GUARD_OFF;
+ if (exc_count) {
+ reply.status = ST_XFRERR;
+ break;
+ }
+ bytes = iodev_read(iodev, (void *)request.mrequest.addr, request.mrequest.size);
+ if (bytes != request.mrequest.size) {
+ reply.status = ST_XFRERR;
+ break;
+ }
+ checksum_val = data_checksum((void *)request.mrequest.addr, request.mrequest.size);
+ reply.mreply.dchecksum = checksum_val;
+ if (reply.mreply.dchecksum != request.mrequest.dchecksum) {
+ reply.status = ST_XFRERR;
+ break;
+ }
+ if (disable_data_csums) {
+ // Check the sentinel that should be present after the data
+ u32 sentinel = 0;
+ bytes = iodev_read(iodev, &sentinel, sizeof(sentinel));
+ if (bytes != sizeof(sentinel) || sentinel != DATA_END_SENTINEL) {
+ reply.status = ST_XFRERR;
+ break;
+ }
+ }
+ break;
+ default:
+ reply.status = ST_BADCMD;
+ break;
+ }
+ sysop("dsb sy");
+ sysop("isb");
+ reply.checksum = checksum(&reply, REPLY_SIZE - 4);
+ iodev_lock(uartproxy_iodev);
+ iodev_queue(iodev, &reply, REPLY_SIZE);
+
+ if ((request.type == REQ_MEMREAD) && (reply.status == ST_OK)) {
+ iodev_queue(iodev, (void *)request.mrequest.addr, request.mrequest.size);
+
+ if (disable_data_csums) {
+ // Since there is no checksum, put a sentinel after the data so the receiver
+ // can check that no packets were lost.
+ u32 sentinel = DATA_END_SENTINEL;
+
+ iodev_queue(iodev, &sentinel, sizeof(sentinel));
+ }
+ }
+
+ iodev_unlock(uartproxy_iodev);
+ // Flush all queued data
+ iodev_write(iodev, NULL, 0);
+ iodev_flush(iodev);
+ }
+
+ return ret;
+}
+
+void uartproxy_send_event(u16 event_type, void *data, u16 length)
+{
+ UartEventHdr hdr;
+ u32 csum;
+
+ hdr.type = REQ_EVENT;
+ hdr.len = length;
+ hdr.event_type = event_type;
+
+ if (disable_data_csums) {
+ csum = CHECKSUM_SENTINEL;
+ } else {
+ csum = checksum_start(&hdr, sizeof(UartEventHdr));
+ csum = checksum_finish(checksum_add(data, length, csum));
+ }
+ iodev_lock(uartproxy_iodev);
+ iodev_queue(uartproxy_iodev, &hdr, sizeof(UartEventHdr));
+ iodev_queue(uartproxy_iodev, data, length);
+ iodev_write(uartproxy_iodev, &csum, sizeof(csum));
+ iodev_unlock(uartproxy_iodev);
+}