Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -38,8 +38,13 @@
#define ARM64_HAS_32BIT_EL0			13
#define ARM64_HYP_OFFSET_LOW			14
#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__

@@ -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));
}

static inline bool system_supports_fpsimd(void)
{
	return !cpus_have_const_cap(ARM64_HAS_NO_FPSIMD);
}

#endif /* __ASSEMBLY__ */

#endif
+2 −1
Original line number Diff line number Diff line
@@ -9,8 +9,9 @@
 */

#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)

+15 −0
Original line number Diff line number Diff line
@@ -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();
}

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[] = {
	{
		.desc = "GIC system register CPU interface",
@@ -829,6 +837,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
		.def_scope = SCOPE_SYSTEM,
		.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,
	},
	{},
};

+14 −0
Original line number Diff line number Diff line
@@ -127,6 +127,8 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs)

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
	 * the registers is in fact the most recent userland FPSIMD state of
@@ -157,6 +159,8 @@ void fpsimd_thread_switch(struct task_struct *next)

void fpsimd_flush_thread(void)
{
	if (!system_supports_fpsimd())
		return;
	memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
	fpsimd_flush_task_state(current);
	set_thread_flag(TIF_FOREIGN_FPSTATE);
@@ -168,6 +172,8 @@ void fpsimd_flush_thread(void)
 */
void fpsimd_preserve_current_state(void)
{
	if (!system_supports_fpsimd())
		return;
	preempt_disable();
	if (!test_thread_flag(TIF_FOREIGN_FPSTATE))
		fpsimd_save_state(&current->thread.fpsimd_state);
@@ -181,6 +187,8 @@ void fpsimd_preserve_current_state(void)
 */
void fpsimd_restore_current_state(void)
{
	if (!system_supports_fpsimd())
		return;
	preempt_disable();
	if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
		struct fpsimd_state *st = &current->thread.fpsimd_state;
@@ -199,6 +207,8 @@ void fpsimd_restore_current_state(void)
 */
void fpsimd_update_current_state(struct fpsimd_state *state)
{
	if (!system_supports_fpsimd())
		return;
	preempt_disable();
	fpsimd_load_state(state);
	if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
@@ -228,6 +238,8 @@ static DEFINE_PER_CPU(struct fpsimd_partial_state, softirq_fpsimdstate);
 */
void kernel_neon_begin_partial(u32 num_regs)
{
	if (WARN_ON(!system_supports_fpsimd()))
		return;
	if (in_interrupt()) {
		struct fpsimd_partial_state *s = this_cpu_ptr(
			in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
@@ -252,6 +264,8 @@ EXPORT_SYMBOL(kernel_neon_begin_partial);

void kernel_neon_end(void)
{
	if (!system_supports_fpsimd())
		return;
	if (in_interrupt()) {
		struct fpsimd_partial_state *s = this_cpu_ptr(
			in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
+11 −0
Original line number Diff line number Diff line
@@ -57,6 +57,16 @@ static int handle_smc(struct kvm_vcpu *vcpu, struct kvm_run *run)
	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
 *		    instruction executed by a guest
@@ -144,6 +154,7 @@ static exit_handle_fn arm_exit_handlers[] = {
	[ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug,
	[ESR_ELx_EC_BKPT32]	= 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)
Loading