2013-08-15 12:43:24

by Maarten Lankhorst

[permalink] [raw]
Subject: [PATCH] fence: dma-buf cross-device synchronization (v14)

A fence can be attached to a buffer which is being filled or consumed
by hw, to allow userspace to pass the buffer without waiting to another
device. For example, userspace can call page_flip ioctl to display the
next frame of graphics after kicking the GPU but while the GPU is still
rendering. The display device sharing the buffer with the GPU would
attach a callback to get notified when the GPU's rendering-complete IRQ
fires, to update the scan-out address of the display, without having to
wake up userspace.

A driver must allocate a fence context for each execution ring that can
run in parallel. The function for this takes an argument with how many
contexts to allocate:
+ fence_context_alloc()

A fence is transient, one-shot deal. It is allocated and attached
to one or more dma-buf's. When the one that attached it is done, with
the pending operation, it can signal the fence:
+ fence_signal()

To have a rough approximation whether a fence is fired, call:
+ fence_is_signaled()

The dma-buf-mgr handles tracking, and waiting on, the fences associated
with a dma-buf.

The one pending on the fence can add an async callback:
+ fence_add_callback()

The callback can optionally be cancelled with:
+ fence_remove_callback()

To wait synchronously, optionally with a timeout:
+ fence_wait()
+ fence_wait_timeout()

A default software-only implementation is provided, which can be used
by drivers attaching a fence to a buffer when they have no other means
for hw sync. But a memory backed fence is also envisioned, because it
is common that GPU's can write to, or poll on some memory location for
synchronization. For example:

