/*             ----> DO NOT REMOVE THE FOLLOWING NOTICE <----
 *
 *                 Copyright (c) 2014-2015 Datalight, Inc.
 *                     All Rights Reserved Worldwide.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; use version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*  Businesses and individuals that for commercial or other reasons cannot
 *  comply with the terms of the GPLv2 license may obtain a commercial license
 *  before incorporating Reliance Edge into proprietary software for
 *  distribution in any form.  Visit http://www.datalight.com/reliance-edge for
 *  more information.
 */

/** @file
 *  @brief Implements block device I/O.
 */
#include <FreeRTOS.h>

#include <redfs.h>
#include <redvolume.h>
#include <redosdeviations.h>


/*------------------------------------------------------------------------------
 *   Porting Note:
 *
 *   Several example implementations of this module for FreeRTOS are available.
 *   If you are lucky, you can use one of these implementations; otherwise, these
 *   can serve as examples of how to implement this service.
 *  ------------------------------------------------------------------------------*/

/** @brief The F_DRIVER example implementation.
 *
 *  This implementation is designed to reuse an existing block device driver
 *  that was written for FreeRTOS+FAT SL.  If you have such a driver, with
 *  little work it can be "dropped in" and used for Reliance Edge.  The only
 *  customization required is that gpfnRedOsBDevInit needs to be defined and
 *  pointed at the F_DRIVERINIT function.  This can be done in this module or in
 *  another C file.
 *
 *  The disadvantage of using the FreeRTOS F_DRIVER functions is that they only
 *  support single-sector reads and writes.  Reliance Edge will issue
 *  multi-sector requests, and servicing these one sector at a time will
 *  significantly slow down the file system.
 */
#define BDEV_F_DRIVER                  ( 0U )

/** @brief The FatFs example implementation.
 *
 *  This implementation is designed to reuse an existing block device driver
 *  that was written for FatFs.  If you have such a driver, it can be linked
 *  in and used immediately.  The FatFs `diskio.h` header must be in the include
 *  directory path.
 */
#define BDEV_FATFS                     ( 1U )

/** @brief The Atmel Studio Framework SD/MMC driver example implementation.
 *
 *  This implementation uses a modified version of the open source SD/MMC driver
 *  included in the Atmel Studio Framework (ASF) and will work as-is for many
 *  varieties of Atmel hardware.  This example assumes relatively minor
 *  modifications to the ASF SD/MMC driver to make it support multi-sector read
 *  and write requests, which greatly improves performance.  The modified driver
 *  is distributed with Reliance Edge and is included in FreeRTOS Atmel projects
 *  (such as in projects/freertos/atmel/sam4e-ek/src/ASF).
 *
 *  This example can easily be modified to work with an unmodified version of
 *  the ASF SD/MMC driver.  Simply replace sd_mmc_mem_2_ram_multi() and
 *  sd_mmc_ram_2_mem_multi() with sd_mmc_mem_2_ram() and sd_mmc_ram_2_mem()
 *  respectively, and add a for loop to loop over each sector in the request.
 *  However, as described in the manual, there are considerable performance
 *  advantages to issuing real multi-sector requests, so using the modified
 *  driver is recommended.
 */
#define BDEV_ATMEL_SDMMC               ( 2U )

/** @brief The ST Microelectronics STM32 SDIO driver example implementation.
 *
 *  This implementation accesses the microSD card through the BSP utilities
 *  provided as part of the STM32Cube package, used with the STM32 HAL drivers.
 *  The STM3240G-EVAL and STM32F746NG-Discovery boards are currently supported.
 */
#define BDEV_STM32_SDIO                ( 3U )

/** @brief The RAM disk example implementation.
 *
 *  This implementation uses a RAM disk.  It will allow you to compile and test
 *  Reliance Edge even if your storage driver is not yet ready.  On typical
 *  target hardware, the amount of spare RAM will be limited so generally only
 *  very small disks will be available.
 */
#define BDEV_RAM_DISK                  ( 4U )

/** @brief Pick which example implementation is compiled.
 *
 *  Must be one of:
 *  - #BDEV_F_DRIVER
 *  - #BDEV_FATFS
 *  - #BDEV_ATMEL_SDMMC
 *  - #BDEV_STM32_SDIO
 *  - #BDEV_RAM_DISK
 */
#define BDEV_EXAMPLE_IMPLEMENTATION    BDEV_RAM_DISK


static REDSTATUS DiskOpen( uint8_t bVolNum,
                           BDEVOPENMODE mode );
