/*
 * Copyright (C) 2000 Kees Cook
 * cook@cpoint.net, http://outflux.net/
 *
 * 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.
 * http://www.gnu.org/copyleft/gpl.html
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>  /* ioctl */
#include <signal.h>

#define __USE_XOPEN
#define __USE_GNU
#include <stdlib.h>

#include "sts.h"
#include "sts_fas.h"	/* fas funcs & defines */


/* helper macros for 16bit Least significant bit first */
#define GRAB_16BIT(ptr, start) ((ptr)[(start)] + ((ptr)[(start)+1]<<8))
#define STORE_16BIT(ptr,start,val) \
			(ptr)[(start)]=((val) & 0xff); \
			(ptr)[(start)+1]=(((val) & 0xff00) >> 8);

/* helper macros for bit-title manipulations: bit# 0, 1, ... 7 */
#define IS_SET(byte, bit)	(((byte) & (1 << (bit))) == (1 << (bit)))
#define SET(byte, bit)		((byte) | (1 << (bit)))
#define UNSET(byte, bit)	((byte) & (~(1 << (bit))))

/****** cmd list manipulation functions
	FIXME: I think we need to be atomically locked way prior to calling
*/


/* adds a FAS command to the doubly linked list of queued cmds */
void add_cmd(sts_state * sts, char * data, int len) {
	cmd_list * cmd;

	if (!sts) {
		fprintf(stderr,"add_cmd: Got null sts\n");
		return;
	}
	if (!data && len!=0) {
		fprintf(stderr,"add_cmd: Got null data pointer\n");
		return;
	}
	if (!(cmd=malloc(sizeof(cmd_list)))) {
		perror("add_cmd pointer malloc");
		return;
	}
	if (!(cmd->data=malloc(len))) {
		perror("add_cmd data malloc");
		return;
	}
	memcpy(cmd->data, data, len);
	cmd->size=len;
	cmd->next=NULL;
	cmd->prev=sts->tail;
	if (sts->tail) sts->tail->next=cmd;
	sts->tail=cmd;
	if (!sts->head) sts->head=cmd;
}

/* find a properly size cmd in the list, starting with "cmd" */
cmd_list * find_cmd(sts_state * sts, cmd_list *cmd, int slotsize) {
	if (!sts) {
		fprintf(stderr,"find_cmd: Got null sts\n");
		return NULL;
	}
	if (!cmd) cmd=sts->head;
	for (;cmd;cmd=cmd->next)
		if (cmd->size<=slotsize) break;
	return cmd;
}

/* throws away a cmd in the list */
void delete_cmd(sts_state * sts, cmd_list * cmd) {
	if (!sts) {
		fprintf(stderr,"delete_cmd: Got null sts\n");
		return;
	}
	if (!cmd) {
		fprintf(stderr,"delete_cmd: Got null cmd\n");
		return;
	}
	if (cmd->prev) cmd->prev->next=cmd->next;
	if (cmd->next) cmd->next->prev=cmd->prev;
	if (sts->head == cmd) sts->head=cmd->next;
	if (sts->tail == cmd) sts->tail=cmd->prev;
	free(cmd->data);
	free(cmd);
}

/* start our scsi command (need to know output size) */
int start_scsi_cmd (
			int fd,			/* sg fd */
			unsigned cmd_len,	/* command length */
		 	unsigned in_size,	/* input data size */
		 	unsigned char *i_buff,	/* input buffer */
		 	unsigned out_size	/* output data size */
     		      ) {
		
  int status = 0;
  struct sg_header *sg_hd;

  /* safety checks */
  if (!cmd_len)
    return -1;			/* need a cmd_len != 0 */
  if (!i_buff)
    return -1;			/* need an input buffer != NULL */

  /* keep the packet size under 4k */
  if (SG_LEN + cmd_len + in_size > MAX_PACKET)
    return -1;

  /* generic scsi device header construction */
  sg_hd = (struct sg_header *) i_buff;
  /* useful stuff */
  sg_hd->reply_len = SG_LEN + out_size;
  sg_hd->twelve_byte = (cmd_len == 12); /* should be set */
  /* not really used stuff */
  sg_hd->other_flags = 0;	/* not used */
  sg_hd->result = 0;
  sg_hd->pack_id = 0;
  /* ignored stuff */
#if     0
  sg_hd->pack_len = SG_LEN + cmd_len + in_size;	/* not necessary */
#endif

  /* send command */

#if 0
  printf("SG_LEN: %d\n",SG_LEN);
  printf("cmd_len: %d\n",cmd_len);
  printf("in_size: %d\n",in_size);
  printf("out_size: %d\n",out_size);
#endif

  /* FIXME: handle EAGAIN errno */
  status = write (fd, i_buff, SG_LEN + cmd_len + in_size);
  if (status < 0 || status != SG_LEN + cmd_len + in_size) {
      /* some error happened */
      fprintf (stderr, "write(sg fd %d) result = 0x%x cmd = 0x%x: ",
	       fd, sg_hd->result, i_buff[SG_LEN]);
      perror ("");
      return status;
  }

  return 0;
}

/* get results from last scsi cmd (need output buffer and size) */
int finish_scsi_cmd (
			int fd,			/* sg fd */
		 	unsigned out_size,	/* output data size */
		 	unsigned char *o_buff	/* output buffer */
		     ) {
  int status = 0;
  struct sg_header *sg_hd;

  /* safety checks */
  if (SG_LEN + out_size > MAX_PACKET)
    return -1;
  if (!o_buff)
    out_size = 0;
  
  /* retrieve result */
  /* FIXME: handle EINTR errno */
  status = read (fd, o_buff, SG_LEN + out_size);
  if (status < 0 || status != SG_LEN + out_size) {
      /* some error happened */
      fprintf (stderr, "read(sg fd %d) result = 0x%x cmd = 0x%x\n",
	       fd, sg_hd->result, o_buff[SG_LEN]);
      fprintf (stderr, "read(sg fd %d) sense "
	       "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", fd,
	       sg_hd->sense_buffer[0], sg_hd->sense_buffer[1],
	       sg_hd->sense_buffer[2], sg_hd->sense_buffer[3],
	       sg_hd->sense_buffer[4], sg_hd->sense_buffer[5],
	       sg_hd->sense_buffer[6], sg_hd->sense_buffer[7],
	       sg_hd->sense_buffer[8], sg_hd->sense_buffer[9],
	       sg_hd->sense_buffer[10], sg_hd->sense_buffer[11],
	       sg_hd->sense_buffer[12], sg_hd->sense_buffer[13],
	       sg_hd->sense_buffer[14], sg_hd->sense_buffer[15]);
      if (status < 0)
	perror ("");
  }

  /* Look if we got what we expected to get */
  if (status == SG_LEN + out_size)
    status = 0;			/* got them all */

  return status;		/* 0 means no error */
}

/* process a complete scsi cmd. Use the generic scsi interface. */
int waitfor_scsi_cmd (
			int fd,			/* sg fd */
			unsigned cmd_len,	/* command length */
		 	unsigned in_size,	/* input data size */
		 	unsigned char *i_buff,	/* input buffer */
		 	unsigned out_size,	/* output data size */
		 	unsigned char *o_buff	/* output buffer */
     		      ) {

  int result;

  if ((result=start_scsi_cmd(fd, cmd_len, in_size, i_buff, out_size))!=0) {
	fprintf(stderr,"start_scsi_cmd failed\n");
	return result;
  }

  /* FIXME: this seems dangerous ... */
  if (!o_buff)
	o_buff=i_buff;

  if ((result=finish_scsi_cmd(fd, out_size, o_buff))!=0) {
	fprintf(stderr,"finish_scsi_cmd failed\n");
	return result;
  }

  return 0;
}

void printchars(unsigned char * str, int len) {
	int i;

	for (i=0;i<len;i++) {
		sts_debug("\t%d: %c (0x%02X)\n",i, str[i],str[i]);
	}
}


/* FIXME: uuuuh... do I need to specify LUNs or does sg handle that? */
/* request vendor brand and model */
unsigned char * Inquiry (int fd) {
#define INQUIRY_CMD     0x12
#define INQUIRY_CMDLEN  6
#define INQUIRY_REPLY_LEN 96
  unsigned char cmd[MAX_PACKET];
  static unsigned char Inqbuffer[SG_LEN + INQUIRY_REPLY_LEN];
  unsigned char cmdblk[INQUIRY_CMDLEN] = { 
    INQUIRY_CMD,		/* command */
    0,				/* lun/reserved */
    0,				/* page code */
    0,				/* reserved */
    INQUIRY_REPLY_LEN,		/* allocation length */
    0				/* reserved/flag/link */
  };


  memcpy (cmd + SG_LEN, cmdblk, sizeof (cmdblk));

  /*
   * +------------------+
   * | struct sg_header | <- cmd
   * +------------------+
   * | copy of cmdblk   | <- cmd + SG_LEN
   * +------------------+
   */

  if (waitfor_scsi_cmd (fd, sizeof (cmdblk), 0, cmd,
		       sizeof (Inqbuffer) - SG_LEN, Inqbuffer)) {
      fprintf (stderr, "Inquiry failed\n");
      exit (2);
  }

  return Inqbuffer+SG_LEN;
}


/* test unit ready: 0 is okay? */
int TestForMedium (int fd) {
#define TESTUNITREADY_CMD 0
#define TESTUNITREADY_CMDLEN 6
#define ADD_SENSECODE 12
#define ADD_SC_QUALIFIER 13
#define NO_MEDIA_SC 0x3a
#define NO_MEDIA_SCQ 0x00
  unsigned char cmd[MAX_PACKET];
  /* request READY status */
  static unsigned char cmdblk[TESTUNITREADY_CMDLEN] = {
    TESTUNITREADY_CMD,		/* command */
    0,				/* lun/reserved */
    0,				/* reserved */
    0,				/* reserved */
    0,				/* reserved */
    0
  };				/* reserved */

  memcpy (cmd + SG_LEN, cmdblk, sizeof (cmdblk));

  /*
   * +------------------+
   * | struct sg_header | <- cmd
   * +------------------+
   * | copy of cmdblk   | <- cmd + SG_LEN
   * +------------------+
   */

  if (waitfor_scsi_cmd (fd, sizeof (cmdblk), 0, cmd, 0, NULL)) {
      fprintf (stderr, "Test unit ready failed\n");
      exit (2);
  }

  return
    *(((struct sg_header *) cmd)->sense_buffer + ADD_SENSECODE) !=
    NO_MEDIA_SC ||
    *(((struct sg_header *) cmd)->sense_buffer + ADD_SC_QUALIFIER) !=
    NO_MEDIA_SCQ;
}

char * fas_opcodes[] = {
	"FAS_GLOBAL",
	"FAS_ENABLE",
	"FAS_DISABLE",
	"FAS_SEND",
	"FAS_RECV",
	"FAS_IN_TIMERS",
	"FAS_OUTPUT_CTL",
	"FAS_INPUT_CTL",
	"FAS_SET_MODEM",
	"FAS_STAT_CHG",
	"FAS_SEND_BRK",
	"FAS_SET_PARAMS",
	"FAS_FLOW_CTL",
	"FAS_RESERVE",
	"FAS_RELEASE"
};

/* send characters to an STS port */
int fas_send(sts_state * sts, unsigned int port, unsigned char * data,
	unsigned int len) {
  unsigned char fascmd[] = {
	FAS_SEND,	/* Command Opcode */
	0,		/* Port number */
	0,0,		/* byte count */
	0,0,0,0	/* Unused */
  };
  int sent;
  unsigned char buffer[FAS_SEND_MAX + FAS_CMD_LEN];

  if (!sts) {
	fprintf(stderr,"fas_send: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_send: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
  }
  else {
	fprintf(stderr,"fas_send: port auto sensing not finished\n");
	return 0;
  }

  fascmd[1]=port;

  /* send the string in FAS_SEND_MAX chunks */
  while (len) {
	sent=len;
	if (len>FAS_SEND_MAX) {
		sts_debug("big string: %d\n",len);
		sent=FAS_SEND_MAX;
	}
  	STORE_16BIT(fascmd,2,sent);
	memcpy(buffer,fascmd,FAS_CMD_LEN);
	memcpy(buffer+FAS_CMD_LEN,data,sent);
	add_cmd(sts, buffer, FAS_CMD_LEN + sent);
	len-=sent;
	data+=sent;
  }
  /* set up port's cmd status */
  sts->ports[port]->status[FAS_SEND]=FAS_PENDING;

  return 1;
}

/* return from fas_send */
void fas_send_ret(sts_state * sts, unsigned int port, unsigned int status) {
  if (!sts) {
	fprintf(stderr,"fas_send_ret: got null sts!\n");
	return;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_send_ret: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return;
	}
  }
  else {
	fprintf(stderr,"fas_send_ret: port auto sensing not finished\n");
	return;
  }

  if ((sts->ports[port]->status[FAS_SEND]=status)!=FAS_OK) {
	fprintf(stderr,"FAS_SEND: error: ");
	fas_error(stderr,status);
  }
}

