/****************************************************************************
*
*                   SciTech OS Portability Manager Library
*
*  ========================================================================
*
*    The contents of this file are subject to the SciTech MGL Public
*    License Version 1.0 (the "License"); you may not use this file
*    except in compliance with the License. You may obtain a copy of
*    the License at http://www.scitechsoft.com/mgl-license.txt
*
*    Software distributed under the License is distributed on an
*    "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
*    implied. See the License for the specific language governing
*    rights and limitations under the License.
*
*    The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
*
*    The Initial Developer of the Original Code is SciTech Software, Inc.
*    All Rights Reserved.
*
*  ========================================================================
*
*                   Portions copyright (C) Josh Vanderhoof
*
* Language:     ANSI C
* Environment:  Linux
*
* Description:  Implementation for the OS Portability Manager Library, which
*               contains functions to implement OS specific services in a
*               generic, cross platform API. Porting the OS Portability
*               Manager library is the first step to porting any SciTech
*               products to a new platform.
*
****************************************************************************/

#include "pmapi.h"
#include "drvlib/os/os.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/kd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/vt.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <syscall.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <asm/io.h>
#include <asm/types.h>
#ifdef ENABLE_MTRR
#include <asm/mtrr.h>
#endif
#include <asm/vm86.h>
#ifdef __GLIBC__
#include <sys/perm.h>
#endif

/*--------------------------- Global variables ----------------------------*/

#define REAL_MEM_BASE       ((void *)0x10000)
#define REAL_MEM_SIZE       0x10000
#define REAL_MEM_BLOCKS     0x100
#define DEFAULT_VM86_FLAGS  (IF_MASK | IOPL_MASK)
#define DEFAULT_STACK_SIZE  0x1000
#define RETURN_TO_32_INT    255

/* Quick and dirty fix for vm86() syscall from lrmi 0.6 */
static int
vm86(struct vm86_struct *vm)
    {
    int r;
#ifdef __PIC__
    asm volatile (
     "pushl %%ebx\n\t"
     "movl %2, %%ebx\n\t"
     "int $0x80\n\t"
     "popl %%ebx"
     : "=a" (r)
     : "0" (113), "r" (vm));
#else
    asm volatile (
     "int $0x80"
     : "=a" (r)
     : "0" (113), "b" (vm));
#endif
    return r;
    }


static struct {
    int                 ready;
    unsigned short      ret_seg, ret_off;
    unsigned short      stack_seg, stack_off;
    struct vm86_struct  vm;
    } context = {0};

struct mem_block {
    unsigned int size : 20;
    unsigned int free : 1;
    };

static struct {
    int ready;
    int count;
    struct mem_block blocks[REAL_MEM_BLOCKS];
    } mem_info = {0};

int                     _PM_console_fd = -1;
int                     _PM_leds = 0,_PM_modifiers = 0;
static ibool            inited = false;
static int              tty_vc = 0;
static int              console_count = 0;
static int              startup_vc;
static int              fd_mem = 0;
static ibool            in_raw_mode = false;
#ifdef ENABLE_MTRR
static int              mtrr_fd;
#endif
static uint VESABuf_len = 1024;     /* Length of the VESABuf buffer     */
static void *VESABuf_ptr = NULL;    /* Near pointer to VESABuf          */
static uint VESABuf_rseg;           /* Real mode segment of VESABuf     */
static uint VESABuf_roff;           /* Real mode offset of VESABuf      */
#ifdef TRACE_IO
static ulong            traceAddr;
#endif

static void (PMAPIP fatalErrorCleanup)(void) = NULL;

/*----------------------------- Implementation ----------------------------*/

#ifdef  TRACE_IO
extern void printk(char *msg,...);
#endif

static inline void port_out(int value, int port)
{
#ifdef TRACE_IO
    printk("%04X:%04X: outb.%04X <- %02X\n", traceAddr >> 16, traceAddr & 0xFFFF, (ushort)port, (uchar)value);
#endif
    asm volatile ("outb %0,%1"
          ::"a" ((unsigned char) value), "d"((unsigned short) port));
}

static inline void port_outw(int value, int port)
{
#ifdef TRACE_IO
    printk("%04X:%04X: outw.%04X <- %04X\n", traceAddr >> 16,traceAddr & 0xFFFF, (ushort)port, (ushort)value);
#endif
    asm volatile ("outw %0,%1"
         ::"a" ((unsigned short) value), "d"((unsigned short) port));
}

static inline void port_outl(int value, int port)
{
#ifdef TRACE_IO
    printk("%04X:%04X: outl.%04X <- %08X\n", traceAddr >> 16,traceAddr & 0xFFFF, (ushort)port, (ulong)value);
#endif
    asm volatile ("outl %0,%1"
         ::"a" ((unsigned long) value), "d"((unsigned short) port));
}

static inline unsigned int port_in(int port)
{
    unsigned char value;
    asm volatile ("inb %1,%0"
              :"=a" ((unsigned char)value)
              :"d"((unsigned short) port));
#ifdef TRACE_IO
    printk("%04X:%04X:  inb.%04X -> %02X\n", traceAddr >> 16,traceAddr & 0xFFFF, (ushort)port, (uchar)value);
#endif
    return value;
}

static inline unsigned int port_inw(int port)
{
    unsigned short value;
    asm volatile ("inw %1,%0"
              :"=a" ((unsigned short)value)
              :"d"((unsigned short) port));
#ifdef TRACE_IO
    printk("%04X:%04X:  inw.%04X -> %04X\n", traceAddr >> 16,traceAddr & 0xFFFF, (ushort)port, (ushort)value);
#endif
    return value;
}

static inline unsigned int port_inl(int port)
{
    unsigned long value;
    asm volatile ("inl %1,%0"
              :"=a" ((unsigned long)value)
              :"d"((unsigned short) port));
#ifdef TRACE_IO
    printk("%04X:%04X:  inl.%04X -> %08X\n", traceAddr >> 16,traceAddr & 0xFFFF, (ushort)port, (ulong)value);
#endif
    return value;
}

static int real_mem_init(void)
{
    void    *m;
    int     fd_zero;

    if (mem_info.ready)
        return 1;

    if ((fd_zero = open("/dev/zero", O_RDONLY)) == -1)
        PM_fatalError("You must have root privledges to run this program!");
    if ((m = mmap((void *)REAL_MEM_BASE, REAL_MEM_SIZE,
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_FIXED | MAP_PRIVATE, fd_zero, 0)) == (void *)-1) {
        close(fd_zero);
        PM_fatalError("You must have root privledges to run this program!");
        }
    mem_info.ready = 1;
    mem_info.count = 1;
    mem_info.blocks[0].size = REAL_MEM_SIZE;
    mem_info.blocks[0].free = 1;
    return 1;
}

static void insert_block(int i)
{
    memmove(
        mem_info.blocks + i + 1,
        mem_info.blocks + i,
        (mem_info.count - i) * sizeof(struct mem_block));
    mem_info.count++;
}

static void delete_block(int i)
{
    mem_info.count--;

    memmove(
        mem_info.blocks + i,
        mem_info.blocks + i + 1,
        (mem_info.count - i) * sizeof(struct mem_block));
}

