/* User code for floppytape.
   Copyright (C) 1992 David L. Brown, Jr.

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; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*
 * Modified for ftape code by Bas Laarhoven (SJL), 30/06/93.
 *
 * userft.c,v 1.14 1992/10/13 01:45:08 dbrown Exp
 *
 * userft.c,v
 * Revision 1.14  1992/10/13  01:45:08  dbrown
 * Added FSF copyright.
 *
 * Revision 1.13  1992/10/12  05:13:33  dbrown
 * Added ioctl to set drive data rate.
 *
 * Revision 1.12  1992/10/10  07:02:09  dbrown
 * Made write stream.
 *
 * Revision 1.11  1992/10/10  03:19:38  dbrown
 * Added write error handling so that errors and their location can be
 * passed back to the user.
 *
 * Revision 1.10  1992/10/09  05:53:44  dbrown
 * Read is reliable.  Write is flaky and can't handle errors.
 *
 * Revision 1.9  1992/10/09  01:29:22  dbrown
 * Write now streams and works somewhat robustly.  One little possible
 * crashing position left.
 *
 * Revision 1.8  1992/10/09  00:36:15  dbrown
 * Initial writing of full write.  Untested.
 *
 * Revision 1.7  1992/10/08  23:45:24  dbrown
 * Read now does a copyout instead of using mapped buffers.  Before, read
 * didn't have any control of when the user would actually get the data
 * out of the buffer.  If the user delayed, then that data could be
 * overwritten with the read from tape.
 *
 * Revision 1.6  1992/10/08  23:29:28  dbrown
 * Performs streaming reliable reads.
 *
 * Revision 1.5  1992/10/08  04:52:42  dbrown
 * First attempt to add streaming read.  Doesn't really work and panics
 * periodically.
 *
 * Revision 1.4  1992/10/04  23:10:14  dbrown
 * Added facilities for performing seek test.
 *
 * Revision 1.3  1992/10/04  21:46:01  dbrown
 * Now supports non-streaming, single sector reads.
 *
 * Revision 1.2  1992/10/03  23:56:40  dbrown
 * Moved enough of the old driver into the new to get drive status, and
 * seek the tape and the tape head.
 *
 * Revision 1.1  1992/10/03  22:54:02  dbrown
 * Copied from old driver.
 */

#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>

#include "ftape-ioctl.h"
#include "qic117.h"

void run (int fd);
void read_many (int fd);

int fdt_report_status( int fd, int verbose)
{
    int status;
    int result = ioctl (fd, FT_REPORT_STATUS, &status);
    if (result != 0) {
        perror ("report drive status");
        result = -1;
    } else if (verbose) {
        printf( "Drive status: (0x%02x)\n", status);
        if (verbose > 1) {
            if (!(status & QIC_STATUS_READY)) {
                printf( "  Drive not ready !\n");
            }
            if ((status & QIC_STATUS_ERROR)) {
                printf( "  Error detected !\n");
            }
            if (!(status & QIC_STATUS_CARTRIDGE_PRESENT)) {
                printf( "  No cartridge present !\n");
            }
            if ((status & QIC_STATUS_WRITE_PROTECT)) {
                printf( "  Tape write protected !\n");
            }
            if ((status & QIC_STATUS_NEW_CARTRIDGE)) {
                printf( "  New tape inserted !\n");
            }
            if (!(status & QIC_STATUS_REFERENCED)) {
                printf( "  Tape not formatted !\n");
            }
            if ((status & QIC_STATUS_AT_BOT)) {
                printf( "  Tape at BOT !\n");
            }
            if ((status & QIC_STATUS_AT_EOT)) {
                printf( "  Tape at EOT !\n");
            }
        }
    }
    return result;
}

int fdt_report_error_code( int fd, int verbose)
{
    static ftape_error ftape_errors[] = QIC117_ERRORS;
    static char* ftape_cmds[] = QIC117_COMMANDS;
    struct fdt_error error;
    int result = ioctl (fd, FT_REPORT_ERROR_CODE, &error);
    if (result != 0) {
        perror ("report ftape error code");
        result = -1;
    } else if (verbose) {
        printf( "Error code: 0x%02x (command: 0x%02x)\n",
               error.error, error.command);
        if (verbose > 1) {
            if (error.error >= 0 &&
                error.error < sizeof( ftape_errors)/sizeof( ftape_error)) {
                printf( "%sFatal ERROR: %s,\n",
                       (ftape_errors[ error.error].fatal ? "" : "Non-"),
                       ftape_errors[ error.error].message);
            } else {
                printf( "Unknown ERROR !\n");
            }
            if (error.command >= 0 &&
                error.command < sizeof( ftape_cmds)/sizeof( *ftape_cmds) &&
                ftape_cmds[ error.command] != NULL) {
                printf( "    caused by command %s\n",
                       ftape_cmds[ error.command]);
            } else {
                printf( "Unknown COMMAND (%d)!\n", error.command);
            }
        }
    }
    return result;
}

