blob: 4db807b0f7d8f583f2532a819083daf9d8e877bd [file] [log] [blame]
// Copyright 2021 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/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: ECLidSwitch,
Desc: "Test EC Lid Switch",
Contacts: []string{"tij@google.com", "cros-fw-engprod@google.com"},
Attr: []string{"group:firmware", "firmware_unstable"},
Fixture: fixture.NormalMode,
// Skip on form factors without a lid.
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.Lid()),
ServiceDeps: []string{"tast.cros.firmware.UtilsService"},
})
}
// time constants
const (
lidDelay time.Duration = 2 * time.Second
wakeDelay time.Duration = 10 * time.Second
noDelay time.Duration = 0 * time.Second // minimal delay to make sure event is registered first
readKeyDelay time.Duration = 2 * time.Second
readKeyTimeout time.Duration = 1 * time.Second
)
// regular expressions
var (
reKeyPress = regexp.MustCompile(`Event.*time.*code\s\d*\s\((KEY\S+)\)`)
)
type lidSwitchArgs struct {
ctx context.Context
h *firmware.Helper
ms *firmware.ModeSwitcher
}
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)
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Failed to create mode switcher: ", err)
}
args := &lidSwitchArgs{
ctx: ctx,
h: h,
ms: ms,
}
s.Log("Check for errant keypresses on lid open/close")
if err := checkKeyPress(args); err != nil {
s.Fatal("Error checking key presses: ", err)
}
s.Log("Power off DUT and wake immediately")
if err := powerOffAndWake(args, noDelay); err != nil {
s.Fatal("Failed to poweroff and wake after delay: ", err)
}
s.Log("Power off DUT and wake after delay")
if err := powerOffAndWake(args, wakeDelay); err != nil {
s.Fatal("Failed to poweroff and wake after delay: ", err)
}
s.Log("Close DUT lid and wake immediately")
if err := closeLidAndWake(args); err != nil {
s.Fatal("Failed to close lid and wake immediately: ", err)
}
s.Log("Suspend DUT and wake immediately")
if err := suspendAndWake(args, noDelay); err != nil {
s.Fatal("Failed to suspend DUT and wake immediately: ", err)
}
s.Log("Suspend DUT and wake after delay")
if err := suspendAndWake(args, wakeDelay); err != nil {
s.Fatal("Failed to suspend DUT and wake after delay: ", err)
}
}
func suspendAndWake(args *lidSwitchArgs, delay time.Duration) error {
defer args.h.WaitConnect(args.ctx)
testing.ContextLog(args.ctx, "Suspending DUT")
cmd := args.h.DUT.Conn().CommandContext(args.ctx, "powerd_dbus_suspend")
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to suspend DUT")
}
testing.ContextLog(args.ctx, "Checking for S0ix or S3 powerstate")
if err := args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0ix", "S3"); err != nil {
return errors.Wrap(err, "failed to get S0ix or S3 powerstate")
}
// Used by main function to either immediately wake or wake after some delay.
if err := testing.Sleep(args.ctx, delay); err != nil {
return err
}
if err := args.h.Servo.CloseLid(args.ctx); err != nil {
return err
}
// Delay by `lidDelay` to ensure lid is detected as closed.
if err := testing.Sleep(args.ctx, lidDelay); err != nil {
return err
}
if err := args.h.Servo.OpenLid(args.ctx); err != nil {
return err
}
testing.ContextLog(args.ctx, "Waiting for S0 powerstate")
err := args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0")
if err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return nil
}
func closeLidAndWake(args *lidSwitchArgs) error {
defer args.h.WaitConnect(args.ctx)
if err := args.h.Servo.CloseLid(args.ctx); err != nil {
return err
}
testing.ContextLog(args.ctx, "Check for G3 or S5 powerstate")
err := args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "G3", "S5")
if err != nil {
return errors.Wrap(err, "failed to get G3 or S5 powerstate")
}
// Delay by `lidDelay` to ensure lid is detected as closed.
if err := testing.Sleep(args.ctx, lidDelay); err != nil {
return err
}
if err := args.h.Servo.OpenLid(args.ctx); err != nil {
return err
}
testing.ContextLog(args.ctx, "Waiting for S0 powerstate")
err = args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0")
if err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return nil
}
func powerOffAndWake(args *lidSwitchArgs, delay time.Duration) error {
defer args.h.WaitConnect(args.ctx)
if err := args.ms.PowerOff(args.ctx); err != nil {
return err
}
// The ms.PowerOff method checks for G3 or S5 but might just wait for DUT to be unreachable.
// So it checks powerstate to make sure it reaches one of those two desired states.
testing.ContextLog(args.ctx, "Check for G3 or S5 powerstate")
err := args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "G3", "S5")
if err != nil {
return errors.Wrap(err, "failed to get G3 or S5 powerstate")
}
// Used by main function to either immediately wake or wake after some delay.
if err := testing.Sleep(args.ctx, delay); err != nil {
return err
}
if err := args.h.Servo.CloseLid(args.ctx); err != nil {
return err
}
// Delay by `lidDelay` to ensure lid is detected as closed.
if err := testing.Sleep(args.ctx, lidDelay); err != nil {
return err
}
if err := args.h.Servo.OpenLid(args.ctx); err != nil {
return err
}
testing.ContextLog(args.ctx, "Waiting for S0 powerstate")
err = args.h.WaitForPowerStates(args.ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "S0")
if err != nil {
return errors.Wrap(err, "failed to get S0 powerstate")
}
return nil
}
func readKeyPresses(ctx context.Context, scanner *bufio.Scanner) error {
text := make(chan string)
go func() {
for scanner.Scan() {
text <- scanner.Text()
}
close(text)
}()
for {
select {
case <-time.After(readKeyTimeout):
return nil
case out := <-text:
if match := reKeyPress.FindStringSubmatch(out); match != nil {
return errors.Errorf("unexpected key pressed detected: %v", match)
}
}
}
}
func checkKeyPress(args *lidSwitchArgs) error {
// Skip keypress test if no keyboard is available on DUT.
res, err := args.h.RPCUtils.FindPhysicalKeyboard(args.ctx, &empty.Empty{})
if err != nil {
testing.ContextLog(args.ctx, "No keyboard found, skipping keyboard tests: ", err)
return nil
}
device := res.Path
testing.ContextLogf(args.ctx, "Keyboard found at %v, checking for unexpected keypresses", device)
if err := stopPowerd(args); err != nil {
return errors.Wrap(err, "failed to stop powerd")
}
defer startPowerd(args)
cmd := args.h.DUT.Conn().CommandContext(args.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(args.ctx, "Started piping output from 'evtest'")
defer cmd.Abort()
if err := args.h.Servo.OpenLid(args.ctx); err != nil {
return errors.Wrap(err, "error in checkKeyPress")
}
// Delay by `lidDelay` to ensure lid is detected as closed.
if err := testing.Sleep(args.ctx, lidDelay); err != nil {
return err
}
if err := args.h.Servo.CloseLid(args.ctx); err != nil {
return errors.Wrap(err, "error in checkKeyPress")
}
// Delay reading by `readKeyDelay` to ensure any keypresses get logged to stdout.
if err := testing.Sleep(args.ctx, readKeyDelay); err != nil {
return err
}
if err := readKeyPresses(args.ctx, scanner); err != nil {
return errors.Wrap(err, "error in checkKeyPress")
}
if err := args.h.Servo.OpenLid(args.ctx); err != nil {
return errors.Wrap(err, "error in checkKeyPress")
}
return nil
}
func startPowerd(args *lidSwitchArgs) error {
testing.ContextLog(args.ctx, "Starting powerd")
startPowerd := args.h.DUT.Conn().CommandContext(args.ctx, "start", "powerd")
return startPowerd.Run()
}
func stopPowerd(args *lidSwitchArgs) error {
testing.ContextLog(args.ctx, "Stopping powerd")
stopPowerd := args.h.DUT.Conn().CommandContext(args.ctx, "stop", "powerd")
return stopPowerd.Run()
}