#include "boot_fat32.h"

#include <boot_config.h>
#include <boot_utils.h>

#include <boot_tty_driver.h>
#include <boot_bdv_driver.h>
#include <boot_hba_driver.h>
#include <boot_mmc_driver.h>

/*
#include <boot_sdc_driver.h>
#include <boot_rdk_driver.h>
*/

#define FAT_MAGIC_VALUE  0x12345678   // FAT descriptor initialized

/****************************************************************************
 *                            Global variables.                             *
 ****************************************************************************/

// FAT32 descriptor
fat_desc_t      boot_fat            __attribute__((aligned(64)));

// Buffer used for FAT scanning
uint32_t        buffer_fat[1024]    __attribute__((aligned(64)));

// Buffer used for directory scanning
unsigned char   buffer_dir[4096]    __attribute__((aligned(64)));

// LBA of cluster currently stored in buffer_fat
uint32_t    buffer_fat_lba;

// LBA of cluster currently stored in buffer_dir
uint32_t    buffer_dir_lba;

/****************************************************************************
 *                           Internal functions.                            *
 ****************************************************************************/

/****************************************************************************
 * This function returns the offset (in bytes) of the field defined by      *
 * 'offset' and 'size'.                                                     *
 * @ offset : offset of the field from the beginning of the sector (in      *
 *            bytes).                                                       *
 * @ size   : length of the field (in bytes).                               *
 *                                                                          *
 * @ returns the field offset.                                              *
 ****************************************************************************/
static inline int get_offset(int offset, int size)
{
    return offset;

} // get_offset()

/****************************************************************************
 * This function returns the length (in bytes) of the field defined by      *
 * 'offset' and 'size'.                                                     *
 * @ offset : offset of the field from the beginning of the sector (in      *
 *            bytes).                                                       *
 * @ size   : length of the field (in bytes).                               *
 *                                                                          *
 * @ returns the field length.                                              *
 ****************************************************************************/
static inline int get_size(int offset, int size)
{
    return size;

} // get_size()

/****************************************************************************
 * This function reads a data field (less than 4 bytes) from 'buffer',      *
 * taking endianness into account. The field to be analyzed is defined by   *
 * 'offset' and 'size'.                                                     *
 * @ offset         : offset (in bytes) from the beginning of the buffer    *
 * @ size           : size (in bytes) of the field to be read               *
 * @ buffer         : base address of the buffer                            *
 * @ little_endian  : 1 if buffer is in little-endian format / 0 otherwise  *
 *                                                                          *
 * @ returns the value read from the data field.                            *
 ****************************************************************************/
static uint32_t read_field( uint32_t         offset,
                            uint32_t         size,
                            unsigned char*   buffer,
                            uint32_t         little_endian )
{
    uint32_t res = 0; 
    uint32_t i; 

    for (i = 0; i < size; i++)
    {
        if (little_endian)       // Least significant bytes are stored first
        {
            res |= buffer[offset+i] << (8*i);
        }
        else                     // Most significant bytes are stored first
        {
            res |= buffer[offset+i] << 8*(size - i - 1);
        }
    }

    return res;

} // read_field()

/****************************************************************************
 * This function transfers 'count' sectors from the block device and a      *
 * memory buffer by calling the relevant driver.                            *
 * @ lba        : first sector address on the block device.                 *
 * @ buff_addr  : memory buffer physical address.                           *
 * @ count      : number of sectors to be transfered.                       *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************/
static int fat_ioc_access( uint32_t lba, 
                           xptr_t   buf_paddr,
                           uint32_t count)
{
    // Call the appropriate driver

#if     USE_IOC_BDV 
    return ( boot_bdv_access( lba, buf_paddr, count) );
#elif   USE_IOC_HBA
    return ( boot_hba_access( lba, buf_paddr, count) );

/*
#elif   USE_IOC_SDC
    return ( boot_sdc_access( lba, buf_paddr, count) );
#elif   USE_IOC_SPI
    return ( boot_spi_access( lba, buf_paddr, count) );
#elif   USE_IOC_RDK
    return ( boot_rdk_access( lba, buf_paddr, count) );
*/

#else
    boot_printf("\n[BOOT ERROR] in fat_ioc_access(): IOC driver not defined\n");
    return 1;
#endif

} // fat_ioc_access()

