/* $NetBSD: e500_tlb.c,v 1.24 2022/05/31 08:43:15 andvar Exp $ */
/*-
* Copyright (c) 2010, 2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Raytheon BBN Technologies Corp and Defense Advanced Research Projects
* Agency and which was developed by Matt Thomas of 3am Software Foundry.
*
* This material is based upon work supported by the Defense Advanced Research
* Projects Agency and Space and Naval Warfare Systems Center, Pacific, under
* Contract No. N66001-09-C-2073.
* Approved for Public Release, Distribution Unlimited
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* ESEL is the way we want to look up.
* If tlbassoc is the same as tlbentries (like in TLB1) then the TLB is
* fully associative, the entire slot is placed into ESEL. If tlbassoc
* is less than the number of tlb entries, the slot is split in two
* fields. Since the TLB is M rows by N ways, the lowers bits are for
* row (MAS2[EPN]) and the upper for the way (MAS1[ESEL]).
*/
const u_int tlbassoc = TLBCFG_ASSOC(tlbcfg);
const u_int tlbentries = TLBCFG_NENTRY(tlbcfg);
const u_int esel_shift =
__builtin_clz(tlbassoc) - __builtin_clz(tlbentries);
/*
* Disable interrupts since we don't want anyone else mucking with
* the MMU Assist registers
*/
const register_t msr = wrtee(0);
const register_t saved_mas0 = mfspr(SPR_MAS0);
mtspr(SPR_MAS0, mas0 | MAS0_ESEL_MAKE(slot >> esel_shift));
if (__predict_true(tlbassoc > tlbentries))
mtspr(SPR_MAS2, slot << PAGE_SHIFT);
/*
* Now select the entry and grab its contents.
*/
__asm volatile("tlbre");
/*
* Need to always write MAS0 and MAS1
*/
mtspr(SPR_MAS0, hwtlb.hwtlb_mas0);
mtspr(SPR_MAS1, hwtlb.hwtlb_mas1);
/*
* Only write the VPN/WIMGE if this is in TLB0 or if a valid mapping.
*/
if ((hwtlb.hwtlb_mas0 & MAS0_TLBSEL) == MAS0_TLBSEL_TLB0
|| (hwtlb.hwtlb_mas1 & MAS1_V)) {
mtspr(SPR_MAS2, hwtlb.hwtlb_mas2);
}
/*
* Only need to write the RPN/prot if we are dealing with a valid
* mapping.
*/
if (hwtlb.hwtlb_mas1 & MAS1_V) {
mtspr(SPR_MAS3, hwtlb.hwtlb_mas3);
//mtspr(SPR_MAS7, 0);
}
static void
e500_tlb_invalidate_all(void)
{
/*
* This does a flash invalidate of all entries in TLB0.
* We don't touch TLB1 since we don't expect those to be volatile.
*/
#if 1
__asm volatile("tlbivax\t0, %0" :: "b"(4)); /* INV_ALL */
__asm volatile("tlbsync\n\tisync\n\tsync");
#else
mtspr(SPR_MMUCSR0, MMUCSR0_TLB0_FI);
while (mfspr(SPR_MMUCSR0) != 0)
;
#endif
}
/*
* Make sure this is a valid kernel entry first.
*/
if ((mas1 & (MAS1_V|MAS1_TID|MAS1_TS)) != MAS1_V)
continue;
/*
* We have a valid kernel TLB entry. But if it matches
* the stack we are currently running on, it would
* unwise to invalidate it. First see if the epn
* overlaps the stack. If it does then get the
* VA and see if it really is part of the stack.
*/
if (epn_kstack_lo < epn_kstack_hi
? (epn_kstack_lo <= epn && epn <= epn_kstack_hi)
: (epn <= epn_kstack_hi || epn_kstack_lo <= epn)) {
const uint32_t mas2_epn =
mfspr(SPR_MAS2) & MAS2_EPN;
if (kstack_lo <= mas2_epn
&& mas2_epn <= kstack_hi)
continue;
}
mtspr(SPR_MAS1, mas1 ^ MAS1_V);
__asm volatile("tlbwe");
}
}
__asm volatile("isync\n\tsync");
wrtee(msr);
#endif /* MULTIPROCESSOR */
}
const register_t msr = wrtee(0);
for (size_t assoc = 0; assoc < tlbassoc; assoc++) {
mtspr(SPR_MAS0, MAS0_ESEL_MAKE(assoc) | MAS0_TLBSEL_TLB0);
for (size_t epn = 0; epn < max_epn; epn += PAGE_SIZE) {
mtspr(SPR_MAS2, epn);
__asm volatile("tlbre");
const uint32_t mas1 = mfspr(SPR_MAS1);
/*
* If this is a valid entry for AS space 1 and
* its asid matches the constraints of the caller,
* clear its valid bit.
*/
if ((mas1 & (MAS1_V|MAS1_TS)) == (MAS1_V|MAS1_TS)) {
const uint32_t asid = MASX_TID_GET(mas1);
const u_int i = asid / nbits;
const u_long mask = 1UL << (asid & (nbits - 1));
if ((bitmap[i] & mask) == 0) {
bitmap[i] |= mask;
found++;
}
}
}
}
wrtee(msr);
return found;
}
static void
e500_tlb_invalidate_addr(vaddr_t va, tlb_asid_t asid)
{
KASSERT((va & PAGE_MASK) == 0);
/*
* Bits 60 & 61 have meaning
*/
if (asid == KERNEL_PID) {
/*
* For data accesses, the context-synchronizing instruction
* before tlbwe or tlbivax ensures that all memory accesses
* due to preceding instructions have completed to a point
* at which they have reported all exceptions they will cause.
*/
__asm volatile("isync");
}
__asm volatile("tlbivax\t0, %0" :: "b"(va));
__asm volatile("tlbsync");
__asm volatile("tlbsync"); /* Why? */
if (asid == KERNEL_PID) {
/*
* The context-synchronizing instruction after tlbwe or tlbivax
* ensures that subsequent accesses (data and instruction) use
* the updated value in any TLB entries affected.
*/
__asm volatile("isync\n\tsync");
}
}
/*
* See if we have a TLB entry for the pa.
*/
for (u_int i = 0; i < tlb1->tlb1_numentries; i++, xtlb++) {
psize_t mask = ~(xtlb->e_tlb.tlb_size - 1);
if ((xtlb->e_hwtlb.hwtlb_mas1 & MAS1_V)
&& ((pa ^ xtlb->e_tlb.tlb_pte) & mask) == 0) {
if (slotp != NULL)
*slotp = i;
return xtlb;
}
}
/*
* See if we have a TLB entry for the va.
*/
for (u_int i = 0; i < tlb1->tlb1_numentries; i++, xtlb++) {
vsize_t mask = ~(xtlb->e_tlb.tlb_size - 1);
if ((xtlb->e_hwtlb.hwtlb_mas1 & MAS1_V)
&& ((va ^ xtlb->e_tlb.tlb_va) & mask) == 0) {
if (slotp != NULL)
*slotp = i;
return xtlb;
}
}
/*
* See if we have a TLB entry for the pa.
*/
for (u_int i = 0; i < tlb1->tlb1_numentries; i++, xtlb++) {
vsize_t mask = ~(xtlb->e_tlb.tlb_size - 1);
if ((xtlb->e_hwtlb.hwtlb_mas1 & MAS1_V)
&& ((va ^ xtlb->e_tlb.tlb_va) & mask) == 0
&& (((va + len - 1) ^ va) & mask) == 0) {
return xtlb;
}
}
/*
* See if we have a TLB entry for the pa. If completely falls within
* mark the reference and return the pa. But only if the tlb entry
* is not cacheable.
*/
if (xtlb
&& (prefetchable
|| (xtlb->e_tlb.tlb_pte & PTE_WIG) == (PTE_I|PTE_G))) {
xtlb->e_refcnt++;
return (void *) (xtlb->e_tlb.tlb_va
+ pa - (xtlb->e_tlb.tlb_pte & PTE_RPN_MASK));
}
return NULL;
}
static void
e500_tlb_unmapiodev(vaddr_t va, vsize_t len)
{
if (va < VM_MIN_KERNEL_ADDRESS || VM_MAX_KERNEL_ADDRESS <= va) {
struct e500_xtlb * const xtlb = e500_tlb_lookup_xtlb(va, NULL);
if (xtlb)
xtlb->e_refcnt--;
}
}
static int
e500_tlb_ioreserve(vaddr_t va, vsize_t len, pt_entry_t pte)
{
struct e500_tlb1 * const tlb1 = &e500_tlb1;
struct e500_xtlb *xtlb;
/*
* Let's see what's in TLB1 and we need to invalidate any entry that
* would fit within the kernel's mapped address space.
*/
psize_t memmapped = 0;
for (u_int i = 0; i < tlb1->tlb1_numentries; i++) {
struct e500_xtlb * const xtlb = &tlb1->tlb1_entries[i];
if (xtlb->e_tlb.tlb_va == 0
|| xtlb->e_tlb.tlb_va + xtlb->e_tlb.tlb_size <= memsize) {
memmapped += xtlb->e_tlb.tlb_size;
/*
* Let make sure main memory is setup so it's memory
* coherent. For some reason u-boot doesn't set it up
* that way.
*/
if ((xtlb->e_hwtlb.hwtlb_mas2 & MAS2_M) == 0) {
xtlb->e_hwtlb.hwtlb_mas2 |= MAS2_M;
hwtlb_write(xtlb->e_hwtlb, true);
}
}
}
if (__predict_false(memmapped < memsize)) {
/*
* Let's see how many TLB entries are needed to map memory.
*/
u_int slotmask = e500_tlbmemmap(0, memsize, tlb1);
/*
* To map main memory into the TLB, we need to flush any
* existing entries from the TLB that overlap the virtual
* address space needed to map physical memory. That may
* include the entries for the pages currently used by the
* stack or that we are executing. So to avoid problems, we
* are going to temporarily map the kernel and stack into AS 1,
* switch to it, and clear out the TLB entries from AS 0,
* install the new TLB entries to map memory, and then switch
* back to AS 0 and free the temp entry used for AS1.
*/
u_int b = __builtin_clz(endkernel);
/*
* If the kernel doesn't end on a clean power of 2, we need
* to round the size up (by decrementing the number of leading
* zero bits). If the size isn't a power of 4KB, decrement
* again to make it one.
*/
if (endkernel & (endkernel - 1))
b--;
if ((b & 1) == 0)
b--;
/*
* Create a TLB1 mapping for the kernel in AS1.
*/
const u_int kslot = e500_alloc_tlb1_entry();
struct e500_xtlb * const kxtlb = &tlb1->tlb1_entries[kslot];
kxtlb->e_tlb.tlb_va = 0;
kxtlb->e_tlb.tlb_size = 1UL << (31 - b);
kxtlb->e_tlb.tlb_pte = PTE_M|PTE_xR|PTE_xW|PTE_xX;
kxtlb->e_tlb.tlb_asid = KERNEL_PID;
/*
* Now that we have a TLB mapping in AS1 for the kernel and its
* stack, we switch to AS1 to cleanup the TLB mappings for TLB0.
*/
const register_t saved_msr = mfmsr();
mtmsr(saved_msr | PSL_DS | PSL_IS);
__asm volatile("isync");
/*
*** Invalidate all the TLB0 entries.
*/
e500_tlb_invalidate_all();
/*
*** Now let's see if we have any entries in TLB1 that would
*** overlap the ones we are about to install. If so, nuke 'em.
*/
for (u_int i = 0; i < tlb1->tlb1_numentries; i++) {
struct e500_xtlb * const xtlb = &tlb1->tlb1_entries[i];
struct e500_hwtlb * const hwtlb = &xtlb->e_hwtlb;
if ((hwtlb->hwtlb_mas1 & (MAS1_V|MAS1_TS)) == MAS1_V
&& (hwtlb->hwtlb_mas2 & MAS2_EPN) < memsize) {
e500_free_tlb1_entry(xtlb, i, false);
}
}
/*
*** Now we can add the TLB entries that will map physical
*** memory. If bit 0 [MSB] in slotmask is set, then tlb
*** entry 0 contains a mapping for physical memory...
*/
struct e500_xtlb *entries = tlb1->tlb1_entries;
while (slotmask != 0) {
const u_int slot = __builtin_clz(slotmask);
hwtlb_write(entries[slot].e_hwtlb, false);
entries += slot + 1;
slotmask <<= slot + 1;
}
/*
*** Synchronize the TLB and the instruction stream.
*/
__asm volatile("tlbsync");
__asm volatile("isync");
/*
*** Switch back to AS 0.
*/
mtmsr(saved_msr);
__asm volatile("isync");