oh, btw, this should be an [RFC] On Wed, Jul 11, 2012 at 5:29 PM, Rob Clark <rob.clark@xxxxxxxxxx> wrote: > From: Rob Clark <rob@xxxxxx> > > A dma-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 dma-fence is transient, one-shot deal. It is allocated and attached > to dma-buf's list of fences. When the one that attached it is done, > with the pending operation, it can signal the fence removing it from the > dma-buf's list of fences: > > + dma_buf_attach_fence() > + dma_fence_signal() > > Other drivers can access the current fence on the dma-buf (if any), > which increment's the fences refcnt: > > + dma_buf_get_fence() > + dma_fence_put() > > The one pending on the fence can add an async callback (and optionally > cancel it.. for example, to recover from GPU hangs): > > + dma_fence_add_callback() > + dma_fence_cancel_callback() > > Or wait synchronously (optionally with timeout or from atomic context): > > + dma_fence_wait() > > 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 = dma_buf_get_fence(dmabuf); > if (fence->ops == &mem_dma_fence_ops) { > dma_buf *fence_buf; > mem_dma_fence_get_buf(fence, &fence_buf, &offset); > ... tell the hw the memory location to wait on ... > } else { > /* fall-back to sw sync * / > dma_fence_add_callback(fence, my_cb); > } > > The memory location is itself backed by dma-buf, to simplify mapping > to the device's address space, an idea borrowed from Maarten Lankhorst. > > NOTE: the memory location fence is not implemented yet, the above is > just for explaining how it would work. > > 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. > > The other non-sw implementations would wrap the add/cancel_callback and > wait fence ops, so that they can keep track if a device not supporting > hw sync is waiting on the fence, and in this case should arrange to > call dma_fence_signal() at some point after the condition has changed, > to notify other devices waiting on the fence. If there are no sw > waiters, this can be skipped to avoid waking the CPU unnecessarily. > > 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). > --- > drivers/base/Makefile | 2 +- > drivers/base/dma-buf.c | 3 + > drivers/base/dma-fence.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ > include/linux/dma-buf.h | 3 + > include/linux/dma-fence.h | 118 ++++++++++++++++ > 5 files changed, 450 insertions(+), 1 deletion(-) > create mode 100644 drivers/base/dma-fence.c > create mode 100644 include/linux/dma-fence.h > > diff --git a/drivers/base/Makefile b/drivers/base/Makefile > index 5aa2d70..6e9f217 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 > +obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf.o dma-fence.o > obj-$(CONFIG_ISA) += isa.o > obj-$(CONFIG_FW_LOADER) += firmware_class.o > obj-$(CONFIG_NUMA) += node.o > diff --git a/drivers/base/dma-buf.c b/drivers/base/dma-buf.c > index 24e88fe..b053236 100644 > --- a/drivers/base/dma-buf.c > +++ b/drivers/base/dma-buf.c > @@ -39,6 +39,8 @@ static int dma_buf_release(struct inode *inode, struct file *file) > > dmabuf = file->private_data; > > + WARN_ON(!list_empty(&dmabuf->fence_list)); > + > dmabuf->ops->release(dmabuf); > kfree(dmabuf); > return 0; > @@ -119,6 +121,7 @@ struct dma_buf *dma_buf_export(void *priv, const struct dma_buf_ops *ops, > > mutex_init(&dmabuf->lock); > INIT_LIST_HEAD(&dmabuf->attachments); > + INIT_LIST_HEAD(&dmabuf->fence_list); > > return dmabuf; > } > diff --git a/drivers/base/dma-fence.c b/drivers/base/dma-fence.c > new file mode 100644 > index 0000000..a94ed01 > --- /dev/null > +++ b/drivers/base/dma-fence.c > @@ -0,0 +1,325 @@ > +/* > + * Fence mechanism for dma-buf to allow for asynchronous dma access > + * > + * Copyright (C) 2012 Texas Instruments > + * Author: Rob Clark <rob.clark@xxxxxxxxxx> > + * > + * 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/sched.h> > +#include <linux/export.h> > +#include <linux/dma-fence.h> > + > +static DEFINE_SPINLOCK(fence_lock); > + > +/** > + * dma_buf_attach_fence - Attach a fence to a dma-buf. > + * > + * @buf: the dma-buf to attach to > + * @fence: the fence to attach > + * > + * A fence can only be attached to a single dma-buf. The dma-buf takes > + * ownership of the fence, which is unref'd when the fence is signaled. > + * The fence takes a reference to the dma-buf so the buffer will not be > + * freed while there is a pending fence. > + */ > +int dma_buf_attach_fence(struct dma_buf *buf, struct dma_fence *fence) > +{ > + if (WARN_ON(!buf || !fence || fence->buf)) > + return -EINVAL; > + > + spin_lock(&fence_lock); > + get_dma_buf(buf); > + fence->buf = buf; > + list_add(&fence->list_node, &buf->fence_list); > + spin_unlock(&fence_lock); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(dma_buf_attach_fence); > + > +/** > + * dma_fence_signal - Signal a fence. > + * > + * @fence: The fence to signal > + * > + * All registered callbacks will be called directly (synchronously) and > + * all blocked waters will be awoken. > + */ > +int dma_fence_signal(struct dma_fence *fence) > +{ > + unsigned long flags; > + int ret; > + > + if (WARN_ON(!fence || !fence->buf)) > + return -EINVAL; > + > + spin_lock_irqsave(&fence_lock, flags); > + list_del(&fence->list_node); > + spin_unlock_irqrestore(&fence_lock, flags); > + > + dma_buf_put(fence->buf); > + fence->buf = NULL; > + > + ret = fence->ops->signal(fence); > + > + dma_fence_put(fence); > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(dma_fence_signal); > + > +/** > + * dma_buf_get_fence - Get the most recent pending fence attached to the > + * dma-buf. > + * > + * @buf: the dma-buf whose fence to get > + * > + * If this returns NULL, there are no pending fences. Otherwise this > + * takes a reference to the returned fence, so the caller must later > + * call dma_fence_put() to release the reference. > + */ > +struct dma_fence *dma_buf_get_fence(struct dma_buf *buf) > +{ > + struct dma_fence *fence = NULL; > + > + if (WARN_ON(!buf)) > + return ERR_PTR(-EINVAL); > + > + spin_lock(&fence_lock); > + if (!list_empty(&buf->fence_list)) { > + fence = list_first_entry(&buf->fence_list, > + struct dma_fence, list_node); > + dma_fence_get(fence); > + } > + spin_unlock(&fence_lock); > + > + return fence; > +} > +EXPORT_SYMBOL_GPL(dma_buf_get_fence); > + > +static void release_fence(struct kref *kref) > +{ > + struct dma_fence *fence = > + container_of(kref, struct dma_fence, refcount); > + > + WARN_ON(waitqueue_active(&fence->event_queue) || fence->buf); > + > + kfree(fence); > +} > + > +/** > + * dma_fence_put - Release a reference to the fence. > + */ > +void dma_fence_put(struct dma_fence *fence) > +{ > + WARN_ON(!fence); > + kref_put(&fence->refcount, release_fence); > +} > +EXPORT_SYMBOL_GPL(dma_fence_put); > + > +/** > + * dma_fence_get - Take a reference to the fence. > + * > + * In most cases this is used only internally by dma-fence. > + */ > +void dma_fence_get(struct dma_fence *fence) > +{ > + WARN_ON(!fence); > + kref_get(&fence->refcount); > +} > +EXPORT_SYMBOL_GPL(dma_fence_get); > + > +/** > + * dma_fence_add_callback - Add a callback to be called when the fence > + * is signaled. > + * > + * @fence: The fence to wait on > + * @cb: The callback to register > + * > + * Any number of callbacks can be registered to a fence, but a callback > + * can only be registered to once fence at a time. > + * > + * Note that the callback can be called from an atomic context. If > + * fence is already signaled, this function will return -EINVAL (and > + * *not* call the callback) > + */ > +int dma_fence_add_callback(struct dma_fence *fence, > + struct dma_fence_cb *cb) > +{ > + int ret = -EINVAL; > + > + if (WARN_ON(!fence || !cb)) > + return -EINVAL; > + > + spin_lock(&fence_lock); > + if (fence->buf) { > + dma_fence_get(fence); > + cb->fence = fence; > + ret = fence->ops->add_callback(fence, cb); > + } > + spin_unlock(&fence_lock); > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(dma_fence_add_callback); > + > +/** > + * dma_fence_cancel_callback - Remove a previously registered callback. > + * > + * @cb: The callback to unregister > + * > + * The callback will not be called after this function returns, but could > + * be called before this function returns. > + */ > +int dma_fence_cancel_callback(struct dma_fence_cb *cb) > +{ > + struct dma_fence *fence; > + int ret = -EINVAL; > + > + if (WARN_ON(!cb)) > + return -EINVAL; > + > + fence = cb->fence; > + > + spin_lock(&fence_lock); > + if (fence) { > + ret = fence->ops->cancel_callback(cb); > + cb->fence = NULL; > + dma_fence_put(fence); > + } > + spin_unlock(&fence_lock); > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(dma_fence_cancel_callback); > + > +/** > + * dma_fence_wait - Wait for a fence to be signaled. > + * > + * @fence: The fence to wait on > + * @interruptible: if true, do an interruptible wait > + * @timeout: timeout, in jiffies > + */ > +int dma_fence_wait(struct dma_fence *fence, bool interruptible, long timeout) > +{ > + if (WARN_ON(!fence)) > + return -EINVAL; > + > + return fence->ops->wait(fence, interruptible, timeout); > +} > +EXPORT_SYMBOL_GPL(dma_fence_wait); > + > +int __dma_fence_wake_func(wait_queue_t *wait, unsigned mode, > + int flags, void *key) > +{ > + struct dma_fence_cb *cb = > + container_of(wait, struct dma_fence_cb, base); > + int ret; > + > + ret = cb->func(cb, cb->fence); > + dma_fence_put(cb->fence); > + cb->fence = NULL; > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(__dma_fence_wake_func); > + > +/* > + * Helpers intended to be used by the ops of the dma_fence implementation: > + * > + * NOTE: helpers and fxns intended to be used by other dma-fence > + * implementations are not exported.. I'm not really sure if it makes > + * sense to have a dma-fence implementation that is itself a module. > + */ > + > +void __dma_fence_init(struct dma_fence *fence, struct dma_fence_ops *ops) > +{ > + WARN_ON(!ops || !ops->signal || !ops->add_callback || > + !ops->cancel_callback || !ops->wait); > + > + kref_init(&fence->refcount); > + fence->ops = ops; > + init_waitqueue_head(&fence->event_queue); > +} > + > +int dma_fence_helper_signal(struct dma_fence *fence) > +{ > + wake_up_all(&fence->event_queue); > + return 0; > +} > + > +int dma_fence_helper_add_callback(struct dma_fence *fence, > + struct dma_fence_cb *cb) > +{ > + add_wait_queue(&fence->event_queue, &cb->base); > + return 0; > +} > + > +int dma_fence_helper_cancel_callback(struct dma_fence_cb *cb) > +{ > + struct dma_fence *fence = cb->fence; > + remove_wait_queue(&fence->event_queue, &cb->base); > + return 0; > +} > + > +int dma_fence_helper_wait(struct dma_fence *fence, bool interruptible, > + long timeout) > +{ > + int ret; > + > + if (interruptible) { > + ret = wait_event_interruptible_timeout(fence->event_queue, > + !fence->buf, timeout); > + } else { > + ret = wait_event_timeout(fence->event_queue, > + !fence->buf, timeout); > + } > + > + return ret; > +} > + > +/* > + * Pure sw implementation for dma-fence. The CPU always gets involved. > + */ > + > +static struct dma_fence_ops sw_fence_ops = { > + .signal = dma_fence_helper_signal, > + .add_callback = dma_fence_helper_add_callback, > + .cancel_callback = dma_fence_helper_cancel_callback, > + .wait = dma_fence_helper_wait, > +}; > + > +/** > + * dma_fence_create - Create a simple sw-only fence. > + * > + * This fence only supports signaling from/to CPU. Other implementations > + * of dma-fence can be used to support hardware to hardware signaling, if > + * supported by the hardware, and use the dma_fence_helper_* functions for > + * compatibility with other devices that only support sw signaling. > + */ > +struct dma_fence *dma_fence_create(void) > +{ > + struct dma_fence *fence; > + > + fence = kzalloc(sizeof(struct dma_fence), GFP_KERNEL); > + if (!fence) > + return ERR_PTR(-ENOMEM); > + > + __dma_fence_init(fence, &sw_fence_ops); > + > + return fence; > +} > +EXPORT_SYMBOL_GPL(dma_fence_create); > diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h > index eb48f38..a0a0b64 100644 > --- a/include/linux/dma-buf.h > +++ b/include/linux/dma-buf.h > @@ -29,6 +29,7 @@ > #include <linux/scatterlist.h> > #include <linux/list.h> > #include <linux/dma-mapping.h> > +#include <linux/dma-fence.h> > #include <linux/fs.h> > > struct device; > @@ -122,6 +123,8 @@ struct dma_buf { > /* mutex to serialize list manipulation and attach/detach */ > struct mutex lock; > void *priv; > + /* list of pending dma_fence's */ > + struct list_head fence_list; > }; > > /** > diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h > new file mode 100644 > index 0000000..ecda334 > --- /dev/null > +++ b/include/linux/dma-fence.h > @@ -0,0 +1,118 @@ > +/* > + * Fence mechanism for dma-buf to allow for asynchronous dma access > + * > + * Copyright (C) 2012 Texas Instruments > + * Author: Rob Clark <rob.clark@xxxxxxxxxx> > + * > + * 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 __DMA_FENCE_H__ > +#define __DMA_FENCE_H__ > + > +#include <linux/err.h> > +#include <linux/list.h> > +#include <linux/wait.h> > +#include <linux/list.h> > +#include <linux/dma-buf.h> > + > +struct dma_fence; > +struct dma_fence_ops; > +struct dma_fence_cb; > + > +struct dma_fence { > + struct kref refcount; > + struct dma_fence_ops *ops; > + wait_queue_head_t event_queue; > + struct list_head list_node; /* for dmabuf's fence_list */ > + > + /* > + * This is initialized when the fence is attached to a dma-buf, > + * and NULL'd before it is signaled. A fence can be attached to > + * at most one dma-buf at a time. > + */ > + struct dma_buf *buf; > +}; > + > +typedef int (*dma_fence_func_t)(struct dma_fence_cb *cb, > + struct dma_fence *fence); > + > +struct dma_fence_ops { > + int (*signal)(struct dma_fence *fence); > + int (*add_callback)(struct dma_fence *fence, struct dma_fence_cb *cb); > + int (*cancel_callback)(struct dma_fence_cb *cb); > + int (*wait)(struct dma_fence *fence, bool interruptible, long timeout); > +}; > + > +struct dma_fence_cb { > + wait_queue_t base; > + dma_fence_func_t func; > + > + /* > + * This is initialized when the cb is added, and NULL'd when it > + * is canceled or expired, so can be used to for error checking > + * if the cb is already pending. A dma_fence_cb can be pending > + * on at most one fence at a time. > + */ > + struct dma_fence *fence; > +}; > + > +int __dma_fence_wake_func(wait_queue_t *wait, unsigned mode, > + int flags, void *key); > + > +#define DMA_FENCE_CB_INITIALIZER(cb_func) { \ > + .base = { .func = __dma_fence_wake_func }, \ > + .func = (cb_func), \ > + } > + > +#define DECLARE_DMA_FENCE(name, cb_func) \ > + struct dma_fence_cb name = DMA_FENCE_CB_INITIALIZER(cb_func) > + > + > +/* > + * TODO does it make sense to be able to enable dma-fence without dma-buf, > + * or visa versa? > + */ > +#ifdef CONFIG_DMA_SHARED_BUFFER > + > +int dma_buf_attach_fence(struct dma_buf *buf, struct dma_fence *fence); > +struct dma_fence *dma_buf_get_fence(struct dma_buf *buf); > + > +/* create a basic (pure sw) fence: */ > +struct dma_fence *dma_fence_create(void); > + > +/* intended to be used by other dma_fence implementations: */ > +void __dma_fence_init(struct dma_fence *fence, struct dma_fence_ops *ops); > + > +void dma_fence_get(struct dma_fence *fence); > +void dma_fence_put(struct dma_fence *fence); > +int dma_fence_signal(struct dma_fence *fence); > + > +int dma_fence_add_callback(struct dma_fence *fence, > + struct dma_fence_cb *cb); > +int dma_fence_cancel_callback(struct dma_fence_cb *cb); > +int dma_fence_wait(struct dma_fence *fence, bool interruptible, long timeout); > + > +/* helpers intended to be used by the ops of the dma_fence implementation: */ > +int dma_fence_helper_signal(struct dma_fence *fence); > +int dma_fence_helper_add_callback(struct dma_fence *fence, > + struct dma_fence_cb *cb); > +int dma_fence_helper_cancel_callback(struct dma_fence_cb *cb); > +int dma_fence_helper_wait(struct dma_fence *fence, bool interruptible, > + long timeout); > + > +#else > +// TODO > +#endif /* CONFIG_DMA_SHARED_BUFFER */ > + > +#endif /* __DMA_FENCE_H__ */ > -- > 1.7.9.5 > _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel