/****************************************************************************
*                                                                           *
*                            Third Year Project                             *
*                                                                           *
*                            An IBM PC Emulator                             *
*                          For Unix and X Windows                           *
*                                                                           *
*                             By David Hedley                               *
*                                                                           *
*                                                                           *
* This program is Copyrighted.  Consult the file COPYRIGHT for more details *
*                                                                           *
****************************************************************************/

/* This is HARDWARE.C It contains stuff about the PC hardware (chips etc) */

#include "global.h"

#include <stdio.h>
#ifdef AMIGA
#include <stdlib.h>
#include <dos.h>
#else
#include <sys/time.h>
#endif
#include <signal.h>

#ifdef AMIGA
#include <exec/exec.h>
#include <dos/dos.h>
#include <devices/timer.h>
#include <proto/exec.h>
#include <proto/dos.h>
#endif

#include "hardware.h"
#include "cpu.h"
#include "vgahard.h"

#ifdef DEBUGGER
#    include "debugger.h"
#endif

#ifndef AMIGA
static double ticks_per_second = TICKSPERSEC;
static struct itimerval timer;    
#endif

static volatile int disable_int;
static volatile int timer_blocked = 1;

#define PORT60SIZE 5

static UBYTE port60buffer[PORT60SIZE];
static int port60start, port60end;

static unsigned PIC_inservice;
static unsigned PIC_irq;
static unsigned PIC_mask = ~(PIC_TIMER|PIC_KEYBOARD);

static unsigned timer_tmp;

static unsigned VGA_status;

static void PIC_interrupt(void)
{
    if (PIC_inservice)
    {
        D2(printf("PIC still blocked\n"););
        return;
    }

    if (PIC_irq & ~PIC_mask & PIC_TIMER)
    {
        D2(printf("INTR: timer\n"););
        PIC_inservice = PIC_TIMER;
        PIC_irq &= ~PIC_TIMER;
        if (IF)
            int_pending = 8;
        else
        {
            D2(printf("INTR blocked: IF disabled\n"););
            int_blocked = 8;
        }
    }
    else if (PIC_irq & ~PIC_mask & PIC_KEYBOARD)
    {
        D2(printf("INTR: keyboard\n"););
        PIC_inservice = PIC_KEYBOARD;
        PIC_irq &= ~PIC_KEYBOARD;
        if (IF)
            int_pending = 9;
        else
        {
            D2(printf("INTR blocked: IF disabled\n"););
            int_blocked = 9;
        }
    }
}


static void PIC_flagint(unsigned interrupt)
{
    disable();
    D2(printf("IRQ %02X\n", interrupt););
    PIC_irq |= interrupt;
    enable();
}


void PIC_EOI(void)
{
    disable();

    if (PIC_inservice & PIC_KEYBOARD)
    {
        if (++port60start >= PORT60SIZE)
            port60start = 0;

        if (port60start != port60end)
            PIC_irq |= PIC_KEYBOARD;
    }


    PIC_inservice = 0;
	PIC_interrupt();
    enable();
}


int port60_buffer_ok(int count)
{
    int tmp = port60end;

    for (; count > 0; count--)
    {
        if (++tmp >= PORT60SIZE)
            tmp = 0;

        if (tmp == port60start)
            return FALSE;
    }

    return TRUE;
}


void put_scancode(UBYTE *code, int count)
{
    for (; count > 0; count--)
    {
        port60buffer[port60end] = *code++;

        if (++port60end >= PORT60SIZE)
            port60end = 0;
    }

    PIC_flagint(PIC_KEYBOARD);

    D2(
      {
          int tmp;

          printf("INT9 signalled\n");
          printf("Port60 buffer: ");

          tmp = port60start;
          while (tmp != port60end)
          {
              printf("%02X ", port60buffer[tmp]);
              if (++tmp >= PORT60SIZE)
                  tmp = 0;
          }
          printf("\n");
      }
    )
}


static UBYTE read_port60(void)
{
    UBYTE ret;
    static UBYTE lastread = 0;

    disable();

    if (port60start == port60end)
        ret = lastread;
    else
        lastread = ret = port60buffer[port60start];
        
    enable();

    return ret;
}

#define TIMER_MULT 4

static volatile int int8_counter = TIMER_MULT;

#ifndef AMIGA
static
#endif
int timer_handler(int sig)
{

#ifdef DEBUGGER
    if (in_debug)
        return 0;
#endif

    if (disable_int)
    {
        D2(printf("Timer called when disabled\n"););
        timer_blocked++;
        return 0;
    }

    disable();

    vga_interrupt();

    timer_tmp++;

    if (--int8_counter == 0)
    {
        PIC_flagint(PIC_TIMER);
        int8_counter = TIMER_MULT;
    }

    PIC_interrupt();

    enable();
    return 0;
}


#ifdef AMIGA

#define SIGALRM 14

volatile int timer_pending;

#ifdef __PPC__

void start_timer (void)
{
}

void stop_timer (void)
{
}

void init_timer (void)
{
}

#else

#define OFF             0
#define ON              1
#define OFF_PENDING     2

struct p {
  struct MsgPort *timer_mp;
  struct timerequest *tr;
  struct Interrupt softint;
  int timer_state;
  int timer;
};

__saveds __interrupt __asm static
void timer_interrupt (register __a1 volatile struct p *p)
{
  struct timerequest *tr;

  tr = (struct timerequest *)GetMsg (p->timer_mp);
  if (p->timer_state == ON) {
    p->timer++;
    timer_pending = 1;
    tr->tr_node.io_Command = TR_ADDREQUEST;
    tr->tr_time.tv_secs = 0;
    tr->tr_time.tv_micro = (long)(1000000.0 /((float)TIMER_MULT*TICKSPERSEC));
    BeginIO ((struct IORequest *)tr);
  } else if (p->timer_state == OFF_PENDING) {
    p->timer_state = OFF;
  }
}

