Commit 074cfcb4 authored by Yongbok Kim's avatar Yongbok Kim Committed by Aleksandar Markovic
Browse files

target/mips: Implement hardware page table walker for MIPS32



Implement hardware page table walker. This implementation is
limiter only to MIPS32.

Reviewed-by: default avatarAleksandar Markovic <amarkovic@wavecomp.com>
Signed-off-by: default avatarYongbok Kim <yongbok.kim@mips.com>
Signed-off-by: default avatarAleksandar Markovic <amarkovic@wavecomp.com>
parent 63010795
Loading
Loading
Loading
Loading
+363 −2
Original line number Diff line number Diff line
@@ -537,6 +537,342 @@ hwaddr mips_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
}
#endif

#if !defined(CONFIG_USER_ONLY)
#if !defined(TARGET_MIPS64)

/*
 * Perform hardware page table walk
 *
 * Memory accesses are performed using the KERNEL privilege level.
 * Synchronous exceptions detected on memory accesses cause a silent exit
 * from page table walking, resulting in a TLB or XTLB Refill exception.
 *
 * Implementations are not required to support page table walk memory
 * accesses from mapped memory regions. When an unsupported access is
 * attempted, a silent exit is taken, resulting in a TLB or XTLB Refill
 * exception.
 *
 * Note that if an exception is caused by AddressTranslation or LoadMemory
 * functions, the exception is not taken, a silent exit is taken,
 * resulting in a TLB or XTLB Refill exception.
 */

static bool get_pte(CPUMIPSState *env, uint64_t vaddr, int entry_size,
        uint64_t *pte)
{
    if ((vaddr & ((entry_size >> 3) - 1)) != 0) {
        return false;
    }
    if (entry_size == 64) {
        *pte = cpu_ldq_code(env, vaddr);
    } else {
        *pte = cpu_ldl_code(env, vaddr);
    }
    return true;
}

static uint64_t get_tlb_entry_layout(CPUMIPSState *env, uint64_t entry,
        int entry_size, int ptei)
{
    uint64_t result = entry;
    uint64_t rixi;
    if (ptei > entry_size) {
        ptei -= 32;
    }
    result >>= (ptei - 2);
    rixi = result & 3;
    result >>= 2;
    result |= rixi << CP0EnLo_XI;
    return result;
}

static int walk_directory(CPUMIPSState *env, uint64_t *vaddr,
        int directory_index, bool *huge_page, bool *hgpg_directory_hit,
        uint64_t *pw_entrylo0, uint64_t *pw_entrylo1)
{
    int dph = (env->CP0_PWCtl >> CP0PC_DPH) & 0x1;
    int psn = (env->CP0_PWCtl >> CP0PC_PSN) & 0x3F;
    int hugepg = (env->CP0_PWCtl >> CP0PC_HUGEPG) & 0x1;
    int pf_ptew = (env->CP0_PWField >> CP0PF_PTEW) & 0x3F;
    int ptew = (env->CP0_PWSize >> CP0PS_PTEW) & 0x3F;
    int native_shift = (((env->CP0_PWSize >> CP0PS_PS) & 1) == 0) ? 2 : 3;
    int directory_shift = (ptew > 1) ? -1 :
            (hugepg && (ptew == 1)) ? native_shift + 1 : native_shift;
    int leaf_shift = (ptew > 1) ? -1 :
            (ptew == 1) ? native_shift + 1 : native_shift;
    uint32_t direntry_size = 1 << (directory_shift + 3);
    uint32_t leafentry_size = 1 << (leaf_shift + 3);
    uint64_t entry;
    uint64_t paddr;
    int prot;
    uint64_t lsb = 0;
    uint64_t w = 0;

    if (get_physical_address(env, &paddr, &prot, *vaddr, MMU_DATA_LOAD,
                             ACCESS_INT, cpu_mmu_index(env, false)) !=
                             TLBRET_MATCH) {
        /* wrong base address */
        return 0;
    }
    if (!get_pte(env, *vaddr, direntry_size, &entry)) {
        return 0;
    }

    if ((entry & (1 << psn)) && hugepg) {
        *huge_page = true;
        *hgpg_directory_hit = true;
        entry = get_tlb_entry_layout(env, entry, leafentry_size, pf_ptew);
        w = directory_index - 1;
        if (directory_index & 0x1) {
            /* Generate adjacent page from same PTE for odd TLB page */
            lsb = (1 << w) >> 6;
            *pw_entrylo0 = entry & ~lsb; /* even page */
            *pw_entrylo1 = entry | lsb; /* odd page */
        } else if (dph) {
            int oddpagebit = 1 << leaf_shift;
            uint64_t vaddr2 = *vaddr ^ oddpagebit;
            if (*vaddr & oddpagebit) {
                *pw_entrylo1 = entry;
            } else {
                *pw_entrylo0 = entry;
            }
            if (get_physical_address(env, &paddr, &prot, vaddr2, MMU_DATA_LOAD,
                                     ACCESS_INT, cpu_mmu_index(env, false)) !=
                                     TLBRET_MATCH) {
                return 0;
            }
            if (!get_pte(env, vaddr2, leafentry_size, &entry)) {
                return 0;
            }
            entry = get_tlb_entry_layout(env, entry, leafentry_size, pf_ptew);
            if (*vaddr & oddpagebit) {
                *pw_entrylo0 = entry;
            } else {
                *pw_entrylo1 = entry;
            }
        } else {
            return 0;
        }
        return 1;
    } else {
        *vaddr = entry;
        return 2;
    }
}

