blob: 331f47df6f35eff2a102c6703f4a97e6143b92f8 [file] [log] [blame]
// Copyright 2020 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 (
"context"
"fmt"
"regexp"
"strconv"
"time"
gossh "golang.org/x/crypto/ssh"
fwCommon "chromiumos/tast/common/firmware"
"chromiumos/tast/common/servo"
"chromiumos/tast/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
"chromiumos/tast/remote/firmware/reporters"
"chromiumos/tast/ssh"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
// eventLogParams contains all the data needed to run a single test iteration.
type eventLogParams struct {
resetType firmware.ResetType
bootToMode fwCommon.BootMode
suspendResume bool
suspendToIdle string
hardwareWatchdog bool
// All of the regexes in one of the sets must be present. Ex.
// [][]string{[]string{`Case 1A`, `Case 1B`}, []string{`Case 2A`, `Case 2[BC]`}}
// Any of these events would pass:
// Case 1A, Case 1B
// Case 2A, Case 2B
// Case 2A, Case 2C
requiredEventSets [][]string
prohibitedEvents string
allowedEvents string
}
func init() {
testing.AddTest(&testing.Test{
Func: Eventlog,
Desc: "Ensure that eventlog is written on boot and suspend/resume",
Contacts: []string{
"gredelston@google.com", // Test author.
"cros-fw-engprod@google.com",
},
Attr: []string{"group:firmware"},
HardwareDeps: hwdep.D(
// Eventlog is broken/wontfix on veyron devices.
// See http://b/35585376#comment14 for more info.
hwdep.SkipOnPlatform("veyron_fievel"),
hwdep.SkipOnPlatform("veyron_tiger"),
),
SoftwareDeps: []string{"crossystem", "flashrom"},
Vars: []string{"firmware.skipFlashUSB"},
Params: []testing.Param{
// Test eventlog upon normal->normal reboot.
{
Name: "normal",
ExtraAttr: []string{"firmware_unstable"},
ExtraHardwareDeps: hwdep.D(hwdep.SkipOnModel("leona")),
Fixture: fixture.NormalMode,
Val: eventLogParams{
resetType: firmware.WarmReset,
requiredEventSets: [][]string{{`System boot`}},
prohibitedEvents: `Developer Mode|Recovery Mode|Sleep| Wake`,
},
},
{
// Allow some normally disallowed events on leona. b/184778308
Name: "leona_normal",
ExtraAttr: []string{"firmware_ec"},
ExtraHardwareDeps: hwdep.D(hwdep.Model("leona")),
Fixture: fixture.NormalMode,
Val: eventLogParams{
resetType: firmware.WarmReset,
requiredEventSets: [][]string{{`System boot`}},
prohibitedEvents: `Developer Mode|Recovery Mode|Sleep| Wake`,
allowedEvents: `^ACPI Wake \| Deep S5$`,
},
},
// Test eventlog upon dev->dev reboot.
{
Name: "dev",
ExtraAttr: []string{"firmware_ec"},
ExtraHardwareDeps: hwdep.D(hwdep.SkipOnModel("leona")),
Fixture: fixture.DevModeGBB,
Val: eventLogParams{
resetType: firmware.WarmReset,
requiredEventSets: [][]string{{`System boot`, `Chrome OS Developer Mode`}},
prohibitedEvents: `Recovery Mode|Sleep| Wake`,
},
},
// Allow some normally disallowed events on leona. b/184778308
{
Name: "leona_dev",
ExtraAttr: []string{"firmware_ec"},
ExtraHardwareDeps: hwdep.D(hwdep.Model("leona")),
Fixture: fixture.DevModeGBB,
Val: eventLogParams{
resetType: firmware.WarmReset,
requiredEventSets: [][]string{{`System boot`, `Chrome OS Developer Mode`}},
prohibitedEvents: `Recovery Mode|Sleep| Wake`,
allowedEvents: `^ACPI Wake \| Deep S5$`,
},
},
// Test eventlog upon normal->rec reboot.
{
Name: "normal_rec",
ExtraAttr: []string{"firmware_unstable", "firmware_usb"},
Fixture: fixture.NormalMode,
Val: eventLogParams{
bootToMode: fwCommon.BootModeRecovery,
requiredEventSets: [][]string{{`System boot`, `Chrome OS Recovery Mode \| Recovery Button`}},
prohibitedEvents: `Developer Mode|Sleep|FW Wake|ACPI Wake \| S3`,
},
Timeout: 60 * time.Minute,
},
// Test eventlog upon rec->normal reboot.
{
Name: "rec_normal",
ExtraAttr: []string{"firmware_unstable", "firmware_usb"},
Fixture: fixture.RecModeNoServices,
Val: eventLogParams{
bootToMode: fwCommon.BootModeNormal,
requiredEventSets: [][]string{{`System boot`}},
prohibitedEvents: `Developer Mode|Recovery Mode|Sleep`,
},
Timeout: 6 * time.Minute,
},
// Test eventlog upon suspend/resume w/ default value of suspend_to_idle.
// treeya: ACPI Enter | S3, EC Event | Power Button, ACPI Wake | S3, Wake Source | Power Button | 0
// kindred: S0ix Enter, S0ix Exit, Wake Source | Power Button | 0, EC Event | Power Button
// leona: S0ix Enter, S0ix Exit, Wake Source | Power Button | 0, EC Event | Power Button
// eldrid: S0ix Enter, S0ix Exit, Wake Source | Power Button | 0, EC Event | Power Button
// hayato: Sleep, Wake
{
Name: "suspend_resume",
ExtraAttr: []string{"firmware_unstable"},
Fixture: fixture.NormalMode,
Val: eventLogParams{
suspendResume: true,
requiredEventSets: [][]string{
{`Sleep`, `^Wake`},
{`ACPI Enter \| S3`, `ACPI Wake \| S3`},
{`S0ix Enter`, `S0ix Exit`},
},
prohibitedEvents: `System |Developer Mode|Recovery Mode`,
},
},
// Test eventlog upon suspend/resume w/ suspend_to_idle.
// On supported machines, this should go to S0ix or stay in S0.
// x86 duts: S0ix Enter, S0ix Exit, Wake Source | Power Button | 0, EC Event | Power Button
// hayato: FAIL Sleep, System boot
// treeya: FAIL Nothing logged
{
Name: "suspend_resume_idle",
ExtraAttr: []string{"firmware_unstable"},
Fixture: fixture.NormalMode,
Val: eventLogParams{
suspendResume: true,
suspendToIdle: "1",
requiredEventSets: [][]string{
{`Sleep`, `^Wake`},
{`S0ix Enter`, `S0ix Exit`},
},
prohibitedEvents: `System |Developer Mode|Recovery Mode`,
},
},
// Test eventlog upon suspend/resume w/o suspend_to_idle.
// This should power down all the way to S3.
// eldrid: FAIL ACPI Enter | S3, EC Event | Power Button, ACPI Wake | S3, Wake Source | Power Button | 0 -> Gets stuck and doesn't boot.
// hayato: Sleep, Wake
// x86 duts: ACPI Enter | S3, EC Event | Power Button, ACPI Wake | S3, Wake Source | Power Button | 0
{
Name: "suspend_resume_noidle",
ExtraAttr: []string{"firmware_unstable"},
Fixture: fixture.NormalMode,
Val: eventLogParams{
suspendResume: true,
suspendToIdle: "0",
requiredEventSets: [][]string{
{`Sleep`, `^Wake`},
{`ACPI Enter \| S3`, `ACPI Wake \| S3`},
},
prohibitedEvents: `System |Developer Mode|Recovery Mode`,
},
},
// Test eventlog with hardware watchdog.
{
Name: "watchdog",
ExtraAttr: []string{"firmware_ec"},
Fixture: fixture.NormalMode,
ExtraSoftwareDeps: []string{"watchdog"},
Val: eventLogParams{
hardwareWatchdog: true,
requiredEventSets: [][]string{
{`System boot|Hardware watchdog reset`},
},
},
},
},
})
}
// eventMessagesContainReMatch returns true if any event's message matches the regexp.
func eventMessagesContainReMatch(ctx context.Context, events []reporters.Event, re *regexp.Regexp) bool {
for _, event := range events {
if re.MatchString(event.Message) {
return true
}
}
return false
}
func Eventlog(ctx context.Context, s *testing.State) {
// Create mode-switcher.
v := s.FixtValue().(*fixture.Value)
h := v.Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to connect to servod")
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Creating mode switcher: ", err)
}
r := h.Reporter
param := s.Param().(eventLogParams)
var cutoffEvent reporters.Event
oldEvents, err := r.EventlogList(ctx)
if err != nil {
s.Fatal("Finding last event: ", err)
}
if len(oldEvents) > 0 {
cutoffEvent = oldEvents[len(oldEvents)-1]
s.Log("Found previous event: ", cutoffEvent)
}
if param.resetType != "" {
if err := ms.ModeAwareReboot(ctx, param.resetType); err != nil {
s.Fatal("Error resetting DUT: ", err)
}
} else if param.bootToMode != "" {
// If booting into recovery, check the USB Key.
if param.bootToMode == fwCommon.BootModeRecovery {
skipFlashUSB := false
if skipFlashUSBStr, ok := s.Var("firmware.skipFlashUSB"); ok {
skipFlashUSB, err = strconv.ParseBool(skipFlashUSBStr)
if err != nil {
s.Fatalf("Invalid value for var firmware.skipFlashUSB: got %q, want true/false", skipFlashUSBStr)
}
}
cs := s.CloudStorage()
if skipFlashUSB {
cs = nil
}
if err := h.SetupUSBKey(ctx, cs); err != nil {
s.Fatal("USBKey not working: ", err)
}
}
if err := ms.RebootToMode(ctx, param.bootToMode); err != nil {
s.Fatalf("Error during transition to %s: %+v", param.bootToMode, err)
}
} else if param.suspendResume {
if err := h.Servo.WatchdogRemove(ctx, servo.WatchdogCCD); err != nil {
s.Error("Failed to remove watchdog for ccd: ", err)
}
if param.suspendToIdle != "" {
if err := h.DUT.Conn().CommandContext(ctx, "sh", "-c", fmt.Sprintf(
"mkdir -p /tmp/power_manager && "+
"echo %q > /tmp/power_manager/suspend_to_idle && "+
"mount --bind /tmp/power_manager /var/lib/power_manager && "+
"restart powerd", param.suspendToIdle),
).Run(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to set suspend to idle: ", err)
}
defer func(ctx context.Context) {
if err := h.DUT.Conn().CommandContext(ctx, "sh", "-c",
"umount /var/lib/power_manager && restart powerd",
).Run(ssh.DumpLogOnError); err != nil {
s.Log("Failed to restore powerd settings: ", err)
}
}(ctx)
// Suspend will fail right after restarting powerd.
testing.Sleep(ctx, 2*time.Second)
}
h.CloseRPCConnection(ctx)
s.Log("Suspending DUT")
shortCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
if err := h.DUT.Conn().CommandContext(shortCtx, "powerd_dbus_suspend").Run(ssh.DumpLogOnError); err != nil &&
!errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, &gossh.ExitMissingError{}) {
s.Fatal("Failed to suspend: ", err)
}
// Let the DUT stay in suspend a little while. 10s seems to be enough to allow wake up. Shorter times might work also.
if err := testing.Sleep(ctx, 10*time.Second); err != nil {
s.Fatal("Failed to sleep: ", err)
}
powerState, err := h.Servo.GetECSystemPowerState(ctx)
if err != nil {
s.Error("Failed to get power state: ", err)
}
s.Log("Power state: ", powerState)
s.Log("Pressing ENTER key to wake DUT")
if err := h.Servo.KeypressWithDuration(ctx, servo.Enter, servo.DurTab); err != nil {
s.Fatal("Failed to press enter key")
}
s.Log("Reconnecting to DUT")
shortCtx, cancel = context.WithTimeout(ctx, 60*time.Second)
defer cancel()
if err := h.WaitConnect(shortCtx); err != nil {
s.Fatal("Failed to reconnect to DUT: ", err)
}
s.Log("Reconnected to DUT")
} else if param.hardwareWatchdog {
if err := h.Servo.WatchdogRemove(ctx, servo.WatchdogCCD); err != nil {
s.Error("Failed to remove watchdog for ccd: ", err)
}
// Daisydog is the watchdog service.
cmd := `nohup sh -c 'sleep 2
sync
stop daisydog
sleep 60 > /dev/watchdog' >/dev/null 2>&1 </dev/null &`
if err := h.DUT.Conn().CommandContext(ctx, "bash", "-c", cmd).Run(); err != nil {
s.Fatal("Failed to panic DUT: ", err)
}
s.Log("Waiting for DUT to become unreachable")
h.CloseRPCConnection(ctx)
if err := h.DUT.WaitUnreachable(ctx); err != nil {
s.Fatal("Failed to wait for DUT to become unreachable: ", err)
}
s.Log("DUT became unreachable (as expected)")
s.Log("Reconnecting to DUT")
shortCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
if err := h.WaitConnect(shortCtx); err != nil {
s.Fatal("Failed to reconnect to DUT: ", err)
}
s.Log("Reconnected to DUT")
}
events, err := r.EventlogListAfter(ctx, cutoffEvent)
if err != nil {
s.Fatal("Gathering events: ", err)
}
for _, event := range events {
s.Log("Found event: ", event)
}
// Complicated rules here.
// One of the param.requiredEventSets must be found.
// Within that event set, all the regexs need to match to be considered found.
requiredEventsFound := false
for _, requiredEventSet := range param.requiredEventSets {
foundAllRequiredEventsInSet := true
for _, requiredEvent := range requiredEventSet {
reRequiredEvent := regexp.MustCompile(requiredEvent)
if !eventMessagesContainReMatch(ctx, events, reRequiredEvent) {
foundAllRequiredEventsInSet = false
break
}
}
if foundAllRequiredEventsInSet {
requiredEventsFound = true
break
}
}
if !requiredEventsFound {
s.Error("Required event missing")
}
if param.prohibitedEvents != "" {
reProhibitedEvents := regexp.MustCompile(param.prohibitedEvents)
var allowedRe *regexp.Regexp
if param.allowedEvents != "" {
allowedRe = regexp.MustCompile(param.allowedEvents)
}
for _, event := range events {
if reProhibitedEvents.MatchString(event.Message) && (allowedRe == nil || !allowedRe.MatchString(event.Message)) {
s.Errorf("Incorrect event logged: %+v", event)
}
}
}
}