static REDSTATUS DiskClose( uint8_t bVolNum );
static REDSTATUS DiskRead( uint8_t bVolNum,
                           uint64_t ullSectorStart,
                           uint32_t ulSectorCount,
                           void * pBuffer );
#if REDCONF_READ_ONLY == 0
    static REDSTATUS DiskWrite( uint8_t bVolNum,
                                uint64_t ullSectorStart,
                                uint32_t ulSectorCount,
                                const void * pBuffer );
    static REDSTATUS DiskFlush( uint8_t bVolNum );
#endif


/** @brief Initialize a block device.
 *
 *  This function is called when the file system needs access to a block
 *  device.
 *
 *  Upon successful return, the block device should be fully initialized and
 *  ready to service read/write/flush/close requests.
 *
 *  The behavior of calling this function on a block device which is already
 *  open is undefined.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
REDSTATUS RedOsBDevOpen( uint8_t bVolNum,
                         BDEVOPENMODE mode )
{
    REDSTATUS ret;

    if( bVolNum >= REDCONF_VOLUME_COUNT )
    {
        ret = -RED_EINVAL;
    }
    else
    {
        ret = DiskOpen( bVolNum, mode );
    }

    return ret;
}


/** @brief Uninitialize a block device.
 *
 *  This function is called when the file system no longer needs access to a
 *  block device.  If any resource were allocated by RedOsBDevOpen() to service
 *  block device requests, they should be freed at this time.
 *
 *  Upon successful return, the block device must be in such a state that it
 *  can be opened again.
 *
 *  The behavior of calling this function on a block device which is already
 *  closed is undefined.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number.
 */
REDSTATUS RedOsBDevClose( uint8_t bVolNum )
{
    REDSTATUS ret;

    if( bVolNum >= REDCONF_VOLUME_COUNT )
    {
        ret = -RED_EINVAL;
    }
    else
    {
        ret = DiskClose( bVolNum );
    }

    return ret;
}


/** @brief Read sectors from a physical block device.
 *
 *  The behavior of calling this function is undefined if the block device is
 *  closed or if it was opened with ::BDEV_O_WRONLY.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number, @p pBuffer is
 *                      `NULL`, or @p ullStartSector and/or @p ulSectorCount
 *                      refer to an invalid range of sectors.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
REDSTATUS RedOsBDevRead( uint8_t bVolNum,
                         uint64_t ullSectorStart,
                         uint32_t ulSectorCount,
                         void * pBuffer )
{
    REDSTATUS ret = 0;

    if( ( bVolNum >= REDCONF_VOLUME_COUNT ) ||
        ( ullSectorStart >= gaRedVolConf[ bVolNum ].ullSectorCount ) ||
        ( ( gaRedVolConf[ bVolNum ].ullSectorCount - ullSectorStart ) < ulSectorCount ) ||
        ( pBuffer == NULL ) )
    {
        ret = -RED_EINVAL;
    }
    else
    {
        ret = DiskRead( bVolNum, ullSectorStart, ulSectorCount, pBuffer );
    }

    return ret;
}


#if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a physical block device.
 *
 *  The behavior of calling this function is undefined if the block device is
 *  closed or if it was opened with ::BDEV_O_RDONLY.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number, @p pBuffer is
 *                      `NULL`, or @p ullStartSector and/or @p ulSectorCount
 *                      refer to an invalid range of sectors.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    REDSTATUS RedOsBDevWrite( uint8_t bVolNum,
                              uint64_t ullSectorStart,
                              uint32_t ulSectorCount,
                              const void * pBuffer )
    {
        REDSTATUS ret = 0;

        if( ( bVolNum >= REDCONF_VOLUME_COUNT ) ||
            ( ullSectorStart >= gaRedVolConf[ bVolNum ].ullSectorCount ) ||
            ( ( gaRedVolConf[ bVolNum ].ullSectorCount - ullSectorStart ) < ulSectorCount ) ||
            ( pBuffer == NULL ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            ret = DiskWrite( bVolNum, ullSectorStart, ulSectorCount, pBuffer );
        }

        return ret;
    }


/** @brief Flush any caches beneath the file system.
 *
 *  This function must synchronously flush all software and hardware caches
 *  beneath the file system, ensuring that all sectors written previously are
 *  committed to permanent storage.
 *
 *  If the environment has no caching beneath the file system, the
 *  implementation of this function can do nothing and return success.
 *
 *  The behavior of calling this function is undefined if the block device is
 *  closed or if it was opened with ::BDEV_O_RDONLY.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    REDSTATUS RedOsBDevFlush( uint8_t bVolNum )
    {
        REDSTATUS ret;

        if( bVolNum >= REDCONF_VOLUME_COUNT )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            ret = DiskFlush( bVolNum );
        }

        return ret;
    }
#endif /* REDCONF_READ_ONLY == 0 */


