// Copyright (C) 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "VirtioGpuAddressSpaceStream.h" #include "EncoderDebug.h" #include "magma_enc.h" static uint64_t get_ns_monotonic(bool raw) { struct timespec time; int ret = clock_gettime(raw ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC, &time); if (ret < 0) return 0; return static_cast(time.tv_sec) * 1000000000ULL + time.tv_nsec; } class MagmaClientContext : public magma_encoder_context_t { public: MagmaClientContext(AddressSpaceStream* stream); AddressSpaceStream* stream() { return reinterpret_cast(magma_encoder_context_t::m_stream); } magma_status_t get_fd_for_buffer(magma_buffer_t buffer, int* fd_out); std::mutex& mutex() { return m_mutex_; } static magma_status_t magma_device_import(void* self, magma_handle_t device_channel, magma_device_t* device_out); static magma_status_t magma_device_query(void* self, magma_device_t device, uint64_t id, magma_handle_t* handle_out, uint64_t* value_out); static magma_status_t magma_buffer_get_handle(void* self, magma_buffer_t buffer, magma_handle_t* handle_out); static magma_status_t magma_buffer_export(void* self, magma_buffer_t buffer, magma_handle_t* handle_out); static magma_status_t magma_poll(void* self, magma_poll_item_t* items, uint32_t count, uint64_t timeout_ns); static magma_status_t magma_connection_create_buffer(void* self, magma_connection_t connection, uint64_t size, uint64_t* size_out, magma_buffer_t* buffer_out, magma_buffer_id_t* id_out); static void magma_connection_release_buffer(void* self, magma_connection_t connection, magma_buffer_t buffer); static void set_thread_local_context_lock(std::unique_lock* lock) { t_lock = lock; } static std::unique_lock* get_thread_local_context_lock() { return t_lock; } magma_device_import_client_proc_t magma_device_import_enc_; magma_buffer_get_handle_client_proc_t magma_buffer_get_handle_enc_; magma_poll_client_proc_t magma_poll_enc_; magma_connection_create_buffer_client_proc_t magma_connection_create_buffer_enc_; magma_connection_release_buffer_client_proc_t magma_connection_release_buffer_enc_; int render_node_fd_; // Stores buffer info upon creation. struct BufferInfo { magma_connection_t connection; // Owning connection. uint64_t size; // Actual size. magma_buffer_id_t id; // Id. }; std::unordered_map buffer_info_; std::mutex m_mutex_; static thread_local std::unique_lock* t_lock; }; // This makes the mutex lock available to decoding methods that can take time // (eg magma_poll), to prevent one thread from locking out others. class ContextLock { public: ContextLock(MagmaClientContext* context) : m_context_(context), m_lock_(context->mutex()) { m_context_->set_thread_local_context_lock(&m_lock_); } ~ContextLock() { m_context_->set_thread_local_context_lock(nullptr); } private: MagmaClientContext* m_context_; std::unique_lock m_lock_; }; // static thread_local std::unique_lock* MagmaClientContext::t_lock; MagmaClientContext::MagmaClientContext(AddressSpaceStream* stream) : magma_encoder_context_t(stream, new gfxstream::guest::ChecksumCalculator) { magma_device_import_enc_ = magma_client_context_t::magma_device_import; magma_buffer_get_handle_enc_ = magma_client_context_t::magma_buffer_get_handle; magma_poll_enc_ = magma_client_context_t::magma_poll; magma_connection_create_buffer_enc_ = magma_client_context_t::magma_connection_create_buffer; magma_client_context_t::magma_device_import = &MagmaClientContext::magma_device_import; magma_client_context_t::magma_device_query = &MagmaClientContext::magma_device_query; magma_client_context_t::magma_buffer_get_handle = &MagmaClientContext::magma_buffer_get_handle; magma_client_context_t::magma_buffer_export = &MagmaClientContext::magma_buffer_export; magma_client_context_t::magma_poll = &MagmaClientContext::magma_poll; magma_client_context_t::magma_connection_create_buffer = &MagmaClientContext::magma_connection_create_buffer; magma_client_context_t::magma_connection_release_buffer = &MagmaClientContext::magma_connection_release_buffer; } // static magma_status_t MagmaClientContext::magma_device_import(void* self, magma_handle_t device_channel, magma_device_t* device_out) { auto context = reinterpret_cast(self); magma_handle_t placeholder = 0xacbd1234; // not used magma_status_t status = context->magma_device_import_enc_(self, placeholder, device_out); // The local fd isn't needed, just close it. int fd = device_channel; close(fd); return status; } magma_status_t MagmaClientContext::get_fd_for_buffer(magma_buffer_t buffer, int* fd_out) { *fd_out = -1; auto it = buffer_info_.find(buffer); if (it == buffer_info_.end()) { ALOGE("%s: buffer (%lu) not found in map", __func__, buffer); return MAGMA_STATUS_INVALID_ARGS; } auto& info = it->second; // TODO(fxbug.dev/42073573): Evaluate deferred guest resource creation. auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma) ->createBlob({.size = info.size, .flags = kBlobFlagMappable | kBlobFlagShareable, .blobMem = kBlobMemHost3d, .blobId = info.id}); if (!blob) { return MAGMA_STATUS_INTERNAL_ERROR; } VirtGpuExternalHandle handle{}; int result = blob->exportBlob(handle); if (result != 0 || handle.osHandle < 0) { return MAGMA_STATUS_INTERNAL_ERROR; } *fd_out = handle.osHandle; return MAGMA_STATUS_OK; } magma_status_t MagmaClientContext::magma_device_query(void* self, magma_device_t device, uint64_t id, magma_handle_t* handle_out, uint64_t* value_out) { auto context = reinterpret_cast(self); // TODO(b/277219980): Support guest-allocated buffers. constexpr magma_bool_t kHostAllocate = 1; uint64_t value = 0; uint64_t result_buffer_mapping_id = 0; uint64_t result_buffer_size = 0; magma_status_t status = context->magma_device_query_fudge( self, device, id, kHostAllocate, &result_buffer_mapping_id, &result_buffer_size, &value); if (status != MAGMA_STATUS_OK) { ALOGE("magma_device_query_fudge failed: %d\n", status); return status; } // For non-buffer queries, just return the value. if (result_buffer_size == 0) { if (!value_out) { ALOGE("MAGMA_STATUS_INVALID_ARGS\n"); return MAGMA_STATUS_INVALID_ARGS; } *value_out = value; ALOGE("MAGMA_STATUS_OK (value = %lu)\n", value); return MAGMA_STATUS_OK; } // Otherwise, create and return a fd for the host-allocated buffer. if (!handle_out) { ALOGE("MAGMA_STATUS_INVALID_ARGS\n"); return MAGMA_STATUS_INVALID_ARGS; } ALOGI("opening blob id %lu size %lu\n", result_buffer_mapping_id, result_buffer_size); auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma) ->createBlob({.size = result_buffer_size, .flags = kBlobFlagMappable | kBlobFlagShareable, .blobMem = kBlobMemHost3d, .blobId = result_buffer_mapping_id}); if (!blob) { ALOGE("VirtGpuDevice::createBlob failed\n"); return MAGMA_STATUS_INTERNAL_ERROR; } VirtGpuExternalHandle handle{}; int result = blob->exportBlob(handle); if (result != 0 || handle.osHandle < 0) { ALOGE("VirtGpuResource::exportBlob failed\n"); return MAGMA_STATUS_INTERNAL_ERROR; } *handle_out = handle.osHandle; return MAGMA_STATUS_OK; } magma_status_t MagmaClientContext::magma_buffer_get_handle(void* self, magma_buffer_t buffer, magma_handle_t* handle_out) { auto context = reinterpret_cast(self); magma_buffer_info_t info{}; magma_status_t status = context->magma_buffer_get_info(self, buffer, &info); if (status != MAGMA_STATUS_OK) return status; magma_handle_t mapping_id = 0; status = context->magma_buffer_get_handle_enc_(self, buffer, &mapping_id); if (status != MAGMA_STATUS_OK) return status; auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma) ->createBlob({.size = info.size, .flags = kBlobFlagMappable | kBlobFlagShareable, .blobMem = kBlobMemHost3d, .blobId = mapping_id}); if (!blob) { return MAGMA_STATUS_INTERNAL_ERROR; } VirtGpuExternalHandle handle{}; int result = blob->exportBlob(handle); if (result != 0 || handle.osHandle < 0) { return MAGMA_STATUS_INTERNAL_ERROR; } *handle_out = handle.osHandle; return MAGMA_STATUS_OK; } magma_status_t MagmaClientContext::magma_buffer_export(void* self, magma_buffer_t buffer, magma_handle_t* handle_out) { auto context = reinterpret_cast(self); int fd; magma_status_t status = context->get_fd_for_buffer(buffer, &fd); if (status != MAGMA_STATUS_OK) return status; *handle_out = fd; return MAGMA_STATUS_OK; } // We can't pass a non-zero timeout to the server, as that would block the server from handling // requests from other threads. So we busy wait here, which isn't ideal; however if the server did // block, gfxstream would busy wait for the response anyway. magma_status_t MagmaClientContext::magma_poll(void* self, magma_poll_item_t* items, uint32_t count, uint64_t timeout_ns) { auto context = reinterpret_cast(self); int64_t time_start = static_cast(get_ns_monotonic(false)); int64_t abs_timeout_ns = time_start + timeout_ns; if (abs_timeout_ns < time_start) { abs_timeout_ns = std::numeric_limits::max(); } bool warned_for_long_poll = false; while (true) { magma_status_t status = context->magma_poll_enc_(self, items, count, 0); if (status != MAGMA_STATUS_TIMED_OUT) return status; // Not ready, allow other threads to work in with us get_thread_local_context_lock()->unlock(); std::this_thread::yield(); int64_t time_now = static_cast(get_ns_monotonic(false)); // TODO(fxb/122604): Add back-off to the busy loop, ideally based on recent sleep // patterns (e.g. start polling shortly before next expected burst). if (!warned_for_long_poll && time_now - time_start > 5000000000) { ALOGE("magma_poll: long poll detected (%lu us)", (time_now - time_start) / 1000); warned_for_long_poll = true; } if (time_now >= abs_timeout_ns) break; get_thread_local_context_lock()->lock(); } return MAGMA_STATUS_TIMED_OUT; } // Magma 1.0 no longer tracks buffer size and id on behalf of the client, so we mirror it here. magma_status_t MagmaClientContext::magma_connection_create_buffer(void* self, magma_connection_t connection, uint64_t size, uint64_t* size_out, magma_buffer_t* buffer_out, magma_buffer_id_t* id_out) { auto context = reinterpret_cast(self); // TODO(b/277219980): support guest-allocated buffers magma_status_t status = context->magma_connection_create_buffer_enc_( self, connection, size, size_out, buffer_out, id_out); if (status != MAGMA_STATUS_OK) return status; auto [_, inserted] = context->buffer_info_.emplace( *buffer_out, BufferInfo{.connection = connection, .size = *size_out, .id = *id_out}); if (!inserted) { ALOGE("magma_connection_create_buffer: duplicate entry in buffer info map"); return MAGMA_STATUS_INTERNAL_ERROR; } return MAGMA_STATUS_OK; } void MagmaClientContext::magma_connection_release_buffer(void* self, magma_connection_t connection, magma_buffer_t buffer) { auto context = reinterpret_cast(self); context->magma_connection_release_buffer_enc_(self, connection, buffer); // Invalid buffer or connection is treated as no-op by magma, so only log as verbose. auto it = context->buffer_info_.find(buffer); if (it == context->buffer_info_.end()) { ALOGV("magma_connection_release_buffer: buffer (%lu) not found in map", buffer); return; } if (it->second.connection != connection) { ALOGV( "magma_connection_release_buffer: buffer (%lu) attempted release using wrong " "connection (expected %lu, received %lu)", buffer, it->second.connection, connection); return; } context->buffer_info_.erase(it); } template static T SafeCast(const U& value) { if (value > std::numeric_limits::max() || value < std::numeric_limits::min()) { abort(); } return static_cast(value); } // We have a singleton client context for all threads. We want all client // threads served by a single server RenderThread. MagmaClientContext* GetMagmaContext() { static MagmaClientContext* s_context; static std::once_flag once_flag; std::call_once(once_flag, []() { auto stream = createVirtioGpuAddressSpaceStream(kCapsetGfxStreamMagma, nullptr); assert(stream); // RenderThread expects flags: send zero 'clientFlags' to the host. { auto pClientFlags = reinterpret_cast(stream->allocBuffer(sizeof(unsigned int))); *pClientFlags = 0; stream->commitBuffer(sizeof(unsigned int)); } s_context = new MagmaClientContext(stream); auto render_node_fd = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma)->getDeviceHandle(); s_context->render_node_fd_ = SafeCast(render_node_fd); ALOGE("Created new context\n"); fflush(stdout); }); return s_context; } // Used in magma_entry.cpp // Always lock around the encoding methods because we have a singleton context. #define GET_CONTEXT \ MagmaClientContext* ctx = GetMagmaContext(); \ ContextLock lock(ctx) #include "magma_entry.cpp"