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