| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "bus.h" |
| #include "bridge.h" |
| #include "common.h" |
| #include "config.h" |
| #include "device.h" |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/mmio-buffer.h> |
| #include <ddk/platform-defs.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/vector.h> |
| #include <list> |
| |
| namespace pci { |
| |
| // Creates the PCI bus driver instance and attempts initialization. |
| zx_status_t Bus::Create(zx_device_t* parent) { |
| zx_status_t status; |
| pciroot_protocol_t pciroot; |
| if ((status = device_get_protocol(parent, ZX_PROTOCOL_PCIROOT, &pciroot)) != ZX_OK) { |
| pci_errorf("failed to obtain pciroot protocol: %d!\n", status); |
| return status; |
| } |
| |
| Bus* bus = new Bus(parent, &pciroot); |
| if (!bus) { |
| pci_errorf("failed to allocate bus object.\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Grab the info beforehand so we can get segment/bus information from it |
| // and appropriately name our DDK device. |
| pci_platform_info_t info; |
| status = bus->pciroot().GetPciPlatformInfo(&info); |
| if (status != ZX_OK) { |
| pci_errorf("failed to obtain platform information: %d!\n", status); |
| return status; |
| } |
| |
| // Name the bus instance with segment group and bus range, for example: |
| // pci[0][0:255] for a legacy pci bus in segment group 0. |
| char name[32]; |
| snprintf(name, sizeof(name), "pci[%u][%u:%u]", info.segment_group, info.start_bus_num, |
| info.end_bus_num); |
| if ((status = bus->DdkAdd(name)) != ZX_OK) { |
| pci_errorf("failed to add bus driver: %d\n", status); |
| return status; |
| } |
| |
| if ((status = bus->Initialize()) != ZX_OK) { |
| pci_errorf("failed to initialize bus driver: %d!\n", status); |
| bus->DdkRemove(); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Bus::Initialize() { |
| zx_status_t status = pciroot_.GetPciPlatformInfo(&info_); |
| if (status != ZX_OK) { |
| pci_errorf("failed to obtain platform information: %d!\n", status); |
| return status; |
| } |
| |
| if (info_.ecam_vmo != ZX_HANDLE_INVALID) { |
| if ((status = MapEcam()) != ZX_OK) { |
| pci_errorf("failed to map ecam: %d!\n", status); |
| return status; |
| } |
| } |
| |
| // Stash the ops/ctx pointers for the pciroot protocol so we can pass |
| // them to the allocators provided by Pci(e)Root. The initial root is |
| // created to manage the start of the bus id range given to use by the |
| // pciroot protocol. |
| root_ = std::unique_ptr<PciRoot>(new PciRoot(info_.start_bus_num, pciroot_)); |
| |
| // Begin our bus scan starting at our root |
| ScanDownstream(); |
| root_->ConfigureDownstreamBars(); |
| pci_infof("AllDevicesList:\n"); |
| for (auto& dev : device_list_) { |
| dev.Dump(); |
| } |
| |
| pci_infof("%s init done.\n", info_.name); |
| |
| return ZX_OK; |
| } |
| |
| // Maps a vmo as an mmio_buffer to be used as this Bus driver's ECAM region |
| // for config space access. |
| zx_status_t Bus::MapEcam(void) { |
| ZX_DEBUG_ASSERT(info_.ecam_vmo != ZX_HANDLE_INVALID); |
| |
| size_t size; |
| zx_status_t status = zx_vmo_get_size(info_.ecam_vmo, &size); |
| if (status != ZX_OK) { |
| pci_errorf("couldn't get ecam vmo size: %d!\n", status); |
| return status; |
| } |
| |
| status = |
| ddk::MmioBuffer::Create(0, size, zx::vmo(info_.ecam_vmo), ZX_CACHE_POLICY_UNCACHED, &ecam_); |
| if (status != ZX_OK) { |
| pci_errorf("couldn't map ecam vmo: %d!\n", status); |
| return status; |
| } |
| |
| pci_infof("ecam for segment %u mapped at %p (size: %#zx)\n", info_.segment_group, ecam_->get(), |
| ecam_->get_size()); |
| return ZX_OK; |
| } |
| |
| zx_status_t Bus::MakeConfig(pci_bdf_t bdf, std::unique_ptr<Config>* out_config) { |
| zx_status_t status; |
| if (ecam_) { |
| status = MmioConfig::Create(bdf, &(*ecam_), info_.start_bus_num, info_.end_bus_num, out_config); |
| } else { |
| status = ProxyConfig::Create(bdf, &pciroot_, out_config); |
| } |
| |
| if (status != ZX_OK) { |
| pci_errorf("failed to create config for %02x:%02x:%1x: %d!\n", bdf.bus_id, bdf.device_id, |
| bdf.function_id, status); |
| } |
| |
| return status; |
| } |
| |
| // Scan downstream starting at the bus id managed by the Bus's Root. |
| // In the process of scanning, take note of bridges found and configure any that are |
| // unconfigured. In the end the Bus should have a list of all devides, and all bridges |
| // should have a list of pointers to their own downstream devices. |
| zx_status_t Bus::ScanDownstream(void) { |
| std::list<BusScanEntry> scan_list; |
| // First scan the bus id associated with our root. |
| BusScanEntry entry = {{static_cast<uint8_t>(root_->managed_bus_id()), 0, 0}, root_.get()}; |
| entry.upstream = root_.get(); |
| scan_list.push_back(entry); |
| |
| // Process any bridges found under the root, any bridges under those bridges, etc... |
| // It's important that we scan in the order we discover bridges (DFS) because |
| // when we implement bus id assignment it will affect the overall numbering |
| // scheme of the bus topology. |
| while (!scan_list.empty()) { |
| auto entry = scan_list.back(); |
| pci_tracef("scanning from %02x:%02x.%01x upstream: %s\n", entry.bdf.bus_id, entry.bdf.device_id, |
| entry.bdf.function_id, |
| (entry.upstream->type() == UpstreamNode::Type::ROOT) |
| ? "root" |
| : static_cast<Bridge*>(entry.upstream)->config()->addr()); |
| // Remove this entry, otherwise we'll pop the wrong child off if the scan |
| // adds any new bridges / resume points. |
| scan_list.pop_back(); |
| ScanBus(entry, &scan_list); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Bus::ScanBus(BusScanEntry entry, std::list<BusScanEntry>* scan_list) { |
| uint32_t bus_id = entry.bdf.bus_id; // 32bit so bus_id won't overflow 8bit in the loop |
| uint8_t _dev_id = entry.bdf.device_id; |
| uint8_t _func_id = entry.bdf.function_id; |
| UpstreamNode* upstream = entry.upstream; |
| for (uint8_t dev_id = _dev_id; dev_id < PCI_MAX_DEVICES_PER_BUS; dev_id++) { |
| for (uint8_t func_id = _func_id; func_id < PCI_MAX_FUNCTIONS_PER_DEVICE; func_id++) { |
| std::unique_ptr<Config> config; |
| pci_bdf_t bdf = {static_cast<uint8_t>(bus_id), dev_id, func_id}; |
| zx_status_t status = MakeConfig(bdf, &config); |
| if (status != ZX_OK) { |
| continue; |
| } |
| |
| // Check that the device is valid by verifying the vendor and device ids. |
| if (config->Read(Config::kVendorId) == PCI_INVALID_VENDOR_ID) { |
| continue; |
| } |
| |
| bool is_bridge = |
| ((config->Read(Config::kHeaderType) & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE); |
| pci_tracef("\tfound %s at %02x:%02x.%1x\n", (is_bridge) ? "bridge" : "device", bus_id, dev_id, |
| func_id); |
| |
| // If we found a bridge, add it to our bridge list and initialize / |
| // enumerate it after we finish scanning this bus |
| if (is_bridge) { |
| fbl::RefPtr<Bridge> bridge; |
| uint8_t mbus_id = config->Read(Config::kSecondaryBusId); |
| status = Bridge::Create(zxdev(), std::move(config), upstream, this, mbus_id, &bridge); |
| if (status != ZX_OK) { |
| continue; |
| } |
| |
| // Create scan entries for the next device we would have looked |
| // at in the current level of the tree, as well as the new |
| // bridge. Since we always work our way from the top of the scan |
| // stack we effectively scan the bus in a DFS manner. |func_id| |
| // is always incremented by one to ensure we don't scan this |
| // same bdf again. If the incremented value is invalid then the |
| // device_id loop will iterate and we'll be in a good state |
| // again. |
| BusScanEntry resume_entry{}; |
| resume_entry.bdf.bus_id = static_cast<uint8_t>(bus_id); |
| resume_entry.bdf.device_id = dev_id; |
| resume_entry.bdf.function_id = static_cast<uint8_t>(func_id + 1); |
| resume_entry.upstream = upstream; // Same upstream as this scan |
| scan_list->push_back(resume_entry); |
| |
| BusScanEntry bridge_entry{}; |
| bridge_entry.bdf.bus_id = static_cast<uint8_t>(bridge->managed_bus_id()); |
| bridge_entry.upstream = bridge.get(); // the new bridge will be this scan's upstream |
| scan_list->push_back(bridge_entry); |
| // Quit this scan and pick up again based on the scan entries found. |
| return; |
| } else { |
| // Create a device |
| pci::Device::Create(zxdev(), std::move(config), upstream, this); |
| } |
| } |
| |
| // Reset _func_id to zero here so that after we resume a single function |
| // scan we'll be able to scan the full 8 functions of a given device. |
| _func_id = 0; |
| } |
| } |
| |
| void Bus::DdkRelease() { delete this; } |
| |
| } // namespace pci |