/*++

Copyright (c) 1993 - Colorado Memory Systems, Inc.
All Rights Reserved

Module Name:

    init.c

Abstract:

    This section reads the QIC40 Header and initalizes some
    of the Context for the rest of the system.


Revision History:




--*/

//
// Includes
//

#include <ntddk.h>
#include <ntddtape.h>
#include "common.h"
#include "q117.h"
#include "protos.h"

#define FCT_ID 0x010b

dStatus
q117FixBadSectorMapPtr(
    IN OUT PQ117_CONTEXT Context,
    IN OUT PTAPE_HEADER header
    );

dBoolean
ValidateBadSectorMap(
    IN PTAPE_HEADER HeaderPointer,
    IN int StartOffset,
    IN dBoolean allow_null_list
    );


dStatus
q117LoadTape (
    IN OUT PTAPE_HEADER *HeaderPointer,
    IN OUT PQ117_CONTEXT Context,
    IN dUByte *driver_format_code
    )

/*++

Routine Description:

    Initialize the tape interface for read or write
    This routine reads the bad sector map into memory.

Arguments:

    HeaderPointer -

    Context - Context of the driver

Return Value:


--*/

{
    dStatus ret;           // Return value from other routines called or
                          // the status of block 1 of tracks 1-5 when read in.
    PTAPE_HEADER hdr;
    unsigned badSize;
    IO_REQUEST ioreq;


    q117ClearQueue(Context);

    //
    // Is defined later at ReadVolumeEntry().
    //

    Context->CurrentOperation.EndOfUsedTape=0;


    //
    // this memset allows IssIOReq to find the first two
    // good segments on the tape
    //

    RtlZeroMemory(
        Context->CurrentTape.BadMapPtr,
        Context->CurrentTape.BadSectorMapSize);

    Context->CurrentTape.CurBadListIndex = 0;


    if (!(ret = q117ReadHeaderSegment(&hdr, Context))) {

        if (HeaderPointer) {

            *HeaderPointer = (PVOID)hdr;

        }

        if (Context->CurrentTape.BadSectorMapFormat == BadMap4ByteArray) {

            badSize = sizeof(LONG) * (hdr->LastSegment+1);

            if (badSize > Context->CurrentTape.BadSectorMapSize) {

                ret = ERROR_ENCODE(ERR_BAD_TAPE, FCT_ID, 1);

            }

        }

        //
        // Now,  if the driver needs to know how big the tape is,
        // pass the information from the tape header.
        //
        switch (*driver_format_code) {
            case QIC_XLFORMAT:
            case QICFLX_FORMAT:
                // If no problems,  then change the tape length to the proper
                // size. (this is needed because the drive can not distinguish
                // between a standard 205 ft. tape and a 425 ft. tape.  Since
                // we read a valid header
                // Also needed for flex formats

                if (hdr->VendorUnique.correct_name.unused2 == 0 &&
                    hdr->VendorUnique.correct_name.TrackSeg != 0) {

                    // now,  set the number of segments that was recorded when
                    // the tape was formatted.
                    ioreq.x.ioTapeParms.segments_per_track = hdr->VendorUnique.correct_name.TrackSeg;

                } else {
                    // This is an old tape that does not have the
                    // number of segments in the header.  Therefore, it must
                    // be a 205ft cart.
                    ioreq.x.ioTapeParms.segments_per_track = 68;
                    CheckedDump(QIC117INFO,("NOTE:  this is an old tape without segment info - Assuming 68 segments\n"));
                }
                CheckedDump(QIC117INFO,("Informing the driver that the tape has 0x%x segments\n",ioreq.x.ioTapeParms.segments_per_track));
                ret = q117DoCmd(&ioreq, CMD_SET_TAPE_PARMS, NULL, Context);

                // Now,  change the format code from the old value,  to the
                // correct (new) value based on the tape header information.
                CheckedDump(QIC117INFO,("Changed format from %d to %d\n",*driver_format_code, ioreq.x.ioTapeParms.tape_cfg.tape_format_code));
                *driver_format_code = ioreq.x.ioTapeParms.tape_cfg.tape_format_code;

                break;
        }

        if (ret == ERR_NO_ERR) {


            // Get the pointer to the bad sector map
            ret = q117FixBadSectorMapPtr(Context, hdr);

            //
            // Copy over the header (and bad sector map, etc.)
            //
            RtlMoveMemory(
                Context->CurrentTape.TapeHeader,
                hdr,
                sizeof(*Context->CurrentTape.TapeHeader) );


#if DBG
            {
                ULONG bits,tmp,i,seg;
                ULONG *ptr, *ptrbase, *newbase, ptrsize,ptrused;

                ptrsize = 2048;
                ptrused = 0;
                ptrbase = ptr = ExAllocatePool(PagedPool, ptrsize*sizeof(*ptr));

                CheckedDump(QIC117SHOWBSM,("Bad Sector map:\n"));
                for (seg=0;seg<hdr->LastSegment;++seg) {
                    bits = q117ReadBadSectorList(Context, (SEGMENT)seg);
                    if (bits) {
                        tmp = 1;

                        //
                        // Loop through checking all the bits
                        //
                        for (i = 0; i < BLOCKS_PER_SEGMENT; ++i) {

                            if ( bits & tmp ) {
                                if (ptrused == ptrsize) {
                                    newbase = ExAllocatePool(PagedPool, (ptrsize+512)*sizeof(*ptr));
                                    RtlMoveMemory(newbase, ptrbase, ptrsize*sizeof(*ptr));
                                    ExFreePool(ptrbase);
                                    ptr = newbase + (ptr-ptrbase);
                                    ptrbase = newbase;
                                    ptrsize += 512;
                                }
                                ++ptrused;
                                *ptr++=i+(seg*BLOCKS_PER_SEGMENT);
                                CheckedDump(QIC117SHOWBSM,("%x ",i+(seg*BLOCKS_PER_SEGMENT)));

                            }


                            //
                            // shift left one (tmp *= 2 optimized)
                            //
                            tmp += tmp;

                        }

                    }

                }

                CheckedDump(QIC117SHOWBSM,("\n *** End of Bad Sector map\n"));

                RtlWriteRegistryValue(
                    RTL_REGISTRY_DEVICEMAP,
                    L"Tape\\Unit 0",
                    L"BadSectorMap",
                    REG_BINARY,
                    ptrbase,
                    ptrused*sizeof(*ptrbase));

                RtlWriteRegistryValue(
                    RTL_REGISTRY_DEVICEMAP,
                    L"Tape\\Unit 0",
                    L"BadSectorMapFormat",
                    REG_DWORD,
                    &Context->CurrentTape.BadSectorMapFormat,
                    4);

                RtlWriteRegistryValue(
                    RTL_REGISTRY_DEVICEMAP,
                    L"Tape\\Unit 0",
                    L"TapeHeader",
                    REG_BINARY,
                    Context->CurrentTape.TapeHeader,
                    3*1024);

                {
                    int offset;

                    offset = (int)Context->CurrentTape.BadMapPtr - (int)Context->CurrentTape.TapeHeader;

                    RtlWriteRegistryValue(
                        RTL_REGISTRY_DEVICEMAP,
                        L"Tape\\Unit 0",
                        L"BadMapOffset",
                        REG_DWORD,
                        &offset,
                        4);
                }

                ExFreePool(ptrbase);

            }
#endif

            //
            // if any bad sectors in the tape directory then don't
            // read ahead
            //

            if (q117CountBits(Context, Context->CurrentTape.VolumeSegment, 0l)) {

                Context->tapedir = (PIO_REQUEST)NULL;

            }

            //
            // set global variables
            //

            Context->CurrentTape.LastUsedSegment = Context->CurrentTape.VolumeSegment;

            //
            // Last data block that can be written to on tape
            //

            Context->CurrentTape.LastSegment = hdr->LastSegment;
            Context->CurrentTape.MaximumVolumes = (USHORT) (
                q117GoodDataBytes(hdr->FirstSegment,Context) /
                sizeof(VOLUME_TABLE_ENTRY));

            //
            // get number of bad sectors on tape and set CurrentTape.LastSegment to
            // last good block
            //

            q117GetBadSectors(Context);
        }
    }
    return(ret);
}

