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