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 <dev/interrupt.h>
13#include <dev/pcie_bus_driver.h>
14#include <dev/pcie_platform.h>
15#include <fbl/limits.h>
16#include <inttypes.h>
17#include <kernel/mutex.h>
18#include <lk/init.h>
19#include <string.h>
20#include <trace.h>
21#include <zircon/syscalls/pci.h>
22#include <zircon/types.h>
23
24#include "platform_p.h"
25
26class X86PciePlatformSupport : public PciePlatformInterface {
27public:
28    X86PciePlatformSupport()
29        : PciePlatformInterface(MsiSupportLevel::MSI) {}
30    zx_status_t AllocMsiBlock(uint requested_irqs,
31                              bool can_target_64bit,
32                              bool is_msix,
33                              msi_block_t* out_block) override {
34        return msi_alloc_block(requested_irqs, can_target_64bit, is_msix, out_block);
35    }
36
37    void FreeMsiBlock(msi_block_t* block) override {
38        msi_free_block(block);
39    }
40
41    void RegisterMsiHandler(const msi_block_t* block,
42                            uint msi_id,
43                            int_handler handler,
44                            void* ctx) override {
45        msi_register_handler(block, msi_id, handler, ctx);
46    }
47};
48
49X86PciePlatformSupport platform_pcie_support;
50
51static void lockdown_pcie_bus_regions(PcieBusDriver& pcie) {
52    // If we get to this point, something has gone Extremely Wrong.  Attempt to
53    // remove all possible allocatable bus addresses from the PCIe bus driver.
54    // This should *never* fail.  If it does, halt and catch fire, even in a
55    // release build.
56    zx_status_t res;
57    res = pcie.SubtractBusRegion(0x0, 0x10000, PciAddrSpace::PIO);
58    ASSERT(res == ZX_OK);
59
60    res = pcie.SubtractBusRegion(0x0, fbl::numeric_limits<uint64_t>::max(), PciAddrSpace::MMIO);
61    ASSERT(res == ZX_OK);
62}
63
64static void x86_pcie_init_hook(uint level) {
65    // Initialize the bus driver
66    zx_status_t res = PcieBusDriver::InitializeDriver(platform_pcie_support);
67    if (res != ZX_OK) {
68        TRACEF("Failed to initialize PCI bus driver (res = %d).  "
69               "PCI will be non-functional.\n",
70               res);
71        return;
72    }
73
74    auto pcie = PcieBusDriver::GetDriver();
75    DEBUG_ASSERT(pcie != nullptr);
76
77    // Compute the initial set of PIO/MMIO bus regions which PCIe is allowed to
78    // allocate to devices for BAR windows.
79    //
80    // TODO(johngro) : do a better job of computing the valid initial PIO
81    // regions we are permitted to use.  Right now, we just hardcode it.
82    constexpr uint64_t pcie_pio_base = 0x8000;
83    constexpr uint64_t pcie_pio_size = 0x10000 - pcie_pio_base;
84
85    res = pcie->AddBusRegion(pcie_pio_base, pcie_pio_size, PciAddrSpace::PIO);
86    if (res != ZX_OK) {
87        TRACEF("WARNING - Failed to add initial PCIe PIO region "
88               "[%" PRIx64 ", %" PRIx64 ") to bus driver! (res %d)\n",
89               pcie_pio_base, pcie_pio_base + pcie_pio_size, res);
90    }
91
92    // TODO(johngro) : Right now, we add only the low memory (< 4GB) region to
93    // the allocatable set and then subtract out everything else.  Someday, we
94    // should really add in the entire 64-bit address space as a starting point.
95    //
96    // Also, we may want to consider unconditionally subtracting out the region
97    // from [0xFEC00000, 4 << 30).  x86/64 architecture specific registers tend
98    // to live here and it would be Very Bad to allow PCI to allocate BARs in
99    // this region.  In theory, this region should be listed in the e820 map
100    // given to us by the bootloader/BIOS, but bootloaders have been known to
101    // make mistakes in the past.
102    constexpr uint64_t pcie_mmio_base = 0x0;
103    constexpr uint64_t pcie_mmio_size = 0x100000000;
104    res = pcie->AddBusRegion(pcie_mmio_base, pcie_mmio_size, PciAddrSpace::MMIO);
105    if (res != ZX_OK) {
106        TRACEF("WARNING - Failed to add initial PCIe MMIO region "
107               "[%" PRIx64 ", %" PRIx64 ") to bus driver! (res %d)\n",
108               pcie_mmio_base, pcie_mmio_base + pcie_mmio_size, res);
109        return;
110    }
111
112    res = enumerate_e820([](uint64_t base, uint64_t size, bool is_mem, void* ctx) -> void {
113        DEBUG_ASSERT(ctx != nullptr);
114        auto pcie = reinterpret_cast<PcieBusDriver*>(ctx);
115        zx_status_t res;
116
117        res = pcie->SubtractBusRegion(base, size, PciAddrSpace::MMIO);
118        if (res != ZX_OK) {
119            // Woah, this is Very Bad!  If we failed to prohibit the PCIe bus
120            // driver from using a region of the MMIO bus we are in a pretty
121            // dangerous situation.  For now, log a message, then attempt to
122            // lockdown the bus.
123            TRACEF("FATAL ERROR - Failed to subtract PCIe MMIO region "
124                   "[%" PRIx64 ", %" PRIx64 ") from bus driver! (res %d)\n",
125                   base, base + size, res);
126            lockdown_pcie_bus_regions(*pcie);
127        }
128    },
129                         pcie.get());
130
131    if (res != ZX_OK) {
132        // Woah, this is Very Bad!  If we failed to prohibit the PCIe bus
133        // driver from using a region of the MMIO bus we are in a pretty
134        // dangerous situation.  For now, log a message, then attempt to
135        // lockdown the bus.
136        TRACEF("FATAL ERROR - Failed to enumerate e820 (res = %d)\n", res);
137        lockdown_pcie_bus_regions(*pcie);
138    }
139}
140
141LK_INIT_HOOK(x86_pcie_init, x86_pcie_init_hook, LK_INIT_LEVEL_PLATFORM);
142
143#endif // WITH_KERNEL_PCIE
144