#if BDEV_EXAMPLE_IMPLEMENTATION == BDEV_F_DRIVER

    #include <api_mdriver.h>


/*  This must be declared and initialized elsewere (e.g., in project code) to
 *  point at the initialization function for the F_DRIVER block device.
 */
    extern const F_DRIVERINIT gpfnRedOsBDevInit;

    static F_DRIVER * gapFDriver[ REDCONF_VOLUME_COUNT ];


/** @brief Initialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskOpen( uint8_t bVolNum,
                               BDEVOPENMODE mode )
    {
        REDSTATUS ret;

        ( void ) mode;

        if( ( gpfnRedOsBDevInit == NULL ) || ( gapFDriver[ bVolNum ] != NULL ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            F_DRIVER * pDriver;

            pDriver = gpfnRedOsBDevInit( bVolNum );

            if( pDriver != NULL )
            {
                F_PHY geom;
                int iErr;

                /*  Validate that the geometry is consistent with the volume
                 *  configuration.
                 */
                iErr = pDriver->getphy( pDriver, &geom );

                if( iErr == 0 )
                {
                    if( ( geom.bytes_per_sector != gaRedVolConf[ bVolNum ].ulSectorSize ) ||
                        ( geom.number_of_sectors < gaRedVolConf[ bVolNum ].ullSectorCount ) )
                    {
                        ret = -RED_EINVAL;
                    }
                    else
                    {
                        gapFDriver[ bVolNum ] = pDriver;
                        ret = 0;
                    }
                }
                else
                {
                    ret = -RED_EIO;
                }

                if( ret != 0 )
                {
                    pDriver->release( pDriver );
                }
            }
            else
            {
                ret = -RED_EIO;
            }
        }

        return ret;
    }


/** @brief Uninitialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskClose( uint8_t bVolNum )
    {
        REDSTATUS ret;

        if( gapFDriver[ bVolNum ] == NULL )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            gapFDriver[ bVolNum ]->release( gapFDriver[ bVolNum ] );
            gapFDriver[ bVolNum ] = NULL;

            ret = 0;
        }

        return ret;
    }


/** @brief Read sectors from a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskRead( uint8_t bVolNum,
                               uint64_t ullSectorStart,
                               uint32_t ulSectorCount,
                               void * pBuffer )
    {
        REDSTATUS ret = 0;
        F_DRIVER * pDriver = gapFDriver[ bVolNum ];

        if( pDriver == NULL )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );
            uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
            uint32_t ulSectorIdx;
            int iErr;

            for( ulSectorIdx = 0U; ulSectorIdx < ulSectorCount; ulSectorIdx++ )
            {
                iErr = pDriver->readsector( pDriver, &pbBuffer[ ulSectorIdx * ulSectorSize ],
                                            CAST_ULONG( ullSectorStart + ulSectorIdx ) );

                if( iErr != 0 )
                {
                    ret = -RED_EIO;
                    break;
                }
            }
        }

        return ret;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL The block device is not open.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
        static REDSTATUS DiskWrite( uint8_t bVolNum,
                                    uint64_t ullSectorStart,
                                    uint32_t ulSectorCount,
                                    const void * pBuffer )
        {
            REDSTATUS ret = 0;
            F_DRIVER * pDriver = gapFDriver[ bVolNum ];

            if( pDriver == NULL )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );
                uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
                uint32_t ulSectorIdx;
                int iErr;

                for( ulSectorIdx = 0U; ulSectorIdx < ulSectorCount; ulSectorIdx++ )
                {
                    /*  We have to cast pbBuffer to non-const since the writesector
                     *  prototype is flawed, using a non-const pointer for the buffer.
                     */
                    iErr = pDriver->writesector( pDriver, CAST_AWAY_CONST( uint8_t, &pbBuffer[ ulSectorIdx * ulSectorSize ] ),
                                                 CAST_ULONG( ullSectorStart + ulSectorIdx ) );

                    if( iErr != 0 )
                    {
                        ret = -RED_EIO;
                        break;
                    }
                }
            }

            return ret;
        }


/** @brief Flush any caches beneath the file system.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskFlush( uint8_t bVolNum )
        {
            REDSTATUS ret;

            if( gapFDriver[ bVolNum ] == NULL )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                /*  The F_DRIVER interface does not include a flush function, so to be
                 *  reliable the F_DRIVER implementation must use synchronous writes.
                 */
                ret = 0;
            }

            return ret;
        }
    #endif /* REDCONF_READ_ONLY == 0 */


