diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-03-29 08:38:34 +1100 |
---|---|---|
committer | Eli Qiao <taget@linux.vnet.ibm.com> | 2014-03-31 11:16:40 +0800 |
commit | 0da78b73bb245274703585c1bd0a79989e71744a (patch) | |
tree | add5443878a3c5f6f547fda48d0a05017e23808f | |
parent | e035063a1a6e8fbfa17ae024a807ebc753fd0289 (diff) | |
download | powerkvm-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.c | 25 |
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); } |