|
| 1 | +""" |
| 2 | +Copyright (c) 2018-2019 Arm Limited and affiliates. |
| 3 | +SPDX-License-Identifier: Apache-2.0 |
| 4 | +
|
| 5 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +you may not use this file except in compliance with the License. |
| 7 | +You may obtain a copy of the License at |
| 8 | +
|
| 9 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +
|
| 11 | +Unless required by applicable law or agreed to in writing, software |
| 12 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +See the License for the specific language governing permissions and |
| 15 | +limitations under the License. |
| 16 | +""" |
| 17 | +import time |
| 18 | +from mbed_host_tests import BaseHostTest |
| 19 | + |
| 20 | +DEFAULT_SYNC_DELAY = 4.0 |
| 21 | + |
| 22 | +MSG_VALUE_WATCHDOG_PRESENT = 1 |
| 23 | +MSG_VALUE_DUMMY = '0' |
| 24 | +MSG_VALUE_RESET_REASON_GET = 'get' |
| 25 | +MSG_VALUE_RESET_REASON_CLEAR = 'clear' |
| 26 | +MSG_VALUE_DEVICE_RESET_NVIC = 'nvic' |
| 27 | +MSG_VALUE_DEVICE_RESET_WATCHDOG = 'watchdog' |
| 28 | + |
| 29 | +MSG_KEY_DEVICE_READY = 'ready' |
| 30 | +MSG_KEY_RESET_REASON_RAW = 'reason_raw' |
| 31 | +MSG_KEY_RESET_REASON = 'reason' |
| 32 | +MSG_KEY_DEVICE_RESET = 'reset' |
| 33 | +MSG_KEY_SYNC = '__sync' |
| 34 | +MSG_KEY_RESET_COMPLETE = 'reset_complete' |
| 35 | + |
| 36 | +RESET_REASONS = { |
| 37 | + 'POWER_ON': '0', |
| 38 | + 'PIN_RESET': '1', |
| 39 | + 'BROWN_OUT': '2', |
| 40 | + 'SOFTWARE': '3', |
| 41 | + 'WATCHDOG': '4', |
| 42 | + 'LOCKUP': '5', |
| 43 | + 'WAKE_LOW_POWER': '6', |
| 44 | + 'ACCESS_ERROR': '7', |
| 45 | + 'BOOT_ERROR': '8', |
| 46 | + 'MULTIPLE': '9', |
| 47 | + 'PLATFORM': '10', |
| 48 | + 'UNKNOWN': '11' |
| 49 | +} |
| 50 | + |
| 51 | + |
| 52 | +def raise_if_different(expected, actual, text=''): |
| 53 | + """Raise a RuntimeError if actual is different than expected.""" |
| 54 | + if expected != actual: |
| 55 | + raise RuntimeError('{}Got {!r}, expected {!r}' |
| 56 | + .format(text, actual, expected)) |
| 57 | + |
| 58 | + |
| 59 | +class ResetReasonTest(BaseHostTest): |
| 60 | + """Test for the Reset Reason HAL API. |
| 61 | +
|
| 62 | + Given a device supporting a Reset Reason API. |
| 63 | + When the device is restarted using various methods. |
| 64 | + Then the device returns a correct reset reason for every restart. |
| 65 | + """ |
| 66 | + |
| 67 | + def __init__(self): |
| 68 | + super(ResetReasonTest, self).__init__() |
| 69 | + self.device_reasons = None |
| 70 | + self.device_has_watchdog = None |
| 71 | + self.raw_reset_reasons = set() |
| 72 | + self.sync_delay = DEFAULT_SYNC_DELAY |
| 73 | + self.test_steps_sequence = self.test_steps() |
| 74 | + # Advance the coroutine to it's first yield statement. |
| 75 | + self.test_steps_sequence.send(None) |
| 76 | + |
| 77 | + def setup(self): |
| 78 | + sync_delay = self.get_config_item('forced_reset_timeout') |
| 79 | + self.sync_delay = sync_delay if sync_delay is not None else DEFAULT_SYNC_DELAY |
| 80 | + self.register_callback(MSG_KEY_DEVICE_READY, self.cb_device_ready) |
| 81 | + self.register_callback(MSG_KEY_RESET_REASON_RAW, self.cb_reset_reason_raw) |
| 82 | + self.register_callback(MSG_KEY_RESET_REASON, self.cb_reset_reason) |
| 83 | + self.register_callback(MSG_KEY_DEVICE_RESET, self.cb_reset_reason) |
| 84 | + self.register_callback(MSG_KEY_RESET_COMPLETE, self.cb_reset_reason) |
| 85 | + |
| 86 | + def cb_device_ready(self, key, value, timestamp): |
| 87 | + """Request a raw value of the reset_reason register. |
| 88 | +
|
| 89 | + Additionally, save the device's reset_reason capabilities |
| 90 | + and the watchdog status on the first call. |
| 91 | + """ |
| 92 | + if self.device_reasons is None: |
| 93 | + reasons, wdg_status = (int(i, base=16) for i in value.split(',')) |
| 94 | + self.device_has_watchdog = (wdg_status == MSG_VALUE_WATCHDOG_PRESENT) |
| 95 | + self.device_reasons = [k for k, v in RESET_REASONS.items() if (reasons & 1 << int(v))] |
| 96 | + self.send_kv(MSG_KEY_RESET_REASON_RAW, MSG_VALUE_RESET_REASON_GET) |
| 97 | + |
| 98 | + def cb_reset_reason_raw(self, key, value, timestamp): |
| 99 | + """Verify that the raw reset_reason register value is unique. |
| 100 | +
|
| 101 | + Fail the test suite if the raw reset_reason value is not unique. |
| 102 | + Request a platform independent reset_reason otherwise. |
| 103 | + """ |
| 104 | + if value in self.raw_reset_reasons: |
| 105 | + self.log('TEST FAILED: The raw reset reason is not unique. ' |
| 106 | + '{!r} is already present in {!r}.' |
| 107 | + .format(value, self.raw_reset_reasons)) |
| 108 | + self.notify_complete(False) |
| 109 | + else: |
| 110 | + self.raw_reset_reasons.add(value) |
| 111 | + self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_GET) |
| 112 | + |
| 113 | + def cb_reset_reason(self, key, value, timestamp): |
| 114 | + """Feed the test_steps coroutine with reset_reason value. |
| 115 | +
|
| 116 | + Pass the test suite if the coroutine yields True. |
| 117 | + Fail the test suite if the iterator stops or raises a RuntimeError. |
| 118 | + """ |
| 119 | + try: |
| 120 | + if self.test_steps_sequence.send(value): |
| 121 | + self.notify_complete(True) |
| 122 | + except (StopIteration, RuntimeError) as exc: |
| 123 | + self.log('TEST FAILED: {}'.format(exc)) |
| 124 | + self.notify_complete(False) |
| 125 | + |
| 126 | + def test_steps(self): |
| 127 | + """Generate a sequence of test steps. |
| 128 | +
|
| 129 | + This coroutine calls yield to wait for the input from the device |
| 130 | + (the reset_reason). If the device gives the wrong response, the |
| 131 | + generator raises a RuntimeError exception and fails the test. |
| 132 | + """ |
| 133 | + # Ignore the first reason. |
| 134 | + __ignored_reset_reason = yield |
| 135 | + self.raw_reset_reasons.clear() |
| 136 | + self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR) |
| 137 | + __ignored_clear_ack = yield |
| 138 | + |
| 139 | + # Request a NVIC_SystemReset() call. |
| 140 | + expected_reason = 'SOFTWARE' |
| 141 | + if expected_reason not in self.device_reasons: |
| 142 | + self.log('Skipping the {} reset reason -- not supported.'.format(expected_reason)) |
| 143 | + else: |
| 144 | + # Request a NVIC_SystemReset() call. |
| 145 | + self.send_kv(MSG_KEY_DEVICE_RESET, MSG_VALUE_DEVICE_RESET_NVIC) |
| 146 | + __ignored_reset_ack = yield |
| 147 | + time.sleep(self.sync_delay) |
| 148 | + self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY) |
| 149 | + reset_reason = yield |
| 150 | + raise_if_different(RESET_REASONS[expected_reason], reset_reason, 'Wrong reset reason. ') |
| 151 | + self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR) |
| 152 | + __ignored_clear_ack = yield |
| 153 | + |
| 154 | + # Reset the device using DAP. |
| 155 | + expected_reason = 'PIN_RESET' |
| 156 | + if expected_reason not in self.device_reasons: |
| 157 | + self.log('Skipping the {} reset reason -- not supported.'.format(expected_reason)) |
| 158 | + else: |
| 159 | + self.reset() |
| 160 | + __ignored_reset_ack = yield # 'reset_complete' |
| 161 | + time.sleep(self.sync_delay) |
| 162 | + self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY) |
| 163 | + reset_reason = yield |
| 164 | + raise_if_different(RESET_REASONS[expected_reason], reset_reason, 'Wrong reset reason. ') |
| 165 | + self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR) |
| 166 | + __ignored_clear_ack = yield |
| 167 | + |
| 168 | + # Start a watchdog timer and wait for it to reset the device. |
| 169 | + expected_reason = 'WATCHDOG' |
| 170 | + if expected_reason not in self.device_reasons or not self.device_has_watchdog: |
| 171 | + self.log('Skipping the {} reset reason -- not supported.'.format(expected_reason)) |
| 172 | + else: |
| 173 | + self.send_kv(MSG_KEY_DEVICE_RESET, MSG_VALUE_DEVICE_RESET_WATCHDOG) |
| 174 | + __ignored_reset_ack = yield |
| 175 | + time.sleep(self.sync_delay) |
| 176 | + self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY) |
| 177 | + reset_reason = yield |
| 178 | + raise_if_different(RESET_REASONS[expected_reason], reset_reason, 'Wrong reset reason. ') |
| 179 | + |
| 180 | + # The sequence is correct -- test passed. |
| 181 | + yield True |
0 commit comments