Skip to content

Commit e1ac4f8

Browse files
committed
KVM: s390: Fix to clear PTE when discarding a swapped page
JIRA: https://issues.redhat.com/browse/RHEL-113440 commit 5deafa2 Author: Gautam Gala <ggala@linux.ibm.com> Date: Wed Sep 24 13:26:44 2025 +0200 KVM: s390: Fix to clear PTE when discarding a swapped page KVM run fails when guests with 'cmm' cpu feature and host are under memory pressure and use swap heavily. This is because npages becomes ENOMEN (out of memory) in hva_to_pfn_slow() which inturn propagates as EFAULT to qemu. Clearing the page table entry when discarding an address that maps to a swap entry resolves the issue. Fixes: 2001979 ("KVM: s390: Refactor and split some gmap helpers") Cc: stable@vger.kernel.org Suggested-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Signed-off-by: Gautam Gala <ggala@linux.ibm.com> Reviewed-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Conflicts: arch/s390/mm/pgtable.c (contextual conflict in #include statements due to other missing patch) Signed-off-by: Thomas Huth <thuth@redhat.com>
1 parent 9823320 commit e1ac4f8

File tree

3 files changed

+34
-23
lines changed

3 files changed

+34
-23
lines changed

arch/s390/include/asm/pgtable.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,4 +2017,26 @@ static inline unsigned long gmap_pgste_get_pgt_addr(unsigned long *pgt)
20172017
return res;
20182018
}
20192019

2020+
static inline pgste_t pgste_get_lock(pte_t *ptep)
2021+
{
2022+
unsigned long value = 0;
2023+
#ifdef CONFIG_PGSTE
2024+
unsigned long *ptr = (unsigned long *)(ptep + PTRS_PER_PTE);
2025+
2026+
do {
2027+
value = __atomic64_or_barrier(PGSTE_PCL_BIT, ptr);
2028+
} while (value & PGSTE_PCL_BIT);
2029+
value |= PGSTE_PCL_BIT;
2030+
#endif
2031+
return __pgste(value);
2032+
}
2033+
2034+
static inline void pgste_set_unlock(pte_t *ptep, pgste_t pgste)
2035+
{
2036+
#ifdef CONFIG_PGSTE
2037+
barrier();
2038+
WRITE_ONCE(*(unsigned long *)(ptep + PTRS_PER_PTE), pgste_val(pgste) & ~PGSTE_PCL_BIT);
2039+
#endif
2040+
}
2041+
20202042
#endif /* _S390_PAGE_H */

arch/s390/mm/gmap_helpers.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <linux/pagewalk.h>
1414
#include <linux/ksm.h>
1515
#include <asm/gmap_helpers.h>
16+
#include <asm/pgtable.h>
1617

1718
/**
1819
* ptep_zap_swap_entry() - discard a swap entry.
@@ -45,6 +46,7 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
4546
{
4647
struct vm_area_struct *vma;
4748
spinlock_t *ptl;
49+
pgste_t pgste;
4850
pte_t *ptep;
4951

5052
mmap_assert_locked(mm);
@@ -58,8 +60,16 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
5860
ptep = get_locked_pte(mm, vmaddr, &ptl);
5961
if (unlikely(!ptep))
6062
return;
61-
if (pte_swap(*ptep))
63+
if (pte_swap(*ptep)) {
64+
preempt_disable();
65+
pgste = pgste_get_lock(ptep);
66+
6267
ptep_zap_swap_entry(mm, pte_to_swp_entry(*ptep));
68+
pte_clear(mm, vmaddr, ptep);
69+
70+
pgste_set_unlock(ptep, pgste);
71+
preempt_enable();
72+
}
6373
pte_unmap_unlock(ptep, ptl);
6474
}
6575
EXPORT_SYMBOL_GPL(gmap_helper_zap_one_page);

arch/s390/mm/pgtable.c

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <asm/tlbflush.h>
2323
#include <asm/mmu_context.h>
2424
#include <asm/page-states.h>
25+
#include <asm/pgtable.h>
2526

2627
pgprot_t pgprot_writecombine(pgprot_t prot)
2728
{
@@ -122,28 +123,6 @@ static inline pte_t ptep_flush_lazy(struct mm_struct *mm,
122123
return old;
123124
}
124125

125-
static inline pgste_t pgste_get_lock(pte_t *ptep)
126-
{
127-
unsigned long value = 0;
128-
#ifdef CONFIG_PGSTE
129-
unsigned long *ptr = (unsigned long *)(ptep + PTRS_PER_PTE);
130-
131-
do {
132-
value = __atomic64_or_barrier(PGSTE_PCL_BIT, ptr);
133-
} while (value & PGSTE_PCL_BIT);
134-
value |= PGSTE_PCL_BIT;
135-
#endif
136-
return __pgste(value);
137-
}
138-
139-
static inline void pgste_set_unlock(pte_t *ptep, pgste_t pgste)
140-
{
141-
#ifdef CONFIG_PGSTE
142-
barrier();
143-
WRITE_ONCE(*(unsigned long *)(ptep + PTRS_PER_PTE), pgste_val(pgste) & ~PGSTE_PCL_BIT);
144-
#endif
145-
}
146-
147126
static inline pgste_t pgste_get(pte_t *ptep)
148127
{
149128
unsigned long pgste = 0;

0 commit comments

Comments
 (0)