blob: 7190b2827f4b70e269c5c6665f8f9014bf7dbfcc [file] [log] [blame]
// 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 "logical_buffer_collection.h"
#include <lib/fidl-utils/bind.h>
#include <zircon/compiler.h>
extern const fidl_type_t fuchsia_sysmem_BufferCollectionConstraintsTable;
namespace {
namespace {
constexpr uint32_t kConcurrencyCap = 64;
} // namespace
// 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, except ZX_RIGHT_INSPECT (at least for now).
ZX_RIGHT_TRANSFER | ZX_RIGHT_DUPLICATE | ZX_RIGHT_WAIT |
// ZX_RIGHTS_IO:
ZX_RIGHT_READ | ZX_RIGHT_WRITE |
// We intentionally omit ZX_RIGHTS_PROPERTY (ZX_RIGHT_GET_PROPERTY |
// ZX_RIGHT_SET_PROPERTY), at least for now.
//
// 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
const fuchsia_sysmem_BufferCollection_ops_t BufferCollection::kOps = {
fidl::Binder<BufferCollection>::BindMember<&BufferCollection::SetEventSink>,
fidl::Binder<BufferCollection>::BindMember<&BufferCollection::Sync>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::SetConstraints>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::WaitForBuffersAllocated>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::CheckBuffersAllocated>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::CloseSingleBuffer>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::AllocateSingleBuffer>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::WaitForSingleBufferAllocated>,
fidl::Binder<BufferCollection>::BindMember<
&BufferCollection::CheckSingleBufferAllocated>,
fidl::Binder<BufferCollection>::BindMember<&BufferCollection::Close>,
};
BufferCollection::~BufferCollection() {
// Close() the SimpleBinding<> before deleting the list of pending Txn(s),
// so that ~Txn doesn't complain about being deleted without being
// completed.
//
// Don't run the error handler; if any error handler remains it'll just get
// deleted here.
(void)binding_.Close();
}
zx_status_t BufferCollection::SetEventSink(
zx_handle_t buffer_collection_events_client_param) {
zx::channel buffer_collection_events_client(
buffer_collection_events_client_param);
if (is_done_) {
FailAsync(
ZX_ERR_BAD_STATE,
"BufferCollectionToken::SetEventSink() when already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
if (!buffer_collection_events_client) {
zx_status_t status = ZX_ERR_INVALID_ARGS;
FailAsync(status, "BufferCollection::SetEventSink() must be called "
"with a non-zero handle.");
return status;
}
if (is_set_constraints_seen_) {
zx_status_t status = ZX_ERR_INVALID_ARGS;
// It's not required to use SetEventSink(), but if it's used, it must be
// before SetConstraints().
FailAsync(status, "BufferCollection::SetEventSink() (if any) must be "
"before SetConstraints().");
return status;
}
if (events_) {
zx_status_t status = ZX_ERR_INVALID_ARGS;
FailAsync(
status,
"BufferCollection::SetEventSink() must be called only up to once.");
return status;
}
events_ = std::move(buffer_collection_events_client);
// We don't create BufferCollection until after processing all inbound
// messages that were queued toward the server on the token channel of the
// token that was used to create this BufferCollection, so we can send this
// event now, as all the Duplicate() inbound from that token are done being
// processed before here.
fuchsia_sysmem_BufferCollectionEventsOnDuplicatedTokensKnownByServer(
events_.get());
return ZX_OK;
}
zx_status_t BufferCollection::Sync(fidl_txn_t* txn) {
BindingType::Txn::RecognizeTxn(txn);
return fuchsia_sysmem_BufferCollectionSync_reply(txn);
}
zx_status_t BufferCollection::SetConstraints(
bool has_constraints,
const fuchsia_sysmem_BufferCollectionConstraints* constraints_param) {
// Regardless of has_constraints or not, we need to unconditionally take
// ownership of any handles in constraints_param. Not that there are
// necessarily any handles in here currently, but to avoid being fragile re.
// any handles potentially added later.
constraints_.reset(constraints_param);
if (is_done_) {
FailAsync(
ZX_ERR_BAD_STATE,
"BufferCollectionToken::SetConstraints() when already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
if (is_set_constraints_seen_) {
FailAsync(ZX_ERR_NOT_SUPPORTED,
"For now, 2nd SetConstraints() causes failure.");
return ZX_ERR_NOT_SUPPORTED;
}
is_set_constraints_seen_ = true;
if (!has_constraints) {
// We don't need any of the handles/info in constraints_param, so close
// the handles sooner rather than later. This also lets us track
// whether we have null constraints without a separate bool.
constraints_.reset(nullptr);
}
// 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()->OnSetConstraints();
// |this| may be gone at this point, if the allocation failed. Regardless,
// SetConstraints() worked, so ZX_OK.
return ZX_OK;
}
zx_status_t BufferCollection::WaitForBuffersAllocated(fidl_txn_t* txn_param) {
BindingType::Txn::RecognizeTxn(txn_param);
if (is_done_) {
FailAsync(
ZX_ERR_BAD_STATE,
"BufferCollectionToken::WaitForBuffersAllocated() when already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
// In general we're handling this async, so take ownership of the txn.
std::unique_ptr<BindingType::Txn> txn =
BindingType::Txn::TakeTxn(txn_param);
pending_wait_for_buffers_allocated_.emplace_back(std::move(txn));
// The allocation is a one-shot (once true, remains true) and may already be
// done, in which case this immediately completes txn.
MaybeCompleteWaitForBuffersAllocated();
return ZX_OK;
}
zx_status_t BufferCollection::CheckBuffersAllocated(fidl_txn_t* txn) {
BindingType::Txn::RecognizeTxn(txn);
if (is_done_) {
FailAsync(ZX_ERR_BAD_STATE,
"BufferCollectionToken::CheckBuffersAllocated() when "
"already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
LogicalBufferCollection::AllocationResult allocation_result =
parent()->allocation_result();
if (allocation_result.status == ZX_OK &&
!allocation_result.buffer_collection_info) {
return fuchsia_sysmem_BufferCollectionCheckBuffersAllocated_reply(txn, ZX_ERR_UNAVAILABLE);
}
// Buffer collection has either been allocated or failed.
return fuchsia_sysmem_BufferCollectionCheckBuffersAllocated_reply(txn, allocation_result.status);
}
zx_status_t BufferCollection::CloseSingleBuffer(uint64_t buffer_index) {
if (is_done_) {
FailAsync(
ZX_ERR_BAD_STATE,
"BufferCollectionToken::CloseSingleBuffer() when already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
// FailAsync() instead of returning a failure, mainly because FailAsync()
// prints a message that's more obvious than the generic _dispatch() failure
// would.
FailAsync(ZX_ERR_NOT_SUPPORTED, "CloseSingleBuffer() not yet implemented");
return ZX_OK;
}
zx_status_t BufferCollection::AllocateSingleBuffer(uint64_t buffer_index) {
if (is_done_) {
FailAsync(ZX_ERR_BAD_STATE,
"BufferCollectionToken::AllocateSingleBuffer() when already "
"is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
FailAsync(ZX_ERR_NOT_SUPPORTED,
"AllocateSingleBuffer() not yet implemented");
return ZX_OK;
}
zx_status_t
BufferCollection::WaitForSingleBufferAllocated(uint64_t buffer_index,
fidl_txn_t* txn) {
BindingType::Txn::RecognizeTxn(txn);
if (is_done_) {
FailAsync(ZX_ERR_BAD_STATE,
"BufferCollectionToken::WaitForSingleBufferAllocated() when "
"already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
FailAsync(ZX_ERR_NOT_SUPPORTED,
"WaitForSingleBufferAllocated() not yet implemented");
return ZX_OK;
}
zx_status_t
BufferCollection::CheckSingleBufferAllocated(uint64_t buffer_index) {
if (is_done_) {
FailAsync(ZX_ERR_BAD_STATE,
"BufferCollectionToken::CheckSingleBufferAllocated() when "
"already is_done_");
// We're failing async - no need to try to fail sync.
return ZX_OK;
}
FailAsync(ZX_ERR_NOT_SUPPORTED,
"CheckSingleBufferAllocated() not yet implemented");
return ZX_OK;
}
zx_status_t BufferCollection::Close() {
if (is_done_) {
FailAsync(ZX_ERR_BAD_STATE,
"BufferCollection::Close() when already closed.");
return ZX_OK;
}
// We still want to enforce that the client doesn't send any other messages
// between Close() and closing the channel, so we just set is_done_ here and
// do a FailAsync() if is_done_ is seen to be set while handling any other
// message.
is_done_ = true;
return ZX_OK;
}
void BufferCollection::OnBuffersAllocated() {
// 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 (!events_) {
return;
}
LogicalBufferCollection::AllocationResult allocation_result =
parent()->allocation_result();
ZX_DEBUG_ASSERT(allocation_result.buffer_collection_info ||
allocation_result.status != ZX_OK);
BufferCollectionInfo to_send(BufferCollectionInfo::Default);
if (allocation_result.buffer_collection_info) {
to_send =
BufferCollectionInfoClone(allocation_result.buffer_collection_info);
if (!to_send) {
// FailAsync() already called by Clone()
return;
}
}
// Ownership of all handles in to_send is transferred to this function.
fuchsia_sysmem_BufferCollectionEventsOnBuffersAllocated(
events_.get(), allocation_result.status, to_send.release());
}
bool BufferCollection::is_set_constraints_seen() {
return is_set_constraints_seen_;
}
const fuchsia_sysmem_BufferCollectionConstraints*
BufferCollection::constraints() {
ZX_DEBUG_ASSERT(is_set_constraints_seen());
if (!constraints_) {
return nullptr;
}
return constraints_.get();
}
BufferCollection::Constraints
BufferCollection::TakeConstraints() {
ZX_DEBUG_ASSERT(is_set_constraints_seen());
return std::move(constraints_);
}
LogicalBufferCollection* BufferCollection::parent() {
return parent_.get();
}
fbl::RefPtr<LogicalBufferCollection> BufferCollection::parent_shared() {
return parent_;
}
bool BufferCollection::is_done() {
return is_done_;
}
BufferCollection::BufferCollection(fbl::RefPtr<LogicalBufferCollection> parent)
: FidlServer("BufferCollection", kConcurrencyCap), parent_(parent) {
ZX_DEBUG_ASSERT(parent_);
}
// This method is only meant to be called from GetClientVmoRights().
uint32_t BufferCollection::GetUsageBasedRightsAttenuation() {
ZX_DEBUG_ASSERT(is_set_constraints_seen_);
// If there are no constraints from this participant, it means this
// participant doesn't intend to do any "usage" at all aside from referring
// to buffers by their index in communication with other participants, so
// this participant doesn't need any VMO handles at all. So this method
// never should be called if that's the case.
ZX_DEBUG_ASSERT(constraints_);
// We assume that read and map are both needed by all participants. Only
// ZX_RIGHT_WRITE is controlled by usage.
bool is_write_needed = false;
const fuchsia_sysmem_BufferUsage* usage = &constraints_->usage;
const uint32_t kCpuWriteBits =
fuchsia_sysmem_cpuUsageWriteOften | fuchsia_sysmem_cpuUsageWrite;
// This list may not be complete.
const uint32_t kVulkanWriteBits = fuchsia_sysmem_vulkanUsageTransferDst |
fuchsia_sysmem_vulkanUsageStorage;
// Display usages don't include any writing.
const uint32_t kDisplayWriteBits = 0;
const uint32_t kVideoWriteBits = fuchsia_sysmem_videoUsageHwDecoder;
is_write_needed = (usage->cpu & kCpuWriteBits) ||
(usage->vulkan & kVulkanWriteBits) ||
(usage->display & kDisplayWriteBits) ||
(usage->video & kVideoWriteBits);
// 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 (!is_write_needed) {
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.
client_rights_attenuation_mask_;
}
void BufferCollection::MaybeCompleteWaitForBuffersAllocated() {
LogicalBufferCollection::AllocationResult allocation_result =
parent()->allocation_result();
if (allocation_result.status == ZX_OK &&
!allocation_result.buffer_collection_info) {
// Everything is ok so far, but allocation isn't done yet.
return;
}
while (!pending_wait_for_buffers_allocated_.empty()) {
std::unique_ptr<BindingType::Txn> txn =
std::move(pending_wait_for_buffers_allocated_.front());
pending_wait_for_buffers_allocated_.pop_front();
BufferCollectionInfo to_send(BufferCollectionInfo::Default);
ZX_DEBUG_ASSERT(allocation_result.buffer_collection_info ||
allocation_result.status != ZX_OK);
if (allocation_result.buffer_collection_info) {
to_send = BufferCollectionInfoClone(
allocation_result.buffer_collection_info);
if (!to_send) {
// FailAsync() has already been run by the Clone()
return;
}
}
// Ownership of handles in to_send are transferred to _reply().
zx_status_t reply_status =
fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated_reply(
&txn->raw_txn(), allocation_result.status, to_send.release());
if (reply_status != ZX_OK) {
FailAsync(reply_status,
"fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated_"
"reply failed - status: %d",
reply_status);
return;
}
// ~txn
}
}
BufferCollection::BufferCollectionInfo
BufferCollection::BufferCollectionInfoClone(
const fuchsia_sysmem_BufferCollectionInfo_2* buffer_collection_info) {
// We must not set handles in here that we don't own. This means any
// sending of handles involves duplicating handles first, not just assigning
// a handle value into the |to_send| struct.
BufferCollectionInfo clone(BufferCollectionInfo::Default);
// We don't close the handles in temp on returning from this method. Those
// handles don't belong to this method call, they belong to the caller.
//
// struct copy
fuchsia_sysmem_BufferCollectionInfo_2 temp = *buffer_collection_info;
// Zero the handles in temp so we can copy the rest of temp into to_send
// without putting any handles in to_send yet (as we haven't duplicated any
// handles yet). There isn't any fidl_zero_handles() and
// fidl::internal::BufferWalker isn't meant to be used as a lib, so do this
// manually for now.
for (auto& vmo_buffer : temp.buffers) {
if (vmo_buffer.vmo == ZX_HANDLE_INVALID) {
// All the rest are already 0, so we can stop here.
break;
}
vmo_buffer.vmo = ZX_HANDLE_INVALID;
}
// Now we can copy the data of |temp| into to_send without owning handles we
// haven't duplicated yet.
//
// struct copy
*clone.get() = temp;
if (!constraints_) {
// No VMO handles should be copied in this case.
return clone;
}
// We duplicate the handles in buffer_collection_info into to_send, so that
// if we fail mid-way we'll still remember to close the non-sent duplicates.
for (uint32_t i = 0; i < countof(buffer_collection_info->buffers); ++i) {
if (buffer_collection_info->buffers[i].vmo == ZX_HANDLE_INVALID) {
// The rest are ZX_HANDLE_INVALID also.
break;
}
zx::vmo handle_to_send;
zx_status_t duplicate_status = zx_handle_duplicate(
buffer_collection_info->buffers[i].vmo, GetClientVmoRights(),
handle_to_send.reset_and_get_address());
if (duplicate_status != ZX_OK) {
// We fail the BufferCollection view with FailAsync(), which the
// LogicalBufferCollection will likely handle by failing the whole
// LogicalBufferCollection. However, the LogicalBufferCollection is
// still permitted to delete |this| before FailAsync() is fully done
// - that possibility is handled cleanly by FailAsync() code.
//
// The important thing here is that by failing async, this
// particular stack doesn't have to check whether |this| still
// exists, or whether LogicalBufferCollection still exists.
FailAsync(duplicate_status,
"BufferCollection::OnBuffersAllocated() "
"zx::vmo::duplicate() failed - status: %d",
duplicate_status);
return BufferCollectionInfo(BufferCollectionInfo::Null);
}
// Transfer ownership of owned handle directly from zx::vmo to
// FidlStruct, in a way that can't fail.
clone->buffers[i].vmo = handle_to_send.release();
}
return clone;
}