/* $Id: tape.c,v 1.13 1991/09/28 02:46:46 dvtmc Rel $ */
/*****************************************************************************
* 	  (c) Copyright  1989 Thinking Machines Corporation, Inc.,	     *
*		of Cambridge, Mass.   All rights reserved.		     *
*									     *
*  This notice is intended as a precaution against inadvertent publication   *
*  and does not constitute an admission or acknowledgement that publication  *
*  has occurred or constitute a waiver of confidentiality.		     *
*									     *
*  Connection Machine software is the proprietary and confidential property  *
*  of Thinking Machines Corporation.					     *
*****************************************************************************/

#include <sys/types.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>

#if !defined(major)
#include <sys/sysmacros.h>
#endif

#include "cm_ioctl.h"
#include "cm_mtio.h"
#include "cm_param.h"
#include "cm_errno.h"
#include "cm_conf.h"
#include "cm_file.h"

/*
 * Tape drive device driver
 * for the CM file system
 */

#define T_NOREWIND	0x04
#define T_HIGH_DENSITY	0x08

#define NTAPE 4

#define swapshort(x)	(((x<<8)|((x>>8)&0xff))&0xffff)
#define swaplong(x)	((swapshort(x)<<16)|swapshort((x)>>16))

struct cmfs_tape_info {
    int tape_fd;		/* file descriptor of the UNIX tape device */
    int tape_flags;		/* some state information */
    int tape_bufsz;		/* buffer size to use for tape I/O */
} cmfs_tape_info[NTAPE];

/* Flags */
#define OPEN 1
#define AT_EOF 2

#define TAPE_BUFSZ (10*1024)

extern int errno;

static swap_mtop();
static swap_mtget();

/*
 * Open routine
 * Open the UNIX tape device
 */
int tapeopen(dev, flags)
dev_t dev;
int flags;
{
    int unit;
    char unix_dev[80];
    int ret;
    struct cmfs_tape_info *cti;
    
    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];
    
#ifdef sun
    sprintf(unix_dev, "/dev/%srst%d",
	    minor(dev) & T_NOREWIND ? "n" : "",
	    minor(dev) & T_HIGH_DENSITY ? unit + 8 : unit);
#endif
#ifdef vax
    sprintf(unix_dev, "/dev/%srmt%d%s",
	    minor(dev) & T_NOREWIND ? "n" : "",
	    unit,
	    minor(dev) & T_HIGH_DENSITY ? "h" : "l");
#endif
    
    flags = flags & (CMFS_O_RDONLY|CMFS_O_WRONLY|CMFS_O_RDWR);
    cti->tape_fd = open(unix_dev, flags);

    if (cti->tape_fd < 0)
	return(errno);

    cti->tape_flags = OPEN;
    cti->tape_bufsz = TAPE_BUFSZ;

    return (0);
}

/*
 * Close routine
 * Close the UNIX tape device
 */
int tapeclose(dev, flags)
dev_t dev;
int flags;
{
    int unit;
    struct cmfs_tape_info *cti;
    
    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];

    cti->tape_flags = 0;
    return (close(cti->tape_fd));
}

/*
 * Read from the tape and write to the I/O system
 */
int taperead(dev, uio)
dev_t dev;
struct cm_uio *uio;
{
    int unit;
    struct cmfs_tape_info *cti;
    int bytes_read, size;
    int ret, err;

    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];

    if (!(cti->tape_flags&OPEN))
	return (CMFS_EINVAL);

    ret = 0;

    while (uio->uio_resid > 0) {
	/*
	 * Figure out how much to read
	 */
	size = min(uio->uio_bufsz, uio->uio_resid);
 	if (cti->tape_flags & AT_EOF) {
	    /* Can't read anymore, send back zeros */
	    bzero(uio->uio_buf, size);
	    bytes_read = size;
	} else {
	    /* Read the tape */
	    bytes_read = read(cti->tape_fd, uio->uio_buf, size);
	    if (bytes_read == 0) {
		cti->tape_flags |= AT_EOF;
		bzero(uio->uio_buf, size);
	    } else if (bytes_read < 0) {
		cti->tape_flags |= AT_EOF;
		ret = errno;
		bzero(uio->uio_buf, size);
	    }
	}

	/* Send it to the CM */
	if (err = (*uio->uio_xfr)(uio, uio->uio_buf, bytes_read)) {
	    return(err);
	}
	uio->uio_resid -= bytes_read;
    }
    return ret;
}

