summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/pcibios.c
blob: 5e52e05ae7ea3cbb61802fec50e9e316d96bf1cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/* $Id: pcibios.c $ */
/** @file
 * PCI BIOS support.
 */

/*
 * Copyright (C) 2004-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

#include <stdint.h>
#include <string.h>
#include "biosint.h"
#include "inlines.h"

#if DEBUG_PCI
#  define BX_DEBUG_PCI(...) BX_DEBUG(__VA_ARGS__)
#else
#  define BX_DEBUG_PCI(...)
#endif

/* PCI function codes. */
enum pci_func {
    PCI_BIOS_PRESENT    = 0x01,     /* PCI BIOS presence check. */
    FIND_PCI_DEVICE     = 0x02,     /* Find PCI device by ID. */
    FIND_PCI_CLASS_CODE = 0x03,     /* Find PCI device by class. */
    GEN_SPECIAL_CYCLE   = 0x06,     /* Generate special cycle. */
    READ_CONFIG_BYTE    = 0x08,     /* Read a byte from PCI config space. */
    READ_CONFIG_WORD    = 0x09,     /* Read a word from PCI config space. */
    READ_CONFIG_DWORD   = 0x0A,     /* Read a dword from PCI config space. */
    WRITE_CONFIG_BYTE   = 0x0B,     /* Write a byte to PCI config space. */
    WRITE_CONFIG_WORD   = 0x0C,     /* Write a word to PCI config space. */
    WRITE_CONFIG_DWORD  = 0x0D,     /* Write a dword to PCI config space. */
    GET_IRQ_ROUTING     = 0x0E,     /* Get IRQ routing table. */
    SET_PCI_HW_INT      = 0x0F,     /* Set PCI hardware interrupt. */
};

enum pci_error {
    SUCCESSFUL          = 0x00,     /* Success. */
    FUNC_NOT_SUPPORTED  = 0x81,     /* Unsupported function. */
    BAD_VENDOR_ID       = 0x83,     /* Bad vendor ID (all bits set) passed. */
    DEVICE_NOT_FOUND    = 0x86,     /* No matching device found. */
    BAD_REGISTER_NUMBER = 0x87,     /* Register number out of range. */
    SET_FAILED          = 0x88,     /* Failed to set PCI interrupt. */
    BUFFER_TOO_SMALL    = 0x89      /* Routing table buffer insufficient. */
};

/// @todo merge with system.c
#define AX      r.gr.u.r16.ax
#define BX      r.gr.u.r16.bx
#define CX      r.gr.u.r16.cx
#define DX      r.gr.u.r16.dx
#define SI      r.gr.u.r16.si
#define DI      r.gr.u.r16.di
#define BP      r.gr.u.r16.bp
#define SP      r.gr.u.r16.sp
#define EAX     r.gr.u.r32.eax
#define EBX     r.gr.u.r32.ebx
#define ECX     r.gr.u.r32.ecx
#define EDX     r.gr.u.r32.edx
#define ES      r.es

/* The 16-bit PCI BIOS service must be callable from both real and protected
 * mode. In protected mode, the caller must set the CS selector base to F0000h
 * (but the CS selector value is not specified!). The caller does not always
 * provide a DS which covers the BIOS segment.
 *
 * Unlike APM, there are no provisions for the 32-bit PCI BIOS interface
 * calling the 16-bit implementation.
 *
 * The PCI Firmware Specification requires that the PCI BIOS service is called
 * with at least 1,024 bytes of stack space available, that interrupts are not
 * enabled during execution, and that the routines are re-entrant.
 *
 * Implementation notes:
 * - The PCI BIOS interface already uses certain 32-bit registers even in
 * 16-bit mode. To simplify matters, all 32-bit GPRs are saved/restored and
 * may be used by helper routines (notably for 32-bit port I/O).
 */

#define PCI_CFG_ADDR    0xCF8
#define PCI_CFG_DATA    0xCFC

#ifdef __386__

#define PCIxx(x)    pci32_##x