/****************************************************************************
 * This function directly accesses the FS Information Sector on the block   *
 * device to set the free_cluster_hint and free_clusters_nr fields of       *
 * the FAT32 descriptor.                                                    *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************/
static int set_fsi()
{
#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s enters at cycle %d\n",
                __FUNCTION__ , boot_get_proctime() );
#endif

    // Load FS Information Sector into the FAT buffer
    if ( fat_ioc_access( boot_fat.fsi_lba,
                         XPTR( BOOT_CORE_CXY , boot_fat.block_buffer ),
                         1 ) )
    {
        boot_printf("\n[BOOT ERROR] %s: Cannot load FS Information Sector\n",
                    __FUNCTION__ );
        return -1;
    }

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s : FSI Sector loaded at cycle %d\n",
                __FUNCTION__ , boot_get_proctime() );
#endif

    boot_fat.block_buffer_lba = boot_fat.fsi_lba;

    // Get free_clusters_nr field from FS Information Sector
    boot_fat.free_clusters_nr = read_field(FSI_FREE_COUNT,
                                           boot_fat.block_buffer,
                                           1);

    // check free clusters number no larger than total number of clusters 
    if (boot_fat.free_clusters_nr >= (boot_fat.data_sectors >> 3))
    {
        boot_printf("\n[BOOT ERROR] %s: FSI_FREE_COUNT in FSI sector (%x)\n",
                    "\texceeds number of data clusters (%x)\n",
                    __FUNCTION__ , boot_fat.free_clusters_nr , (boot_fat.data_sectors >> 3));
        return -1;
    }

    // Get free_cluster_hint field from FS Information Sector
    boot_fat.free_cluster_hint = read_field(FSI_NXT_FREE,
                                            boot_fat.block_buffer,
                                            1);
    // check free_cluster_hint no larger than total number of clusters
    if (boot_fat.free_cluster_hint >= (boot_fat.data_sectors >> 3))
    {
        boot_printf("\n[BOOT ERROR] %s: FSI_NXT_FREE in FSI sector (%x)\n",
                    "\texceeds number of data clusters (%x)\n",
                    __FUNCTION__ , boot_fat.free_cluster_hint , (boot_fat.data_sectors >> 3));
        return -1;
    }

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s : free_clusters_nr = %x / free_cluster_hint = %x\n",
                __FUNCTION__ , boot_fat.free_clusters_nr , boot_fat.free_cluster_hint );
#endif

    return 0;

} // set_fsi()

/****************************************************************************
 * This debug function displays the FAT32 File System descriptor content.   *
 ****************************************************************************/

#if DEBUG_BOOT_FAT32    

static void fat32_desc_display()
{
    boot_printf("\n############### FAT32 DESCRIPTOR ####################"
                "\nFAT initialized:                         %x"
                "\nSector size (in bytes):                  %x"
                "\nCluster size (in bytes):                 %x"
                "\nFAT Region LBA:                          %x"
                "\nFAT Region size (in sectors):            %x"
                "\nDATA Region LBA:                         %x"
                "\nDATA Region size (in sectors):           %x"
                "\nCount of free clusters:                  %x"
                "\nMost recently allocated cluster number:  %x"
                "\n#####################################################\n",
                 boot_fat.initialized,
                 boot_fat.sector_size,
                 boot_fat.cluster_size,
                 boot_fat.fat_lba,
                 boot_fat.fat_sectors,
                 boot_fat.data_lba,
                 boot_fat.data_sectors,
                 boot_fat.free_clusters_nr,
                 boot_fat.free_cluster_hint
               );

} // fat32_desc_display()

#endif

/****************************************************************************
 * This function computes the logical block address (LBA) of the data       *
 * cluster whose number is 'cluster_number'. It exits if 'cluster_number'   *
 * value is smaller than 2.                                                 *
 * @ cluster_number : number of the cluster whose LBA is desired.           *
 *                                                                          *
 * @ returns the LBA of the cluster.                                        *
 ***************************************************************************/
