WSL2-Linux-Kernel/arch/powerpc/platforms/pseries/suspend.c

247 строки
5.3 KiB
C
Исходник Обычный вид История

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2010 Brian King IBM Corporation
*/
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <linux/stat.h>
#include <asm/firmware.h>
#include <asm/hvcall.h>
#include <asm/machdep.h>
#include <asm/mmu.h>
#include <asm/rtas.h>
powerpc/pseries: Fix partition migration hang in stop_topology_update This fixes a hang that was observed during live partition migration. Since stop_topology_update must not be called from an interrupt context, call it earlier in the migration process. The hang observed can be seen below: WARNING: at kernel/timer.c:1011 Modules linked in: ip6t_LOG xt_tcpudp xt_pkttype ipt_LOG xt_limit ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_raw xt_NOTRACK ipt_REJECT xt_state iptable_raw iptable_filter ip6table_mangle nf_conntrack_netbios_ns nf_conntrack_broadcast nf_conntrack_ipv4 nf_conntrack nf_defrag_ipv4 ip_tables ip6table_filter ip6_tables x_tables ipv6 fuse loop ibmveth sg ext3 jbd mbcache raid456 async_raid6_recov async_pq raid6_pq async_xor xor async_memcpy async_tx raid10 raid1 raid0 scsi_dh_alua scsi_dh_rdac scsi_dh_hp_sw scsi_dh_emc dm_round_robin dm_multipath scsi_dh sd_mod crc_t10dif ibmvfc scsi_transport_fc scsi_tgt scsi_mod dm_snapshot dm_mod NIP: c0000000000c52d8 LR: c00000000004be28 CTR: 0000000000000000 REGS: c00000005ffd77d0 TRAP: 0700 Not tainted (3.2.0-git-00001-g07d106d) MSR: 8000000000021032 <ME,CE,IR,DR> CR: 48000084 XER: 00000001 CFAR: c00000000004be20 TASK = c00000005ec78860[0] 'swapper/3' THREAD: c00000005ec98000 CPU: 3 GPR00: 0000000000000001 c00000005ffd7a50 c000000000fbbc98 c000000000ec8340 GPR04: 00000000282a0020 0000000000000000 0000000000004000 0000000000000101 GPR08: 0000000000000012 c00000005ffd4000 0000000000000020 c000000000f3ba88 GPR12: 0000000000000000 c000000007f40900 0000000000000001 0000000000000004 GPR16: 0000000000000001 0000000000000000 0000000000000000 c000000001022310 GPR20: 0000000000000001 0000000000000000 0000000000200200 c000000001029e14 GPR24: 0000000000000000 0000000000000001 0000000000000040 c00000003f74bc80 GPR28: c00000003f74bc84 c000000000f38038 c000000000f16b58 c000000000ec8340 NIP [c0000000000c52d8] .del_timer_sync+0x28/0x60 LR [c00000000004be28] .stop_topology_update+0x20/0x38 Call Trace: [c00000005ffd7a50] [c00000005ec78860] 0xc00000005ec78860 (unreliable) [c00000005ffd7ad0] [c00000000004be28] .stop_topology_update+0x20/0x38 [c00000005ffd7b40] [c000000000028378] .__rtas_suspend_last_cpu+0x58/0x260 [c00000005ffd7bf0] [c0000000000fa230] .generic_smp_call_function_interrupt+0x160/0x358 [c00000005ffd7cf0] [c000000000036ec8] .smp_ipi_demux+0x88/0x100 [c00000005ffd7d80] [c00000000005c154] .icp_hv_ipi_action+0x5c/0x80 [c00000005ffd7e00] [c00000000012a088] .handle_irq_event_percpu+0x100/0x318 [c00000005ffd7f00] [c00000000012e774] .handle_percpu_irq+0x84/0xd0 [c00000005ffd7f90] [c000000000022ba8] .call_handle_irq+0x1c/0x2c [c00000005ec9ba20] [c00000000001157c] .do_IRQ+0x22c/0x2a8 [c00000005ec9bae0] [c0000000000054bc] hardware_interrupt_entry+0x18/0x1c Exception: 501 at .cpu_idle+0x194/0x2f8 LR = .cpu_idle+0x194/0x2f8 [c00000005ec9bdd0] [c000000000017e58] .cpu_idle+0x188/0x2f8 (unreliable) [c00000005ec9be90] [c00000000067ec18] .start_secondary+0x3e4/0x524 [c00000005ec9bf90] [c0000000000093e8] .start_secondary_prolog+0x10/0x14 Instruction dump: ebe1fff8 4e800020 fbe1fff8 7c0802a6 f8010010 7c7f1b78 f821ff81 78290464 80090014 5400019e 7c0000d0 78000fe0 <0b000000> 4800000c 7c210b78 7c421378 Signed-off-by: Brian King <brking@linux.vnet.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
2012-01-11 10:56:04 +04:00
#include <asm/topology.h>
#include "../../kernel/cacheinfo.h"
static u64 stream_id;
static struct device suspend_dev;
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;
}
/**
* 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());
}
/**
* 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;
reinit_completion(&suspend_work);
return 0;
}
/**
* store_hibernate - Initiate partition hibernation
* @dev: subsys root device
* @attr: device attribute struct
* @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
**/
static ssize_t store_hibernate(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int rc;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
stream_id = simple_strtoul(buf, NULL, 16);
do {
rc = pseries_suspend_begin(PM_SUSPEND_MEM);
if (rc == -EAGAIN)
ssleep(1);
} while (rc == -EAGAIN);
if (!rc)
rc = pm_suspend(PM_SUSPEND_MEM);
stream_id = 0;
if (!rc)
rc = count;
powerpc/rtas: don't online CPUs for partition suspend Partition suspension, used for hibernation and migration, requires that the OS place all but one of the LPAR's processor threads into one of two states prior to calling the ibm,suspend-me RTAS function: * the architected offline state (via RTAS stop-self); or * the H_JOIN hcall, which does not return until the partition resumes execution Using H_CEDE as the offline mode, introduced by commit 3aa565f53c39 ("powerpc/pseries: Add hooks to put the CPU into an appropriate offline state"), means that any threads which are offline from Linux's point of view must be moved to one of those two states before a partition suspension can proceed. This was eventually addressed in commit 120496ac2d2d ("powerpc: Bring all threads online prior to migration/hibernation"), which added code to temporarily bring up any offline processor threads so they can call H_JOIN. Conceptually this is fine, but the implementation has had multiple races with cpu hotplug operations initiated from user space[1][2][3], the error handling is fragile, and it generates user-visible cpu hotplug events which is a lot of noise for a platform feature that's supposed to minimize disruption to workloads. With commit 3aa565f53c39 ("powerpc/pseries: Add hooks to put the CPU into an appropriate offline state") reverted, this code becomes unnecessary, so remove it. Since any offline CPUs now are truly offline from the platform's point of view, it is no longer necessary to bring up CPUs only to have them call H_JOIN and then go offline again upon resuming. Only active threads are required to call H_JOIN; stopped threads can be left alone. [1] commit a6717c01ddc2 ("powerpc/rtas: use device model APIs and serialization during LPM") [2] commit 9fb603050ffd ("powerpc/rtas: retry when cpu offline races with suspend/migration") [3] commit dfd718a2ed1f ("powerpc/rtas: Fix a potential race between CPU-Offline & Migration") Fixes: 120496ac2d2d ("powerpc: Bring all threads online prior to migration/hibernation") Signed-off-by: Nathan Lynch <nathanl@linux.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20200612051238.1007764-3-nathanl@linux.ibm.com
2020-06-12 08:12:22 +03:00
return rc;
}
#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);
}
static DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate);
static struct bus_type suspend_subsys = {
.name = "power",
.dev_name = "power",
};
static const struct platform_suspend_ops pseries_suspend_ops = {
.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
**/
static int pseries_suspend_sysfs_register(struct device *dev)
{
int rc;
if ((rc = subsys_system_register(&suspend_subsys, NULL)))
return rc;
dev->id = 0;
dev->bus = &suspend_subsys;
if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate)))
goto subsys_unregister;
return 0;
subsys_unregister:
bus_unregister(&suspend_subsys);
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;
if (!firmware_has_feature(FW_FEATURE_LPAR))
return 0;
suspend_data.token = rtas_token("ibm,suspend-me");
if (suspend_data.token == RTAS_UNKNOWN_SERVICE)
return 0;
if ((rc = pseries_suspend_sysfs_register(&suspend_dev)))
return rc;
ppc_md.suspend_disable_cpu = pseries_suspend_cpu;
ppc_md.suspend_enable_irqs = pseries_suspend_enable_irqs;
suspend_set_ops(&pseries_suspend_ops);
return 0;
}
machine_device_initcall(pseries, pseries_suspend_init);