/*
 * kern.c - C-side experimental NetWare/DOS request wrappers
 *
 * kern_wasm.asm remains the production/reference implementation.
 * These C functions are test wrappers so we can inspect register setup and
 * slowly port proven pieces from ASM to C.
 */

#include <stdio.h>
#include <string.h>
#include <dos.h>

#include "net.h"

#if defined(__WATCOMC__)
#define KERN_C_CALL _Cdecl
#else
#define KERN_C_CALL
#endif

typedef struct {
  unsigned int in_ax;
  unsigned int in_bx;
  unsigned int in_cx;
  unsigned int in_dx;
  unsigned int in_si;
  unsigned int in_di;
  unsigned int in_ds;
  unsigned int in_es;

  unsigned int out_ax;
  unsigned int out_bx;
  unsigned int out_cx;
  unsigned int out_dx;
  unsigned int out_si;
  unsigned int out_di;
  unsigned int out_flags;

  void far *req_ptr;
  void far *repl_ptr;

  int rc;
} NET_CALL_C_DEBUG;

NET_CALL_C_DEBUG Net_Call_C_Last;

static void net_call_c_clear(void)
{
  memset(&Net_Call_C_Last, 0, sizeof(Net_Call_C_Last));
}

static void net_call_c_save_in(union REGS *r, struct SREGS *s,
                               void *req, void *repl)
{
  Net_Call_C_Last.in_ax = r->x.ax;
  Net_Call_C_Last.in_bx = r->x.bx;
  Net_Call_C_Last.in_cx = r->x.cx;
  Net_Call_C_Last.in_dx = r->x.dx;
  Net_Call_C_Last.in_si = r->x.si;
  Net_Call_C_Last.in_di = r->x.di;
  Net_Call_C_Last.in_ds = s->ds;
  Net_Call_C_Last.in_es = s->es;
  Net_Call_C_Last.req_ptr  = req;
  Net_Call_C_Last.repl_ptr = repl;
}

static void net_call_c_save_out(union REGS *r, unsigned int returned_ax)
{
  Net_Call_C_Last.out_ax = returned_ax;
  Net_Call_C_Last.out_bx = r->x.bx;
  Net_Call_C_Last.out_cx = r->x.cx;
  Net_Call_C_Last.out_dx = r->x.dx;
  Net_Call_C_Last.out_si = r->x.si;
  Net_Call_C_Last.out_di = r->x.di;
  Net_Call_C_Last.out_flags = r->x.cflag;
  Net_Call_C_Last.rc = returned_ax & 0x00ff;
}

/*
 * C equivalent of kern_wasm.asm Net_Call:
 *   AX    = func
 *   DS:SI = request
 *   ES:DI = reply
 *   int 21h
 *   return AL only, because kern_wasm.asm clears AH before return.
 */
int KERN_C_CALL Net_Call_C(unsigned int ax, void *req, void *repl)
{
  return Net_Call_CX(ax, 0, 0, 0, req, repl);
}

int KERN_C_CALL Net_Call_CX(unsigned int ax, unsigned int bx,
                            unsigned int cx, unsigned int dx,
                            void *req, void *repl)
{
  union REGS inregs;
  union REGS outregs;
  struct SREGS segregs;
  unsigned int ret_ax;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));
  memset(&segregs, 0, sizeof(segregs));
  net_call_c_clear();

  inregs.x.ax = ax;
  inregs.x.bx = bx;
  inregs.x.cx = cx;
  inregs.x.dx = dx;

  inregs.x.si = FP_OFF(req);
  inregs.x.di = FP_OFF(repl);
  segregs.ds  = FP_SEG(req);
  segregs.es  = FP_SEG(repl);

  net_call_c_save_in(&inregs, &segregs, req, repl);

  int86x(0x21, &inregs, &outregs, &segregs);

  ret_ax = outregs.x.ax & 0x00ff;     /* match ASM Net_Call return */

  net_call_c_save_out(&outregs, ret_ax);

  return (int)ret_ax;
}

/*
 * Experimental Novell Client/VLM AH=F2 wrapper derived from official FLAG.EXE
 * disassembly:
 *   AH    = F2h
 *   AL    = NCP function, e.g. 57h for NCP 87
 *   CX    = request length
 *   DX    = reply buffer length
 *   DS:SI = request buffer
 *   ES:DI = reply buffer
 */
