| /* |
| * Copyright © 2021 Google, LLC |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| */ |
| |
| #include "gbmint.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <magma/magma.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #include <drm-uapi/drm_fourcc.h> |
| |
| #define LOG_VERBOSE(msg, ...) \ |
| if (false) \ |
| fprintf(stderr, msg, ##__VA_ARGS__) |
| |
| struct gbm_magma_device { |
| struct gbm_device base; |
| magma_connection_t connection; |
| }; |
| |
| struct gbm_magma_bo { |
| struct gbm_bo base; |
| magma_buffer_t image; |
| magma_image_info_t info; |
| uint64_t size; |
| }; |
| |
| static struct gbm_magma_device* magma_device(struct gbm_device* device) |
| { |
| return (struct gbm_magma_device*)device; |
| } |
| |
| static struct gbm_magma_bo* magma_bo(struct gbm_bo* bo) { return (struct gbm_magma_bo*)bo; } |
| |
| /* TODO(fxbug.dev/91126) - support for all image formats */ |
| static int bytes_per_pixel() { return 4; } |
| |
| static int magma_is_format_supported(struct gbm_device* gbm, uint32_t format, uint32_t usage) |
| { |
| switch (format) { |
| case GBM_FORMAT_XRGB8888: |
| case GBM_FORMAT_XBGR8888: |
| case GBM_FORMAT_RGBX8888: |
| case GBM_FORMAT_BGRX8888: |
| case GBM_FORMAT_ARGB8888: |
| case GBM_FORMAT_ABGR8888: |
| case GBM_FORMAT_RGBA8888: |
| case GBM_FORMAT_BGRA8888: |
| return 1; |
| default: |
| LOG_VERBOSE("Format not supported: 0x%x\n", format); |
| return 0; |
| } |
| } |
| |
| static int magma_get_format_modifier_plane_count(struct gbm_device* device, uint32_t format, |
| uint64_t modifier) |
| { |
| switch (modifier) { |
| case I915_FORMAT_MOD_Y_TILED_CCS: |
| case I915_FORMAT_MOD_Yf_TILED_CCS: |
| return 2; |
| default: |
| return 1; |
| } |
| } |
| |
| static struct gbm_bo* magma_bo_create(struct gbm_device* device, uint32_t width, uint32_t height, |
| uint32_t format, uint32_t usage, const uint64_t* modifiers, |
| const unsigned int count) |
| { |
| if (count >= MAGMA_MAX_DRM_FORMAT_MODIFIERS) { |
| LOG_VERBOSE("count %u >= MAGMA_MAX_DRM_FORMAT_MODIFIERS\n", count); |
| return NULL; |
| } |
| |
| /* DRM formats match GBM formats */ |
| uint32_t drm_format = format; |
| |
| /* gbm_bo_create_with_modifiers doesn't let the user specify usage, so if modifiers are |
| * provided we assume the user may want their buffer to be presentable. |
| */ |
| bool presentable = (usage & GBM_BO_USE_SCANOUT) || (count > 0); |
| |
| magma_image_create_info_t create_info = { |
| .width = width, |
| .height = height, |
| .drm_format = drm_format, |
| .flags = presentable ? MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE : 0, |
| }; |
| |
| if (usage & GBM_BO_USE_LINEAR) { |
| create_info.drm_format_modifiers[0] = DRM_FORMAT_MOD_LINEAR; |
| create_info.drm_format_modifiers[1] = DRM_FORMAT_MOD_INVALID; |
| } else { |
| memcpy(create_info.drm_format_modifiers, modifiers, count * sizeof(uint64_t)); |
| create_info.drm_format_modifiers[count] = DRM_FORMAT_MOD_INVALID; |
| } |
| |
| magma_buffer_t image; |
| uint64_t size; |
| magma_buffer_id_t buffer_id; |
| magma_status_t status = magma_virt_connection_create_image( |
| magma_device(device)->connection, &create_info, &size, &image, &buffer_id); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_virt_create_image failed: %d", status); |
| return NULL; |
| } |
| |
| magma_image_info_t info; |
| status = magma_virt_connection_get_image_info(magma_device(device)->connection, image, &info); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_virt_get_image_info failed: %d", status); |
| magma_connection_release_buffer(magma_device(device)->connection, image); |
| return NULL; |
| } |
| |
| struct gbm_magma_bo* bo = malloc(sizeof(struct gbm_magma_bo)); |
| bo->image = image; |
| bo->info = info; |
| bo->size = size; |
| |
| bo->base.gbm = device; |
| bo->base.v0.width = width; |
| bo->base.v0.height = height; |
| bo->base.v0.format = format; |
| bo->base.v0.stride = info.plane_strides[0]; |
| bo->base.v0.handle.u64 = image; |
| bo->base.v0.user_data = NULL; |
| bo->base.v0.destroy_user_data = NULL; |
| |
| return &bo->base; |
| } |
| |
| static void magma_bo_destroy(struct gbm_bo* _bo) |
| { |
| struct gbm_magma_bo* bo = magma_bo(_bo); |
| struct gbm_magma_device* device = magma_device(bo->base.gbm); |
| |
| magma_connection_release_buffer(device->connection, bo->image); |
| |
| free(bo); |
| } |
| |
| static struct gbm_bo* magma_bo_import(struct gbm_device* device, uint32_t type, void* data, |
| uint32_t usage) |
| { |
| struct gbm_import_fd_modifier_data import_data = {}; |
| |
| switch (type) { |
| case GBM_BO_IMPORT_FD_MODIFIER: { |
| struct gbm_import_fd_modifier_data* fd_data = data; |
| if (fd_data->num_fds != 1) { |
| LOG_VERBOSE("Unhandled num_fds %d\n", fd_data->num_fds); |
| return NULL; |
| } |
| import_data = *fd_data; |
| break; |
| } |
| case GBM_BO_IMPORT_FD: { |
| struct gbm_import_fd_data* fd_data = data; |
| import_data.width = fd_data->width; |
| import_data.height = fd_data->height; |
| import_data.format = fd_data->format; |
| import_data.num_fds = 1; |
| import_data.fds[0] = fd_data->fd; |
| import_data.strides[0] = fd_data->stride; |
| import_data.modifier = DRM_FORMAT_MOD_INVALID; |
| break; |
| } |
| /* TODO(fxbug.dev/91126) - support for GBM_BO_IMPORT_WL_BUFFER/GBM_BO_IMPORT_EGL_IMAGE */ |
| default: |
| LOG_VERBOSE("Unhandled import type: %u\n", type); |
| return NULL; |
| } |
| |
| magma_buffer_t image; |
| uint64_t size; |
| magma_buffer_id_t buffer_id; |
| magma_status_t status = magma_connection_import_buffer( |
| magma_device(device)->connection, import_data.fds[0], &size, &image, &buffer_id); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_import failed: %d", status); |
| return NULL; |
| } |
| |
| magma_image_info_t info; |
| status = magma_virt_connection_get_image_info(magma_device(device)->connection, image, &info); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_virt_get_image_info failed: %d", status); |
| return NULL; |
| } |
| |
| struct gbm_magma_bo* bo = malloc(sizeof(struct gbm_magma_bo)); |
| bo->image = image; |
| // don't use the client given modifier |
| bo->info = info; |
| bo->size = size; |
| |
| bo->base.gbm = device; |
| bo->base.v0.width = import_data.width; |
| bo->base.v0.height = import_data.height; |
| bo->base.v0.format = import_data.format; |
| // don't use the given stride |
| bo->base.v0.stride = info.plane_strides[0]; |
| bo->base.v0.handle.u64 = image; |
| bo->base.v0.user_data = NULL; |
| bo->base.v0.destroy_user_data = NULL; |
| |
| return &bo->base; |
| } |
| |
| static int magma_bo_get_planes(struct gbm_bo* bo) |
| { |
| return magma_get_format_modifier_plane_count(bo->gbm, magma_bo(bo)->base.v0.format, |
| magma_bo(bo)->info.drm_format_modifier); |
| } |
| |
| static union gbm_bo_handle magma_bo_get_handle_for_plane(struct gbm_bo* bo, int plane) |
| { |
| // We don't support more than one memory plane |
| if (plane != 0) { |
| LOG_VERBOSE("Only one memory plane supported"); |
| union gbm_bo_handle handle; |
| handle.s32 = -1; |
| return handle; |
| } |
| |
| return magma_bo(bo)->base.v0.handle; |
| } |
| |
| static uint64_t magma_bo_get_modifier(struct gbm_bo* bo) |
| { |
| return magma_bo(bo)->info.drm_format_modifier; |
| } |
| |
| static int find_plane(struct gbm_bo* bo, int plane) |
| { |
| int found_plane = -1; |
| |
| for (int i = 0; i < MAGMA_MAX_IMAGE_PLANES; i++) { |
| if (i == 0 || magma_bo(bo)->info.plane_offsets[i]) { |
| if (++found_plane == plane) |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static uint32_t magma_bo_get_offset(struct gbm_bo* bo, int plane) |
| { |
| int plane_index = find_plane(bo, plane); |
| if (plane_index < 0) { |
| LOG_VERBOSE("Unhandled plane: %d\n", plane); |
| return 0; |
| } |
| return magma_bo(bo)->info.plane_offsets[plane_index]; |
| } |
| |
| static uint32_t magma_bo_get_stride(struct gbm_bo* bo, int plane) |
| { |
| int plane_index = find_plane(bo, plane); |
| if (plane_index < 0) { |
| LOG_VERBOSE("Unhandled plane: %d\n", plane); |
| return 0; |
| } |
| return magma_bo(bo)->info.plane_strides[plane_index]; |
| } |
| |
| static int magma_bo_get_plane_fd(struct gbm_bo* bo, int plane) |
| { |
| // We don't support more than one memory plane |
| if (plane != 0) { |
| LOG_VERBOSE("Only one memory plane supported"); |
| return -1; |
| } |
| |
| magma_handle_t handle; |
| magma_status_t status = magma_buffer_export(magma_bo(bo)->image, &handle); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_buffer_export failed: %d\n", status); |
| return -1; |
| } |
| int fd = handle; |
| return fd; |
| } |
| |
| static int magma_bo_get_fd(struct gbm_bo* bo) { return magma_bo_get_plane_fd(bo, 0); } |
| |
| struct Vma { |
| void* addr; |
| off_t offset; |
| size_t length; |
| uint32_t flags; |
| }; |
| |
| static void* magma_bo_map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width, |
| uint32_t height, uint32_t flags, uint32_t* stride, void** map_data) |
| { |
| magma_handle_t handle; |
| magma_status_t status = magma_buffer_get_handle(magma_bo(bo)->image, &handle); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_get_buffer_handle failed: %d", status); |
| return MAP_FAILED; |
| } |
| |
| if (width == 0 || height == 0) { |
| LOG_VERBOSE("Invalid width %u or height %u\n", width, height); |
| return MAP_FAILED; |
| } |
| |
| int fd = handle; |
| |
| uint32_t map_flags = 0; |
| if (flags & GBM_BO_TRANSFER_READ) |
| map_flags |= PROT_READ; |
| if (flags & GBM_BO_TRANSFER_WRITE) |
| map_flags |= PROT_WRITE; |
| |
| size_t offset = y * bo->v0.stride + x * bytes_per_pixel(); |
| size_t length = (height - 1) * bo->v0.stride + width * bytes_per_pixel(); |
| |
| /* Don't pass offset to mmap because it must be page aligned */ |
| void* addr = mmap(NULL, offset + length, map_flags, MAP_SHARED, fd, 0 /*offset*/); |
| |
| close(fd); |
| |
| if (addr == MAP_FAILED) { |
| LOG_VERBOSE("mmap failed: errno %d offset %lu length %lu buffer size %lu\n", errno, offset, |
| length, magma_bo(bo)->size); |
| return MAP_FAILED; |
| } |
| |
| struct Vma* vma = malloc(sizeof(struct Vma)); |
| vma->addr = addr; |
| vma->offset = offset; |
| vma->length = length; |
| vma->flags = flags; |
| |
| if ((flags & GBM_BO_TRANSFER_READ) && |
| magma_bo(bo)->info.coherency_domain == MAGMA_COHERENCY_DOMAIN_RAM) { |
| magma_status_t status = magma_buffer_clean_cache(magma_bo(bo)->image, vma->offset, vma->length, |
| MAGMA_CACHE_OPERATION_CLEAN_INVALIDATE); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_clean_cache failed: %d\n", status); |
| } |
| } |
| |
| *stride = bo->v0.stride; |
| *map_data = vma; |
| |
| return (uint8_t*)addr + offset; |
| } |
| |
| static void magma_bo_unmap(struct gbm_bo* bo, void* map_data) |
| { |
| struct Vma* vma = map_data; |
| |
| if ((vma->flags & GBM_BO_TRANSFER_WRITE) && |
| magma_bo(bo)->info.coherency_domain == MAGMA_COHERENCY_DOMAIN_RAM) { |
| magma_status_t status = magma_buffer_clean_cache(magma_bo(bo)->image, vma->offset, vma->length, |
| MAGMA_CACHE_OPERATION_CLEAN); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_clean_cache failed: %d\n", status); |
| } |
| } |
| |
| munmap(vma->addr, vma->length); |
| |
| free(vma); |
| } |
| |
| static int magma_bo_write(struct gbm_bo* bo, const void* buf, size_t data) |
| { |
| uint32_t stride; |
| void* map_data; |
| |
| void* addr = |
| magma_bo_map(bo, 0, 0, bo->v0.width, bo->v0.height, GBM_BO_TRANSFER_WRITE, &stride, &map_data); |
| if (addr == MAP_FAILED) |
| return -1; |
| |
| memcpy(addr, buf, data); |
| |
| magma_bo_unmap(bo, map_data); |
| |
| return 0; |
| } |
| |
| static struct gbm_surface* magma_surface_create(struct gbm_device* gbm, uint32_t width, |
| uint32_t height, uint32_t format, uint32_t flags, |
| const uint64_t* modifiers, const unsigned count) |
| { |
| LOG_VERBOSE("magma_surface_create unimplemented\n"); |
| assert(false); |
| return NULL; |
| } |
| |
| static struct gbm_bo* magma_surface_lock_front_buffer(struct gbm_surface* surface) |
| { |
| LOG_VERBOSE("magma_surface_lock_front_buffer unimplemented\n"); |
| assert(false); |
| return NULL; |
| } |
| |
| static void magma_surface_release_buffer(struct gbm_surface* surface, struct gbm_bo* bo) |
| { |
| LOG_VERBOSE("magma_surface_release_buffer unimplemented\n"); |
| assert(false); |
| } |
| |
| static int magma_surface_has_free_buffers(struct gbm_surface* surface) |
| { |
| LOG_VERBOSE("magma_surface_has_free_buffers unimplemented\n"); |
| assert(false); |
| return 0; |
| } |
| |
| static void magma_surface_destroy(struct gbm_surface* surface) |
| { |
| LOG_VERBOSE("magma_surface_destroy unimplemented\n"); |
| assert(false); |
| } |
| |
| static void magma_device_destroy(struct gbm_device* device) |
| { |
| magma_connection_release(magma_device(device)->connection); |
| free(magma_device(device)); |
| } |
| |
| static struct gbm_device* magma_device_create(int fd, uint32_t gbm_backend_version) |
| { |
| struct gbm_magma_device* device = calloc(1, sizeof(struct gbm_magma_device)); |
| if (!device) |
| return NULL; |
| |
| magma_device_t magma_device; |
| magma_status_t status = magma_device_import(fd, &magma_device); |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_device_import failed: %d", status); |
| return NULL; |
| } |
| |
| status = magma_device_create_connection(magma_device, &device->connection); |
| magma_device_release(magma_device); |
| |
| if (status != MAGMA_STATUS_OK) { |
| LOG_VERBOSE("magma_create_connection2 failed: %d", status); |
| return NULL; |
| } |
| |
| device->base.v0.fd = fd; |
| device->base.v0.name = "magma"; |
| device->base.v0.bo_create = magma_bo_create; |
| device->base.v0.bo_import = magma_bo_import; |
| device->base.v0.bo_map = magma_bo_map; |
| device->base.v0.bo_unmap = magma_bo_unmap; |
| device->base.v0.is_format_supported = magma_is_format_supported; |
| device->base.v0.get_format_modifier_plane_count = magma_get_format_modifier_plane_count; |
| device->base.v0.bo_write = magma_bo_write; |
| device->base.v0.bo_get_fd = magma_bo_get_fd; |
| device->base.v0.bo_get_planes = magma_bo_get_planes; |
| device->base.v0.bo_get_handle = magma_bo_get_handle_for_plane; |
| device->base.v0.bo_get_plane_fd = magma_bo_get_plane_fd; |
| device->base.v0.bo_get_stride = magma_bo_get_stride; |
| device->base.v0.bo_get_offset = magma_bo_get_offset; |
| device->base.v0.bo_get_modifier = magma_bo_get_modifier; |
| device->base.v0.bo_destroy = magma_bo_destroy; |
| device->base.v0.destroy = magma_device_destroy; |
| device->base.v0.surface_create = magma_surface_create; |
| device->base.v0.surface_destroy = magma_surface_destroy; |
| |
| return &device->base; |
| } |
| |
| const struct gbm_backend gbm_magma_backend = {.v0={ |
| .backend_name = "magma", |
| .create_device = magma_device_create, |
| }}; |