/* The stack layout is different in 32-bit mode. */
typedef struct {
    pushad_regs_t   gr;
    uint32_t        es;
    uint32_t        flags;
} pci_regs_t;

#define FLAGS   r.flags

/* In 32-bit mode, don't do any output; not technically impossible but needs
 * a lot of extra code.
 */
#undef  BX_INFO
#define BX_INFO(...)
#undef  BX_DEBUG_PCI
#define BX_DEBUG_PCI(...)

#else

#define PCIxx(x)    pci16_##x

typedef struct {
    pushad_regs_t   gr;
    uint16_t        ds;
    uint16_t        es;
    iret_addr_t     ra;
} pci_regs_t;

#define FLAGS   r.ra.flags.u.r16.flags

#endif

#ifdef __386__

/* 32-bit code can just use the compiler intrinsics. */
extern unsigned inpd(unsigned port);
extern unsigned outpd(unsigned port, unsigned value);
#pragma intrinsic(inpd,outpd)

#else

/// @todo merge with AHCI code

/* Warning: Destroys high bits of EAX. */
uint32_t inpd(uint16_t port);
#pragma aux inpd =      \
    ".386"              \
    "in     eax, dx"    \
    "mov    dx, ax"     \
    "shr    eax, 16"    \
    "xchg   ax, dx"     \
    parm [dx] value [dx ax] modify nomemory;

/* Warning: Destroys high bits of EAX. */
void outpd(uint16_t port, uint32_t val);
#pragma aux outpd =     \
    ".386"              \
    "xchg   ax, cx"     \
    "shl    eax, 16"    \
    "mov    ax, cx"     \
    "out    dx, eax"    \
    parm [dx] [cx ax] modify nomemory;

#endif

/* PCI IRQ routing expansion buffer descriptor. */
typedef struct {
    uint16_t        buf_size;
    uint8_t __far   *buf_ptr;
} pci_route_buf;

/* Defined in assembler module .*/
extern char     pci_routing_table[];
extern uint16_t pci_routing_table_size;

/* Write the CONFIG_ADDRESS register to prepare for data access. Requires
 * the register offset to be DWORD aligned (low two bits clear). Warning:
 * destroys high bits of EAX.
 */
void pci16_w_addr(uint16_t bus_dev_fn, uint16_t ofs, uint16_t cfg_addr);
#pragma aux pci16_w_addr =      \
    ".386"                      \
    "movzx  eax, ax"            \
    "shl    eax, 8"             \
    "or     eax, 80000000h"     \
    "mov    al, bl"             \
    "out    dx, eax"            \
    parm [ax] [bx] [dx] modify exact [ax] nomemory;


/* Select a PCI configuration register given its offset and bus/dev/fn.
 * This is largely a wrapper to avoid excessive inlining.
 */
void PCIxx(select_reg)(uint16_t bus_dev_fn, uint16_t ofs)
{
    pci16_w_addr(bus_dev_fn, ofs & ~3, PCI_CFG_ADDR);
}

/* Selected configuration space offsets. */
#define PCI_VEN_ID          0x00
#define PCI_DEV_ID          0x02
#define PCI_REV_ID          0x08
#define PCI_CLASS_CODE      0x09
#define PCI_HEADER_TYPE     0x0E
#define PCI_BRIDGE_SUBORD   0x1A

/* To avoid problems with 16-bit code, we reserve the last possible
 * bus/dev/fn combination (65,535). Upon reaching this location, the
 * probing will end.
 */
#define BUSDEVFN_NOT_FOUND  0xFFFF

/* In the search algorithm, we decrement the device index every time
 * a matching device is found. If the requested device is indeed found,
 * the index will have decremented down to -1/0xFFFF.
 */
#define INDEX_DEV_FOUND     0xFFFF

