/* Terminal lower half for running on a Mach device.
   Copyright (C) 1994 Free Software Foundation

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */


#include "term.h"
#include <device/device.h>
#include <device/device_request.h>
#include "device_reply_S.h"
#include "notify_S.h"

/* The Mach values of the B* bits are different from the Hurd/BSD ones;
   we use the Hurd/BSD ones and a conversion table to give Mach ones
   to the kernel.  Of course, the kernel noods to be fixed to use the
   Hurd/BSD ones at some point. */
#undef B0
#undef B50
#undef B75
#undef B110
#undef B134
#undef B150
#undef B200
#undef B300
#undef B600
#undef B1200
#undef B1800
#undef B2400
#undef B4800
#undef B9600
#undef EXTA
#undef EXTB
#include <device/tty_status.h>

/* This interface doesn't function quite right because of the following
   deficiencies in the Mach TTY code.  

1. device_write returns the RPC immediately (or after a few characters have 
   been sent) instead of waiting for the transmission to complete.  As a
   result, we can't treat the output silo properly.  Notably, drain is
   unimplemented.  Sometimes it does block (notably, when the kernel's 
   queue is full)--so it's not so nice that we don't have to worry about 
   handling reply messages asynchronously.

2. There is no way to reliably interrupt a pending input operation except by
   deallocating its reply port (which then races against reading a reply
   from an already completed read).

3. Mach provides no way to set the number of bits per byte, or to do
   parity generation and not parity checking.

4. Mach doesn't let us control the modem like we'd like.
   Luckily, it implements what we need; it's just kludged into
   device_open and device_close.  We assume that nobody else is
   using the device; else we will lose. 
*/


/* Speed conversion table; indexed by Mach speed number, value is 
   Hurd/BSD speed number. */
static const int speed_convert[] =
{
  0,
  50,
  75,
  110,
  134,
  150,
  200,
  300,
  600,
  1200,
  1800,
  2400,
  4800,
  9600,
  19200,
  38400,
};

/* Port to Mach device */
static device_t pterm;
static char *pterm_name;
static mach_port_t master_device;

/* Reply port for calls to pterm */
static struct port_info *read_reply;
static struct port_info *write_reply;
static struct port_info *open_reply;


/* Physical output silo. */
static struct queue *osilo;

/* State buts */
static int pstate;
#define PENDING_OUTPUT 0x00000001 /* output request outstanding */
#define OUTPUT_SUSP    0x00000002 /* Don't transmit anything */
#define PENDING_INPUT  0x00000004 /* input request outstanding */
#define SILO_FULL      0x00000008 /* don't write anything for now */
#define PENDING_OPEN   0x00000010 /* open request outstanding */
#define OUTPUT_BATCH   0x00000020 /* don't bother sending immediately */

/* Function table */
static void devio_transmit (int);
static void devio_set_break ();
static void devio_clear_break ();
static void devio_drain_output (int);
static void devio_drop_output ();
static int devio_outsize ();
static void devio_stop_sending ();
static void devio_start_sending ();
static void devio_stop_reading ();
static void devio_start_reading ();
static void devio_start_batch ();
static void devio_stop_batch ();
static void devio_set_bits ();
static void devio_shutdown ();
static void devio_mdmctl (int, int);
static int devio_mdmstate ();
static void devio_dtrflicker ();
static error_t devio_dtron ();
static struct lowerhalf devio_table =
{
  devio_transmit,
  devio_set_break,
  devio_clear_break,
  devio_drain_output,
  devio_drop_output,
  devio_outsize,
  devio_stop_sending,
  devio_start_sending,
  devio_stop_reading,
  devio_start_reading,
  devio_start_batch,
  devio_stop_batch,
  devio_set_bits,
  devio_shutdown,
  devio_mdmctl,
  devio_mdmstate,
  devio_dtrflicker,
  devio_dtron,
};

/* Open the devio channel for Mach device DEV on master device port 
   MASTER.  */
