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  * Copyright 2020 Oxide Computer Company
  15  */
  16 
  17 #include <sys/avl.h>
  18 #include <sys/ddi_ufm.h>
  19 #include <sys/ddi_ufm_impl.h>
  20 #include <sys/debug.h>
  21 #include <sys/kmem.h>
  22 #include <sys/sunddi.h>
  23 #include <sys/stddef.h>
  24 #include <sys/sunndi.h>
  25 #include <sys/file.h>
  26 #include <sys/sysmacros.h>
  27 
  28 /*
  29  * The UFM subsystem tracks its internal state with respect to device
  30  * drivers that participate in the DDI UFM subsystem on a per-instance basis
  31  * via ddi_ufm_handle_t structures (see ddi_ufm_impl.h).  This is known as the
  32  * UFM handle.  The UFM handle contains a pointer to the driver's UFM ops,
  33  * which the ufm(7D) pseudo driver uses to invoke the UFM entry points in
  34  * response to DDI UFM ioctls.  Additionally, the DDI UFM subsystem uses the
  35  * handle to maintain cached UFM image and slot data.
  36  *
  37  * In order to track and provide fast lookups of a driver instance's UFM
  38  * handle, the DDI UFM subsystem stores a pointer to the handle in a global AVL
  39  * tree. UFM handles are added to the tree when a driver calls ddi_ufm_init(9E)
  40  * and removed from the tree when a driver calls ddi_ufm_fini(9E).
  41  *
  42  * Some notes on the locking strategy/rules.
  43  *
  44  * All access to the tree is serialized via the mutex, ufm_lock.
  45  * Additionally, each UFM handle is protected by a per-handle mutex.
  46  *
  47  * Code must acquire ufm_lock in order to walk the tree.  Before reading or
  48  * modifying the state of any UFM handle, code must then acquire the
  49  * UFM handle lock.  Once the UFM handle lock has been acquired, ufm_lock
  50  * should be dropped.
  51  *
  52  * Only one UFM handle lock should be held at any time.
  53  * If a UFM handle lock is held, it must be released before attempting to
  54  * re-acquire ufm_lock.
  55  *
  56  * For example, the lock sequence for calling a UFM entry point and/or
  57  * reading/modifying UFM handle state would be as follows:
  58  * - acquire ufm_lock
  59  * - walk tree to find UFH handle
  60  * - acquire UFM handle lock
  61  * - release ufm_lock
  62  * - call entry point and/or access handle state
  63  *
  64  * Testing
  65  * -------
  66  * A set of automated tests for the DDI UFM subsystem exists at:
  67  * usr/src/test/os-tests/tests/ddi_ufm/
  68  *
  69  * These tests should be run whenever changes are made to the DDI UFM
  70  * subsystem or the ufm driver.
  71  */
  72 
  73 /*
  74  * Amount of data to read in one go (1 MiB).
  75  */
  76 #define UFM_READ_STRIDE (1024 * 1024)
  77 
  78 static avl_tree_t ufm_handles;
  79 static kmutex_t ufm_lock;
  80 
  81 static int ufm_handle_compare(const void *, const void *);
  82 
  83 static void
  84 ufm_cache_invalidate(ddi_ufm_handle_t *ufmh)
  85 {
  86         ASSERT(MUTEX_HELD(&ufmh->ufmh_lock));
  87 
  88         if (ufmh->ufmh_images == NULL)
  89                 return;
  90 
  91         for (uint_t i = 0; i < ufmh->ufmh_nimages; i++) {
  92                 struct ddi_ufm_image *img = &ufmh->ufmh_images[i];
  93 
  94                 if (img->ufmi_slots == NULL)
  95                         continue;
  96 
  97                 for (uint_t s = 0; s < img->ufmi_nslots; s++) {
  98                         struct ddi_ufm_slot *slot = &img->ufmi_slots[s];
  99 
 100                         if (slot->ufms_version != NULL)
 101                                 strfree(slot->ufms_version);
 102                         nvlist_free(slot->ufms_misc);
 103                 }
 104                 kmem_free(img->ufmi_slots,
 105                     (img->ufmi_nslots * sizeof (ddi_ufm_slot_t)));
 106                 if (img->ufmi_desc != NULL)
 107                         strfree(img->ufmi_desc);
 108                 nvlist_free(img->ufmi_misc);
 109         }
 110 
 111         kmem_free(ufmh->ufmh_images,
 112             (ufmh->ufmh_nimages * sizeof (ddi_ufm_image_t)));
 113         ufmh->ufmh_images = NULL;
 114         ufmh->ufmh_nimages = 0;
 115         ufmh->ufmh_caps = 0;
 116         nvlist_free(ufmh->ufmh_report);
 117         ufmh->ufmh_report = NULL;
 118 }
 119 
 120 static void
 121 free_nvlist_array(nvlist_t **nvlarr, uint_t nelems)
 122 {
 123         for (uint_t i = 0; i < nelems; i++) {
 124                 if (nvlarr[i] != NULL)
 125                         nvlist_free(nvlarr[i]);
 126         }
 127         kmem_free(nvlarr, nelems * sizeof (nvlist_t *));
 128 }
 129 
 130 int
 131 ufm_cache_fill(ddi_ufm_handle_t *ufmh)
 132 {
 133         int ret;
 134         uint_t nimgs;
 135         ddi_ufm_cap_t caps;
 136         nvlist_t **images = NULL, **slots = NULL;
 137 
 138         ASSERT(MUTEX_HELD(&ufmh->ufmh_lock));
 139 
 140         /*
 141          * Check whether we already have a cached report and if so, return
 142          * straight away.
 143          */
 144         if (ufmh->ufmh_report != NULL)
 145                 return (0);
 146 
 147         /*
 148          * First check which UFM caps this driver supports.  If it doesn't
 149          * support DDI_UFM_CAP_REPORT, then there's nothing to cache and we
 150          * can just return.
 151          */
 152         ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps);
 153         if (ret != 0)
 154                 return (ret);
 155 
 156         ufmh->ufmh_caps = caps;
 157         if ((ufmh->ufmh_caps & DDI_UFM_CAP_REPORT) == 0)
 158                 return (ENOTSUP);
 159 
 160         /*
 161          * Next, figure out how many UFM images the device has.  If a
 162          * ddi_ufm_op_nimages entry point wasn't specified, then we assume
 163          * that the device has a single image.
 164          */
 165         if (ufmh->ufmh_ops->ddi_ufm_op_nimages != NULL) {
 166                 ret = ufmh->ufmh_ops->ddi_ufm_op_nimages(ufmh, ufmh->ufmh_arg,
 167                     &nimgs);
 168                 if (ret == 0 && nimgs > 0)
 169                         ufmh->ufmh_nimages = nimgs;
 170                 else
 171                         goto cache_fail;
 172         } else {
 173                 ufmh->ufmh_nimages = 1;
 174         }
 175 
 176         /*
 177          * Now that we know how many images we're dealing with, allocate space
 178          * for an appropriately-sized array of ddi_ufm_image_t structs and then
 179          * iterate through them calling the ddi_ufm_op_fill_image entry point
 180          * so that the driver can fill them in.
 181          */
 182         ufmh->ufmh_images =
 183             kmem_zalloc((sizeof (ddi_ufm_image_t) * ufmh->ufmh_nimages),
 184             KM_NOSLEEP_LAZY);
 185         if (ufmh->ufmh_images == NULL)
 186                 return (ENOMEM);
 187 
 188         for (uint_t i = 0; i < ufmh->ufmh_nimages; i++) {
 189                 struct ddi_ufm_image *img = &ufmh->ufmh_images[i];
 190 
 191                 ret = ufmh->ufmh_ops->ddi_ufm_op_fill_image(ufmh,
 192                     ufmh->ufmh_arg, i, img);
 193 
 194                 if (ret != 0)
 195                         goto cache_fail;
 196 
 197                 if (img->ufmi_desc == NULL || img->ufmi_nslots == 0) {
 198                         ret = EIO;
 199                         goto cache_fail;
 200                 }
 201 
 202                 img->ufmi_slots =
 203                     kmem_zalloc((sizeof (ddi_ufm_slot_t) * img->ufmi_nslots),
 204                     KM_NOSLEEP_LAZY);
 205                 if (img->ufmi_slots == NULL) {
 206                         ret = ENOMEM;
 207                         goto cache_fail;
 208                 }
 209 
 210                 for (uint_t s = 0; s < img->ufmi_nslots; s++) {
 211                         struct ddi_ufm_slot *slot = &img->ufmi_slots[s];
 212 
 213                         ret = ufmh->ufmh_ops->ddi_ufm_op_fill_slot(ufmh,
 214                             ufmh->ufmh_arg, i, s, slot);
 215 
 216                         if (ret != 0)
 217                                 goto cache_fail;
 218 
 219                         ASSERT(slot->ufms_attrs & DDI_UFM_ATTR_EMPTY ||
 220                             slot->ufms_version != NULL);
 221                 }
 222         }
 223         images = kmem_zalloc(sizeof (nvlist_t *) * ufmh->ufmh_nimages,
 224             KM_SLEEP);
 225         for (uint_t i = 0; i < ufmh->ufmh_nimages; i ++) {
 226                 ddi_ufm_image_t *img = &ufmh->ufmh_images[i];
 227 
 228                 images[i] = fnvlist_alloc();
 229                 fnvlist_add_string(images[i], DDI_UFM_NV_IMAGE_DESC,
 230                     img->ufmi_desc);
 231                 if (img->ufmi_misc != NULL) {
 232                         fnvlist_add_nvlist(images[i], DDI_UFM_NV_IMAGE_MISC,
 233                             img->ufmi_misc);
 234                 }
 235 
 236                 slots = kmem_zalloc(sizeof (nvlist_t *) * img->ufmi_nslots,
 237                     KM_SLEEP);
 238                 for (uint_t s = 0; s < img->ufmi_nslots; s++) {
 239                         ddi_ufm_slot_t *slot = &img->ufmi_slots[s];
 240 
 241                         slots[s] = fnvlist_alloc();
 242                         fnvlist_add_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR,
 243                             slot->ufms_attrs);
 244                         if (slot->ufms_attrs & DDI_UFM_ATTR_EMPTY)
 245                                 continue;
 246 
 247                         if (slot->ufms_imgsize != 0) {
 248                                 fnvlist_add_uint64(slots[s],
 249                                     DDI_UFM_NV_SLOT_IMGSIZE,
 250                                     slot->ufms_imgsize);
 251                         }
 252 
 253                         fnvlist_add_string(slots[s], DDI_UFM_NV_SLOT_VERSION,
 254                             slot->ufms_version);
 255                         if (slot->ufms_misc != NULL) {
 256                                 fnvlist_add_nvlist(slots[s],
 257                                     DDI_UFM_NV_SLOT_MISC, slot->ufms_misc);
 258                         }
 259                 }
 260                 fnvlist_add_nvlist_array(images[i], DDI_UFM_NV_IMAGE_SLOTS,
 261                     slots, img->ufmi_nslots);
 262                 free_nvlist_array(slots, img->ufmi_nslots);
 263         }
 264         ufmh->ufmh_report = fnvlist_alloc();
 265         fnvlist_add_nvlist_array(ufmh->ufmh_report, DDI_UFM_NV_IMAGES, images,
 266             ufmh->ufmh_nimages);
 267         free_nvlist_array(images, ufmh->ufmh_nimages);
 268 
 269         return (0);
 270 
 271 cache_fail:
 272         ufm_cache_invalidate(ufmh);
 273         return (ret);
 274 }
 275 
 276 int
 277 ufm_read_img(ddi_ufm_handle_t *ufmh, uint_t img, uint_t slot, uint64_t len,
 278     uint64_t off, uintptr_t uaddr, uint64_t *nreadp, int copyflags)
 279 {
 280         int ret = 0;
 281         ddi_ufm_cap_t caps;
 282         void *buf;
 283         uint64_t nread;
 284 
 285         ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps);
 286         if (ret != 0) {
 287                 return (ret);
 288         }
 289 
 290         if ((caps & DDI_UFM_CAP_READIMG) == 0 ||
 291             ufmh->ufmh_ops->ddi_ufm_op_readimg == NULL) {
 292                 return (ENOTSUP);
 293         }
 294 
 295         if (off + len < MAX(off, len)) {
 296                 return (EOVERFLOW);
 297         }
 298 
 299         buf = kmem_zalloc(UFM_READ_STRIDE, KM_SLEEP);
 300         nread = 0;
 301         while (len > 0) {
 302                 uint64_t toread = MIN(len, UFM_READ_STRIDE);
 303                 uint64_t iter;
 304 
 305                 ret = ufmh->ufmh_ops->ddi_ufm_op_readimg(ufmh, ufmh->ufmh_arg,
 306                     img, slot, toread, off + nread, buf, &iter);
 307                 if (ret != 0) {
 308                         break;
 309                 }
 310 
 311                 if (ddi_copyout(buf, (void *)(uintptr_t)(uaddr + nread), iter,
 312                     copyflags & FKIOCTL) != 0) {
 313                         ret = EFAULT;
 314                         break;
 315                 }
 316 
 317                 nread += iter;
 318                 len -= iter;
 319         }
 320 
 321         *nreadp = nread;
 322         kmem_free(buf, UFM_READ_STRIDE);
 323         return (ret);
 324 }
 325 
 326 /*
 327  * This gets called early in boot by setup_ddi().
 328  */
 329 void
 330 ufm_init(void)
 331 {
 332         mutex_init(&ufm_lock, NULL, MUTEX_DEFAULT, NULL);
 333 
 334         avl_create(&ufm_handles, ufm_handle_compare,
 335             sizeof (ddi_ufm_handle_t),
 336             offsetof(ddi_ufm_handle_t, ufmh_link));
 337 }
 338 
 339 static int
 340 ufm_handle_compare(const void *a1, const void *a2)
 341 {
 342         const struct ddi_ufm_handle *hdl1, *hdl2;
 343         int cmp;
 344 
 345         hdl1 = (struct ddi_ufm_handle *)a1;
 346         hdl2 = (struct ddi_ufm_handle *)a2;
 347 
 348         cmp = strcmp(hdl1->ufmh_devpath, hdl2->ufmh_devpath);
 349 
 350         if (cmp > 0)
 351                 return (1);
 352         else if (cmp < 0)
 353                 return (-1);
 354         else
 355                 return (0);
 356 }
 357 
 358 /*
 359  * This is used by the ufm driver to lookup the UFM handle associated with a
 360  * particular devpath.
 361  *
 362  * On success, this function returns the reqested UFH handle, with its lock
 363  * held.  Caller is responsible to dropping the lock when it is done with the
 364  * handle.
 365  */
 366 struct ddi_ufm_handle *
 367 ufm_find(const char *devpath)
 368 {
 369         struct ddi_ufm_handle find = { 0 }, *ufmh;
 370 
 371         (void) strlcpy(find.ufmh_devpath, devpath, MAXPATHLEN);
 372 
 373         mutex_enter(&ufm_lock);
 374         ufmh = avl_find(&ufm_handles, &find, NULL);
 375         if (ufmh != NULL)
 376                 mutex_enter(&ufmh->ufmh_lock);
 377         mutex_exit(&ufm_lock);
 378 
 379         return (ufmh);
 380 }
 381 
 382 int
 383 ddi_ufm_init(dev_info_t *dip, uint_t version, ddi_ufm_ops_t *ufmops,
 384     ddi_ufm_handle_t **ufmh, void *arg)
 385 {
 386         ddi_ufm_handle_t *old_ufmh;
 387         char devpath[MAXPATHLEN];
 388 
 389         VERIFY(version != 0 && ufmops != NULL);
 390         VERIFY(ufmops->ddi_ufm_op_fill_image != NULL &&
 391             ufmops->ddi_ufm_op_fill_slot != NULL &&
 392             ufmops->ddi_ufm_op_getcaps != NULL);
 393 
 394         if (version < DDI_UFM_VERSION_ONE || version > DDI_UFM_CURRENT_VERSION)
 395                 return (ENOTSUP);
 396 
 397         /*
 398          * First we check if we already have a UFM handle for this device
 399          * instance.  This can happen if the module got unloaded or the driver
 400          * was suspended after previously registering with the UFM subsystem.
 401          *
 402          * If we find an old handle then we simply reset its state and hand it
 403          * back to the driver.
 404          *
 405          * If we don't find an old handle then this is a new registration, so
 406          * we allocate and initialize a new handle.
 407          *
 408          * In either case, we don't need to NULL-out the other fields (like
 409          * ufmh_report) as in order for them to be referenced, ufmh_state has to
 410          * first transition to DDI_UFM_STATE_READY.  The only way that can
 411          * happen is for the driver to call ddi_ufm_update(), which will call
 412          * ufm_cache_invalidate(), which in turn will take care of properly
 413          * cleaning up and reinitializing the other fields in the handle.
 414          */
 415         (void) ddi_pathname(dip, devpath);
 416         if ((old_ufmh = ufm_find(devpath)) != NULL) {
 417                 *ufmh = old_ufmh;
 418         } else {
 419                 *ufmh = kmem_zalloc(sizeof (ddi_ufm_handle_t), KM_SLEEP);
 420                 (void) strlcpy((*ufmh)->ufmh_devpath, devpath, MAXPATHLEN);
 421                 mutex_init(&(*ufmh)->ufmh_lock, NULL, MUTEX_DEFAULT, NULL);
 422         }
 423         (*ufmh)->ufmh_ops = ufmops;
 424         (*ufmh)->ufmh_arg = arg;
 425         (*ufmh)->ufmh_version = version;
 426         (*ufmh)->ufmh_state = DDI_UFM_STATE_INIT;
 427 
 428         /*
 429          * If this is a new registration, add the UFM handle to the global AVL
 430          * tree of handles.
 431          *
 432          * Otherwise, if it's an old registration then ufm_find() will have
 433          * returned the old handle with the lock already held, so we need to
 434          * release it before returning.
 435          */
 436         if (old_ufmh == NULL) {
 437                 mutex_enter(&ufm_lock);
 438                 avl_add(&ufm_handles, *ufmh);
 439                 mutex_exit(&ufm_lock);
 440         } else {
 441                 mutex_exit(&old_ufmh->ufmh_lock);
 442         }
 443 
 444         /*
 445          * Give a hint in the devinfo tree that this device supports UFM
 446          * capabilities.
 447          */
 448         (void) ndi_prop_create_boolean(DDI_DEV_T_NONE, dip, "ddi-ufm-capable");
 449 
 450         return (DDI_SUCCESS);
 451 }
 452 
 453 void
 454 ddi_ufm_fini(ddi_ufm_handle_t *ufmh)
 455 {
 456         VERIFY(ufmh != NULL);
 457 
 458         mutex_enter(&ufmh->ufmh_lock);
 459         ufmh->ufmh_state |= DDI_UFM_STATE_SHUTTING_DOWN;
 460         ufm_cache_invalidate(ufmh);
 461         mutex_exit(&ufmh->ufmh_lock);
 462 }
 463 
 464 void
 465 ddi_ufm_update(ddi_ufm_handle_t *ufmh)
 466 {
 467         VERIFY(ufmh != NULL);
 468 
 469         mutex_enter(&ufmh->ufmh_lock);
 470         if (ufmh->ufmh_state & DDI_UFM_STATE_SHUTTING_DOWN) {
 471                 mutex_exit(&ufmh->ufmh_lock);
 472                 return;
 473         }
 474         ufm_cache_invalidate(ufmh);
 475         ufmh->ufmh_state |= DDI_UFM_STATE_READY;
 476         mutex_exit(&ufmh->ufmh_lock);
 477 }
 478 
 479 void
 480 ddi_ufm_image_set_desc(ddi_ufm_image_t *uip, const char *desc)
 481 {
 482         VERIFY(uip != NULL && desc != NULL);
 483         if (uip->ufmi_desc != NULL)
 484                 strfree(uip->ufmi_desc);
 485 
 486         uip->ufmi_desc = ddi_strdup(desc, KM_SLEEP);
 487 }
 488 
 489 void
 490 ddi_ufm_image_set_nslots(ddi_ufm_image_t *uip, uint_t nslots)
 491 {
 492         VERIFY(uip != NULL);
 493         uip->ufmi_nslots = nslots;
 494 }
 495 
 496 void
 497 ddi_ufm_image_set_misc(ddi_ufm_image_t *uip, nvlist_t *misc)
 498 {
 499         VERIFY(uip != NULL && misc != NULL);
 500         nvlist_free(uip->ufmi_misc);
 501         uip->ufmi_misc = misc;
 502 }
 503 
 504 void
 505 ddi_ufm_slot_set_version(ddi_ufm_slot_t *usp, const char *version)
 506 {
 507         VERIFY(usp != NULL && version != NULL);
 508         if (usp->ufms_version != NULL)
 509                 strfree(usp->ufms_version);
 510 
 511         usp->ufms_version = ddi_strdup(version, KM_SLEEP);
 512 }
 513 
 514 void
 515 ddi_ufm_slot_set_attrs(ddi_ufm_slot_t *usp, ddi_ufm_attr_t attr)
 516 {
 517         VERIFY(usp != NULL && attr <= DDI_UFM_ATTR_MAX);
 518         usp->ufms_attrs = attr;
 519 }
 520 
 521 void
 522 ddi_ufm_slot_set_misc(ddi_ufm_slot_t *usp, nvlist_t *misc)
 523 {
 524         VERIFY(usp != NULL && misc != NULL);
 525         nvlist_free(usp->ufms_misc);
 526         usp->ufms_misc = misc;
 527 }
 528 
 529 void
 530 ddi_ufm_slot_set_imgsize(ddi_ufm_slot_t *usp, uint64_t size)
 531 {
 532         VERIFY3P(usp, !=, NULL);
 533         usp->ufms_imgsize = size;
 534 }