changeset 20101:1a5588aae48c

13144 refactor amdf17nbdf into a nexus 13145 rewrite amdf17nbdf to use the ksensor framework 13146 Want a driver for AMD SMN user access Reviewed by: Patrick Mooney <pmooney@pfmooney.com> Reviewed by: Mike Zeller <mike.zeller@joyent.com> Reviewed by: Robert French <robert@robertdfrench.me> Approved by: Richard Lowe <richlowe@richlowe.net>
author Robert Mustacchi <rm@fingolfin.org>
date Wed, 08 Apr 2020 21:35:09 -0700
parents 6d24e460e13d
children 040be0b6e0f3
files usr/src/cmd/Makefile usr/src/cmd/usmn/Makefile usr/src/cmd/usmn/usmn.c usr/src/man/man7d/Makefile usr/src/man/man7d/amdf17nbdf.7d usr/src/man/man7d/amdnbtemp.7d usr/src/man/man7d/amdzen.7d usr/src/man/man7d/smntemp.7d usr/src/man/man7d/usmn.7d usr/src/pkg/manifests/driver-cpu-amd-zen.mf usr/src/pkg/manifests/driver-cpu-sensor.mf usr/src/pkg/manifests/driver-developer-amd-zen.mf usr/src/uts/common/io/usb/hcd/xhci/xhci.c usr/src/uts/common/os/policy.c usr/src/uts/common/sys/policy.h usr/src/uts/intel/Makefile.files usr/src/uts/intel/Makefile.intel usr/src/uts/intel/Makefile.rules usr/src/uts/intel/amdf17nbdf/Makefile usr/src/uts/intel/amdzen/Makefile usr/src/uts/intel/amdzen_stub/Makefile usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c usr/src/uts/intel/io/amdzen/amdzen.c usr/src/uts/intel/io/amdzen/amdzen.conf usr/src/uts/intel/io/amdzen/amdzen.h usr/src/uts/intel/io/amdzen/amdzen_client.h usr/src/uts/intel/io/amdzen/amdzen_stub.c usr/src/uts/intel/io/amdzen/smntemp.c usr/src/uts/intel/io/amdzen/usmn.c usr/src/uts/intel/io/amdzen/usmn.h usr/src/uts/intel/smntemp/Makefile usr/src/uts/intel/usmn/Makefile
diffstat 32 files changed, 2632 insertions(+), 1165 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/Makefile	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/cmd/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -499,6 +499,7 @@
 	rdmsr		\
 	rtc		\
 	ucodeadm	\
+	usmn		\
 	xhci		\
 	xvm
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/usmn/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,32 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+PROG= usmn
+
+include ../Makefile.cmd
+
+ROOTCMDDIR = $(ROOTLIB)
+CPPFLAGS += -I$(SRC)/uts/intel/io/amdzen
+CFLAGS += $(CCVERBOSE)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTCMD)
+
+clean:
+
+include ../Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/usmn/usmn.c	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,100 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * Read and write to the AMD SMN.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <usmn.h>
+
+static boolean_t
+usmn_read(int fd, const char *addr)
+{
+	unsigned long long l;
+	char *eptr;
+	usmn_reg_t usr;
+
+	errno = 0;
+	l = strtoull(addr, &eptr, 16);
+	if (errno != 0 || *eptr != '\0' || l > UINT32_MAX) {
+		warnx("failed to parse %s: invalid string or address", addr);
+		return (B_FALSE);
+	}
+
+	usr.usr_addr = (uint32_t)l;
+	usr.usr_data = 0;
+
+	if (ioctl(fd, USMN_READ, &usr) != 0) {
+		warn("failed to read SMN at 0x%x", usr.usr_addr);
+		return (B_FALSE);
+	}
+
+	(void) printf("0x%x: 0x%x\n", usr.usr_addr, usr.usr_data);
+	return (B_TRUE);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int i, c, fd, ret;
+	const char *device = NULL;
+
+	while ((c = getopt(argc, argv, "d:")) != -1) {
+		switch (c) {
+		case 'd':
+			device = optarg;
+			break;
+		default:
+			(void) fprintf(stderr, "Usage: usmn -d device addr "
+			    "[addr]...\n"
+			    "Note: All addresses are interpreted as hex\n");
+			return (2);
+		}
+	}
+
+	if (device == NULL) {
+		errx(EXIT_FAILURE, "missing required device");
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0) {
+		errx(EXIT_FAILURE, "missing registers to read");
+	}
+
+	if ((fd = open(device, O_RDONLY)) < 0) {
+		err(EXIT_FAILURE, "failed to open %s", device);
+	}
+
+	ret = EXIT_SUCCESS;
+	for (i = 0; i < argc; i++) {
+		if (!usmn_read(fd, argv[i])) {
+			ret = EXIT_FAILURE;
+		}
+	}
+
+	(void) close(fd);
+
+	return (ret);
+}
--- a/usr/src/man/man7d/Makefile	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/man/man7d/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -180,8 +180,8 @@
 
 i386_MANFILES=	ahci.7d		\
 		amd8111s.7d	\
-		amdf17nbdf.7d	\
 		amdnbtemp.7d	\
+		amdzen.7d	\
 		amr.7d		\
 		arcmsr.7d	\
 		arn.7d		\
@@ -235,9 +235,11 @@
 		si3124.7d	\
 		skd.7d		\
 		smbios.7d	\
+		smntemp.7d	\
 		uath.7d		\
 		ural.7d		\
 		urtw.7d		\
+		usmn.7d		\
 		vioblk.7d	\
 		vioif.7d	\
 		virtio.7d	\
@@ -258,9 +260,13 @@
 
 sparc_MANLINKS=	drmach.7d
 
+i386_MANLINKS=	amdzen_stub.7d
+
 MANFILES =	$(_MANFILES) $($(MACH)_MANFILES)
 MANLINKS =	$(_MANLINKS) $($(MACH)_MANLINKS)
 
+amdzen_stub.7d	:= LINKSRC = amdzen.7d
+
 drmach.7d	:= LINKSRC = dr.7d
 
 fdc.7d		:= LINKSRC = fd.7d
--- a/usr/src/man/man7d/amdf17nbdf.7d	Sat Oct 03 11:05:18 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-.\"
-.\" This file and its contents are supplied under the terms of the
-.\" Common Development and Distribution License ("CDDL"), version 1.0.
-.\" You may only use this file in accordance with the terms of version
-.\" 1.0 of the CDDL.
-.\"
-.\" A full copy of the text of the CDDL should have accompanied this
-.\" source.  A copy of the CDDL is also available via the Internet at
-.\" http://www.illumos.org/license/CDDL.
-.\"
-.\"
-.\" Copyright 2019, Joyent, Inc.
-.\"
-.Dd November 16, 2019
-.Dt AMDF17NBDF 7D
-.Os
-.Sh NAME
-.Nm amdf17nbdf
-.Nd AMD Family 17h Northbridge and Data Fabric Driver
-.Sh SYNOPSIS
-.Pa /dev/sensors/temperature/cpu/*
-.Sh DESCRIPTION
-The
-.Nm
-driver provides the system access to the Northbridge and Data Fabric
-devices on AMD Family 17h
-.Pq Zen, Zen+, and Zen 2
-processors allowing the operating system to communicate with the system
-management unit
-.Pq SMU .
-.Pp
-From this, the driver exposes temperature sensors.
-On Family 17h systems, temperature sensors exist for each Zeppelin die,
-of which there may be multiple in a single package.
-This means that each sensor covers more than one core.
-.Pp
-Temperature information is available to the system via the fault
-management architecture
-.Pq FMA .
-The file system location and programming interface to the
-.Nm
-driver are considered
-.Sy Volatile ,
-subject to change without notice, and should not be used directly.
-Raw temperature information can be dumped through the FMA developer
-utility fmtopo.
-.Sh SEE ALSO
-.Xr fmadm 1M
-.Rs
-.%A AMD
-.%B Open-Source Register Reference For AMD Family 17h Processors Models 00h-2Fh
-.%D July, 2018
-.Re
--- a/usr/src/man/man7d/amdnbtemp.7d	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/man/man7d/amdnbtemp.7d	Wed Apr 08 21:35:09 2020 -0700
@@ -61,11 +61,11 @@
 For AMD Family 17h
 .Pq Zen
 processors, the
-.Xr amdf17nbdf 7D
+.Xr smntemp 7D
 driver provides access to the temperature sensors.
 .Sh SEE ALSO
 .Xr fmadm 1M ,
-.Xr amdf17nbdf 7D
+.Xr smntemp 7D
 .Rs
 .%A AMD
 .%B BIOS and Kernel Developer’s Guide (BKDG) for AMD Family 16h Models 00h-0Fh Processors
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/man/man7d/amdzen.7d	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,48 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2020 Oxide Computer Company
+.\"
+.Dd September 1, 2020
+.Dt AMDZEN 7D
+.Os
+.Sh NAME
+.Nm amdzen ,
+.Nm amdzen_stub
+.Nd AMD Zen Nexus Driver
+.Sh DESCRIPTION
+The
+.Sy amdzen
+driver provides access to the AMD Zen, Zen+, and Zen 2
+.Pq Family 17h
+processor families Northbridge, Data Fabric, and System Management Network
+.Pq SMN .
+This driver is a nexus driver and facilitates access to these devices
+between various other devices such as
+.Xr smntemp 7D .
+The various processor devices that make up the northbridge and data
+fabric have the
+.Sy amdzen_stub
+driver attached to them.
+The different devices are all amalgamated and a single uniform view is
+provided by the
+.Sy amdzen
+driver.
+.Sh ARCHITECTURE
+The
+.Sy amdzen
+and
+.Sy amdzen_stub
+drivers are limited to
+.Sy x86
+platforms with AMD Family 17h processors.
+.Sh SEE ALSO
+.Xr smntemp 7D
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/man/man7d/smntemp.7d	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,56 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2019, Joyent, Inc.
+.\" Copyright 2020 Oxide Computer Company
+.\"
+.Dd September 1, 2020
+.Dt SMNTEMP 7D
+.Os
+.Sh NAME
+.Nm smntemp
+.Nd AMD SMN Temperature Sensor Driver
+.Sh SYNOPSIS
+.Pa /dev/sensors/temperature/cpu/*
+.Sh DESCRIPTION
+The
+.Nm
+driver provides the system access to the temperature sensor found in the
+AMD System Management Network
+.Pq SMN
+on AMD Family 17h
+.Pq Zen, Zen+, and Zen 2
+processors.
+.Pp
+From this, the driver exposes temperature sensors.
+On Family 17h systems, temperature sensors exist for each Zeppelin die,
+of which there may be multiple in a single package.
+This means that each sensor covers more than one core.
+.Pp
+Temperature information is available to the system via the fault
+management architecture
+.Pq FMA .
+The file system location and programming interface to the
+.Nm
+driver are considered
+.Sy Volatile ,
+subject to change without notice, and should not be used directly.
+Raw temperature information can be dumped through the FMA developer
+utility fmtopo.
+.Sh SEE ALSO
+.Xr fmadm 1M ,
+.Xr amdnbtemp 7D ,
+.Xr amdzen 7D
+.Rs
+.%A AMD
+.%B Open-Source Register Reference For AMD Family 17h Processors Models 00h-2Fh
+.%D July, 2018
+.Re
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/man/man7d/usmn.7d	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,36 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2020 Oxide Computer Company
+.\"
+.Dd September 1, 2020
+.Dt USMN 7D
+.Os
+.Sh NAME
+.Nm usmn
+.Nd AMD SMN user access driver
+.Sh SYNOPSIS
+.Pa /devices/pseudo/amdzen@0/usmn@2:usmn.*
+.Sh DESCRIPTION
+The
+.Nm
+driver provides the ability to read data from the AMD system management
+network
+.Pq SMN
+on AMD Family 17h
+.Pq Zen, Zen+, and Zen 2
+processors.
+.Pp
+This driver is intended strictly for facilitating platform development
+and is not recommended for systems that aren't doing kernel development
+on AMD Zen platforms.
+.Sh SEE ALSO
+.Xr amdzen 7D
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkg/manifests/driver-cpu-amd-zen.mf	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,70 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/driver/cpu/amd/zen@$(PKGVERS)
+set name=pkg.description value="AMD Zen Nexus Driver"
+set name=pkg.summary value="AMD Zen Nexus Driver"
+set name=info.classification \
+    value=org.opensolaris.category.2008:System/Hardware
+set name=variant.arch value=i386
+dir path=kernel group=sys
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+dir path=usr/share/man
+dir path=usr/share/man/man7d
+driver name=amdzen
+driver name=amdzen_stub \
+    alias=pci1022,1440,p \
+    alias=pci1022,1441,p \
+    alias=pci1022,1442,p \
+    alias=pci1022,1443,p \
+    alias=pci1022,1444,p \
+    alias=pci1022,1445,p \
+    alias=pci1022,1446,p \
+    alias=pci1022,1447,p \
+    alias=pci1022,1450,p \
+    alias=pci1022,1460,p \
+    alias=pci1022,1461,p \
+    alias=pci1022,1462,p \
+    alias=pci1022,1463,p \
+    alias=pci1022,1464,p \
+    alias=pci1022,1465,p \
+    alias=pci1022,1466,p \
+    alias=pci1022,1467,p \
+    alias=pci1022,1480,p \
+    alias=pci1022,1490,p \
+    alias=pci1022,1491,p \
+    alias=pci1022,1492,p \
+    alias=pci1022,1493,p \
+    alias=pci1022,1494,p \
+    alias=pci1022,1495,p \
+    alias=pci1022,1496,p \
+    alias=pci1022,1497,p \
+    alias=pci1022,15d0,p \
+    alias=pci1022,15e8,p \
+    alias=pci1022,15e9,p \
+    alias=pci1022,15ea,p \
+    alias=pci1022,15eb,p \
+    alias=pci1022,15ec,p \
+    alias=pci1022,15ed,p \
+    alias=pci1022,15ee,p \
+    alias=pci1022,15ef,p
+file path=kernel/drv/$(ARCH64)/amdzen group=sys
+file path=kernel/drv/$(ARCH64)/amdzen_stub group=sys
+file path=kernel/drv/amdzen.conf group=sys
+file path=usr/share/man/man7d/amdzen.7d
+license lic_CDDL license=lic_CDDL
+link path=usr/share/man/man7d/amdzen_stub.7d target=amdzen.7d
--- a/usr/src/pkg/manifests/driver-cpu-sensor.mf	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/pkg/manifests/driver-cpu-sensor.mf	Wed Apr 08 21:35:09 2020 -0700
@@ -25,14 +25,6 @@
 dir path=kernel/drv/$(ARCH64) group=sys
 dir path=usr/share/man
 dir path=usr/share/man/man7d
-driver name=amdf17nbdf \
-    alias=pci1022,1440,p \
-    alias=pci1022,1450,p \
-    alias=pci1022,1460,p \
-    alias=pci1022,1480,p \
-    alias=pci1022,1490,p \
-    alias=pci1022,15d0,p \
-    alias=pci1022,15e8,p
 driver name=amdnbtemp \
     alias=pci1022,1203,p \
     alias=pci1022,1303,p \
@@ -55,14 +47,16 @@
     alias=pci8086,a231,p \
     alias=pci8086,a2b1,p \
     alias=pci8086,a379,p
-file path=kernel/drv/$(ARCH64)/amdf17nbdf group=sys
+driver name=smntemp alias=smntemp
 file path=kernel/drv/$(ARCH64)/amdnbtemp group=sys
 file path=kernel/drv/$(ARCH64)/coretemp group=sys
 file path=kernel/drv/$(ARCH64)/pchtemp group=sys
+file path=kernel/drv/$(ARCH64)/smntemp group=sys
 file path=kernel/drv/coretemp.conf group=sys
-file path=usr/share/man/man7d/amdf17nbdf.7d
 file path=usr/share/man/man7d/amdnbtemp.7d
 file path=usr/share/man/man7d/coretemp.7d
 file path=usr/share/man/man7d/pchtemp.7d
+file path=usr/share/man/man7d/smntemp.7d
 license lic_CDDL license=lic_CDDL
+depend fmri=driver/cpu/amd/zen type=require
 depend fmri=system/ksensor type=require
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkg/manifests/driver-developer-amd-zen.mf	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,33 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/driver/developer/amd/zen@$(PKGVERS)
+set name=pkg.description value="AMD Zen Developer Drivers"
+set name=pkg.summary value="Misc. AMD Zen Drivers for Platform Development"
+set name=info.classification \
+    value=org.opensolaris.category.2008:System/Hardware
+set name=variant.arch value=i386
+dir path=kernel group=sys
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+dir path=usr/lib
+dir path=usr/share/man
+dir path=usr/share/man/man7d
+driver name=usmn
+file path=kernel/drv/$(ARCH64)/usmn group=sys
+file path=usr/lib/usmn mode=0555
+file path=usr/share/man/man7d/usmn.7d
+license lic_CDDL license=lic_CDDL
--- a/usr/src/uts/common/io/usb/hcd/xhci/xhci.c	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/common/io/usb/hcd/xhci/xhci.c	Wed Apr 08 21:35:09 2020 -0700
@@ -1911,7 +1911,7 @@
 		xhci_t *xhcip = ddi_get_soft_state(xhci_soft_state,
 		    getminor(dev) & ~HUBD_IS_ROOT_HUB);
 
-		if (secpolicy_xhci(credp) != 0 ||
+		if (secpolicy_hwmanip(credp) != 0 ||
 		    crgetzoneid(credp) != GLOBAL_ZONEID)
 			return (EPERM);
 
--- a/usr/src/uts/common/os/policy.c	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/common/os/policy.c	Wed Apr 08 21:35:09 2020 -0700
@@ -1364,7 +1364,7 @@
  * this is required because vop_access function should lock the
  * node for reading.  A three argument function should be defined
  * which accepts the following argument:
- * 	A pointer to the internal "node" type (inode *)
+ *	A pointer to the internal "node" type (inode *)
  *	vnode access bits (VREAD|VWRITE|VEXEC)
  *	a pointer to the credential
  *
@@ -1436,8 +1436,8 @@
 		 *
 		 * If you are the file owner:
 		 *	chown to other uid		FILE_CHOWN_SELF
-		 *	chown to gid (non-member) 	FILE_CHOWN_SELF
-		 *	chown to gid (member) 		<none>
+		 *	chown to gid (non-member)	FILE_CHOWN_SELF
+		 *	chown to gid (member)		<none>
 		 *
 		 * Instead of PRIV_FILE_CHOWN_SELF, FILE_CHOWN is also
 		 * acceptable but the first one is reported when debugging.
@@ -2409,13 +2409,14 @@
 }
 
 /*
- * secpolicy_xhci
+ * secpolicy_hwmanip
  *
- * Determine if the subject can observe and manipulate the xhci driver with a
- * dangerous blunt hammer.  Requires all privileges.
+ * Determine if the subject can observe and manipulate a hardware device with a
+ * dangerous blunt hammer, often suggests they can do something destructive.
+ * Requires all privileges.
  */
 int
