projects/linux·blob master · refs

367 lines · 9.2 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) 2004, 2013 Intel Corporation
4 * Author: Naveen B S <[email protected]>
5 * Author: Rafael J. Wysocki <[email protected]>
6 *
7 * All rights reserved.
8 *
9 * ACPI based HotPlug driver that supports Memory Hotplug
10 * This driver fields notifications from firmware for memory add
11 * and remove operations and alerts the VM of the affected memory
12 * ranges.
13 */
14
15#include <linux/acpi.h>
16#include <linux/memory.h>
17#include <linux/memory_hotplug.h>
18
19#include "internal.h"
20
21#define ACPI_MEMORY_DEVICE_HID "PNP0C80"
22
23static const struct acpi_device_id memory_device_ids[] = {
24 {ACPI_MEMORY_DEVICE_HID, 0},
25 {"", 0},
26};
27
28#ifdef CONFIG_ACPI_HOTPLUG_MEMORY
29
30static int acpi_memory_device_add(struct acpi_device *device,
31 const struct acpi_device_id *not_used);
32static void acpi_memory_device_remove(struct acpi_device *device);
33
34static struct acpi_scan_handler memory_device_handler = {
35 .ids = memory_device_ids,
36 .attach = acpi_memory_device_add,
37 .detach = acpi_memory_device_remove,
38 .hotplug = {
39 .enabled = true,
40 },
41};
42
43struct acpi_memory_info {
44 struct list_head list;
45 u64 start_addr; /* Memory Range start physical addr */
46 u64 length; /* Memory Range length */
47 unsigned short caching; /* memory cache attribute */
48 unsigned short write_protect; /* memory read/write attribute */
49 unsigned int enabled:1;
50};
51
52struct acpi_memory_device {
53 struct acpi_device *device;
54 struct list_head res_list;
55 int mgid;
56};
57
58static acpi_status
59acpi_memory_get_resource(struct acpi_resource *resource, void *context)
60{
61 struct acpi_memory_device *mem_device = context;
62 struct acpi_resource_address64 address64;
63 struct acpi_memory_info *info, *new;
64 acpi_status status;
65
66 status = acpi_resource_to_address64(resource, &address64);
67 if (ACPI_FAILURE(status) ||
68 (address64.resource_type != ACPI_MEMORY_RANGE))
69 return AE_OK;
70
71 list_for_each_entry(info, &mem_device->res_list, list) {
72 /* Can we combine the resource range information? */
73 if ((info->caching == address64.info.mem.caching) &&
74 (info->write_protect == address64.info.mem.write_protect) &&
75 (info->start_addr + info->length == address64.address.minimum)) {
76 info->length += address64.address.address_length;
77 return AE_OK;
78 }
79 }
80
81 new = kzalloc_obj(struct acpi_memory_info);
82 if (!new)
83 return AE_ERROR;
84
85 INIT_LIST_HEAD(&new->list);
86 new->caching = address64.info.mem.caching;
87 new->write_protect = address64.info.mem.write_protect;
88 new->start_addr = address64.address.minimum;
89 new->length = address64.address.address_length;
90 list_add_tail(&new->list, &mem_device->res_list);
91
92 return AE_OK;
93}
94
95static void
96acpi_memory_free_device_resources(struct acpi_memory_device *mem_device)
97{
98 struct acpi_memory_info *info, *n;
99
100 list_for_each_entry_safe(info, n, &mem_device->res_list, list)
101 kfree(info);
102 INIT_LIST_HEAD(&mem_device->res_list);
103}
104
105static int
106acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
107{
108 acpi_status status;
109
110 if (!list_empty(&mem_device->res_list))
111 return 0;
112
113 status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,
114 acpi_memory_get_resource, mem_device);
115 if (ACPI_FAILURE(status)) {
116 acpi_memory_free_device_resources(mem_device);
117 return -EINVAL;
118 }
119
120 return 0;
121}
122
123static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
124{
125 unsigned long long current_status;
126
127 /* Get device present/absent information from the _STA */
128 if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle,
129 METHOD_NAME__STA, NULL,
130 &current_status)))
131 return -ENODEV;
132 /*
133 * Check for device status. Device should be
134 * present/enabled/functioning.
135 */
136 if (!((current_status & ACPI_STA_DEVICE_PRESENT)
137 && (current_status & ACPI_STA_DEVICE_ENABLED)
138 && (current_status & ACPI_STA_DEVICE_FUNCTIONING)))
139 return -ENODEV;
140
141 return 0;
142}
143
144static int acpi_bind_memblk(struct memory_block *mem, void *arg)
145{
146 return acpi_bind_one(&mem->dev, arg);
147}
148
149static int acpi_bind_memory_blocks(struct acpi_memory_info *info,
150 struct acpi_device *adev)
151{
152 return walk_memory_blocks(info->start_addr, info->length, adev,
153 acpi_bind_memblk);
154}
155
156static int acpi_unbind_memblk(struct memory_block *mem, void *arg)
157{
158 acpi_unbind_one(&mem->dev);
159 return 0;
160}
161
162static void acpi_unbind_memory_blocks(struct acpi_memory_info *info)
163{
164 walk_memory_blocks(info->start_addr, info->length, NULL,
165 acpi_unbind_memblk);
166}
167
168static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)
169{
170 acpi_handle handle = mem_device->device->handle;
171 mhp_t mhp_flags = MHP_NID_IS_MGID;
172 int result, num_enabled = 0;
173 struct acpi_memory_info *info;
174 u64 total_length = 0;
175 int node, mgid;
176
177 node = acpi_get_node(handle);
178
179 list_for_each_entry(info, &mem_device->res_list, list) {
180 if (!info->length)
181 continue;
182 /* We want a single node for the whole memory group */
183 if (node < 0)
184 node = memory_add_physaddr_to_nid(info->start_addr);
185 total_length += info->length;
186 }
187
188 if (!total_length) {
189 dev_err(&mem_device->device->dev, "device is empty\n");
190 return -EINVAL;
191 }
192
193 mgid = memory_group_register_static(node, PFN_UP(total_length));
194 if (mgid < 0)
195 return mgid;
196 mem_device->mgid = mgid;
197
198 /*
199 * Tell the VM there is more memory here...
200 * Note: Assume that this function returns zero on success
201 * We don't have memory-hot-add rollback function,now.
202 * (i.e. memory-hot-remove function)
203 */
204 list_for_each_entry(info, &mem_device->res_list, list) {
205 /*
206 * If the memory block size is zero, please ignore it.
207 * Don't try to do the following memory hotplug flowchart.
208 */
209 if (!info->length)
210 continue;
211
212 mhp_flags |= MHP_MEMMAP_ON_MEMORY;
213 result = __add_memory(mgid, info->start_addr, info->length,
214 mhp_flags);
215
216 /*
217 * If the memory block has been used by the kernel, add_memory()
218 * returns -EEXIST. If add_memory() returns the other error, it
219 * means that this memory block is not used by the kernel.
220 */
221 if (result && result != -EEXIST)
222 continue;
223
224 result = acpi_bind_memory_blocks(info, mem_device->device);
225 if (result) {
226 acpi_unbind_memory_blocks(info);
227 return -ENODEV;
228 }
229
230 info->enabled = 1;
231
232 /*
233 * Add num_enable even if add_memory() returns -EEXIST, so the
234 * device is bound to this driver.
235 */
236 num_enabled++;
237 }
238 if (!num_enabled) {
239 dev_err(&mem_device->device->dev, "add_memory failed\n");
240 return -EINVAL;
241 }
242 /*
243 * Sometimes the memory device will contain several memory blocks.
244 * When one memory block is hot-added to the system memory, it will
245 * be regarded as a success.
246 * Otherwise if the last memory block can't be hot-added to the system
247 * memory, it will be failure and the memory device can't be bound with
248 * driver.
249 */
250 return 0;
251}
252
253static void acpi_memory_remove_memory(struct acpi_memory_device *mem_device)
254{
255 struct acpi_memory_info *info, *n;
256
257 list_for_each_entry_safe(info, n, &mem_device->res_list, list) {
258 if (!info->enabled)
259 continue;
260
261 acpi_unbind_memory_blocks(info);
262 __remove_memory(info->start_addr, info->length);
263 list_del(&info->list);
264 kfree(info);
265 }
266}
267
268static void acpi_memory_device_free(struct acpi_memory_device *mem_device)
269{
270 if (!mem_device)
271 return;
272
273 /* In case we succeeded adding *some* memory, unregistering fails. */
274 if (mem_device->mgid >= 0)
275 memory_group_unregister(mem_device->mgid);
276
277 acpi_memory_free_device_resources(mem_device);
278 mem_device->device->driver_data = NULL;
279 kfree(mem_device);
280}
281
282static int acpi_memory_device_add(struct acpi_device *device,
283 const struct acpi_device_id *not_used)
284{
285 struct acpi_memory_device *mem_device;
286 int result;
287
288 if (!device)
289 return -EINVAL;
290
291 mem_device = kzalloc_obj(struct acpi_memory_device);
292 if (!mem_device)
293 return -ENOMEM;
294
295 INIT_LIST_HEAD(&mem_device->res_list);
296 mem_device->device = device;
297 mem_device->mgid = -1;
298 device->driver_data = mem_device;
299
300 /* Get the range from the _CRS */
301 result = acpi_memory_get_device_resources(mem_device);
302 if (result) {
303 device->driver_data = NULL;
304 kfree(mem_device);
305 return result;
306 }
307
308 result = acpi_memory_check_device(mem_device);
309 if (result) {
310 acpi_memory_device_free(mem_device);
311 return 0;
312 }
313
314 result = acpi_memory_enable_device(mem_device);
315 if (result) {
316 dev_err(&device->dev, "acpi_memory_enable_device() error\n");
317 acpi_memory_device_free(mem_device);
318 return result;
319 }
320
321 dev_dbg(&device->dev, "Memory device configured by ACPI\n");
322 return 1;
323}
324
325static void acpi_memory_device_remove(struct acpi_device *device)
326{
327 struct acpi_memory_device *mem_device;
328
329 if (!device || !acpi_driver_data(device))
330 return;
331
332 mem_device = acpi_driver_data(device);
333 acpi_memory_remove_memory(mem_device);
334 acpi_memory_device_free(mem_device);
335}
336
337static bool __initdata acpi_no_memhotplug;
338
339void __init acpi_memory_hotplug_init(void)
340{
341 if (acpi_no_memhotplug) {
342 memory_device_handler.attach = NULL;
343 acpi_scan_add_handler(&memory_device_handler);
344 return;
345 }
346 acpi_scan_add_handler_with_hotplug(&memory_device_handler, "memory");
347}
348
349static int __init disable_acpi_memory_hotplug(char *str)
350{
351 acpi_no_memhotplug = true;
352 return 1;
353}
354__setup("acpi_no_memhotplug", disable_acpi_memory_hotplug);
355
356#else
357
358static struct acpi_scan_handler memory_device_handler = {
359 .ids = memory_device_ids,
360};
361
362void __init acpi_memory_hotplug_init(void)
363{
364 acpi_scan_add_handler(&memory_device_handler);
365}
366
367#endif /* CONFIG_ACPI_HOTPLUG_MEMORY */