Commit 34f66c4c authored by Mark Rutland's avatar Mark Rutland Committed by Catalin Marinas
Browse files

arm64: Use a positive cpucap for FP/SIMD



Currently we have a negative cpucap which describes the *absence* of
FP/SIMD rather than *presence* of FP/SIMD. This largely works, but is
somewhat awkward relative to other cpucaps that describe the presence of
a feature, and it would be nicer to have a cpucap which describes the
presence of FP/SIMD:

* This will allow the cpucap to be treated as a standard
  ARM64_CPUCAP_SYSTEM_FEATURE, which can be detected with the standard
  has_cpuid_feature() function and ARM64_CPUID_FIELDS() description.

* This ensures that the cpucap will only transition from not-present to
  present, reducing the risk of unintentional and/or unsafe usage of
  FP/SIMD before cpucaps are finalized.

* This will allow using arm64_cpu_capabilities::cpu_enable() to enable
  the use of FP/SIMD later, with FP/SIMD being disabled at boot time
  otherwise. This will ensure that any unintentional and/or unsafe usage
  of FP/SIMD prior to this is trapped, and will ensure that FP/SIMD is
  never unintentionally enabled for userspace in mismatched big.LITTLE
  systems.

This patch replaces the negative ARM64_HAS_NO_FPSIMD cpucap with a
positive ARM64_HAS_FPSIMD cpucap, making changes as described above.
Note that as FP/SIMD will now be trapped when not supported system-wide,
do_fpsimd_acc() must handle these traps in the same way as for SVE and
SME. The commentary in fpsimd_restore_current_state() is updated to
describe the new scheme.

No users of system_supports_fpsimd() need to know that FP/SIMD is
available prior to alternatives being patched, so this is updated to
use alternative_has_cap_likely() to check for the ARM64_HAS_FPSIMD
cpucap, without generating code to test the system_cpucaps bitmap.

Signed-off-by: default avatarMark Rutland <mark.rutland@arm.com>
Reviewed-by: default avatarMark Brown <broonie@kernel.org>
Cc: Suzuki K Poulose <suzuki.poulose@arm.com>
Cc: Will Deacon <will@kernel.org>
Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
parent 14567ba4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -760,7 +760,7 @@ static inline bool system_supports_mixed_endian(void)

static __always_inline bool system_supports_fpsimd(void)
{
	return !cpus_have_const_cap(ARM64_HAS_NO_FPSIMD);
	return alternative_has_cap_likely(ARM64_HAS_FPSIMD);
}

static inline bool system_uses_hw_pan(void)
+1 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ extern void sme_save_state(void *state, int zt);
extern void sme_load_state(void const *state, int zt);

struct arm64_cpu_capabilities;
extern void cpu_enable_fpsimd(const struct arm64_cpu_capabilities *__unused);
extern void cpu_enable_sve(const struct arm64_cpu_capabilities *__unused);
extern void cpu_enable_sme(const struct arm64_cpu_capabilities *__unused);
extern void cpu_enable_sme2(const struct arm64_cpu_capabilities *__unused);
+5 −13
Original line number Diff line number Diff line
@@ -1580,14 +1580,6 @@ static bool has_no_hw_prefetch(const struct arm64_cpu_capabilities *entry, int _
		MIDR_CPU_VAR_REV(1, MIDR_REVISION_MASK));
}

static bool has_no_fpsimd(const struct arm64_cpu_capabilities *entry, int __unused)
{
	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);

	return cpuid_feature_extract_signed_field(pfr0,
					ID_AA64PFR0_EL1_FP_SHIFT) < 0;
}

static bool has_cache_idc(const struct arm64_cpu_capabilities *entry,
			  int scope)
{
@@ -2398,11 +2390,11 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
		ARM64_CPUID_FIELDS(ID_AA64PFR0_EL1, CSV3, IMP)
	},
	{
		/* FP/SIMD is not implemented */
		.capability = ARM64_HAS_NO_FPSIMD,
		.type = ARM64_CPUCAP_BOOT_RESTRICTED_CPU_LOCAL_FEATURE,
		.min_field_value = 0,
		.matches = has_no_fpsimd,
		.capability = ARM64_HAS_FPSIMD,
		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
		.matches = has_cpuid_feature,
		.cpu_enable = cpu_enable_fpsimd,
		ARM64_CPUID_FIELDS(ID_AA64PFR0_EL1, FP, IMP)
	},
