// Copyright 2011 Olivier Gillet. // // Author: Olivier Gillet (ol.gillet@gmail.com) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ----------------------------------------------------------------------------- // // Provides minimal support for reading a FAT16/FAT32 file system. Limitations: // - Read-only. // - The API only exposes the root directory ; though there's a hacky way of // reading other directories through manipulation of the not-so-opaque handle. // - Files must be represented by a 83 name when loaded. This is not a big // problem since this code will be used to open only one pre-determined file // anyway. // // The safe template parameter enables: // - More error checking of arguments / call sequences. // - Concurrent read/write/enumeration of several files/directories. #ifndef AVRLIBX_FILESYSTEM_FAT_FILE_READER_H_ #define AVRLIBX_FILESYSTEM_FAT_FILE_READER_H_ #include #include "avrlibx/avrlibx.h" namespace avrlibx { struct Partition { uint8_t state; uint8_t start_head; uint16_t start_cylinder_sector; uint8_t type; uint8_t end_head; uint16_t end_cylinder_sector; uint32_t sector_offset; uint32_t num_sectors; }; struct MBR { uint8_t code[446]; Partition partition[4]; uint8_t signature; }; struct BootSector { uint8_t jmp_boot[3]; char oem_name[8]; uint16_t bytes_per_sector; uint8_t sec_per_cluster; uint16_t reserved_sec_count; uint8_t num_fats; uint16_t root_entry_count; uint16_t total_sec; uint8_t media; uint16_t fat_size; uint16_t sector_per_track; uint16_t num_heads; uint32_t hidden_sec; uint32_t total_sector; union { struct { uint16_t drive_number; uint8_t ext_signature; uint32_t volume_id; char label[11]; char fs_type[8]; uint8_t padding[28]; } fat16; struct { uint32_t fat_size; uint16_t flags; uint16_t version; uint32_t root_sector; uint16_t info_sector; uint16_t backup_sector; uint8_t reserved[12]; uint16_t drive_number; uint8_t ext_signature; uint32_t volume_id; char label[11]; char fs_type[8]; } fat32; }; uint8_t bootstrap_code[420]; uint16_t signature; }; enum DirAttribute { FILE_READ_ONLY = 1, FILE_HIDDEN = 2, FILE_SYSTEM = 4, FILE_VOLUME = 8, FILE_LFN = 15, FILE_DIRECTORY = 16, FILE_ARCHIVE = 32, FILE_ATTRIBUTES = 0x3f, }; struct DirectoryEntry { char name[11]; uint8_t attribute; uint8_t reserved; uint8_t creation_time_tenth; uint32_t creation_time; uint16_t last_access_time; uint16_t first_cluster_high; uint32_t last_write_time; uint16_t first_cluster; uint32_t file_size; uint8_t is_volume() { return attribute & FILE_VOLUME; } uint8_t is_file() { return !(attribute & (FILE_VOLUME | FILE_DIRECTORY)); } }; union Sector { uint8_t bytes[512]; uint16_t words[256]; uint32_t dwords[128]; DirectoryEntry entries[16]; MBR mbr; BootSector boot; }; enum FatFileReaderStatus { FFR_OK = 0, FFR_ERROR_INIT, FFR_ERROR_READ, FFR_ERROR_DISK_FORMAT_ERROR, FFR_ERROR_NO_FAT, FFR_ERROR_NO_MORE_FILES, FFR_ERROR_BAD_FILE, FFR_ERROR_FILE_NOT_FOUND, }; enum FatType { FFR_FAT_UNKNOWN = 0, FFR_FAT16 = 16, FFR_FAT32 = 32 }; enum HandleType { FFR_DIR_HANDLE = 0, FFR_FILE_HANDLE }; struct FsHandle { HandleType type; // Position of the sector within the cluster. When this is equal to // sector_size_ (number of sectors per cluster), we should use the FAT to // follow the link to the next sector. uint8_t cluster_position; // Position within file (byte) or directory list (entry#). uint16_t cursor; // The cluster/sector to be read at the next Read/Next operation. uint32_t cluster; uint32_t sector; // The cluster read during the most recent operation. uint32_t current_sector; DirectoryEntry entry; uint8_t eof() { return entry.file_size == 0; } }; template class FATFileReader { public: FATFileReader() { } // Init the media access layer and look for a FAT file system in the first // partition. static FatFileReaderStatus Init() { fat_type_ = FFR_FAT_UNKNOWN; if (Media::Init()) { return FFR_ERROR_INIT; } // Is the VBR on sector 0? uint32_t boot_sector = 0; FatFileReaderStatus status = FindBootSector(boot_sector); if (status == FFR_ERROR_READ || status == FFR_ERROR_DISK_FORMAT_ERROR) { return status; } if (status == FFR_ERROR_NO_FAT) { // There is a partition table. Read first partition. boot_sector = sector_.mbr.partition[0].sector_offset; status = FindBootSector(boot_sector); if (status != FFR_OK) { return status; } } // Read FS layout. uint32_t fat_size = sector_.boot.fat_size; if (fat_type_ == FFR_FAT32) { fat_size = sector_.boot.fat32.fat_size; } if (safe) { fat_size *= sector_.boot.num_fats; } else { if (sector_.boot.num_fats == 2) { fat_size += fat_size; } } fat_sector_ = boot_sector + sector_.boot.reserved_sec_count; cluster_size_ = sector_.boot.sec_per_cluster; uint32_t start = fat_sector_ + fat_size; root_dir_ = fat_type_ == FFR_FAT32 ? sector_.boot.fat32.root_sector : start; data_sector_ = start + (sector_.boot.root_entry_count / 16); return FFR_OK; } // Open the root directory and start iterating on the file list. static FatFileReaderStatus OpenRootDir(FsHandle* handle) { memset(handle, 0, sizeof(FsHandle)); if (fat_type_ == FFR_FAT32) { handle->cluster = root_dir_; handle->sector = cluster_to_sector(root_dir_); } else { handle->sector = root_dir_; } return FFR_OK; } // Iterate on the next file in the opened directory. static FatFileReaderStatus Next(FsHandle* handle) { if (safe && handle->type != FFR_DIR_HANDLE) { return FFR_ERROR_NO_MORE_FILES; } if (safe && SyncCache(handle)) { return FFR_ERROR_READ; } while (1) { // Every 16th entry, we need to move to the next sector. uint8_t offset = (handle->cursor & 0x0f); if (offset == 0) { // We have reached the end of the cluster on a FAT32 system! if (ReadNextSector(handle)) { return FFR_ERROR_READ; } } ++handle->cursor; memcpy(&handle->entry, §or_.entries[offset], sizeof(DirectoryEntry)); // Stop if end of table is reached. if (handle->entry.name[0] == 0) { break; } // Skip volumes. if (handle->entry.is_volume()) { continue; } // Skip current directory, or deleted files if (handle->entry.name[0] == '.' || handle->entry.name[0] == 0xe5) { continue; } return FFR_OK; } return FFR_ERROR_NO_MORE_FILES; }; // Open a file identified by a 83 name. static FatFileReaderStatus Open(const char* name83, FsHandle* handle) { OpenRootDir(handle); while (Next(handle) == FFR_OK) { if (!memcmp(name83, handle->entry.name, 11)) { return Open(handle); } } return FFR_ERROR_FILE_NOT_FOUND; } // Open a file identified by a directory handle. static FatFileReaderStatus Open(FsHandle* handle) { if (safe && (handle->type != FFR_DIR_HANDLE || handle->entry.name[0] == 0 || handle->entry.name[0] == 0xe5 || (!handle->entry.is_file()))) { return FFR_ERROR_BAD_FILE; } LongWord c; c.words[0] = handle->entry.first_cluster; c.words[1] = handle->entry.first_cluster_high; uint32_t cluster = c.value; if (!is_valid_cluster(cluster)) { return FFR_ERROR_BAD_FILE; } handle->type = FFR_FILE_HANDLE; handle->cluster = cluster; handle->cluster_position = 0; handle->sector = cluster_to_sector(cluster); handle->cursor = 0; return FFR_OK; } // Read data from a file. static uint16_t Read(FsHandle* handle, uint16_t size, uint8_t* buffer) { if (safe && handle->type != FFR_FILE_HANDLE) { return 0; } if (safe && SyncCache(handle)) { return FFR_ERROR_READ; } uint16_t read = 0; uint32_t remaining = handle->entry.file_size; while (size && remaining) { if (handle->cursor == 0) { if (ReadNextSector(handle)) { break; } } uint16_t readable = 512 - handle->cursor; if (readable > size) { readable = size; } if (readable > remaining) { readable = remaining; } uint16_t count = readable; while (count) { *buffer++ = sector_.bytes[handle->cursor++]; --count; } size -= readable; read += readable; remaining -= readable; if (handle->cursor == 512) { handle->cursor = 0; } } handle->entry.file_size = remaining; return read; } private: // Check if a cluster number is valid. static inline uint8_t is_valid_cluster(uint32_t cluster) { if (fat_type_ == FFR_FAT16) { return (cluster >= 2 && cluster <= 0xffef); } else { return (cluster >= 2 && cluster <= 0x0fffffef); } } // Follow the FAT linked list. static uint32_t NextCluster(uint32_t cluster) { if (cluster < 2) { return 0; } uint32_t fat_sector = fat_sector_; fat_sector += (fat_type_ == FFR_FAT16) ? (cluster >> 8) : (cluster >> 7); if (ReadSector(fat_sector)) { return 0; } uint32_t next_cluster = (fat_type_ == FFR_FAT16) ? sector_.words[cluster & 0xff] : sector_.dwords[cluster & 0x7f] & 0x0fffffff; return next_cluster; } // Check that the sector fetched into memory is the right one for the present // file read / directory iteration operation. If this is not the case, read // from media layer. static uint8_t SyncCache(FsHandle* handle) __attribute__((noinline)) { if (fetched_sector_ != handle->current_sector) { return ReadSector(handle->current_sector); } return 0; } // Shortcut for reading a sector into memory. static uint8_t ReadSector(uint32_t sector) __attribute__((noinline)) { if (Media::ReadSectors(sector, 1, sector_.bytes)) { return 1; } else { if (safe) { fetched_sector_ = sector; } return 0; } } // Read the next sector for the current object (directory or file). // If the end of a cluster is reached, get the next cluster from the FAT. static FatFileReaderStatus ReadNextSector(FsHandle* handle) { if (handle->cluster && handle->cluster_position == cluster_size_) { uint32_t next_cluster = NextCluster(handle->cluster); if (next_cluster == 0) { return FFR_ERROR_READ; } else if (!is_valid_cluster(next_cluster)) { return FFR_ERROR_READ; } handle->cluster = next_cluster; handle->sector = cluster_to_sector(handle->cluster); handle->cluster_position = 0; } if (ReadSector(handle->sector)) { return FFR_ERROR_READ; } if (safe) { handle->current_sector = handle->sector; } ++handle->sector; ++handle->cluster_position; return FFR_OK; } // Convert a cluster index to a sector address. static uint32_t cluster_to_sector(uint32_t cluster) __attribute__((noinline)) { cluster -= 2; uint8_t shift = cluster_size_; shift >>= 1; while (shift) { shift >>= 1; cluster <<= 1; } return cluster + data_sector_; } // Look for a FAT FS at a given sector. static FatFileReaderStatus FindBootSector(uint32_t sector) { if (ReadSector(sector)) { return FFR_ERROR_READ; } if (sector_.boot.signature != 0xaa55) { return FFR_ERROR_DISK_FORMAT_ERROR; } if (sector_.boot.fat16.fs_type[0] == 'F' && sector_.boot.fat16.fs_type[1] == 'A') { fat_type_ = FFR_FAT16; return FFR_OK; } if (sector_.boot.fat32.fs_type[0] == 'F' && sector_.boot.fat32.fs_type[1] == 'A') { fat_type_ = FFR_FAT32; return FFR_OK; } return FFR_ERROR_NO_FAT; } static uint32_t fetched_sector_; static Sector sector_; static FatType fat_type_; static uint8_t cluster_size_; // Root directory sector for FAT16 ; cluster for FAT32 static uint32_t root_dir_; static uint32_t fat_sector_; static uint32_t data_sector_; DISALLOW_COPY_AND_ASSIGN(FATFileReader); }; /* static */ template Sector FATFileReader::sector_; /* static */ template uint32_t FATFileReader::fetched_sector_; /* static */ template FatType FATFileReader::fat_type_; /* static */ template uint8_t FATFileReader::cluster_size_; /* static */ template uint32_t FATFileReader::fat_sector_; /* static */ template uint32_t FATFileReader::root_dir_; /* static */ template uint32_t FATFileReader::data_sector_; // This is how the media access layer can be implemented. struct DummyMediaInterface { static uint8_t Init() { return 0; } static uint8_t ReadSectors(uint32_t start, uint8_t num_sectors, uint8_t* data) { memset(data, 0, 512); return 0; } }; } // namespace avrlibx #endif // AVRLIBX_FILESYSTEM_FAT_FILE_READER_H_