/*
 * crash.c
 *
 * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <common.h>
#include <exception.h>
#include <process.h>
#include <module.h>
#include <memory.h>
#include <log.h>
#include <exec/elf.h>

#define MAX_BACKTRACE 256

/* The background is a fabulous compromise between blue and red */
#define CRASH_SCREEN_ATTRIBUTE 0x5F

extern bool_t video_initialized;

extern const char *lookup_kernel_symbol_name(uintptr_t address);

static char screen[TEXT_WIDTH * TEXT_HEIGHT + 1] = { 0 };
static const char *screen_template[TEXT_HEIGHT] = {
    "",
    "                              >>> KERNEL CRASH <<<",
    "",
    " A fatal error has occurred in the Monolithium kernel.  The system cannot",
    " continue from this point in a reliable manner, and has been shut down.",
    " Please contact the developers so that similar issues can be prevented or",
    " avoided in the future.",
    "",
    " Error: %.71s",
    " %.78s",
    " In %s line %u",
    " Modules involved: %.60s",
    "",
    " If serial logging is enabled, more information might be available in the log.",
};

void __attribute__((noreturn)) kernel_crash(const char *message, registers_t *regs, const char *filename, int line)
{
    int i;
    word_t *video_mem = (word_t*)(video_initialized ? VIDEO_MEMORY : TEXT_VIDEO_MEMORY);
    cpu_disable_interrupts();

    uintptr_t call_stack[MAX_BACKTRACE];
    int call_count = 0;
    uintptr_t *frame_pointer;
    asm volatile ("movl %%ebp, %0" : "=r"(frame_pointer) ::);

    while (frame_pointer && call_count < MAX_BACKTRACE)
    {
        if (!get_physical_address(&frame_pointer[1])) break;
        call_stack[call_count++] = frame_pointer[1];
        if (!get_physical_address(&frame_pointer[0])) break;
        frame_pointer = (uintptr_t*)frame_pointer[0];
    }

    for (i = 0; i < TEXT_HEIGHT; i++)
    {
        if (!screen_template[i]) continue;

        switch (i)
        {
        case 8:
            sprintf(&screen[i * TEXT_WIDTH], screen_template[i], message);
            break;

        case 9:
            if (strlen(message) > 71) sprintf(&screen[i * TEXT_WIDTH], screen_template[i], &message[71]);
            break;

        case 10:
            sprintf(&screen[i * TEXT_WIDTH], screen_template[i], filename, line);
            break;

        case 11:
            {
                int module_count = 0, j, k;
                const char *module_list[call_count];
                char modules[61] = "";

                for (j = 0; j < call_count; j++)
                {
                    module_t *module = get_module_from_address((void*)call_stack[j]);
                    if (!module) continue;

                    bool_t found = FALSE;
                    for (k = 0; k < module_count; k++)
                    {
                        if (strcmp(module_list[k], module->name) == 0)
                        {
                            found = TRUE;
                            break;
                        }
                    }

                    if (found) continue;
                    module_list[module_count++] = module->name;
                    if (module_count) strncat(modules, ", ", sizeof(modules) - strlen(modules) - 1);
                    strncat(modules, module_list[j], sizeof(modules) - strlen(modules) - 1);
                }

                if (!strlen(modules)) sprintf(modules, "(none)");
                sprintf(&screen[i * TEXT_WIDTH], screen_template[i], modules);
            }
            break;

        default:
            strcpy(&screen[i * TEXT_WIDTH], screen_template[i]);
        }
    }
 
    for (i = 0; i < TEXT_WIDTH * TEXT_HEIGHT; i++) video_mem[i] = (CRASH_SCREEN_ATTRIBUTE << 8) | screen[i];

    log_write(LOG_CRITICAL, "******************************\n");
    log_write(LOG_CRITICAL, "*        ATTENTION!!!        *\n");
    log_write(LOG_CRITICAL, "******************************\n");
    log_write(LOG_CRITICAL, "The kernel crashed, specifying the following message:\n");
    log_write(LOG_CRITICAL, "%s\n", message);
    log_write(LOG_CRITICAL, "Crash requested in file %s line %u\n", filename, line);

    thread_t *thread = get_current_thread();

    if (thread)
    {
        log_write(LOG_CRITICAL, "Context: thread %u", thread->tid);

        if (thread->owner_process)
        {
            log_write(LOG_CRITICAL, ", attached to process %u (%s).\n", thread->owner_process->pid, thread->owner_process->name);
        }
        else
        {
            log_write(LOG_CRITICAL, ", not attached to any process.\n");
        }
    }
    else
    {
        log_write(LOG_CRITICAL, "No context available.\n");
    }

    if (regs)
    {
        const char *flag_names[32] = {
            "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF",
            "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL,
            "RF", "VM", "AC", "VIF", "VIP", "ID"
        };

        log_write(LOG_CRITICAL, "Machine state:\n");
        log_write(LOG_CRITICAL, "EAX: 0x%08X\n", regs->eax);
        log_write(LOG_CRITICAL, "ECX: 0x%08X\n", regs->ecx);
        log_write(LOG_CRITICAL, "EDX: 0x%08X\n", regs->edx);
        log_write(LOG_CRITICAL, "EBX: 0x%08X\n", regs->ebx);
        log_write(LOG_CRITICAL, "ESP: 0x%08X\n", regs->esp);
        log_write(LOG_CRITICAL, "EBP: 0x%08X\n", regs->ebp);
        log_write(LOG_CRITICAL, "ESI: 0x%08X\n", regs->eax);
        log_write(LOG_CRITICAL, "EDI: 0x%08X\n", regs->eax);
        log_write(LOG_CRITICAL, "EIP: 0x%08X\n", regs->eip);
        log_write(LOG_CRITICAL, "Code: 0x%04X\n", regs->cs);
        log_write(LOG_CRITICAL, "Data: 0x%04X\n", regs->data_selector);
        log_write(LOG_CRITICAL, "EFLAGS: %08X ( ", regs->eflags);

        for (i = 0; i < 32; i++)
        {
            if (flag_names[i] && regs->eflags & (1 << i))
            {
                log_write(LOG_CRITICAL, "%s ", flag_names[i]);
            }
        }

        log_write(LOG_CRITICAL, ")\n");

        log_write(LOG_CRITICAL, "CR0: 0x%08X\n", cpu_read_master_control_register());
        log_write(LOG_CRITICAL, "CR2: 0x%08X\n", cpu_read_faulting_address());
        log_write(LOG_CRITICAL, "CR3: 0x%08X\n", cpu_read_page_table_register());
        log_write(LOG_CRITICAL, "CR4: 0x%08X\n", cpu_read_feature_register());
    }
    else
    {
        log_write(LOG_CRITICAL, "No machine state available.\n");
    }

    log_write(LOG_CRITICAL, "Backtrace:\n");

    for (i = 0; i < call_count; i++)
    {
        log_write(LOG_CRITICAL, "0x%08X", call_stack[i]);
        module_t *module = get_module_from_address((void*)call_stack[i]);
        extern int _end;

        if (module)
        {
            log_write(LOG_CRITICAL, " in module %s", module->name);
        }
        else if (call_stack[i] >= KERNEL_AREA_START && call_stack[i] < (uintptr_t)&_end)
        {
            log_write(LOG_CRITICAL, " in the kernel");

            const char *function_name = lookup_kernel_symbol_name(call_stack[i]);
            if (function_name) log_write(LOG_CRITICAL, ", function %s", function_name);
        }

        log_write(LOG_CRITICAL, "\n");
    }

    cpu_halt();
    for(;;);
}
