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 provides the basic mechanisms for dealing with the pcie schema. A pcie
18 * FMRI has the form:
19 *
20 * pcie:///cpu=C[/root-complex=R[function=F[...]]]
21 *
22 * That is to say that the top level nodes represent physical CPUs in the
23 * system and their PCIe root complexes are enumerated directly under that.
24 *
25 * Each node inherits the FMRI of its parent, and then appends a new
26 * '/<type>=<instance>' part, for example:
27 *
28 * pcie:///cpu=0
29 * pcie:///cpu=0/root-complex=0
30 * pcie:///cpu=0/root-complex=0/function=0
31 * pcie:///cpu=0/root-complex=0/function=0/port=0
32 *
33 * Types used in the scheme are:
34 * cpu
35 * root-complex
36 * device
37 * function
38 * port
39 * link
40 */
41
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <errno.h>
46 #include <stdbool.h>
47 #include <ctype.h>
48 #include <alloca.h>
49 #include <limits.h>
50 #include <fm/topo_mod.h>
51 #include <sys/param.h>
52 #include <sys/systeminfo.h>
53 #include <sys/fm/protocol.h>
54 #include <sys/stat.h>
55
56 #include <topo_method.h>
57 #include <topo_subr.h>
58 #include <pthread.h>
59
60 #include <pcie.h>
61
62 static size_t
fmri_nvl2str(nvlist_t * nvl,char * buf,size_t buflen)63 fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen)
64 {
65 size_t size = 0;
66 uint8_t version;
67 nvlist_t **plist;
68 uint_t nplist;
69 int err;
70
71 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
72 version > FM_PCIE_SCHEME_VERSION) {
73 return (0);
74 }
75
76 if (!topo_fmristr_build(&size, buf, buflen, FM_FMRI_SCHEME_PCIE, NULL,
77 ":///")) {
78 return (0);
79 }
80
81 err = nvlist_lookup_nvlist_array(nvl, FM_FMRI_PCIE_LIST,
82 &plist, &nplist);
83 if (err != 0 || plist == NULL)
84 return (0);
85
86 for (uint_t i = 0; i < nplist; i++) {
87 char *name = NULL;
88 char *id = NULL;
89
90 if (i > 0) {
91 if (!topo_fmristr_build(&size, buf, buflen, "/", NULL,
92 NULL)) {
93 return (0);
94 }
95 }
96 if (nvlist_lookup_string(plist[i],
97 FM_FMRI_PCIE_NAME, &name) != 0 ||
98 nvlist_lookup_string(plist[i],
99 FM_FMRI_PCIE_ID, &id) != 0) {
100 return (0);
101 }
102 if (name == NULL || id == NULL)
103 return (0);
104 if (!topo_fmristr_build(&size, buf, buflen, "=", name, id))
105 return (0);
106 }
107
108 return (size);
109 }
110
111 static int
pcie_fmri_nvl2str(topo_mod_t * mod,tnode_t * node,topo_version_t version,nvlist_t * nvl,nvlist_t ** out)112 pcie_fmri_nvl2str(topo_mod_t *mod, tnode_t *node, topo_version_t version,
113 nvlist_t *nvl, nvlist_t **out)
114 {
115 size_t len;
116 char *name = NULL;
117 nvlist_t *fmristr;
118
119 if (version > TOPO_METH_NVL2STR_VERSION)
120 return (topo_mod_seterrno(mod, EMOD_VER_NEW));
121
122 if ((len = fmri_nvl2str(nvl, NULL, 0)) == 0)
123 return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
124
125 if ((name = topo_mod_alloc(mod, len + 1)) == NULL)
126 return (topo_mod_seterrno(mod, EMOD_NOMEM));
127
128 if (fmri_nvl2str(nvl, name, len + 1) == 0) {
129 topo_mod_free(mod, name, len + 1);
130 return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
131 }
132
133 if (topo_mod_nvalloc(mod, &fmristr, NV_UNIQUE_NAME) != 0) {
134 topo_mod_free(mod, name, len + 1);
135 return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
136 }
137 if (nvlist_add_string(fmristr, "fmri-string", name) != 0) {
138 topo_mod_free(mod, name, len + 1);
139 nvlist_free(fmristr);
140 return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
141 }
142 topo_mod_free(mod, name, len + 1);
143 *out = fmristr;
144
145 return (0);
146 }
147
148 static nvlist_t *
fmri_create_err(topo_mod_t * mod,nvlist_t ** list,uint_t elems,nvlist_t * fmri,int err)149 fmri_create_err(topo_mod_t *mod, nvlist_t **list, uint_t elems,
150 nvlist_t *fmri, int err)
151 {
152 if (list != NULL) {
153 for (uint_t i = 0; i < elems; i++)
154 nvlist_free(list[i]);
155
156 topo_mod_free(mod, list, sizeof (nvlist_t *) * elems);
157 }
158
159 nvlist_free(fmri);
160
161 (void) topo_mod_seterrno(mod, err);
162
163 topo_mod_dprintf(mod, "unable to create pcie FMRI: %s\n",
164 topo_mod_errmsg(mod));
165
166 return (NULL);
167 }
168
169 static nvlist_t *
fmri_create_component(topo_mod_t * mod,const char * name,topo_instance_t inst)170 fmri_create_component(topo_mod_t *mod, const char *name, topo_instance_t inst)
171 {
172 char str[21]; /* decimal representation of UINT64_MAX + '\0' */
173 nvlist_t *comp;
174
175 if (topo_mod_nvalloc(mod, &comp, NV_UNIQUE_NAME) != 0)
176 return (NULL);
177
178 (void) snprintf(str, sizeof (str), "%" PRIu64, inst);
179
180 if (nvlist_add_string(comp, FM_FMRI_PCIE_NAME, name) != 0 ||
181 nvlist_add_string(comp, FM_FMRI_PCIE_ID, str) != 0) {
182 nvlist_free(comp);
183 return (NULL);
184 }
185
186 return (comp);
187 }
188
189 static nvlist_t *
fmri_create(topo_mod_t * mod,nvlist_t * pfmri,const char * name,topo_instance_t inst,nvlist_t * auth)190 fmri_create(topo_mod_t *mod, nvlist_t *pfmri, const char *name,
191 topo_instance_t inst, nvlist_t *auth)
192 {
193 nvlist_t **pplist = NULL;
194 nvlist_t **plist = NULL;
195 nvlist_t *fmri = NULL;
196 uint_t pelems = 0, elems;
197 uint_t i;
198
199 /*
200 * This FMRI will be constructed from the FMRI of the parent, with a
201 * new path component (name=inst) after it. We copy the parent's
202 * property list to this node, and then add our new property to the
203 * end.
204 */
205
206 /* Retrieve the parent's property list */
207 if (pfmri != NULL) {
208 if (nvlist_lookup_nvlist_array(pfmri, FM_FMRI_PCIE_LIST,
209 &pplist, &pelems) != 0) {
210 return (fmri_create_err(mod, plist, pelems, fmri,
211 EMOD_FMRI_MALFORM));
212 }
213 }
214
215 /* We want space for an extra entry in the new FMRI's property list */
216 elems = pelems + 1;
217
218 plist = topo_mod_zalloc(mod, sizeof (nvlist_t *) * elems);
219 if (plist == NULL) {
220 return (fmri_create_err(mod, plist, elems, fmri,
221 ETOPO_FMRI_NOMEM));
222 }
223
224 /* Copy the parent properties */
225 for (i = 0; i < pelems; i++) {
226 if (topo_mod_nvdup(mod, pplist[i], &plist[i]) != 0) {
227 return (fmri_create_err(mod, plist, elems, fmri,
228 EMOD_FMRI_NVL));
229 }
230 }
231
232 /* Add the new path component */
233 if ((plist[i] = fmri_create_component(mod, name, inst)) == NULL) {
234 return (fmri_create_err(mod, plist, elems, fmri,
235 EMOD_FMRI_NVL));
236 }
237
238 /* Create the fmri */
239 if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0) {
240 return (fmri_create_err(mod, plist, elems, fmri,
241 EMOD_FMRI_NVL));
242 }
243
244 if (nvlist_add_uint8(fmri, FM_VERSION, FM_PCIE_SCHEME_VERSION) ||
245 nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_PCIE)) {
246 return (fmri_create_err(mod, plist, elems, fmri,
247 EMOD_FMRI_NVL));
248 }
249
250 /* Add the new property list */
251 if (nvlist_add_nvlist_array(fmri, FM_FMRI_PCIE_LIST,
252 plist, elems) != 0) {
253 return (fmri_create_err(mod, plist, elems, fmri,
254 EMOD_FMRI_NVL));
255 }
256
257 if (auth != NULL)
258 (void) nvlist_add_nvlist(fmri, FM_FMRI_AUTHORITY, auth);
259
260 if (plist != NULL) {
261 for (uint_t i = 0; i < elems; i++)
262 nvlist_free(plist[i]);
263 topo_mod_free(mod, plist, sizeof (nvlist_t *) * elems);
264 }
265
266 return (fmri);
267 }
268
269 static int
pcie_fmri_create_meth(topo_mod_t * mod,tnode_t * tnode,topo_version_t version,nvlist_t * in,nvlist_t ** out)270 pcie_fmri_create_meth(topo_mod_t *mod, tnode_t *tnode, topo_version_t version,
271 nvlist_t *in, nvlist_t **out)
272 {
273 char *name;
274 topo_instance_t inst;
275 nvlist_t *args, *auth = NULL, *pfmri = NULL;
276 int ret;
277
278 if (version > TOPO_METH_FMRI_VERSION)
279 return (topo_mod_seterrno(mod, EMOD_VER_NEW));
280
281 if (nvlist_lookup_string(in, TOPO_METH_FMRI_ARG_NAME, &name) != 0 ||
282 nvlist_lookup_uint64(in, TOPO_METH_FMRI_ARG_INST, &inst) != 0) {
283 return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
284 }
285
286 if ((ret = nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_NVL,
287 &args)) != 0) {
288 if (ret != ENOENT)
289 return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
290 } else {
291 (void) nvlist_lookup_nvlist(args, TOPO_METH_FMRI_ARG_PARENT,
292 &pfmri);
293 (void) nvlist_lookup_nvlist(args, TOPO_METH_FMRI_ARG_AUTH,
294 &auth);
295 }
296
297 *out = fmri_create(mod, pfmri, name, inst, auth);
298 if (*out == NULL)
299 return (-1);
300
301 return (0);
302 }
303
304 const topo_method_t pcie_methods[] = {
305 {
306 .tm_name = TOPO_METH_NVL2STR,
307 .tm_desc = TOPO_METH_NVL2STR_DESC,
308 .tm_version = TOPO_METH_NVL2STR_VERSION,
309 .tm_stability = TOPO_STABILITY_INTERNAL,
310 .tm_func = pcie_fmri_nvl2str
311 },
312 {
313 .tm_name = TOPO_METH_FMRI,
314 .tm_desc = TOPO_METH_FMRI_DESC,
315 .tm_version = TOPO_METH_FMRI_VERSION,
316 .tm_stability = TOPO_STABILITY_INTERNAL,
317 .tm_func = pcie_fmri_create_meth
318 },
319 { NULL }
320 };
321
322 int
pcie_enum(topo_mod_t * mod,tnode_t * pnode,const char * name,topo_instance_t min,topo_instance_t max,void * u1 __unused,void * u2 __unused)323 pcie_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
324 topo_instance_t min, topo_instance_t max, void *u1 __unused,
325 void *u2 __unused)
326 {
327 topo_mod_dprintf(mod, "enumerating pcie: %s (%"PRIu64" - %"PRIu64")",
328 name, min, max);
329
330 (void) topo_method_register(mod, pnode, pcie_methods);
331 return (0);
332 }
333
334 static void
pcie_rele(topo_mod_t * mp,tnode_t * node)335 pcie_rele(topo_mod_t *mp, tnode_t *node)
336 {
337 topo_method_unregister_all(mp, node);
338 }
339
340 static const topo_modops_t pcie_ops = {
341 .tmo_enum = pcie_enum,
342 .tmo_release = pcie_rele
343 };
344
345 static const topo_modinfo_t pcie_info = {
346 .tmi_desc = PCIE,
347 .tmi_scheme = FM_FMRI_SCHEME_PCIE,
348 .tmi_version = PCIE_VERSION,
349 .tmi_ops = &pcie_ops
350 };
351
352 int
pcie_init(topo_mod_t * mod,topo_version_t version)353 pcie_init(topo_mod_t *mod, topo_version_t version)
354 {
355 if (getenv("TOPOPCIEDEBUG"))
356 topo_mod_setdebug(mod);
357
358 topo_mod_dprintf(mod, "initializing pcie builtin");
359
360 if (version != PCIE_VERSION)
361 return (topo_mod_seterrno(mod, EMOD_VER_NEW));
362
363 if (topo_mod_register(mod, &pcie_info, TOPO_VERSION) != 0) {
364 topo_mod_dprintf(mod, "failed to register pcie: %s",
365 topo_mod_errmsg(mod));
366 return (-1); /* mod errno already set */
367 }
368
369 return (0);
370 }
371
372 void
pcie_fini(topo_mod_t * mod)373 pcie_fini(topo_mod_t *mod)
374 {
375 topo_mod_unregister(mod);
376 }
377