[PATCH V2 3/5] ara virt interface of perf to support kvm guest os statistics collection in guest os

Zhang, Yanmin
2010-06-21T09:31:27Z

The 3rd patch is to implement para virt perf at host kernel.

Signed-off-by: Zhang Yanmin <yanmin_zhang@linux.intel.com>

---

--- linux-2.6_tip0620/arch/x86/include/asm/kvm_para.h 2010-06-21 15:19:38.992999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/include/asm/kvm_para.h 2010-06-21 15:21:39.308999849 +0800
@@ -2,6 +2,7 @@
#define _ASM_X86_KVM_PARA_H

#include <linux/types.h>
+#include <linux/list.h>
#include <asm/hyperv.h>

/* This CPUID returns the signature 'KVMKVMKVM' in ebx, ecx, and edx. It
@@ -19,7 +20,8 @@
/* This indicates that the new set of kvmclock msrs
* are available. The use of 0x11 and 0x12 is deprecated
*/
-#define KVM_FEATURE_CLOCKSOURCE2 3
+#define KVM_FEATURE_CLOCKSOURCE2 3
+#define KVM_FEATURE_PV_PERF 4

/* The last 8 bits are used to indicate how to interpret the flags field
* in pvclock structure. If no bits are set, all flags are ignored.
@@ -33,7 +35,14 @@
#define MSR_KVM_WALL_CLOCK_NEW 0x4b564d00
#define MSR_KVM_SYSTEM_TIME_NEW 0x4b564d01

-#define KVM_MAX_MMU_OP_BATCH 32
+#define KVM_MAX_MMU_OP_BATCH 32
+
+/* Operations for KVM_PERF_OP */
+#define KVM_PERF_OP_OPEN 1
+#define KVM_PERF_OP_CLOSE 2
+#define KVM_PERF_OP_ENABLE 3
+#define KVM_PERF_OP_DISABLE 4
+#define KVM_PERF_OP_READ 5

