/*
 * Mega + USB storage + optional DS1307 + optional expansion RAM + funky status LED,
 * Includes interactive debug level setting, and supports hot-plug.
 *
 * IMPORTANT! PLEASE USE Arduino 1.0.5 or better!
 * Older versions HAVE MAJOR BUGS AND WILL NOT WORK AT ALL!
 * Use of gcc-avr and lib-c that is newer than the Arduino version is even better.
 * If you experience random crashes, use make.
 * The options that the IDE use can generate bad code and cause the AVR to crash.
 *
 * This sketch requires the following libraries:
 * https://github.com/felis/USB_Host_Shield_2.0 Install as 'USB_Host_Shield_2_0'
 * https://github.com/xxxajk/xmem2 Install as 'xmem', provides memory services.
 * https://github.com/xxxajk/generic_storage provides access to FAT file system.
 * https://github.com/xxxajk/RTClib provides access to DS1307, or fake clock.
 *
 * Optional, to use the Makefile (Recommended! See above!):
 * https://github.com/xxxajk/Arduino_Makefile_master
 *
 */

/////////////////////////////////////////////////////////////
// Please Note:                                            //
// This section is for info with the Arduino IDE ONLY.     //
// Unfortunately due to short sightedness of the Arduino   //
// code team, that you must set the following in the       //
// respective libraries.                                   //
// Changing them here will have _NO_ effect!               //
/////////////////////////////////////////////////////////////

// Uncomment to enable debugging
//#define DEBUG_USB_HOST
// This is where stderr/USB debugging goes to
//#define USB_HOST_SERIAL Serial3

// If you have external memory, setting this to 0 enables FAT table caches.
// The 0 setting is recommended only if you have external memory.
//#define _FS_TINY 1

//#define _USE_LFN 3
//#define EXT_RAM_STACK 1
//#define EXT_RAM_HEAP 1
//#define _MAX_SS 512
/////////////////////////////////////////////////////////////
// End of Arduino IDE specific information                 //
/////////////////////////////////////////////////////////////

// You can set this to 0 if you are not using a USB hub.
// It will save a little bit of flash and RAM.
// Set to 1 if you want to use a hub.
#define WANT_HUB_TEST 0


#if defined(__AVR__)
#include <xmem.h>
#else
#include <spi4teensy3.h>
#endif
#if WANT_HUB_TEST
#include <usbhub.h>
#endif
#include <masstorage.h>
#include <Storage.h>
#include <PCpartition/PCPartition.h>
#include <avr/interrupt.h>
#include <FAT/FAT.h>
#include <Wire.h>
#include <RTClib.h>
#include <stdio.h>
#if defined(__AVR__)
static FILE tty_stdio;
static FILE tty_stderr;
volatile uint32_t LEDnext_time; // fade timeout
volatile uint32_t HEAPnext_time; // when to print out next heap report
volatile int brightness = 0; // how bright the LED is
volatile int fadeAmount = 80; // how many points to fade the LED by
#endif

USB Usb;

volatile uint8_t current_state = 1;
volatile uint8_t last_state = 0;
volatile bool fatready = false;
volatile bool partsready = false;
volatile bool notified = false;
volatile bool runtest = false;
volatile bool usbon = false;
volatile uint32_t usbon_time;
volatile bool change = false;
volatile bool reportlvl = false;
int cpart = 0;
PCPartition *PT;

#if WANT_HUB_TEST
#define MAX_HUBS 1
USBHub *Hubs[MAX_HUBS];
#endif

static PFAT *Fats[_VOLUMES];
static part_t parts[_VOLUMES];
static storage_t sto[_VOLUMES];

/*make sure this is a power of two. */
#define mbxs 128
static uint8_t My_Buff_x[mbxs]; /* File read buffer */

#if defined(__AVR__)

#define prescale1       ((1 << WGM12) | (1 << CS10))
#define prescale8       ((1 << WGM12) | (1 << CS11))
#define prescale64      ((1 << WGM12) | (1 << CS10) | (1 << CS11))
#define prescale256     ((1 << WGM12) | (1 << CS12))
#define prescale1024    ((1 << WGM12) | (1 << CS12) | (1 << CS10))

extern "C" unsigned int freeHeap();

static int tty_stderr_putc(char c, FILE *t) {
        USB_HOST_SERIAL.write(c);
        return 0;
}

