xref: /illumos-gate/usr/src/lib/fm/topo/modules/i86pc/pciebus/topo_pcie_i86pc.c (revision 84ceaea936ebcf122d4f0756d298adf307fd491d)
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 2026 Oxide Computer Company
14  */
15 
16 /*
17  * This is the i86pc architecture specific part of the pciebus enumeration
18  * module. It provides hooks which are called at module init and fini, and
19  * after each topology node is created. It uses SMBIOS data to decorate
20  * topology nodes with labels.
21  *
22  * At initialisation time, the SMBIOS structures are walked to collect:
23  *  - Processor entries (Type 4) with their socket designations;
24  *  - System slot entries (Type 9) with their reference designators and
25  *    bus/device/function information;
26  *  - On-board device extended entries (Type 41) with their reference
27  *    designators and bus/device/function information.
28  *
29  * This information is then used when decorating topology nodes:
30  *  - CPU nodes are labelled with the processor socket designation;
31  *  - Downstream port nodes are labelled by matching the parent bridge's
32  *    secondary bus number against SMBIOS slot and on-board device entries.
33  *
34  * The SMBIOS slot and on-board device entries include bus, device, and function
35  * numbers but we only match on bus. This is sufficient because PCIe is a
36  * point-to-point interface; the downstream side of a port is always on a
37  * unique bus, so the bus number uniquely identifies the slot.
38  */
39 
40 #include <stdbool.h>
41 #include <string.h>
42 #include <sys/types.h>
43 #include <sys/debug.h>
44 #include <sys/sysmacros.h>
45 #include <sys/smbios.h>
46 
47 #include <fm/topo_mod.h>
48 #include <fm/topo_list.h>
49 #include <fm/topo_method.h>
50 
51 #include "topo_pcie_impl.h"
52 
53 /*
54  * An SMBIOS device entry, used for both upgradeable system slots (Type 9) and
55  * on-board device extended entries (Type 41). The bus and device/function
56  * numbers allow us to correlate SMBIOS entries with devices in the PCIe
57  * topology.
58  */
59 typedef struct smbios_dev_entry {
60 	const char	*sde_label;	/* reference designator */
61 	uint16_t	sde_sg;		/* segment group */
62 	uint8_t		sde_bus;	/* bus number */
63 	uint8_t		sde_df;		/* device/function number */
64 } smbios_dev_entry_t;
65 
66 /*
67  * An SMBIOS processor entry (Type 4). The label is the socket designation
68  * from the structure's common information (location tag).
69  */
70 typedef struct smbios_proc_entry {
71 	const char	*spe_label;	/* socket designation */
72 } smbios_proc_entry_t;
73 
74 typedef struct mod_pcie_privdata {
75 	/* SMBIOS processor entries (Type 4) */
76 	smbios_proc_entry_t	*mpp_procs;
77 	uint_t			mpp_nprocs;
78 	uint_t			mpp_nprocs_alloc;
79 
80 	/* SMBIOS slot entries (Type 9) */
81 	smbios_dev_entry_t	*mpp_slots;
82 	uint_t			mpp_nslots;
83 	uint_t			mpp_nslots_alloc;
84 
85 	/* SMBIOS on-board device entries (Type 41) */
86 	smbios_dev_entry_t	*mpp_obdevs;
87 	uint_t			mpp_nobdevs;
88 	uint_t			mpp_nobdevs_alloc;
89 } mod_pcie_privdata_t;
90 
91 static const char *
smbios_find_slot_by_bus(const mod_pcie_privdata_t * pd,uint8_t bus)92 smbios_find_slot_by_bus(const mod_pcie_privdata_t *pd, uint8_t bus)
93 {
94 	for (uint_t i = 0; i < pd->mpp_nslots; i++) {
95 		if (pd->mpp_slots[i].sde_bus == bus)
96 			return (pd->mpp_slots[i].sde_label);
97 	}
98 	return (NULL);
99 }
100 
101 static const char *
smbios_find_obdev_by_bus(const mod_pcie_privdata_t * pd,uint8_t bus)102 smbios_find_obdev_by_bus(const mod_pcie_privdata_t *pd, uint8_t bus)
103 {
104 	for (uint_t i = 0; i < pd->mpp_nobdevs; i++) {
105 		if (pd->mpp_obdevs[i].sde_bus == bus)
106 			return (pd->mpp_obdevs[i].sde_label);
107 	}
108 	return (NULL);
109 }
110 
111 /*
112  * Decorate a downstream port node with a label derived from SMBIOS.
113  *
114  * We only label downstream ports, which are ports whose parent in the
115  * topology tree is a function node (not a link). The parent function
116  * represents a root port or bridge whose bus-range property tells us
117  * the secondary bus. This is the bus number where the slot's device
118  * appears, and can be matched against SMBIOS slot (Type 9) and on-board
119  * device (Type 41) entries.
120  */
121 static tnode_t *
decorate_port(const mod_pcie_privdata_t * pd,topo_mod_t * mod,const pcie_node_t * node __unused,tnode_t * tn)122 decorate_port(const mod_pcie_privdata_t *pd, topo_mod_t *mod,
123     const pcie_node_t *node __unused, tnode_t *tn)
124 {
125 	tnode_t *ptn;
126 	const char *label;
127 	int *busrange, nval, err;
128 	uint_t secbus;
129 	const pcie_node_t *fn;
130 
131 	ptn = topo_node_parent(tn);
132 	if (ptn == NULL || strcmp(topo_node_name(ptn), "link") == 0)
133 		return (tn);
134 
135 	/*
136 	 * The parent function node has a pcie_node_t stored via
137 	 * topo_node_setspecific(). Retrieve it to access the devinfo node
138 	 * and its bus-range property.
139 	 */
140 	fn = topo_node_getspecific(ptn);
141 	if (fn == NULL || fn->pn_did == DI_NODE_NIL)
142 		return (tn);
143 
144 	nval = di_prop_lookup_ints(DDI_DEV_T_ANY, fn->pn_did,
145 	    DI_BUSRANGE, &busrange);
146 	if (nval < 1)
147 		return (tn);
148 
149 	secbus = (uint8_t)busrange[0];
150 
151 	topo_mod_dprintf(mod,
152 	    "decorate port: parent %s%" PRIu64 " secondary bus 0x%02x",
153 	    topo_node_name(ptn), topo_node_instance(ptn), secbus);
154 
155 	/*
156 	 * Prefer slot entries as they are more specific to expansion slots.
157 	 * Fall back to on-board device entries which cover integrated devices.
158 	 */
159 	label = smbios_find_slot_by_bus(pd, secbus);
160 	if (label == NULL)
161 		label = smbios_find_obdev_by_bus(pd, secbus);
162 
163 	if (label != NULL) {
164 		topo_mod_dprintf(mod, "decorate port: label '%s'", label);
165 		(void) topo_node_label_set(tn, label, &err);
166 	}
167 
168 	return (tn);
169 }
170 
171 /*
172  * The CPU's topology instance corresponds to the index into the SMBIOS
173  * processor structure list (in the order they appear in the SMBIOS tables).
174  * We assume that the SMBIOS table order matches the topology instance order;
175  * there is no better way to correlate the two, and in practice firmware
176  * enumerates processors in socket order.
177  */
178 static tnode_t *
decorate_cpu(const mod_pcie_privdata_t * pd,topo_mod_t * mod,const pcie_node_t * node __unused,tnode_t * tn)179 decorate_cpu(const mod_pcie_privdata_t *pd, topo_mod_t *mod,
180     const pcie_node_t *node __unused, tnode_t *tn)
181 {
182 	topo_instance_t inst = topo_node_instance(tn);
183 	const char *label;
184 	int err;
185 
186 	if (inst >= pd->mpp_nprocs)
187 		return (tn);
188 
189 	label = pd->mpp_procs[inst].spe_label;
190 	if (label != NULL && label[0] != '\0') {
191 		topo_mod_dprintf(mod,
192 		    "decorate cpu%" PRIu64 ": label '%s'", inst, label);
193 		(void) topo_node_label_set(tn, label, &err);
194 	}
195 
196 	return (tn);
197 }
198 
199 /*
200  * This is the main entry point for this arch-specific pciebus component. It is
201  * called for every topology node that is created after the basic properties
202  * are set.
203  */
204 tnode_t *
mod_pcie_platform_topo_node_decorate(topo_mod_t * mod,const pcie_t * pcie,const pcie_node_t * node,tnode_t * tn)205 mod_pcie_platform_topo_node_decorate(topo_mod_t *mod, const pcie_t *pcie,
206     const pcie_node_t *node, tnode_t *tn)
207 {
208 	const mod_pcie_privdata_t *pd;
209 	const char *name;
210 
211 	pd = pcie_get_platdata(pcie);
212 	if (pd == NULL) {
213 		topo_mod_dprintf(mod, "decorate: no privdata");
214 		return (tn);
215 	}
216 
217 	name = topo_node_name(tn);
218 
219 	topo_mod_dprintf(mod, "decorate: %s%" PRIu64,
220 	    name, topo_node_instance(tn));
221 
222 	if (strcmp(name, CPU) == 0)
223 		return (decorate_cpu(pd, mod, node, tn));
224 	else if (strcmp(name, "port") == 0)
225 		return (decorate_port(pd, mod, node, tn));
226 
227 	return (tn);
228 }
229 
230 nvlist_t *
mod_pcie_platform_auth(topo_mod_t * mod,const pcie_t * pcie,tnode_t * parent)231 mod_pcie_platform_auth(topo_mod_t *mod, const pcie_t *pcie, tnode_t *parent)
232 {
233 	return (topo_mod_auth(mod, parent));
234 }
235 
236 /*
237  * Collect SMBIOS data for use during topology decoration.
238  *
239  * This is done in two passes over the SMBIOS structures:
240  *  1. Count entries of each type;
241  *  2. Allocate arrays and fill them in.
242  */
243 typedef struct mod_pcie_smbios_collect {
244 	topo_mod_t		*sc_mod;
245 	smbios_hdl_t		*sc_shp;
246 
247 	smbios_dev_entry_t	*sc_slots;
248 	uint_t			sc_nslots;
249 
250 	smbios_dev_entry_t	*sc_obdevs;
251 	uint_t			sc_nobdevs;
252 
253 	smbios_proc_entry_t	*sc_procs;
254 	uint_t			sc_nprocs;
255 } mod_pcie_smbios_collect_t;
256 
257 static int
smbios_collect_count_cb(smbios_hdl_t * shp __unused,const smbios_struct_t * sp,void * arg)258 smbios_collect_count_cb(smbios_hdl_t *shp __unused,
259     const smbios_struct_t *sp, void *arg)
260 {
261 	mod_pcie_smbios_collect_t *sc = arg;
262 
263 	switch (sp->smbstr_type) {
264 	case SMB_TYPE_PROCESSOR:
265 		sc->sc_nprocs++;
266 		break;
267 	case SMB_TYPE_OBDEVEXT:
268 		sc->sc_nobdevs++;
269 		break;
270 	case SMB_TYPE_SLOT:
271 		sc->sc_nslots++;
272 		break;
273 	}
274 
275 	return (0);
276 }
277 
278 static int
smbios_collect_cb(smbios_hdl_t * shp,const smbios_struct_t * sp,void * arg)279 smbios_collect_cb(smbios_hdl_t *shp,
280     const smbios_struct_t *sp, void *arg)
281 {
282 	mod_pcie_smbios_collect_t *sc = arg;
283 
284 	switch (sp->smbstr_type) {
285 	case SMB_TYPE_PROCESSOR: {
286 		smbios_proc_entry_t *entry;
287 		smbios_processor_t proc;
288 		smbios_info_t info;
289 
290 		/*
291 		 * Always allocate an entry to maintain index alignment with
292 		 * topology CPU instance numbers, but only populate the label
293 		 * for sockets that are present.
294 		 */
295 		entry = &sc->sc_procs[sc->sc_nprocs++];
296 
297 		if (smbios_info_processor(shp, sp->smbstr_id, &proc) != 0)
298 			break;
299 
300 		if (!SMB_PRSTATUS_PRESENT(proc.smbp_status))
301 			break;
302 
303 		if (smbios_info_common(shp, sp->smbstr_id, &info) != 0)
304 			break;
305 
306 		/*
307 		 * The strings returned by the various smbios_info_*()
308 		 * functions point into the SMBIOS handle's data and are valid
309 		 * for its lifetime.
310 		 */
311 		entry->spe_label = info.smbi_location;
312 
313 		topo_mod_dprintf(sc->sc_mod, "SMBIOS processor[%u]: '%s'",
314 		    sc->sc_nprocs - 1, info.smbi_location);
315 		break;
316 	}
317 	case SMB_TYPE_OBDEVEXT: {
318 		smbios_dev_entry_t *entry;
319 		smbios_obdev_ext_t ob;
320 
321 		if (smbios_info_obdevs_ext(shp, sp->smbstr_id, &ob) != 0)
322 			break;
323 
324 		entry = &sc->sc_obdevs[sc->sc_nobdevs++];
325 		entry->sde_label = ob.smboe_name;
326 		entry->sde_sg = ob.smboe_sg;
327 		entry->sde_bus = ob.smboe_bus;
328 		entry->sde_df = ob.smboe_df;
329 
330 		topo_mod_dprintf(sc->sc_mod,
331 		    "SMBIOS obdev: '%s' seg %u bus 0x%02x df 0x%02x",
332 		    ob.smboe_name, ob.smboe_sg, ob.smboe_bus, ob.smboe_df);
333 		break;
334 	}
335 	case SMB_TYPE_SLOT: {
336 		smbios_dev_entry_t *entry;
337 		smbios_slot_t slot;
338 
339 		if (smbios_info_slot(shp, sp->smbstr_id, &slot) != 0)
340 			break;
341 
342 		/*
343 		 * Slots with bus number 0xff are not populated or do not
344 		 * have valid routing information; skip them.
345 		 */
346 		if (slot.smbl_bus == 0xff)
347 			break;
348 
349 		entry = &sc->sc_slots[sc->sc_nslots++];
350 		entry->sde_label = slot.smbl_name;
351 		entry->sde_sg = slot.smbl_sg;
352 		entry->sde_bus = slot.smbl_bus;
353 		entry->sde_df = slot.smbl_df;
354 
355 		topo_mod_dprintf(sc->sc_mod,
356 		    "SMBIOS slot: '%s' seg %u bus 0x%02x df 0x%02x",
357 		    slot.smbl_name, slot.smbl_sg, slot.smbl_bus, slot.smbl_df);
358 		break;
359 	}
360 	default:
361 		break;
362 	}
363 
364 	return (0);
365 }
366 
367 static bool
smbios_collect_init(topo_mod_t * mod,mod_pcie_privdata_t * pd)368 smbios_collect_init(topo_mod_t *mod, mod_pcie_privdata_t *pd)
369 {
370 	mod_pcie_smbios_collect_t sc = { 0 };
371 	smbios_hdl_t *shp;
372 
373 	shp = topo_mod_smbios(mod);
374 	if (shp == NULL) {
375 		topo_mod_dprintf(mod, "SMBIOS not available");
376 		return (true);
377 	}
378 
379 	sc.sc_mod = mod;
380 	sc.sc_shp = shp;
381 
382 	/*
383 	 * Go through and count up the number of entries of each type.
384 	 * This callback never returns an error.
385 	 */
386 	VERIFY0(smbios_iter(shp, smbios_collect_count_cb, &sc));
387 
388 	topo_mod_dprintf(mod,
389 	    "SMBIOS counts: processors: %u on-board devices: %u slots: %u",
390 	    sc.sc_nprocs, sc.sc_nobdevs, sc.sc_nslots);
391 
392 	if (sc.sc_nprocs > 0) {
393 		pd->mpp_procs = topo_mod_zalloc(mod,
394 		    sc.sc_nprocs * sizeof (smbios_proc_entry_t));
395 		if (pd->mpp_procs == NULL)
396 			return (false);
397 		pd->mpp_nprocs_alloc = sc.sc_nprocs;
398 	}
399 
400 	if (sc.sc_nobdevs > 0) {
401 		pd->mpp_obdevs = topo_mod_zalloc(mod,
402 		    sc.sc_nobdevs * sizeof (smbios_dev_entry_t));
403 		if (pd->mpp_obdevs == NULL)
404 			return (false);
405 		pd->mpp_nobdevs_alloc = sc.sc_nobdevs;
406 	}
407 
408 	if (sc.sc_nslots > 0) {
409 		pd->mpp_slots = topo_mod_zalloc(mod,
410 		    sc.sc_nslots * sizeof (smbios_dev_entry_t));
411 		if (pd->mpp_slots == NULL)
412 			return (false);
413 		pd->mpp_nslots_alloc = sc.sc_nslots;
414 	}
415 
416 	/* Now go through and populate the entries */
417 	sc.sc_procs = pd->mpp_procs;
418 	sc.sc_obdevs = pd->mpp_obdevs;
419 	sc.sc_slots = pd->mpp_slots;
420 	sc.sc_nprocs = sc.sc_nobdevs = sc.sc_nslots = 0;
421 
422 	/* This callback never returns an error */
423 	VERIFY0(smbios_iter(shp, smbios_collect_cb, &sc));
424 
425 	topo_mod_dprintf(mod,
426 	    "SMBIOS populated: processors: %u on-board devices: %u slots: %u",
427 	    sc.sc_nprocs, sc.sc_nobdevs, sc.sc_nslots);
428 
429 	pd->mpp_nprocs = sc.sc_nprocs;
430 	pd->mpp_nobdevs = sc.sc_nobdevs;
431 	pd->mpp_nslots = sc.sc_nslots;
432 
433 	return (true);
434 }
435 
436 static void
privdata_free(topo_mod_t * mod,mod_pcie_privdata_t * pd)437 privdata_free(topo_mod_t *mod, mod_pcie_privdata_t *pd)
438 {
439 	if (pd->mpp_procs != NULL) {
440 		topo_mod_free(mod, pd->mpp_procs,
441 		    pd->mpp_nprocs_alloc * sizeof (smbios_proc_entry_t));
442 	}
443 
444 	if (pd->mpp_obdevs != NULL) {
445 		topo_mod_free(mod, pd->mpp_obdevs,
446 		    pd->mpp_nobdevs_alloc * sizeof (smbios_dev_entry_t));
447 	}
448 
449 	if (pd->mpp_slots != NULL) {
450 		topo_mod_free(mod, pd->mpp_slots,
451 		    pd->mpp_nslots_alloc * sizeof (smbios_dev_entry_t));
452 	}
453 
454 	topo_mod_free(mod, pd, sizeof (*pd));
455 }
456 
457 bool
mod_pcie_platform_init(topo_mod_t * mod,pcie_t * pcie)458 mod_pcie_platform_init(topo_mod_t *mod, pcie_t *pcie)
459 {
460 	mod_pcie_privdata_t *pd;
461 
462 	topo_mod_dprintf(mod, "%s start", __func__);
463 
464 	if ((pd = topo_mod_zalloc(mod, sizeof (*pd))) == NULL)
465 		return (false);
466 
467 	if (!smbios_collect_init(mod, pd)) {
468 		topo_mod_dprintf(mod, "SMBIOS collection failed");
469 		privdata_free(mod, pd);
470 		return (false);
471 	}
472 
473 	return (pcie_set_platdata(pcie, pd));
474 }
475 
476 void
mod_pcie_platform_fini(topo_mod_t * mod,pcie_t * pcie)477 mod_pcie_platform_fini(topo_mod_t *mod, pcie_t *pcie)
478 {
479 	mod_pcie_privdata_t *pd;
480 
481 	if ((pd = pcie_get_platdata(pcie)) == NULL)
482 		return;
483 
484 	privdata_free(mod, pd);
485 	(void) pcie_set_platdata(pcie, NULL);
486 }
487