1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3  * Copyright (C) 2010 Brian King IBM Corporation
4  */
5
6#include <linux/cpu.h>
7#include <linux/delay.h>
8#include <linux/suspend.h>
9#include <linux/stat.h>
10#include <asm/firmware.h>
11#include <asm/hvcall.h>
12#include <asm/machdep.h>
13#include <asm/mmu.h>
14#include <asm/rtas.h>
15#include <asm/topology.h>
16
17static struct device suspend_dev;
18
19/**
20 * pseries_suspend_begin - First phase of hibernation
21 *
22 * Check to ensure we are in a valid state to hibernate
23 *
24 * Return value:
25 * 	0 on success / other on failure
26 **/
27static int pseries_suspend_begin(u64 stream_id)
28{
29	long vasi_state, rc;
30	unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
31
32	/* Make sure the state is valid */
33	rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id);
34
35	vasi_state = retbuf[0];
36
37	if (rc) {
38		pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc);
39		return rc;
40	} else if (vasi_state == H_VASI_ENABLED) {
41		return -EAGAIN;
42	} else if (vasi_state != H_VASI_SUSPENDING) {
43		pr_err("pseries_suspend_begin: vasi_state returned state %ld\n",
44		       vasi_state);
45		return -EIO;
46	}
47	return 0;
48}
49
50/**
51 * pseries_suspend_enter - Final phase of hibernation
52 *
53 * Return value:
54 * 	0 on success / other on failure
55 **/
56static int pseries_suspend_enter(suspend_state_t state)
57{
58	return rtas_ibm_suspend_me(NULL);
59}
60
61/**
62 * store_hibernate - Initiate partition hibernation
63 * @dev:		subsys root device
64 * @attr:		device attribute struct
65 * @buf:		buffer
66 * @count:		buffer size
67 *
68 * Write the stream ID received from the HMC to this file
69 * to trigger hibernating the partition
70 *
71 * Return value:
72 * 	number of bytes printed to buffer / other on failure
73 **/
74static ssize_t store_hibernate(struct device *dev,
75			       struct device_attribute *attr,
76			       const char *buf, size_t count)
77{
78	u64 stream_id;
79	int rc;
80
81	if (!capable(CAP_SYS_ADMIN))
82		return -EPERM;
83
84	stream_id = simple_strtoul(buf, NULL, 16);
85
86	do {
87		rc = pseries_suspend_begin(stream_id);
88		if (rc == -EAGAIN)
89			ssleep(1);
90	} while (rc == -EAGAIN);
91
92	if (!rc)
93		rc = pm_suspend(PM_SUSPEND_MEM);
94
95	if (!rc) {
96		rc = count;
97		post_mobility_fixup();
98	}
99
100
101	return rc;
102}
103
104#define USER_DT_UPDATE	0
105#define KERN_DT_UPDATE	1
106
107/**
108 * show_hibernate - Report device tree update responsibilty
109 * @dev:		subsys root device
110 * @attr:		device attribute struct
111 * @buf:		buffer
112 *
113 * Report whether a device tree update is performed by the kernel after a
114 * resume, or if drmgr must coordinate the update from user space.
115 *
116 * Return value:
117 *	0 if drmgr is to initiate update, and 1 otherwise
118 **/
119static ssize_t show_hibernate(struct device *dev,
120			      struct device_attribute *attr,
121			      char *buf)
122{
123	return sprintf(buf, "%d\n", KERN_DT_UPDATE);
124}
125
126static DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate);
127
128static struct bus_type suspend_subsys = {
129	.name = "power",
130	.dev_name = "power",
131};
132
133static const struct platform_suspend_ops pseries_suspend_ops = {
134	.valid		= suspend_valid_only_mem,
135	.enter		= pseries_suspend_enter,
136};
137
138/**
139 * pseries_suspend_sysfs_register - Register with sysfs
140 *
141 * Return value:
142 * 	0 on success / other on failure
143 **/
144static int pseries_suspend_sysfs_register(struct device *dev)
145{
146	struct device *dev_root;
147	int rc;
148
149	if ((rc = subsys_system_register(&suspend_subsys, NULL)))
150		return rc;
151
152	dev->id = 0;
153	dev->bus = &suspend_subsys;
154
155	dev_root = bus_get_dev_root(&suspend_subsys);
156	if (dev_root) {
157		rc = device_create_file(dev_root, &dev_attr_hibernate);
158		put_device(dev_root);
159		if (rc)
160			goto subsys_unregister;
161	}
162
163	return 0;
164
165subsys_unregister:
166	bus_unregister(&suspend_subsys);
167	return rc;
168}
169
170/**
171 * pseries_suspend_init - initcall for pSeries suspend
172 *
173 * Return value:
174 * 	0 on success / other on failure
175 **/
176static int __init pseries_suspend_init(void)
177{
178	int rc;
179
180	if (!firmware_has_feature(FW_FEATURE_LPAR))
181		return 0;
182
183	if ((rc = pseries_suspend_sysfs_register(&suspend_dev)))
184		return rc;
185
186	suspend_set_ops(&pseries_suspend_ops);
187	return 0;
188}
189machine_device_initcall(pseries, pseries_suspend_init);
190