Skip to content

OTA Architecture

The OTA campaign pushed successfully to 10,000 vehicles, then bricked unit #10,001. How do you prevent that?

TL;DR

OTA (Over-The-Air) updates write to the inactive A/B slot while the active slot keeps the vehicle running, then reboot into the new slot on success. Virtual A/B reduces storage overhead by snapshotting blocks instead of duplicating entire partitions. UpdateEngine (AAOS) orchestrates download → verify → apply → mark pending → reboot → post-boot verification. Slot metadata (bootctrl in misc) tracks current slot, boot success, retry counts, and unbootable flags. Rollback is automatic if the new slot fails verification or post-boot health checks within a merge/rollback window. OTA is a state machine with cryptographic gates, not a simple file copy.

You trigger OTAs with lb campaigns. This page is the machinery behind the button: slots, snapshots, metadata, and the state transitions you’ll grep in update_engine logs.

Classic A/B OTA maintains duplicate physical partitions:

Active: boot_a + super (system_a, vendor_a, ...) + vbmeta_a
Inactive: boot_b + super (system_b, vendor_b, ...) + vbmeta_b
Phase Active Slot Inactive Slot
Normal operation Running OS Stale or empty
OTA download Running OS Unchanged (payload cached separately)
OTA apply Running OS Being written
Reboot pending Running until reboot Marked pending/active
First boot after OTA Unused Boot attempt
Success Becomes fallback Confirmed active
Failure Fallback boot Marked unbootable

OTA always builds on the inactive slot while the active slot keeps the vehicle running.

Mental Model

A/B OTA is changing tires while driving — the car runs on three wheels while the fourth is swapped. You only commit weight to the new tire (reboot) after it’s bolted on (apply complete + verified). If the new tire wobbles (boot failure), you put the old one back (slot fallback).

Full A/B duplicates large partitions, expensive on 64 GB UFS programs. Virtual A/B (Android 11+) uses copy-on-write snapshots:

Classic A/B Virtual A/B
Two full super copies One super + snapshot COW overlay
Higher storage cost ~40–50% less space for same safety
Simple mental model More complex merge phase
Direct partition write snapshotd tracks changed blocks

Virtual A/B flow additions:

  1. Create snapshot before apply
  2. Apply payload to snapshot/COW layer
  3. Boot into snapshot
  4. Merge snapshot into base on success (background, may take minutes)
  5. On failure → cancel snapshot, revert to base

Check your platform:

Terminal window
adb shell getprop ro.virtual_ab.enabled
# true = Virtual A/B
Common Gotcha

Virtual A/B merge in progress can block subsequent OTAs. If update_engine reports NEED_REBOOT or merge running, don’t start another campaign. You’ll get cryptic SNAPSHOT_MERGE errors in logs.

stateDiagram-v2
[*] --> Idle
Idle --> Downloading: Update available
Downloading --> DownloadComplete: Payload on disk
Downloading --> Idle: Cancel / network fail
DownloadComplete --> Verifying: Hash + signature check
Verifying --> Applying: Valid payload
Verifying --> Idle: Invalid — abort
Applying --> Applied: Inactive slot written
Applying --> Idle: Apply error
Applied --> PendingReboot: Mark slot pending
PendingReboot --> FirstBoot: User/system reboot
FirstBoot --> PostVerify: New slot boots
PostVerify --> Success: Health OK
PostVerify --> Rollback: Boot fail / dm-verity fail
Success --> Merge: Virtual A/B merge (if enabled)
Merge --> Idle: Merge complete
Rollback --> Idle: Fallback slot active
Success --> Idle: Classic A/B — done
Phase Component What Happens
1. Check UpdateEngine / campaign agent Query server or local package for new build
2. Download HTTP(S) / gRPC stream Payload to /data/ota_package/ or cache partition
3. Verify UpdateEngine + OpenSSL Signature, metadata, compatible version, rollback index
4. Apply update_engine --payload Binary diff or full payload → inactive slot partitions
5. Finalize bootctrl / misc Set slot priority, mark pending reboot
6. Reboot Power manager / user consent Automotive: often parked + ignition policy gated
7. Post-boot verify ABL + init + UpdateEngine dm-verity, slot success marker, crash detection
8. Merge snapshotd (Virtual A/B) COW merge into base partition

Rollback operates at multiple layers:

Layer Trigger Action
Pre-apply Signature/rollback index fail Abort before writing inactive slot
Apply failure I/O error, hash mismatch mid-write Abort; inactive slot marked incomplete
Boot verification ABL AVB failure Try alternate slot
Post-boot window Crash loop, watchdog, explicit markBootSuccessful() timeout Revert bootctrl to previous slot
User-initiated Factory tools / recovery Manual slot switch via fastboot

Stored in misc partition (or dedicated metadata region):

Field Purpose
Current slot a or b; which slot booted this session
Priority Higher priority slot tried first at boot
Successful boot flag Set after clean boot + health check
Retry count Decremented on failed boot attempts
Unbootable flag Slot skipped entirely when set
Terminal window
# From running Android
adb shell bootctl get-current-slot
adb shell bootctl get-suffix 1 # _b suffix for slot 1
adb shell bootctl dump-bootloader-control

A slot isn’t truly active until markBootSuccessful() runs. First boot after OTA is a probation period.

Delta vs Full Payloads

Full payloads replace entire partition contents: simpler, larger download. Delta payloads (binary diffs against source version) produce smaller OTA packages but require strict source version matching. Mismatch between installed build and delta source → apply failure. Campaign tools like lb must target exact build fingerprints when using deltas.

Cockpit OTAs add policy gates beyond phone Android:

Constraint Reason
Vehicle speed = 0 Safety; no reboot while moving
Battery / power budget Sufficient power for download + apply + reboot
Ignition state Some programs require ACC ON or engine running for apply
Multi-ECU coordination Cockpit + modem + ADAS may need synchronized campaigns
User consent UI AAOS Automotive settings / OEM HMI prompt
Differential signing Production keys, fleet certificate pinning
Where You'll See This

OTA debug grep list (save this):

Terminal window
adb logcat -s update_engine UpdateEngine
adb shell dumpsys update_engine
adb shell bootctl get-current-slot
adb shell getprop ro.build.fingerprint

States to recognize in logs:

Log Signal Meaning
UpdateStatus::DOWNLOADING Payload in progress
UpdateStatus::VERIFYING Signature/hash gate
UpdateStatus::FINALIZING Writing slot metadata
UpdateStatus::UPDATED_NEED_REBOOT Apply done; reboot required
ErrorCode::kDownloadTransferError Network issue, not a signing issue
ErrorCode::kPayloadHashMismatchError Corrupt download or wrong payload
ErrorCode::kRollbackNotPermitted Rollback index too low

After OTA reboot, confirm success before celebrating:

Terminal window
adb shell bootctl get-current-slot # did slot flip?
adb shell getprop ro.build.display.id # correct build?
Connect the Dots

Page 8.1 defined A/B partition names. Page 8.4 explained AVB gates during apply and first boot. Page 8.6 maps this architecture to your lb tool’s gRPC + SOME/IP transport layer.

  1. A/B OTA writes the inactive slot while the active slot runs. Reboot switches.
  2. Virtual A/B trades snapshot complexity for storage savings.
  3. Slot metadata in bootctrl governs fallback. First boot is probation until marked successful.
  4. OTA is a state machine. Learn UpdateEngine statuses to debug failed campaigns.

Check Your Understanding

1. During a classic A/B OTA apply phase, which slot is being written?

2. What marks an OTA slot transition as permanently successful (not subject to automatic rollback)?

3. How does Virtual A/B differ from classic A/B?