xref: /illumos-gate/usr/src/uts/intel/io/ucode_drv.c (revision d32f26ee)
12449e17fSsherrym /*
22449e17fSsherrym  * CDDL HEADER START
32449e17fSsherrym  *
42449e17fSsherrym  * The contents of this file are subject to the terms of the
52449e17fSsherrym  * Common Development and Distribution License (the "License").
62449e17fSsherrym  * You may not use this file except in compliance with the License.
72449e17fSsherrym  *
82449e17fSsherrym  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
92449e17fSsherrym  * or http://www.opensolaris.org/os/licensing.
102449e17fSsherrym  * See the License for the specific language governing permissions
112449e17fSsherrym  * and limitations under the License.
122449e17fSsherrym  *
132449e17fSsherrym  * When distributing Covered Code, include this CDDL HEADER in each
142449e17fSsherrym  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
152449e17fSsherrym  * If applicable, add the following below this CDDL HEADER, with the
162449e17fSsherrym  * fields enclosed by brackets "[]" replaced with your own identifying
172449e17fSsherrym  * information: Portions Copyright [yyyy] [name of copyright owner]
182449e17fSsherrym  *
192449e17fSsherrym  * CDDL HEADER END
202449e17fSsherrym  */
212449e17fSsherrym 
222449e17fSsherrym /*
239c1d4953SPeter Telford  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
242449e17fSsherrym  * Use is subject to license terms.
25*d32f26eeSAndy Fiddaman  *
26*d32f26eeSAndy Fiddaman  * Copyright 2023 Oxide Computer Company
272449e17fSsherrym  */
282449e17fSsherrym 
292449e17fSsherrym #include <sys/types.h>
302449e17fSsherrym #include <sys/file.h>
312449e17fSsherrym #include <sys/errno.h>
322449e17fSsherrym #include <sys/open.h>
332449e17fSsherrym #include <sys/cred.h>
342449e17fSsherrym #include <sys/conf.h>
352449e17fSsherrym #include <sys/stat.h>
362449e17fSsherrym #include <sys/policy.h>
372449e17fSsherrym #include <sys/processor.h>
382449e17fSsherrym #include <sys/kmem.h>
392449e17fSsherrym #include <sys/modctl.h>
402449e17fSsherrym #include <sys/ddi.h>
412449e17fSsherrym #include <sys/sunddi.h>
422449e17fSsherrym 
432449e17fSsherrym #include <sys/auxv.h>
442449e17fSsherrym #include <sys/ucode.h>
452449e17fSsherrym #include <sys/systeminfo.h>
462449e17fSsherrym #include <sys/x86_archext.h>
472449e17fSsherrym 
482449e17fSsherrym static dev_info_t *ucode_devi;
492449e17fSsherrym static uint32_t ucode_max_combined_size;
502449e17fSsherrym static kmutex_t ucode_update_lock;
512449e17fSsherrym 
522449e17fSsherrym static int
ucode_getinfo(dev_info_t * devi,ddi_info_cmd_t cmd,void * arg,void ** result)532449e17fSsherrym ucode_getinfo(dev_info_t *devi, ddi_info_cmd_t cmd, void *arg, void **result)
542449e17fSsherrym {
552449e17fSsherrym 	switch (cmd) {
562449e17fSsherrym 	case DDI_INFO_DEVT2DEVINFO:
572449e17fSsherrym 	case DDI_INFO_DEVT2INSTANCE:
582449e17fSsherrym 		break;
592449e17fSsherrym 	default:
602449e17fSsherrym 		return (DDI_FAILURE);
612449e17fSsherrym 	}
622449e17fSsherrym 
632449e17fSsherrym 	switch (getminor((dev_t)arg)) {
642449e17fSsherrym 	case UCODE_MINOR:
652449e17fSsherrym 		break;
662449e17fSsherrym 	default:
672449e17fSsherrym 		return (DDI_FAILURE);
682449e17fSsherrym 	}
692449e17fSsherrym 
702449e17fSsherrym 	if (cmd == DDI_INFO_DEVT2INSTANCE)
712449e17fSsherrym 		*result = 0;
722449e17fSsherrym 	else
732449e17fSsherrym 		*result = ucode_devi;
742449e17fSsherrym 	return (DDI_SUCCESS);
752449e17fSsherrym }
762449e17fSsherrym 
772449e17fSsherrym static int
ucode_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)782449e17fSsherrym ucode_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
792449e17fSsherrym {
802449e17fSsherrym 	ASSERT(cmd != DDI_RESUME);
812449e17fSsherrym 
822449e17fSsherrym 	switch (cmd) {
832449e17fSsherrym 	case DDI_RESUME:
842449e17fSsherrym 		return (DDI_SUCCESS);
852449e17fSsherrym 
862449e17fSsherrym 	case DDI_ATTACH:
872449e17fSsherrym 		ucode_devi = devi;
882449e17fSsherrym 		ucode_max_combined_size = UCODE_MAX_COMBINED_SIZE;
892449e17fSsherrym 
902449e17fSsherrym 		if (ddi_create_minor_node(devi, UCODE_NODE_NAME, S_IFCHR,
912449e17fSsherrym 		    UCODE_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS) {
922449e17fSsherrym 			cmn_err(CE_WARN, "%s: Unable to create minor node",
932449e17fSsherrym 			    UCODE_NODE_NAME);
942449e17fSsherrym 			return (DDI_FAILURE);
952449e17fSsherrym 		}
962449e17fSsherrym 		ddi_report_dev(devi);
972449e17fSsherrym 		return (DDI_SUCCESS);
982449e17fSsherrym 
992449e17fSsherrym 	default:
1002449e17fSsherrym 		return (DDI_FAILURE);
1012449e17fSsherrym 	}
1022449e17fSsherrym }
1032449e17fSsherrym 
1042449e17fSsherrym static int
ucode_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)1052449e17fSsherrym ucode_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
1062449e17fSsherrym {
1072449e17fSsherrym 	/*
1082449e17fSsherrym 	 * The power management and DR framework should never invoke this
1092449e17fSsherrym 	 * driver with DDI_SUSPEND because the ucode pseudo device does not
1102449e17fSsherrym 	 * have a reg property or hardware binding.  However, we will return
1112449e17fSsherrym 	 * DDI_SUCCESS so that in the unlikely event that it does get
1122449e17fSsherrym 	 * called, the system will still suspend and resume.
1132449e17fSsherrym 	 */
1142449e17fSsherrym 	ASSERT(cmd != DDI_SUSPEND);
1152449e17fSsherrym 
1162449e17fSsherrym 	switch (cmd) {
1172449e17fSsherrym 	case DDI_SUSPEND:
1182449e17fSsherrym 		return (DDI_SUCCESS);
1192449e17fSsherrym 
1202449e17fSsherrym 	case DDI_DETACH:
1212449e17fSsherrym 		ddi_remove_minor_node(devi, NULL);
1222449e17fSsherrym 		ucode_devi = NULL;
1232449e17fSsherrym 		return (DDI_SUCCESS);
1242449e17fSsherrym 
1252449e17fSsherrym 	default:
1262449e17fSsherrym 		return (DDI_FAILURE);
1272449e17fSsherrym 	}
1282449e17fSsherrym }
1292449e17fSsherrym 
1302449e17fSsherrym static int
ucode_open(dev_t * dev,int flag,int otyp,cred_t * cr)1312449e17fSsherrym ucode_open(dev_t *dev, int flag, int otyp, cred_t *cr)
1322449e17fSsherrym {
1332449e17fSsherrym 	return (getminor(*dev) == UCODE_MINOR ? 0 : ENXIO);
1342449e17fSsherrym }
1352449e17fSsherrym 
1362449e17fSsherrym static int
ucode_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * cr,int * rval)1372449e17fSsherrym ucode_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval)
1382449e17fSsherrym {
1392449e17fSsherrym 	switch (cmd) {
1402449e17fSsherrym 	case UCODE_GET_VERSION: {
1412449e17fSsherrym 		int size;
1422449e17fSsherrym 		uint32_t *revp, *rev_array;
1439c1d4953SPeter Telford 		size_t bufsz = NCPU * sizeof (*revp);
1442449e17fSsherrym 		ucode_errno_t rc = EM_OK;
1452449e17fSsherrym 
1462449e17fSsherrym 		STRUCT_DECL(ucode_get_rev_struct, h);
1472449e17fSsherrym 		STRUCT_INIT(h, mode);
1482449e17fSsherrym 		if (ddi_copyin((void *)arg,
1492449e17fSsherrym 		    STRUCT_BUF(h), STRUCT_SIZE(h), mode))
1502449e17fSsherrym 			return (EFAULT);
1512449e17fSsherrym 
1529c1d4953SPeter Telford 		if ((size = STRUCT_FGET(h, ugv_size)) > NCPU || size < 0)
1532449e17fSsherrym 			return (EINVAL);
1542449e17fSsherrym 
1559c1d4953SPeter Telford 		if (size == 0)
1569c1d4953SPeter Telford 			return (0);
1579c1d4953SPeter Telford 
1582449e17fSsherrym 		if ((rev_array = STRUCT_FGETP(h, ugv_rev)) == NULL)
1592449e17fSsherrym 			return (EINVAL);
1602449e17fSsherrym 
1612449e17fSsherrym 		size *= sizeof (uint32_t);
1622449e17fSsherrym 
1639c1d4953SPeter Telford 		/* Can't rely on caller for kernel's buffer size. */
1649c1d4953SPeter Telford 		revp = kmem_zalloc(bufsz, KM_SLEEP);
1652449e17fSsherrym 		if (ddi_copyin((void *)rev_array, revp, size, mode) != 0) {
1669c1d4953SPeter Telford 			kmem_free(revp, bufsz);
1672449e17fSsherrym 			return (EINVAL);
1682449e17fSsherrym 		}
1692449e17fSsherrym 
1702449e17fSsherrym 		rc = ucode_get_rev(revp);
1712449e17fSsherrym 
1722449e17fSsherrym 		STRUCT_FSET(h, ugv_errno, rc);
1732449e17fSsherrym 
1742449e17fSsherrym 		if (ddi_copyout(revp, (void *)rev_array, size, mode) != 0) {
1759c1d4953SPeter Telford 			kmem_free(revp, bufsz);
1762449e17fSsherrym 			return (EFAULT);
1772449e17fSsherrym 		}
1782449e17fSsherrym 
1799c1d4953SPeter Telford 		kmem_free(revp, bufsz);
1802449e17fSsherrym 
1812449e17fSsherrym 		if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
1822449e17fSsherrym 		    STRUCT_SIZE(h), mode))
1832449e17fSsherrym 			return (EFAULT);
1842449e17fSsherrym 
1852449e17fSsherrym 		return (0);
1862449e17fSsherrym 	}
1872449e17fSsherrym 
1882449e17fSsherrym 	case UCODE_UPDATE: {
1892449e17fSsherrym 		int size;
1902449e17fSsherrym 		uint8_t *ucodep, *uw_ucode;
1912449e17fSsherrym 		ucode_errno_t rc = EM_OK;
1922449e17fSsherrym 
1932449e17fSsherrym 		/*
1942449e17fSsherrym 		 * Requires all privilege.
1952449e17fSsherrym 		 */
1962449e17fSsherrym 		if (cr && secpolicy_ucode_update(cr))
1972449e17fSsherrym 			return (EPERM);
1982449e17fSsherrym 
1992449e17fSsherrym 		STRUCT_DECL(ucode_write_struct, h);
2002449e17fSsherrym 
2012449e17fSsherrym 		STRUCT_INIT(h, mode);
2022449e17fSsherrym 		if (ddi_copyin((void *)arg, STRUCT_BUF(h), STRUCT_SIZE(h),
2032449e17fSsherrym 		    mode))
2042449e17fSsherrym 			return (EFAULT);
2052449e17fSsherrym 
2062449e17fSsherrym 		/*
2072449e17fSsherrym 		 * We allow the size of the combined microcode file to be up to
2082449e17fSsherrym 		 * ucode_max_combined_size.  It is initialized to
2092449e17fSsherrym 		 * UCODE_MAX_COMBINED_SIZE, and can be patched if necessary.
2102449e17fSsherrym 		 */
2112449e17fSsherrym 		size = STRUCT_FGET(h, uw_size);
2122449e17fSsherrym 		if (size > ucode_max_combined_size || size == 0)
2132449e17fSsherrym 			return (EINVAL);
2142449e17fSsherrym 
2152449e17fSsherrym 		if ((uw_ucode = STRUCT_FGETP(h, uw_ucode)) == NULL)
2162449e17fSsherrym 			return (EINVAL);
2172449e17fSsherrym 
2182449e17fSsherrym 		ucodep = kmem_zalloc(size, KM_SLEEP);
2192449e17fSsherrym 		if (ddi_copyin((void *)uw_ucode, ucodep, size, mode) != 0) {
2202449e17fSsherrym 			kmem_free(ucodep, size);
2212449e17fSsherrym 			return (EFAULT);
2222449e17fSsherrym 		}
2232449e17fSsherrym 
224*d32f26eeSAndy Fiddaman 		if ((rc = ucode_validate(ucodep, size)) != EM_OK) {
2252449e17fSsherrym 			kmem_free(ucodep, size);
2262449e17fSsherrym 			STRUCT_FSET(h, uw_errno, rc);
2272449e17fSsherrym 			if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
2282449e17fSsherrym 			    STRUCT_SIZE(h), mode))
2292449e17fSsherrym 				return (EFAULT);
2302449e17fSsherrym 			return (0);
2312449e17fSsherrym 		}
2322449e17fSsherrym 
2332449e17fSsherrym 		mutex_enter(&ucode_update_lock);
2342449e17fSsherrym 		rc = ucode_update(ucodep, size);
2352449e17fSsherrym 		mutex_exit(&ucode_update_lock);
2362449e17fSsherrym 
2372449e17fSsherrym 		kmem_free(ucodep, size);
2382449e17fSsherrym 
2392449e17fSsherrym 		STRUCT_FSET(h, uw_errno, rc);
2402449e17fSsherrym 		if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
2412449e17fSsherrym 		    STRUCT_SIZE(h), mode))
2422449e17fSsherrym 			return (EFAULT);
2432449e17fSsherrym 
2442449e17fSsherrym 		/*
2452449e17fSsherrym 		 * Even if rc is not EM_OK, it is a successful operation
2462449e17fSsherrym 		 * from ioctl()'s perspective.  We return the detailed error
2472449e17fSsherrym 		 * code via the ucode_write_struct data structure.
2482449e17fSsherrym 		 */
2492449e17fSsherrym 		return (0);
2502449e17fSsherrym 	}
2512449e17fSsherrym 
2522449e17fSsherrym 
2532449e17fSsherrym 	default:
2542449e17fSsherrym 		return (ENOTTY);
2552449e17fSsherrym 	}
2562449e17fSsherrym }
2572449e17fSsherrym 
2582449e17fSsherrym static struct cb_ops ucode_cb_ops = {
2592449e17fSsherrym 	ucode_open,
2602449e17fSsherrym 	nulldev,	/* close */
2612449e17fSsherrym 	nodev,		/* strategy */
2622449e17fSsherrym 	nodev,		/* print */
2632449e17fSsherrym 	nodev,		/* dump */
2642449e17fSsherrym 	nodev,		/* read */
2652449e17fSsherrym 	nodev,		/* write */
2662449e17fSsherrym 	ucode_ioctl,
2672449e17fSsherrym 	nodev,		/* devmap */
2682449e17fSsherrym 	nodev,		/* mmap */
2692449e17fSsherrym 	nodev,		/* segmap */
2702449e17fSsherrym 	nochpoll,	/* poll */
2712449e17fSsherrym 	ddi_prop_op,
2722449e17fSsherrym 	NULL,
2732449e17fSsherrym 	D_64BIT | D_NEW | D_MP
2742449e17fSsherrym };
2752449e17fSsherrym 
2762449e17fSsherrym static struct dev_ops ucode_dv_ops = {
2772449e17fSsherrym 	DEVO_REV,
2782449e17fSsherrym 	0,
2792449e17fSsherrym 	ucode_getinfo,
28019397407SSherry Moore 	nulldev,		/* identify */
28119397407SSherry Moore 	nulldev,		/* probe */
2822449e17fSsherrym 	ucode_attach,
2832449e17fSsherrym 	ucode_detach,
28419397407SSherry Moore 	nodev,			/* reset */
2852449e17fSsherrym 	&ucode_cb_ops,
28619397407SSherry Moore 	(struct bus_ops *)0,
28719397407SSherry Moore 	NULL,			/* power */
28819397407SSherry Moore 	ddi_quiesce_not_needed,		/* quiesce */
2892449e17fSsherrym };
2902449e17fSsherrym 
2912449e17fSsherrym static struct modldrv modldrv = {
2922449e17fSsherrym 	&mod_driverops,
293613b2871SRichard Bean 	"ucode driver",
2942449e17fSsherrym 	&ucode_dv_ops
2952449e17fSsherrym };
2962449e17fSsherrym 
2972449e17fSsherrym static struct modlinkage modl = {
2982449e17fSsherrym 	MODREV_1,
2992449e17fSsherrym 	&modldrv
3002449e17fSsherrym };
3012449e17fSsherrym 
3022449e17fSsherrym int
_init(void)3032449e17fSsherrym _init(void)
3042449e17fSsherrym {
3052449e17fSsherrym 	int rc;
3062449e17fSsherrym 
3072449e17fSsherrym 	if ((rc = mod_install(&modl)) != 0)
3082449e17fSsherrym 		return (rc);
3092449e17fSsherrym 
3102449e17fSsherrym 	mutex_init(&ucode_update_lock, NULL, MUTEX_DRIVER, NULL);
3112449e17fSsherrym 
3122449e17fSsherrym 	return (0);
3132449e17fSsherrym }
3142449e17fSsherrym 
3152449e17fSsherrym int
_fini(void)3162449e17fSsherrym _fini(void)
3172449e17fSsherrym {
3182449e17fSsherrym 	int rc;
3192449e17fSsherrym 
3202449e17fSsherrym 	if ((rc = mod_remove(&modl)) != 0)
3212449e17fSsherrym 		return (rc);
3222449e17fSsherrym 
3232449e17fSsherrym 	mutex_destroy(&ucode_update_lock);
3242449e17fSsherrym 
3252449e17fSsherrym 	return (0);
3262449e17fSsherrym }
3272449e17fSsherrym 
3282449e17fSsherrym int
_info(struct modinfo * modinfo)3292449e17fSsherrym _info(struct modinfo *modinfo)
3302449e17fSsherrym {
3312449e17fSsherrym 	return (mod_info(&modl, modinfo));
3322449e17fSsherrym }
333