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%x",
192 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