blob: 1e32b8e5c3e974a988c687cac95c4635eabcc0d6 [file] [log] [blame]
// 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 "capabilities/msi.h"
#include "capabilities/msix.h"
#include "capabilities/pci_express.h"
#include "common.h"
#include "device.h"
namespace pci {
// These methods 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.
namespace {
template <class RegType>
struct CapabilityHdr {
RegType id;
RegType ptr;
};
// |RegType| one of uint8_t or uint16_t
// |ConfigRegType| one of PciReg8 or PciReg16
template <class RegType, class ConfigRegType>
bool ReadCapability(Config& cfg, RegType offset, CapabilityHdr<RegType>* header) {
if (offset == 0 || offset == std::numeric_limits<RegType>::max()) {
return false;
}
// Read the id (at offset + 0x0) and pointer to the next cap (at offset +
// 0x1). 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(ConfigRegType(offset));
header->ptr = cfg.Read(ConfigRegType(static_cast<uint16_t>(offset + sizeof(RegType))));
// Return the pointer to the next capability based on the new pointer found
// in this entry.
return true;
}
// |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()) {
pci_errorf("%s found cycle in capabilities, disabling device: ", cfg.addr());
bool first = true;
for (auto& cap = found; cap != list->end(); cap++) {
if (!first) {
zxlogf(ERROR, " -> ");
} else {
first = false;
}
zxlogf(ERROR, "%#x", cap->base());
}
zxlogf(ERROR, " -> %#x\n", offset);
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_status_t Device::ConfigureCapabilities() {
fbl::AutoLock dev_lock(&dev_lock_);
zx_status_t st;
if (caps_.msix) {
auto& msix = *caps_.msix;
st = msix.Init(GetBar(msix.table_bar()), GetBar(msix.pba_bar()));
if (st != ZX_OK) {
pci_errorf("Failed to initialize MSI-X: %d\n", st);
return st;
}
}
return ZX_OK;
}
zx_status_t Device::ParseCapabilities() {
// Our starting point comes from the Capability Pointer in the config header.
struct CapabilityHdr<uint8_t> 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<uint8_t, PciReg8>(*cfg_, cap_offset, &hdr)) {
pci_tracef("%s capability %s(%#02x) @ %#02x. Next is %#02x\n", cfg_->addr(),
CapabilityIdToName(static_cast<Capability::Id>(hdr.id)), hdr.id, cap_offset,
hdr.ptr);
if (CapabilityCycleExists<Capability>(*cfg_, &caps_.list, cap_offset)) {
pci_tracef("%s capability cycle detected\n", 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::kPciExpress:
st = AllocateCapability<PciExpressCapability>(cap_offset, *cfg_, &caps_.pcie, &caps_.list);
if (st != ZX_OK) {
pci_errorf("%s Error allocating PCIe capability: %d, %p\n", cfg_->addr(), st, caps_.pcie);
return st;
}
break;
case Capability::Id::kMsi:
st = AllocateCapability<MsiCapability>(cap_offset, *cfg_, &caps_.msi, &caps_.list);
if (st != ZX_OK) {
pci_errorf("%s Error allocating MSI capability: %d, %p\n", 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) {
pci_errorf("%s Error allocating MSI-X capability: %d, %p\n", cfg_->addr(), st,
caps_.msix);
return st;
}
break;
case Capability::Id::kNull:
case Capability::Id::kPciPowerManagement:
case Capability::Id::kAgp:
case Capability::Id::kVpd:
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>(Capability(hdr.id, cap_offset)));
break;
}
cap_offset = hdr.ptr & 0xFC; // Lower two bits are reserved.
if (cap_offset && (cap_offset < PCI_CAP_PTR_MIN_VALID || cap_offset > PCI_CAP_PTR_MAX_VALID)) {
pci_errorf("%s capability pointer out of range: %#02x, disabling device\n", 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 structure.
zx_status_t Device::ProbeCapabilities() {
zx_status_t st = ParseCapabilities();
if (st != ZX_OK) {
return st;
}
// TODO(ZX-3146): Implement extended capabilities
return ZX_OK;
}
} // namespace pci