diff options
| author | Jack Humbert <[email protected]> | 2017-07-07 11:55:23 -0400 | 
|---|---|---|
| committer | Jack Humbert <[email protected]> | 2017-07-07 11:55:23 -0400 | 
| commit | 60b30c036397cb5627fa374bb930794b225daa29 (patch) | |
| tree | e156c2d6b27f01db6985f2cc560c9c345d48fdac /Bootloaders/MassStorage/Lib/VirtualFAT.c | |
| download | qmk_firmware-60b30c036397cb5627fa374bb930794b225daa29.tar.gz qmk_firmware-60b30c036397cb5627fa374bb930794b225daa29.zip | |
Squashed 'lib/lufa/' content from commit 385d40300
git-subtree-dir: lib/lufa
git-subtree-split: 385d4030035dbaf41591309dbde47653bd03841b
Diffstat (limited to 'Bootloaders/MassStorage/Lib/VirtualFAT.c')
| -rw-r--r-- | Bootloaders/MassStorage/Lib/VirtualFAT.c | 482 | 
1 files changed, 482 insertions, 0 deletions
| diff --git a/Bootloaders/MassStorage/Lib/VirtualFAT.c b/Bootloaders/MassStorage/Lib/VirtualFAT.c new file mode 100644 index 0000000000..ffd453128e --- /dev/null +++ b/Bootloaders/MassStorage/Lib/VirtualFAT.c @@ -0,0 +1,482 @@ +/* +             LUFA Library +     Copyright (C) Dean Camera, 2017. + +  dean [at] fourwalledcubicle [dot] com +           www.lufa-lib.org +*/ + +/* +  Copyright 2017  Dean Camera (dean [at] fourwalledcubicle [dot] com) + +  Permission to use, copy, modify, distribute, and sell this +  software and its documentation for any purpose is hereby granted +  without fee, provided that the above copyright notice appear in +  all copies and that both that the copyright notice and this +  permission notice and warranty disclaimer appear in supporting +  documentation, and that the name of the author not be used in +  advertising or publicity pertaining to distribution of the +  software without specific, written prior permission. + +  The author disclaims all warranties with regard to this +  software, including all implied warranties of merchantability +  and fitness.  In no event shall the author be liable for any +  special, indirect or consequential damages or any damages +  whatsoever resulting from loss of use, data or profits, whether +  in an action of contract, negligence or other tortious action, +  arising out of or in connection with the use or performance of +  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}, +		.Description             = "mkdosfs", +		.SectorSize              = SECTOR_SIZE_BYTES, +		.SectorsPerCluster       = SECTOR_PER_CLUSTER, +		.ReservedSectors         = 1, +		.FATCopies               = 2, +		.RootDirectoryEntries    = (SECTOR_SIZE_BYTES / sizeof(FATDirectoryEntry_t)), +		.TotalSectors16          = LUN_MEDIA_BLOCKS, +		.MediaDescriptor         = 0xF8, +		.SectorsPerFAT           = 1, +		.SectorsPerTrack         = (LUN_MEDIA_BLOCKS % 64), +		.Heads                   = (LUN_MEDIA_BLOCKS / 64), +		.HiddenSectors           = 0, +		.TotalSectors32          = 0, +		.PhysicalDriveNum        = 0, +		.ExtendedBootRecordSig   = 0x29, +		.VolumeSerialNumber      = 0x12345678, +		.VolumeLabel             = "LUFA BOOT  ", +		.FilesystemIdentifier    = "FAT12   ", +	}; + +/** FAT 8.3 style directory entry, for the virtual FLASH contents file. */ +static FATDirectoryEntry_t FirmwareFileEntries[] = +	{ +		/* Root volume label entry; disk label is contained in the Filename and +		 * Extension fields (concatenated) with a special attribute flag - other +		 * fields are ignored. Should be the same as the label in the boot block. +		 */ +		[DISK_FILE_ENTRY_VolumeID] = +		{ +			.MSDOS_Directory = +				{ +					.Name            = "LUFA BOOT  ", +					.Attributes      = FAT_FLAG_VOLUME_NAME, +					.Reserved        = {0}, +					.CreationTime    = 0, +					.CreationDate    = 0, +					.StartingCluster = 0, +					.Reserved2       = 0, +				} +		}, + +		/* VFAT Long File Name entry for the virtual firmware file; required to +		 * prevent corruption from systems that are unable to detect the device +		 * as being a legacy MSDOS style FAT12 volume. */ +		[DISK_FILE_ENTRY_FLASH_LFN] = +		{ +			.VFAT_LongFileName = +				{ +					.Ordinal         = 1 | FAT_ORDINAL_LAST_ENTRY, +					.Attribute       = FAT_FLAG_LONG_FILE_NAME, +					.Reserved1       = 0, +					.Reserved2       = 0, + +					.Checksum        = FAT_CHECKSUM('F','L','A','S','H',' ',' ',' ','B','I','N'), + +					.Unicode1        = 'F', +					.Unicode2        = 'L', +					.Unicode3        = 'A', +					.Unicode4        = 'S', +					.Unicode5        = 'H', +					.Unicode6        = '.', +					.Unicode7        = 'B', +					.Unicode8        = 'I', +					.Unicode9        = 'N', +					.Unicode10       = 0, +					.Unicode11       = 0, +					.Unicode12       = 0, +					.Unicode13       = 0, +				} +		}, + +		/* MSDOS file entry for the virtual Firmware image. */ +		[DISK_FILE_ENTRY_FLASH_MSDOS] = +		{ +			.MSDOS_File = +				{ +					.Filename        = "FLASH   ", +					.Extension       = "BIN", +					.Attributes      = 0, +					.Reserved        = {0}, +					.CreationTime    = FAT_TIME(1, 1, 0), +					.CreationDate    = FAT_DATE(14, 2, 1989), +					.StartingCluster = 2, +					.FileSizeBytes   = FLASH_FILE_SIZE_BYTES, +				} +		}, + +		[DISK_FILE_ENTRY_EEPROM_LFN] = +		{ +			.VFAT_LongFileName = +				{ +					.Ordinal         = 1 | FAT_ORDINAL_LAST_ENTRY, +					.Attribute       = FAT_FLAG_LONG_FILE_NAME, +					.Reserved1       = 0, +					.Reserved2       = 0, + +					.Checksum        = FAT_CHECKSUM('E','E','P','R','O','M',' ',' ','B','I','N'), + +					.Unicode1        = 'E', +					.Unicode2        = 'E', +					.Unicode3        = 'P', +					.Unicode4        = 'R', +					.Unicode5        = 'O', +					.Unicode6        = 'M', +					.Unicode7        = '.', +					.Unicode8        = 'B', +					.Unicode9        = 'I', +					.Unicode10       = 'N', +					.Unicode11       = 0, +					.Unicode12       = 0, +					.Unicode13       = 0, +				} +		}, + +		[DISK_FILE_ENTRY_EEPROM_MSDOS] = +		{ +			.MSDOS_File = +				{ +					.Filename        = "EEPROM  ", +					.Extension       = "BIN", +					.Attributes      = 0, +					.Reserved        = {0}, +					.CreationTime    = FAT_TIME(1, 1, 0), +					.CreationDate    = FAT_DATE(14, 2, 1989), +					.StartingCluster = 2 + FILE_CLUSTERS(FLASH_FILE_SIZE_BYTES), +					.FileSizeBytes   = EEPROM_FILE_SIZE_BYTES, +				} +		}, +	}; + +/** Starting cluster of the virtual FLASH.BIN file on disk, tracked so that the + *  offset from the start of the data sector can be determined. On Windows + *  systems files are usually replaced using the original file's disk clusters, + *  while Linux appears to overwrite with an offset which must be compensated for. + */ +static const uint16_t* FLASHFileStartCluster  = &FirmwareFileEntries[DISK_FILE_ENTRY_FLASH_MSDOS].MSDOS_File.StartingCluster; + +/** Starting cluster of the virtual EEPROM.BIN file on disk, tracked so that the + *  offset from the start of the data sector can be determined. On Windows + *  systems files are usually replaced using the original file's disk clusters, + *  while Linux appears to overwrite with an offset which must be compensated for. + */ +static const uint16_t* EEPROMFileStartCluster = &FirmwareFileEntries[DISK_FILE_ENTRY_EEPROM_MSDOS].MSDOS_File.StartingCluster; + +/** Reads a byte of EEPROM out from the EEPROM memory space. + * + *  \note This function is required as the avr-libc EEPROM functions do not cope + *        with linker relaxations, and a jump longer than 4K of FLASH on the + *        larger USB AVRs will break the linker. This function is marked as + *        never inlinable and placed into the normal text segment so that the + *        call to the EEPROM function will be short even if the AUX boot section + *        is used. + * + *  \param[in]  Address   Address of the EEPROM location to read from + * + *  \return Read byte of EEPROM data. + */ +static uint8_t ReadEEPROMByte(const uint8_t* const Address) +{ +	return eeprom_read_byte(Address); +} + +/** Writes a byte of EEPROM out to the EEPROM memory space. + * + *  \note This function is required as the avr-libc EEPROM functions do not cope + *        with linker relaxations, and a jump longer than 4K of FLASH on the + *        larger USB AVRs will break the linker. This function is marked as + *        never inlinable and placed into the normal text segment so that the + *        call to the EEPROM function will be short even if the AUX boot section + *        is used. + * + *  \param[in]  Address   Address of the EEPROM location to write to + *  \param[in]  Data      New data to write to the EEPROM location + */ +static void WriteEEPROMByte(uint8_t* const Address, +                            const uint8_t Data) +{ +	 eeprom_update_byte(Address, Data); +} + +/** 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 + *  \c 0xFFF should be 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) +{ +	/* Calculate the starting offset of the cluster entry in the FAT12 table */ +	uint8_t FATOffset   = (Index + (Index >> 1)); +	bool    UpperNibble = ((Index & 1) != 0); + +	/* 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); +	} +} + +/** Updates a FAT12 cluster chain in the FAT file table with a linear chain of + *  the specified length. + * + *  \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 start of the cluster chain to update + *  \param[in]   ChainLength  Length of the chain to write, in clusters + */ +static void UpdateFAT12ClusterChain(uint8_t* const FATTable, +                                    const uint16_t Index, +                                    const uint8_t ChainLength) +{ +	for (uint8_t i = 0; i < ChainLength; i++) +	{ +		uint16_t CurrentCluster = Index + i; +		uint16_t NextCluster    = CurrentCluster + 1; + +		/* Mark last cluster as end of file */ +		if (i == (ChainLength - 1)) +		  NextCluster = 0xFFF; + +		UpdateFAT12ClusterEntry(FATTable, CurrentCluster, NextCluster); +	} +} + +/** Reads or writes a block of data from/to the physical device FLASH using a + *  block buffer stored in RAM, if the requested block is within the virtual + *  firmware file's sector ranges in the emulated FAT file system. + * + *  \param[in]      BlockNumber  Physical disk block to read from/write to + *  \param[in,out]  BlockBuffer  Pointer to the start of the block buffer in RAM + *  \param[in]      Read         If \c true, the requested block is read, if + *                               \c false, the requested block is written + */ +static void ReadWriteFLASHFileBlock(const uint16_t BlockNumber, +                                    uint8_t* BlockBuffer, +                                    const bool Read) +{ +	uint16_t FileStartBlock = DISK_BLOCK_DataStartBlock + (*FLASHFileStartCluster - 2) * SECTOR_PER_CLUSTER; +	uint16_t FileEndBlock   = FileStartBlock + (FILE_SECTORS(FLASH_FILE_SIZE_BYTES) - 1); + +	/* Range check the write request - abort if requested block is not within the +	 * virtual firmware file sector range */ +	if (!((BlockNumber >= FileStartBlock) && (BlockNumber <= FileEndBlock))) +	  return; + +	#if (FLASHEND > 0xFFFF) +	uint32_t FlashAddress = (uint32_t)(BlockNumber - FileStartBlock) * SECTOR_SIZE_BYTES; +	#else +	uint16_t FlashAddress = (uint16_t)(BlockNumber - FileStartBlock) * SECTOR_SIZE_BYTES; +	#endif + +	if (Read) +	{ +		/* Read out the mapped block of data from the device's FLASH */ +		for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i++) +		{ +			#if (FLASHEND > 0xFFFF) +			  BlockBuffer[i] = pgm_read_byte_far(FlashAddress++); +			#else +			  BlockBuffer[i] = pgm_read_byte(FlashAddress++); +			#endif +		} +	} +	else +	{ +		/* Write out the mapped block of data to the device's FLASH */ +		for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i += 2) +		{ +			if ((FlashAddress % SPM_PAGESIZE) == 0) +			{ +				/* Erase the given FLASH page, ready to be programmed */ +				BootloaderAPI_ErasePage(FlashAddress); +			} + +			/* Write the next data word to the FLASH page */ +			BootloaderAPI_FillWord(FlashAddress, (BlockBuffer[i + 1] << 8) | BlockBuffer[i]); +			FlashAddress += 2; + +			if ((FlashAddress % SPM_PAGESIZE) == 0) +			{ +				/* Write the filled FLASH page to memory */ +				BootloaderAPI_WritePage(FlashAddress - SPM_PAGESIZE); +			} +		} +	} +} + +/** Reads or writes a block of data from/to the physical device EEPROM using a + *  block buffer stored in RAM, if the requested block is within the virtual + *  firmware file's sector ranges in the emulated FAT file system. + * + *  \param[in]      BlockNumber  Physical disk block to read from/write to + *  \param[in,out]  BlockBuffer  Pointer to the start of the block buffer in RAM + *  \param[in]      Read         If \c true, the requested block is read, if + *                               \c false, the requested block is written + */ +static void ReadWriteEEPROMFileBlock(const uint16_t BlockNumber, +                                     uint8_t* BlockBuffer, +                                     const bool Read) +{ +	uint16_t FileStartBlock = DISK_BLOCK_DataStartBlock + (*EEPROMFileStartCluster - 2) * SECTOR_PER_CLUSTER; +	uint16_t FileEndBlock   = FileStartBlock + (FILE_SECTORS(EEPROM_FILE_SIZE_BYTES) - 1); + +	/* Range check the write request - abort if requested block is not within the +	 * virtual firmware file sector range */ +	if (!((BlockNumber >= FileStartBlock) && (BlockNumber <= FileEndBlock))) +	  return; + +	uint16_t EEPROMAddress = (uint16_t)(BlockNumber - FileStartBlock) * SECTOR_SIZE_BYTES; + +	if (Read) +	{ +		/* Read out the mapped block of data from the device's EEPROM */ +		for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i++) +		  BlockBuffer[i] = ReadEEPROMByte((uint8_t*)EEPROMAddress++); +	} +	else +	{ +		/* Write out the mapped block of data to the device's EEPROM */ +		for (uint16_t i = 0; i < SECTOR_SIZE_BYTES; i++) +		  WriteEEPROMByte((uint8_t*)EEPROMAddress++, BlockBuffer[i]); +	} +} + +/** 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. + */ +void VirtualFAT_WriteBlock(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(); + +	switch (BlockNumber) +	{ +		case DISK_BLOCK_BootBlock: +		case DISK_BLOCK_FATBlock1: +		case DISK_BLOCK_FATBlock2: +			/* Ignore writes to the boot and FAT blocks */ + +			break; + +		case DISK_BLOCK_RootFilesBlock: +			/* Copy over the updated directory entries */ +			memcpy(FirmwareFileEntries, BlockBuffer, sizeof(FirmwareFileEntries)); + +			break; + +		default: +			ReadWriteFLASHFileBlock(BlockNumber, BlockBuffer, false); +			ReadWriteEEPROMFileBlock(BlockNumber, BlockBuffer, false); + +			break; +	} +} + +/** 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. + */ +void VirtualFAT_ReadBlock(const uint16_t BlockNumber) +{ +	uint8_t BlockBuffer[SECTOR_SIZE_BYTES]; +	memset(BlockBuffer, 0x00, sizeof(BlockBuffer)); + +	switch (BlockNumber) +	{ +		case DISK_BLOCK_BootBlock: +			memcpy(BlockBuffer, &BootBlock, sizeof(FATBootBlock_t)); + +			/* Add the magic signature to the end of the block */ +			BlockBuffer[SECTOR_SIZE_BYTES - 2] = 0x55; +			BlockBuffer[SECTOR_SIZE_BYTES - 1] = 0xAA; + +			break; + +		case DISK_BLOCK_FATBlock1: +		case DISK_BLOCK_FATBlock2: +			/* Cluster 0: Media type/Reserved */ +			UpdateFAT12ClusterEntry(BlockBuffer, 0, 0xF00 | BootBlock.MediaDescriptor); + +			/* Cluster 1: Reserved */ +			UpdateFAT12ClusterEntry(BlockBuffer, 1, 0xFFF); + +			/* Cluster 2 onwards: Cluster chain of FLASH.BIN */ +			UpdateFAT12ClusterChain(BlockBuffer, *FLASHFileStartCluster, FILE_CLUSTERS(FLASH_FILE_SIZE_BYTES)); + +			/* Cluster 2+n onwards: Cluster chain of EEPROM.BIN */ +			UpdateFAT12ClusterChain(BlockBuffer, *EEPROMFileStartCluster, FILE_CLUSTERS(EEPROM_FILE_SIZE_BYTES)); + +			break; + +		case DISK_BLOCK_RootFilesBlock: +			memcpy(BlockBuffer, FirmwareFileEntries, sizeof(FirmwareFileEntries)); + +			break; + +		default: +			ReadWriteFLASHFileBlock(BlockNumber, BlockBuffer, true); +			ReadWriteEEPROMFileBlock(BlockNumber, BlockBuffer, true); + +			break; +	} + +	/* Write the entire read block Buffer to the host */ +	Endpoint_Write_Stream_LE(BlockBuffer, sizeof(BlockBuffer), NULL); +	Endpoint_ClearIN(); +} | 