/* Find a specified PCI device, either by vendor+device ID or class.
 * If index is non-zero, the n-th device will be located. When searching
 * by class, the ignore_if flag only compares the base and sub-class code,
 * ignoring the programming interface code.
 *
 * Note: This function is somewhat performance critical; since it may
 * generate a high number of port I/O accesses, it can take a significant
 * amount of time in cases where the caller is looking for a number of
 * non-present devices.
 */
uint16_t PCIxx(find_device)(uint32_t search_item, uint16_t index, int search_class, int ignore_if)
{
    uint32_t    data;
    uint16_t    bus_dev_fn;
    uint8_t     max_bus;
    uint8_t     hdr_type;
    uint8_t     subordinate;
    int         step;
    int         found;

    if (search_class) {
        BX_DEBUG_PCI("PCI: Find class %08lX index %u\n",
                     search_item, index);
    } else
        BX_DEBUG_PCI("PCI: Find device %04X:%04X index %u\n",
                     (uint16_t)search_item, (uint16_t)(search_item >> 16), index);

    bus_dev_fn = 0;     /* Start at the beginning. */
    max_bus    = 0;     /* Initially assume primary bus only. */

    do {
        /* For the first function of a device, read the device's header type.
         * If the header type has all bits set, there's no device. A PCI
         * multi-function device must implement function 0 and the header type
         * will be something other than 0xFF. If the header type has the high
         * bit clear, there is a device but it's not multi-function, so we can
         * skip probing the next 7 sub-functions.
         */
        if ((bus_dev_fn & 7) == 0) {
            PCIxx(select_reg)(bus_dev_fn, PCI_HEADER_TYPE);
            hdr_type = inp(PCI_CFG_DATA + (PCI_HEADER_TYPE & 3));
            if (hdr_type == 0xFF) {
                bus_dev_fn += 8;    /* Skip to next device. */
                continue;
            }
            if (hdr_type & 0x80)
                step = 1;   /* MFD - try every sub-function. */
            else
                step = 8;   /* No MFD, go to next device after probing. */
        }

        /* If the header type indicates a bus, we're interested. The secondary
         * and subordinate bus numbers will indicate which buses are present;
         * thus we can determine the highest bus number. In the common case,
         * there will be only the primary bus (i.e. bus 0) and we can avoid
         * looking at the remaining 255 theoretically present buses. This check
         * only needs to be done on the primary bus, since bridges must report
         * all bridges potentially behind them.
         */
        if ((hdr_type & 7) == 1 && (bus_dev_fn >> 8) == 0) {
            /* Read the subordinate (last) bridge number. */
            PCIxx(select_reg)(bus_dev_fn, PCI_BRIDGE_SUBORD);
            subordinate = inp(PCI_CFG_DATA + (PCI_BRIDGE_SUBORD & 3));
            if (subordinate > max_bus)
                max_bus = subordinate;
        }

        /* Select the appropriate register. */
        PCIxx(select_reg)(bus_dev_fn, search_class ? PCI_REV_ID : PCI_VEN_ID);
        data  = inpd(PCI_CFG_DATA);
        found = 0;

        /* Only 3 or even just 2 bytes are compared for class searches. */
        if (search_class)
            if (ignore_if)
                data >>= 16;
            else
                data >>= 8;

#if 0
        BX_DEBUG_PCI("PCI: Data is %08lX @ %02X:%%02X:%01X\n", data,
                     bus_dev_fn >> 8, bus_dev_fn >> 3 & 31, bus_dev_fn & 7);
#endif

        if (data == search_item)
            found = 1;

        /* If device was found but index is non-zero, decrement index and
         * continue looking. If requested device was found, index will be -1!
         */
        if (found && !index--)
            break;

        bus_dev_fn += step;
    } while ((bus_dev_fn >> 8) <= max_bus);

    if (index == INDEX_DEV_FOUND)
        BX_DEBUG_PCI("PCI: Device found (%02X:%%02X:%01X)\n", bus_dev_fn >> 8,
                     bus_dev_fn >> 3 & 31, bus_dev_fn & 7);

    return index == INDEX_DEV_FOUND ? bus_dev_fn : BUSDEVFN_NOT_FOUND;
}

