blob: 8df644ac6c3632a9b5171cb05c80cf946c23bf3a [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_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_
#define SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include <fidl/fuchsia.sysmem2/cpp/wire.h>
#include <inttypes.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/wire/arena.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/zx/channel.h>
#include <cstdint>
#include <list>
#include <map>
#include <memory>
#include <unordered_map>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/string_printf.h>
#include "src/devices/sysmem/drivers/sysmem/allocation_result.h"
#include "src/devices/sysmem/drivers/sysmem/device.h"
#include "src/devices/sysmem/drivers/sysmem/indent.h"
#include "src/devices/sysmem/drivers/sysmem/logging.h"
#include "src/devices/sysmem/drivers/sysmem/node_properties.h"
#include "src/devices/sysmem/drivers/sysmem/utils.h"
#include "src/devices/sysmem/drivers/sysmem/versions.h"
namespace sysmem_driver {
class BufferCollectionToken;
class BufferCollectionTokenGroup;
class BufferCollection;
class LogicalBuffer;
class LogicalBufferCollection;
class MemoryAllocator;
class Node;
// This class can be used to hold an inspect snapshot of one set of constraints taken from a client
// at a particular point in time.
struct ConstraintInfoSnapshot {
inspect::Node inspect_node;
};
// Each TrackedParentVmo keeps the LogicalBufferCollection alive as long as there are child VMOs
// outstanding (no revoking of child VMOs for now).
//
// This tracking is for the benefit of MemoryAllocator sub-classes that need
// a Delete() call, such as to clean up a slab allocation and/or to inform
// an external allocator of delete.
class TrackedParentVmo {
public:
using DoDelete = fit::callback<void(TrackedParentVmo* parent)>;
// The do_delete callback will be invoked upon the sooner of (A) the client code causing
// ~TrackedParentVmo, or (B) ZX_VMO_ZERO_CHILDREN occurring async after StartWait() is called.
//
// Each TrackedParentVmo associated with a LogicalBufferCollection keeps the
// LogicalBufferCollection alive. Once a (child) VMO has been given out by sysmem, the only
// mechanism to delete TrackedParentVmo is ZX_VMO_ZERO_CHILDREN.
TrackedParentVmo(fbl::RefPtr<LogicalBufferCollection> logical_buffer, zx::vmo vmo,
uint32_t buffer_index, DoDelete do_delete);
~TrackedParentVmo();
// This should only be called after client code has created a child VMO, and will begin the wait
// for ZX_VMO_ZERO_CHILDREN.
zx_status_t StartWait(async_dispatcher_t* dispatcher);
// Cancel the wait.
zx_status_t CancelWait();
zx::vmo TakeVmo();
[[nodiscard]] const zx::vmo& vmo() const;
void set_child_koid(zx_koid_t koid) {
ZX_DEBUG_ASSERT(!child_koid_.has_value());
child_koid_ = koid;
}
[[nodiscard]] std::optional<zx_koid_t> child_koid() const { return child_koid_; }
void set_client_debug_info(ClientDebugInfo client_debug_info) {
client_debug_info_ = client_debug_info;
}
[[nodiscard]] ClientDebugInfo* get_client_debug_info() {
return client_debug_info_.has_value() ? &*client_debug_info_ : nullptr;
}
uint32_t buffer_index() { return buffer_index_; }
// no copy, no move (async::WaitMethod isn't anyway, but just to be clear about it)
TrackedParentVmo(const TrackedParentVmo&) = delete;
TrackedParentVmo& operator=(const TrackedParentVmo&) = delete;
TrackedParentVmo(TrackedParentVmo&&) = delete;
TrackedParentVmo& operator=(TrackedParentVmo&&) = delete;
private:
void OnZeroChildren(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
fbl::RefPtr<LogicalBufferCollection> buffer_collection_;
zx::vmo vmo_;
const uint32_t buffer_index_ = 0x80000000;
// For TrackedParentVmo(s) which are direct parents of sysmem-provided VMO(s), the child VMO's
// koid is retained here for calling Device::RemoveVmoKoid later, since GetVmoInfo by definition
// only asks about VMOs that have handles open (a handle is passed into GetVmoInfo which ensures
// this).
std::optional<zx_koid_t> child_koid_;
// A TrackedParentVmo can outlast a Node, so own a copy here.
std::optional<ClientDebugInfo> client_debug_info_;
DoDelete do_delete_;
async::WaitMethod<TrackedParentVmo, &TrackedParentVmo::OnZeroChildren> zero_children_wait_;
// Only for asserts:
bool waiting_ = {};
};
// This is all the per-VMO info and mechanism except for
// LogicalBufferCollection::allocation_result_info_, which we keep in sync with the set of
// LogicalBuffer(s) in LogicalBufferCollection::buffers_.
//
// This is always held in a std::unique_ptr<> instead of move-only because TrackedParentVmo
// do_delete callbacks within need to capture a non-moving LogicalBuffer. We use std::unique_ptr<>
// rather than shared_ptr<> or fbl::RefPtr<> because we need to ensure we can just delete all the
// buffers in buffers_ if LogicalBufferCollection::Allocate() fails to allocate a later buffer.
class LogicalBuffer {
public:
static fit::result<zx_status_t, std::unique_ptr<LogicalBuffer>> Create(
fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection, uint32_t buffer_index,
zx::vmo parent_vmo);
fit::result<zx_status_t, std::optional<zx::vmo>> CreateWeakVmo(
const ClientDebugInfo& client_debug_info);
LogicalBufferCollection& logical_buffer_collection();
uint32_t buffer_index();
// Client code should take this VMO before moving LogicalBuffer from stack to heap.
zx::vmo TakeStrongChildVmo();
fit::result<zx_status_t, zx::eventpair> DupCloseWeakAsapClientEnd();
// move-only
LogicalBuffer(const LogicalBuffer& to_copy) = delete;
LogicalBuffer& operator=(const LogicalBuffer& to_copy) = delete;
LogicalBuffer(LogicalBuffer&& to_move) = default;
LogicalBuffer& operator=(LogicalBuffer&& to_move) = default;
private:
friend class LogicalBufferCollection;
LogicalBuffer(fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection,
uint32_t buffer_index, zx::vmo parent_vmo);
// true iff construction was successful
bool is_ok();
// requires !is_ok(); returns the failure status
zx_status_t error();
void ComplainLoudlyAboutStillExistingWeakVmoHandles();
fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection_;
uint32_t buffer_index_;
// This is the allocator-provided VMO (parent-most VMO tracked by sysmem; the allocator itself
// may keep a parent VMO of this VMO, but that further parent is not known to sysmem).
//
// When this sees ZX_VMO_ZERO_CHILDREN, we can tell the allocator to free the allocator's VMO and
// reclaim the space. This also deletes parent_vmo_ and LogicalBuffer.
std::unique_ptr<TrackedParentVmo> parent_vmo_;
// This is a child VMO of parent_vmo_, and has as children all sysmem strong VMOs associated with
// this LogicalBuffer.
//
// When this sees ZX_VMO_ZERO_CHILDREN, we can ask clients to close any remaining weak VMOs by
// closing the server end of close_weak_asap, and we set a timer to complain loudly if the
// LogicalBuffer still exists after a while, since this would essentially count as a client
// leaking a VMO. This also deletes strong_parent_vmo_.
std::unique_ptr<TrackedParentVmo> strong_parent_vmo_;
// These are parents of each weak VMO that was sent to a client (separate parent for each sent
// weak VMO).
//
// This map exists to keep each TrackedParentVmo alive until all its child VMOs are gone, and to
// have a way to complain about specific sent child weak VMOs that still haven't closed a while
// after all strong VMOs of a buffer_index have closed.
//
// The TrackedParentVmo's vmo has its name set based on client debug info.
std::unordered_map<TrackedParentVmo*, std::unique_ptr<TrackedParentVmo>> weak_parent_vmos_;
struct CloseWeakAsap {
zx::eventpair server_end;
zx::eventpair client_end;
};
// ~close_weak_asap_ will signal clients via ZX_EVENTPAIR_PEER_CLOSED. This gets deleted when
// ~strong_parent_vmo_.
std::optional<CloseWeakAsap> close_weak_asap_;
bool close_weak_asap_created_ = false;
// If there were any outstanding weak VMOs when *close_weak_asap_ was deleted, this is when
// close_weak_asap_ was deleted, signalling clients via ZX_EVENTPAIR_PEER_CLOSED. If there were
// no outstanding weak VMOs when close_weak_asap_ was deleted, we don't add tallies to
// weak_vmo_histograms because we want the histograms to only include logical buffer collections
// that had any weak VMOs that needed to be closed; this is to avoid a bunch of tallies for
// strong-only collections adding a bunch of tally counts in low duration histogram buckets.
std::optional<zx::time> close_weak_asap_time_;
// Set to a failing status if LogicalBuffer::LogicalBuffer() failed.
zx_status_t error_ = ZX_OK;
zx::vmo strong_child_vmo_;
};
// TODO(dustingreen): MaybeAllocate() should sweep all related incoming channels for ZX_PEER_CLOSED
// and not attempt allocation until all channel close(es) that were pending at the time have been
// processed. Ignoring new channel closes is fine/good.
class LogicalBufferCollection : public fbl::RefCounted<LogicalBufferCollection> {
public:
using Arena = fidl::Arena<>;
using CollectionMap = std::map<BufferCollection*, fbl::RefPtr<BufferCollection>>;
~LogicalBufferCollection();
static void CreateV1(TokenServerEndV1 buffer_collection_token_request, Device* parent_device,
const ClientDebugInfo* client_debug_info);
static void CreateV2(TokenServerEndV2 buffer_collection_token_request, Device* parent_device,
const ClientDebugInfo* client_debug_info);
// |parent_device| the Device* that the calling allocator is part of. The
// tokens_by_koid_ for each Device is separate. If somehow two clients were
// to get connected to two separate sysmem device instances hosted in the
// same devhost, those clients (intentionally) won't be able to share a
// LogicalBufferCollection.
//
// |buffer_collection_token| the client end of the BufferCollectionToken
// being turned in by the client to get a BufferCollection in exchange.
//
// |buffer_collection_request| the server end of a BufferCollection channel
// to be served by the LogicalBufferCollection associated with
// buffer_collection_token.
static void BindSharedCollection(Device* parent_device, zx::channel buffer_collection_token,
CollectionServerEnd buffer_collection_request,
const ClientDebugInfo* client_debug_info);
// ZX_OK if the token is known to the server.
// ZX_ERR_NOT_FOUND if the token isn't known to the server.
static zx_status_t ValidateBufferCollectionToken(Device* parent_device,
zx_koid_t token_server_koid);
// This is used to create the initial BufferCollectionToken, and also used
// by BufferCollectionToken::Duplicate().
//
// The |self| parameter exists only because LogicalBufferCollection can't
// hold a std::weak_ptr<> to itself because that requires libc++ (the binary
// not just the headers) which isn't available in Zircon so far.
void CreateBufferCollectionTokenV1(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
TokenServerEndV1 token_request);
void CreateBufferCollectionTokenV2(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
TokenServerEndV2 token_request);
// This is used by BufferCollectionToken to create a BufferCollectionTokenGroup during the
// FIDL request of the same name.
void CreateBufferCollectionTokenGroupV1(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
GroupServerEndV1 group_request);
void CreateBufferCollectionTokenGroupV2(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
GroupServerEndV2 group_request);
bool CommonCreateBufferCollectionTokenGroupStage1(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
const GroupServerEnd& group_request,
BufferCollectionTokenGroup** out_group);
void AttachLifetimeTracking(zx::eventpair server_end, uint32_t buffers_remaining);
void SweepLifetimeTracking();
// Calling this extra times (including after allocation complete) isn't harmful from a correctness
// point of view.
void OnDependencyReady();
void SetName(uint32_t priority, std::string name);
void SetDebugTimeoutLogDeadline(int64_t deadline);
void SetVerboseLogging();
uint64_t CreateDispensableOrdinal();
void VLogClient(bool is_error, Location location, const NodeProperties* node_properties,
const char* format, va_list args) const;
void LogClientInfo(Location location, const NodeProperties* node_properties, const char* format,
...) const __PRINTFLIKE(4, 5);
void LogClientError(Location location, const NodeProperties* node_properties, const char* format,
...) const __PRINTFLIKE(4, 5);
void VLogClientInfo(Location location, const NodeProperties* node_properties, const char* format,
va_list args) const;
void VLogClientError(Location location, const NodeProperties* node_properties, const char* format,
va_list args) const;
Device* parent_device() const { return parent_device_; }
// For tests.
std::vector<const BufferCollection*> collection_views() const;
// Track/untrack the node by the koid of the client end of its FIDL channel.
//
// While tracked, a node can be found with FindNodeByClientChannelKoid().
//
// Only is_currently_connected() true Node(s) are tracked.
//
// Aside from this tracking, LogicalBufferCollection only cares about NodeProperties, not Nodes,
// but since we need to track by client_koid which is a Node-specific thing, this tracking allows
// for that.
//
// This tracking exists for the benefit of IsAlternateFor(), which is essentially called on one
// node and refers to another node by client endpoint koid, which must be owned by the same
// process as the calling node.
void TrackNodeProperties(NodeProperties* node_properties);
void UntrackNodeProperties(NodeProperties* node_properties);
std::optional<NodeProperties*> FindNodePropertiesByNodeRefKoid(zx_koid_t node_ref_keep_koid);
std::optional<std::string> name() const {
return name_.has_value() ? std::make_optional(name_->name) : std::nullopt;
}
inspect::Node& inspect_node() { return inspect_node_; }
bool is_verbose_logging() const { return is_verbose_logging_; }
uint64_t buffer_collection_id() const { return buffer_collection_id_; }
static fit::result<zx_status_t, BufferCollectionToken*> CommonConvertToken(
Device* parent_device, zx::channel buffer_collection_token,
const ClientDebugInfo* client_debug_info, const char* fidl_message_name);
fit::result<zx_status_t, std::optional<zx::vmo>> CreateWeakVmo(
uint32_t buffer_index, const ClientDebugInfo& client_debug_info);
fit::result<zx_status_t, std::optional<zx::eventpair>> DupCloseWeakAsapClientEnd(
uint32_t buffer_index);
void LogSummary(IndentTracker& indent);
zx::time create_time_monotonic() const { return create_time_monotonic_; }
private:
friend class LogicalBuffer;
friend class NodeProperties;
friend class TrackedParentVmo;
enum class CheckSanitizeStage { kInitial, kNotAggregated, kAggregated };
class Constraints {
public:
Constraints(const Constraints&) = delete;
Constraints(Constraints&&) = default;
Constraints(fuchsia_sysmem2::BufferCollectionConstraints constraints,
const NodeProperties& node_properties)
: buffer_collection_constraints_(std::move(constraints)),
node_properties_(node_properties) {}
const fuchsia_sysmem2::BufferCollectionConstraints& constraints() const {
return buffer_collection_constraints_;
}
fuchsia_sysmem2::BufferCollectionConstraints& mutate_constraints() {
return buffer_collection_constraints_;
}
const ClientDebugInfo& client_debug_info() const {
return node_properties_.client_debug_info();
}
const NodeProperties& node_properties() const { return node_properties_; }
private:
fuchsia_sysmem2::BufferCollectionConstraints buffer_collection_constraints_;
const NodeProperties& node_properties_;
};
using ConstraintsList = std::list<Constraints>;
struct CollectionName {
uint32_t priority{};
std::string name;
};
explicit LogicalBufferCollection(Device* parent_device);
// Will log an error, and then FailRoot().
void LogAndFailRootNode(Location location, fuchsia_sysmem2::Error error, const char* format, ...)
__PRINTFLIKE(4, 5);
// This fails the entire LogicalBufferCollection, by failing the root Node, which propagates that
// failure to the entire Node tree, and also results in zero remaining Node(s). Then once all
// outstanding VMOs have been closed, the LogicalBufferCollection is deleted.
//
// This cleans out a lot of state that's unnecessary after a failure.
void FailRootNode(fuchsia_sysmem2::Error error);
NodeProperties* FindTreeToFail(NodeProperties* failing_node);
// Fails the tree rooted at tree_to_fail. The tree_to_fail is allowed to be the root_, or it can
// be a sub-tree. All the Node(s) from tree_to_fail down are Fail()ed. Any sysmem(1) Node(s) that
// still have channels open will send epitaph. Sysmem2 Node(s) always send ZX_ERR_INTERNAL as the
// epitaph (mainly because an epitaph is required by FIDL bindings). In either case the channel is
// closed. All Node(s) from tree_to_fail downward are also removed from the tree. If tree_to_fail
// is the root, then when all child VMOs have been closed, the LogicalBufferCollection will be
// deleted.
//
// Node(s) are Fail()ed and removed from the tree in child-first order.
//
// These two are only appropriate to use if it's already known which node should fail. If it's
// not yet known which node should fail, call FindTreeToFail() first, or use LogAndFailNode() /
// FailNode().
void LogAndFailDownFrom(Location location, NodeProperties* tree_to_fail,
fuchsia_sysmem2::Error error, const char* format, ...) __PRINTFLIKE(5, 6);
void FailDownFrom(NodeProperties* tree_to_fail, fuchsia_sysmem2::Error error);
// Find the root-most node of member_node's failure domain, and fail that whole failure domain and
// any of its child failure domains.
void LogAndFailNode(Location location, NodeProperties* member_node, fuchsia_sysmem2::Error error,
const char* format, ...) __PRINTFLIKE(5, 6);
void FailNode(NodeProperties* member_node, fuchsia_sysmem2::Error error);
void LogInfo(Location location, const char* format, ...) const;
static void LogErrorStatic(Location location, const ClientDebugInfo* client_debug_info,
const char* format, ...) __PRINTFLIKE(3, 4);
void LogError(Location location, const char* format, ...) const __PRINTFLIKE(3, 4);
void VLogError(Location location, const char* format, va_list args) const;
void ResetGroupChildSelection(std::vector<NodeProperties*>& groups_by_priority);
void InitGroupChildSelection(std::vector<NodeProperties*>& groups_by_priority);
void NextGroupChildSelection(std::vector<NodeProperties*> groups_by_priority);
bool DoneWithGroupChildSelections(std::vector<NodeProperties*> groups_by_priority);
// The caller must keep "this" alive. We require this of the caller since the caller is fairly
// likely to want to keep "this" alive longer than MaybeAllocate() could anyway.
void MaybeAllocate();
fpromise::result<fuchsia_sysmem2::BufferCollectionInfo, fuchsia_sysmem2::Error> TryAllocate(
std::vector<NodeProperties*> nodes);
std::optional<fuchsia_sysmem2::Error> TryLateLogicalAllocation(
std::vector<NodeProperties*> nodes);
zx::result<bool> CompareBufferCollectionInfo(fuchsia_sysmem2::BufferCollectionInfo& lhs,
fuchsia_sysmem2::BufferCollectionInfo& rhs);
void InitializeConstraintSnapshots(const ConstraintsList& constraints_list);
void SetFailedAllocationResult(fuchsia_sysmem2::Error error);
void SetAllocationResult(std::vector<NodeProperties*> visible_pruned_sub_tree,
fuchsia_sysmem2::BufferCollectionInfo info,
std::vector<NodeProperties*> whole_pruned_sub_tree);
void SendAllocationResult(std::vector<NodeProperties*> nodes);
void SetFailedLateLogicalAllocationResult(NodeProperties* tree,
fuchsia_sysmem2::Error status_param);
void SetSucceededLateLogicalAllocationResult(std::vector<NodeProperties*> visible_pruned_sub_tree,
std::vector<NodeProperties*> whole_pruned_sub_tree);
void BindSharedCollectionInternal(BufferCollectionToken* token,
CollectionServerEnd buffer_collection_request);
fpromise::result<fuchsia_sysmem2::BufferCollectionConstraints, void> CombineConstraints(
ConstraintsList* constraints_list);
bool CheckSanitizeBufferCollectionConstraints(
CheckSanitizeStage stage, fuchsia_sysmem2::BufferCollectionConstraints& constraints);
bool CheckSanitizeBufferUsage(CheckSanitizeStage stage,
fuchsia_sysmem2::BufferUsage& buffer_usage);
bool CheckSanitizeHeap(CheckSanitizeStage stage, fuchsia_sysmem2::Heap& heap);
bool CheckSanitizeBufferMemoryConstraints(CheckSanitizeStage stage,
const fuchsia_sysmem2::BufferUsage& buffer_usage,
fuchsia_sysmem2::BufferMemoryConstraints& constraints);
bool CheckSanitizeImageFormatConstraints(CheckSanitizeStage stage,
const fuchsia_sysmem2::BufferUsage& buffer_usage,
fuchsia_sysmem2::ImageFormatConstraints& constraints);
bool AccumulateConstraintBufferCollection(fuchsia_sysmem2::BufferCollectionConstraints* acc,
fuchsia_sysmem2::BufferCollectionConstraints c);
bool AccumulateConstraintsBufferUsage(fuchsia_sysmem2::BufferUsage* acc,
fuchsia_sysmem2::BufferUsage c);
bool AccumulateConstraintPermittedHeaps(std::vector<fuchsia_sysmem2::Heap>* acc,
std::vector<fuchsia_sysmem2::Heap> c);
bool AccumulateConstraintBufferMemory(fuchsia_sysmem2::BufferMemoryConstraints* acc,
fuchsia_sysmem2::BufferMemoryConstraints c);
bool AccumulateConstraintImageFormats(std::vector<fuchsia_sysmem2::ImageFormatConstraints>* acc,
std::vector<fuchsia_sysmem2::ImageFormatConstraints> c);
bool AccumulateConstraintImageFormat(fuchsia_sysmem2::ImageFormatConstraints* acc,
fuchsia_sysmem2::ImageFormatConstraints c);
bool AccumulateConstraintColorSpaces(std::vector<fuchsia_images2::ColorSpace>* acc,
std::vector<fuchsia_images2::ColorSpace> c);
size_t InitialCapacityOrZero(CheckSanitizeStage stage, size_t initial_capacity);
bool IsColorSpaceEqual(const fuchsia_images2::ColorSpace& a,
const fuchsia_images2::ColorSpace& b);
fpromise::result<fuchsia_sysmem2::BufferCollectionInfo, zx_status_t>
GenerateUnpopulatedBufferCollectionInfo(
const fuchsia_sysmem2::BufferCollectionConstraints& constraints);
fpromise::result<fuchsia_sysmem2::BufferCollectionInfo, fuchsia_sysmem2::Error> Allocate(
const fuchsia_sysmem2::BufferCollectionConstraints& constraints,
fuchsia_sysmem2::BufferCollectionInfo* buffer_collection_info);
fpromise::result<zx::vmo> AllocateVmo(MemoryAllocator* allocator,
const fuchsia_sysmem2::SingleBufferSettings& settings,
uint32_t index);
int32_t CompareImageFormatConstraintsTieBreaker(const fuchsia_sysmem2::ImageFormatConstraints& a,
const fuchsia_sysmem2::ImageFormatConstraints& b);
int32_t CompareImageFormatConstraintsByIndex(
const fuchsia_sysmem2::BufferCollectionConstraints& constraints, uint32_t index_a,
uint32_t index_b);
void CreationTimedOut(async_dispatcher_t* dispatcher, async::TaskBase* task, zx_status_t status);
AllocationResult allocation_result();
void LogBufferCollectionInfoDiffs(const fuchsia_sysmem2::BufferCollectionInfo& o,
const fuchsia_sysmem2::BufferCollectionInfo& n);
// To be called only by CombineConstraints().
bool IsMinBufferSizeSpecifiedByAnyParticipant(const ConstraintsList& constraints_list);
// This is the root and any sub-trees created via SetDispensable() or AttachToken().
std::vector<NodeProperties*> FailureDomainSubtrees();
// A Node is a pruned sub-tree eligible for logical allocation if it is not yet logically
// allocated, and does not have a non-logically-allocated direct parent.
//
// Such sub-trees will always have a root-most Node that is either the root or an
// ErrorPropagationMode kDoNotPropagate Node.
//
// The returned pruned sub-trees may not be ready for logical allocation (may not have all its
// constraints yet), nor do they necessarily have constraints that are satisfiable.
std::vector<NodeProperties*> PrunedSubtreesEligibleForLogicalAllocation();
// The granularity of logical allocation is a sub-tree pruned of any ErrorPropagationMode
// kDoNotPropagate Node(s) other than the sub-tree's root-most node.
//
// The root-most Node of a pruned subtree can be the root, or it can be a Node that has
// ErrorPropagationMode kDoNotPropagate (implying that AttachToken() was used to create the
// non-root Node).
//
// The pruning is the same for both the root and a non-root Node so that the locally-observable
// (by a participant) logical allocation granularity is the same regardless of whether the logical
// allocation granule has the root as its root-most Node, or has an ErrorPropagationMode
// kDoNotPropagate Node as its root-most Node.
std::vector<NodeProperties*> NodesOfPrunedSubtreeEligibleForLogicalAllocation(
NodeProperties& subtree);
std::vector<NodeProperties*> PrioritizedGroupsOfPrunedSubtreeEligibleForLogicalAllocation(
NodeProperties& subtree);
// For NodeProperties:
void AddCountsForNode(const Node& node);
void RemoveCountsForNode(const Node& node);
void AdjustRelevantNodeCounts(const Node& node, fit::function<void(uint32_t& count)> visitor);
void DeleteRoot();
// Diff printing.
#if defined(PRINT_DIFF)
#error "Let's pick a different name for PRINT_DIFF"
#endif
// If LLCPP had generic field objects, we wouldn't need this macro. We #undef the macro name
// after we're done using it so it doesn't escape this header.
#define PRINT_DIFF(child_field_name) \
do { \
const std::string& parent_field_name = field_name; \
if (o.child_field_name().has_value() != n.child_field_name().has_value()) { \
LogError(FROM_HERE, \
"o%s." #child_field_name \
"().has_value(): %d " \
"n%s." #child_field_name "().has_value(): %d", \
parent_field_name.c_str(), o.child_field_name().has_value(), \
parent_field_name.c_str(), n.child_field_name().has_value()); \
} else if (o.child_field_name().has_value()) { \
std::string field_name = \
fbl::StringPrintf("%s.%s()", parent_field_name.c_str(), #child_field_name).c_str(); \
DiffPrinter<std::remove_const_t<std::remove_reference_t<decltype(*o.child_field_name())>>>:: \
PrintDiff(*this, field_name, *o.child_field_name(), *n.child_field_name()); \
} \
} while (false)
template <typename FieldType, typename Enable = void>
struct DiffPrinter {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const FieldType& o, const FieldType& n);
};
template <typename ElementType>
struct DiffPrinter<std::vector<ElementType>, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const std::vector<ElementType>& o,
const std::vector<ElementType>& n) {
if (o.size() != n.size()) {
buffer_collection.LogError(FROM_HERE, "o%s.size(): %" PRIu64 " n%s.size(): %" PRIu64,
field_name.c_str(), o.size(), field_name.c_str(), n.size());
}
for (uint32_t i = 0; i < std::max(o.size(), n.size()); ++i) {
std::string new_field_name = fbl::StringPrintf("%s[%u]", field_name.c_str(), i).c_str();
const ElementType& blank = ElementType();
const ElementType& o_element = i < o.size() ? o[i] : blank;
const ElementType& n_element = i < n.size() ? n[i] : blank;
DiffPrinter<ElementType>::PrintDiff(buffer_collection, new_field_name, o_element,
n_element);
}
}
};
template <typename TableType>
struct DiffPrinter<TableType, std::enable_if_t<fidl::IsTable<TableType>::value>> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const TableType& o, const TableType& n) {
buffer_collection.LogTableDiffs<TableType>(field_name, o, n);
}
};
template <>
struct DiffPrinter<bool, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const bool& o, const bool& n) {
if (o != n) {
buffer_collection.LogError(FROM_HERE, "o%s: %d n%s: %d", field_name.c_str(), o,
field_name.c_str(), n);
}
}
};
template <>
struct DiffPrinter<uint32_t, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const uint32_t& o, const uint32_t& n) {
if (o != n) {
buffer_collection.LogError(FROM_HERE, "o%s: %u n%s: %u", field_name.c_str(), o,
field_name.c_str(), n);
}
}
};
template <typename EnumType>
struct DiffPrinter<EnumType, std::enable_if_t<sysmem::IsFidlEnum_v<EnumType>>> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const EnumType& o, const EnumType& n) {
using UnderlyingType = sysmem::FidlUnderlyingTypeOrType_t<EnumType>;
const UnderlyingType o_underlying = safe_cast<UnderlyingType>(o);
const UnderlyingType n_underlying = safe_cast<UnderlyingType>(n);
DiffPrinter<UnderlyingType>::PrintDiff(buffer_collection, field_name, o_underlying,
n_underlying);
}
};
template <>
struct DiffPrinter<uint64_t, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const uint64_t& o, const uint64_t& n) {
if (o != n) {
buffer_collection.LogError(FROM_HERE, "o%s: %" PRIu64 " n%s: %" PRIu64, field_name.c_str(),
o, field_name.c_str(), n);
}
}
};
template <>
struct DiffPrinter<zx::vmo, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const zx::vmo& o, const zx::vmo& n) {
// We don't expect to call the zx::vmo variant since !has_vmo(), but if we do get here,
// complain + print the values regardless of what the values are or whether they differ.
buffer_collection.LogError(FROM_HERE,
"Why did we call zx::vmo PrintDiff? --- o%s: %u n%s: %u",
field_name.c_str(), o.get(), field_name.c_str(), n.get());
}
};
template <>
struct DiffPrinter<zx::eventpair, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const zx::eventpair& o,
const zx::eventpair& n) {
// We don't expect to call the zx::eventpair variant since !has_close_weak_asap(), but if we
// do get here, complain + print the values regardless of what the values are or whether they
// differ.
buffer_collection.LogError(FROM_HERE,
"Why did we call zx::eventpair PrintDiff? --- o%s: %u n%s: %u",
field_name.c_str(), o.get(), field_name.c_str(), n.get());
}
};
template <>
struct DiffPrinter<fuchsia_math::SizeU, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const fuchsia_math::SizeU& o,
const fuchsia_math::SizeU& n) {
if (o.width() != n.width() || o.height() != n.height()) {
buffer_collection.LogError(FROM_HERE, "o%s: %u x %u n%s: %u x %u", field_name.c_str(),
o.width(), o.height(), field_name.c_str(), n.width(),
n.height());
}
}
};
template <>
struct DiffPrinter<std::string, void> {
static void PrintDiff(const LogicalBufferCollection& buffer_collection,
const std::string& field_name, const std::string& o,
const std::string& n) {
if (o != n) {
buffer_collection.LogError(FROM_HERE, "o%s: %s n%s: %s", field_name.c_str(), o.c_str(),
field_name.c_str(), n.c_str());
}
}
};
template <typename Table>
void LogTableDiffs(const std::string& field_name, const Table& o, const Table& n) const;
template <>
void LogTableDiffs<fuchsia_sysmem2::BufferMemorySettings>(
const std::string& field_name, const fuchsia_sysmem2::BufferMemorySettings& o,
const fuchsia_sysmem2::BufferMemorySettings& n) const {
PRINT_DIFF(size_bytes);
PRINT_DIFF(is_physically_contiguous);
PRINT_DIFF(is_secure);
PRINT_DIFF(coherency_domain);
PRINT_DIFF(heap);
}
template <>
void LogTableDiffs<fuchsia_sysmem2::ImageFormatConstraints>(
const std::string& field_name, const fuchsia_sysmem2::ImageFormatConstraints& o,
const fuchsia_sysmem2::ImageFormatConstraints& n) const {
PRINT_DIFF(pixel_format);
PRINT_DIFF(pixel_format_modifier);
PRINT_DIFF(color_spaces);
PRINT_DIFF(min_size);
PRINT_DIFF(max_size);
PRINT_DIFF(min_bytes_per_row);
PRINT_DIFF(max_bytes_per_row);
PRINT_DIFF(max_width_times_height);
PRINT_DIFF(size_alignment);
PRINT_DIFF(display_rect_alignment);
PRINT_DIFF(required_min_size);
PRINT_DIFF(required_max_size);
PRINT_DIFF(bytes_per_row_divisor);
PRINT_DIFF(start_offset_divisor);
}
template <>
void LogTableDiffs<fuchsia_sysmem2::SingleBufferSettings>(
const std::string& field_name, const fuchsia_sysmem2::SingleBufferSettings& o,
const fuchsia_sysmem2::SingleBufferSettings& n) const {
PRINT_DIFF(buffer_settings);
PRINT_DIFF(image_format_constraints);
}
template <>
void LogTableDiffs<fuchsia_sysmem2::VmoBuffer>(const std::string& field_name,
const fuchsia_sysmem2::VmoBuffer& o,
const fuchsia_sysmem2::VmoBuffer& n) const {
PRINT_DIFF(vmo);
PRINT_DIFF(vmo_usable_start);
PRINT_DIFF(close_weak_asap);
}
template <>
void LogTableDiffs<fuchsia_sysmem2::BufferCollectionInfo>(
const std::string& field_name, const fuchsia_sysmem2::BufferCollectionInfo& o,
const fuchsia_sysmem2::BufferCollectionInfo& n) const {
PRINT_DIFF(settings);
PRINT_DIFF(buffers);
}
template <>
void LogTableDiffs<fuchsia_sysmem2::Heap>(const std::string& field_name,
const fuchsia_sysmem2::Heap& o,
const fuchsia_sysmem2::Heap& n) const {
PRINT_DIFF(heap_type);
PRINT_DIFF(id);
}
#undef PRINT_DIFF
void LogDiffsBufferCollectionInfo(const fuchsia_sysmem2::BufferCollectionInfo& o,
const fuchsia_sysmem2::BufferCollectionInfo& n) const {
LOG(WARNING, "LogDiffsBufferCollectionInfo()");
LogTableDiffs<fuchsia_sysmem2::BufferCollectionInfo>("", o, n);
}
void LogConstraints(Location location, NodeProperties* node_properties,
const fuchsia_sysmem2::BufferCollectionConstraints& constraints) const;
void LogBufferCollectionInfo(IndentTracker& indent_tracker,
const fuchsia_sysmem2::BufferCollectionInfo& bci) const;
void LogPixelFormatAndModifier(
IndentTracker& indent_tracker, NodeProperties* node_properties,
const fuchsia_sysmem2::PixelFormatAndModifier& pixel_format_and_modifier) const;
void LogImageFormatConstraints(IndentTracker& indent_tracker, NodeProperties* node_properties,
const fuchsia_sysmem2::ImageFormatConstraints& ifc) const;
void LogPrunedSubTree(NodeProperties* subtree) const;
void LogNodeConstraints(std::vector<NodeProperties*> nodes) const;
// subtree must remain alive >= returned filter
fit::function<NodeFilterResult(const NodeProperties&)> PrunedSubtreeFilter(
NodeProperties& subtree, fit::function<bool(const NodeProperties&)> visit_keep) const;
static fbl::RefPtr<LogicalBufferCollection> CommonCreate(
Device* parent_device, const ClientDebugInfo* client_debug_info);
bool CommonCreateBufferCollectionTokenStage1(fbl::RefPtr<LogicalBufferCollection> self,
NodeProperties* new_node_properties,
const TokenServerEnd& token_request,
BufferCollectionToken** out_token);
void HandleTokenFailure(BufferCollectionToken& token, zx_status_t status);
void IncStrongNodeTally();
void DecStrongNodeTally();
void CheckForZeroStrongNodes();
void IncStrongParentVmoCount();
void DecStrongParentVmoCount();
void CheckForZeroStrongParentVmoCount();
void CreateParentVmoInspect(zx_koid_t parent_vmo_koid);
void ClearBuffers();
bool FlattenPixelFormatAndModifiers(const fuchsia_sysmem2::BufferUsage& buffer_usage,
fuchsia_sysmem2::BufferCollectionConstraints& constraints);
Device* parent_device_ = nullptr;
// This owns the current tree of BufferCollectionToken, BufferCollection, OrphanedNode. The
// Location in the tree is determined by creation path. Child Node(s) are children because they
// were created via their parent node. The root node is created by CreateSharedCollection() which
// also creates the LogicalBufferCollection.
//
// We use shared_ptr<> to manage NodeProperties only so that Node can have a std::weak_ptr<> back
// to NodeProperties instead of a raw pointer, since the Node can last longer than NodeProperties,
// despite NodeProperties being the primary non-transient owner of Node. This way we avoid using
// a raw pointer from Node to NodeProperties.
//
// The tree at root_ is the only non-transient ownership of NodeProperties. Transient ownership
// lasts only during a portion of a single dispatch on parent_device_->dispatcher().
std::shared_ptr<NodeProperties> root_;
std::vector<ConstraintInfoSnapshot> constraints_at_allocation_;
bool is_allocate_attempted_ = false;
// Iff true, initial allocation has been attempted and has succeeded or
// failed. Both allocation_result_status_ and allocation_result_info_ are
// not meaningful until has_allocation_result_ is true.
bool has_allocation_result_ = false;
std::optional<fuchsia_sysmem2::BufferCollectionInfo> buffer_collection_info_before_population_;
std::optional<fuchsia_sysmem2::Error> maybe_allocation_error_;
std::optional<fuchsia_sysmem2::BufferCollectionInfo> allocation_result_info_;
MemoryAllocator* memory_allocator_ = nullptr;
std::optional<CollectionName> name_;
// 0 means not dispensable. Non-zero means dispensable, with each value being a group of
// BufferCollectionToken(s) / BufferCollection(s) that were all created from a single
// BufferCollectionToken that was created with AttachToken() or which had SetDispensable() called
// on it. Each group's constraints are aggregated together and succeed or fail to logically
// allocate as a group, considered in order by when each group's constraints are ready, not in
// order by dispensable_ordinal values.
uint64_t next_dispensable_ordinal_ = 1;
// Information about the current client - only valid while aggregating state for a particular
// client.
const NodeProperties* current_node_properties_ = nullptr;
inspect::Node inspect_node_;
inspect::StringProperty name_property_;
inspect::UintProperty vmo_count_property_;
inspect::ValueList vmo_properties_;
// This does not actually need to be a koid, but for now we do get the value from a koid, so we
// want the initial value at the start of the constructor to be the invalid koid value, until we
// set this to a real koid value that's unique to "this".
uint64_t buffer_collection_id_ = ZX_KOID_INVALID;
// From buffers_remaining to server_end.
std::multimap<uint32_t, zx::eventpair> lifetime_tracking_;
// The key is buffer_index. In the success case, removing a single item from this map is performed
// by TrackedParentVmo::do_delete. Because do_delete also runs when we want to just clear buffers_
// in an error path, when we clear buffers_ we move it out, clear it, then delete the moved-out
// items. This way we don't try to mutate buffers_ during buffers_.clear().
std::unordered_map<uint32_t, std::unique_ptr<LogicalBuffer>> buffers_;
// It's nice for members containing timers to be last for destruction order purposes, but the
// destructor also explicitly cancels timers to avoid any brittle-ness from members potentially
// added below creation_timer_ (so this doesn't actually need to be last).
async::TaskMethod<LogicalBufferCollection, &LogicalBufferCollection::CreationTimedOut>
creation_timer_{this};
bool is_verbose_logging_ = false;
// Only tracked while Node::is_currently_connected() true, to allow for Node sub-classes that may
// stick around for a short duration after their channel is closed, depending on FIDL C++
// generated code requirements on how close / disconnect works.
std::unordered_map<zx_koid_t, NodeProperties*> node_properties_by_node_ref_keep_koid_;
bool done_with_group_child_selection_ = false;
// This can become true before initial allocation if waiting on secure allocators at least once
// is/was necessary. Once this becomes true it stays true, even after this collection is no longer
// currently waiting on secure allocators.
//
// This field helps avoid creation of extra child inspect nodes when the wait is over.
bool waiting_for_secure_allocators_ready_ = false;
uint32_t strong_node_count_ = 0;
uint32_t strong_parent_vmo_count_ = 0;
const zx::time create_time_monotonic_ = zx::time::infinite_past();
};
} // namespace sysmem_driver
#endif // SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_