blob: b7056be38db07a144e4b6021934a88c0b28a8f7d [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2016, Google, Inc. All rights reserved
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <assert.h>
#include <debug.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <dev/pci_config.h>
#include <dev/pcie_device.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <ktl/iterator.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
/*
* TODO(cja) Re-add the paranoid sanity checks on capability placement
* and size that was in the old code. Doing this sanely likely involves keeping
* the various C style structures for the capabilities in pcie_caps.h originally
*/
static bool quirk_should_force_pcie(const PcieDevice& dev) {
static const struct {
uint16_t vendor_id;
uint16_t device_id;
} QUIRK_LIST[] = {
{.vendor_id = 0x8086, .device_id = 0x1616}, // Wildcat Point GPU
};
for (size_t i = 0; i < ktl::size(QUIRK_LIST); ++i) {
if ((QUIRK_LIST[i].vendor_id == dev.vendor_id()) &&
(QUIRK_LIST[i].device_id == dev.device_id()))
return true;
}
return false;
}
/*
* Advanced Capabilities for Conventional PCI ECN
*/
PciCapAdvFeatures::PciCapAdvFeatures(const PcieDevice& dev, uint16_t base, uint8_t id)
: PciStdCapability(dev, base, id) {
DEBUG_ASSERT(id == PCIE_CAP_ID_ADVANCED_FEATURES);
auto cfg = dev.config();
length_ = PciReg8(static_cast<uint16_t>(base_ + kLengthOffset));
af_caps_ = PciReg8(static_cast<uint16_t>(base_ + kAFCapsOffset));
af_ctrl_ = PciReg8(static_cast<uint16_t>(base_ + kAFControlOffset));
af_status_ = PciReg8(static_cast<uint16_t>(base_ + kAFStatusOffset));
uint8_t caps = cfg->Read(af_caps_);
has_flr_ = PCS_ADVCAPS_CAP_HAS_FUNC_LEVEL_RESET(caps);
has_tp_ = PCS_ADVCAPS_CAP_HAS_TRANS_PENDING(caps);
uint8_t length = cfg->Read(length_);
if (length != PCS_ADVCAPS_LENGTH) {
TRACEF("Length of %u does not match the spec length of %u!\n", length, PCS_ADVCAPS_LENGTH);
return;
}
is_valid_ = true;
}
/*
* PCI Express Base Specification 1.1 Section 7.8 (version 1)
* PCI Express Base Specification 3.1a Section 7.8 (version 2)
*/
PciCapPcie::PciCapPcie(const PcieDevice& dev, uint16_t base, uint8_t id)
: PciStdCapability(dev, base, id) {
DEBUG_ASSERT(id == PCIE_CAP_ID_PCI_EXPRESS);
auto cfg = dev.config();
/* Have we already initialized PCIE? */
if (is_valid_) {
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has more than one PCI "
"Express capability structure!\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id());
return;
}
caps_ = PciReg16(static_cast<uint16_t>(base_ + kPcieCapsOffset));
auto cap_val = cfg->Read(caps_);
version_ = PCS_CAPS_VERSION(cap_val);
devtype_ = PCS_CAPS_DEVTYPE(cap_val);
/*
* Set up all the offsets for the various chunks in the device. Some may
* not be supported, but regardless of whether they are there the final
* structure will be the same.
*/
device.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kDeviceOffset)));
device.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kDeviceOffset)));
device.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kDeviceOffset)));
link.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kLinkOffset)));
link.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kLinkOffset)));
link.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kLinkOffset)));
slot.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kSlotOffset)));
slot.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kSlotOffset)));
slot.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kSlotOffset)));
root.caps_ = PciReg16(static_cast<uint16_t>(base_ + kRootCapsOffset));
root.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kRootControlOffset));
root.status_ = PciReg32(static_cast<uint16_t>(base_ + kRootStatusOffset));
device2.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kDevice2Offset)));
device2.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kDevice2Offset)));
device2.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kDevice2Offset)));
link2.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kLinkOffset)));
link2.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kLinkOffset)));
link2.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kLinkOffset)));
slot2.caps_ = PciReg32(static_cast<uint16_t>(base_ + kCapsOffset(kSlotOffset)));
slot2.ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset(kSlotOffset)));
slot2.status_ = PciReg16(static_cast<uint16_t>(base_ + kStatusOffset(kSlotOffset)));
/* Sanity check the device/port type */
switch (devtype_) {
// Type 0 config header types
case PCIE_DEVTYPE_PCIE_ENDPOINT:
case PCIE_DEVTYPE_LEGACY_PCIE_ENDPOINT:
case PCIE_DEVTYPE_RC_INTEGRATED_ENDPOINT:
case PCIE_DEVTYPE_RC_EVENT_COLLECTOR:
if (dev.is_bridge()) {
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has a Type 0 PCIe "
"device type (0x%x) in PCIe capabilities structure, but "
"does not have a Type 0 config header.\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id(), devtype_);
return;
}
break;
// Type 1 config header types
case PCIE_DEVTYPE_RC_ROOT_PORT:
case PCIE_DEVTYPE_SWITCH_UPSTREAM_PORT:
case PCIE_DEVTYPE_SWITCH_DOWNSTREAM_PORT:
case PCIE_DEVTYPE_PCIE_TO_PCI_BRIDGE:
case PCIE_DEVTYPE_PCI_TO_PCIE_BRIDGE:
if (!dev.is_bridge()) {
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has a Type 1 PCIe "
"device type (0x%x) in PCIe capabilities structure, but "
"does not have a Type 1 config header.\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id(), devtype_);
return;
}
break;
default:
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has an illegal PCIe "
"device type (0x%x) in PCIe capabilities structure.\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id(), devtype_);
return;
}
/* TODO(johngro): remember to read the MSI/MSI-X interrupt message number
* field when setting up for MSI/MSI-X. We almost certainly need to hook
* this IRQ in order to be aware of any changes to the extended
* capabilities. It is unclear whether or not we should allow this IRQ to
* be passed thru to the device driver or not.
*/
/* Check device capabilities to see if we support function level reset or
* not */
uint32_t devcaps = cfg->Read(device.caps());
has_flr_ = (PCS_DEV_CAPS_FUNC_LEVEL_RESET(devcaps) != 0);
is_valid_ = true;
}
/*
* @see PCI Local Bus Specification 3.0 Section 6.8.1
*/
PciCapMsi::PciCapMsi(const PcieDevice& dev, uint16_t base, uint8_t id)
: PciStdCapability(dev, base, id) {
DEBUG_ASSERT(id == PCIE_CAP_ID_MSI);
auto cfg = dev.config();
// Set up the rest of the registers based on whether we're 64 bit or not.
ctrl_ = PciReg16(static_cast<uint16_t>(base_ + kControlOffset));
addr_ = PciReg32(static_cast<uint16_t>(base_ + kAddrOffset));
uint16_t ctrl = cfg->Read(ctrl_reg());
has_pvm_ = PCIE_CAP_MSI_CTRL_PVM_SUPPORTED(ctrl);
is_64_bit_ = PCIE_CAP_MSI_CTRL_64BIT_SUPPORTED(ctrl);
msi_size_ = (has_pvm_ ? (is_64_bit_ ? k64BitPvmSize : k32BitPvmSize)
: (is_64_bit_ ? k64BitNoPvmSize : k32BitNoPvmSize));
if (is_64_bit_) {
addr_upper_ = PciReg32(static_cast<uint16_t>(base_ + kAddrUpperOffset));
data_ = PciReg16(static_cast<uint16_t>(base_ + kData64Offset));
mask_bits_ = PciReg32(static_cast<uint16_t>(base_ + kMaskBits64Offset));
pending_bits_ = PciReg32(static_cast<uint16_t>(base_ + kPendingBits64Offset));
} else {
data_ = PciReg16(static_cast<uint16_t>(base_ + kData32Offset));
mask_bits_ = PciReg32(static_cast<uint16_t>(base_ + kMaskBits32Offset));
pending_bits_ = PciReg32(static_cast<uint16_t>(base_ + kPendingBits32Offset));
}
memset(&irq_block_, 0, sizeof(irq_block_));
uint16_t msi_end = static_cast<uint16_t>(base_ + msi_size_);
uint16_t cfgend = PCIE_BASE_CONFIG_SIZE;
if (msi_end >= cfgend) {
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has illegally positioned MSI "
"capability structure. Structure %s 64-bit addressing and %s "
"per-vector masking and should be %u bytes long, but the "
"structure ends at %u, %u bytes past the end of config "
"space\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id(),
is_64_bit_ ? "supports" : "does not support", has_pvm_ ? "supports" : "does not support",
msi_size_, msi_end, static_cast<unsigned int>(cfgend - msi_end));
return;
}
/* Sanity check the Multi-Message Capable field */
max_irqs_ = 0x1u << PCIE_CAP_MSI_CTRL_GET_MMC(ctrl);
if (max_irqs_ > PCIE_MAX_MSI_IRQS) {
TRACEF(
"Device %02x:%02x.%01x (%04hx:%04hx) has invalid Multi-Message "
"Capable value in MSI capability structure (%d). Structure "
"claims to support %u vectors, but %u is the maximum allowed.\n",
dev.bus_id(), dev.dev_id(), dev.func_id(), dev.vendor_id(), dev.device_id(),
PCIE_CAP_MSI_CTRL_GET_MMC(ctrl), max_irqs_, PCIE_MAX_MSI_IRQS);
return;
}
/* Success!
*
* Make sure that MSI is disabled and that the Multi-Message Enable field is
* set to 1-vector (multi-message disabled). Then record our capabilities
* in the device's bookkeeping and we are done.
*/
cfg->Write(ctrl_reg(), PCIE_CAP_MSI_CTRL_SET_MME(0, PCIE_CAP_MSI_CTRL_SET_ENB(0, ctrl)));
if (has_pvm_)
cfg->Write(mask_bits_reg(), 0xFFFFFFFF);
is_valid_ = true;
}
/* Catch quirks and invalid capability offsets we may see */
inline zx_status_t validate_capability_offset(uint8_t offset) {
if (offset == 0xFF || offset < PCIE_CAP_PTR_MIN_VALID || offset > PCIE_CAP_PTR_MAX_VALID) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
/*
* TODO(cja): It may be worth moving to table based solution like we had before
* where we have a single parse function and a function table for it to use,
* but it involves a bit more worrying about ownership of capabilities and
* std / ext attributes.
*/
zx_status_t PcieDevice::ParseStdCapabilitiesLocked() {
zx_status_t res = ZX_OK;
uint8_t cap_offset = cfg_->Read(PciConfig::kCapabilitiesPtr);
uint8_t caps_found = 0;
fbl::AllocChecker ac;
/*
* Walk the pointer list for the standard capabilities table. As a safety,
* keep track of how many capabilities we've looked at to prevent potential
* cycles from walking forever. Any supported capability will be parsed
* by their object in the PcieDevice, and are additionally stored in a list
* for reference later.
*/
LTRACEF("Scanning for capabilities at %02x:%02x.%01x (%04hx:%04hx)\n", bus_id(), dev_id(),
func_id(), vendor_id(), device_id());
while (cap_offset != PCIE_CAP_PTR_NULL && caps_found < PCIE_MAX_CAPABILITIES) {
if ((res = validate_capability_offset(cap_offset)) != ZX_OK) {
TRACEF("Device %02x:%02x.%01x (%04hx:%04hx) has invalid cptr (%#02x)\n", bus_id(), dev_id(),
func_id(), vendor_id(), device_id(), cap_offset);
break;
}
uint8_t id = cfg_->Read(PciReg8(cap_offset));
LTRACEF("Found capability (#%u, id = 0x%02x) for device %02x:%02x.%01x (%04hx:%04hx)\n",
caps_found, id, bus_id(), dev_id(), func_id(), vendor_id(), device_id());
/*
* 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 cache a raw
* pointer to it for fast access, but otherwise everything is found via the capability list.
*
* TODO(cja): if we make this a two stage allocation/initialization in the future we can do
* away with is_valid() style checks that are done in additional checking if pcie_/msi_ are
* valid pointers.
*/
PciStdCapability* cap;
switch (id) {
case PCIE_CAP_ID_MSI:
cap = irq_.msi = new (&ac) PciCapMsi(*this, cap_offset, id);
break;
case PCIE_CAP_ID_PCI_EXPRESS:
cap = pcie_ = new (&ac) PciCapPcie(*this, cap_offset, id);
break;
case PCIE_CAP_ID_ADVANCED_FEATURES:
cap = pci_af_ = new (&ac) PciCapAdvFeatures(*this, cap_offset, id);
break;
default:
cap = new (&ac) PciStdCapability(*this, cap_offset, id);
break;
}
if (!ac.check()) {
TRACEF("Could not allocate memory fori capability 0x%02x\n", id);
return ZX_ERR_NO_MEMORY;
}
caps_.detected.push_front(ktl::unique_ptr<PciStdCapability>(cap));
cap_offset = cfg_->Read(PciReg8(static_cast<uint16_t>(cap_offset + 0x1))) & 0xFC;
caps_found++;
}
return res;
}
zx_status_t PcieDevice::ParseExtCapabilitiesLocked() {
/*
* TODO(cja): Since ExtCaps are a no-op right now (we had nothing in the table for
* supported extended capabilities) this is a stub for now.
*/
return ZX_OK;
}
// Parse PCI Standard Capabilities starting with the pointer in the PCI
// config structure.
zx_status_t PcieDevice::ProbeCapabilitiesLocked() {
zx_status_t ret = ParseStdCapabilitiesLocked();
if (ret != ZX_OK) {
return ret;
}
/* If this device is PCIe device, the parse the extended configuration
* section of the PCI config looking for extended capabilities. Based on
* the spec, we should only need to look for a PCI Express Capability
* Structure in the standard config section to make the determination that
* this device is a legit PCIe device.
*
* This said, I have encountered at least one device (the graphics
* controller in the Wildcat Point PCH) which clearly is PCIe and clearly
* has extended capabilities, but which is not spec compliant and does not
* contain a proper PCI Express Capability Structure. Because of this, we
* maintain a quirks list of non compliant devices which are actually PCIe,
* but do not appear to be so at first glance. */
if ((is_pcie() || quirk_should_force_pcie(*this)) && pcie_->is_valid()) {
ret = ParseExtCapabilitiesLocked();
}
return ret;
}