changeset 25205:2afbf6d9e698

12830 Want centralized ksensor framework 12831 temperature sensors could describe accuracy 12832 topo support for generic PCI device temp sensors Reviewed by: Ryan Zezeski <ryan@zinascii.com> Reviewed by: Toomas Soome <toomas@me.com> Approved by: Dan McDonald <danmcd@joyent.com>
author Robert Mustacchi <rm@fingolfin.org>
date Sat, 04 Apr 2020 21:20:18 -0700
parents 7d97d4b4b4ca
children 5232ecea59e8
files exception_lists/packaging usr/src/cmd/devfsadm/sensor_link.c usr/src/lib/fm/topo/modules/common/pcibus/pci_sensor.c usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c usr/src/lib/fm/topo/modules/common/pcibus/pcibus.h usr/src/lib/fm/topo/modules/i86pc/pcibus/Makefile usr/src/lib/fm/topo/modules/sun4/pcibus/Makefile.pci usr/src/man/man7d/Makefile usr/src/man/man7d/ksensor.7d usr/src/pkg/manifests/driver-cpu-sensor.mf usr/src/pkg/manifests/system-ksensor.mf usr/src/pkg/manifests/system-test-ostest.mf usr/src/test/os-tests/runfiles/default.run usr/src/test/os-tests/tests/Makefile usr/src/test/os-tests/tests/ksensor/Makefile usr/src/test/os-tests/tests/ksensor/ksensor_basic.c usr/src/test/os-tests/tests/ksensor/ksensor_err.c usr/src/test/os-tests/tests/ksensor/ksensor_fini.ksh usr/src/test/os-tests/tests/ksensor/ksensor_init.ksh usr/src/test/os-tests/tests/ksensor/ksensor_sread.c usr/src/test/os-tests/tests/ksensor/ksensor_stress.ksh usr/src/test/os-tests/tests/ksensor/ksensor_unload.ksh usr/src/uts/common/Makefile.files usr/src/uts/common/Makefile.rules usr/src/uts/common/io/ksensor/ksensor.conf usr/src/uts/common/io/ksensor/ksensor_drv.c usr/src/uts/common/io/ksensor/ksensor_test.c usr/src/uts/common/io/ksensor/ksensor_test.conf usr/src/uts/common/mapfiles/ddi.mapfile usr/src/uts/common/mapfiles/ksensor.mapfile usr/src/uts/common/os/autoconf.c usr/src/uts/common/os/devcfg.c usr/src/uts/common/os/ksensor.c usr/src/uts/common/sys/ddi_impldefs.h usr/src/uts/common/sys/esunddi.h usr/src/uts/common/sys/ksensor_impl.h usr/src/uts/common/sys/sensors.h usr/src/uts/intel/Makefile.intel usr/src/uts/intel/ksensor/Makefile usr/src/uts/intel/ksensor_test/Makefile usr/src/uts/sparc/Makefile.sparc usr/src/uts/sparc/ksensor/Makefile usr/src/uts/sparc/ksensor_test/Makefile
diffstat 43 files changed, 2591 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/exception_lists/packaging	Thu Jun 25 01:45:50 2020 +0000
+++ b/exception_lists/packaging	Sat Apr 04 21:20:18 2020 -0700
@@ -861,12 +861,6 @@
 usr/bin/ctfmerge
 
 #
-# SPARC doesn't currently use this today, though it may in the future.
-#
-usr/include/sys/sensors.h	sparc
-usr/lib/devfsadm/linkmod/SUNW_sensor_link.so	sparc
-
-#
 # libjedec is private
 #
 usr/include/libjedec.h
--- a/usr/src/cmd/devfsadm/sensor_link.c	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/cmd/devfsadm/sensor_link.c	Sat Apr 04 21:20:18 2020 -0700
@@ -11,6 +11,7 @@
 
 /*
  * Copyright 2019, Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
  */
 
 /*
@@ -33,8 +34,8 @@
 static int
 sensor_link(di_minor_t minor, di_node_t node)
 {
-	const char *t, *minor_name, *dir_path = NULL;
-	char *type, *c;
+	const char *t, *dir_path = NULL;
+	char *type, *name, *c;
 	char buf[PATH_MAX];
 	size_t len;
 
@@ -42,27 +43,40 @@
 		return (DEVFSADM_CONTINUE);
 	}
 
-	if ((minor_name = di_minor_name(minor)) == NULL) {
-		return (DEVFSADM_CONTINUE);
-	}
-
 	if ((type = strdup(t)) == NULL) {
 		return (DEVFSADM_TERMINATE);
 	}
 
-	while ((c = strchr(type, ':')) != NULL) {
+	c = type;
+	while ((c = strchr(c, ':')) != NULL) {
 		if (dir_path == NULL) {
 			dir_path = c + 1;
 		}
 		*c = '/';
 	}
 
+	if ((t = di_minor_name(minor)) == NULL) {
+		free(type);
+		return (DEVFSADM_CONTINUE);
+	}
+
+	if ((name = strdup(t)) == NULL) {
+		free(type);
+		return (DEVFSADM_TERMINATE);
+	}
+
+	c = name;
+	while ((c = strchr(c, ':')) != NULL) {
+		*c = '/';
+	}
+
+
 	if (dir_path == NULL || *dir_path == '\0') {
 		len = snprintf(buf, sizeof (buf), "%s/%s", SENSORS_BASE,
-		    minor_name);
+		    name);
 	} else {
 		len = snprintf(buf, sizeof (buf), "%s/%s/%s", SENSORS_BASE,
-		    dir_path, minor_name);
+		    dir_path, name);
 	}
 
 	if (len < sizeof (buf)) {
@@ -70,6 +84,7 @@
 	}
 
 	free(type);
+	free(name);
 	return (DEVFSADM_CONTINUE);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/fm/topo/modules/common/pcibus/pci_sensor.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,95 @@
+/*
+ * 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
+ */
+
+/*
+ * Construct sensors based on the ksensor framework for PCI devices. The kernel
+ * will create devices such that they show up
+ * /dev/sensors/temperature/pci/<bus>.<func>/<sensors>. This iterates and adds a
+ * sensor for the device based on the total number that exist for all of them.
+ */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <fm/topo_mod.h>
+#include <fm/topo_hc.h>
+#include <pcibus.h>
+#include <topo_sensor.h>
+
+int
+pci_create_dev_sensors(topo_mod_t *mod, tnode_t *dev)
+{
+	int ret;
+	DIR *d;
+	char path[PATH_MAX];
+	topo_instance_t binst, dinst;
+	struct dirent *ent;
+	tnode_t *parent = topo_node_parent(dev);
+
+	binst = topo_node_instance(parent);
+	dinst = topo_node_instance(dev);
+
+	if (snprintf(path, sizeof (path), "/dev/sensors/temperature/pci/%x.%x",
+	    binst, dinst) >= sizeof (path)) {
+		topo_mod_dprintf(mod, "failed to construct temp sensor "
+		    "directory path, path too long");
+		return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
+	}
+
+	topo_mod_dprintf(mod, "searching for sensors in %s", path);
+
+	d = opendir(path);
+	if (d == NULL) {
+		if (errno == ENOENT) {
+			return (0);
+		}
+
+		topo_mod_dprintf(mod, "failed to open %s: %s", path,
+		    strerror(errno));
+		return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
+	}
+
+	while ((ent = readdir(d)) != NULL) {
+		char spath[PATH_MAX];
+
+		if (strcmp(ent->d_name, ".") == 0 ||
+		    strcmp(ent->d_name, "..") == 0) {
+			continue;
+		}
+
+		if (snprintf(spath, sizeof (spath), "%s/%s", path,
+		    ent->d_name) >= sizeof (spath)) {
+			topo_mod_dprintf(mod, "failed to construct temp sensor "
+			    "path for %s/%s, path too long", path, ent->d_name);
+			ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
+			goto out;
+		}
+
+		topo_mod_dprintf(mod, "attempting to create sensor at %s",
+		    spath);
+		if ((ret = topo_sensor_create_temp_sensor(mod, dev, spath,
+		    ent->d_name)) < 0) {
+			goto out;
+		}
+
+	}
+	ret = 0;
+
+out:
+	(void) closedir(d);
+
+	return (ret);
+}
--- a/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c	Sat Apr 04 21:20:18 2020 -0700
@@ -493,6 +493,11 @@
 		return (NULL);
 	}
 
+	if (pci_create_dev_sensors(mod, ntn) < 0) {
+		topo_node_unbind(ntn);
+		return (NULL);
+	}
+
 	/*
 	 * We can expect to find pci-express functions beneath the device
 	 */
@@ -577,6 +582,11 @@
 		return (NULL);
 	}
 
+	if (pci_create_dev_sensors(mod, ntn) < 0) {
+		topo_node_unbind(ntn);
+		return (NULL);
+	}
+
 	/*
 	 * We can expect to find pci functions beneath the device
 	 */
--- a/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.h	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.h	Sat Apr 04 21:20:18 2020 -0700
@@ -27,8 +27,6 @@
 #ifndef _PCIBUS_H
 #define	_PCIBUS_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <sys/pci.h>
 #include <fm/topo_mod.h>
 #include <libdevinfo.h>
@@ -81,6 +79,7 @@
     int, int, int, int, int);
 
 extern int platform_pci_label(topo_mod_t *, tnode_t *, nvlist_t *, nvlist_t **);
+extern int pci_create_dev_sensors(topo_mod_t *, tnode_t *);
 
 #ifdef __cplusplus
 }
--- a/usr/src/lib/fm/topo/modules/i86pc/pcibus/Makefile	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/lib/fm/topo/modules/i86pc/pcibus/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -31,8 +31,10 @@
 HBDIR = ../../common/hostbridge
 NICDIR = ../../common/nic
 USBDIR = ../../common/usb
+SHAREDDIR = ../../common/shared
 UTILSRCS = did.c did_hash.c did_props.c util.c
-PCISRCS = pcibus.c pcibus_labels.c pcibus_hba.c
+PCISRCS = pcibus.c pcibus_labels.c pcibus_hba.c pci_sensor.c
+SHAREDSRCS = topo_sensor.c
 
 MODULESRCS = $(UTILSRCS) $(PCISRCS) pci_i86pc.c
 
@@ -40,4 +42,8 @@
 
 LDLIBS += -ldevinfo -lsmbios -lpcidb
 
-CPPFLAGS += -I$(UTILDIR) -I$(HBDIR) -I$(NICDIR) -I$(USBDIR)
+CPPFLAGS += -I$(UTILDIR) -I$(HBDIR) -I$(NICDIR) -I$(USBDIR) -I$(SHAREDDIR)
+
+%.o: $(SHAREDDIR)/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
--- a/usr/src/lib/fm/topo/modules/sun4/pcibus/Makefile.pci	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/lib/fm/topo/modules/sun4/pcibus/Makefile.pci	Sat Apr 04 21:20:18 2020 -0700
@@ -31,8 +31,10 @@
 NICDIR = ../../common/nic
 USBDIR = ../../common/usb
 HBDIR = ../../common/hostbridge
+SHAREDDIR = ../../common/shared
 UTILSRCS = did.c did_hash.c did_props.c util.c
-PCISRCS = pcibus.c pcibus_labels.c pci_sun4.c pcibus_hba.c
+PCISRCS = pcibus.c pcibus_labels.c pci_sun4.c pcibus_hba.c pci_sensor.c
+SHAREDSRCS = topo_sensor.c
 
 MODULESRCS = $(PCISRCS) $(UTILSRCS) pci_$(ARCH).c
 
@@ -40,6 +42,7 @@
 
 LDLIBS += -ldevinfo -lsmbios -lpcidb
 CPPFLAGS += -I$(SUN4DIR) -I$(UTILDIR) -I$(HBDIR) -I$(NICDIR) -I$(USBDIR)
