blob: 69ad723f4d10f70e161a6bd1261084eba31b59ba [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package gscdevboard
import (
"context"
"encoding/hex"
"fmt"
"regexp"
"time"
"github.com/google/go-tpm/tpm2"
"go.chromium.org/tast-tests/cros/common/firmware/ti50"
"go.chromium.org/tast-tests/cros/common/perf"
"go.chromium.org/tast-tests/cros/remote/bundles/cros/gscdevboard/utils"
"go.chromium.org/tast-tests/cros/remote/firmware/ti50/fixture"
"go.chromium.org/tast/core/testing"
)
type throughputTest uint
const (
basicTest throughputTest = iota
endlessCryptoTest
bothDirectionsTest
)
func init() {
testing.AddTest(&testing.Test{
Func: GSCUARTThroughput,
Desc: "Tests forwarding between GSC UARTs and USB at sustained maximum throughput",
Timeout: 5 * time.Minute,
Contacts: []string{
"gsc-sheriff@google.com", // CrOS GSC Developers
"jbk@chromium.org", // Test Author
},
BugComponent: "b:715469", // ChromeOS > Platform > System > Hardware Security > HwSec GSC > Ti50
Attr: []string{"group:gsc",
"gsc_dt_ab", "gsc_dt_shield",
"gsc_image_ti50",
"gsc_nightly"},
Fixture: fixture.GSCOpenCCD,
Params: []testing.Param{{
Name: "basic",
Val: basicTest,
ExtraAttr: []string{"gsc_h1_shield", "gsc_ot_fpga_cw310", "gsc_ot_shield"},
}, {
Name: "endless_crypto",
// Run crypto operations in the background to validate it doesn't interfere with UART operations.
Val: endlessCryptoTest,
}, {
Name: "both_directions",
// Send data in both directions through GSC: UART TX to USB RX, and USB TX to UART RX.
Val: bothDirectionsTest,
}},
})
}
const (
uartThroughputBlockSize int = 64
uartThroughputNumWarmupBlocks int = 16
uartThroughputNumMeasurementBlocks int = 5000
// uartBitsPerByte represents how many bit times it takes to transmit a byte on UART.
uartBitsPerByte int = 10
uartThroughputNominalBps float64 = 115200.0
uartThroughputBpsTolerance float64 = 600.0
// When forwarding in both directions, we only achieve about half of the target.
uartThroughputBothDirectionsBps float64 = 55000.0
)
type consoleChannel struct {
name ti50.UartName
magic byte
// CCD USB endpoint.
ccd ti50.SerialChannel
// UART console.
uart ti50.SerialChannel
}
func GSCUARTThroughput(ctx context.Context, s *testing.State) {
const crLf = "\r\n"
const ecMagic byte = 0xEC
const apMagic byte = 0xA5
const fpmcuMagic byte = 0xF5
var consoles []consoleChannel
b := utils.NewDevboardHelper(s)
gscProps := b.GscProperties()
testOption := s.Param().(throughputTest)
th := utils.FirmwareTestingHelper{FirmwareTestingHelperDelegate: s}
i := ti50.MustOpenCrOSImage(ctx, b, s)
defer i.Close(ctx)
s.Log("(Re)starting ti50")
tpm := b.ResetAndTpmStartup(ctx, i, ti50.CcdSuzyQ, ti50.ServoMicroDisconnected)
// Simulate the AP processor being turned on, in order to enable AP forwarding.
b.GpioSet(ctx, ti50.GpioTi50PltRstL, true)
// Wait until CCD USB shows up and CCD UART TX is enabled.
b.WaitUntilCCDConnectedAndUARTTXEnabled(ctx)
// Set up the EC console.
consoles = append(consoles, consoleChannel{
name: ti50.UartEC,
magic: ecMagic,
ccd: b.CcdSerialInterface(ti50.UartEC, time.Second),
uart: b.PhysicalUart(ti50.UartEC),
})
// Set up the AP console.
consoles = append(consoles, consoleChannel{
name: ti50.UartAP,
magic: apMagic,
ccd: b.CcdSerialInterface(ti50.UartAP, time.Second),
uart: b.PhysicalUart(ti50.UartAP),
})
// Set up the FPMCU console on chips that support it.
if gscProps.HasFpmcuUart() {
consoles = append(consoles, consoleChannel{
name: ti50.UartFPMCU,
magic: fpmcuMagic,
ccd: b.CcdSerialInterface(ti50.UartFPMCU, time.Second),
uart: b.PhysicalUart(ti50.UartFPMCU),
})
}
// Open the CCD and UART interfaces.
for _, console := range consoles {
th.MustSucceed(console.ccd.Open(ctx), fmt.Sprintf("Failed to open %s ccd", console.name))
defer console.ccd.Close(ctx)
th.MustSucceed(console.uart.Open(ctx), fmt.Sprintf("Failed to open %s uart", console.name))
defer console.uart.Close(ctx)
}
// Flush out any "DATA LOST" message along with other queued-up data.
for _, console := range consoles {
console.uart.WriteSerial(ctx, []byte("AB\r\n"))
_, _, err := console.ccd.ReadSerialSubmatch(ctx, regexp.MustCompile(`AB\r\n`))
th.MustSucceed(err, "Error clearing buffer")
th.MustSucceed(console.uart.ClearInput(ctx), "Error clearing buffer")
console.ccd.WriteSerial(ctx, []byte("AB\r\n"))
_, _, err = console.uart.ReadSerialSubmatch(ctx, regexp.MustCompile(`AB\r\n`))
th.MustSucceed(err, "Error clearing buffer")
th.MustSucceed(console.ccd.ClearInput(ctx), "Error clearing buffer")
}
// Start out by sending some initial data on all three UARTS, to fill up the buffers.
var sendBlockNo = 0
var recvBlockNo = 0
for ; sendBlockNo < uartThroughputNumWarmupBlocks; sendBlockNo++ {
// Transmit block on each UART.
for _, console := range consoles {
sendIteration(ctx, s, th, console.uart, console.magic, sendBlockNo)
}
if testOption == bothDirectionsTest {
// Transmit block on each console USB endpoint.
for _, console := range consoles {
sendIteration(ctx, s, th, console.ccd, console.magic+1, sendBlockNo)
}
}
}
// Start a separate goroutine, which will repeatedly create RSA keys in order to load the
// GSC with expensive crypto operations, until told to stop. (Except on OpenTitan, which
// does not yet have hardware cryptolib).
stop := make(chan bool)
done := make(chan bool)
if testOption == endlessCryptoTest {
s.Log("Running crypto in the background")
go endlessCrypto(tpm, s, stop, done)
defer func() {
// Tell the endlessCrypto() goroutine to stop, and wait for it to finish any ongoing
// operation.
stop <- true
<-done
}()
}
// Now, read and verify one 64-byte block of data from each of the three CCD endpoints,
// followed by transmitting yet another block on each UART. This way, we ensure that the
// amount of in-transit data is bounded.
start := time.Now()
for ; recvBlockNo < uartThroughputNumWarmupBlocks+uartThroughputNumMeasurementBlocks; recvBlockNo, sendBlockNo = recvBlockNo+1, sendBlockNo+1 {
// Read a block from each console USB endpoint.
for _, console := range consoles {
recvIteration(ctx, s, th, console.ccd, console.magic, recvBlockNo)
}
// Transmit block on each UART.
for _, console := range consoles {
sendIteration(ctx, s, th, console.uart, console.magic, sendBlockNo)
}
if testOption == bothDirectionsTest {
// Read a block from each UART.
for _, console := range consoles {
recvIteration(ctx, s, th, console.uart, console.magic+1, recvBlockNo)
}
// Transmit block on each console USB endpoint.
for _, console := range consoles {
sendIteration(ctx, s, th, console.ccd, console.magic+1, sendBlockNo)
}
}
}
elapsed := time.Since(start)
var bps = float64(uartThroughputNumMeasurementBlocks*uartThroughputBlockSize*uartBitsPerByte) / elapsed.Seconds()
s.Logf("Effective transfer speed: %.02f kbps", bps/1000)
pv := perf.NewValues()
pv.Set(perf.Metric{
Name: "transfer_speed",
Unit: "bps",
Direction: perf.BiggerIsBetter,
}, bps)
if err := pv.Save(s.OutDir()); err != nil {
s.Error("Failed to save perf data: ", err)
}
if bps > uartThroughputNominalBps+uartThroughputBpsTolerance {
s.Error("Transfer speed too fast, something is not right")
}
var minBps = uartThroughputNominalBps - uartThroughputBpsTolerance
if testOption == bothDirectionsTest {
minBps = uartThroughputBothDirectionsBps
}
if bps < minBps {
s.Error("Transfer speed too slow, HyperDebug may not be performing")
}
}
// recvIteration receives a block of data from one specific UART, verifying that it was as expected.
func recvIteration(ctx context.Context, s *testing.State, th utils.FirmwareTestingHelper, ccd ti50.SerialChannel, magic byte, iteration int) {
databuf, err := ccd.ReadSerialBytes(ctx, uartThroughputBlockSize)
th.MustSucceed(err, "Read error")
// All validation errors reported as "Fatal", in order to avoid thousands of lines of
// error messages, as all future data would fail validation in case of a dropped sequence.
if databuf[0] != byte(iteration) ||
databuf[1] != byte(iteration>>8) ||
databuf[2] != byte(iteration>>16) ||
databuf[3] != byte(iteration>>24) {
s.Fatal("Incorrect sequence number")
}
if databuf[4] != magic {
s.Fatal("Incorrect magic")
}
var idx = 5
for idx < uartThroughputBlockSize {
if databuf[idx] != byte(idx) {
s.Fatalf("Incorrect data contents at %d: %s", idx, hex.EncodeToString(databuf))
}
idx = idx + 1
}
}
// sendIteration sends a block of data on one specific UART.
func sendIteration(ctx context.Context, s *testing.State, th utils.FirmwareTestingHelper, uart ti50.SerialChannel, magic byte, iteration int) {
databuf := make([]byte, uartThroughputBlockSize)
var idx = 0
for idx < uartThroughputBlockSize {
databuf[idx] = byte(idx)
idx = idx + 1
}
databuf[0] = byte(iteration)
databuf[1] = byte(iteration >> 8)
databuf[2] = byte(iteration >> 16)
databuf[3] = byte(iteration >> 24)
databuf[4] = magic
th.MustSucceed(uart.WriteSerial(ctx, databuf), "Write error")
}
func endlessCrypto(tpm *utils.TpmHelper, s *testing.State, stop, done chan bool) {
cp := tpm2.CreatePrimary{
PrimaryHandle: tpm2.TPMRHOwner,
InPublic: tpm2.New2B(
tpm2.TPMTPublic{
Type: tpm2.TPMAlgRSA,
NameAlg: tpm2.TPMAlgSHA1,
ObjectAttributes: tpm2.TPMAObject{
Decrypt: true,
Restricted: true,
FixedTPM: true,
FixedParent: true,
SensitiveDataOrigin: true,
UserWithAuth: true,
},
Parameters: tpm2.NewTPMUPublicParms(tpm2.TPMAlgRSA,
&tpm2.TPMSRSAParms{
Symmetric: tpm2.TPMTSymDefObject{
Algorithm: tpm2.TPMAlgAES,
KeyBits: tpm2.NewTPMUSymKeyBits(
tpm2.TPMAlgAES,
tpm2.TPMKeyBits(128),
),
Mode: tpm2.NewTPMUSymMode(
tpm2.TPMAlgAES,
tpm2.TPMAlgCFB,
),
},
KeyBits: 2048,
Exponent: 1<<16 + 1,
}),
}),
}
for {
select {
case <-stop:
done <- true
return
default:
// Generate RSA key pair.
resp, err := cp.Execute(tpm)
if err != nil {
s.Fatalf("Error creating RSA key: %s", err)
}
// Delete newly generated key, in order to not overflow TPM storage.
flush := tpm2.FlushContext{
FlushHandle: resp.ObjectHandle,
}
flush.Execute(tpm)
}
}
}