/*
** File    : /djgpp/src/libc/posix/fcntl/ioctl.c
** Author  : Tom Demmer (demmer@lstm.ruhr-uni-bochum.de)
** SYNOPSIS: ioctl() for djgpp.
** Aim:
**   Provide an ioctl function that does what the naive DOS C programmer
**   expects.
**   Provide an ioctl function that does what the naive UNIX C programmer
**   expects.
**   Mangle and shredder them, roll them into one function and expect it
**   to work both ways.
**   If you really do that, you _ARE_ naive.
**
**   Generally, we follow this approach:
**   Commands for ioctl are 8 bit long because they must fit into AL.
**   The other 8 bit are usually unused. We put some flags inside, e.g.
**   if we need a transfer buffer, which register the DOS ioctl call wants
**   to return on saturdays and sun shine and such. The low byte of the
**   high word takes some more flags.
**
**
**
**   The UNIX ioctls have the command in the lower word (16-Bit).
**   In the high word, the upper three bits tell us if the parameters
**   are in or out parameters. Bit 15 means IN, Bit 14 means OUT, Bit
**   13 means VOID. Because there _must_ be one of them set, we can
**   distinguish UNIX from DOS ioctls. The other tell us the size of the
**   Input/Output parameters.
**   The high byte of the lower word will be used to specify the class
**   to which the command belongs, e.g. `T' for tty. Currently not
**   implemented.
**
**
**   It looks like this:
**   DOS:
**   000fffff ffffffff XFFiibRR CCCCCCCC
**   UNIX:
**   IOVxxxxx xSSSSSSS CCCCCCCC CCCCCCCC
**
**   With
**      C: Command part
**      X: Use Xfer buffer
**      F: What to return
**      f: Still free. Will be used for the generioc ioctl request.
**      i: Number of additional input args (dx,si,di)
**         I think CX is always used or can be safely set to 0
**      b: Brain Dead Flag for generic ioctl
**      R: reserved (might become the number of args someday)
**
**      S: sizeof input/output parameter
**      I: Input  flag
**      O: Output flag
**      V: Void Flag (no parameters)
**      x: Currently unused.
**
**   Right now the function supports the DOS ioctl calls up to 0x440b.
**   If you want to add another function, it goes like this:
**   #define DOS_PLAIN_your_func  0xff // e.g
**   #define DOS_your_func (DOS_PLAIN_your_func | \
**                          [DOS_XFER]| \
**                          [DOS_RETAX/DOS_RETDX/DOS_RETDISI] |\
**                          [DOS_XINARGS(xtra-args)])
**
**
** What is completely missing is the UNIX ioctl handler. I don't have the
** slightest idea about what to do here. Right now it just checks for an FSE
** and calls that if it exist. Otherwise we just return -1.
**
**
$Id: ioctl.c 0.5 1996/07/30 09:42:23 DEMMER Exp DEMMER $
$Log: ioctl.c $
 * Revision 0.5  1996/07/30  09:42:23  DEMMER
 * Minor code cleanups. Final beta.
 *
 * Revision 0.4  1996/07/29  13:03:29  DEMMER
 * Added va_end(). Probably uneeded for Intel machines. Anyway.
 *
 * Revision 0.3  1996/07/29  12:44:55  DEMMER
 * Split the header stuff from the source
 * Changed encoding bits
 *
 * Revision 0.2  1996/07/04  11:17:37  DEMMER
 * Revised flag scheme.
 * Correct some minor bugs
 *
 * Revision 0.1  1996/07/03  15:42:01  DEMMER
 * Cleaned up flag stuff.
 * Added UNIX ioctl test routines
 *
 * Revision 0.0  1996/07/03  13:53:23  DEMMER
 * Initial version
 *
*/



#include <libc/stubs.h>
#include <stdarg.h>
#include <stdlib.h>

#include <sys/ioctl.h>


      
/****************************************************************************/
/****************************************************************************/
/* S T A R T   O F   I M P L E M E N T A T I O N  ***************************/
/****************************************************************************/
/****************************************************************************/


#include <errno.h>
#include <go32.h>
#include <dpmi.h>
#include <sys/fsext.h>
#include <dos.h>
#include <libc/dosio.h>

/*
** This one might be added to <go32.h>
*/
#define __tb_size _go32_info_block.size_of_transfer_buffer

