//*****************************************************************************
//
// simple_fs.c - Functions for simple FAT file system support
//
// Copyright (c) 2013 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 2.0 of the DK-TM4C129X Firmware Package.
//
//*****************************************************************************

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "simple_fs.h"

//*****************************************************************************
//
// \addtogroup simple_fs_api
// @{
//
// This file system API should be used as follows:
// - Initialize it by calling SimpleFsInit().  You must supply a pointer to a
// 512 byte buffer that will be used for storing device sector data.
// - "Open" a file by calling SimpleFsOpen() and passing the 8.3-style filename
// as an 11-character string.
// - Read successive sectors from the file by using the convenience macro
// SimpleFsReadFileSector().
//
// This API does not use any file handles so there is no way to open more than
// one file at a time.  There is also no random access into the file, each
// sector must be read in sequence.
//
// The client of this API supplies a 512-byte buffer for storage of data read
// from the device.  But this file also maintains an additional, internal
// 512-byte buffer used for caching FAT sectors.  This minimizes the amount
// of device reads required to fetch cluster chain entries from the FAT.
//
// The application code (the client) must also provide a function used for
// reading sectors from the storage device, whatever it may be.  This allows
// the code in this file to be independent of the type of device used for
// storing the file system.  The name of the function is
// SimpleFsReadMediaSector().
//
//*****************************************************************************

//*****************************************************************************
//
// Setup a macro for handling packed data structures.
//
//*****************************************************************************
#if defined(ccs) ||                                                           \
    defined(codered) ||                                                       \
    defined(gcc) ||                                                           \
    defined(rvmdk) ||                                                         \
    defined(__ARMCC_VERSION) ||                                               \
    defined(sourcerygxx)
#define PACKED                  __attribute__((packed))
#elif defined(ewarm)
#define PACKED
#else
#error "Unrecognized COMPILER!"
#endif

//*****************************************************************************
//
// Instruct the IAR compiler to pack the following structures.
//
//*****************************************************************************
#ifdef ewarm
#pragma pack(1)
#endif

//*****************************************************************************
//
// Structures for mapping FAT file system
//
//*****************************************************************************

//*****************************************************************************
//
// The FAT16 boot sector extension
//
//*****************************************************************************
typedef struct
{
    uint8_t ui8DriveNumber;
    uint8_t ui8Reserved;
    uint8_t ui8ExtSig;
    uint32_t ui32Serial;
    char pcVolumeLabel[11];
    char pcFsType[8];
    uint8_t ui8BootCode[448];
    uint16_t ui16Sig;
}
PACKED tBootExt16;

//*****************************************************************************
//
// The FAT32 boot sector extension
//
//*****************************************************************************
typedef struct
{
    uint32_t ui32SectorsPerFAT;
    uint16_t ui16Flags;
    uint16_t ui16Version;
    uint32_t ui32RootCluster;
    uint16_t ui16InfoSector;
    uint16_t ui16BootCopy;
    uint8_t ui8Reserved[12];
    uint8_t ui8DriveNumber;
    uint8_t ui8Reserved1;
    uint8_t ui8ExtSig;
    uint32_t ui32Serial;
    char pcVolumeLabel[11];
    char pcFsType[8];
    uint8_t ui8BootCode[420];
    uint16_t ui16Sig;
}
PACKED tBootExt32;

//*****************************************************************************
//
// The FAT16/32 boot sector main section
//
//*****************************************************************************
typedef struct
{
    uint8_t ui8Jump[3];
    uint8_t i8OEMName[8];
    uint16_t ui16BytesPerSector;
    uint8_t ui8SectorsPerCluster;
    uint16_t ui16ReservedSectors;
    uint8_t ui8NumFATs;
    uint16_t ui16NumRootEntries;
    uint16_t ui16TotalSectorsSmall;
    uint8_t ui8MediaDescriptor;
    uint16_t ui16SectorsPerFAT;
    uint16_t ui16SectorsPerTrack;
    uint16_t ui16NumberHeads;
    uint32_t ui32HiddenSectors;
    uint32_t ui32TotalSectorsBig;
    union
    {
        tBootExt16 sExt16;
        tBootExt32 sExt32;
    }
    PACKED ext;
}
PACKED tBootSector;

