// 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 "host/magma/IntelDrmDecoder.h" #include #include #include #include #include #include #include #include #include "RenderThreadInfoMagma.h" #include "host/magma/Connection.h" #include "host/magma/DrmDevice.h" #include "magma/magma_common_defs.h" namespace gfxstream { namespace magma { std::unique_ptr IntelDrmDecoder::create(uint32_t context_id) { std::unique_ptr decoder(new IntelDrmDecoder()); decoder->mContextId = context_id; INFO("IntelDrmDecoder created for context %" PRIu32, context_id); return decoder; } #define MAGMA_DECODER_BIND_METHOD(method) \ magma_server_context_t::method = [](auto... args) { \ auto decoder = RenderThreadInfoMagma::get() -> mMagmaDec.get(); \ return static_cast(decoder)->method(args...); \ } IntelDrmDecoder::IntelDrmDecoder() : Decoder() { MAGMA_DECODER_BIND_METHOD(magma_device_import); MAGMA_DECODER_BIND_METHOD(magma_device_release); MAGMA_DECODER_BIND_METHOD(magma_device_query_fudge); MAGMA_DECODER_BIND_METHOD(magma_device_create_connection); MAGMA_DECODER_BIND_METHOD(magma_connection_release); MAGMA_DECODER_BIND_METHOD(magma_connection_create_buffer); MAGMA_DECODER_BIND_METHOD(magma_connection_release_buffer); MAGMA_DECODER_BIND_METHOD(magma_connection_create_semaphore); MAGMA_DECODER_BIND_METHOD(magma_connection_release_semaphore); MAGMA_DECODER_BIND_METHOD(magma_buffer_export); MAGMA_DECODER_BIND_METHOD(magma_semaphore_signal); MAGMA_DECODER_BIND_METHOD(magma_semaphore_reset); MAGMA_DECODER_BIND_METHOD(magma_poll); MAGMA_DECODER_BIND_METHOD(magma_connection_get_error); MAGMA_DECODER_BIND_METHOD(magma_connection_create_context); MAGMA_DECODER_BIND_METHOD(magma_connection_release_context); MAGMA_DECODER_BIND_METHOD(magma_connection_map_buffer); MAGMA_DECODER_BIND_METHOD(magma_connection_unmap_buffer); } // TODO(b/279936417): Make objects and their IDs orthogonal. #define MAGMA_OBJECT_TO_ID(x) ((x) << 32ull) #define MAGMA_ID_TO_OBJECT(x) ((x) >> 32ull) magma_status_t IntelDrmDecoder::magma_device_import(magma_handle_t device_channel, magma_device_t* device_out) { *device_out = 0; auto device = DrmDevice::create(); if (!device) { return MAGMA_STATUS_INTERNAL_ERROR; } *device_out = mDevices.create(std::move(*device)); INFO("magma_device_import() -> %" PRIu64, *device_out); return MAGMA_STATUS_OK; } void IntelDrmDecoder::magma_device_release(magma_device_t device) { INFO("magma_device_release(%" PRIu64 ")", device); } static uint64_t GetNsMonotonic(bool raw) { timespec ts{}; int result = clock_gettime(raw ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC, &ts); if (result < 0) { return 0; } constexpr uint64_t kNsPerSec = 1'000'000'000ull; return static_cast(ts.tv_sec) * kNsPerSec + ts.tv_nsec; } // Converts a DRM topology to a Magma topology. static std::vector MakeMagmaTopology(const drm_i915_query_topology_info* info) { auto read_bit = [](const uint8_t* ptr, size_t offset) -> bool { return (ptr[offset / 8] >> (offset % 8)) & 1; }; auto append_buffer = [](std::vector& buffer, const uint8_t* src, size_t len) -> void { auto offset = buffer.size(); buffer.resize(offset + len); memcpy(buffer.data() + offset, src, len); }; const auto* slice_base = info->data; const auto* subslice_base = info->data + info->subslice_offset; const auto* eu_base = info->data + info->eu_offset; // Start with a buffer just large enough to hold the magma struct. std::vector buffer(sizeof(magma_intel_gen_topology)); // Copy the slice mask. size_t slice_data_bytes = (info->max_slices + 7) / 8; append_buffer(buffer, slice_base, slice_data_bytes); for (uint32_t slice = 0; slice < info->max_slices; ++slice) { if (!read_bit(slice_base, slice)) { continue; } const auto* subslice_data = &subslice_base[slice * info->subslice_stride]; // For each active slice, copy the subslice mask. size_t subslice_data_bytes = (info->max_subslices + 7) / 8; append_buffer(buffer, subslice_data, subslice_data_bytes); for (uint32_t subslice = 0; subslice < info->max_subslices; ++subslice) { if (!read_bit(subslice_data, subslice)) { continue; } const auto* eu_data = &eu_base[(slice * info->max_subslices + subslice) * info->eu_stride]; // For each active subslice, copy the eu mask. size_t eu_data_bytes = (info->max_eus_per_subslice + 7) / 8; append_buffer(buffer, eu_data, eu_data_bytes); } } // Populate the base struct elements. auto magma_topology = reinterpret_cast(buffer.data()); magma_topology->max_slice_count = info->max_slices; magma_topology->max_subslice_count = info->max_subslices; magma_topology->max_eu_count = info->max_eus_per_subslice; magma_topology->data_byte_count = buffer.size() - sizeof(magma_intel_gen_topology); return buffer; } magma_status_t IntelDrmDecoder::magma_device_query_fudge(magma_device_t device, uint64_t id, magma_bool_t host_allocate, uint64_t* result_buffer_mapping_id_inout, uint64_t* result_buffer_size_inout, uint64_t* result_out) { *result_out = 0; auto dev = mDevices.get(device); if (!dev) { return MAGMA_STATUS_INVALID_ARGS; } // TODO(b/275093891): query or standardize hard-coded values constexpr uint32_t kExtraPageCount = 9; constexpr uint64_t kIntelTimestampRegisterOffset = 0x23f8; switch (id) { case MAGMA_QUERY_VENDOR_ID: { *result_out = MAGMA_VENDOR_ID_INTEL; return MAGMA_STATUS_OK; } case MAGMA_QUERY_DEVICE_ID: { auto result = dev->getParam(I915_PARAM_CHIPSET_ID); if (!result) { return MAGMA_STATUS_INTERNAL_ERROR; } *result_out = *result; return MAGMA_STATUS_OK; } case MAGMA_QUERY_IS_TOTAL_TIME_SUPPORTED: { *result_out = 0; return MAGMA_STATUS_OK; } case kMagmaIntelGenQuerySubsliceAndEuTotal: { auto subslice_result = dev->getParam(I915_PARAM_SUBSLICE_TOTAL); auto eu_result = dev->getParam(I915_PARAM_EU_TOTAL); if (!subslice_result || !eu_result) { return MAGMA_STATUS_INTERNAL_ERROR; } *result_out = (static_cast(*subslice_result) << 32) | *eu_result; return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryGttSize: { *result_out = 0; drm_i915_gem_context_create_ext create_params{}; int create_result = dev->ioctl(DRM_IOCTL_I915_GEM_CONTEXT_CREATE_EXT, &create_params); if (create_result) { ERR("DRM_IOCTL_I915_GEM_CONTEXT_CREATE_EXT failed: %d", errno); return MAGMA_STATUS_INTERNAL_ERROR; } drm_i915_gem_context_param query_params{.ctx_id = create_params.ctx_id, .param = I915_CONTEXT_PARAM_GTT_SIZE}; int query_result = dev->ioctl(DRM_IOCTL_I915_GEM_CONTEXT_GETPARAM, &query_params); if (query_result) { ERR("DRM_IOCTL_I915_GEM_CONTEXT_GETPARAM failed: %d", errno); return MAGMA_STATUS_INTERNAL_ERROR; } drm_i915_gem_context_destroy destroy_params{.ctx_id = create_params.ctx_id}; int destroy_result = dev->ioctl(DRM_IOCTL_I915_GEM_CONTEXT_DESTROY, &destroy_params); if (destroy_result) { ERR("DRM_IOCTL_I915_GEM_CONTEXT_DESTROY failed: %d", errno); return MAGMA_STATUS_INTERNAL_ERROR; } *result_out = query_params.value; ERR("GTT size %" PRIu64, *result_out); return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryExtraPageCount: { *result_out = kExtraPageCount; return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryTimestamp: { if (!host_allocate) { WARN("Guest-allocated buffers are not currently supported."); return MAGMA_STATUS_UNIMPLEMENTED; } auto buffer = DrmBuffer::create(*dev, mContextId, sizeof(magma_intel_gen_timestamp_query)); if (!buffer) { return MAGMA_STATUS_MEMORY_ERROR; } auto ptr = buffer->map(); if (!ptr) { return MAGMA_STATUS_MEMORY_ERROR; } auto ts = reinterpret_cast(ptr); ts->monotonic_raw_timestamp[0] = GetNsMonotonic(true); ts->monotonic_timestamp = GetNsMonotonic(false); // Attempt to read device timestamp register. drm_i915_reg_read params{.offset = kIntelTimestampRegisterOffset | I915_REG_READ_8B_WA}; int result = dev->ioctl(DRM_IOCTL_I915_REG_READ, ¶ms); if (result == 0) { ts->device_timestamp = params.val; } else { ts->device_timestamp = 0; } // The driver uses the second timestamp to determine the sampling span. ts->monotonic_raw_timestamp[1] = GetNsMonotonic(true); *result_buffer_mapping_id_inout = buffer->getId(); *result_buffer_size_inout = buffer->size(); // Add the buffer to the container. auto gem_handle = buffer->getHandle(); auto magma_handle = mBuffers.create(std::move(*buffer)); mGemHandleToBuffer.emplace(gem_handle, magma_handle); return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryTopology: { if (!host_allocate) { WARN("Guest-allocated buffers are not currently supported."); return MAGMA_STATUS_UNIMPLEMENTED; } // Check how much space is needed to represent topology. drm_i915_query_item item{.query_id = DRM_I915_QUERY_TOPOLOGY_INFO}; drm_i915_query query{.num_items = 1, .items_ptr = reinterpret_cast(&item)}; int result = dev->ioctl(DRM_IOCTL_I915_QUERY, &query); if (result != 0) { ERR("DRM_IOCTL_I915_QUERY failed: %d", errno); return MAGMA_STATUS_INTERNAL_ERROR; } std::vector topology_buffer(item.length); item.data_ptr = reinterpret_cast(topology_buffer.data()); // Re-run the query with the allocated buffer. result = dev->ioctl(DRM_IOCTL_I915_QUERY, &query); if (result != 0) { ERR("DRM_IOCTL_I915_QUERY failed: %d", errno); return MAGMA_STATUS_INTERNAL_ERROR; } // Convert to the magma-compatible topology layout. auto magma_topology_buffer = MakeMagmaTopology( reinterpret_cast(topology_buffer.data())); // Create a magma buffer and copy the layout struct to it. auto buffer = DrmBuffer::create(*dev, mContextId, magma_topology_buffer.size()); if (!buffer) { return MAGMA_STATUS_MEMORY_ERROR; } auto ptr = buffer->map(); if (!ptr) { return MAGMA_STATUS_MEMORY_ERROR; } memcpy(ptr, magma_topology_buffer.data(), magma_topology_buffer.size()); *result_buffer_mapping_id_inout = buffer->getId(); *result_buffer_size_inout = buffer->size(); auto gem_handle = buffer->getHandle(); auto magma_handle = mBuffers.create(std::move(*buffer)); mGemHandleToBuffer.emplace(gem_handle, magma_handle); return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryHasContextIsolation: { auto result = dev->getParam(I915_PARAM_HAS_CONTEXT_ISOLATION); if (!result) { return MAGMA_STATUS_INTERNAL_ERROR; } *result_out = *result; return MAGMA_STATUS_OK; } case kMagmaIntelGenQueryTimestampFrequency: { auto result = dev->getParam(I915_PARAM_CS_TIMESTAMP_FREQUENCY); if (!result) { return MAGMA_STATUS_INTERNAL_ERROR; } *result_out = *result; return MAGMA_STATUS_OK; } default: { return MAGMA_STATUS_INVALID_ARGS; } } } magma_status_t IntelDrmDecoder::magma_device_create_connection(magma_device_t device, magma_connection_t* connection_out) { *connection_out = MAGMA_INVALID_OBJECT_ID; auto dev = mDevices.get(device); if (!dev) { return MAGMA_STATUS_INVALID_ARGS; } *connection_out = mConnections.create(*dev); return MAGMA_STATUS_OK; } void IntelDrmDecoder::magma_connection_release(magma_connection_t connection) { bool erased = mConnections.erase(connection); if (!erased) { WARN("invalid connection %" PRIu64, connection); } } magma_status_t IntelDrmDecoder::magma_connection_create_buffer(magma_connection_t connection, uint64_t size, uint64_t* size_out, magma_buffer_t* buffer_out, magma_buffer_id_t* id_out) { *size_out = 0; *buffer_out = MAGMA_INVALID_OBJECT_ID; *id_out = MAGMA_INVALID_OBJECT_ID; auto con = mConnections.get(connection); if (!con) { return MAGMA_STATUS_INVALID_ARGS; } auto buffer = DrmBuffer::create(con->getDevice(), mContextId, size); if (!buffer) { return MAGMA_STATUS_MEMORY_ERROR; } auto gem_handle = buffer->getHandle(); auto magma_handle = mBuffers.create(std::move(*buffer)); mGemHandleToBuffer.emplace(gem_handle, magma_handle); *size_out = buffer->size(); *buffer_out = magma_handle; *id_out = MAGMA_OBJECT_TO_ID(magma_handle); return MAGMA_STATUS_OK; } void IntelDrmDecoder::magma_connection_release_buffer(magma_connection_t connection, magma_buffer_t buffer) { auto con = mConnections.get(connection); if (!con) { return; } auto buf = mBuffers.get(buffer); if (!buf) { return; } mGemHandleToBuffer.erase(buf->getHandle()); mBuffers.erase(buffer); } magma_status_t IntelDrmDecoder::magma_connection_create_semaphore( magma_connection_t magma_connection, magma_semaphore_t* semaphore_out, magma_semaphore_id_t* id_out) { *semaphore_out = MAGMA_INVALID_OBJECT_ID; *id_out = MAGMA_INVALID_OBJECT_ID; WARN("%s not implemented", __FUNCTION__); return MAGMA_STATUS_UNIMPLEMENTED; } void IntelDrmDecoder::magma_connection_release_semaphore(magma_connection_t connection, magma_semaphore_t semaphore) { WARN("%s not implemented", __FUNCTION__); } magma_status_t IntelDrmDecoder::magma_buffer_get_info(magma_buffer_t buffer, magma_buffer_info_t* info_out) { auto buf = mBuffers.get(buffer); if (!buf) { return MAGMA_STATUS_INVALID_ARGS; } info_out->size = buf->size(); info_out->committed_byte_count = buf->size(); return MAGMA_STATUS_OK; } magma_status_t IntelDrmDecoder::magma_buffer_get_handle(magma_buffer_t buffer, magma_handle_t* handle_out) { auto buf = mBuffers.get(buffer); if (!buf) { return MAGMA_STATUS_INVALID_ARGS; } *handle_out = buf->getId(); return MAGMA_STATUS_OK; } magma_status_t IntelDrmDecoder::magma_buffer_export(magma_buffer_t buffer, magma_handle_t* buffer_handle_out) { *buffer_handle_out = MAGMA_INVALID_OBJECT_ID; WARN("%s not implemented", __FUNCTION__); return MAGMA_STATUS_UNIMPLEMENTED; } void IntelDrmDecoder::magma_semaphore_signal(magma_semaphore_t semaphore) { WARN("%s not implemented", __FUNCTION__); } void IntelDrmDecoder::magma_semaphore_reset(magma_semaphore_t semaphore) { WARN("%s not implemented", __FUNCTION__); } magma_status_t IntelDrmDecoder::magma_poll(magma_poll_item_t* items, uint32_t count, uint64_t timeout_ns) { WARN("%s not implemented", __FUNCTION__); return MAGMA_STATUS_UNIMPLEMENTED; } magma_status_t IntelDrmDecoder::magma_connection_get_error(magma_connection_t connection) { WARN("%s not implemented", __FUNCTION__); return MAGMA_STATUS_UNIMPLEMENTED; } magma_status_t IntelDrmDecoder::magma_connection_create_context(magma_connection_t connection, uint32_t* context_id_out) { *context_id_out = MAGMA_INVALID_OBJECT_ID; auto con = mConnections.get(connection); if (!con) { return MAGMA_STATUS_INVALID_ARGS; } auto ctx = con->createContext(); if (!ctx) { WARN("error creating context"); return MAGMA_STATUS_INTERNAL_ERROR; } *context_id_out = ctx.value(); return MAGMA_STATUS_OK; } void IntelDrmDecoder::magma_connection_release_context(magma_connection_t connection, uint32_t context_id) { WARN("%s not implemented", __FUNCTION__); } magma_status_t IntelDrmDecoder::magma_connection_map_buffer(magma_connection_t connection, uint64_t hw_va, magma_buffer_t buffer, uint64_t offset, uint64_t length, uint64_t map_flags) { WARN("%s not implemented", __FUNCTION__); return MAGMA_STATUS_UNIMPLEMENTED; } void IntelDrmDecoder::magma_connection_unmap_buffer(magma_connection_t connection, uint64_t hw_va, magma_buffer_t buffer) { WARN("%s not implemented", __FUNCTION__); } } // namespace magma } // namespace gfxstream