-secpolicy_xhci(const cred_t *cr)
+secpolicy_hwmanip(const cred_t *cr)
 {
 	return (secpolicy_require_set(cr, PRIV_FULLSET, NULL, KLPDARG_NONE));
 }
--- a/usr/src/uts/common/sys/policy.h	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/common/sys/policy.h	Wed Apr 08 21:35:09 2020 -0700
@@ -53,12 +53,12 @@
  * priv_policy_choice
  *		determines extend of operation
  *		audit on success
- * 		returns a boolean_t indicating success (B_TRUE) or failure.
+ *		returns a boolean_t indicating success (B_TRUE) or failure.
  *
  * priv_policy_only
  *		when auditing is in appropriate (interrupt context)
  *		to determine context of operation
- * 		returns a boolean_t indicating success (B_TRUE) or failure.
+ *		returns a boolean_t indicating success (B_TRUE) or failure.
  *
  */
 int priv_policy(const cred_t *, int, boolean_t, int, const char *);
@@ -165,7 +165,7 @@
 int secpolicy_vnode_setids_setgids(const cred_t *, gid_t);
 int secpolicy_vnode_stky_modify(const cred_t *);
 int secpolicy_vscan(const cred_t *);
-int secpolicy_xhci(const cred_t *);
+int secpolicy_hwmanip(const cred_t *);
 int secpolicy_zinject(const cred_t *);
 int secpolicy_zfs(const cred_t *);
 int secpolicy_ucode_update(const cred_t *);
--- a/usr/src/uts/intel/Makefile.files	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/intel/Makefile.files	Wed Apr 08 21:35:09 2020 -0700
@@ -342,3 +342,11 @@
 # AMD Family 10h-16h temperature driver
 #
 AMDNBTEMP_OBJS = amdnbtemp.o
+
+#
+# AMD Zen Nexus Driver
+#
+AMDZEN_OBJS = amdzen.o
+AMDZEN_STUB_OBJS = amdzen_stub.o
+SMNTEMP_OBJS = smntemp.o
+USMN_OBJS = usmn.o
--- a/usr/src/uts/intel/Makefile.intel	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/intel/Makefile.intel	Wed Apr 08 21:35:09 2020 -0700
@@ -766,7 +766,14 @@
 #	Sensor related drivers
 #
 DRV_KMODS	+= ksensor ksensor_test
-DRV_KMODS	+= amdf17nbdf
 DRV_KMODS	+= coretemp
 DRV_KMODS	+= pchtemp
 DRV_KMODS	+= amdnbtemp