#elif BDEV_EXAMPLE_IMPLEMENTATION == BDEV_FATFS

    #include <task.h>
    #include <diskio.h>

/*  disk_read() and disk_write() use an unsigned 8-bit value to specify the
 *  sector count, so no transfer can be larger than 255 sectors.
 */
    #define MAX_SECTOR_TRANSFER    UINT8_MAX


/** @brief Initialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskOpen( uint8_t bVolNum,
                               BDEVOPENMODE mode )
    {
        DSTATUS status;
        uint32_t ulTries;
        REDSTATUS ret = 0;

        /*  With some implementations of disk_initialize(), such as the one
         *  implemented by Atmel for the ASF, the first time the disk is opened, the
         *  SD card can take a while to get ready, in which time disk_initialize()
         *  returns an error.  Try numerous times, waiting half a second after each
         *  failure.  Empirically, this has been observed to succeed on the second
         *  try, so trying 10x more than that provides a margin of error.
         */
        for( ulTries = 0U; ulTries < 20U; ulTries++ )
        {
            /*  Assuming that the volume number is also the correct drive number.
             *  If this is not the case in your environment, a static constant array
             *  can be declared to map volume numbers to the correct driver number.
             */
            status = disk_initialize( bVolNum );

            if( status == 0 )
            {
                break;
            }

            vTaskDelay( 500U / portTICK_PERIOD_MS );
        }

        if( status != 0 )
        {
            ret = -RED_EIO;
        }

        /*  Retrieve the sector size and sector count to ensure they are compatible
         *  with our compile-time geometry.
         */
        if( ret == 0 )
        {
            WORD wSectorSize;
            DWORD dwSectorCount;
            DRESULT result;

            result = disk_ioctl( bVolNum, GET_SECTOR_SIZE, &wSectorSize );

            if( result == RES_OK )
            {
                result = disk_ioctl( bVolNum, GET_SECTOR_COUNT, &dwSectorCount );

                if( result == RES_OK )
                {
                    if( ( wSectorSize != gaRedVolConf[ bVolNum ].ulSectorSize ) ||
                        ( dwSectorCount < gaRedVolConf[ bVolNum ].ullSectorCount ) )
                    {
                        ret = -RED_EINVAL;
                    }
                }
                else
                {
                    ret = -RED_EIO;
                }
            }
            else
            {
                ret = -RED_EIO;
            }
        }

        return ret;
    }


/** @brief Uninitialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskClose( uint8_t bVolNum )
    {
        ( void ) bVolNum;
        return 0;
    }


/** @brief Read sectors from a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskRead( uint8_t bVolNum,
                               uint64_t ullSectorStart,
                               uint32_t ulSectorCount,
                               void * pBuffer )
    {
        REDSTATUS ret = 0;
        uint32_t ulSectorIdx = 0U;
        uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
        uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );

        while( ulSectorIdx < ulSectorCount )
        {
            uint32_t ulTransfer = REDMIN( ulSectorCount - ulSectorIdx, MAX_SECTOR_TRANSFER );
            DRESULT result;

            result = disk_read( bVolNum, &pbBuffer[ ulSectorIdx * ulSectorSize ], ( DWORD ) ( ullSectorStart + ulSectorIdx ), ( BYTE ) ulTransfer );

            if( result != RES_OK )
            {
                ret = -RED_EIO;
                break;
            }

            ulSectorIdx += ulTransfer;
        }

        return ret;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
        static REDSTATUS DiskWrite( uint8_t bVolNum,
                                    uint64_t ullSectorStart,
                                    uint32_t ulSectorCount,
                                    const void * pBuffer )
        {
            REDSTATUS ret = 0;
            uint32_t ulSectorIdx = 0U;
            uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
            const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );

            while( ulSectorIdx < ulSectorCount )
            {
                uint32_t ulTransfer = REDMIN( ulSectorCount - ulSectorIdx, MAX_SECTOR_TRANSFER );
                DRESULT result;

                result = disk_write( bVolNum, &pbBuffer[ ulSectorIdx * ulSectorSize ], ( DWORD ) ( ullSectorStart + ulSectorIdx ), ( BYTE ) ulTransfer );

                if( result != RES_OK )
                {
                    ret = -RED_EIO;
                    break;
                }

                ulSectorIdx += ulTransfer;
            }

            return ret;
        }


/** @brief Flush any caches beneath the file system.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskFlush( uint8_t bVolNum )
        {
            REDSTATUS ret;
            DRESULT result;

            result = disk_ioctl( bVolNum, CTRL_SYNC, NULL );

            if( result == RES_OK )
            {
                ret = 0;
            }
            else
            {
                ret = -RED_EIO;
            }

            return ret;
        }
    #endif /* REDCONF_READ_ONLY == 0 */


