Skip to content

Commit 8003c9a

Browse files
Wanpeng Libonzini
authored andcommitted
KVM: LAPIC: add APIC Timer periodic/oneshot mode VMX preemption timer support
Most windows guests still utilize APIC Timer periodic/oneshot mode instead of tsc-deadline mode, and the APIC Timer periodic/oneshot mode are still emulated by high overhead hrtimer on host. This patch converts the expected expire time of the periodic/oneshot mode to guest deadline tsc in order to leverage VMX preemption timer logic for APIC Timer tsc-deadline mode. After each preemption timer vmexit preemption timer is restarted to emulate LVTT current-count register is automatically reloaded from the initial-count register when the count reaches 0. This patch reduces ~5600 cycles for each APIC Timer periodic mode operation virtualization. Cc: Paolo Bonzini <[email protected]> Cc: Radim Krčmář <[email protected]> Cc: Yunhong Jiang <[email protected]> Signed-off-by: Wanpeng Li <[email protected]> [Squashed with my fixes that were reviewed-by Paolo.] Signed-off-by: Radim Krčmář <[email protected]>
1 parent 7e810a3 commit 8003c9a

File tree

2 files changed

+76
-28
lines changed

2 files changed

+76
-28
lines changed

arch/x86/kvm/lapic.c

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ static void apic_send_ipi(struct kvm_lapic *apic)
10901090

10911091
static u32 apic_get_tmcct(struct kvm_lapic *apic)
10921092
{
1093-
ktime_t remaining;
1093+
ktime_t remaining, now;
10941094
s64 ns;
10951095
u32 tmcct;
10961096

@@ -1101,7 +1101,8 @@ static u32 apic_get_tmcct(struct kvm_lapic *apic)
11011101
apic->lapic_timer.period == 0)
11021102
return 0;
11031103

1104-
remaining = hrtimer_get_remaining(&apic->lapic_timer.timer);
1104+
now = apic->lapic_timer.timer.base->get_time();
1105+
remaining = ktime_sub(apic->lapic_timer.target_expiration, now);
11051106
if (ktime_to_ns(remaining) < 0)
11061107
remaining = ktime_set(0, 0);
11071108

@@ -1348,16 +1349,34 @@ static void start_sw_tscdeadline(struct kvm_lapic *apic)
13481349
}
13491350

