artiq/soc/runtime/session.c

582 lines
16 KiB
C

#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <generated/csr.h>
#include "mailbox.h"
#include "messages.h"
#include "clock.h"
#include "log.h"
#include "kloader.h"
#include "exceptions.h"
#include "flash_storage.h"
#include "rtiocrg.h"
#include "session.h"
#define BUFFER_IN_SIZE (1024*1024)
#define BUFFER_OUT_SIZE (1024*1024)
static int buffer_in_index;
/* The 9th byte (right after the header) of buffer_in must be aligned
* to a 32-bit boundary for elf_loader to work.
*/
static struct {
char padding[3];
char data[BUFFER_IN_SIZE];
} __attribute__((packed)) _buffer_in __attribute__((aligned(4)));
#define buffer_in _buffer_in.data
static int buffer_out_index_data;
static int buffer_out_index_mem;
static char buffer_out[BUFFER_OUT_SIZE];
static int get_in_packet_len(void)
{
int r;
memcpy(&r, &buffer_in[4], 4);
return r;
}
static int get_out_packet_len(void)
{
int r;
memcpy(&r, &buffer_out[4], 4);
return r;
}
static void submit_output(int len)
{
memset(&buffer_out[0], 0x5a, 4);
memcpy(&buffer_out[4], &len, 4);
buffer_out_index_data = 0;
buffer_out_index_mem = 0;
}
static int user_kernel_state;
enum {
USER_KERNEL_NONE = 0,
USER_KERNEL_LOADED,
USER_KERNEL_RUNNING,
USER_KERNEL_WAIT_RPC /* < must come after _RUNNING */
};
void session_start(void)
{
buffer_in_index = 0;
memset(&buffer_out[4], 0, 4);
kloader_stop();
user_kernel_state = USER_KERNEL_NONE;
now = -1;
}
void session_end(void)
{
kloader_stop();
now = -1;
kloader_start_idle_kernel();
}
/* host to device */
enum {
REMOTEMSG_TYPE_LOG_REQUEST = 1,
REMOTEMSG_TYPE_IDENT_REQUEST,
REMOTEMSG_TYPE_SWITCH_CLOCK,
REMOTEMSG_TYPE_LOAD_OBJECT,
REMOTEMSG_TYPE_RUN_KERNEL,
REMOTEMSG_TYPE_RPC_REPLY,
REMOTEMSG_TYPE_FLASH_READ_REQUEST,
REMOTEMSG_TYPE_FLASH_WRITE_REQUEST,
REMOTEMSG_TYPE_FLASH_ERASE_REQUEST,
REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST
};
/* device to host */
enum {
REMOTEMSG_TYPE_LOG_REPLY = 1,
REMOTEMSG_TYPE_IDENT_REPLY,
REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED,
REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED,
REMOTEMSG_TYPE_LOAD_COMPLETED,
REMOTEMSG_TYPE_LOAD_FAILED,
REMOTEMSG_TYPE_KERNEL_FINISHED,
REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED,
REMOTEMSG_TYPE_KERNEL_EXCEPTION,
REMOTEMSG_TYPE_RPC_REQUEST,
REMOTEMSG_TYPE_FLASH_READ_REPLY,
REMOTEMSG_TYPE_FLASH_OK_REPLY,
REMOTEMSG_TYPE_FLASH_ERROR_REPLY
};
static int check_flash_storage_key_len(char *key, unsigned int key_len)
{
if(key_len == get_in_packet_len() - 8) {
log("Invalid key: not a null-terminated string");
buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY;
submit_output(9);
return 0;
}
return 1;
}
static int process_input(void)
{
switch(buffer_in[8]) {
case REMOTEMSG_TYPE_LOG_REQUEST:
#if (LOG_BUFFER_SIZE + 9) > BUFFER_OUT_SIZE
#error Output buffer cannot hold the log buffer
#endif
buffer_out[8] = REMOTEMSG_TYPE_LOG_REPLY;
log_get(&buffer_out[9]);
submit_output(9 + LOG_BUFFER_SIZE);
break;
case REMOTEMSG_TYPE_IDENT_REQUEST:
buffer_out[8] = REMOTEMSG_TYPE_IDENT_REPLY;
buffer_out[9] = 'A';
buffer_out[10] = 'R';
buffer_out[11] = 'O';
buffer_out[12] = 'R';
submit_output(13);
break;
case REMOTEMSG_TYPE_SWITCH_CLOCK:
if(user_kernel_state >= USER_KERNEL_RUNNING) {
log("Attempted to switch RTIO clock while kernel running");
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED;
submit_output(9);
break;
}
if(rtiocrg_switch_clock(buffer_in[9]))
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED;
else
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED;
submit_output(9);
break;
case REMOTEMSG_TYPE_LOAD_OBJECT:
if(user_kernel_state >= USER_KERNEL_RUNNING) {
log("Attempted to load new kernel while already running");
buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED;
submit_output(9);
break;
}
if(kloader_load(&buffer_in[9], get_in_packet_len() - 8)) {
buffer_out[8] = REMOTEMSG_TYPE_LOAD_COMPLETED;
user_kernel_state = USER_KERNEL_LOADED;
} else
buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED;
submit_output(9);
break;
case REMOTEMSG_TYPE_RUN_KERNEL: {
kernel_function k;
if(user_kernel_state != USER_KERNEL_LOADED) {
log("Attempted to run kernel while not in the LOADED state");
buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
submit_output(9);
break;
}
if((buffer_in_index + 1) > BUFFER_OUT_SIZE) {
log("Kernel name too long");
buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
submit_output(9);
break;
}
buffer_in[buffer_in_index] = 0;
k = kloader_find((char *)&buffer_in[9]);
if(k == NULL) {
log("Failed to find kernel entry point '%s' in object", &buffer_in[9]);
buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
submit_output(9);
break;
}
watchdog_init();
kloader_start_user_kernel(k);
user_kernel_state = USER_KERNEL_RUNNING;
break;
}
case REMOTEMSG_TYPE_RPC_REPLY: {
struct msg_rpc_reply reply;
if(user_kernel_state != USER_KERNEL_WAIT_RPC) {
log("Unsolicited RPC reply");
return 0;
}
reply.type = MESSAGE_TYPE_RPC_REPLY;
memcpy(&reply.eid, &buffer_in[9], 4);
memcpy(&reply.retval, &buffer_in[13], 4);
mailbox_send_and_wait(&reply);
user_kernel_state = USER_KERNEL_RUNNING;
break;
}
case REMOTEMSG_TYPE_FLASH_READ_REQUEST: {
#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9
#error Output buffer cannot hold the flash storage data
#elif SPIFLASH_SECTOR_SIZE - 4 > BUFFER_IN_SIZE - 9
#error Input buffer cannot hold the flash storage data
#endif
unsigned int ret, in_packet_len;
char *key;
in_packet_len = get_in_packet_len();
key = &buffer_in[9];
buffer_in[in_packet_len] = '\0';
buffer_out[8] = REMOTEMSG_TYPE_FLASH_READ_REPLY;
ret = fs_read(key, &buffer_out[9], sizeof(buffer_out) - 9, NULL);
submit_output(9 + ret);
break;
}
case REMOTEMSG_TYPE_FLASH_WRITE_REQUEST: {
char *key, *value;
unsigned int key_len, value_len, in_packet_len;
int ret;
in_packet_len = get_in_packet_len();
key = &buffer_in[9];
key_len = strnlen(key, in_packet_len - 9) + 1;
if(!check_flash_storage_key_len(key, key_len))
break;
value_len = in_packet_len - key_len - 9;
value = key + key_len;
ret = fs_write(key, value, value_len);
if(ret)
buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
else
buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY;
submit_output(9);
break;
}
case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: {
fs_erase();
buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
submit_output(9);
break;
}
case REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST: {
char *key;
unsigned int in_packet_len;
in_packet_len = get_in_packet_len();
key = &buffer_in[9];
buffer_in[in_packet_len] = '\0';
fs_remove(key);
buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
submit_output(9);
break;
}
default:
return 0;
}
return 1;
}
/* Returns -1 in case of irrecoverable error
* (the session must be dropped and session_end called)
*/
int session_input(void *data, int len)
{
unsigned char *_data = data;
int consumed;
consumed = 0;
while(len > 0) {
/* Make sure the output buffer is available for any reply
* we might need to send. */
if(get_out_packet_len() != 0)
return consumed;
if(buffer_in_index < 4) {
/* synchronizing */
if(_data[consumed] == 0x5a)
buffer_in[buffer_in_index++] = 0x5a;
else
buffer_in_index = 0;
consumed++; len--;
} else if(buffer_in_index < 8) {
/* receiving length */
buffer_in[buffer_in_index++] = _data[consumed];
consumed++; len--;
if((buffer_in_index == 8) && (get_in_packet_len() == 0))
/* zero-length packet = session reset */
return -2;
} else {
/* receiving payload */
int packet_len;
int count;
packet_len = get_in_packet_len();
if(packet_len > BUFFER_IN_SIZE)
return -1;
count = packet_len - buffer_in_index;
if(count > len)
count = len;
memcpy(&buffer_in[buffer_in_index], &_data[consumed], count);
buffer_in_index += count;
if(buffer_in_index == packet_len) {
if(!process_input())
return -1;
buffer_in_index = 0;
}
consumed += count; len -= count;
}
}
return consumed;
}
static int add_base_rpc_value(char base_type, void *value, char *buffer_out, int available_space)
{
switch(base_type) {
case 'n':
return 0;
case 'b':
if(available_space < 1)
return -1;
if(*(char *)value)
buffer_out[0] = 1;
else
buffer_out[0] = 0;
return 1;
case 'i':
if(available_space < 4)
return -1;
memcpy(buffer_out, value, 4);
return 4;
case 'I':
case 'f':
if(available_space < 8)
return -1;
memcpy(buffer_out, value, 8);
return 8;
case 'F':
if(available_space < 16)
return -1;
memcpy(buffer_out, value, 16);
return 16;
default:
return -1;
}
}
static int add_rpc_value(int bi, int type_tag, void *value)
{
char base_type;
int obi, r;
obi = bi;
base_type = type_tag;
if((bi + 1) > BUFFER_OUT_SIZE)
return -1;
buffer_out[bi++] = base_type;
if(base_type == 'l') {
char elt_type;
int len;
int i, p;
elt_type = type_tag >> 8;
if((bi + 1) > BUFFER_OUT_SIZE)
return -1;
buffer_out[bi++] = elt_type;
len = *(int *)value;
if((bi + 4) > BUFFER_OUT_SIZE)
return -1;
memcpy(&buffer_out[bi], &len, 4);
bi += 4;
p = 4;
for(i=0;i<len;i++) {
r = add_base_rpc_value(elt_type, (char *)value + p,
&buffer_out[bi], BUFFER_OUT_SIZE - bi);
if(r < 0)
return r;
bi += r;
p += r;
}
} else {
r = add_base_rpc_value(base_type, value,
&buffer_out[bi], BUFFER_OUT_SIZE - bi);
if(r < 0)
return r;
bi += r;
}
return bi - obi;
}
static int send_rpc_request(int rpc_num, va_list args)
{
int r;
int bi = 8;
int type_tag;
void *v;
buffer_out[bi++] = REMOTEMSG_TYPE_RPC_REQUEST;
memcpy(&buffer_out[bi], &rpc_num, 4);
bi += 4;
while((type_tag = va_arg(args, int))) {
if(type_tag == 'n')
v = NULL;
else {
v = va_arg(args, void *);
if(!kloader_validate_kpointer(v))
return 0;
}
r = add_rpc_value(bi, type_tag, v);
if(r < 0)
return 0;
bi += r;
}
if((bi + 1) > BUFFER_OUT_SIZE)
return 0;
buffer_out[bi++] = 0;
submit_output(bi);
return 1;
}
/* assumes output buffer is empty when called */
static int process_kmsg(struct msg_base *umsg)
{
if(!kloader_validate_kpointer(umsg))
return 0;
if(kloader_is_essential_kmsg(umsg->type))
return 1; /* handled elsewhere */
if(user_kernel_state != USER_KERNEL_RUNNING) {
log("Received unexpected message from kernel CPU while not in running state");
return 0;
}
switch(umsg->type) {
case MESSAGE_TYPE_FINISHED:
buffer_out[8] = REMOTEMSG_TYPE_KERNEL_FINISHED;
submit_output(9);
kloader_stop();
user_kernel_state = USER_KERNEL_LOADED;
mailbox_acknowledge();
break;
case MESSAGE_TYPE_EXCEPTION: {
struct msg_exception *msg = (struct msg_exception *)umsg;
buffer_out[8] = REMOTEMSG_TYPE_KERNEL_EXCEPTION;
memcpy(&buffer_out[9], &msg->eid, 4);
memcpy(&buffer_out[13], msg->eparams, 3*8);
submit_output(9+4+3*8);
kloader_stop();
user_kernel_state = USER_KERNEL_LOADED;
mailbox_acknowledge();
break;
}
case MESSAGE_TYPE_WATCHDOG_SET_REQUEST: {
struct msg_watchdog_set_request *msg = (struct msg_watchdog_set_request *)umsg;
struct msg_watchdog_set_reply reply;
reply.type = MESSAGE_TYPE_WATCHDOG_SET_REPLY;
reply.id = watchdog_set(msg->ms);
mailbox_send_and_wait(&reply);
break;
}
case MESSAGE_TYPE_WATCHDOG_CLEAR: {
struct msg_watchdog_clear *msg = (struct msg_watchdog_clear *)umsg;
watchdog_clear(msg->id);
mailbox_acknowledge();
break;
}
case MESSAGE_TYPE_RPC_REQUEST: {
struct msg_rpc_request *msg = (struct msg_rpc_request *)umsg;
if(!send_rpc_request(msg->rpc_num, msg->args))
return 0;
user_kernel_state = USER_KERNEL_WAIT_RPC;
mailbox_acknowledge();
break;
}
default: {
log("Received invalid message type from kernel CPU");
return 0;
}
}
return 1;
}
/* len is set to -1 in case of irrecoverable error
* (the session must be dropped and session_end called)
*/
void session_poll(void **data, int *len)
{
int l;
if(user_kernel_state == USER_KERNEL_RUNNING) {
if(watchdog_expired()) {
log("Watchdog expired");
*len = -1;
return;
}
if(!rtiocrg_check()) {
log("RTIO clock failure");
*len = -1;
return;
}
}
l = get_out_packet_len();
/* If the output buffer is available,
* check if the kernel CPU has something to transmit.
*/
if(l == 0) {
struct msg_base *umsg;
umsg = mailbox_receive();
if(umsg) {
if(!process_kmsg(umsg)) {
*len = -1;
return;
}
}
l = get_out_packet_len();
}
if(l > 0) {
*len = l - buffer_out_index_data;
*data = &buffer_out[buffer_out_index_data];
} else
*len = 0;
}
void session_ack_data(int len)
{
buffer_out_index_data += len;
}
void session_ack_mem(int len)
{
buffer_out_index_mem += len;
if(buffer_out_index_mem >= get_out_packet_len())
memset(&buffer_out[4], 0, 4);
}