blob: 4bd584b356ea6a14071b8d5b192f3d689e9f4c8b [file] [log] [blame] [edit]
/*
* Copyright (c) 2014 Travis Geiselbrecht
*
* 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.
*/
#include <bits.h>
#include <ctype.h>
#include <debug.h>
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <arch/arch_ops.h>
#include <arch/arm64.h>
#include <arch/mmu.h>
#include <arch/pan.h>
#include <arch/safecopy.h>
#include <kernel/vm.h>
#include <lib/trusty/trusty_app.h>
#include <inttypes.h>
#define SHUTDOWN_ON_FATAL 1
/**
* struct fault_handler_table_entry - Fault handler table entry.
* @pc: Address of the faulting instruction.
* @fault_handler: Address of the corresponding fault handler.
*
* Both addresses are position-relative, i.e., each field contains the offset
* from the field itself to its target.
*/
struct fault_handler_table_entry {
int64_t pc;
int64_t fault_handler;
};
extern struct fault_handler_table_entry __fault_handler_table_start[];
extern struct fault_handler_table_entry __fault_handler_table_end[];
/**
* prel_to_abs_u64() - Convert a position-relative value to an absolute.
* @ptr: Pointer to a 64-bit position-relative value.
* @result: Pointer to the location for the result.
*
* Return: %true in case of success, %false for overflow.
*/
static inline bool prel_to_abs_u64(const int64_t* ptr, uint64_t* result) {
return !__builtin_add_overflow((uintptr_t)ptr, *ptr, result);
}
static bool check_fault_handler_table(struct arm64_iframe_long *iframe)
{
struct fault_handler_table_entry *fault_handler;
for (fault_handler = __fault_handler_table_start;
fault_handler < __fault_handler_table_end;
fault_handler++) {
uint64_t addr;
if (!prel_to_abs_u64(&fault_handler->pc, &addr)) {
/* Invalid entry, ignore it */
continue;
}
if (addr == iframe->elr) {
if (!prel_to_abs_u64(&fault_handler->fault_handler, &addr)) {
/*
* An entry with an invalid handler address. We don't expect
* another entry with the same pc, so we break out of
* the loop early.
*/
return false;
}
iframe->elr = addr;
return true;
}
}
return false;
}
#if TEST_BUILD
static uint64_t wrap_add(uint64_t addr, int offset) {
uint64_t result;
__builtin_add_overflow(addr, offset, &result);
return result;
}
static bool getmeminfo(uint64_t addr, paddr_t *paddr, uint *flags, char *name,
size_t name_size) {
status_t ret = NO_ERROR;
vmm_aspace_t *aspace = vaddr_to_aspace((void*)addr);
if (aspace) {
ret = arch_mmu_query(&aspace->arch_aspace, addr, paddr, flags);
}
if (aspace && ret == NO_ERROR) {
if (name) {
vmm_get_address_description(addr, name, name_size);
}
return true;
}
return false;
}
static void printmemattrs(
const char *prefix, paddr_t pstart, vaddr_t vstart, size_t len,
uint flags, const char* name) {
if (!len) {
return;
}
printf("%s0x%lx/0x%zx, flags: 0x%02x, region: %s [ read%s%s%s",
prefix, pstart, len, flags, name,
!(flags & ARCH_MMU_FLAG_PERM_RO) ? " write" : "",
!(flags & ARCH_MMU_FLAG_PERM_NO_EXECUTE) ? " execute" : "",
(flags & ARCH_MMU_FLAG_PERM_USER) ? " user" : "");
if (flags & ARCH_MMU_FLAG_TAGGED) {
printf(" tagged(");
/*
* There is one tag for every 16-byte aligned 16 bytes, so depending
* on len and the alignment of vstart, there may be one extra tag.
*/
int numtags = 1 + (round_down(vstart + (len - 1), MTE_GRANULE_SIZE) -
round_down(vstart, MTE_GRANULE_SIZE)) / MTE_GRANULE_SIZE;
for (int i = 0; i < numtags; i++) {
if (i) {
printf("/");
}
int tag = tag_for_address(vstart + i * MTE_GRANULE_SIZE);
if (tag < 0) {
printf("?");
} else {
printf("%d", tag);
}
}
printf(")");
}
printf("%s%s%s ]):\n",
(flags & ARCH_MMU_FLAG_NS) ? " nonsecure" : "",
(flags & ARCH_MMU_FLAG_UNCACHED_DEVICE) ? " device" : "",
(flags & ARCH_MMU_FLAG_UNCACHED) ? " uncached" : "");
}
static void dump_memory_around_register(const char *name, uint64_t regaddr) {
uint64_t addr = wrap_add(regaddr, -16);
uint64_t secondpageaddr;
uint8_t data[48];
int page_size = PAGE_SIZE;
if (is_user_address(addr)) {
page_size = USER_PAGE_SIZE;
}
uint64_t offsetinpage = addr & (page_size - 1);
uint64_t bytesonfirstpage = page_size - offsetinpage;
if (bytesonfirstpage > sizeof(data)) {
bytesonfirstpage = sizeof(data);
}
paddr_t paddr1, paddr2;
uint flags1, flags2;
char name1[VMM_MAX_ADDRESS_DESCRIPTION_SIZE];
char name2[VMM_MAX_ADDRESS_DESCRIPTION_SIZE];
bool info1valid =false;
bool info2valid = false;
bool read1ok = false;
bool read2ok = false;
info1valid = getmeminfo(addr, &paddr1, &flags1, name1, sizeof(name1));
if (bytesonfirstpage < sizeof(data)) {
/* this block spans a page boundary */
secondpageaddr = wrap_add(addr, bytesonfirstpage);
info2valid = getmeminfo(secondpageaddr, &paddr2, &flags2, name2,
sizeof(name2));
}
if (!info1valid && !info2valid) {
return;
}
if (info1valid &&
((flags1 & ARCH_MMU_FLAG_CACHE_MASK) == ARCH_MMU_FLAG_CACHED ||
(flags1 & ARCH_MMU_FLAG_CACHE_MASK) == ARCH_MMU_FLAG_UNCACHED)) {
/* this should only fail if the page was remapped after we queried it */
status_t ret = copy_from_anywhere(data, addr, bytesonfirstpage);
read1ok = (ret == NO_ERROR);
}
if (info2valid &&
((flags2 & ARCH_MMU_FLAG_CACHE_MASK) == ARCH_MMU_FLAG_CACHED ||
(flags2 & ARCH_MMU_FLAG_CACHE_MASK) == ARCH_MMU_FLAG_UNCACHED)) {
status_t ret = copy_from_anywhere(data + bytesonfirstpage,
secondpageaddr, sizeof(data) - bytesonfirstpage);
read2ok = (ret == NO_ERROR);
}
printf(" \nmemory around %3s (", name);
if (info1valid) {
printmemattrs("phys: ", paddr1, addr, bytesonfirstpage, flags1, name1);
} else {
printf("phys: <unmapped>/0x%" PRIx64 "):\n", bytesonfirstpage);
}
if (bytesonfirstpage < sizeof(data)) {
if (info2valid) {
printmemattrs(" and (phys: ",
paddr2,
secondpageaddr,
sizeof(data) - bytesonfirstpage,
flags2,
name2);
} else {
printf(" and (phys: <unmapped>/0x%" PRIx64 "):\n",
sizeof(data) - bytesonfirstpage);
}
}
const int linelen = 16;
int tag_granule_offset = (addr & (MTE_GRANULE_SIZE - 1));
for (size_t offset = 0; offset < sizeof(data); offset += linelen) {
printf("0x%016" PRIx64 ": ", wrap_add(addr, offset));
for (int i = 0; i < linelen; i++) {
if (i == 8) {
printf(" ");
}
if ((offset + i < bytesonfirstpage && read1ok) ||
(offset + i >= bytesonfirstpage && read2ok)) {
printf("%02hhx", data[offset + i]);
if (i != (linelen - 1) &&
((offset + i + tag_granule_offset) %
MTE_GRANULE_SIZE) == (MTE_GRANULE_SIZE - 1) &&
((offset + i < bytesonfirstpage) ? flags1 : flags2) &
ARCH_MMU_FLAG_TAGGED) {
printf("/");
} else {
printf(" ");
}
} else {
printf("-- ");
}
}
printf("|");
for (int i = 0; i < linelen; i++) {
unsigned char c = data[offset + i];
printf("%c", ((offset + i < bytesonfirstpage && read1ok) ||
(offset + i >= bytesonfirstpage && read2ok)) &&
isprint(c) ? c : '.');
}
printf("\n");
}
}
static void dump_memory_around_registers(
const struct arm64_iframe_long *iframe) {
char regname[4];
for (int i = 0; i < 28; i++) {
snprintf(regname, sizeof(regname), "x%d", i);
dump_memory_around_register(regname, iframe->r[i]);
}
dump_memory_around_register("fp", iframe->fp);
dump_memory_around_register("lr", arch_extract_return_addr(iframe->lr));
dump_memory_around_register("sp", iframe->sp);
dump_memory_around_register("elr", iframe->elr);
}
#endif
static void dump_iframe(const struct arm64_iframe_long *iframe)
{
struct thread *thread = get_current_thread();
printf("thread: %p (%s)\n", thread, thread->name);
printf("stack %p-%p\n", thread->stack, thread->stack + thread->stack_size);
printf("iframe %p:\n", iframe);
#if TEST_BUILD
printf("x0 0x%16" PRIx64 " x1 0x%16" PRIx64 " x2 0x%16" PRIx64 " x3 0x%16" PRIx64 "\n", iframe->r[0], iframe->r[1], iframe->r[2], iframe->r[3]);
printf("x4 0x%16" PRIx64 " x5 0x%16" PRIx64 " x6 0x%16" PRIx64 " x7 0x%16" PRIx64 "\n", iframe->r[4], iframe->r[5], iframe->r[6], iframe->r[7]);
printf("x8 0x%16" PRIx64 " x9 0x%16" PRIx64 " x10 0x%16" PRIx64 " x11 0x%16" PRIx64 "\n", iframe->r[8], iframe->r[9], iframe->r[10], iframe->r[11]);
printf("x12 0x%16" PRIx64 " x13 0x%16" PRIx64 " x14 0x%16" PRIx64 " x15 0x%16" PRIx64 "\n", iframe->r[12], iframe->r[13], iframe->r[14], iframe->r[15]);
printf("x16 0x%16" PRIx64 " x17 0x%16" PRIx64 " x18 0x%16" PRIx64 " x19 0x%16" PRIx64 "\n", iframe->r[16], iframe->r[17], iframe->r[18], iframe->r[19]);
printf("x20 0x%16" PRIx64 " x21 0x%16" PRIx64 " x22 0x%16" PRIx64 " x23 0x%16" PRIx64 "\n", iframe->r[20], iframe->r[21], iframe->r[22], iframe->r[23]);
printf("x24 0x%16" PRIx64 " x25 0x%16" PRIx64 " x26 0x%16" PRIx64 " x27 0x%16" PRIx64 "\n", iframe->r[24], iframe->r[25], iframe->r[26], iframe->r[27]);
printf("x28 0x%16" PRIx64 " fp 0x%16" PRIx64 " lr 0x%16" PRIx64 " sp 0x%16" PRIx64 "\n", iframe->r[28], iframe->fp, iframe->lr, iframe->sp);
/* Check if lr contains a PAC and also display the original */
uintptr_t lr_xpac = arch_extract_return_addr(iframe->lr);
if (lr_xpac != iframe->lr) {
printf("lr 0x%16" PRIx64 " (pac removed)\n", lr_xpac);
}
printf("elr 0x%16" PRIx64 "\n", iframe->elr);
printf("spsr 0x%16" PRIx64 "\n", iframe->spsr);
#endif
}
__WEAK void arm64_syscall(struct arm64_iframe_long *iframe, bool is_64bit)
{
panic("unhandled syscall vector\n");
}
static void print_fault_code(uint32_t fsc, uint64_t far) {
printf("fault code 0x%x: ", fsc);
switch (fsc) {
case 0b000000:
case 0b000001:
case 0b000010:
case 0b000011:
printf("Address size fault, level %d", fsc & 0x3);
break;
case 0b000100:
case 0b000101:
case 0b000110:
case 0b000111:
printf("Translation fault, level %d", fsc & 0x3);
break;
case 0b001001:
case 0b001010:
case 0b001011:
printf("Access flag fault, level %d", fsc & 0x3);
break;
case 0b001101:
case 0b001110:
case 0b001111:
printf("Permission fault, level %d", fsc & 0x3);
break;
case 0b010000:
printf("External abort");
break;
case 0b010001: {
printf("Tag check fault");
#if TEST_BUILD
{
uint64_t used_tag = (far >> 56) & 0xf;
int real_tag = tag_for_address(far);
printf(": %" PRIu64 " should be ", used_tag);
if (real_tag < 0) {
printf(" ? (faulted while trying to read actual tag)");
} else {
printf("%d", real_tag);
}
}
#endif
break;
}
case 0b010100:
case 0b010101:
case 0b010110:
case 0b010111:
printf("External abort on translation table, level %d", fsc & 0x3);
break;
case 0b011000:
printf("Parity or ECC error");
break;
case 0b011100:
case 0b011101:
case 0b011110:
case 0b011111:
printf("Parity or ECC error on translation table, level %d", fsc & 0x3);
break;
case 0b100001:
printf("Alignment fault");
break;
case 0b110000:
printf("TLB conflict abort");
break;
case 0b110001:
printf("Unsupported atomic hardware update fault");
break;
case 0b110100:
printf("Lockdown fault");
break;
case 0b110101:
printf("Unsupported exclusive or atomic access");
break;
default:
printf("Unknown fault");
break;
}
printf("\n");
}
static void enable_tag_checks(void)
{
if (arm64_tagging_supported()) {
/* Clearing Tag Check Override enables tag checking */
__asm__ volatile(".arch_extension memtag\n"
"msr tco, #0");
}
}
static void disable_tag_checks(void)
{
if (arm64_tagging_supported()) {
/* Setting Tag Check Override disables tag checking */
__asm__ volatile(".arch_extension memtag\n"
"msr tco, #1");
}
}
void arm64_sync_exception(struct arm64_iframe_long *iframe, bool from_lower)
{
uint32_t esr = ARM64_READ_SYSREG(esr_el1);
uint32_t ec = BITS_SHIFT(esr, 31, 26);
uint32_t il = BIT_SHIFT(esr, 25);
uint32_t iss = BITS(esr, 24, 0);
uintptr_t display_pc = iframe->elr;
uint64_t far;
__UNUSED bool print_mem_around_fault = false;
/*
* Tag checks are automatically disabled on taking an exception, so
* turn them back on
*/
enable_tag_checks();
if (from_lower) {
/*
* load_bias may intentionally overflow to represent a shift
* down of the application base address
*/
__builtin_sub_overflow(display_pc,
current_trusty_app()->load_bias,
&display_pc);
}
switch (ec) {
case 0b000111: /* floating point */
arm64_fpu_exception(iframe);
return;
case 0b010001: /* syscall from arm32 */
case 0b010101: /* syscall from arm64 */
#ifdef WITH_LIB_SYSCALL
arch_enable_fiqs();
arm64_syscall(iframe, (ec == 0x15) ? true : false);
arch_disable_fiqs();
return;
#else
arm64_syscall(iframe, (ec == 0x15) ? true : false);
return;
#endif
case 0b100000: /* instruction abort from lower level */
case 0b100001: /* instruction abort from same level */
if (check_fault_handler_table(iframe)) {
return;
}
printf("instruction abort: PC at 0x%" PRIx64 "(0x%lx)\n", iframe->elr,
display_pc);
print_fault_code(BITS(iss, 5, 0), 0);
break;
case 0b100100: /* data abort from lower level */
case 0b100101: { /* data abort from same level */
if (check_fault_handler_table(iframe)) {
return;
}
/* read the FAR register */
far = ARM64_READ_SYSREG(far_el1);
/* decode the iss */
uint32_t dfsc = BITS(iss, 5, 0);
printf("data fault ");
if (BIT(iss, 6)) {
printf("writing to ");
} else {
printf("reading from ");
}
if (dfsc == 0b010000 && BIT(iss, 10)) {
printf("unknown address (FAR 0x%" PRIx64 " not valid)", far);
} else {
printf("0x%" PRIx64, far);
print_mem_around_fault = true;
vmm_aspace_t *aspace = vaddr_to_aspace((void*)far);
if (aspace) {
char region[VMM_MAX_ADDRESS_DESCRIPTION_SIZE];
vmm_get_address_description(far, region, sizeof(region));
printf(" (%s)", region);
}
}
printf(", PC at 0x%" PRIx64 "(0x%lx)\n", iframe->elr, display_pc);
if (BIT(iss, 24)) { /* ISV bit */
printf("Access size: %d bits, sign extension: %s, register: %s%lu, %s acquire release semantics\n",
8 << BITS_SHIFT(iss,23,22),
BIT_SHIFT(iss,21) ? "yes" : "no",
BIT_SHIFT(iss,15) ? "X" : "W",
BITS_SHIFT(iss,20,16),
BIT_SHIFT(iss,14) ? "" : "no");
}
print_fault_code(dfsc, far);
break;
}
case 0b001101: { /* branch target exception */
#if TEST_BUILD
if (check_fault_handler_table(iframe)) {
return;
}
#endif
printf("branch target exception: BTYPE=0x%lx, "
"PC at 0x%" PRIx64 "(0x%lx)\n", BITS(iss, 1, 0), iframe->elr, display_pc);
break;
}
case 0b011100: { /* FPAC exception */
#if TEST_BUILD
if (check_fault_handler_table(iframe)) {
return;
}
#endif
printf("PAC auth failure target exception: instr_type=%c%c, "
"PC at 0x%" PRIx64 "(0x%lx)\n",
BIT(iss, 1) ? 'D' : 'I', /* data / instruction */
BIT(iss, 0) ? 'B' : 'A', /* key */
iframe->elr, display_pc);
break;
}
case 0b111100: {
printf("BRK #0x%04lx instruction: PC at 0x%" PRIx64 "(0x%lx)\n",
BITS_SHIFT(iss, 15, 0), iframe->elr, display_pc);
break;
}
default:
printf("unhandled synchronous exception: PC at 0x%" PRIx64 "(0x%lx)\n",
iframe->elr, display_pc);
}
/* unhandled exception, die here */
if (from_lower) {
printf("app: %s\n", current_trusty_app()->props.app_name);
printf("load bias: 0x%lx\n", current_trusty_app()->load_bias);
}
printf("ESR 0x%x: ec 0x%x, il 0x%x, iss 0x%x\n", esr, ec, il, iss);
dump_iframe(iframe);
#if TEST_BUILD
disable_tag_checks();
dump_memory_around_registers(iframe);
if (print_mem_around_fault) {
dump_memory_around_register("fault address", far);
}
enable_tag_checks();
#endif
if (from_lower) {
arch_enable_fiqs();
arch_enable_ints();
trusty_app_crash(esr);
}
panic("die\n");
}
void arm64_invalid_exception(struct arm64_iframe_long *iframe, unsigned int which)
{
printf("invalid exception, which 0x%x\n", which);
dump_iframe(iframe);
#if TEST_BUILD
dump_memory_around_registers(iframe);
#endif
panic("die\n");
}