blob: b4e9c8f4bd21c9a4dd35df62c080ed2cd19ceba0 [file] [log] [blame]
// 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_DEV_PCIE
#include <arch/x86/feature.h>
#include <inttypes.h>
#include <dev/pcie_bus_driver.h>
#include <dev/pcie_device.h>
#include <fbl/algorithm.h>
#include <fbl/ref_ptr.h>
#include <trace.h>
#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<PcieDevice>& 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 },
// 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<uint32_t>(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);
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_PCIE_QUIRK_HANDLER(pcie_tolud_quirk);
static void pcie_amd_topmem_quirk(const fbl::RefPtr<PcieDevice>& 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) {
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.
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);
}
}
}
STATIC_PCIE_QUIRK_HANDLER(pcie_amd_topmem_quirk);
#endif // WITH_DEV_PCIE