1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019, Joyent, Inc.
14  * Copyright 2020 Oxide Computer Company
15  */
16 
17 /*
18  * This file provides routines to interact with the kernel sensor framework.
19  * Currently, modules that require interacting with a kernel sensor need to
20  * build this file as part of the module. This takes care of all the work of
21  * setting up and creating the sensor, given a path to that sensor.
22  */
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <libnvpair.h>
31 #include <sys/sensors.h>
32 #include <sys/fm/protocol.h>
33 #include <fm/topo_mod.h>
34 
35 #define	TOPO_METH_TOPO_SENSOR_SCALAR		"topo_sensor_scalar_reading"
36 #define	TOPO_METH_TOPO_SENSOR_SCALAR_DESC	"Kernel Sensor Scalar Reading"
37 #define	TOPO_METH_TOPO_SENSOR_SCALAR_VERSION	0
38 
39 static int
topo_sensor_scalar_read(topo_mod_t * mod,tnode_t * node,topo_version_t vers,nvlist_t * in,nvlist_t ** out)40 topo_sensor_scalar_read(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
41     nvlist_t *in, nvlist_t **out)
42 {
43 	int fd = -1, ret;
44 	nvlist_t *args, *nvl;
45 	char *path;
46 	sensor_ioctl_scalar_t scalar;
47 	double value;
48 
49 	if (vers != TOPO_METH_TOPO_SENSOR_SCALAR_VERSION) {
50 		return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
51 	}
52 
53 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
54 	    nvlist_lookup_string(args, TOPO_IO_DEV_PATH, &path) != 0) {
55 		topo_mod_dprintf(mod, "failed to lookup sensor path from "
56 		    "property %s", TOPO_IO_DEV_PATH);
57 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
58 	}
59 
60 	if ((fd = open(path, O_RDONLY)) < 0) {
61 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
62 		    path, strerror(errno));
63 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
64 	}
65 
66 	(void) memset(&scalar, '\0', sizeof (scalar));
67 	if (ioctl(fd, SENSOR_IOCTL_SCALAR, &scalar) != 0) {
68 		topo_mod_dprintf(mod, "failed to read sensor %s: %s", path,
69 		    strerror(errno));
70 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
71 		goto out;
72 	}
73 
74 	/*
75 	 * Check to see if we need to change the value to get it into an
76 	 * accurate reading. Positive granularities indicate that the sensor
77 	 * reading is in a fractional number of units and that each unit
78 	 * contains scalar.sis_gran steps. A negative number means that the
79 	 * sensor reading represents scalar.sis_gran units.
80 	 */
81 	value = (double)scalar.sis_value;
82 	if (scalar.sis_gran > 1) {
83 		value /= (double)scalar.sis_gran;
84 	} else if (scalar.sis_gran < -1) {
85 		value *= (double)labs(scalar.sis_gran);
86 	}
87 
88 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
89 		topo_mod_dprintf(mod, "failed to allocate output nvl");
90 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
91 		goto out;
92 	}
93 
94 	if (nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_SENSOR_READING) !=
95 	    0 ||
96 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_DOUBLE) != 0 ||
97 	    nvlist_add_double(nvl, TOPO_PROP_VAL_VAL, value) != 0) {
98 		topo_mod_dprintf(mod, "failed to add members to output "
99 		    "sensor nvlist");
100 		nvlist_free(nvl);
101 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
102 		goto out;
103 	}
104 
105 	*out = nvl;
106 	ret = 0;
107 out:
108 	if (fd >= 0) {
109 		(void) close(fd);
110 	}
111 	return (ret);
112 }
113 
114 static const topo_method_t topo_sensor_scalar_fac_methods[] = {
115 	{ TOPO_METH_TOPO_SENSOR_SCALAR, TOPO_METH_TOPO_SENSOR_SCALAR_DESC,
116 		TOPO_METH_TOPO_SENSOR_SCALAR_VERSION, TOPO_STABILITY_INTERNAL,
117 		topo_sensor_scalar_read },
118 	{ NULL }
119 };
120 
121 static topo_sensor_unit_t
topo_sensor_units(const sensor_ioctl_scalar_t * scalar)122 topo_sensor_units(const sensor_ioctl_scalar_t *scalar)
123 {
124 	switch (scalar->sis_unit) {
125 	case SENSOR_UNIT_CELSIUS:
126 		return (TOPO_SENSOR_UNITS_DEGREES_C);
127 	case SENSOR_UNIT_FAHRENHEIT:
128 		return (TOPO_SENSOR_UNITS_DEGREES_F);
129 	case SENSOR_UNIT_KELVIN:
130 		return (TOPO_SENSOR_UNITS_DEGREES_K);
131 	case SENSOR_UNIT_VOLTS:
132 		return (TOPO_SENSOR_UNITS_VOLTS);
133 	case SENSOR_UNIT_AMPS:
134 		return (TOPO_SENSOR_UNITS_AMPS);
135 	default:
136 		return (TOPO_SENSOR_UNITS_UNSPECIFIED);
137 	}
138 }
139 
140 int
topo_sensor_create_scalar_sensor(topo_mod_t * mod,tnode_t * pnode,const char * path,const char * fname)141 topo_sensor_create_scalar_sensor(topo_mod_t *mod, tnode_t *pnode,
142     const char *path, const char *fname)
143 {
144 	int fd, ret, err;
145 	sensor_ioctl_kind_t sik;
146 	sensor_ioctl_scalar_t scalar;
147 	uint32_t topo_type;
148 	tnode_t *fnode = NULL;
149 	topo_pgroup_info_t pgi;
150 	nvlist_t *reader_arg = NULL;
151 
152 	topo_mod_dprintf(mod, "attempting to create sensor for %s at %s",
153 	    topo_node_name(pnode), path);
154 
155 	(void) memset(&sik, '\0', sizeof (sik));
156 	(void) memset(&scalar, '\0', sizeof (scalar));
157 
158 	if ((fd = open(path, O_RDONLY)) < 0) {
159 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
160 		    path, strerror(errno));
161 
162 		/*
163 		 * We always try to create sensors; however, they may not exist
164 		 * or be supported on the system in question.  Therefore ENOENT
165 		 * is totally acceptable.
166 		 */
167 		if (errno == ENOENT) {
168 			return (0);
169 		}
170 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
171 	}
172 
173 	if (ioctl(fd, SENSOR_IOCTL_KIND, &sik) != 0) {
174 		topo_mod_dprintf(mod, "failed to verify sensor kind for sensor "
175 		    "%s: %s", path, strerror(errno));
176 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
177 		goto out;
178 	}
179 
180 	switch (sik.sik_kind) {
181 	case SENSOR_KIND_TEMPERATURE:
182 		topo_type = TOPO_SENSOR_TYPE_TEMP;
183 		break;
184 	case SENSOR_KIND_VOLTAGE:
185 		topo_type = TOPO_SENSOR_TYPE_VOLTAGE;
186 		break;
187 	case SENSOR_KIND_CURRENT:
188 		topo_type = TOPO_SENSOR_TYPE_CURRENT;
189 		break;
190 	default:
191 		topo_mod_dprintf(mod, "unknown sensor kind for %s, found 0x%"
192 		    PRIx64, path, sik.sik_kind);
193 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
194 		goto out;
195 
196 	}
197 
198 	if (ioctl(fd, SENSOR_IOCTL_SCALAR, &scalar) != 0) {
199 		topo_mod_dprintf(mod, "failed to read scalar sensor %s: %s",
200 		    path, strerror(errno));
201 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
202 		goto out;
203 	}
204 
205 	(void) close(fd);
206 	fd = -1;
207 
208 	if ((fnode = topo_node_facbind(mod, pnode, fname,
209 	    TOPO_FAC_TYPE_SENSOR)) == NULL) {
210 		topo_mod_dprintf(mod, "failed to bind sensor facility "
211 		    "node to %s: %d", path, topo_mod_errno(mod));
212 		ret = -1;
213 		goto out;
214 	}
215 
216 	pgi.tpi_name = TOPO_PGROUP_FACILITY;
217 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
218 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
219 	pgi.tpi_version = 1;
220 
221 	if (topo_pgroup_create(fnode, &pgi, &err) != 0) {
222 		topo_mod_dprintf(mod, "failed to create facility pgroup: %s",
223 		    topo_strerror(err));
224 		ret = topo_mod_seterrno(mod, err);
225 		goto out;
226 	}
227 
228 	if (topo_prop_set_string(fnode, TOPO_PGROUP_FACILITY,
229 	    TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE,
230 	    TOPO_SENSOR_CLASS_THRESHOLD, &err) != 0 ||
231 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
232 	    TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, topo_type, &err) != 0 ||
233 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
234 	    TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, topo_sensor_units(&scalar),
235 	    &err) != 0) {
236 		topo_mod_dprintf(mod, "failed to set properties for sensor "
237 		    "%s: %s", path, topo_strerror(err));
238 		ret = topo_mod_seterrno(mod, err);
239 		goto out;
240 
241 	}
242 
243 	if (topo_method_register(mod, fnode, topo_sensor_scalar_fac_methods) <
244 	    0) {
245 		topo_mod_dprintf(mod, "failed to register reading methods on "
246 		    "%s", path);
247 		ret = -1;
248 		goto out;
249 	}
250 
251 	if (topo_mod_nvalloc(mod, &reader_arg, NV_UNIQUE_NAME) != 0 ||
252 	    nvlist_add_string(reader_arg, TOPO_IO_DEV_PATH, path) != 0) {
253 		topo_mod_dprintf(mod, "Failed to set up reader argument nvl");
254 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
255 		goto out;
256 	}
257 
258 	if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY,
259 	    TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, TOPO_METH_TOPO_SENSOR_SCALAR,
260 	    reader_arg, &err) != 0) {
261 		topo_mod_dprintf(mod, "failed to set argument for sensor %s: "
262 		    "%s", path, topo_strerror(err));
263 		ret = topo_mod_seterrno(mod, err);
264 		goto out;
265 	}
266 
267 	topo_mod_dprintf(mod, "created sensor at %s", path);
268 
269 	nvlist_free(reader_arg);
270 	return (0);
271 out:
272 	if (fd >= 0) {
273 		(void) close(fd);
274 	}
275 
276 	topo_node_unbind(fnode);
277 	nvlist_free(reader_arg);
278 	return (ret);
279 }
280