/* ask to recv characters from a port */
int fas_recv(sts_state * sts, unsigned int port, unsigned int len) {
  unsigned char fascmd[] = {
	FAS_RECV,	/* Command Opcode */
	0,		/* Port number */
	0,0,		/* Max byte count */
	0,0,0,0	/* Unused */
  };

  if (!sts) {
	fprintf(stderr,"fas_recv: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_recv: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
  }
  else {
	fprintf(stderr,"fas_recv: port auto sensing not finished\n");
	return 0;
  }

  fascmd[1]=port;
  STORE_16BIT(fascmd,2,len);

  add_cmd(sts, fascmd, FAS_CMD_LEN);

  return 1;
}

void fas_recv_ret(sts_state * sts, unsigned int port, unsigned int status,
  unsigned char * data, unsigned int len) {

  if (!sts) {
	fprintf(stderr,"fas_recv_ret: got null sts!\n");
	return;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_recv_ret: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return;
	}
  }
  else {
	/* silently ignore any stale port reports during port sensing */
	if (sts->portsensed) {
	   fprintf(stderr,"fas_recv_ret: port auto sensing not finished\n");
	}
	return;
  }

  if (status == FAS_OK) {
/*
  	sts_debug("FAS_RECV got %d bytes:\n",len);
	printchars(data,len);
*/
	/* FIXME: handle BUSY, INTR, and under-writes */
	write(sts->ttys[port]->ptm,data,len);
	/* re-post the recv for the next bunch of chars */
	fas_recv(sts,port,300); /* FIXME: 300 baud, har har har */
  }
  else {
	/* FIXME: aren't chars still good from this call, even when
           in error? */

	/* ignore errors during shutdown */
	if (!sts->shutdown) {
		fprintf(stderr,"FAS_RECV: error: ");
		fas_error(stderr,status);
	}
  }
}

/* FIXME: reportonly should be called "sending_to_sts" or something */
void process_fas_block(sts_state * sts,unsigned char * block, int size,
			int reportonly) {
	int remaining;		/* how much left to read */
	unsigned char * ptr;	/* string pointer into block */
	int cmdat;			/* debug: start of command */
	unsigned char cmd[FAS_CMD_LEN];	/* FAS command */
	int len;			/* how much data follows FAS_RECV */

	if (!sts) {
		fprintf(stderr,"process_fas_block: got null sts!\n");
		return;
	}
	if (!block && size!=0) {
		fprintf(stderr,"process_fas_block: got null block!\n");
		return;
	}

	sts_debug("\t(FAS block is %d byte%s)\n",size,size==1?"":"s");

	cmdat=0;
	ptr=block;
	remaining=size;
	for (;remaining;cmdat+=8) {
		*cmd=*ptr++;
		if (*cmd == PKT_END) {
			sts_debug("\tat %d: PKT_END\n",cmdat);
			/* empty packet? */
			if (cmdat == 0) {
				sts->flushed=1;
			}
			break; /* stop processing */
		}
		if (remaining < FAS_CMD_LEN) {
			fprintf(stderr,"process_fas_block: buffer read "
				       "underrun\n");
			return;
		}
		memcpy(cmd+1,ptr,FAS_CMD_LEN - 1);
		ptr+=FAS_CMD_LEN-1;
		remaining-=FAS_CMD_LEN;

		if (*cmd > FAS_CMD_MAX) {
			fprintf(stderr,"process_fas_block: invalid command: "
				"0x%02X -- skipping 8 bytes\n",*cmd);
			continue;
		}

		if (*cmd == FAS_RECV ||
		    *cmd == FAS_SEND) {
			unsigned int port,status;
			/* largest buffer size for FAS_RECV */
			unsigned char data[FAS_PKT_MAX];

			sts_debug("\tat %d: FAS_%s\n",cmdat,
				*cmd == FAS_RECV ? "RECV" : "SEND");

			port=cmd[1];
			status=cmd[2];

			if ((reportonly && *cmd==FAS_SEND) ||
			    (!reportonly && *cmd==FAS_RECV)) {
				len=GRAB_16BIT(cmd,*cmd==FAS_RECV ? 4 : 2);

				if (remaining < len) {
				   fprintf(stderr,"process_fas_block: FAS_%s "
					"data underrun -- expected %d got %d "
					"or less\n",
					*cmd == FAS_RECV ? "RECV" : "SEND",
					len,remaining);
				   return;
				}

				if (len>0) {
					memcpy(data,ptr,len);
					ptr+=len;
					remaining-=len;

					sts_debug("\tat %d: FAS_%s data (%d "
					   "byte%s\n",
					   cmdat+FAS_CMD_LEN,
					   *cmd == FAS_RECV ? "RECV" : "SEND",
					   len, len == 1 ? "" : "s");
					cmdat+=len;
					printchars(data,len);
				}
			}

			if (!reportonly) {
				if (*cmd==FAS_RECV) 
					fas_recv_ret(sts,port,status,data,len);
				else 
					fas_send_ret(sts,port,status);
			}
		}
		else {
			sts_debug("\tat %d: ",cmdat);
			/* FIXME: need proper debug here */
			if (reportonly) {
				sts_debug("%s\n",fas_opcodes[*cmd]);
			}
			else {
				fas_packet_ret(sts,cmd);
			}
		}
	}
}

/* gather fas cmds for a single transmission block.  return final length */
int gather_fas_block(sts_state * sts, unsigned char * block) {
	cmd_list * cmd;
	cmd_list * next;
	unsigned char * ptr;
	int remaining;
	int len;

	ptr=block;
	next=sts->head;
	remaining=FAS_PKT_MAX;
	len=0;
	while ((cmd=find_cmd(sts,next,remaining))) {
		memcpy(ptr, cmd->data, cmd->size);
		remaining-=cmd->size;
		len+=cmd->size;
		ptr+=cmd->size;

		if (cmd != next) /* if we didn't get the first cmd */
			next=cmd->next; /* we need to look at the next packet */
		else
			next=NULL;      /* othersize, start from the top */
		
		delete_cmd(sts,cmd);
		if (!next) next=sts->head; /* restart from the top on NULL */

		if (remaining==0) break; /* stop cramming cmds in */
	}
	if (remaining!=FAS_PKT_MAX && remaining!=0) {
		/* mark the end of the packet, if room */
		*ptr=PKT_END;
		len++;
	}
	return len; /* how many bytes went in? */	
}


void fas_error(FILE * fd, unsigned int status) {
static char * fas_status_strings[FAS_ERR_MAX+1] = {
/* 0 */	"OK",
/* 1 */	"Extra command received for command type that only allows one in progress at a time",
/* 2 */	"Command parameter out of range",
/* 3 */	"Specific line number out of range not present",
/* 4 */	"Input character(s) received with parity errors",
/* 5 */	"Overrun(s) during input",
/* 6 */	"Input character(s) received with framing errors",
/* 7 */	"Possibly multiple errors on received characters",
/* 8 */	"Command issued on uninitialized line or line reinitialized",
/* 9 */	"Command code not valid",
/* A */	"Command aborted by a flush",
/* B */	"Break condition at a line",
/* C */	"Input buffers overflowed at STS, some input has been lost"
};
	unsigned int low;

	if (status == FAS_OK) {
		fprintf(fd,"OK\n");
	}
	else {
		fprintf(fd,"(0x%02X)",status);
		low=(status & (~0x80));
		if ((status & FAS_ERR_FAIL) == FAS_ERR_FAIL) {
			fprintf(fd,"Serious condition: ");
		}
		if (low>FAS_ERR_MAX) {
			fprintf(fd,"unknown status code 0x%02X!\n",low);
		}
		else {
			fprintf(fd,"%s\n",fas_status_strings[low]);
		}
	}
}

