[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[XTF PATCH v1 15/15] x86: add interrupt latency test (hvm64)


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: dmukhin@xxxxxxxx
  • Date: Wed, 4 Nov 2026 17:38:17 -0800
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 205.220.161.53) smtp.rcpttodomain=lists.xenproject.org smtp.mailfrom=ford.com; dmarc=pass (p=reject sp=reject pct=100) action=none header.from=ford.com; dkim=pass (signature was verified) header.d=saarlouis.ford.com; dkim=pass (signature was verified) header.d=ford.com; arc=none (0)
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=o9ZYc4v+PWcDcI6hzGhrBFkRY7QkqkSCRaiH8rIbRQ8=; b=Q6BhdLWrkr532Na6Ia0hnWmwaVOlvkjKbU+0l4+blX5p1Ha2CqxOAu/4VXx5PwJsVkfDmpAesUtip3d7NV5H+f5KTzAiIYnpvgPwdDyJrNSGOcWlwrOoV7Hb/Ic14I95cFGaGw5/6Jan9EDxXWtWQ22mdCsuKTS1c0PgK6OH7VmsyC4qVZ0j6UGLL3BverDz7O8jsltIEfY8lVd/YkmSgQMkygOpVyLbz8Q3lTasJQK30z/G3qKsZmypLFA8ylrGc7Pae6ScpGRANZqB/TPL3puaUjF4pcjLeldHVn4euU0w7wI+1jsCm9liudYMFMGOySngJBK1o/W5MdF/8cEonQ==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=n82vU5AwZKDYSJPqlRO8S82X1vrgmSl2ul7pup5Wifaz0qhsMFppoFc9NoqQSX6svTYpL9tkmQKE4IwvIukLo8kNZzlxr7NlkabnSNkqRPKR8kG0nIm918nBxoUpoV2k+Ji9D9P/T8umAyOq21rDQW1l5192wULPaAEqavDOMBVXsitjZvLWZdeR9eW2LFz80BuG/lqTe+F/FWOTU+uZa7Wf4r/RCzEGJ+pwp1MQiGLAdyN/L8rBgNGE6t4Slu1uNobX/CsnwkPMFtaZZ+KYj7W9IWuQb/vCPfQdIS16U4ptwbKVe3YodHX1bMhjuA7+Lo21NW9zKoFsH1BJ8tBygQ==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=ppford header.d=ford.com header.i="@ford.com" header.h="Cc:Content-Transfer-Encoding:Content-Type:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"; dkim=pass header.s=selector2-azureford-onmicrosoft-com header.d=azureford.onmicrosoft.com header.i="@azureford.onmicrosoft.com" header.h="From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"; dkim=pass header.s=ppserprodsaar header.d=saarlouis.ford.com header.i="@saarlouis.ford.com" header.h="Cc:Content-Transfer-Encoding:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"; dkim=pass header.s=ppfserpocford header.d=ford.com header.i="@ford.com" header.h="Cc:Content-Transfer-Encoding:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"
  • Cc: andrew.cooper3@xxxxxxxxxx, anthony.perard@xxxxxxxxxx, jbeulich@xxxxxxxx, julien@xxxxxxx, michal.orzel@xxxxxxx, roger@xxxxxxxxxxxxxx, sstabellini@xxxxxxxxxx, dmukhin@xxxxxxx, Denis Mukhin <dmukhin@xxxxxxxx>
  • Delivery-date: Thu, 05 Nov 2026 01:39:18 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>
  • Pser-m365-app: SER-APP

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




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.