This guide explains how to port TinyFS to a new platform or hardware configuration.
- Overview
- Required Steps
- Drive Interface Implementation
- Platform Examples
- Testing Your Port
- Troubleshooting
Porting TinyFS to a new platform involves:
- Creating a platform-specific
filesys_conf.hconfiguration file - Implementing the drive interface functions
- Setting up the build system for your platform
- Testing the implementation
The core filesystem code (filesys.c and filesys.h) is platform-independent and should not need modification.
Create a configuration header file for your platform. This file controls which features are enabled.
Minimal template:
#ifndef _FILESYS_CONF_H
#define _FILESYS_CONF_H
// Feature configuration
#define TFS_ENABLE_FORMAT // Enable if you need format capability
#undef TFS_EXTENDED_API // Enable if you need random access
// Platform-specific configurations
// Add any platform-specific macros or includes here
#endifSee CONFIGURATION.md for detailed information about all configuration options.
Implement these five required functions for your storage hardware:
void drive_init(void);
void drive_select(void);
void drive_deselect(void);
void drive_read_block(uint32_t blkno, uint8_t *data);
void drive_write_block(uint32_t blkno, const uint8_t *data);Your drive_init() function must populate the tfs_drive_info structure:
extern TFS_DRIVE_INFO tfs_drive_info;
// In your drive_init():
tfs_drive_info.type = DRIVE_TYPE_SDHC;
tfs_drive_info.blk_count = /* total number of 512-byte blocks */;
strcpy(tfs_drive_info.model, "Your Device");
strcpy(tfs_drive_info.serno, "12345");All drive functions must set tfs_last_error:
extern uint8_t tfs_last_error;
// Set to TFS_ERR_OK on success
// Set to TFS_ERR_NO_DEV if no device detected
// Set to TFS_ERR_IO on communication errorsInitialize the storage hardware and detect the device.
Responsibilities:
- Initialize hardware interfaces (SPI, GPIO, etc.)
- Detect and initialize the storage device
- Read device information (capacity, model, etc.)
- Populate
tfs_drive_infostructure - Set
tfs_last_errorappropriately
Example for SD card over SPI:
void drive_init(void) {
uint8_t card_type;
uint32_t capacity;
// Initialize SPI hardware
spi_init();
// Initialize MMC/SD card
card_type = mmc_init();
if (card_type == 0) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
// Get card capacity
capacity = mmc_get_capacity();
if (capacity == 0) {
tfs_last_error = TFS_ERR_IO;
return;
}
// Populate drive info
tfs_drive_info.type = card_type;
tfs_drive_info.blk_count = capacity;
// Optionally read CID for model/serial
mmc_read_cid(tfs_drive_info.model, tfs_drive_info.serno);
tfs_last_error = TFS_ERR_OK;
}Example for emulated storage (file-backed):
#include <stdio.h>
#include <sys/stat.h>
static FILE *disk_file = NULL;
void drive_init(void) {
struct stat st;
// Open disk image file
disk_file = fopen("disk.img", "r+b");
if (disk_file == NULL) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
// Get file size
if (fstat(fileno(disk_file), &st) != 0) {
fclose(disk_file);
disk_file = NULL;
tfs_last_error = TFS_ERR_IO;
return;
}
// Populate drive info
tfs_drive_info.type = DRIVE_TYPE_EMU;
tfs_drive_info.blk_count = st.st_size / 512;
strcpy(tfs_drive_info.model, "Emulated Disk");
strcpy(tfs_drive_info.serno, "EMU001");
tfs_last_error = TFS_ERR_OK;
}Enable and disable the storage device.
For SPI-based devices:
// Assuming CS (Chip Select) is on GPIO pin
#define SD_CS_PORT PORTB
#define SD_CS_DDR DDRB
#define SD_CS_PIN PB2
void drive_select(void) {
// Pull CS low to select device
SD_CS_PORT &= ~(1 << SD_CS_PIN);
}
void drive_deselect(void) {
// Pull CS high to deselect device
SD_CS_PORT |= (1 << SD_CS_PIN);
}For direct attached devices:
// If the device is always active, these can be empty
void drive_select(void) {
// Nothing to do
}
void drive_deselect(void) {
// Nothing to do
}For devices with enable pins:
void drive_select(void) {
// Enable power or chip enable
DEVICE_ENABLE_PORT |= DEVICE_ENABLE_PIN;
// Wait for device to stabilize
delay_us(10);
}
void drive_deselect(void) {
// Disable device
DEVICE_ENABLE_PORT &= ~DEVICE_ENABLE_PIN;
}Read a single 512-byte block from the device.
For SD card over SPI:
void drive_read_block(uint32_t blkno, uint8_t *data) {
if (mmc_read_block(blkno, data)) {
tfs_last_error = TFS_ERR_OK;
} else {
tfs_last_error = TFS_ERR_IO;
}
}For file-backed emulation:
void drive_read_block(uint32_t blkno, uint8_t *data) {
if (disk_file == NULL) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
// Seek to block position
if (fseek(disk_file, blkno * 512, SEEK_SET) != 0) {
tfs_last_error = TFS_ERR_IO;
return;
}
// Read block
if (fread(data, 512, 1, disk_file) != 1) {
tfs_last_error = TFS_ERR_IO;
return;
}
tfs_last_error = TFS_ERR_OK;
}Important considerations:
- Must read exactly 512 bytes
- Must handle block addressing correctly (some cards use byte address, others use block address)
- Must set
tfs_last_errorappropriately - Should handle timeouts and errors gracefully
Write a single 512-byte block to the device.
For SD card over SPI:
void drive_write_block(uint32_t blkno, const uint8_t *data) {
if (mmc_write_block(blkno, data)) {
tfs_last_error = TFS_ERR_OK;
} else {
tfs_last_error = TFS_ERR_IO;
}
}For file-backed emulation:
void drive_write_block(uint32_t blkno, const uint8_t *data) {
if (disk_file == NULL) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
// Seek to block position
if (fseek(disk_file, blkno * 512, SEEK_SET) != 0) {
tfs_last_error = TFS_ERR_IO;
return;
}
// Write block
if (fwrite(data, 512, 1, disk_file) != 1) {
tfs_last_error = TFS_ERR_IO;
return;
}
// Flush to ensure write completes
fflush(disk_file);
tfs_last_error = TFS_ERR_OK;
}Important considerations:
- Must write exactly 512 bytes
- Must wait for write to complete before returning
- Must handle write protection
- Must set
tfs_last_errorappropriately
Files:
avr/filesys_conf.h- Configurationavr/spi.c- SPI driveravr/main.c- Main programmmc.c- MMC/SD card driver (shared)
Configuration (avr/filesys_conf.h):
#ifndef _FILESYS_CONF_H
#define _FILESYS_CONF_H
#define TFS_ENABLE_FORMAT
#undef TFS_EXTENDED_API
#define spi_send_byte(b) spi_transfer_byte(b)
#define spi_rec_byte() spi_transfer_byte(0xff)
uint8_t spi_transfer_byte(uint8_t b);
#endifSPI Driver (avr/spi.c):
#include <avr/io.h>
void spi_init(void) {
// Set MOSI, SCK, SS as output
DDRB |= (1 << PB3) | (1 << PB5) | (1 << PB2);
// Enable SPI, Master mode, clock/16
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}
uint8_t spi_transfer_byte(uint8_t data) {
SPDR = data;
while (!(SPSR & (1 << SPIF)));
return SPDR;
}Drive Interface (avr/main.c):
#include "../filesys.h"
#include "../mmc.h"
void drive_init(void) {
spi_init();
mmc_card_init(); // From mmc.c
}
void drive_select(void) {
PORTB &= ~(1 << PB2); // CS low
}
void drive_deselect(void) {
PORTB |= (1 << PB2); // CS high
}
void drive_read_block(uint32_t blkno, uint8_t *data) {
if (mmc_read_single_block(blkno, data)) {
tfs_last_error = TFS_ERR_OK;
} else {
tfs_last_error = TFS_ERR_IO;
}
}
void drive_write_block(uint32_t blkno, const uint8_t *data) {
if (mmc_write_single_block(blkno, data)) {
tfs_last_error = TFS_ERR_OK;
} else {
tfs_last_error = TFS_ERR_IO;
}
}Files:
linux/filesys_conf.h- Configurationlinux/drive.c- Drive interfacelinux/tfs_fuse.c- FUSE integration
Configuration (linux/filesys_conf.h):
#ifndef _FILESYS_CONF_H
#define _FILESYS_CONF_H
#include <fuse.h>
#define TFS_ENABLE_FORMAT
#define TFS_FORMAT_STATE_CALLBACK
#define TFS_EXTENDED_API
#define TFS_MAX_FDS 32
typedef struct {
void *buffer;
fuse_fill_dir_t filler;
} TFS_READDIR_FILLER;
#define TFS_READ_DIR_USERDATA const TFS_READDIR_FILLER *
#endifDrive Interface (linux/drive.c):
#include "../filesys.h"
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
static FILE *disk_file = NULL;
static char *disk_path = NULL;
void drive_set_path(const char *path) {
disk_path = strdup(path);
}
void drive_init(void) {
struct stat st;
if (disk_path == NULL) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
disk_file = fopen(disk_path, "r+b");
if (disk_file == NULL) {
tfs_last_error = TFS_ERR_NO_DEV;
return;
}
fstat(fileno(disk_file), &st);
tfs_drive_info.type = DRIVE_TYPE_EMU;
tfs_drive_info.blk_count = st.st_size / 512;
strcpy(tfs_drive_info.model, "Linux File");
strcpy(tfs_drive_info.serno, "FILE001");
tfs_last_error = TFS_ERR_OK;
}
void drive_select(void) {
// Nothing to do
}
void drive_deselect(void) {
// Nothing to do
}
void drive_read_block(uint32_t blkno, uint8_t *data) {
if (fseek(disk_file, blkno * 512, SEEK_SET) != 0 ||
fread(data, 512, 1, disk_file) != 1) {
tfs_last_error = TFS_ERR_IO;
} else {
tfs_last_error = TFS_ERR_OK;
}
}
void drive_write_block(uint32_t blkno, const uint8_t *data) {
if (fseek(disk_file, blkno * 512, SEEK_SET) != 0 ||
fwrite(data, 512, 1, disk_file) != 1) {
tfs_last_error = TFS_ERR_IO;
} else {
fflush(disk_file);
tfs_last_error = TFS_ERR_OK;
}
}Files:
zx81/filesys_conf.h- Configurationzx81/spi.c- Custom hardware SPI implementationmmc.c- MMC/SD card driver (shared)
Configuration (zx81/filesys_conf.h):
#ifndef _FILESYS_CONF_H
#define _FILESYS_CONF_H
// No format support (done externally to save space)
#undef TFS_ENABLE_FORMAT
// No extended API (RAM constrained)
#undef TFS_EXTENDED_API
// Case-insensitive filenames
#define TFS_FILENAME_CMP(ref, cmp) filename_cmp(ref, cmp)
uint8_t filename_cmp(const char *ref, const char *cmp);
// SPI macros
#define spi_send_byte(b) spi_transfer_byte(b)
#define spi_rec_byte() spi_transfer_byte(0xff)
uint8_t spi_transfer_byte(uint8_t b);
#endifKey differences:
- Case-insensitive filename comparison (ZX81 character set)
- No formatting (save ROM space)
- No extended API (save RAM)
- Custom hardware SPI implementation (specially designed hardware for SPI operations)
Ensure the code compiles without errors:
# Replace with your compiler
gcc -c filesys.c -I. -o filesys.oTest basic initialization:
int main(void) {
tfs_init();
if (tfs_last_error != TFS_ERR_OK) {
printf("Init failed: %d\n", tfs_last_error);
return 1;
}
printf("Device initialized\n");
printf("Type: %d\n", tfs_drive_info.type);
printf("Blocks: %u\n", tfs_drive_info.blk_count);
return 0;
}If formatting is enabled:
void test_format(void) {
printf("Formatting...\n");
tfs_format();
if (tfs_last_error != TFS_ERR_OK) {
printf("Format failed: %d\n", tfs_last_error);
return;
}
printf("Format successful\n");
}Test basic file operations:
void test_read_write(void) {
const char *test_data = "Hello, TinyFS!";
uint8_t buffer[100];
// Write file
tfs_write_file("test.txt", (uint8_t*)test_data, strlen(test_data), 1);
if (tfs_last_error != TFS_ERR_OK) {
printf("Write failed: %d\n", tfs_last_error);
return;
}
// Read file back
uint32_t bytes = tfs_read_file("test.txt", buffer, sizeof(buffer));
buffer[bytes] = '\0';
if (tfs_last_error != TFS_ERR_OK) {
printf("Read failed: %d\n", tfs_last_error);
return;
}
// Compare
if (strcmp((char*)buffer, test_data) == 0) {
printf("Read/Write test PASSED\n");
} else {
printf("Read/Write test FAILED\n");
}
}Test with larger files and multiple operations:
void stress_test(void) {
uint8_t buffer[512];
int i;
// Write multiple files
for (i = 0; i < 10; i++) {
char name[20];
sprintf(name, "file%d.dat", i);
// Fill buffer with pattern
memset(buffer, i, sizeof(buffer));
tfs_write_file(name, buffer, sizeof(buffer), 1);
if (tfs_last_error != TFS_ERR_OK) {
printf("Write %d failed: %d\n", i, tfs_last_error);
return;
}
}
// Read and verify
for (i = 0; i < 10; i++) {
char name[20];
sprintf(name, "file%d.dat", i);
memset(buffer, 0, sizeof(buffer));
tfs_read_file(name, buffer, sizeof(buffer));
if (tfs_last_error != TFS_ERR_OK) {
printf("Read %d failed: %d\n", i, tfs_last_error);
return;
}
// Verify pattern
int j;
for (j = 0; j < 512; j++) {
if (buffer[j] != (uint8_t)i) {
printf("Verify %d failed at byte %d\n", i, j);
return;
}
}
}
printf("Stress test PASSED\n");
}Symptom: tfs_init() sets tfs_last_error = TFS_ERR_NO_DEV
Possible causes:
- Hardware not connected or powered
- SPI/communication interface not initialized
- Wrong GPIO pins configured
- Card initialization sequence incorrect
Debug steps:
- Check hardware connections
- Verify power supply to card
- Check SPI clock and data lines with oscilloscope
- Add debug output to
drive_init()to see where it fails
Symptom: tfs_last_error = TFS_ERR_IO during read/write
Possible causes:
- Block addressing wrong (byte vs. block address)
- Timeout in communication
- Card removed or failed
- Buffer overflow or corruption
Debug steps:
- Verify block address calculation
- Check read/write timing requirements
- Add timeouts to prevent infinite loops
- Verify 512-byte buffer alignment
Symptom: Data read back doesn't match what was written
Possible causes:
- Byte order issues (endianness)
- Buffer overrun
- Incomplete writes
- Caching issues
Debug steps:
- Write known pattern and verify
- Check if write completes before read
- Flush buffers after writes
- Verify no buffer sharing issues
Symptom: System crashes or behaves erratically
Possible causes:
- Stack too small for 512-byte buffers
- Recursive functions on constrained systems
- Too many file descriptors configured
Solutions:
- Increase stack size
- Reduce
TFS_MAX_FDSif using Extended API - Check static buffer allocation
- Avoid large local variables in functions
Symptom: Operations are very slow
Possible causes:
- SPI clock too slow
- Excessive bitmap block reads
- Fragmented files
- Debug output slowing down operations
Solutions:
- Increase SPI clock frequency (within card limits)
- Ensure bitmap caching is working
- Format device to defragment
- Remove debug output from hot paths
- See ARCHITECTURE.md for filesystem internals
- See API_REFERENCE.md for function details
- See CONFIGURATION.md for configuration options
- Check existing platform implementations in
avr/,linux/, andzx81/directories
- Created
filesys_conf.hwith appropriate feature flags - Implemented
drive_init() - Implemented
drive_select() - Implemented
drive_deselect() - Implemented
drive_read_block() - Implemented
drive_write_block() - Populated
tfs_drive_infostructure - Set
tfs_last_errorin all drive functions - Code compiles without errors
- Initialization test passes
- Format test passes (if enabled)
- Read/write test passes
- Stress test passes
- Performance is acceptable
- Documentation updated with platform-specific notes