fence = custom_get_fence(...);
if ((seqno_fence = to_seqno_fence(fence)) != NULL) {
dma_buf *fence_buf = fence->sync_buf;
get_dma_buf(fence_buf);

... tell the hw the memory location to wait ...
custom_wait_on(fence_buf, fence->seqno_ofs, fence->seqno);
} else {
/* fall-back to sw sync * /
fence_add_callback(fence, my_cb);
}

On SoC platforms, if some other hw mechanism is provided for synchronizing
between IP blocks, it could be supported as an alternate implementation
with it's own fence ops in a similar way.

enable_signaling callback is used to provide sw signaling in case a cpu
waiter is requested or no compatible hardware signaling could be used.

The intention is to provide a userspace interface (presumably via eventfd)
later, to be used in conjunction with dma-buf's mmap support for sw access
to buffers (or for userspace apps that would prefer to do their own
synchronization).

v1: Original
v2: After discussion w/ danvet and mlankhorst on #dri-devel, we decided
that dma-fence didn't need to care about the sw->hw signaling path
(it can be handled same as sw->sw case), and therefore the fence->ops
can be simplified and more handled in the core. So remove the signal,
add_callback, cancel_callback, and wait ops, and replace with a simple
enable_signaling() op which can be used to inform a fence supporting
hw->hw signaling that one or more devices which do not support hw
signaling are waiting (and therefore it should enable an irq or do
whatever is necessary in order that the CPU is notified when the
fence is passed).
v3: Fix locking fail in attach_fence() and get_fence()
v4: Remove tie-in w/ dma-buf.. after discussion w/ danvet and mlankorst
we decided that we need to be able to attach one fence to N dma-buf's,
so using the list_head in dma-fence struct would be problematic.
v5: [ Maarten Lankhorst ] Updated for dma-bikeshed-fence and dma-buf-manager.
v6: [ Maarten Lankhorst ] I removed dma_fence_cancel_callback and some comments
about checking if fence fired or not. This is broken by design.
waitqueue_active during destruction is now fatal, since the signaller
should be holding a reference in enable_signalling until it signalled
the fence. Pass the original dma_fence_cb along, and call __remove_wait
in the dma_fence_callback handler, so that no cleanup needs to be
performed.
v7: [ Maarten Lankhorst ] Set cb->func and only enable sw signaling if
fence wasn't signaled yet, for example for hardware fences that may
choose to signal blindly.
v8: [ Maarten Lankhorst ] Tons of tiny fixes, moved __dma_fence_init to
header and fixed include mess. dma-fence.h now includes dma-buf.h
All members are now initialized, so kmalloc can be used for
allocating a dma-fence. More documentation added.
v9: Change compiler bitfields to flags, change return type of
enable_signaling to bool. Rework dma_fence_wait. Added
dma_fence_is_signaled and dma_fence_wait_timeout.
s/dma// and change exports to non GPL. Added fence_is_signaled and
fence_enable_sw_signaling calls, add ability to override default
wait operation.
v10: remove event_queue, use a custom list, export try_to_wake_up from
scheduler. Remove fence lock and use a global spinlock instead,
this should hopefully remove all the locking headaches I was having
on trying to implement this. enable_signaling is called with this
lock held.
v11:
Use atomic ops for flags, lifting the need for some spin_lock_irqsaves.
However I kept the guarantee that after fence_signal returns, it is
guaranteed that enable_signaling has either been called to completion,
or will not be called any more.

Add contexts and seqno to base fence implementation. This allows you
to wait for less fences, by testing for seqno + signaled, and then only
wait on the later fence.

Add FENCE_TRACE, FENCE_WARN, and FENCE_ERR. This makes debugging easier.
An CONFIG_DEBUG_FENCE will be added to turn off the FENCE_TRACE
spam, and another runtime option can turn it off at runtime.
v12:
Add CONFIG_FENCE_TRACE. Add missing documentation for the fence->context
and fence->seqno members.
v13:
Fixup CONFIG_FENCE_TRACE kconfig description.
Move fence_context_alloc to fence.
Simplify fence_later.
Kill priv member to fence_cb.
v14:
Remove priv argument from fence_add_callback, oops!
Signed-off-by: Maarten Lankhorst <[email protected]>
---
Documentation/DocBook/device-drivers.tmpl | 2
drivers/base/Kconfig | 10 +
drivers/base/Makefile | 2
drivers/base/fence.c | 311 ++++++++++++++++++++++++++
include/linux/fence.h | 344 +++++++++++++++++++++++++++++
5 files changed, 668 insertions(+), 1 deletion(-)
create mode 100644 drivers/base/fence.c
create mode 100644 include/linux/fence.h

diff --git a/Documentation/DocBook/device-drivers.tmpl b/Documentation/DocBook/device-drivers.tmpl
index fe397f9..95d0db9 100644
--- a/Documentation/DocBook/device-drivers.tmpl
+++ b/Documentation/DocBook/device-drivers.tmpl
@@ -126,6 +126,8 @@ X!Edrivers/base/interface.c
</sect1>
<sect1><title>Device Drivers DMA Management</title>
!Edrivers/base/dma-buf.c
+!Edrivers/base/fence.c
+!Iinclude/linux/fence.h
!Edrivers/base/reservation.c
!Iinclude/linux/reservation.h
!Edrivers/base/dma-coherent.c
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 5daa259..2bf0add 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -200,6 +200,16 @@ config DMA_SHARED_BUFFER
APIs extension; the file's descriptor can then be passed on to other
driver.

+config FENCE_TRACE
+ bool "Enable verbose FENCE_TRACE messages"
+ default n
+ depends on DMA_SHARED_BUFFER
+ help
+ Enable the FENCE_TRACE printks. This will add extra
+ spam to the console log, but will make it easier to diagnose
+ lockup related problems for dma-buffers shared across multiple
+ devices.
+
config CMA
bool "Contiguous Memory Allocator"
depends on HAVE_DMA_CONTIGUOUS && HAVE_MEMBLOCK
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 48029aa..8a55cb9 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -10,7 +10,7 @@ obj-$(CONFIG_CMA) += dma-contiguous.o
obj-y += power/
obj-$(CONFIG_HAS_DMA) += dma-mapping.o
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
-obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf.o reservation.o
+obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf.o fence.o reservation.o
obj-$(CONFIG_ISA) += isa.o
obj-$(CONFIG_FW_LOADER) += firmware_class.o
obj-$(CONFIG_NUMA) += node.o
diff --git a/drivers/base/fence.c b/drivers/base/fence.c
new file mode 100644
index 0000000..a95df7d
--- /dev/null
+++ b/drivers/base/fence.c
@@ -0,0 +1,311 @@
+/*
+ * Fence mechanism for dma-buf and to allow for asynchronous dma access
+ *
+ * Copyright (C) 2012 Canonical Ltd
+ * Copyright (C) 2012 Texas Instruments
+ *
+ * Authors:
+ * Rob Clark <[email protected]>
+ * Maarten Lankhorst <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/fence.h>
+
+/**
+ * fence context counter: each execution context should have its own
+ * fence context, this allows checking if fences belong to the same
+ * context or not. One device can have multiple separate contexts,
+ * and they're used if some engine can run independently of another.
+ */
+static atomic_t fence_context_counter = ATOMIC_INIT(0);
+
+/**
+ * fence_context_alloc - allocate an array of fence contexts
+ * @num: [in] amount of contexts to allocate
+ *
+ * This function will return the first index of the number of fences allocated.
+ * The fence context is used for setting fence->context to a unique number.
+ */
+unsigned fence_context_alloc(unsigned num)
+{
+ BUG_ON(!num);
+ return atomic_add_return(num, &fence_context_counter) - num;
+}
+EXPORT_SYMBOL(fence_context_alloc);
+
+int __fence_signal(struct fence *fence)
+{
+ struct fence_cb *cur, *tmp;
+ int ret = 0;
+
+ if (WARN_ON(!fence))
+ return -EINVAL;
+
+ if (test_and_set_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags)) {
+ ret = -EINVAL;
+
+ /*
+ * we might have raced with the unlocked fence_signal,
+ * still run through all callbacks
+ */
+ }
+
+ list_for_each_entry_safe(cur, tmp, &fence->cb_list, node) {
+ list_del_init(&cur->node);
+ cur->func(fence, cur);
+ }
+ return ret;
+}
+EXPORT_SYMBOL(__fence_signal);
+
+/**
+ * fence_signal - signal completion of a fence
+ * @fence: the fence to signal
+ *
+ * Signal completion for software callbacks on a fence, this will unblock
+ * fence_wait() calls and run all the callbacks added with
+ * fence_add_callback(). Can be called multiple times, but since a fence
+ * can only go from unsignaled to signaled state, it will only be effective
+ * the first time.
+ */
+int fence_signal(struct fence *fence)
+{
+ unsigned long flags;
+
+ if (!fence || test_and_set_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return -EINVAL;
+
+ if (test_bit(FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags)) {
+ struct fence_cb *cur, *tmp;
+
+ spin_lock_irqsave(fence->lock, flags);
+ list_for_each_entry_safe(cur, tmp, &fence->cb_list, node) {
+ list_del_init(&cur->node);
+ cur->func(fence, cur);
+ }
+ spin_unlock_irqrestore(fence->lock, flags);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(fence_signal);
+
+void release_fence(struct kref *kref)
+{
+ struct fence *fence =
+ container_of(kref, struct fence, refcount);
+
+ BUG_ON(!list_empty(&fence->cb_list));
+
+ if (fence->ops->release)
+ fence->ops->release(fence);
+ else
+ kfree(fence);
+}
+EXPORT_SYMBOL(release_fence);
+
+/**
+ * fence_enable_sw_signaling - enable signaling on fence
+ * @fence: [in] the fence to enable
+ *
+ * this will request for sw signaling to be enabled, to make the fence
+ * complete as soon as possible
+ */
+void fence_enable_sw_signaling(struct fence *fence)
+{
+ unsigned long flags;
+
+ if (!test_and_set_bit(FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags) &&
+ !test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags)) {
+ spin_lock_irqsave(fence->lock, flags);
+
+ if (!fence->ops->enable_signaling(fence))
+ __fence_signal(fence);
+
+ spin_unlock_irqrestore(fence->lock, flags);
+ }
+}
+EXPORT_SYMBOL(fence_enable_sw_signaling);
+
+/**
+ * fence_add_callback - add a callback to be called when the fence
+ * is signaled
+ * @fence: [in] the fence to wait on
+ * @cb: [in] the callback to register
+ * @func: [in] the function to call
+ *
+ * cb will be initialized by fence_add_callback, no initialization
+ * by the caller is required. Any number of callbacks can be registered
+ * to a fence, but a callback can only be registered to one fence at a time.
+ *
+ * Note that the callback can be called from an atomic context. If
+ * fence is already signaled, this function will return -ENOENT (and
+ * *not* call the callback)
+ *
+ * Add a software callback to the fence. Same restrictions apply to
+ * refcount as it does to fence_wait, however the caller doesn't need to
+ * keep a refcount to fence afterwards: when software access is enabled,
+ * the creator of the fence is required to keep the fence alive until
+ * after it signals with fence_signal. The callback itself can be called
+ * from irq context.
+ *
+ */
+int fence_add_callback(struct fence *fence, struct fence_cb *cb,
+ fence_func_t func)
+{
+ unsigned long flags;
+ int ret = 0;
+ bool was_set;
+
+ if (WARN_ON(!fence || !func))
+ return -EINVAL;
+
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return -ENOENT;
+
+ spin_lock_irqsave(fence->lock, flags);
+
+ was_set = test_and_set_bit(FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags);
+
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ ret = -ENOENT;
+ else if (!was_set && !fence->ops->enable_signaling(fence)) {
+ __fence_signal(fence);
+ ret = -ENOENT;
+ }
+
+ if (!ret) {
+ cb->func = func;
+ list_add_tail(&cb->node, &fence->cb_list);
+ }
+ spin_unlock_irqrestore(fence->lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(fence_add_callback);
+
+/**
+ * fence_remove_callback - remove a callback from the signaling list
+ * @fence: [in] the fence to wait on
+ * @cb: [in] the callback to remove
+ *
+ * Remove a previously queued callback from the fence. This function returns
+ * true is the callback is succesfully removed, or false if the fence has
+ * already been signaled.
+ *
+ * *WARNING*:
+ * Cancelling a callback should only be done if you really know what you're
+ * doing, since deadlocks and race conditions could occur all too easily. For
+ * this reason, it should only ever be done on hardware lockup recovery,
+ * with a reference held to the fence.
+ */
+bool
+fence_remove_callback(struct fence *fence, struct fence_cb *cb)
+{
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(fence->lock, flags);
+
+ ret = !list_empty(&cb->node);
+ if (ret)
+ list_del_init(&cb->node);
+
+ spin_unlock_irqrestore(fence->lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(fence_remove_callback);
+
+struct default_wait_cb {
+ struct fence_cb base;
+ struct task_struct *task;
+};
+
+static void
+fence_default_wait_cb(struct fence *fence, struct fence_cb *cb)
+{
+ struct default_wait_cb *wait =
+ container_of(cb, struct default_wait_cb, base);
+
+ try_to_wake_up(wait->task, TASK_NORMAL, 0);
+}
+
+/**
+ * fence_default_wait - default sleep until the fence gets signaled
+ * or until timeout elapses
+ * @fence: [in] the fence to wait on
+ * @intr: [in] if true, do an interruptible wait
+ * @timeout: [in] timeout value in jiffies, or MAX_SCHEDULE_TIMEOUT
+ *
+ * Returns -ERESTARTSYS if interrupted, 0 if the wait timed out, or the
+ * remaining timeout in jiffies on success.
+ */
+long
+fence_default_wait(struct fence *fence, bool intr, signed long timeout)
+{
+ struct default_wait_cb cb;
+ unsigned long flags;
+ long ret = timeout;
+ bool was_set;
+
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return timeout;
+
+ spin_lock_irqsave(fence->lock, flags);
+
+ if (intr && signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+
+ was_set = test_and_set_bit(FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags);
+
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ goto out;
+
+ if (!was_set && !fence->ops->enable_signaling(fence)) {
+ __fence_signal(fence);
+ goto out;
+ }
+
+ cb.base.func = fence_default_wait_cb;
+ cb.task = current;
+ list_add(&cb.base.node, &fence->cb_list);
+
+ while (!test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags) && ret > 0) {
+ if (intr)
+ __set_current_state(TASK_INTERRUPTIBLE);
+ else
+ __set_current_state(TASK_UNINTERRUPTIBLE);
+ spin_unlock_irqrestore(fence->lock, flags);
+
+ ret = schedule_timeout(ret);
+
+ spin_lock_irqsave(fence->lock, flags);
+ if (ret > 0 && intr && signal_pending(current))
+ ret = -ERESTARTSYS;
+ }
+
+ if (!list_empty(&cb.base.node))
+ list_del(&cb.base.node);
+ __set_current_state(TASK_RUNNING);
+
+out:
+ spin_unlock_irqrestore(fence->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(fence_default_wait);
diff --git a/include/linux/fence.h b/include/linux/fence.h
new file mode 100644
index 0000000..3ce2155
--- /dev/null
+++ b/include/linux/fence.h
@@ -0,0 +1,344 @@
+/*
+ * Fence mechanism for dma-buf to allow for asynchronous dma access
+ *
+ * Copyright (C) 2012 Canonical Ltd
+ * Copyright (C) 2012 Texas Instruments
+ *
+ * Authors:
+ * Rob Clark <[email protected]>
+ * Maarten Lankhorst <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINUX_FENCE_H
+#define __LINUX_FENCE_H
+
+#include <linux/err.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/kref.h>
+#include <linux/sched.h>
+#include <linux/printk.h>
+
+struct fence;
+struct fence_ops;
+struct fence_cb;
+
+/**
+ * struct fence - software synchronization primitive
+ * @refcount: refcount for this fence
+ * @ops: fence_ops associated with this fence
+ * @cb_list: list of all callbacks to call
+ * @lock: spin_lock_irqsave used for locking
+ * @context: execution context this fence belongs to, returned by
+ * fence_context_alloc()
+ * @seqno: the sequence number of this fence inside the execution context,
+ * can be compared to decide which fence would be signaled later.
+ * @flags: A mask of FENCE_FLAG_* defined below
+ *
+ * the flags member must be manipulated and read using the appropriate
+ * atomic ops (bit_*), so taking the spinlock will not be needed most
+ * of the time.
+ *
+ * FENCE_FLAG_SIGNALED_BIT - fence is already signaled
+ * FENCE_FLAG_ENABLE_SIGNAL_BIT - enable_signaling might have been called*
+ * FENCE_FLAG_USER_BITS - start of the unused bits, can be used by the
+ * implementer of the fence for its own purposes. Can be used in different
+ * ways by different fence implementers, so do not rely on this.
+ *
+ * *) Since atomic bitops are used, this is not guaranteed to be the case.
+ * Particularly, if the bit was set, but fence_signal was called right
+ * before this bit was set, it would have been able to set the
+ * FENCE_FLAG_SIGNALED_BIT, before enable_signaling was called.
+ * Adding a check for FENCE_FLAG_SIGNALED_BIT after setting
+ * FENCE_FLAG_ENABLE_SIGNAL_BIT closes this race, and makes sure that
+ * after fence_signal was called, any enable_signaling call will have either
+ * been completed, or never called at all.
+ */
+struct fence {
+ struct kref refcount;
+ const struct fence_ops *ops;
+ struct list_head cb_list;
+ spinlock_t *lock;
+ unsigned context, seqno;
+ unsigned long flags;
+};
+
+enum fence_flag_bits {
+ FENCE_FLAG_SIGNALED_BIT,
+ FENCE_FLAG_ENABLE_SIGNAL_BIT,
+ FENCE_FLAG_USER_BITS, /* must always be last member */
+};
+
+typedef void (*fence_func_t)(struct fence *fence, struct fence_cb *cb);
+
+/**
+ * struct fence_cb - callback for fence_add_callback
+ * @node: used by fence_add_callback to append this struct to fence::cb_list
+ * @func: fence_func_t to call
+ * @priv: value of priv to pass to function
+ *
+ * This struct will be initialized by fence_add_callback, additional
+ * data can be passed along by embedding fence_cb in another struct.
+ */
+struct fence_cb {
+ struct list_head node;
+ fence_func_t func;
+};
+
+/**
+ * struct fence_ops - operations implemented for fence
+ * @enable_signaling: enable software signaling of fence
+ * @signaled: [optional] peek whether the fence is signaled, can be null
+ * @wait: custom wait implementation
+ * @release: [optional] called on destruction of fence, can be null
+ *
+ * Notes on enable_signaling:
+ * For fence implementations that have the capability for hw->hw
+ * signaling, they can implement this op to enable the necessary
+ * irqs, or insert commands into cmdstream, etc. This is called
+ * in the first wait() or add_callback() path to let the fence
+ * implementation know that there is another driver waiting on
+ * the signal (ie. hw->sw case).
+ *
+ * This function can be called called from atomic context, but not
+ * from irq context, so normal spinlocks can be used.
+ *
+ * A return value of false indicates the fence already passed,
+ * or some failure occured that made it impossible to enable
+ * signaling. True indicates succesful enabling.
+ *
+ * Calling fence_signal before enable_signaling is called allows
+ * for a tiny race window in which enable_signaling is called during,
+ * before, or after fence_signal. To fight this, it is recommended
+ * that before enable_signaling returns true an extra reference is
+ * taken on the fence, to be released when the fence is signaled.
+ * This will mean fence_signal will still be called twice, but
+ * the second time will be a noop since it was already signaled.
+ *
+ * Notes on wait:
+ * Must not be NULL, set to fence_default_wait for default implementation.
+ * the fence_default_wait implementation should work for any fence, as long
+ * as enable_signaling works correctly.
+ *
+ * Must return -ERESTARTSYS if the wait is intr = true and the wait was
+ * interrupted, and remaining jiffies if fence has signaled, or 0 if wait
+ * timed out. Can also return other error values on custom implementations,
+ * which should be treated as if the fence is signaled. For example a hardware
+ * lockup could be reported like that.
+ *
+ * Notes on release:
+ * Can be NULL, this function allows additional commands to run on
+ * destruction of the fence. Can be called from irq context.
+ * If pointer is set to NULL, kfree will get called instead.
+ */
+
+struct fence_ops {
+ bool (*enable_signaling)(struct fence *fence);
+ bool (*signaled)(struct fence *fence);
+ long (*wait)(struct fence *fence, bool intr, signed long timeout);
+ void (*release)(struct fence *fence);
+};
+
+/**
+ * __fence_init - Initialize a custom fence.
+ * @fence: [in] the fence to initialize
+ * @ops: [in] the fence_ops for operations on this fence
+ * @lock: [in] the irqsafe spinlock to use for locking this fence
+ * @context: [in] the execution context this fence is run on
+ * @seqno: [in] a linear increasing sequence number for this context
+ *
+ * Initializes an allocated fence, the caller doesn't have to keep its
+ * refcount after committing with this fence, but it will need to hold a
+ * refcount again if fence_ops.enable_signaling gets called. This can
+ * be used for other implementing other types of fence.
+ *
+ * context and seqno are used for easy comparison between fences, allowing
+ * to check which fence is later by simply using fence_later.
+ */
+static inline void
+__fence_init(struct fence *fence, const struct fence_ops *ops,
+ spinlock_t *lock, unsigned context, unsigned seqno)
+{
+ BUG_ON(!ops || !lock || !ops->enable_signaling || !ops->wait);
+
+ kref_init(&fence->refcount);
+ fence->ops = ops;
+ INIT_LIST_HEAD(&fence->cb_list);
+ fence->lock = lock;
+ fence->context = context;
+ fence->seqno = seqno;
+ fence->flags = 0UL;
+}
+
+/**
+ * fence_get - increases refcount of the fence
+ * @fence: [in] fence to increase refcount of
+ */
+static inline void fence_get(struct fence *fence)
+{
+ if (WARN_ON(!fence))
+ return;
+ kref_get(&fence->refcount);
+}
+
+extern void release_fence(struct kref *kref);
+
+/**
+ * fence_put - decreases refcount of the fence
+ * @fence: [in] fence to reduce refcount of
+ */
+static inline void fence_put(struct fence *fence)
+{
+ if (WARN_ON(!fence))
+ return;
+ kref_put(&fence->refcount, release_fence);
+}
+
+int fence_signal(struct fence *fence);
+int __fence_signal(struct fence *fence);
+long fence_default_wait(struct fence *fence, bool intr, signed long timeout);
+int fence_add_callback(struct fence *fence, struct fence_cb *cb,
+ fence_func_t func);
+bool fence_remove_callback(struct fence *fence, struct fence_cb *cb);
+void fence_enable_sw_signaling(struct fence *fence);
+
+/**
+ * fence_is_signaled - Return an indication if the fence is signaled yet.
+ * @fence: [in] the fence to check
+ *
+ * Returns true if the fence was already signaled, false if not. Since this
+ * function doesn't enable signaling, it is not guaranteed to ever return true
+ * If fence_add_callback, fence_wait or fence_enable_sw_signaling
+ * haven't been called before.
+ *
+ * It's recommended for seqno fences to call fence_signal when the
+ * operation is complete, it makes it possible to prevent issues from
+ * wraparound between time of issue and time of use by checking the return
+ * value of this function before calling hardware-specific wait instructions.
+ */
+static inline bool
+fence_is_signaled(struct fence *fence)
+{
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return true;
+
+ if (fence->ops->signaled && fence->ops->signaled(fence)) {
+ fence_signal(fence);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * fence_later - return the chronologically later fence
+ * @f1: [in] the first fence from the same context
+ * @f2: [in] the second fence from the same context
+ *
+ * Returns NULL if both fences are signaled, otherwise the fence that would be
+ * signaled last. Both fences must be from the same context, since a seqno is
+ * not re-used across contexts.
+ */
+static inline struct fence *fence_later(struct fence *f1, struct fence *f2)
+{
+ BUG_ON(f1->context != f2->context);
+
+ /*
+ * can't check just FENCE_FLAG_SIGNALED_BIT here, it may never have been
+ * set called if enable_signaling wasn't, and enabling that here is
+ * overkill.
+ */
+ if (f2->seqno - f1->seqno <= INT_MAX)
+ return fence_is_signaled(f2) ? NULL : f2;
+ else
+ return fence_is_signaled(f1) ? NULL : f1;
+}
+
+/**
+ * fence_wait_timeout - sleep until the fence gets signaled
+ * or until timeout elapses
+ * @fence: [in] the fence to wait on
+ * @intr: [in] if true, do an interruptible wait
+ * @timeout: [in] timeout value in jiffies, or MAX_SCHEDULE_TIMEOUT
+ *
+ * Returns -ERESTARTSYS if interrupted, 0 if the wait timed out, or the
+ * remaining timeout in jiffies on success. Other error values may be
+ * returned on custom implementations.
+ *
+ * Performs a synchronous wait on this fence. It is assumed the caller
+ * directly or indirectly (buf-mgr between reservation and committing)
+ * holds a reference to the fence, otherwise the fence might be
+ * freed before return, resulting in undefined behavior.
+ */
+static inline long
+fence_wait_timeout(struct fence *fence, bool intr, signed long timeout)
+{
+ if (WARN_ON(timeout < 0))
+ return -EINVAL;
+
+ return fence->ops->wait(fence, intr, timeout);
+}
+
+/**
+ * fence_wait - sleep until the fence gets signaled
+ * @fence: [in] the fence to wait on
+ * @intr: [in] if true, do an interruptible wait
+ *
+ * This function will return -ERESTARTSYS if interrupted by a signal,
+ * or 0 if the fence was signaled. Other error values may be
+ * returned on custom implementations.
+ *
+ * Performs a synchronous wait on this fence. It is assumed the caller
+ * directly or indirectly (buf-mgr between reservation and committing)
+ * holds a reference to the fence, otherwise the fence might be
+ * freed before return, resulting in undefined behavior.
+ */
+static inline long fence_wait(struct fence *fence, bool intr)
+{
+ long ret;
+
+ /* Since fence_wait_timeout cannot timeout with
+ * MAX_SCHEDULE_TIMEOUT, only valid return values are
+ * -ERESTARTSYS and MAX_SCHEDULE_TIMEOUT.
+ */
+ ret = fence_wait_timeout(fence, intr, MAX_SCHEDULE_TIMEOUT);
+
+ return ret < 0 ? ret : 0;
+}
+
+unsigned fence_context_alloc(unsigned num);
+
+#define FENCE_TRACE(f, fmt, args...) \
+ do { \
+ struct fence *__ff = (f); \
+ if (config_enabled(CONFIG_FENCE_TRACE)) \
+ pr_info("f %u#%u: " fmt, \
+ __ff->context, __ff->seqno, ##args); \
+ } while (0)
+
+#define FENCE_WARN(f, fmt, args...) \
+ do { \
+ struct fence *__ff = (f); \
+ pr_warn("f %u#%u: " fmt, __ff->context, __ff->seqno, ##args); \
+ } while (0)
+
+#define FENCE_ERR(f, fmt, args...) \
+ do { \
+ struct fence *__ff = (f); \
+ pr_err("f %u#%u: " fmt, __ff->context, __ff->seqno, ##args); \
+ } while (0)
+
+#endif /* __LINUX_FENCE_H */


2013-08-19 10:11:55

by Maarten Lankhorst

[permalink] [raw]
Subject: [RFC PATCH] drm/nouveau: rework to new fence interface

nouveau was a bit tricky, it has no support for interrupts on <nv84, so I added
an extra call to nouveau_fence_update in nouveau_fence_emit to increase
the chance slightly that deferred work gets triggered.

This patch depends on the vblank locking fix for the definitions of
nouveau_event_enable_locked and nouveau_event_disable_locked.

Signed-off-by: Maarten Lankhorst <[email protected]>
---

diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c
index be31499..78714e4 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fence.c
+++ b/drivers/gpu/drm/nouveau/nouveau_fence.c
@@ -35,88 +35,115 @@

#include <engine/fifo.h>

-struct fence_work {
- struct work_struct base;
- struct list_head head;
- void (*func)(void *);
- void *data;
-};
+static const struct fence_ops nouveau_fence_ops_uevent;
+static const struct fence_ops nouveau_fence_ops_legacy;

static void
nouveau_fence_signal(struct nouveau_fence *fence)
{
- struct fence_work *work, *temp;
+ __fence_signal(&fence->base);
+ list_del(&fence->head);

- list_for_each_entry_safe(work, temp, &fence->work, head) {
- schedule_work(&work->base);
- list_del(&work->head);
+ if (fence->base.ops == &nouveau_fence_ops_uevent &&
+ fence->event.head.next) {
+ struct nouveau_event *event;
+
+ list_del(&fence->event.head);
+ fence->event.head.next = NULL;
+
+ event = container_of(fence->base.lock, typeof(*event), lock);
+ if (!--event->index[0].refs)
+ event->disable(event, 0);
}

- fence->channel = NULL;
- list_del(&fence->head);
+ fence_put(&fence->base);
}

void
nouveau_fence_context_del(struct nouveau_fence_chan *fctx)
{
struct nouveau_fence *fence, *fnext;
- spin_lock(&fctx->lock);
- list_for_each_entry_safe(fence, fnext, &fctx->pending, head) {
+
+ spin_lock_irq(fctx->lock);
+ list_for_each_entry_safe(fence, fnext, &fctx->pending, head)
nouveau_fence_signal(fence);
- }
- spin_unlock(&fctx->lock);
+ spin_unlock_irq(fctx->lock);
}

void
-nouveau_fence_context_new(struct nouveau_fence_chan *fctx)
+nouveau_fence_context_new(struct nouveau_channel *chan, struct nouveau_fence_chan *fctx)
{
+ struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device);
+
+ fctx->lock = &pfifo->uevent->lock;
INIT_LIST_HEAD(&fctx->flip);
INIT_LIST_HEAD(&fctx->pending);
- spin_lock_init(&fctx->lock);
}

+struct nouveau_fence_work {
+ struct work_struct work;
+ struct fence_cb cb;
+ void (*func)(void *);
+ void *data;
+};
+
static void
nouveau_fence_work_handler(struct work_struct *kwork)
{
- struct fence_work *work = container_of(kwork, typeof(*work), base);
+ struct nouveau_fence_work *work = container_of(kwork, typeof(*work), work);
work->func(work->data);
kfree(work);
}

+static void nouveau_fence_work_cb(struct fence *fence, struct fence_cb *cb)
+{
+ struct nouveau_fence_work *work = container_of(cb, typeof(*work), cb);
+
+ schedule_work(&work->work);
+}
+
+/*
+ * In an ideal world, read would not assume the channel context is still alive.
+ * This function may be called from another device, running into free memory as a
+ * result. The drm node should still be there, so we can derive the index from
+ * the fence context.
+ */
+static bool nouveau_fence_is_signaled(struct fence *f)
+{
+ struct nouveau_fence *fence = container_of(f, struct nouveau_fence, base);
+ struct nouveau_channel *chan = fence->channel;
+ struct nouveau_fence_chan *fctx = chan->fence;
+
+ return (int)(fctx->read(chan) - fence->base.seqno) >= 0;
+}
+
void
nouveau_fence_work(struct nouveau_fence *fence,
void (*func)(void *), void *data)
{
- struct nouveau_channel *chan = fence->channel;
- struct nouveau_fence_chan *fctx;
- struct fence_work *work = NULL;
+ struct nouveau_fence_work *work;

- if (nouveau_fence_done(fence)) {
- func(data);
- return;
- }
+ if (fence_is_signaled(&fence->base))
+ goto err;

- fctx = chan->fence;
work = kmalloc(sizeof(*work), GFP_KERNEL);
if (!work) {
WARN_ON(nouveau_fence_wait(fence, false, false));
- func(data);
- return;
- }
-
- spin_lock(&fctx->lock);
- if (!fence->channel) {
- spin_unlock(&fctx->lock);
- kfree(work);
- func(data);
- return;
+ goto err;
}

- INIT_WORK(&work->base, nouveau_fence_work_handler);
+ INIT_WORK(&work->work, nouveau_fence_work_handler);
work->func = func;
work->data = data;
- list_add(&work->head, &fence->work);
- spin_unlock(&fctx->lock);
+
+ if (fence_add_callback(&fence->base, &work->cb, nouveau_fence_work_cb) < 0)
+ goto err_free;
+ return;
+
+err_free:
+ kfree(work);
+err:
+ func(data);
}

static void
@@ -125,33 +152,44 @@ nouveau_fence_update(struct nouveau_channel *chan)
struct nouveau_fence_chan *fctx = chan->fence;
struct nouveau_fence *fence, *fnext;

- spin_lock(&fctx->lock);
+ u32 seq = fctx->read(chan);
+
list_for_each_entry_safe(fence, fnext, &fctx->pending, head) {
- if (fctx->read(chan) < fence->sequence)
+ if ((int)(seq - fence->base.seqno) < 0)
break;

nouveau_fence_signal(fence);
- nouveau_fence_unref(&fence);
}
- spin_unlock(&fctx->lock);
}

int
nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan)
{
struct nouveau_fence_chan *fctx = chan->fence;
+ struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device);
+ struct nouveau_fifo_chan *fifo = (void*)chan->object;
+ struct nouveau_fence_priv *priv = (void*)chan->drm->fence;
int ret;

fence->channel = chan;
fence->timeout = jiffies + (15 * DRM_HZ);
- fence->sequence = ++fctx->sequence;
+
+ if (priv->uevent)
+ __fence_init(&fence->base, &nouveau_fence_ops_uevent,
+ &pfifo->uevent->lock,
+ priv->context_base + fifo->chid, ++fctx->sequence);
+ else
+ __fence_init(&fence->base, &nouveau_fence_ops_legacy,
+ &pfifo->uevent->lock,
+ priv->context_base + fifo->chid, ++fctx->sequence);

ret = fctx->emit(fence);
if (!ret) {
- kref_get(&fence->kref);
- spin_lock(&fctx->lock);
+ fence_get(&fence->base);
+ spin_lock_irq(fctx->lock);
+ nouveau_fence_update(chan);
list_add_tail(&fence->head, &fctx->pending);
- spin_unlock(&fctx->lock);
+ spin_unlock_irq(fctx->lock);
}

return ret;
@@ -160,107 +198,71 @@ nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan)
bool
nouveau_fence_done(struct nouveau_fence *fence)
{
- if (fence->channel)
+ if (fence->base.ops == &nouveau_fence_ops_legacy ||
+ fence->base.ops == &nouveau_fence_ops_uevent) {
+ struct nouveau_fence_chan *fctx;
+ unsigned long flags;
+
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->base.flags))
+ return true;
+
+ fctx = fence->channel->fence;
+ spin_lock_irqsave(fctx->lock, flags);
nouveau_fence_update(fence->channel);
- return !fence->channel;
+ spin_unlock_irqrestore(fctx->lock, flags);
+ }
+ return fence_is_signaled(&fence->base);
}

-struct nouveau_fence_uevent {
- struct nouveau_eventh handler;
- struct nouveau_fence_priv *priv;
-};
-
-static int
-nouveau_fence_wait_uevent_handler(struct nouveau_eventh *event, int index)
+static long
+nouveau_fence_wait_legacy(struct fence *f, bool intr, long wait)
{
- struct nouveau_fence_uevent *uevent =
- container_of(event, struct nouveau_fence_uevent, handler);
- wake_up_all(&uevent->priv->waiting);
- return NVKM_EVENT_KEEP;
-}
+ struct nouveau_fence *fence = container_of(f, typeof(*fence), base);
+ unsigned long sleep_time = NSEC_PER_MSEC / 1000;
+ unsigned long t = jiffies, timeout = t + wait;

-static int
-nouveau_fence_wait_uevent(struct nouveau_fence *fence, bool intr)
+ while (!nouveau_fence_done(fence)) {
+ ktime_t kt;

-{
- struct nouveau_channel *chan = fence->channel;
- struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device);
- struct nouveau_fence_priv *priv = chan->drm->fence;
- struct nouveau_fence_uevent uevent = {
- .handler.func = nouveau_fence_wait_uevent_handler,
- .priv = priv,
- };
- int ret = 0;
+ t = jiffies;

- nouveau_event_get(pfifo->uevent, 0, &uevent.handler);
-
- if (fence->timeout) {
- unsigned long timeout = fence->timeout - jiffies;
-
- if (time_before(jiffies, fence->timeout)) {
- if (intr) {
- ret = wait_event_interruptible_timeout(
- priv->waiting,
- nouveau_fence_done(fence),
- timeout);
- } else {
- ret = wait_event_timeout(priv->waiting,
- nouveau_fence_done(fence),
- timeout);
- }
+ if (wait != MAX_SCHEDULE_TIMEOUT && time_after_eq(t, timeout)) {
+ __set_current_state(TASK_RUNNING);
+ return 0;
}

- if (ret >= 0) {
- fence->timeout = jiffies + ret;
- if (time_after_eq(jiffies, fence->timeout))
- ret = -EBUSY;
- }
- } else {
- if (intr) {
- ret = wait_event_interruptible(priv->waiting,
- nouveau_fence_done(fence));
- } else {
- wait_event(priv->waiting, nouveau_fence_done(fence));
- }
+ __set_current_state(intr ? TASK_INTERRUPTIBLE :
+ TASK_UNINTERRUPTIBLE);
+
+ kt = ktime_set(0, sleep_time);
+ schedule_hrtimeout(&kt, HRTIMER_MODE_REL);
+ sleep_time *= 2;
+ if (sleep_time > NSEC_PER_MSEC)
+ sleep_time = NSEC_PER_MSEC;
+
+ if (intr && signal_pending(current))
+ return -ERESTARTSYS;
}

- nouveau_event_put(pfifo->uevent, 0, &uevent.handler);
- if (unlikely(ret < 0))
- return ret;
+ __set_current_state(TASK_RUNNING);

- return 0;
+ return timeout - t;
}

-int
-nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr)
+static int
+nouveau_fence_wait_busy(struct nouveau_fence *fence, bool intr)
{
- struct nouveau_channel *chan = fence->channel;
- struct nouveau_fence_priv *priv = chan ? chan->drm->fence : NULL;
- unsigned long sleep_time = NSEC_PER_MSEC / 1000;
- ktime_t t;
int ret = 0;

- while (priv && priv->uevent && lazy && !nouveau_fence_done(fence)) {
- ret = nouveau_fence_wait_uevent(fence, intr);
- if (ret < 0)
- return ret;
- }
-
while (!nouveau_fence_done(fence)) {
- if (fence->timeout && time_after_eq(jiffies, fence->timeout)) {
+ if (time_after_eq(jiffies, fence->timeout)) {
ret = -EBUSY;
break;
}

- __set_current_state(intr ? TASK_INTERRUPTIBLE :
- TASK_UNINTERRUPTIBLE);
- if (lazy) {
- t = ktime_set(0, sleep_time);
- schedule_hrtimeout(&t, HRTIMER_MODE_REL);
- sleep_time *= 2;
- if (sleep_time > NSEC_PER_MSEC)
- sleep_time = NSEC_PER_MSEC;
- }
+ __set_current_state(intr ?
+ TASK_INTERRUPTIBLE :
+ TASK_UNINTERRUPTIBLE);

if (intr && signal_pending(current)) {
ret = -ERESTARTSYS;
@@ -273,14 +275,31 @@ nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr)
}

int
+nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr)
+{
+ long ret;
+
+ if (!lazy)
+ return nouveau_fence_wait_busy(fence, intr);
+
+ ret = fence_wait_timeout(&fence->base, intr, 15 * DRM_HZ);
+ if (ret < 0)
+ return ret;
+ else if (!ret)
+ return -EBUSY;
+ else
+ return 0;
+}
+
+int
nouveau_fence_sync(struct nouveau_fence *fence, struct nouveau_channel *chan)
{
struct nouveau_fence_chan *fctx = chan->fence;
- struct nouveau_channel *prev;
int ret = 0;

- prev = fence ? fence->channel : NULL;
- if (prev) {
+ if (fence) {
+ struct nouveau_channel *prev = fence->channel;
+
if (unlikely(prev != chan && !nouveau_fence_done(fence))) {
ret = fctx->sync(fence, prev, chan);
if (unlikely(ret))
@@ -291,25 +310,18 @@ nouveau_fence_sync(struct nouveau_fence *fence, struct nouveau_channel *chan)
return ret;
}

-static void
-nouveau_fence_del(struct kref *kref)
-{
- struct nouveau_fence *fence = container_of(kref, typeof(*fence), kref);
- kfree(fence);
-}
-
void
nouveau_fence_unref(struct nouveau_fence **pfence)
{
if (*pfence)
- kref_put(&(*pfence)->kref, nouveau_fence_del);
+ fence_put(&(*pfence)->base);
*pfence = NULL;
}

struct nouveau_fence *
nouveau_fence_ref(struct nouveau_fence *fence)
{
- kref_get(&fence->kref);
+ fence_get(&fence->base);
return fence;
}

@@ -327,9 +339,7 @@ nouveau_fence_new(struct nouveau_channel *chan, bool sysmem,
if (!fence)
return -ENOMEM;

- INIT_LIST_HEAD(&fence->work);
fence->sysmem = sysmem;
- kref_init(&fence->kref);

ret = nouveau_fence_emit(fence, chan);
if (ret)
@@ -338,3 +348,64 @@ nouveau_fence_new(struct nouveau_channel *chan, bool sysmem,
*pfence = fence;
return ret;
}
+
+
+static bool nouveau_fence_no_signaling(struct fence *f)
+{
+ /*
+ * This needs uevents to work correctly, but fence_add_callback relies on
+ * being able to enable signaling. It will still get signaled eventually,
+ * just not right away.
+ */
+ if (nouveau_fence_is_signaled(f))
+ return false;
+
+ return true;
+}
+
+static const struct fence_ops nouveau_fence_ops_legacy = {
+ .enable_signaling = nouveau_fence_no_signaling,
+ .signaled = nouveau_fence_is_signaled,
+ .wait = nouveau_fence_wait_legacy,
+ .release = NULL
+};
+
+static int
+nouveau_fence_wait_uevent_handler(struct nouveau_eventh *event, int index)
+{
+ struct nouveau_fence *fence =
+ container_of(event, struct nouveau_fence, event);
+
+ if (nouveau_fence_is_signaled(&fence->base))
+ nouveau_fence_signal(fence);
+
+ /*
+ * NVKM_EVENT_DROP is never appropriate here, nouveau_fence_signal
+ * will unlink and free the event if needed.
+ */
+ return NVKM_EVENT_KEEP;
+}
+
+static bool nouveau_fence_enable_signaling(struct fence *f)
+{
+ struct nouveau_fence *fence = container_of(f, struct nouveau_fence, base);
+ struct nouveau_event *event = container_of(f->lock, struct nouveau_event, lock);
+
+ nouveau_event_enable_locked(event, 0);
+ if (nouveau_fence_is_signaled(f)) {
+ nouveau_event_disable_locked(event, 0, 1);
+ return false;
+ }
+
+ list_add_tail(&fence->event.head, &event->index[0].list);
+ fence->event.func = nouveau_fence_wait_uevent_handler;
+
+ return true;
+}
+
+static const struct fence_ops nouveau_fence_ops_uevent = {
+ .enable_signaling = nouveau_fence_enable_signaling,
+ .signaled = nouveau_fence_is_signaled,
+ .wait = fence_default_wait,
+ .release = NULL
+};
diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.h b/drivers/gpu/drm/nouveau/nouveau_fence.h
index c57bb61..1df933c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fence.h
+++ b/drivers/gpu/drm/nouveau/nouveau_fence.h
@@ -1,18 +1,20 @@
#ifndef __NOUVEAU_FENCE_H__
#define __NOUVEAU_FENCE_H__

+#include <linux/fence.h>
+
struct nouveau_drm;

struct nouveau_fence {
+ struct fence base;
+
struct list_head head;
- struct list_head work;
- struct kref kref;
+ struct nouveau_eventh event;

bool sysmem;

struct nouveau_channel *channel;
unsigned long timeout;
- u32 sequence;
};

int nouveau_fence_new(struct nouveau_channel *, bool sysmem,
@@ -38,8 +40,9 @@ struct nouveau_fence_chan {
int (*emit32)(struct nouveau_channel *, u64, u32);
int (*sync32)(struct nouveau_channel *, u64, u32);

- spinlock_t lock;
+ spinlock_t *lock;
u32 sequence;
+ u32 context;
};

struct nouveau_fence_priv {
@@ -49,13 +52,14 @@ struct nouveau_fence_priv {
int (*context_new)(struct nouveau_channel *);
void (*context_del)(struct nouveau_channel *);

- wait_queue_head_t waiting;
bool uevent;
+
+ u32 contexts, context_base;
};

#define nouveau_fence(drm) ((struct nouveau_fence_priv *)(drm)->fence)

-void nouveau_fence_context_new(struct nouveau_fence_chan *);
+void nouveau_fence_context_new(struct nouveau_channel *, struct nouveau_fence_chan *);
void nouveau_fence_context_del(struct nouveau_fence_chan *);

int nv04_fence_create(struct nouveau_drm *);
diff --git a/drivers/gpu/drm/nouveau/nv04_fence.c b/drivers/gpu/drm/nouveau/nv04_fence.c
index 94eadd1..997c541 100644
--- a/drivers/gpu/drm/nouveau/nv04_fence.c
+++ b/drivers/gpu/drm/nouveau/nv04_fence.c
@@ -43,7 +43,7 @@ nv04_fence_emit(struct nouveau_fence *fence)
int ret = RING_SPACE(chan, 2);
if (ret == 0) {
BEGIN_NV04(chan, NvSubSw, 0x0150, 1);
- OUT_RING (chan, fence->sequence);
+ OUT_RING (chan, fence->base.seqno);
FIRE_RING (chan);
}
return ret;
@@ -77,7 +77,7 @@ nv04_fence_context_new(struct nouveau_channel *chan)
{
struct nv04_fence_chan *fctx = kzalloc(sizeof(*fctx), GFP_KERNEL);
if (fctx) {
- nouveau_fence_context_new(&fctx->base);
+ nouveau_fence_context_new(chan, &fctx->base);
fctx->base.emit = nv04_fence_emit;
fctx->base.sync = nv04_fence_sync;
fctx->base.read = nv04_fence_read;
diff --git a/drivers/gpu/drm/nouveau/nv10_fence.c b/drivers/gpu/drm/nouveau/nv10_fence.c
index 06f434f..e8f73f7 100644
--- a/drivers/gpu/drm/nouveau/nv10_fence.c
+++ b/drivers/gpu/drm/nouveau/nv10_fence.c
@@ -36,7 +36,7 @@ nv10_fence_emit(struct nouveau_fence *fence)
int ret = RING_SPACE(chan, 2);
if (ret == 0) {
BEGIN_NV04(chan, 0, NV10_SUBCHAN_REF_CNT, 1);
- OUT_RING (chan, fence->sequence);
+ OUT_RING (chan, fence->base.seqno);
FIRE_RING (chan);
}
return ret;
@@ -74,7 +74,7 @@ nv10_fence_context_new(struct nouveau_channel *chan)
if (!fctx)
return -ENOMEM;

- nouveau_fence_context_new(&fctx->base);
+ nouveau_fence_context_new(chan, &fctx->base);
fctx->base.emit = nv10_fence_emit;
fctx->base.read = nv10_fence_read;
fctx->base.sync = nv10_fence_sync;
diff --git a/drivers/gpu/drm/nouveau/nv17_fence.c b/drivers/gpu/drm/nouveau/nv17_fence.c
index 22aa996..e404bab 100644
--- a/drivers/gpu/drm/nouveau/nv17_fence.c
+++ b/drivers/gpu/drm/nouveau/nv17_fence.c
@@ -83,7 +83,7 @@ nv17_fence_context_new(struct nouveau_channel *chan)
if (!fctx)
return -ENOMEM;

- nouveau_fence_context_new(&fctx->base);
+ nouveau_fence_context_new(chan, &fctx->base);
fctx->base.emit = nv10_fence_emit;
fctx->base.read = nv10_fence_read;
fctx->base.sync = nv17_fence_sync;
diff --git a/drivers/gpu/drm/nouveau/nv50_fence.c b/drivers/gpu/drm/nouveau/nv50_fence.c
index 0ee3638..19f6fcc 100644
--- a/drivers/gpu/drm/nouveau/nv50_fence.c
+++ b/drivers/gpu/drm/nouveau/nv50_fence.c
@@ -47,7 +47,7 @@ nv50_fence_context_new(struct nouveau_channel *chan)
if (!fctx)
return -ENOMEM;

- nouveau_fence_context_new(&fctx->base);
+ nouveau_fence_context_new(chan, &fctx->base);
fctx->base.emit = nv10_fence_emit;
fctx->base.read = nv10_fence_read;
fctx->base.sync = nv17_fence_sync;
diff --git a/drivers/gpu/drm/nouveau/nv84_fence.c b/drivers/gpu/drm/nouveau/nv84_fence.c
index 9fd475c..8a06727 100644
--- a/drivers/gpu/drm/nouveau/nv84_fence.c
+++ b/drivers/gpu/drm/nouveau/nv84_fence.c
@@ -89,7 +89,7 @@ nv84_fence_emit(struct nouveau_fence *fence)
else
addr += fctx->vma.offset;

- return fctx->base.emit32(chan, addr, fence->sequence);
+ return fctx->base.emit32(chan, addr, fence->base.seqno);
}

static int
@@ -105,7 +105,7 @@ nv84_fence_sync(struct nouveau_fence *fence,
else
addr += fctx->vma.offset;

- return fctx->base.sync32(chan, addr, fence->sequence);
+ return fctx->base.sync32(chan, addr, fence->base.seqno);
}

static u32
@@ -149,12 +149,14 @@ nv84_fence_context_new(struct nouveau_channel *chan)
if (!fctx)
return -ENOMEM;

- nouveau_fence_context_new(&fctx->base);
+ nouveau_fence_context_new(chan, &fctx->base);
fctx->base.emit = nv84_fence_emit;
fctx->base.sync = nv84_fence_sync;
fctx->base.read = nv84_fence_read;
fctx->base.emit32 = nv84_fence_emit32;
fctx->base.sync32 = nv84_fence_sync32;
+ fctx->base.sequence = nv84_fence_read(chan);
+ fctx->base.context = priv->base.context_base + fifo->chid;

ret = nouveau_bo_vma_add(priv->bo, client->vm, &fctx->vma);
if (ret == 0) {
@@ -239,7 +241,8 @@ nv84_fence_create(struct nouveau_drm *drm)
priv->base.context_new = nv84_fence_context_new;
priv->base.context_del = nv84_fence_context_del;

- init_waitqueue_head(&priv->base.waiting);
+ priv->base.contexts = pfifo->max + 1;
+ priv->base.context_base = fence_context_alloc(priv->base.contexts);
priv->base.uevent = true;

ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0,

2013-08-19 10:17:45

by Maarten Lankhorst

[permalink] [raw]
Subject: [RFC PATCH] drm/radeon: rework to new fence interface

Signed-off-by: Maarten Lankhorst <[email protected]>
---
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 9f19259..971284e 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -64,6 +64,7 @@
#include <linux/wait.h>
#include <linux/list.h>
#include <linux/kref.h>
+#include <linux/fence.h>

#include <ttm/ttm_bo_api.h>
#include <ttm/ttm_bo_driver.h>
@@ -114,9 +115,6 @@ extern int radeon_aspm;
/* max number of rings */
#define RADEON_NUM_RINGS 6

-/* fence seq are set to this number when signaled */
-#define RADEON_FENCE_SIGNALED_SEQ 0LL
-
/* internal ring indices */
/* r1xx+ has gfx CP ring */
#define RADEON_RING_TYPE_GFX_INDEX 0
@@ -285,12 +283,15 @@ struct radeon_fence_driver {
};

struct radeon_fence {
+ struct fence base;
+
struct radeon_device *rdev;
- struct kref kref;
/* protected by radeon_fence.lock */
uint64_t seq;
/* RB, DMA, etc. */
unsigned ring;
+
+ wait_queue_t fence_wake;
};

int radeon_fence_driver_start_ring(struct radeon_device *rdev, int ring);
@@ -2039,6 +2040,7 @@ struct radeon_device {
struct radeon_mman mman;
struct radeon_fence_driver fence_drv[RADEON_NUM_RINGS];
wait_queue_head_t fence_queue;
+ unsigned fence_context;
struct mutex ring_lock;
struct radeon_ring ring[RADEON_NUM_RINGS];
bool ib_pool_ready;
@@ -2117,11 +2119,6 @@ u32 cik_mm_rdoorbell(struct radeon_device *rdev, u32 offset);
void cik_mm_wdoorbell(struct radeon_device *rdev, u32 offset, u32 v);

/*
- * Cast helper
- */
-#define to_radeon_fence(p) ((struct radeon_fence *)(p))
-
-/*
* Registers read & write functions.
*/
#define RREG8(reg) readb((rdev->rmmio) + (reg))
diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c
index 63398ae..d76a187 100644
--- a/drivers/gpu/drm/radeon/radeon_device.c
+++ b/drivers/gpu/drm/radeon/radeon_device.c
@@ -1150,6 +1150,7 @@ int radeon_device_init(struct radeon_device *rdev,
for (i = 0; i < RADEON_NUM_RINGS; i++) {
rdev->ring[i].idx = i;
}
+ rdev->fence_context = fence_context_alloc(RADEON_NUM_RINGS);

DRM_INFO("initializing kernel modesetting (%s 0x%04X:0x%04X 0x%04X:0x%04X).\n",
radeon_family_name[rdev->family], pdev->vendor, pdev->device,
diff --git a/drivers/gpu/drm/radeon/radeon_fence.c b/drivers/gpu/drm/radeon/radeon_fence.c
index ddb8f8e..92a1576 100644
--- a/drivers/gpu/drm/radeon/radeon_fence.c
+++ b/drivers/gpu/drm/radeon/radeon_fence.c
@@ -39,6 +39,15 @@
#include "radeon.h"
#include "radeon_trace.h"

+static const struct fence_ops radeon_fence_ops;
+
+#define to_radeon_fence(p) \
+ ({ \
+ struct radeon_fence *__f; \
+ __f = container_of((p), struct radeon_fence, base); \
+ __f->base.ops == &radeon_fence_ops ? __f : NULL; \
+ })
+
/*
* Fences
* Fences mark an event in the GPUs pipeline and are used
@@ -111,14 +120,17 @@ int radeon_fence_emit(struct radeon_device *rdev,
struct radeon_fence **fence,
int ring)
{
+ u64 seq = ++rdev->fence_drv[ring].sync_seq[ring];
+
/* we are protected by the ring emission mutex */
*fence = kmalloc(sizeof(struct radeon_fence), GFP_KERNEL);
if ((*fence) == NULL) {
return -ENOMEM;
}
- kref_init(&((*fence)->kref));
+ __fence_init(&(*fence)->base, &radeon_fence_ops,
+ &rdev->fence_queue.lock, rdev->fence_context + ring, seq);
(*fence)->rdev = rdev;
- (*fence)->seq = ++rdev->fence_drv[ring].sync_seq[ring];
+ (*fence)->seq = seq;
(*fence)->ring = ring;
radeon_fence_ring_emit(rdev, ring, *fence);
trace_radeon_fence_emit(rdev->ddev, (*fence)->seq);
@@ -126,15 +138,38 @@ int radeon_fence_emit(struct radeon_device *rdev,
}

/**
- * radeon_fence_process - process a fence
+ * radeon_fence_check_signaled - callback from fence_queue
*
- * @rdev: radeon_device pointer
- * @ring: ring index the fence is associated with
- *
- * Checks the current fence value and wakes the fence queue
- * if the sequence number has increased (all asics).
+ * this function is called with fence_queue lock held, which is also used
+ * for the fence locking itself, so unlocked variants are used for
+ * fence_signal, and remove_wait_queue.
*/
-void radeon_fence_process(struct radeon_device *rdev, int ring)
+static int radeon_fence_check_signaled(wait_queue_t *wait, unsigned mode, int flags, void *key)
+{
+ struct radeon_fence *fence;
+ u64 seq;
+
+ fence = container_of(wait, struct radeon_fence, fence_wake);
+
+ seq = atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq);
+ if (seq >= fence->seq) {
+ int ret = __fence_signal(&fence->base);
+
+ if (!ret)
+ FENCE_TRACE(&fence->base, "signaled from irq context\n");
+ else
+ FENCE_TRACE(&fence->base, "was already signaled\n");
+
+ /* probably a bad idea to call this from the irq handler, so lets not.. */
+ atomic_dec(&fence->rdev->irq.ring_int[fence->ring]);
+ __remove_wait_queue(&fence->rdev->fence_queue, &fence->fence_wake);
+ fence_put(&fence->base);
+ } else
+ FENCE_TRACE(&fence->base, "pending\n");
+ return 0;
+}
+
+static bool __radeon_fence_process(struct radeon_device *rdev, int ring)
{
uint64_t seq, last_seq, last_emitted;
unsigned count_loop = 0;
@@ -190,25 +225,24 @@ void radeon_fence_process(struct radeon_device *rdev, int ring)
}
} while (atomic64_xchg(&rdev->fence_drv[ring].last_seq, seq) > seq);

- if (wake) {
+ if (wake)
rdev->fence_drv[ring].last_activity = jiffies;
- wake_up_all(&rdev->fence_queue);
- }
+ return wake;
}

/**
- * radeon_fence_destroy - destroy a fence
+ * radeon_fence_process - process a fence
*
- * @kref: fence kref
+ * @rdev: radeon_device pointer
+ * @ring: ring index the fence is associated with
*
- * Frees the fence object (all asics).
+ * Checks the current fence value and wakes the fence queue
+ * if the sequence number has increased (all asics).
*/
-static void radeon_fence_destroy(struct kref *kref)
+void radeon_fence_process(struct radeon_device *rdev, int ring)
{
- struct radeon_fence *fence;
-
- fence = container_of(kref, struct radeon_fence, kref);
- kfree(fence);
+ if (__radeon_fence_process(rdev, ring))
+ wake_up_all(&rdev->fence_queue);
}

/**
@@ -239,6 +273,49 @@ static bool radeon_fence_seq_signaled(struct radeon_device *rdev,
return false;
}

+static bool __radeon_fence_signaled(struct fence *f)
+{
+ struct radeon_fence *fence = to_radeon_fence(f);
+
+ return radeon_fence_seq_signaled(fence->rdev, fence->seq, fence->ring);
+}
+
+/**
+ * radeon_fence_enable_signaling - enable signalling on fence
+ * @fence: fence
+ *
+ * This function is called with fence_queue lock held, and adds a callback
+ * to fence_queue that checks if this fence is signaled, and if so it
+ * signals the fence and removes itself.
+ */
+static bool radeon_fence_enable_signaling(struct fence *f)
+{
+ struct radeon_fence *fence = to_radeon_fence(f);
+
+ if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
+ !fence->rdev->ddev->irq_enabled)
+ return false;
+
+ radeon_irq_kms_sw_irq_get(fence->rdev, fence->ring);
+
+ if (__radeon_fence_process(fence->rdev, fence->ring))
+ wake_up_all_locked(&fence->rdev->fence_queue);
+
+ /* did fence get signaled after we enabled the sw irq? */
+ if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq) {
+ radeon_irq_kms_sw_irq_put(fence->rdev, fence->ring);
+ return false;
+ }
+
+ fence->fence_wake.flags = 0;
+ fence->fence_wake.private = NULL;
+ fence->fence_wake.func = radeon_fence_check_signaled;
+ __add_wait_queue(&fence->rdev->fence_queue, &fence->fence_wake);
+ fence_get(f);
+
+ return true;
+}
+
/**
* radeon_fence_signaled - check if a fence has signaled
*
@@ -252,11 +329,13 @@ bool radeon_fence_signaled(struct radeon_fence *fence)
if (!fence) {
return true;
}
- if (fence->seq == RADEON_FENCE_SIGNALED_SEQ) {
- return true;
- }
+
if (radeon_fence_seq_signaled(fence->rdev, fence->seq, fence->ring)) {
- fence->seq = RADEON_FENCE_SIGNALED_SEQ;
+ int ret;
+
+ ret = fence_signal(&fence->base);
+ if (!ret)
+ FENCE_TRACE(&fence->base, "signaled from radeon_fence_signaled\n");
return true;
}
return false;
@@ -379,7 +458,7 @@ static int radeon_fence_wait_seq(struct radeon_device *rdev, u64 target_seq,
* radeon_fence_wait - wait for a fence to signal
*
* @fence: radeon fence object
- * @intr: use interruptable sleep
+ * @intr: use interruptible sleep
*
* Wait for the requested fence to signal (all asics).
* @intr selects whether to use interruptable (true) or non-interruptable
@@ -390,17 +469,17 @@ int radeon_fence_wait(struct radeon_fence *fence, bool intr)
{
int r;

- if (fence == NULL) {
- WARN(1, "Querying an invalid fence : %p !\n", fence);
- return -EINVAL;
- }
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->base.flags))
+ return 0;

r = radeon_fence_wait_seq(fence->rdev, fence->seq,
fence->ring, intr, true);
if (r) {
return r;
}
- fence->seq = RADEON_FENCE_SIGNALED_SEQ;
+ r = fence_signal(&fence->base);
+ if (!r)
+ FENCE_TRACE(&fence->base, "signaled from fence_wait\n");
return 0;
}

@@ -567,8 +646,8 @@ int radeon_fence_wait_any(struct radeon_device *rdev,
continue;
}

- if (fences[i]->seq == RADEON_FENCE_SIGNALED_SEQ) {
- /* something was allready signaled */
+ if (test_bit(FENCE_FLAG_SIGNALED_BIT, &fences[i]->base.flags)) {
+ /* already signaled */
return 0;
}

@@ -641,7 +720,7 @@ int radeon_fence_wait_empty_locked(struct radeon_device *rdev, int ring)
*/
struct radeon_fence *radeon_fence_ref(struct radeon_fence *fence)
{
- kref_get(&fence->kref);
+ fence_get(&fence->base);
return fence;
}

@@ -657,9 +736,8 @@ void radeon_fence_unref(struct radeon_fence **fence)
struct radeon_fence *tmp = *fence;

*fence = NULL;
- if (tmp) {
- kref_put(&tmp->kref, radeon_fence_destroy);
- }
+ if (tmp)
+ fence_put(&tmp->base);
}

/**
@@ -947,3 +1025,10 @@ int radeon_debugfs_fence_init(struct radeon_device *rdev)
return 0;
#endif
}
+
+static const struct fence_ops radeon_fence_ops = {
+ .enable_signaling = radeon_fence_enable_signaling,
+ .signaled = __radeon_fence_signaled,
+ .wait = fence_default_wait,
+ .release = NULL,
+};

2013-08-19 12:36:17

by Christian König

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Am 19.08.2013 12:17, schrieb Maarten Lankhorst:
> [SNIP]
> @@ -190,25 +225,24 @@ void radeon_fence_process(struct radeon_device *rdev, int ring)
> }
> } while (atomic64_xchg(&rdev->fence_drv[ring].last_seq, seq) > seq);
>
> - if (wake) {
> + if (wake)
> rdev->fence_drv[ring].last_activity = jiffies;
> - wake_up_all(&rdev->fence_queue);
> - }
> + return wake;
> }

Very bad idea, when sequence numbers change, you always want to wake up
the whole fence queue here.

> [SNIP]
> +/**
> + * radeon_fence_enable_signaling - enable signalling on fence
> + * @fence: fence
> + *
> + * This function is called with fence_queue lock held, and adds a callback
> + * to fence_queue that checks if this fence is signaled, and if so it
> + * signals the fence and removes itself.
> + */
> +static bool radeon_fence_enable_signaling(struct fence *f)
> +{
> + struct radeon_fence *fence = to_radeon_fence(f);
> +
> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
> + !fence->rdev->ddev->irq_enabled)
> + return false;
> +

Do I get that right that you rely on IRQs to be enabled and working
here? Cause that would be a quite bad idea from the conceptual side.

> + radeon_irq_kms_sw_irq_get(fence->rdev, fence->ring);
> +
> + if (__radeon_fence_process(fence->rdev, fence->ring))
> + wake_up_all_locked(&fence->rdev->fence_queue);
> +
> + /* did fence get signaled after we enabled the sw irq? */
> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq) {
> + radeon_irq_kms_sw_irq_put(fence->rdev, fence->ring);
> + return false;
> + }
> +
> + fence->fence_wake.flags = 0;
> + fence->fence_wake.private = NULL;
> + fence->fence_wake.func = radeon_fence_check_signaled;
> + __add_wait_queue(&fence->rdev->fence_queue, &fence->fence_wake);
> + fence_get(f);
> +
> + return true;
> +}
> +
> /**
> * radeon_fence_signaled - check if a fence has signaled
> *
>

Christian.

2013-08-19 19:37:59

by Maarten Lankhorst

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Op 19-08-13 14:35, Christian K?nig schreef:
> Am 19.08.2013 12:17, schrieb Maarten Lankhorst:
>> [SNIP]
>> @@ -190,25 +225,24 @@ void radeon_fence_process(struct radeon_device *rdev, int ring)
>> }
>> } while (atomic64_xchg(&rdev->fence_drv[ring].last_seq, seq) > seq);
>> - if (wake) {
>> + if (wake)
>> rdev->fence_drv[ring].last_activity = jiffies;
>> - wake_up_all(&rdev->fence_queue);
>> - }
>> + return wake;
>> }
>
> Very bad idea, when sequence numbers change, you always want to wake up the whole fence queue here.
Yes, and the callers of this function call wake_up_all or wake_up_all_locked themselves, based on the return value..

>> [SNIP]
>> +/**
>> + * radeon_fence_enable_signaling - enable signalling on fence
>> + * @fence: fence
>> + *
>> + * This function is called with fence_queue lock held, and adds a callback
>> + * to fence_queue that checks if this fence is signaled, and if so it
>> + * signals the fence and removes itself.
>> + */
>> +static bool radeon_fence_enable_signaling(struct fence *f)
>> +{
>> + struct radeon_fence *fence = to_radeon_fence(f);
>> +
>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>> + !fence->rdev->ddev->irq_enabled)
>> + return false;
>> +
>
> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.
>> + radeon_irq_kms_sw_irq_get(fence->rdev, fence->ring);
>> +
>> + if (__radeon_fence_process(fence->rdev, fence->ring))
>> + wake_up_all_locked(&fence->rdev->fence_queue);
>> +
>> + /* did fence get signaled after we enabled the sw irq? */
>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq) {
>> + radeon_irq_kms_sw_irq_put(fence->rdev, fence->ring);
>> + return false;
>> + }
>> +
>> + fence->fence_wake.flags = 0;
>> + fence->fence_wake.private = NULL;
>> + fence->fence_wake.func = radeon_fence_check_signaled;
>> + __add_wait_queue(&fence->rdev->fence_queue, &fence->fence_wake);
>> + fence_get(f);
>> +
>> + return true;
>> +}
>> +
>> /**
>> * radeon_fence_signaled - check if a fence has signaled
>> *
>>
>
> Christian.
>
~Maarten

2013-08-20 08:38:12

by Christian König

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Am 19.08.2013 21:37, schrieb Maarten Lankhorst:
> Op 19-08-13 14:35, Christian K?nig schreef:
>> Am 19.08.2013 12:17, schrieb Maarten Lankhorst:
>>> [SNIP]
>>> @@ -190,25 +225,24 @@ void radeon_fence_process(struct radeon_device *rdev, int ring)
>>> }
>>> } while (atomic64_xchg(&rdev->fence_drv[ring].last_seq, seq) > seq);
>>> - if (wake) {
>>> + if (wake)
>>> rdev->fence_drv[ring].last_activity = jiffies;
>>> - wake_up_all(&rdev->fence_queue);
>>> - }
>>> + return wake;
>>> }
>> Very bad idea, when sequence numbers change, you always want to wake up the whole fence queue here.
> Yes, and the callers of this function call wake_up_all or wake_up_all_locked themselves, based on the return value..

And as I said that's a very bad idea. The fence processing shouldn't be
called with any locks held and should be self responsible for activating
any waiters.

>
>>> [SNIP]
>>> +/**
>>> + * radeon_fence_enable_signaling - enable signalling on fence
>>> + * @fence: fence
>>> + *
>>> + * This function is called with fence_queue lock held, and adds a callback
>>> + * to fence_queue that checks if this fence is signaled, and if so it
>>> + * signals the fence and removes itself.
>>> + */
>>> +static bool radeon_fence_enable_signaling(struct fence *f)
>>> +{
>>> + struct radeon_fence *fence = to_radeon_fence(f);
>>> +
>>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>>> + !fence->rdev->ddev->irq_enabled)
>>> + return false;
>>> +
>> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
> For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
> and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.

That's a big NAK.

The fence processing is actually very fine tuned to avoid IRQs and as
far as I can see you just leave them enabled by decrementing the atomic
from IRQ context. Additional to that we need allot of special handling
in case of a hardware lockup here, which isn't done if you abuse the
fence interface like this.

Also your approach of leaking the IRQ context outside of the driver is a
very bad idea from the conceptual side. Please don't modify the fence
interface at all and instead use the wait functions already exposed by
radeon_fence.c. If you need some kind of signaling mechanism then wait
inside a workqueue instead.

Christian.

2013-08-20 09:36:47

by Maarten Lankhorst

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Op 20-08-13 10:37, Christian K?nig schreef:
> Am 19.08.2013 21:37, schrieb Maarten Lankhorst:
>> Op 19-08-13 14:35, Christian K?nig schreef:
>>> Am 19.08.2013 12:17, schrieb Maarten Lankhorst:
>>>> [SNIP]
>>>> @@ -190,25 +225,24 @@ void radeon_fence_process(struct radeon_device *rdev, int ring)
>>>> }
>>>> } while (atomic64_xchg(&rdev->fence_drv[ring].last_seq, seq) > seq);
>>>> - if (wake) {
>>>> + if (wake)
>>>> rdev->fence_drv[ring].last_activity = jiffies;
>>>> - wake_up_all(&rdev->fence_queue);
>>>> - }
>>>> + return wake;
>>>> }
>>> Very bad idea, when sequence numbers change, you always want to wake up the whole fence queue here.
>> Yes, and the callers of this function call wake_up_all or wake_up_all_locked themselves, based on the return value..
>
> And as I said that's a very bad idea. The fence processing shouldn't be called with any locks held and should be self responsible for activating any waiters.
The call point (enable_signaling) only needs to know whether its own counter has passed or not. This prevents the race where the counter
has elapsed, but the irq was not yet enabled.

I don't really care if enable_signaling updates last_seq or not, it only needs to check if it's own fence has been signaled after enabling sw_irqs.

>>
>>>> [SNIP]
>>>> +/**
>>>> + * radeon_fence_enable_signaling - enable signalling on fence
>>>> + * @fence: fence
>>>> + *
>>>> + * This function is called with fence_queue lock held, and adds a callback
>>>> + * to fence_queue that checks if this fence is signaled, and if so it
>>>> + * signals the fence and removes itself.
>>>> + */
>>>> +static bool radeon_fence_enable_signaling(struct fence *f)
>>>> +{
>>>> + struct radeon_fence *fence = to_radeon_fence(f);
>>>> +
>>>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>>>> + !fence->rdev->ddev->irq_enabled)
>>>> + return false;
>>>> +
>>> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
>> For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
>> and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.
>
> That's a big NAK.
>
> The fence processing is actually very fine tuned to avoid IRQs and as far as I can see you just leave them enabled by decrementing the atomic from IRQ context. Additional to that we need allot of special handling in case of a hardware lockup here, which isn't done if you abuse the fence interface like this.
I think it's not needed to leave the irq enabled, it's a leftover from when I was debugging the mac and no interrupt occurred at all.

> Also your approach of leaking the IRQ context outside of the driver is a very bad idea from the conceptual side. Please don't modify the fence interface at all and instead use the wait functions already exposed by radeon_fence.c. If you need some kind of signaling mechanism then wait inside a workqueue instead.
The fence takes up the role of a single shot workqueue here. Manually resetting the counter and calling wake_up_all would end up waking all active fences, there's no special handling needed inside radeon for this.
The fence api does provide a synchronous wait function, but this causes a stall of whomever waits on it. When I was testing this with intel I used the fence callback to poke a register in i915, this allowed it to not block until it hits the wait op in the command stream, and even then only if the callback was not called first.

It's documented that the callbacks can be called from any context and will be called with irqs disabled, so nothing scary should be done. The kernel provides enough debug mechanisms to find any violators.
PROVE_LOCKING and DEBUG_ATOMIC_SLEEP for example.

~Maarten

2013-08-20 09:52:13

by Christian König

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Am 20.08.2013 11:36, schrieb Maarten Lankhorst:
[SNIP]

>>>>> [SNIP]
>>>>> +/**
>>>>> + * radeon_fence_enable_signaling - enable signalling on fence
>>>>> + * @fence: fence
>>>>> + *
>>>>> + * This function is called with fence_queue lock held, and adds a callback
>>>>> + * to fence_queue that checks if this fence is signaled, and if so it
>>>>> + * signals the fence and removes itself.
>>>>> + */
>>>>> +static bool radeon_fence_enable_signaling(struct fence *f)
>>>>> +{
>>>>> + struct radeon_fence *fence = to_radeon_fence(f);
>>>>> +
>>>>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>>>>> + !fence->rdev->ddev->irq_enabled)
>>>>> + return false;
>>>>> +
>>>> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
>>> For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
>>> and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.
>> That's a big NAK.
>>
>> The fence processing is actually very fine tuned to avoid IRQs and as far as I can see you just leave them enabled by decrementing the atomic from IRQ context. Additional to that we need allot of special handling in case of a hardware lockup here, which isn't done if you abuse the fence interface like this.
> I think it's not needed to leave the irq enabled, it's a leftover from when I was debugging the mac and no interrupt occurred at all.
>
>> Also your approach of leaking the IRQ context outside of the driver is a very bad idea from the conceptual side. Please don't modify the fence interface at all and instead use the wait functions already exposed by radeon_fence.c. If you need some kind of signaling mechanism then wait inside a workqueue instead.
> The fence takes up the role of a single shot workqueue here. Manually resetting the counter and calling wake_up_all would end up waking all active fences, there's no special handling needed inside radeon for this.

Yeah that's actually the point here, you NEED to activate ALL fences,
otherwise the fence handling inside the driver won't work.

> The fence api does provide a synchronous wait function, but this causes a stall of whomever waits on it.

Which is perfectly fine. What actually is the use case of not stalling a
process who wants to wait for something?

> When I was testing this with intel I used the fence callback to poke a register in i915, this allowed it to not block until it hits the wait op in the command stream, and even then only if the callback was not called first.
>
> It's documented that the callbacks can be called from any context and will be called with irqs disabled, so nothing scary should be done. The kernel provides enough debug mechanisms to find any violators.
> PROVE_LOCKING and DEBUG_ATOMIC_SLEEP for example.

No thanks, we even abandoned that concept internal in the driver. Please
use the blocking wait functions instead.

Christian.

2013-08-20 13:21:57

by Maarten Lankhorst

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Op 20-08-13 11:51, Christian K?nig schreef:
> Am 20.08.2013 11:36, schrieb Maarten Lankhorst:
> [SNIP]
>
>>>>>> [SNIP]
>>>>>> +/**
>>>>>> + * radeon_fence_enable_signaling - enable signalling on fence
>>>>>> + * @fence: fence
>>>>>> + *
>>>>>> + * This function is called with fence_queue lock held, and adds a callback
>>>>>> + * to fence_queue that checks if this fence is signaled, and if so it
>>>>>> + * signals the fence and removes itself.
>>>>>> + */
>>>>>> +static bool radeon_fence_enable_signaling(struct fence *f)
>>>>>> +{
>>>>>> + struct radeon_fence *fence = to_radeon_fence(f);
>>>>>> +
>>>>>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>>>>>> + !fence->rdev->ddev->irq_enabled)
>>>>>> + return false;
>>>>>> +
>>>>> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
>>>> For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
>>>> and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.
>>> That's a big NAK.
>>>
>>> The fence processing is actually very fine tuned to avoid IRQs and as far as I can see you just leave them enabled by decrementing the atomic from IRQ context. Additional to that we need allot of special handling in case of a hardware lockup here, which isn't done if you abuse the fence interface like this.
>> I think it's not needed to leave the irq enabled, it's a leftover from when I was debugging the mac and no interrupt occurred at all.
>>
>>> Also your approach of leaking the IRQ context outside of the driver is a very bad idea from the conceptual side. Please don't modify the fence interface at all and instead use the wait functions already exposed by radeon_fence.c. If you need some kind of signaling mechanism then wait inside a workqueue instead.
>> The fence takes up the role of a single shot workqueue here. Manually resetting the counter and calling wake_up_all would end up waking all active fences, there's no special handling needed inside radeon for this.
>
> Yeah that's actually the point here, you NEED to activate ALL fences, otherwise the fence handling inside the driver won't work.
It's done in a lazy fashion. If there's no need for an activated fence the interrupt will not be enabled.
>> The fence api does provide a synchronous wait function, but this causes a stall of whomever waits on it.
>
> Which is perfectly fine. What actually is the use case of not stalling a process who wants to wait for something?
Does radeon call ttm_bo_wait on all bo's before doing a command submission? No? Why should other drivers do that..

>> When I was testing this with intel I used the fence callback to poke a register in i915, this allowed it to not block until it hits the wait op in the command stream, and even then only if the callback was not called first.
>>
>> It's documented that the callbacks can be called from any context and will be called with irqs disabled, so nothing scary should be done. The kernel provides enough debug mechanisms to find any violators.
>> PROVE_LOCKING and DEBUG_ATOMIC_SLEEP for example.
>
> No thanks, we even abandoned that concept internal in the driver. Please use the blocking wait functions instead.
No, this just stalls all gpu's that share a bo.

The idea is to provide a standardized api so bo's can be synchronized without stalling. The first step to this is ww_mutex.
If this lock is shared between multiple gpu's the same object can be reserved between multiple devices without causing
a deadlock with circular dependencies. With some small patches it's possible to do this already between multiple drivers
that use ttm. ttm_bo_reserve, ttm_bo_unreserve and all the other code dealing with ttm reservations have been converted
to use ww_mutex locking.

Fencing is the next step. When all buffers are locked a callback should be added to any previous fence, and a single new fence
signaling completion of the command submission should be placed on all locked objects. Because the common path is that no
objects are shared, the callback and FIFO stalling will only be needed for dma-bufs. When all callbacks have fired the FIFO can be
unblocked. This prevents having to sync the gpu to the cpu. If a bo is submitted to 1 gpu, and then immediately to another it will not
stall unless needed. For example in a optimus configuration an application could copy a rendered frame from VRAM to a shared
dma-buf (xorg's buffer), then have Xorg copying it again (on intel's gpu) from the dma-buf to a framebuffer .

~Maarten

2013-08-20 14:16:54

by Christian König

[permalink] [raw]
Subject: Re: [RFC PATCH] drm/radeon: rework to new fence interface

Am 20.08.2013 15:21, schrieb Maarten Lankhorst:
> Op 20-08-13 11:51, Christian K?nig schreef:
>> Am 20.08.2013 11:36, schrieb Maarten Lankhorst:
>> [SNIP]
>>
>>>>>>> [SNIP]
>>>>>>> +/**
>>>>>>> + * radeon_fence_enable_signaling - enable signalling on fence
>>>>>>> + * @fence: fence
>>>>>>> + *
>>>>>>> + * This function is called with fence_queue lock held, and adds a callback
>>>>>>> + * to fence_queue that checks if this fence is signaled, and if so it
>>>>>>> + * signals the fence and removes itself.
>>>>>>> + */
>>>>>>> +static bool radeon_fence_enable_signaling(struct fence *f)
>>>>>>> +{
>>>>>>> + struct radeon_fence *fence = to_radeon_fence(f);
>>>>>>> +
>>>>>>> + if (atomic64_read(&fence->rdev->fence_drv[fence->ring].last_seq) >= fence->seq ||
>>>>>>> + !fence->rdev->ddev->irq_enabled)
>>>>>>> + return false;
>>>>>>> +
>>>>>> Do I get that right that you rely on IRQs to be enabled and working here? Cause that would be a quite bad idea from the conceptual side.
>>>>> For cross-device synchronization it would be nice to have working irqs, it allows signalling fences faster,
>>>>> and it allows for callbacks on completion to be called. For internal usage it's no more required than it was before.
>>>> That's a big NAK.
>>>>
>>>> The fence processing is actually very fine tuned to avoid IRQs and as far as I can see you just leave them enabled by decrementing the atomic from IRQ context. Additional to that we need allot of special handling in case of a hardware lockup here, which isn't done if you abuse the fence interface like this.
>>> I think it's not needed to leave the irq enabled, it's a leftover from when I was debugging the mac and no interrupt occurred at all.
>>>
>>>> Also your approach of leaking the IRQ context outside of the driver is a very bad idea from the conceptual side. Please don't modify the fence interface at all and instead use the wait functions already exposed by radeon_fence.c. If you need some kind of signaling mechanism then wait inside a workqueue instead.
>>> The fence takes up the role of a single shot workqueue here. Manually resetting the counter and calling wake_up_all would end up waking all active fences, there's no special handling needed inside radeon for this.
>> Yeah that's actually the point here, you NEED to activate ALL fences, otherwise the fence handling inside the driver won't work.
> It's done in a lazy fashion. If there's no need for an activated fence the interrupt will not be enabled.
>>> The fence api does provide a synchronous wait function, but this causes a stall of whomever waits on it.
>> Which is perfectly fine. What actually is the use case of not stalling a process who wants to wait for something?
> Does radeon call ttm_bo_wait on all bo's before doing a command submission? No? Why should other drivers do that..

Sure it does if hardware synchronization isn't supported.

>>> When I was testing this with intel I used the fence callback to poke a register in i915, this allowed it to not block until it hits the wait op in the command stream, and even then only if the callback was not called first.
>>>
>>> It's documented that the callbacks can be called from any context and will be called with irqs disabled, so nothing scary should be done. The kernel provides enough debug mechanisms to find any violators.
>>> PROVE_LOCKING and DEBUG_ATOMIC_SLEEP for example.
>> No thanks, we even abandoned that concept internal in the driver. Please use the blocking wait functions instead.
> No, this just stalls all gpu's that share a bo.
>
> The idea is to provide a standardized api so bo's can be synchronized without stalling. The first step to this is ww_mutex.
> If this lock is shared between multiple gpu's the same object can be reserved between multiple devices without causing
> a deadlock with circular dependencies. With some small patches it's possible to do this already between multiple drivers
> that use ttm. ttm_bo_reserve, ttm_bo_unreserve and all the other code dealing with ttm reservations have been converted
> to use ww_mutex locking.
>
> Fencing is the next step. When all buffers are locked a callback should be added to any previous fence, and a single new fence
> signaling completion of the command submission should be placed on all locked objects. Because the common path is that no
> objects are shared, the callback and FIFO stalling will only be needed for dma-bufs. When all callbacks have fired the FIFO can be
> unblocked. This prevents having to sync the gpu to the cpu. If a bo is submitted to 1 gpu, and then immediately to another it will not
> stall unless needed. For example in a optimus configuration an application could copy a rendered frame from VRAM to a shared
> dma-buf (xorg's buffer), then have Xorg copying it again (on intel's gpu) from the dma-buf to a framebuffer .

Yeah, that's the same concept we used to have for multiple rings, to
avoid stalling if a buffer is currently used in a different part of the
GPU than the current command submission wants it to. After allot of
internal discussion we came to the conclusion that it just doesn't worth
the effort.

Have you thought over all the consequences that are implied by having a
serialized stream of command submissions?

When a buffer is in use by another device you basically have only two
options: Either block for the buffer to become unused or use a hardware
method (semaphores) to sync up your operations. Of course you can
optimize by using a multiple reader / single writers model, but for this
you basically just have to teach ttm that there might be more than one
fence for each bo.

Anyway those things are only optimizations, and in the end it always
comes down to blocking the command submission if there is no other way
of hardware synchronization and I absolutely don't see any reason why we
shouldn't do this with just a normal blocking call to a waitqueue.

Christian.