Skip to content

Commit 3e0bc4b

Browse files
committed
x86/idle: Implement a new MWAIT IPI-elision algorithm
In order elide IPIs, we must be able to identify whether a target CPU is in MWAIT at the point it is woken up. i.e. the store to wake it up must also identify the state. Create a new in_mwait variable beside __softirq_pending, so we can use a CMPXCHG to set the softirq while also observing the status safely. Implement an x86 version of arch_pend_softirq() which does this. In mwait_idle_with_hints(), advertise in_mwait, with an explanation of precisely what it means. X86_BUG_MONITOR can be accounted for simply by not advertising in_mwait. Signed-off-by: Andrew Cooper <[email protected]> Acked-by: Roger Pau Monné <[email protected]>
1 parent b473e5e commit 3e0bc4b

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

xen/arch/x86/acpi/cpu_idle.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,21 @@ __initcall(cpu_idle_key_init);
443443
void mwait_idle_with_hints(unsigned int eax, unsigned int ecx)
444444
{
445445
unsigned int cpu = smp_processor_id();
446-
const unsigned int *this_softirq_pending = &softirq_pending(cpu);
446+
irq_cpustat_t *stat = &irq_stat[cpu];
447+
const unsigned int *this_softirq_pending = &stat->__softirq_pending;
448+
449+
/*
450+
* By setting in_mwait, we promise to other CPUs that we'll notice changes
451+
* to __softirq_pending without being sent an IPI. We achieve this by
452+
* either not going to sleep, or by having hardware notice on our behalf.
453+
*
454+
* Some errata exist where MONITOR doesn't work properly, and the
455+
* workaround is to force the use of an IPI. Cause this to happen by
456+
* simply not advertising ourselves as being in_mwait.
457+
*/
458+
alternative_io("movb $1, %[in_mwait]",
459+
"", X86_BUG_MONITOR,
460+
[in_mwait] "=m" (stat->in_mwait));
447461

448462
monitor(this_softirq_pending, 0, 0);
449463

@@ -455,6 +469,10 @@ void mwait_idle_with_hints(unsigned int eax, unsigned int ecx)
455469
mwait(eax, ecx);
456470
spec_ctrl_exit_idle(info);
457471
}
472+
473+
alternative_io("movb $0, %[in_mwait]",
474+
"", X86_BUG_MONITOR,
475+
[in_mwait] "=m" (stat->in_mwait));
458476
}
459477

460478
static void acpi_processor_ffh_cstate_enter(struct acpi_processor_cx *cx)

xen/arch/x86/include/asm/hardirq.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@
55
#include <xen/types.h>
66

77
typedef struct {
8-
unsigned int __softirq_pending;
8+
/*
9+
* The layout is important. Any CPU can set bits in __softirq_pending,
10+
* but in_mwait is a status bit owned by the CPU. softirq_mwait_raw must
11+
* cover both, and must be in a single cacheline.
12+
*/
13+
union {
14+
struct {
15+
unsigned int __softirq_pending;
16+
bool in_mwait;
17+
};
18+
uint64_t softirq_mwait_raw;
19+
};
20+
921
unsigned int __local_irq_count;
1022
unsigned int nmi_count;
1123
unsigned int mce_count;

xen/arch/x86/include/asm/softirq.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef __ASM_SOFTIRQ_H__
22
#define __ASM_SOFTIRQ_H__
33

4+
#include <asm/system.h>
5+
46
#define NMI_SOFTIRQ (NR_COMMON_SOFTIRQS + 0)
57
#define TIME_CALIBRATE_SOFTIRQ (NR_COMMON_SOFTIRQS + 1)
68
#define VCPU_KICK_SOFTIRQ (NR_COMMON_SOFTIRQS + 2)
@@ -9,4 +11,50 @@
911
#define HVM_DPCI_SOFTIRQ (NR_COMMON_SOFTIRQS + 4)
1012
#define NR_ARCH_SOFTIRQS 5
1113

14+
/*
15+
* Ensure softirq @nr is pending on @cpu. Return true if an IPI can be
16+
* skipped, false if the IPI cannot be skipped.
17+
*
18+
* We use a CMPXCHG covering both __softirq_pending and in_mwait, in order to
19+
* set softirq @nr while also observing in_mwait in a race-free way.
20+
*/
21+
static always_inline bool arch_set_softirq(unsigned int nr, unsigned int cpu)
22+
{
23+
uint64_t *ptr = &irq_stat[cpu].softirq_mwait_raw;
24+
uint64_t prev, old, new;
25+
unsigned int softirq = 1U << nr;
26+
27+
old = ACCESS_ONCE(*ptr);
28+
29+
for ( ;; )
30+
{
31+
if ( old & softirq )
32+
/* Softirq already pending, nothing to do. */
33+
return true;
34+
35+
new = old | softirq;
36+
37+
prev = cmpxchg(ptr, old, new);
38+
if ( prev == old )
39+
break;
40+
41+
old = prev;
42+
}
43+
44+
/*
45+
* We have caused the softirq to become pending. If in_mwait was set, the
46+
* target CPU will notice the modification and act on it.
47+
*
48+
* We can't access the in_mwait field nicely, so use some BUILD_BUG_ON()'s
49+
* to cross-check the (1UL << 32) opencoding.
50+
*/
51+
BUILD_BUG_ON(sizeof(irq_stat[0].softirq_mwait_raw) != 8);
52+
BUILD_BUG_ON((offsetof(irq_cpustat_t, in_mwait) -
53+
offsetof(irq_cpustat_t, softirq_mwait_raw)) != 4);
54+
55+
return new & (1UL << 32) /* in_mwait */;
56+
57+
}
58+
#define arch_set_softirq arch_set_softirq
59+
1260
#endif /* __ASM_SOFTIRQ_H__ */

0 commit comments

Comments
 (0)