1 |
|
|
/* $OpenBSD: pcidump.c,v 1.46 2017/08/31 12:03:02 otto Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2006, 2007 David Gwynne <loki@animata.net> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/ioctl.h> |
21 |
|
|
#include <sys/pciio.h> |
22 |
|
|
|
23 |
|
|
#include <stdio.h> /* need NULL for dev/pci/ headers */ |
24 |
|
|
|
25 |
|
|
#include <dev/pci/pcireg.h> |
26 |
|
|
#include <dev/pci/pcidevs.h> |
27 |
|
|
#include <dev/pci/pcidevs_data.h> |
28 |
|
|
|
29 |
|
|
#include <err.h> |
30 |
|
|
#include <errno.h> |
31 |
|
|
#include <fcntl.h> |
32 |
|
|
#include <paths.h> |
33 |
|
|
#include <stdlib.h> |
34 |
|
|
#include <string.h> |
35 |
|
|
#include <unistd.h> |
36 |
|
|
#include <limits.h> |
37 |
|
|
|
38 |
|
|
#define PCIDEV "/dev/pci" |
39 |
|
|
|
40 |
|
|
#ifndef nitems |
41 |
|
|
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) |
42 |
|
|
#endif |
43 |
|
|
|
44 |
|
|
__dead void usage(void); |
45 |
|
|
void scanpcidomain(void); |
46 |
|
|
int probe(int, int, int); |
47 |
|
|
void dump(int, int, int); |
48 |
|
|
void hexdump(int, int, int, int); |
49 |
|
|
const char *str2busdevfunc(const char *, int *, int *, int *); |
50 |
|
|
int pci_nfuncs(int, int); |
51 |
|
|
int pci_read(int, int, int, u_int32_t, u_int32_t *); |
52 |
|
|
int pci_readmask(int, int, int, u_int32_t, u_int32_t *); |
53 |
|
|
void dump_caplist(int, int, int, u_int8_t); |
54 |
|
|
void dump_pci_powerstate(int, int, int, uint8_t); |
55 |
|
|
void dump_pcie_linkspeed(int, int, int, uint8_t); |
56 |
|
|
void print_pcie_ls(uint8_t); |
57 |
|
|
int dump_rom(int, int, int); |
58 |
|
|
int dump_vga_bios(void); |
59 |
|
|
|
60 |
|
|
void dump_type0(int bus, int dev, int func); |
61 |
|
|
void dump_type1(int bus, int dev, int func); |
62 |
|
|
void dump_type2(int bus, int dev, int func); |
63 |
|
|
|
64 |
|
|
__dead void |
65 |
|
|
usage(void) |
66 |
|
|
{ |
67 |
|
|
extern char *__progname; |
68 |
|
|
|
69 |
|
|
fprintf(stderr, |
70 |
|
|
"usage: %s [-v] [-x | -xx | -xxx] [-d pcidev] [bus:dev:func]\n" |
71 |
|
|
" %s -r file [-d pcidev] bus:dev:func\n", |
72 |
|
|
__progname, __progname); |
73 |
|
|
exit(1); |
74 |
|
|
} |
75 |
|
|
|
76 |
|
|
int pcifd; |
77 |
|
|
int romfd; |
78 |
|
|
int verbose = 0; |
79 |
|
|
int hex = 0; |
80 |
|
|
int size = 64; |
81 |
|
|
|
82 |
|
|
const char *pci_capnames[] = { |
83 |
|
|
"Reserved", |
84 |
|
|
"Power Management", |
85 |
|
|
"AGP", |
86 |
|
|
"Vital Product Data (VPD)", |
87 |
|
|
"Slot Identification", |
88 |
|
|
"Message Signalled Interrupts (MSI)", |
89 |
|
|
"CompactPCI Hot Swap", |
90 |
|
|
"PCI-X", |
91 |
|
|
"AMD LDT/HT", |
92 |
|
|
"Vendor Specific", |
93 |
|
|
"Debug Port", |
94 |
|
|
"CompactPCI Central Resource Control", |
95 |
|
|
"PCI Hot-Plug", |
96 |
|
|
"PCI-PCI", |
97 |
|
|
"AGP8", |
98 |
|
|
"Secure", |
99 |
|
|
"PCI Express", |
100 |
|
|
"Extended Message Signalled Interrupts (MSI-X)", |
101 |
|
|
"SATA", |
102 |
|
|
"PCI Advanced Features" |
103 |
|
|
}; |
104 |
|
|
|
105 |
|
|
const char *pci_enhanced_capnames[] = { |
106 |
|
|
"Unknown", |
107 |
|
|
"Advanced Error Reporting", |
108 |
|
|
"Virtual Channel Capability", |
109 |
|
|
"Device Serial Number", |
110 |
|
|
"Power Budgeting", |
111 |
|
|
"Root Complex Link Declaration", |
112 |
|
|
"Root Complex Internal Link Control", |
113 |
|
|
"Root Complex Event Collector", |
114 |
|
|
"Multi-Function VC Capability", |
115 |
|
|
"Virtual Channel Capability", |
116 |
|
|
"Root Complex/Root Bridge", |
117 |
|
|
"Vendor-Specific", |
118 |
|
|
"Config Access", |
119 |
|
|
"Access Control Services", |
120 |
|
|
"Alternate Routing ID", |
121 |
|
|
"Address Translation Services", |
122 |
|
|
"Single Root I/O Virtualization", |
123 |
|
|
"Multi Root I/O Virtualization", |
124 |
|
|
"Multicast", |
125 |
|
|
"Page Request Interface", |
126 |
|
|
"Reserved for AMD", |
127 |
|
|
"Resizable BAR", |
128 |
|
|
"Dynamic Power Allocation", |
129 |
|
|
"TPH Requester", |
130 |
|
|
"Latency Tolerance Reporting", |
131 |
|
|
"Secondary PCIe Capability", |
132 |
|
|
"Protocol Multiplexing", |
133 |
|
|
"Process Address Space ID", |
134 |
|
|
"LN Requester", |
135 |
|
|
"Downstream Port Containment", |
136 |
|
|
"L1 PM", |
137 |
|
|
"Precision Time Measurement", |
138 |
|
|
}; |
139 |
|
|
|
140 |
|
|
int |
141 |
|
|
main(int argc, char *argv[]) |
142 |
|
|
{ |
143 |
|
|
int nfuncs; |
144 |
|
|
int bus, dev, func; |
145 |
|
|
char pcidev[PATH_MAX] = PCIDEV; |
146 |
|
|
char *romfile = NULL; |
147 |
|
|
const char *errstr; |
148 |
|
|
int c, error = 0, dumpall = 1, domid = 0; |
149 |
|
|
|
150 |
|
|
while ((c = getopt(argc, argv, "d:r:vx")) != -1) { |
151 |
|
|
switch (c) { |
152 |
|
|
case 'd': |
153 |
|
|
strlcpy(pcidev, optarg, sizeof(pcidev)); |
154 |
|
|
dumpall = 0; |
155 |
|
|
break; |
156 |
|
|
case 'r': |
157 |
|
|
romfile = optarg; |
158 |
|
|
dumpall = 0; |
159 |
|
|
break; |
160 |
|
|
case 'v': |
161 |
|
|
verbose = 1; |
162 |
|
|
break; |
163 |
|
|
case 'x': |
164 |
|
|
hex++; |
165 |
|
|
break; |
166 |
|
|
default: |
167 |
|
|
usage(); |
168 |
|
|
} |
169 |
|
|
} |
170 |
|
|
argc -= optind; |
171 |
|
|
argv += optind; |
172 |
|
|
|
173 |
|
|
if (argc > 1 || (romfile && argc != 1)) |
174 |
|
|
usage(); |
175 |
|
|
|
176 |
|
|
if (romfile) { |
177 |
|
|
romfd = open(romfile, O_WRONLY|O_CREAT|O_TRUNC, 0777); |
178 |
|
|
if (romfd == -1) |
179 |
|
|
err(1, "%s", romfile); |
180 |
|
|
} |
181 |
|
|
|
182 |
|
|
if (hex > 1) |
183 |
|
|
size = 256; |
184 |
|
|
if (hex > 2) |
185 |
|
|
size = 4096; |
186 |
|
|
|
187 |
|
|
if (argc == 1) |
188 |
|
|
dumpall = 0; |
189 |
|
|
|
190 |
|
|
if (dumpall == 0) { |
191 |
|
|
pcifd = open(pcidev, O_RDONLY, 0777); |
192 |
|
|
if (pcifd == -1) |
193 |
|
|
err(1, "%s", pcidev); |
194 |
|
|
} else { |
195 |
|
|
for (;;) { |
196 |
|
|
snprintf(pcidev, 16, "/dev/pci%d", domid++); |
197 |
|
|
pcifd = open(pcidev, O_RDONLY, 0777); |
198 |
|
|
if (pcifd == -1) { |
199 |
|
|
if (errno == ENXIO || errno == ENOENT) { |
200 |
|
|
return 0; |
201 |
|
|
} else { |
202 |
|
|
err(1, "%s", pcidev); |
203 |
|
|
} |
204 |
|
|
} |
205 |
|
|
printf("Domain %s:\n", pcidev); |
206 |
|
|
scanpcidomain(); |
207 |
|
|
close(pcifd); |
208 |
|
|
} |
209 |
|
|
} |
210 |
|
|
|
211 |
|
|
if (argc == 1) { |
212 |
|
|
errstr = str2busdevfunc(argv[0], &bus, &dev, &func); |
213 |
|
|
if (errstr != NULL) |
214 |
|
|
errx(1, "\"%s\": %s", argv[0], errstr); |
215 |
|
|
|
216 |
|
|
nfuncs = pci_nfuncs(bus, dev); |
217 |
|
|
if (nfuncs == -1 || func > nfuncs) |
218 |
|
|
error = ENXIO; |
219 |
|
|
else if (romfile) |
220 |
|
|
error = dump_rom(bus, dev, func); |
221 |
|
|
else |
222 |
|
|
error = probe(bus, dev, func); |
223 |
|
|
|
224 |
|
|
if (error != 0) |
225 |
|
|
errc(1, error, "\"%s\"", argv[0]); |
226 |
|
|
} else { |
227 |
|
|
printf("Domain %s:\n", pcidev); |
228 |
|
|
scanpcidomain(); |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
return (0); |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
void |
235 |
|
|
scanpcidomain(void) |
236 |
|
|
{ |
237 |
|
|
int nfuncs; |
238 |
|
|
int bus, dev, func; |
239 |
|
|
|
240 |
|
|
for (bus = 0; bus < 256; bus++) { |
241 |
|
|
for (dev = 0; dev < 32; dev++) { |
242 |
|
|
nfuncs = pci_nfuncs(bus, dev); |
243 |
|
|
for (func = 0; func < nfuncs; func++) { |
244 |
|
|
probe(bus, dev, func); |
245 |
|
|
} |
246 |
|
|
} |
247 |
|
|
} |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
const char * |
251 |
|
|
str2busdevfunc(const char *string, int *bus, int *dev, int *func) |
252 |
|
|
{ |
253 |
|
|
const char *errstr; |
254 |
|
|
char b[80], *d, *f; |
255 |
|
|
|
256 |
|
|
strlcpy(b, string, sizeof(b)); |
257 |
|
|
|
258 |
|
|
d = strchr(b, ':'); |
259 |
|
|
if (d == NULL) |
260 |
|
|
return("device not specified"); |
261 |
|
|
*d++ = '\0'; |
262 |
|
|
|
263 |
|
|
f = strchr(d, ':'); |
264 |
|
|
if (f == NULL) |
265 |
|
|
return("function not specified"); |
266 |
|
|
*f++ = '\0'; |
267 |
|
|
|
268 |
|
|
*bus = strtonum(b, 0, 255, &errstr); |
269 |
|
|
if (errstr != NULL) |
270 |
|
|
return (errstr); |
271 |
|
|
*dev = strtonum(d, 0, 31, &errstr); |
272 |
|
|
if (errstr != NULL) |
273 |
|
|
return (errstr); |
274 |
|
|
*func = strtonum(f, 0, 7, &errstr); |
275 |
|
|
if (errstr != NULL) |
276 |
|
|
return (errstr); |
277 |
|
|
|
278 |
|
|
return (NULL); |
279 |
|
|
} |
280 |
|
|
|
281 |
|
|
int |
282 |
|
|
probe(int bus, int dev, int func) |
283 |
|
|
{ |
284 |
|
|
u_int32_t id_reg; |
285 |
|
|
const struct pci_known_vendor *pkv; |
286 |
|
|
const struct pci_known_product *pkp; |
287 |
|
|
const char *vendor = NULL, *product = NULL; |
288 |
|
|
|
289 |
|
|
if (pci_read(bus, dev, func, PCI_ID_REG, &id_reg) != 0) |
290 |
|
|
return (errno); |
291 |
|
|
|
292 |
|
|
if (PCI_VENDOR(id_reg) == PCI_VENDOR_INVALID || |
293 |
|
|
PCI_VENDOR(id_reg) == 0) |
294 |
|
|
return (ENXIO); |
295 |
|
|
|
296 |
|
|
for (pkv = pci_known_vendors; pkv->vendorname != NULL; pkv++) { |
297 |
|
|
if (pkv->vendor == PCI_VENDOR(id_reg)) { |
298 |
|
|
vendor = pkv->vendorname; |
299 |
|
|
break; |
300 |
|
|
} |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
if (vendor != NULL) { |
304 |
|
|
for (pkp = pci_known_products; pkp->productname != NULL; pkp++) |
305 |
|
|
if (pkp->vendor == PCI_VENDOR(id_reg) && |
306 |
|
|
pkp->product == PCI_PRODUCT(id_reg)) { |
307 |
|
|
product = pkp->productname; |
308 |
|
|
break; |
309 |
|
|
} |
310 |
|
|
} |
311 |
|
|
|
312 |
|
|
printf(" %d:%d:%d: %s %s\n", bus, dev, func, |
313 |
|
|
(vendor == NULL) ? "unknown" : vendor, |
314 |
|
|
(product == NULL) ? "unknown" : product); |
315 |
|
|
|
316 |
|
|
if (verbose) |
317 |
|
|
dump(bus, dev, func); |
318 |
|
|
if (hex > 0) |
319 |
|
|
hexdump(bus, dev, func, size); |
320 |
|
|
|
321 |
|
|
return (0); |
322 |
|
|
} |
323 |
|
|
|
324 |
|
|
void |
325 |
|
|
dump_pci_powerstate(int bus, int dev, int func, uint8_t ptr) |
326 |
|
|
{ |
327 |
|
|
u_int32_t pmcsr; |
328 |
|
|
|
329 |
|
|
if (pci_read(bus, dev, func, ptr + PCI_PMCSR, &pmcsr) != 0) |
330 |
|
|
return; |
331 |
|
|
|
332 |
|
|
printf("\t State: D%d", pmcsr & PCI_PMCSR_STATE_MASK); |
333 |
|
|
if (pmcsr & PCI_PMCSR_PME_EN) |
334 |
|
|
printf(" PME# enabled"); |
335 |
|
|
if (pmcsr & PCI_PMCSR_PME_STATUS) |
336 |
|
|
printf(" PME# asserted"); |
337 |
|
|
printf("\n"); |
338 |
|
|
} |
339 |
|
|
|
340 |
|
|
void |
341 |
|
|
print_pcie_ls(uint8_t speed) |
342 |
|
|
{ |
343 |
|
|
if (speed & 4) |
344 |
|
|
printf("8.0"); |
345 |
|
|
else if (speed & 2) |
346 |
|
|
printf("5.0"); |
347 |
|
|
else if (speed & 1) |
348 |
|
|
printf("2.5"); |
349 |
|
|
else |
350 |
|
|
printf("unknown (%d)", speed); |
351 |
|
|
} |
352 |
|
|
|
353 |
|
|
void |
354 |
|
|
dump_pcie_linkspeed(int bus, int dev, int func, uint8_t ptr) |
355 |
|
|
{ |
356 |
|
|
u_int32_t lcap, sreg, lcap2 = 0, xcap; |
357 |
|
|
u_int8_t cwidth, cspeed, swidth, sspeed; |
358 |
|
|
|
359 |
|
|
if (pci_read(bus, dev, func, ptr + PCI_PCIE_XCAP, &xcap) != 0) |
360 |
|
|
return; |
361 |
|
|
|
362 |
|
|
if (PCI_PCIE_XCAP_VER(xcap) >= 2) { |
363 |
|
|
if (pci_read(bus, dev, func, ptr + PCI_PCIE_LCAP2, &lcap2) != 0) |
364 |
|
|
lcap2 = 0; |
365 |
|
|
else |
366 |
|
|
cspeed = (lcap2 & 0x0e) >> 1; |
367 |
|
|
} |
368 |
|
|
|
369 |
|
|
if (pci_read(bus, dev, func, ptr + PCI_PCIE_LCAP, &lcap) != 0) |
370 |
|
|
return; |
371 |
|
|
if (lcap2 == 0) |
372 |
|
|
cspeed = lcap & 0x0f; |
373 |
|
|
|
374 |
|
|
if (pci_read(bus, dev, func, ptr + PCI_PCIE_LCSR, &sreg) != 0) |
375 |
|
|
return; |
376 |
|
|
sreg = sreg >> 16; |
377 |
|
|
|
378 |
|
|
cwidth = (lcap >> 4) & 0x3f; |
379 |
|
|
if (cwidth == 0) |
380 |
|
|
return; |
381 |
|
|
|
382 |
|
|
swidth = (sreg >> 4) & 0x3f; |
383 |
|
|
sspeed = sreg & 0x0f; |
384 |
|
|
|
385 |
|
|
printf("\t Link Speed: "); |
386 |
|
|
print_pcie_ls(sspeed); |
387 |
|
|
printf(" / "); |
388 |
|
|
print_pcie_ls(cspeed); |
389 |
|
|
|
390 |
|
|
printf(" GT/s Link Width: x%d / x%d\n", swidth, cwidth); |
391 |
|
|
} |
392 |
|
|
|
393 |
|
|
void |
394 |
|
|
dump_pcie_enhanced_caplist(int bus, int dev, int func) |
395 |
|
|
{ |
396 |
|
|
u_int32_t reg; |
397 |
|
|
u_int32_t capidx; |
398 |
|
|
u_int16_t ptr; |
399 |
|
|
u_int16_t ecap; |
400 |
|
|
|
401 |
|
|
ptr = PCI_PCIE_ECAP; |
402 |
|
|
|
403 |
|
|
do { |
404 |
|
|
if (pci_read(bus, dev, func, ptr, ®) != 0) |
405 |
|
|
return; |
406 |
|
|
|
407 |
|
|
if (PCI_PCIE_ECAP_ID(reg) == 0xffff && |
408 |
|
|
PCI_PCIE_ECAP_NEXT(reg) == PCI_PCIE_ECAP_LAST) |
409 |
|
|
return; |
410 |
|
|
|
411 |
|
|
ecap = PCI_PCIE_ECAP_ID(reg); |
412 |
|
|
if (ecap >= nitems(pci_enhanced_capnames)) |
413 |
|
|
capidx = 0; |
414 |
|
|
else |
415 |
|
|
capidx = ecap; |
416 |
|
|
|
417 |
|
|
printf("\t0x%04x: Enhanced Capability 0x%02x: ", ptr, ecap); |
418 |
|
|
printf("%s\n", pci_enhanced_capnames[capidx]); |
419 |
|
|
|
420 |
|
|
ptr = PCI_PCIE_ECAP_NEXT(reg); |
421 |
|
|
|
422 |
|
|
} while (ptr != PCI_PCIE_ECAP_LAST); |
423 |
|
|
} |
424 |
|
|
|
425 |
|
|
void |
426 |
|
|
dump_caplist(int bus, int dev, int func, u_int8_t ptr) |
427 |
|
|
{ |
428 |
|
|
u_int32_t reg; |
429 |
|
|
u_int8_t cap; |
430 |
|
|
|
431 |
|
|
if (pci_read(bus, dev, func, PCI_COMMAND_STATUS_REG, ®) != 0) |
432 |
|
|
return; |
433 |
|
|
if (!(reg & PCI_STATUS_CAPLIST_SUPPORT)) |
434 |
|
|
return; |
435 |
|
|
|
436 |
|
|
if (pci_read(bus, dev, func, ptr, ®) != 0) |
437 |
|
|
return; |
438 |
|
|
ptr = PCI_CAPLIST_PTR(reg); |
439 |
|
|
while (ptr != 0) { |
440 |
|
|
if (pci_read(bus, dev, func, ptr, ®) != 0) |
441 |
|
|
return; |
442 |
|
|
cap = PCI_CAPLIST_CAP(reg); |
443 |
|
|
printf("\t0x%04x: Capability 0x%02x: ", ptr, cap); |
444 |
|
|
if (cap >= nitems(pci_capnames)) |
445 |
|
|
cap = 0; |
446 |
|
|
printf("%s\n", pci_capnames[cap]); |
447 |
|
|
if (cap == PCI_CAP_PWRMGMT) |
448 |
|
|
dump_pci_powerstate(bus, dev, func, ptr); |
449 |
|
|
if (cap == PCI_CAP_PCIEXPRESS) { |
450 |
|
|
dump_pcie_linkspeed(bus, dev, func, ptr); |
451 |
|
|
dump_pcie_enhanced_caplist(bus, dev, func); |
452 |
|
|
} |
453 |
|
|
ptr = PCI_CAPLIST_NEXT(reg); |
454 |
|
|
} |
455 |
|
|
} |
456 |
|
|
|
457 |
|
|
void |
458 |
|
|
dump_type0(int bus, int dev, int func) |
459 |
|
|
{ |
460 |
|
|
const char *memtype; |
461 |
|
|
u_int64_t mem; |
462 |
|
|
u_int64_t mask; |
463 |
|
|
u_int32_t reg, reg1; |
464 |
|
|
int bar; |
465 |
|
|
|
466 |
|
|
for (bar = PCI_MAPREG_START; bar < PCI_MAPREG_END; bar += 0x4) { |
467 |
|
|
if (pci_read(bus, dev, func, bar, ®) != 0 || |
468 |
|
|
pci_readmask(bus, dev, func, bar, ®1) != 0) |
469 |
|
|
warn("unable to read PCI_MAPREG 0x%02x", bar); |
470 |
|
|
|
471 |
|
|
printf("\t0x%04x: BAR ", bar); |
472 |
|
|
|
473 |
|
|
if (reg == 0 && reg1 == 0) { |
474 |
|
|
printf("empty (%08x)\n", reg); |
475 |
|
|
continue; |
476 |
|
|
} |
477 |
|
|
|
478 |
|
|
switch (PCI_MAPREG_TYPE(reg)) { |
479 |
|
|
case PCI_MAPREG_TYPE_MEM: |
480 |
|
|
printf("mem "); |
481 |
|
|
if (PCI_MAPREG_MEM_PREFETCHABLE(reg)) |
482 |
|
|
printf("prefetchable "); |
483 |
|
|
|
484 |
|
|
memtype = "32bit 1m"; |
485 |
|
|
switch (PCI_MAPREG_MEM_TYPE(reg)) { |
486 |
|
|
case PCI_MAPREG_MEM_TYPE_32BIT: |
487 |
|
|
memtype = "32bit"; |
488 |
|
|
case PCI_MAPREG_MEM_TYPE_32BIT_1M: |
489 |
|
|
printf("%s ", memtype); |
490 |
|
|
|
491 |
|
|
printf("addr: 0x%08x/0x%08x\n", |
492 |
|
|
PCI_MAPREG_MEM_ADDR(reg), |
493 |
|
|
PCI_MAPREG_MEM_SIZE(reg1)); |
494 |
|
|
|
495 |
|
|
break; |
496 |
|
|
case PCI_MAPREG_MEM_TYPE_64BIT: |
497 |
|
|
mem = reg; |
498 |
|
|
mask = reg1; |
499 |
|
|
bar += 0x04; |
500 |
|
|
if (pci_read(bus, dev, func, bar, ®) != 0 || |
501 |
|
|
pci_readmask(bus, dev, func, bar, ®1) != 0) |
502 |
|
|
warn("unable to read 0x%02x", bar); |
503 |
|
|
|
504 |
|
|
mem |= (u_int64_t)reg << 32; |
505 |
|
|
mask |= (u_int64_t)reg1 << 32; |
506 |
|
|
|
507 |
|
|
printf("64bit addr: 0x%016llx/0x%08llx\n", |
508 |
|
|
PCI_MAPREG_MEM64_ADDR(mem), |
509 |
|
|
PCI_MAPREG_MEM64_SIZE(mask)); |
510 |
|
|
|
511 |
|
|
break; |
512 |
|
|
} |
513 |
|
|
break; |
514 |
|
|
|
515 |
|
|
case PCI_MAPREG_TYPE_IO: |
516 |
|
|
printf("io addr: 0x%08x/0x%04x\n", |
517 |
|
|
PCI_MAPREG_IO_ADDR(reg), |
518 |
|
|
PCI_MAPREG_IO_SIZE(reg1)); |
519 |
|
|
break; |
520 |
|
|
} |
521 |
|
|
} |
522 |
|
|
|
523 |
|
|
if (pci_read(bus, dev, func, PCI_CARDBUS_CIS_REG, ®) != 0) |
524 |
|
|
warn("unable to read PCI_CARDBUS_CIS_REG"); |
525 |
|
|
printf("\t0x%04x: Cardbus CIS: %08x\n", PCI_CARDBUS_CIS_REG, reg); |
526 |
|
|
|
527 |
|
|
if (pci_read(bus, dev, func, PCI_SUBSYS_ID_REG, ®) != 0) |
528 |
|
|
warn("unable to read PCI_SUBSYS_ID_REG"); |
529 |
|
|
printf("\t0x%04x: Subsystem Vendor ID: %04x Product ID: %04x\n", |
530 |
|
|
PCI_SUBSYS_ID_REG, PCI_VENDOR(reg), PCI_PRODUCT(reg)); |
531 |
|
|
|
532 |
|
|
if (pci_read(bus, dev, func, PCI_ROM_REG, ®) != 0) |
533 |
|
|
warn("unable to read PCI_ROM_REG"); |
534 |
|
|
printf("\t0x%04x: Expansion ROM Base Address: %08x\n", |
535 |
|
|
PCI_ROM_REG, reg); |
536 |
|
|
|
537 |
|
|
if (pci_read(bus, dev, func, 0x38, ®) != 0) |
538 |
|
|
warn("unable to read 0x38 (reserved)"); |
539 |
|
|
printf("\t0x%04x: %08x\n", 0x38, reg); |
540 |
|
|
|
541 |
|
|
if (pci_read(bus, dev, func, PCI_INTERRUPT_REG, ®) != 0) |
542 |
|
|
warn("unable to read PCI_INTERRUPT_REG"); |
543 |
|
|
printf("\t0x%04x: Interrupt Pin: %02x Line: %02x Min Gnt: %02x" |
544 |
|
|
" Max Lat: %02x\n", PCI_INTERRUPT_REG, PCI_INTERRUPT_PIN(reg), |
545 |
|
|
PCI_INTERRUPT_LINE(reg), PCI_MIN_GNT(reg), PCI_MAX_LAT(reg)); |
546 |
|
|
} |
547 |
|
|
|
548 |
|
|
void |
549 |
|
|
dump_type1(int bus, int dev, int func) |
550 |
|
|
{ |
551 |
|
|
u_int32_t reg; |
552 |
|
|
int bar; |
553 |
|
|
|
554 |
|
|
for (bar = PCI_MAPREG_START; bar < PCI_MAPREG_PPB_END; bar += 0x4) { |
555 |
|
|
if (pci_read(bus, dev, func, bar, ®) != 0) |
556 |
|
|
warn("unable to read PCI_MAPREG 0x%02x", bar); |
557 |
|
|
printf("\t0x%04x: %08x\n", bar, reg); |
558 |
|
|
} |
559 |
|
|
|
560 |
|
|
if (pci_read(bus, dev, func, PCI_PRIBUS_1, ®) != 0) |
561 |
|
|
warn("unable to read PCI_PRIBUS_1"); |
562 |
|
|
printf("\t0x%04x: Primary Bus: %d Secondary Bus: %d " |
563 |
|
|
"Subordinate Bus: %d \n\t Secondary Latency Timer: %02x\n", |
564 |
|
|
PCI_PRIBUS_1, (reg >> 0) & 0xff, (reg >> 8) & 0xff, |
565 |
|
|
(reg >> 16) & 0xff, (reg >> 24) & 0xff); |
566 |
|
|
|
567 |
|
|
if (pci_read(bus, dev, func, PCI_IOBASEL_1, ®) != 0) |
568 |
|
|
warn("unable to read PCI_IOBASEL_1"); |
569 |
|
|
printf("\t0x%04x: I/O Base: %02x I/O Limit: %02x " |
570 |
|
|
"Secondary Status: %04x\n", PCI_IOBASEL_1, (reg >> 0 ) & 0xff, |
571 |
|
|
(reg >> 8) & 0xff, (reg >> 16) & 0xffff); |
572 |
|
|
|
573 |
|
|
if (pci_read(bus, dev, func, PCI_MEMBASE_1, ®) != 0) |
574 |
|
|
warn("unable to read PCI_MEMBASE_1"); |
575 |
|
|
printf("\t0x%04x: Memory Base: %04x Memory Limit: %04x\n", |
576 |
|
|
PCI_MEMBASE_1, (reg >> 0) & 0xffff, (reg >> 16) & 0xffff); |
577 |
|
|
|
578 |
|
|
if (pci_read(bus, dev, func, PCI_PMBASEL_1, ®) != 0) |
579 |
|
|
warn("unable to read PCI_PMBASEL_1"); |
580 |
|
|
printf("\t0x%04x: Prefetch Memory Base: %04x " |
581 |
|
|
"Prefetch Memory Limit: %04x\n", PCI_PMBASEL_1, |
582 |
|
|
(reg >> 0) & 0xffff, (reg >> 16) & 0xffff); |
583 |
|
|
|
584 |
|
|
#undef PCI_PMBASEH_1 |
585 |
|
|
#define PCI_PMBASEH_1 0x28 |
586 |
|
|
if (pci_read(bus, dev, func, PCI_PMBASEH_1, ®) != 0) |
587 |
|
|
warn("unable to read PCI_PMBASEH_1"); |
588 |
|
|
printf("\t0x%04x: Prefetch Memory Base Upper 32 Bits: %08x\n", |
589 |
|
|
PCI_PMBASEH_1, reg); |
590 |
|
|
|
591 |
|
|
#undef PCI_PMLIMITH_1 |
592 |
|
|
#define PCI_PMLIMITH_1 0x2c |
593 |
|
|
if (pci_read(bus, dev, func, PCI_PMLIMITH_1, ®) != 0) |
594 |
|
|
warn("unable to read PCI_PMLIMITH_1"); |
595 |
|
|
printf("\t0x%04x: Prefetch Memory Limit Upper 32 Bits: %08x\n", |
596 |
|
|
PCI_PMLIMITH_1, reg); |
597 |
|
|
|
598 |
|
|
#undef PCI_IOBASEH_1 |
599 |
|
|
#define PCI_IOBASEH_1 0x30 |
600 |
|
|
if (pci_read(bus, dev, func, PCI_IOBASEH_1, ®) != 0) |
601 |
|
|
warn("unable to read PCI_IOBASEH_1"); |
602 |
|
|
printf("\t0x%04x: I/O Base Upper 16 Bits: %04x " |
603 |
|
|
"I/O Limit Upper 16 Bits: %04x\n", PCI_IOBASEH_1, |
604 |
|
|
(reg >> 0) & 0xffff, (reg >> 16) & 0xffff); |
605 |
|
|
|
606 |
|
|
#define PCI_PPB_ROM_REG 0x38 |
607 |
|
|
if (pci_read(bus, dev, func, PCI_PPB_ROM_REG, ®) != 0) |
608 |
|
|
warn("unable to read PCI_PPB_ROM_REG"); |
609 |
|
|
printf("\t0x%04x: Expansion ROM Base Address: %08x\n", |
610 |
|
|
PCI_PPB_ROM_REG, reg); |
611 |
|
|
|
612 |
|
|
if (pci_read(bus, dev, func, PCI_INTERRUPT_REG, ®) != 0) |
613 |
|
|
warn("unable to read PCI_INTERRUPT_REG"); |
614 |
|
|
printf("\t0x%04x: Interrupt Pin: %02x Line: %02x " |
615 |
|
|
"Bridge Control: %04x\n", |
616 |
|
|
PCI_INTERRUPT_REG, PCI_INTERRUPT_PIN(reg), |
617 |
|
|
PCI_INTERRUPT_LINE(reg), reg >> 16); |
618 |
|
|
} |
619 |
|
|
|
620 |
|
|
void |
621 |
|
|
dump_type2(int bus, int dev, int func) |
622 |
|
|
{ |
623 |
|
|
u_int32_t reg; |
624 |
|
|
|
625 |
|
|
if (pci_read(bus, dev, func, PCI_MAPREG_START, ®) != 0) |
626 |
|
|
warn("unable to read PCI_MAPREG\n"); |
627 |
|
|
printf("\t0x%04x: Cardbus Control Registers Base Address: %08x\n", |
628 |
|
|
PCI_MAPREG_START, reg); |
629 |
|
|
|
630 |
|
|
if (pci_read(bus, dev, func, PCI_PRIBUS_2, ®) != 0) |
631 |
|
|
warn("unable to read PCI_PRIBUS_2"); |
632 |
|
|
printf("\t0x%04x: Primary Bus: %d Cardbus Bus: %d " |
633 |
|
|
"Subordinate Bus: %d \n\t Cardbus Latency Timer: %02x\n", |
634 |
|
|
PCI_PRIBUS_2, (reg >> 0) & 0xff, (reg >> 8) & 0xff, |
635 |
|
|
(reg >> 16) & 0xff, (reg >> 24) & 0xff); |
636 |
|
|
|
637 |
|
|
if (pci_read(bus, dev, func, PCI_MEMBASE0_2, ®) != 0) |
638 |
|
|
warn("unable to read PCI_MEMBASE0_2\n"); |
639 |
|
|
printf("\t0x%04x: Memory Base 0: %08x\n", PCI_MEMBASE0_2, reg); |
640 |
|
|
|
641 |
|
|
if (pci_read(bus, dev, func, PCI_MEMLIMIT0_2, ®) != 0) |
642 |
|
|
warn("unable to read PCI_MEMLIMIT0_2\n"); |
643 |
|
|
printf("\t0x%04x: Memory Limit 0: %08x\n", PCI_MEMLIMIT0_2, reg); |
644 |
|
|
|
645 |
|
|
if (pci_read(bus, dev, func, PCI_MEMBASE1_2, ®) != 0) |
646 |
|
|
warn("unable to read PCI_MEMBASE1_2\n"); |
647 |
|
|
printf("\t0x%04x: Memory Base 1: %08x\n", PCI_MEMBASE1_2, reg); |
648 |
|
|
|
649 |
|
|
if (pci_read(bus, dev, func, PCI_MEMLIMIT1_2, ®) != 0) |
650 |
|
|
warn("unable to read PCI_MEMLIMIT1_2\n"); |
651 |
|
|
printf("\t0x%04x: Memory Limit 1: %08x\n", PCI_MEMLIMIT1_2, reg); |
652 |
|
|
|
653 |
|
|
if (pci_read(bus, dev, func, PCI_IOBASE0_2, ®) != 0) |
654 |
|
|
warn("unable to read PCI_IOBASE0_2\n"); |
655 |
|
|
printf("\t0x%04x: I/O Base 0: %08x\n", PCI_IOBASE0_2, reg); |
656 |
|
|
|
657 |
|
|
if (pci_read(bus, dev, func, PCI_IOLIMIT0_2, ®) != 0) |
658 |
|
|
warn("unable to read PCI_IOLIMIT0_2\n"); |
659 |
|
|
printf("\t0x%04x: I/O Limit 0: %08x\n", PCI_IOLIMIT0_2, reg); |
660 |
|
|
|
661 |
|
|
if (pci_read(bus, dev, func, PCI_IOBASE1_2, ®) != 0) |
662 |
|
|
warn("unable to read PCI_IOBASE1_2\n"); |
663 |
|
|
printf("\t0x%04x: I/O Base 1: %08x\n", PCI_IOBASE1_2, reg); |
664 |
|
|
|
665 |
|
|
if (pci_read(bus, dev, func, PCI_IOLIMIT1_2, ®) != 0) |
666 |
|
|
warn("unable to read PCI_IOLIMIT1_2\n"); |
667 |
|
|
printf("\t0x%04x: I/O Limit 1: %08x\n", PCI_IOLIMIT1_2, reg); |
668 |
|
|
|
669 |
|
|
if (pci_read(bus, dev, func, PCI_INTERRUPT_REG, ®) != 0) |
670 |
|
|
warn("unable to read PCI_INTERRUPT_REG"); |
671 |
|
|
printf("\t0x%04x: Interrupt Pin: %02x Line: %02x " |
672 |
|
|
"Bridge Control: %04x\n", |
673 |
|
|
PCI_INTERRUPT_REG, PCI_INTERRUPT_PIN(reg), |
674 |
|
|
PCI_INTERRUPT_LINE(reg), reg >> 16); |
675 |
|
|
|
676 |
|
|
if (pci_read(bus, dev, func, PCI_SUBVEND_2, ®) != 0) |
677 |
|
|
warn("unable to read PCI_SUBVEND_2"); |
678 |
|
|
printf("\t0x%04x: Subsystem Vendor ID: %04x Product ID: %04x\n", |
679 |
|
|
PCI_SUBVEND_2, PCI_VENDOR(reg), PCI_PRODUCT(reg)); |
680 |
|
|
|
681 |
|
|
if (pci_read(bus, dev, func, PCI_PCCARDIF_2, ®) != 0) |
682 |
|
|
warn("unable to read PCI_PCCARDIF_2\n"); |
683 |
|
|
printf("\t0x%04x: 16-bit Legacy Mode Base Address: %08x\n", |
684 |
|
|
PCI_PCCARDIF_2, reg); |
685 |
|
|
} |
686 |
|
|
|
687 |
|
|
void |
688 |
|
|
dump(int bus, int dev, int func) |
689 |
|
|
{ |
690 |
|
|
u_int32_t reg; |
691 |
|
|
u_int8_t capptr = PCI_CAPLISTPTR_REG; |
692 |
|
|
|
693 |
|
|
if (pci_read(bus, dev, func, PCI_ID_REG, ®) != 0) |
694 |
|
|
warn("unable to read PCI_ID_REG"); |
695 |
|
|
printf("\t0x%04x: Vendor ID: %04x Product ID: %04x\n", PCI_ID_REG, |
696 |
|
|
PCI_VENDOR(reg), PCI_PRODUCT(reg)); |
697 |
|
|
|
698 |
|
|
if (pci_read(bus, dev, func, PCI_COMMAND_STATUS_REG, ®) != 0) |
699 |
|
|
warn("unable to read PCI_COMMAND_STATUS_REG"); |
700 |
|
|
printf("\t0x%04x: Command: %04x Status: %04x\n", |
701 |
|
|
PCI_COMMAND_STATUS_REG, reg & 0xffff, (reg >> 16) & 0xffff); |
702 |
|
|
|
703 |
|
|
if (pci_read(bus, dev, func, PCI_CLASS_REG, ®) != 0) |
704 |
|
|
warn("unable to read PCI_CLASS_REG"); |
705 |
|
|
printf("\t0x%04x: Class: %02x Subclass: %02x Interface: %02x " |
706 |
|
|
"Revision: %02x\n", PCI_CLASS_REG, PCI_CLASS(reg), |
707 |
|
|
PCI_SUBCLASS(reg), PCI_INTERFACE(reg), PCI_REVISION(reg)); |
708 |
|
|
|
709 |
|
|
if (pci_read(bus, dev, func, PCI_BHLC_REG, ®) != 0) |
710 |
|
|
warn("unable to read PCI_BHLC_REG"); |
711 |
|
|
printf("\t0x%04x: BIST: %02x Header Type: %02x Latency Timer: %02x " |
712 |
|
|
"Cache Line Size: %02x\n", PCI_BHLC_REG, PCI_BIST(reg), |
713 |
|
|
PCI_HDRTYPE(reg), PCI_LATTIMER(reg), PCI_CACHELINE(reg)); |
714 |
|
|
|
715 |
|
|
switch (PCI_HDRTYPE_TYPE(reg)) { |
716 |
|
|
case 2: |
717 |
|
|
dump_type2(bus, dev, func); |
718 |
|
|
capptr = PCI_CARDBUS_CAPLISTPTR_REG; |
719 |
|
|
break; |
720 |
|
|
case 1: |
721 |
|
|
dump_type1(bus, dev, func); |
722 |
|
|
break; |
723 |
|
|
case 0: |
724 |
|
|
dump_type0(bus, dev, func); |
725 |
|
|
break; |
726 |
|
|
default: |
727 |
|
|
break; |
728 |
|
|
} |
729 |
|
|
dump_caplist(bus, dev, func, capptr); |
730 |
|
|
} |
731 |
|
|
|
732 |
|
|
void |
733 |
|
|
hexdump(int bus, int dev, int func, int size) |
734 |
|
|
{ |
735 |
|
|
u_int32_t reg; |
736 |
|
|
int i; |
737 |
|
|
|
738 |
|
|
for (i = 0; i < size; i += 4) { |
739 |
|
|
if (pci_read(bus, dev, func, i, ®) != 0) { |
740 |
|
|
if (errno == EINVAL) |
741 |
|
|
return; |
742 |
|
|
warn("unable to read 0x%02x", i); |
743 |
|
|
} |
744 |
|
|
|
745 |
|
|
if ((i % 16) == 0) |
746 |
|
|
printf("\t0x%04x:", i); |
747 |
|
|
printf(" %08x", reg); |
748 |
|
|
|
749 |
|
|
if ((i % 16) == 12) |
750 |
|
|
printf("\n"); |
751 |
|
|
} |
752 |
|
|
} |
753 |
|
|
|
754 |
|
|
int |
755 |
|
|
pci_nfuncs(int bus, int dev) |
756 |
|
|
{ |
757 |
|
|
u_int32_t hdr; |
758 |
|
|
|
759 |
|
|
if (pci_read(bus, dev, 0, PCI_BHLC_REG, &hdr) != 0) |
760 |
|
|
return (-1); |
761 |
|
|
|
762 |
|
|
return (PCI_HDRTYPE_MULTIFN(hdr) ? 8 : 1); |
763 |
|
|
} |
764 |
|
|
|
765 |
|
|
int |
766 |
|
|
pci_read(int bus, int dev, int func, u_int32_t reg, u_int32_t *val) |
767 |
|
|
{ |
768 |
|
|
struct pci_io io; |
769 |
|
|
int rv; |
770 |
|
|
|
771 |
|
|
bzero(&io, sizeof(io)); |
772 |
|
|
io.pi_sel.pc_bus = bus; |
773 |
|
|
io.pi_sel.pc_dev = dev; |
774 |
|
|
io.pi_sel.pc_func = func; |
775 |
|
|
io.pi_reg = reg; |
776 |
|
|
io.pi_width = 4; |
777 |
|
|
|
778 |
|
|
rv = ioctl(pcifd, PCIOCREAD, &io); |
779 |
|
|
if (rv != 0) |
780 |
|
|
return (rv); |
781 |
|
|
|
782 |
|
|
*val = io.pi_data; |
783 |
|
|
|
784 |
|
|
return (0); |
785 |
|
|
} |
786 |
|
|
|
787 |
|
|
int |
788 |
|
|
pci_readmask(int bus, int dev, int func, u_int32_t reg, u_int32_t *val) |
789 |
|
|
{ |
790 |
|
|
struct pci_io io; |
791 |
|
|
int rv; |
792 |
|
|
|
793 |
|
|
bzero(&io, sizeof(io)); |
794 |
|
|
io.pi_sel.pc_bus = bus; |
795 |
|
|
io.pi_sel.pc_dev = dev; |
796 |
|
|
io.pi_sel.pc_func = func; |
797 |
|
|
io.pi_reg = reg; |
798 |
|
|
io.pi_width = 4; |
799 |
|
|
|
800 |
|
|
rv = ioctl(pcifd, PCIOCREADMASK, &io); |
801 |
|
|
if (rv != 0) |
802 |
|
|
return (rv); |
803 |
|
|
|
804 |
|
|
*val = io.pi_data; |
805 |
|
|
|
806 |
|
|
return (0); |
807 |
|
|
} |
808 |
|
|
|
809 |
|
|
int |
810 |
|
|
dump_rom(int bus, int dev, int func) |
811 |
|
|
{ |
812 |
|
|
struct pci_rom rom; |
813 |
|
|
u_int32_t cr, addr; |
814 |
|
|
|
815 |
|
|
if (pci_read(bus, dev, func, PCI_ROM_REG, &addr) != 0 || |
816 |
|
|
pci_read(bus, dev, func, PCI_CLASS_REG, &cr) != 0) |
817 |
|
|
return (errno); |
818 |
|
|
|
819 |
|
|
if (addr == 0 && PCI_CLASS(cr) == PCI_CLASS_DISPLAY && |
820 |
|
|
PCI_SUBCLASS(cr) == PCI_SUBCLASS_DISPLAY_VGA) |
821 |
|
|
return dump_vga_bios(); |
822 |
|
|
|
823 |
|
|
bzero(&rom, sizeof(rom)); |
824 |
|
|
rom.pr_sel.pc_bus = bus; |
825 |
|
|
rom.pr_sel.pc_dev = dev; |
826 |
|
|
rom.pr_sel.pc_func = func; |
827 |
|
|
if (ioctl(pcifd, PCIOCGETROMLEN, &rom)) |
828 |
|
|
return (errno); |
829 |
|
|
|
830 |
|
|
rom.pr_rom = malloc(rom.pr_romlen); |
831 |
|
|
if (rom.pr_rom == NULL) |
832 |
|
|
return (ENOMEM); |
833 |
|
|
|
834 |
|
|
if (ioctl(pcifd, PCIOCGETROM, &rom)) |
835 |
|
|
return (errno); |
836 |
|
|
|
837 |
|
|
if (write(romfd, rom.pr_rom, rom.pr_romlen) == -1) |
838 |
|
|
return (errno); |
839 |
|
|
|
840 |
|
|
return (0); |
841 |
|
|
} |
842 |
|
|
|
843 |
|
|
#define VGA_BIOS_ADDR 0xc0000 |
844 |
|
|
#define VGA_BIOS_LEN 0x10000 |
845 |
|
|
|
846 |
|
|
int |
847 |
|
|
dump_vga_bios(void) |
848 |
|
|
{ |
849 |
|
|
#if defined(__amd64__) || defined(__i386__) |
850 |
|
|
void *bios; |
851 |
|
|
int fd; |
852 |
|
|
|
853 |
|
|
fd = open(_PATH_MEM, O_RDONLY, 0777); |
854 |
|
|
if (fd == -1) |
855 |
|
|
err(1, "%s", _PATH_MEM); |
856 |
|
|
|
857 |
|
|
bios = malloc(VGA_BIOS_LEN); |
858 |
|
|
if (bios == NULL) |
859 |
|
|
return (ENOMEM); |
860 |
|
|
|
861 |
|
|
if (pread(fd, bios, VGA_BIOS_LEN, VGA_BIOS_ADDR) == -1) |
862 |
|
|
err(1, "%s", _PATH_MEM); |
863 |
|
|
|
864 |
|
|
if (write(romfd, bios, VGA_BIOS_LEN) == -1) { |
865 |
|
|
free(bios); |
866 |
|
|
return (errno); |
867 |
|
|
} |
868 |
|
|
|
869 |
|
|
free(bios); |
870 |
|
|
|
871 |
|
|
return (0); |
872 |
|
|
#else |
873 |
|
|
return (ENODEV); |
874 |
|
|
#endif |
875 |
|
|
} |