blob: e21409461b2a1a172e4d9379c54a94000085f1f3 [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.
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <mutex>
#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 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;
// PCI capability structure.
// The 1-byte next pointer will be computed dynamically while traversing the
// capabilities list.
typedef struct pci_cap {
// PCI capability ID as defined in PCI LOCAL BUS SPECIFICATION, REV. 3.0
// Appendix H.
uint8_t id;
// Data for this capability. Must be at least |len| bytes. The first two bytes
// will be ignored (id and next) as these will be populated dynamically.
// They're skipped over in the data pointer to allow common structures to be
// used for read/write where the id/next pointers are embedded in the
// structure.
uint8_t* data;
// Size of |data|.
uint8_t len;
} pci_cap_t;
struct PciBar : public IoHandler {
// Register value.
uint64_t addr;
// Size of this BAR.
uint64_t size;
// The type of trap to create for this region.
TrapType trap_type;
// Pointer to the owning device.
PciDevice* device;
// Bar number.
uint8_t n;
// IoHandler interface.
zx_status_t Read(uint64_t addr, IoValue* value) const override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
uint64_t aspace() const;
uint64_t base() const;
/* Stores the state of PCI devices. */
class PciDevice {
// Static attributes associated with a device.
struct Attributes {
// 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;
// Read from a region mapped by a BAR register.
virtual zx_status_t ReadBar(uint8_t bar, uint64_t addr, IoValue* value) const {
// Write to a region mapped by a BAR register.
virtual zx_status_t WriteBar(uint8_t bar, uint64_t addr, const IoValue& value) {
// 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();
// Determines if the given base address register is implemented for this
// device.
bool is_bar_implemented(size_t bar) const { return bar < kPciMaxBars && bar_[bar].size > 0; }
// 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 is_bar_implemented(n) ? &bar_[n] : nullptr; }
// Install a capability list.
void set_capabilities(const pci_cap_t* caps, size_t num_caps) {
capabilities_ = caps;
num_capabilities_ = num_caps;
PciDevice(const Attributes attrs);
virtual ~PciDevice() = default;
// Base address registers.
PciBar bar_[kPciMaxBars] = {};
friend class PciBus;
// Setup traps and handlers for accesses to BAR regions.
zx_status_t SetupBarTraps(Guest* guest, bool skip_bell, async_dispatcher_t* dispatcher);
zx_status_t ReadConfigWord(uint8_t reg, uint32_t* value) const;
zx_status_t ReadCapability(uint8_t addr, uint32_t* out) const;
const pci_cap_t* FindCapability(uint8_t addr, uint8_t* cap_index, uint32_t* cap_base) const;
// Returns true when an interrupt is active.
virtual bool HasPendingInterrupt() const = 0;
mutable std::mutex mutex_;
// Static attributes for this device.
const Attributes attrs_;
// Command register.
uint16_t command_ __TA_GUARDED(mutex_) = 0;
// Array of capabilities for this device.
const pci_cap_t* capabilities_ = nullptr;
// Size of |capabilities|.
size_t num_capabilities_ = 0;
// PCI bus this device is connected to.
PciBus* bus_ = nullptr;
// IRQ vector assigned by the bus.
uint32_t global_irq_ = 0;
class PciPortHandler : public IoHandler {
PciPortHandler(PciBus* bus);
zx_status_t Read(uint64_t addr, IoValue* value) const override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
PciBus* bus_;
class PciEcamHandler : public IoHandler {
PciEcamHandler(PciBus* bus);
zx_status_t Read(uint64_t addr, IoValue* value) const override;
zx_status_t Write(uint64_t addr, const IoValue& value) override;
PciBus* bus_;
class PciRootComplex : public PciDevice {
PciRootComplex(const Attributes attrs) : PciDevice(attrs) {}
bool HasPendingInterrupt() const override { return false; }
class PciBus : public PlatformDevice {
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,
bool skip_bell = false) __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;
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;