#include "arccodes.h"
#include "bootx86.h"
#include "flop.h"

#ifdef FLOPPY_CACHE

//#define FLOPPY_CACHE_DEBUG
#ifdef FLOPPY_CACHE_DEBUG
#define DBGOUT(x) BlPrint x
#else
#define DBGOUT(x)
#endif


#define MAX_FLOPPY_LEN 1474560

UCHAR CachedDiskImage[MAX_FLOPPY_LEN];
UCHAR CachedDiskBadSectorMap[(MAX_FLOPPY_LEN/512)];
UCHAR CachedDiskCylinderMap[80];
USHORT CachedDiskBytesPerSector;
USHORT CachedDiskSectorsPerTrack;
USHORT CachedDiskSectorsPerCylinder;
USHORT CachedDiskBytesPerTrack;
ULONG CachedDiskLastSector;

BOOLEAN DiskInCache = FALSE;


VOID
FcpCacheOneCylinder(
    IN USHORT Cylinder
    )
{
    PUCHAR pCache;
    unsigned track,sector;
    ULONG AbsoluteSector;
    ARC_STATUS Status;
    unsigned retry;

    //
    // Calculate the location in the cache image where this cylinder should go.
    //
    AbsoluteSector = Cylinder * CachedDiskSectorsPerCylinder;
    pCache = CachedDiskImage + (AbsoluteSector * CachedDiskBytesPerSector);

    //
    // Read track 0 and 1 of this cylinder.
    //
    for(track=0; track<2; track++) {

        DBGOUT(("FcCacheFloppyDisk: Cylinder %u head %u: ",Cylinder,track));

        retry = 0;

        do {

            Status = GET_SECTOR(
                        2,                          // int13 request = read
                        0,                          // disk number (a:)
                        (USHORT)track,              // head (0 or 1)
                        Cylinder,                   // track (usually 0-79)
                        1,                          // sector number (1-based)
                        CachedDiskSectorsPerTrack,  // number of sectors to read
                        LocalBuffer                 // buffer
                        );

            if(Status) {
                retry++;
                RESET_DISK(0,0,0,0,0,0,0);
            }

        } while(Status && (retry <= 3));

        if(Status) {

            DBGOUT(("Error!\n"));

            //
            // One or more sectors in the track were bad -- read individually.
            //
            for(sector=1; sector<=CachedDiskSectorsPerTrack; sector++) {
            
                DBGOUT(("                             Sector %u: ",sector));

                retry = 0;

                do {

                    Status = GET_SECTOR(
                                2,                      // int13 request = read
                                0,                      // disk number (a:)
                                (USHORT)track,          // head (0 or 1)
                                Cylinder,               // cylinder (usually 0-79)
                                (USHORT)sector,         // sector number (1-based)
                                1,                      // number of sectors to read
                                LocalBuffer             // buffer
                                );

                    if(Status) {
                        retry++;
                        RESET_DISK(0,0,0,0,0,0,0);
                    }

                } while(Status && (retry <= 2));

                if(Status) {

                    //
                    // Sector is bad.
                    //
                    CachedDiskBadSectorMap[AbsoluteSector] = TRUE;

                    DBGOUT(("bad\n"));

                } else {

                    //
                    // Sector is good.  Transfer the data into the cache buffer.
                    //
                    RtlMoveMemory(pCache,LocalBuffer,CachedDiskBytesPerSector);

                    DBGOUT(("OK\n"));
                }

                //
                // Advance to the next sector in the cache buffer.
                //
                pCache += CachedDiskBytesPerSector;
                AbsoluteSector++;
            }

        } else {
            //
            // Transfer the whole track we just successfully read
            // into the cached disk buffer.
            //
            RtlMoveMemory(pCache,LocalBuffer,CachedDiskBytesPerTrack);
            pCache += CachedDiskBytesPerTrack;
            AbsoluteSector += CachedDiskSectorsPerTrack;

            DBGOUT(("OK\n"));
        }
    }

    CachedDiskCylinderMap[Cylinder] = TRUE;
}


BOOLEAN
FcIsThisFloppyCached(
    IN PUCHAR Buffer
    )
{
    if(!DiskInCache) {
        return(FALSE);
    }

    //
    // Compare the first 512 bytes of the cached disk
    // to the buffer passed in.  If they are equal,
    // then the disk is already cached.
    //
    if(RtlCompareMemory(CachedDiskImage,Buffer,512) == 512) {
        return(TRUE);
    }

    //
    // Disk is not cached.
    //
    return(FALSE);
}
    