int
main (int argc, char **argv)
{
  int fd;
  char *name;
  int done = 0;
  int result;
  struct _fdt_id id;
  int value;
  int is_tty = 1;
  struct write_cease wc;
  int verbose= 2;

  if (argc < 2)
    name = "/dev/ftape";
  else
    {
      name = argv[1];
      if (argc >= 2)
	is_tty = 0;
    }

  fd = open (name, O_RDWR);
  if (fd < 0)
    {
      perror ("Opening tape");
      exit (1);
    }

  run (fd);

  printf ("h for Help\n");
  while (!done)
    {
      char cmd[80];
      printf ("--> ");
      gets (cmd);		/* Yuck. */
      if (!is_tty)
	puts (cmd), fflush (stdout);
      switch (*cmd)
	{
	case 'h':
	  printf ("s  - report Status       e  - report Error\n");
	  printf ("B  - Beginning of tape   E  - End of tape.\n");
	  printf ("fn - forward n segments  rn - reverse n segments\n");
	  printf ("#  - Submit error map    I  - Read id\n");
          printf ("Sn - Seek to track.      @n - Set data rate to n bps.\n");
          printf ("t  - stop tape           R  - reset tape drive\n");
          printf ("F  - logical forward     P  - pause tape\n");
	  printf ("<n m - read m segments starting at n\n");
	  printf (">n m - write m segments starting at m\n");
/*
M - Read many ids.\n\
# - Submit an error map (causes sync)\n\
<n - Read segment n\n
*/
	  printf ("Q - Quit                 Tn - set trace level\n");
	  break;
	case 's':
          result = fdt_report_status( fd, verbose);
	  break;
	case 'e':
	  result = fdt_report_error_code( fd, verbose);
	  break;
	case 'B':
          printf( "\nFT_SEEK_TO_BEGINNING = 0x%08lx\n\n", FT_SEEK_TO_BEGINNING);
	  result = ioctl (fd, FT_SEEK_TO_BEGINNING, 0);
	  if (result != 0)
	    perror ("Seek to beginning");
	  break;
	case 'E':
	  result = ioctl (fd, FT_SEEK_TO_END, 0);
	  if (result != 0)
	    perror ("Seek to end");
	  break;
	case 'f':
	  value = atoi (&cmd[1]);
	  result = ioctl (fd, FT_SEEK_FORWARD, &value);
	  if (result != 0)
	    perror ("Seek forward");
	  break;
	case 'r':
	  value = atoi (&cmd[1]);
	  result = ioctl (fd, FT_SEEK_REVERSE, &value);
	  if (result != 0)
	    perror ("Seek backward");
	  break;
	case '@':
	  value = atoi (&cmd[1]);
	  result = ioctl (fd, FT_SET_DATA_RATE, &value);
	  if (result != 0)
	    perror ("Select data rate");
	  break;
#if 0
	case 'I':
	  {
	    struct _fdt_find_me fm;
	    fm.segment = 0;
	    result = ioctl (fd, FT_FIND_ME, &fm);
	    printf ("result = %d, actual = %d\n",
		    result, fm.actual_segment);
	  }
	  break;
#endif
	case 't':
	  result = ioctl (fd, FT_STOP_TAPE, 0);
	  if (result != 0)
	    perror ("Stop tape");
	  break;
	case 'R':
	  result = ioctl (fd, FT_RESET, 0);
	  if (result != 0)
	    perror ("Reset tape drive");
	  break;
	case 'F':
	  result = ioctl (fd, FT_LOGICAL_FORWARD, 0);
	  if (result != 0)
	    perror ("Logical forward");
	  break;
	case 'P':
	  result = ioctl (fd, FT_PAUSE, 0);
	  if (result != 0)
	    perror ("Pause");
	  break;
	case 'S':
	  value = atoi (&cmd[1]);
	  result = ioctl (fd, FT_SEEK_TO_TRACK, &value);
	  if (result != 0)
	    perror ("Seek to track");
	  break;
	case 'I':
	  result = ioctl (fd, FT_READ_ID, &id);
	  if (result != 0)
	    perror ("Read id.");
	  else
	    {
	      printf ("ID: cylinder = %d, head = %d, sector = %d\n",
		      id.cylinder, id.head, id.sector);
	    }
	  break;
	case '#':
	  {
	    struct error_map emap;
	    unsigned long error_masks[150];
	    bzero (error_masks, 150 * sizeof (unsigned long));
	    emap.count = 150;
	    emap.error_masks = error_masks;
	    result = ioctl (fd, FT_SUBMIT_ERROR_MAP, &emap);
	    if (result != 0)
	      {
		perror ("Cannot submit error map");
		exit (1);
	      }
	  }
	  break;
	case '>':
	  {
	    int fdi;
	    struct tape_write tw;
	    char buffer[32768];
	    int count;

	    sscanf (&cmd[1], "%d%d", &tw.segment,
		    &count);
	    fdi = open ("INPUT", O_RDONLY);
	    if (fdi < 0)
	      {
		perror ("Can't open input\n");
		break;
	      }

	    tw.buffer = buffer;
	    while (count > 0)
	      {
		result = read (fdi, buffer, 32768);
		if (result != 32768)
		  {
		    perror ("Can't read");
		    continue;
		  }
		result = write( fd, buffer, sizeof( buffer));
		printf ("Write result = %d, (%d), error on %d\n", result,
			tw.actual_segment, tw.error_location);
		if (result != 0)
		  perror ("");
		tw.segment++;
		count--;
	      }
            result = write( fd, NULL, -1);
	    if (result < 0)
	      perror ("Ceasing");
	    printf ("Ceased actual_segment = %d, error on %d\n",
		    wc.actual_segment, wc.error_location);
	    close (fdi);
	  }
	  break;
	case '<':
	  {
	    int fdo;
	    struct tape_read tr;
	    char buffer[32768];
	    tr.buffer = buffer;
	    sscanf (&cmd[1], "%d%d", &tr.segment,
		    &tr.count);

	    fdo = open ("OUTPUT", O_WRONLY | O_TRUNC | O_CREAT,
			0666);
	    if (fdo < 0)
	      perror ("Can't create OUTPUT"), exit (1);

	    while (tr.count > 0)
	      {
                result = read( fd, buffer, sizeof( buffer));
		if (result < 0) {
		  perror ("Read error");
                  break;
                }
		result = write (fdo, buffer, 32768);
		if (result != 32768)
		  perror ("Can't write");
		printf ("actual = %d, buffer = %p, error_bits = 0x%08x\n",
			tr.actual_segment, tr.buffer, tr.error_bits);
		tr.segment++;
		tr.count--;
	      }
            close( fdo);
	  }
	  break;
	case 'Q':
	  done = 1;
	  break;
	case 'T':
	  value = atoi (&cmd[1]);
	  result = ioctl (fd, FT_SET_TRACE_LEVEL, &value);
	  if (result != 0)
	    perror ("Set trace level");
	  break;
	default:
	  printf ("Unknown command\n");
	  break;
	}
    }

  close (fd);
  return 0;
}

void
run (int fd)
{
  int status;
  struct fdt_error error;
  int result;

  result = ioctl (fd, FT_REPORT_STATUS, &status);
  if (result != 0)
    perror ("report drive status");
  else
    printf ("status = 0x%x\n", status);

  result = ioctl (fd, FT_REPORT_ERROR_CODE, &error);
  if (result != 0)
    perror ("report error");
  else
    printf ("error = 0x%x, command = 0x%x\n", error.error,
	    error.command);
  result = ioctl (fd, FT_REPORT_CONFIGURATION, &status);
  if (result != 0)
    perror ("report drive configuration");
  else
    printf ("configuration = 0x%x\n", status);

  result = ioctl (fd, FT_REPORT_ROM_VERSION, &status);
  if (result != 0)
    perror ("report rom version");
  else
    printf ("rom version = 0x%x\n", status);

  result = ioctl (fd, FT_REPORT_VENDOR_ID, &status);
  if (result != 0)
    perror ("report vendor id");
  else
    printf ("vendor id = 0x%x\n", status);
}
