/* 
   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. */


/* Terminal lower half for running on a Hurd I/O channel. */

/* "Physical" terminal. */
static io_t pterm;

/* Reply port for writes */
static mach_port_t writeport;

/* Reply port for reads */
static mach_port_t readport;

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

/* Requests set up by hurdio_drain. */
struct drain_req
{
  int offset;			/* qsize(osilo) when set up */
  int code;
  struct drain_req *next;
};
struct drain_req *drain_reqs;
  

/* State bits */
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 */

/* Open the Hurdio channel; consumes a user reference on termpt. */
error_t
hurdio_open (mach_port_t termpt)
{
  error_t err;

  err = io_set_some_openmodes (termpt, O_FSYNC);
  if (err && err != MACH_SEND_INVALID_DEST)
    return err;
  if (err == MACH_SEND_INVALID_DEST)
    return EBADF;
  pterm = termpt;

  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &writeport);
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &readport);

  mach_port_move_member (mach_task_self (), writeport, main_portset);
  mach_port_move_member (mach_task_self (), readport, mainportset);

  pstate = 0;
  fetch_input ();
  report_connect ();
  return 0;
}

/* Close the Hurdio channel */
void
hurdio_close ()
{
  assert (!(pstate & (PENDING_OUTPUT | PENDING_INPUT)));
  assert (!qsize (osilo));
  assert (!drain_reqs);
  mach_port_deallocate (mach_task_self (), pterm);

  mach_port_mod_refs (mach_task_self (), writeport, 
		      MACH_PORT_RIGHT_RECEIVE, -1);
  mach_port_mod_refs (mach_task_self (), readport,
		      MACH_PORT_RIGHT_RECEIVE, -1);
}

/* Send as much data as we can to the physical device. */
static inline void
send_output ()
{
  assert (!(pstate & (PENDING_OUTPUT|OUTPUT_SUSP)));
  assert (qsize (osilo));
  
  err = io_write_request (pterm, writeport, MACH_MSG_TYPE_MAKE_SEND_ONCE,
			  osilo->qs, qsize (osilo), -1);
  if (err == MACH_SEND_INVALID_DEST)
    report_hangup ();
  else if (!err)
    pstate |= PENDING_OUTPUT;
}
  
/* Send a single character out. */
hurdio_transmit (int c)
{
  /* Add it to the output silo */
  enqueue_char (c, &osilo);

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

  if (!(pstate & (PENDING_OUTPUT|OUTPUT_SUSP)))
    send_output ();
}

/* Call drain_finished when all currently pending output is finished. */
void
hurdio_drain (int code)
{
  /* If there is no currently pending I/O, then we are done right now. */
  if (!(pstate & PENDING_OUTPUT))
    drain_finished (code);
  else
    {
      /* Set up and enqueue the request. */
      struct drain_req *dr;
      
      dr->offset = qsize (osilo);
      dr->code = code;
      dr->next = drain_reqs;
      drain_reqs->next = dr;
    }
}

/* Drop pending output. */
void
hurdio_drop ()
{
  struct drain_req *dr, *nxt;
  
  if (pstate & PENDING_OUTPUT)
    {
      interrupt_operation (pterm);
      pstate &= ~PENDING_OUTPUT;
    }
  osilo->qs = osilo->qp = osilo->chars;
  for (dr = drain_reqs; dr; dr = nxt)
    {
      nxt = dr->next;
      drain_finished (dr->code);
      free (dr);
    }
  drain_reqs = 0;
}

/* Stop sending output */
void
hurdio_stop_sending ()
{
  if (pstate & PENDING_OUTPUT)
    {
      interrupt_operation (pterm);
      pstate &= ~PENDING_OUTPUT;
    }
  pstate |= OUTPUT_SUSP;
}

/* Start sending output */
void
hurdio_start_sending ()
{
  if (pstate & OUTPUT_SUSP)
    {
      pstate &= ~OUTPUT_SUSP;
      if (!(pstate & PENDING_OUTPUT))
	send_output ();
    }
}

/* Stop reading input */
void
hurdio_stop_reading ()
{
  if (pstate & PENDING_INPUT)
    {
      interrupt_operation (pterm);
      pstate &= ~PENDiNG_INPUT;
    }
}

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

/* Called by pterm when an output request is finished. */
error_t
hurdio_write_reply (mach_port_t replypt, 
		    int amount)
{
  struct drain_req *dr, *prv;
  
  if (replypt != writeport)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_OUTPUT;

  if (amount >= qsize (osilo))
    {
      printf ("term (hurdio): more written than requested!\n");
      amount = qsize (osilo);
    }
	
  /* Check to see if this finishes any drain requests. */
  dr = drain_reqs;
  prv = 0;
  while (dr)
    {
      if (dr->offset <= amount)
	{
	  drain_finished (dr->code);
	  prv ? prv->next : drain_reqs = dr->next;
	  free (dr);
	  dr = prv ? prv->next : drain_reqs;
	}
      else
	{
	  prv = dr;
	  dr = dr->next;
	}
    }
  
  /* Remove this data from the silo */
  osilo->qs += amount;
  
  if ((pstate & SILO_FULL) && (qsize (osilo) < HIGH_WATER_MARK / 2))
    {
      pstate &= ~SILO_FULL;
      start_writing ();
    }

  /* If appropriate, send another write request. */
  if (qsize (osilo) && !(pstate & OUTPUT_SUSP))
    send_output ();

  return 0;
}

/* Called if we receive a send-once notification on our io_write reply
   port. */
void
hurdio_write_send_once (mach_port_t port)
{
  if (port != writeport)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_OUTPUT;
  send_output ();
}

inline void
fetch_input ()
{
  error_t err;
  
  assert (!(pstate & PENDING_INPUT))

  err = io_read_request (pterm, readport, MACH_MSG_TYPE_MAKE_SEND_ONCE,
			 -1, vm_page_size);

  if (err == MACH_SEND_INVALID_DEST)
    report_hangup ();
  else if (!err)
    pstate |= PENDING_INPUT;
  else
    report_hangup ();		/* what else should be do here??? XXX */
}

error_t
hurdio_read_reply (mach_port_t replypt,
		   char *data,
		   u_int datalen)
{
  int i;
  
  if (replypt != readport)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_INPUT;

  for (i = 0; i < datalen; i++)
    input_character (data[i]);

  fetch_input ();
  return 0;
}

/* Called if we receive a send-once notification. */
error_t
hurdio_read_send_once (mach_port_t port)
{
  if (port != readport)
    return EOPNOTSUPP;
  
  pstate &= ~PENDING_INPUT;
  fetch_input ();
  return 0;
}