VOID
FcUncacheFloppyDisk(
    VOID
    )
{
    DiskInCache = FALSE;
}


VOID
FcCacheFloppyDisk(
    PBIOS_PARAMETER_BLOCK Bpb    
    )
{
    //
    // Indicate that the cache is invalid.
    //
    DiskInCache = FALSE;

    //
    // Sanity check the bpb.
    // Ensure it's a standard 1.2 meg or 1.44 meg disk.
    //
    if((Bpb->Heads != 2) || (Bpb->BytesPerSector != 512)
    || ((Bpb->SectorsPerTrack != 15) && (Bpb->SectorsPerTrack != 18))
    || ((Bpb->Sectors != 2880) && (Bpb->Sectors != 2400)))
    {
        DBGOUT(("FcCacheFloppyDisk: floppy not standard 1.2 or 1.44 meg disk\n"));
        return;
    }

    //
    // Grab a buffer under the 1 meg line.
    // The buffer must be big enough to hold one whole track of 
    // a 1.44 meg floppy.
    //

    if(LocalBuffer == NULL) {
        LocalBuffer = FwAllocateHeap(18 * 512);
        if(LocalBuffer == NULL) {
            DBGOUT(("FcCacheFloppyDisk: Couldn't allocate local buffer\n"));
            return;
        }
    }

    DBGOUT(("FcCacheFloppyDisk: LocalBuffer @ %lx\n",LocalBuffer));

    //
    // The disk is one we can cache.  Indicate that a disk is cached
    // and mark all sectors good and all tracks not present.
    //
    DiskInCache = TRUE;
    RtlZeroMemory(CachedDiskBadSectorMap,sizeof(CachedDiskBadSectorMap));
    RtlZeroMemory(CachedDiskCylinderMap,sizeof(CachedDiskCylinderMap));
    CachedDiskSectorsPerTrack = Bpb->SectorsPerTrack;
    CachedDiskSectorsPerCylinder = Bpb->Heads * Bpb->SectorsPerTrack;
    CachedDiskBytesPerSector = Bpb->BytesPerSector;

    //
    // Calculate the number of bytes in a Track on the floppy.
    //
    CachedDiskBytesPerTrack = CachedDiskSectorsPerTrack * Bpb->BytesPerSector;

    //
    // Calculate the number of tracks.
    //
    CachedDiskLastSector = Bpb->Sectors-1;

    DBGOUT(("FcCacheFloppyDisk: Caching disk, %u sectors per track\n",CachedDiskSectorsPerTrack));

    FcpCacheOneCylinder(0);
}



ARC_STATUS
FcReadFromCache(
    IN  ULONG  Offset,
    IN  ULONG  Length,
    OUT PUCHAR Buffer
    )
{
    ULONG FirstSector,LastSector,Sector;
    ULONG FirstCyl,LastCyl,cyl;

    if(!Length) {
        return(ESUCCESS);
    }

    if(!DiskInCache) {
        return(EINVAL);
    }

    //
    // Determine the first sector in the transfer.
    //
    FirstSector = Offset / 512;

    //
    // Determine and validate the last sector in the transfer.
    //
    LastSector = FirstSector + ((Length-1)/512);

    if(LastSector > CachedDiskLastSector) {
        return(E2BIG);
    }

    //
    // Determine the first and last cylinders involved in the transfer.
    //
    FirstCyl = FirstSector / CachedDiskSectorsPerCylinder;
    LastCyl  = LastSector / CachedDiskSectorsPerCylinder;

    //
    // Make sure all these cylinders are cached.
    //
    for(cyl=FirstCyl; cyl<=LastCyl; cyl++) {
        if(!CachedDiskCylinderMap[cyl]) {
            FcpCacheOneCylinder((USHORT)cyl);
        }
    }

    //
    // Determine if any of the sectors in the transfer range 
    // are marked bad in the sector map.
    // 
    // If so, return an i/o error.
    //
    for(Sector=FirstSector; Sector<=LastSector; Sector++) {
        if(CachedDiskBadSectorMap[Sector]) {
            return(EIO);
        }
    }

    //
    // Transfer the data into the caller's buffer.
    //
    RtlMoveMemory(Buffer,CachedDiskImage+Offset,Length);

    return(ESUCCESS);
}

#endif // def FLOPPY_CACHE