blob: 250dd90982a8c2024f9c8e197c8ec9ef69cb72cf [file]
/*
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <sel4vm/guest_vm.h>
#include <sel4vm/guest_ram.h>
#include <sel4vm/guest_vm_util.h>
#include "fault.h"
#include "arch_fault.h"
#include <sel4/sel4.h>
#include <sel4/messages.h>
#include <vka/capops.h>
#include <utils/ansi.h>
#include <stdlib.h>
#include <sel4/sel4_arch/constants.h>
//#define DEBUG_FAULTS
//#define DEBUG_ERRATA
#ifdef DEBUG_FAULTS
#define DFAULT(...) printf(__VA_ARGS__)
#else
#define DFAULT(...) do{}while(0)
#endif
#ifdef DEBUG_ERRATA
#define DERRATA(...) printf(__VA_ARGS__)
#else
#define DERRATA(...) do{}while(0)
#endif
#ifdef PLAT_EXYNOS5250
/* Stores in thumb mode trigger this errata */
#define HAS_ERRATA766422(f) ( fault_is_write(f) && fault_is_thumb(f))
#else
#define HAS_ERRATA766422(f) 0
#endif
#define HSR_INST32 BIT(25)
#define HSR_IS_INST32(x) ((x) & HSR_INST32)
#define HSR_SYNDROME_VALID BIT(24)
#define HSR_IS_SYNDROME_VALID(hsr) ((hsr) & HSR_SYNDROME_VALID)
#define HSR_SYNDROME_RT(x) (((x) >> 16) & SRT_MASK)
#define HSR_SYNDROME_WIDTH(x) (((x) >> 22) & 0x3)
#define CONTENT_REGS BIT(0)
#define CONTENT_DATA BIT(1)
#define CONTENT_INST BIT(2)
#define CONTENT_WIDTH BIT(3)
#define CONTENT_STAGE BIT(4)
#define CONTENT_PMODE BIT(5)
/*************************
*** Primary functions ***
*************************/
static inline int thumb_is_32bit_instruction(seL4_Word instruction)
{
switch ((instruction >> 11) & 0x1f) {
case 0b11101:
case 0b11110:
case 0b11111:
return 1;
default:
return 0;
}
}
static int maybe_fetch_fault_instruction(fault_t *f)
{
if ((f->content & CONTENT_INST) == 0) {
seL4_Word inst = 0;
/* Fetch the instruction */
if (vm_ram_touch(f->vcpu->vm, f->ip, 4, vm_guest_ram_read_callback, &inst)) {
return -1;
}
/* Fixup the instruction */
if (fault_is_thumb(f)) {
if (thumb_is_32bit_instruction(inst)) {
f->fsr |= HSR_INST32;
}
if (HSR_IS_INST32(f->fsr)) {
/* Swap half words for a 32 bit instruction */
inst = ((inst & 0xffff)) << 16 | ((inst >> 16) & 0xffff);
} else {
/* Mask instruction for 16 bit instruction */
inst &= 0xffff;
}
} else {
/* All ARM instructions are 32 bit so force the HSR flag to be set */
f->fsr |= HSR_INST32;
}
f->instruction = inst;
f->content |= CONTENT_INST;
}
return 0;
}
static int errata766422_get_rt(fault_t *f, seL4_Word hsr)
{
seL4_Word inst;
int err;
err = maybe_fetch_fault_instruction(f);
inst = f->instruction;
if (err) {
return err;
}
if (HSR_IS_INST32(hsr)) {
DERRATA("Errata766422 @ 0x%08x (0x%08x)\n", fault_get_ctx(f)->pc, inst);
if ((inst & 0xff700000) == 0xf8400000) {
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf8800000) {
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf0000000) {
return (inst >> 12) & 0xf;
} else if ((inst & 0x0e500000) == 0x06400000) {
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf8000000) {
return (inst >> 12) & 0xf;
} else {
printf("Unable to decode inst %08lx\n", (long) inst);
return -1;
}
} else {
DERRATA("Errata766422 @ 0x%08lx (0x%04lx)\n", (long) fault_get_ctx(f)->pc, (long) inst);
/* 16 bit insts */
if ((inst & 0xf800) == 0x6000) {
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x9000) {
return (inst >> 8) & 0x7;
} else if ((inst & 0xf800) == 0x5000) {
return (inst >> 0) & 0x7;
} else if ((inst & 0xfe00) == 0x5400) {
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x7000) {
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x8000) {
return (inst >> 0) & 0x7;
} else {
printf("Unable to decode inst 0x%04lx\n", (long) inst);
return -1;
}
}
}
static int decode_instruction(fault_t *f)
{
seL4_Word inst;
maybe_fetch_fault_instruction(f);
inst = f->instruction;
/* Single stage by default */
f->stage = 1;
f->content |= CONTENT_STAGE;
/* Decode */
if (fault_is_thumb(f)) {
if (thumb_is_32bit_instruction(inst)) {
f->fsr |= BIT(25); /* 32 bit instruction */
/* 32 BIT THUMB insts */
if ((inst & 0xff700000) == 0xf8400000) {
print_fault(f);
assert(!"No data width");
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf8800000) {
print_fault(f);
assert(!"No data width");
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf0000000) {
print_fault(f);
assert(!"No data width");
return (inst >> 12) & 0xf;
} else if ((inst & 0x0e500000) == 0x06400000) {
print_fault(f);
assert(!"No data width");
return (inst >> 12) & 0xf;
} else if ((inst & 0xfff00000) == 0xf8000000) {
print_fault(f);
assert(!"No data width");
return (inst >> 12) & 0xf;
} else if ((inst & 0xfe400000) == 0xe8400000) { /* LDRD/STRD */
int rt;
if ((f->content & CONTENT_STAGE) == 0) {
f->stage = 2;
f->width = WIDTH_DOUBLEWORD;
f->content |= CONTENT_WIDTH | CONTENT_STAGE;
}
f->addr = f->base_addr + ((2 - f->stage) * sizeof(seL4_Word));
rt = ((inst >> 12) & 0xf) + (2 - f->stage);
return rt;
} else {
printf("Unable to decode THUMB32 inst 0x%08lx\n", (long) inst);
print_fault(f);
return -1;
}
} else {
/* 16 bit THUMB insts */
if ((inst & 0xf800) == 0x6000) {
print_fault(f);
assert(!"No data width");
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x9000) {
print_fault(f);
assert(!"No data width");
return (inst >> 8) & 0x7;
} else if ((inst & 0xf800) == 0x5000) {
print_fault(f);
assert(!"No data width");
return (inst >> 0) & 0x7;
} else if ((inst & 0xfe00) == 0x5400) {
print_fault(f);
assert(!"No data width");
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x7000) {
print_fault(f);
assert(!"No data width");
return (inst >> 0) & 0x7;
} else if ((inst & 0xf800) == 0x8000) {
print_fault(f);
assert(!"No data width");
return (inst >> 0) & 0x7;
} else {
printf("Unable to decode THUMB16 inst 0x%04lx\n", (long) inst);
print_fault(f);
return -1;
}
}
} else {
printf("32 bit ARM insts not decoded\n");
print_fault(f);
return -1;
}
}
static int get_rt(fault_t *f)
{
/* Save processor mode in fault struct */
if ((f->content & CONTENT_PMODE) == 0) {
#ifdef CONFIG_ARCH_AARCH64
#else
f->pmode = fault_get_ctx(f)->cpsr & 0x1f;
#endif
f->content |= CONTENT_PMODE;
}
int rt;
if (HSR_IS_SYNDROME_VALID(f->fsr)) {
if (HAS_ERRATA766422(f)) {
rt = errata766422_get_rt(f, f->fsr);
} else {
rt = HSR_SYNDROME_RT(f->fsr);
//printf("here rt %d\n", rt);
}
} else {
#ifdef CONFIG_ARCH_AARCH64
printf("decode_insturction for arm64 not implemented\n");
#endif
rt = decode_instruction(f);
}
assert(rt >= 0);
return rt;
}
fault_t *fault_init(vm_vcpu_t *vcpu)
{
fault_t *fault;
seL4_Error err;
fault = (fault_t *)calloc(1, sizeof(*fault));
if (fault != NULL) {
fault->vcpu = vcpu;
/* Reserve a slot for saving reply caps */
err = vka_cspace_alloc_path(vcpu->vm->vka, &fault->reply_cap);
if (err) {
free(fault);
fault = NULL;
}
}
return fault;
}
int new_vcpu_fault(fault_t *fault, uint32_t hsr)
{
int err;
assert(fault_handled(fault));
fault->type = VCPU;
fault->fsr = hsr;
fault->instruction = 0;
fault->data = 0;
fault->width = -1;
fault->content = 0;
fault->stage = 1;
assert(fault->reply_cap.capPtr);
err = vka_cnode_saveCaller(&fault->reply_cap);
assert(!err);
return err;
}
int new_memory_fault(fault_t *fault)
{
seL4_Word ip, addr, fsr;
seL4_Word is_prefetch;
int err;
vm_t *vm;
vm = fault->vcpu->vm;
assert(vm);
assert(fault_handled(fault));
/* First store message registers on the stack to free our message regs */
is_prefetch = seL4_GetMR(seL4_VMFault_PrefetchFault);
addr = seL4_GetMR(seL4_VMFault_Addr),
fsr = seL4_GetMR(seL4_VMFault_FSR);
ip = seL4_GetMR(seL4_VMFault_IP);
DFAULT("%s: New fault @ 0x%x from PC 0x%x\n", vm->vm_name, addr, ip);
/* Create the fault object */
fault->type = is_prefetch ? PREFETCH : DATA;
fault->ip = ip;
fault->base_addr = fault->addr = addr;
fault->fsr = fsr;
fault->instruction = 0;
fault->data = 0;
fault->width = -1;
if (fault_is_data(fault)) {
if (fault_is_read(fault)) {
/* No need to load data */
fault->content = CONTENT_DATA;
} else {
fault->content = 0;
}
if (HSR_IS_SYNDROME_VALID(fault->fsr)) {
fault->stage = 1;
fault->content |= CONTENT_STAGE;
} else {
fault->stage = -1;
}
} else {
/* No need to load width or data */
fault->content = CONTENT_DATA | CONTENT_WIDTH;
}
/* Gather additional information */
assert(fault->reply_cap.capPtr);
err = vka_cnode_saveCaller(&fault->reply_cap);
assert(!err);
return err;
}
int abandon_fault(fault_t *fault)
{
/* Nothing to do here */
DFAULT("%s: Release fault @ 0x%x from PC 0x%x\n",
fault->vcpu->vm->vm_name, fault->addr, fault->ip);
return 0;
}
int restart_fault(fault_t *fault)
{
/* Send the reply */
fault->stage = 0;
seL4_MessageInfo_t reply;
reply = seL4_MessageInfo_new(0, 0, 0, 0);
DFAULT("%s: Restart fault @ 0x%x from PC 0x%x\n",
fault->vcpu->vm->vm_name, fault->addr, fault->ip);
seL4_Send(fault->reply_cap.capPtr, reply);
/* Clean up */
return abandon_fault(fault);
}
int ignore_fault(fault_t *fault)
{
seL4_UserContext *regs;
int err;
regs = fault_get_ctx(fault);
/* Advance the PC */
regs->pc += fault_is_32bit_instruction(fault) ? 4 : 2;
/* Write back CPU registers */
err = seL4_TCB_WriteRegisters(vm_get_vcpu_tcb(fault->vcpu), false, 0,
sizeof(*regs) / sizeof(regs->pc), regs);
assert(!err);
if (err) {
abandon_fault(fault);
return err;
}
/* Reply to thread */
return restart_fault(fault);
}
int advance_fault(fault_t *fault)
{
/* If data was to be read, load it into the user context */
if (fault_is_data(fault) && fault_is_read(fault)) {
/* Get register opearand */
int rt = get_rt(fault);
/* Decode whether operand is banked */
int reg = decode_vcpu_reg(rt, fault);
if (reg == seL4_VCPUReg_Num) {
/* register is not banked, use seL4_UserContext */
seL4_Word *reg_ctx = decode_rt(rt, fault_get_ctx(fault));
*reg_ctx = fault_emulate(fault, *reg_ctx);
} else {
/* register is banked, use vcpu invocations */
seL4_ARM_VCPU_ReadRegs_t res = seL4_ARM_VCPU_ReadRegs(fault->vcpu->vcpu.cptr, reg);
if (res.error) {
ZF_LOGF("Read registers failed");
return -1;
}
int error = seL4_ARM_VCPU_WriteRegs(fault->vcpu->vcpu.cptr, reg, fault_emulate(fault, res.value));
if (error) {
ZF_LOGF("Write registers failed");
return -1;
}
}
}
DFAULT("%s: Emulate fault @ 0x%x from PC 0x%x\n",
fault->vcpu->vm->vm_name, fault->addr, fault->ip);
/* If this is the final stage of the fault, return to user */
assert(fault->stage > 0);
fault->stage--;
if (fault->stage) {
/* Data becomes invalid */
fault->content &= ~CONTENT_DATA;
return 0;
} else {
return ignore_fault(fault);
}
}
seL4_Word fault_emulate(fault_t *f, seL4_Word o)
{
seL4_Word n, m, s;
s = (f->addr & 0x3) * 8;
m = fault_get_data_mask(f);
n = fault_get_data(f);
if (fault_is_read(f)) {
/* Read data must be shifted to lsb */
return (o & ~(m >> s)) | ((n & m) >> s);
} else {
/* Data to write must be shifted left to compensate for alignment */
return (o & ~m) | ((n << s) & m);
}
}
void print_fault(fault_t *fault)
{
printf("--------\n");
printf(ANSI_COLOR(RED, BOLD));
printf("Pagefault from [%s]: %s %s "
"@ PC: 0x"XFMT" IPA: 0x"XFMT", FSR: 0x"XFMT "\n",
fault->vcpu->vm->vm_name,
fault_is_read(fault) ? "read" : "write",
fault_is_prefetch(fault) ? "prefetch fault" : "fault",
fault->ip,
fault->addr,
fault->fsr);
printf("Context:\n");
print_ctx_regs(fault_get_ctx(fault));
printf(ANSI_COLOR(RESET));
printf("--------\n");
}
seL4_Word fault_get_data_mask(fault_t *f)
{
seL4_Word mask = 0;
seL4_Word addr = f->addr;
switch (fault_get_width(f)) {
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:
/* Should never get here... Keep the compiler happy */
assert(0);
return 0;
}
mask <<= (addr & 0x3) * 8;
return mask;
}
/*************************
*** Getters / Setters ***
*************************/
seL4_Word fault_get_data(fault_t *f)
{
if ((f->content & CONTENT_DATA) == 0) {
/* Get register opearand */
int rt = get_rt(f);
/* Decode whether register is banked */
int reg = decode_vcpu_reg(rt, f);
seL4_Word data;
if (reg == seL4_VCPUReg_Num) {
/* Not banked, use seL4_UserContext */
data = *decode_rt(rt, fault_get_ctx(f));
} else {
/* Banked, use VCPU invocations */
seL4_ARM_VCPU_ReadRegs_t res = seL4_ARM_VCPU_ReadRegs(f->vcpu->vcpu.cptr, reg);
if (res.error) {
ZF_LOGF("Read registers failed");
}
data = res.value;
}
fault_set_data(f, data);
}
return f->data;
}
void fault_set_data(fault_t *f, seL4_Word data)
{
f->data = data;
f->content |= CONTENT_DATA;
}
seL4_Word fault_get_address(fault_t *f)
{
return f->addr;
}
seL4_Word fault_get_fsr(fault_t *f)
{
return f->fsr;
}
seL4_UserContext *fault_get_ctx(fault_t *f)
{
if ((f->content & CONTENT_REGS) == 0) {
int err;
err = seL4_TCB_ReadRegisters(vm_get_vcpu_tcb(f->vcpu), false, 0,
sizeof(f->regs) / sizeof(f->regs.pc),
&f->regs);
assert(!err);
f->content |= CONTENT_REGS;
}
return &f->regs;
}
void fault_set_ctx(fault_t *f, seL4_UserContext *ctx)
{
f->regs = *ctx;
f->content |= CONTENT_REGS;
}
int fault_handled(fault_t *f)
{
return f->stage == 0;
}
int fault_is_prefetch(fault_t *f)
{
return f->type == PREFETCH;
}
int fault_is_wfi(fault_t *f)
{
return HSR_EXCEPTION_CLASS(f->fsr) == HSR_WFx_EXCEPTION;
}
int fault_is_vcpu(fault_t *f)
{
return f->type == VCPU;
}
int fault_is_32bit_instruction(fault_t *f)
{
if (fault_is_vcpu(f)) {
return !fault_is_thumb(f);
}
if (!HSR_IS_SYNDROME_VALID(f->fsr)) {
/* (maybe) Trigger a decode to update the fsr. */
fault_get_width(f);
}
return fault_get_fsr(f) & BIT(25);
}
enum fault_width fault_get_width(fault_t *f)
{
if ((f->content & CONTENT_WIDTH) == 0) {
if (HSR_IS_SYNDROME_VALID(f->fsr)) {
switch (HSR_SYNDROME_WIDTH(f->fsr)) {
case 0:
f->width = WIDTH_BYTE;
break;
case 1:
f->width = WIDTH_HALFWORD;
break;
case 2:
f->width = WIDTH_WORD;
break;
case 3:
f->width = WIDTH_DOUBLEWORD;
break;
default:
print_fault(f);
assert(0);
return 0;
}
f->content |= CONTENT_WIDTH;
} else {
int rt;
rt = decode_instruction(f);
assert(rt >= 0);
}
}
return f->width;
}
size_t fault_get_width_size(fault_t *fault)
{
enum fault_width width = fault_get_width(fault);
switch (width) {
case WIDTH_DOUBLEWORD:
return sizeof(long long);
case WIDTH_WORD:
return sizeof(int);
case WIDTH_HALFWORD:
return sizeof(short);
case WIDTH_BYTE:
return sizeof(char);
default:
return 0;
}
}