hotplug-memory.c 6.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*
 * pseries Memory Hotplug infrastructure.
 *
 * Copyright (C) 2008 Badari Pulavarty, IBM Corporation
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

#include <linux/of.h>
R
Rob Herring 已提交
13
#include <linux/of_address.h>
Y
Yinghai Lu 已提交
14
#include <linux/memblock.h>
15
#include <linux/vmalloc.h>
16
#include <linux/memory.h>
17
#include <linux/memory_hotplug.h>
18

19 20
#include <asm/firmware.h>
#include <asm/machdep.h>
R
Rob Herring 已提交
21
#include <asm/prom.h>
22
#include <asm/sparsemem.h>
23

24
unsigned long pseries_memory_block_size(void)
25 26
{
	struct device_node *np;
27 28
	unsigned int memblock_size = MIN_MEMORY_BLOCK_SIZE;
	struct resource r;
29 30 31

	np = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
	if (np) {
32
		const __be64 *size;
33 34

		size = of_get_property(np, "ibm,lmb-size", NULL);
35 36
		if (size)
			memblock_size = be64_to_cpup(size);
37
		of_node_put(np);
38 39
	} else  if (machine_is(pseries)) {
		/* This fallback really only applies to pseries */
40 41 42 43
		unsigned int memzero_size = 0;

		np = of_find_node_by_path("/memory@0");
		if (np) {
44 45
			if (!of_address_to_resource(np, 0, &r))
				memzero_size = resource_size(&r);
46 47 48 49 50 51 52 53 54 55 56 57
			of_node_put(np);
		}

		if (memzero_size) {
			/* We now know the size of memory@0, use this to find
			 * the first memoryblock and get its size.
			 */
			char buf[64];

			sprintf(buf, "/memory@%x", memzero_size);
			np = of_find_node_by_path(buf);
			if (np) {
58 59
				if (!of_address_to_resource(np, 0, &r))
					memblock_size = resource_size(&r);
60 61 62 63 64 65 66
				of_node_put(np);
			}
		}
	}
	return memblock_size;
}

67
#ifdef CONFIG_MEMORY_HOTREMOVE
68
static int pseries_remove_memory(u64 start, u64 size)
69
{
70
	int ret;
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

	/* Remove htab bolted mappings for this section of memory */
	start = (unsigned long)__va(start);
	ret = remove_section_mapping(start, start + size);

	/* Ensure all vmalloc mappings are flushed in case they also
	 * hit that section of memory
	 */
	vm_unmap_aliases();

	return ret;
}

static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size)
{
	unsigned long block_sz, start_pfn;
	int sections_per_block;
	int i, nid;
89

90
	start_pfn = base >> PAGE_SHIFT;
91

92 93 94 95
	lock_device_hotplug();

	if (!pfn_valid(start_pfn))
		goto out;
96

97
	block_sz = pseries_memory_block_size();
98 99
	sections_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE;
	nid = memory_add_physaddr_to_nid(base);
100

101 102 103
	for (i = 0; i < sections_per_block; i++) {
		remove_memory(nid, base, MIN_MEMORY_BLOCK_SIZE);
		base += MIN_MEMORY_BLOCK_SIZE;
104
	}
105

106
out:
107
	/* Update memory regions for memory remove */
Y
Yinghai Lu 已提交
108
	memblock_remove(base, memblock_size);
109
	unlock_device_hotplug();
110
	return 0;
111 112
}

113
static int pseries_remove_mem_node(struct device_node *np)
114 115 116 117
{
	const char *type;
	const unsigned int *regs;
	unsigned long base;
118
	unsigned int lmb_size;
119 120 121 122 123 124 125 126 127 128
	int ret = -EINVAL;

	/*
	 * Check to see if we are actually removing memory
	 */
	type = of_get_property(np, "device_type", NULL);
	if (type == NULL || strcmp(type, "memory") != 0)
		return 0;

	/*
Y
Yinghai Lu 已提交
129
	 * Find the bae address and size of the memblock
130 131 132 133 134 135
	 */
	regs = of_get_property(np, "reg", NULL);
	if (!regs)
		return ret;

	base = *(unsigned long *)regs;
136
	lmb_size = regs[3];
137

138 139
	pseries_remove_memblock(base, lmb_size);
	return 0;
140
}
141 142 143 144 145 146
#else
static inline int pseries_remove_memblock(unsigned long base,
					  unsigned int memblock_size)
{
	return -EOPNOTSUPP;
}
147
static inline int pseries_remove_mem_node(struct device_node *np)
148
{
149
	return 0;
150 151
}
#endif /* CONFIG_MEMORY_HOTREMOVE */
152

