| /* |
| ** 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 |
| */ |
| |
| /* |
| * 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; either version 2, or (at |
| * your option) any later version. |
| */ |
| |
| #include "etherboot.h" |
| #include "pci.h" |
| |
| /*#define DEBUG 1*/ |
| static unsigned int pci_ioaddr = 0; |
| |
| #ifdef CONFIG_PCI_DIRECT |
| #define PCIBIOS_SUCCESSFUL 0x00 |
| |
| /* |
| * Functions for accessing PCI configuration space with type 1 accesses |
| */ |
| |
| #define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) | (device_fn << 8) | (where & ~3)) |
| |
| int pcibios_read_config_byte(unsigned char bus, unsigned char device_fn, |
| unsigned char where, unsigned char *value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| *value = inb(0xCFC + (where&3)); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| int pcibios_read_config_word (unsigned char bus, |
| unsigned char device_fn, unsigned char where, unsigned short *value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| *value = inw(0xCFC + (where&2)); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static int pcibios_read_config_dword (unsigned char bus, unsigned char device_fn, |
| unsigned char where, unsigned int *value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| *value = inl(0xCFC); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| int pcibios_write_config_byte (unsigned char bus, unsigned char device_fn, |
| unsigned char where, unsigned char value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| outb(value, 0xCFC + (where&3)); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| int pcibios_write_config_word (unsigned char bus, unsigned char device_fn, |
| unsigned char where, unsigned short value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| outw(value, 0xCFC + (where&2)); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| int pcibios_write_config_dword (unsigned char bus, unsigned char device_fn, unsigned char where, unsigned int value) |
| { |
| outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
| outl(value, 0xCFC); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| #undef CONFIG_CMD |
| |
| #else /* CONFIG_PCI_DIRECT not defined */ |
| |
| 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\n", service); |
| return 0; |
| default: /* Shouldn't happen */ |
| printf("bios32_service(%d) : returned 0x%x, mail drew@colorado.edu\n", |
| service, return_code); |
| return 0; |
| } |
| } |
| |
| 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; |
| } |
| |
| int pcibios_read_config_word(unsigned char bus, |
| unsigned char device_fn, unsigned char where, unsigned short *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_WORD), |
| "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; |
| } |
| |
| int pcibios_write_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); cli(); |
| __asm__("lcall (%%esi)\n\t" |
| "jc 1f\n\t" |
| "xor %%ah, %%ah\n" |
| "1:" |
| : "=a" (ret) |
| : "0" (PCIBIOS_WRITE_CONFIG_BYTE), |
| "c" (value), |
| "b" (bx), |
| "D" ((long) where), |
| "S" (&pci_indirect)); |
| restore_flags(flags); |
| return (int) (ret & 0xff00) >> 8; |
| } |
| |
| int pcibios_write_config_word (unsigned char bus, |
| unsigned char device_fn, unsigned char where, unsigned short value) |
| { |
| unsigned long ret; |
| unsigned long bx = (bus << 8) | device_fn; |
| unsigned long flags; |
| |
| save_flags(flags); cli(); |
| __asm__("lcall (%%esi)\n\t" |
| "jc 1f\n\t" |
| "xor %%ah, %%ah\n" |
| "1:" |
| : "=a" (ret) |
| : "0" (PCIBIOS_WRITE_CONFIG_WORD), |
| "c" (value), |
| "b" (bx), |
| "D" ((long) where), |
| "S" (&pci_indirect)); |
| restore_flags(flags); |
| return (int) (ret & 0xff00) >> 8; |
| } |
| |
| int pcibios_write_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); cli(); |
| __asm__("lcall (%%esi)\n\t" |
| "jc 1f\n\t" |
| "xor %%ah, %%ah\n" |
| "1:" |
| : "=a" (ret) |
| : "0" (PCIBIOS_WRITE_CONFIG_DWORD), |
| "c" (value), |
| "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????\n"); |
| pcibios_entry = 0; |
| } |
| #if DEBUG |
| if (pcibios_entry) { |
| printf ("pcibios_init : PCI BIOS revision %b.%b" |
| " entry at 0x%X\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\n", |
| check->fields.revision, check); |
| continue; |
| } |
| #if DEBUG |
| printf("pcibios_init : BIOS32 Service Directory " |
| "structure at 0x%X\n", check); |
| #endif |
| if (!bios32_entry) { |
| if (check->fields.entry >= 0x100000) { |
| printf("pcibios_init: entry in high " |
| "memory, giving up\n"); |
| return; |
| } else { |
| bios32_entry = check->fields.entry; |
| #if DEBUG |
| printf("pcibios_init : BIOS32 Service Directory" |
| " entry at 0x%X\n", bios32_entry); |
| #endif |
| bios32_indirect.address = bios32_entry; |
| } |
| } |
| } |
| if (bios32_entry) |
| check_pcibios(); |
| } |
| #endif /* CONFIG_PCI_DIRECT not defined*/ |
| |
| static void scan_bus(struct pci_device *pcidev) |
| { |
| unsigned int devfn, l, bus, buses; |
| unsigned char hdr_type = 0; |
| unsigned short vendor, device; |
| unsigned int membase, ioaddr, romaddr; |
| unsigned char class, subclass; |
| int i, reg; |
| |
| pci_ioaddr = 0; |
| buses=1; |
| for (bus = 0; bus < buses; ++bus) { |
| for (devfn = 0; devfn < 0xff; ++devfn) { |
| if (PCI_FUNC (devfn) == 0) |
| pcibios_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type); |
| else if (!(hdr_type & 0x80)) /* not a multi-function device */ |
| continue; |
| pcibios_read_config_dword(bus, 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; |
| |
| /* check for pci-pci bridge devices!! - more buses when found */ |
| pcibios_read_config_byte(bus, devfn, PCI_CLASS_CODE, &class); |
| pcibios_read_config_byte(bus, devfn, PCI_SUBCLASS_CODE, &subclass); |
| if (class == 0x06 && subclass == 0x04) |
| buses++; |
| |
| #if DEBUG |
| printf("bus %x, function %x, vendor %x, device %x\n", |
| bus, devfn, vendor, device); |
| #endif |
| for (i = 0; pcidev[i].vendor != 0; i++) { |
| if (vendor != pcidev[i].vendor |
| || device != pcidev[i].dev_id) |
| continue; |
| for (reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4) { |
| pcibios_read_config_dword(bus, devfn, reg, &ioaddr); |
| |
| if ((ioaddr & PCI_BASE_ADDRESS_IO_MASK) == 0 || (ioaddr & PCI_BASE_ADDRESS_SPACE_IO) == 0) |
| continue; |
| /* Strip the I/O address out of the returned value */ |
| ioaddr &= PCI_BASE_ADDRESS_IO_MASK; |
| /* Get the memory base address */ |
| pcibios_read_config_dword(bus, devfn, |
| PCI_BASE_ADDRESS_1, &membase); |
| /* Get the ROM base address */ |
| pcibios_read_config_dword(bus, devfn, PCI_ROM_ADDRESS, &romaddr); |
| romaddr >>= 10; |
| printf("Found %s at 0x%x, ROM address 0x%X\n", |
| pcidev[i].name, ioaddr, romaddr); |
| /* Take the first one or the one that matches in boot ROM address */ |
| if (pci_ioaddr == 0 || romaddr == ((unsigned long) rom.rom_segment << 4)) { |
| pcidev[i].membase = membase; |
| pcidev[i].ioaddr = ioaddr; |
| pcidev[i].devfn = devfn; |
| |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void eth_pci_init(struct pci_device *pcidev) |
| { |
| #ifndef CONFIG_PCI_DIRECT |
| pcibios_init(); |
| if (!pcibios_entry) { |
| printf("pci_init: no BIOS32 detected\n"); |
| return; |
| } |
| #endif |
| scan_bus(pcidev); |
| /* return values are in pcidev structures */ |
| } |