xref: /illumos-gate/usr/src/uts/common/io/ufmtest.c (revision 6f0e4dc91b854250fff5c24de2d27aed3375ac69)
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
116 _init(void)
117 {
118 	return (mod_install(&modlinkage));
119 }
120 
121 int
122 _fini(void)
123 {
124 	return (mod_remove(&modlinkage));
125 }
126 
127 int
128 _info(struct modinfo *modinfop)
129 {
130 	return (mod_info(&modlinkage, modinfop));
131 }
132 
133 static int
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
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
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
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
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
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 | KM_NORMALPRI);
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
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
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
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
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
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
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