Commit 82e0191a authored by Suzuki K Poulose's avatar Suzuki K Poulose Committed by Catalin Marinas

arm64: Support systems without FP/ASIMD

The arm64 kernel assumes that FP/ASIMD units are always present
and accesses the FP/ASIMD specific registers unconditionally. This
could cause problems when they are absent. This patch adds the
support for kernel handling systems without FP/ASIMD by skipping the
register access within the kernel. For kvm, we trap the accesses
to FP/ASIMD and inject an undefined instruction exception to the VM.

The callers of the exported kernel_neon_begin_partial() should
make sure that the FP/ASIMD is supported.

Cc: Will Deacon <will.deacon@arm.com>
Cc: Christoffer Dall <christoffer.dall@linaro.org>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: default avatarSuzuki K Poulose <suzuki.poulose@arm.com>
Reviewed-by: default avatarMarc Zyngier <marc.zyngier@arm.com>
[catalin.marinas@arm.com: add comment on the ARM64_HAS_NO_FPSIMD conflict and the new location]
Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
parent a4023f68
...@@ -38,8 +38,13 @@ ...@@ -38,8 +38,13 @@
#define ARM64_HAS_32BIT_EL0 13 #define ARM64_HAS_32BIT_EL0 13
#define ARM64_HYP_OFFSET_LOW 14 #define ARM64_HYP_OFFSET_LOW 14
#define ARM64_MISMATCHED_CACHE_LINE_SIZE 15 #define ARM64_MISMATCHED_CACHE_LINE_SIZE 15
/*
* The macro below will be moved to asm/cpucaps.h together with the
* ARM64_NCAPS update.
*/
#define ARM64_HAS_NO_FPSIMD 16
#define ARM64_NCAPS 16 #define ARM64_NCAPS 17
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
...@@ -231,6 +236,11 @@ static inline bool system_supports_mixed_endian_el0(void) ...@@ -231,6 +236,11 @@ static inline bool system_supports_mixed_endian_el0(void)
return id_aa64mmfr0_mixed_endian_el0(read_system_reg(SYS_ID_AA64MMFR0_EL1)); return id_aa64mmfr0_mixed_endian_el0(read_system_reg(SYS_ID_AA64MMFR0_EL1));
} }
static inline bool system_supports_fpsimd(void)
{
return !cpus_have_const_cap(ARM64_HAS_NO_FPSIMD);
}
#endif /* __ASSEMBLY__ */ #endif /* __ASSEMBLY__ */
#endif #endif
...@@ -9,8 +9,9 @@ ...@@ -9,8 +9,9 @@
*/ */
#include <linux/types.h> #include <linux/types.h>
#include <asm/fpsimd.h>
#define cpu_has_neon() (1) #define cpu_has_neon() system_supports_fpsimd()
#define kernel_neon_begin() kernel_neon_begin_partial(32) #define kernel_neon_begin() kernel_neon_begin_partial(32)
......
...@@ -746,6 +746,14 @@ static bool hyp_offset_low(const struct arm64_cpu_capabilities *entry, ...@@ -746,6 +746,14 @@ static bool hyp_offset_low(const struct arm64_cpu_capabilities *entry,
return idmap_addr > GENMASK(VA_BITS - 2, 0) && !is_kernel_in_hyp_mode(); return idmap_addr > GENMASK(VA_BITS - 2, 0) && !is_kernel_in_hyp_mode();
} }
static bool has_no_fpsimd(const struct arm64_cpu_capabilities *entry, int __unused)
{
u64 pfr0 = read_system_reg(SYS_ID_AA64PFR0_EL1);
return cpuid_feature_extract_signed_field(pfr0,
ID_AA64PFR0_FP_SHIFT) < 0;
}
static const struct arm64_cpu_capabilities arm64_features[] = { static const struct arm64_cpu_capabilities arm64_features[] = {
{ {
.desc = "GIC system register CPU interface", .desc = "GIC system register CPU interface",
...@@ -829,6 +837,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = { ...@@ -829,6 +837,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
.def_scope = SCOPE_SYSTEM, .def_scope = SCOPE_SYSTEM,
.matches = hyp_offset_low, .matches = hyp_offset_low,
}, },
{
/* FP/SIMD is not implemented */
.capability = ARM64_HAS_NO_FPSIMD,
.def_scope = SCOPE_SYSTEM,
.min_field_value = 0,
.matches = has_no_fpsimd,
},
{}, {},
}; };
......
...@@ -127,6 +127,8 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs) ...@@ -127,6 +127,8 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs)
void fpsimd_thread_switch(struct task_struct *next) void fpsimd_thread_switch(struct task_struct *next)
{ {
if (!system_supports_fpsimd())
return;
/* /*
* Save the current FPSIMD state to memory, but only if whatever is in * Save the current FPSIMD state to memory, but only if whatever is in
* the registers is in fact the most recent userland FPSIMD state of * the registers is in fact the most recent userland FPSIMD state of
...@@ -157,6 +159,8 @@ void fpsimd_thread_switch(struct task_struct *next) ...@@ -157,6 +159,8 @@ void fpsimd_thread_switch(struct task_struct *next)
void fpsimd_flush_thread(void) void fpsimd_flush_thread(void)
{ {
if (!system_supports_fpsimd())
return;
memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state)); memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
fpsimd_flush_task_state(current); fpsimd_flush_task_state(current);
set_thread_flag(TIF_FOREIGN_FPSTATE); set_thread_flag(TIF_FOREIGN_FPSTATE);
...@@ -168,6 +172,8 @@ void fpsimd_flush_thread(void) ...@@ -168,6 +172,8 @@ void fpsimd_flush_thread(void)
*/ */
void fpsimd_preserve_current_state(void) void fpsimd_preserve_current_state(void)
{ {
if (!system_supports_fpsimd())
return;
preempt_disable(); preempt_disable();
if (!test_thread_flag(TIF_FOREIGN_FPSTATE)) if (!test_thread_flag(TIF_FOREIGN_FPSTATE))
fpsimd_save_state(&current->thread.fpsimd_state); fpsimd_save_state(&current->thread.fpsimd_state);
...@@ -181,6 +187,8 @@ void fpsimd_preserve_current_state(void) ...@@ -181,6 +187,8 @@ void fpsimd_preserve_current_state(void)
*/ */
void fpsimd_restore_current_state(void) void fpsimd_restore_current_state(void)
{ {
if (!system_supports_fpsimd())
return;
preempt_disable(); preempt_disable();
if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) { if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
struct fpsimd_state *st = &current->thread.fpsimd_state; struct fpsimd_state *st = &current->thread.fpsimd_state;
...@@ -199,6 +207,8 @@ void fpsimd_restore_current_state(void) ...@@ -199,6 +207,8 @@ void fpsimd_restore_current_state(void)
*/ */
void fpsimd_update_current_state(struct fpsimd_state *state) void fpsimd_update_current_state(struct fpsimd_state *state)
{ {
if (!system_supports_fpsimd())
return;
preempt_disable(); preempt_disable();
fpsimd_load_state(state); fpsimd_load_state(state);
if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) { if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
...@@ -228,6 +238,8 @@ static DEFINE_PER_CPU(struct fpsimd_partial_state, softirq_fpsimdstate); ...@@ -228,6 +238,8 @@ static DEFINE_PER_CPU(struct fpsimd_partial_state, softirq_fpsimdstate);
*/ */
void kernel_neon_begin_partial(u32 num_regs) void kernel_neon_begin_partial(u32 num_regs)
{ {
if (WARN_ON(!system_supports_fpsimd()))
return;
if (in_interrupt()) { if (in_interrupt()) {
struct fpsimd_partial_state *s = this_cpu_ptr( struct fpsimd_partial_state *s = this_cpu_ptr(
in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate); in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
...@@ -252,6 +264,8 @@ EXPORT_SYMBOL(kernel_neon_begin_partial); ...@@ -252,6 +264,8 @@ EXPORT_SYMBOL(kernel_neon_begin_partial);
void kernel_neon_end(void) void kernel_neon_end(void)
{ {
if (!system_supports_fpsimd())
return;
if (in_interrupt()) { if (in_interrupt()) {
struct fpsimd_partial_state *s = this_cpu_ptr( struct fpsimd_partial_state *s = this_cpu_ptr(
in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate); in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
......
...@@ -57,6 +57,16 @@ static int handle_smc(struct kvm_vcpu *vcpu, struct kvm_run *run) ...@@ -57,6 +57,16 @@ static int handle_smc(struct kvm_vcpu *vcpu, struct kvm_run *run)
return 1; return 1;
} }
/*
* Guest access to FP/ASIMD registers are routed to this handler only
* when the system doesn't support FP/ASIMD.
*/
static int handle_no_fpsimd(struct kvm_vcpu *vcpu, struct kvm_run *run)
{
kvm_inject_undefined(vcpu);
return 1;
}
/** /**
* kvm_handle_wfx - handle a wait-for-interrupts or wait-for-event * kvm_handle_wfx - handle a wait-for-interrupts or wait-for-event
* instruction executed by a guest * instruction executed by a guest
...@@ -144,6 +154,7 @@ static exit_handle_fn arm_exit_handlers[] = { ...@@ -144,6 +154,7 @@ static exit_handle_fn arm_exit_handlers[] = {
[ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug, [ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug,
[ESR_ELx_EC_BKPT32] = kvm_handle_guest_debug, [ESR_ELx_EC_BKPT32] = kvm_handle_guest_debug,
[ESR_ELx_EC_BRK64] = kvm_handle_guest_debug, [ESR_ELx_EC_BRK64] = kvm_handle_guest_debug,
[ESR_ELx_EC_FP_ASIMD] = handle_no_fpsimd,
}; };
static exit_handle_fn kvm_get_exit_handler(struct kvm_vcpu *vcpu) static exit_handle_fn kvm_get_exit_handler(struct kvm_vcpu *vcpu)
......
...@@ -106,9 +106,16 @@ el1_trap: ...@@ -106,9 +106,16 @@ el1_trap:
* x0: ESR_EC * x0: ESR_EC
*/ */
/* Guest accessed VFP/SIMD registers, save host, restore Guest */ /*
* We trap the first access to the FP/SIMD to save the host context
* and restore the guest context lazily.
* If FP/SIMD is not implemented, handle the trap and inject an
* undefined instruction exception to the guest.
*/
alternative_if_not ARM64_HAS_NO_FPSIMD
cmp x0, #ESR_ELx_EC_FP_ASIMD cmp x0, #ESR_ELx_EC_FP_ASIMD
b.eq __fpsimd_guest_restore b.eq __fpsimd_guest_restore
alternative_else_nop_endif
mrs x1, tpidr_el2 mrs x1, tpidr_el2
mov x0, #ARM_EXCEPTION_TRAP mov x0, #ARM_EXCEPTION_TRAP
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <asm/kvm_asm.h> #include <asm/kvm_asm.h>
#include <asm/kvm_emulate.h> #include <asm/kvm_emulate.h>
#include <asm/kvm_hyp.h> #include <asm/kvm_hyp.h>
#include <asm/fpsimd.h>
static bool __hyp_text __fpsimd_enabled_nvhe(void) static bool __hyp_text __fpsimd_enabled_nvhe(void)
{ {
...@@ -76,9 +77,11 @@ static void __hyp_text __activate_traps(struct kvm_vcpu *vcpu) ...@@ -76,9 +77,11 @@ static void __hyp_text __activate_traps(struct kvm_vcpu *vcpu)
* traps are only taken to EL2 if the operation would not otherwise * traps are only taken to EL2 if the operation would not otherwise
* trap to EL1. Therefore, always make sure that for 32-bit guests, * trap to EL1. Therefore, always make sure that for 32-bit guests,
* we set FPEXC.EN to prevent traps to EL1, when setting the TFP bit. * we set FPEXC.EN to prevent traps to EL1, when setting the TFP bit.
* If FP/ASIMD is not implemented, FPEXC is UNDEFINED and any access to
* it will cause an exception.
*/ */
val = vcpu->arch.hcr_el2; val = vcpu->arch.hcr_el2;
if (!(val & HCR_RW)) { if (!(val & HCR_RW) && system_supports_fpsimd()) {
write_sysreg(1 << 30, fpexc32_el2); write_sysreg(1 << 30, fpexc32_el2);
isb(); isb();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment