blob: 828367946ad8e6b7d3cc2109157520811f1341e2 [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.
#ifndef SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_NODE_H_
#define SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_NODE_H_
#include <fidl/fuchsia.sysmem2/cpp/wire.h>
#include <lib/ddk/trace/event.h>
#include <lib/fit/defer.h>
#include <lib/zx/channel.h>
#include <lib/zx/eventpair.h>
#include <stdint.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <unordered_set>
#include <vector>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include "allocation_result.h"
#include "device.h"
#include "koid_util.h"
#include "logical_buffer_collection.h"
#include "versions.h"
namespace sysmem_driver {
class NodeProperties;
class BufferCollection;
class BufferCollectionToken;
class BufferCollectionTokenGroup;
class OrphanedNode;
// Implemented by BufferCollectionToken, BufferCollectionTokenGroup, BufferCollection, and
// OrphanedNode.
//
// Things that can change when transmuting from BufferCollectionToken to BufferCollection, from
// BufferCollectionToken to OrphanedNode, or from BufferCollection to OrphanedNode, should generally
// go in Node. Things that don't change when transmuting go in NodeProperties.
class Node : public fbl::RefCounted<Node> {
public:
Node(const Node& to_copy) = delete;
Node(Node&& to_move) = delete;
Node(fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection,
NodeProperties* node_properties, zx::unowned_channel server_end);
// Construction status.
zx_status_t create_status() const;
virtual ~Node();
void Bind(NodeServerEnd server_end);
void SetErrorHandler(fit::function<void(zx_status_t)> error_handler);
// The Node must have 0 children to call Fail(). The maybe_error is translated to an epitaph for
// sysmem(1), and ignored for sysmem2 (epitaph always ZX_ERR_INTERNAL). In both cases the channel
// is closed.
void Fail(fuchsia_sysmem2::Error error);
// Not all Node(s) that are ReadyForAllocation() have buffer_collection_constraints(). In
// particular an OrphanedNode is always ReadyForAllocation(), but may or may not have
// buffer_collection_constraints().
virtual bool ReadyForAllocation() = 0;
// buffers_logically_allocated() must be false to call this.
virtual void OnBuffersAllocated(const AllocationResult& allocation_result) = 0;
// Consider whether to use the sub-class accessors below, or whether to use
// NodeProperties::is_token(), is_token_group(), is_collection() instead, depending on the
// situation. A client Release() + channel close will replace the sub-class of Node with an
// OrphanedNode, yet the NodeProperties still needs to be treated as a token, collection, or
// group when it comes to the logical node tree and allocation. These accessors are for getting a
// pointer to the current Node sub-class, not for checking the logical node type. These can also
// be used to determine if the current Node sub-class indicates that the client still has a
// connection open to this logical node.
//
// If this Node is a BufferCollectionToken, returns the BufferCollectionToken*, else returns
// nullptr.
virtual BufferCollectionToken* buffer_collection_token() = 0;
virtual const BufferCollectionToken* buffer_collection_token() const = 0;
// If this Node is a BufferCollection, returns the BufferCollection*, else returns nullptr.
virtual BufferCollection* buffer_collection() = 0;
virtual const BufferCollection* buffer_collection() const = 0;
// If this Node is an OrphanedNode, returns the OrphanedNode*, else returns nullptr.
virtual OrphanedNode* orphaned_node() = 0;
virtual const OrphanedNode* orphaned_node() const = 0;
// If this Node is a BufferCollectionTokenGroup, returns the BufferCollectionTokenGroup*, else
// returns nullptr.
virtual BufferCollectionTokenGroup* buffer_collection_token_group() = 0;
virtual const BufferCollectionTokenGroup* buffer_collection_token_group() const = 0;
// This is a constant per sub-class of Node. When a "connected" node is no longer connected, the
// Node sub-class is replaced with OrphanedNode, or deleted as appropriate.
virtual bool is_connected_type() const = 0;
// This is dynamic depending on whether the Node sub-class server-side binding is currently bound
// or in other words whether the node is currently connected. This will always return false
// when !is_connected_type(), and can return true or false if is_connected_type().
virtual bool is_currently_connected() const = 0;
virtual const char* node_type_string() const = 0;
virtual ConnectionVersion connection_version() const = 0;
LogicalBufferCollection& logical_buffer_collection() const;
fbl::RefPtr<LogicalBufferCollection> shared_logical_buffer_collection();
// If the NodeProperties this Node started with is gone, this asserts, including in release. A
// hard crash is better than going off in the weeds.
NodeProperties& node_properties() const;
void EnsureDetachedFromNodeProperties();
// Returns server end of the channel serving this node. At least for now, this must only be
// called when it's known that the binding is still valid. We check this using
// is_currently_connected().
zx::unowned_channel channel() const;
bool is_done() const;
bool has_client_koid() const;
zx_koid_t client_koid() const;
bool has_server_koid() const;
zx_koid_t server_koid() const;
void set_unfound_node() { was_unfound_node_ = true; }
bool was_unfound_node() const { return was_unfound_node_; }
Device* parent_device() const;
void SetDebugClientInfoInternal(ClientDebugInfo debug_info);
protected:
// Called during Bind() to perform the sub-class protocol-specific bind itself.
using ErrorHandlerWrapper = fit::function<void(fidl::UnbindInfo info)>;
virtual void BindInternalV1(zx::channel server_end,
ErrorHandlerWrapper error_handler_wrapper) = 0;
virtual void BindInternalV2(zx::channel server_end,
ErrorHandlerWrapper error_handler_wrapper) = 0;
virtual void BindInternalCombinedV1AndV2(zx::channel server_end,
ErrorHandlerWrapper error_handler_wrapper) = 0;
template <typename Completer>
void FailSync(Location location, ConnectionVersion version, Completer& completer,
zx_status_t status, const char* format, ...) {
va_list args;
va_start(args, format);
logical_buffer_collection().VLogClientError(location, &node_properties(), format, args);
va_end(args);
if (version == ConnectionVersion::kVersion1) {
completer.Close(status);
} else {
completer.Close(ZX_ERR_INTERNAL);
}
async_failure_result_ = status;
}
template <class SyncCompleterSync>
void SyncImpl(ConnectionVersion version, SyncCompleterSync& completer) {
TRACE_DURATION("gfx", "Node::SyncImpl", "this", this, "logical_buffer_collection",
&logical_buffer_collection());
if (is_done_) {
// Probably a Release() followed by Sync(), which is illegal and
// causes the whole LogicalBufferCollection to fail.
FailSync(FROM_HERE, version, completer, ZX_ERR_BAD_STATE, "Sync() after Close()/Release()");
return;
}
completer.Reply();
}
template <class ReleaseCompleterSync>
void ReleaseImpl(ConnectionVersion version, ReleaseCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, version, completer, ZX_ERR_BAD_STATE,
"Close()/Release() after Close()/Release()");
return;
}
// We still want to enforce that the client doesn't send any other messages
// between Release() and closing the channel, so we just set is_done_ here and
// do a FailSync() if is_done_ is seen to be set while handling any other
// message.
is_done_ = true;
}
template <class SetNameRequestView, class SetNameCompleterSync>
void SetNameImplV1(SetNameRequestView request, SetNameCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion1, completer, ZX_ERR_BAD_STATE,
"SetName() after Close()");
return;
}
logical_buffer_collection().SetName(request.priority(),
std::string(request.name().begin(), request.name().end()));
}
template <class SetNameRequest, class SetNameCompleterSync>
void SetNameImplV2(SetNameRequest& request, SetNameCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetName() after Release()");
return;
}
if (!request.priority().has_value()) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetName() requires priority set");
return;
}
if (!request.name().has_value()) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetName() requires name set");
return;
}
logical_buffer_collection().SetName(
request.priority().value(),
std::string(request.name().value().begin(), request.name().value().end()));
}
template <class SetDebugClientInfoRequestView, class SetDebugClientInfoCompleterSync>
void SetDebugClientInfoImplV1(SetDebugClientInfoRequestView request,
SetDebugClientInfoCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion1, completer, ZX_ERR_BAD_STATE,
"SetDebugClientInfo() after Close()");
return;
}
ClientDebugInfo debug_info;
debug_info.name = std::string(request.name().begin(), request.name().end());
debug_info.id = request.id();
SetDebugClientInfoInternal(std::move(debug_info));
}
template <class SetDebugClientInfoRequest, class SetDebugClientInfoCompleterSync>
void SetDebugClientInfoImplV2(SetDebugClientInfoRequest& request,
SetDebugClientInfoCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetDebugClientInfo() after Release()");
return;
}
if (!request.name().has_value()) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetDebugClientInfo() requires name set");
return;
}
uint64_t id = 0;
if (request.id().has_value()) {
id = *request.id();
}
ClientDebugInfo debug_info;
debug_info.name = std::string(request.name()->begin(), request.name()->end());
debug_info.id = id;
SetDebugClientInfoInternal(std::move(debug_info));
}
template <class SetDebugTimeoutLogDeadlineRequestView,
class SetDebugTimeoutLogDeadlineCompleterSync>
void SetDebugTimeoutLogDeadlineImplV1(SetDebugTimeoutLogDeadlineRequestView request,
SetDebugTimeoutLogDeadlineCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion1, completer, ZX_ERR_BAD_STATE,
"SetDebugTimeoutLogDeadline() after Close()");
return;
}
logical_buffer_collection().SetDebugTimeoutLogDeadline(request.deadline());
}
template <class SetDebugTimeoutLogDeadlineRequest, class SetDebugTimeoutLogDeadlineCompleterSync>
void SetDebugTimeoutLogDeadlineImplV2(SetDebugTimeoutLogDeadlineRequest& request,
SetDebugTimeoutLogDeadlineCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetDebugTimeoutLogDeadline() after Release()");
return;
}
if (!request.deadline().has_value()) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetDebugTimeoutLogDeadline() requires deadline set");
return;
}
logical_buffer_collection().SetDebugTimeoutLogDeadline(request.deadline().value());
}
template <class SetVerboseLoggingCompleterSync>
void SetVerboseLoggingImpl(ConnectionVersion version, SetVerboseLoggingCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, version, completer, ZX_ERR_BAD_STATE,
"SetVerboseLogging() after Close()/Release()");
return;
}
logical_buffer_collection_->SetVerboseLogging();
}
template <typename GetNodeRefCompleterSync>
bool CommonGetNodeRefImplStage1(ConnectionVersion version, GetNodeRefCompleterSync& completer,
zx::event* out_to_vend) {
if (is_done_) {
FailSync(FROM_HERE, version, completer, ZX_ERR_BAD_STATE,
"GetNodeRef() after Close()/Release()");
return false;
}
// No process actually needs to wait on or signal this event. It's just a generic handle that
// needs get_info to work so we can check the koid.
zx_status_t status =
node_properties_->node_ref()->duplicate(ZX_RIGHTS_BASIC & ~(ZX_RIGHT_WAIT), out_to_vend);
if (status != ZX_OK) {
// We treat this similarly to a code page-in that fails due to low memory.
ZX_PANIC("node_ref_vend_.duplicate() failed - sysmem terminating");
}
return true;
}
template <class GetNodeRefCompleterSync>
void GetNodeRefImplV1(GetNodeRefCompleterSync& completer) {
zx::event to_vend;
if (!CommonGetNodeRefImplStage1(ConnectionVersion::kVersion1, completer, &to_vend)) {
return;
}
completer.Reply(std::move(to_vend));
}
template <class GetNodeRefCompleterSync, class Response = fuchsia_sysmem2::NodeGetNodeRefResponse>
void GetNodeRefImplV2(GetNodeRefCompleterSync& completer) {
zx::event to_vend;
if (!CommonGetNodeRefImplStage1(ConnectionVersion::kVersion2, completer, &to_vend)) {
return;
}
Response response;
response.node_ref().emplace(std::move(to_vend));
completer.Reply(std::move(response));
}
template <typename Completer>
bool CommonIsAlternateFor(zx::event node_ref, ConnectionVersion version, Completer& completer,
bool* out_result) {
if (is_done_) {
FailSync(FROM_HERE, version, completer, ZX_ERR_BAD_STATE, "IsAlternateFor() after Release()");
return false;
}
zx_koid_t node_ref_koid;
zx_koid_t not_used;
zx_status_t status = get_handle_koids(node_ref, &node_ref_koid, &not_used, ZX_OBJ_TYPE_EVENT);
if (status != ZX_OK) {
completer.Reply(fit::error(ZX_ERR_INVALID_ARGS));
return false;
}
auto maybe_other_node_properties =
logical_buffer_collection_->FindNodePropertiesByNodeRefKoid(node_ref_koid);
if (!maybe_other_node_properties.has_value()) {
completer.Reply(fit::error(ZX_ERR_NOT_FOUND));
return false;
}
auto* other_node_properties = maybe_other_node_properties.value();
for (auto* iter = &node_properties(); iter; iter = iter->parent()) {
iter->set_marked(true);
}
// Ensure we set_marked(false), even if we add an early return.
auto clear_marked = fit::defer([this] {
for (auto* iter = &node_properties(); iter; iter = iter->parent()) {
iter->set_marked(false);
}
});
NodeProperties* common_parent = nullptr;
for (auto* iter = other_node_properties; iter; iter = iter->parent()) {
if (iter->is_marked()) {
common_parent = iter;
break;
}
}
clear_marked.call();
ZX_DEBUG_ASSERT(common_parent);
// The common_parent can presently be associated with an OrphanedNode, but
// common_parent->is_token_group() will still return true if the original Node sub-class was
// BufferCollectionTokenGroup (even after the client has done a Release() + channel close on
// common_parent).
bool is_alternate_for = common_parent->is_token_group();
*out_result = is_alternate_for;
return true;
}
template <class IsAlternateForRequest, class IsAlternateForCompleterSync,
class Response = fuchsia_sysmem::NodeIsAlternateForResponse>
void IsAlternateForImplV1(IsAlternateForRequest& request,
IsAlternateForCompleterSync& completer) {
bool is_alternate_for;
if (!CommonIsAlternateFor(std::move(request.node_ref()), ConnectionVersion::kVersion1,
completer, &is_alternate_for)) {
return;
}
Response response;
response.is_alternate() = is_alternate_for;
completer.Reply(fit::ok(std::move(response)));
}
template <class IsAlternateForRequest, class IsAlternateForCompleterSync,
class Response = fuchsia_sysmem2::NodeIsAlternateForResponse>
void IsAlternateForImplV2(IsAlternateForRequest& request,
IsAlternateForCompleterSync& completer) {
if (!request.node_ref().has_value()) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"IsAlternateFor() requires node_ref set");
return;
}
bool is_alernate_for;
if (!CommonIsAlternateFor(std::move(request.node_ref().value()), ConnectionVersion::kVersion2,
completer, &is_alernate_for)) {
return;
}
Response response;
response.is_alternate() = is_alernate_for;
completer.Reply(fit::ok(std::move(response)));
}
template <class GetBufferCollectionIdCompleterSync,
class Response = fuchsia_sysmem2::NodeGetBufferCollectionIdResponse>
void GetBufferCollectionIdImplV2(GetBufferCollectionIdCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"GetBufferCollectionId after Release");
return;
}
Response response;
response.buffer_collection_id() = logical_buffer_collection().buffer_collection_id();
completer.Reply(std::move(response));
}
template <class SetWeakCompleterSync>
void SetWeakImplV2(SetWeakCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetWeak after Release");
return;
}
if (ReadyForAllocation()) {
// BufferCollection after SetConstraints, or BufferCollectionTokenGroup after
// AllChildrenPresent.
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetWeak after ready for allocation");
return;
}
node_properties().SetWeak();
// done
//
// ~completer; one-way message; no reply
}
template <class SetWeakOkRequest, class SetWeakOkCompleterSync>
void SetWeakOkImplV2(SetWeakOkRequest& request, SetWeakOkCompleterSync& completer) {
if (is_done_) {
FailSync(FROM_HERE, ConnectionVersion::kVersion2, completer, ZX_ERR_BAD_STATE,
"SetWeakOk() when already is_done_");
return;
}
bool for_child_nodes_also =
request.for_child_nodes_also().has_value() && *request.for_child_nodes_also();
node_properties().SetWeakOk(for_child_nodes_also);
}
void CloseChannel(fuchsia_sysmem2::Error error);
virtual void CloseServerBinding(zx_status_t epitaph) = 0;
// Becomes true on the first Release() (or BindSharedCollection(), in the case of
// BufferCollectionToken). This being true means a channel close is not fatal to the node's
// sub-tree. However, if the client sends a redundant Release(), that is fatal to the node's
// sub-tree.
bool is_done_ = false;
std::optional<zx_status_t> async_failure_result_;
// Used by all Node subclasses except OrphanedNode.
fit::function<void(zx_status_t)> error_handler_;
inspect::Node inspect_node_;
inspect::UintProperty debug_id_property_;
inspect::StringProperty debug_name_property_;
inspect::ValueList properties_;
private:
// Construction status.
zx_status_t create_status_ = ZX_ERR_INTERNAL;
// At least one call to status() needs to happen before ~Node, typically shortly after
// construction (and if status is failed, typically that one check of status() will also be
// shortly before destruction).
mutable bool create_status_was_checked_ = false;
// This is in Node instead of NodeProperties because when BufferCollectionToken or
// BufferCollection becomes an OrphanedNode, we no longer reference LogicalBufferCollection.
fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection_;
// The Node is co-owned by the NodeProperties, so the Node has a raw pointer back to
// NodeProperties.
//
// This pointer is set to nullptr during ~NodeProperties(), so if we attempt to access via
// node_properties_ after that, we'll get a hard crash instead of going off in the weeds.
//
// The main way we avoid accessing NodeProperties beyond when it goes away is the setting of
// error_handler_ = {} in the two CloseChannel() methods. We rely on sub-class's error_handler_
// not running after CloseChannel(), and we rely on LLCPP not calling protocol message handlers
// after server binding Release() (other than completion of any currently-in-progress message
// handler), since we're running Release() on the same dispatcher.
NodeProperties* node_properties_ = nullptr;
// We keep server_end_ around
zx::unowned_channel server_end_;
zx_koid_t client_koid_ = ZX_KOID_INVALID;
zx_koid_t server_koid_ = ZX_KOID_INVALID;
// If true, this node was looked up by koid at some previous time, but at that time the koid
// wasn't found. When true, we log info later if/when the koid shows up and/or debug information
// shows up.
bool was_unfound_node_ = false;
};
} // namespace sysmem_driver
#endif // SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_NODE_H_