Firmware Update Implementation Using TSFS Snapshots

In the previous article of this series on TSFS snapshots, we have shown how snapshots can be used to design a simple yet robust firmware upgrade procedure. This time, we go from design to implementation, delving into the specifics of the TSFS snapshot management interface. More specifically, we show how we can meet our initial design requirements by combining three essential TSFS snapshot management functions:

  • tsfs_sshot_create()
  • tsfs_sshot_delete()
  • tsfs_revert()

Design Overview

As you may recall, the initial requirements for the firmware upgrade procedure were as follows:

  • The device must be able to sustain normal operation while the upgrade is being performed.
  • The upgrade procedure must be fail-safe.
  • The upgrade procedure must include a revert path.

In order to meet these requirements, we have previously designed a simple procedure, leveraging the availability of snapshots and write transactions. This procedure went like this:

  1. Create a “revert” snapshot (overwriting the previous “revert” snapshot).
  2. Update the files (or portions of files) as needed.
  3. Create a “current” snapshot for the updated firmware (overwriting the previous “current” snapshot).
  4. Commit the above modifications (snapshot creation and file updates).
  5. Reboot or reload the firmware.

A Possible Implementation

The following listing shows a possible implementation for the firmware upgrade procedure, based on TSFS snapshots and write transactions. Details not directly relevant to snapshot and transaction management were purposely omitted for clarity.

rtn = tsfs_sshot_delete("fs0", "revert");
if (rtn != RTNC_SUCCESS) { /* Error handling. */ }

rtn = tsfs_sshot_create("fs0", "revert");
if (rtn != RTNC_SUCCESS) { /* Error handling. */ }

// Upgrade files in place through "fs0/fw/".
// The running application can still operate normally by
//  accessing firmware files through "fs0/.ss/current/fw".
firmware_upgrade("fs0/fw");    

// Stop the application here, before creating a new "current" snapshot.

// Remove the old "current" snapshot and create a new one.
rtn = tsfs_sshot_delete("fs0", "current");
if (rtn != RTNC_SUCCESS) { /* Error handling. */ }

rtn = tsfs_sshot_create("fs0", "current");
if (rtn != RTNC_SUCCESS) { /* Error handling. */ }

// Commit the new firmware version.
rtn = tsfs_commit();
if (rtn != RTNC_SUCESS) { /* Error handling. */ }

// Re-boot to make the upgrade effective.
reboot();

Two snapshot management functions are used in this short example:

tsfs_sshot_create() and tsfs_sshot_delete(). Both functions take two arguments: the name of the target TSFS instance and the name of the snapshot to be created/deleted. The snapshot deletion is needed here because tsfs_sshot_create() fails if a snapshot with the same name already exists. This particular behaviour is intended to protect against programming errors and prevent inadvertent overwriting to go unnoticed.

Snapshot creation and deletion are very fast. In fact, they are faster than any single file update. Also, because they are performed independently of transaction commits, snapshot creation and deletion do not require a costly flush operation, where all internal caches and buffers must be written to the media.

Once created, snapshots can be found under <instance name>/.ss. In the firmware upgrade example, the “revert” and “current” snapshots can be found at "fs0/.ss/revert" and "fs0/.ss/current" respectively.

As discussed in the previous article, the “current” snapshot provides the application with an immutable and coherent view of the firmware files. These “frozen” files can safely be read from by the application, while a firmware upgrade procedure is seamlessly performed on "fs0/fw".

As for the “revert” snapshot, it provides the needed revert path in case something is wrong with the upgraded version. This roll back operation can be achieved using a single call to tsfs_revert() as shown in the following listing:

// If something is wrong with the new firmware,
// stop the application and go back to the previous version.
rtn = tsfs_revert("fs0/.ss/revert");
if (rtn != RTNC_SUCCESS) { /* Error handling. */} 
  
// Reboot to return to the previous firmware version.
reboot();

Notice that there is no need to commit before rebooting, as tsfs_revert() always comes with an implicit commit. On the contrary, at the end of the previous example, right before rebooting, the call to tsfs_commit() is needed to make the previous modifications permanent. Without this final function call, the whole upgrade would be useless as all modifications would be lost upon mount cycling.

Snapshots vs Transactions

Snapshot operations — much like file and directory operations — are part of the current write transaction. A deleted snapshot, for instance, is not permanently removed until the containing write transaction is committed. If some interruption occurs before the containing write transaction is committed, the deleted snapshot is automatically restored upon recovery. This transactional behaviour of the snapshot deletion (and snapshot operations in general) is illustrated in Figure 1.

Timing diagram showing the interaction between TREESpan File System's (TSFS) snapshot and transactions.
Figure 1 – Transactional behaviour of the snapshot deletion.

Conclusion

In this article, we have shown how TSFS snapshot and transaction management API functions can be combined to implement a fail-safe and flexible firmware upgrade procedure. More precisely, we have seen how snapshots can be created and deleted using tsfs_sshot_create() and tsfs_sshot_delete() respectively. We have also seen how snapshots can easily be accessed under the built-in /.ss directory and reverted to using the dedicated tsfs_revert() API function.

Questions or comments? Do not hesitate to contact us at blog@jblopen.com. Your questions, comments and, suggestions are appreciated.


See all articles