1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #define pr_fmt(fmt)	"papr-sysparm: " fmt
4 
5 #include <linux/bug.h>
6 #include <linux/init.h>
7 #include <linux/kernel.h>
8 #include <linux/printk.h>
9 #include <linux/slab.h>
10 #include <asm/rtas.h>
11 #include <asm/papr-sysparm.h>
12 #include <asm/rtas-work-area.h>
13 
papr_sysparm_buf_alloc(void)14 struct papr_sysparm_buf *papr_sysparm_buf_alloc(void)
15 {
16 	struct papr_sysparm_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL);
17 
18 	return buf;
19 }
20 
papr_sysparm_buf_free(struct papr_sysparm_buf * buf)21 void papr_sysparm_buf_free(struct papr_sysparm_buf *buf)
22 {
23 	kfree(buf);
24 }
25 
papr_sysparm_buf_get_length(const struct papr_sysparm_buf * buf)26 static size_t papr_sysparm_buf_get_length(const struct papr_sysparm_buf *buf)
27 {
28 	return be16_to_cpu(buf->len);
29 }
30 
papr_sysparm_buf_set_length(struct papr_sysparm_buf * buf,size_t length)31 static void papr_sysparm_buf_set_length(struct papr_sysparm_buf *buf, size_t length)
32 {
33 	WARN_ONCE(length > sizeof(buf->val),
34 		  "bogus length %zu, clamping to safe value", length);
35 	length = min(sizeof(buf->val), length);
36 	buf->len = cpu_to_be16(length);
37 }
38 
39 /*
40  * For use on buffers returned from ibm,get-system-parameter before
41  * returning them to callers. Ensures the encoded length of valid data
42  * cannot overrun buf->val[].
43  */
papr_sysparm_buf_clamp_length(struct papr_sysparm_buf * buf)44 static void papr_sysparm_buf_clamp_length(struct papr_sysparm_buf *buf)
45 {
46 	papr_sysparm_buf_set_length(buf, papr_sysparm_buf_get_length(buf));
47 }
48 
49 /*
50  * Perform some basic diligence on the system parameter buffer before
51  * submitting it to RTAS.
52  */
papr_sysparm_buf_can_submit(const struct papr_sysparm_buf * buf)53 static bool papr_sysparm_buf_can_submit(const struct papr_sysparm_buf *buf)
54 {
55 	/*
56 	 * Firmware ought to reject buffer lengths that exceed the
57 	 * maximum specified in PAPR, but there's no reason for the
58 	 * kernel to allow them either.
59 	 */
60 	if (papr_sysparm_buf_get_length(buf) > sizeof(buf->val))
61 		return false;
62 
63 	return true;
64 }
65 
66 /**
67  * papr_sysparm_get() - Retrieve the value of a PAPR system parameter.
68  * @param: PAPR system parameter token as described in
69  *         7.3.16 "System Parameters Option".
70  * @buf: A &struct papr_sysparm_buf as returned from papr_sysparm_buf_alloc().
71  *
72  * Place the result of querying the specified parameter, if available,
73  * in @buf. The result includes a be16 length header followed by the
74  * value, which may be a string or binary data. See &struct papr_sysparm_buf.
75  *
76  * Since there is at least one parameter (60, OS Service Entitlement
77  * Status) where the results depend on the incoming contents of the
78  * work area, the caller-supplied buffer is copied unmodified into the
79  * work area before calling ibm,get-system-parameter.
80  *
81  * A defined parameter may not be implemented on a given system, and
82  * some implemented parameters may not be available to all partitions
83  * on a system. A parameter's disposition may change at any time due
84  * to system configuration changes or partition migration.
85  *
86  * Context: This function may sleep.
87  *
88  * Return: 0 on success, -errno otherwise. @buf is unmodified on error.
89  */
90 
papr_sysparm_get(papr_sysparm_t param,struct papr_sysparm_buf * buf)91 int papr_sysparm_get(papr_sysparm_t param, struct papr_sysparm_buf *buf)
92 {
93 	const s32 token = rtas_function_token(RTAS_FN_IBM_GET_SYSTEM_PARAMETER);
94 	struct rtas_work_area *work_area;
95 	s32 fwrc;
96 	int ret;
97 
98 	might_sleep();
99 
100 	if (WARN_ON(!buf))
101 		return -EFAULT;
102 
103 	if (token == RTAS_UNKNOWN_SERVICE)
104 		return -ENOENT;
105 
106 	if (!papr_sysparm_buf_can_submit(buf))
107 		return -EINVAL;
108 
109 	work_area = rtas_work_area_alloc(sizeof(*buf));
110 
111 	memcpy(rtas_work_area_raw_buf(work_area), buf, sizeof(*buf));
112 
113 	do {
114 		fwrc = rtas_call(token, 3, 1, NULL, param.token,
115 				 rtas_work_area_phys(work_area),
116 				 rtas_work_area_size(work_area));
117 	} while (rtas_busy_delay(fwrc));
118 
119 	switch (fwrc) {
120 	case 0:
121 		ret = 0;
122 		memcpy(buf, rtas_work_area_raw_buf(work_area), sizeof(*buf));
123 		papr_sysparm_buf_clamp_length(buf);
124 		break;
125 	case -3: /* parameter not implemented */
126 		ret = -EOPNOTSUPP;
127 		break;
128 	case -9002: /* this partition not authorized to retrieve this parameter */
129 		ret = -EPERM;
130 		break;
131 	case -9999: /* "parameter error" e.g. the buffer is too small */
132 		ret = -EINVAL;
133 		break;
134 	default:
135 		pr_err("unexpected ibm,get-system-parameter result %d\n", fwrc);
136 		fallthrough;
137 	case -1: /* Hardware/platform error */
138 		ret = -EIO;
139 		break;
140 	}
141 
142 	rtas_work_area_free(work_area);
143 
144 	return ret;
145 }
146 
papr_sysparm_set(papr_sysparm_t param,const struct papr_sysparm_buf * buf)147 int papr_sysparm_set(papr_sysparm_t param, const struct papr_sysparm_buf *buf)
148 {
149 	const s32 token = rtas_function_token(RTAS_FN_IBM_SET_SYSTEM_PARAMETER);
150 	struct rtas_work_area *work_area;
151 	s32 fwrc;
152 	int ret;
153 
154 	might_sleep();
155 
156 	if (WARN_ON(!buf))
157 		return -EFAULT;
158 
159 	if (token == RTAS_UNKNOWN_SERVICE)
160 		return -ENOENT;
161 
162 	if (!papr_sysparm_buf_can_submit(buf))
163 		return -EINVAL;
164 
165 	work_area = rtas_work_area_alloc(sizeof(*buf));
166 
167 	memcpy(rtas_work_area_raw_buf(work_area), buf, sizeof(*buf));
168 
169 	do {
170 		fwrc = rtas_call(token, 2, 1, NULL, param.token,
171 				 rtas_work_area_phys(work_area));
172 	} while (rtas_busy_delay(fwrc));
173 
174 	switch (fwrc) {
175 	case 0:
176 		ret = 0;
177 		break;
178 	case -3: /* parameter not supported */
179 		ret = -EOPNOTSUPP;
180 		break;
181 	case -9002: /* this partition not authorized to modify this parameter */
182 		ret = -EPERM;
183 		break;
184 	case -9999: /* "parameter error" e.g. invalid input data */
185 		ret = -EINVAL;
186 		break;
187 	default:
188 		pr_err("unexpected ibm,set-system-parameter result %d\n", fwrc);
189 		fallthrough;
190 	case -1: /* Hardware/platform error */
191 		ret = -EIO;
192 		break;
193 	}
194 
195 	rtas_work_area_free(work_area);
196 
197 	return ret;
198 }
199