blob: ee060574e184fe8b4fab2fb2f67be0600140056b [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 power
import (
"context"
"regexp"
"strconv"
"time"
"chromiumos/tast/common/chameleon"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/graphics"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: HdmiSuspendResume,
Desc: "Verifies HDMI native port functionality with suspend-resume cycles",
Contacts: []string{"pathan.jilani@intel.com", "intel-chrome-system-automation-team@intel.com"},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome"},
Vars: []string{
"power.chameleon_addr",
"power.chameleon_display_port",
},
Pre: chrome.LoggedIn(),
Timeout: 8 * time.Minute,
})
}
func HdmiSuspendResume(ctx context.Context, s *testing.State) {
var (
C10PkgPattern = regexp.MustCompile(`C10 : ([A-Za-z0-9]+)`)
connectorInfoPtrns = regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[HDMI]+.*`)
connectedPtrns = regexp.MustCompile(`\[CONNECTOR:\d+:HDMI.*status: connected`)
modesPtrns = regexp.MustCompile(`modes:\n.*"1920x1080":.60`)
SuspndFailurePattern = regexp.MustCompile("Suspend failures: 0")
FrmwreLogErrorPattern = regexp.MustCompile("Firmware log errors: 0")
S0ixErrorPattern = regexp.MustCompile("s0ix errors: 0")
)
const (
SlpS0Cmd = "cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec"
PkgCstateCmd = "cat /sys/kernel/debug/pmc_core/package_cstate_show"
SuspendStressCmd = "suspend_stress_test -c 10"
)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 3*time.Second)
defer cancel()
if chameleonAddr, ok := s.Var("power.chameleon_addr"); ok {
// Use chameleon board as extended display. Make sure chameleon is connected.
che, err := chameleon.New(ctx, chameleonAddr)
if err != nil {
s.Fatal("Failed to connect to chameleon board: ", err)
}
defer che.Close(cleanupCtx)
portID := 3 // Use default port 3 for display.
if port, ok := s.Var("power.chameleon_display_port"); ok {
portID, err = strconv.Atoi(port)
if err != nil {
s.Fatalf("Failed to parse chameleon display port %q: %v", port, err)
}
}
dp, err := che.NewPort(ctx, portID)
if err != nil {
s.Fatalf("Failed to create chameleon port %d: %v", portID, err)
}
if err := dp.Plug(ctx); err != nil {
s.Fatal("Failed to plug chameleon port: ", err)
}
s.Log("chameleon plugged successfully")
defer dp.Unplug(cleanupCtx)
// Wait for DUT to detect external display.
if err := dp.WaitVideoInputStable(ctx, 10*time.Second); err != nil {
s.Fatal("Failed to wait for video input on chameleon board: ", err)
}
}
displayInfoPatterns := []*regexp.Regexp{connectorInfoPtrns, connectedPtrns, modesPtrns}
if err := externalDisplayValidaion(ctx, 1, displayInfoPatterns); err != nil {
s.Fatal("Failed plugging external display: ", err)
}
cmdOutput := func(cmd string) string {
s.Logf("Executing command: %s", cmd)
out, err := testexec.CommandContext(ctx, "sh", "-c", cmd).Output()
if err != nil {
s.Fatalf("Failed to execute %s command: %v", cmd, err)
}
return string(out)
}
slpOpSetPre := cmdOutput(SlpS0Cmd)
pkgOpSetOutput := cmdOutput(PkgCstateCmd)
matchSetPre := (C10PkgPattern).FindStringSubmatch(pkgOpSetOutput)
if matchSetPre == nil {
s.Fatal("Failed to match pre PkgCstate value: ", pkgOpSetOutput)
}
pkgOpSetPre := matchSetPre[1]
stressOut := cmdOutput(SuspendStressCmd)
suspendErrors := []*regexp.Regexp{SuspndFailurePattern, FrmwreLogErrorPattern, S0ixErrorPattern}
for _, errmsg := range suspendErrors {
if !(errmsg).MatchString(string(stressOut)) {
s.Fatalf("Failed expected%q failures are non-zero", errmsg)
}
}
if err := externalDisplayValidaion(ctx, 1, displayInfoPatterns); err != nil {
s.Fatal("Failed external display is not connected after suspend-resume: ", err)
}
slpOpSetPost := cmdOutput(SlpS0Cmd)
if slpOpSetPre == slpOpSetPost {
s.Fatalf("Failed SLP counter value must be different than the value %q noted most recently %q", slpOpSetPre, slpOpSetPost)
}
if slpOpSetPost == "0" {
s.Fatal("Failed SLP counter value must be non-zero, noted is: ", slpOpSetPost)
}
pkgOpSetPostOutput := cmdOutput(PkgCstateCmd)
matchSetPost := (C10PkgPattern).FindStringSubmatch(pkgOpSetPostOutput)
if matchSetPost == nil {
s.Fatal("Failed to match post PkgCstate value: ", pkgOpSetPostOutput)
}
pkgOpSetPost := matchSetPost[1]
if pkgOpSetPre == pkgOpSetPost {
s.Fatalf("Failed Package C10 value %q must be different than value noted earlier %q", pkgOpSetPre, pkgOpSetPost)
}
if pkgOpSetPost == "0x0" || pkgOpSetPost == "0" {
s.Fatal("Failed Package C10 should be non-zero")
}
if err := externalDisplayValidaion(ctx, 1, displayInfoPatterns); err != nil {
s.Fatal("Failed to check plug status of external display: ", err)
}
}
func externalDisplayValidaion(ctx context.Context, numberOfDisplays int, regexpPatterns []*regexp.Regexp) error {
const DisplayInfoCommand = "cat /sys/kernel/debug/dri/0/i915_display_info"
displCount, err := graphics.NumberOfOutputsConnected(ctx)
if err != nil {
return errors.Wrap(err, "failed to get connected displays ")
}
var DisplayInfo = regexp.MustCompile(`.*pipe\s+[BCD]\]:\n.*active=yes, mode=.[0-9]+x[0-9]+.: [0-9]+.*\s+[hw: active=yes]+`)
if displCount < 2 {
return errors.New("external display is not connected")
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := testexec.CommandContext(ctx, "sh", "-c", DisplayInfoCommand).Output()
if err != nil {
return errors.Wrap(err, "failed to run display info command ")
}
matchedString := (DisplayInfo).FindAllString(string(out), -1)
if len(matchedString) != numberOfDisplays {
return errors.New("connected external display info not found")
}
if regexpPatterns != nil {
for _, pattern := range regexpPatterns {
if !(pattern).MatchString(string(out)) {
return errors.Errorf("failed %q error message", pattern)
}
}
}
return nil
}, &testing.PollOptions{
Timeout: 15 * time.Second,
}); err != nil {
return errors.Wrap(err, "please connect external display as required")
}
return nil
}