blob: 148e10be94c4acc4126cd852e4ba0c2f067390b9 [file] [log] [blame] [edit]
// Copyright 2019 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 wilco
import (
"bytes"
"context"
"os"
"time"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: ECTelemetry,
Desc: "Checks that telemetry requests to the EC (e.g. hardware temperature or fan state info) work on Wilco devices",
Contacts: []string{
"campello@chromium.org", // Test maintainer.
"chromeos-wilco@google.com", // Possesses some more domain-specific knowledge.
"chromeos-kernel-test@google.com",
},
SoftwareDeps: []string{"wilco"},
Timeout: 10 * time.Second,
Attr: []string{"group:mainline"},
})
}
// ECTelemetry tests the Wilco EC's ability to respond to telemetry
// commands. The Wilco EC is able to return telemetry information (such
// as temperature and fan state) via sysfs: You write a binary command
// to the sysfs file, the kernel driver performs some filtering on the
// command to ensure it is valid, and the EC returns the binary result,
// to be read from the same file. You must keep the file descriptor open
// between the read and write for the response to be kept. This test
// checks for end-to-end communication with the EC, and checks that the
// driver performs some basic filtering and validity checks. Since the
// responses are variable and opaque binary data, it's impractical to
// actually check the values of the responses.
//
// See https://chromium.googlesource.com/chromiumos/third_party/kernel/+/ea0f6a09a7a993fc7c781fd8ca675b29c42d4719/drivers/platform/chrome/wilco_ec/telemetry.c
// for the kernel driver.
func ECTelemetry(ctx context.Context, s *testing.State) {
type errMsg string
// These are intended to be merely human-readable and may not necessarily
// match exactly any real error messages.
const (
noErr errMsg = "NONE"
invalidErr errMsg = "invalid argument"
tooLongErr errMsg = "message too long"
)
type params struct {
// Telemetry command to run.
cmd []byte
// If the command fails, this should be the cause of the error.
expectedErr errMsg
}
const (
telemPath = "/dev/wilco_telem0"
// Number of bytes in a telemetry request and response.
telemetryCommandSize = 32
)
// Send the telemetry command to the EC. Return whether the write succeeded.
writeCommand := func(f *os.File, p params) (success bool) {
_, err := f.Write(p.cmd)
// It would be brittle to check for specific errors, so the best we can do
// is check for the existence of errors.
if err != nil {
if p.expectedErr == noErr {
s.Errorf("Sending command [% #x] failed, but was supposed to succeed: %v", p.cmd, err)
}
return false
}
if err == nil && p.expectedErr != noErr {
s.Errorf("Sending command [% #x] succeded, but was supposed to fail with a %q error", p.cmd, p.expectedErr)
return false
}
return true
}
readAndCheckResult := func(f *os.File, p params) {
var bytes [telemetryCommandSize]byte
n, err := f.Read(bytes[:])
if err != nil {
s.Errorf("Failed to read %s after sending command [% #x]: %v", telemPath, p.cmd, err)
return
}
if n != telemetryCommandSize {
s.Errorf("Read %v bytes from %v after sending command [% #x]; needed to read %v", n, telemPath, p.cmd, telemetryCommandSize)
return
}
// The result of telemetry commands is not deterministic, so the best we
// can do is check that there is at least something non-zero in there.
// I don't *think* the EC ever returns all 0s as a valid response, but
// perhaps it sometimes does? Please adjust if you encounter flakiness.
for _, b := range bytes {
if b != 0 {
return
}
}
s.Fatalf("Bytes returned from command [% #x] were all zero", p.cmd)
}
f, err := os.OpenFile(telemPath, os.O_RDWR, 0644)
if err != nil {
s.Fatalf("Failed to open %s: %v", telemPath, err)
}
defer f.Close()
for _, p := range []params{
// Get various pieces of EC FW version information. For the 3rd byte:
// 0 = label
// 1 = svn_rev
// 2 = model_no
// 3 = build_date
{[]byte{0x38, 0x00, 0x00}, noErr},
{[]byte{0x38, 0x00, 0x01}, noErr},
{[]byte{0x38, 0x00, 0x02}, noErr},
{[]byte{0x38, 0x00, 0x03}, noErr},
// The 2nd byte on all commands is reserved to be 0.
{[]byte{0x38, 0x01, 0x00}, invalidErr},
// Too short.
{[]byte{}, invalidErr},
// Too long for this command.
{[]byte{0x38, 0x00, 0x03, 0x00}, tooLongErr},
// Too long in general. This is 33 bytes, but max is 32.
{append([]byte{0x38}, bytes.Repeat([]byte{0}, telemetryCommandSize)...), tooLongErr},
// Bad first byte, not one of the allowed commands. The list of allowed
// commands are in the kernel driver, linked above.
{[]byte{0x39, 0x00, 0x03}, invalidErr},
// Read the temperature from various sensors.
{[]byte{0x2c, 0x00, 0x00}, noErr},
{[]byte{0x2c, 0x00, 0x01}, noErr},
{[]byte{0x2c, 0x00, 0x02}, noErr},
{[]byte{0x2c, 0x00, 0x03}, noErr},
// Too many bytes for this command.
{[]byte{0x2c, 0x00, 0x03, 0x00}, tooLongErr},
// Get the battery PPID info. 3rd byte should always be 1.
{[]byte{0x8a, 0x00, 0x01}, noErr},
{[]byte{0x8a, 0x00, 0x00}, invalidErr},
{[]byte{0x8a, 0x00, 0x02}, invalidErr},
} {
if writeCommand(f, p) {
readAndCheckResult(f, p)
}
}
}