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

scripts: sdfw: add scripts for communicating with SDFW #15235

Closed
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
82 changes: 82 additions & 0 deletions scripts/sdfw/conn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

"""
Module for communicating with the SDFW using the ADAC protocol over the CTRLAP interface
"""

from __future__ import annotations

import struct
from typing import List

from sdfw_adac_cmd import AdacResponse, AdacRequest, AdacStatus
from ctrlap import Ctrlap

BYTES_PER_WORD = 4


class Adac:
"""SDFW ADAC connector."""

def __init__(
self,
ctrlap: Ctrlap,
) -> None:
"""
:param ctrlap: Used for performing ctrlap operations
"""
self.ctrlap = ctrlap

def request(self, req: AdacRequest) -> AdacResponse:
"""
Issue an ADAC request and read the response.

:param req: The ADAC request to execute.
:returns: ADAC response.
"""
self.ctrlap.wait_for_ready()
self._write_request(req)

self.ctrlap.wait_for_ready()
rsp = self._read_response()

return rsp

def _write_request(self, req: AdacRequest) -> None:
"""
Write an ADAC request to CTRL-AP

:param req: ADAC request.
"""
for word in _to_word_chunks(req.to_bytes()):
self.ctrlap.write(word)

def _read_response(self) -> AdacResponse:
"""
Read a whole ADAC response over CTRL-AP

:return: ADAC response.
"""
_reserved, status = struct.unpack("<HH", self.ctrlap.read())
(data_count,) = struct.unpack("<I", self.ctrlap.read())