static inline void set_bit(unsigned int bit, void *array)
{
    unsigned char *a = array;
    a[bit / 8] |= (1 << (bit % 8));
}

static inline unsigned int get_int_seg(int i)
{
    return *(unsigned short *)(i * 4 + 2);
}

static inline unsigned int get_int_off(int i)
{
    return *(unsigned short *)(i * 4);
}

static inline void pushw(unsigned short i)
{
    struct vm86_regs *r = &context.vm.regs;
    r->esp -= 2;
    *(unsigned short *)(((unsigned int)r->ss << 4) + r->esp) = i;
}

ibool PMAPI PM_haveBIOSAccess(void)
{ return true; }

void PMAPI PM_init(void)
{
    void    *m;
    uint    r_seg,r_off;

    if (inited)
        return;

    /* Map the Interrupt Vectors (0x0 - 0x400) + BIOS data (0x400 - 0x502)
     * and the physical framebuffer and ROM images from (0xa0000 - 0x100000)
     */
    real_mem_init();
    if (!fd_mem && (fd_mem = open("/dev/mem", O_RDWR)) == -1) {
        PM_fatalError("You must have root privileges to run this program!");
        }
    if ((m = mmap((void *)0, 0x502,
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_FIXED | MAP_PRIVATE, fd_mem, 0)) == (void *)-1) {
        PM_fatalError("You must have root privileges to run this program!");
        }
    if ((m = mmap((void *)0xA0000, 0xC0000 - 0xA0000,
            PROT_READ | PROT_WRITE,
            MAP_FIXED | MAP_SHARED, fd_mem, 0xA0000)) == (void *)-1) {
        PM_fatalError("You must have root privileges to run this program!");
        }
    if ((m = mmap((void *)0xC0000, 0xD0000 - 0xC0000,
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_FIXED | MAP_PRIVATE, fd_mem, 0xC0000)) == (void *)-1) {
        PM_fatalError("You must have root privileges to run this program!");
        }
    if ((m = mmap((void *)0xD0000, 0x100000 - 0xD0000,
            PROT_READ | PROT_WRITE,
            MAP_FIXED | MAP_SHARED, fd_mem, 0xD0000)) == (void *)-1) {
        PM_fatalError("You must have root privileges to run this program!");
        }
    inited = 1;

    /* Allocate a stack */
    m = PM_allocRealSeg(DEFAULT_STACK_SIZE,&r_seg,&r_off);
    context.stack_seg = r_seg;
    context.stack_off = r_off+DEFAULT_STACK_SIZE;

    /* Allocate the return to 32 bit routine */
    m = PM_allocRealSeg(2,&r_seg,&r_off);
    context.ret_seg = r_seg;
    context.ret_off = r_off;
    ((uchar*)m)[0] = 0xCD;         /* int opcode */
    ((uchar*)m)[1] = RETURN_TO_32_INT;
    memset(&context.vm, 0, sizeof(context.vm));

    /* Enable kernel emulation of all ints except RETURN_TO_32_INT */
    memset(&context.vm.int_revectored, 0, sizeof(context.vm.int_revectored));
    set_bit(RETURN_TO_32_INT, &context.vm.int_revectored);
    context.ready = 1;
#ifdef ENABLE_MTRR
    mtrr_fd =  open("/dev/cpu/mtrr", O_RDWR, 0);
    if (mtrr_fd < 0)
       mtrr_fd =  open("/proc/mtrr", O_RDWR, 0);
#endif
    /* Enable I/O permissions to directly access I/O ports. We break the
     * allocation into two parts, one for the ports from 0-0x3FF and
     * another for the remaining ports up to 0xFFFF. Standard Linux kernels
     * only allow the first 0x400 ports to be enabled, so to enable all
     * 65536 ports you need a patched kernel that will enable the full
     * 8Kb I/O permissions bitmap.
     */
#ifndef TRACE_IO
    ioperm(0x0,0x400,1);
    ioperm(0x400,0x10000-0x400,1);
#endif
    iopl(3);
}

long PMAPI PM_getOSType(void)
{ return _OS_LINUX; }

char * PMAPI PM_getOSName(void)
{ return "Linux"; }

int PMAPI PM_getModeType(void)
{ return PM_386; }

void PMAPI PM_backslash(char *s)
{
    uint pos = strlen(s);
    if (s[pos-1] != '/') {
        s[pos] = '/';
        s[pos+1] = '\0';
        }
}

void PMAPI PM_setFatalErrorCleanup(
    void (PMAPIP cleanup)(void))
{
    fatalErrorCleanup = cleanup;
}

void PMAPI PM_fatalError(const char *msg)
{
    if (fatalErrorCleanup)
        fatalErrorCleanup();
    fprintf(stderr,"%s\n", msg);
    fflush(stderr);
    exit(1);
}

static void ExitVBEBuf(void)
{
    if (VESABuf_ptr)
        PM_freeRealSeg(VESABuf_ptr);
    VESABuf_ptr = 0;
}

void * PMAPI PM_getVESABuf(uint *len,uint *rseg,uint *roff)
{
    if (!VESABuf_ptr) {
        /* Allocate a global buffer for communicating with the VESA VBE */
        if ((VESABuf_ptr = PM_allocRealSeg(VESABuf_len, &VESABuf_rseg, &VESABuf_roff)) == NULL)
            return NULL;
        atexit(ExitVBEBuf);
        }
    *len = VESABuf_len;
    *rseg = VESABuf_rseg;
    *roff = VESABuf_roff;
    return VESABuf_ptr;
}

/* New raw console based getch and kbhit functions */

#define KB_CAPS     LED_CAP /* 4 */
#define KB_NUMLOCK  LED_NUM /* 2 */
#define KB_SCROLL   LED_SCR /* 1 */
#define KB_SHIFT    8
#define KB_CONTROL  16
#define KB_ALT      32

/* Structure used to save the keyboard mode to disk. We save it to disk
 * so that we can properly restore the mode later if the program crashed.
 */

typedef struct {
    struct termios  termios;
    int             kb_mode;
    int             leds;
    int             flags;
    int             startup_vc;
    } keyboard_mode;

/* Name of the file used to save keyboard mode information */

#define KBMODE_DAT  "kbmode.dat"

/****************************************************************************
REMARKS:
Open the keyboard mode file on disk.
****************************************************************************/
static FILE *open_kb_mode(
    char *mode,
    char *path)
{
    if (!PM_findBPD("graphics.bpd",path))
        PM_fatalError("Unable to find graphics.bpd file!");
    PM_backslash(path);
    strcat(path,KBMODE_DAT);
    return fopen(path,mode);
}

/****************************************************************************
REMARKS:
Restore the keyboard to normal mode
****************************************************************************/
void _PM_restore_kb_mode(void)
{
    FILE            *kbmode;
    keyboard_mode   mode;
    char            path[PM_MAX_PATH];

    if (_PM_console_fd != -1 && (kbmode = open_kb_mode("rb",path)) != NULL) {
        if (fread(&mode,1,sizeof(mode),kbmode) == sizeof(mode)) {
            if (mode.startup_vc > 0)
                ioctl(_PM_console_fd, VT_ACTIVATE, mode.startup_vc);
            ioctl(_PM_console_fd, KDSKBMODE, mode.kb_mode);
            ioctl(_PM_console_fd, KDSETLED, mode.leds);
            tcsetattr(_PM_console_fd, TCSAFLUSH, &mode.termios);
            fcntl(_PM_console_fd,F_SETFL,mode.flags);
            }
        fclose(kbmode);
        unlink(path);
        in_raw_mode = false;
        }
}