#elif BDEV_EXAMPLE_IMPLEMENTATION == BDEV_ATMEL_SDMMC

    #include <task.h>

    #include <conf_sd_mmc.h>
    #include <sd_mmc.h>
    #include <sd_mmc_mem.h>
    #include <ctrl_access.h>

/*  sd_mmc_mem_2_ram_multi() and sd_mmc_ram_2_mem_multi() use an unsigned
 *  16-bit value to specify the sector count, so no transfer can be larger
 *  than UINT16_MAX sectors.
 */
    #define MAX_SECTOR_TRANSFER    UINT16_MAX


/** @brief Initialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EROFS  The device is read-only media and write access was
 *                      requested.
 */
    static REDSTATUS DiskOpen( uint8_t bVolNum,
                               BDEVOPENMODE mode )
    {
        REDSTATUS ret = 0;
        uint32_t ulTries;
        Ctrl_status cs;

        /*  Note: Assuming the volume number is the same as the SD card slot.  The
         *  ASF SD/MMC driver supports two SD slots.  This implementation will need
         *  to be modified if multiple volumes share a single SD card.
         */

        /*  The first time the disk is opened, the SD card can take a while to get
         *  ready, in which time sd_mmc_test_unit_ready() returns either CTRL_BUSY
         *  or CTRL_NO_PRESENT.  Try numerous times, waiting half a second after
         *  each failure.  Empirically, this has been observed to succeed on the
         *  second try, so trying 10x more than that provides a margin of error.
         */
        for( ulTries = 0U; ulTries < 20U; ulTries++ )
        {
            cs = sd_mmc_test_unit_ready( bVolNum );

            if( ( cs != CTRL_NO_PRESENT ) && ( cs != CTRL_BUSY ) )
            {
                break;
            }

            vTaskDelay( 500U / portTICK_PERIOD_MS );
        }

        if( cs == CTRL_GOOD )
        {
            #if REDCONF_READ_ONLY == 0
                if( mode != BDEV_O_RDONLY )
                {
                    if( sd_mmc_wr_protect( bVolNum ) )
                    {
                        ret = -RED_EROFS;
                    }
                }

                if( ret == 0 )
            #endif
            {
                uint32_t ulSectorLast;

                IGNORE_ERRORS( sd_mmc_read_capacity( bVolNum, &ulSectorLast ) );

                /*  The ASF SD/MMC driver only supports 512-byte sectors.
                 */
                if( ( gaRedVolConf[ bVolNum ].ulSectorSize != 512U ) ||
                    ( ( ( uint64_t ) ulSectorLast + 1U ) < gaRedVolConf[ bVolNum ].ullSectorCount ) )
                {
                    ret = -RED_EINVAL;
                }
            }
        }
        else
        {
            ret = -RED_EIO;
        }

        return ret;
    }


/** @brief Uninitialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskClose( uint8_t bVolNum )
    {
        ( void ) bVolNum;
        return 0;
    }


/** @brief Read sectors from a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskRead( uint8_t bVolNum,
                               uint64_t ullSectorStart,
                               uint32_t ulSectorCount,
                               void * pBuffer )
    {
        REDSTATUS ret = 0;
        uint32_t ulSectorIdx = 0U;
        uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
        uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );

        while( ulSectorIdx < ulSectorCount )
        {
            uint32_t ulTransfer = REDMIN( ulSectorCount - ulSectorIdx, MAX_SECTOR_TRANSFER );
            Ctrl_status cs;

            cs = sd_mmc_mem_2_ram_multi( bVolNum, ( uint32_t ) ( ullSectorStart + ulSectorIdx ),
                                         ( uint16_t ) ulTransfer, &pbBuffer[ ulSectorIdx * ulSectorSize ] );

            if( cs != CTRL_GOOD )
            {
                ret = -RED_EIO;
                break;
            }

            ulSectorIdx += ulTransfer;
        }

        return ret;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskWrite( uint8_t bVolNum,
                                    uint64_t ullSectorStart,
                                    uint32_t ulSectorCount,
                                    const void * pBuffer )
        {
            REDSTATUS ret = 0;
            uint32_t ulSectorIdx = 0U;
            uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
            const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );

            while( ulSectorIdx < ulSectorCount )
            {
                uint32_t ulTransfer = REDMIN( ulSectorCount - ulSectorIdx, MAX_SECTOR_TRANSFER );
                Ctrl_status cs;

                cs = sd_mmc_ram_2_mem_multi( bVolNum, ( uint32_t ) ( ullSectorStart + ulSectorIdx ),
                                             ( uint16_t ) ulTransfer, &pbBuffer[ ulSectorIdx * ulSectorSize ] );

                if( cs != CTRL_GOOD )
                {
                    ret = -RED_EIO;
                    break;
                }

                ulSectorIdx += ulTransfer;
            }

            return ret;
        }


/** @brief Flush any caches beneath the file system.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskFlush( uint8_t bVolNum )
        {
            REDSTATUS ret;
            Ctrl_status cs;

            /*  The ASF SD/MMC driver appears to write sectors synchronously, so it
             *  should be fine to do nothing and return success.  However, Atmel's
             *  implementation of the FatFs diskio.c file does the equivalent of the
             *  below when the disk is flushed.  Just in case this is important for some
             *  non-obvious reason, do the same.
             */
            cs = sd_mmc_test_unit_ready( bVolNum );

            if( cs == CTRL_GOOD )
            {
                ret = 0;
            }
            else
            {
                ret = -RED_EIO;
            }

            return ret;
        }
    #endif /* REDCONF_READ_ONLY == 0 */

