s390/mm: clean up pte/pmd encoding
authorGerald Schaefer <gerald.schaefer@de.ibm.com>
Mon, 18 Jul 2016 12:35:13 +0000 (14:35 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Sun, 31 Jul 2016 09:27:57 +0000 (05:27 -0400)
The hugetlbfs pte<->pmd conversion functions currently assume that the pmd
bit layout is consistent with the pte layout, which is not really true.

The SW read and write bits are encoded as the sequence "wr" in a pte, but
in a pmd it is "rw". The hugetlbfs conversion assumes that the sequence
is identical in both cases, which results in swapped read and write bits
in the pmd. In practice this is not a problem, because those pmd bits are
only relevant for THP pmds and not for hugetlbfs pmds. The hugetlbfs code
works on (fake) ptes, and the converted pte bits are correct.

There is another variation in pte/pmd encoding which affects dirty
prot-none ptes/pmds. In this case, a pmd has both its HW read-only and
invalid bit set, while it is only the invalid bit for a pte. This also has
no effect in practice, but it should better be consistent.

This patch fixes both inconsistencies by changing the SW read/write bit
layout for pmds as well as the PAGE_NONE encoding for ptes. It also makes
the hugetlbfs conversion functions more robust by introducing a
move_set_bit() macro that uses the pte/pmd bit #defines instead of
constant shifts.

Signed-off-by: Gerald Schaefer <gerald.schaefer@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
arch/s390/include/asm/pgtable.h
arch/s390/mm/hugetlbpage.c

index ea1533e07271e14e23db89639f2679df81941fe7..48d383af078f90c61bc5dcfedacaf6d15319f663 100644 (file)
@@ -242,8 +242,8 @@ static inline int is_module_addr(void *addr)
  * swap                                .11..ttttt.0
  * prot-none, clean, old       .11.xx0000.1
  * prot-none, clean, young     .11.xx0001.1
- * prot-none, dirty, old       .10.xx0010.1
- * prot-none, dirty, young     .10.xx0011.1
+ * prot-none, dirty, old       .11.xx0010.1
+ * prot-none, dirty, young     .11.xx0011.1
  * read-only, clean, old       .11.xx0100.1
  * read-only, clean, young     .01.xx0101.1
  * read-only, dirty, old       .11.xx0110.1
@@ -323,8 +323,8 @@ static inline int is_module_addr(void *addr)
 #define _SEGMENT_ENTRY_DIRTY   0x2000  /* SW segment dirty bit */
 #define _SEGMENT_ENTRY_YOUNG   0x1000  /* SW segment young bit */
 #define _SEGMENT_ENTRY_LARGE   0x0400  /* STE-format control, large page */
-#define _SEGMENT_ENTRY_READ    0x0002  /* SW segment read bit */
-#define _SEGMENT_ENTRY_WRITE   0x0001  /* SW segment write bit */
+#define _SEGMENT_ENTRY_WRITE   0x0002  /* SW segment write bit */
+#define _SEGMENT_ENTRY_READ    0x0001  /* SW segment read bit */
 
 #ifdef CONFIG_MEM_SOFT_DIRTY
 #define _SEGMENT_ENTRY_SOFT_DIRTY 0x4000 /* SW segment soft dirty bit */
@@ -335,15 +335,15 @@ static inline int is_module_addr(void *addr)
 /*
  * Segment table and region3 table entry encoding
  * (R = read-only, I = invalid, y = young bit):
- *                             dy..R...I...rw
+ *                             dy..R...I...wr
  * prot-none, clean, old       00..1...1...00
  * prot-none, clean, young     01..1...1...00
  * prot-none, dirty, old       10..1...1...00
  * prot-none, dirty, young     11..1...1...00
- * read-only, clean, old       00..1...1...10
- * read-only, clean, young     01..1...0...10
- * read-only, dirty, old       10..1...1...10
- * read-only, dirty, young     11..1...0...10
+ * read-only, clean, old       00..1...1...01
+ * read-only, clean, young     01..1...0...01
+ * read-only, dirty, old       10..1...1...01
+ * read-only, dirty, young     11..1...0...01
  * read-write, clean, old      00..1...1...11
  * read-write, clean, young    01..1...0...11
  * read-write, dirty, old      10..0...1...11
@@ -382,7 +382,7 @@ static inline int is_module_addr(void *addr)
 /*
  * Page protection definitions.
  */
-#define PAGE_NONE      __pgprot(_PAGE_PRESENT | _PAGE_INVALID)
+#define PAGE_NONE      __pgprot(_PAGE_PRESENT | _PAGE_INVALID | _PAGE_PROTECT)
 #define PAGE_READ      __pgprot(_PAGE_PRESENT | _PAGE_READ | \
                                 _PAGE_INVALID | _PAGE_PROTECT)
 #define PAGE_WRITE     __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
