2019-05-27 08:55:05 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2010-07-07 12:31:02 +00:00
/*
* Copyright ( C ) 2010 Brian King IBM Corporation
*/
2013-05-07 04:34:11 +00:00
# include <linux/cpu.h>
2010-07-07 12:31:02 +00:00
# include <linux/delay.h>
# include <linux/suspend.h>
2011-05-27 13:27:45 -04:00
# include <linux/stat.h>
2010-07-07 12:31:02 +00:00
# include <asm/firmware.h>
# include <asm/hvcall.h>
# include <asm/machdep.h>
# include <asm/mmu.h>
# include <asm/rtas.h>
2012-01-11 06:56:04 +00:00
# include <asm/topology.h>
2014-02-25 20:02:18 -08:00
# include "../../kernel/cacheinfo.h"
2010-07-07 12:31:02 +00:00
static u64 stream_id ;
2011-12-21 15:09:52 -08:00
static struct device suspend_dev ;
2010-07-07 12:31:02 +00:00
static DECLARE_COMPLETION ( suspend_work ) ;
static struct rtas_suspend_me_data suspend_data ;
static atomic_t suspending ;
/**
* pseries_suspend_begin - First phase of hibernation
*
* Check to ensure we are in a valid state to hibernate
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_suspend_begin ( suspend_state_t state )
{
long vasi_state , rc ;
unsigned long retbuf [ PLPAR_HCALL_BUFSIZE ] ;
/* Make sure the state is valid */
rc = plpar_hcall ( H_VASI_STATE , retbuf , stream_id ) ;
vasi_state = retbuf [ 0 ] ;
if ( rc ) {
pr_err ( " pseries_suspend_begin: vasi_state returned %ld \n " , rc ) ;
return rc ;
} else if ( vasi_state = = H_VASI_ENABLED ) {
return - EAGAIN ;
} else if ( vasi_state ! = H_VASI_SUSPENDING ) {
pr_err ( " pseries_suspend_begin: vasi_state returned state %ld \n " ,
vasi_state ) ;
return - EIO ;
}
return 0 ;
}
/**
* pseries_suspend_cpu - Suspend a single CPU
*
* Makes the H_JOIN call to suspend the CPU
*
* */
static int pseries_suspend_cpu ( void )
{
if ( atomic_read ( & suspending ) )
return rtas_suspend_cpu ( & suspend_data ) ;
return 0 ;
}
2014-02-25 20:02:18 -08:00
/**
* pseries_suspend_enable_irqs
*
* Post suspend configuration updates
*
* */
static void pseries_suspend_enable_irqs ( void )
{
/*
* Update configuration which can be modified based on device tree
* changes during resume .
*/
cacheinfo_cpu_offline ( smp_processor_id ( ) ) ;
post_mobility_fixup ( ) ;
cacheinfo_cpu_online ( smp_processor_id ( ) ) ;
}
2010-07-07 12:31:02 +00:00
/**
* pseries_suspend_enter - Final phase of hibernation
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_suspend_enter ( suspend_state_t state )
{
int rc = rtas_suspend_last_cpu ( & suspend_data ) ;
atomic_set ( & suspending , 0 ) ;
atomic_set ( & suspend_data . done , 1 ) ;
return rc ;
}
/**
* pseries_prepare_late - Prepare to suspend all other CPUs
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_prepare_late ( void )
{
atomic_set ( & suspending , 1 ) ;
atomic_set ( & suspend_data . working , 0 ) ;
atomic_set ( & suspend_data . done , 0 ) ;
atomic_set ( & suspend_data . error , 0 ) ;
suspend_data . complete = & suspend_work ;
2013-11-14 14:32:02 -08:00
reinit_completion ( & suspend_work ) ;
2010-07-07 12:31:02 +00:00
return 0 ;
}
/**
* store_hibernate - Initiate partition hibernation
2011-12-21 15:09:52 -08:00
* @ dev : subsys root device
* @ attr : device attribute struct
2010-07-07 12:31:02 +00:00
* @ buf : buffer
* @ count : buffer size
*
* Write the stream ID received from the HMC to this file
* to trigger hibernating the partition
*
* Return value :
* number of bytes printed to buffer / other on failure
* */
2011-12-21 15:09:52 -08:00
static ssize_t store_hibernate ( struct device * dev ,
struct device_attribute * attr ,
2010-07-07 12:31:02 +00:00
const char * buf , size_t count )
{
2013-05-07 04:34:11 +00:00
cpumask_var_t offline_mask ;
2010-07-07 12:31:02 +00:00
int rc ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
2017-09-13 16:28:29 -07:00
if ( ! alloc_cpumask_var ( & offline_mask , GFP_KERNEL ) )
2013-05-07 04:34:11 +00:00
return - ENOMEM ;
2010-07-07 12:31:02 +00:00
stream_id = simple_strtoul ( buf , NULL , 16 ) ;
do {
rc = pseries_suspend_begin ( PM_SUSPEND_MEM ) ;
if ( rc = = - EAGAIN )
ssleep ( 1 ) ;
} while ( rc = = - EAGAIN ) ;
2012-01-11 06:56:04 +00:00
if ( ! rc ) {
2013-05-07 04:34:11 +00:00
/* All present CPUs must be online */
cpumask_andnot ( offline_mask , cpu_present_mask ,
cpu_online_mask ) ;
rc = rtas_online_cpus_mask ( offline_mask ) ;
if ( rc ) {
pr_err ( " %s: Could not bring present CPUs online. \n " ,
__func__ ) ;
goto out ;
}
2012-01-11 06:56:04 +00:00
stop_topology_update ( ) ;
2010-07-07 12:31:02 +00:00
rc = pm_suspend ( PM_SUSPEND_MEM ) ;
2012-01-11 06:56:04 +00:00
start_topology_update ( ) ;
2013-05-07 04:34:11 +00:00
/* Take down CPUs not online prior to suspend */
if ( ! rtas_offline_cpus_mask ( offline_mask ) )
pr_warn ( " %s: Could not restore CPUs to offline "
" state. \n " , __func__ ) ;
2012-01-11 06:56:04 +00:00
}
2010-07-07 12:31:02 +00:00
stream_id = 0 ;
if ( ! rc )
rc = count ;
2013-05-07 04:34:11 +00:00
out :
free_cpumask_var ( offline_mask ) ;
2010-07-07 12:31:02 +00:00
return rc ;
}
2014-02-19 12:56:54 -08:00
# define USER_DT_UPDATE 0
# define KERN_DT_UPDATE 1
/**
* show_hibernate - Report device tree update responsibilty
* @ dev : subsys root device
* @ attr : device attribute struct
* @ buf : buffer
*
* Report whether a device tree update is performed by the kernel after a
* resume , or if drmgr must coordinate the update from user space .
*
* Return value :
* 0 if drmgr is to initiate update , and 1 otherwise
* */
static ssize_t show_hibernate ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
return sprintf ( buf , " %d \n " , KERN_DT_UPDATE ) ;
}
2017-01-12 14:54:13 +11:00
static DEVICE_ATTR ( hibernate , 0644 , show_hibernate , store_hibernate ) ;
2010-07-07 12:31:02 +00:00
2011-12-21 15:09:52 -08:00
static struct bus_type suspend_subsys = {
2010-07-07 12:31:02 +00:00
. name = " power " ,
2011-12-21 15:09:52 -08:00
. dev_name = " power " ,
2010-07-07 12:31:02 +00:00
} ;
2010-11-16 14:14:02 +01:00
static const struct platform_suspend_ops pseries_suspend_ops = {
2010-07-07 12:31:02 +00:00
. valid = suspend_valid_only_mem ,
. begin = pseries_suspend_begin ,
. prepare_late = pseries_prepare_late ,
. enter = pseries_suspend_enter ,
} ;
/**
* pseries_suspend_sysfs_register - Register with sysfs
*
* Return value :
* 0 on success / other on failure
* */
2011-12-21 15:09:52 -08:00
static int pseries_suspend_sysfs_register ( struct device * dev )
2010-07-07 12:31:02 +00:00
{
int rc ;
2011-12-21 15:09:52 -08:00
if ( ( rc = subsys_system_register ( & suspend_subsys , NULL ) ) )
2010-07-07 12:31:02 +00:00
return rc ;
2011-12-21 15:09:52 -08:00
dev - > id = 0 ;
dev - > bus = & suspend_subsys ;
2010-07-07 12:31:02 +00:00
2011-12-21 15:09:52 -08:00
if ( ( rc = device_create_file ( suspend_subsys . dev_root , & dev_attr_hibernate ) ) )
goto subsys_unregister ;
2010-07-07 12:31:02 +00:00
return 0 ;
2011-12-21 15:09:52 -08:00
subsys_unregister :
bus_unregister ( & suspend_subsys ) ;
2010-07-07 12:31:02 +00:00
return rc ;
}
/**
* pseries_suspend_init - initcall for pSeries suspend
*
* Return value :
* 0 on success / other on failure
* */
static int __init pseries_suspend_init ( void )
{
int rc ;
2014-07-16 12:02:43 +10:00
if ( ! firmware_has_feature ( FW_FEATURE_LPAR ) )
2010-07-07 12:31:02 +00:00
return 0 ;
suspend_data . token = rtas_token ( " ibm,suspend-me " ) ;
if ( suspend_data . token = = RTAS_UNKNOWN_SERVICE )
return 0 ;
2011-12-21 15:09:52 -08:00
if ( ( rc = pseries_suspend_sysfs_register ( & suspend_dev ) ) )
2010-07-07 12:31:02 +00:00
return rc ;
ppc_md . suspend_disable_cpu = pseries_suspend_cpu ;
2014-02-25 20:02:18 -08:00
ppc_md . suspend_enable_irqs = pseries_suspend_enable_irqs ;
2010-07-07 12:31:02 +00:00
suspend_set_ops ( & pseries_suspend_ops ) ;
return 0 ;
}
2014-07-16 12:02:43 +10:00
machine_device_initcall ( pseries , pseries_suspend_init ) ;