/* FIXME: need to make port ints into unsigned values */

void fas_enable_ret(sts_state * sts, unsigned int port, unsigned int status,
			unsigned int disable) {
	char * opname;
	opname=disable ? "DISABLE" : "ENABLE";

	sts_debug("FAS_%s: port: %d status: 0x%02X\n", opname, port, status);
	
	if (!sts) {
		fprintf(stderr,"fas_enable_ret: got null sts!\n");
		return;
	}	
	if (sts->portsensed) {
		if (port>sts->total_ports) {
			fprintf(stderr,"fas_enable_ret: got response for "
				"unmanaged port %d (> %d)!\n",port,
				sts->total_ports-1);
			return;
		}
	}

	if (status == FAS_OK) {
	        if (!sts->portsensed && !disable) {
#if 0
			fprintf(stderr,"fas_enable_ret: got enable response "
				"for port %d during auto-sense!\n",port);
#endif
			return;
		}
		/* do memory allocation for newly discovered ports */
		if (!sts->ports) {
			/* allocate array table */
			sts->ports=(port_state **)calloc((port+1),sizeof(port_state *));
			if (!sts->ports) {
				fprintf(stderr,"fas_enable_ret: alloc 'ports' failed\n");
				perror("calloc");
				exit(11);
			}
			sts->total_ports=port+1;
		}
		if (sts->total_ports<port+1) {
			int i;
			/* grow array table */
			sts->ports=(port_state **)realloc((void*)sts->ports,
					(port+1)*sizeof(port_state *));
			if (!sts->ports) {
				fprintf(stderr,"fas_enable_ret: realloc 'ports' failed\n");
				perror("realloc");
				exit(12);
			}
			/* clear the memory just made available */
			for (i=sts->total_ports;i<=port;i++) {
				sts->ports[i]=NULL;
			}
			sts->total_ports=port+1;
		}
		if (!sts->ports[port]) {
			/* allocate array table */
			sts->ports[port]=(port_state *)calloc(1,sizeof(port_state));
			if (!sts->ports[port]) {
				fprintf(stderr,"fas_enable_ret: alloc port %d failed\n",port);
				perror("calloc");
				exit(13);
			}
		}

		sts->ports[port]->enabled=!disable;
	}
	else {
		if (sts->portsensed) {
			/* when not autosensing */
			if (sts->ports[port]->enabled &&
		    	    status == (FAS_ERR_FAIL | FAS_ERR_INITD)) {
				/* ignore reenablement complaints */
			}
			else {
				/* but report anything else */
				fprintf(stderr,"FAS_%s: error: ", opname);
				fas_error(stderr,status);
			}
		}
		else {
			/* when autosensing */
			if (status != (FAS_ERR_FAIL | FAS_ERR_NOT_PRESENT) &&
			    status != (FAS_ERR_FAIL | FAS_ERR_INITD)) {
				/* report anything not expected */
				fprintf(stderr,"FAS_%s: error: ", opname);
				fas_error(stderr,status);
			}
		}
	}
}

void fas_reserve_ret(sts_state * sts, unsigned int port, unsigned int status,
		unsigned int release) {
	char * opname;
	opname=release ? "RELEASE" : "RESERVE";

	sts_debug("FAS_%s: port: %d status: 0x%02X\n", opname, port, status);
	
	if (!sts) {
		fprintf(stderr,"fas_reserve_ret: got null sts!\n");
		return;
	}	
	if (sts->portsensed) {
		if (port>sts->total_ports) {
			fprintf(stderr,"fas_reserve_ret: got response for "
				"unmanaged port %d (> %d)!\n",port,
				sts->total_ports-1);
			return;
		}
	}
	else {
		fprintf(stderr,"fas_reserve_ret: got response for "
			"port %d during auto-sense!\n", port);
			return;
	}

	if (status == FAS_OK) {
		sts->ports[port]->reserved=!release;
	}
	else {
		fprintf(stderr,"FAS_%s: error: ", opname);
		fas_error(stderr,status);
	}
}

void fas_set_params_ret(sts_state * sts, unsigned int port,
	unsigned int status) {

  sts_debug("FAS_SET_PARAMS: port: %d status: 0x%02X\n", port, status);

  if (!sts) {
	fprintf(stderr,"fas_set_params_ret: got null sts!\n");
	return;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_set_params_ret: port %d out of range "
				"(> %d)!\n", port, sts->total_ports-1);
		return;
	}
  }
  else {
	fprintf(stderr,"fas_set_params_ret: port auto sensing not finished\n");
	return;
  }

  if ((sts->ports[port]->status[FAS_SET_PARAMS]=status)!=FAS_OK) {
	fprintf(stderr,"FAS_SET_PARAMS: error: ");
	fas_error(stderr,status);
  }
}

void fas_set_modem_ret(sts_state * sts, unsigned int port,
	unsigned int status) {

  sts_debug("FAS_SET_MODEM: port: %d status: 0x%02X\n", port, status);

  if (!sts) {
	fprintf(stderr,"fas_set_modem_ret: got null sts!\n");
	return;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_set_modem_ret: port %d out of range "
				"(> %d)!\n", port, sts->total_ports-1);
		return;
	}
  }
  else {
	fprintf(stderr,"fas_set_modem_ret: port auto sensing not finished\n");
	return;
  }

  if ((sts->ports[port]->status[FAS_SET_MODEM]=status)!=FAS_OK) {
	fprintf(stderr,"FAS_SET_MODEM: error: ");
	fas_error(stderr,status);
  }
}

void fas_global_ret(sts_state * sts, unsigned int version,
		unsigned int status) {
	sts_debug("FAS_GLOBAL: version: %d status: 0x%02X\n",version,status);
	if (!sts) {
		fprintf(stderr,"fas_global_ret: got null sts!\n");
		return;
	}	
	if (status == FAS_OK) {
		sts->globaled=1;
	}
	else {
		fprintf(stderr,"FAS_GLOBAL: error: ");
		fas_error(stderr,status);
	}
}

