Partitions (MBR)

Introduction

Common hard drives can have their storage space split up into smaller units called 'partitions'.

This allows having multiple file systems on a single physical storage device, which has been very common on modern computers for many years. Indeed, having one smaller partition for all of the operating system's files, and another, larger partition, to hold the user's files makes it particularly easy to back-up and restore things. De-coupling the operating system's storage from the user's storage while only using one physical storage is a pretty neat trick, and is only one of the many uses for disk partitioning.

Wikipedia has a decent page on partitioning.

Partitioning schemes

The idea of partitioning is just that - an idea. Over the years we have seen different ways to actually implement this. Most rely on the simple idea of having a structure of information somewhere on the storage itself (usually at the beginning of the storage). This information structure indicates how the storage is split up into partitions.

The most commonly used partitioning schemes are:

  • MBR - a simple method dating back to 1983's IBM PC-DOS 2.0, still very largely used today
  • GPT - a more complex scheme from the late-1990's UEFI specification

While GPT allows for more partitions and bigger sizes, MBR is still widely used. MBR allows up to 4 partitions and 2 TiB (232 × 512 bytes) sizes, which is more than enough in many cases, while also being simpler to manage.

The 'mbr' library

To handle MBR-formatted storage devices, we can use the 'mbr' library. However, please note that most storage drivers (such as the SD Card driver) will take care of using 'mbr' to detect partitions for you. You should normally not have to do this yourself, unless you are dealing with very specific non-standard cases.

If you actually want to detect partitions from a storage device yourself, read on.

These examples will assume we have a storage device available through this pointer: struct storage *stor;.

Let's start by adding the 'mbr' library to the list of dependencies in our dfe.conf:

# ...
deps:
  - mbr

Using substrate

The 'mbr' library offers a substrate provider that we can use to load any partitions present on our storage device:

# Detect partitions from 'stor'
uses :mbr, base: 'stor'

This will create the following storage devices for the partitions:

  • struct storage *storp0;
  • struct storage *storp1;
  • struct storage *storp2;
  • struct storage *storp3;

Custom sector sizes

Because the MBR format does not use bytes but sectors to delimit the starting point and size of a partition, we need to be able to configure this value. The size of the sectors is usually 512 bytes (or 4096 bytes in very large modern hard drives) and therefore this is the default. However in some cases it might be necessary to specify a different sector size:

# Detect partitions from 'stor' (using 256-byte sectors)
uses :mbr, base: 'stor', sector_size: 256

Manually (without substrate)

If we do not want to use the substrate, we can detect the partitions manually instead:

 1 #include <mbr/part_storage.h>
 2 
 3 // Structure to hold our partition storage information
 4 struct mbr_part_storage stor_p0_data;
 5 
 6 // Storage interface for partition '0'
 7 struct storage *stor_p0;
 8 
 9 void main()
10 {
11     struct mbr_part mbr_part_buf;
12 
13     // ...
14 
15     // Detect partition '0' from storage device (stor)
16     memset(&stor_p0_data, 0, sizeof(struct mbr_part_storage));
17     if(mbr_get_part(stor, 0, &mbr_part_buf))    { /* ERROR: Reading partition info failed! */ }
18 
19     // Initialize partition storage
20     mbr_part_storage_init(&stor_p0_data, stor, 512, &mbr_part_buf, 0);
21     stor_p0 = &(stor_p0_data.st);
22 
23     // ...
24 }