blob: 336a982046d6646ca7344f76eb59dae1f7c37ed9 [file] [log] [blame]
// 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 "device.h"
#include "allocator.h"
#include "amlogic_memory_allocator.h"
#include "buffer_collection_token.h"
#include "macros.h"
#include <ddk/device.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform/bus.h>
#include <lib/fidl-async-2/simple_binding.h>
#include <lib/fidl-utils/bind.h>
#include <lib/zx/event.h>
#include <zircon/assert.h>
#include <zircon/device/sysmem.h>
namespace {
class SystemRamMemoryAllocator : public MemoryAllocator {
public:
zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
return zx::vmo::create(size, 0, vmo);
}
bool CoherencyDomainIsInaccessible() override { return false; }
};
class ContiguousSystemRamMemoryAllocator : public MemoryAllocator {
public:
explicit ContiguousSystemRamMemoryAllocator(Device* parent_device)
: parent_device_(parent_device) {}
zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
// TODO(dustingreen): Optionally (per board) pre-allocate a
// physically-contiguous VMO with board-specific size, have a page heap,
// dole out portions of that VMO or non-copy-on-write child VMOs of that
// VMO. For now, we just attempt to allocate contiguous when buffers
// are allocated, which is unlikely to work after the system has been
// running for a while and physical memory is more fragmented than early
// during boot.
zx_status_t status =
zx::vmo::create_contiguous(parent_device_->bti(), size, 0, vmo);
if (status != ZX_OK) {
DRIVER_ERROR(
"zx::vmo::create_contiguous() failed - size_bytes: %lu "
"status: %d",
size, status);
// sanitize to ZX_ERR_NO_MEMORY regardless of why.
status = ZX_ERR_NO_MEMORY;
return status;
}
return ZX_OK;
}
bool CoherencyDomainIsInaccessible() override { return false; }
private:
Device* const parent_device_;
};
class ExternalMemoryAllocator : public MemoryAllocator {
public:
ExternalMemoryAllocator(zx::channel connection,
fbl::unique_ptr<async::Wait> wait_for_close)
: connection_(std::move(connection)),
wait_for_close_(std::move(wait_for_close)) {}
zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
zx::vmo parent_vmo;
zx_status_t status2 = ZX_OK;
zx_status_t status =
fuchsia_sysmem_HeapAllocateVmo(connection_.get(), size, &status2,
parent_vmo.reset_and_get_address());
if (status != ZX_OK || status2 != ZX_OK) {
DRIVER_ERROR("HeapAllocate() failed - status: %d status2: %d",
status, status2);
// sanitize to ZX_ERR_NO_MEMORY regardless of why.
status = ZX_ERR_NO_MEMORY;
return status;
}
// Create child VMO. This makes it possible to detect when all
// references to the VMO are gone by waiting for VMO_ZERO_CHILDREN
// signal on parent VMO.
//
// Note: Always a 1:1 relationship between parent and child VMOs.
//
// TODO(reveman): Don't assume that copy-on-write VMO is OK.
zx::vmo child_vmo;
status =
parent_vmo.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, size, &child_vmo);
if (status != ZX_OK) {
DRIVER_ERROR("zx::vmo::create_child() failed - status: %d\n",
status);
// sanitize to ZX_ERR_NO_MEMORY regardless of why.
status = ZX_ERR_NO_MEMORY;
return status;
}
zx::vmo vmo_copy;
status = child_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_copy);
if (status != ZX_OK) {
DRIVER_ERROR("duplicate() failed - status: %d", status);
// sanitize to ZX_ERR_NO_MEMORY regardless of why.
status = ZX_ERR_NO_MEMORY;
return status;
}
uint64_t id;
status = fuchsia_sysmem_HeapCreateResource(
connection_.get(), vmo_copy.release(), &status2, &id);
if (status != ZX_OK || status2 != ZX_OK) {
DRIVER_ERROR("HeapCreateResource() failed - status: %d status2: %d",
status, status2);
// sanitize to ZX_ERR_NO_MEMORY regardless of why.
status = ZX_ERR_NO_MEMORY;
return status;
}
auto vmo_handle = parent_vmo.get();
// Free resource when parent VMO has zero references.
auto wait = std::make_unique<async::Wait>(
vmo_handle, ZX_VMO_ZERO_CHILDREN,
async::Wait::Handler([this, id, vmo = std::move(parent_vmo)](
async_dispatcher_t* dispatcher,
async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) mutable {
auto it = allocations_.find(vmo.get());
if (it == allocations_.end()) {
DRIVER_ERROR("Invalid allocation - vmo_handle: %d",
vmo.get());
return;
}
status =
fuchsia_sysmem_HeapDestroyResource(connection_.get(), id);
if (status != ZX_OK) {
DRIVER_ERROR("HeapDestroyResource() failed - status: %d",
status);
// fall-through - this can only fail because resource has
// already been destroyed.
}
allocations_.erase(it);
}));
// It is safe to call Begin() here before adding entry to the map as
// handler will run on current thread.
wait->Begin(async_get_default_dispatcher());
allocations_[vmo_handle] = std::move(wait);
*vmo = std::move(child_vmo);
return ZX_OK;
}
bool CoherencyDomainIsInaccessible() override {
// TODO(reveman): Add support for CPU/RAM domains to external heaps.
return true;
}
private:
zx::channel connection_;
fbl::unique_ptr<async::Wait> wait_for_close_;
std::map<zx_handle_t, std::unique_ptr<async::Wait>> allocations_;
};
fuchsia_sysmem_DriverConnector_ops_t driver_connector_ops = {
.Connect = fidl::Binder<Device>::BindMember<&Device::Connect>,
.GetProtectedMemoryInfo = fidl::Binder<Device>::BindMember<&Device::GetProtectedMemoryInfo>,
};
zx_status_t sysmem_message(void* device_ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
return fuchsia_sysmem_DriverConnector_dispatch(device_ctx, txn, msg,
&driver_connector_ops);
}
// -Werror=missing-field-initializers seems more paranoid than I want here.
zx_protocol_device_t sysmem_device_ops = [] {
zx_protocol_device_t tmp{};
tmp.version = DEVICE_OPS_VERSION;
tmp.message = sysmem_message;
return tmp;
}();
zx_status_t in_proc_sysmem_Connect(void* ctx,
zx_handle_t allocator_request_param) {
Device* self = static_cast<Device*>(ctx);
return self->Connect(allocator_request_param);
}
zx_status_t in_proc_sysmem_RegisterHeap(void* ctx, uint64_t heap,
zx_handle_t heap_connection_param) {
Device* self = static_cast<Device*>(ctx);
return self->RegisterHeap(heap, heap_connection_param);
}
// In-proc sysmem interface. Essentially an in-proc version of
// fuchsia.sysmem.DriverConnector.
sysmem_protocol_ops_t in_proc_sysmem_protocol_ops = {
.connect = in_proc_sysmem_Connect,
.register_heap = in_proc_sysmem_RegisterHeap,
};
} // namespace
Device::Device(zx_device_t* parent_device, Driver* parent_driver)
: parent_device_(parent_device),
parent_driver_(parent_driver), in_proc_sysmem_protocol_{
.ops = &in_proc_sysmem_protocol_ops,
.ctx = this} {
ZX_DEBUG_ASSERT(parent_device_);
ZX_DEBUG_ASSERT(parent_driver_);
}
zx_status_t Device::Bind() {
zx_status_t status =
device_get_protocol(parent_device_, ZX_PROTOCOL_PDEV, &pdev_);
if (status != ZX_OK) {
DRIVER_ERROR(
"Failed device_get_protocol() ZX_PROTOCOL_PDEV - status: %d",
status);
return status;
}
uint64_t protected_memory_size = 0;
sysmem_metadata_t metadata;
size_t metadata_actual;
status = device_get_metadata(parent_device_, SYSMEM_METADATA, &metadata, sizeof(metadata), &metadata_actual);
if (status == ZX_OK && metadata_actual == sizeof(metadata)) {
pdev_device_info_vid_ = metadata.vid;
pdev_device_info_pid_ = metadata.pid;
protected_memory_size = metadata.protected_memory_size;
}
allocators_[fuchsia_sysmem_HeapType_SYSTEM_RAM] =
std::make_unique<SystemRamMemoryAllocator>();
contiguous_system_ram_allocator_ =
std::make_unique<ContiguousSystemRamMemoryAllocator>(this);
status = pdev_get_bti(&pdev_, 0, bti_.reset_and_get_address());
if (status != ZX_OK) {
DRIVER_ERROR("Failed pdev_get_bti() - status: %d", status);
return status;
}
zx::bti bti_copy;
status = bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, &bti_copy);
if (status != ZX_OK) {
DRIVER_ERROR("BTI duplicate failed: %d", status);
return status;
}
// TODO: Separate protected memory allocator into separate driver or library
if (pdev_device_info_vid_ == PDEV_VID_AMLOGIC && protected_memory_size > 0) {
auto amlogic_allocator = std::make_unique<AmlogicMemoryAllocator>(std::move(bti_copy));
status = amlogic_allocator->Init(protected_memory_size);
if (status != ZX_OK) {
DRIVER_ERROR("Failed to init allocator for amlogic protected memory: %d", status);
return status;
}
protected_allocator_ = amlogic_allocator.get();
allocators_[fuchsia_sysmem_HeapType_AMLOGIC_SECURE] = std::move(amlogic_allocator);
}
pbus_protocol_t pbus;
status = device_get_protocol(parent_device_, ZX_PROTOCOL_PBUS, &pbus);
if (status != ZX_OK) {
DRIVER_ERROR("ZX_PROTOCOL_PBUS not available %d \n", status);
return status;
}
device_add_args_t device_add_args = {};
device_add_args.version = DEVICE_ADD_ARGS_VERSION;
device_add_args.name = "sysmem";
device_add_args.ctx = this;
device_add_args.ops = &sysmem_device_ops;
// ZX_PROTOCOL_SYSMEM causes /dev/class/sysmem to get created, and flags
// support for the fuchsia.sysmem.DriverConnector protocol. The .message
// callback used is sysmem_device_ops.message, not
// sysmem_protocol_ops.message.
device_add_args.proto_id = ZX_PROTOCOL_SYSMEM;
device_add_args.proto_ops = &in_proc_sysmem_protocol_ops;
device_add_args.flags = DEVICE_ADD_INVISIBLE;
status = device_add(parent_device_, &device_add_args, &device_);
if (status != ZX_OK) {
DRIVER_ERROR("Failed to bind device");
return status;
}
// Register the sysmem protocol with the platform bus.
//
// This is essentially the in-proc version of
// fuchsia.sysmem.DriverConnector.
//
// We should only pbus_register_protocol() if device_add() succeeded, but if
// pbus_register_protocol() fails, we should remove the device without it
// ever being visible.
status = pbus_register_protocol(
&pbus, ZX_PROTOCOL_SYSMEM, &in_proc_sysmem_protocol_,
sizeof(in_proc_sysmem_protocol_));
if (status != ZX_OK) {
zx_status_t remove_status = device_remove(device_);
// If this failed, we're potentially leaving the device invisible in a
// --release build, which is about the best we can do if removing fails.
// Of course, remove shouldn't fail in the first place.
ZX_DEBUG_ASSERT(remove_status == ZX_OK);
return status;
}
// We only do this if Bind() fully worked. Else we don't want any client
// to be able to see the device. This call returns void, thankfully.
device_make_visible(device_);
return ZX_OK;
}
zx_status_t Device::Connect(zx_handle_t allocator_request) {
zx::channel local_allocator_request(allocator_request);
// The Allocator is channel-owned / self-owned.
Allocator::CreateChannelOwned(std::move(local_allocator_request), this);
return ZX_OK;
}
zx_status_t Device::RegisterHeap(uint64_t heap, zx_handle_t heap_connection) {
zx::channel local_heap_connection(heap_connection);
// External heaps should not have bit 63 set but bit 60 must be set.
if ((heap & 0x8000000000000000) || !(heap & 0x1000000000000000)) {
DRIVER_ERROR("Invalid external heap");
return ZX_ERR_INVALID_ARGS;
}
// Clean up heap allocator after peer closed channel.
auto wait_for_close = std::make_unique<async::Wait>(
local_heap_connection.get(), ZX_CHANNEL_PEER_CLOSED,
async::Wait::Handler([this, heap](async_dispatcher_t* dispatcher,
async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
allocators_.erase(heap);
}));
// It is safe to call Begin() here before adding entry to the map as
// handler will run on current thread.
wait_for_close->Begin(async_get_default_dispatcher());
// This replaces any previously registered allocator for heap. This
// behavior is preferred as it avoids a potential race-condition during
// heap restart.
allocators_[heap] = std::make_unique<ExternalMemoryAllocator>(
std::move(local_heap_connection), std::move(wait_for_close));
return ZX_OK;
}
zx_status_t Device::GetProtectedMemoryInfo(fidl_txn* txn) {
if (!protected_allocator_) {
return fuchsia_sysmem_DriverConnectorGetProtectedMemoryInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, 0u, 0u);
}
uint64_t base;
uint64_t size;
zx_status_t status = protected_allocator_->GetProtectedMemoryInfo(&base, &size);
return fuchsia_sysmem_DriverConnectorGetProtectedMemoryInfo_reply(txn, status, base, size);
}
const zx::bti& Device::bti() {
return bti_;
}
uint32_t Device::pdev_device_info_vid() {
ZX_DEBUG_ASSERT(pdev_device_info_vid_ !=
std::numeric_limits<uint32_t>::max());
return pdev_device_info_vid_;
}
uint32_t Device::pdev_device_info_pid() {
ZX_DEBUG_ASSERT(pdev_device_info_pid_ !=
std::numeric_limits<uint32_t>::max());
return pdev_device_info_pid_;
}
void Device::TrackToken(BufferCollectionToken* token) {
zx_koid_t server_koid = token->server_koid();
ZX_DEBUG_ASSERT(server_koid != ZX_KOID_INVALID);
ZX_DEBUG_ASSERT(tokens_by_koid_.find(server_koid) == tokens_by_koid_.end());
tokens_by_koid_.insert({server_koid, token});
}
void Device::UntrackToken(BufferCollectionToken* token) {
zx_koid_t server_koid = token->server_koid();
if (server_koid == ZX_KOID_INVALID) {
// The caller is allowed to un-track a token that never saw
// SetServerKoid().
return;
}
auto iter = tokens_by_koid_.find(server_koid);
ZX_DEBUG_ASSERT(iter != tokens_by_koid_.end());
tokens_by_koid_.erase(iter);
}
BufferCollectionToken* Device::FindTokenByServerChannelKoid(
zx_koid_t token_server_koid) {
auto iter = tokens_by_koid_.find(token_server_koid);
if (iter == tokens_by_koid_.end()) {
return nullptr;
}
return iter->second;
}
MemoryAllocator* Device::GetAllocator(
const fuchsia_sysmem_BufferMemorySettings* settings) {
if (settings->heap == fuchsia_sysmem_HeapType_SYSTEM_RAM &&
settings->is_physically_contiguous) {
return contiguous_system_ram_allocator_.get();
}
auto iter = allocators_.find(settings->heap);
if (iter == allocators_.end()) {
return nullptr;
}
return iter->second.get();
}