blob: b9eb0378b2fb8d864ab08395b9d086432389b794 [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 gscdevboard
import (
"bytes"
"context"
"regexp"
"time"
"go.chromium.org/tast-tests/cros/common/firmware/ti50"
"go.chromium.org/tast-tests/cros/common/perf"
"go.chromium.org/tast-tests/cros/remote/bundles/cros/gscdevboard/utils"
"go.chromium.org/tast-tests/cros/remote/firmware/ti50/fixture"
"go.chromium.org/tast/core/testing"
)
type ti50SleepParam struct {
tpmCommunication ti50.TpmBus
tpmStrapping ti50.GpioStrap
servoMicroStrapping ti50.GpioStrap
}
func init() {
testing.AddTest(&testing.Test{
Func: Ti50Sleep,
Desc: "Test ti50 deep/normal sleep",
Timeout: 40 * time.Minute,
Contacts: []string{
"gsc-sheriff@google.com", // CrOS GSC Developers
"jbk@chromium.org", // Test Author
},
BugComponent: "b:715469", // ChromeOS > Platform > System > Hardware Security > HwSec GSC > Ti50
Attr: []string{"group:gsc", "gsc_dt_ab", "gsc_dt_shield", "gsc_ot_shield", "gsc_image_ti50", "gsc_nightly"},
Fixture: fixture.GSCOpenCCD,
Vars: []string{"bypass_sleep_check"},
Params: []testing.Param{{
Name: "spi_no_uservo",
Val: ti50SleepParam{
tpmCommunication: ti50.TpmBusSpi,
tpmStrapping: ti50.TpmSpi,
servoMicroStrapping: ti50.ServoMicroDisconnected,
},
}, {
Name: "spi_uservo",
Val: ti50SleepParam{
tpmCommunication: ti50.TpmBusSpi,
tpmStrapping: ti50.TpmSpi,
servoMicroStrapping: ti50.ServoMicroConnected,
},
}, {
Name: "i2c_no_uservo",
Val: ti50SleepParam{
tpmCommunication: ti50.TpmBusI2c,
tpmStrapping: ti50.TpmI2c,
servoMicroStrapping: ti50.ServoMicroDisconnected,
},
}, {
Name: "i2c_uservo",
Val: ti50SleepParam{
tpmCommunication: ti50.TpmBusI2c,
tpmStrapping: ti50.TpmI2c,
servoMicroStrapping: ti50.ServoMicroConnected,
},
}},
})
}
var (
reBoot *regexp.Regexp = regexp.MustCompile(`Ravn4|([0-9a-fA-F]{8})`)
reResetType *regexp.Regexp = regexp.MustCompile(`Reset Type: ([0-9a-zA-Z_]*)[\r\n]`)
reWakeSource *regexp.Regexp = regexp.MustCompile(`Wake source: 0x([0-9a-fA-F]{8})`)
reWhichPin *regexp.Regexp = regexp.MustCompile(`Which pin: 0x([0-9a-zA-Z_]*)[\r\n]`)
reConsole *regexp.Regexp = regexp.MustCompile(`Console is enabled`)
reAnyOutput *regexp.Regexp = regexp.MustCompile(`\w+`)
// Time to wait to ensure GSC did not go to sleep. This should be longer
// than the longest sleep delay in hil/pmu.rs
waitForNoSleep = 70 * time.Second
waitForNoWake = 3 * time.Second
)
type wakeSource string
type whichPin string
func logCurrent(ctx context.Context, s *testing.State, b utils.DevboardHelper, pv *perf.Values, label string) {
for n := 1; n <= 5; n++ {
testing.Sleep(ctx, 100*time.Millisecond) // GoBigSleepLint: Space out readings
c := b.ReadGscTotalMilliAmps(ctx)
pv.Append(perf.Metric{
Name: label,
Unit: "milliamps",
Direction: perf.SmallerIsBetter,
Multiple: true,
}, float64(c))
s.Logf("%s: %.2f mA", label, c)
}
}
func verifyDeepSleep(ctx context.Context, s *testing.State, i *ti50.CrOSImage, th utils.FirmwareTestingHelper) {
s.Log("Waiting for deep sleep")
th.MustSucceed(i.WaitUntilDeepSleep(ctx, ti50.WaitForSleepTimeout), "Did not deep sleep")
verifyNoWake(ctx, s, i, th)
}
func verifyNormalSleep(ctx context.Context, s *testing.State, i *ti50.CrOSImage, th utils.FirmwareTestingHelper) {
s.Log("Waiting for normal sleep")
th.MustSucceed(i.WaitUntilNormalSleep(ctx, ti50.WaitForSleepTimeout), "Did not normal sleep")
verifyNoWake(ctx, s, i, th)
}
func verifyNoWake(ctx context.Context, s *testing.State, i *ti50.CrOSImage, th utils.FirmwareTestingHelper) {
s.Log("Check we stay asleep")
if match, err := i.WaitUntilMatch(ctx, reAnyOutput, waitForNoWake); err == nil {
s.Errorf("Did not stay asleep: %q", match[0])
}
}
func verifyNoSleep(ctx context.Context, s *testing.State, i *ti50.CrOSImage, th utils.FirmwareTestingHelper) {
s.Log("Check we stay awake")
if err := i.WaitUntilAnySleep(ctx, waitForNoSleep); err == nil {
s.Error("Did not stay awake")
}
}
// verifyNormalWakeup verifies that Ti50 emits console output indicating wakeup from normal sleep.
// Aside from reporting any unexpected behavior through `s.Error`, the return value will be true
// only if Ti50 did wake up, which is useful for the top-level test script to know which state
// Ti50 is in, in case it wants to continue testing other aspects.
func verifyNormalWakeup(ctx context.Context, s *testing.State, i *ti50.CrOSImage, b utils.DevboardHelper, gpioMonitor utils.GpioMonitorSession, expected wakeSource, expectedGpio *whichPin, trigger string) bool {
wakeMatch, err := i.WaitUntilMatch(ctx, reWakeSource, time.Second*3)
if err != nil {
s.Errorf("Waking on %s: Unable to recognize wake source", trigger)
return false
}
if string(wakeMatch[1]) != string(expected) {
s.Errorf("Waking on %s: Unexpected wake mask: got %s, expected %s", trigger, wakeMatch[1], expected)
}
// Verify GPIO wake source if specified
if expectedGpio != nil {
// Sent a newline without reading output to ensure that the which pin regex
// will match since there is no guarantee anything will be written to the
// GSC console after the which pin line, so there might not be a trailing
// newline to match (and we need that newline to bound the hex string).
if err := i.WriteSerial(ctx, []byte("\n")); err != nil {
s.Fatal("Could not write to serial: ", err)
return false
}
whichPinMatch, err := i.WaitUntilMatch(ctx, reWhichPin, time.Second*3)
if err != nil {
s.Errorf("Waking on %s: Unable to recognize which pin source", trigger)
return true
}
if string(whichPinMatch[1]) != string(*expectedGpio) {
s.Errorf("Waking on %s: Unexpected which pin mask: got %s, expected %s", trigger, whichPinMatch[1], *expectedGpio)
}
}
if err := i.WaitUntilBooted(ctx); err != nil {
s.Error("Ti50 did not wake up by ", trigger)
}
events := b.GpioMonitorRead(ctx, gpioMonitor)
if len(events.Sorted) != 0 {
s.Errorf("Waking on %s: Unexpected gpio events: %s", trigger, events)
}
return true
}
// verifyDeepWakeup verifies that Ti50 emits console output indicating wakeup from deep sleep.
// Aside from reporting any unexpected behavior through `s.Error`, the return value will be true
// only if Ti50 did wake up, which is useful for the top-level test script to know which state
// Ti50 is in, in case it wants to continue testing other aspects.
func verifyDeepWakeup(ctx context.Context, s *testing.State, i *ti50.CrOSImage, b utils.DevboardHelper, gpioMonitor utils.GpioMonitorSession, expectedWake wakeSource, expectedGpio *whichPin, trigger string) bool {
_, err := i.WaitUntilMatch(ctx, reBoot, time.Second*3)
if err != nil {
s.Error("Ti50 did not wake up by ", trigger)
return false
}
resetMatch, err := i.WaitUntilMatch(ctx, reResetType, time.Second*3)
if err != nil {
s.Errorf("Waking on %s: did not recognize reset type", trigger)
return true
}
if string(resetMatch[1]) == "Wake" {
wakeMatch, err := i.WaitUntilMatch(ctx, reWakeSource, time.Second*3)
if err != nil {
s.Errorf("Waking on %s: Unable to recognize wake source", trigger)
return true
}
if string(wakeMatch[1]) != string(expectedWake) {
s.Errorf("Waking on %s: Unexpected wake mask: got %s, expected %s", trigger, wakeMatch[1], expectedWake)
}
// Verify GPIO wake source if specified
if expectedGpio != nil {
whichPinMatch, err := i.WaitUntilMatch(ctx, reWhichPin, time.Second*3)
if err != nil {
s.Errorf("Waking on %s: Unable to recognize which pin source", trigger)
return true
}
if string(whichPinMatch[1]) != string(*expectedGpio) {
s.Errorf("Waking on %s: Unexpected which pin mask: got %s, expected %s", trigger, whichPinMatch[1], *expectedGpio)
}
}
} else {
s.Errorf("Waking on %s: Unexpected reset type: got %q, expected %q", trigger, string(resetMatch[1]), "Wake")
}
_, err = i.WaitUntilMatch(ctx, reConsole, time.Second*3)
if err != nil {
s.Error("Console not enabled after wake up by ", trigger)
return true
}
if err := i.WaitUntilBooted(ctx); err != nil {
s.Error("Ti50 did not wake up by ", trigger)
}
events := b.GpioMonitorRead(ctx, gpioMonitor)
if len(events.Sorted) != 0 {
s.Errorf("Waking on %s: Unexpected gpio events: %s", trigger, events)
}
return true
}
func Ti50Sleep(ctx context.Context, s *testing.State) {
// Check if runtime bypass_sleep_check var is present, if so (even if value is
// "false") set the 70 seconds wait to 1 second the rest of the test
if _, fastSleep := s.Var("bypass_sleep_check"); fastSleep {
s.Log("Wait for no sleep reduced from 70 seconds to 1 second")
waitForNoSleep = 1 * time.Second
}
testParams := s.Param().(ti50SleepParam)
b := utils.NewDevboardHelper(s)
th := utils.FirmwareTestingHelper{FirmwareTestingHelperDelegate: s}
i := ti50.MustOpenCrOSImage(ctx, b, s)
defer i.Close(ctx)
pv := perf.NewValues()
// Wake source and pin values for DT chip.
var (
wakeSourceGpio wakeSource = "00000001"
wakeSourceRbox wakeSource = "00000004"
wakeSourceAdc wakeSource = "00000008"
whichPinWPSense whichPin = "0000000000000004"
whichPinPltRstL whichPin = "0000000000000800"
whichPinEcPacketMode whichPin = "0000000001000000"
whichPinLidOpen whichPin = "0000000002000000"
whichPinGscUart whichPin = "0000010000000000"
whichPinCcdMode whichPin = "0000080000000000"
whichPinTpmSpi whichPin = "0200000000000000"
whichPinTpmI2C whichPin = "0000000000000003"
)
// Wake source and pin values for OT chip.
if b.TestbedType == ti50.GscOTShield || b.TestbedType == ti50.GscOpentitanCw310Fpga {
wakeSourceGpio = "00000004"
wakeSourceRbox = "00000001"
wakeSourceAdc = "00000002"
whichPinWPSense = "0000000000000020"
whichPinPltRstL = "0000000000000001"
whichPinEcPacketMode = "0000000000000010"
whichPinLidOpen = "0000000000000002"
whichPinGscUart = "0000000000000008"
whichPinCcdMode = "0000000000000004"
whichPinTpmSpi = "0000000000000040"
whichPinTpmI2C = "00000000000000c0"
}
// Set the correct TPM wake up pins based on test parameters
var whichPinTpmBus whichPin
if testParams.tpmStrapping == ti50.TpmI2c {
whichPinTpmBus = whichPinTpmI2C
} else if testParams.tpmStrapping == ti50.TpmSpi {
whichPinTpmBus = whichPinTpmSpi
}
s.Log("Restarting ti50 with appropriate straps")
b.GpioSet(ctx, ti50.GpioTi50PltRstL, false)
b.GpioSet(ctx, ti50.GpioTi50LidOpen, false)
b.GpioSet(ctx, ti50.GpioTi50EcRstL, true)
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, false)
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, true)
b.GpioSet(ctx, ti50.GpioTi50WriteProtectSenseL, false)
b.GpioSet(ctx, ti50.GpioTi50ACPresent, false)
b.ResetWithStraps(ctx, testParams.servoMicroStrapping, ti50.CcdDisconnected, testParams.tpmStrapping)
th.MustSucceed(i.WaitUntilBooted(ctx), "Ti50 revives after reboot")
var gpioMonitor utils.GpioMonitorSession
if b.GscProperties().HasEcRstFet() {
gpioMonitor = b.GpioMonitorStart(ctx, ti50.GpioTi50EcRstL, ti50.GpioTi50EcRstFet)
} else {
gpioMonitor = b.GpioMonitorStart(ctx, ti50.GpioTi50EcRstL)
}
logCurrent(ctx, s, b, pv, "Awake")
verifyDeepSleep(ctx, s, i, th)
logCurrent(ctx, s, b, pv, "DeepSleep")
s.Log("Simulating power button press")
b.GpioSet(ctx, ti50.GpioTi50PowerBtnL, false)
b.GpioSet(ctx, ti50.GpioTi50PowerBtnL, true)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceRbox, nil, "Power Button") {
verifyDeepSleep(ctx, s, i, th)
}
// OpenTitan does not sleep while AC present.
if b.TestbedType == ti50.GscOTShield || b.TestbedType == ti50.GscOpentitanCw310Fpga {
s.Log("Simulating AC present")
b.GpioSet(ctx, ti50.GpioTi50ACPresent, true)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceRbox, nil, "AC present") {
verifyNoSleep(ctx, s, i, th)
}
s.Log("Simulating AC not present")
b.GpioSet(ctx, ti50.GpioTi50ACPresent, false)
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating lid low-to-high event")
b.GpioSet(ctx, ti50.GpioTi50LidOpen, true)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinLidOpen, "Lid low-to-high event") {
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating EC packet mode")
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, true)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinEcPacketMode, "EC packet mode") {
verifyNoSleep(ctx, s, i, th)
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, false)
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating CCD_MODE asserted")
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, false)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinCcdMode, "CCD_MODE asserted") {
verifyNoSleep(ctx, s, i, th)
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, true)
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating WP_SENSE_L de-assert pulse event")
b.GpioSet(ctx, ti50.GpioTi50WriteProtectSenseL, true)
b.GpioSet(ctx, ti50.GpioTi50WriteProtectSenseL, false)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinWPSense, "WP_SENSE_L de-assert event") {
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating SuzyQ inserted")
b.GpioApplyStrap(ctx, ti50.CcdSuzyQ)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceAdc, nil, "CCD connection") {
logCurrent(ctx, s, b, pv, "Awake_CCD")
verifyNoSleep(ctx, s, i, th)
b.GpioApplyStrap(ctx, ti50.CcdDisconnected)
verifyDeepSleep(ctx, s, i, th)
logCurrent(ctx, s, b, pv, "DeepSleep_CCD")
} else {
// Error already reported by `verifyDeepWakeup`, disconnect SuzyQ and move on to
// testing other wake sources.
b.GpioApplyStrap(ctx, ti50.CcdDisconnected)
}
s.Log("Simulating serial console input")
th.MustSucceed(b.WriteSerial(ctx, []byte("hello\r")), "Serial write")
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinGscUart, "console input") {
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Simulating EC_PACKET_MODE toggle")
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, true)
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, false)
if verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinEcPacketMode, "EC_PACKET_MODE") {
verifyDeepSleep(ctx, s, i, th)
}
s.Log("Setting WP enabled at boot to prevent wp dirty state. Reset GSC to clear dirty WP state")
// Pause the gpio monitor since we are about to reboot the GSC on purpose.
th.MustSucceed(i.WaitUntilBooted(ctx), "Ti50 revives after reboot")
th.MustSucceed(i.TestlabOpen(ctx), "testlab open")
th.MustSucceed(i.SetWpAtBoot(ctx, true), "Enable WP at boot")
th.MustSucceed(i.SendConsoleRebootCmd(ctx), "GSC reboot")
th.MustSucceed(i.WaitUntilBooted(ctx), "Ti50 revives after reboot")
// Clear the last gpio monitor events since we expect GSC to reset EC from above commands
b.GpioMonitorRead(ctx, gpioMonitor)
verifyDeepSleep(ctx, s, i, th)
s.Log("Simulating AP booting")
b.GpioSet(ctx, ti50.GpioTi50PltRstL, true)
if !verifyDeepWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinPltRstL, "PltRstL") {
s.Fatal("Could not get Ti50 into 'AP on' mode, preventing further testing")
}
logCurrent(ctx, s, b, pv, "Awake_AP")
s.Log("Waiting for sleep after boot")
// Nominally, Ti50 should refrain from sleeping 60 seconds after AP boot, allow 15 seconds
// either way, to account for test script execution delays.
if err := i.WaitUntilAnySleep(ctx, 45*time.Second); err == nil {
s.Error("Ti50 went to sleep too soon after AP boot")
}
th.MustSucceed(i.WaitUntilNormalSleep(ctx, 30*time.Second), "Sleep when AP on")
logCurrent(ctx, s, b, pv, "NormalSleep")
s.Log("Simulating AP TPM request")
tpmHandle := b.Tpm(ctx, testParams.tpmCommunication)
didVid := tpmHandle.ReadRegister(ti50.TpmRegDidVid)
expectedDidVidValue := b.GscProperties().ExpectedDidVidValue()
if !bytes.Equal(didVid, expectedDidVidValue) {
s.Error("Unexpected TPM DID_VID immediately after wakeup: ", didVid)
}
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinTpmBus, "AP TPM request") {
verifyNormalSleep(ctx, s, i, th)
}
s.Log("Simulating EC packet mode")
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, true)
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinEcPacketMode, "EC packet mode") {
verifyNoSleep(ctx, s, i, th)
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, false)
verifyNormalSleep(ctx, s, i, th)
} else {
// Error already reported by `verifyNormalWakeup`, move on to testing other wake sources.
b.GpioSet(ctx, ti50.GpioTi50EcPacketMode, false)
}
s.Log("Simulating power button pressed")
b.GpioSet(ctx, ti50.GpioTi50PowerBtnL, false)
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceRbox, nil, "Power button") {
verifyNoSleep(ctx, s, i, th)
b.GpioSet(ctx, ti50.GpioTi50PowerBtnL, true)
verifyNormalSleep(ctx, s, i, th)
} else {
// Error already reported by `verifyNormalWakeup`, move on to testing other wake sources.
b.GpioSet(ctx, ti50.GpioTi50PowerBtnL, true)
}
s.Log("Simulating CCD_MODE asserted")
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, false)
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinCcdMode, "CCD_MODE asserted") {
verifyNoSleep(ctx, s, i, th)
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, true)
verifyNormalSleep(ctx, s, i, th)
} else {
// Error already reported by `verifyNormalWakeup`, move on to testing other wake sources.
b.GpioSet(ctx, ti50.GpioTi50CcdModeL, true)
}
s.Log("Simulating SuzyQ inserted")
b.GpioApplyStrap(ctx, ti50.CcdSuzyQ)
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceAdc, nil, "CCD connection") {
logCurrent(ctx, s, b, pv, "Awake_AP_CCD")
verifyNoSleep(ctx, s, i, th)
b.GpioApplyStrap(ctx, ti50.CcdDisconnected)
verifyNormalSleep(ctx, s, i, th)
logCurrent(ctx, s, b, pv, "NormalSleep_CCD")
// For some reason, after USB disconnect it takes five seconds for Dauntless power
// consumption to drop.
testing.Sleep(ctx, 5*time.Second) // GoBigSleepLint: Wait for power change
logCurrent(ctx, s, b, pv, "NormalSleep_CCD_2")
} else {
// Error already reported by `verifyNormalWakeup`, move on to testing other wake sources.
b.GpioApplyStrap(ctx, ti50.CcdDisconnected)
}
s.Log("Simulating serial console input")
th.MustSucceed(b.WriteSerial(ctx, []byte("hello\r")), "Serial write")
if verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinGscUart, "serial console input") {
verifyNormalSleep(ctx, s, i, th)
}
s.Log("Simulating WP_SENSE_L de-assert pulse event")
b.GpioSet(ctx, ti50.GpioTi50WriteProtectSenseL, true)
b.GpioSet(ctx, ti50.GpioTi50WriteProtectSenseL, false)
verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinWPSense, "WP_SENSE_L pulse")
// Need to clear an WP dirty state that would cause extra GSC reboots
th.MustSucceed(i.SendConsoleRebootCmd(ctx), "GSC reboot")
th.MustSucceed(i.WaitUntilBooted(ctx), "Ti50 revives after reboot")
// Clear the last gpio monitor events since we expect GSC to reset EC from above commands
b.GpioMonitorRead(ctx, gpioMonitor)
verifyNormalSleep(ctx, s, i, th)
s.Log("Simulating AP powering off")
b.GpioSet(ctx, ti50.GpioTi50PltRstL, false)
if !verifyNormalWakeup(ctx, s, i, b, gpioMonitor, wakeSourceGpio, &whichPinPltRstL, "PltRstL") {
s.Fatal("Could not get Ti50 out of 'AP on' mode, preventing further testing")
}
verifyDeepSleep(ctx, s, i, th)
b.GpioMonitorFinish(ctx, gpioMonitor)
if err := pv.Save(s.OutDir()); err != nil {
s.Error("Failed to save perf data: ", err)
}
}