Skip to content

Commit ccb63d7

Browse files
authored
Merge pull request #10857 from ARMmbed/feature-watchdog
Add Watchdog and ResetReason
2 parents 149d53c + e12125b commit ccb63d7

File tree

70 files changed

+6278
-56
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+6278
-56
lines changed

TESTS/host_tests/reset_reason.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
from mbed_host_tests.host_tests_runner.host_test_default import DefaultTestSelector
20+
21+
DEFAULT_SYNC_DELAY = 4.0
22+
23+
MSG_VALUE_WATCHDOG_PRESENT = 'wdg_present'
24+
MSG_VALUE_DUMMY = '0'
25+
MSG_VALUE_RESET_REASON_GET = 'get'
26+
MSG_VALUE_RESET_REASON_CLEAR = 'clear'
27+
MSG_VALUE_DEVICE_RESET_NVIC = 'nvic'
28+
MSG_VALUE_DEVICE_RESET_WATCHDOG = 'watchdog'
29+
30+
MSG_KEY_DEVICE_READY = 'ready'
31+
MSG_KEY_RESET_REASON_RAW = 'reason_raw'
32+
MSG_KEY_RESET_REASON = 'reason'
33+
MSG_KEY_DEVICE_RESET = 'reset'
34+
MSG_KEY_SYNC = '__sync'
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_has_watchdog = None
70+
self.raw_reset_reasons = set()
71+
self.sync_delay = DEFAULT_SYNC_DELAY
72+
self.test_steps_sequence = self.test_steps()
73+
# Advance the coroutine to it's first yield statement.
74+
self.test_steps_sequence.send(None)
75+
76+
def setup(self):
77+
sync_delay = self.get_config_item('forced_reset_timeout')
78+
self.sync_delay = sync_delay if sync_delay is not None else DEFAULT_SYNC_DELAY
79+
self.register_callback(MSG_KEY_DEVICE_READY, self.cb_device_ready)
80+
self.register_callback(MSG_KEY_RESET_REASON_RAW, self.cb_reset_reason_raw)
81+
self.register_callback(MSG_KEY_RESET_REASON, self.cb_reset_reason)
82+
self.register_callback(MSG_KEY_DEVICE_RESET, self.cb_reset_reason)
83+
84+
def cb_device_ready(self, key, value, timestamp):
85+
"""Request a raw value of the reset_reason register.
86+
87+
Additionally, save the device's watchdog status on the first call.
88+
"""
89+
if self.device_has_watchdog is None:
90+
self.device_has_watchdog = (value == MSG_VALUE_WATCHDOG_PRESENT)
91+
self.send_kv(MSG_KEY_RESET_REASON_RAW, MSG_VALUE_RESET_REASON_GET)
92+
93+
def cb_reset_reason_raw(self, key, value, timestamp):
94+
"""Verify that the raw reset_reason register value is unique.
95+
96+
Fail the test suite if the raw reset_reason value is not unique.
97+
Request a platform independent reset_reason otherwise.
98+
"""
99+
if value in self.raw_reset_reasons:
100+
self.log('TEST FAILED: The raw reset reason is not unique. '
101+
'{!r} is already present in {!r}.'
102+
.format(value, self.raw_reset_reasons))
103+
self.notify_complete(False)
104+
else:
105+
self.raw_reset_reasons.add(value)
106+
self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_GET)
107+
108+
def cb_reset_reason(self, key, value, timestamp):
109+
"""Feed the test_steps coroutine with reset_reason value.
110+
111+
Pass the test suite if the coroutine yields True.
112+
Fail the test suite if the iterator stops or raises a RuntimeError.
113+
"""
114+
try:
115+
if self.test_steps_sequence.send(value):
116+
self.notify_complete(True)
117+
except (StopIteration, RuntimeError) as exc:
118+
self.log('TEST FAILED: {}'.format(exc))
119+
self.notify_complete(False)
120+
121+
def test_steps(self):
122+
"""Generate a sequence of test steps.
123+
124+
This coroutine calls yield to wait for the input from the device
125+
(the reset_reason). If the device gives the wrong response, the
126+
generator raises a RuntimeError exception and fails the test.
127+
"""
128+
# Ignore the first reason.
129+
__ignored_reset_reason = yield
130+
self.raw_reset_reasons.clear()
131+
self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR)
132+
__ignored_clear_ack = yield
133+
134+
# Request a NVIC_SystemReset() call.
135+
self.send_kv(MSG_KEY_DEVICE_RESET, MSG_VALUE_DEVICE_RESET_NVIC)
136+
__ignored_reset_ack = yield
137+
time.sleep(self.sync_delay)
138+
self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY)
139+
reset_reason = yield
140+
raise_if_different(RESET_REASONS['SOFTWARE'], reset_reason, 'Wrong reset reason. ')
141+
self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR)
142+
__ignored_clear_ack = yield
143+
144+
# Reset the device using DAP.
145+
self.reset_dut(DefaultTestSelector.RESET_TYPE_SW_RST)
146+
reset_reason = yield
147+
raise_if_different(RESET_REASONS['PIN_RESET'], reset_reason, 'Wrong reset reason. ')
148+
self.send_kv(MSG_KEY_RESET_REASON, MSG_VALUE_RESET_REASON_CLEAR)
149+
__ignored_clear_ack = yield
150+
151+
# Start a watchdog timer and wait for it to reset the device.
152+
if not self.device_has_watchdog:
153+
self.log('DUT does not have a watchdog. Skipping this reset reason.')
154+
else:
155+
self.send_kv(MSG_KEY_DEVICE_RESET, MSG_VALUE_DEVICE_RESET_WATCHDOG)
156+
__ignored_reset_ack = yield
157+
time.sleep(self.sync_delay)
158+
self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY)
159+
reset_reason = yield
160+
raise_if_different(RESET_REASONS['WATCHDOG'], reset_reason, 'Wrong reset reason. ')
161+
162+
# The sequence is correct -- test passed.
163+
yield True