13501351
static void start_sw_period(struct kvm_lapic *apic)
1352+
{
1353+
if (!apic->lapic_timer.period)
1354+
return;
1355+
1356+
if (apic_lvtt_oneshot(apic) &&
1357+
ktime_after(apic->lapic_timer.timer.base->get_time(),
1358+
apic->lapic_timer.target_expiration)) {
1359+
apic_timer_expired(apic);
1360+
return;
1361+
}
1362+
1363+
hrtimer_start(&apic->lapic_timer.timer,
1364+
apic->lapic_timer.target_expiration,
1365+
HRTIMER_MODE_ABS_PINNED);
1366+
}
1367+
1368+
static bool set_target_expiration(struct kvm_lapic *apic)
13511369
{
13521370
ktime_t now;
1371+
u64 tscl = rdtsc();
13531372

1354-
/* lapic timer in oneshot or periodic mode */
13551373
now = apic->lapic_timer.timer.base->get_time();
13561374
apic->lapic_timer.period = (u64)kvm_lapic_get_reg(apic, APIC_TMICT)
1357-
* APIC_BUS_CYCLE_NS * apic->divide_count;
1375+
* APIC_BUS_CYCLE_NS * apic->divide_count;
13581376

13591377
if (!apic->lapic_timer.period)
1360-
return;
1378+
return false;
1379+
13611380
/*
13621381
* Do not allow the guest to program periodic timers with small
13631382
* interval, since the hrtimers are not throttled by the host
@@ -1376,10 +1395,6 @@ static void start_sw_period(struct kvm_lapic *apic)
13761395
}
13771396
}
13781397

1379-
hrtimer_start(&apic->lapic_timer.timer,
1380-
ktime_add_ns(now, apic->lapic_timer.period),
1381-
HRTIMER_MODE_ABS_PINNED);
1382-
13831398
apic_debug("%s: bus cycle is %" PRId64 "ns, now 0x%016"
13841399
PRIx64 ", "
13851400
"timer initial count 0x%x, period %lldns, "
@@ -1389,6 +1404,21 @@ static void start_sw_period(struct kvm_lapic *apic)
13891404
apic->lapic_timer.period,
13901405
ktime_to_ns(ktime_add_ns(now,
13911406
apic->lapic_timer.period)));
1407+
1408+
apic->lapic_timer.tscdeadline = kvm_read_l1_tsc(apic->vcpu, tscl) +
1409+
nsec_to_cycles(apic->vcpu, apic->lapic_timer.period);
1410+
apic->lapic_timer.target_expiration = ktime_add_ns(now, apic->lapic_timer.period);
1411+
1412+
return true;
1413+
}
1414+
1415+
static void advance_periodic_target_expiration(struct kvm_lapic *apic)
1416+
{
1417+
apic->lapic_timer.tscdeadline +=
1418+
nsec_to_cycles(apic->vcpu, apic->lapic_timer.period);
1419+
apic->lapic_timer.target_expiration =
1420+
ktime_add_ns(apic->lapic_timer.target_expiration,
1421+
apic->lapic_timer.period);
13921422
}
13931423

13941424
bool kvm_lapic_hv_timer_in_use(struct kvm_vcpu *vcpu)
@@ -1406,22 +1436,12 @@ static void cancel_hv_timer(struct kvm_lapic *apic)
14061436
apic->lapic_timer.hv_timer_in_use = false;
14071437
}
14081438

1409-
void kvm_lapic_expired_hv_timer(struct kvm_vcpu *vcpu)
1410-
{
1411-
struct kvm_lapic *apic = vcpu->arch.apic;
1412-
1413-
WARN_ON(!apic->lapic_timer.hv_timer_in_use);
1414-
WARN_ON(swait_active(&vcpu->wq));
1415-
cancel_hv_timer(apic);
1416-
apic_timer_expired(apic);
1417-
}
1418-
EXPORT_SYMBOL_GPL(kvm_lapic_expired_hv_timer);
1419-
14201439
static bool start_hv_timer(struct kvm_lapic *apic)
14211440
{
14221441
u64 tscdeadline = apic->lapic_timer.tscdeadline;
14231442

1424-
if (atomic_read(&apic->lapic_timer.pending) ||
1443+
if ((atomic_read(&apic->lapic_timer.pending) &&
1444+
!apic_lvtt_period(apic)) ||
14251445
kvm_x86_ops->set_hv_timer(apic->vcpu, tscdeadline)) {
14261446
if (apic->lapic_timer.hv_timer_in_use)
14271447
cancel_hv_timer(apic);
@@ -1430,22 +1450,39 @@ static bool start_hv_timer(struct kvm_lapic *apic)
14301450
hrtimer_cancel(&apic->lapic_timer.timer);
14311451

14321452
/* In case the sw timer triggered in the window */
1433-
if (atomic_read(&apic->lapic_timer.pending))
1453+
if (atomic_read(&apic->lapic_timer.pending) &&
1454+
!apic_lvtt_period(apic))
14341455
cancel_hv_timer(apic);
14351456
}
14361457
trace_kvm_hv_timer_state(apic->vcpu->vcpu_id,
14371458
apic->lapic_timer.hv_timer_in_use);
14381459
return apic->lapic_timer.hv_timer_in_use;
14391460
}
14401461

