powerpc/pseries: Update memory hotplug code to use drmem LMB array
[linux-2.6-block.git] / arch / powerpc / mm / drmem.c
CommitLineData
6c6ea537
NF
1/*
2 * Dynamic reconfiguration memory support
3 *
4 * Copyright 2017 IBM Corporation
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12#define pr_fmt(fmt) "drmem: " fmt
13
14#include <linux/kernel.h>
15#include <linux/of.h>
16#include <linux/of_fdt.h>
17#include <linux/memblock.h>
18#include <asm/prom.h>
19#include <asm/drmem.h>
20
21static struct drmem_lmb_info __drmem_info;
22struct drmem_lmb_info *drmem_info = &__drmem_info;
23
514a9cb3
NF
24u64 drmem_lmb_memory_max(void)
25{
26 struct drmem_lmb *last_lmb;
27
28 last_lmb = &drmem_info->lmbs[drmem_info->n_lmbs - 1];
29 return last_lmb->base_addr + drmem_lmb_size();
30}
31
6195a500
NF
32static u32 drmem_lmb_flags(struct drmem_lmb *lmb)
33{
34 /*
35 * Return the value of the lmb flags field minus the reserved
36 * bit used internally for hotplug processing.
37 */
38 return lmb->flags & ~DRMEM_LMB_RESERVED;
39}
40
41static struct property *clone_property(struct property *prop, u32 prop_sz)
42{
43 struct property *new_prop;
44
45 new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
46 if (!new_prop)
47 return NULL;
48
49 new_prop->name = kstrdup(prop->name, GFP_KERNEL);
50 new_prop->value = kzalloc(prop_sz, GFP_KERNEL);
51 if (!new_prop->name || !new_prop->value) {
52 kfree(new_prop->name);
53 kfree(new_prop->value);
54 kfree(new_prop);
55 return NULL;
56 }
57
58 new_prop->length = prop_sz;
59#if defined(CONFIG_OF_DYNAMIC)
60 of_property_set_flag(new_prop, OF_DYNAMIC);
61#endif
62 return new_prop;
63}
64
65static int drmem_update_dt_v1(struct device_node *memory,
66 struct property *prop)
67{
68 struct property *new_prop;
69 struct of_drconf_cell *dr_cell;
70 struct drmem_lmb *lmb;
71 u32 *p;
72
73 new_prop = clone_property(prop, prop->length);
74 if (!new_prop)
75 return -1;
76
77 p = new_prop->value;
78 *p++ = cpu_to_be32(drmem_info->n_lmbs);
79
80 dr_cell = (struct of_drconf_cell *)p;
81
82 for_each_drmem_lmb(lmb) {
83 dr_cell->base_addr = cpu_to_be64(lmb->base_addr);
84 dr_cell->drc_index = cpu_to_be32(lmb->drc_index);
85 dr_cell->aa_index = cpu_to_be32(lmb->aa_index);
86 dr_cell->flags = cpu_to_be32(drmem_lmb_flags(lmb));
87
88 dr_cell++;
89 }
90
91 of_update_property(memory, new_prop);
92 return 0;
93}
94
95int drmem_update_dt(void)
96{
97 struct device_node *memory;
98 struct property *prop;
99 int rc = -1;
100
101 memory = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
102 if (!memory)
103 return -1;
104
105 prop = of_find_property(memory, "ibm,dynamic-memory", NULL);
106 if (prop)
107 rc = drmem_update_dt_v1(memory, prop);
108
109 of_node_put(memory);
110 return rc;
111}
112
6c6ea537
NF
113static void __init read_drconf_v1_cell(struct drmem_lmb *lmb,
114 const __be32 **prop)
115{
116 const __be32 *p = *prop;
117
118 lmb->base_addr = dt_mem_next_cell(dt_root_addr_cells, &p);
119 lmb->drc_index = of_read_number(p++, 1);
120
121 p++; /* skip reserved field */
122
123 lmb->aa_index = of_read_number(p++, 1);
124 lmb->flags = of_read_number(p++, 1);
125
126 *prop = p;
127}
128
129static void __init __walk_drmem_v1_lmbs(const __be32 *prop, const __be32 *usm,
130 void (*func)(struct drmem_lmb *, const __be32 **))
131{
132 struct drmem_lmb lmb;
133 u32 i, n_lmbs;
134
135 n_lmbs = of_read_number(prop++, 1);
136
137 for (i = 0; i < n_lmbs; i++) {
138 read_drconf_v1_cell(&lmb, &prop);
139 func(&lmb, &usm);
140 }
141}
142
514a9cb3 143#ifdef CONFIG_PPC_PSERIES
6c6ea537
NF
144void __init walk_drmem_lmbs_early(unsigned long node,
145 void (*func)(struct drmem_lmb *, const __be32 **))
146{
147 const __be32 *prop, *usm;
148 int len;
149
150 prop = of_get_flat_dt_prop(node, "ibm,lmb-size", &len);
151 if (!prop || len < dt_root_size_cells * sizeof(__be32))
152 return;
153
154 drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop);
155
156 usm = of_get_flat_dt_prop(node, "linux,drconf-usable-memory", &len);
157
158 prop = of_get_flat_dt_prop(node, "ibm,dynamic-memory", &len);
159 if (prop)
160 __walk_drmem_v1_lmbs(prop, usm, func);
161
162 memblock_dump_all();
163}
164
165#endif
514a9cb3
NF
166
167static int __init init_drmem_lmb_size(struct device_node *dn)
168{
169 const __be32 *prop;
170 int len;
171
172 if (drmem_info->lmb_size)
173 return 0;
174
175 prop = of_get_property(dn, "ibm,lmb-size", &len);
176 if (!prop || len < dt_root_size_cells * sizeof(__be32)) {
177 pr_info("Could not determine LMB size\n");
178 return -1;
179 }
180
181 drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop);
182 return 0;
183}
184
185/*
186 * Returns the property linux,drconf-usable-memory if
187 * it exists (the property exists only in kexec/kdump kernels,
188 * added by kexec-tools)
189 */
190static const __be32 *of_get_usable_memory(struct device_node *dn)
191{
192 const __be32 *prop;
193 u32 len;
194
195 prop = of_get_property(dn, "linux,drconf-usable-memory", &len);
196 if (!prop || len < sizeof(unsigned int))
197 return NULL;
198
199 return prop;
200}
201
202void __init walk_drmem_lmbs(struct device_node *dn,
203 void (*func)(struct drmem_lmb *, const __be32 **))
204{
205 const __be32 *prop, *usm;
206
207 if (init_drmem_lmb_size(dn))
208 return;
209
210 usm = of_get_usable_memory(dn);
211
212 prop = of_get_property(dn, "ibm,dynamic-memory", NULL);
213 if (prop)
214 __walk_drmem_v1_lmbs(prop, usm, func);
215}
216
217static void __init init_drmem_v1_lmbs(const __be32 *prop)
218{
219 struct drmem_lmb *lmb;
220
221 drmem_info->n_lmbs = of_read_number(prop++, 1);
222
223 drmem_info->lmbs = kcalloc(drmem_info->n_lmbs, sizeof(*lmb),
224 GFP_KERNEL);
225 if (!drmem_info->lmbs)
226 return;
227
228 for_each_drmem_lmb(lmb)
229 read_drconf_v1_cell(lmb, &prop);
230}
231
232static int __init drmem_init(void)
233{
234 struct device_node *dn;
235 const __be32 *prop;
236
237 dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
238 if (!dn) {
239 pr_info("No dynamic reconfiguration memory found\n");
240 return 0;
241 }
242
243 if (init_drmem_lmb_size(dn)) {
244 of_node_put(dn);
245 return 0;
246 }
247
248 prop = of_get_property(dn, "ibm,dynamic-memory", NULL);
249 if (prop)
250 init_drmem_v1_lmbs(prop);
251
252 of_node_put(dn);
253 return 0;
254}
255late_initcall(drmem_init);