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
38static int
39topo_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;
107out:
108	if (fd >= 0) {
109		(void) close(fd);
110	}
111	return (ret);
112}
113
114static 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
121static topo_sensor_unit_t
122topo_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
136int
137topo_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);
254out:
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