aboutsummaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorNathan Fontenot <nfont@austin.ibm.com>2009-11-24 16:10:49 -0500
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2009-12-09 01:09:32 -0500
commitab519a011caa5ec47d992cb8a4fc8e7af9b9e3f8 (patch)
treea76bb05e86bbc12fe4041a1552a0543f44c4f738 /arch
parent1496e89ae2a0962748e55165a590fa3209c6f158 (diff)
powerpc/pseries: Kernel DLPAR Infrastructure
The Dynamic Logical Partitioning capabilities of the powerpc pseries platform allows for the addition and removal of resources (i.e. CPU's, memory, and PCI devices) from a partition. The removal of a resource involves removing the resource's node from the device tree and then returning the resource to firmware via the rtas set-indicator call. To add a resource, it is first obtained from firmware via the rtas set-indicator call and then a new device tree node is created using the ibm,configure-coinnector rtas call and added to the device tree. This patch provides the kernel DLPAR infrastructure in a new filed named dlpar.c. The functionality provided is for acquiring and releasing a resource from firmware and the parsing of information returned from the ibm,configure-connector rtas call. Additionally this exports the pSeries reconfiguration notifier chain so that it can be invoked when device tree updates are made. Signed-off-by: Nathan Fontenot <nfont@austin.ibm.com> Acked-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch')
-rw-r--r--arch/powerpc/include/asm/pSeries_reconfig.h1
-rw-r--r--arch/powerpc/platforms/pseries/Makefile2
-rw-r--r--arch/powerpc/platforms/pseries/dlpar.c344
-rw-r--r--arch/powerpc/platforms/pseries/reconfig.c2
4 files changed, 347 insertions, 2 deletions
diff --git a/arch/powerpc/include/asm/pSeries_reconfig.h b/arch/powerpc/include/asm/pSeries_reconfig.h
index e482e5352e69..d4b4bfa26fb3 100644
--- a/arch/powerpc/include/asm/pSeries_reconfig.h
+++ b/arch/powerpc/include/asm/pSeries_reconfig.h
@@ -17,6 +17,7 @@
17#ifdef CONFIG_PPC_PSERIES 17#ifdef CONFIG_PPC_PSERIES
18extern int pSeries_reconfig_notifier_register(struct notifier_block *); 18extern int pSeries_reconfig_notifier_register(struct notifier_block *);
19extern void pSeries_reconfig_notifier_unregister(struct notifier_block *); 19extern void pSeries_reconfig_notifier_unregister(struct notifier_block *);
20extern struct blocking_notifier_head pSeries_reconfig_chain;
20#else /* !CONFIG_PPC_PSERIES */ 21#else /* !CONFIG_PPC_PSERIES */
21static inline int pSeries_reconfig_notifier_register(struct notifier_block *nb) 22static inline int pSeries_reconfig_notifier_register(struct notifier_block *nb)
22{ 23{
diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile
index 4b1c422b8145..0ff5174ae4f5 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -8,7 +8,7 @@ endif
8 8
9obj-y := lpar.o hvCall.o nvram.o reconfig.o \ 9obj-y := lpar.o hvCall.o nvram.o reconfig.o \
10 setup.o iommu.o ras.o \ 10 setup.o iommu.o ras.o \
11 firmware.o power.o 11 firmware.o power.o dlpar.o
12obj-$(CONFIG_SMP) += smp.o 12obj-$(CONFIG_SMP) += smp.o
13obj-$(CONFIG_XICS) += xics.o 13obj-$(CONFIG_XICS) += xics.o
14obj-$(CONFIG_SCANLOG) += scanlog.o 14obj-$(CONFIG_SCANLOG) += scanlog.o
diff --git a/arch/powerpc/platforms/pseries/dlpar.c b/arch/powerpc/platforms/pseries/dlpar.c
new file mode 100644
index 000000000000..c80e8ef0eb58
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/dlpar.c
@@ -0,0 +1,344 @@
1/*
2 * Support for dynamic reconfiguration for PCI, Memory, and CPU
3 * Hotplug and Dynamic Logical Partitioning on RPA platforms.
4 *
5 * Copyright (C) 2009 Nathan Fontenot
6 * Copyright (C) 2009 IBM Corporation
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License version
10 * 2 as published by the Free Software Foundation.
11 */
12
13#include <linux/kernel.h>
14#include <linux/kref.h>
15#include <linux/notifier.h>
16#include <linux/proc_fs.h>
17#include <linux/spinlock.h>
18#include <linux/cpu.h>
19
20#include <asm/prom.h>
21#include <asm/machdep.h>
22#include <asm/uaccess.h>
23#include <asm/rtas.h>
24#include <asm/pSeries_reconfig.h>
25
26struct cc_workarea {
27 u32 drc_index;
28 u32 zero;
29 u32 name_offset;
30 u32 prop_length;
31 u32 prop_offset;
32};
33
34static void dlpar_free_cc_property(struct property *prop)
35{
36 kfree(prop->name);
37 kfree(prop->value);
38 kfree(prop);
39}
40
41static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa)
42{
43 struct property *prop;
44 char *name;
45 char *value;
46
47 prop = kzalloc(sizeof(*prop), GFP_KERNEL);
48 if (!prop)
49 return NULL;
50
51 name = (char *)ccwa + ccwa->name_offset;
52 prop->name = kstrdup(name, GFP_KERNEL);
53
54 prop->length = ccwa->prop_length;
55 value = (char *)ccwa + ccwa->prop_offset;
56 prop->value = kzalloc(prop->length, GFP_KERNEL);
57 if (!prop->value) {
58 dlpar_free_cc_property(prop);
59 return NULL;
60 }
61
62 memcpy(prop->value, value, prop->length);
63 return prop;
64}
65
66static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa)
67{
68 struct device_node *dn;
69 char *name;
70
71 dn = kzalloc(sizeof(*dn), GFP_KERNEL);
72 if (!dn)
73 return NULL;
74
75 /* The configure connector reported name does not contain a
76 * preceeding '/', so we allocate a buffer large enough to
77 * prepend this to the full_name.
78 */
79 name = (char *)ccwa + ccwa->name_offset;
80 dn->full_name = kmalloc(strlen(name) + 2, GFP_KERNEL);
81 if (!dn->full_name) {
82 kfree(dn);
83 return NULL;
84 }
85
86 sprintf(dn->full_name, "/%s", name);
87 return dn;
88}
89
90static void dlpar_free_one_cc_node(struct device_node *dn)
91{
92 struct property *prop;
93
94 while (dn->properties) {
95 prop = dn->properties;
96 dn->properties = prop->next;
97 dlpar_free_cc_property(prop);
98 }
99
100 kfree(dn->full_name);
101 kfree(dn);
102}
103
104static void dlpar_free_cc_nodes(struct device_node *dn)
105{
106 if (dn->child)
107 dlpar_free_cc_nodes(dn->child);
108
109 if (dn->sibling)
110 dlpar_free_cc_nodes(dn->sibling);
111
112 dlpar_free_one_cc_node(dn);
113}
114
115#define NEXT_SIBLING 1
116#define NEXT_CHILD 2
117#define NEXT_PROPERTY 3
118#define PREV_PARENT 4
119#define MORE_MEMORY 5
120#define CALL_AGAIN -2
121#define ERR_CFG_USE -9003
122
123struct device_node *dlpar_configure_connector(u32 drc_index)
124{
125 struct device_node *dn;
126 struct device_node *first_dn = NULL;
127 struct device_node *last_dn = NULL;
128 struct property *property;
129 struct property *last_property = NULL;
130 struct cc_workarea *ccwa;
131 int cc_token;
132 int rc;
133
134 cc_token = rtas_token("ibm,configure-connector");
135 if (cc_token == RTAS_UNKNOWN_SERVICE)
136 return NULL;
137
138 spin_lock(&rtas_data_buf_lock);
139 ccwa = (struct cc_workarea *)&rtas_data_buf[0];
140 ccwa->drc_index = drc_index;
141 ccwa->zero = 0;
142
143 rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL);
144 while (rc) {
145 switch (rc) {
146 case NEXT_SIBLING:
147 dn = dlpar_parse_cc_node(ccwa);
148 if (!dn)
149 goto cc_error;
150
151 dn->parent = last_dn->parent;
152 last_dn->sibling = dn;
153 last_dn = dn;
154 break;
155
156 case NEXT_CHILD:
157 dn = dlpar_parse_cc_node(ccwa);
158 if (!dn)
159 goto cc_error;
160
161 if (!first_dn)
162 first_dn = dn;
163 else {
164 dn->parent = last_dn;
165 if (last_dn)
166 last_dn->child = dn;
167 }
168
169 last_dn = dn;
170 break;
171
172 case NEXT_PROPERTY:
173 property = dlpar_parse_cc_property(ccwa);
174 if (!property)
175 goto cc_error;
176
177 if (!last_dn->properties)
178 last_dn->properties = property;
179 else
180 last_property->next = property;
181
182 last_property = property;
183 break;
184
185 case PREV_PARENT:
186 last_dn = last_dn->parent;
187 break;
188
189 case CALL_AGAIN:
190 break;
191
192 case MORE_MEMORY:
193 case ERR_CFG_USE:
194 default:
195 printk(KERN_ERR "Unexpected Error (%d) "
196 "returned from configure-connector\n", rc);
197 goto cc_error;
198 }
199
200 rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL);
201 }
202
203 spin_unlock(&rtas_data_buf_lock);
204 return first_dn;
205
206cc_error:
207 if (first_dn)
208 dlpar_free_cc_nodes(first_dn);
209 spin_unlock(&rtas_data_buf_lock);
210 return NULL;
211}
212
213static struct device_node *derive_parent(const char *path)
214{
215 struct device_node *parent;
216 char *last_slash;
217
218 last_slash = strrchr(path, '/');
219 if (last_slash == path) {
220 parent = of_find_node_by_path("/");
221 } else {
222 char *parent_path;
223 int parent_path_len = last_slash - path + 1;
224 parent_path = kmalloc(parent_path_len, GFP_KERNEL);
225 if (!parent_path)
226 return NULL;
227
228 strlcpy(parent_path, path, parent_path_len);
229 parent = of_find_node_by_path(parent_path);
230 kfree(parent_path);
231 }
232
233 return parent;
234}
235
236int dlpar_attach_node(struct device_node *dn)
237{
238 struct proc_dir_entry *ent;
239 int rc;
240
241 of_node_set_flag(dn, OF_DYNAMIC);
242 kref_init(&dn->kref);
243 dn->parent = derive_parent(dn->full_name);
244 if (!dn->parent)
245 return -ENOMEM;
246
247 rc = blocking_notifier_call_chain(&pSeries_reconfig_chain,
248 PSERIES_RECONFIG_ADD, dn);
249 if (rc == NOTIFY_BAD) {
250 printk(KERN_ERR "Failed to add device node %s\n",
251 dn->full_name);
252 return -ENOMEM; /* For now, safe to assume kmalloc failure */
253 }
254
255 of_attach_node(dn);
256
257#ifdef CONFIG_PROC_DEVICETREE
258 ent = proc_mkdir(strrchr(dn->full_name, '/') + 1, dn->parent->pde);
259 if (ent)
260 proc_device_tree_add_node(dn, ent);
261#endif
262
263 of_node_put(dn->parent);
264 return 0;
265}
266
267int dlpar_detach_node(struct device_node *dn)
268{
269 struct device_node *parent = dn->parent;
270 struct property *prop = dn->properties;
271
272#ifdef CONFIG_PROC_DEVICETREE
273 while (prop) {
274 remove_proc_entry(prop->name, dn->pde);
275 prop = prop->next;
276 }
277
278 if (dn->pde)
279 remove_proc_entry(dn->pde->name, parent->pde);
280#endif
281
282 blocking_notifier_call_chain(&pSeries_reconfig_chain,
283 PSERIES_RECONFIG_REMOVE, dn);
284 of_detach_node(dn);
285 of_node_put(dn); /* Must decrement the refcount */
286
287 return 0;
288}
289
290#define DR_ENTITY_SENSE 9003
291#define DR_ENTITY_PRESENT 1
292#define DR_ENTITY_UNUSABLE 2
293#define ALLOCATION_STATE 9003
294#define ALLOC_UNUSABLE 0
295#define ALLOC_USABLE 1
296#define ISOLATION_STATE 9001
297#define ISOLATE 0
298#define UNISOLATE 1
299
300int dlpar_acquire_drc(u32 drc_index)
301{
302 int dr_status, rc;
303
304 rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
305 DR_ENTITY_SENSE, drc_index);
306 if (rc || dr_status != DR_ENTITY_UNUSABLE)
307 return -1;
308
309 rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_USABLE);
310 if (rc)
311 return rc;
312
313 rc = rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
314 if (rc) {
315 rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
316 return rc;
317 }
318
319 return 0;
320}
321
322int dlpar_release_drc(u32 drc_index)
323{
324 int dr_status, rc;
325
326 rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
327 DR_ENTITY_SENSE, drc_index);
328 if (rc || dr_status != DR_ENTITY_PRESENT)
329 return -1;
330
331 rc = rtas_set_indicator(ISOLATION_STATE, drc_index, ISOLATE);
332 if (rc)
333 return rc;
334
335 rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
336 if (rc) {
337 rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
338 return rc;
339 }
340
341 return 0;
342}
343
344
diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c
index 5182d2b992c6..a2305d29bbbd 100644
--- a/arch/powerpc/platforms/pseries/reconfig.c
+++ b/arch/powerpc/platforms/pseries/reconfig.c
@@ -96,7 +96,7 @@ static struct device_node *derive_parent(const char *path)
96 return parent; 96 return parent;
97} 97}
98 98
99static BLOCKING_NOTIFIER_HEAD(pSeries_reconfig_chain); 99BLOCKING_NOTIFIER_HEAD(pSeries_reconfig_chain);
100 100
101int pSeries_reconfig_notifier_register(struct notifier_block *nb) 101int pSeries_reconfig_notifier_register(struct notifier_block *nb)
102{ 102{