static int _dos_ioctl(int fd, int cmd, int argcx,int argdx,int argsi,int argdi,
                      int xarg)
{
/*
** Do an int0x21,0x44xx and such
*/
    __dpmi_regs r;
    int dos_selector = 0;
    int dos_segment = 0;

    /*
    ** Lower word of cmd -> al
    */
    r.x.ax = 0x4400 |(cmd &0xff);
    r.x.bx = fd;
    r.x.cx = argcx;
    r.x.dx = argdx;
    r.x.si = argsi;
    r.x.di = argdi;
    /*
    ** This one must have dl=0
    */
    if(cmd == DOS_SETDEVDATA) r.x.dx = argdx &0xf;
    /*
    ** Normally, there is a difference between reading and writing.
    ** But some functions require codes in the buffer on input already.
    ** So we transfer before int and after the int.
    ** We always use DX as a pointer to the buffer.
    ** I _do_ like clear APIs.
    */
    if(cmd & DOS_XFER){
        if(argcx <= __tb_size){ /* Can we use transfer buffer ? */
            dosmemput((void *)argdx,argcx, __tb);
            r.x.ds = (__tb>>4) &0xffff;
            r.x.dx = __tb &0xf;
        }
        else{
            /* No, we have to get some DOS mem ourselves */
            dos_segment = __dpmi_allocate_dos_memory((argcx+15)>>4,
                                                     &dos_selector);
            r.x.ds = dos_segment;
            if(-1 == dos_segment){
                errno = ENOMEM;
                return -1;
            }
        }
    }
    /*
    ** At DOS 3.2+ MS decided to change the API to an a bit more
    ** abstruse way. CX now does not hold the number of bytes
    ** to r/w, but is now a function_number:categegory_code.
    ** The size of the XBuffer is determined by the device driver
    ** which can set it to whatever it likes.
    ** We do not have second sight, so you'll have to add this size as a
    ** parameter after all the registers and the buffer pointer.
    */
    if( cmd & DOS_BRAINDEAD  ){
        if(xarg <= __tb_size){ /* Can we use transfer buffer ? */
            dosmemput((void *)argdx,xarg, __tb);
            r.x.ds = (__tb>>4) &0xffff;
            r.x.dx = __tb &0xf;
        }
        else{
            /* No, we have to get some DOS mem ourselves */
            dos_segment = __dpmi_allocate_dos_memory((xarg+15)>>4,
                                                      &dos_selector);
            r.x.ds = dos_segment;
            if(-1 == dos_segment){
                errno = ENOMEM;
                return -1;
            }
        }
    }
    /*
    ** Call DOS
    */
    if(-1 == __dpmi_int(0x21,&r)){
        if(dos_selector) __dpmi_free_dos_memory(dos_selector);
        errno = EINVAL;
        return -1;
    }
    errno = 0 ;
    /*
    ** DOS error?
    */
    if(r.x.flags &1){
        if(dos_selector) __dpmi_free_dos_memory(dos_selector);
        errno = __doserr_to_errno(r.x.ax);
        return -1;
    }
    /*
    ** move back to our buffers and find the return value
    */
    if(cmd & DOS_XFER){
        if(dos_selector){
            dosmemget(dos_segment<<4,argcx,(void*) argdx);
            __dpmi_free_dos_memory(dos_selector);
        }
        else
           dosmemget(__tb,argcx,(void *) argdx);
    }
    if(cmd & DOS_BRAINDEAD){
        if(dos_selector){
            dosmemget(dos_segment<<4,xarg,(void*) argdx);
            __dpmi_free_dos_memory(dos_selector);
        }
        else
           dosmemget(__tb,xarg,(void *) argdx);
    }
    /*
    ** Return the requested value or 0.
    */
    switch(cmd & DOS_RETMASK){
        case DOS_RETAX   :   return r.x.ax;
        case DOS_RETDX   :   return r.x.dx;
        case DOS_RETDISI :   return (r.x.di << 16) | r.x.si;
        default          :   return 0;
    }
    /* NOTREACHED */
}


static int _unix_ioctl(int fd,int cmd, int arg){
/*
** What to do _HERE_ ?
*/
    __FSEXT_Function *func = __FSEXT_get_function(fd);
    if(func){
        int rv;
        if(func(__FSEXT_ioctl,&rv, &fd))
           return rv;
    }

    /*
    ** All else fails so far.
    */
    errno =  ENOTTY;
    return -1;
}

