//******************************************************************************
//
//  KS10 Console Microcontroller
//
//! Console Program
//!
//! This is the main program.  This code barely attempts to be compatible with
//! the DEC KS10 console code.
//!
//! \file
//!    console.cpp
//!
//! \author
//!    Rob Doyle - doyle (at) cox (dot) net
//
//******************************************************************************
//
// Copyright (C) 2013 Rob Doyle
//
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//******************************************************************************

#include <stdint.h>

#include "sd.h"
#include "fpga.h"
#include "uart.h"
#include "stdio.h"
#include "ks10.hpp"
#include "commands.hpp"
#include "driverlib/rom.h"
#include "driverlib/gpio.h"
#include "driverlib/inc/hw_types.h"
#include "driverlib/inc/hw_memmap.h"
#include "driverlib/inc/hw_sysctl.h"

bool ctyMode = true;


//
//! Hang forever
//

void die(const char * msg) {
    if (!msg) {
        printf(msg);
    }
    printf("KS10> Fatal Error.  Unable to continue.\n");
    ROM_IntMasterDisable();
#if 1
    for (;;) {
        ;
    }
#endif
}

//
//! Print Halt Status
//!
//! This function prints the Halt Status Word and the Halt Status Block.
//!
//! \param haltStatusWord
//!     Halt Status Word from memory
//!
//! \param haltStatusBlock
//!     Halt Status Block from memory
//

void printHaltStatus(const ks10_t::haltStatusWord_t &haltStatusWord,
                    const ks10_t::haltStatusBlock_t &haltStatusBlock) {
    printf("Halt Cause: %s\n"
           "PC  is %012llo     MAG is %012llo\n"
           "PC  is %012llo     HR  is %012llo\n"
           "AR  is %012llo     ARX is %012llo\n"
           "BR  is %012llo     BRX is %012llo\n"
           "ONE is %012llo     EBR is %012llo\n"
           "UBR is %012llo     MSK is %012llo\n"
           "FLG is %012llo     PI  is %012llo\n"
           "X1  is %012llo     TO  is %012llo\n"
           "T1  is %012llo     VMA is %012llo\n"
           "FE  is %012llo                   \n",
           (haltStatusWord.status == 00000 ? "Microcode Startup."                     :
            (haltStatusWord.status == 00001 ? "Halt Instruction."                     :
             (haltStatusWord.status == 00002 ? "Console Halt."                        :
              (haltStatusWord.status == 00100 ? "IO Page Failure."                    :
               (haltStatusWord.status == 00101 ? "Illegal Interrupt Instruction."     :
                (haltStatusWord.status == 00102 ? "Pointer to Unibus Vector is zero." :
                 (haltStatusWord.status == 01000 ? "Illegal Microcode Dispatch."      :
                  (haltStatusWord.status == 01005 ? "Microcode Startup Check Failed." :
                   "Unknown.")))))))),
           haltStatusWord.pc,
           haltStatusBlock.mag,
           haltStatusBlock.pc,
           haltStatusBlock.hr,
           haltStatusBlock.ar,
           haltStatusBlock.arx,
           haltStatusBlock.br,
           haltStatusBlock.brx,
           haltStatusBlock.one,
           haltStatusBlock.ebr,
           haltStatusBlock.ubr,
           haltStatusBlock.mask,
           haltStatusBlock.flg,
           haltStatusBlock.pi,
           haltStatusBlock.x1,
           haltStatusBlock.t0,
           haltStatusBlock.t1,
           haltStatusBlock.vma,
           haltStatusBlock.fe);
}

//
//! The protocol for transferring a character from the KS10 to the Console is
//! as follows:
//! -#  The KS10 places a character with the Valid flag set in memory location
//!     0000032.
//! -#  The KS10 interrupts the Console Processor.
//! -#  The Console Processor is interrupted.
//! -#  The Console Processor extracts the character from location 000032 bits
//!     28-35.  This character is output on the CTY serial output port.
//! -#  The Console Processor zeros location 000032.  (Zeros Valid bit).
//! -#  The Console Processor Interrupts the KS10 by setting the KS10_INTR bit
//!     in the Console Control/Status Register.
//

void CTYinterrupt(void) {

    static const ks10_t::addr_t ctyOutAddr  = 000032;
    static const ks10_t::data_t ctyOutValid = 000400;
    static const ks10_t::data_t ctyOutMask  = 000177;

    //
    // See if there is data from the KS10 to output on the CTY port.
    //

    ks10_t::data_t ctyOutData = ks10_t::readMem(ctyOutAddr);
    if (ctyOutData & ctyOutValid) {
        putchar(ctyOutData & ctyOutMask);
        ks10_t::writeMem(ctyOutAddr, 0);
        ks10_t::cpuIntr();
    }

}