//*****************************************************************************
//
// The partition table
//
//*****************************************************************************
typedef struct
{
    uint8_t ui8Status;
    uint8_t ui8CHSFirst[3];
    uint8_t ui8Type;
    uint8_t ui8CHSLast[3];
    uint32_t ui32FirstSector;
    uint32_t ui32NumBlocks;
}
PACKED tPartitionTable;

//*****************************************************************************
//
// The master boot record (MBR)
//
//*****************************************************************************
typedef struct
{
    uint8_t ui8CodeArea[440];
    uint8_t ui8DiskSignature[4];
    uint8_t ui8Nulls[2];
    tPartitionTable sPartTable[4];
    uint16_t ui16Sig;
}
PACKED tMasterBootRecord;

//*****************************************************************************
//
// The structure for a single directory entry
//
//*****************************************************************************
typedef struct
{
    char    pcFileName[11];
    uint8_t ui8Attr;
    uint8_t ui8Reserved;
    uint8_t ui8CreateTime[5];
    uint8_t ui8LastDate[2];
    uint16_t ui16ClusterHi;
    uint8_t ui8LastModified[4];
    uint16_t ui16Cluster;
    uint32_t ui32FileSize;
}
PACKED tDirEntry;

//*****************************************************************************
//
// Tell the IAR compiler that the remaining structures do not need to be
// packed.
//
//*****************************************************************************
#ifdef ewarm
#pragma pack()
#endif

//*****************************************************************************
//
// This structure holds information about the layout of the file system
//
//*****************************************************************************
typedef struct
{
    uint32_t ui32FirstSector;
    uint32_t ui32NumBlocks;
    uint16_t ui16SectorsPerCluster;
    uint16_t ui16MaxRootEntries;
    uint32_t ui32SectorsPerFAT;
    uint32_t ui32FirstFATSector;
    uint32_t ui32LastFATSector;
    uint32_t ui32FirstDataSector;
    uint32_t ui32Type;
    uint32_t ui32StartRootDir;
}
tPartitionInfo;

static tPartitionInfo sPartInfo;

//*****************************************************************************
//
// A pointer to the client provided sector buffer.
//
//*****************************************************************************
static uint8_t *g_pui8SectorBuf;

