blob: 416d3fc8dc5211bcd2f994545086907126508d52 [file] [log] [blame]
// Copyright 2023 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"
"os"
"path/filepath"
"strconv"
"time"
"github.com/golang/protobuf/ptypes/empty"
common "go.chromium.org/tast-tests/cros/common/firmware"
"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-tests/cros/remote/firmware/reporters"
pb "go.chromium.org/tast-tests/cros/services/cros/firmware"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/ssh"
"go.chromium.org/tast/core/ssh/linuxssh"
"go.chromium.org/tast/core/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: RollbackKernel,
Desc: "Decrement both kernel copies version and verify its goes to recovery",
Contacts: []string{
"chromeos-faft@google.com",
"tij@google.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
Attr: []string{"group:firmware", "firmware_bios", "firmware_level2"},
Requirements: []string{"sys-fw-0021-v01", "sys-fw-0024-v01", "sys-fw-0025-v01"},
ServiceDeps: []string{"tast.cros.firmware.KernelService"},
LacrosStatus: testing.LacrosVariantUnneeded,
Params: []testing.Param{
{
Name: "normal",
Fixture: fixture.NormalMode,
Val: common.BootModeNormal,
Timeout: 30 * time.Minute,
},
{
Name: "dev",
Fixture: fixture.DevModeGBB,
Val: common.BootModeDev,
Timeout: 30 * time.Minute,
},
},
})
}
func RollbackKernel(ctx context.Context, s *testing.State) {
h := s.FixtValue().(*fixture.Value).Helper
bootMode := s.Param().(common.BootMode)
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Requiring servo: ", err)
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to require config: ", err)
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Creating mode switcher: ", err)
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Fatal("Requiring KernelServiceClient: ", err)
}
kernAHostBackup, err := os.CreateTemp("", "KernABackup")
if err != nil {
s.Fatal("Failed to create temporary dir for kernel A backup")
}
kernBHostBackup, err := os.CreateTemp("", "KernBBackup")
if err != nil {
s.Fatal("Failed to create temporary dir for kernel B backup")
}
cleanupContext := ctx
ctx, cancel := ctxutil.Shorten(ctx, 5*time.Minute)
defer cancel()
defer func(ctx context.Context) {
kernAHostBackup.Close()
kernBHostBackup.Close()
if err := os.Remove(kernAHostBackup.Name()); err != nil {
s.Log("Failed to delete KERN-A back up dir from host")
}
if err := os.Remove(kernBHostBackup.Name()); err != nil {
s.Log("Failed to delete KERN-B back up dir from host")
}
}(cleanupContext)
needsRestoreFromDevMode := false
rolledBackKernB := false
s.Log("Backing up current DUT kernel copies")
kernelBackup, err := h.KernelServiceClient.BackupKernel(ctx, &pb.KernelBackup{})
if err != nil {
s.Fatal("Failed to back up KERN-A and KERN-B: ", err)
}
if bootMode == common.BootModeNormal {
if !h.DoesServerHaveTastHostFiles() {
if err := h.CopyTastFilesFromDUT(ctx); err != nil {
s.Fatal("Copying Tast files to Host failed: ", err)
}
}
s.Log("Copying kernel back up to host")
if err := linuxssh.GetFile(ctx, h.DUT.Conn(), kernelBackup.KernA.BackupPath, kernAHostBackup.Name(), linuxssh.PreserveSymlinks); err != nil {
s.Fatal("Failed to copy a KERN-A backup to the host")
}
if err := linuxssh.GetFile(ctx, h.DUT.Conn(), kernelBackup.KernB.BackupPath, kernBHostBackup.Name(), linuxssh.PreserveSymlinks); err != nil {
s.Fatal("Failed to copy a KERN-B backup to the host")
}
s.Logf("Backed up KERN-A to %q and KERN-B to %q on host", kernAHostBackup.Name(), kernBHostBackup.Name())
}
defer func(ctx context.Context) {
if needsRestoreFromDevMode {
if err := bootToDevAndRestore(ctx, h, ms, rolledBackKernB); err != nil {
s.Fatal("Failed to restore dut from dev mode: ", err)
}
needsRestoreFromDevMode = false
rolledBackKernB = false
}
if bootMode == common.BootModeNormal {
if err := h.EnsureDUTBooted(ctx); err != nil {
s.Fatal("Failed to ensure DUT booted: ", err)
}
s.Log("Sync KERN-A/B backups from host to DUT")
if _, err := linuxssh.PutFiles(ctx, h.DUT.Conn(), map[string]string{
kernAHostBackup.Name(): kernelBackup.KernA.BackupPath,
kernBHostBackup.Name(): kernelBackup.KernB.BackupPath,
}, linuxssh.DereferenceSymlinks); err != nil {
s.Fatal("Failed to get backup files to DUT from host")
}
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Error("Failed to connect to kernel service: ", err)
}
s.Log("Restoring kernel from backup")
if _, err := h.KernelServiceClient.RestoreKernel(ctx, kernelBackup); err != nil {
s.Error("Failed to restore kernel from backup: ", err)
}
s.Log("Delete backup files from DUT")
rmargs := []string{
kernelBackup.KernA.BackupPath,
kernelBackup.KernB.BackupPath,
}
if _, err := h.DUT.Conn().CommandContext(ctx, "rm", rmargs...).Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to delete backup files: ", err)
}
// Save the firmware log file for upload to Testhaus at the end of the test.
if s.HasError() {
saveLogPath := filepath.Join(s.OutDir(), "firmware.log")
if err := h.SaveCBMEMLogs(ctx, saveLogPath); err != nil {
s.Fatal("Failed to save firmware log: ", err)
}
}
}(cleanupContext)
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Fatal("Failed to connect to kernel service: ", err)
}
// Make sure we start with a deterministic state so we don't have a situation where
// KERN-B is not bootable, additionally boot to both copies to make sure they are bootable.
if err := bootToBothCopies(ctx, h, ms); err != nil {
s.Fatal("Failed to make both copies bootable: ", err)
}
if err := h.Reporter.ClearEventlog(ctx); err != nil {
s.Fatal("Failed to clear event log: ", err)
}
prevKernAVer, err := changeKernelVersion(ctx, h, pb.PartitionCopy_A, -1)
if err != nil {
s.Fatal("Failed to reduce KERN-A version by 1: ", err)
}
needsRestoreFromDevMode = true
s.Log("Performing mode aware reboot")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset, firmware.SkipWaitConnect); err != nil {
s.Fatal("Failed to reboot: ", err)
}
if bootMode == common.BootModeDev {
connectCtx, cancel := context.WithTimeout(ctx, h.Config.DelayRebootToPing)
defer cancel()
if err := h.WaitConnect(connectCtx, firmware.ResetEthernetDongle); err != nil {
s.Fatal("Failed to connect to DUT: ", err)
}
needsRestoreFromDevMode = false
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Fatal("Failed to connect to kernel service: ", err)
}
s.Log("Verify DUT in ROOT-A")
if _, err := h.KernelServiceClient.VerifyKernelCopy(ctx, &pb.Partition{
Copy: pb.PartitionCopy_A,
}); err != nil {
s.Fatal("Failed to verify DUT currently is in copy A: ", err)
}
s.Log("Get current kernel version for KERN-A and reset it")
currVersion, err := changeKernelVersion(ctx, h, pb.PartitionCopy_A, +1)
if err != nil {
s.Fatal("Failed to reset KERN-A version: ", err)
}
if currVersion != prevKernAVer-1 {
s.Fatalf("Expected kernel version to be %d but was %d", prevKernAVer-1, currVersion)
}
// Since no rollback is expected to occur in dev mode, the test ends here.
return
}
connectCtx, cancel := context.WithTimeout(ctx, h.Config.DelayRebootToPing)
defer cancel()
if err := h.WaitConnect(connectCtx, firmware.ResetEthernetDongle); err != nil {
s.Fatal("Failed to connect to DUT and failed to boot to KERN-B: ", err)
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Fatal("Failed to connect to kernel service: ", err)
}
s.Log("Verify DUT in ROOT-B")
if _, err := h.KernelServiceClient.VerifyKernelCopy(ctx, &pb.Partition{
Copy: pb.PartitionCopy_B,
}); err != nil {
s.Fatal("Failed to verify DUT currently is in copy B: ", err)
}
_, err = changeKernelVersion(ctx, h, pb.PartitionCopy_B, -1)
if err != nil {
s.Fatal("Failed to reduce KERN-B version by 1: ", err)
}
rolledBackKernB = true
s.Log("Rebooting the DUT")
if err := h.DUT.Conn().CommandContext(ctx, "reboot").Run(); err != nil && !errors.As(err, &context.DeadlineExceeded) {
s.Fatal("Failed to run reboot command: ", err)
}
waitDisconnectCtx, cancelWaitDisconnect := context.WithTimeout(ctx, 1*time.Minute)
defer cancelWaitDisconnect()
if err := h.DUT.WaitUnreachable(waitDisconnectCtx); err != nil {
s.Fatal("Failed to wait for DUT to become unreachable, warm reset failed: ", err)
}
s.Log("Waiting for DUT to reach the firmware screen")
if err := h.WaitFirmwareScreen(ctx, h.Config.FirmwareScreenRecMode); err != nil {
s.Fatal("Failed to get to firmware screen: ", err)
}
s.Log("Checking if DUT stays at the Broken Screen")
brokenToDevWaitConnectCtx, cancelWaitConnectBrokenToDev := context.WithTimeout(ctx, h.Config.DelayRebootToPing)
defer cancelWaitConnectBrokenToDev()
err = h.WaitConnect(brokenToDevWaitConnectCtx, firmware.ResetEthernetDongle)
switch err.(type) {
case nil:
s.Fatal("DUT woke up unexpectedly")
default:
if !errors.As(err, &context.DeadlineExceeded) {
s.Fatal("Unexpected error occurred: ", err)
}
}
if err := bootToDevAndRestore(ctx, h, ms, rolledBackKernB); err != nil {
s.Fatal("Failed to restore kernel versions from dev mode: ", err)
}
needsRestoreFromDevMode = false
rolledBackKernB = false
if err := h.RequireKernelServiceClient(ctx); err != nil {
s.Fatal("Failed to connect to kernel service: ", err)
}
s.Log("Verify DUT in ROOT-A")
if _, err := h.KernelServiceClient.VerifyKernelCopy(ctx, &pb.Partition{
Copy: pb.PartitionCopy_A,
}); err != nil {
s.Fatal("Failed to verify DUT currently is in copy A: ", err)
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
events, err := h.Reporter.EventlogList(ctx)
if err != nil {
return errors.Wrap(err, "failed to get event log")
}
foundExpRecReason := false
for _, recRes := range []reporters.RecoveryReason{
reporters.RecoveryReasonDeprecatedRWNoDisk,
reporters.RecoveryReasonRWNoKernel,
reporters.RecoveryReasonRWInvalidOS,
} {
if h.Reporter.CheckRecoveryEventExists(ctx, events, recRes) {
foundExpRecReason = true
break
}
}
if !foundExpRecReason {
return errors.Errorf("Did not find expected recovery reasons in event log. Events: %v", events)
}
return nil
}, &testing.PollOptions{
Timeout: 1 * time.Minute,
}); err != nil {
saveEventLogPath := filepath.Join(s.OutDir(), "eventlog.txt")
if err := h.SaveEventLog(ctx, saveEventLogPath); err != nil {
s.Error("Failed to save event log: ", err)
}
s.Fatal("Looking for recovery reason: ", err)
}
}
func bootToDevAndRestore(ctx context.Context, h *firmware.Helper, ms *firmware.ModeSwitcher, restoreB bool) error {
if err := ms.EnableRecMode(ctx, servo.PowerStateRec, servo.USBMuxOff); err != nil {
return err
}
if err := ms.RecScreenToDevMode(ctx); err != nil {
return errors.Wrap(err, "moving from firmware screen to dev mode")
}
bootedFromRemovableDevice, err := h.Reporter.BootedFromRemovableDevice(ctx)
if err != nil {
testing.ContextLog(ctx, "Could not determine boot device type: ", err)
}
if bootedFromRemovableDevice {
testing.ContextLog(ctx, "DUT unexpectedly booted from the usb device")
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
return errors.Wrap(err, "failed to connect to kernel service")
}
testing.ContextLog(ctx, "Reset KERN-A version")
_, err = changeKernelVersion(ctx, h, pb.PartitionCopy_A, +1)
if err != nil {
return errors.Wrap(err, "failed to increase KERN-A version by 1")
}
if restoreB {
testing.ContextLog(ctx, "Reset KERN-B version")
_, err = changeKernelVersion(ctx, h, pb.PartitionCopy_B, +1)
if err != nil {
testing.ContextLog(ctx, "Failed to increase KERN-B version by 1: ", err)
}
}
testing.ContextLog(ctx, "Performing mode aware reboot to ensure boot to copy A")
if err := ms.RebootToMode(ctx, common.BootModeNormal); err != nil {
return errors.Wrap(err, "failed to reboot")
}
h.DisconnectDUT(ctx)
connectCtx, cancel := context.WithTimeout(ctx, h.Config.DelayRebootToPing)
defer cancel()
if err := h.WaitConnect(connectCtx, firmware.ResetEthernetDongle); err != nil {
return errors.Wrap(err, "failed to connect to DUT")
}
if err := h.SyncTastFilesToDUT(ctx); err != nil {
return errors.Wrap(err, "copying Tast files to DUT failed")
}
return nil
}
func bootToBothCopies(ctx context.Context, h *firmware.Helper, ms *firmware.ModeSwitcher) error {
if _, err := h.KernelServiceClient.EnsureBothKernelCopiesBootable(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, "failed to ensure both kernel copies are bootable")
}
testing.ContextLog(ctx, "Sleeping for 10s")
// GoBigSleepLint: There is a risk that the priority value may revert to its
// original setting if the priority is set immediately after
// EnsureBothKernelCopiesBootable(). Add a 10-second delay before setting the priority.
if err := testing.Sleep(ctx, 10*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 10 seconds")
}
if _, err := h.KernelServiceClient.PrioritizeKernelCopy(ctx, &pb.Partition{
Name: pb.PartitionName_KERNEL,
Copy: pb.PartitionCopy_B,
}); err != nil {
return errors.Wrap(err, "failed to prioritize KERN-B")
}
testing.ContextLog(ctx, "Performing mode aware reboot to ensure boot to copy B")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset); err != nil {
return errors.Wrap(err, "failed to reboot")
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
return errors.Wrap(err, "failed to connect to kernel service")
}
testing.ContextLog(ctx, "Verify DUT in ROOT-B")
if _, err := h.KernelServiceClient.VerifyKernelCopy(ctx, &pb.Partition{
Copy: pb.PartitionCopy_B,
}); err != nil {
return errors.Wrap(err, "failed to verify DUT currently is in copy B")
}
if _, err := h.KernelServiceClient.PrioritizeKernelCopy(ctx, &pb.Partition{
Name: pb.PartitionName_KERNEL,
Copy: pb.PartitionCopy_A,
}); err != nil {
return errors.Wrap(err, "failed to prioritize KERN-A")
}
testing.ContextLog(ctx, "Performing mode aware reboot to ensure boot to copy A")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset); err != nil {
return errors.Wrap(err, "failed to reboot")
}
if err := h.RequireKernelServiceClient(ctx); err != nil {
return errors.Wrap(err, "failed to connect to kernel service")
}
testing.ContextLog(ctx, "Verify DUT in ROOT-A")
if _, err := h.KernelServiceClient.VerifyKernelCopy(ctx, &pb.Partition{
Copy: pb.PartitionCopy_A,
}); err != nil {
return errors.Wrap(err, "failed to verify DUT currently is in copy A")
}
return nil
}
func changeKernelVersion(ctx context.Context, h *firmware.Helper, copy pb.PartitionCopy, change int) (int, error) {
testing.ContextLog(ctx, "Get current kernel version")
kernVersion, err := h.KernelServiceClient.GetKernelVersion(ctx, &pb.Partition{
Name: pb.PartitionName_KERNEL,
Copy: copy,
})
if err != nil {
return -1, errors.Wrap(err, "failed to get kernel version")
}
testing.ContextLogf(ctx, "Current KERN-%s version is: %v", copy, kernVersion.Version)
versionInt, err := strconv.Atoi(kernVersion.Version)
if err != nil {
return -1, errors.Wrap(err, "failed to parse kernel version as int")
}
newKernVersion := pb.KernelVersion{
Version: strconv.Itoa(versionInt + change),
Copy: copy,
}
testing.ContextLogf(ctx, "Setting KERN-%s version to %s", copy, newKernVersion.Version)
if _, err := h.KernelServiceClient.SetKernelVersion(ctx, &newKernVersion); err != nil {
return versionInt, errors.Wrapf(err, "failed to set KERN-%s version to %s", copy, newKernVersion.Version)
}
return versionInt, nil
}