1462+
void kvm_lapic_expired_hv_timer(struct kvm_vcpu *vcpu)
1463+
{
1464+
struct kvm_lapic *apic = vcpu->arch.apic;
1465+
1466+
WARN_ON(!apic->lapic_timer.hv_timer_in_use);
1467+
WARN_ON(swait_active(&vcpu->wq));
1468+
cancel_hv_timer(apic);
1469+
apic_timer_expired(apic);
1470+
1471+
if (apic_lvtt_period(apic) && apic->lapic_timer.period) {
1472+
advance_periodic_target_expiration(apic);
1473+
if (!start_hv_timer(apic))
1474+
start_sw_period(apic);
1475+
}
1476+
}
1477+
EXPORT_SYMBOL_GPL(kvm_lapic_expired_hv_timer);
1478+
14411479
void kvm_lapic_switch_to_hv_timer(struct kvm_vcpu *vcpu)
14421480
{
14431481
struct kvm_lapic *apic = vcpu->arch.apic;
14441482

14451483
WARN_ON(apic->lapic_timer.hv_timer_in_use);
14461484

1447-
if (apic_lvtt_tscdeadline(apic))
1448-
start_hv_timer(apic);
1485+
start_hv_timer(apic);
14491486
}
14501487
EXPORT_SYMBOL_GPL(kvm_lapic_switch_to_hv_timer);
14511488

@@ -1462,17 +1499,22 @@ void kvm_lapic_switch_to_sw_timer(struct kvm_vcpu *vcpu)
14621499
if (atomic_read(&apic->lapic_timer.pending))
14631500
return;
14641501

1465-
start_sw_tscdeadline(apic);
1502+
if (apic_lvtt_period(apic) || apic_lvtt_oneshot(apic))
1503+
start_sw_period(apic);
1504+
else if (apic_lvtt_tscdeadline(apic))
1505+
start_sw_tscdeadline(apic);
14661506
}
14671507
EXPORT_SYMBOL_GPL(kvm_lapic_switch_to_sw_timer);
14681508

14691509
static void start_apic_timer(struct kvm_lapic *apic)
14701510
{
14711511
atomic_set(&apic->lapic_timer.pending, 0);
14721512

1473-
if (apic_lvtt_period(apic) || apic_lvtt_oneshot(apic))
1474-
start_sw_period(apic);
1475-
else if (apic_lvtt_tscdeadline(apic)) {
1513+
if (apic_lvtt_period(apic) || apic_lvtt_oneshot(apic)) {
1514+
if (set_target_expiration(apic) &&
1515+
!(kvm_x86_ops->set_hv_timer && start_hv_timer(apic)))
1516+
start_sw_period(apic);
1517+
} else if (apic_lvtt_tscdeadline(apic)) {
14761518
if (!(kvm_x86_ops->set_hv_timer && start_hv_timer(apic)))
14771519
start_sw_tscdeadline(apic);
14781520
}
@@ -1923,6 +1965,7 @@ static enum hrtimer_restart apic_timer_fn(struct hrtimer *data)
19231965
apic_timer_expired(apic);
19241966

19251967
if (lapic_is_periodic(apic)) {
1968+
advance_periodic_target_expiration(apic);
19261969
hrtimer_add_expires_ns(&ktimer->timer, ktimer->period);
19271970
return HRTIMER_RESTART;
19281971
} else
@@ -2007,6 +2050,10 @@ void kvm_inject_apic_timer_irqs(struct kvm_vcpu *vcpu)
20072050
kvm_apic_local_deliver(apic, APIC_LVTT);
20082051
if (apic_lvtt_tscdeadline(apic))
20092052
apic->lapic_timer.tscdeadline = 0;
2053+
if (apic_lvtt_oneshot(apic)) {
2054+
apic->lapic_timer.tscdeadline = 0;
2055+
apic->lapic_timer.target_expiration = ktime_set(0, 0);
2056+
}
20102057
atomic_set(&apic->lapic_timer.pending, 0);
20112058
}
20122059
}

arch/x86/kvm/lapic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
struct kvm_timer {
1616
struct hrtimer timer;
1717
s64 period; /* unit: ns */
18+
ktime_t target_expiration;
1819
u32 timer_mode;
1920
u32 timer_mode_mask;
2021
u64 tscdeadline;

0 commit comments

Comments
 (0)