blob: 0561c6a5d7e873d607c5cec5aae9b517cbfcd796 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package firmware
import (
"context"
"regexp"
"strings"
"time"
"go.chromium.org/tast-tests/cros/common/servo"
"go.chromium.org/tast-tests/cros/remote/firmware"
"go.chromium.org/tast-tests/cros/remote/firmware/fixture"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/hwdep"
)
// If this test fails, check that CONFIG_SYSTEM_UNLOCKED is disabled.
func init() {
testing.AddTest(&testing.Test{
Func: ECSystemLocked,
Desc: "This test case verifies that changing the FW write protection state has expected effect in EC sysinfo",
Contacts: []string{
"chromeos-faft@google.com",
"jbettis@google.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
Attr: []string{"group:firmware", "firmware_ec"},
Fixture: fixture.DevModeGBB,
HardwareDeps: hwdep.D(hwdep.ChromeEC()),
Requirements: []string{"sys-fw-0022-v02"},
LacrosStatus: testing.LacrosVariantUnneeded,
Timeout: 12 * time.Minute,
})
}
func ECSystemLocked(ctx context.Context, s *testing.State) {
h := s.FixtValue().(*fixture.Value).Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to connect to servod")
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to get config")
}
// WP should be disabled at test end.
defer func() {
s.Log("Restore the FW write protect to disabled")
if err := setWPState(ctx, h, false); err != nil {
s.Fatal("Failed to disable wp at test end: ", err)
}
}()
s.Log("Test wp state with WP disabled")
if err := verifyECWPStatus(ctx, h, false); err != nil {
s.Fatal("EC lockstate wrong: ", err)
}
s.Log("Test wp state with WP enabled")
if err := verifyECWPStatus(ctx, h, true); err != nil {
s.Fatal("EC lockstate wrong: ", err)
}
}
func setWPState(ctx context.Context, h *firmware.Helper, newState bool) error {
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
return errors.Wrap(err, "creating mode switcher")
}
/*
Some ITE ECs can only clear their WP status on a power-on reset,
so changing HW WP requires hard ec reboot to take effect, possible cases:
init HW | init SW | desired | Steps
0 | 0 | 0 | Return
0 | 0 | 1 | SW on -> HW on -> Reboot -> Return
0 | 1 | 0 | SW off -> Return
0 | 1 | 1 | HW on -> Reboot -> Return
1 | 0 | 0 | HW off -> Reboot -> Return
1 | 0 | 1 | HW off -> Reboot -> SW on -> HW on -> Reboot -> Return
1 | 1 | 0 | HW off -> Reboot -> SW off -> Return
1 | 1 | 1 | Return
Case 6 would require 2 reboots here, but since hw and sw wp should always match,
and both should be disabled at test start/end, we assume they always correspond.
Then, we only need to consider cases 1, 2, 7, 8.
*/
initialState, err := isHWWPEnabled(ctx, h)
if err != nil {
return errors.Wrap(err, "failed to get initial write protect state")
}
if initialState == newState {
testing.ContextLogf(ctx, "WP State already %v, no action required", initialState)
return nil
} else if newState { // Need to enable WP from disabled state.
// Enable SW WP before hardware WP.
testing.ContextLog(ctx, "Setting sw wp to enable")
if err := h.Servo.RunECCommand(ctx, "flashwp enable"); err != nil {
return errors.Wrap(err, "failed to set flashwp enable")
}
if err := h.Servo.SetFWWPState(ctx, servo.FWWPStateOn); err != nil {
return errors.Wrap(err, "failed to enable hw write protect")
}
testing.ContextLog(ctx, "Rebooting the DUT")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset, firmware.AllowGBBForce); err != nil {
return errors.Wrap(err, "failed to perform mode aware reboot")
}
} else { // Need to disable WP from enabled state.
if err := h.Servo.SetFWWPState(ctx, servo.FWWPStateOff); err != nil {
return errors.Wrap(err, "failed to disable hw write protect")
}
// Reboot after deasserting hardware write protect pin to deactivate
// write protect. And then remove software write protect flag.
testing.ContextLog(ctx, "Rebooting the DUT")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset, firmware.AllowGBBForce); err != nil {
return errors.Wrap(err, "failed to perform mode aware reboot")
}
// Disable SW WP after hardware WP.
testing.ContextLog(ctx, "Setting sw wp to disable")
if err := h.Servo.RunECCommand(ctx, "flashwp disable"); err != nil {
return errors.Wrap(err, "failed to set flashwp disable")
}
}
currState, err := isHWWPEnabled(ctx, h)
if err != nil {
return errors.Wrap(err, "failed to get new write protect state")
}
if currState != newState {
return errors.Errorf("fw wp state after reboot was %v, want %v", currState, newState)
}
testing.ContextLog(ctx, "FW write protect state has been successfully set to ", currState)
return nil
}
func isHWWPEnabled(ctx context.Context, h *firmware.Helper) (bool, error) {
state, err := h.Servo.GetString(ctx, servo.FWWPState)
if err != nil {
return false, errors.Wrap(err, "failed to get initial write protect state")
}
switch servo.FWWPStateValue(state) {
case servo.FWWPStateOn:
return true, nil
case servo.FWWPStateOff:
return false, nil
default:
return false, errors.New("invalid FW WP state: " + state)
}
}
func verifyECWPStatus(ctx context.Context, h *firmware.Helper, wp bool) error {
if err := setWPState(ctx, h, wp); err != nil {
return errors.Wrapf(err, "failed to set wp to %v", wp)
}
// Stop interrupting logs/console spam, not essential.
testing.ContextLog(ctx, "Powering off AP")
if err := h.Servo.RunECCommand(ctx, "apshutdown"); err != nil {
testing.ContextLog(ctx, "Failed to shutdown ap: ", err)
}
defer func() {
testing.ContextLog(ctx, "Checking for G3 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "G3"); err != nil {
testing.ContextLog(ctx, "Failed to get G3 state")
}
testing.ContextLog(ctx, "Restarting AP")
if err := h.Servo.RunECCommand(ctx, "powerbtn"); err != nil {
testing.ContextLog(ctx, "Failed to restart: ", err)
}
if err := h.WaitConnect(ctx); err != nil {
testing.ContextLog(ctx, "Failed to boot to connect to DUT")
}
}()
testing.ContextLog(ctx, "Checking lock state in sysinfo")
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := h.Servo.RunECCommandGetOutput(ctx, "sysinfo", []string{`Flags:\s+(locked|unlocked)[^\n]*\n`})
if err != nil {
return errors.Wrap(err, "sysinfo failed")
}
if wp && out[0][1] != "locked" {
return errors.Errorf("sysinfo reported wrong flags, got %v want %v", out[0][1], "locked")
}
if !wp && out[0][1] != "unlocked" {
return errors.Errorf("sysinfo reported wrong flags, got %v want %v", out[0][1], "unlocked")
}
return nil
}, &testing.PollOptions{Timeout: 5 * time.Second, Interval: 500 * time.Millisecond}); err != nil {
return errors.Wrap(err, "looking for sysinfo")
}
testing.ContextLog(ctx, "Checking wp_gpio_asserted state in flashinfo")
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := h.Servo.RunECCommandGetOutput(ctx, "flashinfo", []string{`Flags:.*\n.*\n`})
if out == nil || err != nil {
testing.ContextLogf(ctx, "Failed to get flashinfo output, got: %v with err %v", out, err)
return errors.Wrap(err, "failed to get output from flashinfo")
} else if err != nil {
testing.ContextLog(ctx, "flashinfo failed to be fully parsed, trying anyway")
}
flashinfoOutput := strings.Join(out[0], "\n")
testing.ContextLog(ctx, "flashinfo: ", flashinfoOutput)
oldPattern := regexp.MustCompile(`Flags:.*\n`)
newPattern := regexp.MustCompile(`wp_gpio_asserted:\s+(ON|OFF)\s*`)
if newMatch := newPattern.FindStringSubmatch(flashinfoOutput); newMatch != nil {
testing.ContextLog(ctx, "New format match: ", newMatch)
if wp && newMatch[1] != "ON" {
return errors.New("flashinfo reported wp_gpio_asserted OFF when expecting ON, check wp gpio config")
}
if !wp && newMatch[1] != "OFF" {
return errors.New("flashinfo reported wp_gpio_asserted ON when expecting OFF, check wp gpio config")
}
} else if oldMatch := oldPattern.FindStringSubmatch(flashinfoOutput); oldMatch != nil {
testing.ContextLog(ctx, "Old format match: ", oldMatch)
if wp && !strings.Contains(oldMatch[0], "wp_gpio_asserted") {
return errors.New("flashinfo reported wp_gpio_asserted OFF when expecting ON, check wp gpio config")
}
if !wp && strings.Contains(oldMatch[0], "wp_gpio_asserted") {
return errors.New("flashinfo reported wp_gpio_asserted ON when expecting OFF, check wp gpio config")
}
} else {
return errors.Errorf("failed to parse flashinfo output, got: %v", flashinfoOutput)
}
return nil
}, &testing.PollOptions{Timeout: 5 * time.Second, Interval: 1 * time.Second}); err != nil {
return errors.Wrap(err, "looking for flashinfo")
}
return nil
}