error_t
devio_open (char *dev, mach_port_t master)
{
  error_t err;
  struct tty_status ttystat;
  u_int statuscnt;
  mach_port_t foo;

  pstate = 0;
  pterm = MACH_PORT_NULL;
  
  /* Find out whether we can open the device at all so that
     we can return some error here. */
  err = device_open (master, D_READ|D_WRITE|D_NODELAY, dev, &foo);
  if (err && err != D_WOULD_BLOCK)
    return err;

  /* Record args for later use. */
  pterm_name = malloc (strlen (dev) + 1);
  strcpy (pterm_name, dev);
  master_device = master;

  /* The kernel claims to have knowledge about some devices; so
     initialize termstate as it thinks wise. */
  statuscnt = TTY_STATUS_COUNT;
  device_get_status (foo, TTY_STATUS, (dev_status_t)&ttystat, &statuscnt);
  if (statuscnt != TTY_STATUS_COUNT)
    return ENODEV;
  if (ttystat.tt_flags & TF_ECHO)
    termstate.c_lflag |= ECHO | ECHONL | ECHOKE | ECHOCTL;
  if (ttystat.tt_flags & TF_CRMOD)
    termstate.c_oflag |= ONLCR;
  if (ttystat.tt_flags & TF_XTABS)
    termstate.c_oflag |= OXTABS;

  /* Close the device back; we don't want to assert DTR yet.   */
  device_close (foo);
  mach_port_deallocate (mach_task_self (), foo);

  read_reply = ports_allocate_port (sizeof (struct port_info), PT_DEVRD);
  ports_port_ref (read_reply);
  write_reply = ports_allocate_port (sizeof (struct port_info), PT_DEVWR);
  ports_port_ref (write_reply);
  open_reply = ports_allocate_port (sizeof (struct port_info), PT_DEVOP);
  ports_port_ref (open_reply);

  osilo = create_queue ();

  lowerhalf = &devio_table;

  return 0;
}

/* Close the devio channel */
void
devio_shutdown ()
{
  if (pterm != MACH_PORT_NULL)
    {
      device_close (pterm);
      mach_port_deallocate (mach_task_self (), pterm);
    }
  
  mach_port_deallocate (mach_task_self (), master_device);
  free (pterm_name);

  ports_done_with_port (read_reply);
  ports_done_with_port (write_reply);
  ports_done_with_port (open_reply);

  clear_queue (osilo);
  free (osilo);
}

/* Send as much data as we can to the physical device. */
static inline void
send_output ()
{
  int err;
  int amt;
  
  if ((pstate & (PENDING_OUTPUT | OUTPUT_SUSP))
      || !qsize (osilo))
    return;
  
  if ((pstate & OUTPUT_BATCH) && qsize (osilo) < HIGH_WATER_MARK / 2)
    return;

  assert (pterm != MACH_PORT_NULL);

  /* XXX We should use out-of-band transmission when we have
     so much to write. */

  if (qsize (osilo) > IO_INBAND_MAX)
    amt = IO_INBAND_MAX;
  else
    amt = qsize (osilo);
  
  err = device_write_request_inband (pterm, write_reply->port_right, 0, 0, 
				     osilo->qs, amt);
  if (err == MACH_SEND_INVALID_DEST)
    report_hangup ();
  else if (!err)
    pstate |= PENDING_OUTPUT;
}

/* Queue a read request */
inline void
fetch_input ()
{
  error_t err;
  
  assert (!(pstate & PENDING_INPUT));
  assert (pterm != MACH_PORT_NULL);

  err = device_read_request_inband (pterm, read_reply->port_right, 
				    0, 0, vm_page_size);

  if (err == MACH_SEND_INVALID_DEST || err == D_IO_ERROR)
    report_hangup ();
  else if (!err)
    pstate |= PENDING_INPUT;
  else
    report_hangup ();		/* what else should be do here??? XXX */
}
  
/* Send a single character out. */
void
devio_transmit (int c)
{
  extern char const char_parity[]; /* from input.c */

  /* We can't have Mach do parity generation, because then we lose
     the ability to do parity detection ourselves.  Lose, lose, lose. */
  if (termstate.c_cflag & PARENB)
    {
      c &= 0x7f;
      if ((char_parity[c] && (termstate.c_cflag & PARODD))
	  || (!char_parity[c] && !(termstate.c_cflag & PARODD)))
	c |= 0x80;
    }

  /* Add it to the output silo */
  enqueue_char (c, &osilo);

  if (qsize (osilo) > HIGH_WATER_MARK)
    {
      pstate |= SILO_FULL;
      stop_writing ();
    }

  send_output ();
}

