blob: a6c54f4d64fa73fd0d31d8785b511c5144e0f501 [file] [log] [blame]
// 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_, &not_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