diff options
Diffstat (limited to 'armada_bufmgr.c')
-rw-r--r-- | armada_bufmgr.c | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/armada_bufmgr.c b/armada_bufmgr.c new file mode 100644 index 0000000..01d9955 --- /dev/null +++ b/armada_bufmgr.c @@ -0,0 +1,554 @@ +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/mman.h> +#include <time.h> + +#include <drm.h> + +#include "libdrm_lists.h" +#include "armada_bufmgr.h" +#include "armada_ioctl.h" + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +/* The interval in seconds between cache cleans */ +#define BO_CACHE_CLEAN_INTERVAL 1 +/* The maximum age in seconds of a BO in the cache */ +#define BO_CACHE_MAX_AGE 2 +/* Number of buckets in the BO cache */ +#define NUM_BUCKETS (3*9) + +/* + * These sizes come from the i915 DRM backend - which uses roughly + * for n = 2.. + * (4096 << n) + (4096 << n) * 1 / 4 + * (4096 << n) + (4096 << n) * 2 / 4 + * (4096 << n) + (4096 << n) * 3 / 4 + * The reasoning being that powers of two are too wasteful in X. + */ +static size_t bucket_size[NUM_BUCKETS] = { + 4096, 8192, 12288, + 20480, 24576, 28672, + 40960, 49152, 57344, + 81920, 98304, 114688, + 163840, 196608, 229376, + 327680, 393216, 458752, + 655360, 786432, 917504, + 1310720, 1572864, 1835008, + 2621440, 3145728, 3670016, +}; + +struct armada_bucket { + drmMMListHead head; /* LRU list of bos in this size */ + size_t size; +}; + +struct armada_bo_cache { + struct armada_bucket buckets[NUM_BUCKETS]; + drmMMListHead head; /* LRU list of all freed bos */ + time_t last_cleaned; +}; + +struct drm_armada_bufmgr { + struct armada_bo_cache cache; + int fd; +}; + +struct armada_bo { + struct drm_armada_bo bo; + struct drm_armada_bufmgr *mgr; /* manager associated with this bo */ + drmMMListHead bucket; /* Cache bucket list */ + drmMMListHead free; /* Free list */ + time_t free_time; /* Time this bo was freed */ + size_t alloc_size; /* Allocated size */ + uint32_t ref; /* Reference count */ + uint32_t name; /* Global name */ +}; + +#define to_armada_bo(_bo) container_of(_bo, struct armada_bo, bo) + +#ifndef DRM_IOCTL_MODE_CREATE_DUMB +/* create a dumb scanout buffer */ +struct drm_mode_create_dumb { + uint32_t height; + uint32_t width; + uint32_t bpp; + uint32_t flags; + /* handle, pitch, size will be returned */ + uint32_t handle; + uint32_t pitch; + uint64_t size; +}; +#define DRM_IOCTL_MODE_CREATE_DUMB DRM_IOWR(0xB2, struct drm_mode_create_dumb) +#endif + +#ifndef DRM_IOCTL_MODE_DESTROY_DUMB +struct drm_mode_destroy_dumb { + uint32_t handle; +}; +#define DRM_IOCTL_MODE_DESTROY_DUMB DRM_IOWR(0xB4, struct drm_mode_destroy_dumb) +#endif + +#ifndef DRM_IOCTL_MODE_MAP_DUMB +struct drm_mode_map_dumb { + uint32_t handle; + uint32_t pad; + uint64_t offset; +}; +#define DRM_IOCTL_MODE_MAP_DUMB DRM_IOWR(0xB3, struct drm_mode_map_dumb) +#endif + +/* Given a width and bpp, return the pitch of a bo */ +static unsigned armada_bo_pitch(unsigned width, unsigned bpp) +{ + unsigned pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2; + + /* 88AP510 spec recommends pitch be a multiple of 128 */ + return (pitch + 127) & ~127; +} + +/* Given the pitch and height, return the allocated size in bytes of a bo */ +static size_t armada_bo_size(unsigned pitch, unsigned height) +{ + return pitch * height; +} + +static size_t armada_bo_round_size(size_t size) +{ + if (size > 1048576) + size = (size + 1048575) & ~1048575; + else if (size > 65536) + size = (size + 65535) & ~65535; + else + size = (size + 4095) & ~4095; + return size; +} + +static void armada_bo_free(struct armada_bo *bo) +{ + int ret, fd = bo->mgr->fd; + + if (bo->bo.ptr) { + munmap(bo->bo.ptr, bo->alloc_size); + bo->bo.ptr = NULL; + } + + if (bo->bo.type == DRM_ARMADA_BO_DUMB) { + struct drm_mode_destroy_dumb arg; + + memset(&arg, 0, sizeof(arg)); + arg.handle = bo->bo.handle; + ret = drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &arg); + } else { + struct drm_gem_close close; + + memset(&close, 0, sizeof(close)); + close.handle = bo->bo.handle; + ret = ioctl(fd, DRM_IOCTL_GEM_CLOSE, &close); + } + + if (ret == 0) + free(bo); +} + +static void armada_bo_cache_init(struct armada_bo_cache *cache) +{ + struct timespec time; + unsigned i; + + clock_gettime(CLOCK_MONOTONIC, &time); + + cache->last_cleaned = time.tv_sec; + DRMINITLISTHEAD(&cache->head); + + for (i = 0; i < NUM_BUCKETS; i++) { + DRMINITLISTHEAD(&cache->buckets[i].head); + cache->buckets[i].size = bucket_size[i]; + } +} + +static void armada_bo_cache_fini(struct armada_bo_cache *cache) +{ + while (!DRMLISTEMPTY(&cache->head)) { + struct armada_bo *bo; + + bo = DRMLISTENTRY(struct armada_bo, cache->head.next, free); + + DRMLISTDEL(&bo->bucket); + DRMLISTDEL(&bo->free); + + armada_bo_free(bo); + } +} + +static struct armada_bucket *armada_find_bucket(struct armada_bo_cache *cache, size_t size) +{ + unsigned i; + + for (i = 0; i < NUM_BUCKETS; i++) { + struct armada_bucket *bucket = &cache->buckets[i]; + + if (bucket->size >= size) + return bucket; + } + + return NULL; +} + +static void armada_bo_cache_clean(struct armada_bo_cache *cache, time_t time) +{ + if (time - cache->last_cleaned < BO_CACHE_CLEAN_INTERVAL) + return; + + cache->last_cleaned = time; + + while (!DRMLISTEMPTY(&cache->head)) { + struct armada_bo *bo; + + bo = DRMLISTENTRY(struct armada_bo, cache->head.next, free); + if (time - bo->free_time < BO_CACHE_MAX_AGE) + break; + + DRMLISTDEL(&bo->bucket); + DRMLISTDEL(&bo->free); + + armada_bo_free(bo); + } +} + +static struct armada_bo *armada_bo_bucket_get(struct armada_bucket *bucket, size_t size) +{ + struct armada_bo *bo = NULL; + + if (!DRMLISTEMPTY(&bucket->head)) { + drmMMListHead *entry = bucket->head.next; + + bo = DRMLISTENTRY(struct armada_bo, entry, bucket); + DRMLISTDEL(&bo->bucket); + DRMLISTDEL(&bo->free); + } + return bo; +} + +static void armada_bo_cache_put(struct armada_bo *bo) +{ + struct armada_bo_cache *cache = &bo->mgr->cache; + struct armada_bucket *bucket = armada_find_bucket(cache, bo->alloc_size); + + if (bucket) { + struct timespec time; + + clock_gettime(CLOCK_MONOTONIC, &time); + + bo->free_time = time.tv_sec; + DRMLISTADDTAIL(&bo->bucket, &bucket->head); + DRMLISTADDTAIL(&bo->free, &cache->head); + + armada_bo_cache_clean(cache, time.tv_sec); + + return; + } + armada_bo_free(bo); +} + +struct drm_armada_bo *drm_armada_bo_create_phys(struct drm_armada_bufmgr *mgr, + uint32_t phys, size_t size) +{ + struct armada_bo *bo; + int fd = mgr->fd; + + bo = calloc(1, sizeof *bo); + if (bo) { + struct drm_armada_gem_create_phys arg; + int ret; + + memset(&arg, 0, sizeof(arg)); + arg.phys = phys; + arg.size = size; + + ret = drmIoctl(fd, DRM_IOCTL_ARMADA_GEM_CREATE_PHYS, &arg); + if (ret) { + free(bo); + return NULL; + } + bo->bo.ref = 1; + bo->bo.handle = arg.handle; + bo->bo.size = size; + bo->bo.phys = phys; + bo->bo.type = DRM_ARMADA_BO_LINEAR; + bo->alloc_size = size; + bo->ref = 1; + bo->mgr = mgr; + } + return &bo->bo; +} + +struct drm_armada_bo *drm_armada_bo_create(struct drm_armada_bufmgr *mgr, + unsigned w, unsigned h, unsigned bpp) +{ + struct drm_armada_gem_create arg; + struct armada_bucket *bucket; + struct armada_bo *bo; + unsigned pitch; + size_t alloc_size; + int fd = mgr->fd; + int ret; + + pitch = armada_bo_pitch(w, bpp); + alloc_size = armada_bo_size(pitch, h); + + /* Try to find a bucket for this allocation */ + bucket = armada_find_bucket(&mgr->cache, alloc_size); + if (bucket) { + /* Can we allocate from our cache? */ + bo = armada_bo_bucket_get(bucket, alloc_size); + if (bo) { + bo->bo.size = pitch * h; + bo->bo.pitch = pitch; + bo->ref = 1; + return &bo->bo; + } + + /* Otherwise, allocate a bo of the bucket size */ + alloc_size = bucket->size; + } else { + /* No bucket, so round the size up according to our old rules */ + alloc_size = armada_bo_round_size(alloc_size); + } + + /* No, create a new bo */ + bo = calloc(1, sizeof *bo); + if (!bo) + return NULL; + + memset(&arg, 0, sizeof(arg)); + arg.width = w; + arg.height = h; + arg.bpp = bpp; + arg.size = alloc_size; + + ret = drmIoctl(fd, DRM_IOCTL_ARMADA_GEM_CREATE, &arg); + if (ret) { + free(bo); + return NULL; + } + + bo->bo.ref = 1; + bo->bo.handle = arg.handle; + bo->bo.size = pitch * h; + bo->bo.pitch = pitch; + bo->bo.type = DRM_ARMADA_BO_SHMEM; + bo->alloc_size = alloc_size; + bo->ref = 1; + bo->mgr = mgr; + + return &bo->bo; +} + +struct drm_armada_bo *drm_armada_bo_create_from_name(struct drm_armada_bufmgr *mgr, + uint32_t name) +{ + struct armada_bo *bo; + int fd = mgr->fd; + + bo = calloc(1, sizeof *bo); + if (bo) { + struct drm_gem_open arg; + int ret; + + memset(&arg, 0, sizeof(arg)); + arg.name = name; + ret = drmIoctl(fd, DRM_IOCTL_GEM_OPEN, &arg); + if (ret == -1) { + free(bo); + return NULL; + } + bo->bo.ref = 1; + bo->bo.handle = arg.handle; + bo->bo.size = arg.size; + bo->bo.type = DRM_ARMADA_BO_LINEAR; /* assumed */ + bo->alloc_size = arg.size; + bo->ref = 1; + bo->name = name; + bo->mgr = mgr; + } + return &bo->bo; +} + +struct drm_armada_bo *drm_armada_bo_dumb_create(struct drm_armada_bufmgr *mgr, + unsigned w, unsigned h, unsigned bpp) +{ + struct armada_bo *bo; + int fd = mgr->fd; + + bo = calloc(1, sizeof *bo); + if (bo) { + struct drm_mode_create_dumb arg; + int ret; + + memset(&arg, 0, sizeof(arg)); + arg.width = w; + arg.height = h; + arg.bpp = bpp; + + ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &arg); + if (ret) { + free(bo); + return NULL; + } + bo->bo.ref = 1; + bo->bo.handle = arg.handle; + bo->bo.size = arg.size; + bo->bo.pitch = arg.pitch; + bo->bo.type = DRM_ARMADA_BO_DUMB; + bo->alloc_size = arg.size; + bo->ref = 1; + bo->mgr = mgr; + } + return &bo->bo; +} + +void drm_armada_bo_get(struct drm_armada_bo *dbo) +{ + struct armada_bo *bo = to_armada_bo(dbo); + bo->ref++; +} + +void drm_armada_bo_put(struct drm_armada_bo *dbo) +{ + struct armada_bo *bo = to_armada_bo(dbo); + + if (bo->ref-- == 1) { + int ret; + + if (bo->bo.type == DRM_ARMADA_BO_SHMEM) + armada_bo_cache_put(bo); + else + armada_bo_free(bo); + } +} + +int drm_armada_bo_flink(struct drm_armada_bo *dbo, uint32_t *name) +{ + struct armada_bo *bo = to_armada_bo(dbo); + int fd = bo->mgr->fd; + + if (!bo->name) { + struct drm_gem_flink flink; + int ret; + + memset(&flink, 0, sizeof(flink)); + flink.handle = bo->bo.handle; + ret = ioctl(fd, DRM_IOCTL_GEM_FLINK, &flink); + if (ret) + return ret; + bo->name = flink.name; + } + *name = bo->name; + return 0; +} + +int drm_armada_bo_map(struct drm_armada_bo *dbo) +{ + struct armada_bo *bo = to_armada_bo(dbo); + void *map; + int ret, fd = bo->mgr->fd; + + if (bo->bo.ptr) + return 0; + + if (bo->bo.type == DRM_ARMADA_BO_DUMB) { + struct drm_mode_map_dumb arg; + + memset(&arg, 0, sizeof(arg)); + arg.handle = bo->bo.handle; + + ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &arg); + if (ret) + return ret; + + map = mmap(0, bo->alloc_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + arg.offset); + + if (map == MAP_FAILED) + return -1; + } else if (bo->bo.type == DRM_ARMADA_BO_SHMEM) { + struct drm_armada_gem_mmap arg; + + memset(&arg, 0, sizeof(arg)); + arg.handle = bo->bo.handle; + arg.offset = 0; + arg.size = bo->alloc_size; + + ret = drmIoctl(fd, DRM_IOCTL_ARMADA_GEM_MMAP, &arg); + if (ret) + return -1; + + map = (void *)(uintptr_t)arg.addr; + } else { + errno = EINVAL; + return -1; + } + + bo->bo.ptr = map; + + return 0; +} + +uint32_t drm_armada_bo_phys(struct drm_armada_bo *dbo) +{ + struct armada_bo *bo = to_armada_bo(dbo); + struct drm_armada_gem_prop arg; + int ret, fd = bo->mgr->fd; + + memset(&arg, 0, sizeof(arg)); + arg.handle = bo->bo.handle; + + ret = drmIoctl(fd, DRM_IOCTL_ARMADA_GEM_PROP, &arg); + + return ret ? -1 : (uint32_t)arg.phys; +} + +int drm_armada_bo_subdata(struct drm_armada_bo *dbo, unsigned long offset, + unsigned long size, const void *data) +{ + struct armada_bo *bo = to_armada_bo(dbo); + struct drm_armada_gem_pwrite arg; + int fd = bo->mgr->fd; + + memset(&arg, 0, sizeof(arg)); + arg.handle = bo->bo.handle; + arg.offset = offset; + arg.size = size; + arg.ptr = (uint64_t)(uintptr_t)data; + + return drmIoctl(fd, DRM_IOCTL_ARMADA_GEM_PWRITE, &arg); +} + +int drm_armada_init(int fd, struct drm_armada_bufmgr **mgrp) +{ + struct drm_armada_bufmgr *mgr; + + mgr = calloc(1, sizeof(*mgr)); + if (!mgr) + return -1; + + armada_bo_cache_init(&mgr->cache); + mgr->fd = fd; + *mgrp = mgr; + + return 0; +} + +void drm_armada_fini(struct drm_armada_bufmgr *mgr) +{ + armada_bo_cache_fini(&mgr->cache); + free(mgr); +} |