chromium/media/fuchsia/common/sysmem_client.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/fuchsia/common/sysmem_client.h"

#include <lib/sys/cpp/component_context.h>
#include <zircon/rights.h>

#include <algorithm>
#include <string_view>

#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/functional/bind.h"
#include "base/process/process_handle.h"
#include "media/fuchsia/common/vmo_buffer.h"

namespace media {

SysmemCollectionClient::SysmemCollectionClient(
    fuchsia::sysmem2::Allocator* allocator,
    fuchsia::sysmem2::BufferCollectionTokenPtr collection_token)
    : allocator_(allocator), collection_token_(std::move(collection_token)) {
  DCHECK(allocator_);
  DCHECK(collection_token_);
}

SysmemCollectionClient::~SysmemCollectionClient() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (collection_)
    collection_->Release();
}

void SysmemCollectionClient::Initialize(
    fuchsia::sysmem2::BufferCollectionConstraints constraints,
    std::string_view name,
    uint32_t name_priority) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  uint32_t cpu = 0;
  if (constraints.has_usage() && constraints.usage().has_cpu()) {
    cpu = constraints.usage().cpu();
  }
  writable_ = (cpu & fuchsia::sysmem2::CPU_USAGE_WRITE) ==
              fuchsia::sysmem2::CPU_USAGE_WRITE;

  allocator_->BindSharedCollection(
      std::move(fuchsia::sysmem2::AllocatorBindSharedCollectionRequest{}
                    .set_token(std::move(collection_token_))
                    .set_buffer_collection_request(collection_.NewRequest())));

  collection_.set_error_handler(
      fit::bind_member(this, &SysmemCollectionClient::OnError));
  collection_->SetName(std::move(fuchsia::sysmem2::NodeSetNameRequest{}
                                     .set_priority(name_priority)
                                     .set_name(std::string(name))));

  // We may need to send a Sync to ensure previously-started CreateSharedToken()
  // calls can complete. The Sync completion is how we know that sysmem knows
  // about the existence of the tokens created by the CreateSharedToken() calls,
  // which is needed before we can send the token to a different participant.
  //
  // CreateSharedToken can complete as soon as this Sync is done.
  if (!shared_token_ready_closures_.empty()) {
    collection_->Sync(
        fit::bind_member(this, &SysmemCollectionClient::OnSyncComplete));
  }

  collection_->SetConstraints(std::move(
      fuchsia::sysmem2::BufferCollectionSetConstraintsRequest{}.set_constraints(
          std::move(constraints))));
}

void SysmemCollectionClient::CreateSharedToken(
    GetSharedTokenCB cb,
    std::string_view debug_client_name,
    uint64_t debug_client_id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(collection_token_);

  fuchsia::sysmem2::BufferCollectionTokenPtr token;
  collection_token_->Duplicate(
      std::move(fuchsia::sysmem2::BufferCollectionTokenDuplicateRequest{}
                    .set_rights_attenuation_mask(ZX_RIGHT_SAME_RIGHTS)
                    .set_token_request(token.NewRequest())));

  if (!debug_client_name.empty()) {
    token->SetDebugClientInfo(
        std::move(fuchsia::sysmem2::NodeSetDebugClientInfoRequest{}
                      .set_name(std::string(debug_client_name))
                      .set_id(debug_client_id)));
  }

  shared_token_ready_closures_.push_back(
      base::BindOnce(std::move(cb), std::move(token)));
}

void SysmemCollectionClient::AcquireBuffers(AcquireBuffersCB cb) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!collection_token_);

  if (!collection_) {
    std::move(cb).Run({}, {});
    return;
  }

  acquire_buffers_cb_ = std::move(cb);
  collection_->WaitForAllBuffersAllocated(
      fit::bind_member(this, &SysmemCollectionClient::OnBuffersAllocated));
}

void SysmemCollectionClient::OnSyncComplete(
    fuchsia::sysmem2::Node_Sync_Result sync_result) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  std::vector<base::OnceClosure> sync_closures =
      std::move(shared_token_ready_closures_);
  for (auto& cb : sync_closures) {
    std::move(cb).Run();
  }
}

void SysmemCollectionClient::OnBuffersAllocated(
    fuchsia::sysmem2::BufferCollection_WaitForAllBuffersAllocated_Result
        wait_result) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (!wait_result.is_response()) {
    zx_status_t error_status;
    if (wait_result.is_framework_err()) {
      ZX_LOG(ERROR, fidl::ToUnderlying(wait_result.framework_err()))
          << "Failed to allocate sysmem buffers (framework_err).";
      error_status = ZX_ERR_INTERNAL;
    } else {
      ZX_LOG(ERROR, static_cast<uint32_t>(wait_result.err()))
          << "Failed to allocate sysmem buffers (err).";
      // no real need to translate from sysmem2::Error to zx_status_t here
      error_status = ZX_ERR_INTERNAL;
    }
    OnError(error_status);
    return;
  }
  auto buffer_collection_info =
      std::move(*wait_result.response().mutable_buffer_collection_info());

  if (acquire_buffers_cb_) {
    auto buffers = VmoBuffer::CreateBuffersFromSysmemCollection(
        &buffer_collection_info, writable_);
    std::move(acquire_buffers_cb_)
        .Run(std::move(buffers), buffer_collection_info.settings());
  }
}

void SysmemCollectionClient::OnError(zx_status_t status) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  ZX_DLOG(ERROR, status) << "Connection to BufferCollection was disconnected.";
  collection_.Unbind();
  if (acquire_buffers_cb_)
    std::move(acquire_buffers_cb_).Run({}, {});
}

SysmemAllocatorClient::SysmemAllocatorClient(std::string_view client_name) {
  allocator_ = base::ComponentContextForProcess()
                   ->svc()
                   ->Connect<fuchsia::sysmem2::Allocator>();

  allocator_->SetDebugClientInfo(
      std::move(fuchsia::sysmem2::AllocatorSetDebugClientInfoRequest{}
                    .set_name(std::string(client_name))
                    .set_id(base::GetCurrentProcId())));

  allocator_.set_error_handler([](zx_status_t status) {
    // Just log a warning. We will handle BufferCollection the failure when
    // trying to create a new BufferCollection.
    ZX_DLOG(WARNING, status)
        << "The fuchsia.sysmem.Allocator channel was disconnected.";
  });
}

SysmemAllocatorClient::~SysmemAllocatorClient() = default;

fuchsia::sysmem2::BufferCollectionTokenPtr
SysmemAllocatorClient::CreateNewToken() {
  fuchsia::sysmem2::BufferCollectionTokenPtr collection_token;
  allocator_->AllocateSharedCollection(
      std::move(fuchsia::sysmem2::AllocatorAllocateSharedCollectionRequest{}
                    .set_token_request(collection_token.NewRequest())));
  return collection_token;
}

std::unique_ptr<SysmemCollectionClient>
SysmemAllocatorClient::AllocateNewCollection() {
  return std::make_unique<SysmemCollectionClient>(allocator_.get(),
                                                  CreateNewToken());
}

std::unique_ptr<SysmemCollectionClient>
SysmemAllocatorClient::BindSharedCollection(
    fuchsia::sysmem2::BufferCollectionTokenPtr token) {
  return std::make_unique<SysmemCollectionClient>(allocator_.get(),
                                                  std::move(token));
}

}  // namespace media