blob: 17e5722cda279c6f9c8c9c0c60c215b8ee865843 [file] [log] [blame] [raw]
/*
** Support for NE2000 PCI clones added David Monro June 1997
** Generalised to other NICs by Ken Yap July 1997
**
** Most of this is taken from:
**
** /usr/src/linux/drivers/pci/pci.c
** /usr/src/linux/include/linux/pci.h
** /usr/src/linux/arch/i386/bios32.c
** /usr/src/linux/include/linux/bios32.h
** /usr/src/linux/drivers/net/ne.c
**
** Limitations: only finds devices on bus 0 (I figure that anybody with
** a multi-pcibus machine will have something better than an ne2000)
*/
#include "netboot.h"
#include "pci.h"
#define DEBUG 1
static unsigned int pci_ioaddr = 0;
static unsigned long bios32_entry = 0;
static struct {
unsigned long address;
unsigned short segment;
} bios32_indirect = { 0, KERN_CODE_SEG };
static long pcibios_entry = 0;
static struct {
unsigned long address;
unsigned short segment;
} pci_indirect = { 0, KERN_CODE_SEG };
static unsigned long bios32_service(unsigned long service)
{
unsigned char return_code; /* %al */
unsigned long address; /* %ebx */
unsigned long length; /* %ecx */
unsigned long entry; /* %edx */
unsigned long flags;
save_flags(flags);
__asm__("lcall (%%edi)"
: "=a" (return_code),
"=b" (address),
"=c" (length),
"=d" (entry)
: "0" (service),
"1" (0),
"D" (&bios32_indirect));
restore_flags(flags);
switch (return_code) {
case 0:
return address + entry;
case 0x80: /* Not present */
printf("bios32_service(%d) : not present\r\n", service);
return 0;
default: /* Shouldn't happen */
printf("bios32_service(%d) : returned 0x%x, mail drew@colorado.edu\r\n",
service, return_code);
return 0;
}
}
static int pcibios_read_config_byte(unsigned char bus,
unsigned char device_fn, unsigned char where, unsigned char *value)
{
unsigned long ret;
unsigned long bx = (bus << 8) | device_fn;
unsigned long flags;
save_flags(flags);
__asm__("lcall (%%esi)\n\t"
"jc 1f\n\t"
"xor %%ah, %%ah\n"
"1:"
: "=c" (*value),
"=a" (ret)
: "1" (PCIBIOS_READ_CONFIG_BYTE),
"b" (bx),
"D" ((long) where),
"S" (&pci_indirect));
restore_flags(flags);
return (int) (ret & 0xff00) >> 8;
}
static int pcibios_read_config_dword (unsigned char bus,
unsigned char device_fn, unsigned char where, unsigned int *value)
{
unsigned long ret;
unsigned long bx = (bus << 8) | device_fn;
unsigned long flags;
save_flags(flags);
__asm__("lcall (%%esi)\n\t"
"jc 1f\n\t"
"xor %%ah, %%ah\n"
"1:"
: "=c" (*value),
"=a" (ret)
: "1" (PCIBIOS_READ_CONFIG_DWORD),
"b" (bx),
"D" ((long) where),
"S" (&pci_indirect));
restore_flags(flags);
return (int) (ret & 0xff00) >> 8;
}
static void check_pcibios(void)
{
unsigned long signature;
unsigned char present_status;
unsigned char major_revision;
unsigned char minor_revision;
unsigned long flags;
int pack;
if ((pcibios_entry = bios32_service(PCI_SERVICE))) {
pci_indirect.address = pcibios_entry;
save_flags(flags);
__asm__("lcall (%%edi)\n\t"
"jc 1f\n\t"
"xor %%ah, %%ah\n"
"1:\tshl $8, %%eax\n\t"
"movw %%bx, %%ax"
: "=d" (signature),
"=a" (pack)
: "1" (PCIBIOS_PCI_BIOS_PRESENT),
"D" (&pci_indirect)
: "bx", "cx");
restore_flags(flags);
present_status = (pack >> 16) & 0xff;
major_revision = (pack >> 8) & 0xff;
minor_revision = pack & 0xff;
if (present_status || (signature != PCI_SIGNATURE)) {
printf("ERROR: BIOS32 says PCI BIOS, but no PCI "
"BIOS????\r\n");
pcibios_entry = 0;
}
#if DEBUG
if (pcibios_entry) {
printf ("pcibios_init : PCI BIOS revision %b.%b"
" entry at 0x%X\r\n", major_revision,
minor_revision, pcibios_entry);
}
#endif
}
}
static void pcibios_init(void)
{
union bios32 *check;
unsigned char sum;
int i, length;
/*
* Follow the standard procedure for locating the BIOS32 Service
* directory by scanning the permissible address range from
* 0xe0000 through 0xfffff for a valid BIOS32 structure.
*
*/
for (check = (union bios32 *) 0xe0000; check <= (union bios32 *) 0xffff0; ++check) {
if (check->fields.signature != BIOS32_SIGNATURE)
continue;
length = check->fields.length * 16;
if (!length)
continue;
sum = 0;
for (i = 0; i < length ; ++i)
sum += check->chars[i];
if (sum != 0)
continue;
if (check->fields.revision != 0) {
printf("pcibios_init : unsupported revision %d at 0x%X, mail drew@colorado.edu\r\n",
check->fields.revision, check);
continue;
}
#if DEBUG
printf("pcibios_init : BIOS32 Service Directory "
"structure at 0x%X\r\n", check);
#endif
if (!bios32_entry) {
if (check->fields.entry >= 0x100000) {
printf("pcibios_init: entry in high "
"memory, giving up\r\n");
return;
} else {
bios32_entry = check->fields.entry;
#if DEBUG
printf("pcibios_init : BIOS32 Service Directory"
" entry at 0x%X\r\n", bios32_entry);
#endif
bios32_indirect.address = bios32_entry;
}
}
}
if (bios32_entry)
check_pcibios();
}
static void scan_bus(struct pci_device *pcidev)
{
unsigned int devfn, l;
unsigned char hdr_type = 0;
unsigned short vendor, device;
int i;
for (devfn = 0; devfn < 0xff; ++devfn) {
if (PCI_FUNC(devfn) == 0) {
pcibios_read_config_byte(0, devfn,
PCI_HEADER_TYPE, &hdr_type);
} else if (!(hdr_type & 0x80)) {
/* not a multi-function device */
continue;
}
pcibios_read_config_dword(0, devfn,
PCI_VENDOR_ID, &l);
/* some broken boards return 0 if a slot is empty: */
if (l == 0xffffffff || l == 0x00000000) {
hdr_type = 0;
continue;
}
vendor = l & 0xffff;
device = (l >> 16) & 0xffff;
#if DEBUG
printf("bus 0, function %x, vendor %x, device %x\r\n",
devfn, vendor, device);
#endif
for (i = 0; pcidev[i].vendor != 0; i++) {
if (vendor == pcidev[i].vendor
&& device == pcidev[i].dev_id) {
pcibios_read_config_dword(0, devfn,
PCI_BASE_ADDRESS_0, &pci_ioaddr);
/* Strip the I/O address out of the
* returned value */
pci_ioaddr &= PCI_BASE_ADDRESS_IO_MASK;
printf("Found %s at 0x%x\r\n",
pcidev[i].name, pci_ioaddr);
}
}
}
}
void eth_pci_init(struct pci_device *pcidev, unsigned short *ioaddr)
{
pcibios_init();
if (!pcibios_entry) {
printf("pci_init: no BIOS32 detected\r\n");
return;
}
scan_bus(pcidev);
/* we only return one address at the moment, maybe more later? */
*ioaddr = pci_ioaddr;
}