+CPPFLAGS += -I$(SHAREDDIR)
 
 %.o: $(SUN4DIR)/%.c
 	$(COMPILE.c) -o $@ $<
@@ -48,3 +51,7 @@
 %.o: $(UTILDIR)/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
+
+%.o: $(SHAREDDIR)/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
--- a/usr/src/man/man7d/Makefile	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/man/man7d/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -78,6 +78,7 @@
 		ixgbe.7d	\
 		kmdb.7d		\
 		kstat.7d	\
+		ksensor.7d	\
 		ksyms.7d	\
 		llc1.7d		\
 		lockstat.7d	\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/man/man7d/ksensor.7d	Sat Apr 04 21:20:18 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
+.\"
+.Dd June 9, 2020
+.Dt KSENSOR 7D
+.Os
+.Sh NAME
+.Nm ksensor
+.Nd kernel sensor driver
+.Sh SYNOPSIS
+.In sys/sensors.h
+.Pp
+.Pa /dev/sensors/
+.Sh DESCRIPTION
+The
+.Nm
+driver provides access to sensors that are built using the kernel sensor
+framework.
+Sensors register with the framework are automatically made available to
+the system and used with the fault management architecture
+.Pq FMA .
+.Pp
+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.
+Sensor information can be dumped through the FMA developer utility
+.Sy fmtopo .
+.Sh SEE ALSO
+.Xr fmadm 1M
--- a/usr/src/pkg/manifests/driver-cpu-sensor.mf	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/pkg/manifests/driver-cpu-sensor.mf	Sat Apr 04 21:20:18 2020 -0700
@@ -23,10 +23,6 @@
 dir path=kernel group=sys
 dir path=kernel/drv group=sys
 dir path=kernel/drv/$(ARCH64) group=sys
-dir path=usr/include
-dir path=usr/include/sys
-dir path=usr/lib/devfsadm group=sys
-dir path=usr/lib/devfsadm/linkmod group=sys
 dir path=usr/share/man
 dir path=usr/share/man/man7d
 driver name=amdf17nbdf \
@@ -63,10 +59,9 @@
 file path=kernel/drv/$(ARCH64)/coretemp group=sys
 file path=kernel/drv/$(ARCH64)/pchtemp group=sys
 file path=kernel/drv/coretemp.conf group=sys
-file path=usr/include/sys/sensors.h mode=0644
-file path=usr/lib/devfsadm/linkmod/SUNW_sensor_link.so 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
 license lic_CDDL license=lic_CDDL
+depend fmri=system/ksensor type=require
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkg/manifests/system-ksensor.mf	Sat Apr 04 21:20:18 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
+#
+
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/system/ksensor@$(PKGVERS)
+set name=pkg.description value="Kernel Sensor Framework"
+set name=pkg.summary value="Kernel Sensor Framework"
+set name=info.classification \
+    value=org.opensolaris.category.2008:System/Hardware
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+dir path=usr/include
+dir path=usr/include/sys
+dir path=usr/lib/devfsadm group=sys
+dir path=usr/lib/devfsadm/linkmod group=sys
+dir path=usr/share/man
+dir path=usr/share/man/man7d
+driver name=ksensor
+file path=kernel/drv/$(ARCH64)/ksensor group=sys
+file path=kernel/drv/ksensor.conf group=sys
+file path=usr/include/sys/sensors.h mode=0644
+file path=usr/lib/devfsadm/linkmod/SUNW_sensor_link.so group=sys
+file path=usr/share/man/man7d/ksensor.7d
+license lic_CDDL license=lic_CDDL
--- a/usr/src/pkg/manifests/system-test-ostest.mf	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/pkg/manifests/system-test-ostest.mf	Sat Apr 04 21:20:18 2020 -0700
@@ -22,6 +22,8 @@
 set name=info.classification \
     value=org.opensolaris.category.2008:Development/System
 set name=variant.arch value=$(ARCH)
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
 dir path=opt/os-tests
 dir path=opt/os-tests/bin
 dir path=opt/os-tests/runfiles
@@ -29,6 +31,7 @@
 dir path=opt/os-tests/tests/ddi_ufm
 dir path=opt/os-tests/tests/file-locking
 $(i386_ONLY)dir path=opt/os-tests/tests/i386
+dir path=opt/os-tests/tests/ksensor
 dir path=opt/os-tests/tests/libtopo
 dir path=opt/os-tests/tests/pf_key
 dir path=opt/os-tests/tests/sdevfs
@@ -38,6 +41,8 @@
 dir path=opt/os-tests/tests/stress
 dir path=opt/os-tests/tests/timer
 dir path=opt/os-tests/tests/uccid
+file path=kernel/drv/$(ARCH64)/ksensor_test group=sys
+file path=kernel/drv/ksensor_test.conf group=sys
 file path=opt/os-tests/README mode=0444
 file path=opt/os-tests/bin/ostest mode=0555
 file path=opt/os-tests/runfiles/default.run mode=0444
@@ -53,6 +58,16 @@
 $(i386_ONLY)file path=opt/os-tests/tests/i386/badseg_exec mode=0555
 $(i386_ONLY)file path=opt/os-tests/tests/i386/ldt mode=0555
 $(i386_ONLY)file path=opt/os-tests/tests/imc_test mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_basic.32 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_basic.64 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_err.32 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_err.64 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_fini mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_init mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_sread.32 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_sread.64 mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_stress mode=0555
+file path=opt/os-tests/tests/ksensor/ksensor_unload mode=0555
 file path=opt/os-tests/tests/libtopo/digraph-test mode=0555
 file path=opt/os-tests/tests/libtopo/digraph-test-in-badedge.xml mode=0444
 file path=opt/os-tests/tests/libtopo/digraph-test-in-badelement.xml mode=0444
--- a/usr/src/test/os-tests/runfiles/default.run	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/test/os-tests/runfiles/default.run	Sat Apr 04 21:20:18 2020 -0700
@@ -100,3 +100,14 @@
 [/opt/os-tests/tests/libtopo]
 user = root
 tests = ['digraph-test']
+
+#
+# Only the ksensor functional tests are included in the run file.
+# The stress test is designed to be run separately.
+#
+[/opt/os-tests/tests/ksensor]
+user = root
+pre = ksensor_init
+tests = [ 'ksensor_basic.32','ksensor_basic.64', 'ksensor_err.32',
+    'ksensor_err.64' ]
+post = ksensor_fini
--- a/usr/src/test/os-tests/tests/Makefile	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/test/os-tests/tests/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -19,6 +19,7 @@
 SUBDIRS =       \
 		ddi_ufm \
 		file-locking \
+		ksensor \
 		libtopo \
 		pf_key \
 		poll \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,71 @@
