Oops - fix broken FAT_DATE() and FAT_TIME() macros.
[pub/USBasp.git] / Bootloaders / Incomplete / MassStorage / Lib / VirtualFAT.c
index 7a248d8..0bcda06 100644 (file)
   this software.
 */
 
+/** \file
+ *
+ *  Virtualized FAT12 filesystem implementation, to perform self-programming
+ *  in response to read and write requests to the virtual filesystem by the
+ *  host PC.
+ */
+
+#define  INCLUDE_FROM_VIRTUAL_FAT_C
 #include "VirtualFAT.h"
 
+/** FAT filesystem boot sector block, must be the first sector on the physical
+ *  disk so that the host can identify the presence of a FAT filesystem. This
+ *  block is truncated; normally a large bootstrap section is located near the
+ *  end of the block for booting purposes however as this is not meant to be a
+ *  bootable disk it is omitted for space reasons.
+ *
+ *  \note When returning the boot block to the host, the magic signature 0xAA55
+ *        must be added to the very end of the block to identify it as a boot
+ *        block.
+ */
 static const FATBootBlock_t BootBlock =
        {
                .Bootstrap               = {0xEB, 0x3C, 0x90},
@@ -42,106 +60,168 @@ static const FATBootBlock_t BootBlock =
                .TotalSectors16          = LUN_MEDIA_BLOCKS,
                .MediaDescriptor         = 0xF8,
                .SectorsPerFAT           = 1,
-               .SectorsPerTrack         = 32,
-               .Heads                   = 64,
+               .SectorsPerTrack         = (LUN_MEDIA_BLOCKS % 64),
+               .Heads                   = (LUN_MEDIA_BLOCKS / 64),
                .HiddenSectors           = 0,
                .TotalSectors32          = 0,
                .PhysicalDriveNum        = 0,
                .ExtendedBootRecordSig   = 0x29,
                .VolumeSerialNumber      = 0x12345678,
                .VolumeLabel             = "LUFA BOOT  ",
-               .FilesystemIdentifier    = "FAT16   ",
-               .BootstrapProgram        = {0},
-               .MagicSignature          = 0xAA55,
+               .FilesystemIdentifier    = "FAT12   ",
        };
 
+/** FAT 8.3 style directory entry, for the virtual FLASH contents file. */
 static FATDirectoryEntry_t FirmwareFileEntry =
        {
-               .Filename        = "FIRMWARE",
-               .Extension       = "BIN",
-               .Attributes      = 0,
-               .Reserved        = {0},
-               .CreationTime    = FAT_TIME(1, 1, 0),
-               .CreationDate    = FAT_DATE(14, 2, 1989),
-               .StartingCluster = 2,
-               .FileSizeBytes   = 2049,
+               .Filename               = "FIRMWARE",
+               .Extension              = "BIN",
+               .Attributes             = 0,
+               .Reserved               = {0},
+               .CreationTime           = FAT_TIME(1, 1, 0),
+               .CreationDate           = FAT_DATE(14, 2, 1989),
+               .StartingCluster        = 2,
+               .FileSizeBytes          = FIRMWARE_FILE_SIZE_BYTES,
        };
 
 
-static void WriteBlock(uint16_t BlockNumber)
+/** Updates a FAT12 cluster entry in the FAT file table with the specified next
+ *  chain index. If the cluster is the last in the file chain, the magic value
+ *  0xFFF is used.
+ *
+ *  \note FAT data cluster indexes are offset by 2, so that cluster 2 is the
+ *        first file data cluster on the disk. See the FAT specification.
+ *
+ *  \param[out]  FATTable    Pointer to the FAT12 allocation table
+ *  \param[in]   Index       Index of the cluster entry to update
+ *  \param[in]   ChainEntry  Next cluster index in the file chain
+ */
+static void UpdateFAT12ClusterEntry(uint8_t* const FATTable,
+                                    const uint16_t Index,
+                                    const uint16_t ChainEntry)
 {
-       uint8_t BlockBuffer[512];
+       /* Calculate the starting offset of the cluster entry in the FAT12 table */
+       uint8_t FATOffset   =   (Index * 3) / 2;
+       bool    UpperNibble = (((Index * 3) % 2) != 0);
 
-       /* Wait until endpoint is ready before continuing */
-       if (Endpoint_WaitUntilReady())
-         return;
+       /* Check if the start of the entry is at an upper nibble of the byte, fill
+        * out FAT12 entry as required */
+       if (UpperNibble)
+       {
+               FATTable[FATOffset]     = (FATTable[FATOffset] & 0x0F) | ((ChainEntry & 0x0F) << 4);
+               FATTable[FATOffset + 1] = (ChainEntry >> 4);
+       }
+       else
+       {
+               FATTable[FATOffset]     = ChainEntry;
+               FATTable[FATOffset + 1] = (FATTable[FATOffset] & 0xF0) | (ChainEntry >> 8);
+       }
+}
 
