• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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 logging
6import pprint
7import time
8import re
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server import autotest
12from autotest_lib.server.cros.faft.cr50_test import Cr50Test
13
14
15class firmware_Cr50DeviceState(Cr50Test):
16    """Verify Cr50 tracks the EC and AP state correctly.
17
18    Put the device through S0, S0ix, S3, and G3. Cr50 responds to these state
19    changes by enabling/disabling uart and changing its suspend type. Verify
20    that none of these cause any interrupt storms on Cr50. Make sure that there
21    aren't any interrupt storms and that Cr50 enters regular or deep sleep a
22    reasonable amount of times.
23    """
24    version = 1
25
26    DEEP_SLEEP_STEP_SUFFIX = ' Num Deep Sleep Steps'
27
28    # Use negative numbers to keep track of counts not in the IRQ list.
29    KEY_DEEP_SLEEP = -3
30    KEY_TIME = -2
31    KEY_RESET = -1
32    KEY_REGULAR_SLEEP = 112
33    INT_NAME = {
34        KEY_RESET  : 'Reset Count',
35        KEY_DEEP_SLEEP  : 'Deep Sleep Count',
36        KEY_TIME  : 'Cr50 Time',
37        4 : 'HOST_CMD_DONE',
38        81  : 'GPIO0',
39        98  : 'GPIO1',
40        103 : 'I2CS WRITE',
41        KEY_REGULAR_SLEEP : 'PMU WAKEUP',
42        113 : 'AC present FED',
43        114 : 'AC present RED',
44        124 : 'RBOX_INTR_PWRB',
45        130 : 'SPS CS deassert',
46        138 : 'SPS RXFIFO LVL',
47        159 : 'SPS RXFIFO overflow',
48        160 : 'EVENT TIMER',
49        174 : 'CR50_RX_SERVO_TX',
50        177 : 'CR50_TX_SERVO_RX',
51        181 : 'AP_TX_CR50_RX',
52        184 : 'AP_RX_CR50_TX',
53        188 : 'EC_TX_CR50_RX',
54        191 : 'EC_RX_CR50_TX',
55        193 : 'USB',
56    }
57    SLEEP_KEYS = [ KEY_REGULAR_SLEEP, KEY_DEEP_SLEEP ]
58    # Cr50 won't enable any form of sleep until it has been up for 20 seconds.
59    SLEEP_DELAY = 20
60    # The time in seconds to wait in each state
61    SLEEP_TIME = 30
62    SHORT_WAIT = 5
63    CONSERVATIVE_WAIT_TIME = SLEEP_TIME + SHORT_WAIT + 10
64    # Cr50 should wake up twice per second while in regular sleep
65    SLEEP_RATE = 2
66
67    DEEP_SLEEP_MAX = 2
68    ARM = 'ARM '
69
70    # If there are over 100,000 interrupts, it is an interrupt storm.
71    DEFAULT_COUNTS = [0, 100000]
72    # A dictionary of ok count values for each irq that shouldn't follow the
73    # DEFAULT_COUNTS range.
74    EXPECTED_IRQ_COUNT_RANGE = {
75        KEY_RESET : [0, 0],
76        KEY_DEEP_SLEEP : [0, DEEP_SLEEP_MAX],
77        KEY_TIME : [0, CONSERVATIVE_WAIT_TIME],
78        'S0ix' + DEEP_SLEEP_STEP_SUFFIX : [0, 0],
79        # Cr50 may enter deep sleep an extra time, because of how the test
80        # collects taskinfo counts. Just verify that it does enter deep sleep
81        'S3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2],
82        'G3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2],
83        # ARM devices don't enter deep sleep in S3
84        ARM + 'S3' + DEEP_SLEEP_STEP_SUFFIX : [0, 0],
85        ARM + 'G3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2],
86        # Regular sleep is calculated based on the cr50 time
87    }
88
89    GET_TASKINFO = ['IRQ counts by type:(.*)Service calls']
90
91    START = ''
92    INCREASE = '+'
93    DS_RESUME = 'DS'
94
95
96    def initialize(self, host, cmdline_args, full_args):
97        super(firmware_Cr50DeviceState, self).initialize(host, cmdline_args,
98                                                         full_args)
99        # Don't bother if there is no Chrome EC or if EC hibernate doesn't work.
100        if not self.check_ec_capability():
101            raise error.TestNAError("Nothing needs to be tested on this device")
102
103
104    def get_taskinfo_output(self):
105        """Return a dict with the irq numbers as keys and counts as values"""
106        output = self.cr50.send_command_get_output('taskinfo',
107            self.GET_TASKINFO)[0][1].strip()
108        logging.info(output)
109        return output
110
111
112    def get_irq_counts(self):
113        """Return a dict with the irq numbers as keys and counts as values"""
114        output = self.get_taskinfo_output()
115        irq_list = re.findall('\d+\s+\d+\r', output)
116        # Make sure the regular sleep irq is in the dictionary, even if cr50
117        # hasn't seen any interrupts. It's important the test sees that there's
118        # never an interrupt.
119        irq_counts = { self.KEY_REGULAR_SLEEP : 0 }
120        for irq_info in irq_list:
121            logging.debug(irq_info)
122            num, count = irq_info.split()
123            irq_counts[int(num)] = int(count)
124        irq_counts[self.KEY_RESET] = int(self.servo.get('cr50_reset_count'))
125        irq_counts[self.KEY_DEEP_SLEEP] = int(self.cr50.get_deep_sleep_count())
126        irq_counts[self.KEY_TIME] = int(self.cr50.gettime())
127        return irq_counts
128
129
130    def get_expected_count(self, irq_key, cr50_time):
131        """Get the expected irq increase for the given irq and state
132
133        Args:
134            irq_key: the irq int
135            cr50_time: the cr50 time in seconds
136
137        Returns:
138            A list with the expected irq count range [min, max]
139        """
140        # CCD will prevent sleep
141        if self.ccd_enabled and (irq_key in self.SLEEP_KEYS or
142            self.DEEP_SLEEP_STEP_SUFFIX in str(irq_key)):
143            return [0, 0]
144        if irq_key == self.KEY_REGULAR_SLEEP:
145            # If cr50_time is really low, we probably woke cr50 up using
146            # taskinfo, which would be a pmu wakeup.
147            if cr50_time == 0:
148                return [0, 1]
149
150            min_count = max(cr50_time - self.SLEEP_DELAY, 0)
151            # Just checking there is not a lot of extra sleep wakeups. Add 1 to
152            # the sleep rate so cr50 can have some extra wakeups, but not too
153            # many.
154            max_count = cr50_time * (self.SLEEP_RATE + 1)
155            return [min_count, max_count]
156        return self.EXPECTED_IRQ_COUNT_RANGE.get(irq_key, self.DEFAULT_COUNTS)
157
158
159    def check_increase(self, irq_key, name, increase, expected_range):
160        """Verify the irq count is within the expected range
161
162        Args:
163            irq_key: the irq int
164            name: the irq name string
165            increase: the irq count
166            expected_range: A list with the valid irq count range [min, max]
167
168        Returns:
169            '' if increase is in the given range. If the increase isn't in the
170            range, it returns an error message.
171        """
172        min_count, max_count = expected_range
173        if min_count > increase or max_count < increase:
174            err_msg = '%s %s: %s not in range %s' % (name, irq_key, increase,
175                expected_range)
176            return err_msg
177        return ''
178
179
180    def get_step_events(self):
181        """Use the deep sleep counts to determine the step events"""
182        ds_counts = self.get_irq_step_counts(self.KEY_DEEP_SLEEP)
183        events = []
184        for i, count in enumerate(ds_counts):
185            if not i:
186                events.append(self.START)
187            elif count != ds_counts[i - 1]:
188                # If the deep sleep count changed, Cr50 recovered deep sleep
189                # and the irq counts are reset.
190                events.append(self.DS_RESUME)
191            else:
192                events.append(self.INCREASE)
193        return events
194
195
196    def get_irq_step_counts(self, irq_key):
197        """Get a list of the all of the step counts for the given irq"""
198        return [ irq_dict.get(irq_key, 0) for irq_dict in self.steps ]
199
200
201    def check_for_errors(self, state):
202        """Check for unexpected IRQ counts at each step.
203
204        Find the irq count errors and add them to run_errors.
205
206        Args:
207            state: The power state: S0, S0ix, S3, or G3.
208        """
209        num_steps = len(self.steps)
210        # Get all of the deep sleep counts
211        events = self.get_step_events()
212
213        irq_list = list(self.irqs)
214        irq_list.sort()
215
216        irq_diff = ['%23s' % 'step' + ''.join(self.step_names)]
217        step_errors = [ [] for i in range(num_steps) ]
218
219        cr50_times = self.get_irq_step_counts(self.KEY_TIME)
220        cr50_diffs = []
221        for i, cr50_time in enumerate(cr50_times):
222            if events[i] == self.INCREASE:
223                cr50_time -= cr50_times[i - 1]
224            cr50_diffs.append(cr50_time)
225
226        # Go through each irq and update its info in the progress dict
227        for irq_key in irq_list:
228            name = self.INT_NAME.get(irq_key, 'Unknown')
229            irq_progress_str = ['%19s %3s:' % (name, irq_key)]
230
231            irq_counts = self.get_irq_step_counts(irq_key)
232            for step, count in enumerate(irq_counts):
233                event = events[step]
234
235                # The deep sleep counts are not reset after deep sleep. Change
236                # the event to INCREASE.
237                if irq_key == self.KEY_DEEP_SLEEP and event == self.DS_RESUME:
238                    event = self.INCREASE
239
240                if event == self.INCREASE:
241                    count -= irq_counts[step - 1]
242
243                # Check that the count increase is within the expected value.
244                if event != self.START:
245                    expected_range = self.get_expected_count(irq_key,
246                            cr50_diffs[step])
247
248                    rv = self.check_increase(irq_key, name, count,
249                            expected_range)
250                    if rv:
251                        logging.info('Unexpected count for %s %s', state, rv)
252                        step_errors[step].append(rv)
253
254                irq_progress_str.append(' %2s %7d' % (event, count))
255
256            irq_diff.append(''.join(irq_progress_str))
257
258        errors = {}
259
260        ds_key = self.ARM if self.is_arm else ''
261        ds_key += state + self.DEEP_SLEEP_STEP_SUFFIX
262        expected_range = self.get_expected_count(ds_key, 0)
263        rv = self.check_increase(None, ds_key, events.count(self.DS_RESUME),
264                expected_range)
265        if rv:
266            logging.info('Unexpected count for %s %s', state, rv)
267            errors[ds_key] = rv
268        for i, step_error in enumerate(step_errors):
269            if step_error:
270                logging.error('Step %d errors:\n%s', i,
271                        pprint.pformat(step_error))
272                step = '%s step %d %s' % (state, i, self.step_names[i].strip())
273                errors[step] = step_error
274
275        logging.info('DIFF %s IRQ Counts:\n%s', state, pprint.pformat(irq_diff))
276        if errors:
277            logging.info('ERRORS %s IRQ Counts:\n%s', state,
278                    pprint.pformat(errors))
279            self.run_errors.update(errors)
280
281
282    def trigger_s0(self):
283        """Press the power button so the DUT will wake up."""
284        self.servo.power_short_press()
285
286
287    def enter_state(self, state):
288        """Get the command to enter the power state"""
289        self.stage_irq_add(self.get_irq_counts(), 'start %s' % state)
290        if state == 'S0':
291            self.trigger_s0()
292        else:
293            if state == 'S0ix':
294                full_command = 'echo freeze > /sys/power/state &'
295            elif state == 'S3':
296                full_command = 'echo mem > /sys/power/state &'
297            elif state == 'G3':
298                full_command = 'poweroff'
299            self.faft_client.system.run_shell_command(full_command)
300
301        time.sleep(self.SHORT_WAIT);
302        # check state transition
303        if not self.wait_power_state(state, self.SHORT_WAIT):
304            raise error.TestFail('Platform failed to reach %s state.' % state)
305        self.stage_irq_add(self.get_irq_counts(), 'in %s' % state)
306
307
308    def stage_irq_add(self, irq_dict, name=''):
309        """Add the current irq counts to the stored dictionary of irq info"""
310        self.steps.append(irq_dict)
311        self.step_names.append('%11s' % name)
312        self.irqs.update(irq_dict.keys())
313
314
315    def reset_irq_counts(self):
316        """Reset the test IRQ counts"""
317        self.steps = []
318        self.step_names = []
319        self.irqs = set()
320
321
322    def run_transition(self, state):
323        """Enter the given power state and reenter s0
324
325        Enter the power state and return to S0. Wait long enough to ensure cr50
326        will enter sleep mode, so we can verify that as well.
327
328        Args:
329            state: the power state: S0ix, S3, or G3
330        """
331        self.reset_irq_counts()
332
333        # Enter the given state
334        self.enter_state(state)
335
336        logging.info('waiting %d seconds', self.SLEEP_TIME)
337        time.sleep(self.SLEEP_TIME)
338
339        # Return to S0
340        self.enter_state('S0')
341
342
343    def verify_state(self, state):
344        """Verify cr50 behavior while running through the power state"""
345
346        try:
347            self.run_transition(state)
348        finally:
349            # reset the system to S0 no matter what happens
350            self.trigger_s0()
351
352        # Check that the progress of the irq counts seems reasonable
353        self.check_for_errors(state)
354
355
356    def is_arm_family(self):
357        """Returns True if the DUT is an ARM device."""
358        arch = self.host.run('arch').stdout.strip()
359        return arch in ['aarch64', 'armv7l']
360
361
362    def run_through_power_states(self):
363        """Go through S0ix, S3, and G3. Verify there are no interrupt storms"""
364        self.run_errors = {}
365        self.ccd_str = 'ccd ' + ('enabled' if self.ccd_enabled else 'disabled')
366        logging.info('Running through states with %s', self.ccd_str)
367
368        # Initialize the Test IRQ counts
369        self.reset_irq_counts()
370
371        # Make sure the DUT is in s0
372        self.enter_state('S0')
373
374        # Check if the device supports S0ix. The exit status will be 0 if it
375        # does 1 if it doesn't.
376        result = self.host.run('check_powerd_config --suspend_to_idle',
377                ignore_status=True)
378        if not result.exit_status:
379            # Login before entering S0ix so cr50 will be able to enter regular
380            # sleep
381            client_at = autotest.Autotest(self.host)
382            client_at.run_test('login_LoginSuccess')
383            self.verify_state('S0ix')
384
385        # Enter S3
386        self.verify_state('S3')
387
388        # Enter G3
389        self.verify_state('G3')
390        if self.run_errors:
391            self.all_errors[self.ccd_str] = self.run_errors
392
393
394    def run_once(self, host):
395        """Go through S0ix, S3, and G3. Verify there are no interrupt storms"""
396        self.all_errors = {}
397        self.host = host
398        self.is_arm = self.is_arm_family()
399        supports_dts_control = self.cr50.servo_v4_supports_dts_mode()
400
401        if supports_dts_control:
402            self.cr50.ccd_disable(raise_error=True)
403
404        self.ccd_enabled = self.cr50.ccd_is_enabled()
405        self.run_through_power_states()
406
407        if supports_dts_control:
408            ccd_was_enabled = self.ccd_enabled
409            self.cr50.ccd_enable(raise_error=supports_dts_control)
410            self.ccd_enabled = self.cr50.ccd_is_enabled()
411            # If the first run had ccd disabled, and the test was able to enable
412            # ccd, run through the states again to make sure there are no issues
413            # come up when ccd is enabled.
414            if not ccd_was_enabled and self.ccd_enabled:
415                self.run_through_power_states()
416        else:
417            logging.info('Current setup only supports test with ccd %sabled.',
418                    'en' if self.ccd_enabled else 'dis')
419
420        self.trigger_s0()
421        if self.all_errors:
422            raise error.TestFail('Unexpected Device State: %s' %
423                    self.all_errors)
424        if not supports_dts_control:
425            raise error.TestNAError('Verified device state with %s. Please '
426                    'run with type c servo v4 to test full device state.' %
427                    self.ccd_str)
428