blob: f46ff5aab0a8dc562c24d5a312bd9fe267aec9a0 [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 (
"bufio"
"context"
"regexp"
"time"
"github.com/golang/protobuf/ptypes/empty"
"chromiumos/tast/common/servo"
"chromiumos/tast/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
"chromiumos/tast/ssh"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
type powerOffTest int
const (
powerOffWithAndWithoutPowerd powerOffTest = iota
ignoresShortPowerKey
powerOffWithShortPowerKey
)
func init() {
testing.AddTest(&testing.Test{
Func: ECPowerButton,
Desc: "Verify using servo power key results in expected shutdown behaviour",
Contacts: []string{"tij@google.com", "cros-fw-engprod@google.com"},
Attr: []string{"group:firmware", "firmware_unstable"},
SoftwareDeps: []string{"crossystem"},
ServiceDeps: []string{"tast.cros.firmware.UtilsService"},
HardwareDeps: hwdep.D(hwdep.ChromeEC()),
Fixture: fixture.NormalMode,
Timeout: 45 * time.Minute,
Params: []testing.Param{
{
Name: "toggle_powerd",
Val: powerOffWithAndWithoutPowerd,
},
{
Name: "ignore_short_power_key",
ExtraHardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Val: ignoresShortPowerKey,
},
{
Name: "short_power_key",
ExtraHardwareDeps: hwdep.D(hwdep.NoInternalDisplay()),
Val: powerOffWithShortPowerKey,
},
},
})
}
const (
shortPowerKeyPressDur time.Duration = 200 * time.Millisecond
)
func ECPowerButton(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)
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to require config: ", err)
}
switch s.Param().(powerOffTest) {
case powerOffWithAndWithoutPowerd:
s.Log("Test power off with power state")
if err := testRebootWithSettingPowerState(ctx, h); err != nil {
s.Fatal("Failed to reboot from setting servo powerstate: ", err)
}
s.Log("Test power off with powerd on and off")
if err := testPowerdPowerOff(ctx, h); err != nil {
s.Fatal("Failed powering off with or without powerd: ", err)
}
case ignoresShortPowerKey:
// If DUT has internal display, expect 200ms power key press to be ignored.
s.Log("Test that device with internal display ignores short power key press")
if err := testIgnoreShortPowerKey(ctx, h); err != nil {
s.Fatal("DUT unexpectedly shut down from short power key press: ", err)
}
s.Log("Test that device with internal display debounces very short power key press")
if err := testPowerKeyDebounce(ctx, h); err != nil {
s.Fatal("Expected power key pressed for 10ms to be debounced: ", err)
}
case powerOffWithShortPowerKey:
// If DUT doesn't have internal display, expect 200ms power key press to power off DUT.
s.Log("Test device without internal display doesn't ignore short power key press")
if err := testPowerOffWithShortPowerKey(ctx, h); err != nil {
s.Fatal("DUT didn't shut down from short power key press: ", err)
}
}
}
func testPowerOffWithShortPowerKey(ctx context.Context, h *firmware.Helper) error {
testing.ContextLog(ctx, "Pressing power key")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(shortPowerKeyPressDur)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLog(ctx, "Checking for S5 or G3 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S5", "G3"); err != nil {
return errors.Wrap(err, "failed to get S5 or G3 powerstate")
}
testing.ContextLog(ctx, "Pressing power key")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(h.Config.HoldPwrButtonPowerOff)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0"); err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return h.WaitConnect(ctx)
}
func testPowerKeyDebounce(ctx context.Context, h *firmware.Helper) error {
readKeyPress := func(scanner *bufio.Scanner, keyCode string) error {
regex := `Event.*time.*code\s(\d*)\s\(` + keyCode + `\)`
expMatch := regexp.MustCompile(regex)
text := make(chan string)
go func() {
for scanner.Scan() {
text <- scanner.Text()
}
close(text)
}()
for {
select {
case <-time.After(2 * time.Second):
return errors.New("did not detect keycode within expected time")
case out := <-text:
if match := expMatch.FindStringSubmatch(out); match != nil {
testing.ContextLog(ctx, "key pressed: ", match)
return nil
}
}
}
}
if err := h.RequireRPCUtils(ctx); err != nil {
return errors.Wrap(err, "requiring RPC utils")
}
testing.ContextLog(ctx, "Look for physical keyboard")
res, err := h.RPCUtils.FindPhysicalKeyboard(ctx, &empty.Empty{})
if err != nil {
return errors.Wrap(err, "during FindPhysicalKeyboard")
}
device := res.Path
testing.ContextLog(ctx, "Device path: ", device)
cmd := h.DUT.Conn().CommandContext(ctx, "evtest", device)
stdout, err := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()
testing.ContextLog(ctx, "Pressing power key for 10ms")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(10*time.Millisecond)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLog(ctx, "Read for power key press")
if err := readKeyPress(scanner, "KEY_POWER"); err != nil {
return errors.Wrap(err, "failed to read key")
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0"); err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return h.WaitConnect(ctx)
}
func testIgnoreShortPowerKey(ctx context.Context, h *firmware.Helper) error {
testing.ContextLog(ctx, "Getting current boot id")
bootID, err := h.Reporter.BootID(ctx)
if err != nil {
return errors.Wrap(err, "failed to get boot id")
}
testing.ContextLog(ctx, "Pressing power key")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(shortPowerKeyPressDur)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
if err := testing.Sleep(ctx, shortPowerKeyPressDur); err != nil {
return errors.Wrapf(err, "failed to sleep for %s ms", shortPowerKeyPressDur)
}
testing.ContextLog(ctx, "Expect S0 powerstate")
currPowerState, err := h.Servo.GetECSystemPowerState(ctx)
if err != nil {
return errors.Wrap(err, "failed to get current power state")
} else if currPowerState != "S0" {
return errors.Errorf("Current power state is: %s, expected S0", currPowerState)
}
testing.ContextLog(ctx, "Get new boot id, compare to old")
newBootID, err := h.Reporter.BootID(ctx)
if err != nil {
return errors.Wrap(err, "failed to get current boot id")
} else if newBootID != bootID {
return errors.Errorf("boot ID unexpectedly changed from %s to %s", bootID, newBootID)
}
return nil
}
func testPowerdPowerOff(ctx context.Context, h *firmware.Helper) error {
shutdownAndWake := func(shutDownDur time.Duration, expStates ...string) error {
testing.ContextLogf(ctx, "Pressing power key for %s", shutDownDur)
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(shutDownDur)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLogf(ctx, "Checking for %v powerstates", expStates)
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, expStates...); err != nil {
return errors.Wrapf(err, "failed to get %v powerstates", expStates)
}
// If we are expecting S5/G3, we might still get to G3 after S5, so give it a little time before we wake up again.
if err := testing.Sleep(ctx, time.Second*2); err != nil {
return errors.Wrap(err, "sleep failed")
}
testing.ContextLog(ctx, "Send cmd to EC to wake up from deepsleep")
h.Servo.RunECCommand(ctx, "help")
testing.ContextLog(ctx, "Pressing power key (press)")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.DurPress); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0"); err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
testing.ContextLog(ctx, "Waiting for DUT to connect")
if err := h.WaitConnect(ctx); err != nil {
return errors.Wrap(err, "failed to wait for DUT to connect")
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
return errors.Wrap(err, "creating mode switcher")
}
testing.ContextLog(ctx, "Perform mode aware reboot")
return ms.ModeAwareReboot(ctx, firmware.ColdReset)
}
powerdDur := h.Config.HoldPwrButtonPowerOff
noPowerdDur := h.Config.HoldPwrButtonNoPowerdShutdown
if err := shutdownAndWake(powerdDur, "S5", "G3"); err != nil {
return errors.Wrap(err, "failed shut down and wake with powerd")
}
testing.ContextLog(ctx, "stopping powerd")
if _, err := h.DUT.Conn().CommandContext(ctx, "stop", "powerd").Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to stop powerd")
}
if err := shutdownAndWake(noPowerdDur, "G3"); err != nil {
return errors.Wrap(err, "failed shut down and wake with no powerd")
}
if err := shutdownAndWake(powerdDur, "G3"); err != nil {
return errors.Wrap(err, "failed shut down and wake with powerd")
}
testing.ContextLog(ctx, "stopping powerd")
if _, err := h.DUT.Conn().CommandContext(ctx, "stop", "powerd").Output(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to stop powerd")
}
if err := shutdownAndWake(noPowerdDur, "S5", "G3"); err != nil {
return errors.Wrap(err, "failed shut down and wake with no powerd")
}
return nil
}
func testRebootWithSettingPowerState(ctx context.Context, h *firmware.Helper) error {
if err := h.Servo.SetPowerState(ctx, servo.PowerStateOff); err != nil {
return errors.Wrap(err, "failed to set 'power_state' to 'off'")
}
testing.ContextLog(ctx, "Pressing power key")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(h.Config.HoldPwrButtonPowerOff)); err != nil {
return errors.Wrap(err, "failed to press power key on DUT")
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0"); err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return h.WaitConnect(ctx)
}