static uint32_t cluster_to_lba(uint32_t cluster_number)
{
    /* 
     * The clusters begin their numbering at 2, so there is no cluster #0 
     * or cluster #1. 
     */
    if (cluster_number < 2)
    {
        boot_printf("\n[BOOT ERROR] cluster_to_lba(): "
                    "Cluster number smaller than 2\n");
        boot_exit();
    }

    /* 
     * LBA = boot_fat.data_lba + ((cluster_number - 2) * 
     *       (boot_fat.cluster_size /boot_fat.sector_size));
     */
    return (boot_fat.data_lba + ((cluster_number - 2) << 3));

} // cluster_to_lba()

/****************************************************************************
 * This function directly looks up the FAT to find the entry corresponding  *
 * to 'cur_cluster' and return the value stored in this entry (usually the  *
 * index of the next cluster in the cluster chain).                         *
 * @ cur_cluster    : index of current cluster.                             *
 * @ nxt_cluster    : pointer to the Rbuffer for the next cluster index.    *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************
 * Implementation note
 * There is two versions:
 * - In the "software engineer" version, the FAT is seen as a set of sectors
 *   containing 128 FAT entries each.
 *   + : buffer of only 512 bytes is needed (we only read a sector). 
 *   - : nonetheless, I find it less elegant than the other one:
 *       divisions and multiplications using MULT and DIV instructions are
 *       usually slower.
 * - In the "hardware engineer" version, the FAT is seen as a set of clusters 
 *   containing 1024 FAT entries each.
 *   + : divisions and multiplications are actually performed via SHIFT 
 *       operations, which are much faster on 2s complement architectures.
 *       Personally, I pretty like this "hardware" approach.
 *   - : on current Intel X86 processors, MULT and DIV instructions are
 *       heavily optimized for multiplication and division by powers of 2.
 *       Moreover, since we read a cluster of FAT entries, the buffer needs
 *       to be of 4096 bytes.
 ****************************************************************************/

/*
static int get_next_cluster_soft(uint32_t    cur_cluster,
                                 uint32_t*   nxt_cluster)
{
    uint32_t fat_region_offset;     // Offset of 'cur_cluster' in the
                                        // FAT Region (in bytes)
    uint32_t fat_sec_lba;           // LBA of the FAT sector that 
                                        // contains the entry for
                                        // 'cur_cluster' in the FAT
    uint32_t fat_entry_offset;      // Offset of the entry 
                                        // corresponding to 'cur_cluster' 
                                        // in 'fat_sec_num'

    // Initialize the variables
    fat_region_offset   = cur_cluster * FAT_ENTRY_SIZE;
    fat_sec_lba         = boot_fat.fat_lba +
                          fat_region_offset / boot_fat.sector_size;
    fat_entry_offset    = fat_region_offset % boot_fat.sector_size;

    // Read the FAT sector containing the FAT entry for 'cur_cluster'
    if (buffer_fat_lba != fat_sec_lba)
    {
        if ( fat_ioc_access( fat_sec_lba,
                             (uint32_t)buffer_fat,
                             1 ) )
        {
            boot_printf("\n[BOOT ERROR] get_next_cluster_soft(): "
                        "Cannot load sector of LBA %x into buffer_fat\n",
                        fat_sec_lba);
            return -1;
        }

        buffer_fat_lba = fat_sec_lba;
    }

    // Fetch the content of the entry
    *nxt_cluster = *(uint32_t*)&buffer_fat[fat_entry_offset] & 0x0FFFFFFF;

    // Check the previously read value of the next cluster number
    if ((*nxt_cluster < USED_MIN) || (*nxt_cluster > USED_MAX))
    {
        boot_printf("\n[BOOT ERROR] get_next_cluster_soft(): "
                    "Illegal next cluster number (%x)\n",
                    *nxt_cluster);
        return -1;
    }

    return 0;

} // get_next_cluster_soft()
*/

