blob: 65de13ff2de81c5542bf2776a952b1c32ba44039 [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 "zircon_platform_connection.h"
#include <mutex>
#include "platform_connection_client.h"
#include <fuchsia/gpu/magma/c/fidl.h>
#include <fuchsia/gpu/magma/cpp/fidl.h>
#include <garnet/public/lib/fidl/cpp/vector.h>
#include <lib/fdio/io.h>
#include <lib/fdio/unsafe.h>
#include <lib/zx/channel.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;
default:
return MAGMA_STATUS_INTERNAL_ERROR;
}
}
} // namespace
namespace magma {
#if MAGMA_FIDL
/**
* ZirconPlatformConnectionClient with FIDL
*/
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_->ImportBufferFIDL(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_->ReleaseBufferFIDL(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_->ImportObjectFIDL(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_->ReleaseObjectFIDL(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_->CreateContextFIDL(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_->DestroyContextFIDL(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_->ExecuteCommandBufferFIDL(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: ExecuteImmediateCommandsFIDL");
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);
fidl::VectorPtr<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_->ExecuteImmediateCommandsFIDL(
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_->GetErrorFIDL(&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_->MapBufferGpuFIDL(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_->UnmapBufferGpuFIDL(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_->CommitBufferFIDL(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");
}
int GetNotificationChannelFd() override
{
return fdio_handle_fd(notification_channel_.get(),
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0, true);
}
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, buffer_size, &buffer_actual_size,
nullptr, 0, 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);
}
}
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
#else // MAGMA_FIDL
/**
* ZirconPlatformConnectionClient without FIDL
*/
class ZirconPlatformConnectionClient : public PlatformConnectionClient {
public:
ZirconPlatformConnectionClient(zx::channel channel, zx::channel notification_channel)
: channel_(std::move(channel)), notification_channel_(std::move(notification_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");
ImportBufferOp op;
zx_handle_t duplicate_handle_zx = duplicate_handle;
magma_status_t result = channel_write(&op, sizeof(op), &duplicate_handle_zx, 1);
if (result != MAGMA_STATUS_OK) {
DLOG("ZirconPlatformConnectionClient: ImportBuffer - failed to write to channel");
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");
ReleaseBufferOp op;
op.buffer_id = buffer_id;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
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");
ImportObjectOp op;
op.object_type = object_type;
zx_handle_t duplicate_handle_zx = handle;
magma_status_t result = channel_write(&op, sizeof(op), &duplicate_handle_zx, 1);
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");
ReleaseObjectOp op;
op.object_id = object_id;
op.object_type = object_type;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
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;
CreateContextOp op;
op.context_id = context_id;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
if (result != MAGMA_STATUS_OK)
SetError(result);
}
// Destroys a context for the given id
void DestroyContext(uint32_t context_id) override
{
DLOG("ZirconPlatformConnectionClient: DestroyContext");
DestroyContextOp op;
op.context_id = context_id;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
if (result != MAGMA_STATUS_OK)
SetError(result);
}
void ExecuteCommandBuffer(uint32_t command_buffer_handle, uint32_t context_id) override
{
DLOG("ZirconPlatformConnectionClient: ExecuteCommandBuffer");
ExecuteCommandBufferOp op;
op.context_id = context_id;
zx_handle_t duplicate_handle_zx = command_buffer_handle;
magma_status_t result = channel_write(&op, sizeof(op), &duplicate_handle_zx, 1);
if (result != MAGMA_STATUS_OK)
SetError(result);
}
void ExecuteImmediateCommands(uint32_t context_id, uint64_t command_count,
magma_system_inline_command_buffer* command_buffers) override
{
DLOG("ZirconPlatformConnectionClient: ExecuteImmediateCommands");
uint8_t payload[fuchsia::gpu::magma::kReceiveBufferSize];
uint64_t commands_sent = 0;
while (commands_sent < command_count) {
auto op = new (payload) ExecuteImmediateCommandsOp;
op->context_id = context_id;
uint64_t space_used = sizeof(ExecuteImmediateCommandsOp);
uint64_t semaphores_used = 0;
uint64_t last_command;
for (last_command = commands_sent; last_command < command_count; last_command++) {
uint64_t command_space =
command_buffers[last_command].size +
command_buffers[last_command].semaphore_count * sizeof(uint64_t);
space_used += command_space;
if (space_used > sizeof(payload))
break;
semaphores_used += command_buffers[last_command].semaphore_count;
}
op->semaphore_count = semaphores_used;
uint64_t* semaphore_data = op->semaphores;
uint8_t* command_data = op->command_data();
uint64_t command_data_used = 0;
for (uint64_t i = commands_sent; i < last_command; i++) {
memcpy(semaphore_data, command_buffers[i].semaphores,
command_buffers[i].semaphore_count * sizeof(uint64_t));
semaphore_data += command_buffers[i].semaphore_count;
memcpy(command_data, command_buffers[i].data, command_buffers[i].size);
command_data += command_buffers[i].size;
command_data_used += command_buffers[i].size;
}
op->commands_size = command_data_used;
commands_sent = last_command;
uint64_t payload_size =
ExecuteImmediateCommandsOp::size(op->semaphore_count, op->commands_size);
DASSERT(payload_size <= sizeof(payload));
magma_status_t result = channel_write(payload, payload_size, nullptr, 0);
if (result != MAGMA_STATUS_OK)
SetError(result);
}
}
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;
GetErrorOp op;
magma_status_t status = channel_write(&op, sizeof(op), nullptr, 0);
if (status != MAGMA_STATUS_OK)
return DRET_MSG(status, "failed to write to channel");
status = WaitError(&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");
MapBufferGpuOp op;
op.buffer_id = buffer_id;
op.gpu_va = gpu_va;
op.page_offset = page_offset;
op.page_count = page_count;
op.flags = flags;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
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");
UnmapBufferGpuOp op;
op.buffer_id = buffer_id;
op.gpu_va = gpu_va;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
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");
CommitBufferOp op;
op.buffer_id = buffer_id;
op.page_offset = page_offset;
op.page_count = page_count;
magma_status_t result = channel_write(&op, sizeof(op), nullptr, 0);
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");
}
magma_status_t WaitError(magma_status_t* error_out)
{
return WaitMessage(reinterpret_cast<uint8_t*>(error_out), sizeof(*error_out), true);
}
magma_status_t WaitMessage(uint8_t* msg_out, uint32_t msg_size, bool blocking)
{
zx_signals_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
zx_signals_t pending = 0;
zx_status_t status =
channel_.wait_one(signals, blocking ? zx::time::infinite() : zx::time(), &pending);
if (status == ZX_ERR_TIMED_OUT) {
return 0;
} else if (status == ZX_OK) {
DLOG("got ZX_OK, blocking: %s, readable: %s, closed %s", blocking ? "true" : "false",
pending & ZX_CHANNEL_READABLE ? "true" : "false",
pending & ZX_CHANNEL_PEER_CLOSED ? "true" : "false");
if (pending & ZX_CHANNEL_READABLE) {
uint32_t actual_bytes;
zx_status_t status =
channel_.read(0, msg_out, msg_size, &actual_bytes, nullptr, 0, nullptr);
if (status != ZX_OK)
return DRET_MSG(MAGMA_STATUS_INTERNAL_ERROR, "failed to read from channel");
if (actual_bytes != msg_size)
return DRET_MSG(MAGMA_STATUS_INTERNAL_ERROR,
"read wrong number of bytes from channel");
} else if (pending & ZX_CHANNEL_PEER_CLOSED) {
return DRET_MSG(MAGMA_STATUS_CONNECTION_LOST, "channel, closed");
}
return 0;
} else {
return DRET_MSG(MAGMA_STATUS_INTERNAL_ERROR, "failed to wait on channel");
}
}
int GetNotificationChannelFd() override
{
return fdio_handle_fd(notification_channel_.get(),
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0, true);
}
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, buffer_size, &buffer_actual_size,
nullptr, 0, 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);
}
}
private:
magma_status_t channel_write(const void* bytes, uint32_t num_bytes, const zx_handle_t* handles,
uint32_t num_handles)
{
return MagmaChannelStatus(channel_.write(0, bytes, num_bytes, handles, num_handles));
}
zx::channel channel_;
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
#endif // MAGMA_FIDL
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