+#
+# 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
+#
+
+PROGS = \
+	ksensor_basic \
+	ksensor_err \
+	ksensor_sread
+
+SCRIPTS = \
+	ksensor_init \
+	ksensor_fini \
+	ksensor_stress \
+	ksensor_unload
+
+PROGS32 = $(PROGS:%=%.32)
+PROGS64 = $(PROGS:%=%.64)
+
+ROOTOPTDIR = $(ROOT)/opt/os-tests/tests
+ROOTOPTKSENSOR = $(ROOTOPTDIR)/ksensor
+ROOTOPTPROGS = $(PROGS32:%=$(ROOTOPTKSENSOR)/%) \
+	$(PROGS64:%=$(ROOTOPTKSENSOR)/%) \
+	$(SCRIPTS:%=$(ROOTOPTKSENSOR)/%)
+
+include $(SRC)/cmd/Makefile.cmd
+
+.KEEP_STATE:
+
+all: $(PROGS32) $(PROGS64)
+
+install: $(ROOTOPTPROGS)
+
+clean:
+
+$(ROOTOPTPROGS): $(PROGS32) $(PROGS64) $(ROOTOPTKSENSOR)
+
+$(ROOTOPTDIR):
+	$(INS.dir)
+
+$(ROOTOPTKSENSOR): $(ROOTOPTDIR)
+	$(INS.dir)
+
+$(ROOTOPTKSENSOR)/%: %
+	$(INS.file)
+
+$(ROOTOPTKSENSOR)/%: %.ksh
+	$(INS.rename)
+
+%.64: %.c
+	$(LINK64.c) -o $@ $< $(LDLIBS64)
+	$(POST_PROCESS)
+
+%.32: %.c
+	$(LINK.c) -o $@ $< $(LDLIBS)
+	$(POST_PROCESS)
+
+clobber:
+	$(RM) $(PROGS32) $(PROGS64)
+
+FRC:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_basic.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,92 @@
+/*
+ * 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
+ */
+
+/*
+ * Basic ksensor functionality test
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <err.h>
+#include <sys/sensors.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+static const char *ksensor_path = "/dev/sensors/test/test.temp.0.1";
+
+int
+main(void)
+{
+	sensor_ioctl_kind_t kind;
+	sensor_ioctl_temperature_t temp;
+	int ret = 0;
+
+	int fd = open(ksensor_path, O_RDONLY);
+	if (fd < 0) {
+		err(EXIT_FAILURE, "TEST FAILED: failed to open %s",
+		    ksensor_path);
+	}
+
+	arc4random_buf(&kind, sizeof (kind));
+	arc4random_buf(&temp, sizeof (temp));
+
+	if (ioctl(fd, SENSOR_IOCTL_TYPE, &kind) != 0) {
+		warn("TEST FAILED: failed to get sensor type");
+		ret = EXIT_FAILURE;
+	}
+
+	if (kind.sik_kind != SENSOR_KIND_TEMPERATURE) {
+		warnx("TEST FAILED: expected temperature sensor, found kind %d",
+		    kind);
+		ret = EXIT_FAILURE;
+	}
+
+	if (ioctl(fd, SENSOR_IOCTL_TEMPERATURE, &temp) != 0) {
+		warn("TEST FAILED: failed to get sensor temperature");
+		ret = EXIT_FAILURE;
+	}
+
+	/*
+	 * These values come from the dummy temperature sensor in ksensor_test.
+	 */
+	if (temp.sit_unit != SENSOR_UNIT_CELSIUS) {
+		warnx("TEST FAILED: expected temp unit %" PRIu32 ", but found "
+		    "%" PRIu32, SENSOR_UNIT_CELSIUS, temp.sit_unit);
+		ret = EXIT_FAILURE;
+	}
+
+	if (temp.sit_gran != 4) {
+		warnx("TEST FAILED: expected temp gran %" PRId32 ", but found "
+		    "%" PRId32, 4, temp.sit_gran);
+		ret = EXIT_FAILURE;
+	}
+
+	if (temp.sit_prec != -2) {
+		warnx("TEST FAILED: expected temp prec %" PRId32 ", but found "
+		    "%" PRId32, -2, temp.sit_prec);
+		ret = EXIT_FAILURE;
+	}
+
+	if (temp.sit_temp != 23) {
+		warnx("TEST FAILED: expected temp %" PRId64 ", but found "
+		    "%" PRId64, 23, temp.sit_temp);
+		ret = EXIT_FAILURE;
+	}
+
+	return (ret);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_err.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,117 @@
+/*
+ * 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
+ */
+
+/*
+ * Describe the purpose of this file.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <err.h>
+#include <sys/sensors.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/sysmacros.h>
+
+static const char *error_sensor = "/dev/sensors/test/test.eio.0";
+static int error_exit;
+
+static void
+error_kind(int fd, int exp)
+{
+	sensor_ioctl_kind_t kind, alt_kind;
+
+	arc4random_buf(&alt_kind, sizeof (alt_kind));
+	(void) memcpy(&kind, &alt_kind, sizeof (alt_kind));
+
+	if (ioctl(fd, SENSOR_IOCTL_TYPE, &kind) == 0) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TYPE succeeded on EIO "
+		    "sensor");
+		error_exit = EXIT_FAILURE;
+	}
+
+	if (errno != exp) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TYPE got errno %d, "
+		    "expected %d", errno, exp);
+		error_exit = EXIT_FAILURE;
+	}
+
+	if (memcmp(&kind, &alt_kind, sizeof (alt_kind)) != 0) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TYPE modified data on error");
+		error_exit = EXIT_FAILURE;
+	}
+}
+
+static void
+error_temp(int fd, int exp)
+{
+	sensor_ioctl_temperature_t temp, alt_temp;
+
+	arc4random_buf(&alt_temp, sizeof (alt_temp));
+	(void) memcpy(&temp, &alt_temp, sizeof (alt_temp));
+
+	if (ioctl(fd, SENSOR_IOCTL_TEMPERATURE, &temp) == 0) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TEMPERATURE suceeded on "
+		    "EIO sensor");
+		error_exit = EXIT_FAILURE;
+	}
+
+	if (errno != exp) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TEMPERATURE got errno %d, "
+		    "expected %d", errno, EIO);
+		error_exit = EXIT_FAILURE;
+	}
+
+	if (memcmp(&temp, &alt_temp, sizeof (alt_temp)) != 0) {
+		warnx("TEST FAILED: SENSIOR_IOCTL_TEMPERATURE modified "
+		    "data on error");
+		error_exit = EXIT_FAILURE;
+	}
+}
+
+int
+main(void)
+{
+	int i;
+	int flags[] = { O_RDWR, O_WRONLY, O_RDONLY | O_NDELAY,
+		O_RDONLY | O_NONBLOCK };
+	int fd = open(error_sensor, O_RDONLY);
+	if (fd < 0) {
+		err(EXIT_FAILURE, "TEST FAILED: failed to open %s",
+		    error_sensor);
+	}
+
+	error_kind(fd, EIO);
+	error_temp(fd, EIO);
+	(void) close(fd);
+
+	/*
+	 * Check for illegal open combinations.
+	 */
+	for (i = 0; i < ARRAY_SIZE(flags); i++) {
+		fd = open(error_sensor, flags[i]);
+		if (fd >= 0) {
+			printf("i is %d\n", i);
+			warnx("TEST FAILED: opened a sensor with flags 0x%x, "
+			    "but expected failure", flags[i]);
+			error_exit = EXIT_FAILURE;
+		}
+	}
+
+	return (error_exit);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_fini.ksh	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,23 @@
+#!/usr/bin/ksh
+#
+#
+# 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
+#
+
+if ! rem_drv ksensor_test; then
+	printf "failed to remove ksensor_test driver\n" 2>&1
+	exit 1
+fi
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_init.ksh	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,23 @@
+#!/usr/bin/ksh
+#
+#
+# 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
+#
+
+if ! add_drv ksensor_test; then
+	printf "failed to add ksensor_test driver\n" 2>&1
+	exit 1
+fi
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_sread.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,95 @@
+/*
+ * 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
+ */
+
+/*
+ * Basic program that reads random sensors, mostly ignoring errors. This is in
+ * support of the stress test program.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/sensors.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+#include <limits.h>
+#include <strings.h>
+
+/*
+ * Wait for a random amount in 1500 ms, but make sure to wait at least 10ms.
+ */
+static uint32_t timeout = 1500;
+static uint32_t skew = 10;
+
+int
+main(int argc, const char *argv[])
+{
+	int nsensors = 0, ninst = 0;
+	uint32_t ms;
+
+	if (argc != 3) {
+		errx(EXIT_FAILURE, "missing required args: ninstance, "
+		    "nsensors");
+	}
+
+	nsensors = atoi(argv[1]);
+	ninst = atoi(argv[2]);
+	if (nsensors <= 0 || ninst <= 0) {
+		errx(EXIT_FAILURE, "got bad values for some of nesnsors (%u), "
+		    "ninst (%u)", nsensors, ninst);
+	}
+
+	for (;;) {
+		int fd;
+		char buf[PATH_MAX];
+		uint32_t sens, inst;
+		struct timespec ts;
+		sensor_ioctl_temperature_t temp;
+
+		/* 0s based */
+		sens = arc4random_uniform(nsensors);
+		/* 1s based */
+		inst = arc4random_uniform(ninst) + 1;
+		(void) snprintf(buf, sizeof (buf),
+		    "/dev/sensors/test/test.temp.%u.%u", sens, inst);
+
+		fd = open(buf, O_RDONLY);
+		if (fd < 0) {
+			warn("failed to open %s", buf);
+			goto wait;
+		}
+
+		bzero(&temp, sizeof (temp));
+		if (ioctl(fd, SENSOR_IOCTL_TEMPERATURE, &temp) != 0) {
+			warn("failed to get sensor temp on %s", buf);
+		}
+
+		if (temp.sit_unit != SENSOR_UNIT_CELSIUS) {
+			warnx("data from sensor %s looks off, expected sensor "
+			    "to indicate Celsius, but instead %u",
+			    temp.sit_unit);
+		}
+
+		(void) close(fd);
+wait:
+		ms = arc4random_uniform(timeout) + skew;
+		ts.tv_sec = ms / 1000;
+		ts.tv_nsec = (ms % 1000) * (NANOSEC / MILLISEC);
+		(void) nanosleep(&ts, NULL);
+	}
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_stress.ksh	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,86 @@
+#!/usr/bin/ksh
+#
+#
+# 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
+#
+
+#
+# This is a stress test that tries to just generate a bunch of ksensor
+# activity. It will run launching a number of threads that do different
+# activities. The goal here is to try and have the following:
+#
+# o Things trying to modunload the ksensor_test and ksensor driver
+# o things trying to actually use the various sensors from the
+#   ksensor_test driver
+#
+# To make sure that everything cleans up when this exits, this script
+# wraps itself in a ctrun. The caller needs to ensure that the
+# ksensor_test driver is loaded to begin with. It may or may not be part
+# of the system when all is said and done.
+#
+
+sensor_base="/dev/sensors/test"
+
+#
+# The number of instances that we expect to exist.
+#
+sensor_inst=42
+sensor_count=4
+
+#
+# Tunnables
+#
+sensor_unloaders=50
+sensor_readers=500
+
+if [[ ${@:$#} != "elbereth" ]]; then
+	exec ctrun -o noorphan ksh $0 "elbereth"
+fi
+
+
+if [[ ! -L "$sensor_base/test.temp.0.1" ]]; then
+	printf "missing ksensor test data, ksensor_temp driver loaded\n" 2>&1
+	exit 1
+fi
+
+cat << EOL
+Beginning to run the ksensor stress test. This will launch processes
+which will:
+
+ o Attempt to modunload 'ksensor' driver ($sensor_unloaders procs)
+ o Attempt to modunload 'ksensor_test' driver ($sensor_unloaders procs)
+ o Attempt to read test sensors ($sensor_readers procs)
+
+To stop things, simply kill this process. All dependent processes will
+be cleaned up.
+EOL
+
+for ((i = 0; i < $sensor_unloaders; i++)); do
+	ksh ./ksensor_unload.ksh ksensor_test &
+	ksh ./ksensor_unload.ksh ksensor &
+done
+
+for ((i = 0; i < $sensor_readers; i++)); do
+	if [[ $(( $i % 2 )) -eq 0 ]]; then
+		./ksensor_sread.32 $sensor_inst $sensor_count &
+	else
+		./ksensor_sread.64 $sensor_inst $sensor_count &
+	fi
+done
+
+while :; do
+	wait
+done
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/os-tests/tests/ksensor/ksensor_unload.ksh	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,59 @@
+#!/usr/bin/ksh
+#
+#
+# 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
+#
+
+#
+# Sit in a loop trying to unload the driver specified as an argument.
+#
+
+ksensor_id=
+ksensor_drv=
+ksensor_to=30
+ksensor_skew=5
+
+function get_id
+{
+
+	while [[ -z "$ksensor_id" ]]; do
+		sleep 1
+		ksensor_id=$(modinfo | awk "{
+			if (\$6 == \"$1\") {
+			    print \$1
+			} }")
+	done
+}
+
+function unload
+{
+	while :; do
+		if ! modunload -i $ksensor_id 2>/dev/null; then
+			echo "failed to unload $ksensor_drv" >&2
+		else
+			echo "unloaded $ksensor_drv"
+		fi
+		sleep $((($RANDOM % $ksensor_to) + $ksensor_skew))
+	done
+}
+
+if [[ -z "$1" ]]; then
+	echo "Missing required driver name" >&2
+	exit 1
+fi
+
+ksensor_drv=$1
+get_id $ksensor_drv
+printf "Got module id for %s: %u\n" "$ksensor_drv" $ksensor_id
+unload
--- a/usr/src/uts/common/Makefile.files	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/Makefile.files	Sat Apr 04 21:20:18 2020 -0700
@@ -217,6 +217,7 @@
 		kiconv.o	\
 		klpd.o		\
 		kmem.o		\
+		ksensor.o	\
 		ksyms_snapshot.o	\
 		l_strplumb.o	\
 		labelsys.o	\
@@ -743,6 +744,9 @@
 
 KSTAT_OBJS +=	kstat.o
 
+KSENSOR_OBJS +=	ksensor_drv.o
+KSENSOR_TEST_OBJS += ksensor_test.o
+
 KSYMS_OBJS +=	ksyms.o
 
 INSTANCE_OBJS += inst_sync.o
--- a/usr/src/uts/common/Makefile.rules	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/Makefile.rules	Sat Apr 04 21:20:18 2020 -0700
@@ -913,6 +913,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/ksensor/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/ksocket/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/ksensor/ksensor.conf	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,23 @@
+#
+# 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="ksensor" parent="pseudo" instance=0;
+
+#
+# Unfortunately, to support the fact that drivers can come and create
+# sensors at any time. Right now the kernel doesn't cons the ksensor
+# driver back into existence.
+#
+ddi-forceattach=1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/ksensor/ksensor_drv.c	Sat Apr 04 21:20:18 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
+ */
+
+/*
+ * This pseudo-device driver implements access to kernel sensors. See
+ * uts/common/os/ksensor.c for more information on the framework and how this
+ * driver fits in.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/open.h>
+#include <sys/cred.h>
+#include <sys/stat.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/zone.h>
+#include <sys/ksensor_impl.h>
+
+static dev_info_t *ksensor_dip;
+
+static int
+ksensor_create_cb(id_t id, const char *class, const char *name)
+{
+	if (ddi_create_minor_node(ksensor_dip, name, S_IFCHR, (minor_t)id,
+	    class, 0) != 0) {
+		dev_err(ksensor_dip, CE_WARN, "!failed to create ksensor node "
+		    "for %s:%s (minor %d)", class, name, id);
+		return (EIO);
+	}
+
+	return (0);
+}
+
+static void
+ksensor_remove_cb(id_t id, const char *name)
+{
+	ddi_remove_minor_node(ksensor_dip, (char *)name);
+}
+
+static int
+ksensor_open(dev_t *devp, int flags, int otype, cred_t *credp)
+{
+	if (crgetzoneid(credp) != GLOBAL_ZONEID || drv_priv(credp) != 0) {
+		return (EPERM);
+	}
+
+	if ((flags & (FEXCL | FNDELAY | FNONBLOCK | FWRITE)) != 0) {
+		return (EINVAL);
+	}
+
+	if (otype != OTYP_CHR) {
+		return (EINVAL);
+	}
+
+	return (0);
+}
+
+static int
+ksensor_ioctl_kind(minor_t min, intptr_t arg, int mode)
+{
+	int ret;
+	sensor_ioctl_kind_t kind;
+
+	bzero(&kind, sizeof (kind));
+	ret = ksensor_op_kind((id_t)min, &kind);
+	if (ret == 0) {
+		if (ddi_copyout(&kind, (void *)arg, sizeof (kind),
+		    mode & FKIOCTL) != 0) {
+			ret = EFAULT;
+		}
+	}
+	return (ret);
+}
+
+static int
+ksensor_ioctl_temp(minor_t min, intptr_t arg, int mode)
+{
+	int ret;
+	sensor_ioctl_temperature_t temp;
+
+	bzero(&temp, sizeof (temp));
+	ret = ksensor_op_temperature((id_t)min, &temp);
+	if (ret == 0) {
+		if (ddi_copyout(&temp, (void *)arg, sizeof (temp),
+		    mode & FKIOCTL) != 0) {
+			ret = EFAULT;
+		}
+	}
+	return (ret);
+}
+
+static int
+ksensor_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+    int *rvalp)
+{
+	minor_t m;
+
+	if ((mode & FREAD) == 0) {
+		return (EINVAL);
+	}
+
+	m = getminor(dev);
+	switch (cmd) {
+	case SENSOR_IOCTL_TYPE:
+		return (ksensor_ioctl_kind(m, arg, mode));
+	case SENSOR_IOCTL_TEMPERATURE:
+		return (ksensor_ioctl_temp(m, arg, mode));
+	default:
+		return (ENOTTY);
+	}
+}
+
+static int
+ksensor_close(dev_t dev, int flags, int otype, cred_t *credp)
+{
+	return (0);
+}
+
+static int
+ksensor_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	switch (cmd) {
+	case DDI_RESUME:
+		return (DDI_SUCCESS);
+	case DDI_ATTACH:
+		break;
+	default:
+		return (DDI_FAILURE);
+	}
+
+	if (ksensor_dip != NULL) {
+		dev_err(dip, CE_WARN, "ksensor driver already attatched");
+		return (DDI_FAILURE);
+	}
+
+	ksensor_dip = dip;
+	if (ksensor_register(dip, ksensor_create_cb, ksensor_remove_cb) != 0) {
+		ksensor_dip = NULL;
+		return (DDI_FAILURE);
+	}
+
+	return (DDI_SUCCESS);
+}
+
+/*
+ * All minors always maps to a single instance. Don't worry about minor validity
+ * here.
+ */
+static int
+ksensor_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
+    void **resultp)
+{
+	if (cmd != DDI_INFO_DEVT2DEVINFO && cmd != DDI_INFO_DEVT2INSTANCE) {
+		return (DDI_FAILURE);
+	}
+
+	if (cmd == DDI_INFO_DEVT2DEVINFO) {
+		*resultp = ksensor_dip;
+	} else {
+		int inst = ddi_get_instance(ksensor_dip);
+		*resultp = (void *)(uintptr_t)inst;
+	}
+
+	return (DDI_SUCCESS);
+}
+
+static int
+ksensor_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	switch (cmd) {
+	case DDI_DETACH:
+		break;
+	case DDI_SUSPEND:
+		return (DDI_SUCCESS);
+	default:
+		return (DDI_FAILURE);
+	}
+
+	if (ksensor_dip == NULL) {
+		dev_err(dip, CE_WARN, "asked to detach ksensor driver when no "
+		    "dip is attached");
+		return (DDI_FAILURE);
+	}
+
+	if (ksensor_dip != dip) {
+		dev_err(dip, CE_WARN, "asked to detach ksensor driver, but dip "
+		    "doesn't match");
+		return (DDI_FAILURE);
+	}
+
+	ksensor_unregister(dip);
+	ddi_remove_minor_node(dip, NULL);
+	ksensor_dip = NULL;
+	return (DDI_SUCCESS);
+}
+
+static struct cb_ops ksensor_cb_ops = {
+	.cb_open = ksensor_open,
+	.cb_close = ksensor_close,
+	.cb_strategy = nodev,
+	.cb_print = nodev,
+	.cb_dump = nodev,
+	.cb_read = nodev,
+	.cb_write = nodev,
+	.cb_ioctl = ksensor_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 ksensor_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = ksensor_getinfo,
+	.devo_identify = nulldev,
+	.devo_probe = nulldev,
+	.devo_attach = ksensor_attach,
+	.devo_detach = ksensor_detach,
+	.devo_reset = nodev,
+	.devo_power = ddi_power,
+	.devo_quiesce = ddi_quiesce_not_needed,
+	.devo_cb_ops = &ksensor_cb_ops
+};
+
+static struct modldrv ksensor_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "Kernel Sensor driver",
+	.drv_dev_ops = &ksensor_dev_ops
+};
+
+static struct modlinkage ksensor_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &ksensor_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	return (mod_install(&ksensor_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&ksensor_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&ksensor_modlinkage));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/ksensor/ksensor_test.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,217 @@
+/*
+ * 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
+ */
+
+/*
+ * This driver is used to implement parts of the ksensor test suite.
+ */
+
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/conf.h>
+#include <sys/devops.h>
+#include <sys/zone.h>
+#include <sys/sensors.h>
+
+typedef struct ksensor_test {
+	dev_info_t *kt_dip;
+	id_t kt_sensor1;
+	id_t kt_sensor2;
+	id_t kt_sensor3;
+	id_t kt_sensor4;
+	id_t kt_sensor5;
+} ksensor_test_t;
+
+static int
+ksensor_test_temperature(void *arg, sensor_ioctl_temperature_t *temp)
+{
+	temp->sit_unit = SENSOR_UNIT_CELSIUS;
+	temp->sit_gran = 4;
+	temp->sit_prec = -2;
+	temp->sit_temp = 23;
+	return (0);
+}
+
+static const ksensor_ops_t ksensor_test_temp_ops = {
+	ksensor_kind_temperature,
+	ksensor_test_temperature
+};
+
+static int
+ksensor_test_kind_eio(void *arg, sensor_ioctl_kind_t *kindp)
+{
+	return (EIO);
+}
+
+static int
+ksensor_test_temp_eio(void *arg, sensor_ioctl_temperature_t *tempp)
+{
+	return (EIO);
+}
+
+static const ksensor_ops_t ksensor_test_eio_ops = {
+	ksensor_test_kind_eio,
+	ksensor_test_temp_eio
+};
+
+static int
+ksensor_test_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	int ret;
+	char buf[128];
+	ksensor_test_t *kt;
+
+	switch (cmd) {
+	case DDI_RESUME:
+		return (DDI_SUCCESS);
+	case DDI_ATTACH:
+		break;
+	default:
+		return (DDI_FAILURE);
+	}
+
+	kt = kmem_zalloc(sizeof (ksensor_test_t), KM_SLEEP);
+	kt->kt_dip = dip;
+
+	(void) snprintf(buf, sizeof (buf), "test.temp.%d.1",
+	    ddi_get_instance(dip));
+	if ((ret = ksensor_create(dip, &ksensor_test_temp_ops, NULL, buf,
+	    "ddi_sensor:test", &kt->kt_sensor1)) != 0) {
+		dev_err(dip, CE_WARN, "failed to attatch sensor %s: %d", buf,
+		    ret);
+		goto err;
+	}
+
+	(void) snprintf(buf, sizeof (buf), "test.temp.%d.2",
+	    ddi_get_instance(dip));
+	if ((ret = ksensor_create(dip, &ksensor_test_temp_ops, NULL, buf,
+	    "ddi_sensor:test", &kt->kt_sensor2)) != 0) {
+		dev_err(dip, CE_WARN, "failed to attatch sensor %s: %d", buf,
+		    ret);
+		goto err;
+	}
+
+	(void) snprintf(buf, sizeof (buf), "test.temp.%d.3",
+	    ddi_get_instance(dip));
+	if ((ret = ksensor_create(dip, &ksensor_test_temp_ops, NULL, buf,
+	    "ddi_sensor:test", &kt->kt_sensor3)) != 0) {
+		dev_err(dip, CE_WARN, "failed to attatch sensor %s: %d", buf,
+		    ret);
+		goto err;
+	}
+
+	(void) snprintf(buf, sizeof (buf), "test.temp.%d.4",
+	    ddi_get_instance(dip));
+	if ((ret = ksensor_create(dip, &ksensor_test_temp_ops, NULL, buf,
+	    "ddi_sensor:test", &kt->kt_sensor4)) != 0) {
+		dev_err(dip, CE_WARN, "failed to attatch sensor %s: %d", buf,
+		    ret);
+		goto err;
+	}
+
+	(void) snprintf(buf, sizeof (buf), "test.eio.%d",
+	    ddi_get_instance(dip));
+	if ((ret = ksensor_create(dip, &ksensor_test_eio_ops, NULL, buf,
+	    "ddi_sensor:test", &kt->kt_sensor5)) != 0) {
+		dev_err(dip, CE_WARN, "failed to attatch sensor %s: %d", buf,
+		    ret);
+		goto err;
+	}
+
+	ddi_set_driver_private(dip, kt);
+
+	return (DDI_SUCCESS);
+err:
+	(void) ksensor_remove(dip, KSENSOR_ALL_IDS);
+	kmem_free(kt, sizeof (ksensor_test_t));
+	return (DDI_FAILURE);
+}
+
+static int
+ksensor_test_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	ksensor_test_t *kt;
+
+	switch (cmd) {
+	case DDI_DETACH:
+		break;
+	case DDI_SUSPEND:
+		return (DDI_SUCCESS);
+	default:
+		return (DDI_FAILURE);
+	}
+
+	kt = ddi_get_driver_private(dip);
+	if (kt == NULL) {
+		dev_err(dip, CE_WARN, "failed to find ksensor_test_t");
+		return (DDI_FAILURE);
+	}
+
+	if (kt->kt_sensor3 != 0 &&
+	    ksensor_remove(dip, kt->kt_sensor3) != 0) {
+		dev_err(dip, CE_WARN, "failed to remove sensor 3");
+		return (DDI_FAILURE);
+	}
+	kt->kt_sensor3 = 0;
+	if (ksensor_remove(dip, KSENSOR_ALL_IDS) != 0) {
+		dev_err(dip, CE_WARN, "failed to remove sensors");
+		return (DDI_FAILURE);
+	}
+	kmem_free(kt, sizeof (*kt));
+	ddi_set_driver_private(dip, NULL);
+	return (DDI_SUCCESS);
+}
+
+static struct dev_ops ksensor_test_dev_ops = {
+	.devo_rev = DEVO_REV,
+	.devo_refcnt = 0,
+	.devo_getinfo = nodev,
+	.devo_identify = nulldev,
+	.devo_probe = nulldev,
+	.devo_attach = ksensor_test_attach,
+	.devo_detach = ksensor_test_detach,
+	.devo_reset = nodev,
+	.devo_power = ddi_power,
+	.devo_quiesce = ddi_quiesce_not_needed,
+};
+
+static struct modldrv ksensor_test_modldrv = {
+	.drv_modops = &mod_driverops,
+	.drv_linkinfo = "Kernel Sensor test driver",
+	.drv_dev_ops = &ksensor_test_dev_ops
+};
+
+static struct modlinkage ksensor_test_modlinkage = {
+	.ml_rev = MODREV_1,
+	.ml_linkage = { &ksensor_test_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	return (mod_install(&ksensor_test_modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&ksensor_test_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&ksensor_test_modlinkage));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/ksensor/ksensor_test.conf	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,58 @@
+#
+# 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="ksensor_test" parent="pseudo" instance=0;
+name="ksensor_test" parent="pseudo" instance=1;
+name="ksensor_test" parent="pseudo" instance=2;
+name="ksensor_test" parent="pseudo" instance=3;
+name="ksensor_test" parent="pseudo" instance=4;
+name="ksensor_test" parent="pseudo" instance=5;
+name="ksensor_test" parent="pseudo" instance=6;
+name="ksensor_test" parent="pseudo" instance=7;
+name="ksensor_test" parent="pseudo" instance=8;
+name="ksensor_test" parent="pseudo" instance=9;
+name="ksensor_test" parent="pseudo" instance=10;
+name="ksensor_test" parent="pseudo" instance=11;
+name="ksensor_test" parent="pseudo" instance=12;
+name="ksensor_test" parent="pseudo" instance=13;
+name="ksensor_test" parent="pseudo" instance=14;
+name="ksensor_test" parent="pseudo" instance=15;
+name="ksensor_test" parent="pseudo" instance=16;
+name="ksensor_test" parent="pseudo" instance=17;
+name="ksensor_test" parent="pseudo" instance=18;
+name="ksensor_test" parent="pseudo" instance=19;
+name="ksensor_test" parent="pseudo" instance=20;
+name="ksensor_test" parent="pseudo" instance=21;
+name="ksensor_test" parent="pseudo" instance=22;
+name="ksensor_test" parent="pseudo" instance=23;
+name="ksensor_test" parent="pseudo" instance=24;
+name="ksensor_test" parent="pseudo" instance=25;
+name="ksensor_test" parent="pseudo" instance=26;
+name="ksensor_test" parent="pseudo" instance=27;
+name="ksensor_test" parent="pseudo" instance=28;
+name="ksensor_test" parent="pseudo" instance=29;
+name="ksensor_test" parent="pseudo" instance=30;
+name="ksensor_test" parent="pseudo" instance=31;
+name="ksensor_test" parent="pseudo" instance=32;
+name="ksensor_test" parent="pseudo" instance=33;
+name="ksensor_test" parent="pseudo" instance=34;
+name="ksensor_test" parent="pseudo" instance=35;
+name="ksensor_test" parent="pseudo" instance=36;
+name="ksensor_test" parent="pseudo" instance=37;
+name="ksensor_test" parent="pseudo" instance=38;
+name="ksensor_test" parent="pseudo" instance=39;
+name="ksensor_test" parent="pseudo" instance=40;
+name="ksensor_test" parent="pseudo" instance=41;
+name="ksensor_test" parent="pseudo" instance=42;
--- a/usr/src/uts/common/mapfiles/ddi.mapfile	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/mapfiles/ddi.mapfile	Sat Apr 04 21:20:18 2020 -0700
@@ -70,6 +70,7 @@
 	cv_reltimedwait			{ FLAGS = EXTERN };
 	ddi_cb_register			{ FLAGS = EXTERN };
 	ddi_cb_unregister		{ FLAGS = EXTERN };
+	ddi_create_minor_node		{ FLAGS = EXTERN };
 	ddi_dev_regsize			{ FLAGS = EXTERN };
 	ddi_dma_addr_bind_handle	{ FLAGS = EXTERN };
 	ddi_dma_alloc_handle		{ FLAGS = EXTERN };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/mapfiles/ksensor.mapfile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,43 @@
+#
+# 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
+#
+
+#
+# MAPFILE HEADER START
+#
+# WARNING:  STOP NOW.  DO NOT MODIFY THIS FILE.
+# Object scoping must comply with the rules detailed in
+#
+#	usr/src/uts/common/README.mapfiles
+#
+# You should not be making modifications here until you've read the most current
+# copy of that file. If you need help, contact a gatekeeper for guidance.
+#
+# MAPFILE HEADER END
+#
+
+#
+# This file contains functions that are provided for ksensors.
+#
+
+$mapfile_version 2
+
+SYMBOL_SCOPE {
+    global:
+	ksensor_create			{ FLAGS = EXTERN };
+	ksensor_create_temp_pcidev	{ FLAGS = EXTERN };
+	ksensor_remove			{ FLAGS = EXTERN };
+	ksensor_kind_temperature	{ FLAGS = EXTERN };
+};
+
--- a/usr/src/uts/common/os/autoconf.c	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/os/autoconf.c	Sat Apr 04 21:20:18 2020 -0700
@@ -53,6 +53,7 @@
 #include <sys/fm/util.h>
 #include <sys/ddifm_impl.h>
 #include <sys/ddi_ufm_impl.h>
+#include <sys/ksensor_impl.h>
 
 extern dev_info_t *top_devinfo;
 extern dev_info_t *scsi_vhci_dip;
@@ -96,6 +97,7 @@
 	ndi_fm_init();
 	irm_init();
 	ufm_init();
+	ksensor_init();
 
 	(void) i_ddi_load_drvconf(DDI_MAJOR_T_NONE);
 
--- a/usr/src/uts/common/os/devcfg.c	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/os/devcfg.c	Sat Apr 04 21:20:18 2020 -0700
@@ -397,6 +397,9 @@
 	devi->devi_ct_count = -1;	/* counter not in use if -1 */
 	list_create(&(devi->devi_ct), sizeof (cont_device_t),
 	    offsetof(cont_device_t, cond_next));
+	list_create(&devi->devi_unbind_cbs, sizeof (ddi_unbind_callback_t),
+	    offsetof(ddi_unbind_callback_t, ddiub_next));
+	mutex_init(&devi->devi_unbind_lock, NULL, MUTEX_DEFAULT, NULL);
 
 	i_ddi_set_node_state((dev_info_t *)devi, DS_PROTO);
 	da_log_enter((dev_info_t *)devi);
@@ -493,6 +496,9 @@
 	if (devi->devi_ev_path)
 		kmem_free(devi->devi_ev_path, MAXPATHLEN);
 
+	mutex_destroy(&devi->devi_unbind_lock);
+	list_destroy(&devi->devi_unbind_cbs);
+
 	kmem_cache_free(ddi_node_cache, devi);
 }
 
@@ -830,6 +836,7 @@
 static int
 unbind_node(dev_info_t *dip)
 {
+	ddi_unbind_callback_t *cb;
 	ASSERT(DEVI(dip)->devi_node_state == DS_BOUND);
 	ASSERT(DEVI(dip)->devi_major != DDI_MAJOR_T_NONE);
 
@@ -844,6 +851,11 @@
 
 	DEVI(dip)->devi_major = DDI_MAJOR_T_NONE;
 	DEVI(dip)->devi_binding_name = DEVI(dip)->devi_node_name;
+
+	while ((cb = list_remove_head(&DEVI(dip)->devi_unbind_cbs)) != NULL) {
+		cb->ddiub_cb(cb->ddiub_arg, dip);
+	}
+
 	return (DDI_SUCCESS);
 }
 
@@ -9281,3 +9293,13 @@
 	;
 #endif
 }
