1 | /* $Id: pcibios.c 42793 2012-08-13 14:38:20Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * PCI BIOS support.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2004-2012 Oracle Corporation
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
10 | * available from http://www.virtualbox.org. This file is free software;
|
---|
11 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
12 | * General Public License (GPL) as published by the Free Software
|
---|
13 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
14 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
15 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
16 | */
|
---|
17 |
|
---|
18 | #include <stdint.h>
|
---|
19 | #include <string.h>
|
---|
20 | #include "biosint.h"
|
---|
21 | #include "inlines.h"
|
---|
22 |
|
---|
23 | #if DEBUG_PCI
|
---|
24 | # define BX_DEBUG_PCI(...) BX_DEBUG(__VA_ARGS__)
|
---|
25 | #else
|
---|
26 | # define BX_DEBUG_PCI(...)
|
---|
27 | #endif
|
---|
28 |
|
---|
29 | /* PCI function codes. */
|
---|
30 | enum pci_func {
|
---|
31 | PCI_BIOS_PRESENT = 0x01, /* PCI BIOS presence check. */
|
---|
32 | FIND_PCI_DEVICE = 0x02, /* Find PCI device by ID. */
|
---|
33 | FIND_PCI_CLASS_CODE = 0x03, /* Find PCI device by class. */
|
---|
34 | GEN_SPECIAL_CYCLE = 0x06, /* Generate special cycle. */
|
---|
35 | READ_CONFIG_BYTE = 0x08, /* Read a byte from PCI config space. */
|
---|
36 | READ_CONFIG_WORD = 0x09, /* Read a word from PCI config space. */
|
---|
37 | READ_CONFIG_DWORD = 0x0A, /* Read a dword from PCI config space. */
|
---|
38 | WRITE_CONFIG_BYTE = 0x0B, /* Write a byte to PCI config space. */
|
---|
39 | WRITE_CONFIG_WORD = 0x0C, /* Write a word to PCI config space. */
|
---|
40 | WRITE_CONFIG_DWORD = 0x0D, /* Write a dword to PCI config space. */
|
---|
41 | GET_IRQ_ROUTING = 0x0E, /* Get IRQ routing table. */
|
---|
42 | SET_PCI_HW_INT = 0x0F, /* Set PCI hardware interrupt. */
|
---|
43 | };
|
---|
44 |
|
---|
45 | enum pci_error {
|
---|
46 | SUCCESSFUL = 0x00, /* Success. */
|
---|
47 | FUNC_NOT_SUPPORTED = 0x81, /* Unsupported function. */
|
---|
48 | BAD_VENDOR_ID = 0x83, /* Bad vendor ID (all bits set) passed. */
|
---|
49 | DEVICE_NOT_FOUND = 0x86, /* No matching device found. */
|
---|
50 | BAD_REGISTER_NUMBER = 0x87, /* Register number out of range. */
|
---|
51 | SET_FAILED = 0x88, /* Failed to set PCI interrupt. */
|
---|
52 | BUFFER_TOO_SMALL = 0x89 /* Routing table buffer insufficient. */
|
---|
53 | };
|
---|
54 |
|
---|
55 | // @todo: merge with system.c
|
---|
56 | #define AX r.gr.u.r16.ax
|
---|
57 | #define BX r.gr.u.r16.bx
|
---|
58 | #define CX r.gr.u.r16.cx
|
---|
59 | #define DX r.gr.u.r16.dx
|
---|
60 | #define SI r.gr.u.r16.si
|
---|
61 | #define DI r.gr.u.r16.di
|
---|
62 | #define BP r.gr.u.r16.bp
|
---|
63 | #define SP r.gr.u.r16.sp
|
---|
64 | #define EAX r.gr.u.r32.eax
|
---|
65 | #define EBX r.gr.u.r32.ebx
|
---|
66 | #define ECX r.gr.u.r32.ecx
|
---|
67 | #define EDX r.gr.u.r32.edx
|
---|
68 | #define ES r.es
|
---|
69 |
|
---|
70 | /* The 16-bit PCI BIOS service must be callable from both real and protected
|
---|
71 | * mode. In protected mode, the caller must set the CS selector base to F0000h
|
---|
72 | * (but the CS selector value is not specified!). The caller does not always
|
---|
73 | * provide a DS which covers the BIOS segment.
|
---|
74 | *
|
---|
75 | * Unlike APM, there are no provisions for the 32-bit PCI BIOS interface
|
---|
76 | * calling the 16-bit implementation.
|
---|
77 | *
|
---|
78 | * The PCI Firmware Specification requires that the PCI BIOS service is called
|
---|
79 | * with at least 1,024 bytes of stack space available, that interrupts are not
|
---|
80 | * enabled during execution, and that the routines are re-entrant.
|
---|
81 | *
|
---|
82 | * Implementation notes:
|
---|
83 | * - The PCI BIOS interface already uses certain 32-bit registers even in
|
---|
84 | * 16-bit mode. To simplify matters, all 32-bit GPRs are saved/restored and
|
---|
85 | * may be used by helper routines (notably for 32-bit port I/O).
|
---|
86 | */
|
---|
87 |
|
---|
88 | #define PCI_CFG_ADDR 0xCF8
|
---|
89 | #define PCI_CFG_DATA 0xCFC
|
---|
90 |
|
---|
91 | #ifdef __386__
|
---|
92 |
|
---|
93 | #define PCIxx(x) pci32_##x
|
---|
94 |
|
---|
95 | /* The stack layout is different in 32-bit mode. */
|
---|
96 | typedef struct {
|
---|
97 | pushad_regs_t gr;
|
---|
98 | uint32_t es;
|
---|
99 | uint32_t flags;
|
---|
100 | } pci_regs_t;
|
---|
101 |
|
---|
102 | #define FLAGS r.flags
|
---|
103 |
|
---|
104 | /* In 32-bit mode, don't do any output; not technically impossible but needs
|
---|
105 | * a lot of extra code.
|
---|
106 | */
|
---|
107 | #undef BX_INFO
|
---|
108 | #define BX_INFO(...)
|
---|
109 | #undef BX_DEBUG_PCI
|
---|
110 | #define BX_DEBUG_PCI(...)
|
---|
111 |
|
---|
112 | #else
|
---|
113 |
|
---|
114 | #define PCIxx(x) pci16_##x
|
---|
115 |
|
---|
116 | typedef struct {
|
---|
117 | pushad_regs_t gr;
|
---|
118 | uint16_t es;
|
---|
119 | uint16_t ds;
|
---|
120 | iret_addr_t ra;
|
---|
121 | } pci_regs_t;
|
---|
122 |
|
---|
123 | #define FLAGS r.ra.flags.u.r16.flags
|
---|
124 |
|
---|
125 | #endif
|
---|
126 |
|
---|
127 | #ifdef __386__
|
---|
128 |
|
---|
129 | /* 32-bit code can just use the compiler intrinsics. */
|
---|
130 | extern unsigned inpd(unsigned port);
|
---|
131 | extern unsigned outpd(unsigned port, unsigned value);
|
---|
132 | #pragma intrinsic(inpd,outpd)
|
---|
133 |
|
---|
134 | #else
|
---|
135 |
|
---|
136 | //@todo: merge with AHCI code
|
---|
137 |
|
---|
138 | /* Warning: Destroys high bits of EAX. */
|
---|
139 | uint32_t inpd(uint16_t port);
|
---|
140 | #pragma aux inpd = \
|
---|
141 | ".386" \
|
---|
142 | "in eax, dx" \
|
---|
143 | "mov dx, ax" \
|
---|
144 | "shr eax, 16" \
|
---|
145 | "xchg ax, dx" \
|
---|
146 | parm [dx] value [dx ax] modify nomemory;
|
---|
147 |
|
---|
148 | /* Warning: Destroys high bits of EAX. */
|
---|
149 | void outpd(uint16_t port, uint32_t val);
|
---|
150 | #pragma aux outpd = \
|
---|
151 | ".386" \
|
---|
152 | "xchg ax, cx" \
|
---|
153 | "shl eax, 16" \
|
---|
154 | "mov ax, cx" \
|
---|
155 | "out dx, eax" \
|
---|
156 | parm [dx] [cx ax] modify nomemory;
|
---|
157 |
|
---|
158 | #endif
|
---|
159 |
|
---|
160 | /* PCI IRQ routing expansion buffer descriptor. */
|
---|
161 | typedef struct {
|
---|
162 | uint16_t buf_size;
|
---|
163 | uint8_t __far *buf_ptr;
|
---|
164 | } pci_route_buf;
|
---|
165 |
|
---|
166 | /* Defined in assembler module .*/
|
---|
167 | extern char pci_routing_table[];
|
---|
168 | extern uint16_t pci_routing_table_size;
|
---|
169 |
|
---|
170 | /* Write the CONFIG_ADDRESS register to prepare for data access. Requires
|
---|
171 | * the register offset to be DWORD aligned (low two bits clear). Warning:
|
---|
172 | * destroys high bits of EAX.
|
---|
173 | */
|
---|
174 | void pci16_w_addr(uint16_t bus_dev_fn, uint16_t ofs, uint16_t cfg_addr);
|
---|
175 | #pragma aux pci16_w_addr = \
|
---|
176 | ".386" \
|
---|
177 | "movzx eax, ax" \
|
---|
178 | "shl eax, 8" \
|
---|
179 | "or eax, 80000000h" \
|
---|
180 | "mov al, bl" \
|
---|
181 | "out dx, eax" \
|
---|
182 | parm [ax] [bx] [dx] modify exact [ax] nomemory;
|
---|
183 |
|
---|
184 |
|
---|
185 | /* Select a PCI configuration register given its offset and bus/dev/fn.
|
---|
186 | * This is largely a wrapper to avoid excessive inlining.
|
---|
187 | */
|
---|
188 | void PCIxx(select_reg)(uint16_t bus_dev_fn, uint16_t ofs)
|
---|
189 | {
|
---|
190 | pci16_w_addr(bus_dev_fn, ofs & ~3, PCI_CFG_ADDR);
|
---|
191 | }
|
---|
192 |
|
---|
193 | /* Selected configuration space offsets. */
|
---|
194 | #define PCI_VEN_ID 0x00
|
---|
195 | #define PCI_DEV_ID 0x02
|
---|
196 | #define PCI_REV_ID 0x08
|
---|
197 | #define PCI_CLASS_CODE 0x09
|
---|
198 | #define PCI_HEADER_TYPE 0x0E
|
---|
199 | #define PCI_BRIDGE_SUBORD 0x1A
|
---|
200 |
|
---|
201 | /* To avoid problems with 16-bit code, we reserve the last possible
|
---|
202 | * bus/dev/fn combination (65,535). Upon reaching this location, the
|
---|
203 | * probing will end.
|
---|
204 | */
|
---|
205 | #define BUSDEVFN_NOT_FOUND 0xFFFF
|
---|
206 |
|
---|
207 | /* In the search algorithm, we decrement the device index every time
|
---|
208 | * a matching device is found. If the requested device is indeed found,
|
---|
209 | * the index will have decremented down to -1/0xFFFF.
|
---|
210 | */
|
---|
211 | #define INDEX_DEV_FOUND 0xFFFF
|
---|
212 |
|
---|
213 | /* Find a specified PCI device, either by vendor+device ID or class.
|
---|
214 | * If index is non-zero, the n-th device will be located.
|
---|
215 | *
|
---|
216 | * Note: This function is somewhat performance critical; since it may
|
---|
217 | * generate a high number of port I/O accesses, it can take a significant
|
---|
218 | * amount of time in cases where the caller is looking for a number of
|
---|
219 | * non-present devices.
|
---|
220 | */
|
---|
221 | uint16_t PCIxx(find_device)(uint32_t search_item, uint16_t index, int search_class)
|
---|
222 | {
|
---|
223 | uint32_t data;
|
---|
224 | uint16_t bus_dev_fn;
|
---|
225 | uint8_t max_bus;
|
---|
226 | uint8_t hdr_type;
|
---|
227 | uint8_t subordinate;
|
---|
228 | int step;
|
---|
229 | int found;
|
---|
230 |
|
---|
231 | if (search_class) {
|
---|
232 | BX_DEBUG_PCI("PCI: Find class %08lX index %u\n",
|
---|
233 | search_item, index);
|
---|
234 | } else
|
---|
235 | BX_DEBUG_PCI("PCI: Find device %04X:%04X index %u\n",
|
---|
236 | (uint16_t)search_item, (uint16_t)(search_item >> 16), index);
|
---|
237 |
|
---|
238 | bus_dev_fn = 0; /* Start at the beginning. */
|
---|
239 | max_bus = 0; /* Initially assume primary bus only. */
|
---|
240 |
|
---|
241 | do {
|
---|
242 | /* For the first function of a device, read the device's header type.
|
---|
243 | * If the header type has all bits set, there's no device. A PCI
|
---|
244 | * multi-function device must implement function 0 and the header type
|
---|
245 | * will be something other than 0xFF. If the header type has the high
|
---|
246 | * bit clear, there is a device but it's not multi-function, so we can
|
---|
247 | * skip probing the next 7 sub-functions.
|
---|
248 | */
|
---|
249 | if ((bus_dev_fn & 7) == 0) {
|
---|
250 | PCIxx(select_reg)(bus_dev_fn, PCI_HEADER_TYPE);
|
---|
251 | hdr_type = inp(PCI_CFG_DATA + (PCI_HEADER_TYPE & 3));
|
---|
252 | if (hdr_type == 0xFF) {
|
---|
253 | bus_dev_fn += 8; /* Skip to next device. */
|
---|
254 | continue;
|
---|
255 | }
|
---|
256 | if (hdr_type & 0x80)
|
---|
257 | step = 1; /* MFD - try every sub-function. */
|
---|
258 | else
|
---|
259 | step = 8; /* No MFD, go to next device after probing. */
|
---|
260 | }
|
---|
261 |
|
---|
262 | /* If the header type indicates a bus, we're interested. The secondary
|
---|
263 | * and subordinate bus numbers will indicate which buses are present;
|
---|
264 | * thus we can determine the highest bus number. In the common case,
|
---|
265 | * there will be only the primary bus (i.e. bus 0) and we can avoid
|
---|
266 | * looking at the remaining 255 theoretically present buses. This check
|
---|
267 | * only needs to be done on the primary bus, since bridges must report
|
---|
268 | * all bridges potentially behind them.
|
---|
269 | */
|
---|
270 | if ((hdr_type & 7) == 1 && (bus_dev_fn >> 8) == 0) {
|
---|
271 | /* Read the subordinate (last) bridge number. */
|
---|
272 | PCIxx(select_reg)(bus_dev_fn, PCI_BRIDGE_SUBORD);
|
---|
273 | subordinate = inp(PCI_CFG_DATA + (PCI_BRIDGE_SUBORD & 3));
|
---|
274 | if (subordinate > max_bus)
|
---|
275 | max_bus = subordinate;
|
---|
276 | }
|
---|
277 |
|
---|
278 | /* Select the appropriate register. */
|
---|
279 | PCIxx(select_reg)(bus_dev_fn, search_class ? PCI_REV_ID : PCI_VEN_ID);
|
---|
280 | data = inpd(PCI_CFG_DATA);
|
---|
281 | found = 0;
|
---|
282 |
|
---|
283 | /* Only 3 bytes are compared for class searches. */
|
---|
284 | if (search_class)
|
---|
285 | data >>= 8;
|
---|
286 |
|
---|
287 | #if 0
|
---|
288 | BX_DEBUG_PCI("PCI: Data is %08lX @ %02X:%%02X:%01X\n", data,
|
---|
289 | bus_dev_fn >> 8, bus_dev_fn >> 3 & 31, bus_dev_fn & 7);
|
---|
290 | #endif
|
---|
291 |
|
---|
292 | if (data == search_item)
|
---|
293 | found = 1;
|
---|
294 |
|
---|
295 | /* If device was found but index is non-zero, decrement index and
|
---|
296 | * continue looking. If requested device was found, index will be -1!
|
---|
297 | */
|
---|
298 | if (found && !index--)
|
---|
299 | break;
|
---|
300 |
|
---|
301 | bus_dev_fn += step;
|
---|
302 | } while ((bus_dev_fn >> 8) <= max_bus);
|
---|
303 |
|
---|
304 | if (index == INDEX_DEV_FOUND)
|
---|
305 | BX_DEBUG_PCI("PCI: Device found (%02X:%%02X:%01X)\n", bus_dev_fn >> 8,
|
---|
306 | bus_dev_fn >> 3 & 31, bus_dev_fn & 7);
|
---|
307 |
|
---|
308 | return index == INDEX_DEV_FOUND ? bus_dev_fn : BUSDEVFN_NOT_FOUND;
|
---|
309 | }
|
---|
310 |
|
---|
311 | void BIOSCALL PCIxx(function)(volatile pci_regs_t r)
|
---|
312 | {
|
---|
313 | pci_route_buf __far *route_buf;
|
---|
314 | uint16_t device;
|
---|
315 |
|
---|
316 | BX_DEBUG_PCI("PCI: AX=%04X BX=%04X CX=%04X DI=%04X\n", AX, BX, CX, DI);
|
---|
317 |
|
---|
318 | SET_AH(SUCCESSFUL); /* Assume success. */
|
---|
319 | CLEAR_CF();
|
---|
320 |
|
---|
321 | switch (GET_AL()) {
|
---|
322 | case PCI_BIOS_PRESENT:
|
---|
323 | AX = 0x0001; /* Configuration mechanism #1 supported. */
|
---|
324 | BX = 0x0210; /* Version 2.1. */
|
---|
325 | //@todo: return true max bus # in CL
|
---|
326 | CX = 0; /* Maximum bus number. */
|
---|
327 | EDX = 'P' | ('C' << 8) | ((uint32_t)'I' << 16) | ((uint32_t)' ' << 24);
|
---|
328 | break;
|
---|
329 | case FIND_PCI_DEVICE:
|
---|
330 | /* Vendor ID FFFFh is reserved so that non-present devices can
|
---|
331 | * be easily detected.
|
---|
332 | */
|
---|
333 | if (DX == 0xFFFF) {
|
---|
334 | SET_AH(BAD_VENDOR_ID);
|
---|
335 | SET_CF();
|
---|
336 | } else {
|
---|
337 | device = PCIxx(find_device)(DX | (uint32_t)CX << 16, SI, 0);
|
---|
338 | if (device == BUSDEVFN_NOT_FOUND) {
|
---|
339 | SET_AH(DEVICE_NOT_FOUND);
|
---|
340 | SET_CF();
|
---|
341 | } else {
|
---|
342 | BX = device;
|
---|
343 | }
|
---|
344 | }
|
---|
345 | break;
|
---|
346 | case FIND_PCI_CLASS_CODE:
|
---|
347 | device = PCIxx(find_device)(ECX, SI, 1);
|
---|
348 | if (device == BUSDEVFN_NOT_FOUND) {
|
---|
349 | SET_AH(DEVICE_NOT_FOUND);
|
---|
350 | SET_CF();
|
---|
351 | } else {
|
---|
352 | BX = device;
|
---|
353 | }
|
---|
354 | break;
|
---|
355 | case READ_CONFIG_BYTE:
|
---|
356 | case READ_CONFIG_WORD:
|
---|
357 | case READ_CONFIG_DWORD:
|
---|
358 | case WRITE_CONFIG_BYTE:
|
---|
359 | case WRITE_CONFIG_WORD:
|
---|
360 | case WRITE_CONFIG_DWORD:
|
---|
361 | if (DI >= 256) {
|
---|
362 | SET_AH(BAD_REGISTER_NUMBER);
|
---|
363 | SET_CF();
|
---|
364 | } else {
|
---|
365 | PCIxx(select_reg)(BX, DI);
|
---|
366 | switch (GET_AL()) {
|
---|
367 | case READ_CONFIG_BYTE:
|
---|
368 | SET_CL(inp(PCI_CFG_DATA + (DI & 3)));
|
---|
369 | break;
|
---|
370 | case READ_CONFIG_WORD:
|
---|
371 | CX = inpw(PCI_CFG_DATA + (DI & 2));
|
---|
372 | break;
|
---|
373 | case READ_CONFIG_DWORD:
|
---|
374 | ECX = inpd(PCI_CFG_DATA);
|
---|
375 | break;
|
---|
376 | case WRITE_CONFIG_BYTE:
|
---|
377 | outp(PCI_CFG_DATA + (DI & 3), GET_CL());
|
---|
378 | break;
|
---|
379 | case WRITE_CONFIG_WORD:
|
---|
380 | outpw(PCI_CFG_DATA + (DI & 2), CX);
|
---|
381 | break;
|
---|
382 | case WRITE_CONFIG_DWORD:
|
---|
383 | outpd(PCI_CFG_DATA, ECX);
|
---|
384 | break;
|
---|
385 | }
|
---|
386 | }
|
---|
387 | break;
|
---|
388 | case GET_IRQ_ROUTING:
|
---|
389 | route_buf = ES :> (void *)DI;
|
---|
390 | if (pci_routing_table_size > route_buf->buf_size) {
|
---|
391 | SET_AH(BUFFER_TOO_SMALL);
|
---|
392 | SET_CF();
|
---|
393 | } else {
|
---|
394 | rep_movsb(route_buf->buf_ptr, pci_routing_table, pci_routing_table_size);
|
---|
395 | /* IRQs 9 and 11 are PCI only. */
|
---|
396 | BX = (1 << 9) | (1 << 11);
|
---|
397 | }
|
---|
398 | break;
|
---|
399 | default:
|
---|
400 | BX_INFO("PCI: Unsupported function AX=%04X BX=%04X called\n", AX, BX);
|
---|
401 | SET_AH(FUNC_NOT_SUPPORTED);
|
---|
402 | SET_CF();
|
---|
403 | }
|
---|
404 | }
|
---|