blob: 155302335375468b5963c9eca8edca88dee32cb4 [file] [log] [blame]
// Copyright 2021 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 (
"bufio"
"context"
"regexp"
"time"
"github.com/golang/protobuf/ptypes/empty"
"go.chromium.org/tast-tests/cros/remote/firmware"
"go.chromium.org/tast-tests/cros/remote/firmware/fixture"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/ssh"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/hwdep"
)
type lidSwitchTest int
const (
checkKeyPresses lidSwitchTest = iota
bootWithLid
shutdownWithLid
unsuspendWithLid
)
func init() {
testing.AddTest(&testing.Test{
Func: ECLidSwitch,
Desc: "Test EC Lid Switch",
Contacts: []string{
"chromeos-faft@google.com",
"tij@google.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
Attr: []string{"group:firmware", "firmware_ec"},
Fixture: fixture.NormalMode,
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.Lid()),
SoftwareDeps: []string{"chrome"},
LacrosStatus: testing.LacrosVariantUnneeded,
Requirements: []string{"sys-fw-0022-v02"},
ServiceDeps: []string{"tast.cros.firmware.UtilsService"},
Timeout: 10 * time.Minute,
Params: []testing.Param{
{
Name: "check_key_press",
Val: checkKeyPresses,
ExtraHardwareDeps: hwdep.D(hwdep.Keyboard()),
},
{
Name: "open_lid_to_boot",
Val: bootWithLid,
},
{
Name: "close_lid_to_shutdown",
Val: shutdownWithLid,
},
{
Name: "open_lid_to_unsuspend",
Val: unsuspendWithLid,
},
},
})
}
const (
lidDelay time.Duration = 1 * time.Second
wakeDelay time.Duration = 10 * time.Second
noDelay time.Duration = lidDelay // Just wait for lid state to change, no additional delay.
bootDelay time.Duration = 15 * time.Second
)
func ECLidSwitch(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.RequireRPCUtils(ctx); err != nil {
s.Fatal("Requiring RPC utils: ", err)
}
testMethod := s.Param().(lidSwitchTest)
switch testMethod {
case checkKeyPresses:
s.Log("Check for errant keypresses on lid open/close")
if err := checkKeyPressesWithLidClosed(ctx, h); err != nil {
s.Fatal("Error checking key presses: ", err)
}
case bootWithLid:
s.Log("Power off DUT and wake immediately")
if err := bootWithLidOpen(ctx, h, noDelay); err != nil {
s.Fatal("Failed to poweroff and wake immediately: ", err)
}
s.Log("Power off DUT and wake after delay")
if err := bootWithLidOpen(ctx, h, wakeDelay); err != nil {
s.Fatal("Failed to poweroff and wake after delay: ", err)
}
case shutdownWithLid:
s.Log("Close DUT lid and wake immediately")
if err := shutdownWithLidClose(ctx, h, noDelay); err != nil {
s.Fatal("Failed to close lid and wake immediately: ", err)
}
s.Log("Close DUT lid and wake immediately")
if err := shutdownWithLidClose(ctx, h, wakeDelay); err != nil {
s.Fatal("Failed to close lid and wake immediately: ", err)
}
case unsuspendWithLid:
// Create instance of chrome for login so that DUT suspends and does not shut down.
s.Log("Use Chrome service")
if _, err := h.RPCUtils.ReuseChrome(ctx, &empty.Empty{}); err != nil {
s.Fatal("Failed to create instance of chrome: ", err)
}
s.Log("Suspend DUT and wake immediately")
if err := suspendAndWakeWithLid(ctx, h, noDelay); err != nil {
s.Fatal("Failed to suspend DUT and wake immediately: ", err)
}
s.Log("Suspend DUT and wake after delay")
if err := suspendAndWakeWithLid(ctx, h, wakeDelay); err != nil {
s.Fatal("Failed to suspend DUT and wake after delay: ", err)
}
}
}
func suspendAndWakeWithLid(ctx context.Context, h *firmware.Helper, delay time.Duration) error {
testing.ContextLog(ctx, "Suspending DUT")
cmd := h.DUT.Conn().CommandContext(ctx, "powerd_dbus_suspend", "--delay=5")
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to suspend DUT")
}
testing.ContextLog(ctx, "Checking for S0ix or S3 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0ix", "S3"); err != nil {
return errors.Wrap(err, "failed to get S0ix or S3 powerstate")
}
if err := h.Servo.CloseLid(ctx); err != nil {
return err
}
testing.ContextLog(ctx, "Delaying opening lid for ", delay)
// GoBigSleepLint: used by main function to either immediately wake or wake after some delay.
if err := testing.Sleep(ctx, delay); err != nil {
return err
}
if err := h.Servo.OpenLid(ctx); err != nil {
return err
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0")
if err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
if err := h.EnsureDUTBooted(ctx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT after unsuspending")
}
return nil
}
func shutdownWithLidClose(ctx context.Context, h *firmware.Helper, delay time.Duration) (reterr error) {
// WA to clean system state for lid misfunction if harmless. Root cause is still unknown.
if err := h.DUT.Reboot(ctx); err != nil {
return errors.Wrap(err, "failed to reboot DUT")
}
// Log variables from powerd files to monitor unexpected settings.
logCmd := `d="/var/lib/power_manager"; for f in $(ls -A $d); do echo "$f: $(cat $d/$f)"; done`
out, err := h.DUT.Conn().CommandContext(ctx, "sh", "-c", logCmd).Output(ssh.DumpLogOnError)
if err != nil {
return errors.Wrap(err, "failed to read files in /var/lib/power_manager")
}
testing.ContextLog(ctx, "Files in /var/lib/power_manager: ", string(out))
testing.ContextLog(ctx, "Delaying closing lid for ", bootDelay)
// GoBigSleepLint: delay a few seconds to ensure no lid state change from system.
if err := testing.Sleep(ctx, bootDelay); err != nil {
return err
}
if err := h.Servo.CloseLid(ctx); err != nil {
return err
}
// This usually takes longer than usual to reach G3/S5, so increase timeout.
testing.ContextLog(ctx, "Check for G3 or S5 powerstate")
err = h.WaitForPowerStates(ctx, firmware.PowerStateInterval, 2*firmware.PowerStateTimeout, "G3", "S5")
if err != nil {
return errors.Wrap(err, "failed to get G3 or S5 powerstate")
}
testing.ContextLog(ctx, "Delaying opening lid for ", delay)
// GoBigSleepLint: delay by `lidDelay` to ensure lid is detected as closed.
if err := testing.Sleep(ctx, delay); err != nil {
return err
}
if err := h.Servo.OpenLid(ctx); err != nil {
return err
}
testing.ContextLog(ctx, "Waiting for S0 powerstate")
err = h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0")
if err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
if err := h.EnsureDUTBooted(ctx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT after unsuspending")
}
return nil
}
func bootWithLidOpen(ctx context.Context, h *firmware.Helper, delay time.Duration) error {
testing.ContextLog(ctx, "Shutdown dut")
if err := h.DUT.Conn().CommandContext(ctx, "sh", "-c", "(sleep 2; /sbin/shutdown -P now) &").Start(); err != nil {
return errors.Wrap(err, "failed to run `/sbin/shutdown -P now` cmd")
}
testing.ContextLog(ctx, "Check for G3 or S5 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, 2*firmware.PowerStateTimeout, "G3", "S5"); err != nil {
return errors.Wrap(err, "failed to get G3 or S5 powerstate")
}
if err := h.Servo.CloseLid(ctx); err != nil {
return err
}
testing.ContextLogf(ctx, "Delay opening lid by %s", delay)
// GoBigSleepLint: used by main function to either immediately wake or wake after some delay.
if err := testing.Sleep(ctx, delay); err != nil {
return err
}
if err := h.Servo.OpenLid(ctx); err != nil {
return err
}
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")
}
if err := h.EnsureDUTBooted(ctx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT after unsuspending")
}
return nil
}
func checkKeyPressesWithLidClosed(ctx context.Context, h *firmware.Helper) (reterr error) {
res, err := h.RPCUtils.FindPhysicalKeyboard(ctx, &empty.Empty{})
if err != nil {
return errors.Wrap(err, "failed to find keyboard")
}
device := res.Path
testing.ContextLogf(ctx, "Keyboard found at %v, checking for unexpected keypresses", device)
powerdCmd := "mkdir -p /tmp/power_manager && " +
"echo 0 > /tmp/power_manager/use_lid && " +
"mount --bind /tmp/power_manager /var/lib/power_manager && " +
"restart powerd"
if err := h.DUT.Conn().CommandContext(ctx, "sh", "-c", powerdCmd).Run(ssh.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to set use_lid")
}
defer func(ctx context.Context) {
restartPowerd := "umount /var/lib/power_manager && restart powerd"
if err := h.DUT.Conn().CommandContext(ctx, "sh", "-c", restartPowerd).Run(ssh.DumpLogOnError); err != nil {
reterr = errors.Wrap(err, "failed to restore powerd settings")
}
}(ctx)
cmd := h.DUT.Conn().CommandContext(ctx, "evtest", device)
stdout, err := cmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "failed to pipe stdout from 'evtest' cmd")
}
scanner := bufio.NewScanner(stdout)
cmd.Start()
testing.ContextLog(ctx, "Started piping output from 'evtest'")
defer cmd.Abort()
readKeyPress := func() error {
text := make(chan string)
go func() {
defer close(text)
for scanner.Scan() {
text <- scanner.Text()
}
}()
for {
select {
case <-time.After(5 * time.Second):
return nil
case out := <-text:
if match := regexp.MustCompile(`Event.*time.*code\s(\d*)\s\(\S+\)`).FindStringSubmatch(out); match != nil {
return errors.Errorf("unexpected key pressed detected: %s", match[0])
}
}
}
}
// Make sure lid is open in case DUT is in closed lid state at test start.
if err := h.Servo.OpenLid(ctx); err != nil {
return errors.Wrap(err, "error opening lid")
}
// GoBigSleepLint: delay by `lidDelay` to ensure lid is detected as open before re-closing.
if err := testing.Sleep(ctx, lidDelay); err != nil {
return errors.Wrap(err, "failed to sleep")
}
if err := h.Servo.CloseLid(ctx); err != nil {
return errors.Wrap(err, "error closing lid")
}
testing.ContextLog(ctx, "Checking for unexpected keypresses on lid close")
if err := readKeyPress(); err != nil {
return errors.Wrap(err, "expected no keypresses with lid closed")
}
if err := h.Servo.OpenLid(ctx); err != nil {
return errors.Wrap(err, "error opening lid")
}
testing.ContextLog(ctx, "Checking for unexpected keypresses on lid open")
if err := readKeyPress(); err != nil {
return errors.Wrap(err, "expected no keypresses with lid closed")
}
return nil
}