index e19d853883beaad9f79daaf1199b55d2d48f157a..cd404aa3931c101c963f9940e80669173453022c 100644 (file)
 #include <linux/mm.h>
 #include <linux/hugetlb.h>
 
+/*
+ * If the bit selected by single-bit bitmask "a" is set within "x", move
+ * it to the position indicated by single-bit bitmask "b".
+ */
+#define move_set_bit(x, a, b)  (((x) & (a)) >> ilog2(a) << ilog2(b))
+
 static inline unsigned long __pte_to_rste(pte_t pte)
 {
        unsigned long rste;
@@ -37,13 +43,22 @@ static inline unsigned long __pte_to_rste(pte_t pte)
         */
        if (pte_present(pte)) {
                rste = pte_val(pte) & PAGE_MASK;
-               rste |= (pte_val(pte) & _PAGE_READ) >> 4;
-               rste |= (pte_val(pte) & _PAGE_WRITE) >> 4;
-               rste |= (pte_val(pte) & _PAGE_INVALID) >> 5;
-               rste |= (pte_val(pte) & _PAGE_PROTECT);
-               rste |= (pte_val(pte) & _PAGE_DIRTY) << 10;
-               rste |= (pte_val(pte) & _PAGE_YOUNG) << 10;
-               rste |= (pte_val(pte) & _PAGE_SOFT_DIRTY) << 13;
+               rste |= move_set_bit(pte_val(pte), _PAGE_READ,
+                                    _SEGMENT_ENTRY_READ);
+               rste |= move_set_bit(pte_val(pte), _PAGE_WRITE,
+                                    _SEGMENT_ENTRY_WRITE);
+               rste |= move_set_bit(pte_val(pte), _PAGE_INVALID,
+                                    _SEGMENT_ENTRY_INVALID);
+               rste |= move_set_bit(pte_val(pte), _PAGE_PROTECT,
+                                    _SEGMENT_ENTRY_PROTECT);
+               rste |= move_set_bit(pte_val(pte), _PAGE_DIRTY,
+                                    _SEGMENT_ENTRY_DIRTY);
+               rste |= move_set_bit(pte_val(pte), _PAGE_YOUNG,
+                                    _SEGMENT_ENTRY_YOUNG);
+#ifdef CONFIG_MEM_SOFT_DIRTY
+               rste |= move_set_bit(pte_val(pte), _PAGE_SOFT_DIRTY,
+                                    _SEGMENT_ENTRY_SOFT_DIRTY);
+#endif
        } else
                rste = _SEGMENT_ENTRY_INVALID;
        return rste;
@@ -82,13 +97,22 @@ static inline pte_t __rste_to_pte(unsigned long rste)
        if (present) {
                pte_val(pte) = rste & _SEGMENT_ENTRY_ORIGIN_LARGE;
                pte_val(pte) |= _PAGE_LARGE | _PAGE_PRESENT;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_READ) << 4;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_WRITE) << 4;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_INVALID) << 5;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_PROTECT);
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_DIRTY) >> 10;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_YOUNG) >> 10;
-               pte_val(pte) |= (rste & _SEGMENT_ENTRY_SOFT_DIRTY) >> 13;
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_READ,
+                                            _PAGE_READ);
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_WRITE,
+                                            _PAGE_WRITE);
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_INVALID,
+                                            _PAGE_INVALID);
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_PROTECT,
+                                            _PAGE_PROTECT);
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_DIRTY,
+                                            _PAGE_DIRTY);
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_YOUNG,
+                                            _PAGE_YOUNG);
+#ifdef CONFIG_MEM_SOFT_DIRTY
+               pte_val(pte) |= move_set_bit(rste, _SEGMENT_ENTRY_SOFT_DIRTY,
+                                            _PAGE_DIRTY);
+#endif
        } else
                pte_val(pte) = _PAGE_INVALID;
        return pte;