static volatile struct p p;

struct Library *TimerBase = NULL;

void start_timer (void)
{
  if (p.timer_state == OFF) {
    p.timer_state = ON;
    p.tr->tr_node.io_Command = TR_ADDREQUEST;
    p.tr->tr_time.tv_secs = 0;
    p.tr->tr_time.tv_micro = (long)(1000000.0 /((float)TIMER_MULT*TICKSPERSEC));
    BeginIO ((struct IORequest *)p.tr);
  }
}

void init_timer (void)
{
  p.softint.is_Code = (void (*)())timer_interrupt;
  p.softint.is_Data = (void *)&p;
  p.softint.is_Node.ln_Pri = 32;
  if ((p.timer_mp = (struct MsgPort *)AllocMem (sizeof(struct MsgPort),
                                           MEMF_PUBLIC | MEMF_CLEAR)) == NULL) {
    fprintf (stderr, "Out of memory\n");
    exit (1);
  }
  p.timer_mp->mp_Node.ln_Type = NT_MSGPORT;
  p.timer_mp->mp_Flags = PA_SOFTINT;
  p.timer_mp->mp_SigTask = (struct Task *)&p.softint;
  NewList (&p.timer_mp->mp_MsgList);
  if ((p.tr = (struct timerequest *)CreateExtIO (p.timer_mp,
                                         sizeof(struct timerequest))) == NULL) {
    fprintf (stderr, "CreateExtIO() failed\n");
    exit (1);
  }
  if (OpenDevice ("timer.device", UNIT_MICROHZ, (struct IORequest *)p.tr, 0)
                                                                         != 0) {
    fprintf (stderr, "Can't open timer.device\n");
    exit (1);
  }
  TimerBase = (struct Library *)p.tr->tr_node.io_Device;
  p.timer_state = OFF;
  p.timer = 0;
  start_timer ();
}

void stoptimer (void)
{
  if (p.timer_state != OFF) {
    p.timer_state = OFF_PENDING;
    while (p.timer_state != OFF) {
      chkabort ();
      Delay (1);
    }
  }
}

void _STDclosetimer (void)
{
  stoptimer ();
  if (TimerBase != NULL) {
    CloseDevice ((struct IORequest *)p.tr);
    TimerBase = NULL;
  }
  if (p.tr != NULL) {
    DeleteExtIO ((struct IORequest *)p.tr);
    p.tr = NULL;
  }
  if (p.timer_mp != NULL) {
    FreeMem (p.timer_mp, sizeof(struct MsgPort));
    p.timer_mp = NULL;
  }
}

#endif

#else

#ifndef SA_RESTART
#define SA_RESTART 0
#endif

void init_timer(void)
{
    struct sigaction sa;

    sa.sa_handler = (void *)timer_handler;
    sa.sa_flags = SA_RESTART;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGALRM, &sa, NULL);

    timer.it_interval.tv_sec = timer.it_value.tv_sec = 0;
    timer.it_interval.tv_usec = timer.it_value.tv_usec =  
        (long)(1000000.0 /((float)TIMER_MULT*TICKSPERSEC));

    D2(printf("Set timer to %ld microseconds\n", timer.it_interval.tv_usec););
    setitimer(ITIMER_REAL, &timer, NULL);
}


void stoptimer(void)
{
    timer.it_interval.tv_sec = timer.it_value.tv_sec = 
    timer.it_interval.tv_usec = timer.it_value.tv_usec =  0;
    setitimer(ITIMER_REAL, &timer, NULL);
    signal(SIGALRM, SIG_IGN);
}


void starttimer(void)
{
    init_timer();
}

#endif

void disable(void)
{
    if (disable_int == 0)
        timer_blocked = 0;
    disable_int++;
}


void enable(void)
{
    if (disable_int > 0)
        disable_int--;

    if (disable_int == 0)
        while (timer_blocked > 0)
        {
            timer_blocked--;
            timer_handler(SIGALRM);
        }
}


static int PIT_toggle = 1;

UBYTE read_port(unsigned port)
{
    UBYTE val;
    static unsigned timer_tmp;

    switch(port)
    {
    case 0x21:
        val = PIC_mask;
        break;
    case 0x40:
    case 0x41:
    case 0x42:
        disable();
        if ((PIT_toggle = !PIT_toggle) == 0)
            val = timer_tmp & 0xff;
        else
        {
            val = (timer_tmp & 0xff00) >> 8;
            timer_tmp++;
        }
        enable();
        break;
    case 0x60:
        val = read_port60();
        break;
    case 0x61:
        val = 0xd0;
        break;
    case 0x3da:
        val = VGA_status;
        VGA_status ^= 0x9;
        break;
    default:
        val = 0;
        break;
    }
    
    D(printf("Reading 0x%02X from port 0x%04X\n", val, port););
    return val;
}


void write_port(unsigned port, UBYTE value)
{
    switch(port)
    {
    case 0x20:
        if (value == 0x20)
        {
            PIC_EOI();
            return;
        }
        break;
    case 0x21:
        disable();
        PIC_mask = value;
        enable();
        break;
    case 0x43:
        PIT_toggle = 1;
        break;
    default:
        break;
    }
    D(printf("Writing 0x%02X to port 0x%04X\n", value, port););

}