/////////////////////////////////////////////////////////
static int get_next_cluster_hard(uint32_t    cur_cluster,
                                 uint32_t *  nxt_cluster)
{
    uint32_t fat_cluster;           // Index of cluster containing the FAT entry
    uint32_t fat_cluster_offset;    // Offset of FAT entry in fat_cluster 
    uint32_t fat_cluster_lba;       // LBA for fat_cluster

    // Compute the reqired variables
    fat_cluster         = cur_cluster >> 10;
    fat_cluster_offset  = cur_cluster & 0x3FF;
    fat_cluster_lba     = boot_fat.fat_lba + (fat_cluster << 3);

    // Read the FAT cluster containing the FAT entry if required
    if (buffer_fat_lba != fat_cluster_lba)
    {
        if ( fat_ioc_access( fat_cluster_lba,
                             XPTR( BOOT_CORE_CXY , buffer_fat ),
                             8 ) )
        {
            boot_printf("\n[BOOT ERROR] get_next_cluster_hard(): "
                        "Cannot load cluster of LBA %x into buffer_fat\n",
                        fat_cluster_lba);
            return -1;
        }

        buffer_fat_lba = fat_cluster_lba;
    }

    // returns the FAT entry
    *nxt_cluster = buffer_fat[fat_cluster_offset] & 0x0FFFFFFF;

    return 0;

} // get_next_cluster_hard()

/****************************************************************************
 * This function breaks a 'pathname' pathname into a sequence of path       *
 * components which are separated by the delimiting character "/". Each     *
 * call to the function gets the next path component and places it in the   *
 * buffer pointed to by 'path_component'. The result does not include the   *
 * "/" separator.                                                           *
 * A sequence of calls to the function that operate on the same pathname    *
 * maintains a pointer 'nb_read' that determines the point from which to    *
 * start searching for the next path component.                             *
 * @ pathname       : pathname to be analyzed.                              *
 * @ path_component : pointer to the buffer for a path component.           *
 * @ nb_read        : number of characters already read from the pathname.  *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************/
static int get_path_component(char*      pathname,
                              char*      path_component,
                              uint32_t * nb_read)
{
    uint32_t pathname_offset;   // index used to scan the LFN entry
    uint32_t path_comp_offset;  // index used to write to the buffer

    // Initialize the variables
    pathname_offset     = *nb_read;
    path_comp_offset    = 0;

    // Skip the delimiting character
    if (pathname[pathname_offset] == '/') pathname_offset++;

    // Get a path component
    while ((pathname[pathname_offset] != '/') && 
           (pathname[pathname_offset] != '\0'))
    {
        path_component[path_comp_offset++] = pathname[pathname_offset++];
        if (path_comp_offset > NAME_MAX_SIZE)
        {
            boot_printf("\n[BOOT ERROR] get_path_component(): "
                        "File/directory name is too long\n");
            return -1;
        }
    }

    path_component[path_comp_offset] = '\0';
    
    // Update 'nb_read' for the next path component
    *nb_read = pathname_offset;

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s : returns <%s> from <%s> at cycle %d\n",
                __FUNCTION__ , path_component , pathname , boot_get_proctime() );
#endif

    return 0;

} // get_path_component()

/****************************************************************************
 * This function analyzes a Long File Name entry pointed to by 'lfn_entry'  *
 * to get a portion of a file name and stores it in the temporary buffer    *
 * pointed to by 'lfn_buffer'.                                              *
 * @ lfn_entry  : pointer to a LFN entry.                                   *
 * @ lfn_buffer : pointer to the temporary buffer for a portion of the      *
 *                full long name.                                           *
 ****************************************************************************/
