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 /* 18 * The ufm(7D) pseudo driver provides an ioctl interface for DDI UFM 19 * information. See ddi_ufm.h. 20 */ 21 #include <sys/ddi.h> 22 #include <sys/sunddi.h> 23 #include <sys/esunddi.h> 24 #include <sys/ddi_ufm.h> 25 #include <sys/ddi_ufm_impl.h> 26 #include <sys/conf.h> 27 #include <sys/debug.h> 28 #include <sys/file.h> 29 #include <sys/kmem.h> 30 #include <sys/stat.h> 31 #include <sys/sysmacros.h> 32 33 #define UFM_READ_SIZE (1 * 1024 * 1024) 34 35 #define UFMTEST_IOC ('u' << 24) | ('f' << 16) | ('t' << 8) 36 #define UFMTEST_IOC_SETFW (UFMTEST_IOC | 1) 37 38 static dev_info_t *ufm_devi = NULL; 39 40 static int ufm_open(dev_t *, int, int, cred_t *); 41 static int ufm_close(dev_t, int, int, cred_t *); 42 static int ufm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 43 44 static struct cb_ops ufm_cb_ops = { 45 .cb_open = ufm_open, 46 .cb_close = ufm_close, 47 .cb_strategy = nodev, 48 .cb_print = nodev, 49 .cb_dump = nodev, 50 .cb_read = nodev, 51 .cb_write = nodev, 52 .cb_ioctl = ufm_ioctl, 53 .cb_devmap = nodev, 54 .cb_mmap = nodev, 55 .cb_segmap = nodev, 56 .cb_chpoll = nochpoll, 57 .cb_prop_op = ddi_prop_op, 58 .cb_str = NULL, 59 .cb_flag = D_NEW | D_MP, 60 .cb_rev = CB_REV, 61 .cb_aread = nodev, 62 .cb_awrite = nodev 63 }; 64 65 static int ufm_info(dev_info_t *, ddi_info_cmd_t, void *, void **); 66 static int ufm_attach(dev_info_t *, ddi_attach_cmd_t); 67 static int ufm_detach(dev_info_t *, ddi_detach_cmd_t); 68 69 static struct dev_ops ufm_ops = { 70 .devo_rev = DEVO_REV, 71 .devo_refcnt = 0, 72 .devo_getinfo = ufm_info, 73 .devo_identify = nulldev, 74 .devo_probe = nulldev, 75 .devo_attach = ufm_attach, 76 .devo_detach = ufm_detach, 77 .devo_reset = nodev, 78 .devo_cb_ops = &ufm_cb_ops, 79 .devo_bus_ops = NULL, 80 .devo_power = NULL, 81 .devo_quiesce = ddi_quiesce_not_needed 82 }; 83 84 static struct modldrv modldrv = { 85 .drv_modops = &mod_driverops, 86 .drv_linkinfo = "Upgradeable FW Module driver", 87 .drv_dev_ops = &ufm_ops 88 }; 89 90 static struct modlinkage modlinkage = { 91 .ml_rev = MODREV_1, 92 .ml_linkage = { (void *)&modldrv, NULL } 93 }; 94 95 int 96 _init(void) 97 { 98 return (mod_install(&modlinkage)); 99 } 100 101 int 102 _fini(void) 103 { 104 return (mod_remove(&modlinkage)); 105 } 106 107 int 108 _info(struct modinfo *modinfop) 109 { 110 return (mod_info(&modlinkage, modinfop)); 111 } 112 113 static int 114 ufm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 115 { 116 switch (infocmd) { 117 case DDI_INFO_DEVT2DEVINFO: 118 *result = ufm_devi; 119 return (DDI_SUCCESS); 120 case DDI_INFO_DEVT2INSTANCE: 121 *result = 0; 122 return (DDI_SUCCESS); 123 } 124 return (DDI_FAILURE); 125 } 126 127 static int 128 ufm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 129 { 130 if (cmd != DDI_ATTACH || ufm_devi != NULL) 131 return (DDI_FAILURE); 132 133 if (ddi_create_minor_node(devi, "ufm", S_IFCHR, 0, DDI_PSEUDO, 0) == 134 DDI_FAILURE) { 135 ddi_remove_minor_node(devi, NULL); 136 return (DDI_FAILURE); 137 } 138 139 ufm_devi = devi; 140 return (DDI_SUCCESS); 141 } 142 143 static int 144 ufm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) 145 { 146 if (cmd != DDI_DETACH) 147 return (DDI_FAILURE); 148 149 if (devi != NULL) 150 ddi_remove_minor_node(devi, NULL); 151 152 ufm_devi = NULL; 153 return (DDI_SUCCESS); 154 } 155 156 static int 157 ufm_open(dev_t *devp, int flag, int otyp, cred_t *credp) 158 { 159 const int inv_flags = FWRITE | FEXCL | FNDELAY | FNONBLOCK; 160 161 if (otyp != OTYP_CHR) 162 return (EINVAL); 163 164 if (flag & inv_flags) 165 return (EINVAL); 166 167 if (drv_priv(credp) != 0) 168 return (EPERM); 169 170 return (0); 171 } 172 173 static int 174 ufm_close(dev_t dev, int flag, int otyp, cred_t *credp) 175 { 176 return (0); 177 } 178 179 static boolean_t 180 ufm_driver_ready(ddi_ufm_handle_t *ufmh) 181 { 182 VERIFY(ufmh != NULL); 183 184 if (ufmh->ufmh_state & DDI_UFM_STATE_SHUTTING_DOWN || 185 !(ufmh->ufmh_state & DDI_UFM_STATE_READY)) { 186 return (B_FALSE); 187 } 188 return (B_TRUE); 189 } 190 191 static int 192 ufm_do_getcaps(intptr_t data, int mode) 193 { 194 ddi_ufm_handle_t *ufmh; 195 ddi_ufm_cap_t caps; 196 ufm_ioc_getcaps_t ugc; 197 dev_info_t *dip; 198 int ret; 199 char devpath[MAXPATHLEN]; 200 201 if (ddi_copyin((void *)data, &ugc, sizeof (ufm_ioc_getcaps_t), 202 mode) != 0) 203 return (EFAULT); 204 205 if (strlcpy(devpath, ugc.ufmg_devpath, MAXPATHLEN) >= MAXPATHLEN) 206 return (EOVERFLOW); 207 208 if ((dip = e_ddi_hold_devi_by_path(devpath, 0)) == NULL) { 209 return (ENOTSUP); 210 } 211 if ((ufmh = ufm_find(devpath)) == NULL) { 212 ddi_release_devi(dip); 213 return (ENOTSUP); 214 } 215 ASSERT(MUTEX_HELD(&ufmh->ufmh_lock)); 216 217 if (!ufm_driver_ready(ufmh)) { 218 ddi_release_devi(dip); 219 mutex_exit(&ufmh->ufmh_lock); 220 return (EAGAIN); 221 } 222 223 if (ugc.ufmg_version != ufmh->ufmh_version) { 224 ddi_release_devi(dip); 225 mutex_exit(&ufmh->ufmh_lock); 226 return (ENOTSUP); 227 } 228 229 if ((ret = ufm_cache_fill(ufmh)) != 0) { 230 ddi_release_devi(dip); 231 mutex_exit(&ufmh->ufmh_lock); 232 return (ret); 233 } 234 235 ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps); 236 mutex_exit(&ufmh->ufmh_lock); 237 ddi_release_devi(dip); 238 239 if (ret != 0) 240 return (ret); 241 242 ugc.ufmg_caps = caps; 243 244 if (ddi_copyout(&ugc, (void *)data, sizeof (ufm_ioc_getcaps_t), 245 mode) != 0) 246 return (EFAULT); 247 248 return (0); 249 } 250 251 static int 252 ufm_do_reportsz(intptr_t data, int mode) 253 { 254 ddi_ufm_handle_t *ufmh; 255 dev_info_t *dip; 256 uint_t model; 257 size_t sz; 258 int ret; 259 char devpath[MAXPATHLEN]; 260 ufm_ioc_bufsz_t ufbz; 261 #ifdef _MULTI_DATAMODEL 262 ufm_ioc_bufsz32_t ufbz32; 263 #endif 264 265 model = ddi_model_convert_from(mode); 266 267 switch (model) { 268 #ifdef _MULTI_DATAMODEL 269 case DDI_MODEL_ILP32: 270 if (ddi_copyin((void *)data, &ufbz32, 271 sizeof (ufm_ioc_bufsz32_t), mode) != 0) 272 return (EFAULT); 273 ufbz.ufbz_version = ufbz32.ufbz_version; 274 if (strlcpy(ufbz.ufbz_devpath, ufbz32.ufbz_devpath, 275 MAXPATHLEN) >= MAXPATHLEN) { 276 return (EOVERFLOW); 277 } 278 break; 279 #endif /* _MULTI_DATAMODEL */ 280 case DDI_MODEL_NONE: 281 default: 282 if (ddi_copyin((void *)data, &ufbz, 283 sizeof (ufm_ioc_bufsz_t), mode) != 0) 284 return (EFAULT); 285 } 286 287 if (strlcpy(devpath, ufbz.ufbz_devpath, MAXPATHLEN) >= MAXPATHLEN) 288 return (EOVERFLOW); 289 290 if ((dip = e_ddi_hold_devi_by_path(devpath, 0)) == NULL) { 291 return (ENOTSUP); 292 } 293 if ((ufmh = ufm_find(devpath)) == NULL) { 294 ddi_release_devi(dip); 295 return (ENOTSUP); 296 } 297 ASSERT(MUTEX_HELD(&ufmh->ufmh_lock)); 298 299 if (!ufm_driver_ready(ufmh)) { 300 ddi_release_devi(dip); 301 mutex_exit(&ufmh->ufmh_lock); 302 return (EAGAIN); 303 } 304 305 if (ufbz.ufbz_version != ufmh->ufmh_version) { 306 ddi_release_devi(dip); 307 mutex_exit(&ufmh->ufmh_lock); 308 return (ENOTSUP); 309 } 310 311 /* 312 * Note - ufm_cache_fill() also takes care of verifying that the driver 313 * supports the DDI_UFM_CAP_REPORT capability and will return non-zero, 314 * if not supported. 315 */ 316 if ((ret = ufm_cache_fill(ufmh)) != 0) { 317 ddi_release_devi(dip); 318 mutex_exit(&ufmh->ufmh_lock); 319 return (ret); 320 } 321 ddi_release_devi(dip); 322 323 ret = nvlist_size(ufmh->ufmh_report, &sz, NV_ENCODE_NATIVE); 324 mutex_exit(&ufmh->ufmh_lock); 325 if (ret != 0) 326 return (ret); 327 328 switch (model) { 329 #ifdef _MULTI_DATAMODEL 330 case DDI_MODEL_ILP32: 331 ufbz32.ufbz_size = sz; 332 if (ddi_copyout(&ufbz32, (void *)data, 333 sizeof (ufm_ioc_bufsz32_t), mode) != 0) 334 return (EFAULT); 335 break; 336 #endif /* _MULTI_DATAMODEL */ 337 case DDI_MODEL_NONE: 338 default: 339 ufbz.ufbz_size = sz; 340 if (ddi_copyout(&ufbz, (void *)data, 341 sizeof (ufm_ioc_bufsz_t), mode) != 0) 342 return (EFAULT); 343 } 344 return (0); 345 } 346 347 static int 348 ufm_do_report(intptr_t data, int mode) 349 { 350 ddi_ufm_handle_t *ufmh; 351 uint_t model; 352 int ret = 0; 353 char *buf; 354 size_t sz; 355 dev_info_t *dip; 356 char devpath[MAXPATHLEN]; 357 ufm_ioc_report_t ufmr; 358 #ifdef _MULTI_DATAMODEL 359 ufm_ioc_report32_t ufmr32; 360 #endif 361 362 model = ddi_model_convert_from(mode); 363 364 switch (model) { 365 #ifdef _MULTI_DATAMODEL 366 case DDI_MODEL_ILP32: 367 if (ddi_copyin((void *)data, &ufmr32, 368 sizeof (ufm_ioc_report32_t), mode) != 0) 369 return (EFAULT); 370 ufmr.ufmr_version = ufmr32.ufmr_version; 371 if (strlcpy(ufmr.ufmr_devpath, ufmr32.ufmr_devpath, 372 MAXPATHLEN) >= MAXPATHLEN) { 373 return (EOVERFLOW); 374 } 375 ufmr.ufmr_bufsz = ufmr32.ufmr_bufsz; 376 ufmr.ufmr_buf = (caddr_t)(uintptr_t)ufmr32.ufmr_buf; 377 break; 378 #endif /* _MULTI_DATAMODEL */ 379 case DDI_MODEL_NONE: 380 default: 381 if (ddi_copyin((void *)data, &ufmr, 382 sizeof (ufm_ioc_report_t), mode) != 0) 383 return (EFAULT); 384 } 385 386 if (strlcpy(devpath, ufmr.ufmr_devpath, MAXPATHLEN) >= MAXPATHLEN) 387 return (EOVERFLOW); 388 389 if ((dip = e_ddi_hold_devi_by_path(devpath, 0)) == NULL) { 390 return (ENOTSUP); 391 } 392 if ((ufmh = ufm_find(devpath)) == NULL) { 393 ddi_release_devi(dip); 394 return (ENOTSUP); 395 } 396 ASSERT(MUTEX_HELD(&ufmh->ufmh_lock)); 397 398 if (!ufm_driver_ready(ufmh)) { 399 ddi_release_devi(dip); 400 mutex_exit(&ufmh->ufmh_lock); 401 return (EAGAIN); 402 } 403 404 if (ufmr.ufmr_version != ufmh->ufmh_version) { 405 ddi_release_devi(dip); 406 mutex_exit(&ufmh->ufmh_lock); 407 return (ENOTSUP); 408 } 409 410 /* 411 * Note - ufm_cache_fill() also takes care of verifying that the driver 412 * supports the DDI_UFM_CAP_REPORT capability and will return non-zero, 413 * if not supported. 414 */ 415 if ((ret = ufm_cache_fill(ufmh)) != 0) { 416 ddi_release_devi(dip); 417 mutex_exit(&ufmh->ufmh_lock); 418 return (ret); 419 } 420 ddi_release_devi(dip); 421 422 if ((ret = nvlist_size(ufmh->ufmh_report, &sz, NV_ENCODE_NATIVE)) != 423 0) { 424 mutex_exit(&ufmh->ufmh_lock); 425 return (ret); 426 } 427 if (sz > ufmr.ufmr_bufsz) { 428 mutex_exit(&ufmh->ufmh_lock); 429 return (EOVERFLOW); 430 } 431 432 buf = fnvlist_pack(ufmh->ufmh_report, &sz); 433 mutex_exit(&ufmh->ufmh_lock); 434 435 if (ddi_copyout(buf, ufmr.ufmr_buf, sz, mode) != 0) { 436 kmem_free(buf, sz); 437 return (EFAULT); 438 } 439 kmem_free(buf, sz); 440 441 switch (model) { 442 #ifdef _MULTI_DATAMODEL 443 case DDI_MODEL_ILP32: 444 ufmr32.ufmr_bufsz = sz; 445 if (ddi_copyout(&ufmr32, (void *)data, 446 sizeof (ufm_ioc_report32_t), mode) != 0) 447 return (EFAULT); 448 break; 449 #endif /* _MULTI_DATAMODEL */ 450 case DDI_MODEL_NONE: 451 default: 452 ufmr.ufmr_bufsz = sz; 453 if (ddi_copyout(&ufmr, (void *)data, 454 sizeof (ufm_ioc_report_t), mode) != 0) 455 return (EFAULT); 456 } 457 458 return (0); 459 } 460 461 static int 462 ufm_do_readimg(intptr_t data, int mode) 463 { 464 int ret; 465 uint_t model; 466 ufm_ioc_readimg_t ufri; 467 char devpath[MAXPATHLEN]; 468 ddi_ufm_handle_t *ufmh; 469 dev_info_t *dip; 470 #ifdef _MULTI_DATAMODEL 471 ufm_ioc_readimg32_t ufri32; 472 #endif 473 474 model = ddi_model_convert_from(mode); 475 switch (model) { 476 #ifdef _MULTI_DATAMODEL 477 case DDI_MODEL_ILP32: 478 if (ddi_copyin((void *)data, &ufri32, sizeof (ufri32), 479 mode) != 0) { 480 return (EFAULT); 481 } 482 ufri.ufri_version = ufri32.ufri_version; 483 ufri.ufri_imageno = ufri32.ufri_imageno; 484 ufri.ufri_slotno = ufri32.ufri_slotno; 485 ufri.ufri_offset = ufri32.ufri_offset; 486 ufri.ufri_len = ufri32.ufri_len; 487 ufri.ufri_nread = ufri32.ufri_nread; 488 489 if (strlcpy(ufri.ufri_devpath, ufri32.ufri_devpath, 490 MAXPATHLEN) >= MAXPATHLEN) { 491 return (EOVERFLOW); 492 } 493 ufri.ufri_buf = (caddr_t)(uintptr_t)ufri32.ufri_buf; 494 break; 495 #endif /* _MULTI_DATAMODEL */ 496 case DDI_MODEL_NONE: 497 default: 498 if (ddi_copyin((void *)data, &ufri, sizeof (ufri), mode) != 0) { 499 return (EFAULT); 500 } 501 } 502 503 if (strlcpy(devpath, ufri.ufri_devpath, MAXPATHLEN) >= MAXPATHLEN) 504 return (EOVERFLOW); 505 506 if ((dip = e_ddi_hold_devi_by_path(devpath, 0)) == NULL) { 507 return (ENOTSUP); 508 } 509 if ((ufmh = ufm_find(devpath)) == NULL) { 510 ddi_release_devi(dip); 511 return (ENOTSUP); 512 } 513 ASSERT(MUTEX_HELD(&ufmh->ufmh_lock)); 514 515 if (!ufm_driver_ready(ufmh)) { 516 ret = EAGAIN; 517 goto out; 518 } 519 520 if (ufri.ufri_version != ufmh->ufmh_version) { 521 ret = ENOTSUP; 522 goto out; 523 } 524 525 ret = ufm_read_img(ufmh, ufri.ufri_imageno, ufri.ufri_slotno, 526 ufri.ufri_len, ufri.ufri_offset, (uintptr_t)ufri.ufri_buf, 527 &ufri.ufri_nread, mode); 528 529 out: 530 mutex_exit(&ufmh->ufmh_lock); 531 ddi_release_devi(dip); 532 533 if (ret == 0) { 534 switch (model) { 535 #ifdef _MULTI_DATAMODEL 536 case DDI_MODEL_ILP32: 537 ufri32.ufri_nread = ufri.ufri_nread; 538 if (ddi_copyout(&ufri32, (void *)data, sizeof (ufri32), 539 mode) != 0) { 540 return (EFAULT); 541 } 542 break; 543 #endif /* _MULTI_DATAMODEL */ 544 case DDI_MODEL_NONE: 545 default: 546 if (ddi_copyout(&ufri, (void *)data, sizeof (ufri), 547 mode) != 0) { 548 return (EFAULT); 549 } 550 } 551 } 552 553 return (ret); 554 } 555 556 static int 557 ufm_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, 558 int *rvalp) 559 { 560 int ret = 0; 561 562 if (drv_priv(credp) != 0) 563 return (EPERM); 564 565 switch (cmd) { 566 case UFM_IOC_GETCAPS: 567 ret = ufm_do_getcaps(data, mode); 568 break; 569 570 case UFM_IOC_REPORTSZ: 571 ret = ufm_do_reportsz(data, mode); 572 break; 573 574 case UFM_IOC_REPORT: 575 ret = ufm_do_report(data, mode); 576 break; 577 578 case UFM_IOC_READIMG: 579 ret = ufm_do_readimg(data, mode); 580 break; 581 default: 582 return (ENOTTY); 583 } 584 return (ret); 585 586 } 587