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