blob: 0e2c4e17fa1a3b55e77c22d62c700f97976ac963 [file]
// 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 (
"context"
"fmt"
"strconv"
"time"
"go.chromium.org/tast-tests/cros/common/servo"
"go.chromium.org/tast-tests/cros/common/tbdep"
"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 ecUsbPortTest int
const (
testUSBOnLidClose ecUsbPortTest = iota
testUSBOnShutdown
)
func init() {
testing.AddTest(&testing.Test{
Func: ECUSBPorts,
Desc: "Verify usb ports stop read/write after DUT shuts down",
Contacts: []string{
"chromeos-faft@google.com",
"tij@google.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
TestBedDeps: tbdep.ServoPresentAndWorking,
Attr: []string{"group:firmware", "firmware_ec", "firmware_stressed", "firmware_meets_kpi", "firmware_ec_ro", "firmware_ec_rw"},
HardwareDeps: hwdep.D(hwdep.ChromeEC()),
Fixture: fixture.NormalMode,
Timeout: 5 * time.Minute,
Params: []testing.Param{
{
Name: "usb_pins_on_lid_close",
Val: testUSBOnLidClose,
ExtraHardwareDeps: hwdep.D(hwdep.Lid()),
},
{
Name: "usb_pins_on_shutdown",
Val: testUSBOnShutdown,
},
},
})
}
const (
// Output from ec console for gpioget or ioexget looks like:
// "0* EN_USB_A0_5V" for gpio, or "1* O H EN_USB_A0_5V" for ioex.
reECUSBPortGet string = `(?i)(0|1)[^\r\n]*%s`
usbPortStatePollTimeout time.Duration = 15 * time.Second
usbPortStatePollInterval time.Duration = 5 * time.Second
)
func ECUSBPorts(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.RequireConfig(ctx); err != nil {
s.Fatal("Failed to connect to servo: ", err)
}
enablePins, err := getUSBPorts(ctx, h)
if err != nil {
s.Fatal("Failed to probe usb ports: ", err)
}
// If device has no USB A ports, there is no reason to run the full test.
if len(enablePins) == 0 {
s.Log("No USB A ports to test")
return
}
s.Log("Check that ports are initially enabled")
if err := testing.Poll(ctx, func(ctx context.Context) error {
if err := checkUSBAPortEnabled(ctx, h, enablePins, 1); err != nil {
return errors.Wrap(err, "failed to check usb ports")
}
return nil
}, &testing.PollOptions{Timeout: usbPortStatePollTimeout, Interval: usbPortStatePollInterval}); err != nil {
s.Fatal("Expected USB Ports to be enabled: ", err)
}
switch s.Param().(ecUsbPortTest) {
case testUSBOnShutdown:
if err := testPortsAfterShutdown(ctx, h, enablePins); err != nil {
s.Fatal("Some USB Ports enabled after shutdown: ", err)
}
defer func() {
s.Log("Reset DUT after test end in case it was left powered off")
if err := h.Servo.SetPowerState(ctx, servo.PowerStateReset); err != nil {
s.Fatal("Failed to make sure DUT is booted after test end: ", err)
}
if err := h.WaitConnect(ctx); err != nil {
s.Fatal("Failed to reconnect to DUT after test end: ", err)
}
}()
case testUSBOnLidClose:
// Restart UI to ensure no user is logged in, as this will change power state behaviour on lid close.
if err := h.DUT.Conn().CommandContext(ctx, "restart", "ui").Run(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to restart ui before test: ", err)
}
if err := testPortsAfterLidClose(ctx, h, enablePins); err != nil {
s.Fatal("Some USB Ports enabled after lidclose: ", err)
}
defer func() {
s.Log("Reopen DUT lid in case it was left closed at test end")
if err := h.Servo.OpenLid(ctx); err != nil {
s.Fatal("Failed to make sure lid is open after test end: ", err)
}
}()
}
s.Log("Poll for USB ports re enabled")
if err := testing.Poll(ctx, func(ctx context.Context) error {
if err := checkUSBAPortEnabled(ctx, h, enablePins, 1); err != nil {
return errors.Wrap(err, "failed to check usb ports")
}
return nil
}, &testing.PollOptions{Timeout: usbPortStatePollTimeout, Interval: usbPortStatePollInterval}); err != nil {
s.Fatal("Not all usb ports enabled after booting again: ", err)
}
}
func testPortsAfterLidClose(ctx context.Context, h *firmware.Helper, enablePins []firmware.USBEnablePin) error {
if err := h.Servo.CloseLid(ctx); err != nil {
return errors.Wrap(err, "failed to close lid")
}
testing.ContextLog(ctx, "Check for G3 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "G3"); err != nil {
return errors.Wrap(err, "failed to get G3 powerstate")
}
testing.ContextLog(ctx, "Poll for disabled USB ports")
if err := testing.Poll(ctx, func(ctx context.Context) error {
if err := checkUSBAPortEnabled(ctx, h, enablePins, 0); err != nil {
return errors.Wrap(err, "failed to check usb ports")
}
return nil
}, &testing.PollOptions{Timeout: usbPortStatePollTimeout, Interval: usbPortStatePollInterval}); err != nil {
return errors.Wrap(err, "not all usb ports disabled")
}
if err := h.Servo.OpenLid(ctx); err != nil {
return errors.Wrap(err, "failed to open lid")
}
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.WaitConnect(ctx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT after restarting")
}
return nil
}
func testPortsAfterShutdown(ctx context.Context, h *firmware.Helper, enablePins []firmware.USBEnablePin) error {
testing.ContextLog(ctx, "Shut down DUT")
cmd := h.DUT.Conn().CommandContext(ctx, "/sbin/shutdown", "-P", "now")
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to shut down DUT")
}
testing.ContextLog(ctx, "Check for G3 powerstate")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, firmware.PowerStateTimeout, "G3"); err != nil {
return errors.Wrap(err, "failed to get G3 powerstate")
}
testing.ContextLog(ctx, "Poll for disabled USB ports")
if err := testing.Poll(ctx, func(ctx context.Context) error {
if err := checkUSBAPortEnabled(ctx, h, enablePins, 0); err != nil {
return errors.Wrap(err, "failed to check usb ports")
}
return nil
}, &testing.PollOptions{Timeout: usbPortStatePollTimeout, Interval: usbPortStatePollInterval}); err != nil {
return errors.Wrap(err, "not all usb ports disabled")
}
testing.ContextLog(ctx, "Power DUT back on with short press of the power button")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.DurTab); err != nil {
return errors.Wrap(err, "failed to power on DUT with short press of the power button")
}
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.WaitConnect(ctx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT after restarting")
}
return nil
}
func getUSBPorts(ctx context.Context, h *firmware.Helper) ([]firmware.USBEnablePin, error) {
enablePins := make([]firmware.USBEnablePin, 0)
ec := firmware.NewECTool(h.DUT, firmware.ECToolNameMain)
for _, pin := range h.Config.USBEnablePins {
gpioName := firmware.GpioName(pin.Name)
if !pin.Ioex {
// Probe pin to verify it actually exists.
gpios, err := ec.FindGPIOs(ctx, []firmware.GpioName{gpioName})
if err != nil {
return enablePins, errors.Wrapf(err, "failed to probe for gpio %q", pin.Name)
}
if len(gpios) == 0 {
return enablePins, errors.Errorf("GPIO pin %q defined in fw testing configs but not found in gpio, update configs to reflect this", pin.Name)
}
} else {
matchList := []string{fmt.Sprintf(reECUSBPortGet, pin.Name)}
_, err := h.Servo.RunECCommandGetOutput(ctx, fmt.Sprintf("ioexget %s", pin.Name), matchList)
if err != nil {
return enablePins, errors.Wrapf(err, "IOEX pin %q defined in fw testing configs but not found in ioex, update configs to reflect this", pin.Name)
}
}
enablePins = append(enablePins, pin)
}
// Check for unset value for usb port a count in testing configs.
// Many models with custom enable pins set have not set a port count, ignore in that case.
if h.Config.USBAPortCount == nil && len(h.Config.USBEnablePins) == 0 {
return enablePins, errors.Errorf("USB A port count for model %s set to null (default value) and no custom usb a gpio pins defined. Set to correct amount in fw-testing-configs", h.Model)
}
portsToCheck := 0
if h.Config.USBAPortCount != nil {
portsToCheck = *h.Config.USBAPortCount
}
// If the value is -1 (unknown), we want to check a bunch of numbers manually.
if portsToCheck < 0 {
portsToCheck = 11
}
for i := 1; i <= portsToCheck; i++ {
name := fmt.Sprintf("USB%d_ENABLE", i)
testing.ContextLogf(ctx, "Probing port %q with gpioget", name)
gpios, err := ec.FindGPIOs(ctx, []firmware.GpioName{firmware.GpioName(name)})
if err != nil {
return enablePins, errors.Wrapf(err, "failed to probe for gpio %q", name)
}
if len(gpios) > 0 {
testing.ContextLogf(ctx, "Found usb port: %q with gpioget", name)
enablePins = append(enablePins, firmware.USBEnablePin{Name: name, Ioex: false})
} else if *h.Config.USBAPortCount >= i {
// If port i doesn't exist (regex fails) but it is expected to exist (0 < i <= h.Config.USBAPortCount), raise an error.
return enablePins, errors.Errorf("explicit port count is %d; expected port %d to exist but it does not", h.Config.USBAPortCount, i)
} else {
testing.ContextLogf(ctx, "Did not find port %q with gpioget", name)
}
}
return enablePins, nil
}
func checkUSBAPortEnabled(ctx context.Context, h *firmware.Helper, enablePins []firmware.USBEnablePin, expectedStatusInt int) error {
// Collect errors for all usb ports instead of failing at first.
var unexpectedStatus = map[string]string{}
expectedStatus := strconv.Itoa(expectedStatusInt)
for _, pin := range enablePins {
gpioOrIoex := "gpio"
if pin.Ioex {
gpioOrIoex = "ioex"
}
if pin.ActiveLow {
expectedStatus = strconv.Itoa(1 - expectedStatusInt)
}
testing.ContextLogf(ctx, "Checking status of %q pin name: %q", gpioOrIoex, pin.Name)
cmd := fmt.Sprintf("%sget %s", gpioOrIoex, pin.Name)
matchList := []string{fmt.Sprintf(reECUSBPortGet, pin.Name)}
out, err := h.Servo.RunECCommandGetOutput(ctx, cmd, matchList)
if err != nil {
return errors.Wrapf(err, "failed to run cmd %q, got error", cmd)
}
if out[0][1] != expectedStatus {
unexpectedStatus[pin.Name] = out[0][1]
}
}
if len(unexpectedStatus) != 0 {
failStr := fmt.Sprintf("The following USB Ports didn't have state %q", expectedStatus)
for name, state := range unexpectedStatus {
failStr += fmt.Sprintf(", %q had status %q", name, state)
}
return errors.New(failStr)
}
return nil
}