Skip to content
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
9 changes: 9 additions & 0 deletions Documentation/config-yaml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,10 @@ Flash command can use special tags that are handled by NTFC:
- ``$IMAGE_BIN`` is replaced by path to ``nuttx.bin``.
- ``$IMAGE_HEX`` is replaced by path to ``nuttx.hex``.
- ``$IMAGE_ELF`` is replaced by path to the core image (``elf_path``).
- ``$APPS_BINDIR`` is replaced by the application binaries directory
(kernel-mode builds).
- ``$APPS_IMG`` is replaced by the generated application filesystem
image (requires ``apps_image``).

Example usage with ``st-flash`` tool:

Expand Down Expand Up @@ -466,6 +470,11 @@ These fields are parsed by :class:`ntfc.coreconfig.CoreConfig`.
- (Optional) Directory with kernel-mode application binaries. Defaults
to the ``bin/`` directory next to the NuttX ELF for kernel-mode
builds (``CONFIG_BUILD_KERNEL=y``)
* - ``apps_image``
- (Optional) Generate a filesystem image with the application
binaries after build, e.g. ``apps_image: {type: romfs}``. The
image path is available as ``$APPS_IMG`` in the ``flash``
command. Requires ``genromfs`` and a kernel-mode build
* - ``defconfig``
- Path to NuttX defconfig (auto-build)
* - ``elf_path``
Expand Down
3 changes: 3 additions & 0 deletions Documentation/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ product: # many products can be supported in tests (pro
app_bindir: '' # (optional) directory with kernel-mode application binaries.
# Defaults to the bin/ directory next to the NuttX ELF
# for kernel-mode builds.
apps_image: # (optional) generate a filesystem image with the application
type: romfs # binaries after build (kernel-mode only, requires genromfs).
# The image path is available as $APPS_IMG in 'flash'.

# NTFC can use pre-build image or build it from defconfig
# the behavior will depend on the parameters specified in config.
Expand Down
6 changes: 5 additions & 1 deletion Documentation/writing-test-cases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ Execute NSH command and verify output:

Decorators:

- ``@pytest.mark.cmd_check("symbol_name")``: Verify ELF symbol exists
- ``@pytest.mark.cmd_check("symbol_name")``: Verify ELF symbol exists.
On kernel-mode targets (``CONFIG_BUILD_KERNEL=y``) the marker is first
matched against application file names (a trailing ``_main`` maps to
the file name, so ``hello_main`` matches the ``hello`` binary) and
then against symbols in the unstripped application binaries
- ``@pytest.mark.dep_config("CONFIG_X", "CONFIG_Y")``: Skip if configs not
enabled

Expand Down
32 changes: 32 additions & 0 deletions config/nuttx-qemu-armv7a-knsh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# NuttX kernel-mode (CONFIG_BUILD_KERNEL=y) target for QEMU armv7a.
#
# The CMake build installs application binaries to <build>/bin and the
# builder sets exec_cwd=<build> for kernel-mode cores, so the hostfs
# mount data 'fs=.' maps /system to the build directory and
# /system/bin/init resolves to <build>/bin/init. Semihosting is
# required for the hostfs mount.

config:
cwd: './external'
build_dir: './build'

product:

name: "ntfc-armv7a-knsh"
cores:
core0:
name: 'main'
device: 'qemu'
exec_path: 'qemu-system-arm'
exec_args: '-semihosting -cpu cortex-a7 -nographic
-machine virt,highmem=off,virtualization=off,gic-version=2
-chardev stdio,id=con,mux=on -serial chardev:con
-mon chardev=con,mode=readline'
defconfig: 'boards/arm/qemu/qemu-armv7a/configs/knsh'
boot_timeout: 15
kv:
CONFIG_INIT_MOUNT_DATA: "fs=."
# required by the arch/os ostest test case
CONFIG_SYSTEM_SETLOGMASK: "y"
# required by the arch/os heap stability test case
CONFIG_TESTING_HEAP: "y"
32 changes: 32 additions & 0 deletions config/nuttx-qemu-armv8a-knsh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# NuttX kernel-mode (CONFIG_BUILD_KERNEL=y) target for QEMU armv8a.
#
# The CMake build installs application binaries to <build>/bin and the
# builder sets exec_cwd=<build> for kernel-mode cores, so the hostfs
# mount data 'fs=.' maps /system to the build directory and
# /system/bin/init resolves to <build>/bin/init. Semihosting is
# required for the hostfs mount.

config:
cwd: './external'
build_dir: './build'

product:

