diff options
author | Kevin O'Connor <kevin@koconnor.net> | 2013-09-02 21:25:21 -0400 |
---|---|---|
committer | Kevin O'Connor <kevin@koconnor.net> | 2013-09-02 21:25:21 -0400 |
commit | ccee6e8491a2dbc5f8f2085ee9567bfb4146693b (patch) | |
tree | 3deca9e3b7904b7139c365490fcc6e497a9ed24a /src/fw/pciinit.c | |
parent | 5d369d8d9eb5326db111cc2e518c74739dbe3f84 (diff) | |
download | seabios-ccee6e8491a2dbc5f8f2085ee9567bfb4146693b.tar.gz |
Move code cenetered around firmware initialization to src/fw/
Move many C files from the src/ directory to the new src/fw/ directory.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'src/fw/pciinit.c')
-rw-r--r-- | src/fw/pciinit.c | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/src/fw/pciinit.c b/src/fw/pciinit.c new file mode 100644 index 00000000..ca32d431 --- /dev/null +++ b/src/fw/pciinit.c @@ -0,0 +1,843 @@ +// Initialize PCI devices (on emulators) +// +// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net> +// Copyright (C) 2006 Fabrice Bellard +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "hw/pci.h" // pci_config_readl +#include "hw/pci_ids.h" // PCI_VENDOR_ID_INTEL +#include "hw/pci_regs.h" // PCI_COMMAND +#include "ioport.h" // PORT_ATA1_CMD_BASE +#include "config.h" // CONFIG_* +#include "memmap.h" // add_e820 +#include "paravirt.h" // RamSize +#include "dev-q35.h" // Q35_HOST_BRIDGE_PCIEXBAR_ADDR +#include "list.h" // struct hlist_node +#include "acpi.h" // acpi_pm1a_cnt + +#define PCI_DEVICE_MEM_MIN 0x1000 +#define PCI_BRIDGE_IO_MIN 0x1000 +#define PCI_BRIDGE_MEM_MIN 0x100000 + +enum pci_region_type { + PCI_REGION_TYPE_IO, + PCI_REGION_TYPE_MEM, + PCI_REGION_TYPE_PREFMEM, + PCI_REGION_TYPE_COUNT, +}; + +static const char *region_type_name[] = { + [ PCI_REGION_TYPE_IO ] = "io", + [ PCI_REGION_TYPE_MEM ] = "mem", + [ PCI_REGION_TYPE_PREFMEM ] = "prefmem", +}; + +u64 pcimem_start = BUILD_PCIMEM_START; +u64 pcimem_end = BUILD_PCIMEM_END; +u64 pcimem64_start = BUILD_PCIMEM64_START; +u64 pcimem64_end = BUILD_PCIMEM64_END; + +struct pci_region_entry { + struct pci_device *dev; + int bar; + u64 size; + u64 align; + int is64; + enum pci_region_type type; + struct hlist_node node; +}; + +struct pci_region { + /* pci region assignments */ + u64 base; + struct hlist_head list; +}; + +struct pci_bus { + struct pci_region r[PCI_REGION_TYPE_COUNT]; + struct pci_device *bus_dev; +}; + +static u32 pci_bar(struct pci_device *pci, int region_num) +{ + if (region_num != PCI_ROM_SLOT) { + return PCI_BASE_ADDRESS_0 + region_num * 4; + } + +#define PCI_HEADER_TYPE_MULTI_FUNCTION 0x80 + u8 type = pci->header_type & ~PCI_HEADER_TYPE_MULTI_FUNCTION; + return type == PCI_HEADER_TYPE_BRIDGE ? PCI_ROM_ADDRESS1 : PCI_ROM_ADDRESS; +} + +static void +pci_set_io_region_addr(struct pci_device *pci, int bar, u64 addr, int is64) +{ + u32 ofs = pci_bar(pci, bar); + pci_config_writel(pci->bdf, ofs, addr); + if (is64) + pci_config_writel(pci->bdf, ofs + 4, addr >> 32); +} + + +/**************************************************************** + * Misc. device init + ****************************************************************/ + +/* host irqs corresponding to PCI irqs A-D */ +const u8 pci_irqs[4] = { + 10, 10, 11, 11 +}; + +static int dummy_pci_slot_get_irq(struct pci_device *pci, int pin) +{ + dprintf(1, "pci_slot_get_irq called with unknown routing\n"); + + return 0xff; /* PCI defined "unknown" or "no connection" for x86 */ +} + +static int (*pci_slot_get_irq)(struct pci_device *pci, int pin) = + dummy_pci_slot_get_irq; + +// Return the global irq number corresponding to a host bus device irq pin. +static int piix_pci_slot_get_irq(struct pci_device *pci, int pin) +{ + int slot_addend = 0; + + while (pci->parent != NULL) { + slot_addend += pci_bdf_to_dev(pci->bdf); + pci = pci->parent; + } + slot_addend += pci_bdf_to_dev(pci->bdf) - 1; + return pci_irqs[(pin - 1 + slot_addend) & 3]; +} + +static int mch_pci_slot_get_irq(struct pci_device *pci, int pin) +{ + int irq, slot, pin_addend = 0; + + while (pci->parent != NULL) { + pin_addend += pci_bdf_to_dev(pci->bdf); + pci = pci->parent; + } + slot = pci_bdf_to_dev(pci->bdf); + + switch (slot) { + /* Slots 0-24 rotate slot:pin mapping similar to piix above, but + with a different starting index - see q35-acpi-dsdt.dsl */ + case 0 ... 24: + irq = pci_irqs[(pin - 1 + pin_addend + slot) & 3]; + break; + /* Slots 25-31 all use LNKA mapping (or LNKE, but A:D = E:H) */ + case 25 ... 31: + irq = pci_irqs[(pin - 1 + pin_addend) & 3]; + break; + } + + return irq; +} + +/* PIIX3/PIIX4 PCI to ISA bridge */ +static void piix_isa_bridge_setup(struct pci_device *pci, void *arg) +{ + int i, irq; + u8 elcr[2]; + + elcr[0] = 0x00; + elcr[1] = 0x00; + for (i = 0; i < 4; i++) { + irq = pci_irqs[i]; + /* set to trigger level */ + elcr[irq >> 3] |= (1 << (irq & 7)); + /* activate irq remapping in PIIX */ + pci_config_writeb(pci->bdf, 0x60 + i, irq); + } + outb(elcr[0], 0x4d0); + outb(elcr[1], 0x4d1); + dprintf(1, "PIIX3/PIIX4 init: elcr=%02x %02x\n", elcr[0], elcr[1]); +} + +/* ICH9 LPC PCI to ISA bridge */ +/* PCI_VENDOR_ID_INTEL && PCI_DEVICE_ID_INTEL_ICH9_LPC */ +void mch_isa_bridge_setup(struct pci_device *dev, void *arg) +{ + u16 bdf = dev->bdf; + int i, irq; + u8 elcr[2]; + + elcr[0] = 0x00; + elcr[1] = 0x00; + + for (i = 0; i < 4; i++) { + irq = pci_irqs[i]; + /* set to trigger level */ + elcr[irq >> 3] |= (1 << (irq & 7)); + + /* activate irq remapping in LPC */ + + /* PIRQ[A-D] routing */ + pci_config_writeb(bdf, ICH9_LPC_PIRQA_ROUT + i, irq); + /* PIRQ[E-H] routing */ + pci_config_writeb(bdf, ICH9_LPC_PIRQE_ROUT + i, irq); + } + outb(elcr[0], ICH9_LPC_PORT_ELCR1); + outb(elcr[1], ICH9_LPC_PORT_ELCR2); + dprintf(1, "Q35 LPC init: elcr=%02x %02x\n", elcr[0], elcr[1]); + + /* pm io base */ + pci_config_writel(bdf, ICH9_LPC_PMBASE, + PORT_ACPI_PM_BASE | ICH9_LPC_PMBASE_RTE); + + /* acpi enable, SCI: IRQ9 000b = irq9*/ + pci_config_writeb(bdf, ICH9_LPC_ACPI_CTRL, ICH9_LPC_ACPI_CTRL_ACPI_EN); + + acpi_pm1a_cnt = PORT_ACPI_PM_BASE + 0x04; + pmtimer_setup(PORT_ACPI_PM_BASE + 0x08); +} + +static void storage_ide_setup(struct pci_device *pci, void *arg) +{ + /* IDE: we map it as in ISA mode */ + pci_set_io_region_addr(pci, 0, PORT_ATA1_CMD_BASE, 0); + pci_set_io_region_addr(pci, 1, PORT_ATA1_CTRL_BASE, 0); + pci_set_io_region_addr(pci, 2, PORT_ATA2_CMD_BASE, 0); + pci_set_io_region_addr(pci, 3, PORT_ATA2_CTRL_BASE, 0); +} + +/* PIIX3/PIIX4 IDE */ +static void piix_ide_setup(struct pci_device *pci, void *arg) +{ + u16 bdf = pci->bdf; + pci_config_writew(bdf, 0x40, 0x8000); // enable IDE0 + pci_config_writew(bdf, 0x42, 0x8000); // enable IDE1 +} + +static void pic_ibm_setup(struct pci_device *pci, void *arg) +{ + /* PIC, IBM, MPIC & MPIC2 */ + pci_set_io_region_addr(pci, 0, 0x80800000 + 0x00040000, 0); +} + +static void apple_macio_setup(struct pci_device *pci, void *arg) +{ + /* macio bridge */ + pci_set_io_region_addr(pci, 0, 0x80800000, 0); +} + +/* PIIX4 Power Management device (for ACPI) */ +static void piix4_pm_setup(struct pci_device *pci, void *arg) +{ + u16 bdf = pci->bdf; + // acpi sci is hardwired to 9 + pci_config_writeb(bdf, PCI_INTERRUPT_LINE, 9); + + pci_config_writel(bdf, 0x40, PORT_ACPI_PM_BASE | 1); + pci_config_writeb(bdf, 0x80, 0x01); /* enable PM io space */ + pci_config_writel(bdf, 0x90, PORT_SMB_BASE | 1); + pci_config_writeb(bdf, 0xd2, 0x09); /* enable SMBus io space */ + + acpi_pm1a_cnt = PORT_ACPI_PM_BASE + 0x04; + pmtimer_setup(PORT_ACPI_PM_BASE + 0x08); +} + +/* ICH9 SMBUS */ +/* PCI_VENDOR_ID_INTEL && PCI_DEVICE_ID_INTEL_ICH9_SMBUS */ +void ich9_smbus_setup(struct pci_device *dev, void *arg) +{ + u16 bdf = dev->bdf; + /* map smbus into io space */ + pci_config_writel(bdf, ICH9_SMB_SMB_BASE, + PORT_SMB_BASE | PCI_BASE_ADDRESS_SPACE_IO); + + /* enable SMBus */ + pci_config_writeb(bdf, ICH9_SMB_HOSTC, ICH9_SMB_HOSTC_HST_EN); +} + +static const struct pci_device_id pci_device_tbl[] = { + /* PIIX3/PIIX4 PCI to ISA bridge */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_0, + piix_isa_bridge_setup), + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_0, + piix_isa_bridge_setup), + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_LPC, + mch_isa_bridge_setup), + + /* STORAGE IDE */ + PCI_DEVICE_CLASS(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_1, + PCI_CLASS_STORAGE_IDE, piix_ide_setup), + PCI_DEVICE_CLASS(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB, + PCI_CLASS_STORAGE_IDE, piix_ide_setup), + PCI_DEVICE_CLASS(PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_STORAGE_IDE, + storage_ide_setup), + + /* PIC, IBM, MIPC & MPIC2 */ + PCI_DEVICE_CLASS(PCI_VENDOR_ID_IBM, 0x0046, PCI_CLASS_SYSTEM_PIC, + pic_ibm_setup), + PCI_DEVICE_CLASS(PCI_VENDOR_ID_IBM, 0xFFFF, PCI_CLASS_SYSTEM_PIC, + pic_ibm_setup), + + /* PIIX4 Power Management device (for ACPI) */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, + piix4_pm_setup), + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_SMBUS, + ich9_smbus_setup), + + /* 0xff00 */ + PCI_DEVICE_CLASS(PCI_VENDOR_ID_APPLE, 0x0017, 0xff00, apple_macio_setup), + PCI_DEVICE_CLASS(PCI_VENDOR_ID_APPLE, 0x0022, 0xff00, apple_macio_setup), + + PCI_DEVICE_END, +}; + +static void pci_bios_init_device(struct pci_device *pci) +{ + u16 bdf = pci->bdf; + dprintf(1, "PCI: init bdf=%02x:%02x.%x id=%04x:%04x\n" + , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf), pci_bdf_to_fn(bdf) + , pci->vendor, pci->device); + + /* map the interrupt */ + int pin = pci_config_readb(bdf, PCI_INTERRUPT_PIN); + if (pin != 0) + pci_config_writeb(bdf, PCI_INTERRUPT_LINE, pci_slot_get_irq(pci, pin)); + + pci_init_device(pci_device_tbl, pci, NULL); + + /* enable memory mappings */ + pci_config_maskw(bdf, PCI_COMMAND, 0, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_SERR); +} + +static void pci_bios_init_devices(void) +{ + struct pci_device *pci; + foreachpci(pci) { + pci_bios_init_device(pci); + } +} + +static void pci_enable_default_vga(void) +{ + struct pci_device *pci; + + foreachpci(pci) { + if (is_pci_vga(pci)) { + dprintf(1, "PCI: Using %02x:%02x.%x for primary VGA\n", + pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), + pci_bdf_to_fn(pci->bdf)); + return; + } + } + + pci = pci_find_class(PCI_CLASS_DISPLAY_VGA); + if (!pci) { + dprintf(1, "PCI: No VGA devices found\n"); + return; + } + + dprintf(1, "PCI: Enabling %02x:%02x.%x for primary VGA\n", + pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), + pci_bdf_to_fn(pci->bdf)); + + pci_config_maskw(pci->bdf, PCI_COMMAND, 0, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + + while (pci->parent) { + pci = pci->parent; + + dprintf(1, "PCI: Setting VGA enable on bridge %02x:%02x.%x\n", + pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), + pci_bdf_to_fn(pci->bdf)); + + pci_config_maskw(pci->bdf, PCI_BRIDGE_CONTROL, 0, PCI_BRIDGE_CTL_VGA); + pci_config_maskw(pci->bdf, PCI_COMMAND, 0, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + } +} + +/**************************************************************** + * Platform device initialization + ****************************************************************/ + +void i440fx_mem_addr_setup(struct pci_device *dev, void *arg) +{ + if (RamSize <= 0x80000000) + pcimem_start = 0x80000000; + else if (RamSize <= 0xc0000000) + pcimem_start = 0xc0000000; + + pci_slot_get_irq = piix_pci_slot_get_irq; +} + +void mch_mem_addr_setup(struct pci_device *dev, void *arg) +{ + u64 addr = Q35_HOST_BRIDGE_PCIEXBAR_ADDR; + u32 size = Q35_HOST_BRIDGE_PCIEXBAR_SIZE; + + /* setup mmconfig */ + u16 bdf = dev->bdf; + u32 upper = addr >> 32; + u32 lower = (addr & 0xffffffff) | Q35_HOST_BRIDGE_PCIEXBAREN; + pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, 0); + pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR + 4, upper); + pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, lower); + add_e820(addr, size, E820_RESERVED); + + /* setup pci i/o window (above mmconfig) */ + pcimem_start = addr + size; + + pci_slot_get_irq = mch_pci_slot_get_irq; +} + +static const struct pci_device_id pci_platform_tbl[] = { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441, + i440fx_mem_addr_setup), + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_Q35_MCH, + mch_mem_addr_setup), + PCI_DEVICE_END +}; + +static void pci_bios_init_platform(void) +{ + struct pci_device *pci; + foreachpci(pci) { + pci_init_device(pci_platform_tbl, pci, NULL); + } +} + + +/**************************************************************** + * Bus initialization + ****************************************************************/ + +static void +pci_bios_init_bus_rec(int bus, u8 *pci_bus) +{ + int bdf; + u16 class; + + dprintf(1, "PCI: %s bus = 0x%x\n", __func__, bus); + + /* prevent accidental access to unintended devices */ + foreachbdf(bdf, bus) { + class = pci_config_readw(bdf, PCI_CLASS_DEVICE); + if (class == PCI_CLASS_BRIDGE_PCI) { + pci_config_writeb(bdf, PCI_SECONDARY_BUS, 255); + pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, 0); + } + } + + foreachbdf(bdf, bus) { + class = pci_config_readw(bdf, PCI_CLASS_DEVICE); + if (class != PCI_CLASS_BRIDGE_PCI) { + continue; + } + dprintf(1, "PCI: %s bdf = 0x%x\n", __func__, bdf); + + u8 pribus = pci_config_readb(bdf, PCI_PRIMARY_BUS); + if (pribus != bus) { + dprintf(1, "PCI: primary bus = 0x%x -> 0x%x\n", pribus, bus); + pci_config_writeb(bdf, PCI_PRIMARY_BUS, bus); + } else { + dprintf(1, "PCI: primary bus = 0x%x\n", pribus); + } + + u8 secbus = pci_config_readb(bdf, PCI_SECONDARY_BUS); + (*pci_bus)++; + if (*pci_bus != secbus) { + dprintf(1, "PCI: secondary bus = 0x%x -> 0x%x\n", + secbus, *pci_bus); + secbus = *pci_bus; + pci_config_writeb(bdf, PCI_SECONDARY_BUS, secbus); + } else { + dprintf(1, "PCI: secondary bus = 0x%x\n", secbus); + } + + /* set to max for access to all subordinate buses. + later set it to accurate value */ + u8 subbus = pci_config_readb(bdf, PCI_SUBORDINATE_BUS); + pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, 255); + + pci_bios_init_bus_rec(secbus, pci_bus); + + if (subbus != *pci_bus) { + dprintf(1, "PCI: subordinate bus = 0x%x -> 0x%x\n", + subbus, *pci_bus); + subbus = *pci_bus; + } else { + dprintf(1, "PCI: subordinate bus = 0x%x\n", subbus); + } + pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, subbus); + } +} + +static void +pci_bios_init_bus(void) +{ + u8 pci_bus = 0; + pci_bios_init_bus_rec(0 /* host bus */, &pci_bus); +} + + +/**************************************************************** + * Bus sizing + ****************************************************************/ + +static void +pci_bios_get_bar(struct pci_device *pci, int bar, + int *ptype, u64 *psize, int *pis64) +{ + u32 ofs = pci_bar(pci, bar); + u16 bdf = pci->bdf; + u32 old = pci_config_readl(bdf, ofs); + int is64 = 0, type = PCI_REGION_TYPE_MEM; + u64 mask; + + if (bar == PCI_ROM_SLOT) { + mask = PCI_ROM_ADDRESS_MASK; + pci_config_writel(bdf, ofs, mask); + } else { + if (old & PCI_BASE_ADDRESS_SPACE_IO) { + mask = PCI_BASE_ADDRESS_IO_MASK; + type = PCI_REGION_TYPE_IO; + } else { + mask = PCI_BASE_ADDRESS_MEM_MASK; + if (old & PCI_BASE_ADDRESS_MEM_PREFETCH) + type = PCI_REGION_TYPE_PREFMEM; + is64 = ((old & PCI_BASE_ADDRESS_MEM_TYPE_MASK) + == PCI_BASE_ADDRESS_MEM_TYPE_64); + } + pci_config_writel(bdf, ofs, ~0); + } + u64 val = pci_config_readl(bdf, ofs); + pci_config_writel(bdf, ofs, old); + if (is64) { + u32 hold = pci_config_readl(bdf, ofs + 4); + pci_config_writel(bdf, ofs + 4, ~0); + u32 high = pci_config_readl(bdf, ofs + 4); + pci_config_writel(bdf, ofs + 4, hold); + val |= ((u64)high << 32); + mask |= ((u64)0xffffffff << 32); + *psize = (~(val & mask)) + 1; + } else { + *psize = ((~(val & mask)) + 1) & 0xffffffff; + } + *ptype = type; + *pis64 = is64; +} + +static int pci_bios_bridge_region_is64(struct pci_region *r, + struct pci_device *pci, int type) +{ + if (type != PCI_REGION_TYPE_PREFMEM) + return 0; + u32 pmem = pci_config_readl(pci->bdf, PCI_PREF_MEMORY_BASE); + if (!pmem) { + pci_config_writel(pci->bdf, PCI_PREF_MEMORY_BASE, 0xfff0fff0); + pmem = pci_config_readl(pci->bdf, PCI_PREF_MEMORY_BASE); + pci_config_writel(pci->bdf, PCI_PREF_MEMORY_BASE, 0x0); + } + if ((pmem & PCI_PREF_RANGE_TYPE_MASK) != PCI_PREF_RANGE_TYPE_64) + return 0; + struct pci_region_entry *entry; + hlist_for_each_entry(entry, &r->list, node) { + if (!entry->is64) + return 0; + } + return 1; +} + +static u64 pci_region_align(struct pci_region *r) +{ + struct pci_region_entry *entry; + hlist_for_each_entry(entry, &r->list, node) { + // The first entry in the sorted list has the largest alignment + return entry->align; + } + return 1; +} + +static u64 pci_region_sum(struct pci_region *r) +{ + u64 sum = 0; + struct pci_region_entry *entry; + hlist_for_each_entry(entry, &r->list, node) { + sum += entry->size; + } + return sum; +} + +static void pci_region_migrate_64bit_entries(struct pci_region *from, + struct pci_region *to) +{ + struct hlist_node *n, **last = &to->list.first; + struct pci_region_entry *entry; + hlist_for_each_entry_safe(entry, n, &from->list, node) { + if (!entry->is64) + continue; + // Move from source list to destination list. + hlist_del(&entry->node); + hlist_add(&entry->node, last); + } +} + +static struct pci_region_entry * +pci_region_create_entry(struct pci_bus *bus, struct pci_device *dev, + int bar, u64 size, u64 align, int type, int is64) +{ + struct pci_region_entry *entry = malloc_tmp(sizeof(*entry)); + if (!entry) { + warn_noalloc(); + return NULL; + } + memset(entry, 0, sizeof(*entry)); + entry->dev = dev; + entry->bar = bar; + entry->size = size; + entry->align = align; + entry->is64 = is64; + entry->type = type; + // Insert into list in sorted order. + struct hlist_node **pprev; + struct pci_region_entry *pos; + hlist_for_each_entry_pprev(pos, pprev, &bus->r[type].list, node) { + if (pos->align < align || (pos->align == align && pos->size < size)) + break; + } + hlist_add(&entry->node, pprev); + return entry; +} + +static int pci_bios_check_devices(struct pci_bus *busses) +{ + dprintf(1, "PCI: check devices\n"); + + // Calculate resources needed for regular (non-bus) devices. + struct pci_device *pci; + foreachpci(pci) { + if (pci->class == PCI_CLASS_BRIDGE_PCI) + busses[pci->secondary_bus].bus_dev = pci; + + struct pci_bus *bus = &busses[pci_bdf_to_bus(pci->bdf)]; + int i; + for (i = 0; i < PCI_NUM_REGIONS; i++) { + if ((pci->class == PCI_CLASS_BRIDGE_PCI) && + (i >= PCI_BRIDGE_NUM_REGIONS && i < PCI_ROM_SLOT)) + continue; + int type, is64; + u64 size; + pci_bios_get_bar(pci, i, &type, &size, &is64); + if (size == 0) + continue; + + if (type != PCI_REGION_TYPE_IO && size < PCI_DEVICE_MEM_MIN) + size = PCI_DEVICE_MEM_MIN; + struct pci_region_entry *entry = pci_region_create_entry( + bus, pci, i, size, size, type, is64); + if (!entry) + return -1; + + if (is64) + i++; + } + } + + // Propagate required bus resources to parent busses. + int secondary_bus; + for (secondary_bus=MaxPCIBus; secondary_bus>0; secondary_bus--) { + struct pci_bus *s = &busses[secondary_bus]; + if (!s->bus_dev) + continue; + struct pci_bus *parent = &busses[pci_bdf_to_bus(s->bus_dev->bdf)]; + int type; + for (type = 0; type < PCI_REGION_TYPE_COUNT; type++) { + u64 align = (type == PCI_REGION_TYPE_IO) ? + PCI_BRIDGE_IO_MIN : PCI_BRIDGE_MEM_MIN; + if (pci_region_align(&s->r[type]) > align) + align = pci_region_align(&s->r[type]); + u64 sum = pci_region_sum(&s->r[type]); + u64 size = ALIGN(sum, align); + int is64 = pci_bios_bridge_region_is64(&s->r[type], + s->bus_dev, type); + // entry->bar is -1 if the entry represents a bridge region + struct pci_region_entry *entry = pci_region_create_entry( + parent, s->bus_dev, -1, size, align, type, is64); + if (!entry) + return -1; + dprintf(1, "PCI: secondary bus %d size %08llx type %s\n", + entry->dev->secondary_bus, size, + region_type_name[entry->type]); + } + } + return 0; +} + + +/**************************************************************** + * BAR assignment + ****************************************************************/ + +// Setup region bases (given the regions' size and alignment) +static int pci_bios_init_root_regions(struct pci_bus *bus) +{ + bus->r[PCI_REGION_TYPE_IO].base = 0xc000; + + struct pci_region *r_end = &bus->r[PCI_REGION_TYPE_PREFMEM]; + struct pci_region *r_start = &bus->r[PCI_REGION_TYPE_MEM]; + + if (pci_region_align(r_start) < pci_region_align(r_end)) { + // Swap regions to improve alignment. + r_end = r_start; + r_start = &bus->r[PCI_REGION_TYPE_PREFMEM]; + } + u64 sum = pci_region_sum(r_end); + u64 align = pci_region_align(r_end); + r_end->base = ALIGN_DOWN((pcimem_end - sum), align); + sum = pci_region_sum(r_start); + align = pci_region_align(r_start); + r_start->base = ALIGN_DOWN((r_end->base - sum), align); + + if ((r_start->base < pcimem_start) || + (r_start->base > pcimem_end)) + // Memory range requested is larger than available. + return -1; + return 0; +} + +#define PCI_IO_SHIFT 8 +#define PCI_MEMORY_SHIFT 16 +#define PCI_PREF_MEMORY_SHIFT 16 + +static void +pci_region_map_one_entry(struct pci_region_entry *entry, u64 addr) +{ + u16 bdf = entry->dev->bdf; + if (entry->bar >= 0) { + dprintf(1, "PCI: map device bdf=%02x:%02x.%x" + " bar %d, addr %08llx, size %08llx [%s]\n", + pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf), pci_bdf_to_fn(bdf), + entry->bar, addr, entry->size, region_type_name[entry->type]); + + pci_set_io_region_addr(entry->dev, entry->bar, addr, entry->is64); + return; + } + + u64 limit = addr + entry->size - 1; + if (entry->type == PCI_REGION_TYPE_IO) { + pci_config_writeb(bdf, PCI_IO_BASE, addr >> PCI_IO_SHIFT); + pci_config_writew(bdf, PCI_IO_BASE_UPPER16, 0); + pci_config_writeb(bdf, PCI_IO_LIMIT, limit >> PCI_IO_SHIFT); + pci_config_writew(bdf, PCI_IO_LIMIT_UPPER16, 0); + } + if (entry->type == PCI_REGION_TYPE_MEM) { + pci_config_writew(bdf, PCI_MEMORY_BASE, addr >> PCI_MEMORY_SHIFT); + pci_config_writew(bdf, PCI_MEMORY_LIMIT, limit >> PCI_MEMORY_SHIFT); + } + if (entry->type == PCI_REGION_TYPE_PREFMEM) { + pci_config_writew(bdf, PCI_PREF_MEMORY_BASE, addr >> PCI_PREF_MEMORY_SHIFT); + pci_config_writew(bdf, PCI_PREF_MEMORY_LIMIT, limit >> PCI_PREF_MEMORY_SHIFT); + pci_config_writel(bdf, PCI_PREF_BASE_UPPER32, addr >> 32); + pci_config_writel(bdf, PCI_PREF_LIMIT_UPPER32, limit >> 32); + } +} + +static void pci_region_map_entries(struct pci_bus *busses, struct pci_region *r) +{ + struct hlist_node *n; + struct pci_region_entry *entry; + hlist_for_each_entry_safe(entry, n, &r->list, node) { + u64 addr = r->base; + r->base += entry->size; + if (entry->bar == -1) + // Update bus base address if entry is a bridge region + busses[entry->dev->secondary_bus].r[entry->type].base = addr; + pci_region_map_one_entry(entry, addr); + hlist_del(&entry->node); + free(entry); + } +} + +static void pci_bios_map_devices(struct pci_bus *busses) +{ + if (pci_bios_init_root_regions(busses)) { + struct pci_region r64_mem, r64_pref; + r64_mem.list.first = NULL; + r64_pref.list.first = NULL; + pci_region_migrate_64bit_entries(&busses[0].r[PCI_REGION_TYPE_MEM], + &r64_mem); + pci_region_migrate_64bit_entries(&busses[0].r[PCI_REGION_TYPE_PREFMEM], + &r64_pref); + + if (pci_bios_init_root_regions(busses)) + panic("PCI: out of 32bit address space\n"); + + u64 sum_mem = pci_region_sum(&r64_mem); + u64 sum_pref = pci_region_sum(&r64_pref); + u64 align_mem = pci_region_align(&r64_mem); + u64 align_pref = pci_region_align(&r64_pref); + + r64_mem.base = ALIGN(0x100000000LL + RamSizeOver4G, align_mem); + r64_pref.base = ALIGN(r64_mem.base + sum_mem, align_pref); + pcimem64_start = r64_mem.base; + pcimem64_end = r64_pref.base + sum_pref; + + pci_region_map_entries(busses, &r64_mem); + pci_region_map_entries(busses, &r64_pref); + } else { + // no bars mapped high -> drop 64bit window (see dsdt) + pcimem64_start = 0; + } + // Map regions on each device. + int bus; + for (bus = 0; bus<=MaxPCIBus; bus++) { + int type; + for (type = 0; type < PCI_REGION_TYPE_COUNT; type++) + pci_region_map_entries(busses, &busses[bus].r[type]); + } +} + + +/**************************************************************** + * Main setup code + ****************************************************************/ + +void +pci_setup(void) +{ + if (!CONFIG_QEMU) + return; + + dprintf(3, "pci setup\n"); + + dprintf(1, "=== PCI bus & bridge init ===\n"); + if (pci_probe_host() != 0) { + return; + } + pci_bios_init_bus(); + + dprintf(1, "=== PCI device probing ===\n"); + pci_probe_devices(); + + pcimem_start = RamSize; + pci_bios_init_platform(); + + dprintf(1, "=== PCI new allocation pass #1 ===\n"); + struct pci_bus *busses = malloc_tmp(sizeof(*busses) * (MaxPCIBus + 1)); + if (!busses) { + warn_noalloc(); + return; + } + memset(busses, 0, sizeof(*busses) * (MaxPCIBus + 1)); + if (pci_bios_check_devices(busses)) + return; + + dprintf(1, "=== PCI new allocation pass #2 ===\n"); + pci_bios_map_devices(busses); + + pci_bios_init_devices(); + + free(busses); + + pci_enable_default_vga(); +} |