summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan McDonald <danmcd@joyent.com>2020-10-22 10:51:12 -0400
committerDan McDonald <danmcd@joyent.com>2020-10-22 10:51:12 -0400
commitebcb78defa820eaf313b5b3936a1bfe49002f105 (patch)
treeb63f0ad26d0a4440299f76bf80957eb1340168ef
parent54c8d80ce91aece72e185f6585e3ca8d62a0ea31 (diff)
parent1d276e0b382cf066dae93640746d8b4c54d15452 (diff)
downloadillumos-joyent-ebcb78defa820eaf313b5b3936a1bfe49002f105.tar.gz
[illumos-gate merge]
commit 1d276e0b382cf066dae93640746d8b4c54d15452 13242 parse_user_name in PAM is sloppy commit 549e0fd315406a4a97f9043f44860eed39a715da 13213 Want development driver for accessing AMD DF commit becd642c1e97d1674cef9e3dccb159c20c6992ae 13212 Add Renoir to amdzen(7D) commit 047043c2181e939608ea2c09257fd2d515e99643 13144 refactor amdf17nbdf into a nexus 13145 rewrite amdf17nbdf to use the ksensor framework 13146 Want a driver for AMD SMN user access
-rw-r--r--manifest9
-rw-r--r--usr/src/cmd/Makefile1
-rw-r--r--usr/src/cmd/amdzen/Makefile32
-rw-r--r--usr/src/cmd/amdzen/udf.c118
-rw-r--r--usr/src/cmd/amdzen/usmn.c100
-rw-r--r--usr/src/lib/libpam/pam_framework.c11
-rw-r--r--usr/src/man/man7d/Makefile9
-rw-r--r--usr/src/man/man7d/amdnbtemp.7d4
-rw-r--r--usr/src/man/man7d/amdzen.7d50
-rw-r--r--usr/src/man/man7d/smntemp.7d (renamed from usr/src/man/man7d/amdf17nbdf.7d)23
-rw-r--r--usr/src/man/man7d/usmn.7d37
-rw-r--r--usr/src/man/man7d/zen_udf.7d36
-rw-r--r--usr/src/pkg/manifests/driver-cpu-amd-zen.mf90
-rw-r--r--usr/src/pkg/manifests/driver-cpu-sensor.mf14
-rw-r--r--usr/src/pkg/manifests/driver-developer-amd-zen.mf37
-rw-r--r--usr/src/uts/common/io/usb/hcd/xhci/xhci.c2
-rw-r--r--usr/src/uts/common/os/policy.c15
-rw-r--r--usr/src/uts/common/sys/policy.h6
-rw-r--r--usr/src/uts/intel/Makefile.files9
-rw-r--r--usr/src/uts/intel/Makefile.intel9
-rw-r--r--usr/src/uts/intel/Makefile.rules4
-rw-r--r--usr/src/uts/intel/amdzen/Makefile41
-rw-r--r--usr/src/uts/intel/amdzen_stub/Makefile (renamed from usr/src/uts/intel/amdf17nbdf/Makefile)18
-rw-r--r--usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c1040
-rw-r--r--usr/src/uts/intel/io/amdzen/amdzen.c1016
-rw-r--r--usr/src/uts/intel/io/amdzen/amdzen.conf16
-rw-r--r--usr/src/uts/intel/io/amdzen/amdzen.h306
-rw-r--r--usr/src/uts/intel/io/amdzen/amdzen_client.h38
-rw-r--r--usr/src/uts/intel/io/amdzen/amdzen_stub.c81
-rw-r--r--usr/src/uts/intel/io/amdzen/smntemp.c343
-rw-r--r--usr/src/uts/intel/io/amdzen/usmn.c275
-rw-r--r--usr/src/uts/intel/io/amdzen/usmn.h41
-rw-r--r--usr/src/uts/intel/io/amdzen/zen_udf.c286
-rw-r--r--usr/src/uts/intel/io/amdzen/zen_udf.h44
-rw-r--r--usr/src/uts/intel/os/driver_aliases52
-rw-r--r--usr/src/uts/intel/os/name_to_major3
-rw-r--r--usr/src/uts/intel/smntemp/Makefile41
-rw-r--r--usr/src/uts/intel/usmn/Makefile41
-rw-r--r--usr/src/uts/intel/zen_udf/Makefile41
39 files changed, 3238 insertions, 1101 deletions
diff --git a/manifest b/manifest
index 312696eccf..7d2b43dba8 100644
--- a/manifest
+++ b/manifest
@@ -622,6 +622,7 @@ f kernel/drv/aac.conf 0644 root sys
f kernel/drv/acpi_drv.conf 0644 root sys
f kernel/drv/adpu320.conf 0644 root sys
f kernel/drv/aggr.conf 0644 root sys
+f kernel/drv/amdzen.conf 0644 root sys
d kernel/drv/amd64 0755 root sys
f kernel/drv/amd64/aac 0755 root sys
f kernel/drv/amd64/acpi_drv 0755 root sys
@@ -630,7 +631,8 @@ f kernel/drv/amd64/afe 0755 root sys
f kernel/drv/amd64/aggr 0755 root sys
f kernel/drv/amd64/ahci 0755 root sys
f kernel/drv/amd64/amd8111s 0755 root sys
-f kernel/drv/amd64/amdf17nbdf 0755 root sys
+f kernel/drv/amd64/amdzen 0755 root sys
+f kernel/drv/amd64/amdzen_stub 0755 root sys
f kernel/drv/amd64/amdnbtemp 0755 root sys
f kernel/drv/amd64/amr 0755 root sys
f kernel/drv/amd64/arcmsr 0755 root sys
@@ -769,6 +771,7 @@ f kernel/drv/amd64/si3124 0755 root sys
f kernel/drv/amd64/simnet 0755 root sys
f kernel/drv/amd64/skd 0755 root sys
f kernel/drv/amd64/smbios 0755 root sys
+f kernel/drv/amd64/smntemp 0755 root sys
f kernel/drv/amd64/smp 0755 root sys
f kernel/drv/amd64/smrt 0755 root sys
f kernel/drv/amd64/softmac 0755 root sys
@@ -19558,8 +19561,9 @@ f usr/share/man/man7d/afe.7d 0444 root bin
f usr/share/man/man7d/ahci.7d 0444 root bin
s usr/share/man/man7d/allkmem.7d=mem.7d
f usr/share/man/man7d/amd8111s.7d 0444 root bin
-f usr/share/man/man7d/amdf17nbdf.7d 0444 root bin
f usr/share/man/man7d/amdnbtemp.7d 0444 root bin
+f usr/share/man/man7d/amdzen.7d 0444 root bin
+s usr/share/man/man7d/amdzen_stub.7d=amdzen.7d
f usr/share/man/man7d/amr.7d 0444 root bin
f usr/share/man/man7d/arcmsr.7d 0444 root bin
f usr/share/man/man7d/asy.7d 0444 root bin
@@ -19665,6 +19669,7 @@ f usr/share/man/man7d/sgen.7d 0444 root bin
f usr/share/man/man7d/si3124.7d 0444 root bin
f usr/share/man/man7d/skd.7d 0444 root bin
f usr/share/man/man7d/smbios.7d 0444 root bin
+f usr/share/man/man7d/smntemp.7d 0444 root bin
f usr/share/man/man7d/st.7d 0444 root bin
f usr/share/man/man7d/sysmsg.7d 0444 root bin
f usr/share/man/man7d/systrace.7d 0444 root bin
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index 6d6276c70d..32329de0e9 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -488,6 +488,7 @@ i386_SUBDIRS= \
acpihpd \
addbadsec \
ahciem \
+ amdzen \
bhyve \
bhyvectl \
biosdev \
diff --git a/usr/src/cmd/amdzen/Makefile b/usr/src/cmd/amdzen/Makefile
new file mode 100644
index 0000000000..c9f8b7778e
--- /dev/null
+++ b/usr/src/cmd/amdzen/Makefile
@@ -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 udf
+
+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
diff --git a/usr/src/cmd/amdzen/udf.c b/usr/src/cmd/amdzen/udf.c
new file mode 100644
index 0000000000..604e0b4802
--- /dev/null
+++ b/usr/src/cmd/amdzen/udf.c
@@ -0,0 +1,118 @@
+/*
+ * 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
+ */
+
+/*
+ * Facilitate access to the AMD Zen data fabric
+ */
+
+#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 <strings.h>
+#include <zen_udf.h>
+
+static void
+udf_readone(int fd, uint8_t inst, uint8_t func, uint16_t reg, boolean_t do64)
+{
+ int ret;
+ zen_udf_io_t zui;
+
+ bzero(&zui, sizeof (zui));
+ zui.zui_inst = inst;
+ zui.zui_func = func;
+ zui.zui_reg = reg;
+
+ ret = ioctl(fd, do64 ? ZEN_UDF_READ64 : ZEN_UDF_READ32, &zui);
+ if (ret != 0) {
+ err(EXIT_FAILURE, "failed to issue read ioctl");
+ }
+
+ (void) printf("ifr %x/%x/%x: 0x%" PRIx64 "\n",
+ inst, func, reg, zui.zui_data);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, fd;
+ const char *device = NULL;
+ const char *funcstr = NULL;
+ const char *inststr = NULL;
+ const char *regstr = NULL;
+ uint8_t func, inst;
+ uint16_t reg;
+ unsigned long lval;
+ char *eptr;
+ boolean_t do64 = B_FALSE;
+
+ while ((c = getopt(argc, argv, "d:f:i:r:l")) != -1) {
+ switch (c) {
+ case 'd':
+ device = optarg;
+ break;
+ case 'f':
+ funcstr = optarg;
+ break;
+ case 'i':
+ inststr = optarg;
+ break;
+ case 'l':
+ do64 = B_TRUE;
+ break;
+ case 'r':
+ regstr = optarg;
+ break;
+ }
+ }
+
+ if (device == NULL || funcstr == NULL || inststr == NULL ||
+ regstr == NULL) {
+ warnx("missing required arguments");
+ (void) fprintf(stderr, "Usage: udf [-l] -d device -f func -i "
+ "inst -r reg\n");
+ }
+
+ errno = 0;
+ lval = strtoul(funcstr, &eptr, 0);
+ if (errno != 0 || lval > UINT8_MAX || *eptr != '\0') {
+ errx(EXIT_FAILURE, "failed to parse -f: %s", funcstr);
+ }
+ func = (uint8_t)lval;
+
+ lval = strtoul(inststr, &eptr, 0);
+ if (errno != 0 || lval > UINT8_MAX || *eptr != '\0') {
+ errx(EXIT_FAILURE, "failed to parse -i: %s", inststr);
+ }
+ inst = (uint8_t)lval;
+
+ lval = strtoul(regstr, &eptr, 0);
+ if (errno != 0 || lval > UINT16_MAX || *eptr != '\0') {
+ errx(EXIT_FAILURE, "failed to parse -r: %s", regstr);
+ }
+ reg = (uint16_t)lval;
+
+ if ((fd = open(device, O_RDONLY)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", device);
+ }
+
+ udf_readone(fd, inst, func, reg, do64);
+ (void) close(fd);
+ return (0);
+}
diff --git a/usr/src/cmd/amdzen/usmn.c b/usr/src/cmd/amdzen/usmn.c
new file mode 100644
index 0000000000..3b9998f021
--- /dev/null
+++ b/usr/src/cmd/amdzen/usmn.c
@@ -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);
+}
diff --git a/usr/src/lib/libpam/pam_framework.c b/usr/src/lib/libpam/pam_framework.c
index b3340ffd5e..9c46218812 100644
--- a/usr/src/lib/libpam/pam_framework.c
+++ b/usr/src/lib/libpam/pam_framework.c
@@ -24,7 +24,7 @@
*/
/*
- * Copyright (c) 2019, Joyent, Inc.
+ * Copyright 2020, Joyent, Inc.
*/
#include <syslog.h>
@@ -656,9 +656,10 @@ parse_user_name(char *user_input, char **ret_username)
* - we skip leading whitespaces and ignore trailing whitespaces
*/
while (*ptr != '\0') {
- if ((*ptr == ' ') || (*ptr == '\t'))
+ if ((*ptr == ' ') || (*ptr == '\t') ||
+ (index >= PAM_MAX_RESP_SIZE)) {
break;
- else {
+ } else {
username[index] = *ptr;
index++;
ptr++;
@@ -666,9 +667,9 @@ parse_user_name(char *user_input, char **ret_username)
}
/* ret_username will be freed in pam_get_user(). */
- if ((*ret_username = malloc(index + 1)) == NULL)
+ if (index >= PAM_MAX_RESP_SIZE ||
+ (*ret_username = strdup(username)) == NULL)
return (PAM_BUF_ERR);
- (void) strcpy(*ret_username, username);
return (PAM_SUCCESS);
}
diff --git a/usr/src/man/man7d/Makefile b/usr/src/man/man7d/Makefile
index bd7a7006c6..b762d59742 100644
--- a/usr/src/man/man7d/Makefile
+++ b/usr/src/man/man7d/Makefile
@@ -182,8 +182,8 @@ sparc_MANFILES= audiocs.7d \
i386_MANFILES= ahci.7d \
amd8111s.7d \
- amdf17nbdf.7d \
amdnbtemp.7d \
+ amdzen.7d \
amr.7d \
arcmsr.7d \
arn.7d \
@@ -237,14 +237,17 @@ i386_MANFILES= ahci.7d \
si3124.7d \
skd.7d \
smbios.7d \
+ smntemp.7d \
uath.7d \
ural.7d \
urtw.7d \
+ usmn.7d \
vioblk.7d \
vioif.7d \
virtio.7d \
wpi.7d \
xhci.7d \
+ zen_udf.7d \
zyd.7d
_MANLINKS= 1394.7d \
@@ -260,9 +263,13 @@ _MANLINKS= 1394.7d \
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
diff --git a/usr/src/man/man7d/amdnbtemp.7d b/usr/src/man/man7d/amdnbtemp.7d
index 12e1ab5329..a086ebce99 100644
--- a/usr/src/man/man7d/amdnbtemp.7d
+++ b/usr/src/man/man7d/amdnbtemp.7d
@@ -61,11 +61,11 @@ are not supported at this time.
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
diff --git a/usr/src/man/man7d/amdzen.7d b/usr/src/man/man7d/amdzen.7d
new file mode 100644
index 0000000000..171d36b0d6
--- /dev/null
+++ b/usr/src/man/man7d/amdzen.7d
@@ -0,0 +1,50 @@
+.\"
+.\" 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 ,
+.Xr usmn 7D ,
+.Xr zen_udf 7D
diff --git a/usr/src/man/man7d/amdf17nbdf.7d b/usr/src/man/man7d/smntemp.7d
index e6e6ce863d..903d060825 100644
--- a/usr/src/man/man7d/amdf17nbdf.7d
+++ b/usr/src/man/man7d/smntemp.7d
@@ -10,24 +10,25 @@
.\"
.\"
.\" Copyright 2019, Joyent, Inc.
+.\" Copyright 2020 Oxide Computer Company
.\"
-.Dd November 16, 2019
-.Dt AMDF17NBDF 7D
+.Dd September 1, 2020
+.Dt SMNTEMP 7D
.Os
.Sh NAME
-.Nm amdf17nbdf
-.Nd AMD Family 17h Northbridge and Data Fabric Driver
+.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 Northbridge and Data Fabric
-devices on AMD Family 17h
+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 allowing the operating system to communicate with the system
-management unit
-.Pq SMU .
+processors.
.Pp
From this, the driver exposes temperature sensors.
On Family 17h systems, temperature sensors exist for each Zeppelin die,
@@ -45,7 +46,9 @@ 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 fmadm 1M ,
+.Xr amdnbtemp 7D ,
+.Xr amdzen 7D
.Rs
.%A AMD
.%B Open-Source Register Reference For AMD Family 17h Processors Models 00h-2Fh
diff --git a/usr/src/man/man7d/usmn.7d b/usr/src/man/man7d/usmn.7d
new file mode 100644
index 0000000000..b36da78be4
--- /dev/null
+++ b/usr/src/man/man7d/usmn.7d
@@ -0,0 +1,37 @@
+.\"
+.\" 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 ,
+.Xr zen_udf 7D
diff --git a/usr/src/man/man7d/zen_udf.7d b/usr/src/man/man7d/zen_udf.7d
new file mode 100644
index 0000000000..a2243ef67f
--- /dev/null
+++ b/usr/src/man/man7d/zen_udf.7d
@@ -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 October 7, 2020
+.Dt ZEN_UDF 7D
+.Os
+.Sh NAME
+.Nm zen_udf
+.Nd AMD data fabric user access driver
+.Sh SYNOPSIS
+.Pa /devices/pseudo/amdzen@0/zen_udf@3:zen_udf.*
+.Sh DESCRIPTION
+The
+.Nm
+driver provides the ability to read data from the AMD data fabric
+.Pq DF
+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 ,
+.Xr usmn 7D
diff --git a/usr/src/pkg/manifests/driver-cpu-amd-zen.mf b/usr/src/pkg/manifests/driver-cpu-amd-zen.mf
new file mode 100644
index 0000000000..df4adf3339
--- /dev/null
+++ b/usr/src/pkg/manifests/driver-cpu-amd-zen.mf
@@ -0,0 +1,90 @@
+#
+# 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
+#
+# 1440-1447: f17h m70-7fh df
+# 1448-144f: f17h m60-6fh df
+# 1450: f17h m00-0f nb
+# 1460-1467: f17h m00-0f df
+# 1480: f17h m30-3f/70-7f nb
+# 1490-1497: f17h m30-3f nb
+# 15d0: f17h m10-m2f nb
+# 15e8-15ef: f17h m10-m2f df
+# 1630: f17h m60-6f nb
+#
+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,1448,p \
+ alias=pci1022,1449,p \
+ alias=pci1022,144a,p \
+ alias=pci1022,144b,p \
+ alias=pci1022,144c,p \
+ alias=pci1022,144d,p \
+ alias=pci1022,144e,p \
+ alias=pci1022,144f,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 \
+ alias=pci1022,1630,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
diff --git a/usr/src/pkg/manifests/driver-cpu-sensor.mf b/usr/src/pkg/manifests/driver-cpu-sensor.mf
index cf413d078c..7ac1e352ff 100644
--- a/usr/src/pkg/manifests/driver-cpu-sensor.mf
+++ b/usr/src/pkg/manifests/driver-cpu-sensor.mf
@@ -25,14 +25,6 @@ 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=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 @@ driver name=pchtemp \
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
diff --git a/usr/src/pkg/manifests/driver-developer-amd-zen.mf b/usr/src/pkg/manifests/driver-developer-amd-zen.mf
new file mode 100644
index 0000000000..7df997705f
--- /dev/null
+++ b/usr/src/pkg/manifests/driver-developer-amd-zen.mf
@@ -0,0 +1,37 @@
+#
+# 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
+driver name=zen_udf
+file path=kernel/drv/$(ARCH64)/usmn group=sys
+file path=kernel/drv/$(ARCH64)/zen_udf group=sys
+file path=usr/lib/udf mode=0555
+file path=usr/lib/usmn mode=0555
+file path=usr/share/man/man7d/usmn.7d
+file path=usr/share/man/man7d/zen_udf.7d
+license lic_CDDL license=lic_CDDL
diff --git a/usr/src/uts/common/io/usb/hcd/xhci/xhci.c b/usr/src/uts/common/io/usb/hcd/xhci/xhci.c
index 2beeb69972..a28b1fa0d3 100644
--- a/usr/src/uts/common/io/usb/hcd/xhci/xhci.c
+++ b/usr/src/uts/common/io/usb/hcd/xhci/xhci.c
@@ -1911,7 +1911,7 @@ xhci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
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);
diff --git a/usr/src/uts/common/os/policy.c b/usr/src/uts/common/os/policy.c
index 861c748cff..00ca6ec54a 100644
--- a/usr/src/uts/common/os/policy.c
+++ b/usr/src/uts/common/os/policy.c
@@ -1381,7 +1381,7 @@ secpolicy_xvattr(xvattr_t *xvap, uid_t owner, cred_t *cr, vtype_t vtype)
* 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
*
@@ -1453,8 +1453,8 @@ secpolicy_vnode_setattr(cred_t *cr, struct vnode *vp, struct vattr *vap,
*
* 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.
@@ -2433,13 +2433,14 @@ secpolicy_gart_map(const cred_t *cr)
}
/*
- * 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));
}
diff --git a/usr/src/uts/common/sys/policy.h b/usr/src/uts/common/sys/policy.h
index 816d6995cf..9f1b80d390 100644
--- a/usr/src/uts/common/sys/policy.h
+++ b/usr/src/uts/common/sys/policy.h
@@ -53,12 +53,12 @@ typedef uint16_t in_port_t;
* 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 *);
@@ -166,7 +166,7 @@ int secpolicy_vnode_setid_retain(const cred_t *, boolean_t);
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 *);
diff --git a/usr/src/uts/intel/Makefile.files b/usr/src/uts/intel/Makefile.files
index baeccfaac8..1437fa19bd 100644
--- a/usr/src/uts/intel/Makefile.files
+++ b/usr/src/uts/intel/Makefile.files
@@ -434,3 +434,12 @@ PCHTEMP_OBJS = pchtemp.o
# 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
+ZEN_UDF_OBJS = zen_udf.o
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index 21210e9ef1..636eebcb94 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -784,7 +784,14 @@ LINTFLAGS += -D_MACHDEP -I$(UTSBASE)/i86pc
# 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 zen_udf
diff --git a/usr/src/uts/intel/Makefile.rules b/usr/src/uts/intel/Makefile.rules
index 763c448725..551896e151 100644
--- a/usr/src/uts/intel/Makefile.rules
+++ b/usr/src/uts/intel/Makefile.rules
@@ -149,6 +149,10 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/intel/io/amdnbtemp/%.c
$(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)
diff --git a/usr/src/uts/intel/amdzen/Makefile b/usr/src/uts/intel/amdzen/Makefile
new file mode 100644
index 0000000000..1c7cf547cc
--- /dev/null
+++ b/usr/src/uts/intel/amdzen/Makefile
@@ -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
diff --git a/usr/src/uts/intel/amdf17nbdf/Makefile b/usr/src/uts/intel/amdzen_stub/Makefile
index a5543f176f..f14d44e526 100644
--- a/usr/src/uts/intel/amdf17nbdf/Makefile
+++ b/usr/src/uts/intel/amdzen_stub/Makefile
@@ -10,22 +10,22 @@
#
#
-# Copyright 2019, Joyent, Inc.
+# Copyright 2020 Oxide Computer Company
#
UTSBASE = ../..
-MODULE = amdf17nbdf
-OBJECTS = $(AMDF17NBDF_OBJS:%=$(OBJS_DIR)/%)
+MODULE = amdzen_stub
+OBJECTS = $(AMDZEN_STUB_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
+ALL_TARGET = $(BINARY)
INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+LDFLAGS += -dy -Ndrv/amdzen
+
.KEEP_STATE:
def: $(DEF_DEPS)
@@ -36,12 +36,6 @@ 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
diff --git a/usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c b/usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c
deleted file mode 100644
index 7be8a4a9f8..0000000000
--- a/usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c
+++ /dev/null
@@ -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);
-}
diff --git a/usr/src/uts/intel/io/amdzen/amdzen.c b/usr/src/uts/intel/io/amdzen/amdzen.c
new file mode 100644
index 0000000000..25e38ee988
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/amdzen.c
@@ -0,0 +1,1016 @@
+/*
+ * 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, Kestrel, Dali Models 10h-2fh (Zen uarch) */
+ 0x15d0,
+ /* Family 17h Epyc Models 30h-3fh, Matisse 70-7fh (Zen 2 uarch) */
+ 0x1480,
+ /* Family 17h Renoir Models 60-6fh (Zen 2 uarch) */
+ 0x1630
+};
+
+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 },
+ { "zen_udf", AMDZEN_C_ZEN_UDF }
+};
+
+static uint32_t
+amdzen_stub_get32(amdzen_stub_t *stub, off_t reg)
+{
+ return (pci_config_get32(stub->azns_cfgspace, reg));
+}
+
+static uint64_t
+amdzen_stub_get64(amdzen_stub_t *stub, off_t reg)
+{
+ return (pci_config_get64(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));
+}
+
+/*
+ * Perform a targeted 64-bit indirect read to a specific instance and function.
+ */
+static uint64_t
+amdzen_df_read64(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_DF_F4_FICAA_SET_64B;
+ amdzen_stub_put32(df->adf_funcs[4], AMDZEN_DF_F4_FICAA, val);
+ return (amdzen_stub_get64(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);
+}
+
+int
+amdzen_c_df_read32(uint_t dfno, uint8_t inst, uint8_t func,
+ uint16_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);
+ }
+
+ *valp = amdzen_df_read32(azn, df, inst, func, reg);
+ mutex_exit(&azn->azn_mutex);
+
+ return (0);
+}
+
+int
+amdzen_c_df_read64(uint_t dfno, uint8_t inst, uint8_t func,
+ uint16_t reg, uint64_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);
+ }
+
+ *valp = amdzen_df_read64(azn, df, inst, func, reg);
+ mutex_exit(&azn->azn_mutex);
+
+ return (0);
+}
+
+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);
+}
diff --git a/usr/src/uts/intel/io/amdzen/amdzen.conf b/usr/src/uts/intel/io/amdzen/amdzen.conf
new file mode 100644
index 0000000000..9c69d42c11
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/amdzen.conf
@@ -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;
diff --git a/usr/src/uts/intel/io/amdzen/amdzen.h b/usr/src/uts/intel/io/amdzen/amdzen.h
new file mode 100644
index 0000000000..8150495911
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/amdzen.h
@@ -0,0 +1,306 @@
+/*
+ * 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_C_ZEN_UDF
+} 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 */
diff --git a/usr/src/uts/intel/io/amdzen/amdzen_client.h b/usr/src/uts/intel/io/amdzen/amdzen_client.h
new file mode 100644
index 0000000000..41ca3e8afd
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/amdzen_client.h
@@ -0,0 +1,38 @@
+/*
+ * 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, uint32_t, uint32_t *);
+extern uint_t amdzen_c_df_count(void);
+extern int amdzen_c_df_read32(uint_t, uint8_t, uint8_t, uint16_t, uint32_t *);
+extern int amdzen_c_df_read64(uint_t, uint8_t, uint8_t, uint16_t, uint64_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AMDZEN_CLIENT_H */
diff --git a/usr/src/uts/intel/io/amdzen/amdzen_stub.c b/usr/src/uts/intel/io/amdzen/amdzen_stub.c
new file mode 100644
index 0000000000..f10849dc98
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/amdzen_stub.c
@@ -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));
+}
diff --git a/usr/src/uts/intel/io/amdzen/smntemp.c b/usr/src/uts/intel/io/amdzen/smntemp.c
new file mode 100644
index 0000000000..aa595f5ce5
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/smntemp.c
@@ -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));
+}
diff --git a/usr/src/uts/intel/io/amdzen/usmn.c b/usr/src/uts/intel/io/amdzen/usmn.c
new file mode 100644
index 0000000000..a219f997e1
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/usmn.c
@@ -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));
+}
diff --git a/usr/src/uts/intel/io/amdzen/usmn.h b/usr/src/uts/intel/io/amdzen/usmn.h
new file mode 100644
index 0000000000..10f057525d
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/usmn.h
@@ -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 */
diff --git a/usr/src/uts/intel/io/amdzen/zen_udf.c b/usr/src/uts/intel/io/amdzen/zen_udf.c
new file mode 100644
index 0000000000..61d1e79774
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/zen_udf.c
@@ -0,0 +1,286 @@
+/*
+ * 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 companion to zen_udf(7D) that allows user access to read the data fabric
+ * for development 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 <zen_udf.h>
+
+typedef struct zen_udf {
+ dev_info_t *zudf_dip;
+ uint_t zudf_ndfs;
+} zen_udf_t;
+
+static zen_udf_t zen_udf_data;
+
+static int
+zen_udf_open(dev_t *devp, int flags, int otype, cred_t *credp)
+{
+ minor_t m;
+ zen_udf_t *zen_udf = &zen_udf_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 >= zen_udf->zudf_ndfs) {
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+zen_udf_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+ int *rvalp)
+{
+ uint_t dfno;
+ zen_udf_t *zen_udf = &zen_udf_data;
+ zen_udf_io_t zui;
+
+ if (cmd != ZEN_UDF_READ32 && cmd != ZEN_UDF_READ64) {
+ return (ENOTTY);
+ }
+
+ dfno = getminor(dev);
+ if (dfno >= zen_udf->zudf_ndfs) {
+ return (ENXIO);
+ }
+
+ if (crgetzoneid(credp) != GLOBAL_ZONEID ||
+ secpolicy_hwmanip(credp) != 0) {
+ return (EPERM);
+ }
+
+ if (ddi_copyin((void *)arg, &zui, sizeof (zui), mode & FKIOCTL) != 0) {
+ return (EFAULT);
+ }
+
+ if (cmd == ZEN_UDF_READ32) {
+ int ret;
+ uint32_t data;
+
+ ret = amdzen_c_df_read32(dfno, zui.zui_inst, zui.zui_func,
+ zui.zui_reg, &data);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ zui.zui_data = data;
+ } else {
+ int ret;
+
+ ret = amdzen_c_df_read64(dfno, zui.zui_inst, zui.zui_func,
+ zui.zui_reg, &zui.zui_data);
+ if (ret != 0) {
+ return (ret);
+ }
+ }
+
+ if (ddi_copyout(&zui, (void *)arg, sizeof (zui), mode & FKIOCTL) != 0) {
+ return (EFAULT);
+ }
+
+ return (0);
+}
+
+static int
+zen_udf_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ return (0);
+}
+
+static void
+zen_udf_cleanup(zen_udf_t *zen_udf)
+{
+ ddi_remove_minor_node(zen_udf->zudf_dip, NULL);
+ zen_udf->zudf_ndfs = 0;
+ zen_udf->zudf_dip = NULL;
+}
+
+static int
+zen_udf_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ zen_udf_t *zen_udf = &zen_udf_data;
+
+ if (cmd == DDI_RESUME) {
+ return (DDI_SUCCESS);
+ } else if (cmd != DDI_ATTACH) {
+ return (DDI_FAILURE);
+ }
+
+ if (zen_udf->zudf_dip != NULL) {
+ dev_err(dip, CE_WARN, "!zen_udf is already attached to a "
+ "dev_info_t: %p", zen_udf->zudf_dip);
+ return (DDI_FAILURE);
+ }
+
+ zen_udf->zudf_dip = dip;
+ zen_udf->zudf_ndfs = amdzen_c_df_count();
+ for (uint_t i = 0; i < zen_udf->zudf_ndfs; i++) {
+ char buf[32];
+
+ (void) snprintf(buf, sizeof (buf), "zen_udf.%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:
+ zen_udf_cleanup(zen_udf);
+ return (DDI_FAILURE);
+}
+
+static int
+zen_udf_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
+{
+ zen_udf_t *zen_udf = &zen_udf_data;
+ minor_t m;
+
+ switch (cmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ m = getminor((dev_t)arg);
+ if (m >= zen_udf->zudf_ndfs) {
+ return (DDI_FAILURE);
+ }
+ *resultp = (void *)zen_udf->zudf_dip;
+ break;
+ case DDI_INFO_DEVT2INSTANCE:
+ m = getminor((dev_t)arg);
+ if (m >= zen_udf->zudf_ndfs) {
+ return (DDI_FAILURE);
+ }
+ *resultp = (void *)(uintptr_t)ddi_get_instance(
+ zen_udf->zudf_dip);
+ break;
+ default:
+ return (DDI_FAILURE);
+ }
+ return (DDI_SUCCESS);
+}
+
+static int
+zen_udf_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ zen_udf_t *zen_udf = &zen_udf_data;
+
+ if (cmd == DDI_SUSPEND) {
+ return (DDI_SUCCESS);
+ } else if (cmd != DDI_DETACH) {
+ return (DDI_FAILURE);
+ }
+
+ if (zen_udf->zudf_dip != dip) {
+ dev_err(dip, CE_WARN, "!asked to detach zen_udf, but dip "
+ "doesn't match");
+ return (DDI_FAILURE);
+ }
+
+ zen_udf_cleanup(zen_udf);
+ return (DDI_SUCCESS);
+}
+
+static struct cb_ops zen_udf_cb_ops = {
+ .cb_open = zen_udf_open,
+ .cb_close = zen_udf_close,
+ .cb_strategy = nodev,
+ .cb_print = nodev,
+ .cb_dump = nodev,
+ .cb_read = nodev,
+ .cb_write = nodev,
+ .cb_ioctl = zen_udf_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 zen_udf_dev_ops = {
+ .devo_rev = DEVO_REV,
+ .devo_refcnt = 0,
+ .devo_getinfo = zen_udf_getinfo,
+ .devo_identify = nulldev,
+ .devo_probe = nulldev,
+ .devo_attach = zen_udf_attach,
+ .devo_detach = zen_udf_detach,
+ .devo_reset = nodev,
+ .devo_quiesce = ddi_quiesce_not_needed,
+ .devo_cb_ops = &zen_udf_cb_ops
+};
+
+static struct modldrv zen_udf_modldrv = {
+ .drv_modops = &mod_driverops,
+ .drv_linkinfo = "AMD User DF Access",
+ .drv_dev_ops = &zen_udf_dev_ops
+};
+
+static struct modlinkage zen_udf_modlinkage = {
+ .ml_rev = MODREV_1,
+ .ml_linkage = { &zen_udf_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ return (mod_install(&zen_udf_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&zen_udf_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+ return (mod_remove(&zen_udf_modlinkage));
+}
diff --git a/usr/src/uts/intel/io/amdzen/zen_udf.h b/usr/src/uts/intel/io/amdzen/zen_udf.h
new file mode 100644
index 0000000000..ef5a3184d5
--- /dev/null
+++ b/usr/src/uts/intel/io/amdzen/zen_udf.h
@@ -0,0 +1,44 @@
+/*
+ * 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 _ZEN_UDF_H
+#define _ZEN_UDF_H
+
+/*
+ * Private ioctls for interfacing with the zen_udf driver.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZEN_UDF_IOCTL (('u' << 24) | ('d' << 16) | ('f' << 8))
+
+#define ZEN_UDF_READ32 (ZEN_UDF_IOCTL | 0x01)
+#define ZEN_UDF_READ64 (ZEN_UDF_IOCTL | 0x02)
+
+typedef struct zen_udf_io {
+ uint8_t zui_inst;
+ uint8_t zui_func;
+ uint16_t zui_reg;
+ uint32_t zui_pad;
+ uint64_t zui_data;
+} zen_udf_io_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEN_UDF_H */
diff --git a/usr/src/uts/intel/os/driver_aliases b/usr/src/uts/intel/os/driver_aliases
index a55b9802c1..6c79bd182c 100644
--- a/usr/src/uts/intel/os/driver_aliases
+++ b/usr/src/uts/intel/os/driver_aliases
@@ -67,13 +67,50 @@ amd64_gart "pci1022,1103"
amd8111s "pci1022,7462"
amd_iommu "pci1002,5a23"
amd_iommu "pci1022,11ff"
-amdf17nbdf "pci1022,1440,p"
-amdf17nbdf "pci1022,1450,p"
-amdf17nbdf "pci1022,1460,p"
-amdf17nbdf "pci1022,1480,p"
-amdf17nbdf "pci1022,1490,p"
-amdf17nbdf "pci1022,15d0,p"
-amdf17nbdf "pci1022,15e8,p"
+amdzen_stub "pci1022,1440,p"
+amdzen_stub "pci1022,1441,p"
+amdzen_stub "pci1022,1442,p"
+amdzen_stub "pci1022,1443,p"
+amdzen_stub "pci1022,1444,p"
+amdzen_stub "pci1022,1445,p"
+amdzen_stub "pci1022,1446,p"
+amdzen_stub "pci1022,1447,p"
+amdzen_stub "pci1022,1448,p"
+amdzen_stub "pci1022,1449,p"
+amdzen_stub "pci1022,144a,p"
+amdzen_stub "pci1022,144b,p"
+amdzen_stub "pci1022,144c,p"
+amdzen_stub "pci1022,144d,p"
+amdzen_stub "pci1022,144e,p"
+amdzen_stub "pci1022,144f,p"
+amdzen_stub "pci1022,1450,p"
+amdzen_stub "pci1022,1460,p"
+amdzen_stub "pci1022,1461,p"
+amdzen_stub "pci1022,1462,p"
+amdzen_stub "pci1022,1463,p"
+amdzen_stub "pci1022,1464,p"
+amdzen_stub "pci1022,1465,p"
+amdzen_stub "pci1022,1466,p"
+amdzen_stub "pci1022,1467,p"
+amdzen_stub "pci1022,1480,p"
+amdzen_stub "pci1022,1490,p"
+amdzen_stub "pci1022,1491,p"
+amdzen_stub "pci1022,1492,p"
+amdzen_stub "pci1022,1493,p"
+amdzen_stub "pci1022,1494,p"
+amdzen_stub "pci1022,1495,p"
+amdzen_stub "pci1022,1496,p"
+amdzen_stub "pci1022,1497,p"
+amdzen_stub "pci1022,15d0,p"
+amdzen_stub "pci1022,15e8,p"
+amdzen_stub "pci1022,15e9,p"
+amdzen_stub "pci1022,15ea,p"
+amdzen_stub "pci1022,15eb,p"
+amdzen_stub "pci1022,15ec,p"
+amdzen_stub "pci1022,15ed,p"
+amdzen_stub "pci1022,15ee,p"
+amdzen_stub "pci1022,15ef,p"
+amdzen_stub "pci1022,1630,p"
amdnbtemp "pci1022,1203,p"
amdnbtemp "pci1022,1303,p"
amdnbtemp "pci1022,1403,p"
@@ -1365,6 +1402,7 @@ sfxge "pci1924,903"
sfxge "pci1924,923"
sgen "scsa,08.bfcp"
sgen "scsa,08.bvhci"
+smntemp "smntemp"
smrt "pci103c,1920"
smrt "pci103c,1921"
smrt "pci103c,1922"
diff --git a/usr/src/uts/intel/os/name_to_major b/usr/src/uts/intel/os/name_to_major
index d4c9c1ebe6..d557fd918f 100644
--- a/usr/src/uts/intel/os/name_to_major
+++ b/usr/src/uts/intel/os/name_to_major
@@ -311,3 +311,6 @@ imc 314
ccid 315
ksensor 316
mlxcx 317
+amdzen_stub 318
+amdzen 319
+smntemp 320
diff --git a/usr/src/uts/intel/smntemp/Makefile b/usr/src/uts/intel/smntemp/Makefile
new file mode 100644
index 0000000000..119944e10d
--- /dev/null
+++ b/usr/src/uts/intel/smntemp/Makefile
@@ -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
diff --git a/usr/src/uts/intel/usmn/Makefile b/usr/src/uts/intel/usmn/Makefile
new file mode 100644
index 0000000000..50e825d560
--- /dev/null
+++ b/usr/src/uts/intel/usmn/Makefile
@@ -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
diff --git a/usr/src/uts/intel/zen_udf/Makefile b/usr/src/uts/intel/zen_udf/Makefile
new file mode 100644
index 0000000000..5b312d7013
--- /dev/null
+++ b/usr/src/uts/intel/zen_udf/Makefile
@@ -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 = zen_udf
+OBJECTS = $(ZEN_UDF_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