1# Copyright (c) 2020 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 string
6import random
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
11
12
13def random_string(length, chars):
14    """Generate a random string of characters.
15
16    @param length: the length of string to generate.
17    @param chars: the set of characters to use when generating the string.
18
19    @returns The generated string.
20    """
21    return ''.join(random.SystemRandom().choice(chars) for _ in range(length))
22
23
24class firmware_SysfsVPD(FirmwareTest):
25    """
26    Servo based test for reading VPD data through sysfs.
27
28    This test writes random test strings to the RO and RW sections of VPD data
29    and verifies that they can be read through sysfs after a reboot.
30    """
31    version = 1
32
33    # Length of test string to generate and write to VPD
34    _TEST_VAL_LENGTH = 8
35
36    # Character set to use when generating string to write to VPD
37    _TEST_VAL_CHARS = string.ascii_lowercase + string.digits
38
39    # Name of key to write to RO section of VPD
40    VPD_RO_TEST_KEY = "RO_TEST"
41
42    # Name of key to write to RW section of VPD
43    VPD_RW_TEST_KEY = "RW_TEST"
44
45    def initialize(self, host, cmdline_args, dev_mode=False):
46        """Initialize the test"""
47        super(firmware_SysfsVPD, self).initialize(host, cmdline_args)
48
49        fwver = self.faft_client.system.run_shell_command_get_output(
50                'crossystem fwid')[0]
51        try:
52            fwver_major = int(fwver.split('.')[1])
53        except ValueError:
54            raise error.TestFail('Could not determine firmware version')
55        # Only run this test for 8846 or newer because previous firmware
56        # versions don't add the pointer to ACPI that Linux uses to look up
57        # cbmem which is then used to get the VPD.
58        # See b:156407743 for details.
59        self.disable_test = fwver_major < 8846
60
61        # Backup and mode switching is expensive so skip if we won't be
62        # doing anything anyway.
63        if self.disable_test:
64            raise error.TestNAError("Firmware too old for SysfsVPD")
65
66        self.host = host
67        self.backup_firmware()
68        self.switcher.setup_mode('dev' if dev_mode else 'normal')
69
70    def cleanup(self):
71        """Cleanup the test"""
72        try:
73            if self.is_firmware_saved():
74                self.restore_firmware()
75        finally:
76            super(firmware_SysfsVPD, self).cleanup()
77
78    def run_once(self, dev_mode=False):
79        """Runs a single iteration of the test."""
80        # Log the initial VPD sections so we can manually restore VPD from
81        # test logs if necessary.
82        logging.info("Logging initial RO+RW VPD data")
83        self.host.run("vpd -i RO_VPD -l")
84        self.host.run("vpd -i RW_VPD -l")
85
86        # Generate a random string and write it to RO section of VPD
87        vpd_ro_test_val = random_string(length=self._TEST_VAL_LENGTH,
88                                        chars=self._TEST_VAL_CHARS)
89        logging.info("Writting RO test data to VPD (key = %s, val = %s)",
90                     self.VPD_RO_TEST_KEY, vpd_ro_test_val)
91        self.host.run("vpd -i RO_VPD -s %s=%s" %
92                      (self.VPD_RO_TEST_KEY, vpd_ro_test_val))
93
94        # Generate a random string and write it to RW section of VPD
95        vpd_rw_test_val = random_string(length=self._TEST_VAL_LENGTH,
96                                        chars=self._TEST_VAL_CHARS)
97        logging.info("Writting RW test data to VPD (key = %s, val = %s)",
98                     self.VPD_RW_TEST_KEY, vpd_rw_test_val)
99        self.host.run("vpd -i RW_VPD -s %s=%s" %
100                      (self.VPD_RW_TEST_KEY, vpd_rw_test_val))
101
102        # Reboot DUT to load new VPD data in sysfs
103        logging.info('Rebooting DUT')
104        self.host.reset_via_servo()
105
106        # Verify RO test string can be read through sysfs and matches test value
107        logging.info('Verifying RO VPD test data in sysfs')
108        try:
109            path = "/sys/firmware/vpd/ro/%s" % self.VPD_RO_TEST_KEY
110            result = self.host.run("cat %s" % path)
111            value = result.stdout.strip()
112        except error.AutoservRunError:
113            raise error.TestFail("Failed to read back RO VPD data")
114        if value != vpd_ro_test_val:
115            raise error.TestFail(
116                "Mismatched RO VPD data, read %s (expected %s)" %
117                (value, vpd_ro_test_val))
118
119        # Verify RW test string can be read through sysfs and matches test value
120        logging.info('Verifying RW VPD test data in sysfs')
121        try:
122            path = "/sys/firmware/vpd/rw/%s" % self.VPD_RW_TEST_KEY
123            result = self.host.run("cat %s" % path)
124            value = result.stdout.strip()
125        except error.AutoservRunError:
126            raise error.TestFail("Failed to read back RW VPD data")
127        if value != vpd_rw_test_val:
128            raise error.TestFail(
129                "Mismatched RW VPD data, read %s (expected %s)" %
130                (value, vpd_rw_test_val))
131
132        # Remove the test keys from VPD
133        logging.info("Deleting test data from VPD")
134        self.host.run("vpd -i RO_VPD -d %s" % self.VPD_RO_TEST_KEY)
135        self.host.run("vpd -i RW_VPD -d %s" % self.VPD_RW_TEST_KEY)
136