[RFC PATCH 08/10] powerpc/powernv: Set up an mm context to call OPAL in

Nicholas Piggin npiggin at gmail.com
Sat May 2 21:19:12 AEST 2020


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 <npiggin at 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



More information about the Linuxppc-dev mailing list