1 // Copyright 2016 The Fuchsia Authors
2 // Copyright (c) 2009 Corey Tabaka
3 // Copyright (c) 2015 Intel Corporation
4 // Copyright (c) 2016 Travis Geiselbrecht
5 //
6 // Use of this source code is governed by a MIT-style
7 // license that can be found in the LICENSE file or at
8 // https://opensource.org/licenses/MIT
9
10 #if WITH_KERNEL_PCIE
11
12 #include <arch/x86/feature.h>
13 #include <dev/pcie_bus_driver.h>
14 #include <dev/pcie_device.h>
15 #include <fbl/algorithm.h>
16 #include <fbl/ref_ptr.h>
17 #include <inttypes.h>
18 #include <trace.h>
19 #include <zircon/types.h>
20
21 #define LOCAL_TRACE 0
22
23 // Top-of-lower-usable-DRAM quirk.
24 //
25 // Intel processors sometimes steal a bit memory for GPU and SMM needs. When
26 // they do, the BIOS/bootloader sometimes does not report these regions as
27 // reserved in the memory map passed to the OS, they just remove them from the
28 // usable RAM portion of the memory map. If we fail to remove these regions
29 // from the set allocatable MMIO regions used by the PCIe bus driver, we can end
30 // up allocating portions of the bus containing GPU/SMM stolen memory to devices
31 // to use for BAR windows (this would be Very Bad).
32 //
33 // For processors which have a "TOLUD" register (top of lower usable DRAM), we
34 // can simply subtract out the region [0, TOLUD) from the PCIe bus driver's
35 // allocatable regions. This register (on 6th gen Intel Core processors at
36 // least) lives in the config space for the host bridge device. Look for it and
37 // subtract out the region. If we don't find the register, and cannot be sure
38 // that the target we are running on does not need this special treatment, log a
39 // big warning so someone can come and update this code to do the right thing.
pcie_tolud_quirk(const fbl::RefPtr<PcieDevice> & dev)40 static void pcie_tolud_quirk(const fbl::RefPtr<PcieDevice>& dev) {
41 // TODO(johngro): Expand this table as we add support for new
42 // processors/chipsets. Set offset to 0 if no action needs to be taken.
43 static const struct {
44 uint32_t match;
45 uint32_t mask;
46 uint16_t offset;
47 } TOLUD_CHIPSET_LUT[] = {
48 // QEMU's emulation of Intel Q35. No TOLUD register that I know of.
49 {.match = 0x808629c0, .mask = 0xFFFFFFFF, .offset = 0x0},
50 // PIIX4
51 {.match = 0x80861237, .mask = 0xFFFFFFFF, .offset = 0x0},
52 // Second/Third gen core family
53 {.match = 0x80860100, .mask = 0xFFFFFF00, .offset = 0xBC},
54 // Intel 6th Generation Core Family (Skylake)
55 {.match = 0x80861900, .mask = 0xFFFFFF00, .offset = 0xBC},
56
57 // Intel 7th Generation Core Family (Kaby Lake)
58 //
59 // TODO(johngro) : Get confirmation of this. Intel's public docs claim
60 // that the DID is 0x19xx, like Skylake. Hardware I have seen
61 // (i3-7100u), as well as HW that people have talked about online
62 // (i5-7500u, as well as some desktop SKUs), however, all seem to use
63 // 0x59xx.
64 {.match = 0x80865900, .mask = 0xFFFFFF00, .offset = 0xBC},
65 };
66
67 // only makes sense on intel hardware
68 if (x86_vendor != X86_VENDOR_INTEL)
69 return;
70
71 static bool found_chipset_device = false;
72
73 // If we have already recognized our chipset and taken appropriate action,
74 // then there is nothing left for us to do.
75 if (found_chipset_device)
76 return;
77
78 // If dev is nullptr, then the PCIe bus driver is about to start allocating
79 // resources. If we have not recognized the chipset we are running on yet,
80 // log a big warning. Someone needs to come into this code and add support
81 // for the unrecognized chipset (even if not special action needs to be
82 // taken, the quirk needs to be taught to recognize the chipset we are
83 // running on).
84 if (dev == nullptr) {
85 if (!found_chipset_device) {
86 TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n");
87 TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n");
88 TRACEF("PCIe TOLUD quirk was not able to identify the chipset we are running on!\n");
89 TRACEF("Someone needs to teach this quirk about the new chipset!\n");
90 TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n");
91 TRACEF("WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING\n");
92 }
93 return;
94 }
95
96 // The device we are looking for will always be a BDF 00:00.0
97 if (dev->bus_id() || dev->dev_id() || dev->func_id())
98 return;
99
100 // Concatenate the vendor and device ID and search our LUT to see if we
101 // recognize this host bridge.
102 size_t i;
103 uint32_t vid_did = (static_cast<uint32_t>(dev->vendor_id()) << 16) | dev->device_id();
104 for (i = 0; i < fbl::count_of(TOLUD_CHIPSET_LUT); ++i) {
105 const auto& entry = TOLUD_CHIPSET_LUT[i];
106 if ((vid_did & entry.mask) == entry.match)
107 break;
108 }
109
110 if (i >= fbl::count_of(TOLUD_CHIPSET_LUT))
111 return;
112
113 // Looks like we recognize this chip. Check our table to see if there is a
114 // TOLUD register we should read.
115 uint16_t offset = TOLUD_CHIPSET_LUT[i].offset;
116 if (offset) {
117 static constexpr uint32_t TOLUD_MASK = 0xFFF00000;
118 auto tolud_reg = PciReg32(offset);
119 uint32_t tolud_val = dev->config()->Read(tolud_reg) & TOLUD_MASK;
120
121 // Subtract out the TOLUD region from the PCI driver's allocatable MMIO region.
122 if (tolud_val) {
123 LTRACEF("TOLUD Quirk subtracting region [0x%08x, 0x%08x)\n", 0u, tolud_val);
124 zx_status_t res = dev->driver().SubtractBusRegion(0u, tolud_val, PciAddrSpace::MMIO);
125 if (res != ZX_OK)
126 TRACEF("WARNING : PCIe TOLUD Quirk failed to subtract region "
127 "[0x%08x, 0x%08x) (res %d)!\n",
128 0u, tolud_val, res);
129 }
130 }
131
132 found_chipset_device = true;
133 }
134
pcie_amd_topmem_quirk(const fbl::RefPtr<PcieDevice> & dev)135 static void pcie_amd_topmem_quirk(const fbl::RefPtr<PcieDevice>& dev) {
136 // only makes sense on AMD hardware
137 if (x86_vendor != X86_VENDOR_AMD)
138 return;
139
140 // do this the first time
141 static bool initialized = false;
142 if (initialized)
143 return;
144
145 // only do this once
146 initialized = true;
147
148 // see if the TOP_MEM and TOP_MEM2 msrs are active by reading the SYSCFG MSR
149 uint64_t syscfg = read_msr(0xc0010010);
150 LTRACEF("SYSCFG 0x%lx\n", syscfg);
151
152 // for AMD, use the TOP_MEM and TOP_MEM2 MSR
153 // see AMD64 architecture programming manual, volume 2, rev 3.25, page 209
154 uint64_t top_mem = 0;
155 uint64_t top_mem2 = 0;
156 if (syscfg & (1 << 20)) { // MtrrVarDramEn
157 top_mem = read_msr(0xc001001a);
158 }
159 if (syscfg & (1 << 21)) { // MtrrTom2En
160 top_mem2 = read_msr(0xc001001d);
161 }
162
163 /* mask out reserved bits */
164 top_mem &= ((1ULL << 52) - 1);
165 top_mem &= ~((1ULL << 23) - 1);
166 top_mem2 &= ((1ULL << 52) - 1);
167 top_mem2 &= ~((1ULL << 23) - 1);
168
169 LTRACEF("TOP_MEM %#" PRIx64 " TOP_MEM2 %#" PRIx64 "\n", top_mem, top_mem2);
170
171 if (top_mem >= UINT32_MAX) {
172 TRACEF("WARNING: AMD TOP_MEM >= 4GB\n");
173 }
174
175 if (top_mem && dev) {
176 zx_status_t res = dev->driver().SubtractBusRegion(0u, top_mem, PciAddrSpace::MMIO);
177 if (res != ZX_OK) {
178 TRACEF("WARNING : PCIe AMD top_mem quirk failed to subtract region "
179 "[0x0, %#" PRIx64 ") (res %d)!\n",
180 top_mem, res);
181 }
182 }
183
184 if (top_mem2 && dev) {
185 uint64_t max = (1ULL << x86_physical_address_width());
186
187 // TODO: make this subtractive on (0, TOP_MEM2) when we start preloading the
188 // upper pci range.
189 zx_status_t res = dev->driver().AddBusRegion(top_mem2, max, PciAddrSpace::MMIO);
190 if (res != ZX_OK) {
191 TRACEF("WARNING : PCIe AMD top_mem quirk failed to add 64bit region "
192 "[%#" PRIx64 ", %#" PRIx64 ") (res %d)!\n",
193 top_mem2, max, res);
194 }
195 }
196 }
197
198 extern const PcieBusDriver::QuirkHandler pcie_quirk_handlers[] = {
199 pcie_tolud_quirk,
200 pcie_amd_topmem_quirk,
201 nullptr,
202 };
203
204 #endif // WITH_KERNEL_PCIE
205