# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging, time
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.power import power_utils
from autotest_lib.client.cros.power import sys_power

MSR_POSITIVE = {
    # IA32_FEATURE_CONTROL[2:0]
    #   0 - Lock bit (1 = locked)
    #   1 - Enable VMX in SMX operation
    #   2 - Enable VMX outside SMX operation
    'Atom': {
        # Some CPUs reporting as "Atom" have VMX enabled.
        },
    'Core M': {
        # Some CPUs reporting as "Core M" have VMX enabled.
        },
    'Core': {
        # Some CPUs reporting as "Core" have VMX enabled.
        },
    'Stoney': {
        # VM_CR MSR (C001_0114h) with SVMDIS Bit 4
        # can be used to lock writes to EFER.SVME.
        #   0 - writes to EFER.SVME are not blocked
        #   1 - writes to EFER treat EFER.SVME as MBZ
        '0xc0010114':  [('4', 0)],
        },
    }

MSR_NEGATIVE = {
    'Atom': {
        # No board has all bits set so this should fail.
        '0x3a':  [('2:0', 7)],
        },
    'Core M': {
        # No board has all bits set so this should fail.
        '0x3a':  [('2:0', 7)],
        },
    'Core': {
        # No board has all bits set so this should fail.
        '0x3a':  [('2:0', 7)],
        },
    'Stoney': {
        # Inverted from positive case: none of these bits should be set.
        '0xc0010114':  [('4', 1)],
        },
    }

RCBA_POSITIVE = {
    'Atom': {
        # GCS.BILD is not set on H2C UEFI Firmware. :(
        # https://code.google.com/p/chromium/issues/detail?id=269633
        '0x3410': [('0', 0)],
        },
    'Core M': {
        # GCS (General Control and Status) register, BILD (BIOS Interface
        # Lock-Down) bit should be set.
        '0x3410': [('0', 1)],
        },
    'Core': {
        # GCS (General Control and Status) register, BILD (BIOS Interface
        # Lock-Down) bit should be set.
        '0x3410': [('0', 1)],
        },
    'Stoney': {
        # Skipping this test as there is no register to change
        # reset vector on Stoney. NA for Stoney.
        },
    }

RCBA_NEGATIVE = {
    'Atom': {
        # GCS register, BILD bit inverted from positive test.
        '0x3410': [('0', 1)],
        },
    'Core M': {
        # GCS register, BILD bit inverted from positive test.
        '0x3410': [('0', 0)],
        },
    'Core': {
        # GCS register, BILD bit inverted from positive test.
        '0x3410': [('0', 0)],
        },
    'Stoney': {
        },
    }

class security_x86Registers(test.test):
    """
    Checks various CPU and firmware registers for security-sensitive safe
    settings.
    """
    version = 1

    def _check_negative_positive(self, name, func, match_neg, match_pos):
        errors = 0

        # Catch missing test conditions.
        if len(match_neg) == 0:
            logging.debug('No inverted %s tests defined!', name)
        if len(match_pos) == 0:
            logging.debug('No positive %s tests defined!', name)
        if len(match_neg) == 0 or len(match_pos) == 0:
            return errors

        # Negative tests; make sure infrastructure is working.
        logging.debug("=== BEGIN [expecting %s FAILs] ===", name)
        if func(match_neg) == 0:
            logging.error('BAD: inverted %s tests did not fail!', name)
            errors += 1
        logging.debug("=== END [expecting %s FAILs] ===", name)

        # Positive tests; make sure values are for real.
        logging.debug("=== BEGIN [expecting %s oks] ===", name)
        errors += func(match_pos)
        logging.debug("=== END [expecting %s oks] ===", name)

        logging.debug("%s errors found: %d", name, errors)
        return errors

    def _check_msr(self):
        return self._check_negative_positive('MSR',
                                             self._registers.verify_msr,
                                             MSR_NEGATIVE[self._cpu_type],
                                             MSR_POSITIVE[self._cpu_type])

    def _check_bios(self):
        return self._check_negative_positive('BIOS',
                                             self._registers.verify_rcba,
                                             RCBA_NEGATIVE[self._cpu_type],
                                             RCBA_POSITIVE[self._cpu_type])

    def _check_all(self):
        errors = 0
        errors += self._check_msr()
        errors += self._check_bios()
        return errors

    def run_once(self):
        errors = 0

        cpu_arch = power_utils.get_x86_cpu_arch()
        if not cpu_arch:
            cpu_arch = utils.get_cpu_arch()
            if cpu_arch == "arm":
                logging.info('OK: skipping x86-only test on %s.', cpu_arch)
                return
            raise error.TestNAError('Unknown CPU with arch "%s".' % (cpu_arch))

        if cpu_arch == 'Stoney':
            self._cpu_type = 'Stoney'
        elif cpu_arch == 'Atom':
            self._cpu_type = 'Atom'
        elif cpu_arch == 'Core M':
            self._cpu_type = 'Core M'
        else:
            self._cpu_type = 'Core'

        self._registers = power_utils.Registers()

        # Check running machine.
        errors += self._check_all()

        # Pause briefly to make sure the RTC is ready for suspend/resume.
        time.sleep(3)
        # Suspend the system to RAM and return after 10 seconds.
        sys_power.do_suspend(10)

        # Check resumed machine.
        errors += self._check_all()

        if errors > 0:
            raise error.TestFail('x86 register mismatch detected')