name: "ntfc-armv8a-knsh"
cores:
core0:
name: 'main'
device: 'qemu'
exec_path: 'qemu-system-aarch64'
exec_args: '-semihosting -cpu cortex-a53 -nographic
-machine virt,virtualization=on,gic-version=3
-net none -chardev stdio,id=con,mux=on
-serial chardev:con -mon chardev=con,mode=readline'
defconfig: 'boards/arm64/qemu/qemu-armv8a/configs/knsh'
boot_timeout: 15
kv:
CONFIG_INIT_MOUNT_DATA: "fs=."
# required by the arch/os ostest test case
CONFIG_SYSTEM_SETLOGMASK: "y"
# required by the arch/os heap stability test case
CONFIG_TESTING_HEAP: "y"
39 changes: 39 additions & 0 deletions config/nuttx-qemu-intel64-knsh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# NuttX kernel-mode (CONFIG_BUILD_KERNEL=y) target for QEMU intel64.
#
# The knsh_romfs configuration loads user-space applications from a
# ROMFS image linked into the kernel; there is no hostfs, so exec_cwd
# is not relevant for the mount. Application command discovery uses the
# <build>/bin directory registered by the builder.
#
# Requires NuttX with CMake kernel-build support for x86_64 (branch
# fix-x86_64-cmake-kernel-build in the vendored checkout: arch_interface
# guard, CMAKE_LD, relocatable binary install, board ROMFS generation).

config:
cwd: './external'
build_dir: './build'

product:

name: "ntfc-intel64-knsh"
cores:
core0:
name: 'main'
device: 'qemu'
exec_path: 'qemu-system-x86_64'
exec_args: '-m 2G -cpu host -enable-kvm -nographic -serial mon:stdio'
defconfig: 'boards/x86_64/qemu/qemu-intel64/configs/knsh_romfs'
boot_timeout: 15
kv:
# resolve bare command names against the ROMFS mount point
CONFIG_LIBC_ENVPATH: "y"
CONFIG_PATH_INITIAL: "/system/bin"
# required by the arch/os ostest test case
CONFIG_SYSTEM_SETLOGMASK: "y"
# required by the arch/os heap stability test case
CONFIG_TESTING_HEAP: "y"
# setlocale()/nl_langinfo(), referenced by the LTP strftime cases
CONFIG_LIBC_LOCALE: "y"
# without it SIGKILL is absent from the default action table and
# can be ignored, which the LTP sigignore cases check for
CONFIG_SIG_SIGKILL_ACTION: "y"
31 changes: 31 additions & 0 deletions config/nuttx-qemu-riscv-rv-virt-knsh64.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# NuttX kernel-mode (CONFIG_BUILD_KERNEL=y) reference target.
#
# The knsh64 configuration is an S-mode build: QEMU boots its bundled
# OpenSBI firmware (no '-bios none') and NTFC appends '-kernel <elf>'.
#
# The CMake build installs application binaries to <build>/bin and the
# builder sets exec_cwd=<build> for kernel-mode cores, so the hostfs
# mount data 'fs=.' maps /system to the build directory and
# /system/bin/init resolves to <build>/bin/init.

config:
cwd: './external'
build_dir: './build'

product:

name: "ntfc-rv-virt-knsh64"
cores:
core0:
name: 'main'
device: 'qemu'
exec_path: 'qemu-system-riscv64'
exec_args: '-semihosting -M virt,aclint=on -cpu rv64 -smp 1 -nographic'
defconfig: 'boards/risc-v/qemu-rv/rv-virt/configs/knsh64'
boot_timeout: 15
kv:
CONFIG_INIT_MOUNT_DATA: "fs=."
# required by the arch/os ostest test case
CONFIG_SYSTEM_SETLOGMASK: "y"
# required by the arch/os heap stability test case
CONFIG_TESTING_HEAP: "y"
33 changes: 33 additions & 0 deletions config/nuttx-serial-knsh.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Template for a kernel-mode (CONFIG_BUILD_KERNEL=y) hardware target
# on a serial console. Copy, rename and fill in the board specifics.
#
# Kernel-mode hardware notes:
# - There is no hostfs on hardware: application binaries must reach the
# target filesystem. Either the board defconfig bakes a ROMFS into the
# kernel image, or 'apps_image' generates one from <build>/bin and the
# 'flash' command writes it with $APPS_IMG.
# - When NTFC builds the image, command discovery uses <build>/bin. For
# prebuilt images (no 'defconfig'), commands are discovered once from
# the running target by listing CONFIG_PATH_INITIAL.
# - Kernel boot (mount filesystem, load init ELF) plus a bootloader can
# exceed the 5 second default boot wait; raise 'boot_timeout'.

config:
cwd: './external'
build_dir: './build'

product:

name: "ntfc-<board>-knsh"
cores:
core0:
name: 'main'
device: 'serial'
exec_path: '/dev/ttyUSB0'
exec_args: '115200,n,8,1'
defconfig: 'boards/<arch>/<chip>/<board>/configs/knsh'
boot_timeout: 30
apps_image:
type: romfs
flash: '<flash-tool> write $IMAGE_BIN <kernel-address> $APPS_IMG <apps-address>'
reboot: '<reset-tool command>'
106 changes: 92 additions & 14 deletions src/ntfc/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import re
import shutil
import subprocess
from functools import partial
from pathlib import Path
from typing import Any, Dict, List, Optional