/****************************************************************************
REMARKS:
Safely abort the event module upon catching a fatal error.
****************************************************************************/
void _PM_abort(
    int signo)
{
    char    buf[80];

    sprintf(buf,"Terminating on signal %d",signo);
    _PM_restore_kb_mode();
    PM_fatalError(buf);
}

/****************************************************************************
REMARKS:
Put the keyboard into raw mode
****************************************************************************/
void _PM_keyboard_rawmode(void)
{
    struct termios conf;
    FILE            *kbmode;
    keyboard_mode   mode;
    char            path[PM_MAX_PATH];
    int             i;
    static int sig_list[] = {
        SIGHUP,
        SIGINT,
        SIGQUIT,
        SIGILL,
        SIGTRAP,
        SIGABRT,
        SIGIOT,
        SIGBUS,
        SIGFPE,
        SIGKILL,
        SIGSEGV,
        SIGTERM,
        };

    if ((kbmode = open_kb_mode("rb",path)) == NULL) {
        if ((kbmode = open_kb_mode("wb",path)) == NULL)
            PM_fatalError("Unable to open kbmode.dat file for writing!");
        if (ioctl(_PM_console_fd, KDGKBMODE, &mode.kb_mode))
            perror("KDGKBMODE");
        ioctl(_PM_console_fd, KDGETLED, &mode.leds);
        _PM_leds = mode.leds & 0xF;
        _PM_modifiers = 0;
        tcgetattr(_PM_console_fd, &mode.termios);
        conf = mode.termios;
        conf.c_lflag &= ~(ICANON | ECHO | ISIG);
        conf.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | BRKINT | PARMRK | INPCK | IUCLC | IXON | IXOFF);
        conf.c_iflag  |= (IGNBRK | IGNPAR);
        conf.c_cc[VMIN] = 1;
        conf.c_cc[VTIME] = 0;
        conf.c_cc[VSUSP] = 0;
        tcsetattr(_PM_console_fd, TCSAFLUSH, &conf);
        mode.flags = fcntl(_PM_console_fd,F_GETFL);
        if (ioctl(_PM_console_fd, KDSKBMODE, K_MEDIUMRAW))
            perror("KDSKBMODE");
        atexit(_PM_restore_kb_mode);
        for (i = 0; i < sizeof(sig_list)/sizeof(sig_list[0]); i++)
            signal(sig_list[i], _PM_abort);
        mode.startup_vc = startup_vc;
        if (fwrite(&mode,1,sizeof(mode),kbmode) != sizeof(mode))
            PM_fatalError("Error writing kbmode.dat!");
        fclose(kbmode);
        in_raw_mode = true;
        }
}

int PMAPI PM_kbhit(void)
{
    fd_set s;
    struct timeval tv = { 0, 0 };

    if (console_count == 0)
        PM_fatalError("You *must* open a console before using PM_kbhit!");
    if (!in_raw_mode)
        _PM_keyboard_rawmode();
    FD_ZERO(&s);
    FD_SET(_PM_console_fd, &s);
    return select(_PM_console_fd+1, &s, NULL, NULL, &tv) > 0;
}

int PMAPI PM_getch(void)
{
    static uchar    c;
    int release;
    static struct kbentry ke;

    if (console_count == 0)
        PM_fatalError("You *must* open a console before using PM_getch!");
    if (!in_raw_mode)
        _PM_keyboard_rawmode();
    while (read(_PM_console_fd, &c, 1) > 0) {
        release = c & 0x80;
        c &= 0x7F;
        if (release) {
            switch(c){
                case 42: case 54: // Shift
                    _PM_modifiers &= ~KB_SHIFT;
                    break;
                case 29: case 97: // Control
                    _PM_modifiers &= ~KB_CONTROL;
                    break;
                case 56: case 100: // Alt / AltGr
                    _PM_modifiers &= ~KB_ALT;
                    break;
                }
            continue;
            }
        switch (c) {
            case 42: case 54: // Shift
                _PM_modifiers |= KB_SHIFT;
                 break;
            case 29: case 97: // Control
                _PM_modifiers |= KB_CONTROL;
                break;
            case 56: case 100: // Alt / AltGr
                _PM_modifiers |= KB_ALT;
                break;
            case 58: // Caps Lock
                _PM_modifiers ^= KB_CAPS;
                ioctl(_PM_console_fd, KDSETLED, _PM_modifiers & 7);
                break;
            case 69: // Num Lock
                _PM_modifiers ^= KB_NUMLOCK;
                ioctl(_PM_console_fd, KDSETLED, _PM_modifiers & 7);
                break;
            case 70: // Scroll Lock
                _PM_modifiers ^= KB_SCROLL;
                ioctl(_PM_console_fd, KDSETLED, _PM_modifiers & 7);
                break;
            case 28:
                return 0x1C;
            default:
                ke.kb_index = c;
                ke.kb_table = 0;
                if ((_PM_modifiers & KB_SHIFT) || (_PM_modifiers & KB_CAPS))
                    ke.kb_table |= K_SHIFTTAB;
                if (_PM_modifiers & KB_ALT)
                    ke.kb_table |= K_ALTTAB;
                ioctl(_PM_console_fd, KDGKBENT, (ulong)&ke);
                c = ke.kb_value & 0xFF;
                return c;
            }
        }
    return 0;
}

/****************************************************************************
REMARKS:
Sleep until the virtual terminal is active
****************************************************************************/
static void wait_vt_active(
    int _PM_console_fd)
{
    while (ioctl(_PM_console_fd, VT_WAITACTIVE, tty_vc) < 0) {
        if ((errno != EAGAIN) && (errno != EINTR)) {
            perror("ioctl(VT_WAITACTIVE)");
            exit(1);
            }
        usleep(150000);
        }
}

/****************************************************************************
REMARKS:
Checks the owner of the specified virtual console.
****************************************************************************/
static int check_owner(
    int vc)
{
    struct stat sbuf;
    char fname[30];

    sprintf(fname, "/dev/tty%d", vc);
    if ((stat(fname, &sbuf) >= 0) && (getuid() == sbuf.st_uid))
        return 1;
    printf("You must be the owner of the current console to use this program.\n");
    return 0;
}

/****************************************************************************
REMARKS:
Checks if the console is currently in graphics mode, and if so we forcibly
restore it back to text mode again. This handles the case when a Nucleus or
MGL program crashes and leaves the console in graphics mode. Running the
textmode utility (or any other Nucleus/MGL program) via a telnet session
into the machine will restore it back to normal.
****************************************************************************/
static void restore_text_console(
    int console_id)
{
    if (ioctl(console_id, KDSETMODE, KD_TEXT) < 0)
        LOGWARN("ioctl(KDSETMODE) failed");
    _PM_restore_kb_mode();
}