void BIOSCALL PCIxx(function)(volatile pci_regs_t r)
{
    pci_route_buf __far     *route_buf;
    uint16_t                device;

    BX_DEBUG_PCI("PCI: AX=%04X BX=%04X CX=%04X DI=%04X\n", AX, BX, CX, DI);

    SET_AH(SUCCESSFUL);     /* Assume success. */
    CLEAR_CF();

    switch (GET_AL()) {
    case PCI_BIOS_PRESENT:
        AX  = 0x0001;   /* Configuration mechanism #1 supported. */
        BX  = 0x0210;   /* Version 2.1. */
        /// @todo return true max bus # in CL
        CX  = 0;        /* Maximum bus number. */
        EDX = 'P' | ('C' << 8) | ((uint32_t)'I' << 16) | ((uint32_t)' ' << 24);
        break;
    case FIND_PCI_DEVICE:
        /* Vendor ID FFFFh is reserved so that non-present devices can
         * be easily detected.
         */
        if (DX == 0xFFFF) {
            SET_AH(BAD_VENDOR_ID);
            SET_CF();
        } else {
            device = PCIxx(find_device)(DX | (uint32_t)CX << 16, SI, 0, 0);
            if (device == BUSDEVFN_NOT_FOUND) {
                SET_AH(DEVICE_NOT_FOUND);
                SET_CF();
            } else {
                BX = device;
            }
        }
        break;
    case FIND_PCI_CLASS_CODE:
        device = PCIxx(find_device)(ECX, SI, 1, 0);
        if (device == BUSDEVFN_NOT_FOUND) {
            SET_AH(DEVICE_NOT_FOUND);
            SET_CF();
        } else {
            BX = device;
        }
        break;
    case READ_CONFIG_BYTE:
    case READ_CONFIG_WORD:
    case READ_CONFIG_DWORD:
    case WRITE_CONFIG_BYTE:
    case WRITE_CONFIG_WORD:
    case WRITE_CONFIG_DWORD:
        if (DI >= 256) {
            SET_AH(BAD_REGISTER_NUMBER);
            SET_CF();
        } else {
            PCIxx(select_reg)(BX, DI);
            switch (GET_AL()) {
            case READ_CONFIG_BYTE:
                SET_CL(inp(PCI_CFG_DATA + (DI & 3)));
                break;
            case READ_CONFIG_WORD:
                CX = inpw(PCI_CFG_DATA + (DI & 2));
                break;
            case READ_CONFIG_DWORD:
                ECX = inpd(PCI_CFG_DATA);
                break;
            case WRITE_CONFIG_BYTE:
                outp(PCI_CFG_DATA + (DI & 3), GET_CL());
                break;
            case WRITE_CONFIG_WORD:
                outpw(PCI_CFG_DATA + (DI & 2), CX);
                break;
            case WRITE_CONFIG_DWORD:
                outpd(PCI_CFG_DATA, ECX);
                break;
            }
        }
        break;
    case GET_IRQ_ROUTING:
        route_buf = ES :> (void *)DI;
        BX_DEBUG_PCI("PCI: Route Buf %04X:%04X size %04X, need %04X (at %04X:%04X)\n",
                     FP_SEG(route_buf->buf_ptr), FP_OFF(route_buf->buf_ptr),
                     route_buf->buf_size, pci_routing_table_size, ES, DI);
        if (pci_routing_table_size > route_buf->buf_size) {
            SET_AH(BUFFER_TOO_SMALL);
            SET_CF();
        } else {
            rep_movsb(route_buf->buf_ptr, pci_routing_table, pci_routing_table_size);
            /* IRQs 9 and 11 are PCI only. */
            BX = (1 << 9) | (1 << 11);
        }
        route_buf->buf_size = pci_routing_table_size;
        break;
    default:
        BX_INFO("PCI: Unsupported function AX=%04X BX=%04X called\n", AX, BX);
        SET_AH(FUNC_NOT_SUPPORTED);
        SET_CF();
    }
}