#elif BDEV_EXAMPLE_IMPLEMENTATION == BDEV_STM32_SDIO

    #ifdef USE_STM324xG_EVAL
        #include <stm324xg_eval.h>
        #include <stm324xg_eval_sd.h>
    #elif defined( USE_STM32746G_DISCO )
        #include <stm32746g_discovery.h>
        #include <stm32746g_discovery_sd.h>
    #else

/*  If you are using a compatible STM32 device other than the two listed above
 *  and you have SD card driver headers, you can try adding them to the above
 *  list.
 */
        #error "Unsupported device."
    #endif

    #if REDCONF_VOLUME_COUNT > 1
        #error "The STM32 SDIO block device implementation does not support multiple volumes."
    #endif


    #ifndef USE_HAL_DRIVER
        #error "The STM32 StdPeriph driver is not supported. Please use the HAL driver or modify the Reliance Edge block device interface."
    #endif


/** @brief Number of times to call BSP_SD_GetStatus() before timing out and
 *         returning an error.
 *
 *  See ::CheckStatus().
 *
 *  NOTE: Datalight has not observed a scenario where BSP_SD_GetStatus()
 *  returns SD_TRANSFER_BUSY after a transfer command returns successfully.
 *  Set SD_STATUS_TIMEOUT to 0U to skip checking BSP_SD_GetStatus().
 */
    #define SD_STATUS_TIMEOUT    ( 100000U )

/** @brief 4-byte aligned buffer to use for DMA transfers when passed in
 *         an unaligned buffer.
 */
    static uint32_t gaulAlignedBuffer[ 512U / sizeof( uint32_t ) ];


    #if SD_STATUS_TIMEOUT > 0U
        static REDSTATUS CheckStatus( void );
    #endif


/** @brief Initialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    No SD card was found; or BSP_SD_Init() failed.
 *  @retval -RED_EINVAL The SD card's block size is not the same as the
 *                      configured sector size; or the SD card is not large
 *                      enough for the volume; or the volume size is above
 *                      4GiB, meaning that part of it cannot be accessed
 *                      through the STM32 SDIO driver.
 */
    static REDSTATUS DiskOpen( uint8_t bVolNum,
                               BDEVOPENMODE mode )
    {
        REDSTATUS ret = 0;
        static bool fSdInitted = false;

        ( void ) mode;

        if( !fSdInitted )
        {
            if( BSP_SD_Init() == MSD_OK )
            {
                fSdInitted = true;
            }
        }

        if( !fSdInitted )
        {
            /*  Above initialization attempt failed.
             */
            ret = -RED_EIO;
        }
        else if( BSP_SD_IsDetected() == SD_NOT_PRESENT )
        {
            ret = -RED_EIO;
        }
        else
        {
            uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
            HAL_SD_CardInfoTypedef sdCardInfo = { { 0 } };

            BSP_SD_GetCardInfo( &sdCardInfo );

            /*  Note: the actual card block size is sdCardInfo.CardBlockSize,
             *  but the interface only supports a 512 byte block size. Further,
             *  one card has been observed to report a 1024-byte block size,
             *  but it worked fine with a 512-byte Reliance Edge ulSectorSize.
             */
            if( ( ulSectorSize != 512U ) ||
                ( sdCardInfo.CardCapacity < ( gaRedVolConf[ bVolNum ].ullSectorCount * ulSectorSize ) ) )
            {
                ret = -RED_EINVAL;
            }
        }

        return ret;
    }