#ifdef CONFIG_ARM64_PMEM
	{
+35 −9
Original line number Diff line number Diff line
@@ -1520,8 +1520,17 @@ void do_sme_acc(unsigned long esr, struct pt_regs *regs)
 */
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs)
{
	/* TODO: implement lazy context saving/restoring */
	WARN_ON(1);
	/* Even if we chose not to use FPSIMD, the hardware could still trap: */
	if (!system_supports_fpsimd()) {
		force_signal_inject(SIGILL, ILL_ILLOPC, regs->pc, 0);
		return;
	}

	/*
	 * When FPSIMD is enabled, we should never take a trap unless something
	 * has gone very wrong.
	 */
	BUG();
}

/*
@@ -1762,13 +1771,23 @@ void fpsimd_bind_state_to_cpu(struct cpu_fp_state *state)
void fpsimd_restore_current_state(void)
{
	/*
	 * For the tasks that were created before we detected the absence of
	 * FP/SIMD, the TIF_FOREIGN_FPSTATE could be set via fpsimd_thread_switch(),
	 * e.g, init. This could be then inherited by the children processes.
	 * If we later detect that the system doesn't support FP/SIMD,
	 * we must clear the flag for  all the tasks to indicate that the
	 * FPSTATE is clean (as we can't have one) to avoid looping for ever in
	 * do_notify_resume().
	 * TIF_FOREIGN_FPSTATE is set on the init task and copied by
	 * arch_dup_task_struct() regardless of whether FP/SIMD is detected.
	 * Thus user threads can have this set even when FP/SIMD hasn't been
	 * detected.
	 *
	 * When FP/SIMD is detected, begin_new_exec() will set
	 * TIF_FOREIGN_FPSTATE via flush_thread() -> fpsimd_flush_thread(),
	 * and fpsimd_thread_switch() will set TIF_FOREIGN_FPSTATE when
	 * switching tasks. We detect FP/SIMD before we exec the first user
	 * process, ensuring this has TIF_FOREIGN_FPSTATE set and
	 * do_notify_resume() will call fpsimd_restore_current_state() to
	 * install the user FP/SIMD context.
	 *
	 * When FP/SIMD is not detected, nothing else will clear or set
	 * TIF_FOREIGN_FPSTATE prior to the first return to userspace, and
	 * we must clear TIF_FOREIGN_FPSTATE to avoid do_notify_resume()
	 * looping forever calling fpsimd_restore_current_state().
	 */
	if (!system_supports_fpsimd()) {
		clear_thread_flag(TIF_FOREIGN_FPSTATE);
@@ -2101,6 +2120,13 @@ static inline void fpsimd_hotplug_init(void)
static inline void fpsimd_hotplug_init(void) { }
#endif

void cpu_enable_fpsimd(const struct arm64_cpu_capabilities *__always_unused p)
{
	unsigned long enable = CPACR_EL1_FPEN_EL1EN | CPACR_EL1_FPEN_EL0EN;
	write_sysreg(read_sysreg(CPACR_EL1) | enable, CPACR_EL1);
	isb();
}

/*
 * FP/SIMD support code initialisation.
 */
+1 −2
Original line number Diff line number Diff line
@@ -405,8 +405,7 @@ SYM_FUNC_START(__cpu_setup)
	tlbi	vmalle1				// Invalidate local TLB
	dsb	nsh

	mov	x1, #3 << 20
	msr	cpacr_el1, x1			// Enable FP/ASIMD
	msr	cpacr_el1, xzr			// Reset cpacr_el1
	mov	x1, #1 << 12			// Reset mdscr_el1 and disable
	msr	mdscr_el1, x1			// access to the DCC from EL0
	isb					// Unmask debug exceptions now,
Loading