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_hotplug.h>
  32
  33#include "internal.h"
  34
  35#define ACPI_MEMORY_DEVICE_CLASS                "memory"
  36#define ACPI_MEMORY_DEVICE_HID                  "PNP0C80"
  37#define ACPI_MEMORY_DEVICE_NAME                 "Hotplug Mem Device"
  38
  39#define _COMPONENT              ACPI_MEMORY_DEVICE_COMPONENT
  40
  41#undef PREFIX
  42#define         PREFIX          "ACPI:memory_hp:"
  43
  44ACPI_MODULE_NAME("acpi_memhotplug");
  45
  46/* Memory Device States */
  47#define MEMORY_INVALID_STATE    0
  48#define MEMORY_POWER_ON_STATE   1
  49#define MEMORY_POWER_OFF_STATE  2
  50
  51static int acpi_memory_device_add(struct acpi_device *device,
  52                                  const struct acpi_device_id *not_used);
  53static void acpi_memory_device_remove(struct acpi_device *device);
  54
  55static const struct acpi_device_id memory_device_ids[] = {
  56        {ACPI_MEMORY_DEVICE_HID, 0},
  57        {"", 0},
  58};
  59
  60static struct acpi_scan_handler memory_device_handler = {
  61        .ids = memory_device_ids,
  62        .attach = acpi_memory_device_add,
  63        .detach = acpi_memory_device_remove,
  64        .hotplug = {
  65                .enabled = true,
  66        },
  67};
  68
  69struct acpi_memory_info {
  70        struct list_head list;
  71        u64 start_addr;         /* Memory Range start physical addr */
  72        u64 length;             /* Memory Range length */
  73        unsigned short caching; /* memory cache attribute */
  74        unsigned short write_protect;   /* memory read/write attribute */
  75        unsigned int enabled:1;
  76};
  77
  78struct acpi_memory_device {
  79        struct acpi_device * device;
  80        unsigned int state;     /* State of the memory device */
  81        struct list_head res_list;
  82};
  83
  84static acpi_status
  85acpi_memory_get_resource(struct acpi_resource *resource, void *context)
  86{
  87        struct acpi_memory_device *mem_device = context;
  88        struct acpi_resource_address64 address64;
  89        struct acpi_memory_info *info, *new;
  90        acpi_status status;
  91
  92        status = acpi_resource_to_address64(resource, &address64);
  93        if (ACPI_FAILURE(status) ||
  94            (address64.resource_type != ACPI_MEMORY_RANGE))
  95                return AE_OK;
  96
  97        list_for_each_entry(info, &mem_device->res_list, list) {
  98                /* Can we combine the resource range information? */
  99                if ((info->caching == address64.info.mem.caching) &&
 100                    (info->write_protect == address64.info.mem.write_protect) &&
 101                    (info->start_addr + info->length == address64.minimum)) {
 102                        info->length += address64.address_length;
 103                        return AE_OK;
 104                }
 105        }
 106
 107        new = kzalloc(sizeof(struct acpi_memory_info), GFP_KERNEL);
 108        if (!new)
 109                return AE_ERROR;
 110
 111        INIT_LIST_HEAD(&new->list);
 112        new->caching = address64.info.mem.caching;
 113        new->write_protect = address64.info.mem.write_protect;
 114        new->start_addr = address64.minimum;
 115        new->length = address64.address_length;
 116        list_add_tail(&new->list, &mem_device->res_list);
 117
 118        return AE_OK;
 119}
 120
 121static void
 122acpi_memory_free_device_resources(struct acpi_memory_device *mem_device)
 123{
 124        struct acpi_memory_info *info, *n;
 125
 126        list_for_each_entry_safe(info, n, &mem_device->res_list, list)
 127                kfree(info);
 128        INIT_LIST_HEAD(&mem_device->res_list);
 129}
 130
 131static int
 132acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
 133{
 134        acpi_status status;
 135
 136        if (!list_empty(&mem_device->res_list))
 137                return 0;
 138
 139        status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,
 140                                     acpi_memory_get_resource, mem_device);
 141        if (ACPI_FAILURE(status)) {
 142                acpi_memory_free_device_resources(mem_device);
 143                return -EINVAL;
 144        }
 145
 146        return 0;
 147}
 148
 149static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
 150{
 151        unsigned long long current_status;
 152
 153        /* Get device present/absent information from the _STA */
 154        if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle, "_STA",
 155                                               NULL, &current_status)))
 156                return -ENODEV;
 157        /*
 158         * Check for device status. Device should be
 159         * present/enabled/functioning.
 160         */
 161        if (!((current_status & ACPI_STA_DEVICE_PRESENT)
 162              && (current_status & ACPI_STA_DEVICE_ENABLED)
 163              && (current_status & ACPI_STA_DEVICE_FUNCTIONING)))
 164                return -ENODEV;
 165
 166        return 0;
 167}
 168
 169static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)
 170{
 171        int result, num_enabled = 0;
 172        struct acpi_memory_info *info;
 173        int node;
 174
 175        node = acpi_get_node(mem_device->device->handle);
 176        /*
 177         * Tell the VM there is more memory here...
 178         * Note: Assume that this function returns zero on success
 179         * We don't have memory-hot-add rollback function,now.
 180         * (i.e. memory-hot-remove function)
 181         */
 182        list_for_each_entry(info, &mem_device->res_list, list) {
 183                if (info->enabled) { /* just sanity check...*/
 184                        num_enabled++;
 185                        continue;
 186                }
 187                /*
 188                 * If the memory block size is zero, please ignore it.
 189                 * Don't try to do the following memory hotplug flowchart.
 190                 */
 191                if (!info->length)
 192                        continue;
 193                if (node < 0)
 194                        node = memory_add_physaddr_to_nid(info->start_addr);
 195
 196                result = add_memory(node, info->start_addr, info->length);
 197
 198                /*
 199                 * If the memory block has been used by the kernel, add_memory()
 200                 * returns -EEXIST. If add_memory() returns the other error, it
 201                 * means that this memory block is not used by the kernel.
 202                 */
 203                if (result && result != -EEXIST)
 204                        continue;
 205
 206                info->enabled = 1;
 207
 208                /*
 209                 * Add num_enable even if add_memory() returns -EEXIST, so the
 210                 * device is bound to this driver.
 211                 */
 212                num_enabled++;
 213        }
 214        if (!num_enabled) {
 215                dev_err(&mem_device->device->dev, "add_memory failed\n");
 216                mem_device->state = MEMORY_INVALID_STATE;
 217                return -EINVAL;
 218        }
 219        /*
 220         * Sometimes the memory device will contain several memory blocks.
 221         * When one memory block is hot-added to the system memory, it will
 222         * be regarded as a success.
 223         * Otherwise if the last memory block can't be hot-added to the system
 224         * memory, it will be failure and the memory device can't be bound with
 225         * driver.
 226         */
 227        return 0;
 228}
 229
 230static int acpi_memory_remove_memory(struct acpi_memory_device *mem_device)
 231{
 232        int result = 0, nid;
 233        struct acpi_memory_info *info, *n;
 234
 235        nid = acpi_get_node(mem_device->device->handle);
 236
 237        list_for_each_entry_safe(info, n, &mem_device->res_list, list) {
 238                if (!info->enabled)
 239                        continue;
 240
 241                if (nid < 0)
 242                        nid = memory_add_physaddr_to_nid(info->start_addr);
 243                result = remove_memory(nid, info->start_addr, info->length);
 244                if (result)
 245                        return result;
 246
 247                list_del(&info->list);
 248                kfree(info);
 249        }
 250
 251        return result;
 252}
 253
 254static void acpi_memory_device_free(struct acpi_memory_device *mem_device)
 255{
 256        if (!mem_device)
 257                return;
 258
 259        acpi_memory_free_device_resources(mem_device);
 260        mem_device->device->driver_data = NULL;
 261        kfree(mem_device);
 262}
 263
 264static int acpi_memory_device_add(struct acpi_device *device,
 265                                  const struct acpi_device_id *not_used)
 266{
 267        struct acpi_memory_device *mem_device;
 268        int result;
 269
 270        if (!device)
 271                return -EINVAL;
 272
 273        mem_device = kzalloc(sizeof(struct acpi_memory_device), GFP_KERNEL);
 274        if (!mem_device)
 275                return -ENOMEM;
 276
 277        INIT_LIST_HEAD(&mem_device->res_list);
 278        mem_device->device = device;
 279        sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME);
 280        sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS);
 281        device->driver_data = mem_device;
 282
 283        /* Get the range from the _CRS */
 284        result = acpi_memory_get_device_resources(mem_device);
 285        if (result) {
 286                kfree(mem_device);
 287                return result;
 288        }
 289
 290        /* Set the device state */
 291        mem_device->state = MEMORY_POWER_ON_STATE;
 292
 293        result = acpi_memory_check_device(mem_device);
 294        if (result) {
 295                acpi_memory_device_free(mem_device);
 296                return 0;
 297        }
 298
 299        result = acpi_memory_enable_device(mem_device);
 300        if (result) {
 301                dev_err(&device->dev, "acpi_memory_enable_device() error\n");
 302                acpi_memory_device_free(mem_device);
 303                return -ENODEV;
 304        }
 305
 306        dev_dbg(&device->dev, "Memory device configured by ACPI\n");
 307        return 1;
 308}
 309
 310static void acpi_memory_device_remove(struct acpi_device *device)
 311{
 312        struct acpi_memory_device *mem_device;
 313
 314        if (!device || !acpi_driver_data(device))
 315                return;
 316
 317        mem_device = acpi_driver_data(device);
 318        acpi_memory_remove_memory(mem_device);
 319        acpi_memory_device_free(mem_device);
 320}
 321
 322void __init acpi_memory_hotplug_init(void)
 323{
 324        acpi_scan_add_handler_with_hotplug(&memory_device_handler, "memory");
 325}
 326
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.