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
fac_prov_libhp_find_node(topo_mod_t * mod,const char * conn)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
fac_prov_libhp_set_val(topo_mod_t * mod,hp_node_t hp,const char * opt_name,const char * val)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
fac_prov_libhp_get_opt(topo_mod_t * mod,hp_node_t hp,const char * opt_name,const char * opt_on,nvlist_t ** nvout)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
fac_prov_libhp_opt_set(topo_mod_t * mod,tnode_t * tn,topo_version_t vers,nvlist_t * in,nvlist_t ** nvout)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
topo_fac_prov_libhp_enum(topo_mod_t * mod,tnode_t * tn,const char * name,topo_instance_t min,topo_instance_t max,void * modarg,void * data)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
_topo_init(topo_mod_t * mod,topo_version_t version)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
_topo_fini(topo_mod_t * mod)329 _topo_fini(topo_mod_t *mod)
330 {
331 topo_mod_unregister(mod);
332 }
333