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 6 7import common 8from autotest_lib.client.common_lib import error 9from autotest_lib.server import test 10 11 12def _assert_equal(expected, actual): 13 """Compares objects. 14 15 @param expected: the expected value. 16 @param actual: the actual value. 17 18 @raises error.TestFail 19 """ 20 if expected != actual: 21 raise error.TestFail('Expected: %r, actual: %r' % (expected, actual)) 22 23 24class brillo_BootLoader(test.test): 25 """A/B tests for boot loader and boot_control HAL implementation.""" 26 version = 1 27 28 29 def get_slots_and_suffix(self): 30 """Gets number of slots supported and slot suffixes used. 31 32 Prerequisite: The DUT is in ADB mode. 33 """ 34 self.num_slots = int(self.dut.run_output('bootctl get-number-slots')) 35 logging.info('Number of slots: %d', self.num_slots) 36 self.suffix_a = self.dut.run_output('bootctl get-suffix 0') 37 self.suffix_b = self.dut.run_output('bootctl get-suffix 1') 38 logging.info('Slot 0 suffix: "%s"', self.suffix_a) 39 logging.info('Slot 1 suffix: "%s"', self.suffix_b) 40 _assert_equal(2, self.num_slots) 41 # We're going to need the size of the boot partitions later. 42 self.boot_a_size = int(self.dut.run_output( 43 'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_a)) 44 self.boot_b_size = int(self.dut.run_output( 45 'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_b)) 46 if self.boot_a_size != self.boot_b_size: 47 raise error.TestFail('boot partitions are not the same size') 48 logging.info('boot partition size: %d bytes', self.boot_a_size) 49 50 51 def fastboot_get_var(self, variable_name): 52 """Gets a fastboot variable. 53 54 Returns a string with the value or the empty string if the 55 variable does not exist. 56 57 Prerequisite: The DUT is in bootloader mode. 58 59 @param variable_name: Name of fastboot variable to get. 60 61 """ 62 cmd_output = self.dut.fastboot_run('getvar %s' % variable_name) 63 # Gah, 'fastboot getvar' prints requested output on stderr 64 # instead of stdout as you'd expect. 65 lines = cmd_output.stderr.split('\n') 66 if lines[0].startswith(variable_name + ': '): 67 return (lines[0])[len(variable_name + ': '):] 68 return '' 69 70 71 def check_fastboot_variables(self): 72 """Checks that fastboot bootloader has necessary variables for A/B. 73 74 Prerequisite: The DUT is in ADB mode. 75 """ 76 logging.info('Checking fastboot-compliant bootloader has necessary ' 77 'variables for A/B.') 78 self.dut.ensure_bootloader_mode() 79 # The slot-suffixes looks like '_a,_b' and may have a trailing comma. 80 suffixes = self.fastboot_get_var('slot-suffixes') 81 if suffixes.rstrip(',').split(',') != [self.suffix_a, self.suffix_b]: 82 raise error.TestFail('Variable slot-suffixes has unexpected ' 83 'value "%s"' % suffixes) 84 # Back to ADB mode. 85 self.dut.fastboot_reboot() 86 87 88 def get_current_slot(self): 89 """Gets the current slot the DUT is running from. 90 91 Prerequisite: The DUT is in ADB mode. 92 """ 93 return int(self.dut.run_output('bootctl get-current-slot')) 94 95 96 def assert_current_slot(self, slot_number): 97 """Checks that DUT is running from the given slot. 98 99 Prerequisite: The DUT is in ADB mode. 100 101 @param slot_number: Zero-based index of slot to be running from. 102 """ 103 _assert_equal(slot_number, self.get_current_slot()) 104 105 106 def set_active_slot(self, slot_number): 107 """Instructs the DUT to attempt booting from given slot. 108 109 Prerequisite: The DUT is in ADB mode. 110 111 @param slot_number: Zero-based index of slot to make active. 112 """ 113 logging.info('Setting slot %d active.', slot_number) 114 self.dut.run('bootctl set-active-boot-slot %d' % slot_number) 115 116 117 def ensure_running_slot(self, slot_number): 118 """Ensures that DUT is running from the given slot. 119 120 Prerequisite: The DUT is in ADB mode. 121 122 @param slot_number: Zero-based index of slot to be running from. 123 """ 124 logging.info('Ensuring device is running from slot %d.', slot_number) 125 if self.get_current_slot() != slot_number: 126 logging.info('Rebooting into slot %d', slot_number) 127 self.set_active_slot(slot_number) 128 self.dut.reboot() 129 self.assert_current_slot(slot_number) 130 131 132 def copy_a_to_b(self): 133 """Copies contents of slot A to slot B. 134 135 Prerequisite: The DUT is in ADB mode and booted from slot A. 136 """ 137 self.assert_current_slot(0) 138 for i in ['boot', 'system']: 139 logging.info('Copying %s%s to %s%s.', 140 i, self.suffix_a, i, self.suffix_b) 141 self.dut.run('dd if=/dev/block/by-name/%s%s ' 142 'of=/dev/block/by-name/%s%s bs=4096' % 143 (i, self.suffix_a, i, self.suffix_b)) 144 145 146 def check_bootctl_set_active(self): 147 """Checks that setActiveBootSlot in the boot_control HAL work. 148 149 Prerequisite: The DUT is in ADB mode with populated A and B slots. 150 """ 151 logging.info('Check setActiveBootSlot() in boot_control HAL.') 152 self.set_active_slot(0) 153 self.dut.reboot() 154 self.assert_current_slot(0) 155 self.set_active_slot(1) 156 self.dut.reboot() 157 self.assert_current_slot(1) 158 159 160 def check_fastboot_set_active(self): 161 """Checks that 'fastboot set_active <SUFFIX>' work. 162 163 Prerequisite: The DUT is in ADB mode with populated A and B slots. 164 """ 165 logging.info('Check set_active command in fastboot-compliant bootloader.') 166 self.dut.ensure_bootloader_mode() 167 self.dut.fastboot_run('set_active %s' % self.suffix_a) 168 self.dut.fastboot_reboot() 169 self.dut.adb_run('wait-for-device') 170 self.assert_current_slot(0) 171 self.dut.ensure_bootloader_mode() 172 self.dut.fastboot_run('set_active %s' % self.suffix_b) 173 self.dut.fastboot_reboot() 174 self.dut.adb_run('wait-for-device') 175 self.assert_current_slot(1) 176 177 178 def check_bootloader_fallback_on_invalid(self): 179 """Checks bootloader fallback if current slot is invalid. 180 181 Prerequisite: The DUT is in ADB mode with populated A and B slots. 182 """ 183 logging.info('Checking bootloader fallback if current slot ' 184 'is invalid.') 185 # Make sure we're in slot B, then zero out boot_b (so slot B 186 # is invalid), reboot and check that the bootloader correctly 187 # fell back to A. 188 self.ensure_running_slot(1) 189 self.dut.run('dd if=/dev/zero of=/dev/block/by-name/boot%s ' 190 'count=%d bs=4096' % (self.suffix_b, 191 self.boot_b_size/4096)) 192 self.dut.reboot() 193 self.assert_current_slot(0) 194 # Restore boot_b for use in future test cases. 195 self.dut.run('dd if=/dev/block/by-name/boot%s ' 196 'of=/dev/block/by-name/boot%s bs=4096' % 197 (self.suffix_a, self.suffix_b)) 198 199 200 def check_bootloader_fallback_on_retries(self): 201 """Checks bootloader fallback if slot made active runs out of tries. 202 203 Prerequisite: The DUT is in ADB mode with populated A and B slots. 204 205 @raises error.TestFail 206 """ 207 logging.info('Checking bootloader fallback if slot made active ' 208 'runs out of tries.') 209 self.ensure_running_slot(0) 210 self.set_active_slot(1) 211 self.dut.reboot() 212 num_retries = 1 213 while num_retries < 10 and self.get_current_slot() == 1: 214 logging.info('Still with B after %d retries', num_retries) 215 num_retries += 1 216 self.dut.reboot() 217 if self.get_current_slot() != 0: 218 raise error.TestFail('Bootloader did not fall back after ' 219 '%d retries without the slot being marked ' 220 'as GOOD' % num_retries) 221 logging.info('Fell back to A after %d retries.', num_retries) 222 223 224 def check_bootloader_mark_successful(self): 225 """Checks bootloader stays with slot after it has been marked good. 226 227 Prerequisite: The DUT is in ADB mode with populated A and B slots. 228 """ 229 logging.info('Checking bootloader is staying with a slot after it has ' 230 'been marked as GOOD for at least 10 reboots.') 231 self.ensure_running_slot(0) 232 self.dut.run('bootctl mark-boot-successful') 233 num_reboots = 0 234 while num_reboots < 10: 235 self.dut.reboot() 236 self.assert_current_slot(0) 237 num_reboots += 1 238 logging.info('Still with A after %d reboots', num_reboots) 239 240 241 def run_once(self, dut=None): 242 """A/B tests for boot loader and boot_control HAL implementation. 243 244 Verifies that boot loader and boot_control HAL implementation 245 implements A/B correctly. 246 247 Prerequisite: The DUT is in ADB mode. 248 249 @param dut: host object representing the device under test. 250 """ 251 self.dut = dut 252 self.get_slots_and_suffix() 253 self.check_fastboot_variables() 254 self.ensure_running_slot(0) 255 self.copy_a_to_b() 256 self.check_bootctl_set_active() 257 self.check_fastboot_set_active() 258 self.check_bootloader_fallback_on_invalid() 259 self.check_bootloader_fallback_on_retries() 260 self.check_bootloader_mark_successful() 261