18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
28c2ecf20Sopenharmony_ci#ifndef _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H
38c2ecf20Sopenharmony_ci#define _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include <linux/const.h>
68c2ecf20Sopenharmony_ci#include <asm/reg.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define AMR_KUAP_BLOCK_READ	UL(0x4000000000000000)
98c2ecf20Sopenharmony_ci#define AMR_KUAP_BLOCK_WRITE	UL(0x8000000000000000)
108c2ecf20Sopenharmony_ci#define AMR_KUAP_BLOCKED	(AMR_KUAP_BLOCK_READ | AMR_KUAP_BLOCK_WRITE)
118c2ecf20Sopenharmony_ci#define AMR_KUAP_SHIFT		62
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#ifdef __ASSEMBLY__
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci.macro kuap_restore_amr	gpr1, gpr2
168c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP
178c2ecf20Sopenharmony_ci	BEGIN_MMU_FTR_SECTION_NESTED(67)
188c2ecf20Sopenharmony_ci	mfspr	\gpr1, SPRN_AMR
198c2ecf20Sopenharmony_ci	ld	\gpr2, STACK_REGS_KUAP(r1)
208c2ecf20Sopenharmony_ci	cmpd	\gpr1, \gpr2
218c2ecf20Sopenharmony_ci	beq	998f
228c2ecf20Sopenharmony_ci	isync
238c2ecf20Sopenharmony_ci	mtspr	SPRN_AMR, \gpr2
248c2ecf20Sopenharmony_ci	/* No isync required, see kuap_restore_amr() */
258c2ecf20Sopenharmony_ci998:
268c2ecf20Sopenharmony_ci	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
278c2ecf20Sopenharmony_ci#endif
288c2ecf20Sopenharmony_ci.endm
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP
318c2ecf20Sopenharmony_ci.macro kuap_check_amr gpr1, gpr2
328c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP_DEBUG
338c2ecf20Sopenharmony_ci	BEGIN_MMU_FTR_SECTION_NESTED(67)
348c2ecf20Sopenharmony_ci	mfspr	\gpr1, SPRN_AMR
358c2ecf20Sopenharmony_ci	li	\gpr2, (AMR_KUAP_BLOCKED >> AMR_KUAP_SHIFT)
368c2ecf20Sopenharmony_ci	sldi	\gpr2, \gpr2, AMR_KUAP_SHIFT
378c2ecf20Sopenharmony_ci999:	tdne	\gpr1, \gpr2
388c2ecf20Sopenharmony_ci	EMIT_BUG_ENTRY 999b, __FILE__, __LINE__, (BUGFLAG_WARNING | BUGFLAG_ONCE)
398c2ecf20Sopenharmony_ci	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
408c2ecf20Sopenharmony_ci#endif
418c2ecf20Sopenharmony_ci.endm
428c2ecf20Sopenharmony_ci#endif
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci.macro kuap_save_amr_and_lock gpr1, gpr2, use_cr, msr_pr_cr
458c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP
468c2ecf20Sopenharmony_ci	BEGIN_MMU_FTR_SECTION_NESTED(67)
478c2ecf20Sopenharmony_ci	.ifnb \msr_pr_cr
488c2ecf20Sopenharmony_ci	bne	\msr_pr_cr, 99f
498c2ecf20Sopenharmony_ci	.endif
508c2ecf20Sopenharmony_ci	mfspr	\gpr1, SPRN_AMR
518c2ecf20Sopenharmony_ci	std	\gpr1, STACK_REGS_KUAP(r1)
528c2ecf20Sopenharmony_ci	li	\gpr2, (AMR_KUAP_BLOCKED >> AMR_KUAP_SHIFT)
538c2ecf20Sopenharmony_ci	sldi	\gpr2, \gpr2, AMR_KUAP_SHIFT
548c2ecf20Sopenharmony_ci	cmpd	\use_cr, \gpr1, \gpr2
558c2ecf20Sopenharmony_ci	beq	\use_cr, 99f
568c2ecf20Sopenharmony_ci	// We don't isync here because we very recently entered via rfid
578c2ecf20Sopenharmony_ci	mtspr	SPRN_AMR, \gpr2
588c2ecf20Sopenharmony_ci	isync
598c2ecf20Sopenharmony_ci99:
608c2ecf20Sopenharmony_ci	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
618c2ecf20Sopenharmony_ci#endif
628c2ecf20Sopenharmony_ci.endm
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#else /* !__ASSEMBLY__ */
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci#include <linux/jump_label.h>
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ciDECLARE_STATIC_KEY_FALSE(uaccess_flush_key);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci#include <asm/mmu.h>
738c2ecf20Sopenharmony_ci#include <asm/ptrace.h>
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic inline void kuap_restore_amr(struct pt_regs *regs, unsigned long amr)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	if (mmu_has_feature(MMU_FTR_RADIX_KUAP) && unlikely(regs->kuap != amr)) {
788c2ecf20Sopenharmony_ci		isync();
798c2ecf20Sopenharmony_ci		mtspr(SPRN_AMR, regs->kuap);
808c2ecf20Sopenharmony_ci		/*
818c2ecf20Sopenharmony_ci		 * No isync required here because we are about to RFI back to
828c2ecf20Sopenharmony_ci		 * previous context before any user accesses would be made,
838c2ecf20Sopenharmony_ci		 * which is a CSI.
848c2ecf20Sopenharmony_ci		 */
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic inline unsigned long kuap_get_and_check_amr(void)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	if (mmu_has_feature(MMU_FTR_RADIX_KUAP)) {
918c2ecf20Sopenharmony_ci		unsigned long amr = mfspr(SPRN_AMR);
928c2ecf20Sopenharmony_ci		if (IS_ENABLED(CONFIG_PPC_KUAP_DEBUG)) /* kuap_check_amr() */
938c2ecf20Sopenharmony_ci			WARN_ON_ONCE(amr != AMR_KUAP_BLOCKED);
948c2ecf20Sopenharmony_ci		return amr;
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci	return 0;
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic inline void kuap_check_amr(void)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_PPC_KUAP_DEBUG) && mmu_has_feature(MMU_FTR_RADIX_KUAP))
1028c2ecf20Sopenharmony_ci		WARN_ON_ONCE(mfspr(SPRN_AMR) != AMR_KUAP_BLOCKED);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci/*
1068c2ecf20Sopenharmony_ci * We support individually allowing read or write, but we don't support nesting
1078c2ecf20Sopenharmony_ci * because that would require an expensive read/modify write of the AMR.
1088c2ecf20Sopenharmony_ci */
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic inline unsigned long get_kuap(void)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	/*
1138c2ecf20Sopenharmony_ci	 * We return AMR_KUAP_BLOCKED when we don't support KUAP because
1148c2ecf20Sopenharmony_ci	 * prevent_user_access_return needs to return AMR_KUAP_BLOCKED to
1158c2ecf20Sopenharmony_ci	 * cause restore_user_access to do a flush.
1168c2ecf20Sopenharmony_ci	 *
1178c2ecf20Sopenharmony_ci	 * This has no effect in terms of actually blocking things on hash,
1188c2ecf20Sopenharmony_ci	 * so it doesn't break anything.
1198c2ecf20Sopenharmony_ci	 */
1208c2ecf20Sopenharmony_ci	if (!early_mmu_has_feature(MMU_FTR_RADIX_KUAP))
1218c2ecf20Sopenharmony_ci		return AMR_KUAP_BLOCKED;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return mfspr(SPRN_AMR);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic inline void set_kuap(unsigned long value)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	if (!early_mmu_has_feature(MMU_FTR_RADIX_KUAP))
1298c2ecf20Sopenharmony_ci		return;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/*
1328c2ecf20Sopenharmony_ci	 * ISA v3.0B says we need a CSI (Context Synchronising Instruction) both
1338c2ecf20Sopenharmony_ci	 * before and after the move to AMR. See table 6 on page 1134.
1348c2ecf20Sopenharmony_ci	 */
1358c2ecf20Sopenharmony_ci	isync();
1368c2ecf20Sopenharmony_ci	mtspr(SPRN_AMR, value);
1378c2ecf20Sopenharmony_ci	isync();
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic inline bool
1418c2ecf20Sopenharmony_cibad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
1448c2ecf20Sopenharmony_ci		    (regs->kuap & (is_write ? AMR_KUAP_BLOCK_WRITE : AMR_KUAP_BLOCK_READ)),
1458c2ecf20Sopenharmony_ci		    "Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci#else /* CONFIG_PPC_KUAP */
1488c2ecf20Sopenharmony_cistatic inline void kuap_restore_amr(struct pt_regs *regs, unsigned long amr) { }
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic inline unsigned long kuap_get_and_check_amr(void)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	return 0UL;
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_cistatic inline unsigned long get_kuap(void)
1568c2ecf20Sopenharmony_ci{
1578c2ecf20Sopenharmony_ci	return AMR_KUAP_BLOCKED;
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic inline void set_kuap(unsigned long value) { }
1618c2ecf20Sopenharmony_ci#endif /* !CONFIG_PPC_KUAP */
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic __always_inline void allow_user_access(void __user *to, const void __user *from,
1648c2ecf20Sopenharmony_ci					      unsigned long size, unsigned long dir)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	// This is written so we can resolve to a single case at build time
1678c2ecf20Sopenharmony_ci	BUILD_BUG_ON(!__builtin_constant_p(dir));
1688c2ecf20Sopenharmony_ci	if (dir == KUAP_READ)
1698c2ecf20Sopenharmony_ci		set_kuap(AMR_KUAP_BLOCK_WRITE);
1708c2ecf20Sopenharmony_ci	else if (dir == KUAP_WRITE)
1718c2ecf20Sopenharmony_ci		set_kuap(AMR_KUAP_BLOCK_READ);
1728c2ecf20Sopenharmony_ci	else if (dir == KUAP_READ_WRITE)
1738c2ecf20Sopenharmony_ci		set_kuap(0);
1748c2ecf20Sopenharmony_ci	else
1758c2ecf20Sopenharmony_ci		BUILD_BUG();
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic inline void prevent_user_access(void __user *to, const void __user *from,
1798c2ecf20Sopenharmony_ci				       unsigned long size, unsigned long dir)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	set_kuap(AMR_KUAP_BLOCKED);
1828c2ecf20Sopenharmony_ci	if (static_branch_unlikely(&uaccess_flush_key))
1838c2ecf20Sopenharmony_ci		do_uaccess_flush();
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic inline unsigned long prevent_user_access_return(void)
1878c2ecf20Sopenharmony_ci{
1888c2ecf20Sopenharmony_ci	unsigned long flags = get_kuap();
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	set_kuap(AMR_KUAP_BLOCKED);
1918c2ecf20Sopenharmony_ci	if (static_branch_unlikely(&uaccess_flush_key))
1928c2ecf20Sopenharmony_ci		do_uaccess_flush();
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return flags;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic inline void restore_user_access(unsigned long flags)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	set_kuap(flags);
2008c2ecf20Sopenharmony_ci	if (static_branch_unlikely(&uaccess_flush_key) && flags == AMR_KUAP_BLOCKED)
2018c2ecf20Sopenharmony_ci		do_uaccess_flush();
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci#endif /* __ASSEMBLY__ */
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci#endif /* _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H */
206