linux/arch/powerpc/platforms/pseries/suspend.c
<<
>>
Prefs
   1/*
   2  * Copyright (C) 2010 Brian King IBM Corporation
   3  *
   4  * This program is free software; you can redistribute it and/or modify
   5  * it under the terms of the GNU General Public License as published by
   6  * the Free Software Foundation; either version 2 of the License, or
   7  * (at your option) any later version.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  * GNU General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU General Public License
  15  * along with this program; if not, write to the Free Software
  16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  17  */
  18
  19#include <linux/cpu.h>
  20#include <linux/delay.h>
  21#include <linux/suspend.h>
  22#include <linux/stat.h>
  23#include <asm/firmware.h>
  24#include <asm/hvcall.h>
  25#include <asm/machdep.h>
  26#include <asm/mmu.h>
  27#include <asm/rtas.h>
  28#include <asm/topology.h>
  29#include "../../kernel/cacheinfo.h"
  30
  31static u64 stream_id;
  32static struct device suspend_dev;
  33static DECLARE_COMPLETION(suspend_work);
  34static struct rtas_suspend_me_data suspend_data;
  35static atomic_t suspending;
  36
  37/**
  38 * pseries_suspend_begin - First phase of hibernation
  39 *
  40 * Check to ensure we are in a valid state to hibernate
  41 *
  42 * Return value:
  43 *      0 on success / other on failure
  44 **/
  45static int pseries_suspend_begin(suspend_state_t state)
  46{
  47        long vasi_state, rc;
  48        unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
  49
  50        /* Make sure the state is valid */
  51        rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id);
  52
  53        vasi_state = retbuf[0];
  54
  55        if (rc) {
  56                pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc);
  57                return rc;
  58        } else if (vasi_state == H_VASI_ENABLED) {
  59                return -EAGAIN;
  60        } else if (vasi_state != H_VASI_SUSPENDING) {
  61                pr_err("pseries_suspend_begin: vasi_state returned state %ld\n",
  62                       vasi_state);
  63                return -EIO;
  64        }
  65
  66        return 0;
  67}
  68
  69/**
  70 * pseries_suspend_cpu - Suspend a single CPU
  71 *
  72 * Makes the H_JOIN call to suspend the CPU
  73 *
  74 **/
  75static int pseries_suspend_cpu(void)
  76{
  77        if (atomic_read(&suspending))
  78                return rtas_suspend_cpu(&suspend_data);
  79        return 0;
  80}
  81
  82/**
  83 * pseries_suspend_enable_irqs
  84 *
  85 * Post suspend configuration updates
  86 *
  87 **/
  88static void pseries_suspend_enable_irqs(void)
  89{
  90        /*
  91         * Update configuration which can be modified based on device tree
  92         * changes during resume.
  93         */
  94        cacheinfo_cpu_offline(smp_processor_id());
  95        post_mobility_fixup();
  96        cacheinfo_cpu_online(smp_processor_id());
  97}
  98
  99/**
 100 * pseries_suspend_enter - Final phase of hibernation
 101 *
 102 * Return value:
 103 *      0 on success / other on failure
 104 **/
 105static int pseries_suspend_enter(suspend_state_t state)
 106{
 107        int rc = rtas_suspend_last_cpu(&suspend_data);
 108
 109        atomic_set(&suspending, 0);
 110        atomic_set(&suspend_data.done, 1);
 111        return rc;
 112}
 113
 114/**
 115 * pseries_prepare_late - Prepare to suspend all other CPUs
 116 *
 117 * Return value:
 118 *      0 on success / other on failure
 119 **/
 120static int pseries_prepare_late(void)
 121{
 122        atomic_set(&suspending, 1);
 123        atomic_set(&suspend_data.working, 0);
 124        atomic_set(&suspend_data.done, 0);
 125        atomic_set(&suspend_data.error, 0);
 126        suspend_data.complete = &suspend_work;
 127        reinit_completion(&suspend_work);
 128        return 0;
 129}
 130
 131/**
 132 * store_hibernate - Initiate partition hibernation
 133 * @dev:                subsys root device
 134 * @attr:               device attribute struct
 135 * @buf:                buffer
 136 * @count:              buffer size
 137 *
 138 * Write the stream ID received from the HMC to this file
 139 * to trigger hibernating the partition
 140 *
 141 * Return value:
 142 *      number of bytes printed to buffer / other on failure
 143 **/
 144static ssize_t store_hibernate(struct device *dev,
 145                               struct device_attribute *attr,
 146                               const char *buf, size_t count)
 147{
 148        cpumask_var_t offline_mask;
 149        int rc;
 150
 151        if (!capable(CAP_SYS_ADMIN))
 152                return -EPERM;
 153
 154        if (!alloc_cpumask_var(&offline_mask, GFP_KERNEL))
 155                return -ENOMEM;
 156
 157        stream_id = simple_strtoul(buf, NULL, 16);
 158
 159        do {
 160                rc = pseries_suspend_begin(PM_SUSPEND_MEM);
 161                if (rc == -EAGAIN)
 162                        ssleep(1);
 163        } while (rc == -EAGAIN);
 164
 165        if (!rc) {
 166                /* All present CPUs must be online */
 167                cpumask_andnot(offline_mask, cpu_present_mask,
 168                                cpu_online_mask);
 169                rc = rtas_online_cpus_mask(offline_mask);
 170                if (rc) {
 171                        pr_err("%s: Could not bring present CPUs online.\n",
 172                                        __func__);
 173                        goto out;
 174                }
 175
 176                stop_topology_update();
 177                rc = pm_suspend(PM_SUSPEND_MEM);
 178                start_topology_update();
 179
 180                /* Take down CPUs not online prior to suspend */
 181                if (!rtas_offline_cpus_mask(offline_mask))
 182                        pr_warn("%s: Could not restore CPUs to offline "
 183                                        "state.\n", __func__);
 184        }
 185
 186        stream_id = 0;
 187
 188        if (!rc)
 189                rc = count;
 190out:
 191        free_cpumask_var(offline_mask);
 192        return rc;
 193}
 194
 195#define USER_DT_UPDATE  0
 196#define KERN_DT_UPDATE  1
 197
 198/**
 199 * show_hibernate - Report device tree update responsibilty
 200 * @dev:                subsys root device
 201 * @attr:               device attribute struct
 202 * @buf:                buffer
 203 *
 204 * Report whether a device tree update is performed by the kernel after a
 205 * resume, or if drmgr must coordinate the update from user space.
 206 *
 207 * Return value:
 208 *      0 if drmgr is to initiate update, and 1 otherwise
 209 **/
 210static ssize_t show_hibernate(struct device *dev,
 211                              struct device_attribute *attr,
 212                              char *buf)
 213{
 214        return sprintf(buf, "%d\n", KERN_DT_UPDATE);
 215}
 216
 217static DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate);
 218
 219static struct bus_type suspend_subsys = {
 220        .name = "power",
 221        .dev_name = "power",
 222};
 223
 224static const struct platform_suspend_ops pseries_suspend_ops = {
 225        .valid          = suspend_valid_only_mem,
 226        .begin          = pseries_suspend_begin,
 227        .prepare_late   = pseries_prepare_late,
 228        .enter          = pseries_suspend_enter,
 229};
 230
 231/**
 232 * pseries_suspend_sysfs_register - Register with sysfs
 233 *
 234 * Return value:
 235 *      0 on success / other on failure
 236 **/
 237static int pseries_suspend_sysfs_register(struct device *dev)
 238{
 239        int rc;
 240
 241        if ((rc = subsys_system_register(&suspend_subsys, NULL)))
 242                return rc;
 243
 244        dev->id = 0;
 245        dev->bus = &suspend_subsys;
 246
 247        if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate)))
 248                goto subsys_unregister;
 249
 250        return 0;
 251
 252subsys_unregister:
 253        bus_unregister(&suspend_subsys);
 254        return rc;
 255}
 256
 257/**
 258 * pseries_suspend_init - initcall for pSeries suspend
 259 *
 260 * Return value:
 261 *      0 on success / other on failure
 262 **/
 263static int __init pseries_suspend_init(void)
 264{
 265        int rc;
 266
 267        if (!firmware_has_feature(FW_FEATURE_LPAR))
 268                return 0;
 269
 270        suspend_data.token = rtas_token("ibm,suspend-me");
 271        if (suspend_data.token == RTAS_UNKNOWN_SERVICE)
 272                return 0;
 273
 274        if ((rc = pseries_suspend_sysfs_register(&suspend_dev)))
 275                return rc;
 276
 277        ppc_md.suspend_disable_cpu = pseries_suspend_cpu;
 278        ppc_md.suspend_enable_irqs = pseries_suspend_enable_irqs;
 279        suspend_set_ops(&pseries_suspend_ops);
 280        return 0;
 281}
 282machine_device_initcall(pseries, pseries_suspend_init);
 283