+
+void
+e_ddi_register_unbind_callback(dev_info_t *dip, ddi_unbind_callback_t *cb)
+{
+	struct dev_info *devi = DEVI(dip);
+
+	mutex_enter(&devi->devi_unbind_lock);
+	list_insert_tail(&devi->devi_unbind_cbs, cb);
+	mutex_exit(&devi->devi_unbind_lock);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/os/ksensor.c	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,843 @@
+/*
+ * 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
+ */
+
+/*
+ * Kernel Sensor Framework
+ *
+ * The kernel sensor framework exists to provide a simple and straightforward
+ * means for various parts of the system to declare and instantiate sensor
+ * information. Between this and the ksensor character device
+ * (uts/common/io/ksensor/ksensor_drv.c) this exposes per-device sensors and
+ * character devices.
+ *
+ * --------------------------
+ * Driver and User Interfaces
+ * --------------------------
+ *
+ * Each sensor that is registered with the framework is exposed as a character
+ * device under /dev/sensors. The device class and node name are often ':'
+ * delineated and must begin with 'ddi_sensor'. Everything after 'ddi_sensor'
+ * will be created in a directory under /dev/sensors. So for example the Intel
+ * PCH driver uses a class "ddi_sensor:temperature:pch" and a node name of
+ * 'ts.%d'. This creates the node /dev/sensors/temperature/pch/ts.0. The
+ * devfsadm plugin automatically handles the creation of directories which makes
+ * the addition of additional sensor types easy to create.
+ *
+ * Strictly speaking, any device can manage their own sensors and minor nodes by
+ * using the appropriate class and implementing the corresponding ioctls. That
+ * was how the first kernel sensors were written; however, there are a lot of
+ * issues with that which led to this:
+ *
+ * 1. Every driver had to actually implement character devices.
+ *
+ * 2. Every driver had to duplicate a lot of the logic around open(9E),
+ *    close(9E), and ioctl(9E).
+ *
+ * 3. Drivers that tied into frameworks like mac(9E) or SCSAv3 needed a lot more
+ *    work to fit into this model. For example, because the minor state is
+ *    shared between all the instances and the frameworks, they would have
+ *    required shared, global state that they don't have today.
+ *
+ * Ultimately, having an operations vector and a callback argument makes work a
+ * lot simpler for the producers of sensor data and that simplicity makes it
+ * worthwhile to take on additional effort and work here.
+ *
+ * ----------
+ * Components
+ * ----------
+ *
+ * The ksensor framework is made of a couple of different pieces:
+ *
+ * 1. This glue that is a part of genunix.
+ * 2. The ksensor character device driver.
+ * 3. Sensor providers, which are generally drivers that register with the
+ *    ksensor framework.
+ *
+ * The implementation of (1) is all in this file. The implementation of (2) is
+ * in uts/common/io/ksensor/ksensor_drv.c. The implementation of (3) is found in
+ * all of the different leaf devices. Examples of (3) include pchtemp(7D) and
+ * igb(7D).
+ *
+ * We separate numbers one and two into two different components for a few
+ * reasons. The most important thing is that drivers that provide sensors should
+ * not be dependent on some other part of the system having been loaded. This
+ * makes a compelling argument for it being a part of the core kernel. However,
+ * like other subsystems (e.g. kstats, smbios, etc.), it's useful to separate
+ * out the thing that provides the interface to users with the thing that is
+ * used to glue together providers in the kernel. There's the added benefit that
+ * it's practically simpler to spin up a pseudo-device through a module.
+ *
+ * The ksensor character device driver (2) registers with the main genunix
+ * ksensor code (1) when it attaches and when it detaches. The kernel only
+ * allows a single driver to be attached to it. When that character device
+ * driver attaches, the ksensor framework will walk through all of the currently
+ * registered sensors and inform the character device driver of the nodes that
+ * it needs to create. While the character device driver is attached, the
+ * ksensor framework will also call back into it when a sensor needs to be
+ * removed.
+ *
+ * Generally speaking, this distinction of responsibilities allows the kernel
+ * sensor character device driver to attach and detach without impact to the
+ * sensor providers or them even being notified at all, it's all transparent to
+ * them.
+ *
+ * ------------------------------
+ * Sensor Lifetime and detach(9E)
+ * ------------------------------
+ *
+ * Traditionally, a device driver may be detached by the broader kernel whenever
+ * the kernel desires it. On debug builds this happens by a dedicated thread. On
+ * a non-debug build this may happen due to memory pressure or as an attempt to
+ * reclaim idle resources (though this is much less common). However, when the
+ * module is detached, the system remembers that minor nodes previously existed
+ * and that entries in /devices had been created. When something proceeds to
+ * access an entry in /devices again, the system will use that to bring a driver
+ * back to life. It doesn't matter whether it's a pseudo-device driver or
+ * something else, this can happen.
+ *
+ * One downside to the sensor framework, is that we need to emulate this
+ * behavior which leads to some amount of complexity here. But this is a
+ * worthwhile tradeoff as it makes things much simpler for providers and it's
+ * not too hard for us to emulate this behavior.
+ *
+ * When a sensor provider registers the sensor, the sensor becomes available to
+ * the system. When the sensor provider unregisters with the system, which
+ * happens during its detach routine, then we note that it has been detached;
+ * however, we don't delete its minor node and if something accesses it, we
+ * attempt to load the driver again, the same way that devfs (the file system
+ * behind /devices) does.
+ *
+ * For each dev_info_t that registers a sensor we register a callback such that
+ * when the device is removed, e.g. someone called rem_drv or physically pulls
+ * the device, then we'll be able to finally clean up the device. This lifetime
+ * can be represented in the following image:
+ *
+ *         |
+ *         |
+ *         +-----<-------------------------------------+
+ *         |                                           |
+ *         | . . call ksensor_create()                 |
+ *         v                                           |
+ *     +-------+                                       |
+ *     | Valid |                                       |
+ *     +-------+                                       |
+ *         |                                           ^
+ *         | . . call ksensor_remove()                 |
+ *         v                                           |
+ *    +---------+                                      |
+ *    | Invalid |                                      |
+ *    +---------+                                      |
+ *      |     |                                        |
+ *      |     | . . user uses sensor again             |
+ *      |     |                                        |
+ *      |     +-------------------+                    |
+ *      |                         |                    |
+ *      |                         v                    |
+ *      |                 +---------------+            |
+ *      |                 | Attatching... |-->---------+
+ *      |                 +---------------+
+ *      | . . ddi unbind cb       |
+ *      |                         |
+ *      v                         | . . attatch fails or
+ *   +---------+                  |     no call to ksensor_create()
+ *   | Deleted |--<---------------+     again
+ *   +---------+
+ *
+ * When the DDI unbind callback is called, we know that the device is going to
+ * be removed. However, this happens within a subtle context with a majority of
+ * the device tree held (at least the dip's parent). In particular, another
+ * thread may be trying to obtain a hold on it and be blocked in
+ * ndi_devi_enter(). As the callback thread holds that, that could lead to a
+ * deadlock. As a result, we clean things up in two phases. One during the
+ * synchronous callback and the other via a taskq. In the first phase we
+ * logically do the following:
+ *
+ *  o Remove the dip from the list of ksensor dips and set the flag that
+ *    indicates that it's been removed.
+ *  o Remove all of the sensors from the global avl to make sure that new
+ *    threads cannot look it up.
+ *
+ * Then, after the taskq is dispatched, we do the following in taskq context:
+ *
+ *  o Tell the ksensor driver that it should remove the minor node.
+ *  o Block on each sensor until it is no-longer busy and then clean it up.
+ *  o Clean up the ksensor_dip_t.
+ *
+ * ------------------
+ * Accessing a Sensor
+ * ------------------
+ *
+ * Access to a particular sensor is serialized in the system. In addition to
+ * that, a number of steps are required to access one that is not unlike
+ * accessing a character device. When a given sensor is held the KSENSOR_F_BUSY
+ * flag is set in the ksensor_flags member. In addition, as part of taking a
+ * hold a number of side effects occur that ensure that the sensor provider's
+ * dev_info_t is considered busy and can't be detached.
+ *
+ * To obtain a hold on a sensor the following logical steps are required (see
+ * ksensor_hold_by_id() for the implementation):
+ *
+ *  1. Map the minor to the ksensor_t via the avl tree
+ *  2. Check that the ksensor's dip is valid
+ *  3. If the sensor is busy, wait until it is no longer so, and restart from
+ *     the top. Otherwise, mark the sensor as busy.
+ *  4. Enter the parent and place a hold on the sensor provider's dip.
+ *  5. Once again check if the dip is removed or not because we have to drop
+ *     locks during that operation.
+ *  6. Check if the ksensor has the valid flag set. If not, attempt to configure
+ *     the dip.
+ *  7. Assuming the sensor is now valid, we can return it.
+ *
+ * After this point, the sensor is considered valid for use. Once the consumer
+ * is finished with the sensor, it should be released by calling
+ * ksensor_release().
+ *
+ * An important aspect of the above scheme is that the KSENSOR_F_BUSY flag is
+ * required to progress through the validation and holding of the device. This
+ * makes sure that only one thread is attempting to attach it at a given time. A
+ * reasonable future optimization would be to amortize this cost in open(9E)
+ * and close(9E) of the minor and to bump a count as it being referenced as long
+ * as it is open.
+ *
+ * -----------------------------
+ * Character Device Registration
+ * -----------------------------
+ *
+ * The 'ksensor' character device driver can come and go. To support this, the
+ * ksensor framework communicates with the ksensor character device by a
+ * well-defined set of callbacks, used to indicate sensor addition and removal.
+ * The ksensor character device is found in uts/common/io/ksensor/ksensor_drv.c.
+ * The ksensor character device is responsible for creating and destroying minor
+ * nodes.
+ *
+ * Each ksensor_t has a flag, KSENSOR_F_NOTIFIED, that is used to indicate
+ * whether or not the registered driver has been notified of the sensor. When a
+ * callback is first registered, we'll walk through the entire list of nodes to
+ * make sure that its minor has been created. When unregistering, the minor node
+ * remove callback will not be called; however, this can generally by dealt with
+ * by calling something like ddi_remove_minor_node(dip, NULL).
+ *
+ * -------
+ * Locking
+ * -------
+ *
+ * The following rules apply to dealing with lock ordering:
+ *
+ * 1. The global ksensor_g_mutex protects all global data and must be taken
+ *    before a ksensor_t's individual mutex.
+ *
+ * 2. A thread should not hold any two ksensor_t's mutex at any time.
+ *
+ * 3. No locks should be held when attempting to grab or manipulate a
+ *    dev_info_t, e.g. ndi_devi_enter().
+ *
+ * 4. Unless the ksensor is actively being held, whenever a ksensor is found,
+ *    one must check whether the ksensor_dip_t flag KSENSOR_DIP_F_REMOVED is
+ *    set or not and whether the ksensor_t's KSENSOR_F_VALID flag is set.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/cred.h>
+#include <sys/ddi.h>
+#include <sys/stat.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/esunddi.h>
+#include <sys/ksensor_impl.h>
+#include <sys/ddi_impldefs.h>
+#include <sys/pci.h>
+#include <sys/avl.h>
+#include <sys/list.h>
+#include <sys/stddef.h>
+#include <sys/sysmacros.h>
+#include <sys/fs/dv_node.h>
+
+typedef enum {
+	/*
+	 * This flag indicates that the subscribing ksensor character device has
+	 * been notified about this flag.
+	 */
+	KSENSOR_F_NOTIFIED	= 1 << 0,
+	/*
+	 * This indicates that the sensor is currently valid, meaning that the
+	 * ops vector and argument are safe to use. This is removed when a
+	 * driver with a sensor is detached.
+	 */
+	KSENSOR_F_VALID		= 1 << 1,
+	/*
+	 * Indicates that a client has a hold on the sensor for some purpose.
+	 * This must be set before trying to get an NDI hold. Once this is set
+	 * and a NDI hold is in place, it is safe to use the operations vector
+	 * and argument.
+	 */
+	KSENSOR_F_BUSY		= 1 << 2,
+} ksensor_flags_t;
+
+typedef enum {
+	KSENSOR_DIP_F_REMOVED	= 1 << 0
+} ksensor_dip_flags_t;
+
+typedef struct {
+	list_node_t ksdip_link;
+	ksensor_dip_flags_t ksdip_flags;
+	dev_info_t *ksdip_dip;
+	ddi_unbind_callback_t ksdip_cb;
+	list_t ksdip_sensors;
+} ksensor_dip_t;
+
+typedef struct {
+	kmutex_t ksensor_mutex;
+	kcondvar_t ksensor_cv;
+	ksensor_flags_t ksensor_flags;
+	list_node_t ksensor_dip_list;
+	avl_node_t ksensor_id_avl;
+	uint_t ksensor_nwaiters;
+	ksensor_dip_t *ksensor_ksdip;
+	char *ksensor_name;
+	char *ksensor_class;
+	id_t ksensor_id;
+	const ksensor_ops_t *ksensor_ops;
+	void *ksensor_arg;
+} ksensor_t;
+
+static kmutex_t ksensor_g_mutex;
+static id_space_t *ksensor_ids;
+static list_t ksensor_dips;
+static avl_tree_t ksensor_avl;
+static dev_info_t *ksensor_cb_dip;
+static ksensor_create_f ksensor_cb_create;
+static ksensor_remove_f ksensor_cb_remove;
+
+static int
+ksensor_avl_compare(const void *l, const void *r)
+{
+	const ksensor_t *kl = l;
+	const ksensor_t *kr = r;
+
+	if (kl->ksensor_id > kr->ksensor_id) {
+		return (1);
+	} else if (kl->ksensor_id < kr->ksensor_id) {
+		return (-1);
+	} else {
+		return (0);
+	}
+}
+
+static ksensor_t *
+ksensor_find_by_id(id_t id)
+{
+	ksensor_t k, *ret;
+
+	ASSERT(MUTEX_HELD(&ksensor_g_mutex));
+
+	k.ksensor_id = id;
+	return (avl_find(&ksensor_avl, &k, NULL));
+
+}
+
+static ksensor_t *
+ksensor_search_ksdip(ksensor_dip_t *ksdip, const char *name, const char *class)
+{
+	ksensor_t *s;
+
+	ASSERT(MUTEX_HELD(&ksensor_g_mutex));
+
+	for (s = list_head(&ksdip->ksdip_sensors); s != NULL;
+	    s = list_next(&ksdip->ksdip_sensors, s)) {
+		if (strcmp(s->ksensor_name, name) == 0 &&
+		    strcmp(s->ksensor_class, class) == 0) {
+			return (s);
+		}
+	}
+
+	return (NULL);
+}
+
+static void
+ksensor_free_sensor(ksensor_t *sensor)
+{
+	strfree(sensor->ksensor_name);
+	strfree(sensor->ksensor_class);
+	id_free(ksensor_ids, sensor->ksensor_id);
+	mutex_destroy(&sensor->ksensor_mutex);
+	kmem_free(sensor, sizeof (ksensor_t));
+}
+
+static void
+ksensor_free_dip(ksensor_dip_t *ksdip)
+{
+	list_destroy(&ksdip->ksdip_sensors);
+	kmem_free(ksdip, sizeof (ksensor_dip_t));
+}
+
+static void
+ksensor_dip_unbind_taskq(void *arg)
+{
+	ksensor_dip_t *k = arg;
+	ksensor_t *sensor;
+
+	/*
+	 * First notify an attached driver that the nodes are going away
+	 * before we block and wait on them.
+	 */
+	mutex_enter(&ksensor_g_mutex);
+	for (sensor = list_head(&k->ksdip_sensors); sensor != NULL;
+	    sensor = list_next(&k->ksdip_sensors, sensor)) {
+		mutex_enter(&sensor->ksensor_mutex);
+		if (sensor->ksensor_flags & KSENSOR_F_NOTIFIED) {
+			ksensor_cb_remove(sensor->ksensor_id,
+			    sensor->ksensor_name);
+			sensor->ksensor_flags &= ~KSENSOR_F_NOTIFIED;
+		}
+		mutex_exit(&sensor->ksensor_mutex);
+	}
+	mutex_exit(&ksensor_g_mutex);
+
+	/*
+	 * Now that the driver has destroyed its minor, wait for anything that's
+	 * still there.
+	 */
+	while ((sensor = list_remove_head(&k->ksdip_sensors)) != NULL) {
+		mutex_enter(&sensor->ksensor_mutex);
+		while ((sensor->ksensor_flags & KSENSOR_F_BUSY) != 0 ||
+		    sensor->ksensor_nwaiters > 0) {
+			cv_wait(&sensor->ksensor_cv, &sensor->ksensor_mutex);
+		}
+		mutex_exit(&sensor->ksensor_mutex);
+		ksensor_free_sensor(sensor);
+	}
+	ksensor_free_dip(k);
+}
+
+static void
+ksensor_dip_unbind_cb(void *arg, dev_info_t *dip)
+{
+	ksensor_dip_t *k = arg;
+	ksensor_t *sensor;
+
+	/*
+	 * Remove the dip and the associated sensors from global visibility.
+	 * This will ensure that no new clients can find this; however, others
+	 * may have extent attempts to grab it (but lost the race in an NDI
+	 * hold).
+	 */
+	mutex_enter(&ksensor_g_mutex);
+	list_remove(&ksensor_dips, k);
+	k->ksdip_flags |= KSENSOR_DIP_F_REMOVED;
+	for (sensor = list_head(&k->ksdip_sensors); sensor != NULL;
+	    sensor = list_next(&k->ksdip_sensors, sensor)) {
+		avl_remove(&ksensor_avl, sensor);
+	}
+	mutex_exit(&ksensor_g_mutex);
+
+	(void) taskq_dispatch(system_taskq, ksensor_dip_unbind_taskq, k,
+	    TQ_SLEEP);
+}
+
+static ksensor_dip_t *
+ksensor_dip_create(dev_info_t *dip)
+{
+	ksensor_dip_t *k;
+
+	k = kmem_zalloc(sizeof (ksensor_dip_t), KM_SLEEP);
+	k->ksdip_dip = dip;
+	k->ksdip_cb.ddiub_cb = ksensor_dip_unbind_cb;
+	k->ksdip_cb.ddiub_arg = k;
+	list_create(&k->ksdip_sensors, sizeof (ksensor_t),
+	    offsetof(ksensor_t, ksensor_dip_list));
+	e_ddi_register_unbind_callback(dip, &k->ksdip_cb);
+
+	return (k);
+}
+
+static ksensor_dip_t *
+ksensor_dip_find(dev_info_t *dip)
+{
+	ksensor_dip_t *k;
+
+	ASSERT(MUTEX_HELD(&ksensor_g_mutex));
+	for (k = list_head(&ksensor_dips); k != NULL;
+	    k = list_next(&ksensor_dips, k)) {
+		if (dip == k->ksdip_dip) {
+			return (k);
+		}
+	}
+
+	return (NULL);
+}
+
+int
+ksensor_create(dev_info_t *dip, const ksensor_ops_t *ops, void *arg,
+    const char *name, const char *class, id_t *idp)
+{
+	ksensor_dip_t *ksdip;
+	ksensor_t *sensor;
+
+	if (dip == NULL || ops == NULL || name == NULL || class == NULL ||
+	    idp == NULL) {
+		return (EINVAL);
+	}
+
+	if (!DEVI_IS_ATTACHING(dip)) {
+		return (EAGAIN);
+	}
+
+	mutex_enter(&ksensor_g_mutex);
+	ksdip = ksensor_dip_find(dip);
+	if (ksdip == NULL) {
+		ksdip = ksensor_dip_create(dip);
+		list_insert_tail(&ksensor_dips, ksdip);
+	}
+
+	sensor = ksensor_search_ksdip(ksdip, name, class);
+	if (sensor != NULL) {
+		ASSERT3P(sensor->ksensor_ksdip, ==, ksdip);
+		if ((sensor->ksensor_flags & KSENSOR_F_VALID) != 0) {
+			mutex_exit(&ksensor_g_mutex);
+			dev_err(dip, CE_WARN, "tried to create sensor %s:%s "
+			    "which is currently active", class, name);
+			return (EEXIST);
+		}
+
+		sensor->ksensor_ops = ops;
+		sensor->ksensor_arg = arg;
+	} else {
+		sensor = kmem_zalloc(sizeof (ksensor_t), KM_SLEEP);
+		sensor->ksensor_ksdip = ksdip;
+		sensor->ksensor_name = ddi_strdup(name, KM_SLEEP);
+		sensor->ksensor_class = ddi_strdup(class, KM_SLEEP);
+		sensor->ksensor_id = id_alloc(ksensor_ids);
+		sensor->ksensor_ops = ops;
+		sensor->ksensor_arg = arg;
+		list_insert_tail(&ksdip->ksdip_sensors, sensor);
+		avl_add(&ksensor_avl, sensor);
+	}
+
+	sensor->ksensor_flags |= KSENSOR_F_VALID;
+
+	if (ksensor_cb_create != NULL) {
+
+		if (ksensor_cb_create(sensor->ksensor_id, sensor->ksensor_class,
+		    sensor->ksensor_name) == 0) {
+			sensor->ksensor_flags |= KSENSOR_F_NOTIFIED;
+		}
+	}
+
+	*idp = sensor->ksensor_id;
+	mutex_exit(&ksensor_g_mutex);
+
+	return (0);
+}
+
+int
+ksensor_create_temp_pcidev(dev_info_t *dip, const ksensor_ops_t *ops,
+    void *arg, const char *name, id_t *idp)
+{
+	char *pci_name, *type;
+	int *regs, ret;
+	uint_t nregs;
+	uint16_t bus, dev;
+
+	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0, "device_type",
+	    &type) != DDI_PROP_SUCCESS) {
+		return (EINVAL);
+	}
+
+	if (strcmp(type, "pciex") != 0 && strcmp(type, "pci") != 0) {
+		ddi_prop_free(type);
+		return (EINVAL);
+	}
+	ddi_prop_free(type);
+
+	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, "reg",
+	    &regs, &nregs) != DDI_PROP_SUCCESS) {
+		return (EINVAL);
+	}
+
+	if (nregs < 1) {
+		ddi_prop_free(regs);
+		return (EIO);
+	}
+
+	bus = PCI_REG_BUS_G(regs[0]);
+	dev = PCI_REG_DEV_G(regs[0]);
+	ddi_prop_free(regs);
+
+	pci_name = kmem_asprintf("%x.%x:%s", bus, dev, name);
+
+	ret = ksensor_create(dip, ops, arg, pci_name,
+	    "ddi_sensor:temperature:pci", idp);
+	strfree(pci_name);
+	return (ret);
+}
+
+/*
+ * When a driver removes a sensor, we basically mark it as invalid. This happens
+ * because drivers can detach and we will need to reattach them when the sensor
+ * is used again.
+ */
+int
+ksensor_remove(dev_info_t *dip, id_t id)
+{
+	ksensor_dip_t *kdip;
+	ksensor_t *sensor;
+
+	if (!DEVI_IS_ATTACHING(dip) && !DEVI_IS_DETACHING(dip)) {
+		return (EAGAIN);
+	}
+
+	mutex_enter(&ksensor_g_mutex);
+	kdip = ksensor_dip_find(dip);
+	if (kdip == NULL) {
+		mutex_exit(&ksensor_g_mutex);
+		return (ENOENT);
+	}
+
+	for (sensor = list_head(&kdip->ksdip_sensors); sensor != NULL;
+	    sensor = list_next(&kdip->ksdip_sensors, sensor)) {
+		if (sensor->ksensor_id == id || id == KSENSOR_ALL_IDS) {
+			mutex_enter(&sensor->ksensor_mutex);
+			sensor->ksensor_flags &= ~KSENSOR_F_VALID;
+			sensor->ksensor_ops = NULL;
+			sensor->ksensor_arg = NULL;
+			mutex_exit(&sensor->ksensor_mutex);
+		}
+	}
+	mutex_exit(&ksensor_g_mutex);
+	return (0);
+}
+
+static void
+ksensor_release(ksensor_t *sensor)
+{
+	int circ;
+	dev_info_t *pdip;
+
+	ddi_release_devi(sensor->ksensor_ksdip->ksdip_dip);
+
+	mutex_enter(&sensor->ksensor_mutex);
+	sensor->ksensor_flags &= ~KSENSOR_F_BUSY;
+	cv_broadcast(&sensor->ksensor_cv);
+	mutex_exit(&sensor->ksensor_mutex);
+}
+
+static int
+ksensor_hold_by_id(id_t id, ksensor_t **outp)
+{
+	int circ;
+	ksensor_t *sensor;
+	dev_info_t *pdip;
+
+restart:
+	mutex_enter(&ksensor_g_mutex);
+	sensor = ksensor_find_by_id(id);
+	if (sensor == NULL) {
+		mutex_exit(&ksensor_g_mutex);
+		*outp = NULL;
+		return (ESTALE);
+	}
+
+	if ((sensor->ksensor_ksdip->ksdip_flags & KSENSOR_DIP_F_REMOVED) != 0) {
+		mutex_exit(&ksensor_g_mutex);
+		*outp = NULL;
+		return (ESTALE);
+	}
+
+	mutex_enter(&sensor->ksensor_mutex);
+	if ((sensor->ksensor_flags & KSENSOR_F_BUSY) != 0) {
+		mutex_exit(&ksensor_g_mutex);
+		sensor->ksensor_nwaiters++;
+		while ((sensor->ksensor_flags & KSENSOR_F_BUSY) != 0) {
+			int cv = cv_wait_sig(&sensor->ksensor_cv,
+			    &sensor->ksensor_mutex);
+			if (cv == 0) {
+				sensor->ksensor_nwaiters--;
+				cv_broadcast(&sensor->ksensor_cv);
+				mutex_exit(&sensor->ksensor_mutex);
+				*outp = NULL;
+				return (EINTR);
+			}
+		}
+		sensor->ksensor_nwaiters--;
+		cv_broadcast(&sensor->ksensor_cv);
+		mutex_exit(&sensor->ksensor_mutex);
+		goto restart;
+	}
+
+	/*
+	 * We have obtained ownership of the sensor. At this point, we should
+	 * check to see if it's valid or not.
+	 */
+	sensor->ksensor_flags |= KSENSOR_F_BUSY;
+	pdip = ddi_get_parent(sensor->ksensor_ksdip->ksdip_dip);
+	mutex_exit(&sensor->ksensor_mutex);
+	mutex_exit(&ksensor_g_mutex);
+
+	/*
+	 * Grab a reference on the device node to ensure that it won't go away.
+	 */
+	ndi_devi_enter(pdip, &circ);
+	e_ddi_hold_devi(sensor->ksensor_ksdip->ksdip_dip);
+	ndi_devi_exit(pdip, circ);
+
+	/*
+	 * Now that we have an NDI hold, check if it's valid or not. It may have
+	 * become invalid while we were waiting due to a race.
+	 */
+	mutex_enter(&ksensor_g_mutex);
+	if ((sensor->ksensor_ksdip->ksdip_flags & KSENSOR_DIP_F_REMOVED) != 0) {
+		mutex_exit(&ksensor_g_mutex);
+		ksensor_release(sensor);
+		return (ESTALE);
+	}
+
+	mutex_enter(&sensor->ksensor_mutex);
+	if ((sensor->ksensor_flags & KSENSOR_F_VALID) == 0) {
+		mutex_exit(&sensor->ksensor_mutex);
+		mutex_exit(&ksensor_g_mutex);
+		(void) ndi_devi_config(pdip, NDI_NO_EVENT);
+		mutex_enter(&ksensor_g_mutex);
+		mutex_enter(&sensor->ksensor_mutex);
+
+		/*
+		 * If we attempted to reattach it and it isn't now valid, fail
+		 * this request.
+		 */
+		if ((sensor->ksensor_ksdip->ksdip_flags &
+		    KSENSOR_DIP_F_REMOVED) != 0 ||
+		    (sensor->ksensor_flags & KSENSOR_F_VALID) == 0) {
+			mutex_exit(&sensor->ksensor_mutex);
+			mutex_exit(&ksensor_g_mutex);
+			ksensor_release(sensor);
+			return (ESTALE);
+		}
+	}
+	mutex_exit(&sensor->ksensor_mutex);
+	mutex_exit(&ksensor_g_mutex);
+	*outp = sensor;
+
+	return (0);
+}
+
+int
+ksensor_op_kind(id_t id, sensor_ioctl_kind_t *kind)
+{
+	int ret;
+	ksensor_t *sensor;
+
+	if ((ret = ksensor_hold_by_id(id, &sensor)) != 0) {
+		return (ret);
+	}
+
+	ret = sensor->ksensor_ops->kso_kind(sensor->ksensor_arg, kind);
+	ksensor_release(sensor);
+
+	return (ret);
+}
+
+int
+ksensor_op_temperature(id_t id, sensor_ioctl_temperature_t *temp)
+{
+	int ret;
+	ksensor_t *sensor;
+
+	if ((ret = ksensor_hold_by_id(id, &sensor)) != 0) {
+		return (ret);
+	}
+
+	ret = sensor->ksensor_ops->kso_temp(sensor->ksensor_arg, temp);
+	ksensor_release(sensor);
+
+	return (ret);
+}
+
+void
+ksensor_unregister(dev_info_t *reg_dip)
+{
+	ksensor_t *sensor;
+
+	mutex_enter(&ksensor_g_mutex);
+	if (ksensor_cb_dip != reg_dip) {
+		dev_err(reg_dip, CE_PANIC, "asked to unregister illegal dip");
+	}
+
+	for (sensor = avl_first(&ksensor_avl); sensor != NULL; sensor =
+	    AVL_NEXT(&ksensor_avl, sensor)) {
+		mutex_enter(&sensor->ksensor_mutex);
+		sensor->ksensor_flags &= ~KSENSOR_F_NOTIFIED;
+		mutex_exit(&sensor->ksensor_mutex);
+	}
+
+	ksensor_cb_dip = NULL;
+	ksensor_cb_create = NULL;
+	ksensor_cb_remove = NULL;
+	mutex_exit(&ksensor_g_mutex);
+}
+
+int
+ksensor_register(dev_info_t *reg_dip, ksensor_create_f create,
+    ksensor_remove_f remove)
+{
+	ksensor_t *sensor;
+
+	mutex_enter(&ksensor_g_mutex);
+	if (ksensor_cb_dip != NULL) {
+		dev_err(reg_dip, CE_WARN, "kernel sensors are already "
+		    "registered");
+		mutex_exit(&ksensor_g_mutex);
+		return (EEXIST);
+	}
+
+	ksensor_cb_dip = reg_dip;
+	ksensor_cb_create = create;
+	ksensor_cb_remove = remove;
+
+	for (sensor = avl_first(&ksensor_avl); sensor != NULL; sensor =
+	    AVL_NEXT(&ksensor_avl, sensor)) {
+		mutex_enter(&sensor->ksensor_mutex);
+		ASSERT0(sensor->ksensor_flags & KSENSOR_F_NOTIFIED);
+
+		if (ksensor_cb_create(sensor->ksensor_id, sensor->ksensor_class,
+		    sensor->ksensor_name) == 0) {
+			sensor->ksensor_flags |= KSENSOR_F_NOTIFIED;
+		}
+
+		mutex_exit(&sensor->ksensor_mutex);
+	}
+
+	mutex_exit(&ksensor_g_mutex);
+
+	return (0);
+}
+
+int
+ksensor_kind_temperature(void *unused, sensor_ioctl_kind_t *k)
+{
+	k->sik_kind = SENSOR_KIND_TEMPERATURE;
+	return (0);
+}
+
+void
+ksensor_init(void)
+{
+	mutex_init(&ksensor_g_mutex, NULL, MUTEX_DRIVER, NULL);
+	list_create(&ksensor_dips, sizeof (ksensor_dip_t),
+	    offsetof(ksensor_dip_t, ksdip_link));
+	ksensor_ids = id_space_create("ksensor", 1, L_MAXMIN32);
+	avl_create(&ksensor_avl, ksensor_avl_compare, sizeof (ksensor_t),
+	    offsetof(ksensor_t, ksensor_id_avl));
+}
--- a/usr/src/uts/common/sys/ddi_impldefs.h	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/sys/ddi_impldefs.h	Sat Apr 04 21:20:18 2020 -0700
@@ -289,6 +289,12 @@
 	/* detach event data */
 	char	*devi_ev_path;
 	int	devi_ev_instance;
