// Copyright 2016 The Fuchsia Authors // Copyright (c) 2009 Corey Tabaka // Copyright (c) 2015 Intel Corporation // Copyright (c) 2016 Travis Geiselbrecht // // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT #if WITH_KERNEL_PCIE #include #include #include #include #include #include #include #include #define LOCAL_TRACE 0 // Top-of-lower-usable-DRAM quirk. // // Intel processors sometimes steal a bit memory for GPU and SMM needs. When // they do, the BIOS/bootloader sometimes does not report these regions as // reserved in the memory map passed to the OS, they just remove them from the // usable RAM portion of the memory map. If we fail to remove these regions // from the set allocatable MMIO regions used by the PCIe bus driver, we can end // up allocating portions of the bus containing GPU/SMM stolen memory to devices // to use for BAR windows (this would be Very Bad). // // For processors which have a "TOLUD" register (top of lower usable DRAM), we // can simply subtract out the region [0, TOLUD) from the PCIe bus driver's // allocatable regions. This register (on 6th gen Intel Core processors at // least) lives in the config space for the host bridge device. Look for it and // subtract out the region. If we don't find the register, and cannot be sure // that the target we are running on does not need this special treatment, log a // big warning so someone can come and update this code to do the right thing. static void pcie_tolud_quirk(const fbl::RefPtr& dev) { // TODO(johngro): Expand this table as we add support for new // processors/chipsets. Set offset to 0 if no action needs to be taken. static const struct { uint32_t match; uint32_t mask; uint16_t offset; } TOLUD_CHIPSET_LUT[] = { // QEMU's emulation of Intel Q35. No TOLUD register that I know of. {.match = 0x808629c0, .mask = 0xFFFFFFFF, .offset = 0x0}, // PIIX4 {.match = 0x80861237, .mask = 0xFFFFFFFF, .offset = 0x0}, // Second/Third gen core family {.match = 0x80860100, .mask = 0xFFFFFF00, .offset = 0xBC}, // Intel 6th Generation Core Family (Skylake) {.match = 0x80861900, .mask = 0xFFFFFF00, .offset = 0xBC}, // Intel 7th Generation Core Family (Kaby Lake) // // TODO(johngro) : Get confirmation of this. Intel's public docs claim // that the DID is 0x19xx, like Skylake. Hardware I have seen // (i3-7100u), as well as HW that people have talked about online // (i5-7500u, as well as some desktop SKUs), however, all seem to use // 0x59xx. {.match = 0x80865900, .mask = 0xFFFFFF00, .offset = 0xBC}, }; // only makes sense on intel hardware if (x86_vendor != X86_VENDOR_INTEL) return; static bool found_chipset_device = false; // If we have already recognized our chipset and taken appropriate action, // then there is nothing left for us to do. if (found_chipset_device) return; // If dev is nullptr, then the PCIe bus driver is about to start allocating // resources. If we have not recognized the chipset we are running on yet, // log a big warning. Someone needs to come into this code and add support // for the unrecognized chipset (even if not special action needs to be // taken, the quirk needs to be taught to recognize the chipset we are // running on). if (dev == nullptr) { if (!found_chipset_device) { TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n"); TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n"); TRACEF("PCIe TOLUD quirk was not able to identify the chipset we are running on!\n"); TRACEF("Someone needs to teach this quirk about the new chipset!\n"); TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n"); TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n"); } return; } // The device we are looking for will always be a BDF 00:00.0 if (dev->bus_id() || dev->dev_id() || dev->func_id()) return; // Concatenate the vendor and device ID and search our LUT to see if we // recognize this host bridge. size_t i; uint32_t vid_did = (static_cast(dev->vendor_id()) << 16) | dev->device_id(); for (i = 0; i < fbl::count_of(TOLUD_CHIPSET_LUT); ++i) { const auto& entry = TOLUD_CHIPSET_LUT[i]; if ((vid_did & entry.mask) == entry.match) break; } if (i >= fbl::count_of(TOLUD_CHIPSET_LUT)) return; // Looks like we recognize this chip. Check our table to see if there is a // TOLUD register we should read. uint16_t offset = TOLUD_CHIPSET_LUT[i].offset; if (offset) { static constexpr uint32_t TOLUD_MASK = 0xFFF00000; auto tolud_reg = PciReg32(offset); uint32_t tolud_val = dev->config()->Read(tolud_reg) & TOLUD_MASK; // Subtract out the TOLUD region from the PCI driver's allocatable MMIO region. if (tolud_val) { LTRACEF("TOLUD Quirk subtracting region [0x%08x, 0x%08x)\n", 0u, tolud_val); zx_status_t res = dev->driver().SubtractBusRegion(0u, tolud_val, PciAddrSpace::MMIO); if (res != ZX_OK) TRACEF("WARNING : PCIe TOLUD Quirk failed to subtract region " "[0x%08x, 0x%08x) (res %d)!\n", 0u, tolud_val, res); } } found_chipset_device = true; } static void pcie_amd_topmem_quirk(const fbl::RefPtr& dev) { // only makes sense on AMD hardware if (x86_vendor != X86_VENDOR_AMD) return; // do this the first time static bool initialized = false; if (initialized) return; // only do this once initialized = true; // see if the TOP_MEM and TOP_MEM2 msrs are active by reading the SYSCFG MSR uint64_t syscfg = read_msr(0xc0010010); LTRACEF("SYSCFG 0x%lx\n", syscfg); // for AMD, use the TOP_MEM and TOP_MEM2 MSR // see AMD64 architecture programming manual, volume 2, rev 3.25, page 209 uint64_t top_mem = 0; uint64_t top_mem2 = 0; if (syscfg & (1 << 20)) { // MtrrVarDramEn top_mem = read_msr(0xc001001a); } if (syscfg & (1 << 21)) { // MtrrTom2En top_mem2 = read_msr(0xc001001d); } /* mask out reserved bits */ top_mem &= ((1ULL << 52) - 1); top_mem &= ~((1ULL << 23) - 1); top_mem2 &= ((1ULL << 52) - 1); top_mem2 &= ~((1ULL << 23) - 1); LTRACEF("TOP_MEM %#" PRIx64 " TOP_MEM2 %#" PRIx64 "\n", top_mem, top_mem2); if (top_mem >= UINT32_MAX) { TRACEF("WARNING: AMD TOP_MEM >= 4GB\n"); } if (top_mem && dev) { zx_status_t res = dev->driver().SubtractBusRegion(0u, top_mem, PciAddrSpace::MMIO); if (res != ZX_OK) { TRACEF("WARNING : PCIe AMD top_mem quirk failed to subtract region " "[0x0, %#" PRIx64 ") (res %d)!\n", top_mem, res); } } if (top_mem2 && dev) { uint64_t max = (1ULL << x86_physical_address_width()); // TODO: make this subtractive on (0, TOP_MEM2) when we start preloading the // upper pci range. zx_status_t res = dev->driver().AddBusRegion(top_mem2, max, PciAddrSpace::MMIO); if (res != ZX_OK) { TRACEF("WARNING : PCIe AMD top_mem quirk failed to add 64bit region " "[%#" PRIx64 ", %#" PRIx64 ") (res %d)!\n", top_mem2, max, res); } } } extern const PcieBusDriver::QuirkHandler pcie_quirk_handlers[] = { pcie_tolud_quirk, pcie_amd_topmem_quirk, nullptr, }; #endif // WITH_KERNEL_PCIE