blob: 79400a31aba706a43e800e5e273e9f1bd21d69c2 [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 <mutex>
#include "platform_connection_client.h"
#include <fuchsia/gpu/magma/c/fidl.h>
#include <fuchsia/gpu/magma/cpp/fidl.h>
#include <lib/fdio/unsafe.h>
namespace {
// Convert zx channel status to magma status.
static magma_status_t MagmaChannelStatus(const zx_status_t status)
{
switch (status) {
case ZX_OK:
return MAGMA_STATUS_OK;
case ZX_ERR_PEER_CLOSED:
return MAGMA_STATUS_CONNECTION_LOST;
case ZX_ERR_TIMED_OUT:
return MAGMA_STATUS_TIMED_OUT;
default:
return MAGMA_STATUS_INTERNAL_ERROR;
}
}
} // namespace
namespace magma {
class ZirconPlatformConnectionClient : public PlatformConnectionClient {
public:
ZirconPlatformConnectionClient(zx::channel channel, zx::channel notification_channel)
: notification_channel_(std::move(notification_channel))
{
magma_fidl_.Bind(std::move(channel));
}
// Imports a buffer for use in the system driver
magma_status_t ImportBuffer(PlatformBuffer* buffer) override
{
DLOG("ZirconPlatformConnectionClient: ImportBuffer");
if (!buffer)
return DRET_MSG(MAGMA_STATUS_INVALID_ARGS, "attempting to import null buffer");
uint32_t duplicate_handle;
if (!buffer->duplicate_handle(&duplicate_handle))
return DRET_MSG(MAGMA_STATUS_INVALID_ARGS, "failed to get duplicate_handle");
zx::vmo vmo(duplicate_handle);
magma_status_t result = MagmaChannelStatus(magma_fidl_->ImportBuffer(std::move(vmo)));
if (result != MAGMA_STATUS_OK) {
return DRET_MSG(result, "failed to write to channel");
}
return MAGMA_STATUS_OK;
}
// Destroys the buffer with |buffer_id| within this connection
// returns false if the buffer with |buffer_id| has not been imported
magma_status_t ReleaseBuffer(uint64_t buffer_id) override
{
DLOG("ZirconPlatformConnectionClient: ReleaseBuffer");
magma_status_t result = MagmaChannelStatus(magma_fidl_->ReleaseBuffer(buffer_id));
if (result != MAGMA_STATUS_OK)
return DRET_MSG(result, "failed to write to channel");
return MAGMA_STATUS_OK;
}
magma_status_t ImportObject(uint32_t handle, PlatformObject::Type object_type) override
{
DLOG("ZirconPlatformConnectionClient: ImportObject");
zx::handle duplicate_handle(handle);
magma_status_t result =
MagmaChannelStatus(magma_fidl_->ImportObject(std::move(duplicate_handle), object_type));
if (result != MAGMA_STATUS_OK) {
return DRET_MSG(result, "failed to write to channel");
}
return MAGMA_STATUS_OK;
}
magma_status_t ReleaseObject(uint64_t object_id, PlatformObject::Type object_type) override
{
DLOG("ZirconPlatformConnectionClient: ReleaseObject");
magma_status_t result =
MagmaChannelStatus(magma_fidl_->ReleaseObject(object_id, object_type));
if (result != MAGMA_STATUS_OK)
return DRET_MSG(result, "failed to write to channel");
return MAGMA_STATUS_OK;
}
// Creates a context and returns the context id
void CreateContext(uint32_t* context_id_out) override
{
DLOG("ZirconPlatformConnectionClient: CreateContext");
auto context_id = next_context_id_++;
*context_id_out = context_id;
magma_status_t result = MagmaChannelStatus(magma_fidl_->CreateContext(context_id));
if (result != MAGMA_STATUS_OK)
SetError(result);
}
// Destroys a context for the given id
void DestroyContext(uint32_t context_id) override
{
DLOG("ZirconPlatformConnectionClient: DestroyContext");
magma_status_t result = MagmaChannelStatus(magma_fidl_->DestroyContext(context_id));
if (result != MAGMA_STATUS_OK)
SetError(result);
}
void ExecuteCommandBuffer(uint32_t command_buffer_handle, uint32_t context_id) override
{
DLOG("ZirconPlatformConnectionClient: ExecuteCommandBuffer");
zx::handle duplicate_handle(command_buffer_handle);
magma_status_t result = MagmaChannelStatus(
magma_fidl_->ExecuteCommandBuffer(std::move(duplicate_handle), context_id));
if (result != MAGMA_STATUS_OK)
SetError(result);
}
// Returns the number of commands that will fit within |max_bytes|.
static int FitCommands(const size_t max_bytes, const int num_buffers,
const magma_system_inline_command_buffer* buffers,
const int starting_index, uint64_t* command_bytes,
uint32_t* num_semaphores)
{
int buffer_count = 0;
uint64_t bytes_used = 0;
*command_bytes = 0;
*num_semaphores = 0;
while (starting_index + buffer_count < num_buffers && bytes_used < max_bytes) {
*command_bytes += buffers[starting_index + buffer_count].size;
*num_semaphores += buffers[starting_index + buffer_count].semaphore_count;
bytes_used = *command_bytes + *num_semaphores * sizeof(uint64_t);
buffer_count++;
}
if (bytes_used > max_bytes)
return (buffer_count - 1);
return buffer_count;
}
void ExecuteImmediateCommands(uint32_t context_id, uint64_t num_buffers,
magma_system_inline_command_buffer* buffers) override
{
DLOG("ZirconPlatformConnectionClient: ExecuteImmediateCommands");
uint64_t buffers_sent = 0;
while (buffers_sent < num_buffers) {
// Tally up the number of commands to send in this batch.
uint64_t command_bytes = 0;
uint32_t num_semaphores = 0;
int buffers_to_send =
FitCommands(fuchsia::gpu::magma::kReceiveBufferSize, num_buffers, buffers,
buffers_sent, &command_bytes, &num_semaphores);
// TODO(MA-536): Figure out how to move command and semaphore bytes across the FIDL
// interface without copying.
std::vector<uint8_t> command_vec;
command_vec.reserve(command_bytes);
std::vector<uint64_t> semaphore_vec;
semaphore_vec.reserve(num_semaphores);
for (int i = 0; i < buffers_to_send; ++i) {
const auto& buffer = buffers[buffers_sent + i];
const auto buffer_data = static_cast<uint8_t*>(buffer.data);
std::copy(buffer_data, buffer_data + buffer.size, std::back_inserter(command_vec));
std::copy(buffer.semaphores, buffer.semaphores + buffer.semaphore_count,
std::back_inserter(semaphore_vec));
}
magma_status_t result = MagmaChannelStatus(magma_fidl_->ExecuteImmediateCommands(
context_id, std::move(command_vec), std::move(semaphore_vec)));
if (result != MAGMA_STATUS_OK)
SetError(result);
buffers_sent += buffers_to_send;
}
}
magma_status_t GetError() override
{
// We need a lock around the channel write and read, because otherwise it's possible two
// threads will send the GetErrorOp, the first WaitError will get a response and read it,
// and the second WaitError will wake up because of the first response and error out because
// there's no message available to read yet.
std::lock_guard<std::mutex> lock(get_error_lock_);
magma_status_t error = error_;
error_ = 0;
if (error != MAGMA_STATUS_OK)
return error;
magma_status_t status = MagmaChannelStatus(magma_fidl_->GetError(&error));
if (status != MAGMA_STATUS_OK)
return status;
return error;
}
magma_status_t MapBufferGpu(uint64_t buffer_id, uint64_t gpu_va, uint64_t page_offset,
uint64_t page_count, uint64_t flags) override
{
DLOG("ZirconPlatformConnectionClient: MapBufferGpu");
magma_status_t result = MagmaChannelStatus(
magma_fidl_->MapBufferGpu(buffer_id, gpu_va, page_offset, page_count, flags));
if (result != MAGMA_STATUS_OK)
return DRET_MSG(result, "failed to write to channel");
return MAGMA_STATUS_OK;
}
magma_status_t UnmapBufferGpu(uint64_t buffer_id, uint64_t gpu_va) override
{
DLOG("ZirconPlatformConnectionClient: UnmapBufferGpu");
magma_status_t result = MagmaChannelStatus(magma_fidl_->UnmapBufferGpu(buffer_id, gpu_va));
if (result != MAGMA_STATUS_OK)
return DRET_MSG(result, "failed to write to channel");
return MAGMA_STATUS_OK;
}
magma_status_t CommitBuffer(uint64_t buffer_id, uint64_t page_offset,
uint64_t page_count) override
{
DLOG("ZirconPlatformConnectionClient: CommitBuffer");
magma_status_t result =
MagmaChannelStatus(magma_fidl_->CommitBuffer(buffer_id, page_offset, page_count));
if (result != MAGMA_STATUS_OK)
return DRET_MSG(result, "failed to write to channel");
return MAGMA_STATUS_OK;
}
void SetError(magma_status_t error)
{
std::lock_guard<std::mutex> lock(get_error_lock_);
if (!error_)
error_ = DRET_MSG(error, "ZirconPlatformConnectionClient encountered dispatcher error");
}
uint32_t GetNotificationChannelHandle() override { return notification_channel_.get(); }
magma_status_t ReadNotificationChannel(void* buffer, size_t buffer_size,
size_t* buffer_size_out) override
{
uint32_t buffer_actual_size;
zx_status_t status = notification_channel_.read(0, buffer, nullptr, buffer_size, 0,
&buffer_actual_size, nullptr);
*buffer_size_out = buffer_actual_size;
if (status == ZX_ERR_SHOULD_WAIT) {
*buffer_size_out = 0;
return MAGMA_STATUS_OK;
} else if (status == ZX_OK) {
return MAGMA_STATUS_OK;
} else if (status == ZX_ERR_PEER_CLOSED) {
return DRET_MSG(MAGMA_STATUS_CONNECTION_LOST, "notification channel, closed");
} else {
return DRET_MSG(MAGMA_STATUS_INTERNAL_ERROR,
"failed to wait on notification channel status %u", status);
}
}
magma_status_t WaitNotificationChannel(int64_t timeout_ns) override
{
zx_signals_t pending;
zx_status_t status =
notification_channel_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
zx::deadline_after(zx::nsec(timeout_ns)), &pending);
if (status != ZX_OK)
return DRET(MagmaChannelStatus(status));
if (pending & ZX_CHANNEL_READABLE)
return MAGMA_STATUS_OK;
if (pending & ZX_CHANNEL_PEER_CLOSED)
return DRET(MAGMA_STATUS_CONNECTION_LOST);
DASSERT(false);
return MAGMA_STATUS_INTERNAL_ERROR;
}
private:
fuchsia::gpu::magma::PrimarySyncPtr magma_fidl_;
zx::channel notification_channel_;
uint32_t next_context_id_ = 1;
std::mutex get_error_lock_;
MAGMA_GUARDED(get_error_lock_) magma_status_t error_{};
}; // class ZirconPlatformConnectionClient
std::unique_ptr<PlatformConnectionClient>
PlatformConnectionClient::Create(uint32_t device_handle, uint32_t device_notification_handle)
{
return std::unique_ptr<ZirconPlatformConnectionClient>(new ZirconPlatformConnectionClient(
zx::channel(device_handle), zx::channel(device_notification_handle)));
}
bool PlatformConnectionClient::Query(int fd, uint64_t query_id, uint64_t* result_out)
{
fdio_t* fdio = fdio_unsafe_fd_to_io(fd);
if (!fdio)
return DRETF(false, "invalid fd: %d", fd);
zx_status_t status =
fuchsia_gpu_magma_DeviceQuery(fdio_unsafe_borrow_channel(fdio), query_id, result_out);
fdio_unsafe_release(fdio);
if (status != ZX_OK)
return DRETF(false, "magma_DeviceQuery failed: %d", status);
return true;
}
bool PlatformConnectionClient::GetHandles(int fd, uint32_t* device_handle_out,
uint32_t* device_notification_handle_out)
{
fdio_t* fdio = fdio_unsafe_fd_to_io(fd);
if (!fdio)
return DRETF(false, "invalid fd: %d", fd);
zx_status_t status = fuchsia_gpu_magma_DeviceConnect(
fdio_unsafe_borrow_channel(fdio), magma::PlatformThreadId().id(), device_handle_out,
device_notification_handle_out);
fdio_unsafe_release(fdio);
if (status != ZX_OK)
return DRETF(false, "magma_DeviceConnect failed: %d", status);
return true;
}
} // namespace magma