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