1# Copyright 2015 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import time 7 8import common 9from autotest_lib.client.common_lib import error 10from autotest_lib.server import test 11 12 13def _assert_equal(expected, actual): 14 """Compares objects. 15 16 @param expected: the expected value. 17 @param actual: the actual value. 18 19 @raises error.TestFail 20 """ 21 if expected != actual: 22 raise error.TestFail('Expected: %r, actual: %r' % (expected, actual)) 23 24 25class brillo_BootLoader(test.test): 26 """A/B tests for boot loader and boot_control HAL implementation.""" 27 version = 1 28 29 30 def get_slots_and_suffix(self): 31 """Gets number of slots supported and slot suffixes used. 32 33 Prerequisite: The DUT is in ADB mode. 34 """ 35 self.num_slots = int(self.dut.run_output('bootctl get-number-slots')) 36 logging.info('Number of slots: %d', self.num_slots) 37 self.suffix_a = self.dut.run_output('bootctl get-suffix 0') 38 self.suffix_b = self.dut.run_output('bootctl get-suffix 1') 39 logging.info('Slot 0 suffix: "%s"', self.suffix_a) 40 logging.info('Slot 1 suffix: "%s"', self.suffix_b) 41 _assert_equal(self.num_slots, 2) 42 43 44 def get_current_slot(self): 45 """Gets the current slot the DUT is running from. 46 47 Prerequisite: The DUT is in ADB mode. 48 """ 49 return int(self.dut.run_output('bootctl get-current-slot')) 50 51 52 def assert_current_slot(self, slot_number): 53 """Checks that DUT is running from the given slot. 54 55 Prerequisite: The DUT is in ADB mode. 56 57 @slot_number: Zero-based index of slot to be running from. 58 """ 59 _assert_equal(self.get_current_slot(), slot_number) 60 61 62 def set_active_slot(self, slot_number): 63 """Instructs the DUT to attempt booting from given slot. 64 65 Prerequisite: The DUT is in ADB mode. 66 67 @slot_number: Zero-based index of slot to make active. 68 """ 69 logging.info('Setting slot %d active.', slot_number) 70 self.dut.run('bootctl set-active-boot-slot %d' % slot_number) 71 72 73 def ensure_running_slot(self, slot_number): 74 """Ensures that DUT is running from the given slot. 75 76 Prerequisite: The DUT is in ADB mode. 77 78 @slot_number: Zero-based index of slot to be running from. 79 """ 80 logging.info('Ensuring device is running from slot %d.', slot_number) 81 if self.get_current_slot() != slot_number: 82 logging.info('Rebooting into slot %d', slot_number) 83 self.set_active_slot(slot_number) 84 self.dut.reboot() 85 self.assert_current_slot(slot_number) 86 87 88 def copy_a_to_b(self): 89 """Copies contents of slot A to slot B. 90 91 Prerequisite: The DUT is in ADB mode and booted from slot A. 92 """ 93 self.assert_current_slot(0) 94 for i in ['boot', 'system']: 95 logging.info('Copying %s%s to %s%s.', 96 i, self.suffix_a, i, self.suffix_b) 97 self.dut.run('dd if=/dev/block/by-name/%s%s ' 98 'of=/dev/block/by-name/%s%s bs=4096' % 99 (i, self.suffix_a, i, self.suffix_b)) 100 101 102 def check_bootctl_set_active(self): 103 """Checks that setActiveBootSlot in the boot_control HAL work. 104 105 Prerequisite: The DUT is in ADB mode with populated A and B slots. 106 """ 107 logging.info('Check setActiveBootSlot() in boot_control HAL.') 108 self.set_active_slot(0) 109 self.dut.reboot() 110 self.assert_current_slot(0) 111 self.set_active_slot(1) 112 self.dut.reboot() 113 self.assert_current_slot(1) 114 115 116 def check_fastboot_set_active(self): 117 """Checks that 'fastboot set_active <SUFFIX>' work. 118 119 Prerequisite: The DUT is in ADB mode with populated A and B slots. 120 """ 121 logging.info('Check set_active command in fastboot-compliant bootloader.') 122 self.dut.ensure_bootloader_mode() 123 # TODO(zeuthen): The "oem set_active <NUMBER>" is specific to 124 # edison. We need to get set_active support in fastboot - see 125 # b/25075082 - and then for vendors to implement "set_active 126 # <SLOT_SUFFIX>" e.g. use suffix instead of number. 127 self.dut.fastboot_run('oem set_active 0') 128 self.dut.fastboot_run('continue') 129 self.dut.adb_run('wait-for-device') 130 self.assert_current_slot(0) 131 self.dut.ensure_bootloader_mode() 132 self.dut.fastboot_run('oem set_active 1') 133 self.dut.fastboot_run('continue') 134 self.dut.adb_run('wait-for-device') 135 self.assert_current_slot(1) 136 137 138 def check_bootloader_fallback_on_invalid(self): 139 """Checks bootloader fallback if current slot is invalid. 140 141 Prerequisite: The DUT is in ADB mode with populated A and B slots. 142 """ 143 logging.info('Checking bootloader fallback if current slot ' 144 'is invalid.') 145 # Make sure we're in slot B, then zero out boot_b (so slot B 146 # is invalid), reboot and check that the bootloader correctly 147 # fell back to A. 148 self.ensure_running_slot(1) 149 self.dut.run('dd if=/dev/zero of=/dev/block/by-name/boot%s ' 150 'count=8192 bs=4096' % self.suffix_b) 151 self.dut.reboot() 152 self.assert_current_slot(0) 153 # Restore boot_b for use in future test cases. 154 self.dut.run('dd if=/dev/block/by-name/boot%s ' 155 'of=/dev/block/by-name/boot%s bs=4096' % 156 (self.suffix_a, self.suffix_b)) 157 158 159 def check_bootloader_fallback_on_retries(self): 160 """Checks bootloader fallback if slot made active runs out of tries. 161 162 Prerequisite: The DUT is in ADB mode with populated A and B slots. 163 164 @raises error.TestFail 165 """ 166 logging.info('Checking bootloader fallback if slot made active ' 167 'runs out of tries.') 168 self.ensure_running_slot(0) 169 self.set_active_slot(1) 170 self.dut.reboot() 171 num_retries = 1 172 while num_retries < 10 and self.get_current_slot() == 1: 173 logging.info('Still with B after %d retries' % num_retries) 174 num_retries += 1 175 self.dut.reboot() 176 if self.get_current_slot() != 0: 177 raise error.TestFail('Bootloader did not fall back after ' 178 '%d retries without the slot being marked ' 179 'as GOOD' % num_retries) 180 logging.info('Falled back to A after %d retries.', num_retries) 181 182 183 def check_bootloader_mark_successful(self): 184 """Checks bootloader stays with slot after it has been marked good. 185 186 Prerequisite: The DUT is in ADB mode with populated A and B slots. 187 """ 188 logging.info('Checking bootloader is staying with a slot after it has ' 189 'been marked as GOOD for at least 10 reboots.') 190 self.ensure_running_slot(0) 191 self.dut.run('bootctl mark-boot-successful') 192 num_reboots = 0 193 while num_reboots < 10: 194 self.dut.reboot() 195 self.assert_current_slot(0) 196 num_reboots += 1 197 logging.info('Still with A after %d reboots' % num_reboots) 198 199 200 def run_once(self, dut=None): 201 """A/B tests for boot loader and boot_control HAL implementation. 202 203 Verifies that boot loader and boot_control HAL implementation 204 implements A/B correctly. 205 206 Prerequisite: The DUT is in ADB mode. 207 208 @param dut: host object representing the device under test. 209 """ 210 self.dut = dut 211 self.get_slots_and_suffix() 212 self.ensure_running_slot(0) 213 self.copy_a_to_b() 214 self.check_bootctl_set_active() 215 self.check_fastboot_set_active() 216 self.check_bootloader_fallback_on_invalid() 217 self.check_bootloader_fallback_on_retries() 218 self.check_bootloader_mark_successful() 219