HelenOS sources

root/kernel/arch/mips32/src/mm/tlb.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. tlb_arch_init
  2. tlb_refill
  3. tlb_invalid
  4. tlb_modified
  5. tlb_prepare_entry_lo
  6. tlb_prepare_entry_hi
  7. tlb_print
  8. tlb_invalidate_all
  9. tlb_invalidate_asid
  10. tlb_invalidate_pages

/*
 * 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);
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub