Skip to content

Commit

Permalink
Merge pull request #4 from tock/dev/nrf_tests
Browse files Browse the repository at this point in the history
nrf52840dk Intial Tests
  • Loading branch information
lschuermann authored Dec 18, 2024
2 parents 101a3b8 + 8506263 commit 023f426
Show file tree
Hide file tree
Showing 30 changed files with 1,025 additions and 23 deletions.
3 changes: 3 additions & 0 deletions hwci/boards/mock_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def get_serial_port(self):
def erase_board(self):
logging.info("Mock erase of the board")

def reset(self):
logging.info("Mock board reset")

def flash_kernel(self):
logging.info("Mock flashing of the Tock OS kernel")

Expand Down
35 changes: 35 additions & 0 deletions hwci/boards/nrf52dk.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import serial.tools.list_ports
from boards.tockloader_board import TockloaderBoard
from utils.serial_port import SerialPort
from gpio.gpio import GPIO
import yaml
import os


class Nrf52dk(TockloaderBoard):
Expand All @@ -23,6 +26,7 @@ def __init__(self):
self.openocd_board = "nrf52dk"
self.board = "nrf52dk"
self.serial = self.get_serial_port()
self.gpio = self.get_gpio_interface()

def get_uart_port(self):
logging.info("Getting list of serial ports")
Expand All @@ -47,6 +51,20 @@ def get_serial_port(self):
)
return SerialPort(self.uart_port, self.uart_baudrate)

def get_gpio_interface(self):
# Load the target spec from a YAML file
target_spec = load_target_spec()
# Initialize GPIO with the target spec
gpio = GPIO(target_spec)
return gpio

def cleanup(self):
if self.gpio:
for interface in self.gpio.gpio_interfaces.values():
interface.cleanup()
if self.serial:
self.serial.close()

def flash_kernel(self):
logging.info("Flashing the Tock OS kernel")
tock_dir = os.path.join(self.base_dir, "tock")
Expand All @@ -68,6 +86,15 @@ def erase_board(self):
]
subprocess.run(command, check=True)

def reset(self):
logging.info("Performing a target reset via JTAG")
command = [
"openocd",
"-c",
"adapter driver jlink; transport select swd; source [find target/nrf52.cfg]; init; reset; exit",
]
subprocess.run(command, check=True)

# The flash_app method is inherited from TockloaderBoard

@contextmanager
Expand All @@ -82,4 +109,12 @@ def change_directory(self, new_dir):
logging.info(f"Reverted to directory: {os.getcwd()}")


def load_target_spec():
# Assume the target spec file is in a fixed location
target_spec_path = os.path.join(os.getcwd(), "target_spec.yaml")
with open(target_spec_path, "r") as f:
target_spec = yaml.safe_load(f)
return target_spec


board = Nrf52dk()
37 changes: 25 additions & 12 deletions hwci/boards/tockloader_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,38 @@ def __init__(self):
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)

def flash_app(self, app):
logging.info(f"Flashing app: {app}")
def flash_app(self, app_path):
app_name = os.path.basename(app_path)
logging.info(f"Flashing app: {app_name}")
libtock_c_dir = os.path.join(self.base_dir, "libtock-c")
if not os.path.exists(libtock_c_dir):
logging.error(f"libtock-c directory {libtock_c_dir} not found")
raise FileNotFoundError(f"libtock-c directory {libtock_c_dir} not found")

app_dir = os.path.join(libtock_c_dir, "examples", app)
app_dir = os.path.join(libtock_c_dir, "examples", app_path)
if not os.path.exists(app_dir):
logging.error(f"App directory {app_dir} not found")
raise FileNotFoundError(f"App directory {app_dir} not found")