static void get_name_from_long(unsigned char*   lfn_entry,
                               char*            lfn_buffer)
{
    uint32_t entry_offset;      /* Index used to scan the LFN entry.    */
    uint32_t buffer_offset;     /* Index used to write to the buffer.   */
    uint32_t lfn_name1_end;     /* End of the first part of this 
                                       entry name portion.                  */
    uint32_t lfn_name2_end;     /* End of the second part of this 
                                       entry name portion.                  */
    uint32_t lfn_name3_end;     /* End of the third part of this
                                       entry name portion.                  */

    /* Initializing the variables. */
    buffer_offset   = 0;
    entry_offset    = get_offset(LDIR_NAME1);
    lfn_name1_end   = get_offset(LDIR_ATTR);
    lfn_name2_end   = get_offset(LDIR_FSTCLUSLO);
    lfn_name3_end   = DIR_ENTRY_SIZE;
    
    /* Iterating through the first part of this entry name portion. */
    while (entry_offset != lfn_name1_end)
    {
        // If this is the last portion of a file name (file names are also NUL 
        // terminated), we can stop the LFN entry analyzing process.
        if (lfn_entry[entry_offset] == '\0')
            goto exit;

        // Writing to the name buffer.
        lfn_buffer[buffer_offset] = lfn_entry[entry_offset];

        // Preparing variables for the next iteration.
        buffer_offset++;
        entry_offset += 2;
    }

    /* Getting to the next part of the name portion. */
    entry_offset = get_offset(LDIR_NAME2);

    /* Iterating through the second part of this entry name portion. */
    while (entry_offset != lfn_name2_end)
    {
        // If this is the last portion of a file name (file names are also NUL 
        // terminated), we can stop the LFN entry analyzing process.
        if (lfn_entry[entry_offset] == '\0')
            goto exit;

        // Writing to the name buffer.
        lfn_buffer[buffer_offset] = lfn_entry[entry_offset];

        // Preparing variables for the next iteration.
        buffer_offset++;
        entry_offset += 2;
    }

    /* Getting to the next part of the name portion. */
    entry_offset = get_offset(LDIR_NAME3);

    /* Iterating through the last part of this entry name portion. */
    while (entry_offset != lfn_name3_end)
    {
        // If this is the last portion of a file name (file names are also NUL 
        // terminated), we can stop the LFN entry analyzing process.
        if (lfn_entry[entry_offset] == '\0')
            break;

        // Writing to the name buffer.
        lfn_buffer[buffer_offset] = lfn_entry[entry_offset];

        // Preparing variables for the next iteration.
        buffer_offset++;
        entry_offset += 2;
    }

exit:

    /* Appending the trailing NUL to the buffer. */
    lfn_buffer[buffer_offset] = '\0';           

} // get_name_from_long()

/****************************************************************************
 * This function analyzes a standard 8.3 entry pointed to by 'entry' to     *
 * get the name of the file/directory corresponding to this entry and       *
 * stores it in the buffer pointed to by 'buffer'.                          *
 * @ entry  : pointer to a standard 8.3 entry.                              *
 * @ buffer : pointer to the buffer for the entry name.                     *
 ****************************************************************************/
static void get_name_from_short(unsigned char*  entry,
                                char*           buffer)
{
    uint32_t entry_offset;      /* Index used to scan the 8.3 entry.    */
    uint32_t buffer_offset;     /* Index used to write to the buffer.   */

    /* Initializing the variables. */
    entry_offset    = 0;
    buffer_offset   = 0;

    /* Getting the file name. */
    while ((entry_offset < 8) && (entry[entry_offset] != ' '))
        buffer[buffer_offset++] = boot_to_lower(entry[entry_offset++]);
    
    /* Appending the dot to the name buffer. */
    buffer[buffer_offset++] = '.';

    /* Getting the file extension. */
    while ((entry_offset < 11) && (entry[entry_offset] != ' '))
        buffer[buffer_offset++] = boot_to_lower(entry[entry_offset++]);
    
    /* Appending the trailing NUL to the buffer. */
    buffer[buffer_offset++] = '\0';
        
} // get_name_from_short()

/****************************************************************************
 * This function searches for the a file identifid by its pathname.         *
 * It returns the first cluster index and the file size.                    *
 * @ pathname       : searched file pathname.                               *
 * @ first_cluster  : pointer to the first cluster index                    *
 * @ file_size      : pointer to the file size.                             *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************/
static int fat_file_search(char*     pathname,
                           uint32_t* first_cluster,
                           uint32_t* file_size)
{
    char       path_comp[PATH_MAX_SIZE];    // Buffer for a path component
    char       buffer_lfn[16];              // Buffer for a portion of the LFN
    char       name[NAME_MAX_SIZE];         // Buffer for a full name
    uint32_t   nb_read;                     // Number of characters already read 
    uint32_t   parent_cluster;              // Cluster of the parent directory
    uint32_t   next_cluster;                // Next cluster number
    uint32_t   child_cluster;               // Cluster of searched file/directory
    uint32_t   child_size;                  // Size of searched file/directory
    uint32_t   child_is_dir;                // Type of searched file/directory  
    uint32_t   cluster_lba;                 // LBA of current cluster
    uint32_t   offset;                      // Offset in cluster buffer
    uint32_t   ord;                         // First byte of a directory entry
    uint32_t   attr;                        // Attribute of a directory entry
    uint32_t   lfn_seq_elem_nr;             // Number of elements in a LFN
    uint32_t   lfn_seq_order;               // Order of this entry in LFN

    uint32_t   found;             

    unsigned char* entry;                

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s enters for <%s> file at cycle %d\n",
                __FUNCTION__ , pathname, boot_get_proctime());
