GNU Linux-libre 6.8.9-gnu
[releases.git] / arch / powerpc / platforms / pseries / pmem.c
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
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
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
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
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);