mirror of https://github.com/m-labs/artiq.git
605 lines
16 KiB
C
605 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_startup_kernel(void)
|
|
{
|
|
struct msg_base *umsg;
|
|
|
|
now = -1;
|
|
watchdog_init();
|
|
if(!kloader_start_startup_kernel())
|
|
return;
|
|
|
|
while(1) {
|
|
kloader_service_essential_kmsg();
|
|
|
|
umsg = mailbox_receive();
|
|
if(umsg) {
|
|
if(!kloader_validate_kpointer(umsg))
|
|
break;
|
|
if(kloader_is_essential_kmsg(umsg->type))
|
|
continue;
|
|
if(umsg->type == MESSAGE_TYPE_FINISHED)
|
|
break;
|
|
else if(umsg->type == MESSAGE_TYPE_EXCEPTION) {
|
|
log("WARNING: startup kernel ended with exception");
|
|
break;
|
|
} else {
|
|
log("ERROR: received invalid message type from kernel CPU");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(watchdog_expired()) {
|
|
log("WARNING: watchdog expired in startup kernel");
|
|
break;
|
|
}
|
|
}
|
|
kloader_stop();
|
|
log("Startup kernel terminated");
|
|
}
|
|
|
|
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;
|
|
watchdog_init();
|
|
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_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("ERROR: 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);
|
|
}
|