1 // SPDX-License-Identifier: GPL-2.0
2 
3 /*
4  * Handles hot and cold plug of persistent memory regions on pseries.
5  */
6 
7 #define pr_fmt(fmt)     "pseries-pmem: " fmt
8 
9 #include <linux/kernel.h>
10 #include <linux/interrupt.h>
11 #include <linux/delay.h>
12 #include <linux/sched.h>	/* for idle_task_exit */
13 #include <linux/sched/hotplug.h>
14 #include <linux/cpu.h>
15 #include <linux/of.h>
16 #include <linux/of_platform.h>
17 #include <linux/slab.h>
18 #include <asm/rtas.h>
19 #include <asm/firmware.h>
20 #include <asm/machdep.h>
21 #include <asm/vdso_datapage.h>
22 #include <asm/plpar_wrappers.h>
23 #include <asm/topology.h>
24 
25 #include "pseries.h"
26 
27 static struct device_node *pmem_node;
28 
pmem_drc_add_node(u32 drc_index)29 static ssize_t pmem_drc_add_node(u32 drc_index)
30 {
31 	struct device_node *dn;
32 	int rc;
33 
34 	pr_debug("Attempting to add pmem node, drc index: %x\n", drc_index);
35 
36 	rc = dlpar_acquire_drc(drc_index);
37 	if (rc) {
38 		pr_err("Failed to acquire DRC, rc: %d, drc index: %x\n",
39 			rc, drc_index);
40 		return -EINVAL;
41 	}
42 
43 	dn = dlpar_configure_connector(cpu_to_be32(drc_index), pmem_node);
44 	if (!dn) {
45 		pr_err("configure-connector failed for drc %x\n", drc_index);
46 		dlpar_release_drc(drc_index);
47 		return -EINVAL;
48 	}
49 
50 	/* NB: The of reconfig notifier creates platform device from the node */
51 	rc = dlpar_attach_node(dn, pmem_node);
52 	if (rc) {
53 		pr_err("Failed to attach node %pOF, rc: %d, drc index: %x\n",
54 			dn, rc, drc_index);
55 
56 		if (dlpar_release_drc(drc_index))
57 			dlpar_free_cc_nodes(dn);
58 
59 		return rc;
60 	}
61 
62 	pr_info("Successfully added %pOF, drc index: %x\n", dn, drc_index);
63 
64 	return 0;
65 }
66 
pmem_drc_remove_node(u32 drc_index)67 static ssize_t pmem_drc_remove_node(u32 drc_index)
68 {
69 	struct device_node *dn;
70 	uint32_t index;
71 	int rc;
72 
73 	for_each_child_of_node(pmem_node, dn) {
74 		if (of_property_read_u32(dn, "ibm,my-drc-index", &index))
75 			continue;
76 		if (index == drc_index)
77 			break;
78 	}
79 
80 	if (!dn) {
81 		pr_err("Attempting to remove unused DRC index %x\n", drc_index);
82 		return -ENODEV;
83 	}
84 
85 	pr_debug("Attempting to remove %pOF, drc index: %x\n", dn, drc_index);
86 
87 	/* * NB: tears down the ibm,pmemory device as a side-effect */
88 	rc = dlpar_detach_node(dn);
89 	if (rc)
90 		return rc;
91 
92 	rc = dlpar_release_drc(drc_index);
93 	if (rc) {
94 		pr_err("Failed to release drc (%x) for CPU %pOFn, rc: %d\n",
95 			drc_index, dn, rc);
96 		dlpar_attach_node(dn, pmem_node);
97 		return rc;
98 	}
99 
100 	pr_info("Successfully removed PMEM with drc index: %x\n", drc_index);
101 
102 	return 0;
103 }
104 
dlpar_hp_pmem(struct pseries_hp_errorlog * hp_elog)105 int dlpar_hp_pmem(struct pseries_hp_errorlog *hp_elog)
106 {
107 	u32 drc_index;
108 	int rc;
109 
110 	/* slim chance, but we might get a hotplug event while booting */
111 	if (!pmem_node)
112 		pmem_node = of_find_node_by_type(NULL, "ibm,persistent-memory");
113 	if (!pmem_node) {
114 		pr_err("Hotplug event for a pmem device, but none exists\n");
115 		return -ENODEV;
116 	}
117 
118 	if (hp_elog->id_type != PSERIES_HP_ELOG_ID_DRC_INDEX) {
119 		pr_err("Unsupported hotplug event type %d\n",
120 				hp_elog->id_type);
121 		return -EINVAL;
122 	}
123 
124 	drc_index = hp_elog->_drc_u.drc_index;
125 
126 	lock_device_hotplug();
127 
128 	if (hp_elog->action == PSERIES_HP_ELOG_ACTION_ADD) {
129 		rc = pmem_drc_add_node(drc_index);
130 	} else if (hp_elog->action == PSERIES_HP_ELOG_ACTION_REMOVE) {
131 		rc = pmem_drc_remove_node(drc_index);
132 	} else {
133 		pr_err("Unsupported hotplug action (%d)\n", hp_elog->action);
134 		rc = -EINVAL;
135 	}
136 
137 	unlock_device_hotplug();
138 	return rc;
139 }
140 
141 static const struct of_device_id drc_pmem_match[] = {
142 	{ .type = "ibm,persistent-memory", },
143 	{}
144 };
145 
pseries_pmem_init(void)146 static int pseries_pmem_init(void)
147 {
148 	/*
149 	 * Only supported on POWER8 and above.
150 	 */
151 	if (!cpu_has_feature(CPU_FTR_ARCH_207S))
152 		return 0;
153 
154 	pmem_node = of_find_node_by_type(NULL, "ibm,persistent-memory");
155 	if (!pmem_node)
156 		return 0;
157 
158 	/*
159 	 * The generic OF bus probe/populate handles creating platform devices
160 	 * from the child (ibm,pmemory) nodes. The generic code registers an of
161 	 * reconfig notifier to handle the hot-add/remove cases too.
162 	 */
163 	of_platform_bus_probe(pmem_node, drc_pmem_match, NULL);
164 
165 	return 0;
166 }
167 machine_arch_initcall(pseries, pseries_pmem_init);
168