thermosphere: add in basic hypervisor skeleton

This commit is contained in:
Kate J. Temkin 2018-04-20 04:06:09 -06:00
parent 60c0df032d
commit d104ff61ca
15 changed files with 2989 additions and 0 deletions

69
thermosphere/Makefile Normal file
View file

@ -0,0 +1,69 @@
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitPro")
endif
include $(DEVKITPRO)/devkitA64/base_tools
name := thermosphere
dir_source := src
dir_build := build
dir_out := out
ARCH := -march=armv8-a -mtune=cortex-a57
ASFLAGS := -g $(ARCH)
CFLAGS = \
$(ARCH) \
-g \
-O2 \
-ffunction-sections \
-fdata-sections \
-mgeneral-regs-only \
-fomit-frame-pointer \
-std=gnu11 \
-Werror \
-Wall \
-Wno-main
LDFLAGS = -specs=linker.specs -g $(ARCH)
objects = $(patsubst $(dir_source)/%.s, $(dir_build)/%.o, \
$(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \
$(patsubst $(dir_source)/%.S, $(dir_build)/%.o, \
$(call rwildcard, $(dir_source), *.s *.c *.S))))
define bin2o
bin2s $< | $(AS) -o $(@)
endef
.PHONY: all
all: $(dir_out)/$(name).bin
.PHONY: clean
clean:
@rm -rf $(dir_build)
@rm -rf $(dir_out)
$(dir_out)/$(name).bin: $(dir_build)/$(name).elf
@mkdir -p "$(@D)"
$(OBJCOPY) -S -O binary $< $@
$(dir_build)/$(name).elf: $(objects)
$(LINK.o) $(OUTPUT_OPTION) $^
$(dir_build)/%.bin.o: $(dir_build)/%.bin
@$(bin2o)
$(dir_build)/%.o: $(dir_source)/%.c
@mkdir -p "$(@D)"
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(dir_build)/%.o: $(dir_source)/%.s
@mkdir -p "$(@D)"
$(COMPILE.c) -x assembler-with-cpp $(OUTPUT_OPTION) $<

6
thermosphere/README.md Normal file
View file

