hrtimer: Cleanup usage trainwrecks treewide
Subsystems (9)
cover hrtimer: Cleanup usage trainwrecks treewide
While doing a tree wide audit of hrtimer users I found several bugs which can result in data corruption.
Fix the bugs and update the documentation in the hope that people actually read it.
Thanks,
tglx
PATCH 1/5 perf: Fixup hrtimer forward wreckage
perf_event_mux_interval_ms_store() tries to apply an updated hrtimer_interval to all possible cpus in a completely unsafe way. The changelog of the offending commit says:
"In the 5th version, we handle the reprogramming of the hrtimer using
hrtimer_forward_now(). That way, we sync up to new timer value quickly (suggested by Jiri Olsa)."
The hrtimer reprogramming is completely unrelated to hrtimer_forward_now(). hrtimer_forward_now() merily forwards the expiry time of the hrtimer past now and that's where things get really bad in this code:
The forward function is called on enqueued timers. That means the update of the expiry time corrupts the ordering of the hrtimer rbtree.
The proper way to update hrtimers on remote cpus is to use a smp function call on all online cpus and perform the update locally by canceling the timer, forwarding and restarting it.
--- kernel/events/core.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-)
kernel/events/core.c +20 -16
@@ -6827,13 +6827,27 @@ perf_event_mux_interval_ms_show(struct dreturn snprintf (page,PAGE_SIZE -1 ,"%d\n" , pmu->hrtimer_interval_ms); } +static void perf_event_mux_update_interval (void *info) +{ +struct pmu *pmu = info; +struct perf_cpu_context *cpuctx =this_cpu_ptr (pmu->pmu_cpu_context); +int was_armed =hrtimer_cancel (&cpuctx->hrtimer); + +/* Update the interval and restart the timer with the new interval */ + cpuctx->hrtimer_interval =ms_to_ktime (pmu->hrtimer_interval_ms); +if (was_armed) { +hrtimer_forward_now (&cpuctx->hrtimer, cpuctx->hrtimer_interval); +hrtimer_start_expires (&cpuctx->hrtimer,HRTIMER_MODE_ABS ); + } +} +static ssize_t perf_event_mux_interval_ms_store (struct device *dev,struct device_attribute *attr,const char *buf,size_t count) {struct pmu *pmu =dev_get_drvdata (dev); -int timer, cpu, ret; +int timer, ret; ret =kstrtoint (buf,0 , &timer);if (ret)
@@ -6842,22 +6856,12 @@ perf_event_mux_interval_ms_store(structif (timer <1 )return -EINVAL ; -/* same value, noting to do */ -if (timer == pmu->hrtimer_interval_ms) -return count; - - pmu->hrtimer_interval_ms = timer; - -/* update all cpuctx for this PMU */ -for_each_possible_cpu (cpu) { -struct perf_cpu_context *cpuctx; - cpuctx =per_cpu_ptr (pmu->pmu_cpu_context, cpu); - cpuctx->hrtimer_interval =ns_to_ktime (NSEC_PER_MSEC * timer); - -if (hrtimer_active (&cpuctx->hrtimer)) -hrtimer_forward_now (&cpuctx->hrtimer, cpuctx->hrtimer_interval); +if (timer != pmu->hrtimer_interval_ms) { +get_online_cpus (); + pmu->hrtimer_interval_ms = timer; +on_each_cpu (perf_event_mux_update_interval, pmu,1 ); +put_online_cpus (); } -return count; }static DEVICE_ATTR_RW (perf_event_mux_interval_ms);
Re: [patch 1/5] perf: Fixup hrtimer forward wreckage
Quoted 21 lines · Thomas Gleixner
perf_event_mux_interval_ms_store()tries to apply an updatedhrtimer_intervalto all possible cpus in a completely unsafe way. The changelog of the offending commit says:"In the 5th version, we handle the reprogramming of the hrtimer using
hrtimer_forward_now(). That way, we sync up to new timer value quickly (suggested by Jiri Olsa)."The hrtimer reprogramming is completely unrelated to
hrtimer_forward_now().hrtimer_forward_now()merily forwards the expiry time of the hrtimer past now and that's where things get really bad in this code:The forward function is called on enqueued timers. That means the update of the expiry time corrupts the ordering of the hrtimer rbtree.
The proper way to update hrtimers on remote cpus is to use a smp function call on all online cpus and perform the update locally by canceling the timer, forwarding and restarting it.
Quoted 6 lines
Fixes 62b856397927 "perf: Add sysfs entry to adjust multiplexing interval per PMU"Signed-off-by Thomas Gleixner <[email protected]>Cc Stephane Eranian <[email protected]>Cc Jiri Olsa <[email protected]>Cc Arnaldo Carvalho de Melo <[email protected]>
Thanks for fixing this Thomas!
Quoted 68 lines
--- kernel/events/core.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-)Index:
linux/kernel/events/core.c
===================================================================kernel/events/core.c +20
-16@@ -6827,13 +6827,27 @@ perf_event_mux_interval_ms_show(struct dreturn snprintf (page,PAGE_SIZE -1 ,"%d\n" , pmu->hrtimer_interval_ms); } +static void perf_event_mux_update_interval (void *info) +{ +struct pmu *pmu = info; +struct perf_cpu_context *cpuctx =this_cpu_ptr (pmu->pmu_cpu_context); +int was_armed =hrtimer_cancel (&cpuctx->hrtimer); + +/* Update the interval and restart the timer with the new interval */ + cpuctx->hrtimer_interval =ms_to_ktime (pmu->hrtimer_interval_ms); +if (was_armed) { +hrtimer_forward_now (&cpuctx->hrtimer, cpuctx->hrtimer_interval); +hrtimer_start_expires (&cpuctx->hrtimer,HRTIMER_MODE_ABS ); + } +} +static ssize_t perf_event_mux_interval_ms_store (struct device *dev,struct device_attribute *attr,const char *buf,size_t count) {struct pmu *pmu =dev_get_drvdata (dev); -int timer, cpu, ret; +int timer, ret; ret =kstrtoint (buf,0 , &timer);if (ret)@@ -6842,22 +6856,12 @@ perf_event_mux_interval_ms_store(structif (timer <1 )return -EINVAL ; -/* same value, noting to do */ -if (timer == pmu->hrtimer_interval_ms) -return count; - - pmu->hrtimer_interval_ms = timer; - -/* update all cpuctx for this PMU */ -for_each_possible_cpu (cpu) { -struct perf_cpu_context *cpuctx; - cpuctx =per_cpu_ptr (pmu->pmu_cpu_context, cpu); - cpuctx->hrtimer_interval =ns_to_ktime (NSEC_PER_MSEC * timer); - -if (hrtimer_active (&cpuctx->hrtimer)) -hrtimer_forward_now (&cpuctx->hrtimer, cpuctx->hrtimer_interval); +if (timer != pmu->hrtimer_interval_ms) { +get_online_cpus (); + pmu->hrtimer_interval_ms = timer; +on_each_cpu (perf_event_mux_update_interval, pmu,1 ); +put_online_cpus (); } -return count; }static DEVICE_ATTR_RW (perf_event_mux_interval_ms);
PATCH 2/5 s390: crypto: Protect poll timeout update
In reply to Thomas GleixnerThe poll timeout update via sysfs runs completely unprotected against other usage sites of the timer. Take the proper lock before fiddling with the timer.
---
P.S: I have serious doubts about the following snippets in that code:
@@ -1174,11 +1174,13 @@ static ssize_t poll_timeout_store(struct
poll_timeout = time;
hr_time = ktime_set(0, poll_timeout);
spin_lock_bh(&ap_poll_timer_lock);
if (!hrtimer_is_queued(&ap_poll_timer) ||--> !hrtimer_forward(&ap_poll_timer, hrtimer_get_expires(&ap_poll_timer), hr_time)) {
Why does it check, whether the forwarding resulted in an overrun?
If the timer is not queued, then it is either expired or has never been started. So if the poll timeout gets written, then the timer is started when
timer->expires + hr_time < now
This results in random behaviour at best.
hrtimer_set_expires(&ap_poll_timer, hr_time);
hrtimer_start_expires(&ap_poll_timer, HRTIMER_MODE_ABS);
}
@@ -1552,11 +1553,16 @@ static inline void __ap_schedule_poll_timeout
spin_lock_bh(&ap_poll_timer_lock);
if (hrtimer_is_queued(&ap_poll_timer) || ap_suspend_flag)
goto out;---> if (ktime_to_ns(hrtimer_expires_remaining(&ap_poll_timer)) <= 0) {
The check above does not make any sense either. Again, if the timer is not queued then it is either expired or was never started. As the timer is never canceled except on module unload the condition is always true.
hr_time = ktime_set(0, poll_timeout);
hrtimer_forward_now(&ap_poll_timer, hr_time);
hrtimer_restart(&ap_poll_timer);
}But that's not scope of that patch and I leave this to the s390 wizards to digest.
---
drivers/s390/crypto/ap_bus.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/s390/crypto/ap_bus.c +2 -0
@@ -1174,11 +1174,13 @@ static ssize_t poll_timeout_store(struct poll_timeout = time; hr_time =ktime_set (0 , poll_timeout); +spin_lock_bh (&ap_poll_timer_lock);if (!hrtimer_is_queued (&ap_poll_timer) || !hrtimer_forward (&ap_poll_timer,hrtimer_get_expires (&ap_poll_timer), hr_time)) {hrtimer_set_expires (&ap_poll_timer, hr_time);hrtimer_start_expires (&ap_poll_timer,HRTIMER_MODE_ABS ); } +spin_unlock_bh (&ap_poll_timer_lock);return count; }
Re: [patch 2/5] s390: crypto: Protect poll timeout update
You're right. Thanks for pointing this out.
I'll fix these issues and bring them upstream.
Mit freundlichen Grüßen / Kind regards
Ingo Tuchscherer
Software Development - Linux on z Systems
IBM Systems &Technology Group
Phone: +49-7031-16-1986 IBM Deutschland (Embedded
image moved
to file:
pic14137.gif)E-Mail: [email protected] Schoenaicher Str. 220
71032 Boeblingen
Germany IBM Deutschland
Research &
Development
GmbH /
Vorsitzender des
Aufsichtsrats:
Martina Koederitz
Geschäftsführung:
Dirk Wittkopp
Sitz der
Gesellschaft:
Böblingen /
Registergericht:
Amtsgericht
Stuttgart, HRB
243294
From: Thomas Gleixner <[email protected]>
To: LKML <[email protected]>
Cc: Peter Zijlstra <[email protected]>, Ingo Molnar
<[email protected]>, Ingo Tuchscherer/Germany/IBM@IBMDE, [email protected], [email protected], [email protected]
Date: 04/13/2015 11:02 PM
Subject: [patch 2/5] s390: crypto: Protect poll timeout update
The poll timeout update via sysfs runs completely unprotected against other usage sites of the timer. Take the proper lock before fiddling with the timer.
---
P.S: I have serious doubts about the following snippets in that code:
@@ -1174,11 +1174,13 @@ static ssize_t poll_timeout_store(struct
poll_timeout = time;
hr_time = ktime_set(0, poll_timeout);
spin_lock_bh(&ap_poll_timer_lock);
if (!hrtimer_is_queued(&ap_poll_timer) ||--> !hrtimer_forward(&ap_poll_timer, hrtimer_get_expires
(&ap_poll_timer), hr_time)) {
Why does it check, whether the forwarding resulted in an overrun?
If the timer is not queued, then it is either expired or has never been started. So if the poll timeout gets written, then the timer is started when
timer->expires + hr_time < now
This results in random behaviour at best.
hrtimer_set_expires(&ap_poll_timer, hr_time);
hrtimer_start_expires(&ap_poll_timer, HRTIMER_MODE_ABS);
}
@@ -1552,11 +1553,16 @@ static inline void __ap_schedule_poll_timeout
spin_lock_bh(&ap_poll_timer_lock);
if (hrtimer_is_queued(&ap_poll_timer) || ap_suspend_flag)
goto out;---> if (ktime_to_ns(hrtimer_expires_remaining(&ap_poll_timer)) <= 0) {
The check above does not make any sense either. Again, if the timer is not queued then it is either expired or was never started. As the timer is never canceled except on module unload the condition is always true.
hr_time = ktime_set(0, poll_timeout);
hrtimer_forward_now(&ap_poll_timer, hr_time);
hrtimer_restart(&ap_poll_timer);
}But that's not scope of that patch and I leave this to the s390 wizards to digest.
---
drivers/s390/crypto/ap_bus.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/s390/crypto/ap_bus.c +1 -0
@@ -1174,11 +1174,13 @@ static ssize_t poll_timeout_store(struct poll_timeout = time; hr_time =ktime_set (0 , poll_timeout); +spin_lock_bh (&ap_poll_timer_lock);if (!hrtimer_is_queued (&ap_poll_timer) || !hrtimer_forward(&ap_poll_timer, hrtimer_get_expires
(&ap_poll_timer), hr_time)) {
hrtimer_set_expires(&ap_poll_timer, hr_time); hrtimer_start_expires(&ap_poll_timer,HRTIMER_MODE_ABS);
}
+ spin_unlock_bh(&ap_poll_timer_lock);
return count;
}
PATCH 3/5 hrtimer: Document hrtimer_forward[_now]() proper
In reply to Thomas GleixnerDocument the calling context conditions.
--- include/linux/hrtimer.h | 17 ++++++++++++++++- kernel/time/hrtimer.c | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-)
include/linux/hrtimer.h +16 -1
@@ -418,7 +418,22 @@ static inline int hrtimer_callback_runniextern u64 hrtimer_forward (struct hrtimer *timer,ktime_t now,ktime_t interval); -/* Forward a hrtimer so it expires after the hrtimer's current now */ +/** +* hrtimer_forward_now - forward the timer expiry so it expires after now +* @timer: hrtimer to forward +* @interval: the interval to forward +* +* Forward the timer expiry so it will expire after the current time +* of the hrtimer clock base. Returns the number of overruns. +* +* Can be safely called from the callback function of @timer. If +* called from other contexts @timer must neither be enqueued nor +* running the callback and the caller needs to take care of +* serialization. +* +* Note: This only updates the timer expiry value and does not requeue +* the timer. +*/ static inline u64 hrtimer_forward_now (struct hrtimer *timer,ktime_t interval) {
kernel/time/hrtimer.c +8 -0
@@ -801,6 +801,14 @@ void unlock_hrtimer_base(const struct hr* * Forward the timer expiry so it will expire in the future. * Returns the number of overruns. +* +* Can be safely called from the callback function of @timer. If +* called from other contexts @timer must neither be enqueued nor +* running the callback and the caller needs to take care of +* serialization. +* +* Note: This only updates the timer expiry value and does not requeue +* the timer. */ u64 hrtimer_forward (struct hrtimer *timer,ktime_t now,ktime_t interval) {
tip-bot for Thomas Gleixner
merge bot[tip:timers/core] hrtimer: Document hrtimer_forward[_now]() proper
hrtimer: Document hrtimer_forward[_now]() proper
Commit-ID: 91e5a2170e795989da9f90c18ba18984f23acc5b Gitweb: http://git.kernel.org/tip/91e5a2170e795989da9f90c18ba18984f23acc5b Author: Thomas Gleixner <[email protected]> AuthorDate: Mon, 13 Apr 2015 21:02:22 +0000 Committer: Thomas Gleixner <[email protected]> CommitDate: Wed, 22 Apr 2015 17:06:48 +0200
hrtimer: Document hrtimer_forward[_now]() proper
Document the calling context conditions.
--- include/linux/hrtimer.h | 17 ++++++++++++++++- kernel/time/hrtimer.c | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-)
include/linux/hrtimer.h +16 -1
@@ -418,7 +418,22 @@ static inline int hrtimer_callback_running(struct hrtimer *timer)extern u64 hrtimer_forward (struct hrtimer *timer,ktime_t now,ktime_t interval); -/* Forward a hrtimer so it expires after the hrtimer's current now */ +/** +* hrtimer_forward_now - forward the timer expiry so it expires after now +* @timer: hrtimer to forward +* @interval: the interval to forward +* +* Forward the timer expiry so it will expire after the current time +* of the hrtimer clock base. Returns the number of overruns. +* +* Can be safely called from the callback function of @timer. If +* called from other contexts @timer must neither be enqueued nor +* running the callback and the caller needs to take care of +* serialization. +* +* Note: This only updates the timer expiry value and does not requeue +* the timer. +*/ static inline u64 hrtimer_forward_now (struct hrtimer *timer,ktime_t interval) {
kernel/time/hrtimer.c +8 -0
@@ -801,6 +801,14 @@ void unlock_hrtimer_base(const struct hrtimer *timer, unsigned long *flags)* * Forward the timer expiry so it will expire in the future. * Returns the number of overruns. +* +* Can be safely called from the callback function of @timer. If +* called from other contexts @timer must neither be enqueued nor +* running the callback and the caller needs to take care of +* serialization. +* +* Note: This only updates the timer expiry value and does not requeue +* the timer. */ u64 hrtimer_forward (struct hrtimer *timer,ktime_t now,ktime_t interval) {
PATCH 4/5 net: hip04: Make tx coalesce timer actually work
In reply to Thomas GleixnerThe code sets the expiry value of the timer to a relative value and starts it with hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).
So once the timer expired, a new call to hrtimer_start_expires results in an immidiately expired timer, because the expiry value is already in the past.
Use the proper mechanisms to (re)start the timer in the intended way.
--- drivers/net/ethernet/hisilicon/hip04_eth.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)
drivers/net/ethernet/hisilicon/hip04_eth.c +11 -7
@@ -413,6 +413,15 @@ out:return count; } +static void hip04_start_tx_timer (struct hip04_priv *priv) +{ +ktime_t t; + +/* allow timer to fire after half the time at the earliest */ + t =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); +hrtimer_start (&priv->tx_coalesce_timer, t,HRTIMER_MODE_REL ); +} +static int hip04_mac_start_xmit (struct sk_buff *skb,struct net_device *ndev) {struct hip04_priv *priv =netdev_priv (ndev);
@@ -466,8 +475,7 @@ static int hip04_mac_start_xmit(struct s } }else if (!hrtimer_is_queued (&priv->tx_coalesce_timer)) {/* cleanup not pending yet, start a new timer */ -hrtimer_start_expires (&priv->tx_coalesce_timer, -HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv); }return NETDEV_TX_OK ;
@@ -549,7 +557,7 @@ done:/* clean up tx descriptors and start a new timer if necessary */ tx_remaining =hip04_tx_reclaim (ndev, false);if (rx < budget && tx_remaining) -hrtimer_start_expires (&priv->tx_coalesce_timer,HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv);return rx; }
@@ -809,7 +817,6 @@ static int hip04_mac_probe(struct platfostruct hip04_priv *priv;struct resource *res;unsigned int irq; -ktime_t txtime;int ret; ndev =alloc_etherdev (sizeof (struct hip04_priv ));
@@ -846,9 +853,6 @@ static int hip04_mac_probe(struct platfo*/ priv->tx_coalesce_frames =TX_DESC_NUM *3 /4 ; priv->tx_coalesce_usecs =200 ; -/* allow timer to fire after half the time at the earliest */ - txtime =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); -hrtimer_set_expires_range (&priv->tx_coalesce_timer, txtime, txtime); priv->tx_coalesce_timer.function = tx_done; priv->map =syscon_node_to_regmap (arg.np);
Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work
Quoted 11 lines · On Monday 13 April 2015 21:02:23 Thomas Gleixner wrote:
The code sets the expiry value of the timer to a relative value and starts it with
hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).So once the timer expired, a new call to
hrtimer_start_expiresresults in an immidiately expired timer, because the expiry value is already in the past.Use the proper mechanisms to (re)start the timer in the intended way.
Quoted 7 lines
Signed-off-by Thomas Gleixner <[email protected]>Cc "David S. Miller" <[email protected]>Cc dingtianhong <[email protected]>Cc Arnd Bergmann <[email protected]>Cc Zhangfei Gao <[email protected]>Cc Dan Carpenter <[email protected]>
Thanks a lot for the fix. The mistake was clearly mine, as I had sent a patch to introduce the tx coalesce timer without access to hardware or a way to test that what I did was correct.
There are other known problems in the version of the driver that got merged, and I believe that someone is now looking at them.
What I think we really want here is a way for user space to configure both the minimum and maximum coalesce timer separately rather than assuming half the time is what we want.
Arnd
drivers/net/ethernet/hisilicon/hip04_eth.c out: · line 423
Quoted 12 lines
drivers/net/ethernet/hisilicon/hip04_eth.c +8
-0@@ -413,6 +413,15 @@ out:return count; } +static void hip04_start_tx_timer (struct hip04_priv *priv) +{ +ktime_t t; + +/* allow timer to fire after half the time at the earliest */ + t =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); +hrtimer_start (&priv->tx_coalesce_timer, t,HRTIMER_MODE_REL ); +}
Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?
Arnd
Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work
Quoted 37 lines · Arnd Bergmann
Quoted 11 lines · On Monday 13 April 2015 21:02:23 Thomas Gleixner wrote:
The code sets the expiry value of the timer to a relative value and starts it with
hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).So once the timer expired, a new call to
hrtimer_start_expiresresults in an immidiately expired timer, because the expiry value is already in the past.Use the proper mechanisms to (re)start the timer in the intended way.
Quoted 7 lines
Signed-off-by Thomas Gleixner <[email protected]>Cc "David S. Miller" <[email protected]>Cc dingtianhong <[email protected]>Cc Arnd Bergmann <[email protected]>Cc Zhangfei Gao <[email protected]>Cc Dan Carpenter <[email protected]>Thanks a lot for the fix. The mistake was clearly mine, as I had sent a patch to introduce the tx coalesce timer without access to hardware or a way to test that what I did was correct.
There are other known problems in the version of the driver that got merged, and I believe that someone is now looking at them.
What I think we really want here is a way for user space to configure both the minimum and maximum coalesce timer separately rather than assuming half the time is what we want.
Arnd
Quoted 4 lines
drivers/net/ethernet/hisilicon/hip04_eth.c +0
-0@@ -413,6 +413,15 @@ out:return count; }
Quoted 14 lines
drivers/net/ethernet/hisilicon/hip04_eth.c out: · line 423
Quoted 8 lines
drivers/net/ethernet/hisilicon/hip04_eth.c +8
-0+static void hip04_start_tx_timer (struct hip04_priv *priv) +{ +ktime_t t; + +/* allow timer to fire after half the time at the earliest */ + t =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); +hrtimer_start (&priv->tx_coalesce_timer, t,HRTIMER_MODE_REL ); +}Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to
priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?
Yes, I missed that. Simple fix for this is:
unsigned long t_ns = priv->tx_coalesce_usecs * NSEC_PER_USEC / 2;
hrtimer_start_range_ns(&priv->tx_coalesce_timer, ns_to_ktime(t_ns),
t_ns, HRTIMER_MODE_REL);
Thanks,
tglx
Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work
Quoted 7 lines · On Monday 13 April 2015 23:42:03 Thomas Gleixner wrote:
Quoted 6 lines
Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to
priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?
Quoted 6 lines
Yes, I missed that. Simple fix for this is:
unsigned long t_ns = priv->tx_coalesce_usecs * NSEC_PER_USEC / 2;hrtimer_start_range_ns(&priv->tx_coalesce_timer,
ns_to_ktime(t_ns),t_ns, HRTIMER_MODE_REL);
Ah, good. I have to admit that I'd probably make the same mistake again if I was to do this for another driver and you hadn't sent the fix. The hrtimer_set_expires_range() function just looked like it had been designed for the use case I was interested in ;-).
Any idea how to prevent the next person from making the same mistake?
Arnd
Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work
Quoted 15 lines · Arnd Bergmann
Quoted 7 lines · On Monday 13 April 2015 23:42:03 Thomas Gleixner wrote:
Quoted 6 lines
Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to
priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?Quoted 6 lines
Yes, I missed that. Simple fix for this is:
unsigned long t_ns = priv->tx_coalesce_usecs * NSEC_PER_USEC / 2;hrtimer_start_range_ns(&priv->tx_coalesce_timer,
ns_to_ktime(t_ns),t_ns, HRTIMER_MODE_REL);
Quoted 6 lines
Ah, good. I have to admit that I'd probably make the same mistake again if I was to do this for another driver and you hadn't sent the fix. The
hrtimer_set_expires_range()function just looked like it had been designed for the use case I was interested in ;-).Any idea how to prevent the next person from making the same mistake?
Yes. Documentation :)
Thanks,
tglx
Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work
Quoted 16 lines · Thomas Gleixner
Quoted 15 lines · Arnd Bergmann
Quoted 13 lines · On Monday 13 April 2015 23:42:03 Thomas Gleixner wrote:
Quoted 6 lines
Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to
priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?Yes, I missed that. Simple fix for this is:
unsigned long t_ns = priv->tx_coalesce_usecs * NSEC_PER_USEC / 2;hrtimer_start_range_ns(&priv->tx_coalesce_timer,
ns_to_ktime(t_ns),t_ns, HRTIMER_MODE_REL);
Quoted 9 lines
Quoted 6 lines
Ah, good. I have to admit that I'd probably make the same mistake again if I was to do this for another driver and you hadn't sent the fix. The
hrtimer_set_expires_range()function just looked like it had been designed for the use case I was interested in ;-).Any idea how to prevent the next person from making the same mistake?
Yes. Documentation :)
Looks good to me, thanks everyone.
Ding
Quoted 6 lines
Thanks,
tglx
.
David Miller
maintainerRe: [patch 4/5] net: hip04: Make tx coalesce timer actually work
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Date: Tue, 14 Apr 2015 00:08:23 +0200 (CEST)
Quoted 16 lines · Arnd Bergmann
Quoted 15 lines · On Monday 13 April 2015 23:42:03 Thomas Gleixner wrote:
Quoted 13 lines
Quoted 6 lines
Question: this looks to me like it sets both the minimum and maximum time to priv->tx_coalesce_usecs/2, when the intention was to set the minimum to priv->tx_coalesce_usecs/2 and the maximum to
priv->tx_coalesce_usecs. Am I missing something subtle here, or did you just misread my original intention from the botched code?Yes, I missed that. Simple fix for this is:
unsigned long t_ns = priv->tx_coalesce_usecs * NSEC_PER_USEC / 2;hrtimer_start_range_ns(&priv->tx_coalesce_timer,
ns_to_ktime(t_ns),t_ns, HRTIMER_MODE_REL);
Quoted 8 lines
Quoted 6 lines
Ah, good. I have to admit that I'd probably make the same mistake again if I was to do this for another driver and you hadn't sent the fix. The
hrtimer_set_expires_range()function just looked like it had been designed for the use case I was interested in ;-).Any idea how to prevent the next person from making the same mistake?
Yes. Documentation :)
Can I get a respin of this patch with the above?
PATCH v2 1/1 net: hip04: Make tx coalesce timer actually work
The code sets the expiry value of the timer to a relative value and starts it with hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).
So once the timer expired, a new call to hrtimer_start_expires results in an immidiately expired timer, because the expiry value is already in the past.
Use the proper mechanisms to (re)start the timer in the intended way.
--- drivers/net/ethernet/hisilicon/hip04_eth.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)
drivers/net/ethernet/hisilicon/hip04_eth.c +11 -7
@@ -413,6 +413,15 @@ out:return count; } +static void hip04_start_tx_timer (struct hip04_priv *priv) +{ +unsigned long ns = priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ; + +/* allow timer to fire after half the time at the earliest */ +hrtimer_start_range_ns (&priv->tx_coalesce_timer,ns_to_ktime (ns), + ns,HRTIMER_MODE_REL ); +} +static int hip04_mac_start_xmit (struct sk_buff *skb,struct net_device *ndev) {struct hip04_priv *priv =netdev_priv (ndev);
@@ -466,8 +475,7 @@ static int hip04_mac_start_xmit(struct s } }else if (!hrtimer_is_queued (&priv->tx_coalesce_timer)) {/* cleanup not pending yet, start a new timer */ -hrtimer_start_expires (&priv->tx_coalesce_timer, -HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv); }return NETDEV_TX_OK ;
@@ -549,7 +557,7 @@ done:/* clean up tx descriptors and start a new timer if necessary */ tx_remaining =hip04_tx_reclaim (ndev, false);if (rx < budget && tx_remaining) -hrtimer_start_expires (&priv->tx_coalesce_timer,HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv);return rx; }
@@ -809,7 +817,6 @@ static int hip04_mac_probe(struct platfostruct hip04_priv *priv;struct resource *res;unsigned int irq; -ktime_t txtime;int ret; ndev =alloc_etherdev (sizeof (struct hip04_priv ));
@@ -846,9 +853,6 @@ static int hip04_mac_probe(struct platfo*/ priv->tx_coalesce_frames =TX_DESC_NUM *3 /4 ; priv->tx_coalesce_usecs =200 ; -/* allow timer to fire after half the time at the earliest */ - txtime =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); -hrtimer_set_expires_range (&priv->tx_coalesce_timer, txtime, txtime); priv->tx_coalesce_timer.function = tx_done; priv->map =syscon_node_to_regmap (arg.np);
Re: [patch v2] net: hip04: Make tx coalesce timer actually work
Quoted 11 lines · Thomas Gleixner
The code sets the expiry value of the timer to a relative value and starts it with
hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).So once the timer expired, a new call to
hrtimer_start_expiresresults in an immidiately expired timer, because the expiry value is already in the past.Use the proper mechanisms to (re)start the timer in the intended way.
Quoted 71 lines
Signed-off-by Thomas Gleixner <[email protected]>Cc "David S. Miller" <[email protected]>Cc dingtianhong <[email protected]>Cc Arnd Bergmann <[email protected]>Cc Zhangfei Gao <[email protected]>Cc Dan Carpenter <[email protected]>--- drivers/net/ethernet/hisilicon/hip04_eth.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)Index:
linux/drivers/net/ethernet/hisilicon/hip04_eth.c
===================================================================drivers/net/ethernet/hisilicon/hip04_eth.c +11
-7@@ -413,6 +413,15 @@ out:return count; } +static void hip04_start_tx_timer (struct hip04_priv *priv) +{ +unsigned long ns = priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ; + +/* allow timer to fire after half the time at the earliest */ +hrtimer_start_range_ns (&priv->tx_coalesce_timer,ns_to_ktime (ns), + ns,HRTIMER_MODE_REL ); +} +static int hip04_mac_start_xmit (struct sk_buff *skb,struct net_device *ndev) {struct hip04_priv *priv =netdev_priv (ndev);@@ -466,8 +475,7 @@ static int hip04_mac_start_xmit(struct s } }else if (!hrtimer_is_queued (&priv->tx_coalesce_timer)) {/* cleanup not pending yet, start a new timer */ -hrtimer_start_expires (&priv->tx_coalesce_timer, -HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv); }return NETDEV_TX_OK ;@@ -549,7 +557,7 @@ done:/* clean up tx descriptors and start a new timer if necessary */ tx_remaining =hip04_tx_reclaim (ndev, false);if (rx < budget && tx_remaining) -hrtimer_start_expires (&priv->tx_coalesce_timer,HRTIMER_MODE_REL ); +hip04_start_tx_timer (priv);return rx; }@@ -809,7 +817,6 @@ static int hip04_mac_probe(struct platfostruct hip04_priv *priv;struct resource *res;unsigned int irq; -ktime_t txtime;int ret; ndev =alloc_etherdev (sizeof (struct hip04_priv ));@@ -846,9 +853,6 @@ static int hip04_mac_probe(struct platfo*/ priv->tx_coalesce_frames =TX_DESC_NUM *3 /4 ; priv->tx_coalesce_usecs =200 ; -/* allow timer to fire after half the time at the earliest */ - txtime =ktime_set (0 , priv->tx_coalesce_usecs *NSEC_PER_USEC /2 ); -hrtimer_set_expires_range (&priv->tx_coalesce_timer, txtime, txtime); priv->tx_coalesce_timer.function = tx_done; priv->map =syscon_node_to_regmap (arg.np);.
Re: [patch v2] net: hip04: Make tx coalesce timer actually work
In reply to Thomas GleixnerQuoted 11 lines · On Tuesday 14 April 2015 21:42:42 Thomas Gleixner wrote:
The code sets the expiry value of the timer to a relative value and starts it with
hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).So once the timer expired, a new call to
hrtimer_start_expiresresults in an immidiately expired timer, because the expiry value is already in the past.Use the proper mechanisms to (re)start the timer in the intended way.
Quoted 8 lines
Signed-off-by Thomas Gleixner <[email protected]>Cc "David S. Miller" <[email protected]>Cc dingtianhong <[email protected]>Cc Arnd Bergmann <[email protected]>Cc Zhangfei Gao <[email protected]>Cc Dan Carpenter <[email protected]>
Re: [patch v2] net: hip04: Make tx coalesce timer actually work
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Date: Tue, 14 Apr 2015 21:42:42 +0200 (CEST)
Quoted 5 lines
The code sets the expiry value of the timer to a relative value and starts it with
hrtimer_start_expires. That's fine, but that only works once. The timer is started in relative mode, so the expiry value gets overwritten with the absolut expiry time (now + expiry).
Quoted 7 lines
So once the timer expired, a new call to
hrtimer_start_expiresresults in an immidiately expired timer, because the expiry value is already in the past.Use the proper mechanisms to (re)start the timer in the intended way.
Signed-off-by Thomas Gleixner <[email protected]>
Applied, thanks Thomas.
PATCH 5/5 staging: ozwpan: Fix hrtimer wreckage
In reply to Thomas Gleixneroz_timer_add() modifies the expiry value of an active timer, which results in data corruption.
Use hrtimer_start() and remove the silly conditional.
While at it use the proper helper function to convert milliseconds to ktime.
--- drivers/staging/ozwpan/ozproto.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
drivers/staging/ozwpan/ozproto.c +4 -13
@@ -566,23 +566,14 @@ void oz_timer_add(struct oz_pd *pd, intswitch (type) {case OZ_TIMER_TOUT :case OZ_TIMER_STOP : -if (hrtimer_active (&pd->timeout)) { -hrtimer_set_expires (&pd->timeout,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC )); -hrtimer_start_expires (&pd->timeout,HRTIMER_MODE_REL ); - }else { -hrtimer_start (&pd->timeout,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC ),HRTIMER_MODE_REL ); - } +hrtimer_start (&pd->timeout,ms_to_ktime (due_time), +HRTIMER_MODE_REL ); pd->timeout_type = type;break ;case OZ_TIMER_HEARTBEAT :if (!hrtimer_active (&pd->heartbeat)) -hrtimer_start (&pd->heartbeat,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC ),HRTIMER_MODE_REL ); +hrtimer_start (&pd->heartbeat,ms_to_ktime (due_time), +HRTIMER_MODE_REL );break ; }spin_unlock_bh (&g_polling_lock);
Re: [patch 5/5] staging: ozwpan: Fix hrtimer wreckage
Quoted 8 lines · Thomas Gleixner
oz_timer_add()modifies the expiry value of an active timer, which results in data corruption.Use
hrtimer_start()and remove the silly conditional.While at it use the proper helper function to convert milliseconds to ktime.
Quoted 7 lines
Signed-off-by Thomas Gleixner <[email protected]>Cc Shigekatsu Tateno <[email protected]>Cc Greg Kroah-Hartman <[email protected]>--- drivers/staging/ozwpan/ozproto.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
tip-bot for Thomas Gleixner
merge bot[tip:timers/core] staging: ozwpan: Fix hrtimer wreckage
In reply to Thomas Gleixnerstaging: ozwpan: Fix hrtimer wreckage
Commit-ID: 5a447f09a8dffc0dd7569aec614ad3613a050098 Gitweb: http://git.kernel.org/tip/5a447f09a8dffc0dd7569aec614ad3613a050098 Author: Thomas Gleixner <[email protected]> AuthorDate: Mon, 13 Apr 2015 21:02:25 +0000 Committer: Thomas Gleixner <[email protected]> CommitDate: Mon, 20 Jul 2015 11:37:46 +0200
staging: ozwpan: Fix hrtimer wreckage
oz_timer_add() modifies the expiry value of an active timer, which results in data corruption.
Use hrtimer_start() and remove the silly conditional.
While at it use the proper helper function to convert milliseconds to ktime.
--- drivers/staging/ozwpan/ozproto.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
drivers/staging/ozwpan/ozproto.c +4 -13
@@ -566,23 +566,14 @@ void oz_timer_add(struct oz_pd *pd, int type, unsigned long due_time)switch (type) {case OZ_TIMER_TOUT :case OZ_TIMER_STOP : -if (hrtimer_active (&pd->timeout)) { -hrtimer_set_expires (&pd->timeout,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC )); -hrtimer_start_expires (&pd->timeout,HRTIMER_MODE_REL ); - }else { -hrtimer_start (&pd->timeout,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC ),HRTIMER_MODE_REL ); - } +hrtimer_start (&pd->timeout,ms_to_ktime (due_time), +HRTIMER_MODE_REL ); pd->timeout_type = type;break ;case OZ_TIMER_HEARTBEAT :if (!hrtimer_active (&pd->heartbeat)) -hrtimer_start (&pd->heartbeat,ktime_set (due_time / -MSEC_PER_SEC , (due_time %MSEC_PER_SEC ) * -NSEC_PER_MSEC ),HRTIMER_MODE_REL ); +hrtimer_start (&pd->heartbeat,ms_to_ktime (due_time), +HRTIMER_MODE_REL );break ; }spin_unlock_bh (&g_polling_lock);