/****************************************************************************
REMARKS:
Opens up the console device for output by finding an appropriate virutal
console that we can run on.
****************************************************************************/
PM_HWND PMAPI PM_openConsole(
    PM_HWND hwndUser,
    int device,
    int xRes,
    int yRes,
    int bpp,
    ibool fullScreen)
{
    struct vt_mode  vtm;
    struct vt_stat  vts;
    struct stat     sbuf;
    char            fname[30];

    /* Check if we have already opened the console */
    if (console_count++)
        return _PM_console_fd;

    /* Now, it would be great if we could use /dev/tty and see what it is
     * connected to. Alas, we cannot find out reliably what VC /dev/tty is
     * bound to. Thus we parse stdin through stderr for a reliable VC.
     */
    startup_vc = 0;
    for (_PM_console_fd = 0; _PM_console_fd < 3; _PM_console_fd++) {
        if (fstat(_PM_console_fd, &sbuf) < 0)
            continue;
        if (ioctl(_PM_console_fd, VT_GETMODE, &vtm) < 0)
            continue;
        if ((sbuf.st_rdev & 0xFF00) != 0x400)
            continue;
        if (!(sbuf.st_rdev & 0xFF))
            continue;
        tty_vc = sbuf.st_rdev & 0xFF;
        restore_text_console(_PM_console_fd);
        return _PM_console_fd;
        }
    if ((_PM_console_fd = open("/dev/console", O_RDWR)) < 0) {
        printf("open_dev_console: can't open /dev/console \n");
        exit(1);
        }
    if (ioctl(_PM_console_fd, VT_OPENQRY, &tty_vc) < 0)
        goto Error;
    if (tty_vc <= 0)
        goto Error;
    sprintf(fname, "/dev/tty%d", tty_vc);
    close(_PM_console_fd);

    /* Change our control terminal */
    setsid();

    /* We must use RDWR to allow for output... */
    if (((_PM_console_fd = open(fname, O_RDWR)) >= 0) &&
            (ioctl(_PM_console_fd, VT_GETSTATE, &vts) >= 0)) {
        if (!check_owner(vts.v_active))
            goto Error;
        restore_text_console(_PM_console_fd);

        /* Success, redirect all stdios */
        fflush(stdin);
        fflush(stdout);
        fflush(stderr);
        close(0);
        close(1);
        close(2);
        dup(_PM_console_fd);
        dup(_PM_console_fd);
        dup(_PM_console_fd);

        /* clear screen and switch to it */
        fwrite("\e[H\e[J", 6, 1, stderr);
        fflush(stderr);
        if (tty_vc != vts.v_active) {
            startup_vc = vts.v_active;
            ioctl(_PM_console_fd, VT_ACTIVATE, tty_vc);
            wait_vt_active(_PM_console_fd);
            }
        }
    return _PM_console_fd;

Error:
    if (_PM_console_fd > 2)
        close(_PM_console_fd);
    console_count = 0;
    PM_fatalError(
        "Not running in a graphics capable console,\n"
        "and unable to find one.\n");
    return -1;
}

#define FONT_C  0x10000     /* 64KB for font data                       */

/****************************************************************************
REMARKS:
Returns the size of the console state buffer.
****************************************************************************/
int PMAPI PM_getConsoleStateSize(void)
{
    if (!inited)
        PM_init();
    return PM_getVGAStateSize() + FONT_C*2;
}

/****************************************************************************
REMARKS:
Save the state of the Linux console.
****************************************************************************/
void PMAPI PM_saveConsoleState(void *stateBuf,int console_id)
{
    uchar   *regs = stateBuf;

    /* Save the current console font */
    if (ioctl(console_id,GIO_FONT,&regs[PM_getVGAStateSize()]) < 0)
        perror("ioctl(GIO_FONT)");

    /* Inform the Linux console that we are going into graphics mode */
    if (ioctl(console_id, KDSETMODE, KD_GRAPHICS) < 0)
        perror("ioctl(KDSETMODE)");

    /* Save state of VGA registers */
    PM_saveVGAState(stateBuf);
}

void PMAPI PM_setSuspendAppCallback(int (_ASMAPIP saveState)(int flags))
{
    /* TODO: Implement support for allowing console switching! */
}

/****************************************************************************
REMARKS:
Restore the state of the Linux console.
****************************************************************************/
void PMAPI PM_restoreConsoleState(const void *stateBuf,PM_HWND console_id)
{
    const uchar *regs = stateBuf;

    /* Restore the state of the VGA compatible registers */
    PM_restoreVGAState(stateBuf);

    /* Inform the Linux console that we are back from graphics modes */
    if (ioctl(console_id, KDSETMODE, KD_TEXT) < 0)
        LOGWARN("ioctl(KDSETMODE) failed");

    /* Restore the old console font */
    if (ioctl(console_id,PIO_FONT,&regs[PM_getVGAStateSize()]) < 0)
        LOGWARN("ioctl(KDSETMODE) failed");

    /* Coming back from graphics mode on Linux also restored the previous
     * text mode console contents, so we need to clear the screen to get
     * around this since the cursor does not get homed by our code.
     */
    fflush(stdout);
    fflush(stderr);
    printf("\033[H\033[J");
    fflush(stdout);
}

/****************************************************************************
REMARKS:
Close the Linux console and put it back to normal.
****************************************************************************/
void PMAPI PM_closeConsole(PM_HWND _PM_console_fd)
{
    /* Restore console to normal operation */
    if (--console_count == 0) {
        /* Re-activate the original virtual console */
        if (startup_vc > 0)
            ioctl(_PM_console_fd, VT_ACTIVATE, startup_vc);

        /* Close the console file descriptor */
        if (_PM_console_fd > 2)
            close(_PM_console_fd);
        _PM_console_fd = -1;
        }
}

void PM_setOSCursorLocation(int x,int y)
{
    /* Nothing to do in here */
}

/****************************************************************************
REMARKS:
Set the screen width and height for the Linux console.
****************************************************************************/
void PM_setOSScreenWidth(int width,int height)
{
    struct winsize  ws;
    struct vt_sizes vs;

    // Resize the software terminal
    ws.ws_col = width;
    ws.ws_row = height;
    ioctl(_PM_console_fd, TIOCSWINSZ, &ws);

    // And the hardware
    vs.v_rows = height;
    vs.v_cols = width;
    vs.v_scrollsize = 0;
    ioctl(_PM_console_fd, VT_RESIZE, &vs);
}

ibool PMAPI PM_setRealTimeClockHandler(PM_intHandler ih, int frequency)
{
    // TODO: Implement this for Linux
    return false;
}

void PMAPI PM_setRealTimeClockFrequency(int frequency)
{
    // TODO: Implement this for Linux
}

/****************************************************************************
REMARKS:
Stops the real time clock from ticking. Note that when we are actually
using IRQ0 instead, this functions does nothing (unlike calling
PM_setRealTimeClockFrequency directly).
****************************************************************************/
void PMAPI PM_stopRealTimeClock(void)
{
    PM_setRealTimeClockFrequency(0);
}

/****************************************************************************
REMARKS:
Restarts the real time clock ticking. Note that when we are actually using
IRQ0 instead, this functions does nothing.
****************************************************************************/
void PMAPI PM_restartRealTimeClock(
    int frequency)
{
    PM_setRealTimeClockFrequency(frequency);
}

