💾 Archived View for gemini.f4grx.net › nrfs.gmi captured on 2023-12-28 at 15:01:07. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-11-14)

-=-=-=-=-=-=-

Back to index

The New Retro File System

This page describes NRFS, the fifteenth filesystem for retro machines that will unify the 14 previous ones.

While playing with my 68hc11 boards, I noticed that there is so much I can do with just a monitor ROM. If I want to imagine more complex systems, I will need to elaborate a mass storage system using compatible storage devices, in order to store executable programs, load operating system modules, and store application data. I also need this if I want an assembler and, maybe, a compiler for some higher level language.

So I started looking around, to evaluate what storage devices I can use. I found two directions:

However, these peripheral have different block sizes.

After procrastinating a bit and rotating ideas in my head, and after looking at a video that presented a filesystem for a modern Z80 SBC (ZealFS), I determined that I had to elaborate my own filesystem to fit my diverse requirements:

No other machine has that flexibility. Most of the systems I saw use a fixed block size of 512 bytes, or are closely related to a single machine.

And last but not least, I want to build a filesystem from scratch for study purpose.

NRFS

And so I started drafting specifications for a filesystem.

The filesystem uses a block layer that allows me to read and write blocks in a transparent way. A block driver can be implemented to use SPI EEPROM, IO mapped CompactFlash, or just system RAM/ROM.

I decided that I would be able to store just files and directories, with future options not forbidding symbolic links. The file entries also store a date, a read only flag, there is provision for a future executable flag.

The filesystem starts with a superblock, that holds a signature and FS parameters including block size and block index size, and the block index of the root directory.

The rest of the disk is made of chainable blocks, the first four bytes of the block holds the link pointer, the rest is data. So a disk block is not able to hold a power-of-two number of bytes, but that is not really important.

Directories are special files that contain 30-bytes directory entries (16 of them for a 512-byte block). Each entry has a data pointer, data size, flags, and date field. As of now, there is no file owner, and only one date field, that defaults to creation date.

If dir entries are extended to 31 bytes, then one byte can hold a user identifier, and the flags have room for owner/other acess flags. The date could also be redefined as a modification date instead of creation.

Dates are packed in 5-byte arrays to avoid year-2038 restrictions. All date fields are packed in the minimum amount of bits.

Can I try it?

Sure, some features are still missing, but I can send you the last code I have.

just drop me an email (my call @ same domain as this web site) or ask me about that on the fediverse. There is only one @f4grx over there, on the mastodon.social instance.

Specification

As extracted from source code:

