How to Flash a Pre-Loaded Filesystem During Production

Zephyr has a filesystem API that makes it pretty easy to read and write to device flash. We’ve found this useful when testing out certificate authentication. The Golioth Firmware SDK shows how to write credentials onto the filesystem via SMP, useful since credentials are different for each device. But what if you want to write the same files to all devices during production?

Whether it’s machine learning models or simple configuration data, you can preload a filesystem and flash it as part of your production process. Today we’ll cover generating, flashing, reading, and extracting LittleFS images using Zephyr. However, the process will be nearly identical for other RTOSes.

Why not just make it part of the firmware?

Why are we even doing this, can’t these files just be built into the firmware? The problem with hardcoding data like this is that you need to update the whole firmware image to change them.

Take the case of machine learning models. These models are commonly trained on specialized hardware, and only once the model has been refined is it presented to the constrained device (your microcontroller-based product). If a more performant model is available later on, you want to be able to update just the model. Firmware updates consume bandwidth (which for cellular has a cost) and battery budget. Golioth specializes in OTA updates of not just firmware, but assets like ML models. So you can send the new model as an OTA asset which gets written to the filesystem.

Separating this data into a filesystem makes a lot of sense. You can update the firmware without sending the machine learning model with it. The device is also capable of updating what’s on the filesystem without touching the firmware its currently running. So let’s take care of the chicken and egg issue by flashing a populated filesystem as devices roll off the line.

Running the Zephyr LFS Sample

Zephy has a built-in support for LFS, the “little fail-safe filesystem designed for microcontrollers”. The RTOS reserves a partition for the filesystem, and at first run it will format the partition if unable to a read valid filesystem configuration. We can build the Zephyr LFS sample to see the formatting happen, and the sample will also populate some files in the filesystem afterward.

I’m building for the NXP frdm_rw612 board. By default, this sample will build a filesystem that is ~57MB, so I’m going to use a devicetree overlay file to create a more manageable ~24k filesystem.

/delete-node/ &storage_partition;

/ {
    fstab {
        compatible = "zephyr,fstab";
        lfs1: lfs1 {
            compatible = "zephyr,fstab,littlefs";
            mount-point = "/lfs1";
            partition = <&lfs1_part>;
            automount;
            read-size = <16>;
            prog-size = <16>;
            cache-size = <64>;
            lookahead-size = <32>;
            block-cycles = <512>;
        };
    };
};

&w25q512jvfiq {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        lfs1_part: partition@623000 {
            label = "storage";
            reg = <0x623000 0x6000>;
        };
    };
};

Now let’s build and flash the application:

west build -p -b frdm_rw612
west flash

You should see the application format the filesystem, mount it, then write a file to it.

I: littlefs partition at /lfs1
I: LittleFS version 2.10, disk version 2.1
I: FS at w25q512jvfiq@0:0x623000 is 6 0x1000-byte blocks with 512 cycle
I: partition sizes: rd 16 ; pr 16 ; ca 64 ; la 32
E: WEST_TOPDIR/modules/fs/littlefs/lfs.c:1389: Corrupted dir pair at {0x0, 0x1}
W: can't mount (LFS -84); formatting
I: /lfs1 mounted
I: Automount /lfs1 succeeded
*** Booting Zephyr OS build v4.1.0 ***
Sample program to r/w files on littlefs
Area 0 at 0x623000 on w25q512jvfiq@0 for 24576 bytes
/lfs1 automounted
/lfs1: bsize = 16 ; frsize = 4096 ; blocks = 6 ; bfree = 4

Listing dir /lfs1 ...
/lfs1/boot_count read count:0 (bytes: 0)
/lfs1/boot_count write new boot count 1: [wr:1]
I: Test file: /lfs1/pattern.bin not found, create one!
------ FILE: /lfs1/pattern.bin ------
01 55 55 55 55 55 55 55 02 55 55 55 55 55 55 55
03 55 55 55 55 55 55 55 04 55 55 55 55 55 55 55

--- snip ---

43 55 55 55 55 55 55 55 44 55 55 55 55 55 55 55
45 55 aa
I: /lfs unmounted
/lfs unmount: 0

Reading and Analyzing an LFS Partition

Now that we have a filesystem on the device, let’s read it from flash and analyze the contents. I’m using a J-Link to read the memory. For this particular chip, flash is locate at an offset of 0x8000000. Combine this with the partition address (0x623000) and size (0x6000) from our devicetree overlay file and we can read the LFS partition to a file.

JLinkExe -Device RW612 -if SWD -Speed 4000
connect
SaveBin lfs.bin 0x8623000 0x6000

Now we have an lfs.bin file. There’s a handy tool called littlefs-python that we can use to view the files on the filesystem. Let’s install it and list the files. If you go back and look at the output of the sample program you’ll notice the filesystem is reported 6 blocks of size 0x1000 (4096 bytes) which we use when viewing the binary.

