* new selftests
 * fixes for migration with HyperV re-enlightenment enabled
 * fix RCU/SRCU usage
 * fixes for local_irq_restore misuse false positive
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmBUpO8UHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroPj6Af+LSkDniR08Eh/x4GHdX+ZSA9EhNuP
 PMqL+nDYvLXqc0XaErbZQpQbSP4aK7Tjly0LguZmNkBk17pnbjLb5Vv9hqJ30pM/
 pI8bGgdh+KDO9LClfrgsaYgC+B4R+fwqqTIvtBYMilVZ96JwixFiODB4ntRQmZgd
 xJS99jwjD8TO9pTYskKPf8y8yv5W9RH+wVQGXwc+T/sSzK/rcL4Jwt/ibO2FLcJK
 gBRXJDVjMIlpxPrqqoejVB2FHQQe36Bns85QU3dz0QuXfDuuEvbShY/f4R1z32fT
 RaccrvdMQtvgwS0l9Ij06PT0BdiG0EdZv/gOBUq5gVgx4XZyJTleJaVURw==
 =WZP4
 -----END PGP SIGNATURE-----

Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm

Pull kvm fixes from Paolo Bonzini:
 "Fixes for kvm on x86:

   - new selftests

   - fixes for migration with HyperV re-enlightenment enabled

   - fix RCU/SRCU usage

   - fixes for local_irq_restore misuse false positive"

* tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm:
  documentation/kvm: additional explanations on KVM_SET_BOOT_CPU_ID
  x86/kvm: Fix broken irq restoration in kvm_wait
  KVM: X86: Fix missing local pCPU when executing wbinvd on all dirty pCPUs
  KVM: x86: Protect userspace MSR filter with SRCU, and set atomically-ish
  selftests: kvm: add set_boot_cpu_id test
  selftests: kvm: add _vm_ioctl
  selftests: kvm: add get_msr_index_features
  selftests: kvm: Add basic Hyper-V clocksources tests
  KVM: x86: hyper-v: Don't touch TSC page values when guest opted for re-enlightenment
  KVM: x86: hyper-v: Track Hyper-V TSC page status
  KVM: x86: hyper-v: Prevent using not-yet-updated TSC page by secondary CPUs
  KVM: x86: hyper-v: Limit guest to writing zero to HV_X64_MSR_TSC_EMULATION_STATUS
  KVM: x86/mmu: Store the address space ID in the TDP iterator
  KVM: x86/mmu: Factor out tdp_iter_return_to_root
  KVM: x86/mmu: Fix RCU usage when atomically zapping SPTEs
  KVM: x86/mmu: Fix RCU usage in handle_removed_tdp_mmu_page
This commit is contained in:
Linus Torvalds 2021-03-19 14:10:07 -07:00
Родитель 3149860dc7 9ce3746d64
Коммит ecd8ee7f9c
18 изменённых файлов: 808 добавлений и 121 удалений

Просмотреть файл

@ -1495,7 +1495,8 @@ Fails if any VCPU has already been created.
Define which vcpu is the Bootstrap Processor (BSP). Values are the same
as the vcpu id in KVM_CREATE_VCPU. If this ioctl is not called, the default
is vcpu 0.
is vcpu 0. This ioctl has to be called before vcpu creation,
otherwise it will return EBUSY error.
4.42 KVM_GET_XSAVE
@ -4806,8 +4807,10 @@ If an MSR access is not permitted through the filtering, it generates a
allows user space to deflect and potentially handle various MSR accesses
into user space.
If a vCPU is in running state while this ioctl is invoked, the vCPU may
experience inconsistent filtering behavior on MSR accesses.
Note, invoking this ioctl with a vCPU is running is inherently racy. However,
KVM does guarantee that vCPUs will see either the previous filter or the new
filter, e.g. MSRs with identical settings in both the old and new filter will
have deterministic behavior.
4.127 KVM_XEN_HVM_SET_ATTR
--------------------------

Просмотреть файл

