1// Copyright 2016 The Fuchsia Authors
2// Copyright (c) 2016, Google, Inc. All rights reserved
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7
8#include <assert.h>
9#include <zircon/compiler.h>
10#include <debug.h>
11#include <err.h>
12#include <inttypes.h>
13#include <kernel/mutex.h>
14#include <kernel/spinlock.h>
15#include <vm/vm.h>
16#include <lk/init.h>
17#include <fbl/algorithm.h>
18#include <fbl/limits.h>
19#include <dev/interrupt.h>
20#include <string.h>
21#include <trace.h>
22#include <platform.h>
23
24#include <dev/pcie_bridge.h>
25#include <dev/pcie_root.h>
26
27#define LOCAL_TRACE 0
28
29PcieUpstreamNode::~PcieUpstreamNode() {
30#if LK_DEBUGLEVEL > 0
31     // Sanity check to make sure that all child devices have been released as
32     // well.
33    for (size_t i = 0; i < fbl::count_of(downstream_); ++i)
34        DEBUG_ASSERT(!downstream_[i]);
35#endif
36}
37
38void PcieUpstreamNode::AllocateDownstreamBars() {
39    /* Finally, allocate all of the BARs for our downstream devices.  Make sure
40     * to not access our downstream devices directly.  Instead, hold references
41     * to downstream devices we obtain while holding bus driver's topology lock.
42     * */
43    for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
44        auto device = GetDownstream(i);
45        if (device != nullptr) {
46            zx_status_t res = device->AllocateBars();
47            if (res != ZX_OK)
48                device->Disable();
49        }
50    }
51}
52
53void PcieUpstreamNode::DisableDownstream() {
54    for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
55        auto downstream_device = GetDownstream(i);
56        if (downstream_device)
57            downstream_device->Disable();
58    }
59}
60
61void PcieUpstreamNode::UnplugDownstream() {
62    for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
63        auto downstream_device = GetDownstream(i);
64        if (downstream_device)
65            downstream_device->Unplug();
66    }
67}
68
69void PcieUpstreamNode::ScanDownstream() {
70    DEBUG_ASSERT(driver().RescanLockIsHeld());
71
72    for (uint dev_id = 0; dev_id < PCIE_MAX_DEVICES_PER_BUS; ++dev_id) {
73        for (uint func_id = 0; func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE; ++func_id) {
74            /* If we can find the config, and it has a valid vendor ID, go ahead
75             * and scan it looking for a valid function. */
76            auto cfg = driver().GetConfig(managed_bus_id_, dev_id, func_id);
77            if (cfg == nullptr) {
78                TRACEF("Warning: bus being scanned is outside ecam region!\n");
79                return;
80            }
81
82            uint16_t vendor_id = cfg->Read(PciConfig::kVendorId);
83            bool good_device = cfg && (vendor_id != PCIE_INVALID_VENDOR_ID);
84            if (good_device) {
85                uint16_t device_id = cfg->Read(PciConfig::kDeviceId);
86                LTRACEF("found valid device %04x:%04x at %02x:%02x.%01x\n",
87                        vendor_id, device_id, managed_bus_id_, dev_id, func_id);
88                /* Don't scan the function again if we have already discovered
89                 * it.  If this function happens to be a bridge, go ahead and
90                 * look under it for new devices. */
91                uint ndx    = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id;
92                DEBUG_ASSERT(ndx < fbl::count_of(downstream_));
93
94                auto downstream_device = GetDownstream(ndx);
95                if (!downstream_device) {
96                    auto new_dev = ScanDevice(cfg, dev_id, func_id);
97                    if (new_dev == nullptr) {
98                        TRACEF("Failed to initialize device %02x:%02x.%01x; This is Very Bad.  "
99                               "Device (and any of its children) will be inaccessible!\n",
100                               managed_bus_id_, dev_id, func_id);
101                        good_device = false;
102                    }
103                } else if (downstream_device->is_bridge()) {
104                    // TODO(johngro) : Instead of going up and down the class graph with static
105                    // casts, would it be better to do this with vtable tricks?
106                    static_cast<PcieUpstreamNode*>(
107                    static_cast<PcieBridge*>(downstream_device.get()))->ScanDownstream();
108                }
109            }
110
111            /* If this was function zero, and there is either no device, or the
112             * config's header type indicates that this is not a multi-function
113             * device, then just move on to the next device. */
114            if (!func_id &&
115               (!good_device || !(cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MULTI_FN)))
116                break;
117        }
118    }
119}
120
121fbl::RefPtr<PcieDevice> PcieUpstreamNode::ScanDevice(const PciConfig* cfg,
122                                                      uint dev_id,
123                                                      uint func_id) {
124    DEBUG_ASSERT(cfg);
125    DEBUG_ASSERT(dev_id  < PCIE_MAX_DEVICES_PER_BUS);
126    DEBUG_ASSERT(func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE);
127    DEBUG_ASSERT(driver().RescanLockIsHeld());
128
129    __UNUSED uint ndx = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id;
130    DEBUG_ASSERT(ndx < fbl::count_of(downstream_));
131    DEBUG_ASSERT(downstream_[ndx] == nullptr);
132
133    LTRACEF("Scanning new function at %02x:%02x.%01x\n", managed_bus_id_, dev_id, func_id);
134
135    /* Is there an actual device here? */
136    uint16_t vendor_id = cfg->Read(PciConfig::kVendorId);
137    if (vendor_id == PCIE_INVALID_VENDOR_ID) {
138        LTRACEF("Bad vendor ID (0x%04hx) when looking for PCIe device at %02x:%02x.%01x\n",
139                vendor_id, managed_bus_id_, dev_id, func_id);
140        return nullptr;
141    }
142
143    // Create the either a PcieBridge or a PcieDevice based on the configuration
144    // header type.
145    uint8_t header_type = cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MASK;
146    if (header_type == PCI_HEADER_TYPE_PCI_BRIDGE) {
147        uint secondary_id = cfg->Read(PciConfig::kSecondaryBusId);
148        return PcieBridge::Create(*this, dev_id, func_id, secondary_id);
149    }
150
151    return PcieDevice::Create(*this, dev_id, func_id);
152}
153