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 2023 Oxide Computer Company 14 */ 15 16 /* 17 * This facility provider works with libhotplug to use its private properties to 18 * try and provide LED functionality. To work with libhotplug's bus-specific 19 * private options (which really here is all about PCIe) we require that the 20 * caller give us the name of the slot as cfgadm and others know about it. This 21 * varies on the system. 22 * 23 * Our assumption is that the name of this is the name of the 'connector' in 24 * libhotplug parlance and that by reading the /dev/cfg/<name> link, we'll 25 * figure out where in the tree it is. 26 * 27 * The LED method on this facility does not attempt to prescribe meaning to the 28 * actual logical topology facility type. The assumption we have is that the 29 * caller has set up what makes sense. That means if they want to use the power 30 * LED for attention or something else entirely, that is their prerogative. For 31 * all of this to work, the members of the 'libhp' property group defined below 32 * are required. 33 * 34 * "connector" (string): This is the name of the connector to look for. 35 * 36 * "option" (string): This is the name of the libhotplug option to actually look 37 * for. For a PCI class device this is generally something like "attn_led", 38 * "power_led", etc. or similar. 39 * 40 * "opt_on" (string): This indicates the value of the option that should be set 41 * when we are turning it on. This must be one of the supported option strings. 42 * Currently there is no validation of the option string. When querying for 43 * whether the indicator is on or off in libtopo, if we get a value that is not 44 * this string, then we will consider the indicator off. 45 * 46 * "opt_off" (string): This indicates the value of the option that should be set 47 * when we are turning it off. This should generally by the 'default' option for 48 * cases where the indicator is otherwise used. 49 */ 50 51 #include <sys/fm/protocol.h> 52 #include <fm/topo_mod.h> 53 #include <libhotplug.h> 54 #include <string.h> 55 #include <errno.h> 56 #include <unistd.h> 57 58 /* 59 * The following are the members of the libhp property group that we expect to 60 * exist. These values are defined above. 61 */ 62 #define TOPO_PGROUP_LIBHP "libhp" 63 #define TOPO_PGROUP_LIBHP_CONNECTOR "connector" 64 #define TOPO_PGROUP_LIBHP_OPTION "option" 65 #define TOPO_PGROUP_LIBHP_OPT_ON "opt_on" 66 #define TOPO_PGROUP_LIBHP_OPT_OFF "opt_off" 67 #define LIBHP_OFF_MODE_VALUE "value" 68 #define LIBHP_OFF_MODE_CONN_POWER "conn_power" 69 70 /* 71 * This is the approximate buffer size we expect to use construct the 72 * opt_name=opt_val buffers in here. 73 */ 74 #define FAC_PROV_LIBHP_OPTLEN 128 75 76 /* 77 * Given the name for a connector, attempt to find the corresponding hp_node_t. 78 * As mentioned in the introduction to this module, the connector name is 79 * expected to be something in the form of a /dev/cfg/<name> link that'll point 80 * back to a /devices minor node. This link will have a fair bit of data before 81 * it gets back to a /devices part. So we'll find that and move past that. 82 */ 83 static hp_node_t 84 fac_prov_libhp_find_node(topo_mod_t *mod, const char *conn) 85 { 86 char cfg[PATH_MAX], link[PATH_MAX]; 87 ssize_t ret; 88 const char *prefix = "/devices"; 89 char *start, *end; 90 hp_node_t node; 91 92 if (snprintf(cfg, sizeof (cfg), "/dev/cfg/%s", conn) >= sizeof (cfg)) { 93 topo_mod_dprintf(mod, "failed to construct /dev/cfg path"); 94 return (NULL); 95 } 96 97 ret = readlink(cfg, link, sizeof (link)); 98 if (ret < 0) { 99 topo_mod_dprintf(mod, "failed to readlink %s: %s", cfg, 100 strerror(errno)); 101 return (NULL); 102 } 103 104 if ((size_t)ret >= sizeof (link)) { 105 topo_mod_dprintf(mod, "cannot process readlink of %s: link " 106 "did not fit in buffer", cfg); 107 return (NULL); 108 } 109 link[ret] = '\0'; 110 111 start = strstr(link, prefix); 112 if (start == NULL) { 113 topo_mod_dprintf(mod, "failed to find %s in %s", prefix, link); 114 return (NULL); 115 } 116 117 start += strlen(prefix); 118 end = strchr(start, ':'); 119 if (end == NULL) { 120 topo_mod_dprintf(mod, "failed to find ':' to indicate start of " 121 "minor node in %s", start); 122 return (NULL); 123 } 124 *end = '\0'; 125 126 topo_mod_dprintf(mod, "attempting to hp_init %s %s", start, conn); 127 node = hp_init(start, conn, 0); 128 if (node == NULL) { 129 topo_mod_dprintf(mod, "failed to init hp node: %s\n", 130 strerror(errno)); 131 return (NULL); 132 } 133 134 return (node); 135 } 136 137 static int 138 fac_prov_libhp_set_val(topo_mod_t *mod, hp_node_t hp, const char *opt_name, 139 const char *val) 140 { 141 int ret; 142 char buf[FAC_PROV_LIBHP_OPTLEN]; 143 char *res = NULL; 144 145 if (snprintf(buf, sizeof (buf), "%s=%s", opt_name, val) >= 146 sizeof (buf)) { 147 topo_mod_dprintf(mod, "failed to construct option buf"); 148 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 149 } 150 151 ret = hp_set_private(hp, buf, &res); 152 if (ret != 0) { 153 topo_mod_dprintf(mod, "failed to set prop %s: %s", buf, 154 strerror(ret)); 155 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 156 } 157 free(res); 158 159 return (0); 160 } 161 162 static int 163 fac_prov_libhp_get_opt(topo_mod_t *mod, hp_node_t hp, const char *opt_name, 164 const char *opt_on, nvlist_t **nvout) 165 { 166 int ret; 167 char *val; 168 uint32_t state; 169 nvlist_t *nvl; 170 char buf[FAC_PROV_LIBHP_OPTLEN]; 171 172 if (snprintf(buf, sizeof (buf), "%s=%s", opt_name, opt_on) >= 173 sizeof (buf)) { 174 topo_mod_dprintf(mod, "failed to construct option buf"); 175 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 176 } 177 178 ret = hp_get_private(hp, opt_name, &val); 179 if (ret != 0) { 180 topo_mod_dprintf(mod, "failed to get hp node private prop " 181 "%s: %s", opt_name, strerror(ret)); 182 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 183 } 184 185 topo_mod_dprintf(mod, "got hp node opt %s", val); 186 if (strcmp(val, buf) == 0) { 187 state = TOPO_LED_STATE_ON; 188 } else { 189 state = TOPO_LED_STATE_OFF; 190 } 191 free(val); 192 193 if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 || 194 nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 || 195 nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 || 196 nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, state) != 0) { 197 topo_mod_dprintf(mod, "failed to construct output nvl for " 198 "libhp node state"); 199 nvlist_free(nvl); 200 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 201 } 202 203 *nvout = nvl; 204 return (0); 205 } 206 207 static int 208 fac_prov_libhp_opt_set(topo_mod_t *mod, tnode_t *tn, topo_version_t vers, 209 nvlist_t *in, nvlist_t **nvout) 210 { 211 int err, ret = -1; 212 char *conn = NULL, *opt_name = NULL, *opt_on = NULL, *opt_off = NULL; 213 hp_node_t hp = NULL; 214 nvlist_t *pargs; 215 216 if (vers != 0) { 217 return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW)); 218 } 219 220 if (topo_prop_get_string(tn, TOPO_PGROUP_LIBHP, 221 TOPO_PGROUP_LIBHP_CONNECTOR, &conn, &err) != 0 || 222 topo_prop_get_string(tn, TOPO_PGROUP_LIBHP, 223 TOPO_PGROUP_LIBHP_OPTION, &opt_name, &err) != 0 || 224 topo_prop_get_string(tn, TOPO_PGROUP_LIBHP, 225 TOPO_PGROUP_LIBHP_OPT_ON, &opt_on, &err) != 0 || 226 topo_prop_get_string(tn, TOPO_PGROUP_LIBHP, 227 TOPO_PGROUP_LIBHP_OPT_OFF, &opt_off, &err) != 0) { 228 topo_mod_dprintf(mod, "failed to get required libhp props: %s", 229 topo_strerror(err)); 230 (void) topo_mod_seterrno(mod, err); 231 goto out; 232 } 233 234 hp = fac_prov_libhp_find_node(mod, conn); 235 if (hp == NULL) { 236 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 237 goto out; 238 } 239 240 if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) && 241 nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) { 242 uint32_t val; 243 244 err = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL, &val); 245 if (err != 0) { 246 ret = topo_mod_seterrno(mod, EMOD_NVL_INVAL); 247 goto out; 248 } 249 250 switch (val) { 251 case TOPO_LED_STATE_ON: 252 ret = fac_prov_libhp_set_val(mod, hp, opt_name, opt_on); 253 break; 254 case TOPO_LED_STATE_OFF: 255 ret = fac_prov_libhp_set_val(mod, hp, opt_name, 256 opt_off); 257 break; 258 default: 259 topo_mod_dprintf(mod, "unknown LED mode: 0x%x\n", val); 260 ret = topo_mod_seterrno(mod, EMOD_NVL_INVAL); 261 break; 262 } 263 } else { 264 ret = fac_prov_libhp_get_opt(mod, hp, opt_name, opt_on, nvout); 265 } 266 267 out: 268 topo_mod_strfree(mod, conn); 269 topo_mod_strfree(mod, opt_name); 270 topo_mod_strfree(mod, opt_on); 271 topo_mod_strfree(mod, opt_off); 272 if (hp != NULL) { 273 hp_fini(hp); 274 } 275 return (ret); 276 } 277 278 static const topo_method_t fac_prov_libhp_methods[] = { 279 { "libhp_opt_set", TOPO_PROP_METH_DESC, 0, 280 TOPO_STABILITY_INTERNAL, fac_prov_libhp_opt_set }, 281 { NULL } 282 }; 283 284 static int 285 topo_fac_prov_libhp_enum(topo_mod_t *mod, tnode_t *tn, const char *name, 286 topo_instance_t min, topo_instance_t max, void *modarg, void *data) 287 { 288 const char *tname = topo_node_name(tn); 289 topo_instance_t inst = topo_node_instance(tn); 290 int flags = topo_node_flags(tn); 291 292 topo_mod_dprintf(mod, "asked to enum %s [%" PRIu64 ", %" PRIu64 "] on " 293 "%s[%" PRIu64 "]", name, min, max, tname, inst); 294 295 if (flags != TOPO_NODE_FACILITY) { 296 topo_mod_dprintf(mod, "node %s[%" PRIu64 "] has unexpected " 297 "flags: 0x%x", tname, inst, flags); 298 return (-1); 299 } 300 301 if (topo_method_register(mod, tn, fac_prov_libhp_methods) != 0) { 302 topo_mod_dprintf(mod, "failed to register libhp facility " 303 "methods: %s", topo_mod_errmsg(mod)); 304 return (-1); 305 } 306 307 return (0); 308 } 309 310 static const topo_modops_t fac_prov_libhp_ops = { 311 topo_fac_prov_libhp_enum, NULL 312 }; 313 314 static const topo_modinfo_t fac_prov_libhp_mod = { 315 "libhotplug facility provider", FM_FMRI_SCHEME_HC, TOPO_VERSION, 316 &fac_prov_libhp_ops 317 }; 318 319 int 320 _topo_init(topo_mod_t *mod, topo_version_t version) 321 { 322 if (getenv("TOPOFACLIBHPDEBUG") != NULL) 323 topo_mod_setdebug(mod); 324 325 return (topo_mod_register(mod, &fac_prov_libhp_mod, TOPO_VERSION)); 326 } 327 328 void 329 _topo_fini(topo_mod_t *mod) 330 { 331 topo_mod_unregister(mod); 332 } 333