Sepehr's Blog

A place to begin

Lessons from Building FatFS Mount Handler

 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”.