/* this assumes a valid cmd has 8 bytes in it */
void fas_packet_ret(sts_state * sts, unsigned char * cmd) {
	unsigned int opcode,port,status;

	if (!sts) {
		fprintf(stderr,"fas_packet_ret: got null sts\n");
		return;
	}
	if (!cmd) {
		fprintf(stderr,"fas_packet_ret: got null cmd\n");
		return;
	}

	opcode=cmd[0];
	port=  cmd[1];
	status=cmd[2];

	switch (opcode) {
		case FAS_GLOBAL:
			fas_global_ret(sts,port,status);
			break;
		case FAS_RESERVE: case FAS_RELEASE:
			fas_reserve_ret(sts,port,status,
					(opcode == FAS_RELEASE));
			break;
		case FAS_ENABLE: case FAS_DISABLE:
			fas_enable_ret(sts,port,status,
					(opcode == FAS_DISABLE));
			break;
		case FAS_SET_MODEM:
			fas_set_modem_ret(sts,port,status);
			break;
		case FAS_SET_PARAMS:
			fas_set_params_ret(sts,port,status);
			break;
/*
		case FAS_SEND:
			fas_send_ret(sts,port,status);
			break;
*/
		default:
			fprintf(stderr,"got unsupported FAS opcode: 0x%02X "
				       "port: %d status: 0x%02X\n",opcode,
				       port, status);
	}
}


int send_fas_block(sts_state * sts, unsigned char * fasblock, int len) {
#define SENDMSG_CMD	0x0A
#define SENDMSG_CMDLEN	6
  static unsigned char sendblk[SENDMSG_CMDLEN] = {
	SENDMSG_CMD,	/* Op Code */
	0,		/* Lun/reserved  FIXME? */
	0,0,0,          /* transfer length (needs to be set) */
	0		/* Control ?! */
  };
  static unsigned char cmd[MAX_PACKET];
  int result;
  unsigned char buffer[FAS_PKT_MAX + SG_LEN];

  if (!sts) {
	fprintf(stderr,"send_fas_block: got null sts!\n");
	return 1;
  }

  if (len > FAS_PKT_MAX) {
	fprintf(stderr,"send_fas_block: block too large: %d > %d\n",
		len, FAS_PKT_MAX);
	return 1;
  }

  /* set the length bytes */
  sendblk[2]=((len & 0xff0000) >> 16);
  sendblk[3]=((len & 0xff00) >> 8);
  sendblk[4]=(len & 0xff);

  memcpy (cmd + SG_LEN, sendblk, sizeof (sendblk));
  memcpy (cmd + SG_LEN + sizeof(sendblk), fasblock, len);
  /*cmd[SG_LEN + sizeof(sendblk) + len - 1]=PKT_END; *//* don't tack on PKT_END */

  sts_debug("sending:\n");
  /* FIXME: make the report-only var a define here */
  process_fas_block(sts,cmd+SG_LEN+sizeof(sendblk),len,1);

  result=waitfor_scsi_cmd(	
			sts->sendfd,
			SENDMSG_CMDLEN,
			/*sizeof(sendblk)+*/len,
			cmd,
			0, /* FAS_PKT_MAX, */
			buffer);

  if (result!=0) {
	fprintf(stderr,"send_fas_block: SCSI transport failure\n");
	return result;
  }

  return 0;
}


/* send SCSI command to get info from STS */
int recv_fas_block(sts_state * sts) {
#define GETMSG_CMD	0x08
#define GETMSG_CMDLEN	6
  static unsigned char recvblk[GETMSG_CMDLEN] = {
	GETMSG_CMD,	/* Op Code */
	0,		/* Lun/reserved  FIXME? */
	0,8,0,          /* transfer len: 2048 */
	0		/* Control ?! */
  };
  static unsigned char cmd[MAX_PACKET];
  int result;

  if (!sts) {
	fprintf(stderr,"recv_fas_block: got null sts!\n");
	return 1;
  }

  memcpy(cmd + SG_LEN, recvblk, sizeof (recvblk));

  result=start_scsi_cmd(
			sts->recvfd,
			GETMSG_CMDLEN,
			0, /*sizeof(recvblk),*/ /* input size=0, only a cmd */
			cmd,
			FAS_PKT_MAX);

  if (result!=0) {
	fprintf(stderr,"recv_fas_block: SCSI transport failure\n");
  }
  return result;
}

int recv_fas_block_ret(sts_state * sts) {
  int result;
  unsigned char buffer[FAS_PKT_MAX + SG_LEN];

  /* FIXME: is this really needed? */
  /* fill overflow data with PKT_ENDs for safety */
  memset(buffer+SG_LEN,PKT_END,FAS_PKT_MAX);

  result=finish_scsi_cmd(
			sts->recvfd,
			FAS_PKT_MAX,
			buffer);

  if (result!=0) {
	fprintf(stderr,"recv_fas_block_ret: SCSI transport failure\n");
	return result;
  }

  sts_debug("received:\n");
  process_fas_block(sts,buffer+SG_LEN,FAS_PKT_MAX,0);

  return 0;
}

int waitfor_recv_fas_block(sts_state * sts) {
	int result;

	if ((result=recv_fas_block(sts))==FAS_OK)
		result=recv_fas_block_ret(sts);

	return result;
}

int do_one_fas_cmd(sts_state * sts, unsigned char * fascmd, int len) {
	int result;

	if ((result=send_fas_block(sts, fascmd, len)) == FAS_OK)
		result=waitfor_recv_fas_block(sts);

	return result;
}

