| // 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.sysmem/cpp/wire.h> |
| #include <fidl/fuchsia.sysmem2/cpp/wire.h> |
| #include <inttypes.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fidl-async-2/fidl_struct.h> |
| #include <lib/fidl/llcpp/arena.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 "allocation_result.h" |
| #include "device.h" |
| #include "logging.h" |
| #include "node.h" |
| #include "node_properties.h" |
| #include "table_set.h" |
| |
| namespace sysmem_driver { |
| |
| class BufferCollectionToken; |
| class BufferCollection; |
| class MemoryAllocator; |
| |
| // 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; |
| inspect::ValueList node_constraints; |
| }; |
| |
| // 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 Create(zx::channel buffer_collection_token_request, Device* parent_device); |
| |
| // |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, |
| zx::channel 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 CreateBufferCollectionToken( |
| fbl::RefPtr<LogicalBufferCollection> self, NodeProperties* new_node_properties, |
| fidl::ServerEnd<fuchsia_sysmem::BufferCollectionToken> token_request); |
| |
| void AttachLifetimeTracking(zx::eventpair server_end, uint32_t buffers_remaining); |
| void SweepLifetimeTracking(); |
| |
| void OnSetConstraints(); |
| |
| void SetName(uint32_t priority, std::string name); |
| void SetDebugTimeoutLogDeadline(int64_t deadline); |
| |
| 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; |
| |
| TableSet& table_set() { return table_set_; } |
| |
| std::optional<std::string> name() const { |
| return name_ ? std::make_optional(name_->name) : std::optional<std::string>(); |
| } |
| |
| inspect::Node& inspect_node() { return inspect_node_; } |
| |
| private: |
| friend class NodeProperties; |
| |
| enum class CheckSanitizeStage { kInitial, kNotAggregated, kAggregated }; |
| |
| class Constraints { |
| public: |
| Constraints(const Constraints&) = delete; |
| Constraints(Constraints&&) = default; |
| Constraints(TableSet& table_set, |
| fuchsia_sysmem2::wire::BufferCollectionConstraints&& constraints, |
| const NodeProperties& node_properties) |
| : buffer_collection_constraints_(table_set, std::move(constraints)), |
| node_properties_(node_properties) {} |
| |
| const fuchsia_sysmem2::wire::BufferCollectionConstraints& constraints() const { |
| return *buffer_collection_constraints_; |
| } |
| fuchsia_sysmem2::wire::BufferCollectionConstraints& mutate_constraints() { |
| return buffer_collection_constraints_.mutate(); |
| } |
| |
| const ClientDebugInfo& client_debug_info() const { |
| return node_properties_.client_debug_info(); |
| } |
| const NodeProperties& node_properties() const { return node_properties_; } |
| |
| private: |
| TableHolder<fuchsia_sysmem2::wire::BufferCollectionConstraints> buffer_collection_constraints_; |
| const NodeProperties& node_properties_; |
| }; |
| |
| using ConstraintsList = std::list<Constraints>; |
| |
| struct CollectionName { |
| uint32_t priority{}; |
| std::string name; |
| }; |
| |
| LogicalBufferCollection(Device* parent_device); |
| |
| // Will log an error, and then FailRoot(). |
| void LogAndFailRootNode(Location location, zx_status_t epitaph, 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(zx_status_t epitaph); |
| |
| 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 Node(s) that still |
| // have channels open will send epitaph just before the Node('s) channel closes. 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, zx_status_t epitaph, |
| const char* format, ...) __PRINTFLIKE(5, 6); |
| void FailDownFrom(NodeProperties* tree_to_fail, zx_status_t epitaph); |
| |
| // 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, zx_status_t epitaph, |
| const char* format, ...) __PRINTFLIKE(5, 6); |
| void FailNode(NodeProperties* member_node, zx_status_t epitaph); |
| |
| static void LogInfo(Location location, const char* format, ...); |
| 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; |
| |
| // 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(); |
| |
| void TryAllocate(std::vector<NodeProperties*> nodes); |
| |
| void TryLateLogicalAllocation(std::vector<NodeProperties*> nodes); |
| |
| void InitializeConstraintSnapshots(const ConstraintsList& constraints_list); |
| |
| void SetFailedAllocationResult(zx_status_t status); |
| |
| void SetAllocationResult(std::vector<NodeProperties*> nodes, |
| fuchsia_sysmem2::wire::BufferCollectionInfo&& info); |
| |
| void SendAllocationResult(std::vector<NodeProperties*> nodes); |
| |
| void SetFailedLateLogicalAllocationResult(NodeProperties* tree, zx_status_t status_param); |
| |
| void SetSucceededLateLogicalAllocationResult(std::vector<NodeProperties*> nodes); |
| |
| void BindSharedCollectionInternal(BufferCollectionToken* token, |
| zx::channel buffer_collection_request); |
| |
| fpromise::result<fuchsia_sysmem2::wire::BufferCollectionConstraints, void> CombineConstraints( |
| ConstraintsList* constraints_list); |
| |
| bool CheckSanitizeBufferCollectionConstraints( |
| CheckSanitizeStage stage, fuchsia_sysmem2::wire::BufferCollectionConstraints& constraints); |
| |
| bool CheckSanitizeBufferUsage(CheckSanitizeStage stage, |
| fuchsia_sysmem2::wire::BufferUsage& buffer_usage); |
| |
| bool CheckSanitizeBufferMemoryConstraints( |
| CheckSanitizeStage stage, const fuchsia_sysmem2::wire::BufferUsage& buffer_usage, |
| fuchsia_sysmem2::wire::BufferMemoryConstraints& constraints); |
| |
| bool CheckSanitizeImageFormatConstraints( |
| CheckSanitizeStage stage, fuchsia_sysmem2::wire::ImageFormatConstraints& constraints); |
| |
| bool AccumulateConstraintBufferCollection(fuchsia_sysmem2::wire::BufferCollectionConstraints* acc, |
| fuchsia_sysmem2::wire::BufferCollectionConstraints c); |
| |
| bool AccumulateConstraintsBufferUsage(fuchsia_sysmem2::wire::BufferUsage* acc, |
| fuchsia_sysmem2::wire::BufferUsage c); |
| |
| bool AccumulateConstraintHeapPermitted(fidl::VectorView<fuchsia_sysmem2::wire::HeapType>* acc, |
| fidl::VectorView<fuchsia_sysmem2::wire::HeapType> c); |
| |
| bool AccumulateConstraintBufferMemory(fuchsia_sysmem2::wire::BufferMemoryConstraints* acc, |
| fuchsia_sysmem2::wire::BufferMemoryConstraints c); |
| |
| bool AccumulateConstraintImageFormats( |
| fidl::VectorView<fuchsia_sysmem2::wire::ImageFormatConstraints>* acc, |
| fidl::VectorView<fuchsia_sysmem2::wire::ImageFormatConstraints> c); |
| |
| bool AccumulateConstraintImageFormat(fuchsia_sysmem2::wire::ImageFormatConstraints* acc, |
| fuchsia_sysmem2::wire::ImageFormatConstraints c); |
| |
| bool AccumulateConstraintColorSpaces(fidl::VectorView<fuchsia_sysmem2::wire::ColorSpace>* acc, |
| fidl::VectorView<fuchsia_sysmem2::wire::ColorSpace> c); |
| |
| size_t InitialCapacityOrZero(CheckSanitizeStage stage, size_t initial_capacity); |
| |
| bool IsColorSpaceEqual(const fuchsia_sysmem2::wire::ColorSpace& a, |
| const fuchsia_sysmem2::wire::ColorSpace& b); |
| |
| fpromise::result<fuchsia_sysmem2::wire::BufferCollectionInfo, zx_status_t> |
| GenerateUnpopulatedBufferCollectionInfo( |
| const fuchsia_sysmem2::wire::BufferCollectionConstraints& constraints); |
| |
| fpromise::result<fuchsia_sysmem2::wire::BufferCollectionInfo, zx_status_t> Allocate( |
| const fuchsia_sysmem2::wire::BufferCollectionConstraints& constraints, |
| fuchsia_sysmem2::wire::BufferCollectionInfo* buffer_collection_info); |
| |
| fpromise::result<zx::vmo> AllocateVmo(MemoryAllocator* allocator, |
| const fuchsia_sysmem2::wire::SingleBufferSettings& settings, |
| uint32_t index); |
| |
| int32_t CompareImageFormatConstraintsTieBreaker( |
| const fuchsia_sysmem2::wire::ImageFormatConstraints& a, |
| const fuchsia_sysmem2::wire::ImageFormatConstraints& b); |
| |
| int32_t CompareImageFormatConstraintsByIndex( |
| const fuchsia_sysmem2::wire::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::wire::BufferCollectionInfo& o, |
| const fuchsia_sysmem2::wire::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); |
| |
| void LogTreeForDebugOnly(NodeProperties* node); |
| |
| // 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.has_##child_field_name() != n.has_##child_field_name()) { \ |
| LogError(FROM_HERE, \ |
| "o%s.has_" #child_field_name "(): %d n%s.has_" #child_field_name "(): %d", \ |
| parent_field_name.c_str(), o.has_##child_field_name(), parent_field_name.c_str(), \ |
| n.has_##child_field_name()); \ |
| } else if (o.has_##child_field_name()) { \ |
| 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<fidl::VectorView<ElementType>, |
| std::enable_if_t<fidl::IsVectorView<fidl::VectorView<ElementType>>::value>> { |
| static void PrintDiff(const LogicalBufferCollection& buffer_collection, |
| const std::string& field_name, const fidl::VectorView<ElementType>& o, |
| const fidl::VectorView<ElementType>& n) { |
| if (o.count() != n.count()) { |
| buffer_collection.LogError(FROM_HERE, "o%s.count(): %" PRIu64 " n%s.count(): %" PRIu64, |
| field_name.c_str(), o.count(), field_name.c_str(), n.count()); |
| } |
| for (uint32_t i = 0; i < std::max(o.count(), n.count()); ++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.count() ? o[i] : blank; |
| const ElementType& n_element = i < n.count() ? 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<std::is_enum_v<EnumType>>> { |
| static void PrintDiff(const LogicalBufferCollection& buffer_collection, |
| const std::string& field_name, const EnumType& o, const EnumType& n) { |
| using UnderlyingType = std::underlying_type_t<EnumType>; |
| const UnderlyingType o_underlying = static_cast<UnderlyingType>(o); |
| const UnderlyingType n_underlying = static_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() and !has_aux_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 <typename Table> |
| void LogTableDiffs(const std::string& field_name, const Table& o, const Table& n) const; |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::BufferMemorySettings>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::BufferMemorySettings& o, |
| const fuchsia_sysmem2::wire::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::wire::PixelFormat>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::PixelFormat& o, |
| const fuchsia_sysmem2::wire::PixelFormat& n) const { |
| PRINT_DIFF(type); |
| PRINT_DIFF(format_modifier_value); |
| } |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::ColorSpace>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::ColorSpace& o, |
| const fuchsia_sysmem2::wire::ColorSpace& n) const { |
| PRINT_DIFF(type); |
| } |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::ImageFormatConstraints>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::ImageFormatConstraints& o, |
| const fuchsia_sysmem2::wire::ImageFormatConstraints& n) const { |
| PRINT_DIFF(pixel_format); |
| PRINT_DIFF(color_spaces); |
| PRINT_DIFF(min_coded_width); |
| PRINT_DIFF(max_coded_width); |
| PRINT_DIFF(min_coded_height); |
| PRINT_DIFF(max_coded_height); |
| PRINT_DIFF(min_bytes_per_row); |
| PRINT_DIFF(max_bytes_per_row); |
| PRINT_DIFF(max_coded_width_times_coded_height); |
| PRINT_DIFF(coded_width_divisor); |
| PRINT_DIFF(coded_height_divisor); |
| PRINT_DIFF(bytes_per_row_divisor); |
| PRINT_DIFF(start_offset_divisor); |
| PRINT_DIFF(display_width_divisor); |
| PRINT_DIFF(display_height_divisor); |
| PRINT_DIFF(required_min_coded_width); |
| PRINT_DIFF(required_max_coded_width); |
| PRINT_DIFF(required_min_coded_height); |
| PRINT_DIFF(required_max_coded_height); |
| PRINT_DIFF(required_min_bytes_per_row); |
| PRINT_DIFF(required_max_bytes_per_row); |
| } |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::SingleBufferSettings>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::SingleBufferSettings& o, |
| const fuchsia_sysmem2::wire::SingleBufferSettings& n) const { |
| PRINT_DIFF(buffer_settings); |
| PRINT_DIFF(image_format_constraints); |
| } |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::VmoBuffer>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::VmoBuffer& o, |
| const fuchsia_sysmem2::wire::VmoBuffer& n) const { |
| PRINT_DIFF(vmo); |
| PRINT_DIFF(vmo_usable_start); |
| PRINT_DIFF(aux_vmo); |
| } |
| |
| template <> |
| void LogTableDiffs<fuchsia_sysmem2::wire::BufferCollectionInfo>( |
| const std::string& field_name, const fuchsia_sysmem2::wire::BufferCollectionInfo& o, |
| const fuchsia_sysmem2::wire::BufferCollectionInfo& n) const { |
| PRINT_DIFF(settings); |
| PRINT_DIFF(buffers); |
| } |
| #undef PRINT_DIFF |
| |
| void LogDiffsBufferCollectionInfo(const fuchsia_sysmem2::wire::BufferCollectionInfo& o, |
| const fuchsia_sysmem2::wire::BufferCollectionInfo& n) const { |
| LOG(WARNING, "LogDiffsBufferCollectionInfo()"); |
| LogTableDiffs<fuchsia_sysmem2::wire::BufferCollectionInfo>("", o, n); |
| } |
| |
| void LogConstraints(Location location, NodeProperties* node_properties, |
| const fuchsia_sysmem2::wire::BufferCollectionConstraints& constraints) const; |
| |
| Device* parent_device_ = nullptr; |
| |
| // We occasionally swap out the allocator for a fresh one, to avoid the possibility of churn |
| // leading to excessive un-used memory allocation in the allocator. This is accomplished via |
| // TableHolder and TableSet. |
| TableSet table_set_; |
| |
| // 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<TableHolder<fuchsia_sysmem2::wire::BufferCollectionInfo>> |
| buffer_collection_info_before_population_; |
| std::optional<fidl::unstable::OwnedEncodedMessage<fuchsia_sysmem2::wire::BufferCollectionInfo>> |
| linearized_buffer_collection_info_before_population_; |
| zx_status_t allocation_result_status_ = ZX_OK; |
| std::optional<TableHolder<fuchsia_sysmem2::wire::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_; |
| |
| // We keep 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 ~ParentVmo, or (B) ZX_VMO_ZERO_CHILDREN occurring async |
| // after StartWait() is called. |
| TrackedParentVmo(fbl::RefPtr<LogicalBufferCollection> buffer_collection, zx::vmo vmo, |
| 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. This should only be used by LogicalBufferCollection |
| zx_status_t CancelWait(); |
| |
| zx::vmo TakeVmo(); |
| [[nodiscard]] const zx::vmo& vmo() const; |
| |
| void set_child_koid(zx_koid_t koid) { child_koid_ = koid; } |
| |
| TrackedParentVmo(const TrackedParentVmo&) = delete; |
| TrackedParentVmo(TrackedParentVmo&&) = delete; |
| TrackedParentVmo& operator=(const 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_; |
| zx_koid_t child_koid_{}; |
| DoDelete do_delete_; |
| async::WaitMethod<TrackedParentVmo, &TrackedParentVmo::OnZeroChildren> zero_children_wait_; |
| // Only for asserts: |
| bool waiting_ = {}; |
| }; |
| |
| // From buffers_remaining to server_end. |
| std::multimap<uint32_t, zx::eventpair> lifetime_tracking_; |
| |
| // 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 after these. |
| using ParentVmoMap = std::map<zx_handle_t, std::unique_ptr<TrackedParentVmo>>; |
| ParentVmoMap parent_vmos_; |
| async::TaskMethod<LogicalBufferCollection, &LogicalBufferCollection::CreationTimedOut> |
| creation_timer_{this}; |
| }; |
| |
| } // namespace sysmem_driver |
| |
| #endif // SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_ |