Add posibility to register callback on dma-buffer which is called before dma_buf->ops->release call. This helps when external user of the dma buffer should be notified before buffer releases without changing dma-buf ops. This is needed when external dma buffer is used as backing storage for gntdev refs export and grant refs should be unmapped before dma buffer release. Signed-off-by: Oleksii Moisieiev <oleksii_moisieiev@xxxxxxxx> --- drivers/dma-buf/dma-buf.c | 44 +++++++++++++++++++++++++++++++++++++++ include/linux/dma-buf.h | 15 +++++++++++++ 2 files changed, 59 insertions(+) diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index efb4990b29e1..3e663ef92e1f 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -25,6 +25,7 @@ #include <linux/dma-resv.h> #include <linux/mm.h> #include <linux/mount.h> +#include <linux/notifier.h> #include <linux/pseudo_fs.h> #include <uapi/linux/dma-buf.h> @@ -57,6 +58,46 @@ static char *dmabuffs_dname(struct dentry *dentry, char *buffer, int buflen) dentry->d_name.name, ret > 0 ? name : ""); } +int dma_buf_register_release_notifier(struct dma_buf *dmabuf, + ext_release_notifier_cb ext_release_cb, void *priv) +{ + int ret = 0; + + spin_lock(&dmabuf->ext_release_lock); + + if (dmabuf->ext_release_cb) { + ret = -EEXIST; + goto unlock; + } + + dmabuf->ext_release_cb = ext_release_cb; + dmabuf->ext_release_priv = priv; + unlock: + spin_unlock(&dmabuf->ext_release_lock); + return ret; +} +EXPORT_SYMBOL_GPL(dma_buf_register_release_notifier); + +void dma_buf_unregister_release_notifier(struct dma_buf *dmabuf) +{ + spin_lock(&dmabuf->ext_release_lock); + dmabuf->ext_release_cb = NULL; + spin_unlock(&dmabuf->ext_release_lock); +} +EXPORT_SYMBOL_GPL(dma_buf_unregister_release_notifier); + +static void dma_buf_call_release_notifier(struct dma_buf *dmabuf) +{ + if (!dmabuf->ext_release_cb) + return; + + spin_lock(&dmabuf->ext_release_lock); + dmabuf->ext_release_cb(dmabuf, dmabuf->ext_release_priv); + spin_unlock(&dmabuf->ext_release_lock); + + dma_buf_unregister_release_notifier(dmabuf); +} + static void dma_buf_release(struct dentry *dentry) { struct dma_buf *dmabuf; @@ -75,6 +116,8 @@ static void dma_buf_release(struct dentry *dentry) BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active); dma_buf_stats_teardown(dmabuf); + dma_buf_call_release_notifier(dmabuf); + dmabuf->ops->release(dmabuf); if (dmabuf->resv == (struct dma_resv *)&dmabuf[1]) @@ -642,6 +685,7 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info) init_waitqueue_head(&dmabuf->poll); dmabuf->cb_in.poll = dmabuf->cb_out.poll = &dmabuf->poll; dmabuf->cb_in.active = dmabuf->cb_out.active = 0; + spin_lock_init(&dmabuf->ext_release_lock); if (!resv) { resv = (struct dma_resv *)&dmabuf[1]; diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index 71731796c8c3..6282d56ac040 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -287,6 +287,8 @@ struct dma_buf_ops { void (*vunmap)(struct dma_buf *dmabuf, struct iosys_map *map); }; +typedef void (*ext_release_notifier_cb)(struct dma_buf *dmabuf, void *priv); + /** * struct dma_buf - shared buffer object * @@ -432,6 +434,15 @@ struct dma_buf { */ struct dma_resv *resv; + /** @ext_release_cb notififier callback to call on release */ + ext_release_notifier_cb ext_release_cb; + + /** @ext_release_priv private data for callback */ + void *ext_release_priv; + + /** @ext_release_lock spinlock for ext_notifier helper */ + spinlock_t ext_release_lock; + /** @poll: for userspace poll support */ wait_queue_head_t poll; @@ -632,4 +643,8 @@ int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned long); int dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map); void dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map); + +int dma_buf_register_release_notifier(struct dma_buf *dmabuf, + ext_release_notifier_cb ext_release_cb, void *priv); +void dma_buf_unregister_release_notifier(struct dma_buf *dmabuf); #endif /* __DMA_BUF_H__ */ -- 2.25.1