/* Cause a break */
void
devio_set_break ()
{
  device_set_status (pterm, TTY_SET_BREAK, 0, 0);
}

/* Resume after break */
void
devio_clear_break ()
{
  device_set_status (pterm, TTY_CLEAR_BREAK, 0, 0);
}

/* Call drain_finished when all currently pending output is finished. */
void
devio_drain_output (int code)
{
  /* There is no way to correctly implement this in the current Mach 
     TTY device code.  XXX  */
  drain_finished (code);
}

/* Drop pending output. */
void
devio_drop_output ()
{
  int val = D_WRITE;

  if (pstate & PENDING_OUTPUT)
    {
      /* Deallocating the reply port tells Mach to punt the outstanding
	 request. */
      ports_reallocate_port (write_reply);
      pstate &= ~PENDING_OUTPUT;
    }
  device_set_status (pterm, TTY_FLUSH, &val, TTY_FLUSH_COUNT);
  osilo->qs = osilo->qp = osilo->chars;
}

/* Report output queue size */
int
devio_outsize ()
{
  /* We have no way to find out the kernel's state, so 
     just report what we can.  I think it's better to return
     a number that's too big than too small, anyway. */
  return qsize (osilo);
}

/* Stop sending output */
void
devio_stop_sending ()
{
  device_set_status (pterm, TTY_STOP, 0, 0);
  pstate |= OUTPUT_SUSP;
}

/* Start sending output */
void
devio_start_sending ()
{
  if (pstate & OUTPUT_SUSP)
    {
      device_set_status (pterm, TTY_START, 0, 0);
      pstate &= ~OUTPUT_SUSP;
      send_output ();
    }
}

/* Stop reading input */
void
devio_stop_reading ()
{
  /* There might already be a reply message waiting; there is no
     way to distinguish between this case and a pending read reliably.  
     Argh.  So we don't even try.  This isn't *terrible* because
     the users of this function doen't depend on perfect 
     performance here. XXX */
}

/* Start reading input */
void
devio_start_reading ()
{
  if (!(pstate & PENDING_INPUT))
    fetch_input ();
}

/* Start batching output */
void
devio_start_batch ()
{
  pstate |= OUTPUT_BATCH;
}

/* Stop batching output */
void
devio_stop_batch ()
{
  pstate &= ~OUTPUT_BATCH;
  send_output ();
}

void
devio_dtrflicker ()
{
  /* device_close will do this for us, because we set the
     TF_HUPCLS bit. */
  device_close (pterm);
  mach_port_deallocate (mach_task_self (), pterm);
  pterm = MACH_PORT_NULL;
  report_hangup ();
}

void
devio_mdmctl (int how, int bits)
{
  int oldbits, newbits;
  int cnt;
  if ((how == MDMCTL_BIS) || (how == MDMCTL_BIC))
    {
      cnt = TTY_MODEM_COUNT;
      device_get_status (pterm, TTY_MODEM, (dev_status_t) &oldbits, &cnt);
      if (cnt < TTY_MODEM_COUNT)
	oldbits = 0;		/* what else can we do? */
    }

  if (how == MDMCTL_BIS)
    newbits = (oldbits | bits);
  else if (how == MDMCTL_BIC)
    newbits = (oldbits &= ~bits);
  else
    newbits = bits;
  
  device_set_status (pterm, TTY_MODEM, 
		     (dev_status_t) &newbits, TTY_MODEM_COUNT);
}

int
devio_mdmstate ()
{
  int bits, cnt;
  
  cnt = TTY_MODEM_COUNT;
  device_get_status (pterm, TTY_MODEM, (dev_status_t) &bits, &cnt);
  if (cnt != TTY_MODEM_COUNT)
    return 0;
  else
    return bits;
}

/* Assert DTR.  We do this by starting a device_open.  When that returns, 
   the modem has answered, and we call report_connect. */