+
+	/*
+	 * Unbind callback data.
+	 */
+	kmutex_t	devi_unbind_lock;
+	list_t		devi_unbind_cbs;
 };
 
 #define	DEVI(dev_info_type)	((struct dev_info *)(dev_info_type))
--- a/usr/src/uts/common/sys/esunddi.h	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/sys/esunddi.h	Sat Apr 04 21:20:18 2020 -0700
@@ -22,6 +22,7 @@
  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  * Copyright (c) 2016 by Delphix. All rights reserved.
+ * Copyright 2020 Oxide Computer Company
  */
 
 #ifndef	_SYS_ESUNDDI_H
@@ -46,15 +47,15 @@
 
 int
 e_ddi_prop_create(dev_t dev, dev_info_t *dip, int flag,
-	char *name, caddr_t value, int length);
+    char *name, caddr_t value, int length);
 
 int
 e_ddi_prop_modify(dev_t dev, dev_info_t *dip, int flag,
-	char *name, caddr_t value, int length);
+    char *name, caddr_t value, int length);
 
 int
 e_ddi_prop_update_int(dev_t match_dev, dev_info_t *dip,
-	char *name, int data);
+    char *name, int data);
 
 int
 e_ddi_prop_update_int64(dev_t match_dev, dev_info_t *dip,
@@ -70,7 +71,7 @@
 
 int
 e_ddi_prop_update_string(dev_t match_dev, dev_info_t *dip,
-	char *name, char *data);
+    char *name, char *data);
 
 int
 e_ddi_prop_update_string_array(dev_t match_dev, dev_info_t *dip,
@@ -94,18 +95,18 @@
 
 int64_t
 e_ddi_getprop_int64(dev_t dev, vtype_t type, char *name,
-	int flags, int64_t defvalue);
+    int flags, int64_t defvalue);
 
 int
 e_ddi_getproplen(dev_t dev, vtype_t type, char *name, int flags, int *lengthp);
 
 int
 e_ddi_getlongprop(dev_t dev, vtype_t type, char *name, int flags,
-	caddr_t valuep, int *lengthp);
+    caddr_t valuep, int *lengthp);
 
 int
 e_ddi_getlongprop_buf(dev_t dev, vtype_t type, char *name, int flags,
-	caddr_t valuep, int *lengthp);
+    caddr_t valuep, int *lengthp);
 
 int
 e_ddi_parental_suspend_resume(dev_info_t *dip);
