diff options
-rw-r--r-- | arch/x86/kvm/mmu.c | 34 | ||||
-rw-r--r-- | arch/x86/kvm/paging_tmpl.h | 7 | ||||
-rw-r--r-- | include/linux/kvm_host.h | 1 | ||||
-rw-r--r-- | include/linux/mm.h | 8 | ||||
-rw-r--r-- | mm/memory-failure.c | 30 | ||||
-rw-r--r-- | virt/kvm/kvm_main.c | 30 |
6 files changed, 95 insertions, 15 deletions
diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c index b1ed0a1a5913..b666d8d106a9 100644 --- a/arch/x86/kvm/mmu.c +++ b/arch/x86/kvm/mmu.c | |||
@@ -32,6 +32,7 @@ | |||
32 | #include <linux/compiler.h> | 32 | #include <linux/compiler.h> |
33 | #include <linux/srcu.h> | 33 | #include <linux/srcu.h> |
34 | #include <linux/slab.h> | 34 | #include <linux/slab.h> |
35 | #include <linux/uaccess.h> | ||
35 | 36 | ||
36 | #include <asm/page.h> | 37 | #include <asm/page.h> |
37 | #include <asm/cmpxchg.h> | 38 | #include <asm/cmpxchg.h> |
@@ -1960,6 +1961,27 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t v, int write, | |||
1960 | return pt_write; | 1961 | return pt_write; |
1961 | } | 1962 | } |
1962 | 1963 | ||
1964 | static void kvm_send_hwpoison_signal(struct kvm *kvm, gfn_t gfn) | ||
1965 | { | ||
1966 | char buf[1]; | ||
1967 | void __user *hva; | ||
1968 | int r; | ||
1969 | |||
1970 | /* Touch the page, so send SIGBUS */ | ||
1971 | hva = (void __user *)gfn_to_hva(kvm, gfn); | ||
1972 | r = copy_from_user(buf, hva, 1); | ||
1973 | } | ||
1974 | |||
1975 | static int kvm_handle_bad_page(struct kvm *kvm, gfn_t gfn, pfn_t pfn) | ||
1976 | { | ||
1977 | kvm_release_pfn_clean(pfn); | ||
1978 | if (is_hwpoison_pfn(pfn)) { | ||
1979 | kvm_send_hwpoison_signal(kvm, gfn); | ||
1980 | return 0; | ||
1981 | } | ||
1982 | return 1; | ||
1983 | } | ||
1984 | |||
1963 | static int nonpaging_map(struct kvm_vcpu *vcpu, gva_t v, int write, gfn_t gfn) | 1985 | static int nonpaging_map(struct kvm_vcpu *vcpu, gva_t v, int write, gfn_t gfn) |
1964 | { | 1986 | { |
1965 | int r; | 1987 | int r; |
@@ -1983,10 +2005,8 @@ static int nonpaging_map(struct kvm_vcpu *vcpu, gva_t v, int write, gfn_t gfn) | |||
1983 | pfn = gfn_to_pfn(vcpu->kvm, gfn); | 2005 | pfn = gfn_to_pfn(vcpu->kvm, gfn); |
1984 | 2006 | ||
1985 | /* mmio */ | 2007 | /* mmio */ |
1986 | if (is_error_pfn(pfn)) { | 2008 | if (is_error_pfn(pfn)) |
1987 | kvm_release_pfn_clean(pfn); | 2009 | return kvm_handle_bad_page(vcpu->kvm, gfn, pfn); |
1988 | return 1; | ||
1989 | } | ||
1990 | 2010 | ||
1991 | spin_lock(&vcpu->kvm->mmu_lock); | 2011 | spin_lock(&vcpu->kvm->mmu_lock); |
1992 | if (mmu_notifier_retry(vcpu, mmu_seq)) | 2012 | if (mmu_notifier_retry(vcpu, mmu_seq)) |
@@ -2198,10 +2218,8 @@ static int tdp_page_fault(struct kvm_vcpu *vcpu, gva_t gpa, | |||
2198 | mmu_seq = vcpu->kvm->mmu_notifier_seq; | 2218 | mmu_seq = vcpu->kvm->mmu_notifier_seq; |
2199 | smp_rmb(); | 2219 | smp_rmb(); |
2200 | pfn = gfn_to_pfn(vcpu->kvm, gfn); | 2220 | pfn = gfn_to_pfn(vcpu->kvm, gfn); |
2201 | if (is_error_pfn(pfn)) { | 2221 | if (is_error_pfn(pfn)) |
2202 | kvm_release_pfn_clean(pfn); | 2222 | return kvm_handle_bad_page(vcpu->kvm, gfn, pfn); |
2203 | return 1; | ||
2204 | } | ||
2205 | spin_lock(&vcpu->kvm->mmu_lock); | 2223 | spin_lock(&vcpu->kvm->mmu_lock); |
2206 | if (mmu_notifier_retry(vcpu, mmu_seq)) | 2224 | if (mmu_notifier_retry(vcpu, mmu_seq)) |
2207 | goto out_unlock; | 2225 | goto out_unlock; |
diff --git a/arch/x86/kvm/paging_tmpl.h b/arch/x86/kvm/paging_tmpl.h index 2331bdc2b549..c7f27779c998 100644 --- a/arch/x86/kvm/paging_tmpl.h +++ b/arch/x86/kvm/paging_tmpl.h | |||
@@ -431,11 +431,8 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, | |||
431 | pfn = gfn_to_pfn(vcpu->kvm, walker.gfn); | 431 | pfn = gfn_to_pfn(vcpu->kvm, walker.gfn); |
432 | 432 | ||
433 | /* mmio */ | 433 | /* mmio */ |
434 | if (is_error_pfn(pfn)) { | 434 | if (is_error_pfn(pfn)) |
435 | pgprintk("gfn %lx is mmio\n", walker.gfn); | 435 | return kvm_handle_bad_page(vcpu->kvm, walker.gfn, pfn); |
436 | kvm_release_pfn_clean(pfn); | ||
437 | return 1; | ||
438 | } | ||
439 | 436 | ||
440 | spin_lock(&vcpu->kvm->mmu_lock); | 437 | spin_lock(&vcpu->kvm->mmu_lock); |
441 | if (mmu_notifier_retry(vcpu, mmu_seq)) | 438 | if (mmu_notifier_retry(vcpu, mmu_seq)) |
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 7cb116afa1cd..a0e019769f5d 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h | |||
@@ -266,6 +266,7 @@ extern pfn_t bad_pfn; | |||
266 | 266 | ||
267 | int is_error_page(struct page *page); | 267 | int is_error_page(struct page *page); |
268 | int is_error_pfn(pfn_t pfn); | 268 | int is_error_pfn(pfn_t pfn); |
269 | int is_hwpoison_pfn(pfn_t pfn); | ||
269 | int kvm_is_error_hva(unsigned long addr); | 270 | int kvm_is_error_hva(unsigned long addr); |
270 | int kvm_set_memory_region(struct kvm *kvm, | 271 | int kvm_set_memory_region(struct kvm *kvm, |
271 | struct kvm_userspace_memory_region *mem, | 272 | struct kvm_userspace_memory_region *mem, |
diff --git a/include/linux/mm.h b/include/linux/mm.h index a2b48041b910..7a9ab7db1975 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h | |||
@@ -1465,6 +1465,14 @@ extern int sysctl_memory_failure_recovery; | |||
1465 | extern void shake_page(struct page *p, int access); | 1465 | extern void shake_page(struct page *p, int access); |
1466 | extern atomic_long_t mce_bad_pages; | 1466 | extern atomic_long_t mce_bad_pages; |
1467 | extern int soft_offline_page(struct page *page, int flags); | 1467 | extern int soft_offline_page(struct page *page, int flags); |
1468 | #ifdef CONFIG_MEMORY_FAILURE | ||
1469 | int is_hwpoison_address(unsigned long addr); | ||
1470 | #else | ||
1471 | static inline int is_hwpoison_address(unsigned long addr) | ||
1472 | { | ||
1473 | return 0; | ||
1474 | } | ||
1475 | #endif | ||
1468 | 1476 | ||
1469 | extern void dump_page(struct page *page); | 1477 | extern void dump_page(struct page *page); |
1470 | 1478 | ||
diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 620b0b461593..378b0f61fd3c 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c | |||
@@ -45,6 +45,7 @@ | |||
45 | #include <linux/page-isolation.h> | 45 | #include <linux/page-isolation.h> |
46 | #include <linux/suspend.h> | 46 | #include <linux/suspend.h> |
47 | #include <linux/slab.h> | 47 | #include <linux/slab.h> |
48 | #include <linux/swapops.h> | ||
48 | #include "internal.h" | 49 | #include "internal.h" |
49 | 50 | ||
50 | int sysctl_memory_failure_early_kill __read_mostly = 0; | 51 | int sysctl_memory_failure_early_kill __read_mostly = 0; |
@@ -1296,3 +1297,32 @@ done: | |||
1296 | /* keep elevated page count for bad page */ | 1297 | /* keep elevated page count for bad page */ |
1297 | return ret; | 1298 | return ret; |
1298 | } | 1299 | } |
1300 | |||
1301 | int is_hwpoison_address(unsigned long addr) | ||
1302 | { | ||
1303 | pgd_t *pgdp; | ||
1304 | pud_t pud, *pudp; | ||
1305 | pmd_t pmd, *pmdp; | ||
1306 | pte_t pte, *ptep; | ||
1307 | swp_entry_t entry; | ||
1308 | |||
1309 | pgdp = pgd_offset(current->mm, addr); | ||
1310 | if (!pgd_present(*pgdp)) | ||
1311 | return 0; | ||
1312 | pudp = pud_offset(pgdp, addr); | ||
1313 | pud = *pudp; | ||
1314 | if (!pud_present(pud) || pud_large(pud)) | ||
1315 | return 0; | ||
1316 | pmdp = pmd_offset(pudp, addr); | ||
1317 | pmd = *pmdp; | ||
1318 | if (!pmd_present(pmd) || pmd_large(pmd)) | ||
1319 | return 0; | ||
1320 | ptep = pte_offset_map(pmdp, addr); | ||
1321 | pte = *ptep; | ||
1322 | pte_unmap(ptep); | ||
1323 | if (!is_swap_pte(pte)) | ||
1324 | return 0; | ||
1325 | entry = pte_to_swp_entry(pte); | ||
1326 | return is_hwpoison_entry(entry); | ||
1327 | } | ||
1328 | EXPORT_SYMBOL_GPL(is_hwpoison_address); | ||
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index f032806a212f..187aa8d984a7 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c | |||
@@ -92,6 +92,9 @@ static bool kvm_rebooting; | |||
92 | 92 | ||
93 | static bool largepages_enabled = true; | 93 | static bool largepages_enabled = true; |
94 | 94 | ||
95 | struct page *hwpoison_page; | ||
96 | pfn_t hwpoison_pfn; | ||
97 | |||
95 | inline int kvm_is_mmio_pfn(pfn_t pfn) | 98 | inline int kvm_is_mmio_pfn(pfn_t pfn) |
96 | { | 99 | { |
97 | if (pfn_valid(pfn)) { | 100 | if (pfn_valid(pfn)) { |
@@ -810,16 +813,22 @@ EXPORT_SYMBOL_GPL(kvm_disable_largepages); | |||
810 | 813 | ||
811 | int is_error_page(struct page *page) | 814 | int is_error_page(struct page *page) |
812 | { | 815 | { |
813 | return page == bad_page; | 816 | return page == bad_page || page == hwpoison_page; |
814 | } | 817 | } |
815 | EXPORT_SYMBOL_GPL(is_error_page); | 818 | EXPORT_SYMBOL_GPL(is_error_page); |
816 | 819 | ||
817 | int is_error_pfn(pfn_t pfn) | 820 | int is_error_pfn(pfn_t pfn) |
818 | { | 821 | { |
819 | return pfn == bad_pfn; | 822 | return pfn == bad_pfn || pfn == hwpoison_pfn; |
820 | } | 823 | } |
821 | EXPORT_SYMBOL_GPL(is_error_pfn); | 824 | EXPORT_SYMBOL_GPL(is_error_pfn); |
822 | 825 | ||
826 | int is_hwpoison_pfn(pfn_t pfn) | ||
827 | { | ||
828 | return pfn == hwpoison_pfn; | ||
829 | } | ||
830 | EXPORT_SYMBOL_GPL(is_hwpoison_pfn); | ||
831 | |||
823 | static inline unsigned long bad_hva(void) | 832 | static inline unsigned long bad_hva(void) |
824 | { | 833 | { |
825 | return PAGE_OFFSET; | 834 | return PAGE_OFFSET; |
@@ -945,6 +954,11 @@ static pfn_t hva_to_pfn(struct kvm *kvm, unsigned long addr) | |||
945 | if (unlikely(npages != 1)) { | 954 | if (unlikely(npages != 1)) { |
946 | struct vm_area_struct *vma; | 955 | struct vm_area_struct *vma; |
947 | 956 | ||
957 | if (is_hwpoison_address(addr)) { | ||
958 | get_page(hwpoison_page); | ||
959 | return page_to_pfn(hwpoison_page); | ||
960 | } | ||
961 | |||
948 | down_read(¤t->mm->mmap_sem); | 962 | down_read(¤t->mm->mmap_sem); |
949 | vma = find_vma(current->mm, addr); | 963 | vma = find_vma(current->mm, addr); |
950 | 964 | ||
@@ -2197,6 +2211,15 @@ int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align, | |||
2197 | 2211 | ||
2198 | bad_pfn = page_to_pfn(bad_page); | 2212 | bad_pfn = page_to_pfn(bad_page); |
2199 | 2213 | ||
2214 | hwpoison_page = alloc_page(GFP_KERNEL | __GFP_ZERO); | ||
2215 | |||
2216 | if (hwpoison_page == NULL) { | ||
2217 | r = -ENOMEM; | ||
2218 | goto out_free_0; | ||
2219 | } | ||
2220 | |||
2221 | hwpoison_pfn = page_to_pfn(hwpoison_page); | ||
2222 | |||
2200 | if (!zalloc_cpumask_var(&cpus_hardware_enabled, GFP_KERNEL)) { | 2223 | if (!zalloc_cpumask_var(&cpus_hardware_enabled, GFP_KERNEL)) { |
2201 | r = -ENOMEM; | 2224 | r = -ENOMEM; |
2202 | goto out_free_0; | 2225 | goto out_free_0; |
@@ -2269,6 +2292,8 @@ out_free_1: | |||
2269 | out_free_0a: | 2292 | out_free_0a: |
2270 | free_cpumask_var(cpus_hardware_enabled); | 2293 | free_cpumask_var(cpus_hardware_enabled); |
2271 | out_free_0: | 2294 | out_free_0: |
2295 | if (hwpoison_page) | ||
2296 | __free_page(hwpoison_page); | ||
2272 | __free_page(bad_page); | 2297 | __free_page(bad_page); |
2273 | out: | 2298 | out: |
2274 | kvm_arch_exit(); | 2299 | kvm_arch_exit(); |
@@ -2290,6 +2315,7 @@ void kvm_exit(void) | |||
2290 | kvm_arch_hardware_unsetup(); | 2315 | kvm_arch_hardware_unsetup(); |
2291 | kvm_arch_exit(); | 2316 | kvm_arch_exit(); |
2292 | free_cpumask_var(cpus_hardware_enabled); | 2317 | free_cpumask_var(cpus_hardware_enabled); |
2318 | __free_page(hwpoison_page); | ||
2293 | __free_page(bad_page); | 2319 | __free_page(bad_page); |
2294 | } | 2320 | } |
2295 | EXPORT_SYMBOL_GPL(kvm_exit); | 2321 | EXPORT_SYMBOL_GPL(kvm_exit); |