dStatus q117FixBadSectorMapPtr(
    IN OUT PQ117_CONTEXT Context,
    IN OUT PTAPE_HEADER header
    )

{
    int badmap;
    dStatus ret;

    //
    // Point to extra area.  This information (union of several structurs)
    // determines what the bad sector map is in several versions of QIC
    // tape formats
    //
    Context->CurrentTape.BadSectorMapFormat = BadMapFormatUnknown;
    ret = ERR_NO_ERR;

    // This better be correct for the next call to work
    ASSERT(header->FormatCode == Context->CurrentTape.TapeFormatCode);

    badmap = q117SelectBSMLocation(Context);
    ASSERT(badmap != 0);

    if (badmap == 0) {

        ret = ERROR_ENCODE(ERR_BAD_TAPE, FCT_ID, 4);

    } else {

        //
        // For a 3 byte list format,  make sure we have an assending
        // list of bad sectors.
        //
        if (Context->CurrentTape.BadSectorMapFormat == BadMap3ByteList) {

            if (ValidateBadSectorMap(header,badmap,TRUE) &&
                Context->CurrentTape.BadSectorMapFormat != BadMapFormatUnknown) {

                ret = ERR_NO_ERR;

            } else {

                ret = ERROR_ENCODE(ERR_BAD_TAPE, FCT_ID, 3);

            }
        }

    }

#if DBG
    {
        static char *btype[] = { "BadMap3ByteList",
                                "BadMap8ByteList",
                                "BadMap4ByteArray",
                                "BadMapFormatUnknown" };

        CheckedDump(QIC117INFO,("Found a bad sector map at offset %x of type %s\n",badmap,btype[Context->CurrentTape.BadSectorMapFormat]));
    }
#endif

    return ret;
}

int
q117SelectBSMLocation(
    IN OUT PQ117_CONTEXT Context
    )

/*++

Routine Description:

    For pre-format screwup tapes,  and all tapes created by NT,
    find the correct location for the bad sector map based
    on the drive type,  and the format code being used

Arguments:

    Context - Context of the driver

Return Value:


--*/

{
    int badmap;

    Context->CurrentTape.BadSectorMapFormat = BadMapFormatUnknown;
    badmap = 0;  //default to a detectable error condition

//
// OK,  this tape does not have a rev level (format sub code) so we must
// determine what format the bad sector map is in.
//
// History:
//
//   QIC80 rev K.  Spec format code, and BSM location/format
//
//      QIC_FORMAT   (2) = QIC-80 Rev B & later, 205 foot, and 307.5 foot tape.
//                         BSM = 1024, BadMap4ByteArray
//
//      QICEST_FORMAT(3) = QIC-80 Rev B & later, 1,100 foot tape.
//                         BSM = 1024, BadMap3ByteList
//
//      QICFLX_FORMAT(4) = QIC-80 Rev B & later variable format
//                         on any tape .250 inch or .315 inch.
//                         BSM = 256, BadMap3ByteList
//
//      QIC_XLFORMAT (5) = QIC-80 Rev B & later, 425 foot
//                         BSM = 1024, BadMap4ByteArray
//
//   QIC80 rev M. Spec format code, and BSM location/format

    switch (Context->CurrentTape.TapeFormatCode) {
        case QIC_FORMAT:
        case QIC_XLFORMAT:
            badmap = 2048;
            Context->CurrentTape.BadSectorMapFormat = BadMap4ByteArray;
            break;


        case QICEST_FORMAT:
            // This is a Pegasus cart (not manufactured)
            badmap = 2048;
            Context->CurrentTape.BadSectorMapFormat = BadMap3ByteList;
            break;

        case QICFLX_FORMAT:
            // This is a Travan or QIC Wide media,  so the bad
            // sector map is at 0x100,  and it is
            badmap = 256;
            Context->CurrentTape.BadSectorMapFormat = BadMap3ByteList;
            break;

    }

	 // Now,  fix up the pointer and size of the bad sector map
    Context->CurrentTape.BadMapPtr = (void *)(((char *)(Context->CurrentTape.TapeHeader))+badmap);
    Context->CurrentTape.BadSectorMapSize = sizeof(struct _TAPE_HEADER)-badmap;

    return badmap;
}


