/*
*   This file is part of Luma3DS
*   Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
*   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 3 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, see <http://www.gnu.org/licenses/>.
*
*   Additional Terms 7.b and 7.c of GPLv3 apply to this file:
*       * Requiring preservation of specified reasonable legal notices or
*         author attributions in that material or in the Appropriate Legal
*         Notices displayed by works containing it.
*       * Prohibiting misrepresentation of the origin of that material,
*         or requiring that modified versions of such material be marked in
*         reasonable ways as different from the original version.
*/

#include "kernel_extension.h"
#include "kernel_extension_setup.h"

#define MPCORE_REGS_BASE        ((u32)PA_PTR(0x17E00000))
#define MPCORE_GID_REGS_BASE    (MPCORE_REGS_BASE + 0x1000)
#define MPCORE_GID_SGI          (*(vu32 *)(MPCORE_GID_REGS_BASE + 0xF00))

struct Parameters
{
    void (*SGI0HandlerCallback)(struct Parameters *, u32 *);
    void *interruptManager;
    u32 *L2MMUTable; // bit31 mapping

    void (*initFPU)(void);
    void (*mcuReboot)(void);
    void (*coreBarrier)(void);

    u32 TTBCR;
    u32 L1MMUTableAddrs[4];

    u32 kernelVersion;

    struct CfwInfo
    {
        char magic[4];

        u8 versionMajor;
        u8 versionMinor;
        u8 versionBuild;
        u8 flags;

        u32 commitHash;

        u32 config;
    } __attribute__((packed)) info;
};


static void K_SGI0HandlerCallback(volatile struct Parameters *p)
{
    u32 L1MMUTableAddr;
    vu32 *L1MMUTable;
    u32 coreId;

    __asm__ volatile("cpsid aif"); // disable interrupts

    p->coreBarrier();

    __asm__ volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(coreId));
    coreId &= 3;

    __asm__ volatile("mrc p15, 0, %0, c2, c0, 1" : "=r"(L1MMUTableAddr));
    L1MMUTableAddr &= ~0x3FFF;
    p->L1MMUTableAddrs[coreId] = L1MMUTableAddr;
    L1MMUTable = (vu32 *)(L1MMUTableAddr | (1 << 31));

    // Actually map the kernel ext
    u32 L2MMUTableAddr = (u32)(p->L2MMUTable) & ~(1 << 31);
    L1MMUTable[0x40000000 >> 20] = L2MMUTableAddr | 1;

    __asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 4" :: [val] "r" (0) : "memory");
    ((void (*)(volatile struct Parameters *))0x40000000)(p);

    p->coreBarrier();
}

static u32 ALIGN(0x400) L2MMUTableFor0x40000000[256] = { 0 };
u32 TTBCR;
static void K_ConfigureSGI0(void)
{
    // see /patches/k11MainHook.s
    u32 *off;
    u32 *initFPU, *mcuReboot, *coreBarrier;

    // Search for stuff in the 0xFFFF0000 page
    for(initFPU = (u32 *)0xFFFF0000; initFPU < (u32 *)0xFFFF1000 && *initFPU != 0xE1A0D002; initFPU++);
    initFPU += 3;

    for(mcuReboot = initFPU; mcuReboot < (u32 *)0xFFFF1000 && *mcuReboot != 0xE3A0A0C2; mcuReboot++);
    mcuReboot--;
    coreBarrier = (u32 *)decodeARMBranch(mcuReboot - 4);

    for(off = mcuReboot; off < (u32 *)0xFFFF1000 && *off != 0x726C6468; off++); // "hdlr"

    volatile struct Parameters *p = (struct Parameters *)PA_FROM_VA_PTR(off); // Caches? What are caches?
    p->SGI0HandlerCallback = (void (*)(struct Parameters *, u32 *))PA_FROM_VA_PTR(K_SGI0HandlerCallback);
    p->L2MMUTable = (u32 *)PA_FROM_VA_PTR(L2MMUTableFor0x40000000);
    p->initFPU = (void (*) (void))initFPU;
    p->mcuReboot = (void (*) (void))mcuReboot;
    p->coreBarrier = (void (*) (void))coreBarrier;

    __asm__ volatile("mrc p15, 0, %0, c2, c0, 2" : "=r"(TTBCR));
    p->TTBCR = TTBCR;

    p->kernelVersion = *(vu32 *)0x1FF80000;

    // Now let's configure the L2 table

    //4KB extended small pages: [SYS:RW USR:-- X  TYP:NORMAL SHARED OUTER NOCACHE, INNER CACHED WB WA]
    for(u32 offset = 0; offset < kernel_extension_size; offset += 0x1000)
        L2MMUTableFor0x40000000[offset >> 12] = (u32)convertVAToPA(kernel_extension + offset) | 0x516;
}

static void K_SendSGI0ToAllCores(void)
{
    MPCORE_GID_SGI = 0xF0000; // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360f/CACGDJJC.html
}

static inline void flushAllCaches(void)
{
    svcUnmapProcessMemory(CUR_PROCESS_HANDLE, 0, 0); // this SVC flush both caches entirely (and properly) even when returing an error
}

void installKernelExtension(void)
{
    svc0x2F(K_ConfigureSGI0);
    flushAllCaches();
    svc0x2F(K_SendSGI0ToAllCores);
    flushAllCaches();
}