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