github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/crosbundle/hardware.go (about) 1 // Copyright 2021 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package crosbundle 6 7 import ( 8 "bufio" 9 "bytes" 10 "context" 11 "encoding/json" 12 "fmt" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path" 17 "path/filepath" 18 "regexp" 19 "strconv" 20 "strings" 21 "time" 22 23 configpb "go.chromium.org/chromiumos/config/go/api" 24 25 "go.chromium.org/tast/core/errors" 26 "go.chromium.org/tast/core/internal/logging" 27 "go.chromium.org/tast/core/testing/wlan" 28 29 "go.chromium.org/tast/core/framework/protocol" 30 ) 31 32 // GSCKeyID is a hex value that represents a key used to sign a GSC image. 33 type GSCKeyID string 34 35 // prodRWGSCKeyIDs is a slice with production keyIDs used to sign the RW GSC image. 36 var prodRWGSCKeyIDs = []GSCKeyID{"0x87b73b67", "0xde88588d"} 37 38 func crosConfig(path, prop string) (string, error) { 39 cmd := exec.Command("cros_config", path, prop) 40 var buf bytes.Buffer 41 cmd.Stderr = &buf 42 b, err := cmd.Output() 43 if err != nil { 44 return "", errors.Errorf("cros_config failed (stderr: %q): %v", buf.Bytes(), err) 45 } 46 return string(b), nil 47 } 48 49 // detectHardwareFeatures returns a device.Config and api.HardwareFeatures instances 50 // some of whose members are filled based on runtime information. 51 func detectHardwareFeatures(ctx context.Context) (*protocol.HardwareFeatures, error) { 52 platform, err := func() (string, error) { 53 out, err := crosConfig("/identity", "platform-name") 54 if err != nil { 55 return "", err 56 } 57 return out, nil 58 }() 59 if err != nil { 60 logging.Infof(ctx, "Unknown platform-id: %v", err) 61 } 62 model, err := func() (string, error) { 63 out, err := crosConfig("/", "name") 64 if err != nil { 65 return "", err 66 } 67 return out, nil 68 }() 69 if err != nil { 70 logging.Infof(ctx, "Unknown model-id: %v", err) 71 } 72 brand, err := func() (string, error) { 73 out, err := crosConfig("/", "brand-code") 74 if err != nil { 75 return "", err 76 } 77 return out, nil 78 }() 79 if err != nil { 80 logging.Infof(ctx, "Unknown brand-id: %v", err) 81 } 82 83 info, err := cpuInfo() 84 if err != nil { 85 logging.Infof(ctx, "Unknown CPU information: %v", err) 86 } 87 88 vboot2, err := func() (bool, error) { 89 out, err := exec.Command("crossystem", "fw_vboot2").Output() 90 if err != nil { 91 return false, err 92 } 93 return strings.TrimSpace(string(out)) == "1", nil 94 }() 95 if err != nil { 96 logging.Infof(ctx, "Unknown vboot2 info: %v", err) 97 } 98 99 config := &protocol.DeprecatedDeviceConfig{ 100 Id: &protocol.DeprecatedConfigId{ 101 Platform: platform, 102 Model: model, 103 Brand: brand, 104 }, 105 Soc: info.soc, 106 Cpu: info.cpuArch, 107 HasNvmeSelfTest: false, 108 HasVboot2: vboot2, 109 } 110 features := &configpb.HardwareFeatures{ 111 Screen: &configpb.HardwareFeatures_Screen{}, 112 Fingerprint: &configpb.HardwareFeatures_Fingerprint{}, 113 EmbeddedController: &configpb.HardwareFeatures_EmbeddedController{}, 114 Storage: &configpb.HardwareFeatures_Storage{}, 115 Memory: &configpb.HardwareFeatures_Memory{}, 116 Audio: &configpb.HardwareFeatures_Audio{}, 117 PrivacyScreen: &configpb.HardwareFeatures_PrivacyScreen{}, 118 Soc: &configpb.HardwareFeatures_Soc{}, 119 Touchpad: &configpb.HardwareFeatures_Touchpad{}, 120 Keyboard: &configpb.HardwareFeatures_Keyboard{}, 121 FormFactor: &configpb.HardwareFeatures_FormFactor{}, 122 DpConverter: &configpb.HardwareFeatures_DisplayPortConverter{}, 123 Wifi: &configpb.HardwareFeatures_Wifi{}, 124 Cellular: &configpb.HardwareFeatures_Cellular{}, 125 Bluetooth: &configpb.HardwareFeatures_Bluetooth{}, 126 Hps: &configpb.HardwareFeatures_Hps{}, 127 Battery: &configpb.HardwareFeatures_Battery{}, 128 Camera: &configpb.HardwareFeatures_Camera{}, 129 TrustedPlatformModule: &configpb.HardwareFeatures_TrustedPlatformModule{}, 130 FwConfig: &configpb.HardwareFeatures_FirmwareConfiguration{}, 131 RuntimeProbeConfig: &configpb.HardwareFeatures_RuntimeProbeConfig{}, 132 HardwareProbeConfig: &configpb.HardwareFeatures_HardwareProbe{}, 133 Display: &configpb.HardwareFeatures_Display{}, 134 } 135 136 formFactor, err := func() (string, error) { 137 out, err := crosConfig("/hardware-properties", "form-factor") 138 if err != nil { 139 return "", err 140 } 141 return out, nil 142 }() 143 if err != nil { 144 logging.Infof(ctx, "Unknown /hardware-properties/form-factor: %v", err) 145 } 146 lidConvertible, err := func() (string, error) { 147 out, err := crosConfig("/hardware-properties", "is-lid-convertible") 148 if err != nil { 149 return "", err 150 } 151 return out, nil 152 }() 153 if err != nil { 154 logging.Infof(ctx, "Unknown /hardware-properties/is-lid-convertible: %v", err) 155 } 156 features.Wifi, err = wifiFeatures() 157 if err != nil { 158 logging.Infof(ctx, "Error getting Wifi: %v", err) 159 } 160 161 // Battery 162 noBatteryBootSupported, err := func() (bool, error) { 163 out, err := crosConfig("/battery", "no-battery-boot-supported") 164 if err != nil { 165 return false, err 166 } 167 return out == "true", nil 168 }() 169 features.Battery.NoBatteryBootSupported = noBatteryBootSupported 170 171 detachableBasePath, err := func() (string, error) { 172 out, err := crosConfig("/detachable-base", "usb-path") 173 if err != nil { 174 return "", err 175 } 176 return out, nil 177 }() 178 if err != nil { 179 logging.Infof(ctx, "Unknown /detachable-base/usbpath: %v", err) 180 } 181 182 checkHammer := func() bool { 183 hammer, err := exec.Command("udevadm", "info", "--export-db").Output() 184 if err != nil { 185 return false 186 } 187 return regexp.MustCompile(`(?m)^E: ID_MODEL=Hammer$`).Match(hammer) 188 } 189 190 if formFactorEnum, ok := configpb.HardwareFeatures_FormFactor_FormFactorType_value[formFactor]; ok { 191 features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_FormFactorType(formFactorEnum) 192 } else if formFactor == "CHROMEBOOK" { 193 // Gru devices have formFactor=="CHROMEBOOK", detachableBasePath=="", lidConvertible="", but are really CHROMESLATE 194 if platform == "Gru" { 195 features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CHROMESLATE 196 } else if detachableBasePath != "" { 197 features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_DETACHABLE 198 } else if lidConvertible == "true" { 199 features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CONVERTIBLE 200 } else { 201 features.FormFactor.FormFactor = configpb.HardwareFeatures_FormFactor_CLAMSHELL 202 } 203 } else { 204 logging.Infof(ctx, "Form factor not found: %v", formFactor) 205 } 206 switch features.FormFactor.FormFactor { 207 case configpb.HardwareFeatures_FormFactor_CHROMEBASE, configpb.HardwareFeatures_FormFactor_CHROMEBIT, configpb.HardwareFeatures_FormFactor_CHROMEBOX, configpb.HardwareFeatures_FormFactor_CHROMESLATE: 208 features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_NONE 209 case configpb.HardwareFeatures_FormFactor_CLAMSHELL, configpb.HardwareFeatures_FormFactor_CONVERTIBLE: 210 features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_INTERNAL 211 case configpb.HardwareFeatures_FormFactor_DETACHABLE: 212 // When the dut is a detachable, check whether hammer exists 213 // to determine if a removable keyboard is connected. 214 hasHammer := checkHammer() 215 if hasHammer { 216 features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_DETACHABLE 217 } else { 218 features.Keyboard.KeyboardType = configpb.HardwareFeatures_Keyboard_NONE 219 } 220 } 221 222 keyboardBacklight, err := func() (bool, error) { 223 out, err := crosConfig("/keyboard", "backlight") 224 if err != nil { 225 return false, err 226 } 227 return out == "true", nil 228 }() 229 if err != nil { 230 logging.Infof(ctx, "Unknown /keyboard/backlight: %v", err) 231 } 232 233 hasKeyboardBacklight, err := func() (string, error) { 234 out, err := crosConfig("/power", "has-keyboard-backlight") 235 if err != nil { 236 return "", err 237 } 238 return out, nil 239 }() 240 if err != nil { 241 logging.Infof(ctx, "Unknown /power/has-keyboard-backlight: %v", err) 242 } 243 244 hasKeyboardBacklightUnderPowerManager, err := func() (bool, error) { 245 const fileName = "/usr/share/power_manager/has_keyboard_backlight" 246 content, err := ioutil.ReadFile(fileName) 247 if err != nil { 248 if os.IsNotExist(err) { 249 return false, nil 250 } 251 return false, errors.Errorf("failed to read file %q: %v", fileName, err) 252 } 253 return strings.TrimSuffix(string(content), "\n") == "1", nil 254 }() 255 if err != nil { 256 logging.Infof(ctx, "Unknown /usr/share/power_manager: %v", err) 257 } 258 259 switch hasKeyboardBacklight { 260 case "1": 261 features.Keyboard.Backlight = configpb.HardwareFeatures_PRESENT 262 case "": 263 if keyboardBacklight || hasKeyboardBacklightUnderPowerManager { 264 features.Keyboard.Backlight = configpb.HardwareFeatures_PRESENT 265 } 266 default: 267 features.Keyboard.Backlight = configpb.HardwareFeatures_NOT_PRESENT 268 } 269 270 checkForConnector := func(connectorRegexp string) bool { 271 const drmSysFS = "/sys/class/drm" 272 273 drmFiles, err := ioutil.ReadDir(drmSysFS) 274 if err != nil { 275 return false 276 } 277 278 cardMatch := regexp.MustCompile(connectorRegexp) 279 for _, file := range drmFiles { 280 fileName := file.Name() 281 282 if cardMatch.MatchString(fileName) { 283 if cardConnected, err := ioutil.ReadFile(path.Join(drmSysFS, fileName, "status")); err != nil { 284 if !os.IsNotExist(err) { 285 return false 286 } 287 } else { 288 return strings.HasPrefix(string(cardConnected), "connected") 289 } 290 } 291 } 292 293 // No indication of internal panel connected and recognised. 294 return false 295 } 296 297 // eDP displays show up as card*-eDP-1 298 // MIPI panels show up as card*-DSI-1 299 // Virtual displays in VMs show up as card*-Virtual-1 300 internalDisplayRegexp := `^card[0-9]-(eDP|DSI|Virtual)-1$` 301 hasInternalDisplay := checkForConnector(internalDisplayRegexp) 302 if hasInternalDisplay { 303 features.Screen.PanelProperties = &configpb.Component_DisplayPanel_Properties{} 304 } 305 306 // Display ports show up as card*-DP-[0-9] 307 // HDMI ports show up as card*-HDMI-A-1 308 // DVI ports show up as card*-DVI-I-1 309 externalDisplayRegexp := `^card[0-9]-(DP|HDMI-A|DVI-I)-[0-9]$` 310 hasExternalDisplay := checkForConnector(externalDisplayRegexp) 311 switch { 312 case hasInternalDisplay && hasExternalDisplay: 313 features.Display.Type = configpb.HardwareFeatures_Display_TYPE_INTERNAL_EXTERNAL 314 case hasExternalDisplay: 315 features.Display.Type = configpb.HardwareFeatures_Display_TYPE_EXTERNAL 316 case hasInternalDisplay: 317 features.Display.Type = configpb.HardwareFeatures_Display_TYPE_INTERNAL 318 } 319 320 hasTouchScreen := func() bool { 321 b, err := exec.Command("udevadm", "info", "--export-db").Output() 322 if err != nil { 323 return false 324 } 325 return regexp.MustCompile(`(?m)^E: ID_INPUT_TOUCHSCREEN=1$`).Match(b) 326 }() 327 if hasTouchScreen { 328 features.Screen.TouchSupport = configpb.HardwareFeatures_PRESENT 329 } 330 331 hasTouchpad := func() bool { 332 tp, err := exec.Command("udevadm", "info", "--export-db").Output() 333 if err != nil { 334 return false 335 } 336 return regexp.MustCompile(`(?m)^E: ID_INPUT_TOUCHPAD=1$`).Match(tp) 337 }() 338 if hasTouchpad { 339 features.Touchpad.Present = configpb.HardwareFeatures_PRESENT 340 } 341 342 hasFingerprint := func() bool { 343 out, err := crosConfig("/fingerprint", "sensor-location") 344 if err != nil { 345 return false 346 } 347 if out == "" || out == "none" { 348 return false 349 } 350 return true 351 }() 352 features.Fingerprint.Present = hasFingerprint 353 354 // Device has ChromeEC if /dev/cros_ec exists. 355 // TODO(b/173741162): Pull EmbeddedController data directly from Boxster. 356 if _, err := os.Stat("/dev/cros_ec"); err == nil { 357 features.EmbeddedController.Present = configpb.HardwareFeatures_PRESENT 358 features.EmbeddedController.EcType = configpb.HardwareFeatures_EmbeddedController_EC_CHROME 359 // Check for EC_FEATURE_TYPEC_CMD. 360 output, err := exec.Command("ectool", "inventory").Output() 361 if err != nil { 362 features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_PRESENT_UNKNOWN 363 } else { 364 // The presence of the integer "41" in the inventory output is a sufficient check, since 41 is 365 // the bit position associated with this feature. 366 if bytes.Contains(output, []byte("41")) { 367 features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_PRESENT 368 } else { 369 features.EmbeddedController.FeatureTypecCmd = configpb.HardwareFeatures_NOT_PRESENT 370 } 371 } 372 // Check if the detachable base is attached. 373 output, err = exec.Command("ectool", "mkbpget", "switches").Output() 374 if err != nil { 375 features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_PRESENT_UNKNOWN 376 } else if strings.Contains(string(output), "Base attached: ON") { 377 features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_PRESENT 378 } else { 379 features.EmbeddedController.DetachableBase = configpb.HardwareFeatures_NOT_PRESENT 380 } 381 // Running `ectool chargecontrol` with no args will fail if version 2 isn't 382 // supported. Check for battery sustainer output if the command doesn't 383 // fail to make sure charger control v2 is fully supported. 384 if out, err := exec.Command("ectool", "chargecontrol").Output(); err != nil || !regexp.MustCompile(`.*Battery sustainer`).Match(out) { 385 logging.Infof(ctx, "Charge control V2 not supported: %v", err) 386 features.EmbeddedController.FeatureChargeControlV2 = configpb.HardwareFeatures_NOT_PRESENT 387 } else { 388 features.EmbeddedController.FeatureChargeControlV2 = configpb.HardwareFeatures_PRESENT 389 } 390 } 391 392 // Device has CBI if ectool cbi get doesn't raise error. 393 if out, err := exec.Command("ectool", "cbi", "get", "0").Output(); err != nil { 394 logging.Infof(ctx, "CBI not present: %v", err) 395 features.EmbeddedController.Cbi = configpb.HardwareFeatures_NOT_PRESENT 396 } else if strings.Contains(string(out), "As uint:") { 397 features.EmbeddedController.Cbi = configpb.HardwareFeatures_PRESENT 398 } else { 399 features.EmbeddedController.Cbi = configpb.HardwareFeatures_PRESENT_UNKNOWN 400 } 401 402 // Device has GSC with production RW KeyId if gsctool -a -I -M 403 // returns RW KeyID with value 0x87b73b67 or 0xde88588d 404 func() { 405 if out, err := exec.Command("gsctool", "-a", "-f", "-M").Output(); err != nil { 406 logging.Infof(ctx, "Failed to exec command for KeyId info: %v", err) 407 features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_PRESENT_UNKNOWN 408 } else if keyIDRW, err := findGSCKeyID(string(out), "RW"); err != nil { 409 logging.Infof(ctx, "Failed to read RW KeyId: %v", err) 410 } else if containsGSCKeyID(prodRWGSCKeyIDs, GSCKeyID(keyIDRW)) { 411 features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_PRESENT 412 } else { 413 features.TrustedPlatformModule.ProductionRwKeyId = configpb.HardwareFeatures_NOT_PRESENT 414 } 415 }() 416 417 // Whether device has TPM enabled can be checked by `tpm_manager_client status`. 418 // If TPM is enabled, we can check the version by `tpm_version`. 419 func() { 420 features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_DISABLED 421 out, err := exec.Command("tpm_manager_client", "status", "--nonsensitive").Output() 422 if err != nil { 423 logging.Info(ctx, "Failed to exec command `tpm_manager_client status`: ", err) 424 return 425 } 426 if !strings.Contains(string(out), "is_enabled: true") { 427 return 428 } 429 out, err = exec.Command("tpm_version").Output() 430 if err != nil { 431 logging.Info(ctx, "Failed to exec command `tpm_version`: ", err) 432 return 433 } 434 status := string(out) 435 if strings.Contains(status, "TPM 1.2") { 436 features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_V1_2 437 } else if strings.Contains(status, "TPM 2.0") { 438 features.TrustedPlatformModule.RuntimeTpmVersion = configpb.HardwareFeatures_TrustedPlatformModule_TPM_VERSION_V2 439 } 440 }() 441 442 modemVariant, err := crosConfig("/modem", "firmware-variant") 443 if err != nil { 444 logging.Infof(ctx, "Modem not found: %v", err) 445 features.Cellular.Present = configpb.HardwareFeatures_NOT_PRESENT 446 } else { 447 features.Cellular.Present = configpb.HardwareFeatures_PRESENT 448 features.Cellular.Model = modemVariant 449 swDynamicSar, err := func() (bool, error) { 450 out, err := crosConfig("/power", "use-modemmanager-for-dynamic-sar") 451 if err != nil { 452 return false, err 453 } 454 return out == "1", nil 455 }() 456 if err != nil { 457 logging.Infof(ctx, "Unknown /power/use-modemmanager-for-dynamic-sar: %v", err) 458 } 459 features.Cellular.DynamicPowerReductionConfig = &configpb.HardwareFeatures_Cellular_DynamicPowerReductionConfig{ 460 DynamicPowerReductionConfig: &configpb.HardwareFeatures_Cellular_DynamicPowerReductionConfig_ModemManager{ModemManager: swDynamicSar}} 461 } 462 463 // bluetoothctl hangs when bluetoothd is not built with asan enabled or 464 // crashes. Set state to PRESENT_UNKNOWN on timeout. 465 const timeout = 3 * time.Second 466 cmdCtx, cancel := context.WithTimeout(ctx, timeout) 467 defer cancel() 468 if out, err := exec.CommandContext(cmdCtx, "bluetoothctl", "list").Output(); err != nil { 469 features.Bluetooth.Present = configpb.HardwareFeatures_PRESENT_UNKNOWN 470 } else if len(string(out)) != 0 { 471 features.Bluetooth.Present = configpb.HardwareFeatures_PRESENT 472 } else { 473 logging.Infof(ctx, "bluetooth controller not found") 474 features.Bluetooth.Present = configpb.HardwareFeatures_NOT_PRESENT 475 } 476 477 hasEmmcStorage := func() bool { 478 matches, err := filepath.Glob("/dev/mmc*") 479 if err != nil { 480 return false 481 } 482 if len(matches) > 0 { 483 return true 484 } 485 return false 486 }() 487 if hasEmmcStorage { 488 features.Storage.StorageType = configpb.Component_Storage_EMMC 489 } 490 491 // TODO(b/173741162): Pull storage information from boxster config and add 492 // additional storage types. 493 hasNvmeStorage := func() bool { 494 matches, err := filepath.Glob("/dev/nvme*") 495 if err != nil { 496 return false 497 } 498 if len(matches) > 0 { 499 return true 500 } 501 return false 502 }() 503 if hasNvmeStorage { 504 features.Storage.StorageType = configpb.Component_Storage_NVME 505 } 506 507 // TODO(b/211755998): Pull information from boxster config after this got supported in boxster. 508 hasNvmeSelfTestStorage := func() bool { 509 matches, err := filepath.Glob("/dev/nvme*n1") 510 if err != nil { 511 return false 512 } 513 if len(matches) == 0 { 514 return false 515 } 516 517 nvmePath := matches[0] 518 b, err := exec.Command("nvme", "id-ctrl", "-H", nvmePath).Output() 519 if err != nil { 520 return false 521 } 522 return bytes.Contains(b, []byte("Device Self-test Supported")) 523 }() 524 if hasNvmeStorage && hasNvmeSelfTestStorage { 525 config.HasNvmeSelfTest = true 526 } 527 528 hasUfsStorage := func() bool { 529 out, err := crosConfig("/hardware-properties", "storage-type") 530 if err != nil { 531 return false 532 } 533 return out == "UFS" 534 }() 535 if hasUfsStorage { 536 features.Storage.StorageType = configpb.Component_Storage_UFS 537 } 538 539 func() { 540 // This function determines DUT's power supply type and stores it to config.Power. 541 // If DUT has a battery, config.Power is DeprecatedDeviceConfig_POWER_SUPPLY_BATTERY. 542 // If DUT has AC power supplies only, config.Power is DeprecatedDeviceConfig_POWER_SUPPLY_AC_ONLY. 543 // Otherwise, DeprecatedDeviceConfig_POWER_SUPPLY_UNSPECIFIED is populated. 544 const sysFsPowerSupplyPath = "/sys/class/power_supply" 545 // AC power types come from power_supply driver in Linux kernel (drivers/power/supply/power_supply_sysfs.c) 546 acPowerTypes := [...]string{ 547 "Unknown", "UPS", "Mains", "USB", 548 "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", 549 "USB_PD", "USB_PD_DRP", "BrickID", 550 } 551 isACPower := make(map[string]bool) 552 for _, s := range acPowerTypes { 553 isACPower[s] = true 554 } 555 config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_UNSPECIFIED 556 files, err := ioutil.ReadDir(sysFsPowerSupplyPath) 557 if err != nil { 558 logging.Infof(ctx, "Failed to read %v: %v", sysFsPowerSupplyPath, err) 559 return 560 } 561 for _, file := range files { 562 devPath := path.Join(sysFsPowerSupplyPath, file.Name()) 563 supplyTypeBytes, err := ioutil.ReadFile(path.Join(devPath, "type")) 564 supplyType := strings.TrimSuffix(string(supplyTypeBytes), "\n") 565 if err != nil { 566 logging.Infof(ctx, "Failed to read supply type of %v: %v", devPath, err) 567 continue 568 } 569 if strings.HasPrefix(supplyType, "Battery") { 570 supplyScopeBytes, err := ioutil.ReadFile(path.Join(devPath, "scope")) 571 supplyScope := strings.TrimSuffix(string(supplyScopeBytes), "\n") 572 if err != nil && !os.IsNotExist(err) { 573 // Ignore NotExist error since /sys/class/power_supply/*/scope may not exist 574 logging.Infof(ctx, "Failed to read supply type of %v: %v", devPath, err) 575 continue 576 } 577 if strings.HasPrefix(string(supplyScope), "Device") { 578 // Ignore batteries for peripheral devices. 579 continue 580 } 581 config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_BATTERY 582 // Found at least one battery so this device is powered by battery. 583 break 584 } 585 if !isACPower[supplyType] { 586 logging.Infof(ctx, "Unknown supply type %v for %v", supplyType, devPath) 587 continue 588 } 589 config.Power = protocol.DeprecatedDeviceConfig_POWER_SUPPLY_AC_ONLY 590 } 591 }() 592 593 storageBytes, err := func() (int64, error) { 594 b, err := exec.Command("lsblk", "-J", "-b").Output() 595 if err != nil { 596 return 0, err 597 } 598 return findDiskSize(b) 599 }() 600 if err != nil { 601 logging.Infof(ctx, "Failed to get disk size: %v", err) 602 } 603 features.Storage.SizeGb = uint32(storageBytes / 1_000_000_000) 604 605 memoryBytes, err := func() (int64, error) { 606 b, err := ioutil.ReadFile("/proc/meminfo") 607 if err != nil { 608 return 0, err 609 } 610 return findMemorySize(b) 611 }() 612 if err != nil { 613 logging.Infof(ctx, "Failed to get memory size: %v", err) 614 } 615 features.Memory.Profile = &configpb.Component_Memory_Profile{ 616 SizeMegabytes: int32(memoryBytes / 1_000_000), 617 } 618 619 lidMicrophone, err := matchCrasDeviceType(`(INTERNAL|FRONT)_MIC`) 620 if err != nil { 621 logging.Infof(ctx, "Failed to get lid microphone: %v", err) 622 } 623 features.Audio.LidMicrophone = lidMicrophone 624 baseMicrophone, err := matchCrasDeviceType(`REAR_MIC`) 625 if err != nil { 626 logging.Infof(ctx, "Failed to get base microphone: %v", err) 627 } 628 features.Audio.BaseMicrophone = baseMicrophone 629 expectAudio := hasBuiltinAudio(ctx, features.FormFactor.FormFactor) 630 if features.Audio.LidMicrophone.GetValue() == 0 && features.Audio.BaseMicrophone.GetValue() == 0 && expectAudio { 631 features.Audio.LidMicrophone = &configpb.HardwareFeatures_Count{Value: 1} 632 } 633 speaker, err := matchCrasDeviceType(`INTERNAL_SPEAKER`) 634 if err != nil { 635 logging.Infof(ctx, "Failed to get speaker: %v", err) 636 } 637 638 if speaker.GetValue() > 0 || expectAudio { 639 640 amp, err := findSpeakerAmplifier() 641 if err != nil { 642 logging.Infof(ctx, "Failed to get amp: %v", err) 643 } 644 if amp == nil { 645 // Do not assume findSpeakerAmplifier() always returns a non-nil amp. 646 // Always signal that the device has a hwdep.Speaker(). 647 amp = &configpb.Component_Amplifier{} 648 } 649 features.Audio.SpeakerAmplifier = amp 650 } 651 652 hasPrivacyScreen := func() bool { 653 // Get list of connectors. 654 value, err := exec.Command("modetest", "-c").Output() 655 if err != nil { 656 logging.Infof(ctx, "Failed to get connectors: %v", err) 657 return false 658 } 659 // Check if privacy-screen prop is present. 660 result := strings.Contains(string(value), "privacy-screen:") 661 662 return result 663 }() 664 if hasPrivacyScreen { 665 features.PrivacyScreen.Present = configpb.HardwareFeatures_PRESENT 666 } 667 668 cpuSMT, err := func() (bool, error) { 669 // NB: this sysfs API exists only on kernel >=4.19 (b/195061310). But we don't 670 // target SMT-specific tests on earlier kernels. 671 b, err := ioutil.ReadFile("/sys/devices/system/cpu/smt/control") 672 if err != nil { 673 if os.IsNotExist(err) { 674 return false, nil 675 } 676 return false, errors.Wrap(err, "failed to read SMT control file") 677 } 678 s := strings.TrimSpace(string(b)) 679 switch s { 680 case "on", "off", "forceoff": 681 return true, nil 682 case "notsupported", "notimplemented": 683 return false, nil 684 default: 685 return false, errors.Errorf("unknown SMT control status: %q", s) 686 } 687 }() 688 if err != nil { 689 logging.Infof(ctx, "Failed to determine CPU SMT features: %v", err) 690 } 691 if cpuSMT { 692 features.Soc.Features = append(features.Soc.Features, configpb.Component_Soc_SMT) 693 } 694 695 cpuVulnerabilityPresent := func(vulnerability string) (bool, error) { 696 vPath := filepath.Join("/sys/devices/system/cpu/vulnerabilities", vulnerability) 697 b, err := ioutil.ReadFile(vPath) 698 if err != nil { 699 return false, errors.Wrapf(err, "failed to read vulnerability file %q", vPath) 700 } 701 s := strings.TrimSpace(string(b)) 702 if s == "Not affected" { 703 return false, nil 704 } 705 return true, nil 706 } 707 708 cpuL1TF, err := cpuVulnerabilityPresent("l1tf") 709 if err != nil { 710 logging.Infof(ctx, "Failed to determine L1TF vulnerability: %v", err) 711 } else if cpuL1TF { 712 features.Soc.Vulnerabilities = append(features.Soc.Vulnerabilities, configpb.Component_Soc_L1TF) 713 } 714 715 cpuMDS, err := cpuVulnerabilityPresent("mds") 716 if err != nil { 717 logging.Infof(ctx, "Failed to determine MDS vulnerability: %v", err) 718 } else if cpuMDS { 719 features.Soc.Vulnerabilities = append(features.Soc.Vulnerabilities, configpb.Component_Soc_MDS) 720 } 721 722 for _, v := range info.flags { 723 if v == "sha_ni" { 724 features.Soc.Features = append(features.Soc.Features, configpb.Component_Soc_SHA_NI) 725 } 726 } 727 728 func() { 729 // Probe for presence of DisplayPort converters 730 devices := map[string]string{ 731 "i2c-10EC2141:00": "RTD2141B", 732 "i2c-10EC2142:00": "RTD2142", 733 "i2c-1AF80175:00": "PS175", 734 } 735 for f, name := range devices { 736 path := filepath.Join("/sys/bus/i2c/devices", f) 737 if _, err := os.Stat(path); err != nil { 738 continue 739 } 740 features.DpConverter.Converters = append(features.DpConverter.Converters, &configpb.Component_DisplayPortConverter{ 741 Name: name, 742 }) 743 } 744 }() 745 746 hasHps, err := func() (bool, error) { 747 out, err := crosConfig("/hps", "has-hps") 748 if err != nil { 749 return false, err 750 } 751 return out == "true", nil 752 }() 753 if err != nil { 754 logging.Infof(ctx, "Unknown /hps: %v", err) 755 } 756 if hasHps { 757 features.Hps.Present = configpb.HardwareFeatures_PRESENT 758 } 759 760 camFeatures, err := cameraFeatures(model) 761 if err != nil { 762 logging.Infof(ctx, "failed to load camera feature profile: %v", err) 763 } 764 features.Camera.Features = camFeatures 765 766 if err := parseKConfigs(ctx, features); err != nil { 767 logging.Info(ctx, "Failed to parse BIOS kConfig: ", err) 768 } 769 770 rpConfigPresent, err := hasRuntimeProbeConfig(model) 771 if err != nil { 772 logging.Info(ctx, "Failed to determine if the config of Runtime Probe exists: ", err) 773 } 774 if rpConfigPresent { 775 features.RuntimeProbeConfig.Present = configpb.HardwareFeatures_PRESENT 776 } else { 777 logging.Infof(ctx, "Config of Runtime Probe not found") 778 features.RuntimeProbeConfig.Present = configpb.HardwareFeatures_NOT_PRESENT 779 } 780 781 gpuFamily, gpuVendor, cpuSocFamily, err := func() (gpuFamily, gpuVendor, cpuSocFamily string, fetchErr error) { 782 out, err := exec.Command("/usr/local/graphics/hardware_probe", "--gpu-family", "--gpu-vendor", "--cpu-soc-family").Output() 783 if err != nil { 784 return "", "", "", err 785 } 786 fetch := func(regexStr, input string) string { 787 match := regexp.MustCompile(regexStr).FindStringSubmatch(input) 788 if match == nil { 789 fetchErr = errors.Wrapf(fetchErr, "regex (%v) not match", regexStr) 790 return "" 791 } 792 return match[1] 793 } 794 gpuFamily = fetch(`GPU_Family: (\S+)`, string(out)) 795 gpuVendor = fetch(`GPU_Vendor: (\S+)`, string(out)) 796 cpuSocFamily = fetch(`CPU_SOC_Family: (\S+)`, string(out)) 797 return 798 }() 799 if err != nil { 800 logging.Infof(ctx, "failed to parse hardware_probe output: %v", err) 801 } 802 features.HardwareProbeConfig.GpuFamily = gpuFamily 803 features.HardwareProbeConfig.GpuVendor = gpuVendor 804 features.HardwareProbeConfig.CpuSocFamily = cpuSocFamily 805 806 hevcSupport, err := func() (configpb.HardwareFeatures_Present, error) { 807 out, err := crosConfig("/ui", "serialized-ash-switches") 808 if err != nil { 809 return configpb.HardwareFeatures_PRESENT_UNKNOWN, err 810 } 811 if regexp.MustCompile("disable-features=[^\x00]*PlatformHEVCDecoderSupport").MatchString(out) { 812 return configpb.HardwareFeatures_NOT_PRESENT, nil 813 } 814 if regexp.MustCompile("enable-features=[^\x00]*PlatformHEVCDecoderSupport").MatchString(out) { 815 return configpb.HardwareFeatures_PRESENT, nil 816 } 817 return configpb.HardwareFeatures_PRESENT_UNKNOWN, nil 818 }() 819 if err != nil { 820 logging.Infof(ctx, "Unknown /ui/serialized-ash-switches: %v", err) 821 } 822 features.Soc.HevcSupport = hevcSupport 823 824 return &protocol.HardwareFeatures{ 825 HardwareFeatures: features, 826 DeprecatedDeviceConfig: config, 827 }, nil 828 } 829 830 type lscpuEntry struct { 831 Field string `json:"field"` // includes trailing ":" 832 Data string `json:"data"` 833 } 834 835 type lscpuResult struct { 836 Entries []lscpuEntry `json:"lscpu"` 837 } 838 839 func (r *lscpuResult) find(name string) (data string, ok bool) { 840 for _, e := range r.Entries { 841 if e.Field == name { 842 return e.Data, true 843 } 844 } 845 return "", false 846 } 847 848 type cpuConfig struct { 849 cpuArch protocol.DeprecatedDeviceConfig_Architecture 850 soc protocol.DeprecatedDeviceConfig_SOC 851 flags []string 852 } 853 854 // cpuInfo returns a structure containing field data from the "lscpu" command 855 // which outputs CPU architecture information from "sysfs" and "/proc/cpuinfo". 856 func cpuInfo() (cpuConfig, error) { 857 errInfo := cpuConfig{protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, nil} 858 b, err := exec.Command("lscpu", "--json").Output() 859 if err != nil { 860 return errInfo, err 861 } 862 var parsed lscpuResult 863 if err := json.Unmarshal(b, &parsed); err != nil { 864 return errInfo, errors.Wrap(err, "failed to parse lscpu result") 865 } 866 flagsStr, _ := parsed.find("Flags:") 867 flags := strings.Split(flagsStr, " ") 868 arch, err := findArchitecture(parsed) 869 if err != nil { 870 return errInfo, errors.Wrap(err, "failed to find CPU architecture") 871 } 872 soc, err := findSOC(parsed) 873 if err != nil { 874 return cpuConfig{arch, protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, flags}, errors.Wrap(err, "failed to find SOC") 875 } 876 return cpuConfig{arch, soc, flags}, nil 877 } 878 879 // findArchitecture returns an architecture configuration based from parsed output 880 // data value of the "Architecture" field. 881 func findArchitecture(parsed lscpuResult) (protocol.DeprecatedDeviceConfig_Architecture, error) { 882 arch, ok := parsed.find("Architecture:") 883 if !ok { 884 return protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, errors.New("failed to find Architecture field") 885 } 886 887 switch arch { 888 case "x86_64": 889 return protocol.DeprecatedDeviceConfig_X86_64, nil 890 case "i686": 891 return protocol.DeprecatedDeviceConfig_X86, nil 892 case "aarch64": 893 return protocol.DeprecatedDeviceConfig_ARM64, nil 894 case "armv7l", "armv8l": 895 return protocol.DeprecatedDeviceConfig_ARM, nil 896 default: 897 return protocol.DeprecatedDeviceConfig_ARCHITECTURE_UNDEFINED, errors.Errorf("unknown architecture: %q", arch) 898 } 899 } 900 901 // findSOC returns a SOC configuration based from parsed output data value of the 902 // "Vendor ID" and other related fields. 903 func findSOC(parsed lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) { 904 vendorID, ok := parsed.find("Vendor ID:") 905 if !ok { 906 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Vendor ID field") 907 } 908 909 switch vendorID { 910 case "ARM": 911 fallthrough 912 case "Qualcomm": 913 return findARMSOC() 914 case "GenuineIntel": 915 return findIntelSOC(&parsed) 916 case "AuthenticAMD": 917 return findAMDSOC(&parsed) 918 default: 919 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown vendor ID: %q", vendorID) 920 } 921 } 922 923 // findARMSOC returns an ARM SOC configuration based on "soc_id" from "/sys/bus/soc/devices". 924 func findARMSOC() (protocol.DeprecatedDeviceConfig_SOC, error) { 925 // Platforms with SMCCC >= 1.2 should implement get_soc functions in firmware 926 const socSysFS = "/sys/bus/soc/devices" 927 socs, err := ioutil.ReadDir(socSysFS) 928 if err == nil { 929 for _, soc := range socs { 930 c, err := ioutil.ReadFile(path.Join(socSysFS, soc.Name(), "soc_id")) 931 if err != nil || !strings.HasPrefix(string(c), "jep106:") { 932 continue 933 } 934 // Trim trailing \x00 and \n 935 socID := strings.TrimRight(string(c), "\x00\n") 936 switch socID { 937 case "jep106:0070:01a9": 938 fallthrough 939 case "jep106:0070:01ef": 940 fallthrough 941 case "jep106:0070:7180": // Used by older SC7180 firmware 942 return protocol.DeprecatedDeviceConfig_SOC_SC7180, nil 943 case "jep106:0070:7280": 944 return protocol.DeprecatedDeviceConfig_SOC_SC7280, nil 945 case "jep106:0426:8192": 946 return protocol.DeprecatedDeviceConfig_SOC_MT8192, nil 947 case "jep106:0426:8186": 948 return protocol.DeprecatedDeviceConfig_SOC_MT8186, nil 949 case "jep106:0426:8195": 950 return protocol.DeprecatedDeviceConfig_SOC_MT8195, nil 951 case "jep106:0426:8188": 952 return protocol.DeprecatedDeviceConfig_SOC_MT8188G, nil 953 default: 954 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown ARM model: %s", socID) 955 } 956 } 957 } 958 959 // For old platforms with SMCCC < 1.2: mt8173, mt8183, rk3288, rk3399, 960 // match with their compatible string. Obtain the string after the last , and trim \x00. 961 // Example: google,krane-sku176\x00google,krane\x00mediatek,mt8183\x00 962 c, err := ioutil.ReadFile("/sys/firmware/devicetree/base/compatible") 963 if err != nil { 964 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Wrap(err, "failed to find ARM model") 965 } 966 967 compatible := string(c) 968 model := strings.ToLower(compatible[strings.LastIndex(compatible, ",")+1:]) 969 model = strings.TrimRight(model, "\x00") 970 971 switch model { 972 case "mt8173": 973 return protocol.DeprecatedDeviceConfig_SOC_MT8173, nil 974 case "mt8183": 975 return protocol.DeprecatedDeviceConfig_SOC_MT8183, nil 976 case "rk3288": 977 return protocol.DeprecatedDeviceConfig_SOC_RK3288, nil 978 case "rk3399": 979 return protocol.DeprecatedDeviceConfig_SOC_RK3399, nil 980 default: 981 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown ARM model: %s", model) 982 } 983 } 984 985 // findIntelSOC returns an Intel SOC configuration based on "CPU family", "Model", 986 // and "Model name" fields. 987 func findIntelSOC(parsed *lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) { 988 if family, ok := parsed.find("CPU family:"); !ok { 989 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel family") 990 } else if family != "6" { 991 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown Intel family: %s", family) 992 } 993 994 modelStr, ok := parsed.find("Model:") 995 if !ok { 996 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model") 997 } 998 model, err := strconv.ParseInt(modelStr, 10, 64) 999 if err != nil { 1000 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Wrapf(err, "failed to parse Intel model: %q", modelStr) 1001 } 1002 switch model { 1003 case INTEL_FAM6_KABYLAKE_L: 1004 // AMBERLAKE_Y, COMET_LAKE_U, WHISKEY_LAKE_U, KABYLAKE_U, KABYLAKE_U_R, and 1005 // KABYLAKE_Y share the same model. Parse model name. 1006 // Note that Pentium brand is unsupported. 1007 modelName, ok := parsed.find("Model name:") 1008 if !ok { 1009 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model name") 1010 } 1011 for _, e := range []struct { 1012 soc protocol.DeprecatedDeviceConfig_SOC 1013 ptn string 1014 }{ 1015 // https://ark.intel.com/content/www/us/en/ark/products/codename/186968/amber-lake-y.html 1016 {protocol.DeprecatedDeviceConfig_SOC_AMBERLAKE_Y, `Core.* [mi]\d-(10|8)\d{3}Y`}, 1017 1018 // https://ark.intel.com/content/www/us/en/ark/products/codename/90354/comet-lake.html 1019 {protocol.DeprecatedDeviceConfig_SOC_COMET_LAKE_U, `Core.* i\d-10\d{3}U|Celeron.* 5[23]05U|Intel\(R\) Pentium\(R\) CPU 6405U`}, 1020 1021 // https://ark.intel.com/content/www/us/en/ark/products/codename/135883/whiskey-lake.html 1022 {protocol.DeprecatedDeviceConfig_SOC_WHISKEY_LAKE_U, `Core.* i\d-8\d{2}5U|Celeron.* 4[23]05U`}, 1023 1024 // https://ark.intel.com/content/www/us/en/ark/products/codename/82879/kaby-lake.html 1025 {protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_U, `Core.* i\d-7\d{3}U|Celeron.* 3[89]65U`}, 1026 {protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_Y, `Core.* [mi]\d-7Y\d{2}|Celeron.* 3965Y`}, 1027 1028 // https://ark.intel.com/content/www/us/en/ark/products/codename/126287/kaby-lake-r.html 1029 {protocol.DeprecatedDeviceConfig_SOC_KABYLAKE_U_R, `Core.* i\d-8\d{2}0U|Celeron.* 3867U`}, 1030 } { 1031 r := regexp.MustCompile(e.ptn) 1032 if r.MatchString(modelName) { 1033 return e.soc, nil 1034 } 1035 } 1036 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown model name: %s", modelName) 1037 case INTEL_FAM6_ICELAKE_L: 1038 return protocol.DeprecatedDeviceConfig_SOC_ICE_LAKE_Y, nil 1039 case INTEL_FAM6_ATOM_GOLDMONT_PLUS: 1040 return protocol.DeprecatedDeviceConfig_SOC_GEMINI_LAKE, nil 1041 case INTEL_FAM6_ATOM_TREMONT_L: 1042 return protocol.DeprecatedDeviceConfig_SOC_JASPER_LAKE, nil 1043 case INTEL_FAM6_TIGERLAKE_L: 1044 return protocol.DeprecatedDeviceConfig_SOC_TIGER_LAKE, nil 1045 case INTEL_FAM6_ALDERLAKE_L: 1046 return protocol.DeprecatedDeviceConfig_SOC_ALDER_LAKE, nil 1047 case INTEL_FAM6_METEORLAKE_L: 1048 return protocol.DeprecatedDeviceConfig_SOC_METEOR_LAKE, nil 1049 case INTEL_FAM6_CANNONLAKE_L: 1050 return protocol.DeprecatedDeviceConfig_SOC_CANNON_LAKE_Y, nil 1051 case INTEL_FAM6_ATOM_GOLDMONT: 1052 return protocol.DeprecatedDeviceConfig_SOC_APOLLO_LAKE, nil 1053 case INTEL_FAM6_SKYLAKE_L: 1054 // SKYLAKE_U and SKYLAKE_Y share the same model. Parse model name. 1055 modelName, ok := parsed.find("Model name:") 1056 if !ok { 1057 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find Intel model name") 1058 } 1059 for _, e := range []struct { 1060 soc protocol.DeprecatedDeviceConfig_SOC 1061 ptn string 1062 }{ 1063 // https://ark.intel.com/content/www/us/en/ark/products/codename/37572/skylake.html 1064 {protocol.DeprecatedDeviceConfig_SOC_SKYLAKE_U, `Core.* i\d-6\d{3}U|Celeron.*3[89]55U`}, 1065 {protocol.DeprecatedDeviceConfig_SOC_SKYLAKE_Y, `Core.* m\d-6Y\d{2}`}, 1066 } { 1067 r := regexp.MustCompile(e.ptn) 1068 if r.MatchString(modelName) { 1069 return e.soc, nil 1070 } 1071 } 1072 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown model name: %s", modelName) 1073 case INTEL_FAM6_ATOM_AIRMONT: 1074 return protocol.DeprecatedDeviceConfig_SOC_BRASWELL, nil 1075 case INTEL_FAM6_BROADWELL: 1076 return protocol.DeprecatedDeviceConfig_SOC_BROADWELL, nil 1077 case INTEL_FAM6_HASWELL, INTEL_FAM6_HASWELL_L: 1078 return protocol.DeprecatedDeviceConfig_SOC_HASWELL, nil 1079 case INTEL_FAM6_IVYBRIDGE: 1080 return protocol.DeprecatedDeviceConfig_SOC_IVY_BRIDGE, nil 1081 case INTEL_FAM6_ATOM_SILVERMONT: 1082 return protocol.DeprecatedDeviceConfig_SOC_BAY_TRAIL, nil 1083 case INTEL_FAM6_SANDYBRIDGE: 1084 return protocol.DeprecatedDeviceConfig_SOC_SANDY_BRIDGE, nil 1085 case INTEL_FAM6_ATOM_BONNELL: 1086 return protocol.DeprecatedDeviceConfig_SOC_PINE_TRAIL, nil 1087 default: 1088 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown Intel model: %d", model) 1089 } 1090 } 1091 1092 // findAMDSOC returns an AMD SOC configuration based on "Model" field. 1093 func findAMDSOC(parsed *lscpuResult) (protocol.DeprecatedDeviceConfig_SOC, error) { 1094 model, ok := parsed.find("Model:") 1095 if !ok { 1096 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.New("failed to find AMD model") 1097 } 1098 if model == "112" { 1099 return protocol.DeprecatedDeviceConfig_SOC_STONEY_RIDGE, nil 1100 } 1101 if family, ok := parsed.find("CPU family:"); ok { 1102 if family == "23" { 1103 if model == "24" || model == "32" { 1104 return protocol.DeprecatedDeviceConfig_SOC_PICASSO, nil 1105 } else if model == "160" { 1106 return protocol.DeprecatedDeviceConfig_SOC_MENDOCINO, nil 1107 } 1108 } else if family == "25" { 1109 if model == "80" { 1110 return protocol.DeprecatedDeviceConfig_SOC_CEZANNE, nil 1111 } else if model == "116" { 1112 return protocol.DeprecatedDeviceConfig_SOC_PHOENIX, nil 1113 } 1114 } 1115 } 1116 1117 return protocol.DeprecatedDeviceConfig_SOC_UNSPECIFIED, errors.Errorf("unknown AMD model: %s", model) 1118 } 1119 1120 // lsblk command output differs depending on the version. Attempt parsing in multiple ways to accept all the cases. 1121 1122 // lsblk from util-linux 2.32 1123 type blockDevices2_32 struct { 1124 Name string `json:"name"` 1125 Removable string `json:"rm"` 1126 Size string `json:"size"` 1127 Type string `json:"type"` 1128 } 1129 1130 type lsblkRoot2_32 struct { 1131 BlockDevices []blockDevices2_32 `json:"blockdevices"` 1132 } 1133 1134 // lsblk from util-linux 2.36.1 1135 type blockDevices struct { 1136 Name string `json:"name"` 1137 Removable bool `json:"rm"` 1138 Size int64 `json:"size"` 1139 Type string `json:"type"` 1140 } 1141 1142 type lsblkRoot struct { 1143 BlockDevices []blockDevices `json:"blockdevices"` 1144 } 1145 1146 func parseLsblk2_32(jsonData []byte) (*lsblkRoot, error) { 1147 var old lsblkRoot2_32 1148 err := json.Unmarshal(jsonData, &old) 1149 if err != nil { 1150 return nil, err 1151 } 1152 1153 var r lsblkRoot 1154 for _, e := range old.BlockDevices { 1155 s, err := strconv.ParseInt(e.Size, 10, 64) 1156 if err != nil { 1157 return nil, err 1158 } 1159 var rm bool 1160 if e.Removable == "0" || e.Removable == "" { 1161 rm = false 1162 } else if e.Removable == "1" { 1163 rm = true 1164 } else { 1165 return nil, fmt.Errorf("unknown value for rm: %q", e.Removable) 1166 } 1167 r.BlockDevices = append(r.BlockDevices, blockDevices{ 1168 Name: e.Name, 1169 Removable: rm, 1170 Size: s, 1171 Type: e.Type, 1172 }) 1173 } 1174 return &r, nil 1175 } 1176 1177 func parseLsblk2_36(jsonData []byte) (*lsblkRoot, error) { 1178 var r lsblkRoot 1179 err := json.Unmarshal(jsonData, &r) 1180 if err != nil { 1181 return nil, err 1182 } 1183 return &r, nil 1184 } 1185 1186 func parseLsblk(jsonData []byte) (*lsblkRoot, error) { 1187 var errs []error 1188 parsers := []func([]byte) (*lsblkRoot, error){parseLsblk2_36, parseLsblk2_32} 1189 for _, p := range parsers { 1190 r, err := p(jsonData) 1191 if err == nil { 1192 return r, nil 1193 } 1194 errs = append(errs, err) 1195 } 1196 var errStrings []string 1197 for _, e := range errs { 1198 errStrings = append(errStrings, e.Error()) 1199 } 1200 return nil, fmt.Errorf("failed to parse JSON in all the expected formats: %s", strings.Join(errStrings, "; ")) 1201 } 1202 1203 // findDiskSize detects the size of the storage device from "lsblk -J -b" output in bytes. 1204 // When there are multiple disks, returns the size of the largest one. 1205 func findDiskSize(jsonData []byte) (int64, error) { 1206 r, err := parseLsblk(jsonData) 1207 if err != nil { 1208 return 0, err 1209 } 1210 var maxSize int64 1211 var found bool 1212 for _, x := range r.BlockDevices { 1213 if x.Type == "disk" && !x.Removable && !strings.HasPrefix(x.Name, "zram") { 1214 found = true 1215 if x.Size > maxSize { 1216 maxSize = x.Size 1217 } 1218 } 1219 } 1220 if !found { 1221 return 0, errors.New("no disk device found") 1222 } 1223 return maxSize, nil 1224 } 1225 1226 // findMemorySize parses a content of /proc/meminfo and returns the total memory size in bytes. 1227 func findMemorySize(meminfo []byte) (int64, error) { 1228 r := bytes.NewReader(meminfo) 1229 sc := bufio.NewScanner(r) 1230 for sc.Scan() { 1231 line := sc.Text() 1232 tokens := strings.SplitN(line, ":", 2) 1233 if len(tokens) != 2 || strings.TrimSpace(tokens[0]) != "MemTotal" { 1234 continue 1235 } 1236 cap := strings.SplitN(strings.TrimSpace(tokens[1]), " ", 2) 1237 if len(cap) != 2 { 1238 return 0, fmt.Errorf("unexpected line format: input=%s", line) 1239 } 1240 if cap[1] != "kB" { 1241 return 0, fmt.Errorf("unexpected unit: got %s, want kB; input=%s", cap[1], line) 1242 } 1243 val, err := strconv.ParseInt(cap[0], 10, 64) 1244 if err != nil { 1245 return 0, err 1246 } 1247 return val * 1_000, nil 1248 } 1249 return 0, fmt.Errorf("MemTotal not found; input=%q", string(meminfo)) 1250 } 1251 1252 func matchCrasDeviceType(pattern string) (*configpb.HardwareFeatures_Count, error) { 1253 b, err := exec.Command("cras_test_client").Output() 1254 if err != nil { 1255 return nil, err 1256 } 1257 if regexp.MustCompile(pattern).Match(b) { 1258 return &configpb.HardwareFeatures_Count{Value: 1}, nil 1259 } 1260 return &configpb.HardwareFeatures_Count{Value: 0}, nil 1261 } 1262 1263 // findSpeakerAmplifier parses a content of in "/sys/kernel/debug/asoc/components" 1264 // and returns the speaker amplifier used. 1265 func findSpeakerAmplifier() (*configpb.Component_Amplifier, error) { 1266 1267 // This sys path exists only on kernel >=4.14. But we don't 1268 // target amp tests on earlier kernels. 1269 f, err := os.Open("/sys/kernel/debug/asoc/components") 1270 if err != nil { 1271 return &configpb.Component_Amplifier{}, err 1272 } 1273 defer f.Close() 1274 scanner := bufio.NewScanner(f) 1275 for scanner.Scan() { 1276 if amp, found := matchSpeakerAmplifier(scanner.Text()); found { 1277 if enabled, err := bootTimeCalibration(); err == nil && enabled { 1278 amp.Features = append(amp.Features, configpb.Component_Amplifier_BOOT_TIME_CALIBRATION) 1279 } 1280 return amp, err 1281 } 1282 } 1283 return &configpb.Component_Amplifier{}, nil 1284 } 1285 1286 var ampsRegexp = map[string]*regexp.Regexp{ 1287 configpb.HardwareFeatures_Audio_MAX98357.String(): regexp.MustCompile(`^(i2c-)?ma?x98357a?((:\d*)|([_-]?\d*))?$`), 1288 configpb.HardwareFeatures_Audio_MAX98373.String(): regexp.MustCompile(`^(i2c-)?ma?x98373((:\d*)|([_-]?\d*))?$`), 1289 configpb.HardwareFeatures_Audio_MAX98360.String(): regexp.MustCompile(`^(i2c-)?ma?x98360a?((:\d*)|([_-]?\d*))?$`), 1290 configpb.HardwareFeatures_Audio_RT1015.String(): regexp.MustCompile(`^(i2c-)?((rtl?)|(10ec))?1015(\.\d*)?((:\d*)|([_-]?\d*))?$`), 1291 configpb.HardwareFeatures_Audio_RT1015P.String(): regexp.MustCompile(`^(i2c-)?(rtl?)?(10ec)?1015p(\.\d*)?((:\d*)|([_-]?\d*))?$`), 1292 configpb.HardwareFeatures_Audio_ALC1011.String(): regexp.MustCompile(`^(i2c-)?((rtl?)|(10ec))?1011(\.\d*)?((:\d*)|([_-]?\d*))?$`), 1293 configpb.HardwareFeatures_Audio_MAX98390.String(): regexp.MustCompile(`^(i2c-)?ma?x98390((:\d*)|(\.\d-\d+)|([_-]?\d*))?$`), 1294 configpb.HardwareFeatures_Audio_CS35L41.String(): regexp.MustCompile(`^(i2c-)?csc3541((:\d*)|([_-]?\d*))?$`), 1295 } 1296 1297 func matchSpeakerAmplifier(line string) (*configpb.Component_Amplifier, bool) { 1298 for amp, re := range ampsRegexp { 1299 if re.MatchString(strings.ToLower(line)) { 1300 return &configpb.Component_Amplifier{Name: amp}, true 1301 } 1302 } 1303 return nil, false 1304 } 1305 1306 // bootTimeCalibration returns whether the boot time calibration is 1307 // enabled by parsing the sound_card_init config. 1308 func bootTimeCalibration() (bool, error) { 1309 config, err := crosConfig("/audio/main", "sound-card-init-conf") 1310 if err != nil { 1311 return false, err 1312 } 1313 path := "/etc/sound_card_init/" + config 1314 if _, err := os.Stat(path); err != nil { 1315 // Regard config non-existence as boot_time_calibration disabled. 1316 if os.IsNotExist(err) { 1317 return false, nil 1318 } 1319 return false, err 1320 } 1321 b, err := ioutil.ReadFile(path) 1322 if err != nil { 1323 return false, errors.New("failed to read sound_card_init config") 1324 } 1325 return isBootTimeCalibrationEnabled(string(b)) 1326 } 1327 1328 func isBootTimeCalibrationEnabled(s string) (bool, error) { 1329 re := regexp.MustCompile(`boot_time_calibration_enabled\s*?:\s*?(true|false)`) 1330 match := re.FindStringSubmatch(s) 1331 if match == nil { 1332 return false, errors.New("invalid sound_card_init config") 1333 } 1334 enabled := match[1] 1335 return enabled == "true", nil 1336 } 1337 1338 func wifiFeatures() (*configpb.HardwareFeatures_Wifi, error) { 1339 dev, err := wlan.DeviceInfo() 1340 if err != nil { 1341 return nil, errors.Wrap(err, "failed to get device") 1342 } 1343 1344 _, err = exec.Command("vpd", "-g", "wifi_sar").Output() 1345 vpdSarFound := err == nil 1346 1347 return &configpb.HardwareFeatures_Wifi{ 1348 WifiChips: []configpb.HardwareFeatures_Wifi_WifiChip{ 1349 configpb.HardwareFeatures_Wifi_WifiChip(dev.ID)}, 1350 WifiVpdSar: vpdSarFound, 1351 }, nil 1352 } 1353 1354 // hasBuiltinAudio tells if a given form factor has built-in audio devices 1355 func hasBuiltinAudio(ctx context.Context, ff configpb.HardwareFeatures_FormFactor_FormFactorType) bool { 1356 switch ff { 1357 case configpb.HardwareFeatures_FormFactor_CLAMSHELL, 1358 configpb.HardwareFeatures_FormFactor_CONVERTIBLE, 1359 configpb.HardwareFeatures_FormFactor_DETACHABLE, 1360 configpb.HardwareFeatures_FormFactor_CHROMEBASE, 1361 configpb.HardwareFeatures_FormFactor_CHROMESLATE: 1362 return true 1363 case configpb.HardwareFeatures_FormFactor_CHROMEBIT, 1364 configpb.HardwareFeatures_FormFactor_CHROMEBOX: 1365 return false 1366 default: 1367 logging.Infof(ctx, "Unknown form factor: %s", ff) 1368 return false 1369 } 1370 } 1371 1372 // cameraFeatures returns the list of configured camera features for the given 1373 // |model| by inspecting the on-device feature config file. 1374 func cameraFeatures(model string) ([]string, error) { 1375 type modelConfig map[string]struct { 1376 FeatureSet []map[string]interface{} `json:"feature_set"` 1377 } 1378 const featureProfilePath = "/etc/camera/feature_profile.json" 1379 jsonInput, err := ioutil.ReadFile(featureProfilePath) 1380 if err != nil { 1381 return nil, errors.Wrap(err, "cannot load feature profile config") 1382 } 1383 conf := make(modelConfig) 1384 if err := json.Unmarshal(jsonInput, &conf); err != nil { 1385 return nil, errors.Wrap(err, "cannot unmarshal feature profile config") 1386 } 1387 c, ok := conf[model] 1388 if !ok { 1389 return nil, errors.Errorf("feature set config for model %s doesn't exist", model) 1390 } 1391 featureSet := make(map[string]bool) 1392 for _, f := range c.FeatureSet { 1393 var v interface{} 1394 // The "type" attribute is always a string. 1395 if v, ok = f["type"]; !ok { 1396 continue 1397 } 1398 // There can be multiple entries for a feature with different 1399 // constraints. 1400 if _, ok := featureSet[v.(string)]; !ok { 1401 featureSet[v.(string)] = true 1402 } 1403 } 1404 var ret []string 1405 for k := range featureSet { 1406 ret = append(ret, k) 1407 } 1408 return ret, nil 1409 } 1410 1411 // findGSCKeyID parses a content of "gsctool -a -f -M" and return a required key 1412 func findGSCKeyID(str, keyIDType string) (string, error) { 1413 re := regexp.MustCompile(`(?m)^keyids: RO (0x.+), RW (0x.+)$`) 1414 1415 switch keyIDType { 1416 case "RO": 1417 keyID := re.FindAllStringSubmatch(str, -1)[0][1] 1418 return keyID, nil 1419 case "RW": 1420 keyID := re.FindAllStringSubmatch(str, -1)[0][2] 1421 return keyID, nil 1422 default: 1423 return "", errors.Errorf("Unknown keyId type %s", keyIDType) 1424 } 1425 } 1426 1427 // containsGSCKeyID returns true if reqKeyID is in the keyIDs 1428 func containsGSCKeyID(keyIDs []GSCKeyID, reqKeyID GSCKeyID) bool { 1429 for _, keyID := range keyIDs { 1430 if keyID == reqKeyID { 1431 return true 1432 } 1433 } 1434 return false 1435 } 1436 1437 // For mocking. 1438 var flashromExtractCoreBootCmd = func(ctx context.Context, corebootBinName string) error { 1439 return exec.CommandContext(ctx, "flashrom", "-p", "host", "-r", "-i", fmt.Sprintf("FW_MAIN_A:%s", corebootBinName)).Run() 1440 } 1441 var cbfsToolExtractConfigCmd = func(ctx context.Context, corebootBinName, fwConfigName string) error { 1442 return exec.CommandContext(ctx, "cbfstool", corebootBinName, "extract", "-n", "config", "-f", fwConfigName).Run() 1443 } 1444 1445 var configLineRegexp = regexp.MustCompile(`^(# )?(CONFIG\S*)(=(y)| (is not set))`) 1446 1447 // parseKConfigs updates the provided HardwareFeatures with the features found 1448 // by reading reading through the BIOS Kconfigs. 1449 func parseKConfigs(ctx context.Context, features *configpb.HardwareFeatures) error { 1450 corebootBin, err := ioutil.TempFile("/var/tmp", "") 1451 if err != nil { 1452 return errors.Wrap(err, "failed to create temp file") 1453 } 1454 corebootBin.Close() 1455 defer os.Remove(corebootBin.Name()) 1456 1457 fwConfig, err := ioutil.TempFile("/var/tmp", "") 1458 if err != nil { 1459 return errors.Wrap(err, "failed to create temp file") 1460 } 1461 fwConfig.Close() 1462 defer os.Remove(fwConfig.Name()) 1463 1464 if err := flashromExtractCoreBootCmd(ctx, corebootBin.Name()); err != nil { 1465 return errors.Wrap(err, "failed to extract FW_MAIN_A bios section") 1466 } 1467 if err := cbfsToolExtractConfigCmd(ctx, corebootBin.Name(), fwConfig.Name()); err != nil { 1468 return errors.Wrap(err, "failed to extract bios Kconfig file") 1469 } 1470 inFile, err := os.Open(fwConfig.Name()) 1471 if err != nil { 1472 return errors.Wrap(err, "failed to read bios Kconfig file") 1473 } 1474 defer inFile.Close() 1475 1476 importantConfigs := map[string]*configpb.HardwareFeatures_Present{ 1477 "CONFIG_MAINBOARD_HAS_EARLY_LIBGFXINIT": &features.FwConfig.MainboardHasEarlyLibgfxinit, 1478 "CONFIG_VBOOT_CBFS_INTEGRATION": &features.FwConfig.VbootCbfsIntegration, 1479 } 1480 1481 scanner := bufio.NewScanner(inFile) 1482 for scanner.Scan() { 1483 line := scanner.Text() 1484 if match := configLineRegexp.FindStringSubmatch(line); match != nil { 1485 if val, ok := importantConfigs[match[2]]; ok { 1486 if match[4] == "y" { 1487 *val = configpb.HardwareFeatures_PRESENT 1488 } else if match[5] == "is not set" { 1489 *val = configpb.HardwareFeatures_NOT_PRESENT 1490 } 1491 } 1492 } 1493 } 1494 return nil 1495 } 1496 1497 // hasRuntimeProbeConfig returns true if the corresponding probe config for the 1498 // model of the DUT exists. The err is set if the error os.Stat returns is not 1499 // fs.ErrNotExist. 1500 func hasRuntimeProbeConfig(model string) (bool, error) { 1501 probeConfigRelPath := "etc/runtime_probe/" + model + "/probe_config.json" 1502 configRoots := []string{ 1503 "/usr/local/", 1504 "/", 1505 } 1506 for _, configRoot := range configRoots { 1507 _, err := os.Stat(configRoot + probeConfigRelPath) 1508 if err == nil { 1509 return true, nil 1510 } 1511 if !os.IsNotExist(err) { 1512 return false, err 1513 } 1514 } 1515 return false, nil 1516 }