1# Copyright (c) 2019 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 random
7import time
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
11
12# Serial number cannot contain vowels after the first character, so don't allow
13# any vowels in this test.
14VALID_CHARS = "BCDFGHJKLMNPQRSTVWXYZ0123456789"
15
16class firmware_SetSerialNumber(FirmwareTest):
17    """
18    Servo based test to set serial number in firmware.
19
20    Test that setting the serial number in firmware during an onsite RMA works.
21    Mainboards for onsite RMA will have WP# asserted, flash WP bit cleared,
22    the serial_number in VPD set to all spaces, and be in dev mode. After the
23    serial number is set in firmware flash write protect will be enabled and
24    the device will be in normal mode.
25    """
26    version = 1
27
28    def start_serial_number_prompt(self):
29        """Repeatedly press ctrl-s to bring up serial number prompt."""
30        logging.info("Pressing Ctrl-S.")
31        timeout = time.time() + (self.faft_config.firmware_screen)
32        while time.time() < timeout:
33            self.servo.set_nocheck('ctrl_s', 'tab')
34            time.sleep(1)
35
36    def generate_serial_number_string(self):
37        """Generate a valid serial number based on config"""
38        return ''.join(random.choice(VALID_CHARS)
39                       for _ in range(self.faft_config.serial_number_length))
40
41    def send_string(self, keys):
42        """Send a string over a servo"""
43        for key in keys.lower():
44            self.servo.set_nocheck('arb_key_config', key)
45            self.servo.set_nocheck('arb_key', 'tab')
46
47    def initialize(self, host, cmdline_args):
48        super(firmware_SetSerialNumber, self).initialize(host, cmdline_args)
49
50        if self.faft_config.serial_number_length == 0:
51            raise error.TestNAError("This test is only valid on devices where "
52                                    "serial number can be set in firmware.")
53
54        # This test is run on developer mode only.
55        self.switcher.setup_mode('dev')
56
57        # Disable fw wp to clear VPD value
58        self.set_hardware_write_protect(False)
59
60        self.faft_client.system.run_shell_command_get_output(
61            'flashrom -p host --wp-disable')
62
63        self.faft_client.system.run_shell_command_get_output(
64            'vpd -i RO_VPD -s serial_number="%s"' %
65            (' ' * self.faft_config.serial_number_length)
66        )
67
68        self.set_hardware_write_protect(True)
69
70    def run_once(self):
71        """Run the test"""
72        serial_number = self.generate_serial_number_string()
73        logging.info("Setting serial number to '%s'.", serial_number)
74
75        self.switcher.simple_reboot()
76        if not self.faft_config.rma_entered_automatically:
77            self.start_serial_number_prompt()
78        else:
79            time.sleep(self.faft_config.firmware_screen)
80        self.send_string(serial_number)
81        # Press enter on set
82        self.servo.enter_key()
83        # Press enter on confirm
84        self.servo.enter_key()
85        self.switcher.wait_for_client()
86
87        # Check that device is no longer in dev mode
88        self.checkers.mode_checker('normal')
89
90        # Check that serial_number is correctly set
91        result = self.faft_client.system.run_shell_command_get_output(
92            'vpd -i RO_VPD -g serial_number')[0]
93
94        if result != serial_number:
95            raise error.TestFail("Expected serial number '%s' but got '%s'" % (
96                serial_number, result))
97
98        # Check that write protect is correctly set
99        result = self.faft_client.system.run_shell_command_get_output(
100            'flashrom -p host --wp-status')
101
102        result = '\n'.join(result)
103
104        if ('is disabled' in result or
105                'start=0x00000000' in result or
106                'len=0x00000000' in result):
107           raise error.TestFail('Expected write protection to be enabled '
108                                'but output was:\n\n%s' % result)
109
110    def cleanup(self):
111        self.servo.set_nocheck('fw_wp_state', 'reset')
112        super(firmware_SetSerialNumber, self).cleanup()
113