blob: 87ad0de4bb24f61a9439dff6a26e09025e88df82 [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_ALLOCATION_H_
#define SRC_DEVICES_BUS_DRIVERS_PCI_ALLOCATION_H_
#include <fuchsia/hardware/pciroot/cpp/banjo.h>
#include <lib/ddk/debug.h>
#include <lib/zx/resource.h>
#include <lib/zx/result.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <optional>
#include <fbl/macros.h>
#include <region-alloc/region-alloc.h>
// PciAllocations and PciAllocators are concepts internal to UpstreamNodes which
// track address space allocations across roots and bridges. PciAllocator is an
// interface for roots and bridges to provide allocators to downstream bridges
// for their own allocations.
//
// === The Life of a PciAllocation ===
// Allocations at the top level of the bus driver are provided by a
// PciRootALlocator. This allocator serves requests from PCI Bridges & Devices
// that are just under the root complex and fulfills them by requesting space
// from the platform bus driver over the PciRoot protocol. When these bridges
// allocate their windows and bars from upstream they are requesting address
// space from the PciRootAllocator. The PciAllocations handed back to them
// contain a base/size pair, as well as a zx::resource corresponding to the
// given address space. A PciAllocation also has the ability to create a VMO
// constrained by the base / size it understands, which can be used for device bar
// allocations for drivers. If the requester of a PciAllocation is a Bridge
// fulfilling its bridge windows then the allocation is fed to the PciAllocators
// of that bridge. These allocators fulfill the same interface as
// PciRootAllocators, except they allow those bridges to provide for devices
// downstream of them.
//
// As a tree, the system looks like this:
//
// Root Protocol
// | |
// v v
// Bridge Bridge
// (RootAllocator) (RootAllocator)
// | |
// v v
// RootAllocation RootAllocation
// | |
// v v
// Bridge Device (bar 4)
// (RegionAllocator)
// | |
// v v
// RegionAllocation RegionAllocation
// | |
// v v
// Device (bar 2) Device (bar 1)
namespace pci {
class PciAllocation {
public:
// Delete Copy and Assignment ctors
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(PciAllocation);
virtual ~PciAllocation() = default;
virtual zx_paddr_t base() const = 0;
virtual size_t size() const = 0;
// Create a VMO bounded by the base/size of this allocation using the
// provided resource. This is used to provide VMOs for device BAR
// allocations.
virtual zx::result<zx::vmo> CreateVmo() const;
// Create a resource for use with zx_ioports_request in the device driver.
virtual zx::result<zx::resource> CreateResource() const;
pci_address_space_t type() const { return type_; }
protected:
PciAllocation(pci_address_space_t type, zx::resource&& resource)
: type_(type), resource_(std::move(resource)) {}
const zx::resource& resource() { return resource_; }
private:
// Allow PciRegionAllocator / Device to duplicate the resource for use further
// down the bridge chain. The security implications of this are not a
// concern because:
// 1. The allocation object strictly bounds the VMO to the specified base & size
// 2. The resource is already in the driver process's address space, so we're not
// leaking it anywhere out of band.
// 3. Device needs to be able to pass a resource to PciProxy for setting
// IO permission bits.
// This is only needed for PciRegionAllocators because PciRootAllocators do not
// hold a backing PciAllocation object.
const pci_address_space_t type_;
const zx::resource resource_;
friend class PciRegionAllocator;
friend class Device;
const zx::resource& resource() const { return resource_; }
};
class PciRootAllocation final : public PciAllocation {
public:
PciRootAllocation(const ddk::PcirootProtocolClient client, const pci_address_space_t type,
zx::resource resource, zx::eventpair ep, zx_paddr_t base, size_t size)
: PciAllocation(type, std::move(resource)),
pciroot_client_(client),
ep_(std::move(ep)),
base_(base),
size_(size) {}
~PciRootAllocation() final = default;
zx_paddr_t base() const final { return base_; }
size_t size() const final { return size_; }
private:
const ddk::PcirootProtocolClient pciroot_client_;
zx::eventpair ep_;
const zx_paddr_t base_;
const size_t size_;
// The platform bus driver is notified the allocation is free when this eventpair is closed.
};
class PciRegionAllocation final : public PciAllocation {
public:
PciRegionAllocation(pci_address_space_t type, zx::resource&& resource,
RegionAllocator::Region::UPtr&& region)
: PciAllocation(type, std::move(resource)), region_(std::move(region)) {}
zx_paddr_t base() const final { return region_->base; }
size_t size() const final { return region_->size; }
private:
// The Region contains the base & size for the allocation through .base and .size
const RegionAllocator::Region::UPtr region_;
};
// The base class for Root & Region allocators used by UpstreamNodes
class PciAllocator {
public:
virtual ~PciAllocator() = default;
// Delete Copy and Assignment ctors
DISALLOW_COPY_ASSIGN_AND_MOVE(PciAllocator);
// Request a region of address space spanning from |base| to |base| + |size|
// for a downstream device or bridge. If |base| is nullopt then the region can be
// allocated from any base.
virtual zx::result<std::unique_ptr<PciAllocation>> Allocate(std::optional<zx_paddr_t> base,
size_t size) = 0;
// Provide this allocator with a PciAllocation, granting it ownership of that
// range of address space for calls to Allocate.
virtual zx_status_t SetParentAllocation(std::unique_ptr<PciAllocation> alloc) {
return ZX_ERR_NOT_SUPPORTED;
}
virtual pci_address_space_t type() const { return type_; }
protected:
explicit PciAllocator(pci_address_space_t type) : type_(type) {}
PciAllocator() = default;
private:
pci_address_space_t type_;
};
// PciRootAllocators are an implementation of PciAllocator designed
// to use the Pciroot protocol for allocation, fulfilling the requirements
// for a PciRoot to implement the UpstreamNode interface.
class PciRootAllocator : public PciAllocator {
public:
PciRootAllocator(ddk::PcirootProtocolClient proto, pci_address_space_t type, bool low)
: PciAllocator(type), pciroot_(proto), low_(low) {}
zx::result<std::unique_ptr<PciAllocation>> Allocate(std::optional<zx_paddr_t> base,
size_t size) final;
private:
// The bus driver outlives allocator objects.
ddk::PcirootProtocolClient const pciroot_;
// This denotes whether this allocator requests memory < 4GB. More detail
// can be found in the explanation for mmio in root.h.
const bool low_;
};
// PciRegionAllocators are a wrapper around RegionAllocators to allow Bridge
// objects to implement the UpstreamNode interface by using regions that they
// are provided by nodes further upstream. They hand out PciRegionAllocations
// which will release allocations back upstream if they go out of scope.
class PciRegionAllocator : public PciAllocator {
public:
zx::result<std::unique_ptr<PciAllocation>> Allocate(std::optional<zx_paddr_t> base,
size_t size) override;
zx_status_t SetParentAllocation(std::unique_ptr<PciAllocation> alloc) override;
// Region allocators are unique in that they derive their time from their backing allocation.
pci_address_space_t type() const final {
if (!parent_alloc_) {
return PCI_ADDRESS_SPACE_NONE;
}
return parent_alloc_->type();
}
private:
std::unique_ptr<PciAllocation> parent_alloc_;
// Unlike a Root allocator which has bookkeeping handled by Pciroot, a
// Region allocator has a backing RegionAllocator object to handle that
// metadata.
RegionAllocator allocator_;
};
} // namespace pci
#endif // SRC_DEVICES_BUS_DRIVERS_PCI_ALLOCATION_H_