XBURST = {}

function XBURST.read_cp0(reg, sel)
    return DEV.read32_cop({0, reg, sel})
end

function XBURST.write_cp0(reg, sel, val)
    DEV.write32_cop({0, reg, sel}, val)
end

XBURST.prid_table = {
    [0x0ad0024f] = "JZ4740",
    [0x1ed0024f] = "JZ4755",
    [0x2ed0024f] = "JZ4760(B)",
    [0x3ee1024f] = "JZ4780"
}

XBURST.at_table = {
    [0] = "MIPS32",
    [1] = "MIPS64 with 32-bit segments",
    [2] = "MIPS64"
}

XBURST.ar_table = {
    [0] = "Release 1",
    [1] = "Release 2 (or more)"
}

XBURST.mt_table = {
    [0] = "None",
    [1] = "Standard TLB",
    [2] = "BAT",
    [3] = "Fixed Mapping",
    [4] = "Dual VTLB and FTLB"
}

XBURST.is_table = {
    [0] = 64,
    [1] = 128,
    [2] = 256,
    [3] = 512,
    [4] = 1024,
    [5] = 2048,
    [6] = 4096,
    [7] = 32
}

XBURST.il_table = {
    [0] = 0,
    [1] = 4,
    [2] = 8,
    [3] = 16,
    [4] = 32,
    [5] = 64,
    [6] = 128
}

function XBURST.get_table_or(tbl, index, dflt)
    if tbl[index] ~= nil then
        return tbl[index]
    else
        return dflt
    end
end

function XBURST.do_ebase_test()
    XBURST.write_cp0(15, 1, 0x80000000)
    print(string.format("    Value after writing 0x80000000: 0x%x", XBURST.read_cp0(15, 1)))
    if XBURST.read_cp0(15, 1) ~= 0x80000000 then
        return "Value 0x8000000 does not stick, EBASE is probably not working"
    end
    XBURST.write_cp0(15, 1, 0x80040000)
    print(string.format("    Value after writing 0x80040000: 0x%x", XBURST.read_cp0(15, 1)))
    if XBURST.read_cp0(15, 1) ~= 0x80040000 then
        return "Value 0x80040000 does not stick, EBASE is probably not working"
    end
    return "EBase seems to work"
end

function XBURST.do_ebase_cfg7gate_test()
    -- test gate in config7
    config7_old = XBURST.read_cp0(16, 7)
    XBURST.write_cp0(16, 7, bit32.replace(config7_old, 0, 7)) -- disable EBASE[30] modification
    print(string.format("    Disable config7 gate: write 0x%x to Config7", bit32.replace(config7_old, 0, 7)))
    XBURST.write_cp0(15, 1, 0xfffff000)
    print(string.format("    Value after writing 0xfffff000: 0x%x", XBURST.read_cp0(15, 1)))
    if XBURST.read_cp0(15, 1) == 0xfffff000 then
        return "Config7 gate has no effect but modifications are allowed anyway"
    end
    XBURST.write_cp0(16, 7, bit32.replace(config7_old, 1, 7)) -- enable EBASE[30] modification
    print(string.format("    Enable config7 gate: write 0x%x to Config7", bit32.replace(config7_old, 1, 7)))
    XBURST.write_cp0(15, 1, 0xc0000000)
    print(string.format("    Value after writing 0xc0000000: 0x%x", XBURST.read_cp0(15, 1)))
    if XBURST.read_cp0(15, 1) ~= 0xc0000000 then
        return "Config7 gate does not work"
    end
    XBURST.write_cp0(16, 7, config7_old)
    return "Config7 gate seems to work"
end

