]>
Commit | Line | Data |
---|---|---|
c7a34993 MM |
1 | /* |
2 | * The PCI Utilities -- Show Bus Tree | |
3 | * | |
d1b22bb0 | 4 | * Copyright (c) 1997--2021 Martin Mares <mj@ucw.cz> |
c7a34993 | 5 | * |
61829219 MM |
6 | * Can be freely distributed and used under the terms of the GNU GPL v2+. |
7 | * | |
8 | * SPDX-License-Identifier: GPL-2.0-or-later | |
c7a34993 MM |
9 | */ |
10 | ||
76d47191 | 11 | #include <stdarg.h> |
c7a34993 MM |
12 | #include <stdio.h> |
13 | #include <string.h> | |
14 | ||
15 | #include "lspci.h" | |
16 | ||
6662052f | 17 | struct bridge host_bridge = { NULL, NULL, NULL, NULL, NULL, NULL, ~0, ~0, ~0, ~0, NULL }; |
c7a34993 MM |
18 | |
19 | static struct bus * | |
20 | find_bus(struct bridge *b, unsigned int domain, unsigned int n) | |
21 | { | |
22 | struct bus *bus; | |
23 | ||
24 | for (bus=b->first_bus; bus; bus=bus->sibling) | |
25 | if (bus->domain == domain && bus->number == n) | |
26 | break; | |
27 | return bus; | |
28 | } | |
29 | ||
67954c8b PR |
30 | static struct device * |
31 | find_device(struct pci_dev *dd) | |
32 | { | |
33 | struct device *d; | |
34 | ||
35 | if (!dd) | |
36 | return NULL; | |
37 | for (d=first_dev; d; d=d->next) | |
38 | if (d->dev == dd) | |
39 | break; | |
40 | return d; | |
41 | } | |
42 | ||
c7a34993 MM |
43 | static struct bus * |
44 | new_bus(struct bridge *b, unsigned int domain, unsigned int n) | |
45 | { | |
46 | struct bus *bus = xmalloc(sizeof(struct bus)); | |
47 | bus->domain = domain; | |
48 | bus->number = n; | |
b1cff3a4 | 49 | bus->sibling = NULL; |
c7a34993 MM |
50 | bus->first_dev = NULL; |
51 | bus->last_dev = &bus->first_dev; | |
6b056c8e | 52 | bus->parent_bridge = b; |
b1cff3a4 PR |
53 | if (b->last_bus) |
54 | b->last_bus->sibling = bus; | |
55 | b->last_bus = bus; | |
56 | if (!b->first_bus) | |
57 | b->first_bus = bus; | |
c7a34993 MM |
58 | return bus; |
59 | } | |
60 | ||
61 | static void | |
62 | insert_dev(struct device *d, struct bridge *b) | |
63 | { | |
64 | struct pci_dev *p = d->dev; | |
67954c8b PR |
65 | struct device *parent = NULL; |
66 | struct bus *bus = NULL; | |
67 | ||
68 | if (p->known_fields & PCI_FILL_PARENT) | |
69 | parent = find_device(p->parent); | |
c7a34993 | 70 | |
67954c8b PR |
71 | if (parent && parent->bridge) |
72 | { | |
73 | bus = parent->bridge->first_bus; | |
74 | if (!bus) | |
75 | bus = new_bus(parent->bridge, p->domain, p->bus); | |
76 | } | |
77 | ||
7eb87449 PR |
78 | if (!bus && b == &host_bridge) |
79 | { | |
80 | for (b=b->child; b; b=b->prev) | |
81 | if (b->domain == (unsigned)p->domain) | |
82 | break; | |
83 | if (!b) | |
84 | b = &host_bridge; | |
85 | } | |
86 | ||
67954c8b | 87 | if (!bus && ! (bus = find_bus(b, p->domain, p->bus))) |
c7a34993 MM |
88 | { |
89 | struct bridge *c; | |
5b0411aa | 90 | for (c=b->child; c; c=c->prev) |
ab61451d | 91 | if (c->domain == (unsigned)p->domain && c->secondary <= p->bus && p->bus <= c->subordinate) |
c7a34993 MM |
92 | { |
93 | insert_dev(d, c); | |
94 | return; | |
95 | } | |
96 | bus = new_bus(b, p->domain, p->bus); | |
97 | } | |
98 | /* Simple insertion at the end _does_ guarantee the correct order as the | |
99 | * original device list was sorted by (domain, bus, devfn) lexicographically | |
100 | * and all devices on the new list have the same bus number. | |
101 | */ | |
102 | *bus->last_dev = d; | |
6b056c8e MM |
103 | bus->last_dev = &d->bus_next; |
104 | d->bus_next = NULL; | |
105 | d->parent_bus = bus; | |
c7a34993 MM |
106 | } |
107 | ||
6b056c8e | 108 | void |
c7a34993 MM |
109 | grow_tree(void) |
110 | { | |
6b056c8e | 111 | struct device *d; |
c7a34993 MM |
112 | struct bridge **last_br, *b; |
113 | ||
7eb87449 PR |
114 | last_br = &host_bridge.chain; |
115 | ||
116 | /* Build list of top level domain bridges */ | |
117 | ||
118 | for (d=first_dev; d; d=d->next) | |
119 | { | |
120 | for (b=host_bridge.chain; b; b=b->chain) | |
121 | if (b->domain == (unsigned)d->dev->domain) | |
122 | break; | |
123 | if (b) | |
124 | continue; | |
125 | b = xmalloc(sizeof(struct bridge)); | |
126 | b->domain = d->dev->domain; | |
127 | b->primary = ~0; | |
128 | b->secondary = 0; | |
129 | b->subordinate = ~0; | |
130 | *last_br = b; | |
131 | last_br = &b->chain; | |
6662052f | 132 | b->prev = b->next = b->child = NULL; |
7eb87449 PR |
133 | b->first_bus = NULL; |
134 | b->last_bus = NULL; | |
135 | b->br_dev = NULL; | |
136 | b->chain = NULL; | |
137 | pacc->debug("Tree: domain %04x\n", b->domain); | |
138 | } | |
139 | ||
c7a34993 MM |
140 | /* Build list of bridges */ |
141 | ||
c7a34993 MM |
142 | for (d=first_dev; d; d=d->next) |
143 | { | |
f558905d MM |
144 | struct pci_dev *dd = d->dev; |
145 | word class = dd->device_class; | |
832b07a8 | 146 | byte ht = d->no_config_access ? -1 : (get_conf_byte(d, PCI_HEADER_TYPE) & 0x7f); |
f558905d | 147 | if ((class >> 8) == PCI_BASE_CLASS_BRIDGE && |
c7a34993 MM |
148 | (ht == PCI_HEADER_TYPE_BRIDGE || ht == PCI_HEADER_TYPE_CARDBUS)) |
149 | { | |
150 | b = xmalloc(sizeof(struct bridge)); | |
f558905d | 151 | b->domain = dd->domain; |
c3c2e3cb | 152 | b->primary = dd->bus; |
c7a34993 MM |
153 | if (ht == PCI_HEADER_TYPE_BRIDGE) |
154 | { | |
c7a34993 MM |
155 | b->secondary = get_conf_byte(d, PCI_SECONDARY_BUS); |
156 | b->subordinate = get_conf_byte(d, PCI_SUBORDINATE_BUS); | |
157 | } | |
158 | else | |
159 | { | |
c7a34993 MM |
160 | b->secondary = get_conf_byte(d, PCI_CB_CARD_BUS); |
161 | b->subordinate = get_conf_byte(d, PCI_CB_SUBORDINATE_BUS); | |
162 | } | |
163 | *last_br = b; | |
164 | last_br = &b->chain; | |
6662052f | 165 | b->prev = b->next = b->child = NULL; |
c7a34993 | 166 | b->first_bus = NULL; |
b1cff3a4 | 167 | b->last_bus = NULL; |
c7a34993 | 168 | b->br_dev = d; |
6b056c8e | 169 | d->bridge = b; |
f558905d MM |
170 | pacc->debug("Tree: bridge %04x:%02x:%02x.%d: %02x -> %02x-%02x\n", |
171 | dd->domain, dd->bus, dd->dev, dd->func, | |
172 | b->primary, b->secondary, b->subordinate); | |
c7a34993 MM |
173 | } |
174 | } | |
67954c8b PR |
175 | |
176 | /* Append additional bridges reported by libpci via d->parent */ | |
177 | ||
178 | for (d=first_dev; d; d=d->next) | |
179 | { | |
180 | struct device *parent = NULL; | |
181 | if (d->dev->known_fields & PCI_FILL_PARENT) | |
182 | parent = find_device(d->dev->parent); | |
183 | if (!parent || parent->bridge) | |
184 | continue; | |
185 | b = xmalloc(sizeof(struct bridge)); | |
186 | b->domain = parent->dev->domain; | |
187 | b->primary = parent->dev->bus; | |
188 | b->secondary = d->dev->bus; | |
189 | /* At this stage subordinate number is unknown, so set it to secondary bus number. */ | |
190 | b->subordinate = b->secondary; | |
191 | *last_br = b; | |
192 | last_br = &b->chain; | |
6662052f | 193 | b->prev = b->next = b->child = NULL; |
67954c8b PR |
194 | b->first_bus = NULL; |
195 | b->last_bus = NULL; | |
196 | b->br_dev = parent; | |
197 | parent->bridge = b; | |
198 | pacc->debug("Tree: bridge %04x:%02x:%02x.%d\n", b->domain, | |
199 | parent->dev->bus, parent->dev->dev, parent->dev->func); | |
200 | } | |
c7a34993 MM |
201 | *last_br = NULL; |
202 | ||
203 | /* Create a bridge tree */ | |
204 | ||
7eb87449 | 205 | for (b=host_bridge.chain; b; b=b->chain) |
c7a34993 | 206 | { |
67954c8b PR |
207 | struct device *br_dev = b->br_dev; |
208 | struct bridge *c, *best = NULL; | |
209 | struct device *parent = NULL; | |
210 | ||
211 | if (br_dev && (br_dev->dev->known_fields & PCI_FILL_PARENT)) | |
212 | parent = find_device(br_dev->dev->parent); | |
213 | if (parent) | |
214 | best = parent->bridge; | |
215 | if (!best) | |
c7a34993 MM |
216 | for (c=&host_bridge; c; c=c->chain) |
217 | if (c != b && (c == &host_bridge || b->domain == c->domain) && | |
218 | b->primary >= c->secondary && b->primary <= c->subordinate && | |
7eb87449 | 219 | (!best || best == &host_bridge || best->subordinate - best->primary > c->subordinate - c->primary)) |
c7a34993 MM |
220 | best = c; |
221 | if (best) | |
222 | { | |
5b0411aa | 223 | b->prev = best->child; |
c7a34993 MM |
224 | best->child = b; |
225 | } | |
226 | } | |
227 | ||
228 | /* Insert secondary bus for each bridge */ | |
229 | ||
7eb87449 PR |
230 | for (b=host_bridge.chain; b; b=b->chain) |
231 | if (b->br_dev && !find_bus(b, b->domain, b->secondary)) | |
c7a34993 MM |
232 | new_bus(b, b->domain, b->secondary); |
233 | ||
234 | /* Create bus structs and link devices */ | |
235 | ||
6b056c8e MM |
236 | for (d=first_dev; d; d=d->next) |
237 | insert_dev(d, &host_bridge); | |
c7a34993 MM |
238 | } |
239 | ||
d1b22bb0 MM |
240 | #define LINE_BUF_SIZE 1024 |
241 | ||
c7a34993 MM |
242 | static void |
243 | print_it(char *line, char *p) | |
244 | { | |
c7a34993 MM |
245 | *p = 0; |
246 | fputs(line, stdout); | |
d1b22bb0 MM |
247 | if (p >= line + LINE_BUF_SIZE - 1) |
248 | fputs("...", stdout); | |
249 | putchar('\n'); | |
c7a34993 MM |
250 | for (p=line; *p; p++) |
251 | if (*p == '+' || *p == '|') | |
252 | *p = '|'; | |
253 | else | |
254 | *p = ' '; | |
255 | } | |
256 | ||
82dfc667 | 257 | static void show_tree_bridge(struct pci_filter *filter, struct bridge *, char *, char *); |
c7a34993 | 258 | |
76d47191 MM |
259 | static char * FORMAT_CHECK(printf, 3, 4) |
260 | tree_printf(char *line, char *p, char *fmt, ...) | |
261 | { | |
262 | va_list args; | |
d1b22bb0 | 263 | int space = line + LINE_BUF_SIZE - 1 - p; |
76d47191 | 264 | |
d1b22bb0 | 265 | if (space <= 0) |
76d47191 MM |
266 | return p; |
267 | ||
268 | va_start(args, fmt); | |
d1b22bb0 | 269 | int res = vsnprintf(p, space, fmt, args); |
76d47191 MM |
270 | if (res < 0) |
271 | { | |
d1b22bb0 MM |
272 | /* Ancient C libraries return -1 on overflow and they do not truncate the output properly. */ |
273 | *p = 0; | |
274 | p += space; | |
76d47191 | 275 | } |
d1b22bb0 | 276 | else if (res >= space) |
7eb8b947 PR |
277 | { |
278 | /* Ancient C libraries do not truncate the output properly. */ | |
279 | *(p+space-1) = 0; | |
280 | p += space; | |
281 | } | |
76d47191 MM |
282 | else |
283 | p += res; | |
284 | ||
285 | va_end(args); | |
286 | return p; | |
287 | } | |
288 | ||
c7a34993 | 289 | static void |
82dfc667 | 290 | show_tree_dev(struct pci_filter *filter, struct device *d, char *line, char *p) |
c7a34993 MM |
291 | { |
292 | struct pci_dev *q = d->dev; | |
293 | struct bridge *b; | |
294 | char namebuf[256]; | |
295 | ||
76d47191 | 296 | p = tree_printf(line, p, "%02x.%x", q->dev, q->func); |
7eb87449 | 297 | for (b=host_bridge.chain; b; b=b->chain) |
c7a34993 MM |
298 | if (b->br_dev == d) |
299 | { | |
fd9c6a29 PR |
300 | if (b->secondary == 0) |
301 | p = tree_printf(line, p, "-"); | |
302 | else if (b->secondary == b->subordinate) | |
76d47191 | 303 | p = tree_printf(line, p, "-[%02x]-", b->secondary); |
c7a34993 | 304 | else |
76d47191 | 305 | p = tree_printf(line, p, "-[%02x-%02x]-", b->secondary, b->subordinate); |
82dfc667 | 306 | show_tree_bridge(filter, b, line, p); |
c7a34993 MM |
307 | return; |
308 | } | |
309 | if (verbose) | |
76d47191 MM |
310 | p = tree_printf(line, p, " %s", |
311 | pci_lookup_name(pacc, namebuf, sizeof(namebuf), | |
312 | PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE, | |
313 | q->vendor_id, q->device_id)); | |
c7a34993 MM |
314 | print_it(line, p); |
315 | } | |
316 | ||
f0aa3a46 PR |
317 | static struct pci_filter * |
318 | get_filter_for_child(struct pci_filter *filter, struct device *d) | |
319 | { | |
320 | if (!filter) | |
321 | return NULL; | |
322 | ||
323 | if (pci_filter_match(filter, d->dev)) | |
324 | return NULL; | |
325 | ||
326 | return filter; | |
327 | } | |
328 | ||
82dfc667 PR |
329 | static int |
330 | check_bus_filter(struct pci_filter *filter, struct bus *b); | |
331 | ||
332 | static int | |
333 | check_dev_filter(struct pci_filter *filter, struct device *d) | |
334 | { | |
335 | struct bridge *br; | |
336 | struct bus *b; | |
337 | ||
338 | if (!filter) | |
339 | return 1; | |
340 | ||
341 | if (pci_filter_match(filter, d->dev)) | |
342 | return 1; | |
343 | ||
7eb87449 | 344 | for (br = host_bridge.chain; br; br = br->chain) |
82dfc667 PR |
345 | if (br->br_dev == d) |
346 | { | |
347 | for (b = br->first_bus; b; b = b->sibling) | |
348 | if (check_bus_filter(filter, b)) | |
349 | return 1; | |
350 | break; | |
351 | } | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
356 | static int | |
357 | check_bus_filter(struct pci_filter *filter, struct bus *b) | |
358 | { | |
359 | struct device *d; | |
360 | ||
361 | if (!filter) | |
362 | return 1; | |
363 | ||
364 | for (d = b->first_dev; d; d = d->bus_next) | |
365 | if (check_dev_filter(filter, d)) | |
366 | return 1; | |
367 | ||
368 | return 0; | |
369 | } | |
370 | ||
c7a34993 | 371 | static void |
82dfc667 | 372 | show_tree_bus(struct pci_filter *filter, struct bus *b, char *line, char *p) |
c7a34993 MM |
373 | { |
374 | if (!b->first_dev) | |
375 | print_it(line, p); | |
6b056c8e | 376 | else if (!b->first_dev->bus_next) |
c7a34993 | 377 | { |
82dfc667 PR |
378 | if (check_dev_filter(filter, b->first_dev)) |
379 | { | |
380 | p = tree_printf(line, p, "--"); | |
f0aa3a46 | 381 | show_tree_dev(get_filter_for_child(filter, b->first_dev), b->first_dev, line, p); |
82dfc667 PR |
382 | } |
383 | else | |
384 | print_it(line, p); | |
c7a34993 MM |
385 | } |
386 | else | |
387 | { | |
f0aa3a46 | 388 | int i, count = 0; |
c7a34993 | 389 | struct device *d = b->first_dev; |
f0aa3a46 PR |
390 | |
391 | do | |
392 | { | |
393 | if (check_dev_filter(filter, d)) | |
394 | count++; | |
395 | d = d->bus_next; | |
396 | } | |
397 | while (d); | |
398 | ||
399 | for (i = 0, d = b->first_dev; d; d = d->bus_next) | |
82dfc667 | 400 | { |
f0aa3a46 PR |
401 | if (!check_dev_filter(filter, d)) |
402 | continue; | |
403 | char *p2 = tree_printf(line, p, count == 1 ? "--" : count == i+1 ? "\\-" : "+-"); | |
404 | show_tree_dev(get_filter_for_child(filter, d), d, line, p2); | |
405 | i++; | |
82dfc667 | 406 | } |
f0aa3a46 PR |
407 | |
408 | if (count == 0) | |
82dfc667 | 409 | print_it(line, p); |
c7a34993 MM |
410 | } |
411 | } | |
412 | ||
413 | static void | |
82dfc667 | 414 | show_tree_bridge(struct pci_filter *filter, struct bridge *b, char *line, char *p) |
c7a34993 MM |
415 | { |
416 | *p++ = '-'; | |
417 | if (!b->first_bus->sibling) | |
418 | { | |
82dfc667 PR |
419 | if (check_bus_filter(filter, b->first_bus)) |
420 | { | |
7eb87449 PR |
421 | if (!b->br_dev) |
422 | p = tree_printf(line, p, "[%04x:%02x]-", b->first_bus->domain, b->first_bus->number); | |
82dfc667 PR |
423 | show_tree_bus(filter, b->first_bus, line, p); |
424 | } | |
425 | else | |
426 | print_it(line, p); | |
c7a34993 MM |
427 | } |
428 | else | |
429 | { | |
f0aa3a46 | 430 | int i, count = 0; |
c7a34993 MM |
431 | struct bus *u = b->first_bus; |
432 | char *k; | |
433 | ||
f0aa3a46 | 434 | do |
c7a34993 | 435 | { |
82dfc667 | 436 | if (check_bus_filter(filter, u)) |
f0aa3a46 | 437 | count++; |
c7a34993 MM |
438 | u = u->sibling; |
439 | } | |
f0aa3a46 PR |
440 | while (u); |
441 | ||
442 | for (i = 0, u = b->first_bus; u; u = u->sibling) | |
82dfc667 | 443 | { |
f0aa3a46 PR |
444 | if (!check_bus_filter(filter, u)) |
445 | continue; | |
446 | k = tree_printf(line, p, count == 1 ? "[%04x:%02x]-" : count == i+1 ? "\\-[%04x:%02x]-" : "+-[%04x:%02x]-", u->domain, u->number); | |
82dfc667 | 447 | show_tree_bus(filter, u, line, k); |
f0aa3a46 | 448 | i++; |
82dfc667 | 449 | } |
f0aa3a46 PR |
450 | |
451 | if (count == 0) | |
82dfc667 | 452 | print_it(line, p); |
c7a34993 MM |
453 | } |
454 | } | |
455 | ||
456 | void | |
888ddf0e | 457 | show_forest(struct pci_filter *filter) |
c7a34993 | 458 | { |
76d47191 | 459 | char line[LINE_BUF_SIZE]; |
7eb87449 PR |
460 | struct bridge *b; |
461 | if (host_bridge.child) | |
462 | { | |
463 | for (b=host_bridge.child; b->prev; b=b->prev) | |
6662052f PR |
464 | b->prev->next = b; |
465 | for (; b; b=b->next) | |
7eb87449 PR |
466 | show_tree_bridge(filter, b, line, line); |
467 | } | |
c7a34993 | 468 | } |