error_t
devio_dtron ()
{ 
  error_t err;
  
  if ((pterm != MACH_PORT_NULL) || (pstate & PENDING_OPEN))
    return 0;

  /* Drop the current reply ports */
  ports_reallocate_port (read_reply);
  ports_reallocate_port (write_reply);
  ports_reallocate_port (open_reply);
  
  pstate &= ~(PENDING_OUTPUT|PENDING_INPUT);
  
  pstate |= PENDING_OPEN;
  err = device_open_request (master_device, open_reply->port_right, 
			     D_READ|D_WRITE, pterm_name);

  return err;
}

kern_return_t
device_open_reply (mach_port_t replyport,
		   int returncode,
		   mach_port_t device)
{
  int i;
  int ispeed = 0, ospeed = 0;
  struct tty_status ttystat;
  int count = TTY_STATUS_COUNT;

  if (replyport != open_reply->port_right)
    return EOPNOTSUPP;
  if (returncode != 0)
    {
      report_open_error (returncode);
      return 0;
    }

  assert (pterm == MACH_PORT_NULL);
  pterm = device;

  if (!(termstate.c_cflag & CIGNORE))
    {
      for (i = 0; i < NSPEEDS && (!ispeed || !ospeed); i++)
	{
	  if (speed_convert[i] == termstate.__ispeed)
	    ispeed = i;
	  if (speed_convert[i] == termstate.__ospeed)
	    ospeed = i;
	}
  
      /* They must have already been validated by devio_set_bits. */
      assert (ispeed && ospeed);
      ttystat.tt_ispeed = ispeed;
      ttystat.tt_ospeed = ospeed;
    }
  else
    device_get_status (pterm, TTY_STATUS, (dev_status_t)&ttystat, &count);
  
  ttystat.tt_breakc = 0;
  ttystat.tt_flags = TF_ANYP | TF_LITOUT | TF_NOHANG | TF_HUPCLS;

  device_set_status (pterm, TTY_STATUS, 
		     (dev_status_t)&ttystat, TTY_STATUS_COUNT);

  pstate &= ~PENDING_OPEN;

  fetch_input ();

  report_connect ();

  return 0;
}  
  
void
devio_set_bits ()
{
  struct tty_status ttystat;
  int ispeed = 0, ospeed = 0;
  int i;

  assert (!(termstate.c_cflag & CIGNORE));

  /* The Mach terminal interface only supports eight bits or seven
     with parity.  Sigh.  */
  termstate.c_cflag &= ~CSIZE;
  
  if (termstate.c_cflag & PARENB)
    termstate.c_cflag |= CS7;
  else
    termstate.c_cflag |= CS8;
  
  /* Mach doesn't allow us to turn off the receiver. */
  termstate.c_cflag |= CREAD;

  /* If one of the speeds is unspecified in termstate, then
     ask the kernel for its speeds.  */
  if (!termstate.__ispeed || !termstate.__ospeed)
    {
      int cnt = TTY_STATUS_COUNT;
      error_t err;
      err = device_get_status (pterm, TTY_STATUS,
			       (dev_status_t) &ttystat, &cnt);
      if (!err && (cnt == TTY_STATUS_COUNT))
	{
	  ispeed = ttystat.tt_ispeed;
	  ospeed = ttystat.tt_ospeed;
	}
      /* For those which are unset, set them from the kernel's info. 
       If the kernel has no idea, use 9600 bps. */
      if (!termstate.__ispeed)
	{
	  if (ispeed)
	    termstate.__ispeed = speed_convert[ispeed];
	  else
	    termstate.__ispeed = 9600;
	}
	    
      if (!termstate.__ospeed)
	{
	  if (ospeed)
	    termstate.__ospeed = speed_convert[ospeed];
	  else
	    termstate.__ospeed = 9600;
	}
    }
      
  /* At this point, termstate.__[io]speed are the correct speeds;
     and [io]speed is the kernel's current idea (or zero, if we don't
     know what the kernel's idea is).  Check to see that
     the speeds in termstate are valid and find out the kernel's
     name (if we don't already know it). */
  if (termstate.__ispeed != speed_convert [ispeed])
    {
      for (i = 0; i < NSPEEDS && !ispeed; i++)
	if (speed_convert[i] == termstate.__ispeed)
	  ispeed = i;
      /* This has to be set because our caller is responsible
	 for validation. */
      assert (ispeed);
    }
  
  if (termstate.__ospeed != speed_convert [ospeed])
    {
      for (i = 0; i < NSPEEDS && !ospeed; i++)
	if (speed_convert[i] == termstate.__ospeed)
	  ospeed = i;
      /* This has to be correct because our caller is responsible
	 for validation. */
      assert (ospeed);
    }
  
  /* Mach forces us to use the normal stop-bit convention:
     2 bits at 110 baud; 1 bit otherwise.  Mach (or at least 
     i386at/com.c) looks at ispeed for this.  Hmm.  */
  if (termstate.__ispeed == 110)
    termstate.c_cflag |= CSTOPB;
  else
    termstate.c_cflag &= ~CSTOPB;
  
  if (pterm != MACH_PORT_NULL)
    {
      int count = TTY_STATUS_COUNT;
      ttystat.tt_ispeed = ispeed;
      ttystat.tt_ospeed = ospeed;
      ttystat.tt_flags = TF_ANYP | TF_LITOUT | TF_NOHANG | TF_HUPCLS;
      ttystat.tt_breakc = 0;
  
      device_set_status (pterm, TTY_STATUS, 
			 (dev_status_t)&ttystat, TTY_STATUS_COUNT);
      device_get_status (pterm, TTY_STATUS, (dev_status_t)&ttystat,
			 &count);
      /* If we didn't successfully change the speeds, then mutate
	 back. */
      if (ttystat.tt_ispeed != ispeed
	  || ttystat.tt_ospeed != ospeed)
	{
	  termstate.__ispeed = speed_convert[ttystat.tt_ispeed];
	  termstate.__ospeed = speed_convert[ttystat.tt_ospeed];
	}
    }
}

