// 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_