//*****************************************************************************
//
// Initializes the simple file system
//
// \param pui8SectorBuf is a pointer to a caller supplied 512-byte buffer
// that will be used for holding sectors that are loaded from the media
// storage device.
//
// Reads the MBR, partition table, and boot record to find the logical
// structure of the file system.  This function stores the file system
// structural data internally so that the remaining functions of the API
// can read the file system.
//
// To read data from the storage device, the function SimpleFsReadMediaSector()
// will be called.  This function is not implemented here but must be
// implemented by the user of this simple file system.
//
// This file system support is extremely simple-minded.  It will only
// find the first partition of a FAT16 or FAT32 formatted mass storage
// device.  Only very minimal error checking is performed in order to save
// code space.
//
// \return Zero if successful, non-zero if there was an error.
//
//*****************************************************************************
uint32_t
SimpleFsInit(uint8_t *pui8SectorBuf)
{
    tMasterBootRecord *pMBR;
    tPartitionTable *pPart;
    tBootSector *pBoot;

    //
    // Save the sector buffer pointer.  The input parameter is assumed
    // to be good.
    //
    g_pui8SectorBuf = pui8SectorBuf;

    //
    // Get the MBR
    //
    if(SimpleFsReadMediaSector(0, pui8SectorBuf))
    {
        return(1);
    }

    //
    // Verify MBR signature - bare minimum validation of MBR.
    //
    pMBR = (tMasterBootRecord *)pui8SectorBuf;
    if(pMBR->ui16Sig != 0xAA55)
    {
        return(1);
    }

    //
    // See if this is a MBR or a boot sector.
    //
    pBoot = (tBootSector *)pui8SectorBuf;
    if((strncmp(pBoot->ext.sExt16.pcFsType, "FAT", 3) != 0) &&
       (strncmp(pBoot->ext.sExt32.pcFsType, "FAT32", 5) != 0))
    {
        //
        // Get the first partition table
        //
        pPart = &(pMBR->sPartTable[0]);

        //
        // Could optionally check partition type here ...
        //

        //
        // Get the partition location and size
        //
        sPartInfo.ui32FirstSector = pPart->ui32FirstSector;
        sPartInfo.ui32NumBlocks = pPart->ui32NumBlocks;

        //
        // Read the boot sector from the partition
        //
        if(SimpleFsReadMediaSector(sPartInfo.ui32FirstSector, pui8SectorBuf))
        {
            return(1);
        }
    }
    else
    {
        //
        // Extract the number of sectors from the boot sector.
        //
        sPartInfo.ui32FirstSector = 0;
        if(pBoot->ui16TotalSectorsSmall == 0)
        {
            sPartInfo.ui32NumBlocks = pBoot->ui32TotalSectorsBig;
        }
        else
        {
            sPartInfo.ui32NumBlocks = pBoot->ui16TotalSectorsSmall;
        }
    }

    //
    // Get pointer to the boot sector
    //
    if(pBoot->ext.sExt16.ui16Sig != 0xAA55)
    {
        return(1);
    }

    //
    // Verify the sector size is 512.  We can't deal with anything else
    //
    if(pBoot->ui16BytesPerSector != 512)
    {
        return(1);
    }

    //
    // Extract some info from the boot record
    //
    sPartInfo.ui16SectorsPerCluster = pBoot->ui8SectorsPerCluster;
    sPartInfo.ui16MaxRootEntries = pBoot->ui16NumRootEntries;

    //
    // Decide if we are dealing with FAT16 or FAT32.
    // If number of root entries is 0, that suggests FAT32
    //
    if(sPartInfo.ui16MaxRootEntries == 0)
    {
        //
        // Confirm FAT 32 signature in the expected place
        //
        if(!strncmp(pBoot->ext.sExt32.pcFsType, "FAT32   ", 8))
        {
            sPartInfo.ui32Type = 32;
        }
        else
        {
            return(1);
        }
    }
    //
    // Root entries is non-zero, suggests FAT16
    //
    else
    {
        //
        // Confirm FAT16 signature
        //
        if(!strncmp(pBoot->ext.sExt16.pcFsType, "FAT16   ", 8))
        {
            sPartInfo.ui32Type = 16;
        }
        else
        {
            return(1);
        }
    }

    //
    // Find the beginning of the FAT, in absolute sectors
    //
    sPartInfo.ui32FirstFATSector = sPartInfo.ui32FirstSector +
                                 pBoot->ui16ReservedSectors;

    //
    // Find the end of the FAT in absolute sectors.  FAT16 and 32
    // are handled differently.
    //
    sPartInfo.ui32SectorsPerFAT = (sPartInfo.ui32Type == 16) ?
                                pBoot->ui16SectorsPerFAT :
                                pBoot->ext.sExt32.ui32SectorsPerFAT;
    sPartInfo.ui32LastFATSector = sPartInfo.ui32FirstFATSector +
                                sPartInfo.ui32SectorsPerFAT - 1;

    //
    // Find the start of the root directory and the data area.
    // For FAT16, the root will be stored as an absolute sector number
    // For FAT32, the root will be stored as the starting cluster of the root
    // The data area start is the absolute first sector of the data area.
    //
    if(sPartInfo.ui32Type == 16)
    {
        sPartInfo.ui32StartRootDir = sPartInfo.ui32FirstFATSector +
                                   (sPartInfo.ui32SectorsPerFAT *
                                    pBoot->ui8NumFATs);
        sPartInfo.ui32FirstDataSector = sPartInfo.ui32StartRootDir +
                                      (sPartInfo.ui16MaxRootEntries / 16);
    }
    else
    {
        sPartInfo.ui32StartRootDir = pBoot->ext.sExt32.ui32RootCluster;
        sPartInfo.ui32FirstDataSector = sPartInfo.ui32FirstFATSector +
                            (sPartInfo.ui32SectorsPerFAT * pBoot->ui8NumFATs);
    }

    //
    // At this point the file system has been initialized, so return
    // success to the caller.
    //
    return(0);
}