@ -0,0 +1,6 @@
Thermoshère
=====
![License](https://img.shields.io/badge/License-GPLv2-blue.svg)
Thermoshère is a hypervisor for the Nintendo Switch.

55
thermosphere/linker.ld Normal file
View file

@ -0,0 +1,55 @@
OUTPUT_FORMAT("elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
. = 0x800D0000;
. = ALIGN(4);
.text : {
PROVIDE(lds_thermo_start = .);
build/start.o (.text*)
*(.text*)
}
. = ALIGN(8);
.rodata : {
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
}
. = ALIGN(8);
.data : {
*(.data*)
}
/* Uninitialised data */
. = ALIGN(8);
PROVIDE(lds_bss_start = .);
.bss (NOLOAD) : {
*(.bss*) . = ALIGN(8);
}
PROVIDE(lds_bss_end = .);
/* EL2 stack */
. = ALIGN(16);
. += 0x10000; /* 64 KiB stack */
el2_stack_end = .;
/* Page align the end of binary */
. = ALIGN(512);
PROVIDE(lds_el2_thermo_end = .);
/* EL1 stack */
. = ALIGN(16);
. += 0x10000; /* 64 KiB stack */
el1_stack_end = .;
lds_thermo_end = .;
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}

View file

@ -0,0 +1,7 @@
%rename link old_link
*link:
%(old_link) -T linker.ld --nmagic --gc-sections
*startfile:
crti%O%s crtbegin%O%s

View file

@ -0,0 +1,99 @@
/**
* Thermosphère: exception handler
* Handles all exceptions, including a return to EL2.
*
* Copyright (c) 2018 Kate J. Temkin <k@ktemkin.com>
*/
#include <stdint.h>
#include <stddef.h>
#include "exceptions.h"
#include "lib/printk.h"
/**
* Simple debug function that prints all of our saved registers.
*/
static void print_registers(struct guest_state *regs)
{
// print x0-29
for(int i = 0; i < 30; i += 2) {
printk("x%d:\t0x%p\t", i, regs->x[i]);
printk("x%d:\t0x%p\n", i + 1, regs->x[i + 1]);
}
// print x30; don't bother with x31 (SP), as it's used by the stack that's
// storing this stuff; we really care about the saved SP anyways
printk("x30:\t0x%p\n", regs->x[30]);
// Special registers.
printk("pc:\t0x%p\tcpsr:\t0x%p\n", regs->pc, regs->cpsr);
printk("sp_el1:\t0x%p\tsp_el0:\t0x%p\n", regs->sp_el1, regs->sp_el0);
printk("elr_el1:0x%p\tspsr_el1:0x%p\n", regs->elr_el1, regs->spsr_el1);
// Note that we don't print ESR_EL2, as this isn't really part of the saved state.
}
/**
* Placeholder function that triggers whenever a vector happens we're not
* expecting. Currently prints out some debug information.
*/
void unhandled_vector(struct guest_state *regs)
{
printk("\nAn unexpected vector happened!\n");
print_registers(regs);
printk("\n\n");
}
/**
* Handles an HVC call.
*/
static void handle_hvc(struct guest_state *regs, int call_number)
{
switch(call_number) {
default:
printk("Got a HVC call from 64-bit code.\n");
printk("Calling instruction was: hvc %d\n\n", call_number);
printk("Calling context (you can use these regs as hypercall args!):\n");
print_registers(regs);
printk("\n\n");
break;
}
}
/**
* Placeholder function that triggers whenever a user event triggers a
* synchronous interrupt. Currently, we really only care about 'hvc',
* so that's all we're going to handle here.
*/
void handle_hypercall(struct guest_state *regs)
{
// This is demonstration code.
// In the future, you'd stick your hypercall table here.
switch (regs->esr_el2.ec) {
case HSR_EC_HVC64: {
// Read the hypercall number.
int hvc_nr = regs->esr_el2.iss & 0xFFFF;
// ... and handle the hypercall.
handle_hvc(regs, hvc_nr);
break;
}
default:
printk("Unexpected hypercall! ESR=%p\n", regs->esr_el2.bits);
print_registers(regs);
printk("\n\n");
break;
}
}

View file

@ -0,0 +1,176 @@
/**
* Thermosphère: exception handler
* Handles all exceptions, including a return to EL2.
*
* Copyright (c) 2018 Kate J. Temkin <k@ktemkin.com>
*/
#ifndef __EXCEPTION_H__
#define __EXCEPTION_H__
/**
* Borrowed fom Xen (not copyrightable as these are facts).
* Description of the EL2 exception syndrome register.
*/
#define HSR_EC_UNKNOWN 0x00
#define HSR_EC_WFI_WFE 0x01
#define HSR_EC_CP15_32 0x03
#define HSR_EC_CP15_64 0x04
#define HSR_EC_CP14_32 0x05 /* Trapped MCR or MRC access to CP14 */
#define HSR_EC_CP14_DBG 0x06 /* Trapped LDC/STC access to CP14 (only for debug registers) */
#define HSR_EC_CP 0x07 /* HCPTR-trapped access to CP0-CP13 */
#define HSR_EC_CP10 0x08
#define HSR_EC_JAZELLE 0x09
#define HSR_EC_BXJ 0x0a
#define HSR_EC_CP14_64 0x0c
#define HSR_EC_SVC32 0x11
#define HSR_EC_HVC32 0x12
#define HSR_EC_SMC32 0x13
#define HSR_EC_HVC64 0x16
#define HSR_EC_SMC64 0x17
#define HSR_EC_SYSREG 0x18
#define HSR_EC_INSTR_ABORT_LOWER_EL 0x20
#define HSR_EC_INSTR_ABORT_CURR_EL 0x21
#define HSR_EC_DATA_ABORT_LOWER_EL 0x24
#define HSR_EC_DATA_ABORT_CURR_EL 0x25
#define HSR_EC_BRK 0x3c
/**
* Borrowed fom Xen (not copyrightable as these are facts).
* Description of the EL2 exception syndrome register.
*/
union esr {
uint32_t bits;
struct {
unsigned long iss:25; /* Instruction Specific Syndrome */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
};
/* Common to all conditional exception classes (0x0N, except 0x00). */
struct hsr_cond {
unsigned long iss:20; /* Instruction Specific Syndrome */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cond;
struct hsr_wfi_wfe {
unsigned long ti:1; /* Trapped instruction */
unsigned long sbzp:19;
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} wfi_wfe;
/* reg, reg0, reg1 are 4 bits on AArch32, the fifth bit is sbzp. */
struct hsr_cp32 {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg:5; /* Rt */
unsigned long crn:4; /* CRn */
unsigned long op1:3; /* Op1 */
unsigned long op2:3; /* Op2 */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp32; /* HSR_EC_CP15_32, CP14_32, CP10 */
struct hsr_cp64 {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg1:5; /* Rt1 */
unsigned long reg2:5; /* Rt2 */
unsigned long sbzp2:1;
unsigned long op1:4; /* Op1 */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp64; /* HSR_EC_CP15_64, HSR_EC_CP14_64 */
struct hsr_cp {
unsigned long coproc:4; /* Number of coproc accessed */
unsigned long sbz0p:1;
unsigned long tas:1; /* Trapped Advanced SIMD */
unsigned long res0:14;
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp; /* HSR_EC_CP */
struct hsr_sysreg {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg:5; /* Rt */
unsigned long crn:4; /* CRn */
unsigned long op1:3; /* Op1 */
unsigned long op2:3; /* Op2 */
unsigned long op0:2; /* Op0 */
unsigned long res0:3;
unsigned long len:1; /* Instruction length */
unsigned long ec:6;
} sysreg; /* HSR_EC_SYSREG */
struct hsr_iabt {
unsigned long ifsc:6; /* Instruction fault status code */
unsigned long res0:1;
unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */
unsigned long res1:1;
unsigned long eat:1; /* External abort type */
unsigned long res2:15;
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} iabt; /* HSR_EC_INSTR_ABORT_* */
struct hsr_dabt {
unsigned long dfsc:6; /* Data Fault Status Code */
unsigned long write:1; /* Write / not Read */
unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */
unsigned long cache:1; /* Cache Maintenance */
unsigned long eat:1; /* External Abort Type */
unsigned long sbzp0:4;
unsigned long ar:1; /* Acquire Release */
unsigned long sf:1; /* Sixty Four bit register */
unsigned long reg:5; /* Register */
unsigned long sign:1; /* Sign extend */
unsigned long size:2; /* Access Size */
unsigned long valid:1; /* Syndrome Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} dabt; /* HSR_EC_DATA_ABORT_* */
struct hsr_brk {
unsigned long comment:16; /* Comment */
unsigned long res0:9;
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} brk;
};
/**
* Structure that stores the saved register values on a hypercall.
*/
struct guest_state {
uint64_t pc;
uint64_t cpsr;
uint64_t elr_el1;
uint64_t spsr_el1;
uint64_t sp_el0;
uint64_t sp_el1;
union esr esr_el2;
uint64_t x[31];
}
__attribute__((packed));
#endif

269
thermosphere/src/lib/ini.c Normal file
View file

@ -0,0 +1,269 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
int max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC
char* new_line;
int offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, max_line - offset, stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

130
thermosphere/src/lib/ini.h Normal file
View file

@ -0,0 +1,130 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

View file

@ -0,0 +1,25 @@
/**
* Kernel print functions.
*/
#include "printk.h"
#include "vsprintf.h"
/**
* Temporary stand-in main printk.
*
* TODO: This should print via UART, console framebuffer, and to a ring for
* consumption by Horizon
*/
void printk(char *fmt, ...)
{
va_list list;
char buf[512];
va_start(list, fmt);
vsnprintf(buf, sizeof(buf), fmt, list);
/* FIXME: print via UART */
va_end(list);
}

View file

@ -0,0 +1,2 @@
void printk(char *fmt, ...);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2011 Andrei Warkentin <andrey.warkentin@gmail.com>
*
* This program is free software ; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <stdarg.h>
#include <stdlib.h>
#ifndef VSPRINTF_H
#define VSPRINTF_H
struct va_format {
const char *fmt;
va_list *va;
};
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base);
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
int sscanf(const char *buf, const char *fmt, ...);
#endif /* VSPRINTF_H */

134
thermosphere/src/main.c Normal file
View file

@ -0,0 +1,134 @@
/**
* Thermosphère hypervisor -- primary setup code
* Copyright (c) 2018 Kate J. Temkin <k@ktemkin.com>
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "regs.h"
#include "lib/printk.h"
/**
* Switches to EL1, and then calls main_el1.
* Implemented in assembly in entry.S.
*/
void switch_to_el1();
/**
* C entry point for execution at EL1.
*/
void main_el1(void * fdt);
/**
* Reference to the EL2 vector table.
* Note that the type here isn't reprsentative-- we just need the address of the label.
*/
extern uint64_t el2_vector_table;
/**
* Clear out the system's bss.
*/
void _clear_bss(void)
{
// These symbols don't actually have a meaningful type-- instead,
// we care about the locations at which the linker /placed/ these
// symbols, which happen to be at the start and end of the BSS.
// We use chars here to make the math easy. :)
extern char lds_bss_start, lds_bss_end;
memset(&lds_bss_start, 0, &lds_bss_end - &lds_bss_start);
}
/**
* Triggered on an unrecoverable condition; prints an error message
* and terminates execution.
*/
void panic(const char * message)
{
printk("\n\n");
printk("-----------------------------------------------------------------\n");
printk("PANIC: %s\n", message);
printk("-----------------------------------------------------------------\n");
// TODO: This should probably induce a reboot,
// rather than sticking here.
while(1);
}
/**
* Launch an executable kernel image. Should be the last thing called by
* Discharge, as it does not return.
*
* @param kernel The kernel to be executed.
* @param fdt The device tree to be passed to the given kernel.
*/
void launch_kernel(const void *kernel)
{
// Construct a function pointer to our kernel, which will allow us to
// jump there immediately. Note that we don't care what this leaves on
// the stack, as either our entire stack will be ignored, or it'll
// be torn down by the target kernel anyways.
void (*target_kernel)(void) = kernel;
printk("Launching Horizon kernel...\n");
target_kernel();
}
/**
* Core section of the stub-- sets up the hypervisor from up in EL2.
*/
int main(int argc, void **argv)
{
// Read the currrent execution level...
uint32_t el = get_current_el();
/* Say hello. */
printk("Welcome to Atmosph\xe8re Thermosph\xe8" "re!\n");
printk("Running at EL%d.\n", el);
// ... and ensure we're in EL2.
if (el != 2) {
panic("Thermosph\xe8" "re must be launched from EL2!");
}
// Set up the vector table for EL2, so that the HVC instruction can be used
// from EL1. This allows us to return to EL2 after starting the EL1 guest.
set_vbar_el2(&el2_vector_table);
// TODO:
// Insert any setup you want done in EL2, here. For now, EL2 is set up
// to do almost nothing-- it doesn't take control of any hardware,
// and it hasn't set up any trap-to-hypervisor features.
printk("\nSwitching to EL1...\n");
switch_to_el1();
}
/**
* Secondary section of the stub, executed once we've surrendered
* hypervisor privileges.
*/
void main_el1(void * fdt)
{
int rc;
(void)rc;
// Read the currrent execution level...
uint32_t el = get_current_el();
// Validate that we're in EL1.
printk("Now executing from EL%d!\n", el);
if(el != 1) {
panic("Executing with more privilege than we expect!");
}
// If we've made it here, we failed to boot, and we can't recover.
panic("We should launch Horizon, here!");
}

62
thermosphere/src/regs.h Normal file
View file

@ -0,0 +1,62 @@
/**
* Thermosphère: register access primitives
* Copyright (c) Kate J. Temkin <k@ktemkin.com>
*/
#ifndef __REGS_H__
#define __REGS_H__
/**
* Access to system registers.
*/
#define WRITE_SYSREG(sysreg, val, type) \
asm volatile ("msr "#sysreg", %0\n" : : "r"((type)(val)))
#define READ_SYSREG(sysreg, val, type) \
asm volatile ("mrs %0, "#sysreg"\n" : "=r"((type)(val)))
#define READ_SYSREG_32(sysreg, val) READ_SYSREG(sysreg, val, uint32_t)
#define WRITE_SYSREG_32(sysreg, val) WRITE_SYSREG(sysreg, val, uint32_t)
#define READ_SYSREG_64(sysreg, val) READ_SYSREG(sysreg, val, uint64_t)
#define WRITE_SYSREG_64(sysreg, val) WRITE_SYSREG(sysreg, val, uint64_t)
/**
* Returns the system's current Execution Level (EL).
*/
inline static uint32_t get_current_el(void) {
uint32_t val;
// Read the CurrentEl register, and extract the bits that tell us our EL.
READ_SYSREG_32(CurrentEl, val);
return val >> 2;
}
/**
* Sets the base address of the EL2 exception table.
*/
inline static void set_vbar_el2(void * address) {
WRITE_SYSREG_64(vbar_el2, (uint64_t)address);
}
/**
* Sets the address to 'return to' when leaving EL2.
*/
inline static void set_elr_el2(void * address) {
WRITE_SYSREG_64(elr_el2, (uint64_t)address);
}
/**
* Returns the MMU status bit from the SCTLR register.
*/
inline static uint32_t get_el2_mmu_status(void) {
uint32_t val;
// Read the CurrentEl register, and extract the bits that tell us our EL.
READ_SYSREG_32(sctlr_el2, val);
return val & 1;
}
#endif

254
thermosphere/src/start.s Normal file
View file

@ -0,0 +1,254 @@
/**
* Thermosphère hypervisor entry points
* This file contains all entry points (e.g. when launching or switching ELs).
*
* Copyright (c) Kate J. Temkin <k@ktemkin.com>
*/
.section ".text"
.global _start
/**
* Simple macro to help with generating vector table entries.
*/
.macro ventry label
.align 7
b \label
.endm
/**
* Start of day code. This is the first code that executes after we're launched
* by the bootloader. We use this only to set up a C environment.
*/
_start:
// Create a simple stack for the hypervisor, while executing in EL2.
ldr x1, =el2_stack_end
mov sp, x1
// Clear out our binary's bss.
stp x0, x1, [sp, #-16]!
bl _clear_bss
ldp x0, x1, [sp], #16
// Run the main routine. This shouldn't return.
b main
// We shouldn't ever reach here; trap.
1: b 1b
/*
* Vector table for interrupts/exceptions that reach EL2.
*/
.align 11
.global el2_vector_table;
el2_vector_table:
ventry _unhandled_vector // Synchronous EL2t
ventry _unhandled_vector // IRQ EL2t
ventry _unhandled_vector // FIQ EL2t
ventry _unhandled_vector // Error EL2t
ventry _unhandled_vector // Synchronous EL2h
ventry _unhandled_vector // IRQ EL2h
ventry _unhandled_vector // FIQ EL2h
ventry _unhandled_vector // Error EL2h
ventry _handle_hypercall // Synchronous 64-bit EL0/EL1
ventry _unhandled_vector // IRQ 64-bit EL0/EL1
ventry _unhandled_vector // FIQ 64-bit EL0/EL1
ventry _unhandled_vector // Error 64-bit EL0/EL1
ventry _unhandled_vector // Synchronous 32-bit EL0/EL1
ventry _unhandled_vector // IRQ 32-bit EL0/EL1
ventry _unhandled_vector // FIQ 32-bit EL0/EL1
ventry _unhandled_vector // Error 32-bit EL0/EL1
/*
* Switch down to EL1 and then execute the second half of our stub.
* Implemented in assembly, as this manipulates the stack.
*
* Obliterates the stack, but leaves the rest of memory intact. This should be
* fine, as we should be hiding the EL2 memory from the rest of the system.
*
* x0: The location of the device tree to be passed into EL0.
*/
.global switch_to_el1
switch_to_el1:
// Set up a post-EL1-switch return address...
ldr x2, =_post_el1_switch
msr elr_el2, x2
// .. and set up the CPSR after we switch to EL1.
// We overwrite the saved program status register. Note that setting
// this with the EL = EL1 is what actually causes the switch.
mov x2, #0x3c5 // EL1_SP1 | D | A | I | F
msr spsr_el2, x2
// Reset the stack pointer to the very end of the stack, so it's
// fresh and clean for when we jump back up into EL2.
ldr x2, =el2_stack_end
mov sp, x2
// ... and switch down to EL1. (This essentially asks the processor
// to switch down to EL1 and then load ELR_EL2 to the PC.)
eret
/*
* Entry point after the switch to EL1.
*/
.global _post_el1_switch
_post_el1_switch:
// Create a simple stack for us to use while at EL1.
// We use this temporarily to launch Horizon.
ldr x2, =el1_stack_end
mov sp, x2
// Run the main routine. This shouldn't return.
b main_el1
// We shouldn't ever reach here; trap.
1: b 1b
/**
* Push and pop 'psuedo-op' macros that simplify the ARM syntax to make the below pretty.
*/
.macro push, xreg1, xreg2
stp \xreg1, \xreg2, [sp, #-16]!
.endm
.macro pop, xreg1, xreg2
ldp \xreg1, \xreg2, [sp], #16
.endm
/**
* Macro that saves registers onto the stack when entering an exception handler--
* effectively saving the guest state. Once this method is complete, *sp will
* point to a struct guest_state.
*
* You can modify this to save whatever you'd like, but:
* 1) We can only push in pairs due to armv8 architecture quirks.
* 2) Be careful not to trounce registers until after you've saved them.
* 3) r31 is your stack pointer, and doesn't need to be saved. You'll want to
* save the lesser EL's stack pointers separately.
* 4) Make sure any changes you make are reflected both in _restore_registers_
* and in struct guest_state, or things will break pretty badly.
*/
.macro save_registers
// General purpose registers x1 - x30
push x29, x30
push x27, x28
push x25, x26
push x23, x24
push x21, x22
push x19, x20
push x17, x18
push x15, x16
push x13, x14
push x11, x12
push x9, x10
push x7, x8
push x5, x6
push x3, x4
push x1, x2
// x0 and the el2_esr
mrs x20, esr_el2
push x20, x0
// the el1_sp and el0_sp
mrs x0, sp_el0
mrs x1, sp_el1
push x0, x1
// the el1 elr/spsr
mrs x0, elr_el1
mrs x1, spsr_el1
push x0, x1
// the el2 elr/spsr
mrs x0, elr_el2
mrs x1, spsr_el2
push x0, x1
.endm
/**
* Macro that restores registers when returning from EL2.
* Mirrors save_registers.
*/
.macro restore_registers
// the el2 elr/spsr
pop x0, x1
msr elr_el2, x0
msr spsr_el2, x1
// the el1 elr/spsr
pop x0, x1
msr elr_el1, x0
msr spsr_el1, x1
// the el1_sp and el0_sp
pop x0, x1
msr sp_el0, x0
msr sp_el1, x1
// x0, and the el2_esr
// Note that we don't restore el2_esr, as this wouldn't
// have any meaning.
pop x20, x0
// General purpose registers x1 - x30
pop x1, x2
pop x3, x4
pop x5, x6
pop x7, x8
pop x9, x10
pop x11, x12
pop x13, x14
pop x15, x16
pop x17, x18
pop x19, x20
pop x21, x22
pop x23, x24
pop x25, x26
pop x27, x28
pop x29, x30
.endm
/*
* Handler for any vector we're not equipped to handle.
*/
_unhandled_vector:
// TODO: Save interrupt state and turn off interrupts.
save_registers
// Point x0 at our saved registers, and then call our C handler.
mov x0, sp
bl unhandled_vector
restore_registers
eret
/*
* Handler for any synchronous event coming from the guest (any trap-to-EL2).
* This _stub_ only uses this to handle hypercalls-- hence the name.
*/
_handle_hypercall:
// TODO: Save interrupt state and turn off interrupts.
save_registers
// Point x0 at our saved registers, and then call our C handler.
mov x0, sp
bl handle_hypercall
restore_registers
eret