/*
 * Micro-X -- an X server for DOS
 * Copyright (C) 1994 StarNet Communications Corp.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * StarNet Communications Corp.
 * 550 Lakeside Dr. #3
 * Sunnyvale CA 94086 US
 * http://www.starnet.com
 * x-dos@starnet.com
 */

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <i86.h>
#include <string.h>
#include "rpc.h"
#include "exe.h"
#include "defs.h"

struct rminfo {
    long	edi, esi, ebp, res;
    long	ebx, edx, ecx, eax;
    unsigned short	flags;
    unsigned short	es, ds, fs, gs, ip, cs, sp, ss;
};

extern long get_base(short);

static char do_envp(void);

static struct rpc mxrpc;
/*
 * Load file as an overlay and return a "handle".
 */
struct rpc *
rpc_init(const char *file)
{
    struct rpc *rpc;
    int fd, i;
    struct exe exe;
    union REGS regs;
    struct SREGS sregs;
    char _far *fp;
    /* real-mode interrupt */
    struct rminfo rmi;
    static char did_env = 0;

    fd = open(file, O_RDONLY);
    if (fd < 0)
	return 0;
    i = read(fd, &exe, sizeof exe);
    close(fd);
    if (i < sizeof exe)
	return 0;
    if (exe.magic != MAGIC_EXE) {
	printf("%s: wrong magic number %#x\n", file, exe.magic);
	return 0;
    }

    rpc = &mxrpc;	/* We could call malloc. */

    i = (long)exe.size * 512 + exe.extra - exe.header_size * 16;

    if (!did_env) {
	did_env = do_envp();
    }

    /* Allocate some memory for the xfer buffer */
    regs.w.ax = 0x100;
    regs.w.bx = MAX_IO/16;
    int386(0x31, &regs, &regs);
    if (regs.w.cflag) {
	printf("%s: can't allocate buffer memory\n", file);
	return 0;
    }
    rpc->memory = regs.w.ax;

    /* Allocate memory for our overlay. */
    regs.w.ax = 0x100;
    regs.w.bx = (i + 15) / 16;
    int386(0x31, &regs, &regs);
    if (regs.w.cflag) {
	printf("%s: can't allocate %d bytes of memory\n", file, i);
	return 0;
    }
    fp = MK_FP(regs.w.dx, 0);
    rpc->segment = regs.w.ax;
    /* Fill in paramater block */
    ((short _far *)fp)[0] = regs.w.ax;
    ((short _far *)fp)[1] = regs.w.ax;
    _fmemcpy(fp+4, file, strlen(file) + 1);
    memset(&rmi, 0, sizeof rmi);
    rmi.eax = 0x4b03;	/* EXEC overlay */
    rmi.es = rpc->segment;
    rmi.ds = rpc->segment;
    rmi.edx = 4;		/* filename pointer */
    rmi.ebx = 0;		/* parameter block pointer */
    regs.w.ax = 0x300;	/* real-mode interrupt */
    regs.h.bl = 0x21;	/* DOS interrupt */
    regs.h.bh = 0;
    regs.w.cx = 0;
    segread(&sregs);
    sregs.es = FP_SEG(&rmi);
    regs.x.edi = FP_OFF(&rmi);
    int386x(0x31, &regs, &regs, &sregs);
    if (rmi.flags & 1) {
	printf("can't exec %s: error %d\n", (short)rmi.eax);
	return 0;
    }
    /* Since we can't do a real procedure call, install the real-mode
     * procedure as an interrupt.
     */
    rpc->intno = 0x63;	/* XXX */
    regs.x.eax = 0x200;		/* get real-mode interrupt */
    regs.h.bl = rpc->intno;
    int386(0x31, &regs, &regs);
    rpc->oldsegment = regs.w.cx;
    rpc->oldoffset = regs.w.dx;
    regs.x.eax = 0x201;		/* set real-mode interrupt */
    regs.h.bl = rpc->intno;
    regs.w.cx = exe.cs + rpc->segment;
    regs.w.dx = exe.ip;
    int386(0x31, &regs, &regs);
    if (regs.w.cflag) {
	printf("can't set interrupt %#x\n", rpc->intno);
	return 0;
    }
    return rpc;
}

/*
 *	func:	Function.  Pass in AX.
 *	arg:	First argument to function.  Pass in BX.
 *	len:	Length of data passed.  Pass in CX.
 *	inout:	Which direction to copy.
 *  The real mode function returns:
 *	AX:	function result.
 *	BX:	neterrno if error.
 *	CX:	errno if neterror == NETERR_ERRNO
 *	CX:	bytes returned if successful
 */
int
rpc_call(struct rpc *rpc, short func, short arg, short len, short inout, void *buffer)
{
    struct rminfo rmi;
    union REGS regs;
    struct SREGS sregs;
    void *real;

    memset(&rmi, 0, sizeof rmi);
    if (buffer) {
	if (len > MAX_IO)
	    len = MAX_IO;
	rmi.ds = rpc->memory;
	real = (void *)((long)rmi.ds << 4);
	if (inout & RPC_WRITE)
	    memcpy(real, buffer, len);
    }
    rmi.eax = func;
    rmi.ebx = arg;
    rmi.ecx = len;
    regs.w.ax = 0x300;	/* simulate real-mode interrupt */
    regs.h.bl = rpc->intno;
    regs.h.bh = 0;
    regs.w.cx = 0;
    segread(&sregs);
    sregs.es = FP_SEG(&rmi);
    regs.x.edi = FP_OFF(&rmi);
    int386x(0x31, &regs, &regs, &sregs);
    if ((short)rmi.eax < 0) {
	neterrno = (short)rmi.ebx;
	if (neterrno == NETERR_ERRNO)
	    map_errno((short)rmi.ecx);
	return (short)rmi.eax;
    }
    if (inout & RPC_READ) {
	if ((short)rmi.ecx > len)
	    rmi.ecx = len;
	memcpy(buffer, real, (short)rmi.ecx);
    }
    return rmi.eax;
}

void
rpc_close(const struct rpc *rpc)
{
    union REGS regs;

    /* Set interrupt back to old vector. */
    regs.x.eax = 0x201;		/* set real-mode interrupt */
    regs.h.bl = rpc->intno;
    regs.w.cx = rpc->oldsegment;
    regs.w.dx = rpc->oldoffset;
    int386(0x31, &regs, &regs);
}

static char
do_envp()
{
    union REGS regs;
    unsigned short _far *envp;
    unsigned long real;

    regs.h.ah = 0x62;	/* get PSP */
    int386(0x21, &regs, &regs);
    if (regs.x.cflag)
	return 0;
    envp = MK_FP(regs.w.bx, 0x2c);
    /* Now convert to a real-mode segment. */
    real = get_base(*envp);
    if (!real)
	return 0;
    /* Convert from linear memory to real-mode segment. */
    real >>= 4;
    /* Stick it back in the PSP to the real-mode progam can see it. */
    *envp = real;
    /* Don't try this again! */
    return 1;
}