void PMAPI PM_restoreRealTimeClockHandler(void)
{
    // TODO: Implement this for Linux
}

char * PMAPI PM_getCurrentPath(
    char *path,
    int maxLen)
{
    return getcwd(path,maxLen);
}

char PMAPI PM_getBootDrive(void)
{ return '/'; }

const char * PMAPI PM_getVBEAFPath(void)
{ return PM_getNucleusConfigPath(); }

const char * PMAPI PM_getNucleusPath(void)
{
    char *env = getenv("NUCLEUS_PATH");
    return env ? env : "/usr/lib/nucleus";
}

const char * PMAPI PM_getNucleusConfigPath(void)
{
    static char path[256];
    strcpy(path,PM_getNucleusPath());
    PM_backslash(path);
    strcat(path,"config");
    return path;
}

const char * PMAPI PM_getUniqueID(void)
{
    return PM_getMachineName();
}

const char * PMAPI PM_getMachineName(void)
{
    static char buf[128];
    gethostname(buf, 128);
    return buf;
}

void * PMAPI PM_getBIOSPointer(void)
{
    static uchar *zeroPtr = NULL;
    if (!zeroPtr)
        zeroPtr = PM_mapPhysicalAddr(0,0xFFFFF,true);
    return (void*)(zeroPtr + 0x400);
}

void * PMAPI PM_getA0000Pointer(void)
{
    /* PM_init maps in the 0xA0000 framebuffer region 1:1 with our
     * address mapping, so we can return the address here.
     */
    if (!inited)
        PM_init();
    return (void*)(0xA0000);
}

void * PMAPI PM_mapPhysicalAddr(ulong base,ulong limit,ibool isCached)
{
    uchar   *p;
    ulong   baseAddr,baseOfs;

    if (!inited)
        PM_init();
    if (base >= 0xA0000 && base < 0x100000)
        return (void*)base;
    if (!fd_mem && (fd_mem = open("/dev/mem", O_RDWR)) == -1)
        return NULL;

    /* Round the physical address to a 4Kb boundary and the limit to a
     * 4Kb-1 boundary before passing the values to mmap. If we round the
     * physical address, then we also add an extra offset into the address
     * that we return.
     */
    baseOfs = base & 4095;
    baseAddr = base & ~4095;
    limit = ((limit+baseOfs+1+4095) & ~4095)-1;
    if ((p = mmap(0, limit+1,
            PROT_READ | PROT_WRITE, MAP_SHARED,
            fd_mem, baseAddr)) == (void *)-1)
        return NULL;
    return (void*)(p+baseOfs);
}

void PMAPI PM_freePhysicalAddr(void *ptr,ulong limit)
{
    if ((ulong)ptr >= 0x100000)
        munmap(ptr,limit+1);
}

ulong PMAPI PM_getPhysicalAddr(void *p)
{
    // TODO: This function should find the physical address of a linear
    //       address.
    return 0xFFFFFFFFUL;
}

ibool PMAPI PM_getPhysicalAddrRange(void *p,ulong length,ulong *physAddress)
{
    // TODO: This function should find a range of physical addresses
    //       for a linear address.
    return false;
}

void PMAPI PM_sleep(ulong milliseconds)
{
    // TODO: Put the process to sleep for milliseconds
}

int PMAPI PM_getCOMPort(int port)
{
    // TODO: Re-code this to determine real values using the Plug and Play
    //       manager for the OS.
    switch (port) {
        case 0: return 0x3F8;
        case 1: return 0x2F8;
        }
    return 0;
}

int PMAPI PM_getLPTPort(int port)
{
    // TODO: Re-code this to determine real values using the Plug and Play
    //       manager for the OS.
    switch (port) {
        case 0: return 0x3BC;
        case 1: return 0x378;
        case 2: return 0x278;
        }
    return 0;
}

void * PMAPI PM_mallocShared(long size)
{
    return PM_malloc(size);
}

void PMAPI PM_freeShared(void *ptr)
{
    PM_free(ptr);
}

void * PMAPI PM_mapToProcess(void *base,ulong limit)
{ return (void*)base; }

void * PMAPI PM_mapRealPointer(uint r_seg,uint r_off)
{
    /* PM_init maps in the 0xA0000-0x100000 region 1:1 with our
     * address mapping, as well as all memory blocks in a 1:1 address
     * mapping so we can simply return the physical address in here.
     */
    if (!inited)
        PM_init();
    return (void*)MK_PHYS(r_seg,r_off);
}

void * PMAPI PM_allocRealSeg(uint size,uint *r_seg,uint *r_off)
{
    int     i;
    char    *r = (char *)REAL_MEM_BASE;

    if (!inited)
        PM_init();
    if (!mem_info.ready)
        return NULL;
    if (mem_info.count == REAL_MEM_BLOCKS)
        return NULL;
    size = (size + 15) & ~15;
    for (i = 0; i < mem_info.count; i++) {
        if (mem_info.blocks[i].free && size < mem_info.blocks[i].size) {
            insert_block(i);
            mem_info.blocks[i].size = size;
            mem_info.blocks[i].free = 0;
            mem_info.blocks[i + 1].size -= size;
            *r_seg = (uint)(r) >> 4;
            *r_off = (uint)(r) & 0xF;
            return (void *)r;
            }
        r += mem_info.blocks[i].size;
        }
    return NULL;
}

void PMAPI PM_freeRealSeg(void *mem)
{
    int     i;
    char    *r = (char *)REAL_MEM_BASE;

    if (!mem_info.ready)
        return;
    i = 0;
    while (mem != (void *)r) {
        r += mem_info.blocks[i].size;
        i++;
        if (i == mem_info.count)
            return;
        }
    mem_info.blocks[i].free = 1;
    if (i + 1 < mem_info.count && mem_info.blocks[i + 1].free) {
        mem_info.blocks[i].size += mem_info.blocks[i + 1].size;
        delete_block(i + 1);
        }
    if (i - 1 >= 0 && mem_info.blocks[i - 1].free) {
        mem_info.blocks[i - 1].size += mem_info.blocks[i].size;
        delete_block(i);
        }
}

#define DIRECTION_FLAG  (1 << 10)

static void em_ins(int size)
{
    unsigned int edx, edi;

    edx = context.vm.regs.edx & 0xffff;
    edi = context.vm.regs.edi & 0xffff;
    edi += (unsigned int)context.vm.regs.ds << 4;
    if (context.vm.regs.eflags & DIRECTION_FLAG) {
        if (size == 4)
            asm volatile ("std; insl; cld"
             : "=D" (edi) : "d" (edx), "0" (edi));
        else if (size == 2)
            asm volatile ("std; insw; cld"
             : "=D" (edi) : "d" (edx), "0" (edi));
        else
            asm volatile ("std; insb; cld"
             : "=D" (edi) : "d" (edx), "0" (edi));
        }
    else {
        if (size == 4)
            asm volatile ("cld; insl"
             : "=D" (edi) : "d" (edx), "0" (edi));
        else if (size == 2)
            asm volatile ("cld; insw"
             : "=D" (edi) : "d" (edx), "0" (edi));
        else
            asm volatile ("cld; insb"
             : "=D" (edi) : "d" (edx), "0" (edi));
        }
    edi -= (unsigned int)context.vm.regs.ds << 4;
    context.vm.regs.edi &= 0xffff0000;
    context.vm.regs.edi |= edi & 0xffff;
}

