blob: 1d542fc5df51cb8d2d49842df4e134a016c4d472 [file] [log] [blame]
/****************************************************************************
*
* SciTech OS Portability Manager Library
*
* ========================================================================
*
* The contents of this file are subject to the SciTech MGL Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.scitechsoft.com/mgl-license.txt
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
*
* The Initial Developer of the Original Code is SciTech Software, Inc.
* All Rights Reserved.
*
* ========================================================================
*
* Language: ANSI C
* Environment: Any
*
* Description: Module for interfacing to the PCI bus and configuration
* space registers.
*
****************************************************************************/
#include "pmapi.h"
#include "pcilib.h"
#if !defined(__WIN32_VXD__) && !defined(__NT_DRIVER__)
#include <string.h>
#endif
/*---------------------- Macros and type definitions ----------------------*/
#pragma pack(1)
/* Length of the memory mapping for the PCI BIOS */
#define BIOS_LIMIT (128 * 1024L - 1)
/* Macros for accessing the PCI BIOS functions from 32-bit protected mode */
#define BIOS32_SIGNATURE (((ulong)'_' << 0) + ((ulong)'3' << 8) + ((ulong)'2' << 16) + ((ulong)'_' << 24))
#define PCI_SIGNATURE (((ulong)'P' << 0) + ((ulong)'C' << 8) + ((ulong)'I' << 16) + ((ulong)' ' << 24))
#define PCI_SERVICE (((ulong)'$' << 0) + ((ulong)'P' << 8) + ((ulong)'C' << 16) + ((ulong)'I' << 24))
#define PCI_BIOS_PRESENT 0xB101
#define FIND_PCI_DEVICE 0xB102
#define FIND_PCI_CLASS 0xB103
#define GENERATE_SPECIAL 0xB106
#define READ_CONFIG_BYTE 0xB108
#define READ_CONFIG_WORD 0xB109
#define READ_CONFIG_DWORD 0xB10A
#define WRITE_CONFIG_BYTE 0xB10B
#define WRITE_CONFIG_WORD 0xB10C
#define WRITE_CONFIG_DWORD 0xB10D
#define GET_IRQ_ROUTING_OPT 0xB10E
#define SET_PCI_IRQ 0xB10F
/* This is the standard structure used to identify the entry point to the
* BIOS32 Service Directory, as documented in PCI 2.1 BIOS Specicition.
*/
typedef union {
struct {
ulong signature; /* _32_ */
ulong entry; /* 32 bit physical address */
uchar revision; /* Revision level, 0 */
uchar length; /* Length in paragraphs should be 01 */
uchar checksum; /* All bytes must add up to zero */
uchar reserved[5]; /* Must be zero */
} fields;
char chars[16];
} PCI_bios32;
/* Structure for a far pointer to call the PCI BIOS services with */
typedef struct {
ulong address;
ushort segment;
} PCIBIOS_entry;
/* Macros to copy a structure that includes dwSize members */
#define COPY_STRUCTURE(d,s) memcpy(d,s,MIN((s)->dwSize,(d)->dwSize))
#pragma pack()
/*--------------------------- Global variables ----------------------------*/
static uchar *BIOSImage = NULL; /* BIOS image mapping */
static int PCIBIOSVersion = -1;/* PCI BIOS version */
static PCIBIOS_entry PCIEntry; /* PCI services entry point */
static ulong PCIPhysEntry = 0; /* Physical address */
/*----------------------------- Implementation ----------------------------*/
/* External assembler helper functions */
uchar _ASMAPI _BIOS32_service(ulong service,ulong function,ulong *physBase,ulong *length,ulong *serviceOffset,PCIBIOS_entry entry);
ushort _ASMAPI _PCIBIOS_isPresent(ulong i_eax,ulong *o_edx,ushort *o_ax,uchar *o_cl,PCIBIOS_entry entry);
ulong _ASMAPI _PCIBIOS_service(ulong r_eax,ulong r_ebx,ulong r_edi,ulong r_ecx,PCIBIOS_entry entry);
int _ASMAPI _PCIBIOS_getRouting(PCIRoutingOptionsBuffer *buf,PCIBIOS_entry entry);
ibool _ASMAPI _PCIBIOS_setIRQ(int busDev,int intPin,int IRQ,PCIBIOS_entry entry);
ulong _ASMAPI _PCIBIOS_specialCycle(int bus,ulong data,PCIBIOS_entry entry);
ushort _ASMAPI _PCI_getCS(void);
/****************************************************************************
REMARKS:
This functions returns the physical address of the PCI BIOS entry point.
****************************************************************************/
ulong _ASMAPI PCIBIOS_getEntry(void)
{ return PCIPhysEntry; }
/****************************************************************************
PARAMETERS:
hwType - Place to store the PCI hardware access mechanism flags
lastBus - Place to store the index of the last PCI bus in the system
RETURNS:
Version number of the PCI BIOS found.
REMARKS:
This function determines if the PCI BIOS is present in the system, and if
so returns the information returned by the PCI BIOS detect function.
****************************************************************************/
static int PCIBIOS_detect(
uchar *hwType,
uchar *lastBus)
{
ulong signature;
ushort stat,version;
#ifndef __16BIT__
PCIBIOS_entry BIOSEntry = {0};
uchar *BIOSEnd;
PCI_bios32 *BIOSDir;
ulong physBase,length,offset;
/* Bail if we have already detected no BIOS is present */
if (PCIBIOSVersion == 0)
return 0;
/* First scan the memory from 0xE0000 to 0xFFFFF looking for the
* BIOS32 service directory, so we can determine if we can call it
* from 32-bit protected mode.
*/
if (PCIBIOSVersion == -1) {
PCIBIOSVersion = 0;
BIOSImage = PM_mapPhysicalAddr(0xE0000,BIOS_LIMIT,false);
if (!BIOSImage)
return 0;
BIOSEnd = BIOSImage + 0x20000;
for (BIOSDir = (PCI_bios32*)BIOSImage; BIOSDir < (PCI_bios32*)BIOSEnd; BIOSDir++) {
uchar sum;
int i,length;
if (BIOSDir->fields.signature != BIOS32_SIGNATURE)
continue;
length = BIOSDir->fields.length * 16;
if (!length)
continue;
for (sum = i = 0; i < length ; i++)
sum += BIOSDir->chars[i];
if (sum != 0)
continue;
BIOSEntry.address = (ulong)BIOSImage + (BIOSDir->fields.entry - 0xE0000);
BIOSEntry.segment = _PCI_getCS();
break;
}
/* If we found the BIOS32 directory, call it to get the address of the
* PCI services.
*/
if (BIOSEntry.address == 0)
return 0;
if (_BIOS32_service(PCI_SERVICE,0,&physBase,&length,&offset,BIOSEntry) != 0)
return 0;
PCIPhysEntry = physBase + offset;
PCIEntry.address = (ulong)BIOSImage + (PCIPhysEntry - 0xE0000);
PCIEntry.segment = _PCI_getCS();
}
#endif
/* We found the BIOS entry, so now do the version check */
version = _PCIBIOS_isPresent(PCI_BIOS_PRESENT,&signature,&stat,lastBus,PCIEntry);
if (version > 0 && ((stat >> 8) == 0) && signature == PCI_SIGNATURE) {
*hwType = stat & 0xFF;
return PCIBIOSVersion = version;
}
return 0;
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to check against
index - Index of the current device to check
RETURNS:
True if the device is a duplicate, false if not.
REMARKS:
This function goes through the list of all devices preceeding the newly
found device in the info structure, and checks that the device is not a
duplicate of a previous device. Some devices incorrectly enumerate
themselves at different function addresses so we check here to exclude
those cases.
****************************************************************************/
static ibool CheckDuplicate(
PCIDeviceInfo *info,
PCIDeviceInfo *prev)
{
/* Ignore devices with a vendor ID of 0 */
if (info->VendorID == 0)
return true;
/* NOTE: We only check against the current device on
* the bus to ensure that we do not exclude
* multiple controllers of the same device ID.
*/
if (info->slot.p.Bus == prev->slot.p.Bus &&
info->slot.p.Device == prev->slot.p.Device &&
info->DeviceID == prev->DeviceID)
return true;
return false;
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to fill in
maxDevices - Maximum number of of devices to enumerate into array
RETURNS:
Number of PCI devices found and enumerated on the PCI bus, 0 if not PCI.
REMARKS:
Function to enumerate all available devices on the PCI bus into an array
of configuration information blocks.
****************************************************************************/
static int PCI_enumerateMech1(
PCIDeviceInfo info[])
{
int bus,device,function,i,numFound = 0;
ulong *lp,tmp;
PCIslot slot = {{0,0,0,0,0,0,1}};
PCIDeviceInfo pci,prev = {0};
/* Try PCI access mechanism 1 */
PM_outpb(0xCFB,0x01);
tmp = PM_inpd(0xCF8);
PM_outpd(0xCF8,slot.i);
if ((PM_inpd(0xCF8) == slot.i) && (PM_inpd(0xCFC) != 0xFFFFFFFFUL)) {
/* PCI access mechanism 1 - the preferred mechanism */
for (bus = 0; bus < 8; bus++) {
slot.p.Bus = bus;
for (device = 0; device < 32; device++) {
slot.p.Device = device;
for (function = 0; function < 8; function++) {
slot.p.Function = function;
slot.p.Register = 0;
PM_outpd(0xCF8,slot.i);
if (PM_inpd(0xCFC) != 0xFFFFFFFFUL) {
memset(&pci,0,sizeof(pci));
pci.dwSize = sizeof(pci);
pci.mech1 = 1;
pci.slot = slot;
lp = (ulong*)&(pci.VendorID);
for (i = 0; i < NUM_PCI_REG; i++, lp++) {
slot.p.Register = i;
PM_outpd(0xCF8,slot.i);
*lp = PM_inpd(0xCFC);
}
if (!CheckDuplicate(&pci,&prev)) {
if (info)
COPY_STRUCTURE(&info[numFound],&pci);
++numFound;
}
prev = pci;
}
}
}
}
/* Disable PCI config cycle on exit */
PM_outpd(0xCF8,0);
return numFound;
}
PM_outpd(0xCF8,tmp);
/* No hardware access mechanism 1 found */
return 0;
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to fill in
maxDevices - Maximum number of of devices to enumerate into array
RETURNS:
Number of PCI devices found and enumerated on the PCI bus, 0 if not PCI.
REMARKS:
Function to enumerate all available devices on the PCI bus into an array
of configuration information blocks.
****************************************************************************/
static int PCI_enumerateMech2(
PCIDeviceInfo info[])
{
int bus,device,function,i,numFound = 0;
ushort deviceIO;
ulong *lp;
PCIslot slot = {{0,0,0,0,0,0,1}};
PCIDeviceInfo pci,prev = {0};
/* Try PCI access mechanism 2 */
PM_outpb(0xCFB,0x00);
PM_outpb(0xCF8,0x00);
PM_outpb(0xCFA,0x00);
if (PM_inpb(0xCF8) == 0x00 && PM_inpb(0xCFB) == 0x00) {
/* PCI access mechanism 2 - the older mechanism for legacy busses */
for (bus = 0; bus < 2; bus++) {
slot.p.Bus = bus;
PM_outpb(0xCFA,(uchar)bus);
for (device = 0; device < 16; device++) {
slot.p.Device = device;
deviceIO = 0xC000 + (device << 8);
for (function = 0; function < 8; function++) {
slot.p.Function = function;
slot.p.Register = 0;
PM_outpb(0xCF8,(uchar)((function << 1) | 0x10));
if (PM_inpd(deviceIO) != 0xFFFFFFFFUL) {
memset(&pci,0,sizeof(pci));
pci.dwSize = sizeof(pci);
pci.mech1 = 0;
pci.slot = slot;
lp = (ulong*)&(pci.VendorID);
for (i = 0; i < NUM_PCI_REG; i++, lp++) {
slot.p.Register = i;
*lp = PM_inpd(deviceIO + (i << 2));
}
if (!CheckDuplicate(&pci,&prev)) {
if (info)
COPY_STRUCTURE(&info[numFound],&pci);
++numFound;
}
prev = pci;
}
}
}
}
/* Disable PCI config cycle on exit */
PM_outpb(0xCF8,0);
return numFound;
}
/* No hardware access mechanism 2 found */
return 0;
}
/****************************************************************************
REMARKS:
This functions reads a configuration dword via the PCI BIOS.
****************************************************************************/
static ulong PCIBIOS_readDWORD(
int index,
ulong slot)
{
return (ulong)_PCIBIOS_service(READ_CONFIG_DWORD,slot >> 8,index,0,PCIEntry);
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to fill in
maxDevices - Maximum number of of devices to enumerate into array
RETURNS:
Number of PCI devices found and enumerated on the PCI bus, 0 if not PCI.
REMARKS:
Function to enumerate all available devices on the PCI bus into an array
of configuration information blocks.
****************************************************************************/
static int PCI_enumerateBIOS(
PCIDeviceInfo info[])
{
uchar hwType,lastBus;
int bus,device,function,i,numFound = 0;
ulong *lp;
PCIslot slot = {{0,0,0,0,0,0,1}};
PCIDeviceInfo pci,prev = {0};
if (PCIBIOS_detect(&hwType,&lastBus)) {
/* PCI BIOS access - the ultimate fallback */
for (bus = 0; bus <= lastBus; bus++) {
slot.p.Bus = bus;
for (device = 0; device < 32; device++) {
slot.p.Device = device;
for (function = 0; function < 8; function++) {
slot.p.Function = function;
if (PCIBIOS_readDWORD(0,slot.i) != 0xFFFFFFFFUL) {
memset(&pci,0,sizeof(pci));
pci.dwSize = sizeof(pci);
pci.mech1 = 2;
pci.slot = slot;
lp = (ulong*)&(pci.VendorID);
for (i = 0; i < NUM_PCI_REG; i++, lp++)
*lp = PCIBIOS_readDWORD(i << 2,slot.i);
if (!CheckDuplicate(&pci,&prev)) {
if (info)
COPY_STRUCTURE(&info[numFound],&pci);
++numFound;
}
prev = pci;
}
}
}
}
}
/* Return number of devices found */
return numFound;
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to fill in
maxDevices - Maximum number of of devices to enumerate into array
RETURNS:
Number of PCI devices found and enumerated on the PCI bus, 0 if not PCI.
REMARKS:
Function to enumerate all available devices on the PCI bus into an array
of configuration information blocks.
****************************************************************************/
int _ASMAPI PCI_enumerate(
PCIDeviceInfo info[])
{
int numFound;
/* First try via the direct access mechanisms which are faster if we
* have them (nearly always). The BIOS is used as a fallback, and for
* stuff we can't do directly.
*/
if ((numFound = PCI_enumerateMech1(info)) == 0) {
if ((numFound = PCI_enumerateMech2(info)) == 0) {
if ((numFound = PCI_enumerateBIOS(info)) == 0)
return 0;
}
}
return numFound;
}
/****************************************************************************
PARAMETERS:
info - Array of PCIDeviceInfo structures to fill in
maxDevices - Maximum number of of devices to enumerate into array
RETURNS:
Number of PCI devices found and enumerated on the PCI bus, 0 if not PCI.
REMARKS:
Function to enumerate all available devices on the PCI bus into an array
of configuration information blocks.
****************************************************************************/
int _ASMAPI PCI_getNumDevices(void)
{
return PCI_enumerate(NULL);
}
/****************************************************************************
PARAMETERS:
bar - Base address to measure
pci - PCI device to access
RETURNS:
Size of the PCI base address in bytes
REMARKS:
This function measures the size of the PCI base address register in bytes,
by writing all F's to the register, and reading the value back. The size
of the base address is determines by the bits that are hardwired to zero's.
****************************************************************************/
ulong _ASMAPI PCI_findBARSize(
int bar,
PCIDeviceInfo *pci)
{
ulong base,size = 0;
base = PCI_accessReg(bar,0,PCI_READ_DWORD,pci);
if (base && !(base & 0x1)) {
/* For some strange reason some devices don't properly decode
* their base address registers (Intel PCI/PCI bridges!), and
* we read completely bogus values. We check for that here
* and clear out those BAR's.
*
* We check for that here because at least the low 12 bits
* of the address range must be zeros, since the page size
* on IA32 processors is always 4Kb.
*/
if ((base & 0xFFF) == 0) {
PCI_accessReg(bar,0xFFFFFFFF,PCI_WRITE_DWORD,pci);
size = PCI_accessReg(bar,0,PCI_READ_DWORD,pci) & ~0xFF;
size = ~size+1;
PCI_accessReg(bar,base,PCI_WRITE_DWORD,pci);
}
}
pci->slot.p.Register = 0;
return size;
}
/****************************************************************************
PARAMETERS:
index - DWORD index of the register to access
value - Value to write to the register for write access
func - Function to implement
RETURNS:
The value read from the register for read operations
REMARKS:
The function code are defined as follows
code - function
0 - Read BYTE
1 - Read WORD
2 - Read DWORD
3 - Write BYTE
4 - Write WORD
5 - Write DWORD
****************************************************************************/
ulong _ASMAPI PCI_accessReg(
int index,
ulong value,
int func,
PCIDeviceInfo *info)
{
int iobase;
if (info->mech1 == 2) {
/* Use PCI BIOS access since we dont have direct hardware access */
switch (func) {
case PCI_READ_BYTE:
return (uchar)_PCIBIOS_service(READ_CONFIG_BYTE,info->slot.i >> 8,index,0,PCIEntry);
case PCI_READ_WORD:
return (ushort)_PCIBIOS_service(READ_CONFIG_WORD,info->slot.i >> 8,index,0,PCIEntry);
case PCI_READ_DWORD:
return (ulong)_PCIBIOS_service(READ_CONFIG_DWORD,info->slot.i >> 8,index,0,PCIEntry);
case PCI_WRITE_BYTE:
_PCIBIOS_service(WRITE_CONFIG_BYTE,info->slot.i >> 8,index,value,PCIEntry);
break;
case PCI_WRITE_WORD:
_PCIBIOS_service(WRITE_CONFIG_WORD,info->slot.i >> 8,index,value,PCIEntry);
break;
case PCI_WRITE_DWORD:
_PCIBIOS_service(WRITE_CONFIG_DWORD,info->slot.i >> 8,index,value,PCIEntry);
break;
}
}
else {
/* Use direct hardware access mechanisms */
if (info->mech1) {
/* PCI access mechanism 1 */
iobase = 0xCFC + (index & 3);
info->slot.p.Register = index >> 2;
PM_outpd(0xCF8,info->slot.i);
}
else {
/* PCI access mechanism 2 */
PM_outpb(0xCF8,(uchar)((info->slot.p.Function << 1) | 0x10));
PM_outpb(0xCFA,(uchar)info->slot.p.Bus);
iobase = 0xC000 + (info->slot.p.Device << 8) + index;
}
switch (func) {
case PCI_READ_BYTE:
case PCI_READ_WORD:
case PCI_READ_DWORD: value = PM_inpd(iobase); break;
case PCI_WRITE_BYTE: PM_outpb(iobase,(uchar)value); break;
case PCI_WRITE_WORD: PM_outpw(iobase,(ushort)value); break;
case PCI_WRITE_DWORD: PM_outpd(iobase,(ulong)value); break;
}
PM_outpd(0xCF8,0);
}
return value;
}
/****************************************************************************
PARAMETERS:
numDevices - Number of devices to query info for
RETURNS:
0 on success, -1 on error, number of devices to enumerate if numDevices = 0
REMARKS:
This function reads the PCI routing information. If you pass a value of
0 for numDevices, this function will return with the number of devices
needed in the routing buffer that will be filled in by the BIOS.
****************************************************************************/
ibool _ASMAPI PCI_getIRQRoutingOptions(
int numDevices,
PCIRouteInfo *buffer)
{
PCIRoutingOptionsBuffer buf;
int ret;
if (PCIPhysEntry) {
buf.BufferSize = numDevices * sizeof(PCIRouteInfo);
buf.DataBuffer = buffer;
if ((ret = _PCIBIOS_getRouting(&buf,PCIEntry)) == 0x89)
return buf.BufferSize / sizeof(PCIRouteInfo);
if (ret != 0)
return -1;
return 0;
}
/* We currently only support this via the PCI BIOS functions */
return -1;
}
/****************************************************************************
PARAMETERS:
info - PCI device information for the specified device
intPin - Value to store in the PCI InterruptPin register
IRQ - New ISA IRQ to map the PCI interrupt to (0-15)
RETURNS:
True on success, or false if this function failed.
REMARKS:
This function changes the PCI IRQ routing for the specified device to the
desired PCI interrupt and the desired ISA bus compatible IRQ. This function
may not be supported by the PCI BIOS, in which case this function will
fail.
****************************************************************************/
ibool _ASMAPI PCI_setHardwareIRQ(
PCIDeviceInfo *info,
uint intPin,
uint IRQ)
{
if (PCIPhysEntry) {
if (_PCIBIOS_setIRQ(info->slot.i >> 8,intPin,IRQ,PCIEntry)) {
info->u.type0.InterruptPin = intPin;
info->u.type0.InterruptLine = IRQ;
return true;
}
return false;
}
/* We currently only support this via the PCI BIOS functions */
return false;
}
/****************************************************************************
PARAMETERS:
bus - Bus number to generate the special cycle for
specialCycleData - Data to send for the special cyle
REMARKS:
This function generates a special cycle on the specified bus using with
the specified data.
****************************************************************************/
void _ASMAPI PCI_generateSpecialCyle(
uint bus,
ulong specialCycleData)
{
if (PCIPhysEntry)
_PCIBIOS_specialCycle(bus,specialCycleData,PCIEntry);
/* We currently only support this via the PCI BIOS functions */
}
/****************************************************************************
PARAMETERS:
info - PCI device information block for device to access
index - Index of register to start reading from
dst - Place to store the values read from configuration space
count - Count of bytes to read from configuration space
REMARKS:
This function is used to read a block of PCI configuration space registers
from the configuration space into the passed in data block. This function
will properly handle reading non-DWORD aligned data from the configuration
space correctly.
****************************************************************************/
void _ASMAPI PCI_readRegBlock(
PCIDeviceInfo *info,
int index,
void *dst,
int count)
{
uchar *pb;
ulong *pd;
int i;
int startCount = (index & 3);
int middleCount = (count - startCount) >> 2;
int endCount = count - middleCount * 4 - startCount;
for (i = 0,pb = dst; i < startCount; i++, index++) {
*pb++ = (uchar)PCI_accessReg(index,0,PCI_READ_BYTE,info);
}
for (i = 0,pd = (ulong*)pb; i < middleCount; i++, index += 4) {
*pd++ = (ulong)PCI_accessReg(index,0,PCI_READ_DWORD,info);
}
for (i = 0,pb = (uchar*)pd; i < endCount; i++, index++) {
*pb++ = (uchar)PCI_accessReg(index,0,PCI_READ_BYTE,info);
}
}
/****************************************************************************
PARAMETERS:
info - PCI device information block for device to access
index - Index of register to start reading from
dst - Place to store the values read from configuration space
count - Count of bytes to read from configuration space
REMARKS:
This function is used to write a block of PCI configuration space registers
to the configuration space from the passed in data block. This function
will properly handle writing non-DWORD aligned data to the configuration
space correctly.
****************************************************************************/
void _ASMAPI PCI_writeRegBlock(
PCIDeviceInfo *info,
int index,
void *src,
int count)
{
uchar *pb;
ulong *pd;
int i;
int startCount = (index & 3);
int middleCount = (count - startCount) >> 2;
int endCount = count - middleCount * 4 - startCount;
for (i = 0,pb = src; i < startCount; i++, index++) {
PCI_accessReg(index,*pb++,PCI_WRITE_BYTE,info);
}
for (i = 0,pd = (ulong*)pb; i < middleCount; i++, index += 4) {
PCI_accessReg(index,*pd++,PCI_WRITE_DWORD,info);
}
for (i = 0,pb = (uchar*)pd; i < endCount; i++, index++) {
PCI_accessReg(index,*pb++,PCI_WRITE_BYTE,info);
}
}