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

#include "scoped_bo_mapping_factory.h"

#include <linux/dma-buf.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

namespace libvafake {

void GbmDeviceDeleter::operator()(gbm_device* device) {
  if (device)
    gbm_device_destroy(device);
}

ScopedBOMapping::ScopedAccess::ScopedAccess(const ScopedBOMapping& mapping)
    : mapping_(mapping) {
  for (const auto& plane : mapping_.planes_) {
    struct dma_buf_sync sync_start;
    memset(&sync_start, 0, sizeof(sync_start));
    sync_start.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;

    // This will fail for the fake GBM backend, so ignore the return result. We
    // leave the IOCTL in here anyway in case we're running on top of a real GEM
    // driver.
    HANDLE_EINTR(ioctl(plane.prime_fd.get(), DMA_BUF_IOCTL_SYNC, &sync_start));
  }
}

ScopedBOMapping::ScopedAccess::~ScopedAccess() {
  for (const auto& plane : mapping_.planes_) {
    struct dma_buf_sync sync_end;
    memset(&sync_end, 0, sizeof(sync_end));
    sync_end.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;
    HANDLE_EINTR(ioctl(plane.prime_fd.get(), DMA_BUF_IOCTL_SYNC, &sync_end));
  }
}

uint8_t* ScopedBOMapping::ScopedAccess::GetData(size_t plane) const {
  CHECK_LT(plane, mapping_.planes_.size());
  return static_cast<uint8_t*>(mapping_.planes_[plane].addr);
}

uint32_t ScopedBOMapping::ScopedAccess::GetStride(size_t plane) const {
  CHECK_LT(plane, mapping_.planes_.size());
  return mapping_.planes_[plane].stride;
}

ScopedBOMapping::ScopedBOMapping()
    : scoped_bo_mapping_factory_(nullptr), bo_import_(nullptr) {}

ScopedBOMapping::ScopedBOMapping(
    ScopedBOMappingFactory* scoped_bo_mapping_factory,
    std::vector<Plane> planes,
    struct gbm_bo* bo_import)
    : scoped_bo_mapping_factory_(scoped_bo_mapping_factory),
      planes_(std::move(planes)),
      bo_import_(bo_import) {}

ScopedBOMapping::ScopedBOMapping(ScopedBOMapping&& other)
    : scoped_bo_mapping_factory_(std::move(other.scoped_bo_mapping_factory_)),
      planes_(std::move(other.planes_)),
      bo_import_(std::move(other.bo_import_)) {
  // Note: we explicitly set these members to nullptr because a raw_ptr<T> may
  // or may not be zeroed out on move (it depends on the build configuration).
  other.scoped_bo_mapping_factory_ = nullptr;
  other.bo_import_ = nullptr;
}

ScopedBOMapping& ScopedBOMapping::operator=(ScopedBOMapping&& other) {
  // Note: we explicitly set |other.scoped_bo_mapping_factory_| and
  // |other.bo_import_| to nullptr because a raw_ptr<T> may or may not be zeroed
  // out on move (it depends on the build configuration).
  scoped_bo_mapping_factory_ = std::move(other.scoped_bo_mapping_factory_);
  other.scoped_bo_mapping_factory_ = nullptr;

  planes_ = std::move(other.planes_);

  bo_import_ = std::move(other.bo_import_);
  other.bo_import_ = nullptr;

  return *this;
}

ScopedBOMapping::~ScopedBOMapping() {
  if (IsValid()) {
    // We tell the factory to unmap and destroy the Buffer Object as it
    // does so under a lock which cannot be done here.
    scoped_bo_mapping_factory_->UnmapAndDestroyBufferObject(std::move(planes_),
                                                            bo_import_);
  }
}

ScopedBOMapping::Plane::Plane(uint32_t stride,
                              void* addr,
                              void* mmap_data,
                              int prime_fd)
    : stride(stride), addr(addr), mmap_data(mmap_data), prime_fd(prime_fd) {}

ScopedBOMapping::Plane::Plane(Plane&& other)
    : stride(other.stride),
      addr(std::move(other.addr)),
      mmap_data(std::move(other.mmap_data)),
      prime_fd(std::move(other.prime_fd)) {
  other.stride = 0u;

  // Note: we explicitly set these members to nullptr because a raw_ptr<T> may
  // or may not be zeroed out on move (it depends on the build configuration).
  other.addr = nullptr;
  other.mmap_data = nullptr;
}

ScopedBOMapping::Plane& ScopedBOMapping::Plane::operator=(
    ScopedBOMapping::Plane&& other) {
  stride = other.stride;
  other.stride = 0u;

  // Note: we explicitly set |other.addr| and |other.mmap_data| to nullptr
  // because a raw_ptr<T> may or may not be zeroed out on move (it depends on
  // the build configuration).
  addr = std::move(other.addr);
  other.addr = nullptr;

  mmap_data = std::move(other.mmap_data);
  other.mmap_data = nullptr;

  prime_fd = std::move(other.prime_fd);

  return *this;
}

ScopedBOMapping::Plane::~Plane() = default;

ScopedBOMapping::ScopedAccess ScopedBOMapping::BeginAccess() const {
  CHECK(IsValid());
  return ScopedBOMapping::ScopedAccess(*this);
}

ScopedBOMappingFactory::ScopedBOMappingFactory(int drm_fd)
    : gbm_device_(gbm_create_device(drm_fd)) {
  CHECK_GE(drm_fd, 0);
  CHECK(gbm_device_);
}

ScopedBOMappingFactory::~ScopedBOMappingFactory() = default;

ScopedBOMapping ScopedBOMappingFactory::Create(
    gbm_import_fd_modifier_data import_data) {
  const std::lock_guard<std::mutex> lock(lock_);
  struct gbm_bo* bo_import =
      gbm_bo_import(gbm_device_.get(), GBM_BO_IMPORT_FD_MODIFIER, &import_data,
                    GBM_BO_USE_SW_READ_OFTEN | GBM_BO_USE_SW_WRITE_OFTEN);
  CHECK(bo_import);

  std::vector<ScopedBOMapping::Plane> planes;
  for (int plane = 0; plane < gbm_bo_get_plane_count(bo_import); plane++) {
    uint32_t stride;
    void* mmap_data;
    void* addr =
        gbm_bo_map2(bo_import, /*x=*/0, /*y=*/0, gbm_bo_get_width(bo_import),
                    gbm_bo_get_height(bo_import), GBM_BO_TRANSFER_READ_WRITE,
                    &stride, &mmap_data, plane);
    CHECK_NE(addr, MAP_FAILED);
    CHECK_GE(stride, 0u);
    CHECK(mmap_data);

    const int prime_fd = gbm_bo_get_fd_for_plane(bo_import, plane);
    CHECK_GE(prime_fd, 0);

    planes.emplace_back(stride, addr, mmap_data, prime_fd);
  }
  return ScopedBOMapping(this, std::move(planes), bo_import);
}

void ScopedBOMappingFactory::UnmapAndDestroyBufferObject(
    std::vector<ScopedBOMapping::Plane> planes,
    struct gbm_bo* bo_import) {
  const std::lock_guard<std::mutex> lock(lock_);
  CHECK(bo_import);
  CHECK(gbm_device_);
  for (const auto& plane : planes) {
    CHECK(plane.mmap_data);
    gbm_bo_unmap(bo_import, plane.mmap_data);
  }

  // Note that calling gbm_bo_destroy() may not actually end up destroying the
  // buffer object. That's because minigbm is expected to do reference counting
  // of GEM handles. This is fine: suppose we create multiple ScopedBOMapping
  // instances for the same dma-buf; all of them should share the same GEM
  // handle which should only get destroyed when all the ScopedBOMappings
  // sharing that GEM handle are destroyed.
  gbm_bo_destroy(bo_import);
}

}  // namespace libvafake