static int tty_stderr_flush(FILE *t) {
        USB_HOST_SERIAL.flush();
        return 0;
}

static int tty_std_putc(char c, FILE *t) {
        Serial.write(c);
        return 0;
}

static int tty_std_getc(FILE *t) {
        while(!Serial.available());
        return Serial.read();
}

static int tty_std_flush(FILE *t) {
        Serial.flush();
        return 0;
}

#else
extern "C" {

        int _write(int fd, const char *ptr, int len) {
                int j;
                for(j = 0; j < len; j++) {
                        if(fd == 1)
                                Serial.write(*ptr++);
                        else if(fd == 2)
                                USB_HOST_SERIAL.write(*ptr++);
                }
                return len;
        }

        int _read(int fd, char *ptr, int len) {
                if(len > 0 && fd == 0) {
                        while(!Serial.available());
                        *ptr = Serial.read();
                        return 1;
                }
                return 0;
        }

#include <sys/stat.h>

        int _fstat(int fd, struct stat *st) {
                memset(st, 0, sizeof (*st));
                st->st_mode = S_IFCHR;
                st->st_blksize = 1024;
                return 0;
        }

        int _isatty(int fd) {
                return (fd < 3) ? 1 : 0;
        }
}
#endif

void setup() {
        bool serr = false;
        for(int i = 0; i < _VOLUMES; i++) {
                Fats[i] = NULL;
                sto[i].private_data = new pvt_t;
                ((pvt_t *)sto[i].private_data)->B = 255; // impossible
        }
        // Set this to higher values to enable more debug information
        // minimum 0x00, maximum 0xff
        UsbDEBUGlvl = 0x51;

#if !defined(CORE_TEENSY) && defined(__AVR__)
        // make LED pin as an output:
        pinMode(LED_BUILTIN, OUTPUT);
        pinMode(2, OUTPUT);
        // Ensure TX is off
        _SFR_BYTE(UCSR0B) &= ~_BV(TXEN0);
        // Initialize 'debug' serial port
        USB_HOST_SERIAL.begin(115200);
        // Do not start primary Serial port if already started.
        if(bit_is_clear(UCSR0B, TXEN0)) {
                Serial.begin(115200);
                serr = true;
        }


        // Blink LED
        delay(500);
        analogWrite(LED_BUILTIN, 255);
        delay(500);
        analogWrite(LED_BUILTIN, 0);
        delay(500);
#else
        while(!Serial);
        Serial.begin(115200); // On the Teensy 3.x we get a delay at least!
#endif
#if defined(__AVR__)
        // Set up stdio/stderr
        tty_stdio.put = tty_std_putc;
        tty_stdio.get = tty_std_getc;
        tty_stdio.flags = _FDEV_SETUP_RW;
        tty_stdio.udata = 0;

        tty_stderr.put = tty_stderr_putc;
        tty_stderr.get = NULL;
        tty_stderr.flags = _FDEV_SETUP_WRITE;
        tty_stderr.udata = 0;

        stdout = &tty_stdio;
        stdin = &tty_stdio;
        stderr = &tty_stderr;
#endif
        printf_P(PSTR("\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nStart\r\n"));
        printf_P(PSTR("Current UsbDEBUGlvl %02x\r\n"), UsbDEBUGlvl);
        printf_P(PSTR("'+' and '-' increase/decrease by 0x01\r\n"));
        printf_P(PSTR("'.' and ',' increase/decrease by 0x10\r\n"));
        printf_P(PSTR("'t' will run a 10MB write/read test and print out the time it took.\r\n"));
        printf_P(PSTR("'e' will toggle vbus off for a few moments.\r\n\r\n"));
        printf_P(PSTR("Long filename support: "
#if _USE_LFN
                "Enabled"
#else
                "Disabled"
#endif
                "\r\n"));
        if(serr) {
                fprintf_P(stderr, PSTR("\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nStart\r\n"));
                fprintf_P(stderr, PSTR("Current UsbDEBUGlvl %02x\r\n"), UsbDEBUGlvl);
                fprintf_P(stderr, PSTR("Long filename support: "
#if _USE_LFN
                        "Enabled"
#else
                        "Disabled"
#endif
                        "\r\n"));
        }

#if !defined(CORE_TEENSY) && defined(__AVR__)
        analogWrite(LED_BUILTIN, 255);
        delay(500);
        analogWrite(LED_BUILTIN, 0);
        delay(500);
        analogWrite(LED_BUILTIN, 255);
        delay(500);
        analogWrite(LED_BUILTIN, 0);
        delay(500);
        analogWrite(LED_BUILTIN, 255);
        delay(500);
        analogWrite(LED_BUILTIN, 0);
        delay(500);

        LEDnext_time = millis() + 1;
#if EXT_RAM
        printf_P(PSTR("Total EXT RAM banks %i\r\n"), xmem::getTotalBanks());
#endif
        printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap());
        printf_P(PSTR("SP %x\r\n"), (uint8_t *)(SP));
