blob: f1b8e8cae3b6516926e723111fdb4f0fca653ca6 [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.
package deviceconfig
import (
"bytes"
"context"
"fmt"
"reflect"
"sort"
"strings"
"github.com/golang/protobuf/jsonpb"
"go.chromium.org/chromiumos/config/go/api"
"go.chromium.org/chromiumos/config/go/payload"
"go.chromium.org/chromiumos/infra/proto/go/device"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
luciproto "go.chromium.org/luci/common/proto"
"infra/libs/git"
)
var (
unmarshaller = jsonpb.Unmarshaler{AllowUnknownFields: true}
)
type gitilesInfo struct {
project string
path string
}
// Programs defines the structure of a DLM program list.
type Programs struct {
Programs []struct {
Name string `json:"name,omitempty"`
Repo *Repo `json:"repo,omitempty"`
DeviceProjects []struct {
Repo *Repo `json:"repo,omitempty"`
} `json:"deviceProjects,omitempty"`
} `json:"programs,omitempty"`
}
// Repo defines the repo info in DLM configs.
type Repo struct {
Name string `json:"name,omitempty"`
RepoPath string `json:"repoPath,omitempty"`
ConfigPath string `json:"configPath,omitempty"`
}
func fixFieldMaskForConfigBundleList(b []byte) ([]byte, error) {
var payload payload.ConfigBundleList
t := reflect.TypeOf(payload)
buf, err := luciproto.FixFieldMasksBeforeUnmarshal(b, t)
if err != nil {
return nil, err
}
return buf, nil
}
func getDeviceConfigs(ctx context.Context, gc git.ClientInterface, joinedConfigPath string) ([]*device.Config, error) {
logging.Infof(ctx, "reading device configs from %s", joinedConfigPath)
content, err := gc.GetFile(ctx, joinedConfigPath)
if err != nil {
return nil, err
}
var payloads payload.ConfigBundleList
buf, err := fixFieldMaskForConfigBundleList([]byte(content))
if err != nil {
return nil, errors.Annotate(err, "fail to fix field mask for %s", joinedConfigPath).Err()
}
if err := unmarshaller.Unmarshal(bytes.NewBuffer(buf), &payloads); err != nil {
return nil, errors.Annotate(err, "fail to unmarshal %s", joinedConfigPath).Err()
}
var allCfgs []*device.Config
for _, payload := range payloads.GetValues() {
dcs := parseConfigBundle(payload)
allCfgs = append(allCfgs, dcs...)
}
return allCfgs, nil
}
func correctProjectName(n string) string {
return strings.Replace(n, "+", "plus", -1)
}
func correctConfigPath(p string) string {
return strings.Replace(p, "config.jsonproto", "joined.jsonproto", -1)
}
func validRepo(r *Repo) bool {
return r != nil && r.Name != "" && r.ConfigPath != ""
}
func skuLessDeviceConfigKey(board, model string) string {
return fmt.Sprintf("sku-less-%s-%s", board, model)
}
func parseConfigBundle(configBundle *payload.ConfigBundle) []*device.Config {
designs := configBundle.GetDesignList()
dcs := make(map[string]*device.Config, 0)
for _, d := range designs {
board := d.GetProgramId().GetValue()
model := d.GetName()
// Add a sku-less device config to unblock deployment
dcs[skuLessDeviceConfigKey(board, model)] = &device.Config{
Id: &device.ConfigId{
PlatformId: &device.PlatformId{Value: board},
ModelId: &device.ModelId{Value: model},
},
}
for _, c := range d.GetConfigs() {
dcs[c.GetId().GetValue()] = &device.Config{
Id: &device.ConfigId{
PlatformId: &device.PlatformId{Value: board},
ModelId: &device.ModelId{Value: model},
},
FormFactor: parseFormFactor(c.GetHardwareFeatures().GetFormFactor().GetFormFactor()),
HardwareFeatures: parseHardwareFeatures(configBundle.GetComponents(), c.GetHardwareFeatures()),
// Note: no STORAGE_SSD, STORAGE_HDD, STORAGE_UFS storage
// label-storage is not used for scheduling tests for at least 3 months: https://screenshot.googleplex.com/B8spRMj22aUWkbb
Storage: parseStorage(c.GetHardwareTopology(), configBundle.GetComponents()),
Cpu: parseArchitecture(configBundle.GetComponents()),
Ec: parseEcType(c.GetHardwareFeatures()),
// TODO(xixuan): GpuFamily, gpu_family in Component.Soc hasn't been set
Power: parsePowerSupply(c.GetHardwareFeatures().GetFormFactor().GetFormFactor()),
// Graphics: removed from boxster for now
// Remove platform's usage due to http://crrev.com/c/2832476
// label-video_acceleration is not used for scheduling tests for at least 3 months: https://screenshot.googleplex.com/86h2scqNsStwoiW
// VideoAccelerationSupports: parseVideoAccelerations(d.GetPlatform().GetVideoAcceleration()),
// Soc: parseSoc(d.GetPlatform().GetName()),
}
}
}
// Setup the sku
for _, sc := range configBundle.GetSoftwareConfigs() {
designCID := sc.GetDesignConfigId().GetValue()
dcs[designCID].Id.VariantId = &device.VariantId{Value: fmt.Sprint(sc.GetIdScanConfig().GetFirmwareSku())}
}
res := make([]*device.Config, len(dcs))
i := 0
for _, v := range dcs {
res[i] = v
i++
}
return res
}
func parseFormFactor(ff api.HardwareFeatures_FormFactor_FormFactorType) device.Config_FormFactor {
switch ff {
case api.HardwareFeatures_FormFactor_CLAMSHELL:
return device.Config_FORM_FACTOR_CLAMSHELL
case api.HardwareFeatures_FormFactor_CONVERTIBLE:
return device.Config_FORM_FACTOR_CONVERTIBLE
case api.HardwareFeatures_FormFactor_DETACHABLE:
return device.Config_FORM_FACTOR_DETACHABLE
case api.HardwareFeatures_FormFactor_CHROMEBASE:
return device.Config_FORM_FACTOR_CHROMEBASE
case api.HardwareFeatures_FormFactor_CHROMEBOX:
return device.Config_FORM_FACTOR_CHROMEBOX
case api.HardwareFeatures_FormFactor_CHROMEBIT:
return device.Config_FORM_FACTOR_CHROMEBIT
case api.HardwareFeatures_FormFactor_CHROMESLATE:
return device.Config_FORM_FACTOR_CHROMESLATE
default:
return device.Config_FORM_FACTOR_UNSPECIFIED
}
}
func parsePowerSupply(ff api.HardwareFeatures_FormFactor_FormFactorType) device.Config_PowerSupply {
switch ff {
case api.HardwareFeatures_FormFactor_CHROMEBASE, api.HardwareFeatures_FormFactor_CHROMEBOX, api.HardwareFeatures_FormFactor_CHROMEBIT:
return device.Config_POWER_SUPPLY_AC_ONLY
case api.HardwareFeatures_FormFactor_FORM_FACTOR_UNKNOWN:
return device.Config_POWER_SUPPLY_UNSPECIFIED
default:
return device.Config_POWER_SUPPLY_BATTERY
}
}
func parseHardwareFeatures(components []*api.Component, hf *api.HardwareFeatures) []device.Config_HardwareFeature {
resMap := make(map[device.Config_HardwareFeature]bool)
// Use bluetooth/camera/touchpad/touchscreen component to check
for _, c := range components {
if c.GetBluetooth() != nil {
resMap[device.Config_HARDWARE_FEATURE_BLUETOOTH] = true
}
// How to determine it's webcam or not?
if c.GetCamera() != nil {
resMap[device.Config_HARDWARE_FEATURE_WEBCAM] = true
}
if c.GetTouchpad() != nil {
resMap[device.Config_HARDWARE_FEATURE_TOUCHPAD] = true
}
if c.GetTouchscreen() != nil {
resMap[device.Config_HARDWARE_FEATURE_TOUCHSCREEN] = true
}
}
// HARDWARE_FEATURE_INTERNAL_DISPLAY: Only chromeboxes have this UNSET
ff := hf.GetFormFactor().GetFormFactor()
if ff != api.HardwareFeatures_FormFactor_CHROMEBOX && ff != api.HardwareFeatures_FormFactor_FORM_FACTOR_UNKNOWN {
resMap[device.Config_HARDWARE_FEATURE_INTERNAL_DISPLAY] = true
}
// HARDWARE_FEATURE_STYLUS: Ensure stylus is not an empty object, e.g. "stylus": {}
if hf.GetStylus() != nil {
switch hf.GetStylus().GetStylus() {
case api.HardwareFeatures_Stylus_STYLUS_UNKNOWN, api.HardwareFeatures_Stylus_NONE:
default:
resMap[device.Config_HARDWARE_FEATURE_STYLUS] = true
}
}
// HARDWARE_FEATURE_FINGERPRINT: needs to be present
if fp := hf.GetFingerprint(); fp != nil {
if fp.GetLocation() != api.HardwareFeatures_Fingerprint_NOT_PRESENT {
resMap[device.Config_HARDWARE_FEATURE_FINGERPRINT] = true
}
}
// HARDWARE_FEATURE_DETACHABLE_KEYBOARD
if hf.GetKeyboard() != nil && hf.GetKeyboard().GetKeyboardType() == api.HardwareFeatures_Keyboard_DETACHABLE {
resMap[device.Config_HARDWARE_FEATURE_DETACHABLE_KEYBOARD] = true
}
// TODO: HARDWARE_FEATURE_FLASHROM, not used
// TODO: HARDWARE_FEATURE_HOTWORDING, field in topology.Audio hasn't been set
// TODO: HARDWARE_FEATURE_LUCID_SLEEP, which key in powerConfig?
// Deduplicate & sort
res := make([]device.Config_HardwareFeature, 0)
for k := range resMap {
res = append(res, k)
}
sort.Slice(res, func(i, j int) bool { return int32(res[i]) < int32(res[j]) })
return res
}
func matchStorageType(st string) device.Config_Storage {
switch st {
case api.Component_Storage_NVME.String():
return device.Config_STORAGE_NVME
case api.Component_Storage_EMMC.String():
return device.Config_STORAGE_MMC
case api.Component_Storage_SATA.String():
return device.Config_STORAGE_SSD
}
return device.Config_STORAGE_UNSPECIFIED
}
func parseStorage(hf *api.HardwareTopology, components []*api.Component) device.Config_Storage {
v := matchStorageType(hf.GetNonVolatileStorage().GetHardwareFeature().GetStorage().GetStorageType().String())
if v != device.Config_STORAGE_UNSPECIFIED {
return v
}
storageComponent := make(map[string]bool)
for _, c := range components {
if t := c.GetStorage().GetType(); t != api.Component_Storage_STORAGE_TYPE_UNKNOWN {
storageComponent[t.String()] = true
}
}
// Verify if all storage components have the same storage type
if len(storageComponent) == 1 {
for k := range storageComponent {
return matchStorageType(k)
}
}
return device.Config_STORAGE_UNSPECIFIED
}
func parseArchitecture(components []*api.Component) device.Config_Architecture {
for _, c := range components {
if soc := c.GetSoc(); soc != nil {
switch soc.GetFamily().GetArch() {
case api.Component_Soc_ARM:
return device.Config_ARM
case api.Component_Soc_ARM64:
return device.Config_ARM64
case api.Component_Soc_X86:
return device.Config_X86
case api.Component_Soc_X86_64:
return device.Config_X86_64
default:
return device.Config_ARCHITECTURE_UNDEFINED
}
}
}
return device.Config_ARCHITECTURE_UNDEFINED
}
func parseEcType(hf *api.HardwareFeatures) device.Config_EC {
switch hf.GetEmbeddedController().GetEcType() {
case api.HardwareFeatures_EmbeddedController_EC_CHROME:
return device.Config_EC_CHROME
case api.HardwareFeatures_EmbeddedController_EC_WILCO:
return device.Config_EC_WILCO
}
return device.Config_EC_UNSPECIFIED
}