//*****************************************************************************
//
// Find the next cluster in a FAT chain
//
// \param ui32ThisCluster is the current cluster in the chain
//
// Reads the File Allocation Table (FAT) of the file system to find the
// next cluster in a chain of clusters.  The current cluster is passed in
// and the next cluster in the chain will be returned.
//
// This function reads sectors from the storage device as needed in order
// to parse the FAT tables.  Error handling is minimal since there is not
// much that can be done if an error is encountered.  If any error is
// encountered, or if this is the last cluster in the chain, then 0 is
// returned.  This signals the caller to stop traversing the chain (either
// due to error or end of chain).
//
// The function maintains a cache of a single sector from the FAT.  It only
// reads in a new FAT sector if the requested cluster is not in the
// currently cached sector.
//
// \return Next cluster number if successful, 0 if this is the last cluster
// or any error is found.
//
//*****************************************************************************
static uint32_t
SimpleFsGetNextCluster(uint_fast32_t ui32ThisCluster)
{
    static uint8_t ui8FATCache[512];
    static uint_fast32_t ui32CachedFATSector = (uint32_t)-1;
    uint_fast32_t ui32ClustersPerFATSector;
    uint_fast32_t ui32ClusterIdx;
    uint_fast32_t ui32FATSector;
    uint_fast32_t ui32NextCluster;
    uint_fast32_t ui32MaxCluster;

    //
    // Compute the maximum possible reasonable cluster number
    //
    ui32MaxCluster = sPartInfo.ui32NumBlocks / sPartInfo.ui16SectorsPerCluster;

    //
    // Make sure cluster input number is reasonable.  If not then return
    // 0 indicating error.
    //
    if((ui32ThisCluster < 2) || (ui32ThisCluster > ui32MaxCluster))
    {
        return(0);
    }

    //
    // Compute the index of the requested cluster within the sector.
    // Also compute the sector number within the FAT that contains the
    // entry for the requested cluster.
    //
    ui32ClustersPerFATSector = (sPartInfo.ui32Type == 16) ? 256 : 128;
    ui32ClusterIdx = ui32ThisCluster % ui32ClustersPerFATSector;
    ui32FATSector = ui32ThisCluster / ui32ClustersPerFATSector;

    //
    // Check to see if the FAT sector we need is already cached
    //
    if(ui32FATSector != ui32CachedFATSector)
    {
        //
        // FAT sector we need is not cached, so read it in
        //
        if(SimpleFsReadMediaSector(sPartInfo.ui32FirstFATSector + ui32FATSector,
                                 ui8FATCache) != 0)
        {
            //
            // There was an error so mark cache as unavailable and return
            // an error.
            //
            ui32CachedFATSector = (uint32_t)-1;
            return(0);
        }

        //
        // Remember which FAT sector was just loaded into the cache.
        //
        ui32CachedFATSector = ui32FATSector;
    }

    //
    // Now look up the next cluster value from the cached sector, using this
    // requested cluster as an index. It needs to be indexed as 16 or 32
    // bit values depending on whether it is FAT16 or 32
    // If the cluster value means last cluster, then return 0
    //
    if(sPartInfo.ui32Type == 16)
    {
        ui32NextCluster = ((uint16_t *)ui8FATCache)[ui32ClusterIdx];
        if(ui32NextCluster >= 0xFFF8)
        {
            return(0);
        }
    }
    else
    {
        ui32NextCluster = ((uint32_t *)ui8FATCache)[ui32ClusterIdx];
        if(ui32NextCluster >= 0x0FFFFFF8)
        {
            return(0);
        }
    }

    //
    // Check new cluster value to make sure it is reasonable.  If not then
    // return 0 to indicate an error.
    //
    if((ui32NextCluster >= 2) && (ui32NextCluster <= ui32MaxCluster))
    {
        return(ui32NextCluster);
    }
    else
    {
        return(0);
    }
}