#endif

        // Even though I'm not going to actually be deleting,
        // I want to be able to have slightly more control.
        // Besides, it is easier to initialize stuff...
#if WANT_HUB_TEST
        for(int i = 0; i < MAX_HUBS; i++) {
                Hubs[i] = new USBHub(&Usb);
#if defined(__AVR__)
                printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap());
#endif
        }
#endif
        // Initialize generic storage. This must be done before USB starts.
        Init_Generic_Storage();

        while(Usb.Init(1000) == -1) {
                printf_P(PSTR("No USB HOST Shield?\r\n"));
                Notify(PSTR("OSC did not start."), 0x40);
        }

#if !defined(CORE_TEENSY) && defined(__AVR__)
        cli();
        TCCR3A = 0;
        TCCR3B = 0;
        // (0.01/(1/((16 *(10^6)) / 8))) - 1 = 19999
        OCR3A = 19999;
        TCCR3B |= prescale8;
        TIMSK3 |= (1 << OCIE1A);
        sei();

        HEAPnext_time = millis() + 10000;
#endif
#if defined(__AVR__)
        HEAPnext_time = millis() + 10000;
#endif
}

void serialEvent() {
        // Adjust UsbDEBUGlvl level on-the-fly.
        // + to increase, - to decrease, * to display current level.
        // . to increase by 16, , to decrease by 16
        // e to flick VBUS
        // * to report debug level
        if(Serial.available()) {
                int inByte = Serial.read();
                switch(inByte) {
                        case '+':
                                if(UsbDEBUGlvl < 0xff) UsbDEBUGlvl++;
                                reportlvl = true;
                                break;
                        case '-':
                                if(UsbDEBUGlvl > 0x00) UsbDEBUGlvl--;
                                reportlvl = true;
                                break;
                        case '.':
                                if(UsbDEBUGlvl < 0xf0) UsbDEBUGlvl += 16;
                                reportlvl = true;
                                break;
                        case ',':
                                if(UsbDEBUGlvl > 0x0f) UsbDEBUGlvl -= 16;
                                reportlvl = true;
                                break;
                        case '*':
                                reportlvl = true;
                                break;
                        case 't':
                                runtest = true;
                                break;
                        case 'e':
                                change = true;
                                usbon = false;
                                break;
                }
        }
}

#if !defined(CORE_TEENSY) && defined(__AVR__)
// ALL teensy versions LACK PWM ON LED

ISR(TIMER3_COMPA_vect) {
        if((long)(millis() - LEDnext_time) >= 0L) {
                LEDnext_time = millis() + 30;

                // set the brightness of LED
                analogWrite(LED_BUILTIN, brightness);

                // change the brightness for next time through the loop:
                brightness = brightness + fadeAmount;

                // reverse the direction of the fading at the ends of the fade:
                if(brightness <= 0) {
                        brightness = 0;
                        fadeAmount = -fadeAmount;
                }
                if(brightness >= 255) {
                        brightness = 255;
                        fadeAmount = -fadeAmount;
                }
        }
}
#endif

bool isfat(uint8_t t) {
        return (t == 0x01 || t == 0x04 || t == 0x06 || t == 0x0b || t == 0x0c || t == 0x0e || t == 0x1);
}

void die(FRESULT rc) {
        printf_P(PSTR("Failed with rc=%u.\r\n"), rc);
        //for (;;);
}

void loop() {
        FIL My_File_Object_x; /* File object */

#if defined(__AVR__)
        // Print a heap status report about every 10 seconds.
        if((long)(millis() - HEAPnext_time) >= 0L) {
                if(UsbDEBUGlvl > 0x50) {
                        printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap());
                }
                HEAPnext_time = millis() + 10000;
        }
        TCCR3B = 0;
#endif
#if defined(CORE_TEENSY)
        // Teensy suffers here, oh well...
        serialEvent();
