1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2019 Joyent, Inc.
14 */
15
16 /*
17 * This is a test driver used for exercising the DDI UFM subsystem.
18 *
19 * Most of the test cases depend on the ufmtest driver being loaded.
20 * On SmartOS, this driver will need to be manually installed, as it is not
21 * part of the platform image.
22 */
23 #include <sys/ddi.h>
24 #include <sys/sunddi.h>
25 #include <sys/esunddi.h>
26 #include <sys/ddi_ufm.h>
27 #include <sys/conf.h>
28 #include <sys/debug.h>
29 #include <sys/file.h>
30 #include <sys/kmem.h>
31 #include <sys/stat.h>
32 #include <sys/zone.h>
33
34 #include "ufmtest.h"
35
36 typedef struct ufmtest {
37 dev_info_t *ufmt_devi;
38 nvlist_t *ufmt_nvl;
39 ddi_ufm_handle_t *ufmt_ufmh;
40 uint32_t ufmt_failflags;
41 } ufmtest_t;
42
43 static ufmtest_t ufmt = { 0 };
44
45 static int ufmtest_open(dev_t *, int, int, cred_t *);
46 static int ufmtest_close(dev_t, int, int, cred_t *);
47 static int ufmtest_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
48
49 static struct cb_ops ufmtest_cb_ops = {
50 .cb_open = ufmtest_open,
51 .cb_close = ufmtest_close,
52 .cb_strategy = nodev,
53 .cb_print = nodev,
54 .cb_dump = nodev,
55 .cb_read = nodev,
56 .cb_write = nodev,
57 .cb_ioctl = ufmtest_ioctl,
58 .cb_devmap = nodev,
59 .cb_mmap = nodev,
60 .cb_segmap = nodev,
61 .cb_chpoll = nochpoll,
62 .cb_prop_op = ddi_prop_op,
63 .cb_str = NULL,
64 .cb_flag = D_NEW | D_MP,
65 .cb_rev = CB_REV,
66 .cb_aread = nodev,
67 .cb_awrite = nodev
68 };
69
70 static int ufmtest_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
71 static int ufmtest_attach(dev_info_t *, ddi_attach_cmd_t);
72 static int ufmtest_detach(dev_info_t *, ddi_detach_cmd_t);
73
74 static struct dev_ops ufmtest_ops = {
75 .devo_rev = DEVO_REV,
76 .devo_refcnt = 0,
77 .devo_getinfo = ufmtest_info,
78 .devo_identify = nulldev,
79 .devo_probe = nulldev,
80 .devo_attach = ufmtest_attach,
81 .devo_detach = ufmtest_detach,
82 .devo_reset = nodev,
83 .devo_cb_ops = &ufmtest_cb_ops,
84 .devo_bus_ops = NULL,
85 .devo_power = NULL,
86 .devo_quiesce = ddi_quiesce_not_needed
87 };
88
89 static struct modldrv modldrv = {
90 .drv_modops = &mod_driverops,
91 .drv_linkinfo = "DDI UFM test driver",
92 .drv_dev_ops = &ufmtest_ops
93 };
94
95 static struct modlinkage modlinkage = {
96 .ml_rev = MODREV_1,
97 .ml_linkage = { (void *)&modldrv, NULL }
98 };
99
100 static int ufmtest_nimages(ddi_ufm_handle_t *, void *, uint_t *);
101 static int ufmtest_fill_image(ddi_ufm_handle_t *, void *, uint_t,
102 ddi_ufm_image_t *);
103 static int ufmtest_fill_slot(ddi_ufm_handle_t *, void *, uint_t, uint_t,
104 ddi_ufm_slot_t *);
105 static int ufmtest_getcaps(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *);
106
107 static ddi_ufm_ops_t ufmtest_ufm_ops = {
108 ufmtest_nimages,
109 ufmtest_fill_image,
110 ufmtest_fill_slot,
111 ufmtest_getcaps
112 };
113
114
115 int
_init(void)116 _init(void)
117 {
118 return (mod_install(&modlinkage));
119 }
120
121 int
_fini(void)122 _fini(void)
123 {
124 return (mod_remove(&modlinkage));
125 }
126
127 int
_info(struct modinfo * modinfop)128 _info(struct modinfo *modinfop)
129 {
130 return (mod_info(&modlinkage, modinfop));
131 }
132
133 static int
ufmtest_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)134 ufmtest_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
135 {
136 switch (infocmd) {
137 case DDI_INFO_DEVT2DEVINFO:
138 *result = ufmt.ufmt_devi;
139 return (DDI_SUCCESS);
140 case DDI_INFO_DEVT2INSTANCE:
141 *result = (void *)(uintptr_t)ddi_get_instance(dip);
142 return (DDI_SUCCESS);
143 }
144 return (DDI_FAILURE);
145 }
146
147 static int
ufmtest_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)148 ufmtest_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
149 {
150 if (cmd != DDI_ATTACH || ufmt.ufmt_devi != NULL)
151 return (DDI_FAILURE);
152
153 if (ddi_create_minor_node(devi, "ufmtest", S_IFCHR, 0, DDI_PSEUDO,
154 0) == DDI_FAILURE) {
155 ddi_remove_minor_node(devi, NULL);
156 return (DDI_FAILURE);
157 }
158
159 ufmt.ufmt_devi = devi;
160
161 if (ddi_ufm_init(ufmt.ufmt_devi, DDI_UFM_CURRENT_VERSION,
162 &ufmtest_ufm_ops, &ufmt.ufmt_ufmh, NULL) != 0) {
163 dev_err(ufmt.ufmt_devi, CE_WARN, "failed to initialize UFM "
164 "subsystem");
165 return (DDI_FAILURE);
166 }
167
168 return (DDI_SUCCESS);
169 }
170
171 static int
ufmtest_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)172 ufmtest_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
173 {
174 if (cmd != DDI_DETACH)
175 return (DDI_FAILURE);
176
177 if (devi != NULL)
178 ddi_remove_minor_node(devi, NULL);
179
180 ddi_ufm_fini(ufmt.ufmt_ufmh);
181 if (ufmt.ufmt_nvl != NULL) {
182 nvlist_free(ufmt.ufmt_nvl);
183 ufmt.ufmt_nvl = NULL;
184 }
185
186 return (DDI_SUCCESS);
187 }
188
189 static int
ufmtest_open(dev_t * devp,int flag,int otyp,cred_t * credp)190 ufmtest_open(dev_t *devp, int flag, int otyp, cred_t *credp)
191 {
192 const int inv_flags = FWRITE | FEXCL | FNDELAY | FNONBLOCK;
193
194 if (otyp != OTYP_CHR)
195 return (EINVAL);
196
197 if (flag & inv_flags)
198 return (EINVAL);
199
200 if (drv_priv(credp) != 0)
201 return (EPERM);
202
203 if (getzoneid() != GLOBAL_ZONEID)
204 return (EPERM);
205
206 return (0);
207 }
208
209 static int
ufmtest_close(dev_t dev,int flag,int otyp,cred_t * credp)210 ufmtest_close(dev_t dev, int flag, int otyp, cred_t *credp)
211 {
212 return (0);
213 }
214
215 /*
216 * By default, this pseudo test driver contains no hardcoded UFM data to
217 * report. This ioctl takes a packed nvlist, representing a UFM report.
218 * This data is then used as a source for firmware information by this
219 * driver when it's UFM callback are called.
220 *
221 * External test programs can use this ioctl to effectively seed this
222 * driver with arbitrary firmware information which it will report up to the
223 * DDI UFM subsystem.
224 */
225 static int
ufmtest_do_setfw(intptr_t data,int mode)226 ufmtest_do_setfw(intptr_t data, int mode)
227 {
228 int ret;
229 uint_t model;
230 ufmtest_ioc_setfw_t setfw;
231 char *nvlbuf = NULL;
232 #ifdef _MULTI_DATAMODEL
233 ufmtest_ioc_setfw32_t setfw32;
234 #endif
235 model = ddi_model_convert_from(mode);
236
237 switch (model) {
238 #ifdef _MULTI_DATAMODEL
239 case DDI_MODEL_ILP32:
240 if (ddi_copyin((void *)data, &setfw32,
241 sizeof (ufmtest_ioc_setfw32_t), mode) != 0)
242 return (EFAULT);
243 setfw.utsw_bufsz = setfw32.utsw_bufsz;
244 setfw.utsw_buf = (caddr_t)(uintptr_t)setfw32.utsw_buf;
245 break;
246 #endif /* _MULTI_DATAMODEL */
247 case DDI_MODEL_NONE:
248 default:
249 if (ddi_copyin((void *)data, &setfw,
250 sizeof (ufmtest_ioc_setfw_t), mode) != 0)
251 return (EFAULT);
252 }
253
254 if (ufmt.ufmt_nvl != NULL) {
255 nvlist_free(ufmt.ufmt_nvl);
256 ufmt.ufmt_nvl = NULL;
257 }
258
259 nvlbuf = kmem_zalloc(setfw.utsw_bufsz, KM_NOSLEEP_LAZY);
260 if (nvlbuf == NULL)
261 return (ENOMEM);
262
263 if (ddi_copyin(setfw.utsw_buf, nvlbuf, setfw.utsw_bufsz, mode) != 0) {
264 kmem_free(nvlbuf, setfw.utsw_bufsz);
265 return (EFAULT);
266 }
267
268 ret = nvlist_unpack(nvlbuf, setfw.utsw_bufsz, &ufmt.ufmt_nvl,
269 KM_NOSLEEP);
270 kmem_free(nvlbuf, setfw.utsw_bufsz);
271
272 if (ret != 0)
273 return (ret);
274
275 /*
276 * Notify the UFM subsystem that our firmware information has changed.
277 */
278 ddi_ufm_update(ufmt.ufmt_ufmh);
279
280 return (0);
281 }
282
283 static int
ufmtest_do_toggle_fails(intptr_t data,int mode)284 ufmtest_do_toggle_fails(intptr_t data, int mode)
285 {
286 ufmtest_ioc_fails_t fails;
287
288 if (ddi_copyin((void *)data, &fails, sizeof (ufmtest_ioc_fails_t),
289 mode) != 0)
290 return (EFAULT);
291
292 if (fails.utfa_flags > UFMTEST_MAX_FAILFLAGS)
293 return (EINVAL);
294
295 ufmt.ufmt_failflags = fails.utfa_flags;
296
297 return (0);
298 }
299
300 /* ARGSUSED */
301 static int
ufmtest_ioctl(dev_t dev,int cmd,intptr_t data,int mode,cred_t * credp,int * rvalp)302 ufmtest_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
303 int *rvalp)
304 {
305 int ret = 0;
306
307 if (drv_priv(credp) != 0)
308 return (EPERM);
309
310 switch (cmd) {
311 case UFMTEST_IOC_SET_FW:
312 ret = ufmtest_do_setfw(data, mode);
313 break;
314 case UFMTEST_IOC_TOGGLE_FAILS:
315 ret = ufmtest_do_toggle_fails(data, mode);
316 break;
317 case UFMTEST_IOC_DO_UPDATE:
318 ddi_ufm_update(ufmt.ufmt_ufmh);
319 break;
320 default:
321 return (ENOTTY);
322 }
323 return (ret);
324 }
325
326 static int
ufmtest_nimages(ddi_ufm_handle_t * ufmh,void * arg,uint_t * nimgs)327 ufmtest_nimages(ddi_ufm_handle_t *ufmh, void *arg, uint_t *nimgs)
328 {
329 nvlist_t **imgs;
330 uint_t ni;
331
332 if (ufmt.ufmt_failflags & UFMTEST_FAIL_NIMAGES ||
333 ufmt.ufmt_nvl == NULL)
334 return (EINVAL);
335
336 if (nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES, &imgs,
337 &ni) != 0)
338 return (EINVAL);
339
340 *nimgs = ni;
341 return (0);
342 }
343
344 static int
ufmtest_fill_image(ddi_ufm_handle_t * ufmh,void * arg,uint_t imgno,ddi_ufm_image_t * img)345 ufmtest_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
346 ddi_ufm_image_t *img)
347 {
348 nvlist_t **images, *misc, *miscdup = NULL, **slots;
349 char *desc;
350 uint_t ni, ns;
351
352 if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLIMAGE ||
353 ufmt.ufmt_nvl == NULL ||
354 nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES,
355 &images, &ni) != 0)
356 goto err;
357
358 if (imgno >= ni)
359 goto err;
360
361 if (nvlist_lookup_string(images[imgno], DDI_UFM_NV_IMAGE_DESC,
362 &desc) != 0 ||
363 nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS,
364 &slots, &ns) != 0)
365 goto err;
366
367 ddi_ufm_image_set_desc(img, desc);
368 ddi_ufm_image_set_nslots(img, ns);
369
370 if (nvlist_lookup_nvlist(images[imgno], DDI_UFM_NV_IMAGE_MISC, &misc)
371 == 0) {
372 if (nvlist_dup(misc, &miscdup, 0) != 0)
373 return (ENOMEM);
374
375 ddi_ufm_image_set_misc(img, miscdup);
376 }
377 return (0);
378 err:
379 return (EINVAL);
380 }
381
382 static int
ufmtest_fill_slot(ddi_ufm_handle_t * ufmh,void * arg,uint_t imgno,uint_t slotno,ddi_ufm_slot_t * slot)383 ufmtest_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
384 uint_t slotno, ddi_ufm_slot_t *slot)
385 {
386 nvlist_t **images, *misc, *miscdup = NULL, **slots;
387 char *vers;
388 uint32_t attrs;
389 uint_t ni, ns;
390
391 if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLSLOT ||
392 ufmt.ufmt_nvl == NULL ||
393 nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES,
394 &images, &ni) != 0)
395 goto err;
396
397 if (imgno >= ni)
398 goto err;
399
400 if (nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS,
401 &slots, &ns) != 0)
402 goto err;
403
404 if (slotno >= ns)
405 goto err;
406
407 if (nvlist_lookup_uint32(slots[slotno], DDI_UFM_NV_SLOT_ATTR,
408 &attrs) != 0)
409 goto err;
410
411 ddi_ufm_slot_set_attrs(slot, attrs);
412 if (attrs & DDI_UFM_ATTR_EMPTY)
413 return (0);
414
415 if (nvlist_lookup_string(slots[slotno], DDI_UFM_NV_SLOT_VERSION,
416 &vers) != 0)
417 goto err;
418
419 ddi_ufm_slot_set_version(slot, vers);
420
421 if (nvlist_lookup_nvlist(slots[slotno], DDI_UFM_NV_SLOT_MISC, &misc) ==
422 0) {
423 if (nvlist_dup(misc, &miscdup, 0) != 0)
424 return (ENOMEM);
425
426 ddi_ufm_slot_set_misc(slot, miscdup);
427 }
428 return (0);
429 err:
430 return (EINVAL);
431 }
432
433 static int
ufmtest_getcaps(ddi_ufm_handle_t * ufmh,void * arg,ddi_ufm_cap_t * caps)434 ufmtest_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps)
435 {
436 if (ufmt.ufmt_failflags & UFMTEST_FAIL_GETCAPS)
437 return (EINVAL);
438
439 *caps = DDI_UFM_CAP_REPORT;
440
441 return (0);
442 }
443