static void em_rep_ins(int size)
{
    unsigned int ecx, edx, edi;

    ecx = context.vm.regs.ecx & 0xffff;
    edx = context.vm.regs.edx & 0xffff;
    edi = context.vm.regs.edi & 0xffff;
    edi += (unsigned int)context.vm.regs.ds << 4;
    if (context.vm.regs.eflags & DIRECTION_FLAG) {
        if (size == 4)
            asm volatile ("std; rep; insl; cld"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        else if (size == 2)
            asm volatile ("std; rep; insw; cld"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        else
            asm volatile ("std; rep; insb; cld"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        }
    else {
        if (size == 4)
            asm volatile ("cld; rep; insl"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        else if (size == 2)
            asm volatile ("cld; rep; insw"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        else
            asm volatile ("cld; rep; insb"
             : "=D" (edi), "=c" (ecx)
             : "d" (edx), "0" (edi), "1" (ecx));
        }

    edi -= (unsigned int)context.vm.regs.ds << 4;
    context.vm.regs.edi &= 0xffff0000;
    context.vm.regs.edi |= edi & 0xffff;
    context.vm.regs.ecx &= 0xffff0000;
    context.vm.regs.ecx |= ecx & 0xffff;
}

static void em_outs(int size)
{
    unsigned int edx, esi;

    edx = context.vm.regs.edx & 0xffff;
    esi = context.vm.regs.esi & 0xffff;
    esi += (unsigned int)context.vm.regs.ds << 4;
    if (context.vm.regs.eflags & DIRECTION_FLAG) {
        if (size == 4)
            asm volatile ("std; outsl; cld"
             : "=S" (esi) : "d" (edx), "0" (esi));
        else if (size == 2)
            asm volatile ("std; outsw; cld"
             : "=S" (esi) : "d" (edx), "0" (esi));
        else
            asm volatile ("std; outsb; cld"
             : "=S" (esi) : "d" (edx), "0" (esi));
        }
    else {
        if (size == 4)
            asm volatile ("cld; outsl"
             : "=S" (esi) : "d" (edx), "0" (esi));
        else if (size == 2)
            asm volatile ("cld; outsw"
             : "=S" (esi) : "d" (edx), "0" (esi));
        else
            asm volatile ("cld; outsb"
             : "=S" (esi) : "d" (edx), "0" (esi));
        }

    esi -= (unsigned int)context.vm.regs.ds << 4;
    context.vm.regs.esi &= 0xffff0000;
    context.vm.regs.esi |= esi & 0xffff;
}

static void em_rep_outs(int size)
{
    unsigned int ecx, edx, esi;

    ecx = context.vm.regs.ecx & 0xffff;
    edx = context.vm.regs.edx & 0xffff;
    esi = context.vm.regs.esi & 0xffff;
    esi += (unsigned int)context.vm.regs.ds << 4;
    if (context.vm.regs.eflags & DIRECTION_FLAG) {
        if (size == 4)
            asm volatile ("std; rep; outsl; cld"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        else if (size == 2)
            asm volatile ("std; rep; outsw; cld"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        else
            asm volatile ("std; rep; outsb; cld"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        }
    else {
        if (size == 4)
            asm volatile ("cld; rep; outsl"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        else if (size == 2)
            asm volatile ("cld; rep; outsw"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        else
            asm volatile ("cld; rep; outsb"
             : "=S" (esi), "=c" (ecx)
             : "d" (edx), "0" (esi), "1" (ecx));
        }

    esi -= (unsigned int)context.vm.regs.ds << 4;
    context.vm.regs.esi &= 0xffff0000;
    context.vm.regs.esi |= esi & 0xffff;
    context.vm.regs.ecx &= 0xffff0000;
    context.vm.regs.ecx |= ecx & 0xffff;
}

static int emulate(void)
{
    unsigned char *insn;
    struct {
        unsigned int size : 1;
        unsigned int rep : 1;
        } prefix = { 0, 0 };
    int i = 0;

    insn = (unsigned char *)((unsigned int)context.vm.regs.cs << 4);
    insn += context.vm.regs.eip;

    while (1) {
#ifdef TRACE_IO
        traceAddr = ((ulong)context.vm.regs.cs << 16) + context.vm.regs.eip + i;
#endif
        if (insn[i] == 0x66) {
            prefix.size = 1 - prefix.size;
            i++;
            }
        else if (insn[i] == 0xf3) {
            prefix.rep = 1;
            i++;
            }
        else if (insn[i] == 0xf0 || insn[i] == 0xf2
             || insn[i] == 0x26 || insn[i] == 0x2e
             || insn[i] == 0x36 || insn[i] == 0x3e
             || insn[i] == 0x64 || insn[i] == 0x65
             || insn[i] == 0x67) {
            /* these prefixes are just ignored */
            i++;
            }
        else if (insn[i] == 0x6c) {
            if (prefix.rep)
                em_rep_ins(1);
            else
                em_ins(1);
            i++;
            break;
            }
        else if (insn[i] == 0x6d) {
            if (prefix.rep) {
                if (prefix.size)
                    em_rep_ins(4);
                else
                    em_rep_ins(2);
                }
            else {
                if (prefix.size)
                    em_ins(4);
                else
                    em_ins(2);
                }
            i++;
            break;
            }
        else if (insn[i] == 0x6e) {
            if (prefix.rep)
                em_rep_outs(1);
            else
                em_outs(1);
            i++;
            break;
            }
        else if (insn[i] == 0x6f) {
            if (prefix.rep) {
                if (prefix.size)
                    em_rep_outs(4);
                else
                    em_rep_outs(2);
                }
            else {
                if (prefix.size)
                    em_outs(4);
                else
                    em_outs(2);
                }
            i++;
            break;
            }
        else if (insn[i] == 0xec) {
            *((uchar*)&context.vm.regs.eax) = port_in(context.vm.regs.edx);
            i++;
            break;
            }
        else if (insn[i] == 0xed) {
            if (prefix.size)
                *((ulong*)&context.vm.regs.eax) = port_inl(context.vm.regs.edx);
            else
                *((ushort*)&context.vm.regs.eax) = port_inw(context.vm.regs.edx);
            i++;
            break;
            }
        else if (insn[i] == 0xee) {
            port_out(context.vm.regs.eax,context.vm.regs.edx);
            i++;
            break;
            }
        else if (insn[i] == 0xef) {
            if (prefix.size)
                port_outl(context.vm.regs.eax,context.vm.regs.edx);
            else
                port_outw(context.vm.regs.eax,context.vm.regs.edx);
            i++;
            break;
            }
        else
            return 0;
        }

    context.vm.regs.eip += i;
    return 1;
}

static void debug_info(int vret)
{
    int i;
    unsigned char *p;

    fputs("vm86() failed\n", stderr);
    fprintf(stderr, "return = 0x%x\n", vret);
    fprintf(stderr, "eax = 0x%08lx\n", context.vm.regs.eax);
    fprintf(stderr, "ebx = 0x%08lx\n", context.vm.regs.ebx);
    fprintf(stderr, "ecx = 0x%08lx\n", context.vm.regs.ecx);
    fprintf(stderr, "edx = 0x%08lx\n", context.vm.regs.edx);
    fprintf(stderr, "esi = 0x%08lx\n", context.vm.regs.esi);
    fprintf(stderr, "edi = 0x%08lx\n", context.vm.regs.edi);
    fprintf(stderr, "ebp = 0x%08lx\n", context.vm.regs.ebp);
    fprintf(stderr, "eip = 0x%08lx\n", context.vm.regs.eip);
    fprintf(stderr, "cs  = 0x%04x\n", context.vm.regs.cs);
    fprintf(stderr, "esp = 0x%08lx\n", context.vm.regs.esp);
    fprintf(stderr, "ss  = 0x%04x\n", context.vm.regs.ss);
    fprintf(stderr, "ds  = 0x%04x\n", context.vm.regs.ds);
    fprintf(stderr, "es  = 0x%04x\n", context.vm.regs.es);
    fprintf(stderr, "fs  = 0x%04x\n", context.vm.regs.fs);
    fprintf(stderr, "gs  = 0x%04x\n", context.vm.regs.gs);
    fprintf(stderr, "eflags  = 0x%08lx\n", context.vm.regs.eflags);
    fputs("cs:ip = [ ", stderr);
    p = (unsigned char *)((context.vm.regs.cs << 4) + (context.vm.regs.eip & 0xffff));
    for (i = 0; i < 16; ++i)
            fprintf(stderr, "%02x ", (unsigned int)p[i]);
    fputs("]\n", stderr);
    fflush(stderr);
}

static int run_vm86(void)
{
    unsigned int vret;

    for (;;) {
        vret = vm86(&context.vm);
        if (VM86_TYPE(vret) == VM86_INTx) {
            unsigned int v = VM86_ARG(vret);
            if (v == RETURN_TO_32_INT)
                return 1;
            pushw(context.vm.regs.eflags);
            pushw(context.vm.regs.cs);
            pushw(context.vm.regs.eip);
            context.vm.regs.cs = get_int_seg(v);
            context.vm.regs.eip = get_int_off(v);
            context.vm.regs.eflags &= ~(VIF_MASK | TF_MASK);
            continue;
            }
        if (VM86_TYPE(vret) != VM86_UNKNOWN)
            break;
        if (!emulate())
            break;
        }
    debug_info(vret);
    return 0;
}

#define IND(ereg) context.vm.regs.ereg = regs->ereg
#define OUTD(ereg) regs->ereg = context.vm.regs.ereg

void PMAPI DPMI_int86(int intno, DPMI_regs *regs)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    IND(eax); IND(ebx); IND(ecx); IND(edx); IND(esi); IND(edi);
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(intno);
    context.vm.regs.eip = get_int_off(intno);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
    OUTD(eax); OUTD(ebx); OUTD(ecx); OUTD(edx); OUTD(esi); OUTD(edi);
    regs->flags = context.vm.regs.eflags;
}

#define IN(ereg) context.vm.regs.ereg = in->e.ereg
#define OUT(ereg) out->e.ereg = context.vm.regs.ereg

int PMAPI PM_int86(int intno, RMREGS *in, RMREGS *out)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    IN(eax); IN(ebx); IN(ecx); IN(edx); IN(esi); IN(edi);
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(intno);
    context.vm.regs.eip = get_int_off(intno);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
    OUT(eax); OUT(ebx); OUT(ecx); OUT(edx); OUT(esi); OUT(edi);
    out->x.cflag = context.vm.regs.eflags & 1;
    return out->x.ax;
}

int PMAPI PM_int86x(int intno, RMREGS *in, RMREGS *out,
    RMSREGS *sregs)
{
    if (!inited)
        PM_init();
    if (intno == 0x21) {
        time_t today = time(NULL);
        struct tm *t;
        t = localtime(&today);
        out->x.cx = t->tm_year + 1900;
        out->h.dh = t->tm_mon + 1;
        out->h.dl = t->tm_mday;
        }
    else {
        unsigned int seg, off;
        seg = get_int_seg(intno);
        off = get_int_off(intno);
        memset(&context.vm.regs, 0, sizeof(context.vm.regs));
        IN(eax); IN(ebx); IN(ecx); IN(edx); IN(esi); IN(edi);
        context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
        context.vm.regs.cs = seg;
        context.vm.regs.eip = off;
        context.vm.regs.es = sregs->es;
        context.vm.regs.ds = sregs->ds;
        context.vm.regs.fs = sregs->fs;
        context.vm.regs.gs = sregs->gs;
        context.vm.regs.ss = context.stack_seg;
        context.vm.regs.esp = context.stack_off;
        pushw(DEFAULT_VM86_FLAGS);
        pushw(context.ret_seg);
        pushw(context.ret_off);
        run_vm86();
        OUT(eax); OUT(ebx); OUT(ecx); OUT(edx); OUT(esi); OUT(edi);
        sregs->es = context.vm.regs.es;
        sregs->ds = context.vm.regs.ds;
        sregs->fs = context.vm.regs.fs;
        sregs->gs = context.vm.regs.gs;
        out->x.cflag = context.vm.regs.eflags & 1;
        }
    return out->e.eax;
}

#define OUTR(ereg) in->e.ereg = context.vm.regs.ereg

void PMAPI PM_callRealMode(uint seg,uint off, RMREGS *in,
    RMSREGS *sregs)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    IN(eax); IN(ebx); IN(ecx); IN(edx); IN(esi); IN(edi);
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = seg;
    context.vm.regs.eip = off;
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    context.vm.regs.es = sregs->es;
    context.vm.regs.ds = sregs->ds;
    context.vm.regs.fs = sregs->fs;
    context.vm.regs.gs = sregs->gs;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
    OUTR(eax); OUTR(ebx); OUTR(ecx); OUTR(edx); OUTR(esi); OUTR(edi);
    sregs->es = context.vm.regs.es;
    sregs->ds = context.vm.regs.ds;
    sregs->fs = context.vm.regs.fs;
    sregs->gs = context.vm.regs.gs;
    in->x.cflag = context.vm.regs.eflags & 1;
}

void PMAPI PM_availableMemory(ulong *physical,ulong *total)
{
    FILE    *mem = fopen("/proc/meminfo","r");
    char    buf[1024];

    fgets(buf,1024,mem);
    fgets(buf,1024,mem);
    sscanf(buf,"Mem: %*d %*d %ld", physical);
    fgets(buf,1024,mem);
    sscanf(buf,"Swap: %*d %*d %ld", total);
    fclose(mem);
    *total += *physical;
}

void * PMAPI PM_allocLockedMem(uint size,ulong *physAddr,ibool contiguous,ibool below16M)
{
    // TODO: Implement this for Linux
    return NULL;
}

void PMAPI PM_freeLockedMem(void *p,uint size,ibool contiguous)
{
    // TODO: Implement this for Linux
}

void * PMAPI PM_allocPage(
    ibool locked)
{
    // TODO: Implement this for Linux
    return NULL;
}

void PMAPI PM_freePage(
    void *p)
{
    // TODO: Implement this for Linux
}

void PMAPI PM_setBankA(int bank)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    context.vm.regs.eax = 0x4F05;
    context.vm.regs.ebx = 0x0000;
    context.vm.regs.edx = bank;
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(0x10);
    context.vm.regs.eip = get_int_off(0x10);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
}

void PMAPI PM_setBankAB(int bank)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    context.vm.regs.eax = 0x4F05;
    context.vm.regs.ebx = 0x0000;
    context.vm.regs.edx = bank;
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(0x10);
    context.vm.regs.eip = get_int_off(0x10);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
    context.vm.regs.eax = 0x4F05;
    context.vm.regs.ebx = 0x0001;
    context.vm.regs.edx = bank;
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(0x10);
    context.vm.regs.eip = get_int_off(0x10);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
}

void PMAPI PM_setCRTStart(int x,int y,int waitVRT)
{
    if (!inited)
        PM_init();
    memset(&context.vm.regs, 0, sizeof(context.vm.regs));
    context.vm.regs.eax = 0x4F07;
    context.vm.regs.ebx = waitVRT;
    context.vm.regs.ecx = x;
    context.vm.regs.edx = y;
    context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
    context.vm.regs.cs = get_int_seg(0x10);
    context.vm.regs.eip = get_int_off(0x10);
    context.vm.regs.ss = context.stack_seg;
    context.vm.regs.esp = context.stack_off;
    pushw(DEFAULT_VM86_FLAGS);
    pushw(context.ret_seg);
    pushw(context.ret_off);
    run_vm86();
}

int PMAPI PM_enableWriteCombine(ulong base,ulong length,uint type)
{
#ifdef ENABLE_MTRR
    struct mtrr_sentry sentry;

    if (mtrr_fd < 0)
        return PM_MTRR_ERR_NO_OS_SUPPORT;
    sentry.base = base;
    sentry.size = length;
    sentry.type = type;
    if (ioctl(mtrr_fd, MTRRIOC_ADD_ENTRY, &sentry) == -1) {
        // TODO: Need to decode MTRR error codes!!
        return PM_MTRR_NOT_SUPPORTED;
        }
    return PM_MTRR_ERR_OK;
#else
    return PM_MTRR_ERR_NO_OS_SUPPORT;
#endif
}

/****************************************************************************
PARAMETERS:
callback    - Function to callback with write combine information

REMARKS:
Function to enumerate all write combine regions currently enabled for the
processor.
****************************************************************************/
int PMAPI PM_enumWriteCombine(
    PM_enumWriteCombine_t callback)
{
#ifdef ENABLE_MTRR
    struct mtrr_gentry gentry;

    if (mtrr_fd < 0)
        return PM_MTRR_ERR_NO_OS_SUPPORT;

    for (gentry.regnum = 0; ioctl (mtrr_fd, MTRRIOC_GET_ENTRY, &gentry) == 0;
         ++gentry.regnum) {
        if (gentry.size > 0) {
            // WARNING: This code assumes that the types in pmapi.h match the ones
            // in the Linux kernel (mtrr.h)
            callback(gentry.base, gentry.size, gentry.type);
        }
    }

    return PM_MTRR_ERR_OK;
#else
    return PM_MTRR_ERR_NO_OS_SUPPORT;
#endif
}

ibool PMAPI PM_doBIOSPOST(
    ushort axVal,
    ulong BIOSPhysAddr,
    void *copyOfBIOS,
    ulong BIOSLen)
{
    char        *bios_ptr = (char*)0xC0000;
    char        *old_bios;
    ulong       Current10, Current6D, *rvec = 0;
    RMREGS      regs;
    RMSREGS     sregs;

    /* The BIOS is mapped to 0xC0000 with a private memory mapping enabled
     * which means we have a copy on write scheme. Hence we simply copy
     * the secondary BIOS image over the top of the old one.
     */
    if (!inited)
        PM_init();
    if ((old_bios = PM_malloc(BIOSLen)) == NULL)
        return false;
    if (BIOSPhysAddr != 0xC0000) {
        memcpy(old_bios,bios_ptr,BIOSLen);
        memcpy(bios_ptr,copyOfBIOS,BIOSLen);
        }

    /* The interrupt vectors should already be mmap()'ed from 0-0x400 in PM_init */
    Current10 = rvec[0x10];
    Current6D = rvec[0x6D];

    /* POST the secondary BIOS */
    rvec[0x10] = rvec[0x42]; /* Restore int 10h to STD-BIOS */
    regs.x.ax = axVal;
    PM_callRealMode(0xC000,0x0003,&regs,&sregs);

    /* Restore interrupt vectors */
    rvec[0x10] = Current10;
    rvec[0x6D] = Current6D;

    /* Restore original BIOS image */
    if (BIOSPhysAddr != 0xC0000)
        memcpy(bios_ptr,old_bios,BIOSLen);
    PM_free(old_bios);
    return true;
}

int PMAPI PM_lockDataPages(void *p,uint len,PM_lockHandle *lh)
{
    p = p;  len = len;
    return 1;
}

int PMAPI PM_unlockDataPages(void *p,uint len,PM_lockHandle *lh)
{
    p = p;  len = len;
    return 1;
}

int PMAPI PM_lockCodePages(void (*p)(),uint len,PM_lockHandle *lh)
{
    p = p;  len = len;
    return 1;
}

int PMAPI PM_unlockCodePages(void (*p)(),uint len,PM_lockHandle *lh)
{
    p = p;  len = len;
    return 1;
}

PM_MODULE PMAPI PM_loadLibrary(
    const char *szDLLName)
{
    // TODO: Implement this to load shared libraries!
    (void)szDLLName;
    return NULL;
}

void * PMAPI PM_getProcAddress(
    PM_MODULE hModule,
    const char *szProcName)
{
    // TODO: Implement this!
    (void)hModule;
    (void)szProcName;
    return NULL;
}

void PMAPI PM_freeLibrary(
    PM_MODULE hModule)
{
    // TODO: Implement this!
    (void)hModule;
}

int PMAPI PM_setIOPL(
    int level)
{
    // TODO: Move the IOPL switching into this function!!
    return level;
}

void PMAPI PM_flushTLB(void)
{
    // Do nothing on Linux.
}

/****************************************************************************
REMARKS:
Do nothing for this OS.
****************************************************************************/
ulong PMAPI PM_setMaxThreadPriority(void)
{
    // TODO: Implement this on Linux!
    return 0;
}

/****************************************************************************
REMARKS:
Do nothing for this OS.
****************************************************************************/
void PMAPI PM_restoreThreadPriority(
    ulong oldPriority)
{
    (void)oldPriority;
}

/****************************************************************************
REMARKS:
Returns true if SDD is the active display driver in the system.
****************************************************************************/
ibool PMAPI PM_isSDDActive(void)
{
    return false;
}

/****************************************************************************
REMARKS:
This is not relevant to this OS.
****************************************************************************/
ibool PMAPI PM_runningInAWindow(void)
{
    // TODO: It would be nice to use this function to determine if a Linux
    //       console app is running under X11 or not...
    return false;
}

