blob: bcb7fb2198a6edc61ae51332ef35417fe73e86c4 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// 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
#ifndef ZIRCON_SYSTEM_DEV_BUS_PCI_DEVICE_H_
#define ZIRCON_SYSTEM_DEV_BUS_PCI_DEVICE_H_
#include "allocation.h"
#include "capabilities.h"
#include "config.h"
#include "device_proxy.h"
#include "ref_counted.h"
#include <assert.h>
#include <ddktl/device.h>
#include <fbl/algorithm.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/macros.h>
#include <fbl/mutex.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <hw/pci.h>
#include <lib/zx/channel.h>
#include <region-alloc/region-alloc.h>
#include <sys/types.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/hw/pci.h>
#include <zircon/thread_annotations.h>
namespace pci {
// UpstreamNode includes device.h, so only forward declare it here.
class UpstreamNode;
class BusLinkInterface;
// A pci::Device represents a given PCI(e) device on a bus. It can be used
// standalone for a regular PCI(e) device on the bus, or as the base class for a
// Bridge. Most work a pci::Device does is limited to its own registers in
// configuration space and are managed through their Config object handled to it
// during creation. One of the biggest responsibilities of the pci::Device class
// is fulfill the PCI protocol for the driver downstream operating the PCI
// device this corresponds to.
class Device;
using PciDeviceType = ddk::Device<pci::Device, ddk::Rxrpcable>;
class Device : public PciDeviceType,
public fbl::DoublyLinkedListable<Device*> {
public:
using NodeState = fbl::WAVLTreeNodeState<fbl::RefPtr<pci::Device>>;
struct BusListTraits {
static NodeState& node_state(Device& device) {
return device.bus_list_state_;
}
};
// These traits are used for the WAVL tree implementation. They allow device objects
// to be sorted and found in trees by composite bdf address.
struct KeyTraitsSortByBdf {
static const pci_bdf_t& GetKey(pci::Device& dev) {
return dev.cfg_->bdf();
}
static bool LessThan(const pci_bdf_t& bdf1, const pci_bdf_t& bdf2) {
return (bdf1.bus_id < bdf2.bus_id) ||
((bdf1.bus_id == bdf2.bus_id) && (bdf1.device_id < bdf2.device_id)) ||
((bdf1.bus_id == bdf2.bus_id) && (bdf1.device_id == bdf2.device_id) &&
(bdf1.function_id < bdf2.function_id));
}
static bool EqualTo(const pci_bdf_t& bdf1, const pci_bdf_t& bdf2) {
return (bdf1.bus_id == bdf2.bus_id) && (bdf1.device_id == bdf2.device_id) &&
(bdf1.function_id == bdf2.function_id);
}
};
// struct used to hold information about a configured base address register
struct BarInfo {
size_t size = 0;
zx_paddr_t address = 0; // Allocated address for the bar
bool is_mmio;
bool is_64bit;
bool is_prefetchable;
uint32_t bar_id; // The bar index in the config space. If the bar is 64 bit
// then this corresponds to the first half of the register pair
fbl::unique_ptr<PciAllocation> allocation;
};
struct Capabilities {
CapabilityList list;
PciExpressCapability* pcie;
};
// DDKTL PciProtocol methods that will be called by Rxrpc.
zx_status_t RpcConfigRead(const zx::unowned_channel& ch);
zx_status_t RpcConfigWrite(const zx::unowned_channel& ch);
zx_status_t RpcEnableBusMaster(const zx::unowned_channel& ch);
zx_status_t RpcGetAuxdata(const zx::unowned_channel& ch);
zx_status_t RpcGetBar(const zx::unowned_channel& ch);
zx_status_t RpcGetBti(const zx::unowned_channel& ch);
zx_status_t RpcGetDeviceInfo(const zx::unowned_channel& ch);
zx_status_t RpcGetNextCapability(const zx::unowned_channel& ch);
zx_status_t RpcMapInterrupt(const zx::unowned_channel& ch);
zx_status_t RpcQueryIrqMode(const zx::unowned_channel& ch);
zx_status_t RpcResetDevice(const zx::unowned_channel& ch);
zx_status_t RpcSetIrqMode(const zx::unowned_channel& ch);
zx_status_t DdkRxrpc(zx_handle_t channel);
// DDK mix-in impls
void DdkRelease() { delete this; }
// Create, but do not initialize, a device.
static zx_status_t Create(zx_device_t* parent,
fbl::RefPtr<Config>&& config,
UpstreamNode* upstream,
BusLinkInterface* bli);
zx_status_t CreateProxy();
virtual ~Device();
// Bridge or DeviceImpl will need to implement refcounting
PCI_REQUIRE_REFCOUNTED;
// Disallow copying, assigning and moving.
DISALLOW_COPY_ASSIGN_AND_MOVE(Device);
// Trigger a function level reset (if possible)
// TODO(cja): port zx_status_t DoFunctionLevelReset() when we have a way to test it
// Modify bits in the device's command register (in the device config space),
// clearing the bits specified by clr_bits and setting the bits specified by set
// bits. Specifically, the operation will be applied as...
//
// WR(cmd, (RD(cmd) & ~clr) | set)
//
// @param clr_bits The mask of bits to be cleared.
// @param clr_bits The mask of bits to be set.
// @return A zx_status_t indicating success or failure of the operation.
zx_status_t ModifyCmd(uint16_t clr_bits, uint16_t set_bits) TA_EXCL(dev_lock_);
// Enable or disable bus mastering in a device's configuration.
//
// @param enable If true, allow the device to access main system memory as a bus
// master.
// @return A zx_status_t indicating success or failure of the operation.
zx_status_t EnableBusMaster(bool enabled) TA_EXCL(dev_lock_);
// Enable or disable PIO access in a device's configuration.
//
// @param enable If true, allow the device to access its PIO mapped registers.
// @return A zx_status_t indicating success or failure of the operation.
zx_status_t EnablePio(bool enabled) TA_EXCL(dev_lock_);
// Enable or disable MMIO access in a device's configuration.
//
// @param enable If true, allow the device to access its MMIO mapped registers.
// @return A zx_status_t indicating success or failure of the operation.
zx_status_t EnableMmio(bool enabled) TA_EXCL(dev_lock_);
// Return information about the requested base address register, if it has been
// allocated.
//
// @param bar_id The index of the BAR register to fetch info for
// @param out_info A pointer to a BarInfo buffer to store the bar info
//
// @return ZX_OK on success, ZX_INVALID_ARGS if bad a bar bar index or
// pointer is passed in, and ZX_BAD_STATE if the device is disabled.
zx_status_t GetBarInfo(uint32_t bar_id, const BarInfo* out_info) const;
// Requests a device unplug itself from its UpstreamNode and the Bus list.
virtual void Unplug() TA_EXCL(dev_lock_);
// TODO(cja): port void SetQuirksDone() TA_REQ(dev_lock_) { quirks_done_ = true; }
const fbl::RefPtr<Config>& config() const { return cfg_; }
bool plugged_in() const { return plugged_in_; }
bool disabled() const { return disabled_; }
bool quirks_done() const { return quirks_done_; }
bool is_bridge() const { return is_bridge_; }
uint16_t vendor_id() const { return vendor_id_; }
uint16_t device_id() const { return device_id_; }
uint8_t class_id() const { return class_id_; }
uint8_t subclass() const { return subclass_; }
uint8_t prog_if() const { return prog_if_; }
uint8_t rev_id() const { return rev_id_; }
uint8_t bus_id() const { return cfg_->bdf().bus_id; }
uint8_t dev_id() const { return cfg_->bdf().device_id; }
uint8_t func_id() const { return cfg_->bdf().function_id; }
uint32_t bar_count() const { return bar_count_; }
const CapabilityList& capabilities() const { return caps_.list; }
// Dump some information about the device
virtual void Dump() const;
// Devices need to exist in both the top level bus driver class, as well
// as in a list for roots/bridges to track their downstream children. These
// traits facilitate that for us.
protected:
Device(zx_device_t* parent,
fbl::RefPtr<Config>&& config,
UpstreamNode* upstream,
BusLinkInterface* bli,
bool is_bridge)
: PciDeviceType(parent),
is_bridge_(is_bridge),
cfg_(std::move(config)),
bar_count_(is_bridge ? PCI_BAR_REGS_PER_BRIDGE : PCI_BAR_REGS_PER_DEVICE),
upstream_(upstream),
bli_(bli) {}
zx_status_t Init() TA_EXCL(dev_lock_);
zx_status_t InitLocked() TA_REQ(dev_lock_);
fbl::Mutex* dev_lock() { return &dev_lock_; }
// Read the value of the Command register, requires the dev_lock.
uint16_t ReadCmdLocked() TA_REQ(dev_lock_) TA_EXCL(cmd_reg_lock_) {
fbl::AutoLock cmd_lock(&cmd_reg_lock_);
return cfg_->Read(Config::kCommand);
}
void ModifyCmdLocked(uint16_t clr_bits, uint16_t set_bits)
TA_REQ(dev_lock_) TA_EXCL(cmd_reg_lock_);
void AssignCmdLocked(uint16_t value) TA_REQ(dev_lock_) TA_EXCL(cmd_reg_lock_) {
ModifyCmdLocked(0xFFFF, value);
}
bool IoEnabled() TA_REQ(dev_lock_) {
return ReadCmdLocked() & PCI_COMMAND_IO_EN;
}
bool MmioEnabled() TA_REQ(dev_lock_) {
return ReadCmdLocked() & PCI_COMMAND_MEM_EN;
}
zx_status_t ProbeCapabilities() TA_REQ(dev_lock_);
zx_status_t ParseCapabilities() TA_REQ(dev_lock_);
// TODO(cja) port zx_status_t ParseExtendedCapabilities() TA_REQ(dev_lock_);
fbl::Mutex cmd_reg_lock_; // Protection for access to the command register.
const bool is_bridge_; // True if this device is also a bridge
const fbl::RefPtr<Config> cfg_; // Pointer to the device's config interface.
uint16_t vendor_id_; // The device's vendor ID, as read from config
uint16_t device_id_; // The device's device ID, as read from config
uint8_t class_id_; // The device's class ID, as read from config.
uint8_t subclass_; // The device's subclass, as read from config.
uint8_t prog_if_; // The device's programming interface (from cfg)
uint8_t rev_id_; // The device's revision ID (from cfg)
// State related to lifetime management.
bool plugged_in_ = false;
bool disabled_ = false;
bool quirks_done_ = false;
mutable fbl::Mutex dev_lock_;
// Info about the BARs computed and cached during the initial setup/probe,
// indexed by starting BAR register index.
BarInfo bars_[PCI_MAX_BAR_REGS];
const uint32_t bar_count_;
// An upstream node will outlive its downstream devices
UpstreamNode* upstream_; // The upstream node in the device graph.
BusLinkInterface* const bli_;
private:
zx_status_t RpcReply(const zx::unowned_channel& ch, zx_status_t st,
zx_handle_t* handles = nullptr, const uint32_t handle_cnt = 0);
// Allow UpstreamNode implementations to Probe/Allocate/Configure/Disable.
friend class UpstreamNode;
friend class Bridge;
friend class Root;
// Probes a BAR's configuration. If it is already allocated it will try to
// reserve the existing address window for it so that devices configured by system
// firmware can be maintained as much as possible.
zx_status_t ProbeBar(uint32_t bar_id) TA_REQ(dev_lock_);
// Allocates address space for a BAR if it does not already exist.
zx_status_t AllocateBar(uint32_t bar_id) TA_REQ(dev_lock_);
// Called a device to configure (probe/allocate) its BARs
zx_status_t ConfigureBarsLocked() TA_REQ(dev_lock_);
// Called by an UpstreamNode to configure the BARs of a device downsteream.
// Bridge implements it so it can allocate its bridge windows and own BARs before
// configuring downstream BARs..
virtual zx_status_t ConfigureBars() TA_EXCL(dev_lock_);
// Disable a device, and anything downstream of it. The device will
// continue to enumerate, but users will only be able to access config (and
// only in a read only fashion). BAR windows, bus mastering, and interrupts
// will all be disabled.
virtual void Disable() TA_EXCL(dev_lock_);
void DisableLocked() TA_REQ(dev_lock_);
Capabilities caps_ = {};
// IRQ structures
// TODO(cja): Port over the IRQ support from kernel pci.
// Used for Rxrpc / RpcReply for protocol buffers.
PciRpcMsg request_;
PciRpcMsg response_;
// These allow a device to exist in an upstream node list as well
// as the top level bus list of all devices.
friend struct BusListTraits;
friend struct UpstreamListTraits;
NodeState bus_list_state_;
};
} // namespace pci
#endif // ZIRCON_SYSTEM_DEV_BUS_PCI_DEVICE_H_