xref: /illumos-gate/usr/src/uts/common/io/ufm.c (revision bbf21555)
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