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