int fas_reserve(sts_state * sts, unsigned int port, unsigned int release) {
  unsigned char fascmd[] = {
	FAS_RESERVE,	/* Command Opcode */
	0,		/* Port number */
	0,0,0,0,0,0	/* Unused */
  };

  if (!sts) {
	fprintf(stderr,"fas_reserve: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_reserve: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
  }

  if (release) fascmd[0]=FAS_RELEASE;
  else fascmd[0]=FAS_RESERVE;

  fascmd[1]=port;

  add_cmd(sts, fascmd, FAS_CMD_LEN);

  return 1;
}

int fas_enable(sts_state * sts, unsigned int port, unsigned int disable) {
  unsigned char fascmd[] = {
	FAS_ENABLE,	/* Command Opcode */
	0,		/* Port number */
	0,0,0,0,0,0	/* Unused */
  };

  if (!sts) {
	fprintf(stderr,"fas_enable: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_enable: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
#ifdef DO_RESERVATIONS
	if (!sts->ports[port]->reserved) {
		fprintf(stderr,"fas_enable: port %d is not reserved!\n", port);
		return 0;
	}
#endif
  }

  if (disable) fascmd[0]=FAS_DISABLE;
  else fascmd[0]=FAS_ENABLE;

  fascmd[1]=port;

  add_cmd(sts, fascmd, FAS_CMD_LEN);

  return 1;
}

/* stuff a FAS_SET_PARAMS into the cmd list */
int fas_set_params(sts_state * sts, unsigned port, unsigned int bits,
	unsigned int framing, unsigned int parity, unsigned int flags,
	unsigned int baud) {
  unsigned char fascmd[] = {
    FAS_SET_PARAMS,	/* Command Opcode */
    0,			/* Port number */
    0,			/* Bits */
    0,			/* Framing */
    0,			/* Parity */
    0,			/* Flags */
    0,			/* Baud */
    0			/* Unused */
  };

  if (!sts) {
	fprintf(stderr,"fas_set_params: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_set_params: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
  }
  else {
	fprintf(stderr,"fas_set_params: port auto sensing not finished\n");
	return 0;
  }

  fascmd[1]=port;
  fascmd[2]=bits;
  fascmd[3]=framing;
  fascmd[4]=parity;
  fascmd[5]=flags;
  fascmd[6]=((baud << 4) | (baud));
  
  add_cmd(sts, fascmd, FAS_CMD_LEN);

  /* set up port's cmd status */
  sts->ports[port]->status[FAS_SET_PARAMS]=FAS_PENDING;

  return 1;
}

/* stuff a FAS_SET_MODEM into the cmd list */
int fas_set_modem(sts_state * sts, unsigned int port, unsigned int rts,
	unsigned int dtr) {
  unsigned char fascmd[] = {
    FAS_SET_MODEM,	/* Command Opcode */
    0,			/* Port number */
    0,			/* RTS Control */
    0,			/* DTR Control */
    0,0,0		/* Unused */
  };

  if (!sts) {
	fprintf(stderr,"fas_set_modem: got null sts!\n");
	return 0;
  }

  if (sts->portsensed) {
	if (port>=sts->total_ports) {
		fprintf(stderr,"fas_set_modem: port %d out of range (> %d)!\n",
				port, sts->total_ports-1);
		return 0;
	}
  }
  else {
	fprintf(stderr,"fas_set_modem: port auto sensing not finished\n");
	return 0;
  }

  fascmd[1]=port;
  fascmd[2]=rts;
  fascmd[3]=dtr;
  
  add_cmd(sts, fascmd, FAS_CMD_LEN);

  /* set up port's cmd status */
  sts->ports[port]->status[FAS_SET_MODEM]=FAS_PENDING;

  return 1;
}

/* stuffs a FAS_GLOBAL into the cmd list */
int fas_global(sts_state * sts, unsigned int blinky) {
  unsigned char fascmd[] = {
    FAS_GLOBAL,		/* Command Opcode */
    0,			/* Version (FIXME: we should say 4.1 or 5.7) */
    30,0,		/* interrupt rate (least sig first) */
    0, 0,		/* Buffer Size */
    0,			/* Option Flags */
#ifdef STS_DEBUG
    1			/* heavy error-checking */
#else
    0			/* No heavy error-checking */
#endif
  };

  if (!sts) {
	fprintf(stderr,"fas_global: got null sts!\n");
	return 0;
  }

  if (blinky) fascmd[6]|=GLOBAL_LED_BLINK;
  else fascmd[6]=(fascmd[6] & (~GLOBAL_LED_BLINK));

#ifdef STS_DEBUG
  fascmd[6]|=GLOBAL_DEBUG_MODE;
#endif

  add_cmd(sts, fascmd, FAS_CMD_LEN);

  return 1;
}

/* waits for all message to flush from the SCSI queue */
int fas_msg_flush(sts_state * sts) {
  int i;

  if (!sts) {
	fprintf(stderr,"fas_msg_flush: got null sts!\n");
	return 0;
  }

  sts->flushed=0;
  for (i=0;i<10;i++) {
	waitfor_recv_fas_block(sts);
	if (sts->flushed) return 1;
  }
  fprintf(stderr,"STS SCSI Recv queue would not flush -- aborting\n");
  return 0;
}

/* gathers and sends all the cmds until the cmd list is empty */
void flush_and_wait(sts_state * sts) {
	int len;
	unsigned char buffer[FAS_PKT_MAX];

	for (len=gather_fas_block(sts,buffer);
	     len;
	     len=gather_fas_block(sts,buffer)) {
		if (send_fas_block(sts, buffer, len) == FAS_OK)
			waitfor_recv_fas_block(sts);
	}
}

/* gathers each block and sends it */
void flush_cmd_list(sts_state * sts) {
	int len;
	unsigned char buffer[FAS_PKT_MAX];

	for (len=gather_fas_block(sts,buffer);
	     len;
	     len=gather_fas_block(sts,buffer)) {
		send_fas_block(sts, buffer, len);
	}
}

sts_state * initialize_sts(char * argv[]) {
  sts_state *sts;
  Sg_scsi_id id_info;
  int seen_id=0, lun, fd, flags;
  unsigned char extra_len;
  unsigned char * response;
  unsigned char * ptr;
  char * extra;
  int i,port;

  /* alloc with cleared memory */
  if (!(sts=(sts_state *)calloc(1,sizeof(sts_state)))) {
	perror("calloc");
	return NULL;
  }
  sts->sendfd=sts->recvfd=-1;

  /* to read and verify the device args */
  for (i=1;i<3;i++) {
	if ((fd = open (argv[i], O_RDWR | O_EXCL | O_NONBLOCK))<0) {
		fprintf(stderr,
			"Need exclusive read/write permissions on '%s'\nopen: ",
	                argv[i]);
		perror("");
		return NULL;
	}
	if ((flags = fcntl(fd, F_GETFL))<0) {
		perror("fcntl F_GETFL");
		return NULL;
	}
	if (fcntl(fd, F_SETFL, flags & (~ O_NONBLOCK))<0) {
		perror("fcntl F_SETFL");
		return NULL;
	}
	if (sts->sendfd<0)
		sts->sendfd=fd;
	else
		sts->recvfd=fd;
  
	if (ioctl(fd, SG_GET_SCSI_ID, &id_info)<0) {
		perror("ioctl SG_GET_SCSI_ID");
		return NULL;
	}  
        if (!seen_id) {
		seen_id=1;
		sts->host=id_info.host_no;
		sts->chan=id_info.channel;
		sts->id=id_info.scsi_id;
		lun=id_info.lun;
	}
	else {
		if (sts->host != id_info.host_no ||
		    sts->chan != id_info.channel ||
		    sts->id   != id_info.scsi_id ||
		    lun != !id_info.lun) {
			/* FIXME: dump both sets of IDs */
			fprintf(stderr,"Mismatched devices!  host/chan/id are "
				"different, or LUNs are the same.\n");
			return NULL;
		}
	}

	response = Inquiry(fd);
	extra_len = response[EXTRA_OFF];
	ptr=response + VENDOR_START;
	memcpy(sts->vendor,ptr, VENDOR_LEN); sts->vendor[VENDOR_LEN]='\0';
	ptr+=VENDOR_LEN;
	memcpy(sts->product,ptr, PRODUCT_LEN); sts->product[PRODUCT_LEN]='\0';
	ptr+=PRODUCT_LEN;
	memcpy(sts->rev,ptr, REV_LEN); sts->rev[REV_LEN]='\0';
	/* get extra if it is there */
	ptr+=REV_LEN;
	extra_len -= ((ptr-response) - 1);
	if (fd == sts->sendfd)
		extra = sts->sendextra;
	else
		extra = sts->recvextra;
	/* clear out extra */
	memset(extra,0,EXTRA_LEN+1);
	if (extra_len > 0)
		memcpy(extra,ptr, extra_len);

	printf("vendor: [%s]  product: [%s]  rev: [%s%s%s%s]\n",sts->vendor,
		sts->product,sts->rev,*extra ? " (" : "",
		extra, *extra ? ")" : "");

	/* look if medium is loaded */
	if (!TestForMedium (sts->sendfd)) {
		fprintf (stderr,"device is not ready -- aborting...\n");
		return NULL;
	}

	  printf("  host: %d chan: %d id: %d lun: %d\n",sts->host, sts->chan,
		sts->id, id_info.lun);

  }

#if 0
  sts_debug("flushing SCSI commands ...\n");
  /* flush the msg queue in case anything is pending */
  if (!fas_msg_flush(sts)) return NULL;
#endif
  /* set up global parameters */
  fas_global(sts,atoi(argv[3]));
  flush_and_wait(sts);

  if (!sts->globaled) {
	fprintf(stderr,"Cannot set global STS parameters -- aborting\n");
	return NULL;
  }

  sts_debug("sensing ports ...\n");

  /* autosense ports */
  for (port=0;;port++) {
	fas_enable(sts,port,0);
	fas_enable(sts,port,1);
	flush_and_wait(sts);
	/* did it fail to be reserved? */
	if (port>=sts->total_ports) break;
	/* || !sts->ports[port]->reserved) break;*/
  }
  fas_msg_flush(sts); /* just in case */
  sts->portsensed=1; /* done sensing ports */

  if (!sts->total_ports) {
	fprintf(stderr,"Cannot enable any ports -- aborting\n");
	return NULL;
  }
  else {
	printf("%d port%s detected.\n",(sts->total_ports),
		sts->total_ports==0 ? "" : "s");
  }

  /* FIXME: auto-sense maximum baud rate here */

#ifdef DO_RESERVATIONS
  sts_debug("reserving ports ...\n");

  /* reserve ports */
  for (port=0;port<sts->total_ports;port++) {
	fas_reserve(sts,port,0);
  }
  flush_and_wait(sts);
  fas_msg_flush(sts); /* just in case */
#endif

  sts_debug("enabling ports ...\n");

  /* enable ports */
  for (port=0;port<sts->total_ports;port++) {
	fas_enable(sts,port,0);
  }
  flush_and_wait(sts);
  fas_msg_flush(sts); /* just in case */
  for (port=0;port<sts->total_ports;port++) {
	/* did it get enabled? */
	if (!sts->ports[port]->enabled) {
		fprintf(stderr,"warning: port %d did not enable!\n",port);
	}
  }

  for (port=0;port<sts->total_ports;port++) {
	fas_set_modem(sts,port,1,1);
	fas_set_params(sts,port,PARAMS_BITS8,PARAMS_STOP1,PARAMS_PARITYNO,
		PARAMS_STRIPNO|PARAMS_RECVENABLE,PARAMS_BAUD38400);
  }
  flush_and_wait(sts);
  fas_msg_flush(sts); /* just in case */
  for (port=0;port<sts->total_ports;port++) {
	/* did it get set? */
	if (sts->ports[port]->status[FAS_SET_MODEM]) {
		fprintf(stderr,"warning: port %d did not take modem "
			"settings!\n",port);
	}
  }

  return sts;
}

int open_pty(struct tty_state_t * tty, int i) {
	char * ptr;

	if ((tty->ptm=getpt())<0) {
		perror("getpt");
		return 0;
	}

	if (grantpt(tty->ptm)<0) {
		close(tty->ptm);
		perror("grantpt");
		return 0;
	}

	if (unlockpt(tty->ptm)<0) {
		close(tty->ptm);
		perror("unlockpt");
		return 0;
	}

	if ( (ptr = ptsname(tty->ptm)) ) {
		if (!(tty->pts_name=strdup(ptr))) {
			perror("strdup");
			return 0;
		}
               	printf("STS port %d is '%s'\n",i,ptr);
	}
	else {
		perror("ptsname");
		return 0;
	}

	return 1;
}

int initialize_ptys(sts_state * sts) {
	int i;

	if (!(sts->ttys=calloc(sts->total_ports,sizeof(tty_state *)))) {
		fprintf(stderr,"Cannot allocate tty array\n");
		perror("calloc");
		return 0;
	}
	for (i=0;i<sts->total_ports;i++) {
		if (!(sts->ttys[i]=calloc(1,sizeof(tty_state)))) {
			fprintf(stderr,"Cannot allocate tty data\n");
			perror("calloc");
			return 0;
		}

		if (!open_pty(sts->ttys[i],i)) {
			return 0;
		}
	}

	return 1;
}

void sts_loop(sts_state * sts) {
  int i, maxfd, len;
  fd_set read_set;
  fd_set write_set;
  fd_set excl_set;
  unsigned char buffer[FAS_PKT_MAX];
	
  /* find largest fd */
  maxfd=0;
  if (sts->recvfd > maxfd) maxfd=sts->recvfd;
  if (sts->sendfd > maxfd) maxfd=sts->sendfd;

  /* post RECV to every port */
  /* post STAT_CHG to every port */
  for (i=0;i<sts->total_ports;i++) {
	if (sts->ttys[i]->ptm > maxfd) maxfd=sts->ttys[i]->ptm;
	fas_recv(sts,i,FAS_SEND_MAX); /* FIXME: 300 baud, har har har */
	/* FIXME: write this 
	fas_stat_chg(sts,i);
	*/
  }
  /* flush commands */
  flush_cmd_list(sts);
  /* issue Recv processing */
  recv_fas_block(sts);

  maxfd++;

  printf("Unit Online.\n");

  /* loop until shutdown */
  while (!sts->shutdown) {
	FD_ZERO( &read_set );
	FD_ZERO( &write_set );
	FD_ZERO( &excl_set );

	/* add ttys w/o pending STS write to readset */
	/* add all ttys to exclset */
	for (i=0;i<sts->total_ports;i++) {
		FD_SET( sts->ttys[i]->ptm, &excl_set );
		if (sts->ports[i]->status[FAS_SEND]!=FAS_PENDING) {
			FD_SET( sts->ttys[i]->ptm, &read_set );
		}
		else {
			sts_debug("sts_loop: skipping tty %d (FAS_SEND "
				"pending)\n",i);
		}		
	}
	/* add sts fds to readset */
	FD_SET( sts->recvfd, &read_set );
#if 0
	FD_SET( sts->sendfd, &read_set );
	/* add sts send fs to writeset */
	FD_SET( sts->sendfd, &write_set );
#endif
	/* add sts fds to exclset */
	FD_SET( sts->sendfd, &excl_set );
	FD_SET( sts->recvfd, &excl_set );

	/* select (or poll?) */
	if ((select( maxfd, &read_set, &write_set, &excl_set, NULL)) < 0) {
		if (errno != EINTR) { /* caught our signal */
			perror("select");
		}
		sts->shutdown=1;
		continue;
	}
	sts_debug("select finished\n");

	/* FIXME: must test excl sets! */

	/* if sts recv fd is readable */
	if (FD_ISSET( sts->recvfd, &read_set )) {
		/* process FAS block */
		recv_fas_block_ret(sts);
		/* post new Recv */
		recv_fas_block(sts);
	}
	/* foreach tty */
	for (i=0;i<sts->total_ports;i++) {
		/* if tty readable */
		if (FD_ISSET(sts->ttys[i]->ptm, &read_set)) {
			sts_debug("got ptm input\n");
			/* read tty, post FAS_SEND */
			len=read(sts->ttys[i]->ptm, buffer, FAS_SEND_MAX);
			if (len>0) {
				fas_send(sts,i,buffer,len);
			}
			else if (len<0 && errno!=EIO) {
				fprintf(stderr,"sts_loop: tty %d error\n", i);
				perror("read");
			}
			if (len==0 || (len<0 && errno==EIO)) {
				/* end of file, close & reopen pty */
				close(sts->ttys[i]->ptm);
				if (!open_pty(sts->ttys[i],i)) {
					fprintf(stderr,"Could not reopen TTY "
						"%d\n",i);
					return;
				}
			}
		}
	}
#if 0
	/* if sts Send fd is readable */
	if (FD_ISSET( sts->sendfd, &read_set )) {
		/* report any errors */
	}
	/* if FAS cmds are queued, & Send fd available */
	if (FD_ISSET( sts->sendfd, &write_set ) &&
	    sts->head) {
		/* post Send to STS */
		flush_cmd_list(sts);
	}
#endif
	/* FIXME: something is wrong with this.  Basically, if I'm
	   doing a flush (which waits on the SCSI answer), the
	   sendfd will ALWAYS be writable.  Therefore, this code loop
	   will just spin very very rapidly.

	   However, unless an event occurs elsewhere, I shouldn't
	   ever need to send more commands, as long as I flush the
	   entire command list each time through to loop.  That, however,
	   might cause delays?
        */
	flush_cmd_list(sts);
  }
  printf("Unit Shutting Down.\n");
}

void shutdown_sts(sts_state * sts) {
	int i;

	if (!sts) {
		fprintf(stderr,"shutdown_sts: sts is NULL\n");
		return;
	}

	for (i=0;i<sts->total_ports;i++) {
		if (sts->ports[i]->enabled) fas_enable(sts,i,1);
	}
	fas_global(sts,0);

	/* send all the fas commands */
	flush_cmd_list(sts);

	/* handle last half of recv */
	recv_fas_block_ret(sts);

	/* reflush, just in case */
	fas_msg_flush(sts);

	/* close all the pty's */
	for (i=0;i<sts->total_ports;i++)
		close(sts->ttys[i]->ptm);

	close(sts->sendfd);
	close(sts->recvfd);
	sts->sendfd=sts->recvfd=-1;

	return;
}

void die_off(int sig) {
	/* do nothing */
	return;
}

int main (int argc, char * argv[]) {
  sts_state *sts;
  struct sigaction sig;

  if (argc<4) {
     fprintf (stderr,
	"Which 2 devices should be used for the sts? (and blink on or off?)\n");
     return 1;
  }

  /* initialize STS & ports */
  if (!(sts = initialize_sts(argv))) {
	fprintf(stderr,"STS not initialized\n");
	return 2;
  }

  /* open, clear, and initialize ptys */
  if (!initialize_ptys(sts)) {
	fprintf(stderr,"Could not open ptys\n");
	return 3;
  }

  /* set up signal handlers */
  memset(&sig,0,sizeof(struct sigaction));
  sig.sa_handler=die_off;
  sigaction(2,&sig,NULL); /* INT */
  sigaction(3,&sig,NULL); /* QUIT */
  sigaction(15,&sig,NULL);/* TERM */

  /* processing loop */
  sts_loop(sts);

  /* shutdown and close STS ports & PTYs*/
  /* shutdown STS */
  shutdown_sts(sts);

  return 0;
}

#ifdef JUST_MY_DESIGN
08/07/2000

Goals: design a user space (and eventually a kernel-space) SCSI Terminal
       Server driver.

Implementation:
	- use SCSI generic driver to access both LUNs of the STS
	- use ptys to attach the STS ports to

Blocks:
	- I have no idea how to pass user-changes of termios back to the
	  STS


Basic operational outline:

- verify that we have the proper device pair (by matching host/channel/id/lun)
- run INQUIRYs
- run TEST_UNIT_READYs
- query device for # of ports
- initialize global STS parameters
- open, clear, and initialize STS ports
- open, clear, and initialize ptys
- mark STS write fd as writable
- post initial STS read cmd
- enter processing loop (bulk of run-time)
- exit processing loop
- close PTYs safely
- shutdown and close STS ports
- shutdown STS


The processing loop outline:

- add PTYs without pending STS writes to read set
- add all PTYS to exclusion set
- add STS fds to readset
- add STS fds to exclusion set
- select on fd set
- if STS read fd is readable:
	- process STS packet(s)
	- repost STS read cmd
- for each PTY:
	- if PTY is readable:
		- read PTY
		- buffer reads for future STS writes
- if STS write fd is readable
	- process STS packet(s)
	- mark STS write fd as writable
- if STS write fd is writable
	- build STS packet(s)
	- post STS write cmd
	- mark STS write fd as not writable
- repeat from the top


Some structures:

typeset struct sts_state_t {
	int bus, ctrlr, id;      /* SCSI identifiers */
	char * manufacturer;     /* SCSI manufacturer string */
	char * model;            /* SCSI model string */
	char * rev;              /* SCSI revision */
	int writefd, readfd;     /* write and read fds (LUN 0 and 1) */
	int total_ports;         /* how many ports in the STS */
	struct port_state_t * ports;  /* array of STS port info structures */
	int writable:1;          /* can I post an STS write cmd? */
	int readable:1;          /* can I post an STS read cmd? */
	struct tty_state_t * ttys;  /* array of PTY info structures */
} sts_state;

typeset struct port_state_t {
	int available;      /* is this port usable at all? */
	int writable;       /* is it OK to post an STS writes for this port? */
	/* serial stuff should go here ..... */
} port_state;

typeset struct tty_state_t {
	int ptm;            /* pseudo terminal master fd */
	char * pts_name;    /* strdup'd copy of the pt slave name */
	/* anything else here? ..... */
} tty_state;
#endif
