workqueue: use irqsafe timer in delayed_work
Subsystems (19)
Tejun Heo
maintainerPATCH 1/1 workqueue: use irqsafe timer in delayed_work
Hello,
Because IRQs can happen between delayed_work->timer being dispatched and delayed_work_timer_fn() actually queueing delayed_work->work, try_to_grab_pending() couldn't be used from IRQ handlers. If it hits the window, it will return -EAGAIN perpetually. This makes it impossible to steal PENDING from IRQ handlers using try_to_grab_pending() leading to the following issues.
* mod_delayed_work() can't be used from IRQ handlers.
* __cancel_delayed_work() can't use the usual try_to_grab_pending() which handles all three states but instead only deals with the first state using a separate implementation. There's no way to make a delayed_work not pending from IRQ handlers.
* The context / behavior differences among cancel_delayed_work(), __cancel_delayed_work(), cancel_delayed_work_sync() are subtle and confusing (the first two are mostly historical tho).
This patchset makes delayed_work use the irqsafe timer added by the pending "timer: clean up initializers and implement irqsafe timers" patchset[1]. This enables try_to_grab_pending() to be used from any context which in turn makes mod_delayed_work() usable from IRQ handlers. cancel_delayed_work() is reimplemented using try_to_grab_pending() so that it also can be used from IRQ handlers and its behavior is consitent with other canceling operations. __cancel_delayed_work() is no longer necessary and deprecated.
0001-workqueue-cosmetic-whitespace-updates-for-macro-defi.patch 0002-workqueue-make-deferrable-delayed_work-initializer-n.patch 0003-workqueue-clean-up-delayed_work-initializers-and-add.patch 0004-workqueue-use-irqsafe-timer-for-delayed_work.patch 0005-workqueue-use-mod_delayed_work-instead-of-__cancel-q.patch 0006-workqueue-reimplement-cancel_delayed_work-using-try_.patch 0007-workqueue-deprecate-__cancel_delayed_work.patch
0001-0003 are prep patches.
0004 makes delayed_work use irqsafe timers. This makes try_to_grab_pending() and mod_delayed_work() safe to use from any context.
0005 converts all __cancel_delayed_work() + queue_delayed_work() sequences to mod_delayed_work(). The link_watch conversion needs David's ack.
0006 reimplements cancel_delayed_work() using try_to_grab_pending().
0007 replaces __cancel_delayed_work() calls with cancel_delayed_work() and deprecates the underscored one.
This patchset is on top of
[2] wq/for-3.7 (8fcd63664f "workqueue: fix CPU binding of flush_delayed...")
+ [1] timer: clean up initializers and implement irqsafe timers
and available in the following git branch.
git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git review-delayed_work-irqsafe
diffstat follows.
arch/powerpc/platforms/cell/cpufreq_spudemand.c | 2 block/blk-core.c | 8 - block/blk-throttle.c | 7 - drivers/block/floppy.c | 5 drivers/cpufreq/cpufreq_conservative.c | 2 drivers/cpufreq/cpufreq_ondemand.c | 2 drivers/devfreq/devfreq.c | 2 drivers/infiniband/core/mad.c | 16 -- drivers/input/keyboard/qt2160.c | 3 drivers/input/mouse/synaptics_i2c.c | 7 - drivers/net/ethernet/mellanox/mlx4/sense.c | 2 drivers/power/ab8500_btemp.c | 2 drivers/power/ab8500_charger.c | 8 - drivers/power/ab8500_fg.c | 8 - drivers/power/abx500_chargalg.c | 4 drivers/power/max17040_battery.c | 2 drivers/video/omap2/displays/panel-taal.c | 6 drivers/video/omap2/dss/dsi.c | 6 include/linux/workqueue.h | 155 ++++++++++-------------- kernel/workqueue.c | 50 ++++++- mm/slab.c | 2 mm/vmstat.c | 2 net/core/link_watch.c | 21 --- net/core/neighbour.c | 2 net/ipv4/inetpeer.c | 2 net/sunrpc/cache.c | 2 26 files changed, 159 insertions(+), 169 deletions(-)
--
tejun
[1] http://thread.gmane.org/gmane.linux.kernel/1340224
[2] git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-3.7
Tejun Heo
maintainerPATCH 1/7 workqueue: cosmetic whitespace updates for macro definitions
Consistently use the last tab position for '\' line continuation in complex macro definitions. This is to help the following patches.
This patch is cosmetic.
--- include/linux/workqueue.h | 126 ++++++++++++++++++++++---------------------- 1 files changed, 63 insertions(+), 63 deletions(-)
include/linux/workqueue.h +63 -63
@@ -126,43 +126,43 @@ struct execute_work {#define __WORK_INIT_LOCKDEP_MAP (n, k)#endif -#define __WORK_INITIALIZER (n, f) { \ - .data = WORK_DATA_STATIC_INIT(), \ - .entry = { &(n).entry, &(n).entry }, \ - .func = (f), \ - __WORK_INIT_LOCKDEP_MAP(#n, &(n)) \ +#define __WORK_INITIALIZER (n, f) { \ + .data = WORK_DATA_STATIC_INIT(), \ + .entry = { &(n).entry, &(n).entry }, \ + .func = (f), \ + __WORK_INIT_LOCKDEP_MAP(#n, &(n)) \ } -#define __DELAYED_WORK_INITIALIZER (n, f) { \ - .work = __WORK_INITIALIZER((n).work, (f)), \ - .timer = TIMER_INITIALIZER(delayed_work_timer_fn, \ - 0, (unsigned long)&(n)), \ +#define __DELAYED_WORK_INITIALIZER (n, f) { \ + .work = __WORK_INITIALIZER((n).work, (f)), \ + .timer = TIMER_INITIALIZER(delayed_work_timer_fn, \ + 0, (unsigned long)&(n)), \ } -#define __DEFERRED_WORK_INITIALIZER (n, f) { \ - .work = __WORK_INITIALIZER((n).work, (f)), \ - .timer = TIMER_DEFERRED_INITIALIZER(delayed_work_timer_fn, \ - 0, (unsigned long)&(n)), \ +#define __DEFERRED_WORK_INITIALIZER (n, f) { \ + .work = __WORK_INITIALIZER((n).work, (f)), \ + .timer = TIMER_DEFERRED_INITIALIZER(delayed_work_timer_fn, \ + 0, (unsigned long)&(n)), \ } -#define DECLARE_WORK (n, f) \ +#define DECLARE_WORK (n, f) \ struct work_struct n = __WORK_INITIALIZER(n, f) -#define DECLARE_DELAYED_WORK (n, f) \ +#define DECLARE_DELAYED_WORK (n, f) \ struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f) -#define DECLARE_DEFERRED_WORK (n, f) \ +#define DECLARE_DEFERRED_WORK (n, f) \ struct delayed_work n = __DEFERRED_WORK_INITIALIZER(n, f)/* * initialize a work item's function pointer */ -#define PREPARE_WORK (_work, _func) \ - do { \ - (_work)->func = (_func); \ +#define PREPARE_WORK (_work, _func) \ + do { \ + (_work)->func = (_func); \ } while (0) -#define PREPARE_DELAYED_WORK (_work, _func) \ +#define PREPARE_DELAYED_WORK (_work, _func) \ PREPARE_WORK(&(_work)->work, (_func))#ifdef CONFIG_DEBUG_OBJECTS_WORK
@@ -192,7 +192,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } \__init_work ((_work), _onstack); \ (_work)->data = (atomic_long_t )WORK_DATA_INIT (); \ - lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);\ + lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0); \INIT_LIST_HEAD (&(_work)->entry); \PREPARE_WORK ((_work), (_func)); \ }while (0 )
@@ -206,38 +206,38 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } } while (0)#endif -#define INIT_WORK (_work, _func) \ - do { \ - __INIT_WORK((_work), (_func), 0); \ +#define INIT_WORK (_work, _func) \ + do { \ + __INIT_WORK((_work), (_func), 0); \ } while (0) -#define INIT_WORK_ONSTACK (_work, _func) \ - do { \ - __INIT_WORK((_work), (_func), 1); \ +#define INIT_WORK_ONSTACK (_work, _func) \ + do { \ + __INIT_WORK((_work), (_func), 1); \ } while (0) -#define INIT_DELAYED_WORK (_work, _func) \ - do { \ - INIT_WORK(&(_work)->work, (_func)); \ - init_timer(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn;\ - (_work)->timer.data = (unsigned long)(_work); \ +#define INIT_DELAYED_WORK (_work, _func) \ + do { \ + INIT_WORK(&(_work)->work, (_func)); \ + init_timer(&(_work)->timer); \ + (_work)->timer.function = delayed_work_timer_fn; \ + (_work)->timer.data = (unsigned long)(_work); \ } while (0) -#define INIT_DELAYED_WORK_ONSTACK (_work, _func) \ - do { \ - INIT_WORK_ONSTACK(&(_work)->work, (_func)); \ - init_timer_on_stack(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn;\ - (_work)->timer.data = (unsigned long)(_work); \ +#define INIT_DELAYED_WORK_ONSTACK (_work, _func) \ + do { \ + INIT_WORK_ONSTACK(&(_work)->work, (_func)); \ + init_timer_on_stack(&(_work)->timer); \ + (_work)->timer.function = delayed_work_timer_fn; \ + (_work)->timer.data = (unsigned long)(_work); \ } while (0) -#define INIT_DELAYED_WORK_DEFERRABLE (_work, _func) \ - do { \ - INIT_WORK(&(_work)->work, (_func)); \ - init_timer_deferrable(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn;\ - (_work)->timer.data = (unsigned long)(_work); \ +#define INIT_DELAYED_WORK_DEFERRABLE (_work, _func) \ + do { \ + INIT_WORK(&(_work)->work, (_func)); \ + init_timer_deferrable(&(_work)->timer); \ + (_work)->timer.function = delayed_work_timer_fn; \ + (_work)->timer.data = (unsigned long)(_work); \ } while (0)/**
@@ -340,22 +340,22 @@ __alloc_workqueue_key(const char *fmt, unsigned int flags, int max_active,* Pointer to the allocated workqueue on success, %NULL on failure. */ #ifdef CONFIG_LOCKDEP -#define alloc_workqueue(fmt, flags, max_active, args...) \ -({ \ - static struct lock_class_key __key; \ - const char *__lock_name; \ - \ - if (__builtin_constant_p(fmt)) \ - __lock_name = (fmt); \ - else \ - __lock_name = #fmt; \ - \ - __alloc_workqueue_key((fmt), (flags), (max_active), \ - &__key, __lock_name, ##args); \ +#define alloc_workqueue(fmt, flags, max_active, args...) \ +({ \ + static struct lock_class_key __key; \ + const char *__lock_name; \ + \ + if (__builtin_constant_p(fmt)) \ + __lock_name = (fmt); \ + else \ + __lock_name = #fmt; \ + \ + __alloc_workqueue_key((fmt), (flags), (max_active), \ + &__key, __lock_name, ##args); \ })#else -#define alloc_workqueue(fmt, flags, max_active, args...) \ - __alloc_workqueue_key((fmt), (flags), (max_active), \ +#define alloc_workqueue(fmt, flags, max_active, args...) \ + __alloc_workqueue_key((fmt), (flags), (max_active), \ NULL, NULL, ##args)#endif
@@ -372,14 +372,14 @@ __alloc_workqueue_key(const char *fmt, unsigned int flags, int max_active,* RETURNS: * Pointer to the allocated workqueue on success, %NULL on failure. */ -#define alloc_ordered_workqueue(fmt, flags, args...) \ +#define alloc_ordered_workqueue(fmt, flags, args...) \ alloc_workqueue(fmt, WQ_UNBOUND | (flags), 1, ##args) -#define create_workqueue (name) \ +#define create_workqueue (name) \ alloc_workqueue((name), WQ_MEM_RECLAIM, 1) -#define create_freezable_workqueue (name) \ +#define create_freezable_workqueue (name) \ alloc_workqueue((name), WQ_FREEZABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, 1) -#define create_singlethread_workqueue (name) \ +#define create_singlethread_workqueue (name) \ alloc_workqueue((name), WQ_UNBOUND | WQ_MEM_RECLAIM, 1)extern void destroy_workqueue (struct workqueue_struct *wq);
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 2/7 workqueue: make deferrable delayed_work initializer names consistent
In reply to Tejun HeoInitalizers for deferrable delayed_work are confused.
* __DEFERRED_WORK_INITIALIZER()
* DECLARE_DEFERRED_WORK()
* INIT_DELAYED_WORK_DEFERRABLE()
Rename them to
* __DEFERRABLE_WORK_INITIALIZER()
* DECLARE_DEFERRABLE_WORK()
* INIT_DEFERRABLE_WORK()
This patch doesn't cause any functional changes.
--- arch/powerpc/platforms/cell/cpufreq_spudemand.c | 2 +- drivers/cpufreq/cpufreq_conservative.c | 2 +- drivers/cpufreq/cpufreq_ondemand.c | 2 +- drivers/devfreq/devfreq.c | 2 +- drivers/net/ethernet/mellanox/mlx4/sense.c | 2 +- drivers/power/ab8500_btemp.c | 2 +- drivers/power/ab8500_charger.c | 8 ++++---- drivers/power/ab8500_fg.c | 8 ++++---- drivers/power/abx500_chargalg.c | 4 ++-- drivers/power/max17040_battery.c | 2 +- drivers/video/omap2/displays/panel-taal.c | 6 +++--- drivers/video/omap2/dss/dsi.c | 4 ++-- include/linux/workqueue.h | 8 ++++---- mm/slab.c | 2 +- mm/vmstat.c | 2 +- net/core/neighbour.c | 2 +- net/ipv4/inetpeer.c | 2 +- net/sunrpc/cache.c | 2 +- 18 files changed, 31 insertions(+), 31 deletions(-)
arch/powerpc/platforms/cell/cpufreq_spudemand.c +1 -1
@@ -76,7 +76,7 @@ static void spu_gov_work(struct work_struct *work)static void spu_gov_init_work (struct spu_gov_info_struct *info) {int delay =usecs_to_jiffies (info->poll_int); -INIT_DELAYED_WORK_DEFERRABLE (&info->work, spu_gov_work); +INIT_DEFERRABLE_WORK (&info->work, spu_gov_work);schedule_delayed_work_on (info->policy->cpu, &info->work, delay); }
drivers/cpufreq/cpufreq_conservative.c +1 -1
@@ -466,7 +466,7 @@ static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info) delay -= jiffies % delay; dbs_info->enable =1 ; -INIT_DELAYED_WORK_DEFERRABLE (&dbs_info->work, do_dbs_timer); +INIT_DEFERRABLE_WORK (&dbs_info->work, do_dbs_timer);schedule_delayed_work_on (dbs_info->cpu, &dbs_info->work, delay); }
drivers/cpufreq/cpufreq_ondemand.c +1 -1
@@ -644,7 +644,7 @@ static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info) delay -= jiffies % delay; dbs_info->sample_type =DBS_NORMAL_SAMPLE ; -INIT_DELAYED_WORK_DEFERRABLE (&dbs_info->work, do_dbs_timer); +INIT_DEFERRABLE_WORK (&dbs_info->work, do_dbs_timer);schedule_delayed_work_on (dbs_info->cpu, &dbs_info->work, delay); }
drivers/devfreq/devfreq.c +1 -1
@@ -607,7 +607,7 @@ static int __init devfreq_start_polling(void)mutex_lock (&devfreq_list_lock); polling = false; devfreq_wq =create_freezable_workqueue ("devfreq_wq" ); -INIT_DELAYED_WORK_DEFERRABLE (&devfreq_work, devfreq_monitor); +INIT_DEFERRABLE_WORK (&devfreq_work, devfreq_monitor);mutex_unlock (&devfreq_list_lock);devfreq_monitor (&devfreq_work.work);
drivers/net/ethernet/mellanox/mlx4/sense.c +1 -1
@@ -153,5 +153,5 @@ void mlx4_sense_init(struct mlx4_dev *dev)for (port =1 ; port <= dev->caps.num_ports; port++) sense->do_sense_port[port] =1 ; -INIT_DELAYED_WORK_DEFERRABLE (&sense->sense_poll, mlx4_sense_port); +INIT_DEFERRABLE_WORK (&sense->sense_poll, mlx4_sense_port); }
drivers/power/ab8500_btemp.c +1 -1
@@ -1018,7 +1018,7 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) }/* Init work for measuring temperature periodically */ -INIT_DELAYED_WORK_DEFERRABLE (&di->btemp_periodic_work, +INIT_DEFERRABLE_WORK (&di->btemp_periodic_work, ab8500_btemp_periodic_work);/* Identify the battery */
drivers/power/ab8500_charger.c +4 -4
@@ -2618,9 +2618,9 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) }/* Init work for HW failure check */ -INIT_DELAYED_WORK_DEFERRABLE (&di->check_hw_failure_work, +INIT_DEFERRABLE_WORK (&di->check_hw_failure_work, ab8500_charger_check_hw_failure_work); -INIT_DELAYED_WORK_DEFERRABLE (&di->check_usbchgnotok_work, +INIT_DEFERRABLE_WORK (&di->check_usbchgnotok_work, ab8500_charger_check_usbchargernotok_work);/*
@@ -2632,10 +2632,10 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev)* watchdog have to be kicked by the charger driver * when the AC charger is disabled */ -INIT_DELAYED_WORK_DEFERRABLE (&di->kick_wd_work, +INIT_DEFERRABLE_WORK (&di->kick_wd_work, ab8500_charger_kick_watchdog_work); -INIT_DELAYED_WORK_DEFERRABLE (&di->check_vbat_work, +INIT_DEFERRABLE_WORK (&di->check_vbat_work, ab8500_charger_check_vbat_work);/* Init work for charger detection */
drivers/power/ab8500_fg.c +4 -4
@@ -2516,19 +2516,19 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev)INIT_WORK (&di->fg_acc_cur_work, ab8500_fg_acc_cur_work);/* Init work for reinitialising the fg algorithm */ -INIT_DELAYED_WORK_DEFERRABLE (&di->fg_reinit_work, +INIT_DEFERRABLE_WORK (&di->fg_reinit_work, ab8500_fg_reinit_work);/* Work delayed Queue to run the state machine */ -INIT_DELAYED_WORK_DEFERRABLE (&di->fg_periodic_work, +INIT_DEFERRABLE_WORK (&di->fg_periodic_work, ab8500_fg_periodic_work);/* Work to check low battery condition */ -INIT_DELAYED_WORK_DEFERRABLE (&di->fg_low_bat_work, +INIT_DEFERRABLE_WORK (&di->fg_low_bat_work, ab8500_fg_low_bat_work);/* Init work for HW failure check */ -INIT_DELAYED_WORK_DEFERRABLE (&di->fg_check_hw_failure_work, +INIT_DEFERRABLE_WORK (&di->fg_check_hw_failure_work, ab8500_fg_check_hw_failure_work);/* Initialize OVV, and other registers */
drivers/power/abx500_chargalg.c +2 -2
@@ -1848,9 +1848,9 @@ static int __devinit abx500_chargalg_probe(struct platform_device *pdev) }/* Init work for chargalg */ -INIT_DELAYED_WORK_DEFERRABLE (&di->chargalg_periodic_work, +INIT_DEFERRABLE_WORK (&di->chargalg_periodic_work, abx500_chargalg_periodic_work); -INIT_DELAYED_WORK_DEFERRABLE (&di->chargalg_wd_work, +INIT_DEFERRABLE_WORK (&di->chargalg_wd_work, abx500_chargalg_wd_work);/* Init work for chargalg */
drivers/power/max17040_battery.c +1 -1
@@ -232,7 +232,7 @@ static int __devinit max17040_probe(struct i2c_client *client,max17040_reset (client);max17040_get_version (client); -INIT_DELAYED_WORK_DEFERRABLE (&chip->work, max17040_work); +INIT_DEFERRABLE_WORK (&chip->work, max17040_work);schedule_delayed_work (&chip->work,MAX17040_DELAY );return 0 ;
drivers/video/omap2/displays/panel-taal.c +3 -3
@@ -906,7 +906,7 @@ static int taal_probe(struct omap_dss_device *dssdev) r = -ENOMEM ; goto err_wq; } -INIT_DELAYED_WORK_DEFERRABLE (&td->esd_work, taal_esd_work); +INIT_DEFERRABLE_WORK (&td->esd_work, taal_esd_work);INIT_DELAYED_WORK (&td->ulps_work, taal_ulps_work);dev_set_drvdata (&dssdev->dev, td);
@@ -962,8 +962,8 @@ static int taal_probe(struct omap_dss_device *dssdev) goto err_irq; } -INIT_DELAYED_WORK_DEFERRABLE (&td->te_timeout_work, - taal_te_timeout_work_callback); +INIT_DEFERRABLE_WORK (&td->te_timeout_work, + taal_te_timeout_work_callback);dev_dbg (&dssdev->dev,"Using GPIO TE\n" ); }
drivers/video/omap2/dss/dsi.c +2 -2
@@ -4863,8 +4863,8 @@ static int __init omap_dsihw_probe(struct platform_device *dsidev)mutex_init (&dsi->lock);sema_init (&dsi->bus_lock,1 ); -INIT_DELAYED_WORK_DEFERRABLE (&dsi->framedone_timeout_work, - dsi_framedone_timeout_work_callback); +INIT_DEFERRABLE_WORK (&dsi->framedone_timeout_work, + dsi_framedone_timeout_work_callback);#ifdef DSI_CATCH_MISSING_TE init_timer (&dsi->te_timer);
include/linux/workqueue.h +4 -4
@@ -139,7 +139,7 @@ struct execute_work { 0, (unsigned long)&(n)), \ } -#define __DEFERRED_WORK_INITIALIZER (n, f) { \ +#define __DEFERRABLE_WORK_INITIALIZER (n, f) { \ .work = __WORK_INITIALIZER((n).work, (f)), \ .timer = TIMER_DEFERRED_INITIALIZER(delayed_work_timer_fn, \ 0, (unsigned long)&(n)), \
@@ -151,8 +151,8 @@ struct execute_work {#define DECLARE_DELAYED_WORK (n, f) \ struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f) -#define DECLARE_DEFERRED_WORK (n, f) \ - struct delayed_work n = __DEFERRED_WORK_INITIALIZER(n, f) +#define DECLARE_DEFERRABLE_WORK (n, f) \ + struct delayed_work n = __DEFERRABLE_WORK_INITIALIZER(n, f)/* * initialize a work item's function pointer
@@ -232,7 +232,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } (_work)->timer.data = (unsigned long )(_work); \ } while (0) -#define INIT_DELAYED_WORK_DEFERRABLE (_work, _func) \ +#define INIT_DEFERRABLE_WORK (_work, _func) \ do { \ INIT_WORK(&(_work)->work, (_func)); \ init_timer_deferrable(&(_work)->timer); \
mm/slab.c +1 -1
@@ -900,7 +900,7 @@ static void __cpuinit start_cpu_timer(int cpu)*/ if (keventd_up () && reap_work->work.func ==NULL ) {init_reap_node (cpu); -INIT_DELAYED_WORK_DEFERRABLE (reap_work, cache_reap); +INIT_DEFERRABLE_WORK (reap_work, cache_reap);schedule_delayed_work_on (cpu, reap_work,__round_jiffies_relative (HZ , cpu)); }
mm/vmstat.c +1 -1
@@ -1157,7 +1157,7 @@ static void __cpuinit start_cpu_timer(int cpu) {struct delayed_work *work = &per_cpu (vmstat_work, cpu); -INIT_DELAYED_WORK_DEFERRABLE (work, vmstat_update); +INIT_DEFERRABLE_WORK (work, vmstat_update);schedule_delayed_work_on (cpu, work,__round_jiffies_relative (HZ , cpu)); }
net/core/neighbour.c +1 -1
@@ -1545,7 +1545,7 @@ static void neigh_table_init_no_netlink(struct neigh_table *tbl)panic ("cannot allocate neighbour cache hashes" );rwlock_init (&tbl->lock); -INIT_DELAYED_WORK_DEFERRABLE (&tbl->gc_work, neigh_periodic_work); +INIT_DEFERRABLE_WORK (&tbl->gc_work, neigh_periodic_work);schedule_delayed_work (&tbl->gc_work, tbl->parms.reachable_time);setup_timer (&tbl->proxy_timer, neigh_proxy_process, (unsigned long )tbl); skb_queue_head_init_class(&tbl->proxy_queue,
net/ipv4/inetpeer.c +1 -1
@@ -194,7 +194,7 @@ void __init inet_initpeers(void)0 ,SLAB_HWCACHE_ALIGN |SLAB_PANIC , NULL); -INIT_DELAYED_WORK_DEFERRABLE (&gc_work, inetpeer_gc_worker); +INIT_DEFERRABLE_WORK (&gc_work, inetpeer_gc_worker); } static int addr_compare(const struct inetpeer_addr *a,
net/sunrpc/cache.c +1 -1
@@ -1635,7 +1635,7 @@ static int create_cache_proc_entries(struct cache_detail *cd, struct net *net)void __init cache_initialize (void ) { -INIT_DELAYED_WORK_DEFERRABLE (&cache_cleaner, do_cache_clean); +INIT_DEFERRABLE_WORK (&cache_cleaner, do_cache_clean); }int cache_register_net (struct cache_detail *cd,struct net *net)
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 3/7 workqueue: clean up delayed_work initializers and add missing one
In reply to Tejun HeoReimplement delayed_work initializers using new timer initializers which take timer flags. This reduces code duplications and will ease further initializer changes. This patch also adds a missing initializer - INIT_DEFERRABLE_WORK_ONSTACK().
--- include/linux/workqueue.h | 48 +++++++++++++++++++++----------------------- 1 files changed, 23 insertions(+), 25 deletions(-)
include/linux/workqueue.h +23 -25
@@ -133,26 +133,20 @@ struct execute_work { __WORK_INIT_LOCKDEP_MAP(#n, &(n)) \ } -#define __DELAYED_WORK_INITIALIZER (n, f) { \ +#define __DELAYED_WORK_INITIALIZER (n, f, tflags) { \ .work = __WORK_INITIALIZER((n).work, (f)), \ - .timer = TIMER_INITIALIZER(delayed_work_timer_fn, \ - 0, (unsigned long)&(n)), \ - } - -#define __DEFERRABLE_WORK_INITIALIZER (n, f) { \ - .work = __WORK_INITIALIZER((n).work, (f)), \ - .timer = TIMER_DEFERRED_INITIALIZER(delayed_work_timer_fn, \ - 0, (unsigned long)&(n)), \ + .timer = __TIMER_INITIALIZER(delayed_work_timer_fn, \ + 0, (unsigned long)&(n), (tflags)), \ }#define DECLARE_WORK (n, f) \ struct work_struct n = __WORK_INITIALIZER(n, f)#define DECLARE_DELAYED_WORK (n, f) \ - struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f) + struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)#define DECLARE_DEFERRABLE_WORK (n, f) \ - struct delayed_work n = __DEFERRABLE_WORK_INITIALIZER(n, f) + struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, TIMER_DEFERRABLE)/* * initialize a work item's function pointer
@@ -216,29 +210,33 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; }__INIT_WORK ((_work), (_func),1 ); \ } while (0) -#define INIT_DELAYED_WORK (_work, _func) \ +#define __INIT_DELAYED_WORK (_work, _func, _tflags) \ do { \ INIT_WORK(&(_work)->work, (_func)); \ - init_timer(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn; \ - (_work)->timer.data = (unsigned long)(_work); \ + __setup_timer(&(_work)->timer, delayed_work_timer_fn, \ + (unsigned long)(_work), (_tflags)); \ } while (0) -#define INIT_DELAYED_WORK_ONSTACK (_work, _func) \ +#define __INIT_DELAYED_WORK_ONSTACK (_work, _func, _tflags) \ do { \ INIT_WORK_ONSTACK(&(_work)->work, (_func)); \ - init_timer_on_stack(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn; \ - (_work)->timer.data = (unsigned long)(_work); \ + __setup_timer_on_stack(&(_work)->timer, \ + delayed_work_timer_fn, \ + (unsigned long)(_work), \ + (_tflags)); \ } while (0) +#define INIT_DELAYED_WORK (_work, _func) \ + __INIT_DELAYED_WORK(_work, _func, 0) + +#define INIT_DELAYED_WORK_ONSTACK (_work, _func) \ + __INIT_DELAYED_WORK_ONSTACK(_work, _func, 0) +#define INIT_DEFERRABLE_WORK (_work, _func) \ - do { \ - INIT_WORK(&(_work)->work, (_func)); \ - init_timer_deferrable(&(_work)->timer); \ - (_work)->timer.function = delayed_work_timer_fn; \ - (_work)->timer.data = (unsigned long)(_work); \ - } while (0) + __INIT_DELAYED_WORK(_work, _func, TIMER_DEFERRABLE) + +#define INIT_DEFERRABLE_WORK_ONSTACK (_work, _func) \ + __INIT_DELAYED_WORK_ONSTACK(_work, _func, TIMER_DEFERRABLE)/** * work_pending - Find out whether a work item is currently pending
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 4/7 workqueue: use irqsafe timer for delayed_work
In reply to Tejun HeoUp to now, for delayed_works, try_to_grab_pending() couldn't be used from IRQ handlers because IRQs may happen while delayed_work_timer_fn() is in progress leading to indefinite -EAGAIN.
This patch makes delayed_work use the new TIMER_IRQSAFE flag for delayed_work->timer. This makes try_to_grab_pending() and thus mod_delayed_work_on() safe to call from IRQ handlers.
--- include/linux/workqueue.h | 8 +++++--- kernel/workqueue.c | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-)
include/linux/workqueue.h +5 -3
@@ -136,7 +136,8 @@ struct execute_work {#define __DELAYED_WORK_INITIALIZER (n, f, tflags) { \ .work = __WORK_INITIALIZER((n).work, (f)), \ .timer = __TIMER_INITIALIZER(delayed_work_timer_fn, \ - 0, (unsigned long)&(n), (tflags)), \ + 0, (unsigned long)&(n), \ + (tflags) | TIMER_IRQSAFE), \ }#define DECLARE_WORK (n, f) \
@@ -214,7 +215,8 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; }do { \INIT_WORK (&(_work)->work, (_func)); \__setup_timer (&(_work)->timer, delayed_work_timer_fn, \ - (unsigned long )(_work), (_tflags)); \ + (unsigned long )(_work), \ + (_tflags) |TIMER_IRQSAFE ); \ } while (0)#define __INIT_DELAYED_WORK_ONSTACK (_work, _func, _tflags) \
@@ -223,7 +225,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; }__setup_timer_on_stack (&(_work)->timer, \ delayed_work_timer_fn, \ (unsigned long )(_work), \ - (_tflags)); \ + (_tflags) |TIMER_IRQSAFE ); \ }while (0 ) #define INIT_DELAYED_WORK(_work, _func) \
kernel/workqueue.c +11 -9
@@ -1041,16 +1041,14 @@ static void cwq_dec_nr_in_flight(struct cpu_workqueue_struct *cwq, int color,* for arbitrarily long * * On >= 0 return, the caller owns @work's PENDING bit. To avoid getting -* preempted while holding PENDING and @work off queue, preemption must be -* disabled on entry. This ensures that we don't return -EAGAIN while -* another task is preempted in this function. +* interrupted while holding PENDING and @work off queue, irq must be +* disabled on entry. This, combined with delayed_work->timer being +* irqsafe, ensures that we return -EAGAIN for finite short period of time. * * On successful return, >= 0, irq is disabled and the caller is * responsible for releasing it using local_irq_restore(*@flags). * -* This function is safe to call from any context other than IRQ handler. -* An IRQ handler may run on top of delayed_work_timer_fn() which can make -* this function return -EAGAIN perpetually. +* This function is safe to call from any context including IRQ handler. */ static int try_to_grab_pending (struct work_struct *work,bool is_dwork,unsigned long *flags)
@@ -1065,6 +1063,11 @@ static int try_to_grab_pending(struct work_struct *work, bool is_dwork,if (is_dwork) {struct delayed_work *dwork =to_delayed_work (work); +/* +* dwork->timer is irqsafe. If del_timer() fails, it's +* guaranteed that the timer is not queued anywhere and not +* running on the local CPU. +*/ if (likely (del_timer (&dwork->timer)))return 1 ; }
@@ -1318,9 +1321,8 @@ void delayed_work_timer_fn(unsigned long __data)struct delayed_work *dwork = (struct delayed_work *)__data;struct cpu_workqueue_struct *cwq =get_work_cwq (&dwork->work); -local_irq_disable (); +/* should have been called from irqsafe timer with irq already off */ __queue_work (dwork->cpu, cwq->wq, &dwork->work); -local_irq_enable (); }EXPORT_SYMBOL_GPL (delayed_work_timer_fn);
@@ -1429,7 +1431,7 @@ EXPORT_SYMBOL_GPL(queue_delayed_work);* Returns %false if @dwork was idle and queued, %true if @dwork was * pending and its timer was modified. * -* This function is safe to call from any context other than IRQ handler. +* This function is safe to call from any context including IRQ handler. * See try_to_grab_pending() for details. */ bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 5/7 workqueue: use mod_delayed_work() instead of __cancel + queue
In reply to Tejun HeoNow that mod_delayed_work() is safe to call from IRQ handlers, __cancel_delayed_work() followed by queue_delayed_work() can be replaced with mod_delayed_work().
Most conversions are straight-forward except for the following.
* net/core/link_watch.c: linkwatch_schedule_work() was doing a quite elaborate dancing around its delayed_work. Collapse it such that linkwatch_work is queued for immediate execution if LW_URGENT and existing timer is kept otherwise.
--- block/blk-core.c | 6 ++---- block/blk-throttle.c | 7 +------ drivers/block/floppy.c | 3 +-- drivers/infiniband/core/mad.c | 14 +++++--------- drivers/input/keyboard/qt2160.c | 3 +-- drivers/input/mouse/synaptics_i2c.c | 7 +------ net/core/link_watch.c | 21 ++++++--------------- 7 files changed, 17 insertions(+), 44 deletions(-)
block/blk-core.c +2 -4
@@ -319,10 +319,8 @@ EXPORT_SYMBOL(__blk_run_queue);*/ void blk_run_queue_async (struct request_queue *q) { -if (likely (!blk_queue_stopped (q))) { -__cancel_delayed_work (&q->delay_work); -queue_delayed_work (kblockd_workqueue, &q->delay_work,0 ); - } +if (likely (!blk_queue_stopped (q))) +mod_delayed_work (kblockd_workqueue, &q->delay_work,0 ); }EXPORT_SYMBOL (blk_run_queue_async);
block/blk-throttle.c +1 -6
@@ -930,12 +930,7 @@ throtl_schedule_delayed_work(struct throtl_data *td, unsigned long delay)/* schedule work if limits changed even if no bio is queued */ if (total_nr_queued (td) || td->limits_changed) { -/* -* We might have a work scheduled to be executed in future. -* Cancel that and schedule a new one. -*/ -__cancel_delayed_work (dwork); -queue_delayed_work (kthrotld_workqueue, dwork, delay); +mod_delayed_work (kthrotld_workqueue, dwork, delay);throtl_log (td,"schedule work. delay=%lu jiffies=%lu" , delay, jiffies); }
drivers/block/floppy.c +1 -2
@@ -672,7 +672,6 @@ static void __reschedule_timeout(int drive, const char *message)if (drive == current_reqD) drive = current_drive; -__cancel_delayed_work (&fd_timeout);if (drive <0 || drive >=N_DRIVE ) { delay =20UL *HZ ;
@@ -680,7 +679,7 @@ static void __reschedule_timeout(int drive, const char *message) }else delay =UDP ->timeout; -queue_delayed_work (floppy_wq, &fd_timeout, delay); +mod_delayed_work (floppy_wq, &fd_timeout, delay);if (UDP ->flags &FD_DEBUG )DPRINT ("reschedule timeout %s\n" , message); timeout_message = message;
drivers/infiniband/core/mad.c +5 -9
@@ -2013,13 +2013,11 @@ static void adjust_timeout(struct ib_mad_agent_private *mad_agent_priv)if (time_after (mad_agent_priv->timeout, mad_send_wr->timeout)) { mad_agent_priv->timeout = mad_send_wr->timeout; -__cancel_delayed_work (&mad_agent_priv->timed_work); delay = mad_send_wr->timeout - jiffies;if ((long )delay <=0 ) delay =1 ; -queue_delayed_work (mad_agent_priv->qp_info-> - port_priv->wq, - &mad_agent_priv->timed_work, delay); +mod_delayed_work (mad_agent_priv->qp_info->port_priv->wq, + &mad_agent_priv->timed_work, delay); } } }
@@ -2052,11 +2050,9 @@ static void wait_for_response(struct ib_mad_send_wr_private *mad_send_wr)list_add (&mad_send_wr->agent_list, list_item);/* Reschedule a work item if we have a shorter timeout */ -if (mad_agent_priv->wait_list.next == &mad_send_wr->agent_list) { -__cancel_delayed_work (&mad_agent_priv->timed_work); -queue_delayed_work (mad_agent_priv->qp_info->port_priv->wq, - &mad_agent_priv->timed_work, delay); - } +if (mad_agent_priv->wait_list.next == &mad_send_wr->agent_list) +mod_delayed_work (mad_agent_priv->qp_info->port_priv->wq, + &mad_agent_priv->timed_work, delay); } void ib_reset_mad_timeout(struct ib_mad_send_wr_private *mad_send_wr,
drivers/input/keyboard/qt2160.c +1 -2
@@ -156,8 +156,7 @@ static irqreturn_t qt2160_irq(int irq, void *_qt2160)spin_lock_irqsave (&qt2160->lock, flags); -__cancel_delayed_work (&qt2160->dwork); -schedule_delayed_work (&qt2160->dwork,0 ); +mod_delayed_work (system_wq, &qt2160->dwork,0 );spin_unlock_irqrestore (&qt2160->lock, flags);
drivers/input/mouse/synaptics_i2c.c +1 -6
@@ -376,12 +376,7 @@ static void synaptics_i2c_reschedule_work(struct synaptics_i2c *touch,spin_lock_irqsave (&touch->lock, flags); -/* -* If work is already scheduled then subsequent schedules will not -* change the scheduled time that's why we have to cancel it first. -*/ -__cancel_delayed_work (&touch->dwork); -schedule_delayed_work (&touch->dwork, delay); +mod_delayed_work (system_wq, &touch->dwork, delay);spin_unlock_irqrestore (&touch->lock, flags); }
net/core/link_watch.c +6 -15
@@ -120,22 +120,13 @@ static void linkwatch_schedule_work(int urgent) delay =0 ;/* -* This is true if we've scheduled it immeditately or if we don't -* need an immediate execution and it's already pending. +* If urgent, schedule immediate execution; otherwise, don't +* override the existing timer. */ -if (schedule_delayed_work (&linkwatch_work, delay) == !delay) -return ; - -/* Don't bother if there is nothing urgent. */ -if (!test_bit (LW_URGENT , &linkwatch_flags)) -return ; - -/* It's already running which is good enough. */ -if (!__cancel_delayed_work (&linkwatch_work)) -return ; - -/* Otherwise we reschedule it again for immediate execution. */ -schedule_delayed_work (&linkwatch_work,0 ); +if (test_bit (LW_URGENT , &linkwatch_flags)) +mod_delayed_work (system_wq, &linkwatch_work,0 ); +else +schedule_delayed_work (&linkwatch_work, delay); }
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 6/7 workqueue: reimplement cancel_delayed_work() using try_to_grab_pending()
In reply to Tejun Heocancel_delayed_work() can't be called from IRQ handlers due to its use of del_timer_sync() and can't cancel work items which are already transferred from timer to worklist.
Also, unlike other flush and cancel functions, a canceled delayed_work would still point to the last associated cpu_workqueue. If the workqueue is destroyed afterwards and the work item is re-used on a different workqueue, the queueing code can oops trying to dereference already freed cpu_workqueue.
This patch reimplements cancel_delayed_work() using try_to_grab_pending() and set_work_cpu_and_clear_pending(). This allows the function to be called from IRQ handlers and makes its behavior consistent with other flush / cancel functions.
--- include/linux/workqueue.h | 17 +---------------- kernel/workqueue.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 16 deletions(-)
include/linux/workqueue.h +1 -16
@@ -417,6 +417,7 @@ extern bool cancel_work_sync(struct work_struct *work);extern bool flush_delayed_work (struct delayed_work *dwork);extern bool flush_delayed_work_sync (struct delayed_work *work); +extern bool cancel_delayed_work (struct delayed_work *dwork);extern bool cancel_delayed_work_sync (struct delayed_work *dwork); extern void workqueue_set_max_active(struct workqueue_struct *wq,
@@ -426,22 +427,6 @@ extern unsigned int work_cpu(struct work_struct *work);extern unsigned int work_busy (struct work_struct *work);/* -* Kill off a pending schedule_delayed_work(). Note that the work callback -* function may still be running on return from cancel_delayed_work(), unless -* it returns 1 and the work doesn't re-arm itself. Run flush_workqueue() or -* cancel_work_sync() to wait on it. -*/ -static inline bool cancel_delayed_work (struct delayed_work *work) -{ -bool ret; - - ret =del_timer_sync (&work->timer); -if (ret) -work_clear_pending (&work->work); -return ret; -} - -/* * Like above, but uses del_timer() instead of del_timer_sync(). This means, * if it returns 0 the timer function may be running and the queueing is in * progress.
kernel/workqueue.c +30 -0
@@ -3031,6 +3031,36 @@ bool flush_delayed_work_sync(struct delayed_work *dwork)EXPORT_SYMBOL (flush_delayed_work_sync);/** +* cancel_delayed_work - cancel a delayed work +* @dwork: delayed_work to cancel +* +* Kill off a pending delayed_work. Returns %true if @dwork was pending +* and canceled; %false if wasn't pending. Note that the work callback +* function may still be running on return, unless it returns %true and the +* work doesn't re-arm itself. Explicitly flush or use cancel_work_sync() +* to wait on it. +* +* This function is safe to call from any context including IRQ handler. +*/ +bool cancel_delayed_work (struct delayed_work *dwork) +{ +unsigned long flags; +int ret; + +do { + ret =try_to_grab_pending (&dwork->work, true, &flags); + }while (unlikely (ret == -EAGAIN )); + +if (unlikely (ret <0 )) +return false; + +set_work_cpu_and_clear_pending (&dwork->work,work_cpu (&dwork->work)); +local_irq_restore (flags); +return true; +} +EXPORT_SYMBOL (cancel_delayed_work); + +/** * cancel_delayed_work_sync - cancel a delayed work and wait for it to finish * @dwork: the delayed work cancel *
Signature · 1 line
1.7.7.3
Tejun Heo
maintainerPATCH 7/7 workqueue: deprecate __cancel_delayed_work()
In reply to Tejun HeoNow that cancel_delayed_work() can be safely called from IRQ handlers, there's no reason to use __cancel_delayed_work(). Use cancel_delayed_work() instead of __cancel_delayed_work() and mark the latter deprecated.
--- block/blk-core.c | 2 +- drivers/block/floppy.c | 2 +- drivers/infiniband/core/mad.c | 2 +- drivers/video/omap2/dss/dsi.c | 2 +- include/linux/workqueue.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-)
block/blk-core.c +1 -1
@@ -262,7 +262,7 @@ EXPORT_SYMBOL(blk_start_queue);**/ void blk_stop_queue (struct request_queue *q) { -__cancel_delayed_work (&q->delay_work); +cancel_delayed_work (&q->delay_work);queue_flag_set (QUEUE_FLAG_STOPPED , q); }EXPORT_SYMBOL (blk_stop_queue);
drivers/block/floppy.c +1 -1
@@ -890,7 +890,7 @@ static void unlock_fdc(void) raw_cmd =NULL ; command_status =FD_COMMAND_NONE ; -__cancel_delayed_work (&fd_timeout); +cancel_delayed_work (&fd_timeout); do_floppy =NULL ; cont =NULL ;clear_bit (0 , &fdc_busy);
drivers/infiniband/core/mad.c +1 -1
@@ -2004,7 +2004,7 @@ static void adjust_timeout(struct ib_mad_agent_private *mad_agent_priv)unsigned long delay;if (list_empty (&mad_agent_priv->wait_list)) { -__cancel_delayed_work (&mad_agent_priv->timed_work); +cancel_delayed_work (&mad_agent_priv->timed_work); }else { mad_send_wr = list_entry(mad_agent_priv->wait_list.next, struct ib_mad_send_wr_private,
drivers/video/omap2/dss/dsi.c +1 -1
@@ -4306,7 +4306,7 @@ static void dsi_framedone_irq_callback(void *data, u32 mask)* and is sending the data. */ -__cancel_delayed_work (&dsi->framedone_timeout_work); +cancel_delayed_work (&dsi->framedone_timeout_work);dsi_handle_framedone (dsidev,0 ); }
include/linux/workqueue.h +1 -1
@@ -431,7 +431,7 @@ extern unsigned int work_busy(struct work_struct *work);* if it returns 0 the timer function may be running and the queueing is in * progress. */ -static inline bool __cancel_delayed_work (struct delayed_work *work) +static inline bool __deprecated __cancel_delayed_work (struct delayed_work *work) {bool ret;
Signature · 1 line
1.7.7.3
Jens Axboe
maintainerRe: [PATCH 7/7] workqueue: deprecate __cancel_delayed_work()
Quoted 10 lines · Tejun Heo
Now that
cancel_delayed_work()can be safely called from IRQ handlers, there's no reason to use__cancel_delayed_work(). Usecancel_delayed_work()instead of__cancel_delayed_work()and mark the latter deprecated.Signed-off-by Tejun Heo <[email protected]>Cc Jens Axboe <[email protected]>Cc Jiri Kosina <[email protected]>Cc Roland Dreier <[email protected]>Cc Tomi Valkeinen <[email protected]>
You can add my acked by to this one.
Signature · 1 line
Jens Axboe
Tejun Heo
maintainerHello,
Quoted 8 lines · Tejun Heo
This patchset makes
delayed_workuse the irqsafe timer added by the pending "timer: clean up initializers and implement irqsafe timers" patchset[1]. This enablestry_to_grab_pending()to be used from any context which in turn makesmod_delayed_work()usable from IRQ handlers.cancel_delayed_work()is reimplemented usingtry_to_grab_pending()so that it also can be used from IRQ handlers and its behavior is consitent with other canceling operations.__cancel_delayed_work()is no longer necessary and deprecated.
I'll soon push this to linux-next through wq/for-3.7 together with irqsafe timer patchset. Plese scream if you don't like that.
Thanks.
Signature · 1 line
tejun
Quoted 8 lines · Tejun Heo
This patchset makes
delayed_workuse the irqsafe timer added by the pending "timer: clean up initializers and implement irqsafe timers" patchset[1]. This enablestry_to_grab_pending()to be used from any context which in turn makesmod_delayed_work()usable from IRQ handlers.cancel_delayed_work()is reimplemented usingtry_to_grab_pending()so that it also can be used from IRQ handlers and its behavior is consitent with other canceling operations.__cancel_delayed_work()is no longer necessary and deprecated.
Applied to wq/for-3.7 after pulling in tip/timers/core.
Thanks.
Signature · 1 line
tejun