162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-only */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * A stand-alone ticket spinlock implementation for use by the non-VHE
462306a36Sopenharmony_ci * KVM hypervisor code running at EL2.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2020 Google LLC
762306a36Sopenharmony_ci * Author: Will Deacon <will@kernel.org>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Heavily based on the implementation removed by c11090474d70 which was:
1062306a36Sopenharmony_ci * Copyright (C) 2012 ARM Ltd.
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#ifndef __ARM64_KVM_NVHE_SPINLOCK_H__
1462306a36Sopenharmony_ci#define __ARM64_KVM_NVHE_SPINLOCK_H__
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/alternative.h>
1762306a36Sopenharmony_ci#include <asm/lse.h>
1862306a36Sopenharmony_ci#include <asm/rwonce.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_citypedef union hyp_spinlock {
2162306a36Sopenharmony_ci	u32	__val;
2262306a36Sopenharmony_ci	struct {
2362306a36Sopenharmony_ci#ifdef __AARCH64EB__
2462306a36Sopenharmony_ci		u16 next, owner;
2562306a36Sopenharmony_ci#else
2662306a36Sopenharmony_ci		u16 owner, next;
2762306a36Sopenharmony_ci#endif
2862306a36Sopenharmony_ci	};
2962306a36Sopenharmony_ci} hyp_spinlock_t;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define __HYP_SPIN_LOCK_INITIALIZER \
3262306a36Sopenharmony_ci	{ .__val = 0 }
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define __HYP_SPIN_LOCK_UNLOCKED \
3562306a36Sopenharmony_ci	((hyp_spinlock_t) __HYP_SPIN_LOCK_INITIALIZER)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define DEFINE_HYP_SPINLOCK(x)	hyp_spinlock_t x = __HYP_SPIN_LOCK_UNLOCKED
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define hyp_spin_lock_init(l)						\
4062306a36Sopenharmony_cido {									\
4162306a36Sopenharmony_ci	*(l) = __HYP_SPIN_LOCK_UNLOCKED;				\
4262306a36Sopenharmony_ci} while (0)
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic inline void hyp_spin_lock(hyp_spinlock_t *lock)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	u32 tmp;
4762306a36Sopenharmony_ci	hyp_spinlock_t lockval, newval;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	asm volatile(
5062306a36Sopenharmony_ci	/* Atomically increment the next ticket. */
5162306a36Sopenharmony_ci	ARM64_LSE_ATOMIC_INSN(
5262306a36Sopenharmony_ci	/* LL/SC */
5362306a36Sopenharmony_ci"	prfm	pstl1strm, %3\n"
5462306a36Sopenharmony_ci"1:	ldaxr	%w0, %3\n"
5562306a36Sopenharmony_ci"	add	%w1, %w0, #(1 << 16)\n"
5662306a36Sopenharmony_ci"	stxr	%w2, %w1, %3\n"
5762306a36Sopenharmony_ci"	cbnz	%w2, 1b\n",
5862306a36Sopenharmony_ci	/* LSE atomics */
5962306a36Sopenharmony_ci"	mov	%w2, #(1 << 16)\n"
6062306a36Sopenharmony_ci"	ldadda	%w2, %w0, %3\n"
6162306a36Sopenharmony_ci	__nops(3))
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* Did we get the lock? */
6462306a36Sopenharmony_ci"	eor	%w1, %w0, %w0, ror #16\n"
6562306a36Sopenharmony_ci"	cbz	%w1, 3f\n"
6662306a36Sopenharmony_ci	/*
6762306a36Sopenharmony_ci	 * No: spin on the owner. Send a local event to avoid missing an
6862306a36Sopenharmony_ci	 * unlock before the exclusive load.
6962306a36Sopenharmony_ci	 */
7062306a36Sopenharmony_ci"	sevl\n"
7162306a36Sopenharmony_ci"2:	wfe\n"
7262306a36Sopenharmony_ci"	ldaxrh	%w2, %4\n"
7362306a36Sopenharmony_ci"	eor	%w1, %w2, %w0, lsr #16\n"
7462306a36Sopenharmony_ci"	cbnz	%w1, 2b\n"
7562306a36Sopenharmony_ci	/* We got the lock. Critical section starts here. */
7662306a36Sopenharmony_ci"3:"
7762306a36Sopenharmony_ci	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp), "+Q" (*lock)
7862306a36Sopenharmony_ci	: "Q" (lock->owner)
7962306a36Sopenharmony_ci	: "memory");
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic inline void hyp_spin_unlock(hyp_spinlock_t *lock)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	u64 tmp;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	asm volatile(
8762306a36Sopenharmony_ci	ARM64_LSE_ATOMIC_INSN(
8862306a36Sopenharmony_ci	/* LL/SC */
8962306a36Sopenharmony_ci	"	ldrh	%w1, %0\n"
9062306a36Sopenharmony_ci	"	add	%w1, %w1, #1\n"
9162306a36Sopenharmony_ci	"	stlrh	%w1, %0",
9262306a36Sopenharmony_ci	/* LSE atomics */
9362306a36Sopenharmony_ci	"	mov	%w1, #1\n"
9462306a36Sopenharmony_ci	"	staddlh	%w1, %0\n"
9562306a36Sopenharmony_ci	__nops(1))
9662306a36Sopenharmony_ci	: "=Q" (lock->owner), "=&r" (tmp)
9762306a36Sopenharmony_ci	:
9862306a36Sopenharmony_ci	: "memory");
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic inline bool hyp_spin_is_locked(hyp_spinlock_t *lock)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	hyp_spinlock_t lockval = READ_ONCE(*lock);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return lockval.owner != lockval.next;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci#ifdef CONFIG_NVHE_EL2_DEBUG
10962306a36Sopenharmony_cistatic inline void hyp_assert_lock_held(hyp_spinlock_t *lock)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	/*
11262306a36Sopenharmony_ci	 * The __pkvm_init() path accesses protected data-structures without
11362306a36Sopenharmony_ci	 * holding locks as the other CPUs are guaranteed to not enter EL2
11462306a36Sopenharmony_ci	 * concurrently at this point in time. The point by which EL2 is
11562306a36Sopenharmony_ci	 * initialized on all CPUs is reflected in the pkvm static key, so
11662306a36Sopenharmony_ci	 * wait until it is set before checking the lock state.
11762306a36Sopenharmony_ci	 */
11862306a36Sopenharmony_ci	if (static_branch_likely(&kvm_protected_mode_initialized))
11962306a36Sopenharmony_ci		BUG_ON(!hyp_spin_is_locked(lock));
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci#else
12262306a36Sopenharmony_cistatic inline void hyp_assert_lock_held(hyp_spinlock_t *lock) { }
12362306a36Sopenharmony_ci#endif
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci#endif /* __ARM64_KVM_NVHE_SPINLOCK_H__ */
126