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