xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/shared/topo_sensor.c (revision f52943a93040563107b95bccb9db87d9971ef47d)
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  */
15 
16 /*
17  * This file provides routines to interact with the kernel sensor framework.
18  * Currently, modules that require interacting with a kernel sensor need to
19  * build this file as part of the module. This takes care of all the work of
20  * setting up and creating the temperature sensor, given a path to that sensor.
21  */
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <libnvpair.h>
30 #include <sys/sensors.h>
31 #include <sys/fm/protocol.h>
32 #include <fm/topo_mod.h>
33 
34 #define	TOPO_METH_TOPO_SENSOR_TEMP		"topo_sensor_temp_reading"
35 #define	TOPO_METH_TOPO_SENSOR_TEMP_DESC		"Kernel Temperature Reading"
36 #define	TOPO_METH_TOPO_SENSOR_TEMP_VERSION	0
37 
38 static int
39 topo_sensor_temp_read(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
40     nvlist_t *in, nvlist_t **out)
41 {
42 	int fd = -1, ret;
43 	nvlist_t *args, *nvl;
44 	char *path;
45 	sensor_ioctl_temperature_t temp;
46 	double degrees;
47 
48 	if (vers != TOPO_METH_TOPO_SENSOR_TEMP_VERSION) {
49 		return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
50 	}
51 
52 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
53 	    nvlist_lookup_string(args, TOPO_IO_DEV_PATH, &path) != 0) {
54 		topo_mod_dprintf(mod, "failed to lookup sensor path from "
55 		    "property %s", TOPO_IO_DEV_PATH);
56 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
57 	}
58 
59 	if ((fd = open(path, O_RDONLY)) < 0) {
60 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
61 		    path, strerror(errno));
62 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
63 	}
64 
65 	(void) memset(&temp, '\0', sizeof (temp));
66 	if (ioctl(fd, SENSOR_IOCTL_TEMPERATURE, &temp) != 0) {
67 		topo_mod_dprintf(mod, "failed to read temperature sensor "
68 		    "%s: %s", path, strerror(errno));
69 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
70 		goto out;
71 	}
72 
73 	/*
74 	 * Check to see if we need to change the value to get it into an
75 	 * accurate reading. Positive values indicate that the temperature
76 	 * reading is in a fractional number of degrees and that each degree
77 	 * contains temp.sit_gran steps. A negative number means that the
78 	 * temperature reading represents temp.sit_gran degrees.
79 	 */
80 	degrees = (double)temp.sit_temp;
81 	if (temp.sit_gran > 1) {
82 		degrees /= (double)temp.sit_gran;
83 	} else if (temp.sit_gran < -1) {
84 		degrees *= (double)labs(temp.sit_gran);
85 	}
86 
87 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
88 		topo_mod_dprintf(mod, "failed to allocate output temperature "
89 		    "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, degrees) != 0) {
98 		topo_mod_dprintf(mod, "failed to add members to output "
99 		    "temperature 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_temp_fac_methods[] = {
115 	{ TOPO_METH_TOPO_SENSOR_TEMP, TOPO_METH_TOPO_SENSOR_TEMP_DESC,
116 		TOPO_METH_TOPO_SENSOR_TEMP_VERSION, TOPO_STABILITY_INTERNAL,
117 		topo_sensor_temp_read },
118 	{ NULL }
119 };
120 
121 static topo_sensor_unit_t
122 topo_sensor_units(const sensor_ioctl_temperature_t *temp)
123 {
124 	switch (temp->sit_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 	default:
132 		return (TOPO_SENSOR_UNITS_UNSPECIFIED);
133 	}
134 }
135 
136 int
137 topo_sensor_create_temp_sensor(topo_mod_t *mod, tnode_t *pnode,
138     const char *path, const char *fname)
139 {
140 	int fd, ret, err;
141 	sensor_ioctl_kind_t sik;
142 	sensor_ioctl_temperature_t temp;
143 	tnode_t *fnode = NULL;
144 	topo_pgroup_info_t pgi;
145 	nvlist_t *reader_arg = NULL;
146 
147 	topo_mod_dprintf(mod, "attempting to create sensor for %s at %s",
148 	    topo_node_name(pnode), path);
149 
150 	(void) memset(&sik, '\0', sizeof (sik));
151 	(void) memset(&temp, '\0', sizeof (temp));
152 
153 	if ((fd = open(path, O_RDONLY)) < 0) {
154 		topo_mod_dprintf(mod, "failed to open sensor path %s: %s",
155 		    path, strerror(errno));
156 
157 		/*
158 		 * We always try to create temperature sensors; however, they
159 		 * may not exist or be supported on the system in question.
160 		 * Therefore ENOENT is totally acceptable.
161 		 */
162 		if (errno == ENOENT) {
163 			return (0);
164 		}
165 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
166 	}
167 
168 	if (ioctl(fd, SENSOR_IOCTL_TYPE, &sik) != 0) {
169 		topo_mod_dprintf(mod, "failed to verify sensor kind for sensor "
170 		    "%s: %s", path, strerror(errno));
171 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
172 		goto out;
173 	}
174 
175 	if (sik.sik_kind != SENSOR_KIND_TEMPERATURE) {
176 		topo_mod_dprintf(mod, "sensor kind for %s is not temperature, "
177 		    "found 0x%x", path, sik.sik_kind);
178 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
179 		goto out;
180 	}
181 
182 	if (ioctl(fd, SENSOR_IOCTL_TEMPERATURE, &temp) != 0) {
183 		topo_mod_dprintf(mod, "failed to read temperature sensor "
184 		    "%s: %s", path, strerror(errno));
185 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
186 		goto out;
187 	}
188 
189 	(void) close(fd);
190 	fd = -1;
191 
192 	if ((fnode = topo_node_facbind(mod, pnode, fname,
193 	    TOPO_FAC_TYPE_SENSOR)) == NULL) {
194 		topo_mod_dprintf(mod, "failed to bind temperature facility "
195 		    "node to %s: %d", path, topo_mod_errno(mod));
196 		ret = -1;
197 		goto out;
198 	}
199 
200 	pgi.tpi_name = TOPO_PGROUP_FACILITY;
201 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
202 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
203 	pgi.tpi_version = 1;
204 
205 	if (topo_pgroup_create(fnode, &pgi, &err) != 0) {
206 		topo_mod_dprintf(mod, "failed to create facility pgroup: %s",
207 		    topo_strerror(err));
208 		ret = topo_mod_seterrno(mod, err);
209 		goto out;
210 	}
211 
212 	if (topo_prop_set_string(fnode, TOPO_PGROUP_FACILITY,
213 	    TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE,
214 	    TOPO_SENSOR_CLASS_THRESHOLD, &err) != 0 ||
215 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
216 	    TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, TOPO_SENSOR_TYPE_TEMP,
217 	    &err) != 0 ||
218 	    topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
219 	    TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, topo_sensor_units(&temp),
220 	    &err) != 0) {
221 		topo_mod_dprintf(mod, "failed to set properties for sensor "
222 		    "%s: %s", path, topo_strerror(err));
223 		ret = topo_mod_seterrno(mod, err);
224 		goto out;
225 
226 	}
227 
228 	if (topo_method_register(mod, fnode, topo_sensor_temp_fac_methods) <
229 	    0) {
230 		topo_mod_dprintf(mod, "failed to register reading methods on "
231 		    "%s", path);
232 		ret = -1;
233 		goto out;
234 	}
235 
236 	if (topo_mod_nvalloc(mod, &reader_arg, NV_UNIQUE_NAME) != 0 ||
237 	    nvlist_add_string(reader_arg, TOPO_IO_DEV_PATH, path) != 0) {
238 		topo_mod_dprintf(mod, "Failed to set up reader argument nvl");
239 		ret = topo_mod_seterrno(mod, EMOD_NOMEM);
240 		goto out;
241 	}
242 
243 	if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY,
244 	    TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, TOPO_METH_TOPO_SENSOR_TEMP,
245 	    reader_arg, &err) != 0) {
246 		topo_mod_dprintf(mod, "failed to set argument for sensor %s: "
247 		    "%s", path, topo_strerror(err));
248 		err = topo_mod_seterrno(mod, err);
249 		goto out;
250 	}
251 
252 	nvlist_free(reader_arg);
253 	return (0);
254 out:
255 	if (fd >= 0) {
256 		(void) close(fd);
257 	}
258 
259 	topo_node_unbind(fnode);
260 	nvlist_free(reader_arg);
261 	return (ret);
262 }
263