/*      $NetBSD: pcio.c,v 1.30 2011/06/08 16:04:40 joerg Exp $   */

/*
* Copyright (c) 1996, 1997
*      Matthias Drochner.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

/*
* console I/O
* needs lowlevel routines from conio.S and comio.S
*/

#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include <sys/bootblock.h>

#include "libi386.h"
#include "bootinfo.h"

extern struct x86_boot_params boot_params;

struct btinfo_console btinfo_console;

#ifdef SUPPORT_SERIAL
static int iodev;

#ifdef DIRECT_SERIAL
#include "comio_direct.h"

#define cominit_x()     btinfo_console.speed = \
                           cominit_d(btinfo_console.addr, btinfo_console.speed)
#define computc_x(ch)   computc_d(ch, btinfo_console.addr)
#define comgetc_x()     comgetc_d(btinfo_console.addr)
#define comstatus_x()   comstatus_d(btinfo_console.addr)

#else
#define cominit_x()     cominit(iodev - CONSDEV_COM0)
#define computc_x(ch)   computc(ch, iodev - CONSDEV_COM0)
#define comgetc_x()     comgetc(iodev - CONSDEV_COM0)
#define comstatus_x()   comstatus(iodev - CONSDEV_COM0)

#endif /* DIRECT_SERIAL */

static int getcomaddr(int);
#endif /* SUPPORT_SERIAL */

#define POLL_FREQ 10

static void
wait(int us)
{
       int prev = biosgetsystime();
       int tgt = prev + (20 * us) / 1000000;
       int new;

       while ((new = biosgetsystime()) < tgt) {
               if (new < prev) /* XXX timer wrapped */
                       break;
               prev = new;
       }
}

#ifdef SUPPORT_SERIAL
static int
getcomaddr(int idx)
{
       short addr;
#ifdef CONSADDR
       if (CONSADDR != 0)
               return CONSADDR;
#endif
       /* read in BIOS data area */
       pvbcopy((void *)(0x400 + 2 * idx), &addr, 2);
       return addr;
}
#endif

void
clear_pc_screen(void)
{
#ifdef SUPPORT_SERIAL
       /* Clear the screen if we are on a glass tty. */
       if (iodev == CONSDEV_PC)
               conclr();
#endif
}

void
initio(int dev)
{
#ifdef SUPPORT_SERIAL
       int i;

#if defined(DIRECT_SERIAL) && defined(CONSPEED)
       btinfo_console.speed = CONSPEED;
#else
       btinfo_console.speed = 9600;
#endif

       switch (dev) {
       case CONSDEV_AUTO:
               for (i = 0; i < 3; i++) {
                       iodev = CONSDEV_COM0 + i;
                       btinfo_console.addr = getcomaddr(i);
                       if (!btinfo_console.addr)
                               break;
                       conputc('0' + i); /* to tell user what happens */
                       cominit_x();
#ifdef DIRECT_SERIAL
                       /* check for:
                        *  1. successful output
                        *  2. optionally, keypress within 7s
                        */
                       if (    computc_x(':') &&
                               computc_x('-') &&
                               computc_x('(')
#ifdef COMCONS_KEYPRESS
                          && awaitkey(7, 0)
#endif
                          )
                               goto ok;
#else /* ! DIRECT_SERIAL */
                       /*
                        * serial console must have hardware handshake!
                        * check:
                        *  1. character output without error
                        *  2. status bits for modem ready set
                        *     (status seems only useful after character output)
                        *  3. optionally, keypress within 7s
                        */
                       if (!(computc_x('@') & 0x80)
                           && (comstatus_x() & 0x00b0)
#ifdef COMCONS_KEYPRESS
                           && awaitkey(7, 0)
#endif
                           )
                               goto ok;
#endif /* DIRECT_SERIAL */
               }
               iodev = CONSDEV_PC;
ok:
               break;
       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
               iodev = dev;
               btinfo_console.addr = getcomaddr(iodev - CONSDEV_COM0);
               if (!btinfo_console.addr)
                       goto nocom;
               cominit_x();
               break;
       case CONSDEV_COM0KBD:
       case CONSDEV_COM1KBD:
       case CONSDEV_COM2KBD:
       case CONSDEV_COM3KBD:
               iodev = dev - CONSDEV_COM0KBD + CONSDEV_COM0;
               i = iodev - CONSDEV_COM0;
               btinfo_console.addr = getcomaddr(i);
               if (!btinfo_console.addr)
                       goto nocom;
               conputc('0' + i); /* to tell user what happens */
               cominit_x();
#ifdef DIRECT_SERIAL
                       /* check for:
                        *  1. successful output
                        *  2. optionally, keypress within 7s
                        */
                       if (    computc_x(':') &&
                               computc_x('-') &&
                               computc_x('(')
#ifdef COMCONS_KEYPRESS
                          && awaitkey(7, 0)
#endif
                          )
                               break;
#else /* ! DIRECT_SERIAL */
                       /*
                        * serial console must have hardware handshake!
                        * check:
                        *  1. character output without error
                        *  2. status bits for modem ready set
                        *     (status seems only useful after character output)
                        *  3. optionally, keypress within 7s
                        */
                       if (!(computc_x('@') & 0x80)
                           && (comstatus_x() & 0x00b0)
#ifdef COMCONS_KEYPRESS
                           && awaitkey(7, 0)
#endif
                           )
                               break;
#endif /* DIRECT_SERIAL */
       default:
nocom:
               iodev = CONSDEV_PC;
               break;
       }
       conputc('\015');
       conputc('\n');
       strncpy(btinfo_console.devname, iodev == CONSDEV_PC ? "pc" : "com", 16);

#else /* !SUPPORT_SERIAL */
       btinfo_console.devname[0] = 'p';
       btinfo_console.devname[1] = 'c';
       btinfo_console.devname[2] = 0;
#endif /* SUPPORT_SERIAL */
}

