blob: 8c7869384f5b82df9c58b5832fe5973a13befe65 [file]
// 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 power
import (
"context"
"regexp"
"time"
"chromiumos/tast/common/servo"
"chromiumos/tast/ctxutil"
"chromiumos/tast/dut"
"chromiumos/tast/errors"
"chromiumos/tast/remote/powercontrol"
"chromiumos/tast/ssh/linuxssh"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
type extendedDisplayFunctionTestParams struct {
powerMode string
ecStateToCheck string
expectedPrevSleepState int
}
func init() {
testing.AddTest(&testing.Test{
Func: ExtendedDisplayFunctionality,
Desc: "Verifies type-C extended display functionality before and after performing cold boot and warm boot",
Contacts: []string{"ambalavanan.m.m@intel.com", "intel-chrome-system-automation-team@intel.com"},
SoftwareDeps: []string{"chrome", "reboot"},
ServiceDeps: []string{"tast.cros.security.BootLockboxService"},
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.InternalDisplay()),
Timeout: 5 * time.Minute,
Vars: []string{
"servo",
"power.mode", // Optional. Expecting "tablet". By default power.mode will be "clamshell".
},
Params: []testing.Param{{
Name: "typec_hdmi_shutdown",
Val: extendedDisplayFunctionTestParams{
powerMode: "shutdown_command",
ecStateToCheck: "S5",
expectedPrevSleepState: 5,
},
}, {
Name: "typec_hdmi_reboot",
Val: extendedDisplayFunctionTestParams{
powerMode: "reboot_command",
expectedPrevSleepState: 0,
},
}, {
Name: "typec_hdmi_powerbtn",
Val: extendedDisplayFunctionTestParams{
powerMode: "powerbtn_shutdown",
ecStateToCheck: "S5",
expectedPrevSleepState: 5,
},
}},
})
}
func ExtendedDisplayFunctionality(ctx context.Context, s *testing.State) {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 2*time.Minute)
defer cancel()
servoSpec, _ := s.Var("servo")
dut := s.DUT()
testOpt := s.Param().(extendedDisplayFunctionTestParams)
pxy, err := servo.NewProxy(ctx, servoSpec, dut.KeyFile(), dut.KeyDir())
if err != nil {
s.Fatal("Failed to connect to servo: ", err)
}
defer pxy.Close(cleanupCtx)
// Get the initial tablet_mode_angle settings to restore at the end of test.
re := regexp.MustCompile(`tablet_mode_angle=(\d+) hys=(\d+)`)
tabletOut, err := dut.Conn().CommandContext(ctx, "ectool", "motionsense", "tablet_mode_angle").Output()
if err != nil {
s.Fatal("Failed to retrieve tablet_mode_angle settings: ", err)
}
m := re.FindSubmatch(tabletOut)
if len(m) != 3 {
s.Fatalf("Failed to get initial tablet_mode_angle settings: got submatches %+v", m)
}
initLidAngle := m[1]
initHys := m[2]
defaultMode := "clamshell"
if mode, ok := s.Var("power.mode"); ok {
defaultMode = mode
}
if defaultMode == "tablet" {
// Set tabletModeAngle to 0 to force the DUT into tablet mode.
testing.ContextLog(ctx, "Put DUT into tablet mode")
if err := dut.Conn().CommandContext(ctx, "ectool", "motionsense", "tablet_mode_angle", "0", "0").Run(); err != nil {
s.Fatal("Failed to set DUT into tablet mode: ", err)
}
}
defer func(ctx context.Context) {
testing.ContextLog(ctx, "Performing cleanup")
if !dut.Connected(ctx) {
if err := powercontrol.PowerOntoDUT(ctx, pxy, dut); err != nil {
s.Fatal("Failed to power on DUT at cleanup: ", err)
}
}
if err := dut.Conn().CommandContext(ctx, "ectool", "motionsense", "tablet_mode_angle", string(initLidAngle), string(initHys)).Run(); err != nil {
s.Fatal("Failed to restore tablet_mode_angle to the original settings: ", err)
}
}(cleanupCtx)
// Perform a Chrome login.
s.Log("Login to Chrome")
if err := powercontrol.ChromeOSLogin(ctx, dut, s.RPCHint()); err != nil {
s.Fatal("Failed to login to chrome: ", err)
}
// Verifying external display detection before shutdown/reboot.
if err := externalTypecDisplayDetection(ctx, dut, 1); err != nil {
s.Fatalf("Failed detecting external display before %q: %v", testOpt.powerMode, err)
}
if testOpt.powerMode == "shutdown_command" {
testing.ContextLog(ctx, "Executing shutdown command")
if err := powercontrol.ShutdownAndWaitForPowerState(ctx, pxy, dut, testOpt.ecStateToCheck); err != nil {
s.Fatal("Failed to shutdown DUT and check EC state: ", err)
}
}
if testOpt.powerMode == "reboot_command" {
testing.ContextLog(ctx, "Rebooting the DUT")
if err := performReboot(ctx, dut); err != nil {
s.Fatal("Failed to perform DUT reboot: ", err)
}
}
if testOpt.powerMode == "typec_hdmi_powerbtn" {
testing.ContextLog(ctx, "Shutdown DUT with power button long press via servo")
if err := shutdownWithPowerButtonViaServo(ctx, pxy, dut); err != nil {
s.Fatal("Failed to shutdown DUT with power long press via servo: ", err)
}
if err := verifyECPowerState(ctx, pxy, testOpt.ecStateToCheck); err != nil {
s.Fatal("Failed to check EC power state: ", err)
}
}
if testOpt.ecStateToCheck != "" {
// Performing power normal press to power ON DUT.
if err := powercontrol.PowerOntoDUT(ctx, pxy, dut); err != nil {
s.Fatal("Failed to power on DUT: ", err)
}
}
// Verifying external display detection after shutdown/reboot.
if err := externalTypecDisplayDetection(ctx, dut, 1); err != nil {
s.Fatalf("Failed detecting external display after %q: %v", testOpt.powerMode, err)
}
// Perfoming prev_sleep_state check.
if err := powercontrol.ValidatePrevSleepState(ctx, dut, testOpt.expectedPrevSleepState); err != nil {
s.Fatal("Failed to validate previous sleep state: ", err)
}
}
// externalTypecDisplayDetection verifies extended display is connected through typec adapter or not.
func externalTypecDisplayDetection(ctx context.Context, dut *dut.DUT, numberOfDisplays int) error {
// Checking whether typec adapter connected to DUT.
lsbOut, err := dut.Conn().CommandContext(ctx, "lsusb", "-t").Output()
if err != nil {
return errors.Wrap(err, "failed to execute lsusb command")
}
usbDetectionRe := regexp.MustCompile(`If 0.*Class=.*5000M`)
if !usbDetectionRe.MatchString(string(lsbOut)) {
return errors.New("failed to detect typec adapter")
}
var (
displayInfoRe = regexp.MustCompile(`.*pipe\s+[BCD]\]:\n.*active=yes, mode=.[0-9]+x[0-9]+.: [0-9]+.*\s+[hw: active=yes]+`)
connectorInfoRe = regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[DP]+.*`)
connectedStatusRe = regexp.MustCompile(`\[CONNECTOR:\d+:DP.*status: connected`)
modesRe = regexp.MustCompile(`modes:\n.*"1920x1080":.60`)
)
displayInfoFile := "/sys/kernel/debug/dri/0/i915_display_info"
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := linuxssh.ReadFile(ctx, dut.Conn(), displayInfoFile)
if err != nil {
return errors.Wrapf(err, "failed to read %q file", displayInfoFile)
}
matchedString := displayInfoRe.FindAllString(string(out), -1)
if len(matchedString) != numberOfDisplays {
return errors.New("connected external display info not found")
}
displayInfoPatterns := []*regexp.Regexp{connectorInfoRe, connectedStatusRe, modesRe}
for _, pattern := range displayInfoPatterns {
if !pattern.MatchString(string(out)) {
return errors.Errorf("failed to find display info match %q", pattern)
}
}
return nil
}, &testing.PollOptions{
Timeout: 20 * time.Second,
}); err != nil {
return errors.Wrap(err, "unable to find external display")
}
return nil
}
// shutdownWithPowerButtonViaServo performs shutdown DUT with power button long press via servo.
func shutdownWithPowerButtonViaServo(ctx context.Context, pxy *servo.Proxy, dut *dut.DUT) error {
if err := pxy.Servo().KeypressWithDuration(ctx, servo.PowerKey, servo.DurLongPress); err != nil {
return errors.Wrap(err, "failed to power long press")
}
sdCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
if err := dut.WaitUnreachable(sdCtx); err != nil {
return errors.Wrap(err, "failed to wait DUT to be unreachable")
}
return nil
}
// verifyECPowerState verifies EC powerState of DUT.
func verifyECPowerState(ctx context.Context, pxy *servo.Proxy, powerState string) error {
return testing.Poll(ctx, func(ctx context.Context) error {
got, err := pxy.Servo().GetECSystemPowerState(ctx)
if err != nil {
return errors.Wrap(err, "failed to get EC power state")
}
if want := powerState; got != want {
return errors.Errorf("unexpected DUT EC power state = got %q, want %q", got, want)
}
return nil
}, &testing.PollOptions{Timeout: 20 * time.Second})
}
// performReboot performs DUT reboot.
func performReboot(ctx context.Context, dut *dut.DUT) error {
if err := dut.Reboot(ctx); err != nil {
return errors.Wrap(err, "failed to reboot the DUT")
}
waitCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
if err := dut.WaitConnect(waitCtx); err != nil {
return errors.Wrap(err, "failed to wait connect to DUT")
}
return nil
}