/*
 * Write data from the CM I/O system 
 * to the tapedrive
 */
int tapewrite(dev, uio)
dev_t dev;
struct cm_uio *uio;
{
    int unit;
    struct cmfs_tape_info *cti;
    int size, bytes_written, ret;
    int blocks, bytes_left, err;
    char *p;

    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];

    if (!(cti->tape_flags&OPEN))
	return (CMFS_EINVAL);

    ret = 0;
    while (uio->uio_resid > 0) {
	/*
	 * Write the tape with a blocksize of cti->tape_bufsz
	 */
	size = min(uio->uio_bufsz, uio->uio_resid);
	blocks = size / cti->tape_bufsz;
	bytes_left = size % cti->tape_bufsz;
	if ((blocks > 0) && bytes_left && (size > bytes_left))
	    size -= bytes_left;
	p = uio->uio_buf;
	if (err = (*uio->uio_xfr)(uio, p, size)) {
	    return(err);
	}
	while (blocks-- > 0) {
	    if (!(cti->tape_flags & AT_EOF)) {
		bytes_written = write(cti->tape_fd, p, cti->tape_bufsz);
		if (bytes_written < 0) {
		    cti->tape_flags |= AT_EOF;
		    ret = errno;
		}
	    }
	    p += cti->tape_bufsz;
	    uio->uio_resid -= cti->tape_bufsz;
	}
	if (bytes_left > 0) {
	    if (!(cti->tape_flags & AT_EOF)) {
		bytes_written = write(cti->tape_fd, p, bytes_left);
		if (bytes_written < 0) {
		    cti->tape_flags |= AT_EOF;
		    ret = errno;
		}
	    }
	    uio->uio_resid -= bytes_left;
	}
    }
    return ret;
}

/*
 * Make a guess at how much we can read
 */
int 
tapesize(dev, count)
dev_t dev;
int count;
{
    int unit;
    struct cmfs_tape_info *cti;

    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];

    if (!(cti->tape_flags&OPEN))
	return (0);

    if (cti->tape_flags & AT_EOF)
	return(0);

    return (count);
}


/*
 * IOCTL routine
 * Here we implement all the standard UNIX
 * magtape operations
 */
int tapeioctl(dev, cmd, data, need_to_swap)
dev_t dev;
int cmd, need_to_swap;
caddr_t data;
{
    int unit;
    struct cmfs_tape_info *cti;
    int ret = 0;

    unit = minor(dev) & 0x3;
    cti = &cmfs_tape_info[unit];

    if (!(cti->tape_flags&OPEN))
	return (CMFS_EINVAL);

    switch(cmd) {
    case CMMTIOCTOP:
	{
	    struct cm_mtop *mop = (struct cm_mtop *)data;

	    if (need_to_swap)
		swap_mtop(mop);
	    if (ioctl(cti->tape_fd, MTIOCTOP, mop) < 0)
		ret = errno;
	    if (need_to_swap)
		swap_mtop(mop);
	}
	break;

    case CMMTIOCGET:
	{
	    struct cm_mtget *mg = (struct cm_mtget *)data;
	
	    if (ioctl(cti->tape_fd, MTIOCGET, mg) < 0)
		ret = errno;
	    if (need_to_swap)
		swap_mtget(mg);
	}
	break;

    case CMMTIOCSETREC:
	{
	    int reclen;

	    reclen = *(int *) data;
	    if (need_to_swap)
		reclen = swaplong(reclen);

	    cti->tape_bufsz = reclen;
	}
	break;

    default:
	ret = CMFS_ENOTTY;
	break;
    }

    return ret;
}

/*
 * byte swapping routines for when we are running
 * on a system with different byte order from the originator
 * of the ioctl call
 */

static
swap_mtop(mop)
struct cm_mtop *mop;
{
    mop->mt_op = swapshort(mop->mt_op);
    mop->mt_op = swaplong(mop->mt_count);
}

static
swap_mtget(mg)
struct cm_mtget *mg;
{
    mg->mt_type = swapshort(mg->mt_type);
    mg->mt_dsreg = swapshort(mg->mt_dsreg);
    mg->mt_erreg = swapshort(mg->mt_erreg);
    mg->mt_resid = swapshort(mg->mt_resid);
}

