1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import ast
6import logging
7import re
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros import ec
12from autotest_lib.server.cros.servo import servo
13
14# Hostevent codes, copied from:
15#     ec/include/ec_commands.h
16HOSTEVENT_LID_CLOSED        = 0x00000001
17HOSTEVENT_LID_OPEN          = 0x00000002
18HOSTEVENT_POWER_BUTTON      = 0x00000004
19HOSTEVENT_AC_CONNECTED      = 0x00000008
20HOSTEVENT_AC_DISCONNECTED   = 0x00000010
21HOSTEVENT_BATTERY_LOW       = 0x00000020
22HOSTEVENT_BATTERY_CRITICAL  = 0x00000040
23HOSTEVENT_BATTERY           = 0x00000080
24HOSTEVENT_THERMAL_THRESHOLD = 0x00000100
25HOSTEVENT_THERMAL_OVERLOAD  = 0x00000200
26HOSTEVENT_THERMAL           = 0x00000400
27HOSTEVENT_USB_CHARGER       = 0x00000800
28HOSTEVENT_KEY_PRESSED       = 0x00001000
29HOSTEVENT_INTERFACE_READY   = 0x00002000
30# Keyboard recovery combo has been pressed
31HOSTEVENT_KEYBOARD_RECOVERY = 0x00004000
32# Shutdown due to thermal overload
33HOSTEVENT_THERMAL_SHUTDOWN  = 0x00008000
34# Shutdown due to battery level too low
35HOSTEVENT_BATTERY_SHUTDOWN  = 0x00010000
36HOSTEVENT_INVALID           = 0x80000000
37
38# Time to wait after sending keypress commands.
39KEYPRESS_RECOVERY_TIME = 0.5
40
41# Wakemask types, copied from:
42#     ec/include/ec_commands.h
43EC_HOST_EVENT_MAIN = 0
44EC_HOST_EVENT_B = 1
45EC_HOST_EVENT_SCI_MASK = 2
46EC_HOST_EVENT_SMI_MASK = 3
47EC_HOST_EVENT_ALWAYS_REPORT_MASK = 4
48EC_HOST_EVENT_ACTIVE_WAKE_MASK = 5
49EC_HOST_EVENT_LAZY_WAKE_MASK_S0IX = 6
50EC_HOST_EVENT_LAZY_WAKE_MASK_S3 = 7
51EC_HOST_EVENT_LAZY_WAKE_MASK_S5 = 8
52
53
54class ChromeConsole(object):
55    """Manages control of a Chrome console.
56
57    We control the Chrome console via the UART of a Servo board. Chrome console
58    provides many interfaces to set and get its behavior via console commands.
59    This class is to abstract these interfaces.
60    """
61
62    CMD = "_cmd"
63    REGEXP = "_regexp"
64    MULTICMD = "_multicmd"
65
66    # EC Features
67    # Quoted from 'enum ec_feature_code' in platform/ec/include/ec_commands.h.
68    EC_FEATURE = {
69        'EC_FEATURE_LIMITED'                            : 0,
70        'EC_FEATURE_FLASH'                              : 1,
71        'EC_FEATURE_PWM_FAN'                            : 2,
72        'EC_FEATURE_PWM_KEYB'                           : 3,
73        'EC_FEATURE_LIGHTBAR'                           : 4,
74        'EC_FEATURE_LED'                                : 5,
75        'EC_FEATURE_MOTION_SENSE'                       : 6,
76        'EC_FEATURE_KEYB'                               : 7,
77        'EC_FEATURE_PSTORE'                             : 8,
78        'EC_FEATURE_PORT80'                             : 9,
79        'EC_FEATURE_THERMAL'                            : 10,
80        'EC_FEATURE_BKLIGHT_SWITCH'                     : 11,
81        'EC_FEATURE_WIFI_SWITCH'                        : 12,
82        'EC_FEATURE_HOST_EVENTS'                        : 13,
83        'EC_FEATURE_GPIO'                               : 14,
84        'EC_FEATURE_I2C'                                : 15,
85        'EC_FEATURE_CHARGER'                            : 16,
86        'EC_FEATURE_BATTERY'                            : 17,
87        'EC_FEATURE_SMART_BATTERY'                      : 18,
88        'EC_FEATURE_HANG_DETECT'                        : 19,
89        'EC_FEATURE_PMU'                                : 20,
90        'EC_FEATURE_SUB_MCU'                            : 21,
91        'EC_FEATURE_USB_PD'                             : 22,
92        'EC_FEATURE_USB_MUX'                            : 23,
93        'EC_FEATURE_MOTION_SENSE_FIFO'                  : 24,
94        'EC_FEATURE_VSTORE'                             : 25,
95        'EC_FEATURE_USBC_SS_MUX_VIRTUAL'                : 26,
96        'EC_FEATURE_RTC'                                : 27,
97        'EC_FEATURE_FINGERPRINT'                        : 28,
98        'EC_FEATURE_TOUCHPAD'                           : 29,
99        'EC_FEATURE_RWSIG'                              : 30,
100        'EC_FEATURE_DEVICE_EVENT'                       : 31,
101        'EC_FEATURE_UNIFIED_WAKE_MASKS'                 : 32,
102        'EC_FEATURE_HOST_EVENT64'                       : 33,
103        'EC_FEATURE_EXEC_IN_RAM'                        : 34,
104        'EC_FEATURE_CEC'                                : 35,
105        'EC_FEATURE_MOTION_SENSE_TIGHT_TIMESTAMPS'      : 36,
106        'EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS'     : 37,
107        'EC_FEATURE_EFS2'                               : 38,
108        'EC_FEATURE_SCP'                                : 39,
109        'EC_FEATURE_ISH'                                : 40,
110    }
111
112    def __init__(self, servo, name):
113        """Initialize and keep the servo object.
114
115        Args:
116          servo: A Servo object.
117          name: The console name.
118        """
119        self.name = name
120        self.uart_cmd = self.name + self.CMD
121        self.uart_regexp = self.name + self.REGEXP
122        self.uart_multicmd = self.name + self.MULTICMD
123
124        self._servo = servo
125
126    def __repr__(self):
127        """Return a string representation: <ChromeConsole 'foo_uart'>"""
128        return "<%s %r>" % (self.__class__.__name__, self.name)
129
130    def set_uart_regexp(self, regexp):
131        self._servo.set(self.uart_regexp, regexp)
132
133    def clear_uart_regex(self):
134        """Clear uart_regexp"""
135        self.set_uart_regexp('None')
136
137    def send_command(self, commands):
138        """Send command through UART.
139
140        This function opens UART pty when called, and then command is sent
141        through UART.
142
143        Args:
144          commands: The commands to send, either a list or a string.
145        """
146        self.clear_uart_regex()
147        if isinstance(commands, list):
148            try:
149                self._servo.set_nocheck(self.uart_multicmd, ';'.join(commands))
150            except servo.ControlUnavailableError:
151                logging.warning('The servod is too old that uart_multicmd not '
152                                'supported. Use uart_cmd instead.')
153                for command in commands:
154                    self._servo.set_nocheck(self.uart_cmd, command)
155        else:
156            self._servo.set_nocheck(self.uart_cmd, commands)
157        self.clear_uart_regex()
158
159    def has_command(self, command):
160        """Check whether EC console supports |command|.
161
162        Args:
163          command: Command to look for.
164
165        Returns:
166          True: If the |command| exist on the EC image of the device.
167          False: If the |command| does not exist on the EC image of the device.
168        """
169        result = None
170        try:
171            # Throws error.TestFail (on timeout) if it cannot find a line with
172            # 'command' in the output. Thus return False in that case.
173            result = self.send_command_get_output('help', [command])
174        except error.TestFail:
175            return False
176        return result is not None
177
178    def send_command_get_output(self, command, regexp_list):
179        """Send command through UART and wait for response.
180
181        This function waits for response message matching regular expressions.
182
183        Args:
184          command: The command sent.
185          regexp_list: List of regular expressions used to match response
186            message. Note, list must be ordered.
187
188        Returns:
189          List of tuples, each of which contains the entire matched string and
190          all the subgroups of the match. None if not matched.
191          For example:
192            response of the given command:
193              High temp: 37.2
194              Low temp: 36.4
195            regexp_list:
196              ['High temp: (\d+)\.(\d+)', 'Low temp: (\d+)\.(\d+)']
197            returns:
198              [('High temp: 37.2', '37', '2'), ('Low temp: 36.4', '36', '4')]
199
200        Raises:
201          error.TestError: An error when the given regexp_list is not valid.
202        """
203        if not isinstance(regexp_list, list):
204            raise error.TestError('Arugment regexp_list is not a list: %s' %
205                                  str(regexp_list))
206
207        self.set_uart_regexp(str(regexp_list))
208        self._servo.set_nocheck(self.uart_cmd, command)
209        rv = ast.literal_eval(self._servo.get(self.uart_cmd))
210        self.clear_uart_regex()
211
212        return rv
213
214
215    def is_dfp(self, port=0):
216        """This function checks if EC is DFP
217
218        Args:
219          port: Port of EC to check
220
221        Returns:
222          True: if EC is DFP
223          False: if EC is not DFP
224        """
225        is_dfp = None
226        try:
227            # After reboot, EC should be UFP, but workaround in servod
228            # can perform PD Data Swap in workaroud so check that
229            ret = self.send_command_get_output("pd %d state" % port, ["DFP"])
230            is_dfp = True
231        except Exception as e:
232            # EC is UFP
233            is_dfp = False
234
235        return is_dfp
236
237
238class ChromeEC(ChromeConsole):
239    """Manages control of a Chrome EC.
240
241    We control the Chrome EC via the UART of a Servo board. Chrome EC
242    provides many interfaces to set and get its behavior via console commands.
243    This class is to abstract these interfaces.
244    """
245
246    def __init__(self, servo, name="ec_uart"):
247        super(ChromeEC, self).__init__(servo, name)
248
249    def __repr__(self):
250        """Return a string representation of the object: <ChromeEC 'ec_uart'>"""
251        return "<%s %r>" % (self.__class__.__name__, self.name)
252
253    def key_down(self, keyname):
254        """Simulate pressing a key.
255
256        Args:
257          keyname: Key name, one of the keys of KEYMATRIX.
258        """
259        self.send_command('kbpress %d %d 1' %
260                (ec.KEYMATRIX[keyname][1], ec.KEYMATRIX[keyname][0]))
261
262
263    def key_up(self, keyname):
264        """Simulate releasing a key.
265
266        Args:
267          keyname: Key name, one of the keys of KEYMATRIX.
268        """
269        self.send_command('kbpress %d %d 0' %
270                (ec.KEYMATRIX[keyname][1], ec.KEYMATRIX[keyname][0]))
271
272
273    def key_press(self, keyname):
274        """Press and then release a key.
275
276        Args:
277          keyname: Key name, one of the keys of KEYMATRIX.
278        """
279        self.send_command([
280                'kbpress %d %d 1' %
281                    (ec.KEYMATRIX[keyname][1], ec.KEYMATRIX[keyname][0]),
282                'kbpress %d %d 0' %
283                    (ec.KEYMATRIX[keyname][1], ec.KEYMATRIX[keyname][0]),
284                ])
285        # Don't spam the EC console as fast as we can; leave some recovery time
286        # in between commands.
287        time.sleep(KEYPRESS_RECOVERY_TIME)
288
289
290    def send_key_string_raw(self, string):
291        """Send key strokes consisting of only characters.
292
293        Args:
294          string: Raw string.
295        """
296        for c in string:
297            self.key_press(c)
298
299
300    def send_key_string(self, string):
301        """Send key strokes including special keys.
302
303        Args:
304          string: Character string including special keys. An example
305            is "this is an<tab>example<enter>".
306        """
307        for m in re.finditer("(<[^>]+>)|([^<>]+)", string):
308            sp, raw = m.groups()
309            if raw is not None:
310                self.send_key_string_raw(raw)
311            else:
312                self.key_press(sp)
313
314
315    def reboot(self, flags=''):
316        """Reboot EC with given flags.
317
318        Args:
319          flags: Optional, a space-separated string of flags passed to the
320                 reboot command, including:
321                   default: EC soft reboot;
322                   'hard': EC hard/cold reboot;
323                   'ap-off': Leave AP off after EC reboot (by default, EC turns
324                             AP on after reboot if lid is open).
325
326        Raises:
327          error.TestError: If the string of flags is invalid.
328        """
329        for flag in flags.split():
330            if flag not in ('hard', 'ap-off'):
331                raise error.TestError(
332                        'The flag %s of EC reboot command is invalid.' % flag)
333        self.send_command("reboot %s" % flags)
334
335
336    def set_flash_write_protect(self, enable):
337        """Set the software write protect of EC flash.
338
339        Args:
340          enable: True to enable write protect, False to disable.
341        """
342        if enable:
343            self.send_command("flashwp enable")
344        else:
345            self.send_command("flashwp disable")
346
347
348    def set_hostevent(self, codes):
349        """Set the EC hostevent codes.
350
351        Args:
352          codes: Hostevent codes, HOSTEVENT_*
353        """
354        self.send_command("hostevent set %#x" % codes)
355        # Allow enough time for EC to process input and set flag.
356        # See chromium:371631 for details.
357        # FIXME: Stop importing time module if this hack becomes obsolete.
358        time.sleep(1)
359
360
361    def enable_console_channel(self, channel):
362        """Find console channel mask and enable that channel only
363
364        @param channel: console channel name
365        """
366        # The 'chan' command returns a list of console channels,
367        # their channel masks and channel numbers
368        regexp = r'(\d+)\s+([\w]+)\s+\*?\s+{0}'.format(channel)
369        l = self.send_command_get_output('chan', [regexp])
370        # Use channel mask and append the 0x for proper hex input value
371        cmd = 'chan 0x' + l[0][2]
372        # Set console to only output the desired channel
373        self.send_command(cmd)
374
375
376    def get_version(self):
377        """Get version information from the Chrome EC console.
378           Additionally, can be used to verify if EC console is available.
379        """
380        self.send_command("chan 0")
381        expected_output = ["Chip:\s+([^\r\n]*)\r\n",
382                           "RO:\s+([^\r\n]*)\r\n",
383                           "RW_?[AB]?:\s+([^\r\n]*)\r\n",
384                           "Build:\s+([^\r\n]*)\r\n"]
385        l = self.send_command_get_output("version", expected_output)
386        self.send_command("chan 0xffffffff")
387        return l
388
389    def check_ro_rw(self, img_exp):
390        """Tell if the current EC image matches the given input, 'RW' or 'RO.
391
392        Args:
393            img_exp: Expected image type. It should be either 'RW' or 'RO'.
394        Return:
395            True if the active EC image matches to 'img_exp'.
396            False otherwise.
397        Raise:
398            TestError if img_exp is neither 'RW' nor 'RO'.
399        """
400        if img_exp not in ['RW', 'RO']:
401            raise error.TestError('Arugment img_exp is neither RW nor RO')
402
403        result = self.send_command_get_output('sysinfo', [r'Copy:\s*(RO|RW)'])
404        return result[0][1] == img_exp
405
406    def check_feature(self, feature):
407        """Return true if EC supports the given feature
408
409        Args:
410            feature: feature name as a string as in self.EC_FEATURE.
411
412        Returns:
413            True if 'feature' is in EC's feature set.
414            False otherwise
415        """
416        feat_id = self.EC_FEATURE[feature]
417        if feat_id < 32:
418            feat_start = 0
419        else:
420            feat_start = 32
421
422        regexp = r'%d-%d:\s*(0x[0-9a-fA-F]{8})' % (feat_start,
423                                                   feat_start + 31)
424
425        try:
426            result = self.send_command_get_output('feat', [regexp])
427        except servo.ResponsiveConsoleError as e:
428            logging.warn("feat command is not available: %s", str(e))
429            return False
430
431        feat_bitmap = int(result[0][1], 16)
432
433        return ((1 << (feat_id - feat_start)) & feat_bitmap) != 0
434
435
436class ChromeUSBPD(ChromeEC):
437    """Manages control of a Chrome USBPD.
438
439    We control the Chrome EC via the UART of a Servo board. Chrome USBPD
440    provides many interfaces to set and get its behavior via console commands.
441    This class is to abstract these interfaces.
442    """
443
444    def __init__(self, servo):
445        super(ChromeUSBPD, self).__init__(servo, "usbpd_uart")
446