Merge branches 'x86/acpi', 'x86/asm', 'x86/cpudetect', 'x86/crashdump', 'x86/debug...
[linux-2.6] / arch / powerpc / platforms / pseries / hotplug-memory.c
1 /*
2  * pseries Memory Hotplug infrastructure.
3  *
4  * Copyright (C) 2008 Badari Pulavarty, 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 #include <linux/of.h>
13 #include <linux/lmb.h>
14 #include <asm/firmware.h>
15 #include <asm/machdep.h>
16 #include <asm/pSeries_reconfig.h>
17 #include <asm/sparsemem.h>
18
19 static int pseries_remove_lmb(unsigned long base, unsigned int lmb_size)
20 {
21         unsigned long start, start_pfn;
22         struct zone *zone;
23         int ret;
24
25         start_pfn = base >> PAGE_SHIFT;
26
27         if (!pfn_valid(start_pfn)) {
28                 lmb_remove(base, lmb_size);
29                 return 0;
30         }
31
32         zone = page_zone(pfn_to_page(start_pfn));
33
34         /*
35          * Remove section mappings and sysfs entries for the
36          * section of the memory we are removing.
37          *
38          * NOTE: Ideally, this should be done in generic code like
39          * remove_memory(). But remove_memory() gets called by writing
40          * to sysfs "state" file and we can't remove sysfs entries
41          * while writing to it. So we have to defer it to here.
42          */
43         ret = __remove_pages(zone, start_pfn, lmb_size >> PAGE_SHIFT);
44         if (ret)
45                 return ret;
46
47         /*
48          * Update memory regions for memory remove
49          */
50         lmb_remove(base, lmb_size);
51
52         /*
53          * Remove htab bolted mappings for this section of memory
54          */
55         start = (unsigned long)__va(base);
56         ret = remove_section_mapping(start, start + lmb_size);
57         return ret;
58 }
59
60 static int pseries_remove_memory(struct device_node *np)
61 {
62         const char *type;
63         const unsigned int *regs;
64         unsigned long base;
65         unsigned int lmb_size;
66         int ret = -EINVAL;
67
68         /*
69          * Check to see if we are actually removing memory
70          */
71         type = of_get_property(np, "device_type", NULL);
72         if (type == NULL || strcmp(type, "memory") != 0)
73                 return 0;
74
75         /*
76          * Find the bae address and size of the lmb
77          */
78         regs = of_get_property(np, "reg", NULL);
79         if (!regs)
80                 return ret;
81
82         base = *(unsigned long *)regs;
83         lmb_size = regs[3];
84
85         ret = pseries_remove_lmb(base, lmb_size);
86         return ret;
87 }
88
89 static int pseries_add_memory(struct device_node *np)
90 {
91         const char *type;
92         const unsigned int *regs;
93         unsigned long base;
94         unsigned int lmb_size;
95         int ret = -EINVAL;
96
97         /*
98          * Check to see if we are actually adding memory
99          */
100         type = of_get_property(np, "device_type", NULL);
101         if (type == NULL || strcmp(type, "memory") != 0)
102                 return 0;
103
104         /*
105          * Find the base and size of the lmb
106          */
107         regs = of_get_property(np, "reg", NULL);
108         if (!regs)
109                 return ret;
110
111         base = *(unsigned long *)regs;
112         lmb_size = regs[3];
113
114         /*
115          * Update memory region to represent the memory add
116          */
117         ret = lmb_add(base, lmb_size);
118         return (ret < 0) ? -EINVAL : 0;
119 }
120
121 static int pseries_drconf_memory(unsigned long *base, unsigned int action)
122 {
123         struct device_node *np;
124         const unsigned long *lmb_size;
125         int rc;
126
127         np = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
128         if (!np)
129                 return -EINVAL;
130
131         lmb_size = of_get_property(np, "ibm,lmb-size", NULL);
132         if (!lmb_size) {
133                 of_node_put(np);
134                 return -EINVAL;
135         }
136
137         if (action == PSERIES_DRCONF_MEM_ADD) {
138                 rc = lmb_add(*base, *lmb_size);
139                 rc = (rc < 0) ? -EINVAL : 0;
140         } else if (action == PSERIES_DRCONF_MEM_REMOVE) {
141                 rc = pseries_remove_lmb(*base, *lmb_size);
142         } else {
143                 rc = -EINVAL;
144         }
145
146         of_node_put(np);
147         return rc;
148 }
149
150 static int pseries_memory_notifier(struct notifier_block *nb,
151                                 unsigned long action, void *node)
152 {
153         int err = NOTIFY_OK;
154
155         switch (action) {
156         case PSERIES_RECONFIG_ADD:
157                 if (pseries_add_memory(node))
158                         err = NOTIFY_BAD;
159                 break;
160         case PSERIES_RECONFIG_REMOVE:
161                 if (pseries_remove_memory(node))
162                         err = NOTIFY_BAD;
163                 break;
164         case PSERIES_DRCONF_MEM_ADD:
165         case PSERIES_DRCONF_MEM_REMOVE:
166                 if (pseries_drconf_memory(node, action))
167                         err = NOTIFY_BAD;
168                 break;
169         default:
170                 err = NOTIFY_DONE;
171                 break;
172         }
173         return err;
174 }
175
176 static struct notifier_block pseries_mem_nb = {
177         .notifier_call = pseries_memory_notifier,
178 };
179
180 static int __init pseries_memory_hotplug_init(void)
181 {
182         if (firmware_has_feature(FW_FEATURE_LPAR))
183                 pSeries_reconfig_notifier_register(&pseries_mem_nb);
184
185         return 0;
186 }
187 machine_device_initcall(pseries, pseries_memory_hotplug_init);