android_external_minigbm/drv.c
Yiwei Zhang 8bc35bf9cc drv: check if a bo has lost all references in a 2nd pass
The same buffer can back multiple planes with different offsets. Thus
when we dereference the bo, we have to check again after all planes are
dereferenced.

This change also adds a missing unlock on an asserted return pass in
drv_bo_mapping_destroy.

BUG=b:201767377
TEST=atest android.media.cts.DecodeAccuracyTest#testGLViewDecodeAccuracy

Change-Id: I9dabf2f9b7ed33c9ed2f45b8ae498e98bfb0fc03
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/minigbm/+/3203235
Commit-Queue: Yiwei Zhang <zzyiwei@chromium.org>
Commit-Queue: Chia-I Wu <olv@google.com>
Tested-by: Yiwei Zhang <zzyiwei@chromium.org>
Auto-Submit: Yiwei Zhang <zzyiwei@chromium.org>
Reviewed-by: Chia-I Wu <olv@google.com>
2021-10-05 06:25:36 +00:00

787 lines
18 KiB
C

/*
* Copyright 2016 The Chromium OS 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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>
#ifdef __ANDROID__
#include <cutils/log.h>
#include <libgen.h>
#endif
#include "drv_helpers.h"
#include "drv_priv.h"
#include "util.h"
#ifdef DRV_AMDGPU
extern const struct backend backend_amdgpu;
#endif
#ifdef DRV_EXYNOS
extern const struct backend backend_exynos;
#endif
#ifdef DRV_I915
extern const struct backend backend_i915;
#endif
#ifdef DRV_MEDIATEK
extern const struct backend backend_mediatek;
#endif
#ifdef DRV_MSM
extern const struct backend backend_msm;
#endif
#ifdef DRV_ROCKCHIP
extern const struct backend backend_rockchip;
#endif
#ifdef DRV_VC4
extern const struct backend backend_vc4;
#endif
// Dumb / generic drivers
extern const struct backend backend_evdi;
extern const struct backend backend_marvell;
extern const struct backend backend_meson;
extern const struct backend backend_nouveau;
extern const struct backend backend_komeda;
extern const struct backend backend_radeon;
extern const struct backend backend_synaptics;
extern const struct backend backend_virtgpu;
extern const struct backend backend_udl;
extern const struct backend backend_vkms;
static const struct backend *drv_get_backend(int fd)
{
drmVersionPtr drm_version;
unsigned int i;
drm_version = drmGetVersion(fd);
if (!drm_version)
return NULL;
const struct backend *backend_list[] = {
#ifdef DRV_AMDGPU
&backend_amdgpu,
#endif
#ifdef DRV_EXYNOS
&backend_exynos,
#endif
#ifdef DRV_I915
&backend_i915,
#endif
#ifdef DRV_MEDIATEK
&backend_mediatek,
#endif
#ifdef DRV_MSM
&backend_msm,
#endif
#ifdef DRV_ROCKCHIP
&backend_rockchip,
#endif
#ifdef DRV_VC4
&backend_vc4,
#endif
&backend_evdi, &backend_marvell, &backend_meson, &backend_nouveau,
&backend_komeda, &backend_radeon, &backend_synaptics, &backend_virtgpu,
&backend_udl, &backend_virtgpu, &backend_vkms
};
for (i = 0; i < ARRAY_SIZE(backend_list); i++) {
const struct backend *b = backend_list[i];
if (!strcmp(drm_version->name, b->name)) {
drmFreeVersion(drm_version);
return b;
}
}
drmFreeVersion(drm_version);
return NULL;
}
struct driver *drv_create(int fd)
{
struct driver *drv;
int ret;
drv = (struct driver *)calloc(1, sizeof(*drv));
if (!drv)
return NULL;
char *minigbm_debug;
minigbm_debug = getenv("MINIGBM_DEBUG");
drv->compression = (minigbm_debug == NULL) || (strcmp(minigbm_debug, "nocompression") != 0);
drv->fd = fd;
drv->backend = drv_get_backend(fd);
if (!drv->backend)
goto free_driver;
if (pthread_mutex_init(&drv->buffer_table_lock, NULL))
goto free_driver;
drv->buffer_table = drmHashCreate();
if (!drv->buffer_table)
goto free_buffer_table_lock;
if (pthread_mutex_init(&drv->mappings_lock, NULL))
goto free_buffer_table;
drv->mappings = drv_array_init(sizeof(struct mapping));
if (!drv->mappings)
goto free_mappings_lock;
drv->combos = drv_array_init(sizeof(struct combination));
if (!drv->combos)
goto free_mappings;
if (drv->backend->init) {
ret = drv->backend->init(drv);
if (ret) {
drv_array_destroy(drv->combos);
goto free_mappings;
}
}
return drv;
free_mappings:
drv_array_destroy(drv->mappings);
free_mappings_lock:
pthread_mutex_destroy(&drv->mappings_lock);
free_buffer_table:
drmHashDestroy(drv->buffer_table);
free_buffer_table_lock:
pthread_mutex_destroy(&drv->buffer_table_lock);
free_driver:
free(drv);
return NULL;
}
void drv_destroy(struct driver *drv)
{
if (drv->backend->close)
drv->backend->close(drv);
drv_array_destroy(drv->combos);
drv_array_destroy(drv->mappings);
pthread_mutex_destroy(&drv->mappings_lock);
drmHashDestroy(drv->buffer_table);
pthread_mutex_destroy(&drv->buffer_table_lock);
free(drv);
}
int drv_get_fd(struct driver *drv)
{
return drv->fd;
}
const char *drv_get_name(struct driver *drv)
{
return drv->backend->name;
}
struct combination *drv_get_combination(struct driver *drv, uint32_t format, uint64_t use_flags)
{
struct combination *curr, *best;
if (format == DRM_FORMAT_NONE || use_flags == BO_USE_NONE)
return 0;
best = NULL;
uint32_t i;
for (i = 0; i < drv_array_size(drv->combos); i++) {
curr = drv_array_at_idx(drv->combos, i);
if ((format == curr->format) && use_flags == (curr->use_flags & use_flags))
if (!best || best->metadata.priority < curr->metadata.priority)
best = curr;
}
return best;
}
struct bo *drv_bo_new(struct driver *drv, uint32_t width, uint32_t height, uint32_t format,
uint64_t use_flags, bool is_test_buffer)
{
struct bo *bo;
bo = (struct bo *)calloc(1, sizeof(*bo));
if (!bo)
return NULL;
bo->drv = drv;
bo->meta.width = width;
bo->meta.height = height;
bo->meta.format = format;
bo->meta.use_flags = use_flags;
bo->meta.num_planes = drv_num_planes_from_format(format);
bo->is_test_buffer = is_test_buffer;
if (!bo->meta.num_planes) {
free(bo);
errno = EINVAL;
return NULL;
}
return bo;
}
static void drv_bo_mapping_destroy(struct bo *bo)
{
struct driver *drv = bo->drv;
uint32_t idx = 0;
/*
* This function is called right before the buffer is destroyed. It will free any mappings
* associated with the buffer.
*/
pthread_mutex_lock(&drv->mappings_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
while (idx < drv_array_size(drv->mappings)) {
struct mapping *mapping =
(struct mapping *)drv_array_at_idx(drv->mappings, idx);
if (mapping->vma->handle != bo->handles[plane].u32) {
idx++;
continue;
}
if (!--mapping->vma->refcount) {
int ret = drv->backend->bo_unmap(bo, mapping->vma);
if (ret) {
pthread_mutex_unlock(&drv->mappings_lock);
assert(ret);
drv_log("munmap failed\n");
return;
}
free(mapping->vma);
}
/* This shrinks and shifts the array, so don't increment idx. */
drv_array_remove(drv->mappings, idx);
}
}
pthread_mutex_unlock(&drv->mappings_lock);
}
/*
* Acquire a reference on plane buffers of the bo.
*/
static void drv_bo_acquire(struct bo *bo)
{
struct driver *drv = bo->drv;
pthread_mutex_lock(&drv->buffer_table_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
uintptr_t num = 0;
if (!drmHashLookup(drv->buffer_table, bo->handles[plane].u32, (void **)&num))
drmHashDelete(drv->buffer_table, bo->handles[plane].u32);
drmHashInsert(drv->buffer_table, bo->handles[plane].u32, (void *)(num + 1));
}
pthread_mutex_unlock(&drv->buffer_table_lock);
}
/*
* Release a reference on plane buffers of the bo. Return true when the bo has lost all its
* references. Otherwise, return false.
*/
static bool drv_bo_release(struct bo *bo)
{
struct driver *drv = bo->drv;
uintptr_t num;
pthread_mutex_lock(&drv->buffer_table_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
if (!drmHashLookup(drv->buffer_table, bo->handles[plane].u32, (void **)&num)) {
drmHashDelete(drv->buffer_table, bo->handles[plane].u32);
if (num > 1) {
drmHashInsert(drv->buffer_table, bo->handles[plane].u32,
(void *)(num - 1));
}
}
}
/* The same buffer can back multiple planes with different offsets. */
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
if (!drmHashLookup(drv->buffer_table, bo->handles[plane].u32, (void **)&num)) {
/* num is positive if found in the hashmap. */
pthread_mutex_unlock(&drv->buffer_table_lock);
return false;
}
}
pthread_mutex_unlock(&drv->buffer_table_lock);
return true;
}
struct bo *drv_bo_create(struct driver *drv, uint32_t width, uint32_t height, uint32_t format,
uint64_t use_flags)
{
int ret;
struct bo *bo;
bool is_test_alloc;
is_test_alloc = use_flags & BO_USE_TEST_ALLOC;
use_flags &= ~BO_USE_TEST_ALLOC;
bo = drv_bo_new(drv, width, height, format, use_flags, is_test_alloc);
if (!bo)
return NULL;
ret = -EINVAL;
if (drv->backend->bo_compute_metadata) {
ret = drv->backend->bo_compute_metadata(bo, width, height, format, use_flags, NULL,
0);
if (!is_test_alloc && ret == 0)
ret = drv->backend->bo_create_from_metadata(bo);
} else if (!is_test_alloc) {
ret = drv->backend->bo_create(bo, width, height, format, use_flags);
}
if (ret) {
errno = -ret;
free(bo);
return NULL;
}
drv_bo_acquire(bo);
return bo;
}
struct bo *drv_bo_create_with_modifiers(struct driver *drv, uint32_t width, uint32_t height,
uint32_t format, const uint64_t *modifiers, uint32_t count)
{
int ret;
struct bo *bo;
if (!drv->backend->bo_create_with_modifiers && !drv->backend->bo_compute_metadata) {
errno = ENOENT;
return NULL;
}
bo = drv_bo_new(drv, width, height, format, BO_USE_NONE, false);
if (!bo)
return NULL;
ret = -EINVAL;
if (drv->backend->bo_compute_metadata) {
ret = drv->backend->bo_compute_metadata(bo, width, height, format, BO_USE_NONE,
modifiers, count);
if (ret == 0)
ret = drv->backend->bo_create_from_metadata(bo);
} else {
ret = drv->backend->bo_create_with_modifiers(bo, width, height, format, modifiers,
count);
}
if (ret) {
free(bo);
return NULL;
}
drv_bo_acquire(bo);
return bo;
}
void drv_bo_destroy(struct bo *bo)
{
if (!bo->is_test_buffer && drv_bo_release(bo)) {
drv_bo_mapping_destroy(bo);
bo->drv->backend->bo_destroy(bo);
}
free(bo);
}
struct bo *drv_bo_import(struct driver *drv, struct drv_import_fd_data *data)
{
int ret;
size_t plane;
struct bo *bo;
off_t seek_end;
bo = drv_bo_new(drv, data->width, data->height, data->format, data->use_flags, false);
if (!bo)
return NULL;
ret = drv->backend->bo_import(bo, data);
if (ret) {
free(bo);
return NULL;
}
drv_bo_acquire(bo);
bo->meta.format_modifier = data->format_modifier;
for (plane = 0; plane < bo->meta.num_planes; plane++) {
bo->meta.strides[plane] = data->strides[plane];
bo->meta.offsets[plane] = data->offsets[plane];
seek_end = lseek(data->fds[plane], 0, SEEK_END);
if (seek_end == (off_t)(-1)) {
drv_log("lseek() failed with %s\n", strerror(errno));
goto destroy_bo;
}
lseek(data->fds[plane], 0, SEEK_SET);
if (plane == bo->meta.num_planes - 1 || data->offsets[plane + 1] == 0)
bo->meta.sizes[plane] = seek_end - data->offsets[plane];
else
bo->meta.sizes[plane] = data->offsets[plane + 1] - data->offsets[plane];
if ((int64_t)bo->meta.offsets[plane] + bo->meta.sizes[plane] > seek_end) {
drv_log("buffer size is too large.\n");
goto destroy_bo;
}
bo->meta.total_size += bo->meta.sizes[plane];
}
return bo;
destroy_bo:
drv_bo_destroy(bo);
return NULL;
}
void *drv_bo_map(struct bo *bo, const struct rectangle *rect, uint32_t map_flags,
struct mapping **map_data, size_t plane)
{
struct driver *drv = bo->drv;
uint32_t i;
uint8_t *addr;
struct mapping mapping = { 0 };
assert(rect->width >= 0);
assert(rect->height >= 0);
assert(rect->x + rect->width <= drv_bo_get_width(bo));
assert(rect->y + rect->height <= drv_bo_get_height(bo));
assert(BO_MAP_READ_WRITE & map_flags);
/* No CPU access for protected buffers. */
assert(!(bo->meta.use_flags & BO_USE_PROTECTED));
if (bo->is_test_buffer)
return MAP_FAILED;
mapping.rect = *rect;
mapping.refcount = 1;
pthread_mutex_lock(&drv->mappings_lock);
for (i = 0; i < drv_array_size(drv->mappings); i++) {
struct mapping *prior = (struct mapping *)drv_array_at_idx(drv->mappings, i);
if (prior->vma->handle != bo->handles[plane].u32 ||
prior->vma->map_flags != map_flags)
continue;
if (rect->x != prior->rect.x || rect->y != prior->rect.y ||
rect->width != prior->rect.width || rect->height != prior->rect.height)
continue;
prior->refcount++;
*map_data = prior;
goto exact_match;
}
for (i = 0; i < drv_array_size(drv->mappings); i++) {
struct mapping *prior = (struct mapping *)drv_array_at_idx(drv->mappings, i);
if (prior->vma->handle != bo->handles[plane].u32 ||
prior->vma->map_flags != map_flags)
continue;
prior->vma->refcount++;
mapping.vma = prior->vma;
goto success;
}
mapping.vma = calloc(1, sizeof(*mapping.vma));
if (!mapping.vma) {
*map_data = NULL;
pthread_mutex_unlock(&drv->mappings_lock);
return MAP_FAILED;
}
memcpy(mapping.vma->map_strides, bo->meta.strides, sizeof(mapping.vma->map_strides));
addr = drv->backend->bo_map(bo, mapping.vma, plane, map_flags);
if (addr == MAP_FAILED) {
*map_data = NULL;
free(mapping.vma);
pthread_mutex_unlock(&drv->mappings_lock);
return MAP_FAILED;
}
mapping.vma->refcount = 1;
mapping.vma->addr = addr;
mapping.vma->handle = bo->handles[plane].u32;
mapping.vma->map_flags = map_flags;
success:
*map_data = drv_array_append(drv->mappings, &mapping);
exact_match:
drv_bo_invalidate(bo, *map_data);
addr = (uint8_t *)((*map_data)->vma->addr);
addr += drv_bo_get_plane_offset(bo, plane);
pthread_mutex_unlock(&drv->mappings_lock);
return (void *)addr;
}
int drv_bo_unmap(struct bo *bo, struct mapping *mapping)
{
struct driver *drv = bo->drv;
uint32_t i;
int ret = 0;
pthread_mutex_lock(&drv->mappings_lock);
if (--mapping->refcount)
goto out;
if (!--mapping->vma->refcount) {
ret = drv->backend->bo_unmap(bo, mapping->vma);
free(mapping->vma);
}
for (i = 0; i < drv_array_size(drv->mappings); i++) {
if (mapping == (struct mapping *)drv_array_at_idx(drv->mappings, i)) {
drv_array_remove(drv->mappings, i);
break;
}
}
out:
pthread_mutex_unlock(&drv->mappings_lock);
return ret;
}
int drv_bo_invalidate(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
if (bo->drv->backend->bo_invalidate)
ret = bo->drv->backend->bo_invalidate(bo, mapping);
return ret;
}
int drv_bo_flush(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
if (bo->drv->backend->bo_flush)
ret = bo->drv->backend->bo_flush(bo, mapping);
return ret;
}
int drv_bo_flush_or_unmap(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
assert(!(bo->meta.use_flags & BO_USE_PROTECTED));
if (bo->drv->backend->bo_flush)
ret = bo->drv->backend->bo_flush(bo, mapping);
else
ret = drv_bo_unmap(bo, mapping);
return ret;
}
uint32_t drv_bo_get_width(struct bo *bo)
{
return bo->meta.width;
}
uint32_t drv_bo_get_height(struct bo *bo)
{
return bo->meta.height;
}
size_t drv_bo_get_num_planes(struct bo *bo)
{
return bo->meta.num_planes;
}
union bo_handle drv_bo_get_plane_handle(struct bo *bo, size_t plane)
{
return bo->handles[plane];
}
#ifndef DRM_RDWR
#define DRM_RDWR O_RDWR
#endif
int drv_bo_get_plane_fd(struct bo *bo, size_t plane)
{
int ret, fd;
assert(plane < bo->meta.num_planes);
if (bo->is_test_buffer)
return -EINVAL;
ret = drmPrimeHandleToFD(bo->drv->fd, bo->handles[plane].u32, DRM_CLOEXEC | DRM_RDWR, &fd);
// Older DRM implementations blocked DRM_RDWR, but gave a read/write mapping anyways
if (ret)
ret = drmPrimeHandleToFD(bo->drv->fd, bo->handles[plane].u32, DRM_CLOEXEC, &fd);
if (ret)
drv_log("Failed to get plane fd: %s\n", strerror(errno));
return (ret) ? ret : fd;
}
uint32_t drv_bo_get_plane_offset(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.offsets[plane];
}
uint32_t drv_bo_get_plane_size(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.sizes[plane];
}
uint32_t drv_bo_get_plane_stride(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.strides[plane];
}
uint64_t drv_bo_get_format_modifier(struct bo *bo)
{
return bo->meta.format_modifier;
}
uint32_t drv_bo_get_format(struct bo *bo)
{
return bo->meta.format;
}
uint32_t drv_bo_get_tiling(struct bo *bo)
{
return bo->meta.tiling;
}
uint64_t drv_bo_get_use_flags(struct bo *bo)
{
return bo->meta.use_flags;
}
size_t drv_bo_get_total_size(struct bo *bo)
{
return bo->meta.total_size;
}
/*
* Map internal fourcc codes back to standard fourcc codes.
*/
uint32_t drv_get_standard_fourcc(uint32_t fourcc_internal)
{
return (fourcc_internal == DRM_FORMAT_YVU420_ANDROID) ? DRM_FORMAT_YVU420 : fourcc_internal;
}
void drv_resolve_format_and_use_flags(struct driver *drv, uint32_t format, uint64_t use_flags,
uint32_t *out_format, uint64_t *out_use_flags)
{
assert(drv->backend->resolve_format_and_use_flags);
drv->backend->resolve_format_and_use_flags(drv, format, use_flags, out_format,
out_use_flags);
}
uint32_t drv_num_buffers_per_bo(struct bo *bo)
{
uint32_t count = 0;
size_t plane, p;
if (bo->is_test_buffer)
return 0;
for (plane = 0; plane < bo->meta.num_planes; plane++) {
for (p = 0; p < plane; p++)
if (bo->handles[p].u32 == bo->handles[plane].u32)
break;
if (p == plane)
count++;
}
return count;
}
void drv_log_prefix(const char *prefix, const char *file, int line, const char *format, ...)
{
char buf[50];
snprintf(buf, sizeof(buf), "[%s:%s(%d)]", prefix, basename(file), line);
va_list args;
va_start(args, format);
#ifdef __ANDROID__
__android_log_vprint(ANDROID_LOG_ERROR, buf, format, args);
#else
fprintf(stderr, "%s ", buf);
vfprintf(stderr, format, args);
#endif
va_end(args);
}
int drv_resource_info(struct bo *bo, uint32_t strides[DRV_MAX_PLANES],
uint32_t offsets[DRV_MAX_PLANES], uint64_t *format_modifier)
{
for (uint32_t plane = 0; plane < bo->meta.num_planes; plane++) {
strides[plane] = bo->meta.strides[plane];
offsets[plane] = bo->meta.offsets[plane];
}
*format_modifier = bo->meta.format_modifier;
if (bo->drv->backend->resource_info)
return bo->drv->backend->resource_info(bo, strides, offsets, format_modifier);
return 0;
}
uint32_t drv_get_max_texture_2d_size(struct driver *drv)
{
if (drv->backend->get_max_texture_2d_size)
return drv->backend->get_max_texture_2d_size(drv);
return UINT32_MAX;
}