1f2dbfd32SRobert Mustacchi /*
2f2dbfd32SRobert Mustacchi  * This file and its contents are supplied under the terms of the
3f2dbfd32SRobert Mustacchi  * Common Development and Distribution License ("CDDL"), version 1.0.
4f2dbfd32SRobert Mustacchi  * You may only use this file in accordance with the terms of version
5f2dbfd32SRobert Mustacchi  * 1.0 of the CDDL.
6f2dbfd32SRobert Mustacchi  *
7f2dbfd32SRobert Mustacchi  * A full copy of the text of the CDDL should have accompanied this
8f2dbfd32SRobert Mustacchi  * source.  A copy of the CDDL is also available via the Internet at
9f2dbfd32SRobert Mustacchi  * http://www.illumos.org/license/CDDL.
10f2dbfd32SRobert Mustacchi  */
11f2dbfd32SRobert Mustacchi 
12f2dbfd32SRobert Mustacchi /*
13f2dbfd32SRobert Mustacchi  * Copyright 2019, Joyent, Inc.
141045e13aSRobert Mustacchi  * Copyright 2020 Oxide Computer Company
15f2dbfd32SRobert Mustacchi  */
16f2dbfd32SRobert Mustacchi 
17f2dbfd32SRobert Mustacchi /*
18f2dbfd32SRobert Mustacchi  * This file provides routines to interact with the kernel sensor framework.
19f2dbfd32SRobert Mustacchi  * Currently, modules that require interacting with a kernel sensor need to
20f2dbfd32SRobert Mustacchi  * build this file as part of the module. This takes care of all the work of
211045e13aSRobert Mustacchi  * setting up and creating the sensor, given a path to that sensor.
22f2dbfd32SRobert Mustacchi  */
23f2dbfd32SRobert Mustacchi 
24f2dbfd32SRobert Mustacchi #include <sys/types.h>
25f2dbfd32SRobert Mustacchi #include <sys/stat.h>
26f2dbfd32SRobert Mustacchi #include <fcntl.h>
27f2dbfd32SRobert Mustacchi #include <stdio.h>
28f2dbfd32SRobert Mustacchi #include <string.h>
29f2dbfd32SRobert Mustacchi #include <unistd.h>
30f2dbfd32SRobert Mustacchi #include <libnvpair.h>
31f2dbfd32SRobert Mustacchi #include <sys/sensors.h>
32f2dbfd32SRobert Mustacchi #include <sys/fm/protocol.h>
33f2dbfd32SRobert Mustacchi #include <fm/topo_mod.h>
34f2dbfd32SRobert Mustacchi 
351045e13aSRobert Mustacchi #define	TOPO_METH_TOPO_SENSOR_SCALAR		"topo_sensor_scalar_reading"
361045e13aSRobert Mustacchi #define	TOPO_METH_TOPO_SENSOR_SCALAR_DESC	"Kernel Sensor Scalar Reading"
371045e13aSRobert Mustacchi #define	TOPO_METH_TOPO_SENSOR_SCALAR_VERSION	0
38f2dbfd32SRobert Mustacchi 
39f2dbfd32SRobert Mustacchi static int
topo_sensor_scalar_read(topo_mod_t * mod,tnode_t * node,topo_version_t vers,nvlist_t * in,nvlist_t ** out)401045e13aSRobert Mustacchi topo_sensor_scalar_read(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
41f2dbfd32SRobert Mustacchi     nvlist_t *in, nvlist_t **out)
42f2dbfd32SRobert Mustacchi {
43f2dbfd32SRobert Mustacchi 	int fd = -1, ret;
44f2dbfd32SRobert Mustacchi 	nvlist_t *args, *nvl;
45f2dbfd32SRobert Mustacchi 	char *path;
461045e13aSRobert Mustacchi 	sensor_ioctl_scalar_t scalar;
471045e13aSRobert Mustacchi 	double value;
48f2dbfd32SRobert Mustacchi 
491045e13aSRobert Mustacchi 	if (vers != TOPO_METH_TOPO_SENSOR_SCALAR_VERSION) {
50f2dbfd32SRobert Mustacchi 		return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
51f2dbfd32SRobert Mustacchi 	}
52f2dbfd32SRobert Mustacchi 
53f2dbfd32SRobert Mustacchi 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
54f2dbfd32SRobert Mustacchi 	    nvlist_lookup_string(args, TOPO_IO_DEV_PATH, &path) != 0) {
55f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to lookup sensor path from "
56f2dbfd32SRobert Mustacchi 		    "property %s", TOPO_IO_DEV_PATH);
57f2dbfd32SRobert Mustacchi 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
58f2dbfd32SRobert Mustacchi 	}
59f2dbfd32SRobert Mustacchi 
60f2dbfd32SRobert Mustacchi 	if ((fd = open(path, O_RDONLY)) < 0) {
61f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
62f2dbfd32SRobert Mustacchi 		    path, strerror(errno));
63f2dbfd32SRobert Mustacchi 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
64f2dbfd32SRobert Mustacchi 	}
65f2dbfd32SRobert Mustacchi 
661045e13aSRobert Mustacchi 	(void) memset(&scalar, '\0', sizeof (scalar));
671045e13aSRobert Mustacchi 	if (ioctl(fd, SENSOR_IOCTL_SCALAR, &scalar) != 0) {
681045e13aSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to read sensor %s: %s", path,
691045e13aSRobert Mustacchi 		    strerror(errno));
70f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
71f2dbfd32SRobert Mustacchi 		goto out;
72f2dbfd32SRobert Mustacchi 	}
73f2dbfd32SRobert Mustacchi 
74f2dbfd32SRobert Mustacchi 	/*
75f2dbfd32SRobert Mustacchi 	 * Check to see if we need to change the value to get it into an
761045e13aSRobert Mustacchi 	 * accurate reading. Positive granularities indicate that the sensor
771045e13aSRobert Mustacchi 	 * reading is in a fractional number of units and that each unit
781045e13aSRobert Mustacchi 	 * contains scalar.sis_gran steps. A negative number means that the
791045e13aSRobert Mustacchi 	 * sensor reading represents scalar.sis_gran units.
80f2dbfd32SRobert Mustacchi 	 */
811045e13aSRobert Mustacchi 	value = (double)scalar.sis_value;
821045e13aSRobert Mustacchi 	if (scalar.sis_gran > 1) {
831045e13aSRobert Mustacchi 		value /= (double)scalar.sis_gran;
841045e13aSRobert Mustacchi 	} else if (scalar.sis_gran < -1) {
851045e13aSRobert Mustacchi 		value *= (double)labs(scalar.sis_gran);
86f2dbfd32SRobert Mustacchi 	}
87f2dbfd32SRobert Mustacchi 
88f2dbfd32SRobert Mustacchi 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
891045e13aSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to allocate output nvl");
90f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
91f2dbfd32SRobert Mustacchi 		goto out;
92f2dbfd32SRobert Mustacchi 	}
93f2dbfd32SRobert Mustacchi 
94f2dbfd32SRobert Mustacchi 	if (nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_SENSOR_READING) !=
95f2dbfd32SRobert Mustacchi 	    0 ||
96f2dbfd32SRobert Mustacchi 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_DOUBLE) != 0 ||
971045e13aSRobert Mustacchi 	    nvlist_add_double(nvl, TOPO_PROP_VAL_VAL, value) != 0) {
98f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to add members to output "
991045e13aSRobert Mustacchi 		    "sensor nvlist");
100f2dbfd32SRobert Mustacchi 		nvlist_free(nvl);
101f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
102f2dbfd32SRobert Mustacchi 		goto out;
103f2dbfd32SRobert Mustacchi 	}
104f2dbfd32SRobert Mustacchi 
105f2dbfd32SRobert Mustacchi 	*out = nvl;
106f2dbfd32SRobert Mustacchi 	ret = 0;
107f2dbfd32SRobert Mustacchi out:
108f2dbfd32SRobert Mustacchi 	if (fd >= 0) {
109f2dbfd32SRobert Mustacchi 		(void) close(fd);
110f2dbfd32SRobert Mustacchi 	}
111f2dbfd32SRobert Mustacchi 	return (ret);
112f2dbfd32SRobert Mustacchi }
113f2dbfd32SRobert Mustacchi 
1141045e13aSRobert Mustacchi static const topo_method_t topo_sensor_scalar_fac_methods[] = {
1151045e13aSRobert Mustacchi 	{ TOPO_METH_TOPO_SENSOR_SCALAR, TOPO_METH_TOPO_SENSOR_SCALAR_DESC,
1161045e13aSRobert Mustacchi 		TOPO_METH_TOPO_SENSOR_SCALAR_VERSION, TOPO_STABILITY_INTERNAL,
1171045e13aSRobert Mustacchi 		topo_sensor_scalar_read },
118f2dbfd32SRobert Mustacchi 	{ NULL }
119f2dbfd32SRobert Mustacchi };
120f2dbfd32SRobert Mustacchi 
121f2dbfd32SRobert Mustacchi static topo_sensor_unit_t
topo_sensor_units(const sensor_ioctl_scalar_t * scalar)1221045e13aSRobert Mustacchi topo_sensor_units(const sensor_ioctl_scalar_t *scalar)
123f2dbfd32SRobert Mustacchi {
1241045e13aSRobert Mustacchi 	switch (scalar->sis_unit) {
125f2dbfd32SRobert Mustacchi 	case SENSOR_UNIT_CELSIUS:
126f2dbfd32SRobert Mustacchi 		return (TOPO_SENSOR_UNITS_DEGREES_C);
127f2dbfd32SRobert Mustacchi 	case SENSOR_UNIT_FAHRENHEIT:
128f2dbfd32SRobert Mustacchi 		return (TOPO_SENSOR_UNITS_DEGREES_F);
129f2dbfd32SRobert Mustacchi 	case SENSOR_UNIT_KELVIN:
130f2dbfd32SRobert Mustacchi 		return (TOPO_SENSOR_UNITS_DEGREES_K);
1311045e13aSRobert Mustacchi 	case SENSOR_UNIT_VOLTS:
1321045e13aSRobert Mustacchi 		return (TOPO_SENSOR_UNITS_VOLTS);
1331045e13aSRobert Mustacchi 	case SENSOR_UNIT_AMPS:
1341045e13aSRobert Mustacchi 		return (TOPO_SENSOR_UNITS_AMPS);
135f2dbfd32SRobert Mustacchi 	default:
136f2dbfd32SRobert Mustacchi 		return (TOPO_SENSOR_UNITS_UNSPECIFIED);
137f2dbfd32SRobert Mustacchi 	}
138f2dbfd32SRobert Mustacchi }
139f2dbfd32SRobert Mustacchi 
140f2dbfd32SRobert Mustacchi int
topo_sensor_create_scalar_sensor(topo_mod_t * mod,tnode_t * pnode,const char * path,const char * fname)1411045e13aSRobert Mustacchi topo_sensor_create_scalar_sensor(topo_mod_t *mod, tnode_t *pnode,
142f2dbfd32SRobert Mustacchi     const char *path, const char *fname)
143f2dbfd32SRobert Mustacchi {
144f2dbfd32SRobert Mustacchi 	int fd, ret, err;
145f2dbfd32SRobert Mustacchi 	sensor_ioctl_kind_t sik;
1461045e13aSRobert Mustacchi 	sensor_ioctl_scalar_t scalar;
1471045e13aSRobert Mustacchi 	uint32_t topo_type;
148f2dbfd32SRobert Mustacchi 	tnode_t *fnode = NULL;
149f2dbfd32SRobert Mustacchi 	topo_pgroup_info_t pgi;
150f2dbfd32SRobert Mustacchi 	nvlist_t *reader_arg = NULL;
151f2dbfd32SRobert Mustacchi 
152f2dbfd32SRobert Mustacchi 	topo_mod_dprintf(mod, "attempting to create sensor for %s at %s",
153f2dbfd32SRobert Mustacchi 	    topo_node_name(pnode), path);
154f2dbfd32SRobert Mustacchi 
155f2dbfd32SRobert Mustacchi 	(void) memset(&sik, '\0', sizeof (sik));
1561045e13aSRobert Mustacchi 	(void) memset(&scalar, '\0', sizeof (scalar));
157f2dbfd32SRobert Mustacchi 
158f2dbfd32SRobert Mustacchi 	if ((fd = open(path, O_RDONLY)) < 0) {
159f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
160f2dbfd32SRobert Mustacchi 		    path, strerror(errno));
161f2dbfd32SRobert Mustacchi 
162f2dbfd32SRobert Mustacchi 		/*
1631045e13aSRobert Mustacchi 		 * We always try to create sensors; however, they may not exist
1641045e13aSRobert Mustacchi 		 * or be supported on the system in question.  Therefore ENOENT
1651045e13aSRobert Mustacchi 		 * is totally acceptable.
166f2dbfd32SRobert Mustacchi 		 */
167f2dbfd32SRobert Mustacchi 		if (errno == ENOENT) {
168f2dbfd32SRobert Mustacchi 			return (0);
169f2dbfd32SRobert Mustacchi 		}
170f2dbfd32SRobert Mustacchi 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
171f2dbfd32SRobert Mustacchi 	}
172f2dbfd32SRobert Mustacchi 
1731045e13aSRobert Mustacchi 	if (ioctl(fd, SENSOR_IOCTL_KIND, &sik) != 0) {
174f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to verify sensor kind for sensor "
175f2dbfd32SRobert Mustacchi 		    "%s: %s", path, strerror(errno));
176f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
177f2dbfd32SRobert Mustacchi 		goto out;
178f2dbfd32SRobert Mustacchi 	}
179f2dbfd32SRobert Mustacchi 
1801045e13aSRobert Mustacchi 	switch (sik.sik_kind) {
1811045e13aSRobert Mustacchi 	case SENSOR_KIND_TEMPERATURE:
1821045e13aSRobert Mustacchi 		topo_type = TOPO_SENSOR_TYPE_TEMP;
1831045e13aSRobert Mustacchi 		break;
1841045e13aSRobert Mustacchi 	case SENSOR_KIND_VOLTAGE:
1851045e13aSRobert Mustacchi 		topo_type = TOPO_SENSOR_TYPE_VOLTAGE;
1861045e13aSRobert Mustacchi 		break;
1871045e13aSRobert Mustacchi 	case SENSOR_KIND_CURRENT:
1881045e13aSRobert Mustacchi 		topo_type = TOPO_SENSOR_TYPE_CURRENT;
1891045e13aSRobert Mustacchi 		break;
1901045e13aSRobert Mustacchi 	default:
191*6597d6fcSRobert Mustacchi 		topo_mod_dprintf(mod, "unknown sensor kind for %s, found 0x%"
192*6597d6fcSRobert Mustacchi 		    PRIx64, path, sik.sik_kind);
193f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
194f2dbfd32SRobert Mustacchi 		goto out;
1951045e13aSRobert Mustacchi 
196f2dbfd32SRobert Mustacchi 	}
197f2dbfd32SRobert Mustacchi 
1981045e13aSRobert Mustacchi 	if (ioctl(fd, SENSOR_IOCTL_SCALAR, &scalar) != 0) {
1991045e13aSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to read scalar sensor %s: %s",
2001045e13aSRobert Mustacchi 		    path, strerror(errno));
201f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
202f2dbfd32SRobert Mustacchi 		goto out;
203f2dbfd32SRobert Mustacchi 	}
204f2dbfd32SRobert Mustacchi 
205f2dbfd32SRobert Mustacchi 	(void) close(fd);
206f2dbfd32SRobert Mustacchi 	fd = -1;
207f2dbfd32SRobert Mustacchi 
208f2dbfd32SRobert Mustacchi 	if ((fnode = topo_node_facbind(mod, pnode, fname,
209f2dbfd32SRobert Mustacchi 	    TOPO_FAC_TYPE_SENSOR)) == NULL) {
2101045e13aSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to bind sensor facility "
211f2dbfd32SRobert Mustacchi 		    "node to %s: %d", path, topo_mod_errno(mod));
212f2dbfd32SRobert Mustacchi 		ret = -1;
213f2dbfd32SRobert Mustacchi 		goto out;
214f2dbfd32SRobert Mustacchi 	}
215f2dbfd32SRobert Mustacchi 
216f2dbfd32SRobert Mustacchi 	pgi.tpi_name = TOPO_PGROUP_FACILITY;
217f2dbfd32SRobert Mustacchi 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
218f2dbfd32SRobert Mustacchi 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
219f2dbfd32SRobert Mustacchi 	pgi.tpi_version = 1;
220f2dbfd32SRobert Mustacchi 
221f2dbfd32SRobert Mustacchi 	if (topo_pgroup_create(fnode, &pgi, &err) != 0) {
222f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to create facility pgroup: %s",
223f2dbfd32SRobert Mustacchi 		    topo_strerror(err));
224f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, err);
225f2dbfd32SRobert Mustacchi 		goto out;
226f2dbfd32SRobert Mustacchi 	}
227f2dbfd32SRobert Mustacchi 
228f2dbfd32SRobert Mustacchi 	if (topo_prop_set_string(fnode, TOPO_PGROUP_FACILITY,
229f2dbfd32SRobert Mustacchi 	    TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE,
230f2dbfd32SRobert Mustacchi 	    TOPO_SENSOR_CLASS_THRESHOLD, &err) != 0 ||
231f2dbfd32SRobert Mustacchi 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
2321045e13aSRobert Mustacchi 	    TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, topo_type, &err) != 0 ||
233f2dbfd32SRobert Mustacchi 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
2341045e13aSRobert Mustacchi 	    TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, topo_sensor_units(&scalar),
235f2dbfd32SRobert Mustacchi 	    &err) != 0) {
236f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set properties for sensor "
237f2dbfd32SRobert Mustacchi 		    "%s: %s", path, topo_strerror(err));
238f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, err);
239f2dbfd32SRobert Mustacchi 		goto out;
240f2dbfd32SRobert Mustacchi 
241f2dbfd32SRobert Mustacchi 	}
242f2dbfd32SRobert Mustacchi 
2431045e13aSRobert Mustacchi 	if (topo_method_register(mod, fnode, topo_sensor_scalar_fac_methods) <
244f2dbfd32SRobert Mustacchi 	    0) {
245f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to register reading methods on "
246f2dbfd32SRobert Mustacchi 		    "%s", path);
247f2dbfd32SRobert Mustacchi 		ret = -1;
248f2dbfd32SRobert Mustacchi 		goto out;
249f2dbfd32SRobert Mustacchi 	}
250f2dbfd32SRobert Mustacchi 
251f2dbfd32SRobert Mustacchi 	if (topo_mod_nvalloc(mod, &reader_arg, NV_UNIQUE_NAME) != 0 ||
252f2dbfd32SRobert Mustacchi 	    nvlist_add_string(reader_arg, TOPO_IO_DEV_PATH, path) != 0) {
253f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "Failed to set up reader argument nvl");
254f2dbfd32SRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
255f2dbfd32SRobert Mustacchi 		goto out;
256f2dbfd32SRobert Mustacchi 	}
257f2dbfd32SRobert Mustacchi 
258f2dbfd32SRobert Mustacchi 	if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY,
2591045e13aSRobert Mustacchi 	    TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, TOPO_METH_TOPO_SENSOR_SCALAR,
260f2dbfd32SRobert Mustacchi 	    reader_arg, &err) != 0) {
261f2dbfd32SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set argument for sensor %s: "
262f2dbfd32SRobert Mustacchi 		    "%s", path, topo_strerror(err));
26358d4b16fSRobert Mustacchi 		ret = topo_mod_seterrno(mod, err);
264f2dbfd32SRobert Mustacchi 		goto out;
265f2dbfd32SRobert Mustacchi 	}
266f2dbfd32SRobert Mustacchi 
2671045e13aSRobert Mustacchi 	topo_mod_dprintf(mod, "created sensor at %s", path);
2681045e13aSRobert Mustacchi 
269f2dbfd32SRobert Mustacchi 	nvlist_free(reader_arg);
270f2dbfd32SRobert Mustacchi 	return (0);
271f2dbfd32SRobert Mustacchi out:
272f2dbfd32SRobert Mustacchi 	if (fd >= 0) {
273f2dbfd32SRobert Mustacchi 		(void) close(fd);
274f2dbfd32SRobert Mustacchi 	}
275f2dbfd32SRobert Mustacchi 
276f2dbfd32SRobert Mustacchi 	topo_node_unbind(fnode);
277f2dbfd32SRobert Mustacchi 	nvlist_free(reader_arg);
278f2dbfd32SRobert Mustacchi 	return (ret);
279f2dbfd32SRobert Mustacchi }
280