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