blob: 0f8053c5a25dd2ba5cf088347d72636cf5d1c740 [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"
"regexp"
"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"
)
// USBEnablePin represents each object in the list in the config files for the key "custom_usb_enable_pins".
type USBEnablePin struct {
Name string
Ioex bool
}
// 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"`
Hibernate bool `json:"hibernate"`
HasKeyboard bool `json:"has_keyboard"`
RawUSBEnablePins []json.RawMessage `json:"custom_usb_enable_pins"`
USBAPortCount int `json:"usb_a_port_count"`
// 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"`
RawSerialFirmwareBootDelay float64 `json:"serial_firmware_boot_delay"`
RawUSBPlug float64 `json:"usb_plug"`
RawHoldPwrButtonPowerOff float64 `json:"hold_pwr_button_poweroff"`
RawHoldPwrButtonPowerOn float64 `json:"hold_pwr_button_poweron"`
RawHoldPwrButtonNoPowerdShutdown float64 `json:"hold_pwr_button_nopowerd_shutdown"`
RawShutdown float64 `json:"shutdown"`
// 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
SerialFirmwareBootDelay time.Duration
USBPlug time.Duration
HoldPwrButtonPowerOff time.Duration
HoldPwrButtonPowerOn time.Duration
HoldPwrButtonNoPowerdShutdown time.Duration
Shutdown 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"`
// Holds a list of objects under the "custom_usb_enable_pins" key in the configs.
USBEnablePins []USBEnablePin
}
// 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.SerialFirmwareBootDelay = toSeconds(cfg.RawSerialFirmwareBootDelay)
cfg.USBPlug = toSeconds(cfg.RawUSBPlug)
cfg.HoldPwrButtonPowerOff = toSeconds(cfg.RawHoldPwrButtonPowerOff)
cfg.HoldPwrButtonPowerOn = toSeconds(cfg.RawHoldPwrButtonPowerOn)
cfg.HoldPwrButtonNoPowerdShutdown = toSeconds(cfg.RawHoldPwrButtonNoPowerdShutdown)
cfg.Shutdown = toSeconds(cfg.RawShutdown)
// Parse list of raw json objects into go structs
cfg.USBEnablePins = parseRawUSBEnablePins(cfg.RawUSBEnablePins)
return &cfg, nil
}
// parseRawUSBEnablePins takes the list of json objects and converts them to a list of structs.
func parseRawUSBEnablePins(pinsList []json.RawMessage) []USBEnablePin {
// Look for ones with non-empty name.
reNonEmptyName := regexp.MustCompile(`\S+`)
enablePins := make([]USBEnablePin, 0)
for _, pin := range pinsList {
pinStruct := USBEnablePin{}
json.Unmarshal(pin, &pinStruct)
if match := reNonEmptyName.FindStringSubmatch(pinStruct.Name); match != nil {
enablePins = append(enablePins, pinStruct)
}
}
return enablePins
}
// 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
}