{"id":53,"date":"2025-07-04T23:18:56","date_gmt":"2025-07-04T23:18:56","guid":{"rendered":"https:\/\/blog.ganji.dev\/?p=53"},"modified":"2025-07-04T23:23:11","modified_gmt":"2025-07-04T23:23:11","slug":"lessons-from-building-fatfs-mount-handler","status":"publish","type":"post","link":"https:\/\/blog.ganji.dev\/?p=53","title":{"rendered":"Lessons from Building FatFS Mount Handler"},"content":{"rendered":"\n<p>&nbsp;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&#8217;s what I learned while implementing the FatFS mount functionality for RTEMS.<br><br>&nbsp;<strong>The Challenge: Two Different Worlds<\/strong><br><br>&nbsp;RTEMS uses a POSIX-like filesystem abstraction with operations like open(), read(), and write(), while FatFS provides its own API with<br>&nbsp;functions like f_open(), f_read(), and f_write(). The integration layer must translate between these two worlds seamlessly.<br><br>&nbsp;<strong>Understanding RTEMS Filesystem Architecture<\/strong><br><br>&nbsp;RTEMS filesystems are built around several key structures:<br><br>&nbsp;&#8211; <strong>rtems_filesystem_operations_table<\/strong>: Contains filesystem-level operations (mount, unmount, path evaluation, etc.)<br>&nbsp;&#8211; <strong>rtems_filesystem_file_handlers_r<\/strong>: Contains file-level operations (open, close, read, write, etc.)<br>&nbsp;&#8211; <strong>rtems_filesystem_mount_table_entry_t<\/strong>: Describes a mounted filesystem instance<br><br>&nbsp;The mount handler is the entry point where everything begins. It&#8217;s responsible for:<br>&nbsp;1. Initializing the filesystem<br>&nbsp;2. Setting up the root directory<br>&nbsp;3. Configuring the mount table entry<br>&nbsp;4. Handling errors gracefully<br><br>&nbsp;<strong>Key Components of the FatFS Integration<\/strong><br><br>&nbsp;1. The Filesystem Info Structure<\/p>\n\n\n\n<p>&nbsp;typedef struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FATFS fatfs; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ FatFS filesystem object<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rtems_recursive_mutex vol_mutex; \/\/ Thread safety<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const rtems_filesystem_file_handlers_r *file_handlers;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const rtems_filesystem_file_handlers_r *dir_handlers;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BYTE drive_number; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ Drive number for FatFS<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char mount_path[256]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ FatFS drive path (&#8220;0:&#8221;, &#8220;1:&#8221;, etc.)<br>&nbsp;} rtems_fatfs_fs_info_t;<br><br>&nbsp;This structure bridges RTEMS and FatFS, containing both the FatFS volume object and RTEMS-specific data.<br><br>&nbsp;2. The Disk I\/O Bridge<br><br>&nbsp;One of the most critical pieces is the disk I\/O layer (<code>rtems-diskio.c<\/code>). FatFS expects certain disk I\/O functions (disk_read, disk_write,<br>&nbsp;disk_ioctl, disk_status), while RTEMS provides block device access through its rtems_bdbuf interface.<br><br>&nbsp;The bridge maintains a mapping between FatFS drive numbers and RTEMS block devices:<br><br>&nbsp;typedef struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int fd; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ RTEMS file descriptor<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rtems_disk_device *dd; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ RTEMS disk device<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bool initialized; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ Registration state<br>&nbsp;} fatfs_volume_t;<br><br>&nbsp;static fatfs_volume_t volumes[FF_VOLUMES];<br><br>&nbsp;3. Error Code Translation<br><br>&nbsp;FatFS and RTEMS use different error codes. A translation layer converts FatFS FRESULT codes to POSIX errno values:<br><br>&nbsp;static inline int rtems_fatfs_fresult_to_errno(FRESULT fr) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;switch (fr) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case FR_OK: return 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case FR_NO_FILE: return ENOENT;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case FR_DENIED: return EACCES;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case FR_EXIST: return EEXIST;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\/\/ &#8230; more mappings<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;}<br><br>&nbsp;<strong>The Mount Process: Step by Step<\/strong><br><br>&nbsp;1. Memory Allocation and Setup<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fs_info = calloc(1, sizeof(*fs_info));\nroot_node = calloc(1, sizeof(*root_node));\nrtems_recursive_mutex_init(&amp;fs_info-&gt;vol_mutex, RTEMS_FILESYSTEM_TYPE_FATFS);<\/code><\/pre>\n\n\n\n<p><br>&nbsp;2. Drive Number Management<\/p>\n\n\n\n<p>&nbsp;FatFS uses numeric drive identifiers (0-9). The mount handler assigns unique drive numbers with wraparound:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&nbsp;fs_info-&gt;drive_number = next_drive_number++;\n&nbsp;if (next_drive_number &gt;= FF_VOLUMES) {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next_drive_number = 0;\n&nbsp;}<\/code><\/pre>\n\n\n\n<p>&nbsp;3. Device Registration<br><br>&nbsp;The RTEMS block device must be registered with the FatFS disk I\/O layer:<br><br>&nbsp;<code>rc = fatfs_diskio_register_device(fs_info-&gt;drive_number, mt_entry-&gt;dev);<\/code><br><br>&nbsp;4. FatFS Mount<br><br>&nbsp;Create the drive path and mount the FatFS volume:<br><br>&nbsp;<code>snprintf(drive_path, sizeof(drive_path), \"%u:\", fs_info-&gt;drive_number);<br>&nbsp;fr = f_mount(&amp;fs_info-&gt;fatfs, drive_path, 1); &nbsp;\/\/ 1 = immediate mount<\/code><br><br>&nbsp;5. Root Directory Setup<br><br>&nbsp;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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&nbsp;root_node-&gt;is_directory = true;\n&nbsp;root_node-&gt;is_open = false;\n&nbsp;strcpy(root_node-&gt;path, \"\/\");\n&nbsp;memset(&amp;root_node-&gt;info, 0, sizeof(root_node-&gt;info));\n&nbsp;root_node-&gt;info.fattrib = AM_DIR; &nbsp;\/\/ Mark as directory<\/code><\/pre>\n\n\n\n<p><br>6. Mount Table Configuration<br><br>&nbsp;Finally, configure the RTEMS mount table entry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&nbsp;mt_entry-&gt;fs_info = fs_info;\n&nbsp;mt_entry-&gt;ops = &amp;rtems_fatfs_ops;\n&nbsp;mt_entry-&gt;mt_fs_root-&gt;location.node_access = root_node;\n&nbsp;mt_entry-&gt;mt_fs_root-&gt;location.handlers = fs_info-&gt;dir_handlers;<\/code><\/pre>\n\n\n\n<p><br>\u00a0<strong>Lessons Learned<\/strong><br><br>\u00a01. Path Handling Complexity<br><br>\u00a0FatFS uses drive-prefixed paths (&#8220;0:\/file.txt&#8221;) while RTEMS uses Unix-style paths (&#8220;\/mnt\/fat\/file.txt&#8221;). The integration layer must handle this translation carefully.<br><br>\u00a02. Error Handling is Critical<br><br>\u00a0Both allocation failures and FatFS errors must be handled with proper cleanup. The mount handler needs comprehensive error paths:<br><br>\u00a0if (fr != FR_OK) {<br>\u00a0\u00a0\u00a0\u00a0\u00a0fatfs_diskio_unregister_device(fs_info->drive_number);<br>\u00a0\u00a0\u00a0\u00a0\u00a0rtems_recursive_mutex_destroy(&amp;fs_info->vol_mutex);<br>\u00a0\u00a0\u00a0\u00a0\u00a0free(root_node);<br>\u00a0\u00a0\u00a0\u00a0\u00a0free(fs_info);<br>\u00a0\u00a0\u00a0\u00a0\u00a0errno = rtems_fatfs_fresult_to_errno(fr);<br>\u00a0\u00a0\u00a0\u00a0\u00a0return -1;<br>\u00a0}<br><br>\u00a03. Thread Safety from Day One<br><br>\u00a0RTEMS is multithreaded, so the filesystem must be thread-safe. Using recursive mutexes allows proper locking without deadlocks when filesystem operations call each other.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging<\/h2>\n\n\n\n<p>Debugging was a crucial part of making this whole thing work. It is as simple as running the app with <code>-s<\/code> flag, then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gdb \/path\/to\/exe<\/code><\/pre>\n\n\n\n<p>And inside gdb (assuming you&#8217;ve ran your code with QEMU):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>target remote localhost:1234<\/code><\/pre>\n\n\n\n<p>I saw a really good <a href=\"https:\/\/darkdust.net\/files\/GDB%20Cheat%20Sheet.pdf\">cheatsheet<\/a> for GDB. Manually running GDB is both powerful and dangerous. A wise man once said: &#8220;Programs walk before they can run&#8221;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp;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&#8217;s what I learned while implementing the FatFS mount functionality for RTEMS. &nbsp;The Challenge: Two Different Worlds &nbsp;RTEMS uses a POSIX-like filesystem abstraction with operations like open(), read(), and write(), while FatFS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-53","post","type-post","status-publish","format-standard","hentry","category-gsoc"],"_links":{"self":[{"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/posts\/53","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=53"}],"version-history":[{"count":4,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/posts\/53\/revisions"}],"predecessor-version":[{"id":57,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=\/wp\/v2\/posts\/53\/revisions\/57"}],"wp:attachment":[{"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=53"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=53"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ganji.dev\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=53"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}