/*
 * DANG_BEGIN_MODULE
 *
 *
 *
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 *
 * DANG_END_CHANGELOG
 */

#include "dosemu.h"
#include "base/termio.h"
#include "base/pic.h"
#include "base/misc.h"
#include "base/ioctlq.h"

#include "arch/kversion.h"
#include "arch/signals.h"
#include "arch/emusys.h"

#include <fcntl.h>
#include <sys/utsname.h>
#include <sys/time.h>

/* Silly Interrupt Generator Initialization/Closedown */

#ifdef CONFIG_SIG
SillyG_t       *SillyG = 0;
static SillyG_t SillyG_[16 + 1];
#endif

/*
 * DANG_BEGIN_FUNCTION SIG_int
 * 
 * description: Allow DOSEMU to be made aware when a hard interrupt occurs
 * Requires the sig/sillyint.o driver loaded (using NEW modules package),
 * or a kernel patch (implementing sig/int.c driver).
 * 
 * The IRQ numbers to monitor are taken from config.sillyint, each bit
 * corresponding to one IRQ. The higher 16 bit are defining the use of
 * SIGIO
 * 
 * DANG_END_FUNCTION
 */
static void SIG_init(void)
{
#ifdef CONFIG_SIG
  /* Get in touch with my Silly Interrupt Driver */
  if (config.sillyint) {
    SillyG_t  *sg           = SillyG_;
    char       prio_table[] = {9, 10, 11, 12, 14, 15, 3, 4, 5, 6, 7};
    char       devname[20];
    int        i, irq, fd;

    for (i = 0; i < sizeof(prio_table); i++) {
      irq = prio_table[i];
      if (config.sillyint & (1 << irq)) {
#ifdef CONFIG_EMUMODULE
	if (emusyscall(EMUSYS_REQUEST_IRQ, irq) < 0) {
	  g_printf("Not gonna touch IRQ %d you requested!\n", irq);
	} else {
	  g_printf("Gonna monitor the IRQ %d you requested\n", irq);
	  sg->fd = -1;
#else
	  sprintf(devname, "/dev/int/%d", irq);
	  if ((fd = open(devname, O_RDWR)) < 1) {
	    g_printf("Not gonna touch IRQ %d you requested!\n", irq);
	  } else {
	    /* Reset interupt incase it went off already */
	    RPT_SYSCALL(write(fd, NULL, (int) NULL));
	    g_printf("Gonna monitor the IRQ %d you requested, Return=0x%02x\n",
		     irq, fd);
	    if (config.sillyint & (0x10000 << irq)) {
	      /* Use SIGIO, this should be faster */
	      io_select_add(fd, 1);
	    }
	    /*
	     * DANG_BEGIN_REMARK At this time we have to use
	     * SIGALRM in addition to SIGIO I don't (yet) know why
	     * the SIGIO signal gets lost sometimes (once per
	     * minute or longer). But if it happens, we can
	     * retrigger this way over SIGALRM. Normally SIGIO
	     * happens before SIGALARM, so nothing hurts. (Hans)
	     * DANG_END_REMARK
	     */
	    {
	      /* use SIGALRM  */
	      io_select_add(fd, 0);
	    }
	    sg->fd = fd;
#endif /* !CONFIG_EMUMODULE */
	    sg->irq = irq;
	    g_printf("SIG: IRQ%d, enabling PIC-level %d\n", irq,
		     pic_irq_list[irq]);
#ifdef CONFIG_EMUMODULE
	    {
	      extern int SillyG_do_irq(void);
	      pic_seti(pic_irq_list[irq], SillyG_do_irq, 0);
	    }
#else
	    pic_seti(pic_irq_list[irq], do_irq, 0);
#endif
	    pic_unmaski(pic_irq_list[irq]);
	    sg++;
	  }
	}
      }
      sg->fd = 0;
      if (sg != SillyG_)
	SillyG = SillyG_;
    }
#endif
  }

static inline void SIG_close(void)
{
#ifdef CONFIG_SIG
  if (SillyG) {
    SillyG_t *sg = SillyG;
    while (sg->fd) {
#ifdef CONFIG_EMUMODULE
      emusyscall(EMUSYS_FREE_IRQ, sg->irq);
#else
      close(sg->fd);
#endif
      sg++;
    }
    fprintf(stderr, "Closing all IRQ you opened!\n");
  }
#endif
}

static void emumodule_init(void)
{
#ifdef CONFIG_EMUMODULE
  resolve_emusyscall();
  if (EMUSYS_AVAILABLE) {
    if (emusyscall(EMUSYS_GETVERSION, 0) >= EMUSYSVERSION) return;
  }
  fprintf(stderr, "emumodule not loaded or wrong version\n\r");
  fflush(stdout);
  fflush(stderr);
  _exit(1);
#endif
}

/*
 * DANG_BEGIN_FUNCTION version_init
 * 
 * description:
 *  Find version of OS running and set necessary global parms.
 *
 * DANG_END_FUNCTION
 */
static void version_init(void)
{
  struct utsname unames;

  uname(&unames);
  warn("DOSEMU-%s is coming up on %s version %s\n", version_str,
       unames.sysname, unames.release);
  warn("Built for %d\n", KERNEL_VERSION);

  if (strcmp(unames.release, "1.1.20") >= 0) {
    use_sigio=FASYNC;
  }

  if (strcmp(unames.release, "1.1.43") < 0) {
    error("DOSEMU requires at least Linux version 1.1.43\n");
    leavedos(1);
  }

  if (strcmp(unames.release, "1.1.65") < 0) {
    error("Versions of Linux in the 1.1.5x and 1.1.6x are known to have a bug \
which\ncorrupts file systems!  Consider upgrading your kernel!\n");
  }

  /* Next Check input */
  if (isatty(STDIN_FILENO)) {
    k_printf("STDIN is tty\n");
    config.kbd_tty = 0;
  } else {
    k_printf("STDIN not a tty\n");
    config.kbd_tty = 1;
  }
}

/*
 * DANG_BEGIN_FUNCTION selfmem_init
 * 
 * description:
 * Gets the file descriptor for doing mmap's to /proc/self/mem
 * 
 * DANG_END_FUNCTION
 */
static void selfmem_init(void)
{
#ifndef CONFIG_RUN_AS_ROOT
  if (i_am_root)
    priv_on();
#endif
  if ((selfmem_fd = open("/proc/self/mem", O_RDWR)) < 0) {
    error("Can't open /proc/self/mem: %s\n", strerror(errno));
    leavedos(1);
  }
  else
    g_printf("INIT: /proc/self/mem opened successfully\n");
#ifndef CONFIG_RUN_AS_ROOT
  priv_off();
#endif

  if (i_am_root) {
#ifndef CONFIG_RUN_AS_ROOT
    priv_on();
#endif
    if ((mem_fd = open("/dev/mem", O_RDWR)) < 0) {
#ifndef CONFIG_RUN_AS_ROOT
      priv_off();
#endif
      error ("Can't open /dev/mem: %s\n", strerror(errno));
      leavedos (1);
    }
    else
      g_printf("INIT: /dev/mem opened successfully\n");
#ifndef CONFIG_RUN_AS_ROOT
    priv_off();
#endif
  }
  else
    g_printf("INIT: can not open /dev/mem without being root\n");
}

void arch_init(void)
{
  version_init();
  emumodule_init();
  SIG_init();
  linux_signal_init();
  selfmem_init();
}

void arch_term(void)
{
  SIG_close();
}
