/
ec.go
377 lines (347 loc) · 14 KB
/
ec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// Copyright 2021 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 servo
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
// These are the EC Servo controls which can be get/set with a string value.
const (
ECBoard StringControl = "ec_board"
ECSystemPowerState StringControl = "ec_system_powerstate"
ECUARTCmd StringControl = "ec_uart_cmd"
ECUARTRegexp StringControl = "ec_uart_regexp"
ECUARTStream StringControl = "ec_uart_stream"
ECChip StringControl = "ec_chip"
ECFlashSize StringControl = "ec_flash_size"
DUTPDDataRole StringControl = "dut_pd_data_role"
)
// These controls accept only "on" and "off" as values.
const (
ECUARTCapture OnOffControl = "ec_uart_capture"
)
// Cmd constants for RunECCommand.
const (
// Using with no additional arguments returns current backlight level
// If additional int arg (0-100) provided, sets backlight to that level
kbLight string = "kblight"
)
// Pattern expression for RunCommandGetOutput.
const (
reKBBacklight string = `Keyboard backlight: (\d+)\%`
reCheckKBLight string = `Keyboard backlight: \d+\%|Command 'kblight' not found or ambiguous.`
reTabletmodeNotFound string = `Command 'tabletmode' not found or ambiguous`
reBasestateNotFound string = `Command 'basestate' not found or ambiguous`
reTabletmodeStatus string = `\[\S+ tablet mode (enabled|disabled)\]`
reBasestateStatus string = `\[\S+ base state: (attached|detached)\]`
reBdStatus string = `\[\S+ BD forced (connected|disconnected)\]`
reLidAccel string = `\[\S+ Lid Accel ODR:(?i)[^\n\r]*(?i)(1|0)\S+]`
)
// USBCDataRole is a USB-C data role.
type USBCDataRole string
// USB-C data roles.
const (
// UFP is Upward facing partner, i.e. a peripheral. The servo should normally be in this role.
UFP USBCDataRole = "UFP"
// DFP is Downward facing partner, i.e. a host. The DUT should normally be in this role.
DFP USBCDataRole = "DFP"
)
// KBMatrixPair is a struct to store key row and col for the kbpress cmd
type KBMatrixPair struct {
row int
col int
}
// KeyMatrix is a map that stores a row/col pair for each key using KBMatrixPair
// It's stored in order of appearance in a keyboard
var KeyMatrix = map[string]KBMatrixPair{
"<esc>": KBMatrixPair{1, 1},
"<f1>": KBMatrixPair{0, 2},
"<f2>": KBMatrixPair{3, 2},
"<f3>": KBMatrixPair{2, 2},
"<f4>": KBMatrixPair{1, 2},
"<f5>": KBMatrixPair{3, 4},
"<f6>": KBMatrixPair{2, 4},
"<f7>": KBMatrixPair{1, 4},
"<f8>": KBMatrixPair{2, 9},
"<f9>": KBMatrixPair{1, 9},
"<f10>": KBMatrixPair{0, 4},
"`": KBMatrixPair{3, 1},
"1": KBMatrixPair{6, 1},
"2": KBMatrixPair{6, 4},
"3": KBMatrixPair{6, 2},
"4": KBMatrixPair{6, 3},
"5": KBMatrixPair{3, 3},
"6": KBMatrixPair{3, 6},
"7": KBMatrixPair{6, 6},
"8": KBMatrixPair{6, 5},
"9": KBMatrixPair{6, 9},
"0": KBMatrixPair{6, 8},
"-": KBMatrixPair{3, 8},
"=": KBMatrixPair{0, 8},
"<backspace>": KBMatrixPair{1, 11},
"<tab>": KBMatrixPair{2, 1},
"q": KBMatrixPair{7, 1},
"w": KBMatrixPair{7, 4},
"e": KBMatrixPair{7, 2},
"r": KBMatrixPair{7, 3},
"t": KBMatrixPair{2, 3},
"y": KBMatrixPair{2, 6},
"u": KBMatrixPair{7, 6},
"i": KBMatrixPair{7, 5},
"o": KBMatrixPair{7, 9},
"p": KBMatrixPair{7, 8},
"[": KBMatrixPair{2, 8},
"]": KBMatrixPair{2, 5},
"\\": KBMatrixPair{3, 11},
"<search>": KBMatrixPair{0, 1},
"a": KBMatrixPair{4, 1},
"s": KBMatrixPair{4, 4},
"d": KBMatrixPair{4, 2},
"f": KBMatrixPair{4, 3},
"g": KBMatrixPair{1, 3},
"h": KBMatrixPair{1, 6},
"j": KBMatrixPair{4, 6},
"k": KBMatrixPair{4, 5},
"l": KBMatrixPair{4, 9},
";": KBMatrixPair{4, 8},
"'": KBMatrixPair{1, 8},
"<enter>": KBMatrixPair{4, 11},
"<shift_l>": KBMatrixPair{5, 7},
"z": KBMatrixPair{5, 1},
"x": KBMatrixPair{5, 4},
"c": KBMatrixPair{5, 2},
"v": KBMatrixPair{5, 3},
"b": KBMatrixPair{0, 3},
"n": KBMatrixPair{0, 6},
"m": KBMatrixPair{5, 6},
",": KBMatrixPair{5, 5},
".": KBMatrixPair{5, 9},
"/": KBMatrixPair{5, 8},
"<shift_r>": KBMatrixPair{7, 7},
"<ctrl_l>": KBMatrixPair{2, 0},
"<alt_l>": KBMatrixPair{6, 10},
" ": KBMatrixPair{5, 1},
"<alt_r>": KBMatrixPair{0, 10},
"<ctrl_r>": KBMatrixPair{4, 0},
"<left>": KBMatrixPair{7, 12},
"<up>": KBMatrixPair{7, 11},
"<down>": KBMatrixPair{6, 11},
"<right>": KBMatrixPair{6, 12},
}
// HibernationOpt is an option for hibernating DUT.
type HibernationOpt string
// Available options for triggering hibernation.
const (
// UseKeyboard uses keyboard shortcut for hibernating DUT: alt+vol_up+h.
UseKeyboard HibernationOpt = "keyboard"
// UseConsole uses the EC command `hibernate` to put DUT in hibernation.
UseConsole HibernationOpt = "console"
)
// RunECCommand runs the given command on the EC on the device.
func (s *Servo) RunECCommand(ctx context.Context, cmd string) error {
if err := s.SetString(ctx, ECUARTRegexp, "None"); err != nil {
return errors.Wrap(err, "Clearing EC UART Regexp")
}
return s.SetString(ctx, ECUARTCmd, cmd)
}
// RunECCommandGetOutput runs the given command on the EC on the device and returns the output matching patterns.
func (s *Servo) RunECCommandGetOutput(ctx context.Context, cmd string, patterns []string) ([][]string, error) {
err := s.SetStringList(ctx, ECUARTRegexp, patterns)
if err != nil {
return nil, errors.Wrapf(err, "setting ECUARTRegexp to %s", patterns)
}
defer s.SetString(ctx, ECUARTRegexp, "None")
err = s.SetString(ctx, ECUARTCmd, cmd)
if err != nil {
return nil, errors.Wrapf(err, "setting ECUARTCmd to %s", cmd)
}
iList, err := s.GetStringList(ctx, ECUARTCmd)
if err != nil {
return nil, errors.Wrap(err, "decoding string list")
}
return ConvertToStringArrayArray(ctx, iList)
}
// GetECSystemPowerState returns the power state, like "S0" or "G3"
func (s *Servo) GetECSystemPowerState(ctx context.Context) (string, error) {
return s.GetString(ctx, ECSystemPowerState)
}
// ECHibernate puts the EC into hibernation mode, after removing the servo watchdog for CCD if necessary.
func (s *Servo) ECHibernate(ctx context.Context, option HibernationOpt) error {
if err := s.WatchdogRemove(ctx, WatchdogCCD); err != nil {
return errors.Wrap(err, "failed to remove watchdog for ccd")
}
switch option {
case "keyboard":
if err := func(ctx context.Context) error {
for _, targetKey := range []string{"<alt_l>", "<f10>", "h"} {
row, col, err := s.GetKeyRowCol(targetKey)
if err != nil {
return errors.Wrapf(err, "failed to get key %s column and row", targetKey)
}
targetKeyName := targetKey
targetKeyHold := fmt.Sprintf("kbpress %d %d 1", col, row)
targetKeyRelease := fmt.Sprintf("kbpress %d %d 0", col, row)
testing.ContextLogf(ctx, "Pressing and holding key %s", targetKey)
if err := s.RunECCommand(ctx, targetKeyHold); err != nil {
return errors.Wrapf(err, "failed to press and hold key %s", targetKey)
}
defer func(releaseKey, name string) error {
testing.ContextLogf(ctx, "Releasing key %s", name)
if err := s.RunECCommand(ctx, releaseKey); err != nil {
return errors.Wrapf(err, "failed to release key %s", releaseKey)
}
return nil
}(targetKeyRelease, targetKeyName)
}
return nil
}(ctx); err != nil {
return err
}
case "console":
if err := s.RunECCommand(ctx, "hibernate"); err != nil {
return errors.Wrap(err, "failed to run EC command: hibernate")
}
}
// Delay for a few seconds to allow proper propagation of the
// hibernation command, prior to checking EC unresponsive.
if err := testing.Sleep(ctx, 5*time.Second); err != nil {
return errors.Wrap(err, "failed to sleep")
}
if err := s.CheckUnresponsiveEC(ctx); err != nil {
return errors.Wrap(err, "while verifying whether EC is unresponsive after hibernating DUT")
}
return nil
}
// GetECFlashSize returns the size of EC in KB e.g. 512
func (s *Servo) GetECFlashSize(ctx context.Context) (int, error) {
sizeStr, err := s.GetString(ctx, ECFlashSize)
if err != nil {
return 0, errors.Wrap(err, "failed to get value for ec size")
}
// ECFlashSize method matches an int regex so Atoi should always work
return strconv.Atoi(sizeStr)
}
// GetECChip returns the DUT chip e.g. "npcx_uut"
func (s *Servo) GetECChip(ctx context.Context) (string, error) {
return s.GetString(ctx, ECChip)
}
// SetDUTPDDataRole tries to find the port attached to the servo, and performs a data role swap if the role doesn't match `role`.
// Will fail if there is no chromeos EC.
func (s *Servo) SetDUTPDDataRole(ctx context.Context, role USBCDataRole) error {
return s.SetString(ctx, DUTPDDataRole, string(role))
}
// GetKeyRowCol returns the key row and column for kbpress cmd
func (s *Servo) GetKeyRowCol(key string) (int, int, error) {
pair, ok := KeyMatrix[key]
if !ok {
return 0, 0, errors.New("failed to find key in KeyMatrix map")
}
return pair.row, pair.col, nil
}
// ECPressKey simulates a keypress on the DUT from the servo using kbpress.
func (s *Servo) ECPressKey(ctx context.Context, key string) error {
row, col, err := s.GetKeyRowCol(key)
if err != nil {
return errors.Wrapf(err, "failed to get key %q in key matrix", key)
}
if err := s.RunECCommand(ctx, fmt.Sprintf("kbpress %d %d 1", col, row)); err != nil {
return errors.Wrapf(err, "failed to press key %q", key)
}
if err := s.RunECCommand(ctx, fmt.Sprintf("kbpress %d %d 0", col, row)); err != nil {
return errors.Wrapf(err, "failed to release key %q", key)
}
return nil
}
// SetKBBacklight sets the DUT keyboards backlight to the given value (0 - 100).
func (s *Servo) SetKBBacklight(ctx context.Context, percent int) error {
testing.ContextLog(ctx, "Setting keyboard backlight to: ", percent)
err := s.RunECCommand(ctx, fmt.Sprintf("%v %v", kbLight, percent))
if err != nil {
return errors.Wrapf(err, "running '%v %v' on DUT", kbLight, percent)
}
return nil
}
// GetKBBacklight gets the DUT keyboards backlight value in percent (0 - 100).
func (s *Servo) GetKBBacklight(ctx context.Context) (int, error) {
testing.ContextLog(ctx, "Getting current keyboard backlight percent")
out, err := s.RunECCommandGetOutput(ctx, kbLight, []string{reKBBacklight})
if err != nil {
return 0, errors.Wrapf(err, "running %v on DUT", kbLight)
}
return strconv.Atoi(out[0][1])
}
// HasKBBacklight checks if the DUT keyboards has backlight functionality.
func (s *Servo) HasKBBacklight(ctx context.Context) bool {
testing.ContextLog(ctx, "Checking if DUT keyboard supports backlight")
out, _ := s.RunECCommandGetOutput(ctx, kbLight, []string{reCheckKBLight})
expMatch := regexp.MustCompile(reKBBacklight)
match := expMatch.FindStringSubmatch(out[0][0])
return match != nil
}
// CheckUnresponsiveEC verifies that EC console is unresponsive in situations such as
// hibernation and battery cutoff. Ignore null chars, sometimes the servo returns null
// when the EC is off.
func (s *Servo) CheckUnresponsiveEC(ctx context.Context) error {
return testing.Poll(ctx, func(ctx context.Context) error {
out, err := s.RunECCommandGetOutput(ctx, "version", []string{`[^\x00]+`})
if err == nil {
return errors.Errorf("EC is still active: got %v; expected error", out)
}
if !strings.Contains(err.Error(), "No data was sent from the pty") &&
!strings.Contains(err.Error(), "EC: Timeout waiting for response.") &&
!strings.Contains(err.Error(), "Timed out waiting for interfaces to become available") {
return errors.Wrap(err, "unexpected EC error")
}
return nil
}, &testing.PollOptions{Interval: 1 * time.Second, Timeout: 20 * time.Second})
}
// CheckAndRunTabletModeCommand checks if relevant EC commands exist and use them for setting tablet mode.
// For example, detachables use 'basestate (attach|detach)', and convertibles use 'tabletmode (on|off)'.
func (s *Servo) CheckAndRunTabletModeCommand(ctx context.Context, command string) (string, error) {
// regular expressions.
reStr := strings.Join([]string{reTabletmodeNotFound, reTabletmodeStatus,
reBasestateNotFound, reBasestateStatus, reBdStatus, reLidAccel}, "|")
checkTabletMode := fmt.Sprintf("%s%s%s", "(", reStr, ")")
// Run EC command to check tablet mode setting.
out, err := s.RunECCommandGetOutput(ctx, command, []string{checkTabletMode})
if err != nil {
return "", errors.Wrapf(err, "failed to run command %q", command)
}
tabletModeUnavailable := []*regexp.Regexp{regexp.MustCompile(reTabletmodeNotFound),
regexp.MustCompile(reBasestateNotFound)}
for _, v := range tabletModeUnavailable {
if match := v.FindStringSubmatch(out[0][0]); match != nil {
return "", errors.Errorf("device does not support tablet mode: %q", match)
}
}
return string(out[0][1]), nil
}
// OpenCCD checks if a CCD connection exists, and then opens CCD if it's locked.
func (s *Servo) OpenCCD(ctx context.Context) error {
if hasCCD, err := s.HasCCD(ctx); err != nil {
return errors.Wrap(err, "while checking if servo has a CCD connection")
} else if hasCCD {
if val, err := s.GetString(ctx, GSCCCDLevel); err != nil {
return errors.Wrap(err, "failed to get gsc_ccd_level")
} else if val != Open {
testing.ContextLogf(ctx, "CCD is not open, got %q. Attempting to unlock", val)
if err := s.SetString(ctx, CR50Testlab, Open); err != nil {
return errors.Wrap(err, "failed to unlock CCD")
}
}
// For debugging purposes, log CCD state after unlocking CCD.
checkedVal, err := s.GetString(ctx, GSCCCDLevel)
if err != nil {
return errors.Wrap(err, "failed to get gsc_ccd_level after unlocking CCD")
}
testing.ContextLogf(ctx, "CCD State: %q", checkedVal)
}
return nil
}