blob: d7e3820f90f39ace78d8466be628b0cd82e08327 [file] [log] [blame]
// Copyright 2017 The Chromium 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 "components/arc/video_accelerator/protected_buffer_manager.h"
#include <utility>
#include "base/bind.h"
#include "base/bits.h"
#include "base/logging.h"
#include "base/memory/platform_shared_memory_region.h"
#include "base/posix/eintr_wrapper.h"
#include "base/system/sys_info.h"
#include "base/threading/thread_checker.h"
#include "components/arc/video_accelerator/protected_buffer_allocator.h"
#include "media/gpu/macros.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "ui/gfx/geometry/size.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/surface_factory_ozone.h"
namespace arc {
namespace {
// Size of the pixmap to be used as the dummy handle for protected buffers.
constexpr gfx::Size kDummyBufferSize(16, 16);
// Maximum number of concurrent ProtectedBufferAllocatorImpl instances.
// Currently we have no way to know the resources of ProtectedBufferAllocator.
// Arbitrarily chosen a reasonable constant as the limit.
constexpr size_t kMaxConcurrentProtectedBufferAllocators = 32;
// Maximum number of concurrent allocated protected buffers in a single
// ProtectedBufferAllocator. This limitation, 64 is arbitrarily chosen.
constexpr size_t kMaxBuffersPerAllocator = 64;
} // namespace
class ProtectedBufferManager::ProtectedBuffer {
public:
virtual ~ProtectedBuffer() {}
// Downcasting methods to return duplicated handles to the underlying
// protected buffers for each buffer type, or empty/null handles if not
// applicable.
virtual base::subtle::PlatformSharedMemoryRegion
DuplicatePlatformSharedMemoryRegion() const {
return {};
}
virtual gfx::NativePixmapHandle DuplicateNativePixmapHandle() const {
return gfx::NativePixmapHandle();
}
// Downcasting method to return a scoped_refptr to the underlying
// NativePixmap, or null if not applicable.
virtual scoped_refptr<gfx::NativePixmap> GetNativePixmap() const {
return nullptr;
}
protected:
explicit ProtectedBuffer(scoped_refptr<gfx::NativePixmap> dummy_handle)
: dummy_handle_(std::move(dummy_handle)) {}
private:
scoped_refptr<gfx::NativePixmap> dummy_handle_;
DISALLOW_COPY_AND_ASSIGN(ProtectedBuffer);
};
class ProtectedBufferManager::ProtectedSharedMemory
: public ProtectedBufferManager::ProtectedBuffer {
public:
~ProtectedSharedMemory() override;
// Allocate a ProtectedSharedMemory buffer of |size| bytes.
static std::unique_ptr<ProtectedSharedMemory> Create(
scoped_refptr<gfx::NativePixmap> dummy_handle,
size_t size);
base::subtle::PlatformSharedMemoryRegion DuplicatePlatformSharedMemoryRegion()
const override {
return region_.Duplicate();
}
private:
explicit ProtectedSharedMemory(scoped_refptr<gfx::NativePixmap> dummy_handle);
base::subtle::PlatformSharedMemoryRegion region_;
};
ProtectedBufferManager::ProtectedSharedMemory::ProtectedSharedMemory(
scoped_refptr<gfx::NativePixmap> dummy_handle)
: ProtectedBuffer(std::move(dummy_handle)) {}
ProtectedBufferManager::ProtectedSharedMemory::~ProtectedSharedMemory() {}
// static
std::unique_ptr<ProtectedBufferManager::ProtectedSharedMemory>
ProtectedBufferManager::ProtectedSharedMemory::Create(
scoped_refptr<gfx::NativePixmap> dummy_handle,
size_t size) {
std::unique_ptr<ProtectedSharedMemory> protected_shmem(
new ProtectedSharedMemory(std::move(dummy_handle)));
size_t aligned_size =
base::bits::Align(size, base::SysInfo::VMAllocationGranularity());
auto shmem_region = mojo::CreateUnsafeSharedMemoryRegion(aligned_size);
if (!shmem_region.IsValid()) {
VLOGF(1) << "Failed to allocate shared memory";
return nullptr;
}
protected_shmem->region_ =
base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(shmem_region));
return protected_shmem;
}
class ProtectedBufferManager::ProtectedNativePixmap
: public ProtectedBufferManager::ProtectedBuffer {
public:
~ProtectedNativePixmap() override;
// Allocate a ProtectedNativePixmap of |format| and |size|.
static std::unique_ptr<ProtectedNativePixmap> Create(
scoped_refptr<gfx::NativePixmap> dummy_handle,
gfx::BufferFormat format,
const gfx::Size& size);
gfx::NativePixmapHandle DuplicateNativePixmapHandle() const override {
return native_pixmap_->ExportHandle();
}
scoped_refptr<gfx::NativePixmap> GetNativePixmap() const override {
return native_pixmap_;
}
private:
explicit ProtectedNativePixmap(scoped_refptr<gfx::NativePixmap> dummy_handle);
scoped_refptr<gfx::NativePixmap> native_pixmap_;
};
ProtectedBufferManager::ProtectedNativePixmap::ProtectedNativePixmap(
scoped_refptr<gfx::NativePixmap> dummy_handle)
: ProtectedBuffer(std::move(dummy_handle)) {}
ProtectedBufferManager::ProtectedNativePixmap::~ProtectedNativePixmap() {}
// static
std::unique_ptr<ProtectedBufferManager::ProtectedNativePixmap>
ProtectedBufferManager::ProtectedNativePixmap::Create(
scoped_refptr<gfx::NativePixmap> dummy_handle,
gfx::BufferFormat format,
const gfx::Size& size) {
std::unique_ptr<ProtectedNativePixmap> protected_pixmap(
new ProtectedNativePixmap(std::move(dummy_handle)));
ui::OzonePlatform* platform = ui::OzonePlatform::GetInstance();
ui::SurfaceFactoryOzone* factory = platform->GetSurfaceFactoryOzone();
protected_pixmap->native_pixmap_ = factory->CreateNativePixmap(
gfx::kNullAcceleratedWidget, VK_NULL_HANDLE, size, format,
gfx::BufferUsage::SCANOUT_VDA_WRITE);
if (!protected_pixmap->native_pixmap_) {
VLOGF(1) << "Failed allocating a native pixmap";
return nullptr;
}
return protected_pixmap;
}
// The ProtectedBufferAllocator implementation using ProtectedBufferManager.
// The functions just delegate the corresponding functions of
// ProtectedBufferManager.
// This destructor executes the callback to release the references of all non
// released protected buffers.
class ProtectedBufferManager::ProtectedBufferAllocatorImpl
: public ProtectedBufferAllocator {
public:
ProtectedBufferAllocatorImpl(
uint64_t allocator_id,
scoped_refptr<ProtectedBufferManager> protected_buffer_manager,
base::OnceClosure release_all_protected_buffers_cb);
~ProtectedBufferAllocatorImpl() override;
bool AllocateProtectedSharedMemory(base::ScopedFD dummy_fd,
size_t size) override;
bool AllocateProtectedNativePixmap(base::ScopedFD dummy_fd,
gfx::BufferFormat format,
const gfx::Size& size) override;
void ReleaseProtectedBuffer(base::ScopedFD dummy_fd) override;
private:
const uint64_t allocator_id_;
const scoped_refptr<ProtectedBufferManager> protected_buffer_manager_;
base::OnceClosure release_all_protected_buffers_cb_;
THREAD_CHECKER(thread_checker_);
DISALLOW_COPY_AND_ASSIGN(ProtectedBufferAllocatorImpl);
};
ProtectedBufferManager::ProtectedBufferAllocatorImpl::
ProtectedBufferAllocatorImpl(
uint64_t allocator_id,
scoped_refptr<ProtectedBufferManager> protected_buffer_manager,
base::OnceClosure release_all_protected_buffers_cb)
: allocator_id_(allocator_id),
protected_buffer_manager_(std::move(protected_buffer_manager)),
release_all_protected_buffers_cb_(
std::move(release_all_protected_buffers_cb)) {}
ProtectedBufferManager::ProtectedBufferAllocatorImpl::
~ProtectedBufferAllocatorImpl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(release_all_protected_buffers_cb_);
std::move(release_all_protected_buffers_cb_).Run();
}
bool ProtectedBufferManager::ProtectedBufferAllocatorImpl::
AllocateProtectedSharedMemory(base::ScopedFD dummy_fd, size_t size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return protected_buffer_manager_->AllocateProtectedSharedMemory(
allocator_id_, std::move(dummy_fd), size);
}
bool ProtectedBufferManager::ProtectedBufferAllocatorImpl::
AllocateProtectedNativePixmap(base::ScopedFD dummy_fd,
gfx::BufferFormat format,
const gfx::Size& size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return protected_buffer_manager_->AllocateProtectedNativePixmap(
allocator_id_, std::move(dummy_fd), format, size);
}
void ProtectedBufferManager::ProtectedBufferAllocatorImpl::
ReleaseProtectedBuffer(base::ScopedFD dummy_fd) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
protected_buffer_manager_->ReleaseProtectedBuffer(allocator_id_,
std::move(dummy_fd));
}
ProtectedBufferManager::ProtectedBufferManager()
: next_protected_buffer_allocator_id_(0) {
VLOGF(2);
}
ProtectedBufferManager::~ProtectedBufferManager() {
VLOGF(2);
}
bool ProtectedBufferManager::GetAllocatorId(uint64_t* const allocator_id) {
base::AutoLock lock(buffer_map_lock_);
if (allocator_to_buffers_map_.size() ==
kMaxConcurrentProtectedBufferAllocators) {
VLOGF(1) << "Failed Creating PBA due to many ProtectedBufferAllocators: "
<< kMaxConcurrentProtectedBufferAllocators;
return false;
}
*allocator_id = next_protected_buffer_allocator_id_;
allocator_to_buffers_map_.insert({*allocator_id, {}});
next_protected_buffer_allocator_id_++;
return true;
}
// static
std::unique_ptr<ProtectedBufferAllocator>
ProtectedBufferManager::CreateProtectedBufferAllocator(
scoped_refptr<ProtectedBufferManager> protected_buffer_manager) {
uint64_t allocator_id = 0;
if (!protected_buffer_manager->GetAllocatorId(&allocator_id))
return nullptr;
return std::make_unique<ProtectedBufferAllocatorImpl>(
allocator_id, protected_buffer_manager,
base::BindOnce(&ProtectedBufferManager::ReleaseAllProtectedBuffers,
protected_buffer_manager, allocator_id));
}
bool ProtectedBufferManager::AllocateProtectedSharedMemory(
uint64_t allocator_id,
base::ScopedFD dummy_fd,
size_t size) {
VLOGF(2) << "allocator_id:" << allocator_id
<< ", dummy_fd: " << dummy_fd.get() << ", size: " << size;
// Import the |dummy_fd| to produce a unique id for it.
uint32_t id;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
if (!pixmap)
return false;
base::AutoLock lock(buffer_map_lock_);
if (!CanAllocateFor(allocator_id, id))
return false;
// Allocate a protected buffer and associate it with the dummy pixmap.
// The pixmap needs to be stored to ensure the id remains the same for
// the entire lifetime of the dummy pixmap.
auto protected_shmem = ProtectedSharedMemory::Create(pixmap, size);
if (!protected_shmem) {
VLOGF(1) << "Failed allocating a protected shared memory buffer";
return false;
}
if (!allocator_to_buffers_map_[allocator_id].insert(id).second) {
VLOGF(1) << "Failed inserting id: " << id
<< " to allocator_to_buffers_map_, allocator_id: " << allocator_id;
return false;
}
// This will always succeed as we find() first in CanAllocateFor().
buffer_map_.emplace(id, std::move(protected_shmem));
VLOGF(2) << "New protected shared memory buffer, handle id: " << id;
return true;
}
bool ProtectedBufferManager::AllocateProtectedNativePixmap(
uint64_t allocator_id,
base::ScopedFD dummy_fd,
gfx::BufferFormat format,
const gfx::Size& size) {
VLOGF(2) << "allocator_id: " << allocator_id
<< ", dummy_fd: " << dummy_fd.get()
<< ", format: " << static_cast<int>(format)
<< ", size: " << size.ToString();
// Import the |dummy_fd| to produce a unique id for it.
uint32_t id = 0;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
if (!pixmap)
return false;
base::AutoLock lock(buffer_map_lock_);
if (!CanAllocateFor(allocator_id, id))
return false;
// Allocate a protected buffer and associate it with the dummy pixmap.
// The pixmap needs to be stored to ensure the id remains the same for
// the entire lifetime of the dummy pixmap.
auto protected_pixmap = ProtectedNativePixmap::Create(pixmap, format, size);
if (!protected_pixmap) {
VLOGF(1) << "Failed allocating a protected native pixmap";
return false;
}
if (!allocator_to_buffers_map_[allocator_id].insert(id).second) {
VLOGF(1) << "Failed inserting id: " << id
<< " to allocator_to_buffers_map_, allocator_id: " << allocator_id;
return false;
}
// This will always succeed as we find() first in CanAllocateFor().
buffer_map_.emplace(id, std::move(protected_pixmap));
VLOGF(2) << "New protected native pixmap, handle id: " << id;
return true;
}
void ProtectedBufferManager::ReleaseProtectedBuffer(uint64_t allocator_id,
base::ScopedFD dummy_fd) {
VLOGF(2) << "allocator_id: " << allocator_id
<< ", dummy_fd: " << dummy_fd.get();
// Import the |dummy_fd| to produce a unique id for it.
uint32_t id = 0;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
if (!pixmap)
return;
base::AutoLock lock(buffer_map_lock_);
RemoveEntry(id);
auto it = allocator_to_buffers_map_.find(allocator_id);
if (it == allocator_to_buffers_map_.end()) {
VLOGF(1) << "No allocated buffer by allocator id " << allocator_id;
return;
}
if (it->second.erase(id) != 1)
VLOGF(1) << "No buffer id " << id << " to destroy";
}
void ProtectedBufferManager::ReleaseAllProtectedBuffers(uint64_t allocator_id) {
base::AutoLock lock(buffer_map_lock_);
auto it = allocator_to_buffers_map_.find(allocator_id);
if (it == allocator_to_buffers_map_.end())
return;
for (const uint32_t id : it->second) {
RemoveEntry(id);
}
allocator_to_buffers_map_.erase(allocator_id);
}
base::subtle::PlatformSharedMemoryRegion
ProtectedBufferManager::GetProtectedSharedMemoryRegionFor(
base::ScopedFD dummy_fd) {
uint32_t id = 0;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
base::AutoLock lock(buffer_map_lock_);
const auto& iter = buffer_map_.find(id);
if (iter == buffer_map_.end())
return {};
return iter->second->DuplicatePlatformSharedMemoryRegion();
}
gfx::NativePixmapHandle
ProtectedBufferManager::GetProtectedNativePixmapHandleFor(
base::ScopedFD dummy_fd) {
uint32_t id = 0;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
base::AutoLock lock(buffer_map_lock_);
const auto& iter = buffer_map_.find(id);
if (iter == buffer_map_.end())
return gfx::NativePixmapHandle();
return iter->second->DuplicateNativePixmapHandle();
}
scoped_refptr<gfx::NativePixmap>
ProtectedBufferManager::GetProtectedNativePixmapFor(
const gfx::NativePixmapHandle& handle) {
// Only the first fd is used for lookup.
if (handle.planes.empty())
return nullptr;
base::ScopedFD dummy_fd(HANDLE_EINTR(dup(handle.planes[0].fd.get())));
uint32_t id = 0;
auto pixmap = ImportDummyFd(std::move(dummy_fd), &id);
base::AutoLock lock(buffer_map_lock_);
const auto& iter = buffer_map_.find(id);
if (iter == buffer_map_.end())
return nullptr;
return iter->second->GetNativePixmap();
}
scoped_refptr<gfx::NativePixmap> ProtectedBufferManager::ImportDummyFd(
base::ScopedFD dummy_fd,
uint32_t* id) const {
// 0 is an invalid handle id.
*id = 0;
// Import dummy_fd to acquire its unique id.
// CreateNativePixmapFromHandle() takes ownership and will close the handle
// also on failure.
gfx::NativePixmapHandle pixmap_handle;
pixmap_handle.planes.emplace_back(
gfx::NativePixmapPlane(0, 0, 0, std::move(dummy_fd)));
ui::OzonePlatform* platform = ui::OzonePlatform::GetInstance();
ui::SurfaceFactoryOzone* factory = platform->GetSurfaceFactoryOzone();
scoped_refptr<gfx::NativePixmap> pixmap =
factory->CreateNativePixmapForProtectedBufferHandle(
gfx::kNullAcceleratedWidget, kDummyBufferSize,
gfx::BufferFormat::RGBA_8888, std::move(pixmap_handle));
if (!pixmap) {
VLOGF(1) << "Failed importing dummy handle";
return nullptr;
}
*id = pixmap->GetUniqueId();
if (*id == 0) {
VLOGF(1) << "Failed acquiring unique id for handle";
return nullptr;
}
return pixmap;
}
void ProtectedBufferManager::RemoveEntry(uint32_t id) {
VLOGF(2) << "id: " << id;
auto num_erased = buffer_map_.erase(id);
if (num_erased != 1)
VLOGF(1) << "No buffer id " << id << " to destroy";
}
bool ProtectedBufferManager::CanAllocateFor(uint64_t allocator_id,
uint32_t id) {
if (buffer_map_.find(id) != buffer_map_.end()) {
VLOGF(1) << "A protected buffer for this handle already exists";
return false;
}
auto it = allocator_to_buffers_map_.find(allocator_id);
if (it == allocator_to_buffers_map_.end()) {
VLOGF(1) << "allocator_to_buffers_map_ has no entry, allocator_id="
<< allocator_id;
return false;
}
auto& allocated_protected_buffer_ids = it->second;
// Check if the number of allocated protected buffers for |allocator_id| is
// less than kMaxBuffersPerAllocator.
if (allocated_protected_buffer_ids.size() >= kMaxBuffersPerAllocator) {
VLOGF(1) << "Too many allocated protected buffers: "
<< kMaxBuffersPerAllocator;
return false;
}
return true;
}
} // namespace arc