#endif

    // Initialize some variables before getting into the search loop
    nb_read         = 0;
    child_cluster   = 0;
    child_size      = 0;
    child_is_dir    = 0;
    parent_cluster  = boot_fat.root_cluster;

    // this first loop is on components in the pathname
    while ( pathname[nb_read] != '\0' )
    {
        // Parse the file pathname.
        if ( get_path_component( pathname, path_comp, &nb_read) ) return -1;
        
        // scan one directory for one component in pathname
        // this second loop is on clusters  
        // (found = 1 if success / found = 2 if failure)
        found = 0;
        while ( found == 0 )
        {
            cluster_lba = cluster_to_lba( parent_cluster );
            
            // Load the cluster containing the parent directory
            if (buffer_dir_lba != cluster_lba)
            {
                if ( fat_ioc_access( cluster_lba,
                                     XPTR( BOOT_CORE_CXY , buffer_dir ),
                                     boot_fat.cluster_size / boot_fat.sector_size ) ) 
                {
                    boot_printf("\n[BOOT ERROR] %s: Cannot load cluster at lba %x\n", 
                                __FUNCTION__ , cluster_lba);
                    return -1;
                }

                buffer_dir_lba = cluster_lba;
            }

            // this third loop is on entries in this cluster
            for ( offset = 0, lfn_seq_elem_nr = 0;
                 (offset < boot_fat.cluster_size) && (found == 0);
                  offset += DIR_ENTRY_SIZE)
            {
                entry = buffer_dir + offset;
                ord   = read_field(LDIR_ORD, entry, 1);
                attr  = read_field(DIR_ATTR, entry, 1);

                if (ord == LAST_ENTRY)             // no more entry in this directory
                {
                    found = 2;
                }

                else if (ord == FREE_ENTRY)        // unused, check the next entry
                {
                    continue;
                }

                else if (attr == ATTR_LONG_NAME)   // LFN entry
                {
                    // Get the order of this entry in the long file name
                    // as well as its number of elements.
                    lfn_seq_order   = ord & 0x3F;
                    lfn_seq_elem_nr = (ord & LAST_LONG_ENTRY) ?
                                      lfn_seq_order           :
                                      lfn_seq_elem_nr;

                    // Load the portion of the long file name into temporary buffer
                    get_name_from_long(entry, buffer_lfn);

                    // Append this portion of the name to the full name buffer
                    boot_strcpy(name + 13 * (lfn_seq_order-1) , buffer_lfn);

                    // Append the trailing NUL if last LFN entry
                    if (lfn_seq_order == lfn_seq_elem_nr)
                        name[13 * (lfn_seq_order-1) + boot_strlen(buffer_lfn)] = '\0';
                }
                else                              // Normal entry (standard 8.3 entry)
                {
                    if (lfn_seq_elem_nr == 0) get_name_from_short(entry, name);

                    // check if the full name is what we are looking for.
                    if (boot_strcmp(name, path_comp) == 0)
                    {
                        found = 1;

                        // Get the first cluster for this entry.
                        child_cluster = (read_field(DIR_FSTCLUSHI, entry, 1) << 16) |
                                        (read_field(DIR_FSTCLUSLO, entry, 1));

                        // Test if this entry is a directory.
                        child_is_dir  = (attr & ATTR_DIRECTORY);
    
                        // Get its size.
                        child_size    = read_field(DIR_FILESIZE, entry, 1);
                    }
                    
                    // Reset lfn_seq_elem_nr for the next LFN
                    lfn_seq_elem_nr = 0;
                }
            }  // end loop on entries in current cluster

            // Compute next cluster index if not found in current cluster
            if ( found == 0 )
            {
                if ( get_next_cluster_hard( parent_cluster, &next_cluster ) )
                {
                    boot_printf("\n[BOOT ERROR] %s: Cannot get next cluster for cluster %x\n", 
                                __FUNCTION__ , parent_cluster );
                    return -1;
                }

                parent_cluster = next_cluster;
            }

        } // end second while for one component in pathname
        
        // Check the result of this path component search.
        if (found == 2)
        {
            boot_printf("\n[BOOT ERROR] %s: <%s> not found\n",
                        path_comp);
            return -1;
        }

        // check type for each pathname component
        if (((pathname[nb_read] == '\0') && (child_is_dir != 0)) ||
            ((pathname[nb_read] != '\0') && (child_is_dir == 0)))
        {
            boot_printf("\n[BOOT ERROR] %s: Illegal type for <%s>"
                        "  nb_read = %d / last_char = %x / child_is_dir = %x\n",
                        path_comp , nb_read , pathname[nb_read] , child_is_dir );
            return -1;
        }

        // prepare for the next iteration.
        parent_cluster = child_cluster;

    }  // end first while on the complete pathname

    // return file information 
    *first_cluster  = child_cluster;
    *file_size      = child_size;

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s : <%s> file found at cycle %d\n"
                "    fat_cluster = %x / size = %x\n",
                __FUNCTION__ , pathname , boot_get_proctime() , *first_cluster , *file_size );
