blob: b5dcc640ce723b5e44a3821af4bc387a33d6a8e0 [file] [log] [blame]
// Copyright 2017 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_VIRTUALIZATION_BIN_VMM_PCI_H_
#define SRC_VIRTUALIZATION_BIN_VMM_PCI_H_
#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <array>
#include <mutex>
#include <vector>
#include <fbl/array.h>
#include "src/virtualization/bin/vmm/bits.h"
#include "src/virtualization/bin/vmm/guest.h"
#include "src/virtualization/bin/vmm/interrupt_controller.h"
#include "src/virtualization/bin/vmm/io.h"
#include "src/virtualization/bin/vmm/platform_device.h"
// clang-format off
// PCI configuration constants.
#define PCI_VENDOR_ID_INTEL 0x8086u
#define PCI_DEVICE_ID_INTEL_Q35 0x29c0u
#define PCI_CLASS_BRIDGE_HOST 0x0600u
// clang-format on
class Guest;
static constexpr size_t kPciMaxDevices = 16;
static constexpr size_t kPciMaxBars = 2;
static constexpr uint8_t kPciCapMinSize = 2; // Minimum size of a PCI capability, in bytes.
static constexpr uint64_t kPciBarMmioAccessSpace = 0;
static constexpr uint64_t kPciBarMmioType64Bit = 0b10 << 1;
static constexpr uint64_t kPciBarMmioAddrMask = ~bit_mask<uint64_t>(4);
// PCI type 1 address manipulation.
constexpr uint8_t pci_type1_bus(uint64_t addr) {
return static_cast<uint8_t>(bits_shift(addr, 23, 16));
}
constexpr uint8_t pci_type1_device(uint64_t addr) {
return static_cast<uint8_t>(bits_shift(addr, 15, 11));
}
constexpr uint8_t pci_type1_function(uint64_t addr) {
return static_cast<uint8_t>(bits_shift(addr, 10, 8));
}
constexpr uint8_t pci_type1_register(uint64_t addr) {
return static_cast<uint8_t>(bits_shift(addr, 7, 2) << 2);
}
class PciBus;
class PciDevice;
// 64-bit PCI Base Address Register (BAR)
//
// PCI BARs indicate a region in memory or (for x86) the IO Port space
// that is used to interact with the device.
//
// This class tracks the size/region/type of such a region and implements
// logic to call back into the device to handle reads and writes as
// necessary.
//
// Thread compatible.
class PciBar : public IoHandler {
public:
class Callback {
public:
virtual zx_status_t Read(uint64_t offset, IoValue* value) = 0;
virtual zx_status_t Write(uint64_t offset, const IoValue& value) = 0;
};
// Construct a BAR of the given type, size, and ID.
//
// `size` will be rounded up to be a power of two, and at least PAGE_SIZE.
PciBar(PciDevice* device, uint64_t size, TrapType trap_type, Callback* callback);
// Get the size / type of the region.
uint64_t size() const { return size_; }
TrapType trap_type() const { return trap_type_; }
// Get/set base address.
//
// Setting the address overwrites any guest-configured value of the register.
uint64_t addr() const { return addr_; }
void set_addr(uint64_t value);
// Get/set the high/low 32-bits of the BAR registers in the PCI config space.
//
// Each 64-bit BAR occupies two 32-bit slots in the config space,
// so `slot` must be 0 or 1.
uint32_t pci_config_reg(size_t slot) const;
void set_pci_config_reg(size_t slot, uint32_t value);
// IoHandler interface.
zx_status_t Read(uint64_t addr, IoValue* value) override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
std::string_view Name() const override;
private:
// Number of 32-bit registers this BAR occupies.
static constexpr size_t kNumBarSlots = 2;
// Calculate the low bits of the BAR containing the type of address space
// this BAR represents.
uint32_t AspaceType() const;
// Pointer to the owning device.
PciDevice* device_;
// Callback used for read/write accesses.
//
// Owned elsewhere.
Callback* callback_;
// Base address.
//
// This is the real base address of the BAR. The value in the PCI
// configuration registers can be modified by the guest, but don't actually
// cause the location of the BAR to change.
uint64_t addr_;
// Size of region, in bytes.
uint64_t size_;
// The type of trap to create for this region.
TrapType trap_type_;
// Raw registers exposed in the PCI config space.
std::array<uint32_t, kNumBarSlots> pci_config_reg_;
};
/* Stores the state of PCI devices. */
class PciDevice {
public:
// Static attributes associated with a device.
struct Attributes {
std::string_view name;
// Device attributes.
uint16_t device_id;
uint16_t vendor_id;
uint16_t subsystem_id;
uint16_t subsystem_vendor_id;
// class, subclass, prog_if, and revision id.
uint32_t device_class;
};
// Handle accesses to this device config space.
zx_status_t ReadConfig(uint64_t reg, IoValue* value) const;
zx_status_t WriteConfig(uint64_t reg, const IoValue& value);
// If interrupts are enabled and the device has one pending, send it to the
// bus.
zx_status_t Interrupt();
// Return a human-readable name for this device, for debugging and logging.
std::string_view name() { return attrs_.name; }
// Returns a pointer to a base address register for this device.
//
// Returns nullptr if the register is not implemented.
const PciBar* bar(size_t n) const { return n < bars_.size() ? &bars_[n] : nullptr; }
// Return static device attributes.
const Attributes& attrs() const { return attrs_; }
protected:
explicit PciDevice(const Attributes& attrs);
virtual ~PciDevice() = default;
// Install the given POD type as a PCI capability.
//
// Capabilities types must have a size aligned to 32-bits.
//
// The "next" pointer in cap header (byte 2) will be overwritten by
// the function, and need not contain any particular value.
template <typename T>
zx_status_t AddCapability(const T& capability) {
static_assert(sizeof(T) >= kPciCapMinSize, "Caps must be at least kPciCapMinSize bytes.");
static_assert(std::is_pod<T>::value, "Type T should be POD.");
static_assert(std::has_unique_object_representations<T>::value,
"Type T should not contain implicit padding.");
return AddCapability(cpp20::span(reinterpret_cast<const uint8_t*>(&capability), sizeof(T)));
}
// Install the given PciBar in the next available slot, returning the index
// the PciBar was installed at.
//
// Returns ZX_ERR_NO_RESOURCES if all BARs have already been used.
zx::result<size_t> AddBar(PciBar bar);
private:
friend class PciBus;
// Install a capability from the given payload.
zx_status_t AddCapability(cpp20::span<const uint8_t> payload);
// Setup traps and handlers for accesses to BAR regions.
zx_status_t SetupBarTraps(Guest* guest, async_dispatcher_t* dispatcher);
zx_status_t ReadConfigWord(uint8_t reg, uint32_t* value) const;
// Read 32-bit from the capability area of the device's config space.
zx_status_t ReadCapability(size_t offset, uint32_t* out) const __TA_REQUIRES(mutex_);
// Returns true when an interrupt is active.
virtual bool HasPendingInterrupt() const = 0;
mutable std::mutex mutex_;
// Base address registers.
std::vector<PciBar> bars_;
// Static attributes for this device.
const Attributes attrs_;
// PCI bus this device is connected to.
PciBus* bus_ = nullptr;
// IRQ vector assigned by the bus.
uint32_t global_irq_ = 0;
// PCI config register "command".
uint16_t command_ __TA_GUARDED(mutex_) = 0;
// PCI config register "interrupt line".
//
// The value written here is not used by us for anything, but
// software relies on storing arbitrary values here.
uint8_t reg_interrupt_line_ __TA_GUARDED(mutex_) = 0;
// Capability section of the config space.
std::vector<uint8_t> capabilities_ __TA_GUARDED(mutex_);
// Offset to the beginning of the final capability in the config space.
std::optional<uint8_t> last_cap_offset_ __TA_GUARDED(mutex_);
};
class PciPortHandler : public IoHandler {
public:
explicit PciPortHandler(PciBus* bus);
zx_status_t Read(uint64_t addr, IoValue* value) override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
std::string_view Name() const override { return "PCI Bus"; }
private:
PciBus* bus_;
};
class PciEcamHandler : public IoHandler {
public:
explicit PciEcamHandler(PciBus* bus);
zx_status_t Read(uint64_t addr, IoValue* value) override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
std::string_view Name() const override { return "PCI Bus"; }
private:
PciBus* bus_;
};
class PciRootComplex : public PciDevice {
public:
explicit PciRootComplex(const Attributes& attrs);
private:
bool HasPendingInterrupt() const override { return false; }
};
class PciBus : public PlatformDevice {
public:
PciBus(Guest* guest, InterruptController* interrupt_controller);
zx_status_t Init(async_dispatcher_t* dispatcher);
// Connect a PCI device to the bus.
//
// |slot| must be between 1 and kPciMaxDevices (slot 0 is reserved for the
// root complex).
//
// This method is *not* thread-safe and must only be called during
// initialization.
zx_status_t Connect(PciDevice* device,
async_dispatcher_t* dispatcher) __TA_NO_THREAD_SAFETY_ANALYSIS;
// Access devices via the ECAM region.
//
// |addr| is the offset from the start of the ECAM region for this bus.
zx_status_t ReadEcam(uint64_t addr, IoValue* value) const;
zx_status_t WriteEcam(uint64_t addr, const IoValue& value);
// Handle access to the PC IO ports (0xcf8 - 0xcff).
zx_status_t ReadIoPort(uint64_t port, IoValue* value) const;
zx_status_t WriteIoPort(uint64_t port, const IoValue& value);
// Raise an interrupt for the given device.
zx_status_t Interrupt(PciDevice& device) const;
// Returns true if |bus|, |device|, |function| corresponds to a valid
// device address.
bool is_addr_valid(uint8_t bus, uint8_t device, uint8_t function) const {
return bus == 0 && device < kPciMaxDevices && function == 0 && device_[device];
}
// Current config address selected by the 0xcf8 IO port.
uint32_t config_addr();
void set_config_addr(uint32_t addr);
PciDevice* root_complex() { return &root_complex_; }
zx_status_t ConfigureDtb(void* dtb) const override;
private:
mutable std::mutex mutex_;
Guest* guest_;
PciEcamHandler ecam_handler_;
PciPortHandler port_handler_;
// Selected address in PCI config space.
uint32_t config_addr_ __TA_GUARDED(mutex_) = 0;
// Devices on the virtual PCI bus.
PciDevice* device_[kPciMaxDevices] = {};
// IO APIC for use with interrupt redirects.
InterruptController* interrupt_controller_ = nullptr;
// Embedded root complex device.
PciRootComplex root_complex_;
// Next mmio window to be allocated to connected devices.
uint64_t mmio_base_;
// Pointer to the next open PCI slot.
size_t next_open_slot_ = 0;
};
#endif // SRC_VIRTUALIZATION_BIN_VMM_PCI_H_