//
//! The main program.
//!
//! This is where everything starts.  The STDIO devices should have been
//! initialized by a static constructor so STDIO should be available by the
//! time main() is executed.
//!
//! \post
//!     The main() program should never exit.
//

void main(void) {

    //
    // Print boot message
    //

    printf("\n");
    printf("KS10> Console Booted...\n");
    printf("KS10> CPU device identifier is 0x%08lx.\n", HWREG(SYSCTL_DID0));

    //
    // Device Rev C3 is required for the ROM functions.
    //

    if (!REVISION_IS_C3) {
        die("KS10> Unsupported processor revision.\n");
    }

    //
    // Enable interrupts
    //

    ROM_IntMasterEnable();

    //
    // Load firmware into the FPGA
    //

    bool success = programFPGA();
    if (!success) {
        die("");
    }

    //
    // Initialize the KS10 Interface
    //

    ks10_t ks10(CTYinterrupt);

    //
    // Attempt to communicate with KS10 FPGA
    //
    //  Read the FPGA firmware revision register.  The FPGA should respond with
    //  "REVxx.yy" where xx is the major revision and yy is the minor revision.
    //
    //  If this fails, the FPGA is not programmed or the bus connection is
    //  broken.  Either way, there is no reason to continue.
    //

    const char *buf = ks10_t::getFirmwareRev();
    if ((buf[0] == 'R') && (buf[1] == 'E') &&
        (buf[2] == 'V') && (buf[5] == 0xae)) {
        printf("KS10> FPGA firmware is %c%c%c %c%c%c%c%c\n",
               buf[0] & 0x7f, buf[1] & 0x7f, buf[2] & 0x7f, buf[3] & 0x7f,
               buf[4] & 0x7f, buf[5] & 0x7f, buf[6] & 0x7f, buf[7] & 0x7f);
    } else {
        die("KS10> Unable to communicate with the KS10 FPGA.\n");
    }

#if 0

    success = ks10_t::testRegs();
    if (!success) {
        die("KS10> Problem accessing KS10 control registers.\n");
    }

#endif

    ks10_t::cacheEnable(true);
    ks10_t::trapEnable(true);
    ks10_t::timerEnable(true);

    //
    // Unreset the CPU
    //

    if (!ks10_t::cpuReset()) {
        die("KS10 should be reset.\n");
    }

    if (ks10_t::halt()) {
        die("KS10 should not be halted.\n");
    }

    if (ks10_t::run()) {
        die("KS10 should not be running.\n");
    }

    ks10_t::cpuReset(false);

    if (ks10_t::cpuReset()) {
        die("KS10 should be unreset.\n");
    }

    if (ks10_t::halt()) {
        die("KS10 should not be halted.\n");
    }

    if (ks10_t::run()) {
        die("KS10 should not be running.\n");
    }

    //
    // Wait for the KS10 to peform a selftest and initialize the ALU.  When
    // the microcode initialization is completed, the KS10 will enter a HALT
    // state.  Wait for that to occur.   Timeout if there is a problem.
    //

    success = ks10_t::waitHalt();
    if (!success) {
        die("KS10> Timeout waiting for KS10 to initialize.\n");
    }

    //
    // See if a SD Card is inserted.  If so, initialize it and mount the
    // FAT32 filesystem
    //

    sdCardDetect(true);

    //
    // Prepare for CTY mode
    //

    bool lastHalt = false;

    printf("KS10> ");

    for (;;) {

        if (ctyMode) {

            //
            // CTY Mode processing
            //

            char *buf;
            if (getLine(&buf)) {
                parseCMD(buf);
                printf("KS10> ");
            }

            //
            // See if KS10 state just changed.
            //

            bool halt = ks10_t::halt();
            if (halt && !lastHalt) {

                //
                // Just halted. Print the halt status.
                //

                printf("\nKS10> Halted\n");
                printHaltStatus(ks10_t::getHaltStatusWord(),
                                ks10_t::getHaltStatusBlock(0376000));
                printf("KS10> ");

            } else if (!halt && lastHalt) {

                //
                // Just started.  Enter user mode.
                //

                ctyMode = false;
                printf("\n");

            }
            lastHalt = halt;

        } else {

            //
            // USER mode processing
            //

            static const ks10_t::addr_t ctyInAddr   = 000031;
            static const ks10_t::data_t ctyInValid  = 000400;
            static const ks10_t::data_t ctyInMask   = 000177;

            //
            // See if there is data on the CTY port to send to the KS10.
            //

            ks10_t::data_t ctyInData = ks10_t::readMem(ctyInAddr);
            if (!rxEmpty() && !(ctyInData & ctyInMask)) {
                ks10_t::writeMem(ctyInAddr, ctyInValid | getUART());
                ks10_t::cpuIntr();
            }

            //
            // Enter Console Mode if KS10 halts
            //

            if (ks10_t::halt()) {
                ctyMode = true;
            }
        }
    }
}
