| // Copyright 2019 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 "internal_buffer.h" |
| |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <lib/fpromise/result.h> |
| #include <lib/sysmem-version/sysmem-version.h> |
| #include <threads.h> |
| |
| #include <limits> |
| |
| #include <bind/fuchsia/amlogic/platform/sysmem/heap/cpp/bind.h> |
| #include <bind/fuchsia/sysmem/heap/cpp/bind.h> |
| #include <fbl/algorithm.h> |
| |
| #include "src/lib/memory_barriers/memory_barriers.h" |
| |
| fpromise::result<InternalBuffer, zx_status_t> InternalBuffer::Create( |
| const char* name, fidl::SyncClient<fuchsia_sysmem2::Allocator>* sysmem, |
| const zx::unowned_bti& bti, size_t size, bool is_secure, bool is_writable, |
| bool is_mapping_needed) { |
| return CreateAligned(name, sysmem, bti, size, 0, is_secure, is_writable, is_mapping_needed); |
| } |
| |
| fpromise::result<InternalBuffer, zx_status_t> InternalBuffer::Create( |
| const char* name, fuchsia::sysmem::AllocatorSyncPtr* sysmem, const zx::unowned_bti& bti, |
| size_t size, bool is_secure, bool is_writable, bool is_mapping_needed) { |
| return CreateAligned(name, sysmem, bti, size, 0, is_secure, is_writable, is_mapping_needed); |
| } |
| |
| fpromise::result<InternalBuffer, zx_status_t> InternalBuffer::CreateAligned( |
| const char* name, fidl::SyncClient<fuchsia_sysmem2::Allocator>* sysmem, |
| const zx::unowned_bti& bti, size_t size, size_t alignment, bool is_secure, bool is_writable, |
| bool is_mapping_needed) { |
| ZX_DEBUG_ASSERT(sysmem); |
| ZX_DEBUG_ASSERT(*sysmem); |
| ZX_DEBUG_ASSERT(*bti); |
| ZX_DEBUG_ASSERT(size); |
| ZX_DEBUG_ASSERT(size % ZX_PAGE_SIZE == 0); |
| ZX_DEBUG_ASSERT(!is_mapping_needed || !is_secure); |
| InternalBuffer local_result(size, is_secure, is_writable, is_mapping_needed); |
| zx_status_t status = local_result.Init(name, sysmem, alignment, bti); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Init() failed status=%i\n", status); |
| return fpromise::error(status); |
| } |
| return fpromise::ok(std::move(local_result)); |
| } |
| |
| fpromise::result<InternalBuffer, zx_status_t> InternalBuffer::CreateAligned( |
| const char* name, fuchsia::sysmem::AllocatorSyncPtr* sysmem, const zx::unowned_bti& bti, |
| size_t size, size_t alignment, bool is_secure, bool is_writable, bool is_mapping_needed) { |
| ZX_DEBUG_ASSERT(sysmem); |
| ZX_DEBUG_ASSERT(*sysmem); |
| ZX_DEBUG_ASSERT(*bti); |
| ZX_DEBUG_ASSERT(size); |
| ZX_DEBUG_ASSERT(size % ZX_PAGE_SIZE == 0); |
| ZX_DEBUG_ASSERT(!is_mapping_needed || !is_secure); |
| |
| // Get sysmem2 from sysmem(1). This overload of CreateAligned is deprecated and will be removed |
| // once all clients are using the sysmem2 version instead. |
| auto sysmem2_endpoints = fidl::CreateEndpoints<fuchsia_sysmem2::Allocator>(); |
| ZX_ASSERT(sysmem2_endpoints.is_ok()); |
| zx_status_t status = (*sysmem)->ConnectToSysmem2Allocator( |
| fidl::InterfaceRequest<fuchsia::sysmem2::Allocator>(sysmem2_endpoints->server.TakeChannel())); |
| ZX_ASSERT(status == ZX_OK); |
| auto sysmem2_sync = fidl::SyncClient(std::move(sysmem2_endpoints->client)); |
| |
| return CreateAligned(name, &sysmem2_sync, bti, size, alignment, is_secure, is_writable, |
| is_mapping_needed); |
| } |
| |
| InternalBuffer::~InternalBuffer() { DeInit(); } |
| |
| InternalBuffer::InternalBuffer(InternalBuffer&& other) |
| : size_(other.size_), |
| alignment_(other.alignment_), |
| is_secure_(other.is_secure_), |
| is_writable_(other.is_writable_), |
| is_mapping_needed_(other.is_mapping_needed_), |
| virt_base_(other.virt_base_), |
| real_size_(other.real_size_), |
| real_virt_base_(other.real_virt_base_), |
| alignment_offset_(other.alignment_offset_), |
| pin_(std::move(other.pin_)), |
| phys_base_(other.phys_base_), |
| buffer_collection_(std::move(other.buffer_collection_)), |
| vmo_(std::move(other.vmo_)) { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| other.is_moved_out_ = true; |
| ZX_ASSERT(!other.pin_); |
| check_pin_ = [this] { ZX_ASSERT(!pin_); }; |
| } |
| |
| InternalBuffer& InternalBuffer::operator=(InternalBuffer&& other) { |
| // Let's just use a new variable instead of letting this happen, even though this isn't prevented |
| // by C++ rules. |
| ZX_ASSERT(!is_moved_out_); |
| ZX_ASSERT(!other.is_moved_out_); |
| DeInit(); |
| ZX_ASSERT(!pin_); |
| size_ = other.size_; |
| alignment_ = other.alignment_; |
| is_secure_ = other.is_secure_; |
| is_writable_ = other.is_writable_; |
| is_mapping_needed_ = other.is_mapping_needed_; |
| // Let's only move instances that returned success from Init() and haven't been moved out. |
| ZX_ASSERT(other.pin_ && !other.is_moved_out_); |
| pin_ = std::move(other.pin_); |
| virt_base_ = other.virt_base_; |
| real_size_ = other.real_size_; |
| real_virt_base_ = other.real_virt_base_; |
| alignment_offset_ = other.alignment_offset_; |
| phys_base_ = other.phys_base_; |
| buffer_collection_ = std::move(other.buffer_collection_); |
| vmo_ = std::move(other.vmo_); |
| other.is_moved_out_ = true; |
| return *this; |
| } |
| |
| uint8_t* InternalBuffer::virt_base() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(is_mapping_needed_); |
| return virt_base_; |
| } |
| |
| zx_paddr_t InternalBuffer::phys_base() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return phys_base_; |
| } |
| |
| size_t InternalBuffer::size() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return size_; |
| } |
| |
| size_t InternalBuffer::alignment() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return alignment_; |
| } |
| |
| bool InternalBuffer::is_secure() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return is_secure_; |
| } |
| |
| bool InternalBuffer::is_writable() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return is_writable_; |
| } |
| |
| bool InternalBuffer::is_mapping_needed() { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(pin_); |
| return is_mapping_needed_; |
| } |
| |
| const zx::vmo& InternalBuffer::vmo() { |
| ZX_DEBUG_ASSERT(vmo_); |
| return vmo_; |
| } |
| |
| void InternalBuffer::CacheFlush(size_t offset, size_t length) { |
| CacheFlushPossibleInvalidate(offset, length, false); |
| } |
| |
| void InternalBuffer::CacheFlushInvalidate(size_t offset, size_t length) { |
| CacheFlushPossibleInvalidate(offset, length, true); |
| } |
| |
| void InternalBuffer::CacheFlushPossibleInvalidate(size_t offset, size_t length, bool invalidate) { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(offset <= size()); |
| ZX_DEBUG_ASSERT(offset + length >= offset); |
| ZX_DEBUG_ASSERT(offset + length <= size()); |
| ZX_DEBUG_ASSERT(vmo_); |
| zx_status_t status; |
| if (is_secure_) { |
| return; |
| } |
| if (invalidate) { |
| BarrierBeforeInvalidate(); |
| } |
| if (is_mapping_needed_) { |
| ZX_DEBUG_ASSERT(virt_base_); |
| status = zx_cache_flush(virt_base_ + offset, length, |
| ZX_CACHE_FLUSH_DATA | (invalidate ? ZX_CACHE_FLUSH_INVALIDATE : 0)); |
| if (status != ZX_OK) { |
| ZX_PANIC("InternalBuffer::CacheFlush() zx_cache_flush() failed: %d", status); |
| } |
| } else { |
| status = vmo_.op_range(ZX_VMO_OP_CACHE_CLEAN, alignment_offset_ + offset, length, nullptr, 0); |
| if (status != ZX_OK) { |
| ZX_PANIC("InternalBuffer::CacheFlush() op_range(CACHE_CLEAN) failed: %d", status); |
| } |
| } |
| BarrierAfterFlush(); |
| } |
| |
| InternalBuffer::InternalBuffer(size_t size, bool is_secure, bool is_writable, |
| bool is_mapping_needed) |
| : size_(size), |
| is_secure_(is_secure), |
| is_writable_(is_writable), |
| is_mapping_needed_(is_mapping_needed) { |
| ZX_DEBUG_ASSERT(size_); |
| ZX_DEBUG_ASSERT(size_ % ZX_PAGE_SIZE == 0); |
| ZX_ASSERT(!pin_); |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| ZX_DEBUG_ASSERT(!is_mapping_needed_ || !is_secure_); |
| check_pin_ = [this] { ZX_ASSERT(!pin_); }; |
| } |
| |
| zx_status_t InternalBuffer::Init(const char* name, |
| fidl::SyncClient<fuchsia_sysmem2::Allocator>* sysmem, |
| size_t alignment, const zx::unowned_bti& bti) { |
| ZX_DEBUG_ASSERT(!is_moved_out_); |
| // Init() should only be called on newly-constructed instances using a constructor other than the |
| // move constructor. |
| ZX_ASSERT(!pin_); |
| |
| // Let's interact with BufferCollection sync, since we're the only participant. |
| auto collection_endpoints = fidl::CreateEndpoints<fuchsia_sysmem2::BufferCollection>(); |
| ZX_ASSERT(collection_endpoints.is_ok()); |
| fuchsia_sysmem2::AllocatorAllocateNonSharedCollectionRequest non_shared_request; |
| non_shared_request.collection_request() = std::move(collection_endpoints->server); |
| // discard result; deal with potential wait failed below instead |
| (void)(*sysmem)->AllocateNonSharedCollection(std::move(non_shared_request)); |
| auto buffer_collection = fidl::SyncClient(std::move(collection_endpoints->client)); |
| |
| fuchsia_sysmem2::BufferCollectionConstraints constraints; |
| auto& usage = constraints.usage().emplace(); |
| usage.video() = fuchsia_sysmem2::kVideoUsageHwDecoderInternal; |
| // we only want one buffer |
| constraints.min_buffer_count_for_camping() = 1; |
| constraints.max_buffer_count() = 1; |
| |
| // Allocate enough so that some portion must be aligned and large enough. |
| alignment_ = alignment; |
| real_size_ = size_ + alignment; |
| ZX_DEBUG_ASSERT(real_size_ < std::numeric_limits<uint32_t>::max()); |
| auto& bmc = constraints.buffer_memory_constraints().emplace(); |
| bmc.min_size_bytes() = static_cast<uint32_t>(real_size_); |
| bmc.max_size_bytes() = static_cast<uint32_t>(real_size_); |
| // amlogic-video always requires contiguous; only contiguous is supported by InternalBuffer. |
| bmc.physically_contiguous_required() = true; |
| bmc.secure_required() = is_secure_; |
| // If we need a mapping, then we don't want INACCESSIBLE domain, so we need to support at least |
| // one other domain. We choose RAM domain since InternalBuffer(s) are always used for HW DMA, and |
| // we always have to CachFlush() after any write, or CacheInvalidate() before any read. So RAM |
| // domain is a better fit than CPU domain, even though we're not really sharing with any other |
| // participant so the choice is less critical here. |
| bmc.cpu_domain_supported() = false; |
| bmc.ram_domain_supported() = is_mapping_needed_; |
| // Secure buffers need support for INACCESSIBLE, and it's fine to indicate support for |
| // INACCESSIBLE as long as we don't need to map, but when is_mapping_needed_ we shouldn't accept |
| // INACCESSIBLE. |
| // |
| // Nothing presently technically stops us from mapping a buffer that's INACCESSIBLE, because MAP |
| // and PIN are the same right and sysmem assumes PIN will be needed so always grants MAP, but if |
| // the rights were separated, we'd potentially want to exclude MAP unless CPU/RAM domain in |
| // sysmem. |
| bmc.inaccessible_domain_supported() = !is_mapping_needed_; |
| |
| if (is_secure_) { |
| // AMLOGIC_SECURE_VDEC is only ever allocated for input/output buffers, never for internal |
| // buffers. This is "normal" non-VDEC secure memory. See also secmem TA's ProtectMemory / |
| // sysmem. |
| bmc.permitted_heaps() = { |
| sysmem::MakeHeap(bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE, 0)}; |
| } else { |
| bmc.permitted_heaps() = {sysmem::MakeHeap(bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM, 0)}; |
| } |
| |
| // InternalBuffer(s) don't need any image format constraints, as they don't store image data. |
| ZX_DEBUG_ASSERT(!constraints.image_format_constraints().has_value()); |
| |
| fuchsia_sysmem2::NodeSetNameRequest set_name_request; |
| set_name_request.priority() = 10u; |
| set_name_request.name() = name; |
| // discard result; deal with potential wait failed below instead |
| (void)buffer_collection->SetName(std::move(set_name_request)); |
| |
| fuchsia_sysmem2::BufferCollectionSetConstraintsRequest set_constraints_request; |
| set_constraints_request.constraints() = std::move(constraints); |
| // discard result; deal with potential wait failed below instead |
| (void)buffer_collection->SetConstraints(std::move(set_constraints_request)); |
| |
| // There's only one participant, and we've already called SetConstraints(), so this should be |
| // quick. |
| auto wait_result = buffer_collection->WaitForAllBuffersAllocated(); |
| if (wait_result.is_error()) { |
| zx_status_t status; |
| if (wait_result.error_value().is_framework_error()) { |
| status = wait_result.error_value().framework_error().status(); |
| fprintf(stderr, "WaitForBuffersAllocated() failed status=%d\n", status); |
| } else { |
| status = sysmem::V1CopyFromV2Error(wait_result.error_value().domain_error()); |
| fprintf(stderr, "WaitForBuffersAllocated() failed error=%u status=%d\n", |
| fidl::ToUnderlying(wait_result.error_value().domain_error()), status); |
| } |
| return status; |
| } |
| auto out_buffer_collection_info = std::move(*wait_result->buffer_collection_info()); |
| |
| if (!!is_secure_ != !!*out_buffer_collection_info.settings()->buffer_settings()->is_secure()) { |
| fprintf(stderr, "sysmem bug?\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| ZX_DEBUG_ASSERT( |
| out_buffer_collection_info.buffers()->at(0).vmo_usable_start().value() % ZX_PAGE_SIZE == 0); |
| zx::vmo vmo = std::move(*out_buffer_collection_info.buffers()->at(0).vmo()); |
| |
| uintptr_t virt_base = 0; |
| if (is_mapping_needed_) { |
| zx_vm_option_t map_options = ZX_VM_PERM_READ; |
| if (is_writable_) { |
| map_options |= ZX_VM_PERM_WRITE; |
| } |
| |
| zx_status_t status = zx::vmar::root_self()->map(map_options, /*vmar_offset=*/0, vmo, |
| /*vmo_offset=*/0, real_size_, &virt_base); |
| if (status != ZX_OK) { |
| fprintf(stderr, "zx::vmar::root_self()->map() failed status=%i\n", status); |
| return status; |
| } |
| } |
| |
| uint32_t pin_options = ZX_BTI_CONTIGUOUS | ZX_BTI_PERM_READ; |
| if (is_writable_) { |
| pin_options |= ZX_BTI_PERM_WRITE; |
| } |
| |
| zx_paddr_t phys_base; |
| zx::pmt pin; |
| zx_status_t status = |
| bti->pin(pin_options, vmo, *out_buffer_collection_info.buffers()->at(0).vmo_usable_start(), |
| real_size_, &phys_base, 1, &pin); |
| if (status != ZX_OK) { |
| fprintf(stderr, "BTI pin() failed status=%i\n", status); |
| return status; |
| } |
| |
| virt_base_ = reinterpret_cast<uint8_t*>(virt_base); |
| real_virt_base_ = virt_base_; |
| phys_base_ = phys_base; |
| if (alignment) { |
| // Shift the base addresses so the physical address is aligned correctly. |
| zx_paddr_t new_phys_base = fbl::round_up(phys_base, alignment); |
| alignment_offset_ = new_phys_base - phys_base; |
| if (is_mapping_needed_) { |
| virt_base_ += alignment_offset_; |
| } |
| phys_base_ = new_phys_base; |
| } |
| pin_ = std::move(pin); |
| ZX_ASSERT(!pin); |
| ZX_ASSERT(pin_); |
| // We keep the buffer_collection_ channel alive, but we don't listen for channel failure. This |
| // isn't ideal, since we should listen for channel failure so that sysmem can request that we |
| // close the VMO handle ASAP, but so far sysmem won't try to force relinquishing buffers anyway, |
| // so ... it's acceptable for now. We keep the channel open for the lifetime of the |
| // InternalBuffer so this won't look like a buffer that's pending deletion in sysmem. |
| buffer_collection_ = std::move(buffer_collection); |
| vmo_ = std::move(vmo); |
| |
| // Sysmem guarantees that the newly-allocated buffer starts out zeroed and cache clean, to the |
| // extent possible based on is_secure. |
| |
| return ZX_OK; |
| } |
| |
| void InternalBuffer::DeInit() { |
| if (is_moved_out_) { |
| ZX_ASSERT(!pin_); |
| return; |
| } |
| if (pin_) { |
| pin_.unpin(); |
| ZX_ASSERT(!pin_); |
| } |
| if (virt_base_) { |
| zx_status_t status = |
| zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(real_virt_base_), real_size_); |
| // Unmap is expected to work unless there's a bug in how we're calling it. |
| ZX_ASSERT(status == ZX_OK); |
| virt_base_ = nullptr; |
| real_virt_base_ = nullptr; |
| } |
| } |