pcibios.c revision 61dd3960a1ce187031dda7803b06169a68e36167
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync/* $Id$ */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync/** @file
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * PCI BIOS support.
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync/*
c58f1213e628a545081c70e26c6b67a841cff880vboxsync * Copyright (C) 2004-2012 Oracle Corporation
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync *
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * This file is part of VirtualBox Open Source Edition (OSE), as
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * available from http://www.virtualbox.org. This file is free software;
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * you can redistribute it and/or modify it under the terms of the GNU
6eea1c2a48951b898f17a82b2432e5233226d6cdvboxsync * General Public License (GPL) as published by the Free Software
6eea1c2a48951b898f17a82b2432e5233226d6cdvboxsync * Foundation, in version 2 as it comes in the "COPYING" file of the
6eea1c2a48951b898f17a82b2432e5233226d6cdvboxsync * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
6eea1c2a48951b898f17a82b2432e5233226d6cdvboxsync * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#include <stdint.h>
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#include <string.h>
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#include "biosint.h"
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#include "inlines.h"
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#if DEBUG_PCI
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync# define BX_DEBUG_PCI(...) BX_DEBUG(__VA_ARGS__)
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#else
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync# define BX_DEBUG_PCI(...)
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#endif
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsync/* PCI function codes. */
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsyncenum pci_func {
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsync PCI_BIOS_PRESENT = 0x01, /* PCI BIOS presence check. */
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsync FIND_PCI_DEVICE = 0x02, /* Find PCI device by ID. */
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsync FIND_PCI_CLASS_CODE = 0x03, /* Find PCI device by class. */
193a3d2712f792f29e133722eebd0ee9d61358c6vboxsync GEN_SPECIAL_CYCLE = 0x06, /* Generate special cycle. */
fd647f0a684c087d1481c46d9aa85962e0341b1fvboxsync READ_CONFIG_BYTE = 0x08, /* Read a byte from PCI config space. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync READ_CONFIG_WORD = 0x09, /* Read a word from PCI config space. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync READ_CONFIG_DWORD = 0x0A, /* Read a dword from PCI config space. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync WRITE_CONFIG_BYTE = 0x0B, /* Write a byte to PCI config space. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync WRITE_CONFIG_WORD = 0x0C, /* Write a word to PCI config space. */
010b49150eff471de701712aaa3e643d56a2fd73vboxsync WRITE_CONFIG_DWORD = 0x0D, /* Write a dword to PCI config space. */
010b49150eff471de701712aaa3e643d56a2fd73vboxsync GET_IRQ_ROUTING = 0x0E, /* Get IRQ routing table. */
010b49150eff471de701712aaa3e643d56a2fd73vboxsync SET_PCI_HW_INT = 0x0F, /* Set PCI hardware interrupt. */
010b49150eff471de701712aaa3e643d56a2fd73vboxsync};
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsyncenum pci_error {
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync SUCCESSFUL = 0x00, /* Success. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync FUNC_NOT_SUPPORTED = 0x81, /* Unsupported function. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync BAD_VENDOR_ID = 0x83, /* Bad vendor ID (all bits set) passed. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync DEVICE_NOT_FOUND = 0x86, /* No matching device found. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync BAD_REGISTER_NUMBER = 0x87, /* Register number out of range. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync SET_FAILED = 0x88, /* Failed to set PCI interrupt. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync BUFFER_TOO_SMALL = 0x89 /* Routing table buffer insufficient. */
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync};
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync// @todo: merge with system.c
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define AX r.gr.u.r16.ax
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define BX r.gr.u.r16.bx
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define CX r.gr.u.r16.cx
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define DX r.gr.u.r16.dx
fd647f0a684c087d1481c46d9aa85962e0341b1fvboxsync#define SI r.gr.u.r16.si
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define DI r.gr.u.r16.di
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define BP r.gr.u.r16.bp
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define SP r.gr.u.r16.sp
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define EAX r.gr.u.r32.eax
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define EBX r.gr.u.r32.ebx
ad27e1d5e48ca41245120c331cc88b50464813cevboxsync#define ECX r.gr.u.r32.ecx
fd647f0a684c087d1481c46d9aa85962e0341b1fvboxsync#define EDX r.gr.u.r32.edx
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync#define ES r.es
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync/* The 16-bit PCI BIOS service must be callable from both real and protected
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * mode. In protected mode, the caller must set the CS selector base to F0000h
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * (but the CS selector value is not specified!). The caller does not always
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * provide a DS which covers the BIOS segment.
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync *
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * Unlike APM, there are no provisions for the 32-bit PCI BIOS interface
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync * calling the 16-bit implementation.
f7ac348ea0a65d69e3ddb443f5cb3f449e9ad825vboxsync *
* 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 es;
uint16_t ds;
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 INDEX_NOT_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.
*
* 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)
{
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 bytes are compared for class searches. */
if (search_class)
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_NOT_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_NOT_FOUND ? bus_dev_fn : INDEX_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\n", AX, BX, CX);
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);
if (device == INDEX_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);
if (device == INDEX_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;
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);
}
break;
default:
BX_INFO("PCI: Unsupported function AX=%04X BX=%04X called\n", AX, BX);
SET_AH(FUNC_NOT_SUPPORTED);
SET_CF();
}
}