153
static int pseries_add_mem_node(struct device_node *np)
154 155 156
{
	const char *type;
	const unsigned int *regs;
157
	unsigned long base;
158
	unsigned int lmb_size;
159 160 161 162 163 164 165 166 167 168
	int ret = -EINVAL;

	/*
	 * Check to see if we are actually adding memory
	 */
	type = of_get_property(np, "device_type", NULL);
	if (type == NULL || strcmp(type, "memory") != 0)
		return 0;

	/*
Y
Yinghai Lu 已提交
169
	 * Find the base and size of the memblock
170 171 172 173 174
	 */
	regs = of_get_property(np, "reg", NULL);
	if (!regs)
		return ret;

175
	base = *(unsigned long *)regs;
176
	lmb_size = regs[3];
177 178 179 180

	/*
	 * Update memory region to represent the memory add
	 */
181
	ret = memblock_add(base, lmb_size);
182 183 184
	return (ret < 0) ? -EINVAL : 0;
}

185
static int pseries_update_drconf_memory(struct of_prop_reconfig *pr)
186
{
187
	struct of_drconf_cell *new_drmem, *old_drmem;
188
	unsigned long memblock_size;
189 190 191
	u32 entries;
	u32 *p;
	int i, rc = -EINVAL;
192

193
	memblock_size = pseries_memory_block_size();
194
	if (!memblock_size)
195 196
		return -EINVAL;

197
	p = (u32 *) pr->old_prop->value;
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
	if (!p)
		return -EINVAL;

	/* The first int of the property is the number of lmb's described
	 * by the property. This is followed by an array of of_drconf_cell
	 * entries. Get the niumber of entries and skip to the array of
	 * of_drconf_cell's.
	 */
	entries = *p++;
	old_drmem = (struct of_drconf_cell *)p;

	p = (u32 *)pr->prop->value;
	p++;
	new_drmem = (struct of_drconf_cell *)p;

	for (i = 0; i < entries; i++) {
		if ((old_drmem[i].flags & DRCONF_MEM_ASSIGNED) &&
		    (!(new_drmem[i].flags & DRCONF_MEM_ASSIGNED))) {
			rc = pseries_remove_memblock(old_drmem[i].base_addr,
						     memblock_size);
			break;
		} else if ((!(old_drmem[i].flags & DRCONF_MEM_ASSIGNED)) &&
			   (new_drmem[i].flags & DRCONF_MEM_ASSIGNED)) {
			rc = memblock_add(old_drmem[i].base_addr,
					  memblock_size);
			rc = (rc < 0) ? -EINVAL : 0;
			break;
		}
226 227 228
	}

	return rc;
229 230
}

231
static int pseries_memory_notifier(struct notifier_block *nb,
232
				   unsigned long action, void *node)
233
{
234
	struct of_prop_reconfig *pr;
235
	int err = 0;
236 237

	switch (action) {
238
	case OF_RECONFIG_ATTACH_NODE:
239
		err = pseries_add_mem_node(node);
240
		break;
241
	case OF_RECONFIG_DETACH_NODE:
242
		err = pseries_remove_mem_node(node);
243
		break;
244 245 246 247
	case OF_RECONFIG_UPDATE_PROPERTY:
		pr = (struct of_prop_reconfig *)node;
		if (!strcmp(pr->prop->name, "ibm,dynamic-memory"))
			err = pseries_update_drconf_memory(pr);
248 249
		break;
	}
250
	return notifier_from_errno(err);
251 252 253 254 255 256 257 258 259
}

static struct notifier_block pseries_mem_nb = {
	.notifier_call = pseries_memory_notifier,
};

static int __init pseries_memory_hotplug_init(void)
{
	if (firmware_has_feature(FW_FEATURE_LPAR))
260
		of_reconfig_notifier_register(&pseries_mem_nb);
261

262 263 264 265
#ifdef CONFIG_MEMORY_HOTREMOVE
	ppc_md.remove_memory = pseries_remove_memory;
#endif

266 267 268
	return 0;
}
machine_device_initcall(pseries, pseries_memory_hotplug_init);