Goto sanos source index
//
// pci.c
//
// PCI bus driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
struct {
int classcode;
char *name;
} pci_classnames[] = {
{0x000000, "Non-VGA unclassified device"},
{0x000100, "VGA compatible unclassified device"},
{0x010000, "SCSI storage controller"},
{0x010100, "IDE interface"},
{0x010200, "Floppy disk controller"},
{0x010300, "IPI bus controller"},
{0x010400, "RAID bus controller"},
{0x018000, "Unknown mass storage controller"},
{0x020000, "Ethernet controller"},
{0x020100, "Token ring network controller"},
{0x020200, "FDDI network controller"},
{0x020300, "ATM network controller"},
{0x020400, "ISDN controller"},
{0x028000, "Network controller"},
{0x030000, "VGA controller"},
{0x030100, "XGA controller"},
{0x030200, "3D controller"},
{0x038000, "Display controller"},
{0x040000, "Multimedia video controller"},
{0x040100, "Multimedia audio controller"},
{0x040200, "Computer telephony device"},
{0x048000, "Multimedia controller"},
{0x050000, "RAM memory"},
{0x050100, "FLASH memory"},
{0x058000, "Memory controller"},
{0x060000, "Host bridge"},
{0x060100, "ISA bridge"},
{0x060200, "EISA bridge"},
{0x060300, "MicroChannel bridge"},
{0x060400, "PCI bridge"},
{0x060500, "PCMCIA bridge"},
{0x060600, "NuBus bridge"},
{0x060700, "CardBus bridge"},
{0x060800, "RACEway bridge"},
{0x060900, "Semi-transparent PCI-to-PCI bridge"},
{0x060A00, "InfiniBand to PCI host bridge"},
{0x068000, "Bridge"},
{0x070000, "Serial controller"},
{0x070100, "Parallel controller"},
{0x070200, "Multiport serial controller"},
{0x070300, "Modem"},
{0x078000, "Communication controller"},
{0x080000, "PIC"},
{0x080100, "DMA controller"},
{0x080200, "Timer"},
{0x080300, "RTC"},
{0x080400, "PCI Hot-plug controller"},
{0x088000, "System peripheral"},
{0x090000, "Keyboard controller"},
{0x090100, "Digitizer Pen"},
{0x090200, "Mouse controller"},
{0x090300, "Scanner controller"},
{0x090400, "Gameport controller"},
{0x098000, "Input device controller"},
{0x0A0000, "Generic Docking Station"},
{0x0A8000, "Docking Station"},
{0x0B0000, "386"},
{0x0B0100, "486"},
{0x0B0200, "Pentium"},
{0x0B1000, "Alpha"},
{0x0B2000, "Power PC"},
{0x0B3000, "MIPS"},
{0x0B4000, "Co-processor"},
{0x0C0000, "FireWire (IEEE 1394)"},
{0x0C0100, "ACCESS Bus"},
{0x0C0200, "SSA"},
{0x0C0300, "USB Controller"},
{0x0C0400, "Fiber Channel"},
{0x0C0500, "SMBus"},
{0x0C0600, "InfiniBand"},
{0x0D0000, "IRDA controller"},
{0x0D0100, "Consumer IR controller"},
{0x0D1000, "RF controller"},
{0x0D8000, "Wireless controller"},
{0x0E0000, "I2O"},
{0x0F0000, "Satellite TV controller"},
{0x0F0100, "Satellite audio communication controller"},
{0x0F0300, "Satellite voice communication controller"},
{0x0F0400, "Satellite data communication controller"},
{0x100000, "Network and computing encryption device"},
{0x101000, "Entertainment encryption device"},
{0x108000, "Encryption controller"},
{0x110000, "DPIO module"},
{0x110100, "Performance counters"},
{0x111000, "Communication synchronizer"},
{0x118000, "Signal processing controller"},
{0x000000, NULL}
};
static __inline unsigned char pci_read_byte(int busno, int devno, int funcno, int addr) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
return inp(PCI_CONFIG_DATA);
}
static __inline unsigned short pci_read_word(int busno, int devno, int funcno, int addr) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
return inpw(PCI_CONFIG_DATA);
}
static __inline unsigned long pci_read_dword(int busno, int devno, int funcno, int addr) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
return inpd(PCI_CONFIG_DATA);
}
static __inline void pci_write_byte(int busno, int devno, int funcno, int addr, unsigned char value) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
outp(PCI_CONFIG_DATA, value);
}
static __inline void pci_write_word(int busno, int devno, int funcno, int addr, unsigned short value) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
outpw(PCI_CONFIG_DATA, value);
}
static __inline void pci_write_dword(int busno, int devno, int funcno, int addr, unsigned long value) {
outpd(PCI_CONFIG_ADDR, ((unsigned long) 0x80000000 | (busno << 16) | (devno << 11) | (funcno << 8) | addr));
outpd(PCI_CONFIG_DATA, value);
}
unsigned char pci_read_config_byte(struct unit *unit, int addr) {
return pci_read_byte(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr);
}
unsigned short pci_read_config_word(struct unit *unit, int addr) {
return pci_read_word(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr);
}
unsigned long pci_read_config_dword(struct unit *unit, int addr) {
return pci_read_dword(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr);
}
void pci_write_config_byte(struct unit *unit, int addr, unsigned char value) {
pci_write_byte(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr, value);
}
void pci_write_config_word(struct unit *unit, int addr, unsigned short value) {
pci_write_word(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr, value);
}
void pci_write_config_dword(struct unit *unit, int addr, unsigned long value) {
pci_write_dword(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr, value);
}
void pci_read_buffer(struct unit *unit, int addr, void *buffer, int len) {
if ((addr & 3) == 0 && (len && 3) == 0) {
unsigned long *buf = (unsigned long *) buffer;
while (len > 0) {
*buf++ = pci_read_dword(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr);
addr += 4;
len -= 4;
}
} else {
unsigned char *buf = (unsigned char *) buffer;
while (len > 0) {
*buf++ = pci_read_byte(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr);
addr++;
len--;
}
}
}
void pci_write_buffer(struct unit *unit, int addr, void *buffer, int len) {
if ((addr & 3) == 0 && (len && 3) == 0) {
unsigned long *buf = (unsigned long *) buffer;
while (len > 0) {
pci_write_dword(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr, *buf++);
addr += 4;
len -= 4;
}
} else {
unsigned char *buf = (unsigned char *) buffer;
while (len > 0) {
pci_write_byte(unit->bus->busno, PCI_DEVNO(unit->unitno), PCI_FUNCNO(unit->unitno), addr, *buf++);
addr++;
len--;
}
}
}
int pci_find_capability(struct unit *unit, int cap) {
unsigned short status;
unsigned char pos, id;
int ttl = 48;
status = pci_read_config_word(unit, PCI_CONFIG_STATUS);
if (!(status & PCI_STATUS_CAP_LIST)) return 0;
pos = pci_read_config_byte(unit, PCI_CONFIG_CAPABILITIES);
while (ttl-- && pos >= 0x40) {
pos &= ~3;
id = pci_read_config_byte(unit, pos + PCI_CAP_LIST_ID);
if (id == 0xff) break;
if (id == cap) return pos;
pos = pci_read_config_byte(unit, pos + PCI_CAP_LIST_NEXT);
}
return 0;
}
void pci_enable_busmastering(struct unit *unit) {
unsigned long value;
value = pci_read_config_dword(unit, PCI_CONFIG_CMD_STAT);
value |= 0x00000004;
pci_write_config_dword(unit, PCI_CONFIG_CMD_STAT, value);
}
static char *get_pci_class_name(int classcode) {
int i = 0;
while (pci_classnames[i].name != NULL) {
if (pci_classnames[i].classcode == classcode) return pci_classnames[i].name;
if (pci_classnames[i].classcode == (classcode & 0xFFFF00)) return pci_classnames[i].name;
i++;
}
return "Unknown";
}
static unsigned long pci_size(unsigned long base, unsigned long mask) {
// Find the significant bits
unsigned long size = mask & base;
// Get the lowest of them to find the decode size
size = size & ~(size-1);
return size;
}
void enum_pci_bus(struct bus *bus) {
int devno;
int funcno;
int busno;
int bar;
int intrpin;
int irq;
unsigned long value;
unsigned long len;
unsigned long vendorid;
unsigned long deviceid;
unsigned long classcode;
unsigned long revision;
struct unit *unit;
unsigned long prev_deviceid;
for (devno = 0; devno < 32; devno++) {
prev_deviceid = 0;
for (funcno = 0; funcno < 8; funcno++) {
// Vendor and device ids
value = pci_read_dword(bus->busno, devno, funcno, PCI_CONFIG_VENDOR);
vendorid = value & 0xFFFF;
deviceid = value >> 16;
if (vendorid == 0xFFFF || vendorid == 0) continue;
if (deviceid == prev_deviceid) continue;
prev_deviceid = deviceid;
// Function class code
value = pci_read_dword(bus->busno, devno, funcno, PCI_CONFIG_CLASS_REV);
classcode = value >> 8;
revision = value & 0xFF;
// Register new unit, host bridge is a special case
if (bus->busno == 0 && devno == 0 && funcno == 0 && bus->self) {
unit = bus->self;
} else {
unit = add_unit(bus, classcode, PCI_UNITCODE(vendorid, deviceid), PCI_UNITNO(devno, funcno));
}
unit->classname = get_pci_class_name(classcode);
unit->revision = revision;
// Subsystem id
value = pci_read_dword(bus->busno, devno, funcno, PCI_CONFIG_SUBSYSTEM);
unit->subunitcode = PCI_UNITCODE(value & 0xFFFF, value >> 16);
if (classcode == PCI_BRIDGE) {
struct bus *bridge;
// Get secondary bus number for bridge
value = pci_read_dword(bus->busno, devno, funcno, PCI_CONFIG_BASE_ADDR_2);
busno = (value >> 8) & 0xFF;
// Allocate and initialize new PCI bus
bridge = add_bus(unit, BUSTYPE_PCI, busno);
// Scan for devices on secondary bus
enum_pci_bus(bridge);
} else {
// Function I/O and memory base addresses
for (bar = 0; bar < 6; bar++) {
int reg = PCI_CONFIG_BASE_ADDR_0 + (bar << 2);
value = pci_read_dword(bus->busno, devno, funcno, reg);
pci_write_dword(bus->busno, devno, funcno, reg, 0xFFFFFFFF);
len = pci_read_dword(bus->busno, devno, funcno, reg);
pci_write_dword(bus->busno, devno, funcno, reg, value);
if (len != 0 && len != 0xFFFFFFFF) {
if (value == 0xFFFFFFFF) value = 0;
if (value & 1) {
add_resource(unit, RESOURCE_IO, 0, value & PCI_BASE_ADDRESS_IO_MASK, pci_size(len, PCI_BASE_ADDRESS_IO_MASK & 0xFFFF));
} else {
add_resource(unit, RESOURCE_MEM, 0, value & PCI_BASE_ADDRESS_MEM_MASK, pci_size(len, PCI_BASE_ADDRESS_MEM_MASK));
}
}
}
// Function interrupt line
value = pci_read_dword(bus->busno, devno, funcno, PCI_CONFIG_INTR);
if ((value & 0xFF) > 0 && (value & 0xFF) < 32) {
intrpin = (value >> 8) & 0xFF;
irq = value & 0xFF;
add_resource(unit, RESOURCE_IRQ, 0, irq, 1);
}
}
}
}
}
unsigned long get_pci_hostbus_unitcode() {
unsigned long value;
unsigned long vendorid;
unsigned long deviceid;
// Try to read bus 0 device 0 function 0 vendor
value = pci_read_dword(0, 0, 0, PCI_CONFIG_VENDOR);
vendorid = value & 0xFFFF;
deviceid = value >> 16;
if (vendorid == 0 || vendorid == 0xFFFF) return 0;
return PCI_UNITCODE(vendorid, deviceid);
}