static bool page_table_walk_refill(CPUMIPSState *env, vaddr address, int rw,
        int mmu_idx)
{
    int gdw = (env->CP0_PWSize >> CP0PS_GDW) & 0x3F;
    int udw = (env->CP0_PWSize >> CP0PS_UDW) & 0x3F;
    int mdw = (env->CP0_PWSize >> CP0PS_MDW) & 0x3F;
    int ptw = (env->CP0_PWSize >> CP0PS_PTW) & 0x3F;
    int ptew = (env->CP0_PWSize >> CP0PS_PTEW) & 0x3F;

    /* Initial values */
    bool huge_page = false;
    bool hgpg_bdhit = false;
    bool hgpg_gdhit = false;
    bool hgpg_udhit = false;
    bool hgpg_mdhit = false;

    int32_t pw_pagemask = 0;
    target_ulong pw_entryhi = 0;
    uint64_t pw_entrylo0 = 0;
    uint64_t pw_entrylo1 = 0;

    /* Native pointer size */
    /*For the 32-bit architectures, this bit is fixed to 0.*/
    int native_shift = (((env->CP0_PWSize >> CP0PS_PS) & 1) == 0) ? 2 : 3;

    /* Indices from PWField */
    int pf_gdw = (env->CP0_PWField >> CP0PF_GDW) & 0x3F;
    int pf_udw = (env->CP0_PWField >> CP0PF_UDW) & 0x3F;
    int pf_mdw = (env->CP0_PWField >> CP0PF_MDW) & 0x3F;
    int pf_ptw = (env->CP0_PWField >> CP0PF_PTW) & 0x3F;
    int pf_ptew = (env->CP0_PWField >> CP0PF_PTEW) & 0x3F;

    /* Indices computed from faulting address */
    int gindex = (address >> pf_gdw) & ((1 << gdw) - 1);
    int uindex = (address >> pf_udw) & ((1 << udw) - 1);
    int mindex = (address >> pf_mdw) & ((1 << mdw) - 1);
    int ptindex = (address >> pf_ptw) & ((1 << ptw) - 1);

    /* Other HTW configs */
    int hugepg = (env->CP0_PWCtl >> CP0PC_HUGEPG) & 0x1;

    /* HTW Shift values (depend on entry size) */
    int directory_shift = (ptew > 1) ? -1 :
            (hugepg && (ptew == 1)) ? native_shift + 1 : native_shift;
    int leaf_shift = (ptew > 1) ? -1 :
            (ptew == 1) ? native_shift + 1 : native_shift;

    /* Offsets into tables */
    int goffset = gindex << directory_shift;
    int uoffset = uindex << directory_shift;
    int moffset = mindex << directory_shift;
    int ptoffset0 = (ptindex >> 1) << (leaf_shift + 1);
    int ptoffset1 = ptoffset0 | (1 << (leaf_shift));

    uint32_t leafentry_size = 1 << (leaf_shift + 3);

    /* Starting address - Page Table Base */
    uint64_t vaddr = env->CP0_PWBase;

    uint64_t dir_entry;
    uint64_t paddr;
    int prot;
    int m;

    if (!(env->CP0_Config3 & (1 << CP0C3_PW))) {
        /* walker is unimplemented */
        return false;
    }
    if (!(env->CP0_PWCtl & (1 << CP0PC_PWEN))) {
        /* walker is disabled */
        return false;
    }
    if (!(gdw > 0 || udw > 0 || mdw > 0)) {
        /* no structure to walk */
        return false;
    }
    if ((directory_shift == -1) || (leaf_shift == -1)) {
        return false;
    }

    /* Global Directory */
    if (gdw > 0) {
        vaddr |= goffset;
        switch (walk_directory(env, &vaddr, pf_gdw, &huge_page, &hgpg_gdhit,
                               &pw_entrylo0, &pw_entrylo1))
        {
        case 0:
            return false;
        case 1:
            goto refill;
        case 2:
        default:
            break;
        }
    }

    /* Upper directory */
    if (udw > 0) {
        vaddr |= uoffset;
        switch (walk_directory(env, &vaddr, pf_udw, &huge_page, &hgpg_udhit,
                               &pw_entrylo0, &pw_entrylo1))
        {
        case 0:
            return false;
        case 1:
            goto refill;
        case 2:
        default:
            break;
        }
    }

    /* Middle directory */
    if (mdw > 0) {
        vaddr |= moffset;
        switch (walk_directory(env, &vaddr, pf_mdw, &huge_page, &hgpg_mdhit,
                               &pw_entrylo0, &pw_entrylo1))
        {
        case 0:
            return false;
        case 1:
            goto refill;
        case 2:
        default:
            break;
        }
    }

    /* Leaf Level Page Table - First half of PTE pair */
    vaddr |= ptoffset0;
    if (get_physical_address(env, &paddr, &prot, vaddr, MMU_DATA_LOAD,
                             ACCESS_INT, cpu_mmu_index(env, false)) !=
                             TLBRET_MATCH) {
        return false;
    }
    if (!get_pte(env, vaddr, leafentry_size, &dir_entry)) {
        return false;
    }
    dir_entry = get_tlb_entry_layout(env, dir_entry, leafentry_size, pf_ptew);
    pw_entrylo0 = dir_entry;

    /* Leaf Level Page Table - Second half of PTE pair */
    vaddr |= ptoffset1;
    if (get_physical_address(env, &paddr, &prot, vaddr, MMU_DATA_LOAD,
                             ACCESS_INT, cpu_mmu_index(env, false)) !=
                             TLBRET_MATCH) {
        return false;
    }
    if (!get_pte(env, vaddr, leafentry_size, &dir_entry)) {
        return false;
    }
    dir_entry = get_tlb_entry_layout(env, dir_entry, leafentry_size, pf_ptew);
    pw_entrylo1 = dir_entry;

refill:

    m = (1 << pf_ptw) - 1;

    if (huge_page) {
        switch (hgpg_bdhit << 3 | hgpg_gdhit << 2 | hgpg_udhit << 1 |
                hgpg_mdhit)
        {
        case 4:
            m = (1 << pf_gdw) - 1;
            if (pf_gdw & 1) {
                m >>= 1;
            }
            break;
        case 2:
            m = (1 << pf_udw) - 1;
            if (pf_udw & 1) {
                m >>= 1;
            }
            break;
        case 1:
            m = (1 << pf_mdw) - 1;
            if (pf_mdw & 1) {
                m >>= 1;
            }
            break;
        }
    }
    pw_pagemask = m >> 12;
    update_pagemask(env, pw_pagemask << 13, &pw_pagemask);
    pw_entryhi = (address & ~0x1fff) | (env->CP0_EntryHi & 0xFF);
    {
        target_ulong tmp_entryhi = env->CP0_EntryHi;
        int32_t tmp_pagemask = env->CP0_PageMask;
        uint64_t tmp_entrylo0 = env->CP0_EntryLo0;
        uint64_t tmp_entrylo1 = env->CP0_EntryLo1;

        env->CP0_EntryHi = pw_entryhi;
        env->CP0_PageMask = pw_pagemask;
        env->CP0_EntryLo0 = pw_entrylo0;
        env->CP0_EntryLo1 = pw_entrylo1;

        /*
         * The hardware page walker inserts a page into the TLB in a manner
         * identical to a TLBWR instruction as executed by the software refill
         * handler.
         */
        r4k_helper_tlbwr(env);

        env->CP0_EntryHi = tmp_entryhi;
        env->CP0_PageMask = tmp_pagemask;
        env->CP0_EntryLo0 = tmp_entrylo0;
        env->CP0_EntryLo1 = tmp_entrylo1;
    }
    return true;
}
#endif
#endif