@@ -159,9 +160,8 @@
  */
 int
 umem_lockmemory(caddr_t addr, size_t size, int flags,
-		ddi_umem_cookie_t *cookie,
-		struct umem_callback_ops *ops_vector,
-		proc_t *procp);
+    ddi_umem_cookie_t *cookie, struct umem_callback_ops *ops_vector,
+    proc_t *procp);
 
 #define	DDI_UMEMLOCK_LONGTERM	0x04
 
@@ -249,15 +249,20 @@
  * Obsolete interfaces, no longer used, to be removed.
  * Retained only for driver compatibility.
  */
-void
-e_ddi_enter_driver_list(struct devnames *, int *);	/* obsolete */
+void e_ddi_enter_driver_list(struct devnames *, int *);		/* obsolete */
+
+int e_ddi_tryenter_driver_list(struct devnames *, int *);	/* obsolete */
+
+void e_ddi_exit_driver_list(struct devnames *, int);		/* obsolete */
 
-int
-e_ddi_tryenter_driver_list(struct devnames *, int *);	/* obsolete */
+typedef struct ddi_unbind_callback {
+	list_node_t ddiub_next;
+	void (*ddiub_cb)(void *, dev_info_t *);
+	void *ddiub_arg;
+} ddi_unbind_callback_t;
 
