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