summaryrefslogtreecommitdiff
path: root/src/arch/riscv/misaligned.c
diff options
context:
space:
mode:
authorXiang Wang <wxjstz@126.com>2018-08-09 16:20:35 +0800
committerPatrick Georgi <pgeorgi@google.com>2018-09-10 15:03:58 +0000
commitcda59b56ba1af83a64579901a5395c1b3c1bf519 (patch)
tree1ea1d63305663f76623b8db878e33f72b4867f82 /src/arch/riscv/misaligned.c
parentaa5f821ee3313b869784eec25fab5da265225738 (diff)
riscv: update misaligned memory access exception handling
Support for more situations: floating point, compressed instructions, etc. Add support for redirect exception to S-Mode. Change-Id: I9983d56245eab1d458a84cb1432aeb805df7a49f Signed-off-by: Xiang Wang <wxjstz@126.com> Reviewed-on: https://review.coreboot.org/27972 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
Diffstat (limited to 'src/arch/riscv/misaligned.c')
-rw-r--r--src/arch/riscv/misaligned.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/arch/riscv/misaligned.c b/src/arch/riscv/misaligned.c
new file mode 100644
index 0000000000..cb045b8323
--- /dev/null
+++ b/src/arch/riscv/misaligned.c
@@ -0,0 +1,267 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2018 HardenedLinux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <vm.h>
+#include <arch/exception.h>
+#include <commonlib/helpers.h>
+
+/* these functions are defined in src/arch/riscv/fp_asm.S */
+#if defined(__riscv_flen)
+#if __riscv_flen >= 32
+extern void read_f32(int regnum, uint32_t *v);
+extern void write_f32(int regnum, uint32_t *v);
+#endif // __riscv_flen >= 32
+#if __riscv_flen >= 64
+extern void read_f64(int regnum, uint64_t *v);
+extern void write_f64(int regnum, uint64_t *v);
+#endif // __riscv_flen >= 64
+#endif // defined(__riscv_flen)
+
+/* This union makes it easy to read multibyte types by byte operations. */
+union endian_buf {
+ uint8_t b[8];
+ uint16_t h[4];
+ uint32_t w[2];
+ uint64_t d[1];
+ uintptr_t v;
+};
+
+/* This struct hold info of load/store instruction */
+struct memory_instruction_info {
+ /* opcode/mask used to identify instruction,
+ * (instruction_val) & mask == opcode */
+ uint32_t opcode;
+ uint32_t mask;
+ /* reg_shift/reg_mask/reg_addition used to get register number
+ * ((instruction_val >> reg_shift) & reg_mask) + reg_addition */
+ unsigned int reg_shift;
+ unsigned int reg_mask;
+ unsigned int reg_addition;
+ unsigned int is_fp : 1; /* mark as a float operation */
+ unsigned int is_load : 1; /* mark as a load operation */
+ unsigned int width : 8; /* Record the memory width of the operation */
+ unsigned int sign_extend : 1; /* mark need to be sign extended */
+};
+
+static struct memory_instruction_info insn_info[] = {
+#if __riscv_xlen == 128
+ { 0x00002000, 0x0000e003, 2, 7, 8, 0, 1, 16, 1}, // C.LQ
+#else
+ { 0x00002000, 0x0000e003, 2, 7, 8, 1, 1, 8, 0}, // C.FLD
+#endif
+ { 0x00004000, 0x0000e003, 2, 7, 8, 0, 1, 4, 1}, // C.LW
+#if __riscv_xlen == 32
+ { 0x00006000, 0x0000e003, 2, 7, 8, 1, 1, 4, 0}, // C.FLW
+#else
+ { 0x00006000, 0x0000e003, 2, 7, 8, 0, 1, 8, 1}, // C.LD
+#endif
+
+#if __riscv_xlen == 128
+ { 0x0000a000, 0x0000e003, 2, 7, 8, 0, 0, 16, 0}, // C.SQ
+#else
+ { 0x0000a000, 0x0000e003, 2, 7, 8, 1, 0, 8, 0}, // C.FSD
+#endif
+ { 0x0000c000, 0x0000e003, 2, 7, 8, 0, 0, 4, 0}, // C.SW
+#if __riscv_xlen == 32
+ { 0x0000e000, 0x0000e003, 2, 7, 8, 1, 0, 4, 0}, // C.FSW
+#else
+ { 0x0000e000, 0x0000e003, 2, 7, 8, 0, 0, 8, 0}, // C.SD
+#endif
+
+#if __riscv_xlen == 128
+ { 0x00002002, 0x0000e003, 7, 15, 0, 0, 1, 16, 1}, // C.LQSP
+#else
+ { 0x00002002, 0x0000e003, 7, 15, 0, 1, 1, 8, 0}, // C.FLDSP
+#endif
+ { 0x00004002, 0x0000e003, 7, 15, 0, 0, 1, 4, 1}, // C.LWSP
+#if __riscv_xlen == 32
+ { 0x00006002, 0x0000e003, 7, 15, 0, 1, 1, 4, 0}, // C.FLWSP
+#else
+ { 0x00006002, 0x0000e003, 7, 15, 0, 0, 1, 8, 1}, // C.LDSP
+#endif
+
+#if __riscv_xlen == 128
+ { 0x0000a002, 0x0000e003, 2, 15, 0, 0, 0, 16, 0}, // C.SQSP
+#else
+ { 0x0000a002, 0x0000e003, 2, 15, 0, 1, 0, 8, 0}, // C.FSDSP
+#endif
+ { 0x0000c002, 0x0000e003, 2, 15, 0, 0, 0, 4, 0}, // C.SWSP
+#if __riscv_xlen == 32
+ { 0x0000e002, 0x0000e003, 2, 15, 0, 1, 0, 4, 0}, // C.FSWSP
+#else
+ { 0x0000e002, 0x0000e003, 2, 15, 0, 0, 0, 8, 0}, // C.SDSP
+#endif
+
+ { 0x00000003, 0x0000707f, 7, 15, 0, 0, 1, 1, 1}, // LB
+ { 0x00001003, 0x0000707f, 7, 15, 0, 0, 1, 2, 1}, // LH
+ { 0x00002003, 0x0000707f, 7, 15, 0, 0, 1, 4, 1}, // LW
+#if __riscv_xlen > 32
+ { 0x00003003, 0x0000707f, 7, 15, 0, 0, 1, 8, 1}, // LD
+#endif
+ { 0x00004003, 0x0000707f, 7, 15, 0, 0, 1, 1, 0}, // LBU
+ { 0x00005003, 0x0000707f, 7, 15, 0, 0, 1, 2, 0}, // LHU
+ { 0x00006003, 0x0000707f, 7, 15, 0, 0, 1, 4, 0}, // LWU
+
+ { 0x00000023, 0x0000707f, 20, 15, 0, 0, 0, 1, 0}, // SB
+ { 0x00001023, 0x0000707f, 20, 15, 0, 0, 0, 2, 0}, // SH
+ { 0x00002023, 0x0000707f, 20, 15, 0, 0, 0, 4, 0}, // SW
+#if __riscv_xlen > 32
+ { 0x00003023, 0x0000707f, 20, 15, 0, 0, 0, 8, 0}, // SD
+#endif
+
+#if defined(__riscv_flen)
+#if __riscv_flen >= 32
+ { 0x00002007, 0x0000707f, 7, 15, 0, 1, 1, 4, 0}, // FLW
+ { 0x00003007, 0x0000707f, 7, 15, 0, 1, 1, 8, 0}, // FLD
+#endif // __riscv_flen >= 32
+
+#if __riscv_flen >= 64
+ { 0x00002027, 0x0000707f, 20, 15, 0, 1, 0, 4, 0}, // FSW
+ { 0x00003027, 0x0000707f, 20, 15, 0, 1, 0, 8, 0}, // FSD
+#endif // __riscv_flen >= 64
+#endif // defined(__riscv_flen)
+};
+
+static struct memory_instruction_info *match_instruction(uintptr_t insn)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(insn_info); i++)
+ if ((insn_info[i].mask & insn) == insn_info[i].opcode)
+ return &(insn_info[i]);
+ return NULL;
+}
+
+
+static int fetch_16bit_instruction(uintptr_t vaddr, uintptr_t *insn)
+{
+ uint16_t ins = mprv_read_mxr_u16((uint16_t *)vaddr);
+ if (EXTRACT_FIELD(ins, 0x3) != 3) {
+ *insn = ins;
+ return 0;
+ }
+ return -1;
+}
+
+static int fetch_32bit_instruction(uintptr_t vaddr, uintptr_t *insn)
+{
+ uint32_t l = (uint32_t)mprv_read_mxr_u16((uint16_t *)vaddr + 0);
+ uint32_t h = (uint32_t)mprv_read_mxr_u16((uint16_t *)vaddr + 2);
+ uint32_t ins = (h << 16) | l;
+ if ((EXTRACT_FIELD(ins, 0x3) == 3) &&
+ (EXTRACT_FIELD(ins, 0x1c) != 0x7)) {
+ *insn = ins;
+ return 0;
+ }
+ return -1;
+}
+
+
+void handle_misaligned(trapframe *tf)
+{
+ uintptr_t insn = 0;
+ union endian_buf buff;
+
+ /* try to fetch 16/32 bits instruction */
+ if (fetch_16bit_instruction(tf->epc, &insn))
+ if (fetch_32bit_instruction(tf->epc, &insn))
+ redirect_trap();
+
+ /* matching instruction */
+ struct memory_instruction_info *match = match_instruction(insn);
+
+ if (!match) {
+ redirect_trap();
+ return;
+ }
+
+ int regnum;
+ regnum = ((insn >> match->reg_shift) & match->reg_mask);
+ regnum = regnum + match->reg_addition;
+ buff.v = 0;
+ if (match->is_load) {
+ /* load operation */
+
+ /* reading from memory by bytes prevents misaligned
+ * memory access */
+ for (int i = 0; i < match->width; i++) {
+ uint8_t *addr = (uint8_t *)(tf->badvaddr + i);
+ buff.b[i] = mprv_read_u8(addr);
+ }
+
+ /* sign extend for signed integer loading */
+ if (match->sign_extend)
+ if (buff.v >> (8 * match->width - 1))
+ buff.v |= -1 << (8 * match->width);
+
+ /* write to register */
+ if (match->is_fp) {
+ int done = 0;
+#if defined(__riscv_flen)
+#if __riscv_flen >= 32
+ /* single-precision floating-point */
+ if (match->width == 4) {
+ write_f32(regnum, buff.w);
+ done = 1;
+ }
+#endif // __riscv_flen >= 32
+#if __riscv_flen >= 64
+ /* double-precision floating-point */
+ if (match->width == 8) {
+ write_f64(regnum, buff.d);
+ done = 1;
+ }
+#endif // __riscv_flen >= 64
+#endif // defined(__riscv_flen)
+ if (!done)
+ redirect_trap();
+ } else {
+ tf->gpr[regnum] = buff.v;
+ }
+ } else {
+ /* store operation */
+
+ /* reading from register */
+ if (match->is_fp) {
+ int done = 0;
+#if defined(__riscv_flen)
+#if __riscv_flen >= 32
+ if (match->width == 4) {
+ read_f32(regnum, buff.w);
+ done = 1;
+ }
+#endif // __riscv_flen >= 32
+#if __riscv_flen >= 64
+ if (match->width == 8) {
+ read_f64(regnum, buff.d);
+ done = 1;
+ }
+#endif // __riscv_flen >= 64
+#endif // defined(__riscv_flen)
+ if (!done)
+ redirect_trap();
+ } else {
+ buff.v = tf->gpr[regnum];
+ }
+
+ /* writing to memory by bytes prevents misaligned
+ * memory access */
+ for (int i = 0; i < match->width; i++) {
+ uint8_t *addr = (uint8_t *)(tf->badvaddr + i);
+ mprv_write_u8(addr, buff.b[i]);
+ }
+ }
+}