| <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 <public-key-id> <path-to-system-partition>"</code></pre> |
| ... where the <code><public-key-id></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 <x509-pem-certificate> -outform der -out <x509-der-certificate></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><public-key-id></code> in the kernel command line. For example, |
| pass <code>Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f</code> in place of |
| <code><public-key-id></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> boot \</code><br/> |
| <code> system \</code><br/> |
| <code> 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> update_engine \</code><br/> |
| <code> 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"> |
| <path-to-block-device>/vendor /vendor ext4 ro |
| wait,verify=<path-to-block-device>/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 <slot></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:<partition-base-name-without-suffix></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:<slot-suffix></code>. Returns "yes" if the given |
| slot has been marked as successfully booting, "no" otherwise.</li> |
| <li><code>slot-unbootable:<slot-suffix></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> |