| // Copyright 2021 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 "node_properties.h" |
| |
| #include <fidl/fuchsia.sysmem2/cpp/wire.h> |
| #include <lib/zx/event.h> |
| #include <lib/zx/eventpair.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <vector> |
| |
| #include "koid_util.h" |
| #include "logical_buffer_collection.h" |
| #include "node.h" |
| #include "utils.h" |
| |
| namespace sysmem_driver { |
| |
| NodeProperties::~NodeProperties() { |
| ZX_DEBUG_ASSERT(child_count() == 0); |
| if (node_) { |
| logical_buffer_collection_->RemoveCountsForNode(*node_); |
| node_->EnsureDetachedFromNodeProperties(); |
| } |
| if (!is_weak_) { |
| logical_buffer_collection_->DecStrongNodeTally(); |
| } |
| logical_buffer_collection_->UntrackNodeProperties(this); |
| } |
| |
| // static |
| std::unique_ptr<NodeProperties> NodeProperties::NewRoot( |
| LogicalBufferCollection* logical_buffer_collection, const ClientDebugInfo* client_debug_info) { |
| auto result = std::unique_ptr<NodeProperties>(new NodeProperties(logical_buffer_collection)); |
| ZX_DEBUG_ASSERT(!result->parent_); |
| // Set later with SetNode(). |
| ZX_DEBUG_ASSERT(!result->node_); |
| ZX_DEBUG_ASSERT(result->children_.empty()); |
| result->logical_buffer_collection_->IncStrongNodeTally(); |
| if (client_debug_info) { |
| auto& new_debug_info = result->client_debug_info(); |
| new_debug_info.name = client_debug_info->name; |
| new_debug_info.id = client_debug_info->id; |
| } |
| return result; |
| } |
| |
| NodeProperties* NodeProperties::NewChild(LogicalBufferCollection* logical_buffer_collection) { |
| auto result = std::unique_ptr<NodeProperties>(new NodeProperties(logical_buffer_collection)); |
| auto result_ptr = result.get(); |
| // The parent Node owns the child Node. |
| this->children_.emplace_back(std::move(result)); |
| result_ptr->parent_ = this; |
| // Set later with SetNode(). |
| ZX_DEBUG_ASSERT(!result_ptr->node_); |
| ZX_DEBUG_ASSERT(result_ptr->children_.empty()); |
| // Default to parent's debug info until/unless overriden later (by the client, or by later code |
| // that always runs regardless of client behavior). |
| // |
| // struct copy |
| result_ptr->client_debug_info_ = client_debug_info_; |
| // Soon we'll do another &= on this mask with the rights_attenuation_mask supplied by the client |
| // when creating the child, but the child starts with the same rights masked away as the parent. |
| result_ptr->rights_attenuation_mask_ = rights_attenuation_mask_; |
| ZX_DEBUG_ASSERT(result_ptr->error_propagation_mode_ == ErrorPropagationMode::kPropagate); |
| |
| // The is_weak must propagate when the child is created; later changes to parent is_weak_ don't |
| // change the child's is_weak_, intentionally. |
| result_ptr->is_weak_ = is_weak_; |
| if (!is_weak_) { |
| result_ptr->logical_buffer_collection_->IncStrongNodeTally(); |
| } |
| |
| // is_weak_ok_ doesn't propagate to children by default |
| if (is_weak_ok_for_child_nodes_also_) { |
| ZX_DEBUG_ASSERT(is_weak_ok_); |
| // propagation to all descendents of the child as well |
| result_ptr->is_weak_ok_for_child_nodes_also_ = true; |
| result_ptr->is_weak_ok_ = is_weak_ok_; |
| result_ptr->is_weak_ok_from_parent_ = true; |
| } |
| |
| return result_ptr; |
| } |
| |
| // static |
| std::unique_ptr<NodeProperties> NodeProperties::NewTemporary( |
| LogicalBufferCollection* logical_buffer_collection, |
| fuchsia_sysmem2::BufferCollectionConstraints buffer_collection_constraints, |
| std::string debug_name) { |
| auto result = std::unique_ptr<NodeProperties>(new NodeProperties(logical_buffer_collection)); |
| ZX_DEBUG_ASSERT(!result->parent_); |
| // Since temporary, won't ever have a node_. |
| ZX_DEBUG_ASSERT(!result->node_); |
| ZX_DEBUG_ASSERT(result->children_.empty()); |
| result->SetBufferCollectionConstraints(std::move(buffer_collection_constraints)); |
| result->client_debug_info().name = debug_name; |
| // just to avoid subtracting strong node tally in destructor |
| result->is_weak_ = true; |
| return result; |
| } |
| |
| void NodeProperties::RemoveFromTreeAndDelete() { |
| ZX_DEBUG_ASSERT(child_count() == 0); |
| // This also deletes "this". |
| if (parent_) { |
| // As we keep a shared_ptr of `this` in the scope, `parent_` is guaranteed |
| // to be valid till the end of the block. |
| // |
| // It could be tempting to detach `this` from `parent_` here. However, |
| // `parent_` of the NodeProperties must not be changed until it's destroyed |
| // because it is used to propagate the node counters on the tree on |
| // NodeProperties destructor. |
| std::shared_ptr<NodeProperties> shared_this = this->shared_from_this(); |
| Children::iterator iter; |
| // typically called to remove last child, so search from end of vector |
| for (iter = parent_->children_.end() - 1;; --iter) { |
| if (*iter == shared_this) { |
| break; |
| } |
| ZX_DEBUG_ASSERT(iter != parent_->children_.begin()); |
| } |
| parent_->children_.erase(iter); |
| } else { |
| logical_buffer_collection_->DeleteRoot(); |
| } |
| // "this" is now gone |
| } |
| |
| std::vector<NodeProperties*> NodeProperties::BreadthFirstOrder( |
| fit::function<NodeFilterResult(const NodeProperties&)> node_filter) { |
| std::vector<NodeProperties*> iterate_children_tmp; |
| std::vector<NodeProperties*> result; |
| NodeFilterResult this_result; |
| if (node_filter) { |
| this_result = node_filter(*this); |
| } |
| if (this_result.keep_node) { |
| result.push_back(this); |
| } |
| if (this_result.iterate_children) { |
| iterate_children_tmp.push_back(this); |
| } |
| for (uint32_t i = 0; i < iterate_children_tmp.size(); ++i) { |
| NodeProperties* node = iterate_children_tmp[i]; |
| for (auto& child_shared : node->children_) { |
| NodeFilterResult child_result; |
| if (node_filter) { |
| child_result = node_filter(*child_shared); |
| } |
| if (child_result.keep_node) { |
| result.push_back(child_shared.get()); |
| } |
| if (child_result.iterate_children) { |
| iterate_children_tmp.push_back(child_shared.get()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| std::vector<NodeProperties*> NodeProperties::DepthFirstPreOrder( |
| fit::function<NodeFilterResult(const NodeProperties&)> node_filter) { |
| std::vector<NodeProperties*> result; |
| struct StackLevel { |
| NodeProperties* node_properties = nullptr; |
| uint32_t next_child = 0; |
| NodeFilterResult filter_result = {}; |
| }; |
| // This vector used as a stack will be on the heap, so avoids stack overflow of the thread stack. |
| std::vector<StackLevel> stack; |
| stack.emplace_back( |
| StackLevel{.node_properties = this, .next_child = 0, .filter_result = node_filter(*this)}); |
| while (!stack.empty()) { |
| auto& cur = stack.back(); |
| if (cur.next_child == 0 && cur.filter_result.keep_node) { |
| result.emplace_back(cur.node_properties); |
| } |
| if (!cur.filter_result.iterate_children) { |
| stack.pop_back(); |
| continue; |
| } |
| if (cur.next_child == cur.node_properties->child_count()) { |
| stack.pop_back(); |
| continue; |
| } |
| auto* child = cur.node_properties->children_[cur.next_child].get(); |
| ++cur.next_child; |
| stack.push_back(StackLevel{ |
| .node_properties = child, .next_child = 0, .filter_result = node_filter(*child)}); |
| } |
| return result; |
| } |
| |
| NodeProperties* NodeProperties::parent() const { |
| // Can be nullptr if this is the root. |
| return parent_; |
| } |
| |
| Node* NodeProperties::node() const { |
| // Can be nullptr if no Node is owned yet. |
| return node_.get(); |
| } |
| |
| uint32_t NodeProperties::child_count() const { return safe_cast<uint32_t>(children_.size()); } |
| |
| NodeProperties& NodeProperties::child(uint32_t which) const { return *children_[which]; } |
| |
| ClientDebugInfo& NodeProperties::client_debug_info() { return client_debug_info_; } |
| |
| const ClientDebugInfo& NodeProperties::client_debug_info() const { return client_debug_info_; } |
| |
| uint32_t& NodeProperties::rights_attenuation_mask() { return rights_attenuation_mask_; } |
| |
| ErrorPropagationMode& NodeProperties::error_propagation_mode() { return error_propagation_mode_; } |
| |
| const ErrorPropagationMode& NodeProperties::error_propagation_mode() const { |
| return error_propagation_mode_; |
| } |
| |
| bool NodeProperties::buffers_logically_allocated() const { return buffers_logically_allocated_; } |
| |
| void NodeProperties::SetBuffersLogicallyAllocated() { |
| ZX_DEBUG_ASSERT(!buffers_logically_allocated_); |
| buffers_logically_allocated_ = true; |
| } |
| |
| bool NodeProperties::has_constraints() const { return buffer_collection_constraints_.has_value(); } |
| |
| const fuchsia_sysmem2::BufferCollectionConstraints* NodeProperties::buffer_collection_constraints() |
| const { |
| if (!buffer_collection_constraints_.has_value()) { |
| return nullptr; |
| } |
| return &*buffer_collection_constraints_; |
| } |
| |
| void NodeProperties::SetBufferCollectionConstraints( |
| fuchsia_sysmem2::BufferCollectionConstraints buffer_collection_constraints) { |
| ZX_DEBUG_ASSERT(!buffer_collection_constraints_.has_value()); |
| buffer_collection_constraints_.emplace(std::move(buffer_collection_constraints)); |
| } |
| |
| void NodeProperties::SetNode(fbl::RefPtr<Node> node) { |
| // Once a Node is owned, it's ok to switch to a different Node, but not ok to set back to nullptr. |
| ZX_DEBUG_ASSERT(node); |
| logical_buffer_collection_->AddCountsForNode(*node); |
| if (node_) { |
| logical_buffer_collection_->RemoveCountsForNode(*node_); |
| node_->EnsureDetachedFromNodeProperties(); |
| } |
| |
| // Remember the logical node type. This intentionally does not check for OrphanedNode because |
| // OrphanedNode does not correspond to a logical node type. The logical node type persists after |
| // potential replacement of the (non-OrphanedNode) Node by an OrphanedNode. |
| if (!!node->buffer_collection_token()) { |
| logical_node_type_ = NodeType::kToken; |
| } else if (!!node->buffer_collection()) { |
| logical_node_type_ = NodeType::kCollection; |
| } else if (!!node->buffer_collection_token_group()) { |
| logical_node_type_ = NodeType::kGroup; |
| } |
| |
| node_ = std::move(node); |
| } |
| |
| void NodeProperties::SetWhichChild(uint32_t which_child) { |
| ZX_DEBUG_ASSERT(which_child < child_count()); |
| which_child_ = {which_child}; |
| } |
| |
| void NodeProperties::ResetWhichChild() { which_child_ = {std::nullopt}; } |
| |
| std::optional<uint32_t> NodeProperties::which_child() const { return which_child_; } |
| |
| bool NodeProperties::visible() const { |
| // We could stop at the root of the pruned sub-tree of the current logical allocation, but for now |
| // we just iterate to the root (in visibile true case). |
| for (const auto* iter = this; iter; iter = iter->parent()) { |
| if (!iter->parent()) { |
| return true; |
| } |
| auto* parent = iter->parent(); |
| if (!parent->which_child().has_value()) { |
| // If which_child() isn't set, then that means "all", which can't be hiding "this". |
| continue; |
| } |
| ZX_DEBUG_ASSERT(*parent->which_child() < parent->child_count()); |
| if (parent->children_[*parent->which_child()].get() != iter) { |
| return false; |
| } |
| } |
| ZX_PANIC("impossible"); |
| return true; |
| } |
| |
| void NodeProperties::SetWeak() { |
| if (is_weak_) { |
| return; |
| } |
| logical_buffer_collection_->DecStrongNodeTally(); |
| is_weak_ = true; |
| } |
| |
| void NodeProperties::SetWeakOk(bool for_child_nodes_also) { |
| is_weak_ok_ = true; |
| if (for_child_nodes_also) { |
| is_weak_ok_for_child_nodes_also_ = true; |
| } |
| } |
| |
| NodeProperties::NodeProperties(LogicalBufferCollection* logical_buffer_collection) |
| : logical_buffer_collection_(logical_buffer_collection) { |
| zx_status_t status = zx::event::create(0, &node_ref_); |
| if (status != ZX_OK) { |
| // Sysmem treats this much like a code page-in that fails due to out of memory. Both will only |
| // happen if we're so low on memory that we've already committed to OOMing (or at least, that's |
| // the stated intent IIUC). |
| ZX_PANIC("zx::eventpair::create() failed - status: %d", status); |
| } |
| zx_koid_t not_used; |
| status = get_handle_koids(node_ref_, &node_ref_koid_, ¬_used, ZX_OBJ_TYPE_EVENT); |
| if (status != ZX_OK) { |
| ZX_PANIC("get_handle_koids(node_ref_) failed - status: %d", status); |
| } |
| ZX_DEBUG_ASSERT(logical_buffer_collection_); |
| logical_buffer_collection_->TrackNodeProperties(this); |
| } |
| |
| uint32_t NodeProperties::node_count() const { return node_count_; } |
| |
| uint32_t NodeProperties::connected_client_count() const { return connected_client_count_; } |
| |
| uint32_t NodeProperties::buffer_collection_count() const { return buffer_collection_count_; } |
| |
| uint32_t NodeProperties::buffer_collection_token_count() const { |
| return buffer_collection_token_count_; |
| } |
| |
| void NodeProperties::LogInfo(Location location, const char* format, ...) const { |
| va_list args; |
| va_start(args, format); |
| logical_buffer_collection_->VLogClientInfo(location, this, format, args); |
| va_end(args); |
| } |
| |
| void NodeProperties::LogError(Location location, const char* format, ...) const { |
| va_list args; |
| va_start(args, format); |
| logical_buffer_collection_->VLogClientError(location, this, format, args); |
| va_end(args); |
| } |
| |
| void NodeProperties::LogConstraints(Location location) { |
| if (!has_constraints()) { |
| LogInfo(FROM_HERE, "No constraints yet."); |
| return; |
| } |
| logical_buffer_collection_->LogConstraints(location, this, *buffer_collection_constraints()); |
| } |
| |
| const char* NodeProperties::node_type_name() const { return node()->node_type_string(); } |
| |
| ConnectionVersion NodeProperties::connection_version() const { |
| return node()->connection_version(); |
| } |
| |
| } // namespace sysmem_driver |