| // 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 <zircon/errors.h> |
| |
| #include <fbl/string_buffer.h> |
| #include <hwreg/bitfields.h> |
| |
| #include "src/devices/bus/drivers/pci/capabilities/msi.h" |
| #include "src/devices/bus/drivers/pci/capabilities/msix.h" |
| #include "src/devices/bus/drivers/pci/capabilities/pci_express.h" |
| #include "src/devices/bus/drivers/pci/capabilities/power_management.h" |
| #include "src/devices/bus/drivers/pci/common.h" |
| #include "src/devices/bus/drivers/pci/device.h" |
| |
| namespace pci { |
| |
| struct CapabilityHdr { |
| uint8_t id; |
| uint8_t ptr; |
| }; |
| |
| bool ReadCapability(Config& cfg, uint8_t offset, CapabilityHdr* header) { |
| if (offset == 0 || (offset & 0xfc) == 0xfc) { |
| return false; |
| } |
| |
| // Read the id (at offset + 0x0) and pointer to the next cap (at offset + 1 |
| // The lower two bits must be masked off per PCI Local Bus Spec 6.7. |
| // In the case of PCIe, the ptr field also contains the revision number of |
| // the capability and that can be handled in the ParseExtCapabilities() |
| // method. |
| header->id = cfg.Read(PciReg8(offset)); |
| header->ptr = cfg.Read(PciReg8(static_cast<uint16_t>(offset + 1u))) & static_cast<uint8_t>(~0x3); |
| return true; |
| } |
| |
| // PCI Express Base Spec 7.6 |
| struct ExtCapabilityIdReg { |
| uint32_t value; |
| DEF_SUBFIELD(value, 31, 20, offset); |
| DEF_SUBFIELD(value, 19, 16, version); |
| DEF_SUBFIELD(value, 15, 0, id); |
| }; |
| |
| struct ExtCapabilityHdr { |
| uint16_t id; |
| uint16_t ptr; |
| uint8_t version; |
| }; |
| |
| bool ReadExtCapability(Config& cfg, uint16_t offset, ExtCapabilityHdr* header) { |
| if (offset == 0 || (offset & 0xffc) == 0xffc) { |
| return false; |
| } |
| |
| ExtCapabilityIdReg reg = {.value = cfg.Read(PciReg32(offset))}; |
| uint16_t id = static_cast<uint16_t>(reg.id()); |
| if (id == 0xffff) { |
| return false; |
| } |
| |
| // Extended capabilities start with a 16 bit id, followed by a 4 bit version |
| // and a 12 bit pointer to the next offset. Like standard capabilities, the bottom |
| // 2 bits off the offset must be masked off. |
| header->id = id; |
| header->ptr = static_cast<uint16_t>(reg.offset() & ~0x3); |
| header->version = static_cast<uint8_t>(reg.version()); |
| return true; |
| } |
| |
| namespace { |
| // The methods here provide common helper functions that are useful to both |
| // Capability and Extended Capability parsing since they work the same but |
| // differ by the widths of their register space and the valid range of their |
| // addresses. |
| |
| // |CapabilityBaseType| one of Capability, or ExtendedCapability |
| template <class CapabilityBaseType> |
| bool CapabilityCycleExists(const Config& cfg, |
| fbl::DoublyLinkedList<std::unique_ptr<CapabilityBaseType>>* list, |
| typename CapabilityBaseType::RegType offset) { |
| auto found = list->find_if([&offset](const auto& c) { return c.base() == offset; }); |
| if (found != list->end()) { |
| fbl::StringBuffer<256> log; |
| log.AppendPrintf("%s found cycle in capabilities, disabling device: ", cfg.addr()); |
| bool first = true; |
| for (auto& cap = found; cap != list->end(); cap++) { |
| if (!first) { |
| log.AppendPrintf(" -> "); |
| } else { |
| first = false; |
| } |
| log.AppendPrintf("%#x", cap->base()); |
| } |
| log.AppendPrintf(" -> %#x", offset); |
| zxlogf(ERROR, "%s", log.c_str()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // |CapabilityType| one of the values in Capability::Ids or ExtendedCapability::Ids |
| template <class CapabilityType> |
| zx_status_t AllocateCapability( |
| uint16_t offset, const Config& cfg, CapabilityType** out, |
| fbl::DoublyLinkedList<std::unique_ptr<typename CapabilityType::BaseClass>>* list) { |
| // If we find a duplicate of a singleton capability then either we've parsed incorrectly, |
| // or the device configuration space is suspect. |
| if (*out != nullptr) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| auto new_cap = |
| std::make_unique<CapabilityType>(cfg, static_cast<typename CapabilityType::RegType>(offset)); |
| *out = new_cap.get(); |
| list->push_back(std::move(new_cap)); |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| zx::result<> Device::ConfigureCapabilities() { |
| const fbl::AutoLock dev_lock(&dev_lock_); |
| if (caps_.msix && bars()[caps_.msix->table_bar()].has_value() && |
| bars()[caps_.msix->pba_bar()].has_value()) { |
| auto& msix = *caps_.msix; |
| ZX_DEBUG_ASSERT(bars()[msix.table_bar()]); |
| ZX_DEBUG_ASSERT(bars()[msix.pba_bar()]); |
| |
| const zx_status_t status = msix.Init(*bars()[msix.table_bar()], *bars()[msix.pba_bar()]); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to initialize MSI-X: %d", status); |
| return zx::error(status); |
| } |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx_status_t Device::ParseCapabilities() { |
| // Our starting point comes from the Capability Pointer in the config header. |
| struct CapabilityHdr hdr; |
| auto cap_offset = cfg_->Read(Config::kCapabilitiesPtr); |
| if (!cap_offset) { |
| return ZX_OK; |
| } |
| |
| // Walk the pointer list for the standard capabilities table. Check for |
| // cycles and invalid pointers. |
| while (ReadCapability(*cfg_, cap_offset, &hdr)) { |
| if (CapabilityCycleExists<Capability>(*cfg_, &caps_.list, cap_offset)) { |
| zxlogf(ERROR, "%s capability cycle detected", cfg_->addr()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Depending on the capability found we allocate a structure of the |
| // appropriate type and add it to the bookkeeping tree. For important |
| // things like MSI & PCIE we'll cache a raw pointer to it for fast |
| // access, but otherwise everything is found via the capability list. |
| zx_status_t st; |
| switch (static_cast<Capability::Id>(hdr.id)) { |
| case Capability::Id::kPciPowerManagement: |
| st = AllocateCapability<PowerManagementCapability>(cap_offset, *cfg_, &caps_.power, |
| &caps_.list); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "%s Error allocating PowerManagement capability: %d, %p", cfg_->addr(), st, |
| caps_.pcie); |
| return st; |
| } |
| break; |
| case Capability::Id::kPciExpress: |
| st = AllocateCapability<PciExpressCapability>(cap_offset, *cfg_, &caps_.pcie, &caps_.list); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "%s Error allocating PCIe capability: %d, %p", cfg_->addr(), st, |
| caps_.pcie); |
| return st; |
| } |
| is_pcie_ = true; |
| break; |
| case Capability::Id::kMsi: |
| st = AllocateCapability<MsiCapability>(cap_offset, *cfg_, &caps_.msi, &caps_.list); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "%s Error allocating MSI capability: %d, %p", cfg_->addr(), st, caps_.msi); |
| return st; |
| } |
| break; |
| case Capability::Id::kMsiX: |
| st = AllocateCapability<MsixCapability>(cap_offset, *cfg_, &caps_.msix, &caps_.list); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "%s Error allocating MSI-X capability: %d, %p", cfg_->addr(), st, |
| caps_.msix); |
| return st; |
| } |
| break; |
| case Capability::Id::kNull: |
| case Capability::Id::kAgp: |
| case Capability::Id::kVitalProductData: |
| case Capability::Id::kSlotIdentification: |
| case Capability::Id::kCompactPciHotSwap: |
| case Capability::Id::kPciX: |
| case Capability::Id::kHyperTransport: |
| case Capability::Id::kVendor: |
| case Capability::Id::kDebugPort: |
| case Capability::Id::kCompactPciCrc: |
| case Capability::Id::kPciHotplug: |
| case Capability::Id::kPciBridgeSubsystemVendorId: |
| case Capability::Id::kAgp8x: |
| case Capability::Id::kSecureDevice: |
| case Capability::Id::kSataDataNdxCfg: |
| case Capability::Id::kAdvancedFeatures: |
| case Capability::Id::kEnhancedAllocation: |
| case Capability::Id::kFlatteningPortalBridge: |
| caps_.list.push_back(std::make_unique<Capability>(hdr.id, cap_offset)); |
| break; |
| } |
| |
| cap_offset = hdr.ptr; |
| if (cap_offset && (cap_offset < PCI_CAP_PTR_MIN_VALID || cap_offset > PCI_CAP_PTR_MAX_VALID)) { |
| zxlogf(ERROR, "%s capability pointer out of range: %#02x, disabling device", cfg_->addr(), |
| cap_offset); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::ParseExtendedCapabilities() { |
| // Extended capabilities always start at offset 256, the first byte in extended |
| // configuration space. |
| struct ExtCapabilityHdr hdr; |
| uint16_t cap_offset = PCIE_EXT_CAP_BASE_PTR; |
| |
| // Walk the pointer list for the standard capabilities table. Check for |
| // cycles and invalid pointers. |
| while (ReadExtCapability(*cfg_, cap_offset, &hdr)) { |
| if (CapabilityCycleExists<ExtCapability>(*cfg_, &caps_.ext_list, cap_offset)) { |
| zxlogf(TRACE, "%s ext_capability cycle detected", cfg_->addr()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Depending on the capability found we allocate a structure of the |
| // appropriate type and add it to the bookkeeping tree. For important |
| // things like MSI & PCIE we'll cache a raw pointer to it for fast |
| // access, but otherwise everything is found via the capability list. |
| switch (static_cast<ExtCapability::Id>(hdr.id)) { |
| case ExtCapability::Id::kNull: |
| case ExtCapability::Id::kAdvancedErrorReporting: |
| case ExtCapability::Id::kVirtualChannelNoMFVC: |
| case ExtCapability::Id::kDeviceSerialNumber: |
| case ExtCapability::Id::kPowerBudgeting: |
| case ExtCapability::Id::kRootComplexLinkDeclaration: |
| case ExtCapability::Id::kRootComplexInternalLinkControl: |
| case ExtCapability::Id::kRootComplexEventCollectorEndpointAssociation: |
| case ExtCapability::Id::kMultiFunctionVirtualChannel: |
| case ExtCapability::Id::kVirtualChannel: |
| case ExtCapability::Id::kRCRB: |
| case ExtCapability::Id::kVendor: |
| case ExtCapability::Id::kCAC: |
| case ExtCapability::Id::kACS: |
| case ExtCapability::Id::kARI: |
| case ExtCapability::Id::kATS: |
| case ExtCapability::Id::kSR_IOV: |
| case ExtCapability::Id::kMR_IOV: |
| case ExtCapability::Id::kMulticast: |
| case ExtCapability::Id::kPRI: |
| case ExtCapability::Id::kEnhancedAllocation: |
| case ExtCapability::Id::kResizableBAR: |
| case ExtCapability::Id::kDynamicPowerAllocation: |
| case ExtCapability::Id::kTPHRequester: |
| case ExtCapability::Id::kLatencyToleranceReporting: |
| case ExtCapability::Id::kSecondaryPCIExpress: |
| case ExtCapability::Id::kPMUX: |
| case ExtCapability::Id::kPASID: |
| case ExtCapability::Id::kLNR: |
| case ExtCapability::Id::kDPC: |
| case ExtCapability::Id::kL1PMSubstates: |
| case ExtCapability::Id::kPrecisionTimeMeasurement: |
| case ExtCapability::Id::kMPCIe: |
| case ExtCapability::Id::kFRSQueueing: |
| case ExtCapability::Id::kReadinessTimeReporting: |
| case ExtCapability::Id::kDesignatedVendor: |
| case ExtCapability::Id::kVFResizableBAR: |
| case ExtCapability::Id::kDataLinkFeature: |
| case ExtCapability::Id::kPhysicalLayer16: |
| case ExtCapability::Id::kLaneMarginingAtReceiver: |
| case ExtCapability::Id::kHierarchyId: |
| case ExtCapability::Id::kNativePCIeEnclosure: |
| case ExtCapability::Id::kPhysicalLayer32: |
| case ExtCapability::Id::kAlternateProtocol: |
| case ExtCapability::Id::kSystemFirmwareIntermediary: |
| caps_.ext_list.push_back(std::make_unique<ExtCapability>(hdr.id, hdr.version, cap_offset)); |
| break; |
| } |
| |
| cap_offset = hdr.ptr; |
| if (cap_offset && |
| (cap_offset < PCIE_EXT_CAP_PTR_MIN_VALID || cap_offset > PCIE_EXT_CAP_PTR_MAX_VALID)) { |
| zxlogf(ERROR, "%s ext_capability pointer out of range: %#02x, disabling device", cfg_->addr(), |
| cap_offset); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Parse PCI Standard Capabilities starting with the pointer in the PCI |
| // config header. |
| zx_status_t Device::ProbeCapabilities() { |
| zx_status_t st = ParseCapabilities(); |
| if (st != ZX_OK) { |
| return st; |
| } |
| |
| // Only attempt to parse extended capabilities if a device is identified as a |
| // PCIe device. |
| if (is_pcie()) { |
| st = ParseExtendedCapabilities(); |
| } |
| return st; |
| } |
| |
| } // namespace pci |