/** @brief Uninitialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskClose( uint8_t bVolNum )
    {
        ( void ) bVolNum;
        return 0;
    }


/** @brief Read sectors from a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskRead( uint8_t bVolNum,
                               uint64_t ullSectorStart,
                               uint32_t ulSectorCount,
                               void * pBuffer )
    {
        REDSTATUS redStat = 0;
        uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
        uint8_t bSdError;

        if( IS_UINT32_ALIGNED_PTR( pBuffer ) )
        {
            bSdError = BSP_SD_ReadBlocks_DMA( CAST_UINT32_PTR( pBuffer ), ullSectorStart * ulSectorSize, ulSectorSize, ulSectorCount );

            if( bSdError != MSD_OK )
            {
                redStat = -RED_EIO;
            }

            #if SD_STATUS_TIMEOUT > 0U
                else
                {
                    redStat = CheckStatus();
                }
            #endif
        }
        else
        {
            uint32_t ulSectorIdx;

            for( ulSectorIdx = 0U; ulSectorIdx < ulSectorCount; ulSectorIdx++ )
            {
                bSdError = BSP_SD_ReadBlocks_DMA( gaulAlignedBuffer, ( ullSectorStart + ulSectorIdx ) * ulSectorSize, ulSectorSize, 1U );

                if( bSdError != MSD_OK )
                {
                    redStat = -RED_EIO;
                }

                #if SD_STATUS_TIMEOUT > 0U
                    else
                    {
                        redStat = CheckStatus();
                    }
                #endif

                if( redStat == 0 )
                {
                    uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );

                    RedMemCpy( &pbBuffer[ ulSectorIdx * ulSectorSize ], gaulAlignedBuffer, ulSectorSize );
                }
                else
                {
                    break;
                }
            }
        }

        return redStat;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
        static REDSTATUS DiskWrite( uint8_t bVolNum,
                                    uint64_t ullSectorStart,
                                    uint32_t ulSectorCount,
                                    const void * pBuffer )
        {
            REDSTATUS redStat = 0;
            uint32_t ulSectorSize = gaRedVolConf[ bVolNum ].ulSectorSize;
            uint8_t bSdError;

            if( IS_UINT32_ALIGNED_PTR( pBuffer ) )
            {
                bSdError = BSP_SD_WriteBlocks_DMA( CAST_UINT32_PTR( CAST_AWAY_CONST( void, pBuffer ) ), ullSectorStart * ulSectorSize,
                                                   ulSectorSize, ulSectorCount );

                if( bSdError != MSD_OK )
                {
                    redStat = -RED_EIO;
                }

                #if SD_STATUS_TIMEOUT > 0U
                    else
                    {
                        redStat = CheckStatus();
                    }
                #endif
            }
            else
            {
                uint32_t ulSectorIdx;

                for( ulSectorIdx = 0U; ulSectorIdx < ulSectorCount; ulSectorIdx++ )
                {
                    const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );

                    RedMemCpy( gaulAlignedBuffer, &pbBuffer[ ulSectorIdx * ulSectorSize ], ulSectorSize );

                    bSdError = BSP_SD_WriteBlocks_DMA( gaulAlignedBuffer, ( ullSectorStart + ulSectorIdx ) * ulSectorSize, ulSectorSize, 1U );

                    if( bSdError != MSD_OK )
                    {
                        redStat = -RED_EIO;
                    }

                    #if SD_STATUS_TIMEOUT > 0U
                        else
                        {
                            redStat = CheckStatus();
                        }
                    #endif

                    if( redStat != 0 )
                    {
                        break;
                    }
                }
            }

            return redStat;
        }


/** @brief Flush any caches beneath the file system.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskFlush( uint8_t bVolNum )
        {
            /*  Disk transfer is synchronous; nothing to flush.
             */
            ( void ) bVolNum;
            return 0;
        }


        #if SD_STATUS_TIMEOUT > 0U