pip install littlefs-python
littlefs-python list lfs.bin --block-size 4096

/boot_count
/pattern.bin

Voila! We can see the pattern.bin file as well as a boot count file, both written by the sample application. If you want access to these files, you can extract them using the same tool.

littlefs-python extract --block-size 4096 lfs.bin output/

Creating an LFS Binary

Now for the main event. Let’s create our own filesystem binary and write it to the device. I’m going to start by setting up the desired filesystem on my computer. I’m going to do this with empty files but of course any files may be used here.

mkdir my-new-fs
touch my-new-fs/hello-there.txt
mkdir my-new-fs/models
touch my-new-fs/models/yolo.bin

My filesystem now looks like this:

my-new-fs/
├── hello-there.txt
└── models
    └── yolo.bin

Now use the python tool to convert this to a binary. Make sure the block count matches the filesize you set up in Zephyr.

littlefs-python create my-new-fs/ lfs-new.bin --block-size 4096 --block-count 6

And then list the contents to make sure we have what we want:

littlefs-python list lfs-new.bin --block-size 4096

/models
/hello-there.txt
/models/yolo.bin

Now writing it back onto the device is very similar to the read step earlier in this post. Once again, the start address (0x623000) comes from the partition in build/zephyr/zephyr.dts which was set by the overlay file that I used. And this chip begins at a memory offset of 0x8000000.

JLinkExe -Device RW612 -if SWD -Speed 4000
connect
LoadBin lfs-new.bin 0x8623000
reset

Our new filesystem is written to device flash and we rebooted the chip. However, it’s a bit difficult to tell so let’s use the filesystem shell so we can get a directory listing.

Using Zephyr’s Filesystem Shell

We can recompile and reflash our firmware to enable the shell. Remember, the filesystem is on its own partition, so this will not overwrite our data.

One change is required in the sample code. At the end of the application, the filesystem is unmounted. We want to leave it mounted so we can list the files. Before rebuilding, comment out these two lines at the end of main.c:

/*rc = fs_unmount(mountpoint);*/
/*LOG_PRINTK("%s unmount: %d\n", mountpoint->mnt_point, rc);*/

Now rebuild with additional Kconfig symbols to turn on the shell and the filesystem shell and flash the new firmware. These Kconfig can also go into your prj.conf if you want them to be permanently part of your project.

west build -p -b frdm_rw612 -- -DCONFIG_SHELL=y -DCONFIG_FILE_SYSTEM_SHELL=y
west flash

We can now use shell commands to list files on the filesystem, confirming our generated binary is present on the device.

uart:~$ fs ls
lfs1/
uart:~$ fs ls lfs1
boot_count
hello-there.txt
models/
pattern.bin
uart:~$ fs ls lfs1/models
yolo.bin

What will you use it for?

These are some nice filesystem tricks that may come in handy during a manufacturing run. While we’ve used Zephyr’s SMP subsystem to read from and write to the filesystem, that requires the SMP to be built into your application. A programmer is already present on the production line, so writing a filesystem at the same time as the firmware makes sense.

If it’s the same filesystem for every device, the binary may be bundled along with the firmware. But there are use cases where each device would have different data. Such is the case where certificate authentication is used. Your PKI should have the device itself generate a key, and the certificate signing request (CSR) may be written to the filesystem then read/signed and the resulting public cert written back onto the device.

Whatever your use case, we’d love to hear about why you need to read/write the filesystem. Let us know by posting to the Golioth forum!

Mike Szczys
Mike Szczys
Mike is a Firmware Engineer at Golioth. His deep love of microcontrollers began in the early 2000s, growing from the desire to make more of the BEAM robotics he was building. During his 12 years at Hackaday (eight of them as Editor in Chief), he had a front-row seat for the growth of the industry, and was active in developing a number of custom electronic conference badges. When he's not reading data sheets he's busy as an orchestra musician in Madison, Wisconsin.

Post Comments

No comments yet! Start the discussion at forum.golioth.io

More from this author

Related posts

spot_img

Latest posts

How to build a Bluetooth-connected digital signage fleet

Use Golioth Connectivity to create Bluetooth-enabled Digital Signage fleets. This demo shows how you can create an LED matrix that shows different informational callouts. We also show how you can easily provision a new device onto your fleet with certificates.

A Settings System for any Bluetooth Fleet

The Golioth settings service is now available via Golioth Connectivity, which supports Bluetooth devices. This allows you to send updates to all your devices, a subset, or a single device, just like other devices using the Golioth Firmware SDK.

Get started with Bluetooth and Golioth Connectivity

Golioth just announced Bluetooth Support...but how can you get started? This post walks you through the installation steps and has a signup for an upcoming livestream on August 29th where we'll go through the entire workflow together.

Want to stay up to date with the latest news?

Subscribe to our newsletter and get updates every 2 weeks. Follow the latest blogs and industry trends.