/* ** 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*/ #define DEBUG 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 int bus, unsigned int device_fn, unsigned int 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 int bus, unsigned int device_fn, unsigned int where, unsigned short *value) { outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); *value = inw(0xCFC + (where&2)); return PCIBIOS_SUCCESSFUL; } int pcibios_read_config_dword (unsigned int bus, unsigned int device_fn, unsigned int where, unsigned int *value) { outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); *value = inl(0xCFC); return PCIBIOS_SUCCESSFUL; } int pcibios_write_config_byte (unsigned int bus, unsigned int device_fn, unsigned int 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 int bus, unsigned int device_fn, unsigned int 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 int bus, unsigned int device_fn, unsigned int 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 struct { unsigned long address; unsigned short segment; } bios32_indirect = { 0, KERN_CODE_SEG }; static long pcibios_entry; 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__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%edi)" #else "lcall *(%%edi)" #endif : "=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 %#X, mail drew@colorado.edu\n", service, return_code); return 0; } } int pcibios_read_config_byte(unsigned int bus, unsigned int device_fn, unsigned int where, unsigned char *value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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 int bus, unsigned int device_fn, unsigned int where, unsigned short *value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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; } int pcibios_read_config_dword(unsigned int bus, unsigned int device_fn, unsigned int where, unsigned int *value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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 int bus, unsigned int device_fn, unsigned int where, unsigned char value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); cli(); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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 int bus, unsigned int device_fn, unsigned int where, unsigned short value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); cli(); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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 int bus, unsigned int device_fn, unsigned int where, unsigned int value) { unsigned long ret; unsigned long bx = (bus << 8) | device_fn; unsigned long flags; save_flags(flags); cli(); __asm__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%esi)\n\t" #else "lcall *(%%esi)\n\t" #endif "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__( #ifdef ABSOLUTE_WITHOUT_ASTERISK "lcall (%%edi)\n\t" #else "lcall *(%%edi)\n\t" #endif "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 %hhX.%hhX" " entry at %#X\n", major_revision, minor_revision, pcibios_entry); } #endif } } static void pcibios_init(void) { union bios32 *check; unsigned char sum; int i, length; unsigned long bios32_entry = 0; /* * 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 %#X, mail drew@colorado.edu\n", check->fields.revision, check); continue; } #if DEBUG printf("pcibios_init : BIOS32 Service Directory " "structure at %#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 %#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; int i, reg; unsigned int pci_ioaddr = 0; /* Scan all PCI buses, until we find our card. * We could be smart only scan the required busses but that * is error prone, and tricky. * By scanning all possible pci busses in order we should find * our card eventually. */ buses=256; 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; #if DEBUG printf("bus %hhX, function %hhX, vendor %hX, device %hX\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; pcidev[i].devfn = devfn; pcidev[i].bus = bus; 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 %#hx, ROM address %#hx\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; 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 */ } /* * Set device to be a busmaster in case BIOS neglected to do so. * Also adjust PCI latency timer to a reasonable value, 32. */ void adjust_pci_device(struct pci_device *p) { unsigned short new_command, pci_command; unsigned char pci_latency; pcibios_read_config_word(p->bus, p->devfn, PCI_COMMAND, &pci_command); new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; if (pci_command != new_command) { printf("The PCI BIOS has not enabled this device!\nUpdating PCI command %hX->%hX. pci_bus %hhX pci_device_fn %hhX\n", pci_command, new_command, p->bus, p->devfn); pcibios_write_config_word(p->bus, p->devfn, PCI_COMMAND, new_command); } pcibios_read_config_byte(p->bus, p->devfn, PCI_LATENCY_TIMER, &pci_latency); if (pci_latency < 32) { printf("PCI latency timer (CFLT) is unreasonably low at %d. Setting to 32 clocks.\n", pci_latency); pcibios_write_config_byte(p->bus, p->devfn, PCI_LATENCY_TIMER, 32); } }