blob: 8ea0277acc00910c5553b5d681cb3457d9f593f9 [file] [log] [blame]
// Copyright 2022 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"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"unicode"
"go.chromium.org/tast-tests/cros/common/servo"
"go.chromium.org/tast-tests/cros/remote/firmware"
"go.chromium.org/tast-tests/cros/remote/firmware/fixture"
"go.chromium.org/tast-tests/cros/remote/firmware/reporters"
"go.chromium.org/tast/core/ctxutil"
"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"
)
func init() {
testing.AddTest(&testing.Test{
Func: FlagsPreservation,
Desc: "Checks that flag values are preserved over different power cycles",
Contacts: []string{
"chromeos-faft@google.com",
"arthur.chuang@cienet.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
Attr: []string{"group:firmware", "firmware_bios", "firmware_level4"},
Requirements: []string{"sys-fw-0021-v01", "sys-fw-0024-v01", "sys-fw-0025-v01"},
Fixture: fixture.DevModeGBB,
SoftwareDeps: []string{"crossystem"},
HardwareDeps: hwdep.D(hwdep.ChromeEC(), hwdep.Battery()),
Timeout: 50 * time.Minute,
LacrosStatus: testing.LacrosVariantUnneeded,
})
}
type crossystemValues struct {
devBootUSBVal string
devBootAltfw string
fwUpdateTriesVal string
locIdxVal string
backupNvramRequest string
}
type fwUpdaterVersions struct {
RO string `json:"ro"`
RW string `json:"rw"`
}
type currentFwVersions struct {
ro string
rw string
}
func FlagsPreservation(ctx context.Context, s *testing.State) {
h := s.FixtValue().(*fixture.Value).Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to init servo: ", err)
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to create config: ", err)
}
cs := crossystemValues{}
s.Log("Saving original crossystem values under evaluation to restore at the end of test")
csOriginal, err := readTargetCsVals(ctx, s, h, cs)
if err != nil {
s.Fatal("Failed to read initial crossystem values: ", err)
}
originalCrossystemMap := createCsMap(csOriginal)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 5*time.Minute)
defer cancel()
defer func(ctx context.Context) {
// Run a cold reset first to ensure DUT connected.
s.Log("Cold resetting DUT at the end of test")
if err := h.Servo.SetPowerState(ctx, servo.PowerStateReset); err != nil {
s.Fatal("Failed to cold reset DUT at the end of test: ", err)
}
s.Log("Waiting for reconnection to DUT")
if err := h.WaitConnect(ctx); err != nil {
s.Fatal("Unable to reconnect to DUT: ", err)
}
s.Log("Restoring crossystem values to the original settings")
if err := setTargetCsVals(ctx, s, h, originalCrossystemMap); err != nil {
s.Fatal("Failed to restore crossystem values to the original settings: ", err)
}
}(cleanupCtx)
// Target crossystem params and their values to be tested.
targetCrossystemMap := map[reporters.CrossystemParam]string{
reporters.CrossystemParamDevBootUsb: "1",
reporters.CrossystemParamDevBootAltfw: "1",
reporters.CrossystemParamFWUpdatetries: "2",
reporters.CrossystemParamLocIdx: "3",
}
s.Log("Setting crossystem params with target values")
if err := setTargetCsVals(ctx, s, h, targetCrossystemMap); err != nil {
s.Fatal("Failed to set target crossystem values: ", err)
}
// On dirinboz and gumboz, running fw update allowed them to wake from a 45W charger.
// needFwUpdate first checks the ec, and then ap to find if an update is required.
// It will return true as soon as it detects that fw updater has a newer version, either
// for ro, or for rw. In case that the dut doesn't wake up during power cycles, include
// information from needFwUpdate for debugging purposes.
needFwUpdate := func() string {
for _, programmer := range []firmware.FWType{firmware.ECFirmware, firmware.APFirmware} {
if val, err := compareForFwUpdate(ctx, h, programmer); err != nil {
s.Log("Failed to determine if fw update is required: ", err)
return "unknown"
} else if val {
return "need to run firmware update mode recovery"
}
}
return "all fw versions are up to date"
}
fwStatus := needFwUpdate()
for _, powerDisruption := range []string{
"powerCycleByReboot",
"powerCycleByPressingPowerKey",
"powerCycleByRemovingBattery",
} {
s.Log("Saving crossystem params and their values before a power-cycle")
csBefore, err := readTargetCsVals(ctx, s, h, cs)
if err != nil {
s.Fatal("Failed to read crossystem values before a power-cycle: ", err)
}
beforeCrossystemMap := createCsMap(csBefore)
switch powerDisruption {
case "powerCycleByReboot":
s.Log("Power-cycling DUT with a reboot")
if err := h.Servo.SetPowerState(ctx, servo.PowerStateReset); err != nil {
s.Fatal("Failed to reboot DUT by servo: ", err)
}
case "powerCycleByPressingPowerKey":
// pressPowerBtn sends a press on the power button and waits for DUT to reach G3.
pressPowerBtn := func(pressDur time.Duration) error {
s.Logf("Power-cycling DUT by pressing power button for %s", pressDur)
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(pressDur)); err != nil {
return errors.Wrap(err, "failed to press power through servo")
}
s.Log("Waiting for power state to become G3")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, 2*time.Minute, "G3"); err != nil {
return errors.Wrap(err, "failed to get powerstate at G3")
}
return nil
}
// Retry powering off DUT with a longer press if the duration was less than
// 10 seconds on the power button, and the DUT remained at S0.
powerOff := h.Config.HoldPwrButtonPowerOff
if powerOff >= 10*time.Second {
if err := pressPowerBtn(powerOff); err != nil {
s.Fatal("Failed to power off DUT: ", err)
}
} else {
for powerOff < 10*time.Second {
err := pressPowerBtn(powerOff)
if err == nil {
break
}
powerState, _ := checkPowerState(ctx, h)
if powerState != "S0" || powerOff == 9*time.Second {
s.Fatal("Failed to power off DUT: ", err)
}
powerOff += 1 * time.Second
}
}
s.Log("Pressing on the power button to power on DUT")
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.DurPress); err != nil {
s.Fatal("Failed to perform a tap on the power button: ", err)
}
case "powerCycleByRemovingBattery":
// Explicitly log servo type and dut connection type for debugging purposes.
s.Log("Logging servo type and dut connection type")
var validInfo = []string{"servoType", "dutConnectionType"}
for _, info := range validInfo {
if result, err := logInformation(ctx, h, info); err != nil {
s.Logf("Unable to find information on %s: %v", info, err)
} else {
s.Logf("%s: %s", info, result)
}
}
// Dirinboz [zork] did not wake from battery cutoff with a 45W charger,
// but it did with a 65W. Also, when connected to a servo v4 board, the
// same 65W charger only outputs 60W to DUT. On servo v4.1, it output the
// full 65W. Log more information about how much power DUT receives from
// charger in the lab.
if err := checkMaxChargerPower(ctx, h); err != nil {
s.Fatal("Failed to check for max power: ", err)
}
s.Log("Power-cycling DUT by disconnecting AC and removing battery")
if err := firmware.PollToSetChargerStatus(ctx, h, false); err != nil {
s.Fatal("Failed to remove charger: ", err)
}
// When power is cut, there's a temporary drop in connection with the DUT.
// Wait for DUT to reconnect before proceeding to the next step.
waitConnectCtx, cancelWaitConnect := context.WithTimeout(ctx, 2*time.Minute)
defer cancelWaitConnect()
if err := s.DUT().WaitConnect(waitConnectCtx); err != nil {
s.Fatal("Failed to reconnect to DUT: ", err)
}
// Log information on PD communication status after disconnecting charger.
if pdStatus, err := logInformation(ctx, h, "pdCommunication"); err != nil {
s.Log("Unable to check PD communication status: ", err)
} else {
s.Logf("PD communication status: %s", pdStatus)
}
s.Log("Removing CCD watchdog")
if err := h.Servo.RemoveCCDWatchdogs(ctx); err != nil {
s.Fatal("Failed to remove watchdog for ccd: ", err)
}
// Check if DUT is offline after battery cutoff at the end of test. If it is,
// first check if power state is in S5 or G3, and boot DUT to S0 by pressing
// on power button. If there's no response at all, disconnecting and then
// reconnecting charger seems to help with restoring the connection.
defer func(ctx context.Context) {
if !s.DUT().Connected(ctx) {
if err := checkDUTAsleepAndPressPwr(ctx, h); err != nil {
s.Logf("DUT completely offline: %v Attempting to restore connection", err)
if err := h.Servo.RemoveCCDWatchdogs(ctx); err != nil {
s.Fatal("Failed to remove watchdog for ccd: ", err)
}
s.Log("Sleeping for 5 seconds")
// GoBigSleepLint: To-do: depending on how the results turn out, we could
// extend the sleep in RemoveCCDWatchdogs(), instead of adding another sleep here.
if err := testing.Sleep(ctx, 5*time.Second); err != nil {
s.Fatal("Failed to sleep: ", err)
}
s.Log("Removing DUT's power")
if err := h.SetDUTPower(ctx, false); err != nil {
s.Fatal("Failed to set pd role: ", err)
}
s.Log("Sleeping for 60 seconds")
// GoBigSleepLint: Allow some time before reconnecting charger.
if err := testing.Sleep(ctx, 60*time.Second); err != nil {
s.Fatal("Failed to sleep: ", err)
}
s.Log("Reconnecting DUT's power")
if err := h.SetDUTPower(ctx, true); err != nil {
s.Fatal("Failed to set pd role: ", err)
}
}
if err := h.WaitConnect(ctx); err != nil {
s.Fatal("DUT did not wake up: ", err)
}
if hasCCD, err := h.Servo.HasCCD(ctx); err != nil {
s.Fatal("While checking if servo has a CCD connection: ", err)
} else if hasCCD {
if err := h.OpenCCD(ctx, true, true); err != nil {
s.Fatal("CCD not opened: ", err)
}
}
}
}(cleanupCtx)
s.Log("Cutting off DUT's battery")
cmd := firmware.NewECTool(s.DUT(), firmware.ECToolNameMain)
if err := cmd.BatteryCutoff(ctx); err != nil {
s.Fatal("Failed to send the battery cutoff command: ", err)
}
s.Log("Sleeping for 60 seconds")
// GoBigSleepLint: 60 seconds of sleep may be necessary in order for some batteries to
// be fully cut off, and for reducing complications in waking DUTs.
if err := testing.Sleep(ctx, 60*time.Second); err != nil {
s.Fatal("Failed to sleep: ", err)
}
s.Log("Checking EC unresponsive")
if err := h.Servo.CheckUnresponsiveEC(ctx); err != nil {
s.Fatal("While verifying whether EC is unresponsive after cutting off DUT's battery: ", err)
}
s.Log("Powering on DUT by reconnecting AC")
if err := firmware.PollToSetChargerStatus(ctx, h, true); err != nil {
s.Fatal("Failed to reconnect charger: ", err)
}
s.Log("Waiting for battery to be connected")
if err := firmware.WaitForBatteryConnection(ctx, h, 1*time.Minute, 5*time.Second); err != nil {
s.Fatal("Failed to wait for battery connection: ", err)
}
// After reconnecting AC, DUT should get into S0.
// If it's not in S0, try with a press on power.
s.Log("Waiting for powerstate S0")
if err := h.WaitForPowerStates(ctx, firmware.PowerStateInterval, 1*time.Minute, "S0"); err != nil {
s.Log("Failed to get power state at S0: ", err)
}
pwrState, err := checkPowerState(ctx, h)
if err != nil {
s.Fatal("Failed to check power state: ", err)
}
s.Log("Powerstate: ", pwrState)
if pwrState != "S0" {
s.Logf("Pressing power button for %s to wake DUT into S0", h.Config.HoldPwrButtonPowerOn)
if err := h.Servo.KeypressWithDuration(ctx, servo.PowerKey, servo.Dur(h.Config.HoldPwrButtonPowerOn)); err != nil {
s.Fatal("Failed to press power button: ", err)
}
}
}
s.Log("Waiting for DUT to power ON")
waitConnectCtx, cancelWaitConnect := context.WithTimeout(ctx, 8*time.Minute)
defer cancelWaitConnect()
if err := h.WaitConnect(waitConnectCtx, firmware.SkipPDRoleSnk); err != nil {
// When reconnecting to the DUT fails from plugging in AC,
// check for its power state, charge state, and whether
// the battery is detected (present). Also, log gpio output
// indicating when the system power is good for AP to pwr up.
pwrState, _ := checkPowerState(ctx, h)
stateOfCharge, _ := checkChgstateBatt(ctx, h, "state_of_charge")
battPresent, _ := checkChgstateBatt(ctx, h, "is_present")
// To-do: EC_FCH_PWROK was configured for zork devices, for example,
// vilboz and dirinboz. Add more gpio names in the future if more
// models are to be checked.
pwrOkGpio, _ := grepGpio(ctx, h, "EC_FCH_PWROK")
s.Fatalf("Failed to reconnect to DUT [power state %s, battery %s, %s, gpio_pwrok %s, fw status: %s]: %v",
pwrState, stateOfCharge, battPresent, pwrOkGpio, fwStatus, err)
}
// Cr50 goes to sleep when the battery is disconnected, and when DUT wakes,
// CCD might be locked. Open CCD after waking DUT and before talking to the EC.
if hasCCD, err := h.Servo.HasCCD(ctx); err != nil {
s.Fatal("While checking if servo has a CCD connection: ", err)
} else if hasCCD {
if err := h.OpenCCD(ctx, true, true); err != nil {
s.Fatal("CCD not opened: ", err)
}
}
s.Log("Saving crossystem params and their values after a power-cycle")
csAfter, err := readTargetCsVals(ctx, s, h, cs)
if err != nil {
s.Fatal("Failed to read crossystem values after a power-cycle: ", err)
}
afterCrossystemMap := createCsMap(csAfter)
// Compare the before and after values in crossystemValues, except backupNvramRequest,
// which is only checked on DUTs using vboot1.
if ok, err := equal(ctx, beforeCrossystemMap, afterCrossystemMap); err != nil || !ok {
s.Fatal("Crossystem values are different after power-cycle: ", err)
} else if ok {
s.Log("Flags are preserved after power-cycle")
}
}
}
func createCsMap(cs *crossystemValues) map[reporters.CrossystemParam]string {
var csParamsMap = map[reporters.CrossystemParam]string{
reporters.CrossystemParamDevBootUsb: cs.devBootUSBVal,
reporters.CrossystemParamDevBootAltfw: cs.devBootAltfw,
reporters.CrossystemParamFWUpdatetries: cs.fwUpdateTriesVal,
reporters.CrossystemParamLocIdx: cs.locIdxVal,
}
return csParamsMap
}
func readTargetCsVals(ctx context.Context, s *testing.State, h *firmware.Helper, cs crossystemValues) (*crossystemValues, error) {
s.Log("Reading crossystem values under test")
var csParamsMap = map[reporters.CrossystemParam]*string{
reporters.CrossystemParamDevBootUsb: &cs.devBootUSBVal,
reporters.CrossystemParamDevBootAltfw: &cs.devBootAltfw,
reporters.CrossystemParamFWUpdatetries: &cs.fwUpdateTriesVal,
reporters.CrossystemParamLocIdx: &cs.locIdxVal,
reporters.CrossystemParamBackupNvramRequest: &cs.backupNvramRequest,
}
for csKey, csVal := range csParamsMap {
current, err := h.Reporter.CrossystemParam(ctx, csKey)
if err != nil {
return nil, err
}
*csVal = current
}
return &cs, nil
}
func setTargetCsVals(ctx context.Context, s *testing.State, h *firmware.Helper, targetMap map[reporters.CrossystemParam]string) error {
targetArgs := make([]string, len(targetMap))
i := 0
for targetKey, targetVal := range targetMap {
targetArgs[i] = fmt.Sprintf("%s=%s", targetKey, targetVal)
i++
}
if err := h.DUT.Conn().CommandContext(ctx, "crossystem", targetArgs...).Run(); err != nil {
return errors.Wrapf(err, "running crossystem %s", strings.Join(targetArgs, " "))
}
return nil
}
func equal(ctx context.Context, mapBefore, mapAfter map[reporters.CrossystemParam]string) (bool, error) {
if len(mapBefore) != len(mapAfter) {
return false, errors.New("Lengths of maps under evaluation do not match")
}
for k, v := range mapBefore {
if elem, ok := mapAfter[k]; !ok || v != elem {
return false, errors.Errorf("found mismatch in key %q, values are different: one has %q, while the other has %q", k, mapBefore[k], mapAfter[k])
}
}
return true, nil
}
// logInformation logs some information for debugging purposes.
func logInformation(ctx context.Context, h *firmware.Helper, information string) (string, error) {
var (
err error
dutConnection servo.DUTConnTypeValue
result string
)
switch information {
case "servoType":
result, err = h.Servo.GetServoType(ctx)
case "dutConnectionType":
dutConnection, err = h.Servo.GetDUTConnectionType(ctx)
result = string(dutConnection)
case "pdCommunication":
result, err = h.Servo.GetPDCommunication(ctx)
default:
result = "Unknown information"
}
if err != nil {
return "", err
}
return result, nil
}
// checkDUTAsleepAndPressPwr checks if DUT is at S5 or G3, and presses power button to boot it.
func checkDUTAsleepAndPressPwr(ctx context.Context, h *firmware.Helper) error {
shortCtx, cancelShortCtx := context.WithTimeout(ctx, 2*time.Minute)
defer cancelShortCtx()
testing.ContextLog(shortCtx, "Checking if power state is at S5 or G3")
if err := h.WaitForPowerStates(shortCtx, firmware.PowerStateInterval, 1*time.Minute, "S5", "G3"); err != nil {
return err
}
testing.ContextLogf(shortCtx, "Pressing power button for %s to wake DUT", h.Config.HoldPwrButtonPowerOn)
if err := h.Servo.KeypressWithDuration(shortCtx, servo.PowerKey, servo.Dur(h.Config.HoldPwrButtonPowerOn)); err != nil {
return errors.Wrap(err, "failed to press power button")
}
return nil
}
// checkMaxChargerPower runs the local command 'power_supply_info' on DUT
// to check for max voltage and max current, and multiply them to get max power.
func checkMaxChargerPower(ctx context.Context, h *firmware.Helper) error {
// Regular expressions.
var (
reMaxVoltage = regexp.MustCompile(`max\s+voltage\D*([^\n\r]*)`)
reMaxCurrnet = regexp.MustCompile(`max\s+current\D*([^\n\r]*)`)
)
out, err := h.DUT.Conn().CommandContext(ctx, "power_supply_info").Output()
if err != nil {
return errors.Wrap(err, "failed to retrieve power supply info from DUT")
}
findMatch := func(maxPowerVar string, pattern *regexp.Regexp, scannedOut []byte) (float64, error) {
match := pattern.FindStringSubmatch(string(scannedOut))
if len(match) < 2 {
return 0, errors.Errorf("did not find value for %s", maxPowerVar)
}
val, err := strconv.ParseFloat(match[1], 64)
if err != nil {
return 0, errors.Wrapf(err, "failed to parse for %s", maxPowerVar)
}
return val, nil
}
maxVoltage, err := findMatch("max_voltage", reMaxVoltage, out)
if err != nil {
return err
}
maxCurrent, err := findMatch("max_current", reMaxCurrnet, out)
if err != nil {
return err
}
testing.ContextLogf(ctx, "DUT receives max_voltage: %f, max_current: %f, max_power: %f",
maxVoltage, maxCurrent, maxVoltage*maxCurrent)
return nil
}
// checkChgstateBatt runs ec command 'chgstate' and collects information
// from the battery section.
func checkChgstateBatt(ctx context.Context, h *firmware.Helper, attr string) (string, error) {
chgState, err := firmware.GetChargingState(ctx, h)
if err != nil {
return "unknown", errors.Wrap(err, "failed to get charging state")
}
key := "batt." + attr
return chgState[key], nil
}
// grepGpio runs ec command 'gpioget' to check for a gpio's value.
func grepGpio(ctx context.Context, h *firmware.Helper, name string) (string, error) {
if err := h.Servo.RunECCommand(ctx, "chan save"); err != nil {
return "unknown", errors.Wrap(err, "failed to send 'chan save' to EC")
}
if err := h.Servo.RunECCommand(ctx, "chan 0"); err != nil {
return "unknown", errors.Wrap(err, "failed to send 'chan 0' to EC")
}
defer func() {
if err := h.Servo.RunECCommand(ctx, "chan restore"); err != nil {
testing.ContextLog(ctx, "Failed to send 'chan restore' to EC: ", err)
}
}()
match := fmt.Sprintf(`(?i)(0|1)[^\n\r]*\s%s`, name)
cmd := fmt.Sprintf("gpioget %s", name)
out, err := h.Servo.RunECCommandGetOutput(ctx, cmd, []string{match})
if err != nil {
return "unknown", errors.Wrapf(err, "failed to run command %v", cmd)
}
return out[0][1], nil
}
// checkPowerState checks for the dut's power state.
func checkPowerState(ctx context.Context, h *firmware.Helper) (string, error) {
testing.ContextLog(ctx, "Checking for the DUT's power state")
state, err := h.Servo.GetECSystemPowerState(ctx)
if err != nil {
return "unknown", err
}
return state, nil
}
// checkUpdaterFirmware checks for the fw versions contained in the firmware updater archive
// for a specific programmer and dut model.
func checkUpdaterFirmware(ctx context.Context, h *firmware.Helper, programmer firmware.FWType) (fwUpdaterVersions, error) {
testing.ContextLogf(ctx, "Checking %s firmware updater version for %s", programmer, h.Model)
checkFirmwareUpdaterManifest := fmt.Sprintf(
"chromeos-firmwareupdate --manifest | jq -c .%s.%s.versions", h.Model, programmer)
out, err := h.DUT.Conn().CommandContext(ctx, "bash", "-c", checkFirmwareUpdaterManifest).Output(ssh.DumpLogOnError)
if err != nil {
return fwUpdaterVersions{}, errors.Wrap(err, "failed to check for firmware updater manifest")
}
var data fwUpdaterVersions
if err := json.Unmarshal(out, &data); err != nil {
return fwUpdaterVersions{}, errors.Wrap(err, "failed to parse JSON file")
}
return data, nil
}
// checkCurrentFirmware checks for the current fw versions running on the dut.
// For ec, it runs 'ectool version' to find both the ro and rw versions.
// For ap, it runs 'crossystem ro_fwid', and 'crossystem fwid' to find ro and rw respectively.
func checkCurrentFirmware(ctx context.Context, h *firmware.Helper, programmer firmware.FWType) (currentFwVersions, error) {
var data currentFwVersions
switch programmer {
case firmware.ECFirmware:
ec := firmware.NewECTool(h.DUT, firmware.ECToolNameMain)
output, err := ec.Command(ctx, "version").Output(ssh.DumpLogOnError)
if err != nil {
return currentFwVersions{}, errors.Wrap(err, "failed to run 'ectool version' on DUT")
}
reROVersion := regexp.MustCompile(`RO version:\s*(\S+)\s`)
roVersion := reROVersion.FindSubmatch(output)
if len(roVersion) == 0 {
return data, errors.Errorf("failed to match regexp %s in ectool version output: %s", reROVersion, output)
}
reRWVersion := regexp.MustCompile(`RW version:\s*(\S+)\s`)
rwVersion := reRWVersion.FindSubmatch(output)
if len(rwVersion) == 0 {
return data, errors.Errorf("failed to match regexp %s in ectool version output: %s", reRWVersion, output)
}
data.ro = string(roVersion[1])
data.rw = string(rwVersion[1])
case firmware.APFirmware:
rofwid, err := firmware.GetFwVersion(ctx, h, reporters.CrossystemParamRoFwid)
if err != nil {
return data, errors.Wrap(err, "failed to get crosystem ro_fwid")
}
rwfwid, err := firmware.GetFwVersion(ctx, h, reporters.CrossystemParamFwid)
if err != nil {
return data, errors.Wrap(err, "failed to get crossystem fwid")
}
data.ro = rofwid
data.rw = rwfwid
default:
return data, errors.New("unknown programmer name")
}
return data, nil
}
// checkUpdaterVersionBigger checks whether fw updater has newer versions (higher version numbers) than
// the current ones running on the dut.
func checkUpdaterVersionBigger(ctx context.Context, h *firmware.Helper, fwUpdater, current string, programmer firmware.FWType) (bool, error) {
var fwUpdaterIDs, currentIDs []string
testing.ContextLogf(ctx, "Full fw updater version value: %s, current fw version value: %s", fwUpdater, current)
switch programmer {
case firmware.ECFirmware:
// As an example, before parsed into ids, ec fw version is represented by
// the following format, "cret_v2.0.11733-88ee536526", where cret is the
// dut's model name. Use parseECVerIntoID to extract 2, 0, and 11733.
parseECVerIntoID := func(version string) string {
str := strings.TrimLeft(version, fmt.Sprintf("%s_v", h.Model))
nums := strings.Split(str, "-")
id := nums[0]
return id
}
fwUpdaterIDs = strings.Split(parseECVerIntoID(fwUpdater), ".")
currentIDs = strings.Split(parseECVerIntoID(current), ".")
case firmware.APFirmware:
// As an example, before parsed into ids, host fw version is represented by
// the following format, "Google_Cret.13606.426.0". Use parseHostVerIntoID
// to extract 13606, 426, and 0.
parseHostVerIntoID := func(version string) string {
id := strings.TrimLeftFunc(version, func(r rune) bool { return !unicode.IsNumber(r) })
return id
}
fwUpdaterIDs = strings.Split(parseHostVerIntoID(fwUpdater), ".")
currentIDs = strings.Split(parseHostVerIntoID(current), ".")
default:
return false, errors.New("unknown programmer name")
}
testing.ContextLogf(ctx, "Found fw updater id: %s, current id: %s", fwUpdaterIDs, currentIDs)
for i, val := range fwUpdaterIDs {
var idA, idB int
// As an example, say we have "fw updater id: [2 0 11733], current id: [2 0 11081]".
// Running fmt.Sscanf below would assign '2' to idA, and '2' to idB during the first
// iteration. In the second iteration, '0' to idA, and '0' to idB, and so on and so
// fourth, until finding out eventually that '11733' is greater than '11081'.
if _, err := fmt.Sscanf(val, "%d", &idA); err != nil {
return false, errors.Wrapf(err, "failed to sscanf %s", val)
}
if _, err := fmt.Sscanf(currentIDs[i], "%d", &idB); err != nil {
return false, errors.Wrapf(err, "failed to sscanf %s", currentIDs[i])
}
if idA > idB {
testing.ContextLogf(ctx, "%v is bigger than %v", fwUpdaterIDs, currentIDs)
return true, nil
}
}
return false, nil
}
// compareForFwUpdate returns true if either the ro or rw version number is found bigger from fw updater,
// denoting that the dut needs a fw update.
func compareForFwUpdate(ctx context.Context, h *firmware.Helper, programmer firmware.FWType) (bool, error) {
updater, err := checkUpdaterFirmware(ctx, h, programmer)
if err != nil {
return false, err
}
current, err := checkCurrentFirmware(ctx, h, programmer)
if err != nil {
return false, err
}
testing.ContextLogf(ctx, "Compaing %s ro fw versions", programmer)
updateRO, err := checkUpdaterVersionBigger(ctx, h, updater.RO, current.ro, programmer)
if err != nil {
return false, err
}
testing.ContextLogf(ctx, "Compaing %s rw fw versions", programmer)
updateRW, err := checkUpdaterVersionBigger(ctx, h, updater.RW, current.rw, programmer)
if err != nil {
return false, err
}
if updateRO || updateRW {
return true, nil
}
return false, nil
}