void
q117GetBadSectors (
    IN OUT PQ117_CONTEXT Context
    )

/*++

Routine Description:

    Gets the number of bad sectors on the whole tape.

Arguments:

    Context - Context of the driver

Return Value:


--*/

{
    ULONG badBits;
    SEGMENT segment,lastGood;

    Context->CurrentTape.BadSectors = 0;

    //
    // count up the bad blocks for status information
    //

    for ( segment = 0; segment <= Context->CurrentTape.LastSegment; ++segment ) {

        badBits = q117CountBits(Context, segment, 0l);

        if (badBits >= BLOCKS_PER_SEGMENT-ECC_BLOCKS_PER_SEGMENT) {

            badBits = BLOCKS_PER_SEGMENT;

        } else {

            lastGood = segment;

        }

        Context->CurrentTape.BadSectors += badBits;

    }


    //
    // set CurrentTape.LastSegment to last good segment
    //

    Context->CurrentTape.LastSegment = lastGood;
}


dStatus
q117ReadHeaderSegment (
    OUT PTAPE_HEADER *HeaderPointer,
    IN OUT PQ117_CONTEXT Context
    )

/*++

Routine Description:

    Reads a QIC40 tape header.  This includes reconstructing the header
    using 100% redundancy and Reed-Solomon Error correction.

Arguments:

    HeaderPointer -

    Context - Context of the driver

Return Value:


--*/