//*****************************************************************************
//
// Read a single sector from a file into the sector buffer
//
// \param ui32StartCluster is the first cluster of the file, used to
// initialize the file read.  Use 0 for successive sectors.
//
// Reads sectors in sequence from a file and stores the data in the sector
// buffer that was passed in the initial call to SimpleFsInit().  The function
// is initialized with the file to read by passing the starting cluster of
// the file.  The function will initialize some static data and return.  It
// does not read any file data when passed a starting cluster (and
// returns 0 - this is normal).
//
// Once the function has been initialized with the file's starting cluster,
// then successive calls should be made, passing a value of 0 for the
// cluster number.  This tells the function to read the next sector from the
// file and store it in the sector buffer.  The function remembers the last
// sector that was read, and each time it is called with a cluster value of
// 0, it will read the next sector.  The function will traverse the FAT
// chain as needed to read all the sectors.  When a sector has been
// successfully read from a file, the function will return non-zero.  When
// there are no more sectors to read, or any error is encountered, the
// function will return 0.
//
// Note that the function always reads a whole sector, even if the end of
// a file does not fill the last sector.  It is the responsibility of the
// caller to track the file size and to deal with a partially full last
// sector.
//
// \return Non-zero if a sector was read into the sector buffer, or
// 0 if there are no more sectors or if any error occurred.
//
//*****************************************************************************
uint32_t
SimpleFsGetNextFileSector(uint_fast32_t ui32StartCluster)
{
    static uint_fast32_t ui32WorkingCluster = 0;
    static uint_fast32_t ui32WorkingSector;
    uint_fast32_t ui32ReadSector;

    //
    // If user specified starting cluster, then init the working cluster
    // and sector values
    //
    if(ui32StartCluster)
    {
        ui32WorkingCluster = ui32StartCluster;
        ui32WorkingSector = 0;
        return(0);
    }

    //
    // Otherwise, make sure there is a valid working cluster already
    //
    else if(ui32WorkingCluster == 0)
    {
        return(0);
    }

    //
    // If the current working sector is the same as sectors per cluster,
    // then that means that the next cluster needs to be loaded.
    //
    if(ui32WorkingSector == sPartInfo.ui16SectorsPerCluster)
    {
        //
        // Get the next cluster in the chain for this file.
        //
        ui32WorkingCluster = SimpleFsGetNextCluster(ui32WorkingCluster);

        //
        // If the next cluster is valid, then reset the working sector
        //
        if(ui32WorkingCluster)
        {
            ui32WorkingSector = 0;
        }

        //
        // Next cluster is not valid, or this was the end of the chain.
        // Clear the working cluster and return an indication that no new
        // sector data was loaded.
        //
        else
        {
            ui32WorkingCluster = 0;
            return(0);
        }
    }

    //
    // Calculate the sector to read from.  It is the sector of the start
    // of the working cluster, plus the working sector (the sector within
    // the cluster), plus the offset to the start of the data area.
    // Note that the cluster needs to be reduced by 2 in order to index
    // properly into the data area.  That is a feature of FAT file system.
    //
    ui32ReadSector = (ui32WorkingCluster - 2) * sPartInfo.ui16SectorsPerCluster;
    ui32ReadSector += ui32WorkingSector;
    ui32ReadSector += sPartInfo.ui32FirstDataSector;

    //
    // Attempt to read the next sector from the cluster.  If not successful,
    // then clear the working cluster and return a non-success indication.
    //
    if(SimpleFsReadMediaSector(ui32ReadSector, g_pui8SectorBuf) != 0)
    {
        ui32WorkingCluster = 0;
        return(0);
    }
    else
    {
        //
        // Read was successful.  Increment to the next sector of the cluster
        // and return a success indication.
        //
        ui32WorkingSector++;
        return(1);
    }
}

