/**************************************************************************
*
* Copyright 2008 Tungsten Graphics, Inc., Cedar Park, TX., USA
* All Rights Reserved.
* Copyright 2009 VMware, Inc., Palo Alto, CA., USA
* All Rights Reserved.
*
* 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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: Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "wsbm_pool.h"
#include "wsbm_fencemgr.h"
#include "wsbm_manager.h"
#include "wsbm_mm.h"
#include "wsbm_priv.h"
/*
* Malloced memory must be aligned to 16 bytes, since that's what
* the DMA bitblt requires.
*/
#define WSBM_USER_ALIGN_ADD 16
#define WSBM_USER_ALIGN_SYSMEM(_val) \
((void *)(((unsigned long) (_val) + 15) & ~15))
struct _WsbmUserBuffer
{
struct _WsbmBufStorage buf;
struct _WsbmKernelBuf kBuf;
/* Protected by the pool mutex */
struct _WsbmListHead lru;
struct _WsbmListHead delayed;
/* Protected by the buffer mutex */
unsigned long size;
unsigned long alignment;
struct _WsbmCond event;
uint32_t proposedPlacement;
uint32_t newFenceType;
void *map;
void *sysmem;
int unFenced;
struct _WsbmFenceObject *fence;
struct _WsbmMMNode *node;
struct _WsbmAtomic writers;
};
struct _WsbmUserPool
{
/*
* Constant after initialization.
*/
struct _WsbmBufferPool pool;
unsigned long agpOffset;
unsigned long agpMap;
unsigned long agpSize;
unsigned long vramOffset;
unsigned long vramMap;
unsigned long vramSize;
struct _WsbmMutex mutex;
struct _WsbmListHead delayed;
struct _WsbmListHead vramLRU;
struct _WsbmListHead agpLRU;
struct _WsbmMM vramMM;
struct _WsbmMM agpMM;
uint32_t(*fenceTypes) (uint64_t);
};
static inline struct _WsbmUserPool *
userPool(struct _WsbmUserBuffer *buf)
{
return containerOf(buf->buf.pool, struct _WsbmUserPool, pool);
}
static inline struct _WsbmUserBuffer *
userBuf(struct _WsbmBufStorage *buf)
{
return containerOf(buf, struct _WsbmUserBuffer, buf);
}
static void
waitIdleLocked(struct _WsbmBufStorage *buf, int lazy)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
while (vBuf->unFenced || vBuf->fence != NULL) {
if (vBuf->unFenced)
WSBM_COND_WAIT(&vBuf->event, &buf->mutex);
if (vBuf->fence != NULL) {
if (!wsbmFenceSignaled(vBuf->fence, vBuf->kBuf.fence_type_mask)) {
struct _WsbmFenceObject *fence =
wsbmFenceReference(vBuf->fence);
WSBM_MUTEX_UNLOCK(&buf->mutex);
(void)wsbmFenceFinish(fence, vBuf->kBuf.fence_type_mask,
lazy);
WSBM_MUTEX_LOCK(&buf->mutex);
if (vBuf->fence == fence)
wsbmFenceUnreference(&vBuf->fence);
wsbmFenceUnreference(&fence);
} else {
wsbmFenceUnreference(&vBuf->fence);
}
}
}
}
static int
pool_waitIdle(struct _WsbmBufStorage *buf, int lazy)
{
WSBM_MUTEX_UNLOCK(&buf->mutex);
waitIdleLocked(buf, lazy);
WSBM_MUTEX_UNLOCK(&buf->mutex);
return 0;
}
static int
evict_lru(struct _WsbmListHead *lru)
{
struct _WsbmUserBuffer *vBuf;
struct _WsbmUserPool *p;
struct _WsbmListHead *list = lru->next;
int err;
if (list == lru) {
return -ENOMEM;
}
vBuf = WSBMLISTENTRY(list, struct _WsbmUserBuffer, lru);
p = userPool(vBuf);
WSBM_MUTEX_UNLOCK(&p->mutex);
WSBM_MUTEX_LOCK(&vBuf->buf.mutex);
WSBM_MUTEX_LOCK(&p->mutex);
vBuf->sysmem = malloc(vBuf->size + WSBM_USER_ALIGN_ADD);
if (!vBuf->sysmem) {
err = -ENOMEM;
goto out_unlock;
}
(void)wsbmFenceFinish(vBuf->fence, vBuf->kBuf.fence_type_mask, 0);
wsbmFenceUnreference(&vBuf->fence);
memcpy(WSBM_USER_ALIGN_SYSMEM(vBuf->sysmem), vBuf->map, vBuf->size);
WSBMLISTDELINIT(&vBuf->lru);
vBuf->kBuf.placement = WSBM_PL_FLAG_SYSTEM;
vBuf->map = WSBM_USER_ALIGN_SYSMEM(vBuf->sysmem);
/*
* FIXME: Free memory.
*/
err = 0;
out_unlock:
WSBM_MUTEX_UNLOCK(&vBuf->buf.mutex);
return err;
}
static struct _WsbmBufStorage *
pool_create(struct _WsbmBufferPool *pool,
unsigned long size, uint32_t placement, unsigned alignment)
{
struct _WsbmUserPool *p = containerOf(pool, struct _WsbmUserPool, pool);
struct _WsbmUserBuffer *vBuf = calloc(1, sizeof(*vBuf));
if (!vBuf)
return NULL;
wsbmBufStorageInit(&vBuf->buf, pool);
vBuf->sysmem = NULL;
vBuf->proposedPlacement = placement;
vBuf->size = size;
vBuf->alignment = alignment;
WSBMINITLISTHEAD(&vBuf->lru);
WSBMINITLISTHEAD(&vBuf->delayed);
WSBM_MUTEX_LOCK(&p->mutex);
if (placement & WSBM_PL_FLAG_TT) {
vBuf->node = wsbmMMSearchFree(&p->agpMM, size, alignment, 1);
if (vBuf->node)
vBuf->node = wsbmMMGetBlock(vBuf->node, size, alignment);
if (vBuf->node) {
vBuf->kBuf.placement = WSBM_PL_FLAG_TT;
vBuf->kBuf.gpuOffset = p->agpOffset + vBuf->node->start;
vBuf->map = (void *)(p->agpMap + vBuf->node->start);
WSBMLISTADDTAIL(&vBuf->lru, &p->agpLRU);
goto have_mem;
}
}
if (placement & WSBM_PL_FLAG_VRAM) {
vBuf->node = wsbmMMSearchFree(&p->vramMM, size, alignment, 1);
if (vBuf->node)
vBuf->node = wsbmMMGetBlock(vBuf->node, size, alignment);
if (vBuf->node) {
vBuf->kBuf.placement = WSBM_PL_FLAG_VRAM;
vBuf->kBuf.gpuOffset = p->vramOffset + vBuf->node->start;
vBuf->map = (void *)(p->vramMap + vBuf->node->start);
WSBMLISTADDTAIL(&vBuf->lru, &p->vramLRU);
goto have_mem;
}
}
if ((placement & WSBM_PL_FLAG_NO_EVICT)
&& !(placement & WSBM_PL_FLAG_SYSTEM)) {
WSBM_MUTEX_UNLOCK(&p->mutex);
goto out_err;
}
vBuf->sysmem = malloc(size + WSBM_USER_ALIGN_ADD);
vBuf->kBuf.placement = WSBM_PL_FLAG_SYSTEM;
vBuf->map = WSBM_USER_ALIGN_SYSMEM(vBuf->sysmem);
have_mem:
WSBM_MUTEX_UNLOCK(&p->mutex);
if (vBuf->sysmem != NULL
|| (!(vBuf->kBuf.placement & WSBM_PL_FLAG_SYSTEM)))
return &vBuf->buf;
out_err:
free(vBuf);
return NULL;
}
static int
pool_validate(struct _WsbmBufStorage *buf, uint64_t set_flags,
uint64_t clr_flags)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
struct _WsbmUserPool *p = userPool(vBuf);
int err = -ENOMEM;
WSBM_MUTEX_LOCK(&buf->mutex);
while (wsbmAtomicRead(&vBuf->writers) != 0)
WSBM_COND_WAIT(&vBuf->event, &buf->mutex);
vBuf->unFenced = 1;
WSBM_MUTEX_LOCK(&p->mutex);
WSBMLISTDELINIT(&vBuf->lru);
vBuf->proposedPlacement =
(vBuf->proposedPlacement | set_flags) & ~clr_flags;
if ((vBuf->proposedPlacement & vBuf->kBuf.placement & WSBM_PL_MASK_MEM) ==
vBuf->kBuf.placement) {
err = 0;
goto have_mem;
}
/*
* We're moving to another memory region, so evict first and we'll
* do a sw copy to the other region.
*/
if (!(vBuf->kBuf.placement & WSBM_PL_FLAG_SYSTEM)) {
struct _WsbmListHead tmpLRU;
WSBMINITLISTHEAD(&tmpLRU);
WSBMLISTADDTAIL(&tmpLRU, &vBuf->lru);
err = evict_lru(&tmpLRU);
if (err)
goto have_mem;
}
if (vBuf->proposedPlacement & WSBM_PL_FLAG_TT) {
do {
vBuf->node =
wsbmMMSearchFree(&p->agpMM, vBuf->size, vBuf->alignment, 1);
if (vBuf->node)
vBuf->node =
wsbmMMGetBlock(vBuf->node, vBuf->size, vBuf->alignment);
if (vBuf->node) {
vBuf->kBuf.placement = WSBM_PL_FLAG_TT;
vBuf->kBuf.gpuOffset = p->agpOffset + vBuf->node->start;
vBuf->map = (void *)(p->agpMap + vBuf->node->start);
memcpy(vBuf->map, WSBM_USER_ALIGN_SYSMEM(vBuf->sysmem),
vBuf->size);
free(vBuf->sysmem);
goto have_mem;
}
} while (evict_lru(&p->agpLRU) == 0);
}
if (vBuf->proposedPlacement & WSBM_PL_FLAG_VRAM) {
do {
vBuf->node =
wsbmMMSearchFree(&p->vramMM, vBuf->size, vBuf->alignment, 1);
if (vBuf->node)
vBuf->node =
wsbmMMGetBlock(vBuf->node, vBuf->size, vBuf->alignment);
if (!err && vBuf->node) {
vBuf->kBuf.placement = WSBM_PL_FLAG_VRAM;
vBuf->kBuf.gpuOffset = p->vramOffset + vBuf->node->start;
vBuf->map = (void *)(p->vramMap + vBuf->node->start);
memcpy(vBuf->map, WSBM_USER_ALIGN_SYSMEM(vBuf->sysmem),
vBuf->size);
free(vBuf->sysmem);
goto have_mem;
}
} while (evict_lru(&p->vramLRU) == 0);
}
if (vBuf->proposedPlacement & WSBM_PL_FLAG_SYSTEM)
goto have_mem;
err = -ENOMEM;
have_mem:
vBuf->newFenceType = p->fenceTypes(set_flags);
WSBM_MUTEX_UNLOCK(&p->mutex);
WSBM_MUTEX_UNLOCK(&buf->mutex);
return err;
}
static int
pool_setStatus(struct _WsbmBufStorage *buf,
uint32_t set_placement, uint32_t clr_placement)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
int ret;
ret = pool_validate(buf, set_placement, clr_placement);
vBuf->unFenced = 0;
return ret;
}
void
release_delayed_buffers(struct _WsbmUserPool *p)
{
struct _WsbmUserBuffer *vBuf;
struct _WsbmListHead *list, *next;
WSBM_MUTEX_LOCK(&p->mutex);
/*
* We don't need to take the buffer mutexes in this loop, since
* the only other user is the evict_lru function, which has the
* pool mutex held when accessing the buffer fence member.
*/
WSBMLISTFOREACHSAFE(list, next, &p->delayed) {
vBuf = WSBMLISTENTRY(list, struct _WsbmUserBuffer, delayed);
if (!vBuf->fence
|| wsbmFenceSignaled(vBuf->fence, vBuf->kBuf.fence_type_mask)) {
if (vBuf->fence)
wsbmFenceUnreference(&vBuf->fence);
WSBMLISTDEL(&vBuf->delayed);
WSBMLISTDEL(&vBuf->lru);
if ((vBuf->kBuf.placement & WSBM_PL_FLAG_SYSTEM) == 0)
wsbmMMPutBlock(vBuf->node);
else
free(vBuf->sysmem);
free(vBuf);
} else
break;
}
WSBM_MUTEX_UNLOCK(&p->mutex);
}
static void
pool_destroy(struct _WsbmBufStorage **buf)
{
struct _WsbmUserBuffer *vBuf = userBuf(*buf);
struct _WsbmUserPool *p = userPool(vBuf);
*buf = NULL;
WSBM_MUTEX_LOCK(&vBuf->buf.mutex);
if ((vBuf->fence
&& !wsbmFenceSignaled(vBuf->fence, vBuf->kBuf.fence_type_mask))) {
WSBM_MUTEX_LOCK(&p->mutex);
WSBMLISTADDTAIL(&vBuf->delayed, &p->delayed);
WSBM_MUTEX_UNLOCK(&p->mutex);
WSBM_MUTEX_UNLOCK(&vBuf->buf.mutex);
return;
}
if (vBuf->fence)
wsbmFenceUnreference(&vBuf->fence);
WSBM_MUTEX_LOCK(&p->mutex);
WSBMLISTDEL(&vBuf->lru);
WSBM_MUTEX_UNLOCK(&p->mutex);
if (!(vBuf->kBuf.placement & WSBM_PL_FLAG_SYSTEM))
wsbmMMPutBlock(vBuf->node);
else
free(vBuf->sysmem);
free(vBuf);
return;
}
static int
pool_map(struct _WsbmBufStorage *buf, unsigned mode __attribute__ ((unused)), void **virtual)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
*virtual = vBuf->map;
return 0;
}
static void
pool_unmap(struct _WsbmBufStorage *buf __attribute__ ((unused)))
{
;
}
static void
pool_releaseFromCpu(struct _WsbmBufStorage *buf, unsigned mode __attribute__ ((unused)))
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
if (wsbmAtomicDecZero(&vBuf->writers))
WSBM_COND_BROADCAST(&vBuf->event);
}
static int
pool_syncForCpu(struct _WsbmBufStorage *buf, unsigned mode)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
int ret = 0;
WSBM_MUTEX_LOCK(&buf->mutex);
if ((mode & WSBM_SYNCCPU_DONT_BLOCK)) {
if (vBuf->unFenced) {
ret = -EBUSY;
goto out_unlock;
}
ret = 0;
if ((vBuf->fence == NULL) ||
wsbmFenceSignaled(vBuf->fence, vBuf->kBuf.fence_type_mask)) {
wsbmFenceUnreference(&vBuf->fence);
wsbmAtomicInc(&vBuf->writers);
} else
ret = -EBUSY;
goto out_unlock;
}
waitIdleLocked(buf, 0);
wsbmAtomicInc(&vBuf->writers);
out_unlock:
WSBM_MUTEX_UNLOCK(&buf->mutex);
return ret;
}
static unsigned long
pool_offset(struct _WsbmBufStorage *buf)
{
return userBuf(buf)->kBuf.gpuOffset;
}
static unsigned long
pool_poolOffset(struct _WsbmBufStorage *buf __attribute__ ((unused)))
{
return 0UL;
}
static unsigned long
pool_size(struct _WsbmBufStorage *buf)
{
return userBuf(buf)->size;
}
static void
pool_fence(struct _WsbmBufStorage *buf, struct _WsbmFenceObject *fence)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
struct _WsbmUserPool *p = userPool(vBuf);
WSBM_MUTEX_LOCK(&buf->mutex);
if (vBuf->fence)
wsbmFenceUnreference(&vBuf->fence);
vBuf->fence = wsbmFenceReference(fence);
vBuf->unFenced = 0;
vBuf->kBuf.fence_type_mask = vBuf->newFenceType;
WSBM_COND_BROADCAST(&vBuf->event);
WSBM_MUTEX_LOCK(&p->mutex);
if (vBuf->kBuf.placement & WSBM_PL_FLAG_VRAM)
WSBMLISTADDTAIL(&vBuf->lru, &p->vramLRU);
else if (vBuf->kBuf.placement & WSBM_PL_FLAG_TT)
WSBMLISTADDTAIL(&vBuf->lru, &p->agpLRU);
WSBM_MUTEX_UNLOCK(&p->mutex);
WSBM_MUTEX_UNLOCK(&buf->mutex);
}
static void
pool_unvalidate(struct _WsbmBufStorage *buf)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
struct _WsbmUserPool *p = userPool(vBuf);
WSBM_MUTEX_LOCK(&buf->mutex);
if (!vBuf->unFenced)
goto out_unlock;
vBuf->unFenced = 0;
WSBM_COND_BROADCAST(&vBuf->event);
WSBM_MUTEX_LOCK(&p->mutex);
if (vBuf->kBuf.placement & WSBM_PL_FLAG_VRAM)
WSBMLISTADDTAIL(&vBuf->lru, &p->vramLRU);
else if (vBuf->kBuf.placement & WSBM_PL_FLAG_TT)
WSBMLISTADDTAIL(&vBuf->lru, &p->agpLRU);
WSBM_MUTEX_UNLOCK(&p->mutex);
out_unlock:
WSBM_MUTEX_UNLOCK(&buf->mutex);
}
static struct _WsbmKernelBuf *
pool_kernel(struct _WsbmBufStorage *buf)
{
struct _WsbmUserBuffer *vBuf = userBuf(buf);
return &vBuf->kBuf;
}
static void
pool_takedown(struct _WsbmBufferPool *pool)
{
struct _WsbmUserPool *p = containerOf(pool, struct _WsbmUserPool, pool);
int empty;
do {
release_delayed_buffers(p);
WSBM_MUTEX_LOCK(&p->mutex);
empty = (p->delayed.next == &p->delayed);
WSBM_MUTEX_UNLOCK(&p->mutex);
if (!empty)
usleep(1000);
} while (!empty);
WSBM_MUTEX_LOCK(&p->mutex);
while (evict_lru(&p->vramLRU) == 0) ;
while (evict_lru(&p->agpLRU) == 0) ;
WSBM_MUTEX_UNLOCK(&p->mutex);
wsbmMMtakedown(&p->agpMM);
wsbmMMtakedown(&p->vramMM);
free(p);
}
void
wsbmUserPoolClean(struct _WsbmBufferPool *pool, int cleanVram, int cleanAgp)
{
struct _WsbmUserPool *p = containerOf(pool, struct _WsbmUserPool, pool);
WSBM_MUTEX_LOCK(&p->mutex);
if (cleanVram)
while (evict_lru(&p->vramLRU) == 0) ;
if (cleanAgp)
while (evict_lru(&p->agpLRU) == 0) ;
WSBM_MUTEX_UNLOCK(&p->mutex);
}
struct _WsbmBufferPool *
wsbmUserPoolInit(void *vramAddr,
unsigned long vramStart, unsigned long vramSize,
void *agpAddr, unsigned long agpStart,
unsigned long agpSize,
uint32_t(*fenceTypes) (uint64_t set_flags))
{
struct _WsbmBufferPool *pool;
struct _WsbmUserPool *uPool;
int ret;
uPool = calloc(1, sizeof(*uPool));
if (!uPool)
goto out_err0;
ret = WSBM_MUTEX_INIT(&uPool->mutex);
if (ret)
goto out_err0;
ret = wsbmMMinit(&uPool->vramMM, 0, vramSize);
if (ret)
goto out_err1;
ret = wsbmMMinit(&uPool->agpMM, 0, agpSize);
if (ret)
goto out_err2;
WSBMINITLISTHEAD(&uPool->delayed);
WSBMINITLISTHEAD(&uPool->vramLRU);
WSBMINITLISTHEAD(&uPool->agpLRU);
uPool->agpOffset = agpStart;
uPool->agpMap = (unsigned long)agpAddr;
uPool->vramOffset = vramStart;
uPool->vramMap = (unsigned long)vramAddr;
uPool->fenceTypes = fenceTypes;
pool = &uPool->pool;
pool->map = &pool_map;
pool->unmap = &pool_unmap;
pool->destroy = &pool_destroy;
pool->offset = &pool_offset;
pool->poolOffset = &pool_poolOffset;
pool->size = &pool_size;
pool->create = &pool_create;
pool->fence = &pool_fence;
pool->unvalidate = &pool_unvalidate;
pool->kernel = &pool_kernel;
pool->validate = &pool_validate;
pool->waitIdle = &pool_waitIdle;
pool->takeDown = &pool_takedown;
pool->setStatus = &pool_setStatus;
pool->syncforcpu = &pool_syncForCpu;
pool->releasefromcpu = &pool_releaseFromCpu;
return pool;
out_err2:
wsbmMMtakedown(&uPool->vramMM);
out_err1:
WSBM_MUTEX_FREE(&uPool->mutex);
out_err0:
free(uPool);
return NULL;
}