TESTS/host_tests/sync_on_reset.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Copyright (c) 2018-2019 Arm Limited and affiliates.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
"""
18+
import time
19+
from mbed_host_tests import BaseHostTest
20+
21+
DEFAULT_SYNC_DELAY = 4.0
22+
23+
MSG_VALUE_DUMMY = '0'
24+
MSG_KEY_DEVICE_READY = 'ready'
25+
MSG_KEY_START_CASE = 'start_case'
26+
MSG_KEY_DEVICE_RESET = 'reset_on_case_teardown'
27+
MSG_KEY_SYNC = '__sync'
28+
29+
30+
class SyncOnReset(BaseHostTest):
31+
"""Host side test that handles device reset during case teardown.
32+
33+
Given a device that performs a reset during a test case teardown.
34+
When the device notifies the host about the reset.
35+
Then the host:
36+
* keeps track of the test case index of the current test suite,
37+
* performs a dev-host handshake,
38+
* advances the test suite to next test case.
39+
40+
Note:
41+
Developed for a watchdog test, so that it can be run on devices that
42+
do not support watchdog timeout updates after the initial setup.
43+
As a solution, after testing watchdog with one set of settings, the
44+
device performs a reset and notifies the host with the test case number,
45+
so that the test suite may be advanced once the device boots again.
46+
"""
47+
48+
def __init__(self):
49+
super(SyncOnReset, self).__init__()
50+
self.test_case_num = 0
51+
self.sync_delay = DEFAULT_SYNC_DELAY
52+
53+
def setup(self):
54+
sync_delay = self.get_config_item('forced_reset_timeout')
55+
self.sync_delay = sync_delay if sync_delay is not None else DEFAULT_SYNC_DELAY
56+
self.register_callback(MSG_KEY_DEVICE_READY, self.cb_device_ready)
57+
self.register_callback(MSG_KEY_DEVICE_RESET, self.cb_device_reset)
58+
59+
def cb_device_ready(self, key, value, timestamp):
60+
"""Advance the device test suite to the next test case."""
61+
self.send_kv(MSG_KEY_START_CASE, self.test_case_num)
62+
63+
def cb_device_reset(self, key, value, timestamp):
64+
"""Wait for the device to boot and perform a handshake.
65+
66+
Additionally, keep track of the last test case number.
67+
"""
68+
try:
69+
self.test_case_num = int(value)
70+
except ValueError:
71+
pass
72+
self.test_case_num += 1
73+
time.sleep(self.sync_delay)
74+
self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY)

TESTS/host_tests/watchdog_reset.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 collections
18+
import threading
19+
from mbed_host_tests import BaseHostTest
20+
21+
TestCaseData = collections.namedtuple('TestCaseData', ['index', 'data_to_send'])
22+
23+
DEFAULT_SYNC_DELAY = 4.0
24+
MAX_HB_PERIOD = 2.5 # [s] Max expected heartbeat period.
25+
26+
MSG_VALUE_DUMMY = '0'
27+
CASE_DATA_INVALID = 0xffffffff
28+
CASE_DATA_PHASE2_OK = 0xfffffffe
29+
CASE_DATA_INSUFF_HB = 0x0
30+
31+
MSG_KEY_SYNC = '__sync'
32+
MSG_KEY_DEVICE_READY = 'ready'
33+
MSG_KEY_START_CASE = 'start_case'
34+
MSG_KEY_DEVICE_RESET = 'dev_reset'
35+
MSG_KEY_HEARTBEAT = 'hb'
36+
37+
38+
class WatchdogReset(BaseHostTest):
39+
"""Host side test that handles device reset.
40+
41+
Given a device with a watchdog timer started.
42+
When the device notifies the host about an incoming reset.
43+
Then the host:
44+
* keeps track of the test case index of the current test suite,
45+
* performs a dev-host handshake.
46+
"""
47+
48+
def __init__(self):
49+
super(WatchdogReset, self).__init__()
50+
self.current_case = TestCaseData(0, CASE_DATA_INVALID)
51+
self.__handshake_timer = None
52+
self.sync_delay = DEFAULT_SYNC_DELAY
53+
self.drop_heartbeat_messages = True
54+
self.hb_timestamps_us = []
55+
56+
def handshake_timer_start(self, seconds=1.0, pre_sync_fun=None):
57+
"""Start a new handshake timer."""
58+
59+
def timer_handler():
60+
"""Perform a dev-host handshake by sending a sync message."""
61+
if pre_sync_fun is not None:
62+
pre_sync_fun()
63+
self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY)
64+
65+
self.__handshake_timer = threading.Timer(seconds, timer_handler)
66+
self.__handshake_timer.start()
67+
68+
def handshake_timer_cancel(self):
69+
"""Cancel the current handshake timer."""
70+
try:
71+
self.__handshake_timer.cancel()
72+
except AttributeError:
73+
pass
74+
finally:
75+
self.__handshake_timer = None
76+
77+
def heartbeat_timeout_handler(self):
78+
"""Handler for the heartbeat timeout.
79+
80+
Compute the time span of the last heartbeat sequence.
81+
Set self.current_case.data_to_send to CASE_DATA_INVALID if no heartbeat was received.
82+
Set self.current_case.data_to_send to CASE_DATA_INSUFF_HB if only one heartbeat was
83+
received.
84+
"""
85+
self.drop_heartbeat_messages = True
86+
dev_data = CASE_DATA_INVALID
87+
if len(self.hb_timestamps_us) == 1:
88+
dev_data = CASE_DATA_INSUFF_HB
89+
self.log('Not enough heartbeats received.')
90+
elif len(self.hb_timestamps_us) >= 2:
91+
dev_data = int(round(0.001 * (self.hb_timestamps_us[-1] - self.hb_timestamps_us[0])))
92+
self.log('Heartbeat time span was {} ms.'.format(dev_data))
93+
self.current_case = TestCaseData(self.current_case.index, dev_data)
94+
95+
def setup(self):
96+
sync_delay = self.get_config_item('forced_reset_timeout')
97+
self.sync_delay = sync_delay if sync_delay is not None else DEFAULT_SYNC_DELAY
98+
self.register_callback(MSG_KEY_DEVICE_READY, self.cb_device_ready)
99+
self.register_callback(MSG_KEY_DEVICE_RESET, self.cb_device_reset)
100+
self.register_callback(MSG_KEY_HEARTBEAT, self.cb_heartbeat)
101+
102+
def teardown(self):
103+
self.handshake_timer_cancel()
104+
105+
def cb_device_ready(self, key, value, timestamp):
106+
"""Advance the device test suite to a proper test case.
107+
108+
Additionally, send test case data to the device.
109+
"""
110+
self.handshake_timer_cancel()
111+
msg_value = '{0.index:02x},{0.data_to_send:08x}'.format(self.current_case)
112+
self.send_kv(MSG_KEY_START_CASE, msg_value)
113+
self.drop_heartbeat_messages = False
114+
self.hb_timestamps_us = []
115+
116+
def cb_device_reset(self, key, value, timestamp):
117+
"""Keep track of the test case number.
118+
119+
Also set a new handshake timeout, so when the device gets
120+
restarted by the watchdog, the communication will be restored
121+
by the __handshake_timer.
122+
"""
123+
self.handshake_timer_cancel()
124+
case_num, dev_reset_delay_ms = (int(i, base=16) for i in value.split(','))
125+
self.current_case = TestCaseData(case_num, CASE_DATA_PHASE2_OK)
126+
self.handshake_timer_start(self.sync_delay + dev_reset_delay_ms / 1000.0)
127+
128+
def cb_heartbeat(self, key, value, timestamp):
129+
"""Save the timestamp of a heartbeat message.
130+
131+
Additionally, keep track of the test case number.
132+
133+
Also each heartbeat sets a new timeout, so when the device gets
134+
restarted by the watchdog, the communication will be restored
135+
by the __handshake_timer.
136+
"""
137+
if self.drop_heartbeat_messages:
138+
return
139+
self.handshake_timer_cancel()
140+
case_num, timestamp_us = (int(i, base=16) for i in value.split(','))
141+
self.current_case = TestCaseData(case_num, CASE_DATA_INVALID)
142+
self.hb_timestamps_us.append(timestamp_us)
143+
self.handshake_timer_start(
144+
seconds=(MAX_HB_PERIOD + self.sync_delay),
145+
pre_sync_fun=self.heartbeat_timeout_handler)

0 commit comments

Comments
 (0)