File Index
Files and directories
Sign in
FILE

papr-vpd.c

File preview with syntax highlighting.

linux-6.19.14/arch/powerpc/platforms/pseries/papr-vpd.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2
  3#define pr_fmt(fmt) "papr-vpd: " fmt
  4
  5#include <linux/build_bug.h>
  6#include <linux/file.h>
  7#include <linux/fs.h>
  8#include <linux/init.h>
  9#include <linux/lockdep.h>
 10#include <linux/kernel.h>
 11#include <linux/miscdevice.h>
 12#include <linux/signal.h>
 13#include <linux/slab.h>
 14#include <linux/string.h>
 15#include <linux/string_helpers.h>
 16#include <linux/uaccess.h>
 17#include <asm/machdep.h>
 18#include <asm/papr-vpd.h>
 19#include <asm/rtas-work-area.h>
 20#include <asm/rtas.h>
 21#include <uapi/asm/papr-vpd.h>
 22#include "papr-rtas-common.h"
 23
 24/**
 25 * struct rtas_ibm_get_vpd_params - Parameters (in and out) for ibm,get-vpd.
 26 * @loc_code:  In: Caller-provided location code buffer. Must be RTAS-addressable.
 27 * @work_area: In: Caller-provided work area buffer for results.
 28 * @sequence:  In: Sequence number. Out: Next sequence number.
 29 * @written:   Out: Bytes written by ibm,get-vpd to @work_area.
 30 * @status:    Out: RTAS call status.
 31 */
 32struct rtas_ibm_get_vpd_params {
 33	const struct papr_location_code *loc_code;
 34	struct rtas_work_area *work_area;
 35	u32 sequence;
 36	u32 written;
 37	s32 status;
 38};
 39
 40/**
 41 * rtas_ibm_get_vpd() - Call ibm,get-vpd to fill a work area buffer.
 42 * @params: See &struct rtas_ibm_get_vpd_params.
 43 *
 44 * Calls ibm,get-vpd until it errors or successfully deposits data
 45 * into the supplied work area. Handles RTAS retry statuses. Maps RTAS
 46 * error statuses to reasonable errno values.
 47 *
 48 * The caller is expected to invoke rtas_ibm_get_vpd() multiple times
 49 * to retrieve all the VPD for the provided location code. Only one
 50 * sequence should be in progress at any time; starting a new sequence
 51 * will disrupt any sequence already in progress. Serialization of VPD
 52 * retrieval sequences is the responsibility of the caller.
 53 *
 54 * The caller should inspect @params.status to determine whether more
 55 * calls are needed to complete the sequence.
 56 *
 57 * Context: May sleep.
 58 * Return: -ve on error, 0 otherwise.
 59 */
 60static int rtas_ibm_get_vpd(struct rtas_ibm_get_vpd_params *params)
 61{
 62	const struct papr_location_code *loc_code = params->loc_code;
 63	struct rtas_work_area *work_area = params->work_area;
 64	u32 rets[2];
 65	s32 fwrc;
 66	int ret;
 67
 68	lockdep_assert_held(&rtas_ibm_get_vpd_lock);
 69
 70	do {
 71		fwrc = rtas_call(rtas_function_token(RTAS_FN_IBM_GET_VPD), 4, 3,
 72				 rets,
 73				 __pa(loc_code),
 74				 rtas_work_area_phys(work_area),
 75				 rtas_work_area_size(work_area),
 76				 params->sequence);
 77	} while (rtas_busy_delay(fwrc));
 78
 79	switch (fwrc) {
 80	case RTAS_HARDWARE_ERROR:
 81		ret = -EIO;
 82		break;
 83	case RTAS_INVALID_PARAMETER:
 84		ret = -EINVAL;
 85		break;
 86	case RTAS_SEQ_START_OVER:
 87		ret = -EAGAIN;
 88		pr_info_ratelimited("VPD changed during retrieval, retrying\n");
 89		break;
 90	case RTAS_SEQ_MORE_DATA:
 91		params->sequence = rets[0];
 92		fallthrough;
 93	case RTAS_SEQ_COMPLETE:
 94		params->written = rets[1];
 95		/*
 96		 * Kernel or firmware bug, do not continue.
 97		 */
 98		if (WARN(params->written > rtas_work_area_size(work_area),
 99			 "possible write beyond end of work area"))
100			ret = -EFAULT;
101		else
102			ret = 0;
103		break;
104	default:
105		ret = -EIO;
106		pr_err_ratelimited("unexpected ibm,get-vpd status %d\n", fwrc);
107		break;
108	}
109
110	params->status = fwrc;
111	return ret;
112}
113
114/*
115 * Internal VPD sequence APIs. A VPD sequence is a series of calls to
116 * ibm,get-vpd for a given location code. The sequence ends when an
117 * error is encountered or all VPD for the location code has been
118 * returned.
119 */
120
121/**
122 * vpd_sequence_begin() - Begin a VPD retrieval sequence.
123 * @seq: vpd call parameters from sequence struct
124 *
125 * Context: May sleep.
126 */
127static void vpd_sequence_begin(struct papr_rtas_sequence *seq)
128{
129	struct rtas_ibm_get_vpd_params *vpd_params;
130	/*
131	 * Use a static data structure for the location code passed to
132	 * RTAS to ensure it's in the RMA and avoid a separate work
133	 * area allocation. Guarded by the function lock.
134	 */
135	static struct papr_location_code static_loc_code;
136
137	vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
138	/*
139	 * We could allocate the work area before acquiring the
140	 * function lock, but that would allow concurrent requests to
141	 * exhaust the limited work area pool for no benefit. So
142	 * allocate the work area under the lock.
143	 */
144	mutex_lock(&rtas_ibm_get_vpd_lock);
145	static_loc_code = *(struct papr_location_code *)vpd_params->loc_code;
146	vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
147	vpd_params->work_area = rtas_work_area_alloc(SZ_4K);
148	vpd_params->loc_code = &static_loc_code;
149	vpd_params->sequence = 1;
150	vpd_params->status = 0;
151}
152
153/**
154 * vpd_sequence_end() - Finalize a VPD retrieval sequence.
155 * @seq: Sequence state.
156 *
157 * Releases resources obtained by vpd_sequence_begin().
158 */
159static void vpd_sequence_end(struct papr_rtas_sequence *seq)
160{
161	struct rtas_ibm_get_vpd_params *vpd_params;
162
163	vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
164	rtas_work_area_free(vpd_params->work_area);
165	mutex_unlock(&rtas_ibm_get_vpd_lock);
166}
167
168/*
169 * Generator function to be passed to papr_rtas_blob_generate().
170 */
171static const char *vpd_sequence_fill_work_area(struct papr_rtas_sequence *seq,
172						size_t *len)
173{
174	struct rtas_ibm_get_vpd_params *p;
175	bool init_state;
176
177	p = (struct rtas_ibm_get_vpd_params *)seq->params;
178	init_state = (p->written == 0) ? true : false;
179
180	if (papr_rtas_sequence_should_stop(seq, p->status, init_state))
181		return NULL;
182	if (papr_rtas_sequence_set_err(seq, rtas_ibm_get_vpd(p)))
183		return NULL;
184	*len = p->written;
185	return rtas_work_area_raw_buf(p->work_area);
186}
187
188static const struct file_operations papr_vpd_handle_ops = {
189	.read = papr_rtas_common_handle_read,
190	.llseek = papr_rtas_common_handle_seek,
191	.release = papr_rtas_common_handle_release,
192};
193
194/**
195 * papr_vpd_create_handle() - Create a fd-based handle for reading VPD.
196 * @ulc: Location code in user memory; defines the scope of the VPD to
197 *       retrieve.
198 *
199 * Handler for PAPR_VPD_IOC_CREATE_HANDLE ioctl command. Validates
200 * @ulc and instantiates an immutable VPD "blob" for it. The blob is
201 * attached to a file descriptor for reading by user space. The memory
202 * backing the blob is freed when the file is released.
203 *
204 * The entire requested VPD is retrieved by this call and all
205 * necessary RTAS interactions are performed before returning the fd
206 * to user space. This keeps the read handler simple and ensures that
207 * the kernel can prevent interleaving of ibm,get-vpd call sequences.
208 *
209 * Return: The installed fd number if successful, -ve errno otherwise.
210 */
211static long papr_vpd_create_handle(struct papr_location_code __user *ulc)
212{
213	struct rtas_ibm_get_vpd_params vpd_params = {};
214	struct papr_rtas_sequence seq = {};
215	struct papr_location_code klc;
216	int fd;
217
218	if (copy_from_user(&klc, ulc, sizeof(klc)))
219		return -EFAULT;
220
221	if (!string_is_terminated(klc.str, ARRAY_SIZE(klc.str)))
222		return -EINVAL;
223
224	seq = (struct papr_rtas_sequence) {
225		.begin = vpd_sequence_begin,
226		.end = vpd_sequence_end,
227		.work = vpd_sequence_fill_work_area,
228	};
229
230	vpd_params.loc_code = &klc;
231	seq.params = (void *)&vpd_params;
232
233	fd = papr_rtas_setup_file_interface(&seq, &papr_vpd_handle_ops,
234			"[papr-vpd]");
235
236	return fd;
237}
238
239/*
240 * Top-level ioctl handler for /dev/papr-vpd.
241 */
242static long papr_vpd_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
243{
244	void __user *argp = (__force void __user *)arg;
245	long ret;
246
247	switch (ioctl) {
248	case PAPR_VPD_IOC_CREATE_HANDLE:
249		ret = papr_vpd_create_handle(argp);
250		break;
251	default:
252		ret = -ENOIOCTLCMD;
253		break;
254	}
255	return ret;
256}
257
258static const struct file_operations papr_vpd_ops = {
259	.unlocked_ioctl = papr_vpd_dev_ioctl,
260};
261
262static struct miscdevice papr_vpd_dev = {
263	.minor = MISC_DYNAMIC_MINOR,
264	.name = "papr-vpd",
265	.fops = &papr_vpd_ops,
266};
267
268static __init int papr_vpd_init(void)
269{
270	if (!rtas_function_implemented(RTAS_FN_IBM_GET_VPD))
271		return -ENODEV;
272
273	return misc_register(&papr_vpd_dev);
274}
275machine_device_initcall(pseries, papr_vpd_init);
~/ Read-only file preview.