blob: d1c359018cba7a6846b1461fe17fe3659e92f87a [file] [log] [blame]
/**************************************************************************
*
* Copyright (c) 2006-2008 Tungsten Graphics, Inc., Cedar Park, TX., USA
* All Rights Reserved.
* Copyright (c) 2009 VMware, Inc., Palo Alto, CA., USA
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
**************************************************************************/
/*
* Authors: Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
*/
#include "psb_ttm_fence_api.h"
#include "psb_ttm_fence_driver.h"
#include <linux/wait.h>
#include <linux/sched.h>
#include <drm/drmP.h>
/*
* Simple implementation for now.
*/
static void ttm_fence_lockup(struct ttm_fence_object *fence, uint32_t mask)
{
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
printk(KERN_ERR "GPU lockup dectected on engine %u "
"fence type 0x%08x\n",
(unsigned int)fence->fence_class, (unsigned int)mask);
/*
* Give engines some time to idle?
*/
write_lock(&fc->lock);
ttm_fence_handler(fence->fdev, fence->fence_class,
fence->sequence, mask, -EBUSY);
write_unlock(&fc->lock);
}
/*
* Convenience function to be called by fence::wait methods that
* need polling.
*/
int ttm_fence_wait_polling(struct ttm_fence_object *fence, bool lazy,
bool interruptible, uint32_t mask)
{
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
const struct ttm_fence_driver *driver = ttm_fence_driver(fence);
uint32_t count = 0;
int ret;
unsigned long end_jiffies = fence->timeout_jiffies;
DECLARE_WAITQUEUE(entry, current);
add_wait_queue(&fc->fence_queue, &entry);
ret = 0;
for (;;) {
__set_current_state((interruptible) ?
TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
if (ttm_fence_object_signaled(fence, mask))
break;
if (time_after_eq(jiffies, end_jiffies)) {
if (driver->lockup)
driver->lockup(fence, mask);
else
ttm_fence_lockup(fence, mask);
continue;
}
if (lazy)
schedule_timeout(1);
else if ((++count & 0x0F) == 0) {
__set_current_state(TASK_RUNNING);
schedule();
__set_current_state((interruptible) ?
TASK_INTERRUPTIBLE :
TASK_UNINTERRUPTIBLE);
}
if (interruptible && signal_pending(current)) {
ret = -ERESTART;
break;
}
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&fc->fence_queue, &entry);
return ret;
}
/*
* Typically called by the IRQ handler.
*/
void ttm_fence_handler(struct ttm_fence_device *fdev, uint32_t fence_class,
uint32_t sequence, uint32_t type, uint32_t error)
{
int wake = 0;
uint32_t diff;
uint32_t relevant_type;
uint32_t new_type;
struct ttm_fence_class_manager *fc = &fdev->fence_class[fence_class];
const struct ttm_fence_driver *driver = ttm_fence_driver_from_dev(fdev);
struct list_head *head;
struct ttm_fence_object *fence, *next;
bool found = false;
if (list_empty(&fc->ring))
return;
list_for_each_entry(fence, &fc->ring, ring) {
diff = (sequence - fence->sequence) & fc->sequence_mask;
if (diff > fc->wrap_diff) {
found = true;
break;
}
}
fc->waiting_types &= ~type;
head = (found) ? &fence->ring : &fc->ring;
list_for_each_entry_safe_reverse(fence, next, head, ring) {
if (&fence->ring == &fc->ring)
break;
DRM_DEBUG("Fence 0x%08lx, sequence 0x%08x, type 0x%08x\n",
(unsigned long)fence, fence->sequence,
fence->fence_type);
if (error) {
fence->info.error = error;
fence->info.signaled_types = fence->fence_type;
list_del_init(&fence->ring);
wake = 1;
break;
}
relevant_type = type & fence->fence_type;
new_type = (fence->info.signaled_types | relevant_type) ^
fence->info.signaled_types;
if (new_type) {
fence->info.signaled_types |= new_type;
DRM_DEBUG("Fence 0x%08lx signaled 0x%08x\n",
(unsigned long)fence,
fence->info.signaled_types);
if (unlikely(driver->signaled))
driver->signaled(fence);
if (driver->needed_flush)
fc->pending_flush |=
driver->needed_flush(fence);
if (new_type & fence->waiting_types)
wake = 1;
}
fc->waiting_types |=
fence->waiting_types & ~fence->info.signaled_types;
if (!(fence->fence_type & ~fence->info.signaled_types)) {
DRM_DEBUG("Fence completely signaled 0x%08lx\n",
(unsigned long)fence);
list_del_init(&fence->ring);
}
}
/*
* Reinstate lost waiting types.
*/
if ((fc->waiting_types & type) != type) {
head = head->prev;
list_for_each_entry(fence, head, ring) {
if (&fence->ring == &fc->ring)
break;
diff =
(fc->highest_waiting_sequence -
fence->sequence) & fc->sequence_mask;
if (diff > fc->wrap_diff)
break;
fc->waiting_types |=
fence->waiting_types & ~fence->info.signaled_types;
}
}
if (wake)
wake_up_all(&fc->fence_queue);
}
static void ttm_fence_unring(struct ttm_fence_object *fence)
{
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
unsigned long irq_flags;
write_lock_irqsave(&fc->lock, irq_flags);
list_del_init(&fence->ring);
write_unlock_irqrestore(&fc->lock, irq_flags);
}
bool ttm_fence_object_signaled(struct ttm_fence_object *fence, uint32_t mask)
{
unsigned long flags;
bool signaled;
const struct ttm_fence_driver *driver = ttm_fence_driver(fence);
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
mask &= fence->fence_type;
read_lock_irqsave(&fc->lock, flags);
signaled = (mask & fence->info.signaled_types) == mask;
read_unlock_irqrestore(&fc->lock, flags);
if (!signaled && driver->poll) {
write_lock_irqsave(&fc->lock, flags);
driver->poll(fence->fdev, fence->fence_class, mask);
signaled = (mask & fence->info.signaled_types) == mask;
write_unlock_irqrestore(&fc->lock, flags);
}
return signaled;
}
int ttm_fence_object_flush(struct ttm_fence_object *fence, uint32_t type)
{
const struct ttm_fence_driver *driver = ttm_fence_driver(fence);
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
unsigned long irq_flags;
uint32_t saved_pending_flush;
uint32_t diff;
bool call_flush;
if (type & ~fence->fence_type) {
DRM_ERROR("Flush trying to extend fence type, "
"0x%x, 0x%x\n", type, fence->fence_type);
return -EINVAL;
}
write_lock_irqsave(&fc->lock, irq_flags);
fence->waiting_types |= type;
fc->waiting_types |= fence->waiting_types;
diff = (fence->sequence - fc->highest_waiting_sequence) &
fc->sequence_mask;
if (diff < fc->wrap_diff)
fc->highest_waiting_sequence = fence->sequence;
/*
* fence->waiting_types has changed. Determine whether
* we need to initiate some kind of flush as a result of this.
*/
saved_pending_flush = fc->pending_flush;
if (driver->needed_flush)
fc->pending_flush |= driver->needed_flush(fence);
if (driver->poll)
driver->poll(fence->fdev, fence->fence_class,
fence->waiting_types);
call_flush = (fc->pending_flush != 0);
write_unlock_irqrestore(&fc->lock, irq_flags);
if (call_flush && driver->flush)
driver->flush(fence->fdev, fence->fence_class);
return 0;
}
/*
* Make sure old fence objects are signaled before their fence sequences are
* wrapped around and reused.
*/
void ttm_fence_flush_old(struct ttm_fence_device *fdev,
uint32_t fence_class, uint32_t sequence)
{
struct ttm_fence_class_manager *fc = &fdev->fence_class[fence_class];
struct ttm_fence_object *fence;
unsigned long irq_flags;
const struct ttm_fence_driver *driver = fdev->driver;
bool call_flush;
uint32_t diff;
write_lock_irqsave(&fc->lock, irq_flags);
list_for_each_entry_reverse(fence, &fc->ring, ring) {
diff = (sequence - fence->sequence) & fc->sequence_mask;
if (diff <= fc->flush_diff)
break;
fence->waiting_types = fence->fence_type;
fc->waiting_types |= fence->fence_type;
if (driver->needed_flush)
fc->pending_flush |= driver->needed_flush(fence);
}
if (driver->poll)
driver->poll(fdev, fence_class, fc->waiting_types);
call_flush = (fc->pending_flush != 0);
write_unlock_irqrestore(&fc->lock, irq_flags);
if (call_flush && driver->flush)
driver->flush(fdev, fence->fence_class);
/*
* FIXME: Shold we implement a wait here for really old fences?
*/
}
int ttm_fence_object_wait(struct ttm_fence_object *fence,
bool lazy, bool interruptible, uint32_t mask)
{
const struct ttm_fence_driver *driver = ttm_fence_driver(fence);
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
int ret = 0;
unsigned long timeout;
unsigned long cur_jiffies;
unsigned long to_jiffies;
if (mask & ~fence->fence_type) {
DRM_ERROR("Wait trying to extend fence type"
" 0x%08x 0x%08x\n", mask, fence->fence_type);
BUG();
return -EINVAL;
}
if (driver->wait)
return driver->wait(fence, lazy, interruptible, mask);
ttm_fence_object_flush(fence, mask);
retry:
if (!driver->has_irq ||
driver->has_irq(fence->fdev, fence->fence_class, mask)) {
cur_jiffies = jiffies;
to_jiffies = fence->timeout_jiffies;
timeout = (time_after(to_jiffies, cur_jiffies)) ?
to_jiffies - cur_jiffies : 1;
if (interruptible)
ret = wait_event_interruptible_timeout
(fc->fence_queue,
ttm_fence_object_signaled(fence, mask), timeout);
else
ret = wait_event_timeout
(fc->fence_queue,
ttm_fence_object_signaled(fence, mask), timeout);
if (unlikely(ret == -ERESTARTSYS))
return -ERESTART;
if (unlikely(ret == 0)) {
if (driver->lockup)
driver->lockup(fence, mask);
else
ttm_fence_lockup(fence, mask);
goto retry;
}
return 0;
}
return ttm_fence_wait_polling(fence, lazy, interruptible, mask);
}
int ttm_fence_object_emit(struct ttm_fence_object *fence, uint32_t fence_flags,
uint32_t fence_class, uint32_t type)
{
const struct ttm_fence_driver *driver = ttm_fence_driver(fence);
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
unsigned long flags;
uint32_t sequence;
unsigned long timeout;
int ret;
ttm_fence_unring(fence);
ret = driver->emit(fence->fdev,
fence_class, fence_flags, &sequence, &timeout);
if (ret)
return ret;
write_lock_irqsave(&fc->lock, flags);
fence->fence_class = fence_class;
fence->fence_type = type;
fence->waiting_types = 0;
fence->info.signaled_types = 0;
fence->info.error = 0;
fence->sequence = sequence;
fence->timeout_jiffies = timeout;
if (list_empty(&fc->ring))
fc->highest_waiting_sequence = sequence - 1;
list_add_tail(&fence->ring, &fc->ring);
fc->latest_queued_sequence = sequence;
write_unlock_irqrestore(&fc->lock, flags);
return 0;
}
int ttm_fence_object_init(struct ttm_fence_device *fdev,
uint32_t fence_class,
uint32_t type,
uint32_t create_flags,
void (*destroy) (struct ttm_fence_object *),
struct ttm_fence_object *fence)
{
int ret = 0;
kref_init(&fence->kref);
fence->fence_class = fence_class;
fence->fence_type = type;
fence->info.signaled_types = 0;
fence->waiting_types = 0;
fence->sequence = 0;
fence->info.error = 0;
fence->fdev = fdev;
fence->destroy = destroy;
INIT_LIST_HEAD(&fence->ring);
atomic_inc(&fdev->count);
if (create_flags & TTM_FENCE_FLAG_EMIT) {
ret = ttm_fence_object_emit(fence, create_flags,
fence->fence_class, type);
}
return ret;
}
int ttm_fence_object_create(struct ttm_fence_device *fdev,
uint32_t fence_class,
uint32_t type,
uint32_t create_flags,
struct ttm_fence_object **c_fence)
{
struct ttm_fence_object *fence;
int ret;
ret = ttm_mem_global_alloc(fdev->mem_glob,
sizeof(*fence),
false,
false);
if (unlikely(ret != 0)) {
printk(KERN_ERR "Out of memory creating fence object\n");
return ret;
}
fence = kmalloc(sizeof(*fence), GFP_KERNEL);
if (!fence) {
printk(KERN_ERR "Out of memory creating fence object\n");
ttm_mem_global_free(fdev->mem_glob, sizeof(*fence));
return -ENOMEM;
}
ret = ttm_fence_object_init(fdev, fence_class, type,
create_flags, NULL, fence);
if (ret) {
ttm_fence_object_unref(&fence);
return ret;
}
*c_fence = fence;
return 0;
}
static void ttm_fence_object_destroy(struct kref *kref)
{
struct ttm_fence_object *fence =
container_of(kref, struct ttm_fence_object, kref);
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
unsigned long irq_flags;
write_lock_irqsave(&fc->lock, irq_flags);
list_del_init(&fence->ring);
write_unlock_irqrestore(&fc->lock, irq_flags);
atomic_dec(&fence->fdev->count);
if (fence->destroy)
fence->destroy(fence);
else {
ttm_mem_global_free(fence->fdev->mem_glob,
sizeof(*fence));
kfree(fence);
}
}
void ttm_fence_device_release(struct ttm_fence_device *fdev)
{
kfree(fdev->fence_class);
}
int
ttm_fence_device_init(int num_classes,
struct ttm_mem_global *mem_glob,
struct ttm_fence_device *fdev,
const struct ttm_fence_class_init *init,
bool replicate_init,
const struct ttm_fence_driver *driver)
{
struct ttm_fence_class_manager *fc;
const struct ttm_fence_class_init *fci;
int i;
fdev->mem_glob = mem_glob;
fdev->fence_class = kzalloc(num_classes *
sizeof(*fdev->fence_class), GFP_KERNEL);
if (unlikely(!fdev->fence_class))
return -ENOMEM;
fdev->num_classes = num_classes;
atomic_set(&fdev->count, 0);
fdev->driver = driver;
for (i = 0; i < fdev->num_classes; ++i) {
fc = &fdev->fence_class[i];
fci = &init[(replicate_init) ? 0 : i];
fc->wrap_diff = fci->wrap_diff;
fc->flush_diff = fci->flush_diff;
fc->sequence_mask = fci->sequence_mask;
rwlock_init(&fc->lock);
INIT_LIST_HEAD(&fc->ring);
init_waitqueue_head(&fc->fence_queue);
}
return 0;
}
struct ttm_fence_info ttm_fence_get_info(struct ttm_fence_object *fence)
{
struct ttm_fence_class_manager *fc = ttm_fence_fc(fence);
struct ttm_fence_info tmp;
unsigned long irq_flags;
read_lock_irqsave(&fc->lock, irq_flags);
tmp = fence->info;
read_unlock_irqrestore(&fc->lock, irq_flags);
return tmp;
}
void ttm_fence_object_unref(struct ttm_fence_object **p_fence)
{
struct ttm_fence_object *fence = *p_fence;
*p_fence = NULL;
(void)kref_put(&fence->kref, &ttm_fence_object_destroy);
}
/*
* Placement / BO sync object glue.
*/
bool ttm_fence_sync_obj_signaled(void *sync_obj, void *sync_arg)
{
struct ttm_fence_object *fence = (struct ttm_fence_object *)sync_obj;
uint32_t fence_types = (uint32_t) (unsigned long)sync_arg;
return ttm_fence_object_signaled(fence, fence_types);
}
int ttm_fence_sync_obj_wait(void *sync_obj, void *sync_arg,
bool lazy, bool interruptible)
{
struct ttm_fence_object *fence = (struct ttm_fence_object *)sync_obj;
uint32_t fence_types = (uint32_t) (unsigned long)sync_arg;
return ttm_fence_object_wait(fence, lazy, interruptible, fence_types);
}
int ttm_fence_sync_obj_flush(void *sync_obj, void *sync_arg)
{
struct ttm_fence_object *fence = (struct ttm_fence_object *)sync_obj;
uint32_t fence_types = (uint32_t) (unsigned long)sync_arg;
return ttm_fence_object_flush(fence, fence_types);
}
void ttm_fence_sync_obj_unref(void **sync_obj)
{
ttm_fence_object_unref((struct ttm_fence_object **)sync_obj);
}
void *ttm_fence_sync_obj_ref(void *sync_obj)
{
return (void *)
ttm_fence_object_ref((struct ttm_fence_object *)sync_obj);
}