!C99Shell v. 1.0 pre-release build #13!

Software: Apache/2.0.54 (Unix) mod_perl/1.99_09 Perl/v5.8.0 mod_ssl/2.0.54 OpenSSL/0.9.7l DAV/2 FrontPage/5.0.2.2635 PHP/4.4.0 mod_gzip/2.0.26.1a 

uname -a: Linux snow.he.net 4.4.276-v2-mono-1 #1 SMP Wed Jul 21 11:21:17 PDT 2021 i686 

uid=99(nobody) gid=98(nobody) groups=98(nobody) 

Safe-mode: OFF (not secure)

/usr/src/linux-2.4.18-xfs-1.1/drivers/char/   drwxr-xr-x
Free 318.37 GB of 458.09 GB (69.5%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Feedback    Self remove    Logout    


Viewing file:     tpqic02.c (86.56 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* $Id: tpqic02.c,v 1.10 1997/01/26 07:13:20 davem Exp $
 *
 * Driver for tape drive support for Linux-i386
 *
 * Copyright (c) 1992--1996 by H. H. Bergman. All rights reserved.
 * Current e-mail address: hennus@cybercomm.nl
 *
 * Distribution of this program in executable form is only allowed if
 * all of the corresponding source files are made available through the same
 * medium at no extra cost.
 *
 * I will not accept any responsibility for damage caused directly or
 * indirectly by this program, or code derived from this program.
 *
 * Use this code at your own risk. Don't blame me if it destroys your data!
 * Make sure you have a backup before you try this code.
 *
 * If you make changes to my code and redistribute it in source or binary
 * form you must make it clear to even casual users of your code that you
 * have modified my code, clearly point out what the changes exactly are
 * (preferably in the form of a context diff file), how to undo your changes,
 * where the original can be obtained, and that complaints/requests about the
 * modified code should be directed to you instead of me.
 *
 * This driver was partially inspired by the 'wt' driver in the 386BSD
 * source distribution, which carries the following copyright notice:
 *
 *  Copyright (c) 1991 The Regents of the University of California.
 *  All rights reserved.
 *
 * You are not allowed to change this line nor the text above.
 *
 * 2001/02/26    Minor s/suser/capable/
 *
 * 1996/10/10   Emerald changes
 *
 * 1996/05/21    Misc changes+merges+cleanups + I/O reservations
 *
 * 1996/05/20    Module support patches submitted by Brian McCauley.
 *
 * 1994/05/03    Initial attempt at Mountain support for the Mountain 7150.
 * Based on patches provided by Erik Jacobson. Still incomplete, I suppose.
 *
 * 1994/02/07    Archive changes & some cleanups by Eddy Olk.
 *
 * 1994/01/19    Speed measuring stuff moved from aperf.h to delay.h.
 *        BogoMips (tm) introduced by Linus.
 *
 * 1993/01/25    Kernel udelay. Eof fixups.
 * 
 * 1992/09/19    Some changes based on patches by Eddy Olk to support
 *         Archive SC402/SC499R controller cards.
 *
 * 1992/05/27    First release.
 *
 * 1992/05/26    Initial version. Copyright H. H. Bergman 1992
 */

/* After the legalese, now the important bits:
 * 
 * This is a driver for the Wangtek 5150 tape drive with 
 * a QIC-02 controller for ISA-PC type computers.
 * Hopefully it will work with other QIC-02 tape drives as well.
 *
 * Make sure your setup matches the configuration parameters.
 * Also, be careful to avoid IO conflicts with other devices!
 */


/*
#define TDEBUG
*/

#define REALLY_SLOW_IO        /* it sure is ... */

#include <linux/module.h>

#include <linux/config.h>

#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/errno.h>
#include <linux/mtio.h>
#include <linux/fcntl.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/tpqic02.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>

#include <asm/dma.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/uaccess.h>

/* check existence of required configuration parameters */
#if !defined(QIC02_CMD_PORT) || !defined(QIC02_TAPE_IRQ) || !defined(QIC02_TAPE_DMA)
# error qic02_tape configuration error
#endif


#define TPQIC02_NAME    "tpqic02"

/* Linux outb() commands have (value,port) as parameters.
 * One might expect (port,value) instead, so beware!
 */

#ifdef CONFIG_QIC02_DYNCONF
/* This holds the dynamic configuration info for the interface
 * card+drive info if runtime configuration has been selected.
 */

static struct mtconfiginfo qic02_tape_dynconf =    /* user settable */
{ 0, 0, BOGUS_IRQ, 0, 0, TPQD_DEFAULT_FLAGS, };
static struct qic02_ccb qic02_tape_ccb;    /* private stuff */

#else

unsigned long qic02_tape_debug = TPQD_DEFAULT_FLAGS;

# if ((QIC02_TAPE_IFC!=WANGTEK) && (QIC02_TAPE_IFC!=ARCHIVE) && (QIC02_TAPE_IFC!=MOUNTAIN))
#  error No valid interface card specified
# endif
#endif                /* CONFIG_QIC02_DYNCONF */

static volatile int ctlbits;    /* control reg bits for tape interface */

static wait_queue_head_t qic02_tape_transfer;    /* sync rw with interrupts */

static volatile struct mtget ioctl_status;    /* current generic status */

static volatile struct tpstatus tperror;    /* last drive status */

static char rcs_revision[] = "$Revision: 1.10 $";
static char rcs_date[] = "$Date: 1997/01/26 07:13:20 $";

/* Flag bits for status and outstanding requests.
 * (Could all be put in one bit-field-struct.)
 * Some variables need `volatile' because they may be modified
 * by an interrupt.
 */
static volatile flag status_dead = YES;    /* device is legally dead until proven alive */
static flag status_zombie = YES;    /* it's `zombie' until irq/dma allocated */

static volatile flag status_bytes_wr = NO;    /* write FM at close or not */
static volatile flag status_bytes_rd = NO;    /* (rd|wr) used for rewinding */

static volatile unsigned long status_cmd_pending;    /* cmd in progress */
static volatile flag status_expect_int = NO;    /* ready for interrupts */
static volatile flag status_timer_on = NO;    /* using time-out */
static volatile int status_error;    /* int handler may detect error */
static volatile flag status_eof_detected = NO;    /* end of file */
static volatile flag status_eom_detected = NO;    /* end of recorded media */
static volatile flag status_eot_detected = NO;    /* end of tape */
static volatile flag doing_read = NO;
static volatile flag doing_write = NO;

static volatile unsigned long dma_bytes_todo;
static volatile unsigned long dma_bytes_done;
static volatile unsigned dma_mode;    /* !=0 also means DMA in use */
static flag need_rewind = YES;

static kdev_t current_tape_dev;
static int extra_blocks_left = BLOCKS_BEYOND_EW;

static struct timer_list tp_timer;

/* return_*_eof:
 *    NO:    not at EOF,
 *    YES:    tell app EOF was reached (return 0).
 *
 * return_*_eof==YES && reported_*_eof==NO  ==>
 *    return current buffer, next time(s) return EOF.
 *
 * return_*_eof==YES && reported_*_eof==YES  ==>
 *    at EOF and application knows it, so we can
 *    move on to the next file.
 *
 */
static flag return_read_eof = NO;    /* set to signal app EOF was reached */
static flag return_write_eof = NO;
static flag reported_read_eof = NO;    /* set when we've done that */
static flag reported_write_eof = NO;


/* This is for doing `mt seek <blocknr>' */
static char seek_addr_buf[AR_SEEK_BUF_SIZE];


/* In write mode, we have to write a File Mark after the last block written, 
 * when the tape device is closed. Tape repositioning and reading in write
 * mode is allowed as long as no actual writing has been done. After writing
 * the File Mark, repositioning and reading are allowed again.
 */
static int mode_access;        /* access mode: READ or WRITE */

static int qic02_get_resources(void);
static void qic02_release_resources(void);

/* This is a pointer to the actual kernel buffer where the interrupt routines
 * read from/write to. It is needed because the DMA channels 1 and 3 cannot
 * always access the user buffers. [The kernel buffer must reside in the
 * lower 16MBytes of system memory because of the DMA controller.] The user
 * must ensure that a large enough buffer is passed to the kernel, in order
 * to reduce tape repositioning wear and tear.
 */
static void *buffaddr;        /* virtual address of buffer */

/* This translates minor numbers to the corresponding recording format: */
static const char *format_names[] = {
    "not set",        /* for dumb drives unable to handle format selection */
    "11",            /* extinct */
    "24",
    "120",
    "150",
    "300",            /* untested. */
    "600"            /* untested. */
};


/* `exception_list' is needed for exception status reporting.
 * Exceptions 1..14 are defined by QIC-02 rev F.
 * The drive status is matched sequentially to each entry,
 * ignoring irrelevant bits, until a match is found. If no
 * match is found, exception number 0 is used. (That should of
 * course never happen...) The original table was based on the
 * "Exception Status Summary" in QIC-02 rev F, but some changes
 * were required to make it work with real-world drives.
 *
 * Exception 2 (CNI) is changed to also cover status 0x00e0 (mask USL),
 * Exception 4 (EOM) is changed to also cover status 0x8288 (mask EOR),
 * Exception 11 (FIL) is changed to also cover status 0x0089 (mask EOM).
 * Exception 15 (EOR) is added for seek-to-end-of-data (catch EOR),
 * Exception 16 (BOM) is added for beginning-of-media (catch BOM).
 *
 * Had to swap EXC_NDRV and EXC_NCART to ensure that extended EXC_NCART
 * (because of the incorrect Wangtek status code) doesn't catch the
 * EXC_NDRV first.
 */
static struct exception_list_type {
    unsigned short mask, code;
    const char *msg;
    /* EXC_nr attribute should match with tpqic02.h */
} exception_list[] = {
    {
    0, 0, "Unknown exception status code", /* extra: 0 */ },
    {
    ~(0), TP_ST0 | TP_CNI | TP_USL | TP_WRP,
            "Drive not online" /* 1 */ },
        /* Drive presence goes before cartridge presence. */
    {
        ~(TP_WRP | TP_USL), TP_ST0 | TP_CNI,
            /* My Wangtek 5150EQ sometimes reports a status code
             * of 0x00e0, which is not a valid exception code, but
             * I think it should be recognized as "NO CARTRIDGE".
             */
    "Cartridge not in place" /* 2 */ },
    {
    (unsigned short) ~(TP_ST1 | TP_BOM), (TP_ST0 | TP_WRP),
            "Write protected cartridge" /* 3 */ },
    {
    (unsigned short) ~(TP_ST1 | TP_EOR), (TP_ST0 | TP_EOM),
            "End of media" /* 4 */ },
    {
    ~TP_WRP, TP_ST0 | TP_UDA | TP_ST1 | TP_BOM,
            "Read or Write abort. Rewind tape." /* 5 */ },
    {
    ~TP_WRP, TP_ST0 | TP_UDA,
            "Read error. Bad block transferred." /* 6 */ },
    {
    ~TP_WRP, TP_ST0 | TP_UDA | TP_BNL,
            "Read error. Filler block transferred." /* 7 */ },
    {
    ~TP_WRP, TP_ST0 | TP_UDA | TP_BNL | TP_ST1 | TP_NDT,
            "Read error. No data detected." /* 8 */ },
    {
    ~TP_WRP,
            TP_ST0 | TP_EOM | TP_UDA | TP_BNL | TP_ST1 |
            TP_NDT, "Read error. No data detected. EOM." /* 9 */ },
    {
    ~(TP_WRP | TP_MBD | TP_PAR | TP_EOR),
            TP_ST0 | TP_UDA | TP_BNL | TP_ST1 | TP_NDT |
            TP_BOM,
            "Read error. No data detected. BOM." /* 10 */ },
    {
        ~(TP_WRP | TP_EOM), TP_ST0 | TP_FIL,
            /* Status 0x0089 (EOM & FM) is viewed as an FM,
             * because it can only happen during a read.
             * EOM is checked separately for an FM condition.
             */
    "File mark detected" /* 11 */ },
    {
    ~(TP_ST0 | TP_CNI | TP_USL | TP_WRP | TP_BOM),
            TP_ST1 | TP_ILL, "Illegal command" /* 12 */ },
    {
    ~(TP_ST0 | TP_CNI | TP_USL | TP_WRP | TP_BOM),
            TP_ST1 | TP_POR, "Reset occurred" /* 13 */ },
    {
        ~TP_WRP, TP_ST0 | TP_FIL | TP_MBD,    /* NOTE: ST1 not set! */
    "Marginal block detected" /* 14 */ },
    {
        ~(TP_ST0 | TP_WRP | TP_EOM | TP_UDA | TP_BNL | TP_FIL |
          TP_NDT), TP_ST1 | TP_EOR,
        /********** Is the extra TP_NDT really needed Eddy? **********/
    "End of recorded media" /* extra: 15 */ },
        /* 15 is returned when SEEKEOD completes successfully */
    {
    ~(TP_WRP | TP_ST0), TP_ST1 | TP_BOM, "Beginning of media" /* extra: 16 */ }
};

#define NR_OF_EXC    (sizeof(exception_list)/sizeof(struct exception_list_type))

/* Compare expected struct size and actual struct size. This
 * is useful to catch programs compiled with old #includes.
 */
#define CHECK_IOC_SIZE(structure) \
    if (_IOC_SIZE(iocmd) != sizeof(struct structure)) { \
        tpqputs(TPQD_ALWAYS, "sizeof(struct " #structure \
            ") does not match!"); \
        return -EFAULT; \
    } \

static void tpqputs(unsigned long flags, const char *s)
{
    if ((flags & TPQD_ALWAYS) || (flags & QIC02_TAPE_DEBUG))
        printk(TPQIC02_NAME ": %s\n", s);
}                /* tpqputs */


/* Perform byte order swapping for a 16-bit word.
 *
 * [FIXME] This should probably be in include/asm/
 * ([FIXME] i486 can do this faster)
 */
static inline void byte_swap_w(volatile unsigned short *w)
{
    int t = *w;
    *w = (t >> 8) | ((t & 0xff) << 8);
}



/* Init control register bits on interface card.
 * For Archive, interrupts must be enabled explicitly.
 * Wangtek interface card requires ONLINE to be set, Archive SC402/SC499R
 * cards keep it active all the time.
 */
static void ifc_init(void)
{
    if (QIC02_TAPE_IFC == WANGTEK) {    /* || (QIC02_TAPE_IFC == EVEREX) */
        ctlbits = WT_CTL_ONLINE;    /* online */
        outb_p(ctlbits, QIC02_CTL_PORT);
    } else if (QIC02_TAPE_IFC == ARCHIVE) {
        ctlbits = 0;    /* no interrupts yet */
        outb_p(ctlbits, QIC02_CTL_PORT);
        outb_p(0, AR_RESET_DMA_PORT);    /* dummy write to reset DMA */
    } else {        /* MOUNTAIN */

        ctlbits = MTN_CTL_ONLINE;    /* online, and logic enabled */
        outb_p(ctlbits, QIC02_CTL_PORT);
    }
}                /* ifc_init */


static void report_qic_exception(unsigned n)
{
    if (n >= NR_OF_EXC) {
        tpqputs(TPQD_ALWAYS, "Oops -- report_qic_exception");
        n = 0;
    }
    if (TPQDBG(SENSE_TEXT) || n == 0) {
        printk(TPQIC02_NAME ": sense: %s\n",
               exception_list[n].msg);
    }
}                /* report_qic_exception */


/* Try to map the drive-exception bits `s' to a predefined "exception number",
 * by comparing the significant exception bits for each entry in the
 * exception table (`exception_list[]').
 * It is assumed that s!=0.
 */
static int decode_qic_exception_nr(unsigned s)
{
    int i;

    for (i = 1; i < NR_OF_EXC; i++) {
        if ((s & exception_list[i].mask) == exception_list[i].code) {
            return i;
        }
    }
    printk(TPQIC02_NAME
           ": decode_qic_exception_nr: exception(%x) not recognized\n",
           s);
    return 0;
}                /* decode_qic_exception_nr */



/* Perform appropriate action for certain exceptions.
 * should return a value to indicate stop/continue (in case of bad blocks)
 */
static void handle_qic_exception(int exnr, int exbits)
{
    if (exnr == EXC_NCART) {
        /* Cartridge was changed. Redo sense().
         * EXC_NCART should be handled in open().
         * It is not permitted to remove the tape while
         * the tape driver has open files. 
         */
        need_rewind = YES;
        status_eof_detected = NO;
        status_eom_detected = NO;
    } else if (exnr == EXC_XFILLER) {
        tpqputs(TPQD_ALWAYS,
            "[Bad block -- filler data transferred.]");
    } else if (exnr == EXC_XBAD) {
        tpqputs(TPQD_ALWAYS, "[CRC failed!]");
    } else if (exnr == EXC_MARGINAL) {
        /* A marginal block behaves much like a FM.
         * User may continue reading, if desired.
         */
        tpqputs(TPQD_ALWAYS, "[Marginal block]");
        doing_read = NO;
    } else if (exnr == EXC_FM) {
        doing_read = NO;
    }
}                /* handle_qic_exception */


static inline int is_exception(void)
{
    return (inb(QIC02_STAT_PORT) & QIC02_STAT_EXCEPTION) == 0;
}                /* is_exception */


/* Reset the tape drive and controller.
 * When reset fails, it marks  the drive as dead and all
 * requests (except reset) are to be ignored (ENXIO).
 */
static int tape_reset(int verbose)
{
    ifc_init();        /* reset interface card */

    /* assert reset */
    if (QIC02_TAPE_IFC == MOUNTAIN) {
        outb_p(ctlbits & ~MTN_QIC02_CTL_RESET_NOT, QIC02_CTL_PORT);
    } else {        /* WANGTEK, ARCHIVE */

        outb_p(ctlbits | QIC02_CTL_RESET, QIC02_CTL_PORT);
    }

    /* Next, we need to wait >=25 usec. */
    udelay(30);

    /* after reset, we will be at BOT (modulo an automatic rewind) */
    status_eof_detected = NO;
    status_eom_detected = NO;
    status_cmd_pending = 0;
    need_rewind = YES;
    doing_read = doing_write = NO;
    ioctl_status.mt_fileno = ioctl_status.mt_blkno = 0;

    /* de-assert reset */
    if (QIC02_TAPE_IFC == MOUNTAIN) {
        outb_p(ctlbits | MTN_QIC02_CTL_RESET_NOT, QIC02_CTL_PORT);
    } else {
        outb_p(ctlbits & ~QIC02_CTL_RESET, QIC02_CTL_PORT);
    }

    /* KLUDGE FOR G++ BUG */
    {
        int stat = inb_p(QIC02_STAT_PORT);
        status_dead =
            ((stat & QIC02_STAT_RESETMASK) != QIC02_STAT_RESETVAL);
    }
    /* if successful, inb(STAT) returned RESETVAL */
    if (status_dead == YES) {
        printk(TPQIC02_NAME ": reset failed!\n");
    } else if (verbose) {
        printk(TPQIC02_NAME ": reset successful\n");
    }

    return (status_dead == YES) ? TE_DEAD : TE_OK;
}                /* tape_reset */



/* Notify tape drive of a new command. It only waits for the
 * command to be accepted, not for the actual command to complete.
 *
 * Before calling this routine, QIC02_CMD_PORT must have been loaded
 * with the command to be executed.
 * After this routine, the exception bit must be checked.
 * This routine is also used by rdstatus(), so in that case, any exception
 * must be ignored (`ignore_ex' flag).
 */
static int notify_cmd(char cmd, short ignore_ex)
{
    int i;

    outb_p(cmd, QIC02_CMD_PORT);    /* output the command */

    /* wait 1 usec before asserting /REQUEST */
    udelay(1);

    if ((!ignore_ex) && is_exception()) {
        tpqputs(TPQD_ALWAYS, "*** exception detected in notify_cmd");
        /** force a reset here **/
        if (tape_reset(1) == TE_DEAD)
            return TE_DEAD;
        if (is_exception()) {
            tpqputs(TPQD_ALWAYS, "exception persists after reset.");
            tpqputs(TPQD_ALWAYS, " ^ exception ignored.");
        }
    }

    outb_p(ctlbits | QIC02_CTL_REQUEST, QIC02_CTL_PORT);    /* set request bit */
    i = TAPE_NOTIFY_TIMEOUT;
    /* The specs say this takes about 500 usec, but there is no upper limit!
     * If the drive were busy retensioning or something like that,
     * it could be *much* longer!
     */
    while ((inb_p(QIC02_STAT_PORT) & QIC02_STAT_READY) && (--i > 0))
        /*skip */ ;
    /* wait for ready */
    if (i == 0) {
        tpqputs(TPQD_ALWAYS,
            "timed out waiting for ready in notify_cmd");
        status_dead = YES;
        return TE_TIM;
    }

    outb_p(ctlbits & ~QIC02_CTL_REQUEST, QIC02_CTL_PORT);    /* reset request bit */
    i = TAPE_NOTIFY_TIMEOUT;
    /* according to the specs this one should never time-out */
    while (((inb_p(QIC02_STAT_PORT) & QIC02_STAT_READY) == 0) && (--i > 0))
        /*skip */ ;
    /* wait for not ready */
    if (i == 0) {
        tpqputs(TPQD_ALWAYS, "timed out waiting for !ready in notify_cmd");
        status_dead = YES;
        return TE_TIM;
    }
    /* command accepted */
    return TE_OK;
}                /* notify_cmd */



/* Wait for a command to complete, with timeout */
static int wait_for_ready(time_t timeout)
{
    int stat;
    time_t spin_t;

    /* Wait for ready or exception, without driving the loadavg up too much.
     * In most cases, the tape drive already has READY asserted,
     * so optimize for that case.
     *
     * First, busy wait a few usec:
     */
    spin_t = 50;
    while (((stat = inb_p(QIC02_STAT_PORT) & QIC02_STAT_MASK) == QIC02_STAT_MASK) && (--spin_t > 0))
        /*SKIP*/;
    if ((stat & QIC02_STAT_READY) == 0)
        return TE_OK;    /* covers 99.99% of all calls */

    /* Then use schedule() a few times */
    spin_t = 3;        /* max 0.03 sec busy waiting */
    if (spin_t > timeout)
        spin_t = timeout;
    timeout -= spin_t;
    spin_t += jiffies;

    /* FIXME...*/
    while (((stat = inb_p(QIC02_STAT_PORT) & QIC02_STAT_MASK) == QIC02_STAT_MASK) 
        && time_before(jiffies, spin_t))
        schedule();    /* don't waste all the CPU time */
    if ((stat & QIC02_STAT_READY) == 0)
        return TE_OK;

    /* If we reach this point, we probably need to wait much longer, or
     * an exception occurred. Either case is not very time-critical.
     * Check the status port only a few times every second.
     * A interval of less than 0.10 sec will not be noticed by the user,
     * more than 0.40 sec may give noticeable delays.
     */
    spin_t += timeout;
    TPQDEB({printk("wait_for_ready: additional timeout: %d\n", spin_t);})

        /* not ready and no exception && timeout not expired yet */
    while (((stat = inb_p(QIC02_STAT_PORT) & QIC02_STAT_MASK) == QIC02_STAT_MASK) && time_before(jiffies, spin_t)) {
        /* be `nice` to other processes on long operations... */
        current->state = TASK_INTERRUPTIBLE;
        /* nap 0.30 sec between checks, */
        /* but could be woken up earlier by signals... */
        schedule_timeout(3 * HZ / 10);
    }

    /* don't use jiffies for this test because it may have changed by now */
    if ((stat & QIC02_STAT_MASK) == QIC02_STAT_MASK) {
        tpqputs(TPQD_ALWAYS, "wait_for_ready() timed out");
        return TE_TIM;
    }

    if ((stat & QIC02_STAT_EXCEPTION) == 0) {
        tpqputs(TPQD_ALWAYS,
            "exception detected after waiting_for_ready");
        return TE_EX;
    } else {
        return TE_OK;
    }
}                /* wait_for_ready */



/* Send some data to the drive */
static int send_qic02_data(char sb[], unsigned size, int ignore_ex)
{
    int i, stat;

    for (i = 0; i < size; i++) {

        stat = wait_for_ready(TIM_S);
        if (stat != TE_OK)
            return stat;

        stat = notify_cmd(sb[i], ignore_ex);
        if (stat != TE_OK)
            return stat;
    }
    return TE_OK;

}                /* send_qic02_data */


/* Send a QIC-02 command (`cmd') to the tape drive, with
 * a time-out (`timeout').
 * This one is also used by tp_sense(), so we must have
 * a flag to disable exception checking (`ignore_ex'). 
 *
 * On entry, the controller is supposed to be READY.
 */
static int send_qic02_cmd(int cmd, time_t timeout, int ignore_ex)
{
    int stat;

    stat = inb_p(QIC02_STAT_PORT);
    if ((stat & QIC02_STAT_EXCEPTION) == 0) {    /* if exception */
        tpqputs(TPQD_ALWAYS, "send_qic02_cmd: Exception!");
        return TE_EX;
    }
    if (stat & QIC02_STAT_READY) {    /* if not ready */
        tpqputs(TPQD_ALWAYS, "send_qic02_cmd: not Ready!");
        return TE_ERR;
    }

    /* assert(ready & !exception) */

    /* Remember current command for later re-use with dma transfers.
     * (For reading/writing multiple blocks.)
     */
    status_cmd_pending = cmd;

    stat = notify_cmd(cmd, ignore_ex);    /* tell drive new command was loaded, */
    /* inherit exception check. */
    if (TP_HAVE_SEEK && (cmd == AR_QCMDV_SEEK_BLK)) {
        /* This one needs to send 3 more bytes, MSB first */
        stat = send_qic02_data(seek_addr_buf, sizeof(seek_addr_buf), ignore_ex);
    }

    if (stat != TE_OK) {
        tpqputs(TPQD_ALWAYS, "send_qic02_cmd failed");
    }
    return stat;
}                /* send_qic02_cmd */



/* Get drive status. Assume drive is ready or has exception set.
 * (or will be in <1000 usec.)
 * Extra parameters added because of 'Read Extended Status 3' command.
 */
static int rdstatus(char *stp, unsigned size, char qcmd)
{
    int s, n;
    char *q = stp;

    /* Try to busy-wait a few (700) usec, after that de-schedule.
     *
     * The problem is, if we don't de-schedule, performance will
     * drop to zero when the drive is not responding and if we
     * de-schedule immediately, we waste a lot of time because a
     * task switch is much longer than we usually have to wait here.
     */
    n = 1000;        /* 500 is not enough on a 486/33 */
    while ((n > 0) && ((inb_p(QIC02_STAT_PORT) & QIC02_STAT_MASK) == QIC02_STAT_MASK))
        n--;        /* wait for ready or exception or timeout */
    if (n == 0) {
        /* n (above) should be chosen such that on your machine
         * you rarely ever see the message below, and it should
         * be small enough to give reasonable response time.]
         */
         /* FIXME */
        tpqputs(TPQD_ALWAYS, "waiting looong in rdstatus() -- drive dead?");
        while ((inb_p(QIC02_STAT_PORT) & QIC02_STAT_MASK) == QIC02_STAT_MASK)
            schedule();
        tpqputs(TPQD_ALWAYS, "finished waiting in rdstatus()");
    }

    (void) notify_cmd(qcmd, 1);    /* send read status command */
    /* ignore return code -- should always be ok, STAT may contain 
     * exception flag from previous exception which we are trying to clear.
     */

    if (TP_DIAGS(current_tape_dev))
        printk(TPQIC02_NAME ": reading status bytes: ");

    for (q = stp; q < stp + size; q++) {
        do
            s = inb_p(QIC02_STAT_PORT);
        while ((s & QIC02_STAT_MASK) == QIC02_STAT_MASK);    /* wait for ready or exception */

        if ((s & QIC02_STAT_EXCEPTION) == 0) {    /* if exception */
            tpqputs(TPQD_ALWAYS, "rdstatus: exception error");
            ioctl_status.mt_erreg = 0;    /* dunno... */
            return TE_NS;    /* error, shouldn't happen... */
        }

        *q = inb_p(QIC02_DATA_PORT);    /* read status byte */

        if (TP_DIAGS(current_tape_dev))
            printk("[%1d]=0x%x  ", q - stp,
                   (unsigned) (*q) & 0xff);

        outb_p(ctlbits | QIC02_CTL_REQUEST, QIC02_CTL_PORT);    /* set request */

        while ((inb_p(QIC02_STAT_PORT) & QIC02_STAT_READY) == 0);    /* wait for not ready */

        udelay(22);    /* delay >20 usec */

        outb_p(ctlbits & ~QIC02_CTL_REQUEST, QIC02_CTL_PORT);    /* un-set request */

    }

    /* Specs say we should wait for READY here.
     * My drive doesn't seem to need it here yet, but others do?
     */
    while (inb_p(QIC02_STAT_PORT) & QIC02_STAT_READY)
        /*skip */ ;
    /* wait for ready */

    if (TP_DIAGS(current_tape_dev))
        printk("\n");

    return TE_OK;
}                /* rdstatus */



/* Get standard status (6 bytes).
 * The `.dec' and `.urc' fields are in MSB-first byte-order,
 * so they have to be swapped first.
 */
static int get_status(volatile struct tpstatus *stp)
{
    int stat = rdstatus((char *) stp, TPSTATSIZE, QCMD_RD_STAT);
#if defined(__i386__) || defined (__x86_64__)
    byte_swap_w(&(stp->dec));
    byte_swap_w(&(stp->urc));
#else
#warning Undefined architecture
    /* should probably swap status bytes #definition */
#endif
    return stat;
}                /* get_status */


#if 0
/* This fails for my Wangtek drive */
/* get "Extended Status Register 3" (64 bytes)
 *
 * If the meaning of the returned bytes were known, the MT_TYPE
 * identifier could be used to decode them, since they are
 * "vendor unique". :-(
 */
static int get_ext_status3(void)
{
    char vus[64];        /* vendor unique status */
    int stat, i;

    tpqputs(TPQD_ALWAYS, "Attempting to read Extended Status 3...");
    stat = rdstatus(vus, sizeof(vus), QCMD_RD_STAT_X3);
    if (stat != TE_OK)
        return stat;

    tpqputs(TPQD_ALWAYS, "Returned status bytes:");
    for (i = 0; i < sizeof(vus); i++) {
        if (i % 8 == 0)
            printk("\n" TPQIC02_NAME ": %2d:");
        printk(" %2x", vus[i] & 0xff);
    }
    printk("\n");

    return TE_OK;
}                /* get_ext_status3 */
#endif


/* Read drive status and set generic status too.
 * NOTE: Once we do a tp_sense(), read/write transfers are killed.
 */
static int tp_sense(int ignore)
{
    unsigned err = 0, exnr = 0, gs = 0;
    static void finish_rw(int cmd);

    if (TPQDBG(SENSE_TEXT))
        printk(TPQIC02_NAME ": tp_sense(ignore=0x%x) enter\n",
               ignore);

    /* sense() is not allowed during a read or write cycle */
    if (doing_write == YES)
        tpqputs(TPQD_ALWAYS, "Warning: File Mark inserted because of sense() request");
    /* The extra test is to avoid calling finish_rw during booting */
    if ((doing_read != NO) || (doing_write != NO))
        finish_rw(QCMD_RD_STAT);

    if (get_status(&tperror) != TE_OK) {
        tpqputs(TPQD_ALWAYS, "tp_sense: could not read tape drive status");
        return TE_ERR;
    }

    err = tperror.exs;    /* get exception status bits */
    if (err & (TP_ST0 | TP_ST1))
        printk(TPQIC02_NAME ": tp_sense: status: %x, error count: %d, underruns: %d\n",
               tperror.exs, tperror.dec, tperror.urc);
    else if ((tperror.dec != 0) || (tperror.urc != 0)
         || TPQDBG(SENSE_CNTS))
        printk(TPQIC02_NAME
               ": tp_sense: no hard errors, soft error count: %d, underruns: %d\n",
               tperror.dec, tperror.urc);

    /* Set generic status. HP-UX defines these, but some extra would 
     * be useful. Problem is to remain compatible. [Do we want to be
     * compatible??]
     */
    if (err & TP_ST0) {
        if (err & TP_CNI)    /* no cartridge */
            gs |= GMT_DR_OPEN(-1);
        if (status_dead == NO)
            gs |= GMT_ONLINE(-1);    /* always online */
        if (err & TP_USL)    /* not online */
            gs &= ~GMT_ONLINE(-1);
        if (err & TP_WRP)
            gs |= GMT_WR_PROT(-1);
        if (err & TP_EOM) {    /* end of media */
            gs |= GMT_EOT(-1);    /* not sure this is correct for writes */
            status_eom_detected = YES;
            /* I don't know whether drive always reports EOF at or before EOM. */
            status_eof_detected = YES;
        }
        /** if (err & TP_UDA) "Unrecoverable data error" **/
        /** if (err & TP_BNL) "Bad block not located" **/
        if (err & TP_FIL) {
            gs |= GMT_EOF(-1);
            status_eof_detected = YES;
        }
    }
    if (err & TP_ST1) {
        /** if (err & TP_ILL) "Illegal command" **/
        /** if (err & TP_NDT) "No data detected" **/
        /** if (err & TP_MBD) "Marginal block detected" **/
        if (err & TP_BOM)
            gs |= GMT_BOT(-1);    /* beginning of tape */
    }
    ioctl_status.mt_gstat = gs;
    ioctl_status.mt_dsreg = tperror.exs;    /* "drive status" */
    ioctl_status.mt_erreg = tperror.dec;    /* "sense key error" */

    if (err & (TP_ST0 | TP_ST1)) {
        /* My Wangtek occasionally reports `status' 1212 which should be ignored. */
        exnr = decode_qic_exception_nr(err);
        handle_qic_exception(exnr, err);    /* update driver state wrt drive status */
        report_qic_exception(exnr);
    }
    err &= ~ignore;        /* mask unwanted errors -- not the correct way, use exception nrs?? */
    if (((err & TP_ST0) && (err & REPORT_ERR0)) ||
        ((err & TP_ST1) && (err & REPORT_ERR1)))
        return TE_ERR;
    return TE_OK;
}                /* tp_sense */



/* Wait for a wind or rewind operation to finish or
 * to time-out. (May take very long).
 */
static int wait_for_rewind(time_t timeout)
{
    int stat;

    stat = inb(QIC02_STAT_PORT) & QIC02_STAT_MASK;
    if (TPQDBG(REWIND))
        printk(TPQIC02_NAME
               ": Waiting for (re-)wind to finish: stat=0x%x\n",
               stat);

    stat = wait_for_ready(timeout);

    if (stat != TE_OK) {
        tpqputs(TPQD_ALWAYS, "(re-) winding failed\n");
    }
    return stat;
}                /* wait_for_rewind */



/* Perform a full QIC02 command, and wait for completion,
 * check status when done. Complain about exceptions.
 *
 * This function should return an OS error code when
 * something goes wrong, 0 otherwise.
 */
static int ll_do_qic_cmd(int cmd, time_t timeout)
{
    int stat;

    if (status_dead == YES) {
        tpqputs(TPQD_ALWAYS, "Drive is dead. Do a `mt reset`.");
        return -ENXIO;    /* User should do an MTRESET. */
    }

    stat = wait_for_ready(timeout);    /* wait for ready or exception */
    if (stat == TE_EX) {
        if (tp_sense(TP_WRP | TP_BOM | TP_EOM | TP_FIL) != TE_OK)
            return -EIO;
        /* else nothing to worry about, I hope */
        stat = TE_OK;
    }
    if (stat != TE_OK) {
        printk(TPQIC02_NAME ": ll_do_qic_cmd(%x, %ld) failed\n",
               cmd, (long) timeout);
        return -EIO;
    }
#if OBSOLETE
    /* wait for ready since it may not be active immediately after reading status */
    while ((inb_p(QIC02_STAT_PORT) & QIC02_STAT_READY) != 0);
#endif

    stat = send_qic02_cmd(cmd, timeout, 0);    /* (checks for exceptions) */

    if (cmd == QCMD_RD_FM) {
        status_eof_detected = NO;
        ioctl_status.mt_fileno++;
        /* Should update block count as well, but can't.
         * Can do a `read address' for some drives, when MTNOP is done.
         */
    } else if (cmd == QCMD_WRT_FM) {
        status_eof_detected = NO;
        ioctl_status.mt_fileno++;
    } else if ((cmd == QCMD_REWIND) || (cmd == QCMD_ERASE)
           || (cmd == QCMD_RETEN)) {
        status_eof_detected = NO;
        status_eom_detected = NO;
        status_eot_detected = NO;
        need_rewind = NO;
        ioctl_status.mt_fileno = ioctl_status.mt_blkno = 0;
        extra_blocks_left = BLOCKS_BEYOND_EW;
        return_write_eof = NO;
        return_read_eof = NO;
        reported_read_eof = NO;
        reported_write_eof = NO;
    }
    /* sense() will set eof/eom as required */
    if (stat == TE_EX) {
        if (tp_sense(TP_WRP | TP_BOM | TP_EOM | TP_FIL) != TE_OK) {
            printk(TPQIC02_NAME
                   ": Exception persist in ll_do_qic_cmd[1](%x, %ld)",
                   cmd, (long) timeout);
            status_dead = YES;
            return -ENXIO;
            /* if rdstatus fails too, we're in trouble */
        }
    } else if (stat != TE_OK) {
        printk(TPQIC02_NAME
               ": ll_do_qic_cmd: send_qic02_cmd failed, stat = 0x%x\n",
               stat);
        return -EIO;    /*** -EIO is probably not always appropriate */
    }


    if (timeout == TIM_R)
        stat = wait_for_rewind(timeout);
    else
        stat = wait_for_ready(timeout);

    if (stat == TE_EX) {
        if (tp_sense((cmd == QCMD_SEEK_EOD ?        /*****************************/
                  TP_EOR | TP_NDT | TP_UDA | TP_BNL | TP_WRP |
                  TP_BOM | TP_EOM | TP_FIL : TP_WRP | TP_BOM |
                  TP_EOM | TP_FIL)) != TE_OK) {
            printk(TPQIC02_NAME
                   ": Exception persist in ll_do_qic_cmd[2](%x, %ld)\n",
                   cmd, (long) timeout);
            if (cmd != QCMD_RD_FM)
                status_dead = YES;
            return -ENXIO;
            /* if rdstatus fails too, we're in trouble */
        }
    } else if (stat != TE_OK) {
        printk(TPQIC02_NAME
               ": ll_do_qic_cmd %x: wait failed, stat == 0x%x\n",
               cmd, stat);
        return -EIO;
    }
    return 0;
}                /* ll_do_qic_cmd */


/* 
 * Problem: What to do when the user cancels a read/write operation
 * in-progress?
 *
 * "Deactivating ONLINE during a READ also causes the"
 * "tape to be rewound to BOT." Ditto for WRITEs, except
 * a FM is written first. "The host may alternatively terminate
 * the READ/WRITE command by issuing a RFM/WFM command."
 *
 * For READs:
 * Neither option will leave the tape positioned where it was.
 * Another (better?) solution is to terminate the READ by two
 * subsequent sense() operations, the first to stop the current
 * READ cycle, the second to clear the `Illegal command' exception,
 * because the QIC-02 specs didn't anticipate this. This is
 * delayed until actually needed, so a tar listing can be aborted
 * by the user and continued later.
 * If anybody has a better solution, let me know! [Also, let me
 * know if your drive (mine is a Wangtek5150EQ) does not accept
 * this sequence for canceling the read-cycle.]
 *
 * For WRITEs it's simple: Just do a WRITE_FM, leaving the tape
 * positioned after the FM.
 */

static void terminate_read(int cmd)
{
    if (doing_read == YES) {
        doing_read = NO;
        if (cmd != QCMD_RD_FM) {
            /* if the command is a RFM, there is no need to do this
             * because a RFM will legally terminate the read-cycle.
             */
            tpqputs(TPQD_ALWAYS, "terminating pending read-cycle");

            /* I'm not too sure about this part  -- hhb */
            if (QIC02_TAPE_IFC == MOUNTAIN) {
                /* Mountain reference says can terminate by de-asserting online */
                ctlbits &= ~MTN_QIC02_CTL_ONLINE;
            }

            if (tp_sense(TP_FIL | TP_EOM | TP_WRP) != TE_OK) {
                tpqputs(TPQD_ALWAYS,
                    "finish_rw[read1]: ignore the 2 lines above");
                if (is_exception()) {
                    if (tp_sense
                        (TP_ILL | TP_FIL | TP_EOM |
                         TP_WRP) != TE_OK)
                        tpqputs(TPQD_ALWAYS,"finish_rw[read2]: read cycle error");
                }
            }
        }
    }
}                /* terminate_read */


static void terminate_write(int cmd)
{
    int stat;

    if (doing_write == YES) {
        doing_write = NO;
        /* Finish writing by appending a FileMark at the end. */
        if (cmd != QCMD_WRT_FM) {
            /* finish off write cycle */
            stat = ll_do_qic_cmd(QCMD_WRT_FM, TIM_M);
            if (stat != TE_OK)
                tpqputs(TPQD_ALWAYS,
                    "Couldn't finish write cycle properly");
            (void) tp_sense(0);
        }
        /* If there is an EOF token waiting to be returned to
         * the (writing) application, discard it now.
         * We could be at EOT, so don't reset return_write_eof.
         */
        reported_write_eof = YES;
    }
}                /* terminate_write */


/* terminate read or write cycle because of command `cmd' */
static void finish_rw(int cmd)
{
    if (wait_for_ready(TIM_S) != TE_OK) {
        tpqputs(TPQD_ALWAYS,
            "error: drive not ready in finish_rw() !");
        return;
    }
    terminate_read(cmd);
    terminate_write(cmd);
}                /* finish_rw */


/* Perform a QIC command through ll_do_qic_cmd().
 * If necessary, rewind the tape first.
 * Return an OS error code if something goes wrong, 0 if all is well.
 */
static int do_qic_cmd(int cmd, time_t timeout)
{
    int stat;


    finish_rw(cmd);

    if (need_rewind) {
        tpqputs(TPQD_REWIND, "Rewinding tape...");
        stat = ll_do_qic_cmd(QCMD_REWIND, TIM_R);
        if (stat != 0) {
            printk(TPQIC02_NAME ": rewind failed in do_qic_cmd(). stat=0x%2x", stat);
            return stat;
        }
        need_rewind = NO;
        if (cmd == QCMD_REWIND)    /* don't wind beyond BOT ;-) */
            return 0;
    }

    return ll_do_qic_cmd(cmd, timeout);
}                /* do_qic_cmd */


/* Not all ioctls are supported for all drives. Some rely on
 * optional QIC-02 commands. Check tpqic02.h for configuration.
 * Some of these commands may require ONLINE to be active.
 */
static int do_ioctl_cmd(int cmd)
{
    int stat;

    /* It is not permitted to read or wind the tape after bytes have
     * been written. It is not permitted to write the tape while in
     * read mode.
     * We try to be kind and allow reading again after writing a FM...
     */

    switch (cmd) {
    case MTRESET:
        /* reset verbose */
        return (tape_reset(1) == TE_OK) ? 0 : -EIO;

    case MTFSF:
        tpqputs(TPQD_IOCTLS, "MTFSF forward searching filemark");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        return do_qic_cmd(QCMD_RD_FM, TIM_F);

    case MTBSF:
        if (TP_HAVE_BSF) {
            tpqputs(TPQD_IOCTLS,
                "MTBSF backward searching filemark -- optional command");
            if ((mode_access == WRITE) && status_bytes_wr)
                return -EACCES;
            stat = do_qic_cmd(QCMD_RD_FM_BCK, TIM_F);
        } else {
            stat = -ENXIO;
        }
        status_eom_detected = status_eof_detected = NO;
        return stat;

    case MTFSR:
        if (TP_HAVE_FSR) {    /* This is an optional QIC-02 command */
            tpqputs(TPQD_IOCTLS, "MTFSR forward space record");
            if ((mode_access == WRITE) && status_bytes_wr)
                return -EACCES;
            stat = do_qic_cmd(QCMD_SPACE_FWD, TIM_F);
        } else {
                /**** fake it by doing a read data block command? ******/
            tpqputs(TPQD_IOCTLS, "MTFSR not supported");
            stat = -ENXIO;
        }
        return stat;

    case MTBSR:
        if (TP_HAVE_BSR) {    /* This is an optional QIC-02 command */
            /* we need this for appending files with GNU tar!! */
            tpqputs(TPQD_IOCTLS, "MTFSR backward space record");
            if ((mode_access == WRITE) && status_bytes_wr)
                return -EACCES;
            stat = do_qic_cmd(QCMD_SPACE_BCK, TIM_F);
        } else {
            tpqputs(TPQD_IOCTLS, "MTBSR not supported");
            stat = -ENXIO;
        }
        status_eom_detected = status_eof_detected = NO;
        return stat;

    case MTWEOF:
        tpqputs(TPQD_IOCTLS, "MTWEOF write eof mark");
        /* Plain GNU mt(1) 2.2 uses read-only mode for writing FM. :-( */
        if (mode_access == READ)
            return -EACCES;

        /* allow tape movement after writing FM */
        status_bytes_rd = status_bytes_wr;    /* Kludge-O-Matic */
        status_bytes_wr = NO;
        return do_qic_cmd(QCMD_WRT_FM, TIM_M);
        /* not sure what to do with status_bytes when WFM should fail */

    case MTREW:
        tpqputs(TPQD_IOCTLS, "MTREW rewinding tape");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        status_eom_detected = status_eof_detected = NO;
        return do_qic_cmd(QCMD_REWIND, TIM_R);

    case MTOFFL:
        tpqputs(TPQD_IOCTLS, "MTOFFL rewinding & going offline");
        /* Doing a drive select will clear (unlock) the current drive.
         * But that requires support for multiple drives and locking.
         */
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        status_eom_detected = status_eof_detected = NO;
            /**** do rewind depending on minor bits??? ***/
        stat = do_qic_cmd(QCMD_REWIND, TIM_R);
        return stat;

    case MTNOP:
        tpqputs(TPQD_IOCTLS, "MTNOP setting status only");
            /********** should do `read position' for drives that support it **********/
        return (tp_sense(-1) == TE_OK) ? 0 : -EIO;    /**** check return codes ****/

    case MTRETEN:
        tpqputs(TPQD_IOCTLS, "MTRETEN retension tape");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        status_eom_detected = status_eof_detected = NO;
        return do_qic_cmd(QCMD_RETEN, TIM_R);

    case MTBSFM:
        /* Think think is like MTBSF, except that
         * we shouldn't skip the FM. Tricky.
         * Maybe use RD_FM_BCK, then do a SPACE_FWD?
         */
        tpqputs(TPQD_IOCTLS, "MTBSFM not supported");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        return -ENXIO;

    case MTFSFM:
        /* I think this is like MTFSF, except that
         * we shouldn't skip the FM. Tricky.
         * Maybe use QCMD_RD_DATA until we get a TP_FIL exception?
         * But then the FM will have been skipped...
         * Maybe use RD_FM, then RD_FM_BCK, but not all
         * drives will support that!
         */
        tpqputs(TPQD_IOCTLS, "MTFSFM not supported");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        return -ENXIO;

    case MTEOM:
        /* This should leave the tape ready for appending
         * another file to the end, such that it would append
         * after the last FM on tape.
         */
        tpqputs(TPQD_IOCTLS, "MTEOM search for End Of recorded Media");
        if ((mode_access == WRITE) && status_bytes_wr)
            return -EACCES;
        if (TP_HAVE_EOD) {
            /* Use faster seeking when possible.
             * This requires the absence of data beyond the EOM.
             * It seems that my drive does not always perform the
             * SEEK_EOD correctly, unless it is preceded by a
             * rewind command.
             */
# if 0
            status_eom_detected = status_eof_detected = NO;
# endif
            stat = do_qic_cmd(QCMD_REWIND, TIM_R);
            if (stat)
                return stat;
            stat = do_qic_cmd(QCMD_SEEK_EOD, TIM_F);
            /* After a successful seek, TP_EOR should be returned */
        } else {
            /* else just seek until the drive returns exception "No Data" */
            stat = 0;
            while ((stat == 0) && (!status_eom_detected)) {
                stat = do_qic_cmd(QCMD_RD_FM, TIM_F);          /***** should use MTFSFM here???? ******/
            }
            if (tperror.exs & TP_NDT)
                return 0;
        }
        return stat;

    case MTERASE:
        tpqputs(TPQD_IOCTLS, "MTERASE -- ERASE TAPE !");
        if ((tperror.exs & TP_ST0) && (tperror.exs & TP_WRP)) {
            tpqputs(TPQD_ALWAYS, "Cartridge is write-protected.");
            return -EACCES;
        } else {
            time_t t = jiffies;

            /* Plain GNU mt(1) 2.2 erases a tape in O_RDONLY. :-( */
            if (mode_access == READ)
                return -EACCES;

            /* FIXME */
            /* give user a few seconds to pull out tape */
            while (jiffies - t < 4 * HZ)
                schedule();
        }

        /* don't bother writing filemark first */
        status_eom_detected = status_eof_detected = NO;
        return do_qic_cmd(QCMD_ERASE, TIM_R);

    case MTRAS1:
        if (TP_HAVE_RAS1) {
            tpqputs(TPQD_IOCTLS, "MTRAS1: non-destructive self test");
            stat = do_qic_cmd(QCMD_SELF_TST1, TIM_R);
            if (stat != 0) {
                tpqputs(TPQD_ALWAYS, "RAS1 failed");
                return stat;
            }
            return (tp_sense(0) == TE_OK) ? 0 : -EIO;    /* get_ext_status3(); */
        }
        tpqputs(TPQD_IOCTLS, "RAS1 not supported");
        return -ENXIO;

    case MTRAS2:
        if (TP_HAVE_RAS2) {
            tpqputs(TPQD_IOCTLS, "MTRAS2: destructive self test");
            stat = do_qic_cmd(QCMD_SELF_TST2, TIM_R);
            if (stat != 0) {
                tpqputs(TPQD_ALWAYS, "RAS2 failed");
                return stat;
            }
            return (tp_sense(0) == TE_OK) ? 0 : -EIO;    /* get_ext_status3(); */
        }
        tpqputs(TPQD_IOCTLS, "RAS2 not supported");
        return -ENXIO;

    case MTSEEK:
        if (TP_HAVE_SEEK && (QIC02_TAPE_IFC == ARCHIVE)) {
            tpqputs(TPQD_IOCTLS, "MTSEEK seeking block");
            if ((mode_access == WRITE) && status_bytes_wr)
                return -EACCES;
            /* NOTE: address (24 bits) is in seek_addr_buf[] */
            return do_qic_cmd(AR_QCMDV_SEEK_BLK, TIM_F);
        } else
            return -ENOTTY;

    default:
        return -ENOTTY;
    }
}                /* do_ioctl_cmd */


/* dma_transfer(): This routine is called for every 512 bytes to be read
 * from/written to the tape controller. Speed is important here!
 * (There must be enough time left for the hd controller!)
 * When other devices use DMA they must ensure they use un-interruptible
 * double byte accesses to the DMA controller. Floppy.c is ok.
 * Must have interrupts disabled when this function is invoked,
 * otherwise, the double-byte transfers to the DMA controller will not
 * be atomic. That could lead to nasty problems when they are interrupted
 * by other DMA interrupt-routines.
 *
 * This routine merely does the least possible to keep
 * the transfers going:
 *    - set the DMA count register for the next 512 bytes
 *    - adjust the DMA address and page registers
 *    - adjust the timeout
 *    - tell the tape controller to start transferring
 * We assume the dma address and mode are, and remain, valid.
 */
static inline void dma_transfer(void)
{
    unsigned long flags;

    if (QIC02_TAPE_IFC == WANGTEK)    /* or EVEREX */
        outb_p(WT_CTL_ONLINE, QIC02_CTL_PORT);    /* back to normal */
    else if (QIC02_TAPE_IFC == ARCHIVE)
        outb_p(0, AR_RESET_DMA_PORT);
    else            /* QIC02_TAPE_IFC == MOUNTAIN */
        outb_p(ctlbits, QIC02_CTL_PORT);


    flags = claim_dma_lock();
    clear_dma_ff(QIC02_TAPE_DMA);
    set_dma_mode(QIC02_TAPE_DMA, dma_mode);
    set_dma_addr(QIC02_TAPE_DMA,
             virt_to_bus(buffaddr) + dma_bytes_done);
    set_dma_count(QIC02_TAPE_DMA, TAPE_BLKSIZE);

    /* start tape DMA controller */
    if (QIC02_TAPE_IFC == WANGTEK)    /* or EVEREX */
        outb_p(WT_CTL_DMA | WT_CTL_ONLINE, QIC02_CTL_PORT);    /* trigger DMA transfer */

    else if (QIC02_TAPE_IFC == ARCHIVE) {
        outb_p(AR_CTL_IEN | AR_CTL_DNIEN, QIC02_CTL_PORT);    /* enable interrupts again */
        outb_p(0, AR_START_DMA_PORT);    /* start DMA transfer */
        /* In dma_end() AR_RESET_DMA_PORT is written too. */

    } else {        /* QIC02_TAPE_IFC == MOUNTAIN */

        inb(MTN_R_DESELECT_DMA_PORT);
        outb_p(ctlbits | (MTN_CTL_EXC_IEN | MTN_CTL_DNIEN),
               QIC02_CTL_PORT);
        outb_p(0, MTN_W_SELECT_DMA_PORT);    /* start DMA transfer */
        if (dma_mode == DMA_MODE_WRITE)
            outb_p(0, MTN_W_DMA_WRITE_PORT);    /* start DMA transfer */
    }

    /* start computer DMA controller */
    enable_dma(QIC02_TAPE_DMA);

    release_dma_lock(flags);

    /* block transfer should start now, jumping to the 
     * interrupt routine when done or an exception was detected.
     */
}                /* dma_transfer */


/* start_dma() sets a DMA transfer up between the tape controller and
 * the kernel qic02_tape_buf buffer.
 * Normally bytes_todo==dma_bytes_done at the end of a DMA transfer. If not,
 * a filemark was read, or an attempt to write beyond the End Of Tape 
 * was made. [Or some other bad thing happened.]
 * Must do a sense() before returning error.
 */
static int start_dma(short mode, unsigned long bytes_todo)
/* assume 'bytes_todo'>0 */
{
    int stat;
    unsigned long flags;

    tpqputs(TPQD_DEBUG, "start_dma() enter");
    TPQDEB( {printk(TPQIC02_NAME ": doing_read==%d, doing_write==%d\n",
              doing_read, doing_write);})

        dma_bytes_done = 0;
    dma_bytes_todo = bytes_todo;
    status_error = NO;
    /* dma_mode!=0 indicates that the dma controller is in use */
    dma_mode = (mode == WRITE) ? DMA_MODE_WRITE : DMA_MODE_READ;

    /* Only give READ/WRITE DATA command to tape drive if we haven't
     * done that already. Otherwise the drive will rewind to the beginning
     * of the current file on tape. Any QIC command given other than
     * R/W FM will break the read/write transfer cycle.
     * do_qic_cmd() will terminate doing_{read,write}
     */
    if ((doing_read == NO) && (doing_write == NO)) {
        /* First, we have to clear the status -- maybe remove TP_FIL???
         */

#if 0
        /* Next dummy get status is to make sure CNI is valid,
           since we're only just starting a read/write it doesn't
           matter some exceptions are cleared by reading the status;
           we're only interested in CNI and WRP. -Eddy */
        get_status(&tperror);
#else
        /* TP_CNI should now be handled in open(). -Hennus */
#endif

        stat =
            tp_sense(((mode ==
                   WRITE) ? 0 : TP_WRP) | TP_BOM | TP_FIL);
        if (stat != TE_OK)
            return stat;

#if OBSOLETE
        /************* not needed iff rd_status() would wait for ready!!!!!! **********/
        if (wait_for_ready(TIM_S) != TE_OK) {    /*** not sure this is needed ***/
            tpqputs(TPQD_ALWAYS,
                "wait_for_ready failed in start_dma");
            return -EIO;
        }
#endif

        if (QIC02_TAPE_IFC == MOUNTAIN) {
            /* Set control bits to select ONLINE during command */
            ctlbits |= MTN_QIC02_CTL_ONLINE;
        }

        /* Tell the controller the data direction */

        /* r/w, timeout medium, check exceptions, sets status_cmd_pending. */
        stat = send_qic02_cmd((mode == WRITE) 
                    ? QCMD_WRT_DATA : QCMD_RD_DATA, TIM_M, 0);
        if (stat != TE_OK) {
            printk(TPQIC02_NAME ": start_dma: init %s failed\n",
                   (mode == WRITE) ? "write" : "read");
            (void) tp_sense(0);
            return stat;
        }

        /* Do this last, because sense() will clear the doing_{read,write}
         * flags, causing trouble next time around.
         */
        if (wait_for_ready(TIM_M) != TE_OK)
            return -EIO;
        switch (mode) {
        case READ:
            doing_read = YES;
            break;
        case WRITE:
            doing_write = YES;
            break;
        default:
            printk(TPQIC02_NAME
                   ": requested unknown mode %d\n", mode);
            panic(TPQIC02_NAME
                  ": invalid mode in start_dma()");
        }

    } else if (is_exception()) {
        /* This is for Archive drives, to handle reads with 0 bytes
         * left for the last read request.
         *
         * ******** this also affects EOF/EOT handling! ************
         */
        tpqputs(TPQD_ALWAYS,
            "detected exception in start_dma() while transfer in progress");
        status_error = YES;
        return TE_END;
    }


    status_expect_int = YES;

    /* This assumes tape is already positioned, but these
     * semi-'intelligent' drives are unpredictable...
     */
    TIMERON(TIM_M * 2);

    /* initiate first data block read from/write to the tape controller */

    save_flags(flags);
    cli();
    dma_transfer();
    restore_flags(flags);

    TPQPUTS("start_dma() end");
    return TE_OK;
}                /* start_dma */


/* This cleans up after the dma transfer has completed
 * (or failed). If an exception occurred, a sense()
 * must be done. If the exception was caused by a FM,
 * sense() will set `status_eof_detected' and
 * `status_eom_detected', as required.
 */
static void end_dma(unsigned long *bytes_done)
{
    int stat = TE_OK;
    unsigned long flags;

    TIMEROFF;

    TPQPUTS("end_dma() enter");

    flags = claim_dma_lock();

    disable_dma(QIC02_TAPE_DMA);
    clear_dma_ff(QIC02_TAPE_DMA);

    release_dma_lock(flags);

    if (QIC02_TAPE_IFC == WANGTEK)    /* or EVEREX */
        outb_p(WT_CTL_ONLINE, QIC02_CTL_PORT);    /* back to normal */
    else if (QIC02_TAPE_IFC == ARCHIVE)
        outb_p(0, AR_RESET_DMA_PORT);
    else {            /* QIC02_TAPE_IFC == MOUNTAIN */

        /* Clear control bits, de-select ONLINE during tp_sense */
        ctlbits &= ~MTN_QIC02_CTL_ONLINE;
    }

    stat = wait_for_ready(TIM_M);
    if (status_error || (stat != TE_OK)) {
        tpqputs(TPQD_DMAX, "DMA transfer exception");
        stat = tp_sense((dma_mode == READ) ? TP_WRP : 0);
        /* no return here -- got to clean up first! */
    } else {        /* if (QIC02_TAPE_IFC == MOUNTAIN) */

        outb_p(ctlbits, QIC02_CTL_PORT);
    }

    if (QIC02_TAPE_IFC == MOUNTAIN)
        inb(MTN_R_DESELECT_DMA_PORT);

    /* take the tape controller offline */

    /* finish off DMA stuff */


    dma_mode = 0;
    /* Note: The drive is left on-line, ready for the next
     * data transfer.
     * If the next command to the drive does not continue
     * the pending cycle, it must do 2 sense()s first.
     */

    *bytes_done = dma_bytes_done;
    status_expect_int = NO;
    ioctl_status.mt_blkno += (dma_bytes_done / TAPE_BLKSIZE);

    TPQPUTS("end_dma() exit");
    /*** could return stat here ***/
}                /* end_dma */

/*********** Below are the (public) OS-interface procedures ***********/


/* qic02_tape_times_out() is called when a DMA transfer doesn't complete
 * quickly enough. Usually this means there is something seriously wrong
 * with the hardware/software, but it could just be that the controller
 * has decided to do a long rewind, just when I didn't expect it.
 * Just try again.
 */
static void qic02_tape_times_out(unsigned long dummy)
{
    printk("time-out in %s driver\n", TPQIC02_NAME);
    if ((status_cmd_pending > 0) || dma_mode) {
        /* takes tooo long, shut it down */
        status_dead = YES;
        status_cmd_pending = 0;
        status_timer_on = NO;
        status_expect_int = NO;
        status_error = YES;
        if (dma_mode) {
            dma_mode = 0;    /* signal end to read/write routine */
            wake_up(&qic02_tape_transfer);
        }
    }
}                /* qic02_tape_times_out */

/*
 * Interrupt handling:
 *
 * 1) Interrupt is generated iff at the end of 
 *    a 512-DMA-block transfer.
 * 2) EXCEPTION is not raised unless something 
 *    is wrong or EOT/FM is detected.
 * 3) FM EXCEPTION is set *after* the last byte has
 *    been transferred by DMA. By the time the interrupt
 *    is handled, the EXCEPTION may already be set.
 *
 * So,
 * 1) On EXCEPTION, assume data has been transferred, so
 *    continue as usual, but set a flag to indicate the
 *    exception was detected.
 *    Do a sense status when the flag is found set.
 * 2) Do not attempt to continue a transfer after an exception.
 *    [??? What about marginal blocks???????]
 */


/* qic02_tape_interrupt() is called when the tape controller completes 
 * a DMA transfer.
 * We are not allowed to sleep here! 
 *
 * Check if the transfer was successful, check if we need to transfer
 * more. If the buffer contains enough data/is empty enough, signal the
 * read/write() thread to copy to/from user space.
 * When we are finished, set flags to indicate end, disable timer.
 * NOTE: This *must* be fast! 
 */
static void qic02_tape_interrupt(int irq, void *dev_id,
                 struct pt_regs *regs)
{
    int stat, r, i;
    unsigned long flags;

    TIMEROFF;

    if (status_expect_int) {
#ifdef WANT_EXTRA_FULL_DEBUGGING
        if (TP_DIAGS(current_tape_dev))
            printk("@");
#endif
        stat = inb(QIC02_STAT_PORT);    /* Knock, knock */
        if (QIC02_TAPE_IFC == ARCHIVE) {    /* "Who's there?" */
            if (((stat & (AR_STAT_DMADONE)) == 0) &&
                ((stat & (QIC02_STAT_EXCEPTION)) != 0)) {
                TIMERCONT;
                return;    /* "Linux with IRQ sharing" */
            }
        }

        if ((stat & QIC02_STAT_EXCEPTION) == 0) {    /* exception occurred */
            /* Possible causes for an exception during a transfer:
             *      - during a write-cycle: end of tape (EW) hole detected.
             *      - during a read-cycle: filemark or EOD detected.
             *      - something went wrong
             * So don't continue with the next block.
             */
            tpqputs(TPQD_ALWAYS,
                "isr: exception on tape controller");
            printk("      status %02x\n", stat);
            status_error = TE_EX;

            dma_bytes_done += TAPE_BLKSIZE;

            dma_mode = 0;    /* wake up rw() */
            status_expect_int = NO;
            wake_up(&qic02_tape_transfer);
            return;
        }
        /* return if tape controller not ready, or
         * if dma channel hasn't finished last byte yet.
         */
        r = 0;

        /* Skip next ready check for Archive controller because
         * it may be busy reading ahead. Weird. --hhb
         */
        if (QIC02_TAPE_IFC == WANGTEK)    /* I think this is a drive-dependency, not IFC -- hhb */
            if (stat & QIC02_STAT_READY) {    /* not ready */
                tpqputs(TPQD_ALWAYS,
                    "isr: ? Tape controller not ready");
                r = 1;
            }

        flags = claim_dma_lock();

        if ((i = get_dma_residue(QIC02_TAPE_DMA)) != 0) {
            printk(TPQIC02_NAME ": dma_residue == %x !!!\n",
                   i);
            r = 1;    /* big trouble, but can't do much about it... */
        }

        release_dma_lock(flags);

        if (r)
            return;

        /* finish DMA cycle */

        /* no errors detected, continue */
        dma_bytes_done += TAPE_BLKSIZE;
        if (dma_bytes_done >= dma_bytes_todo) {
            /* finished! Wakeup rw() */
            dma_mode = 0;
            status_expect_int = NO;
            TPQPUTS("isr: dma_bytes_done");
            wake_up(&qic02_tape_transfer);
        } else {
            /* start next transfer, account for track-switching time */
            mod_timer(&tp_timer, jiffies + 6 * HZ);
            dma_transfer();
        }
    } else {
        printk(TPQIC02_NAME ": Unexpected interrupt, stat == %x\n",
               inb(QIC02_STAT_PORT));
    }
}                /* qic02_tape_interrupt */


/* read/write routines:
 * This code copies between a kernel buffer and a user buffer. The 
 * actual data transfer is done using DMA and interrupts. Time-outs
 * are also used.
 *
 * When a filemark is read, we return '0 bytes read' and continue with the
 * next file after that.
 * When EOM is read, we return '0 bytes read' twice.
 * When the EOT marker is detected on writes, '0 bytes read' should be
 * returned twice. If user program does a MTNOP after that, 2 additional
 * blocks may be written.    ------- FIXME: Implement this correctly  *************************************************
 *
 * Only read/writes in multiples of 512 bytes are accepted.
 * When no bytes are available, we sleep() until they are. The controller will
 * generate an interrupt, and we (should) get a wake_up() call.
 *
 * Simple buffering is used. User program should ensure that a large enough
 * buffer is used. Usually the drive does some buffering as well (something
 * like 4k or so).
 *
 * Scott S. Bertilson suggested to continue filling the user buffer, rather
 * than waste time on a context switch, when the kernel buffer fills up.
 */

/*
 * Problem: tar(1) doesn't always read the entire file. Sometimes the entire file
 * has been read, but the EOF token is never returned to tar(1), simply because
 * tar(1) knows it has already read all of the data it needs. So we must use
 * open/release to reset the `reported_read_eof' flag. If we don't, the next read
 * request would return the EOF flag for the previous file.
 */

static ssize_t qic02_tape_read(struct file *filp, char *buf, size_t count,
                   loff_t * ppos)
{
    int err;
    kdev_t dev = filp->f_dentry->d_inode->i_rdev;
    unsigned short flags = filp->f_flags;
    unsigned long bytes_todo, bytes_done, total_bytes_done = 0;
    int stat;

    if (status_zombie == YES) {
        tpqputs(TPQD_ALWAYS, "configs not set");
        return -ENXIO;
    }

    if (TP_DIAGS(current_tape_dev))
        /* can't print a ``long long'' (for filp->f_pos), so chop it */
        printk(TPQIC02_NAME
               ": request READ, minor=%x, buf=%p, count=%lx"
               ", pos=%lx, flags=%x\n", MINOR(dev), buf,
               (long) count, (unsigned long) filp->f_pos, flags);

    if (count % TAPE_BLKSIZE) {    /* Only allow mod 512 bytes at a time. */
        tpqputs(TPQD_BLKSZ, "Wrong block size");
        return -EINVAL;
    }

    /* Just assume everything is ok. Controller will scream if not. */

    if (status_bytes_wr) {    /* Once written, no more reads, 'till after WFM. */
        return -EACCES;
    }

    /* This is rather ugly because it has to implement a finite state
     * machine in order to handle the EOF situations properly.
     */
    while ((signed) count >= 0) {
        bytes_done = 0;
        /* see how much fits in the kernel buffer */
        bytes_todo = TPQBUF_SIZE;
        if (bytes_todo > count) {
            bytes_todo = count;
        }

        /* Must ensure that user program sees exactly one EOF token (==0) */
        if (return_read_eof == YES) {
            if (TPQDBG(DEBUG)) {
                printk
                    ("read: return_read_eof==%d, reported_read_eof==%d, total_bytes_done==%lu\n",
                     return_read_eof, reported_read_eof,
                     total_bytes_done);
            }

            if (reported_read_eof == NO) {
                /* have not yet returned EOF to user program */
                if (total_bytes_done > 0) {
                    return total_bytes_done;    /* next time return EOF */
                } else {
                    reported_read_eof = YES;    /* move on next time */
                    return 0;    /* return EOF */
                }
            } else {
                /* Application program has already received EOF
                 * (above), now continue with next file on tape,
                 * if possible.
                 * When the FM is reached, EXCEPTION is set,
                 * causing a sense(). Subsequent read/writes will
                 * continue after the FM.
                 */
        /*********** ?????????? this should check for (EOD|NDT), not EOM, 'cause we can read past EW: ************/
                if (status_eom_detected) {
                    /* If EOM, nothing left to read, so keep returning EOFs.
                     *** should probably set some flag to avoid clearing
                     *** status_eom_detected through ioctls or something
                     */
                    return 0;
                } else {
                    /* just eof, there may be more files ahead... */
                    return_read_eof = NO;
                    reported_read_eof = NO;
                    status_eof_detected = NO;    /* reset this too */
                    /*fall through */
                }
            }
        }

    /*****************************/
        if (bytes_todo == 0) {
            return total_bytes_done;
        }

        if (bytes_todo > 0) {
            /* start reading data */
            if (is_exception()) {
/****************************************/
                tpqputs(TPQD_DMAX,
                    "is_exception() before start_dma()!");
            }

/******************************************************************
 ***** if start_dma() fails because the head is positioned 0 bytes
 ***** before the FM, (causing EXCEPTION to be set) return_read_eof should
 ***** be set to YES, and we should return total_bytes_done, rather than -ENXIO.
 ***** The app should recognize this as an EOF condition.
 ***************************************************************************/
            stat = start_dma(READ, bytes_todo);
            if (stat == TE_OK) {
                /* Wait for transfer to complete, interrupt should wake us */
                while (dma_mode != 0) {
                    sleep_on(&qic02_tape_transfer);
                }
                if (status_error) {
                    return_read_eof = YES;
                }

            } else if (stat != TE_END) {
                /* should do sense() on error here */
#if 0
                return -ENXIO;
#else
                printk("Trouble: stat==%02x\n", stat);
                return_read_eof = YES;
        /*************** check EOF/EOT handling!!!!!! **/
#endif
            }
            end_dma(&bytes_done);
            if (bytes_done > bytes_todo) {
                tpqputs(TPQD_ALWAYS,
                    "read: Oops, read more bytes than requested");
                return -EIO;
            }
            /* copy buffer to user-space in one go */
            if (bytes_done > 0) {
                err =
                    copy_to_user(buf, buffaddr,
                         bytes_done);
                if (err) {
                    return -EFAULT;
                }
            }
#if 1
            /* Checks Ton's patch below */
            if ((return_read_eof == NO)
                && (status_eof_detected == YES)) {
                printk(TPQIC02_NAME
                       ": read(): return_read_eof=%d, status_eof_detected=YES. return_read_eof:=YES\n",
                       return_read_eof);
            }
#endif
            if ((bytes_todo != bytes_done)
                || (status_eof_detected == YES)) {
                /* EOF or EOM detected. return EOF next time. */
                return_read_eof = YES;
            }

        }
        /* else: ignore read request for 0 bytes */
        if (bytes_done > 0) {
            status_bytes_rd = YES;
            buf += bytes_done;
            *ppos += bytes_done;
            total_bytes_done += bytes_done;
            count -= bytes_done;
        }
    }
    tpqputs(TPQD_ALWAYS, "read request for <0 bytes");
    return -EINVAL;
}                /* qic02_tape_read */



/* The drive detects near-EOT by means of the holes in the tape.
 * When the holes are detected, there is some space left. The drive
 * reports this as a TP_EOM exception. After clearing the exception,
 * the drive should accept two extra blocks.
 *
 * It seems there are some archiver programs that would like to use the
 * extra space for writing a continuation marker. The driver should return
 * end-of-file to the user program on writes, when the holes are detected.
 * If the user-program wants to use the extra space, it should use the
 * MTNOP ioctl() to get the generic status register and may then continue
 * writing (max 1kB).    ----------- doesn't work yet...............
 *
 * EOF behaviour on writes:
 * If there is enough room, write all of the data.
 * If there is insufficient room, write as much as will fit and
 * return the amount written. If the requested amount differs from the
 * written amount, the application program should recognize that as the
 * end of file. Subsequent writes will return -ENOSPC.
 * Unless the minor bits specify a rewind-on-close, the tape will not
 * be rewound when it is full. The user-program should do that, if desired.
 * If the driver were to do that automatically, a user-program could be 
 * confused about the EOT/BOT condition after re-opening the tape device.
 *
 * Multiple volume support: Tar closes the tape device before prompting for
 * the next tape. The user may then insert a new tape and tar will open the
 * tape device again. The driver will detect an exception status in (No Cartridge)
 * and force a rewind. After that tar may continue writing.
 */
static ssize_t qic02_tape_write(struct file *filp, const char *buf,
                size_t count, loff_t * ppos)
{
    int err;
    kdev_t dev = filp->f_dentry->d_inode->i_rdev;
    unsigned short flags = filp->f_flags;
    unsigned long bytes_todo, bytes_done, total_bytes_done = 0;

    if (status_zombie == YES) {
        tpqputs(TPQD_ALWAYS, "configs not set");
        return -ENXIO;
    }

    if (TP_DIAGS(current_tape_dev)) {
        /* can't print a ``long long'' (for filp->f_pos), so chop it */
        printk(TPQIC02_NAME ": request WRITE, minor=%x, buf=%p"
               ", count=%lx, pos=%lx, flags=%x\n",
               MINOR(dev), buf,
               (long) count, (unsigned long) filp->f_pos, flags);
    }

    if (count % TAPE_BLKSIZE) {    /* only allow mod 512 bytes at a time */
        tpqputs(TPQD_BLKSZ, "Wrong block size");
        return -EINVAL;
    }

    if (mode_access == READ) {
        tpqputs(TPQD_ALWAYS, "Not in write mode");
        return -EACCES;
    }

    /* open() does a sense() and we can assume the tape isn't changed
     * between open() and release(), so the tperror.exs bits will still
     * be valid.
     */
    if ((tperror.exs & TP_ST0) && (tperror.exs & TP_WRP)) {
        tpqputs(TPQD_ALWAYS, "Cartridge is write-protected.");
        return -EACCES;    /* don't even try when write protected */
    }

    if (doing_read == YES) {
        terminate_read(0);
    }

    while ((signed) count >= 0) {
        /* see how much fits in the kernel buffer */
        bytes_done = 0;
        bytes_todo = TPQBUF_SIZE;
        if (bytes_todo > count) {
            bytes_todo = count;
        }

        if (return_write_eof == YES) {
            /* return_write_eof should be reset on reverse tape movements. */

            if (reported_write_eof == NO) {
                if (bytes_todo > 0) {
                    tpqputs(TPQD_ALWAYS,
                        "partial write");
                    /* partial write signals EOF to user program */
                }
                reported_write_eof = YES;
                return total_bytes_done;
            } else {
                return -ENOSPC;    /* return error */
            }
        }

        /* Quit when done. */
        if (bytes_todo == 0) {
            return total_bytes_done;
        }

        /* copy from user to DMA buffer and initiate transfer. */
        if (bytes_todo > 0) {
            err = copy_from_user(buffaddr, buf, bytes_todo);
            if (err) {
                return -EFAULT;
            }

/****************** similar problem with read() at FM could happen here at EOT.
 ******************/

/***** if at EOT, 0 bytes can be written. start_dma() will
 ***** fail and write() will return ENXIO error
 *****/
            if (start_dma(WRITE, bytes_todo) != TE_OK) {
                tpqputs(TPQD_ALWAYS,
                    "write: start_dma() failed");
                /* should do sense() on error here */
                return -ENXIO;
                /*********** FIXTHIS **************/
            }

            /* Wait for write to complete, interrupt should wake us. */
            while ((status_error == 0) && (dma_mode != 0)) {
                sleep_on(&qic02_tape_transfer);
            }

            end_dma(&bytes_done);
            if (bytes_done > bytes_todo) {
                tpqputs(TPQD_ALWAYS,
                    "write: Oops, wrote more bytes than requested");
                return -EIO;
            }
            /* If the dma-transfer was aborted because of an exception,
             * status_error will have been set in the interrupt handler.
             * Then end_dma() will do a sense().
             * If the exception was EXC_EOM, the EW-hole was encountered
             * and two more blocks could be written. For the time being we'll
             * just consider this to be the EOT.
             * Otherwise, something Bad happened, such as the maximum number
             * of block-rewrites was exceeded. [e.g. A very bad spot on tape was
             * encountered. Normally short dropouts are compensated for by
             * rewriting the block in error, up to 16 times. I'm not sure
             * QIC-24 drives can do this.]
             */
            if (status_error) {
                if (status_eom_detected == YES) {
                    tpqputs(TPQD_ALWAYS,
                        "write: EW detected");
                    return_write_eof = YES;
                } else {
                    /* probably EXC_RWA */
                    tpqputs(TPQD_ALWAYS,
                        "write: dma: error in writing");
                    return -EIO;
                }
            }
            if (bytes_todo != bytes_done) {
                /* EOF or EOM detected. return EOT next time. */
                return_write_eof = YES;
            }
        }
        /* else: ignore write request for 0 bytes. */

        if (bytes_done > 0) {
            status_bytes_wr = YES;
            buf += bytes_done;
            *ppos += bytes_done;
            total_bytes_done += bytes_done;
            count -= bytes_done;
        }
    }

    tpqputs(TPQD_ALWAYS, "write request for <0 bytes");
    if (TPQDBG(DEBUG)) {
        printk(TPQIC02_NAME ": status_bytes_wr %x, buf %p"
               ", total_bytes_done %lx, count %lx\n",
               status_bytes_wr, buf, total_bytes_done,
               (long) count);
    }
    return -EINVAL;
}                /* qic02_tape_write */



/* qic02_tape_open()
 * We allow the device to be opened, even if it is marked 'dead' because
 * we want to be able to reset the tape device without rebooting.
 * Only one open tape file at a time, except when minor=255.
 * Minor 255 is only allowed for resetting and always returns <0.
 * 
 * The density command is only allowed when TP_BOM is set. Thus, remember
 * the most recently used minor bits. When they are different from the
 * remembered values, rewind the tape and set the required density.
 * Don't rewind if the minor bits specify density 0.
 */

static int qic02_tape_open(struct inode *inode, struct file *filp)
{
    static int qic02_tape_open_no_use_count(struct inode *,
                        struct file *);
    int open_error;

    open_error = qic02_tape_open_no_use_count(inode, filp);
    return open_error;
}

static int qic02_tape_open_no_use_count(struct inode *inode,
                    struct file *filp)
{
    kdev_t dev = inode->i_rdev;
    unsigned short flags = filp->f_flags;
    unsigned short dens = 0;
    int s;


    if (TP_DIAGS(dev)) {
        printk("qic02_tape_open: dev=%s, flags=%x     ",
               kdevname(dev), flags);
    }

    if (MINOR(dev) == 255) {    /* special case for resetting */
        if (capable(CAP_SYS_ADMIN)) {
            return (tape_reset(1) == TE_OK) ? -EAGAIN : -ENXIO;
        } else {
            return -EPERM;
        }
    }

    if (status_dead == YES) {
        /* Allow `mt reset' ioctl() even when already open()ed. */
        return 0;
    }

    /* Only one at a time from here on... */
    if (file_count(filp) > 1) {    /* filp->f_count==1 for the first open() */
        return -EBUSY;
    }

    if (status_zombie == YES) {
        /* no irq/dma/port stuff allocated yet, no reset done
         * yet, so return until MTSETCONFIG has been done.
         */
        return 0;
    }

    status_bytes_rd = NO;
    status_bytes_wr = NO;

    return_read_eof = NO;    /********????????????????*****/
    return_write_eof = (status_eot_detected) ? YES : NO;

    /* Clear this in case user app close()d before reading EOF token */
    status_eof_detected = NO;

    reported_read_eof = NO;
    reported_write_eof = NO;


    switch (flags & O_ACCMODE) {
    case O_RDONLY:
        mode_access = READ;
        break;
    case O_WRONLY:        /* Fallthru... Strictly speaking this is not correct... */
    case O_RDWR:        /* Reads are allowed as long as nothing is written */
        mode_access = WRITE;
        break;
    }

    /* This is to avoid tape-changed problems (TP_CNI exception).
     *
     * Since removing the cartridge will not raise an exception,
     * we always do a tp_sense() to make sure we have the proper
     * CNI status, the 2150L may need an additional sense.... - Eddy
     */
    s = tp_sense(TP_WRP | TP_EOM | TP_BOM | TP_CNI | TP_EOR);

    if (s == TE_OK) {
        /* Try to clear cartridge-changed status for Archive-2150L */
        if ((tperror.exs & TP_ST0) && (tperror.exs & TP_CNI)) {
            s = tp_sense(TP_WRP | TP_EOM | TP_BOM | TP_CNI |
                     TP_EOR);
        }
    }

    if (s != TE_OK) {
        tpqputs(TPQD_ALWAYS, "open: sense() failed");
        return -EIO;
    }

    /* exception bits should be up-to-date now, so check for
     * tape presence and exit if absent.
     * Even `mt stat' will fail without a tape.
     */
    if ((tperror.exs & TP_ST0) && (tperror.exs & TP_CNI)) {
        tpqputs(TPQD_ALWAYS, "No tape present.");
        return -EIO;
    }

    /* At this point we can assume that a tape is present and
     * that it will remain present until release() is called.
     */

    /* not allowed to do QCMD_DENS_* unless tape is rewound */
    if ((TP_DENS(dev) != 0)
        && (TP_DENS(current_tape_dev) != TP_DENS(dev))) {
        /* force rewind if minor bits have changed,
         * i.e. user wants to use tape in different format.
         * [assuming single drive operation]
         */
        if (TP_HAVE_DENS) {
            tpqputs(TPQD_REWIND,
                "Density minor bits have changed. Forcing rewind.");
            need_rewind = YES;
        }
    } else {
        /* density bits still the same, but TP_DIAGS bit 
         * may have changed.
         */
        current_tape_dev = dev;
    }

    if (need_rewind == YES) {
/***************** CHECK THIS!!!!!!!! **********/
        s = do_qic_cmd(QCMD_REWIND, TIM_R);
        if (s != 0) {
            tpqputs(TPQD_ALWAYS, "open: rewind failed");
            return -EIO;
        }
    }


/* Note: After a reset command, the controller will rewind the tape
 *     just before performing any tape movement operation! ************ SO SET need_rewind flag!!!!!
 */
    if (status_dead == YES) {
        tpqputs(TPQD_ALWAYS, "open: tape dead, attempting reset");
        if (tape_reset(1) != TE_OK) {
            return -ENXIO;
        } else {
            status_dead = NO;
            if (tp_sense(~(TP_ST1 | TP_ILL)) != TE_OK) {
                tpqputs(TPQD_ALWAYS,
                    "open: tp_sense() failed\n");
                status_dead = YES;    /* try reset next time */
                return -EIO;
            }
        }
    }

    /* things should be ok, once we get here */


    /* set density: only allowed when TP_BOM status bit is set,
     * so we must have done a rewind by now. If not, just skip over.
     * Only give set density command when minor bits have changed.
     */
    if (TP_DENS(current_tape_dev) == TP_DENS(dev)) {
        return 0;
    }

    current_tape_dev = dev;
    need_rewind = NO;
    if (TP_HAVE_DENS) {
        dens = TP_DENS(dev);
    }

    if (dens < sizeof(format_names) / sizeof(char *)) {
        printk(TPQIC02_NAME ": format: %s%s\n",
               (dens != 0) ? "QIC-" : "", format_names[dens]);
    } else {
        tpqputs(TPQD_REWIND, "Wait for retensioning...");
    }

    switch (TP_DENS(dev)) {
    case 0:        /* Minor 0 is for drives without set-density support */
        s = 0;
        break;
    case 1:
        s = do_qic_cmd(QCMD_DENS_11, TIM_S);
        break;
    case 2:
        s = do_qic_cmd(QCMD_DENS_24, TIM_S);
        break;
    case 3:
        s = do_qic_cmd(QCMD_DENS_120, TIM_S);
        break;
    case 4:
        s = do_qic_cmd(QCMD_DENS_150, TIM_S);
        break;
    case 5:
        s = do_qic_cmd(QCMD_DENS_300, TIM_S);
        break;
    case 6:
        s = do_qic_cmd(QCMD_DENS_600, TIM_S);
        break;
    default:        /* otherwise do a retension before anything else */
        s = do_qic_cmd(QCMD_RETEN, TIM_R);
    }
    if (s != 0) {
        status_dead = YES;    /* force reset */
        current_tape_dev = 0;    /* earlier 0xff80 */
        return -EIO;
    }

    return 0;
}                /* qic02_tape_open */


static int qic02_tape_release(struct inode *inode, struct file *filp)
{
    kdev_t dev = inode->i_rdev;

    lock_kernel();
    if (TP_DIAGS(dev)) {
        printk("qic02_tape_release: dev=%s\n", kdevname(dev));
    }

    if (status_zombie == NO) {    /* don't rewind in zombie mode */
        /* Terminate any pending write cycle. Terminating the read-cycle
         * is delayed until it is required to do so for a new command.
         */
        terminate_write(-1);

        if (status_dead == YES) {
            tpqputs(TPQD_ALWAYS, "release: device dead!?");
        }

        /* Rewind only if minor number requires it AND 
         * read/writes have been done. ************* IS THIS CORRECT??????????
         */
        if ((TP_REWCLOSE(dev))
            && (status_bytes_rd | status_bytes_wr)) {
            tpqputs(TPQD_REWIND, "release: Doing rewind...");
            (void) do_qic_cmd(QCMD_REWIND, TIM_R);
        }
    }
    unlock_kernel();
    return 0;
}                /* qic02_tape_release */


#ifdef CONFIG_QIC02_DYNCONF
/* Set masks etc. based on the interface card type. */
static int update_ifc_masks(int ifc)
{
    QIC02_TAPE_IFC = ifc;

    if ((QIC02_TAPE_IFC == WANGTEK) || (QIC02_TAPE_IFC == EVEREX)) {
        QIC02_STAT_PORT = QIC02_TAPE_PORT;
        QIC02_CTL_PORT = QIC02_TAPE_PORT;
        QIC02_CMD_PORT = QIC02_TAPE_PORT + 1;
        QIC02_DATA_PORT = QIC02_TAPE_PORT + 1;
        QIC02_STAT_READY = WT_QIC02_STAT_READY;
        QIC02_STAT_EXCEPTION = WT_QIC02_STAT_EXCEPTION;
        QIC02_STAT_MASK = WT_QIC02_STAT_MASK;

        QIC02_STAT_RESETMASK = WT_QIC02_STAT_RESETMASK;
        QIC02_STAT_RESETVAL = WT_QIC02_STAT_RESETVAL;

        QIC02_CTL_RESET = WT_QIC02_CTL_RESET;
        QIC02_CTL_REQUEST = WT_QIC02_CTL_REQUEST;

        if (QIC02_TAPE_DMA == 3) {
            WT_CTL_DMA = WT_CTL_DMA3;
        } else if (QIC02_TAPE_DMA == 1) {
            WT_CTL_DMA = WT_CTL_DMA1;
        } else {
            tpqputs(TPQD_ALWAYS,
                "Unsupported or incorrect DMA channel");
            return -EIO;
        }

        if (QIC02_TAPE_IFC == EVEREX) {
            /* Everex is a special case for Wangtek (actually
             * it's the other way 'round, but I saw Wangtek first)
             */
            if (QIC02_TAPE_DMA == 3) {
                WT_CTL_DMA = WT_CTL_DMA1;
            }

            /* Fixup the kernel copy of the IFC type to that
             * we don't have to distinguish between Wangtek and
             * and Everex at runtime.
             */
            QIC02_TAPE_IFC = WANGTEK;
        }
    } else if (QIC02_TAPE_IFC == ARCHIVE) {
        QIC02_STAT_PORT = QIC02_TAPE_PORT + 1;
        QIC02_CTL_PORT = QIC02_TAPE_PORT + 1;
        QIC02_CMD_PORT = QIC02_TAPE_PORT;
        QIC02_DATA_PORT = QIC02_TAPE_PORT;
        QIC02_STAT_READY = AR_QIC02_STAT_READY;
        QIC02_STAT_EXCEPTION = AR_QIC02_STAT_EXCEPTION;
        QIC02_STAT_MASK = AR_QIC02_STAT_MASK;

        QIC02_STAT_RESETMASK = AR_QIC02_STAT_RESETMASK;
        QIC02_STAT_RESETVAL = AR_QIC02_STAT_RESETVAL;

        QIC02_CTL_RESET = AR_QIC02_CTL_RESET;
        QIC02_CTL_REQUEST = AR_QIC02_CTL_REQUEST;

        if (QIC02_TAPE_DMA > 3) {
            tpqputs(TPQD_ALWAYS,
                "Unsupported or incorrect DMA channel");
            return -EIO;
        }
    } else if (QIC02_TAPE_IFC == MOUNTAIN) {
        QIC02_STAT_PORT = QIC02_TAPE_PORT + 1;
        QIC02_CTL_PORT = QIC02_TAPE_PORT + 1;
        QIC02_CMD_PORT = QIC02_TAPE_PORT;
        QIC02_DATA_PORT = QIC02_TAPE_PORT;

        QIC02_STAT_READY = MTN_QIC02_STAT_READY;
        QIC02_STAT_EXCEPTION = MTN_QIC02_STAT_EXCEPTION;
        QIC02_STAT_MASK = MTN_QIC02_STAT_MASK;

        QIC02_STAT_RESETMASK = MTN_QIC02_STAT_RESETMASK;
        QIC02_STAT_RESETVAL = MTN_QIC02_STAT_RESETVAL;

        QIC02_CTL_RESET = MTN_QIC02_CTL_RESET;
        QIC02_CTL_REQUEST = MTN_QIC02_CTL_REQUEST;

        if (QIC02_TAPE_DMA > 3) {
            tpqputs(TPQD_ALWAYS,
                "Unsupported or incorrect DMA channel");
            return -EIO;
        }
    } else {
        tpqputs(TPQD_ALWAYS, "Invalid interface type");
        return -ENXIO;
    }
    return qic02_get_resources();
}                /* update_ifc_masks */
#endif


/* ioctl allows user programs to rewind the tape and stuff like that */
static int qic02_tape_ioctl(struct inode *inode, struct file *filp,
                unsigned int iocmd, unsigned long ioarg)
{
    int error;
    int dev_maj = MAJOR(inode->i_rdev);
    int c;
    struct mtop operation;
    unsigned char blk_addr[6];
    struct mtpos ioctl_tell;


    if (TP_DIAGS(current_tape_dev)) {
        printk(TPQIC02_NAME ": ioctl(%4x, %4x, %4lx)\n", dev_maj,
               iocmd, ioarg);
    }

    if (!inode || !ioarg) {
        return -EINVAL;
    }

    /* check iocmd first */

    if (dev_maj != QIC02_TAPE_MAJOR) {
        printk(TPQIC02_NAME ": Oops! Wrong device?\n");
        /* A panic() would be appropriate here */
        return -ENODEV;
    }

    c = _IOC_NR(iocmd);

#ifdef CONFIG_QIC02_DYNCONF
    if (c == _IOC_NR(MTIOCGETCONFIG)) {
        CHECK_IOC_SIZE(mtconfiginfo);

        if (copy_to_user
            ((char *) ioarg, (char *) &qic02_tape_dynconf,
             sizeof(qic02_tape_dynconf))) {
            return -EFAULT;
        }
        return 0;

    } else if (c == _IOC_NR(MTIOCSETCONFIG)) {
        /* One should always do a MTIOCGETCONFIG first, then update
         * user-settings, then write back with MTIOCSETCONFIG.
         * The qic02conf program should re-open() the device before actual
         * use, to make sure everything is initialized.
         */

        CHECK_IOC_SIZE(mtconfiginfo);

        if (!capable(CAP_SYS_ADMIN)) {
            return -EPERM;
        }

        if ((doing_read != NO) || (doing_write != NO)) {
            return -EBUSY;
        }

        if (status_zombie == NO) {
            qic02_release_resources();    /* and go zombie */
        }

        /* copy struct from user space to kernel space */
        if (copy_from_user
            ((char *) &qic02_tape_dynconf, (char *) ioarg,
             sizeof(qic02_tape_dynconf))) {
            return -EFAULT;
        }
        return update_ifc_masks(qic02_tape_dynconf.ifc_type);
    }
    if (status_zombie == YES) {
        tpqputs(TPQD_ALWAYS, "Configs not set");
        return -ENXIO;
    }
#endif
    if (c == _IOC_NR(MTIOCTOP)) {
        CHECK_IOC_SIZE(mtop);

        /* copy mtop struct from user space to kernel space */
        if (copy_from_user
            ((char *) &operation, (char *) ioarg,
             sizeof(operation))) {
            return -EFAULT;
        }

        /* ---note: mt_count is signed, negative seeks must be
         * ---      translated to seeks in opposite direction!
         * (only needed for Sun-programs, I think.)
         */
        /* ---note: MTFSF with count 0 should position the
         * ---      tape at the beginning of the current file.
         */

        if (TP_DIAGS(current_tape_dev)) {
            printk("OP op=%4x, count=%4x\n", operation.mt_op,
                   operation.mt_count);
        }

        if (operation.mt_count < 0) {
            tpqputs(TPQD_ALWAYS,
                "Warning: negative mt_count ignored");
        }

        ioctl_status.mt_resid = operation.mt_count;
        if (operation.mt_op == MTSEEK) {
            if (!TP_HAVE_SEEK) {
                return -ENOTTY;
            }

            seek_addr_buf[0] =
                (operation.mt_count >> 16) & 0xff;
            seek_addr_buf[1] =
                (operation.mt_count >> 8) & 0xff;
            seek_addr_buf[2] = (operation.mt_count) & 0xff;
            if (operation.mt_count >> 24) {
                return -EINVAL;
            }
            if ((error = do_ioctl_cmd(operation.mt_op)) != 0) {
                return error;
            }

            ioctl_status.mt_resid = 0;
        } else {
            while (operation.mt_count > 0) {
                operation.mt_count--;
                if ((error =
                     do_ioctl_cmd(operation.mt_op)) != 0) {
                    return error;
                }

                ioctl_status.mt_resid = operation.mt_count;
            }
        }
        return 0;

    } else if (c == _IOC_NR(MTIOCGET)) {
        if (TP_DIAGS(current_tape_dev)) {
            printk("GET ");
        }

        CHECK_IOC_SIZE(mtget);

        /* It appears (gmt(1)) that it is normal behaviour to
         * first set the status with MTNOP, and then to read
         * it out with MTIOCGET
         */

        /* copy results to user space */
        if (copy_to_user
            ((char *) ioarg, (char *) &ioctl_status,
             sizeof(ioctl_status))) {
            return -EFAULT;
        }
        return 0;
    } else if (TP_HAVE_TELL && (c == _IOC_NR(MTIOCPOS))) {
        if (TP_DIAGS(current_tape_dev)) {
            printk("POS ");
        }

        CHECK_IOC_SIZE(mtpos);

        tpqputs(TPQD_IOCTLS, "MTTELL reading block address");
        if ((doing_read == YES) || (doing_write == YES)) {
            finish_rw(AR_QCMDV_TELL_BLK);
        }

        c = rdstatus((char *) blk_addr, sizeof(blk_addr),
                 AR_QCMDV_TELL_BLK);
        if (c != TE_OK) {
            return -EIO;
        }

        ioctl_tell.mt_blkno =
            (blk_addr[3] << 16) | (blk_addr[4] << 8) | blk_addr[5];

        /* copy results to user space */
        if (copy_to_user
            ((char *) ioarg, (char *) &ioctl_tell,
             sizeof(ioctl_tell))) {
            return -EFAULT;
        }
        return 0;

    } else {
        return -ENOTTY;    /* Other cmds not supported. */
    }
}                /* qic02_tape_ioctl */



/* These are (most) of the interface functions: */
static struct file_operations qic02_tape_fops = {
    owner:THIS_MODULE,
    llseek:no_llseek,
    read:qic02_tape_read,
    write:qic02_tape_write,
    ioctl:qic02_tape_ioctl,
    open:qic02_tape_open,
    release:qic02_tape_release,
};


static void qic02_release_resources(void)
{
    free_irq(QIC02_TAPE_IRQ, NULL);
    free_dma(QIC02_TAPE_DMA);
    release_region(QIC02_TAPE_PORT, QIC02_TAPE_PORT_RANGE);
    if (buffaddr) {
        free_pages((unsigned long) buffaddr,
               get_order(TPQBUF_SIZE));
    }
    buffaddr = 0;        /* Better to cause a panic than overwite someone else */
    status_zombie = YES;
}                /* qic02_release_resources */


static int qic02_get_resources(void)
{
    /* First perform some checks. If one of them fails,
     * the tape driver will not be registered to the system.
     */
    if (QIC02_TAPE_IRQ > 16) {
        tpqputs(TPQD_ALWAYS, "Bogus interrupt number.");
        return -ENXIO;
    }

    /* for DYNCONF, allocating IO, DMA and IRQ should not be done until 
     * the config parameters have been set using MTSETCONFIG.
     */

    if (check_region(QIC02_TAPE_PORT, QIC02_TAPE_PORT_RANGE)) {
        printk(TPQIC02_NAME
               ": IO space at 0x%x [%d ports] already reserved\n",
               QIC02_TAPE_PORT, QIC02_TAPE_PORT_RANGE);
        return -ENXIO;
    }

    /* get IRQ */
    if (request_irq
        (QIC02_TAPE_IRQ, qic02_tape_interrupt, SA_INTERRUPT, "QIC-02",
         NULL)) {
        printk(TPQIC02_NAME
               ": can't allocate IRQ%d for QIC-02 tape\n",
               QIC02_TAPE_IRQ);
        return -EBUSY;
    }

    /* After IRQ, allocate DMA channel */
    if (request_dma(QIC02_TAPE_DMA, "QIC-02")) {
        printk(TPQIC02_NAME
               ": can't allocate DMA%d for QIC-02 tape\n",
               QIC02_TAPE_DMA);
        free_irq(QIC02_TAPE_IRQ, NULL);
        return -EBUSY;
    }

    /* Grab the IO region. We already made sure it's available. */
    request_region(QIC02_TAPE_PORT, QIC02_TAPE_PORT_RANGE,
               TPQIC02_NAME);

    /* Setup the page-address for the dma transfer. */
    buffaddr =
        (void *) __get_dma_pages(GFP_KERNEL, get_order(TPQBUF_SIZE));

    if (!buffaddr) {
        qic02_release_resources();
        return -EBUSY;    /* Not ideal, EAGAIN perhaps? */
    }

    memset(buffaddr, 0, TPQBUF_SIZE);

    printk(TPQIC02_NAME
           ": Settings: IRQ %d, DMA %d, IO 0x%x, IFC %s\n",
           QIC02_TAPE_IRQ, QIC02_TAPE_DMA, ((QIC02_TAPE_IFC == ARCHIVE)
                        || (QIC02_TAPE_IFC ==
                            MOUNTAIN)) ?
           QIC02_CMD_PORT : QIC02_STAT_PORT,
           (QIC02_TAPE_IFC ==
        MOUNTAIN) ? "Mountain" : ((QIC02_TAPE_IFC ==
                       ARCHIVE) ? "Archive" :
                      "Wangtek"));

    if (tape_reset(0) != TE_OK
        || tp_sense(TP_WRP | TP_POR | TP_CNI) != TE_OK) {
        /* No drive detected, so vanish */
        tpqputs(TPQD_ALWAYS,
            "No drive detected -- releasing IO/IRQ/DMA.");
        status_dead = YES;
        qic02_release_resources();
        return -EIO;
    }

    /* All should be ok now */
    status_zombie = NO;
    return 0;
}                /* qic02_get_resources */

int __init qic02_tape_init(void)
{
    if (TPSTATSIZE != 6) {
        printk(TPQIC02_NAME
               ": internal error: tpstatus struct incorrect!\n");
        return -ENODEV;
    }
    if ((TPQBUF_SIZE < 512) || (TPQBUF_SIZE >= 0x10000)) {
        printk(TPQIC02_NAME
               ": internal error: DMA buffer size out of range\n");
        return -ENODEV;
    }

    current_tape_dev = MKDEV(QIC02_TAPE_MAJOR, 0);

#ifndef CONFIG_QIC02_DYNCONF
    printk(TPQIC02_NAME ": IRQ %d, DMA %d, IO 0x%x, IFC %s, %s, %s\n",
           QIC02_TAPE_IRQ, QIC02_TAPE_DMA,
# if QIC02_TAPE_IFC == WANGTEK
           QIC02_STAT_PORT, "Wangtek",
# elif QIC02_TAPE_IFC == ARCHIVE
           QIC02_CMD_PORT, "Archive",
# elif QIC02_TAPE_IFC == MOUNTAIN
           QIC02_CMD_PORT, "Mountain",
# else
#  error
# endif
           rcs_revision, rcs_date);
    if (qic02_get_resources()) {
        return -ENODEV;
    }
#else
    printk(TPQIC02_NAME ": Runtime config, %s, %s\n",
           rcs_revision, rcs_date);
#endif
    printk(TPQIC02_NAME ": DMA buffers: %u blocks\n", NR_BLK_BUF);
    /* If we got this far, install driver functions */
    if (devfs_register_chrdev
        (QIC02_TAPE_MAJOR, TPQIC02_NAME, &qic02_tape_fops)) {
        printk(TPQIC02_NAME ": Unable to get chrdev major %d\n",
               QIC02_TAPE_MAJOR);
#ifndef CONFIG_QIC02_DYNCONF
        qic02_release_resources();
#endif
        return -ENODEV;
    }
    devfs_register(NULL, "ntpqic11", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 2,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "tpqic11", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 3,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "ntpqic24", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 4,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "tpqic24", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 5,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "ntpqic120", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 6,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "tpqic120", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 7,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "ntpqic150", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 8,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    devfs_register(NULL, "tpqic150", DEVFS_FL_DEFAULT,
               QIC02_TAPE_MAJOR, 9,
               S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               &qic02_tape_fops, NULL);
    init_waitqueue_head(&qic02_tape_transfer);
    /* prepare timer */
    TIMEROFF;
    init_timer(&tp_timer);
    tp_timer.function = qic02_tape_times_out;

#ifndef CONFIG_QIC02_DYNCONF
    if (tape_reset(0) != TE_OK
        || tp_sense(TP_WRP | TP_POR | TP_CNI) != TE_OK) {
        /* No drive detected, so vanish */
        tpqputs(TPQD_ALWAYS,
            "No drive detected -- driver going on vacation...");
        qic02_release_resources();
        status_dead = YES;
        return -ENODEV;
    } else {
        if (is_exception()) {
            tpqputs(TPQD_ALWAYS, "exception detected\n");
            (void) tp_sense(TP_WRP | TP_POR | TP_CNI);
        }
    }
#endif

    /* initialize generic status for ioctl requests */

    ioctl_status.mt_type = QIC02_TAPE_DRIVE;    /* MT_IS* id nr */

    ioctl_status.mt_resid = 0;    /* ---residual count */
    ioctl_status.mt_gstat = 0;    /* ---generic status */
    ioctl_status.mt_erreg = 0;    /* not used */
    ioctl_status.mt_fileno = 0;    /* number of current file on tape */
    ioctl_status.mt_blkno = 0;    /* number of current (logical) block */

    return 0;
}                /* qic02_tape_init */

#ifdef MODULE

void cleanup_module(void)
{
    if (status_zombie == NO) {
        qic02_release_resources();
    }
    devfs_unregister_chrdev(QIC02_TAPE_MAJOR, TPQIC02_NAME);
    devfs_unregister(devfs_find_handle
             (NULL, "ntpqic11", QIC02_TAPE_MAJOR, 2,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "tpqic11", QIC02_TAPE_MAJOR, 3,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "ntpqic24", QIC02_TAPE_MAJOR, 4,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "tpqic24", QIC02_TAPE_MAJOR, 5,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "ntpqic120", QIC02_TAPE_MAJOR, 6,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "tpqic120", QIC02_TAPE_MAJOR, 7,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "ntpqic150", QIC02_TAPE_MAJOR, 8,
              DEVFS_SPECIAL_CHR, 0));
    devfs_unregister(devfs_find_handle
             (NULL, "tpqic150", QIC02_TAPE_MAJOR, 9,
              DEVFS_SPECIAL_CHR, 0));
}

int init_module(void)
{
    int retval;
    retval = qic02_tape_init();
# ifdef CONFIG_QIC02_DYNCONF
    /* This allows the dynamic config program to setup the card
     * by presetting qic02_tape_dynconf via insmod
     */
    if (!retval && qic02_tape_dynconf.ifc_type) {
        retval = update_ifc_masks(qic02_tape_dynconf.ifc_type);
        if (retval) {
            cleanup_module();
        }
    }
# endif
    return retval;
}
#endif

MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 1.0 pre-release build #13 powered by Captain Crunch Security Team | http://ccteam.ru | Generation time: 0.0198 ]--