function XBURST.do_ebase_exc_test(mem_addr)
    if (mem_addr % 0x1000) ~= 0 then
        return "  memory address for exception test must aligned on a 0x1000 boundary";
    end
    print(string.format("Exception test with EBASE at 0x%x...", mem_addr))
    print("  Writing instructions to memory")
    -- create instructions in memory
    exc_addr = mem_addr + 0x180 -- general exception vector
    data_addr = mem_addr + 0x300
    -- lui k0,<low part of data_addr>
    -- ori     k0,k0,<high part>
    DEV.write32(exc_addr + 0, 0x3c1a0000 + bit32.rshift(data_addr, 16))
    DEV.write32(exc_addr + 4, 0x375a0000 + bit32.band(data_addr, 0xffff))
    -- lui     k1,0xdead
    -- ori     k1,k1,0xbeef
    DEV.write32(exc_addr + 8, 0x3c1bdead)
    DEV.write32(exc_addr + 12, 0x377bbeef)
    -- sw      k1,0(k0)
    DEV.write32(exc_addr + 16, 0xaf5b0000)
    -- mfc0    k0,c0_epc
    -- addi    k0,k0,4
    -- mtc0    k0,c0_epc
    DEV.write32(exc_addr + 20, 0x401a7000)
    DEV.write32(exc_addr + 24, 0x235a0004)
    DEV.write32(exc_addr + 28, 0x409a7000)
    -- eret
    -- nop
    DEV.write32(exc_addr + 32, 0x42000018)
    DEV.write32(exc_addr + 36, 0)
    -- fill data with some initial value
    DEV.write32(data_addr, 0xcafebabe)
    -- write instructions to trigger an interrupt
    bug_addr = mem_addr
    -- syscall
    DEV.write32(bug_addr + 0, 0x0000000c)
    -- jr   ra
    -- nop
    DEV.write32(bug_addr + 4, 0x03e00008)
    DEV.write32(bug_addr + 8, 0)

    -- make sure we are the right shape for the test: SR should have BEV cleared,
    -- mask all interrupts, enable interrupts
    old_sr = XBURST.read_cp0(12, 0)
    print(string.format("  Old SR: 0x%x", old_sr))
    XBURST.write_cp0(12, 0, 0xfc00) -- BEV set to 0, all interrupts masked and interrupt disabled
    print(string.format("  New SR: 0x%x", XBURST.read_cp0(12, 0)))
    -- change EBASE
    old_ebase = XBURST.read_cp0(15, 1)
    XBURST.write_cp0(15, 1, mem_addr)
    print(string.format("  EBASE: %x", XBURST.read_cp0(15, 1)))
    -- test
    print(string.format("  Before: %x", DEV.read32(data_addr)))
    DEV.call(bug_addr)
    print(string.format("  After: %x", DEV.read32(data_addr)))
    success = DEV.read32(data_addr) == 0xdeadbeef
    -- restore SR and EBASE
    XBURST.write_cp0(12, 0, old_sr)
    XBURST.write_cp0(15, 1, ebase_old)

    return success and "Exception and EBASE are working" or "Exception and EBASE are NOT working"
end

function XBURST.test_ebase(mem_addr)
    -- EBase
    ebase_old = XBURST.read_cp0(15, 1)
    sr_old = XBURST.read_cp0(12, 0)
    print("Testing EBASE...")
    print("  Disable BEV")
    XBURST.write_cp0(12, 0, bit32.replace(sr_old, 0, 22)) -- clear BEV
    print(string.format("  SR value: 0x%x", XBURST.read_cp0(12, 0)))
    print(string.format("  EBASE value: 0x%x", ebase_old))
    print("  Test result: " .. XBURST.do_ebase_test())
    print("  Config7 result: " .. XBURST.do_ebase_cfg7gate_test())
    XBURST.write_cp0(12, 0, sr_old)
    XBURST.write_cp0(15, 1, ebase_old)
    -- now try with actual exceptions
    if mem_addr == nil then
        print("  Not doing exception test, please specify memory to use: sram, ram")
        return
    end
    print("  Exception result: " .. XBURST.do_ebase_exc_test(mem_addr))
end

function XBURST.test_ext_inst(mem_addr)
    data_addr = mem_addr + 0x80
    -----------
    -- test ext
    -----------
    for pos = 0, 31 do
        for size = 1, 32 - pos do
            -- lui     v0,<low part of data_addr>
            -- addiu   v0,v0,<high part>
            -- lw      v1,0(v0)
            DEV.write32(mem_addr + 0, 0x3c020000 + bit32.rshift(data_addr, 16))
            DEV.write32(mem_addr + 4, 0x8c430000 + bit32.band(data_addr, 0xffff))
            DEV.write32(mem_addr + 8, 0x8c430000)
            -- ext     v1, v1, pos, size
            DEV.write32(mem_addr + 12, 0x7c630000 + bit32.rshift(size - 1, 11) + bit32.rshift(pos, 6))
            -- sw      v1,0(v0)
            DEV.write32(mem_addr + 16, 0xac430000)
            -- jr   ra
            -- nop
            DEV.write32(mem_addr + 20, 0x03e00008)
            DEV.write32(mem_addr + 24, 0)
            -- write some random data
            data = math.random(0xffffffff)
            print(string.format("  data: %x", data))
            DEV.write32(data_addr, data)
            DEV.call(mem_addr)
            ext_data = DEV.read32(data_addr)
            print(string.format("  result: %x vs %x", ext_data, bit32.extract(data, pos, size)))
            break
        end
        break
    end
end

