162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2010 Brian King IBM Corporation 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/cpu.h> 762306a36Sopenharmony_ci#include <linux/delay.h> 862306a36Sopenharmony_ci#include <linux/suspend.h> 962306a36Sopenharmony_ci#include <linux/stat.h> 1062306a36Sopenharmony_ci#include <asm/firmware.h> 1162306a36Sopenharmony_ci#include <asm/hvcall.h> 1262306a36Sopenharmony_ci#include <asm/machdep.h> 1362306a36Sopenharmony_ci#include <asm/mmu.h> 1462306a36Sopenharmony_ci#include <asm/rtas.h> 1562306a36Sopenharmony_ci#include <asm/topology.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic struct device suspend_dev; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/** 2062306a36Sopenharmony_ci * pseries_suspend_begin - First phase of hibernation 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Check to ensure we are in a valid state to hibernate 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * Return value: 2562306a36Sopenharmony_ci * 0 on success / other on failure 2662306a36Sopenharmony_ci **/ 2762306a36Sopenharmony_cistatic int pseries_suspend_begin(u64 stream_id) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci long vasi_state, rc; 3062306a36Sopenharmony_ci unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci /* Make sure the state is valid */ 3362306a36Sopenharmony_ci rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci vasi_state = retbuf[0]; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci if (rc) { 3862306a36Sopenharmony_ci pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); 3962306a36Sopenharmony_ci return rc; 4062306a36Sopenharmony_ci } else if (vasi_state == H_VASI_ENABLED) { 4162306a36Sopenharmony_ci return -EAGAIN; 4262306a36Sopenharmony_ci } else if (vasi_state != H_VASI_SUSPENDING) { 4362306a36Sopenharmony_ci pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", 4462306a36Sopenharmony_ci vasi_state); 4562306a36Sopenharmony_ci return -EIO; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/** 5162306a36Sopenharmony_ci * pseries_suspend_enter - Final phase of hibernation 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci * Return value: 5462306a36Sopenharmony_ci * 0 on success / other on failure 5562306a36Sopenharmony_ci **/ 5662306a36Sopenharmony_cistatic int pseries_suspend_enter(suspend_state_t state) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return rtas_ibm_suspend_me(NULL); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/** 6262306a36Sopenharmony_ci * store_hibernate - Initiate partition hibernation 6362306a36Sopenharmony_ci * @dev: subsys root device 6462306a36Sopenharmony_ci * @attr: device attribute struct 6562306a36Sopenharmony_ci * @buf: buffer 6662306a36Sopenharmony_ci * @count: buffer size 6762306a36Sopenharmony_ci * 6862306a36Sopenharmony_ci * Write the stream ID received from the HMC to this file 6962306a36Sopenharmony_ci * to trigger hibernating the partition 7062306a36Sopenharmony_ci * 7162306a36Sopenharmony_ci * Return value: 7262306a36Sopenharmony_ci * number of bytes printed to buffer / other on failure 7362306a36Sopenharmony_ci **/ 7462306a36Sopenharmony_cistatic ssize_t store_hibernate(struct device *dev, 7562306a36Sopenharmony_ci struct device_attribute *attr, 7662306a36Sopenharmony_ci const char *buf, size_t count) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci u64 stream_id; 7962306a36Sopenharmony_ci int rc; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 8262306a36Sopenharmony_ci return -EPERM; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci stream_id = simple_strtoul(buf, NULL, 16); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci do { 8762306a36Sopenharmony_ci rc = pseries_suspend_begin(stream_id); 8862306a36Sopenharmony_ci if (rc == -EAGAIN) 8962306a36Sopenharmony_ci ssleep(1); 9062306a36Sopenharmony_ci } while (rc == -EAGAIN); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (!rc) 9362306a36Sopenharmony_ci rc = pm_suspend(PM_SUSPEND_MEM); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (!rc) { 9662306a36Sopenharmony_ci rc = count; 9762306a36Sopenharmony_ci post_mobility_fixup(); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return rc; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci#define USER_DT_UPDATE 0 10562306a36Sopenharmony_ci#define KERN_DT_UPDATE 1 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci/** 10862306a36Sopenharmony_ci * show_hibernate - Report device tree update responsibilty 10962306a36Sopenharmony_ci * @dev: subsys root device 11062306a36Sopenharmony_ci * @attr: device attribute struct 11162306a36Sopenharmony_ci * @buf: buffer 11262306a36Sopenharmony_ci * 11362306a36Sopenharmony_ci * Report whether a device tree update is performed by the kernel after a 11462306a36Sopenharmony_ci * resume, or if drmgr must coordinate the update from user space. 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Return value: 11762306a36Sopenharmony_ci * 0 if drmgr is to initiate update, and 1 otherwise 11862306a36Sopenharmony_ci **/ 11962306a36Sopenharmony_cistatic ssize_t show_hibernate(struct device *dev, 12062306a36Sopenharmony_ci struct device_attribute *attr, 12162306a36Sopenharmony_ci char *buf) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci return sprintf(buf, "%d\n", KERN_DT_UPDATE); 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic struct bus_type suspend_subsys = { 12962306a36Sopenharmony_ci .name = "power", 13062306a36Sopenharmony_ci .dev_name = "power", 13162306a36Sopenharmony_ci}; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic const struct platform_suspend_ops pseries_suspend_ops = { 13462306a36Sopenharmony_ci .valid = suspend_valid_only_mem, 13562306a36Sopenharmony_ci .enter = pseries_suspend_enter, 13662306a36Sopenharmony_ci}; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci/** 13962306a36Sopenharmony_ci * pseries_suspend_sysfs_register - Register with sysfs 14062306a36Sopenharmony_ci * 14162306a36Sopenharmony_ci * Return value: 14262306a36Sopenharmony_ci * 0 on success / other on failure 14362306a36Sopenharmony_ci **/ 14462306a36Sopenharmony_cistatic int pseries_suspend_sysfs_register(struct device *dev) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct device *dev_root; 14762306a36Sopenharmony_ci int rc; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if ((rc = subsys_system_register(&suspend_subsys, NULL))) 15062306a36Sopenharmony_ci return rc; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci dev->id = 0; 15362306a36Sopenharmony_ci dev->bus = &suspend_subsys; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci dev_root = bus_get_dev_root(&suspend_subsys); 15662306a36Sopenharmony_ci if (dev_root) { 15762306a36Sopenharmony_ci rc = device_create_file(dev_root, &dev_attr_hibernate); 15862306a36Sopenharmony_ci put_device(dev_root); 15962306a36Sopenharmony_ci if (rc) 16062306a36Sopenharmony_ci goto subsys_unregister; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cisubsys_unregister: 16662306a36Sopenharmony_ci bus_unregister(&suspend_subsys); 16762306a36Sopenharmony_ci return rc; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/** 17162306a36Sopenharmony_ci * pseries_suspend_init - initcall for pSeries suspend 17262306a36Sopenharmony_ci * 17362306a36Sopenharmony_ci * Return value: 17462306a36Sopenharmony_ci * 0 on success / other on failure 17562306a36Sopenharmony_ci **/ 17662306a36Sopenharmony_cistatic int __init pseries_suspend_init(void) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci int rc; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (!firmware_has_feature(FW_FEATURE_LPAR)) 18162306a36Sopenharmony_ci return 0; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if ((rc = pseries_suspend_sysfs_register(&suspend_dev))) 18462306a36Sopenharmony_ci return rc; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci suspend_set_ops(&pseries_suspend_ops); 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_cimachine_device_initcall(pseries, pseries_suspend_init); 190