Home | History | Annotate | Download | only in brillo_BootLoader
      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 
      5 import logging
      6 
      7 import common
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.server import test
     10 
     11 
     12 def _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 
     24 class 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