Dynamic partitioning is implemented using the dm-linear device-mapper
module in the Linux kernel. The super
partition contains
metadata listing the names and block ranges of each dynamic partition
within super
. During first-stage init
, this
metadata is parsed and validated, and virtual block devices are created to
represent each dynamic partition.
When applying an OTA, dynamic partitions are automatically created, resized, or deleted as needed. For A/B devices, there are two copies of the metadata, and changes are applied only to the copy representing the target slot.
Because dynamic partitions are implemented in userspace, partitions needed
by the bootloader can't be made dynamic. For example, boot
,
dtbo
, and vbmeta
are read by the bootloader, and
so must remain as physical partitions.
Each dynamic partition can belong to an update group. These
groups limit the maximum space that partitions in that group can consume.
For example, system
and vendor
can belong to a
group that restricts the total size of system
and
vendor
.
Implement dynamic partitions on new devices
This section details how to implement dynamic partitions on new devices launching with Android 10 and higher. To update existing devices, see Upgrading Android devices.
Partition changes
For devices launching with Android 10, create
a partition called super
. The super
partition handles A/B slots internally, so A/B devices don't need
separate super_a
and super_b
partitions.
All read-only AOSP partitions that aren't used by the bootloader must
be dynamic and must be removed from the GUID Partition Table (GPT).
Vendor-specific partitions don't have to be dynamic and may be placed
in the GPT.
To estimate the size of super
, add the sizes of the
partitions being deleted from the GPT. For A/B devices, this
should include the size of both slots. Figure 1 shows
an example partition table before and after converting to dynamic
partitions.
The supported dynamic partitions are:
- System
- Vendor
- Product
- System Ext
- ODM
For devices launching with Android 10, the
kernel command line option androidboot.super_partition
must be empty so that the command sysprop
ro.boot.super_partition
is empty.
Partition alignment
The device-mapper module may operate less efficiently if the
super
partition is not properly aligned. The
super
partition MUST be aligned to the minimum I/O
request size as determined by the block layer. By default, the
build system (via lpmake
, which generates the
super
partition image), assumes that a 1 MiB alignment
is sufficient for every dynamic partition. However, vendors should
ensure that the super
partition is properly aligned.
You can determine the minimum request size of a block device by
inspecting sysfs
. For example:
# ls -l /dev/block/by-name/super lrwxrwxrwx 1 root root 16 1970-04-05 01:41 /dev/block/by-name/super -> /dev/block/sda17 # cat /sys/block/sda/queue/minimum_io_size 786432
You can verify the super
partition's alignment in a
similar manner:
# cat /sys/block/sda/sda17/alignment_offset
The alignment offset MUST be 0.
Device configuration changes
To enable dynamic partitioning, add the following flag in
device.mk
:
PRODUCT_USE_DYNAMIC_PARTITIONS := true
Board configuration changes
You're required to set the size of the super
partition:
BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>
On A/B devices, the build system throws an error if the total size
of dynamic partition images is more than half of the super
partition size.
You can configure the list of dynamic partitions as follows. For
devices using update groups, list the groups in the
BOARD_SUPER_PARTITION_GROUPS
variable. Each group name
then has a BOARD_group_SIZE
and BOARD_group_PARTITION_LIST
variable.
For A/B devices, the maximum size of a group should cover only one
slot, as the group names are slot-suffixed internally.
Here's an example device that places all partitions into a group
called example_dynamic_partitions
:
BOARD_SUPER_PARTITION_GROUPS := example_dynamic_partitions BOARD_EXAMPLE_DYNAMIC_PARTITIONS_SIZE := 6442450944 BOARD_EXAMPLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
Here's an example device that places system and product services into
group_foo
, and vendor
, product
,
and odm
into group_bar
:
BOARD_SUPER_PARTITION_GROUPS := group_foo group_bar BOARD_GROUP_FOO_SIZE := 4831838208 BOARD_GROUP_FOO_PARTITION_LIST := system product_services BOARD_GROUP_BAR_SIZE := 1610612736 BOARD_GROUP_BAR_PARTITION_LIST := vendor product odm
-
For Virtual A/B launch devices, the sum of maximum sizes of all groups must
be at most:
BOARD_SUPER_PARTITION_SIZE
- overhead
See Implementing Virtual A/B. -
For A/B launch devices, the sum of maximum sizes of all groups must
be:
BOARD_SUPER_PARTITION_SIZE
/ 2 - overhead -
For non-A/B devices and retrofit A/B devices, the sum of maximum
sizes of all groups must be:
BOARD_SUPER_PARTITION_SIZE
- overhead - At build time, the sum of the sizes of the images of each partition in an update group must not exceed the maximum size of the group.
- Overhead is required in the computation to account for metadata, alignments, and so on. A reasonable overhead is 4 MiB, but you can pick a larger overhead as needed by the device.
Size dynamic partitions
Before dynamic partitions, partition sizes were over-allocated to ensure that they had enough room for future updates. The actual size was taken as is and most read-only partitions had some amount of free space in their file system. In dynamic partitions, that free space is unusable and could be used to grow partitions during an OTA. It's critical to ensure that partitions aren't wasting space and are allocated to a minimum possible size.
For read-only ext4 images, the build system automatically allocates the minimum size if no hardcoded partition size is specified. The build system fits the image so that the file system has as little unused space as possible. This ensures that the device doesn't waste space that can be used for OTAs.
Additionally, ext4 images can be further compressed by enabling block- level deduplication. To enable this, use the following configuration:
BOARD_EXT4_SHARE_DUP_BLOCKS := true
If automatic allocation of a partition minimum size is undesirable,
there are two ways to control the partition size. You can specify a
minimum amount of free space with
BOARD_partitionIMAGE_PARTITION_RESERVED_SIZE
,
or you can specify
BOARD_partitionIMAGE_PARTITION_SIZE
to force
dynamic partitions to a specific size. Neither of these is
recommended unless necessary.
For example:
BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800
This forces the file system in product.img
to have
50 MiB of unused space.
System-as-root changes
Devices launching with Android 10 must not use system-as-root.
Devices with dynamic partitions (whether it launches with or retrofits
dynamic partitions) must not use system-as-root. The Linux kernel can't
interpret the super
partition and so can't mount
system
itself. system
is now mounted by
first-stage init
, which resides in the ramdisk.
Don't set BOARD_BUILD_SYSTEM_ROOT_IMAGE
. In
Android 10, the
BOARD_BUILD_SYSTEM_ROOT_IMAGE
flag is only used to
differentiate whether the system is mounted by the kernel or by the
first-stage init
in ramdisk.
Setting BOARD_BUILD_SYSTEM_ROOT_IMAGE
to true
causes a build error when
PRODUCT_USE_DYNAMIC_PARTITIONS
is also true
.
When BOARD_USES_RECOVERY_AS_BOOT
is set to true, the
recovery image is built as boot.img, containing the recovery's
ramdisk. Previously, bootloader used the skip_initramfs
kernel
command line parameter to decide which mode to boot into. For
Android 10 devices, the bootloader MUST NOT pass
skip_initramfs
to the kernel command-line. Instead, bootloader
should pass androidboot.force_normal_boot=1
to skip recovery
and boot normal Android. Devices launching with Android 12
or later must use bootconfig to pass androidboot.force_normal_boot=1
.
AVB configuration changes
When using Android Verified Boot 2.0, if the device isn't using chained partition descriptors, then no change is necessary. If using chained partitions, however, and one of the verified partitions is dynamic, then changes are necessary.
Here's an example configuration for a device that chains
vbmeta
for the system
and
vendor
partitions.
BOARD_AVB_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_SYSTEM_ALGORITHM := SHA256_RSA2048 BOARD_AVB_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION := 1 BOARD_AVB_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VENDOR_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VENDOR_ROLLBACK_INDEX_LOCATION := 1
With this configuration, the bootloader expects to find a vbmeta
footer at the end of the system
and
vendor
partitions. Because these partitions are no longer
visible to the bootloader (they reside in super
), two
changes are needed.
-
Add
vbmeta_system
andvbmeta_vendor
partitions to the device's partition table. For A/B devices, addvbmeta_system_a
,vbmeta_system_b
,vbmeta_vendor_a
, andvbmeta_vendor_b
. If adding one or more of these partitions, they should be the same size as thevbmeta
partition. -
Rename the configuration flags by adding
VBMETA_
and specify which partitions the chaining extends to:BOARD_AVB_VBMETA_SYSTEM := system BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION := 1 BOARD_AVB_VBMETA_VENDOR := vendor BOARD_AVB_VBMETA_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VBMETA_VENDOR_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX_LOCATION := 1
A device may be using one, both, or none of these partitions. Changes are need only when chaining to a logical partition.
AVB bootloader changes
If the bootloader has embedded libavb, include the following patches:
- 818cf56740775446285466eda984acedd4baeac0 — "libavb: Only query partition GUIDs when the cmdline needs them."
- 5abd6bc2578968d24406d834471adfd995a0c2e9 — "Allow system partition to be absent"
- 9ba3b6613b4e5130fa01a11d984c6b5f0eb3af05 — "Fix AvbSlotVerifyData->cmdline might be NULL"
If using chained partitions, include an additional patch:
- 49936b4c0109411fdd38bd4ba3a32a01c40439a9 — "libavb: Support vbmeta blobs in beginning of partition."
Kernel command line changes
A new parameter, androidboot.boot_devices
, must be added
to the kernel command line. This is used by init
to
enable /dev/block/by-name
symlinks. It should be the
device path component to the underlying by-name symlink created by
ueventd
, that is,
/dev/block/platform/device-path/by-name/partition-name
.
Devices launching with Android 12 or later must use
bootconfig to pass androidboot.boot_devices
to init
.
For example, if the super partition by-name symlink is
/dev/block/platform/soc/100000.ufshc/by-name/super
,
you can add the command line parameter in the BoardConfig.mk file as
follows:
BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc
fstab changes
The device tree and device tree overlays must not contain fstab entries. Use an fstab file that will be part of the ramdisk.
Changes must be made to the fstab file for logical partitions:
-
The fs_mgr flags field must include the
logical
flag and thefirst_stage_mount
flag, introduced in Android 10, which indicates that a partition is to be mounted in the first stage. -
A partition may specify
avb=vbmeta partition name
as anfs_mgr
flag and then the specifiedvbmeta
partition is initialized by first stageinit
before attempting to mount any devices. -
The
dev
field must be the partition name.
The following fstab entries set system, vendor, and product as logical partitions following the above rules.
#<dev> <mnt_point> <type> <mnt_flags options> <fs_mgr_flags> system /system ext4 ro,barrier=1 wait,slotselect,avb=vbmeta,logical,first_stage_mount vendor /vendor ext4 ro,barrier=1 wait,slotselect,avb,logical,first_stage_mount product /product ext4 ro,barrier=1 wait,slotselect,avb,logical,first_stage_mount
Copy the fstab file into the first stage ramdisk.
SELinux changes
The super partition block device must be marked with the label
super_block_device
. For example, if the super partition by-name symlink is
/dev/block/platform/soc/100000.ufshc/by-name/super
,
add the following line to file_contexts
:
/dev/block/platform/soc/10000\.ufshc/by-name/super u:object_r:super_block_device:s0
fastbootd
The bootloader (or any non-userspace flashing tool) doesn't understand dynamic partitions, so it can't flash them. To address this, devices must use a user-space implementation of the fastboot protocol, called fastbootd.
For more information on how to implement fastbootd, see Moving Fastboot to User Space.
adb remount
For developers using eng or userdebug builds, adb remount
is extremely useful for fast iteration. Dynamic partitions pose a
problem for adb remount
because there is no longer free
space within each file system. To address this, devices can enable
overlayfs. As long as there is free space within the super partition,
adb remount
automatically creates a temporary dynamic
partition and uses overlayfs for writes. The temporary partition is
named scratch
, so don't use this name for other
partitions.
For more information on how to enable overlayfs, see the overlayfs README in AOSP.
Upgrade Android devices
If you upgrade a device to Android 10, and want to include dynamic partitions support in the OTA, you don't need to change the built-in partition table. Some extra configuration is required.
Device configuration changes
To retrofit dynamic partitioning, add the following flags in
device.mk
:
PRODUCT_USE_DYNAMIC_PARTITIONS := true PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true
Board configuration changes
You're required to set the following board variables:
- Set
BOARD_SUPER_PARTITION_BLOCK_DEVICES
to the list of block devices used to store extents of dynamic partitions. This is the list of names of existing physical partitions on the device. - Set
BOARD_SUPER_PARTITION_partition_DEVICE_SIZE
to the sizes of each block device inBOARD_SUPER_PARTITION_BLOCK_DEVICES
, respectively. This is the list of sizes of existing physical partitions on the device. This is usuallyBOARD_partitionIMAGE_PARTITION_SIZE
in existing board configurations. - Unset existing
BOARD_partitionIMAGE_PARTITION_SIZE
for all partitions inBOARD_SUPER_PARTITION_BLOCK_DEVICES
. - Set
BOARD_SUPER_PARTITION_SIZE
to the sum ofBOARD_SUPER_PARTITION_partition_DEVICE_SIZE
. - Set
BOARD_SUPER_PARTITION_METADATA_DEVICE
to the block device where dynamic partition metadata is stored. It must be one ofBOARD_SUPER_PARTITION_BLOCK_DEVICES
. Usually, this is set tosystem
. - Set
BOARD_SUPER_PARTITION_GROUPS
,BOARD_group_SIZE
, andBOARD_group_PARTITION_LIST
, respectively. See Board configuration changes on new devices for details.
For example, if the device already has system and vendor partitions, and you want to convert them to dynamic partitions and add a new product partition during the update, set this board configuration:
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor BOARD_SUPER_PARTITION_METADATA_DEVICE := system # Rename BOARD_SYSTEMIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE. BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := <size-in-bytes> # Rename BOARD_VENDORIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := <size-in-bytes> # This is BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE + BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE BOARD_SUPER_PARTITION_SIZE := <size-in-bytes> # Configuration for dynamic partitions. For example: BOARD_SUPER_PARTITION_GROUPS := group_foo BOARD_GROUP_FOO_SIZE := <size-in-bytes> BOARD_GROUP_FOO_PARTITION_LIST := system vendor product
SELinux changes
The super partition block devices must be marked with the attribute
super_block_device_type
. For example, if the device already has
system
and vendor
partitions, you want to use them as block
devices to store extents of dynamic partitions, and their by-name symlinks are marked as
system_block_device
:
/dev/block/platform/soc/10000\.ufshc/by-name/system u:object_r:system_block_device:s0 /dev/block/platform/soc/10000\.ufshc/by-name/vendor u:object_r:system_block_device:s0
Then, add the following line to device.te
:
typeattribute system_block_device super_block_device_type;
For other configurations, see Implementing dynamic partitions on new devices.
For more information about retrofit updates, see OTA for A/B Devices without Dynamic Partitions.
Factory images
For a device launching with dynamic partitions support, avoid using userspace fastboot to flash factory images, as booting to userspace is slower than other flashing methods.
To address this, make dist
now builds an additional
super.img
image that can be flashed directly to the super
partition. It automatically bundles the contents of logical
partitions, meaning it contains system.img
,
vendor.img
, and so on, in addition to the super
partition metadata. This image can be flashed directly to the
super
partition without any additional tooling or using
fastbootd. After the build, super.img
is placed in
${ANDROID_PRODUCT_OUT}
.
For A/B devices that launch with dynamic partitions,
super.img
contains images in the A slot. After flashing the
super image directly, mark slot A as bootable before rebooting the
device.
For retrofit devices, make dist
builds a set of
super_*.img
images that can be flashed directly to
corresponding physical partitions. For example, make dist
builds super_system.img
and super_vendor.img
when BOARD_SUPER_PARTITION_BLOCK_DEVICES
is the system
vendor. These images are placed in the OTA folder in
target_files.zip
.
Device mapper storage-device tuning
Dynamic partitioning accommodates a number of nondeterministic device-mapper objects. These may not all instantiate as expected, so you must track all mounts, and update the Android properties of all the associated partitions with their underlying storage devices.
A mechanism inside init
tracks the mounts and asynchronously
updates the Android properties. The amount of time this takes isn’t guaranteed
to be within a specific period, so you must provide enough time
for all on property
triggers to react. The properties are
dev.mnt.blk.<partition>
where
<partition>
is root
,
system
, data
, or
vendor
, for example. Each property is associated with the
base storage-device name, as shown in these examples:
taimen:/ % getprop | grep dev.mnt.blk [dev.mnt.blk.data]: [sda] [dev.mnt.blk.firmware]: [sde] [dev.mnt.blk.metadata]: [sde] [dev.mnt.blk.persist]: [sda] [dev.mnt.blk.root]: [dm-0] [dev.mnt.blk.vendor]: [dm-1] blueline:/ $ getprop | grep dev.mnt.blk [dev.mnt.blk.data]: [dm-4] [dev.mnt.blk.metadata]: [sda] [dev.mnt.blk.mnt.scratch]: [sda] [dev.mnt.blk.mnt.vendor.persist]: [sdf] [dev.mnt.blk.product]: [dm-2] [dev.mnt.blk.root]: [dm-0] [dev.mnt.blk.system_ext]: [dm-3] [dev.mnt.blk.vendor]: [dm-1] [dev.mnt.blk.vendor.firmware_mnt]: [sda]
The init.rc
language allows the Android properties to be
expanded as part of the rules, and storage devices can be tuned by the platform
as needed with commands like these:
write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb 128 write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb 128
Once command processing starts in second-stage init
, the
epoll loop
becomes active, and the values start to update. However,
because property triggers aren't active until late-init
, they
can’t be used in the initial boot stages to handle root
,
system
, or vendor
. You may expect the
kernel default read_ahead_kb
to be sufficient until the
init.rc
scripts can override in early-fs
(when
various daemons and facilities start). Therefore, Google recommends that
you use the on property
feature, coupled with an
init.rc
-controlled property like sys.read_ahead_kb
,
to deal with timing the operations and to prevent race conditions, as in these
examples:
on property:dev.mnt.blk.root=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.system=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.system}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.vendor=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.vendor}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.product=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.system_ext}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.oem=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.oem}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.data=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on early-fs: setprop sys.read_ahead_kb ${ro.read_ahead_kb.boot:-2048} on property:sys.boot_completed=1 setprop sys.read_ahead_kb ${ro.read_ahead_kb.bootcomplete:-128}