@ -884,12 +884,29 @@ struct kvm_hv_syndbg {
u64 options;
};
/* Current state of Hyper-V TSC page clocksource */
enum hv_tsc_page_status {
/* TSC page was not set up or disabled */
HV_TSC_PAGE_UNSET = 0,
/* TSC page MSR was written by the guest, update pending */
HV_TSC_PAGE_GUEST_CHANGED,
/* TSC page MSR was written by KVM userspace, update pending */
HV_TSC_PAGE_HOST_CHANGED,
/* TSC page was properly set up and is currently active */
HV_TSC_PAGE_SET,
/* TSC page is currently being updated and therefore is inactive */
HV_TSC_PAGE_UPDATING,
/* TSC page was set up with an inaccessible GPA */
HV_TSC_PAGE_BROKEN,
};
/* Hyper-V emulation context */
struct kvm_hv {
struct mutex hv_lock;
u64 hv_guest_os_id;
u64 hv_hypercall;
u64 hv_tsc_page;
enum hv_tsc_page_status hv_tsc_page_status;
/* Hyper-v based guest crash (NT kernel bugcheck) parameters */
u64 hv_crash_param[HV_X64_MSR_CRASH_PARAMS];
@ -931,6 +948,12 @@ enum kvm_irqchip_mode {
KVM_IRQCHIP_SPLIT, /* created with KVM_CAP_SPLIT_IRQCHIP */
};
struct kvm_x86_msr_filter {
u8 count;
bool default_allow:1;
struct msr_bitmap_range ranges[16];
};
#define APICV_INHIBIT_REASON_DISABLE 0
#define APICV_INHIBIT_REASON_HYPERV 1
#define APICV_INHIBIT_REASON_NESTED 2
@ -1025,16 +1048,11 @@ struct kvm_arch {
bool guest_can_read_msr_platform_info;
bool exception_payload_enabled;
bool bus_lock_detection_enabled;
/* Deflect RDMSR and WRMSR to user space when they trigger a #GP */
u32 user_space_msr_mask;
struct {
u8 count;
bool default_allow:1;
struct msr_bitmap_range ranges[16];
} msr_filter;
bool bus_lock_detection_enabled;
struct kvm_x86_msr_filter __rcu *msr_filter;
struct kvm_pmu_event_filter __rcu *pmu_event_filter;
struct task_struct *nx_lpage_recovery_thread;

Просмотреть файл

@ -836,28 +836,25 @@ static void kvm_kick_cpu(int cpu)
static void kvm_wait(u8 *ptr, u8 val)
{
unsigned long flags;
if (in_nmi())
return;
local_irq_save(flags);
if (READ_ONCE(*ptr) != val)
goto out;
/*
* halt until it's our turn and kicked. Note that we do safe halt
* for irq enabled case to avoid hang when lock info is overwritten
* in irq spinlock slowpath and no spurious interrupt occur to save us.
*/
if (arch_irqs_disabled_flags(flags))
halt();
else
safe_halt();
if (irqs_disabled()) {
if (READ_ONCE(*ptr) == val)
halt();
} else {
local_irq_disable();
out:
local_irq_restore(flags);
if (READ_ONCE(*ptr) == val)
safe_halt();
local_irq_enable();
}
}
#ifdef CONFIG_X86_32

Просмотреть файл

@ -520,10 +520,10 @@ static u64 get_time_ref_counter(struct kvm *kvm)
u64 tsc;
/*
* The guest has not set up the TSC page or the clock isn't
* stable, fall back to get_kvmclock_ns.
* Fall back to get_kvmclock_ns() when TSC page hasn't been set up,
* is broken, disabled or being updated.
*/
if (!hv->tsc_ref.tsc_sequence)
if (hv->hv_tsc_page_status != HV_TSC_PAGE_SET)
return div_u64(get_kvmclock_ns(kvm), 100);
vcpu = kvm_get_vcpu(kvm, 0);
@ -1077,6 +1077,21 @@ static bool compute_tsc_page_parameters(struct pvclock_vcpu_time_info *hv_clock,
return true;
}
/*
* Don't touch TSC page values if the guest has opted for TSC emulation after
* migration. KVM doesn't fully support reenlightenment notifications and TSC
* access emulation and Hyper-V is known to expect the values in TSC page to
* stay constant before TSC access emulation is disabled from guest side
* (HV_X64_MSR_TSC_EMULATION_STATUS). KVM userspace is expected to preserve TSC
* frequency and guest visible TSC value across migration (and prevent it when
* TSC scaling is unsupported).
*/
static inline bool tsc_page_update_unsafe(struct kvm_hv *hv)
{
return (hv->hv_tsc_page_status != HV_TSC_PAGE_GUEST_CHANGED) &&
hv->hv_tsc_emulation_control;
}
void kvm_hv_setup_tsc_page(struct kvm *kvm,
struct pvclock_vcpu_time_info *hv_clock)
{
@ -1087,7 +1102,8 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
BUILD_BUG_ON(sizeof(tsc_seq) != sizeof(hv->tsc_ref.tsc_sequence));
BUILD_BUG_ON(offsetof(struct ms_hyperv_tsc_page, tsc_sequence) != 0);
if (!(hv->hv_tsc_page & HV_X64_MSR_TSC_REFERENCE_ENABLE))
if (hv->hv_tsc_page_status == HV_TSC_PAGE_BROKEN ||
hv->hv_tsc_page_status == HV_TSC_PAGE_UNSET)
return;
mutex_lock(&hv->hv_lock);
@ -1101,7 +1117,15 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
*/
if (unlikely(kvm_read_guest(kvm, gfn_to_gpa(gfn),
&tsc_seq, sizeof(tsc_seq))))
goto out_err;
if (tsc_seq && tsc_page_update_unsafe(hv)) {
if (kvm_read_guest(kvm, gfn_to_gpa(gfn), &hv->tsc_ref, sizeof(hv->tsc_ref)))
goto out_err;
hv->hv_tsc_page_status = HV_TSC_PAGE_SET;
goto out_unlock;
}
/*
* While we're computing and writing the parameters, force the
@ -1110,15 +1134,15 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
hv->tsc_ref.tsc_sequence = 0;
if (kvm_write_guest(kvm, gfn_to_gpa(gfn),
&hv->tsc_ref, sizeof(hv->tsc_ref.tsc_sequence)))
goto out_unlock;
goto out_err;
if (!compute_tsc_page_parameters(hv_clock, &hv->tsc_ref))
goto out_unlock;
goto out_err;
/* Ensure sequence is zero before writing the rest of the struct. */
smp_wmb();
if (kvm_write_guest(kvm, gfn_to_gpa(gfn), &hv->tsc_ref, sizeof(hv->tsc_ref)))
goto out_unlock;
goto out_err;
/*
* Now switch to the TSC page mechanism by writing the sequence.
@ -1131,8 +1155,45 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
smp_wmb();
hv->tsc_ref.tsc_sequence = tsc_seq;
kvm_write_guest(kvm, gfn_to_gpa(gfn),
&hv->tsc_ref, sizeof(hv->tsc_ref.tsc_sequence));
if (kvm_write_guest(kvm, gfn_to_gpa(gfn),
&hv->tsc_ref, sizeof(hv->tsc_ref.tsc_sequence)))
goto out_err;
hv->hv_tsc_page_status = HV_TSC_PAGE_SET;
goto out_unlock;
out_err:
hv->hv_tsc_page_status = HV_TSC_PAGE_BROKEN;
out_unlock:
mutex_unlock(&hv->hv_lock);
}
void kvm_hv_invalidate_tsc_page(struct kvm *kvm)
{
struct kvm_hv *hv = to_kvm_hv(kvm);
u64 gfn;
if (hv->hv_tsc_page_status == HV_TSC_PAGE_BROKEN ||
hv->hv_tsc_page_status == HV_TSC_PAGE_UNSET ||
tsc_page_update_unsafe(hv))
return;
mutex_lock(&hv->hv_lock);
if (!(hv->hv_tsc_page & HV_X64_MSR_TSC_REFERENCE_ENABLE))
goto out_unlock;
/* Preserve HV_TSC_PAGE_GUEST_CHANGED/HV_TSC_PAGE_HOST_CHANGED states */
if (hv->hv_tsc_page_status == HV_TSC_PAGE_SET)
hv->hv_tsc_page_status = HV_TSC_PAGE_UPDATING;
gfn = hv->hv_tsc_page >> HV_X64_MSR_TSC_REFERENCE_ADDRESS_SHIFT;
hv->tsc_ref.tsc_sequence = 0;
if (kvm_write_guest(kvm, gfn_to_gpa(gfn),
&hv->tsc_ref, sizeof(hv->tsc_ref.tsc_sequence)))
hv->hv_tsc_page_status = HV_TSC_PAGE_BROKEN;
out_unlock:
mutex_unlock(&hv->hv_lock);
}
@ -1193,8 +1254,15 @@ static int kvm_hv_set_msr_pw(struct kvm_vcpu *vcpu, u32 msr, u64 data,
}
case HV_X64_MSR_REFERENCE_TSC:
hv->hv_tsc_page = data;
if (hv->hv_tsc_page & HV_X64_MSR_TSC_REFERENCE_ENABLE)
if (hv->hv_tsc_page & HV_X64_MSR_TSC_REFERENCE_ENABLE) {
if (!host)
hv->hv_tsc_page_status = HV_TSC_PAGE_GUEST_CHANGED;
else
hv->hv_tsc_page_status = HV_TSC_PAGE_HOST_CHANGED;
kvm_make_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);
} else {
hv->hv_tsc_page_status = HV_TSC_PAGE_UNSET;
}
break;
case HV_X64_MSR_CRASH_P0 ... HV_X64_MSR_CRASH_P4:
return kvm_hv_msr_set_crash_data(kvm,
@ -1229,6 +1297,9 @@ static int kvm_hv_set_msr_pw(struct kvm_vcpu *vcpu, u32 msr, u64 data,
hv->hv_tsc_emulation_control = data;
break;
case HV_X64_MSR_TSC_EMULATION_STATUS:
if (data && !host)
return 1;
hv->hv_tsc_emulation_status = data;
break;
case HV_X64_MSR_TIME_REF_COUNT:

Просмотреть файл

@ -133,6 +133,7 @@ void kvm_hv_process_stimers(struct kvm_vcpu *vcpu);
void kvm_hv_setup_tsc_page(struct kvm *kvm,
struct pvclock_vcpu_time_info *hv_clock);
void kvm_hv_invalidate_tsc_page(struct kvm *kvm);
void kvm_hv_init_vm(struct kvm *kvm);
void kvm_hv_destroy_vm(struct kvm *kvm);

Просмотреть файл

@ -78,6 +78,11 @@ static inline struct kvm_mmu_page *sptep_to_sp(u64 *sptep)
return to_shadow_page(__pa(sptep));
}
static inline int kvm_mmu_page_as_id(struct kvm_mmu_page *sp)
{
return sp->role.smm ? 1 : 0;
}
static inline bool kvm_vcpu_ad_need_write_protect(struct kvm_vcpu *vcpu)
{
/*

Просмотреть файл

@ -20,6 +20,21 @@ static gfn_t round_gfn_for_level(gfn_t gfn, int level)
return gfn & -KVM_PAGES_PER_HPAGE(level);
}
/*
* Return the TDP iterator to the root PT and allow it to continue its
* traversal over the paging structure from there.
*/
void tdp_iter_restart(struct tdp_iter *iter)
{
iter->yielded_gfn = iter->next_last_level_gfn;
iter->level = iter->root_level;
iter->gfn = round_gfn_for_level(iter->next_last_level_gfn, iter->level);
tdp_iter_refresh_sptep(iter);
iter->valid = true;
}
/*
* Sets a TDP iterator to walk a pre-order traversal of the paging structure
* rooted at root_pt, starting with the walk to translate next_last_level_gfn.
@ -31,16 +46,12 @@ void tdp_iter_start(struct tdp_iter *iter, u64 *root_pt, int root_level,
WARN_ON(root_level > PT64_ROOT_MAX_LEVEL);
iter->next_last_level_gfn = next_last_level_gfn;
iter->yielded_gfn = iter->next_last_level_gfn;
iter->root_level = root_level;
iter->min_level = min_level;
iter->level = root_level;
iter->pt_path[iter->level - 1] = (tdp_ptep_t)root_pt;
iter->pt_path[iter->root_level - 1] = (tdp_ptep_t)root_pt;
iter->as_id = kvm_mmu_page_as_id(sptep_to_sp(root_pt));
iter->gfn = round_gfn_for_level(iter->next_last_level_gfn, iter->level);
tdp_iter_refresh_sptep(iter);
iter->valid = true;
tdp_iter_restart(iter);
}
/*
@ -159,8 +170,3 @@ void tdp_iter_next(struct tdp_iter *iter)
iter->valid = false;
}
tdp_ptep_t tdp_iter_root_pt(struct tdp_iter *iter)
{
return iter->pt_path[iter->root_level - 1];
}

Просмотреть файл

@ -36,6 +36,8 @@ struct tdp_iter {
int min_level;
/* The iterator's current level within the paging structure */
int level;
/* The address space ID, i.e. SMM vs. regular. */
int as_id;
/* A snapshot of the value at sptep */
u64 old_spte;
/*
@ -62,6 +64,6 @@ tdp_ptep_t spte_to_child_pt(u64 pte, int level);
void tdp_iter_start(struct tdp_iter *iter, u64 *root_pt, int root_level,
int min_level, gfn_t next_last_level_gfn);
void tdp_iter_next(struct tdp_iter *iter);
tdp_ptep_t tdp_iter_root_pt(struct tdp_iter *iter);
void tdp_iter_restart(struct tdp_iter *iter);
#endif /* __KVM_X86_MMU_TDP_ITER_H */

Просмотреть файл

@ -203,11 +203,6 @@ static void handle_changed_spte(struct kvm *kvm, int as_id, gfn_t gfn,
u64 old_spte, u64 new_spte, int level,
bool shared);
static int kvm_mmu_page_as_id(struct kvm_mmu_page *sp)
{
return sp->role.smm ? 1 : 0;
}
static void handle_changed_spte_acc_track(u64 old_spte, u64 new_spte, int level)
{
bool pfn_changed = spte_to_pfn(old_spte) != spte_to_pfn(new_spte);
@ -301,11 +296,16 @@ static void tdp_mmu_unlink_page(struct kvm *kvm, struct kvm_mmu_page *sp,
*
* Given a page table that has been removed from the TDP paging structure,
* iterates through the page table to clear SPTEs and free child page tables.
*
* Note that pt is passed in as a tdp_ptep_t, but it does not need RCU
* protection. Since this thread removed it from the paging structure,
* this thread will be responsible for ensuring the page is freed. Hence the
* early rcu_dereferences in the function.
*/
static void handle_removed_tdp_mmu_page(struct kvm *kvm, u64 *pt,
static void handle_removed_tdp_mmu_page(struct kvm *kvm, tdp_ptep_t pt,
bool shared)
{
struct kvm_mmu_page *sp = sptep_to_sp(pt);
struct kvm_mmu_page *sp = sptep_to_sp(rcu_dereference(pt));
int level = sp->role.level;
gfn_t base_gfn = sp->gfn;
u64 old_child_spte;
@ -318,7 +318,7 @@ static void handle_removed_tdp_mmu_page(struct kvm *kvm, u64 *pt,
tdp_mmu_unlink_page(kvm, sp, shared);
for (i = 0; i < PT64_ENT_PER_PAGE; i++) {
sptep = pt + i;
sptep = rcu_dereference(pt) + i;
gfn = base_gfn + (i * KVM_PAGES_PER_HPAGE(level - 1));
if (shared) {
@ -492,10 +492,6 @@ static inline bool tdp_mmu_set_spte_atomic(struct kvm *kvm,
struct tdp_iter *iter,
u64 new_spte)
{
u64 *root_pt = tdp_iter_root_pt(iter);
struct kvm_mmu_page *root = sptep_to_sp(root_pt);
int as_id = kvm_mmu_page_as_id(root);
lockdep_assert_held_read(&kvm->mmu_lock);
/*
@ -509,8 +505,8 @@ static inline bool tdp_mmu_set_spte_atomic(struct kvm *kvm,
new_spte) != iter->old_spte)
return false;
handle_changed_spte(kvm, as_id, iter->gfn, iter->old_spte, new_spte,
iter->level, true);
handle_changed_spte(kvm, iter->as_id, iter->gfn, iter->old_spte,
new_spte, iter->level, true);
return true;
}
@ -538,7 +534,7 @@ static inline bool tdp_mmu_zap_spte_atomic(struct kvm *kvm,
* here since the SPTE is going from non-present
* to non-present.
*/
WRITE_ONCE(*iter->sptep, 0);
WRITE_ONCE(*rcu_dereference(iter->sptep), 0);
return true;
}
@ -564,10 +560,6 @@ static inline void __tdp_mmu_set_spte(struct kvm *kvm, struct tdp_iter *iter,
u64 new_spte, bool record_acc_track,
bool record_dirty_log)
{
tdp_ptep_t root_pt = tdp_iter_root_pt(iter);
struct kvm_mmu_page *root = sptep_to_sp(root_pt);
int as_id = kvm_mmu_page_as_id(root);
lockdep_assert_held_write(&kvm->mmu_lock);
/*
@ -581,13 +573,13 @@ static inline void __tdp_mmu_set_spte(struct kvm *kvm, struct tdp_iter *iter,
WRITE_ONCE(*rcu_dereference(iter->sptep), new_spte);
__handle_changed_spte(kvm, as_id, iter->gfn, iter->old_spte, new_spte,
iter->level, false);
__handle_changed_spte(kvm, iter->as_id, iter->gfn, iter->old_spte,
new_spte, iter->level, false);
if (record_acc_track)
handle_changed_spte_acc_track(iter->old_spte, new_spte,
iter->level);
if (record_dirty_log)
handle_changed_spte_dirty_log(kvm, as_id, iter->gfn,
handle_changed_spte_dirty_log(kvm, iter->as_id, iter->gfn,
iter->old_spte, new_spte,
iter->level);
}
@ -659,9 +651,7 @@ static inline bool tdp_mmu_iter_cond_resched(struct kvm *kvm,
WARN_ON(iter->gfn > iter->next_last_level_gfn);
tdp_iter_start(iter, iter->pt_path[iter->root_level - 1],
iter->root_level, iter->min_level,
iter->next_last_level_gfn);
tdp_iter_restart(iter);
return true;
}

Просмотреть файл

@ -1526,35 +1526,44 @@ EXPORT_SYMBOL_GPL(kvm_enable_efer_bits);
bool kvm_msr_allowed(struct kvm_vcpu *vcpu, u32 index, u32 type)
{
struct kvm_x86_msr_filter *msr_filter;
struct msr_bitmap_range *ranges;
struct kvm *kvm = vcpu->kvm;
struct msr_bitmap_range *ranges = kvm->arch.msr_filter.ranges;
u32 count = kvm->arch.msr_filter.count;
u32 i;
bool r = kvm->arch.msr_filter.default_allow;
bool allowed;
int idx;
u32 i;
/* MSR filtering not set up or x2APIC enabled, allow everything */
if (!count || (index >= 0x800 && index <= 0x8ff))
/* x2APIC MSRs do not support filtering. */
if (index >= 0x800 && index <= 0x8ff)
return true;
/* Prevent collision with set_msr_filter */
idx = srcu_read_lock(&kvm->srcu);
for (i = 0; i < count; i++) {
msr_filter = srcu_dereference(kvm->arch.msr_filter, &kvm->srcu);
if (!msr_filter) {
allowed = true;
goto out;
}
allowed = msr_filter->default_allow;
ranges = msr_filter->ranges;
for (i = 0; i < msr_filter->count; i++) {
u32 start = ranges[i].base;
u32 end = start + ranges[i].nmsrs;
u32 flags = ranges[i].flags;
unsigned long *bitmap = ranges[i].bitmap;
if ((index >= start) && (index < end) && (flags & type)) {
r = !!test_bit(index - start, bitmap);
allowed = !!test_bit(index - start, bitmap);
break;
}
}
out:
srcu_read_unlock(&kvm->srcu, idx);
return r;
return allowed;
}
EXPORT_SYMBOL_GPL(kvm_msr_allowed);
@ -2551,6 +2560,8 @@ static void kvm_gen_update_masterclock(struct kvm *kvm)
struct kvm_vcpu *vcpu;
struct kvm_arch *ka = &kvm->arch;
kvm_hv_invalidate_tsc_page(kvm);
spin_lock(&ka->pvclock_gtod_sync_lock);
kvm_make_mclock_inprogress_request(kvm);
/* no guest entries from this point */
@ -5352,25 +5363,34 @@ split_irqchip_unlock:
return r;
}
static void kvm_clear_msr_filter(struct kvm *kvm)
static struct kvm_x86_msr_filter *kvm_alloc_msr_filter(bool default_allow)
{
u32 i;
u32 count = kvm->arch.msr_filter.count;
struct msr_bitmap_range ranges[16];
struct kvm_x86_msr_filter *msr_filter;
mutex_lock(&kvm->lock);
kvm->arch.msr_filter.count = 0;
memcpy(ranges, kvm->arch.msr_filter.ranges, count * sizeof(ranges[0]));
mutex_unlock(&kvm->lock);
synchronize_srcu(&kvm->srcu);
msr_filter = kzalloc(sizeof(*msr_filter), GFP_KERNEL_ACCOUNT);
if (!msr_filter)
return NULL;
for (i = 0; i < count; i++)
kfree(ranges[i].bitmap);
msr_filter->default_allow = default_allow;
return msr_filter;
}
static int kvm_add_msr_filter(struct kvm *kvm, struct kvm_msr_filter_range *user_range)
static void kvm_free_msr_filter(struct kvm_x86_msr_filter *msr_filter)
{
u32 i;
if (!msr_filter)
return;
for (i = 0; i < msr_filter->count; i++)
kfree(msr_filter->ranges[i].bitmap);
kfree(msr_filter);
}
static int kvm_add_msr_filter(struct kvm_x86_msr_filter *msr_filter,
struct kvm_msr_filter_range *user_range)
{
struct msr_bitmap_range *ranges = kvm->arch.msr_filter.ranges;
struct msr_bitmap_range range;
unsigned long *bitmap = NULL;
size_t bitmap_size;
@ -5404,11 +5424,9 @@ static int kvm_add_msr_filter(struct kvm *kvm, struct kvm_msr_filter_range *user
goto err;
}
/* Everything ok, add this range identifier to our global pool */
ranges[kvm->arch.msr_filter.count] = range;
/* Make sure we filled the array before we tell anyone to walk it */
smp_wmb();
kvm->arch.msr_filter.count++;
/* Everything ok, add this range identifier. */
msr_filter->ranges[msr_filter->count] = range;
msr_filter->count++;
return 0;
err:
@ -5419,10 +5437,11 @@ err:
static int kvm_vm_ioctl_set_msr_filter(struct kvm *kvm, void __user *argp)
{
struct kvm_msr_filter __user *user_msr_filter = argp;
struct kvm_x86_msr_filter *new_filter, *old_filter;
struct kvm_msr_filter filter;
bool default_allow;
int r = 0;
bool empty = true;
int r = 0;
u32 i;
if (copy_from_user(&filter, user_msr_filter, sizeof(filter)))
@ -5435,25 +5454,32 @@ static int kvm_vm_ioctl_set_msr_filter(struct kvm *kvm, void __user *argp)
if (empty && !default_allow)
return -EINVAL;
kvm_clear_msr_filter(kvm);
new_filter = kvm_alloc_msr_filter(default_allow);
if (!new_filter)
return -ENOMEM;
kvm->arch.msr_filter.default_allow = default_allow;
/*
* Protect from concurrent calls to this function that could trigger
* a TOCTOU violation on kvm->arch.msr_filter.count.
*/
mutex_lock(&kvm->lock);
for (i = 0; i < ARRAY_SIZE(filter.ranges); i++) {
r = kvm_add_msr_filter(kvm, &filter.ranges[i]);
if (r)
break;
r = kvm_add_msr_filter(new_filter, &filter.ranges[i]);
if (r) {
kvm_free_msr_filter(new_filter);
return r;
}
}
mutex_lock(&kvm->lock);
/* The per-VM filter is protected by kvm->lock... */
old_filter = srcu_dereference_check(kvm->arch.msr_filter, &kvm->srcu, 1);
rcu_assign_pointer(kvm->arch.msr_filter, new_filter);
synchronize_srcu(&kvm->srcu);
kvm_free_msr_filter(old_filter);
kvm_make_all_cpus_request(kvm, KVM_REQ_MSR_FILTER_CHANGED);
mutex_unlock(&kvm->lock);
return r;
return 0;
}
long kvm_arch_vm_ioctl(struct file *filp,
@ -6603,7 +6629,7 @@ static int kvm_emulate_wbinvd_noskip(struct kvm_vcpu *vcpu)
int cpu = get_cpu();
cpumask_set_cpu(cpu, vcpu->arch.wbinvd_dirty_mask);
smp_call_function_many(vcpu->arch.wbinvd_dirty_mask,
on_each_cpu_mask(vcpu->arch.wbinvd_dirty_mask,
wbinvd_ipi, NULL, 1);
put_cpu();
cpumask_clear(vcpu->arch.wbinvd_dirty_mask);
@ -10634,8 +10660,6 @@ void kvm_arch_pre_destroy_vm(struct kvm *kvm)
void kvm_arch_destroy_vm(struct kvm *kvm)
{
u32 i;
if (current->mm == kvm->mm) {
/*
* Free memory regions allocated on behalf of userspace,
@ -10651,8 +10675,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
mutex_unlock(&kvm->slots_lock);
}
static_call_cond(kvm_x86_vm_destroy)(kvm);
for (i = 0; i < kvm->arch.msr_filter.count; i++)
kfree(kvm->arch.msr_filter.ranges[i].bitmap);
kvm_free_msr_filter(srcu_dereference_check(kvm->arch.msr_filter, &kvm->srcu, 1));
kvm_pic_destroy(kvm);
kvm_ioapic_destroy(kvm);
kvm_free_vcpus(kvm);

3
tools/testing/selftests/kvm/.gitignore поставляемый
Просмотреть файл

@ -8,10 +8,13 @@
/x86_64/debug_regs
/x86_64/evmcs_test
/x86_64/get_cpuid_test
/x86_64/get_msr_index_features
/x86_64/kvm_pv_test
/x86_64/hyperv_clock
/x86_64/hyperv_cpuid
/x86_64/mmio_warning_test
/x86_64/platform_info_test
/x86_64/set_boot_cpu_id
/x86_64/set_sregs_test
/x86_64/smm_test
/x86_64/state_test

Просмотреть файл

@ -39,12 +39,15 @@ LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c
LIBKVM_s390x = lib/s390x/processor.c lib/s390x/ucall.c lib/s390x/diag318_test_handler.c
TEST_GEN_PROGS_x86_64 = x86_64/cr4_cpuid_sync_test
TEST_GEN_PROGS_x86_64 += x86_64/get_msr_index_features
TEST_GEN_PROGS_x86_64 += x86_64/evmcs_test
TEST_GEN_PROGS_x86_64 += x86_64/get_cpuid_test
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_clock
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_cpuid
TEST_GEN_PROGS_x86_64 += x86_64/kvm_pv_test
TEST_GEN_PROGS_x86_64 += x86_64/mmio_warning_test
TEST_GEN_PROGS_x86_64 += x86_64/platform_info_test
TEST_GEN_PROGS_x86_64 += x86_64/set_boot_cpu_id
TEST_GEN_PROGS_x86_64 += x86_64/set_sregs_test
TEST_GEN_PROGS_x86_64 += x86_64/smm_test
TEST_GEN_PROGS_x86_64 += x86_64/state_test

Просмотреть файл

@ -16,6 +16,7 @@
#include "sparsebit.h"
#define KVM_DEV_PATH "/dev/kvm"
#define KVM_MAX_VCPUS 512
/*
@ -133,6 +134,7 @@ void vcpu_ioctl(struct kvm_vm *vm, uint32_t vcpuid, unsigned long ioctl,
int _vcpu_ioctl(struct kvm_vm *vm, uint32_t vcpuid, unsigned long ioctl,
void *arg);
void vm_ioctl(struct kvm_vm *vm, unsigned long ioctl, void *arg);
int _vm_ioctl(struct kvm_vm *vm, unsigned long cmd, void *arg);
void kvm_ioctl(struct kvm_vm *vm, unsigned long ioctl, void *arg);
int _kvm_ioctl(struct kvm_vm *vm, unsigned long ioctl, void *arg);
void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags);

Просмотреть файл

@ -1697,11 +1697,16 @@ void vm_ioctl(struct kvm_vm *vm, unsigned long cmd, void *arg)
{
int ret;
ret = ioctl(vm->fd, cmd, arg);
ret = _vm_ioctl(vm, cmd, arg);
TEST_ASSERT(ret == 0, "vm ioctl %lu failed, rc: %i errno: %i (%s)",
cmd, ret, errno, strerror(errno));
}
int _vm_ioctl(struct kvm_vm *vm, unsigned long cmd, void *arg)
{
return ioctl(vm->fd, cmd, arg);
}
/*
* KVM system ioctl
*

Просмотреть файл

@ -10,8 +10,6 @@
#include "sparsebit.h"
#define KVM_DEV_PATH "/dev/kvm"
struct userspace_mem_region {
struct kvm_userspace_memory_region region;
struct sparsebit *unused_phy_pages;

Просмотреть файл

@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test that KVM_GET_MSR_INDEX_LIST and
* KVM_GET_MSR_FEATURE_INDEX_LIST work as intended
*
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
static int kvm_num_index_msrs(int kvm_fd, int nmsrs)
{
struct kvm_msr_list *list;
int r;
list = malloc(sizeof(*list) + nmsrs * sizeof(list->indices[0]));
list->nmsrs = nmsrs;
r = ioctl(kvm_fd, KVM_GET_MSR_INDEX_LIST, list);
TEST_ASSERT(r == -1 && errno == E2BIG,
"Unexpected result from KVM_GET_MSR_INDEX_LIST probe, r: %i",
r);
r = list->nmsrs;
free(list);
return r;
}
static void test_get_msr_index(void)
{
int old_res, res, kvm_fd, r;
struct kvm_msr_list *list;
kvm_fd = open(KVM_DEV_PATH, O_RDONLY);
if (kvm_fd < 0)
exit(KSFT_SKIP);
old_res = kvm_num_index_msrs(kvm_fd, 0);
TEST_ASSERT(old_res != 0, "Expecting nmsrs to be > 0");
if (old_res != 1) {
res = kvm_num_index_msrs(kvm_fd, 1);
TEST_ASSERT(res > 1, "Expecting nmsrs to be > 1");
TEST_ASSERT(res == old_res, "Expecting nmsrs to be identical");
}
list = malloc(sizeof(*list) + old_res * sizeof(list->indices[0]));
list->nmsrs = old_res;
r = ioctl(kvm_fd, KVM_GET_MSR_INDEX_LIST, list);
TEST_ASSERT(r == 0,
"Unexpected result from KVM_GET_MSR_FEATURE_INDEX_LIST, r: %i",
r);
TEST_ASSERT(list->nmsrs == old_res, "Expecting nmsrs to be identical");
free(list);
close(kvm_fd);
}
static int kvm_num_feature_msrs(int kvm_fd, int nmsrs)
{
struct kvm_msr_list *list;
int r;
list = malloc(sizeof(*list) + nmsrs * sizeof(list->indices[0]));
list->nmsrs = nmsrs;
r = ioctl(kvm_fd, KVM_GET_MSR_FEATURE_INDEX_LIST, list);
TEST_ASSERT(r == -1 && errno == E2BIG,
"Unexpected result from KVM_GET_MSR_FEATURE_INDEX_LIST probe, r: %i",
r);
r = list->nmsrs;
free(list);
return r;
}
struct kvm_msr_list *kvm_get_msr_feature_list(int kvm_fd, int nmsrs)
{
struct kvm_msr_list *list;
int r;
list = malloc(sizeof(*list) + nmsrs * sizeof(list->indices[0]));
list->nmsrs = nmsrs;
r = ioctl(kvm_fd, KVM_GET_MSR_FEATURE_INDEX_LIST, list);
TEST_ASSERT(r == 0,
"Unexpected result from KVM_GET_MSR_FEATURE_INDEX_LIST, r: %i",
r);
return list;
}
static void test_get_msr_feature(void)
{
int res, old_res, i, kvm_fd;
struct kvm_msr_list *feature_list;
kvm_fd = open(KVM_DEV_PATH, O_RDONLY);
if (kvm_fd < 0)
exit(KSFT_SKIP);
old_res = kvm_num_feature_msrs(kvm_fd, 0);
TEST_ASSERT(old_res != 0, "Expecting nmsrs to be > 0");
if (old_res != 1) {
res = kvm_num_feature_msrs(kvm_fd, 1);
TEST_ASSERT(res > 1, "Expecting nmsrs to be > 1");
TEST_ASSERT(res == old_res, "Expecting nmsrs to be identical");
}
feature_list = kvm_get_msr_feature_list(kvm_fd, old_res);
TEST_ASSERT(old_res == feature_list->nmsrs,
"Unmatching number of msr indexes");
for (i = 0; i < feature_list->nmsrs; i++)
kvm_get_feature_msr(feature_list->indices[i]);
free(feature_list);
close(kvm_fd);
}
int main(int argc, char *argv[])
{
if (kvm_check_cap(KVM_CAP_GET_MSR_FEATURES))
test_get_msr_feature();
test_get_msr_index();
}

Просмотреть файл

@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021, Red Hat, Inc.
*
* Tests for Hyper-V clocksources
*/
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
struct ms_hyperv_tsc_page {
volatile u32 tsc_sequence;
u32 reserved1;
volatile u64 tsc_scale;
volatile s64 tsc_offset;
} __packed;
#define HV_X64_MSR_GUEST_OS_ID 0x40000000
#define HV_X64_MSR_TIME_REF_COUNT 0x40000020
#define HV_X64_MSR_REFERENCE_TSC 0x40000021
#define HV_X64_MSR_TSC_FREQUENCY 0x40000022
#define HV_X64_MSR_REENLIGHTENMENT_CONTROL 0x40000106
#define HV_X64_MSR_TSC_EMULATION_CONTROL 0x40000107
/* Simplified mul_u64_u64_shr() */
static inline u64 mul_u64_u64_shr64(u64 a, u64 b)
{
union {
u64 ll;
struct {
u32 low, high;
} l;
} rm, rn, rh, a0, b0;
u64 c;
a0.ll = a;
b0.ll = b;
rm.ll = (u64)a0.l.low * b0.l.high;
rn.ll = (u64)a0.l.high * b0.l.low;
rh.ll = (u64)a0.l.high * b0.l.high;
rh.l.low = c = rm.l.high + rn.l.high + rh.l.low;
rh.l.high = (c >> 32) + rh.l.high;
return rh.ll;
}
static inline void nop_loop(void)
{
int i;
for (i = 0; i < 1000000; i++)
asm volatile("nop");
}
static inline void check_tsc_msr_rdtsc(void)
{
u64 tsc_freq, r1, r2, t1, t2;
s64 delta_ns;
tsc_freq = rdmsr(HV_X64_MSR_TSC_FREQUENCY);
GUEST_ASSERT(tsc_freq > 0);
/* First, check MSR-based clocksource */
r1 = rdtsc();
t1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
nop_loop();
r2 = rdtsc();
t2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
GUEST_ASSERT(r2 > r1 && t2 > t1);
/* HV_X64_MSR_TIME_REF_COUNT is in 100ns */
delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);
if (delta_ns < 0)
delta_ns = -delta_ns;
/* 1% tolerance */
GUEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100);
}
static inline void check_tsc_msr_tsc_page(struct ms_hyperv_tsc_page *tsc_page)
{
u64 r1, r2, t1, t2;
/* Compare TSC page clocksource with HV_X64_MSR_TIME_REF_COUNT */
t1 = mul_u64_u64_shr64(rdtsc(), tsc_page->tsc_scale) + tsc_page->tsc_offset;
r1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
/* 10 ms tolerance */
GUEST_ASSERT(r1 >= t1 && r1 - t1 < 100000);
nop_loop();
t2 = mul_u64_u64_shr64(rdtsc(), tsc_page->tsc_scale) + tsc_page->tsc_offset;
r2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
GUEST_ASSERT(r2 >= t1 && r2 - t2 < 100000);
}
static void guest_main(struct ms_hyperv_tsc_page *tsc_page, vm_paddr_t tsc_page_gpa)
{
u64 tsc_scale, tsc_offset;
/* Set Guest OS id to enable Hyper-V emulation */
GUEST_SYNC(1);
wrmsr(HV_X64_MSR_GUEST_OS_ID, (u64)0x8100 << 48);
GUEST_SYNC(2);
check_tsc_msr_rdtsc();
GUEST_SYNC(3);
/* Set up TSC page is disabled state, check that it's clean */
wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa);
GUEST_ASSERT(tsc_page->tsc_sequence == 0);
GUEST_ASSERT(tsc_page->tsc_scale == 0);
GUEST_ASSERT(tsc_page->tsc_offset == 0);
GUEST_SYNC(4);
/* Set up TSC page is enabled state */
wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa | 0x1);
GUEST_ASSERT(tsc_page->tsc_sequence != 0);
GUEST_SYNC(5);
check_tsc_msr_tsc_page(tsc_page);
GUEST_SYNC(6);
tsc_offset = tsc_page->tsc_offset;
/* Call KVM_SET_CLOCK from userspace, check that TSC page was updated */
GUEST_SYNC(7);
GUEST_ASSERT(tsc_page->tsc_offset != tsc_offset);
nop_loop();
/*
* Enable Re-enlightenment and check that TSC page stays constant across
* KVM_SET_CLOCK.
*/
wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0x1 << 16 | 0xff);
wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0x1);
tsc_offset = tsc_page->tsc_offset;
tsc_scale = tsc_page->tsc_scale;
GUEST_SYNC(8);
GUEST_ASSERT(tsc_page->tsc_offset == tsc_offset);
GUEST_ASSERT(tsc_page->tsc_scale == tsc_scale);
GUEST_SYNC(9);
check_tsc_msr_tsc_page(tsc_page);
/*
* Disable re-enlightenment and TSC page, check that KVM doesn't update
* it anymore.
*/
wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0);
wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0);
wrmsr(HV_X64_MSR_REFERENCE_TSC, 0);
memset(tsc_page, 0, sizeof(*tsc_page));
GUEST_SYNC(10);
GUEST_ASSERT(tsc_page->tsc_sequence == 0);
GUEST_ASSERT(tsc_page->tsc_offset == 0);
GUEST_ASSERT(tsc_page->tsc_scale == 0);
GUEST_DONE();
}
#define VCPU_ID 0
static void host_check_tsc_msr_rdtsc(struct kvm_vm *vm)
{
u64 tsc_freq, r1, r2, t1, t2;
s64 delta_ns;
tsc_freq = vcpu_get_msr(vm, VCPU_ID, HV_X64_MSR_TSC_FREQUENCY);
TEST_ASSERT(tsc_freq > 0, "TSC frequency must be nonzero");
/* First, check MSR-based clocksource */
r1 = rdtsc();
t1 = vcpu_get_msr(vm, VCPU_ID, HV_X64_MSR_TIME_REF_COUNT);
nop_loop();
r2 = rdtsc();
t2 = vcpu_get_msr(vm, VCPU_ID, HV_X64_MSR_TIME_REF_COUNT);
TEST_ASSERT(t2 > t1, "Time reference MSR is not monotonic (%ld <= %ld)", t1, t2);
/* HV_X64_MSR_TIME_REF_COUNT is in 100ns */
delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);
if (delta_ns < 0)
delta_ns = -delta_ns;
/* 1% tolerance */
TEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100,
"Elapsed time does not match (MSR=%ld, TSC=%ld)",
(t2 - t1) * 100, (r2 - r1) * 1000000000 / tsc_freq);
}
int main(void)
{
struct kvm_vm *vm;
struct kvm_run *run;
struct ucall uc;
vm_vaddr_t tsc_page_gva;
int stage;
vm = vm_create_default(VCPU_ID, 0, guest_main);
run = vcpu_state(vm, VCPU_ID);
vcpu_set_hv_cpuid(vm, VCPU_ID);
tsc_page_gva = vm_vaddr_alloc(vm, getpagesize(), 0x10000, 0, 0);
memset(addr_gpa2hva(vm, tsc_page_gva), 0x0, getpagesize());
TEST_ASSERT((addr_gva2gpa(vm, tsc_page_gva) & (getpagesize() - 1)) == 0,
"TSC page has to be page aligned\n");
vcpu_args_set(vm, VCPU_ID, 2, tsc_page_gva, addr_gva2gpa(vm, tsc_page_gva));
host_check_tsc_msr_rdtsc(vm);
for (stage = 1;; stage++) {
_vcpu_run(vm, VCPU_ID);
TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
"Stage %d: unexpected exit reason: %u (%s),\n",
stage, run->exit_reason,
exit_reason_str(run->exit_reason));
switch (get_ucall(vm, VCPU_ID, &uc)) {
case UCALL_ABORT:
TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0],
__FILE__, uc.args[1]);
/* NOT REACHED */
case UCALL_SYNC:
break;
case UCALL_DONE:
/* Keep in sync with guest_main() */
TEST_ASSERT(stage == 11, "Testing ended prematurely, stage %d\n",
stage);
goto out;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
uc.args[1] == stage,
"Stage %d: Unexpected register values vmexit, got %lx",
stage, (ulong)uc.args[1]);
/* Reset kvmclock triggering TSC page update */
if (stage == 7 || stage == 8 || stage == 10) {
struct kvm_clock_data clock = {0};
vm_ioctl(vm, KVM_SET_CLOCK, &clock);
}
}
out:
kvm_vm_free(vm);
}

Просмотреть файл

@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test that KVM_SET_BOOT_CPU_ID works as intended
*
* Copyright (C) 2020, Red Hat, Inc.
*/
#define _GNU_SOURCE /* for program_invocation_name */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
#define N_VCPU 2
#define VCPU_ID0 0
#define VCPU_ID1 1
static uint32_t get_bsp_flag(void)
{
return rdmsr(MSR_IA32_APICBASE) & MSR_IA32_APICBASE_BSP;
}
static void guest_bsp_vcpu(void *arg)
{
GUEST_SYNC(1);
GUEST_ASSERT(get_bsp_flag() != 0);
GUEST_DONE();
}
static void guest_not_bsp_vcpu(void *arg)
{
GUEST_SYNC(1);
GUEST_ASSERT(get_bsp_flag() == 0);
GUEST_DONE();
}
static void test_set_boot_busy(struct kvm_vm *vm)
{
int res;
res = _vm_ioctl(vm, KVM_SET_BOOT_CPU_ID, (void *) VCPU_ID0);
TEST_ASSERT(res == -1 && errno == EBUSY,
"KVM_SET_BOOT_CPU_ID set while running vm");
}
static void run_vcpu(struct kvm_vm *vm, uint32_t vcpuid)
{
struct ucall uc;
int stage;
for (stage = 0; stage < 2; stage++) {
vcpu_run(vm, vcpuid);
switch (get_ucall(vm, vcpuid, &uc)) {
case UCALL_SYNC:
TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
uc.args[1] == stage + 1,
"Stage %d: Unexpected register values vmexit, got %lx",
stage + 1, (ulong)uc.args[1]);
test_set_boot_busy(vm);
break;
case UCALL_DONE:
TEST_ASSERT(stage == 1,
"Expected GUEST_DONE in stage 2, got stage %d",
stage);
break;
case UCALL_ABORT:
TEST_ASSERT(false, "%s at %s:%ld\n\tvalues: %#lx, %#lx",
(const char *)uc.args[0], __FILE__,
uc.args[1], uc.args[2], uc.args[3]);
default:
TEST_ASSERT(false, "Unexpected exit: %s",
exit_reason_str(vcpu_state(vm, vcpuid)->exit_reason));
}
}
}
static struct kvm_vm *create_vm(void)
{
struct kvm_vm *vm;
uint64_t vcpu_pages = (DEFAULT_STACK_PGS) * 2;
uint64_t extra_pg_pages = vcpu_pages / PTES_PER_MIN_PAGE * N_VCPU;
uint64_t pages = DEFAULT_GUEST_PHY_PAGES + vcpu_pages + extra_pg_pages;
pages = vm_adjust_num_guest_pages(VM_MODE_DEFAULT, pages);
vm = vm_create(VM_MODE_DEFAULT, pages, O_RDWR);
kvm_vm_elf_load(vm, program_invocation_name, 0, 0);
vm_create_irqchip(vm);
return vm;
}
static void add_x86_vcpu(struct kvm_vm *vm, uint32_t vcpuid, bool bsp_code)
{
if (bsp_code)
vm_vcpu_add_default(vm, vcpuid, guest_bsp_vcpu);
else
vm_vcpu_add_default(vm, vcpuid, guest_not_bsp_vcpu);
vcpu_set_cpuid(vm, vcpuid, kvm_get_supported_cpuid());
}
static void run_vm_bsp(uint32_t bsp_vcpu)
{
struct kvm_vm *vm;
bool is_bsp_vcpu1 = bsp_vcpu == VCPU_ID1;
vm = create_vm();
if (is_bsp_vcpu1)
vm_ioctl(vm, KVM_SET_BOOT_CPU_ID, (void *) VCPU_ID1);
add_x86_vcpu(vm, VCPU_ID0, !is_bsp_vcpu1);
add_x86_vcpu(vm, VCPU_ID1, is_bsp_vcpu1);
run_vcpu(vm, VCPU_ID0);
run_vcpu(vm, VCPU_ID1);
kvm_vm_free(vm);
}
static void check_set_bsp_busy(void)
{
struct kvm_vm *vm;
int res;
vm = create_vm();
add_x86_vcpu(vm, VCPU_ID0, true);
add_x86_vcpu(vm, VCPU_ID1, false);
res = _vm_ioctl(vm, KVM_SET_BOOT_CPU_ID, (void *) VCPU_ID1);
TEST_ASSERT(res == -1 && errno == EBUSY, "KVM_SET_BOOT_CPU_ID set after adding vcpu");
run_vcpu(vm, VCPU_ID0);
run_vcpu(vm, VCPU_ID1);
res = _vm_ioctl(vm, KVM_SET_BOOT_CPU_ID, (void *) VCPU_ID1);
TEST_ASSERT(res == -1 && errno == EBUSY, "KVM_SET_BOOT_CPU_ID set to a terminated vcpu");
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
if (!kvm_check_cap(KVM_CAP_SET_BOOT_CPU_ID)) {
print_skip("set_boot_cpu_id not available");
return 0;
}
run_vm_bsp(VCPU_ID0);
run_vm_bsp(VCPU_ID1);
run_vm_bsp(VCPU_ID0);
check_set_bsp_busy();
}