mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 21:38:32 +08:00
KVM: PPC: Book3S HV: Fix bug in dirty page tracking
This fixes a bug in the tracking of pages that get modified by the guest. If the guest creates a large-page HPTE, writes to memory somewhere within the large page, and then removes the HPTE, we only record the modified state for the first normal page within the large page, when in fact the guest might have modified some other normal page within the large page. To fix this we use some unused bits in the rmap entry to record the order (log base 2) of the size of the page that was modified, when removing an HPTE. Then in kvm_test_clear_dirty_npages() we use that order to return the correct number of modified pages. The same thing could in principle happen when removing a HPTE at the host's request, i.e. when paging out a page, except that we never page out large pages, and the guest can only create large-page HPTEs if the guest RAM is backed by large pages. However, we also fix this case for the sake of future-proofing. The reference bit is also subject to the same loss of information. We don't make the same fix here for the reference bit because there isn't an interface for userspace to find out which pages the guest has referenced, whereas there is one for userspace to find out which pages the guest has modified. Because of this loss of information, the kvm_age_hva_hv() and kvm_test_age_hva_hv() functions might incorrectly say that a page has not been referenced when it has, but that doesn't matter greatly because we never page or swap out large pages. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Alexander Graf <agraf@suse.de>
This commit is contained in:
parent
1e5bf454f5
commit
08fe1e7bd2
@ -158,6 +158,7 @@ extern pfn_t kvmppc_gpa_to_pfn(struct kvm_vcpu *vcpu, gpa_t gpa, bool writing,
|
|||||||
bool *writable);
|
bool *writable);
|
||||||
extern void kvmppc_add_revmap_chain(struct kvm *kvm, struct revmap_entry *rev,
|
extern void kvmppc_add_revmap_chain(struct kvm *kvm, struct revmap_entry *rev,
|
||||||
unsigned long *rmap, long pte_index, int realmode);
|
unsigned long *rmap, long pte_index, int realmode);
|
||||||
|
extern void kvmppc_update_rmap_change(unsigned long *rmap, unsigned long psize);
|
||||||
extern void kvmppc_invalidate_hpte(struct kvm *kvm, __be64 *hptep,
|
extern void kvmppc_invalidate_hpte(struct kvm *kvm, __be64 *hptep,
|
||||||
unsigned long pte_index);
|
unsigned long pte_index);
|
||||||
void kvmppc_clear_ref_hpte(struct kvm *kvm, __be64 *hptep,
|
void kvmppc_clear_ref_hpte(struct kvm *kvm, __be64 *hptep,
|
||||||
|
@ -205,8 +205,10 @@ struct revmap_entry {
|
|||||||
*/
|
*/
|
||||||
#define KVMPPC_RMAP_LOCK_BIT 63
|
#define KVMPPC_RMAP_LOCK_BIT 63
|
||||||
#define KVMPPC_RMAP_RC_SHIFT 32
|
#define KVMPPC_RMAP_RC_SHIFT 32
|
||||||
|
#define KVMPPC_RMAP_CHG_SHIFT 48
|
||||||
#define KVMPPC_RMAP_REFERENCED (HPTE_R_R << KVMPPC_RMAP_RC_SHIFT)
|
#define KVMPPC_RMAP_REFERENCED (HPTE_R_R << KVMPPC_RMAP_RC_SHIFT)
|
||||||
#define KVMPPC_RMAP_CHANGED (HPTE_R_C << KVMPPC_RMAP_RC_SHIFT)
|
#define KVMPPC_RMAP_CHANGED (HPTE_R_C << KVMPPC_RMAP_RC_SHIFT)
|
||||||
|
#define KVMPPC_RMAP_CHG_ORDER (0x3ful << KVMPPC_RMAP_CHG_SHIFT)
|
||||||
#define KVMPPC_RMAP_PRESENT 0x100000000ul
|
#define KVMPPC_RMAP_PRESENT 0x100000000ul
|
||||||
#define KVMPPC_RMAP_INDEX 0xfffffffful
|
#define KVMPPC_RMAP_INDEX 0xfffffffful
|
||||||
|
|
||||||
|
@ -761,6 +761,8 @@ static int kvm_unmap_rmapp(struct kvm *kvm, unsigned long *rmapp,
|
|||||||
/* Harvest R and C */
|
/* Harvest R and C */
|
||||||
rcbits = be64_to_cpu(hptep[1]) & (HPTE_R_R | HPTE_R_C);
|
rcbits = be64_to_cpu(hptep[1]) & (HPTE_R_R | HPTE_R_C);
|
||||||
*rmapp |= rcbits << KVMPPC_RMAP_RC_SHIFT;
|
*rmapp |= rcbits << KVMPPC_RMAP_RC_SHIFT;
|
||||||
|
if (rcbits & HPTE_R_C)
|
||||||
|
kvmppc_update_rmap_change(rmapp, psize);
|
||||||
if (rcbits & ~rev[i].guest_rpte) {
|
if (rcbits & ~rev[i].guest_rpte) {
|
||||||
rev[i].guest_rpte = ptel | rcbits;
|
rev[i].guest_rpte = ptel | rcbits;
|
||||||
note_hpte_modification(kvm, &rev[i]);
|
note_hpte_modification(kvm, &rev[i]);
|
||||||
@ -927,8 +929,12 @@ static int kvm_test_clear_dirty_npages(struct kvm *kvm, unsigned long *rmapp)
|
|||||||
retry:
|
retry:
|
||||||
lock_rmap(rmapp);
|
lock_rmap(rmapp);
|
||||||
if (*rmapp & KVMPPC_RMAP_CHANGED) {
|
if (*rmapp & KVMPPC_RMAP_CHANGED) {
|
||||||
*rmapp &= ~KVMPPC_RMAP_CHANGED;
|
long change_order = (*rmapp & KVMPPC_RMAP_CHG_ORDER)
|
||||||
|
>> KVMPPC_RMAP_CHG_SHIFT;
|
||||||
|
*rmapp &= ~(KVMPPC_RMAP_CHANGED | KVMPPC_RMAP_CHG_ORDER);
|
||||||
npages_dirty = 1;
|
npages_dirty = 1;
|
||||||
|
if (change_order > PAGE_SHIFT)
|
||||||
|
npages_dirty = 1ul << (change_order - PAGE_SHIFT);
|
||||||
}
|
}
|
||||||
if (!(*rmapp & KVMPPC_RMAP_PRESENT)) {
|
if (!(*rmapp & KVMPPC_RMAP_PRESENT)) {
|
||||||
unlock_rmap(rmapp);
|
unlock_rmap(rmapp);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <linux/kvm_host.h>
|
#include <linux/kvm_host.h>
|
||||||
#include <linux/hugetlb.h>
|
#include <linux/hugetlb.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/log2.h>
|
||||||
|
|
||||||
#include <asm/tlbflush.h>
|
#include <asm/tlbflush.h>
|
||||||
#include <asm/kvm_ppc.h>
|
#include <asm/kvm_ppc.h>
|
||||||
@ -97,6 +98,20 @@ void kvmppc_add_revmap_chain(struct kvm *kvm, struct revmap_entry *rev,
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(kvmppc_add_revmap_chain);
|
EXPORT_SYMBOL_GPL(kvmppc_add_revmap_chain);
|
||||||
|
|
||||||
|
/* Update the changed page order field of an rmap entry */
|
||||||
|
void kvmppc_update_rmap_change(unsigned long *rmap, unsigned long psize)
|
||||||
|
{
|
||||||
|
unsigned long order;
|
||||||
|
|
||||||
|
if (!psize)
|
||||||
|
return;
|
||||||
|
order = ilog2(psize);
|
||||||
|
order <<= KVMPPC_RMAP_CHG_SHIFT;
|
||||||
|
if (order > (*rmap & KVMPPC_RMAP_CHG_ORDER))
|
||||||
|
*rmap = (*rmap & ~KVMPPC_RMAP_CHG_ORDER) | order;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(kvmppc_update_rmap_change);
|
||||||
|
|
||||||
/* Remove this HPTE from the chain for a real page */
|
/* Remove this HPTE from the chain for a real page */
|
||||||
static void remove_revmap_chain(struct kvm *kvm, long pte_index,
|
static void remove_revmap_chain(struct kvm *kvm, long pte_index,
|
||||||
struct revmap_entry *rev,
|
struct revmap_entry *rev,
|
||||||
@ -131,6 +146,8 @@ static void remove_revmap_chain(struct kvm *kvm, long pte_index,
|
|||||||
*rmap = (*rmap & ~KVMPPC_RMAP_INDEX) | head;
|
*rmap = (*rmap & ~KVMPPC_RMAP_INDEX) | head;
|
||||||
}
|
}
|
||||||
*rmap |= rcbits << KVMPPC_RMAP_RC_SHIFT;
|
*rmap |= rcbits << KVMPPC_RMAP_RC_SHIFT;
|
||||||
|
if (rcbits & HPTE_R_C)
|
||||||
|
kvmppc_update_rmap_change(rmap, hpte_page_size(hpte_v, hpte_r));
|
||||||
unlock_rmap(rmap);
|
unlock_rmap(rmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user