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