function XBURST.test_ei_di_inst(mem_addr)
    -- save SR and disable interrupts
    old_sr = XBURST.read_cp0(12, 0)
    XBURST.write_cp0(12, 0, bit32.replace(old_sr, 0, 0)) -- clear EI
    print("Testing ei")
    print("  Test SR")
    print("    Enable interrupts with CP0")
    XBURST.write_cp0(12, 0, bit32.replace(old_sr, 1, 0)) -- set EI
    print(string.format("    SR: 0x%x", XBURST.read_cp0(12, 0)))
    print("    Disable interrupts with CP0")
    XBURST.write_cp0(12, 0, bit32.replace(old_sr, 0, 0)) -- clear EI
    print(string.format("    SR: 0x%x", XBURST.read_cp0(12, 0)))

    print("  Test ei/di")
    print("    Enable interrupts with ei")
    -- ei
    -- jr   ra
    -- nop
    DEV.write32(mem_addr + 4, 0x41606020)
    DEV.write32(mem_addr + 4, 0x03e00008)
    DEV.write32(mem_addr + 8, 0)
    DEV.call(mem_addr)
    print(string.format("    SR: 0x%x", XBURST.read_cp0(12, 0)))
    print("    Disable interrupts with di")
    -- di
    DEV.write32(mem_addr + 4, 0x41606000)
    DEV.call(mem_addr)
    print(string.format("    SR: 0x%x", XBURST.read_cp0(12, 0)))

    -- restore SR
    XBURST.write_cp0(old_sr)
end

function XBURST.init()
    -- enable CP1 in SR
    sr_old = XBURST.read_cp0(12, 0)
    XBURST.write_cp0(12, 0, bit32.replace(sr_old, 1, 29)) -- set CU1
    print("XBurst:")
    -- PRId
    XBURST.prid = XBURST.read_cp0(15, 0)
    print(string.format("  PRId: 0x%x", XBURST.prid))
    print("    CPU: " .. XBURST.get_table_or(XBURST.prid_table, XBURST.prid, "unknown"))
    -- Config
    XBURST.config = XBURST.read_cp0(16, 0)
    print(string.format("  Config: 0x%x", XBURST.config))
    print("    Architecture Type: " .. XBURST.get_table_or(XBURST.at_table,
        bit32.extract(XBURST.config, 13, 2), "unknown"))
    print("    Architecture Level: " .. XBURST.get_table_or(XBURST.ar_table,
        bit32.extract(XBURST.config, 10, 3), "unknown"))
    print("    MMU Type: " .. XBURST.get_table_or(XBURST.mt_table,
        bit32.extract(XBURST.config, 7, 3), "unknown"))
    -- Config1
    XBURST.config1 = XBURST.read_cp0(16, 1)
    print(string.format("  Config1: 0x%x", XBURST.config1))
    -- don't print of no MMU
    if bit32.extract(XBURST.config, 7, 3) ~= 0 then
        print(string.format("    MMU Size: %d", bit32.extract(XBURST.config1, 25, 6) + 1))
    end
    print("    ICache")
    print("      Sets per way: " .. XBURST.get_table_or(XBURST.is_table,
        bit32.extract(XBURST.config1, 22, 3), "unknown"))
    print("      Ways: " .. (1 + bit32.extract(XBURST.config1, 16, 3)))
    print("      Line size: " .. XBURST.get_table_or(XBURST.il_table,
        bit32.extract(XBURST.config1, 19, 3), "unknown"))
    print("    DCache")
    print("      Sets per way: " .. XBURST.get_table_or(XBURST.is_table,
        bit32.extract(XBURST.config1, 13, 3), "unknown"))
    print("      Ways: " .. (1 + bit32.extract(XBURST.config1, 7, 3)))
    print("      Line size: " .. XBURST.get_table_or(XBURST.il_table,
        bit32.extract(XBURST.config1, 10, 3), "unknown"))
    print("    FPU: " .. (bit32.extract(XBURST.config1, 0) == 1 and "yes" or "no"))

    -- Config 2
    XBURST.config2 = XBURST.read_cp0(16, 2)
    print(string.format("  Config2: 0x%x", XBURST.config2))

    -- Config 3
    XBURST.config3 = XBURST.read_cp0(16, 3)
    print(string.format("  Config3: 0x%x", XBURST.config3))
    print("    Vectored interrupt: " .. (bit32.extract(XBURST.config2, 5) and "yes" or "no"))

    -- Config 7
    XBURST.config7 = XBURST.read_cp0(16, 7)
    print(string.format("  Config7: 0x%x", XBURST.config7))

    -- restore SR
    XBURST.write_cp0(12, 0, sr_old)
end
