blob: e11d81f643166f0c3f8eca5374c9fe08cb70398a [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package crosbundle
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
configpb "go.chromium.org/chromiumos/config/go/api"
softwarepb "go.chromium.org/chromiumos/config/go/api/software"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/internal/logging"
"go.chromium.org/tast/core/testing/wlan"
"go.chromium.org/tast/core/framework/protocol"
)
// GSCKeyID is a hex value that represents a key used to sign a GSC image.
type GSCKeyID string
// prodRWGSCKeyIDs is a slice with production keyIDs used to sign the RW GSC image.
var prodRWGSCKeyIDs = []GSCKeyID{"0x87b73b67", "0xde88588d"}
func crosConfig(path, prop string) (string, error) {
cmd := exec.Command("cros_config", path, prop)
var buf bytes.Buffer
cmd.Stderr = &buf
b, err := cmd.Output()
if err != nil {
if xerr, ok := err.(*exec.ExitError); !ok || xerr.ExitCode() != 1 {
return "", errors.Errorf("cros_config failed (stderr: %q): %v", buf.Bytes(), err)
}
}
return string(b), nil
}
// detectHardwareFeatures returns a device.Config and api.HardwareFeatures instances
// some of whose members are filled based on runtime information.
func detectHardwareFeatures(ctx context.Context) (*protocol.HardwareFeatures, error) {
platform, err := func() (string, error) {
out, err := crosConfig("/identity", "platform-name")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown platform-id: %v", err)
}
model, err := func() (string, error) {
out, err := crosConfig("/", "name")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown model-id: %v", err)
}
brand, err := func() (string, error) {
out, err := crosConfig("/", "brand-code")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown brand-id: %v", err)
}
info, err := cpuInfo()
if err != nil {
logging.Infof(ctx, "Unknown CPU information: %v", err)
}
vboot2, err := func() (bool, error) {
out, err := exec.Command("crossystem", "fw_vboot2").Output()
if err != nil {
return false, err
}
return strings.TrimSpace(string(out)) == "1", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown vboot2 info: %v", err)
}
hasSideVolumeButton, err := func() (bool, error) {
out, err := crosConfig("/hardware-properties", "has-side-volume-button")
if err != nil {
return false, err
}
return strings.TrimSpace(string(out)) == "true", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown has-side-volume-button: %v", err)
}
config := &protocol.DeprecatedDeviceConfig{
Id: &protocol.DeprecatedConfigId{
Platform: platform,
Model: model,
Brand: brand,
},
Soc: info.soc,
Cpu: info.cpuArch,
HasNvmeSelfTest: false,
HasVboot2: vboot2,
HasSideVolumeButton: hasSideVolumeButton,
}
features := &configpb.HardwareFeatures{
Screen: &configpb.HardwareFeatures_Screen{},
Fingerprint: &configpb.HardwareFeatures_Fingerprint{},
EmbeddedController: &configpb.HardwareFeatures_EmbeddedController{},
Storage: &configpb.HardwareFeatures_Storage{},
Memory: &configpb.HardwareFeatures_Memory{},
Audio: &configpb.HardwareFeatures_Audio{},
PrivacyScreen: &configpb.HardwareFeatures_PrivacyScreen{},
Soc: &configpb.HardwareFeatures_Soc{},
Touchpad: &configpb.HardwareFeatures_Touchpad{},
Keyboard: &configpb.HardwareFeatures_Keyboard{},
FormFactor: &configpb.HardwareFeatures_FormFactor{},
DpConverter: &configpb.HardwareFeatures_DisplayPortConverter{},
Wifi: &configpb.HardwareFeatures_Wifi{},
Cellular: &configpb.HardwareFeatures_Cellular{},
Bluetooth: &configpb.HardwareFeatures_Bluetooth{},
Hps: &configpb.HardwareFeatures_Hps{},
Battery: &configpb.HardwareFeatures_Battery{},
Camera: &configpb.HardwareFeatures_Camera{},
TrustedPlatformModule: &configpb.HardwareFeatures_TrustedPlatformModule{},
FwConfig: &configpb.HardwareFeatures_FirmwareConfiguration{},
RuntimeProbeConfig: &configpb.HardwareFeatures_RuntimeProbeConfig{},
HardwareProbeConfig: &configpb.HardwareFeatures_HardwareProbe{},
Display: &configpb.HardwareFeatures_Display{},
Vrr: &configpb.HardwareFeatures_Vrr{},
Hdmi: &configpb.HardwareFeatures_Hdmi{},
InterruptControllerInfo: &configpb.HardwareFeatures_InterruptControllerInfo{},
}
swConfig := &softwarepb.SoftwareConfig{
FirmwareInfo: &softwarepb.FirmwareInfo{},
}
formFactor, err := func() (string, error) {
out, err := crosConfig("/hardware-properties", "form-factor")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /hardware-properties/form-factor: %v", err)
}
lidConvertible, err := func() (string, error) {
out, err := crosConfig("/hardware-properties", "is-lid-convertible")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /hardware-properties/is-lid-convertible: %v", err)
}
features.Wifi, err = wifiFeatures()
if err != nil {
logging.Infof(ctx, "Error getting Wifi: %v", err)
}
// Battery
noBatteryBootSupported, err := func() (bool, error) {
out, err := crosConfig("/battery", "no-battery-boot-supported")
if err != nil {
return false, err
}
return out == "true", nil
}()
features.Battery.NoBatteryBootSupported = noBatteryBootSupported
detachableBasePath, err := func() (string, error) {
out, err := crosConfig("/detachable-base", "usb-path")
if err != nil {
return "", err
}
return out, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /detachable-base/usbpath: %v", err)
}
checkHammer := func() bool {
hammer, err := exec.Command("udevadm", "info", "--export-db").Output()
if err != nil {
return false
}
return regexp.MustCompile(`(?m)^E: ID_MODEL=Hammer$`).Match(hammer)
}
if formFactorEnum, ok := configpb.HardwareFeatures_FormFactor_FormFactorType_value[formFactor]; ok {
features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_FormFactorType(formFactorEnum)
} else if formFactor == "CHROMEBOOK" {
// Gru devices have formFactor=="CHROMEBOOK", detachableBasePath=="", lidConvertible="", but are really CHROMESLATE
if platform == "Gru" {
features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CHROMESLATE
} else if detachableBasePath != "" {
features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_DETACHABLE
} else if lidConvertible == "true" {
features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CONVERTIBLE
} else {
features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CLAMSHELL
}
} else {
logging.Infof(ctx, "Form factor not found: %v", formFactor)
}
switch features.FormFactor.FormFactor {
case configpb.HardwareFeatures_FormFactor_CHROMEBASE, configpb.HardwareFeatures_FormFactor_CHROMEBIT, configpb.HardwareFeatures_FormFactor_CHROMEBOX, configpb.HardwareFeatures_FormFactor_CHROMESLATE:
features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_NONE
features.Touchpad.TouchpadType = configpb.HardwareFeatures_Touchpad_NONE
case configpb.HardwareFeatures_FormFactor_CLAMSHELL, configpb.HardwareFeatures_FormFactor_CONVERTIBLE:
features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_INTERNAL
features.Touchpad.TouchpadType = configpb.HardwareFeatures_Touchpad_INTERNAL
case configpb.HardwareFeatures_FormFactor_DETACHABLE:
// When the dut is a detachable, check whether hammer exists
// to determine if a removable keyboard is connected.
hasHammer := checkHammer()
if hasHammer {
features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_DETACHABLE
features.Touchpad.TouchpadType = configpb.HardwareFeatures_Touchpad_DETACHABLE
} else {
features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_NONE
features.Touchpad.TouchpadType = configpb.HardwareFeatures_Touchpad_NONE
}
}
keyboardBacklight, err := func() (bool, error) {
out, err := crosConfig("/keyboard", "backlight")
if err != nil {
return false, err
}
return out == "true", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /keyboard/backlight: %v", err)
}
if keyboardBacklight {
features.Keyboard.Backlight = configpb.HardwareFeatures_PRESENT
}
checkForConnector := func(connectorRegexp string) bool {
const drmSysFS = "/sys/class/drm"
drmFiles, err := ioutil.ReadDir(drmSysFS)
if err != nil {
return false
}
cardMatch := regexp.MustCompile(connectorRegexp)
for _, file := range drmFiles {
fileName := file.Name()
if cardMatch.MatchString(fileName) {
if cardConnected, err := ioutil.ReadFile(path.Join(drmSysFS, fileName, "status")); err != nil {
if !os.IsNotExist(err) {
return false
}
} else {
if strings.HasPrefix(string(cardConnected), "connected") {
return true
}
}
}
}
// No indication of internal panel connected and recognised.
return false
}
// eDP displays show up as card*-eDP-1
// MIPI panels show up as card*-DSI-1
// Virtual displays in VMs show up as card*-Virtual-1
internalDisplayRegexp := `^card[0-9]-(eDP|DSI|Virtual)-1$`
hasInternalDisplay := checkForConnector(internalDisplayRegexp)
if hasInternalDisplay {
features.Screen.PanelProperties = &configpb.Component_DisplayPanel_Properties{}
}
// Display ports show up as card*-DP-[0-9]
// HDMI ports show up as card*-HDMI-A-1
// DVI ports show up as card*-DVI-I-1
externalDisplayRegexp := `^card[0-9]-(DP|HDMI-A|DVI-I)-[0-9]$`
hasExternalDisplay := checkForConnector(externalDisplayRegexp)
switch {
case hasInternalDisplay && hasExternalDisplay:
features.Display.Type = configpb.HardwareFeatures_Display_TYPE_INTERNAL_EXTERNAL
case hasExternalDisplay:
features.Display.Type = configpb.HardwareFeatures_Display_TYPE_EXTERNAL
case hasInternalDisplay:
features.Display.Type = configpb.HardwareFeatures_Display_TYPE_INTERNAL
}
isHdmiConnected := func() bool {
// HDMI ports show up as card*-HDMI-A-1
hdmiCardRegex := `^card[0-9]-HDMI-A-[0-9]$`
return checkForConnector(hdmiCardRegex)
}()
if isHdmiConnected {
features.Hdmi.Present = configpb.HardwareFeatures_PRESENT
} else {
features.Hdmi.Present = configpb.HardwareFeatures_NOT_PRESENT
}
hasTouchScreen := func() bool {
b, err := exec.Command("udevadm", "info", "--export-db").Output()
if err != nil {
return false
}
return regexp.MustCompile(`(?m)^E: ID_INPUT_TOUCHSCREEN=1$`).Match(b)
}()
if hasTouchScreen {
features.Screen.TouchSupport = configpb.HardwareFeatures_PRESENT
}
hasTouchpad := func() bool {
tp, err := exec.Command("udevadm", "info", "--export-db").Output()
if err != nil {
return false
}
return regexp.MustCompile(`(?m)^E: ID_INPUT_TOUCHPAD=1$`).Match(tp)
}()
if hasTouchpad {
features.Touchpad.Present = configpb.HardwareFeatures_PRESENT
} else {
features.Touchpad.Present = configpb.HardwareFeatures_PRESENT_UNKNOWN
features.Touchpad.TouchpadType = configpb.HardwareFeatures_Touchpad_TYPE_UNKNOWN
}
hasFingerprint := func() bool {
out, err := crosConfig("/fingerprint", "sensor-location")
if err != nil {
return false
}
if out == "" || out == "none" {
return false
}
return true
}()
features.Fingerprint.Present = hasFingerprint
fingerprintBoard := func() string {
out, err := crosConfig("/fingerprint", "board")
if err != nil {
return ""
}
return out
}()
features.Fingerprint.Board = fingerprintBoard
features.Fingerprint.FingerprintDiag = &configpb.HardwareFeatures_Fingerprint_FingerprintDiag{}
fingerprintDiagRoutineEnabled := func() bool {
out, err := crosConfig("/cros-healthd/routines/fingerprint-diag", "routine-enable")
return err == nil && out == "true"
}()
features.Fingerprint.FingerprintDiag.RoutineEnable = fingerprintDiagRoutineEnabled
features.FwConfig.FwRwVersion = &configpb.HardwareFeatures_FirmwareConfiguration_SemVer{}
if err := func() error {
out, err := exec.Command("crossystem", "fwid").Output()
if err != nil {
return err
}
rwFwIds := strings.Split(string(out), ".")
wantRwIds := rwFwIds[1:]
versions := []*uint32{
&features.FwConfig.FwRwVersion.MajorVersion,
&features.FwConfig.FwRwVersion.MinorVersion,
&features.FwConfig.FwRwVersion.PatchVersion,
}
if len(versions) != len(wantRwIds) {
return errors.Errorf("got unexpected length of fwid: %s", out)
}
for idx, strID := range wantRwIds {
id, err := strconv.Atoi(strID)
if err != nil {
return err
}
*versions[idx] = uint32(id)
}
return nil
}(); err != nil {
logging.Infof(ctx, "Unable to parse fwid versions: %v", err)
}
features.FwConfig.FwRoVersion = &configpb.HardwareFeatures_FirmwareConfiguration_SemVer{}
if err := func() error {
out, err := exec.Command("crossystem", "ro_fwid").Output()
if err != nil {
return err
}
roFwIds := strings.Split(string(out), ".")
wantIds := roFwIds[1:]
versions := []*uint32{
&features.FwConfig.FwRoVersion.MajorVersion,
&features.FwConfig.FwRoVersion.MinorVersion,
&features.FwConfig.FwRoVersion.PatchVersion,
}
if len(versions) != len(wantIds) {
return errors.Errorf("got unexpected length of ro_fwid: %s", out)
}
for idx, strID := range wantIds {
// There are some fwids that look like Google_Foob.11297.437.0~RO
numericPart := strings.Split(strID, "~")
id, err := strconv.Atoi(numericPart[0])
if err != nil {
return err
}
*versions[idx] = uint32(id)
}
return nil
}(); err != nil {
logging.Infof(ctx, "Unable to parse ro_fwid versions: %v", err)
}
// Device has ChromeEC if /dev/cros_ec exists.
// TODO(b/173741162): Pull EmbeddedController data directly from Boxster.
if _, err := os.Stat("/dev/cros_ec"); err == nil {
features.EmbeddedController.Present = configpb.HardwareFeatures_PRESENT
features.EmbeddedController.EcType = configpb.HardwareFeatures_EmbeddedController_EC_CHROME
// Check for features inferred from ectool inventory.
output, err := exec.Command("ectool", "inventory").Output()
if err != nil {
features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_PRESENT_UNKNOWN
features.EmbeddedController.FeatureSystemSafeMode = configpb.HardwareFeatures_PRESENT_UNKNOWN
features.EmbeddedController.FeatureAssertsPanic = configpb.HardwareFeatures_PRESENT_UNKNOWN
features.EmbeddedController.FeatureMemoryDumpCommands = configpb.HardwareFeatures_PRESENT_UNKNOWN
} else {
// The presence of the integer "41" in the inventory output is a sufficient check, since 41 is
// the bit position associated with this feature.
if bytes.Contains(output, []byte("41")) {
features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_NOT_PRESENT
}
// EC_FEATURE_SYSTEM_SAFE_MODE == 47
if bytes.Contains(output, []byte("47")) {
features.EmbeddedController.FeatureSystemSafeMode = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.FeatureSystemSafeMode = configpb.HardwareFeatures_NOT_PRESENT
}
// EC_FEATURE_ASSERT_REBOOTS == 48
if bytes.Contains(output, []byte("48")) {
features.EmbeddedController.FeatureAssertsPanic = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.FeatureAssertsPanic = configpb.HardwareFeatures_NOT_PRESENT
}
// EC_FEATURE_ASSERT_REBOOTS == 51
if bytes.Contains(output, []byte("51")) {
features.EmbeddedController.FeatureMemoryDumpCommands = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.FeatureMemoryDumpCommands = configpb.HardwareFeatures_NOT_PRESENT
}
}
// Check if the detachable base is attached.
output, err = exec.Command("ectool", "mkbpget", "switches").Output()
if err != nil {
features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_PRESENT_UNKNOWN
} else if strings.Contains(string(output), "Base attached: ON") {
features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_NOT_PRESENT
}
// Running `ectool chargecontrol` with no args will fail if version 2 isn't
// supported. Check for battery sustainer output if the command doesn't
// fail to make sure charger control v2 is fully supported.
if out, err := exec.Command("ectool", "chargecontrol").Output(); err != nil || !regexp.MustCompile(`.*Battery sustainer`).Match(out) {
logging.Infof(ctx, "Charge control V2 not supported: %v", err)
features.EmbeddedController.FeatureChargeControlV2 = configpb.HardwareFeatures_NOT_PRESENT
} else {
features.EmbeddedController.FeatureChargeControlV2 = configpb.HardwareFeatures_PRESENT
}
}
// Device has CBI if ectool cbi get doesn't raise error.
if out, err := exec.Command("ectool", "cbi", "get", "0").Output(); err != nil {
logging.Infof(ctx, "CBI not present: %v", err)
features.EmbeddedController.Cbi = configpb.HardwareFeatures_NOT_PRESENT
} else if strings.Contains(string(out), "As uint:") {
features.EmbeddedController.Cbi = configpb.HardwareFeatures_PRESENT
} else {
features.EmbeddedController.Cbi = configpb.HardwareFeatures_PRESENT_UNKNOWN
}
// Get number of PD ports from ectool, if possible.
if out, err := exec.Command("ectool", "usbpdpower").Output(); err == nil {
// Returns multiple lines like:
// Port 0: SNK Charger PD 15263mV / 3000mA, max 15000mV / 3000mA / 45000mW
// Port 1: Disconnected
// We want to match the last port number seen to get the count.
re := regexp.MustCompile(`Port (\d+):`)
match := re.FindAllSubmatch(out, -1)
if match == nil {
// If ectool succeeds but doesn't match the regex something is very wrong. I.e. someone modified the output of ectool.
return nil, errors.Errorf("ectool usbpdpower output unexpected: %v", string(out))
}
count, err := strconv.Atoi(string(match[len(match)-1][1]))
if err != nil {
return nil, errors.Errorf("Failed to parse: %v", string(match[len(match)-1][1]))
}
features.UsbC = &configpb.HardwareFeatures_UsbC{Count: &configpb.HardwareFeatures_Count{Value: uint32(count + 1)}}
}
// Device has GSC with production RW KeyId if gsctool -a -I -M
// returns RW KeyID with value 0x87b73b67 or 0xde88588d
func() {
if out, err := exec.Command("gsctool", "-a", "-f", "-M").Output(); err != nil {
logging.Infof(ctx, "Failed to exec command for KeyId info: %v", err)
features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_PRESENT_UNKNOWN
} else if keyIDRW, err := findGSCKeyID(string(out), "RW"); err != nil {
logging.Infof(ctx, "Failed to read RW KeyId: %v", err)
} else if containsGSCKeyID(prodRWGSCKeyIDs, GSCKeyID(keyIDRW)) {
features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_PRESENT
} else {
features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_NOT_PRESENT
}
}()
// Whether device has TPM enabled can be checked by `tpm_manager_client status`.
// If TPM is enabled, we can check the version by `tpm_version`.
func() {
features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_DISABLED
out, err := exec.Command("tpm_manager_client", "status", "--nonsensitive").Output()
if err != nil {
logging.Info(ctx, "Failed to exec command `tpm_manager_client status`: ", err)
return
}
if !strings.Contains(string(out), "is_enabled: true") {
return
}
out, err = exec.Command("tpm_version").Output()
if err != nil {
logging.Info(ctx, "Failed to exec command `tpm_version`: ", err)
return
}
status := string(out)
if strings.Contains(status, "TPM 1.2") {
features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_V1_2
} else if strings.Contains(status, "TPM 2.0") {
features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_V2
}
}()
// Check if rollback NVRAM space is present in the TPM.
func() {
out, err := exec.Command("tpm_manager_client", "list_spaces").Output()
if err != nil {
logging.Info(ctx, "Failed to exec command `tpm_manager_client list_spaces`: ", err)
features.TrustedPlatformModule.EnterpriseRollbackSpace = configpb.HardwareFeatures_PRESENT_UNKNOWN
return
}
if bytes.Contains(out, []byte("0x0000100E")) {
features.TrustedPlatformModule.EnterpriseRollbackSpace = configpb.HardwareFeatures_PRESENT
return
}
features.TrustedPlatformModule.EnterpriseRollbackSpace = configpb.HardwareFeatures_NOT_PRESENT
}()
// Obtain GSC name from existence of /opt/google/{cr50,ti50}/firmware directory.
func() {
if _, err := os.Stat("/opt/google/cr50/firmware"); err == nil {
features.TrustedPlatformModule.GscFwName = configpb.HardwareFeatures_TrustedPlatformModule_GSC_CR50
} else if _, err := os.Stat("/opt/google/ti50/firmware"); err == nil {
features.TrustedPlatformModule.GscFwName = configpb.HardwareFeatures_TrustedPlatformModule_GSC_TI50
} else {
features.TrustedPlatformModule.GscFwName = configpb.HardwareFeatures_TrustedPlatformModule_GSC_NONE
}
}()
modemVariant, err := crosConfig("/modem", "firmware-variant")
features.Cellular.Present = configpb.HardwareFeatures_NOT_PRESENT
if err != nil {
logging.Infof(ctx, "Modem not found with err: %v", err)
} else if modemVariant == "" {
logging.Info(ctx, "Modem not found")
} else {
features.Cellular.Present = configpb.HardwareFeatures_PRESENT
features.Cellular.Model = modemVariant
swDynamicSar, err := func() (bool, error) {
out, err := crosConfig("/power", "use-modemmanager-for-dynamic-sar")
if err != nil {
return false, err
}
return out == "1", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /power/use-modemmanager-for-dynamic-sar: %v", err)
}
features.Cellular.DynamicPowerReductionConfig = &configpb.HardwareFeatures_Cellular_DynamicPowerReductionConfig{
DynamicPowerReductionConfig: &configpb.HardwareFeatures_Cellular_DynamicPowerReductionConfig_ModemManager{ModemManager: swDynamicSar}}
}
// bluetoothctl hangs when bluetoothd is not built with asan enabled or
// crashes. Set state to PRESENT_UNKNOWN on timeout.
const timeout = 3 * time.Second
cmdCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
if out, err := exec.CommandContext(cmdCtx, "bluetoothctl", "list").Output(); err != nil {
features.Bluetooth.Present = configpb.HardwareFeatures_PRESENT_UNKNOWN
} else if len(string(out)) != 0 {
features.Bluetooth.Present = configpb.HardwareFeatures_PRESENT
} else {
logging.Infof(ctx, "bluetooth controller not found")
features.Bluetooth.Present = configpb.HardwareFeatures_NOT_PRESENT
}
readSysfsString := func(dev, relPath string) string {
path := "/sys/block/" + dev + "/" + relPath
out, err := exec.CommandContext(cmdCtx, "cat", path).Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
hasEmmcStorage := func() bool {
matches, err := filepath.Glob("/dev/mmc*")
if err != nil {
return false
}
for _, dev := range matches {
pathComponenets := strings.Split(dev, "/")
phyDev := pathComponenets[len(pathComponenets)-1]
out := readSysfsString(phyDev, "device/type")
if out == "MMC" {
return true
}
}
return false
}()
if hasEmmcStorage {
features.Storage.StorageType = configpb.Component_Storage_EMMC
}
hasEmmcBridgeStorage := func() bool {
const bh799Ven = 0x1217
const bh799Dev = 0x0002
var readSysfsHex func(string, string) int64
readSysfsHex = func(devPath, relPath string) int64 {
pathComponenets := strings.Split(devPath, "/")
phyDev := pathComponenets[len(pathComponenets)-1]
outStr := readSysfsString(phyDev, relPath)
if outStr == "" {
return 0
}
if outStr[0:2] == "0x" {
outStr = outStr[2:]
}
val, err := strconv.ParseInt(outStr, 16, 64)
if err != nil {
return 0
}
return val
}
matches, err := filepath.Glob("/dev/nvme*")
if err != nil {
return false
}
for _, dev := range matches {
subsysVen := readSysfsHex(dev, "device/device/subsystem_vendor")
subsysDev := readSysfsHex(dev, "device/device/subsystem_device")
if subsysVen == bh799Ven && subsysDev == bh799Dev {
return true
}
}
return false
}()
if hasEmmcBridgeStorage {
features.Storage.StorageType = configpb.Component_Storage_BRIDGED_EMMC
}
hasNvmeStorage := func() bool {
matches, err := filepath.Glob("/dev/nvme*")
if err != nil {
return false
}
if len(matches) > 0 {
return true
}
return false
}()
if hasNvmeStorage && !hasEmmcBridgeStorage {
features.Storage.StorageType = configpb.Component_Storage_NVME
}
hasNvmeSelfTestStorage := func() bool {
matches, err := filepath.Glob("/dev/nvme*n1")
if err != nil {
return false
}
if len(matches) == 0 {
return false
}
nvmePath := matches[0]
b, err := exec.Command("nvme", "id-ctrl", "-H", nvmePath).Output()
if err != nil {
return false
}
return bytes.Contains(b, []byte("Device Self-test Supported"))
}()
if hasNvmeStorage && hasNvmeSelfTestStorage {
config.HasNvmeSelfTest = true
}
hasSataStorage := func() bool {
matches, err := filepath.Glob("/dev/sd*")
if err != nil {
return false
}
for _, dev := range matches {
pathComponenets := strings.Split(dev, "/")
physDev := pathComponenets[len(pathComponenets)-1]
outStr := readSysfsString(physDev, "device/inquiry")
if strings.Contains(outStr, "ATA") {
return true
}
return false
}
return false
}()
if hasSataStorage {
features.Storage.StorageType = configpb.Component_Storage_SATA
}
hasUfsStorage := func() bool {
matches, err := filepath.Glob("/dev/sd*")
if err != nil {
return false
}
for _, dev := range matches {
pathComponenets := strings.Split(dev, "/")
physDev := pathComponenets[len(pathComponenets)-1]
// /sys/block/<dev>/device is a symlink to the scsi target node,
// the symlink resolution will resolve .. directory against the linked one
// so we can get to the controller's node
path := "/sys/block/" + physDev + "/device/../../../driver"
out, err := exec.Command("readlink", "-f", path).Output()
if err != nil {
return false
}
resComponents := strings.Split(strings.TrimSpace(string(out)), "/")
res := resComponents[len(resComponents)-1]
if res == "ufshcd" {
return true
}
return false
}
return false
}()
if hasUfsStorage {
features.Storage.StorageType = configpb.Component_Storage_UFS
}
func() {
// This function determines DUT's power supply type and stores it to config.Power.
// If DUT has a battery, config.Power is DeprecatedDeviceConfig_POWER_SUPPLY_BATTERY.
// If DUT has AC power supplies only, config.Power is DeprecatedDeviceConfig_POWER_SUPPLY_AC_ONLY.
// Otherwise, DeprecatedDeviceConfig_POWER_SUPPLY_UNSPECIFIED is populated.
const sysFsPowerSupplyPath = "/sys/class/power_supply"
// AC power types come from power_supply driver in Linux kernel (drivers/power/supply/power_supply_sysfs.c)
acPowerTypes := [...]string{
"Unknown", "UPS", "Mains", "USB",
"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
"USB_PD", "USB_PD_DRP", "BrickID",
}
isACPower := make(map[string]bool)
for _, s := range acPowerTypes {
isACPower[s] = true
}
config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_UNSPECIFIED
files, err := ioutil.ReadDir(sysFsPowerSupplyPath)
if err != nil {
logging.Infof(ctx, "Failed to read %v: %v", sysFsPowerSupplyPath, err)
return
}
for _, file := range files {
devPath := path.Join(sysFsPowerSupplyPath, file.Name())
supplyTypeBytes, err := ioutil.ReadFile(path.Join(devPath, "type"))
supplyType := strings.TrimSuffix(string(supplyTypeBytes), "\n")
if err != nil {
logging.Infof(ctx, "Failed to read supply type of %v: %v", devPath, err)
continue
}
if strings.HasPrefix(supplyType, "Battery") {
supplyScopeBytes, err := ioutil.ReadFile(path.Join(devPath, "scope"))
supplyScope := strings.TrimSuffix(string(supplyScopeBytes), "\n")
if err != nil && !os.IsNotExist(err) {
// Ignore NotExist error since /sys/class/power_supply/*/scope may not exist
logging.Infof(ctx, "Failed to read supply type of %v: %v", devPath, err)
continue
}
if strings.HasPrefix(string(supplyScope), "Device") {
// Ignore batteries for peripheral devices.
continue
}
config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_BATTERY
// Found at least one battery so this device is powered by battery.
break
}
if !isACPower[supplyType] {
logging.Infof(ctx, "Unknown supply type %v for %v", supplyType, devPath)
continue
}
config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_AC_ONLY
}
}()
storageBytes, err := func() (int64, error) {
b, err := exec.Command("lsblk", "-J", "-b").Output()
if err != nil {
return 0, err
}
return findDiskSize(b)
}()
if err != nil {
logging.Infof(ctx, "Failed to get disk size: %v", err)
}
features.Storage.SizeGb = uint32(storageBytes / 1_000_000_000)
memoryBytes, err := func() (int64, error) {
b, err := ioutil.ReadFile("/proc/meminfo")
if err != nil {
return 0, err
}
return findMemorySize(b)
}()
if err != nil {
logging.Infof(ctx, "Failed to get memory size: %v", err)
}
features.Memory.Profile = &configpb.Component_Memory_Profile{
// Use 2^20 megabytes to be consistent with how memory size is advertised.
SizeMegabytes: int32(memoryBytes >> 20),
}
lidMicrophone, err := matchCrasDeviceType(`(INTERNAL|FRONT)_MIC`)
if err != nil {
logging.Infof(ctx, "Failed to get lid microphone: %v", err)
}
features.Audio.LidMicrophone = lidMicrophone
baseMicrophone, err := matchCrasDeviceType(`REAR_MIC`)
if err != nil {
logging.Infof(ctx, "Failed to get base microphone: %v", err)
}
features.Audio.BaseMicrophone = baseMicrophone
expectAudio := hasBuiltinAudio(ctx, features.FormFactor.FormFactor)
if features.Audio.LidMicrophone.GetValue() == 0 && features.Audio.BaseMicrophone.GetValue() == 0 && expectAudio {
features.Audio.LidMicrophone = &configpb.HardwareFeatures_Count{Value: 1}
}
speaker, err := matchCrasDeviceType(`INTERNAL_SPEAKER`)
if err != nil {
logging.Infof(ctx, "Failed to get speaker: %v", err)
}
if speaker.GetValue() > 0 || expectAudio {
amp, err := findSpeakerAmplifier()
if err != nil {
logging.Infof(ctx, "Failed to get amp: %v", err)
}
if amp == nil {
// Do not assume findSpeakerAmplifier() always returns a non-nil amp.
// Always signal that the device has a hwdep.Speaker().
amp = &configpb.Component_Amplifier{}
}
features.Audio.SpeakerAmplifier = amp
}
hasPrivacyScreen := func() bool {
// Get list of connectors.
value, err := exec.Command("modetest", "-c").Output()
if err != nil {
logging.Infof(ctx, "Failed to get connectors: %v", err)
return false
}
modetestInfo := string(value)
// Check if privacy-screen prop is present.
if strings.Contains(modetestInfo, "privacy-screen sw-state:") && strings.Contains(modetestInfo, "privacy-screen hw-state:") {
return true
}
// Fallback to legacy style.
return strings.Contains(modetestInfo, "privacy-screen:")
}()
if hasPrivacyScreen {
features.PrivacyScreen.Present = configpb.HardwareFeatures_PRESENT
} else {
features.PrivacyScreen.Present = configpb.HardwareFeatures_NOT_PRESENT
}
cpuSMT, err := func() (bool, error) {
// NB: this sysfs API exists only on kernel >=4.19 (b/195061310). But we don't
// target SMT-specific tests on earlier kernels.
b, err := ioutil.ReadFile("/sys/devices/system/cpu/smt/control")
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, errors.Wrap(err, "failed to read SMT control file")
}
s := strings.TrimSpace(string(b))
switch s {
case "on", "off", "forceoff":
return true, nil
case "notsupported", "notimplemented":
return false, nil
default:
return false, errors.Errorf("unknown SMT control status: %q", s)
}
}()
if err != nil {
logging.Infof(ctx, "Failed to determine CPU SMT features: %v", err)
}
if cpuSMT {
features.Soc.Features = append(features.Soc.Features, configpb.Component_Soc_SMT)
}
cpuVulnerabilityPresent := func(vulnerability string) (bool, error) {
vPath := filepath.Join("/sys/devices/system/cpu/vulnerabilities", vulnerability)
b, err := ioutil.ReadFile(vPath)
if err != nil {
return false, errors.Wrapf(err, "failed to read vulnerability file %q", vPath)
}
s := strings.TrimSpace(string(b))
if s == "Not affected" {
return false, nil
}
return true, nil
}
cpuL1TF, err := cpuVulnerabilityPresent("l1tf")
if err != nil {
logging.Infof(ctx, "Failed to determine L1TF vulnerability: %v", err)
} else if cpuL1TF {
features.Soc.Vulnerabilities = append(features.Soc.Vulnerabilities, configpb.Component_Soc_L1TF)
}
cpuMDS, err := cpuVulnerabilityPresent("mds")
if err != nil {
logging.Infof(ctx, "Failed to determine MDS vulnerability: %v", err)
} else if cpuMDS {
features.Soc.Vulnerabilities = append(features.Soc.Vulnerabilities, configpb.Component_Soc_MDS)
}
for _, v := range info.flags {
if v == "sha_ni" {
features.Soc.Features = append(features.Soc.Features, configpb.Component_Soc_SHA_NI)
}
}
func() {
// Probe for presence of DisplayPort converters
devices := map[string]string{
"i2c-10EC2141:00": "RTD2141B",
"i2c-10EC2142:00": "RTD2142",
"i2c-1AF80175:00": "PS175",
}
for f, name := range devices {
path := filepath.Join("/sys/bus/i2c/devices", f)
if _, err := os.Stat(path); err != nil {
continue
}
features.DpConverter.Converters = append(features.DpConverter.Converters, &configpb.Component_DisplayPortConverter{
Name: name,
})
}
}()
hasHps, err := func() (bool, error) {
out, err := crosConfig("/hps", "has-hps")
if err != nil {
return false, err
}
return out == "true", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /hps: %v", err)
}
if hasHps {
features.Hps.Present = configpb.HardwareFeatures_PRESENT
}
camFeatures, err := cameraFeatures(model)
if err != nil {
logging.Infof(ctx, "failed to load camera feature profile: %v", err)
}
features.Camera.Features = camFeatures
camEnumerated, camEnumeratedUsbIds, err := cameraEnumerated()
if err != nil {
logging.Infof(ctx, "failed to load camera enumeration status: %v", err)
}
features.Camera.Enumerated = camEnumerated
features.Camera.EnumeratedUsbIds = camEnumeratedUsbIds
if err := parseKConfigs(ctx, features); err != nil {
logging.Info(ctx, "Failed to parse BIOS kConfig: ", err)
}
if err := parseECBuildConfig(ctx, features); err != nil {
logging.Info(ctx, "Failed to parse EC Config: ", err)
}
rpConfigPresent, err := hasRuntimeProbeConfig(model, false)
if err != nil {
logging.Info(ctx, "Failed to determine if the config of Runtime Probe exists: ", err)
}
if rpConfigPresent {
features.RuntimeProbeConfig.Present = configpb.HardwareFeatures_PRESENT
} else {
logging.Infof(ctx, "Config of Runtime Probe not found")
features.RuntimeProbeConfig.Present = configpb.HardwareFeatures_NOT_PRESENT
}
rpEncryptedConfigPresent, err := hasRuntimeProbeConfig(model, true)
if err != nil {
logging.Info(ctx, "Failed to determine if the encrypted config of Runtime Probe exists: ", err)
}
if rpEncryptedConfigPresent {
features.RuntimeProbeConfig.EncryptedConfigPresent = configpb.HardwareFeatures_PRESENT
} else {
logging.Infof(ctx, "Encrypted config of Runtime Probe not found")
features.RuntimeProbeConfig.EncryptedConfigPresent = configpb.HardwareFeatures_NOT_PRESENT
}
gpuFamily, gpuVendor, cpuSocFamily, err := func() (gpuFamily, gpuVendor, cpuSocFamily string, fetchErr error) {
outBin, err := ioutil.TempFile("/tmp", "hardware_probe")
if err != nil {
return "", "", "", errors.Wrap(err, "failed to create temp file")
}
outBin.Close()
defer os.Remove(outBin.Name())
out, err := exec.Command("/usr/local/graphics/hardware_probe", "-output", outBin.Name()).CombinedOutput()
if err != nil {
return "", "", "", errors.Wrapf(err, "failed to run hardware_probe, output: %v", string(out))
}
type gpuInfo struct {
Family string `json:"Family"`
Vendor string `json:"GPUVendor"`
}
type hardwareProbeResult struct {
GPUInfo []gpuInfo `json:"GPU_Family"`
CPUFamily string `json:"CPU_SOC_Family"`
}
b, err := os.ReadFile(outBin.Name())
if err != nil {
return "", "", "", errors.Wrap(err, "failed to read hardware_probe.json")
}
var result hardwareProbeResult
if err := json.Unmarshal(b, &result); err != nil {
return "", "", "", err
}
if len(result.GPUInfo) > 0 {
gpuFamily = result.GPUInfo[0].Family
gpuVendor = result.GPUInfo[0].Vendor
if len(result.GPUInfo) > 1 {
logging.Infof(ctx, "Found multiple GPUInfo(%v), only use the first one detected.", result.GPUInfo)
}
}
cpuSocFamily = result.CPUFamily
return
}()
if err != nil {
logging.Infof(ctx, "failed to parse hardware_probe output: %v", err)
}
features.HardwareProbeConfig.GpuFamily = gpuFamily
features.HardwareProbeConfig.GpuVendor = gpuVendor
features.HardwareProbeConfig.CpuSocFamily = cpuSocFamily
hevcSupport, err := func() (configpb.HardwareFeatures_Present, error) {
out, err := crosConfig("/ui", "serialized-ash-switches")
if err != nil {
return configpb.HardwareFeatures_PRESENT_UNKNOWN, err
}
if regexp.MustCompile("disable-features=[^\x00]*PlatformHEVCDecoderSupport").MatchString(out) {
return configpb.HardwareFeatures_NOT_PRESENT, nil
}
if regexp.MustCompile("enable-features=[^\x00]*PlatformHEVCDecoderSupport").MatchString(out) {
return configpb.HardwareFeatures_PRESENT, nil
}
return configpb.HardwareFeatures_PRESENT_UNKNOWN, nil
}()
if err != nil {
logging.Infof(ctx, "Unknown /ui/serialized-ash-switches: %v", err)
}
features.Soc.HevcSupport = hevcSupport
hasVrrSupport := func() bool {
modetestOutput, err := exec.Command("modetest").Output()
if err != nil {
logging.Infof(ctx, "failed to run modetest: %v", err)
return false
}
// check if there exists a vrr_capable property with a value set to 1 in
// the modetest command output
regex := regexp.MustCompile("\n\t.*vrr_capable:(\n\t\t.*)*\n\t\tvalue: 1")
return regex.Match(modetestOutput)
}()
if hasVrrSupport {
features.Vrr.Present = configpb.HardwareFeatures_PRESENT
} else {
features.Vrr.Present = configpb.HardwareFeatures_NOT_PRESENT
}
features.FeatureLevel, err = featureLevel()
if err != nil {
logging.Infof(ctx, "Failed to get feature level: %v", err)
}
features.OemInfo = &configpb.HardwareFeatures_OEMInfo{
Name: oemName(),
}
// FirmwareFeatures
altfw, err := func() (bool, error) {
out, err := crosConfig("/firmware", "has-alt-firmware")
if err != nil {
return false, err
}
return out == "true", nil
}()
if err != nil {
logging.Infof(ctx, "Unknown firmware/altfw-present : %v", err)
}
if altfw {
swConfig.FirmwareInfo.HasAltFirmware = true
} else {
swConfig.FirmwareInfo.HasAltFirmware = false
}
// InterruptControllerInfo
// Default to assuming we have NMI support. We only say NMI is
// unsupported if we've explicitly decided we can't support it for
// a given configuration. Note that upon any errors we'll leave this
// as "present". If a test doesn't care then that's fine. If a test
// relies on NMI support then we _want_ the test to fail and point out
// that we failed to get the config properly.
features.InterruptControllerInfo.NmiSupport = configpb.HardwareFeatures_PRESENT
if runtime.GOARCH == "arm" {
features.InterruptControllerInfo.NmiSupport = configpb.HardwareFeatures_NOT_PRESENT
} else if runtime.GOARCH == "arm64" {
// GICv2 can't support NMIs. There's no wonderful way to detect
// the version of the GIC, but /proc/interrupts will contain
// GICv2 in this case so we'll use that.
if interruptsOutput, err := os.ReadFile("/proc/interrupts"); err != nil {
// Give out a warning because /proc/interrupts should always exist.
logging.Infof(ctx, "Couldn't read /proc/interrupts: %v", err)
} else if strings.Contains(string(interruptsOutput), "GICv2") {
features.InterruptControllerInfo.NmiSupport = configpb.HardwareFeatures_NOT_PRESENT
}
// If the device tree indicates broken firmware then we know we
// don't support NMI.
if brokenGicOutput, err := exec.Command("find", "/proc/device-tree/", "-name", "mediatek,broken-save-restore-fw").Output(); err != nil {
// All arm64 should have /proc/device-tree/
logging.Infof(ctx, "Failed to search device-tree for GIC FW Quirk: %v", err)
} else if string(brokenGicOutput) != "" {
features.InterruptControllerInfo.NmiSupport = configpb.HardwareFeatures_NOT_PRESENT
}
}
features.Accelerometer = &configpb.HardwareFeatures_Accelerometer{}
func() {
out, err := crosConfig("/hardware-properties", "has-base-accelerometer")
if err != nil {
features.Accelerometer.BaseAccelerometer = configpb.HardwareFeatures_PRESENT_UNKNOWN
return
}
if out == "" || out == "false" {
features.Accelerometer.BaseAccelerometer = configpb.HardwareFeatures_NOT_PRESENT
return
}
features.Accelerometer.BaseAccelerometer = configpb.HardwareFeatures_PRESENT
}()
func() {
out, err := exec.CommandContext(cmdCtx, "lspci", "-mm").Output()
if err != nil {
features.FwConfig.IntelIsh = configpb.HardwareFeatures_PRESENT_UNKNOWN
return
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
if strings.Contains(line, "Integrated Sensor Hub") {
features.FwConfig.IntelIsh = configpb.HardwareFeatures_PRESENT
return
}
}
features.FwConfig.IntelIsh = configpb.HardwareFeatures_NOT_PRESENT
}()
return &protocol.HardwareFeatures{
HardwareFeatures: features,
DeprecatedDeviceConfig: config,
SoftwareConfig: swConfig,
}, nil
}
type lscpuEntry struct {
Field string `json:"field"` // includes trailing ":"
Data string `json:"data"`
}
type lscpuResult struct {
Entries []lscpuEntry `json:"lscpu"`
}
func (r *lscpuResult) find(name string) (data string, ok bool) {
for _, e := range r.Entries {
if e.Field == name {
return e.Data, true
}
}
return "", false
}
type cpuConfig struct {
cpuArch protocol.DeprecatedDeviceConfig_Architecture
soc protocol.DeprecatedDeviceConfig_SOC
flags []string
}
// cpuInfo returns a structure containing field data from the "lscpu" command
// which outputs CPU architecture information from "sysfs" and "/proc/cpuinfo".
func cpuInfo() (cpuConfig, error) {
errInfo := cpuConfig{protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, nil}
b, err := exec.Command("lscpu", "--json").Output()
if err != nil {
return errInfo, err
}
var parsed lscpuResult
if err := json.Unmarshal(b, &parsed); err != nil {
return errInfo, errors.Wrap(err, "failed to parse lscpu result")
}
flagsStr, _ := parsed.find("Flags:")
flags := strings.Split(flagsStr, " ")
arch, err := findArchitecture(parsed)
if err != nil {
return errInfo, errors.Wrap(err, "failed to find CPU architecture")
}
soc, err := findSOC(parsed)
if err != nil {
return cpuConfig{arch, protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, flags}, errors.Wrap(err, "failed to find SOC")
}
return cpuConfig{arch, soc, flags}, nil
}
// findArchitecture returns an architecture configuration based from parsed output
// data value of the "Architecture" field.
func findArchitecture(parsed lscpuResult) (protocol.DeprecatedDeviceConfig_Architecture, error) {
arch, ok := parsed.find("Architecture:")
if !ok {
return protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, errors.New("failed to find Architecture field")
}
switch arch {
case "x86_64":
return protocol.DeprecatedDeviceConfig_X86_64, nil
case "i686":
return protocol.DeprecatedDeviceConfig_X86, nil
case "aarch64":
return protocol.DeprecatedDeviceConfig_ARM64, nil
case "armv7l", "armv8l":
return protocol.DeprecatedDeviceConfig_ARM, nil
default:
return protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, errors.Errorf("unknown architecture: %q", arch)
}
}
// findSOC returns a SOC configuration based from parsed output data value of the
// "Vendor ID" and other related fields.
func findSOC(parsed lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) {
vendorID, ok := parsed.find("Vendor ID:")
if !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Vendor ID field")
}
switch vendorID {
case "ARM":
fallthrough
case "Qualcomm":
return findARMSOC()
case "GenuineIntel":
return findIntelSOC(&parsed)
case "AuthenticAMD":
return findAMDSOC(&parsed)
default:
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown vendor ID: %q", vendorID)
}
}
// findARMSOC returns an ARM SOC configuration based on "soc_id" from "/sys/bus/soc/devices".
func findARMSOC() (protocol.DeprecatedDeviceConfig_SOC, error) {
// Platforms with SMCCC >= 1.2 should implement get_soc functions in firmware
const socSysFS = "/sys/bus/soc/devices"
socs, err := ioutil.ReadDir(socSysFS)
if err == nil {
for _, soc := range socs {
c, err := ioutil.ReadFile(path.Join(socSysFS, soc.Name(), "soc_id"))
if err != nil || !strings.HasPrefix(string(c), "jep106:") {
continue
}
// Trim trailing \x00 and \n
socID := strings.TrimRight(string(c), "\x00\n")
switch socID {
case "jep106:0070:01a9":
fallthrough
case "jep106:0070:01ef":
fallthrough
case "jep106:0070:7180": // Used by older SC7180 firmware
return protocol.DeprecatedDeviceConfig_SOC_SC7180, nil
case "jep106:0070:7280":
return protocol.DeprecatedDeviceConfig_SOC_SC7280, nil
case "jep106:0426:8192":
return protocol.DeprecatedDeviceConfig_SOC_MT8192, nil
case "jep106:0426:8186":
return protocol.DeprecatedDeviceConfig_SOC_MT8186, nil
case "jep106:0426:8195":
return protocol.DeprecatedDeviceConfig_SOC_MT8195, nil
case "jep106:0426:8188":
return protocol.DeprecatedDeviceConfig_SOC_MT8188G, nil
default:
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown ARM model: %s", socID)
}
}
}
// For old platforms with SMCCC < 1.2: mt8173, mt8183, rk3288, rk3399,
// match with their compatible string. Obtain the string after the last , and trim \x00.
// Example: google,krane-sku176\x00google,krane\x00mediatek,mt8183\x00
c, err := ioutil.ReadFile("/sys/firmware/devicetree/base/compatible")
if err != nil {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Wrap(err, "failed to find ARM model")
}
compatible := string(c)
model := strings.ToLower(compatible[strings.LastIndex(compatible, ",")+1:])
model = strings.TrimRight(model, "\x00")
switch model {
case "mt8173":
return protocol.DeprecatedDeviceConfig_SOC_MT8173, nil
case "mt8183":
return protocol.DeprecatedDeviceConfig_SOC_MT8183, nil
case "rk3288":
return protocol.DeprecatedDeviceConfig_SOC_RK3288, nil
case "rk3399":
return protocol.DeprecatedDeviceConfig_SOC_RK3399, nil
default:
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown ARM model: %s", model)
}
}
// findIntelSOC returns an Intel SOC configuration based on "CPU family", "Model",
// and "Model name" fields.
func findIntelSOC(parsed *lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) {
if family, ok := parsed.find("CPU family:"); !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel family")
} else if family != "6" {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown Intel family: %s", family)
}
modelStr, ok := parsed.find("Model:")
if !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model")
}
model, err := strconv.ParseInt(modelStr, 10, 64)
if err != nil {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Wrapf(err, "failed to parse Intel model: %q", modelStr)
}
switch model {
case INTEL_FAM6_KABYLAKE_L:
// AMBERLAKE_Y, COMET_LAKE_U, WHISKEY_LAKE_U, KABYLAKE_U, KABYLAKE_U_R, and
// KABYLAKE_Y share the same model. Parse model name.
// Note that Pentium brand is unsupported.
modelName, ok := parsed.find("Model name:")
if !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model name")
}
for _, e := range []struct {
soc protocol.DeprecatedDeviceConfig_SOC
ptn string
}{
// https://ark.intel.com/content/www/us/en/ark/products/codename/186968/amber-lake-y.html
{protocol.DeprecatedDeviceConfig_SOC_AMBERLAKE_Y, `Core.* [mi]\d-(10|8)\d{3}Y`},
// https://ark.intel.com/content/www/us/en/ark/products/codename/90354/comet-lake.html
{protocol.DeprecatedDeviceConfig_SOC_COMET_LAKE_U, `Core.* i\d-10\d{3}U|Celeron.* 5[23]05U|Intel\(R\) Pentium\(R\) CPU 6405U`},
// https://ark.intel.com/content/www/us/en/ark/products/codename/135883/whiskey-lake.html
{protocol.DeprecatedDeviceConfig_SOC_WHISKEY_LAKE_U, `Core.* i\d-8\d{2}5U|Celeron.* 4[23]05U`},
// https://ark.intel.com/content/www/us/en/ark/products/codename/82879/kaby-lake.html
{protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_U, `Core.* i\d-7\d{3}U|Celeron.* 3[89]65U`},
{protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_Y, `Core.* [mi]\d-7Y\d{2}|Celeron.* 3965Y`},
// https://ark.intel.com/content/www/us/en/ark/products/codename/126287/kaby-lake-r.html
{protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_U_R, `Core.* i\d-8\d{2}0U|Celeron.* 3867U`},
} {
r := regexp.MustCompile(e.ptn)
if r.MatchString(modelName) {
return e.soc, nil
}
}
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown model name: %s", modelName)
case INTEL_FAM6_ICELAKE_L:
return protocol.DeprecatedDeviceConfig_SOC_ICE_LAKE_Y, nil
case INTEL_FAM6_ATOM_GOLDMONT_PLUS:
return protocol.DeprecatedDeviceConfig_SOC_GEMINI_LAKE, nil
case INTEL_FAM6_ATOM_TREMONT_L:
return protocol.DeprecatedDeviceConfig_SOC_JASPER_LAKE, nil
case INTEL_FAM6_TIGERLAKE_L:
return protocol.DeprecatedDeviceConfig_SOC_TIGER_LAKE, nil
case INTEL_FAM6_ALDERLAKE_L:
return protocol.DeprecatedDeviceConfig_SOC_ALDER_LAKE, nil
case INTEL_FAM6_METEORLAKE_L:
return protocol.DeprecatedDeviceConfig_SOC_METEOR_LAKE, nil
case INTEL_FAM6_CANNONLAKE_L:
return protocol.DeprecatedDeviceConfig_SOC_CANNON_LAKE_Y, nil
case INTEL_FAM6_ATOM_GOLDMONT:
return protocol.DeprecatedDeviceConfig_SOC_APOLLO_LAKE, nil
case INTEL_FAM6_SKYLAKE_L:
// SKYLAKE_U and SKYLAKE_Y share the same model. Parse model name.
modelName, ok := parsed.find("Model name:")
if !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model name")
}
for _, e := range []struct {
soc protocol.DeprecatedDeviceConfig_SOC
ptn string
}{
// https://ark.intel.com/content/www/us/en/ark/products/codename/37572/skylake.html
{protocol.DeprecatedDeviceConfig_SOC_SKYLAKE_U, `Core.* i\d-6\d{3}U|Celeron.*3[89]55U`},
{protocol.DeprecatedDeviceConfig_SOC_SKYLAKE_Y, `Core.* m\d-6Y\d{2}`},
} {
r := regexp.MustCompile(e.ptn)
if r.MatchString(modelName) {
return e.soc, nil
}
}
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown model name: %s", modelName)
case INTEL_FAM6_ATOM_AIRMONT:
return protocol.DeprecatedDeviceConfig_SOC_BRASWELL, nil
case INTEL_FAM6_BROADWELL:
return protocol.DeprecatedDeviceConfig_SOC_BROADWELL, nil
case INTEL_FAM6_HASWELL, INTEL_FAM6_HASWELL_L:
return protocol.DeprecatedDeviceConfig_SOC_HASWELL, nil
case INTEL_FAM6_IVYBRIDGE:
return protocol.DeprecatedDeviceConfig_SOC_IVY_BRIDGE, nil
case INTEL_FAM6_ATOM_SILVERMONT:
return protocol.DeprecatedDeviceConfig_SOC_BAY_TRAIL, nil
case INTEL_FAM6_SANDYBRIDGE:
return protocol.DeprecatedDeviceConfig_SOC_SANDY_BRIDGE, nil
case INTEL_FAM6_ATOM_BONNELL:
return protocol.DeprecatedDeviceConfig_SOC_PINE_TRAIL, nil
default:
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown Intel model: %d", model)
}
}
// findAMDSOC returns an AMD SOC configuration based on "Model" field.
func findAMDSOC(parsed *lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) {
model, ok := parsed.find("Model:")
if !ok {
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find AMD model")
}
if model == "112" {
return protocol.DeprecatedDeviceConfig_SOC_STONEY_RIDGE, nil
}
if family, ok := parsed.find("CPU family:"); ok {
if family == "23" {
if model == "24" || model == "32" {
return protocol.DeprecatedDeviceConfig_SOC_PICASSO, nil
} else if model == "160" {
return protocol.DeprecatedDeviceConfig_SOC_MENDOCINO, nil
}
} else if family == "25" {
if model == "80" {
return protocol.DeprecatedDeviceConfig_SOC_CEZANNE, nil
} else if model == "116" {
return protocol.DeprecatedDeviceConfig_SOC_PHOENIX, nil
}
}
}
return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown AMD model: %s", model)
}
// lsblk command output differs depending on the version. Attempt parsing in multiple ways to accept all the cases.
// lsblk from util-linux 2.32
type blockDevices2_32 struct {
Name string `json:"name"`
Removable string `json:"rm"`
Size string `json:"size"`
Type string `json:"type"`
}
type lsblkRoot2_32 struct {
BlockDevices []blockDevices2_32 `json:"blockdevices"`
}
// lsblk from util-linux 2.36.1
type blockDevices struct {
Name string `json:"name"`
Removable bool `json:"rm"`
Size int64 `json:"size"`
Type string `json:"type"`
}
type lsblkRoot struct {
BlockDevices []blockDevices `json:"blockdevices"`
}
func parseLsblk2_32(jsonData []byte) (*lsblkRoot, error) {
var old lsblkRoot2_32
err := json.Unmarshal(jsonData, &old)
if err != nil {
return nil, err
}
var r lsblkRoot
for _, e := range old.BlockDevices {
s, err := strconv.ParseInt(e.Size, 10, 64)
if err != nil {
return nil, err
}
var rm bool
if e.Removable == "0" || e.Removable == "" {
rm = false
} else if e.Removable == "1" {
rm = true
} else {
return nil, fmt.Errorf("unknown value for rm: %q", e.Removable)
}
r.BlockDevices = append(r.BlockDevices, blockDevices{
Name: e.Name,
Removable: rm,
Size: s,
Type: e.Type,
})
}
return &r, nil
}
func parseLsblk2_36(jsonData []byte) (*lsblkRoot, error) {
var r lsblkRoot
err := json.Unmarshal(jsonData, &r)
if err != nil {
return nil, err
}
return &r, nil
}
func parseLsblk(jsonData []byte) (*lsblkRoot, error) {
var errs []error
parsers := []func([]byte) (*lsblkRoot, error){parseLsblk2_36, parseLsblk2_32}
for _, p := range parsers {
r, err := p(jsonData)
if err == nil {
return r, nil
}
errs = append(errs, err)
}
var errStrings []string
for _, e := range errs {
errStrings = append(errStrings, e.Error())
}
return nil, fmt.Errorf("failed to parse JSON in all the expected formats: %s", strings.Join(errStrings, "; "))
}
// findDiskSize detects the size of the storage device from "lsblk -J -b" output in bytes.
// When there are multiple disks, returns the size of the largest one.
func findDiskSize(jsonData []byte) (int64, error) {
r, err := parseLsblk(jsonData)
if err != nil {
return 0, err
}
var maxSize int64
var found bool
for _, x := range r.BlockDevices {
if x.Type == "disk" && !x.Removable && !strings.HasPrefix(x.Name, "zram") {
found = true
if x.Size > maxSize {
maxSize = x.Size
}
}
}
if !found {
return 0, errors.New("no disk device found")
}
return maxSize, nil
}
// findMemorySize parses a content of /proc/meminfo and returns the total memory size in bytes.
func findMemorySize(meminfo []byte) (int64, error) {
r := bytes.NewReader(meminfo)
sc := bufio.NewScanner(r)
for sc.Scan() {
line := sc.Text()
tokens := strings.SplitN(line, ":", 2)
if len(tokens) != 2 || strings.TrimSpace(tokens[0]) != "MemTotal" {
continue
}
cap := strings.SplitN(strings.TrimSpace(tokens[1]), " ", 2)
if len(cap) != 2 {
return 0, fmt.Errorf("unexpected line format: input=%s", line)
}
if cap[1] != "kB" {
return 0, fmt.Errorf("unexpected unit: got %s, want kB; input=%s", cap[1], line)
}
val, err := strconv.ParseInt(cap[0], 10, 64)
if err != nil {
return 0, err
}
// meminfo reported kB is 2^10 bytes not 10^3.
return val << 10, nil
}
return 0, fmt.Errorf("MemTotal not found; input=%q", string(meminfo))
}
func matchCrasDeviceType(pattern string) (*configpb.HardwareFeatures_Count, error) {
b, err := exec.Command("cras_test_client").Output()
if err != nil {
return nil, err
}
if regexp.MustCompile(pattern).Match(b) {
return &configpb.HardwareFeatures_Count{Value: 1}, nil
}
return &configpb.HardwareFeatures_Count{Value: 0}, nil
}
// findSpeakerAmplifier parses a content of in "/sys/kernel/debug/asoc/components"
// and returns the speaker amplifier used.
func findSpeakerAmplifier() (*configpb.Component_Amplifier, error) {
// This sys path exists only on kernel >=4.14. But we don't
// target amp tests on earlier kernels.
f, err := os.Open("/sys/kernel/debug/asoc/components")
if err != nil {
return &configpb.Component_Amplifier{}, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if amp, found := matchSpeakerAmplifier(scanner.Text()); found {
if enabled, err := bootTimeCalibration(); err == nil && enabled {
amp.Features = append(amp.Features, configpb.Component_Amplifier_BOOT_TIME_CALIBRATION)
}
return amp, err
}
}
return &configpb.Component_Amplifier{}, nil
}
var ampsRegexp = map[string]*regexp.Regexp{
configpb.HardwareFeatures_Audio_MAX98357.String(): regexp.MustCompile(`^(i2c-)?ma?x98357a?((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_MAX98373.String(): regexp.MustCompile(`^(i2c-)?ma?x98373((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_MAX98360.String(): regexp.MustCompile(`^(i2c-)?ma?x98360a?((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_RT1015.String(): regexp.MustCompile(`^(i2c-)?((rtl?)|(10ec))?1015(\.\d*)?((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_RT1015P.String(): regexp.MustCompile(`^(i2c-)?(rtl?)?(10ec)?1015p(\.\d*)?((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_ALC1011.String(): regexp.MustCompile(`^(i2c-)?((rtl?)|(10ec))?1011(\.\d*)?((:\d*)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_MAX98390.String(): regexp.MustCompile(`^(i2c-)?ma?x98390((:\d*)|(\.\d-\d+)|([_-]?\d*))?$`),
configpb.HardwareFeatures_Audio_CS35L41.String(): regexp.MustCompile(`^(i2c-)?csc3541((:\d*)|([_-]?\d*))?$`),
}
func matchSpeakerAmplifier(line string) (*configpb.Component_Amplifier, bool) {
for amp, re := range ampsRegexp {
if re.MatchString(strings.ToLower(line)) {
return &configpb.Component_Amplifier{Name: amp}, true
}
}
return nil, false
}
// bootTimeCalibration returns whether the boot time calibration is
// enabled by parsing the sound_card_init config.
func bootTimeCalibration() (bool, error) {
config, err := crosConfig("/audio/main", "sound-card-init-conf")
if err != nil {
return false, err
}
path := "/etc/sound_card_init/" + config
if _, err := os.Stat(path); err != nil {
// Regard config non-existence as boot_time_calibration disabled.
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return false, errors.New("failed to read sound_card_init config")
}
return isBootTimeCalibrationEnabled(string(b))
}
func isBootTimeCalibrationEnabled(s string) (bool, error) {
re := regexp.MustCompile(`boot_time_calibration_enabled\s*?:\s*?(true|false)`)
match := re.FindStringSubmatch(s)
if match == nil {
return false, errors.New("invalid sound_card_init config")
}
enabled := match[1]
return enabled == "true", nil
}
func wifiFeatures() (*configpb.HardwareFeatures_Wifi, error) {
dev, err := wlan.DeviceInfo()
if err != nil {
return nil, errors.Wrap(err, "failed to get device")
}
_, err = exec.Command("vpd", "-g", "wifi_sar").Output()
vpdSarFound := err == nil
return &configpb.HardwareFeatures_Wifi{
WifiChips: []configpb.HardwareFeatures_Wifi_WifiChip{
configpb.HardwareFeatures_Wifi_WifiChip(dev.ID)},
WifiVpdSar: vpdSarFound,
}, nil
}
// hasBuiltinAudio tells if a given form factor has built-in audio devices
func hasBuiltinAudio(ctx context.Context, ff configpb.HardwareFeatures_FormFactor_FormFactorType) bool {
switch ff {
case configpb.HardwareFeatures_FormFactor_CLAMSHELL,
configpb.HardwareFeatures_FormFactor_CONVERTIBLE,
configpb.HardwareFeatures_FormFactor_DETACHABLE,
configpb.HardwareFeatures_FormFactor_CHROMEBASE,
configpb.HardwareFeatures_FormFactor_CHROMESLATE:
return true
case configpb.HardwareFeatures_FormFactor_CHROMEBIT,
configpb.HardwareFeatures_FormFactor_CHROMEBOX:
return false
default:
logging.Infof(ctx, "Unknown form factor: %s", ff)
return false
}
}
// cameraFeatures returns the list of configured camera features for the given
// |model| by inspecting the on-device feature config file.
func cameraFeatures(model string) ([]string, error) {
type modelConfig map[string]struct {
FeatureSet []map[string]interface{} `json:"feature_set"`
}
const featureProfilePath = "/etc/camera/feature_profile.json"
jsonInput, err := ioutil.ReadFile(featureProfilePath)
if err != nil {
return nil, errors.Wrap(err, "cannot load feature profile config")
}
conf := make(modelConfig)
if err := json.Unmarshal(jsonInput, &conf); err != nil {
return nil, errors.Wrap(err, "cannot unmarshal feature profile config")
}
c, ok := conf[model]
if !ok {
return nil, errors.Errorf("feature set config for model %s doesn't exist", model)
}
featureSet := make(map[string]bool)
for _, f := range c.FeatureSet {
var v interface{}
// The "type" attribute is always a string.
if v, ok = f["type"]; !ok {
continue
}
// There can be multiple entries for a feature with different
// constraints.
if _, ok := featureSet[v.(string)]; !ok {
featureSet[v.(string)] = true
}
}
var ret []string
for k := range featureSet {
ret = append(ret, k)
}
return ret, nil
}
// cameraUsbEnumerated returns the list of enumerated USB cameras.
func cameraUsbEnumerated() ([]string, error) {
var ids []string
out, err := exec.Command("media_v4l2_test", "--list_builtin_usbcam").Output()
if err != nil {
return nil, errors.Wrap(err, "failed to run media_v4l2_test")
}
for _, name := range strings.Fields(string(out)) {
path := "/sys/class/video4linux/" + filepath.Base(name) + "/device"
path, err := filepath.EvalSymlinks(path)
if err != nil {
return nil, err
}
path = filepath.Dir(string(path))
out, err := os.ReadFile(path + "/idVendor")
if err != nil {
return nil, err
}
vid := strings.TrimSuffix(string(out), "\n")
out, err = os.ReadFile(path + "/idProduct")
if err != nil {
return nil, err
}
pid := strings.TrimSuffix(string(out), "\n")
ids = append(ids, vid+":"+pid)
}
return ids, nil
}
// cameraEnumerated returns whether camera devices have been all enumerated, together with
// the list of enumerated USB cameras.
func cameraEnumerated() (bool, []string, error) {
countStr, err := crosConfig("/camera", "count")
if err != nil {
return false, nil, errors.Wrap(err, "failed to get count from crosConfig")
}
count, err := strconv.Atoi(countStr)
if err != nil {
return false, nil, errors.Wrap(err, "failed to parse crosConfig output")
}
usbCamIds, err := cameraUsbEnumerated()
if err != nil {
return false, nil, errors.Wrap(err, "failed to get enumerated USB cams")
}
usbCams := len(usbCamIds)
mipiCams := func() int {
out, err := exec.Command("cros-camera-tool", "modules", "list").Output()
if err != nil {
return 0
}
var cams []map[string]string
if err := json.Unmarshal(out, &cams); err != nil {
return 0
}
return len(cams)
}()
return usbCams+mipiCams == count, usbCamIds, nil
}
// findGSCKeyID parses a content of "gsctool -a -f -M" and return a required key
func findGSCKeyID(str, keyIDType string) (string, error) {
re := regexp.MustCompile(`(?m)^keyids: RO (0x.+), RW (0x.+)$`)
switch keyIDType {
case "RO":
keyID := re.FindAllStringSubmatch(str, -1)[0][1]
return keyID, nil
case "RW":
keyID := re.FindAllStringSubmatch(str, -1)[0][2]
return keyID, nil
default:
return "", errors.Errorf("Unknown keyId type %s", keyIDType)
}
}
// containsGSCKeyID returns true if reqKeyID is in the keyIDs
func containsGSCKeyID(keyIDs []GSCKeyID, reqKeyID GSCKeyID) bool {
for _, keyID := range keyIDs {
if keyID == reqKeyID {
return true
}
}
return false
}
// For mocking.
var flashromExtractCoreBootCmd = func(ctx context.Context, corebootBinName string) error {
return exec.CommandContext(ctx, "flashrom", "-p", "internal", "-r", "-i", fmt.Sprintf("FW_MAIN_A:%s", corebootBinName)).Run()
}
var cbfsToolExtractCmd = func(ctx context.Context, corebootBinName, fileName, outputPath string) error {
return exec.CommandContext(ctx, "cbfstool", corebootBinName, "extract", "-n", fileName, "-f", outputPath).Run()
}
var configLineRegexp = regexp.MustCompile(`^(# )?(CONFIG\S*)(=(y)| (is not set))`)
// parseKConfigs updates the provided HardwareFeatures with the features found
// by reading reading through the BIOS Kconfigs.
func parseKConfigs(ctx context.Context, features *configpb.HardwareFeatures) error {
corebootBin, err := ioutil.TempFile("/var/tmp", "")
if err != nil {
return errors.Wrap(err, "failed to create temp file")
}
corebootBin.Close()
defer os.Remove(corebootBin.Name())
fwConfig, err := ioutil.TempFile("/var/tmp", "")
if err != nil {
return errors.Wrap(err, "failed to create temp file")
}
fwConfig.Close()
defer os.Remove(fwConfig.Name())
if err := flashromExtractCoreBootCmd(ctx, corebootBin.Name()); err != nil {
return errors.Wrap(err, "failed to extract FW_MAIN_A bios section")
}
if err := cbfsToolExtractCmd(ctx, corebootBin.Name(), "config", fwConfig.Name()); err != nil {
return errors.Wrap(err, "failed to extract bios Kconfig file")
}
inFile, err := os.Open(fwConfig.Name())
if err != nil {
return errors.Wrap(err, "failed to read bios Kconfig file")
}
defer inFile.Close()
importantConfigs := map[string]*configpb.HardwareFeatures_Present{
"CONFIG_MAINBOARD_HAS_EARLY_LIBGFXINIT": &features.FwConfig.MainboardHasEarlyLibgfxinit,
"CONFIG_VBOOT_CBFS_INTEGRATION": &features.FwConfig.VbootCbfsIntegration,
"CONFIG_BMP_LOGO": &features.FwConfig.BmpLogo,
"CONFIG_CHROMEOS_FW_SPLASH_SCREEN": &features.FwConfig.FwSplashScreen,
}
scanner := bufio.NewScanner(inFile)
for scanner.Scan() {
line := scanner.Text()
if match := configLineRegexp.FindStringSubmatch(line); match != nil {
if val, ok := importantConfigs[match[2]]; ok {
if match[4] == "y" {
*val = configpb.HardwareFeatures_PRESENT
} else if match[5] == "is not set" {
*val = configpb.HardwareFeatures_NOT_PRESENT
}
}
}
}
return nil
}
func parseECBuildConfig(ctx context.Context, features *configpb.HardwareFeatures) error {
corebootBin, err := ioutil.TempFile("/var/tmp", "")
if err != nil {
return errors.Wrap(err, "failed to create temp file")
}
corebootBin.Close()
defer os.Remove(corebootBin.Name())
ecConfig, err := ioutil.TempFile("/var/tmp", "")
if err != nil {
return errors.Wrap(err, "failed to create temp file")
}
ecConfig.Close()
defer os.Remove(ecConfig.Name())
if err := flashromExtractCoreBootCmd(ctx, corebootBin.Name()); err != nil {
return errors.Wrap(err, "failed to extract FW_MAIN_A bios section")
}
if err := cbfsToolExtractCmd(ctx, corebootBin.Name(), "ecrw.config", ecConfig.Name()); err != nil {
return errors.Wrap(err, "failed to extract ec config file")
}
inFile, err := os.Open(ecConfig.Name())
if err != nil {
return errors.Wrap(err, "failed to read ec Kconfig file")
}
defer inFile.Close()
ecBuildConfig := make(map[string]configpb.HardwareFeatures_Present)
scanner := bufio.NewScanner(inFile)
for scanner.Scan() {
line := scanner.Text()
if match := configLineRegexp.FindStringSubmatch(line); match != nil {
if match[4] == "y" {
ecBuildConfig[match[2]] = configpb.HardwareFeatures_PRESENT
} else if match[5] == "is not set" {
ecBuildConfig[match[2]] = configpb.HardwareFeatures_NOT_PRESENT
} else {
return errors.Errorf("Malformed config line: %s", line)
}
}
}
features.EmbeddedController.BuildConfig = ecBuildConfig
return nil
}
// hasRuntimeProbeConfig returns true if the corresponding probe config for the
// model of the DUT exists. The err is set if the error os.Stat returns is not
// fs.ErrNotExist.
func hasRuntimeProbeConfig(model string, encrypted bool) (bool, error) {
probeConfigRelPath := "etc/runtime_probe/" + model + "/probe_config.json"
if encrypted {
probeConfigRelPath += ".enc"
}
configRoots := []string{
"/usr/local/",
"/",
}
for _, configRoot := range configRoots {
_, err := os.Stat(configRoot + probeConfigRelPath)
if err == nil {
return true, nil
}
if !os.IsNotExist(err) {
return false, err
}
}
return false, nil
}
// featureLevel returns the feature level number of the DUT.
func featureLevel() (level uint32, err error) {
cmd := exec.Command("feature_explorer", "--feature_level")
out, err := cmd.CombinedOutput()
if err != nil {
return 0, errors.Wrapf(err, "failed to call feature_explorer %s", string(out))
}
value, err := strconv.ParseUint(strings.TrimSpace(string(out)), 0, 64)
if err != nil {
return 0, errors.Wrapf(err, "failed to parse feature level %s", string(out))
}
return uint32(value), nil
}
func oemName() string {
if out, err := crosConfig("/branding", "oem-name"); err == nil {
if out != "" {
return out
}
}
if out, err := os.ReadFile("/sys/firmware/vpd/ro/oem_name"); err == nil {
if string(out) != "" {
return string(out)
}
}
return ""
}