Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/powerpc/platforms/pseries/papr-vpd.c
26481 views
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
*/
32
struct 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
*/
60
static 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
*/
127
static 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
*/
159
static 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
*/
171
static 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
188
static 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
*/
211
static 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
*/
242
static 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
258
static const struct file_operations papr_vpd_ops = {
259
.unlocked_ioctl = papr_vpd_dev_ioctl,
260
};
261
262
static struct miscdevice papr_vpd_dev = {
263
.minor = MISC_DYNAMIC_MINOR,
264
.name = "papr-vpd",
265
.fops = &papr_vpd_ops,
266
};
267
268
static __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
}
275
machine_device_initcall(pseries, papr_vpd_init);
276
277