//*****************************************************************************
//
// Find a file in the root directory of the file system and open it for
// reading.
//
// \param pcName83 is an 11-character string that represents the 8.3 file
// name of the file to open.
//
// This function traverses the root directory of the file system to find
// the file name specified by the caller.  Note that the file name must be
// an 8.3 file name that is 11 characters int32_t.  The first 8 characters are
// the base name and the last 3 characters are the extension.  If there are
// fewer characters in the base name or extension, the name should be padded
// with spaces.  For example "myfile.bn" has fewer than 11 characters, and
// should be passed with padding like this: "myfile  bn ".  Note the extra
// spaces, and that the dot ('.') is not part of the string that is passed
// to this function.
//
// If the file is found, then it initializes the file for reading, and returns
// the file length.  The file can be read by making successive calls to
// SimpleFsReadFileSector().
//
// The function only searches the root directory and ignores any
// subdirectories.  It also ignores any int32_t file name entries, looking only
// at the 8.3 file name for a match.
//
// \return The size of the file if it is found, or 0 if the file could not
// be found.
//
//*****************************************************************************
uint32_t
SimpleFsOpen(char *pcName83)
{
    tDirEntry *pDirEntry;
    uint_fast32_t ui32DirSector;
    uint_fast32_t ui32FirstCluster;

    //
    // Find starting root dir sector, only used for FAT16
    // If FAT32 then this is the first cluster of root dir
    //
    ui32DirSector = sPartInfo.ui32StartRootDir;

    //
    // For FAT32, root dir is like a file, so init a file read of the root dir
    //
    if(sPartInfo.ui32Type == 32)
    {
        SimpleFsGetNextFileSector(ui32DirSector);
    }

    //
    // Search the root directory entry for the firmware file
    //
    while(1)
    {
        //
        // Read in a directory block.
        //
        if(sPartInfo.ui32Type == 16)
        {
            //
            // For FAT16, read in a sector of the root directory
            //
            if(SimpleFsReadMediaSector(ui32DirSector, g_pui8SectorBuf))
            {
                return(0);
            }
        }
        else
        {
            //
            // For FAT32, the root directory is treated like a file.
            // The root directory sector will be loaded into the sector buf
            //
            if(SimpleFsGetNextFileSector(0) == 0)
            {
                return(0);
            }
        }

        //
        // Initialize the directory entry pointer to the first entry of
        // this sector.
        //
        pDirEntry = (tDirEntry *)g_pui8SectorBuf;

        //
        // Iterate through all the directory entries in this sector
        //
        while((uint8_t *)pDirEntry < &g_pui8SectorBuf[512])
        {
            //
            // If the 8.3 filename of this entry matches the firmware
            // file name, then we have a match, so return a pointer to
            // this entry.
            //
            if(!strncmp(pDirEntry->pcFileName, pcName83, 11))
            {
                //
                // Compute the starting cluster of the file
                //
                ui32FirstCluster = pDirEntry->ui16Cluster;
                if(sPartInfo.ui32Type == 32)
                {
                    //
                    // For FAT32, add in the upper word of the
                    // starting cluster number
                    //
                    ui32FirstCluster += pDirEntry->ui16ClusterHi << 16;
                }

                //
                // Initialize the start of the file
                //
                SimpleFsGetNextFileSector(ui32FirstCluster);
                return(pDirEntry->ui32FileSize);
            }

            //
            // Advance to the next entry in this sector.
            //
            pDirEntry++;
        }

        //
        // Need to get the next sector in the directory.  Handled
        // differently depending on if this is FAT16 or 32
        //
        if(sPartInfo.ui32Type == 16)
        {
            //
            // FAT16: advance sectors as int32_t as there are more possible
            // entries.
            //
            sPartInfo.ui16MaxRootEntries -= 512 / 32;
            if(sPartInfo.ui16MaxRootEntries)
            {
                ui32DirSector++;
            }
            else
            {
                //
                // Ran out of directory entries and didn't find the file,
                // so return a null.
                //
                return(0);
            }
        }
        else
        {
            //
            // FAT32: there is nothing to compute here.  The next root
            // dir sector will be fetched at the top of the loop
            //
        }
    }
}

//*****************************************************************************
//
// Close the Doxygen group.
// @}
//
//*****************************************************************************