/** @brief Wait until BSP_SD_GetStatus returns SD_TRANSFER_OK.
 *
 *  This function calls BSP_SD_GetStatus repeatedly as long as it returns
 *  SD_TRANSFER_BUSY up to SD_STATUS_TIMEOUT times.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           SD_TRANSFER_OK was returned.
 *  @retval -RED_EIO    SD_TRANSFER_ERROR received, or timed out waiting for
 *                      SD_TRANSFER_OK.
 */
            static REDSTATUS CheckStatus( void )
            {
                REDSTATUS redStat = 0;
                uint32_t ulTimeout = SD_STATUS_TIMEOUT;
                HAL_SD_TransferStateTypedef transferState;

                do
                {
                    transferState = BSP_SD_GetStatus();
                    ulTimeout--;
                } while( ( transferState == SD_TRANSFER_BUSY ) && ( ulTimeout > 0U ) );

                if( transferState != SD_TRANSFER_OK )
                {
                    redStat = -RED_EIO;
                }

                return redStat;
            }
        #endif /* if SD_STATUS_TIMEOUT > 0U */

    #endif /* REDCONF_READ_ONLY == 0 */

#elif BDEV_EXAMPLE_IMPLEMENTATION == BDEV_RAM_DISK

    #include <stdlib.h> /* For ALLOCATE_CLEARED_MEMORY(), which expands to calloc(). */


    static uint8_t * gapbRamDisk[ REDCONF_VOLUME_COUNT ];


/** @brief Initialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  initialized.
 *  @param mode     The open mode, indicating the type of access required.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS DiskOpen( uint8_t bVolNum,
                               BDEVOPENMODE mode )
    {
        REDSTATUS ret = 0;

        ( void ) mode;

        if( gapbRamDisk[ bVolNum ] == NULL )
        {
            gapbRamDisk[ bVolNum ] = ALLOCATE_CLEARED_MEMORY( gaRedVolume[ bVolNum ].ulBlockCount, REDCONF_BLOCK_SIZE );

            if( gapbRamDisk[ bVolNum ] == NULL )
            {
                ret = -RED_EIO;
            }
        }

        return ret;
    }


/** @brief Uninitialize a disk.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  uninitialized.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskClose( uint8_t bVolNum )
    {
        REDSTATUS ret;

        if( gapbRamDisk[ bVolNum ] == NULL )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            /*  This implementation uses dynamically allocated memory, but must
             *  retain previously written data after the block device is closed, and
             *  thus the memory cannot be freed and will remain allocated until
             *  reboot.
             */
            ret = 0;
        }

        return ret;
    }


/** @brief Read sectors from a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being read from.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to read.
 *  @param pBuffer          The buffer into which to read the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
    static REDSTATUS DiskRead( uint8_t bVolNum,
                               uint64_t ullSectorStart,
                               uint32_t ulSectorCount,
                               void * pBuffer )
    {
        REDSTATUS ret;

        if( gapbRamDisk[ bVolNum ] == NULL )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            uint64_t ullByteOffset = ullSectorStart * gaRedVolConf[ bVolNum ].ulSectorSize;
            uint32_t ulByteCount = ulSectorCount * gaRedVolConf[ bVolNum ].ulSectorSize;

            RedMemCpy( pBuffer, &gapbRamDisk[ bVolNum ][ ullByteOffset ], ulByteCount );

            ret = 0;
        }

        return ret;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write sectors to a disk.
 *
 *  @param bVolNum          The volume number of the volume whose block device
 *                          is being written to.
 *  @param ullSectorStart   The starting sector number.
 *  @param ulSectorCount    The number of sectors to write.
 *  @param pBuffer          The buffer from which to write the sector data.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskWrite( uint8_t bVolNum,
                                    uint64_t ullSectorStart,
                                    uint32_t ulSectorCount,
                                    const void * pBuffer )
        {
            REDSTATUS ret;

            if( gapbRamDisk[ bVolNum ] == NULL )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                uint64_t ullByteOffset = ullSectorStart * gaRedVolConf[ bVolNum ].ulSectorSize;
                uint32_t ulByteCount = ulSectorCount * gaRedVolConf[ bVolNum ].ulSectorSize;

                RedMemCpy( &gapbRamDisk[ bVolNum ][ ullByteOffset ], pBuffer, ulByteCount );

                ret = 0;
            }

            return ret;
        }


/** @brief Flush any caches beneath the file system.
 *
 *  @param bVolNum  The volume number of the volume whose block device is being
 *                  flushed.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
        static REDSTATUS DiskFlush( uint8_t bVolNum )
        {
            REDSTATUS ret;

            if( gapbRamDisk[ bVolNum ] == NULL )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                ret = 0;
            }

            return ret;
        }
    #endif /* REDCONF_READ_ONLY == 0 */

#else /* if BDEV_EXAMPLE_IMPLEMENTATION == BDEV_F_DRIVER */

    #error "Invalid BDEV_EXAMPLE_IMPLEMENTATION value"

#endif /* BDEV_EXAMPLE_IMPLEMENTATION == ... */
