Skip to contentSkip to conversation

hrtimer: Cleanup usage trainwrecks treewide

PATCH In review Thomas Gleixner 8 participants 21 messages Apr 13, 2015 – Jul 20, 2015

Subsystems (9)
lore
Share & tools
lore
#0 lore raw

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

#1 lore raw

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.

Fixes 62b856397927 "perf: Add sysfs entry to adjust multiplexing interval per PMU"
Cc Stephane Eranian <[email protected]>
Cc Jiri Olsa <[email protected]>
Cc Arnaldo Carvalho de Melo <[email protected]>
---
 kernel/events/core.c |   36 ++++++++++++++++++++----------------
 1 file changed, 20 insertions(+), 16 deletions(-)
kernel/events/core.c +20 -16
Index: linux/kernel/events/core.c
===================================================================
@@ -6827,13 +6827,27 @@ perf_event_mux_interval_ms_show(struct d
 	return 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(struct
 	if (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);
#2 lore raw

Re: [patch 1/5] perf: Fixup hrtimer forward wreckage

Quoted 21 lines · Thomas Gleixner

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.

Quoted 6 lines
Fixes 62b856397927 "perf: Add sysfs entry to adjust multiplexing interval per PMU"
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 d
        return 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(struct
        if (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);
#3 lore raw

PATCH 2/5 s390: crypto: Protect poll timeout update

In reply to Thomas Gleixner

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.

Cc Ingo Tuchscherer <[email protected]>
Cc Martin Schwidefsky <[email protected]>
Cc Heiko Carstens <[email protected]>

---

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
Index: linux/drivers/s390/crypto/ap_bus.c
===================================================================
@@ -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;
 }
 
#4 lore raw

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.

Cc Ingo Tuchscherer <[email protected]>
Cc Martin Schwidefsky <[email protected]>
Cc Heiko Carstens <[email protected]>

---

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
Index: linux/drivers/s390/crypto/ap_bus.c
===================================================================
@@ -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;
}

#5 lore raw

PATCH 3/5 hrtimer: Document hrtimer_forward[_now]() proper

In reply to Thomas Gleixner

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
Index: linux/include/linux/hrtimer.h
===================================================================
@@ -418,7 +418,22 @@ static inline int hrtimer_callback_runni
 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
Index: linux/kernel/time/hrtimer.c
===================================================================
@@ -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)
 {
#6 lore raw

[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
index 05f6df1..7770676 100644
@@ -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
index 76d4bd9..b1a74ee 100644
@@ -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)
 {
#7 lore raw

PATCH 4/5 net: hip04: Make tx coalesce timer actually work

In reply to 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_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.

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(-)
drivers/net/ethernet/hisilicon/hip04_eth.c +11 -7
Index: linux/drivers/net/ethernet/hisilicon/hip04_eth.c
===================================================================
@@ -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 platfo
 	struct 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);
#8 lore raw

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_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.

Quoted 7 lines
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

#9 lore raw

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_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.

Quoted 7 lines
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

#10 lore raw

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

#11 lore raw

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

#12 lore raw

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

.

maintainer
#13 lore raw

Re: [patch 4/5] net: hip04: Make tx coalesce timer actually work

In reply to Thomas Gleixner

From: 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?

#14 lore raw

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.

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(-)
drivers/net/ethernet/hisilicon/hip04_eth.c +11 -7
Index: linux/drivers/net/ethernet/hisilicon/hip04_eth.c
===================================================================
@@ -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 platfo
 	struct 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);
Acked-by on v2 1/1
#15 lore raw

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_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.

Quoted 71 lines
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 platfo
 	struct 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);

.

Acked-by on v2 1/1
#16 lore raw

Re: [patch v2] net: hip04: Make tx coalesce timer actually work

In reply to Thomas Gleixner
Quoted 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_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.

Quoted 8 lines
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]>
maintainer Applied
#17 lore raw

Re: [patch v2] net: hip04: Make tx coalesce timer actually work

In reply to Thomas Gleixner

From: 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_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.

Applied, thanks Thomas.

#18 lore raw

PATCH 5/5 staging: ozwpan: Fix hrtimer wreckage

In reply to 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.

Cc Shigekatsu Tateno <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
---
 drivers/staging/ozwpan/ozproto.c |   17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)
drivers/staging/ozwpan/ozproto.c +4 -13
Index: linux/drivers/staging/ozwpan/ozproto.c
===================================================================
@@ -566,23 +566,14 @@ void oz_timer_add(struct oz_pd *pd, int
 	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);
maintainer Acked-by on v1 5/5
#19 lore raw

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
Cc Shigekatsu Tateno <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
---
 drivers/staging/ozwpan/ozproto.c |   17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)
#20 lore raw

[tip:timers/core] staging: ozwpan: Fix hrtimer wreckage

In reply to Thomas Gleixner
staging: 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
index 1ba24a2..23aa817 100644
@@ -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);