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(4D) 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
_init(void)96 _init(void)
97 {
98 return (mod_install(&modlinkage));
99 }
100
101 int
_fini(void)102 _fini(void)
103 {
104 return (mod_remove(&modlinkage));
105 }
106
107 int
_info(struct modinfo * modinfop)108 _info(struct modinfo *modinfop)
109 {
110 return (mod_info(&modlinkage, modinfop));
111 }
112
113 static int
ufm_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
ufm_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)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
ufm_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)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
ufm_open(dev_t * devp,int flag,int otyp,cred_t * credp)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
ufm_close(dev_t dev,int flag,int otyp,cred_t * credp)174 ufm_close(dev_t dev, int flag, int otyp, cred_t *credp)
175 {
176 return (0);
177 }
178
179 static boolean_t
ufm_driver_ready(ddi_ufm_handle_t * ufmh)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
ufm_do_getcaps(intptr_t data,int mode)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
ufm_do_reportsz(intptr_t data,int mode)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
ufm_do_report(intptr_t data,int mode)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
ufm_do_readimg(intptr_t data,int mode)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
ufm_ioctl(dev_t dev,int cmd,intptr_t data,int mode,cred_t * credp,int * rvalp)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