diff options
Diffstat (limited to 'freedreno')
26 files changed, 4782 insertions, 0 deletions
diff --git a/freedreno/Android.mk b/freedreno/Android.mk new file mode 100644 index 0000000..2b582ae --- /dev/null +++ b/freedreno/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Import variables LIBDRM_FREEDRENO_FILES, LIBDRM_FREEDRENO_H_FILES +include $(LOCAL_PATH)/Makefile.sources + +LOCAL_MODULE := libdrm_freedreno + +LOCAL_SHARED_LIBRARIES := libdrm + +LOCAL_SRC_FILES := $(LIBDRM_FREEDRENO_FILES) + +include $(LIBDRM_COMMON_MK) +include $(BUILD_SHARED_LIBRARY) diff --git a/freedreno/Makefile.sources b/freedreno/Makefile.sources new file mode 100644 index 0000000..ca89511 --- /dev/null +++ b/freedreno/Makefile.sources @@ -0,0 +1,25 @@ +LIBDRM_FREEDRENO_FILES := \ + freedreno_device.c \ + freedreno_pipe.c \ + freedreno_priv.h \ + freedreno_ringbuffer.c \ + freedreno_bo.c \ + freedreno_bo_cache.c \ + msm/msm_bo.c \ + msm/msm_device.c \ + msm/msm_pipe.c \ + msm/msm_priv.h \ + msm/msm_ringbuffer.c + +LIBDRM_FREEDRENO_KGSL_FILES := \ + kgsl/kgsl_bo.c \ + kgsl/kgsl_device.c \ + kgsl/kgsl_drm.h \ + kgsl/kgsl_pipe.c \ + kgsl/kgsl_priv.h \ + kgsl/kgsl_ringbuffer.c \ + kgsl/msm_kgsl.h + +LIBDRM_FREEDRENO_H_FILES := \ + freedreno_drmif.h \ + freedreno_ringbuffer.h diff --git a/freedreno/freedreno-symbols.txt b/freedreno/freedreno-symbols.txt new file mode 100644 index 0000000..471ca99 --- /dev/null +++ b/freedreno/freedreno-symbols.txt @@ -0,0 +1,45 @@ +fd_bo_cpu_fini +fd_bo_cpu_prep +fd_bo_del +fd_bo_dmabuf +fd_bo_from_dmabuf +fd_bo_from_fbdev +fd_bo_from_handle +fd_bo_from_name +fd_bo_get_iova +fd_bo_get_name +fd_bo_handle +fd_bo_map +fd_bo_new +fd_bo_put_iova +fd_bo_ref +fd_bo_size +fd_device_del +fd_device_fd +fd_device_new +fd_device_new_dup +fd_device_ref +fd_device_version +fd_pipe_del +fd_pipe_get_param +fd_pipe_new +fd_pipe_new2 +fd_pipe_ref +fd_pipe_wait +fd_pipe_wait_timeout +fd_ringbuffer_cmd_count +fd_ringbuffer_del +fd_ringbuffer_emit_reloc_ring_full +fd_ringbuffer_flush +fd_ringbuffer_grow +fd_ringbuffer_new +fd_ringbuffer_new_flags +fd_ringbuffer_new_object +fd_ringbuffer_ref +fd_ringbuffer_reloc +fd_ringbuffer_reloc2 +fd_ringbuffer_reset +fd_ringbuffer_set_parent +fd_ringbuffer_size +fd_ringbuffer_timestamp +fd_ringbuffer_flush2 diff --git a/freedreno/freedreno_bo.c b/freedreno/freedreno_bo.c new file mode 100644 index 0000000..3cdc973 --- /dev/null +++ b/freedreno/freedreno_bo.c @@ -0,0 +1,359 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "freedreno_drmif.h" +#include "freedreno_priv.h" + +drm_private pthread_mutex_t table_lock = PTHREAD_MUTEX_INITIALIZER; +drm_private void bo_del(struct fd_bo *bo); + +/* set buffer name, and add to table, call w/ table_lock held: */ +static void set_name(struct fd_bo *bo, uint32_t name) +{ + bo->name = name; + /* add ourself into the handle table: */ + drmHashInsert(bo->dev->name_table, name, bo); +} + +/* lookup a buffer, call w/ table_lock held: */ +static struct fd_bo * lookup_bo(void *tbl, uint32_t key) +{ + struct fd_bo *bo = NULL; + if (!drmHashLookup(tbl, key, (void **)&bo)) { + /* found, incr refcnt and return: */ + bo = fd_bo_ref(bo); + + /* don't break the bucket if this bo was found in one */ + list_delinit(&bo->list); + } + return bo; +} + +/* allocate a new buffer object, call w/ table_lock held */ +static struct fd_bo * bo_from_handle(struct fd_device *dev, + uint32_t size, uint32_t handle) +{ + struct fd_bo *bo; + + bo = dev->funcs->bo_from_handle(dev, size, handle); + if (!bo) { + drmCloseBufferHandle(dev->fd, handle); + return NULL; + } + bo->dev = fd_device_ref(dev); + bo->size = size; + bo->handle = handle; + atomic_set(&bo->refcnt, 1); + list_inithead(&bo->list); + /* add ourself into the handle table: */ + drmHashInsert(dev->handle_table, handle, bo); + return bo; +} + +static struct fd_bo * +bo_new(struct fd_device *dev, uint32_t size, uint32_t flags, + struct fd_bo_cache *cache) +{ + struct fd_bo *bo = NULL; + uint32_t handle; + int ret; + + bo = fd_bo_cache_alloc(cache, &size, flags); + if (bo) + return bo; + + ret = dev->funcs->bo_new_handle(dev, size, flags, &handle); + if (ret) + return NULL; + + pthread_mutex_lock(&table_lock); + bo = bo_from_handle(dev, size, handle); + pthread_mutex_unlock(&table_lock); + + VG_BO_ALLOC(bo); + + return bo; +} + +drm_public struct fd_bo * +fd_bo_new(struct fd_device *dev, uint32_t size, uint32_t flags) +{ + struct fd_bo *bo = bo_new(dev, size, flags, &dev->bo_cache); + if (bo) + bo->bo_reuse = BO_CACHE; + return bo; +} + +/* internal function to allocate bo's that use the ringbuffer cache + * instead of the normal bo_cache. The purpose is, because cmdstream + * bo's get vmap'd on the kernel side, and that is expensive, we want + * to re-use cmdstream bo's for cmdstream and not unrelated purposes. + */ +drm_private struct fd_bo * +fd_bo_new_ring(struct fd_device *dev, uint32_t size, uint32_t flags) +{ + struct fd_bo *bo = bo_new(dev, size, flags, &dev->ring_cache); + if (bo) + bo->bo_reuse = RING_CACHE; + return bo; +} + +drm_public struct fd_bo * +fd_bo_from_handle(struct fd_device *dev, uint32_t handle, uint32_t size) +{ + struct fd_bo *bo = NULL; + + pthread_mutex_lock(&table_lock); + + bo = lookup_bo(dev->handle_table, handle); + if (bo) + goto out_unlock; + + bo = bo_from_handle(dev, size, handle); + + VG_BO_ALLOC(bo); + +out_unlock: + pthread_mutex_unlock(&table_lock); + + return bo; +} + +drm_public struct fd_bo * +fd_bo_from_dmabuf(struct fd_device *dev, int fd) +{ + int ret, size; + uint32_t handle; + struct fd_bo *bo; + + pthread_mutex_lock(&table_lock); + ret = drmPrimeFDToHandle(dev->fd, fd, &handle); + if (ret) { + pthread_mutex_unlock(&table_lock); + return NULL; + } + + bo = lookup_bo(dev->handle_table, handle); + if (bo) + goto out_unlock; + + /* lseek() to get bo size */ + size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_CUR); + + bo = bo_from_handle(dev, size, handle); + + VG_BO_ALLOC(bo); + +out_unlock: + pthread_mutex_unlock(&table_lock); + + return bo; +} + +drm_public struct fd_bo * fd_bo_from_name(struct fd_device *dev, uint32_t name) +{ + struct drm_gem_open req = { + .name = name, + }; + struct fd_bo *bo; + + pthread_mutex_lock(&table_lock); + + /* check name table first, to see if bo is already open: */ + bo = lookup_bo(dev->name_table, name); + if (bo) + goto out_unlock; + + if (drmIoctl(dev->fd, DRM_IOCTL_GEM_OPEN, &req)) { + ERROR_MSG("gem-open failed: %s", strerror(errno)); + goto out_unlock; + } + + bo = lookup_bo(dev->handle_table, req.handle); + if (bo) + goto out_unlock; + + bo = bo_from_handle(dev, req.size, req.handle); + if (bo) { + set_name(bo, name); + VG_BO_ALLOC(bo); + } + +out_unlock: + pthread_mutex_unlock(&table_lock); + + return bo; +} + +drm_public uint64_t fd_bo_get_iova(struct fd_bo *bo) +{ + return bo->funcs->iova(bo); +} + +drm_public void fd_bo_put_iova(struct fd_bo *bo) +{ + /* currently a no-op */ +} + +drm_public struct fd_bo * fd_bo_ref(struct fd_bo *bo) +{ + atomic_inc(&bo->refcnt); + return bo; +} + +drm_public void fd_bo_del(struct fd_bo *bo) +{ + struct fd_device *dev = bo->dev; + + if (!atomic_dec_and_test(&bo->refcnt)) + return; + + pthread_mutex_lock(&table_lock); + + if ((bo->bo_reuse == BO_CACHE) && (fd_bo_cache_free(&dev->bo_cache, bo) == 0)) + goto out; + if ((bo->bo_reuse == RING_CACHE) && (fd_bo_cache_free(&dev->ring_cache, bo) == 0)) + goto out; + + bo_del(bo); + fd_device_del_locked(dev); +out: + pthread_mutex_unlock(&table_lock); +} + +/* Called under table_lock */ +drm_private void bo_del(struct fd_bo *bo) +{ + VG_BO_FREE(bo); + + if (bo->map) + drm_munmap(bo->map, bo->size); + + /* TODO probably bo's in bucket list get removed from + * handle table?? + */ + + if (bo->handle) { + drmHashDelete(bo->dev->handle_table, bo->handle); + if (bo->name) + drmHashDelete(bo->dev->name_table, bo->name); + drmCloseBufferHandle(bo->dev->fd, bo->handle); + } + + bo->funcs->destroy(bo); +} + +drm_public int fd_bo_get_name(struct fd_bo *bo, uint32_t *name) +{ + if (!bo->name) { + struct drm_gem_flink req = { + .handle = bo->handle, + }; + int ret; + + ret = drmIoctl(bo->dev->fd, DRM_IOCTL_GEM_FLINK, &req); + if (ret) { + return ret; + } + + pthread_mutex_lock(&table_lock); + set_name(bo, req.name); + pthread_mutex_unlock(&table_lock); + bo->bo_reuse = NO_CACHE; + } + + *name = bo->name; + + return 0; +} + +drm_public uint32_t fd_bo_handle(struct fd_bo *bo) +{ + return bo->handle; +} + +drm_public int fd_bo_dmabuf(struct fd_bo *bo) +{ + int ret, prime_fd; + + ret = drmPrimeHandleToFD(bo->dev->fd, bo->handle, DRM_CLOEXEC, + &prime_fd); + if (ret) { + ERROR_MSG("failed to get dmabuf fd: %d", ret); + return ret; + } + + bo->bo_reuse = NO_CACHE; + + return prime_fd; +} + +drm_public uint32_t fd_bo_size(struct fd_bo *bo) +{ + return bo->size; +} + +drm_public void * fd_bo_map(struct fd_bo *bo) +{ + if (!bo->map) { + uint64_t offset; + int ret; + + ret = bo->funcs->offset(bo, &offset); + if (ret) { + return NULL; + } + + bo->map = drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, + bo->dev->fd, offset); + if (bo->map == MAP_FAILED) { + ERROR_MSG("mmap failed: %s", strerror(errno)); + bo->map = NULL; + } + } + return bo->map; +} + +/* a bit odd to take the pipe as an arg, but it's a, umm, quirk of kgsl.. */ +drm_public int fd_bo_cpu_prep(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op) +{ + return bo->funcs->cpu_prep(bo, pipe, op); +} + +drm_public void fd_bo_cpu_fini(struct fd_bo *bo) +{ + bo->funcs->cpu_fini(bo); +} + +#if !HAVE_FREEDRENO_KGSL +drm_public struct fd_bo * fd_bo_from_fbdev(struct fd_pipe *pipe, int fbfd, uint32_t size) +{ + return NULL; +} +#endif diff --git a/freedreno/freedreno_bo_cache.c b/freedreno/freedreno_bo_cache.c new file mode 100644 index 0000000..bb0605a --- /dev/null +++ b/freedreno/freedreno_bo_cache.c @@ -0,0 +1,220 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2016 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "freedreno_drmif.h" +#include "freedreno_priv.h" + +drm_private void bo_del(struct fd_bo *bo); +drm_private extern pthread_mutex_t table_lock; + +static void +add_bucket(struct fd_bo_cache *cache, int size) +{ + unsigned int i = cache->num_buckets; + + assert(i < ARRAY_SIZE(cache->cache_bucket)); + + list_inithead(&cache->cache_bucket[i].list); + cache->cache_bucket[i].size = size; + cache->num_buckets++; +} + +/** + * @coarse: if true, only power-of-two bucket sizes, otherwise + * fill in for a bit smoother size curve.. + */ +drm_private void +fd_bo_cache_init(struct fd_bo_cache *cache, int coarse) +{ + unsigned long size, cache_max_size = 64 * 1024 * 1024; + + /* OK, so power of two buckets was too wasteful of memory. + * Give 3 other sizes between each power of two, to hopefully + * cover things accurately enough. (The alternative is + * probably to just go for exact matching of sizes, and assume + * that for things like composited window resize the tiled + * width/height alignment and rounding of sizes to pages will + * get us useful cache hit rates anyway) + */ + add_bucket(cache, 4096); + add_bucket(cache, 4096 * 2); + if (!coarse) + add_bucket(cache, 4096 * 3); + + /* Initialize the linked lists for BO reuse cache. */ + for (size = 4 * 4096; size <= cache_max_size; size *= 2) { + add_bucket(cache, size); + if (!coarse) { + add_bucket(cache, size + size * 1 / 4); + add_bucket(cache, size + size * 2 / 4); + add_bucket(cache, size + size * 3 / 4); + } + } +} + +/* Frees older cached buffers. Called under table_lock */ +drm_private void +fd_bo_cache_cleanup(struct fd_bo_cache *cache, time_t time) +{ + int i; + + if (cache->time == time) + return; + + for (i = 0; i < cache->num_buckets; i++) { + struct fd_bo_bucket *bucket = &cache->cache_bucket[i]; + struct fd_bo *bo; + + while (!LIST_IS_EMPTY(&bucket->list)) { + bo = LIST_ENTRY(struct fd_bo, bucket->list.next, list); + + /* keep things in cache for at least 1 second: */ + if (time && ((time - bo->free_time) <= 1)) + break; + + VG_BO_OBTAIN(bo); + list_del(&bo->list); + bo_del(bo); + } + } + + cache->time = time; +} + +static struct fd_bo_bucket * get_bucket(struct fd_bo_cache *cache, uint32_t size) +{ + int i; + + /* hmm, this is what intel does, but I suppose we could calculate our + * way to the correct bucket size rather than looping.. + */ + for (i = 0; i < cache->num_buckets; i++) { + struct fd_bo_bucket *bucket = &cache->cache_bucket[i]; + if (bucket->size >= size) { + return bucket; + } + } + + return NULL; +} + +static int is_idle(struct fd_bo *bo) +{ + return fd_bo_cpu_prep(bo, NULL, + DRM_FREEDRENO_PREP_READ | + DRM_FREEDRENO_PREP_WRITE | + DRM_FREEDRENO_PREP_NOSYNC) == 0; +} + +static struct fd_bo *find_in_bucket(struct fd_bo_bucket *bucket, uint32_t flags) +{ + struct fd_bo *bo = NULL; + + /* TODO .. if we had an ALLOC_FOR_RENDER flag like intel, we could + * skip the busy check.. if it is only going to be a render target + * then we probably don't need to stall.. + * + * NOTE that intel takes ALLOC_FOR_RENDER bo's from the list tail + * (MRU, since likely to be in GPU cache), rather than head (LRU).. + */ + pthread_mutex_lock(&table_lock); + if (!LIST_IS_EMPTY(&bucket->list)) { + bo = LIST_ENTRY(struct fd_bo, bucket->list.next, list); + /* TODO check for compatible flags? */ + if (is_idle(bo)) { + list_del(&bo->list); + } else { + bo = NULL; + } + } + pthread_mutex_unlock(&table_lock); + + return bo; +} + +/* NOTE: size is potentially rounded up to bucket size: */ +drm_private struct fd_bo * +fd_bo_cache_alloc(struct fd_bo_cache *cache, uint32_t *size, uint32_t flags) +{ + struct fd_bo *bo = NULL; + struct fd_bo_bucket *bucket; + + *size = ALIGN(*size, 4096); + bucket = get_bucket(cache, *size); + + /* see if we can be green and recycle: */ +retry: + if (bucket) { + *size = bucket->size; + bo = find_in_bucket(bucket, flags); + if (bo) { + VG_BO_OBTAIN(bo); + if (bo->funcs->madvise(bo, TRUE) <= 0) { + /* we've lost the backing pages, delete and try again: */ + pthread_mutex_lock(&table_lock); + bo_del(bo); + pthread_mutex_unlock(&table_lock); + goto retry; + } + atomic_set(&bo->refcnt, 1); + fd_device_ref(bo->dev); + return bo; + } + } + + return NULL; +} + +drm_private int +fd_bo_cache_free(struct fd_bo_cache *cache, struct fd_bo *bo) +{ + struct fd_bo_bucket *bucket = get_bucket(cache, bo->size); + + /* see if we can be green and recycle: */ + if (bucket) { + struct timespec time; + + bo->funcs->madvise(bo, FALSE); + + clock_gettime(CLOCK_MONOTONIC, &time); + + bo->free_time = time.tv_sec; + VG_BO_RELEASE(bo); + list_addtail(&bo->list, &bucket->list); + fd_bo_cache_cleanup(cache, time.tv_sec); + + /* bo's in the bucket cache don't have a ref and + * don't hold a ref to the dev: + */ + fd_device_del_locked(bo->dev); + + return 0; + } + + return -1; +} diff --git a/freedreno/freedreno_device.c b/freedreno/freedreno_device.c new file mode 100644 index 0000000..ac23430 --- /dev/null +++ b/freedreno/freedreno_device.c @@ -0,0 +1,145 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "freedreno_drmif.h" +#include "freedreno_priv.h" + +static pthread_mutex_t table_lock = PTHREAD_MUTEX_INITIALIZER; + +struct fd_device * kgsl_device_new(int fd); +struct fd_device * msm_device_new(int fd); + +drm_public struct fd_device * fd_device_new(int fd) +{ + struct fd_device *dev; + drmVersionPtr version; + + /* figure out if we are kgsl or msm drm driver: */ + version = drmGetVersion(fd); + if (!version) { + ERROR_MSG("cannot get version: %s", strerror(errno)); + return NULL; + } + + if (!strcmp(version->name, "msm")) { + DEBUG_MSG("msm DRM device"); + if (version->version_major != 1) { + ERROR_MSG("unsupported version: %u.%u.%u", version->version_major, + version->version_minor, version->version_patchlevel); + dev = NULL; + goto out; + } + + dev = msm_device_new(fd); + dev->version = version->version_minor; +#if HAVE_FREEDRENO_KGSL + } else if (!strcmp(version->name, "kgsl")) { + DEBUG_MSG("kgsl DRM device"); + dev = kgsl_device_new(fd); +#endif + } else { + ERROR_MSG("unknown device: %s", version->name); + dev = NULL; + } + +out: + drmFreeVersion(version); + + if (!dev) + return NULL; + + atomic_set(&dev->refcnt, 1); + dev->fd = fd; + dev->handle_table = drmHashCreate(); + dev->name_table = drmHashCreate(); + fd_bo_cache_init(&dev->bo_cache, FALSE); + fd_bo_cache_init(&dev->ring_cache, TRUE); + + return dev; +} + +/* like fd_device_new() but creates it's own private dup() of the fd + * which is close()d when the device is finalized. + */ +drm_public struct fd_device * fd_device_new_dup(int fd) +{ + int dup_fd = dup(fd); + struct fd_device *dev = fd_device_new(dup_fd); + if (dev) + dev->closefd = 1; + else + close(dup_fd); + return dev; +} + +drm_public struct fd_device * fd_device_ref(struct fd_device *dev) +{ + atomic_inc(&dev->refcnt); + return dev; +} + +static void fd_device_del_impl(struct fd_device *dev) +{ + int close_fd = dev->closefd ? dev->fd : -1; + fd_bo_cache_cleanup(&dev->bo_cache, 0); + drmHashDestroy(dev->handle_table); + drmHashDestroy(dev->name_table); + dev->funcs->destroy(dev); + if (close_fd >= 0) + close(close_fd); +} + +drm_private void fd_device_del_locked(struct fd_device *dev) +{ + if (!atomic_dec_and_test(&dev->refcnt)) + return; + fd_device_del_impl(dev); +} + +drm_public void fd_device_del(struct fd_device *dev) +{ + if (!atomic_dec_and_test(&dev->refcnt)) + return; + pthread_mutex_lock(&table_lock); + fd_device_del_impl(dev); + pthread_mutex_unlock(&table_lock); +} + +drm_public int fd_device_fd(struct fd_device *dev) +{ + return dev->fd; +} + +drm_public enum fd_version fd_device_version(struct fd_device *dev) +{ + return dev->version; +} diff --git a/freedreno/freedreno_drmif.h b/freedreno/freedreno_drmif.h new file mode 100644 index 0000000..c95c21b --- /dev/null +++ b/freedreno/freedreno_drmif.h @@ -0,0 +1,140 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#ifndef FREEDRENO_DRMIF_H_ +#define FREEDRENO_DRMIF_H_ + +#include <xf86drm.h> +#include <stdint.h> + +#if defined(__GNUC__) +# define drm_deprecated __attribute__((__deprecated__)) +#else +# define drm_deprecated +#endif + +/* an empty marker for things that will be deprecated in the future: */ +#define will_be_deprecated + +struct fd_bo; +struct fd_pipe; +struct fd_device; + +enum fd_pipe_id { + FD_PIPE_3D = 1, + FD_PIPE_2D = 2, + /* some devices have two 2d blocks.. not really sure how to + * use that yet, so just ignoring the 2nd 2d pipe for now + */ + FD_PIPE_MAX +}; + +enum fd_param_id { + FD_DEVICE_ID, + FD_GMEM_SIZE, + FD_GPU_ID, + FD_CHIP_ID, + FD_MAX_FREQ, + FD_TIMESTAMP, + FD_NR_RINGS, /* # of rings == # of distinct priority levels */ +}; + +/* bo flags: */ +#define DRM_FREEDRENO_GEM_TYPE_SMI 0x00000001 +#define DRM_FREEDRENO_GEM_TYPE_KMEM 0x00000002 +#define DRM_FREEDRENO_GEM_TYPE_MEM_MASK 0x0000000f +#define DRM_FREEDRENO_GEM_CACHE_NONE 0x00000000 +#define DRM_FREEDRENO_GEM_CACHE_WCOMBINE 0x00100000 +#define DRM_FREEDRENO_GEM_CACHE_WTHROUGH 0x00200000 +#define DRM_FREEDRENO_GEM_CACHE_WBACK 0x00400000 +#define DRM_FREEDRENO_GEM_CACHE_WBACKWA 0x00800000 +#define DRM_FREEDRENO_GEM_CACHE_MASK 0x00f00000 +#define DRM_FREEDRENO_GEM_GPUREADONLY 0x01000000 + +/* bo access flags: (keep aligned to MSM_PREP_x) */ +#define DRM_FREEDRENO_PREP_READ 0x01 +#define DRM_FREEDRENO_PREP_WRITE 0x02 +#define DRM_FREEDRENO_PREP_NOSYNC 0x04 + +/* device functions: + */ + +struct fd_device * fd_device_new(int fd); +struct fd_device * fd_device_new_dup(int fd); +struct fd_device * fd_device_ref(struct fd_device *dev); +void fd_device_del(struct fd_device *dev); +int fd_device_fd(struct fd_device *dev); + +enum fd_version { + FD_VERSION_MADVISE = 1, /* kernel supports madvise */ + FD_VERSION_UNLIMITED_CMDS = 1, /* submits w/ >4 cmd buffers (growable ringbuffer) */ + FD_VERSION_FENCE_FD = 2, /* submit command supports in/out fences */ + FD_VERSION_SUBMIT_QUEUES = 3, /* submit queues and multiple priority levels */ + FD_VERSION_BO_IOVA = 3, /* supports fd_bo_get/put_iova() */ +}; +enum fd_version fd_device_version(struct fd_device *dev); + +/* pipe functions: + */ + +struct fd_pipe * fd_pipe_new(struct fd_device *dev, enum fd_pipe_id id); +struct fd_pipe * fd_pipe_new2(struct fd_device *dev, enum fd_pipe_id id, uint32_t prio); +struct fd_pipe * fd_pipe_ref(struct fd_pipe *pipe); +void fd_pipe_del(struct fd_pipe *pipe); +int fd_pipe_get_param(struct fd_pipe *pipe, enum fd_param_id param, + uint64_t *value); +int fd_pipe_wait(struct fd_pipe *pipe, uint32_t timestamp); +/* timeout in nanosec */ +int fd_pipe_wait_timeout(struct fd_pipe *pipe, uint32_t timestamp, + uint64_t timeout); + + +/* buffer-object functions: + */ + +struct fd_bo * fd_bo_new(struct fd_device *dev, + uint32_t size, uint32_t flags); +struct fd_bo * fd_bo_from_fbdev(struct fd_pipe *pipe, + int fbfd, uint32_t size); +struct fd_bo *fd_bo_from_handle(struct fd_device *dev, + uint32_t handle, uint32_t size); +struct fd_bo * fd_bo_from_name(struct fd_device *dev, uint32_t name); +struct fd_bo * fd_bo_from_dmabuf(struct fd_device *dev, int fd); +uint64_t fd_bo_get_iova(struct fd_bo *bo); +void fd_bo_put_iova(struct fd_bo *bo); +struct fd_bo * fd_bo_ref(struct fd_bo *bo); +void fd_bo_del(struct fd_bo *bo); +int fd_bo_get_name(struct fd_bo *bo, uint32_t *name); +uint32_t fd_bo_handle(struct fd_bo *bo); +int fd_bo_dmabuf(struct fd_bo *bo); +uint32_t fd_bo_size(struct fd_bo *bo); +void * fd_bo_map(struct fd_bo *bo); +int fd_bo_cpu_prep(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op); +void fd_bo_cpu_fini(struct fd_bo *bo); + +#endif /* FREEDRENO_DRMIF_H_ */ diff --git a/freedreno/freedreno_pipe.c b/freedreno/freedreno_pipe.c new file mode 100644 index 0000000..e82e644 --- /dev/null +++ b/freedreno/freedreno_pipe.c @@ -0,0 +1,102 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "freedreno_drmif.h" +#include "freedreno_priv.h" + +/** + * priority of zero is highest priority, and higher numeric values are + * lower priorities + */ +drm_public struct fd_pipe * +fd_pipe_new2(struct fd_device *dev, enum fd_pipe_id id, uint32_t prio) +{ + struct fd_pipe *pipe; + uint64_t val; + + if (id > FD_PIPE_MAX) { + ERROR_MSG("invalid pipe id: %d", id); + return NULL; + } + + if ((prio != 1) && (fd_device_version(dev) < FD_VERSION_SUBMIT_QUEUES)) { + ERROR_MSG("invalid priority!"); + return NULL; + } + + pipe = dev->funcs->pipe_new(dev, id, prio); + if (!pipe) { + ERROR_MSG("allocation failed"); + return NULL; + } + + pipe->dev = dev; + pipe->id = id; + atomic_set(&pipe->refcnt, 1); + + fd_pipe_get_param(pipe, FD_GPU_ID, &val); + pipe->gpu_id = val; + + return pipe; +} + +drm_public struct fd_pipe * +fd_pipe_new(struct fd_device *dev, enum fd_pipe_id id) +{ + return fd_pipe_new2(dev, id, 1); +} + +drm_public struct fd_pipe * fd_pipe_ref(struct fd_pipe *pipe) +{ + atomic_inc(&pipe->refcnt); + return pipe; +} + +drm_public void fd_pipe_del(struct fd_pipe *pipe) +{ + if (!atomic_dec_and_test(&pipe->refcnt)) + return; + pipe->funcs->destroy(pipe); +} + +drm_public int fd_pipe_get_param(struct fd_pipe *pipe, + enum fd_param_id param, uint64_t *value) +{ + return pipe->funcs->get_param(pipe, param, value); +} + +drm_public int fd_pipe_wait(struct fd_pipe *pipe, uint32_t timestamp) +{ + return fd_pipe_wait_timeout(pipe, timestamp, ~0); +} + +drm_public int fd_pipe_wait_timeout(struct fd_pipe *pipe, uint32_t timestamp, + uint64_t timeout) +{ + return pipe->funcs->wait(pipe, timestamp, timeout); +} diff --git a/freedreno/freedreno_priv.h b/freedreno/freedreno_priv.h new file mode 100644 index 0000000..b8eac4b --- /dev/null +++ b/freedreno/freedreno_priv.h @@ -0,0 +1,258 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#ifndef FREEDRENO_PRIV_H_ +#define FREEDRENO_PRIV_H_ + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <pthread.h> +#include <stdio.h> +#include <assert.h> + +#include "libdrm_macros.h" +#include "xf86drm.h" +#include "xf86atomic.h" + +#include "util_double_list.h" +#include "util_math.h" + +#include "freedreno_drmif.h" +#include "freedreno_ringbuffer.h" +#include "drm.h" + +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +struct fd_device_funcs { + int (*bo_new_handle)(struct fd_device *dev, uint32_t size, + uint32_t flags, uint32_t *handle); + struct fd_bo * (*bo_from_handle)(struct fd_device *dev, + uint32_t size, uint32_t handle); + struct fd_pipe * (*pipe_new)(struct fd_device *dev, enum fd_pipe_id id, + unsigned prio); + void (*destroy)(struct fd_device *dev); +}; + +struct fd_bo_bucket { + uint32_t size; + struct list_head list; +}; + +struct fd_bo_cache { + struct fd_bo_bucket cache_bucket[14 * 4]; + int num_buckets; + time_t time; +}; + +struct fd_device { + int fd; + enum fd_version version; + atomic_t refcnt; + + /* tables to keep track of bo's, to avoid "evil-twin" fd_bo objects: + * + * handle_table: maps handle to fd_bo + * name_table: maps flink name to fd_bo + * + * We end up needing two tables, because DRM_IOCTL_GEM_OPEN always + * returns a new handle. So we need to figure out if the bo is already + * open in the process first, before calling gem-open. + */ + void *handle_table, *name_table; + + const struct fd_device_funcs *funcs; + + struct fd_bo_cache bo_cache; + struct fd_bo_cache ring_cache; + + int closefd; /* call close(fd) upon destruction */ + + /* just for valgrind: */ + int bo_size; +}; + +drm_private void fd_bo_cache_init(struct fd_bo_cache *cache, int coarse); +drm_private void fd_bo_cache_cleanup(struct fd_bo_cache *cache, time_t time); +drm_private struct fd_bo * fd_bo_cache_alloc(struct fd_bo_cache *cache, + uint32_t *size, uint32_t flags); +drm_private int fd_bo_cache_free(struct fd_bo_cache *cache, struct fd_bo *bo); + +/* for where @table_lock is already held: */ +drm_private void fd_device_del_locked(struct fd_device *dev); + +struct fd_pipe_funcs { + struct fd_ringbuffer * (*ringbuffer_new)(struct fd_pipe *pipe, uint32_t size, + enum fd_ringbuffer_flags flags); + int (*get_param)(struct fd_pipe *pipe, enum fd_param_id param, uint64_t *value); + int (*wait)(struct fd_pipe *pipe, uint32_t timestamp, uint64_t timeout); + void (*destroy)(struct fd_pipe *pipe); +}; + +struct fd_pipe { + struct fd_device *dev; + enum fd_pipe_id id; + uint32_t gpu_id; + atomic_t refcnt; + const struct fd_pipe_funcs *funcs; +}; + +struct fd_ringbuffer_funcs { + void * (*hostptr)(struct fd_ringbuffer *ring); + int (*flush)(struct fd_ringbuffer *ring, uint32_t *last_start, + int in_fence_fd, int *out_fence_fd); + void (*grow)(struct fd_ringbuffer *ring, uint32_t size); + void (*reset)(struct fd_ringbuffer *ring); + void (*emit_reloc)(struct fd_ringbuffer *ring, + const struct fd_reloc *reloc); + uint32_t (*emit_reloc_ring)(struct fd_ringbuffer *ring, + struct fd_ringbuffer *target, uint32_t cmd_idx); + uint32_t (*cmd_count)(struct fd_ringbuffer *ring); + void (*destroy)(struct fd_ringbuffer *ring); +}; + +struct fd_bo_funcs { + int (*offset)(struct fd_bo *bo, uint64_t *offset); + int (*cpu_prep)(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op); + void (*cpu_fini)(struct fd_bo *bo); + int (*madvise)(struct fd_bo *bo, int willneed); + uint64_t (*iova)(struct fd_bo *bo); + void (*destroy)(struct fd_bo *bo); +}; + +struct fd_bo { + struct fd_device *dev; + uint32_t size; + uint32_t handle; + uint32_t name; + void *map; + atomic_t refcnt; + const struct fd_bo_funcs *funcs; + + enum { + NO_CACHE = 0, + BO_CACHE = 1, + RING_CACHE = 2, + } bo_reuse; + + struct list_head list; /* bucket-list entry */ + time_t free_time; /* time when added to bucket-list */ +}; + +drm_private struct fd_bo *fd_bo_new_ring(struct fd_device *dev, + uint32_t size, uint32_t flags); + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define enable_debug 0 /* TODO make dynamic */ + +#define INFO_MSG(fmt, ...) \ + do { drmMsg("[I] "fmt " (%s:%d)\n", \ + ##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0) +#define DEBUG_MSG(fmt, ...) \ + do if (enable_debug) { drmMsg("[D] "fmt " (%s:%d)\n", \ + ##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0) +#define WARN_MSG(fmt, ...) \ + do { drmMsg("[W] "fmt " (%s:%d)\n", \ + ##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0) +#define ERROR_MSG(fmt, ...) \ + do { drmMsg("[E] " fmt " (%s:%d)\n", \ + ##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0) + +#define U642VOID(x) ((void *)(unsigned long)(x)) +#define VOID2U64(x) ((uint64_t)(unsigned long)(x)) + +static inline uint32_t +offset_bytes(void *end, void *start) +{ + return ((char *)end) - ((char *)start); +} + +#if HAVE_VALGRIND +# include <memcheck.h> + +/* + * For tracking the backing memory (if valgrind enabled, we force a mmap + * for the purposes of tracking) + */ +static inline void VG_BO_ALLOC(struct fd_bo *bo) +{ + if (bo && RUNNING_ON_VALGRIND) { + VALGRIND_MALLOCLIKE_BLOCK(fd_bo_map(bo), bo->size, 0, 1); + } +} + +static inline void VG_BO_FREE(struct fd_bo *bo) +{ + VALGRIND_FREELIKE_BLOCK(bo->map, 0); +} + +/* + * For tracking bo structs that are in the buffer-cache, so that valgrind + * doesn't attribute ownership to the first one to allocate the recycled + * bo. + * + * Note that the list_head in fd_bo is used to track the buffers in cache + * so disable error reporting on the range while they are in cache so + * valgrind doesn't squawk about list traversal. + * + */ +static inline void VG_BO_RELEASE(struct fd_bo *bo) +{ + if (RUNNING_ON_VALGRIND) { + VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(bo, bo->dev->bo_size); + VALGRIND_MAKE_MEM_NOACCESS(bo, bo->dev->bo_size); + VALGRIND_FREELIKE_BLOCK(bo->map, 0); + } +} +static inline void VG_BO_OBTAIN(struct fd_bo *bo) +{ + if (RUNNING_ON_VALGRIND) { + VALGRIND_MAKE_MEM_DEFINED(bo, bo->dev->bo_size); + VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(bo, bo->dev->bo_size); + VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, 1); + } +} +#else +static inline void VG_BO_ALLOC(struct fd_bo *bo) {} +static inline void VG_BO_FREE(struct fd_bo *bo) {} +static inline void VG_BO_RELEASE(struct fd_bo *bo) {} +static inline void VG_BO_OBTAIN(struct fd_bo *bo) {} +#endif + + +#endif /* FREEDRENO_PRIV_H_ */ diff --git a/freedreno/freedreno_ringbuffer.c b/freedreno/freedreno_ringbuffer.c new file mode 100644 index 0000000..8f0093a --- /dev/null +++ b/freedreno/freedreno_ringbuffer.c @@ -0,0 +1,182 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <assert.h> + +#include "freedreno_drmif.h" +#include "freedreno_priv.h" +#include "freedreno_ringbuffer.h" + +drm_public struct fd_ringbuffer * +fd_ringbuffer_new_flags(struct fd_pipe *pipe, uint32_t size, + enum fd_ringbuffer_flags flags) +{ + struct fd_ringbuffer *ring; + + /* we can't really support "growable" rb's in general for + * stateobj's since we need a single gpu addr (ie. can't + * do the trick of a chain of IB packets): + */ + if (flags & FD_RINGBUFFER_OBJECT) + assert(size); + + ring = pipe->funcs->ringbuffer_new(pipe, size, flags); + if (!ring) + return NULL; + + ring->flags = flags; + ring->pipe = pipe; + ring->start = ring->funcs->hostptr(ring); + ring->end = &(ring->start[ring->size/4]); + + ring->cur = ring->last_start = ring->start; + + return ring; +} + +drm_public struct fd_ringbuffer * +fd_ringbuffer_new(struct fd_pipe *pipe, uint32_t size) +{ + return fd_ringbuffer_new_flags(pipe, size, 0); +} + +drm_public struct fd_ringbuffer * +fd_ringbuffer_new_object(struct fd_pipe *pipe, uint32_t size) +{ + return fd_ringbuffer_new_flags(pipe, size, FD_RINGBUFFER_OBJECT); +} + +drm_public void fd_ringbuffer_del(struct fd_ringbuffer *ring) +{ + if (!atomic_dec_and_test(&ring->refcnt)) + return; + + fd_ringbuffer_reset(ring); + ring->funcs->destroy(ring); +} + +drm_public struct fd_ringbuffer * +fd_ringbuffer_ref(struct fd_ringbuffer *ring) +{ + STATIC_ASSERT(sizeof(ring->refcnt) <= sizeof(ring->__pad)); + atomic_inc(&ring->refcnt); + return ring; +} + +/* ringbuffers which are IB targets should set the toplevel rb (ie. + * the IB source) as it's parent before emitting reloc's, to ensure + * the bookkeeping works out properly. + */ +drm_public void fd_ringbuffer_set_parent(struct fd_ringbuffer *ring, + struct fd_ringbuffer *parent) +{ + /* state objects should not be parented! */ + assert(!(ring->flags & FD_RINGBUFFER_OBJECT)); + ring->parent = parent; +} + +drm_public void fd_ringbuffer_reset(struct fd_ringbuffer *ring) +{ + uint32_t *start = ring->start; + if (ring->pipe->id == FD_PIPE_2D) + start = &ring->start[0x140]; + ring->cur = ring->last_start = start; + if (ring->funcs->reset) + ring->funcs->reset(ring); +} + +drm_public int fd_ringbuffer_flush(struct fd_ringbuffer *ring) +{ + return ring->funcs->flush(ring, ring->last_start, -1, NULL); +} + +drm_public int fd_ringbuffer_flush2(struct fd_ringbuffer *ring, int in_fence_fd, + int *out_fence_fd) +{ + return ring->funcs->flush(ring, ring->last_start, in_fence_fd, out_fence_fd); +} + +drm_public void fd_ringbuffer_grow(struct fd_ringbuffer *ring, uint32_t ndwords) +{ + assert(ring->funcs->grow); /* unsupported on kgsl */ + + /* there is an upper bound on IB size, which appears to be 0x100000 */ + if (ring->size < 0x100000) + ring->size *= 2; + + ring->funcs->grow(ring, ring->size); + + ring->start = ring->funcs->hostptr(ring); + ring->end = &(ring->start[ring->size/4]); + + ring->cur = ring->last_start = ring->start; +} + +drm_public uint32_t fd_ringbuffer_timestamp(struct fd_ringbuffer *ring) +{ + return ring->last_timestamp; +} + +drm_public void fd_ringbuffer_reloc(struct fd_ringbuffer *ring, + const struct fd_reloc *reloc) +{ + assert(ring->pipe->gpu_id < 500); + ring->funcs->emit_reloc(ring, reloc); +} + +drm_public void fd_ringbuffer_reloc2(struct fd_ringbuffer *ring, + const struct fd_reloc *reloc) +{ + ring->funcs->emit_reloc(ring, reloc); +} + +drm_public uint32_t fd_ringbuffer_cmd_count(struct fd_ringbuffer *ring) +{ + if (!ring->funcs->cmd_count) + return 1; + return ring->funcs->cmd_count(ring); +} + +drm_public uint32_t +fd_ringbuffer_emit_reloc_ring_full(struct fd_ringbuffer *ring, + struct fd_ringbuffer *target, uint32_t cmd_idx) +{ + return ring->funcs->emit_reloc_ring(ring, target, cmd_idx); +} + +drm_public uint32_t +fd_ringbuffer_size(struct fd_ringbuffer *ring) +{ + /* only really needed for stateobj ringbuffers, and won't really + * do what you expect for growable rb's.. so lets just restrict + * this to stateobj's for now: + */ + assert(ring->flags & FD_RINGBUFFER_OBJECT); + return offset_bytes(ring->cur, ring->start); +} + diff --git a/freedreno/freedreno_ringbuffer.h b/freedreno/freedreno_ringbuffer.h new file mode 100644 index 0000000..bc41a31 --- /dev/null +++ b/freedreno/freedreno_ringbuffer.h @@ -0,0 +1,142 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2012 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#ifndef FREEDRENO_RINGBUFFER_H_ +#define FREEDRENO_RINGBUFFER_H_ + +#include <freedreno_drmif.h> + +/* the ringbuffer object is not opaque so that OUT_RING() type stuff + * can be inlined. Note that users should not make assumptions about + * the size of this struct. + */ + +struct fd_ringbuffer_funcs; + +enum fd_ringbuffer_flags { + + /* Ringbuffer is a "state object", which is potentially reused + * many times, rather than being used in one-shot mode linked + * to a parent ringbuffer. + */ + FD_RINGBUFFER_OBJECT = 0x1, + + /* Hint that the stateobj will be used for streaming state + * that is used once or a few times and then discarded. + * + * For sub-allocation, non streaming stateobj's should be + * sub-allocated from a page size buffer, so one long lived + * state obj doesn't prevent other pages from being freed. + * (Ie. it would be no worse than allocating a page sized + * bo for each small non-streaming stateobj). + * + * But streaming stateobj's could be sub-allocated from a + * larger buffer to reduce the alloc/del overhead. + */ + FD_RINGBUFFER_STREAMING = 0x2, +}; + +struct fd_ringbuffer { + int size; + uint32_t *cur, *end, *start, *last_start; + struct fd_pipe *pipe; + const struct fd_ringbuffer_funcs *funcs; + uint32_t last_timestamp; + struct fd_ringbuffer *parent; + + /* for users of fd_ringbuffer to store their own private per- + * ringbuffer data + */ + void *user; + + enum fd_ringbuffer_flags flags; + + /* This is a bit gross, but we can't use atomic_t in exported + * headers. OTOH, we don't need the refcnt to be publicly + * visible. The only reason that this struct is exported is + * because fd_ringbuffer_emit needs to be something that can + * be inlined for performance reasons. + */ + union { +#ifdef HAS_ATOMIC_OPS + atomic_t refcnt; +#endif + uint64_t __pad; + }; +}; + +struct fd_ringbuffer * fd_ringbuffer_new(struct fd_pipe *pipe, + uint32_t size); +will_be_deprecated +struct fd_ringbuffer * fd_ringbuffer_new_object(struct fd_pipe *pipe, + uint32_t size); +struct fd_ringbuffer * fd_ringbuffer_new_flags(struct fd_pipe *pipe, + uint32_t size, enum fd_ringbuffer_flags flags); + +struct fd_ringbuffer *fd_ringbuffer_ref(struct fd_ringbuffer *ring); +void fd_ringbuffer_del(struct fd_ringbuffer *ring); +void fd_ringbuffer_set_parent(struct fd_ringbuffer *ring, + struct fd_ringbuffer *parent); +will_be_deprecated +void fd_ringbuffer_reset(struct fd_ringbuffer *ring); +int fd_ringbuffer_flush(struct fd_ringbuffer *ring); +/* in_fence_fd: -1 for no in-fence, else fence fd + * out_fence_fd: NULL for no output-fence requested, else ptr to return out-fence + */ +int fd_ringbuffer_flush2(struct fd_ringbuffer *ring, int in_fence_fd, + int *out_fence_fd); +void fd_ringbuffer_grow(struct fd_ringbuffer *ring, uint32_t ndwords); +uint32_t fd_ringbuffer_timestamp(struct fd_ringbuffer *ring); + +static inline void fd_ringbuffer_emit(struct fd_ringbuffer *ring, + uint32_t data) +{ + (*ring->cur++) = data; +} + +struct fd_reloc { + struct fd_bo *bo; +#define FD_RELOC_READ 0x0001 +#define FD_RELOC_WRITE 0x0002 + uint32_t flags; + uint32_t offset; + uint32_t or; + int32_t shift; + uint32_t orhi; /* used for a5xx+ */ +}; + +/* NOTE: relocs are 2 dwords on a5xx+ */ + +void fd_ringbuffer_reloc2(struct fd_ringbuffer *ring, const struct fd_reloc *reloc); +will_be_deprecated void fd_ringbuffer_reloc(struct fd_ringbuffer *ring, const struct fd_reloc *reloc); +uint32_t fd_ringbuffer_cmd_count(struct fd_ringbuffer *ring); +uint32_t fd_ringbuffer_emit_reloc_ring_full(struct fd_ringbuffer *ring, + struct fd_ringbuffer *target, uint32_t cmd_idx); +uint32_t fd_ringbuffer_size(struct fd_ringbuffer *ring); + +#endif /* FREEDRENO_RINGBUFFER_H_ */ diff --git a/freedreno/kgsl/README b/freedreno/kgsl/README new file mode 100644 index 0000000..c46ba08 --- /dev/null +++ b/freedreno/kgsl/README @@ -0,0 +1,26 @@ +This is a historical description of what is now the kgsl backend +in libdrm freedreno (before the upstream drm/msm driver). Note +that the kgsl backend requires the "kgsl-drm" shim driver, which +usually is in disrepair (QCOM does not build it for android), and +due to random differences between different downstream android +kernel branches it may or may not work. So YMMV. + +Original README: +---------------- + +Note that current msm kernel driver is a bit strange. It provides a +DRM interface for GEM, which is basically sufficient to have DRI2 +working. But it does not provide KMS. And interface to 2d and 3d +cores is via different other devices (/dev/kgsl-*). This is not +quite how I'd write a DRM driver, but at this stage it is useful for +xf86-video-freedreno and fdre (and eventual gallium driver) to be +able to work on existing kernel driver from QCOM, to allow to +capture cmdstream dumps from the binary blob drivers without having +to reboot. So libdrm_freedreno attempts to hide most of the crazy. +The intention is that when there is a proper kernel driver, it will +be mostly just changes in libdrm_freedreno to adapt the gallium +driver and xf86-video-freedreno (ignoring the fbdev->KMS changes). + +So don't look at freedreno as an example of how to write a libdrm +module or a DRM driver.. it is just an attempt to paper over a non- +standard kernel driver architecture. diff --git a/freedreno/kgsl/kgsl_bo.c b/freedreno/kgsl/kgsl_bo.c new file mode 100644 index 0000000..7a6af2f --- /dev/null +++ b/freedreno/kgsl/kgsl_bo.c @@ -0,0 +1,311 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "kgsl_priv.h" + +#include <linux/fb.h> + +static int set_memtype(struct fd_device *dev, uint32_t handle, uint32_t flags) +{ + struct drm_kgsl_gem_memtype req = { + .handle = handle, + .type = flags & DRM_FREEDRENO_GEM_TYPE_MEM_MASK, + }; + + return drmCommandWrite(dev->fd, DRM_KGSL_GEM_SETMEMTYPE, + &req, sizeof(req)); +} + +static int bo_alloc(struct kgsl_bo *kgsl_bo) +{ + struct fd_bo *bo = &kgsl_bo->base; + if (!kgsl_bo->offset) { + struct drm_kgsl_gem_alloc req = { + .handle = bo->handle, + }; + int ret; + + /* if the buffer is already backed by pages then this + * doesn't actually do anything (other than giving us + * the offset) + */ + ret = drmCommandWriteRead(bo->dev->fd, DRM_KGSL_GEM_ALLOC, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("alloc failed: %s", strerror(errno)); + return ret; + } + + kgsl_bo->offset = req.offset; + } + + return 0; +} + +static int kgsl_bo_offset(struct fd_bo *bo, uint64_t *offset) +{ + struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); + int ret = bo_alloc(kgsl_bo); + if (ret) + return ret; + *offset = kgsl_bo->offset; + return 0; +} + +static int kgsl_bo_cpu_prep(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op) +{ + uint32_t timestamp = kgsl_bo_get_timestamp(to_kgsl_bo(bo)); + + if (op & DRM_FREEDRENO_PREP_NOSYNC) { + uint32_t current; + int ret; + + /* special case for is_idle().. we can't really handle that + * properly in kgsl (perhaps we need a way to just disable + * the bo-cache for kgsl?) + */ + if (!pipe) + return -EBUSY; + + ret = kgsl_pipe_timestamp(to_kgsl_pipe(pipe), ¤t); + if (ret) + return ret; + + if (timestamp > current) + return -EBUSY; + + return 0; + } + + if (timestamp) + fd_pipe_wait(pipe, timestamp); + + return 0; +} + +static void kgsl_bo_cpu_fini(struct fd_bo *bo) +{ +} + +static int kgsl_bo_madvise(struct fd_bo *bo, int willneed) +{ + return willneed; /* not supported by kgsl */ +} + +static void kgsl_bo_destroy(struct fd_bo *bo) +{ + struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); + free(kgsl_bo); + +} + +static const struct fd_bo_funcs funcs = { + .offset = kgsl_bo_offset, + .cpu_prep = kgsl_bo_cpu_prep, + .cpu_fini = kgsl_bo_cpu_fini, + .madvise = kgsl_bo_madvise, + .destroy = kgsl_bo_destroy, +}; + +/* allocate a buffer handle: */ +drm_private int kgsl_bo_new_handle(struct fd_device *dev, + uint32_t size, uint32_t flags, uint32_t *handle) +{ + struct drm_kgsl_gem_create req = { + .size = size, + }; + int ret; + + ret = drmCommandWriteRead(dev->fd, DRM_KGSL_GEM_CREATE, + &req, sizeof(req)); + if (ret) + return ret; + + // TODO make flags match msm driver, since kgsl is legacy.. + // translate flags in kgsl.. + + set_memtype(dev, req.handle, flags); + + *handle = req.handle; + + return 0; +} + +/* allocate a new buffer object */ +drm_private struct fd_bo * kgsl_bo_from_handle(struct fd_device *dev, + uint32_t size, uint32_t handle) +{ + struct kgsl_bo *kgsl_bo; + struct fd_bo *bo; + unsigned i; + + kgsl_bo = calloc(1, sizeof(*kgsl_bo)); + if (!kgsl_bo) + return NULL; + + bo = &kgsl_bo->base; + bo->funcs = &funcs; + + for (i = 0; i < ARRAY_SIZE(kgsl_bo->list); i++) + list_inithead(&kgsl_bo->list[i]); + + return bo; +} + +drm_public struct fd_bo * +fd_bo_from_fbdev(struct fd_pipe *pipe, int fbfd, uint32_t size) +{ + struct fd_bo *bo; + + if (!is_kgsl_pipe(pipe)) + return NULL; + + bo = fd_bo_new(pipe->dev, 1, 0); + + /* this is fugly, but works around a bug in the kernel.. + * priv->memdesc.size never gets set, so getbufinfo ioctl + * thinks the buffer hasn't be allocate and fails + */ + if (bo) { + void *fbmem = drm_mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED, fbfd, 0); + struct kgsl_map_user_mem req = { + .memtype = KGSL_USER_MEM_TYPE_ADDR, + .len = size, + .offset = 0, + .hostptr = (unsigned long)fbmem, + }; + struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); + int ret; + + ret = ioctl(to_kgsl_pipe(pipe)->fd, IOCTL_KGSL_MAP_USER_MEM, &req); + if (ret) { + ERROR_MSG("mapping user mem failed: %s", + strerror(errno)); + goto fail; + } + kgsl_bo->gpuaddr = req.gpuaddr; + bo->map = fbmem; + } + + return bo; +fail: + if (bo) + fd_bo_del(bo); + return NULL; +} + +drm_private uint32_t kgsl_bo_gpuaddr(struct kgsl_bo *kgsl_bo, uint32_t offset) +{ + struct fd_bo *bo = &kgsl_bo->base; + if (!kgsl_bo->gpuaddr) { + struct drm_kgsl_gem_bufinfo req = { + .handle = bo->handle, + }; + int ret; + + ret = bo_alloc(kgsl_bo); + if (ret) { + return ret; + } + + ret = drmCommandWriteRead(bo->dev->fd, DRM_KGSL_GEM_GET_BUFINFO, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("get bufinfo failed: %s", strerror(errno)); + return 0; + } + + kgsl_bo->gpuaddr = req.gpuaddr[0]; + } + return kgsl_bo->gpuaddr + offset; +} + +/* + * Super-cheezy way to synchronization between mesa and ddx.. the + * SET_ACTIVE ioctl gives us a way to stash a 32b # w/ a GEM bo, and + * GET_BUFINFO gives us a way to retrieve it. We use this to stash + * the timestamp of the last ISSUEIBCMDS on the buffer. + * + * To avoid an obscene amount of syscalls, we: + * 1) Only set the timestamp for buffers w/ an flink name, ie. + * only buffers shared across processes. This is enough to + * catch the DRI2 buffers. + * 2) Only set the timestamp for buffers submitted to the 3d ring + * and only check the timestamps on buffers submitted to the + * 2d ring. This should be enough to handle synchronizing of + * presentation blit. We could do synchronization in the other + * direction too, but that would be problematic if we are using + * the 3d ring from DDX, since client side wouldn't know this. + * + * The waiting on timestamp happens before flush, and setting of + * timestamp happens after flush. It is transparent to the user + * of libdrm_freedreno as all the tracking of buffers happens via + * _emit_reloc().. + */ + +drm_private void kgsl_bo_set_timestamp(struct kgsl_bo *kgsl_bo, + uint32_t timestamp) +{ + struct fd_bo *bo = &kgsl_bo->base; + if (bo->name) { + struct drm_kgsl_gem_active req = { + .handle = bo->handle, + .active = timestamp, + }; + int ret; + + ret = drmCommandWrite(bo->dev->fd, DRM_KGSL_GEM_SET_ACTIVE, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("set active failed: %s", strerror(errno)); + } + } +} + +drm_private uint32_t kgsl_bo_get_timestamp(struct kgsl_bo *kgsl_bo) +{ + struct fd_bo *bo = &kgsl_bo->base; + uint32_t timestamp = 0; + if (bo->name) { + struct drm_kgsl_gem_bufinfo req = { + .handle = bo->handle, + }; + int ret; + + ret = drmCommandWriteRead(bo->dev->fd, DRM_KGSL_GEM_GET_BUFINFO, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("get bufinfo failed: %s", strerror(errno)); + return 0; + } + + timestamp = req.active; + } + return timestamp; +} diff --git a/freedreno/kgsl/kgsl_device.c b/freedreno/kgsl/kgsl_device.c new file mode 100644 index 0000000..914f341 --- /dev/null +++ b/freedreno/kgsl/kgsl_device.c @@ -0,0 +1,63 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "kgsl_priv.h" + +static void kgsl_device_destroy(struct fd_device *dev) +{ + struct kgsl_device *kgsl_dev = to_kgsl_device(dev); + free(kgsl_dev); +} + +static const struct fd_device_funcs funcs = { + .bo_new_handle = kgsl_bo_new_handle, + .bo_from_handle = kgsl_bo_from_handle, + .pipe_new = kgsl_pipe_new, + .destroy = kgsl_device_destroy, +}; + +drm_private struct fd_device * kgsl_device_new(int fd) +{ + struct kgsl_device *kgsl_dev; + struct fd_device *dev; + + kgsl_dev = calloc(1, sizeof(*kgsl_dev)); + if (!kgsl_dev) + return NULL; + + dev = &kgsl_dev->base; + dev->funcs = &funcs; + + dev->bo_size = sizeof(struct kgsl_bo); + + return dev; +} diff --git a/freedreno/kgsl/kgsl_drm.h b/freedreno/kgsl/kgsl_drm.h new file mode 100644 index 0000000..281978e --- /dev/null +++ b/freedreno/kgsl/kgsl_drm.h @@ -0,0 +1,192 @@ +#ifndef _KGSL_DRM_H_ +#define _KGSL_DRM_H_ + +#include "drm.h" + +#define DRM_KGSL_GEM_CREATE 0x00 +#define DRM_KGSL_GEM_PREP 0x01 +#define DRM_KGSL_GEM_SETMEMTYPE 0x02 +#define DRM_KGSL_GEM_GETMEMTYPE 0x03 +#define DRM_KGSL_GEM_MMAP 0x04 +#define DRM_KGSL_GEM_ALLOC 0x05 +#define DRM_KGSL_GEM_BIND_GPU 0x06 +#define DRM_KGSL_GEM_UNBIND_GPU 0x07 + +#define DRM_KGSL_GEM_GET_BUFINFO 0x08 +#define DRM_KGSL_GEM_SET_BUFCOUNT 0x09 +#define DRM_KGSL_GEM_SET_ACTIVE 0x0A +#define DRM_KGSL_GEM_LOCK_HANDLE 0x0B +#define DRM_KGSL_GEM_UNLOCK_HANDLE 0x0C +#define DRM_KGSL_GEM_UNLOCK_ON_TS 0x0D +#define DRM_KGSL_GEM_CREATE_FD 0x0E + +#define DRM_IOCTL_KGSL_GEM_CREATE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_CREATE, struct drm_kgsl_gem_create) + +#define DRM_IOCTL_KGSL_GEM_PREP \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_PREP, struct drm_kgsl_gem_prep) + +#define DRM_IOCTL_KGSL_GEM_SETMEMTYPE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SETMEMTYPE, \ +struct drm_kgsl_gem_memtype) + +#define DRM_IOCTL_KGSL_GEM_GETMEMTYPE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_GETMEMTYPE, \ +struct drm_kgsl_gem_memtype) + +#define DRM_IOCTL_KGSL_GEM_MMAP \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_MMAP, struct drm_kgsl_gem_mmap) + +#define DRM_IOCTL_KGSL_GEM_ALLOC \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_ALLOC, struct drm_kgsl_gem_alloc) + +#define DRM_IOCTL_KGSL_GEM_BIND_GPU \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_BIND_GPU, struct drm_kgsl_gem_bind_gpu) + +#define DRM_IOCTL_KGSL_GEM_UNBIND_GPU \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNBIND_GPU, \ +struct drm_kgsl_gem_bind_gpu) + +#define DRM_IOCTL_KGSL_GEM_GET_BUFINFO \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_GET_BUFINFO, \ + struct drm_kgsl_gem_bufinfo) + +#define DRM_IOCTL_KGSL_GEM_SET_BUFCOUNT \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SET_BUFCOUNT, \ + struct drm_kgsl_gem_bufcount) + +#define DRM_IOCTL_KGSL_GEM_SET_ACTIVE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SET_ACTIVE, \ + struct drm_kgsl_gem_active) + +#define DRM_IOCTL_KGSL_GEM_LOCK_HANDLE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_LOCK_HANDLE, \ +struct drm_kgsl_gem_lock_handles) + +#define DRM_IOCTL_KGSL_GEM_UNLOCK_HANDLE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNLOCK_HANDLE, \ +struct drm_kgsl_gem_unlock_handles) + +#define DRM_IOCTL_KGSL_GEM_UNLOCK_ON_TS \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNLOCK_ON_TS, \ +struct drm_kgsl_gem_unlock_on_ts) + +#define DRM_IOCTL_KGSL_GEM_CREATE_FD \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_CREATE_FD, \ +struct drm_kgsl_gem_create_fd) + +/* Maximum number of sub buffers per GEM object */ +#define DRM_KGSL_GEM_MAX_BUFFERS 2 + +/* Memory types - these define the source and caching policies + of the GEM memory chunk */ + +/* Legacy definitions left for compatibility */ + +#define DRM_KGSL_GEM_TYPE_EBI 0 +#define DRM_KGSL_GEM_TYPE_SMI 1 +#define DRM_KGSL_GEM_TYPE_KMEM 2 +#define DRM_KGSL_GEM_TYPE_KMEM_NOCACHE 3 +#define DRM_KGSL_GEM_TYPE_MEM_MASK 0xF + +/* Contiguous memory (PMEM) */ +#define DRM_KGSL_GEM_TYPE_PMEM 0x000100 + +/* PMEM memory types */ +#define DRM_KGSL_GEM_PMEM_EBI 0x001000 +#define DRM_KGSL_GEM_PMEM_SMI 0x002000 + +/* Standard paged memory */ +#define DRM_KGSL_GEM_TYPE_MEM 0x010000 + +/* Caching controls */ +#define DRM_KGSL_GEM_CACHE_NONE 0x000000 +#define DRM_KGSL_GEM_CACHE_WCOMBINE 0x100000 +#define DRM_KGSL_GEM_CACHE_WTHROUGH 0x200000 +#define DRM_KGSL_GEM_CACHE_WBACK 0x400000 +#define DRM_KGSL_GEM_CACHE_WBACKWA 0x800000 +#define DRM_KGSL_GEM_CACHE_MASK 0xF00000 + +/* FD based objects */ +#define DRM_KGSL_GEM_TYPE_FD_FBMEM 0x1000000 +#define DRM_KGSL_GEM_TYPE_FD_MASK 0xF000000 + +/* Timestamp types */ +#define DRM_KGSL_GEM_TS_3D 0x00000430 +#define DRM_KGSL_GEM_TS_2D 0x00000180 + + +struct drm_kgsl_gem_create { + uint32_t size; + uint32_t handle; +}; + +struct drm_kgsl_gem_prep { + uint32_t handle; + uint32_t phys; + uint64_t offset; +}; + +struct drm_kgsl_gem_memtype { + uint32_t handle; + uint32_t type; +}; + +struct drm_kgsl_gem_mmap { + uint32_t handle; + uint32_t size; + uint32_t hostptr; + uint64_t offset; +}; + +struct drm_kgsl_gem_alloc { + uint32_t handle; + uint64_t offset; +}; + +struct drm_kgsl_gem_bind_gpu { + uint32_t handle; + uint32_t gpuptr; +}; + +struct drm_kgsl_gem_bufinfo { + uint32_t handle; + uint32_t count; + uint32_t active; + uint32_t offset[DRM_KGSL_GEM_MAX_BUFFERS]; + uint32_t gpuaddr[DRM_KGSL_GEM_MAX_BUFFERS]; +}; + +struct drm_kgsl_gem_bufcount { + uint32_t handle; + uint32_t bufcount; +}; + +struct drm_kgsl_gem_active { + uint32_t handle; + uint32_t active; +}; + +struct drm_kgsl_gem_lock_handles { + uint32_t num_handles; + uint32_t *handle_list; + uint32_t pid; + uint32_t lock_id; /* Returned lock id used for unlocking */ +}; + +struct drm_kgsl_gem_unlock_handles { + uint32_t lock_id; +}; + +struct drm_kgsl_gem_unlock_on_ts { + uint32_t lock_id; + uint32_t timestamp; /* This field is a hw generated ts */ + uint32_t type; /* Which pipe to check for ts generation */ +}; + +struct drm_kgsl_gem_create_fd { + uint32_t fd; + uint32_t handle; +}; + +#endif diff --git a/freedreno/kgsl/kgsl_pipe.c b/freedreno/kgsl/kgsl_pipe.c new file mode 100644 index 0000000..0a8b658 --- /dev/null +++ b/freedreno/kgsl/kgsl_pipe.c @@ -0,0 +1,282 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "kgsl_priv.h" + + +static int kgsl_pipe_get_param(struct fd_pipe *pipe, + enum fd_param_id param, uint64_t *value) +{ + struct kgsl_pipe *kgsl_pipe = to_kgsl_pipe(pipe); + switch (param) { + case FD_DEVICE_ID: + *value = kgsl_pipe->devinfo.device_id; + return 0; + case FD_GPU_ID: + *value = kgsl_pipe->devinfo.gpu_id; + return 0; + case FD_GMEM_SIZE: + *value = kgsl_pipe->devinfo.gmem_sizebytes; + return 0; + case FD_CHIP_ID: + *value = kgsl_pipe->devinfo.chip_id; + return 0; + case FD_MAX_FREQ: + case FD_TIMESTAMP: + case FD_NR_RINGS: + /* unsupported on kgsl */ + return -1; + default: + ERROR_MSG("invalid param id: %d", param); + return -1; + } +} + +static int kgsl_pipe_wait(struct fd_pipe *pipe, uint32_t timestamp, + uint64_t timeout) +{ + struct kgsl_pipe *kgsl_pipe = to_kgsl_pipe(pipe); + struct kgsl_device_waittimestamp req = { + .timestamp = timestamp, + .timeout = 5000, + }; + int ret; + + do { + ret = ioctl(kgsl_pipe->fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP, &req); + } while ((ret == -1) && ((errno == EINTR) || (errno == EAGAIN))); + if (ret) + ERROR_MSG("waittimestamp failed! %d (%s)", ret, strerror(errno)); + else + kgsl_pipe_process_pending(kgsl_pipe, timestamp); + return ret; +} + +drm_private int kgsl_pipe_timestamp(struct kgsl_pipe *kgsl_pipe, + uint32_t *timestamp) +{ + struct kgsl_cmdstream_readtimestamp req = { + .type = KGSL_TIMESTAMP_RETIRED + }; + int ret = ioctl(kgsl_pipe->fd, IOCTL_KGSL_CMDSTREAM_READTIMESTAMP, &req); + if (ret) { + ERROR_MSG("readtimestamp failed! %d (%s)", + ret, strerror(errno)); + return ret; + } + *timestamp = req.timestamp; + return 0; +} + +static void kgsl_pipe_destroy(struct fd_pipe *pipe) +{ + struct kgsl_pipe *kgsl_pipe = to_kgsl_pipe(pipe); + struct kgsl_drawctxt_destroy req = { + .drawctxt_id = kgsl_pipe->drawctxt_id, + }; + + if (kgsl_pipe->drawctxt_id) + ioctl(kgsl_pipe->fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &req); + + if (kgsl_pipe->fd >= 0) + close(kgsl_pipe->fd); + + free(kgsl_pipe); +} + +static const struct fd_pipe_funcs funcs = { + .ringbuffer_new = kgsl_ringbuffer_new, + .get_param = kgsl_pipe_get_param, + .wait = kgsl_pipe_wait, + .destroy = kgsl_pipe_destroy, +}; + +drm_private int is_kgsl_pipe(struct fd_pipe *pipe) +{ + return pipe->funcs == &funcs; +} + +/* add buffer to submit list when it is referenced in cmdstream: */ +drm_private void kgsl_pipe_add_submit(struct kgsl_pipe *kgsl_pipe, + struct kgsl_bo *kgsl_bo) +{ + struct fd_pipe *pipe = &kgsl_pipe->base; + struct fd_bo *bo = &kgsl_bo->base; + struct list_head *list = &kgsl_bo->list[pipe->id]; + if (LIST_IS_EMPTY(list)) { + fd_bo_ref(bo); + } else { + list_del(list); + } + list_addtail(list, &kgsl_pipe->submit_list); +} + +/* prepare buffers on submit list before flush: */ +drm_private void kgsl_pipe_pre_submit(struct kgsl_pipe *kgsl_pipe) +{ + struct fd_pipe *pipe = &kgsl_pipe->base; + struct kgsl_bo *kgsl_bo = NULL; + + if (!kgsl_pipe->p3d) + kgsl_pipe->p3d = fd_pipe_new(pipe->dev, FD_PIPE_3D); + + LIST_FOR_EACH_ENTRY(kgsl_bo, &kgsl_pipe->submit_list, list[pipe->id]) { + uint32_t timestamp = kgsl_bo_get_timestamp(kgsl_bo); + if (timestamp) + fd_pipe_wait(kgsl_pipe->p3d, timestamp); + } +} + +/* process buffers on submit list after flush: */ +drm_private void kgsl_pipe_post_submit(struct kgsl_pipe *kgsl_pipe, + uint32_t timestamp) +{ + struct fd_pipe *pipe = &kgsl_pipe->base; + struct kgsl_bo *kgsl_bo = NULL, *tmp; + + LIST_FOR_EACH_ENTRY_SAFE(kgsl_bo, tmp, &kgsl_pipe->submit_list, list[pipe->id]) { + struct list_head *list = &kgsl_bo->list[pipe->id]; + list_del(list); + kgsl_bo->timestamp[pipe->id] = timestamp; + list_addtail(list, &kgsl_pipe->pending_list); + + kgsl_bo_set_timestamp(kgsl_bo, timestamp); + } + + if (!kgsl_pipe_timestamp(kgsl_pipe, ×tamp)) + kgsl_pipe_process_pending(kgsl_pipe, timestamp); +} + +drm_private void kgsl_pipe_process_pending(struct kgsl_pipe *kgsl_pipe, + uint32_t timestamp) +{ + struct fd_pipe *pipe = &kgsl_pipe->base; + struct kgsl_bo *kgsl_bo = NULL, *tmp; + + LIST_FOR_EACH_ENTRY_SAFE(kgsl_bo, tmp, &kgsl_pipe->pending_list, list[pipe->id]) { + struct list_head *list = &kgsl_bo->list[pipe->id]; + if (kgsl_bo->timestamp[pipe->id] > timestamp) + return; + list_delinit(list); + kgsl_bo->timestamp[pipe->id] = 0; + fd_bo_del(&kgsl_bo->base); + } +} + +static int getprop(int fd, enum kgsl_property_type type, + void *value, int sizebytes) +{ + struct kgsl_device_getproperty req = { + .type = type, + .value = value, + .sizebytes = sizebytes, + }; + return ioctl(fd, IOCTL_KGSL_DEVICE_GETPROPERTY, &req); +} + +#define GETPROP(fd, prop, x) do { \ + if (getprop((fd), KGSL_PROP_##prop, &(x), sizeof(x))) { \ + ERROR_MSG("failed to get property: " #prop); \ + goto fail; \ + } } while (0) + + +drm_private struct fd_pipe * kgsl_pipe_new(struct fd_device *dev, + enum fd_pipe_id id, uint32_t prio) +{ + static const char *paths[] = { + [FD_PIPE_3D] = "/dev/kgsl-3d0", + [FD_PIPE_2D] = "/dev/kgsl-2d0", + }; + struct kgsl_drawctxt_create req = { + .flags = 0x2000, /* ??? */ + }; + struct kgsl_pipe *kgsl_pipe = NULL; + struct fd_pipe *pipe = NULL; + int ret, fd; + + fd = open(paths[id], O_RDWR); + if (fd < 0) { + ERROR_MSG("could not open %s device: %d (%s)", + paths[id], fd, strerror(errno)); + goto fail; + } + + ret = ioctl(fd, IOCTL_KGSL_DRAWCTXT_CREATE, &req); + if (ret) { + ERROR_MSG("failed to allocate context: %d (%s)", + ret, strerror(errno)); + goto fail; + } + + kgsl_pipe = calloc(1, sizeof(*kgsl_pipe)); + if (!kgsl_pipe) { + ERROR_MSG("allocation failed"); + goto fail; + } + + pipe = &kgsl_pipe->base; + pipe->funcs = &funcs; + + kgsl_pipe->fd = fd; + kgsl_pipe->drawctxt_id = req.drawctxt_id; + + list_inithead(&kgsl_pipe->submit_list); + list_inithead(&kgsl_pipe->pending_list); + + GETPROP(fd, VERSION, kgsl_pipe->version); + GETPROP(fd, DEVICE_INFO, kgsl_pipe->devinfo); + + if (kgsl_pipe->devinfo.gpu_id >= 500) { + ERROR_MSG("64b unsupported with kgsl"); + goto fail; + } + + INFO_MSG("Pipe Info:"); + INFO_MSG(" Device: %s", paths[id]); + INFO_MSG(" Chip-id: %d.%d.%d.%d", + (kgsl_pipe->devinfo.chip_id >> 24) & 0xff, + (kgsl_pipe->devinfo.chip_id >> 16) & 0xff, + (kgsl_pipe->devinfo.chip_id >> 8) & 0xff, + (kgsl_pipe->devinfo.chip_id >> 0) & 0xff); + INFO_MSG(" Device-id: %d", kgsl_pipe->devinfo.device_id); + INFO_MSG(" GPU-id: %d", kgsl_pipe->devinfo.gpu_id); + INFO_MSG(" MMU enabled: %d", kgsl_pipe->devinfo.mmu_enabled); + INFO_MSG(" GMEM Base addr: 0x%08x", kgsl_pipe->devinfo.gmem_gpubaseaddr); + INFO_MSG(" GMEM size: 0x%08x", kgsl_pipe->devinfo.gmem_sizebytes); + INFO_MSG(" Driver version: %d.%d", + kgsl_pipe->version.drv_major, kgsl_pipe->version.drv_minor); + INFO_MSG(" Device version: %d.%d", + kgsl_pipe->version.dev_major, kgsl_pipe->version.dev_minor); + + return pipe; +fail: + if (pipe) + fd_pipe_del(pipe); + return NULL; +} diff --git a/freedreno/kgsl/kgsl_priv.h b/freedreno/kgsl/kgsl_priv.h new file mode 100644 index 0000000..a6bf2d4 --- /dev/null +++ b/freedreno/kgsl/kgsl_priv.h @@ -0,0 +1,120 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#ifndef KGSL_PRIV_H_ +#define KGSL_PRIV_H_ + +#include "freedreno_priv.h" +#include "msm_kgsl.h" +#include "kgsl_drm.h" + +struct kgsl_device { + struct fd_device base; +}; + +static inline struct kgsl_device * to_kgsl_device(struct fd_device *x) +{ + return (struct kgsl_device *)x; +} + +struct kgsl_pipe { + struct fd_pipe base; + + int fd; + uint32_t drawctxt_id; + + /* device properties: */ + struct kgsl_version version; + struct kgsl_devinfo devinfo; + + /* list of bo's that are referenced in ringbuffer but not + * submitted yet: + */ + struct list_head submit_list; + + /* list of bo's that have been submitted but timestamp has + * not passed yet (so still ref'd in active cmdstream) + */ + struct list_head pending_list; + + /* if we are the 2d pipe, and want to wait on a timestamp + * from 3d, we need to also internally open the 3d pipe: + */ + struct fd_pipe *p3d; +}; + +static inline struct kgsl_pipe * to_kgsl_pipe(struct fd_pipe *x) +{ + return (struct kgsl_pipe *)x; +} + +drm_private int is_kgsl_pipe(struct fd_pipe *pipe); + +struct kgsl_bo { + struct fd_bo base; + uint64_t offset; + uint32_t gpuaddr; + /* timestamp (per pipe) for bo's in a pipe's pending_list: */ + uint32_t timestamp[FD_PIPE_MAX]; + /* list-node for pipe's submit_list or pending_list */ + struct list_head list[FD_PIPE_MAX]; +}; + +static inline struct kgsl_bo * to_kgsl_bo(struct fd_bo *x) +{ + return (struct kgsl_bo *)x; +} + + +drm_private struct fd_device * kgsl_device_new(int fd); + +drm_private int kgsl_pipe_timestamp(struct kgsl_pipe *kgsl_pipe, + uint32_t *timestamp); +drm_private void kgsl_pipe_add_submit(struct kgsl_pipe *pipe, + struct kgsl_bo *bo); +drm_private void kgsl_pipe_pre_submit(struct kgsl_pipe *pipe); +drm_private void kgsl_pipe_post_submit(struct kgsl_pipe *pipe, + uint32_t timestamp); +drm_private void kgsl_pipe_process_pending(struct kgsl_pipe *pipe, + uint32_t timestamp); +drm_private struct fd_pipe * kgsl_pipe_new(struct fd_device *dev, + enum fd_pipe_id id, uint32_t prio); + +drm_private struct fd_ringbuffer * kgsl_ringbuffer_new(struct fd_pipe *pipe, + uint32_t size, enum fd_ringbuffer_flags flags); + +drm_private int kgsl_bo_new_handle(struct fd_device *dev, + uint32_t size, uint32_t flags, uint32_t *handle); +drm_private struct fd_bo * kgsl_bo_from_handle(struct fd_device *dev, + uint32_t size, uint32_t handle); + +drm_private uint32_t kgsl_bo_gpuaddr(struct kgsl_bo *bo, uint32_t offset); +drm_private void kgsl_bo_set_timestamp(struct kgsl_bo *bo, uint32_t timestamp); +drm_private uint32_t kgsl_bo_get_timestamp(struct kgsl_bo *bo); + +#endif /* KGSL_PRIV_H_ */ diff --git a/freedreno/kgsl/kgsl_ringbuffer.c b/freedreno/kgsl/kgsl_ringbuffer.c new file mode 100644 index 0000000..9abf0ad --- /dev/null +++ b/freedreno/kgsl/kgsl_ringbuffer.c @@ -0,0 +1,235 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <assert.h> + +#include "xf86atomic.h" +#include "freedreno_ringbuffer.h" +#include "kgsl_priv.h" + + +/* because kgsl tries to validate the gpuaddr on kernel side in ISSUEIBCMDS, + * we can't use normal gem bo's for ringbuffer.. someday the kernel part + * needs to be reworked into a single sane drm driver :-/ + */ +struct kgsl_rb_bo { + struct kgsl_pipe *pipe; + void *hostptr; + uint32_t gpuaddr; + uint32_t size; +}; + +struct kgsl_ringbuffer { + struct fd_ringbuffer base; + struct kgsl_rb_bo *bo; +}; + +static inline struct kgsl_ringbuffer * to_kgsl_ringbuffer(struct fd_ringbuffer *x) +{ + return (struct kgsl_ringbuffer *)x; +} + +static void kgsl_rb_bo_del(struct kgsl_rb_bo *bo) +{ + struct kgsl_sharedmem_free req = { + .gpuaddr = bo->gpuaddr, + }; + int ret; + + drm_munmap(bo->hostptr, bo->size); + + ret = ioctl(bo->pipe->fd, IOCTL_KGSL_SHAREDMEM_FREE, &req); + if (ret) { + ERROR_MSG("sharedmem free failed: %s", strerror(errno)); + } + + free(bo); +} + +static struct kgsl_rb_bo * kgsl_rb_bo_new(struct kgsl_pipe *pipe, uint32_t size) +{ + struct kgsl_rb_bo *bo; + struct kgsl_gpumem_alloc req = { + .size = ALIGN(size, 4096), + .flags = KGSL_MEMFLAGS_GPUREADONLY, + }; + int ret; + + bo = calloc(1, sizeof(*bo)); + if (!bo) { + ERROR_MSG("allocation failed"); + return NULL; + } + ret = ioctl(pipe->fd, IOCTL_KGSL_GPUMEM_ALLOC, &req); + if (ret) { + ERROR_MSG("gpumem allocation failed: %s", strerror(errno)); + goto fail; + } + + bo->pipe = pipe; + bo->gpuaddr = req.gpuaddr; + bo->size = size; + bo->hostptr = drm_mmap(NULL, size, PROT_WRITE|PROT_READ, + MAP_SHARED, pipe->fd, req.gpuaddr); + + return bo; +fail: + if (bo) + kgsl_rb_bo_del(bo); + return NULL; +} + +static void * kgsl_ringbuffer_hostptr(struct fd_ringbuffer *ring) +{ + struct kgsl_ringbuffer *kgsl_ring = to_kgsl_ringbuffer(ring); + return kgsl_ring->bo->hostptr; +} + +static int kgsl_ringbuffer_flush(struct fd_ringbuffer *ring, uint32_t *last_start, + int in_fence_fd, int *out_fence_fd) +{ + struct kgsl_ringbuffer *kgsl_ring = to_kgsl_ringbuffer(ring); + struct kgsl_pipe *kgsl_pipe = to_kgsl_pipe(ring->pipe); + uint32_t offset = (uint8_t *)last_start - (uint8_t *)ring->start; + struct kgsl_ibdesc ibdesc = { + .gpuaddr = kgsl_ring->bo->gpuaddr + offset, + .hostptr = last_start, + .sizedwords = ring->cur - last_start, + }; + struct kgsl_ringbuffer_issueibcmds req = { + .drawctxt_id = kgsl_pipe->drawctxt_id, + .ibdesc_addr = (unsigned long)&ibdesc, + .numibs = 1, + .flags = KGSL_CONTEXT_SUBMIT_IB_LIST, + }; + int ret; + + assert(in_fence_fd == -1); + assert(out_fence_fd == NULL); + + kgsl_pipe_pre_submit(kgsl_pipe); + + /* z180_cmdstream_issueibcmds() is made of fail: */ + if (ring->pipe->id == FD_PIPE_2D) { + /* fix up size field in last cmd packet */ + uint32_t last_size = (uint32_t)(ring->cur - last_start); + /* 5 is length of first packet, 2 for the two 7f000000's */ + last_start[2] = last_size - (5 + 2); + ibdesc.gpuaddr = kgsl_ring->bo->gpuaddr; + ibdesc.hostptr = kgsl_ring->bo->hostptr; + ibdesc.sizedwords = 0x145; + req.timestamp = (uintptr_t)kgsl_ring->bo->hostptr; + } + + do { + ret = ioctl(kgsl_pipe->fd, IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS, &req); + } while ((ret == -1) && ((errno == EINTR) || (errno == EAGAIN))); + if (ret) + ERROR_MSG("issueibcmds failed! %d (%s)", ret, strerror(errno)); + + ring->last_timestamp = req.timestamp; + ring->last_start = ring->cur; + + kgsl_pipe_post_submit(kgsl_pipe, req.timestamp); + + return ret; +} + +static void kgsl_ringbuffer_emit_reloc(struct fd_ringbuffer *ring, + const struct fd_reloc *r) +{ + struct kgsl_bo *kgsl_bo = to_kgsl_bo(r->bo); + uint32_t addr = kgsl_bo_gpuaddr(kgsl_bo, r->offset); + assert(addr); + if (r->shift < 0) + addr >>= -r->shift; + else + addr <<= r->shift; + (*ring->cur++) = addr | r->or; + kgsl_pipe_add_submit(to_kgsl_pipe(ring->pipe), kgsl_bo); +} + +static uint32_t kgsl_ringbuffer_emit_reloc_ring(struct fd_ringbuffer *ring, + struct fd_ringbuffer *target, uint32_t cmd_idx) +{ + struct kgsl_ringbuffer *target_ring = to_kgsl_ringbuffer(target); + assert(cmd_idx == 0); + (*ring->cur++) = target_ring->bo->gpuaddr; + return offset_bytes(target->cur, target->start); +} + +static void kgsl_ringbuffer_destroy(struct fd_ringbuffer *ring) +{ + struct kgsl_ringbuffer *kgsl_ring = to_kgsl_ringbuffer(ring); + if (ring->last_timestamp) + fd_pipe_wait(ring->pipe, ring->last_timestamp); + if (kgsl_ring->bo) + kgsl_rb_bo_del(kgsl_ring->bo); + free(kgsl_ring); +} + +static const struct fd_ringbuffer_funcs funcs = { + .hostptr = kgsl_ringbuffer_hostptr, + .flush = kgsl_ringbuffer_flush, + .emit_reloc = kgsl_ringbuffer_emit_reloc, + .emit_reloc_ring = kgsl_ringbuffer_emit_reloc_ring, + .destroy = kgsl_ringbuffer_destroy, +}; + +drm_private struct fd_ringbuffer * kgsl_ringbuffer_new(struct fd_pipe *pipe, + uint32_t size, enum fd_ringbuffer_flags flags) +{ + struct kgsl_ringbuffer *kgsl_ring; + struct fd_ringbuffer *ring = NULL; + + assert(!flags); + + kgsl_ring = calloc(1, sizeof(*kgsl_ring)); + if (!kgsl_ring) { + ERROR_MSG("allocation failed"); + goto fail; + } + + ring = &kgsl_ring->base; + atomic_set(&ring->refcnt, 1); + + ring->funcs = &funcs; + ring->size = size; + + kgsl_ring->bo = kgsl_rb_bo_new(to_kgsl_pipe(pipe), size); + if (!kgsl_ring->bo) { + ERROR_MSG("ringbuffer allocation failed"); + goto fail; + } + + return ring; +fail: + if (ring) + fd_ringbuffer_del(ring); + return NULL; +} diff --git a/freedreno/kgsl/msm_kgsl.h b/freedreno/kgsl/msm_kgsl.h new file mode 100644 index 0000000..5b36eeb --- /dev/null +++ b/freedreno/kgsl/msm_kgsl.h @@ -0,0 +1,519 @@ +#ifndef _MSM_KGSL_H +#define _MSM_KGSL_H + +#define KGSL_VERSION_MAJOR 3 +#define KGSL_VERSION_MINOR 11 + +/*context flags */ +#define KGSL_CONTEXT_SAVE_GMEM 0x00000001 +#define KGSL_CONTEXT_NO_GMEM_ALLOC 0x00000002 +#define KGSL_CONTEXT_SUBMIT_IB_LIST 0x00000004 +#define KGSL_CONTEXT_CTX_SWITCH 0x00000008 +#define KGSL_CONTEXT_PREAMBLE 0x00000010 +#define KGSL_CONTEXT_TRASH_STATE 0x00000020 +#define KGSL_CONTEXT_PER_CONTEXT_TS 0x00000040 + +#define KGSL_CONTEXT_INVALID 0xffffffff + +/* Memory allocayion flags */ +#define KGSL_MEMFLAGS_GPUREADONLY 0x01000000 + +/* generic flag values */ +#define KGSL_FLAGS_NORMALMODE 0x00000000 +#define KGSL_FLAGS_SAFEMODE 0x00000001 +#define KGSL_FLAGS_INITIALIZED0 0x00000002 +#define KGSL_FLAGS_INITIALIZED 0x00000004 +#define KGSL_FLAGS_STARTED 0x00000008 +#define KGSL_FLAGS_ACTIVE 0x00000010 +#define KGSL_FLAGS_RESERVED0 0x00000020 +#define KGSL_FLAGS_RESERVED1 0x00000040 +#define KGSL_FLAGS_RESERVED2 0x00000080 +#define KGSL_FLAGS_SOFT_RESET 0x00000100 +#define KGSL_FLAGS_PER_CONTEXT_TIMESTAMPS 0x00000200 + +/* Clock flags to show which clocks should be controlled by a given platform */ +#define KGSL_CLK_SRC 0x00000001 +#define KGSL_CLK_CORE 0x00000002 +#define KGSL_CLK_IFACE 0x00000004 +#define KGSL_CLK_MEM 0x00000008 +#define KGSL_CLK_MEM_IFACE 0x00000010 +#define KGSL_CLK_AXI 0x00000020 + +/* + * Reset status values for context + */ +enum kgsl_ctx_reset_stat { + KGSL_CTX_STAT_NO_ERROR = 0x00000000, + KGSL_CTX_STAT_GUILTY_CONTEXT_RESET_EXT = 0x00000001, + KGSL_CTX_STAT_INNOCENT_CONTEXT_RESET_EXT = 0x00000002, + KGSL_CTX_STAT_UNKNOWN_CONTEXT_RESET_EXT = 0x00000003 +}; + +#define KGSL_MAX_PWRLEVELS 5 + +#define KGSL_CONVERT_TO_MBPS(val) \ + (val*1000*1000U) + +/* device id */ +enum kgsl_deviceid { + KGSL_DEVICE_3D0 = 0x00000000, + KGSL_DEVICE_2D0 = 0x00000001, + KGSL_DEVICE_2D1 = 0x00000002, + KGSL_DEVICE_MAX = 0x00000003 +}; + +enum kgsl_user_mem_type { + KGSL_USER_MEM_TYPE_PMEM = 0x00000000, + KGSL_USER_MEM_TYPE_ASHMEM = 0x00000001, + KGSL_USER_MEM_TYPE_ADDR = 0x00000002, + KGSL_USER_MEM_TYPE_ION = 0x00000003, + KGSL_USER_MEM_TYPE_MAX = 0x00000004, +}; + +struct kgsl_devinfo { + + unsigned int device_id; + /* chip revision id + * coreid:8 majorrev:8 minorrev:8 patch:8 + */ + unsigned int chip_id; + unsigned int mmu_enabled; + unsigned int gmem_gpubaseaddr; + /* + * This field contains the adreno revision + * number 200, 205, 220, etc... + */ + unsigned int gpu_id; + unsigned int gmem_sizebytes; +}; + +/* this structure defines the region of memory that can be mmap()ed from this + driver. The timestamp fields are volatile because they are written by the + GPU +*/ +struct kgsl_devmemstore { + volatile unsigned int soptimestamp; + unsigned int sbz; + volatile unsigned int eoptimestamp; + unsigned int sbz2; + volatile unsigned int ts_cmp_enable; + unsigned int sbz3; + volatile unsigned int ref_wait_ts; + unsigned int sbz4; + unsigned int current_context; + unsigned int sbz5; +}; + +#define KGSL_MEMSTORE_OFFSET(ctxt_id, field) \ + ((ctxt_id)*sizeof(struct kgsl_devmemstore) + \ + offsetof(struct kgsl_devmemstore, field)) + +/* timestamp id*/ +enum kgsl_timestamp_type { + KGSL_TIMESTAMP_CONSUMED = 0x00000001, /* start-of-pipeline timestamp */ + KGSL_TIMESTAMP_RETIRED = 0x00000002, /* end-of-pipeline timestamp*/ + KGSL_TIMESTAMP_QUEUED = 0x00000003, +}; + +/* property types - used with kgsl_device_getproperty */ +enum kgsl_property_type { + KGSL_PROP_DEVICE_INFO = 0x00000001, + KGSL_PROP_DEVICE_SHADOW = 0x00000002, + KGSL_PROP_DEVICE_POWER = 0x00000003, + KGSL_PROP_SHMEM = 0x00000004, + KGSL_PROP_SHMEM_APERTURES = 0x00000005, + KGSL_PROP_MMU_ENABLE = 0x00000006, + KGSL_PROP_INTERRUPT_WAITS = 0x00000007, + KGSL_PROP_VERSION = 0x00000008, + KGSL_PROP_GPU_RESET_STAT = 0x00000009, + KGSL_PROP_PWRCTRL = 0x0000000E, +}; + +struct kgsl_shadowprop { + unsigned int gpuaddr; + unsigned int size; + unsigned int flags; /* contains KGSL_FLAGS_ values */ +}; + +struct kgsl_pwrlevel { + unsigned int gpu_freq; + unsigned int bus_freq; + unsigned int io_fraction; +}; + +struct kgsl_version { + unsigned int drv_major; + unsigned int drv_minor; + unsigned int dev_major; + unsigned int dev_minor; +}; + +#ifdef __KERNEL__ + +#define KGSL_3D0_REG_MEMORY "kgsl_3d0_reg_memory" +#define KGSL_3D0_IRQ "kgsl_3d0_irq" +#define KGSL_2D0_REG_MEMORY "kgsl_2d0_reg_memory" +#define KGSL_2D0_IRQ "kgsl_2d0_irq" +#define KGSL_2D1_REG_MEMORY "kgsl_2d1_reg_memory" +#define KGSL_2D1_IRQ "kgsl_2d1_irq" + +enum kgsl_iommu_context_id { + KGSL_IOMMU_CONTEXT_USER = 0, + KGSL_IOMMU_CONTEXT_PRIV = 1, +}; + +struct kgsl_iommu_ctx { + const char *iommu_ctx_name; + enum kgsl_iommu_context_id ctx_id; +}; + +struct kgsl_device_iommu_data { + const struct kgsl_iommu_ctx *iommu_ctxs; + int iommu_ctx_count; + unsigned int physstart; + unsigned int physend; +}; + +struct kgsl_device_platform_data { + struct kgsl_pwrlevel pwrlevel[KGSL_MAX_PWRLEVELS]; + int init_level; + int num_levels; + int (*set_grp_async)(void); + unsigned int idle_timeout; + bool strtstp_sleepwake; + unsigned int nap_allowed; + unsigned int clk_map; + unsigned int idle_needed; + struct msm_bus_scale_pdata *bus_scale_table; + struct kgsl_device_iommu_data *iommu_data; + int iommu_count; + struct msm_dcvs_core_info *core_info; +}; + +#endif + +/* structure holds list of ibs */ +struct kgsl_ibdesc { + unsigned int gpuaddr; + void *hostptr; + unsigned int sizedwords; + unsigned int ctrl; +}; + +/* ioctls */ +#define KGSL_IOC_TYPE 0x09 + +/* get misc info about the GPU + type should be a value from enum kgsl_property_type + value points to a structure that varies based on type + sizebytes is sizeof() that structure + for KGSL_PROP_DEVICE_INFO, use struct kgsl_devinfo + this structure contaings hardware versioning info. + for KGSL_PROP_DEVICE_SHADOW, use struct kgsl_shadowprop + this is used to find mmap() offset and sizes for mapping + struct kgsl_memstore into userspace. +*/ +struct kgsl_device_getproperty { + unsigned int type; + void *value; + unsigned int sizebytes; +}; + +#define IOCTL_KGSL_DEVICE_GETPROPERTY \ + _IOWR(KGSL_IOC_TYPE, 0x2, struct kgsl_device_getproperty) + +/* IOCTL_KGSL_DEVICE_READ (0x3) - removed 03/2012 + */ + +/* block until the GPU has executed past a given timestamp + * timeout is in milliseconds. + */ +struct kgsl_device_waittimestamp { + unsigned int timestamp; + unsigned int timeout; +}; + +#define IOCTL_KGSL_DEVICE_WAITTIMESTAMP \ + _IOW(KGSL_IOC_TYPE, 0x6, struct kgsl_device_waittimestamp) + +struct kgsl_device_waittimestamp_ctxtid { + unsigned int context_id; + unsigned int timestamp; + unsigned int timeout; +}; + +#define IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID \ + _IOW(KGSL_IOC_TYPE, 0x7, struct kgsl_device_waittimestamp_ctxtid) + +/* issue indirect commands to the GPU. + * drawctxt_id must have been created with IOCTL_KGSL_DRAWCTXT_CREATE + * ibaddr and sizedwords must specify a subset of a buffer created + * with IOCTL_KGSL_SHAREDMEM_FROM_PMEM + * flags may be a mask of KGSL_CONTEXT_ values + * timestamp is a returned counter value which can be passed to + * other ioctls to determine when the commands have been executed by + * the GPU. + */ +struct kgsl_ringbuffer_issueibcmds { + unsigned int drawctxt_id; + unsigned int ibdesc_addr; + unsigned int numibs; + unsigned int timestamp; /*output param */ + unsigned int flags; +}; + +#define IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS \ + _IOWR(KGSL_IOC_TYPE, 0x10, struct kgsl_ringbuffer_issueibcmds) + +/* read the most recently executed timestamp value + * type should be a value from enum kgsl_timestamp_type + */ +struct kgsl_cmdstream_readtimestamp { + unsigned int type; + unsigned int timestamp; /*output param */ +}; + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_OLD \ + _IOR(KGSL_IOC_TYPE, 0x11, struct kgsl_cmdstream_readtimestamp) + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP \ + _IOWR(KGSL_IOC_TYPE, 0x11, struct kgsl_cmdstream_readtimestamp) + +/* free memory when the GPU reaches a given timestamp. + * gpuaddr specify a memory region created by a + * IOCTL_KGSL_SHAREDMEM_FROM_PMEM call + * type should be a value from enum kgsl_timestamp_type + */ +struct kgsl_cmdstream_freememontimestamp { + unsigned int gpuaddr; + unsigned int type; + unsigned int timestamp; +}; + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP \ + _IOW(KGSL_IOC_TYPE, 0x12, struct kgsl_cmdstream_freememontimestamp) + +/* Previous versions of this header had incorrectly defined + IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP as a read-only ioctl instead + of a write only ioctl. To ensure binary compatibility, the following + #define will be used to intercept the incorrect ioctl +*/ + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_OLD \ + _IOR(KGSL_IOC_TYPE, 0x12, struct kgsl_cmdstream_freememontimestamp) + +/* create a draw context, which is used to preserve GPU state. + * The flags field may contain a mask KGSL_CONTEXT_* values + */ +struct kgsl_drawctxt_create { + unsigned int flags; + unsigned int drawctxt_id; /*output param */ +}; + +#define IOCTL_KGSL_DRAWCTXT_CREATE \ + _IOWR(KGSL_IOC_TYPE, 0x13, struct kgsl_drawctxt_create) + +/* destroy a draw context */ +struct kgsl_drawctxt_destroy { + unsigned int drawctxt_id; +}; + +#define IOCTL_KGSL_DRAWCTXT_DESTROY \ + _IOW(KGSL_IOC_TYPE, 0x14, struct kgsl_drawctxt_destroy) + +/* add a block of pmem, fb, ashmem or user allocated address + * into the GPU address space */ +struct kgsl_map_user_mem { + int fd; + unsigned int gpuaddr; /*output param */ + unsigned int len; + unsigned int offset; + unsigned int hostptr; /*input param */ + enum kgsl_user_mem_type memtype; + unsigned int reserved; /* May be required to add + params for another mem type */ +}; + +#define IOCTL_KGSL_MAP_USER_MEM \ + _IOWR(KGSL_IOC_TYPE, 0x15, struct kgsl_map_user_mem) + +struct kgsl_cmdstream_readtimestamp_ctxtid { + unsigned int context_id; + unsigned int type; + unsigned int timestamp; /*output param */ +}; + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID \ + _IOWR(KGSL_IOC_TYPE, 0x16, struct kgsl_cmdstream_readtimestamp_ctxtid) + +struct kgsl_cmdstream_freememontimestamp_ctxtid { + unsigned int context_id; + unsigned int gpuaddr; + unsigned int type; + unsigned int timestamp; +}; + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_CTXTID \ + _IOW(KGSL_IOC_TYPE, 0x17, \ + struct kgsl_cmdstream_freememontimestamp_ctxtid) + +/* add a block of pmem or fb into the GPU address space */ +struct kgsl_sharedmem_from_pmem { + int pmem_fd; + unsigned int gpuaddr; /*output param */ + unsigned int len; + unsigned int offset; +}; + +#define IOCTL_KGSL_SHAREDMEM_FROM_PMEM \ + _IOWR(KGSL_IOC_TYPE, 0x20, struct kgsl_sharedmem_from_pmem) + +/* remove memory from the GPU's address space */ +struct kgsl_sharedmem_free { + unsigned int gpuaddr; +}; + +#define IOCTL_KGSL_SHAREDMEM_FREE \ + _IOW(KGSL_IOC_TYPE, 0x21, struct kgsl_sharedmem_free) + +struct kgsl_cff_user_event { + unsigned char cff_opcode; + unsigned int op1; + unsigned int op2; + unsigned int op3; + unsigned int op4; + unsigned int op5; + unsigned int __pad[2]; +}; + +#define IOCTL_KGSL_CFF_USER_EVENT \ + _IOW(KGSL_IOC_TYPE, 0x31, struct kgsl_cff_user_event) + +struct kgsl_gmem_desc { + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + unsigned int pitch; +}; + +struct kgsl_buffer_desc { + void *hostptr; + unsigned int gpuaddr; + int size; + unsigned int format; + unsigned int pitch; + unsigned int enabled; +}; + +struct kgsl_bind_gmem_shadow { + unsigned int drawctxt_id; + struct kgsl_gmem_desc gmem_desc; + unsigned int shadow_x; + unsigned int shadow_y; + struct kgsl_buffer_desc shadow_buffer; + unsigned int buffer_id; +}; + +#define IOCTL_KGSL_DRAWCTXT_BIND_GMEM_SHADOW \ + _IOW(KGSL_IOC_TYPE, 0x22, struct kgsl_bind_gmem_shadow) + +/* add a block of memory into the GPU address space */ +struct kgsl_sharedmem_from_vmalloc { + unsigned int gpuaddr; /*output param */ + unsigned int hostptr; + unsigned int flags; +}; + +#define IOCTL_KGSL_SHAREDMEM_FROM_VMALLOC \ + _IOWR(KGSL_IOC_TYPE, 0x23, struct kgsl_sharedmem_from_vmalloc) + +#define IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE \ + _IOW(KGSL_IOC_TYPE, 0x24, struct kgsl_sharedmem_free) + +struct kgsl_drawctxt_set_bin_base_offset { + unsigned int drawctxt_id; + unsigned int offset; +}; + +#define IOCTL_KGSL_DRAWCTXT_SET_BIN_BASE_OFFSET \ + _IOW(KGSL_IOC_TYPE, 0x25, struct kgsl_drawctxt_set_bin_base_offset) + +enum kgsl_cmdwindow_type { + KGSL_CMDWINDOW_MIN = 0x00000000, + KGSL_CMDWINDOW_2D = 0x00000000, + KGSL_CMDWINDOW_3D = 0x00000001, /* legacy */ + KGSL_CMDWINDOW_MMU = 0x00000002, + KGSL_CMDWINDOW_ARBITER = 0x000000FF, + KGSL_CMDWINDOW_MAX = 0x000000FF, +}; + +/* write to the command window */ +struct kgsl_cmdwindow_write { + enum kgsl_cmdwindow_type target; + unsigned int addr; + unsigned int data; +}; + +#define IOCTL_KGSL_CMDWINDOW_WRITE \ + _IOW(KGSL_IOC_TYPE, 0x2e, struct kgsl_cmdwindow_write) + +struct kgsl_gpumem_alloc { + unsigned long gpuaddr; + size_t size; + unsigned int flags; +}; + +#define IOCTL_KGSL_GPUMEM_ALLOC \ + _IOWR(KGSL_IOC_TYPE, 0x2f, struct kgsl_gpumem_alloc) + +struct kgsl_cff_syncmem { + unsigned int gpuaddr; + unsigned int len; + unsigned int __pad[2]; /* For future binary compatibility */ +}; + +#define IOCTL_KGSL_CFF_SYNCMEM \ + _IOW(KGSL_IOC_TYPE, 0x30, struct kgsl_cff_syncmem) + +/* + * A timestamp event allows the user space to register an action following an + * expired timestamp. + */ + +struct kgsl_timestamp_event { + int type; /* Type of event (see list below) */ + unsigned int timestamp; /* Timestamp to trigger event on */ + unsigned int context_id; /* Context for the timestamp */ + void *priv; /* Pointer to the event specific blob */ + size_t len; /* Size of the event specific blob */ +}; + +#define IOCTL_KGSL_TIMESTAMP_EVENT \ + _IOW(KGSL_IOC_TYPE, 0x31, struct kgsl_timestamp_event) + +/* A genlock timestamp event releases an existing lock on timestamp expire */ + +#define KGSL_TIMESTAMP_EVENT_GENLOCK 1 + +struct kgsl_timestamp_event_genlock { + int handle; /* Handle of the genlock lock to release */ +}; + +/* + * Set a property within the kernel. Uses the same structure as + * IOCTL_KGSL_GETPROPERTY + */ + +#define IOCTL_KGSL_SETPROPERTY \ + _IOW(KGSL_IOC_TYPE, 0x32, struct kgsl_device_getproperty) + +#ifdef __KERNEL__ +#ifdef CONFIG_MSM_KGSL_DRM +int kgsl_gem_obj_addr(int drm_fd, int handle, unsigned long *start, + unsigned long *len); +#else +#define kgsl_gem_obj_addr(...) 0 +#endif +#endif +#endif /* _MSM_KGSL_H */ diff --git a/freedreno/libdrm_freedreno.pc.in b/freedreno/libdrm_freedreno.pc.in new file mode 100644 index 0000000..b736b65 --- /dev/null +++ b/freedreno/libdrm_freedreno.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libdrm_freedreno +Description: Userspace interface to freedreno kernel DRM services +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -ldrm_freedreno +Cflags: -I${includedir} -I${includedir}/libdrm -I${includedir}/freedreno +Requires.private: libdrm diff --git a/freedreno/meson.build b/freedreno/meson.build new file mode 100644 index 0000000..5ba1a3f --- /dev/null +++ b/freedreno/meson.build @@ -0,0 +1,82 @@ +# Copyright © 2017-2018 Intel Corporation + +# 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 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. + +files_freedreno = files( + 'freedreno_device.c', + 'freedreno_pipe.c', + 'freedreno_ringbuffer.c', + 'freedreno_bo.c', + 'freedreno_bo_cache.c', + 'msm/msm_bo.c', + 'msm/msm_device.c', + 'msm/msm_pipe.c', + 'msm/msm_ringbuffer.c', +) + +if with_freedreno_kgsl + files_freedreno += files( + 'kgsl/kgsl_bo.c', + 'kgsl/kgsl_device.c', + 'kgsl/kgsl_pipe.c', + 'kgsl/kgsl_ringbuffer.c', + ) +endif + +libdrm_freedreno = library( + 'drm_freedreno', + [files_freedreno, config_file], + c_args : libdrm_c_args, + include_directories : [inc_root, inc_drm], + dependencies : [dep_valgrind, dep_threads, dep_rt, dep_atomic_ops], + link_with : libdrm, + version : '1.0.0', + install : true, +) + +ext_libdrm_freedreno = declare_dependency( + link_with : [libdrm, libdrm_freedreno], + include_directories : [inc_drm, include_directories('.')], +) + +if meson.version().version_compare('>= 0.54.0') + meson.override_dependency('libdrm_freedreno', ext_libdrm_freedreno) +endif + +install_headers( + 'freedreno_drmif.h', 'freedreno_ringbuffer.h', + subdir : 'freedreno' +) + +pkg.generate( + libdrm_freedreno, + name : 'libdrm_freedreno', + subdirs : ['.', 'libdrm', 'freedreno'], + description : 'Userspace interface to freedreno kernel DRM services', +) + +test( + 'freedreno-symbols-check', + symbols_check, + args : [ + '--lib', libdrm_freedreno, + '--symbols-file', files('freedreno-symbols.txt'), + '--nm', prog_nm.path(), + ], +) diff --git a/freedreno/msm/msm_bo.c b/freedreno/msm/msm_bo.c new file mode 100644 index 0000000..8b3d0bc --- /dev/null +++ b/freedreno/msm/msm_bo.c @@ -0,0 +1,170 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "msm_priv.h" + +static int bo_allocate(struct msm_bo *msm_bo) +{ + struct fd_bo *bo = &msm_bo->base; + if (!msm_bo->offset) { + struct drm_msm_gem_info req = { + .handle = bo->handle, + }; + int ret; + + /* if the buffer is already backed by pages then this + * doesn't actually do anything (other than giving us + * the offset) + */ + ret = drmCommandWriteRead(bo->dev->fd, DRM_MSM_GEM_INFO, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("alloc failed: %s", strerror(errno)); + return ret; + } + + msm_bo->offset = req.offset; + } + + return 0; +} + +static int msm_bo_offset(struct fd_bo *bo, uint64_t *offset) +{ + struct msm_bo *msm_bo = to_msm_bo(bo); + int ret = bo_allocate(msm_bo); + if (ret) + return ret; + *offset = msm_bo->offset; + return 0; +} + +static int msm_bo_cpu_prep(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op) +{ + struct drm_msm_gem_cpu_prep req = { + .handle = bo->handle, + .op = op, + }; + + get_abs_timeout(&req.timeout, 5000000000); + + return drmCommandWrite(bo->dev->fd, DRM_MSM_GEM_CPU_PREP, &req, sizeof(req)); +} + +static void msm_bo_cpu_fini(struct fd_bo *bo) +{ + struct drm_msm_gem_cpu_fini req = { + .handle = bo->handle, + }; + + drmCommandWrite(bo->dev->fd, DRM_MSM_GEM_CPU_FINI, &req, sizeof(req)); +} + +static int msm_bo_madvise(struct fd_bo *bo, int willneed) +{ + struct drm_msm_gem_madvise req = { + .handle = bo->handle, + .madv = willneed ? MSM_MADV_WILLNEED : MSM_MADV_DONTNEED, + }; + int ret; + + /* older kernels do not support this: */ + if (bo->dev->version < FD_VERSION_MADVISE) + return willneed; + + ret = drmCommandWriteRead(bo->dev->fd, DRM_MSM_GEM_MADVISE, &req, sizeof(req)); + if (ret) + return ret; + + return req.retained; +} + +static uint64_t msm_bo_iova(struct fd_bo *bo) +{ + struct drm_msm_gem_info req = { + .handle = bo->handle, + .flags = MSM_INFO_IOVA, + }; + + drmCommandWriteRead(bo->dev->fd, DRM_MSM_GEM_INFO, &req, sizeof(req)); + + return req.offset; +} + +static void msm_bo_destroy(struct fd_bo *bo) +{ + struct msm_bo *msm_bo = to_msm_bo(bo); + free(msm_bo); + +} + +static const struct fd_bo_funcs funcs = { + .offset = msm_bo_offset, + .cpu_prep = msm_bo_cpu_prep, + .cpu_fini = msm_bo_cpu_fini, + .madvise = msm_bo_madvise, + .iova = msm_bo_iova, + .destroy = msm_bo_destroy, +}; + +/* allocate a buffer handle: */ +drm_private int msm_bo_new_handle(struct fd_device *dev, + uint32_t size, uint32_t flags, uint32_t *handle) +{ + struct drm_msm_gem_new req = { + .size = size, + .flags = MSM_BO_WC, // TODO figure out proper flags.. + }; + int ret; + + ret = drmCommandWriteRead(dev->fd, DRM_MSM_GEM_NEW, + &req, sizeof(req)); + if (ret) + return ret; + + *handle = req.handle; + + return 0; +} + +/* allocate a new buffer object */ +drm_private struct fd_bo * msm_bo_from_handle(struct fd_device *dev, + uint32_t size, uint32_t handle) +{ + struct msm_bo *msm_bo; + struct fd_bo *bo; + + msm_bo = calloc(1, sizeof(*msm_bo)); + if (!msm_bo) + return NULL; + + bo = &msm_bo->base; + bo->funcs = &funcs; + + return bo; +} diff --git a/freedreno/msm/msm_device.c b/freedreno/msm/msm_device.c new file mode 100644 index 0000000..58b0746 --- /dev/null +++ b/freedreno/msm/msm_device.c @@ -0,0 +1,63 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "msm_priv.h" + +static void msm_device_destroy(struct fd_device *dev) +{ + struct msm_device *msm_dev = to_msm_device(dev); + free(msm_dev); +} + +static const struct fd_device_funcs funcs = { + .bo_new_handle = msm_bo_new_handle, + .bo_from_handle = msm_bo_from_handle, + .pipe_new = msm_pipe_new, + .destroy = msm_device_destroy, +}; + +drm_private struct fd_device * msm_device_new(int fd) +{ + struct msm_device *msm_dev; + struct fd_device *dev; + + msm_dev = calloc(1, sizeof(*msm_dev)); + if (!msm_dev) + return NULL; + + dev = &msm_dev->base; + dev->funcs = &funcs; + + dev->bo_size = sizeof(struct msm_bo); + + return dev; +} diff --git a/freedreno/msm/msm_pipe.c b/freedreno/msm/msm_pipe.c new file mode 100644 index 0000000..e070b31 --- /dev/null +++ b/freedreno/msm/msm_pipe.c @@ -0,0 +1,212 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include "msm_priv.h" + +static int query_param(struct fd_pipe *pipe, uint32_t param, + uint64_t *value) +{ + struct msm_pipe *msm_pipe = to_msm_pipe(pipe); + struct drm_msm_param req = { + .pipe = msm_pipe->pipe, + .param = param, + }; + int ret; + + ret = drmCommandWriteRead(pipe->dev->fd, DRM_MSM_GET_PARAM, + &req, sizeof(req)); + if (ret) + return ret; + + *value = req.value; + + return 0; +} + +static int msm_pipe_get_param(struct fd_pipe *pipe, + enum fd_param_id param, uint64_t *value) +{ + struct msm_pipe *msm_pipe = to_msm_pipe(pipe); + switch(param) { + case FD_DEVICE_ID: // XXX probably get rid of this.. + case FD_GPU_ID: + *value = msm_pipe->gpu_id; + return 0; + case FD_GMEM_SIZE: + *value = msm_pipe->gmem; + return 0; + case FD_CHIP_ID: + *value = msm_pipe->chip_id; + return 0; + case FD_MAX_FREQ: + return query_param(pipe, MSM_PARAM_MAX_FREQ, value); + case FD_TIMESTAMP: + return query_param(pipe, MSM_PARAM_TIMESTAMP, value); + case FD_NR_RINGS: + return query_param(pipe, MSM_PARAM_NR_RINGS, value); + default: + ERROR_MSG("invalid param id: %d", param); + return -1; + } +} + +static int msm_pipe_wait(struct fd_pipe *pipe, uint32_t timestamp, + uint64_t timeout) +{ + struct fd_device *dev = pipe->dev; + struct drm_msm_wait_fence req = { + .fence = timestamp, + .queueid = to_msm_pipe(pipe)->queue_id, + }; + int ret; + + get_abs_timeout(&req.timeout, timeout); + + ret = drmCommandWrite(dev->fd, DRM_MSM_WAIT_FENCE, &req, sizeof(req)); + if (ret) { + ERROR_MSG("wait-fence failed! %d (%s)", ret, strerror(errno)); + return ret; + } + + return 0; +} + +static int open_submitqueue(struct fd_pipe *pipe, uint32_t prio) +{ + struct drm_msm_submitqueue req = { + .flags = 0, + .prio = prio, + }; + uint64_t nr_rings = 1; + int ret; + + if (fd_device_version(pipe->dev) < FD_VERSION_SUBMIT_QUEUES) { + to_msm_pipe(pipe)->queue_id = 0; + return 0; + } + + msm_pipe_get_param(pipe, FD_NR_RINGS, &nr_rings); + + req.prio = MIN2(req.prio, MAX2(nr_rings, 1) - 1); + + ret = drmCommandWriteRead(pipe->dev->fd, DRM_MSM_SUBMITQUEUE_NEW, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("could not create submitqueue! %d (%s)", ret, strerror(errno)); + return ret; + } + + to_msm_pipe(pipe)->queue_id = req.id; + return 0; +} + +static void close_submitqueue(struct fd_pipe *pipe, uint32_t queue_id) +{ + if (fd_device_version(pipe->dev) < FD_VERSION_SUBMIT_QUEUES) + return; + + drmCommandWrite(pipe->dev->fd, DRM_MSM_SUBMITQUEUE_CLOSE, + &queue_id, sizeof(queue_id)); +} + +static void msm_pipe_destroy(struct fd_pipe *pipe) +{ + struct msm_pipe *msm_pipe = to_msm_pipe(pipe); + close_submitqueue(pipe, msm_pipe->queue_id); + + if (msm_pipe->suballoc_ring) { + fd_ringbuffer_del(msm_pipe->suballoc_ring); + msm_pipe->suballoc_ring = NULL; + } + + free(msm_pipe); +} + +static const struct fd_pipe_funcs funcs = { + .ringbuffer_new = msm_ringbuffer_new, + .get_param = msm_pipe_get_param, + .wait = msm_pipe_wait, + .destroy = msm_pipe_destroy, +}; + +static uint64_t get_param(struct fd_pipe *pipe, uint32_t param) +{ + uint64_t value; + int ret = query_param(pipe, param, &value); + if (ret) { + ERROR_MSG("get-param failed! %d (%s)", ret, strerror(errno)); + return 0; + } + return value; +} + +drm_private struct fd_pipe * msm_pipe_new(struct fd_device *dev, + enum fd_pipe_id id, uint32_t prio) +{ + static const uint32_t pipe_id[] = { + [FD_PIPE_3D] = MSM_PIPE_3D0, + [FD_PIPE_2D] = MSM_PIPE_2D0, + }; + struct msm_pipe *msm_pipe = NULL; + struct fd_pipe *pipe = NULL; + + msm_pipe = calloc(1, sizeof(*msm_pipe)); + if (!msm_pipe) { + ERROR_MSG("allocation failed"); + goto fail; + } + + pipe = &msm_pipe->base; + pipe->funcs = &funcs; + + /* initialize before get_param(): */ + pipe->dev = dev; + msm_pipe->pipe = pipe_id[id]; + + /* these params should be supported since the first version of drm/msm: */ + msm_pipe->gpu_id = get_param(pipe, MSM_PARAM_GPU_ID); + msm_pipe->gmem = get_param(pipe, MSM_PARAM_GMEM_SIZE); + msm_pipe->chip_id = get_param(pipe, MSM_PARAM_CHIP_ID); + + if (! msm_pipe->gpu_id) + goto fail; + + INFO_MSG("Pipe Info:"); + INFO_MSG(" GPU-id: %d", msm_pipe->gpu_id); + INFO_MSG(" Chip-id: 0x%08x", msm_pipe->chip_id); + INFO_MSG(" GMEM size: 0x%08x", msm_pipe->gmem); + + if (open_submitqueue(pipe, prio)) + goto fail; + + return pipe; +fail: + if (pipe) + fd_pipe_del(pipe); + return NULL; +} diff --git a/freedreno/msm/msm_priv.h b/freedreno/msm/msm_priv.h new file mode 100644 index 0000000..cc951fb --- /dev/null +++ b/freedreno/msm/msm_priv.h @@ -0,0 +1,141 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#ifndef MSM_PRIV_H_ +#define MSM_PRIV_H_ + +#include "freedreno_priv.h" + +#ifndef __user +# define __user +#endif + +#include "msm_drm.h" + +struct msm_device { + struct fd_device base; + struct fd_bo_cache ring_cache; + unsigned ring_cnt; +}; + +static inline struct msm_device * to_msm_device(struct fd_device *x) +{ + return (struct msm_device *)x; +} + +drm_private struct fd_device * msm_device_new(int fd); + +struct msm_pipe { + struct fd_pipe base; + uint32_t pipe; + uint32_t gpu_id; + uint32_t gmem; + uint32_t chip_id; + uint32_t queue_id; + + /* Allow for sub-allocation of stateobj ring buffers (ie. sharing + * the same underlying bo).. + * + * This takes advantage of each context having it's own fd_pipe, + * so we don't have to worry about access from multiple threads. + * + * We also rely on previous stateobj having been fully constructed + * so we can reclaim extra space at it's end. + */ + struct fd_ringbuffer *suballoc_ring; +}; + +static inline struct msm_pipe * to_msm_pipe(struct fd_pipe *x) +{ + return (struct msm_pipe *)x; +} + +drm_private struct fd_pipe * msm_pipe_new(struct fd_device *dev, + enum fd_pipe_id id, uint32_t prio); + +drm_private struct fd_ringbuffer * msm_ringbuffer_new(struct fd_pipe *pipe, + uint32_t size, enum fd_ringbuffer_flags flags); + +struct msm_bo { + struct fd_bo base; + uint64_t offset; + uint64_t presumed; + /* to avoid excess hashtable lookups, cache the ring this bo was + * last emitted on (since that will probably also be the next ring + * it is emitted on) + */ + unsigned current_ring_seqno; + uint32_t idx; +}; + +static inline struct msm_bo * to_msm_bo(struct fd_bo *x) +{ + return (struct msm_bo *)x; +} + +drm_private int msm_bo_new_handle(struct fd_device *dev, + uint32_t size, uint32_t flags, uint32_t *handle); +drm_private struct fd_bo * msm_bo_from_handle(struct fd_device *dev, + uint32_t size, uint32_t handle); + +static inline void get_abs_timeout(struct drm_msm_timespec *tv, uint64_t ns) +{ + struct timespec t; + uint32_t s = ns / 1000000000; + clock_gettime(CLOCK_MONOTONIC, &t); + tv->tv_sec = t.tv_sec + s; + tv->tv_nsec = t.tv_nsec + ns - (s * 1000000000); +} + +/* + * Stupid/simple growable array implementation: + */ + +static inline void * +grow(void *ptr, uint32_t nr, uint32_t *max, uint32_t sz) +{ + if ((nr + 1) > *max) { + if ((*max * 2) < (nr + 1)) + *max = nr + 5; + else + *max = *max * 2; + ptr = realloc(ptr, *max * sz); + } + return ptr; +} + +#define DECLARE_ARRAY(type, name) \ + unsigned nr_ ## name, max_ ## name; \ + type * name; + +#define APPEND(x, name) ({ \ + (x)->name = grow((x)->name, (x)->nr_ ## name, &(x)->max_ ## name, sizeof((x)->name[0])); \ + (x)->nr_ ## name ++; \ +}) + +#endif /* MSM_PRIV_H_ */ diff --git a/freedreno/msm/msm_ringbuffer.c b/freedreno/msm/msm_ringbuffer.c new file mode 100644 index 0000000..7b9df4a --- /dev/null +++ b/freedreno/msm/msm_ringbuffer.c @@ -0,0 +1,723 @@ +/* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */ + +/* + * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org> + * + * 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. + * + * Authors: + * Rob Clark <robclark@freedesktop.org> + */ + +#include <assert.h> +#include <inttypes.h> + +#include "xf86atomic.h" +#include "freedreno_ringbuffer.h" +#include "msm_priv.h" + +/* represents a single cmd buffer in the submit ioctl. Each cmd buffer has + * a backing bo, and a reloc table. + */ +struct msm_cmd { + struct list_head list; + + struct fd_ringbuffer *ring; + struct fd_bo *ring_bo; + + /* reloc's table: */ + DECLARE_ARRAY(struct drm_msm_gem_submit_reloc, relocs); + + uint32_t size; + + /* has cmd already been added to parent rb's submit.cmds table? */ + int is_appended_to_submit; +}; + +struct msm_ringbuffer { + struct fd_ringbuffer base; + + /* submit ioctl related tables: + * Note that bos and cmds are tracked by the parent ringbuffer, since + * that is global to the submit ioctl call. The reloc's table is tracked + * per cmd-buffer. + */ + struct { + /* bo's table: */ + DECLARE_ARRAY(struct drm_msm_gem_submit_bo, bos); + + /* cmd's table: */ + DECLARE_ARRAY(struct drm_msm_gem_submit_cmd, cmds); + } submit; + + /* should have matching entries in submit.bos: */ + /* Note, only in parent ringbuffer */ + DECLARE_ARRAY(struct fd_bo *, bos); + + /* should have matching entries in submit.cmds: */ + DECLARE_ARRAY(struct msm_cmd *, cmds); + + /* List of physical cmdstream buffers (msm_cmd) associated with this + * logical fd_ringbuffer. + * + * Note that this is different from msm_ringbuffer::cmds (which + * shadows msm_ringbuffer::submit::cmds for tracking submit ioctl + * related stuff, and *only* is tracked in the parent ringbuffer. + * And only has "completed" cmd buffers (ie. we already know the + * size) added via get_cmd(). + */ + struct list_head cmd_list; + + int is_growable; + unsigned cmd_count; + + unsigned offset; /* for sub-allocated stateobj rb's */ + + unsigned seqno; + + /* maps fd_bo to idx: */ + void *bo_table; + + /* maps msm_cmd to drm_msm_gem_submit_cmd in parent rb. Each rb has a + * list of msm_cmd's which correspond to each chunk of cmdstream in + * a 'growable' rb. For each of those we need to create one + * drm_msm_gem_submit_cmd in the parent rb which collects the state + * for the submit ioctl. Because we can have multiple IB's to the same + * target rb (for example, or same stateobj emit multiple times), and + * because in theory we can have multiple different rb's that have a + * reference to a given target, we need a hashtable to track this per + * rb. + */ + void *cmd_table; +}; + +static inline struct msm_ringbuffer * to_msm_ringbuffer(struct fd_ringbuffer *x) +{ + return (struct msm_ringbuffer *)x; +} + +#define INIT_SIZE 0x1000 + +static pthread_mutex_t idx_lock = PTHREAD_MUTEX_INITIALIZER; + +static struct msm_cmd *current_cmd(struct fd_ringbuffer *ring) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + assert(!LIST_IS_EMPTY(&msm_ring->cmd_list)); + return LIST_LAST_ENTRY(&msm_ring->cmd_list, struct msm_cmd, list); +} + +static void ring_cmd_del(struct msm_cmd *cmd) +{ + fd_bo_del(cmd->ring_bo); + list_del(&cmd->list); + to_msm_ringbuffer(cmd->ring)->cmd_count--; + free(cmd->relocs); + free(cmd); +} + +static struct msm_cmd * ring_cmd_new(struct fd_ringbuffer *ring, uint32_t size, + enum fd_ringbuffer_flags flags) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + struct msm_cmd *cmd = calloc(1, sizeof(*cmd)); + + if (!cmd) + return NULL; + + cmd->ring = ring; + + /* TODO separate suballoc buffer for small non-streaming state, using + * smaller page-sized backing bo's. + */ + if (flags & FD_RINGBUFFER_STREAMING) { + struct msm_pipe *msm_pipe = to_msm_pipe(ring->pipe); + unsigned suballoc_offset = 0; + struct fd_bo *suballoc_bo = NULL; + + if (msm_pipe->suballoc_ring) { + struct msm_ringbuffer *suballoc_ring = to_msm_ringbuffer(msm_pipe->suballoc_ring); + + assert(msm_pipe->suballoc_ring->flags & FD_RINGBUFFER_OBJECT); + assert(suballoc_ring->cmd_count == 1); + + suballoc_bo = current_cmd(msm_pipe->suballoc_ring)->ring_bo; + + suballoc_offset = fd_ringbuffer_size(msm_pipe->suballoc_ring) + + suballoc_ring->offset; + + suballoc_offset = ALIGN(suballoc_offset, 0x10); + + if ((size + suballoc_offset) > suballoc_bo->size) { + suballoc_bo = NULL; + } + } + + if (!suballoc_bo) { + cmd->ring_bo = fd_bo_new_ring(ring->pipe->dev, 0x8000, 0); + msm_ring->offset = 0; + } else { + cmd->ring_bo = fd_bo_ref(suballoc_bo); + msm_ring->offset = suballoc_offset; + } + + if (msm_pipe->suballoc_ring) + fd_ringbuffer_del(msm_pipe->suballoc_ring); + + msm_pipe->suballoc_ring = fd_ringbuffer_ref(ring); + } else { + cmd->ring_bo = fd_bo_new_ring(ring->pipe->dev, size, 0); + } + if (!cmd->ring_bo) + goto fail; + + list_addtail(&cmd->list, &msm_ring->cmd_list); + msm_ring->cmd_count++; + + return cmd; + +fail: + ring_cmd_del(cmd); + return NULL; +} + +static uint32_t append_bo(struct fd_ringbuffer *ring, struct fd_bo *bo) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + uint32_t idx; + + idx = APPEND(&msm_ring->submit, bos); + idx = APPEND(msm_ring, bos); + + msm_ring->submit.bos[idx].flags = 0; + msm_ring->submit.bos[idx].handle = bo->handle; + msm_ring->submit.bos[idx].presumed = to_msm_bo(bo)->presumed; + + msm_ring->bos[idx] = fd_bo_ref(bo); + + return idx; +} + +/* add (if needed) bo, return idx: */ +static uint32_t bo2idx(struct fd_ringbuffer *ring, struct fd_bo *bo, uint32_t flags) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + struct msm_bo *msm_bo = to_msm_bo(bo); + uint32_t idx; + pthread_mutex_lock(&idx_lock); + if (msm_bo->current_ring_seqno == msm_ring->seqno) { + idx = msm_bo->idx; + } else { + void *val; + + if (!msm_ring->bo_table) + msm_ring->bo_table = drmHashCreate(); + + if (!drmHashLookup(msm_ring->bo_table, bo->handle, &val)) { + /* found */ + idx = (uint32_t)(uintptr_t)val; + } else { + idx = append_bo(ring, bo); + val = (void *)(uintptr_t)idx; + drmHashInsert(msm_ring->bo_table, bo->handle, val); + } + msm_bo->current_ring_seqno = msm_ring->seqno; + msm_bo->idx = idx; + } + pthread_mutex_unlock(&idx_lock); + if (flags & FD_RELOC_READ) + msm_ring->submit.bos[idx].flags |= MSM_SUBMIT_BO_READ; + if (flags & FD_RELOC_WRITE) + msm_ring->submit.bos[idx].flags |= MSM_SUBMIT_BO_WRITE; + return idx; +} + +/* Ensure that submit has corresponding entry in cmds table for the + * target cmdstream buffer: + * + * Returns TRUE if new cmd added (else FALSE if it was already in + * the cmds table) + */ +static int get_cmd(struct fd_ringbuffer *ring, struct msm_cmd *target_cmd, + uint32_t submit_offset, uint32_t size, uint32_t type) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + struct drm_msm_gem_submit_cmd *cmd; + uint32_t i; + void *val; + + if (!msm_ring->cmd_table) + msm_ring->cmd_table = drmHashCreate(); + + /* figure out if we already have a cmd buf.. short-circuit hash + * lookup if: + * - target cmd has never been added to submit.cmds + * - target cmd is not a streaming stateobj (which unlike longer + * lived CSO stateobj, is not expected to be reused with multiple + * submits) + */ + if (target_cmd->is_appended_to_submit && + !(target_cmd->ring->flags & FD_RINGBUFFER_STREAMING) && + !drmHashLookup(msm_ring->cmd_table, (unsigned long)target_cmd, &val)) { + i = VOID2U64(val); + cmd = &msm_ring->submit.cmds[i]; + + assert(cmd->submit_offset == submit_offset); + assert(cmd->size == size); + assert(cmd->type == type); + assert(msm_ring->submit.bos[cmd->submit_idx].handle == + target_cmd->ring_bo->handle); + + return FALSE; + } + + /* create cmd buf if not: */ + i = APPEND(&msm_ring->submit, cmds); + APPEND(msm_ring, cmds); + msm_ring->cmds[i] = target_cmd; + cmd = &msm_ring->submit.cmds[i]; + cmd->type = type; + cmd->submit_idx = bo2idx(ring, target_cmd->ring_bo, FD_RELOC_READ); + cmd->submit_offset = submit_offset; + cmd->size = size; + cmd->pad = 0; + + target_cmd->is_appended_to_submit = TRUE; + + if (!(target_cmd->ring->flags & FD_RINGBUFFER_STREAMING)) { + drmHashInsert(msm_ring->cmd_table, (unsigned long)target_cmd, + U642VOID(i)); + } + + target_cmd->size = size; + + return TRUE; +} + +static void * msm_ringbuffer_hostptr(struct fd_ringbuffer *ring) +{ + struct msm_cmd *cmd = current_cmd(ring); + uint8_t *base = fd_bo_map(cmd->ring_bo); + return base + to_msm_ringbuffer(ring)->offset; +} + +static void delete_cmds(struct msm_ringbuffer *msm_ring) +{ + struct msm_cmd *cmd, *tmp; + + LIST_FOR_EACH_ENTRY_SAFE(cmd, tmp, &msm_ring->cmd_list, list) { + ring_cmd_del(cmd); + } +} + +static void flush_reset(struct fd_ringbuffer *ring) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + unsigned i; + + for (i = 0; i < msm_ring->nr_bos; i++) { + struct msm_bo *msm_bo = to_msm_bo(msm_ring->bos[i]); + if (!msm_bo) + continue; + msm_bo->current_ring_seqno = 0; + fd_bo_del(&msm_bo->base); + } + + for (i = 0; i < msm_ring->nr_cmds; i++) { + struct msm_cmd *msm_cmd = msm_ring->cmds[i]; + + if (msm_cmd->ring == ring) + continue; + + if (msm_cmd->ring->flags & FD_RINGBUFFER_OBJECT) + fd_ringbuffer_del(msm_cmd->ring); + } + + msm_ring->submit.nr_cmds = 0; + msm_ring->submit.nr_bos = 0; + msm_ring->nr_cmds = 0; + msm_ring->nr_bos = 0; + + if (msm_ring->bo_table) { + drmHashDestroy(msm_ring->bo_table); + msm_ring->bo_table = NULL; + } + + if (msm_ring->cmd_table) { + drmHashDestroy(msm_ring->cmd_table); + msm_ring->cmd_table = NULL; + } + + if (msm_ring->is_growable) { + delete_cmds(msm_ring); + } else { + /* in old mode, just reset the # of relocs: */ + current_cmd(ring)->nr_relocs = 0; + } +} + +static void finalize_current_cmd(struct fd_ringbuffer *ring, uint32_t *last_start) +{ + uint32_t submit_offset, size, type; + struct fd_ringbuffer *parent; + + if (ring->parent) { + parent = ring->parent; + type = MSM_SUBMIT_CMD_IB_TARGET_BUF; + } else { + parent = ring; + type = MSM_SUBMIT_CMD_BUF; + } + + submit_offset = offset_bytes(last_start, ring->start); + size = offset_bytes(ring->cur, last_start); + + get_cmd(parent, current_cmd(ring), submit_offset, size, type); +} + +static void dump_submit(struct msm_ringbuffer *msm_ring) +{ + uint32_t i, j; + + for (i = 0; i < msm_ring->submit.nr_bos; i++) { + struct drm_msm_gem_submit_bo *bo = &msm_ring->submit.bos[i]; + ERROR_MSG(" bos[%d]: handle=%u, flags=%x", i, bo->handle, bo->flags); + } + for (i = 0; i < msm_ring->submit.nr_cmds; i++) { + struct drm_msm_gem_submit_cmd *cmd = &msm_ring->submit.cmds[i]; + struct drm_msm_gem_submit_reloc *relocs = U642VOID(cmd->relocs); + ERROR_MSG(" cmd[%d]: type=%u, submit_idx=%u, submit_offset=%u, size=%u", + i, cmd->type, cmd->submit_idx, cmd->submit_offset, cmd->size); + for (j = 0; j < cmd->nr_relocs; j++) { + struct drm_msm_gem_submit_reloc *r = &relocs[j]; + ERROR_MSG(" reloc[%d]: submit_offset=%u, or=%08x, shift=%d, reloc_idx=%u" + ", reloc_offset=%"PRIu64, j, r->submit_offset, r->or, r->shift, + r->reloc_idx, r->reloc_offset); + } + } +} + +static struct drm_msm_gem_submit_reloc * +handle_stateobj_relocs(struct fd_ringbuffer *parent, struct fd_ringbuffer *stateobj, + struct drm_msm_gem_submit_reloc *orig_relocs, unsigned nr_relocs) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(stateobj); + struct drm_msm_gem_submit_reloc *relocs = malloc(nr_relocs * sizeof(*relocs)); + unsigned i; + + for (i = 0; i < nr_relocs; i++) { + unsigned idx = orig_relocs[i].reloc_idx; + struct fd_bo *bo = msm_ring->bos[idx]; + unsigned flags = 0; + + if (msm_ring->submit.bos[idx].flags & MSM_SUBMIT_BO_READ) + flags |= FD_RELOC_READ; + if (msm_ring->submit.bos[idx].flags & MSM_SUBMIT_BO_WRITE) + flags |= FD_RELOC_WRITE; + + relocs[i] = orig_relocs[i]; + relocs[i].reloc_idx = bo2idx(parent, bo, flags); + } + + /* stateobj rb's could have reloc's to other stateobj rb's which didn't + * get propagated to the parent rb at _emit_reloc_ring() time (because + * the parent wasn't known then), so fix that up now: + */ + for (i = 0; i < msm_ring->nr_cmds; i++) { + struct msm_cmd *msm_cmd = msm_ring->cmds[i]; + struct drm_msm_gem_submit_cmd *cmd = &msm_ring->submit.cmds[i]; + + if (msm_ring->cmds[i]->ring == stateobj) + continue; + + assert(msm_cmd->ring->flags & FD_RINGBUFFER_OBJECT); + + if (get_cmd(parent, msm_cmd, cmd->submit_offset, cmd->size, cmd->type)) { + fd_ringbuffer_ref(msm_cmd->ring); + } + } + + return relocs; +} + +static int msm_ringbuffer_flush(struct fd_ringbuffer *ring, uint32_t *last_start, + int in_fence_fd, int *out_fence_fd) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + struct msm_pipe *msm_pipe = to_msm_pipe(ring->pipe); + struct drm_msm_gem_submit req = { + .flags = msm_pipe->pipe, + .queueid = msm_pipe->queue_id, + }; + uint32_t i; + int ret; + + assert(!ring->parent); + + if (in_fence_fd != -1) { + req.flags |= MSM_SUBMIT_FENCE_FD_IN | MSM_SUBMIT_NO_IMPLICIT; + req.fence_fd = in_fence_fd; + } + + if (out_fence_fd) { + req.flags |= MSM_SUBMIT_FENCE_FD_OUT; + } + + finalize_current_cmd(ring, last_start); + + /* for each of the cmd's fix up their reloc's: */ + for (i = 0; i < msm_ring->submit.nr_cmds; i++) { + struct msm_cmd *msm_cmd = msm_ring->cmds[i]; + struct drm_msm_gem_submit_reloc *relocs = msm_cmd->relocs; + struct drm_msm_gem_submit_cmd *cmd; + unsigned nr_relocs = msm_cmd->nr_relocs; + + /* for reusable stateobjs, the reloc table has reloc_idx that + * points into it's own private bos table, rather than the global + * bos table used for the submit, so we need to add the stateobj's + * bos to the global table and construct new relocs table with + * corresponding reloc_idx + */ + if (msm_cmd->ring->flags & FD_RINGBUFFER_OBJECT) { + relocs = handle_stateobj_relocs(ring, msm_cmd->ring, + relocs, nr_relocs); + } + + cmd = &msm_ring->submit.cmds[i]; + cmd->relocs = VOID2U64(relocs); + cmd->nr_relocs = nr_relocs; + } + + /* needs to be after get_cmd() as that could create bos/cmds table: */ + req.bos = VOID2U64(msm_ring->submit.bos), + req.nr_bos = msm_ring->submit.nr_bos; + req.cmds = VOID2U64(msm_ring->submit.cmds), + req.nr_cmds = msm_ring->submit.nr_cmds; + + DEBUG_MSG("nr_cmds=%u, nr_bos=%u", req.nr_cmds, req.nr_bos); + + ret = drmCommandWriteRead(ring->pipe->dev->fd, DRM_MSM_GEM_SUBMIT, + &req, sizeof(req)); + if (ret) { + ERROR_MSG("submit failed: %d (%s)", ret, strerror(errno)); + dump_submit(msm_ring); + } else if (!ret) { + /* update timestamp on all rings associated with submit: */ + for (i = 0; i < msm_ring->submit.nr_cmds; i++) { + struct msm_cmd *msm_cmd = msm_ring->cmds[i]; + msm_cmd->ring->last_timestamp = req.fence; + } + + if (out_fence_fd) { + *out_fence_fd = req.fence_fd; + } + } + + /* free dynamically constructed stateobj relocs tables: */ + for (i = 0; i < msm_ring->submit.nr_cmds; i++) { + struct drm_msm_gem_submit_cmd *cmd = &msm_ring->submit.cmds[i]; + struct msm_cmd *msm_cmd = msm_ring->cmds[i]; + if (msm_cmd->ring->flags & FD_RINGBUFFER_OBJECT) { + free(U642VOID(cmd->relocs)); + } + } + + flush_reset(ring); + + return ret; +} + +static void msm_ringbuffer_grow(struct fd_ringbuffer *ring, uint32_t size) +{ + assert(to_msm_ringbuffer(ring)->is_growable); + finalize_current_cmd(ring, ring->last_start); + ring_cmd_new(ring, size, 0); +} + +static void msm_ringbuffer_reset(struct fd_ringbuffer *ring) +{ + flush_reset(ring); +} + +static void msm_ringbuffer_emit_reloc(struct fd_ringbuffer *ring, + const struct fd_reloc *r) +{ + struct fd_ringbuffer *parent = ring->parent ? ring->parent : ring; + struct msm_bo *msm_bo = to_msm_bo(r->bo); + struct drm_msm_gem_submit_reloc *reloc; + struct msm_cmd *cmd = current_cmd(ring); + uint32_t idx = APPEND(cmd, relocs); + uint32_t addr; + + reloc = &cmd->relocs[idx]; + + reloc->reloc_idx = bo2idx(parent, r->bo, r->flags); + reloc->reloc_offset = r->offset; + reloc->or = r->or; + reloc->shift = r->shift; + reloc->submit_offset = offset_bytes(ring->cur, ring->start) + + to_msm_ringbuffer(ring)->offset; + + addr = msm_bo->presumed; + if (reloc->shift < 0) + addr >>= -reloc->shift; + else + addr <<= reloc->shift; + (*ring->cur++) = addr | r->or; + + if (ring->pipe->gpu_id >= 500) { + struct drm_msm_gem_submit_reloc *reloc_hi; + + /* NOTE: grab reloc_idx *before* APPEND() since that could + * realloc() meaning that 'reloc' ptr is no longer valid: + */ + uint32_t reloc_idx = reloc->reloc_idx; + + idx = APPEND(cmd, relocs); + + reloc_hi = &cmd->relocs[idx]; + + reloc_hi->reloc_idx = reloc_idx; + reloc_hi->reloc_offset = r->offset; + reloc_hi->or = r->orhi; + reloc_hi->shift = r->shift - 32; + reloc_hi->submit_offset = offset_bytes(ring->cur, ring->start) + + to_msm_ringbuffer(ring)->offset; + + addr = msm_bo->presumed >> 32; + if (reloc_hi->shift < 0) + addr >>= -reloc_hi->shift; + else + addr <<= reloc_hi->shift; + (*ring->cur++) = addr | r->orhi; + } +} + +static uint32_t msm_ringbuffer_emit_reloc_ring(struct fd_ringbuffer *ring, + struct fd_ringbuffer *target, uint32_t cmd_idx) +{ + struct msm_cmd *cmd = NULL; + struct msm_ringbuffer *msm_target = to_msm_ringbuffer(target); + uint32_t idx = 0; + int added_cmd = FALSE; + uint32_t size; + uint32_t submit_offset = msm_target->offset; + + LIST_FOR_EACH_ENTRY(cmd, &msm_target->cmd_list, list) { + if (idx == cmd_idx) + break; + idx++; + } + + assert(cmd && (idx == cmd_idx)); + + if (idx < (msm_target->cmd_count - 1)) { + /* All but the last cmd buffer is fully "baked" (ie. already has + * done get_cmd() to add it to the cmds table). But in this case, + * the size we get is invalid (since it is calculated from the + * last cmd buffer): + */ + size = cmd->size; + } else { + struct fd_ringbuffer *parent = ring->parent ? ring->parent : ring; + size = offset_bytes(target->cur, target->start); + added_cmd = get_cmd(parent, cmd, submit_offset, size, + MSM_SUBMIT_CMD_IB_TARGET_BUF); + } + + msm_ringbuffer_emit_reloc(ring, &(struct fd_reloc){ + .bo = cmd->ring_bo, + .flags = FD_RELOC_READ, + .offset = submit_offset, + }); + + /* Unlike traditional ringbuffers which are deleted as a set (after + * being flushed), mesa can't really guarantee that a stateobj isn't + * destroyed after emitted but before flush, so we must hold a ref: + */ + if (added_cmd && (target->flags & FD_RINGBUFFER_OBJECT)) { + fd_ringbuffer_ref(target); + } + + return size; +} + +static uint32_t msm_ringbuffer_cmd_count(struct fd_ringbuffer *ring) +{ + return to_msm_ringbuffer(ring)->cmd_count; +} + +static void msm_ringbuffer_destroy(struct fd_ringbuffer *ring) +{ + struct msm_ringbuffer *msm_ring = to_msm_ringbuffer(ring); + + flush_reset(ring); + delete_cmds(msm_ring); + + free(msm_ring->submit.cmds); + free(msm_ring->submit.bos); + free(msm_ring->bos); + free(msm_ring->cmds); + free(msm_ring); +} + +static const struct fd_ringbuffer_funcs funcs = { + .hostptr = msm_ringbuffer_hostptr, + .flush = msm_ringbuffer_flush, + .grow = msm_ringbuffer_grow, + .reset = msm_ringbuffer_reset, + .emit_reloc = msm_ringbuffer_emit_reloc, + .emit_reloc_ring = msm_ringbuffer_emit_reloc_ring, + .cmd_count = msm_ringbuffer_cmd_count, + .destroy = msm_ringbuffer_destroy, +}; + +drm_private struct fd_ringbuffer * msm_ringbuffer_new(struct fd_pipe *pipe, + uint32_t size, enum fd_ringbuffer_flags flags) +{ + struct msm_ringbuffer *msm_ring; + struct fd_ringbuffer *ring; + + msm_ring = calloc(1, sizeof(*msm_ring)); + if (!msm_ring) { + ERROR_MSG("allocation failed"); + return NULL; + } + + if (size == 0) { + assert(pipe->dev->version >= FD_VERSION_UNLIMITED_CMDS); + size = INIT_SIZE; + msm_ring->is_growable = TRUE; + } + + list_inithead(&msm_ring->cmd_list); + msm_ring->seqno = ++to_msm_device(pipe->dev)->ring_cnt; + + ring = &msm_ring->base; + atomic_set(&ring->refcnt, 1); + + ring->funcs = &funcs; + ring->size = size; + ring->pipe = pipe; /* needed in ring_cmd_new() */ + + ring_cmd_new(ring, size, flags); + + return ring; +} |