int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
                              int mmu_idx)
{
@@ -558,8 +894,7 @@ int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,

    /* data access */
#if !defined(CONFIG_USER_ONLY)
    /* XXX: put correct access by using cpu_restore_state()
       correctly */
    /* XXX: put correct access by using cpu_restore_state() correctly */
    access_type = ACCESS_INT;
    ret = get_physical_address(env, &physical, &prot,
                               address, rw, access_type, mmu_idx);
@@ -583,6 +918,32 @@ int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
    } else if (ret < 0)
#endif
    {
#if !defined(CONFIG_USER_ONLY)
#if !defined(TARGET_MIPS64)
        if ((ret == TLBRET_NOMATCH) && (env->tlb->nb_tlb > 1)) {
            /*
             * Memory reads during hardware page table walking are performed
             * as if they were kernel-mode load instructions.
             */
            int mode = (env->hflags & MIPS_HFLAG_KSU);
            bool ret_walker;
            env->hflags &= ~MIPS_HFLAG_KSU;
            ret_walker = page_table_walk_refill(env, address, rw, mmu_idx);
            env->hflags |= mode;
            if (ret_walker) {
                ret = get_physical_address(env, &physical, &prot,
                                           address, rw, access_type, mmu_idx);
                if (ret == TLBRET_MATCH) {
                    tlb_set_page(cs, address & TARGET_PAGE_MASK,
                            physical & TARGET_PAGE_MASK, prot | PAGE_EXEC,
                            mmu_idx, TARGET_PAGE_SIZE);
                    ret = 0;
                    return ret;
                }
            }
        }
#endif
#endif
        raise_mmu_exception(env, address, rw, ret);
        ret = 1;
    }
