blob: db36b55ac1eea4c5a7db465af6f4f839e6ce1851 [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"
"regexp"
"strconv"
"time"
"chromiumos/tast/common/servo"
"chromiumos/tast/dut"
"chromiumos/tast/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
"chromiumos/tast/ssh"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: ECCbiEeprom,
Desc: "Test that I2C controls can be used to read/write EEPROM, and setting write protect prevents writing",
Contacts: []string{"tij@google.com", "cros-fw-engprod@google.com"},
Attr: []string{"group:firmware", "firmware_unstable"},
Fixture: fixture.NormalMode,
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.ECFeatureCBI()),
})
}
type eepromData struct {
wpDecoupled bool
testData1 string
testData2 string
writeData string
cbiTag string
writeSize int
}
func ECCbiEeprom(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)
}
pageSize := 8
writeSize := (pageSize + 1) / 2 // + 1 in case page size is made an odd num.
testCbiTag := "99"
testData1 := "0xb928de99"
testData2 := "0xeb79441e"
s.Log("Test data 1: ", testData1)
s.Log("Test data 2: ", testData2)
s.Log("Attempting to set 'i2c_mux_en' to 'off'")
if hasCtrl, err := h.Servo.HasControl(ctx, string(servo.I2CMuxEn)); err != nil {
s.Fatal("Failed to check if servo has control: ", servo.I2CMuxEn)
} else if hasCtrl {
if err := h.Servo.SetOnOff(ctx, servo.I2CMuxEn, servo.Off); err != nil {
s.Fatalf("Failed to set %q control to %s", servo.I2CMuxEn, servo.Off)
}
s.Logf("Control %q is present and was reset", servo.I2CMuxEn)
} else {
s.Logf("Control %q does not exist, won't reset", servo.I2CMuxEn)
}
data := &eepromData{
wpDecoupled: false, testData1: testData1, testData2: testData2,
writeData: "", cbiTag: testCbiTag, writeSize: writeSize,
}
s.Log("Use ectool gpioget to check if cbi wp is decoupled")
out, err := firmware.NewECTool(s.DUT(), firmware.ECToolNameMain).FindBaseGpio(ctx, []firmware.GpioName{firmware.ECCbiWp})
if err != nil || out == nil {
s.Log("CBI WP is not decoupled from EC WP")
} else if val, ok := out[firmware.ECCbiWp]; ok {
s.Log("CBI WP is decoupled from EC WP: ", val)
data.wpDecoupled = true
}
defer func() {
s.Log("Cleaning up wp status")
if err := h.Servo.SetFWWPState(ctx, servo.FWWPStateOff); err != nil {
s.Fatal("Failed to disable firmware write protect: ", err)
}
if err := h.Servo.RunECCommand(ctx, "flashwp false"); err != nil {
s.Fatal("Failed to disable flashwp: ", err)
}
// if data.wpDecoupled {
if _, err := h.DUT.Conn().CommandContext(ctx, "flashrom", "-p", "ec", "--wp-disable").Output(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to disable wp: ", err)
}
// }
s.Log("Reboot DUT")
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Failed to create mode switcher: ", err)
}
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset); err != nil {
s.Fatal("Failed to perform mode aware reboot: ", err)
}
}()
s.Log("Test EEPROM is not writable with WP enabled")
if err := checkEepromWithWP(ctx, h, s.DUT(), data); err != nil {
s.Fatal("EEPROM read/write test failed with wp enabled: ", err)
}
s.Log("Test EEPROM is writable with WP disabled")
if err := checkEepromWithoutWP(ctx, h, s.DUT(), data); err != nil {
s.Fatal("EEPROM read/write test failed with wp disabled: ", err)
}
}
func checkEepromWithWP(ctx context.Context, h *firmware.Helper, d *dut.DUT, data *eepromData) error {
// Remove tag if already exists before enabling wp to test creating new tag.
testing.ContextLogf(ctx, "Checking if tag %q already exists", data.cbiTag)
if _, err := readTagFromEeprom(ctx, d, data); err == nil {
testing.ContextLogf(ctx, "tag %q already exists in CBI, removing it", data.cbiTag)
if err := removeTagFromEeprom(ctx, d, data); err != nil {
return errors.Wrap(err, "failed to remove tag from cbi")
}
}
testing.ContextLog(ctx, "Enabling write protect")
if err := h.Servo.RunECCommand(ctx, "flashwp true"); err != nil {
return errors.Wrap(err, "failed to enable flashwp")
}
if err := h.Servo.SetFWWPState(ctx, servo.FWWPStateOn); err != nil {
return errors.Wrap(err, "failed to enable firmware write protect")
}
testing.ContextLog(ctx, "Waiting so write protect can enable")
if err := testing.Sleep(ctx, 1*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 1s")
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
return errors.Wrap(err, "failed to create mode switcher")
}
testing.ContextLog(ctx, "Rebooting DUT")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset); err != nil {
return errors.Wrap(err, "failed to perform mode aware reboot")
}
if data.wpDecoupled {
testing.ContextLog(ctx, "Enabling software write protect")
if _, err := h.DUT.Conn().CommandContext(ctx, "flashrom", "-p", "ec", "--wp-enable").Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to enable wp")
}
}
// Test writing data to new or existing tag.
data.writeData = data.testData1
if err := writeTagToEeprom(ctx, h, d, data); err == nil {
return errors.Wrap(err, "expected write to failed with wp enabled, but succeeded instead")
}
return nil
}
func checkEepromWithoutWP(ctx context.Context, h *firmware.Helper, d *dut.DUT, data *eepromData) error {
testing.ContextLog(ctx, "Enabling write protect")
if err := h.Servo.SetFWWPState(ctx, servo.FWWPStateOff); err != nil {
return errors.Wrap(err, "failed to disable firmware write protect")
}
if err := h.Servo.RunECCommand(ctx, "flashwp false"); err != nil {
return errors.Wrap(err, "failed to disable flashwp")
}
testing.ContextLog(ctx, "Waiting so write protect can disable")
if err := testing.Sleep(ctx, 1*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 1s")
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
return errors.Wrap(err, "failed to create mode switcher")
}
testing.ContextLog(ctx, "Rebooting DUT")
if err := ms.ModeAwareReboot(ctx, firmware.ColdReset); err != nil {
return errors.Wrap(err, "failed to perform mode aware reboot")
}
if data.wpDecoupled {
testing.ContextLog(ctx, "Enabling software write protect")
if _, err := h.DUT.Conn().CommandContext(ctx, "flashrom", "-p", "ec", "--wp-disable").Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to disable wp")
}
}
// Remove tag if already exists to test creating new tag.
testing.ContextLogf(ctx, "Checking if tag %q already exists", data.cbiTag)
if _, err := readTagFromEeprom(ctx, d, data); err == nil {
testing.ContextLogf(ctx, "tag %q already exists in CBI, removing it", data.cbiTag)
if err := removeTagFromEeprom(ctx, d, data); err != nil {
return errors.Wrap(err, "failed to remove tag from cbi")
}
}
// Test writing data to new tag.
data.writeData = data.testData1
testing.ContextLogf(ctx, "Attempting to write data %q to tag %q", data.writeData, data.cbiTag)
if err := writeTagToEeprom(ctx, h, d, data); err != nil {
return errors.Wrap(err, "failed to write data to cbi")
}
testing.ContextLog(ctx, "Sleeping for 5 seconds to let writes complete")
if err := testing.Sleep(ctx, 5*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 2 seconds")
}
// Data should have written correctly to tag.
testing.ContextLog(ctx, "Attempting to read data at tag ", data.cbiTag)
if out, err := readTagFromEeprom(ctx, d, data); err != nil {
return errors.Wrapf(err, "failed to read data from cbi, got: %v", out)
} else if out != data.writeData {
return errors.Wrapf(err, "read data %q, expected to read %q", out, data.writeData)
}
// Test changing tag value to new data.
data.writeData = data.testData2
testing.ContextLogf(ctx, "Attempting to write data %q to tag %q", data.writeData, data.cbiTag)
if err := writeTagToEeprom(ctx, h, d, data); err != nil {
return errors.Wrap(err, "failed to write data to cbi")
}
testing.ContextLog(ctx, "Sleeping for 5 seconds to let writes complete")
if err := testing.Sleep(ctx, 5*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 2 seconds")
}
// Tag value should have been correctly overwritten.
testing.ContextLog(ctx, "Attempting to read data at tag ", data.cbiTag)
if out, err := readTagFromEeprom(ctx, d, data); err != nil {
return errors.Wrap(err, "failed to read data from cbi")
} else if out != data.writeData {
return errors.Wrapf(err, "Read data %q, expected to read %q", out, data.writeData)
}
// Remove test tag from CBI.
testing.ContextLog(ctx, "Attempting to remove tag ", data.cbiTag)
if err := removeTagFromEeprom(ctx, d, data); err != nil {
return errors.Wrap(err, "failed to remove tag from cbi")
}
testing.ContextLog(ctx, "Sleeping for 5 seconds to let tag be removed")
if err := testing.Sleep(ctx, 5*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep for 2 seconds")
}
// Verify test tag was successfully removed.
testing.ContextLogf(ctx, "Verifying tag %q was removed ", data.cbiTag)
if _, err := readTagFromEeprom(ctx, d, data); err == nil {
return errors.Wrapf(err, "expected tag %q to be removed from CBI", data.cbiTag)
}
return nil
}
func readTagFromEeprom(ctx context.Context, d *dut.DUT, data *eepromData) (string, error) {
testing.ContextLog(ctx, "Using ectool cbi get to try to read tag (upto 3 retries) ", data.cbiTag)
strMatch := ""
err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := firmware.NewECTool(d, firmware.ECToolNameMain).CBI(ctx, firmware.CBIGet, data.cbiTag)
if err != nil {
return errors.Wrapf(err, "failed to read tag %q from cbi, got output: %v", data.cbiTag, out)
}
cbiGetRegexp := regexp.MustCompile(`As uint:\s*(\S+)\s*\((\S+)\)\sAs binary:\s*(\S+)\s`)
match := cbiGetRegexp.FindStringSubmatch(out)
if match == nil || len(match) == 0 {
return errors.Errorf("cbi read output didn't match expected format, got: %q", out)
}
strMatch = match[2]
return nil
}, &testing.PollOptions{Interval: 500 * time.Millisecond, Timeout: 2 * time.Second})
if err != nil {
return "", errors.Wrap(err, "failed to read cbi")
}
testing.ContextLogf(ctx, "Read data from tag %q: %s", data.cbiTag, strMatch)
return strMatch, nil
}
func writeTagToEeprom(ctx context.Context, h *firmware.Helper, d *dut.DUT, data *eepromData) error {
testing.ContextLogf(ctx, "Attempting to write data %q to tag %q", data.writeData, data.cbiTag)
writeArgs := []string{data.cbiTag, data.writeData, strconv.Itoa(data.writeSize)}
out, err := firmware.NewECTool(d, firmware.ECToolNameMain).CBI(ctx, firmware.CBISet, writeArgs...)
if err != nil {
return errors.Wrapf(err, "failed to write data %q to cbi tag %q, got output: %v", data.writeData, data.cbiTag, out)
}
return nil
}
func removeTagFromEeprom(ctx context.Context, d *dut.DUT, data *eepromData) error {
testing.ContextLog(ctx, "Using ectool cbi remove to erase tag ", data.cbiTag)
out, err := firmware.NewECTool(d, firmware.ECToolNameMain).CBI(ctx, firmware.CBIRemove, data.cbiTag)
if err != nil {
return errors.Wrapf(err, "failed to remove tag %q from cbi, got output: %v", data.cbiTag, out)
}
return nil
}