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 }