/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   Mupen64plus - tlb.c                                                   *
 *   Mupen64Plus homepage: https://mupen64plus.org/                        *
 *   Copyright (C) 2002 Hacktarux                                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "tlb.h"

#include "api/m64p_types.h"
#include "device/r4300/r4300_core.h"
#include "device/rdram/rdram.h"

#include <assert.h>
#include <string.h>

void poweron_tlb(struct tlb* tlb)
{
    /* clear TLB entries */
    memset(tlb->entries, 0, 32 * sizeof(tlb->entries[0]));
    memset(tlb->LUT_r, 0, 0x100000 * sizeof(tlb->LUT_r[0]));
    memset(tlb->LUT_w, 0, 0x100000 * sizeof(tlb->LUT_w[0]));
    memset(tlb->r_cached, 0, 0x100000 * sizeof(tlb->r_cached[0]));
    memset(tlb->w_cached, 0, 0x100000 * sizeof(tlb->w_cached[0]));
}

void tlb_unmap(struct tlb* tlb, size_t entry)
{
    unsigned int i;
    const struct tlb_entry* e;

    assert(entry < 32);
    e = &tlb->entries[entry];

    if (e->v_even)
    {
        for (i=e->start_even; i<e->end_even; i += 0x1000)
        {
            tlb->LUT_r[i>>12] = 0;
            tlb->r_cached[i>>12] = 0;
        }
        if (e->d_even)
            for (i=e->start_even; i<e->end_even; i += 0x1000)
            {
                tlb->LUT_w[i>>12] = 0;
                tlb->w_cached[i>>12] = 0;
            }
    }

    if (e->v_odd)
    {
        for (i=e->start_odd; i<e->end_odd; i += 0x1000)
        {
            tlb->LUT_r[i>>12] = 0;
            tlb->r_cached[i>>12] = 0;
        }
        if (e->d_odd)
            for (i=e->start_odd; i<e->end_odd; i += 0x1000)
            {
                tlb->LUT_w[i>>12] = 0;
                tlb->w_cached[i>>12] = 0;
            }
    }
}

void tlb_map(struct tlb* tlb, size_t entry)
{
    unsigned int i;
    const struct tlb_entry* e;

    assert(entry < 32);
    e = &tlb->entries[entry];

    if (e->v_even)
    {
        if (e->start_even < e->end_even &&
            !(e->start_even >= 0x80000000 && e->end_even < 0xC0000000) &&
            e->phys_even < 0x20000000)
        {
            for (i=e->start_even;i<e->end_even;i+=0x1000)
            {
                tlb->LUT_r[i>>12] = UINT32_C(0x80000000) | (e->phys_even + (i - e->start_even) + 0xFFF);
                tlb->r_cached[i>>12] = e->c_even != 2;
            }
            if (e->d_even)
                for (i=e->start_even;i<e->end_even;i+=0x1000)
                {
                    tlb->LUT_w[i>>12] = UINT32_C(0x80000000) | (e->phys_even + (i - e->start_even) + 0xFFF);
                    tlb->w_cached[i>>12] = e->c_even != 2;
                }
        }
    }

    if (e->v_odd)
    {
        if (e->start_odd < e->end_odd &&
            !(e->start_odd >= 0x80000000 && e->end_odd < 0xC0000000) &&
            e->phys_odd < 0x20000000)
        {
            for (i=e->start_odd;i<e->end_odd;i+=0x1000)
            {
                tlb->LUT_r[i>>12] = UINT32_C(0x80000000) | (e->phys_odd + (i - e->start_odd) + 0xFFF);
                tlb->r_cached[i>>12] = e->c_odd != 2;
            }
            if (e->d_odd)
                for (i=e->start_odd;i<e->end_odd;i+=0x1000)
                {
                    tlb->LUT_w[i>>12] = UINT32_C(0x80000000) | (e->phys_odd + (i - e->start_odd) + 0xFFF);
                    tlb->w_cached[i>>12] = e->c_odd != 2;
                }
        }
    }
}

uint32_t virtual_to_physical_address(struct r4300_core* r4300, uint32_t address, int w, uint8_t *cached)
{
    const struct tlb* tlb = &r4300->cp0.tlb;
    unsigned int addr = address >> 12;

#ifdef NEW_DYNAREC
    if (r4300->emumode == EMUMODE_DYNAREC)
    {
        intptr_t map = r4300->new_dynarec_hot_state.memory_map[addr];
        if ((tlb->LUT_w[addr]) && (w == 1))
        {
            assert(map == (((uintptr_t)r4300->rdram->dram + (uintptr_t)((tlb->LUT_w[addr] & 0xFFFFF000) - 0x80000000) - (address & 0xFFFFF000)) >> 2));
        }
        else if ((tlb->LUT_r[addr]) && (w == 0))
        {
            assert((map&~WRITE_PROTECT) == (((uintptr_t)r4300->rdram->dram + (uintptr_t)((tlb->LUT_r[addr] & 0xFFFFF000) - 0x80000000) - (address & 0xFFFFF000)) >> 2));
            if (map & WRITE_PROTECT)
            {
                assert(tlb->LUT_w[addr] == 0);
            }
        }
        else {
            assert(map < 0);
        }
    }
#endif

    if (w == 1)
    {
        if (tlb->LUT_w[addr])
        {
            *cached = tlb->w_cached[addr];
            return (tlb->LUT_w[addr] & UINT32_C(0xFFFFF000)) | (address & UINT32_C(0xFFF));
        }
    }
    else
    {
        if (tlb->LUT_r[addr])
        {
            *cached = tlb->r_cached[addr];
            return (tlb->LUT_r[addr] & UINT32_C(0xFFFFF000)) | (address & UINT32_C(0xFFF));
        }
    }
    //printf("tlb exception !!! @ %x, %x, add:%x\n", address, w, r4300->pc->addr);
    //getchar();

    TLB_refill_exception(r4300, address, w);

    //return 0x80000000;
    return 0x00000000;
}
