/*
* Copyright (c) 2003-2004 Jakub Jermar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - 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.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
/** @addtogroup kernel_mips32_mm
* @{
*/
/** @file
*/
#include <arch/mm/tlb.h>
#include <mm/asid.h>
#include <mm/tlb.h>
#include <mm/page.h>
#include <mm/as.h>
#include <arch/cp0.h>
#include <panic.h>
#include <arch.h>
#include <synch/mutex.h>
#include <stdio.h>
#include <log.h>
#include <assert.h>
#include <align.h>
#include <interrupt.h>
#include <symtab.h>
#define PFN_SHIFT 12
#define VPN_SHIFT 12
#define ADDR2HI_VPN(a) ((a) >> VPN_SHIFT)
#define ADDR2HI_VPN2(a) (ADDR2HI_VPN((a)) >> 1)
#define HI_VPN2ADDR(vpn) ((vpn) << VPN_SHIFT)
#define HI_VPN22ADDR(vpn2) (HI_VPN2ADDR(vpn2) << 1)
#define LO_PFN2ADDR(pfn) ((pfn) << PFN_SHIFT)
#define BANK_SELECT_BIT(a) (((a) >> PAGE_WIDTH) & 1)
/** Initialize TLB.
*
* Invalidate all entries and mark wired entries.
*/
void tlb_arch_init(void)
{
int i;
cp0_pagemask_write(TLB_PAGE_MASK_16K);
cp0_entry_hi_write(0);
cp0_entry_lo0_write(0);
cp0_entry_lo1_write(0);
/* Clear and initialize TLB. */
for (i = 0; i < TLB_ENTRY_COUNT; i++) {
cp0_index_write(i);
tlbwi();
}
/*
* The kernel is going to make use of some wired
* entries (e.g. mapping kernel stacks in kseg3).
*/
cp0_wired_write(TLB_WIRED);
}
/** Process TLB Refill Exception.
*
* @param istate Interrupted register context.
*/
void tlb_refill(istate_t *istate)
{
entry_lo_t lo;
uintptr_t badvaddr;
pte_t pte;
badvaddr = cp0_badvaddr_read();
bool found = page_mapping_find(AS, badvaddr, true, &pte);
if (found && pte.p) {
/*
* Record access to PTE.
*/
pte.a = 1;
tlb_prepare_entry_lo(&lo, pte.g, pte.p, pte.d,
pte.cacheable, pte.pfn);
page_mapping_update(AS, badvaddr, true, &pte);
/*
* New entry is to be inserted into TLB
*/
if (BANK_SELECT_BIT(badvaddr) == 0) {
cp0_entry_lo0_write(lo.value);
cp0_entry_lo1_write(0);
} else {
cp0_entry_lo0_write(0);
cp0_entry_lo1_write(lo.value);
}
cp0_pagemask_write(TLB_PAGE_MASK_16K);
tlbwr();
return;
}
(void) as_page_fault(badvaddr, PF_ACCESS_READ, istate);
}
/** Process TLB Invalid Exception.
*
* @param istate Interrupted register context.
*/
void tlb_invalid(istate_t *istate)
{
entry_lo_t lo;
tlb_index_t index;
uintptr_t badvaddr;
pte_t pte;
/*
* Locate the faulting entry in TLB.
*/
tlbp();
index.value = cp0_index_read();
#if defined(PROCESSOR_4Kc)
/*
* This can happen on a 4Kc when Status.EXL is 1 and there is a TLB miss.
* EXL is 1 when interrupts are disabled. The combination of a TLB miss
* and disabled interrupts is possible in copy_to/from_uspace().
*/
if (index.p) {
tlb_refill(istate);
return;
}
#endif
assert(!index.p);
badvaddr = cp0_badvaddr_read();
bool found = page_mapping_find(AS, badvaddr, true, &pte);
if (found && pte.p) {
/*
* Read the faulting TLB entry.
*/
tlbr();
/*
* Record access to PTE.
*/
pte.a = 1;
tlb_prepare_entry_lo(&lo, pte.g, pte.p, pte.d,
pte.cacheable, pte.pfn);
page_mapping_update(AS, badvaddr, true, &pte);
/*
* The entry is to be updated in TLB.
*/
if (BANK_SELECT_BIT(badvaddr) == 0)
cp0_entry_lo0_write(lo.value);
else
cp0_entry_lo1_write(lo.value);
tlbwi();
return;
}
(void) as_page_fault(badvaddr, PF_ACCESS_READ, istate);
}
/** Process TLB Modified Exception.
*
* @param istate Interrupted register context.
*/
void tlb_modified(istate_t *istate)
{
entry_lo_t lo;
tlb_index_t index;
uintptr_t badvaddr;
pte_t pte;
badvaddr = cp0_badvaddr_read();
/*
* Locate the faulting entry in TLB.
*/
tlbp();
index.value = cp0_index_read();
/*
* Emit warning if the entry is not in TLB.
*
* We do not assert on this because this could be a manifestation of
* an emulator bug, such as QEMU Bug #1128935:
* https://bugs.launchpad.net/qemu/+bug/1128935
*/
if (index.p) {
log(LF_ARCH, LVL_WARN, "%s: TLBP failed in exception handler (badvaddr=%#"
PRIxn ", ASID=%d).\n", __func__, badvaddr,
AS ? AS->asid : -1);
return;
}
bool found = page_mapping_find(AS, badvaddr, true, &pte);
if (found && pte.p && pte.w) {
/*
* Read the faulting TLB entry.
*/
tlbr();
/*
* Record access and write to PTE.
*/
pte.a = 1;
pte.d = 1;
tlb_prepare_entry_lo(&lo, pte.g, pte.p, pte.w,
pte.cacheable, pte.pfn);
page_mapping_update(AS, badvaddr, true, &pte);
/*
* The entry is to be updated in TLB.
*/
if (BANK_SELECT_BIT(badvaddr) == 0)
cp0_entry_lo0_write(lo.value);
else
cp0_entry_lo1_write(lo.value);
tlbwi();
return;
}
(void) as_page_fault(badvaddr, PF_ACCESS_WRITE, istate);
}
void
tlb_prepare_entry_lo(entry_lo_t *lo, bool g, bool v, bool d, bool cacheable,
uintptr_t pfn)
{
lo->value = 0;
lo->g = g;
lo->v = v;
lo->d = d;
lo->c = cacheable ? PAGE_CACHEABLE_EXC_WRITE : PAGE_UNCACHED;
lo->pfn = pfn;
}
void tlb_prepare_entry_hi(entry_hi_t *hi, asid_t asid, uintptr_t addr)
{
hi->value = 0;
hi->vpn2 = ADDR2HI_VPN2(ALIGN_DOWN(addr, PAGE_SIZE));
hi->asid = asid;
}
/** Print contents of TLB. */
void tlb_print(void)
{
page_mask_t mask, mask_save;
entry_lo_t lo0, lo0_save, lo1, lo1_save;
entry_hi_t hi, hi_save;
unsigned int i;
hi_save.value = cp0_entry_hi_read();
lo0_save.value = cp0_entry_lo0_read();
lo1_save.value = cp0_entry_lo1_read();
mask_save.value = cp0_pagemask_read();
printf("[nr] [asid] [vpn2 ] [mask] [gvdc] [pfn ]\n");
for (i = 0; i < TLB_ENTRY_COUNT; i++) {
cp0_index_write(i);
tlbr();
mask.value = cp0_pagemask_read();
hi.value = cp0_entry_hi_read();
lo0.value = cp0_entry_lo0_read();
lo1.value = cp0_entry_lo1_read();
printf("%-4u %-6u %0#10x %-#6x %1u%1u%1u%1u %0#10x\n",
i, hi.asid, HI_VPN22ADDR(hi.vpn2), mask.mask,
lo0.g, lo0.v, lo0.d, lo0.c, LO_PFN2ADDR(lo0.pfn));
printf(" %1u%1u%1u%1u %0#10x\n",
lo1.g, lo1.v, lo1.d, lo1.c, LO_PFN2ADDR(lo1.pfn));
}
cp0_entry_hi_write(hi_save.value);
cp0_entry_lo0_write(lo0_save.value);
cp0_entry_lo1_write(lo1_save.value);
cp0_pagemask_write(mask_save.value);
}
/** Invalidate all not wired TLB entries. */
void tlb_invalidate_all(void)
{
entry_lo_t lo0, lo1;
entry_hi_t hi_save;
int i;
assert(interrupts_disabled());
hi_save.value = cp0_entry_hi_read();
for (i = TLB_WIRED; i < TLB_ENTRY_COUNT; i++) {
cp0_index_write(i);
tlbr();
lo0.value = cp0_entry_lo0_read();
lo1.value = cp0_entry_lo1_read();
lo0.v = 0;
lo1.v = 0;
cp0_entry_lo0_write(lo0.value);
cp0_entry_lo1_write(lo1.value);
tlbwi();
}
cp0_entry_hi_write(hi_save.value);
}
/** Invalidate all TLB entries belonging to specified address space.
*
* @param asid Address space identifier.
*/
void tlb_invalidate_asid(asid_t asid)
{
entry_lo_t lo0, lo1;
entry_hi_t hi, hi_save;
int i;
assert(interrupts_disabled());
assert(asid != ASID_INVALID);
hi_save.value = cp0_entry_hi_read();
for (i = 0; i < TLB_ENTRY_COUNT; i++) {
cp0_index_write(i);
tlbr();
hi.value = cp0_entry_hi_read();
if (hi.asid == asid) {
lo0.value = cp0_entry_lo0_read();
lo1.value = cp0_entry_lo1_read();
lo0.v = 0;
lo1.v = 0;
cp0_entry_lo0_write(lo0.value);
cp0_entry_lo1_write(lo1.value);
tlbwi();
}
}
cp0_entry_hi_write(hi_save.value);
}
/** Invalidate TLB entries for specified page range belonging to specified
* address space.
*
* @param asid Address space identifier.
* @param page First page whose TLB entry is to be invalidated.
* @param cnt Number of entries to invalidate.
*/
void tlb_invalidate_pages(asid_t asid, uintptr_t page, size_t cnt)
{
unsigned int i;
entry_lo_t lo0, lo1;
entry_hi_t hi, hi_save;
tlb_index_t index;
assert(interrupts_disabled());
if (asid == ASID_INVALID)
return;
hi_save.value = cp0_entry_hi_read();
for (i = 0; i < cnt + 1; i += 2) {
tlb_prepare_entry_hi(&hi, asid, page + i * PAGE_SIZE);
cp0_entry_hi_write(hi.value);
tlbp();
index.value = cp0_index_read();
if (!index.p) {
/*
* Entry was found, index register contains valid
* index.
*/
tlbr();
lo0.value = cp0_entry_lo0_read();
lo1.value = cp0_entry_lo1_read();
lo0.v = 0;
lo1.v = 0;
cp0_entry_lo0_write(lo0.value);
cp0_entry_lo1_write(lo1.value);
tlbwi();
}
}
cp0_entry_hi_write(hi_save.value);
}
/** @}
*/