root/sys/arch/i386/stand/lib/pcio.c
/*      $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);
}