/* Operations for KVM_HC_MMU_OP */
#define KVM_MMU_OP_WRITE_PTE 1
@@ -64,6 +73,85 @@ struct kvm_mmu_op_release_pt {
#ifdef __KERNEL__
#include <asm/processor.h>

+/*
+ * data communication area about perf_event between
+ * Host kernel and guest kernel
+ */
+struct guest_perf_event {
+ u64 count;
+ atomic_t overflows;
+};
+
+/*
+ * In host kernel, perf_event->host_perf_shadow points to
+ * host_perf_shadow which records some information
+ * about the guest.
+ */
+struct host_perf_shadow {
+ /* guest perf_event id passed from guest os */
+ int id;
+ /*
+ * Host kernel saves data into data member counter firstly.
+ * kvm will get data from this counter and calls kvm functions
+ * to copy or add data back to guets os before entering guest os
+ * next time
+ */
+ struct guest_perf_event counter;
+ /* guest_event_addr is gpa_t pointing to guest os guest_perf_event*/
+ __u64 guest_event_addr;
+
+ /*
+ * Link to of kvm.kvm_arch.shadow_hash_table
+ */
+ struct list_head shadow_entry;
+ struct kvm_vcpu *vcpu;
+
+ struct perf_event *host_event;
+ /*
+ * Below counter is to prevent malicious guest os to try to
+ * close/enable event at the same time.
+ */
+ atomic_t ref_counter;
+};
+
+/*
+ * In guest kernel, perf_event->guest_shadow points to
+ * guest_perf_shadow which records some information
+ * about the guest.
+ */
+struct guest_perf_shadow {
+ /* guest perf_event id passed from guest os */
+ int id;
+ /*
+ * Host kernel kvm saves data into data member counter
+ */
+ struct guest_perf_event counter;
+};
+
+/*
+ * guest_perf_attr is used when guest calls hypercall to
+ * open a new perf_event at host side. Mostly, it's a copy of
+ * perf_event_attr and deletes something not used by host kernel.
+ */
+struct guest_perf_attr {
+ __u32 type;
+ __u64 config;
+ __u64 sample_period;
+ __u64 sample_type;
+ __u64 read_format;
+ __u64 flags;
+ __u32 bp_type;
+ __u64 bp_addr;
+ __u64 bp_len;
+};
+
+struct guest_perf_event_param {
+ __u64 attr_addr;
+ __u64 guest_event_addr;
+ /* In case there is an alignment issue, we put id as the last one */
+ int id;
+};
+
extern void kvmclock_init(void);

--- linux-2.6_tip0620/arch/x86/include/asm/kvm_host.h 2010-06-21 15:19:39.019999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/include/asm/kvm_host.h 2010-06-21 15:21:39.308999849 +0800
@@ -24,6 +24,7 @@
#include <asm/desc.h>
#include <asm/mtrr.h>
#include <asm/msr-index.h>
+#include <asm/perf_event.h>

#define KVM_MAX_VCPUS 64
#define KVM_MEMORY_SLOTS 32
@@ -360,6 +361,18 @@ struct kvm_vcpu_arch {

/* fields used by HYPER-V emulation */
u64 hv_vapic;
+
+ /*
+ * Fields used by PARAVIRT perf interface:
+ *
+ * kvm checks overflow_events before entering guest os,
+ * and copy data back to guest os.
+ * event_mutex is to avoid a race between NMI perf event overflow
+ * handler, event close, and enable/disable.
+ */
+ struct mutex event_mutex;
+ int overflows;
+ struct perf_event *overflow_events[X86_PMC_IDX_MAX];
};

struct kvm_mem_alias {
@@ -377,6 +390,9 @@ struct kvm_mem_aliases {
int naliases;
};

+#define KVM_PARAVIRT_PERF_EVENT_ENTRY_BITS (10)
+#define KVM_PARAVIRT_PERF_EVENT_ENTRY_NUM (1<<KVM_PARAVIRT_PERF_EVENT_ENTRY_BITS)
+
struct kvm_arch {
struct kvm_mem_aliases *aliases;

@@ -415,6 +431,15 @@ struct kvm_arch {
/* fields used by HYPER-V emulation */
u64 hv_guest_os_id;
u64 hv_hypercall;
+
+ /*
+ * fields used by PARAVIRT perf interface:
+ * Used to organize all host perf_events representing guest
+ * perf_event on a specific kvm instance
+ */
+ atomic_t kvm_pv_event_num;
+ spinlock_t shadow_lock;
+ struct list_head *shadow_hash_table;
};

struct kvm_vm_stat {
@@ -561,6 +586,9 @@ int emulator_write_phys(struct kvm_vcpu
const void *val, int bytes);
int kvm_pv_mmu_op(struct kvm_vcpu *vcpu, unsigned long bytes,
gpa_t addr, unsigned long *ret);
+int kvm_pv_perf_op(struct kvm_vcpu *vcpu, int op_code, unsigned long a1,
+ unsigned long a2, unsigned long *result);
+
u8 kvm_get_guest_memory_type(struct kvm_vcpu *vcpu, gfn_t gfn);

extern bool tdp_enabled;
--- linux-2.6_tip0620/include/linux/kvm_para.h 2010-06-21 15:19:53.309999849 +0800
+++ linux-2.6_tip0620perfkvm/include/linux/kvm_para.h 2010-06-21 15:21:39.312999849 +0800
@@ -17,6 +17,7 @@

#define KVM_HC_VAPIC_POLL_IRQ 1
#define KVM_HC_MMU_OP 2
+#define KVM_PERF_OP 3

/*
* hypercalls use architecture specific
--- linux-2.6_tip0620/arch/x86/kvm/vmx.c 2010-06-21 15:19:39.322999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/kvm/vmx.c 2010-06-21 15:21:39.310999849 +0800
@@ -3647,6 +3647,7 @@ static int vmx_handle_exit(struct kvm_vc
struct vcpu_vmx *vmx = to_vmx(vcpu);
u32 exit_reason = vmx->exit_reason;
u32 vectoring_info = vmx->idt_vectoring_info;
+ int ret;

trace_kvm_exit(exit_reason, vcpu);

@@ -3694,12 +3695,17 @@ static int vmx_handle_exit(struct kvm_vc

if (exit_reason < kvm_vmx_max_exit_handlers
&& kvm_vmx_exit_handlers[exit_reason])
- return kvm_vmx_exit_handlers[exit_reason](vcpu);
+ ret = kvm_vmx_exit_handlers[exit_reason](vcpu);
else {
vcpu->run->exit_reason = KVM_EXIT_UNKNOWN;
vcpu->run->hw.hardware_exit_reason = exit_reason;
+ ret = 0;
}
- return 0;
+
+ /* sync paravirt perf event to guest */
+ kvm_sync_events_to_guest(vcpu);
+
+ return ret;
}

static void update_cr8_intercept(struct kvm_vcpu *vcpu, int tpr, int irr)
--- linux-2.6_tip0620/arch/x86/kvm/x86.c 2010-06-21 15:19:39.315999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/kvm/x86.c 2010-06-21 16:49:58.182999849 +0800
@@ -6,12 +6,14 @@
* Copyright (C) 2006 Qumranet, Inc.
* Copyright (C) 2008 Qumranet, Inc.
* Copyright IBM Corporation, 2008
+ * Copyright Intel Corporation, 2010
*
* Authors:
* Avi Kivity <avi@qumranet.com>
* Yaniv Kamay <yaniv@qumranet.com>
* Amit Shah <amit.shah@qumranet.com>
* Ben-Ami Yassour <benami@il.ibm.com>
+ * Yanmin Zhang <yanmin.zhang@intel.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
@@ -1618,6 +1620,7 @@ int kvm_dev_ioctl_check_extension(long e
case KVM_CAP_PCI_SEGMENT:
case KVM_CAP_DEBUGREGS:
case KVM_CAP_X86_ROBUST_SINGLESTEP:
+ case KVM_CAP_PV_PERF:
r = 1;
break;
case KVM_CAP_COALESCED_MMIO:
@@ -1993,7 +1996,9 @@ static void do_cpuid_ent(struct kvm_cpui
entry->eax = (1 << KVM_FEATURE_CLOCKSOURCE) |
(1 << KVM_FEATURE_NOP_IO_DELAY) |
(1 << KVM_FEATURE_CLOCKSOURCE2) |
+ (1 << KVM_FEATURE_PV_PERF) |
(1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT);
+
entry->ebx = 0;
entry->ecx = 0;
entry->edx = 0;
@@ -4052,10 +4057,21 @@ static unsigned long kvm_get_guest_ip(vo
return ip;
}

+int kvm_notify_event_overflow(void)
+{
+ if (percpu_read(current_vcpu)) {
+ kvm_inject_nmi(percpu_read(current_vcpu));
+ return 0;
+ }
+
+ return -1;
+}
+
static struct perf_guest_info_callbacks kvm_guest_cbs = {
.is_in_guest = kvm_is_in_guest,
.is_user_mode = kvm_is_user_mode,
.get_guest_ip = kvm_get_guest_ip,
+ .copy_event_to_shadow = kvm_copy_event_to_shadow,
};

void kvm_before_handle_nmi(struct kvm_vcpu *vcpu)
@@ -4138,15 +4154,6 @@ int kvm_emulate_halt(struct kvm_vcpu *vc
}
EXPORT_SYMBOL_GPL(kvm_emulate_halt);

-static inline gpa_t hc_gpa(struct kvm_vcpu *vcpu, unsigned long a0,
- unsigned long a1)
-{
- if (is_long_mode(vcpu))
- return a0;
- else
- return a0 | ((gpa_t)a1 << 32);
-}
-
int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
{
u64 param, ingpa, outgpa, ret;
@@ -4245,6 +4252,9 @@ int kvm_emulate_hypercall(struct kvm_vcp
case KVM_HC_MMU_OP:
r = kvm_pv_mmu_op(vcpu, a0, hc_gpa(vcpu, a1, a2), &ret);
break;
+ case KVM_PERF_OP:
+ r = kvm_pv_perf_op(vcpu, a0, a1, a2, &ret);
+ break;
default:
ret = -KVM_ENOSYS;
break;
@@ -5334,6 +5344,8 @@ int kvm_arch_vcpu_init(struct kvm_vcpu *
}
vcpu->arch.mcg_cap = KVM_MAX_MCE_BANKS;

+ mutex_init(&vcpu->arch.event_mutex);
+
return 0;
fail_free_lapic:
kvm_free_lapic(vcpu);
@@ -5360,6 +5372,8 @@ void kvm_arch_vcpu_uninit(struct kvm_vcp
struct kvm *kvm_arch_create_vm(void)
{
struct kvm *kvm = kzalloc(sizeof(struct kvm), GFP_KERNEL);
+ struct list_head *hash_table;
+ int i;

if (!kvm)
return ERR_PTR(-ENOMEM);
@@ -5369,6 +5383,18 @@ struct kvm *kvm_arch_create_vm(void)
kfree(kvm);
return ERR_PTR(-ENOMEM);
}
+ hash_table = kmalloc(sizeof(struct list_head) *
+ KVM_PARAVIRT_PERF_EVENT_ENTRY_NUM,
+ GFP_KERNEL);
+ if (!hash_table) {
+ kfree(kvm->arch.aliases);
+ kfree(kvm);
+ return ERR_PTR(-ENOMEM);
+ }
+ for (i = 0; i < KVM_PARAVIRT_PERF_EVENT_ENTRY_NUM; i++)
+ INIT_LIST_HEAD(&hash_table[i]);
+ kvm->arch.shadow_hash_table = hash_table;
+ spin_lock_init(&kvm->arch.shadow_lock);

INIT_LIST_HEAD(&kvm->arch.active_mmu_pages);
INIT_LIST_HEAD(&kvm->arch.assigned_dev_head);
@@ -5416,6 +5442,8 @@ void kvm_arch_sync_events(struct kvm *kv

void kvm_arch_destroy_vm(struct kvm *kvm)
{
+ kvm_remove_all_perf_events(kvm);
+
kvm_iommu_unmap_guest(kvm);
kvm_free_pit(kvm);
kfree(kvm->arch.vpic);
@@ -5427,6 +5455,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm
if (kvm->arch.ept_identity_pagetable)
put_page(kvm->arch.ept_identity_pagetable);
cleanup_srcu_struct(&kvm->srcu);
+ kfree(kvm->arch.shadow_hash_table);
kfree(kvm->arch.aliases);
kfree(kvm);
}
--- linux-2.6_tip0620/arch/x86/kvm/x86.h 2010-06-21 15:19:39.311999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/kvm/x86.h 2010-06-21 15:21:39.312999849 +0800
@@ -72,7 +72,20 @@ static inline struct kvm_mem_aliases *kv
|| lockdep_is_held(&kvm->slots_lock));
}

+static inline gpa_t hc_gpa(struct kvm_vcpu *vcpu, unsigned long a0,
+ unsigned long a1)
+{
+ if (is_long_mode(vcpu))
+ return a0;
+ else
+ return a0 | ((gpa_t)a1 << 32);
+}
+
void kvm_before_handle_nmi(struct kvm_vcpu *vcpu);
void kvm_after_handle_nmi(struct kvm_vcpu *vcpu);
+int kvm_notify_event_overflow(void);
+void kvm_copy_event_to_shadow(struct perf_event *event, int overflows);
+void kvm_sync_events_to_guest(struct kvm_vcpu *vcpu);
+void kvm_remove_all_perf_events(struct kvm *kvm);

#endif
--- linux-2.6_tip0620/arch/x86/kvm/Makefile 2010-06-21 15:19:39.311999849 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/kvm/Makefile 2010-06-21 15:21:39.310999849 +0800
@@ -11,7 +11,7 @@ kvm-y += $(addprefix ../../../virt/kvm
kvm-$(CONFIG_IOMMU_API) += $(addprefix ../../../virt/kvm/, iommu.o)

kvm-y += x86.o mmu.o emulate.o i8259.o irq.o lapic.o \
- i8254.o timer.o
+ i8254.o timer.o kvmperf_event.o
kvm-intel-y += vmx.o
kvm-amd-y += svm.o

--- linux-2.6_tip0620/arch/x86/kvm/kvmperf_event.c 1970-01-01 08:00:00.000000000 +0800
+++ linux-2.6_tip0620perfkvm/arch/x86/kvm/kvmperf_event.c 2010-06-21 16:49:29.509999849 +0800
@@ -0,0 +1,471 @@
+/*
+ * Performance events x86 kvm para architecture code
+ *
+ * Copyright (C) 2010 Intel Inc.
+ * Zhang Yanmin <yanmin.zhang@intel.com>
+ *
+ * For licencing details see kernel-base/COPYING
+ */
+
+#include <linux/perf_event.h>
+#include <linux/capability.h>
+#include <linux/notifier.h>
+#include <linux/hardirq.h>
+#include <linux/kprobes.h>
+#include <linux/module.h>
+#include <linux/kdebug.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/highmem.h>
+#include <linux/cpu.h>
+#include <linux/kvm.h>
+#include <linux/kvm_host.h>
+#include <linux/file.h>
+#include <linux/syscalls.h>
+#include <linux/init.h>
+#include <linux/hash.h>
+
+#include <asm/apic.h>
+#include <asm/stacktrace.h>
+#include <asm/nmi.h>
+#include <asm/compat.h>
+
+#include "x86.h"
+
+#define KVM_MAX_PARAVIRT_PERF_EVENT (1024)
+
+static inline u32 shadow_hash_id(int id)
+{
+ u32 hash_value = id;
+
+ hash_value = hash_32(hash_value, KVM_PARAVIRT_PERF_EVENT_ENTRY_BITS);
+ return hash_value;
+}
+
+static int kvm_add_host_event(struct kvm_vcpu *vcpu,
+ struct host_perf_shadow *host_shadow)
+{
+ long unsigned flags;
+ u32 index = shadow_hash_id(host_shadow->id);
+ struct kvm_arch *arch = &vcpu->kvm->arch;
+ struct list_head *head = &arch->shadow_hash_table[index];
+ struct list_head *pos;
+ struct host_perf_shadow *tmp;
+
+ spin_lock_irqsave(&arch->shadow_lock, flags);
+ list_for_each(pos, head) {
+ tmp = container_of(pos, struct host_perf_shadow, shadow_entry);
+ WARN(tmp->id == host_shadow->id, "%s called when there is an"
+ " item with the same id [%d] in hash table,",
+ __func__, host_shadow->id);
+ }
+ list_add(&host_shadow->shadow_entry, head);
+ spin_unlock_irqrestore(&arch->shadow_lock, flags);
+ return 0;
+}
+
+static struct perf_event *
+kvm_find_get_host_event(struct kvm_vcpu *vcpu, int id, int need_delete)
+{
+ long unsigned flags;
+ u32 index = shadow_hash_id(id);
+ struct kvm_arch *arch = &vcpu->kvm->arch;
+ struct list_head *head = &arch->shadow_hash_table[index];
+ struct list_head *pos;
+ struct host_perf_shadow *tmp = NULL;
+ int found = 0;
+
+ spin_lock_irqsave(&arch->shadow_lock, flags);
+ list_for_each(pos, head) {
+ tmp = container_of(pos, struct host_perf_shadow, shadow_entry);
+ if (tmp->id == id) {
+ found = 1;
+ if (need_delete)
+ list_del_init(&tmp->shadow_entry);
+ else
+ atomic_inc(&tmp->ref_counter);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&arch->shadow_lock, flags);
+
+ if (found)
+ return tmp->host_event;
+ else
+ return NULL;
+}
+
+static void kvm_vcpu_add_event_overflow_ref(struct perf_event *event)
+{
+ struct host_perf_shadow *host_shadow = event->host_perf_shadow;
+ struct kvm_vcpu *vcpu = host_shadow->vcpu;
+ int ret;
+
+ /*
+ * Use trylock as it's in NMI handler. We don't care
+ * too much to lose reporting once of one event to guets os,
+ * because host saves overflows counter in host_perf_shadow.
+ * Next time when a new overflow of the event happens and if
+ * there is no contention, host could push overflows to guest
+ * and guest could process also saved overflows.
+ */
+ ret = mutex_trylock(&vcpu->arch.event_mutex);
+ if (!ret)
+ return;
+ if (vcpu->arch.overflows < X86_PMC_IDX_MAX) {
+ vcpu->arch.overflow_events[vcpu->arch.overflows] = event;
+ vcpu->arch.overflows++;
+ }
+ mutex_unlock(&vcpu->arch.event_mutex);
+}
+
+static int kvm_vcpu_remove_event_overflow_ref(struct host_perf_shadow *shadow)
+{
+ struct kvm_vcpu *vcpu = shadow->vcpu;
+ int i;
+
+ if (!vcpu || !vcpu->arch.overflows)
+ return -1;
+
+ mutex_lock(&vcpu->arch.event_mutex);
+ for (i = 0; i < vcpu->arch.overflows; i++) {
+ if (vcpu->arch.overflow_events[i] == shadow->host_event)
+ vcpu->arch.overflow_events[i] = NULL;
+ }
+ mutex_unlock(&vcpu->arch.event_mutex);
+ return 0;
+}
+
+void kvm_copy_event_to_shadow(struct perf_event *event, int overflows)
+{
+ struct host_perf_shadow *shadow = event->host_perf_shadow;
+
+ shadow->counter.count = local64_read(&event->count);
+ atomic_add(overflows, &shadow->counter.overflows);
+ kvm_vcpu_add_event_overflow_ref(event);
+ /* Inject NMI to guest os */
+ kvm_notify_event_overflow();
+}
+
+static void kvm_perf_event_overflow(struct perf_event *event, int nmi,
+ struct perf_sample_data *data, struct pt_regs *regs)
+{
+ BUG_ON(event->host_perf_shadow == NULL);
+ kvm_copy_event_to_shadow(event, 1);
+}
+
+static void kvm_put_host_event(struct perf_event *host_event)
+{
+ struct host_perf_shadow *shadow = host_event->host_perf_shadow;
+ if (!atomic_dec_return(&shadow->ref_counter)) {
+ /*
+ * detach it in case guest os doesn't disables it
+ * before closing
+ */
+ perf_event_detach(host_event);
+ kvm_vcpu_remove_event_overflow_ref(shadow);
+
+ perf_event_release_kernel(host_event);
+ kfree(shadow);
+ atomic_dec(&shadow->vcpu->kvm->arch.kvm_pv_event_num);
+ }
+}
+
+static void kvm_copy_event_to_guest(struct kvm_vcpu *vcpu,
+ struct perf_event *host_event)
+{
+ struct host_perf_shadow *shadow = host_event->host_perf_shadow;
+ struct guest_perf_event counter;
+ int ret;
+ s32 overflows;
+
+ ret = kvm_read_guest(vcpu->kvm, shadow->guest_event_addr,
+ &counter, sizeof(counter));
+ if (ret < 0)
+ return;
+
+again:
+ overflows = atomic_read(&shadow->counter.overflows);
+ if (atomic_cmpxchg(&shadow->counter.overflows, overflows, 0) !=
+ overflows)
+ goto again;
+
+ counter.count = shadow->counter.count;
+ atomic_add(overflows, &counter.overflows);
+
+ kvm_write_guest(vcpu->kvm,
+ shadow->guest_event_addr,
+ &counter,
+ sizeof(counter));
+ return;
+}
+
+/*
+ * called by KVM to copy both perf_event->count and overflows to guest
+ * after host NMI handler detects guest perf_event overflows
+ */
+void kvm_sync_events_to_guest(struct kvm_vcpu *vcpu)
+{
+ int i;
+
+ if (vcpu->arch.overflows == 0)
+ return;
+
+ mutex_lock(&vcpu->arch.event_mutex);
+ for (i = 0; i < vcpu->arch.overflows; i++) {
+ if (vcpu->arch.overflow_events[i]) {
+ kvm_copy_event_to_guest(vcpu,
+ vcpu->arch.overflow_events[i]);
+ }
+ }
+ vcpu->arch.overflows = 0;
+ mutex_unlock(&vcpu->arch.event_mutex);
+}
+EXPORT_SYMBOL_GPL(kvm_sync_events_to_guest);
+
+/* Just copy perf_event->count to guest. Don't copy overflows to guest */
+static void
+kvm_copy_count_to_guest(struct kvm_vcpu *vcpu, struct perf_event *host_event)
+{
+ struct host_perf_shadow *shadow = host_event->host_perf_shadow;
+
+ shadow->counter.count = local64_read(&host_event->count);
+ kvm_write_guest(vcpu->kvm,
+ shadow->guest_event_addr,
+ &shadow->counter.count,
+ sizeof(shadow->counter.count));
+ return;
+}
+
+static int
+kvm_pv_perf_op_open(struct kvm_vcpu *vcpu, gpa_t addr)
+{
+ int ret = 0;
+ struct perf_event *host_event = NULL;
+ struct host_perf_shadow *shadow = NULL;
+ struct guest_perf_event_param param;
+ struct guest_perf_attr *guest_attr = NULL;
+ struct perf_event_attr *attr = NULL;
+ int next_count;
+
+ next_count = atomic_read(&vcpu->kvm->arch.kvm_pv_event_num);
+ if (next_count >= KVM_MAX_PARAVIRT_PERF_EVENT) {
+ WARN_ONCE(1, "guest os wants to open more than %d events\n",
+ KVM_MAX_PARAVIRT_PERF_EVENT);
+ return -ENOENT;
+ }
+ atomic_inc(&vcpu->kvm->arch.kvm_pv_event_num);
+
+ attr = kzalloc(sizeof(*attr), GFP_KERNEL);
+ if (!attr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ guest_attr = kzalloc(sizeof(*guest_attr), GFP_KERNEL);
+ if (!attr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = kvm_read_guest(vcpu->kvm, addr, &param, sizeof(param));
+ if (ret < 0)
+ goto out;
+
+ host_event = kvm_find_get_host_event(vcpu, param.id, 0);
+ if (host_event) {
+ kvm_put_host_event(host_event);
+ return -EEXIST;
+ }
+
+ ret = kvm_read_guest(vcpu->kvm, param.attr_addr,
+ guest_attr, sizeof(*guest_attr));
+ if (ret < 0)
+ goto out;
+
+ attr->type = guest_attr->type;
+ attr->config = guest_attr->config;
+ attr->sample_period = guest_attr->sample_period;
+ attr->read_format = guest_attr->read_format;
+ attr->flags = guest_attr->flags;
+ attr->bp_type = guest_attr->bp_type;
+ attr->bp_addr = guest_attr->bp_addr;
+ attr->bp_len = guest_attr->bp_len;
+ /*
+ * By default, we disable the host event. Later on, guets os
+ * triggers a perf_event_attach to enable it
+ */
+ attr->disabled = 1;
+ attr->inherit = 0;
+ attr->enable_on_exec = 0;
+ /*
+ * We don't support exclude mode of user and kernel for guest os,
+ * which mean we always collect both user and kernel for guest os
+ */
+ attr->exclude_user = 0;
+ attr->exclude_kernel = 0;
+
+ shadow = kzalloc(sizeof(*shadow), GFP_KERNEL);
+ if (!shadow) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ shadow->id = param.id;
+ shadow->guest_event_addr = param.guest_event_addr;
+ shadow->vcpu = vcpu;
+ INIT_LIST_HEAD(&shadow->shadow_entry);
+
+ /* We always create a cpu context host perf event */
+ host_event = perf_event_create_kernel_counter(attr, -1,
+ current->pid, kvm_perf_event_overflow);
+
+ if (IS_ERR(host_event)) {
+ host_event = NULL;
+ ret = -1;
+ goto out;
+ }
+ host_event->host_perf_shadow = shadow;
+ shadow->host_event = host_event;
+ atomic_set(&shadow->ref_counter, 1);
+ kvm_add_host_event(vcpu, shadow);
+
+out:
+ if (!host_event)
+ kfree(shadow);
+
+ kfree(attr);
+ kfree(guest_attr);
+
+ if (ret)
+ atomic_dec(&vcpu->kvm->arch.kvm_pv_event_num);
+
+ return ret;
+}
+
+static int kvm_pv_perf_op_close(struct kvm_vcpu *vcpu, int id)
+{
+ struct perf_event *host_event;
+
+ /* Find and delete the event from the hashtable */
+ host_event = kvm_find_get_host_event(vcpu, id, 1);
+ if (!host_event)
+ return -1;
+ kvm_put_host_event(host_event);
+ return 0;
+}
+
+static int kvm_pv_perf_op_enable(struct kvm_vcpu *vcpu, int id)
+{
+ struct perf_event *event;
+ struct host_perf_shadow *shadow;
+
+ event = kvm_find_get_host_event(vcpu, id, 0);
+ if (!event)
+ return -1;
+
+ shadow = event->host_perf_shadow;
+ if (shadow->vcpu != vcpu) {
+ kvm_vcpu_remove_event_overflow_ref(event->host_perf_shadow);
+ shadow->vcpu = vcpu;
+ }
+
+ perf_event_attach(event);
+ kvm_put_host_event(event);
+
+ return 0;
+}
+
+static int kvm_pv_perf_op_disable(struct kvm_vcpu *vcpu, int id)
+{
+ struct perf_event *host_event = kvm_find_get_host_event(vcpu, id, 0);
+ if (!host_event)
+ return -1;
+ perf_event_detach(host_event);
+ /* We sync count to guest as we delay the guest count update */
+ kvm_copy_count_to_guest(vcpu, host_event);
+ kvm_put_host_event(host_event);
+
+ return 0;
+}
+
+static int kvm_pv_perf_op_read(struct kvm_vcpu *vcpu, int id)
+{
+ u64 enabled, running;
+ struct perf_event *host_event = kvm_find_get_host_event(vcpu, id, 0);
+
+ if (!host_event)
+ return -1;
+ if (host_event->state == PERF_EVENT_STATE_ACTIVE)
+ perf_event_read_value(host_event, &enabled, &running);
+ kvm_copy_count_to_guest(vcpu, host_event);
+ kvm_put_host_event(host_event);
+ return 0;
+}
+
+int kvm_pv_perf_op(struct kvm_vcpu *vcpu, int op_code, unsigned long a1,
+ unsigned long a2, unsigned long *result)
+{
+ unsigned long ret;
+ gpa_t addr;
+ int id;
+
+ switch (op_code) {
+ case KVM_PERF_OP_OPEN:
+ addr = hc_gpa(vcpu, a1, a2);
+ ret = (unsigned long) kvm_pv_perf_op_open(vcpu, addr);
+ break;
+ case KVM_PERF_OP_CLOSE:
+ id = (int) a1;
+ ret = kvm_pv_perf_op_close(vcpu, id);
+ break;
+ case KVM_PERF_OP_ENABLE:
+ id = (int) a1;
+ ret = kvm_pv_perf_op_enable(vcpu, id);
+ break;
+ case KVM_PERF_OP_DISABLE:
+ id = (int) a1;
+ ret = kvm_pv_perf_op_disable(vcpu, id);
+ break;
+ case KVM_PERF_OP_READ:
+ id = (int) a1;
+ ret = kvm_pv_perf_op_read(vcpu, id);
+ break;
+ default:
+ ret = -KVM_ENOSYS;
+ }
+
+ *result = ret;
+ return 0;
+}
+
+void kvm_remove_all_perf_events(struct kvm *kvm)
+{
+ long unsigned flags;
+ struct kvm_arch *arch = &kvm->arch;
+ LIST_HEAD(total_events);
+ struct list_head *head;
+ struct list_head *pos, *next;
+ struct host_perf_shadow *tmp;
+ int i;
+
+ spin_lock_irqsave(&arch->shadow_lock, flags);
+ for (i = 0; i < KVM_PARAVIRT_PERF_EVENT_ENTRY_NUM; i++) {
+ head = &arch->shadow_hash_table[i];
+ list_for_each_safe(pos, next, head) {
+ tmp = container_of(pos, struct host_perf_shadow,
+ shadow_entry);
+ list_del(&tmp->shadow_entry);
+ list_add(&tmp->shadow_entry, &total_events);
+ }
+ }
+ spin_unlock_irqrestore(&arch->shadow_lock, flags);
+ head = &total_events;
+ list_for_each_safe(pos, next, head) {
+ tmp = container_of(pos, struct host_perf_shadow, shadow_entry);
+ list_del(&tmp->shadow_entry);
+ kvm_put_host_event(tmp->host_event);
+ }
+
+ return;
+}
+
--- linux-2.6_tip0620/include/linux/kvm.h 2010-06-21 15:19:52.605999849 +0800
+++ linux-2.6_tip0620perfkvm/include/linux/kvm.h 2010-06-21 15:21:39.312999849 +0800
@@ -524,6 +524,7 @@ struct kvm_enable_cap {
#define KVM_CAP_PPC_OSI 52
#define KVM_CAP_PPC_UNSET_IRQ 53
#define KVM_CAP_ENABLE_CAP 54
+#define KVM_CAP_PV_PERF 57

#ifdef KVM_CAP_IRQ_ROUTING