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