Expand All @@ -40,6 +41,9 @@ class NuttXBuilder:
IMAGE_BIN_STR = "$IMAGE_BIN"
IMAGE_HEX_STR = "$IMAGE_HEX"
IMAGE_ELF_STR = "$IMAGE_ELF"
APPS_BINDIR_STR = "$APPS_BINDIR"
APPS_IMG_STR = "$APPS_IMG"
APPS_IMG_NAME = "apps.romfs.img"
_KCONFIG_DISABLED_RE = re.compile(
r"^#\s+(CONFIG_[A-Za-z0-9_]+)\s+is not set"
)
Expand Down Expand Up @@ -348,7 +352,6 @@ def _run_build(
"--build",
str(build_path),
]

run_env = os.environ.copy()
if env:
run_env.update(env) # pragma: no cover
Expand Down Expand Up @@ -428,36 +431,117 @@ def _build_core(
if not already_build or self._rebuild: # pragma: no cover
self._log_kconfig_overrides(kv_overrides)

# configure build
self._run_cmake(
configure = partial(
self._run_cmake,
source=nuttx_dir,
build=build_path,
generator="Ninja",
defines=defines,
env=build_env,
)

# configure build
configure()

# apply Kconfig overrides to generated .config before build
self._apply_kconfig_overrides(
nuttx_conf_path, kv_overrides, cfg_cwd
)

# Regenerate include/nuttx/config.h after changing .config.
# Otherwise CMake can relink an image built with stale Kconfig
# values while the saved .config claims the override applied.

if kv_overrides:
# fill in defaults of suboptions the overrides
# unlocked (e.g. *_PROGNAME): applications are
# silently dropped at configure when these are
# missing from .config
self._run_build_target(
build_path, "olddefconfig", env=build_env
)

# application targets are registered at configure
# time from .config: configure again so overrides
# that enable new applications take effect
configure()

# build
self._run_build(build_path, env=build_env)

# add elf and conf path
cores[core]["elf_path"] = nuttx_elf_path
cores[core]["conf_path"] = nuttx_conf_path

if self._is_kernel_config(nuttx_conf_path):
# kernel-mode: applications are installed to <build>/bin
# and hostfs mounts resolve relative to the process cwd
cores[core].setdefault(
"app_bindir", os.path.join(build_path, "bin")
)
cores[core].setdefault("exec_cwd", build_path)

self._make_apps_image(cores[core], build_path)

def _make_apps_image(
self, core_cfg: Dict[str, Any], build_path: str
) -> None:
"""Generate a filesystem image with application binaries.

Enabled with the per-core ``apps_image`` option; the image path
is registered as ``apps_img`` for the ``$APPS_IMG`` flash
placeholder.
"""
img_cfg = core_cfg.get("apps_image", None)
if not img_cfg:
return

if not isinstance(img_cfg, dict):
raise BuilderConfigError("apps_image must be a mapping")

img_type = img_cfg.get("type", "romfs")
if img_type != "romfs":
raise BuilderConfigError(
f"unsupported apps_image type: {img_type}"
)

bindir = core_cfg.get("app_bindir", None)
if not bindir:
raise BuilderConfigError(
"apps_image requires app_bindir (kernel-mode build)"
)

tool = shutil.which("genromfs")
if not tool:
raise BuilderConfigError("genromfs not found in PATH")

img_path = os.path.join(build_path, self.APPS_IMG_NAME)
self._run_command([tool, "-f", img_path, "-d", bindir], env=None)
core_cfg["apps_img"] = img_path

def _expand_flash_cmd(
self, flash_cmd: str, core_cfg: Dict[str, Any]
) -> str:
"""Expand image placeholders in a flash command."""
parent = Path(core_cfg["elf_path"]).parent
values = {
self.IMAGE_BIN_STR: str(parent / "nuttx.bin"),
self.IMAGE_HEX_STR: str(parent / "nuttx.hex"),
self.IMAGE_ELF_STR: core_cfg["elf_path"],
self.APPS_BINDIR_STR: core_cfg.get("app_bindir", ""),
self.APPS_IMG_STR: core_cfg.get("apps_img", ""),
}

for placeholder, value in values.items():
flash_cmd = flash_cmd.replace(placeholder, value)

return flash_cmd

@staticmethod
def _is_kernel_config(conf_path: str) -> bool:
"""Check if a generated .config selects a kernel build."""
if not os.path.isfile(conf_path):
return False

with open(conf_path, "r", encoding="utf-8") as f:
return any(line.strip() == "CONFIG_BUILD_KERNEL=y" for line in f)

def _reboot_core(
self, core: str, cores: Dict[str, Any]
) -> None: # pragma: no cover
Expand All @@ -474,13 +558,7 @@ def _flash_core(
"""Flash single core image."""
flash_cmd = cores[core].get("flash", None)
if flash_cmd:
img_path = Path(cores[core]["elf_path"])
image_hex = str(img_path.parent) + "/nuttx.hex"
image_bin = str(img_path.parent) + "/nuttx.bin"

flash_cmd = flash_cmd.replace(self.IMAGE_BIN_STR, image_bin)
flash_cmd = flash_cmd.replace(self.IMAGE_HEX_STR, image_hex)
flash_cmd = flash_cmd.replace(self.IMAGE_ELF_STR, str(img_path))
flash_cmd = self._expand_flash_cmd(flash_cmd, cores[core])

cmd = flash_cmd.split()

Expand Down
Loading