|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [XTF PATCH v1 15/15] x86: add interrupt latency test (hvm64)
From: Denis Mukhin <dmukhin@xxxxxxxx>
Introduce new test to measure IPI/posted interrupts latencies.
The test uses 2 vCPUs.
vCPU#0 (BSP) issues IPI vector 0x21 to vCPU#1 (AP) in a loop, then performs
data accounting. vCPU#1 runs some noisy code which keeps trashing the CPU
cache.
vCPU#1 (AP) is brought up via minimal framework which is frankenstained
from xsa-472-3. It just starts AP in hvm64 flat mode. Xen conveniently
provides a way to skip INIT/SIPI steps by using VCPUOP_{initialise,up}
hypercalls, which simplified solution.
Had to plumb minimal GDT setup and re-initialize APIC into xAPIC mode.
Also, I was lucky with Xen exposing TSC frequency via CPUID.
Exported and used domctl hypercalls, specifically so it is possible to use
XEN_DOMCTL_DEBUG_OP_POSTED_INTR.
Added missing evtchn hypercall to EVTCHNOP_bind_ipi/EVTCHNOP_unmask test
vector.
There are several measurements done.
vCPU0 vCPU1
executes some workload
+-> t0
| |
| write ICR2 [*]
r write ICR
e |---------------- interrupt -----------> t0' = recv_tsc
p return from APIC emulation [*'] ISR
e | t1' = handler_tsc
a t1
t |
| sync against ISR
| |
+---+
vCPU0 vCPU1
IRQ delivery latency : t0' - t0
IRQ issue latency : t1 - t0
IRQ handler latency : t1' - t0'
[*] For event channel that is EVTCHNOP_send hypercall.
[*'] For event channel that is return from the EVTCHNOP_send hypercall.
Signed-off-by: Denis Mukhin <dmukhin@xxxxxxxx>
---
tests/interrupt-latency/Makefile | 11 +
tests/interrupt-latency/ap-asm.S | 50 ++
tests/interrupt-latency/ap-main.c | 164 +++++++
tests/interrupt-latency/ap.h | 12 +
tests/interrupt-latency/bootargs.in | 1 +
tests/interrupt-latency/extra.cfg.in | 1 +
tests/interrupt-latency/main.c | 706 +++++++++++++++++++++++++++
7 files changed, 945 insertions(+)
create mode 100644 tests/interrupt-latency/Makefile
create mode 100644 tests/interrupt-latency/ap-asm.S
create mode 100644 tests/interrupt-latency/ap-main.c
create mode 100644 tests/interrupt-latency/ap.h
create mode 100644 tests/interrupt-latency/bootargs.in
create mode 100644 tests/interrupt-latency/extra.cfg.in
create mode 100644 tests/interrupt-latency/main.c
diff --git a/tests/interrupt-latency/Makefile b/tests/interrupt-latency/Makefile
new file mode 100644
index 000000000000..43f2a3c69a62
--- /dev/null
+++ b/tests/interrupt-latency/Makefile
@@ -0,0 +1,11 @@
+include $(ROOT)/build/common.mk
+
+NAME := interrupt-latency
+CATEGORY := functional
+TEST-ENVS := hvm64
+
+TEST-EXTRA-CFG := extra.cfg.in
+
+obj-perenv += main.o ap-main.o ap-asm.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/interrupt-latency/ap-asm.S b/tests/interrupt-latency/ap-asm.S
new file mode 100644
index 000000000000..60587c803ac2
--- /dev/null
+++ b/tests/interrupt-latency/ap-asm.S
@@ -0,0 +1,50 @@
+#include <arch/segment.h>
+
+ .text
+
+ .globl ap_load_gdt
+ .type ap_load_gdt, @function
+ap_load_gdt:
+ lgdt gdt_ptr(%rip)
+ pushq $__KERN_CS
+ leaq 1f(%rip), %rax
+ pushq %rax
+ lretq
+1:
+ ret
+ .size ap_load_gdt, . - ap_load_gdt
+
+ .globl x86_vec_avail_entry
+ .type x86_vec_avail_entry, @function
+x86_vec_avail_entry:
+ pushq %rax
+ pushq %rcx
+ pushq %rdx
+ pushq %rsi
+ pushq %rdi
+ pushq %r8
+ pushq %r9
+ pushq %r10
+ pushq %r11
+ pushq %r12
+
+ movq %rsp, %r12
+ andq $-16, %rsp
+
+ call ap_irq_handler
+
+ movq %r12, %rsp
+
+ popq %r12
+ popq %r11
+ popq %r10
+ popq %r9
+ popq %r8
+ popq %rdi
+ popq %rsi
+ popq %rdx
+ popq %rcx
+ popq %rax
+
+ iretq
+ .size x86_vec_avail_entry, . - x86_vec_avail_entry
diff --git a/tests/interrupt-latency/ap-main.c
b/tests/interrupt-latency/ap-main.c
new file mode 100644
index 000000000000..af781232df40
--- /dev/null
+++ b/tests/interrupt-latency/ap-main.c
@@ -0,0 +1,164 @@
+/**
+ * IPI latency test.
+ */
+
+#include <arch/apic.h>
+#include <arch/desc.h>
+#include <arch/system.h>
+
+#include <xtf/framework.h>
+#include <xtf/time.h>
+#include <xtf.h>
+
+#include "ap.h"
+
+#if !defined(NR_APS)
+#error NR_APS is not defined!
+#endif
+
+struct ap_percpu {
+ unsigned int apic_id;
+ unsigned int cpu;
+};
+
+static struct ap_percpu ap_cpu[NR_APS] __page_aligned_bss;
+
+static uint8_t ap_stack[NR_APS][PAGE_SIZE] __page_aligned_bss;
+
+/* See ap-asm.S */
+extern void ap_load_gdt(void);
+extern void x86_vec_avail_entry(void);
+
+static const struct xtf_idte x86_vec_avail_idte = {
+ .addr = _u(x86_vec_avail_entry),
+ .cs = __KERN_CS,
+ .dpl = 0,
+};
+
+int ap_set_irq(unsigned int apic_id, unsigned int vector)
+{
+ apic_write(APIC_ICR2, SET_xAPIC_ID(apic_id));
+ apic_write(APIC_ICR, vector | APIC_DM_FIXED);
+
+ return 0;
+}
+
+static int ap_ipi_init(void)
+{
+ int rc;
+
+ xtf_load_idt();
+
+ xtf_set_idte(X86_VEC_AVAIL + 0, &x86_vec_avail_idte);
+ xtf_set_idte(X86_VEC_AVAIL + 1, &x86_vec_avail_idte);
+
+ rc = apic_init(APIC_MODE_XAPIC);
+ if ( rc )
+ return rc;
+
+ apic_write(APIC_TPR, 0);
+
+ return 0;
+}
+
+static struct ap_percpu *ap_percpu_ptr(unsigned int cpu)
+{
+ if ( !cpu || cpu > NR_APS )
+ return NULL;
+
+ return &ap_cpu[cpu - 1];
+}
+
+static void ap_main(struct ap_percpu *percpu, void (* thread)(void))
+{
+ unsigned int apic_id;
+
+ xtf_irq_disable();
+
+ ap_load_gdt();
+
+ if ( ap_ipi_init() )
+ goto fail;
+
+ apic_id = get_apic_id();
+ ACCESS_ONCE(percpu->apic_id) = apic_id;
+
+ while ( !ACCESS_ONCE(percpu->cpu) )
+ cpu_relax();
+
+ thread();
+
+ fail:
+ arch_crash_hard();
+}
+
+int ap_start(unsigned int cpu, void (* thread)(void))
+{
+ struct xen_vcpu_hvm_context ap_ctx = { 0 };
+ struct ap_percpu *percpu = ap_percpu_ptr(cpu);
+ int rc;
+
+ if ( cpu == 0 )
+ /* Nothing to do. */
+ return 0;
+
+ /*
+ * Setup vCPU context.
+ */
+
+ ap_ctx.mode = VCPU_HVM_MODE_64B;
+
+ ap_ctx.cpu_regs.x86_64.rdi = _u(percpu);
+ ap_ctx.cpu_regs.x86_64.rsi = _u(thread);
+ ap_ctx.cpu_regs.x86_64.rip = _u(ap_main);
+ /* Stack grows backwards and keep canary. */
+ ap_ctx.cpu_regs.x86_64.rsp = _u(&ap_stack[cpu - 1][PAGE_SIZE]) - 8;
+
+ ap_ctx.cpu_regs.x86_64.rflags = X86_EFLAGS_MBS;
+
+ /* Same execution environment as BSP. */
+ ap_ctx.cpu_regs.x86_64.cr0 = read_cr0();
+ ap_ctx.cpu_regs.x86_64.cr3 = read_cr3();
+ ap_ctx.cpu_regs.x86_64.cr4 = read_cr4();
+ ap_ctx.cpu_regs.x86_64.efer = rdmsr(MSR_EFER);
+
+ rc = hypercall_vcpu_op(VCPUOP_initialise, cpu, &ap_ctx);
+ if ( rc )
+ {
+ xtf_error("BSP: unable to init CPU%u: %d\n", cpu, rc);
+ return rc;
+ }
+
+ /*
+ * Start the vCPU execution.
+ */
+
+ rc = hypercall_vcpu_op(VCPUOP_up, cpu, NULL);
+ if ( rc )
+ {
+ xtf_error("BSP: unable to start CPU%u: %d\n", cpu, rc);
+ return rc;
+ }
+
+ /*
+ * Synchronize the AP thread execution.
+ */
+
+ while ( !ACCESS_ONCE(percpu->apic_id) )
+ cpu_relax();
+
+ ACCESS_ONCE(percpu->cpu) = cpu;
+ smp_mb();
+
+ return 0;
+}
+
+unsigned int ap_apic_id(unsigned int cpu)
+{
+ struct ap_percpu *percpu = ap_percpu_ptr(cpu);
+
+ if ( percpu )
+ return percpu->apic_id;
+
+ return get_apic_id();
+}
diff --git a/tests/interrupt-latency/ap.h b/tests/interrupt-latency/ap.h
new file mode 100644
index 000000000000..02a214af8fea
--- /dev/null
+++ b/tests/interrupt-latency/ap.h
@@ -0,0 +1,12 @@
+#ifndef __AP_H_
+#define __AP_H_
+
+#define NR_APS 1
+
+int ap_start(unsigned int cpu, void (* thread)(void));
+
+int ap_set_irq(unsigned int apic_id, unsigned int vector);
+
+unsigned int ap_apic_id(unsigned int cpu);
+
+#endif /* __AP_H_ */
diff --git a/tests/interrupt-latency/bootargs.in
b/tests/interrupt-latency/bootargs.in
new file mode 100644
index 000000000000..c56ba76658e1
--- /dev/null
+++ b/tests/interrupt-latency/bootargs.in
@@ -0,0 +1 @@
+sched=null dom0_max_vcpus=2 dom0_vcpus_pin smt=0 cpuidle=0
cpufreq=xen:performance watchdog=0 bootscrub=0 svm_avic=1 -- samples=20000000
diff --git a/tests/interrupt-latency/extra.cfg.in
b/tests/interrupt-latency/extra.cfg.in
new file mode 100644
index 000000000000..95c6aa2abaa9
--- /dev/null
+++ b/tests/interrupt-latency/extra.cfg.in
@@ -0,0 +1 @@
+vcpus = 2
diff --git a/tests/interrupt-latency/main.c b/tests/interrupt-latency/main.c
new file mode 100644
index 000000000000..907bad7752ec
--- /dev/null
+++ b/tests/interrupt-latency/main.c
@@ -0,0 +1,706 @@
+/**
+ * Interrupt latency test:
+ * - IPI
+ * - Syntheric interrupts (evtchn)
+ */
+
+#include <arch/system.h>
+
+#include <xtf/statistics.h>
+#include <xtf/time.h>
+#include <xtf.h>
+
+#include "ap.h"
+
+const char test_title[] = "Interrupt latency test";
+
+#define DEST_CPU_ID 1
+#define NOISE_PAGE_SIZE KB(256)
+
+/* Interrupt acceleration control operations. */
+enum {
+ POSTED_INTR_DISABLE = 0,
+ POSTED_INTR_ENABLE = 1,
+ POSTED_INTR_DUMP_STATS = 2,
+};
+
+/* Collected statistics. */
+enum stats_id {
+ STAT_IRQ_SEND = 0,
+ STAT_IRQ_DELIVERY = 1,
+ STAT_IRQ_HANDLER = 2,
+ NR_STATS,
+};
+
+enum metric_id {
+ METRIC_MINIMUM = 0,
+ METRIC_MAXIMUM = 1,
+ METRIC_AVERAGE = 2,
+ METRIC_P99 = 3,
+ METRIC_ELAPSED_TIME = 4,
+ NR_METRICS,
+};
+
+enum workload_id {
+ WORKLOAD_IPI_NOACCEL = 0,
+ WORKLOAD_IPI_ACCEL = 1,
+ WORKLOAD_EVTCHN_NOACCEL = 2,
+ WORKLOAD_EVTCHN_ACCEL = 3,
+ NR_WORKLOADS,
+};
+
+enum group_id {
+ TEST_IPI = 0,
+ TEST_EVTCHN = 1,
+ NR_TESTS,
+};
+
+struct event {
+ union {
+ evtchn_port_t evtchn;
+ struct {
+ unsigned int apic_id;
+ unsigned int vector;
+ } ipi;
+ } u;
+};
+
+struct workload {
+ const char *name;
+ unsigned int intr_vector;
+ unsigned int init_debug_op;
+ unsigned int done_debug_op;
+ struct event event;
+ struct statistics stats[NR_STATS];
+ int (*setup)(unsigned int idx, unsigned int cpu, unsigned int vector);
+ int (*send)(struct event *);
+ void (*ack)(struct event *);
+ void (*teardown)(unsigned int idx);
+};
+
+struct test_group {
+ const char *name;
+ enum workload_id noaccel;
+ enum workload_id accel;
+ enum stats_id stats;
+ enum metric_id metric;
+};
+
+struct shared_stats {
+ uint64_t t_handler_start;
+ uint64_t t_handler_end;
+ uint64_t t_elapsed;
+ unsigned int seq;
+ unsigned int isr_fail_cnt;
+ unsigned int irr_fail_cnt;
+ unsigned int t_skew_cnt;
+};
+
+static int ipi_setup(unsigned int idx, unsigned int cpu, unsigned int vector);
+static int ipi_send(struct event *e);
+static void ipi_teardown(unsigned int idx);
+
+static int evtchn_setup(unsigned int idx, unsigned int cpu, unsigned int
vector);
+static int evtchn_send(struct event *e);
+static void evtchn_ack(struct event *e);
+static void evtchn_teardown(unsigned int idx);
+
+static struct workload workload[NR_WORKLOADS] = {
+ [WORKLOAD_IPI_NOACCEL] = {
+ .name = "IPI noaccel",
+ .intr_vector = X86_VEC_AVAIL,
+ .init_debug_op = POSTED_INTR_DISABLE,
+ .done_debug_op = POSTED_INTR_DUMP_STATS,
+ .setup = ipi_setup,
+ .send = ipi_send,
+ .teardown = ipi_teardown,
+ .stats = {
+ [STAT_IRQ_SEND] = {
+ .name = "noaccel: IPI send latency (VCPU#0)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_DELIVERY] = {
+ .name = "noaccel: IPI delivery latency (VCPU#0 -> VCPU#1)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_HANDLER] = {
+ .name = "noaccel: IPI handler latency (VCPU#1)",
+ .min = ~0ULL,
+ },
+ },
+ },
+
+ [WORKLOAD_IPI_ACCEL] = {
+ .name = "IPI accel",
+ .intr_vector = X86_VEC_AVAIL,
+ .init_debug_op = POSTED_INTR_ENABLE,
+ .done_debug_op = POSTED_INTR_DUMP_STATS,
+ .setup = ipi_setup,
+ .send = ipi_send,
+ .teardown = ipi_teardown,
+ .stats = {
+ [STAT_IRQ_SEND] = {
+ .name = "accel: IPI send latency (VCPU#0)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_DELIVERY] = {
+ .name = "accel: IPI delivery latency (VCPU#0 -> VCPU#1)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_HANDLER] = {
+ .name = "accel: IPI handler latency (VCPU#1)",
+ .min = ~0ULL,
+ },
+ },
+ },
+
+ [WORKLOAD_EVTCHN_NOACCEL] = {
+ .name = "evtchn noaccel",
+ .intr_vector = X86_VEC_AVAIL + 1,
+ .init_debug_op = POSTED_INTR_DISABLE,
+ .done_debug_op = POSTED_INTR_DUMP_STATS,
+ .setup = evtchn_setup,
+ .send = evtchn_send,
+ .ack = evtchn_ack,
+ .teardown = evtchn_teardown,
+ .stats = {
+ [STAT_IRQ_SEND] = {
+ .name = "noaccel: evtchn send latency (VCPU#0)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_DELIVERY] = {
+ .name = "noaccel: evtchn delivery latency (VCPU#0 -> VCPU#1)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_HANDLER] = {
+ .name = "noaccel: evtchn handler latency (VCPU#1)",
+ .min = ~0ULL,
+ },
+ },
+ },
+
+ [WORKLOAD_EVTCHN_ACCEL] = {
+ .name = "evtchn accel",
+ .intr_vector = X86_VEC_AVAIL + 1,
+ .init_debug_op = POSTED_INTR_ENABLE,
+ .done_debug_op = POSTED_INTR_DUMP_STATS,
+ .setup = evtchn_setup,
+ .send = evtchn_send,
+ .ack = evtchn_ack,
+ .teardown = evtchn_teardown,
+ .stats = {
+ [STAT_IRQ_SEND] = {
+ .name = "accel: evtchn send latency (VCPU#0)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_DELIVERY] = {
+ .name = "accel: evtchn delivery latency (VCPU#0 -> VCPU#1)",
+ .min = ~0ULL,
+ },
+ [STAT_IRQ_HANDLER] = {
+ .name = "accel: evtchn handler latency (VCPU#1)",
+ .min = ~0ULL,
+ },
+ },
+ },
+};
+
+static struct test_group groups[NR_TESTS] = {
+ [TEST_IPI] = {
+ .name = "IPI latency",
+ .noaccel = WORKLOAD_IPI_NOACCEL,
+ .accel = WORKLOAD_IPI_ACCEL,
+ .stats = STAT_IRQ_DELIVERY,
+ .metric = METRIC_ELAPSED_TIME,
+ },
+
+ [TEST_EVTCHN] = {
+ .name = "evtchn latency",
+ .noaccel = WORKLOAD_EVTCHN_NOACCEL,
+ .accel = WORKLOAD_EVTCHN_ACCEL,
+ .stats = STAT_IRQ_DELIVERY,
+ .metric = METRIC_ELAPSED_TIME,
+ },
+};
+
+static struct shared_stats shared_stats[NR_WORKLOADS] __page_aligned_bss;
+
+static uint8_t noise_page[NOISE_PAGE_SIZE] __page_aligned_bss;
+
+static unsigned int current_workload = WORKLOAD_IPI_NOACCEL;
+static unsigned int opt_samples_measure;
+static unsigned int opt_samples_warmup;
+static unsigned int noisy_neighbour_seq;
+static domid_t domid;
+
+static int ipi_setup(unsigned int idx, unsigned int cpu, unsigned int vector)
+{
+ struct workload *w = &workload[idx];
+
+ w->event.u.ipi.apic_id = ap_apic_id(cpu);
+ w->event.u.ipi.vector = vector;
+
+ return 0;
+}
+
+static int ipi_send(struct event *e)
+{
+ return ap_set_irq(e->u.ipi.apic_id, e->u.ipi.vector);
+}
+
+static void ipi_teardown(unsigned int idx)
+{
+ struct workload *w = &workload[idx];
+
+ w->event.u.ipi.apic_id = 0;
+ w->event.u.ipi.vector = 0;
+}
+
+static int evtchn_setup(unsigned int idx, unsigned int cpu, unsigned int
vector)
+{
+ struct workload *w = &workload[idx];
+ struct evtchn_bind_ipi bind = {
+ .vcpu = cpu,
+ };
+ int rc;
+
+ rc = hypercall_set_evtchn_upcall_vector(cpu, vector);
+ if ( rc )
+ return rc;
+
+ rc = hypercall_evtchn_bind_ipi(&bind);
+ if ( rc )
+ return rc;
+
+ rc = hypercall_evtchn_unmask(bind.port);
+ if ( rc )
+ return rc;
+
+ w->event.u.evtchn = bind.port;
+
+ return 0;
+}
+
+static int evtchn_send(struct event *e)
+{
+ return hypercall_evtchn_send(e->u.evtchn);
+}
+
+static void evtchn_ack(struct event *e)
+{
+ struct vcpu_info *vi = &shared_info.vcpu_info[DEST_CPU_ID];
+
+ if ( !test_and_clear_bit(e->u.evtchn, shared_info.evtchn_pending) )
+ (void)vi;
+
+ ACCESS_ONCE(vi->evtchn_upcall_pending) = 0;
+ ACCESS_ONCE(vi->evtchn_pending_sel) = 0;
+}
+
+static void evtchn_teardown(unsigned int idx)
+{
+ struct workload *w = &workload[idx];
+
+ (void)hypercall_evtchn_close(w->event.u.evtchn);
+ w->event.u.evtchn = 0;
+}
+
+static int hvm_debug_posted_intr(unsigned int arg)
+{
+ struct xen_domctl domctl = { 0 };
+
+ domctl.domain = domid;
+ domctl.cmd = XEN_DOMCTL_debug_op;
+ domctl.interface_version = XEN_DOMCTL_INTERFACE_VERSION;
+ domctl.u.debug_op = (struct xen_domctl_debug_op){
+ .op = XEN_DOMCTL_DEBUG_OP_POSTED_INTR,
+ .vcpu = ~0U,
+ .arg = arg,
+ };
+
+ return hypercall_domctl(&domctl);
+}
+
+void ap_irq_handler(void)
+{
+ unsigned int idx = ACCESS_ONCE(current_workload);
+ struct workload *w = &workload[idx];
+ struct shared_stats *s = &shared_stats[idx];
+ uint64_t start, end;
+
+ start = rdtsc_ordered();
+
+ if ( w->ack )
+ w->ack(&w->event);
+
+ if ( !apic_isr_read(w->intr_vector) )
+ ACCESS_ONCE(s->isr_fail_cnt)++;
+
+ if ( apic_irr_read(w->intr_vector) )
+ ACCESS_ONCE(s->irr_fail_cnt)++;
+
+ apic_write(APIC_EOI, APIC_EOI_ACK);
+
+ end = rdtsc_ordered();
+
+ ACCESS_ONCE(s->t_handler_start) = start;
+ ACCESS_ONCE(s->t_handler_end) = end;
+
+ smp_wmb();
+ ACCESS_ONCE(s->seq)++;
+}
+
+static void do_noisy_neighbour(void)
+{
+ unsigned int nr = NOISE_PAGE_SIZE / sizeof(uint64_t);
+ unsigned int i;
+ uint64_t *noise = (uint64_t *)noise_page;
+ uint64_t sum = 0;
+
+ xtf_irq_enable();
+
+ for ( i = 0; i < nr; i++)
+ noise[i] = i;
+
+ while ( !xtf_status_reported() )
+ {
+ ACCESS_ONCE(noisy_neighbour_seq)++;
+
+ for ( i = 0; i < nr; i++ )
+ {
+ sum += ACCESS_ONCE(noise[i]);
+ ACCESS_ONCE(noise[i]) = sum;
+ }
+ }
+}
+
+static void do_workload_on_noisy_neighbour(enum workload_id idx, unsigned int
cpu)
+{
+ struct workload *w = &workload[idx];
+ struct statistics *stats = w->stats;
+ struct shared_stats *s = &shared_stats[idx];
+ unsigned int i;
+ int rc;
+
+ rc = w->setup(idx, cpu, w->intr_vector);
+ if ( rc )
+ return xtf_error("BSP: cannot ->init(%d): %d\n", idx, rc);
+
+ i = 0;
+ while ( i < opt_samples_warmup + opt_samples_measure )
+ {
+ unsigned int seq;
+ uint64_t t_send_start, t_send_end;
+ uint64_t t_handler_start, t_handler_end;
+
+ seq = ACCESS_ONCE(s->seq);
+
+ t_send_start = rdtsc_ordered();
+
+ rc = w->send(&w->event);
+
+ t_send_end = rdtsc_ordered();
+
+ if ( rc )
+ return xtf_error("BSP: cannot ->send(%u): %d\n", idx, rc);
+
+ while ( ACCESS_ONCE(s->seq) == seq )
+ cpu_relax();
+
+ smp_rmb();
+
+ t_handler_start = ACCESS_ONCE(s->t_handler_start);
+ t_handler_end = ACCESS_ONCE(s->t_handler_end);
+
+ /*
+ * Mitigate for inter-core TSC skew, looks like vCPU#1 can
+ * report TSC values from the past; drop such samples.
+ */
+ if ( t_send_start > t_handler_start )
+ {
+ s->t_skew_cnt++;
+ /* Do not update stats with unstable time, retry the sample. */
+ continue;
+ }
+
+ if ( i++ < opt_samples_warmup )
+ continue;
+
+ xtf_add_sample(&stats[STAT_IRQ_DELIVERY],
+ t_handler_start - t_send_start);
+
+ xtf_add_sample(&stats[STAT_IRQ_SEND],
+ t_send_end - t_send_start);
+
+ xtf_add_sample(&stats[STAT_IRQ_HANDLER],
+ t_handler_end - t_handler_start);
+ }
+
+ w->teardown(idx);
+}
+
+static void test_parse_cmdline(void)
+{
+ const char *optname = "samples=";
+ char *s;
+ size_t optname_len;
+
+ if ( !IS_DEFINED(CONFIG_HVM) )
+ return xtf_skip("N/A\n");
+
+ s = (char *)(uintptr_t)pvh_start_info->cmdline_paddr;
+ optname_len = strlen(optname);
+
+ /* samples=1000 */
+ while ( *s && *s == ' ' )
+ s++;
+
+ if ( !strncmp(s, optname, optname_len) )
+ opt_samples_measure = atou(s + optname_len);
+ else
+ opt_samples_measure = 1000u;
+
+ /* Drop 0.01% of samples at the beginning of the workload. */
+ opt_samples_warmup = opt_samples_measure / 10000u;
+ if ( opt_samples_warmup == 0 )
+ opt_samples_warmup = 100u;
+}
+
+void test_setup(void)
+{
+ unsigned i;
+ int rc;
+
+ if ( !IS_DEFINED(CONFIG_64BIT) )
+ return xtf_skip("BSP: need CONFIG_64BIT\n");
+
+ rc = xtf_get_domid();
+ if ( rc < 0 )
+ return xtf_error("BSP: cannot retrieve domain ID\n");
+ domid = (domid_t)rc;
+
+ test_parse_cmdline();
+
+ rc = apic_init(APIC_MODE_XAPIC);
+ if ( rc )
+ return xtf_error("BSP: cannot enable xAPIC: %d\n", rc);
+
+ xtf_init_time();
+
+ if ( ap_start(DEST_CPU_ID, do_noisy_neighbour) )
+ return xtf_error("vCPU#%u: failed to start\n", DEST_CPU_ID);
+
+ for ( i = 0; i <= NR_APS; i++ )
+ printk("vCPU#%u: APIC ID 0x%x\n", i, ap_apic_id(i));
+}
+
+static void run_workload(enum workload_id idx)
+{
+ struct workload *w = &workload[idx];
+ struct shared_stats *s = &shared_stats[idx];
+ struct statistics *stats = w->stats;
+ uint64_t t0, t1;
+ unsigned int apic_id = ap_apic_id(DEST_CPU_ID);
+ unsigned int seq;
+ unsigned int i;
+ int rc;
+
+ printk("\n");
+ printk("BSP: %s: workload %u vector 0x%x APIC ID 0x%0x warmup %u samples
%u\n",
+ workload[idx].name,
+ idx,
+ w->intr_vector,
+ apic_id,
+ opt_samples_warmup,
+ opt_samples_measure);
+
+ rc = hvm_debug_posted_intr(w->init_debug_op);
+ if ( rc < 0 )
+ return xtf_error("BSP: failed hvm_debug_posted_intr(%u): %d\n",
+ workload[idx].init_debug_op, rc);
+
+ /*
+ * Serialize against the noisy neighbour so that interrupt workload
+ * will not lead to VMEXIT w/ AVIC_IPI_FAILURE_TARGET_NOT_RUNNING
+ * reason.
+ */
+ seq = ACCESS_ONCE(noisy_neighbour_seq);
+ while ( ACCESS_ONCE(noisy_neighbour_seq) == seq )
+ cpu_relax();
+
+ ACCESS_ONCE(current_workload) = idx;
+
+ smp_rmb();
+
+ t0 = rdtsc_ordered();
+
+ do_workload_on_noisy_neighbour(idx, DEST_CPU_ID);
+
+ t1 = rdtsc_ordered();
+
+ ACCESS_ONCE(s->t_elapsed) = t1 - t0;
+
+ rc = hvm_debug_posted_intr(workload[idx].done_debug_op);
+ if ( rc < 0 )
+ return xtf_error("BSP: failed hvm_debug_posted_intr(%u): %d\n",
+ workload[idx].done_debug_op, rc);
+
+ for ( i = 0; i < NR_STATS; i++ )
+ {
+ xtf_finalize_statistics(&stats[i]);
+ xtf_print_metrics(&stats[i], NULL);
+ xtf_print_histogram(&stats[i]);
+ }
+
+ printk("TSC skew %u times\n",
+ s->t_skew_cnt);
+ printk("ISR[%x] %u IRR[%x] %u\n",
+ w->intr_vector, s->isr_fail_cnt,
+ w->intr_vector, s->irr_fail_cnt);
+ printk("Elapsed time %"PRIu64" ms\n",
+ cycles_to_ms(s->t_elapsed));
+ printk("\n");
+}
+
+/* Some rough criteria for success. */
+static bool check_critetia(struct test_group *tg)
+{
+ struct workload *wa = &workload[tg->noaccel];
+ struct workload *wb = &workload[tg->accel];
+ struct statistics *sa = &wa->stats[tg->stats];
+ struct statistics *sb = &wb->stats[tg->stats];
+ struct shared_stats *sha = &shared_stats[tg->noaccel];
+ struct shared_stats *shb = &shared_stats[tg->accel];
+ const char *name, *sign = "", *units;
+ uint64_t x, y, t;
+ int64_t delta;
+ unsigned int i;
+ bool pass;
+
+ switch ( tg->metric ) {
+ case METRIC_MINIMUM:
+ name = "min";
+ x = sa->min;
+ y = sb->min;
+ break;
+
+ case METRIC_MAXIMUM:
+ name = "max";
+ x = sa->max;
+ y = sb->max;
+ break;
+
+ case METRIC_AVERAGE:
+ name = "avg";
+ x = sa->sum / sa->count;
+ y = sb->sum / sb->count;
+ break;
+
+ case METRIC_P99:
+ name = "p99";
+ x = sa->p99;
+ y = sb->p99;
+ break;
+
+ case METRIC_ELAPSED_TIME:
+ name = "elapsed";
+ x = sha->t_elapsed;
+ y = shb->t_elapsed;
+ break;
+
+ default:
+ xtf_error("unsupported metric '%u'\n", tg->metric);
+ return false;
+ }
+
+ if ( x < y )
+ {
+ delta = y - x;
+ sign = "-";
+ }
+ else
+ {
+ delta = x - y;
+ }
+
+ if ( delta >= 1000000 )
+ {
+ units = "ms";
+ t = cycles_to_ms(delta);
+ }
+ else if ( delta >= 1000 )
+ {
+ units = "us";
+ t = cycles_to_us(delta);
+ }
+ else
+ {
+ units = "ns";
+ t = cycles_to_ns(delta);
+ }
+
+ pass = y <= x / 2;
+
+ printk("%s(%s) delta = %s(%s) - %s(%s) = %s%"PRIu64" (%s%"PRIu64"%s):
%s\n",
+ name, tg->name,
+ name, wa->name,
+ name, wb->name,
+ sign, delta,
+ sign, t, units,
+ pass ? "PASS" : "FAIL");
+
+ for ( i = 0; i < NR_STATS; i++ )
+ xtf_print_metrics(&wa->stats[i], &wb->stats[i]);
+
+ return pass;
+}
+
+static bool test_passed(void)
+{
+ bool passed = true;
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE(groups); i++ )
+ if ( !check_critetia(&groups[i]) )
+ return false;
+
+ return passed;
+}
+
+void test_main(void)
+{
+ uint64_t t0, t1;
+ unsigned int idx;
+
+ if ( xtf_status_reported() )
+ return;
+
+ t0 = rdtsc_ordered();
+
+ /* Execute all necessary workloads. */
+ for ( idx = 0; idx < NR_WORKLOADS; idx++ )
+ run_workload(idx);
+
+ /* Signal noisy neighbour to stop. */
+ xtf_success(NULL);
+ smp_mb();
+
+ if ( !test_passed() )
+ xtf_failure("Interrupt acceleration does not work!\n");
+
+ t1 = rdtsc_ordered();
+
+ printk("\n");
+ printk("Total elapsed time %"PRIu64" ms\n",
+ cycles_to_ms(t1 - t0));
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--
2.55.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |