blob: d35ad16d96aaeb579f9d687618bb16ccc73d7861 [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package firmware
import (
"context"
"fmt"
"regexp"
"strings"
"time"
"github.com/golang/protobuf/ptypes/empty"
"chromiumos/tast/common/servo"
"chromiumos/tast/errors"
"chromiumos/tast/remote/firmware"
"chromiumos/tast/remote/firmware/fixture"
pb "chromiumos/tast/services/cros/ui"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: CheckKeyboardBacklightFunctionality,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Confirm keyboard backlight support and check keyboard backlight functionality",
Contacts: []string{"cienet-firmware@cienet.corp-partner.google.com", "chromeos-firmware@google.com"},
Attr: []string{"group:firmware", "firmware_unstable"},
SoftwareDeps: []string{"chrome"},
ServiceDeps: []string{"tast.cros.ui.ChromeUIService"},
HardwareDeps: hwdep.D(
hwdep.ChromeEC(),
hwdep.KeyboardBacklight(),
),
Fixture: fixture.NormalMode,
})
}
// CheckKeyboardBacklightFunctionality confirms keyboard backlight support and verifies its functionality.
func CheckKeyboardBacklightFunctionality(ctx context.Context, s *testing.State) {
h := s.FixtValue().(*fixture.Value).Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to init servo: ", err)
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to get config: ", err)
}
if err := h.RequireRPCClient(ctx); err != nil {
s.Fatal("Failed to connect to the RPC service on the DUT: ", err)
}
serviceClient := pb.NewChromeUIServiceClient(h.RPCClient.Conn)
if _, err := serviceClient.EnsureLoginScreen(ctx, &empty.Empty{}); err != nil {
s.Fatal("Failed to restart ui at login screen: ", err)
}
// Current hardware depencies might miss out on DUTs that actually don't
// support keyboard backlight. "EC_KB_BL_EN" and "KB_BL_EN" appear to be
// two common names for the gpio in control. Checking whether these gpios
// exist would probably help with better sorting out the false positives.
// The list may expand to include more gpio names.
kbLightGpioNames := []string{"EC_KB_BL_EN", "KB_BL_EN"}
s.Logf("Checking if the following keyboard backlight gpios exist: %s", strings.Join(kbLightGpioNames, ", "))
if err := grepKbLightGPIO(ctx, h, s, kbLightGpioNames); err != nil {
s.Log("Unexpected output when checking on gpio: ", err)
}
// Check whether the pwm value for kb backlight exists.
if err := checkKbLightPwm(ctx, s); err != nil {
s.Log("Unexpected output when checking on pwm for kb: ", err)
}
initValue, err := checkInitKBBacklight(ctx, h)
if err != nil {
s.Fatal("Failed to check initial keybaord backlight value: ", err)
}
if initValue == 0 {
s.Log("Keyboard initial backlight value is 0, attempting to increase the light to at least 40 percent before test")
if err := adjustKBBacklight(ctx, s, h, 40, "<f7>", "increasing"); err != nil {
s.Fatal("Failed to adjust keyboard backlight: ", err)
}
} else if initValue == 100 {
s.Log("Keyboard initial backlight value is 100, attempting to decrease the light to at leaset 40 percent before test")
if err := adjustKBBacklight(ctx, s, h, 40, "<f6>", "decreasing"); err != nil {
s.Fatal("Failed to adjust keyboard backlight: ", err)
}
}
kbBacklightTesting := make(map[int]string, 2)
kbBacklightTesting[0] = "<f6>"
kbBacklightTesting[100] = "<f7>"
for extremeValue, key := range kbBacklightTesting {
s.Logf("-----Adjusting keyboard backlight till %d percent-----", extremeValue)
if err := adjustKBBacklight(ctx, s, h, extremeValue, key, ""); err != nil {
s.Fatal("Failed to adjust keyboard backlight: ", err)
}
}
}
func checkInitKBBacklight(ctx context.Context, h *firmware.Helper) (int, error) {
// Press on a key and check the initial keyboard brightness value.
if err := h.Servo.KeypressWithDuration(ctx, servo.Enter, servo.DurPress); err != nil {
return 0, errors.Wrap(err, "failed to press ENTER to check initial kb backlight")
}
// Delay by 1 second to wait for keyboard to be lit up.
if err := testing.Sleep(ctx, time.Second); err != nil {
return 0, errors.Wrap(err, "error in sleeping for 1 second after pressing on the ENTER key")
}
kbLight, err := h.Servo.GetKBBacklight(ctx)
if err != nil {
return 0, errors.Wrap(err, "failed to get kb backlight")
}
return kbLight, nil
}
func shouldContinue(kbBacklight, extremeValue int, action string) bool {
switch action {
case "increasing":
return kbBacklight <= extremeValue
case "decreasing":
return kbBacklight >= extremeValue
default:
return kbBacklight != extremeValue
}
}
func adjustKBBacklight(ctx context.Context, s *testing.State, h *firmware.Helper, extremeValue int, actionKey, action string) error {
kbLight, err := h.Servo.GetKBBacklight(ctx)
if err != nil {
return errors.Wrap(err, "failed to get kb backlight")
}
for shouldContinue(kbLight, extremeValue, action) {
s.Logf("Attempting to match, current: %d, expected: %d", kbLight, extremeValue)
if err := pressShortcut(ctx, h, actionKey); err != nil {
return errors.Wrap(err, "failed to adjust kb backlight brightness")
}
kbLight, err = h.Servo.GetKBBacklight(ctx)
if err != nil {
return errors.Wrap(err, "failed to get kb backlight")
}
}
return nil
}
func pressShortcut(ctx context.Context, h *firmware.Helper, actionKey string) error {
// ShortCuts for decreasing keyboard backlight: Alt+F6 (Alt+BrightnessDown).
// ShortCuts for increasing keyboard backlight: Alt+F7 (Alt+BrightnessUp).
row, col, err := h.Servo.GetKeyRowCol("<alt_l>")
if err != nil {
return errors.Wrap(err, "failed to get key column and row")
}
altHold := fmt.Sprintf("kbpress %d %d 1", col, row)
altRelease := fmt.Sprintf("kbpress %d %d 0", col, row)
if err := h.Servo.RunECCommand(ctx, altHold); err != nil {
return errors.Wrap(err, "failed to press and hold alt")
}
if err := h.Servo.ECPressKey(ctx, actionKey); err != nil {
return errors.Wrapf(err, "failed to press %q", actionKey)
}
if err := h.Servo.RunECCommand(ctx, altRelease); err != nil {
return errors.Wrap(err, "failed to release alt")
}
return nil
}
func grepKbLightGPIO(ctx context.Context, h *firmware.Helper, s *testing.State, gpios []string) error {
if err := h.Servo.RunECCommand(ctx, "chan 0"); err != nil {
return errors.Wrap(err, "failed to send 'chan 0' to EC")
}
for _, name := range gpios {
var (
reFoundGpio = regexp.MustCompile(fmt.Sprintf(`(?i)(0|1)[^\n\r]*\s%s`, name))
reNotFoundGpio = regexp.MustCompile(`Parameter\s+(\d+)\s+invalid`)
checkGpio = `(` + reFoundGpio.String() + `|` + reNotFoundGpio.String() + `)`
)
cmd := fmt.Sprintf("gpioget %s", name)
out, err := h.Servo.RunECCommandGetOutput(ctx, cmd, []string{checkGpio})
if err != nil {
return errors.Wrapf(err, "failed to run command %v, got error", cmd)
}
if match := reFoundGpio.FindStringSubmatch(out[0][0]); match != nil {
s.Logf("Found gpio %s with value %s", name, match[1])
} else {
s.Logf("Did not find gpio: %s", name)
}
}
if err := h.Servo.RunECCommand(ctx, "chan 0xffffffff"); err != nil {
return errors.Wrap(err, "failed to send 'chan 0xffffffff' to EC")
}
return nil
}
func checkKbLightPwm(ctx context.Context, s *testing.State) error {
reFoundValue := regexp.MustCompile(`Current PWM duty:\s*\d*`)
cmd := firmware.NewECTool(s.DUT(), firmware.ECToolNameMain)
out, err := cmd.Command(ctx, "pwmgetduty", "kb").CombinedOutput()
if err != nil {
msg := strings.Split(strings.TrimSpace(string(out)), "\n")
return errors.Errorf("running 'ectool pwmgetduty kb' on DUT failed: %v, and received: %v", err, msg)
}
match := reFoundValue.FindSubmatch(out)
if len(match) == 0 {
return errors.New("did not find pwm value for kb")
}
s.Logf("Found pwm status for kb, %s", string(match[0]))
return nil
}