xref: /illumos-gate/usr/src/uts/common/io/devfm.c (revision 1e4c938b57d1656808e4112127ff1dce3eba5314)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/param.h>
29 #include <sys/cred.h>
30 #include <sys/policy.h>
31 #include <sys/file.h>
32 #include <sys/errno.h>
33 #include <sys/modctl.h>
34 #include <sys/ddi.h>
35 #include <sys/sunddi.h>
36 #include <sys/conf.h>
37 #include <sys/debug.h>
38 #include <sys/systeminfo.h>
39 
40 #include <sys/fm/protocol.h>
41 #include <sys/devfm.h>
42 
43 extern int fm_get_paddr(nvlist_t *, uint64_t *);
44 #if defined(__x86)
45 extern int fm_ioctl_physcpu_info(int, nvlist_t *, nvlist_t **);
46 extern int fm_ioctl_cpu_retire(int, nvlist_t *, nvlist_t **);
47 extern int fm_ioctl_gentopo_legacy(int, nvlist_t *, nvlist_t **);
48 #endif /* __x86 */
49 
50 static int fm_ioctl_versions(int, nvlist_t *, nvlist_t **);
51 static int fm_ioctl_page_retire(int, nvlist_t *, nvlist_t **);
52 
53 /*
54  * The driver's capabilities are strictly versioned, allowing userland patching
55  * without a reboot.  The userland should start with a FM_VERSIONS ioctl to
56  * query the versions of the kernel interfaces, then it's all userland's
57  * responsibility to prepare arguments etc to match the current kenrel.
58  * The version of FM_VERSIONS itself is FM_DRV_VERSION.
59  */
60 typedef struct fm_version {
61 	char		*interface;	/* interface name */
62 	uint32_t	version;	/* interface version */
63 } fm_vers_t;
64 
65 typedef struct fm_subroutine {
66 	int		cmd;		/* ioctl cmd */
67 	boolean_t	priv;		/* require privilege */
68 	char		*version;	/* version name */
69 	int		(*func)(int, nvlist_t *, nvlist_t **);	/* handler */
70 } fm_subr_t;
71 
72 static const fm_vers_t fm_versions[] = {
73 	{ FM_VERSIONS_VERSION, FM_DRV_VERSION },
74 	{ FM_PAGE_OP_VERSION, 1 },
75 	{ FM_CPU_OP_VERSION, 1 },
76 	{ FM_CPU_INFO_VERSION, 1 },
77 	{ FM_TOPO_LEGACY_VERSION, 1 },
78 	{ NULL, 0 }
79 };
80 
81 static const fm_subr_t fm_subrs[] = {
82 	{ FM_IOC_VERSIONS, B_FALSE, FM_VERSIONS_VERSION, fm_ioctl_versions },
83 	{ FM_IOC_PAGE_RETIRE, B_TRUE, FM_PAGE_OP_VERSION,
84 	    fm_ioctl_page_retire },
85 	{ FM_IOC_PAGE_STATUS, B_FALSE, FM_PAGE_OP_VERSION,
86 	    fm_ioctl_page_retire },
87 	{ FM_IOC_PAGE_UNRETIRE, B_TRUE, FM_PAGE_OP_VERSION,
88 	    fm_ioctl_page_retire },
89 #if defined(__x86)
90 	{ FM_IOC_PHYSCPU_INFO, B_FALSE, FM_CPU_INFO_VERSION,
91 	    fm_ioctl_physcpu_info },
92 	{ FM_IOC_CPU_RETIRE, B_TRUE, FM_CPU_OP_VERSION,
93 	    fm_ioctl_cpu_retire },
94 	{ FM_IOC_CPU_STATUS, B_FALSE, FM_CPU_OP_VERSION,
95 	    fm_ioctl_cpu_retire },
96 	{ FM_IOC_CPU_UNRETIRE, B_TRUE, FM_CPU_OP_VERSION,
97 	    fm_ioctl_cpu_retire },
98 	{ FM_IOC_GENTOPO_LEGACY, B_FALSE, FM_TOPO_LEGACY_VERSION,
99 	    fm_ioctl_gentopo_legacy },
100 #endif	/* __x86 */
101 	{ -1, B_FALSE, NULL, NULL },
102 };
103 
104 static dev_info_t *fm_dip;
105 static boolean_t is_i86xpv;
106 static nvlist_t *fm_vers_nvl;
107 
108 static int
109 fm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
110 {
111 	switch (cmd) {
112 	case DDI_ATTACH:
113 		if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
114 		    ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS) {
115 			ddi_remove_minor_node(dip, NULL);
116 			return (DDI_FAILURE);
117 		}
118 		fm_dip = dip;
119 		is_i86xpv = (strcmp(platform, "i86xpv") == 0);
120 		break;
121 	case DDI_RESUME:
122 		break;
123 	default:
124 		return (DDI_FAILURE);
125 	}
126 	return (DDI_SUCCESS);
127 }
128 
129 static int
130 fm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
131 {
132 	int ret = DDI_SUCCESS;
133 
134 	switch (cmd) {
135 	case DDI_DETACH:
136 		ddi_remove_minor_node(dip, NULL);
137 		fm_dip = NULL;
138 		break;
139 	default:
140 		ret = DDI_FAILURE;
141 	}
142 	return (ret);
143 }
144 
145 /*ARGSUSED*/
146 static int
147 fm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
148 {
149 	int error;
150 
151 	switch (infocmd) {
152 	case DDI_INFO_DEVT2DEVINFO:
153 		*result = fm_dip;
154 		error = DDI_SUCCESS;
155 		break;
156 	case DDI_INFO_DEVT2INSTANCE:
157 		*result = NULL;
158 		error = DDI_SUCCESS;
159 		break;
160 	default:
161 		error = DDI_FAILURE;
162 	}
163 	return (error);
164 }
165 
166 /*ARGSUSED1*/
167 static int
168 fm_open(dev_t *devp, int flag, int typ, struct cred *cred)
169 {
170 	if (typ != OTYP_CHR)
171 		return (EINVAL);
172 	if (getminor(*devp) != 0)
173 		return (ENXIO);
174 
175 	return (0);
176 }
177 
178 /*ARGSUSED*/
179 static int
180 fm_ioctl_versions(int cmd, nvlist_t *invl, nvlist_t **onvlp)
181 {
182 	nvlist_t *nvl;
183 	int err;
184 
185 	if ((err = nvlist_dup(fm_vers_nvl, &nvl, KM_SLEEP)) == 0)
186 		*onvlp = nvl;
187 
188 	return (err);
189 }
190 
191 /*
192  * Given a mem-scheme FMRI for a page, execute the given page retire
193  * command on it.
194  */
195 /*ARGSUSED*/
196 static int
197 fm_ioctl_page_retire(int cmd, nvlist_t *invl, nvlist_t **onvlp)
198 {
199 	uint64_t pa;
200 	nvlist_t *fmri;
201 	int err;
202 
203 	if (is_i86xpv)
204 		return (ENOTSUP);
205 
206 	if ((err = nvlist_lookup_nvlist(invl, FM_PAGE_RETIRE_FMRI, &fmri))
207 	    != 0)
208 		return (err);
209 
210 	if ((err = fm_get_paddr(fmri, &pa)) != 0)
211 		return (err);
212 
213 	switch (cmd) {
214 	case FM_IOC_PAGE_STATUS:
215 		return (page_retire_check(pa, NULL));
216 
217 	case FM_IOC_PAGE_RETIRE:
218 		return (page_retire(pa, PR_FMA));
219 
220 	case FM_IOC_PAGE_UNRETIRE:
221 		return (page_unretire(pa));
222 	}
223 
224 	return (ENOTTY);
225 }
226 
227 /*ARGSUSED*/
228 static int
229 fm_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cred, int *rvalp)
230 {
231 	char *buf;
232 	int err;
233 	uint_t model;
234 	const fm_subr_t *subr;
235 	uint32_t vers;
236 	fm_ioc_data_t fid;
237 	nvlist_t *invl = NULL, *onvl = NULL;
238 #ifdef _MULTI_DATAMODEL
239 	fm_ioc_data32_t fid32;
240 #endif
241 
242 	if (getminor(dev) != 0)
243 		return (ENXIO);
244 
245 	for (subr = fm_subrs; subr->cmd != cmd; subr++)
246 		if (subr->cmd == -1)
247 			return (ENOTTY);
248 
249 	if (subr->priv && (flag & FWRITE) == 0 &&
250 	    secpolicy_sys_config(CRED(), 0) != 0)
251 		return (EPERM);
252 
253 	model = ddi_model_convert_from(flag & FMODELS);
254 
255 	switch (model) {
256 #ifdef _MULTI_DATAMODEL
257 	case DDI_MODEL_ILP32:
258 		if (ddi_copyin((void *)data, &fid32,
259 		    sizeof (fm_ioc_data32_t), flag) != 0)
260 			return (EFAULT);
261 		fid.fid_version = fid32.fid_version;
262 		fid.fid_insz = fid32.fid_insz;
263 		fid.fid_inbuf = (caddr_t)(uintptr_t)fid32.fid_inbuf;
264 		fid.fid_outsz = fid32.fid_outsz;
265 		fid.fid_outbuf = (caddr_t)(uintptr_t)fid32.fid_outbuf;
266 		break;
267 #endif /* _MULTI_DATAMODEL */
268 	case DDI_MODEL_NONE:
269 	default:
270 		if (ddi_copyin((void *)data, &fid, sizeof (fm_ioc_data_t),
271 		    flag) != 0)
272 			return (EFAULT);
273 	}
274 
275 	if (nvlist_lookup_uint32(fm_vers_nvl, subr->version, &vers) != 0 ||
276 	    fid.fid_version != vers)
277 		return (ENOTSUP);
278 
279 	if (fid.fid_insz > FM_IOC_MAXBUFSZ)
280 		return (ENAMETOOLONG);
281 	if (fid.fid_outsz > FM_IOC_OUT_MAXBUFSZ)
282 		return (EINVAL);
283 
284 	/*
285 	 * Copy in and unpack the input nvlist.
286 	 */
287 	if (fid.fid_insz != 0 && fid.fid_inbuf != (caddr_t)0) {
288 		buf = kmem_alloc(fid.fid_insz, KM_SLEEP);
289 		if (ddi_copyin(fid.fid_inbuf, buf, fid.fid_insz, flag) != 0) {
290 			kmem_free(buf, fid.fid_insz);
291 			return (EFAULT);
292 		}
293 		err = nvlist_unpack(buf, fid.fid_insz, &invl, KM_SLEEP);
294 		kmem_free(buf, fid.fid_insz);
295 		if (err != 0)
296 			return (err);
297 	}
298 
299 	err = subr->func(cmd, invl, &onvl);
300 
301 	if (invl != NULL)
302 		nvlist_free(invl);
303 
304 	if (err != 0) {
305 		if (onvl != NULL)
306 			nvlist_free(onvl);
307 		return (err);
308 	}
309 
310 	/*
311 	 * If the output nvlist contains any data, pack it and copyout.
312 	 */
313 	if (onvl != NULL) {
314 		size_t sz;
315 
316 		if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) {
317 			nvlist_free(onvl);
318 			return (err);
319 		}
320 		if (sz > fid.fid_outsz) {
321 			nvlist_free(onvl);
322 			return (ENAMETOOLONG);
323 		}
324 
325 		buf = kmem_alloc(sz, KM_SLEEP);
326 		if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE,
327 		    KM_SLEEP)) != 0) {
328 			kmem_free(buf, sz);
329 			nvlist_free(onvl);
330 			return (err);
331 		}
332 		nvlist_free(onvl);
333 		if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) {
334 			kmem_free(buf, sz);
335 			return (EFAULT);
336 		}
337 		kmem_free(buf, sz);
338 		fid.fid_outsz = sz;
339 
340 		switch (model) {
341 #ifdef _MULTI_DATAMODEL
342 		case DDI_MODEL_ILP32:
343 			fid32.fid_outsz = (size32_t)fid.fid_outsz;
344 			if (ddi_copyout(&fid32, (void *)data,
345 			    sizeof (fm_ioc_data32_t), flag) != 0)
346 				return (EFAULT);
347 			break;
348 #endif /* _MULTI_DATAMODEL */
349 		case DDI_MODEL_NONE:
350 		default:
351 			if (ddi_copyout(&fid, (void *)data,
352 			    sizeof (fm_ioc_data_t), flag) != 0)
353 				return (EFAULT);
354 		}
355 	}
356 
357 	return (err);
358 }
359 
360 static struct cb_ops fm_cb_ops = {
361 	fm_open,		/* open */
362 	nulldev,		/* close */
363 	nodev,			/* strategy */
364 	nodev,			/* print */
365 	nodev,			/* dump */
366 	nodev,			/* read */
367 	nodev,			/* write */
368 	fm_ioctl,		/* ioctl */
369 	nodev,			/* devmap */
370 	nodev,			/* mmap */
371 	nodev,			/* segmap */
372 	nochpoll,		/* poll */
373 	ddi_prop_op,		/* prop_op */
374 	NULL,			/* streamtab  */
375 	D_NEW | D_MP | D_64BIT | D_U64BIT
376 };
377 
378 static struct dev_ops fm_ops = {
379 	DEVO_REV,		/* devo_rev, */
380 	0,			/* refcnt  */
381 	fm_info,		/* get_dev_info */
382 	nulldev,		/* identify */
383 	nulldev,		/* probe */
384 	fm_attach,		/* attach */
385 	fm_detach,		/* detach */
386 	nodev,			/* reset */
387 	&fm_cb_ops,		/* driver operations */
388 	(struct bus_ops *)0	/* bus operations */
389 };
390 
391 static struct modldrv modldrv = {
392 	&mod_driverops, "fault management driver", &fm_ops,
393 };
394 
395 static struct modlinkage modlinkage = {
396 	MODREV_1, &modldrv, NULL
397 };
398 
399 int
400 _init(void)
401 {
402 	const fm_vers_t *p;
403 	int ret;
404 
405 
406 	if ((ret = mod_install(&modlinkage)) == 0) {
407 		(void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP);
408 		for (p = fm_versions; p->interface != NULL; p++)
409 			(void) nvlist_add_uint32(fm_vers_nvl, p->interface,
410 			    p->version);
411 	}
412 
413 	return (ret);
414 }
415 
416 int
417 _info(struct modinfo *modinfop)
418 {
419 	return (mod_info(&modlinkage, modinfop));
420 }
421 
422 int
423 _fini(void)
424 {
425 	int ret;
426 
427 	if ((ret = mod_remove(&modlinkage)) == 0) {
428 		if (fm_vers_nvl != NULL)
429 			nvlist_free(fm_vers_nvl);
430 	}
431 
432 	return (ret);
433 }
434