int KERN_C_CALL Net_Call_F2_C(unsigned int function,
                              unsigned int req_len,
                              unsigned int repl_len,
                              void *req,
                              void *repl)
{
  union REGS inregs;
  union REGS outregs;
  struct SREGS segregs;
  unsigned int ret_ax;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));
  memset(&segregs, 0, sizeof(segregs));
  net_call_c_clear();

  inregs.h.ah = 0xF2;
  inregs.h.al = (unsigned char)(function & 0xff);
  inregs.x.cx = req_len;
  inregs.x.dx = repl_len;
  inregs.x.si = FP_OFF(req);
  inregs.x.di = FP_OFF(repl);
  segregs.ds  = FP_SEG(req);
  segregs.es  = FP_SEG(repl);

  net_call_c_save_in(&inregs, &segregs, req, repl);

  int86x(0x21, &inregs, &outregs, &segregs);

  /*
   * Novell wrapper behavior:
   *   xor ah,ah
   *   or al,al
   *   if al != 0: ah=89h
   * Return AL only for C caller, but keep 89xx in debug out_ax.
   */
  ret_ax = outregs.x.ax & 0x00ff;
  if (ret_ax)
    ret_ax |= 0x8900;

  net_call_c_save_out(&outregs, ret_ax);

  return (int)(ret_ax & 0x00ff);
}

/*
 * Fully generic INT 21h register test wrapper.
 * Used for reproducing the NWCREQUEST/NWCSHELLREQ register calls seen in
 * DeveloperNet 1997 clndos16.lib.
 */
int KERN_C_CALL Net_Call_F2X_C(unsigned int ax,
                               unsigned int bx,
                               unsigned int cx,
                               unsigned int dx,
                               void *req,
                               void *repl)
{
  union REGS inregs;
  union REGS outregs;
  struct SREGS segregs;
  unsigned int ret_ax;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));
  memset(&segregs, 0, sizeof(segregs));
  net_call_c_clear();

  inregs.x.ax = ax;
  inregs.x.bx = bx;
  inregs.x.cx = cx;
  inregs.x.dx = dx;
  inregs.x.si = FP_OFF(req);
  inregs.x.di = FP_OFF(repl);
  segregs.ds  = FP_SEG(req);
  segregs.es  = FP_SEG(repl);

  net_call_c_save_in(&inregs, &segregs, req, repl);

  int86x(0x21, &inregs, &outregs, &segregs);

  ret_ax = outregs.x.ax & 0x00ff;
  if (ret_ax)
    ret_ax |= 0x8900;

  net_call_c_save_out(&outregs, ret_ax);

  return (int)(ret_ax & 0x00ff);
}


void KERN_C_CALL Net_Call_C_Dump(void)
{
  fprintf(stdout, "NETCALLC in : AX=%04X BX=%04X CX=%04X DX=%04X DS:SI=%04X:%04X ES:DI=%04X:%04X\n",
          Net_Call_C_Last.in_ax,
          Net_Call_C_Last.in_bx,
          Net_Call_C_Last.in_cx,
          Net_Call_C_Last.in_dx,
          Net_Call_C_Last.in_ds,
          Net_Call_C_Last.in_si,
          Net_Call_C_Last.in_es,
          Net_Call_C_Last.in_di);

  fprintf(stdout, "NETCALLC out: AX=%04X BX=%04X CX=%04X DX=%04X SI=%04X DI=%04X CF=%04X RC=%d\n",
          Net_Call_C_Last.out_ax,
          Net_Call_C_Last.out_bx,
          Net_Call_C_Last.out_cx,
          Net_Call_C_Last.out_dx,
          Net_Call_C_Last.out_si,
          Net_Call_C_Last.out_di,
          Net_Call_C_Last.out_flags,
          Net_Call_C_Last.rc);
}

unsigned int KERN_C_CALL Net_Call_C_GetDebug(unsigned int idx)
{
  switch (idx) {
    case 0:  return Net_Call_C_Last.in_ax;
    case 1:  return Net_Call_C_Last.in_bx;
    case 2:  return Net_Call_C_Last.in_cx;
    case 3:  return Net_Call_C_Last.in_dx;
    case 4:  return Net_Call_C_Last.in_ds;
    case 5:  return Net_Call_C_Last.in_si;
    case 6:  return Net_Call_C_Last.in_es;
    case 7:  return Net_Call_C_Last.in_di;
    case 8:  return Net_Call_C_Last.out_ax;
    case 9:  return Net_Call_C_Last.out_bx;
    case 10: return Net_Call_C_Last.out_cx;
    case 11: return Net_Call_C_Last.out_dx;
    case 12: return Net_Call_C_Last.out_si;
    case 13: return Net_Call_C_Last.out_di;
    case 14: return Net_Call_C_Last.out_flags;
    case 15: return (unsigned int)Net_Call_C_Last.rc;
  }
  return 0xffff;
}
