| /* | |
| * Firmware replacement code. | |
| * | |
| * Work around broken BIOSes that don't set an aperture or only set the | |
| * aperture in the AGP bridge. | |
| * If all fails map the aperture over some low memory. This is cheaper than | |
| * doing bounce buffering. The memory is lost. This is done at early boot | |
| * because only the bootmem allocator can allocate 32+MB. | |
| * | |
| * Copyright 2002 Andi Kleen, SuSE Labs. | |
| * $Id: aperture.c,v 1.7 2003/08/01 03:36:18 ak Exp $ | |
| */ | |
| #include <linux/config.h> | |
| #include <linux/kernel.h> | |
| #include <linux/types.h> | |
| #include <linux/init.h> | |
| #include <linux/bootmem.h> | |
| #include <linux/mmzone.h> | |
| #include <linux/pci_ids.h> | |
| #include <linux/pci.h> | |
| #include <linux/bitops.h> | |
| #include <asm/e820.h> | |
| #include <asm/io.h> | |
| #include <asm/proto.h> | |
| #include <asm/pci-direct.h> | |
| int iommu_aperture; | |
| int iommu_aperture_disabled __initdata = 0; | |
| int iommu_aperture_allowed __initdata = 0; | |
| int fallback_aper_order __initdata = 1; /* 64MB */ | |
| int fallback_aper_force __initdata = 0; | |
| int fix_aperture __initdata = 1; | |
| /* This code runs before the PCI subsystem is initialized, so just | |
| access the northbridge directly. */ | |
| #define NB_ID_3 (PCI_VENDOR_ID_AMD | (0x1103<<16)) | |
| static u32 __init allocate_aperture(void) | |
| { | |
| #ifdef CONFIG_DISCONTIGMEM | |
| pg_data_t *nd0 = NODE_DATA(0); | |
| #else | |
| pg_data_t *nd0 = &contig_page_data; | |
| #endif | |
| u32 aper_size; | |
| void *p; | |
| if (fallback_aper_order > 7) | |
| fallback_aper_order = 7; | |
| aper_size = (32 * 1024 * 1024) << fallback_aper_order; | |
| /* | |
| * Aperture has to be naturally aligned. This means an 2GB aperture won't | |
| * have much chances to find a place in the lower 4GB of memory. | |
| * Unfortunately we cannot move it up because that would make the | |
| * IOMMU useless. | |
| */ | |
| p = __alloc_bootmem_node(nd0, aper_size, aper_size, 0); | |
| if (!p || __pa(p)+aper_size > 0xffffffff) { | |
| printk("Cannot allocate aperture memory hole (%p,%uK)\n", | |
| p, aper_size>>10); | |
| if (p) | |
| free_bootmem_node(nd0, (unsigned long)p, aper_size); | |
| return 0; | |
| } | |
| printk("Mapping aperture over %d KB of RAM @ %lx\n", | |
| aper_size >> 10, __pa(p)); | |
| return (u32)__pa(p); | |
| } | |
| static int __init aperture_valid(char *name, u64 aper_base, u32 aper_size) | |
| { | |
| if (!aper_base) | |
| return 0; | |
| if (aper_size < 64*1024*1024) { | |
| printk("Aperture from %s too small (%d MB)\n", name, aper_size>>20); | |
| return 0; | |
| } | |
| if (aper_base + aper_size >= 0xffffffff) { | |
| printk("Aperture from %s beyond 4GB. Ignoring.\n",name); | |
| return 0; | |
| } | |
| if (e820_mapped(aper_base, aper_base + aper_size, E820_RAM)) { | |
| printk("Aperture from %s pointing to e820 RAM. Ignoring.\n",name); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| /* Find a PCI capability */ | |
| static __u32 __init find_cap(int num, int slot, int func, int cap) | |
| { | |
| u8 pos; | |
| int bytes; | |
| if (!(read_pci_config_16(num,slot,func,PCI_STATUS) & PCI_STATUS_CAP_LIST)) | |
| return 0; | |
| pos = read_pci_config_byte(num,slot,func,PCI_CAPABILITY_LIST); | |
| for (bytes = 0; bytes < 48 && pos >= 0x40; bytes++) { | |
| u8 id; | |
| pos &= ~3; | |
| id = read_pci_config_byte(num,slot,func,pos+PCI_CAP_LIST_ID); | |
| if (id == 0xff) | |
| break; | |
| if (id == cap) | |
| return pos; | |
| pos = read_pci_config_byte(num,slot,func,pos+PCI_CAP_LIST_NEXT); | |
| } | |
| return 0; | |
| } | |
| /* Read a standard AGPv3 bridge header */ | |
| static __u32 __init read_agp(int num, int slot, int func, int cap, u32 *order) | |
| { | |
| u32 apsize; | |
| u32 apsizereg; | |
| int nbits; | |
| u32 aper_low, aper_hi; | |
| u64 aper; | |
| printk("AGP bridge at %02x:%02x:%02x\n", num, slot, func); | |
| apsizereg = read_pci_config_16(num,slot,func, cap + 0x14); | |
| if (apsizereg == 0xffffffff) { | |
| printk("APSIZE in AGP bridge unreadable\n"); | |
| return 0; | |
| } | |
| apsize = apsizereg & 0xfff; | |
| /* Some BIOS use weird encodings not in the AGPv3 table. */ | |
| if (apsize & 0xff) | |
| apsize |= 0xf00; | |
| nbits = hweight16(apsize); | |
| *order = 7 - nbits; | |
| if ((int)*order < 0) /* < 32MB */ | |
| *order = 0; | |
| aper_low = read_pci_config(num,slot,func, 0x10); | |
| aper_hi = read_pci_config(num,slot,func,0x14); | |
| aper = (aper_low & ~((1<<22)-1)) | ((u64)aper_hi << 32); | |
| printk("Aperture from AGP @ %Lx size %u MB (APSIZE %x)\n", | |
| aper, 32 << *order, apsizereg); | |
| if (!aperture_valid("AGP bridge", aper, (32*1024*1024) << *order)) | |
| return 0; | |
| return (u32)aper; | |
| } | |
| /* Look for an AGP bridge. Windows only expects the aperture in the | |
| AGP bridge and some BIOS forget to initialize the Northbridge too. | |
| Work around this here. | |
| Do an PCI bus scan by hand because we're running before the PCI | |
| subsystem. | |
| All K8 AGP bridges are AGPv3 compliant, so we can do this scan | |
| generically. It's probably overkill to always scan all slots because | |
| the AGP bridges should be always an own bus on the HT hierarchy, | |
| but do it here for future safety. */ | |
| static __u32 __init search_agp_bridge(u32 *order, int *valid_agp) | |
| { | |
| int num, slot, func; | |
| /* Poor man's PCI discovery */ | |
| for (num = 0; num < 32; num++) { | |
| for (slot = 0; slot < 32; slot++) { | |
| for (func = 0; func < 8; func++) { | |
| u32 class, cap; | |
| u8 type; | |
| class = read_pci_config(num,slot,func, | |
| PCI_CLASS_REVISION); | |
| if (class == 0xffffffff) | |
| break; | |
| switch (class >> 16) { | |
| case PCI_CLASS_BRIDGE_HOST: | |
| case PCI_CLASS_BRIDGE_OTHER: /* needed? */ | |
| /* AGP bridge? */ | |
| cap = find_cap(num,slot,func,PCI_CAP_ID_AGP); | |
| if (!cap) | |
| break; | |
| *valid_agp = 1; | |
| return read_agp(num,slot,func,cap,order); | |
| } | |
| /* No multi-function device? */ | |
| type = read_pci_config_byte(num,slot,func, | |
| PCI_HEADER_TYPE); | |
| if (!(type & 0x80)) | |
| break; | |
| } | |
| } | |
| } | |
| printk("No AGP bridge found\n"); | |
| return 0; | |
| } | |
| void __init iommu_hole_init(void) | |
| { | |
| int fix, num; | |
| u32 aper_size, aper_alloc = 0, aper_order, last_aper_order = 0; | |
| u64 aper_base, last_aper_base = 0; | |
| int valid_agp = 0; | |
| if (iommu_aperture_disabled || !fix_aperture) | |
| return; | |
| printk("Checking aperture...\n"); | |
| fix = 0; | |
| for (num = 24; num < 32; num++) { | |
| char name[30]; | |
| if (read_pci_config(0, num, 3, 0x00) != NB_ID_3) | |
| continue; | |
| iommu_aperture = 1; | |
| aper_order = (read_pci_config(0, num, 3, 0x90) >> 1) & 7; | |
| aper_size = (32 * 1024 * 1024) << aper_order; | |
| aper_base = read_pci_config(0, num, 3, 0x94) & 0x7fff; | |
| aper_base <<= 25; | |
| printk("CPU %d: aperture @ %Lx size %u MB\n", num-24, | |
| aper_base, aper_size>>20); | |
| sprintf(name, "northbridge cpu %d", num-24); | |
| if (!aperture_valid(name, aper_base, aper_size)) { | |
| fix = 1; | |
| break; | |
| } | |
| if ((last_aper_order && aper_order != last_aper_order) || | |
| (last_aper_base && aper_base != last_aper_base)) { | |
| fix = 1; | |
| break; | |
| } | |
| last_aper_order = aper_order; | |
| last_aper_base = aper_base; | |
| } | |
| if (!fix && !fallback_aper_force) | |
| return; | |
| if (!fallback_aper_force) | |
| aper_alloc = search_agp_bridge(&aper_order, &valid_agp); | |
| if (aper_alloc) { | |
| /* Got the aperture from the AGP bridge */ | |
| } else if ((!no_iommu && end_pfn >= 0xffffffff>>PAGE_SHIFT) || | |
| force_iommu || | |
| valid_agp || | |
| fallback_aper_force) { | |
| printk("Your BIOS doesn't leave a aperture memory hole\n"); | |
| printk("Please enable the IOMMU option in the BIOS setup\n"); | |
| printk("This costs you %d MB of RAM\n", | |
| 32 << fallback_aper_order); | |
| aper_order = fallback_aper_order; | |
| aper_alloc = allocate_aperture(); | |
| if (!aper_alloc) { | |
| /* Could disable AGP and IOMMU here, but it's probably | |
| not worth it. But the later users cannot deal with | |
| bad apertures and turning on the aperture over memory | |
| causes very strange problems, so it's better to | |
| panic early. */ | |
| panic("Not enough memory for aperture"); | |
| } | |
| } else { | |
| return; | |
| } | |
| /* Fix up the north bridges */ | |
| for (num = 24; num < 32; num++) { | |
| if (read_pci_config(0, num, 3, 0x00) != NB_ID_3) | |
| continue; | |
| /* Don't enable translation yet. That is done later. | |
| Assume this BIOS didn't initialise the GART so | |
| just overwrite all previous bits */ | |
| write_pci_config(0, num, 3, 0x90, aper_order<<1); | |
| write_pci_config(0, num, 3, 0x94, aper_alloc>>25); | |
| } | |
| } |