chromium/media/fuchsia/common/vmo_buffer.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

#include <zircon/rights.h>
#include <algorithm>

#include "base/bits.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/memory/page_size.h"

namespace media {

// static
fuchsia::sysmem2::BufferCollectionConstraints
VmoBuffer::GetRecommendedConstraints(size_t min_buffer_count,
                                     std::optional<size_t> min_buffer_size,
                                     bool writable) {
  fuchsia::sysmem2::BufferCollectionConstraints constraints;

  constraints.mutable_usage()->set_cpu(fuchsia::sysmem2::CPU_USAGE_READ);
  if (writable) {
    *constraints.mutable_usage()->mutable_cpu() |=
        fuchsia::sysmem2::CPU_USAGE_WRITE;
  }

  constraints.set_min_buffer_count(min_buffer_count);

  if (min_buffer_size.has_value()) {
    auto& memory_constraints = *constraints.mutable_buffer_memory_constraints();
    memory_constraints.set_min_size_bytes(min_buffer_size.value());
    memory_constraints.set_ram_domain_supported(true);
    memory_constraints.set_cpu_domain_supported(true);
  }

  return constraints;
}

// static
std::vector<VmoBuffer> VmoBuffer::CreateBuffersFromSysmemCollection(
    fuchsia::sysmem2::BufferCollectionInfo* info,
    bool writable) {
  std::vector<VmoBuffer> buffers;
  buffers.resize(info->buffers().size());

  const fuchsia::sysmem2::BufferMemorySettings& settings =
      info->settings().buffer_settings();
  for (size_t i = 0; i < info->buffers().size(); ++i) {
    fuchsia::sysmem2::VmoBuffer& buffer = info->mutable_buffers()->at(i);
    if (!buffers[i].Initialize(std::move(*buffer.mutable_vmo()), writable,
                               buffer.vmo_usable_start(), settings.size_bytes(),
                               settings.coherency_domain())) {
      return {};
    }
  }

  return buffers;
}

VmoBuffer::VmoBuffer() = default;

VmoBuffer::~VmoBuffer() {
  if (!base_address_) {
    return;
  }

  zx_status_t status = zx::vmar::root_self()->unmap(
      reinterpret_cast<uintptr_t>(base_address_), mapped_size());
  ZX_DCHECK(status == ZX_OK, status) << "zx_vmar_unmap";
}

VmoBuffer::VmoBuffer(VmoBuffer&&) = default;
VmoBuffer& VmoBuffer::operator=(VmoBuffer&&) = default;

bool VmoBuffer::Initialize(zx::vmo vmo,
                           bool writable,
                           size_t offset,
                           size_t size,
                           fuchsia::sysmem2::CoherencyDomain coherency_domain) {
  DCHECK(!base_address_);
  DCHECK(vmo);

  writable_ = writable;
  offset_ = offset;
  size_ = size;
  coherency_domain_ = coherency_domain;

  zx_vm_option_t options = ZX_VM_PERM_READ;
  if (writable)
    options |= ZX_VM_PERM_WRITE;

  uintptr_t addr;
  zx_status_t status =
      zx::vmar::root_self()->map(options, /*vmar_offset=*/0, vmo,
                                 /*vmo_offset=*/0, mapped_size(), &addr);
  if (status != ZX_OK) {
    ZX_DLOG(ERROR, status) << "zx_vmar_map";
    return false;
  }

  vmo_ = std::move(vmo);
  base_address_ = reinterpret_cast<uint8_t*>(addr);

  return true;
}

size_t VmoBuffer::Read(size_t offset, base::span<uint8_t> data) {
  if (offset >= size_)
    return 0U;
  size_t bytes_to_fill = std::min(size_ - offset, data.size());
  FlushCache(offset, bytes_to_fill, /*invalidate=*/true);
  memcpy(data.data(), base_address_ + offset_ + offset, bytes_to_fill);
  return bytes_to_fill;
}

size_t VmoBuffer::Write(base::span<const uint8_t> data) {
  DCHECK(writable_);

  size_t bytes_to_fill = std::min(size_, data.size());
  memcpy(base_address_ + offset_, data.data(), bytes_to_fill);

  FlushCache(0, bytes_to_fill, /*invalidate=*/false);

  return bytes_to_fill;
}

base::span<const uint8_t> VmoBuffer::GetMemory() {
  FlushCache(0, size_, /*invalidate=*/true);
  return base::make_span(base_address_ + offset_, size_);
}

base::span<uint8_t> VmoBuffer::GetWritableMemory() {
  DCHECK(writable_);
  return base::make_span(base_address_ + offset_, size_);
}

void VmoBuffer::FlushCache(size_t flush_offset,
                           size_t flush_size,
                           bool invalidate) {
  DCHECK_LE(flush_size, size_ - flush_offset);

  if (coherency_domain_ != fuchsia::sysmem2::CoherencyDomain::RAM) {
    return;
  }

  uint8_t* address = base_address_ + offset_ + flush_offset;
  uint32_t options = ZX_CACHE_FLUSH_DATA;
  if (invalidate)
    options |= ZX_CACHE_FLUSH_INVALIDATE;
  zx_status_t status = zx_cache_flush(address, flush_size, options);
  ZX_DCHECK(status == ZX_OK, status) << "zx_cache_flush";
}

size_t VmoBuffer::mapped_size() {
  return base::bits::AlignUp(offset_ + size_, base::GetPageSize());
}

zx::vmo VmoBuffer::Duplicate(bool writable) {
  zx_rights_t rights = ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ |
                       ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY;
  if (writable)
    rights |= ZX_RIGHT_WRITE;

  zx::vmo vmo;
  zx_status_t status = vmo_.duplicate(rights, &vmo);
  ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";

  return vmo;
}

}  // namespace media