blob: 4347d8f0a9929c12ed20273bfd4315dda1c8f973 [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 firmware
import (
"context"
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/google/go-cmp/cmp"
common "chromiumos/tast/common/firmware"
commonbios "chromiumos/tast/common/firmware/bios"
"chromiumos/tast/common/servo"
"chromiumos/tast/remote/firmware/bios"
"chromiumos/tast/remote/firmware/fixture"
pb "chromiumos/tast/services/cros/firmware"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: ServoGBBFlags,
Desc: "Verifies GBB flags state can be obtained and manipulated via the servo interface",
Timeout: 8 * time.Minute,
Contacts: []string{"cros-fw-engprod@google.com", "jbettis@google.com"},
Attr: []string{"group:firmware", "firmware_cr50", "firmware_ccd"},
SoftwareDeps: []string{"flashrom"},
ServiceDeps: []string{"tast.cros.firmware.BiosService"},
Fixture: fixture.NormalMode,
Data: []string{"fw-config.json"},
// b/111215677: CCD servo detection doesn't work on soraka.
HardwareDeps: hwdep.D(hwdep.SkipOnModel("soraka")),
})
}
func dutControl(ctx context.Context, s *testing.State, svo *servo.Servo, commands [][]string) {
for _, section := range commands {
for _, cmd := range section {
s.Logf("dut-control %q", cmd)
parts := strings.SplitN(cmd, ":", 2)
if len(parts) == 1 {
if _, err := svo.GetString(ctx, servo.StringControl(cmd)); err != nil {
s.Errorf("Could not read servo string %s: %v", cmd, err)
}
} else {
if err := svo.SetString(ctx, servo.StringControl(parts[0]), parts[1]); err != nil {
s.Errorf("Could not set servo string %s: %v", cmd, err)
}
}
}
}
}
type fwConfig struct {
DUTControlOff [][]string `json:"dut_control_off"`
DUTControlOn [][]string `json:"dut_control_on"`
FlashExtraFlagsFlashrom []string `json:"flash_extra_flags_flashrom"`
Programmer string `json:"programmer"`
}
// ServoGBBFlags has been tested to pass with Suzy-Q, Servo V4, Servo V4 + ServoMicro in dual V4 mode.
// Verified fail on Servo V4 + ServoMicro w/o dual v4 mode.
// Has not been tested with with C2D2 (assumed to pass).
func ServoGBBFlags(ctx context.Context, s *testing.State) {
var flashCmds map[string]map[string]fwConfig
fwConfigRaw, err := s.DataFileSystem().Open("fw-config.json")
if err != nil {
s.Fatal("Failed to open fw-config.json")
}
defer fwConfigRaw.Close()
dec := json.NewDecoder(fwConfigRaw)
err = dec.Decode(&flashCmds)
if err != nil {
s.Fatal("Failed to Unmarshall fw-config.json: ", err)
}
h := s.FixtValue().(*fixture.Value).Helper
if err := h.RequirePlatform(ctx); err != nil {
s.Fatal("Failed to require platform: ", err)
}
boardFlashCmds, ok := flashCmds[h.Board]
if !ok {
s.Logf("Board %q does not have fw-config, using generic", h.Board)
boardFlashCmds = flashCmds["generic"]
}
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to connect to servo: ", err)
}
if err := h.Servo.RequireCCD(ctx); err != nil {
s.Fatal("Servo does not have CCD: ", err)
}
if val, err := h.Servo.GetString(ctx, servo.GSCCCDLevel); err != nil {
s.Fatal("Failed to get gsc_ccd_level")
} else if val != servo.Open {
s.Logf("CCD is not open, got %q. Attempting to unlock", val)
if err := h.Servo.SetString(ctx, servo.CR50Testlab, servo.Open); err != nil {
s.Fatal("Failed to unlock CCD")
}
}
servoType, err := h.Servo.GetServoType(ctx)
if err != nil {
s.Fatal("Failed to get servo type: ", err)
}
dualModePattern := regexp.MustCompile(`^(.*_with)_.*_and(_ccd.*)$`)
if parts := dualModePattern.FindStringSubmatch(servoType); parts != nil {
// This is a dual mode servo, but we want the ccd flash config
servoType = parts[1] + parts[2]
}
servoFlashCmds, ok := boardFlashCmds[servoType]
if !ok {
s.Logf("Servo %q does not have fw-config, using ccd_cr50", servoType)
servoFlashCmds = boardFlashCmds["ccd_cr50"]
}
programmer := servoFlashCmds.Programmer
if programmer == "" {
s.Fatalf("servoFlashCmds does not have programmer configured: %+v", servoFlashCmds)
}
s.Logf("Programmer is %s", programmer)
ccdSerial, err := h.Servo.GetCCDSerial(ctx)
if err != nil {
s.Fatal("Failed to get servo serials: ", err)
}
if err = h.RequireBiosServiceClient(ctx); err != nil {
s.Fatal("Requiring BiosServiceClient: ", err)
}
if err = h.Servo.WatchdogRemove(ctx, servo.WatchdogCCD); err != nil {
s.Fatal("Failed to remove ccd watchdog: ", err)
}
s.Log("Getting GBB flags from BiosService")
old, err := h.BiosServiceClient.GetGBBFlags(ctx, &empty.Empty{})
if err != nil {
s.Fatal("initial GetGBBFlags failed: ", err)
}
s.Log("Current GBB flags: ", old.Set)
s.Log("Reading fw image over CCD")
h.DisconnectDUT(ctx) // Some of the dutControl commands will reboot
dutControl(ctx, s, h.Servo, servoFlashCmds.DUTControlOn)
programmer = fmt.Sprintf(programmer, ccdSerial)
img, err := bios.NewRemoteImage(ctx, h.ServoProxy, programmer, commonbios.GBBImageSection, servoFlashCmds.FlashExtraFlagsFlashrom)
if err != nil {
s.Error("Could not read firmware: ", err)
}
dutControl(ctx, s, h.Servo, servoFlashCmds.DUTControlOff)
if s.HasError() {
return
}
cf, sf, err := img.GetGBBFlags()
if err != nil {
s.Fatal("Could not get GBB flags: ", err)
}
ret := pb.GBBFlagsState{Clear: cf, Set: sf}
s.Log("CDD GBB flags: ", ret.Set)
sortSlice := cmp.Transformer("Sort", func(in []pb.GBBFlag) []pb.GBBFlag {
out := append([]pb.GBBFlag(nil), in...)
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
})
if !cmp.Equal(old.Set, ret.Set, sortSlice) {
s.Fatal("GBB flags from CDD do not match SSH'd GBB flags ", cmp.Diff(old.Set, ret.Set, sortSlice))
}
// Flashrom restarts the dut, so wait for it to boot
s.Log("Waiting for reboot")
if err := h.WaitConnect(ctx); err != nil {
s.Fatalf("Failed to connect to DUT: %s", err)
}
// We need to change some GBB flag, but it doesn't really matter which.
// Toggle DEV_SCREEN_SHORT_DELAY
cf = common.GBBToggle(cf, pb.GBBFlag_DEV_SCREEN_SHORT_DELAY)
sf = common.GBBToggle(sf, pb.GBBFlag_DEV_SCREEN_SHORT_DELAY)
if err := img.ClearAndSetGBBFlags(cf, sf); err != nil {
s.Fatal("Failed to toggle GBB flag in image: ", err)
}
s.Log("Writing fw image over CCD")
h.DisconnectDUT(ctx) // Some of the dutControl commands will reboot
dutControl(ctx, s, h.Servo, servoFlashCmds.DUTControlOn)
if err = bios.WriteRemoteFlashrom(ctx, h.ServoProxy, programmer, img, commonbios.GBBImageSection, servoFlashCmds.FlashExtraFlagsFlashrom); err != nil {
s.Error("count not write flashrom: ", err)
}
dutControl(ctx, s, h.Servo, servoFlashCmds.DUTControlOff)
if s.HasError() {
return
}
// Flashrom restarts the dut, so wait for it to boot
s.Log("Waiting for reboot")
if err := h.WaitConnect(ctx); err != nil {
s.Fatalf("Failed to connect to DUT: %s", err)
}
if err := h.RequireBiosServiceClient(ctx); err != nil {
s.Fatal("Requiring BiosServiceClient: ", err)
}
s.Log("Getting GBB flags from BiosService")
newFlags, err := h.BiosServiceClient.GetGBBFlags(ctx, &empty.Empty{})
if err != nil {
s.Fatal("final GetGBBFlags failed: ", err)
}
s.Log("Updated GBB flags: ", newFlags.Set)
expected := pb.GBBFlagsState{Clear: cf, Set: sf}
if !cmp.Equal(expected.Set, newFlags.Set, sortSlice) {
s.Fatal("Updated GBB flags do not match SSH'd GBB flags ", cmp.Diff(expected.Set, newFlags.Set, sortSlice))
}
}