blob: 72186e06330961b671d949849732af76a4d041f3 [file] [log] [blame]
// Copyright 2022 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"
"fmt"
"path/filepath"
"regexp"
"strconv"
"time"
common "chromiumos/tast/common/firmware"
"chromiumos/tast/common/servo"
"chromiumos/tast/dut"
"chromiumos/tast/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
pb "chromiumos/tast/services/cros/firmware"
"chromiumos/tast/ssh"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: ECUpdateID,
Desc: "Verify corrupting RW firmware in EFS system results in switching between RW and RW_B and is corrected by AP",
Contacts: []string{"tij@google.com", "cros-fw-engprod@google.com"},
Attr: []string{"group:firmware", "firmware_unstable"},
SoftwareDeps: []string{"crossystem", "flashrom"},
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.Platform("fizz", "kalista")),
ServiceDeps: []string{"tast.cros.firmware.BiosService", "tast.cros.firmware.UtilsService"},
Timeout: 15 * time.Minute,
Params: []testing.Param{
{
Name: "normal_mode",
Fixture: fixture.NormalMode,
},
{
Name: "dev_gbb",
Fixture: fixture.DevModeGBB,
},
},
})
}
var tmpUpdateIDDir = filepath.Join("/", "mnt", "stateful_partition", fmt.Sprintf("flashrom_%d", time.Now().Unix()))
func ECUpdateID(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 servo: ", err)
}
s.Log("Get initial write protect state")
initialState, err := h.Servo.GetString(ctx, servo.FWWPState)
if err != nil {
s.Fatal("Failed to get initial write protect state: ", err)
}
defer func() {
s.Log("Reset write protect to initial state: ", initialState)
setInitWP := false
if servo.FWWPStateValue(initialState) == servo.FWWPStateOn {
setInitWP = true
}
if err := setFWWriteProtect(ctx, h, setInitWP); err != nil {
s.Fatal("Failed to set FW write protect state: ", err)
}
}()
// Back up AP fw, EC_RW, and EC_RW_B.
if err := h.RequireBiosServiceClient(ctx); err != nil {
s.Fatal("Requiring BiosServiceClient: ", err)
}
s.Log("Back up EC_RW firmware")
ecrwPath, err := h.BiosServiceClient.BackupImageSection(ctx, &pb.FWBackUpSection{Section: pb.ImageSection_ECRWImageSection, Programmer: pb.Programmer_ECProgrammer})
if err != nil {
s.Fatal("Failed to backup current EC_RW region: ", err)
}
s.Log("EC_RW backup is stored at: ", ecrwPath.Path)
s.Log("Back up EC_RW_B firmware")
ecrwbPath, err := h.BiosServiceClient.BackupImageSection(ctx, &pb.FWBackUpSection{Section: pb.ImageSection_ECRWBImageSection, Programmer: pb.Programmer_ECProgrammer})
if err != nil {
s.Fatal("Failed to backup current EC_RW_B region: ", err)
}
s.Log("EC_RW_B backup is stored at: ", ecrwbPath.Path)
// Restore EC_RW and EC_RW_B.
defer func() {
// Disable wp so backup can be restored.
if err := setFWWriteProtect(ctx, h, false); err != nil {
s.Fatal("Failed to set FW write protect state: ", err)
}
// Require again here since reboots in test cause nil pointer errors otherwise.
if err := h.RequireBiosServiceClient(ctx); err != nil {
s.Fatal("Requiring BiosServiceClient: ", err)
}
s.Log("Restore EC_RW firmware with backup from: ", ecrwPath.Path)
if _, err := h.BiosServiceClient.RestoreImageSection(ctx, ecrwPath); err != nil {
s.Fatal("Failed to restore EC_RW firmware: ", err)
}
s.Log("Restore EC_RW_B firmware with backup from: ", ecrwbPath.Path)
if _, err := h.BiosServiceClient.RestoreImageSection(ctx, ecrwbPath); err != nil {
s.Fatal("Failed to restore EC_RW_B firmware: ", err)
}
s.Log("Delete EC_RW fw backup")
if _, err := h.DUT.Conn().CommandContext(ctx, "rm", ecrwPath.Path).Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to delete EC_RW backup: ", err)
}
s.Log("Delete EC_RW_B fw backup")
if _, err := h.DUT.Conn().CommandContext(ctx, "rm", ecrwbPath.Path).Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to delete EC_RW_B backup: ", err)
}
}()
workPath := filepath.Join(tmpUpdateIDDir, "work")
s.Log("Create temp dir in DUT")
if _, err = h.DUT.Conn().CommandContext(ctx, "mkdir", "-p", workPath).Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to create temp dirs: ", err)
}
// Clean up temp directory.
defer func() {
s.Log("Delete temp dir and contained files from DUT")
if _, err := h.DUT.Conn().CommandContext(ctx, "rm", "-r", tmpUpdateIDDir).Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to delete temp dir: ", err)
}
}()
flags := pb.GBBFlagsState{Clear: []pb.GBBFlag{pb.GBBFlag_DISABLE_EC_SOFTWARE_SYNC}, Set: []pb.GBBFlag{pb.GBBFlag_DEV_SCREEN_SHORT_DELAY}}
if err := common.ClearAndSetGBBFlags(ctx, s.DUT(), &flags); err != nil {
s.Fatal("Error setting gbb flags: ", err)
}
initialHash, err := firmware.NewECTool(s.DUT(), firmware.ECToolNameMain).Hash(ctx)
if err != nil {
s.Fatal("Failed to get initial ec hash: ", err)
}
s.Log("Verify active copy is correctly changed after corrupting")
activeCopy, err := testCorruptActiveSectionAndReboot(ctx, h, s.DUT(), initialHash)
if err != nil {
s.Fatal("Failed to test changing active copy after corruption: ", err)
}
s.Log("Current active copy: ", string(activeCopy))
s.Log("Verify active copy is correctly changed back after corrupting secondary copy")
activeCopy, err = testCorruptActiveSectionAndReboot(ctx, h, s.DUT(), initialHash)
if err != nil {
s.Fatal("Failed to test changing active copy after corruption: ", err)
}
s.Log("Current active copy: ", string(activeCopy))
}
func testCorruptActiveSectionAndReboot(ctx context.Context, h *firmware.Helper, d *dut.DUT, initHash string) (string, error) {
var activeCopyToRegionMap = map[string]string{
"RW": "EC_RW",
"RW_B": "EC_RW_B",
}
initActiveCopy, err := h.Servo.GetString(ctx, servo.ECActiveCopy)
if err != nil {
return "", errors.Wrap(err, "failed to get ec active copy")
}
testing.ContextLog(ctx, "Initial active copy: ", initActiveCopy)
testing.ContextLog(ctx, "Disable write protect to allow for r/w for test")
if err := setFWWriteProtect(ctx, h, false); err != nil {
return "", errors.Wrap(err, "failed to disable FW write protect state")
}
testing.ContextLog(ctx, "Corrupt current active section: ", activeCopyToRegionMap[initActiveCopy])
if err := corruptSection(ctx, h, activeCopyToRegionMap[initActiveCopy]); err != nil {
return "", errors.Wrapf(err, "failed to corrupt current active copy: %s", initActiveCopy)
}
testing.ContextLog(ctx, "Reenable write protect")
if err := setFWWriteProtect(ctx, h, true); err != nil {
return "", errors.Wrap(err, "failed to enable FW write protect state")
}
testing.ContextLog(ctx, "perform dut reboot")
if err := coldModeAwareReboot(ctx, h); err != nil {
return "", errors.Wrap(err, "failed to perform mode aware reboot")
}
testing.ContextLog(ctx, "Verify echash has not changed")
currHash, err := firmware.NewECTool(d, firmware.ECToolNameMain).Hash(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to get current ec hash")
} else if currHash != initHash {
return "", errors.Errorf("expected hash to remain %q but is now %q", initHash, currHash)
}
testing.ContextLog(ctx, "Verify active copy is correctly changed")
activeCopy, err := h.Servo.GetString(ctx, servo.ECActiveCopy)
if err != nil {
return "", errors.Wrap(err, "failed to get ec active copy")
} else if activeCopy == initActiveCopy {
return "", errors.Wrapf(err, "Corrupting should result in different active copy, got %s", initActiveCopy)
}
return activeCopy, nil
}
func corruptSection(ctx context.Context, h *firmware.Helper, section string) error {
// Temp file to hold current and later corrupted image section.
sectionPath := filepath.Join(tmpUpdateIDDir, fmt.Sprintf("img_%d", time.Now().Unix()))
testing.ContextLog(ctx, "Read WP_RO section to file ", sectionPath)
if out, err := h.DUT.Conn().CommandContext(ctx, "flashrom", "-p", "ec", "-r", "-i", fmt.Sprintf("WP_RO:%s", sectionPath)).Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to run flashrom cmd")
} else if match := regexp.MustCompile(`SUCCESS`).FindSubmatch(out); match == nil {
return errors.Errorf("flashrom did not produce sucess message: %s", string(out))
}
testing.ContextLog(ctx, "Checking fmap")
out, err := h.DUT.Conn().CommandContext(ctx, "dump_fmap", "-p", sectionPath).Output(ssh.DumpLogOnError)
if err != nil {
return errors.Wrap(err, "failed to dump fmap")
}
// Format for the dumped fmap is "SectionName offset size".
sectionMatch := regexp.MustCompile(fmt.Sprintf(`%s\s+(\d+)\s+(\d+)`, section)).FindSubmatch(out)
if sectionMatch == nil {
return errors.Errorf("didn't find %q section in fmap: %v", section, string(out))
}
sectionSize, err := strconv.Atoi(string(sectionMatch[2]))
testing.ContextLogf(ctx, "Section %q size: %d", section, sectionSize)
if out, err = h.DUT.Conn().CommandContext(ctx, "rm", sectionPath).Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to delete temp file")
}
ddArgs := []string{
"if=/dev/urandom", fmt.Sprintf("of=%s", sectionPath),
"bs=1", fmt.Sprintf("count=%d", sectionSize),
}
testing.ContextLogf(ctx, "Generate random file of size: %d to path %v", sectionSize, sectionPath)
if out, err = h.DUT.Conn().CommandContext(ctx, "dd", ddArgs...).Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to create random file with dd cmd")
}
testing.ContextLog(ctx, "Write random file to section")
if out, err := h.DUT.Conn().CommandContext(ctx, "flashrom", "-p", "ec", "-w", "-i", fmt.Sprintf("%s:%s", section, sectionPath)).Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to run flashrom cmd")
} else if match := regexp.MustCompile(`SUCCESS`).FindSubmatch(out); match == nil {
return errors.Errorf("flashrom did not produce sucess message: %s", string(out))
}
testing.ContextLog(ctx, "Delete temp file at path ", sectionPath)
if out, err = h.DUT.Conn().CommandContext(ctx, "rm", sectionPath).Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to delete temp file")
}
return nil
}
func setFWWriteProtect(ctx context.Context, h *firmware.Helper, enable bool) error {
enableStr := "enable"
fwwpState := servo.FWWPStateOn
if !enable {
enableStr = "disable"
fwwpState = servo.FWWPStateOff
}
// Enable software wp before hardware wp if enabling.
if enable {
if err := h.Servo.RunECCommand(ctx, "flashwp enable"); err != nil {
return errors.Wrap(err, "failed to enable flashwp")
}
}
if err := h.Servo.SetFWWPState(ctx, fwwpState); err != nil {
return errors.Wrapf(err, "failed to %s firmware write protect", enableStr)
}
// Disable software wp after hardware wp so its allowed.
if !enable {
if err := h.Servo.RunECCommand(ctx, "flashwp disable"); err != nil {
return errors.Wrap(err, "failed to disable flashwp")
}
}
return nil
// return coldModeAwareReboot(ctx, h)
}
func coldModeAwareReboot(ctx context.Context, h *firmware.Helper) error {
// Create new mode switcher every time to prevent nil pointer errors.
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
return errors.Wrap(err, "failed to create mode switcher")
}
return ms.ModeAwareReboot(ctx, firmware.ColdReset)
}