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 NV_ENCODE_NATIVE); 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