more sel4 work for ME2150
This commit is contained in:
parent
1e7742c6a3
commit
ab848d700f
Binary file not shown.
@ -0,0 +1,124 @@
|
|||||||
|
# If you would like to choose a different path to the SDK, you can pass it as an
|
||||||
|
# argument.
|
||||||
|
ifndef MICROKIT_SDK
|
||||||
|
MICROKIT_SDK := ../microkit-sdk-1.4.0
|
||||||
|
endif
|
||||||
|
|
||||||
|
# In case the default compiler triple doesn't work for you or your package manager
|
||||||
|
# only has aarch64-none-elf or something, you can specifiy the toolchain.
|
||||||
|
ifndef TOOLCHAIN
|
||||||
|
# Get whether the common toolchain triples exist
|
||||||
|
TOOLCHAIN_AARCH64_LINUX_GNU := $(shell command -v aarch64-linux-gnu-gcc 2> /dev/null)
|
||||||
|
TOOLCHAIN_AARCH64_UNKNOWN_LINUX_GNU := $(shell command -v aarch64-unknown-linux-gnu-gcc 2> /dev/null)
|
||||||
|
# Then check if they are defined and select the appropriate one
|
||||||
|
ifdef TOOLCHAIN_AARCH64_LINUX_GNU
|
||||||
|
TOOLCHAIN := aarch64-linux-gnu
|
||||||
|
else ifdef TOOLCHAIN_AARCH64_UNKNOWN_LINUX_GNU
|
||||||
|
TOOLCHAIN := aarch64-unknown-linux-gnu
|
||||||
|
else
|
||||||
|
$(error "Could not find an AArch64 cross-compiler")
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
BOARD := qemu_virt_aarch64
|
||||||
|
MICROKIT_CONFIG := debug
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
|
CPU := cortex-a53
|
||||||
|
|
||||||
|
CC := $(TOOLCHAIN)-gcc
|
||||||
|
LD := $(TOOLCHAIN)-ld
|
||||||
|
AS := $(TOOLCHAIN)-as
|
||||||
|
MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit
|
||||||
|
|
||||||
|
PRINTF_OBJS := printf.o util.o
|
||||||
|
SERIAL_SERVER_OBJS := $(PRINTF_OBJS) serial_server.o
|
||||||
|
CLIENT_OBJS := $(PRINTF_OBJS) client.o
|
||||||
|
WORDLE_SERVER_OBJS := $(PRINTF_OBJS) wordle_server.o
|
||||||
|
VMM_OBJS := $(PRINTF_OBJS) vmm.o psci.o smc.o fault.o vgic.o global_data.o vgic_v2.o
|
||||||
|
|
||||||
|
BOARD_DIR := $(MICROKIT_SDK)/board/$(BOARD)/$(MICROKIT_CONFIG)
|
||||||
|
|
||||||
|
IMAGES_PART_1 := serial_server.elf
|
||||||
|
IMAGES_PART_2 := serial_server.elf client.elf
|
||||||
|
IMAGES_PART_3 := serial_server.elf client.elf wordle_server.elf
|
||||||
|
IMAGES_PART_4 := serial_server.elf client.elf wordle_server.elf vmm.elf
|
||||||
|
# Note that these warnings being disabled is to avoid compilation errors while in the middle of completing each exercise part
|
||||||
|
CFLAGS := -mcpu=$(CPU) -mstrict-align -nostdlib -ffreestanding -g -Wall -Wno-array-bounds -Wno-unused-variable -Wno-unused-function -Werror -I$(BOARD_DIR)/include -Ivmm/src/util -Iinclude -DBOARD_$(BOARD)
|
||||||
|
LDFLAGS := -L$(BOARD_DIR)/lib
|
||||||
|
LIBS := -lmicrokit -Tmicrokit.ld
|
||||||
|
|
||||||
|
IMAGE_FILE_PART_1 = $(BUILD_DIR)/wordle_part_one.img
|
||||||
|
IMAGE_FILE_PART_2 = $(BUILD_DIR)/wordle_part_two.img
|
||||||
|
IMAGE_FILE_PART_3 = $(BUILD_DIR)/wordle_part_three.img
|
||||||
|
IMAGE_FILE_PART_4 = $(BUILD_DIR)/wordle_part_four.img
|
||||||
|
IMAGE_FILE = $(BUILD_DIR)/loader.img
|
||||||
|
REPORT_FILE = $(BUILD_DIR)/report.txt
|
||||||
|
|
||||||
|
# VMM defines
|
||||||
|
KERNEL_IMAGE = vmm/images/linux
|
||||||
|
DTB_IMAGE = vmm/images/linux.dtb
|
||||||
|
INITRD_IMAGE = vmm/images/rootfs.cpio.gz
|
||||||
|
|
||||||
|
all: directories $(IMAGE_FILE)
|
||||||
|
|
||||||
|
directories:
|
||||||
|
$(info $(shell mkdir -p $(BUILD_DIR)))
|
||||||
|
|
||||||
|
run: $(IMAGE_FILE)
|
||||||
|
qemu-system-aarch64 -machine virt,virtualization=on \
|
||||||
|
-cpu $(CPU) \
|
||||||
|
-serial mon:stdio \
|
||||||
|
-device loader,file=$(IMAGE_FILE),addr=0x70000000,cpu-num=0 \
|
||||||
|
-m size=2G \
|
||||||
|
-nographic \
|
||||||
|
-netdev user,id=mynet0 \
|
||||||
|
-device virtio-net-device,netdev=mynet0,mac=52:55:00:d1:55:01
|
||||||
|
|
||||||
|
part1: directories $(BUILD_DIR)/serial_server.elf $(IMAGE_FILE_PART_1)
|
||||||
|
part2: directories $(BUILD_DIR)/client.elf $(IMAGE_FILE_PART_2)
|
||||||
|
part3: directories $(BUILD_DIR)/wordle_server.elf $(IMAGE_FILE_PART_3)
|
||||||
|
part4: directories $(BUILD_DIR)/vmm.elf $(IMAGE_FILE_PART_4)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%.o: %.c Makefile
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%.o: vmm/src/%.c Makefile
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%.o: vmm/src/util/%.c Makefile
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%.o: vmm/src/vgic/%.c Makefile
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/global_data.o: vmm/src/global_data.S $(KERNEL_IMAGE) $(INITRD_IMAGE) $(DTB_IMAGE)
|
||||||
|
$(CC) -c -g -x assembler-with-cpp \
|
||||||
|
-DVM_KERNEL_IMAGE_PATH=\"$(KERNEL_IMAGE)\" \
|
||||||
|
-DVM_DTB_IMAGE_PATH=\"$(DTB_IMAGE)\" \
|
||||||
|
-DVM_INITRD_IMAGE_PATH=\"$(INITRD_IMAGE)\" \
|
||||||
|
$< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/serial_server.elf: $(addprefix $(BUILD_DIR)/, $(SERIAL_SERVER_OBJS))
|
||||||
|
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/client.elf: $(addprefix $(BUILD_DIR)/, $(CLIENT_OBJS))
|
||||||
|
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/wordle_server.elf: $(addprefix $(BUILD_DIR)/, $(WORDLE_SERVER_OBJS))
|
||||||
|
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/vmm.elf: $(addprefix $(BUILD_DIR)/, $(VMM_OBJS))
|
||||||
|
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(IMAGE_FILE_PART_1): $(addprefix $(BUILD_DIR)/, $(IMAGES_PART_1)) wordle.system
|
||||||
|
$(MICROKIT_TOOL) wordle.system --search-path $(BUILD_DIR) --board $(BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE)
|
||||||
|
|
||||||
|
$(IMAGE_FILE_PART_2): $(addprefix $(BUILD_DIR)/, $(IMAGES_PART_2)) wordle.system
|
||||||
|
$(MICROKIT_TOOL) wordle.system --search-path $(BUILD_DIR) --board $(BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE)
|
||||||
|
|
||||||
|
$(IMAGE_FILE_PART_3): $(addprefix $(BUILD_DIR)/, $(IMAGES_PART_3)) wordle.system
|
||||||
|
$(MICROKIT_TOOL) wordle.system --search-path $(BUILD_DIR) --board $(BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE)
|
||||||
|
|
||||||
|
$(IMAGE_FILE_PART_4): $(addprefix $(BUILD_DIR)/, $(IMAGES_PART_4)) wordle.system
|
||||||
|
$(MICROKIT_TOOL) wordle.system --search-path $(BUILD_DIR) --board $(BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE)
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
#include "printf.h"
|
||||||
|
#include "wordle.h"
|
||||||
|
|
||||||
|
#define SERIAL_CHANNEL 1
|
||||||
|
#define WORDLE_CHANNEL 2
|
||||||
|
|
||||||
|
uintptr_t serial_to_client_vaddr;
|
||||||
|
uintptr_t client_to_serial_vaddr;
|
||||||
|
|
||||||
|
#define MOVE_CURSOR_UP "\033[5A"
|
||||||
|
#define CLEAR_TERMINAL_BELOW_CURSOR "\033[0J"
|
||||||
|
|
||||||
|
#define INVALID_CHAR (-1)
|
||||||
|
|
||||||
|
struct wordle_char {
|
||||||
|
int ch;
|
||||||
|
enum character_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store game state
|
||||||
|
static struct wordle_char table[NUM_TRIES][WORD_LENGTH];
|
||||||
|
// Use these global variables to keep track of the character index that the
|
||||||
|
// player is currently trying to input.
|
||||||
|
static int curr_row = 0;
|
||||||
|
static int curr_letter = 0;
|
||||||
|
|
||||||
|
void wordle_server_send() {
|
||||||
|
// Implement this function to send the word over PPC
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
microkit_mr_set(i, table[curr_row][i].ch);
|
||||||
|
}
|
||||||
|
microkit_ppcall(WORDLE_CHANNEL, microkit_msginfo_new(0, WORD_LENGTH));
|
||||||
|
// After doing the PPC, the Wordle server should have updated
|
||||||
|
// the message-registers containing the state of each character.
|
||||||
|
// Look at the message registers and update the `table` accordingly.
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
table[curr_row][i].state = microkit_mr_get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_send(char *str) {
|
||||||
|
// Implement this function to get the serial server to print the string.
|
||||||
|
int i = 0;
|
||||||
|
while (str[i] != '\0') {
|
||||||
|
((char *)client_to_serial_vaddr)[i] = str[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
((char *)client_to_serial_vaddr)[i] = '\0';
|
||||||
|
microkit_notify(SERIAL_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function prints a CLI Wordle using pretty colours for what characters
|
||||||
|
// are correct, or correct but in the wrong place etc.
|
||||||
|
void print_table(bool clear_terminal) {
|
||||||
|
if (clear_terminal) {
|
||||||
|
// Assuming we have already printed a Wordle table, this will clear the
|
||||||
|
// table we have already printed and then print the updated one. This
|
||||||
|
// is done by moving the cursor up 5 lines and then clearing everything
|
||||||
|
// below it.
|
||||||
|
serial_send(MOVE_CURSOR_UP);
|
||||||
|
serial_send(CLEAR_TERMINAL_BELOW_CURSOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = 0; row < NUM_TRIES; row++) {
|
||||||
|
for (int letter = 0; letter < WORD_LENGTH; letter++) {
|
||||||
|
serial_send("[");
|
||||||
|
enum character_state state = table[row][letter].state;
|
||||||
|
int ch = table[row][letter].ch;
|
||||||
|
if (ch != INVALID_CHAR) {
|
||||||
|
switch (state) {
|
||||||
|
case INCORRECT: break;
|
||||||
|
case CORRECT_PLACEMENT: serial_send("\x1B[32m"); break;
|
||||||
|
case INCORRECT_PLACEMENT: serial_send("\x1B[33m"); break;
|
||||||
|
default:
|
||||||
|
// Print out error messages/debug info via debug output
|
||||||
|
microkit_dbg_puts("CLIENT|ERROR: unexpected character state\n");
|
||||||
|
}
|
||||||
|
char ch_str[] = { ch, '\0' };
|
||||||
|
serial_send(ch_str);
|
||||||
|
// Reset colour
|
||||||
|
serial_send("\x1B[0m");
|
||||||
|
} else {
|
||||||
|
serial_send(" ");
|
||||||
|
}
|
||||||
|
serial_send("] ");
|
||||||
|
}
|
||||||
|
serial_send("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_table() {
|
||||||
|
for (int row = 0; row < NUM_TRIES; row++) {
|
||||||
|
for (int letter = 0; letter < WORD_LENGTH; letter++) {
|
||||||
|
table[row][letter].ch = INVALID_CHAR;
|
||||||
|
table[row][letter].state = INCORRECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool char_is_backspace(int ch) {
|
||||||
|
return (ch == 0x7f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool char_is_valid(int ch) {
|
||||||
|
// Only allow alphabetical letters and do not accept a character if the
|
||||||
|
// current word has already been filled.
|
||||||
|
return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) && curr_letter != WORD_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_char_to_table(char c) {
|
||||||
|
if (char_is_backspace(c)) {
|
||||||
|
if (curr_letter > 0) {
|
||||||
|
curr_letter--;
|
||||||
|
table[curr_row][curr_letter].ch = INVALID_CHAR;
|
||||||
|
}
|
||||||
|
} else if (c != '\r' && c != ' ' && curr_letter != WORD_LENGTH) {
|
||||||
|
table[curr_row][curr_letter].ch = c;
|
||||||
|
curr_letter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user has finished inputting a word, we want to send the
|
||||||
|
// word to the server and move the cursor to the next row.
|
||||||
|
if (c == '\r' && curr_letter == WORD_LENGTH) {
|
||||||
|
wordle_server_send();
|
||||||
|
curr_row += 1;
|
||||||
|
curr_letter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void) {
|
||||||
|
microkit_dbg_puts("CLIENT: starting\n");
|
||||||
|
serial_send("Welcome to the Wordle client!\n");
|
||||||
|
|
||||||
|
init_table();
|
||||||
|
// Don't want to clear the terminal yet since this is the first time
|
||||||
|
// we are printing it (we want to clear just the Wordle table, not
|
||||||
|
// everything on the terminal).
|
||||||
|
print_table(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notified(microkit_channel channel) {
|
||||||
|
switch (channel) {
|
||||||
|
case SERIAL_CHANNEL: {
|
||||||
|
char ch = ((char *)serial_to_client_vaddr)[0];
|
||||||
|
add_char_to_table(ch);
|
||||||
|
print_table(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,914 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// \author (c) Marco Paland (info@paland.com)
|
||||||
|
// 2014-2019, PALANDesign Hannover, Germany
|
||||||
|
//
|
||||||
|
// \license The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
|
||||||
|
// embedded systems with a very limited resources. These routines are thread
|
||||||
|
// safe and reentrant!
|
||||||
|
// Use this instead of the bloated standard/newlib printf cause these use
|
||||||
|
// malloc for printf (and may not be thread safe).
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
|
||||||
|
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
|
||||||
|
// printf_config.h header file
|
||||||
|
// default: undefined
|
||||||
|
#ifdef PRINTF_INCLUDE_CONFIG_H
|
||||||
|
#include "printf_config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
|
||||||
|
// numeric number including padded zeros (dynamically created on stack)
|
||||||
|
// default: 32 byte
|
||||||
|
#ifndef PRINTF_NTOA_BUFFER_SIZE
|
||||||
|
#define PRINTF_NTOA_BUFFER_SIZE 32U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
|
||||||
|
// float number including padded zeros (dynamically created on stack)
|
||||||
|
// default: 32 byte
|
||||||
|
#ifndef PRINTF_FTOA_BUFFER_SIZE
|
||||||
|
#define PRINTF_FTOA_BUFFER_SIZE 32U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the floating point type (%f)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
|
||||||
|
#define PRINTF_SUPPORT_FLOAT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for exponential floating point notation (%e/%g)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
|
||||||
|
#define PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// define the default floating point precision
|
||||||
|
// default: 6 digits
|
||||||
|
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
|
||||||
|
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// define the largest float suitable to print with %f
|
||||||
|
// default: 1e9
|
||||||
|
#ifndef PRINTF_MAX_FLOAT
|
||||||
|
#define PRINTF_MAX_FLOAT 1e9
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the long long types (%llu or %p)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
|
||||||
|
#define PRINTF_SUPPORT_LONG_LONG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the ptrdiff_t type (%t)
|
||||||
|
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
|
||||||
|
#define PRINTF_SUPPORT_PTRDIFF_T
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// internal flag definitions
|
||||||
|
#define FLAGS_ZEROPAD (1U << 0U)
|
||||||
|
#define FLAGS_LEFT (1U << 1U)
|
||||||
|
#define FLAGS_PLUS (1U << 2U)
|
||||||
|
#define FLAGS_SPACE (1U << 3U)
|
||||||
|
#define FLAGS_HASH (1U << 4U)
|
||||||
|
#define FLAGS_UPPERCASE (1U << 5U)
|
||||||
|
#define FLAGS_CHAR (1U << 6U)
|
||||||
|
#define FLAGS_SHORT (1U << 7U)
|
||||||
|
#define FLAGS_LONG (1U << 8U)
|
||||||
|
#define FLAGS_LONG_LONG (1U << 9U)
|
||||||
|
#define FLAGS_PRECISION (1U << 10U)
|
||||||
|
#define FLAGS_ADAPT_EXP (1U << 11U)
|
||||||
|
|
||||||
|
|
||||||
|
// import float.h for DBL_MAX
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
#include <float.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// output function type
|
||||||
|
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
|
||||||
|
|
||||||
|
|
||||||
|
// wrapper (used as buffer) for output function type
|
||||||
|
typedef struct {
|
||||||
|
void (*fct)(char character, void* arg);
|
||||||
|
void* arg;
|
||||||
|
} out_fct_wrap_type;
|
||||||
|
|
||||||
|
|
||||||
|
// internal buffer output
|
||||||
|
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
if (idx < maxlen) {
|
||||||
|
((char*)buffer)[idx] = character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal null output
|
||||||
|
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)character; (void)buffer; (void)idx; (void)maxlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal _putchar wrapper
|
||||||
|
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)buffer; (void)idx; (void)maxlen;
|
||||||
|
if (character) {
|
||||||
|
_putchar(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal output function wrapper
|
||||||
|
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)idx; (void)maxlen;
|
||||||
|
if (character) {
|
||||||
|
// buffer is the output fct pointer
|
||||||
|
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal secure strlen
|
||||||
|
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
|
||||||
|
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
|
||||||
|
{
|
||||||
|
const char* s;
|
||||||
|
for (s = str; *s && maxsize--; ++s);
|
||||||
|
return (unsigned int)(s - str);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal test if char is a digit (0-9)
|
||||||
|
// \return true if char is a digit
|
||||||
|
static inline bool _is_digit(char ch)
|
||||||
|
{
|
||||||
|
return (ch >= '0') && (ch <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal ASCII string to unsigned int conversion
|
||||||
|
static unsigned int _atoi(const char** str)
|
||||||
|
{
|
||||||
|
unsigned int i = 0U;
|
||||||
|
while (_is_digit(**str)) {
|
||||||
|
i = i * 10U + (unsigned int)(*((*str)++) - '0');
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// output the specified string in reverse, taking care of any zero-padding
|
||||||
|
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
const size_t start_idx = idx;
|
||||||
|
|
||||||
|
// pad spaces up to given width
|
||||||
|
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
|
||||||
|
for (size_t i = len; i < width; i++) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse string
|
||||||
|
while (len) {
|
||||||
|
out(buf[--len], buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append pad spaces up to given width
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (idx - start_idx < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa format
|
||||||
|
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
// pad leading zeros
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||||
|
width--;
|
||||||
|
}
|
||||||
|
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle hash
|
||||||
|
if (flags & FLAGS_HASH) {
|
||||||
|
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
|
||||||
|
len--;
|
||||||
|
if (len && (base == 16U)) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'x';
|
||||||
|
}
|
||||||
|
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'X';
|
||||||
|
}
|
||||||
|
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'b';
|
||||||
|
}
|
||||||
|
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||||
|
if (negative) {
|
||||||
|
buf[len++] = '-';
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_PLUS) {
|
||||||
|
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_SPACE) {
|
||||||
|
buf[len++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa for 'long' type
|
||||||
|
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
|
||||||
|
// no hash for 0 values
|
||||||
|
if (!value) {
|
||||||
|
flags &= ~FLAGS_HASH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write if precision != 0 and value is != 0
|
||||||
|
if (!(flags & FLAGS_PRECISION) || value) {
|
||||||
|
do {
|
||||||
|
const char digit = (char)(value % base);
|
||||||
|
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||||
|
value /= base;
|
||||||
|
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa for 'long long' type
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
|
||||||
|
// no hash for 0 values
|
||||||
|
if (!value) {
|
||||||
|
flags &= ~FLAGS_HASH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write if precision != 0 and value is != 0
|
||||||
|
if (!(flags & FLAGS_PRECISION) || value) {
|
||||||
|
do {
|
||||||
|
const char digit = (char)(value % base);
|
||||||
|
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||||
|
value /= base;
|
||||||
|
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||||
|
}
|
||||||
|
#endif // PRINTF_SUPPORT_LONG_LONG
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
|
||||||
|
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// internal ftoa for fixed decimal floating point
|
||||||
|
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_FTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
double diff = 0.0;
|
||||||
|
|
||||||
|
// powers of 10
|
||||||
|
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||||
|
|
||||||
|
// test for special values
|
||||||
|
if (value != value)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
|
||||||
|
if (value < -DBL_MAX)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
|
||||||
|
if (value > DBL_MAX)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
|
||||||
|
|
||||||
|
// test for very large values
|
||||||
|
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
|
||||||
|
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||||
|
#else
|
||||||
|
return 0U;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for negative
|
||||||
|
bool negative = false;
|
||||||
|
if (value < 0) {
|
||||||
|
negative = true;
|
||||||
|
value = 0 - value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default precision, if not set explicitly
|
||||||
|
if (!(flags & FLAGS_PRECISION)) {
|
||||||
|
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||||
|
}
|
||||||
|
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
|
||||||
|
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
prec--;
|
||||||
|
}
|
||||||
|
|
||||||
|
int whole = (int)value;
|
||||||
|
double tmp = (value - whole) * pow10[prec];
|
||||||
|
unsigned long frac = (unsigned long)tmp;
|
||||||
|
diff = tmp - frac;
|
||||||
|
|
||||||
|
if (diff > 0.5) {
|
||||||
|
++frac;
|
||||||
|
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
|
||||||
|
if (frac >= pow10[prec]) {
|
||||||
|
frac = 0;
|
||||||
|
++whole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (diff < 0.5) {
|
||||||
|
}
|
||||||
|
else if ((frac == 0U) || (frac & 1U)) {
|
||||||
|
// if halfway, round up if odd OR if last digit is 0
|
||||||
|
++frac;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prec == 0U) {
|
||||||
|
diff = value - (double)whole;
|
||||||
|
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
|
||||||
|
// exactly 0.5 and ODD, then round up
|
||||||
|
// 1.5 -> 2, but 2.5 -> 2
|
||||||
|
++whole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unsigned int count = prec;
|
||||||
|
// now do fractional part, as an unsigned number
|
||||||
|
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
--count;
|
||||||
|
buf[len++] = (char)(48U + (frac % 10U));
|
||||||
|
if (!(frac /= 10U)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add extra 0s
|
||||||
|
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
// add decimal
|
||||||
|
buf[len++] = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do whole part, number is reversed
|
||||||
|
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
buf[len++] = (char)(48 + (whole % 10));
|
||||||
|
if (!(whole /= 10)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pad leading zeros
|
||||||
|
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
|
||||||
|
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||||
|
width--;
|
||||||
|
}
|
||||||
|
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
if (negative) {
|
||||||
|
buf[len++] = '-';
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_PLUS) {
|
||||||
|
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_SPACE) {
|
||||||
|
buf[len++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
|
||||||
|
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
// check for NaN and special values
|
||||||
|
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
|
||||||
|
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the sign
|
||||||
|
const bool negative = value < 0;
|
||||||
|
if (negative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default precision
|
||||||
|
if (!(flags & FLAGS_PRECISION)) {
|
||||||
|
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the decimal exponent
|
||||||
|
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
|
||||||
|
union {
|
||||||
|
uint64_t U;
|
||||||
|
double F;
|
||||||
|
} conv;
|
||||||
|
|
||||||
|
conv.F = value;
|
||||||
|
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
|
||||||
|
conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
|
||||||
|
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
|
||||||
|
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
|
||||||
|
// now we want to compute 10^expval but we want to be sure it won't overflow
|
||||||
|
exp2 = (int)(expval * 3.321928094887362 + 0.5);
|
||||||
|
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
|
||||||
|
const double z2 = z * z;
|
||||||
|
conv.U = (uint64_t)(exp2 + 1023) << 52U;
|
||||||
|
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
|
||||||
|
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
|
||||||
|
// correct for rounding errors
|
||||||
|
if (value < conv.F) {
|
||||||
|
expval--;
|
||||||
|
conv.F /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
|
||||||
|
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
|
||||||
|
|
||||||
|
// in "%g" mode, "prec" is the number of *significant figures* not decimals
|
||||||
|
if (flags & FLAGS_ADAPT_EXP) {
|
||||||
|
// do we want to fall-back to "%f" mode?
|
||||||
|
if ((value >= 1e-4) && (value < 1e6)) {
|
||||||
|
if ((int)prec > expval) {
|
||||||
|
prec = (unsigned)((int)prec - expval - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prec = 0;
|
||||||
|
}
|
||||||
|
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
|
||||||
|
// no characters in exponent
|
||||||
|
minwidth = 0U;
|
||||||
|
expval = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we use one sigfig for the whole part
|
||||||
|
if ((prec > 0) && (flags & FLAGS_PRECISION)) {
|
||||||
|
--prec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will everything fit?
|
||||||
|
unsigned int fwidth = width;
|
||||||
|
if (width > minwidth) {
|
||||||
|
// we didn't fall-back so subtract the characters required for the exponent
|
||||||
|
fwidth -= minwidth;
|
||||||
|
} else {
|
||||||
|
// not enough characters, so go back to default sizing
|
||||||
|
fwidth = 0U;
|
||||||
|
}
|
||||||
|
if ((flags & FLAGS_LEFT) && minwidth) {
|
||||||
|
// if we're padding on the right, DON'T pad the floating part
|
||||||
|
fwidth = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rescale the float value
|
||||||
|
if (expval) {
|
||||||
|
value /= conv.F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the floating part
|
||||||
|
const size_t start_idx = idx;
|
||||||
|
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
|
||||||
|
|
||||||
|
// output the exponent part
|
||||||
|
if (minwidth) {
|
||||||
|
// output the exponential symbol
|
||||||
|
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
|
||||||
|
// output the exponent value
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
|
||||||
|
// might need to right-pad spaces
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif // PRINTF_SUPPORT_FLOAT
|
||||||
|
|
||||||
|
|
||||||
|
// internal vsnprintf
|
||||||
|
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
|
||||||
|
{
|
||||||
|
unsigned int flags, width, precision, n;
|
||||||
|
size_t idx = 0U;
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
// use null output function
|
||||||
|
out = _out_null;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*format)
|
||||||
|
{
|
||||||
|
// format specifier? %[flags][width][.precision][length]
|
||||||
|
if (*format != '%') {
|
||||||
|
// no
|
||||||
|
out(*format, buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// yes, evaluate it
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate flags
|
||||||
|
flags = 0U;
|
||||||
|
do {
|
||||||
|
switch (*format) {
|
||||||
|
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
|
||||||
|
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
|
||||||
|
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
|
||||||
|
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
|
||||||
|
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
|
||||||
|
default : n = 0U; break;
|
||||||
|
}
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
// evaluate width field
|
||||||
|
width = 0U;
|
||||||
|
if (_is_digit(*format)) {
|
||||||
|
width = _atoi(&format);
|
||||||
|
}
|
||||||
|
else if (*format == '*') {
|
||||||
|
const int w = va_arg(va, int);
|
||||||
|
if (w < 0) {
|
||||||
|
flags |= FLAGS_LEFT; // reverse padding
|
||||||
|
width = (unsigned int)-w;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width = (unsigned int)w;
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate precision field
|
||||||
|
precision = 0U;
|
||||||
|
if (*format == '.') {
|
||||||
|
flags |= FLAGS_PRECISION;
|
||||||
|
format++;
|
||||||
|
if (_is_digit(*format)) {
|
||||||
|
precision = _atoi(&format);
|
||||||
|
}
|
||||||
|
else if (*format == '*') {
|
||||||
|
const int prec = (int)va_arg(va, int);
|
||||||
|
precision = prec > 0 ? (unsigned int)prec : 0U;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate length field
|
||||||
|
switch (*format) {
|
||||||
|
case 'l' :
|
||||||
|
flags |= FLAGS_LONG;
|
||||||
|
format++;
|
||||||
|
if (*format == 'l') {
|
||||||
|
flags |= FLAGS_LONG_LONG;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h' :
|
||||||
|
flags |= FLAGS_SHORT;
|
||||||
|
format++;
|
||||||
|
if (*format == 'h') {
|
||||||
|
flags |= FLAGS_CHAR;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
|
||||||
|
case 't' :
|
||||||
|
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case 'j' :
|
||||||
|
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
case 'z' :
|
||||||
|
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate specifier
|
||||||
|
switch (*format) {
|
||||||
|
case 'd' :
|
||||||
|
case 'i' :
|
||||||
|
case 'u' :
|
||||||
|
case 'x' :
|
||||||
|
case 'X' :
|
||||||
|
case 'o' :
|
||||||
|
case 'b' : {
|
||||||
|
// set the base
|
||||||
|
unsigned int base;
|
||||||
|
if (*format == 'x' || *format == 'X') {
|
||||||
|
base = 16U;
|
||||||
|
}
|
||||||
|
else if (*format == 'o') {
|
||||||
|
base = 8U;
|
||||||
|
}
|
||||||
|
else if (*format == 'b') {
|
||||||
|
base = 2U;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
base = 10U;
|
||||||
|
flags &= ~FLAGS_HASH; // no hash for dec format
|
||||||
|
}
|
||||||
|
// uppercase
|
||||||
|
if (*format == 'X') {
|
||||||
|
flags |= FLAGS_UPPERCASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no plus or space flag for u, x, X, o, b
|
||||||
|
if ((*format != 'i') && (*format != 'd')) {
|
||||||
|
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore '0' flag when precision is given
|
||||||
|
if (flags & FLAGS_PRECISION) {
|
||||||
|
flags &= ~FLAGS_ZEROPAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the integer
|
||||||
|
if ((*format == 'i') || (*format == 'd')) {
|
||||||
|
// signed
|
||||||
|
if (flags & FLAGS_LONG_LONG) {
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
const long long value = va_arg(va, long long);
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_LONG) {
|
||||||
|
const long value = va_arg(va, long);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// unsigned
|
||||||
|
if (flags & FLAGS_LONG_LONG) {
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_LONG) {
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
case 'f' :
|
||||||
|
case 'F' :
|
||||||
|
if (*format == 'F') flags |= FLAGS_UPPERCASE;
|
||||||
|
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
case 'g':
|
||||||
|
case 'G':
|
||||||
|
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
|
||||||
|
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
|
||||||
|
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif // PRINTF_SUPPORT_FLOAT
|
||||||
|
case 'c' : {
|
||||||
|
unsigned int l = 1U;
|
||||||
|
// pre padding
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// char output
|
||||||
|
out((char)va_arg(va, int), buffer, idx++, maxlen);
|
||||||
|
// post padding
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's' : {
|
||||||
|
const char* p = va_arg(va, char*);
|
||||||
|
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
|
||||||
|
// pre padding
|
||||||
|
if (flags & FLAGS_PRECISION) {
|
||||||
|
l = (l < precision ? l : precision);
|
||||||
|
}
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// string output
|
||||||
|
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
|
||||||
|
out(*(p++), buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
// post padding
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'p' : {
|
||||||
|
width = sizeof(void*) * 2U;
|
||||||
|
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
|
||||||
|
if (is_ll) {
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#endif
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '%' :
|
||||||
|
out('%', buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default :
|
||||||
|
out(*format, buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// termination
|
||||||
|
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||||
|
|
||||||
|
// return written chars without terminating \0
|
||||||
|
return (int)idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
int printf_(const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
char buffer[1];
|
||||||
|
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int sprintf_(char* buffer, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int snprintf_(char* buffer, size_t count, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vprintf_(const char* format, va_list va)
|
||||||
|
{
|
||||||
|
char buffer[1];
|
||||||
|
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
|
||||||
|
{
|
||||||
|
return _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const out_fct_wrap_type out_fct_wrap = { out, arg };
|
||||||
|
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// \author (c) Marco Paland (info@paland.com)
|
||||||
|
// 2014-2019, PALANDesign Hannover, Germany
|
||||||
|
//
|
||||||
|
// \license The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
|
||||||
|
// embedded systems with a very limited resources.
|
||||||
|
// Use this instead of bloated standard/newlib printf.
|
||||||
|
// These routines are thread safe and reentrant.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef _PRINTF_H_
|
||||||
|
#define _PRINTF_H_
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output a character to a custom device like UART, used by the printf() function
|
||||||
|
* This function is declared here only. You have to write your custom implementation somewhere
|
||||||
|
* \param character Character to output
|
||||||
|
*/
|
||||||
|
void _putchar(char character);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny printf implementation
|
||||||
|
* You have to implement _putchar if you use printf()
|
||||||
|
* To avoid conflicts with the regular printf() API it is overridden by macro defines
|
||||||
|
* and internal underscore-appended functions like printf_() are used
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are written into the array, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define printf printf_
|
||||||
|
int printf_(const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny sprintf implementation
|
||||||
|
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
|
||||||
|
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define sprintf sprintf_
|
||||||
|
int sprintf_(char* buffer, const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny snprintf/vsnprintf implementation
|
||||||
|
* \param buffer A pointer to the buffer where to store the formatted string
|
||||||
|
* \param count The maximum number of characters to store in the buffer, including a terminating null character
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \param va A value identifying a variable arguments list
|
||||||
|
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
|
||||||
|
* null character. A value equal or larger than count indicates truncation. Only when the returned value
|
||||||
|
* is non-negative and less than count, the string has been completely written.
|
||||||
|
*/
|
||||||
|
#define snprintf snprintf_
|
||||||
|
#define vsnprintf vsnprintf_
|
||||||
|
int snprintf_(char* buffer, size_t count, const char* format, ...);
|
||||||
|
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny vprintf implementation
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \param va A value identifying a variable arguments list
|
||||||
|
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define vprintf vprintf_
|
||||||
|
int vprintf_(const char* format, va_list va);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* printf with output function
|
||||||
|
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
|
||||||
|
* \param out An output function which takes one character and an argument pointer
|
||||||
|
* \param arg An argument pointer for user data passed to output function
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are sent to the output function, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _PRINTF_H_
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
#define NUM_TRIES 5
|
||||||
|
#define WORD_LENGTH 5
|
||||||
|
|
||||||
|
enum character_state {
|
||||||
|
CORRECT_PLACEMENT = 0, // Correct character, in the correct index of the word.
|
||||||
|
INCORRECT_PLACEMENT = 1, // Correct character, in the incorrect index of the word.
|
||||||
|
INCORRECT = 2, // Character does not appear in the word.
|
||||||
|
};
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
// This variable will have the address of the UART device
|
||||||
|
uintptr_t uart_base_vaddr;
|
||||||
|
|
||||||
|
#define RHR_MASK 0b111111111
|
||||||
|
#define UARTDR 0x000
|
||||||
|
#define UARTFR 0x018
|
||||||
|
#define UARTIMSC 0x038
|
||||||
|
#define UARTICR 0x044
|
||||||
|
#define PL011_UARTFR_TXFF (1 << 5)
|
||||||
|
#define PL011_UARTFR_RXFE (1 << 4)
|
||||||
|
|
||||||
|
#define REG_PTR(base, offset) ((volatile uint32_t *)((base) + (offset)))
|
||||||
|
|
||||||
|
void uart_init() {
|
||||||
|
*REG_PTR(uart_base_vaddr, UARTIMSC) = 0x50;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uart_get_char() {
|
||||||
|
int ch = 0;
|
||||||
|
|
||||||
|
if ((*REG_PTR(uart_base_vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) {
|
||||||
|
ch = *REG_PTR(uart_base_vaddr, UARTDR) & RHR_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uart_put_char(int ch) {
|
||||||
|
while ((*REG_PTR(uart_base_vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0);
|
||||||
|
|
||||||
|
*REG_PTR(uart_base_vaddr, UARTDR) = ch;
|
||||||
|
if (ch == '\r') {
|
||||||
|
uart_put_char('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void uart_handle_irq() {
|
||||||
|
*REG_PTR(uart_base_vaddr, UARTICR) = 0x7f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uart_put_str(char *str) {
|
||||||
|
while (*str) {
|
||||||
|
uart_put_char(*str);
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void) {
|
||||||
|
// First we initialise the UART device, which will write to the
|
||||||
|
// device's hardware registers. Which means we need access to
|
||||||
|
// the UART device.
|
||||||
|
uart_init();
|
||||||
|
// After initialising the UART, print a message to the terminal
|
||||||
|
// saying that the serial server has started.
|
||||||
|
uart_put_str("SERIAL SERVER: starting\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UART_IRQ_CH 1
|
||||||
|
#define CLIENT_CH 2
|
||||||
|
|
||||||
|
uintptr_t serial_to_client_vaddr;
|
||||||
|
uintptr_t client_to_serial_vaddr;
|
||||||
|
|
||||||
|
void notified(microkit_channel channel) {
|
||||||
|
switch (channel) {
|
||||||
|
case UART_IRQ_CH:
|
||||||
|
((char *)serial_to_client_vaddr)[0] = uart_get_char();
|
||||||
|
uart_handle_irq();
|
||||||
|
microkit_irq_ack(channel);
|
||||||
|
microkit_notify(CLIENT_CH);
|
||||||
|
break;
|
||||||
|
case CLIENT_CH:
|
||||||
|
uart_put_str((char *)client_to_serial_vaddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,20 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// The structure of the kernel image header and the magic value comes from the
|
||||||
|
// Linux kernel documentation that can be found here:
|
||||||
|
// https://www.kernel.org/doc/Documentation/arm64/booting.txt
|
||||||
|
|
||||||
|
#define LINUX_IMAGE_MAGIC 0x644d5241
|
||||||
|
|
||||||
|
struct linux_image_header {
|
||||||
|
uint32_t code0; // Executable code
|
||||||
|
uint32_t code1; // Executable code
|
||||||
|
uint64_t text_offset; // Image load offset, little endian
|
||||||
|
uint64_t image_size; // Effective Image size, little endian
|
||||||
|
uint64_t flags; // kernel flags, little endian
|
||||||
|
uint64_t res2; // reserved
|
||||||
|
uint64_t res3; // reserved
|
||||||
|
uint64_t res4; // reserved
|
||||||
|
uint32_t magic; // Magic number, little endian, "ARM\x64"
|
||||||
|
uint32_t res5; // reserved (used for PE COFF offset)
|
||||||
|
};
|
||||||
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hsr.h"
|
||||||
|
#include "util/util.h"
|
||||||
|
#include "fault.h"
|
||||||
|
|
||||||
|
// #define CPSR_THUMB (1 << 5)
|
||||||
|
// #define CPSR_IS_THUMB(x) ((x) & CPSR_THUMB)
|
||||||
|
|
||||||
|
// int fault_is_32bit_instruction(seL4_UserContext *regs)
|
||||||
|
// {
|
||||||
|
// // @ivanv: assuming VCPU fault
|
||||||
|
// return !CPSR_IS_THUMB(regs->spsr);
|
||||||
|
// }
|
||||||
|
|
||||||
|
bool fault_advance_vcpu(seL4_UserContext *regs) {
|
||||||
|
// For now we just ignore it and continue
|
||||||
|
// Assume 64-bit instruction
|
||||||
|
regs->pc += 4;
|
||||||
|
int err = seL4_TCB_WriteRegisters(BASE_VM_TCB_CAP + GUEST_ID, true, 0, SEL4_USER_CONTEXT_SIZE, regs);
|
||||||
|
assert(err == seL4_NoError);
|
||||||
|
|
||||||
|
return (err == seL4_NoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *fault_to_string(seL4_Word fault_label) {
|
||||||
|
switch (fault_label) {
|
||||||
|
case seL4_Fault_VMFault: return "virtual memory";
|
||||||
|
case seL4_Fault_UnknownSyscall: return "unknown syscall";
|
||||||
|
case seL4_Fault_UserException: return "user exception";
|
||||||
|
case seL4_Fault_VGICMaintenance: return "VGIC maintenance";
|
||||||
|
case seL4_Fault_VCPUFault: return "VCPU fault";
|
||||||
|
case seL4_Fault_VPPIEvent: return "VPPI event";
|
||||||
|
default: return "unknown fault";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum fault_width {
|
||||||
|
WIDTH_DOUBLEWORD = 0,
|
||||||
|
WIDTH_WORD = 1,
|
||||||
|
WIDTH_HALFWORD = 2,
|
||||||
|
WIDTH_BYTE = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum fault_width fault_get_width(uint64_t fsr)
|
||||||
|
{
|
||||||
|
if (HSR_IS_SYNDROME_VALID(fsr)) {
|
||||||
|
switch (HSR_SYNDROME_WIDTH(fsr)) {
|
||||||
|
case 0: return WIDTH_BYTE;
|
||||||
|
case 1: return WIDTH_HALFWORD;
|
||||||
|
case 2: return WIDTH_WORD;
|
||||||
|
case 3: return WIDTH_DOUBLEWORD;
|
||||||
|
default:
|
||||||
|
// @ivanv: reviist
|
||||||
|
// print_fault(f);
|
||||||
|
assert(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_VMM_ERR("Received invalid FSR: 0x%lx\n", fsr);
|
||||||
|
// @ivanv: reviist
|
||||||
|
// int rt;
|
||||||
|
// rt = decode_instruction(f);
|
||||||
|
// assert(rt >= 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fault_get_data_mask(uint64_t addr, uint64_t fsr)
|
||||||
|
{
|
||||||
|
uint64_t mask = 0;
|
||||||
|
switch (fault_get_width(fsr)) {
|
||||||
|
case WIDTH_BYTE:
|
||||||
|
mask = 0x000000ff;
|
||||||
|
assert(!(addr & 0x0));
|
||||||
|
break;
|
||||||
|
case WIDTH_HALFWORD:
|
||||||
|
mask = 0x0000ffff;
|
||||||
|
assert(!(addr & 0x1));
|
||||||
|
break;
|
||||||
|
case WIDTH_WORD:
|
||||||
|
mask = 0xffffffff;
|
||||||
|
assert(!(addr & 0x3));
|
||||||
|
break;
|
||||||
|
case WIDTH_DOUBLEWORD:
|
||||||
|
mask = ~mask;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("unknown width: 0x%lx, from FSR: 0x%lx, addr: 0x%lx\n",
|
||||||
|
fault_get_width(fsr), fsr, addr);
|
||||||
|
assert(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
mask <<= (addr & 0x3) * 8;
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t wzr = 0;
|
||||||
|
uint64_t *decode_rt(uint64_t reg, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case 0: return ®s->x0;
|
||||||
|
case 1: return ®s->x1;
|
||||||
|
case 2: return ®s->x2;
|
||||||
|
case 3: return ®s->x3;
|
||||||
|
case 4: return ®s->x4;
|
||||||
|
case 5: return ®s->x5;
|
||||||
|
case 6: return ®s->x6;
|
||||||
|
case 7: return ®s->x7;
|
||||||
|
case 8: return ®s->x8;
|
||||||
|
case 9: return ®s->x9;
|
||||||
|
case 10: return ®s->x10;
|
||||||
|
case 11: return ®s->x11;
|
||||||
|
case 12: return ®s->x12;
|
||||||
|
case 13: return ®s->x13;
|
||||||
|
case 14: return ®s->x14;
|
||||||
|
case 15: return ®s->x15;
|
||||||
|
case 16: return ®s->x16;
|
||||||
|
case 17: return ®s->x17;
|
||||||
|
case 18: return ®s->x18;
|
||||||
|
case 19: return ®s->x19;
|
||||||
|
case 20: return ®s->x20;
|
||||||
|
case 21: return ®s->x21;
|
||||||
|
case 22: return ®s->x22;
|
||||||
|
case 23: return ®s->x23;
|
||||||
|
case 24: return ®s->x24;
|
||||||
|
case 25: return ®s->x25;
|
||||||
|
case 26: return ®s->x26;
|
||||||
|
case 27: return ®s->x27;
|
||||||
|
case 28: return ®s->x28;
|
||||||
|
case 29: return ®s->x29;
|
||||||
|
case 30: return ®s->x30;
|
||||||
|
case 31: return &wzr;
|
||||||
|
default:
|
||||||
|
printf("invalid reg %d\n", reg);
|
||||||
|
assert(!"Invalid register");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fault_is_write(uint64_t fsr)
|
||||||
|
{
|
||||||
|
return (fsr & (1U << 6)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fault_is_read(uint64_t fsr)
|
||||||
|
{
|
||||||
|
return !fault_is_write(fsr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_rt(uint64_t fsr)
|
||||||
|
{
|
||||||
|
|
||||||
|
int rt = -1;
|
||||||
|
if (HSR_IS_SYNDROME_VALID(fsr)) {
|
||||||
|
rt = HSR_SYNDROME_RT(fsr);
|
||||||
|
} else {
|
||||||
|
printf("decode_insturction for arm64 not implemented\n");
|
||||||
|
assert(0);
|
||||||
|
// @ivanv: implement decode instruction for aarch64
|
||||||
|
// rt = decode_instruction(f);
|
||||||
|
}
|
||||||
|
assert(rt >= 0);
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fault_get_data(seL4_UserContext *regs, uint64_t fsr)
|
||||||
|
{
|
||||||
|
/* Get register opearand */
|
||||||
|
int rt = get_rt(fsr);
|
||||||
|
|
||||||
|
uint64_t data = *decode_rt(rt, regs);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fault_emulate(seL4_UserContext *regs, uint64_t reg, uint64_t addr, uint64_t fsr, uint64_t reg_val)
|
||||||
|
{
|
||||||
|
uint64_t m, s;
|
||||||
|
s = (addr & 0x3) * 8;
|
||||||
|
m = fault_get_data_mask(addr, fsr);
|
||||||
|
if (fault_is_read(fsr)) {
|
||||||
|
/* Read data must be shifted to lsb */
|
||||||
|
return (reg & ~(m >> s)) | ((reg_val & m) >> s);
|
||||||
|
} else {
|
||||||
|
/* Data to write must be shifted left to compensate for alignment */
|
||||||
|
return (reg & ~m) | ((reg_val << s) & m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fault_advance(seL4_UserContext *regs, uint64_t addr, uint64_t fsr, uint64_t reg_val)
|
||||||
|
{
|
||||||
|
/* Get register opearand */
|
||||||
|
int rt = get_rt(fsr);
|
||||||
|
|
||||||
|
uint64_t *reg_ctx = decode_rt(rt, regs);
|
||||||
|
*reg_ctx = fault_emulate(regs, *reg_ctx, addr, fsr, reg_val);
|
||||||
|
// DFAULT("%s: Emulate fault @ 0x%x from PC 0x%x\n",
|
||||||
|
// fault->vcpu->vm->vm_name, fault->addr, fault->ip);
|
||||||
|
|
||||||
|
return fault_advance_vcpu(regs);
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
|
||||||
|
bool fault_advance_vcpu(seL4_UserContext *regs);
|
||||||
|
bool fault_advance(seL4_UserContext *regs, uint64_t addr, uint64_t fsr, uint64_t reg_val);
|
||||||
|
uint64_t fault_get_data_mask(uint64_t addr, uint64_t fsr);
|
||||||
|
uint64_t fault_get_data(seL4_UserContext *regs, uint64_t fsr);
|
||||||
|
uint64_t fault_emulate(seL4_UserContext *regs, uint64_t reg, uint64_t addr, uint64_t fsr, uint64_t reg_val);
|
||||||
|
|
||||||
|
/* Take the fault label given by the kernel and convert it to a string. */
|
||||||
|
char *fault_to_string(seL4_Word fault_label);
|
||||||
|
|
||||||
|
bool fault_is_write(uint64_t fsr);
|
||||||
|
bool fault_is_read(uint64_t fsr);
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/* @ivanv: need to consider the case where not all of these are used! */
|
||||||
|
/* @probits is used to say that this section contains data. */
|
||||||
|
/* The attributes "aw" is to say that the section is allocatable and that it is writeable. */
|
||||||
|
|
||||||
|
#if defined(VM_KERNEL_IMAGE_PATH)
|
||||||
|
.section .guest_kernel_image, "aw", @progbits
|
||||||
|
.global _guest_kernel_image, _guest_kernel_image_end
|
||||||
|
_guest_kernel_image:
|
||||||
|
.incbin VM_KERNEL_IMAGE_PATH
|
||||||
|
_guest_kernel_image_end:
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(VM_DTB_IMAGE_PATH)
|
||||||
|
.section .guest_dtb_image, "aw", @progbits
|
||||||
|
.global _guest_dtb_image, _guest_dtb_image_end
|
||||||
|
_guest_dtb_image:
|
||||||
|
.incbin VM_DTB_IMAGE_PATH
|
||||||
|
_guest_dtb_image_end:
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(VM_INITRD_IMAGE_PATH)
|
||||||
|
.section .guest_initrd_image, "aw", @progbits
|
||||||
|
.global _guest_initrd_image, _guest_initrd_image_end
|
||||||
|
_guest_initrd_image:
|
||||||
|
.incbin VM_INITRD_IMAGE_PATH
|
||||||
|
_guest_initrd_image_end:
|
||||||
|
#endif
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
// @ivanv, where do these come from?
|
||||||
|
// @ivanv: license
|
||||||
|
#define HSR_EXCEPTION_CLASS_SHIFT (26)
|
||||||
|
#define HSR_EXCEPTION_CLASS_MASK (HSR_MAX_EXCEPTION << HSR_EXCEPTION_CLASS_SHIFT)
|
||||||
|
#define HSR_EXCEPTION_CLASS(hsr) (((hsr) & HSR_EXCEPTION_CLASS_MASK) >> HSR_EXCEPTION_CLASS_SHIFT)
|
||||||
|
|
||||||
|
#define HSR_SYNDROME_VALID (1 << 24)
|
||||||
|
#define HSR_IS_SYNDROME_VALID(hsr) ((hsr) & HSR_SYNDROME_VALID)
|
||||||
|
#define HSR_SYNDROME_WIDTH(x) (((x) >> 22) & 0x3)
|
||||||
|
#define HSR_SYNDROME_RT(x) (((x) >> 16) & 0x1f)
|
||||||
|
|
||||||
|
/* HSR Exception Value */
|
||||||
|
#define HSR_UNKNOWN_EXCEPTION (0x0)
|
||||||
|
#define HSR_WFx_EXCEPTION (0x1)
|
||||||
|
#define HSR_CP15_32_EXCEPTION (0x3)
|
||||||
|
#define HSR_CP15_64_EXCEPTION (0x4)
|
||||||
|
#define HSR_CP14_32_EXCEPTION (0x5)
|
||||||
|
#define HSR_CP14_LC_32_EXCEPTION (0x6)
|
||||||
|
#define HSR_SIMD_EXCEPTION (0x7)
|
||||||
|
#define HSR_CP10_EXCEPTION (0x8)
|
||||||
|
#define HSR_CP14_EXCEPTION (0xC)
|
||||||
|
#define HSR_ILLEGAL_EXCEPTION (0xE)
|
||||||
|
#define HSR_SVC_32_EXCEPTION (0x11)
|
||||||
|
#define HSR_HVC_32_EXCEPTION (0x12)
|
||||||
|
#define HSR_SMC_32_EXCEPTION (0x13)
|
||||||
|
#define HSR_SVC_64_EXCEPTION (0x15)
|
||||||
|
#define HSR_HVC_64_EXCEPTION (0x16)
|
||||||
|
#define HSR_SMC_64_EXCEPTION (0x17)
|
||||||
|
#define HSR_SYSREG_64_EXCEPTION (0x18)
|
||||||
|
#define HSR_IMPL_DEF_EXCEPTION (0x1f)
|
||||||
|
#define HSR_IABT_LOW_EXCEPTION (0x20)
|
||||||
|
#define HSR_IABT_CURR_EXCEPTION (0x21)
|
||||||
|
#define HSR_PC_ALIGN_EXCEPTION (0x22)
|
||||||
|
#define HSR_DABT_LOW_EXCEPTION (0x24)
|
||||||
|
#define HSR_DABT_CUR_EXCEPTION (0x25)
|
||||||
|
#define HSR_SP_ALIGN_EXCEPTION (0x26)
|
||||||
|
#define HSR_FP32_EXCEPTION (0x28)
|
||||||
|
#define HSR_FP64_EXCEPTION (0x2C)
|
||||||
|
#define HSR_SERROR_EXCEPTION (0x2F)
|
||||||
|
#define HSR_BRK_LOW_EXCEPTION (0x30)
|
||||||
|
#define HSR_BRK_CUR_EXCEPTION (0x31)
|
||||||
|
#define HSR_SWSTEP_LOW_EXCEPTION (0x32)
|
||||||
|
#define HSR_SWSTEP_CUR_EXCEPTION (0x33)
|
||||||
|
#define HSR_WATCHPT_LOW_EXCEPTION (0x34)
|
||||||
|
#define HSR_WATCHPT_CUR_EXCEPTION (0x35)
|
||||||
|
#define HSR_SWBRK_32_EXCEPTION (0x38)
|
||||||
|
#define HSW_VECTOR_32_EXCEPTION (0x3a)
|
||||||
|
#define HSR_SWBRK_64_EXCEPTION (0x3c)
|
||||||
|
/* Remaining values (0x3d - 0x3f) are reserved */
|
||||||
|
#define HSR_MAX_EXCEPTION (0x3f)
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "psci.h"
|
||||||
|
#include "smc.h"
|
||||||
|
#include "fault.h"
|
||||||
|
#include "util/util.h"
|
||||||
|
#include "vmm.h"
|
||||||
|
|
||||||
|
bool handle_psci(uint64_t vcpu_id, seL4_UserContext *regs, uint64_t fn_number, uint32_t hsr)
|
||||||
|
{
|
||||||
|
// @ivanv: write a note about what convention we assume, should we be checking
|
||||||
|
// the convention?
|
||||||
|
switch (fn_number) {
|
||||||
|
case PSCI_VERSION: {
|
||||||
|
/* We support PSCI version 1.2 */
|
||||||
|
uint32_t version = PSCI_MAJOR_VERSION(1) | PSCI_MINOR_VERSION(2);
|
||||||
|
smc_set_return_value(regs, version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PSCI_CPU_ON: {
|
||||||
|
uintptr_t target_cpu = smc_get_arg(regs, 1);
|
||||||
|
// Right now we only have one vCPU and so any fault for a target vCPU
|
||||||
|
// that isn't the one that's already on we consider an error on the
|
||||||
|
// guest's side.
|
||||||
|
// @ivanv: adapt for starting other vCPUs
|
||||||
|
if (target_cpu == vcpu_id) {
|
||||||
|
smc_set_return_value(regs, PSCI_ALREADY_ON);
|
||||||
|
} else {
|
||||||
|
// The guest has requested to turn on a virtual CPU that does
|
||||||
|
// not exist.
|
||||||
|
smc_set_return_value(regs, PSCI_INVALID_PARAMETERS);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PSCI_MIGRATE_INFO_TYPE:
|
||||||
|
/*
|
||||||
|
* There are multiple possible return values for MIGRATE_INFO_TYPE.
|
||||||
|
* In this case returning 2 will tell the guest that this is a
|
||||||
|
* system that does not use a "Trusted OS" as the PSCI
|
||||||
|
* specification says.
|
||||||
|
*/
|
||||||
|
smc_set_return_value(regs, 2);
|
||||||
|
break;
|
||||||
|
case PSCI_FEATURES:
|
||||||
|
// @ivanv: seems weird that we just return nothing here.
|
||||||
|
smc_set_return_value(regs, PSCI_NOT_SUPPORTED);
|
||||||
|
break;
|
||||||
|
case PSCI_SYSTEM_RESET: {
|
||||||
|
bool success = guest_restart();
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("Failed to restart guest\n");
|
||||||
|
smc_set_return_value(regs, PSCI_INTERNAL_FAILURE);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If we've successfully restarted the guest, all we want to do
|
||||||
|
* is reply to the fault that caused us to handle the PSCI call
|
||||||
|
* so that the guest can continue executing. We do not need to
|
||||||
|
* advance the vCPU program counter as we typically do when
|
||||||
|
* handling a fault since the correct PC has been set when we
|
||||||
|
* call guest_restart().
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PSCI_SYSTEM_OFF:
|
||||||
|
guest_stop();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unhandled PSCI function ID 0x%lx\n", fn_number);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = fault_advance_vcpu(regs);
|
||||||
|
assert(success);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <microkit.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Values in this file are taken from the:
|
||||||
|
* ARM Power State Coordination Interface
|
||||||
|
* Platform Design Document
|
||||||
|
* Issue E (PSCI version 1.2)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The PSCI version is represented by a 32-bit unsigned integer.
|
||||||
|
* The upper 15 bits represent the major version.
|
||||||
|
* The lower 16 bits represent the minor version.
|
||||||
|
*/
|
||||||
|
#define PSCI_MAJOR_VERSION(v) ((v) << 16)
|
||||||
|
#define PSCI_MINOR_VERSION(v) ((v) & ((1 << 16) - 1))
|
||||||
|
|
||||||
|
/* PSCI return codes */
|
||||||
|
#define PSCI_SUCCESS 0
|
||||||
|
#define PSCI_NOT_SUPPORTED -1
|
||||||
|
#define PSCI_INVALID_PARAMETERS -2
|
||||||
|
#define PSCI_DENIED -3
|
||||||
|
#define PSCI_ALREADY_ON -4
|
||||||
|
#define PSCI_ON_PENDING -5
|
||||||
|
#define PSCI_INTERNAL_FAILURE -6
|
||||||
|
#define PSCI_NOT_PRESENT -7
|
||||||
|
#define PSCI_DISABLED -8
|
||||||
|
#define PSCI_INVALID_ADDRESS -9
|
||||||
|
|
||||||
|
/* PSCI function IDs */
|
||||||
|
typedef enum psci {
|
||||||
|
PSCI_VERSION = 0x0,
|
||||||
|
PSCI_CPU_SUSPEND = 0x1,
|
||||||
|
PSCI_CPU_OFF = 0x2,
|
||||||
|
PSCI_CPU_ON = 0x3,
|
||||||
|
PSCI_AFFINTY_INFO = 0x4,
|
||||||
|
PSCI_MIGRATE = 0x5,
|
||||||
|
PSCI_MIGRATE_INFO_TYPE = 0x6,
|
||||||
|
PSCI_MIGRATE_INFO_UP_CPU = 0x7,
|
||||||
|
PSCI_SYSTEM_OFF = 0x8,
|
||||||
|
PSCI_SYSTEM_RESET = 0x9,
|
||||||
|
PSCI_FEATURES = 0xa,
|
||||||
|
PSCI_CPU_FREEZE = 0xb,
|
||||||
|
PSCI_CPU_DEFAULT_SUSPEND = 0xc,
|
||||||
|
PSCI_NODE_HW_STATE = 0xd,
|
||||||
|
PSCI_SYSTEM_SUSPEND = 0xe,
|
||||||
|
PSCI_SET_SUSPEND_MODE = 0xf,
|
||||||
|
PSCI_STAT_RESIDENCY = 0x10,
|
||||||
|
PSCI_STAT_COUNT = 0x11,
|
||||||
|
PSCI_SYSTEM_RESET2 = 0x12,
|
||||||
|
PSCI_MEM_PROTECT = 0x13,
|
||||||
|
PSCI_MEM_PROTECT_CHECK_RANGE = 0x14,
|
||||||
|
PSCI_MAX = 0x1f
|
||||||
|
} psci_id_t;
|
||||||
|
|
||||||
|
bool handle_psci(uint64_t vcpu_id, seL4_UserContext *regs, uint64_t fn_number, uint32_t hsr);
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "smc.h"
|
||||||
|
#include "psci.h"
|
||||||
|
#include "util/util.h"
|
||||||
|
|
||||||
|
// Values in this file are taken from:
|
||||||
|
// SMC CALLING CONVENTION
|
||||||
|
// System Software on ARM (R) Platforms
|
||||||
|
// Issue B
|
||||||
|
#define SMC_SERVICE_CALL_MASK 0x3F
|
||||||
|
#define SMC_SERVICE_CALL_SHIFT 24
|
||||||
|
|
||||||
|
#define SMC_FUNC_ID_MASK 0xFFFF
|
||||||
|
|
||||||
|
/* SMC and HVC function identifiers */
|
||||||
|
typedef enum {
|
||||||
|
SMC_CALL_ARM_ARCH = 0,
|
||||||
|
SMC_CALL_CPU_SERVICE = 1,
|
||||||
|
SMC_CALL_SIP_SERVICE = 2,
|
||||||
|
SMC_CALL_OEM_SERVICE = 3,
|
||||||
|
SMC_CALL_STD_SERVICE = 4,
|
||||||
|
SMC_CALL_STD_HYP_SERVICE = 5,
|
||||||
|
SMC_CALL_VENDOR_HYP_SERVICE = 6,
|
||||||
|
SMC_CALL_TRUSTED_APP = 48,
|
||||||
|
SMC_CALL_TRUSTED_OS = 50,
|
||||||
|
SMC_CALL_RESERVED = 64,
|
||||||
|
} smc_call_id_t;
|
||||||
|
|
||||||
|
static smc_call_id_t smc_get_call(uintptr_t func_id)
|
||||||
|
{
|
||||||
|
uint64_t service = ((func_id >> SMC_SERVICE_CALL_SHIFT) & SMC_SERVICE_CALL_MASK);
|
||||||
|
assert(service >= 0 && service <= 0xFFFF);
|
||||||
|
|
||||||
|
if (service <= SMC_CALL_VENDOR_HYP_SERVICE) {
|
||||||
|
return service;
|
||||||
|
} else if (service < SMC_CALL_TRUSTED_APP) {
|
||||||
|
return SMC_CALL_RESERVED;
|
||||||
|
} else if (service < SMC_CALL_TRUSTED_OS) {
|
||||||
|
return SMC_CALL_TRUSTED_APP;
|
||||||
|
} else if (service < SMC_CALL_RESERVED) {
|
||||||
|
return SMC_CALL_TRUSTED_OS;
|
||||||
|
} else {
|
||||||
|
return SMC_CALL_RESERVED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t smc_get_function_number(seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
return regs->x0 & SMC_FUNC_ID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void smc_set_return_value(seL4_UserContext *u, uint64_t val)
|
||||||
|
{
|
||||||
|
u->x0 = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t smc_get_arg(seL4_UserContext *u, uint64_t arg)
|
||||||
|
{
|
||||||
|
switch (arg) {
|
||||||
|
case 1: return u->x1;
|
||||||
|
case 2: return u->x2;
|
||||||
|
case 3: return u->x3;
|
||||||
|
case 4: return u->x4;
|
||||||
|
case 5: return u->x5;
|
||||||
|
case 6: return u->x6;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("trying to get SMC arg: 0x%lx, SMC only has 6 argument registers\n", arg);
|
||||||
|
// @ivanv: come back to this
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void smc_set_arg(seL4_UserContext *u, uint64_t arg, uint64_t val)
|
||||||
|
{
|
||||||
|
switch (arg) {
|
||||||
|
case 1: u->x1 = val; break;
|
||||||
|
case 2: u->x2 = val; break;
|
||||||
|
case 3: u->x3 = val; break;
|
||||||
|
case 4: u->x4 = val; break;
|
||||||
|
case 5: u->x5 = val; break;
|
||||||
|
case 6: u->x6 = val; break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("trying to set SMC arg: 0x%lx, with val: 0x%lx, SMC only has 6 argument registers\n", arg, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: print out which SMC call as a string we can't handle.
|
||||||
|
bool handle_smc(uint64_t vcpu_id, uint32_t hsr)
|
||||||
|
{
|
||||||
|
// @ivanv: An optimisation to be made is to store the TCB registers so we don't
|
||||||
|
// end up reading them multiple times
|
||||||
|
seL4_UserContext regs;
|
||||||
|
int err = seL4_TCB_ReadRegisters(BASE_VM_TCB_CAP + GUEST_ID, false, 0, SEL4_USER_CONTEXT_SIZE, ®s);
|
||||||
|
assert(err == seL4_NoError);
|
||||||
|
|
||||||
|
uint64_t fn_number = smc_get_function_number(®s);
|
||||||
|
smc_call_id_t service = smc_get_call(regs.x0);
|
||||||
|
|
||||||
|
switch (service) {
|
||||||
|
case SMC_CALL_STD_SERVICE:
|
||||||
|
if (fn_number < PSCI_MAX) {
|
||||||
|
return handle_psci(vcpu_id, ®s, fn_number, hsr);
|
||||||
|
}
|
||||||
|
LOG_VMM_ERR("Unhandled SMC: standard service call %lu\n", fn_number);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unhandled SMC: unknown value service: 0x%lx, function number: 0x%lx\n", service, fn_number);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
|
||||||
|
// SMC vCPU fault handler
|
||||||
|
bool handle_smc(uint64_t vcpu_id, uint32_t hsr);
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
void smc_set_return_value(seL4_UserContext *u, uint64_t val);
|
||||||
|
|
||||||
|
/* Gets the value of x1-x6 */
|
||||||
|
uint64_t smc_get_arg(seL4_UserContext *u, uint64_t arg);
|
||||||
@ -0,0 +1,914 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// \author (c) Marco Paland (info@paland.com)
|
||||||
|
// 2014-2019, PALANDesign Hannover, Germany
|
||||||
|
//
|
||||||
|
// \license The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
|
||||||
|
// embedded systems with a very limited resources. These routines are thread
|
||||||
|
// safe and reentrant!
|
||||||
|
// Use this instead of the bloated standard/newlib printf cause these use
|
||||||
|
// malloc for printf (and may not be thread safe).
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
|
||||||
|
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
|
||||||
|
// printf_config.h header file
|
||||||
|
// default: undefined
|
||||||
|
#ifdef PRINTF_INCLUDE_CONFIG_H
|
||||||
|
#include "printf_config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
|
||||||
|
// numeric number including padded zeros (dynamically created on stack)
|
||||||
|
// default: 32 byte
|
||||||
|
#ifndef PRINTF_NTOA_BUFFER_SIZE
|
||||||
|
#define PRINTF_NTOA_BUFFER_SIZE 32U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
|
||||||
|
// float number including padded zeros (dynamically created on stack)
|
||||||
|
// default: 32 byte
|
||||||
|
#ifndef PRINTF_FTOA_BUFFER_SIZE
|
||||||
|
#define PRINTF_FTOA_BUFFER_SIZE 32U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the floating point type (%f)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
|
||||||
|
#define PRINTF_SUPPORT_FLOAT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for exponential floating point notation (%e/%g)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
|
||||||
|
#define PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// define the default floating point precision
|
||||||
|
// default: 6 digits
|
||||||
|
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
|
||||||
|
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// define the largest float suitable to print with %f
|
||||||
|
// default: 1e9
|
||||||
|
#ifndef PRINTF_MAX_FLOAT
|
||||||
|
#define PRINTF_MAX_FLOAT 1e9
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the long long types (%llu or %p)
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
|
||||||
|
#define PRINTF_SUPPORT_LONG_LONG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// support for the ptrdiff_t type (%t)
|
||||||
|
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
|
||||||
|
// default: activated
|
||||||
|
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
|
||||||
|
#define PRINTF_SUPPORT_PTRDIFF_T
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// internal flag definitions
|
||||||
|
#define FLAGS_ZEROPAD (1U << 0U)
|
||||||
|
#define FLAGS_LEFT (1U << 1U)
|
||||||
|
#define FLAGS_PLUS (1U << 2U)
|
||||||
|
#define FLAGS_SPACE (1U << 3U)
|
||||||
|
#define FLAGS_HASH (1U << 4U)
|
||||||
|
#define FLAGS_UPPERCASE (1U << 5U)
|
||||||
|
#define FLAGS_CHAR (1U << 6U)
|
||||||
|
#define FLAGS_SHORT (1U << 7U)
|
||||||
|
#define FLAGS_LONG (1U << 8U)
|
||||||
|
#define FLAGS_LONG_LONG (1U << 9U)
|
||||||
|
#define FLAGS_PRECISION (1U << 10U)
|
||||||
|
#define FLAGS_ADAPT_EXP (1U << 11U)
|
||||||
|
|
||||||
|
|
||||||
|
// import float.h for DBL_MAX
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
#include <float.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// output function type
|
||||||
|
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
|
||||||
|
|
||||||
|
|
||||||
|
// wrapper (used as buffer) for output function type
|
||||||
|
typedef struct {
|
||||||
|
void (*fct)(char character, void* arg);
|
||||||
|
void* arg;
|
||||||
|
} out_fct_wrap_type;
|
||||||
|
|
||||||
|
|
||||||
|
// internal buffer output
|
||||||
|
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
if (idx < maxlen) {
|
||||||
|
((char*)buffer)[idx] = character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal null output
|
||||||
|
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)character; (void)buffer; (void)idx; (void)maxlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal _putchar wrapper
|
||||||
|
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)buffer; (void)idx; (void)maxlen;
|
||||||
|
if (character) {
|
||||||
|
_putchar(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal output function wrapper
|
||||||
|
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
|
||||||
|
{
|
||||||
|
(void)idx; (void)maxlen;
|
||||||
|
if (character) {
|
||||||
|
// buffer is the output fct pointer
|
||||||
|
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal secure strlen
|
||||||
|
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
|
||||||
|
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
|
||||||
|
{
|
||||||
|
const char* s;
|
||||||
|
for (s = str; *s && maxsize--; ++s);
|
||||||
|
return (unsigned int)(s - str);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal test if char is a digit (0-9)
|
||||||
|
// \return true if char is a digit
|
||||||
|
static inline bool _is_digit(char ch)
|
||||||
|
{
|
||||||
|
return (ch >= '0') && (ch <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal ASCII string to unsigned int conversion
|
||||||
|
static unsigned int _atoi(const char** str)
|
||||||
|
{
|
||||||
|
unsigned int i = 0U;
|
||||||
|
while (_is_digit(**str)) {
|
||||||
|
i = i * 10U + (unsigned int)(*((*str)++) - '0');
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// output the specified string in reverse, taking care of any zero-padding
|
||||||
|
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
const size_t start_idx = idx;
|
||||||
|
|
||||||
|
// pad spaces up to given width
|
||||||
|
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
|
||||||
|
for (size_t i = len; i < width; i++) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse string
|
||||||
|
while (len) {
|
||||||
|
out(buf[--len], buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append pad spaces up to given width
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (idx - start_idx < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa format
|
||||||
|
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
// pad leading zeros
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||||
|
width--;
|
||||||
|
}
|
||||||
|
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle hash
|
||||||
|
if (flags & FLAGS_HASH) {
|
||||||
|
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
|
||||||
|
len--;
|
||||||
|
if (len && (base == 16U)) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'x';
|
||||||
|
}
|
||||||
|
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'X';
|
||||||
|
}
|
||||||
|
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = 'b';
|
||||||
|
}
|
||||||
|
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||||
|
if (negative) {
|
||||||
|
buf[len++] = '-';
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_PLUS) {
|
||||||
|
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_SPACE) {
|
||||||
|
buf[len++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa for 'long' type
|
||||||
|
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
|
||||||
|
// no hash for 0 values
|
||||||
|
if (!value) {
|
||||||
|
flags &= ~FLAGS_HASH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write if precision != 0 and value is != 0
|
||||||
|
if (!(flags & FLAGS_PRECISION) || value) {
|
||||||
|
do {
|
||||||
|
const char digit = (char)(value % base);
|
||||||
|
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||||
|
value /= base;
|
||||||
|
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal itoa for 'long long' type
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
|
||||||
|
// no hash for 0 values
|
||||||
|
if (!value) {
|
||||||
|
flags &= ~FLAGS_HASH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write if precision != 0 and value is != 0
|
||||||
|
if (!(flags & FLAGS_PRECISION) || value) {
|
||||||
|
do {
|
||||||
|
const char digit = (char)(value % base);
|
||||||
|
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||||
|
value /= base;
|
||||||
|
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||||
|
}
|
||||||
|
#endif // PRINTF_SUPPORT_LONG_LONG
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
|
||||||
|
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// internal ftoa for fixed decimal floating point
|
||||||
|
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
char buf[PRINTF_FTOA_BUFFER_SIZE];
|
||||||
|
size_t len = 0U;
|
||||||
|
double diff = 0.0;
|
||||||
|
|
||||||
|
// powers of 10
|
||||||
|
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||||
|
|
||||||
|
// test for special values
|
||||||
|
if (value != value)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
|
||||||
|
if (value < -DBL_MAX)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
|
||||||
|
if (value > DBL_MAX)
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
|
||||||
|
|
||||||
|
// test for very large values
|
||||||
|
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
|
||||||
|
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||||
|
#else
|
||||||
|
return 0U;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for negative
|
||||||
|
bool negative = false;
|
||||||
|
if (value < 0) {
|
||||||
|
negative = true;
|
||||||
|
value = 0 - value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default precision, if not set explicitly
|
||||||
|
if (!(flags & FLAGS_PRECISION)) {
|
||||||
|
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||||
|
}
|
||||||
|
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
|
||||||
|
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
prec--;
|
||||||
|
}
|
||||||
|
|
||||||
|
int whole = (int)value;
|
||||||
|
double tmp = (value - whole) * pow10[prec];
|
||||||
|
unsigned long frac = (unsigned long)tmp;
|
||||||
|
diff = tmp - frac;
|
||||||
|
|
||||||
|
if (diff > 0.5) {
|
||||||
|
++frac;
|
||||||
|
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
|
||||||
|
if (frac >= pow10[prec]) {
|
||||||
|
frac = 0;
|
||||||
|
++whole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (diff < 0.5) {
|
||||||
|
}
|
||||||
|
else if ((frac == 0U) || (frac & 1U)) {
|
||||||
|
// if halfway, round up if odd OR if last digit is 0
|
||||||
|
++frac;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prec == 0U) {
|
||||||
|
diff = value - (double)whole;
|
||||||
|
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
|
||||||
|
// exactly 0.5 and ODD, then round up
|
||||||
|
// 1.5 -> 2, but 2.5 -> 2
|
||||||
|
++whole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unsigned int count = prec;
|
||||||
|
// now do fractional part, as an unsigned number
|
||||||
|
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
--count;
|
||||||
|
buf[len++] = (char)(48U + (frac % 10U));
|
||||||
|
if (!(frac /= 10U)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add extra 0s
|
||||||
|
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
// add decimal
|
||||||
|
buf[len++] = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do whole part, number is reversed
|
||||||
|
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
buf[len++] = (char)(48 + (whole % 10));
|
||||||
|
if (!(whole /= 10)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pad leading zeros
|
||||||
|
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
|
||||||
|
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||||
|
width--;
|
||||||
|
}
|
||||||
|
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
|
||||||
|
buf[len++] = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||||
|
if (negative) {
|
||||||
|
buf[len++] = '-';
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_PLUS) {
|
||||||
|
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_SPACE) {
|
||||||
|
buf[len++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
|
||||||
|
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||||
|
{
|
||||||
|
// check for NaN and special values
|
||||||
|
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
|
||||||
|
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the sign
|
||||||
|
const bool negative = value < 0;
|
||||||
|
if (negative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default precision
|
||||||
|
if (!(flags & FLAGS_PRECISION)) {
|
||||||
|
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the decimal exponent
|
||||||
|
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
|
||||||
|
union {
|
||||||
|
uint64_t U;
|
||||||
|
double F;
|
||||||
|
} conv;
|
||||||
|
|
||||||
|
conv.F = value;
|
||||||
|
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
|
||||||
|
conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
|
||||||
|
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
|
||||||
|
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
|
||||||
|
// now we want to compute 10^expval but we want to be sure it won't overflow
|
||||||
|
exp2 = (int)(expval * 3.321928094887362 + 0.5);
|
||||||
|
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
|
||||||
|
const double z2 = z * z;
|
||||||
|
conv.U = (uint64_t)(exp2 + 1023) << 52U;
|
||||||
|
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
|
||||||
|
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
|
||||||
|
// correct for rounding errors
|
||||||
|
if (value < conv.F) {
|
||||||
|
expval--;
|
||||||
|
conv.F /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
|
||||||
|
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
|
||||||
|
|
||||||
|
// in "%g" mode, "prec" is the number of *significant figures* not decimals
|
||||||
|
if (flags & FLAGS_ADAPT_EXP) {
|
||||||
|
// do we want to fall-back to "%f" mode?
|
||||||
|
if ((value >= 1e-4) && (value < 1e6)) {
|
||||||
|
if ((int)prec > expval) {
|
||||||
|
prec = (unsigned)((int)prec - expval - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prec = 0;
|
||||||
|
}
|
||||||
|
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
|
||||||
|
// no characters in exponent
|
||||||
|
minwidth = 0U;
|
||||||
|
expval = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we use one sigfig for the whole part
|
||||||
|
if ((prec > 0) && (flags & FLAGS_PRECISION)) {
|
||||||
|
--prec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will everything fit?
|
||||||
|
unsigned int fwidth = width;
|
||||||
|
if (width > minwidth) {
|
||||||
|
// we didn't fall-back so subtract the characters required for the exponent
|
||||||
|
fwidth -= minwidth;
|
||||||
|
} else {
|
||||||
|
// not enough characters, so go back to default sizing
|
||||||
|
fwidth = 0U;
|
||||||
|
}
|
||||||
|
if ((flags & FLAGS_LEFT) && minwidth) {
|
||||||
|
// if we're padding on the right, DON'T pad the floating part
|
||||||
|
fwidth = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rescale the float value
|
||||||
|
if (expval) {
|
||||||
|
value /= conv.F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the floating part
|
||||||
|
const size_t start_idx = idx;
|
||||||
|
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
|
||||||
|
|
||||||
|
// output the exponent part
|
||||||
|
if (minwidth) {
|
||||||
|
// output the exponential symbol
|
||||||
|
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
|
||||||
|
// output the exponent value
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
|
||||||
|
// might need to right-pad spaces
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif // PRINTF_SUPPORT_FLOAT
|
||||||
|
|
||||||
|
|
||||||
|
// internal vsnprintf
|
||||||
|
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
|
||||||
|
{
|
||||||
|
unsigned int flags, width, precision, n;
|
||||||
|
size_t idx = 0U;
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
// use null output function
|
||||||
|
out = _out_null;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*format)
|
||||||
|
{
|
||||||
|
// format specifier? %[flags][width][.precision][length]
|
||||||
|
if (*format != '%') {
|
||||||
|
// no
|
||||||
|
out(*format, buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// yes, evaluate it
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate flags
|
||||||
|
flags = 0U;
|
||||||
|
do {
|
||||||
|
switch (*format) {
|
||||||
|
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
|
||||||
|
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
|
||||||
|
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
|
||||||
|
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
|
||||||
|
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
|
||||||
|
default : n = 0U; break;
|
||||||
|
}
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
// evaluate width field
|
||||||
|
width = 0U;
|
||||||
|
if (_is_digit(*format)) {
|
||||||
|
width = _atoi(&format);
|
||||||
|
}
|
||||||
|
else if (*format == '*') {
|
||||||
|
const int w = va_arg(va, int);
|
||||||
|
if (w < 0) {
|
||||||
|
flags |= FLAGS_LEFT; // reverse padding
|
||||||
|
width = (unsigned int)-w;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width = (unsigned int)w;
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate precision field
|
||||||
|
precision = 0U;
|
||||||
|
if (*format == '.') {
|
||||||
|
flags |= FLAGS_PRECISION;
|
||||||
|
format++;
|
||||||
|
if (_is_digit(*format)) {
|
||||||
|
precision = _atoi(&format);
|
||||||
|
}
|
||||||
|
else if (*format == '*') {
|
||||||
|
const int prec = (int)va_arg(va, int);
|
||||||
|
precision = prec > 0 ? (unsigned int)prec : 0U;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate length field
|
||||||
|
switch (*format) {
|
||||||
|
case 'l' :
|
||||||
|
flags |= FLAGS_LONG;
|
||||||
|
format++;
|
||||||
|
if (*format == 'l') {
|
||||||
|
flags |= FLAGS_LONG_LONG;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h' :
|
||||||
|
flags |= FLAGS_SHORT;
|
||||||
|
format++;
|
||||||
|
if (*format == 'h') {
|
||||||
|
flags |= FLAGS_CHAR;
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
|
||||||
|
case 't' :
|
||||||
|
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case 'j' :
|
||||||
|
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
case 'z' :
|
||||||
|
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate specifier
|
||||||
|
switch (*format) {
|
||||||
|
case 'd' :
|
||||||
|
case 'i' :
|
||||||
|
case 'u' :
|
||||||
|
case 'x' :
|
||||||
|
case 'X' :
|
||||||
|
case 'o' :
|
||||||
|
case 'b' : {
|
||||||
|
// set the base
|
||||||
|
unsigned int base;
|
||||||
|
if (*format == 'x' || *format == 'X') {
|
||||||
|
base = 16U;
|
||||||
|
}
|
||||||
|
else if (*format == 'o') {
|
||||||
|
base = 8U;
|
||||||
|
}
|
||||||
|
else if (*format == 'b') {
|
||||||
|
base = 2U;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
base = 10U;
|
||||||
|
flags &= ~FLAGS_HASH; // no hash for dec format
|
||||||
|
}
|
||||||
|
// uppercase
|
||||||
|
if (*format == 'X') {
|
||||||
|
flags |= FLAGS_UPPERCASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no plus or space flag for u, x, X, o, b
|
||||||
|
if ((*format != 'i') && (*format != 'd')) {
|
||||||
|
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore '0' flag when precision is given
|
||||||
|
if (flags & FLAGS_PRECISION) {
|
||||||
|
flags &= ~FLAGS_ZEROPAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the integer
|
||||||
|
if ((*format == 'i') || (*format == 'd')) {
|
||||||
|
// signed
|
||||||
|
if (flags & FLAGS_LONG_LONG) {
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
const long long value = va_arg(va, long long);
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_LONG) {
|
||||||
|
const long value = va_arg(va, long);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// unsigned
|
||||||
|
if (flags & FLAGS_LONG_LONG) {
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (flags & FLAGS_LONG) {
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||||
|
case 'f' :
|
||||||
|
case 'F' :
|
||||||
|
if (*format == 'F') flags |= FLAGS_UPPERCASE;
|
||||||
|
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
case 'g':
|
||||||
|
case 'G':
|
||||||
|
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
|
||||||
|
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
|
||||||
|
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||||
|
#endif // PRINTF_SUPPORT_FLOAT
|
||||||
|
case 'c' : {
|
||||||
|
unsigned int l = 1U;
|
||||||
|
// pre padding
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// char output
|
||||||
|
out((char)va_arg(va, int), buffer, idx++, maxlen);
|
||||||
|
// post padding
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's' : {
|
||||||
|
const char* p = va_arg(va, char*);
|
||||||
|
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
|
||||||
|
// pre padding
|
||||||
|
if (flags & FLAGS_PRECISION) {
|
||||||
|
l = (l < precision ? l : precision);
|
||||||
|
}
|
||||||
|
if (!(flags & FLAGS_LEFT)) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// string output
|
||||||
|
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
|
||||||
|
out(*(p++), buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
// post padding
|
||||||
|
if (flags & FLAGS_LEFT) {
|
||||||
|
while (l++ < width) {
|
||||||
|
out(' ', buffer, idx++, maxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'p' : {
|
||||||
|
width = sizeof(void*) * 2U;
|
||||||
|
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
|
||||||
|
if (is_ll) {
|
||||||
|
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#endif
|
||||||
|
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
|
||||||
|
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '%' :
|
||||||
|
out('%', buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default :
|
||||||
|
out(*format, buffer, idx++, maxlen);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// termination
|
||||||
|
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||||
|
|
||||||
|
// return written chars without terminating \0
|
||||||
|
return (int)idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
int printf_(const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
char buffer[1];
|
||||||
|
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int sprintf_(char* buffer, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int snprintf_(char* buffer, size_t count, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vprintf_(const char* format, va_list va)
|
||||||
|
{
|
||||||
|
char buffer[1];
|
||||||
|
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
|
||||||
|
{
|
||||||
|
return _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, format);
|
||||||
|
const out_fct_wrap_type out_fct_wrap = { out, arg };
|
||||||
|
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// \author (c) Marco Paland (info@paland.com)
|
||||||
|
// 2014-2019, PALANDesign Hannover, Germany
|
||||||
|
//
|
||||||
|
// \license The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
|
||||||
|
// embedded systems with a very limited resources.
|
||||||
|
// Use this instead of bloated standard/newlib printf.
|
||||||
|
// These routines are thread safe and reentrant.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef _PRINTF_H_
|
||||||
|
#define _PRINTF_H_
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output a character to a custom device like UART, used by the printf() function
|
||||||
|
* This function is declared here only. You have to write your custom implementation somewhere
|
||||||
|
* \param character Character to output
|
||||||
|
*/
|
||||||
|
void _putchar(char character);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny printf implementation
|
||||||
|
* You have to implement _putchar if you use printf()
|
||||||
|
* To avoid conflicts with the regular printf() API it is overridden by macro defines
|
||||||
|
* and internal underscore-appended functions like printf_() are used
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are written into the array, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define printf printf_
|
||||||
|
int printf_(const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny sprintf implementation
|
||||||
|
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
|
||||||
|
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define sprintf sprintf_
|
||||||
|
int sprintf_(char* buffer, const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny snprintf/vsnprintf implementation
|
||||||
|
* \param buffer A pointer to the buffer where to store the formatted string
|
||||||
|
* \param count The maximum number of characters to store in the buffer, including a terminating null character
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \param va A value identifying a variable arguments list
|
||||||
|
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
|
||||||
|
* null character. A value equal or larger than count indicates truncation. Only when the returned value
|
||||||
|
* is non-negative and less than count, the string has been completely written.
|
||||||
|
*/
|
||||||
|
#define snprintf snprintf_
|
||||||
|
#define vsnprintf vsnprintf_
|
||||||
|
int snprintf_(char* buffer, size_t count, const char* format, ...);
|
||||||
|
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny vprintf implementation
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \param va A value identifying a variable arguments list
|
||||||
|
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
#define vprintf vprintf_
|
||||||
|
int vprintf_(const char* format, va_list va);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* printf with output function
|
||||||
|
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
|
||||||
|
* \param out An output function which takes one character and an argument pointer
|
||||||
|
* \param arg An argument pointer for user data passed to output function
|
||||||
|
* \param format A string that specifies the format of the output
|
||||||
|
* \return The number of characters that are sent to the output function, not counting the terminating null character
|
||||||
|
*/
|
||||||
|
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _PRINTF_H_
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
/* This is required to use the printf library we brought in, it is
|
||||||
|
simply for convenience since there's a lot of logging/debug printing
|
||||||
|
in the VMM. */
|
||||||
|
void _putchar(char character)
|
||||||
|
{
|
||||||
|
microkit_dbg_putc(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__ ((__noreturn__))
|
||||||
|
void __assert_func(const char *file, int line, const char *function, const char *str)
|
||||||
|
{
|
||||||
|
microkit_dbg_puts("assert failed: ");
|
||||||
|
microkit_dbg_puts(str);
|
||||||
|
microkit_dbg_puts(" ");
|
||||||
|
microkit_dbg_puts(file);
|
||||||
|
microkit_dbg_puts(" ");
|
||||||
|
microkit_dbg_puts(function);
|
||||||
|
microkit_dbg_puts("\n");
|
||||||
|
while (1) {}
|
||||||
|
}
|
||||||
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021, Breakaway Consulting Pty. Ltd.
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
// @ivanv: these are here for convience, should not be here though
|
||||||
|
#define GUEST_ID 0
|
||||||
|
#define GUEST_VCPU_ID 0
|
||||||
|
#define GUEST_NUM_VCPUS 1
|
||||||
|
// Note that this is AArch64 specific
|
||||||
|
#if defined(CONFIG_ARCH_AARCH64)
|
||||||
|
#define SEL4_USER_CONTEXT_SIZE 0x24
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PAGE_SIZE_4K 4096
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
||||||
|
|
||||||
|
#define CTZ(x) __builtin_ctz(x)
|
||||||
|
|
||||||
|
#if __STDC_VERSION__ >= 201112L && !defined(__cplusplus)
|
||||||
|
#define static_assert _Static_assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// __attribute__ ((__noreturn__))
|
||||||
|
// void __assert_func(const char *file, int line, const char *function, const char *str);
|
||||||
|
|
||||||
|
void _putchar(char character);
|
||||||
|
|
||||||
|
#define LOG_VMM(...) do{ printf("%s|INFO: ", microkit_name); printf(__VA_ARGS__); }while(0)
|
||||||
|
#define LOG_VMM_ERR(...) do{ printf("%s|ERROR: ", microkit_name); printf(__VA_ARGS__); }while(0)
|
||||||
|
|
||||||
|
static char
|
||||||
|
decchar(unsigned int v) {
|
||||||
|
return '0' + v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put8(uint8_t x)
|
||||||
|
{
|
||||||
|
char tmp[4];
|
||||||
|
unsigned i = 3;
|
||||||
|
tmp[3] = 0;
|
||||||
|
do {
|
||||||
|
uint8_t c = x % 10;
|
||||||
|
tmp[--i] = decchar(c);
|
||||||
|
x /= 10;
|
||||||
|
} while (x);
|
||||||
|
microkit_dbg_puts(&tmp[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: sort this out...
|
||||||
|
static void
|
||||||
|
reply_to_fault()
|
||||||
|
{
|
||||||
|
microkit_msginfo msg = microkit_msginfo_new(0, 0);
|
||||||
|
seL4_Send(4, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t get_vmm_id(char *microkit_name)
|
||||||
|
{
|
||||||
|
// @ivanv: Absolute hack
|
||||||
|
return microkit_name[4] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_tcb_regs(seL4_UserContext *ctx) {
|
||||||
|
#if defined(ARCH_aarch64)
|
||||||
|
// I don't know if it's the best idea, but here we'll just dump the
|
||||||
|
// registers in the same order they are defined in seL4_UserContext
|
||||||
|
printf("VMM|INFO: TCB registers:\n");
|
||||||
|
// Frame registers
|
||||||
|
printf(" pc: 0x%016lx\n", ctx->pc);
|
||||||
|
printf(" sp: 0x%016lx\n", ctx->sp);
|
||||||
|
printf(" spsr: 0x%016lx\n", ctx->spsr);
|
||||||
|
printf(" x0: 0x%016lx\n", ctx->x0);
|
||||||
|
printf(" x1: 0x%016lx\n", ctx->x1);
|
||||||
|
printf(" x2: 0x%016lx\n", ctx->x2);
|
||||||
|
printf(" x3: 0x%016lx\n", ctx->x3);
|
||||||
|
printf(" x4: 0x%016lx\n", ctx->x4);
|
||||||
|
printf(" x5: 0x%016lx\n", ctx->x5);
|
||||||
|
printf(" x6: 0x%016lx\n", ctx->x6);
|
||||||
|
printf(" x7: 0x%016lx\n", ctx->x7);
|
||||||
|
printf(" x8: 0x%016lx\n", ctx->x8);
|
||||||
|
printf(" x16: 0x%016lx\n", ctx->x16);
|
||||||
|
printf(" x17: 0x%016lx\n", ctx->x17);
|
||||||
|
printf(" x18: 0x%016lx\n", ctx->x18);
|
||||||
|
printf(" x29: 0x%016lx\n", ctx->x29);
|
||||||
|
printf(" x30: 0x%016lx\n", ctx->x30);
|
||||||
|
// Other integer registers
|
||||||
|
printf(" x9: 0x%016lx\n", ctx->x9);
|
||||||
|
printf(" x10: 0x%016lx\n", ctx->x10);
|
||||||
|
printf(" x11: 0x%016lx\n", ctx->x11);
|
||||||
|
printf(" x12: 0x%016lx\n", ctx->x12);
|
||||||
|
printf(" x13: 0x%016lx\n", ctx->x13);
|
||||||
|
printf(" x14: 0x%016lx\n", ctx->x14);
|
||||||
|
printf(" x15: 0x%016lx\n", ctx->x15);
|
||||||
|
printf(" x19: 0x%016lx\n", ctx->x19);
|
||||||
|
printf(" x20: 0x%016lx\n", ctx->x20);
|
||||||
|
printf(" x21: 0x%016lx\n", ctx->x21);
|
||||||
|
printf(" x22: 0x%016lx\n", ctx->x22);
|
||||||
|
printf(" x23: 0x%016lx\n", ctx->x23);
|
||||||
|
printf(" x24: 0x%016lx\n", ctx->x24);
|
||||||
|
printf(" x25: 0x%016lx\n", ctx->x25);
|
||||||
|
printf(" x26: 0x%016lx\n", ctx->x26);
|
||||||
|
printf(" x27: 0x%016lx\n", ctx->x27);
|
||||||
|
printf(" x28: 0x%016lx\n", ctx->x28);
|
||||||
|
// TODO(ivanv): print out thread ID registers?
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: this should have the same foramtting as the TCB registers
|
||||||
|
static void
|
||||||
|
print_vcpu_regs(uint64_t vcpu_id) {
|
||||||
|
printf("VMM|INFO: VCPU registers: \n");
|
||||||
|
/* VM control registers EL1 */
|
||||||
|
printf(" SCTLR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_SCTLR));
|
||||||
|
printf(" TTBR0: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_TTBR0));
|
||||||
|
printf(" TTBR1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_TTBR1));
|
||||||
|
printf(" TCR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_TCR));
|
||||||
|
printf(" MAIR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_MAIR));
|
||||||
|
printf(" AMAIR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_AMAIR));
|
||||||
|
printf(" CIDR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CIDR));
|
||||||
|
/* other system registers EL1 */
|
||||||
|
printf(" ACTLR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_ACTLR));
|
||||||
|
printf(" CPACR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CPACR));
|
||||||
|
/* exception handling registers EL1 */
|
||||||
|
printf(" AFSR0: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_AFSR0));
|
||||||
|
printf(" AFSR1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_AFSR1));
|
||||||
|
printf(" ESR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_ESR));
|
||||||
|
printf(" FAR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_FAR));
|
||||||
|
printf(" ISR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_ISR));
|
||||||
|
printf(" VBAR: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_VBAR));
|
||||||
|
/* thread pointer/ID registers EL0/EL1 */
|
||||||
|
printf(" TPIDR_EL1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_TPIDR_EL1));
|
||||||
|
#if CONFIG_MAX_NUM_NODES > 1
|
||||||
|
/* Virtualisation Multiprocessor ID Register */
|
||||||
|
printf(" VMPIDR_EL2: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_VMPIDR_EL2));
|
||||||
|
#endif
|
||||||
|
/* general registers x0 to x30 have been saved by traps.S */
|
||||||
|
printf(" SP_EL1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_SP_EL1));
|
||||||
|
printf(" ELR_EL1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_ELR_EL1));
|
||||||
|
printf(" SPSR_EL1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_SPSR_EL1)); // 32-bit // @ivanv what
|
||||||
|
/* generic timer registers, to be completed */
|
||||||
|
printf(" CNTV_CTL: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CNTV_CTL));
|
||||||
|
printf(" CNTV_CVAL: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CNTV_CVAL));
|
||||||
|
printf(" CNTVOFF: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CNTVOFF));
|
||||||
|
printf(" CNTKCTL_EL1: 0x%lx\n", microkit_vcpu_arm_read_reg(vcpu_id, seL4_VCPUReg_CNTKCTL_EL1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *memcpy(void *restrict dest, const void *restrict src, size_t n)
|
||||||
|
{
|
||||||
|
unsigned char *d = dest;
|
||||||
|
const unsigned char *s = src;
|
||||||
|
for (; n; n--) *d++ = *s++;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *memset(void *dest, int c, size_t n)
|
||||||
|
{
|
||||||
|
unsigned char *s = dest;
|
||||||
|
for (; n; n--, s++) *s = c;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assert_fail(
|
||||||
|
const char *assertion,
|
||||||
|
const char *file,
|
||||||
|
unsigned int line,
|
||||||
|
const char *function)
|
||||||
|
{
|
||||||
|
printf("Failed assertion '%s' at %s:%u in function %s\n", assertion, file, line, function);
|
||||||
|
while (1) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert(expr) \
|
||||||
|
do { \
|
||||||
|
if (!(expr)) { \
|
||||||
|
assert_fail(#expr, __FILE__, __LINE__, __FUNCTION__); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
@ -0,0 +1,624 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../util/util.h"
|
||||||
|
#include "../fault.h"
|
||||||
|
|
||||||
|
/* GIC Distributor register access utilities */
|
||||||
|
#define GIC_DIST_REGN(offset, reg) ((offset-reg)/sizeof(uint32_t))
|
||||||
|
#define RANGE32(a, b) a ... b + (sizeof(uint32_t)-1)
|
||||||
|
|
||||||
|
#define IRQ_IDX(irq) ((irq) / 32)
|
||||||
|
#define IRQ_BIT(irq) (1U << ((irq) % 32))
|
||||||
|
|
||||||
|
static inline void set_sgi_ppi_pending(struct gic_dist_map *gic_dist, int irq, bool set_pending, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (set_pending) {
|
||||||
|
gic_dist->pending_set0[vcpu_id] |= IRQ_BIT(irq);
|
||||||
|
gic_dist->pending_clr0[vcpu_id] |= IRQ_BIT(irq);
|
||||||
|
} else {
|
||||||
|
gic_dist->pending_set0[vcpu_id] &= ~IRQ_BIT(irq);
|
||||||
|
gic_dist->pending_clr0[vcpu_id] &= ~IRQ_BIT(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_spi_pending(struct gic_dist_map *gic_dist, int irq, bool set_pending)
|
||||||
|
{
|
||||||
|
if (set_pending) {
|
||||||
|
gic_dist->pending_set[IRQ_IDX(irq)] |= IRQ_BIT(irq);
|
||||||
|
gic_dist->pending_clr[IRQ_IDX(irq)] |= IRQ_BIT(irq);
|
||||||
|
} else {
|
||||||
|
gic_dist->pending_set[IRQ_IDX(irq)] &= ~IRQ_BIT(irq);
|
||||||
|
gic_dist->pending_clr[IRQ_IDX(irq)] &= ~IRQ_BIT(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_pending(struct gic_dist_map *gic_dist, int irq, bool set_pending, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (irq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
set_sgi_ppi_pending(gic_dist, irq, set_pending, vcpu_id);
|
||||||
|
} else {
|
||||||
|
set_spi_pending(gic_dist, irq, set_pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_sgi_ppi_pending(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->pending_set0[vcpu_id] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_spi_pending(struct gic_dist_map *gic_dist, int irq)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->pending_set[IRQ_IDX(irq)] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_pending(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (irq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
return is_sgi_ppi_pending(gic_dist, irq, vcpu_id);
|
||||||
|
} else {
|
||||||
|
return is_spi_pending(gic_dist, irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_sgi_ppi_enable(struct gic_dist_map *gic_dist, int irq, bool set_enable, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (set_enable) {
|
||||||
|
gic_dist->enable_set0[vcpu_id] |= IRQ_BIT(irq);
|
||||||
|
gic_dist->enable_clr0[vcpu_id] |= IRQ_BIT(irq);
|
||||||
|
} else {
|
||||||
|
gic_dist->enable_set0[vcpu_id] &= ~IRQ_BIT(irq);
|
||||||
|
gic_dist->enable_clr0[vcpu_id] &= ~IRQ_BIT(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_spi_enable(struct gic_dist_map *gic_dist, int irq, bool set_enable)
|
||||||
|
{
|
||||||
|
if (set_enable) {
|
||||||
|
gic_dist->enable_set[IRQ_IDX(irq)] |= IRQ_BIT(irq);
|
||||||
|
gic_dist->enable_clr[IRQ_IDX(irq)] |= IRQ_BIT(irq);
|
||||||
|
} else {
|
||||||
|
gic_dist->enable_set[IRQ_IDX(irq)] &= ~IRQ_BIT(irq);
|
||||||
|
gic_dist->enable_clr[IRQ_IDX(irq)] &= ~IRQ_BIT(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_enable(struct gic_dist_map *gic_dist, int irq, bool set_enable, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (irq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
set_sgi_ppi_enable(gic_dist, irq, set_enable, vcpu_id);
|
||||||
|
} else {
|
||||||
|
set_spi_enable(gic_dist, irq, set_enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_sgi_ppi_enabled(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->enable_set0[vcpu_id] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_spi_enabled(struct gic_dist_map *gic_dist, int irq)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->enable_set[IRQ_IDX(irq)] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_enabled(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (irq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
return is_sgi_ppi_enabled(gic_dist, irq, vcpu_id);
|
||||||
|
} else {
|
||||||
|
return is_spi_enabled(gic_dist, irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_sgi_ppi_active(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->active0[vcpu_id] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_spi_active(struct gic_dist_map *gic_dist, int irq)
|
||||||
|
{
|
||||||
|
return !!(gic_dist->active[IRQ_IDX(irq)] & IRQ_BIT(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_active(struct gic_dist_map *gic_dist, int irq, int vcpu_id)
|
||||||
|
{
|
||||||
|
if (irq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
return is_sgi_ppi_active(gic_dist, irq, vcpu_id);
|
||||||
|
} else {
|
||||||
|
return is_spi_active(gic_dist, irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vgic_dist_enable_irq(vgic_t *vgic, uint64_t vcpu_id, int irq)
|
||||||
|
{
|
||||||
|
LOG_DIST("Enabling IRQ %d\n", irq);
|
||||||
|
set_enable(vgic_get_dist(vgic->registers), irq, true, vcpu_id);
|
||||||
|
struct virq_handle *virq_data = virq_find_irq_data(vgic, vcpu_id, irq);
|
||||||
|
// assert(virq_data != NULL);
|
||||||
|
// @ivanv: explain
|
||||||
|
if (!virq_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (virq_data->virq != VIRQ_INVALID) {
|
||||||
|
/* STATE b) */
|
||||||
|
if (!is_pending(vgic_get_dist(vgic->registers), virq_data->virq, vcpu_id)) {
|
||||||
|
virq_ack(vcpu_id, virq_data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_DIST("Enabled IRQ %d has no handle\n", irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vgic_dist_disable_irq(vgic_t *vgic, uint64_t vcpu_id, int irq)
|
||||||
|
{
|
||||||
|
/* STATE g)
|
||||||
|
*
|
||||||
|
* It is IMPLEMENTATION DEFINED if a GIC allows disabling SGIs. Our vGIC
|
||||||
|
* implementation does not allows it, such requests are simply ignored.
|
||||||
|
* Since it is not uncommon that a guest OS tries disabling SGIs, e.g. as
|
||||||
|
* part of the platform initialization, no dedicated messages are logged
|
||||||
|
* here to avoid bloating the logs.
|
||||||
|
*/
|
||||||
|
if (irq >= NUM_SGI_VIRQS) {
|
||||||
|
LOG_DIST("Disabling IRQ %d\n", irq);
|
||||||
|
set_enable(vgic_get_dist(vgic->registers), irq, false, vcpu_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vgic_dist_set_pending_irq(vgic_t *vgic, uint64_t vcpu_id, int irq)
|
||||||
|
{
|
||||||
|
// @ivanv: I believe this function causes a fault in the VMM if the IRQ has not
|
||||||
|
// been registered. This is not good.
|
||||||
|
/* STATE c) */
|
||||||
|
|
||||||
|
struct virq_handle *virq_data = virq_find_irq_data(vgic, vcpu_id, irq);
|
||||||
|
struct gic_dist_map *dist = vgic_get_dist(vgic->registers);
|
||||||
|
|
||||||
|
if (virq_data->virq == VIRQ_INVALID || !vgic_dist_is_enabled(dist) || !is_enabled(dist, irq, vcpu_id)) {
|
||||||
|
if (virq_data->virq == VIRQ_INVALID) {
|
||||||
|
LOG_DIST("vIRQ data could not be found\n");
|
||||||
|
}
|
||||||
|
if (!vgic_dist_is_enabled(dist)) {
|
||||||
|
LOG_DIST("vGIC distributor is not enabled\n");
|
||||||
|
}
|
||||||
|
if (!is_enabled(dist, irq, vcpu_id)) {
|
||||||
|
LOG_DIST("vIRQ is not enabled\n");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_pending(dist, virq_data->virq, vcpu_id)) {
|
||||||
|
// Do nothing if it's already pending
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DIST("Pending set: Inject IRQ from pending set (%d)\n", irq);
|
||||||
|
set_pending(dist, virq_data->virq, true, vcpu_id);
|
||||||
|
|
||||||
|
/* Enqueueing an IRQ and dequeueing it right after makes little sense
|
||||||
|
* now, but in the future this is needed to support IRQ priorities.
|
||||||
|
*/
|
||||||
|
bool success = vgic_irq_enqueue(vgic, vcpu_id, virq_data);
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("Failure enqueueing IRQ, increase MAX_IRQ_QUEUE_LEN");
|
||||||
|
assert(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = vgic_find_empty_list_reg(vgic, vcpu_id);
|
||||||
|
if (idx < 0) {
|
||||||
|
/* There were no empty list registers available, but that's not a big
|
||||||
|
* deal -- we have already enqueued this IRQ and eventually the vGIC
|
||||||
|
* maintenance code will load it to a list register from the queue.
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct virq_handle *virq = vgic_irq_dequeue(vgic, vcpu_id);
|
||||||
|
assert(virq->virq != VIRQ_INVALID);
|
||||||
|
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
int group = 0;
|
||||||
|
#elif defined(GIC_V3)
|
||||||
|
int group = 1;
|
||||||
|
#else
|
||||||
|
#error "Unknown GIC version"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// @ivanv: I don't understand why GIC v2 is group 0 and GIC v3 is group 1.
|
||||||
|
return vgic_vcpu_load_list_reg(vgic, vcpu_id, idx, group, virq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vgic_dist_clr_pending_irq(struct gic_dist_map *dist, uint32_t vcpu_id, int irq)
|
||||||
|
{
|
||||||
|
LOG_DIST("Clear pending IRQ %d\n", irq);
|
||||||
|
set_pending(dist, irq, false, vcpu_id);
|
||||||
|
/* TODO: remove from IRQ queue and list registers as well */
|
||||||
|
// @ivanv
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vgic_dist_reg_read(uint64_t vcpu_id, vgic_t *vgic, uint64_t offset, uint64_t fsr, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
struct gic_dist_map *gic_dist = vgic_get_dist(vgic->registers);
|
||||||
|
uint32_t reg = 0;
|
||||||
|
int reg_offset = 0;
|
||||||
|
uintptr_t base_reg;
|
||||||
|
uint32_t *reg_ptr;
|
||||||
|
switch (offset) {
|
||||||
|
case RANGE32(GIC_DIST_CTLR, GIC_DIST_CTLR):
|
||||||
|
reg = gic_dist->ctlr;
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_TYPER, GIC_DIST_TYPER):
|
||||||
|
reg = gic_dist->typer;
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IIDR, GIC_DIST_IIDR):
|
||||||
|
reg = gic_dist->iidr;
|
||||||
|
break;
|
||||||
|
case RANGE32(0x00C, 0x01C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(0x020, 0x03C):
|
||||||
|
/* IMPLEMENTATION DEFINED registers. */
|
||||||
|
break;
|
||||||
|
case RANGE32(0x040, 0x07C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IGROUPR0, GIC_DIST_IGROUPR0):
|
||||||
|
reg = gic_dist->irq_group0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IGROUPR1, GIC_DIST_IGROUPRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_IGROUPR1);
|
||||||
|
reg = gic_dist->irq_group[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISENABLER0, GIC_DIST_ISENABLER0):
|
||||||
|
reg = gic_dist->enable_set0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISENABLER1, GIC_DIST_ISENABLERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ISENABLER1);
|
||||||
|
reg = gic_dist->enable_set[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICENABLER0, GIC_DIST_ICENABLER0):
|
||||||
|
reg = gic_dist->enable_clr0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICENABLER1, GIC_DIST_ICENABLERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICENABLER1);
|
||||||
|
reg = gic_dist->enable_clr[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISPENDR0, GIC_DIST_ISPENDR0):
|
||||||
|
reg = gic_dist->pending_set0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISPENDR1, GIC_DIST_ISPENDRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ISPENDR1);
|
||||||
|
reg = gic_dist->pending_set[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICPENDR0, GIC_DIST_ICPENDR0):
|
||||||
|
reg = gic_dist->pending_clr0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICPENDR1, GIC_DIST_ICPENDRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICPENDR1);
|
||||||
|
reg = gic_dist->pending_clr[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISACTIVER0, GIC_DIST_ISACTIVER0):
|
||||||
|
reg = gic_dist->active0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISACTIVER1, GIC_DIST_ISACTIVERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ISACTIVER1);
|
||||||
|
reg = gic_dist->active[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICACTIVER0, GIC_DIST_ICACTIVER0):
|
||||||
|
reg = gic_dist->active_clr0[vcpu_id];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICACTIVER1, GIC_DIST_ICACTIVERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICACTIVER1);
|
||||||
|
reg = gic_dist->active_clr[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IPRIORITYR0, GIC_DIST_IPRIORITYR7):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_IPRIORITYR0);
|
||||||
|
reg = gic_dist->priority0[vcpu_id][reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IPRIORITYR8, GIC_DIST_IPRIORITYRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_IPRIORITYR8);
|
||||||
|
reg = gic_dist->priority[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(0x7FC, 0x7FC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ITARGETSR0, GIC_DIST_ITARGETSR7):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ITARGETSR0);
|
||||||
|
reg = gic_dist->targets0[vcpu_id][reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ITARGETSR8, GIC_DIST_ITARGETSRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ITARGETSR8);
|
||||||
|
reg = gic_dist->targets[reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(0xBFC, 0xBFC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICFGR0, GIC_DIST_ICFGRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICFGR0);
|
||||||
|
reg = gic_dist->config[reg_offset];
|
||||||
|
break;
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
case RANGE32(0xD00, 0xDE4):
|
||||||
|
/* IMPLEMENTATION DEFINED registers. */
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->spi[0]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - 0xD00));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case RANGE32(0xDE8, 0xEFC):
|
||||||
|
/* Reserved [0xDE8 - 0xE00) */
|
||||||
|
/* GIC_DIST_NSACR [0xE00 - 0xF00) - Not supported */
|
||||||
|
break; case RANGE32(GIC_DIST_SGIR, GIC_DIST_SGIR):
|
||||||
|
reg = gic_dist->sgir;
|
||||||
|
break;
|
||||||
|
case RANGE32(0xF04, 0xF0C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_CPENDSGIR0, GIC_DIST_CPENDSGIRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_CPENDSGIR0);
|
||||||
|
reg = gic_dist->sgi_pending_clr[vcpu_id][reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_SPENDSGIR0, GIC_DIST_SPENDSGIRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_SPENDSGIR0);
|
||||||
|
reg = gic_dist->sgi_pending_set[vcpu_id][reg_offset];
|
||||||
|
break;
|
||||||
|
case RANGE32(0xF30, 0xFBC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
// @ivanv: understand why this is GIC v2 specific and make a command so others can understand as well.
|
||||||
|
case RANGE32(0xFC0, 0xFFB):
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->periph_id[0]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - 0xFC0));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if defined(GIC_V3)
|
||||||
|
// @ivanv: Understand and comment GICv3 specific stuff
|
||||||
|
case RANGE32(0x6100, 0x7F00):
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->irouter[0]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - 0x6100));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RANGE32(0xFFD0, 0xFFFC):
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->pidrn[0]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - 0xFFD0));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown register offset 0x%x", offset);
|
||||||
|
// err = ignore_fault(fault);
|
||||||
|
success = fault_advance_vcpu(regs);
|
||||||
|
assert(success);
|
||||||
|
goto fault_return;
|
||||||
|
}
|
||||||
|
uint32_t mask = fault_get_data_mask(GIC_DIST_PADDR + offset, fsr);
|
||||||
|
// fault_set_data(fault, reg & mask);
|
||||||
|
// @ivanv: interesting, when we don't call fault_Set_data in the CAmkES VMM, everything works fine?...
|
||||||
|
success = fault_advance(regs, GIC_DIST_PADDR + offset, fsr, reg & mask);
|
||||||
|
assert(success);
|
||||||
|
|
||||||
|
fault_return:
|
||||||
|
if (!success) {
|
||||||
|
printf("reg read success is false\n");
|
||||||
|
}
|
||||||
|
// @ivanv: revisit, also make fault_return consistint. it's called something else in vgic_dist_reg_write
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void emulate_reg_write_access(seL4_UserContext *regs, uint64_t addr, uint64_t fsr, uint32_t *reg)
|
||||||
|
{
|
||||||
|
*reg = fault_emulate(regs, *reg, addr, fsr, fault_get_data(regs, fsr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vgic_dist_reg_write(uint64_t vcpu_id, vgic_t *vgic, uint64_t offset, uint64_t fsr, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
struct gic_dist_map *gic_dist = vgic_get_dist(vgic->registers);
|
||||||
|
uint64_t addr = GIC_DIST_PADDR + offset;
|
||||||
|
uint32_t mask = fault_get_data_mask(addr, fsr);
|
||||||
|
uint32_t reg_offset = 0;
|
||||||
|
uint32_t data;
|
||||||
|
switch (offset) {
|
||||||
|
case RANGE32(GIC_DIST_CTLR, GIC_DIST_CTLR):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
if (data == GIC_ENABLED) {
|
||||||
|
vgic_dist_enable(gic_dist);
|
||||||
|
} else if (data == 0) {
|
||||||
|
vgic_dist_disable(gic_dist);
|
||||||
|
} else {
|
||||||
|
LOG_VMM_ERR("Unknown enable register encoding");
|
||||||
|
// @ivanv: goto ignore fault?
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_TYPER, GIC_DIST_TYPER):
|
||||||
|
/* TYPER provides information about the GIC configuration, we do not do
|
||||||
|
* anything here as there should be no reason for the guest to write to
|
||||||
|
* this register.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IIDR, GIC_DIST_IIDR):
|
||||||
|
/* IIDR provides information about the distributor's implementation, we
|
||||||
|
* do not do anything here as there should be no reason for the guest
|
||||||
|
* to write to this register.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
case RANGE32(0x00C, 0x01C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(0x020, 0x03C):
|
||||||
|
/* IMPLEMENTATION DEFINED registers. */
|
||||||
|
break;
|
||||||
|
case RANGE32(0x040, 0x07C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IGROUPR0, GIC_DIST_IGROUPR0):
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->irq_group0[vcpu_id]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IGROUPR1, GIC_DIST_IGROUPRN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_IGROUPR1);
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->irq_group[reg_offset]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISENABLER0, GIC_DIST_ISENABLERN):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
irq += (offset - GIC_DIST_ISENABLER0) * 8;
|
||||||
|
vgic_dist_enable_irq(vgic, vcpu_id, irq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICENABLER0, GIC_DIST_ICENABLERN):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
irq += (offset - GIC_DIST_ICENABLER0) * 8;
|
||||||
|
vgic_dist_disable_irq(vgic, vcpu_id, irq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISPENDR0, GIC_DIST_ISPENDRN):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
irq += (offset - GIC_DIST_ISPENDR0) * 8;
|
||||||
|
// @ivanv: should be checking this and other calls like it succeed
|
||||||
|
vgic_dist_set_pending_irq(vgic, vcpu_id, irq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICPENDR0, GIC_DIST_ICPENDRN):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
irq += (offset - GIC_DIST_ICPENDR0) * 8;
|
||||||
|
vgic_dist_clr_pending_irq(gic_dist, vcpu_id, irq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISACTIVER0, GIC_DIST_ISACTIVER0):
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->active0[vcpu_id]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ISACTIVER1, GIC_DIST_ISACTIVERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ISACTIVER1);
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->active[reg_offset]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICACTIVER0, GIC_DIST_ICACTIVER0):
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->active_clr0[vcpu_id]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICACTIVER1, GIC_DIST_ICACTIVERN):
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICACTIVER1);
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->active_clr[reg_offset]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_IPRIORITYR0, GIC_DIST_IPRIORITYRN):
|
||||||
|
break;
|
||||||
|
case RANGE32(0x7FC, 0x7FC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ITARGETSR0, GIC_DIST_ITARGETSRN):
|
||||||
|
break;
|
||||||
|
case RANGE32(0xBFC, 0xBFC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_ICFGR0, GIC_DIST_ICFGRN):
|
||||||
|
/*
|
||||||
|
* Emulate accesses to interrupt configuration registers to set the IRQ
|
||||||
|
* to be edge-triggered or level-sensitive.
|
||||||
|
*/
|
||||||
|
reg_offset = GIC_DIST_REGN(offset, GIC_DIST_ICFGR0);
|
||||||
|
emulate_reg_write_access(regs, addr, fsr, &gic_dist->config[reg_offset]);
|
||||||
|
break;
|
||||||
|
case RANGE32(0xD00, 0xDFC):
|
||||||
|
/* IMPLEMENTATION DEFINED registers. */
|
||||||
|
break;
|
||||||
|
case RANGE32(0xE00, 0xEFC):
|
||||||
|
/* GIC_DIST_NSACR [0xE00 - 0xF00) - Not supported */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_SGIR, GIC_DIST_SGIR):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
int mode = (data & GIC_DIST_SGI_TARGET_LIST_FILTER_MASK) >> GIC_DIST_SGI_TARGET_LIST_FILTER_SHIFT;
|
||||||
|
int virq = (data & GIC_DIST_SGI_INTID_MASK);
|
||||||
|
uint16_t target_list = 0;
|
||||||
|
switch (mode) {
|
||||||
|
case GIC_DIST_SGI_TARGET_LIST_SPEC:
|
||||||
|
/* Forward VIRQ to VCPUs specified in CPUTargetList */
|
||||||
|
target_list = (data & GIC_DIST_SGI_CPU_TARGET_LIST_MASK) >> GIC_DIST_SGI_CPU_TARGET_LIST_SHIFT;
|
||||||
|
break;
|
||||||
|
case GIC_DIST_SGI_TARGET_LIST_OTHERS:
|
||||||
|
/* Forward virq to all VCPUs except the requesting VCPU */
|
||||||
|
target_list = (1 << GUEST_NUM_VCPUS) - 1;
|
||||||
|
target_list = target_list & ~(1 << vcpu_id);
|
||||||
|
break;
|
||||||
|
case GIC_DIST_SGI_TARGET_SELF:
|
||||||
|
/* Forward to virq to only the requesting vcpu */
|
||||||
|
target_list = (1 << vcpu_id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown SGIR Target List Filter mode");
|
||||||
|
goto ignore_fault;
|
||||||
|
}
|
||||||
|
// @ivanv: Here we're making the assumption that there's only one vCPU, and
|
||||||
|
// we're also blindly injectnig the given IRQ to that vCPU.
|
||||||
|
// @ivanv: come back to this, do we have two writes to the TCB registers?
|
||||||
|
success = vgic_inject_irq(vcpu_id, virq);
|
||||||
|
assert(success);
|
||||||
|
break;
|
||||||
|
case RANGE32(0xF04, 0xF0C):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(GIC_DIST_CPENDSGIR0, GIC_DIST_SPENDSGIRN):
|
||||||
|
// @ivanv: come back to
|
||||||
|
assert(!"vgic SGI reg not implemented!\n");
|
||||||
|
break;
|
||||||
|
case RANGE32(0xF30, 0xFBC):
|
||||||
|
/* Reserved */
|
||||||
|
break;
|
||||||
|
case RANGE32(0xFC0, 0xFFC):
|
||||||
|
// @ivanv: GICv2 specific, GICv3 has different range for impl defined registers.
|
||||||
|
/* IMPLEMENTATION DEFINED registers. */
|
||||||
|
break;
|
||||||
|
#if defined(GIC_V3)
|
||||||
|
// @ivanv: explain GICv3 specific stuff, and also don't use the hardcoded valuees
|
||||||
|
case RANGE32(0x6100, 0x7F00):
|
||||||
|
// @ivanv revisit
|
||||||
|
// data = fault_get_data(fault);
|
||||||
|
// ZF_LOGF_IF(data, "bad dist: 0x%x 0x%x", offset, data);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown register offset 0x%x", offset);
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
ignore_fault:
|
||||||
|
assert(success);
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = fault_advance_vcpu(regs);
|
||||||
|
assert(success);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
/* @ivanv double check
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
#include <microkit.h>
|
||||||
|
#include "vgic.h"
|
||||||
|
#include "virq.h"
|
||||||
|
#include "../util/util.h"
|
||||||
|
#include "../fault.h"
|
||||||
|
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
#include "vgic_v2.h"
|
||||||
|
#elif defined(GIC_V3)
|
||||||
|
#include "vgic_v3.h"
|
||||||
|
#else
|
||||||
|
#error "Unknown GIC version"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "vdist.h"
|
||||||
|
|
||||||
|
/* The driver expects the VGIC state to be initialised before calling any of the driver functionality. */
|
||||||
|
extern vgic_t vgic;
|
||||||
|
|
||||||
|
bool handle_vgic_maintenance(uint64_t vcpu_id)
|
||||||
|
{
|
||||||
|
// @ivanv: reivist, also inconsistency between int and bool
|
||||||
|
bool success = true;
|
||||||
|
int idx = microkit_mr_get(seL4_VGICMaintenance_IDX);
|
||||||
|
/* Currently not handling spurious IRQs */
|
||||||
|
// @ivanv: wtf, this comment seems irrelevant to the code.
|
||||||
|
assert(idx >= 0);
|
||||||
|
|
||||||
|
// @ivanv: Revisit and make sure it's still correct.
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(&vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
assert((idx >= 0) && (idx < ARRAY_SIZE(vgic_vcpu->lr_shadow)));
|
||||||
|
struct virq_handle *slot = &vgic_vcpu->lr_shadow[idx];
|
||||||
|
assert(slot->virq != VIRQ_INVALID);
|
||||||
|
struct virq_handle lr_virq = *slot;
|
||||||
|
slot->virq = VIRQ_INVALID;
|
||||||
|
slot->ack_fn = NULL;
|
||||||
|
slot->ack_data = NULL;
|
||||||
|
/* Clear pending */
|
||||||
|
LOG_IRQ("Maintenance IRQ %d\n", lr_virq.virq);
|
||||||
|
set_pending(vgic_get_dist(vgic.registers), lr_virq.virq, false, vcpu_id);
|
||||||
|
virq_ack(vcpu_id, &lr_virq);
|
||||||
|
/* Check the overflow list for pending IRQs */
|
||||||
|
struct virq_handle *virq = vgic_irq_dequeue(&vgic, vcpu_id);
|
||||||
|
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
int group = 0;
|
||||||
|
#elif defined(GIC_V3)
|
||||||
|
int group = 1;
|
||||||
|
#else
|
||||||
|
#error "Unknown GIC version"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (virq) {
|
||||||
|
success = vgic_vcpu_load_list_reg(&vgic, vcpu_id, idx, group, virq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
printf("VGIC|ERROR: maintenance handler failed\n");
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: maybe this shouldn't be here?
|
||||||
|
bool vgic_register_irq(uint64_t vcpu_id, int virq_num, irq_ack_fn_t ack_fn, void *ack_data) {
|
||||||
|
assert(virq_num >= 0 && virq_num != VIRQ_INVALID);
|
||||||
|
struct virq_handle virq = {
|
||||||
|
.virq = virq_num,
|
||||||
|
.ack_fn = ack_fn,
|
||||||
|
.ack_data = ack_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
return virq_add(vcpu_id, &vgic, &virq);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vgic_inject_irq(uint64_t vcpu_id, int irq)
|
||||||
|
{
|
||||||
|
LOG_IRQ("Injecting IRQ %d\n", irq);
|
||||||
|
|
||||||
|
return vgic_dist_set_pending_irq(&vgic, vcpu_id, irq);
|
||||||
|
|
||||||
|
// @ivanv: explain why we don't check error before checking this fault stuff
|
||||||
|
// @ivanv: seperately, it seems weird to have this fault handling code here?
|
||||||
|
// @ivanv: revist
|
||||||
|
// if (!fault_handled(vcpu->vcpu_arch.fault) && fault_is_wfi(vcpu->vcpu_arch.fault)) {
|
||||||
|
// // ignore_fault(vcpu->vcpu_arch.fault);
|
||||||
|
// err = advance_vcpu_fault(regs);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: revisit this whole function
|
||||||
|
bool handle_vgic_dist_fault(uint64_t vcpu_id, uint64_t fault_addr, uint64_t fsr, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
/* Make sure that the fault address actually lies within the GIC distributor region. */
|
||||||
|
assert(fault_addr >= GIC_DIST_PADDR);
|
||||||
|
assert(fault_addr - GIC_DIST_PADDR < GIC_DIST_SIZE);
|
||||||
|
|
||||||
|
uint64_t offset = fault_addr - GIC_DIST_PADDR;
|
||||||
|
bool success = false;
|
||||||
|
if (fault_is_read(fsr)) {
|
||||||
|
// printf("VGIC|INFO: Read dist\n");
|
||||||
|
success = vgic_dist_reg_read(vcpu_id, &vgic, offset, fsr, regs);
|
||||||
|
assert(success);
|
||||||
|
} else {
|
||||||
|
// printf("VGIC|INFO: Write dist\n");
|
||||||
|
success = vgic_dist_reg_write(vcpu_id, &vgic, offset, fsr, regs);
|
||||||
|
assert(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <microkit.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// @ivanv: this should all come from the DTS!
|
||||||
|
#if defined(BOARD_qemu_virt_aarch64)
|
||||||
|
#define GIC_V2
|
||||||
|
#define GIC_DIST_PADDR 0x8000000
|
||||||
|
#elif defined(BOARD_odroidc2_hyp)
|
||||||
|
#define GIC_V2
|
||||||
|
#define GIC_DIST_PADDR 0xc4301000
|
||||||
|
#elif defined(BOARD_odroidc4_hyp)
|
||||||
|
#define GIC_V2
|
||||||
|
#define GIC_DIST_PADDR 0xffc01000
|
||||||
|
#elif defined(BOARD_rpi4b_hyp)
|
||||||
|
#define GIC_V2
|
||||||
|
#define GIC_DIST_PADDR 0xff841000
|
||||||
|
#elif defined(BOARD_imx8mm_evk_hyp)
|
||||||
|
#define GIC_V3
|
||||||
|
#define GIC_DIST_PADDR 0x38800000
|
||||||
|
#define GIC_REDIST_PADDR 0x38880000
|
||||||
|
#else
|
||||||
|
#error Need to define GIC addresses
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
#define GIC_DIST_SIZE 0x1000
|
||||||
|
#elif defined(GIC_V3)
|
||||||
|
#define GIC_DIST_SIZE 0x10000
|
||||||
|
#define GIC_REDIST_SIZE 0xc0000
|
||||||
|
#else
|
||||||
|
#error Unknown GIC version
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Uncomment these defines for more verbose logging in the GIC driver. */
|
||||||
|
// #define DEBUG_IRQ
|
||||||
|
// #define DEBUG_DIST
|
||||||
|
|
||||||
|
#if defined(DEBUG_IRQ)
|
||||||
|
#define LOG_IRQ(...) do{ printf("VGIC|IRQ: "); printf(__VA_ARGS__); }while(0)
|
||||||
|
#else
|
||||||
|
#define LOG_IRQ(...) do{}while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(DEBUG_DIST)
|
||||||
|
#define LOG_DIST(...) do{ printf("VGIC|DIST: "); printf(__VA_ARGS__); }while(0)
|
||||||
|
#else
|
||||||
|
#define LOG_DIST(...) do{}while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*irq_ack_fn_t)(uint64_t vcpu_id, int irq, void *cookie);
|
||||||
|
|
||||||
|
void vgic_init();
|
||||||
|
bool handle_vgic_maintenance(uint64_t vcpu_id);
|
||||||
|
bool handle_vgic_dist_fault(uint64_t vcpu_id, uint64_t fault_addr, uint64_t fsr, seL4_UserContext *regs);
|
||||||
|
bool handle_vgic_redist_fault(uint64_t vcpu_id, uint64_t fault_addr, uint64_t fsr, seL4_UserContext *regs);
|
||||||
|
bool vgic_register_irq(uint64_t vcpu_id, int virq_num, irq_ack_fn_t ack_fn, void *ack_data);
|
||||||
|
bool vgic_inject_irq(uint64_t vcpu_id, int irq);
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file controls and maintains the virtualised GICv2 device for the VM. The spec used is: @ivanv
|
||||||
|
* IRQs must be registered at initialisation using the function: vm_register_irq
|
||||||
|
*
|
||||||
|
* @ivanv: talk about what features this driver actually implements
|
||||||
|
* This driver supports multiple vCPUs.
|
||||||
|
*
|
||||||
|
* This function creates and registers an IRQ data structure which will be used for IRQ maintenance
|
||||||
|
* b) ENABLING: When the VM enables the IRQ, it checks the pending flag for the VM.
|
||||||
|
* - If the IRQ is not pending, we either
|
||||||
|
* 1) have not received an IRQ so it is still enabled in seL4
|
||||||
|
* 2) have received an IRQ, but ignored it because the VM had disabled it.
|
||||||
|
* In either case, we simply ACK the IRQ with seL4. In case 1), the IRQ will come straight through,
|
||||||
|
in case 2), we have ACKed an IRQ that was not yet pending anyway.
|
||||||
|
* - If the IRQ is already pending, we can assume that the VM has yet to ACK the IRQ and take no further
|
||||||
|
* action.
|
||||||
|
* Transitions: b->c
|
||||||
|
* c) PIRQ: When an IRQ is received from seL4, seL4 disables the IRQ and sends an async message. When the VMM
|
||||||
|
* receives the message.
|
||||||
|
* - If the IRQ is enabled, we set the pending flag in the VM and inject the appropriate IRQ
|
||||||
|
* leading to state d)
|
||||||
|
* - If the IRQ is disabled, the VMM takes no further action, leading to state b)
|
||||||
|
* Transitions: (enabled)? c->d : c->b
|
||||||
|
* d) When the VM acknowledges the IRQ, an exception is raised and delivered to the VMM. When the VMM
|
||||||
|
* receives the exception, it clears the pending flag and acks the IRQ with seL4, leading back to state c)
|
||||||
|
* Transition: d->c
|
||||||
|
* g) When/if the VM disables the IRQ, we may still have an IRQ resident in the GIC. We allow
|
||||||
|
* this IRQ to be delivered to the VM, but subsequent IRQs will not be delivered as seen by state c)
|
||||||
|
* Transitions g->c
|
||||||
|
*
|
||||||
|
* NOTE: There is a big assumption that the VM will not manually manipulate our pending flags and
|
||||||
|
* destroy our state. The affects of this will be an IRQ that is never acknowledged and hence,
|
||||||
|
* will never occur again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
|
||||||
|
#include "vgic.h"
|
||||||
|
#include "vgic_v2.h"
|
||||||
|
#include "virq.h"
|
||||||
|
#include "vdist.h"
|
||||||
|
#include "../util/util.h"
|
||||||
|
#include "../fault.h"
|
||||||
|
|
||||||
|
vgic_t vgic;
|
||||||
|
struct gic_dist_map dist;
|
||||||
|
|
||||||
|
static void vgic_dist_reset(struct gic_dist_map *gic_dist)
|
||||||
|
{
|
||||||
|
gic_dist->typer = 0x0000fce7; /* RO */
|
||||||
|
gic_dist->iidr = 0x0200043b; /* RO */
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_MAX_NUM_NODES; i++) {
|
||||||
|
gic_dist->enable_set0[i] = 0x0000ffff; /* 16bit RO */
|
||||||
|
gic_dist->enable_clr0[i] = 0x0000ffff; /* 16bit RO */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset value depends on GIC configuration */
|
||||||
|
gic_dist->config[0] = 0xaaaaaaaa; /* RO */
|
||||||
|
gic_dist->config[1] = 0x55540000;
|
||||||
|
gic_dist->config[2] = 0x55555555;
|
||||||
|
gic_dist->config[3] = 0x55555555;
|
||||||
|
gic_dist->config[4] = 0x55555555;
|
||||||
|
gic_dist->config[5] = 0x55555555;
|
||||||
|
gic_dist->config[6] = 0x55555555;
|
||||||
|
gic_dist->config[7] = 0x55555555;
|
||||||
|
gic_dist->config[8] = 0x55555555;
|
||||||
|
gic_dist->config[9] = 0x55555555;
|
||||||
|
gic_dist->config[10] = 0x55555555;
|
||||||
|
gic_dist->config[11] = 0x55555555;
|
||||||
|
gic_dist->config[12] = 0x55555555;
|
||||||
|
gic_dist->config[13] = 0x55555555;
|
||||||
|
gic_dist->config[14] = 0x55555555;
|
||||||
|
gic_dist->config[15] = 0x55555555;
|
||||||
|
|
||||||
|
/* Configure per-processor SGI/PPI target registers */
|
||||||
|
for (int i = 0; i < CONFIG_MAX_NUM_NODES; i++) {
|
||||||
|
for (int j = 0; j < ARRAY_SIZE(gic_dist->targets0[i]); j++) {
|
||||||
|
for (int irq = 0; irq < sizeof(uint32_t); irq++) {
|
||||||
|
gic_dist->targets0[i][j] |= ((1 << i) << (irq * 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Deliver the SPI interrupts to the first CPU interface */
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(gic_dist->targets); i++) {
|
||||||
|
gic_dist->targets[i] = 0x1010101;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* identification */
|
||||||
|
gic_dist->periph_id[4] = 0x00000004; /* RO */
|
||||||
|
gic_dist->periph_id[8] = 0x00000090; /* RO */
|
||||||
|
gic_dist->periph_id[9] = 0x000000b4; /* RO */
|
||||||
|
gic_dist->periph_id[10] = 0x0000002b; /* RO */
|
||||||
|
gic_dist->component_id[0] = 0x0000000d; /* RO */
|
||||||
|
gic_dist->component_id[1] = 0x000000f0; /* RO */
|
||||||
|
gic_dist->component_id[2] = 0x00000005; /* RO */
|
||||||
|
gic_dist->component_id[3] = 0x000000b1; /* RO */
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgic_init()
|
||||||
|
{
|
||||||
|
memset(&vgic, 0, sizeof(vgic_t));
|
||||||
|
for (int i = 0; i < NUM_SLOTS_SPI_VIRQ; i++) {
|
||||||
|
vgic.vspis[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_VCPU_LOCAL_VIRQS; i++) {
|
||||||
|
vgic.vgic_vcpu[GUEST_VCPU_ID].local_virqs[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_LIST_REGS; i++) {
|
||||||
|
vgic.vgic_vcpu[GUEST_VCPU_ID].lr_shadow[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < MAX_IRQ_QUEUE_LEN; i++) {
|
||||||
|
vgic.vgic_vcpu[GUEST_VCPU_ID].irq_queue.irqs[i] = NULL;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_SLOTS_SPI_VIRQ; i++) {
|
||||||
|
vgic.vspis[i].virq = VIRQ_INVALID;
|
||||||
|
vgic.vspis[i].ack_fn = NULL;
|
||||||
|
vgic.vspis[i].ack_data = NULL;
|
||||||
|
}
|
||||||
|
vgic.registers = &dist;
|
||||||
|
memset(vgic.registers, 0, sizeof(struct gic_dist_map));
|
||||||
|
vgic_dist_reset(vgic_get_dist(vgic.registers));
|
||||||
|
}
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "../util/util.h"
|
||||||
|
|
||||||
|
#define GIC_ENABLED 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This the memory map for the GIC distributor.
|
||||||
|
*
|
||||||
|
* You will note that some of these registers in the memory map are actually
|
||||||
|
* arrays with the length of the number of virtual CPUs that the guest has.
|
||||||
|
* The reason for this has to do with the fact that we are virtualising the GIC
|
||||||
|
* distributor. In actual hardware, these registers would be banked, meaning
|
||||||
|
* that the value of each register depends on the CPU that is accessing it.
|
||||||
|
* However, since we are dealing with virutal CPUs, we have to make the
|
||||||
|
* registers appear as if they are banked. You should note that the commented
|
||||||
|
* address offsets listed on the right of each register are the offset from the
|
||||||
|
* *guest's* perspective.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct gic_dist_map {
|
||||||
|
uint32_t ctlr; /* 0x000 */
|
||||||
|
uint32_t typer; /* 0x004 */
|
||||||
|
uint32_t iidr; /* 0x008 */
|
||||||
|
|
||||||
|
uint32_t res1[29]; /* [0x00C, 0x080) */
|
||||||
|
|
||||||
|
uint32_t irq_group0[GUEST_NUM_VCPUS]; /* [0x080, 0x84) */
|
||||||
|
uint32_t irq_group[31]; /* [0x084, 0x100) */
|
||||||
|
uint32_t enable_set0[GUEST_NUM_VCPUS]; /* [0x100, 0x104) */
|
||||||
|
uint32_t enable_set[31]; /* [0x104, 0x180) */
|
||||||
|
uint32_t enable_clr0[GUEST_NUM_VCPUS]; /* [0x180, 0x184) */
|
||||||
|
uint32_t enable_clr[31]; /* [0x184, 0x200) */
|
||||||
|
uint32_t pending_set0[GUEST_NUM_VCPUS]; /* [0x200, 0x204) */
|
||||||
|
uint32_t pending_set[31]; /* [0x204, 0x280) */
|
||||||
|
uint32_t pending_clr0[GUEST_NUM_VCPUS]; /* [0x280, 0x284) */
|
||||||
|
uint32_t pending_clr[31]; /* [0x284, 0x300) */
|
||||||
|
uint32_t active0[GUEST_NUM_VCPUS]; /* [0x300, 0x304) */
|
||||||
|
uint32_t active[31]; /* [0x300, 0x380) */
|
||||||
|
uint32_t active_clr0[GUEST_NUM_VCPUS]; /* [0x380, 0x384) */
|
||||||
|
uint32_t active_clr[31]; /* [0x384, 0x400) */
|
||||||
|
uint32_t priority0[GUEST_NUM_VCPUS][8]; /* [0x400, 0x420) */
|
||||||
|
uint32_t priority[247]; /* [0x420, 0x7FC) */
|
||||||
|
uint32_t res3; /* 0x7FC */
|
||||||
|
|
||||||
|
uint32_t targets0[GUEST_NUM_VCPUS][8]; /* [0x800, 0x820) */
|
||||||
|
uint32_t targets[247]; /* [0x820, 0xBFC) */
|
||||||
|
uint32_t res4; /* 0xBFC */
|
||||||
|
|
||||||
|
uint32_t config[64]; /* [0xC00, 0xD00) */
|
||||||
|
|
||||||
|
uint32_t spi[32]; /* [0xD00, 0xD80) */
|
||||||
|
uint32_t res5[20]; /* [0xD80, 0xDD0) */
|
||||||
|
uint32_t res6; /* 0xDD0 */
|
||||||
|
uint32_t legacy_int; /* 0xDD4 */
|
||||||
|
uint32_t res7[2]; /* [0xDD8, 0xDE0) */
|
||||||
|
uint32_t match_d; /* 0xDE0 */
|
||||||
|
uint32_t enable_d; /* 0xDE4 */
|
||||||
|
uint32_t res8[70]; /* [0xDE8, 0xF00) */
|
||||||
|
|
||||||
|
uint32_t sgir; /* 0xF00 */
|
||||||
|
uint32_t res9[3]; /* [0xF04, 0xF10) */
|
||||||
|
|
||||||
|
uint32_t sgi_pending_clr[GUEST_NUM_VCPUS][4]; /* [0xF10, 0xF20) */
|
||||||
|
uint32_t sgi_pending_set[GUEST_NUM_VCPUS][4]; /* [0xF20, 0xF30) */
|
||||||
|
uint32_t res10[40]; /* [0xF30, 0xFC0) */
|
||||||
|
|
||||||
|
uint32_t periph_id[12]; /* [0xFC0, 0xFF0) */
|
||||||
|
uint32_t component_id[4]; /* [0xFF0, 0xFFF] */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GIC Distributor Register Map
|
||||||
|
* ARM Generic Interrupt Controller (Architecture version 2.0)
|
||||||
|
* Architecture Specification (Issue B.b)
|
||||||
|
* Chapter 4 Programmers' Model - Table 4.1
|
||||||
|
*/
|
||||||
|
#define GIC_DIST_CTLR 0x000
|
||||||
|
#define GIC_DIST_TYPER 0x004
|
||||||
|
#define GIC_DIST_IIDR 0x008
|
||||||
|
#define GIC_DIST_IGROUPR0 0x080
|
||||||
|
#define GIC_DIST_IGROUPR1 0x084
|
||||||
|
#define GIC_DIST_IGROUPRN 0x0FC
|
||||||
|
#define GIC_DIST_ISENABLER0 0x100
|
||||||
|
#define GIC_DIST_ISENABLER1 0x104
|
||||||
|
#define GIC_DIST_ISENABLERN 0x17C
|
||||||
|
#define GIC_DIST_ICENABLER0 0x180
|
||||||
|
#define GIC_DIST_ICENABLER1 0x184
|
||||||
|
#define GIC_DIST_ICENABLERN 0x1fC
|
||||||
|
#define GIC_DIST_ISPENDR0 0x200
|
||||||
|
#define GIC_DIST_ISPENDR1 0x204
|
||||||
|
#define GIC_DIST_ISPENDRN 0x27C
|
||||||
|
#define GIC_DIST_ICPENDR0 0x280
|
||||||
|
#define GIC_DIST_ICPENDR1 0x284
|
||||||
|
#define GIC_DIST_ICPENDRN 0x2FC
|
||||||
|
#define GIC_DIST_ISACTIVER0 0x300
|
||||||
|
#define GIC_DIST_ISACTIVER1 0x304
|
||||||
|
#define GIC_DIST_ISACTIVERN 0x37C
|
||||||
|
#define GIC_DIST_ICACTIVER0 0x380
|
||||||
|
#define GIC_DIST_ICACTIVER1 0x384
|
||||||
|
#define GIC_DIST_ICACTIVERN 0x3FC
|
||||||
|
#define GIC_DIST_IPRIORITYR0 0x400
|
||||||
|
#define GIC_DIST_IPRIORITYR7 0x41C
|
||||||
|
#define GIC_DIST_IPRIORITYR8 0x420
|
||||||
|
#define GIC_DIST_IPRIORITYRN 0x7F8
|
||||||
|
#define GIC_DIST_ITARGETSR0 0x800
|
||||||
|
#define GIC_DIST_ITARGETSR7 0x81C
|
||||||
|
#define GIC_DIST_ITARGETSR8 0x820
|
||||||
|
#define GIC_DIST_ITARGETSRN 0xBF8
|
||||||
|
#define GIC_DIST_ICFGR0 0xC00
|
||||||
|
#define GIC_DIST_ICFGRN 0xCFC
|
||||||
|
#define GIC_DIST_NSACR0 0xE00
|
||||||
|
#define GIC_DIST_NSACRN 0xEFC
|
||||||
|
#define GIC_DIST_SGIR 0xF00
|
||||||
|
#define GIC_DIST_CPENDSGIR0 0xF10
|
||||||
|
#define GIC_DIST_CPENDSGIRN 0xF1C
|
||||||
|
#define GIC_DIST_SPENDSGIR0 0xF20
|
||||||
|
#define GIC_DIST_SPENDSGIRN 0xF2C
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ARM Generic Interrupt Controller (Architecture version 2.0)
|
||||||
|
* Architecture Specification (Issue B.b)
|
||||||
|
* 4.3.15: Software Generated Interrupt Register, GICD_SGI
|
||||||
|
* Values defined as per Table 4-21 (GICD_SGIR bit assignments)
|
||||||
|
*/
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_FILTER_SHIFT 24
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_FILTER_MASK 0x3 << GIC_DIST_SGI_TARGET_LIST_FILTER_SHIFT
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_SPEC 0
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_OTHERS 1
|
||||||
|
#define GIC_DIST_SGI_TARGET_SELF 2
|
||||||
|
|
||||||
|
#define GIC_DIST_SGI_CPU_TARGET_LIST_SHIFT 16
|
||||||
|
#define GIC_DIST_SGI_CPU_TARGET_LIST_MASK 0xFF << GIC_DIST_SGI_CPU_TARGET_LIST_SHIFT
|
||||||
|
|
||||||
|
#define GIC_DIST_SGI_INTID_MASK 0xF
|
||||||
|
|
||||||
|
bool vgic_inject_irq(uint64_t vcpu_id, int irq);
|
||||||
|
|
||||||
|
typedef struct gic_dist_map vgic_reg_t;
|
||||||
|
|
||||||
|
static inline struct gic_dist_map *vgic_get_dist(void *registers)
|
||||||
|
{
|
||||||
|
return (struct gic_dist_map *) registers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool vgic_dist_is_enabled(struct gic_dist_map *gic_dist)
|
||||||
|
{
|
||||||
|
return gic_dist->ctlr == GIC_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vgic_dist_enable(struct gic_dist_map *gic_dist) {
|
||||||
|
gic_dist->ctlr = GIC_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vgic_dist_disable(struct gic_dist_map *gic_dist)
|
||||||
|
{
|
||||||
|
gic_dist->ctlr = 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This component controls and maintains the GIC for the VM.
|
||||||
|
* IRQs must be registered at init time with vm_virq_new(...)
|
||||||
|
* This function creates and registers an IRQ data structure which will be used for IRQ maintenance
|
||||||
|
* b) ENABLING: When the VM enables the IRQ, it checks the pending flag for the VM.
|
||||||
|
* - If the IRQ is not pending, we either
|
||||||
|
* 1) have not received an IRQ so it is still enabled in seL4
|
||||||
|
* 2) have received an IRQ, but ignored it because the VM had disabled it.
|
||||||
|
* In either case, we simply ACK the IRQ with seL4. In case 1), the IRQ will come straight through,
|
||||||
|
in case 2), we have ACKed an IRQ that was not yet pending anyway.
|
||||||
|
* - If the IRQ is already pending, we can assume that the VM has yet to ACK the IRQ and take no further
|
||||||
|
* action.
|
||||||
|
* Transitions: b->c
|
||||||
|
* c) PIRQ: When an IRQ is received from seL4, seL4 disables the IRQ and sends an async message. When the VMM
|
||||||
|
* receives the message.
|
||||||
|
* - If the IRQ is enabled, we set the pending flag in the VM and inject the appropriate IRQ
|
||||||
|
* leading to state d)
|
||||||
|
* - If the IRQ is disabled, the VMM takes no further action, leading to state b)
|
||||||
|
* Transitions: (enabled)? c->d : c->b
|
||||||
|
* d) When the VM acknowledges the IRQ, an exception is raised and delivered to the VMM. When the VMM
|
||||||
|
* receives the exception, it clears the pending flag and acks the IRQ with seL4, leading back to state c)
|
||||||
|
* Transition: d->c
|
||||||
|
* g) When/if the VM disables the IRQ, we may still have an IRQ resident in the GIC. We allow
|
||||||
|
* this IRQ to be delivered to the VM, but subsequent IRQs will not be delivered as seen by state c)
|
||||||
|
* Transitions g->c
|
||||||
|
*
|
||||||
|
* NOTE: There is a big assumption that the VM will not manually manipulate our pending flags and
|
||||||
|
* destroy our state. The affects of this will be an IRQ that is never acknowledged and hence,
|
||||||
|
* will never occur again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vgic.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "../fault.h"
|
||||||
|
#include "virq.h"
|
||||||
|
#include "vgic_v3.h"
|
||||||
|
#include "vdist.h"
|
||||||
|
|
||||||
|
vgic_t vgic;
|
||||||
|
|
||||||
|
static bool handle_vgic_redist_read_fault(uint64_t vcpu_id, vgic_t *vgic, uint64_t offset, uint64_t fsr, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
struct gic_dist_map *gic_dist = vgic_get_dist(vgic->registers);
|
||||||
|
struct gic_redist_map *gic_redist = vgic_get_redist(vgic->registers);
|
||||||
|
uint32_t reg = 0;
|
||||||
|
uintptr_t base_reg;
|
||||||
|
uint32_t *reg_ptr;
|
||||||
|
switch (offset) {
|
||||||
|
case RANGE32(GICR_CTLR, GICR_CTLR):
|
||||||
|
reg = gic_redist->ctlr;
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_IIDR, GICR_IIDR):
|
||||||
|
reg = gic_redist->iidr;
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_TYPER, GICR_TYPER):
|
||||||
|
reg = gic_redist->typer;
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_WAKER, GICR_WAKER):
|
||||||
|
reg = gic_redist->waker;
|
||||||
|
break;
|
||||||
|
case RANGE32(0xFFD0, 0xFFFC):
|
||||||
|
base_reg = (uintptr_t) & (gic_redist->pidr4);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - 0xFFD0));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_IGROUPR0, GICR_IGROUPR0):
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->irq_group0[vcpu_id]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - GICR_IGROUPR0));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_ICFGR1, GICR_ICFGR1):
|
||||||
|
base_reg = (uintptr_t) & (gic_dist->config[1]);
|
||||||
|
reg_ptr = (uint32_t *)(base_reg + (offset - GICR_ICFGR1));
|
||||||
|
reg = *reg_ptr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown register offset 0x%x\n", offset);
|
||||||
|
// @ivanv: used to be ignore_fault, double check this is right
|
||||||
|
success = fault_advance_vcpu(regs);
|
||||||
|
goto fault_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t fault_addr = GIC_REDIST_PADDR + offset;
|
||||||
|
uint32_t mask = fault_get_data_mask(fault_addr, fsr);
|
||||||
|
success = fault_advance(regs, fault_addr, fsr, reg & mask);
|
||||||
|
|
||||||
|
fault_return:
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool handle_vgic_redist_write_fault(uint64_t vcpu_id, vgic_t *vgic, uint64_t offset, uint64_t fsr, seL4_UserContext *regs)
|
||||||
|
{
|
||||||
|
// @ivanv: why is this not reading from the redist?
|
||||||
|
uintptr_t fault_addr = GIC_REDIST_PADDR + offset;
|
||||||
|
struct gic_dist_map *gic_dist = vgic_get_dist(vgic->registers);
|
||||||
|
uint32_t mask = fault_get_data_mask(GIC_REDIST_PADDR + offset, fsr);
|
||||||
|
uint32_t data;
|
||||||
|
switch (offset) {
|
||||||
|
case RANGE32(GICR_WAKER, GICR_WAKER):
|
||||||
|
/* Writes are ignored */
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_IGROUPR0, GICR_IGROUPR0):
|
||||||
|
emulate_reg_write_access(regs, fault_addr, fsr, &gic_dist->irq_group0[vcpu_id]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_ISENABLER0, GICR_ISENABLER0):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
vgic_dist_enable_irq(vgic, vcpu_id, irq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_ICENABLER0, GICR_ICENABLER0):
|
||||||
|
data = fault_get_data(regs, fsr);
|
||||||
|
/* Mask the data to write */
|
||||||
|
data &= mask;
|
||||||
|
while (data) {
|
||||||
|
int irq;
|
||||||
|
irq = CTZ(data);
|
||||||
|
data &= ~(1U << irq);
|
||||||
|
set_enable(gic_dist, irq, false, vcpu_id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_ICACTIVER0, GICR_ICACTIVER0):
|
||||||
|
// @ivanv: understand, this is a comment left over from kent
|
||||||
|
// TODO fix this
|
||||||
|
emulate_reg_write_access(regs, fault_addr, fsr, &gic_dist->active0[vcpu_id]);
|
||||||
|
break;
|
||||||
|
case RANGE32(GICR_IPRIORITYR0, GICR_IPRIORITYRN):
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown register offset 0x%x, value: 0x%x\n", offset, fault_get_data(regs, fsr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = fault_advance_vcpu(regs);
|
||||||
|
assert(!err);
|
||||||
|
if (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle_vgic_redist_fault(uint64_t vcpu_id, uint64_t fault_addr, uint64_t fsr, seL4_UserContext *regs) {
|
||||||
|
assert(fault_addr >= GIC_REDIST_PADDR);
|
||||||
|
uint64_t offset = fault_addr - GIC_REDIST_PADDR;
|
||||||
|
assert(offset < GIC_REDIST_SIZE);
|
||||||
|
|
||||||
|
if (fault_is_read(fsr)) {
|
||||||
|
return handle_vgic_redist_read_fault(vcpu_id, &vgic, offset, fsr, regs);
|
||||||
|
} else {
|
||||||
|
return handle_vgic_redist_write_fault(vcpu_id, &vgic, offset, fsr, regs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vgic_dist_reset(struct gic_dist_map *dist)
|
||||||
|
{
|
||||||
|
// @ivanv: come back to, right now it's a global so we don't need to init the memory to zero
|
||||||
|
// memset(gic_dist, 0, sizeof(*gic_dist));
|
||||||
|
|
||||||
|
dist->typer = 0x7B04B0; /* RO */
|
||||||
|
dist->iidr = 0x1043B ; /* RO */
|
||||||
|
|
||||||
|
dist->enable_set[0] = 0x0000ffff; /* 16bit RO */
|
||||||
|
dist->enable_clr[0] = 0x0000ffff; /* 16bit RO */
|
||||||
|
|
||||||
|
dist->config[0] = 0xaaaaaaaa; /* RO */
|
||||||
|
|
||||||
|
dist->pidrn[0] = 0x44; /* RO */
|
||||||
|
dist->pidrn[4] = 0x92; /* RO */
|
||||||
|
dist->pidrn[5] = 0xB4; /* RO */
|
||||||
|
dist->pidrn[6] = 0x3B; /* RO */
|
||||||
|
|
||||||
|
dist->cidrn[0] = 0x0D; /* RO */
|
||||||
|
dist->cidrn[1] = 0xF0; /* RO */
|
||||||
|
dist->cidrn[2] = 0x05; /* RO */
|
||||||
|
dist->cidrn[3] = 0xB1; /* RO */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vgic_redist_reset(struct gic_redist_map *redist) {
|
||||||
|
// @ivanv: come back to, right now it's a global so we don't need to init the memory to zero
|
||||||
|
// memset(redist, 0, sizeof(*redist));
|
||||||
|
redist->typer = 0x11; /* RO */
|
||||||
|
redist->iidr = 0x1143B; /* RO */
|
||||||
|
|
||||||
|
redist->pidr0 = 0x93; /* RO */
|
||||||
|
redist->pidr1 = 0xB4; /* RO */
|
||||||
|
redist->pidr2 = 0x3B; /* RO */
|
||||||
|
redist->pidr4 = 0x44; /* RO */
|
||||||
|
|
||||||
|
redist->cidr0 = 0x0D; /* RO */
|
||||||
|
redist->cidr1 = 0xF0; /* RO */
|
||||||
|
redist->cidr2 = 0x05; /* RO */
|
||||||
|
redist->cidr3 = 0xB1; /* RO */
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ivanv: come back to
|
||||||
|
struct gic_dist_map dist;
|
||||||
|
struct gic_redist_map redist;
|
||||||
|
vgic_reg_t vgic_regs;
|
||||||
|
|
||||||
|
void vgic_init()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_SLOTS_SPI_VIRQ; i++) {
|
||||||
|
vgic.vspis[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_VCPU_LOCAL_VIRQS; i++) {
|
||||||
|
vgic.vgic_vcpu[VCPU_ID].local_virqs[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_LIST_REGS; i++) {
|
||||||
|
vgic.vgic_vcpu[VCPU_ID].lr_shadow[i].virq = VIRQ_INVALID;
|
||||||
|
}
|
||||||
|
vgic.registers = &vgic_regs;
|
||||||
|
vgic_regs.dist = &dist;
|
||||||
|
vgic_regs.redist = &redist;
|
||||||
|
|
||||||
|
vgic_dist_reset(&dist);
|
||||||
|
vgic_redist_reset(&redist);
|
||||||
|
}
|
||||||
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2019, DornerWorks
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define GIC_500_GRP0 (1 << 0)
|
||||||
|
#define GIC_500_GRP1_NS (1 << 1)
|
||||||
|
#define GIC_500_GRP1_S (1 << 2)
|
||||||
|
#define GIC_500_ARE_S (1 << 4)
|
||||||
|
|
||||||
|
#define GIC_500_ENABLED (GIC_500_ARE_S | GIC_500_GRP1_NS | GIC_500_GRP0)
|
||||||
|
#define GIC_ENABLED GIC_500_ENABLED
|
||||||
|
|
||||||
|
#define GIC_SGI_OFFSET 0x10000
|
||||||
|
#define GIC_MAX_DISABLE 32
|
||||||
|
#define GIC_GROUP 1
|
||||||
|
|
||||||
|
struct gic_dist_map {
|
||||||
|
uint32_t ctlr; /* 0x0000 */
|
||||||
|
uint32_t typer; /* 0x0004 */
|
||||||
|
uint32_t iidr; /* 0x0008 */
|
||||||
|
uint32_t res1[13]; /* [0x000C, 0x0040) */
|
||||||
|
uint32_t setspi_nsr; /* 0x0040 */
|
||||||
|
uint32_t res2; /* 0x0044 */
|
||||||
|
uint32_t clrspi_nsr; /* 0x0048 */
|
||||||
|
uint32_t res3; /* 0x004C */
|
||||||
|
uint32_t setspi_sr; /* 0x0050 */
|
||||||
|
uint32_t res4; /* 0x0054 */
|
||||||
|
uint32_t clrspi_sr; /* 0x0058 */
|
||||||
|
uint32_t res5[9]; /* [0x005C, 0x0080) */
|
||||||
|
uint32_t irq_group0[GUEST_NUM_VCPUS]; /* [0x080, 0x84) */
|
||||||
|
uint32_t irq_group[31]; /* [0x084, 0x100) */
|
||||||
|
uint32_t enable_set0[GUEST_NUM_VCPUS]; /* [0x100, 0x104) */
|
||||||
|
uint32_t enable_set[31]; /* [0x104, 0x180) */
|
||||||
|
uint32_t enable_clr0[GUEST_NUM_VCPUS]; /* [0x180, 0x184) */
|
||||||
|
uint32_t enable_clr[31]; /* [0x184, 0x200) */
|
||||||
|
uint32_t pending_set0[GUEST_NUM_VCPUS]; /* [0x200, 0x204) */
|
||||||
|
uint32_t pending_set[31]; /* [0x204, 0x280) */
|
||||||
|
uint32_t pending_clr0[GUEST_NUM_VCPUS]; /* [0x280, 0x284) */
|
||||||
|
uint32_t pending_clr[31]; /* [0x284, 0x300) */
|
||||||
|
uint32_t active0[GUEST_NUM_VCPUS]; /* [0x300, 0x304) */
|
||||||
|
uint32_t active[31]; /* [0x300, 0x380) */
|
||||||
|
uint32_t active_clr0[GUEST_NUM_VCPUS]; /* [0x380, 0x384) */
|
||||||
|
uint32_t active_clr[31]; /* [0x384, 0x400) */
|
||||||
|
uint32_t priority0[GUEST_NUM_VCPUS][8]; /* [0x400, 0x420) */
|
||||||
|
uint32_t priority[247]; /* [0x420, 0x7FC) */
|
||||||
|
uint32_t res6; /* 0x7FC */
|
||||||
|
|
||||||
|
uint32_t targets0[GUEST_NUM_VCPUS][8]; /* [0x800, 0x820) */
|
||||||
|
uint32_t targets[247]; /* [0x820, 0xBFC) */
|
||||||
|
uint32_t res7; /* 0xBFC */
|
||||||
|
|
||||||
|
uint32_t config[64]; /* [0xC00, 0xD00) */
|
||||||
|
uint32_t group_mod[64]; /* [0xD00, 0xE00) */
|
||||||
|
uint32_t nsacr[64]; /* [0xE00, 0xF00) */
|
||||||
|
uint32_t sgir; /* 0xF00 */
|
||||||
|
uint32_t res8[3]; /* [0xF00, 0xF10) */
|
||||||
|
uint32_t sgi_pending_clr[GUEST_NUM_VCPUS][4]; /* [0xF10, 0xF20) */
|
||||||
|
uint32_t sgi_pending_set[GUEST_NUM_VCPUS][4]; /* [0xF20, 0xF30) */
|
||||||
|
uint32_t res9[5235]; /* [0x0F30, 0x6100) */
|
||||||
|
|
||||||
|
uint64_t irouter[960]; /* [0x6100, 0x7F00) */
|
||||||
|
uint64_t res10[2080]; /* [0x7F00, 0xC000) */
|
||||||
|
uint32_t estatusr; /* 0xC000 */
|
||||||
|
uint32_t errtestr; /* 0xC004 */
|
||||||
|
uint32_t res11[31]; /* [0xC008, 0xC084) */
|
||||||
|
uint32_t spisr[30]; /* [0xC084, 0xC0FC) */
|
||||||
|
uint32_t res12[4021]; /* [0xC0FC, 0xFFD0) */
|
||||||
|
|
||||||
|
uint32_t pidrn[8]; /* [0xFFD0, 0xFFF0) */
|
||||||
|
uint32_t cidrn[4]; /* [0xFFD0, 0xFFFC] */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Memory map for GIC Redistributor Registers for control and physical LPI's */
|
||||||
|
struct gic_redist_map { /* Starting */
|
||||||
|
uint32_t ctlr; /* 0x0000 */
|
||||||
|
uint32_t iidr; /* 0x0004 */
|
||||||
|
uint64_t typer; /* 0x008 */
|
||||||
|
uint32_t res0; /* 0x0010 */
|
||||||
|
uint32_t waker; /* 0x0014 */
|
||||||
|
uint32_t res1[21]; /* 0x0018 */
|
||||||
|
uint64_t propbaser; /* 0x0070 */
|
||||||
|
uint64_t pendbaser; /* 0x0078 */
|
||||||
|
uint32_t res2[16340]; /* 0x0080 */
|
||||||
|
uint32_t pidr4; /* 0xFFD0 */
|
||||||
|
uint32_t pidr5; /* 0xFFD4 */
|
||||||
|
uint32_t pidr6; /* 0xFFD8 */
|
||||||
|
uint32_t pidr7; /* 0xFFDC */
|
||||||
|
uint32_t pidr0; /* 0xFFE0 */
|
||||||
|
uint32_t pidr1; /* 0xFFE4 */
|
||||||
|
uint32_t pidr2; /* 0xFFE8 */
|
||||||
|
uint32_t pidr3; /* 0xFFEC */
|
||||||
|
uint32_t cidr0; /* 0xFFF0 */
|
||||||
|
uint32_t cidr1; /* 0xFFF4 */
|
||||||
|
uint32_t cidr2; /* 0xFFF8 */
|
||||||
|
uint32_t cidr3; /* 0xFFFC */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Memory map for the GIC Redistributor Registers for the SGI and PPI's */
|
||||||
|
// struct gic_redist_sgi_ppi_map { /* Starting */
|
||||||
|
// uint32_t res0[32]; /* 0x0000 */
|
||||||
|
// uint32_t igroup[32]; /* 0x0080 */
|
||||||
|
// uint32_t isenable[32]; /* 0x0100 */
|
||||||
|
// uint32_t icenable[32]; /* 0x0180 */
|
||||||
|
// uint32_t ispend[32]; /* 0x0200 */
|
||||||
|
// uint32_t icpend[32]; /* 0x0280 */
|
||||||
|
// uint32_t isactive[32]; /* 0x0300 */
|
||||||
|
// uint32_t icactive[32]; /* 0x0380 */
|
||||||
|
// uint32_t ipriorityrn[8]; /* 0x0400 */
|
||||||
|
// uint32_t res1[504]; /* 0x0420 */
|
||||||
|
// uint32_t icfgrn_ro; /* 0x0C00 */
|
||||||
|
// uint32_t icfgrn_rw; /* 0x0C04 */
|
||||||
|
// uint32_t res2[62]; /* 0x0C08 */
|
||||||
|
// uint32_t igrpmod[64]; /* 0x0D00 */
|
||||||
|
// uint32_t nsac; /* 0x0E00 */
|
||||||
|
// uint32_t res11[11391]; /* 0x0E04 */
|
||||||
|
// uint32_t miscstatsr; /* 0xC000 */
|
||||||
|
// uint32_t res3[31]; /* 0xC004 */
|
||||||
|
// uint32_t ppisr; /* 0xC080 */
|
||||||
|
// uint32_t res4[4062]; /* 0xC084 */
|
||||||
|
// };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GIC Distributor Register Map
|
||||||
|
* ARM Generic Interrupt Controller (Architecture version 3.0)
|
||||||
|
* Architecture Specification (Issue C)
|
||||||
|
* Chapter 8 Programmers' Model - Table 8-25
|
||||||
|
*/
|
||||||
|
#define GIC_DIST_CTLR 0x000
|
||||||
|
#define GIC_DIST_TYPER 0x004
|
||||||
|
#define GIC_DIST_IIDR 0x008
|
||||||
|
#define GIC_DIST_STATUSR 0x010
|
||||||
|
#define GIC_DIST_SETSPI_NSR 0x040
|
||||||
|
#define GIC_DIST_CLRSPI_NSR 0x048
|
||||||
|
#define GIC_DIST_SETSPI_SR 0x050
|
||||||
|
#define GIC_DIST_CLRSPI_SR 0x058
|
||||||
|
#define GIC_DIST_IGROUPR0 0x080
|
||||||
|
#define GIC_DIST_IGROUPR1 0x084
|
||||||
|
#define GIC_DIST_IGROUPRN 0x0FC
|
||||||
|
#define GIC_DIST_ISENABLER0 0x100
|
||||||
|
#define GIC_DIST_ISENABLER1 0x104
|
||||||
|
#define GIC_DIST_ISENABLERN 0x17C
|
||||||
|
#define GIC_DIST_ICENABLER0 0x180
|
||||||
|
#define GIC_DIST_ICENABLER1 0x184
|
||||||
|
#define GIC_DIST_ICENABLERN 0x1fC
|
||||||
|
#define GIC_DIST_ISPENDR0 0x200
|
||||||
|
#define GIC_DIST_ISPENDR1 0x204
|
||||||
|
#define GIC_DIST_ISPENDRN 0x27C
|
||||||
|
#define GIC_DIST_ICPENDR0 0x280
|
||||||
|
#define GIC_DIST_ICPENDR1 0x284
|
||||||
|
#define GIC_DIST_ICPENDRN 0x2FC
|
||||||
|
#define GIC_DIST_ISACTIVER0 0x300
|
||||||
|
#define GIC_DIST_ISACTIVER1 0x304
|
||||||
|
#define GIC_DIST_ISACTIVERN 0x37C
|
||||||
|
#define GIC_DIST_ICACTIVER0 0x380
|
||||||
|
#define GIC_DIST_ICACTIVER1 0x384
|
||||||
|
#define GIC_DIST_ICACTIVERN 0x3FC
|
||||||
|
#define GIC_DIST_IPRIORITYR0 0x400
|
||||||
|
#define GIC_DIST_IPRIORITYR7 0x41C
|
||||||
|
#define GIC_DIST_IPRIORITYR8 0x420
|
||||||
|
#define GIC_DIST_IPRIORITYRN 0x7F8
|
||||||
|
#define GIC_DIST_ITARGETSR0 0x800
|
||||||
|
#define GIC_DIST_ITARGETSR7 0x81C
|
||||||
|
#define GIC_DIST_ITARGETSR8 0x820
|
||||||
|
#define GIC_DIST_ITARGETSRN 0xBF8
|
||||||
|
#define GIC_DIST_ICFGR0 0xC00
|
||||||
|
#define GIC_DIST_ICFGRN 0xCFC
|
||||||
|
#define GIC_DIST_IGRPMODR0 0xD00
|
||||||
|
#define GIC_DIST_IGRPMODRN 0xD7C
|
||||||
|
#define GIC_DIST_NSACR0 0xE00
|
||||||
|
#define GIC_DIST_NSACRN 0xEFC
|
||||||
|
#define GIC_DIST_SGIR 0xF00
|
||||||
|
#define GIC_DIST_CPENDSGIR0 0xF10
|
||||||
|
#define GIC_DIST_CPENDSGIRN 0xF1C
|
||||||
|
#define GIC_DIST_SPENDSGIR0 0xF20
|
||||||
|
#define GIC_DIST_SPENDSGIRN 0xF2C
|
||||||
|
#define GIC_DIST_IROUTER0 0x6100
|
||||||
|
#define GIC_DIST_IROUTERN 0x7FD8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ARM Generic Interrupt Controller (Architecture version 3.0)
|
||||||
|
* Architecture Specification (Issue C)
|
||||||
|
* 8.9.21: Software Generated Interrupt Register, GICD_SGIR
|
||||||
|
*/
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_FILTER_SHIFT 24
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_FILTER_MASK 0x3 << GIC_DIST_SGI_TARGET_LIST_FILTER_SHIFT
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_SPEC 0
|
||||||
|
#define GIC_DIST_SGI_TARGET_LIST_OTHERS 1
|
||||||
|
#define GIC_DIST_SGI_TARGET_SELF 2
|
||||||
|
|
||||||
|
#define GIC_DIST_SGI_CPU_TARGET_LIST_SHIFT 16
|
||||||
|
#define GIC_DIST_SGI_CPU_TARGET_LIST_MASK 0xFF << GIC_DIST_SGI_CPU_TARGET_LIST_SHIFT
|
||||||
|
|
||||||
|
#define GIC_DIST_SGI_INTID_MASK 0xF
|
||||||
|
|
||||||
|
#define GICR_CTLR 0x000
|
||||||
|
#define GICR_IIDR 0x004
|
||||||
|
#define GICR_TYPER 0x008
|
||||||
|
#define GICR_WAKER 0x014
|
||||||
|
|
||||||
|
#define GICR_IGROUPR0 0x10080
|
||||||
|
#define GICR_ISENABLER0 0x10100
|
||||||
|
#define GICR_ICENABLER0 0x10180
|
||||||
|
#define GICR_ICACTIVER0 0x10380
|
||||||
|
#define GICR_IPRIORITYR0 0x10400
|
||||||
|
#define GICR_IPRIORITYRN 0x1041C
|
||||||
|
#define GICR_ICFGR1 0x10c04
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/// Virtual distributor registers
|
||||||
|
struct gic_dist_map *dist;
|
||||||
|
/// Virtual redistributor registers for control and physical LPIs
|
||||||
|
struct gic_redist_map *redist;
|
||||||
|
/// Virtual redistributor for SGI and PPIs
|
||||||
|
// struct gic_redist_sgi_ppi_map *sgi;
|
||||||
|
} vgic_reg_t;
|
||||||
|
|
||||||
|
static inline bool vgic_dist_is_enabled(struct gic_dist_map *gic_dist) {
|
||||||
|
return gic_dist->ctlr & GIC_500_GRP1_NS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vgic_dist_enable(struct gic_dist_map *gic_dist) {
|
||||||
|
gic_dist->ctlr |= GIC_500_GRP1_NS | GIC_500_ARE_S;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vgic_dist_disable(struct gic_dist_map *gic_dist)
|
||||||
|
{
|
||||||
|
gic_dist->ctlr &= ~(GIC_500_GRP1_NS | GIC_500_ARE_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct gic_dist_map *vgic_get_dist(void *registers)
|
||||||
|
{
|
||||||
|
assert(registers);
|
||||||
|
return ((vgic_reg_t *) registers)->dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct gic_redist_map *vgic_get_redist(void *registers)
|
||||||
|
{
|
||||||
|
assert(registers);
|
||||||
|
return ((vgic_reg_t *) registers)->redist;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "../util/util.h"
|
||||||
|
|
||||||
|
/* The ARM GIC architecture defines 16 SGIs (0 - 7 is recommended for non-secure
|
||||||
|
* state, 8 - 15 for secure state), 16 PPIs (interrupt 16 - 31) and 988 SPIs
|
||||||
|
* (32 - 1019). The interrupt IDs 1020 - 1023 are used for special purposes.
|
||||||
|
* GICv3.1 is not implemented here, it supports 64 additional PPIs (interrupt
|
||||||
|
* 1056 - 1119) and 1024 SPIs (interrupt 4096 – 5119). LPIs starting at
|
||||||
|
* interrupt 8192 are also not implemented here.
|
||||||
|
*/
|
||||||
|
#define NUM_SGI_VIRQS 16 // vCPU local SGI interrupts
|
||||||
|
#define NUM_PPI_VIRQS 16 // vCPU local PPI interrupts
|
||||||
|
#define NUM_VCPU_LOCAL_VIRQS (NUM_SGI_VIRQS + NUM_PPI_VIRQS)
|
||||||
|
|
||||||
|
/* Usually, VMs do not use all SPIs. To reduce the memory footprint, our vGIC
|
||||||
|
* implementation manages the SPIs in a fixed size slot list. 200 entries have
|
||||||
|
* been good trade-off that is sufficient for most systems.
|
||||||
|
*/
|
||||||
|
#define NUM_SLOTS_SPI_VIRQ 200
|
||||||
|
|
||||||
|
#define VIRQ_INVALID -1
|
||||||
|
|
||||||
|
typedef void (*irq_ack_fn_t)(uint64_t vcpu_id, int irq, void *cookie);
|
||||||
|
|
||||||
|
struct virq_handle {
|
||||||
|
int virq;
|
||||||
|
irq_ack_fn_t ack_fn;
|
||||||
|
void *ack_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ivanv: revisit
|
||||||
|
static inline void virq_ack(uint64_t vcpu_id, struct virq_handle *irq)
|
||||||
|
{
|
||||||
|
// printf("VGIC|INFO: Acking for vIRQ %d\n", irq->virq);
|
||||||
|
assert(irq->ack_fn);
|
||||||
|
irq->ack_fn(vcpu_id, irq->virq, irq->ack_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: A typical number of list registers supported by GIC is four, but not
|
||||||
|
* always. One particular way to probe the number of registers is to inject a
|
||||||
|
* dummy IRQ with seL4_ARM_VCPU_InjectIRQ(), using LR index high enough to be
|
||||||
|
* not supported by any target; the kernel will reply with the supported range
|
||||||
|
* of LR indexes.
|
||||||
|
*/
|
||||||
|
#define NUM_LIST_REGS 4
|
||||||
|
/* This is a rather arbitrary number, increase if needed. */
|
||||||
|
#define MAX_IRQ_QUEUE_LEN 64
|
||||||
|
#define IRQ_QUEUE_NEXT(_i) (((_i) + 1) & (MAX_IRQ_QUEUE_LEN - 1))
|
||||||
|
|
||||||
|
static_assert((MAX_IRQ_QUEUE_LEN & (MAX_IRQ_QUEUE_LEN - 1)) == 0,
|
||||||
|
"IRQ ring buffer size must be power of two");
|
||||||
|
|
||||||
|
// @ivanv: make a note of all datastructure invariants
|
||||||
|
struct irq_queue {
|
||||||
|
struct virq_handle *irqs[MAX_IRQ_QUEUE_LEN]; /* circular buffer */
|
||||||
|
uint64_t head;
|
||||||
|
uint64_t tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* vCPU specific interrupt context */
|
||||||
|
typedef struct vgic_vcpu {
|
||||||
|
/* Mirrors the GIC's vCPU list registers */
|
||||||
|
struct virq_handle lr_shadow[NUM_LIST_REGS];
|
||||||
|
/* Queue for IRQs that don't fit in the GIC's vCPU list registers */
|
||||||
|
struct irq_queue irq_queue;
|
||||||
|
/* vCPU local interrupts (SGI, PPI) */
|
||||||
|
struct virq_handle local_virqs[NUM_VCPU_LOCAL_VIRQS];
|
||||||
|
} vgic_vcpu_t;
|
||||||
|
|
||||||
|
/* GIC global interrupt context */
|
||||||
|
typedef struct vgic {
|
||||||
|
/* virtual registers */
|
||||||
|
void *registers;
|
||||||
|
/* registered global interrupts (SPI) */
|
||||||
|
struct virq_handle vspis[NUM_SLOTS_SPI_VIRQ];
|
||||||
|
/* vCPU specific interrupt context */
|
||||||
|
vgic_vcpu_t vgic_vcpu[GUEST_NUM_VCPUS];
|
||||||
|
} vgic_t;
|
||||||
|
|
||||||
|
static inline vgic_vcpu_t *get_vgic_vcpu(vgic_t *vgic, int vcpu_id)
|
||||||
|
{
|
||||||
|
assert(vgic);
|
||||||
|
assert((vcpu_id >= 0) && (vcpu_id < ARRAY_SIZE(vgic->vgic_vcpu)));
|
||||||
|
return &(vgic->vgic_vcpu[vcpu_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct virq_handle *virq_get_sgi_ppi(vgic_t *vgic, uint64_t vcpu_id, int virq)
|
||||||
|
{
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
assert((virq >= 0) && (virq < ARRAY_SIZE(vgic_vcpu->local_virqs)));
|
||||||
|
return &vgic_vcpu->local_virqs[virq];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct virq_handle *virq_find_spi_irq_data(struct vgic *vgic, int virq)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(vgic->vspis); i++) {
|
||||||
|
if (vgic->vspis[i].virq != VIRQ_INVALID && vgic->vspis[i].virq == virq) {
|
||||||
|
return &vgic->vspis[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct virq_handle *virq_find_irq_data(struct vgic *vgic, uint64_t vcpu_id, int virq)
|
||||||
|
{
|
||||||
|
if (virq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
return virq_get_sgi_ppi(vgic, vcpu_id, virq);
|
||||||
|
}
|
||||||
|
return virq_find_spi_irq_data(vgic, virq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool virq_spi_add(vgic_t *vgic, struct virq_handle *virq_data)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(vgic->vspis); i++) {
|
||||||
|
if (vgic->vspis[i].virq == VIRQ_INVALID) {
|
||||||
|
vgic->vspis[i] = *virq_data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_VMM_ERR("Could not add SPI IRQ (0x%lx), ran out of slots.\n", virq_data->virq);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool virq_sgi_ppi_add(uint64_t vcpu_id, vgic_t *vgic, struct virq_handle *virq_data)
|
||||||
|
{
|
||||||
|
// @ivanv: revisit
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
int irq = virq_data->virq;
|
||||||
|
assert((irq >= 0) && (irq < ARRAY_SIZE(vgic_vcpu->local_virqs)));
|
||||||
|
struct virq_handle *slot = &vgic_vcpu->local_virqs[irq];
|
||||||
|
if (slot->virq != VIRQ_INVALID) {
|
||||||
|
LOG_VMM_ERR("IRQ %d already registered on VCPU %u", virq_data->virq, vcpu_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*slot = *virq_data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool virq_add(uint64_t vcpu_id, vgic_t *vgic, struct virq_handle *virq_handle)
|
||||||
|
{
|
||||||
|
if (virq_handle->virq < NUM_VCPU_LOCAL_VIRQS) {
|
||||||
|
return virq_sgi_ppi_add(vcpu_id, vgic, virq_handle);
|
||||||
|
}
|
||||||
|
return virq_spi_add(vgic, virq_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool vgic_irq_enqueue(vgic_t *vgic, uint64_t vcpu_id, struct virq_handle *irq)
|
||||||
|
{
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
struct irq_queue *q = &vgic_vcpu->irq_queue;
|
||||||
|
|
||||||
|
// @ivanv: add "unlikely" call
|
||||||
|
if (IRQ_QUEUE_NEXT(q->tail) == q->head) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
q->irqs[q->tail] = irq;
|
||||||
|
q->tail = IRQ_QUEUE_NEXT(q->tail);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct virq_handle *vgic_irq_dequeue(vgic_t *vgic, uint64_t vcpu_id)
|
||||||
|
{
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
struct irq_queue *q = &vgic_vcpu->irq_queue;
|
||||||
|
struct virq_handle *virq = NULL;
|
||||||
|
|
||||||
|
if (q->head != q->tail) {
|
||||||
|
virq = q->irqs[q->head];
|
||||||
|
q->head = IRQ_QUEUE_NEXT(q->head);
|
||||||
|
}
|
||||||
|
|
||||||
|
return virq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int vgic_find_empty_list_reg(vgic_t *vgic, uint64_t vcpu_id)
|
||||||
|
{
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(vgic_vcpu->lr_shadow); i++) {
|
||||||
|
if (vgic_vcpu->lr_shadow[i].virq == VIRQ_INVALID) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool vgic_vcpu_load_list_reg(vgic_t *vgic, uint64_t vcpu_id, int idx, int group, struct virq_handle *virq)
|
||||||
|
{
|
||||||
|
vgic_vcpu_t *vgic_vcpu = get_vgic_vcpu(vgic, vcpu_id);
|
||||||
|
assert(vgic_vcpu);
|
||||||
|
assert((idx >= 0) && (idx < ARRAY_SIZE(vgic_vcpu->lr_shadow)));
|
||||||
|
// @ivanv: why is the priority 0?
|
||||||
|
microkit_vcpu_arm_inject_irq(GUEST_ID, virq->virq, 0, group, idx);
|
||||||
|
vgic_vcpu->lr_shadow[idx] = *virq;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -0,0 +1,451 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
|
||||||
|
* Copyright 2022, UNSW (ABN 57 195 873 179)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
#include "util/util.h"
|
||||||
|
#include "vgic/vgic.h"
|
||||||
|
#include "smc.h"
|
||||||
|
#include "fault.h"
|
||||||
|
#include "hsr.h"
|
||||||
|
#include "vmm.h"
|
||||||
|
#include "arch/aarch64/linux.h"
|
||||||
|
|
||||||
|
/* Data for the guest's kernel image. */
|
||||||
|
extern char _guest_kernel_image[];
|
||||||
|
extern char _guest_kernel_image_end[];
|
||||||
|
/* Data for the device tree to be passed to the kernel. */
|
||||||
|
extern char _guest_dtb_image[];
|
||||||
|
extern char _guest_dtb_image_end[];
|
||||||
|
/* Data for the initial RAM disk to be passed to the kernel. */
|
||||||
|
extern char _guest_initrd_image[];
|
||||||
|
extern char _guest_initrd_image_end[];
|
||||||
|
/* seL4CP will set this variable to the start of the guest RAM memory region. */
|
||||||
|
uintptr_t guest_ram_vaddr;
|
||||||
|
|
||||||
|
/* @jade: find a better number */
|
||||||
|
#define MAX_IRQ_CH 32
|
||||||
|
int passthrough_irq_map[MAX_IRQ_CH];
|
||||||
|
|
||||||
|
// @ivanv: document where these come from
|
||||||
|
#define SYSCALL_PA_TO_IPA 65
|
||||||
|
#define SYSCALL_NOP 67
|
||||||
|
|
||||||
|
static bool handle_unknown_syscall(microkit_msginfo msginfo)
|
||||||
|
{
|
||||||
|
// @ivanv: should print out the name of the VM the fault came from.
|
||||||
|
uint64_t syscall = microkit_mr_get(seL4_UnknownSyscall_Syscall);
|
||||||
|
uint64_t fault_ip = microkit_mr_get(seL4_UnknownSyscall_FaultIP);
|
||||||
|
|
||||||
|
LOG_VMM("Received syscall 0x%lx\n", syscall);
|
||||||
|
switch (syscall) {
|
||||||
|
case SYSCALL_PA_TO_IPA:
|
||||||
|
// @ivanv: why do we not do anything here?
|
||||||
|
// @ivanv, how to get the physical address to translate?
|
||||||
|
LOG_VMM("Received PA translation syscall\n");
|
||||||
|
break;
|
||||||
|
case SYSCALL_NOP:
|
||||||
|
LOG_VMM("Received NOP syscall\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("Unknown syscall: syscall number: 0x%lx, PC: 0x%lx\n", syscall, fault_ip);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
seL4_UserContext regs;
|
||||||
|
seL4_Error err = seL4_TCB_ReadRegisters(BASE_VM_TCB_CAP + GUEST_ID, false, 0, SEL4_USER_CONTEXT_SIZE, ®s);
|
||||||
|
assert(!err);
|
||||||
|
|
||||||
|
return fault_advance_vcpu(®s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_vppi_event()
|
||||||
|
{
|
||||||
|
uint64_t ppi_irq = microkit_mr_get(seL4_VPPIEvent_IRQ);
|
||||||
|
// We directly inject the interrupt assuming it has been previously registered.
|
||||||
|
// If not the interrupt will dropped by the VM.
|
||||||
|
bool success = vgic_inject_irq(GUEST_VCPU_ID, ppi_irq);
|
||||||
|
if (!success) {
|
||||||
|
// @ivanv, make a note that when having a lot of printing on it can cause this error
|
||||||
|
LOG_VMM_ERR("VPPI IRQ %lu dropped on vCPU %d\n", ppi_irq, GUEST_VCPU_ID);
|
||||||
|
// Acknowledge to unmask it as our guest will not use the interrupt
|
||||||
|
// @ivanv: We're going to assume that we only have one VCPU and that the
|
||||||
|
// cap is the base one.
|
||||||
|
microkit_vcpu_arm_ack_vppi(GUEST_ID, ppi_irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_vcpu_fault(microkit_msginfo msginfo, uint64_t vcpu_id)
|
||||||
|
{
|
||||||
|
uint32_t hsr = microkit_mr_get(seL4_VCPUFault_HSR);
|
||||||
|
uint64_t hsr_ec_class = HSR_EXCEPTION_CLASS(hsr);
|
||||||
|
switch (hsr_ec_class) {
|
||||||
|
case HSR_SMC_64_EXCEPTION:
|
||||||
|
return handle_smc(vcpu_id, hsr);
|
||||||
|
case HSR_WFx_EXCEPTION:
|
||||||
|
// If we get a WFI exception, we just do nothing in the VMM.
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("unknown SMC exception, EC class: 0x%lx, HSR: 0x%lx\n", hsr_ec_class, hsr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_user_exception(microkit_msginfo msginfo)
|
||||||
|
{
|
||||||
|
// @ivanv: print out VM name/vCPU id when we have multiple VMs
|
||||||
|
uint64_t fault_ip = microkit_mr_get(seL4_UserException_FaultIP);
|
||||||
|
uint64_t number = microkit_mr_get(seL4_UserException_Number);
|
||||||
|
LOG_VMM_ERR("Invalid instruction fault at IP: 0x%lx, number: 0x%lx", fault_ip, number);
|
||||||
|
|
||||||
|
// Dump registers
|
||||||
|
seL4_UserContext regs = {0};
|
||||||
|
seL4_Error ret = seL4_TCB_ReadRegisters(BASE_VM_TCB_CAP + GUEST_ID, false, 0, SEL4_USER_CONTEXT_SIZE, ®s);
|
||||||
|
if (ret != seL4_NoError) {
|
||||||
|
printf("Failure reading regs, error %d", ret);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
print_tcb_regs(®s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WORDLE_WORD_SIZE 5
|
||||||
|
#define WORDLE_BUFFER_ADDR 0x50000000
|
||||||
|
#define WORDLE_BUFFER_SIZE (WORDLE_WORD_SIZE * sizeof(char))
|
||||||
|
#define WORDLE_SERVER_CHANNEL 1
|
||||||
|
|
||||||
|
char word[WORDLE_WORD_SIZE] = {0};
|
||||||
|
|
||||||
|
static bool handle_vm_fault()
|
||||||
|
{
|
||||||
|
uint64_t addr = microkit_mr_get(seL4_VMFault_Addr);
|
||||||
|
uint64_t fsr = microkit_mr_get(seL4_VMFault_FSR);
|
||||||
|
|
||||||
|
seL4_UserContext regs;
|
||||||
|
int err = seL4_TCB_ReadRegisters(BASE_VM_TCB_CAP + GUEST_ID, false, 0, SEL4_USER_CONTEXT_SIZE, ®s);
|
||||||
|
assert(err == seL4_NoError);
|
||||||
|
|
||||||
|
switch (addr) {
|
||||||
|
case WORDLE_BUFFER_ADDR...WORDLE_BUFFER_ADDR + WORDLE_BUFFER_SIZE: {
|
||||||
|
char character = fault_get_data(®s, fsr);
|
||||||
|
word[(addr - WORDLE_BUFFER_ADDR) / sizeof(char)] = character;
|
||||||
|
if (addr == WORDLE_BUFFER_ADDR + (WORDLE_BUFFER_SIZE - sizeof(char))) {
|
||||||
|
microkit_vcpu_stop(GUEST_ID);
|
||||||
|
microkit_msginfo msg = microkit_msginfo_new(0, WORDLE_WORD_SIZE);
|
||||||
|
for (int i = 0; i < WORDLE_WORD_SIZE; i++) {
|
||||||
|
microkit_mr_set(i, word[i]);
|
||||||
|
}
|
||||||
|
microkit_ppcall(WORDLE_SERVER_CHANNEL, msg);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return fault_advance_vcpu(®s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case GIC_DIST_PADDR...GIC_DIST_PADDR + GIC_DIST_SIZE:
|
||||||
|
return handle_vgic_dist_fault(GUEST_VCPU_ID, addr, fsr, ®s);
|
||||||
|
#if defined(GIC_V3)
|
||||||
|
/* Need to handle redistributor faults for GICv3 platforms. */
|
||||||
|
case GIC_REDIST_PADDR...GIC_REDIST_PADDR + GIC_REDIST_SIZE:
|
||||||
|
return handle_vgic_redist_fault(GUEST_VCPU_ID, addr, fsr, ®s);
|
||||||
|
#endif
|
||||||
|
default: {
|
||||||
|
uint64_t ip = microkit_mr_get(seL4_VMFault_IP);
|
||||||
|
uint64_t is_prefetch = seL4_GetMR(seL4_VMFault_PrefetchFault);
|
||||||
|
uint64_t is_write = (fsr & (1 << 6)) != 0;
|
||||||
|
LOG_VMM_ERR("unexpected memory fault on address: 0x%lx, FSR: 0x%lx, IP: 0x%lx, is_prefetch: %s, is_write: %s\n", addr, fsr, ip, is_prefetch ? "true" : "false", is_write ? "true" : "false");
|
||||||
|
print_tcb_regs(®s);
|
||||||
|
print_vcpu_regs(GUEST_ID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SGI_RESCHEDULE_IRQ 0
|
||||||
|
#define SGI_FUNC_CALL 1
|
||||||
|
#define PPI_VTIMER_IRQ 27
|
||||||
|
|
||||||
|
static void vppi_event_ack(uint64_t vcpu_id, int irq, void *cookie)
|
||||||
|
{
|
||||||
|
microkit_vcpu_arm_ack_vppi(GUEST_ID, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sgi_ack(uint64_t vcpu_id, int irq, void *cookie) {}
|
||||||
|
|
||||||
|
static void serial_ack(uint64_t vcpu_id, int irq, void *cookie) {
|
||||||
|
/*
|
||||||
|
* For now we by default simply ack the serial IRQ, we have not
|
||||||
|
* come across a case yet where more than this needs to be done.
|
||||||
|
*/
|
||||||
|
microkit_irq_ack(SERIAL_IRQ_CH);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void passthrough_device_ack(uint64_t vcpu_id, int irq, void *cookie) {
|
||||||
|
microkit_channel irq_ch = (microkit_channel)(int64_t)cookie;
|
||||||
|
microkit_irq_ack(irq_ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_passthrough_irq(int irq, microkit_channel irq_ch) {
|
||||||
|
LOG_VMM("Register passthrough IRQ %d (channel: 0x%lx)\n", irq, irq_ch);
|
||||||
|
assert(irq_ch < MAX_IRQ_CH);
|
||||||
|
passthrough_irq_map[irq_ch] = irq;
|
||||||
|
|
||||||
|
int err = vgic_register_irq(GUEST_VCPU_ID, irq, &passthrough_device_ack, (void *)(int64_t)irq_ch);
|
||||||
|
if (!err) {
|
||||||
|
LOG_VMM_ERR("Failed to register IRQ %d\n", irq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool guest_init_images(void) {
|
||||||
|
// First we inspect the kernel image header to confirm it is a valid image
|
||||||
|
// and to determine where in memory to place the image. Currently this
|
||||||
|
// process assumes the guest is the Linux kernel.
|
||||||
|
struct linux_image_header *image_header = (struct linux_image_header *) &_guest_kernel_image;
|
||||||
|
assert(image_header->magic == LINUX_IMAGE_MAGIC);
|
||||||
|
if (image_header->magic != LINUX_IMAGE_MAGIC) {
|
||||||
|
LOG_VMM_ERR("Linux kernel image magic check failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Copy the guest kernel image into the right location
|
||||||
|
uint64_t kernel_image_size = _guest_kernel_image_end - _guest_kernel_image;
|
||||||
|
uint64_t kernel_image_vaddr = guest_ram_vaddr + image_header->text_offset;
|
||||||
|
// This check is because the Linux kernel image requires to be placed at text_offset of
|
||||||
|
// a 2MiB aligned base address anywhere in usable system RAM and called there.
|
||||||
|
// In this case, we place the image at the text_offset of the start of the guest's RAM,
|
||||||
|
// so we need to make sure that the start of guest RAM is 2MiB aligned.
|
||||||
|
//
|
||||||
|
// @ivanv: Ideally this check would be done at build time, we have all the information
|
||||||
|
// we need at build time to enforce this.
|
||||||
|
assert((guest_ram_vaddr & ((1 << 20) - 1)) == 0);
|
||||||
|
LOG_VMM("Copying guest kernel image to 0x%x (0x%x bytes)\n", kernel_image_vaddr, kernel_image_size);
|
||||||
|
memcpy((char *)kernel_image_vaddr, _guest_kernel_image, kernel_image_size);
|
||||||
|
// Copy the guest device tree blob into the right location
|
||||||
|
uint64_t dtb_image_size = _guest_dtb_image_end - _guest_dtb_image;
|
||||||
|
LOG_VMM("Copying guest DTB to 0x%x (0x%x bytes)\n", GUEST_DTB_VADDR, dtb_image_size);
|
||||||
|
memcpy((char *)GUEST_DTB_VADDR, _guest_dtb_image, dtb_image_size);
|
||||||
|
// Copy the initial RAM disk into the right location
|
||||||
|
uint64_t initrd_image_size = _guest_initrd_image_end - _guest_initrd_image;
|
||||||
|
LOG_VMM("Copying guest initial RAM disk to 0x%x (0x%x bytes)\n", GUEST_INIT_RAM_DISK_VADDR, initrd_image_size);
|
||||||
|
memcpy((char *)GUEST_INIT_RAM_DISK_VADDR, _guest_initrd_image, initrd_image_size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void guest_start(void) {
|
||||||
|
// Initialise the virtual GIC driver
|
||||||
|
vgic_init();
|
||||||
|
#if defined(GIC_V2)
|
||||||
|
LOG_VMM("initialised virtual GICv2 driver\n");
|
||||||
|
#elif defined(GIC_V3)
|
||||||
|
LOG_VMM("initialised virtual GICv3 driver\n");
|
||||||
|
#else
|
||||||
|
#error "Unsupported GIC version"
|
||||||
|
#endif
|
||||||
|
bool err = vgic_register_irq(GUEST_VCPU_ID, PPI_VTIMER_IRQ, &vppi_event_ack, NULL);
|
||||||
|
if (!err) {
|
||||||
|
LOG_VMM_ERR("Failed to register vCPU virtual timer IRQ: 0x%lx\n", PPI_VTIMER_IRQ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = vgic_register_irq(GUEST_VCPU_ID, SGI_RESCHEDULE_IRQ, &sgi_ack, NULL);
|
||||||
|
if (!err) {
|
||||||
|
LOG_VMM_ERR("Failed to register vCPU SGI 0 IRQ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = vgic_register_irq(GUEST_VCPU_ID, SGI_FUNC_CALL, &sgi_ack, NULL);
|
||||||
|
if (!err) {
|
||||||
|
LOG_VMM_ERR("Failed to register vCPU SGI 1 IRQ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the IRQ for the passthrough serial
|
||||||
|
register_passthrough_irq(79, 2);
|
||||||
|
|
||||||
|
seL4_UserContext regs = {0};
|
||||||
|
regs.x0 = GUEST_DTB_VADDR;
|
||||||
|
regs.spsr = 5; // PMODE_EL1h
|
||||||
|
// Read the entry point and set it to the program counter
|
||||||
|
struct linux_image_header *image_header = (struct linux_image_header *) &_guest_kernel_image;
|
||||||
|
uint64_t kernel_image_vaddr = guest_ram_vaddr + image_header->text_offset;
|
||||||
|
regs.pc = kernel_image_vaddr;
|
||||||
|
// Set all the TCB registers
|
||||||
|
err = seL4_TCB_WriteRegisters(
|
||||||
|
BASE_VM_TCB_CAP + GUEST_ID,
|
||||||
|
false, // We'll explcitly start the guest below rather than in this call
|
||||||
|
0, // No flags
|
||||||
|
SEL4_USER_CONTEXT_SIZE, // Writing to x0, pc, and spsr // @ivanv: for some reason having the number of registers here does not work... (in this case 2)
|
||||||
|
®s
|
||||||
|
);
|
||||||
|
assert(!err);
|
||||||
|
// Set the PC to the kernel image's entry point and start the thread.
|
||||||
|
LOG_VMM("starting guest at 0x%lx, DTB at 0x%lx, initial RAM disk at 0x%lx\n",
|
||||||
|
regs.pc, regs.x0, GUEST_INIT_RAM_DISK_VADDR);
|
||||||
|
microkit_vcpu_restart(GUEST_ID, regs.pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SCTLR_EL1_UCI (1 << 26) /* Enable EL0 access to DC CVAU, DC CIVAC, DC CVAC,
|
||||||
|
and IC IVAU in AArch64 state */
|
||||||
|
#define SCTLR_EL1_C (1 << 2) /* Enable data and unified caches */
|
||||||
|
#define SCTLR_EL1_I (1 << 12) /* Enable instruction cache */
|
||||||
|
#define SCTLR_EL1_CP15BEN (1 << 5) /* AArch32 CP15 barrier enable */
|
||||||
|
#define SCTLR_EL1_UTC (1 << 15) /* Enable EL0 access to CTR_EL0 */
|
||||||
|
#define SCTLR_EL1_NTWI (1 << 16) /* WFI executed as normal */
|
||||||
|
#define SCTLR_EL1_NTWE (1 << 18) /* WFE executed as normal */
|
||||||
|
|
||||||
|
/* Disable MMU, SP alignment check, and alignment check */
|
||||||
|
/* A57 default value */
|
||||||
|
#define SCTLR_EL1_RES 0x30d00800 /* Reserved value */
|
||||||
|
#define SCTLR_EL1 ( SCTLR_EL1_RES | SCTLR_EL1_CP15BEN | SCTLR_EL1_UTC \
|
||||||
|
| SCTLR_EL1_NTWI | SCTLR_EL1_NTWE )
|
||||||
|
#define SCTLR_EL1_NATIVE (SCTLR_EL1 | SCTLR_EL1_C | SCTLR_EL1_I | SCTLR_EL1_UCI)
|
||||||
|
#define SCTLR_DEFAULT SCTLR_EL1_NATIVE
|
||||||
|
|
||||||
|
void guest_stop(void) {
|
||||||
|
LOG_VMM("Stopping guest\n");
|
||||||
|
microkit_vcpu_stop(GUEST_ID);
|
||||||
|
LOG_VMM("Stopped guest\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool guest_restart(void) {
|
||||||
|
LOG_VMM("Attempting to restart guest\n");
|
||||||
|
// First, stop the guest
|
||||||
|
microkit_vcpu_stop(GUEST_ID);
|
||||||
|
LOG_VMM("Stopped guest\n");
|
||||||
|
// Then, we need to clear all of RAM
|
||||||
|
LOG_VMM("Clearing guest RAM\n");
|
||||||
|
memset((char *)guest_ram_vaddr, 0, GUEST_RAM_SIZE);
|
||||||
|
// Copy back the images into RAM
|
||||||
|
bool success = guest_init_images();
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("Failed to initialise guest images\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Reset registers
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_SCTLR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_TTBR0, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_TTBR1, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_TCR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_MAIR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_AMAIR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CIDR, 0);
|
||||||
|
/* other system registers EL1 */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_ACTLR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CPACR, 0);
|
||||||
|
/* exception handling registers EL1 */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_AFSR0, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_AFSR1, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_ESR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_FAR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_ISR, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_VBAR, 0);
|
||||||
|
/* thread pointer/ID registers EL0/EL1 */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_TPIDR_EL1, 0);
|
||||||
|
#if CONFIG_MAX_NUM_NODES > 1
|
||||||
|
/* Virtualisation Multiprocessor ID Register */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_VMPIDR_EL2, 0);
|
||||||
|
#endif /* CONFIG_MAX_NUM_NODES > 1 */
|
||||||
|
/* general registers x0 to x30 have been saved by traps.S */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_SP_EL1, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_ELR_EL1, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_SPSR_EL1, 0); // 32-bit
|
||||||
|
/* generic timer registers, to be completed */
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CNTV_CTL, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CNTV_CVAL, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CNTVOFF, 0);
|
||||||
|
microkit_vcpu_arm_write_reg(GUEST_ID, seL4_VCPUReg_CNTKCTL_EL1, 0);
|
||||||
|
// Now we need to re-initialise all the VMM state
|
||||||
|
guest_start();
|
||||||
|
LOG_VMM("Restarted guest\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init(void)
|
||||||
|
{
|
||||||
|
// Initialise the VMM, the VCPU(s), and start the guest
|
||||||
|
LOG_VMM("starting \"%s\"\n", microkit_name);
|
||||||
|
// Place all the binaries in the right locations before starting the guest
|
||||||
|
bool success = guest_init_images();
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("Failed to initialise guest images\n");
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
// Initialise and start guest (setup VGIC, setup interrupts, TCB registers)
|
||||||
|
guest_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notified(microkit_channel ch)
|
||||||
|
{
|
||||||
|
switch (ch) {
|
||||||
|
case SERIAL_IRQ_CH: {
|
||||||
|
bool success = vgic_inject_irq(GUEST_VCPU_ID, SERIAL_IRQ);
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("IRQ %d dropped on vCPU %d\n", SERIAL_IRQ, GUEST_VCPU_ID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (passthrough_irq_map[ch]) {
|
||||||
|
bool success = vgic_inject_irq(GUEST_VCPU_ID, passthrough_irq_map[ch]);
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("IRQ %d dropped on vCPU %d\n", passthrough_irq_map[ch], GUEST_VCPU_ID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("Unexpected channel, ch: 0x%lx\n", ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seL4_Bool
|
||||||
|
fault(microkit_child id, microkit_msginfo msginfo, microkit_msginfo *reply_msginfo)
|
||||||
|
{
|
||||||
|
if (id != GUEST_ID) {
|
||||||
|
LOG_VMM_ERR("Unexpected faulting PD/VM with id %d\n", id);
|
||||||
|
return seL4_False;
|
||||||
|
}
|
||||||
|
// This is the primary fault handler for the guest, all faults that come
|
||||||
|
// from seL4 regarding the guest will need to be handled here.
|
||||||
|
uint64_t label = microkit_msginfo_get_label(msginfo);
|
||||||
|
bool success = false;
|
||||||
|
switch (label) {
|
||||||
|
case seL4_Fault_VMFault:
|
||||||
|
success = handle_vm_fault();
|
||||||
|
break;
|
||||||
|
case seL4_Fault_UnknownSyscall:
|
||||||
|
success = handle_unknown_syscall(msginfo);
|
||||||
|
break;
|
||||||
|
case seL4_Fault_UserException:
|
||||||
|
success = handle_user_exception(msginfo);
|
||||||
|
break;
|
||||||
|
case seL4_Fault_VGICMaintenance:
|
||||||
|
success = handle_vgic_maintenance(GUEST_VCPU_ID);
|
||||||
|
break;
|
||||||
|
case seL4_Fault_VCPUFault:
|
||||||
|
success = handle_vcpu_fault(msginfo, GUEST_VCPU_ID);
|
||||||
|
break;
|
||||||
|
case seL4_Fault_VPPIEvent:
|
||||||
|
success = handle_vppi_event();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VMM_ERR("unknown fault, stopping VM with ID %d\n", id);
|
||||||
|
microkit_vcpu_stop(id);
|
||||||
|
return seL4_False;
|
||||||
|
// @ivanv: print out the actual fault details
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
LOG_VMM_ERR("Failed to handle %s fault\n", fault_to_string(label));
|
||||||
|
return seL4_False;
|
||||||
|
}
|
||||||
|
|
||||||
|
*reply_msginfo = microkit_msginfo_new(0, 0);
|
||||||
|
|
||||||
|
return seL4_True;
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// @ivanv: ideally we would have none of these hardcoded values
|
||||||
|
// initrd, ram size come from the DTB
|
||||||
|
// We can probably add a node for the DTB addr and then use that.
|
||||||
|
// Part of the problem is that we might need multiple DTBs for the same example
|
||||||
|
// e.g one DTB for VMM one, one DTB for VMM two. we should be able to hide all
|
||||||
|
// of this in the build system to avoid doing any run-time DTB stuff.
|
||||||
|
#if defined(BOARD_qemu_virt_aarch64)
|
||||||
|
#define GUEST_DTB_VADDR 0x4f000000
|
||||||
|
#define GUEST_INIT_RAM_DISK_VADDR 0x4d700000
|
||||||
|
#define GUEST_RAM_SIZE 0x10000000
|
||||||
|
#elif defined(BOARD_rpi4b_hyp)
|
||||||
|
#define GUEST_DTB_VADDR 0x2e000000
|
||||||
|
#define GUEST_INIT_RAM_DISK_VADDR 0x2d700000
|
||||||
|
#define GUEST_RAM_SIZE 0x10000000
|
||||||
|
#elif defined(BOARD_odroidc2_hyp)
|
||||||
|
#define GUEST_DTB_VADDR 0x2f000000
|
||||||
|
#define GUEST_INIT_RAM_DISK_VADDR 0x2d700000
|
||||||
|
#define GUEST_RAM_SIZE 0x10000000
|
||||||
|
#elif defined(BOARD_odroidc4_hyp)
|
||||||
|
#define GUEST_DTB_VADDR 0x2f000000
|
||||||
|
#define GUEST_INIT_RAM_DISK_VADDR 0x2d700000
|
||||||
|
#define GUEST_RAM_SIZE 0x10000000
|
||||||
|
#elif defined(BOARD_imx8mm_evk_hyp)
|
||||||
|
#define GUEST_DTB_VADDR 0x4f000000
|
||||||
|
#define GUEST_INIT_RAM_DISK_VADDR 0x4d700000
|
||||||
|
#define GUEST_RAM_SIZE 0x10000000
|
||||||
|
#else
|
||||||
|
#error Need to define VM image address and DTB address
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(BOARD_qemu_virt_aarch64)
|
||||||
|
#define SERIAL_IRQ_CH 1
|
||||||
|
#define SERIAL_IRQ 33
|
||||||
|
#elif defined(BOARD_odroidc2_hyp) || defined(BOARD_odroidc4_hyp)
|
||||||
|
#define SERIAL_IRQ_CH 1
|
||||||
|
#define SERIAL_IRQ 225
|
||||||
|
#elif defined(BOARD_rpi4b_hyp)
|
||||||
|
#define SERIAL_IRQ_CH 1
|
||||||
|
#define SERIAL_IRQ 57
|
||||||
|
#elif defined(BOARD_imx8mm_evk_hyp)
|
||||||
|
#define SERIAL_IRQ_CH 1
|
||||||
|
#define SERIAL_IRQ 79
|
||||||
|
#else
|
||||||
|
#error Need to define serial interrupt
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool guest_restart(void);
|
||||||
|
void guest_stop(void);
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<system>
|
||||||
|
<!-- Define your system here -->
|
||||||
|
<memory_region name="uart" size="0x1_000" phys_addr="0x9_000_000"/>
|
||||||
|
<memory_region name="client_to_serial" size="0x1000" />
|
||||||
|
<memory_region name="serial_to_client" size="0x1000" />
|
||||||
|
|
||||||
|
<protection_domain name="wordle_server" priority="254" pp="true">
|
||||||
|
<program_image path="wordle_server.elf" />
|
||||||
|
</protection_domain>
|
||||||
|
|
||||||
|
<protection_domain name="serial_server" priority="254">
|
||||||
|
<program_image path="serial_server.elf" />
|
||||||
|
<map mr="uart" vaddr="0x2000000" perms="rw" cached="false" setvar_vaddr="uart_base_vaddr"/>
|
||||||
|
<map mr="serial_to_client" vaddr="0x4000000" perms="wr" setvar_vaddr="serial_to_client_vaddr"/>
|
||||||
|
<map mr="client_to_serial" vaddr="0x4001000" perms="r" setvar_vaddr="client_to_serial_vaddr"/>
|
||||||
|
<irq irq="33" id="1" />
|
||||||
|
</protection_domain>
|
||||||
|
|
||||||
|
<protection_domain name="client" priority="253" pp="true">
|
||||||
|
<program_image path="client.elf" />
|
||||||
|
<map mr="serial_to_client" vaddr="0x4000000" perms="r" setvar_vaddr="serial_to_client_vaddr"/>
|
||||||
|
<map mr="client_to_serial" vaddr="0x4001000" perms="rw" setvar_vaddr="client_to_serial_vaddr"/>
|
||||||
|
</protection_domain>
|
||||||
|
|
||||||
|
<channel>
|
||||||
|
<end pd="client" id="1" />
|
||||||
|
<end pd="serial_server" id="2" />
|
||||||
|
</channel>
|
||||||
|
|
||||||
|
<channel>
|
||||||
|
<end pd="client" id="2" />
|
||||||
|
<end pd="wordle_server" id="1" />
|
||||||
|
</channel>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is what the virtual machine will use as its "RAM".
|
||||||
|
Remember it does not know it is a VM and so will expect a
|
||||||
|
block of contigious memory as RAM.
|
||||||
|
-->
|
||||||
|
<memory_region name="guest_ram" size="0x10000000" page_size="0x200_000"
|
||||||
|
phys_addr="0x40000000" />
|
||||||
|
<!-- Create a memory region for the ethernet device -->
|
||||||
|
<memory_region name="ethernet" size="0x1000" phys_addr="0xa003000" />
|
||||||
|
<!--
|
||||||
|
Create a memory region for the GIC vCPU, this is part of
|
||||||
|
ARM's hardware virtualisation, I will not go into detail here,
|
||||||
|
but it is necessary for the virtual machine to function.
|
||||||
|
-->
|
||||||
|
<memory_region name="gic_vcpu" size="0x1000" phys_addr="0x8040000" />
|
||||||
|
|
||||||
|
<!-- Create a VMM protection domain -->
|
||||||
|
<protection_domain name="vmm" priority="101">
|
||||||
|
<program_image path="vmm.elf" />
|
||||||
|
<!--
|
||||||
|
Map in the virtual machine's RAM region as the VMM needs
|
||||||
|
access to it as well for starting and setting up the VM.
|
||||||
|
-->
|
||||||
|
<map mr="guest_ram" vaddr="0x40000000" perms="rw"
|
||||||
|
setvar_vaddr="guest_ram_vaddr" />
|
||||||
|
<!--
|
||||||
|
Create the virtual machine, the `id` is used for the
|
||||||
|
VMM to refer to the VM. Similar to channels and IRQs
|
||||||
|
-->
|
||||||
|
<virtual_machine name="linux" priority="100">
|
||||||
|
<vcpu id="0" />
|
||||||
|
<map mr="guest_ram" vaddr="0x40000000" perms="rwx" />
|
||||||
|
<map mr="ethernet" vaddr="0xa003000" perms="rw" cached="false" />
|
||||||
|
<map mr="uart" vaddr="0x9000000" perms="rw" cached="false" />
|
||||||
|
<map mr="gic_vcpu" vaddr="0x8010000" perms="rw" cached="false" />
|
||||||
|
</virtual_machine>
|
||||||
|
<!--
|
||||||
|
We want the VMM to receive the ethernet interrupts, which it
|
||||||
|
will then deliver to the VM
|
||||||
|
-->
|
||||||
|
<irq irq="79" id="2" trigger="edge" />
|
||||||
|
</protection_domain>
|
||||||
|
|
||||||
|
<channel>
|
||||||
|
<end pd="vmm" id="1" />
|
||||||
|
<end pd="wordle_server" id="2" />
|
||||||
|
</channel>
|
||||||
|
</system>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <microkit.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "printf.h"
|
||||||
|
#include "wordle.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we initialise the word to "hello", but later in the tutorial
|
||||||
|
* we will actually randomise the word the user is guessing.
|
||||||
|
*/
|
||||||
|
char word[WORD_LENGTH] = { 'h', 'e', 'l', 'l', 'o' };
|
||||||
|
|
||||||
|
#define CLIENT_CHANNEL 1
|
||||||
|
#define VMM_CHANNEL 2
|
||||||
|
|
||||||
|
bool is_character_in_word(char *word, int ch) {
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
if (word[i] == ch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum character_state char_to_state(int ch, char *word, uint64_t index) {
|
||||||
|
if (ch == word[index]) {
|
||||||
|
return CORRECT_PLACEMENT;
|
||||||
|
} else if (is_character_in_word(word, ch)) {
|
||||||
|
return INCORRECT_PLACEMENT;
|
||||||
|
} else {
|
||||||
|
return INCORRECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void) {
|
||||||
|
microkit_dbg_puts("WORDLE SERVER: starting\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void notified(microkit_channel channel) {}
|
||||||
|
|
||||||
|
microkit_msginfo protected(microkit_channel channel, microkit_msginfo msginfo)
|
||||||
|
{
|
||||||
|
switch (channel) {
|
||||||
|
case CLIENT_CHANNEL:
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
char ch = microkit_mr_get(i);
|
||||||
|
microkit_mr_set(i, char_to_state(ch, word, i));
|
||||||
|
}
|
||||||
|
return microkit_msginfo_new(0, WORD_LENGTH);
|
||||||
|
break;
|
||||||
|
case VMM_CHANNEL:
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
word[i] = microkit_mr_get(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
microkit_dbg_puts("ERROR!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return microkit_msginfo_new(0, 0);
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
ME_2150/microkit_tutorial/microkit_tutorial/tutorial/build/wordle_server.elf
Executable file
BIN
ME_2150/microkit_tutorial/microkit_tutorial/tutorial/build/wordle_server.elf
Executable file
Binary file not shown.
Binary file not shown.
@ -10,6 +10,7 @@ uintptr_t client_to_serial_vaddr;
|
|||||||
#define MOVE_CURSOR_UP "\033[5A"
|
#define MOVE_CURSOR_UP "\033[5A"
|
||||||
#define CLEAR_TERMINAL_BELOW_CURSOR "\033[0J"
|
#define CLEAR_TERMINAL_BELOW_CURSOR "\033[0J"
|
||||||
#define SERIAL_SERVER_CHANNEL_ID 1
|
#define SERIAL_SERVER_CHANNEL_ID 1
|
||||||
|
#define WORDLE_CHANNEL 2
|
||||||
|
|
||||||
#define INVALID_CHAR (-1)
|
#define INVALID_CHAR (-1)
|
||||||
|
|
||||||
@ -30,6 +31,15 @@ void wordle_server_send() {
|
|||||||
// After doing the PPC, the Wordle server should have updated
|
// After doing the PPC, the Wordle server should have updated
|
||||||
// the message-registers containing the state of each character.
|
// the message-registers containing the state of each character.
|
||||||
// Look at the message registers and update the `table` accordingly.
|
// Look at the message registers and update the `table` accordingly.
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
microkit_mr_set(i, table[curr_row][i].ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
microkit_ppcall(WORDLE_CHANNEL, microkit_msginfo_new(0, WORD_LENGTH));
|
||||||
|
|
||||||
|
for (int i = 0; 1 < WORD_LENGTH; i++) {
|
||||||
|
table[curr_row][i].state = microkit_mr_get(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void serial_send(char *str) {
|
void serial_send(char *str) {
|
||||||
|
|||||||
@ -18,9 +18,19 @@
|
|||||||
<map mr="client_to_serial" vaddr="0x4_001_000" perms="rw" setvar_vaddr="client_to_serial_vaddr" />
|
<map mr="client_to_serial" vaddr="0x4_001_000" perms="rw" setvar_vaddr="client_to_serial_vaddr" />
|
||||||
</protection_domain>
|
</protection_domain>
|
||||||
|
|
||||||
|
<protection_domain name="wordle_server" priority="254" pp="true">
|
||||||
|
<program_image path="wordle_server.elf" />
|
||||||
|
</protection_domain>
|
||||||
|
|
||||||
<channel>
|
<channel>
|
||||||
<end pd="serial_server" id="2" />
|
<end pd="serial_server" id="2" />
|
||||||
<end pd="client" id="1" />
|
<end pd="client" id="1" />
|
||||||
</channel>
|
</channel>
|
||||||
|
|
||||||
|
<channel>
|
||||||
|
<end pd="client" id="2" />
|
||||||
|
<end pd="wordle_server" id="1" />
|
||||||
|
</channel>
|
||||||
|
|
||||||
</system>
|
</system>
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,9 @@
|
|||||||
* Here we initialise the word to "hello", but later in the tutorial
|
* Here we initialise the word to "hello", but later in the tutorial
|
||||||
* we will actually randomise the word the user is guessing.
|
* we will actually randomise the word the user is guessing.
|
||||||
*/
|
*/
|
||||||
|
#define CLIENT_CHANNEL 1
|
||||||
|
#define VMM_CHANNEL 2
|
||||||
|
|
||||||
char word[WORD_LENGTH] = { 'h', 'e', 'l', 'l', 'o' };
|
char word[WORD_LENGTH] = { 'h', 'e', 'l', 'l', 'o' };
|
||||||
|
|
||||||
bool is_character_in_word(char *word, int ch) {
|
bool is_character_in_word(char *word, int ch) {
|
||||||
@ -35,4 +38,25 @@ void init(void) {
|
|||||||
microkit_dbg_puts("WORDLE SERVER: starting\n");
|
microkit_dbg_puts("WORDLE SERVER: starting\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void notified(microkit_channel channel) {}
|
microkit_msginfo protected(microkit_channel channel, microkit_msginfo msginfo)
|
||||||
|
{
|
||||||
|
switch (channel) {
|
||||||
|
case CLIENT_CHANNEL:
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
char ch = microkit_mr_get(i);
|
||||||
|
microkit_mr_set(i, char_to_state(ch, word, i));
|
||||||
|
}
|
||||||
|
return microkit_msginfo_new(0, WORD_LENGTH);
|
||||||
|
break;
|
||||||
|
case VMM_CHANNEL:
|
||||||
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||||
|
word[i] = microkit_mr_get(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
microkit_dbg_puts("ERROR!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return microkit_msginfo_new(0, 0);
|
||||||
|
}void notified(microkit_channel channel) {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user