-void
-e_ddi_exit_driver_list(struct devnames *, int);		/* obsolete */
-
+extern void e_ddi_register_unbind_callback(dev_info_t *,
+    ddi_unbind_callback_t *);
 
 #endif	/* _KERNEL */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/sys/ksensor_impl.h	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,52 @@
+/*
+ * 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 _SYS_KSENSOR_IMPL_H
+#define	_SYS_KSENSOR_IMPL_H
+
+/*
+ * ksensor implementation glue.
+ */
+
+#include <sys/sensors.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Routine for the kernel to initalize the subsystem.
+ */
+extern void ksensor_init(void);
+
+/*
+ * Operations vectors.
+ */
+extern int ksensor_op_kind(id_t, sensor_ioctl_kind_t *);
+extern int ksensor_op_temperature(id_t, sensor_ioctl_temperature_t *);
+
+/*
+ * Registration callbacks.
+ */
+typedef int (*ksensor_create_f)(id_t, const char *, const char *);
+typedef void (*ksensor_remove_f)(id_t, const char *);
+extern int ksensor_register(dev_info_t *, ksensor_create_f, ksensor_remove_f);
+extern void ksensor_unregister(dev_info_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_KSENSOR_IMPL_H */
--- a/usr/src/uts/common/sys/sensors.h	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/common/sys/sensors.h	Sat Apr 04 21:20:18 2020 -0700
@@ -11,6 +11,7 @@
 
 /*
  * Copyright 2019, Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
  */
 
 #ifndef _SYS_SENSORS_H
@@ -71,9 +72,43 @@
 typedef struct sensor_ioctl_temperature {
 	uint32_t	sit_unit;
 	int32_t		sit_gran;
+	uint32_t	sit_prec;
+	uint32_t	sit_pad;
 	int64_t		sit_temp;
 } sensor_ioctl_temperature_t;
 
+#ifdef	_KERNEL
+typedef int (*ksensor_kind_f)(void *, sensor_ioctl_kind_t *);
+typedef int (*ksensor_temp_f)(void *, sensor_ioctl_temperature_t *);
+
+typedef struct {
+	ksensor_kind_f	kso_kind;
+	ksensor_temp_f	kso_temp;
+} ksensor_ops_t;
+
+extern int ksensor_kind_temperature(void *, sensor_ioctl_kind_t *);
+
+/*
+ * Create a sensor where the class and name is supplied.
+ */
+extern int ksensor_create(dev_info_t *, const ksensor_ops_t *, void *,
+    const char *, const char *, id_t *);
+
+/*
+ * Create a temperature sensor for a PCI device. If this is not a device-wide
+ * (e.g. per-function) sensor, this should not be used.
+ */
+extern int ksensor_create_temp_pcidev(dev_info_t *, const ksensor_ops_t *,
+    void *, const char *, id_t *);
+
+/*
+ * Remove a named or all sensors from this driver.
+ */
+#define	KSENSOR_ALL_IDS	INT_MIN
+extern int ksensor_remove(dev_info_t *, id_t);
+
+#endif
+
 #ifdef __cplusplus
 }
 #endif