#endif

    return 0;

} // fat_file_search()

/****************************************************************************
 *                               API functions.                             *
 ****************************************************************************/

/////////////////////
int boot_fat32_init()
{
    // FAT32 initialization should be done only once
    if (boot_fat.initialized == FAT_MAGIC_VALUE)
    {
        boot_printf("\n[BOOT WARNING] %s: FAT32 already initialized\n",
                   __FUNCTION__ );
        return 0;
    }

#if DEBUG_BOOT_FAT32    
boot_printf("\n[BOOT INFO] %s: Enters at cycle %d\n",
            __FUNCTION__ , boot_get_proctime() );
#endif

    // Load Boot Sector (VBR) into FAT buffer
    if ( fat_ioc_access( 0, 
                         XPTR( BOOT_CORE_CXY , boot_fat.block_buffer ), 
                         1 ) )
    {
        boot_printf("\n[BOOT ERROR] %s: Cannot load VBR\n",
                    __FUNCTION__ );
        return -1;
    }
    boot_fat.block_buffer_lba = 0;

#if DEBUG_BOOT_FAT32    
boot_printf("\n[BOOT INFO] %s: Boot Sector loaded at cycle %d\n",
            __FUNCTION__ , boot_get_proctime() );
#endif

    // Check assumptions on the Boot Sector
    uint32_t bytes_per_sector = read_field( BPB_BYTSPERSEC, boot_fat.block_buffer, 1 );
    if ( bytes_per_sector != 512 ) 
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): sector size = %x / must be Ox200\n",
                    bytes_per_sector );
        return -1;
    }

    uint32_t sectors_per_cluster = read_field(BPB_SECPERCLUS, boot_fat.block_buffer, 1);
    if ( sectors_per_cluster != 8 )
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): Cluster size = %d / must be 8 sectors \n");
        return -1;
    }

    uint32_t nb_fat_copies = read_field(BPB_NUMFATS, boot_fat.block_buffer, 1);
    if ( nb_fat_copies != 1 )
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): number of FAT copies must be 1 \n");
        return -1;
    }

    uint32_t nb_fat_sectors = read_field(BPB_FATSZ32, boot_fat.block_buffer, 1);
    if ( (nb_fat_sectors & 0xF) != 0 )
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): FAT size must be multiple of 16 sectors\n");
        return -1;
    }

    uint32_t root_cluster = read_field(BPB_ROOTCLUS, boot_fat.block_buffer, 1);
    if ( root_cluster != 2 )
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): Root directory must be at cluster #2\n");
        return -1;
    }

    uint32_t fs_info_sector = read_field(BPB_FSINFO, boot_fat.block_buffer, 1);
    if ( fs_info_sector != 1 )
    {
        boot_printf("\n[BOOT ERROR] boot_fat32_init(): FS Information Sector must be 1\n");
        return -1;
    }

    uint32_t reserved_sectors = read_field(BPB_RSVDSECCNT, boot_fat.block_buffer, 1);

    uint32_t nb_total_sectors = read_field(BPB_TOTSEC32, boot_fat.block_buffer, 1);

    // Initialize FAT32  descriptor from Boot Sector
    boot_fat.sector_size    = bytes_per_sector;
    boot_fat.cluster_size   = bytes_per_sector * sectors_per_cluster;
    boot_fat.fat_sectors    = nb_fat_sectors;
    boot_fat.fat_lba        = reserved_sectors;
    boot_fat.data_sectors   = nb_total_sectors - (nb_fat_sectors + reserved_sectors);
    boot_fat.data_lba       = nb_fat_sectors + boot_fat.fat_lba;
    boot_fat.root_cluster   = root_cluster;
    boot_fat.fsi_lba        = fs_info_sector;
    boot_fat.initialized    = FAT_MAGIC_VALUE;

    // Set information from FS Information Sector
    if (set_fsi()) return -1;

    // Initialize FAT and DIR buffers
    buffer_fat_lba = 0xFFFFFFFF;
    buffer_dir_lba = 0xFFFFFFFF;

