The fence ops of a dma_fence currently need to life as long as the dma_fence is alive. This means that the module which originally issued a dma_fence can't unload unless all fences are freed up.
As first step to solve this issue protect the fence ops by RCU.
While it is counter intuitive to protect a constant function pointer table by RCU it allows modules to wait for an RCU grace period before they unload, to make sure that nobody is executing their functions any more.
This patch has not much functional change, but only adds the RCU handling for the static checker to test.
v2: make one the now duplicated lockdep warnings a comment instead. v3: Add more documentation to ->wait and ->release callback. v4: fix typo in documentation v5: rebased on drm-tip v6: improve code comments v7: improve commit message and code comments
Signed-off-by: Christian König christian.koenig@amd.com Reviewed-by: Tvrtko Ursulin tvrtko.ursulin@igalia.com Reviewed-by: Boris Brezillon boris.brezillon@collabora.com --- drivers/dma-buf/dma-fence.c | 71 +++++++++++++++++++++++++------------ include/linux/dma-fence.h | 29 +++++++++++++-- 2 files changed, 75 insertions(+), 25 deletions(-)
diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index e05beae6e407..d3c4e23bf297 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -522,6 +522,7 @@ EXPORT_SYMBOL(dma_fence_signal); signed long dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout) { + const struct dma_fence_ops *ops; signed long ret;
if (WARN_ON(timeout < 0)) @@ -533,15 +534,22 @@ dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)
dma_fence_enable_sw_signaling(fence);
- if (trace_dma_fence_wait_start_enabled()) { - rcu_read_lock(); - trace_dma_fence_wait_start(fence); + rcu_read_lock(); + ops = rcu_dereference(fence->ops); + trace_dma_fence_wait_start(fence); + if (ops->wait) { + /* + * Implementing the wait ops is deprecated and not supported for + * issuers of fences who need their lifetime to be independent + * of their module after they signal, so it is ok to use the + * ops outside the RCU protected section. + */ + rcu_read_unlock(); + ret = ops->wait(fence, intr, timeout); + } else { rcu_read_unlock(); - } - if (fence->ops->wait) - ret = fence->ops->wait(fence, intr, timeout); - else ret = dma_fence_default_wait(fence, intr, timeout); + } if (trace_dma_fence_wait_end_enabled()) { rcu_read_lock(); trace_dma_fence_wait_end(fence); @@ -562,6 +570,7 @@ void dma_fence_release(struct kref *kref) { struct dma_fence *fence = container_of(kref, struct dma_fence, refcount); + const struct dma_fence_ops *ops;
rcu_read_lock(); trace_dma_fence_destroy(fence); @@ -593,12 +602,12 @@ void dma_fence_release(struct kref *kref) spin_unlock_irqrestore(fence->lock, flags); }
- rcu_read_unlock(); - - if (fence->ops->release) - fence->ops->release(fence); + ops = rcu_dereference(fence->ops); + if (ops->release) + ops->release(fence); else dma_fence_free(fence); + rcu_read_unlock(); } EXPORT_SYMBOL(dma_fence_release);
@@ -617,6 +626,7 @@ EXPORT_SYMBOL(dma_fence_free);
static bool __dma_fence_enable_signaling(struct dma_fence *fence) { + const struct dma_fence_ops *ops; bool was_set;
lockdep_assert_held(fence->lock); @@ -627,14 +637,18 @@ static bool __dma_fence_enable_signaling(struct dma_fence *fence) if (dma_fence_test_signaled_flag(fence)) return false;
- if (!was_set && fence->ops->enable_signaling) { + rcu_read_lock(); + ops = rcu_dereference(fence->ops); + if (!was_set && ops->enable_signaling) { trace_dma_fence_enable_signal(fence);
- if (!fence->ops->enable_signaling(fence)) { + if (!ops->enable_signaling(fence)) { + rcu_read_unlock(); dma_fence_signal_locked(fence); return false; } } + rcu_read_unlock();
return true; } @@ -1007,8 +1021,13 @@ EXPORT_SYMBOL(dma_fence_wait_any_timeout); */ void dma_fence_set_deadline(struct dma_fence *fence, ktime_t deadline) { - if (fence->ops->set_deadline && !dma_fence_is_signaled(fence)) - fence->ops->set_deadline(fence, deadline); + const struct dma_fence_ops *ops; + + rcu_read_lock(); + ops = rcu_dereference(fence->ops); + if (ops->set_deadline && !dma_fence_is_signaled(fence)) + ops->set_deadline(fence, deadline); + rcu_read_unlock(); } EXPORT_SYMBOL(dma_fence_set_deadline);
@@ -1049,7 +1068,13 @@ __dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops, BUG_ON(!ops || !ops->get_driver_name || !ops->get_timeline_name);
kref_init(&fence->refcount); - fence->ops = ops; + /* + * While it is counter intuitive to protect a constant function pointer + * table by RCU it allows modules to wait for an RCU grace period + * before they unload, to make sure that nobody is executing their + * functions any more. + */ + RCU_INIT_POINTER(fence->ops, ops); INIT_LIST_HEAD(&fence->cb_list); fence->lock = lock; fence->context = context; @@ -1129,11 +1154,12 @@ EXPORT_SYMBOL(dma_fence_init64); */ const char __rcu *dma_fence_driver_name(struct dma_fence *fence) { - RCU_LOCKDEP_WARN(!rcu_read_lock_held(), - "RCU protection is required for safe access to returned string"); + const struct dma_fence_ops *ops;
+ /* RCU protection is required for safe access to returned string */ + ops = rcu_dereference(fence->ops); if (!dma_fence_test_signaled_flag(fence)) - return (const char __rcu *)fence->ops->get_driver_name(fence); + return (const char __rcu *)ops->get_driver_name(fence); else return (const char __rcu *)"detached-driver"; } @@ -1161,11 +1187,12 @@ EXPORT_SYMBOL(dma_fence_driver_name); */ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence) { - RCU_LOCKDEP_WARN(!rcu_read_lock_held(), - "RCU protection is required for safe access to returned string"); + const struct dma_fence_ops *ops;
+ /* RCU protection is required for safe access to returned string */ + ops = rcu_dereference(fence->ops); if (!dma_fence_test_signaled_flag(fence)) - return (const char __rcu *)fence->ops->get_driver_name(fence); + return (const char __rcu *)ops->get_driver_name(fence); else return (const char __rcu *)"signaled-timeline"; } diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h index 9c4d25289239..9d8a4ebe8bf7 100644 --- a/include/linux/dma-fence.h +++ b/include/linux/dma-fence.h @@ -67,7 +67,7 @@ struct seq_file; */ struct dma_fence { spinlock_t *lock; - const struct dma_fence_ops *ops; + const struct dma_fence_ops __rcu *ops; /* * We clear the callback list on kref_put so that by the time we * release the fence it is unused. No one should be adding to the @@ -220,6 +220,10 @@ struct dma_fence_ops { * 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. + * + * Implementing this callback prevents the fence from detaching after + * signaling and so it is necessary for the module providing the + * dma_fence_ops to stay loaded as long as the dma_fence exists. */ signed long (*wait)(struct dma_fence *fence, bool intr, signed long timeout); @@ -231,6 +235,13 @@ struct dma_fence_ops { * Can be called from irq context. This callback is optional. If it is * NULL, then dma_fence_free() is instead called as the default * implementation. + * + * Implementing this callback prevents the fence from detaching after + * signaling and so it is necessary for the module providing the + * dma_fence_ops to stay loaded as long as the dma_fence exists. + * + * If the callback is implemented the memory backing the dma_fence + * object must be freed RCU safe. */ void (*release)(struct dma_fence *fence);
@@ -454,13 +465,19 @@ dma_fence_test_signaled_flag(struct dma_fence *fence) static inline bool dma_fence_is_signaled_locked(struct dma_fence *fence) { + const struct dma_fence_ops *ops; + if (dma_fence_test_signaled_flag(fence)) return true;
- if (fence->ops->signaled && fence->ops->signaled(fence)) { + rcu_read_lock(); + ops = rcu_dereference(fence->ops); + if (ops->signaled && ops->signaled(fence)) { + rcu_read_unlock(); dma_fence_signal_locked(fence); return true; } + rcu_read_unlock();
return false; } @@ -484,13 +501,19 @@ dma_fence_is_signaled_locked(struct dma_fence *fence) static inline bool dma_fence_is_signaled(struct dma_fence *fence) { + const struct dma_fence_ops *ops; + if (dma_fence_test_signaled_flag(fence)) return true;
- if (fence->ops->signaled && fence->ops->signaled(fence)) { + rcu_read_lock(); + ops = rcu_dereference(fence->ops); + if (ops->signaled && ops->signaled(fence)) { + rcu_read_unlock(); dma_fence_signal(fence); return true; } + rcu_read_unlock();
return false; }
Hi Christian,
kernel test robot noticed the following build warnings:
[auto build test WARNING on drm-misc/drm-misc-next] [cannot apply to drm-i915/for-linux-next drm-i915/for-linux-next-fixes drm-xe/drm-xe-next linus/master v6.19 next-20260220] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Christian-K-nig/dma-buf-detac... base: https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next patch link: https://lore.kernel.org/r/20260219160822.1529-2-christian.koenig%40amd.com patch subject: [PATCH 1/8] dma-buf: protected fence ops by RCU v7 config: hexagon-randconfig-r121-20260221 (https://download.01.org/0day-ci/archive/20260221/202602212248.xPSr8tt8-lkp@i...) compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project e86750b29fa0ff207cd43213d66dabe565417638) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260221/202602212248.xPSr8tt8-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202602212248.xPSr8tt8-lkp@intel.com/
sparse warnings: (new ones prefixed by >>) drivers/dma-buf/dma-fence-array.c: note: in included file (through include/linux/dma-fence-array.h):
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const * include/linux/dma-fence.h:732:27: sparse: sparse: incompatible types in comparison expression (different address spaces): include/linux/dma-fence.h:732:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:732:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const *
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const * -- drivers/dma-buf/dma-resv.c: note: in included file (through include/linux/dma-resv.h):
include/linux/dma-fence.h:721:27: sparse: sparse: incompatible types in comparison expression (different address spaces):
include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:721:27: sparse: struct dma_fence_ops const * include/linux/dma-fence.h:732:27: sparse: sparse: incompatible types in comparison expression (different address spaces): include/linux/dma-fence.h:732:27: sparse: struct dma_fence_ops const [noderef] __rcu * include/linux/dma-fence.h:732:27: sparse: struct dma_fence_ops const * -- drivers/dma-buf/dma-fence.c:1043:38: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected char const [noderef] __rcu *timeline @@ got char * @@ drivers/dma-buf/dma-fence.c:1043:38: sparse: expected char const [noderef] __rcu *timeline drivers/dma-buf/dma-fence.c:1043:38: sparse: got char * drivers/dma-buf/dma-fence.c:1044:36: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected char const [noderef] __rcu *driver @@ got char * @@ drivers/dma-buf/dma-fence.c:1044:36: sparse: expected char const [noderef] __rcu *driver drivers/dma-buf/dma-fence.c:1044:36: sparse: got char * drivers/dma-buf/dma-fence.c: note: in included file (through include/trace/trace_events.h, include/trace/define_trace.h, include/trace/events/dma_fence.h):
include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression include/trace/events/dma_fence.h:17:1: sparse: sparse: dereference of noderef expression
vim +721 include/linux/dma-fence.h
976b6d97c62347 Christian König 2022-01-19 712 976b6d97c62347 Christian König 2022-01-19 713 /** 976b6d97c62347 Christian König 2022-01-19 714 * dma_fence_is_array - check if a fence is from the array subclass 976b6d97c62347 Christian König 2022-01-19 715 * @fence: the fence to test 976b6d97c62347 Christian König 2022-01-19 716 * 976b6d97c62347 Christian König 2022-01-19 717 * Return true if it is a dma_fence_array and false otherwise. 976b6d97c62347 Christian König 2022-01-19 718 */ 976b6d97c62347 Christian König 2022-01-19 719 static inline bool dma_fence_is_array(struct dma_fence *fence) 976b6d97c62347 Christian König 2022-01-19 720 { 976b6d97c62347 Christian König 2022-01-19 @721 return fence->ops == &dma_fence_array_ops; 976b6d97c62347 Christian König 2022-01-19 722 } 976b6d97c62347 Christian König 2022-01-19 723
Hi Christian,
kernel test robot noticed the following build warnings:
[auto build test WARNING on drm-misc/drm-misc-next] [cannot apply to drm-i915/for-linux-next drm-i915/for-linux-next-fixes drm-xe/drm-xe-next linus/master v6.19 next-20260220] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Christian-K-nig/dma-buf-detac... base: https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next patch link: https://lore.kernel.org/r/20260219160822.1529-2-christian.koenig%40amd.com patch subject: [PATCH 1/8] dma-buf: protected fence ops by RCU v7 config: m68k-randconfig-r111-20260221 (https://download.01.org/0day-ci/archive/20260221/202602212224.vH1Jr91w-lkp@i...) compiler: m68k-linux-gcc (GCC) 8.5.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260221/202602212224.vH1Jr91w-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202602212224.vH1Jr91w-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
drivers/gpu/drm/drm_crtc.c:161:9: sparse: sparse: incompatible types in comparison expression (different address spaces):
drivers/gpu/drm/drm_crtc.c:161:9: sparse: struct dma_fence_ops const [noderef] __rcu * drivers/gpu/drm/drm_crtc.c:161:9: sparse: struct dma_fence_ops const * -- drivers/gpu/drm/scheduler/sched_fence.c:241:1: sparse: sparse: bad integer constant expression drivers/gpu/drm/scheduler/sched_fence.c:241:1: sparse: sparse: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte" drivers/gpu/drm/scheduler/sched_fence.c:242:1: sparse: sparse: bad integer constant expression drivers/gpu/drm/scheduler/sched_fence.c:242:1: sparse: sparse: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
drivers/gpu/drm/scheduler/sched_fence.c:198:20: sparse: sparse: incompatible types in comparison expression (different address spaces):
drivers/gpu/drm/scheduler/sched_fence.c:198:20: sparse: struct dma_fence_ops const [noderef] __rcu * drivers/gpu/drm/scheduler/sched_fence.c:198:20: sparse: struct dma_fence_ops const * drivers/gpu/drm/scheduler/sched_fence.c:201:20: sparse: sparse: incompatible types in comparison expression (different address spaces): drivers/gpu/drm/scheduler/sched_fence.c:201:20: sparse: struct dma_fence_ops const [noderef] __rcu * drivers/gpu/drm/scheduler/sched_fence.c:201:20: sparse: struct dma_fence_ops const *
vim +161 drivers/gpu/drm/drm_crtc.c
35f8cc3b9a92c6 Gustavo Padovan 2016-12-06 158 6d6003c4b613c9 Gustavo Padovan 2016-11-15 159 static struct drm_crtc *fence_to_crtc(struct dma_fence *fence) 6d6003c4b613c9 Gustavo Padovan 2016-11-15 160 { 6d6003c4b613c9 Gustavo Padovan 2016-11-15 @161 BUG_ON(fence->ops != &drm_crtc_fence_ops); 6d6003c4b613c9 Gustavo Padovan 2016-11-15 162 return container_of(fence->lock, struct drm_crtc, fence_lock); 6d6003c4b613c9 Gustavo Padovan 2016-11-15 163 } 6d6003c4b613c9 Gustavo Padovan 2016-11-15 164
linaro-mm-sig@lists.linaro.org