+/** Writes a block of data to the virtual FAT filesystem, from the USB Mass
+ *  Storage interface.
+ *
+ *  \param[in]  BlockNumber  Index of the block to write.
+ */
+static void WriteVirtualBlock(const uint16_t BlockNumber)
+{
+       uint8_t BlockBuffer[SECTOR_SIZE_BYTES];
+
+       /* Buffer the entire block to be written from the host */
        Endpoint_Read_Stream_LE(BlockBuffer, sizeof(BlockBuffer), NULL);
        Endpoint_ClearOUT();
 
-       printf("WRITE %d\r\n", BlockNumber);
-       // TODO: Write to FLASH
+       if ((BlockNumber >= 4) && (BlockNumber < (4 + FILE_SECTORS(FIRMWARE_FILE_SIZE_BYTES))))
+       {
+               uint32_t WriteFlashAddress = (uint32_t)(BlockNumber - 4) * SECTOR_SIZE_BYTES;
+
+               for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i += 2)
+               {
+                       if ((WriteFlashAddress % SPM_PAGESIZE) == 0)
+                       {
+                               /* Erase the given FLASH page, ready to be programmed */
+                               boot_page_erase(WriteFlashAddress);
+                               boot_spm_busy_wait();
+                       }
+
+                       /* Write the next data word to the FLASH page */
+                       boot_page_fill(WriteFlashAddress, (BlockBuffer[i + 1] << 8) | BlockBuffer[i]);
+                       WriteFlashAddress += 2;
+
+                       if ((WriteFlashAddress % SPM_PAGESIZE) == 0)
+                       {
+                               /* Write the filled FLASH page to memory */
+                               boot_page_write(WriteFlashAddress - SPM_PAGESIZE);
+                               boot_spm_busy_wait();
+                       }
+               }
+       }
 }
 
