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 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 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 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