diff options
Diffstat (limited to 'src/core/pci.c')
-rw-r--r-- | src/core/pci.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/core/pci.c b/src/core/pci.c new file mode 100644 index 00000000..8f89d8df --- /dev/null +++ b/src/core/pci.c @@ -0,0 +1,337 @@ +#ifdef CONFIG_PCI + +/* + * 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 void scan_drivers( + int type, + uint32_t class, uint16_t vendor, uint16_t device, + const struct pci_driver *last_driver, struct pci_device *dev) +{ + const struct pci_driver *skip_driver = last_driver; + /* Assume there is only one match of the correct type */ + const struct pci_driver *driver; + + for(driver = pci_drivers; driver < pci_drivers_end; driver++) { + int i; + if (driver->type != type) + continue; + if (skip_driver) { + if (skip_driver == driver) + skip_driver = 0; + continue; + } + for(i = 0; i < driver->id_count; i++) { + if ((vendor == driver->ids[i].vendor) && + (device == driver->ids[i].dev_id)) { + + dev->driver = driver; + dev->name = driver->ids[i].name; + + goto out; + } + } + } + if (!class) { + goto out; + } + for(driver = pci_drivers; driver < pci_drivers_end; driver++) { + if (driver->type != type) + continue; + if (skip_driver) { + if (skip_driver == driver) + skip_driver = 0; + continue; + } + if (last_driver == driver) + continue; + if ((class >> 8) == driver->class) { + dev->driver = driver; + dev->name = driver->name; + goto out; + } + } + out: + return; +} + +void scan_pci_bus(int type, struct pci_device *dev) +{ + unsigned int first_bus, first_devfn; + const struct pci_driver *first_driver; + unsigned int devfn, bus, buses; + unsigned char hdr_type = 0; + uint32_t class; + uint16_t vendor, device; + uint32_t l, membase, ioaddr, romaddr; + uint8_t irq; + int reg; + + first_bus = 0; + first_devfn = 0; + first_driver = 0; + if (dev->driver || dev->use_specified) { + first_driver = dev->driver; + first_bus = dev->bus; + first_devfn = dev->devfn; + /* Re read the header type on a restart */ + pcibios_read_config_byte(first_bus, first_devfn & ~0x7, + PCI_HEADER_TYPE, &hdr_type); + dev->driver = 0; + dev->bus = 0; + dev->devfn = 0; + } + + /* Scan all PCI buses, until we find our card. + * We could be smart only scan the required buses but that + * is error prone, and tricky. + * By scanning all possible pci buses in order we should find + * our card eventually. + */ + buses=256; + for (bus = first_bus; bus < buses; ++bus) { + for (devfn = first_devfn; devfn < 0xff; ++devfn, first_driver = 0) { + 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) { + continue; + } + vendor = l & 0xffff; + device = (l >> 16) & 0xffff; + + pcibios_read_config_dword(bus, devfn, PCI_REVISION, &l); + class = (l >> 8) & 0xffffff; +#if DEBUG + { + int i; + printf("%hhx:%hhx.%hhx [%hX/%hX] Class %hX\n", + bus, PCI_SLOT(devfn), PCI_FUNC(devfn), + vendor, device, class >> 8); +#if DEBUG > 1 + for(i = 0; i < 256; i++) { + unsigned char byte; + if ((i & 0xf) == 0) { + printf("%hhx: ", i); + } + pcibios_read_config_byte(bus, devfn, i, &byte); + printf("%hhx ", byte); + if ((i & 0xf) == 0xf) { + printf("\n"); + } + } +#endif + + } +#endif + scan_drivers(type, class, vendor, device, first_driver, dev); + if (!dev->driver) + continue; + + dev->devfn = devfn; + dev->bus = bus; + dev->class = class; + dev->vendor = vendor; + dev->dev_id = device; + + + /* Get the ROM base address */ + pcibios_read_config_dword(bus, devfn, + PCI_ROM_ADDRESS, &romaddr); + romaddr >>= 10; + dev->romaddr = romaddr; + + /* Get the ``membase'' */ + pcibios_read_config_dword(bus, devfn, + PCI_BASE_ADDRESS_1, &membase); + dev->membase = membase; + + /* Get the ``ioaddr'' */ + 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; + + /* Take the first one or the one that matches in boot ROM address */ + dev->ioaddr = ioaddr; + } + + /* Get the irq */ + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq); + if (irq) { + pci_read_config_byte(dev, PCI_INTERRUPT_LINE, + &irq); + } + dev->irq = irq; + +#if DEBUG > 2 + printf("Found %s ROM address %#hx\n", + dev->name, romaddr); +#endif + return; + } + first_devfn = 0; + } + first_bus = 0; +} + + + +/* + * 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) { +#if DEBUG > 0 + printf( + "The PCI BIOS has not enabled this device!\n" + "Updating PCI command %hX->%hX. pci_bus %hhX pci_device_fn %hhX\n", + pci_command, new_command, p->bus, p->devfn); +#endif + 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) { +#if DEBUG > 0 + printf("PCI latency timer (CFLT) is unreasonably low at %d. Setting to 32 clocks.\n", + pci_latency); +#endif + pcibios_write_config_byte(p->bus, p->devfn, PCI_LATENCY_TIMER, 32); + } +} + +/* + * Find the start of a pci resource. + */ +unsigned long pci_bar_start(struct pci_device *dev, unsigned int index) +{ + uint32_t lo, hi; + unsigned long bar; + pci_read_config_dword(dev, index, &lo); + if (lo & PCI_BASE_ADDRESS_SPACE_IO) { + bar = lo & PCI_BASE_ADDRESS_IO_MASK; + } else { + bar = 0; + if ((lo & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == PCI_BASE_ADDRESS_MEM_TYPE_64) { + pci_read_config_dword(dev, index + 4, &hi); + if (hi) { +#if ULONG_MAX > 0xffffffff + bar = hi; + bar <<=32; +#else + printf("Unhandled 64bit BAR\n"); + return -1UL; +#endif + } + } + bar |= lo & PCI_BASE_ADDRESS_MEM_MASK; + } + return bar + pcibios_bus_base(dev->bus); +} + +/* + * Find the size of a pci resource. + */ +unsigned long pci_bar_size(struct pci_device *dev, unsigned int bar) +{ + uint32_t start, size; + /* Save the original bar */ + pci_read_config_dword(dev, bar, &start); + /* Compute which bits can be set */ + pci_write_config_dword(dev, bar, ~0); + pci_read_config_dword(dev, bar, &size); + /* Restore the original size */ + pci_write_config_dword(dev, bar, start); + /* Find the significant bits */ + if (start & PCI_BASE_ADDRESS_SPACE_IO) { + size &= PCI_BASE_ADDRESS_IO_MASK; + } else { + size &= PCI_BASE_ADDRESS_MEM_MASK; + } + /* Find the lowest bit set */ + size = size & ~(size - 1); + return size; +} + +/** + * pci_find_capability - query for devices' capabilities + * @dev: PCI device to query + * @cap: capability code + * + * Tell if a device supports a given PCI capability. + * Returns the address of the requested capability structure within the + * device's PCI configuration space or 0 in case the device does not + * support it. Possible values for @cap: + * + * %PCI_CAP_ID_PM Power Management + * + * %PCI_CAP_ID_AGP Accelerated Graphics Port + * + * %PCI_CAP_ID_VPD Vital Product Data + * + * %PCI_CAP_ID_SLOTID Slot Identification + * + * %PCI_CAP_ID_MSI Message Signalled Interrupts + * + * %PCI_CAP_ID_CHSWP CompactPCI HotSwap + */ +int pci_find_capability(struct pci_device *dev, int cap) +{ + uint16_t status; + uint8_t pos, id; + uint8_t hdr_type; + int ttl = 48; + + pci_read_config_word(dev, PCI_STATUS, &status); + if (!(status & PCI_STATUS_CAP_LIST)) + return 0; + pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type); + switch (hdr_type & 0x7F) { + case PCI_HEADER_TYPE_NORMAL: + case PCI_HEADER_TYPE_BRIDGE: + default: + pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &pos); + break; + case PCI_HEADER_TYPE_CARDBUS: + pci_read_config_byte(dev, PCI_CB_CAPABILITY_LIST, &pos); + break; + } + while (ttl-- && pos >= 0x40) { + pos &= ~3; + pci_read_config_byte(dev, pos + PCI_CAP_LIST_ID, &id); +#if DEBUG > 0 + printf("Capability: %d\n", id); +#endif + if (id == 0xff) + break; + if (id == cap) + return pos; + pci_read_config_byte(dev, pos + PCI_CAP_LIST_NEXT, &pos); + } + return 0; +} + +#endif /* CONFIG_PCI */ |