+1 −0
Original line number Diff line number Diff line
@@ -211,6 +211,7 @@ uint64_t float_class_d(uint64_t arg, float_status *fst);

extern unsigned int ieee_rm[];
int ieee_ex_to_mips(int xcpt);
void update_pagemask(CPUMIPSState *env, target_ulong arg1, int32_t *pagemask);

static inline void restore_rounding_mode(CPUMIPSState *env)
{
+6 −1
Original line number Diff line number Diff line
@@ -1400,7 +1400,7 @@ void helper_mtc0_context(CPUMIPSState *env, target_ulong arg1)
    env->CP0_Context = (env->CP0_Context & 0x007FFFFF) | (arg1 & ~0x007FFFFF);
}

void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
void update_pagemask(CPUMIPSState *env, target_ulong arg1, int32_t *pagemask)
{
    uint64_t mask = arg1 >> (TARGET_PAGE_BITS + 1);
    if (!(env->insn_flags & ISA_MIPS32R6) || (arg1 == ~0) ||
@@ -1411,6 +1411,11 @@ void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
    }
}

void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
{
    update_pagemask(env, arg1, &env->CP0_PageMask);
}

void helper_mtc0_pagegrain(CPUMIPSState *env, target_ulong arg1)
{
    /* SmartMIPS not implemented */