aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-03-29 08:38:34 +1100
committerEli Qiao <taget@linux.vnet.ibm.com>2014-03-31 11:16:40 +0800
commit0da78b73bb245274703585c1bd0a79989e71744a (patch)
treeadd5443878a3c5f6f547fda48d0a05017e23808f
parente035063a1a6e8fbfa17ae024a807ebc753fd0289 (diff)
downloadpowerkvm-0da78b73bb245274703585c1bd0a79989e71744a.tar.gz
powerpc/mm: Fix page size passed to tlbie
Commit b1022fbd293564de91596b8775340cf41ad5214c and subsequent ones (in 3.10) introduced some preparatory changes for THP which consist of trying to read the actual HPTE page size from the hash table to perform the right variant of tlbie. However this has two issues: - The hash entry can have been evicted and replaced by another one with a different page size. This can in turn cause us to use an impossible combination of psize and actual_psize, in turn causing tlbie to be called with an invalid LP bit combination causing a HW checkstop - The whole business is unnecessary as in 3.10 we don't have THP and thus always have psize == actual_psize When THP was actual enabled in 3.11, we discovered that this wasn't going to work and changed the code significantly to pass the proper actual_psize from the upper layers rather than tyring to deduce it from the HPTE. However, we didn't "fix" 3.10 as we didn't realize that the bug introduced an exposure without THP being enabled. If a user page was hashed as a 64k page, and later got evicted from the hash and replaced with a 4k hash entry (due to a segment being demoted to 4k, for example by subpage protection or because it's an IO page), we could get into a situation where we tried to do a tlbie with a psize of 64k and actual_psize of 4k which is deadly. This is a 3.10-only fix for this situation which essentially removes the actual_psize business from the normal updatepp and invalidate path in hash_native_64.c since we know on 3.10 that the psize coming from the upper levels is always correct (no THP). As such it's a partial revert of b1022fbd293564de91596b8775340cf41ad5214c (we don't touch the bolted path etc... those should be fine and we want to minimize churn). Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r--arch/powerpc/mm/hash_native_64.c25
1 files changed, 5 insertions, 20 deletions
diff --git a/arch/powerpc/mm/hash_native_64.c b/arch/powerpc/mm/hash_native_64.c
index d695d9df0bee44..14c07fd25652ee 100644
--- a/arch/powerpc/mm/hash_native_64.c
+++ b/arch/powerpc/mm/hash_native_64.c
@@ -331,7 +331,6 @@ static long native_hpte_updatepp(unsigned long slot, unsigned long newpp,
struct hash_pte *hptep = htab_address + slot;
unsigned long hpte_v, want_v;
int ret = 0;
- int actual_psize;
want_v = hpte_encode_avpn(vpn, psize, ssize);
@@ -341,7 +340,7 @@ static long native_hpte_updatepp(unsigned long slot, unsigned long newpp,
native_lock_hpte(hptep);
hpte_v = hptep->v;
- actual_psize = hpte_actual_psize(hptep, psize);
+
/*
* We need to invalidate the TLB always because hpte_remove doesn't do
* a tlb invalidate. If a hash bucket gets full, we "evict" a more/less
@@ -349,12 +348,7 @@ static long native_hpte_updatepp(unsigned long slot, unsigned long newpp,
* (hpte_remove) because we assume the old translation is still
* technically "valid".
*/
- if (actual_psize < 0) {
- actual_psize = psize;
- ret = -1;
- goto err_out;
- }
- if (!HPTE_V_COMPARE(hpte_v, want_v)) {
+ if (!(hptep->v & HPTE_V_VALID) || !HPTE_V_COMPARE(hpte_v, want_v)) {
DBG_LOW(" -> miss\n");
ret = -1;
} else {
@@ -363,11 +357,10 @@ static long native_hpte_updatepp(unsigned long slot, unsigned long newpp,
hptep->r = (hptep->r & ~(HPTE_R_PP | HPTE_R_N)) |
(newpp & (HPTE_R_PP | HPTE_R_N | HPTE_R_C));
}
-err_out:
native_unlock_hpte(hptep);
/* Ensure it is out of the tlb too. */
- tlbie(vpn, psize, actual_psize, ssize, local);
+ tlbie(vpn, psize, psize, ssize, local);
return ret;
}
@@ -440,7 +433,6 @@ static void native_hpte_invalidate(unsigned long slot, unsigned long vpn,
unsigned long hpte_v;
unsigned long want_v;
unsigned long flags;
- int actual_psize;
local_irq_save(flags);
@@ -450,7 +442,6 @@ static void native_hpte_invalidate(unsigned long slot, unsigned long vpn,
native_lock_hpte(hptep);
hpte_v = hptep->v;
- actual_psize = hpte_actual_psize(hptep, psize);
/*
* We need to invalidate the TLB always because hpte_remove doesn't do
* a tlb invalidate. If a hash bucket gets full, we "evict" a more/less
@@ -458,20 +449,14 @@ static void native_hpte_invalidate(unsigned long slot, unsigned long vpn,
* (hpte_remove) because we assume the old translation is still
* technically "valid".
*/
- if (actual_psize < 0) {
- actual_psize = psize;
- native_unlock_hpte(hptep);
- goto err_out;
- }
- if (!HPTE_V_COMPARE(hpte_v, want_v))
+ if (!(hptep->v & HPTE_V_VALID) || !HPTE_V_COMPARE(hpte_v, want_v))
native_unlock_hpte(hptep);
else
/* Invalidate the hpte. NOTE: this also unlocks it */
hptep->v = 0;
-err_out:
/* Invalidate the TLB */
- tlbie(vpn, psize, actual_psize, ssize, local);
+ tlbie(vpn, psize, psize, ssize, local);
local_irq_restore(flags);
}