blob: 05ff49d79f0f7fe9efff2689f36ae158c61f1e0b [file] [log] [blame]
/*
* 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,
}};