static inline void internal_putchar(int);

static inline void
internal_putchar(int c)
{
#ifdef SUPPORT_SERIAL
       switch (iodev) {
       case CONSDEV_PC:
#endif
               conputc(c);
#ifdef SUPPORT_SERIAL
               break;
       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
               computc_x(c);
               break;
       }
#endif
}

void
putchar(int c)
{
       if (c == '\n')
               internal_putchar('\r');
       internal_putchar(c);
}

int
getchar(void)
{
       int c;
#ifdef SUPPORT_SERIAL
       switch (iodev) {
       default: /* to make gcc -Wall happy... */
       case CONSDEV_PC:
#endif
               while (!coniskey())
                       ;
               c = congetc();
#ifdef CONSOLE_KEYMAP
               {
                       char *cp = strchr(CONSOLE_KEYMAP, c);
                       if (cp != 0 && cp[1] != 0)
                               c = cp[1];
               }
#endif
               return c;
#ifdef SUPPORT_SERIAL
       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
#ifdef DIRECT_SERIAL
               c = comgetc_x();
#else
               do {
                       c = comgetc_x();
               } while ((c >> 8) == 0xe0); /* catch timeout */
#ifdef COMDEBUG
               if (c & 0x8000) {
                       printf("com input %x, status %x\n",
                              c, comstatus_x());
               }
#endif
               c &= 0xff;
#endif /* DIRECT_SERIAL */
               return c;
       }
#endif /* SUPPORT_SERIAL */
}

int
iskey(int intr)
{
#ifdef SUPPORT_SERIAL
       switch (iodev) {
       default: /* to make gcc -Wall happy... */
       case CONSDEV_PC:
#endif
               return (intr && conisshift()) || coniskey();
#ifdef SUPPORT_SERIAL
       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
#ifdef DIRECT_SERIAL
               return !!comstatus_x();
#else
               return !!(comstatus_x() & 0x0100);
#endif
       }
#endif /* SUPPORT_SERIAL */
}

char
awaitkey(int timeout, int tell)
{
       int i;
       char c = 0;

       i = timeout * POLL_FREQ;

       for (;;) {
               if (tell && (i % POLL_FREQ) == 0) {
                       char numbuf[32];
                       int len;

                       len = snprintf(numbuf, sizeof(numbuf), "%d seconds. ",
                           i/POLL_FREQ);
                       if (len > 0 && len < sizeof(numbuf)) {
                               char *p = numbuf;

                               printf("%s", numbuf);
                               while (*p)
                                       *p++ = '\b';
                               printf("%s", numbuf);
                       }
               }
               if (iskey(1)) {
                       /* flush input buffer */
                       while (iskey(0))
                               c = getchar();
                       if (c == 0)
                               c = -1;
                       goto out;
               }
               if (i--)
                       wait(1000000 / POLL_FREQ);
               else
                       break;
       }

out:
       if (tell)
               printf("0 seconds.     \n");

       return c;
}

void
wait_sec(int sec)
{

       wait(sec * 1000000);
}