--- a/usr/src/uts/intel/Makefile.intel	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/intel/Makefile.intel	Sat Apr 04 21:20:18 2020 -0700
@@ -765,6 +765,7 @@
 #
 #	Sensor related drivers
 #
+DRV_KMODS	+= ksensor ksensor_test
 DRV_KMODS	+= amdf17nbdf
 DRV_KMODS	+= coretemp
 DRV_KMODS	+= pchtemp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/ksensor/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,40 @@
+#
+# 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		= ksensor
+OBJECTS		= $(KSENSOR_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/ksensor
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY) $(CONFMOD)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/ksensor_test/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,40 @@
+#
+# 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		= ksensor_test
+OBJECTS		= $(KSENSOR_TEST_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/ksensor
+
+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
--- a/usr/src/uts/sparc/Makefile.sparc	Thu Jun 25 01:45:50 2020 +0000
+++ b/usr/src/uts/sparc/Makefile.sparc	Sat Apr 04 21:20:18 2020 -0700
@@ -531,3 +531,8 @@
 #
 $(LINTFLAGSUPPRESS)LINTFLAGS	+= -D_MACHDEP -I$(UTSBASE)/sun4 \
 				   -I$(UTSBASE)/sun4u -I$(UTSBASE)/sfmmu
+
+#
+#	Sensor related drivers
+#
+DRV_KMODS	+= ksensor ksensor_test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/sparc/ksensor/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,40 @@
+#
+# 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		= ksensor
+OBJECTS		= $(KSENSOR_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/ksensor
+
+include $(UTSBASE)/sparc/Makefile.sparc
+
+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)/sparc/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/sparc/ksensor_test/Makefile	Sat Apr 04 21:20:18 2020 -0700
@@ -0,0 +1,40 @@
+#
+# 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		= ksensor_test
+OBJECTS		= $(KSENSOR_TEST_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/ksensor
+
+include $(UTSBASE)/sparc/Makefile.sparc
+
+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)/sparc/Makefile.targ