+DRV_KMODS	+= smntemp
+
+#
+#	AMD Zen Nexus driver and Related
+#
+DRV_KMODS	+= amdzen
+DRV_KMODS	+= amdzen_stub
+DRV_KMODS	+= usmn
--- a/usr/src/uts/intel/Makefile.rules	Sat Oct 03 11:05:18 2020 -0700
+++ b/usr/src/uts/intel/Makefile.rules	Wed Apr 08 21:35:09 2020 -0700
@@ -149,6 +149,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/amdzen/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/amr/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
--- a/usr/src/uts/intel/amdf17nbdf/Makefile	Sat Oct 03 11:05:18 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-#
-# This file and its contents are supplied under the terms of the
-# Common Development and Distribution License ("CDDL"), version 1.0.
-# You may only use this file in accordance with the terms of version
-# 1.0 of the CDDL.
-#
-# A full copy of the text of the CDDL should have accompanied this
-# source.  A copy of the CDDL is also available via the Internet at
-# http://www.illumos.org/license/CDDL.
-#
-
-#
-# Copyright 2019, Joyent, Inc.
-#
-
-UTSBASE = ../..
-
-MODULE		= amdf17nbdf
-OBJECTS		= $(AMDF17NBDF_OBJS:%=$(OBJS_DIR)/%)
-ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
-CONF_SRCDIR	= $(UTSBASE)/intel/io/amdf17nb
-
-include $(UTSBASE)/intel/Makefile.intel
-
-ALL_TARGET	= $(BINARY) $(CONFMOD)
-LINT_TARGET	= $(MODULE).lint
-INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
-
-.KEEP_STATE:
-
-def:		$(DEF_DEPS)
-
-all:		$(ALL_DEPS)
-
-clean:		$(CLEAN_DEPS)
-
-clobber:	$(CLOBBER_DEPS)
-
-lint:		$(LINT_DEPS)
-
-modlintlib:	$(MODLINTLIB_DEPS)
-
-clean.lint:	$(CLEAN_LINT_DEPS)
-
-install:	$(INSTALL_DEPS)
-
-include $(UTSBASE)/intel/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/amdzen/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,41 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+UTSBASE = ../..
+
+MODULE		= amdzen
+OBJECTS		= $(AMDZEN_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/intel/io/amdzen
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY) $(CONFMOD)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/amdzen_stub/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,41 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+UTSBASE = ../..
+
+MODULE		= amdzen_stub
+OBJECTS		= $(AMDZEN_STUB_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
+
+LDFLAGS		+= -dy -Ndrv/amdzen
+
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
--- a/usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c	Sat Oct 03 11:05:18 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1040 +0,0 @@
-/*
- * This file and its contents are supplied under the terms of the
- * Common Development and Distribution License ("CDDL"), version 1.0.
- * You may only use this file in accordance with the terms of version
- * 1.0 of the CDDL.
- *
- * A full copy of the text of the CDDL should have accompanied this
- * source.  A copy of the CDDL is also available via the Internet at
- * http://www.illumos.org/license/CDDL.
- */
-
-/*
- * Copyright 2019, Joyent, Inc.
- */
-
-/*
- * AMD Family 17 Northbridge and Data Fabric Driver
- *
- * This driver attaches to the AMD Family 17h northbridge and data fabric bus.
- * Each Zeppelin die ('processor node' in cpuid.c parlance) has its own
- * northbridge and access to the data fabric bus. The northbridge and data
- * fabric both provide access to various features such as:
- *
- *  - The System Management Network (SMN)
- *  - Data Fabric via Fabric Indirect Config Access (FICAA)
- *
- * These are required to access things such as temperature sensors or memory
- * controller configuration registers.
- *
- * In AMD Family 17h systems, the 'northbridge' is an ASIC that is part of the
- * package that contains many I/O capabilities related to things like PCI
- * express, etc. The 'data fabric' is the means by which different components
- * both inside the socket and multiple sockets are connected together. Both the
- * northbridge and the data fabric have dedicated PCI devices which the
- * operating system can use to interact with them.
- *
- * ------------------------
- * Mapping Devices Together
- * ------------------------
- *
- * The operating system needs to expose things like temperature sensors and DRAM
- * configuration registers in terms that are meaningful to the system such as
- * logical CPUs, cores, etc. This driver attaches to the PCI IDs that represent
- * the northbridge and data fabric; however, there are multiple PCI devices (one
- * per die) that exist. This driver does manage to map all of these three things
- * together; however, it requires some acrobatics. Unfortunately, there's no
- * direct way to map a northbridge to its corresponding die. However, we can map
- * a CPU die to a data fabric PCI device and a data fabric PCI device to a
- * corresponding northbridge PCI device.
- *
- * In current Zen based products, there is a direct mapping between processor
- * nodes and a data fabric PCI device. All of the devices are on PCI Bus 0 and
- * start from Device 0x18. Device 0x18 maps to processor node 0, 0x19 to
- * processor node 1, etc. This means that to map a logical CPU to a data fabric
- * device, we take its processor node id, add it to 0x18 and find the PCI device
- * that is on bus 0, device 0x18. As each data fabric device is attached based
- * on its PCI ID, we add it to the global list, amd_nbdf_dfs that is in the
- * amd_f17nbdf_t structure.
- *
- * The northbridge PCI device has a defined device and function, but the PCI bus
- * that it's on can vary. Each die has its own series of PCI buses that are
- * assigned to it and the northbridge PCI device is on the first of die-specific
- * PCI bus for each die. This also means that the northbridge will not show up
- * on PCI bus 0, which is the PCI bus that all of the data fabric devices are
- * on. While conventionally the northbridge with the lowest PCI bus value
- * would correspond to processor node zero, hardware does not guarantee that at
- * all. Because we don't want to be at the mercy of firmware, we don't rely on
- * this ordering, even though we have yet to find a system that deviates from
- * this scheme.
- *
- * One of the registers in the data fabric device's function 0
- * (AMDF17_DF_CFG_ADDR_CTL), happens to have the first PCI bus that is
- * associated with the processor node. This means, that we can map a data fabric
- * device to a northbridge by finding the northbridge whose PCI bus matches the
- * value in the corresponding data fabric's AMDF17_DF_CFG_ADDR_CTL.
- *
- * This means that we can map a northbridge to a data fabric device and a data
- * fabric device to a die. Because these are generally 1:1 mappings, there is a
- * transitive relationship and therefore we know which northbridge is associated
- * with which processor die. This is summarized in the following image:
- *
- *  +-------+      +----------------------------+         +--------------+
- *  | Die 0 | ---> | Data Fabric PCI BDF 0/18/0 |-------> | Northbridge  |
- *  +-------+      | AMDF17_DF_CFG_ADDR: bus 10 |         | PCI  10/0/0  |
- *     ...         +----------------------------+         +--------------+
- *  +-------+      +------------------------------+         +--------------+
- *  | Die n | ---> | Data Fabric PCI BDF 0/18+n/0 |-------> | Northbridge  |
- *  +-------+      | AMDF17_DF_CFG_ADDR: bus 133  |         | PCI 133/0/0  |
- *                 +------------------------------+         +--------------+
- *
- * Note, the PCI buses used by the northbridges here are arbitrary. They do not
- * reflect the actual values by hardware; however, the bus/device/function (BDF)
- * of the data fabric accurately models hardware. All of the BDF values are in
- * hex.
- *
- * Starting with the Rome generation of processors (Family 17h Model 30-3Fh),
- * AMD has multiple northbridges that exist on a given die. All of these
- * northbridges share the same data fabric and system management network port.
- * From our perspective this means that some of the northbridge devices will be
- * redundant and that we will no longer have a 1:1 mapping between the
- * northbridge and the data fabric devices. Every data fabric will have a
- * northbridge, but not every northbridge will have a data fabric device mapped.
- * Because we're always trying to map from a die to a northbridge and not the
- * reverse, the fact that there are extra northbridge devices hanging around
- * that we don't know about shouldn't be a problem.
- *
- * -------------------------------
- * Attach and Detach Complications
- * -------------------------------
- *
- * Because we need to map different PCI devices together, this means that we
- * have multiple dev_info_t structures that we need to manage. Each of these is
- * independently attached and detached. While this is easily managed for attach,
- * it is not for detach.
- *
- * Once a device has been detached it will only come back if we have an active
- * minor node that will be accessed. While we have minor nodes associated with
- * the northbridges, we don't with the data fabric devices. This means that if
- * they are detached, nothing would ever cause them to be reattached. The system
- * also doesn't provide us a way or any guarantees around making sure that we're
- * attached to all such devices before we detach. As a result, unfortunately,
- * it's easier to basically have detach always fail.
- *
- * To deal with both development and if issues arise in the field, there is a
- * knob, amdf17df_allow_detach, which if set to a non-zero value, will allow
- * instances to detach.
- *
- * ---------------
- * Exposed Devices
- * ---------------
- *
- * Currently we expose a single set of character devices which represent
- * temperature sensors for this family of processors. Because temperature
- * sensors exist on a per-processor node basis, we create a single minor node
- * for each one. Because our naming matches the cpuid naming, FMA can match that
- * up to logical CPUs and take care of matching the sensors appropriately. We
- * internally rate limit the sensor updates to 100ms, which is controlled by the
- * global amdf17nbdf_cache_ms.
- */
-
-#include <sys/modctl.h>
-#include <sys/conf.h>
-#include <sys/devops.h>
-#include <sys/types.h>
-#include <sys/file.h>
-#include <sys/open.h>
-#include <sys/cred.h>
-#include <sys/ddi.h>
-#include <sys/sunddi.h>
-#include <sys/cmn_err.h>
-#include <sys/list.h>
-#include <sys/pci.h>
-#include <sys/stddef.h>
-#include <sys/stat.h>
-#include <sys/x86_archext.h>
-#include <sys/cpuvar.h>
-#include <sys/sensors.h>
-
-/*
- * The range of minors that we'll allow.
- */
-#define	AMDF17_MINOR_LOW	1
-#define	AMDF17_MINOR_HIGH	INT32_MAX
-
-/*
- * This is the value of the first PCI data fabric device that globally exists.
- * It always maps to AMD's first nodeid (what we call cpi_procnodeid).
- */
-#define	AMDF17_DF_FIRST_DEVICE	0x18
-
-/*
- * The data fabric devices are defined to always be on PCI bus zero.
- */
-#define	AMDF17_DF_BUSNO		0x00
-
-/*
- * This register contains the BUS A of the the processor node that corresponds
- * to the data fabric device.
- */
-#define	AMDF17_DF_CFG_ADDR_CTL		0x84
-#define	AMDF17_DF_CFG_ADDR_CTL_MASK	0xff
-
-/*
- * Northbridge registers that are related to accessing the SMN. One writes to
- * the SMN address register and then can read from the SMN data register.
- */
-#define	AMDF17_NB_SMN_ADDR	0x60
-#define	AMDF17_NB_SMN_DATA	0x64
-
-/*
- * The following are register offsets and the meaning of their bits related to
- * temperature. These addresses are addresses in the System Management Network
- * which is accessed through the northbridge.  They are not addresses in PCI
- * configuration space.
- */
-#define	AMDF17_SMU_THERMAL_CURTEMP			0x00059800
-#define	AMDF17_SMU_THERMAL_CURTEMP_TEMPERATURE(x)	((x) >> 21)
-#define	AMDF17_SMU_THERMAL_CURTEMP_RANGE_SEL		(1 << 19)
-
-#define	AMDF17_SMU_THERMAL_CURTEMP_RANGE_ADJ		(-49)
-#define	AMDF17_SMU_THERMAL_CURTEMP_DECIMAL_BITS		3
-#define	AMDF17_SMU_THERMAL_CURTEMP_BITS_MASK		0x7
-
-/*
- * The temperature sensor in family 17 is measured in terms of 0.125 C steps.
- */
-#define	AMDF17_THERMAL_GRANULARITY	8
-
-struct amdf17nb;
-struct amdf17df;
-
-typedef struct amdf17nb {
-	list_node_t		amd_nb_link;
-	dev_info_t		*amd_nb_dip;
-	ddi_acc_handle_t	amd_nb_cfgspace;
-	uint_t			amd_nb_bus;
-	uint_t			amd_nb_dev;
-	uint_t			amd_nb_func;
-	struct amdf17df		*amd_nb_df;
-	uint_t			amd_nb_procnodeid;
-	id_t			amd_nb_temp_minor;
-	hrtime_t		amd_nb_temp_last_read;
-	int			amd_nb_temp_off;
-	uint32_t		amd_nb_temp_reg;
-	/* Values derived from the above */
-	int64_t			amd_nb_temp;
-} amdf17nb_t;
-
-typedef struct amdf17df {
-	list_node_t		amd_df_link;
-	dev_info_t		*amd_df_f0_dip;
-	ddi_acc_handle_t	amd_df_f0_cfgspace;
-	uint_t			amd_df_procnodeid;
-	uint_t			amd_df_iobus;
-	amdf17nb_t		*amd_df_nb;
-} amdf17df_t;
-
-typedef struct amdf17nbdf {
-	kmutex_t	amd_nbdf_lock;
-	id_space_t	*amd_nbdf_minors;
-	list_t		amd_nbdf_nbs;
-	list_t		amd_nbdf_dfs;
-} amdf17nbdf_t;
-
-typedef enum {
-	AMD_NBDF_TYPE_UNKNOWN,
-	AMD_NBDF_TYPE_NORTHBRIDGE,
-	AMD_NBDF_TYPE_DATA_FABRIC
-} amdf17nbdf_type_t;
-
-typedef struct {
-	uint16_t		amd_nbdft_pci_did;
-	amdf17nbdf_type_t	amd_nbdft_type;
-} amdf17nbdf_table_t;
-
-static const amdf17nbdf_table_t amdf17nbdf_dev_map[] = {
-	/* Family 17h Ryzen, Epyc Models 00h-0fh (Zen uarch) */
-	{ 0x1450, AMD_NBDF_TYPE_NORTHBRIDGE },
-	{ 0x1460, AMD_NBDF_TYPE_DATA_FABRIC },
-	/* Family 17h Raven Ridge Models 10h-1fh (Zen uarch) */
-	{ 0x15d0, AMD_NBDF_TYPE_NORTHBRIDGE },
-	{ 0x15e8, AMD_NBDF_TYPE_DATA_FABRIC },
-	/* Family 17h Epyc Models 30h-3fh (Zen 2 uarch) */
-	{ 0x1480, AMD_NBDF_TYPE_NORTHBRIDGE },
-	{ 0x1490, AMD_NBDF_TYPE_DATA_FABRIC },
-	/*
-	 * Family 17h Ryzen Models 70-7fh (Zen 2 uarch)
-	 *
-	 * While this family has its own PCI ID for the data fabric device, it
-	 * shares the same northbridge ID as the Zen 2 EPYC models 30-3f --
-	 * 0x1480.
-	 */
-	{ 0x1440, AMD_NBDF_TYPE_DATA_FABRIC },
-	{ PCI_EINVAL16 }
-};
-
-typedef struct {
-	const char	*amd_nbdfo_brand;
-	uint_t		amd_nbdfo_family;
-	int		amd_nbdfo_off;
-} amdf17nbdf_offset_t;
-
-/*
- * AMD processors report a control temperature (called Tctl) which may be
- * different from the junction temperature, which is the value that is actually
- * measured from the die (sometimes called Tdie or Tjct). This is done so that
- * socket-based environmental monitoring can be consistent from a platform
- * perspective, but doesn't help us. Unfortunately, these values aren't in
- * datasheets that we can find, but have been documented partially in a series
- * of blog posts by AMD when discussing their 'Ryzen Master' monitoring software
- * for Windows.
- *
- * The brand strings below may contain partial matches such in the Threadripper
- * cases so we can match the entire family of processors. The offset value is
- * the quantity in degrees that we should adjust Tctl to reach Tdie.
- */
-static const amdf17nbdf_offset_t amdf17nbdf_offsets[] = {
-	{ "AMD Ryzen 5 1600X", 0x17, -20 },
-	{ "AMD Ryzen 7 1700X", 0x17, -20 },
-	{ "AMD Ryzen 7 1800X", 0x17, -20 },
-	{ "AMD Ryzen 7 2700X", 0x17, -10 },
-	{ "AMD Ryzen Threadripper 19", 0x17, -27 },
-	{ "AMD Ryzen Threadripper 29", 0x17, -27 },
-	{ NULL }
-};
-
-/*
- * This indicates a number of milliseconds that we should wait between reads.
- * This is somewhat arbitrary, but the goal is to reduce cross call activity
- * and reflect that the sensor may not update all the time.
- */
-uint_t amdf17nbdf_cache_ms = 100;
-
-/*
- * This indicates whether detach is allowed. It is not by default. See the
- * theory statement section 'Attach and Detach Complications' for more
- * information.
- */
-uint_t amdf17nbdf_allow_detach = 0;
-
-/*
- * Global data that we keep regarding the device.
- */
-amdf17nbdf_t *amdf17nbdf;
-
-static amdf17nb_t *
-amdf17nbdf_lookup_nb(amdf17nbdf_t *nbdf, minor_t minor)
-{
-	ASSERT(MUTEX_HELD(&nbdf->amd_nbdf_lock));
-
-	if (minor < AMDF17_MINOR_LOW || minor > AMDF17_MINOR_HIGH) {
-		return (NULL);
-	}
-
-	for (amdf17nb_t *nb = list_head(&nbdf->amd_nbdf_nbs); nb != NULL;
-	    nb = list_next(&nbdf->amd_nbdf_nbs, nb)) {
-		if ((id_t)minor == nb->amd_nb_temp_minor) {
-			return (nb);
-		}
-	}
-
-	return (NULL);
-}
-
-static void
-amdf17nbdf_cleanup_nb(amdf17nbdf_t *nbdf, amdf17nb_t *nb)
-{
-	if (nb == NULL)
-		return;
-
-	ddi_remove_minor_node(nb->amd_nb_dip, NULL);
-	if (nb->amd_nb_temp_minor > 0) {
-		id_free(nbdf->amd_nbdf_minors, nb->amd_nb_temp_minor);
-	}
-	if (nb->amd_nb_cfgspace != NULL) {
-		pci_config_teardown(&nb->amd_nb_cfgspace);
-	}
-	kmem_free(nb, sizeof (amdf17nb_t));
-}
-
-static void
-amdf17nbdf_cleanup_df(amdf17df_t *df)
-{
-	if (df == NULL)
-		return;
-
-	if (df->amd_df_f0_cfgspace != NULL) {
-		pci_config_teardown(&df->amd_df_f0_cfgspace);
-	}
-	kmem_free(df, sizeof (amdf17df_t));
-}
-
-static int
-amdf17nbdf_smn_read(amdf17nbdf_t *nbdf, amdf17nb_t *nb, uint32_t addr,
-    uint32_t *valp)
-{
-	VERIFY(MUTEX_HELD(&nbdf->amd_nbdf_lock));
-
-	pci_config_put32(nb->amd_nb_cfgspace, AMDF17_NB_SMN_ADDR, addr);
-	*valp = pci_config_get32(nb->amd_nb_cfgspace, AMDF17_NB_SMN_DATA);
-
-	return (0);
-}
-
-static int
-amdf17nbdf_temp_read(amdf17nbdf_t *nbdf, amdf17nb_t *nb)
-{
-	int ret;
-	uint32_t reg, rawtemp, decimal;
-
-	ASSERT(MUTEX_HELD(&nbdf->amd_nbdf_lock));
-
-	/*
-	 * Update the last read time first. Even if this fails, we want to make
-	 * sure that we latch the fact that we tried.
-	 */
-	nb->amd_nb_temp_last_read = gethrtime();
-	if ((ret = amdf17nbdf_smn_read(nbdf, nb, AMDF17_SMU_THERMAL_CURTEMP,
-	    &reg)) != 0) {
-		return (ret);
-	}
-
-	nb->amd_nb_temp_reg = reg;
-
-	/*
-	 * Take the primary temperature value and break apart its decimal value
-	 * from its main value.
-	 */
-	rawtemp = AMDF17_SMU_THERMAL_CURTEMP_TEMPERATURE(reg);
-	decimal = rawtemp & AMDF17_SMU_THERMAL_CURTEMP_BITS_MASK;
-	rawtemp = rawtemp >> AMDF17_SMU_THERMAL_CURTEMP_DECIMAL_BITS;
-
-	if ((reg & AMDF17_SMU_THERMAL_CURTEMP_RANGE_SEL) != 0) {
-		rawtemp += AMDF17_SMU_THERMAL_CURTEMP_RANGE_ADJ;
-	}
-	rawtemp += nb->amd_nb_temp_off;
-	nb->amd_nb_temp = rawtemp << AMDF17_SMU_THERMAL_CURTEMP_DECIMAL_BITS;
-	nb->amd_nb_temp += decimal;
-
-	return (0);
-}
-
-static int
-amdf17nbdf_temp_init(amdf17nbdf_t *nbdf, amdf17nb_t *nb)
-{
-	uint_t i, family;
-	char buf[256];
-
-	if (cpuid_getbrandstr(CPU, buf, sizeof (buf)) >= sizeof (buf)) {
-		dev_err(nb->amd_nb_dip, CE_WARN, "!failed to read processor "
-		    "brand string, brand larger than internal buffer");
-		return (EOVERFLOW);
-	}
-
-	family = cpuid_getfamily(CPU);
-
-	for (i = 0; amdf17nbdf_offsets[i].amd_nbdfo_brand != NULL; i++) {
-		if (family != amdf17nbdf_offsets[i].amd_nbdfo_family)
-			continue;
-		if (strncmp(buf, amdf17nbdf_offsets[i].amd_nbdfo_brand,
-		    strlen(amdf17nbdf_offsets[i].amd_nbdfo_brand)) == 0) {
-			nb->amd_nb_temp_off =
-			    amdf17nbdf_offsets[i].amd_nbdfo_off;
-			break;
-		}
-	}
-
-	return (amdf17nbdf_temp_read(nbdf, nb));
-}
-
-static amdf17nbdf_type_t
-amdf17nbdf_dip_type(uint16_t dev)
-{
-	uint_t i;
-	const amdf17nbdf_table_t *tp = amdf17nbdf_dev_map;
-
-	for (i = 0; tp[i].amd_nbdft_pci_did != PCI_EINVAL16; i++) {
-		if (tp[i].amd_nbdft_pci_did == dev) {
-			return (tp[i].amd_nbdft_type);
-		}
-	}
-
-	return (AMD_NBDF_TYPE_UNKNOWN);
-}
-
-static boolean_t
-amdf17nbdf_map(amdf17nbdf_t *nbdf, amdf17nb_t *nb, amdf17df_t *df)
-{
-	int ret;
-	char buf[128];
-
-	ASSERT(MUTEX_HELD(&nbdf->amd_nbdf_lock));
-
-	/*
-	 * This means that we encountered a duplicate. We're going to stop
-	 * processing, but we're not going to fail its attach at this point.
-	 */
-	if (nb->amd_nb_df != NULL) {
-		dev_err(nb->amd_nb_dip, CE_WARN, "!trying to map NB %u/%u/%u "
-		    "to DF procnode %u, but NB is already mapped to DF "
-		    "procnode %u!",
-		    nb->amd_nb_bus, nb->amd_nb_dev, nb->amd_nb_func,
-		    df->amd_df_procnodeid, nb->amd_nb_df->amd_df_procnodeid);
-		return (B_TRUE);
-	}
-
-	/*
-	 * Now that we have found a mapping, initialize our temperature
-	 * information and create the minor node.
-	 */
-	nb->amd_nb_procnodeid = df->amd_df_procnodeid;
-	nb->amd_nb_temp_minor = id_alloc(nbdf->amd_nbdf_minors);
-
-	if ((ret = amdf17nbdf_temp_init(nbdf, nb)) != 0) {
-		dev_err(nb->amd_nb_dip, CE_WARN, "!failed to init SMN "
-		    "temperature data on node %u: %d", nb->amd_nb_procnodeid,
-		    ret);
-		return (B_FALSE);
-	}
-
-	if (snprintf(buf, sizeof (buf), "procnode.%u", nb->amd_nb_procnodeid) >=
-	    sizeof (buf)) {
-		dev_err(nb->amd_nb_dip, CE_WARN, "!unexpected buffer name "
-		    "overrun assembling temperature minor %u",
-		    nb->amd_nb_procnodeid);
-		return (B_FALSE);
-	}
-
-	if (ddi_create_minor_node(nb->amd_nb_dip, buf, S_IFCHR,
-	    nb->amd_nb_temp_minor, DDI_NT_SENSOR_TEMP_CPU, 0) != DDI_SUCCESS) {
-		dev_err(nb->amd_nb_dip, CE_WARN, "!failed to create minor node "
-		    "%s", buf);
-		return (B_FALSE);
-	}
-
-	/*
-	 * Now that's it's all done, note that they're mapped to each other.
-	 */
-	nb->amd_nb_df = df;
-	df->amd_df_nb = nb;
-
-	return (B_TRUE);
-}
-
-static boolean_t
-amdf17nbdf_add_nb(amdf17nbdf_t *nbdf, amdf17nb_t *nb)
-{
-	amdf17df_t *df;
-	boolean_t ret = B_TRUE;
-
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	list_insert_tail(&nbdf->amd_nbdf_nbs, nb);
-	for (df = list_head(&nbdf->amd_nbdf_dfs); df != NULL;
-	    df = list_next(&nbdf->amd_nbdf_dfs, df)) {
-		if (nb->amd_nb_bus == df->amd_df_iobus) {
-			ret = amdf17nbdf_map(nbdf, nb, df);
-			break;
-		}
-	}
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	return (ret);
-}
-
-static boolean_t
-amdf17nbdf_add_df(amdf17nbdf_t *nbdf, amdf17df_t *df)
-{
-	amdf17nb_t *nb;
-	boolean_t ret = B_TRUE;
-
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	list_insert_tail(&nbdf->amd_nbdf_dfs, df);
-	for (nb = list_head(&nbdf->amd_nbdf_nbs); nb != NULL;
-	    nb = list_next(&nbdf->amd_nbdf_nbs, nb)) {
-		if (nb->amd_nb_bus == df->amd_df_iobus) {
-			ret = amdf17nbdf_map(nbdf, nb, df);
-		}
-	}
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	return (ret);
-}
-
-static boolean_t
-amdf17nbdf_attach_nb(amdf17nbdf_t *nbdf, dev_info_t *dip, ddi_acc_handle_t hdl,
-    uint_t bus, uint_t dev, uint_t func)
-{
-	amdf17nb_t *nb;
-
-	nb = kmem_zalloc(sizeof (amdf17nb_t), KM_SLEEP);
-	nb->amd_nb_dip = dip;
-	nb->amd_nb_cfgspace = hdl;
-	nb->amd_nb_bus = bus;
-	nb->amd_nb_dev = dev;
-	nb->amd_nb_func = func;
-	/*
-	 * Set this to a value we won't get from the processor.
-	 */
-	nb->amd_nb_procnodeid = UINT_MAX;
-
-	if (!amdf17nbdf_add_nb(nbdf, nb)) {
-		amdf17nbdf_cleanup_nb(nbdf, nb);
-		return (B_FALSE);
-	}
-
-	return (B_TRUE);
-}
-
-static boolean_t
-amdf17nbdf_attach_df(amdf17nbdf_t *nbdf, dev_info_t *dip, ddi_acc_handle_t hdl,
-    uint_t bus, uint_t dev, uint_t func)
-{
-	amdf17df_t *df;
-
-	if (bus != AMDF17_DF_BUSNO) {
-		dev_err(dip, CE_WARN, "!encountered data fabric device with "
-		    "unexpected PCI bus assignment, found 0x%x, expected 0x%x",
-		    bus, AMDF17_DF_BUSNO);
-		return (B_FALSE);
-	}
-
-	if (dev < AMDF17_DF_FIRST_DEVICE) {
-		dev_err(dip, CE_WARN, "!encountered data fabric device with "
-		    "PCI device assignment below the first minimum device "
-		    "(0x%x): 0x%x", AMDF17_DF_FIRST_DEVICE, dev);
-		return (B_FALSE);
-	}
-
-	/*
-	 * At the moment we only care about function 0. However, we may care
-	 * about Function 4 in the future which has access to the FICAA.
-	 * However, only function zero should ever be attached, so this is just
-	 * an extra precaution.
-	 */
-	if (func != 0) {
-		dev_err(dip, CE_WARN, "!encountered data fabric device with "
-		    "unxpected PCI function assignment, found 0x%x, expected "
-		    "0x0", func);
-		return (B_FALSE);
-	}
-
-	df = kmem_zalloc(sizeof (amdf17df_t), KM_SLEEP);
-	df->amd_df_f0_dip = dip;
-	df->amd_df_f0_cfgspace = hdl;
-	df->amd_df_procnodeid = dev - AMDF17_DF_FIRST_DEVICE;
-	df->amd_df_iobus = pci_config_get32(hdl, AMDF17_DF_CFG_ADDR_CTL) &
-	    AMDF17_DF_CFG_ADDR_CTL_MASK;
-
-	if (!amdf17nbdf_add_df(nbdf, df)) {
-		amdf17nbdf_cleanup_df(df);
-		return (B_FALSE);
-	}
-
-	return (B_TRUE);
-}
-
-static int
-amdf17nbdf_open(dev_t *devp, int flags, int otype, cred_t *credp)
-{
-	amdf17nbdf_t *nbdf = amdf17nbdf;
-	minor_t m;
-
-	if (crgetzoneid(credp) != GLOBAL_ZONEID || drv_priv(credp)) {
-		return (EPERM);
-	}
-
-	if ((flags & (FEXCL | FNDELAY | FWRITE)) != 0) {
-		return (EINVAL);
-	}
-
-	if (otype != OTYP_CHR) {
-		return (EINVAL);
-	}
-
-	m = getminor(*devp);
-
-	/*
-	 * Sanity check the minor
-	 */
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	if (amdf17nbdf_lookup_nb(nbdf, m) == NULL) {
-		mutex_exit(&nbdf->amd_nbdf_lock);
-		return (ENXIO);
-	}
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	return (0);
-}
-
-static int
-amdf17nbdf_ioctl_kind(intptr_t arg, int mode)
-{
-	sensor_ioctl_kind_t kind;
-
-	bzero(&kind, sizeof (sensor_ioctl_kind_t));
-	kind.sik_kind = SENSOR_KIND_TEMPERATURE;
-
-	if (ddi_copyout((void *)&kind, (void *)arg,
-	    sizeof (sensor_ioctl_kind_t), mode & FKIOCTL) != 0) {
-		return (EFAULT);
-	}
-
-	return (0);
-}
-
-static int
-amdf17nbdf_ioctl_scalar(amdf17nbdf_t *nbdf, minor_t minor, intptr_t arg,
-    int mode)
-{
-	amdf17nb_t *nb;
-	hrtime_t diff;
-	sensor_ioctl_scalar_t scalar;
-
-	bzero(&scalar, sizeof (scalar));
-
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	nb = amdf17nbdf_lookup_nb(nbdf, minor);
-	if (nb == NULL) {
-		mutex_exit(&nbdf->amd_nbdf_lock);
-		return (ENXIO);
-	}
-
-	diff = NSEC2MSEC(gethrtime() - nb->amd_nb_temp_last_read);
-	if (diff > 0 && diff > (hrtime_t)amdf17nbdf_cache_ms) {
-		int ret;
-
-		ret = amdf17nbdf_temp_read(nbdf, nb);
-		if (ret != 0) {
-			mutex_exit(&nbdf->amd_nbdf_lock);
-			return (ret);
-		}
-	}
-
-	scalar.sis_unit = SENSOR_UNIT_CELSIUS;
-	scalar.sis_value = nb->amd_nb_temp;
-	scalar.sis_gran = AMDF17_THERMAL_GRANULARITY;
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	if (ddi_copyout(&scalar, (void *)arg, sizeof (scalar),
-	    mode & FKIOCTL) != 0) {
-		return (EFAULT);
-	}
-
-	return (0);
-}
-
-static int
-amdf17nbdf_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
-    int *rvalp)
-{
-	minor_t m;
-	amdf17nbdf_t *nbdf = amdf17nbdf;
-
-	if ((mode & FREAD) == 0) {
-		return (EINVAL);
-	}
-
-	m = getminor(dev);
-
-	switch (cmd) {
-	case SENSOR_IOCTL_KIND:
-		return (amdf17nbdf_ioctl_kind(arg, mode));
-	case SENSOR_IOCTL_SCALAR:
-		return (amdf17nbdf_ioctl_scalar(nbdf, m, arg, mode));
-	default:
-		return (ENOTTY);
-	}
-}
-
-/*
- * We don't really do any state tracking on close, so for now, just allow it to
- * always succeed.
- */
-static int
-amdf17nbdf_close(dev_t dev, int flags, int otype, cred_t *credp)
-{
-	return (0);
-}
-
-static int
-amdf17nbdf_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
-{
-	uint_t nregs;
-	int *regs;
-	uint_t bus, dev, func;
-	uint16_t pci_did;
-	ddi_acc_handle_t pci_hdl;
-	amdf17nbdf_type_t type;
-	amdf17nbdf_t *nbdf = amdf17nbdf;
-
-	if (cmd == DDI_RESUME)
-		return (DDI_SUCCESS);
-	if (cmd != DDI_ATTACH)
-		return (DDI_FAILURE);
-
-	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, "reg",
-	    &regs, &nregs) != DDI_PROP_SUCCESS) {
-		dev_err(dip, CE_WARN, "!failed to find pci 'reg' property");
-		return (DDI_FAILURE);
-	}
-
-	if (nregs < 1) {
-		ddi_prop_free(regs);
-		return (DDI_FAILURE);
-	}
-
-	bus = PCI_REG_BUS_G(regs[0]);
-	dev = PCI_REG_DEV_G(regs[0]);
-	func = PCI_REG_FUNC_G(regs[0]);
-
-	ddi_prop_free(regs);
-
-	if (pci_config_setup(dip, &pci_hdl) != DDI_SUCCESS) {
-		dev_err(dip, CE_WARN, "!failed to map pci devices");
-		return (DDI_FAILURE);
-	}
-
-	pci_did = pci_config_get16(pci_hdl, PCI_CONF_DEVID);
-
-	type = amdf17nbdf_dip_type(pci_did);
-	switch (type) {
-	case AMD_NBDF_TYPE_NORTHBRIDGE:
-		if (!amdf17nbdf_attach_nb(nbdf, dip, pci_hdl, bus, dev, func)) {
-			return (DDI_FAILURE);
-		}
-		break;
-	case AMD_NBDF_TYPE_DATA_FABRIC:
-		if (!amdf17nbdf_attach_df(nbdf, dip, pci_hdl, bus, dev, func)) {
-			return (DDI_FAILURE);
-		}
-		break;
-	default:
-		pci_config_teardown(&pci_hdl);
-		return (DDI_FAILURE);
-	}
-
-	return (DDI_SUCCESS);
-}
-
-/*
- * Unfortunately, it's hard for us to really support detach here. The problem is
- * that we need both the data fabric devices and the northbridges to make sure
- * that we map everything. However, only the northbridges actually create minor
- * nodes that'll be opened and thus trigger them to reattach when accessed. What
- * we should probably look at doing in the future is making this into a nexus
- * driver that enumerates children like a temperature driver.
- */
-static int
-amdf17nbdf_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
-{
-	amdf17nbdf_t *nbdf = amdf17nbdf;
-
-	if (cmd == DDI_SUSPEND)
-		return (DDI_SUCCESS);
-
-	if (nbdf == NULL) {
-		return (DDI_FAILURE);
-	}
-
-	if (amdf17nbdf_allow_detach == 0) {
-		return (DDI_FAILURE);
-	}
-
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	for (amdf17nb_t *nb = list_head(&nbdf->amd_nbdf_nbs); nb != NULL;
-	    nb = list_next(&nbdf->amd_nbdf_nbs, nb)) {
-		if (dip == nb->amd_nb_dip) {
-			list_remove(&nbdf->amd_nbdf_nbs, nb);
-			if (nb->amd_nb_df != NULL) {
-				ASSERT3P(nb->amd_nb_df->amd_df_nb, ==, nb);
-				nb->amd_nb_df->amd_df_nb = NULL;
-			}
-			amdf17nbdf_cleanup_nb(nbdf, nb);
-			mutex_exit(&nbdf->amd_nbdf_lock);
-			return (DDI_SUCCESS);
-		}
-	}
-
-	for (amdf17df_t *df = list_head(&nbdf->amd_nbdf_dfs); df != NULL;
-	    df = list_next(&nbdf->amd_nbdf_nbs, df)) {
-		if (dip == df->amd_df_f0_dip) {
-			list_remove(&nbdf->amd_nbdf_dfs, df);
-			if (df->amd_df_nb != NULL) {
-				ASSERT3P(df->amd_df_nb->amd_nb_df, ==, df);
-				df->amd_df_nb->amd_nb_df = NULL;
-			}
-			amdf17nbdf_cleanup_df(df);
-			mutex_exit(&nbdf->amd_nbdf_lock);
-			return (DDI_SUCCESS);
-		}
-	}
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	return (DDI_FAILURE);
-}
-
-static int
-amdf17nbdf_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
-    void **resultp)
-{
-	dev_t dev;
-	minor_t minor;
-	amdf17nbdf_t *nbdf;
-	amdf17nb_t *nb;
-
-	switch (cmd) {
-	case DDI_INFO_DEVT2DEVINFO:
-	case DDI_INFO_DEVT2INSTANCE:
-		break;
-	default:
-		return (DDI_FAILURE);
-	}
-
-	dev = (dev_t)arg;
-	minor = getminor(dev);
-	nbdf = amdf17nbdf;
-
-	mutex_enter(&nbdf->amd_nbdf_lock);
-	nb = amdf17nbdf_lookup_nb(nbdf, (id_t)minor);
-	if (nb == NULL) {
-		mutex_exit(&nbdf->amd_nbdf_lock);
-		return (DDI_FAILURE);
-	}
-	if (cmd == DDI_INFO_DEVT2DEVINFO) {
-		*resultp = nb->amd_nb_dip;
-	} else {
-		int inst = ddi_get_instance(nb->amd_nb_dip);
-		*resultp = (void *)(uintptr_t)inst;
-	}
-	mutex_exit(&nbdf->amd_nbdf_lock);
-
-	return (DDI_SUCCESS);
-}
-
-static void
-amdf17nbdf_destroy(amdf17nbdf_t *nbdf)
-{
-	amdf17nb_t *nb;
-	amdf17df_t *df;
-
-	while ((nb = list_remove_head(&nbdf->amd_nbdf_nbs)) != NULL) {
-		amdf17nbdf_cleanup_nb(nbdf, nb);
-	}
-	list_destroy(&nbdf->amd_nbdf_nbs);
-
-	while ((df = list_remove_head(&nbdf->amd_nbdf_dfs)) != NULL) {
-		amdf17nbdf_cleanup_df(df);
-	}
-	list_destroy(&nbdf->amd_nbdf_dfs);
-
-	if (nbdf->amd_nbdf_minors != NULL) {
-		id_space_destroy(nbdf->amd_nbdf_minors);
-	}
-
-	mutex_destroy(&nbdf->amd_nbdf_lock);
-	kmem_free(nbdf, sizeof (amdf17nbdf_t));
-}
-
-static amdf17nbdf_t *
-amdf17nbdf_create(void)
-{
-	amdf17nbdf_t *nbdf;
-
-	nbdf = kmem_zalloc(sizeof (amdf17nbdf_t), KM_SLEEP);
-	mutex_init(&nbdf->amd_nbdf_lock, NULL, MUTEX_DRIVER, NULL);
-	list_create(&nbdf->amd_nbdf_nbs, sizeof (amdf17nb_t),
-	    offsetof(amdf17nb_t, amd_nb_link));
-	list_create(&nbdf->amd_nbdf_dfs, sizeof (amdf17df_t),
-	    offsetof(amdf17df_t, amd_df_link));
-	if ((nbdf->amd_nbdf_minors = id_space_create("amdf17nbdf_minors",
-	    AMDF17_MINOR_LOW, AMDF17_MINOR_HIGH)) == NULL) {
-		amdf17nbdf_destroy(nbdf);
-		return (NULL);
-	}
-
-	return (nbdf);
-}
-
-static struct cb_ops amdf17nbdf_cb_ops = {
-	.cb_open = amdf17nbdf_open,
-	.cb_close = amdf17nbdf_close,
-	.cb_strategy = nodev,
-	.cb_print = nodev,
-	.cb_dump = nodev,
-	.cb_read = nodev,
-	.cb_write = nodev,
-	.cb_ioctl = amdf17nbdf_ioctl,
-	.cb_devmap = nodev,
-	.cb_mmap = nodev,
-	.cb_segmap = nodev,
-	.cb_chpoll = nochpoll,
-	.cb_prop_op = ddi_prop_op,
-	.cb_flag = D_MP,
-	.cb_rev = CB_REV,
-	.cb_aread = nodev,
-	.cb_awrite = nodev
-};
-
-static struct dev_ops amdf17nbdf_dev_ops = {
-	.devo_rev = DEVO_REV,
-	.devo_refcnt = 0,
-	.devo_getinfo = amdf17nbdf_getinfo,
-	.devo_identify = nulldev,
-	.devo_probe = nulldev,
-	.devo_attach = amdf17nbdf_attach,
-	.devo_detach = amdf17nbdf_detach,
-	.devo_reset = nodev,
-	.devo_quiesce = ddi_quiesce_not_needed,
-	.devo_cb_ops = &amdf17nbdf_cb_ops
-};
-
-static struct modldrv amdf17nbdf_modldrv = {
-	.drv_modops = &mod_driverops,
-	.drv_linkinfo = "AMD Family 17h Driver",
-	.drv_dev_ops = &amdf17nbdf_dev_ops
-};
-
-static struct modlinkage amdf17nbdf_modlinkage = {
-	.ml_rev = MODREV_1,
-	.ml_linkage = { &amdf17nbdf_modldrv, NULL }
-};
-
-int
-_init(void)
-{
-	int ret;
-	amdf17nbdf_t *nbdf;
-
-	if ((nbdf = amdf17nbdf_create()) == NULL) {
-		return (ENOMEM);
-	}
-
-	if ((ret = mod_install(&amdf17nbdf_modlinkage)) != 0) {
-		amdf17nbdf_destroy(amdf17nbdf);
-		return (ret);
-	}
-
-	amdf17nbdf = nbdf;
-	return (ret);
-}
-
-int
-_info(struct modinfo *modinfop)
-{
-	return (mod_info(&amdf17nbdf_modlinkage, modinfop));
-}
-
-int
-_fini(void)
-{
-	int ret;
-
-	if ((ret = mod_remove(&amdf17nbdf_modlinkage)) != 0) {
-		return (ret);
-	}
-
-	amdf17nbdf_destroy(amdf17nbdf);
-	amdf17nbdf = NULL;
-	return (ret);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/amdzen.c	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,950 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * Nexus Driver for AMD Zen family systems. The purpose of this driver is to
+ * provide access to the following resources in a single, centralized fashion:
+ *
+ *  - The per-chip Data Fabric
+ *  - The North Bridge
+ *  - The System Management Network (SMN)
+ *
+ * This is a nexus driver as once we have attached to all the requisite
+ * components, we will enumerate child devices which consume this functionality.
+ *
+ * ------------------------
+ * Mapping Devices Together
+ * ------------------------
+ *
+ * The operating system needs to expose things like temperature sensors and DRAM
+ * configuration registers in terms that are meaningful to the system such as
+ * logical CPUs, cores, etc. This driver attaches to the PCI IDs that represent
+ * the northbridge and data fabric; however, there are multiple PCI devices (one
+ * per die) that exist. This driver does manage to map all of these three things
+ * together; however, it requires some acrobatics. Unfortunately, there's no
+ * direct way to map a northbridge to its corresponding die. However, we can map
+ * a CPU die to a data fabric PCI device and a data fabric PCI device to a
+ * corresponding northbridge PCI device.
+ *
+ * In current Zen based products, there is a direct mapping between processor
+ * nodes and a data fabric PCI device. All of the devices are on PCI Bus 0 and
+ * start from Device 0x18. Device 0x18 maps to processor node 0, 0x19 to
+ * processor node 1, etc. This means that to map a logical CPU to a data fabric
+ * device, we take its processor node id, add it to 0x18 and find the PCI device
+ * that is on bus 0, device 0x18. As each data fabric device is attached based
+ * on its PCI ID, we add it to the global list, amd_nbdf_dfs that is in the
+ * amd_f17nbdf_t structure.
+ *
+ * The northbridge PCI device has a defined device and function, but the PCI bus
+ * that it's on can vary. Each die has its own series of PCI buses that are
+ * assigned to it and the northbridge PCI device is on the first of die-specific
+ * PCI bus for each die. This also means that the northbridge will not show up
+ * on PCI bus 0, which is the PCI bus that all of the data fabric devices are
+ * on. While conventionally the northbridge with the lowest PCI bus value
+ * would correspond to processor node zero, hardware does not guarantee that at
+ * all. Because we don't want to be at the mercy of firmware, we don't rely on
+ * this ordering, even though we have yet to find a system that deviates from
+ * this scheme.
+ *
+ * One of the registers in the data fabric device's function 0
+ * (AMDZEN_DF_F0_CFG_ADDR_CTL) happens to have the first PCI bus that is
+ * associated with the processor node. This means that we can map a data fabric
+ * device to a northbridge by finding the northbridge whose PCI bus matches the
+ * value in the corresponding data fabric's AMDZEN_DF_F0_CFG_ADDR_CTL.
+ *
+ * We can map a northbridge to a data fabric device and a data fabric device to
+ * a die. Because these are generally 1:1 mappings, there is a transitive
+ * relationship and therefore we know which northbridge is associated with which
+ * processor die. This is summarized in the following image:
+ *
+ *  +-------+    +-----------------------------------+        +--------------+
+ *  | Die 0 |--->| Data Fabric PCI BDF 0/18/0        |------->| Northbridge  |
+ *  +-------+    | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 10 |        | PCI  10/0/0  |
+ *     ...       +-----------------------------------+        +--------------+
+ *  +-------+     +------------------------------------+        +--------------+
+ *  | Die n |---->| Data Fabric PCI BDF 0/18+n/0       |------->| Northbridge  |
+ *  +-------+     | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 133 |        | PCI 133/0/0  |
+ *                +------------------------------------+        +--------------+
+ *
+ * Note, the PCI buses used by the northbridges here are arbitrary. They do not
+ * reflect the actual values by hardware; however, the bus/device/function (BDF)
+ * of the data fabric accurately models hardware. All of the BDF values are in
+ * hex.
+ *
+ * Starting with the Rome generation of processors (Family 17h Model 30-3Fh),
+ * AMD has multiple northbridges that exist on a given die. All of these
+ * northbridges share the same data fabric and system management network port.
+ * From our perspective this means that some of the northbridge devices will be
+ * redundant and that we will no longer have a 1:1 mapping between the
+ * northbridge and the data fabric devices. Every data fabric will have a
+ * northbridge, but not every northbridge will have a data fabric device mapped.
+ * Because we're always trying to map from a die to a northbridge and not the
+ * reverse, the fact that there are extra northbridge devices hanging around
+ * that we don't know about shouldn't be a problem.
+ *
+ * -------------------------------
+ * Attach and Detach Complications
+ * -------------------------------
+ *
+ * Because we need to map different PCI devices together, this means that we
+ * have multiple dev_info_t structures that we need to manage. Each of these is
+ * independently attached and detached. While this is easily managed for attach,
+ * it is not for detach. Each of these devices is a 'stub'.
+ *
+ * Once a device has been detached it will only come back if we have an active
+ * minor node that will be accessed. This means that if they are detached,
+ * nothing would ever cause them to be reattached. The system also doesn't
+ * provide us a way or any guarantees around making sure that we're attached to
+ * all such devices before we detach. As a result, unfortunately, it's easier to
+ * basically have detach always fail.
+ *
+ * ---------------
+ * Exposed Devices
+ * ---------------
+ *
+ * Rather than try and have all of the different functions that could be
+ * provided by one driver, we instead have created a nexus driver that will
+ * itself try and load children. Children are all pseudo-device drivers that
+ * provide different pieces of functionality that use this.
+ */
+
+#include <sys/modctl.h>
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/pci.h>
+#include <sys/sysmacros.h>
+#include <sys/sunndi.h>
+#include <sys/x86_archext.h>
+#include <sys/cpuvar.h>
+
+#include "amdzen.h"
+
+amdzen_t *amdzen_data;
+
+/*
+ * Array of northbridge IDs that we care about.
+ */
+static const uint16_t amdzen_nb_ids[] = {
+	/* Family 17h Ryzen, Epyc Models 00h-0fh (Zen uarch) */
+	0x1450,
+	/* Family 17h Raven Ridge Models 10h-1fh (Zen uarch) */
+	0x15d0,
+	/* Family 17h Epyc Models 30h-3fh, Matisse 700-7fh (Zen 2 uarch) */
+	0x1480
+};
+
+typedef struct {
+	char *acd_name;
+	amdzen_child_t acd_addr;
+} amdzen_child_data_t;
+
+static const amdzen_child_data_t amdzen_children[] = {
+	{ "smntemp", AMDZEN_C_SMNTEMP },
+	{ "usmn", AMDZEN_C_USMN }
+};
+
+static uint32_t
+amdzen_stub_get32(amdzen_stub_t *stub, off_t reg)
+{
+	return (pci_config_get32(stub->azns_cfgspace, reg));
+}
+
+static void
+amdzen_stub_put32(amdzen_stub_t *stub, off_t reg, uint32_t val)
+{
+	pci_config_put32(stub->azns_cfgspace, reg, val);
+}
+
+/*
+ * Perform a targeted 32-bit indirect read to a specific instance and function.
+ */
+static uint32_t
+amdzen_df_read32(amdzen_t *azn, amdzen_df_t *df, uint8_t inst, uint8_t func,
+    uint16_t reg)
+{
+	uint32_t val;
+
+	VERIFY(MUTEX_HELD(&azn->azn_mutex));
+	val = AMDZEN_DF_F4_FICAA_TARG_INST | AMDZEN_DF_F4_FICAA_SET_REG(reg) |
+	    AMDZEN_DF_F4_FICAA_SET_FUNC(func) |
+	    AMDZEN_DF_F4_FICAA_SET_INST(inst);
+	amdzen_stub_put32(df->adf_funcs[4], AMDZEN_DF_F4_FICAA, val);
+	return (amdzen_stub_get32(df->adf_funcs[4], AMDZEN_DF_F4_FICAD_LO));
+}
+
+static uint32_t
+amdzen_smn_read32(amdzen_t *azn, amdzen_df_t *df, uint32_t reg)
+{
+	VERIFY(MUTEX_HELD(&azn->azn_mutex));
+	amdzen_stub_put32(df->adf_nb, AMDZEN_NB_SMN_ADDR, reg);
+	return (amdzen_stub_get32(df->adf_nb, AMDZEN_NB_SMN_DATA));
+}
+
+static amdzen_df_t *
+amdzen_df_find(amdzen_t *azn, uint_t dfno)
+{
+	uint_t i;
+
+	ASSERT(MUTEX_HELD(&azn->azn_mutex));
+	if (dfno >= azn->azn_ndfs) {
+		return (NULL);
+	}
+
+	for (i = 0; i < azn->azn_ndfs; i++) {
+		amdzen_df_t *df = &azn->azn_dfs[i];
+		if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0) {
+			continue;
+		}
+
+		if (dfno == 0) {
+			return (df);
+		}
+		dfno--;
+	}
+
+	return (NULL);
+}
+
+/*
+ * Client functions that are used by nexus children.
+ */
+int
+amdzen_c_smn_read32(uint_t dfno, uint32_t reg, uint32_t *valp)
+{
+	amdzen_df_t *df;
+	amdzen_t *azn = amdzen_data;
+
+	mutex_enter(&azn->azn_mutex);
+	df = amdzen_df_find(azn, dfno);
+	if (df == NULL) {
+		mutex_exit(&azn->azn_mutex);
+		return (ENOENT);
+	}
+
+	if ((df->adf_flags & AMDZEN_DF_F_FOUND_NB) == 0) {
+		mutex_exit(&azn->azn_mutex);
+		return (ENXIO);
+	}
+
+	*valp = amdzen_smn_read32(azn, df, reg);
+	mutex_exit(&azn->azn_mutex);
+	return (0);
+}
+
+uint_t
+amdzen_c_df_count(void)
+{
+	uint_t ret;
+	amdzen_t *azn = amdzen_data;
+
+	mutex_enter(&azn->azn_mutex);
+	ret = azn->azn_ndfs;
+	mutex_exit(&azn->azn_mutex);
+	return (ret);
+}
+
+
+static boolean_t
+amdzen_create_child(amdzen_t *azn, const amdzen_child_data_t *acd)
+{
+	int ret;
+	dev_info_t *child;
+
+	if (ndi_devi_alloc(azn->azn_dip, acd->acd_name,
+	    (pnode_t)DEVI_SID_NODEID, &child) != NDI_SUCCESS) {
+		dev_err(azn->azn_dip, CE_WARN, "failed to allocate child "
+		    "dip for %s", acd->acd_name);
+		return (B_FALSE);
+	}
+
+	ddi_set_parent_data(child, (void *)acd);
+	if ((ret = ndi_devi_online(child, 0)) != NDI_SUCCESS) {
+		dev_err(azn->azn_dip, CE_WARN, "failed to online child "
+		    "dip %s: %d", acd->acd_name, ret);
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static boolean_t
+amdzen_map_dfs(amdzen_t *azn)
+{
+	amdzen_stub_t *stub;
+
+	ASSERT(MUTEX_HELD(&azn->azn_mutex));
+
+	for (stub = list_head(&azn->azn_df_stubs); stub != NULL;
+	    stub = list_next(&azn->azn_df_stubs, stub)) {
+		amdzen_df_t *df;
+		uint_t dfno;
+
+		dfno = stub->azns_dev - AMDZEN_DF_FIRST_DEVICE;
+		if (dfno > AMDZEN_MAX_DFS) {
+			dev_err(stub->azns_dip, CE_WARN, "encountered df "
+			    "device with illegal DF PCI b/d/f: 0x%x/%x/%x",
+			    stub->azns_bus, stub->azns_dev, stub->azns_func);
+			goto err;
+		}
+
+		df = &azn->azn_dfs[dfno];
+
+		if (stub->azns_func >= AMDZEN_MAX_DF_FUNCS) {
+			dev_err(stub->azns_dip, CE_WARN, "encountered df "
+			    "device with illegal DF PCI b/d/f: 0x%x/%x/%x",
+			    stub->azns_bus, stub->azns_dev, stub->azns_func);
+			goto err;
+		}
+
+		if (df->adf_funcs[stub->azns_func] != NULL) {
+			dev_err(stub->azns_dip, CE_WARN, "encountered "
+			    "duplicate df device with DF PCI b/d/f: 0x%x/%x/%x",
+			    stub->azns_bus, stub->azns_dev, stub->azns_func);
+			goto err;
+		}
+		df->adf_funcs[stub->azns_func] = stub;
+	}
+
+	return (B_TRUE);
+
+err:
+	azn->azn_flags |= AMDZEN_F_DEVICE_ERROR;
+	return (B_FALSE);
+}
+
+static boolean_t
+amdzen_check_dfs(amdzen_t *azn)
+{
+	uint_t i;
+	boolean_t ret = B_TRUE;
+
+	for (i = 0; i < AMDZEN_MAX_DFS; i++) {
+		amdzen_df_t *df = &azn->azn_dfs[i];
+		uint_t count = 0;
+
+		/*
+		 * We require all platforms to have DFs functions 0-6. Not all
+		 * platforms have DF function 7.
+		 */
+		for (uint_t func = 0; func < AMDZEN_MAX_DF_FUNCS - 1; func++) {
+			if (df->adf_funcs[func] != NULL) {
+				count++;
+			}
+		}
+
+		if (count == 0)
+			continue;
+
+		if (count != 7) {
+			ret = B_FALSE;
+			dev_err(azn->azn_dip, CE_WARN, "df %u devices "
+			    "incomplete", i);
+		} else {
+			df->adf_flags |= AMDZEN_DF_F_VALID;
+			azn->azn_ndfs++;
+		}
+	}
+
+	return (ret);
+}
+
+static const uint8_t amdzen_df_rome_ids[0x2b] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23,
+	24, 25, 26, 27, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+	44, 45, 46, 47, 48
+};
+
+/*
+ * Initialize our knowledge about a given series of nodes on the data fabric.
+ */
+static void
+amdzen_setup_df(amdzen_t *azn, amdzen_df_t *df)
+{
+	uint_t i;
+	uint32_t val;
+
+	val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_CFG_ADDR_CTL);
+	df->adf_nb_busno = AMDZEN_DF_F0_CFG_ADDR_CTL_BUS_NUM(val);
+	val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_FBICNT);
+	df->adf_nents = AMDZEN_DF_F0_FBICNT_COUNT(val);
+	if (df->adf_nents == 0)
+		return;
+	df->adf_ents = kmem_zalloc(sizeof (amdzen_df_ent_t) * df->adf_nents,
+	    KM_SLEEP);
+
+	for (i = 0; i < df->adf_nents; i++) {
+		amdzen_df_ent_t *dfe = &df->adf_ents[i];
+		uint8_t inst = i;
+
+		/*
+		 * Unfortunately, Rome uses a discontinuous instance ID pattern
+		 * while everything else we can find uses a contiguous instance
+		 * ID pattern.  This means that for Rome, we need to adjust the
+		 * indexes that we iterate over, though the total number of
+		 * entries is right.
+		 */
+		if (df->adf_funcs[0]->azns_did == 0x1490) {
+			if (inst > ARRAY_SIZE(amdzen_df_rome_ids)) {
+				dev_err(azn->azn_dip, CE_WARN, "Rome family "
+				    "processor reported more ids than the PPR, "
+				    "resting %u to instance zero", inst);
+				inst = 0;
+			} else {
+				inst = amdzen_df_rome_ids[inst];
+			}
+		}
+
+		dfe->adfe_drvid = inst;
+		dfe->adfe_info0 = amdzen_df_read32(azn, df, inst, 0,
+		    AMDZEN_DF_F0_FBIINFO0);
+		dfe->adfe_info1 = amdzen_df_read32(azn, df, inst, 0,
+		    AMDZEN_DF_F0_FBIINFO1);
+		dfe->adfe_info2 = amdzen_df_read32(azn, df, inst, 0,
+		    AMDZEN_DF_F0_FBIINFO2);
+		dfe->adfe_info3 = amdzen_df_read32(azn, df, inst, 0,
+		    AMDZEN_DF_F0_FBIINFO3);
+		dfe->adfe_syscfg = amdzen_df_read32(azn, df, inst, 1,
+		    AMDZEN_DF_F1_SYSCFG);
+		dfe->adfe_mask0 = amdzen_df_read32(azn, df, inst, 1,
+		    AMDZEN_DF_F1_FIDMASK0);
+		dfe->adfe_mask1 = amdzen_df_read32(azn, df, inst, 1,
+		    AMDZEN_DF_F1_FIDMASK1);
+
+		dfe->adfe_type = AMDZEN_DF_F0_FBIINFO0_TYPE(dfe->adfe_info0);
+		dfe->adfe_sdp_width =
+		    AMDZEN_DF_F0_FBIINFO0_SDP_WIDTH(dfe->adfe_info0);
+		if (AMDZEN_DF_F0_FBIINFO0_ENABLED(dfe->adfe_info0)) {
+			dfe->adfe_flags |= AMDZEN_DFE_F_ENABLED;
+		}
+		dfe->adfe_fti_width =
+		    AMDZEN_DF_F0_FBIINFO0_FTI_WIDTH(dfe->adfe_info0);
+		dfe->adfe_sdp_count =
+		    AMDZEN_DF_F0_FBIINFO0_SDP_PCOUNT(dfe->adfe_info0);
+		dfe->adfe_fti_count =
+		    AMDZEN_DF_F0_FBIINFO0_FTI_PCOUNT(dfe->adfe_info0);
+		if (AMDZEN_DF_F0_FBIINFO0_HAS_MCA(dfe->adfe_info0)) {
+			dfe->adfe_flags |= AMDZEN_DFE_F_MCA;
+		}
+		dfe->adfe_subtype =
+		    AMDZEN_DF_F0_FBIINFO0_SUBTYPE(dfe->adfe_info0);
+
+		dfe->adfe_inst_id =
+		    AMDZEN_DF_F0_FBIINFO3_INSTID(dfe->adfe_info3);
+		dfe->adfe_fabric_id =
+		    AMDZEN_DF_F0_FBIINFO3_FABID(dfe->adfe_info3);
+	}
+
+	df->adf_syscfg = amdzen_stub_get32(df->adf_funcs[1],
+	    AMDZEN_DF_F1_SYSCFG);
+	df->adf_nodeid = AMDZEN_DF_F1_SYSCFG_NODEID(df->adf_syscfg);
+	df->adf_mask0 = amdzen_stub_get32(df->adf_funcs[1],
+	    AMDZEN_DF_F1_FIDMASK0);
+	df->adf_mask1 = amdzen_stub_get32(df->adf_funcs[1],
+	    AMDZEN_DF_F1_FIDMASK1);
+}
+
+static void
+amdzen_find_nb(amdzen_t *azn, amdzen_df_t *df)
+{
+	amdzen_stub_t *stub;
+
+	for (stub = list_head(&azn->azn_nb_stubs); stub != NULL;
+	    stub = list_next(&azn->azn_nb_stubs, stub)) {
+		if (stub->azns_bus == df->adf_nb_busno) {
+			df->adf_flags |= AMDZEN_DF_F_FOUND_NB;
+			df->adf_nb = stub;
+			return;
+		}
+	}
+}
+
+static void
+amdzen_nexus_init(void *arg)
+{
+	uint_t i;
+	amdzen_t *azn = arg;
+
+	/*
+	 * First go through all of the stubs and assign the DF entries.
+	 */
+	mutex_enter(&azn->azn_mutex);
+	if (!amdzen_map_dfs(azn) || !amdzen_check_dfs(azn)) {
+		azn->azn_flags |= AMDZEN_F_MAP_ERROR;
+		goto done;
+	}
+
+	for (i = 0; i < AMDZEN_MAX_DFS; i++) {
+		amdzen_df_t *df = &azn->azn_dfs[i];
+
+		if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0)
+			continue;
+		amdzen_setup_df(azn, df);
+		amdzen_find_nb(azn, df);
+	}
+
+	/*
+	 * Not all children may be installed. As such, we do not treat the
+	 * failure of a child as fatal to the driver.
+	 */
+	mutex_exit(&azn->azn_mutex);
+	for (i = 0; i < ARRAY_SIZE(amdzen_children); i++) {
+		(void) amdzen_create_child(azn, &amdzen_children[i]);
+	}
+	mutex_enter(&azn->azn_mutex);
+
+done:
+	azn->azn_flags &= ~AMDZEN_F_ATTACH_DISPATCHED;
+	azn->azn_flags |= AMDZEN_F_ATTACH_COMPLETE;
+	azn->azn_taskqid = TASKQID_INVALID;
+	cv_broadcast(&azn->azn_cv);
+	mutex_exit(&azn->azn_mutex);
+}
+
+static int
+amdzen_stub_scan_cb(dev_info_t *dip, void *arg)
+{
+	amdzen_t *azn = arg;
+	uint16_t vid, did;
+	int *regs;
+	uint_t nregs, i;
+	boolean_t match = B_FALSE;
+
+	if (dip == ddi_root_node()) {
+		return (DDI_WALK_CONTINUE);
+	}
+
+	/*
+	 * If a node in question is not a pci node, then we have no interest in
+	 * it as all the stubs that we care about are related to pci devices.
+	 */
+	if (strncmp("pci", ddi_get_name(dip), 3) != 0) {
+		return (DDI_WALK_PRUNECHILD);
+	}
+
+	/*
+	 * If we can't get a device or vendor ID and prove that this is an AMD
+	 * part, then we don't care about it.
+	 */
+	vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "vendor-id", PCI_EINVAL16);
+	did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "device-id", PCI_EINVAL16);
+	if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) {
+		return (DDI_WALK_CONTINUE);
+	}
+
+	if (vid != AMDZEN_PCI_VID_AMD) {
+		return (DDI_WALK_CONTINUE);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(amdzen_nb_ids); i++) {
+		if (amdzen_nb_ids[i] == did) {
+			match = B_TRUE;
+		}
+	}
+
+	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "reg", &regs, &nregs) != DDI_PROP_SUCCESS) {
+		return (DDI_WALK_CONTINUE);
+	}
+
+	if (nregs == 0) {
+		ddi_prop_free(regs);
+		return (DDI_WALK_CONTINUE);
+	}
+
+	if (PCI_REG_BUS_G(regs[0]) == AMDZEN_DF_BUSNO &&
+	    PCI_REG_DEV_G(regs[0]) >= AMDZEN_DF_FIRST_DEVICE) {
+		match = B_TRUE;
+	}
+
+	ddi_prop_free(regs);
+	if (match) {
+		mutex_enter(&azn->azn_mutex);
+		azn->azn_nscanned++;
+		mutex_exit(&azn->azn_mutex);
+	}
+
+	return (DDI_WALK_CONTINUE);
+}
+
+static void
+amdzen_stub_scan(void *arg)
+{
+	amdzen_t *azn = arg;
+
+	mutex_enter(&azn->azn_mutex);
+	azn->azn_nscanned = 0;
+	mutex_exit(&azn->azn_mutex);
+
+	ddi_walk_devs(ddi_root_node(), amdzen_stub_scan_cb, azn);
+
+	mutex_enter(&azn->azn_mutex);
+	azn->azn_flags &= ~AMDZEN_F_SCAN_DISPATCHED;
+	azn->azn_flags |= AMDZEN_F_SCAN_COMPLETE;
+
+	if (azn->azn_nscanned == 0) {
+		azn->azn_flags |= AMDZEN_F_UNSUPPORTED;
+		azn->azn_taskqid = TASKQID_INVALID;
+		cv_broadcast(&azn->azn_cv);
+	} else if (azn->azn_npresent == azn->azn_nscanned) {
+		azn->azn_flags |= AMDZEN_F_ATTACH_DISPATCHED;
+		azn->azn_taskqid = taskq_dispatch(system_taskq,
+		    amdzen_nexus_init, azn, TQ_SLEEP);
+	}
+	mutex_exit(&azn->azn_mutex);
+}
+
+/*
+ * Unfortunately we can't really let the stubs detach as we may need them to be
+ * available for client operations. We may be able to improve this if we know
+ * that the actual nexus is going away. However, as long as it's active, we need
+ * all the stubs.
+ */
+int
+amdzen_detach_stub(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	if (cmd == DDI_SUSPEND) {
+		return (DDI_SUCCESS);
+	}
+
+	return (DDI_FAILURE);
+}
+
+int
+amdzen_attach_stub(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	int *regs, reg;
+	uint_t nregs, i;
+	uint16_t vid, did;
+	amdzen_stub_t *stub;
+	amdzen_t *azn = amdzen_data;
+	boolean_t valid = B_FALSE;
+	boolean_t nb = B_FALSE;
+
+	if (cmd == DDI_RESUME) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_ATTACH) {
+		return (DDI_FAILURE);
+	}
+
+	/*
+	 * Make sure that the stub that we've been asked to attach is a pci type
+	 * device. If not, then there is no reason for us to proceed.
+	 */
+	if (strncmp("pci", ddi_get_name(dip), 3) != 0) {
+		dev_err(dip, CE_WARN, "asked to attach a bad AMD Zen nexus "
+		    "stub: %s", ddi_get_name(dip));
+		return (DDI_FAILURE);
+	}
+	vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "vendor-id", PCI_EINVAL16);
+	did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "device-id", PCI_EINVAL16);
+	if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) {
+		dev_err(dip, CE_WARN, "failed to get PCI ID properties");
+		return (DDI_FAILURE);
+	}
+
+	if (vid != AMDZEN_PCI_VID_AMD) {
+		dev_err(dip, CE_WARN, "expected AMD vendor ID (0x%x), found "
+		    "0x%x", AMDZEN_PCI_VID_AMD, vid);
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+	    "reg", &regs, &nregs) != DDI_PROP_SUCCESS) {
+		dev_err(dip, CE_WARN, "failed to get 'reg' property");
+		return (DDI_FAILURE);
+	}
+
+	if (nregs == 0) {
+		ddi_prop_free(regs);
+		dev_err(dip, CE_WARN, "missing 'reg' property values");
+		return (DDI_FAILURE);
+	}
+	reg = *regs;
+	ddi_prop_free(regs);
+
+	for (i = 0; i < ARRAY_SIZE(amdzen_nb_ids); i++) {
+		if (amdzen_nb_ids[i] == did) {
+			valid = B_TRUE;
+			nb = B_TRUE;
+		}
+	}
+
+	if (!valid && PCI_REG_BUS_G(reg) == AMDZEN_DF_BUSNO &&
+	    PCI_REG_DEV_G(reg) >= AMDZEN_DF_FIRST_DEVICE) {
+		valid = B_TRUE;
+		nb = B_FALSE;
+	}
+
+	if (!valid) {
+		dev_err(dip, CE_WARN, "device %s didn't match the nexus list",
+		    ddi_get_name(dip));
+		return (DDI_FAILURE);
+	}
+
+	stub = kmem_alloc(sizeof (amdzen_stub_t), KM_SLEEP);
+	if (pci_config_setup(dip, &stub->azns_cfgspace) != DDI_SUCCESS) {
+		dev_err(dip, CE_WARN, "failed to set up config space");
+		kmem_free(stub, sizeof (amdzen_stub_t));
+		return (DDI_FAILURE);
+	}
+
+	stub->azns_dip = dip;
+	stub->azns_vid = vid;
+	stub->azns_did = did;
+	stub->azns_bus = PCI_REG_BUS_G(reg);
+	stub->azns_dev = PCI_REG_DEV_G(reg);
+	stub->azns_func = PCI_REG_FUNC_G(reg);
+	ddi_set_driver_private(dip, stub);
+
+	mutex_enter(&azn->azn_mutex);
+	azn->azn_npresent++;
+	if (nb) {
+		list_insert_tail(&azn->azn_nb_stubs, stub);
+	} else {
+		list_insert_tail(&azn->azn_df_stubs, stub);
+	}
+
+	if ((azn->azn_flags & AMDZEN_F_TASKQ_MASK) == AMDZEN_F_SCAN_COMPLETE &&
+	    azn->azn_nscanned == azn->azn_npresent) {
+		azn->azn_flags |= AMDZEN_F_ATTACH_DISPATCHED;
+		azn->azn_taskqid = taskq_dispatch(system_taskq,
+		    amdzen_nexus_init, azn, TQ_SLEEP);
+	}
+	mutex_exit(&azn->azn_mutex);
+
+	return (DDI_SUCCESS);
+}
+
+static int
+amdzen_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop,
+    void *arg, void *result)
+{
+	char buf[32];
+	dev_info_t *child;
+	const amdzen_child_data_t *acd;
+
+	switch (ctlop) {
+	case DDI_CTLOPS_REPORTDEV:
+		if (rdip == NULL) {
+			return (DDI_FAILURE);
+		}
+		cmn_err(CE_CONT, "amdzen nexus: %s@%s, %s%d\n",
+		    ddi_node_name(rdip), ddi_get_name_addr(rdip),
+		    ddi_driver_name(rdip), ddi_get_instance(rdip));
+		break;
+	case DDI_CTLOPS_INITCHILD:
+		child = arg;
+		if (child == NULL) {
+			dev_err(dip, CE_WARN, "!no child passed for "
+			    "DDI_CTLOPS_INITCHILD");
+		}
+
+		acd = ddi_get_parent_data(child);
+		if (acd == NULL) {
+			dev_err(dip, CE_WARN, "!missing child parent data");
+			return (DDI_FAILURE);
+		}
+
+		if (snprintf(buf, sizeof (buf), "%d", acd->acd_addr) >=
+		    sizeof (buf)) {
+			dev_err(dip, CE_WARN, "!failed to construct device "
+			    "addr due to overflow");
+			return (DDI_FAILURE);
+		}
+
+		ddi_set_name_addr(child, buf);
+		break;
+	case DDI_CTLOPS_UNINITCHILD:
+		child = arg;
+		if (child == NULL) {
+			dev_err(dip, CE_WARN, "!no child passed for "
+			    "DDI_CTLOPS_UNINITCHILD");
+		}
+
+		ddi_set_name_addr(child, NULL);
+		break;
+	default:
+		return (ddi_ctlops(dip, rdip, ctlop, arg, result));
+	}
+	return (DDI_SUCCESS);
+}
+
+static int
+amdzen_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	amdzen_t *azn = amdzen_data;
+
+	if (cmd == DDI_RESUME) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_ATTACH) {
+		return (DDI_FAILURE);
+	}
+
+	mutex_enter(&azn->azn_mutex);
+	if (azn->azn_dip != NULL) {
+		dev_err(dip, CE_WARN, "driver is already attached!");
+		mutex_exit(&azn->azn_mutex);
+		return (DDI_FAILURE);
+	}
+
+	azn->azn_dip = dip;
+	azn->azn_taskqid = taskq_dispatch(system_taskq, amdzen_stub_scan,
+	    azn, TQ_SLEEP);
+	azn->azn_flags |= AMDZEN_F_SCAN_DISPATCHED;
+	mutex_exit(&azn->azn_mutex);
+
+	return (DDI_SUCCESS);
+}
+
+static int
+amdzen_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	amdzen_t *azn = amdzen_data;
+
+	if (cmd == DDI_SUSPEND) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_DETACH) {
+		return (DDI_FAILURE);
+	}
+
+	mutex_enter(&azn->azn_mutex);
+	while (azn->azn_taskqid != TASKQID_INVALID) {
+		cv_wait(&azn->azn_cv, &azn->azn_mutex);
+	}
+
+	/*
+	 * If we've attached any stub drivers, e.g. this platform is important
+	 * for us, then we fail detach.
+	 */
+	if (!list_is_empty(&azn->azn_df_stubs) ||
+	    !list_is_empty(&azn->azn_nb_stubs)) {
+		mutex_exit(&azn->azn_mutex);
+		return (DDI_FAILURE);
+	}
+
+	azn->azn_dip = NULL;
+	mutex_exit(&azn->azn_mutex);
+
+	return (DDI_SUCCESS);
+}
+
+static void
+amdzen_free(void)
+{
+	if (amdzen_data == NULL) {
+		return;
+	}
+
+	VERIFY(list_is_empty(&amdzen_data->azn_df_stubs));
+	list_destroy(&amdzen_data->azn_df_stubs);
+	VERIFY(list_is_empty(&amdzen_data->azn_nb_stubs));
+	list_destroy(&amdzen_data->azn_nb_stubs);
+	cv_destroy(&amdzen_data->azn_cv);
+	mutex_destroy(&amdzen_data->azn_mutex);
+	kmem_free(amdzen_data, sizeof (amdzen_t));
+	amdzen_data = NULL;
+}
+
+static void
+amdzen_alloc(void)
+{
+	amdzen_data = kmem_zalloc(sizeof (amdzen_t), KM_SLEEP);
+	mutex_init(&amdzen_data->azn_mutex, NULL, MUTEX_DRIVER, NULL);
+	list_create(&amdzen_data->azn_df_stubs, sizeof (amdzen_stub_t),
+	    offsetof(amdzen_stub_t, azns_link));
+	list_create(&amdzen_data->azn_nb_stubs, sizeof (amdzen_stub_t),
+	    offsetof(amdzen_stub_t, azns_link));
+	cv_init(&amdzen_data->azn_cv, NULL, CV_DRIVER, NULL);
+}
+
+struct bus_ops amdzen_bus_ops = {
+	.busops_rev = BUSO_REV,
+	.bus_map = nullbusmap,
+	.bus_dma_map = ddi_no_dma_map,
+	.bus_dma_allochdl = ddi_no_dma_allochdl,
+	.bus_dma_freehdl = ddi_no_dma_freehdl,
+	.bus_dma_bindhdl = ddi_no_dma_bindhdl,
+	.bus_dma_unbindhdl = ddi_no_dma_unbindhdl,
+	.bus_dma_flush = ddi_no_dma_flush,
+	.bus_dma_win = ddi_no_dma_win,
+	.bus_dma_ctl = ddi_no_dma_mctl,
+	.bus_prop_op = ddi_bus_prop_op,
+	.bus_ctl = amdzen_bus_ctl
+};
+
+static struct dev_ops amdzen_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = nodev,
+	.devo_identify = nulldev,
+	.devo_probe = nulldev,
+	.devo_attach = amdzen_attach,
+	.devo_detach = amdzen_detach,
+	.devo_reset = nodev,
+	.devo_quiesce = ddi_quiesce_not_needed,
+	.devo_bus_ops = &amdzen_bus_ops
+};
+
+static struct modldrv amdzen_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "AMD Zen Nexus Driver",
+	.drv_dev_ops = &amdzen_dev_ops
+};
+
+static struct modlinkage amdzen_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &amdzen_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	int ret;
+
+	if (cpuid_getvendor(CPU) != X86_VENDOR_AMD) {
+		return (ENOTSUP);
+	}
+
+	if ((ret = mod_install(&amdzen_modlinkage)) == 0) {
+		amdzen_alloc();
+	}
+
+	return (ret);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&amdzen_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	int ret;
+
+	if ((ret = mod_remove(&amdzen_modlinkage)) == 0) {
+		amdzen_free();
+	}
+
+	return (ret);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/amdzen.conf	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,16 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+name="amdzen" parent="pseudo" instance=0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/amdzen.h	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,305 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+#ifndef _AMDZEN_H
+#define	_AMDZEN_H
+
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/list.h>
+#include <sys/pci.h>
+#include <sys/taskq.h>
+#include <sys/bitmap.h>
+
+/*
+ * This header describes properties of the data fabric and our internal state
+ * for the Zen Nexus driver.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The data fabric devices are always defined to be on PCI bus zero starting at
+ * device 0x18.
+ */
+#define	AMDZEN_DF_BUSNO		0x00
+#define	AMDZEN_DF_FIRST_DEVICE	0x18
+
+/*
+ * The maximum amount of Data Fabric node's we can see. In Zen 1 there were up
+ * to four per package.
+ */
+#define	AMDZEN_MAX_DFS		0x8
+
+/*
+ * The maximum number of PCI functions we expect to encounter on the data
+ * fabric.
+ */
+#define	AMDZEN_MAX_DF_FUNCS	0x8
+
+
+/*
+ * Registers in the data fabric space that we care about for the purposes of the
+ * nexus driver understanding itself.
+ */
+
+/*
+ * This set of registers provides us access to the count of instances in the
+ * data fabric and then a number of different pieces of information about them
+ * like their type. Note, these registers require indirect access because the
+ * information cannot be broadcast.
+ */
+#define	AMDZEN_DF_F0_FBICNT	0x40
+#define	AMDZEN_DF_F0_FBICNT_COUNT(x)	BITX(x, 7, 0)
+#define	AMDZEN_DF_F0_FBIINFO0	0x44
+#define	AMDZEN_DF_F0_FBIINFO0_TYPE(x)		BITX(x, 3, 0)
+typedef enum {
+	AMDZEN_DF_TYPE_CCM = 0,
+	AMDZEN_DF_TYPE_GCM,
+	AMDZEN_DF_TYPE_NCM,
+	AMDZEN_DF_TYPE_IOMS,
+	AMDZEN_DF_TYPE_CS,
+	AMDZEN_DF_TYPE_TCDX,
+	AMDZEN_DF_TYPE_PIE,
+	AMDZEN_DF_TYPE_SPF,
+	AMDZEN_DF_TYPE_LLC,
+	AMDZEN_DF_TYPE_CAKE
+} amdzen_df_type_t;
+#define	AMDZEN_DF_F0_FBIINFO0_SDP_WIDTH(x)	BITX(x, 5, 4)
+typedef enum {
+	AMDZEN_DF_SDP_W_64 = 0,
+	AMDZEN_DF_SDP_W_128,
+	AMDZEN_DF_SDP_W_256,
+	AMDZEN_DF_SDP_W_512
+} amdzen_df_sdp_width_t;
+#define	AMDZEN_DF_F0_FBIINFO0_ENABLED(x)	BITX(x, 6, 6)
+#define	AMDZEN_DF_F0_FBIINFO0_FTI_WIDTH(x)	BITX(x, 9, 8)
+typedef enum {
+	AMDZEN_DF_FTI_W_64 = 0,
+	AMDZEN_DF_FTI_W_128,
+	AMDZEN_DF_FTI_W_256,
+	AMDZEN_DF_FTI_W_512
+} amdzen_df_fti_width_t;
+#define	AMDZEN_DF_F0_FBIINFO0_SDP_PCOUNT(x)	BITX(x, 13, 12)
+#define	AMDZEN_DF_F0_FBIINFO0_FTI_PCOUNT(x)	BITX(x, 18, 16)
+#define	AMDZEN_DF_F0_FBIINFO0_HAS_MCA(x)	BITX(x, 23, 23)
+#define	AMDZEN_DF_F0_FBIINFO0_SUBTYPE(x)	BITX(x, 26, 24)
+#define	AMDZEN_DF_SUBTYPE_NONE	0
+typedef enum {
+	AMDZEN_DF_CAKE_SUBTYPE_GMI = 1,
+	AMDZEN_DF_CAKE_SUBTYPE_xGMI = 2
+} amdzen_df_cake_subtype_t;
+
+typedef enum {
+	AMDZEN_DF_IOM_SUBTYPE_IOHUB = 1,
+} amdzen_df_iom_subtype_t;
+
+typedef enum {
+	AMDZEN_DF_CS_SUBTYPE_UMC = 1,
+	AMDZEN_DF_CS_SUBTYPE_CCIX = 2
+} amdzen_df_cs_subtype_t;
+
+#define	AMDZEN_DF_F0_FBIINFO1	0x48
+#define	AMDZEN_DF_F0_FBIINFO1_FTI0_NINSTID(x)	BITX(x, 7, 0)
+#define	AMDZEN_DF_F0_FBIINFO1_FTI1_NINSTID(x)	BITX(x, 15, 8)
+#define	AMDZEN_DF_F0_FBIINFO1_FTI2_NINSTID(x)	BITX(x, 23, 16)
+#define	AMDZEN_DF_F0_FBIINFO1_FTI3_NINSTID(x)	BITX(x, 31, 24)
+#define	AMDZEN_DF_F0_FBIINFO2	0x4c
+#define	AMDZEN_DF_F0_FBIINFO2_FTI4_NINSTID(x)	BITX(x, 7, 0)
+#define	AMDZEN_DF_F0_FBIINFO2_FTI5_NINSTID(x)	BITX(x, 15, 8)
+#define	AMDZEN_DF_F0_FBIINFO3	0x50
+#define	AMDZEN_DF_F0_FBIINFO3_INSTID(x)		BITX(x, 7, 0)
+#define	AMDZEN_DF_F0_FBIINFO3_FABID(x)		BITX(x, 13, 8)
+
+/*
+ * This register contains the information about the configuration of PCIe buses.
+ * We care about finding which one has our BUS A, which is required to map it to
+ * the northbridge.
+ */
+#define	AMDZEN_DF_F0_CFG_ADDR_CTL	0x84
+#define	AMDZEN_DF_F0_CFG_ADDR_CTL_BUS_NUM(x)	BITX(x, 7, 0)
+
+/*
+ * Registers that describe how the system is actually put together.
+ */
+#define	AMDZEN_DF_F1_SYSCFG	0x200
+#define	AMDZEN_DF_F1_SYSCFG_DIE_PRESENT(X)	BITX(x, 7, 0)
+#define	AMDZEN_DF_F1_SYSCFG_DIE_TYPE(x)		BITX(x, 18, 11)
+#define	AMDZEN_DF_F1_SYSCFG_MYDIE_TYPE(x)	BITX(x, 24, 23)
+typedef enum {
+	AMDZEN_DF_DIE_TYPE_CPU	= 0,
+	AMDZEN_DF_DIE_TYPE_APU,
+	AMDZEN_DF_DIE_TYPE_dGPU
+} amdzen_df_die_type_t;
+#define	AMDZEN_DF_F1_SYSCFG_OTHERDIE_TYPE(x)	BITX(x, 26, 25)
+#define	AMDZEN_DF_F1_SYSCFG_OTHERSOCK(x)	BITX(x, 27, 27)
+#define	AMDZEN_DF_F1_SYSCFG_NODEID(x)		BITX(x, 30, 28)
+
+#define	AMDZEN_DF_F1_FIDMASK0	0x208
+#define	AMDZEN_DF_F1_FIDMASK0_COMP_MASK(x)	BITX(x, 9, 0)
+#define	AMDZEN_DF_F1_FIDMASK0_NODE_MASK(x)	BITX(x, 25, 16)
+#define	AMDZEN_DF_F1_FIDMASK1	0x20C
+#define	AMDZEN_DF_F1_FIDMASK1_NODE_SHIFT(x)	BITX(x, 3, 0)
+#define	AMDZEN_DF_F1_FIDMASK1_SKT_SHIFT(x)	BITX(x, 9, 8)
+#define	AMDZEN_DF_F1_FIDMASK1_DIE_MASK(x)	BITX(x, 18, 16)
+#define	AMDZEN_DF_F1_FIDMASK1_SKT_MASK(x)	BITX(x, 26, 24)
+
+/*
+ * These two registers define information about the PSP and SMU on local and
+ * remote dies (from the context of the DF instance). The bits are the same.
+ */
+#define	AMDZEN_DF_F1_PSPSMU_LOCAL	0x268
+#define	AMDZEN_DF_F1_PSPSMU_REMOTE	0x268
+#define	AMDZEN_DF_F1_PSPSMU_SMU_VALID(x)	BITX(x, 0, 0)
+#define	AMDZEN_DF_F1_PSPSMU_SMU_UNITID(x)	BITX(x, 6, 1)
+#define	AMDZEN_DF_F1_PSPSMU_SMU_COMPID(x)	BITX(x, 15, 8)
+#define	AMDZEN_DF_F1_PSPSMU_PSP_VALID(x)	BITX(x, 16, 16)
+#define	AMDZEN_DF_F1_PSPSMU_PSP_UNITID(x)	BITX(x, 22, 17)
+#define	AMDZEN_DF_F1_PSPSMU_PSP_COMPID(x)	BITX(x, 31, 24)
+
+#define	AMDZEN_DF_F1_CAKE_ENCR		0x2cc
+
+/*
+ * These registers are used to define Indirect Access, commonly known as FICAA
+ * and FICAD for the system. While there are multiple copies of the indirect
+ * access registers in device 4, we're only allowed access to one set of those
+ * (which are the ones present here). Specifically the OS is given access to set
+ * 3.
+ */
+#define	AMDZEN_DF_F4_FICAA	0x5c
+#define	AMDZEN_DF_F4_FICAA_TARG_INST	(1 << 0)
+#define	AMDZEN_DF_F4_FICAA_SET_REG(x)	((x) & 0x3fc)
+#define	AMDZEN_DF_F4_FICAA_SET_FUNC(x)	(((x) & 0x7) << 11)
+#define	AMDZEN_DF_F4_FICAA_SET_64B	(1 << 14)
+#define	AMDZEN_DF_F4_FICAA_SET_INST(x)	(((x) & 0xff) << 16)
+#define	AMDZEN_DF_F4_FICAD_LO	0x98
+#define	AMDZEN_DF_F4_FICAD_HI	0x9c
+
+/*
+ * Northbridge registers that are relevant for the nexus, mostly for SMN.
+ */
+#define	AMDZEN_NB_SMN_ADDR	0x60
+#define	AMDZEN_NB_SMN_DATA	0x64
+
+/*
+ * AMD PCI ID for reference
+ */
+#define	AMDZEN_PCI_VID_AMD	0x1022
+
+typedef enum {
+	AMDZEN_STUB_TYPE_DF,
+	AMDZEN_STUB_TYPE_NB
+} amdzen_stub_type_t;
+
+typedef struct {
+	list_node_t		azns_link;
+	dev_info_t		*azns_dip;
+	uint16_t		azns_vid;
+	uint16_t		azns_did;
+	uint16_t		azns_bus;
+	uint16_t		azns_dev;
+	uint16_t		azns_func;
+	ddi_acc_handle_t	azns_cfgspace;
+} amdzen_stub_t;
+
+typedef enum  {
+	AMDZEN_DFE_F_MCA	= 1 << 0,
+	AMDZEN_DFE_F_ENABLED	= 1 << 1
+} amdzen_df_ent_flags_t;
+
+typedef struct {
+	uint8_t adfe_drvid;
+	amdzen_df_ent_flags_t adfe_flags;
+	amdzen_df_type_t adfe_type;
+	uint8_t adfe_subtype;
+	uint8_t adfe_fabric_id;
+	uint8_t adfe_inst_id;
+	amdzen_df_sdp_width_t adfe_sdp_width;
+	amdzen_df_fti_width_t adfe_fti_width;
+	uint8_t adfe_sdp_count;
+	uint8_t adfe_fti_count;
+	uint32_t adfe_info0;
+	uint32_t adfe_info1;
+	uint32_t adfe_info2;
+	uint32_t adfe_info3;
+	uint32_t adfe_syscfg;
+	uint32_t adfe_mask0;
+	uint32_t adfe_mask1;
+} amdzen_df_ent_t;
+
+typedef enum {
+	AMDZEN_DF_F_VALID		= 1 << 0,
+	AMDZEN_DF_F_FOUND_NB		= 1 << 1
+} amdzen_df_flags_t;
+
+typedef struct {
+	amdzen_df_flags_t	adf_flags;
+	uint_t		adf_nb_busno;
+	amdzen_stub_t	*adf_funcs[AMDZEN_MAX_DF_FUNCS];
+	amdzen_stub_t	*adf_nb;
+	uint_t		adf_nents;
+	amdzen_df_ent_t	*adf_ents;
+	uint32_t	adf_nodeid;
+	uint32_t	adf_syscfg;
+	uint32_t	adf_mask0;
+	uint32_t	adf_mask1;
+} amdzen_df_t;
+
+typedef enum {
+	AMDZEN_F_UNSUPPORTED		= 1 << 0,
+	AMDZEN_F_DEVICE_ERROR		= 1 << 1,
+	AMDZEN_F_MAP_ERROR		= 1 << 2,
+	AMDZEN_F_SCAN_DISPATCHED	= 1 << 3,
+	AMDZEN_F_SCAN_COMPLETE		= 1 << 4,
+	AMDZEN_F_ATTACH_DISPATCHED	= 1 << 5,
+	AMDZEN_F_ATTACH_COMPLETE	= 1 << 6
+} amdzen_flags_t;
+
+#define	AMDZEN_F_TASKQ_MASK	(AMDZEN_F_SCAN_DISPATCHED | \
+    AMDZEN_F_SCAN_COMPLETE | AMDZEN_F_ATTACH_DISPATCHED | \
+    AMDZEN_F_ATTACH_COMPLETE)
+
+typedef struct amdzen {
+	kmutex_t	azn_mutex;
+	kcondvar_t	azn_cv;
+	amdzen_flags_t	azn_flags;
+	dev_info_t	*azn_dip;
+	taskqid_t	azn_taskqid;
+	uint_t		azn_nscanned;
+	uint_t		azn_npresent;
+	list_t		azn_df_stubs;
+	list_t		azn_nb_stubs;
+	uint_t		azn_ndfs;
+	amdzen_df_t	azn_dfs[AMDZEN_MAX_DFS];
+} amdzen_t;
+
+typedef enum {
+	AMDZEN_C_SMNTEMP = 1,
+	AMDZEN_C_USMN
+} amdzen_child_t;
+
+/*
+ * Functions for stubs.
+ */
+extern int amdzen_attach_stub(dev_info_t *, ddi_attach_cmd_t);
+extern int amdzen_detach_stub(dev_info_t *, ddi_detach_cmd_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AMDZEN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/amdzen_client.h	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,36 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+#ifndef _AMDZEN_CLIENT_H
+#define	_AMDZEN_CLIENT_H
+
+/*
+ * This header provides client routines to clients of the amdzen nexus driver.
+ */
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int amdzen_c_smn_read32(uint_t df, uint32_t reg, uint32_t *);
+extern uint_t amdzen_c_df_count(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AMDZEN_CLIENT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/amdzen_stub.c	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,81 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * A stub driver for the AMD Zen Nexus. This is used to help us get all the
+ * relevant PCI devices into one place. See uts/intel/io/amdzen/amdzen.c for
+ * more details.
+ */
+
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/modctl.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#include "amdzen.h"
+
+static int
+amdzen_stub_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	return (amdzen_attach_stub(dip, cmd));
+}
+
+static int
+amdzen_stub_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	return (amdzen_detach_stub(dip, cmd));
+}
+
+static struct dev_ops amdzen_stub_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = nodev,
+	.devo_identify = nodev,
+	.devo_probe = nulldev,
+	.devo_attach = amdzen_stub_attach,
+	.devo_detach = amdzen_stub_detach,
+	.devo_reset = nodev,
+	.devo_quiesce = ddi_quiesce_not_needed
+};
+
+static struct modldrv amdzen_stub_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "AMD Zen Nexus Stub driver",
+	.drv_dev_ops = &amdzen_stub_dev_ops
+};
+
+static struct modlinkage amdzen_stub_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &amdzen_stub_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	return (mod_install(&amdzen_stub_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&amdzen_stub_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&amdzen_stub_modlinkage));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/smntemp.c	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,343 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * This implements a temperature sensor for AMD Zen family products that rely
+ * upon the SMN framework for getting temperature information.
+ */
+
+#include <sys/modctl.h>
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/types.h>
+#include <sys/cred.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/cmn_err.h>
+#include <sys/x86_archext.h>
+#include <sys/cpuvar.h>
+#include <sys/sensors.h>
+#include <sys/sysmacros.h>
+#include <amdzen_client.h>
+
+/*
+ * The following are register offsets and the meaning of their bits related to
+ * temperature. These addresses reside in the System Management Network which is
+ * accessed through the northbridge. They are not addresses in PCI configuration
+ * space.
+ */
+#define	SMN_SMU_THERMAL_CURTEMP			0x00059800
+#define	SMN_SMU_THERMAL_CURTEMP_TEMPERATURE(x)	((x) >> 21)
+#define	SMN_SMU_THERMAL_CURTEMP_RANGE_SEL		(1 << 19)
+
+#define	SMN_SMU_THERMAL_CURTEMP_RANGE_ADJ		(-49)
+#define	SMN_SMU_THERMAL_CURTEMP_DECIMAL_BITS		3
+#define	SMN_SMU_THERMAL_CURTEMP_BITS_MASK		0x7
+
+/*
+ * The temperature sensor in Family 17 is measured in terms of 0.125 C steps.
+ */
+#define	SMN_THERMAL_GRANULARITY	8
+
+typedef enum {
+	SMNTEMP_F_MUTEX	= 1 << 0
+} smntemp_flags_t;
+
+typedef struct {
+	uint_t stt_dfno;
+	id_t stt_ksensor;
+	struct smntemp *stt_smn;
+	smntemp_flags_t stt_flags;
+	kmutex_t stt_mutex;
+	hrtime_t stt_last_read;
+	uint32_t stt_reg;
+	int64_t stt_temp;
+} smntemp_temp_t;
+
+typedef struct smntemp {
+	dev_info_t *smn_dip;
+	uint_t smn_ntemps;
+	int smn_offset;
+	smntemp_temp_t *smn_temps;
+} smntemp_t;
+
+static smntemp_t smntemp_data;
+
+/*
+ * AMD processors report a control temperature (called Tctl) which may be
+ * different from the junction temperature, which is the value that is actually
+ * measured from the die (sometimes called Tdie or Tjct). This is done so that
+ * socket-based environmental monitoring can be consistent from a platform
+ * perspective, but doesn't help us. Unfortunately, these values aren't in
+ * datasheets that we can find, but have been documented partially in a series
+ * of blog posts by AMD when discussing their 'Ryzen Master' monitoring software
+ * for Windows.
+ *
+ * The brand strings below may contain partial matches such in the Threadripper
+ * cases so we can match the entire family of processors. The offset value is
+ * the quantity in degrees that we should adjust Tctl to reach Tdie.
+ */
+typedef struct {
+	const char	*sto_brand;
+	uint_t		sto_family;
+	int		sto_off;
+} smntemp_offset_t;
+
+static const smntemp_offset_t smntemp_offsets[] = {
+	{ "AMD Ryzen 5 1600X", 0x17, -20 },
+	{ "AMD Ryzen 7 1700X", 0x17, -20 },
+	{ "AMD Ryzen 7 1800X", 0x17, -20 },
+	{ "AMD Ryzen 7 2700X", 0x17, -10 },
+	{ "AMD Ryzen Threadripper 19", 0x17, -27 },
+	{ "AMD Ryzen Threadripper 29", 0x17, -27 },
+	{ NULL }
+};
+
+static int
+smntemp_temp_update(smntemp_t *smn, smntemp_temp_t *stt)
+{
+	int ret;
+	uint32_t reg;
+	int64_t raw, decimal;
+
+	ASSERT(MUTEX_HELD((&stt->stt_mutex)));
+
+	if ((ret = amdzen_c_smn_read32(stt->stt_dfno, SMN_SMU_THERMAL_CURTEMP,
+	    &reg)) != 0) {
+		return (ret);
+	}
+
+	stt->stt_last_read = gethrtime();
+	stt->stt_reg = reg;
+	raw = SMN_SMU_THERMAL_CURTEMP_TEMPERATURE(reg) >>
+	    SMN_SMU_THERMAL_CURTEMP_DECIMAL_BITS;
+	decimal = SMN_SMU_THERMAL_CURTEMP_TEMPERATURE(reg) &
+	    SMN_SMU_THERMAL_CURTEMP_BITS_MASK;
+	if ((reg & SMN_SMU_THERMAL_CURTEMP_RANGE_SEL) != 0) {
+		raw += SMN_SMU_THERMAL_CURTEMP_RANGE_ADJ;
+	}
+	raw += smn->smn_offset;
+	stt->stt_temp = raw << SMN_SMU_THERMAL_CURTEMP_DECIMAL_BITS;
+	stt->stt_temp += decimal;
+
+	return (0);
+}
+
+static int
+smntemp_temp_read(void *arg, sensor_ioctl_scalar_t *temp)
+{
+	int ret;
+	smntemp_temp_t *stt = arg;
+	smntemp_t *smn = stt->stt_smn;
+
+	mutex_enter(&stt->stt_mutex);
+	if ((ret = smntemp_temp_update(smn, stt)) != 0) {
+		mutex_exit(&stt->stt_mutex);
+		return (ret);
+	}
+
+	temp->sis_unit = SENSOR_UNIT_CELSIUS;
+	temp->sis_value = stt->stt_temp;
+	temp->sis_gran = SMN_THERMAL_GRANULARITY;
+	mutex_exit(&stt->stt_mutex);
+
+	return (0);
+}
+
+static const ksensor_ops_t smntemp_temp_ops = {
+	.kso_kind = ksensor_kind_temperature,
+	.kso_scalar = smntemp_temp_read
+};
+
+static void
+smntemp_cleanup(smntemp_t *smn)
+{
+	if (smn->smn_temps != NULL) {
+		uint_t i;
+
+		(void) ksensor_remove(smn->smn_dip, KSENSOR_ALL_IDS);
+		for (i = 0; i < smn->smn_ntemps; i++) {
+			if ((smn->smn_temps[i].stt_flags & SMNTEMP_F_MUTEX) !=
+			    0) {
+				mutex_destroy(&smn->smn_temps[i].stt_mutex);
+				smn->smn_temps[i].stt_flags &= ~SMNTEMP_F_MUTEX;
+			}
+		}
+		kmem_free(smn->smn_temps, sizeof (smntemp_temp_t) *
+		    smn->smn_ntemps);
+		smn->smn_temps = NULL;
+		smn->smn_ntemps = 0;
+	}
+
+	if (smn->smn_dip != NULL) {
+		ddi_remove_minor_node(smn->smn_dip, NULL);
+		ddi_set_driver_private(smn->smn_dip, NULL);
+		smn->smn_dip = NULL;
+	}
+}
+
+static boolean_t
+smntemp_find_offset(smntemp_t *smn)
+{
+	uint_t i, family;
+	char buf[256];
+
+	if (cpuid_getbrandstr(CPU, buf, sizeof (buf)) >= sizeof (buf)) {
+		dev_err(smn->smn_dip, CE_WARN, "!failed to read processor "
+		    "brand string, brand larger than internal buffer");
+		return (B_FALSE);
+	}
+
+	family = cpuid_getfamily(CPU);
+
+	for (i = 0; i < ARRAY_SIZE(smntemp_offsets); i++) {
+		if (family != smntemp_offsets[i].sto_family)
+			continue;
+		if (strncmp(buf, smntemp_offsets[i].sto_brand,
+		    strlen(smntemp_offsets[i].sto_brand)) == 0) {
+			smn->smn_offset = smntemp_offsets[i].sto_off;
+			break;
+		}
+	}
+
+	return (B_TRUE);
+}
+
+static int
+smntemp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	uint_t i;
+	smntemp_t *smntemp = &smntemp_data;
+
+	if (cmd == DDI_RESUME) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_ATTACH) {
+		return (DDI_FAILURE);
+	}
+
+	if (smntemp->smn_dip != NULL) {
+		dev_err(dip, CE_WARN, "!smntemp already attached");
+		return (DDI_FAILURE);
+	}
+	smntemp->smn_dip = dip;
+	ddi_set_driver_private(dip, smntemp);
+
+	if (!smntemp_find_offset(smntemp)) {
+		goto err;
+	}
+
+	smntemp->smn_ntemps = amdzen_c_df_count();
+	if (smntemp->smn_ntemps == 0) {
+		dev_err(dip, CE_WARN, "!found zero DFs, can't attach smntemp");
+		goto err;
+	}
+	smntemp->smn_temps = kmem_zalloc(sizeof (smntemp_temp_t) *
+	    smntemp->smn_ntemps, KM_SLEEP);
+	for (i = 0; i < smntemp->smn_ntemps; i++) {
+		int ret;
+		char buf[128];
+
+		smntemp->smn_temps[i].stt_smn = smntemp;
+		smntemp->smn_temps[i].stt_dfno = i;
+		mutex_init(&smntemp->smn_temps[i].stt_mutex, NULL, MUTEX_DRIVER,
+		    NULL);
+		smntemp->smn_temps[i].stt_flags |= SMNTEMP_F_MUTEX;
+
+		if (snprintf(buf, sizeof (buf), "procnode.%u", i) >=
+		    sizeof (buf)) {
+			dev_err(dip, CE_WARN, "!unexpected buffer name overrun "
+			    "assembling temperature minor %u", i);
+			goto err;
+		}
+
+		if ((ret = ksensor_create(dip, &smntemp_temp_ops,
+		    &smntemp->smn_temps[i], buf, DDI_NT_SENSOR_TEMP_CPU,
+		    &smntemp->smn_temps[i].stt_ksensor)) != 0) {
+			dev_err(dip, CE_WARN, "!failed to create sensor %s: %d",
+			    buf, ret);
+			goto err;
+		}
+	}
+
+	return (DDI_SUCCESS);
+
+err:
+	smntemp_cleanup(smntemp);
+	return (DDI_FAILURE);
+}
+
+static int
+smntemp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	smntemp_t *smntemp = &smntemp_data;
+
+	if (cmd == DDI_SUSPEND) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_DETACH) {
+		return (DDI_FAILURE);
+	}
+
+	if (smntemp->smn_dip == NULL) {
+		dev_err(smntemp->smn_dip, CE_WARN, "!asked to detach smn "
+		    "instance %d that was never attached",
+		    ddi_get_instance(dip));
+		return (DDI_FAILURE);
+	}
+
+	smntemp_cleanup(smntemp);
+	return (DDI_SUCCESS);
+}
+
+static struct dev_ops smntemp_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = nodev,
+	.devo_identify = nulldev,
+	.devo_probe = nulldev,
+	.devo_attach = smntemp_attach,
+	.devo_detach = smntemp_detach,
+	.devo_reset = nodev,
+	.devo_quiesce = ddi_quiesce_not_needed,
+};
+
+static struct modldrv smntemp_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "AMD SMN Temperature Driver",
+	.drv_dev_ops = &smntemp_dev_ops
+};
+
+static struct modlinkage smntemp_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &smntemp_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	return (mod_install(&smntemp_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&smntemp_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&smntemp_modlinkage));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/usmn.c	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,275 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * A device driver that provides user access to the AMD System Management
+ * Network for debugging purposes.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/open.h>
+#include <sys/cred.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/stat.h>
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/cmn_err.h>
+#include <sys/policy.h>
+#include <amdzen_client.h>
+
+#include "usmn.h"
+
+typedef struct usmn {
+	dev_info_t *usmn_dip;
+	uint_t usmn_ndfs;
+} usmn_t;
+
+static usmn_t usmn_data;
+
+static int
+usmn_open(dev_t *devp, int flags, int otype, cred_t *credp)
+{
+	minor_t m;
+	usmn_t *usmn = &usmn_data;
+
+	if (crgetzoneid(credp) != GLOBAL_ZONEID ||
+	    secpolicy_hwmanip(credp) != 0) {
+		return (EPERM);
+	}
+
+	if ((flags & (FEXCL | FNDELAY | FNONBLOCK)) != 0) {
+		return (EINVAL);
+	}
+
+	if (otype != OTYP_CHR) {
+		return (EINVAL);
+	}
+
+	m = getminor(*devp);
+	if (m >= usmn->usmn_ndfs) {
+		return (ENXIO);
+	}
+
+	return (0);
+}
+
+static int
+usmn_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+    int *rvalp)
+{
+	uint_t dfno;
+	usmn_t *usmn = &usmn_data;
+	usmn_reg_t usr;
+
+	if (cmd != USMN_READ && cmd != USMN_WRITE) {
+		return (ENOTTY);
+	}
+
+	dfno = getminor(dev);
+	if (dfno >= usmn->usmn_ndfs) {
+		return (ENXIO);
+	}
+
+	if (crgetzoneid(credp) != GLOBAL_ZONEID ||
+	    secpolicy_hwmanip(credp) != 0) {
+		return (EPERM);
+	}
+
+	if (ddi_copyin((void *)arg, &usr, sizeof (usr), mode & FKIOCTL) != 0) {
+		return (EFAULT);
+	}
+
+	if (cmd == USMN_READ) {
+		int ret;
+
+		ret = amdzen_c_smn_read32(dfno, usr.usr_addr, &usr.usr_data);
+		if (ret != 0) {
+			return (ret);
+		}
+	} else {
+		return (ENOTSUP);
+	}
+
+	if (ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) {
+		return (EFAULT);
+	}
+
+	return (0);
+}
+
+static int
+usmn_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+	return (0);
+}
+
+static void
+usmn_cleanup(usmn_t *usmn)
+{
+	ddi_remove_minor_node(usmn->usmn_dip, NULL);
+	usmn->usmn_ndfs = 0;
+	usmn->usmn_dip = NULL;
+}
+
+static int
+usmn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	usmn_t *usmn = &usmn_data;
+
+	if (cmd == DDI_RESUME) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_ATTACH) {
+		return (DDI_FAILURE);
+	}
+
+	if (usmn->usmn_dip != NULL) {
+		dev_err(dip, CE_WARN, "!usmn is already attached to a "
+		    "dev_info_t: %p", usmn->usmn_dip);
+		return (DDI_FAILURE);
+	}
+
+	usmn->usmn_dip = dip;
+	usmn->usmn_ndfs = amdzen_c_df_count();
+	for (uint_t i = 0; i < usmn->usmn_ndfs; i++) {
+		char buf[32];
+
+		(void) snprintf(buf, sizeof (buf), "usmn.%u", i);
+		if (ddi_create_minor_node(dip, buf, S_IFCHR, i, DDI_PSEUDO,
+		    0) != DDI_SUCCESS) {
+			dev_err(dip, CE_WARN, "!failed to create minor %s",
+			    buf);
+			goto err;
+		}
+	}
+
+	return (DDI_SUCCESS);
+
+err:
+	usmn_cleanup(usmn);
+	return (DDI_FAILURE);
+}
+
+static int
+usmn_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
+{
+	usmn_t *usmn = &usmn_data;
+	minor_t m;
+
+	switch (cmd) {
+	case DDI_INFO_DEVT2DEVINFO:
+		m = getminor((dev_t)arg);
+		if (m >= usmn->usmn_ndfs) {
+			return (DDI_FAILURE);
+		}
+		*resultp = (void *)usmn->usmn_dip;
+		break;
+	case DDI_INFO_DEVT2INSTANCE:
+		m = getminor((dev_t)arg);
+		if (m >= usmn->usmn_ndfs) {
+			return (DDI_FAILURE);
+		}
+		*resultp = (void *)(uintptr_t)ddi_get_instance(usmn->usmn_dip);
+		break;
+	default:
+		return (DDI_FAILURE);
+	}
+	return (DDI_SUCCESS);
+}
+
+static int
+usmn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	usmn_t *usmn = &usmn_data;
+
+	if (cmd == DDI_SUSPEND) {
+		return (DDI_SUCCESS);
+	} else if (cmd != DDI_DETACH) {
+		return (DDI_FAILURE);
+	}
+
+	if (usmn->usmn_dip != dip) {
+		dev_err(dip, CE_WARN, "!asked to detach usmn, but dip doesn't "
+		    "match");
+		return (DDI_FAILURE);
+	}
+
+	usmn_cleanup(usmn);
+	return (DDI_SUCCESS);
+}
+
+static struct cb_ops usmn_cb_ops = {
+	.cb_open = usmn_open,
+	.cb_close = usmn_close,
+	.cb_strategy = nodev,
+	.cb_print = nodev,
+	.cb_dump = nodev,
+	.cb_read = nodev,
+	.cb_write = nodev,
+	.cb_ioctl = usmn_ioctl,
+	.cb_devmap = nodev,
+	.cb_mmap = nodev,
+	.cb_segmap = nodev,
+	.cb_chpoll = nochpoll,
+	.cb_prop_op = ddi_prop_op,
+	.cb_flag = D_MP,
+	.cb_rev = CB_REV,
+	.cb_aread = nodev,
+	.cb_awrite = nodev
+};
+
+static struct dev_ops usmn_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = usmn_getinfo,
+	.devo_identify = nulldev,
+	.devo_probe = nulldev,
+	.devo_attach = usmn_attach,
+	.devo_detach = usmn_detach,
+	.devo_reset = nodev,
+	.devo_quiesce = ddi_quiesce_not_needed,
+	.devo_cb_ops = &usmn_cb_ops
+};
+
+static struct modldrv usmn_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "AMD User SMN Access",
+	.drv_dev_ops = &usmn_dev_ops
+};
+
+static struct modlinkage usmn_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &usmn_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	return (mod_install(&usmn_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&usmn_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&usmn_modlinkage));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/io/amdzen/usmn.h	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,41 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+#ifndef _USMN_H
+#define	_USMN_H
+
+/*
+ * Private ioctls for interfacing with the usmn driver.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	USMN_IOCTL	(('u' << 24) | ('s' << 16) | ('m' << 8))
+
+#define	USMN_READ	(USMN_IOCTL | 0x01)
+#define	USMN_WRITE	(USMN_IOCTL | 0x02)
+
+typedef struct usmn_reg {
+	uint32_t usr_addr;
+	uint32_t usr_data;
+} usmn_reg_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _USMN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/smntemp/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,41 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer company
+#
+
+UTSBASE = ../..
+
+MODULE		= smntemp
+OBJECTS		= $(SMNTEMP_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
+CPPFLAGS	+= -I$(UTSBASE)/intel/io/amdzen
+LDFLAGS		+= -dy -Ndrv/amdzen
+
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/usmn/Makefile	Wed Apr 08 21:35:09 2020 -0700
@@ -0,0 +1,41 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2020 Oxide Computer Company
+#
+
+UTSBASE = ../..
+
+MODULE		= usmn
+OBJECTS		= $(USMN_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
+CPPFLAGS	+= -I$(UTSBASE)/intel/io/amdzen
+LDFLAGS		+= -dy -Ndrv/amdzen
+
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