| // 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. |
| |
| #include "buffer_collection.h" |
| |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem2/cpp/common_types.h> |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem2/cpp/wire_types.h> |
| #include <lib/ddk/trace/event.h> |
| #include <lib/fidl/cpp/wire/vector_view.h> |
| #include <lib/fidl/cpp/wire_natural_conversions.h> |
| #include <lib/sysmem-version/sysmem-version.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/object.h> |
| #include <zircon/compiler.h> |
| #include <zircon/errors.h> |
| |
| #include <atomic> |
| |
| #include <fbl/ref_ptr.h> |
| |
| #include "buffer_collection_token.h" |
| #include "logical_buffer_collection.h" |
| #include "node_properties.h" |
| #include "utils.h" |
| |
| namespace sysmem_driver { |
| |
| namespace { |
| |
| using Error = fuchsia_sysmem2::Error; |
| |
| // For max client vmo rights, we specify the RIGHT bits individually to avoid |
| // picking up any newly-added rights unintentionally. This is based on |
| // ZX_DEFAULT_VMO_RIGHTS, but with a few rights removed. |
| const uint32_t kMaxClientVmoRights = |
| // ZX_RIGHTS_BASIC includes ZX_RIGHT_INSPECT which is currently required by |
| // at least starnix when mapping a VMO into a starnix-backed process. |
| ZX_RIGHTS_BASIC | |
| // ZX_RIGHTS_IO: |
| ZX_RIGHT_READ | ZX_RIGHT_WRITE | |
| // ZX_RIGHTS_PROPERTY allows a participant to set ZX_PROP_NAME for easier |
| // memory metrics. Nothing prevents participants from figting over the |
| // name, though the kernel should make each set/get atomic with respect to |
| // other set/get. This relies on ZX_RIGHTS_PROPERTY not implying anything |
| // that could be used as an attack vector between processes sharing a VMO. |
| ZX_RIGHTS_PROPERTY | |
| // We intentionally omit ZX_RIGHT_EXECUTE (indefinitely) and ZX_RIGHT_SIGNAL |
| // (at least for now). |
| // |
| // Remaining bits of ZX_DEFAULT_VMO_RIGHTS (as of this writing): |
| ZX_RIGHT_MAP; |
| |
| } // namespace |
| |
| // static |
| BufferCollection& BufferCollection::EmplaceInTree( |
| fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection, BufferCollectionToken* token, |
| const CollectionServerEnd& server_end) { |
| // The token is passed in as a pointer because this method deletes token, but the caller must |
| // provide non-nullptr token. |
| ZX_DEBUG_ASSERT(token); |
| // This conversion from unique_ptr<> to RefPtr<> will go away once we move BufferCollection to |
| // LLCPP FIDL server binding. |
| fbl::RefPtr<Node> node( |
| fbl::AdoptRef(new BufferCollection(logical_buffer_collection, *token, server_end))); |
| BufferCollection* buffer_collection_ptr = static_down_cast<BufferCollection*>(node.get()); |
| // This also deletes token. |
| token->node_properties().SetNode(std::move(node)); |
| return *buffer_collection_ptr; |
| } |
| |
| BufferCollection::~BufferCollection() { |
| TRACE_DURATION("gfx", "BufferCollection::~BufferCollection", "this", this, |
| "logical_buffer_collection", &logical_buffer_collection()); |
| } |
| |
| void BufferCollection::CloseServerBinding(zx_status_t epitaph) { |
| if (server_binding_v1_.has_value()) { |
| server_binding_v1_->Close(epitaph); |
| } |
| if (server_binding_v2_.has_value()) { |
| server_binding_v2_->Close(ZX_ERR_INTERNAL); |
| } |
| server_binding_v1_ = {}; |
| server_binding_v2_ = {}; |
| } |
| |
| void BufferCollection::Bind(CollectionServerEnd collection_server_end) { |
| Node::Bind(TakeNodeServerEnd(std::move(collection_server_end))); |
| } |
| |
| void BufferCollection::BindInternalV1(zx::channel collection_request, |
| ErrorHandlerWrapper error_handler_wrapper) { |
| v1_server_.emplace(*this); |
| server_binding_v1_ = fidl::BindServer( |
| parent_device()->dispatcher(), |
| fidl::ServerEnd<fuchsia_sysmem::BufferCollection>(std::move(collection_request)), |
| &v1_server_.value(), |
| [error_handler_wrapper = std::move(error_handler_wrapper)]( |
| BufferCollection::V1* collection, fidl::UnbindInfo info, CollectionServerEndV1 channel) { |
| error_handler_wrapper(info); |
| }); |
| } |
| |
| void BufferCollection::BindInternalV2(zx::channel collection_request, |
| ErrorHandlerWrapper error_handler_wrapper) { |
| v2_server_.emplace(*this); |
| server_binding_v2_ = fidl::BindServer( |
| parent_device()->dispatcher(), |
| fidl::ServerEnd<fuchsia_sysmem2::BufferCollection>(std::move(collection_request)), |
| &v2_server_.value(), |
| [error_handler_wrapper = std::move(error_handler_wrapper)]( |
| BufferCollection::V2* collection, fidl::UnbindInfo info, CollectionServerEndV2 channel) { |
| error_handler_wrapper(info); |
| }); |
| } |
| |
| void BufferCollection::BindInternalCombinedV1AndV2(zx::channel server_end, |
| ErrorHandlerWrapper error_handler_wrapper) { |
| ZX_PANIC("BufferCollection only serves V1 or V2 separately - never combined V1 and V2"); |
| } |
| |
| void BufferCollection::V1::Sync(SyncCompleter::Sync& completer) { |
| parent_.SyncImpl(ConnectionVersion::kVersion1, completer); |
| } |
| |
| void BufferCollection::V2::Sync(SyncCompleter::Sync& completer) { |
| parent_.SyncImpl(ConnectionVersion::kVersion2, completer); |
| } |
| |
| void BufferCollection::V1::DeprecatedSync(DeprecatedSyncCompleter::Sync& completer) { |
| parent_.SyncImpl(ConnectionVersion::kVersion1, completer); |
| } |
| |
| void BufferCollection::V1::SetConstraintsAuxBuffers( |
| SetConstraintsAuxBuffersRequest& request, SetConstraintsAuxBuffersCompleter::Sync& completer) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "SetConstraintsAuxBuffers() is not supported."); |
| } |
| |
| template <typename Completer> |
| bool BufferCollection::CommonSetConstraintsStage1(Completer& completer) { |
| if (is_set_constraints_seen_) { |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "2nd SetConstraints() causes failure."); |
| return false; |
| } |
| is_set_constraints_seen_ = true; |
| if (is_done_) { |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "SetConstraints() when already is_done_"); |
| // We're failing async - no need to try to fail sync. |
| return false; |
| } |
| return true; |
| } |
| |
| void BufferCollection::V1::SetConstraints(SetConstraintsRequest& request, |
| SetConstraintsCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V1::SetConstraints", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| std::optional<fuchsia_sysmem::BufferCollectionConstraints> local_constraints( |
| std::move(request.constraints())); |
| |
| if (!parent_.CommonSetConstraintsStage1(completer)) { |
| return; |
| } |
| |
| if (!request.has_constraints()) { |
| // Not needed. |
| local_constraints.reset(); |
| } |
| |
| ZX_DEBUG_ASSERT(!parent_.has_constraints()); |
| ZX_DEBUG_ASSERT(request.has_constraints() == local_constraints.has_value()); |
| |
| { // scope result |
| auto result = sysmem::V2CopyFromV1BufferCollectionConstraints( |
| local_constraints.has_value() ? &local_constraints.value() : nullptr); |
| if (!result.is_ok()) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "V2CopyFromV1BufferCollectionConstraints() failed"); |
| return; |
| } |
| ZX_DEBUG_ASSERT(!result.value().IsEmpty() || !local_constraints.has_value()); |
| parent_.node_properties().SetBufferCollectionConstraints(result.take_value()); |
| } // ~result |
| |
| if (parent_.logical_buffer_collection().is_verbose_logging()) { |
| parent_.node_properties().LogInfo(FROM_HERE, "BufferCollection::V1::SetConstraints()"); |
| parent_.node_properties().LogConstraints(FROM_HERE); |
| } |
| |
| // LogicalBufferCollection will ask for constraints when it needs them, |
| // possibly during this call if this is the last participant to report |
| // having initial constraints. |
| // |
| // The LogicalBufferCollection cares if this BufferCollection view has null |
| // constraints, but only later when it asks for the specific constraints. |
| parent_.logical_buffer_collection().OnDependencyReady(); |
| // |this| may be gone at this point, if the allocation failed. Regardless, |
| // SetConstraints() worked, so ZX_OK. |
| } |
| |
| void BufferCollection::V2::SetConstraints(SetConstraintsRequest& request, |
| SetConstraintsCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V2::SetConstraints", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| |
| if (!parent_.CommonSetConstraintsStage1(completer)) { |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!parent_.has_constraints()); |
| |
| // Normalize non-set constraints to constraints with every field non-set. These are semantically |
| // equivalent. |
| fuchsia_sysmem2::BufferCollectionConstraints local_constraints; |
| if (request.constraints().has_value()) { |
| local_constraints = std::move(request.constraints().value()); |
| } |
| |
| parent_.node_properties().SetBufferCollectionConstraints(std::move(local_constraints)); |
| |
| if (parent_.logical_buffer_collection().is_verbose_logging()) { |
| parent_.node_properties().LogInfo(FROM_HERE, "BufferCollection::V2::SetConstraints()"); |
| parent_.node_properties().LogConstraints(FROM_HERE); |
| } |
| |
| // LogicalBufferCollection will ask for constraints when it needs them, |
| // possibly during this call if this is the last participant to report |
| // having initial constraints. |
| // |
| // The LogicalBufferCollection cares if this BufferCollection view has null |
| // constraints, but only later when it asks for the specific constraints. |
| parent_.logical_buffer_collection().OnDependencyReady(); |
| // |this| may be gone at this point, if the allocation failed. Regardless, |
| // SetConstraints() worked, so ZX_OK. |
| } |
| |
| template <typename Completer> |
| bool BufferCollection::CommonWaitForAllBuffersAllocatedStage1( |
| bool enforce_set_constraints_before_wait, Completer& completer, |
| trace_async_id_t* out_event_id) { |
| if (is_done_) { |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "BufferCollection::WaitForAllBuffersAllocated() when already is_done_"); |
| return false; |
| } |
| if (!is_set_constraints_seen_) { |
| if (enforce_set_constraints_before_wait) { |
| // This is enforced for sysmem2, but not for sysmem(1), due to legacy missing check in sysmem1 |
| // timeframe. |
| logical_buffer_collection().LogClientError( |
| FROM_HERE, &node_properties(), |
| "#############################################################################################"); |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "WaitForAllBuffersAllocated before SetConstraints not permitted (in sysmem2)"); |
| return false; |
| } else { |
| // TODO(b/301844809): The referenced bug has the known client(s) that trigger this log output. |
| // Currently this logs at INFO to avoid annoying test runs. |
| logical_buffer_collection().LogClientInfo( |
| FROM_HERE, &node_properties(), |
| "#############################################################################################"); |
| logical_buffer_collection().LogClientInfo( |
| FROM_HERE, &node_properties(), |
| "WaitForAllBuffersAllocated before SetConstraints not permitted in sysmem2; please fix client."); |
| logical_buffer_collection().LogClientInfo( |
| FROM_HERE, &node_properties(), |
| "WaitForAllBuffersAllocated before SetConstraints - allowing because this channel is sysmem(1)"); |
| logical_buffer_collection().LogClientInfo( |
| FROM_HERE, &node_properties(), |
| "#############################################################################################"); |
| } |
| } |
| if (!node_properties().is_weak_ok() && node_properties().is_weak()) { |
| // If this failure happens, the client should make sure to also pay attention to close_weak_asap |
| // ZX_EVENTPAIR_PEER_CLOSED, in addition to sending SetWeakOk(). |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "weak BufferCollection needs SetWeakOk before WaitForAllBuffersAllocated"); |
| return false; |
| } |
| wait_for_buffers_seen_ = true; |
| trace_async_id_t current_event_id = TRACE_NONCE(); |
| TRACE_ASYNC_BEGIN("gfx", "BufferCollection::WaitForAllBuffersAllocated async", current_event_id, |
| "this", this, "logical_buffer_collection", &logical_buffer_collection()); |
| *out_event_id = current_event_id; |
| return true; |
| } |
| |
| void BufferCollection::V1::WaitForBuffersAllocated( |
| WaitForBuffersAllocatedCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V1::WaitForBuffersAllocated", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| |
| trace_async_id_t current_event_id; |
| if (!parent_.CommonWaitForAllBuffersAllocatedStage1(false, completer, ¤t_event_id)) { |
| return; |
| } |
| |
| parent_.pending_wait_for_buffers_allocated_v1_.emplace_back( |
| std::make_pair(current_event_id, completer.ToAsync())); |
| // The allocation is a one-shot (once true, remains true) and may already be done, in which case |
| // this immediately completes txn. |
| parent_.MaybeCompleteWaitForBuffersAllocated(); |
| } |
| |
| void BufferCollection::V2::WaitForAllBuffersAllocated( |
| WaitForAllBuffersAllocatedCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V2::WaitForAllBuffersAllocated", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| |
| trace_async_id_t current_event_id = TRACE_NONCE(); |
| if (!parent_.CommonWaitForAllBuffersAllocatedStage1(true, completer, ¤t_event_id)) { |
| return; |
| } |
| |
| parent_.pending_wait_for_buffers_allocated_v2_.emplace_back( |
| std::make_pair(current_event_id, completer.ToAsync())); |
| // The allocation is a one-shot (once true, remains true) and may already be done, in which case |
| // this immediately completes txn. |
| parent_.MaybeCompleteWaitForBuffersAllocated(); |
| } |
| |
| template <typename Completer> |
| bool BufferCollection::CommonCheckAllBuffersAllocatedStage1( |
| Completer& completer, std::optional<fuchsia_sysmem2::Error>* result) { |
| if (is_done_) { |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "BufferCollectionToken::V1::CheckBuffersAllocated() when " |
| "already is_done_"); |
| // We're failing async - no need to try to fail sync. |
| return false; |
| } |
| if (!logical_allocation_result_.has_value()) { |
| *result = fuchsia_sysmem2::Error::kPending; |
| return true; |
| } |
| // Buffer collection has either been allocated or failed. |
| *result = logical_allocation_result_->maybe_error; |
| return true; |
| } |
| |
| void BufferCollection::V1::CheckBuffersAllocated(CheckBuffersAllocatedCompleter::Sync& completer) { |
| std::optional<fuchsia_sysmem2::Error> result; |
| if (!parent_.CommonCheckAllBuffersAllocatedStage1(completer, &result)) { |
| return; |
| } |
| |
| zx_status_t v1_status; |
| if (!result.has_value()) { |
| v1_status = ZX_OK; |
| } else { |
| v1_status = sysmem::V1CopyFromV2Error(*result); |
| } |
| |
| completer.Reply(v1_status); |
| } |
| |
| void BufferCollection::V2::CheckAllBuffersAllocated( |
| CheckAllBuffersAllocatedCompleter::Sync& completer) { |
| std::optional<fuchsia_sysmem2::Error> result; |
| if (!parent_.CommonCheckAllBuffersAllocatedStage1(completer, &result)) { |
| return; |
| } |
| if (!result.has_value()) { |
| completer.Reply(fit::ok()); |
| } else { |
| completer.Reply(fit::error(*result)); |
| } |
| } |
| |
| void BufferCollection::V1::GetAuxBuffers(GetAuxBuffersCompleter::Sync& completer) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "GetAuxBuffers() is not supported."); |
| } |
| |
| template <typename Completer> |
| bool BufferCollection::CommonAttachTokenStage1(uint32_t rights_attenuation_mask, |
| Completer& completer, |
| NodeProperties** out_node_properties) { |
| if (is_done_) { |
| // This is Release() followed by AttachToken(), which is not permitted and causes the |
| // BufferCollection to fail. |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "BufferCollection::AttachToken() attempted when is_done_"); |
| return false; |
| } |
| |
| if (rights_attenuation_mask == 0) { |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "rights_attenuation_mask of 0 is forbidden"); |
| return false; |
| } |
| |
| // The child created by NewChild() below will be strong not weak because of this check. Any |
| // SetWeak later on a descendant will fail a different check. |
| if (node_properties().is_weak()) { |
| // If this is needed in a real scenario, please reach out. |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "AttachToken on weak collection not (yet?) supported"); |
| return false; |
| } |
| |
| NodeProperties* new_node_properties = node_properties().NewChild(&logical_buffer_collection()); |
| |
| // These defaults can be overriden by Allocator.SetDebugClientInfo() called before |
| // BindSharedCollection(). |
| if (!new_node_properties->client_debug_info().name.empty()) { |
| // This can be overriden via Allocator::SetDebugClientInfo(), but in case that's not called, |
| // this default may help clarify where the new token / BufferCollection channel came from. |
| new_node_properties->client_debug_info().name = |
| new_node_properties->client_debug_info().name + " then AttachToken()"; |
| } else { |
| new_node_properties->client_debug_info().name = "from AttachToken()"; |
| } |
| |
| if (rights_attenuation_mask != ZX_RIGHT_SAME_RIGHTS) { |
| new_node_properties->rights_attenuation_mask() &= rights_attenuation_mask; |
| } |
| |
| // All AttachToken() tokesn are ErrorPropagationMode::kDoNotPropagate from the start. |
| new_node_properties->error_propagation_mode() = ErrorPropagationMode::kDoNotPropagate; |
| |
| *out_node_properties = new_node_properties; |
| |
| return true; |
| } |
| |
| void BufferCollection::V1::AttachToken(AttachTokenRequest& request, |
| AttachTokenCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V1::AttachToken", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| |
| NodeProperties* new_node_properties; |
| if (!parent_.CommonAttachTokenStage1(request.rights_attenuation_mask(), completer, |
| &new_node_properties)) { |
| return; |
| } |
| |
| parent_.logical_buffer_collection().CreateBufferCollectionTokenV1( |
| parent_.shared_logical_buffer_collection(), new_node_properties, |
| std::move(request.token_request())); |
| } |
| |
| void BufferCollection::V2::AttachToken(AttachTokenRequest& request, |
| AttachTokenCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::V2::AttachToken", "this", this, |
| "logical_buffer_collection", &parent_.logical_buffer_collection()); |
| |
| if (!request.rights_attenuation_mask().has_value()) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "V2::AttachToken() requires rights_attenuation_mask set"); |
| return; |
| } |
| |
| if (!request.token_request().has_value()) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "V2::AttachToken() requires token_request set"); |
| return; |
| } |
| |
| NodeProperties* new_node_properties; |
| if (!parent_.CommonAttachTokenStage1(request.rights_attenuation_mask().value(), completer, |
| &new_node_properties)) { |
| return; |
| } |
| |
| parent_.logical_buffer_collection().CreateBufferCollectionTokenV2( |
| parent_.shared_logical_buffer_collection(), new_node_properties, |
| std::move(request.token_request().value())); |
| } |
| |
| template <typename Completer> |
| void BufferCollection::CommonAttachLifetimeTracking(zx::eventpair server_end, |
| uint32_t buffers_remaining, |
| Completer& completer) { |
| TRACE_DURATION("gfx", "BufferCollection::AttachLifetimeTracking", "this", this, |
| "logical_buffer_collection", &logical_buffer_collection()); |
| if (is_done_) { |
| // This is Release() followed by AttachLifetimeTracking() which is not permitted and causes the |
| // BufferCollection to fail. |
| FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "BufferCollection::AttachLifetimeTracking() attempted when is_done_"); |
| return; |
| } |
| pending_lifetime_tracking_.emplace_back(PendingLifetimeTracking{ |
| .server_end = std::move(server_end), .buffers_remaining = buffers_remaining}); |
| MaybeFlushPendingLifetimeTracking(); |
| } |
| |
| void BufferCollection::V1::AttachLifetimeTracking( |
| AttachLifetimeTrackingRequest& request, AttachLifetimeTrackingCompleter::Sync& completer) { |
| parent_.CommonAttachLifetimeTracking(std::move(request.server_end()), request.buffers_remaining(), |
| completer); |
| } |
| |
| void BufferCollection::V2::AttachLifetimeTracking( |
| AttachLifetimeTrackingRequest& request, AttachLifetimeTrackingCompleter::Sync& completer) { |
| if (!request.server_end().has_value()) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "AttachLifetimeTracking() requires server_end set"); |
| return; |
| } |
| if (!request.buffers_remaining().has_value()) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "AttachLifetimeTracking() requires buffers_remaining set"); |
| return; |
| } |
| parent_.CommonAttachLifetimeTracking(std::move(request.server_end().value()), |
| request.buffers_remaining().value(), completer); |
| } |
| |
| void BufferCollection::V1::SetVerboseLogging(SetVerboseLoggingCompleter::Sync& completer) { |
| parent_.SetVerboseLoggingImpl(ConnectionVersion::kVersion1, completer); |
| } |
| |
| void BufferCollection::V2::SetVerboseLogging(SetVerboseLoggingCompleter::Sync& completer) { |
| parent_.SetVerboseLoggingImpl(ConnectionVersion::kVersion2, completer); |
| } |
| |
| void BufferCollection::V1::GetNodeRef(GetNodeRefCompleter::Sync& completer) { |
| parent_.GetNodeRefImplV1(completer); |
| } |
| |
| void BufferCollection::V2::GetNodeRef(GetNodeRefCompleter::Sync& completer) { |
| parent_.GetNodeRefImplV2(completer); |
| } |
| |
| void BufferCollection::V1::IsAlternateFor(IsAlternateForRequest& request, |
| IsAlternateForCompleter::Sync& completer) { |
| parent_.IsAlternateForImplV1(request, completer); |
| } |
| |
| void BufferCollection::V2::IsAlternateFor(IsAlternateForRequest& request, |
| IsAlternateForCompleter::Sync& completer) { |
| parent_.IsAlternateForImplV2(request, completer); |
| } |
| |
| void BufferCollection::V2::GetBufferCollectionId(GetBufferCollectionIdCompleter::Sync& completer) { |
| parent_.GetBufferCollectionIdImplV2(completer); |
| } |
| |
| void BufferCollection::V2::SetWeak(SetWeakCompleter::Sync& completer) { |
| parent_.SetWeakImplV2(completer); |
| // SetWeak() implies SetWeakOk(), but only for this Node, not on behalf of any child Node(s). |
| parent_.node_properties().SetWeakOk(false); |
| } |
| |
| void BufferCollection::V2::SetWeakOk(SetWeakOkRequest& request, |
| SetWeakOkCompleter::Sync& completer) { |
| if (parent_.wait_for_buffers_seen_) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "SetWeakOk() after WaitForAllBuffersAllocated()"); |
| return; |
| } |
| parent_.SetWeakOkImplV2(request, completer); |
| } |
| |
| void BufferCollection::V2::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_sysmem2::BufferCollection> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| parent_.FailSync(FROM_HERE, completer, Error::kProtocolDeviation, |
| "BufferCollection unknown method - ordinal: %" PRIx64, metadata.method_ordinal); |
| } |
| |
| void BufferCollection::V1::Close(CloseCompleter::Sync& completer) { |
| parent_.ReleaseImpl(ConnectionVersion::kVersion1, completer); |
| } |
| |
| void BufferCollection::V2::Release(ReleaseCompleter::Sync& completer) { |
| parent_.ReleaseImpl(ConnectionVersion::kVersion2, completer); |
| } |
| |
| void BufferCollection::V1::DeprecatedClose(DeprecatedCloseCompleter::Sync& completer) { |
| parent_.ReleaseImpl(ConnectionVersion::kVersion1, completer); |
| } |
| |
| void BufferCollection::V1::SetName(SetNameRequest& request, SetNameCompleter::Sync& completer) { |
| parent_.SetNameImplV1(request, completer); |
| } |
| |
| void BufferCollection::V2::SetName(SetNameRequest& request, SetNameCompleter::Sync& completer) { |
| parent_.SetNameImplV2(request, completer); |
| } |
| |
| void BufferCollection::V1::DeprecatedSetName(DeprecatedSetNameRequest& request, |
| DeprecatedSetNameCompleter::Sync& completer) { |
| parent_.SetNameImplV1(request, completer); |
| } |
| |
| void BufferCollection::V1::SetDebugClientInfo(SetDebugClientInfoRequest& request, |
| SetDebugClientInfoCompleter::Sync& completer) { |
| parent_.SetDebugClientInfoImplV1(request, completer); |
| } |
| |
| void BufferCollection::V2::SetDebugClientInfo(SetDebugClientInfoRequest& request, |
| SetDebugClientInfoCompleter::Sync& completer) { |
| parent_.SetDebugClientInfoImplV2(request, completer); |
| } |
| |
| void BufferCollection::V1::DeprecatedSetDebugClientInfo( |
| DeprecatedSetDebugClientInfoRequest& request, |
| DeprecatedSetDebugClientInfoCompleter::Sync& completer) { |
| parent_.SetDebugClientInfoImplV1(request, completer); |
| } |
| |
| void BufferCollection::V1::SetDebugTimeoutLogDeadline( |
| SetDebugTimeoutLogDeadlineRequest& request, |
| SetDebugTimeoutLogDeadlineCompleter::Sync& completer) { |
| parent_.SetDebugTimeoutLogDeadlineImplV1(request, completer); |
| } |
| |
| void BufferCollection::V2::SetDebugTimeoutLogDeadline( |
| SetDebugTimeoutLogDeadlineRequest& request, |
| SetDebugTimeoutLogDeadlineCompleter::Sync& completer) { |
| parent_.SetDebugTimeoutLogDeadlineImplV2(request, completer); |
| } |
| |
| void BufferCollection::FailAsync(Location location, Error error, const char* format, ...) { |
| va_list args; |
| va_start(args, format); |
| logical_buffer_collection().VLogClientError(location, &node_properties(), format, args); |
| va_end(args); |
| |
| // Idempotent, so only close once. |
| if (!server_binding_v1_.has_value() && !server_binding_v2_.has_value()) { |
| return; |
| } |
| ZX_DEBUG_ASSERT(!!server_binding_v1_.has_value() ^ !!server_binding_v2_.has_value()); |
| |
| zx_status_t epitaph = sysmem::V1CopyFromV2Error(error); |
| async_failure_result_ = epitaph; |
| |
| if (server_binding_v1_.has_value()) { |
| server_binding_v1_->Close(epitaph); |
| server_binding_v1_ = {}; |
| } else { |
| ZX_DEBUG_ASSERT(server_binding_v2_.has_value()); |
| server_binding_v2_->Close(ZX_ERR_INTERNAL); |
| server_binding_v2_ = {}; |
| } |
| } |
| |
| template <typename Completer> |
| void BufferCollection::FailSync(Location location, Completer& completer, Error error, |
| const char* format, ...) { |
| va_list args; |
| va_start(args, format); |
| logical_buffer_collection().VLogClientError(location, &node_properties(), format, args); |
| va_end(args); |
| |
| zx_status_t epitaph = sysmem::V1CopyFromV2Error(error); |
| |
| if (server_binding_v1_.has_value()) { |
| completer.Close(epitaph); |
| } else { |
| completer.Close(ZX_ERR_INTERNAL); |
| } |
| |
| async_failure_result_ = epitaph; |
| } |
| |
| fpromise::result<fuchsia_sysmem2::BufferCollectionInfo> BufferCollection::CloneResultForSendingV2( |
| const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) { |
| uint32_t vmo_rights_mask = GetClientVmoRights(); |
| ZX_DEBUG_ASSERT(has_constraints()); |
| bool is_usage = constraints().usage().has_value() && IsAnyUsage(constraints().usage().value()); |
| if (!is_usage || node_properties().is_weak()) { |
| // By specifying 0 for rights, the V2CloneBufferCollectionInfo() below won't dup any VMO handles |
| // (and won't create any child VMOs). |
| vmo_rights_mask = 0; |
| } |
| #if ZX_DEBUG_ASSERT_IMPLEMENTED |
| { |
| if (!node_properties().is_weak()) { |
| bool found_missing = false; |
| for (auto& buffer : *buffer_collection_info.buffers()) { |
| // Because this node isn't weak, this node is owning all these sysmem strong VMOs. |
| if (!buffer.vmo().has_value() || !buffer.vmo()->is_valid()) { |
| found_missing = true; |
| break; |
| } |
| } |
| ZX_DEBUG_ASSERT(!found_missing); |
| } |
| } |
| #endif |
| auto clone_result = sysmem::V2CloneBufferCollectionInfo(buffer_collection_info, vmo_rights_mask); |
| if (!clone_result.is_ok()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, |
| "CloneResultForSendingV1() V2CloneBufferCollectionInfo() failed - status: %d", |
| clone_result.error()); |
| return fpromise::error(); |
| } |
| auto v2_b = clone_result.take_value(); |
| if (node_properties().is_weak()) { |
| // replace no-vmo VmoBuffer(s) with VmoBuffer(s) having both a sysmem weak VMO handle (if |
| // is_usage) and a close_weak_asap client end |
| for (uint32_t buffer_index = 0; buffer_index < buffer_collection_info.buffers()->size(); |
| ++buffer_index) { |
| fuchsia_sysmem2::VmoBuffer vmo_buffer = std::move(v2_b.buffers()->at(buffer_index)); |
| if (is_usage) { |
| // If zero strong VMO handles remain, this will be success but with no VMO handle. |
| auto weak_vmo_result = logical_buffer_collection().CreateWeakVmo( |
| buffer_index, node_properties().client_debug_info()); |
| if (weak_vmo_result.is_error()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, "CreateWeakVmo() failed"); |
| return fpromise::error(); |
| } |
| // This is moving std::optional<zx::vmo>; if zero strong VMO handles remain, the moved-into |
| // optional will be !has_value(). |
| vmo_buffer.vmo() = std::move(weak_vmo_result.value()); |
| } else { |
| ZX_DEBUG_ASSERT(!vmo_buffer.vmo().has_value()); |
| } |
| auto close_weak_asap = logical_buffer_collection().DupCloseWeakAsapClientEnd(buffer_index); |
| if (close_weak_asap.is_error()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, "DupCloseWeakAsapClientEnd() failed"); |
| return fpromise::error(); |
| } |
| // This is moving std::optional<zx::eventpair>; if zero strong VMO handles remain, the |
| // moved-into optional will be !has_value(). |
| vmo_buffer.close_weak_asap() = std::move(close_weak_asap.value()); |
| // If we're giving out a sysmem weak VMO handle then we're also giving out a close_weak_asap, |
| // since any client holding a sysmem weak VMO handle needs to know when to close that handle |
| // asap. |
| ZX_DEBUG_ASSERT(!vmo_buffer.vmo().has_value() || vmo_buffer.close_weak_asap().has_value()); |
| auto attenuated_vmo_buffer_result = |
| sysmem::V2CloneVmoBuffer(vmo_buffer, GetClientVmoRights()); |
| if (attenuated_vmo_buffer_result.is_error()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, "V2CloneVmoBuffer failed"); |
| return fpromise::error(); |
| } |
| auto& attenuated_vmo_buffer = attenuated_vmo_buffer_result.value(); |
| v2_b.buffers()->at(buffer_index) = std::move(attenuated_vmo_buffer); |
| } |
| } |
| return fpromise::ok(std::move(v2_b)); |
| } |
| |
| fpromise::result<fuchsia_sysmem::BufferCollectionInfo2> BufferCollection::CloneResultForSendingV1( |
| const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) { |
| if (node_properties().is_weak() && !node_properties().is_weak_ok_from_parent()) { |
| // To avoid this failure case, consider migrating to sysmem2 (a sysmem (1) token client_end can |
| // be used directly as a sysmem2 token). |
| // |
| // It doesn't work to set for_child_nodes_also true on a v2 Node then convert that Node to a v1 |
| // Node then get VMO handles via that v1 Node. Instead, an _ancestor_ v2 Node must have set |
| // for_child_nodes_also true - only then can a descendant v1 Node be sent weak VMO handles. |
| FailAsync(FROM_HERE, Error::kProtocolDeviation, |
| "sysmem v1 can't do weak unless for_child_nodes_also=true from _ancestor_ v2 node"); |
| return fpromise::error(); |
| } |
| auto v2_result = CloneResultForSendingV2(buffer_collection_info); |
| if (!v2_result.is_ok()) { |
| // FailAsync() already called. |
| return fpromise::error(); |
| } |
| auto v1_result = sysmem::V1MoveFromV2BufferCollectionInfo(v2_result.take_value()); |
| if (!v1_result.is_ok()) { |
| FailAsync(FROM_HERE, Error::kProtocolDeviation, |
| "CloneResultForSendingV1() V1MoveFromV2BufferCollectionInfo() failed"); |
| return fpromise::error(); |
| } |
| return v1_result; |
| } |
| |
| void BufferCollection::OnBuffersAllocated(const AllocationResult& allocation_result) { |
| TRACE_DURATION("gfx", "BufferCollection::OnBuffersAllocated", "status", |
| allocation_result.maybe_error.has_value() |
| ? static_cast<uint32_t>(*allocation_result.maybe_error) |
| : ZX_OK); |
| ZX_DEBUG_ASSERT(!logical_allocation_result_.has_value()); |
| |
| ZX_DEBUG_ASSERT(!allocation_result.maybe_error.has_value() == |
| !!allocation_result.buffer_collection_info); |
| |
| node_properties().SetBuffersLogicallyAllocated(); |
| |
| logical_allocation_result_.emplace(allocation_result); |
| |
| // Any that are pending are completed by this call or something called |
| // FailAsync(). It's fine for this method to ignore the fact that |
| // FailAsync() may have already been called. That's essentially the main |
| // reason we have FailAsync() instead of Fail(). |
| MaybeCompleteWaitForBuffersAllocated(); |
| |
| // If there are any, they'll be flushed to LogicalBufferCollection now. |
| MaybeFlushPendingLifetimeTracking(); |
| } |
| |
| bool BufferCollection::has_constraints() { return node_properties().has_constraints(); } |
| |
| const fuchsia_sysmem2::BufferCollectionConstraints& BufferCollection::constraints() { |
| ZX_DEBUG_ASSERT(has_constraints()); |
| return *node_properties().buffer_collection_constraints(); |
| } |
| |
| fuchsia_sysmem2::BufferCollectionConstraints BufferCollection::CloneConstraints() { |
| ZX_DEBUG_ASSERT(has_constraints()); |
| // copy / clone |
| return constraints(); |
| } |
| |
| BufferCollection::BufferCollection( |
| fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection_param, |
| const BufferCollectionToken& token, const CollectionServerEnd& server_end) |
| : Node(std::move(logical_buffer_collection_param), &token.node_properties(), |
| GetUnownedChannel(server_end)) { |
| TRACE_DURATION("gfx", "BufferCollection::BufferCollection", "this", this, |
| "logical_buffer_collection", &this->logical_buffer_collection()); |
| ZX_DEBUG_ASSERT(shared_logical_buffer_collection()); |
| inspect_node_ = |
| this->logical_buffer_collection().inspect_node().CreateChild(CreateUniqueName("collection-")); |
| } |
| |
| // This method is only meant to be called from GetClientVmoRights(). |
| uint32_t BufferCollection::GetUsageBasedRightsAttenuation() { |
| // This method won't be called for participants without constraints. |
| ZX_DEBUG_ASSERT(has_constraints()); |
| |
| // We assume that read and map are both needed by all participants with any "usage". Only |
| // ZX_RIGHT_WRITE is controlled by usage. |
| |
| // It's not this method's job to attenuate down to kMaxClientVmoRights, so |
| // let's not pretend like it is. |
| uint32_t result = std::numeric_limits<uint32_t>::max(); |
| if (!constraints().usage().has_value() || !IsWriteUsage(*constraints().usage())) { |
| result &= ~ZX_RIGHT_WRITE; |
| } |
| |
| return result; |
| } |
| |
| uint32_t BufferCollection::GetClientVmoRights() { |
| return |
| // max possible rights for a client to have |
| kMaxClientVmoRights & |
| // attenuate write if client doesn't need write |
| GetUsageBasedRightsAttenuation() & |
| // attenuate according to BufferCollectionToken.Duplicate() rights |
| // parameter so that initiator and participant can distribute the token |
| // and remove any unnecessary/unintended rights along the way. |
| node_properties().rights_attenuation_mask(); |
| } |
| |
| void BufferCollection::MaybeCompleteWaitForBuffersAllocated() { |
| if (!logical_allocation_result_.has_value()) { |
| // Everything is ok so far, but allocation isn't done yet. |
| return; |
| } |
| while (!pending_wait_for_buffers_allocated_v1_.empty()) { |
| auto [async_id, txn] = std::move(pending_wait_for_buffers_allocated_v1_.front()); |
| pending_wait_for_buffers_allocated_v1_.pop_front(); |
| |
| fuchsia_sysmem::BufferCollectionInfo2 v1; |
| if (!logical_allocation_result_->maybe_error.has_value()) { |
| ZX_DEBUG_ASSERT(logical_allocation_result_->buffer_collection_info); |
| auto v1_result = CloneResultForSendingV1(*logical_allocation_result_->buffer_collection_info); |
| if (!v1_result.is_ok()) { |
| // FailAsync() already called. |
| return; |
| } |
| v1 = v1_result.take_value(); |
| } |
| TRACE_ASYNC_END("gfx", "BufferCollection::WaitForAllBuffersAllocated async", async_id, "this", |
| this, "logical_buffer_collection", &logical_buffer_collection()); |
| |
| fuchsia_sysmem::BufferCollectionWaitForBuffersAllocatedResponse response; |
| if (logical_allocation_result_->maybe_error.has_value()) { |
| response.status() = sysmem::V1CopyFromV2Error(*logical_allocation_result_->maybe_error); |
| } else { |
| response.status() = ZX_OK; |
| } |
| response.buffer_collection_info() = std::move(v1); |
| txn.Reply(std::move(response)); |
| |
| fidl::Status reply_status = txn.result_of_reply(); |
| if (!reply_status.ok()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, |
| "fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated_" |
| "reply failed - status: %s", |
| reply_status.FormatDescription().c_str()); |
| return; |
| } |
| // ~txn |
| } |
| while (!pending_wait_for_buffers_allocated_v2_.empty()) { |
| auto [async_id, txn] = std::move(pending_wait_for_buffers_allocated_v2_.front()); |
| pending_wait_for_buffers_allocated_v2_.pop_front(); |
| |
| fuchsia_sysmem2::BufferCollectionInfo v2; |
| if (!logical_allocation_result_->maybe_error.has_value()) { |
| ZX_DEBUG_ASSERT(logical_allocation_result_->buffer_collection_info); |
| auto v2_result = CloneResultForSendingV2(*logical_allocation_result_->buffer_collection_info); |
| if (!v2_result.is_ok()) { |
| // FailAsync() already called. |
| return; |
| } |
| v2 = v2_result.take_value(); |
| } |
| TRACE_ASYNC_END("gfx", "BufferCollection::WaitForAllBuffersAllocated async", async_id, "this", |
| this, "logical_buffer_collection", &logical_buffer_collection()); |
| if (logical_allocation_result_->maybe_error.has_value()) { |
| txn.Reply(fit::error(*logical_allocation_result_->maybe_error)); |
| } else { |
| fuchsia_sysmem2::BufferCollectionWaitForAllBuffersAllocatedResponse response; |
| response.buffer_collection_info() = std::move(v2); |
| txn.Reply(fit::ok(std::move(response))); |
| } |
| fidl::Status reply_status = txn.result_of_reply(); |
| if (!reply_status.ok()) { |
| FailAsync(FROM_HERE, Error::kUnspecified, |
| "fuchsia_sysmem2::BufferCollectionWaitForBuffersAllocated " |
| "reply failed - status: %s", |
| reply_status.FormatDescription().c_str()); |
| return; |
| } |
| // ~txn |
| } |
| } |
| |
| void BufferCollection::MaybeFlushPendingLifetimeTracking() { |
| if (!node_properties().buffers_logically_allocated()) { |
| return; |
| } |
| if (logical_allocation_result_->maybe_error.has_value()) { |
| // We close these immediately if logical allocation failed, regardless of |
| // the number of buffers potentially allocated in the overall |
| // LogicalBufferCollection. This is for behavior consistency between |
| // AttachToken() sub-trees and the root_. |
| pending_lifetime_tracking_.clear(); |
| return; |
| } |
| for (auto& pending : pending_lifetime_tracking_) { |
| logical_buffer_collection().AttachLifetimeTracking(std::move(pending.server_end), |
| pending.buffers_remaining); |
| } |
| pending_lifetime_tracking_.clear(); |
| } |
| |
| bool BufferCollection::ReadyForAllocation() { return has_constraints(); } |
| |
| BufferCollectionToken* BufferCollection::buffer_collection_token() { return nullptr; } |
| |
| const BufferCollectionToken* BufferCollection::buffer_collection_token() const { return nullptr; } |
| |
| BufferCollection* BufferCollection::buffer_collection() { return this; } |
| |
| const BufferCollection* BufferCollection::buffer_collection() const { return this; } |
| |
| BufferCollectionTokenGroup* BufferCollection::buffer_collection_token_group() { return nullptr; } |
| |
| const BufferCollectionTokenGroup* BufferCollection::buffer_collection_token_group() const { |
| return nullptr; |
| } |
| |
| OrphanedNode* BufferCollection::orphaned_node() { return nullptr; } |
| |
| const OrphanedNode* BufferCollection::orphaned_node() const { return nullptr; } |
| |
| bool BufferCollection::is_connected_type() const { return true; } |
| |
| bool BufferCollection::is_currently_connected() const { |
| return server_binding_v1_.has_value() || server_binding_v2_.has_value(); |
| } |
| |
| const char* BufferCollection::node_type_string() const { return "collection"; } |
| |
| ConnectionVersion BufferCollection::connection_version() const { |
| if (server_binding_v2_.has_value()) { |
| return ConnectionVersion::kVersion2; |
| } |
| if (server_binding_v1_.has_value()) { |
| return ConnectionVersion::kVersion1; |
| } |
| return ConnectionVersion::kNoConnection; |
| } |
| |
| } // namespace sysmem_driver |