blob: 856a5952ac289868938c58367c6a6c06cc1320a3 [file] [log] [blame]
<html devsite>
<head>
<title>Implementing A/B Updates</title>
<meta name="project_path" value="/_project.yaml" />
<meta name="book_path" value="/_book.yaml" />
</head>
<body>
<!--
Copyright 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<p>OEMs and SoC vendors who want to implement A/B system updates must ensure
their bootloader implements the boot_control HAL and passes the
<a href="#kernel">correct parameters</a> to the kernel.</p>
<h2 id="bootcontrol">Implementing the boot control HAL</h2>
<p>A/B-capable bootloaders must implement the <code>boot_control</code> HAL at
<code><a href="https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/boot_control.h" class="external">hardware/libhardware/include/hardware/boot_control.h</a></code>.
You can test implementations using the
<code><a href="https://android.googlesource.com/platform/system/extras/+/master/bootctl/" class="external">system/extras/bootctl</a></code> utility and
<code><a href="https://android.googlesource.com/platform/system/extras/+/refs/heads/master/tests/bootloader/" class="external">system/extras/tests/bootloader/</a></code>.
</p>
<p>You must also implement the state machine shown below:</p>
<img src="/devices/tech/ota/images/ab-updates-state-machine.png">
<figcaption><strong>Figure 1.</strong> Bootloader state machine</figcaption>
<h2 id="kernel">Setting up the kernel</h2>
<p>To implement A/B system updates:</p>
<ol>
<li>Cherrypick the following kernel patch series (if needed):
<ul>
<li>If booting without ramdisk and using "boot as recovery", cherrypick
<a href="https://android-review.googlesource.com/#/c/158491/" class="external">android-review.googlesource.com/#/c/158491/</a>.</li>
<li>To set up dm-verity without ramdisk, cherrypick
<a href="https://android-review.googlesource.com/#/q/status:merged+project:kernel/common+branch:android-3.18+topic:A_B_Changes_3.18" class="external">android-review.googlesource.com/#/q/status:merged+project:kernel/common+branch:android-3.18+topic:A_B_Changes_3.18</a>.</li>
</ul>
</li>
<li>Ensure kernel command line arguments contain the following extra arguments:
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity &lt;public-key-id&gt; &lt;path-to-system-partition&gt;"</code></pre>
... where the <code>&lt;public-key-id&gt;</code> value is the ID of the public
key used to verify the verity table signature (for details, see
<a href="/security/verifiedboot/dm-verity.html">dm-verity</a>).</li>
<li>Add the .X509 certificate containing the public key to the system keyring:
<ol>
<li>Copy the .X509 certificate formatted in the <code>.der</code> format to the
root of the <code>kernel</code> directory. If the .X509 certificate is
formatted as a <code>.pem</code> file, use the following <code>openssl</code>
command to convert from <code>.pem</code> to <code>.der</code> format:
<pre class="devsite-terminal devsite-click-to-copy">
openssl x509 -in &lt;x509-pem-certificate&gt; -outform der -out &lt;x509-der-certificate&gt;</pre>
</li>
<li>Build the <code>zImage</code> to include the certificate as part of the
system keyring. To verify,check the <code>procfs</code> entry (requires
<code>KEYS_CONFIG_DEBUG_PROC_KEYS</code> to be enabled):
<pre class="devsite-click-to-copy">
angler:/# cat /proc/keys
1c8a217e I------ 1 perm 1f010000 0 0 asymmetri
Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
2d454e3e I------ 1 perm 1f030000 0 0 keyring
.system_keyring: 1/4</pre>
Successful inclusion of the .X509 certificate indicates the presence of the
public key in the system keyring (highlight denotes the public key ID).</li>
<li>Replace the space with <code>#</code> and pass it as
<code>&lt;public-key-id&gt;</code> in the kernel command line. For example,
pass <code>Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f</code> in place of
<code>&lt;public-key-id&gt;</code>.</li>
</ol>
</li>
</ol>
<h2 id="build-variables">Setting build variables</h2>
<p>A/B-capable bootloaders must meet the following build variable criteria:</p>
<table>
<tr>
<th>Must define for A/B target</th>
<td>
<ul>
<li><code>AB_OTA_UPDATER := true</code></li>
<li><code>AB_OTA_PARTITIONS := \</code><br/>
<code>&nbsp; boot \</code><br/>
<code>&nbsp; system \</code><br/>
<code>&nbsp; vendor</code><br/>
and other partitions updated through <code>update_engine</code> (radio,
bootloader, etc.)</li>
<li><code>BOARD_BUILD_SYSTEM_ROOT_IMAGE := true</code></li>
<li><code>TARGET_NO_RECOVERY := true</code></li>
<li><code>BOARD_USES_RECOVERY_AS_BOOT := true</code></li>
<li><code>PRODUCT_PACKAGES += \</code><br/>
<code>&nbsp; update_engine \</code><br/>
<code>&nbsp; update_verifier</code></li>
</ul>
For an example, refer to
<code><a href="https://android.googlesource.com/device/google/marlin/+/android-7.1.0_r1/device-common.mk" class="external">/device/google/marlin/+/android-7.1.0_r1/device-common.mk</a></code>.
You can optionally conduct the post-install (but pre-reboot) dex2oat step
described in <a href="#compilation">Compiling</a>.
</td>
</tr>
<th>Cannot define for A/B target</th>
<td>
<ul>
<li><code>BOARD_RECOVERYIMAGE_PARTITION_SIZE</code></li>
<li><code>BOARD_CACHEIMAGE_PARTITION_SIZE</code></li>
<li><code>BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE</code></li>
</ul>
</td>
</tr>
<tr>
<th>Optional for debug builds</th>
<td><code>PRODUCT_PACKAGES_DEBUG += update_engine_client</code></td>
</tr>
<tr>
</table>
<h2 id="partitions">Setting partitions (slots)</h2>
<p>A/B devices do not need a recovery partition or cache partition because
Android no longer uses these partitions. The data partition is now used for the
downloaded OTA package, and the recovery image code is on the boot partition.
All partitions that are A/B-ed should be named as follows (slots are always
named <code>a</code>, <code>b</code>, etc.): <code>boot_a</code>,
<code>boot_b</code>, <code>system_a</code>, <code>system_b</code>,
<code>vendor_a</code>, <code>vendor_b</code>.</p>
<h3 id="cache">Cache</h3>
<p>For non-A/B updates, the cache partition was used to store downloaded OTA
packages and to stash blocks temporarily while applying updates. There was
never a good way to size the cache partition: how large it needed to be
depended on what updates you wanted to apply. The worst case would be a cache
partition as large as the system image. With A/B updates there's no need to
stash blocks (because you're always writing to a partition that isn't currently
used) and with streaming A/B there's no need to download the whole OTA package
before applying it.</p>
<h3 id="recovery">Recovery</h3>
<p>The recovery RAM disk is now contained in the <code>boot.img</code> file.
When going into recovery, the bootloader <strong>cannot</strong> put the
<code>skip_initramfs</code> option on the kernel command line.</p>
<p>For non-A/B updates, the recovery partition contains the code used to apply
updates. A/B updates are applied by <code>update_engine</code> running in the
regular booted system image. There is still a recovery mode used to implement
factory data reset and sideloading of update packages (which is where the name
"recovery" came from). The code and data for recovery mode is stored in the
regular boot partition in a ramdisk; to boot into the system image, the
bootloader tells the kernel to skip the ramdisk (otherwise the device boots into
recovery mode. Recovery mode is small (and much of it was already on the boot
partition), so the boot partition doesn't increase in size.</p>
<h3 id="fstab">Fstab</h3>
<p>The <code>slotselect</code> argument <strong>must</strong> be on the line for
the A/B-ed partitions. For example:</p>
<pre class="devsite-click-to-copy">
&lt;path-to-block-device&gt;/vendor /vendor ext4 ro
wait,verify=&lt;path-to-block-device&gt;/metadata,slotselect
</pre>
<p>No partition should be named <code>vendor</code>. Instead, partition
<code>vendor_a</code> or <code>vendor_b</code> will be selected and mounted on
the <code>/vendor</code> mount point.</p>
<h3 id="kernel-slot-arguments">Kernel slot arguments</h3>
<p>The current slot suffix should be passed either through a specific device
tree (DT) node (<code>/firmware/android/slot_suffix</code>) or through the
<code>androidboot.slot_suffix</code> command line argument.</p>
<p>By default, fastboot flashes the current slot on an A/B device. If the update
package also contains images for the other, non-current slot, fastboot flashes those
images as well. Available options include:</p>
<ul>
<li><code>--slot <var>SLOT</var></code>. Override the default behavior and
prompt fastboot to flash the slot that is passed in as an argument.</li>
<li><code>--set-active [<var>SLOT</var>]</code>. Set the slot as active. If
no optional argument is specified, then the current slot is set as active.</li>
<li><code>fastboot --help</code>. Get details on commands.</li>
</ul>
<p>If the bootloader implements fastboot, it should support the command
<code>set_active &lt;slot&gt;</code> that sets the current active slot
to the given slot (this must also clear the unbootable flag for that slot and
reset the retry count to default values). The bootloader should also support the
following variables:</p>
<ul>
<li><code>has-slot:&lt;partition-base-name-without-suffix&gt;</code>. Returns
“yes” if the given partition supports slots, “no” otherwise.</li>
<li><code>current-slot</code>. Returns the slot suffix that will be booted from
next.</li>
<li><code>slot-count</code>. Returns an integer representing the number of
available slots. Currently, two slots are supported so this value is
<code>2</code>.</li>
<li><code>slot-successful:&lt;slot-suffix&gt;</code>. Returns "yes" if the given
slot has been marked as successfully booting, "no" otherwise.</li>
<li><code>slot-unbootable:&lt;slot-suffix&gt;</code>. Returns “yes” if the given
slot is marked as unbootable, "no" otherwise.</li>
<li><code>slot-retry-count<slot suffix></code>. Number of retries remaining to
attempt to boot the given slot.</li>
</ul>
<p>To view all variables, run
<code class="devsite-terminal devsite-click-to-copy">fastboot getvar all</code>.
</p>
<h2 id="ota-package-generation">Generating OTA packages</h2>
<p>The <a href="/devices/tech/ota/tools.html">OTA package tools</a> follow the
same commands as the commands for non-A/B devices. The
<code>target_files.zip</code> file must be generated by defining the build
variables for the A/B target. The OTA package tools automatically identify and
generate packages in the format for the A/B updater.</p>
<p>Examples:</p>
<ul>
<li>To generate a full OTA:
<pre class="devsite-terminal devsite-click-to-copy">
./build/tools/releasetools/ota_from_target_files \
dist_output/tardis-target_files.zip ota_update.zip
</pre>
</li>
<li>To generate an incremental OTA:
<pre class="devsite-terminal devsite-click-to-copy">
./build/tools/releasetools/ota_from_target_files \
-i PREVIOUS-tardis-target_files.zip \
dist_output/tardis-target_files.zip incremental_ota_update.zip
</pre>
</li>
</ul>
<h2 id="configuration">Configuring partitions</h2>
<p>The <code>update_engine</code> can update any pair of A/B partitions defined
in the same disk. A pair of partitions has a common prefix (such as
<code>system</code> or <code>boot</code>) and per-slot suffix (such as
<code>_a</code>). The list of partitions for which the payload generator defines
an update is configured by the <code>AB_OTA_PARTITIONS</code> make variable.</p>
<p>For example, if a pair of partitions <code>bootloader_a</code> and
<code>booloader_b</code> are included (<code>_a</code> and <code>_b</code> are
the slot suffixes), you can update these partitions by specifying the following
on the product or board configuration:</p>
<pre class="devsite-click-to-copy">
AB_OTA_PARTITIONS := \
boot \
system \
bootloader
</pre>
<p>All partitions updated by <code>update_engine</code> must not be modified by
the rest of the system. During incremental or <em>delta</em> updates, the binary
data from the current slot is used to generate the data in the new slot. Any
modification may cause the new slot data to fail verification during the update
process, and therefore fail the update.</p>
<h2 id="post-install">Configuring post-installation</h2>
<p>You can configure the post-install step differently for each updated
partition using a set of key-value pairs. To run a program located at
<code>/system/usr/bin/postinst</code> in a new image, specify the path relative
to the root of the filesystem in the system partition.</p>
<p>For example, <code>usr/bin/postinst</code> is
<code>system/usr/bin/postinst</code> (if not using a RAM disk). Additionally,
specify the filesystem type to pass to the <code>mount(2)</code> system call.
Add the following to the product or device <code>.mk</code> files (if
applicable):</p>
<pre class="devsite-click-to-copy">
AB_OTA_POSTINSTALL_CONFIG += \
RUN_POSTINSTALL_system=true \
POSTINSTALL_PATH_system=usr/bin/postinst \
FILESYSTEM_TYPE_system=ext4
</pre>
<h2 id="compilation">Compiling</h2>
<p>For security reasons, <code>system_server</code> cannot use
<a href="/devices/tech/dalvik/jit-compiler">just-in-time (JIT)</a> compilation.
This means you must compile ahead of time odex files for
<code>system_server</code> and its dependencies at a minimum; anything else is
optional.</p>
<p>To compile apps in the background, you must add the following to the
product's device configuration (in the product's device.mk):</p>
<ol>
<li>Include the native components in the build to ensure compilation script and
binaries are compiled and included in the system image.
<pre class="devsite-click-to-copy">
# A/B OTA dexopt package
PRODUCT_PACKAGES += otapreopt_script
</pre></li>
<li>Connect the compilation script to <code>update_engine</code> such that runs
as a post-install step.
<pre class="devsite-click-to-copy">
# A/B OTA dexopt update_engine hookup
AB_OTA_POSTINSTALL_CONFIG += \
RUN_POSTINSTALL_system=true \
POSTINSTALL_PATH_system=system/bin/otapreopt_script \
FILESYSTEM_TYPE_system=ext4 \
POSTINSTALL_OPTIONAL_system=true
</pre>
</li>
</ol>
<p>For help installing the preopted files in the unused second system partition,
refer to <a href="/devices/tech/dalvik/configure.html#other_odex">First boot
installation of DEX_PREOPT files</a>.</p>
</body>
</html>