Home | History | Annotate | Download | only in io
      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_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