blob: b0fc756536ff730a6e2743a60448f0d3044c6d34 [file] [log] [blame]
// Copyright 2019 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.
#ifndef SRC_DEVICES_BUS_DRIVERS_PCI_CONFIG_H_
#define SRC_DEVICES_BUS_DRIVERS_PCI_CONFIG_H_
#include <endian.h>
#include <lib/mmio/mmio.h>
#include <lib/zx/status.h>
#include <stdio.h>
#include <zircon/errors.h>
#include <zircon/hw/pci.h>
#include <zircon/types.h>
#include <ddk/mmio-buffer.h>
#include <ddktl/protocol/pciroot.h>
#include <hwreg/bitfields.h>
namespace pci {
namespace config {
// Fields correspond to the name in the PCI Local Bus Spec section 6.2.
struct Command {
uint16_t value;
// 15-11 are reserved preserve.
DEF_SUBBIT(value, 10, interrupt_disable);
DEF_SUBBIT(value, 9, fast_back_to_back_enable);
DEF_SUBBIT(value, 8, serr_enable);
// 7 is reserved preserve.
DEF_SUBBIT(value, 6, parity_error_response);
DEF_SUBBIT(value, 5, vga_palette_snoop);
DEF_SUBBIT(value, 4, memory_write_and_invalidate_enable);
DEF_SUBBIT(value, 3, special_cycles);
DEF_SUBBIT(value, 2, bus_master);
DEF_SUBBIT(value, 1, memory_space);
DEF_SUBBIT(value, 0, io_space);
};
// The layout of a Base Address Register changes based on its type.
// PCI Local Bus Spec section 6.2.5.1.
struct IoBaseAddress;
struct MmioBaseAddress;
struct BaseAddress : public hwreg::RegisterBase<BaseAddress, uint32_t> {
DEF_RSVDZ_BIT(1);
DEF_BIT(0, is_io_space);
IoBaseAddress* io() { return reinterpret_cast<IoBaseAddress*>(this); }
MmioBaseAddress* mmio() { return reinterpret_cast<MmioBaseAddress*>(this); }
static auto Get() { return hwreg::RegisterAddr<BaseAddress>(0); }
};
struct IoBaseAddress : public BaseAddress {
DEF_UNSHIFTED_FIELD(31, 2, base_address);
};
struct MmioBaseAddress : public BaseAddress {
DEF_UNSHIFTED_FIELD(31, 4, base_address);
DEF_BIT(3, is_prefetchable);
DEF_BIT(2, is_64_bit);
};
} // namespace config
constexpr zx_paddr_t bdf_to_ecam_offset(pci_bdf_t bdf, uint8_t start_bus) {
// Find the offset into the ecam region for the given bdf address. Every bus
// has 32 devices, every device has 8 functions, and each function has an
// extended config space of 4096 bytes. The base address of the vmo provided
// to the bus driver corresponds to the start_bus_num, so offset the bdf address
// based on the bottom of our ecam.
zx_vaddr_t bdf_start = (bdf.bus_id - start_bus) * PCIE_ECAM_BYTES_PER_BUS;
bdf_start += bdf.device_id * PCI_MAX_FUNCTIONS_PER_DEVICE * PCIE_EXTENDED_CONFIG_SIZE;
bdf_start += bdf.function_id * PCIE_EXTENDED_CONFIG_SIZE;
return bdf_start;
}
class PciReg8 {
public:
constexpr explicit PciReg8(uint16_t offset) : offset_(offset) {}
constexpr PciReg8() : offset_(0u) {}
constexpr uint16_t offset() const { return offset_; }
inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }
private:
uint16_t offset_;
};
class PciReg16 {
public:
constexpr explicit PciReg16(uint16_t offset) : offset_(offset) {}
constexpr PciReg16() : offset_(0u) {}
constexpr uint16_t offset() const { return offset_; }
inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }
private:
uint16_t offset_;
};
class PciReg32 {
public:
constexpr explicit PciReg32(uint16_t offset) : offset_(offset) {}
constexpr PciReg32() : offset_(0u) {}
constexpr uint16_t offset() const { return offset_; }
inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }
private:
uint16_t offset_;
};
// Config supplies the factory for creating the appropriate pci config
// object based on the address space of the pci device.
class Config {
public:
// Standard PCI configuration space values. Offsets from PCI Firmware Spec ch 6.
static constexpr PciReg16 kVendorId = PciReg16(0x0);
static constexpr PciReg16 kDeviceId = PciReg16(0x2);
static constexpr PciReg16 kCommand = PciReg16(0x4);
static constexpr PciReg16 kStatus = PciReg16(0x6);
static constexpr PciReg8 kRevisionId = PciReg8(0x8);
static constexpr PciReg8 kProgramInterface = PciReg8(0x9);
static constexpr PciReg8 kSubClass = PciReg8(0xA);
static constexpr PciReg8 kBaseClass = PciReg8(0xB);
static constexpr PciReg8 kCacheLineSize = PciReg8(0xC);
static constexpr PciReg8 kLatencyTimer = PciReg8(0xD);
static constexpr PciReg8 kHeaderType = PciReg8(0xE);
static constexpr PciReg8 kBist = PciReg8(0xF);
// 0x10 is the address of the first BAR in config space
// BAR rather than BaseAddress for space / sanity considerations
static constexpr PciReg32 kBar(uint32_t bar) {
ZX_ASSERT(bar < PCI_MAX_BAR_REGS);
return PciReg32(static_cast<uint16_t>(0x10 + (bar * sizeof(uint32_t))));
}
static constexpr PciReg32 kCardbusCisPtr = PciReg32(0x28);
static constexpr PciReg16 kSubsystemVendorId = PciReg16(0x2C);
static constexpr PciReg16 kSubsystemId = PciReg16(0x2E);
static constexpr PciReg32 kExpansionRomAddress = PciReg32(0x30);
static constexpr PciReg8 kCapabilitiesPtr = PciReg8(0x34);
// 0x35 through 0x3B is reserved
static constexpr PciReg8 kInterruptLine = PciReg8(0x3C);
static constexpr PciReg8 kInterruptPin = PciReg8(0x3D);
static constexpr PciReg8 kMinGrant = PciReg8(0x3E);
static constexpr PciReg8 kMaxLatency = PciReg8(0x3F);
static constexpr uint8_t kStdCfgEnd =
static_cast<uint8_t>(kMaxLatency.offset() + sizeof(uint8_t));
// pci to pci bridge config
// Unlike a normal PCI header, a bridge only has two BARs, but the BAR offset in config space
// is the same.
static constexpr PciReg8 kPrimaryBusId = PciReg8(0x18);
static constexpr PciReg8 kSecondaryBusId = PciReg8(0x19);
static constexpr PciReg8 kSubordinateBusId = PciReg8(0x1A);
static constexpr PciReg8 kSecondaryLatencyTimer = PciReg8(0x1B);
static constexpr PciReg8 kIoBase = PciReg8(0x1C);
static constexpr PciReg8 kIoLimit = PciReg8(0x1D);
static constexpr PciReg16 kSecondaryStatus = PciReg16(0x1E);
static constexpr PciReg16 kMemoryBase = PciReg16(0x20);
static constexpr PciReg16 kMemoryLimit = PciReg16(0x22);
static constexpr PciReg16 kPrefetchableMemoryBase = PciReg16(0x24);
static constexpr PciReg16 kPrefetchableMemoryLimit = PciReg16(0x26);
static constexpr PciReg32 kPrefetchableMemoryBaseUpper = PciReg32(0x28);
static constexpr PciReg32 kPrefetchableMemoryLimitUpper = PciReg32(0x2C);
static constexpr PciReg16 kIoBaseUpper = PciReg16(0x30);
static constexpr PciReg16 kIoLimitUpper = PciReg16(0x32);
// Capabilities Pointer for a bridge matches the standard 0x34 offset
// 0x35 through 0x38 is reserved
static constexpr PciReg32 kBridgeExpansionRomAddress = PciReg32(0x38);
// interrupt line for a bridge matches the standard 0x3C offset
// interrupt pin for a bridge matches the standard 0x3D offset
static constexpr PciReg16 kBridgeControl = PciReg16(0x3E);
inline const pci_bdf_t& bdf() const { return bdf_; }
inline const char* addr(void) const { return addr_; }
virtual const char* type(void) const = 0;
// Return a copy of the MmioView backing the Config's MMIO space, if supported.
virtual zx::status<ddk::MmioView> get_view() const { return zx::error(ZX_ERR_NOT_SUPPORTED); }
// Virtuals
void DumpConfig(uint16_t len) const;
virtual uint8_t Read(const PciReg8 addr) const = 0;
virtual uint16_t Read(const PciReg16 addr) const = 0;
virtual uint32_t Read(const PciReg32 addr) const = 0;
virtual void Write(const PciReg8 addr, uint8_t val) const = 0;
virtual void Write(const PciReg16 addr, uint16_t val) const = 0;
virtual void Write(const PciReg32 addr, uint32_t val) const = 0;
virtual ~Config() {}
protected:
Config(pci_bdf_t bdf) : bdf_(bdf) {
snprintf(addr_, sizeof(addr_), "%02x:%02x.%01x", bdf_.bus_id, bdf_.device_id, bdf_.function_id);
}
const pci_bdf_t bdf_;
char addr_[8];
};
// MMIO config is the stardard method for accessing modern pci configuration space.
// A device's configuration space is mapped to a specific place in a given pci root's
// ecam and can be directly accessed with standard IO operations.t
class MmioConfig : public Config {
public:
static zx_status_t Create(pci_bdf_t bdf, ddk::MmioBuffer* ecam_, uint8_t start_bus,
uint8_t end_bus, std::unique_ptr<Config>* config);
uint8_t Read(const PciReg8 addr) const final;
uint16_t Read(const PciReg16 addr) const final;
uint32_t Read(const PciReg32 addr) const final;
void Write(const PciReg8 addr, uint8_t val) const final;
void Write(const PciReg16 addr, uint16_t val) const final;
void Write(const PciReg32 addr, uint32_t val) const override;
const char* type(void) const override;
virtual zx::status<ddk::MmioView> get_view() const final { return zx::ok(ddk::MmioView(view_)); }
private:
friend class FakeMmioConfig;
MmioConfig(pci_bdf_t bdf, ddk::MmioView&& view) : Config(bdf), view_(std::move(view)) {}
const ddk::MmioView view_;
};
// ProxyConfig is used with PCI buses that do not support MMIO config space,
// or require special controller configuration before config access. Examples
// of this are IO config on x64 due to needing to synchronize CF8/CFC with
// ACPI, and Designware on ARM where the controller needs to be configured to
// map a given device's configuration space in before access.
//
// For proxy configuration access all operations are passed to the pciroot
// protocol implementation hosted in the same devhost as the pci bus driver.
class ProxyConfig final : public Config {
public:
static zx_status_t Create(pci_bdf_t bdf, ddk::PcirootProtocolClient* proto,
std::unique_ptr<Config>* config);
uint8_t Read(const PciReg8 addr) const final;
uint16_t Read(const PciReg16 addr) const final;
uint32_t Read(const PciReg32 addr) const final;
void Write(const PciReg8 addr, uint8_t val) const final;
void Write(const PciReg16 addr, uint16_t val) const final;
void Write(const PciReg32 addr, uint32_t val) const final;
const char* type(void) const final;
private:
ProxyConfig(pci_bdf_t bdf, ddk::PcirootProtocolClient* proto) : Config(bdf), pciroot_(proto) {}
// The bus driver outlives config objects.
ddk::PcirootProtocolClient* const pciroot_;
};
} // namespace pci
#endif // SRC_DEVICES_BUS_DRIVERS_PCI_CONFIG_H_