blob: d0d6a3809ed78c5648812523bb692d0b616d8f60 [file] [log] [blame]
// Copyright 2020 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.
// This file implements Config, a collection of platform-specific attributes used for FW testing.
package firmware
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"time"
"chromiumos/tast/errors"
)
// ModeSwitcherType represents which methods the platform uses for switching between DUT boot modes.
type ModeSwitcherType string
// Currently, there are exactly four possible values for ModeSwitcherType.
const (
JetStreamSwitcher ModeSwitcherType = "jetstream_switcher"
KeyboardDevSwitcher ModeSwitcherType = "keyboard_dev_switcher"
MenuSwitcher ModeSwitcherType = "menu_switcher"
TabletDetachableSwitcher ModeSwitcherType = "tablet_detachable_switcher"
)
// ECCapability represents one feature that the EC can control.
type ECCapability string
// These are the ECCapabilities currently described in fw-testing-configs.
const (
ECADCECTemp ECCapability = "adc_ectemp"
ECARM ECCapability = "arm"
ECBattery ECCapability = "battery"
ECCBI ECCapability = "cbi"
ECCharging ECCapability = "charging"
ECDoubleBoot ECCapability = "doubleboot"
ECKeyboard ECCapability = "keyboard"
ECLid ECCapability = "lid"
ECPECI ECCapability = "peci"
ECSmartUSBCharge ECCapability = "smart_usb_charge"
ECThermal ECCapability = "thermal"
ECUSB ECCapability = "usb"
ECUSBPDUART ECCapability = "usbpd_uart"
ECX86 ECCapability = "x86"
)
// cfgDirName is the name of the folder within data/ containing the platform config datafiles.
const cfgDirName = "fw-testing-configs"
// consolidatedBasename is the name of the .json file containing all platforms' config data.
const consolidatedBasename = "CONSOLIDATED.json"
// ConfigFile is the relative path from data/ to the .json file containing all platforms' config data.
// It is intended to be used in the Data field of a testing.Test declaration.
var ConfigFile = filepath.Join(cfgDirName, consolidatedBasename)
// defaultName is the name of the CONSOLIDATED.json attribute containing default values.
const defaultName = "DEFAULTS"
// Config contains platform-specific attributes.
// Fields are documented in autotest/server/cros/faft/configs/DEFAULTS.json.
type Config struct {
Platform string `json:"platform"`
Parent string `json:"parent"`
ECCapability []ECCapability `json:"ec_capability"`
ModeSwitcherType ModeSwitcherType `json:"mode_switcher_type"`
PowerButtonDevSwitch bool `json:"power_button_dev_switch"`
RecButtonDevSwitch bool `json:"rec_button_dev_switch"`
// Raw duration fields represent a quantity of seconds.
// They are used during NewConfig to populate actual duration fields, which are defined below.
// Generally, these raw fields should not be accessed by tests and libraries.
// Nevertheless, the raw fields must be exported in order for them to be set by json.Unmarshal.
RawKeypressDelay float64 `json:"keypress_delay"`
RawDelayRebootToPing float64 `json:"delay_reboot_to_ping"`
RawECBootToPwrButton float64 `json:"ec_boot_to_pwr_button"`
RawFirmwareScreen float64 `json:"firmware_screen"`
RawUSBPlug float64 `json:"usb_plug"`
RawHoldPwrButtonPowerOff float64 `json:"hold_pwr_button_poweroff"`
// Actual duration fields are populated during NewConfig based on raw JSON duration values, defined above.
KeypressDelay time.Duration
DelayRebootToPing time.Duration
ECBootToPwrButton time.Duration
FirmwareScreen time.Duration
USBPlug time.Duration
HoldPwrButtonPowerOff time.Duration
// Instructions for updating AP firmware over servo
APFlashCCDProgrammer string `json:"ap_flash_ccd_programmer"`
APFlashCCDPreCommands []string `json:"ap_flash_ccd_pre_commands"`
APFlashCCDPostCommands []string `json:"ap_flash_ccd_post_commands"`
// Models maps DUT model names to overriding config JSON objects.
Models map[string]json.RawMessage `json:"models"`
}
// CfgPlatformFromLSBBoard interprets a board name that would come from /etc/lsb-release, and returns the name of the platform whose config should be loaded.
func CfgPlatformFromLSBBoard(board string) string {
// Remove hyphenated suffixes: ex. "samus-kernelnext" becomes "samus"
board = strings.SplitN(board, "-", 2)[0]
// If the board name is given as board_variant, take just the variant: ex. "veyron_minnie" becomes "minnie"
board = strings.Split(board, "_")[strings.Count(board, "_")]
return board
}
// loadBytes reads '${platform}.json' from configDataDir and returns it as a slice of bytes.
func loadBytes(configDataDir, platform string) ([]byte, error) {
fp := filepath.Join(configDataDir, fmt.Sprintf("%s.json", platform))
b, err := ioutil.ReadFile(fp)
if err != nil {
return nil, errors.Wrapf(err, "reading datafile %s", fp)
}
return b, nil
}
// parentFromBytes finds the name of the parent platform referenced by a config's JSON bytes.
func parentFromBytes(b []byte) (string, error) {
var cfg Config
if err := json.Unmarshal(b, &cfg); err != nil {
return "", errors.Wrapf(err, "unmarshaling json bytes %s", b)
}
if cfg.Parent == "" && cfg.Platform != "" {
return defaultName, nil
}
return cfg.Parent, nil
}
// NewConfig creates a new Config matching the DUT platform.
// cfgFilepath should take s.DataPath(firmware.ConfigFile).
func NewConfig(cfgFilepath, board, model string) (*Config, error) {
// Load CONSOLIDATED.json as bytes
b, err := ioutil.ReadFile(cfgFilepath)
if err != nil {
return nil, errors.Wrapf(err, "reading config file %s", cfgFilepath)
}
// Unmarshal consolidated JSON bytes onto a map from platform name to raw JSON
var consolidated map[string]json.RawMessage
if err := json.Unmarshal(b, &consolidated); err != nil {
return nil, errors.Wrapf(err, "unmarshaling consolidated JSON bytes %s", b)
}
// inherits is an array of platform names to be inherited, from most specific (board) to most general (DEFAULTS).
var inherits []string
for platform := CfgPlatformFromLSBBoard(board); platform != ""; {
b, ok := consolidated[platform]
if !ok {
return nil, errors.Errorf("consolidated JSON did not contain platform %s", platform)
}
parent, err := parentFromBytes(b)
if err != nil {
return nil, errors.Wrapf(err, "determining parent from bytes for %s", platform)
}
inherits = append(inherits, platform)
platform = parent
}
// Unmarshal JSON bytes in order from most general (DEFAULTS) to most specific (board).
var cfg Config
for i := len(inherits) - 1; i >= 0; i-- {
if err := json.Unmarshal(consolidated[inherits[i]], &cfg); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal config for %q", inherits[i])
}
}
// Unmarshal model-level config on top of the existing config.
// Models are only expected to be defined in the lowest-level (board) config files, not in parent config files.
if modelCfg, ok := cfg.Models[model]; ok {
if err := json.Unmarshal(modelCfg, &cfg); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal model-level config for %q", model)
}
}
// Populate actual durations based on raw JSON values.
cfg.KeypressDelay = toSeconds(cfg.RawKeypressDelay)
cfg.DelayRebootToPing = toSeconds(cfg.RawDelayRebootToPing)
cfg.ECBootToPwrButton = toSeconds(cfg.RawECBootToPwrButton)
cfg.FirmwareScreen = toSeconds(cfg.RawFirmwareScreen)
cfg.USBPlug = toSeconds(cfg.RawUSBPlug)
cfg.HoldPwrButtonPowerOff = toSeconds(cfg.RawHoldPwrButtonPowerOff)
return &cfg, nil
}
// HasECCapability checks whether cfg has a certain ECCapability.
func (cfg *Config) HasECCapability(ecc ECCapability) bool {
for _, capability := range cfg.ECCapability {
if ecc == capability {
return true
}
}
return false
}
// toSeconds casts a float64 to a time.Duration, in seconds.
func toSeconds(f float64) time.Duration {
// The 1000* factor enables a non-integer f to be cast as a time.Duration, an integer field.
return time.Duration(1000*f) * time.Millisecond
}