]>
Commit | Line | Data |
---|---|---|
3b04fe1f | 1 | /* pci-config-space.c: Read the PCI configuration space. |
2 | ||
3 | Read the PCI configuration space using the Intel bus-bridge interface | |
4 | registers. This bypasses the BIOS32 entry, and thus we are not assured | |
5 | of working on all systems. | |
6 | ||
7 | Copyright 1998-2002 by Donald Becker. | |
8 | This software may be used and distributed according to the terms of | |
9 | the GNU General Public License (GPL), incorporated herein by reference. | |
10 | Contact the author for use under other terms. | |
11 | ||
12 | The author may be reached as becker@scyld.com, or C/O | |
13 | Scyld Computing Corporation | |
14 | 410 Severn Ave., Suite 210 | |
15 | Annapolis MD 21403 | |
16 | ||
17 | Support and updates available at | |
18 | http://www.scyld.com/diag/index.html | |
19 | ||
20 | Common-sense licensing statement: Using any portion of this program in | |
21 | your own program means that you must give credit to the original author | |
22 | and release the resulting code under the GPL. | |
23 | */ | |
24 | ||
25 | static char *version_msg = | |
26 | "pci-config.c:v2.03 4/15/2002 Donald Becker (becker@scyld.com)\n" | |
27 | " http://www.scyld.com/diag/index.html\n"; | |
28 | static char *usage_msg = | |
29 | "Usage: pci-config [-aDfSvVW] [-# <device_index>]\n"; | |
30 | ||
31 | static char *long_usage_msg =" | |
32 | ||
33 | This program shows the contents of PCI configuration space. | |
34 | It reads the hardware registers, and thus must be run as 'root'. | |
35 | ||
36 | Running this program with no options shows the installed PCI devices. | |
37 | Each line is prefixed by its index which may be used with -#<index> | |
38 | e.g. \"pci-config -#3\" to specify the device to operate on. | |
39 | ||
40 | Commonly use options are | |
41 | -# <device-index> Operate only on DEVICE-INDEX e.g -#3 | |
42 | ||
43 | The operations on the selected device are | |
44 | -a --show-addresses Show PCI address registers. | |
45 | -S --sleep Put device to sleep (ACPI D3 state) | |
46 | -W --wake Wake a sleeping device (ACPI D0 state) | |
47 | ||
48 | Less commonly used options are | |
49 | -B <bus> --bus <bus> Show only devices on BUS. | |
50 | -A <addr> --set-addresses Set PCI address register 1 to the ADDR. | |
51 | -D --debug Show details of operations | |
52 | -f --force Override checks and perform the operation | |
53 | -u --usage Show this long usage message | |
54 | -v --verbose Verbose mode | |
55 | -V --version Display this program's version information | |
56 | ||
57 | "; | |
58 | ||
59 | #include <unistd.h> | |
60 | #include <stdio.h> | |
61 | #include <stdlib.h> | |
62 | #include <sys/mman.h> | |
63 | #include <sys/stat.h> | |
64 | #include <fcntl.h> | |
65 | #include <getopt.h> | |
66 | #if defined(__linux__) && __GNU_LIBRARY__ == 1 | |
67 | #include <asm/io.h> /* Newer libraries use <sys/io.h> instead. */ | |
68 | #else | |
69 | #include <sys/io.h> | |
70 | #endif | |
71 | #if !defined(__OPTIMIZE__) | |
72 | #error You must compile this driver with "-O"! | |
73 | #endif | |
74 | ||
75 | struct option longopts[] = { | |
76 | {"show-addresses", 0, 0, 'a'}, /* Show PCI address registers. */ | |
77 | {"set-addresses", 1, 0, 'A'}, /* Show PCI address registers. */ | |
78 | {"bus", 1, 0, 'B'}, /* Show only devices on BUS. */ | |
79 | {"debug", 0, 0, 'D'}, /* Increase debug level. */ | |
80 | {"force", 0, 0, 'f'}, /* Force operation. */ | |
81 | {"set-WOL", 0, 0, 'M'}, /* Set to Wake-On-LAN mode. */ | |
82 | {"sleep", 0, 0, 'S'}, /* Put device to sleep (ACPI D3 state). */ | |
83 | {"usage", 0, 0, 'u'}, /* Show the long usage message. */ | |
84 | {"verbose", 0, 0, 'v'}, /* Verbose mode */ | |
85 | {"version", 0, 0, 'V'}, /* Display version number */ | |
86 | {"wake-on-lan", 0, 0, 'W'}, /* Wake (set to D0 state) the device. */ | |
87 | {"device-index", 1, 0, '#'}, /* Operate only on device INDEX. */ | |
88 | { 0, 0, 0, 0 } | |
89 | }; | |
90 | ||
91 | static int verbose=1, opt_a=0, opt_f=0, opt_wake=0, opt_set_WOL=0, debug=0; | |
92 | static int opt_sleep = 0; | |
93 | static long set_address = -1; | |
94 | ||
95 | static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn); | |
96 | static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, | |
97 | unsigned char pci_dev_fn); | |
98 | static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, | |
99 | int dev_num); | |
100 | ||
101 | static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *pci_config_space); | |
102 | static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, | |
103 | void *config); | |
104 | static void acpi_sleep(unsigned char bus, unsigned char devfn, void *pci_cfg); | |
105 | static int dump_mem_region(long addr); | |
106 | ||
107 | extern int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, | |
108 | unsigned char where, unsigned char *val); | |
109 | int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, | |
110 | unsigned char regnum, unsigned short *val); | |
111 | extern int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, | |
112 | unsigned char regnum, unsigned int *val); | |
113 | void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, | |
114 | unsigned char regnum, unsigned char val); | |
115 | void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, | |
116 | unsigned char regnum, unsigned short val); | |
117 | void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, | |
118 | unsigned char regnum, unsigned int val); | |
119 | ||
120 | \f | |
121 | int main(int argc, char **argv) | |
122 | { | |
123 | int pci_bus = 0, pci_dev_fn; | |
124 | int errflag = 0, show_version = 0; | |
125 | int c, longind, card_num = 0; | |
126 | int dev_num = 0; | |
127 | ||
128 | while ((c = getopt_long(argc, argv, "#:aA:b:B:DfMSuvVW", | |
129 | longopts, &longind)) | |
130 | != -1) | |
131 | switch (c) { | |
132 | case 'a': opt_a++; break; | |
133 | case 'A': set_address = strtol(optarg, NULL, 16); break; | |
134 | case 'b': printf("Setting bus to %s.\n", optarg); | |
135 | case 'B': pci_bus = strtol(optarg, NULL, 0); break; | |
136 | case 'D': debug++; break; | |
137 | case 'f': opt_f++; break; | |
138 | case 'M': opt_set_WOL++; break; | |
139 | case 'S': opt_sleep++; break; | |
140 | case 'u': printf("%s%s", usage_msg, long_usage_msg); return 0; | |
141 | case 'v': verbose++; break; | |
142 | case 'V': show_version++; break; | |
143 | case 'W': opt_wake++; break; | |
144 | case '#': card_num = atoi(optarg); break; | |
145 | case '?': | |
146 | errflag++; | |
147 | } | |
148 | if (errflag) { | |
149 | fprintf(stderr, "%s%s", usage_msg, " Use -u for more information.\n"); | |
150 | return 3; | |
151 | } | |
152 | ||
153 | if (verbose) | |
154 | printf(version_msg); | |
155 | ||
156 | /* Get access to all of I/O space. */ | |
157 | if (iopl(3) < 0) { | |
158 | perror("pci-config: iopl()"); | |
159 | fprintf(stderr, "This program must be run as root.\n"); | |
160 | return 2; | |
161 | } | |
162 | for (pci_dev_fn = 0; pci_dev_fn < 256; pci_dev_fn++) { | |
163 | /*unsigned char cb;*/ | |
164 | unsigned int pci_id; | |
165 | ||
166 | pcibios_read_config_dword(pci_bus, pci_dev_fn, 0, &pci_id); | |
167 | if (pci_id == 0xffffffff) | |
168 | continue; | |
169 | dev_num++; | |
170 | if (card_num == 0) { | |
171 | printf("Device #%d at bus %d device/function %d/%d, %8.8x.\n", | |
172 | dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7, pci_id); | |
173 | } else if (card_num == dev_num) { | |
174 | show_one_device(pci_bus, pci_dev_fn, dev_num); | |
175 | } | |
176 | if ((pci_dev_fn & 7) == 0) { | |
177 | unsigned int cdw; | |
178 | pcibios_read_config_dword(pci_bus, pci_dev_fn, 3*4, &cdw); | |
179 | if ((cdw & 0x00800000) == 0) | |
180 | pci_dev_fn += 7; | |
181 | } | |
182 | } | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, | |
188 | int dev_num) | |
189 | { | |
190 | unsigned int config[64]; | |
191 | int i; | |
192 | int pci_id; | |
193 | ||
194 | printf("Device #%d at bus %d device/function %d/%d.", | |
195 | dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7); | |
196 | for (i = 0; i < 64; i++) { | |
197 | pcibios_read_config_dword(pci_bus, pci_dev_fn, i<<2, &config[i]); | |
198 | printf("%s%8.8x", i % 8 == 0 ? "\n " : " ", config[i]); | |
199 | } | |
200 | printf("\n"); | |
201 | for (i = 0; i < 5; i++) { | |
202 | unsigned int pciaddr = config[4 + i]; | |
203 | if (pciaddr) | |
204 | printf(" Base Address %d: %s at %8.8x.\n", | |
205 | i, pciaddr & 1 ? "I/O" : "Memory", pciaddr & ~1); | |
206 | } | |
207 | if (set_address >= 0) { | |
208 | fprintf(stderr, "Setting PCI address register 1 to 0x%lx.\n", | |
209 | set_address); | |
210 | pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x14, set_address); | |
211 | pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x04, config[1] | 3); | |
212 | } | |
213 | if (opt_a) | |
214 | show_addr_config(pci_bus, pci_dev_fn); | |
215 | pci_id = config[0]; | |
216 | if (config[1] & 0x00100000) | |
217 | show_ext_caps(config, pci_bus, pci_dev_fn); | |
218 | if (config[10]) { | |
219 | char *cis_addr_space[] = {"PCI configuration space", "BAR 0", "BAR 1", "BAR 2", "BAR 3", }; | |
220 | int space = config[10] & 7; | |
221 | printf(" CardBus CIS pointer 0x%4.4x (%s), address %x.\n", config[10], | |
222 | cis_addr_space[space], config[4 + (space-1)]); | |
223 | if (space > 0 && space < 4) | |
224 | dump_mem_region(config[4 + ((space-1))]); | |
225 | } | |
226 | if (opt_sleep) | |
227 | acpi_sleep(pci_bus, pci_dev_fn, config); | |
228 | if (opt_wake) | |
229 | acpi_wake(pci_bus, pci_dev_fn, config); | |
230 | if (opt_set_WOL) | |
231 | if (pci_id == 0x905510b7) | |
232 | cyclone_WOL(pci_bus, pci_dev_fn, config); | |
233 | } | |
234 | ||
235 | static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn) | |
236 | { | |
237 | int i; | |
238 | unsigned int pciaddr, cdw; | |
239 | for (i = 0; i < 5; i++) { | |
240 | int cfg_i = 0x10 + (i<<2); | |
241 | pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &pciaddr); | |
242 | pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, 0xffffffff); | |
243 | pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &cdw); | |
244 | if (cdw == 0) | |
245 | break; | |
246 | printf(" Address %d %s at %8.8x, decoded bits are %8.8x.\n", | |
247 | i, cdw & 1 ? "is I/O" : "memory", pciaddr & ~1, ~cdw); | |
248 | pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, pciaddr); | |
249 | } | |
250 | pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &pciaddr); | |
251 | pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, 0xfffffffe); | |
252 | pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &cdw); | |
253 | pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, pciaddr); | |
254 | if (cdw) | |
255 | printf(" BIOS ROM at %8.8x, decoded bits are %8.8x.\n", pciaddr, cdw); | |
256 | else | |
257 | printf(" No BIOS extension (boot ROM).\n"); | |
258 | return; | |
259 | } | |
260 | static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, | |
261 | unsigned char pci_dev_fn) | |
262 | { | |
263 | unsigned char *pcfg = (void *)cfg_space; | |
264 | int cap_idx = cfg_space[13] & 0xff; | |
265 | ||
266 | printf(" Extended capabilities, first structure at offset 0x%x.\n", | |
267 | cap_idx); | |
268 | for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { | |
269 | printf(" Extended PCI capability type %d at 0x%2.2x, next %d.\n", | |
270 | pcfg[cap_idx], cap_idx, pcfg[cap_idx + 1]); | |
271 | if (pcfg[cap_idx] == 1) { | |
272 | printf(" Power management entry ver. %d: Capabilities %2.2x%2.2x" | |
273 | ", Ctrl %2.2x%2.2x, Event %2.2x%2.2x.\n", | |
274 | pcfg[cap_idx + 2] & 7, | |
275 | pcfg[cap_idx + 3], pcfg[cap_idx + 2], | |
276 | pcfg[cap_idx + 5], pcfg[cap_idx + 4], | |
277 | pcfg[cap_idx + 7], pcfg[cap_idx + 6]); | |
278 | printf(" Power state D%d.\n", pcfg[cap_idx + 4] & 3); | |
279 | } | |
280 | } | |
281 | } | |
282 | ||
283 | ||
284 | static int acpi_find(unsigned char pci_bus, unsigned char pci_dev_fn, | |
285 | void *config) | |
286 | { | |
287 | unsigned char *pcfg = (void *)config; | |
288 | if (pcfg[6] & 0x10) { | |
289 | int cap_idx = pcfg[0x34]; | |
290 | ||
291 | printf(" Extended capabilities, first structure at offset 0x%x.\n", | |
292 | cap_idx); | |
293 | for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { | |
294 | if (pcfg[cap_idx] == 1) | |
295 | return cap_idx; | |
296 | } | |
297 | } | |
298 | return 0; | |
299 | } | |
300 | ||
301 | static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, | |
302 | void *pci_config_space) | |
303 | { | |
304 | unsigned char *config = pci_config_space; | |
305 | unsigned short *configw = pci_config_space; | |
306 | unsigned int *configdw = pci_config_space; | |
307 | int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); | |
308 | unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; | |
309 | int i; | |
310 | ||
311 | if (debug) | |
312 | printf("Power index is %#x.\n", pwr_idx); | |
313 | ||
314 | printf(" Waking up an ACPI device. Currently powered %s, " | |
315 | "I/O %#x IRQ %d.\n" | |
316 | " Updating the power state of %4.4x->%4.4x.\n", | |
317 | pwr_command & 3 ? "down" : "up", configdw[0x10>>2], config[0x3c], | |
318 | pwr_command, pwr_command & ~3); | |
319 | pcibios_write_config_word(pci_bus, pci_dev_fn, pwr_idx + 4, | |
320 | pwr_command & ~3); | |
321 | /* Many devices must have their PCI register state restored when changing | |
322 | from D3 state. */ | |
323 | for (i = 0x10; i <= 0x20; i+=4) | |
324 | pcibios_write_config_dword(pci_bus, pci_dev_fn, i, configdw[i >> 2]); | |
325 | /* PCI_ROM_ADDRESS, interrupt line, cache line size, latency timer */ | |
326 | pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, configdw[0x30 >> 2]); | |
327 | pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x3c, config[0x3c]); | |
328 | pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0c, config[0x0c]); | |
329 | pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0d, config[0x0d]); | |
330 | /* Finally, restore the command register. */ | |
331 | pcibios_write_config_word(pci_bus, pci_dev_fn, 0x04, configw[4>>1]); | |
332 | } | |
333 | ||
334 | static void acpi_sleep(unsigned char bus, unsigned char devfn, | |
335 | void *pci_config_space) | |
336 | { | |
337 | unsigned short *configw = pci_config_space; | |
338 | int pwr_idx = acpi_find(bus, devfn, pci_config_space); | |
339 | unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; | |
340 | pcibios_write_config_word(bus, devfn, pwr_idx + 4, pwr_command | 0x0103); | |
341 | } | |
342 | ||
343 | /* Put the 3Com Cyclone e.g. 3c905B series into Wake On LAN mode. */ | |
344 | static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *config) | |
345 | { | |
346 | int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); | |
347 | unsigned short pwr_command = ((unsigned short *)config)[(pwr_idx + 4)>>1]; | |
348 | long ioaddr = ((int *)config)[0x10]; | |
349 | ||
350 | acpi_wake(pci_bus, pci_dev_fn, config); | |
351 | ||
352 | outw(0x801f, ioaddr + 0x0e); /* Set RxFilter to accept frames. */ | |
353 | outw((1<<11) + 7, ioaddr + 0x0e); | |
354 | outw(7, ioaddr + 0x0c); | |
355 | printf(" Window 7 Power Management Event is %4.4x.\n", | |
356 | inw(ioaddr + 0x0c)); | |
357 | printf(" Changing the power state from %4.4x to 0103.\n", pwr_command); | |
358 | outw(0x2000, ioaddr + 0x0e); /* RxEnable. */ | |
359 | pcibios_write_config_word(pci_bus, pci_dev_fn, 0xe0, pwr_command | 0x8103); | |
360 | } | |
361 | ||
362 | \f | |
363 | #define PCI_CONFIG_ADDR 0x0cf8 | |
364 | #define PCI_CONFIG_DATA 0x0cfc | |
365 | ||
366 | int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, | |
367 | unsigned char regnum, unsigned char *val) | |
368 | { | |
369 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
370 | PCI_CONFIG_ADDR); | |
371 | *val = inb(PCI_CONFIG_DATA + (regnum & 3)); | |
372 | return 0; | |
373 | } | |
374 | int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, | |
375 | unsigned char regnum, unsigned short *val) | |
376 | { | |
377 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
378 | PCI_CONFIG_ADDR); | |
379 | *val = inw(PCI_CONFIG_DATA + (regnum & 2)); | |
380 | return 0; | |
381 | } | |
382 | int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, | |
383 | unsigned char regnum, unsigned int *val) | |
384 | { | |
385 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
386 | PCI_CONFIG_ADDR); | |
387 | *val = inl(PCI_CONFIG_DATA); | |
388 | return 0; | |
389 | } | |
390 | void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, | |
391 | unsigned char regnum, unsigned char val) | |
392 | { | |
393 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
394 | PCI_CONFIG_ADDR); | |
395 | outb(val, PCI_CONFIG_DATA + (regnum & 3)); | |
396 | return; | |
397 | } | |
398 | void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, | |
399 | unsigned char regnum, unsigned short val) | |
400 | { | |
401 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
402 | PCI_CONFIG_ADDR); | |
403 | outw(val, PCI_CONFIG_DATA + (regnum & 2)); | |
404 | return; | |
405 | } | |
406 | void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, | |
407 | unsigned char regnum, unsigned int val) | |
408 | { | |
409 | outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), | |
410 | PCI_CONFIG_ADDR); | |
411 | outl(val, PCI_CONFIG_DATA); | |
412 | return; | |
413 | } | |
414 | ||
415 | \f | |
416 | /* Map the board shared memory into our address space -- this code is | |
417 | a good example of non-kernel access to devices on the PCI bus. */ | |
418 | static int dump_mem_region(long addr) | |
419 | { | |
420 | unsigned short *shared_mem; | |
421 | int i; | |
422 | int memfd = open("/dev/kmem", O_RDWR); | |
423 | ||
424 | if (memfd < 0) { | |
425 | perror("/dev/kmem (shared memory)"); | |
426 | return 2; | |
427 | } else | |
428 | printf("Opened /dev/kmem for PCI memory.\n"); | |
429 | shared_mem = mmap(0, 0x8000, PROT_READ|PROT_WRITE, | |
430 | MAP_SHARED, memfd, addr); | |
431 | printf("Shared memory at %#x (%#x).\n", (int)addr, (int)shared_mem); | |
432 | for (i = 0; i < 100; i++) | |
433 | printf(" %4.4x", shared_mem[i]); | |
434 | close(memfd); | |
435 | printf(" ...\n"); | |
436 | return 0; | |
437 | } | |
438 | ||
439 | /* | |
440 | * Local variables: | |
441 | * compile-command: "cc -O -Wall -o pci-config pci-config.c" | |
442 | * tab-width: 4 | |
443 | * c-indent-level: 4 | |
444 | * c-basic-offset: 4 | |
445 | * End: | |
446 | */ |