Integrating a third-party filesystem like FatFS into RTEMS requires understanding both the RTEMS filesystem architecture and the nuances of bridging two different APIs. Here’s what I learned while implementing the FatFS mount functionality for RTEMS.
The Challenge: Two Different Worlds
RTEMS uses a POSIX-like filesystem abstraction with operations like open(), read(), and write(), while FatFS provides its own API with
functions like f_open(), f_read(), and f_write(). The integration layer must translate between these two worlds seamlessly.
Understanding RTEMS Filesystem Architecture
RTEMS filesystems are built around several key structures:
– rtems_filesystem_operations_table: Contains filesystem-level operations (mount, unmount, path evaluation, etc.)
– rtems_filesystem_file_handlers_r: Contains file-level operations (open, close, read, write, etc.)
– rtems_filesystem_mount_table_entry_t: Describes a mounted filesystem instance
The mount handler is the entry point where everything begins. It’s responsible for:
1. Initializing the filesystem
2. Setting up the root directory
3. Configuring the mount table entry
4. Handling errors gracefully
Key Components of the FatFS Integration
1. The Filesystem Info Structure
typedef struct {
FATFS fatfs; // FatFS filesystem object
rtems_recursive_mutex vol_mutex; // Thread safety
const rtems_filesystem_file_handlers_r *file_handlers;
const rtems_filesystem_file_handlers_r *dir_handlers;
BYTE drive_number; // Drive number for FatFS
char mount_path[256]; // FatFS drive path (“0:”, “1:”, etc.)
} rtems_fatfs_fs_info_t;
This structure bridges RTEMS and FatFS, containing both the FatFS volume object and RTEMS-specific data.
2. The Disk I/O Bridge
One of the most critical pieces is the disk I/O layer (rtems-diskio.c
). FatFS expects certain disk I/O functions (disk_read, disk_write,
disk_ioctl, disk_status), while RTEMS provides block device access through its rtems_bdbuf interface.
The bridge maintains a mapping between FatFS drive numbers and RTEMS block devices:
typedef struct {
int fd; // RTEMS file descriptor
rtems_disk_device *dd; // RTEMS disk device
bool initialized; // Registration state
} fatfs_volume_t;
static fatfs_volume_t volumes[FF_VOLUMES];
3. Error Code Translation
FatFS and RTEMS use different error codes. A translation layer converts FatFS FRESULT codes to POSIX errno values:
static inline int rtems_fatfs_fresult_to_errno(FRESULT fr) {
switch (fr) {
case FR_OK: return 0;
case FR_NO_FILE: return ENOENT;
case FR_DENIED: return EACCES;
case FR_EXIST: return EEXIST;
// … more mappings
}
}
The Mount Process: Step by Step
1. Memory Allocation and Setup
fs_info = calloc(1, sizeof(*fs_info));
root_node = calloc(1, sizeof(*root_node));
rtems_recursive_mutex_init(&fs_info->vol_mutex, RTEMS_FILESYSTEM_TYPE_FATFS);
2. Drive Number Management
FatFS uses numeric drive identifiers (0-9). The mount handler assigns unique drive numbers with wraparound:
fs_info->drive_number = next_drive_number++;
if (next_drive_number >= FF_VOLUMES) {
next_drive_number = 0;
}
3. Device Registration
The RTEMS block device must be registered with the FatFS disk I/O layer:
rc = fatfs_diskio_register_device(fs_info->drive_number, mt_entry->dev);
4. FatFS Mount
Create the drive path and mount the FatFS volume:
snprintf(drive_path, sizeof(drive_path), "%u:", fs_info->drive_number);
fr = f_mount(&fs_info->fatfs, drive_path, 1); // 1 = immediate mount
5. Root Directory Setup
This was trickier than expected. Initially, I tried to use f_stat() to get root directory information, but FatFS has limitations with root directory paths. The solution is to manually set up the root directory:
root_node->is_directory = true;
root_node->is_open = false;
strcpy(root_node->path, "/");
memset(&root_node->info, 0, sizeof(root_node->info));
root_node->info.fattrib = AM_DIR; // Mark as directory
6. Mount Table Configuration
Finally, configure the RTEMS mount table entry:
mt_entry->fs_info = fs_info;
mt_entry->ops = &rtems_fatfs_ops;
mt_entry->mt_fs_root->location.node_access = root_node;
mt_entry->mt_fs_root->location.handlers = fs_info->dir_handlers;
Lessons Learned
1. Path Handling Complexity
FatFS uses drive-prefixed paths (“0:/file.txt”) while RTEMS uses Unix-style paths (“/mnt/fat/file.txt”). The integration layer must handle this translation carefully.
2. Error Handling is Critical
Both allocation failures and FatFS errors must be handled with proper cleanup. The mount handler needs comprehensive error paths:
if (fr != FR_OK) {
fatfs_diskio_unregister_device(fs_info->drive_number);
rtems_recursive_mutex_destroy(&fs_info->vol_mutex);
free(root_node);
free(fs_info);
errno = rtems_fatfs_fresult_to_errno(fr);
return -1;
}
3. Thread Safety from Day One
RTEMS is multithreaded, so the filesystem must be thread-safe. Using recursive mutexes allows proper locking without deadlocks when filesystem operations call each other.
Debugging
Debugging was a crucial part of making this whole thing work. It is as simple as running the app with -s
flag, then:
gdb /path/to/exe
And inside gdb (assuming you’ve ran your code with QEMU):
target remote localhost:1234
I saw a really good cheatsheet for GDB. Manually running GDB is both powerful and dangerous. A wise man once said: “Programs walk before they can run”.