blob: 54d3c1c81c80ddf6fa69bcfc7d730d3a132cdb7f [file] [log] [blame]
// Copyright 2018 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 <fbl/algorithm.h>
#include <lib/media/codec_impl/codec_buffer.h>
#include <lib/media/codec_impl/codec_impl.h>
#include <lib/media/codec_impl/codec_port.h>
#include <zircon/assert.h>
namespace {
void BarrierAfterFlush() {
#if defined(__aarch64__)
// According to the ARMv8 ARM K11.5.4 it's better to use DSB instead of DMB
// for ordering with respect to MMIO (DMB is ok if all agents are just
// observing memory). The system shareability domain is used because that's
// the only domain the video decoder is guaranteed to be in. SY is used
// instead of LD or ST because section B2.3.5 says that the barrier needs both
// read and write access types to be effective with regards to cache
// operations.
asm __volatile__("dsb sy");
#elif defined(__x86_64__)
// This is here just in case we both (a) don't need to flush cache on x86 due to cache coherent
// DMA (CLFLUSH not needed), and (b) we have code using non-temporal stores or "string
// operations" whose surrounding code didn't itself take care of doing an SFENCE. After returning
// from this function, we may write to MMIO to start DMA - we want any previous (program order)
// non-temporal stores to be visible to HW before that MMIO write that starts DMA. The MFENCE
// instead of SFENCE is mainly paranoia, though one could hypothetically create HW that starts or
// continues DMA based on an MMIO read (please don't), in which case MFENCE might be needed here
// before that read.
asm __volatile__("mfence");
#else
ZX_PANIC("codec_buffer.cc missing BarrierAfterFlush() impl for this platform");
#endif
}
} // namespace
CodecBuffer::CodecBuffer(CodecImpl* parent, CodecPort port, fuchsia::media::StreamBuffer buffer, bool is_secure)
: parent_(parent), port_(port), buffer_(std::move(buffer)), is_secure_(is_secure) {
// nothing else to do here
}
CodecBuffer::~CodecBuffer() {
zx_status_t status;
if (is_mapped_) {
ZX_DEBUG_ASSERT(buffer_base_);
uintptr_t unmap_address = fbl::round_down(reinterpret_cast<uintptr_t>(base()), ZX_PAGE_SIZE);
size_t unmap_len = fbl::round_up(reinterpret_cast<uintptr_t>(base() + size()), ZX_PAGE_SIZE) - unmap_address;
status =
zx::vmar::root_self()->unmap(unmap_address, unmap_len);
if (status != ZX_OK) {
parent_->FailFatalLocked(
"CodecBuffer::~CodecBuffer() failed to unmap() Buffer - status: %d", status);
}
buffer_base_ = nullptr;
is_mapped_ = false;
}
if (pinned_) {
status = pinned_.unpin();
if (status != ZX_OK) {
parent_->FailFatalLocked("CodecBuffer::~CodecBuffer() failed unpin() - status: %d", status);
}
}
}
bool CodecBuffer::Map() {
ZX_DEBUG_ASSERT(buffer_.has_data());
ZX_DEBUG_ASSERT(buffer_.data().is_vmo());
const fuchsia::media::StreamBufferDataVmo& data_vmo = buffer_.data().vmo();
ZX_DEBUG_ASSERT(data_vmo.has_vmo_handle());
ZX_DEBUG_ASSERT(data_vmo.has_vmo_usable_size());
ZX_DEBUG_ASSERT(!is_secure_);
// Map the VMO in the local address space.
uintptr_t tmp;
zx_vm_option_t flags = ZX_VM_PERM_READ;
if (port_ == kOutputPort) {
flags |= ZX_VM_PERM_WRITE;
}
// We must page-align the mapping (since HW can only map at page granularity). This means the
// mapping may include up to ZX_PAGE_SIZE - 1 bytes before vmo_usable_start, and up to
// ZX_PAGE_SIZE - 1 bytes after vmo_usable_start + vmo_usable_size. The usage of the mapping is
// expected to stay within CodecBuffer::base() to CodecBuffer::base() + vmo_usable_size.
uint64_t vmo_offset = fbl::round_down(data_vmo.vmo_usable_start(), ZX_PAGE_SIZE);
size_t len =
fbl::round_up(data_vmo.vmo_usable_start() + data_vmo.vmo_usable_size(), ZX_PAGE_SIZE) -
vmo_offset;
zx_status_t res =
zx::vmar::root_self()->map(0, data_vmo.vmo_handle(), vmo_offset, len, flags, &tmp);
if (res != ZX_OK) {
printf("Failed to map %zu byte buffer vmo (res %d)\n", data_vmo.vmo_usable_size(),
res);
return false;
}
buffer_base_ = reinterpret_cast<uint8_t*>(tmp + (data_vmo.vmo_usable_start() % ZX_PAGE_SIZE));
is_mapped_ = true;
return true;
}
void CodecBuffer::FakeMap(uint8_t* fake_map_addr) {
ZX_DEBUG_ASSERT(buffer_.has_data());
ZX_DEBUG_ASSERT(buffer_.data().is_vmo());
const fuchsia::media::StreamBufferDataVmo& data_vmo = buffer_.data().vmo();
ZX_DEBUG_ASSERT(data_vmo.has_vmo_handle());
ZX_DEBUG_ASSERT(data_vmo.has_vmo_usable_size());
ZX_DEBUG_ASSERT(reinterpret_cast<uintptr_t>(fake_map_addr) % ZX_PAGE_SIZE == 0);
buffer_base_ = fake_map_addr + (data_vmo.vmo_usable_start() % ZX_PAGE_SIZE);
ZX_DEBUG_ASSERT(!is_mapped_);
}
uint64_t CodecBuffer::lifetime_ordinal() const {
ZX_DEBUG_ASSERT(buffer_.has_buffer_lifetime_ordinal());
return buffer_.buffer_lifetime_ordinal();
}
uint32_t CodecBuffer::index() const {
ZX_DEBUG_ASSERT(buffer_.has_buffer_index());
return buffer_.buffer_index();
}
uint8_t* CodecBuffer::base() const {
ZX_DEBUG_ASSERT(buffer_base_ && "Shouldn't be using if buffer was not mapped.");
return buffer_base_;
}
zx_paddr_t CodecBuffer::physical_base() const {
// Must call Pin() first.
ZX_DEBUG_ASSERT(pinned_);
// Else we'll need a different method that can deal with scattered pages. For now we don't need
// that.
ZX_DEBUG_ASSERT(is_known_contiguous_);
return contiguous_paddr_base_;
}
size_t CodecBuffer::size() const {
ZX_DEBUG_ASSERT(buffer_.has_data());
ZX_DEBUG_ASSERT(buffer_.data().is_vmo());
ZX_DEBUG_ASSERT(buffer_.data().vmo().has_vmo_usable_size());
return buffer_.data().vmo().vmo_usable_size();
}
const zx::vmo& CodecBuffer::vmo() const {
ZX_ASSERT(buffer_.has_data());
ZX_ASSERT(buffer_.data().is_vmo());
return buffer_.data().vmo().vmo_handle();
}
uint64_t CodecBuffer::offset() const {
ZX_ASSERT(buffer_.has_data());
ZX_ASSERT(buffer_.data().is_vmo());
ZX_ASSERT(buffer_.data().vmo().has_vmo_usable_start());
return buffer_.data().vmo().vmo_usable_start();
}
const fuchsia::media::StreamBuffer& CodecBuffer::codec_buffer() const { return buffer_; }
void CodecBuffer::SetVideoFrame(std::weak_ptr<VideoFrame> video_frame) const {
video_frame_ = video_frame;
}
std::weak_ptr<VideoFrame> CodecBuffer::video_frame() const { return video_frame_; }
zx_status_t CodecBuffer::Pin() {
if (is_pinned()) {
return ZX_OK;
}
ZX_DEBUG_ASSERT(buffer_.has_data());
ZX_DEBUG_ASSERT(buffer_.data().is_vmo());
fuchsia::media::StreamBufferDataVmo& data_vmo = buffer_.mutable_data()->vmo();
ZX_DEBUG_ASSERT(data_vmo.has_vmo_handle());
ZX_DEBUG_ASSERT(data_vmo.has_vmo_usable_start());
ZX_DEBUG_ASSERT(data_vmo.has_vmo_usable_size());
zx::vmo* vmo = data_vmo.mutable_vmo_handle();
zx_info_vmo_t info;
zx_status_t status = vmo->get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
if (!(info.flags & ZX_INFO_VMO_CONTIGUOUS)) {
// Not supported yet.
return ZX_ERR_NOT_SUPPORTED;
}
// We could potentially know this via the BufferCollectionInfo_2, but checking the VMO directly
// also works fine.
is_known_contiguous_ = true;
// We must page-align the pin (since pining is page granularity). This means the pin may include
// up to ZX_PAGE_SIZE - 1 bytes before vmo_usable_start, and up to ZX_PAGE_SIZE - 1 bytes after
// vmo_usable_start + vmo_usable_size. The usage of the pin is expected to stay within
// CodecBuffer::base() to CodecBuffer::base() + vmo_usable_size.
uint64_t pin_offset = fbl::round_down(data_vmo.vmo_usable_start(), ZX_PAGE_SIZE);
uint64_t pin_size =
fbl::round_up(data_vmo.vmo_usable_start() + data_vmo.vmo_usable_size(), ZX_PAGE_SIZE) -
pin_offset;
uint32_t options = ZX_BTI_CONTIGUOUS | ZX_BTI_PERM_READ;
if (port_ == kOutputPort) {
options |= ZX_BTI_PERM_WRITE;
}
zx_paddr_t paddr;
status = parent_->Pin(options, *vmo, pin_offset, pin_size, &paddr, 1, &pinned_);
if (status != ZX_OK) {
return status;
}
// Include the low-order bits of vmo_usable_start() in contiguous_paddr_base_ so that the paddr
// at contiguous_paddr_base_ points (physical) at the byte at offset vmo_usable_start() within
// *vmo.
contiguous_paddr_base_ = paddr + (data_vmo.vmo_usable_start() % ZX_PAGE_SIZE);
return ZX_OK;
}
bool CodecBuffer::is_pinned() const {
return !!pinned_;
}
zx_status_t CodecBuffer::CacheFlush(uint32_t offset, uint32_t length) const {
ZX_DEBUG_ASSERT(!is_secure_);
zx_status_t status;
if (is_mapped_) {
status = zx_cache_flush(base() + offset, length, ZX_CACHE_FLUSH_DATA);
} else {
const fuchsia::media::StreamBufferDataVmo& data_vmo = buffer_.data().vmo();
status = vmo().op_range(
ZX_VMO_OP_CACHE_CLEAN, data_vmo.vmo_usable_start() + offset, length, nullptr, 0);
}
BarrierAfterFlush();
return status;
}