#endif
        // Horrid! This sort of thing really belongs in an ISR, not here!
        // We also will be needing to test each hub port, we don't do this yet!
        if(!change && !usbon && (long)(millis() - usbon_time) >= 0L) {
                change = true;
                usbon = true;
        }

        if(change) {
                change = false;
                if(usbon) {
                        Usb.vbusPower(vbus_on);
                        printf_P(PSTR("VBUS on\r\n"));
                } else {
                        Usb.vbusPower(vbus_off);
                        usbon_time = millis() + 2000;
                }
        }
        Usb.Task();
        current_state = Usb.getUsbTaskState();
        if(current_state != last_state) {
                if(UsbDEBUGlvl > 0x50)
                        printf_P(PSTR("USB state = %x\r\n"), current_state);
#if !defined(CORE_TEENSY) && defined(__AVR__)
                if(current_state == USB_STATE_RUNNING) {
                        fadeAmount = 30;
                }
#endif
                if(current_state == USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE) {
#if !defined(CORE_TEENSY) && defined(__AVR__)
                        fadeAmount = 80;
#endif
                        partsready = false;
                        for(int i = 0; i < cpart; i++) {
                                if(Fats[i] != NULL)
                                        delete Fats[i];
                                Fats[i] = NULL;
                        }
                        fatready = false;
                        notified = false;
                        cpart = 0;
                }
                last_state = current_state;
        }

        // only do any of this if usb is on
        if(usbon) {
                if(partsready && !fatready) {
                        if(cpart > 0) fatready = true;
                }
                // This is horrible, and needs to be moved elsewhere!
                for(int B = 0; B < MAX_USB_MS_DRIVERS; B++) {
                        if(!partsready && (UHS_USB_Storage[B]->GetAddress() != NULL)) {

                                // Build a list.
                                int ML = UHS_USB_Storage[B]->GetbMaxLUN();
                                //printf("MAXLUN = %i\r\n", ML);
                                ML++;
                                for(int i = 0; i < ML; i++) {
                                        if(UHS_USB_Storage[B]->LUNIsGood(i)) {
                                                partsready = true;
                                                ((pvt_t *)(sto[i].private_data))->lun = i;
                                                ((pvt_t *)(sto[i].private_data))->B = B;
                                                sto[i].Reads = *UHS_USB_BulkOnly_Read;
                                                sto[i].Writes = *UHS_USB_BulkOnly_Write;
                                                sto[i].Status = *UHS_USB_BulkOnly_Status;
                                                sto[i].Initialize = *UHS_USB_BulkOnly_Initialize;
                                                sto[i].Commit = *UHS_USB_BulkOnly_Commit;
                                                sto[i].TotalSectors = UHS_USB_Storage[B]->GetCapacity(i);
                                                sto[i].SectorSize = UHS_USB_Storage[B]->GetSectorSize(i);
                                                printf_P(PSTR("LUN:\t\t%u\r\n"), i);
                                                printf_P(PSTR("Total Sectors:\t%08lx\t%lu\r\n"), sto[i].TotalSectors, sto[i].TotalSectors);
                                                printf_P(PSTR("Sector Size:\t%04x\t\t%u\r\n"), sto[i].SectorSize, sto[i].SectorSize);
                                                // get the partition data...
                                                PT = new PCPartition;

                                                if(!PT->Init(&sto[i])) {
                                                        part_t *apart;
                                                        for(int j = 0; j < 4; j++) {
                                                                apart = PT->GetPart(j);
                                                                if(apart != NULL && apart->type != 0x00) {
                                                                        memcpy(&(parts[cpart]), apart, sizeof (part_t));
                                                                        printf_P(PSTR("Partition %u type %#02x\r\n"), j, parts[cpart].type);
                                                                        // for now
                                                                        if(isfat(parts[cpart].type)) {
                                                                                Fats[cpart] = new PFAT(&sto[i], cpart, parts[cpart].firstSector);
                                                                                //int r = Fats[cpart]->Good();
                                                                                if(Fats[cpart]->MountStatus()) {
                                                                                        delete Fats[cpart];
                                                                                        Fats[cpart] = NULL;
                                                                                } else cpart++;
                                                                        }
                                                                }
                                                        }
                                                } else {
                                                        // try superblock
                                                        Fats[cpart] = new PFAT(&sto[i], cpart, 0);
                                                        //int r = Fats[cpart]->Good();
                                                        if(Fats[cpart]->MountStatus()) {
                                                                //printf_P(PSTR("Superblock error %x\r\n"), r);
                                                                delete Fats[cpart];
                                                                Fats[cpart] = NULL;
                                                        } else cpart++;

                                                }
                                                delete PT;
                                        } else {
                                                sto[i].Writes = NULL;
                                                sto[i].Reads = NULL;
                                                sto[i].Initialize = NULL;
                                                sto[i].TotalSectors = 0UL;
                                                sto[i].SectorSize = 0;
                                        }
                                }

                        }
                }

                if(fatready) {
                        if(Fats[0] != NULL) {
                                struct Pvt * p;
                                p = ((struct Pvt *)(Fats[0]->storage->private_data));
                                if(!UHS_USB_Storage[p->B]->LUNIsGood(p->lun)) {
                                        // media change
#if !defined(CORE_TEENSY) && defined(__AVR__)
                                        fadeAmount = 80;
#endif
                                        partsready = false;
                                        for(int i = 0; i < cpart; i++) {
                                                if(Fats[i] != NULL)
                                                        delete Fats[i];
                                                Fats[cpart] = NULL;
                                        }
                                        fatready = false;
                                        notified = false;
                                        cpart = 0;
                                }

                        }
                }
                if(fatready) {
                        FRESULT rc; /* Result code */
                        UINT bw, br, i;
                        if(!notified) {
#if !defined(CORE_TEENSY) && defined(__AVR__)
                                fadeAmount = 5;
#endif
                                notified = true;
                                FATFS *fs = NULL;
                                for(int zz = 0; zz < _VOLUMES; zz++) {
                                        if(Fats[zz]->volmap == 0) fs = Fats[zz]->ffs;
                                }
                                printf_P(PSTR("\r\nOpen an existing file (message.txt).\r\n"));
                                rc = f_open(&My_File_Object_x, "0:/MESSAGE.TXT", FA_READ);
                                if(rc) printf_P(PSTR("Error %i, message.txt not found.\r\n"), rc);
                                else {
                                        printf_P(PSTR("\r\nType the file content.\r\n"));
                                        for(;;) {
                                                rc = f_read(&My_File_Object_x, My_Buff_x, mbxs, &br); /* Read a chunk of file */
                                                if(rc || !br) break; /* Error or end of file */
                                                for(i = 0; i < br; i++) {
                                                        /* Type the data */
                                                        if(My_Buff_x[i] == '\n')
                                                                Serial.write('\r');
                                                        if(My_Buff_x[i] != '\r')
                                                                Serial.write(My_Buff_x[i]);
                                                        Serial.flush();
                                                }
                                        }
                                        if(rc) {
                                                f_close(&My_File_Object_x);
                                                goto out;
                                        }

                                        printf_P(PSTR("\r\nClose the file.\r\n"));
                                        rc = f_close(&My_File_Object_x);
                                        if(rc) goto out;
                                }
                                printf_P(PSTR("\r\nCreate a new file (hello.txt).\r\n"));
                                rc = f_open(&My_File_Object_x, "0:/Hello.TxT", FA_WRITE | FA_CREATE_ALWAYS);
                                if(rc) {
                                        die(rc);
                                        goto outdir;
                                }
                                printf_P(PSTR("\r\nWrite a text data. (Hello world!)\r\n"));
                                rc = f_write(&My_File_Object_x, "Hello world!\r\n", 14, &bw);
                                if(rc) {
                                        goto out;
                                }
                                printf_P(PSTR("%u bytes written.\r\n"), bw);

                                printf_P(PSTR("\r\nClose the file.\r\n"));
                                rc = f_close(&My_File_Object_x);
                                if(rc) {
                                        die(rc);
                                        goto out;
                                }
outdir:{
#if _USE_LFN
                                        char lfn[_MAX_LFN + 1];
                                        FILINFO My_File_Info_Object_x; /* File information object */
                                        My_File_Info_Object_x.lfname = lfn;
#endif
                                        DIR My_Dir_Object_x; /* Directory object */
                                        printf_P(PSTR("\r\nOpen root directory.\r\n"));
                                        rc = f_opendir(&My_Dir_Object_x, "0:/");
                                        if(rc) {
                                                die(rc);
                                                goto out;
                                        }

                                        printf_P(PSTR("\r\nDirectory listing...\r\n"));
#if defined(__AVR__)
                                        printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap());
#endif
                                        for(;;) {
#if _USE_LFN
                                                My_File_Info_Object_x.lfsize = _MAX_LFN;
#endif

                                                rc = f_readdir(&My_Dir_Object_x, &My_File_Info_Object_x); /* Read a directory item */
                                                if(rc || !My_File_Info_Object_x.fname[0]) break; /* Error or end of dir */

                                                if(My_File_Info_Object_x.fattrib & AM_DIR) {
                                                        Serial.write('d');
                                                } else {
                                                        Serial.write('-');
                                                }
                                                Serial.write('r');

                                                if(My_File_Info_Object_x.fattrib & AM_RDO) {
                                                        Serial.write('-');
                                                } else {
                                                        Serial.write('w');
                                                }
                                                if(My_File_Info_Object_x.fattrib & AM_HID) {
                                                        Serial.write('h');
                                                } else {
                                                        Serial.write('-');
                                                }

                                                if(My_File_Info_Object_x.fattrib & AM_SYS) {
                                                        Serial.write('s');
                                                } else {
                                                        Serial.write('-');
                                                }

                                                if(My_File_Info_Object_x.fattrib & AM_ARC) {
                                                        Serial.write('a');
                                                } else {
                                                        Serial.write('-');
                                                }

#if _USE_LFN
                                                if(*My_File_Info_Object_x.lfname)
                                                        printf_P(PSTR(" %8lu  %s (%s)\r\n"), My_File_Info_Object_x.fsize, My_File_Info_Object_x.fname, My_File_Info_Object_x.lfname);
                                                else
#endif
                                                        printf_P(PSTR(" %8lu  %s\r\n"), My_File_Info_Object_x.fsize, &(My_File_Info_Object_x.fname[0]));
                                        }
                                }
out:
                                if(rc) die(rc);

                                DISK_IOCTL(fs->drv, CTRL_COMMIT, 0);
                                printf_P(PSTR("\r\nTest completed.\r\n"));

                        }

                        if(runtest) {
                                ULONG ii, wt, rt, start, end;
                                FATFS *fs = NULL;
                                for(int zz = 0; zz < _VOLUMES; zz++) {
                                        if(Fats[zz]->volmap == 0) fs = Fats[zz]->ffs;
                                }
                                runtest = false;
                                f_unlink("0:/10MB.bin");
                                printf_P(PSTR("\r\nCreate a new 10MB test file (10MB.bin).\r\n"));
                                rc = f_open(&My_File_Object_x, "0:/10MB.bin", FA_WRITE | FA_CREATE_ALWAYS);
                                if(rc) goto failed;
                                for(bw = 0; bw < mbxs; bw++) My_Buff_x[bw] = bw & 0xff;
                                fflush(stdout);
                                start = millis();
                                while(start == millis());
                                for(ii = 10485760LU / mbxs; ii > 0LU; ii--) {
                                        rc = f_write(&My_File_Object_x, My_Buff_x, mbxs, &bw);
                                        if(rc || !bw) goto failed;
                                }
                                rc = f_close(&My_File_Object_x);
                                if(rc) goto failed;
                                end = millis();
                                wt = (end - start) - 1;
                                printf_P(PSTR("Time to write 10485760 bytes: %lu ms (%lu sec) \r\n"), wt, (500 + wt) / 1000UL);
                                rc = f_open(&My_File_Object_x, "0:/10MB.bin", FA_READ);
                                fflush(stdout);
                                start = millis();
                                while(start == millis());
                                if(rc) goto failed;
                                for(;;) {
                                        rc = f_read(&My_File_Object_x, My_Buff_x, mbxs, &bw); /* Read a chunk of file */
                                        if(rc || !bw) break; /* Error or end of file */
                                }
                                end = millis();
                                if(rc) goto failed;
                                rc = f_close(&My_File_Object_x);
                                if(rc) goto failed;
                                rt = (end - start) - 1;
                                printf_P(PSTR("Time to read 10485760 bytes: %lu ms (%lu sec)\r\nDelete test file\r\n"), rt, (500 + rt) / 1000UL);
failed:
                                if(rc) die(rc);
                                DISK_IOCTL(fs->drv, CTRL_COMMIT, 0);
                                printf_P(PSTR("10MB timing test finished.\r\n"));
                        }
                }
        }
}