{
    dStatus ret;
    int i,j;
    BLOCK headerBlock[2];
    LONG headerBlockCount;
    PVOID data;
    PIO_REQUEST ioreq;
    PTAPE_HEADER hdr;

    //
    // default volume directory not loaded
    //

    Context->tapedir = NULL;

    //
    // read first segment from tape
    //

    Context->CurrentOperation.CurrentSegment=0;

    Context->CurrentTape.BadSectors = headerBlockCount = 0;

    //
    // Read the first block of the bad sector map into memory to get the
    // size of the bad sector map.
    //

    do {

        while (Context->CurrentOperation.CurrentSegment <=
                Context->CurrentTape.LastSegment &&
                !q117QueueFull(Context)) {

            if (ret = q117IssIOReq(
                    (PVOID)NULL,
                    CMD_READ_RAW,
                    SEGMENT_TO_BLOCK(Context->CurrentOperation.CurrentSegment),
                    NULL,
                    Context)) {

                return(ret);

            }

            ++Context->CurrentOperation.CurrentSegment;

        }

        ioreq = q117Dequeue(WaitForItem, Context);

        ret = ioreq->x.adi_hdr.status;

        //
        // Allow bad blocks (but not bad marks)
        // as good header segments
        //
        if (ERROR_DECODE(ret) == ERR_BAD_BLOCK_DETECTED || ret == ERR_NO_ERR) {

            headerBlock[headerBlockCount++] = ioreq->x.ioDeviceIO.starting_sector;

            if (q117DoCorrect(ioreq->x.adi_hdr.cmd_buffer_ptr,0l,ioreq->x.ioDeviceIO.crc)) {

                ret = ERROR_ENCODE(ERR_CORRECTION_FAILED, FCT_ID, 1);

            } else {

                ret = ERR_NO_ERR;

            }

        }

        //
        // Re-try (get a new segment) if we failed to correct,  got a bad
        // segment,  or found a bad mark.  All other errors will abort
        // the load.
        //
        if (ret != ERR_NO_ERR && ERROR_DECODE(ret) != ERR_CORRECTION_FAILED
            && ERROR_DECODE(ret) != ERR_BAD_BLOCK_DETECTED
            && ERROR_DECODE(ret) != ERR_BAD_MARK_DETECTED) {

            return(ret);

        }

    } while (ret && headerBlockCount < 2 && (!q117QueueEmpty(Context) ||
             Context->CurrentOperation.CurrentSegment <=
             Context->CurrentTape.LastSegment));

    //
    // if we did not find both tape header blocks and we got a bad block
    // (or other driver error) return to caller with BadTape tape
    //

    if (headerBlockCount < 2 && ret) {

        //
        // All copies of the tape header are bad.
        //
        return ERROR_ENCODE(ERR_BAD_TAPE, FCT_ID, 2);
    }


    //
    // if we got a bad block then we need to do 100% redundancy
    //  reconstruction
    //

    if (ERROR_DECODE(ret) == ERR_CORRECTION_FAILED) {

        ULONG badBits,curentBit;

        //
        // clear out any pending requests
        //

        q117ClearQueue(Context);

        //
        // re-read the first segment (error correction
        // routines corrupt data if they fail)
        //

        ret = q117IssIOReq((PVOID)NULL, CMD_READ_RAW, headerBlock[0],NULL,Context);
        ioreq = q117Dequeue(WaitForItem,Context);
        badBits = ioreq->x.ioDeviceIO.crc;
        data = ioreq->x.adi_hdr.cmd_buffer_ptr;
        curentBit = 1;

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

            if (badBits & curentBit) {

                //
                // try to read bad sector out of either header segment
                //

                for ( j = 0; j < 2; ++j ) {

                    if (ret = q117IssIOReq(
                                (UCHAR *)data+(BYTES_PER_SECTOR * i),
                                CMD_READ_HEROIC,
                                headerBlock[j]+i,
                                ioreq->BufferInfo,
                                Context)) {

                        return(ret);

                    }

                    if (q117Dequeue(WaitForItem, Context)->x.adi_hdr.status == ERR_NO_ERR) {

                        //
                        // turn off the bit (we just got a good copy
                        //  of this sector)
                        //

                        badBits &= ~curentBit;

                        //
                        // don't try the duplicate header segment
                        //

                        break;

                    }

                }

            }

            //
            // shift bit left once (optimized)
            //

            curentBit += curentBit;
        }

        //
        // re-try the error correction after 100% correction
        //

        if (q117DoCorrect(data,0l,badBits)) {

            return ERROR_ENCODE(ERR_CORRECTION_FAILED, FCT_ID, 2);

        }

    } else {

        data = ioreq->x.adi_hdr.cmd_buffer_ptr;


        //
        // go on and read the volume directory if we have
        // already queued it up
        //

        Context->CurrentTape.VolumeSegment =
            ((PTAPE_HEADER)ioreq->x.adi_hdr.cmd_buffer_ptr)->FirstSegment;

        if (Context->CurrentTape.VolumeSegment <
            Context->CurrentOperation.CurrentSegment) {

            do {

                ioreq = q117Dequeue(WaitForItem, Context);

            } while (ioreq->x.ioDeviceIO.starting_sector !=
                SEGMENT_TO_BLOCK( Context->CurrentTape.VolumeSegment ) );

            Context->tapedir = ioreq;
        }

        q117ClearQueue(Context);
    }

    *HeaderPointer = hdr = (PTAPE_HEADER)data;

    //
    // make sure this is a valid QIC40 tape
    //

    if (hdr->Signature != TapeHeaderSig) {

        return ERROR_ENCODE(ERR_BAD_SIGNATURE, FCT_ID, 1);

    }

    if ((hdr->FormatCode != QIC_FORMAT) &&
        (hdr->FormatCode != QICEST_FORMAT) &&
        (hdr->FormatCode != QIC_XLFORMAT) &&
        (hdr->FormatCode != QICFLX_FORMAT)) {

        return ERROR_ENCODE(ERR_UNKNOWN_FORMAT_CODE, FCT_ID, 1);


    } else {

        Context->CurrentTape.TapeFormatCode = hdr->FormatCode;

    }

    if (hdr->HeaderSegment != BLOCK_TO_SEGMENT( headerBlock[0] ) ) {

        //
        // segment number of header
        //

        return ERROR_ENCODE(ERR_UNUSABLE_TAPE, FCT_ID, 1);

    }

    if (headerBlockCount > 1 &&
            hdr->DupHeaderSegment != BLOCK_TO_SEGMENT( headerBlock[1] ) ) {

        //
        // segment number of duplicate header
        //

        return ERROR_ENCODE(ERR_UNUSABLE_TAPE, FCT_ID, 2);

    }


    Context->CurrentTape.VolumeSegment = hdr->FirstSegment;

    return(ERR_NO_ERR);
}
dBoolean
ValidateBadSectorMap(
    IN PTAPE_HEADER HeaderPointer,
    IN int StartOffset,
    IN dBoolean allow_null_list
    )
{
    IN int offset;
    int end;
    int good;
    int previous_value,next_value;
    unsigned char *ptr;
    int start_sector, end_sector;


    ptr = (void *)HeaderPointer;
    ptr += StartOffset;
    offset = StartOffset;

    // Calculate last byte offset (within the header segment)
    end = sizeof(struct _TAPE_HEADER);
    good = TRUE;  // Default to OK

    // Read in one 3byte value
    previous_value = (*(ptr+2) << 16) + *(dUWord *)ptr;

    // Skip past current entry
    ptr += 3;
    offset += 3;

    // Calculate 1 based start and ending sectors (inclusive)
    // Start is just after the duplicate header segment
    // and end is the last segment

    //start_sector = ((HeaderPointer->DupHeaderSegment+1)*BLOCKS_PER_SEGMENT)+1;
    start_sector = 1; // pre-formatted tapes sectors mapped out in the
                      // header area,  so we can't use above commented-
                      // out line

    end_sector = ((HeaderPointer->LastSegment+1)*BLOCKS_PER_SEGMENT);

    // if it's not null,  then we have a bad sector, so make sure
    // the rest of the bad sectors are assending,  and within the
    // boundaries of the tape
    if (previous_value) {
        // Get rid of hi-bit
        previous_value &= ~0x800000;

        // It is illegal for a zero with the hi-bit set
        if (previous_value == 0) {
            good = FALSE;
        }

        while (previous_value && offset + 3 <= end && good) {

            // Check to make sure the value is on the tape
            if (previous_value >= start_sector && previous_value <= end_sector) {

                // Read in next 3byte value
                next_value = (*(ptr+2) << 16) + *(dUWord *)ptr;

                // If we aren't at the null terminator
                if (next_value) {

                    if (next_value & 0x800000) {
                        if ((next_value - 0x800001)%32 != 0) {
                            CheckedDump(QIC117DBGP,("qic117: ERROR: Tape contains an invalid bad sector map\n"));
                            good = FALSE;
                        }
                    }

                    // Mask off hi-bit (full segment bit)
                    next_value &= ~0x800000;

                    // If less than previous value,  then fail the list
                    // NOTE: It is illegal for a zero with the hi-bit set
                    // but we should not need a specific test for this case
                    // as a zero value would fail this test (good=false).
                    if (next_value <= previous_value)
                        good = FALSE;


                }

            }

            // promote this to the previous
            previous_value = next_value;

            // Skip past current entry
            ptr += 3;
            offset += 3;
        }
    } else {

        // If we allow a null list,  then good will be true
        good = allow_null_list;

    }

    CheckedDump(QIC117INFO,("Validate Header at %x %s\n",StartOffset,good?"OK":"FAILED"));

    // good is true if we found an assending list
    return good;
}