/*
nrfs new retro filesystem

Intended to store files on various retro computers.

Constraints
===========
Can be used for several ranges of devices from floppy disks to compact flash
cards to serial eeproms. This means we have to support different block sizes.
No attempt is made at wear leveling, so it's only suitable for magnetic disks,
EEPROMs, and SSDs that contain an FTL, like CompactFlash.

There is no provision for user ID or access rights, except for a read only flag.

Only one date is managed for each entry. This can be set to be a "creation" or
a "modification" date.

Disk Format
===========

General
-------

Multi-byte values are stored in little endian.

The number and indexes of blocks is always stored in 4 byte fields, of wich it
is allowable to only manage a part: the number of bytes actually used for block
indexes is indicated in the superblock.

 8 bits of indexes can manage 256        blocks.
16 bits of indexes can manage 65535      blocks.
24 bits of indexes can manage 16777216   blocks.
32 bits of indexes can manage 4294967296 blocks.

Any implementation not supporting this size can refuse to mount a volume that
uses indices too large, but if a filesystem has a small enough number of blocks
it can be mounted by any implementation supporting this index size.

Super block format
------------------

offset  len     description
0       4       signature 'NRFS'
4       1       version, currently 01h
5       1       power of two of the block size. 7:128, 8:256, 9:512
6       1       number of bytes in block indexes
7       1       reserved
8       4       number of blocks
12      4       index of root directory (usually 1)
16      5       UTC compact date of the volume creation

This specification describes version 1 of the data structures. A future
implementation may choose to use this field for backwards compatibility.

The block size is configurable to match the underlying block device and
allow compromises in buffer space. Only one block needs to be buffered. This
has impacts on performance, but it should not matter on the intended platforms.

Block size 7 (128b) is only expected for very tiny systems, 8 (256) matches I2C
EEPROMs, 9 (512) is for standard IDE/CompactFlash and floppy storage.
12 (4096) could be used on usual serial NOR flash devices, but this is not
recommended without an additional wear leveling layer which is not provided.

For extreme systems, an absolute minimum block size is 64 bytes, to store 2
directory entries per block. 32 bytes can't fit a directory entry in a block.

Info: Max volume size according to block size and number of bytes in block
indexes:

indexbytes   1     2    3     4
indexbits    8    16   24    32
maxblocks  256   64k   16M   4G
blocksize / maxvolumesize
 128       32k    8M   2G  512G
 256       64k   16M   4G    1T
 512      128k   32M   8G    2T

4 bytes indexes are much larger that would be needed by any storage device
usable on any retro class computer.
Implementations supporting 3 bytes are expected to cover much of the use cases
except the largest CompactFlash cards.

Storage blocks
--------------
All blocks on the disk except the super block are for storage of data.
All blocks have the same structure: the first 4 bytes of a block are a link
pointer, and the rest of the block is used to store data.

link pointer 00000000h indicating no link
link pointer ffffffffh indicating a free block

Data elements to be stored are fragmented in a chain of blocks, each holding
(blocksize-4) bytes after the forward link to the next block.

Directories
-----------
Directories are stored the same as regular files. They contain 30-byte sized
directory entries.
Entries do not span across two directory pages.
The first entry of a directory is a .. entry to the parent directory.
There is no . entry for the current directory.
There is no .. entry for the root directory, eg executing cd .. in the root will
result in a "ENOENT" error.

Here is the number of entries that can fit in each block size:
 64:  60 bytes available, 2 entries (none lost)
128: 124 bytes available, 4 entries (120 bytes + 4 lost, ideal size 31b/entry)
256: 252 bytes available, 8 entries (240 bytes + 12 lost, ideal 31.5b/entry)
512: 508 bytes available, 16 entries (480 bytes + 28 lost, ideal 31.75b/entry)

The free bytes shall NOT be used to store any useful data.

Directory entries
-----------------

offset  len     description
0       4       Block index holding the first data block for this entry. This
                  value is 00000000h for deleted/unused entries.
4       4       For regular files, file size in bytes. for directories, number
                  of entries in directory pointed by this entry.
8       1       Entry flags: 0x01 directory, 0x10 read-only, 0x20 executable
9       5       UTC Compact date, binary format as described below.
14      16      entry name, zero padded. No padding when max name length is used
Total: 30 bytes per entry

Date storage: binary compact format of UTC date
2 bytes: year (12 bits, up to 4095) and month (4 bits)
2 bytes: day (5 bits), hour (5 bits) and min (6 bits)
1 byte: seconds (in bits 0-5)
Total 5 bytes per date, including 2 spare bits

Date example: 7E 73 AD CF 06
year  = 0x7E7 = 2023
month = 0x03  = 3
day   = (0xad>>3) = 0x15 = 21
hour  = ((0xad & 0x07) << 2) + (0xCF>>6) = 0x5<<2 + 0x3 = 0x14+0x3 = 0x17 = 23
min   = (0xcf & 0x3F) = 0x0f = 15
sec   = 0x06 = 6
Date "7E 73 AD CF 06" is "2023-03-21 23:15:06 UTC"

Operations
==========

data required to access the filesystem
--------------------------------------
buffer space for one block
one nrfs_t structure
one nrfs_file_t for each opened file


formatting
----------
-set the 4 first bytes of each block to FFFFFFFFh (making them available)
-allocate a free block (usually block number one) as root directory
-write superblock fields including link to root directory

FS Operations
-------------
The algorithms to implement the actual file operations are not specified.
Any implementation making sure that the above structure is maintained is fine.
When building an implementation, please take care of error conditions and make
sure that the FS is as consistent as possible in case an error condition or
power loss event happens.
Below is a reference C implementation. This implementation requires external
access functions to read/write a block and get a system date. The formatting
routine needs to know the number and size of blocks.

Back to index