-static void ReadBlock(uint16_t BlockNumber)
+/** Reads a block of data from the virtual FAT filesystem, and sends it to the
+ *  host via the USB Mass Storage interface.
+ *
+ *  \param[in]  BlockNumber  Index of the block to read.
+ */
+static void ReadVirtualBlock(const uint16_t BlockNumber)
 {
-       uint8_t BlockBuffer[512];
+       uint8_t BlockBuffer[SECTOR_SIZE_BYTES];
        memset(BlockBuffer, 0x00, sizeof(BlockBuffer));
 
-       printf("READ %d", BlockNumber);
-
        switch (BlockNumber)
        {
-               case 0:
+               case 0: /* Block 0: Boot block sector */
                        memcpy(BlockBuffer, &BootBlock, sizeof(FATBootBlock_t));
-                       printf(" <B>\r\n");
-                       break;
 
-               case 1:
-               case 2:
-                       printf(" <F>\r\n");
+                       /* Add the magic signature to the end of the block */
+                       BlockBuffer[SECTOR_SIZE_BYTES - 2] = 0x55;
+                       BlockBuffer[SECTOR_SIZE_BYTES - 1] = 0xAA;
+                       break;
 
+               case 1: /* Block 1: First FAT12 cluster chain copy */
+               case 2: /* Block 2: Second FAT12 cluster chain copy */
                        /* Cluster 0: Media type/Reserved */
-                       ((uint16_t*)&BlockBuffer)[0] = 0xFF00 | BootBlock.MediaDescriptor;
+                       UpdateFAT12ClusterEntry(BlockBuffer, 0, 0xF00 | BootBlock.MediaDescriptor);
 
                        /* Cluster 1: Reserved */
-                       ((uint16_t*)&BlockBuffer)[1] = 0xFFFF;
+                       UpdateFAT12ClusterEntry(BlockBuffer, 1, 0xFFF);
 
                        /* Cluster 2 onwards: Cluster chain of FIRMWARE.BIN */
-                       for (uint16_t i = 0; i < FILE_CLUSTERS(2049); i++)
-                       {
-                               ((uint16_t*)&BlockBuffer)[i + 2] = i + 3;
-                       }
+                       for (uint16_t i = 0; i < FILE_CLUSTERS(FIRMWARE_FILE_SIZE_BYTES); i++)
+                         UpdateFAT12ClusterEntry(BlockBuffer, i+2, i+3);
 
                        /* Mark last cluster as end of file */
-                       ((uint16_t*)&BlockBuffer)[FILE_CLUSTERS(2049) + 1] = 0xFFFF;
+                       UpdateFAT12ClusterEntry(BlockBuffer, FILE_CLUSTERS(FIRMWARE_FILE_SIZE_BYTES) + 1, 0xFFF);
                        break;
 
-               case 3:
-                       printf("<R>\r\n");
+               case 3: /* Block 3: Root file entries */
                        memcpy(BlockBuffer, &FirmwareFileEntry, sizeof(FATDirectoryEntry_t));
                        break;
 
-               default:
-                       if ((BlockNumber >= 4) && (BlockNumber < (4 + FILE_CLUSTERS(FIRMWARE_FILE_SIZE))))
+               default: /* Blocks 4 onwards: Data allocation section */
+                       if ((BlockNumber >= 4) && (BlockNumber < (4 + FILE_SECTORS(FIRMWARE_FILE_SIZE_BYTES))))
                        {
-                               printf("<D>\r\n");
+                               uint32_t ReadFlashAddress = (uint32_t)(BlockNumber - 4) * SECTOR_SIZE_BYTES;
 
-                               for (uint16_t i = 0; i < 512; i++)
-                                 BlockBuffer[i] = '0' + BlockNumber; //A' + (i % 26);
+                               for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i++)
+                                 BlockBuffer[i] = pgm_read_byte_far(ReadFlashAddress++);
                        }
 
                        break;
        }
 
-       /* Wait until endpoint is ready before continuing */
-       if (Endpoint_WaitUntilReady())
-         return;
-
+       /* Write the entire read block Buffer to the host */
        Endpoint_Write_Stream_LE(BlockBuffer, sizeof(BlockBuffer), NULL);
        Endpoint_ClearIN();
 }
 
+/** Writes a number of blocks to the virtual FAT file system, from the host
+ *  PC via the USB Mass Storage interface.
+ *
+ *  \param[in] MSInterfaceInfo  Pointer to a structure containing a Mass Storage Class configuration and state
+ *  \param[in] BlockAddress  Data block starting address for the write sequence
+ *  \param[in] TotalBlocks   Number of blocks of data to write
+ */
 void VirtualFAT_WriteBlocks(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo,
                             const uint32_t BlockAddress,
                             uint16_t TotalBlocks)
@@ -149,11 +229,18 @@ void VirtualFAT_WriteBlocks(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo,
        uint16_t CurrentBlock = (uint16_t)BlockAddress;
 
        /* Emulated FAT is performed per-block, pass each requested block index
-        * to the emulation function */
+        * to the emulated FAT block write function */
        while (TotalBlocks--)
-         WriteBlock(CurrentBlock++);
+         WriteVirtualBlock(CurrentBlock++);
 }
 
+/** Reads a number of blocks from the virtual FAT file system, and sends them
+ *  to the host PC via the USB Mass Storage interface.
+ *
+ *  \param[in] MSInterfaceInfo  Pointer to a structure containing a Mass Storage Class configuration and state
+ *  \param[in] BlockAddress  Data block starting address for the read sequence
+ *  \param[in] TotalBlocks   Number of blocks of data to read
+ */
 void VirtualFAT_ReadBlocks(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo,
                            const uint32_t BlockAddress,
                            uint16_t TotalBlocks)
@@ -161,8 +248,8 @@ void VirtualFAT_ReadBlocks(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo,
        uint16_t CurrentBlock = (uint16_t)BlockAddress;
 
        /* Emulated FAT is performed per-block, pass each requested block index
-        * to the emulation function */
+        * to the emulated FAT block read function */
        while (TotalBlocks--)
-         ReadBlock(CurrentBlock++);
+         ReadVirtualBlock(CurrentBlock++);
 }