/*
**
** The user callable entry point.
**
*/
int ioctl(int fd, int cmd, ...){
  va_list args;
  int argcx,argdx,argsi,argdi;
  int narg,xarg;
  va_start(args,cmd);
  
  if(__IS_UNIX_IOCTL(cmd)){
    int arg = va_arg(args, int);
    va_end(args);
#ifdef TEST
    {
    int inflg   = (cmd & IOC_IN)   == IOC_IN;
    int outflg  = (cmd & IOC_OUT)  == IOC_OUT;
    int voidflg = (cmd & IOC_VOID) == IOC_VOID;

    int size = (cmd >> 16) & IOCPARM_MASK;
    char i_class = (cmd &0xff00) >> 8;
    printf("Calling UNIX ioctl %x:\n"
           "Class:\t\t'%c'\n"
           "Inflag:\t\t%d\tOutflag:\t%d\tvoidflg:\t%d\n"
           "Size:\t\t%d\n",
           cmd & 0xffff,  i_class,
           inflg,outflg,voidflg,size);
    }
#endif
     return _unix_ioctl(fd,cmd,arg);
  }
  /* Handle a DOS request */
  /* extract arguments */
  narg = (cmd >> 12) & 3;
  argdx=argsi=argdi=xarg=0;
  argcx = va_arg(args,int);

  if (narg > 0)             argdx = va_arg(args,int);
  if (narg > 1)             argdi = va_arg(args,int);
  if (narg > 2)             argsi = va_arg(args,int);
  if (cmd & DOS_BRAINDEAD)  xarg  = va_arg(args,int);
  va_end(args);

  return _dos_ioctl(fd,cmd,argcx,argdx,argsi,argdi,xarg);
}




#ifdef TEST

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main (int argc, char **argv){
    int fd;
    int res;
    short *s;
    char mybuf[512];

    if(-1== (fd=open("NUL",O_RDWR))){
        return 2;
    }
    printf("fd: %d\n",fd);

    res = ioctl(fd,DOS_GETDEVDATA,0,0);
    printf("nul:\tDEV_DATA: %x\n",res ) ;
    res = ioctl(fileno(stdout),DOS_GETDEVDATA,0,0);
    printf("stdout:\tDEV_DATA: %x\n",res ) ;
    res = ioctl(fileno(stdin ),DOS_GETDEVDATA,0,0);
    printf("stdin:\tDEV_DATA: %x\n",res ) ;
    close(fd);
    fd = open("ioctl.c",O_RDONLY);
    res = ioctl(fd,DOS_GETDEVDATA,0,0);
    printf("ioctl.c:\tDEV_DATA: %x\n",res ) ;
    close(fd);
    fd=open("EMMQXXX0",O_RDONLY);

    mybuf[0] = '\0';
    mybuf[1] = '\0';
    res = ioctl(fd,DOS_RCVDATA,6,&mybuf);
    s = (short *) &mybuf;
    if (*s == 0x25) printf("EMM386 > 4.45\n");
    mybuf[0] = '\x02';
    mybuf[1] = '\0';
    res = ioctl(fd,DOS_RCVDATA,2,&mybuf);
    printf("res: %d\tEMM Version %d.%d\n",res,(int )mybuf[0],(int) mybuf[1]);
    close(fd);

/*
**
** The generic ioctls need an extra parameter, because CX no longer
** holds the size for the buffer.
**
*/
/*  It would work like this:

    res=ioctl(fd,DOS_GENCHARREQ,category_code,&mybuf,driver_cmd,driver_cmd,
              sizeof(buffer table));
    R.Brown's interrupt list is not too specific about the role of SI and DI.
    It is important for European DOS 4.0 and OS/2 comp box. He just says
    they are the "parameter to pass to driver", whatever that might mean.
    I guess you can safely set the to 0.

*/

    printf("\n\nTIOCGETD = %x\n",TIOCGETD); ioctl(0,TIOCGETD,NULL);
    printf("TIOCSETD = %x\n",TIOCSETD); ioctl(0,TIOCSETD,NULL);
    printf("TIOCHPCL = %x\n",TIOCHPCL); ioctl(0,TIOCHPCL,NULL);
    return 0;
}
#endif

