blob: 27a039fe4cad9a966abd1ce32b9675fbba6f2dc7 [file] [log] [blame]
// Copyright 2019 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.
package firmware
import (
"context"
"strconv"
"time"
fwCommon "chromiumos/tast/common/firmware"
"chromiumos/tast/common/servo"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
"chromiumos/tast/remote/firmware/reporters"
"chromiumos/tast/testing"
)
// bootModeTestParams defines the params for a single test-case.
// bootToMode defines which boot-mode to switch the DUT into.
// allowGBBForce defines whether to force dev mode via GBB flags.
// resetAfterBoot defines whether to perform a ModeAwareReboot after switching to bootToMode.
// resetType defines whether ModeAwareReboot should use a warm or a cold reset.
// checkBootFromMain checks whether device boots from the main storage when a memory device is attached.
type bootModeTestParams struct {
bootToMode fwCommon.BootMode
allowGBBForce bool
resetAfterBoot bool
resetType firmware.ResetType
checkBootFromMain bool
}
func init() {
testing.AddTest(&testing.Test{
Func: BootMode,
Desc: "Verifies that remote tests can boot the DUT into, and confirm that the DUT is in, the different firmware modes (normal, dev, and recovery)",
Contacts: []string{"cros-fw-engprod@google.com"},
Attr: []string{"group:firmware"},
SoftwareDeps: []string{"crossystem", "flashrom"},
Vars: []string{"firmware.skipFlashUSB"},
Params: []testing.Param{{
Name: "normal_warm",
Fixture: fixture.NormalMode,
Val: bootModeTestParams{
resetAfterBoot: true,
resetType: firmware.WarmReset,
},
ExtraAttr: []string{"firmware_smoke"},
Timeout: 15 * time.Minute,
}, {
Name: "normal_cold",
Fixture: fixture.NormalMode,
Val: bootModeTestParams{
resetAfterBoot: true,
resetType: firmware.ColdReset,
},
ExtraAttr: []string{"firmware_smoke"},
Timeout: 15 * time.Minute,
}, {
Name: "rec_warm",
Fixture: fixture.NormalMode,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeRecovery,
resetAfterBoot: true,
resetType: firmware.WarmReset,
},
ExtraAttr: []string{"firmware_smoke", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "rec_cold",
Fixture: fixture.NormalMode,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeRecovery,
resetAfterBoot: true,
resetType: firmware.ColdReset,
},
ExtraAttr: []string{"firmware_smoke", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "dev_usb_cold",
Fixture: fixture.USBDevModeNoServices,
Val: bootModeTestParams{
resetAfterBoot: true,
resetType: firmware.ColdReset,
},
ExtraAttr: []string{"firmware_usb", "firmware_unstable"},
Timeout: 60 * time.Minute,
}, {
Name: "dev_warm",
Fixture: fixture.DevMode,
Val: bootModeTestParams{
resetAfterBoot: true,
resetType: firmware.WarmReset,
checkBootFromMain: true,
},
ExtraAttr: []string{"firmware_smoke", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "dev_cold",
Fixture: fixture.DevMode,
Val: bootModeTestParams{
resetAfterBoot: true,
resetType: firmware.ColdReset,
checkBootFromMain: true,
},
ExtraAttr: []string{"firmware_smoke", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "dev_to_rec",
Fixture: fixture.DevMode,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeRecovery,
},
ExtraAttr: []string{"firmware_smoke", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "rec_to_dev",
Fixture: fixture.RecModeNoServices,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeDev,
},
ExtraAttr: []string{"firmware_unstable", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "dev_gbb_to_rec",
Fixture: fixture.DevModeGBB,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeRecovery,
},
ExtraAttr: []string{"firmware_unstable", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
Name: "rec_to_dev_gbb",
Fixture: fixture.RecModeNoServices,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeDev,
allowGBBForce: true,
},
ExtraAttr: []string{"firmware_unstable", "firmware_usb"},
Timeout: 60 * time.Minute,
}, {
// Verifies that we can go from normal -> dev -> normal without GBB flags.
Name: "normal_dev",
Fixture: fixture.NormalMode,
Val: bootModeTestParams{
bootToMode: fwCommon.BootModeDev,
allowGBBForce: false,
resetAfterBoot: false,
},
ExtraAttr: []string{"firmware_bios", "firmware_level2"},
Timeout: 15 * time.Minute,
}},
})
}
func BootMode(ctx context.Context, s *testing.State) {
tc := s.Param().(bootModeTestParams)
pv := s.FixtValue().(*fixture.Value)
h := pv.Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to init servo: ", err)
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Creating mode switcher: ", err)
}
// Report ModeSwitcherType, for debugging.
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Requiring config")
}
s.Log("Mode switcher type: ", h.Config.ModeSwitcherType)
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Error opening servo: ", err)
}
if tc.bootToMode == fwCommon.BootModeRecovery || tc.checkBootFromMain {
skipFlashUSB := false
if skipFlashUSBStr, ok := s.Var("firmware.skipFlashUSB"); ok {
skipFlashUSB, err = strconv.ParseBool(skipFlashUSBStr)
if err != nil {
s.Fatalf("Invalid value for var firmware.skipFlashUSB: got %q, want true/false", skipFlashUSBStr)
}
}
cs := s.CloudStorage()
if skipFlashUSB {
cs = nil
}
if err := h.SetupUSBKey(ctx, cs); err != nil {
s.Fatal("USBKey not working: ", err)
}
}
// Double-check that DUT starts in the right mode.
if curr, err := h.Reporter.CurrentBootMode(ctx); err != nil {
s.Fatal("Checking boot mode at beginning of test: ", err)
} else if curr != pv.BootMode {
s.Logf("DUT started in boot mode %s. Setting up %s", curr, pv.BootMode)
if err = ms.RebootToMode(ctx, pv.BootMode); err != nil {
s.Fatalf("Failed to set up %s mode: %s", pv.BootMode, err)
}
}
if tc.bootToMode != "" {
// Switch to tc.bootToMode.
// RebootToMode ensures that the DUT winds up in the expected boot mode afterward.
var opts []firmware.ModeSwitchOption
if tc.allowGBBForce {
opts = append(opts, firmware.AllowGBBForce)
} else if !pv.ForcesDevMode {
// Don't check the dev-force GBB flag if there's no reason for it to have been set.
opts = append(opts, firmware.AssumeGBBFlagsCorrect)
}
s.Logf("Transitioning to %s mode with options %+v", tc.bootToMode, opts)
if err = ms.RebootToMode(ctx, tc.bootToMode, opts...); err != nil {
s.Fatalf("Error during transition from %s to %s: %+v", pv.BootMode, tc.bootToMode, err)
}
s.Log("Transition completed successfully")
}
// Reset the DUT, if the test case calls for it.
// ModeAwareReboot ensures the DUT winds up in the expected boot mode afterward.
if tc.resetAfterBoot {
s.Logf("Resetting DUT (resetType=%v)", tc.resetType)
if err := ms.ModeAwareReboot(ctx, tc.resetType); err != nil {
s.Fatal("Error resetting DUT: ", err)
}
// See the doc for ModeAwareReboot, the boot mode should be unchanged except that Recovery goes to normal.
if curr, err := h.Reporter.CurrentBootMode(ctx); err != nil {
s.Fatal("Failed to determine DUT boot mode: ", err)
} else if curr != pv.BootMode && pv.BootMode != fwCommon.BootModeRecovery {
s.Fatalf("Wrong boot mode: got %q, want %q", curr, pv.BootMode)
} else if curr != fwCommon.BootModeNormal && pv.BootMode == fwCommon.BootModeRecovery {
s.Fatalf("Wrong boot mode: got %q, want %q", curr, fwCommon.BootModeNormal)
}
s.Log("Reset completed successfully")
} else {
// Verify the boot mode and then reboot to normal.
if curr, err := h.Reporter.CurrentBootMode(ctx); err != nil {
s.Fatal("Failed to determine DUT boot mode: ", err)
} else if curr != tc.bootToMode {
s.Fatalf("Wrong boot mode: got %q, want %q", curr, pv.BootMode)
} else if curr != fwCommon.BootModeNormal {
s.Logf("Transitioning back from %s to normal mode", curr)
if err = ms.RebootToMode(ctx, fwCommon.BootModeNormal); err != nil {
s.Fatalf("Error returning from %s to %s: %+v", curr, fwCommon.BootModeNormal, err)
}
s.Log("Transition completed successfully")
}
}
// Check that DUT can boot from the main storage despite USB device attached.
if tc.checkBootFromMain {
// Ensure that mainfw_act returns A, and if not set up crossystem param
// for the device to boot from firmware A next time.
mainfwAct, err := h.Reporter.CrossystemParam(ctx, reporters.CrossystemParamMainfwAct)
if err != nil {
s.Fatal("Failed to get crossystem mainfw_act: ", err)
}
if mainfwAct != "A" {
s.Log("Current mainfw_act not set to A. Attempting to set the device to boot from A during next reboot")
if err := h.DUT.Conn().CommandContext(ctx, "crossystem", "fw_try_next=A").Run(); err != nil {
s.Fatal("Failed to set 'crossystem fw_try_next=A': ", err)
}
}
s.Log("Enabling USB connection to DUT")
if err := h.Servo.SetUSBMuxState(ctx, servo.USBMuxDUT); err != nil {
s.Fatal("Failed to set 'usb3_mux_sel:dut_sees_usbkey': ", err)
}
s.Log("Power-cycling DUT with a warm reset")
if err := h.Servo.SetPowerState(ctx, servo.PowerStateWarmReset); err != nil {
s.Fatal("Failed to reboot DUT by servo: ", err)
}
s.Log("Waiting for DUT to power ON")
waitConnectCtx, cancelWaitConnect := context.WithTimeout(ctx, 2*time.Minute)
defer cancelWaitConnect()
if err := s.DUT().WaitConnect(waitConnectCtx); err != nil {
s.Fatal("Failed to reconnect to DUT: ", err)
}
s.Log("Checking that DUT has booted from main")
bootedDeviceType, err := h.Reporter.BootedDevice(ctx)
if err != nil {
s.Fatal("Could not determine boot device type: ", err)
}
if bootedDeviceType != reporters.BootedDeviceDeveloperInternalSig {
s.Fatalf("DUT did not boot from the internal device, and got bootedDeviceType:%v", bootedDeviceType)
}
s.Log("Checking the value of mainfw_act after reboot")
mainfwAct, err = h.Reporter.CrossystemParam(ctx, reporters.CrossystemParamMainfwAct)
if err != nil {
s.Fatal("Failed to get crossystem mainfw_act: ", err)
}
if mainfwAct != "A" {
s.Fatalf("Expected mainfw_act:A but got mainfw_act:%s", mainfwAct)
}
}
}