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