mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-12-18 17:53:10 +00:00
4c672c00b6
This should fix the issue where console writes from another CPU in the middle of events/etc corrupt data Signed-off-by: Hector Martin <marcan@marcan.st>
317 lines
9.5 KiB
C
317 lines
9.5 KiB
C
/* 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);
|
|
}
|