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