Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add MPU stack growth test #9

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions hwci/boards/nrf52dk.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ def cleanup(self):
self.serial.close()

def flash_kernel(self):
if self.args.no_flash_kernel:
logging.warning(f"Skipping flashing the Tock kernel, resetting the board instead.")
self.reset()
return

logging.info("Flashing the Tock OS kernel")
if not os.path.exists(self.kernel_path):
logging.error(f"Tock directory {self.kernel_path} not found")
Expand All @@ -78,6 +83,11 @@ def flash_kernel(self):
)

def erase_board(self):
if self.args.no_erase:
logging.warning(f"Skipping board erase, performing reset instead.")
self.reset()
return

logging.info("Erasing the board")
command = [
"openocd",
Expand Down
9 changes: 9 additions & 0 deletions hwci/boards/tockloader_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ def __init__(self):
self.board = None # Should be set in subclass
self.arch = None # Should be set in subclass
self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.args = None

def set_args(self, args):
self.args = args

def flash_app(self, app_path):
if self.args.no_flash_apps:
logging.warning(f"Skipping flashing app {app_path}, resetting the board instead.")
self.reset()
return

app_name = os.path.basename(app_path)
logging.info(f"Flashing app: {app_name}")
libtock_c_dir = os.path.join(self.base_dir, "repos", "libtock-c")
Expand Down
4 changes: 4 additions & 0 deletions hwci/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def main():
parser = argparse.ArgumentParser(description="Run tests on Tock OS")
parser.add_argument("--board", required=True, help="Path to the board module")
parser.add_argument("--test", required=True, help="Path to the test module")
parser.add_argument("--no-erase", action="store_true", help="Do not erase the board before the test")
parser.add_argument("--no-flash-kernel", action="store_true", help="Disable flashing the Tock kernel, instead perform a reset")
parser.add_argument("--no-flash-apps", action="store_true", help="Disable flashing applications, instead perform a reset")
args = parser.parse_args()

# Set up logging
Expand All @@ -30,6 +33,7 @@ def main():
board_spec.loader.exec_module(board_module)
if hasattr(board_module, "board"):
board = board_module.board
board.set_args(args)
else:
logging.error("No board class found in the specified board module")
sys.exit(1)
Expand Down
84 changes: 84 additions & 0 deletions hwci/tests/mpu_stack_growth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT

import logging
import time
import re
from utils.test_helpers import OneshotTest

class MPUStackGrowthTest(OneshotTest):
def __init__(self):
super().__init__(apps=["tests/mpu/mpu_stack_growth"])

def oneshot_test(self, board):
serial = board.serial

# Expect the test start message:
serial.expect("[TEST] MPU Stack Growth")

# Extract the various memory addresses:
mem_start_pattern = r"mem_start: *0x([0-9a-f]+)"
mem_start_out = serial.expect(mem_start_pattern)

app_heap_break_pattern = r"app_heap_break: *0x([0-9a-f]+)"
app_heap_break_out = serial.expect(app_heap_break_pattern)

kernel_memory_break_pattern = r"kernel_memory_break: *0x([0-9a-f]+)"
kernel_memory_break_out = serial.expect(kernel_memory_break_pattern)

mem_end_pattern = r"mem_end: *0x([0-9a-f]+)"
mem_end_out = serial.expect(mem_end_pattern)

stack_pointer_ish_pattern = r"stack pointer \(ish\): *0x([0-9a-f]+)"
stack_pointer_ish_out = serial.expect(stack_pointer_ish_pattern)

addresses = {
"mem_start": int(re.match(mem_start_pattern, mem_start_out.decode("utf-8")).group(1), 16),
"app_heap_break": int(re.match(app_heap_break_pattern, app_heap_break_out.decode("utf-8")).group(1), 16),
"kernel_memory_break": int(re.match(kernel_memory_break_pattern, kernel_memory_break_out.decode("utf-8")).group(1), 16),
"mem_end": int(re.match(mem_end_pattern, mem_end_out.decode("utf-8")).group(1), 16),
"stack_pointer_ish": int(re.match(stack_pointer_ish_pattern, stack_pointer_ish_out.decode("utf-8")).group(1), 16),
}
max_label_len = max(map(lambda label: len(label), addresses.keys()))
for label, addr in addresses.items():
logging.info(f"Got address: {label.rjust(max_label_len)} = 0x{addr:08x}")

# Extract the fault reason and faulting address:
assert serial.expect(r"Data Access Violation:[ \t]+true") is not None
assert serial.expect(r"Memory Management Stacking Fault:[ \t]+true") is not None
assert serial.expect(r"Forced Hard Fault:[ \t]+true") is not None

fault_address_pattern = r"Faulting Memory Address:[ \t]+0x([0-9A-F]+)"
fault_address_out = serial.expect(fault_address_pattern)
fault_address = int(re.match(fault_address_pattern, fault_address_out.decode("utf-8")).group(1), 16)

# Ensure that the panic message shows that the stack has been exceeded,
# and that the previously reported stack pointer is within the bounds
# in the table drawn on the serial console.
#
# serial.expect matches on ASCII characters, which doesn't support the
# Unicode Tock panic messages:
fault_table_output = serial.expect(r"mpu_stack_growth.*\[Faulted\].*R0 : 0x")
assert fault_table_output is not None
# print("Fault table output: ", fault_table_output)

# # Once we have captured, the table, decode the output as UTF-8 and
# # match the exact expression to extract the stack boundaries / size:
# fault_table_pattern = b"mpu_stack_growth.*A" #\r\n[ \t]+0x([0-9A-F]+) ┼─+ M\r\n[ \t]+| ▼ Stack[ \t]+([0-9]+) |[ \t]+([0-9]+)[ \t]+EXCEEDED!\r\n[ \t]+0x([0-9A-F]+) ┼─+.*"
# fault_table = re.match(re.compile(fault_table_pattern), fault_table_output)
# print(fault_table)

# stack_top = int(serial.child.match.group(1).decode("utf-8"), 16)
# stack_size = int(serial.child.match.group(2).decode("utf-8"), 16)
# stack_size_max = int(serial.child.match.group(3).decode("utf-8"), 16)
# stack_bottom_overrun = int(serial.child.match.group(4).decode("utf-8"), 16)

# assert stack_top > stack_bottom
# assert stack_size > stack_size_max
# assert stack_top - stack_size_max >= stack_bottom_overrun
# assert stack_top > stack_pointer_ish > stack_bottom_overrun
# # we assume that the kernel's view on the stack pointer is roughly in
# # the region of where the fault occurred
# assert stack_top > fault_address > (stack_bottom_overrun + 64)

test = MPUStackGrowthTest()