data = bytearray()
for _ in range(data_count // BYTES_PER_WORD):
data.extend(self.ctrlap.read())

status_enum = AdacStatus(status)

return AdacResponse(status=status_enum, data=bytes(data))


def _to_word_chunks(data: bytes) -> List[bytes]:
if len(data) % BYTES_PER_WORD != 0:
raise ValueError(
f"data of length {len(data)} is not aligned to {BYTES_PER_WORD} bytes"
)

return [data[i: i + BYTES_PER_WORD] for i in range(0, len(data), BYTES_PER_WORD)]
139 changes: 139 additions & 0 deletions scripts/sdfw/ctrlap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

"""
Module exposing CTRLAP functionality
"""

from argparse import ArgumentParser
from enum import IntEnum
from time import sleep, time

from jlink_connector import JLinkConnector

ACCESS_PORT_CTRLAP = 4


class ReadyStatus(IntEnum):
"""Enum for READY status CTRLAP register"""

READY = 0
NOT_READY = 1


class CtrlapAddresses(IntEnum):
"""Enum for addresses used in the CTRLAP"""

READY = 0x4
TXDATA = 0x10
RXDATA = 0x18
RXSTATUS = 0x1C
TXSTATUS = 0x14


class MailboxStatus(IntEnum):
"""Enum for mailbox Rx/Tx status CTRLAP register"""

NO_DATA_PENDING = 0
DATA_PENDING = 1


class Ctrlap:
"""Class for performing CTRLAP operations"""

def __init__(self, **kwargs) -> None:
"""
:param kwargs: dict with wait_time and timeout.
Use 'add_arguments(...)' function from this class to add required arguments to
the argument parser.
"""
self.connector = JLinkConnector(auto_connect=False, **kwargs)
self.wait_time = kwargs["wait_time"]
self.timeout = kwargs["timeout"]

def wait_for_ready(self) -> None:
"""Wait until the CTRLAP READY register is ready.

:raises Exception: If waiting times out.
"""

self._block_on_ctrlap_status(CtrlapAddresses.READY, ReadyStatus.NOT_READY)

def read(self) -> bytes:
"""
Receive a word from CTRLAP by reading the RX register. Will block until there is
data pending.

:return: The value of CTRLAP.RX
"""

self._block_on_ctrlap_status(
CtrlapAddresses.RXSTATUS, MailboxStatus.NO_DATA_PENDING
)

word = self.connector.api.read_access_port_register(
ACCESS_PORT_CTRLAP, CtrlapAddresses.RXDATA
)

return word.to_bytes(4, "little")

def write(self, word: bytes) -> None:
"""
Write a word to CTRLAP, block until the data has been read.

:param word: Word to write
"""

val = int.from_bytes(word, byteorder="little")
self.connector.api.write_access_port_register(
ACCESS_PORT_CTRLAP, CtrlapAddresses.TXDATA, val
)
self._block_on_ctrlap_status(
CtrlapAddresses.TXSTATUS, MailboxStatus.DATA_PENDING
)

def _block_on_ctrlap_status(self, address: IntEnum, status: IntEnum) -> None:
"""
Block while waiting for another status

:param address: Address of register to check status of
:param status: Block while the value of the register equals this value.
:raises RuntimeError: if operation times out.
"""

start = time()
while (
self.connector.api.read_access_port_register(ACCESS_PORT_CTRLAP, address)
) == status:
if (time() - start) >= self.timeout:
raise RuntimeError("Timed out when waiting for CTRLAP status")
sleep(self.wait_time)

@classmethod
def add_arguments(cls, parser: ArgumentParser) -> None:
"""
Append command line options needed by this class.
"""

group = parser.add_argument_group(
title="CTRL-AP connection parameters",
description="Use these parameters for communicating with CTRL-AP",
)
group.add_argument(
"--timeout",
type=float,
default=100,
help="Number of seconds to wait for a CTRL-AP register value before timing out",
)

group.add_argument(
"--wait-time",
type=float,
default=0.1,
help="Number of seconds to wait between reading the CTRL-AP registers while waiting for a given status",
)

JLinkConnector.add_arguments(parser)
105 changes: 105 additions & 0 deletions scripts/sdfw/jlink_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

"""
Module exposing a JLink connection
"""

from argparse import ArgumentParser

from pynrfjprog import LowLevel
from pynrfjprog.Parameters import CoProcessor, DeviceFamily


class JLinkConnector:
"""Connect and maintain a JLink connection"""

def __init__(self, auto_connect: bool = True, **kwargs) -> None:
"""
:param auto_connect: automatically connect to JLink if this is set.
Otherwise, the `connect(...)` function must be used.
:param kwargs: dict with serial, hostname, speed and port
Use the `add_arguments(...)` function from this class to add those to
your CLI's argument list.
"""

self.params = kwargs
self.device = DeviceFamily.NRF54H
self.api = LowLevel.API(self.device)
if auto_connect:
self.connect()

def connect(self) -> None:
"""
Connect to the JLink
"""
if not self.api.is_open():
self.api.open()

if self.params["hostname"] and self.params["serial"]:
self.api.connect_to_emu_with_ip(
hostname=self.params["hostname"],
serial_number=self.params["serial"],
port=self.params["port"],
jlink_speed_khz=self.params["speed"],
)
elif self.params["hostname"]:
self.api.connect_to_emu_with_ip(
hostname=self.params["hostname"],
port=self.params["port"],
jlink_speed_khz=self.params["speed"],
)
elif self.params["serial"]:
self.api.connect_to_emu_with_snr(
serial_number=self.params["serial"],
jlink_speed_khz=self.params["speed"],
)
else:
self.api.connect_to_emu_without_snr(jlink_speed_khz=self.params["speed"])

self.api.select_coprocessor(
LowLevel.Parameters.CoProcessor[self.params["coprocessor"].upper()]
)

def disconnect(self) -> None:
"""
Disconnect from JLink and close the API.
"""
self.api.disconnect_from_emu()
self.api.close()

@staticmethod
def add_arguments(parser: ArgumentParser) -> None:
"""
Append command line options needed by this class.
"""

group = parser.add_argument_group(
title="JLink connection parameters",
description="Use these parameters for connecting to JLink",
)
group.add_argument(
"--speed", type=int, default=4000, help="J-Link emulator speed in kHz"
)

group.add_argument(
"--port",
type=int,
default=19020,
help="Port to use when connecting to J-Link emulator host",
)

group.add_argument("--hostname", type=str, help="J-Link emulator hostname IP")

group.add_argument("--serial", type=int, help="J-Link emulator serial number")

group.add_argument(
"--coprocessor",
type=str,
help="Coprocessor (AP) to connect to",
default=CoProcessor.CP_SECURE.name.lower(),
choices=[i.name.lower() for i in CoProcessor],
)
Loading
Loading