This creates an mm context to be used for OPAL V4 calls, which is populated with ptes according to querying OPAL_FIND_VM_AREA.
Signed-off-by: Nicholas Piggin <npig...@gmail.com> --- arch/powerpc/platforms/powernv/opal-call.c | 21 +++- arch/powerpc/platforms/powernv/opal.c | 119 ++++++++++++++++++++- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/arch/powerpc/platforms/powernv/opal-call.c b/arch/powerpc/platforms/powernv/opal-call.c index e62a74dfb3d0..4bdad3d2fa18 100644 --- a/arch/powerpc/platforms/powernv/opal-call.c +++ b/arch/powerpc/platforms/powernv/opal-call.c @@ -104,6 +104,9 @@ typedef int64_t (*opal_v4_le_entry_fn)(uint64_t r3, uint64_t r4, uint64_t r5, uint64_t r6, uint64_t r7, uint64_t r8, uint64_t r9, uint64_t r10); +extern struct mm_struct *opal_mm; +extern bool opal_mm_enabled; + static int64_t opal_call(int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t opcode) { @@ -117,6 +120,8 @@ static int64_t opal_call(int64_t a0, int64_t a1, int64_t a2, int64_t a3, fn = (opal_v4_le_entry_fn)(opal.v4_le_entry); if (fn) { + struct mm_struct *old_mm = current->active_mm; + if (!mmu) { BUG_ON(msr & MSR_EE); ret = fn(opcode, a0, a1, a2, a3, a4, a5, a6); @@ -126,11 +131,23 @@ static int64_t opal_call(int64_t a0, int64_t a1, int64_t a2, int64_t a3, local_irq_save(flags); hard_irq_disable(); /* XXX r13 */ msr &= ~MSR_EE; - mtmsr(msr & ~(MSR_IR|MSR_DR)); + if (!opal_mm_enabled) + mtmsr(msr & ~(MSR_IR|MSR_DR)); + + if (opal_mm_enabled && old_mm != opal_mm) { + current->active_mm = opal_mm; + switch_mm_irqs_off(NULL, opal_mm, current); + } ret = fn(opcode, a0, a1, a2, a3, a4, a5, a6); - mtmsr(msr); + if (opal_mm_enabled && old_mm != opal_mm) { + current->active_mm = old_mm; + switch_mm_irqs_off(NULL, old_mm, current); + } + + if (!opal_mm_enabled) + mtmsr(msr); local_irq_restore(flags); return ret; diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c index d00772d40680..98d6d7fc5411 100644 --- a/arch/powerpc/platforms/powernv/opal.c +++ b/arch/powerpc/platforms/powernv/opal.c @@ -45,6 +45,10 @@ struct opal_msg_node { static DEFINE_SPINLOCK(msg_list_lock); static LIST_HEAD(msg_list); +struct mm_struct *opal_mm __read_mostly; +bool opal_v4_present __read_mostly; +bool opal_mm_enabled __read_mostly; + /* /sys/firmware/opal */ struct kobject *opal_kobj __read_mostly; @@ -172,7 +176,12 @@ int __init early_init_dt_scan_opal(unsigned long node, if (of_flat_dt_is_compatible(node, "ibm,opal-v3")) { powerpc_firmware_features |= FW_FEATURE_OPAL; - pr_debug("OPAL detected !\n"); + if (of_flat_dt_is_compatible(node, "ibm,opal-v4")) { + opal_v4_present = true; + pr_debug("OPAL v4 runtime firmware\n"); + } else { + pr_debug("OPAL detected !\n"); + } } else { panic("OPAL v3 compatible firmware not detected, can not continue.\n"); } @@ -187,6 +196,9 @@ int __init early_init_dt_scan_opal(unsigned long node, pr_debug("OPAL v4 Entry = 0x%llx (v4_le_entryp=%p v4_le_entrysz=%d)\n", opal.v4_le_entry, v4_le_entryp, v4_le_entrysz); + } else { + /* Can't use v4 entry */ + opal_v4_present = false; } return 1; @@ -1033,6 +1045,111 @@ static void opal_init_heartbeat(void) kopald_tsk = kthread_run(kopald, NULL, "kopald"); } +static pgprot_t opal_vm_flags_to_prot(uint64_t flags) +{ + pgprot_t prot; + + BUG_ON(!flags); + if (flags & OS_VM_FLAG_EXECUTE) { + if (flags & OS_VM_FLAG_CI) + BUG(); + if (flags & OS_VM_FLAG_WRITE) + prot = PAGE_KERNEL_X; + else + prot = PAGE_KERNEL_X /* XXX!? PAGE_KERNEL_ROX */; + } else { + if (flags & OS_VM_FLAG_WRITE) + prot = PAGE_KERNEL; + else if (flags & OS_VM_FLAG_READ) + prot = PAGE_KERNEL_RO; + else + BUG(); + if (flags & OS_VM_FLAG_CI) + prot = pgprot_noncached(prot); + } + return prot; +} + +static int __init opal_init_mm(void) +{ + struct mm_struct *mm; + unsigned long addr; + struct opal_vm_area vm_area; + + mm = copy_init_mm(); + if (!mm) + return -ENOMEM; + + /* Set up initial mappings for OPAL. */ + addr = 0; + while (opal_find_vm_area(addr, &vm_area) == OPAL_SUCCESS) { + unsigned long length; + unsigned long pa; + unsigned long flags; + unsigned long end; + pgprot_t prot; + + addr = be64_to_cpu(vm_area.address); + length = be64_to_cpu(vm_area.length); + pa = be64_to_cpu(vm_area.pa); + flags = be64_to_cpu(vm_area.vm_flags); + + if (flags == 0) { + /* flags == 0 is a special case */ + BUG_ON(pa != 0); + } else { + /* Don't support non-linear (yet?) */ + BUG_ON(addr != pa); + prot = opal_vm_flags_to_prot(flags); + } + + /* Align to PAGE_SIZE */ + end = (addr + length + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + addr &= ~(PAGE_SIZE - 1); + + while (addr < end) { + spinlock_t *ptl; + pte_t pte, *ptep; + + ptep = get_locked_pte(mm, addr, &ptl); + if (flags) { + pte = pfn_pte(addr >> PAGE_SHIFT, prot); + set_pte_at(mm, addr, ptep, pte); + } else { + pte_clear(mm, addr, ptep); + } + pte_unmap_unlock(ptep, ptl); + + addr += PAGE_SIZE; + } + } + + printk("OPAL Virtual Memory Runtime Enabled, using PID=0x%04x\n", (unsigned int)mm->context.id); + + opal_mm = mm; + opal_mm_enabled = true; + + return 0; +} + +static int __init opal_init_early(void) +{ + int rc; + + if (opal_v4_present) { + if (radix_enabled()) { + /* Hash can't resolve SLB faults to the switched mm */ + rc = opal_init_mm(); + if (rc) { + pr_warn("OPAL virtual memory init failed, firmware will run in real-mode.\n"); + } + } + } + + return 0; +} +machine_early_initcall(powernv, opal_init_early); + static int __init opal_init(void) { struct device_node *np, *consoles, *leds; -- 2.23.0