# Build the app using absolute paths
logging.info(f"Building app: {app}")
subprocess.run(["make", f"TOCK_TARGETS={self.arch}"], cwd=app_dir, check=True)
logging.info(f"Building app: {app_name}")
if app_name != "lua-hello":
subprocess.run(
["make", f"TOCK_TARGETS={self.arch}"], cwd=app_dir, check=True
)
else:
# if the app is lua-hello, we need to build the libtock-c submodule first so we need to change directory
# into the libtock-c directory so it knows we are in a git repostiory
self.change_directory(libtock_c_dir)
subprocess.run(
["make", f"TOCK_TARGETS={self.arch}"], cwd=app_dir, check=True
)

tab_file = os.path.join(app_dir, "build", f"{app}.tab")
tab_file = os.path.join(app_dir, "build", f"{app_name}.tab")
if not os.path.exists(tab_file):
logging.error(f"Tab file {tab_file} not found")
raise FileNotFoundError(f"Tab file {tab_file} not found")

logging.info(f"Installing app: {app}")
logging.info(f"Installing app: {app_name}")
subprocess.run(
[
"tockloader",
Expand All @@ -54,16 +64,19 @@ def flash_app(self, app):
)

def get_uart_port(self):
pass
raise NotImplementedError

def get_uart_baudrate(self):
pass
raise NotImplementedError

def erase_board(self):
pass
raise NotImplementedError

def reset(self):
raise NotImplementedError

def flash_kernel(self):
pass
raise NotImplementedError

@contextmanager
def change_directory(self, new_dir):
Expand Down
20 changes: 13 additions & 7 deletions hwci/core/board_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,28 @@ def __init__(self):
self.gpio = None

def get_uart_port(self):
pass
raise NotImplementedError

def get_uart_baudrate(self):
pass
raise NotImplementedError

def get_serial_port(self):
pass
raise NotImplementedError

def get_gpio_interface(self):
pass
raise NotImplementedError

def cleanup(self):
raise NotImplementedError

def erase_board(self):
pass
raise NotImplementedError

def reset(self):
raise NotImplementedError

def flash_kernel(self):
pass
raise NotImplementedError

def flash_app(self, app):
pass
raise NotImplementedError
4 changes: 2 additions & 2 deletions hwci/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def main():

# Set up logging
logging.basicConfig(
level=logging.INFO,
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s",
)

Expand Down Expand Up @@ -48,7 +48,7 @@ def main():
logging.exception("An error occurred during test execution")
sys.exit(1)
finally:
board.serial.close()
board.cleanup()


if __name__ == "__main__":
Expand Down
5 changes: 5 additions & 0 deletions hwci/gpio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.

from .gpio import GPIO
42 changes: 42 additions & 0 deletions hwci/gpio/gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.

import logging

from gpio.interfaces.raspberry_pi5_gpio import RaspberryPi5GPIO
from gpio.interfaces.mock_gpio import MockGPIO


class GPIO:
def __init__(self, target_spec):
self.target_spec = target_spec
self.gpio_interfaces = {}
# Initialize GPIO interfaces based on the target spec
for pin_label, pin_mapping in self.target_spec.get("pin_mappings", {}).items():
interface_name = pin_mapping["io_interface"]
if interface_name not in self.gpio_interfaces:
interface_class = self.load_interface_class(interface_name)
self.gpio_interfaces[interface_name] = interface_class()

def load_interface_class(self, interface_name):
# Map interface names to classes
interface_classes = {
"raspberrypi5gpio": RaspberryPi5GPIO,
"mock_gpio": MockGPIO,
}
if interface_name in interface_classes:
return interface_classes[interface_name]
else:
raise ValueError(f"Unknown GPIO interface: {interface_name}")

def pin(self, pin_label):
# Get the pin mapping from the target spec
pin_mapping = self.target_spec["pin_mappings"].get(pin_label)
if not pin_mapping:
raise ValueError(f"Unknown pin label: {pin_label}")
interface_name = pin_mapping["io_interface"]
interface = self.gpio_interfaces.get(interface_name)
if not interface:
raise ValueError(f"No GPIO interface for {interface_name}")
return interface.pin(pin_label, pin_mapping)
6 changes: 6 additions & 0 deletions hwci/gpio/interfaces/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.

from .raspberry_pi5_gpio import RaspberryPi5GPIO
from .mock_gpio import MockGPIO
40 changes: 40 additions & 0 deletions hwci/gpio/interfaces/mock_gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.

import logging


class MockGPIO:
def __init__(self):
self.pins = {}

def pin(self, target_pin_label, target_pin_mapping):
if target_pin_label not in self.pins:
pin = MockGPIOPin(target_pin_label)
self.pins[target_pin_label] = pin
else:
pin = self.pins[target_pin_label]
return pin

def cleanup(self):
pass # Nothing to clean up in mock


class MockGPIOPin:
def __init__(self, pin_label):
self.pin_label = pin_label
self.mode = None
self.value = None

def set_mode(self, mode):
self.mode = mode
logging.info(f"Pin {self.pin_label} set to mode {mode}")

def read(self):
logging.info(f"Pin {self.pin_label} read value {self.value}")
return self.value

def write(self, value):
self.value = value
logging.info(f"Pin {self.pin_label} write value {value}")
64 changes: 64 additions & 0 deletions hwci/gpio/interfaces/raspberry_pi5_gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.

import logging
from gpiozero import LED, Button, DigitalOutputDevice, DigitalInputDevice


class RaspberryPi5GPIO:
def __init__(self):
self.pins = {}

def pin(self, _target_pin_label, target_pin_mapping):
gpio_pin_number = int(target_pin_mapping["io_pin_spec"])
if gpio_pin_number not in self.pins:
pin = RaspberryPiGPIOPin(gpio_pin_number)
self.pins[gpio_pin_number] = pin
else:
pin = self.pins[gpio_pin_number]
return pin

def cleanup(self):
for pin in self.pins.values():
pin.close()
self.pins.clear()


class RaspberryPiGPIOPin:
def __init__(self, gpio_pin_number):
self.gpio_pin_number = gpio_pin_number
self.device = None # Will be initialized based on mode
self.mode = None

def set_mode(self, mode):
if mode == "input":
if self.device:
self.device.close()
self.device = DigitalInputDevice(self.gpio_pin_number)
self.mode = mode
elif mode == "output":
if self.device:
self.device.close()
self.device = DigitalOutputDevice(self.gpio_pin_number)
self.mode = mode
else:
raise ValueError(f"Unknown mode: {mode}")

def read(self):
if self.mode != "input":
raise RuntimeError("Pin is not set to input mode")
value = self.device.value
logging.debug(f"Read value {value} from pin {self.gpio_pin_number}")
return value

def write(self, value):
if self.mode != "output":
raise RuntimeError("Pin is not set to output mode")
self.device.value = value
logging.debug(f"Wrote value {value} to pin {self.gpio_pin_number}")

def close(self):
if self.device:
self.device.close()
self.device = None
5 changes: 4 additions & 1 deletion hwci/requirements-frozen.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
PyYAML==6.0.2
argcomplete==3.5.1
colorama==0.4.6
crcmod==1.7
gpiozero==2.0.1
lgpio==0.2.2.0
pexpect==4.9.0
prompt-toolkit==3.0.36
ptyprocess==0.7.0
Expand All @@ -10,5 +13,5 @@ questionary==2.0.1
siphash==0.0.1
tockloader==1.13.0
toml==0.10.2
tqdm==4.66.5
tqdm==4.66.6
wcwidth==0.2.13
3 changes: 3 additions & 0 deletions hwci/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
pexpect
pyserial
tockloader
pyyaml
gpiozero
lgpio
1 change: 1 addition & 0 deletions hwci/select_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ def main():

print(f"Selected HWCI tests: {test_files}")


if __name__ == "__main__":
main()
Loading

0 comments on commit 023f426

Please sign in to comment.