# HG changeset patch # User Robert Mustacchi # Date 1586406909 25200 # Node ID 1a5588aae48c53a643488cb5960dce03c0203b41 # Parent 6d24e460e13ddba9aacd34bc090db467268fed18 13144 refactor amdf17nbdf into a nexus 13145 rewrite amdf17nbdf to use the ksensor framework 13146 Want a driver for AMD SMN user access Reviewed by: Patrick Mooney Reviewed by: Mike Zeller Reviewed by: Robert French Approved by: Richard Lowe diff -r 6d24e460e13d -r 1a5588aae48c usr/src/cmd/Makefile --- a/usr/src/cmd/Makefile Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/cmd/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -499,6 +499,7 @@ rdmsr \ rtc \ ucodeadm \ + usmn \ xhci \ xvm diff -r 6d24e460e13d -r 1a5588aae48c usr/src/cmd/usmn/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/usmn/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,32 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + +PROG= usmn + +include ../Makefile.cmd + +ROOTCMDDIR = $(ROOTLIB) +CPPFLAGS += -I$(SRC)/uts/intel/io/amdzen +CFLAGS += $(CCVERBOSE) + +.KEEP_STATE: + +all: $(PROG) + +install: all $(ROOTCMD) + +clean: + +include ../Makefile.targ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/cmd/usmn/usmn.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/usmn/usmn.c Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,100 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +/* + * Read and write to the AMD SMN. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/Makefile --- a/usr/src/man/man7d/Makefile Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/man/man7d/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -180,8 +180,8 @@ i386_MANFILES= ahci.7d \ amd8111s.7d \ - amdf17nbdf.7d \ amdnbtemp.7d \ + amdzen.7d \ amr.7d \ arcmsr.7d \ arn.7d \ @@ -235,9 +235,11 @@ si3124.7d \ skd.7d \ smbios.7d \ + smntemp.7d \ uath.7d \ ural.7d \ urtw.7d \ + usmn.7d \ vioblk.7d \ vioif.7d \ virtio.7d \ @@ -258,9 +260,13 @@ sparc_MANLINKS= drmach.7d +i386_MANLINKS= amdzen_stub.7d + MANFILES = $(_MANFILES) $($(MACH)_MANFILES) MANLINKS = $(_MANLINKS) $($(MACH)_MANLINKS) +amdzen_stub.7d := LINKSRC = amdzen.7d + drmach.7d := LINKSRC = dr.7d fdc.7d := LINKSRC = fd.7d diff -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/amdf17nbdf.7d --- a/usr/src/man/man7d/amdf17nbdf.7d Sat Oct 03 11:05:18 2020 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -.\" -.\" This file and its contents are supplied under the terms of the -.\" Common Development and Distribution License ("CDDL"), version 1.0. -.\" You may only use this file in accordance with the terms of version -.\" 1.0 of the CDDL. -.\" -.\" A full copy of the text of the CDDL should have accompanied this -.\" source. A copy of the CDDL is also available via the Internet at -.\" http://www.illumos.org/license/CDDL. -.\" -.\" -.\" Copyright 2019, Joyent, Inc. -.\" -.Dd November 16, 2019 -.Dt AMDF17NBDF 7D -.Os -.Sh NAME -.Nm amdf17nbdf -.Nd AMD Family 17h Northbridge and Data Fabric Driver -.Sh SYNOPSIS -.Pa /dev/sensors/temperature/cpu/* -.Sh DESCRIPTION -The -.Nm -driver provides the system access to the Northbridge and Data Fabric -devices on AMD Family 17h -.Pq Zen, Zen+, and Zen 2 -processors allowing the operating system to communicate with the system -management unit -.Pq SMU . -.Pp -From this, the driver exposes temperature sensors. -On Family 17h systems, temperature sensors exist for each Zeppelin die, -of which there may be multiple in a single package. -This means that each sensor covers more than one core. -.Pp -Temperature information is available to the system via the fault -management architecture -.Pq FMA . -The file system location and programming interface to the -.Nm -driver are considered -.Sy Volatile , -subject to change without notice, and should not be used directly. -Raw temperature information can be dumped through the FMA developer -utility fmtopo. -.Sh SEE ALSO -.Xr fmadm 1M -.Rs -.%A AMD -.%B Open-Source Register Reference For AMD Family 17h Processors Models 00h-2Fh -.%D July, 2018 -.Re diff -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/amdnbtemp.7d --- a/usr/src/man/man7d/amdnbtemp.7d Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/man/man7d/amdnbtemp.7d Wed Apr 08 21:35:09 2020 -0700 @@ -61,11 +61,11 @@ For AMD Family 17h .Pq Zen processors, the -.Xr amdf17nbdf 7D +.Xr smntemp 7D driver provides access to the temperature sensors. .Sh SEE ALSO .Xr fmadm 1M , -.Xr amdf17nbdf 7D +.Xr smntemp 7D .Rs .%A AMD .%B BIOS and Kernel Developer’s Guide (BKDG) for AMD Family 16h Models 00h-0Fh Processors diff -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/amdzen.7d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/man/man7d/amdzen.7d Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,48 @@ +.\" +.\" This file and its contents are supplied under the terms of the +.\" Common Development and Distribution License ("CDDL"), version 1.0. +.\" You may only use this file in accordance with the terms of version +.\" 1.0 of the CDDL. +.\" +.\" A full copy of the text of the CDDL should have accompanied this +.\" source. A copy of the CDDL is also available via the Internet at +.\" http://www.illumos.org/license/CDDL. +.\" +.\" +.\" Copyright 2020 Oxide Computer Company +.\" +.Dd September 1, 2020 +.Dt AMDZEN 7D +.Os +.Sh NAME +.Nm amdzen , +.Nm amdzen_stub +.Nd AMD Zen Nexus Driver +.Sh DESCRIPTION +The +.Sy amdzen +driver provides access to the AMD Zen, Zen+, and Zen 2 +.Pq Family 17h +processor families Northbridge, Data Fabric, and System Management Network +.Pq SMN . +This driver is a nexus driver and facilitates access to these devices +between various other devices such as +.Xr smntemp 7D . +The various processor devices that make up the northbridge and data +fabric have the +.Sy amdzen_stub +driver attached to them. +The different devices are all amalgamated and a single uniform view is +provided by the +.Sy amdzen +driver. +.Sh ARCHITECTURE +The +.Sy amdzen +and +.Sy amdzen_stub +drivers are limited to +.Sy x86 +platforms with AMD Family 17h processors. +.Sh SEE ALSO +.Xr smntemp 7D diff -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/smntemp.7d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/man/man7d/smntemp.7d Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,56 @@ +.\" +.\" This file and its contents are supplied under the terms of the +.\" Common Development and Distribution License ("CDDL"), version 1.0. +.\" You may only use this file in accordance with the terms of version +.\" 1.0 of the CDDL. +.\" +.\" A full copy of the text of the CDDL should have accompanied this +.\" source. A copy of the CDDL is also available via the Internet at +.\" http://www.illumos.org/license/CDDL. +.\" +.\" +.\" Copyright 2019, Joyent, Inc. +.\" Copyright 2020 Oxide Computer Company +.\" +.Dd September 1, 2020 +.Dt SMNTEMP 7D +.Os +.Sh NAME +.Nm smntemp +.Nd AMD SMN Temperature Sensor Driver +.Sh SYNOPSIS +.Pa /dev/sensors/temperature/cpu/* +.Sh DESCRIPTION +The +.Nm +driver provides the system access to the temperature sensor found in the +AMD System Management Network +.Pq SMN +on AMD Family 17h +.Pq Zen, Zen+, and Zen 2 +processors. +.Pp +From this, the driver exposes temperature sensors. +On Family 17h systems, temperature sensors exist for each Zeppelin die, +of which there may be multiple in a single package. +This means that each sensor covers more than one core. +.Pp +Temperature information is available to the system via the fault +management architecture +.Pq FMA . +The file system location and programming interface to the +.Nm +driver are considered +.Sy Volatile , +subject to change without notice, and should not be used directly. +Raw temperature information can be dumped through the FMA developer +utility fmtopo. +.Sh SEE ALSO +.Xr fmadm 1M , +.Xr amdnbtemp 7D , +.Xr amdzen 7D +.Rs +.%A AMD +.%B Open-Source Register Reference For AMD Family 17h Processors Models 00h-2Fh +.%D July, 2018 +.Re diff -r 6d24e460e13d -r 1a5588aae48c usr/src/man/man7d/usmn.7d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/man/man7d/usmn.7d Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,36 @@ +.\" +.\" This file and its contents are supplied under the terms of the +.\" Common Development and Distribution License ("CDDL"), version 1.0. +.\" You may only use this file in accordance with the terms of version +.\" 1.0 of the CDDL. +.\" +.\" A full copy of the text of the CDDL should have accompanied this +.\" source. A copy of the CDDL is also available via the Internet at +.\" http://www.illumos.org/license/CDDL. +.\" +.\" +.\" Copyright 2020 Oxide Computer Company +.\" +.Dd September 1, 2020 +.Dt USMN 7D +.Os +.Sh NAME +.Nm usmn +.Nd AMD SMN user access driver +.Sh SYNOPSIS +.Pa /devices/pseudo/amdzen@0/usmn@2:usmn.* +.Sh DESCRIPTION +The +.Nm +driver provides the ability to read data from the AMD system management +network +.Pq SMN +on AMD Family 17h +.Pq Zen, Zen+, and Zen 2 +processors. +.Pp +This driver is intended strictly for facilitating platform development +and is not recommended for systems that aren't doing kernel development +on AMD Zen platforms. +.Sh SEE ALSO +.Xr amdzen 7D diff -r 6d24e460e13d -r 1a5588aae48c usr/src/pkg/manifests/driver-cpu-amd-zen.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkg/manifests/driver-cpu-amd-zen.mf Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,70 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + + +set name=pkg.fmri value=pkg:/driver/cpu/amd/zen@$(PKGVERS) +set name=pkg.description value="AMD Zen Nexus Driver" +set name=pkg.summary value="AMD Zen Nexus Driver" +set name=info.classification \ + value=org.opensolaris.category.2008:System/Hardware +set name=variant.arch value=i386 +dir path=kernel group=sys +dir path=kernel/drv group=sys +dir path=kernel/drv/$(ARCH64) group=sys +dir path=usr/share/man +dir path=usr/share/man/man7d +driver name=amdzen +driver name=amdzen_stub \ + alias=pci1022,1440,p \ + alias=pci1022,1441,p \ + alias=pci1022,1442,p \ + alias=pci1022,1443,p \ + alias=pci1022,1444,p \ + alias=pci1022,1445,p \ + alias=pci1022,1446,p \ + alias=pci1022,1447,p \ + alias=pci1022,1450,p \ + alias=pci1022,1460,p \ + alias=pci1022,1461,p \ + alias=pci1022,1462,p \ + alias=pci1022,1463,p \ + alias=pci1022,1464,p \ + alias=pci1022,1465,p \ + alias=pci1022,1466,p \ + alias=pci1022,1467,p \ + alias=pci1022,1480,p \ + alias=pci1022,1490,p \ + alias=pci1022,1491,p \ + alias=pci1022,1492,p \ + alias=pci1022,1493,p \ + alias=pci1022,1494,p \ + alias=pci1022,1495,p \ + alias=pci1022,1496,p \ + alias=pci1022,1497,p \ + alias=pci1022,15d0,p \ + alias=pci1022,15e8,p \ + alias=pci1022,15e9,p \ + alias=pci1022,15ea,p \ + alias=pci1022,15eb,p \ + alias=pci1022,15ec,p \ + alias=pci1022,15ed,p \ + alias=pci1022,15ee,p \ + alias=pci1022,15ef,p +file path=kernel/drv/$(ARCH64)/amdzen group=sys +file path=kernel/drv/$(ARCH64)/amdzen_stub group=sys +file path=kernel/drv/amdzen.conf group=sys +file path=usr/share/man/man7d/amdzen.7d +license lic_CDDL license=lic_CDDL +link path=usr/share/man/man7d/amdzen_stub.7d target=amdzen.7d diff -r 6d24e460e13d -r 1a5588aae48c usr/src/pkg/manifests/driver-cpu-sensor.mf --- a/usr/src/pkg/manifests/driver-cpu-sensor.mf Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/pkg/manifests/driver-cpu-sensor.mf Wed Apr 08 21:35:09 2020 -0700 @@ -25,14 +25,6 @@ dir path=kernel/drv/$(ARCH64) group=sys dir path=usr/share/man dir path=usr/share/man/man7d -driver name=amdf17nbdf \ - alias=pci1022,1440,p \ - alias=pci1022,1450,p \ - alias=pci1022,1460,p \ - alias=pci1022,1480,p \ - alias=pci1022,1490,p \ - alias=pci1022,15d0,p \ - alias=pci1022,15e8,p driver name=amdnbtemp \ alias=pci1022,1203,p \ alias=pci1022,1303,p \ @@ -55,14 +47,16 @@ alias=pci8086,a231,p \ alias=pci8086,a2b1,p \ alias=pci8086,a379,p -file path=kernel/drv/$(ARCH64)/amdf17nbdf group=sys +driver name=smntemp alias=smntemp file path=kernel/drv/$(ARCH64)/amdnbtemp group=sys file path=kernel/drv/$(ARCH64)/coretemp group=sys file path=kernel/drv/$(ARCH64)/pchtemp group=sys +file path=kernel/drv/$(ARCH64)/smntemp group=sys file path=kernel/drv/coretemp.conf group=sys -file path=usr/share/man/man7d/amdf17nbdf.7d file path=usr/share/man/man7d/amdnbtemp.7d file path=usr/share/man/man7d/coretemp.7d file path=usr/share/man/man7d/pchtemp.7d +file path=usr/share/man/man7d/smntemp.7d license lic_CDDL license=lic_CDDL +depend fmri=driver/cpu/amd/zen type=require depend fmri=system/ksensor type=require diff -r 6d24e460e13d -r 1a5588aae48c usr/src/pkg/manifests/driver-developer-amd-zen.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkg/manifests/driver-developer-amd-zen.mf Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,33 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + + +set name=pkg.fmri value=pkg:/driver/developer/amd/zen@$(PKGVERS) +set name=pkg.description value="AMD Zen Developer Drivers" +set name=pkg.summary value="Misc. AMD Zen Drivers for Platform Development" +set name=info.classification \ + value=org.opensolaris.category.2008:System/Hardware +set name=variant.arch value=i386 +dir path=kernel group=sys +dir path=kernel/drv group=sys +dir path=kernel/drv/$(ARCH64) group=sys +dir path=usr/lib +dir path=usr/share/man +dir path=usr/share/man/man7d +driver name=usmn +file path=kernel/drv/$(ARCH64)/usmn group=sys +file path=usr/lib/usmn mode=0555 +file path=usr/share/man/man7d/usmn.7d +license lic_CDDL license=lic_CDDL diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/common/io/usb/hcd/xhci/xhci.c --- a/usr/src/uts/common/io/usb/hcd/xhci/xhci.c Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/common/io/usb/hcd/xhci/xhci.c Wed Apr 08 21:35:09 2020 -0700 @@ -1911,7 +1911,7 @@ xhci_t *xhcip = ddi_get_soft_state(xhci_soft_state, getminor(dev) & ~HUBD_IS_ROOT_HUB); - if (secpolicy_xhci(credp) != 0 || + if (secpolicy_hwmanip(credp) != 0 || crgetzoneid(credp) != GLOBAL_ZONEID) return (EPERM); diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/common/os/policy.c --- a/usr/src/uts/common/os/policy.c Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/common/os/policy.c Wed Apr 08 21:35:09 2020 -0700 @@ -1364,7 +1364,7 @@ * this is required because vop_access function should lock the * node for reading. A three argument function should be defined * which accepts the following argument: - * A pointer to the internal "node" type (inode *) + * A pointer to the internal "node" type (inode *) * vnode access bits (VREAD|VWRITE|VEXEC) * a pointer to the credential * @@ -1436,8 +1436,8 @@ * * If you are the file owner: * chown to other uid FILE_CHOWN_SELF - * chown to gid (non-member) FILE_CHOWN_SELF - * chown to gid (member) + * chown to gid (non-member) FILE_CHOWN_SELF + * chown to gid (member) * * Instead of PRIV_FILE_CHOWN_SELF, FILE_CHOWN is also * acceptable but the first one is reported when debugging. @@ -2409,13 +2409,14 @@ } /* - * secpolicy_xhci + * secpolicy_hwmanip * - * Determine if the subject can observe and manipulate the xhci driver with a - * dangerous blunt hammer. Requires all privileges. + * Determine if the subject can observe and manipulate a hardware device with a + * dangerous blunt hammer, often suggests they can do something destructive. + * Requires all privileges. */ int -secpolicy_xhci(const cred_t *cr) +secpolicy_hwmanip(const cred_t *cr) { return (secpolicy_require_set(cr, PRIV_FULLSET, NULL, KLPDARG_NONE)); } diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/common/sys/policy.h --- a/usr/src/uts/common/sys/policy.h Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/common/sys/policy.h Wed Apr 08 21:35:09 2020 -0700 @@ -53,12 +53,12 @@ * priv_policy_choice * determines extend of operation * audit on success - * returns a boolean_t indicating success (B_TRUE) or failure. + * returns a boolean_t indicating success (B_TRUE) or failure. * * priv_policy_only * when auditing is in appropriate (interrupt context) * to determine context of operation - * returns a boolean_t indicating success (B_TRUE) or failure. + * returns a boolean_t indicating success (B_TRUE) or failure. * */ int priv_policy(const cred_t *, int, boolean_t, int, const char *); @@ -165,7 +165,7 @@ int secpolicy_vnode_setids_setgids(const cred_t *, gid_t); int secpolicy_vnode_stky_modify(const cred_t *); int secpolicy_vscan(const cred_t *); -int secpolicy_xhci(const cred_t *); +int secpolicy_hwmanip(const cred_t *); int secpolicy_zinject(const cred_t *); int secpolicy_zfs(const cred_t *); int secpolicy_ucode_update(const cred_t *); diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/Makefile.files --- a/usr/src/uts/intel/Makefile.files Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/intel/Makefile.files Wed Apr 08 21:35:09 2020 -0700 @@ -342,3 +342,11 @@ # AMD Family 10h-16h temperature driver # AMDNBTEMP_OBJS = amdnbtemp.o + +# +# AMD Zen Nexus Driver +# +AMDZEN_OBJS = amdzen.o +AMDZEN_STUB_OBJS = amdzen_stub.o +SMNTEMP_OBJS = smntemp.o +USMN_OBJS = usmn.o diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/Makefile.intel --- a/usr/src/uts/intel/Makefile.intel Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/intel/Makefile.intel Wed Apr 08 21:35:09 2020 -0700 @@ -766,7 +766,14 @@ # Sensor related drivers # DRV_KMODS += ksensor ksensor_test -DRV_KMODS += amdf17nbdf DRV_KMODS += coretemp DRV_KMODS += pchtemp DRV_KMODS += amdnbtemp +DRV_KMODS += smntemp + +# +# AMD Zen Nexus driver and Related +# +DRV_KMODS += amdzen +DRV_KMODS += amdzen_stub +DRV_KMODS += usmn diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/Makefile.rules --- a/usr/src/uts/intel/Makefile.rules Sat Oct 03 11:05:18 2020 -0700 +++ b/usr/src/uts/intel/Makefile.rules Wed Apr 08 21:35:09 2020 -0700 @@ -149,6 +149,10 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(UTSBASE)/intel/io/amdzen/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/intel/io/amr/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/amdf17nbdf/Makefile --- a/usr/src/uts/intel/amdf17nbdf/Makefile Sat Oct 03 11:05:18 2020 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# -# This file and its contents are supplied under the terms of the -# Common Development and Distribution License ("CDDL"), version 1.0. -# You may only use this file in accordance with the terms of version -# 1.0 of the CDDL. -# -# A full copy of the text of the CDDL should have accompanied this -# source. A copy of the CDDL is also available via the Internet at -# http://www.illumos.org/license/CDDL. -# - -# -# Copyright 2019, Joyent, Inc. -# - -UTSBASE = ../.. - -MODULE = amdf17nbdf -OBJECTS = $(AMDF17NBDF_OBJS:%=$(OBJS_DIR)/%) -ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) -CONF_SRCDIR = $(UTSBASE)/intel/io/amdf17nb - -include $(UTSBASE)/intel/Makefile.intel - -ALL_TARGET = $(BINARY) $(CONFMOD) -LINT_TARGET = $(MODULE).lint -INSTALL_TARGET = $(BINARY) $(ROOTMODULE) - -.KEEP_STATE: - -def: $(DEF_DEPS) - -all: $(ALL_DEPS) - -clean: $(CLEAN_DEPS) - -clobber: $(CLOBBER_DEPS) - -lint: $(LINT_DEPS) - -modlintlib: $(MODLINTLIB_DEPS) - -clean.lint: $(CLEAN_LINT_DEPS) - -install: $(INSTALL_DEPS) - -include $(UTSBASE)/intel/Makefile.targ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/amdzen/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/amdzen/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,41 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + +UTSBASE = ../.. + +MODULE = amdzen +OBJECTS = $(AMDZEN_OBJS:%=$(OBJS_DIR)/%) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/intel/io/amdzen + +include $(UTSBASE)/intel/Makefile.intel + +ALL_TARGET = $(BINARY) $(CONFMOD) +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + + +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +install: $(INSTALL_DEPS) + +include $(UTSBASE)/intel/Makefile.targ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/amdzen_stub/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/amdzen_stub/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,41 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + +UTSBASE = ../.. + +MODULE = amdzen_stub +OBJECTS = $(AMDZEN_STUB_OBJS:%=$(OBJS_DIR)/%) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) + +include $(UTSBASE)/intel/Makefile.intel + +ALL_TARGET = $(BINARY) +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +LDFLAGS += -dy -Ndrv/amdzen + +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +install: $(INSTALL_DEPS) + +include $(UTSBASE)/intel/Makefile.targ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c --- a/usr/src/uts/intel/io/amdf17nbdf/amdf17nbdf.c Sat Oct 03 11:05:18 2020 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1040 +0,0 @@ -/* - * This file and its contents are supplied under the terms of the - * Common Development and Distribution License ("CDDL"), version 1.0. - * You may only use this file in accordance with the terms of version - * 1.0 of the CDDL. - * - * A full copy of the text of the CDDL should have accompanied this - * source. A copy of the CDDL is also available via the Internet at - * http://www.illumos.org/license/CDDL. - */ - -/* - * Copyright 2019, Joyent, Inc. - */ - -/* - * AMD Family 17 Northbridge and Data Fabric Driver - * - * This driver attaches to the AMD Family 17h northbridge and data fabric bus. - * Each Zeppelin die ('processor node' in cpuid.c parlance) has its own - * northbridge and access to the data fabric bus. The northbridge and data - * fabric both provide access to various features such as: - * - * - The System Management Network (SMN) - * - Data Fabric via Fabric Indirect Config Access (FICAA) - * - * These are required to access things such as temperature sensors or memory - * controller configuration registers. - * - * In AMD Family 17h systems, the 'northbridge' is an ASIC that is part of the - * package that contains many I/O capabilities related to things like PCI - * express, etc. The 'data fabric' is the means by which different components - * both inside the socket and multiple sockets are connected together. Both the - * northbridge and the data fabric have dedicated PCI devices which the - * operating system can use to interact with them. - * - * ------------------------ - * Mapping Devices Together - * ------------------------ - * - * The operating system needs to expose things like temperature sensors and DRAM - * configuration registers in terms that are meaningful to the system such as - * logical CPUs, cores, etc. This driver attaches to the PCI IDs that represent - * the northbridge and data fabric; however, there are multiple PCI devices (one - * per die) that exist. This driver does manage to map all of these three things - * together; however, it requires some acrobatics. Unfortunately, there's no - * direct way to map a northbridge to its corresponding die. However, we can map - * a CPU die to a data fabric PCI device and a data fabric PCI device to a - * corresponding northbridge PCI device. - * - * In current Zen based products, there is a direct mapping between processor - * nodes and a data fabric PCI device. All of the devices are on PCI Bus 0 and - * start from Device 0x18. Device 0x18 maps to processor node 0, 0x19 to - * processor node 1, etc. This means that to map a logical CPU to a data fabric - * device, we take its processor node id, add it to 0x18 and find the PCI device - * that is on bus 0, device 0x18. As each data fabric device is attached based - * on its PCI ID, we add it to the global list, amd_nbdf_dfs that is in the - * amd_f17nbdf_t structure. - * - * The northbridge PCI device has a defined device and function, but the PCI bus - * that it's on can vary. Each die has its own series of PCI buses that are - * assigned to it and the northbridge PCI device is on the first of die-specific - * PCI bus for each die. This also means that the northbridge will not show up - * on PCI bus 0, which is the PCI bus that all of the data fabric devices are - * on. While conventionally the northbridge with the lowest PCI bus value - * would correspond to processor node zero, hardware does not guarantee that at - * all. Because we don't want to be at the mercy of firmware, we don't rely on - * this ordering, even though we have yet to find a system that deviates from - * this scheme. - * - * One of the registers in the data fabric device's function 0 - * (AMDF17_DF_CFG_ADDR_CTL), happens to have the first PCI bus that is - * associated with the processor node. This means, that we can map a data fabric - * device to a northbridge by finding the northbridge whose PCI bus matches the - * value in the corresponding data fabric's AMDF17_DF_CFG_ADDR_CTL. - * - * This means that we can map a northbridge to a data fabric device and a data - * fabric device to a die. Because these are generally 1:1 mappings, there is a - * transitive relationship and therefore we know which northbridge is associated - * with which processor die. This is summarized in the following image: - * - * +-------+ +----------------------------+ +--------------+ - * | Die 0 | ---> | Data Fabric PCI BDF 0/18/0 |-------> | Northbridge | - * +-------+ | AMDF17_DF_CFG_ADDR: bus 10 | | PCI 10/0/0 | - * ... +----------------------------+ +--------------+ - * +-------+ +------------------------------+ +--------------+ - * | Die n | ---> | Data Fabric PCI BDF 0/18+n/0 |-------> | Northbridge | - * +-------+ | AMDF17_DF_CFG_ADDR: bus 133 | | PCI 133/0/0 | - * +------------------------------+ +--------------+ - * - * Note, the PCI buses used by the northbridges here are arbitrary. They do not - * reflect the actual values by hardware; however, the bus/device/function (BDF) - * of the data fabric accurately models hardware. All of the BDF values are in - * hex. - * - * Starting with the Rome generation of processors (Family 17h Model 30-3Fh), - * AMD has multiple northbridges that exist on a given die. All of these - * northbridges share the same data fabric and system management network port. - * From our perspective this means that some of the northbridge devices will be - * redundant and that we will no longer have a 1:1 mapping between the - * northbridge and the data fabric devices. Every data fabric will have a - * northbridge, but not every northbridge will have a data fabric device mapped. - * Because we're always trying to map from a die to a northbridge and not the - * reverse, the fact that there are extra northbridge devices hanging around - * that we don't know about shouldn't be a problem. - * - * ------------------------------- - * Attach and Detach Complications - * ------------------------------- - * - * Because we need to map different PCI devices together, this means that we - * have multiple dev_info_t structures that we need to manage. Each of these is - * independently attached and detached. While this is easily managed for attach, - * it is not for detach. - * - * Once a device has been detached it will only come back if we have an active - * minor node that will be accessed. While we have minor nodes associated with - * the northbridges, we don't with the data fabric devices. This means that if - * they are detached, nothing would ever cause them to be reattached. The system - * also doesn't provide us a way or any guarantees around making sure that we're - * attached to all such devices before we detach. As a result, unfortunately, - * it's easier to basically have detach always fail. - * - * To deal with both development and if issues arise in the field, there is a - * knob, amdf17df_allow_detach, which if set to a non-zero value, will allow - * instances to detach. - * - * --------------- - * Exposed Devices - * --------------- - * - * Currently we expose a single set of character devices which represent - * temperature sensors for this family of processors. Because temperature - * sensors exist on a per-processor node basis, we create a single minor node - * for each one. Because our naming matches the cpuid naming, FMA can match that - * up to logical CPUs and take care of matching the sensors appropriately. We - * internally rate limit the sensor updates to 100ms, which is controlled by the - * global amdf17nbdf_cache_ms. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * 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, - ®)) != 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", - ®s, &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 -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/amdzen.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/amdzen.c Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,950 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019, Joyent, Inc. + * Copyright 2020 Oxide Computer Company + */ + +/* + * Nexus Driver for AMD Zen family systems. The purpose of this driver is to + * provide access to the following resources in a single, centralized fashion: + * + * - The per-chip Data Fabric + * - The North Bridge + * - The System Management Network (SMN) + * + * This is a nexus driver as once we have attached to all the requisite + * components, we will enumerate child devices which consume this functionality. + * + * ------------------------ + * Mapping Devices Together + * ------------------------ + * + * The operating system needs to expose things like temperature sensors and DRAM + * configuration registers in terms that are meaningful to the system such as + * logical CPUs, cores, etc. This driver attaches to the PCI IDs that represent + * the northbridge and data fabric; however, there are multiple PCI devices (one + * per die) that exist. This driver does manage to map all of these three things + * together; however, it requires some acrobatics. Unfortunately, there's no + * direct way to map a northbridge to its corresponding die. However, we can map + * a CPU die to a data fabric PCI device and a data fabric PCI device to a + * corresponding northbridge PCI device. + * + * In current Zen based products, there is a direct mapping between processor + * nodes and a data fabric PCI device. All of the devices are on PCI Bus 0 and + * start from Device 0x18. Device 0x18 maps to processor node 0, 0x19 to + * processor node 1, etc. This means that to map a logical CPU to a data fabric + * device, we take its processor node id, add it to 0x18 and find the PCI device + * that is on bus 0, device 0x18. As each data fabric device is attached based + * on its PCI ID, we add it to the global list, amd_nbdf_dfs that is in the + * amd_f17nbdf_t structure. + * + * The northbridge PCI device has a defined device and function, but the PCI bus + * that it's on can vary. Each die has its own series of PCI buses that are + * assigned to it and the northbridge PCI device is on the first of die-specific + * PCI bus for each die. This also means that the northbridge will not show up + * on PCI bus 0, which is the PCI bus that all of the data fabric devices are + * on. While conventionally the northbridge with the lowest PCI bus value + * would correspond to processor node zero, hardware does not guarantee that at + * all. Because we don't want to be at the mercy of firmware, we don't rely on + * this ordering, even though we have yet to find a system that deviates from + * this scheme. + * + * One of the registers in the data fabric device's function 0 + * (AMDZEN_DF_F0_CFG_ADDR_CTL) happens to have the first PCI bus that is + * associated with the processor node. This means that we can map a data fabric + * device to a northbridge by finding the northbridge whose PCI bus matches the + * value in the corresponding data fabric's AMDZEN_DF_F0_CFG_ADDR_CTL. + * + * We can map a northbridge to a data fabric device and a data fabric device to + * a die. Because these are generally 1:1 mappings, there is a transitive + * relationship and therefore we know which northbridge is associated with which + * processor die. This is summarized in the following image: + * + * +-------+ +-----------------------------------+ +--------------+ + * | Die 0 |--->| Data Fabric PCI BDF 0/18/0 |------->| Northbridge | + * +-------+ | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 10 | | PCI 10/0/0 | + * ... +-----------------------------------+ +--------------+ + * +-------+ +------------------------------------+ +--------------+ + * | Die n |---->| Data Fabric PCI BDF 0/18+n/0 |------->| Northbridge | + * +-------+ | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 133 | | PCI 133/0/0 | + * +------------------------------------+ +--------------+ + * + * Note, the PCI buses used by the northbridges here are arbitrary. They do not + * reflect the actual values by hardware; however, the bus/device/function (BDF) + * of the data fabric accurately models hardware. All of the BDF values are in + * hex. + * + * Starting with the Rome generation of processors (Family 17h Model 30-3Fh), + * AMD has multiple northbridges that exist on a given die. All of these + * northbridges share the same data fabric and system management network port. + * From our perspective this means that some of the northbridge devices will be + * redundant and that we will no longer have a 1:1 mapping between the + * northbridge and the data fabric devices. Every data fabric will have a + * northbridge, but not every northbridge will have a data fabric device mapped. + * Because we're always trying to map from a die to a northbridge and not the + * reverse, the fact that there are extra northbridge devices hanging around + * that we don't know about shouldn't be a problem. + * + * ------------------------------- + * Attach and Detach Complications + * ------------------------------- + * + * Because we need to map different PCI devices together, this means that we + * have multiple dev_info_t structures that we need to manage. Each of these is + * independently attached and detached. While this is easily managed for attach, + * it is not for detach. Each of these devices is a 'stub'. + * + * Once a device has been detached it will only come back if we have an active + * minor node that will be accessed. This means that if they are detached, + * nothing would ever cause them to be reattached. The system also doesn't + * provide us a way or any guarantees around making sure that we're attached to + * all such devices before we detach. As a result, unfortunately, it's easier to + * basically have detach always fail. + * + * --------------- + * Exposed Devices + * --------------- + * + * Rather than try and have all of the different functions that could be + * provided by one driver, we instead have created a nexus driver that will + * itself try and load children. Children are all pseudo-device drivers that + * provide different pieces of functionality that use this. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amdzen.h" + +amdzen_t *amdzen_data; + +/* + * Array of northbridge IDs that we care about. + */ +static const uint16_t amdzen_nb_ids[] = { + /* Family 17h Ryzen, Epyc Models 00h-0fh (Zen uarch) */ + 0x1450, + /* Family 17h Raven Ridge Models 10h-1fh (Zen uarch) */ + 0x15d0, + /* Family 17h Epyc Models 30h-3fh, Matisse 700-7fh (Zen 2 uarch) */ + 0x1480 +}; + +typedef struct { + char *acd_name; + amdzen_child_t acd_addr; +} amdzen_child_data_t; + +static const amdzen_child_data_t amdzen_children[] = { + { "smntemp", AMDZEN_C_SMNTEMP }, + { "usmn", AMDZEN_C_USMN } +}; + +static uint32_t +amdzen_stub_get32(amdzen_stub_t *stub, off_t reg) +{ + return (pci_config_get32(stub->azns_cfgspace, reg)); +} + +static void +amdzen_stub_put32(amdzen_stub_t *stub, off_t reg, uint32_t val) +{ + pci_config_put32(stub->azns_cfgspace, reg, val); +} + +/* + * Perform a targeted 32-bit indirect read to a specific instance and function. + */ +static uint32_t +amdzen_df_read32(amdzen_t *azn, amdzen_df_t *df, uint8_t inst, uint8_t func, + uint16_t reg) +{ + uint32_t val; + + VERIFY(MUTEX_HELD(&azn->azn_mutex)); + val = AMDZEN_DF_F4_FICAA_TARG_INST | AMDZEN_DF_F4_FICAA_SET_REG(reg) | + AMDZEN_DF_F4_FICAA_SET_FUNC(func) | + AMDZEN_DF_F4_FICAA_SET_INST(inst); + amdzen_stub_put32(df->adf_funcs[4], AMDZEN_DF_F4_FICAA, val); + return (amdzen_stub_get32(df->adf_funcs[4], AMDZEN_DF_F4_FICAD_LO)); +} + +static uint32_t +amdzen_smn_read32(amdzen_t *azn, amdzen_df_t *df, uint32_t reg) +{ + VERIFY(MUTEX_HELD(&azn->azn_mutex)); + amdzen_stub_put32(df->adf_nb, AMDZEN_NB_SMN_ADDR, reg); + return (amdzen_stub_get32(df->adf_nb, AMDZEN_NB_SMN_DATA)); +} + +static amdzen_df_t * +amdzen_df_find(amdzen_t *azn, uint_t dfno) +{ + uint_t i; + + ASSERT(MUTEX_HELD(&azn->azn_mutex)); + if (dfno >= azn->azn_ndfs) { + return (NULL); + } + + for (i = 0; i < azn->azn_ndfs; i++) { + amdzen_df_t *df = &azn->azn_dfs[i]; + if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0) { + continue; + } + + if (dfno == 0) { + return (df); + } + dfno--; + } + + return (NULL); +} + +/* + * Client functions that are used by nexus children. + */ +int +amdzen_c_smn_read32(uint_t dfno, uint32_t reg, uint32_t *valp) +{ + amdzen_df_t *df; + amdzen_t *azn = amdzen_data; + + mutex_enter(&azn->azn_mutex); + df = amdzen_df_find(azn, dfno); + if (df == NULL) { + mutex_exit(&azn->azn_mutex); + return (ENOENT); + } + + if ((df->adf_flags & AMDZEN_DF_F_FOUND_NB) == 0) { + mutex_exit(&azn->azn_mutex); + return (ENXIO); + } + + *valp = amdzen_smn_read32(azn, df, reg); + mutex_exit(&azn->azn_mutex); + return (0); +} + +uint_t +amdzen_c_df_count(void) +{ + uint_t ret; + amdzen_t *azn = amdzen_data; + + mutex_enter(&azn->azn_mutex); + ret = azn->azn_ndfs; + mutex_exit(&azn->azn_mutex); + return (ret); +} + + +static boolean_t +amdzen_create_child(amdzen_t *azn, const amdzen_child_data_t *acd) +{ + int ret; + dev_info_t *child; + + if (ndi_devi_alloc(azn->azn_dip, acd->acd_name, + (pnode_t)DEVI_SID_NODEID, &child) != NDI_SUCCESS) { + dev_err(azn->azn_dip, CE_WARN, "failed to allocate child " + "dip for %s", acd->acd_name); + return (B_FALSE); + } + + ddi_set_parent_data(child, (void *)acd); + if ((ret = ndi_devi_online(child, 0)) != NDI_SUCCESS) { + dev_err(azn->azn_dip, CE_WARN, "failed to online child " + "dip %s: %d", acd->acd_name, ret); + return (B_FALSE); + } + + return (B_TRUE); +} + +static boolean_t +amdzen_map_dfs(amdzen_t *azn) +{ + amdzen_stub_t *stub; + + ASSERT(MUTEX_HELD(&azn->azn_mutex)); + + for (stub = list_head(&azn->azn_df_stubs); stub != NULL; + stub = list_next(&azn->azn_df_stubs, stub)) { + amdzen_df_t *df; + uint_t dfno; + + dfno = stub->azns_dev - AMDZEN_DF_FIRST_DEVICE; + if (dfno > AMDZEN_MAX_DFS) { + dev_err(stub->azns_dip, CE_WARN, "encountered df " + "device with illegal DF PCI b/d/f: 0x%x/%x/%x", + stub->azns_bus, stub->azns_dev, stub->azns_func); + goto err; + } + + df = &azn->azn_dfs[dfno]; + + if (stub->azns_func >= AMDZEN_MAX_DF_FUNCS) { + dev_err(stub->azns_dip, CE_WARN, "encountered df " + "device with illegal DF PCI b/d/f: 0x%x/%x/%x", + stub->azns_bus, stub->azns_dev, stub->azns_func); + goto err; + } + + if (df->adf_funcs[stub->azns_func] != NULL) { + dev_err(stub->azns_dip, CE_WARN, "encountered " + "duplicate df device with DF PCI b/d/f: 0x%x/%x/%x", + stub->azns_bus, stub->azns_dev, stub->azns_func); + goto err; + } + df->adf_funcs[stub->azns_func] = stub; + } + + return (B_TRUE); + +err: + azn->azn_flags |= AMDZEN_F_DEVICE_ERROR; + return (B_FALSE); +} + +static boolean_t +amdzen_check_dfs(amdzen_t *azn) +{ + uint_t i; + boolean_t ret = B_TRUE; + + for (i = 0; i < AMDZEN_MAX_DFS; i++) { + amdzen_df_t *df = &azn->azn_dfs[i]; + uint_t count = 0; + + /* + * We require all platforms to have DFs functions 0-6. Not all + * platforms have DF function 7. + */ + for (uint_t func = 0; func < AMDZEN_MAX_DF_FUNCS - 1; func++) { + if (df->adf_funcs[func] != NULL) { + count++; + } + } + + if (count == 0) + continue; + + if (count != 7) { + ret = B_FALSE; + dev_err(azn->azn_dip, CE_WARN, "df %u devices " + "incomplete", i); + } else { + df->adf_flags |= AMDZEN_DF_F_VALID; + azn->azn_ndfs++; + } + } + + return (ret); +} + +static const uint8_t amdzen_df_rome_ids[0x2b] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48 +}; + +/* + * Initialize our knowledge about a given series of nodes on the data fabric. + */ +static void +amdzen_setup_df(amdzen_t *azn, amdzen_df_t *df) +{ + uint_t i; + uint32_t val; + + val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_CFG_ADDR_CTL); + df->adf_nb_busno = AMDZEN_DF_F0_CFG_ADDR_CTL_BUS_NUM(val); + val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_FBICNT); + df->adf_nents = AMDZEN_DF_F0_FBICNT_COUNT(val); + if (df->adf_nents == 0) + return; + df->adf_ents = kmem_zalloc(sizeof (amdzen_df_ent_t) * df->adf_nents, + KM_SLEEP); + + for (i = 0; i < df->adf_nents; i++) { + amdzen_df_ent_t *dfe = &df->adf_ents[i]; + uint8_t inst = i; + + /* + * Unfortunately, Rome uses a discontinuous instance ID pattern + * while everything else we can find uses a contiguous instance + * ID pattern. This means that for Rome, we need to adjust the + * indexes that we iterate over, though the total number of + * entries is right. + */ + if (df->adf_funcs[0]->azns_did == 0x1490) { + if (inst > ARRAY_SIZE(amdzen_df_rome_ids)) { + dev_err(azn->azn_dip, CE_WARN, "Rome family " + "processor reported more ids than the PPR, " + "resting %u to instance zero", inst); + inst = 0; + } else { + inst = amdzen_df_rome_ids[inst]; + } + } + + dfe->adfe_drvid = inst; + dfe->adfe_info0 = amdzen_df_read32(azn, df, inst, 0, + AMDZEN_DF_F0_FBIINFO0); + dfe->adfe_info1 = amdzen_df_read32(azn, df, inst, 0, + AMDZEN_DF_F0_FBIINFO1); + dfe->adfe_info2 = amdzen_df_read32(azn, df, inst, 0, + AMDZEN_DF_F0_FBIINFO2); + dfe->adfe_info3 = amdzen_df_read32(azn, df, inst, 0, + AMDZEN_DF_F0_FBIINFO3); + dfe->adfe_syscfg = amdzen_df_read32(azn, df, inst, 1, + AMDZEN_DF_F1_SYSCFG); + dfe->adfe_mask0 = amdzen_df_read32(azn, df, inst, 1, + AMDZEN_DF_F1_FIDMASK0); + dfe->adfe_mask1 = amdzen_df_read32(azn, df, inst, 1, + AMDZEN_DF_F1_FIDMASK1); + + dfe->adfe_type = AMDZEN_DF_F0_FBIINFO0_TYPE(dfe->adfe_info0); + dfe->adfe_sdp_width = + AMDZEN_DF_F0_FBIINFO0_SDP_WIDTH(dfe->adfe_info0); + if (AMDZEN_DF_F0_FBIINFO0_ENABLED(dfe->adfe_info0)) { + dfe->adfe_flags |= AMDZEN_DFE_F_ENABLED; + } + dfe->adfe_fti_width = + AMDZEN_DF_F0_FBIINFO0_FTI_WIDTH(dfe->adfe_info0); + dfe->adfe_sdp_count = + AMDZEN_DF_F0_FBIINFO0_SDP_PCOUNT(dfe->adfe_info0); + dfe->adfe_fti_count = + AMDZEN_DF_F0_FBIINFO0_FTI_PCOUNT(dfe->adfe_info0); + if (AMDZEN_DF_F0_FBIINFO0_HAS_MCA(dfe->adfe_info0)) { + dfe->adfe_flags |= AMDZEN_DFE_F_MCA; + } + dfe->adfe_subtype = + AMDZEN_DF_F0_FBIINFO0_SUBTYPE(dfe->adfe_info0); + + dfe->adfe_inst_id = + AMDZEN_DF_F0_FBIINFO3_INSTID(dfe->adfe_info3); + dfe->adfe_fabric_id = + AMDZEN_DF_F0_FBIINFO3_FABID(dfe->adfe_info3); + } + + df->adf_syscfg = amdzen_stub_get32(df->adf_funcs[1], + AMDZEN_DF_F1_SYSCFG); + df->adf_nodeid = AMDZEN_DF_F1_SYSCFG_NODEID(df->adf_syscfg); + df->adf_mask0 = amdzen_stub_get32(df->adf_funcs[1], + AMDZEN_DF_F1_FIDMASK0); + df->adf_mask1 = amdzen_stub_get32(df->adf_funcs[1], + AMDZEN_DF_F1_FIDMASK1); +} + +static void +amdzen_find_nb(amdzen_t *azn, amdzen_df_t *df) +{ + amdzen_stub_t *stub; + + for (stub = list_head(&azn->azn_nb_stubs); stub != NULL; + stub = list_next(&azn->azn_nb_stubs, stub)) { + if (stub->azns_bus == df->adf_nb_busno) { + df->adf_flags |= AMDZEN_DF_F_FOUND_NB; + df->adf_nb = stub; + return; + } + } +} + +static void +amdzen_nexus_init(void *arg) +{ + uint_t i; + amdzen_t *azn = arg; + + /* + * First go through all of the stubs and assign the DF entries. + */ + mutex_enter(&azn->azn_mutex); + if (!amdzen_map_dfs(azn) || !amdzen_check_dfs(azn)) { + azn->azn_flags |= AMDZEN_F_MAP_ERROR; + goto done; + } + + for (i = 0; i < AMDZEN_MAX_DFS; i++) { + amdzen_df_t *df = &azn->azn_dfs[i]; + + if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0) + continue; + amdzen_setup_df(azn, df); + amdzen_find_nb(azn, df); + } + + /* + * Not all children may be installed. As such, we do not treat the + * failure of a child as fatal to the driver. + */ + mutex_exit(&azn->azn_mutex); + for (i = 0; i < ARRAY_SIZE(amdzen_children); i++) { + (void) amdzen_create_child(azn, &amdzen_children[i]); + } + mutex_enter(&azn->azn_mutex); + +done: + azn->azn_flags &= ~AMDZEN_F_ATTACH_DISPATCHED; + azn->azn_flags |= AMDZEN_F_ATTACH_COMPLETE; + azn->azn_taskqid = TASKQID_INVALID; + cv_broadcast(&azn->azn_cv); + mutex_exit(&azn->azn_mutex); +} + +static int +amdzen_stub_scan_cb(dev_info_t *dip, void *arg) +{ + amdzen_t *azn = arg; + uint16_t vid, did; + int *regs; + uint_t nregs, i; + boolean_t match = B_FALSE; + + if (dip == ddi_root_node()) { + return (DDI_WALK_CONTINUE); + } + + /* + * If a node in question is not a pci node, then we have no interest in + * it as all the stubs that we care about are related to pci devices. + */ + if (strncmp("pci", ddi_get_name(dip), 3) != 0) { + return (DDI_WALK_PRUNECHILD); + } + + /* + * If we can't get a device or vendor ID and prove that this is an AMD + * part, then we don't care about it. + */ + vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "vendor-id", PCI_EINVAL16); + did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "device-id", PCI_EINVAL16); + if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) { + return (DDI_WALK_CONTINUE); + } + + if (vid != AMDZEN_PCI_VID_AMD) { + return (DDI_WALK_CONTINUE); + } + + for (i = 0; i < ARRAY_SIZE(amdzen_nb_ids); i++) { + if (amdzen_nb_ids[i] == did) { + match = B_TRUE; + } + } + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "reg", ®s, &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", ®s, &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 -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/amdzen.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/amdzen.conf Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,16 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + +name="amdzen" parent="pseudo" instance=0; diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/amdzen.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/amdzen.h Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,305 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +#ifndef _AMDZEN_H +#define _AMDZEN_H + +#include +#include +#include +#include +#include +#include + +/* + * This header describes properties of the data fabric and our internal state + * for the Zen Nexus driver. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The data fabric devices are always defined to be on PCI bus zero starting at + * device 0x18. + */ +#define AMDZEN_DF_BUSNO 0x00 +#define AMDZEN_DF_FIRST_DEVICE 0x18 + +/* + * The maximum amount of Data Fabric node's we can see. In Zen 1 there were up + * to four per package. + */ +#define AMDZEN_MAX_DFS 0x8 + +/* + * The maximum number of PCI functions we expect to encounter on the data + * fabric. + */ +#define AMDZEN_MAX_DF_FUNCS 0x8 + + +/* + * Registers in the data fabric space that we care about for the purposes of the + * nexus driver understanding itself. + */ + +/* + * This set of registers provides us access to the count of instances in the + * data fabric and then a number of different pieces of information about them + * like their type. Note, these registers require indirect access because the + * information cannot be broadcast. + */ +#define AMDZEN_DF_F0_FBICNT 0x40 +#define AMDZEN_DF_F0_FBICNT_COUNT(x) BITX(x, 7, 0) +#define AMDZEN_DF_F0_FBIINFO0 0x44 +#define AMDZEN_DF_F0_FBIINFO0_TYPE(x) BITX(x, 3, 0) +typedef enum { + AMDZEN_DF_TYPE_CCM = 0, + AMDZEN_DF_TYPE_GCM, + AMDZEN_DF_TYPE_NCM, + AMDZEN_DF_TYPE_IOMS, + AMDZEN_DF_TYPE_CS, + AMDZEN_DF_TYPE_TCDX, + AMDZEN_DF_TYPE_PIE, + AMDZEN_DF_TYPE_SPF, + AMDZEN_DF_TYPE_LLC, + AMDZEN_DF_TYPE_CAKE +} amdzen_df_type_t; +#define AMDZEN_DF_F0_FBIINFO0_SDP_WIDTH(x) BITX(x, 5, 4) +typedef enum { + AMDZEN_DF_SDP_W_64 = 0, + AMDZEN_DF_SDP_W_128, + AMDZEN_DF_SDP_W_256, + AMDZEN_DF_SDP_W_512 +} amdzen_df_sdp_width_t; +#define AMDZEN_DF_F0_FBIINFO0_ENABLED(x) BITX(x, 6, 6) +#define AMDZEN_DF_F0_FBIINFO0_FTI_WIDTH(x) BITX(x, 9, 8) +typedef enum { + AMDZEN_DF_FTI_W_64 = 0, + AMDZEN_DF_FTI_W_128, + AMDZEN_DF_FTI_W_256, + AMDZEN_DF_FTI_W_512 +} amdzen_df_fti_width_t; +#define AMDZEN_DF_F0_FBIINFO0_SDP_PCOUNT(x) BITX(x, 13, 12) +#define AMDZEN_DF_F0_FBIINFO0_FTI_PCOUNT(x) BITX(x, 18, 16) +#define AMDZEN_DF_F0_FBIINFO0_HAS_MCA(x) BITX(x, 23, 23) +#define AMDZEN_DF_F0_FBIINFO0_SUBTYPE(x) BITX(x, 26, 24) +#define AMDZEN_DF_SUBTYPE_NONE 0 +typedef enum { + AMDZEN_DF_CAKE_SUBTYPE_GMI = 1, + AMDZEN_DF_CAKE_SUBTYPE_xGMI = 2 +} amdzen_df_cake_subtype_t; + +typedef enum { + AMDZEN_DF_IOM_SUBTYPE_IOHUB = 1, +} amdzen_df_iom_subtype_t; + +typedef enum { + AMDZEN_DF_CS_SUBTYPE_UMC = 1, + AMDZEN_DF_CS_SUBTYPE_CCIX = 2 +} amdzen_df_cs_subtype_t; + +#define AMDZEN_DF_F0_FBIINFO1 0x48 +#define AMDZEN_DF_F0_FBIINFO1_FTI0_NINSTID(x) BITX(x, 7, 0) +#define AMDZEN_DF_F0_FBIINFO1_FTI1_NINSTID(x) BITX(x, 15, 8) +#define AMDZEN_DF_F0_FBIINFO1_FTI2_NINSTID(x) BITX(x, 23, 16) +#define AMDZEN_DF_F0_FBIINFO1_FTI3_NINSTID(x) BITX(x, 31, 24) +#define AMDZEN_DF_F0_FBIINFO2 0x4c +#define AMDZEN_DF_F0_FBIINFO2_FTI4_NINSTID(x) BITX(x, 7, 0) +#define AMDZEN_DF_F0_FBIINFO2_FTI5_NINSTID(x) BITX(x, 15, 8) +#define AMDZEN_DF_F0_FBIINFO3 0x50 +#define AMDZEN_DF_F0_FBIINFO3_INSTID(x) BITX(x, 7, 0) +#define AMDZEN_DF_F0_FBIINFO3_FABID(x) BITX(x, 13, 8) + +/* + * This register contains the information about the configuration of PCIe buses. + * We care about finding which one has our BUS A, which is required to map it to + * the northbridge. + */ +#define AMDZEN_DF_F0_CFG_ADDR_CTL 0x84 +#define AMDZEN_DF_F0_CFG_ADDR_CTL_BUS_NUM(x) BITX(x, 7, 0) + +/* + * Registers that describe how the system is actually put together. + */ +#define AMDZEN_DF_F1_SYSCFG 0x200 +#define AMDZEN_DF_F1_SYSCFG_DIE_PRESENT(X) BITX(x, 7, 0) +#define AMDZEN_DF_F1_SYSCFG_DIE_TYPE(x) BITX(x, 18, 11) +#define AMDZEN_DF_F1_SYSCFG_MYDIE_TYPE(x) BITX(x, 24, 23) +typedef enum { + AMDZEN_DF_DIE_TYPE_CPU = 0, + AMDZEN_DF_DIE_TYPE_APU, + AMDZEN_DF_DIE_TYPE_dGPU +} amdzen_df_die_type_t; +#define AMDZEN_DF_F1_SYSCFG_OTHERDIE_TYPE(x) BITX(x, 26, 25) +#define AMDZEN_DF_F1_SYSCFG_OTHERSOCK(x) BITX(x, 27, 27) +#define AMDZEN_DF_F1_SYSCFG_NODEID(x) BITX(x, 30, 28) + +#define AMDZEN_DF_F1_FIDMASK0 0x208 +#define AMDZEN_DF_F1_FIDMASK0_COMP_MASK(x) BITX(x, 9, 0) +#define AMDZEN_DF_F1_FIDMASK0_NODE_MASK(x) BITX(x, 25, 16) +#define AMDZEN_DF_F1_FIDMASK1 0x20C +#define AMDZEN_DF_F1_FIDMASK1_NODE_SHIFT(x) BITX(x, 3, 0) +#define AMDZEN_DF_F1_FIDMASK1_SKT_SHIFT(x) BITX(x, 9, 8) +#define AMDZEN_DF_F1_FIDMASK1_DIE_MASK(x) BITX(x, 18, 16) +#define AMDZEN_DF_F1_FIDMASK1_SKT_MASK(x) BITX(x, 26, 24) + +/* + * These two registers define information about the PSP and SMU on local and + * remote dies (from the context of the DF instance). The bits are the same. + */ +#define AMDZEN_DF_F1_PSPSMU_LOCAL 0x268 +#define AMDZEN_DF_F1_PSPSMU_REMOTE 0x268 +#define AMDZEN_DF_F1_PSPSMU_SMU_VALID(x) BITX(x, 0, 0) +#define AMDZEN_DF_F1_PSPSMU_SMU_UNITID(x) BITX(x, 6, 1) +#define AMDZEN_DF_F1_PSPSMU_SMU_COMPID(x) BITX(x, 15, 8) +#define AMDZEN_DF_F1_PSPSMU_PSP_VALID(x) BITX(x, 16, 16) +#define AMDZEN_DF_F1_PSPSMU_PSP_UNITID(x) BITX(x, 22, 17) +#define AMDZEN_DF_F1_PSPSMU_PSP_COMPID(x) BITX(x, 31, 24) + +#define AMDZEN_DF_F1_CAKE_ENCR 0x2cc + +/* + * These registers are used to define Indirect Access, commonly known as FICAA + * and FICAD for the system. While there are multiple copies of the indirect + * access registers in device 4, we're only allowed access to one set of those + * (which are the ones present here). Specifically the OS is given access to set + * 3. + */ +#define AMDZEN_DF_F4_FICAA 0x5c +#define AMDZEN_DF_F4_FICAA_TARG_INST (1 << 0) +#define AMDZEN_DF_F4_FICAA_SET_REG(x) ((x) & 0x3fc) +#define AMDZEN_DF_F4_FICAA_SET_FUNC(x) (((x) & 0x7) << 11) +#define AMDZEN_DF_F4_FICAA_SET_64B (1 << 14) +#define AMDZEN_DF_F4_FICAA_SET_INST(x) (((x) & 0xff) << 16) +#define AMDZEN_DF_F4_FICAD_LO 0x98 +#define AMDZEN_DF_F4_FICAD_HI 0x9c + +/* + * Northbridge registers that are relevant for the nexus, mostly for SMN. + */ +#define AMDZEN_NB_SMN_ADDR 0x60 +#define AMDZEN_NB_SMN_DATA 0x64 + +/* + * AMD PCI ID for reference + */ +#define AMDZEN_PCI_VID_AMD 0x1022 + +typedef enum { + AMDZEN_STUB_TYPE_DF, + AMDZEN_STUB_TYPE_NB +} amdzen_stub_type_t; + +typedef struct { + list_node_t azns_link; + dev_info_t *azns_dip; + uint16_t azns_vid; + uint16_t azns_did; + uint16_t azns_bus; + uint16_t azns_dev; + uint16_t azns_func; + ddi_acc_handle_t azns_cfgspace; +} amdzen_stub_t; + +typedef enum { + AMDZEN_DFE_F_MCA = 1 << 0, + AMDZEN_DFE_F_ENABLED = 1 << 1 +} amdzen_df_ent_flags_t; + +typedef struct { + uint8_t adfe_drvid; + amdzen_df_ent_flags_t adfe_flags; + amdzen_df_type_t adfe_type; + uint8_t adfe_subtype; + uint8_t adfe_fabric_id; + uint8_t adfe_inst_id; + amdzen_df_sdp_width_t adfe_sdp_width; + amdzen_df_fti_width_t adfe_fti_width; + uint8_t adfe_sdp_count; + uint8_t adfe_fti_count; + uint32_t adfe_info0; + uint32_t adfe_info1; + uint32_t adfe_info2; + uint32_t adfe_info3; + uint32_t adfe_syscfg; + uint32_t adfe_mask0; + uint32_t adfe_mask1; +} amdzen_df_ent_t; + +typedef enum { + AMDZEN_DF_F_VALID = 1 << 0, + AMDZEN_DF_F_FOUND_NB = 1 << 1 +} amdzen_df_flags_t; + +typedef struct { + amdzen_df_flags_t adf_flags; + uint_t adf_nb_busno; + amdzen_stub_t *adf_funcs[AMDZEN_MAX_DF_FUNCS]; + amdzen_stub_t *adf_nb; + uint_t adf_nents; + amdzen_df_ent_t *adf_ents; + uint32_t adf_nodeid; + uint32_t adf_syscfg; + uint32_t adf_mask0; + uint32_t adf_mask1; +} amdzen_df_t; + +typedef enum { + AMDZEN_F_UNSUPPORTED = 1 << 0, + AMDZEN_F_DEVICE_ERROR = 1 << 1, + AMDZEN_F_MAP_ERROR = 1 << 2, + AMDZEN_F_SCAN_DISPATCHED = 1 << 3, + AMDZEN_F_SCAN_COMPLETE = 1 << 4, + AMDZEN_F_ATTACH_DISPATCHED = 1 << 5, + AMDZEN_F_ATTACH_COMPLETE = 1 << 6 +} amdzen_flags_t; + +#define AMDZEN_F_TASKQ_MASK (AMDZEN_F_SCAN_DISPATCHED | \ + AMDZEN_F_SCAN_COMPLETE | AMDZEN_F_ATTACH_DISPATCHED | \ + AMDZEN_F_ATTACH_COMPLETE) + +typedef struct amdzen { + kmutex_t azn_mutex; + kcondvar_t azn_cv; + amdzen_flags_t azn_flags; + dev_info_t *azn_dip; + taskqid_t azn_taskqid; + uint_t azn_nscanned; + uint_t azn_npresent; + list_t azn_df_stubs; + list_t azn_nb_stubs; + uint_t azn_ndfs; + amdzen_df_t azn_dfs[AMDZEN_MAX_DFS]; +} amdzen_t; + +typedef enum { + AMDZEN_C_SMNTEMP = 1, + AMDZEN_C_USMN +} amdzen_child_t; + +/* + * Functions for stubs. + */ +extern int amdzen_attach_stub(dev_info_t *, ddi_attach_cmd_t); +extern int amdzen_detach_stub(dev_info_t *, ddi_detach_cmd_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _AMDZEN_H */ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/amdzen_client.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/amdzen_client.h Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,36 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +#ifndef _AMDZEN_CLIENT_H +#define _AMDZEN_CLIENT_H + +/* + * This header provides client routines to clients of the amdzen nexus driver. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int amdzen_c_smn_read32(uint_t df, uint32_t reg, uint32_t *); +extern uint_t amdzen_c_df_count(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _AMDZEN_CLIENT_H */ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/amdzen_stub.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/amdzen_stub.c Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,81 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +/* + * A stub driver for the AMD Zen Nexus. This is used to help us get all the + * relevant PCI devices into one place. See uts/intel/io/amdzen/amdzen.c for + * more details. + */ + +#include +#include +#include +#include +#include + +#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 -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/smntemp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/smntemp.c Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,343 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019, Joyent, Inc. + * Copyright 2020 Oxide Computer Company + */ + +/* + * This implements a temperature sensor for AMD Zen family products that rely + * upon the SMN framework for getting temperature information. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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, + ®)) != 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 -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/usmn.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/usmn.c Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,275 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +/* + * A device driver that provides user access to the AMD System Management + * Network for debugging purposes. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/io/amdzen/usmn.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/io/amdzen/usmn.h Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,41 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Oxide Computer Company + */ + +#ifndef _USMN_H +#define _USMN_H + +/* + * Private ioctls for interfacing with the usmn driver. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define USMN_IOCTL (('u' << 24) | ('s' << 16) | ('m' << 8)) + +#define USMN_READ (USMN_IOCTL | 0x01) +#define USMN_WRITE (USMN_IOCTL | 0x02) + +typedef struct usmn_reg { + uint32_t usr_addr; + uint32_t usr_data; +} usmn_reg_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _USMN_H */ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/smntemp/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/smntemp/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,41 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer company +# + +UTSBASE = ../.. + +MODULE = smntemp +OBJECTS = $(SMNTEMP_OBJS:%=$(OBJS_DIR)/%) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) + +include $(UTSBASE)/intel/Makefile.intel + +ALL_TARGET = $(BINARY) +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) +CPPFLAGS += -I$(UTSBASE)/intel/io/amdzen +LDFLAGS += -dy -Ndrv/amdzen + +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +install: $(INSTALL_DEPS) + +include $(UTSBASE)/intel/Makefile.targ diff -r 6d24e460e13d -r 1a5588aae48c usr/src/uts/intel/usmn/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/usmn/Makefile Wed Apr 08 21:35:09 2020 -0700 @@ -0,0 +1,41 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Oxide Computer Company +# + +UTSBASE = ../.. + +MODULE = usmn +OBJECTS = $(USMN_OBJS:%=$(OBJS_DIR)/%) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) + +include $(UTSBASE)/intel/Makefile.intel + +ALL_TARGET = $(BINARY) +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) +CPPFLAGS += -I$(UTSBASE)/intel/io/amdzen +LDFLAGS += -dy -Ndrv/amdzen + +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +install: $(INSTALL_DEPS) + +include $(UTSBASE)/intel/Makefile.targ