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, logging, re, time
6
7from autotest_lib.client.common_lib import error
8
9# en-US key matrix (from "kb membrane pin matrix.pdf")
10KEYMATRIX = {'`': (3, 1), '1': (6, 1), '2': (6, 4), '3': (6, 2), '4': (6, 3),
11             '5': (3, 3), '6': (3, 6), '7': (6, 6), '8': (6, 5), '9': (6, 9),
12             '0': (6, 8), '-': (3, 8), '=': (0, 8), 'q': (7, 1), 'w': (7, 4),
13             'e': (7, 2), 'r': (7, 3), 't': (2, 3), 'y': (2, 6), 'u': (7, 6),
14             'i': (7, 5), 'o': (7, 9), 'p': (7, 8), '[': (2, 8), ']': (2, 5),
15             '\\': (3, 11), 'a': (4, 1), 's': (4, 4), 'd': (4, 2), 'f': (4, 3),
16             'g': (1, 3), 'h': (1, 6), 'j': (4, 6), 'k': (4, 5), 'l': (4, 9),
17             ';': (4, 8), '\'': (1, 8), 'z': (5, 1), 'x': (5, 4), 'c': (5, 2),
18             'v': (5, 3), 'b': (0, 3), 'n': (0, 6), 'm': (5, 6), ',': (5, 5),
19             '.': (5, 9), '/': (5, 8), ' ': (5, 11), '<right>': (6, 12),
20             '<alt_r>': (0, 10), '<down>': (6, 11), '<tab>': (2, 1),
21             '<f10>': (0, 4), '<shift_r>': (7, 7), '<ctrl_r>': (4, 0),
22             '<esc>': (1, 1), '<backspace>': (1, 11), '<f2>': (3, 2),
23             '<alt_l>': (6, 10), '<ctrl_l>': (2, 0), '<f1>': (0, 2),
24             '<search>': (0, 1), '<f3>': (2, 2), '<f4>': (1, 2), '<f5>': (3, 4),
25             '<f6>': (2, 4), '<f7>': (1, 4), '<f8>': (2, 9), '<f9>': (1, 9),
26             '<up>': (7, 11), '<shift_l>': (5, 7), '<enter>': (4, 11),
27             '<left>': (7, 12)}
28
29
30# Hostevent codes, copied from:
31#     ec/include/ec_commands.h
32HOSTEVENT_LID_CLOSED        = 0x00000001
33HOSTEVENT_LID_OPEN          = 0x00000002
34HOSTEVENT_POWER_BUTTON      = 0x00000004
35HOSTEVENT_AC_CONNECTED      = 0x00000008
36HOSTEVENT_AC_DISCONNECTED   = 0x00000010
37HOSTEVENT_BATTERY_LOW       = 0x00000020
38HOSTEVENT_BATTERY_CRITICAL  = 0x00000040
39HOSTEVENT_BATTERY           = 0x00000080
40HOSTEVENT_THERMAL_THRESHOLD = 0x00000100
41HOSTEVENT_THERMAL_OVERLOAD  = 0x00000200
42HOSTEVENT_THERMAL           = 0x00000400
43HOSTEVENT_USB_CHARGER       = 0x00000800
44HOSTEVENT_KEY_PRESSED       = 0x00001000
45HOSTEVENT_INTERFACE_READY   = 0x00002000
46# Keyboard recovery combo has been pressed
47HOSTEVENT_KEYBOARD_RECOVERY = 0x00004000
48# Shutdown due to thermal overload
49HOSTEVENT_THERMAL_SHUTDOWN  = 0x00008000
50# Shutdown due to battery level too low
51HOSTEVENT_BATTERY_SHUTDOWN  = 0x00010000
52HOSTEVENT_INVALID           = 0x80000000
53
54
55class ChromeEC(object):
56    """Manages control of a Chrome EC.
57
58    We control the Chrome EC via the UART of a Servo board. Chrome EC
59    provides many interfaces to set and get its behavior via console commands.
60    This class is to abstract these interfaces.
61    """
62
63    def __init__(self, servo):
64        """Initialize and keep the servo object.
65
66        Args:
67          servo: A Servo object.
68        """
69        self._servo = servo
70        self._cached_uart_regexp = None
71
72
73    def set_uart_regexp(self, regexp):
74        if self._cached_uart_regexp == regexp:
75            return
76        self._cached_uart_regexp = regexp
77        self._servo.set('ec_uart_regexp', regexp)
78
79
80    def send_command(self, commands):
81        """Send command through UART.
82
83        This function opens UART pty when called, and then command is sent
84        through UART.
85
86        Args:
87          commands: The commands to send, either a list or a string.
88        """
89        self.set_uart_regexp('None')
90        if isinstance(commands, list):
91            try:
92                self._servo.set_nocheck('ec_uart_multicmd', ';'.join(commands))
93            except error.TestFail as e:
94                if 'No control named' in str(e):
95                    logging.warning(
96                            'The servod is too old that ec_uart_multicmd '
97                            'not supported. Use ec_uart_cmd instead.')
98                    for command in commands:
99                        self._servo.set_nocheck('ec_uart_cmd', command)
100                else:
101                    raise
102        else:
103            self._servo.set_nocheck('ec_uart_cmd', commands)
104
105
106    def send_command_get_output(self, command, regexp_list):
107        """Send command through UART and wait for response.
108
109        This function waits for response message matching regular expressions.
110
111        Args:
112          command: The command sent.
113          regexp_list: List of regular expressions used to match response
114            message. Note, list must be ordered.
115
116        Returns:
117          List of tuples, each of which contains the entire matched string and
118          all the subgroups of the match. None if not matched.
119          For example:
120            response of the given command:
121              High temp: 37.2
122              Low temp: 36.4
123            regexp_list:
124              ['High temp: (\d+)\.(\d+)', 'Low temp: (\d+)\.(\d+)']
125            returns:
126              [('High temp: 37.2', '37', '2'), ('Low temp: 36.4', '36', '4')]
127
128        Raises:
129          error.TestError: An error when the given regexp_list is not valid.
130        """
131        if not isinstance(regexp_list, list):
132            raise error.TestError('Arugment regexp_list is not a list: %s' %
133                                  str(regexp_list))
134
135        self.set_uart_regexp(str(regexp_list))
136        self._servo.set_nocheck('ec_uart_cmd', command)
137        return ast.literal_eval(self._servo.get('ec_uart_cmd'))
138
139
140    def key_down(self, keyname):
141        """Simulate pressing a key.
142
143        Args:
144          keyname: Key name, one of the keys of KEYMATRIX.
145        """
146        self.send_command('kbpress %d %d 1' %
147                (KEYMATRIX[keyname][1], KEYMATRIX[keyname][0]))
148
149
150    def key_up(self, keyname):
151        """Simulate releasing a key.
152
153        Args:
154          keyname: Key name, one of the keys of KEYMATRIX.
155        """
156        self.send_command('kbpress %d %d 0' %
157                (KEYMATRIX[keyname][1], KEYMATRIX[keyname][0]))
158
159
160    def key_press(self, keyname):
161        """Press and then release a key.
162
163        Args:
164          keyname: Key name, one of the keys of KEYMATRIX.
165        """
166        self.send_command([
167                'kbpress %d %d 1' %
168                    (KEYMATRIX[keyname][1], KEYMATRIX[keyname][0]),
169                'kbpress %d %d 0' %
170                    (KEYMATRIX[keyname][1], KEYMATRIX[keyname][0]),
171                ])
172
173
174    def send_key_string_raw(self, string):
175        """Send key strokes consisting of only characters.
176
177        Args:
178          string: Raw string.
179        """
180        for c in string:
181            self.key_press(c)
182
183
184    def send_key_string(self, string):
185        """Send key strokes including special keys.
186
187        Args:
188          string: Character string including special keys. An example
189            is "this is an<tab>example<enter>".
190        """
191        for m in re.finditer("(<[^>]+>)|([^<>]+)", string):
192            sp, raw = m.groups()
193            if raw is not None:
194                self.send_key_string_raw(raw)
195            else:
196                self.key_press(sp)
197
198
199    def reboot(self, flags=''):
200        """Reboot EC with given flags.
201
202        Args:
203          flags: Optional, a space-separated string of flags passed to the
204                 reboot command, including:
205                   default: EC soft reboot;
206                   'hard': EC hard/cold reboot;
207                   'ap-off': Leave AP off after EC reboot (by default, EC turns
208                             AP on after reboot if lid is open).
209
210        Raises:
211          error.TestError: If the string of flags is invalid.
212        """
213        for flag in flags.split():
214            if flag not in ('hard', 'ap-off'):
215                raise error.TestError(
216                        'The flag %s of EC reboot command is invalid.' % flag)
217        self.send_command("reboot %s" % flags)
218
219
220    def set_flash_write_protect(self, enable):
221        """Set the software write protect of EC flash.
222
223        Args:
224          enable: True to enable write protect, False to disable.
225        """
226        if enable:
227            self.send_command("flashwp enable")
228        else:
229            self.send_command("flashwp disable")
230
231
232    def set_hostevent(self, codes):
233        """Set the EC hostevent codes.
234
235        Args:
236          codes: Hostevent codes, HOSTEVENT_*
237        """
238        self.send_command("hostevent set %#x" % codes)
239        # Allow enough time for EC to process input and set flag.
240        # See chromium:371631 for details.
241        # FIXME: Stop importing time module if this hack becomes obsolete.
242        time.sleep(1)
243