summaryrefslogtreecommitdiffstats
path: root/debian/patches-rt/hrtimer-Introduce-expiry-spin-lock.patch
blob: 6a1aac1a95e3f05639b6d629812408dc82ed6fc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
From: Anna-Maria Gleixner <anna-maria@linutronix.de>
Date: Mon, 27 May 2019 16:54:04 +0200
Subject: [PATCH] hrtimer: Introduce expiry spin lock
Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/5.2/older/patches-5.2.10-rt5.tar.xz

When deleting a hrtimer, it is possible, that the CPU has to spin, because the
hrtimer is marked as running. This is done via cpu_relax() and repeating trying to
delete the timer. When doing this in a virtual machine, the CPU wastes vcpu time
because of spinning as long as the timer is no longer running.

The spinning and time wasting, could be prevented by using PARAVIRT_SPINLOCKS
and introducing a per timer base spin lock for expiry. The lock is hold during
expiring the timers of a base. When the deletion of a timer wasn't successful,
because the timer is running at the moment, the expiry lock is trying to
accessed instead of cpu_realax(). The lock is already held by the CPU expring
the timers, so the CPU could be scheduled out instead of spinning until the lock
is released, because of the PARAVIRT_SPINLOCKS code. Thereby wasting time
spinning around is prevented.

The code isn't done conditionally on PARAVIRT_SPINLOCKS. The lock is taken only
at two places. In one of them the lock is directly dropped after accessing
it. So the probability for a slowpath when taking the lock is very low. But this
keeps the code cleaner than introducing several CONFIG_PARAVIRT_SPINLOCKS
dependend code paths and struct members.

Signed-off-by: Anna-Maria Gleixner <anna-maria@linutronix.de>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 include/linux/hrtimer.h |    3 +++
 kernel/time/hrtimer.c   |   15 ++++++++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

--- a/include/linux/hrtimer.h
+++ b/include/linux/hrtimer.h
@@ -183,6 +183,8 @@ enum  hrtimer_base_type {
  * @nr_retries:		Total number of hrtimer interrupt retries
  * @nr_hangs:		Total number of hrtimer interrupt hangs
  * @max_hang_time:	Maximum time spent in hrtimer_interrupt
+ * @softirq_expiry_lock: Lock which is taken while softirq based hrtimer are
+ *			 expired
  * @expires_next:	absolute time of the next event, is required for remote
  *			hrtimer enqueue; it is the total first expiry time (hard
  *			and soft hrtimer are taken into account)
@@ -210,6 +212,7 @@ struct hrtimer_cpu_base {
 	unsigned short			nr_hangs;
 	unsigned int			max_hang_time;
 #endif
+	spinlock_t			softirq_expiry_lock;
 	ktime_t				expires_next;
 	struct hrtimer			*next_timer;
 	ktime_t				softirq_expires_next;
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -930,6 +930,16 @@ u64 hrtimer_forward(struct hrtimer *time
 }
 EXPORT_SYMBOL_GPL(hrtimer_forward);
 
+static void hrtimer_grab_expiry_lock(const struct hrtimer *timer)
+{
+	struct hrtimer_clock_base *base = timer->base;
+
+	if (base && base->cpu_base) {
+		spin_lock(&base->cpu_base->softirq_expiry_lock);
+		spin_unlock(&base->cpu_base->softirq_expiry_lock);
+	}
+}
+
 /*
  * enqueue_hrtimer - internal function to (re)start a timer
  *
@@ -1162,7 +1172,7 @@ int hrtimer_cancel(struct hrtimer *timer
 
 		if (ret >= 0)
 			return ret;
-		cpu_relax();
+		hrtimer_grab_expiry_lock(timer);
 	}
 }
 EXPORT_SYMBOL_GPL(hrtimer_cancel);
@@ -1459,6 +1469,7 @@ static __latent_entropy void hrtimer_run
 	unsigned long flags;
 	ktime_t now;
 
+	spin_lock(&cpu_base->softirq_expiry_lock);
 	raw_spin_lock_irqsave(&cpu_base->lock, flags);
 
 	now = hrtimer_update_base(cpu_base);
@@ -1468,6 +1479,7 @@ static __latent_entropy void hrtimer_run
 	hrtimer_update_softirq_timer(cpu_base, true);
 
 	raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
+	spin_unlock(&cpu_base->softirq_expiry_lock);
 }
 
 #ifdef CONFIG_HIGH_RES_TIMERS
@@ -1809,6 +1821,7 @@ int hrtimers_prepare_cpu(unsigned int cp
 	cpu_base->softirq_next_timer = NULL;
 	cpu_base->expires_next = KTIME_MAX;
 	cpu_base->softirq_expires_next = KTIME_MAX;
+	spin_lock_init(&cpu_base->softirq_expiry_lock);
 	return 0;
 }