#if DEBUG_BOOT_FAT32    
    fat32_desc_display();
    boot_printf("\n[BOOT INFO] %s : FAT32 File System initialized at cycle %d\n",
                __FUNCTION__ , boot_get_proctime() );
#endif

    return 0;

} // boot_fat32_init()

///////////////////////////////////////
int boot_fat32_load( char*    pathname, 
                     uint32_t buff_addr, 
                     uint32_t buff_size )
{
    uint32_t cur_cluster;
    uint32_t nxt_cluster;
    uint32_t size;    
    uint32_t nb_clusters;
    uint32_t buff_offset;
    uint32_t cluster_lba;

    // Checking FAT32 initialization
    if (boot_fat.initialized != FAT_MAGIC_VALUE)
    {
        boot_printf("\n[BOOT ERROR] %s: FAT not initialized\n",
                    __FUNCTION__ );
        return -1;
    }

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s enters for file <%s> at cycle %d\n",
                __FUNCTION__ , pathname, boot_get_proctime() );
#endif

    // Search file
    if (fat_file_search(pathname, 
                        &cur_cluster,
                        &size))
    {
        boot_printf("\n[BOOT ERROR] in %s : File <%s> not found\n", 
                    __FUNCTION__ , pathname);
        return -1;
    }
    
    /* Checking buffer size. */
    if (size > buff_size)
    {
        boot_printf("\n[BOOT ERROR] in %s : file <%s> is too large (%x bytes) / "
                    "buffer size = %x bytes\n",
                    __FUNCTION__ , pathname , size , buff_size );
        return -1;
    }
    
    /* Computing number of clusters to read. */
    // nb_clusters = size / boot_fat.cluster_size
    nb_clusters = size >> 12;

    // if ((size % boot_fat.cluster_size) != 0)
    if (size & 0xFFF)
        nb_clusters++;

    /* Following the cluster chains in the FAT. */
    buff_offset = buff_addr;
    while (nb_clusters > 0)
    {
        cluster_lba = cluster_to_lba(cur_cluster);

        /* Loading the current cluster. */
        if ( fat_ioc_access( cluster_lba,
                             XPTR( BOOT_CORE_CXY , buff_offset ),
                             boot_fat.cluster_size / boot_fat.sector_size ) )
        {
            boot_printf("\n[BOOT ERROR] in %s : cannot load cluster at LBA %x\n", 
                        __FUNCTION__ , cluster_lba );
            return -1;
        }
        
        /* Computing next cluster number. */
        if ( get_next_cluster_hard( cur_cluster , &nxt_cluster ) )
        {
            boot_printf("\n[BOOT ERROR] in %s : cannot get next cluster for cluster %x\n", 
                        __FUNCTION__ , cur_cluster );
            return -1;
        }
        
        /* Getting prepared for the next iteration. */
        nb_clusters--;
        buff_offset += boot_fat.cluster_size;
        cur_cluster    = nxt_cluster;
    }

#if DEBUG_BOOT_FAT32    
    boot_printf("\n[BOOT INFO] %s : file <%s> loaded at cycle %d\n"
                "    address = %x , size = %x\n",
                __FUNCTION__ , pathname , boot_get_proctime() , buff_addr , size );
#endif

    return 0;

} // boot_fat32_load()
