diff options
Diffstat (limited to 'Bootloaders/MassStorage/Lib')
-rw-r--r-- | Bootloaders/MassStorage/Lib/SCSI.c | 294 | ||||
-rw-r--r-- | Bootloaders/MassStorage/Lib/SCSI.h | 84 | ||||
-rw-r--r-- | Bootloaders/MassStorage/Lib/VirtualFAT.c | 482 | ||||
-rw-r--r-- | Bootloaders/MassStorage/Lib/VirtualFAT.h | 302 |
4 files changed, 1162 insertions, 0 deletions
diff --git a/Bootloaders/MassStorage/Lib/SCSI.c b/Bootloaders/MassStorage/Lib/SCSI.c new file mode 100644 index 0000000000..3c14eb9010 --- /dev/null +++ b/Bootloaders/MassStorage/Lib/SCSI.c @@ -0,0 +1,294 @@ +/* + 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 + * + * SCSI command processing routines, for SCSI commands issued by the host. Mass Storage + * devices use a thin "Bulk-Only Transport" protocol for issuing commands and status information, + * which wrap around standard SCSI device commands for controlling the actual storage medium. + */ + +#define INCLUDE_FROM_SCSI_C +#include "SCSI.h" + +/** Structure to hold the SCSI response data to a SCSI INQUIRY command. This gives information about the device's + * features and capabilities. + */ +static const SCSI_Inquiry_Response_t InquiryData = + { + .DeviceType = DEVICE_TYPE_BLOCK, + .PeripheralQualifier = 0, + + .Removable = true, + + .Version = 0, + + .ResponseDataFormat = 2, + .NormACA = false, + .TrmTsk = false, + .AERC = false, + + .AdditionalLength = 0x1F, + + .SoftReset = false, + .CmdQue = false, + .Linked = false, + .Sync = false, + .WideBus16Bit = false, + .WideBus32Bit = false, + .RelAddr = false, + + .VendorID = "LUFA", + .ProductID = "Bootloader", + .RevisionID = {'0','.','0','0'}, + }; + +/** Structure to hold the sense data for the last issued SCSI command, which is returned to the host after a SCSI REQUEST SENSE + * command is issued. This gives information on exactly why the last command failed to complete. + */ +static SCSI_Request_Sense_Response_t SenseData = + { + .ResponseCode = 0x70, + .AdditionalLength = 0x0A, + }; + + +/** Main routine to process the SCSI command located in the Command Block Wrapper read from the host. This dispatches + * to the appropriate SCSI command handling routine if the issued command is supported by the device, else it returns + * a command failure due to a ILLEGAL REQUEST. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * + * \return Boolean \c true if the command completed successfully, \c false otherwise + */ +bool SCSI_DecodeSCSICommand(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) +{ + bool CommandSuccess = false; + + /* Run the appropriate SCSI command hander function based on the passed command */ + switch (MSInterfaceInfo->State.CommandBlock.SCSICommandData[0]) + { + case SCSI_CMD_INQUIRY: + CommandSuccess = SCSI_Command_Inquiry(MSInterfaceInfo); + break; + case SCSI_CMD_REQUEST_SENSE: + CommandSuccess = SCSI_Command_Request_Sense(MSInterfaceInfo); + break; + case SCSI_CMD_READ_CAPACITY_10: + CommandSuccess = SCSI_Command_Read_Capacity_10(MSInterfaceInfo); + break; + case SCSI_CMD_WRITE_10: + CommandSuccess = SCSI_Command_ReadWrite_10(MSInterfaceInfo, DATA_WRITE); + break; + case SCSI_CMD_READ_10: + CommandSuccess = SCSI_Command_ReadWrite_10(MSInterfaceInfo, DATA_READ); + break; + case SCSI_CMD_MODE_SENSE_6: + CommandSuccess = SCSI_Command_ModeSense_6(MSInterfaceInfo); + break; + case SCSI_CMD_START_STOP_UNIT: +#if !defined(NO_APP_START_ON_EJECT) + /* If the user ejected the volume, signal bootloader exit at next opportunity. */ + RunBootloader = ((MSInterfaceInfo->State.CommandBlock.SCSICommandData[4] & 0x03) != 0x02); +#endif + case SCSI_CMD_SEND_DIAGNOSTIC: + case SCSI_CMD_TEST_UNIT_READY: + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + case SCSI_CMD_VERIFY_10: + /* These commands should just succeed, no handling required */ + CommandSuccess = true; + MSInterfaceInfo->State.CommandBlock.DataTransferLength = 0; + break; + default: + /* Update the SENSE key to reflect the invalid command */ + SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_COMMAND, + SCSI_ASENSEQ_NO_QUALIFIER); + break; + } + + /* Check if command was successfully processed */ + if (CommandSuccess) + { + SCSI_SET_SENSE(SCSI_SENSE_KEY_GOOD, + SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, + SCSI_ASENSEQ_NO_QUALIFIER); + + return true; + } + + return false; +} + +/** Command processing for an issued SCSI INQUIRY command. This command returns information about the device's features + * and capabilities to the host. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * + * \return Boolean \c true if the command completed successfully, \c false otherwise. + */ +static bool SCSI_Command_Inquiry(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) +{ + uint16_t AllocationLength = SwapEndian_16(*(uint16_t*)&MSInterfaceInfo->State.CommandBlock.SCSICommandData[3]); + uint16_t BytesTransferred = MIN(AllocationLength, sizeof(InquiryData)); + + /* Only the standard INQUIRY data is supported, check if any optional INQUIRY bits set */ + if ((MSInterfaceInfo->State.CommandBlock.SCSICommandData[1] & ((1 << 0) | (1 << 1))) || + MSInterfaceInfo->State.CommandBlock.SCSICommandData[2]) + { + /* Optional but unsupported bits set - update the SENSE key and fail the request */ + SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, + SCSI_ASENSEQ_NO_QUALIFIER); + + return false; + } + + Endpoint_Write_Stream_LE(&InquiryData, BytesTransferred, NULL); + + /* Pad out remaining bytes with 0x00 */ + Endpoint_Null_Stream((AllocationLength - BytesTransferred), NULL); + + /* Finalize the stream transfer to send the last packet */ + Endpoint_ClearIN(); + + /* Succeed the command and update the bytes transferred counter */ + MSInterfaceInfo->State.CommandBlock.DataTransferLength -= BytesTransferred; + + return true; +} + +/** Command processing for an issued SCSI REQUEST SENSE command. This command returns information about the last issued command, + * including the error code and additional error information so that the host can determine why a command failed to complete. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * + * \return Boolean \c true if the command completed successfully, \c false otherwise. + */ +static bool SCSI_Command_Request_Sense(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) +{ + uint8_t AllocationLength = MSInterfaceInfo->State.CommandBlock.SCSICommandData[4]; + uint8_t BytesTransferred = MIN(AllocationLength, sizeof(SenseData)); + + Endpoint_Write_Stream_LE(&SenseData, BytesTransferred, NULL); + Endpoint_Null_Stream((AllocationLength - BytesTransferred), NULL); + Endpoint_ClearIN(); + + /* Succeed the command and update the bytes transferred counter */ + MSInterfaceInfo->State.CommandBlock.DataTransferLength -= BytesTransferred; + + return true; +} + +/** Command processing for an issued SCSI READ CAPACITY (10) command. This command returns information about the device's capacity + * on the selected Logical Unit (drive), as a number of OS-sized blocks. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * + * \return Boolean \c true if the command completed successfully, \c false otherwise. + */ +static bool SCSI_Command_Read_Capacity_10(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) +{ + Endpoint_Write_32_BE(LUN_MEDIA_BLOCKS - 1); + Endpoint_Write_32_BE(SECTOR_SIZE_BYTES); + Endpoint_ClearIN(); + + /* Succeed the command and update the bytes transferred counter */ + MSInterfaceInfo->State.CommandBlock.DataTransferLength -= 8; + + return true; +} + +/** Command processing for an issued SCSI READ (10) or WRITE (10) command. This command reads in the block start address + * and total number of blocks to process, then calls the appropriate low-level Dataflash routine to handle the actual + * reading and writing of the data. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * \param[in] IsDataRead Indicates if the command is a READ (10) command or WRITE (10) command (DATA_READ or DATA_WRITE) + * + * \return Boolean \c true if the command completed successfully, \c false otherwise. + */ +static bool SCSI_Command_ReadWrite_10(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo, + const bool IsDataRead) +{ + uint16_t BlockAddress; + uint16_t TotalBlocks; + + /* Load in the 32-bit block address (SCSI uses big-endian, so have to reverse the byte order) */ + BlockAddress = SwapEndian_32(*(uint32_t*)&MSInterfaceInfo->State.CommandBlock.SCSICommandData[2]); + + /* Load in the 16-bit total blocks (SCSI uses big-endian, so have to reverse the byte order) */ + TotalBlocks = SwapEndian_16(*(uint16_t*)&MSInterfaceInfo->State.CommandBlock.SCSICommandData[7]); + + /* Check if the block address is outside the maximum allowable value for the LUN */ + if (BlockAddress >= LUN_MEDIA_BLOCKS) + { + /* Block address is invalid, update SENSE key and return command fail */ + SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + SCSI_ASENSEQ_NO_QUALIFIER); + + return false; + } + + /* Determine if the packet is a READ (10) or WRITE (10) command, call appropriate function */ + for (uint16_t i = 0; i < TotalBlocks; i++) + { + if (IsDataRead == DATA_READ) + VirtualFAT_ReadBlock(BlockAddress + i); + else + VirtualFAT_WriteBlock(BlockAddress + i); + } + + /* Update the bytes transferred counter and succeed the command */ + MSInterfaceInfo->State.CommandBlock.DataTransferLength -= ((uint32_t)TotalBlocks * SECTOR_SIZE_BYTES); + + return true; +} + +/** Command processing for an issued SCSI MODE SENSE (6) command. This command returns various informational pages about + * the SCSI device, as well as the device's Write Protect status. + * + * \param[in] MSInterfaceInfo Pointer to the Mass Storage class interface structure that the command is associated with + * + * \return Boolean \c true if the command completed successfully, \c false otherwise. + */ +static bool SCSI_Command_ModeSense_6(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) +{ + /* Send an empty header response indicating Write Protect flag is off */ + Endpoint_Write_32_LE(0); + Endpoint_ClearIN(); + + /* Update the bytes transferred counter and succeed the command */ + MSInterfaceInfo->State.CommandBlock.DataTransferLength -= 4; + + return true; +} + diff --git a/Bootloaders/MassStorage/Lib/SCSI.h b/Bootloaders/MassStorage/Lib/SCSI.h new file mode 100644 index 0000000000..4195593363 --- /dev/null +++ b/Bootloaders/MassStorage/Lib/SCSI.h @@ -0,0 +1,84 @@ +/* + 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 + * + * Header file for SCSI.c. + */ + +#ifndef _SCSI_H_ +#define _SCSI_H_ + + /* Includes: */ + #include <avr/io.h> + #include <avr/pgmspace.h> + + #include <LUFA/Drivers/USB/USB.h> + + #include "../BootloaderMassStorage.h" + #include "../Descriptors.h" + #include "VirtualFAT.h" + + /* Macros: */ + /** Macro to set the current SCSI sense data to the given key, additional sense code and additional sense qualifier. This + * is for convenience, as it allows for all three sense values (returned upon request to the host to give information about + * the last command failure) in a quick and easy manner. + * + * \param[in] Key New SCSI sense key to set the sense code to + * \param[in] Acode New SCSI additional sense key to set the additional sense code to + * \param[in] Aqual New SCSI additional sense key qualifier to set the additional sense qualifier code to + */ + #define SCSI_SET_SENSE(Key, Acode, Aqual) do { SenseData.SenseKey = (Key); \ + SenseData.AdditionalSenseCode = (Acode); \ + SenseData.AdditionalSenseQualifier = (Aqual); } while (0) + + /** Macro for the \ref SCSI_Command_ReadWrite_10() function, to indicate that data is to be read from the storage medium. */ + #define DATA_READ true + + /** Macro for the \ref SCSI_Command_ReadWrite_10() function, to indicate that data is to be written to the storage medium. */ + #define DATA_WRITE false + + /** Value for the DeviceType entry in the SCSI_Inquiry_Response_t enum, indicating a Block Media device. */ + #define DEVICE_TYPE_BLOCK 0x00 + + /* Function Prototypes: */ + bool SCSI_DecodeSCSICommand(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) AUX_BOOT_SECTION; + + #if defined(INCLUDE_FROM_SCSI_C) + static bool SCSI_Command_Inquiry(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) AUX_BOOT_SECTION; + static bool SCSI_Command_Request_Sense(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) AUX_BOOT_SECTION; + static bool SCSI_Command_Read_Capacity_10(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) AUX_BOOT_SECTION; + static bool SCSI_Command_ReadWrite_10(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo, + const bool IsDataRead) AUX_BOOT_SECTION; + static bool SCSI_Command_ModeSense_6(USB_ClassInfo_MS_Device_t* const MSInterfaceInfo) AUX_BOOT_SECTION; + #endif + +#endif + 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(); +} diff --git a/Bootloaders/MassStorage/Lib/VirtualFAT.h b/Bootloaders/MassStorage/Lib/VirtualFAT.h new file mode 100644 index 0000000000..ea80eae4d6 --- /dev/null +++ b/Bootloaders/MassStorage/Lib/VirtualFAT.h @@ -0,0 +1,302 @@ +/* + 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. +*/ + +#ifndef _VIRTUALFAT_H_ +#define _VIRTUALFAT_H_ + + /* Includes: */ + #include <avr/io.h> + #include <avr/pgmspace.h> + + #include <LUFA/Drivers/USB/USB.h> + + #include "../BootloaderAPI.h" + + /* Macros: */ + /** Size of the virtual FLASH.BIN file in bytes. */ + #define FLASH_FILE_SIZE_BYTES (FLASHEND - (FLASHEND - BOOT_START_ADDR) - AUX_BOOT_SECTION_SIZE) + + /** Size of the virtual EEPROM.BIN file in bytes. */ + #define EEPROM_FILE_SIZE_BYTES E2END + + /** Number of sectors that comprise a single logical disk cluster. */ + #define SECTOR_PER_CLUSTER 4 + + /** Size of a single logical sector on the disk. */ + #define SECTOR_SIZE_BYTES 512 + + /** Size of a logical cluster on the disk, in bytes */ + #define CLUSTER_SIZE_BYTES (SECTOR_PER_CLUSTER * SECTOR_SIZE_BYTES) + + /** Number of sectors required to store a given size in bytes. + * + * \param[in] size Size of the data that needs to be stored + * + * \return Number of sectors required to store the given data on the disk. + */ + #define FILE_SECTORS(size) ((size / SECTOR_SIZE_BYTES) + ((size % SECTOR_SIZE_BYTES) ? 1 : 0)) + + /** Number of clusters required to store a given size in bytes. + * + * \param[in] size Size of the data that needs to be stored + * + * \return Number of clusters required to store the given data on the disk. + */ + #define FILE_CLUSTERS(size) ((size / CLUSTER_SIZE_BYTES) + ((size % CLUSTER_SIZE_BYTES) ? 1 : 0)) + + /** Total number of logical sectors/blocks on the disk. */ + #define LUN_MEDIA_BLOCKS (FILE_SECTORS(FLASH_FILE_SIZE_BYTES) + FILE_SECTORS(EEPROM_FILE_SIZE_BYTES) + 32) + + /** Converts a given time in HH:MM:SS format to a FAT filesystem time. + * + * \note The minimum seconds resolution of FAT is 2, thus odd seconds + * will be truncated to the previous integer multiple of 2 seconds. + * + * \param[in] hh Hours (0-23) + * \param[in] mm Minutes (0-59) + * \param[in] ss Seconds (0-59) + * + * \return Given time encoded as a FAT filesystem timestamp + */ + #define FAT_TIME(hh, mm, ss) ((hh << 11) | (mm << 5) | (ss >> 1)) + + /** Converts |