/* Called by pterm when an output request is finished. */
kern_return_t
device_write_reply_inband (mach_port_t replypt, 
			   kern_return_t return_code,
			   int amount)
{
  if (replypt != write_reply->port_right)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_OUTPUT;

  if (return_code == 0)
    {
      if (amount > qsize (osilo))
	amount = qsize (osilo);
      
      /* Remove this data from the silo */
      osilo->qs += amount;
    }
  
  if ((pstate & SILO_FULL) && (qsize (osilo) < HIGH_WATER_MARK / 2))
    {
      pstate &= ~SILO_FULL;
      start_writing ();
    }

  send_output ();

  return 0;
}

/* The pending read operation has returned.  Send all the data to the user
   and start another read if appropriate.  XXX This should check to
   see if input_character generated a signal and do the Right Thing. */
kern_return_t
device_read_reply_inband (mach_port_t replypt,
			  kern_return_t errorcode,
			  char *data,
			  u_int datalen)
{
  int i;
  int flush;
  
  if (replypt != read_reply->port_right)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_INPUT;

  if (!errorcode)
    {
      devio_start_batch ();
      for (i = 0; i < datalen; i++)
	{
	  flush = input_character (data[i]);
	  if (flush)
	    break;
	}
      devio_stop_batch ();
    }

  fetch_input ();
  return 0;
}

/* Called if we receive a send-once notification. */
kern_return_t
do_mach_notify_send_once (mach_port_t port)
{
  if (port == read_reply->port_right)
    {
      pstate &= ~PENDING_INPUT;
      fetch_input ();
      return 0;
    }
  else if (port == write_reply->port_right)
    {  
      pstate &= ~PENDING_OUTPUT;
      send_output ();
      return 0;
    }
  else
    return EOPNOTSUPP;
}

/* Unused stubs */
kern_return_t
device_read_reply (mach_port_t port,
		   kern_return_t retcode,
		   io_buf_ptr_t data,
		   mach_msg_type_number_t cnt)
{
  return EOPNOTSUPP;
}

kern_return_t
device_write_reply (mach_port_t replyport,
		    kern_return_t retcode,
		    int written)
{
  return EOPNOTSUPP;
}

kern_return_t
do_mach_notify_port_deleted (mach_port_t notify,
				    mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_msg_accepted (mach_port_t notify,
				    mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_port_destroyed (mach_port_t notify,
				      mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_no_senders (mach_port_t notify,
			   mach_port_mscount_t mscount)
{
  return 0;
}

kern_return_t
do_mach_notify_dead_name (mach_port_t notify,
				 mach_port_t name)
{
  return 0;
}
