[PATCH v2 11/16] arm64: kernel: Handle deferred SError on kernel entry
James Morse
james.morse at arm.com
Fri Jul 28 07:10:14 PDT 2017
Before we can enable Implicit ESB on exception level change, we need to
handle deferred SErrors that may appear on exception entry.
Add code to kernel_entry to synchronize errors then read and clear
DISR_EL1. Call do_deferred_serror() if it had a non-zero value.
(The IESB feature will allow this explicit ESB to be removed)
These checks are needed in the SError vector too, as we may take a pending
SError that was signalled by a device, and on entry to EL1 synchronize
then defer a RAS SError that hadn't yet been made pending. We process the
'taken' SError first as it is more likely to be fatal.
Clear DISR_EL1 from the RAS cpufeature enable call. This means any value
we find in DISR_EL1 was triggered and deferred by our actions. We have
executed ESB prior to this point, but these occur with SError unmasked so
will not have been deferred.
Signed-off-by: James Morse <james.morse at arm.com>
---
Remove the 'x21 aborted SP' line from entry.S - its not true.
arch/arm64/include/asm/assembler.h | 23 ++++++++++++
arch/arm64/include/asm/esr.h | 7 ++++
arch/arm64/include/asm/exception.h | 14 ++++++++
arch/arm64/include/asm/processor.h | 1 +
arch/arm64/include/asm/sysreg.h | 1 +
arch/arm64/kernel/cpufeature.c | 9 +++++
arch/arm64/kernel/entry.S | 73 ++++++++++++++++++++++++++++++++------
arch/arm64/kernel/traps.c | 5 +++
8 files changed, 122 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/include/asm/assembler.h b/arch/arm64/include/asm/assembler.h
index a013ab05210d..e2bb551f59f7 100644
--- a/arch/arm64/include/asm/assembler.h
+++ b/arch/arm64/include/asm/assembler.h
@@ -110,6 +110,13 @@
.endm
/*
+ * RAS Error Synchronization barrier
+ */
+ .macro esb
+ hint #16
+ .endm
+
+/*
* NOP sequence
*/
.macro nops, num
@@ -455,6 +462,22 @@ alternative_endif
.endm
/*
+ * Read and clear DISR if supported
+ */
+ .macro disr_read, reg
+ alternative_if ARM64_HAS_RAS_EXTN
+ mrs_s \reg, SYS_DISR_EL1
+ cbz \reg, 99f
+ msr_s SYS_DISR_EL1, xzr
+99:
+ alternative_else
+ mov \reg, xzr
+ nop
+ nop
+ alternative_endif
+ .endm
+
+/*
* Errata workaround prior to TTBR0_EL1 update
*
* val: TTBR value with new BADDR, preserved
diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
index 77d5b1baf1a4..41a0767e2600 100644
--- a/arch/arm64/include/asm/esr.h
+++ b/arch/arm64/include/asm/esr.h
@@ -124,6 +124,13 @@
#define ESR_ELx_WFx_ISS_WFE (UL(1) << 0)
#define ESR_ELx_xVC_IMM_MASK ((1UL << 16) - 1)
+#define DISR_EL1_IDS (UL(1) << 24)
+/*
+ * DISR_EL1 and ESR_ELx share the bottom 13 bits, but the RES0 bits may mean
+ * different things in the future...
+ */
+#define DISR_EL1_ESR_MASK (ESR_ELx_AET | ESR_ELx_EA | ESR_ELx_FSC)
+
/* ESR value templates for specific events */
/* BRK instruction trap from AArch64 state */
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 0c2eec490abf..bc30429d8e91 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -18,6 +18,8 @@
#ifndef __ASM_EXCEPTION_H
#define __ASM_EXCEPTION_H
+#include <asm/esr.h>
+
#include <linux/interrupt.h>
#define __exception __attribute__((section(".exception.text")))
@@ -27,4 +29,16 @@
#define __exception_irq_entry __exception
#endif
+static inline u32 disr_to_esr(u64 disr)
+{
+ unsigned int esr = ESR_ELx_EC_SERROR << ESR_ELx_EC_SHIFT;
+
+ if ((disr & DISR_EL1_IDS) == 0)
+ esr |= (disr & DISR_EL1_ESR_MASK);
+ else
+ esr |= (disr & ESR_ELx_ISS_MASK);
+
+ return esr;
+}
+
#endif /* __ASM_EXCEPTION_H */
diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h
index 64c9e78f9882..82e8ff01153d 100644
--- a/arch/arm64/include/asm/processor.h
+++ b/arch/arm64/include/asm/processor.h
@@ -193,5 +193,6 @@ static inline void spin_lock_prefetch(const void *ptr)
int cpu_enable_pan(void *__unused);
int cpu_enable_cache_maint_trap(void *__unused);
+int cpu_clear_disr(void *__unused);
#endif /* __ASM_PROCESSOR_H */
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 58358acf7c9b..18cabd92af22 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -179,6 +179,7 @@
#define SYS_AMAIR_EL1 sys_reg(3, 0, 10, 3, 0)
#define SYS_VBAR_EL1 sys_reg(3, 0, 12, 0, 0)
+#define SYS_DISR_EL1 sys_reg(3, 0, 12, 1, 1)
#define SYS_ICC_IAR0_EL1 sys_reg(3, 0, 12, 8, 0)
#define SYS_ICC_EOIR0_EL1 sys_reg(3, 0, 12, 8, 1)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index a807ab55ee10..6dbefe401dc4 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -899,6 +899,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
.sign = FTR_UNSIGNED,
.field_pos = ID_AA64PFR0_RAS_SHIFT,
.min_field_value = ID_AA64PFR0_RAS_V1,
+ .enable = cpu_clear_disr,
},
#endif /* CONFIG_ARM64_RAS_EXTN */
{},
@@ -1308,3 +1309,11 @@ static int __init enable_mrs_emulation(void)
}
late_initcall(enable_mrs_emulation);
+
+int cpu_clear_disr(void *__unused)
+{
+ /* Firmware may have left a deferred SError in this register. */
+ write_sysreg_s(0, SYS_DISR_EL1);
+
+ return 0;
+}
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index 9e63f69e1366..8cdfca4060e3 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -34,6 +34,18 @@
#include <asm/asm-uaccess.h>
#include <asm/unistd.h>
+
+ /*
+ * Restore syscall arguments from the values already saved on stack
+ * during kernel_entry.
+ */
+ .macro restore_syscall_args
+ ldp x0, x1, [sp]
+ ldp x2, x3, [sp, #S_X2]
+ ldp x4, x5, [sp, #S_X4]
+ ldp x6, x7, [sp, #S_X6]
+ .endm
+
/*
* Context tracking subsystem. Used to instrument transitions
* between user and kernel mode.
@@ -42,14 +54,8 @@
#ifdef CONFIG_CONTEXT_TRACKING
bl context_tracking_user_exit
.if \syscall == 1
- /*
- * Save/restore needed during syscalls. Restore syscall arguments from
- * the values already saved on stack during kernel_entry.
- */
- ldp x0, x1, [sp]
- ldp x2, x3, [sp, #S_X2]
- ldp x4, x5, [sp, #S_X4]
- ldp x6, x7, [sp, #S_X6]
+ /* Save/restore needed during syscalls. */
+ restore_syscall_args
.endif
#endif
.endm
@@ -153,10 +159,13 @@ alternative_else_nop_endif
msr sp_el0, tsk
.endif
+ esb
+ disr_read reg=x15
+
/*
* Registers that may be useful after this macro is invoked:
*
- * x21 - aborted SP
+ * x15 - Deferred Interrupt Status value
* x22 - aborted PC
* x23 - aborted PSTATE
*/
@@ -312,6 +321,31 @@ tsk .req x28 // current thread_info
irq_stack_exit
.endm
+/* Handle any non-zero DISR value if supported.
+ *
+ * @reg the register holding DISR
+ * @syscall whether the syscall args should be restored if we call
+ * do_deferred_serror (default: no)
+ */
+ .macro disr_check reg, syscall = 0
+#ifdef CONFIG_ARM64_RAS_EXTN
+alternative_if_not ARM64_HAS_RAS_EXTN
+ b 9998f
+alternative_else_nop_endif
+ cbz \reg, 9998f
+
+ mov x1, \reg
+ mov x0, sp
+ bl do_deferred_serror
+
+ .if \syscall == 1
+ restore_syscall_args
+ .endif
+9998:
+#endif /* CONFIG_ARM64_RAS_EXTN */
+ .endm
+
+
.text
/*
@@ -404,8 +438,12 @@ ENDPROC(el1_error_invalid)
.align 6
el1_sync:
kernel_entry 1
- mrs x0, far_el1
- mrs x1, esr_el1 // read the syndrome register
+ mrs x26, far_el1
+ mrs x25, esr_el1 // read the syndrome register
+ disr_check reg=x15
+ mov x0, x26
+ mov x1, x25
+
lsr x24, x1, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_BREAKPT_CUR // debug exception in EL1
b.ge el1_dbg
@@ -461,6 +499,7 @@ el1_dbg:
tbz x24, #0, el1_inv // EL1 only
mov x2, sp // struct pt_regs
bl do_debug_exception
+
kernel_exit 1
el1_inv:
// TODO: add support for undefined instructions in kernel mode
@@ -473,6 +512,7 @@ ENDPROC(el1_sync)
.align 6
el1_irq:
kernel_entry 1
+ disr_check reg=x15
enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
@@ -511,6 +551,8 @@ el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register
mrs x26, far_el1
+ disr_check reg=x15, syscall=1
+
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0
b.ge el0_dbg
@@ -544,6 +586,8 @@ el0_sync_compat:
kernel_entry 0, 32
mrs x25, esr_el1 // read the syndrome register
mrs x26, far_el1
+ disr_check reg=x15, syscall=1
+
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0
b.ge el0_dbg
@@ -677,6 +721,7 @@ ENDPROC(el0_sync)
el0_irq:
kernel_entry 0
el0_irq_naked:
+ disr_check reg=x15
enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
@@ -693,18 +738,24 @@ ENDPROC(el0_irq)
el1_serror:
kernel_entry 1
+ mov x20, x15
mrs x1, esr_el1
mov x0, sp
bl do_serror
+
+ disr_check reg=x20
kernel_exit 1
ENDPROC(el1_serror)
el0_serror:
kernel_entry 0
el0_serror_naked:
+ mov x20, x15
mrs x1, esr_el1
mov x0, sp
bl do_serror
+
+ disr_check reg=x20
enable_daif
ct_user_exit
b ret_to_user
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index e1eaccc66548..27ebcaa2f0b6 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -729,6 +729,11 @@ asmlinkage void do_serror(struct pt_regs *regs, unsigned int esr)
nmi_exit();
}
+asmlinkage void do_deferred_serror(struct pt_regs *regs, u64 disr)
+{
+ return do_serror(regs, disr_to_esr(disr));
+}
+
void __pte_error(const char *file, int line, unsigned long val)
{
pr_err("%s:%d: bad pte %016lx.\n", file, line, val);
--
2.13.2
More information about the linux-arm-kernel
mailing list