#include <asm/mman.h>
#include <asm/mmu_context.h>
#include <asm/mmu.h>
#include <asm/setup.h>
#include <asm/smp.h>
#include <asm/firmware.h>
#include <linux/pkeys.h>
#include <linux/of_fdt.h>
int num_pkey;
u32 reserved_allocation_mask __ro_after_init;
static u32 initial_allocation_mask __ro_after_init;
u64 default_amr __ro_after_init = ~0x0UL;
u64 default_iamr __ro_after_init = 0x5555555555555555UL;
u64 default_uamor __ro_after_init;
EXPORT_SYMBOL(default_amr);
static int execute_only_key = 2;
static bool pkey_execute_disable_supported;
#define AMR_BITS_PER_PKEY 2
#define AMR_RD_BIT 0x1UL
#define AMR_WR_BIT 0x2UL
#define IAMR_EX_BIT 0x1UL
#define PKEY_REG_BITS (sizeof(u64) * 8)
#define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey+1) * AMR_BITS_PER_PKEY))
static int __init dt_scan_storage_keys(unsigned long node,
const char *uname, int depth,
void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *prop;
int *pkeys_total = (int *) data;
if (type == NULL || strcmp(type, "cpu") != 0)
return 0;
prop = of_get_flat_dt_prop(node, "ibm,processor-storage-keys", NULL);
if (!prop)
return 0;
*pkeys_total = be32_to_cpu(prop[0]);
return 1;
}
static int __init scan_pkey_feature(void)
{
int ret;
int pkeys_total = 0;
if (early_radix_enabled())
return 0;
ret = of_scan_flat_dt(dt_scan_storage_keys, &pkeys_total);
if (ret == 0) {
if (!firmware_has_feature(FW_FEATURE_LPAR)) {
unsigned long pvr = mfspr(SPRN_PVR);
if (PVR_VER(pvr) == PVR_POWER8 || PVR_VER(pvr) == PVR_POWER8E ||
PVR_VER(pvr) == PVR_POWER8NVL || PVR_VER(pvr) == PVR_POWER9 ||
PVR_VER(pvr) == PVR_HX_C2000)
pkeys_total = 32;
}
}
#ifdef CONFIG_PPC_MEM_KEYS
pkeys_total = min_t(int, pkeys_total,
((ARCH_VM_PKEY_FLAGS >> VM_PKEY_SHIFT) + 1));
#endif
return pkeys_total;
}
void __init pkey_early_init_devtree(void)
{
int pkeys_total, i;
#ifdef CONFIG_PPC_MEM_KEYS
BUILD_BUG_ON(PKEY_DISABLE_EXECUTE &
(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE));
BUILD_BUG_ON(__builtin_clzl(ARCH_VM_PKEY_FLAGS >> VM_PKEY_SHIFT) +
__builtin_popcountl(ARCH_VM_PKEY_FLAGS >> VM_PKEY_SHIFT)
!= (sizeof(u64) * BITS_PER_BYTE));
#endif
if (!early_cpu_has_feature(CPU_FTR_ARCH_206))
return;
pkeys_total = scan_pkey_feature();
if (!pkeys_total)
goto out;
default_uamor = ~0x0UL;
cur_cpu_spec->mmu_features |= MMU_FTR_PKEY;
if (pvr_version_is(PVR_POWER7) || pvr_version_is(PVR_POWER7p))
pkey_execute_disable_supported = false;
else
pkey_execute_disable_supported = true;
#ifdef CONFIG_PPC_4K_PAGES
num_pkey = min(8, pkeys_total);
#else
num_pkey = pkeys_total;
#endif
if (unlikely(num_pkey <= execute_only_key) || !pkey_execute_disable_supported) {
execute_only_key = -1;
} else {
reserved_allocation_mask |= (0x1 << execute_only_key);
default_amr |= (0x3ul << pkeyshift(execute_only_key));
default_iamr &= ~(0x1ul << pkeyshift(execute_only_key));
default_uamor &= ~(0x3ul << pkeyshift(execute_only_key));
}
if (unlikely(num_pkey <= 3)) {
disable_kuep = true;
disable_kuap = true;
WARN(1, "Disabling kernel user protection due to low (%d) max supported keys\n", num_pkey);
} else {
reserved_allocation_mask |= (0x1 << 3);
default_amr &= ~(0x3ul << pkeyshift(3));
default_iamr &= ~(0x1ul << pkeyshift(3));
default_uamor &= ~(0x3ul << pkeyshift(3));
}
default_amr &= ~(0x3ul << pkeyshift(0));
default_iamr &= ~(0x1ul << pkeyshift(0));
default_uamor &= ~(0x3ul << pkeyshift(0));
initial_allocation_mask |= (0x1 << 0);
reserved_allocation_mask |= (0x1 << 1);
default_uamor &= ~(0x3ul << pkeyshift(1));
for (i = num_pkey; i < 32 ; i++) {
reserved_allocation_mask |= (0x1 << i);
default_uamor &= ~(0x3ul << pkeyshift(i));
}
initial_allocation_mask |= reserved_allocation_mask;
pr_info("Enabling pkeys with max key count %d\n", num_pkey);
out:
mtspr(SPRN_UAMOR, default_uamor);
return;
}
#ifdef CONFIG_PPC_KUEP
void setup_kuep(bool disabled)
{
if (disabled)
return;
if (!early_radix_enabled() && !early_mmu_has_feature(MMU_FTR_PKEY))
return;
if (smp_processor_id() == boot_cpuid) {
pr_info("Activating Kernel Userspace Execution Prevention\n");
cur_cpu_spec->mmu_features |= MMU_FTR_BOOK3S_KUEP;
}
mtspr(SPRN_IAMR, AMR_KUEP_BLOCKED);
isync();
}
#endif
#ifdef CONFIG_PPC_KUAP
void setup_kuap(bool disabled)
{
if (disabled)
return;
if (!early_radix_enabled() && !early_mmu_has_feature(MMU_FTR_PKEY))
return;
if (smp_processor_id() == boot_cpuid) {
pr_info("Activating Kernel Userspace Access Prevention\n");
cur_cpu_spec->mmu_features |= MMU_FTR_KUAP;
}
mtspr(SPRN_AMR, AMR_KUAP_BLOCKED);
isync();
}
#endif
#ifdef CONFIG_PPC_MEM_KEYS
void pkey_mm_init(struct mm_struct *mm)
{
if (!mmu_has_feature(MMU_FTR_PKEY))
return;
mm_pkey_allocation_map(mm) = initial_allocation_mask;
mm->context.execute_only_pkey = execute_only_key;
}
static inline void init_amr(int pkey, u8 init_bits)
{
u64 new_amr_bits = (((u64)init_bits & 0x3UL) << pkeyshift(pkey));
u64 old_amr = current_thread_amr() & ~((u64)(0x3ul) << pkeyshift(pkey));
current->thread.regs->amr = old_amr | new_amr_bits;
}
static inline void init_iamr(int pkey, u8 init_bits)
{
u64 new_iamr_bits = (((u64)init_bits & 0x1UL) << pkeyshift(pkey));
u64 old_iamr = current_thread_iamr() & ~((u64)(0x1ul) << pkeyshift(pkey));
if (!likely(pkey_execute_disable_supported))
return;
current->thread.regs->iamr = old_iamr | new_iamr_bits;
}
int __arch_set_user_pkey_access(struct task_struct *tsk, int pkey,
unsigned long init_val)
{
u64 new_amr_bits = 0x0ul;
u64 new_iamr_bits = 0x0ul;
u64 pkey_bits, uamor_pkey_bits;
pkey_bits = 0x3ul << pkeyshift(pkey);
uamor_pkey_bits = (default_uamor & pkey_bits);
if (uamor_pkey_bits != pkey_bits)
return -EINVAL;
if (init_val & PKEY_DISABLE_EXECUTE) {
if (!pkey_execute_disable_supported)
return -EINVAL;
new_iamr_bits |= IAMR_EX_BIT;
}
init_iamr(pkey, new_iamr_bits);
if (init_val & PKEY_DISABLE_ACCESS)
new_amr_bits |= AMR_RD_BIT | AMR_WR_BIT;
else if (init_val & PKEY_DISABLE_WRITE)
new_amr_bits |= AMR_WR_BIT;
init_amr(pkey, new_amr_bits);
return 0;
}
int execute_only_pkey(struct mm_struct *mm)
{
return mm->context.execute_only_pkey;
}
static inline bool vma_is_pkey_exec_only(struct vm_area_struct *vma)
{
if ((vma->vm_flags & VM_ACCESS_FLAGS) != VM_EXEC)
return false;
return (vma_pkey(vma) == vma->vm_mm->context.execute_only_pkey);
}
int __arch_override_mprotect_pkey(struct vm_area_struct *vma, int prot,
int pkey)
{
if (vma_is_pkey_exec_only(vma) && (prot != PROT_EXEC))
return 0;
if (prot == PROT_EXEC) {
pkey = execute_only_pkey(vma->vm_mm);
if (pkey > 0)
return pkey;
}
return vma_pkey(vma);
}
static bool pkey_access_permitted(int pkey, bool write, bool execute)
{
int pkey_shift;
u64 amr;
pkey_shift = pkeyshift(pkey);
if (execute)
return !(current_thread_iamr() & (IAMR_EX_BIT << pkey_shift));
amr = current_thread_amr();
if (write)
return !(amr & (AMR_WR_BIT << pkey_shift));
return !(amr & (AMR_RD_BIT << pkey_shift));
}
bool arch_pte_access_permitted(u64 pte, bool write, bool execute)
{
if (!mmu_has_feature(MMU_FTR_PKEY))
return true;
return pkey_access_permitted(pte_to_pkey_bits(pte), write, execute);
}
bool arch_vma_access_permitted(struct vm_area_struct *vma, bool write,
bool execute, bool foreign)
{
if (!mmu_has_feature(MMU_FTR_PKEY))
return true;
if (foreign || vma_is_foreign(vma))
return true;
return pkey_access_permitted(vma_pkey(vma), write, execute);
}
void arch_dup_pkeys(struct mm_struct *oldmm, struct mm_struct *mm)
{
if (!mmu_has_feature(MMU_FTR_PKEY))
return;
mm_pkey_allocation_map(mm) = mm_pkey_allocation_map(oldmm);
mm->context.execute_only_pkey = oldmm->context.execute_only_pkey;
}
#endif