diff --git a/.bazelrc b/.bazelrc index 247b2162..83cbdc4f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -57,6 +57,7 @@ build:time-arm64-linux --extra_toolchains=@score_gcc_aarch64_toolchain//:aarch64 build:time-x86_64-qnx --config=time_shared build:time-x86_64-qnx --platforms=@score_bazel_platforms//:x86_64-qnx-sdp_8.0.0-posix build:time-x86_64-qnx --extra_toolchains=@score_qcc_x86_64_toolchain//:x86_64-qnx-sdp_8.0.0 +build:time-x86_64-qnx --extra_toolchains=@score_qnx_x86_64_ifs_toolchain//:ifs-x86_64-qnx-sdp_8.0.0 # ------------------------------------------------------------------------------- # Config dedicated to target platform CPU:arm64 and OS:QNX diff --git a/MODULE.bazel b/MODULE.bazel index 4a01c4bc..9696559c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -57,6 +57,7 @@ use_repo( "score_gcc_x86_64_toolchain", "score_qcc_aarch64_toolchain", "score_qcc_x86_64_toolchain", + "score_qcc_x86_64_toolchain_pkg", ) ## Custom Module Loading @@ -212,3 +213,55 @@ pip.parse( requirements_lock = "//third_party/codeql:requirements_lock.txt", ) use_repo(pip, "codeql_coding_standards_pip_hub") +## Integration Test Framework dependencies + +bazel_dep(name = "score_itf", version = "0.2.0", dev_dependency = True) +git_override( + module_name = "score_itf", + commit = "98118b1fcd6c53244fb276e2c7668faaf15d2e10", # tag v0.4.0 + remote = "https://github.com/eclipse-score/itf.git", +) + +bazel_dep(name = "bazel_skylib", version = "1.9.0", dev_dependency = True) +bazel_dep(name = "rules_pkg", version = "1.2.0", dev_dependency = True) +bazel_dep(name = "rules_oci", version = "2.2.7", dev_dependency = True) + +oci = use_extension("@rules_oci//oci:extensions.bzl", "oci", dev_dependency = True) +oci.pull( + name = "ubuntu_24_04", + digest = "sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30", + image = "ubuntu", + platforms = ["linux/amd64"], + tag = "24.04", +) +use_repo(oci, "ubuntu_24_04", "ubuntu_24_04_linux_amd64") + +bazel_dep(name = "score_rules_imagefs", version = "0.0.3", dev_dependency = True) + +imagefs = use_extension("@score_rules_imagefs//extensions:imagefs.bzl", "imagefs", dev_dependency = True) +imagefs.toolchain( + name = "score_qnx_x86_64_ifs_toolchain", + sdp_to_import = "@score_qcc_x86_64_toolchain_pkg", + # imagefs extension does not remap 8.0.4 -> 8.0.0 for platform constraints, + # so use "8.0.0" here to match the only available sdp_version constraint + # value in @score_bazel_platforms. The actual SDP binary version comes + # from sdp_to_import (8.0.4). + sdp_version = "8.0.0", + target_cpu = "x86_64", + target_os = "qnx", + type = "ifs", +) +use_repo(imagefs, "score_qnx_x86_64_ifs_toolchain") + +## JSON Schema validation (build-time lint) + +bazel_dep(name = "nlohmann_json", version = "3.11.3", dev_dependency = True) + +http_archive( + name = "json_schema_validator", + build_file = "//third_party/json_schema_validator:json_schema_validator.BUILD", + dev_dependency = True, + integrity = "sha256-g/YdgRL0heDT8ectUWELo5JLF5kmqDdq7zwDh3D68gI=", + strip_prefix = "json-schema-validator-2.1.0", + urls = ["https://github.com/pboettch/json-schema-validator/archive/refs/tags/2.1.0.tar.gz"], +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 96859963..f2b9a622 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -39,6 +39,7 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.1/MODULE.bazel": "07e3ce3eaaa50dbd0be7fa0094e36890478937adc780ec53e77fd9fe543af8b1", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "7fe0191f047d4fe4a4a46c1107e2350cbb58a8fc2e10913aa4322d3190dec0bf", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/source.json": "369df5b7f2eae82f200fff95cf1425f90dee90a0d0948122060b48150ff0e224", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", @@ -478,6 +479,7 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", + "https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c", "https://bcr.bazel.build/modules/rules_oci/2.3.0/MODULE.bazel": "49075197960c924c0a4d759b7c765c3d00a41d2fdd4a943b42823c1d016ab4ec", "https://bcr.bazel.build/modules/rules_oci/2.3.0/source.json": "47710c28446211b5e61a24015a4669c50c6862d5f91e6bdbc710de8d750cf613", "https://bcr.bazel.build/modules/rules_perl/1.1.0/MODULE.bazel": "22138e75bb8f1ee6c21f609b90d2c24b0c9b796ccf55cc04c1c9190b699f7e9d", @@ -611,6 +613,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.21.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "not found", @@ -942,6 +945,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.5.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.7.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_oci/2.2.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_oci/2.3.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_perl/1.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", @@ -1035,6 +1039,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/3.0.1/MODULE.bazel": "296328e60d5481ff529e0c937ba80c5d3d4838445d119caf517fd82f97cd053a", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.0.0/MODULE.bazel": "522dc070354e6be2f984468a4243fe4ab8bec690922df5f31f7f0916ee264e20", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.0.1/MODULE.bazel": "5955f4cf37228a9cdda7f6009b81db0446f005c618f4bc43665bfa45f2673ebc", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.0.3/MODULE.bazel": "ad24df8df93882297e36ecaa39a94a69eed68aeb5bb1614e85296d001d5e3c03", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.5.0/MODULE.bazel": "4cfe52fe8b8dbeaf7e87500036391da278f72f1c2b41b689ffdd4337196dd8fe", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.5.0/source.json": "e01b29a3e9640a0d41d880d7da525e451115649d90f097ed66d09808c9135486", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_format_checker/0.1.1/MODULE.bazel": "1acc254faa90e9f97b79ac69af25b6c21c561f8d6079914f6352b9b20d26bd37", @@ -1056,12 +1061,15 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.4.3/MODULE.bazel": "eb8243299a6ae3663618db5b4718e2c0d3c93f91a44994641e70106c400b23fb", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.5.2/MODULE.bazel": "be52d29278d6671221f28921e8f1acfce29c3bfc3e6b4f503f0625ab2c61586f", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.5.3/MODULE.bazel": "65024b7f23ce5f72bd6ffd455a67c042ecf56d267f0bf63a90330a3241781b7a", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.5.4/MODULE.bazel": "efd56704f1a93e670032e7c0e4fad97669aa3b348fb1a4cd40f67e4dd4c7037c", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.6.0/MODULE.bazel": "2496bc24311f69f49449ee85d8bb38e3b970cbfcf10d0a7f19b2d5262ce80e8d", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.6.0/source.json": "093424aa8bfed8705a3d142b21fe1d053258f2dd5eb1944941f6439b2c7157e9", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.0/MODULE.bazel": "785ddd5295213e36c31ab86bdc34f29c0f7d1b72e9abd931bb08f42c0e48e2e9", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.1/MODULE.bazel": "99c491109937542e61df090222666a8613ef946fa7bb2b2d5ba648b2baba03ad", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.2/MODULE.bazel": "f25490f64035a0e3a0d53ad9cb6164e8325ce6cf2a7ee68c6ae153840cb2497e", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.4/MODULE.bazel": "53bd16dfbb1fb8ecf6822fb26f9f4e8333bac7b14d12bb02bf84078063820a31", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.0.3/MODULE.bazel": "d87fcd3485438284fa96d6a5a81923d6b6672468947423517cc43c4d7692ca6a", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.0.3/source.json": "5f2de07264fbc8f3adecffde5aea326b9f8fccf9e74fd6a057fc9c3556b00cfa", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/MODULE.bazel": "ade2bad4a331b02d9b7e7d9842e8de8c6fded6186486e02c4f7db5cd4b71d34d", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.3/MODULE.bazel": "9e8310a75c13ccebc49fb9cbf7acc6c1b75654292b2ca907fb5d513133dbf6f3", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.5/MODULE.bazel": "7de02547bdf121d3dedf5141b97f0fd9a545bd255ff5c7b699056b35816ffad9", @@ -9912,6 +9920,44 @@ ] ] } + }, + "@@score_rules_imagefs+//extensions:imagefs.bzl%imagefs": { + "general": { + "bzlTransitiveDigest": "6ZLANe53LXwcnne1ZrZm9GkJ9H5VdsCLSgSNHyUrqA4=", + "usagesDigest": "1ieOQTsTmqRePgmksL8DDGNxZZT2g0ExwBiH6NLXBLg=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "score_qnx_x86_64_ifs_toolchain": { + "repoRuleId": "@@score_rules_imagefs+//rules/qnx:imagefs_toolchain.bzl%imagefs_toolchain", + "attributes": { + "tc_cpu": "x86_64", + "tc_os": "qnx", + "tc_pkg_repo": "'@@score_bazel_cpp_toolchains++gcc+score_qcc_x86_64_toolchain_pkg'", + "sdp_version": "8.0.0", + "tc_type": "ifs" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "", + "score_qcc_x86_64_toolchain_pkg", + "score_bazel_cpp_toolchains++gcc+score_qcc_x86_64_toolchain_pkg" + ], + [ + "score_rules_imagefs+", + "bazel_tools", + "bazel_tools" + ], + [ + "score_rules_imagefs+", + "score_rules_imagefs", + "score_rules_imagefs+" + ] + ] + } } }, "facts": {} diff --git a/bazel/tools/BUILD b/bazel/tools/BUILD new file mode 100644 index 00000000..ca5de742 --- /dev/null +++ b/bazel/tools/BUILD @@ -0,0 +1,12 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* diff --git a/bazel/tools/json_schema_validator.bzl b/bazel/tools/json_schema_validator.bzl new file mode 100644 index 00000000..fb3c76fc --- /dev/null +++ b/bazel/tools/json_schema_validator.bzl @@ -0,0 +1,57 @@ +""" +Creates "validate_json_schema_test" test rule which validates the input "json" file against its "schema" +""" + +visibility(["//..."]) + +def _impl(ctx): + script = """ +readonly expected_failure={expected_failure} +'{validator}' '{schema}' < '{json}' +readonly ret=$? +if test "$expected_failure" = true && test "$ret" -ne 0; then + exit 0 +elif test "$expected_failure" = false && test "$ret" -eq 0; then + exit 0 +fi +echo "Test Failed: Json validator performed json validation of '{json}' against '{schema}' schema with exit code $ret, meanwhile rule attribute 'expected_failure' is equal to '{expected_failure}'. +This means in case expected_failure=false then expected exit code should be zero (valid json). Otherwise, if expected_failure=true then exit code is non-zero (invalid json). +Note: by default expected_failure is false." + exit 1 + """.format( + validator = ctx.attr._validator.files_to_run.executable.short_path, + schema = ctx.file.schema.short_path, + json = ctx.file.json.short_path, + expected_failure = "true" if ctx.attr.expected_failure else "false", + ) + + ctx.actions.write( + output = ctx.outputs.executable, + content = script, + ) + + # To ensure the files needed by the script are available, put them in the runfiles + runfiles = ctx.runfiles(files = [ctx.file._validator, ctx.file.json, ctx.file.schema]) + return [DefaultInfo(runfiles = runfiles)] + +validate_json_schema_test = rule( + implementation = _impl, + attrs = { + "expected_failure": attr.bool(default = False), + "json": attr.label( + allow_single_file = True, + mandatory = True, + ), + "schema": attr.label( + allow_single_file = True, + mandatory = True, + ), + "_validator": attr.label( + default = Label("@json_schema_validator"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), + }, + test = True, +) diff --git a/score/time_slave/config/qnx/BUILD b/score/time_slave/config/qnx/BUILD new file mode 100644 index 00000000..4a8da602 --- /dev/null +++ b/score/time_slave/config/qnx/BUILD @@ -0,0 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +package(default_visibility = ["//visibility:public"]) + +exports_files(["time_slave_qnx_config.json"]) diff --git a/score/time_slave/config/qnx/time_slave_qnx_config.json b/score/time_slave/config/qnx/time_slave_qnx_config.json new file mode 100644 index 00000000..987a4f2a --- /dev/null +++ b/score/time_slave/config/qnx/time_slave_qnx_config.json @@ -0,0 +1,18 @@ +{ + "iface_name": "emac0", + "domain_number": 0, + "pdelay_req_interval_ms": 1000, + "pdelay_warmup_ms": 2000, + "sync_timeout_ms": 3300, + "jump_future_threshold_ns": 500000000, + "shm_path": "/gptp_shmem", + "phc": { + "enabled": true, + "device": "emac0", + "step_threshold_ns": 100000000 + }, + "qnx": { + "bpf_device_prefix": "/dev/bpf", + "see_sent": true + } +} diff --git a/score/time_slave/docs/manuals/index.rst b/score/time_slave/docs/manuals/index.rst new file mode 100644 index 00000000..fa649c74 --- /dev/null +++ b/score/time_slave/docs/manuals/index.rst @@ -0,0 +1,21 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Manuals +======= + +.. toctree:: + :maxdepth: 1 + + qnx_setup diff --git a/score/time_slave/docs/manuals/qnx_setup.rst b/score/time_slave/docs/manuals/qnx_setup.rst new file mode 100644 index 00000000..04e88c8e --- /dev/null +++ b/score/time_slave/docs/manuals/qnx_setup.rst @@ -0,0 +1,190 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Running TimeSlave on QNX +######################## + +This manual explains how to configure, build, and run the TimeSlave +process on QNX (SDP 8.0.4, x86_64). + +Prerequisites +************* + +* `QNX Software Center `_ installed on the + build host, with **QNX SDP 8.0** deployed. +* A valid QNX license, placed under ``/var/tmp/.qnx/license/licenses`` + (the QNX tools hard-code ``QNX_CONFIGURATION_EXCLUSIVE=/var/tmp/.qnx``; + see :doc:`/memory:qnx-license-var-tmp`). +* `Bazel `_ 8.6.0 or later (see + ``tools/setup_toolchain.sh`` for an automated setup script). +* QEMU with KVM support for running the reference integration test + (optional, but required for the QEMU-based integration test). +* Bazel user credentials for ``qnx.com`` (used by + ``tools/qnx_credential_helper.py``) — set via ``~/.netrc`` or the + ``SCORE_QNX_USER`` / ``SCORE_QNX_PASSWORD`` environment variables. + +Configuration +************* + +TimeSlave is configured via a JSON file. The schema is defined in +``score/time_slave/src/application/configuration/time_slave_config_schema.json`` +and covers all runtime parameters: network interface, gPTP timing, PHC +adjustment, shared-memory path, and QNX-specific options. + +Configuration file location +=========================== + +The configuration file is resolved in the following order: + +1. ``--config `` command-line argument. +2. ``TIMESLAVE_CONFIG`` environment variable. +3. ``./etc/time_slave_config.json`` relative to the process working + directory. + +If no configuration file is found at the resolved path, TimeSlave uses +built-in defaults (equivalent to the schema defaults) and logs an +informational message. + +For backwards compatibility, the ``GPTP_IFACE`` environment variable +(when set) still overrides the ``iface_name`` field after the JSON config +is loaded. + +QNX-specific fields +=================== + +The optional ``"qnx"`` section holds QNX-only settings (ignored on +Linux): + +.. code-block:: json + + { + "qnx": { + "bpf_device_prefix": "/dev/bpf", + "see_sent": true + } + } + +``bpf_device_prefix`` + Path prefix for the BPF (Berkeley Packet Filter) devices used for raw + Ethernet frame capture. Defaults to ``/dev/bpf``. + +``see_sent`` + When ``true``, sent frames are also delivered to the RX BPF. This is + required to capture TX hardware timestamps for Pdelay_Req T1 + measurement. Defaults to ``false``. + +PHC configuration +================= + +To enable PTP Hardware Clock adjustment, set ``"phc"."enabled"`` to +``true``. On QNX the ``device`` field identifies the network interface +whose EMAC PTP clock is adjusted (e.g. ``"emac0"``): + +.. code-block:: json + + { + "phc": { + "enabled": true, + "device": "emac0", + "step_threshold_ns": 100000000 + } + } + +When disabled (the default), gPTP runs in slave-only mode without +disciplining a hardware clock. + +Example +======= + +A representative QNX configuration is shipped at +``score/time_slave/config/qnx/time_slave_qnx_config.json``: + +.. literalinclude:: ../../config/qnx/time_slave_qnx_config.json + :language: json + +Build steps +*********** + +Build for QNX x86_64 +==================== + +.. code-block:: shell + + # (Once) Source the setup helper to register QNX and toolchain paths. + source tools/setup_toolchain.sh + + # Build TimeSlave for QNX x86_64. + bazel build --config=time-x86_64-qnx //score/time_slave:time_slave + +The resulting binary appears under ``bazel-bin/score/time_slave/src/application/time_slave``. + +Packaging for deployment +======================== + +To produce a deployable layout (``/opt/time_slave/bin/`` + +``/opt/time_slave/etc/``) use the ``pkg_application`` macro defined in +``score/time_slave/pkg_application.bzl``. A typical BUILD snippet: + +.. code-block:: python + + load("//score/time_slave:pkg_application.bzl", "pkg_application") + + pkg_application( + name = "time_slave-pkg", + app_name = "time_slave", + bin = ["//score/time_slave:time_slave"], + etc = ["//score/time_slave/config/qnx:time_slave_qnx_config.json"], + ) + +The resulting ``pkg_filegroup`` can be fed to ``pkg_tar`` (for +archive-based deployment), ``oci_image`` (for Docker), or ``qnx_ifs`` +(for inclusion in a QNX IFS image). + +Running on QNX +************** + +On a QNX target (or in the QEMU reference integration), copy the binary +and config to the conventional locations and run: + +.. code-block:: shell + + # On the QNX target shell: + mkdir -p /opt/time_slave/etc + cp time_slave /opt/time_slave/bin/ + cp time_slave_qnx_config.json /opt/time_slave/etc/time_slave_config.json + cd /opt/time_slave + ./bin/time_slave --config etc/time_slave_config.json + +To use a non-default interface without a config file: + +.. code-block:: shell + + GPTP_IFACE=emac1 ./bin/time_slave + +Reference integration test +************************** + +A reference integration test is provided at +``score/time_slave/tests/reference_integration/``. It boots a minimal +QNX QEMU image containing the TimeSlave binary and a QNX configuration +file, starts the process, and verifies that it configures itself +correctly, opens the shared-memory channel, and begins publishing gPTP +snapshots. + +Run it with: + +.. code-block:: shell + + bazel test --config=time-x86_64-qnx \ + //score/time_slave/tests/reference_integration:time_slave_qnx_ref_integ diff --git a/score/time_slave/src/application/BUILD b/score/time_slave/src/application/BUILD index 34ed6ef2..022a3734 100644 --- a/score/time_slave/src/application/BUILD +++ b/score/time_slave/src/application/BUILD @@ -24,6 +24,7 @@ cc_binary( tags = ["QM"], visibility = ["//score/time_slave:__pkg__"], deps = [ + "//score/time_slave/src/application/configuration", "//score/time_slave/src/common:logging_contexts", "//score/time_slave/src/gptp:gptp_engine", "//score/ts_client/src:gptp_ipc", diff --git a/score/time_slave/src/application/configuration/BUILD b/score/time_slave/src/application/configuration/BUILD new file mode 100644 index 00000000..e7561ff1 --- /dev/null +++ b/score/time_slave/src/application/configuration/BUILD @@ -0,0 +1,48 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") + +filegroup( + name = "time_slave_config_schema", + srcs = ["time_slave_config_schema.json"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "configuration", + srcs = ["config_parser.cpp"], + hdrs = [ + "config_parser.h", + "time_slave_config.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["QM"], + visibility = ["//score/time_slave:__subpackages__"], + deps = [ + "//score/time_slave/src/common:logging_contexts", + "//score/time_slave/src/gptp:gptp_engine", + "@score_baselibs//score/json", + "@score_baselibs//score/json:json_parser", + "@score_baselibs//score/mw/log:console_only_backend", + ], +) + +validate_json_schema_test( + name = "validate_time_slave_config_schema", + json = "example/time_slave_config.json", + schema = ":time_slave_config_schema", + tags = ["lint"], + target_compatible_with = ["@platforms//os:linux"], +) diff --git a/score/time_slave/src/application/configuration/config_parser.cpp b/score/time_slave/src/application/configuration/config_parser.cpp new file mode 100644 index 00000000..0f758a9b --- /dev/null +++ b/score/time_slave/src/application/configuration/config_parser.cpp @@ -0,0 +1,177 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time_slave/src/application/configuration/config_parser.h" + +#include "score/json/json_parser.h" +#include "score/mw/log/logging.h" +#include "score/time_slave/src/common/logging_contexts.h" + +#include +#include +#include + +namespace score +{ +namespace ts +{ + +namespace +{ + +using score::json::Error; +using score::json::Object; + +/// Fatal out if @p result is a type/parse error (as opposed to the caller having +/// already detected "key not present" via Object::find()). +template +void FatalOnTypeError(const ResultT& result, std::string_view key) +{ + if (!result.has_value() && static_cast(*result.error()) != static_cast(Error::kKeyNotFound)) + { + score::mw::log::LogFatal(kTimeSlaveAppContext) + << "Config field '" << key << "' has wrong type: " << result.error().Message(); + } +} + +/// Read a string field; leaves @p out unchanged if key is missing. +void ParseOptionalString(const Object& obj, std::string_view key, std::string& out) +{ + auto it = obj.find(key); + if (it == obj.end()) + { + return; + } + auto result = it->second.As(); + FatalOnTypeError(result, key); + if (result.has_value()) + { + out = std::string{result.value()}; + } +} + +/// Read an integer field; leaves @p out unchanged if key is missing. +template +void ParseOptionalInt(const Object& obj, std::string_view key, T& out) +{ + auto it = obj.find(key); + if (it == obj.end()) + { + return; + } + auto result = it->second.As(); + FatalOnTypeError(result, key); + if (result.has_value()) + { + out = static_cast(result.value()); + } +} + +/// Read a bool field; leaves @p out unchanged if key is missing. +void ParseOptionalBool(const Object& obj, std::string_view key, bool& out) +{ + auto it = obj.find(key); + if (it == obj.end()) + { + return; + } + auto result = it->second.As(); + FatalOnTypeError(result, key); + if (result.has_value()) + { + out = result.value(); + } +} + +/// Parse the optional "phc" nested object. If absent, @p cfg keeps defaults. +void ParsePhcConfig(const Object& obj, details::PhcConfig& cfg) +{ + auto it = obj.find("phc"); + if (it == obj.end()) + { + return; + } + auto obj_result = it->second.As(); + if (!obj_result.has_value()) + { + score::mw::log::LogFatal(kTimeSlaveAppContext) + << "Config field 'phc' is not an object: " << obj_result.error().Message(); + return; + } + const auto& phc_obj = obj_result.value().get(); + + ParseOptionalBool(phc_obj, "enabled", cfg.enabled); + ParseOptionalString(phc_obj, "device", cfg.device); + ParseOptionalInt(phc_obj, "step_threshold_ns", cfg.step_threshold_ns); +} + +/// Parse the optional "qnx" nested object. If absent, @p qnx keeps defaults. +void ParseQnxConfig(const Object& obj, TimeSlaveConfig::QnxSettings& qnx) +{ + auto it = obj.find("qnx"); + if (it == obj.end()) + { + return; + } + auto obj_result = it->second.As(); + if (!obj_result.has_value()) + { + score::mw::log::LogFatal(kTimeSlaveAppContext) + << "Config field 'qnx' is not an object: " << obj_result.error().Message(); + return; + } + const auto& qnx_obj = obj_result.value().get(); + + ParseOptionalString(qnx_obj, "bpf_device_prefix", qnx.bpf_device_prefix); + ParseOptionalBool(qnx_obj, "see_sent", qnx.see_sent); +} + +} // namespace + +TimeSlaveConfig ParseConfig(const std::string& path) +{ + const score::json::JsonParser parser; + auto root_result = parser.FromFile(path); + if (!root_result.has_value()) + { + score::mw::log::LogFatal(kTimeSlaveAppContext) + << "Failed to parse config file '" << path << "': " << root_result.error().Message(); + return {}; + } + + auto obj_result = root_result.value().As(); + if (!obj_result.has_value()) + { + score::mw::log::LogFatal(kTimeSlaveAppContext) << "Config file '" << path << "' root is not a JSON object"; + return {}; + } + const auto& obj = obj_result.value().get(); + + TimeSlaveConfig cfg; + auto& opts = cfg.engine_opts; + + ParseOptionalString(obj, "iface_name", opts.iface_name); + ParseOptionalInt(obj, "domain_number", opts.domain_number); + ParseOptionalInt(obj, "pdelay_req_interval_ms", opts.pdelay_req_interval_ms); + ParseOptionalInt(obj, "pdelay_warmup_ms", opts.pdelay_warmup_ms); + ParseOptionalInt(obj, "sync_timeout_ms", opts.sync_timeout_ms); + ParseOptionalInt(obj, "jump_future_threshold_ns", opts.jump_future_threshold_ns); + ParseOptionalString(obj, "shm_path", cfg.shm_path); + + ParsePhcConfig(obj, opts.phc_config); + ParseQnxConfig(obj, cfg.qnx); + + return cfg; +} + +} // namespace ts +} // namespace score diff --git a/score/time_slave/src/application/configuration/config_parser.h b/score/time_slave/src/application/configuration/config_parser.h new file mode 100644 index 00000000..d0cd6fd2 --- /dev/null +++ b/score/time_slave/src/application/configuration/config_parser.h @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SLAVE_CONFIGURATION_CONFIG_PARSER_H +#define SCORE_TIME_SLAVE_CONFIGURATION_CONFIG_PARSER_H + +#include "score/time_slave/src/application/configuration/time_slave_config.h" + +#include + +namespace score +{ +namespace ts +{ + +/** + * @brief Parse a TimeSlave JSON configuration file from @p path. + * + * On success, the returned config is fully populated with values from the + * file; missing optional fields fall back to their defaults as described in + * the JSON schema. + * + * On failure (file not found, invalid JSON, schema-violating structure) + * the process terminates via LogFatal. + * + * @param path Filesystem path to the JSON config file. + * @return Parsed TimeSlaveConfig. + */ +TimeSlaveConfig ParseConfig(const std::string& path); + +} // namespace ts +} // namespace score + +#endif // SCORE_TIME_SLAVE_CONFIGURATION_CONFIG_PARSER_H diff --git a/score/time_slave/src/application/configuration/example/time_slave_config.json b/score/time_slave/src/application/configuration/example/time_slave_config.json new file mode 100644 index 00000000..987a4f2a --- /dev/null +++ b/score/time_slave/src/application/configuration/example/time_slave_config.json @@ -0,0 +1,18 @@ +{ + "iface_name": "emac0", + "domain_number": 0, + "pdelay_req_interval_ms": 1000, + "pdelay_warmup_ms": 2000, + "sync_timeout_ms": 3300, + "jump_future_threshold_ns": 500000000, + "shm_path": "/gptp_shmem", + "phc": { + "enabled": true, + "device": "emac0", + "step_threshold_ns": 100000000 + }, + "qnx": { + "bpf_device_prefix": "/dev/bpf", + "see_sent": true + } +} diff --git a/score/time_slave/src/application/configuration/time_slave_config.h b/score/time_slave/src/application/configuration/time_slave_config.h new file mode 100644 index 00000000..efe08b53 --- /dev/null +++ b/score/time_slave/src/application/configuration/time_slave_config.h @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SLAVE_CONFIGURATION_TIME_SLAVE_CONFIG_H +#define SCORE_TIME_SLAVE_CONFIGURATION_TIME_SLAVE_CONFIG_H + +#include "score/time_slave/src/gptp/gptp_engine.h" +#include "score/time_slave/src/gptp/phc/phc_adjuster.h" + +#include +#include + +namespace score +{ +namespace ts +{ + +/** + * @brief Runtime configuration for the TimeSlave process. + * + * Loaded from a JSON file (or filled in with defaults when no file is + * provided). Holds both the GptpEngine/PHC options and any process-level + * configuration. + */ +struct TimeSlaveConfig +{ + /// gPTP engine options (passed directly to GptpEngine). + details::GptpEngineOptions engine_opts{}; + + /// Path to shared-memory segment used to publish PTP snapshots. + std::string shm_path = "/gptp_shmem"; + + /// QNX-specific settings. Only relevant on QNX builds. + struct QnxSettings + { + std::string bpf_device_prefix = "/dev/bpf"; ///< BPF device path prefix. + bool see_sent = false; ///< Deliver sent frames to RX BPF. + }; + + QnxSettings qnx{}; +}; + +} // namespace ts +} // namespace score + +#endif // SCORE_TIME_SLAVE_CONFIGURATION_TIME_SLAVE_CONFIG_H diff --git a/score/time_slave/src/application/configuration/time_slave_config_schema.json b/score/time_slave/src/application/configuration/time_slave_config_schema.json new file mode 100644 index 00000000..93ab083d --- /dev/null +++ b/score/time_slave/src/application/configuration/time_slave_config_schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://eclipse-score.github.io/score_time/time_slave_config_schema.json", + "title": "TimeSlave Configuration", + "description": "Runtime configuration for the TimeSlave gPTP process. The same schema is used on both Linux and QNX; QNX-specific options live under the optional 'qnx' key.", + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "iface_name": { + "type": "string", + "default": "emac0", + "description": "Network interface name used for gPTP (e.g. 'emac0' on QNX, 'eth0' on Linux)." + }, + "domain_number": { + "type": "integer", + "minimum": 0, + "maximum": 127, + "default": 0, + "description": "gPTP domain number." + }, + "pdelay_req_interval_ms": { + "type": "integer", + "minimum": 10, + "maximum": 60000, + "default": 1000, + "description": "Interval between Pdelay_Req transmissions in milliseconds (IEEE 1588 logPdelayReqInterval converted to ms for simplicity)." + }, + "pdelay_warmup_ms": { + "type": "integer", + "minimum": 0, + "maximum": 60000, + "default": 2000, + "description": "Delay before the first Pdelay_Req after startup in milliseconds." + }, + "sync_timeout_ms": { + "type": "integer", + "minimum": 10, + "maximum": 60000, + "default": 3300, + "description": "Declare sync loss after this many milliseconds without receiving a Sync/FollowUp." + }, + "jump_future_threshold_ns": { + "type": "integer", + "minimum": 0, + "default": 500000000, + "description": "A time jump greater than this (nanoseconds) is detected as a future jump." + }, + "shm_path": { + "type": "string", + "default": "/gptp_shmem", + "description": "Shared-memory path used to publish PTP snapshots for TimeDaemon." + }, + "phc": { + "type": "object", + "additionalProperties": false, + "description": "PHC (PTP Hardware Clock) adjustment settings. If omitted, PHC adjustment is disabled.", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "device": { + "type": "string", + "default": "", + "description": "PHC device path. On QNX use the interface name (e.g. 'emac0'); on Linux use the /dev/ptpN path." + }, + "step_threshold_ns": { + "type": "integer", + "minimum": 0, + "default": 100000000, + "description": "Offsets larger than this trigger a step correction (nanoseconds); smaller offsets are slewed." + } + } + }, + "qnx": { + "type": "object", + "additionalProperties": false, + "description": "QNX-specific options. Ignored on Linux.", + "properties": { + "bpf_device_prefix": { + "type": "string", + "default": "/dev/bpf", + "description": "Path prefix for BPF devices used for raw Ethernet frame capture." + }, + "see_sent": { + "type": "boolean", + "default": false, + "description": "If true, sent frames are also delivered to the RX BPF (used for TX timestamp capture)." + } + } + } + } +} diff --git a/score/time_slave/src/application/time_slave.cpp b/score/time_slave/src/application/time_slave.cpp index 9742556a..6d0f0cd3 100644 --- a/score/time_slave/src/application/time_slave.cpp +++ b/score/time_slave/src/application/time_slave.cpp @@ -13,8 +13,12 @@ #include "score/time_slave/src/application/time_slave.h" #include "score/mw/log/logging.h" +#include "score/time_slave/src/application/configuration/config_parser.h" #include "score/time_slave/src/common/logging_contexts.h" +#include +#include +#include #include namespace score @@ -32,8 +36,60 @@ constexpr std::int32_t kInitFailure = -1; TimeSlave::TimeSlave() = default; -std::int32_t TimeSlave::Initialize(const score::mw::lifecycle::ApplicationContext& /*context*/) +std::int32_t TimeSlave::Initialize(const score::mw::lifecycle::ApplicationContext& context) { + namespace fs = std::filesystem; + + // Resolve config path: --config CLI > TIMESLAVE_CONFIG env > ./etc/time_slave_config.json + fs::path config_path; + const std::string cli_config = context.get_argument("--config"); + if (!cli_config.empty()) + { + config_path = cli_config; + } + else if (const char* env_config = std::getenv("TIMESLAVE_CONFIG"); env_config != nullptr && env_config[0] != '\0') + { + config_path = env_config; + } + else + { + config_path = "./etc/time_slave_config.json"; + } + + TimeSlaveConfig cfg; + if (fs::exists(config_path)) + { + cfg = ParseConfig(config_path.string()); + opts_ = cfg.engine_opts; + score::mw::log::LogInfo(kTimeSlaveAppContext) + << "Loaded config from " << config_path.string() << " (iface=" << opts_.iface_name + << ", phc=" << (opts_.phc_config.enabled ? "enabled" : "disabled") << ")"; + } + else + { + score::mw::log::LogInfo(kTimeSlaveAppContext) + << "No config file found at " << config_path.string() << ", using built-in defaults"; + } + + // Legacy env-var override: GPTP_IFACE overrides iface_name. + if (const char* iface_env = std::getenv("GPTP_IFACE"); iface_env != nullptr && iface_env[0] != '\0') + { + opts_.iface_name = iface_env; + score::mw::log::LogInfo(kTimeSlaveAppContext) << "Using interface from GPTP_IFACE: " << opts_.iface_name; + } + + // Apply QNX-specific settings via environment variables (read by the QNX + // raw-socket shim). Pre-existing env vars are never overridden so users can + // still override at the command line. + if (!cfg.qnx.bpf_device_prefix.empty() && std::getenv("SOCK") == nullptr) + { + ::setenv("SOCK", cfg.qnx.bpf_device_prefix.c_str(), 0); + } + if (cfg.qnx.see_sent && std::getenv("QNX_RAW_SEESENT") == nullptr) + { + ::setenv("QNX_RAW_SEESENT", "1", 0); + } + engine_ = std::make_unique(opts_); if (!engine_->Initialize()) diff --git a/score/time_slave/src/gptp/gptp_engine.cpp b/score/time_slave/src/gptp/gptp_engine.cpp index 93161cae..a3c458c9 100644 --- a/score/time_slave/src/gptp/gptp_engine.cpp +++ b/score/time_slave/src/gptp/gptp_engine.cpp @@ -209,7 +209,7 @@ void GptpEngine::PdelayLoop() noexcept next.tv_nsec = static_cast(next_warmup_ns % 1'000'000'000LL); const std::int64_t interval_ns = - static_cast(opts_.pdelay_interval_ms > 0 ? opts_.pdelay_interval_ms : 1000) * 1'000'000LL; + static_cast(opts_.pdelay_req_interval_ms > 0 ? opts_.pdelay_req_interval_ms : 1000) * 1'000'000LL; while (running_.load(std::memory_order_acquire)) { diff --git a/score/time_slave/src/gptp/gptp_engine.h b/score/time_slave/src/gptp/gptp_engine.h index f6f5c276..a0df2093 100644 --- a/score/time_slave/src/gptp/gptp_engine.h +++ b/score/time_slave/src/gptp/gptp_engine.h @@ -42,7 +42,7 @@ namespace details struct GptpEngineOptions { std::string iface_name = "emac0"; ///< Network interface for gPTP - int pdelay_interval_ms = 1000; ///< Period between Pdelay_Req transmissions (ms) + int pdelay_req_interval_ms = 1000; ///< Period between Pdelay_Req transmissions (ms) int pdelay_warmup_ms = 2000; ///< Delay before first Pdelay_Req (ms) int sync_timeout_ms = 3300; ///< Declare timeout after this many ms without Sync std::int64_t jump_future_threshold_ns = 500'000'000LL; ///< 500 ms diff --git a/score/time_slave/src/gptp/gptp_engine_test.cpp b/score/time_slave/src/gptp/gptp_engine_test.cpp index 13a87cbb..f544b2bf 100644 --- a/score/time_slave/src/gptp/gptp_engine_test.cpp +++ b/score/time_slave/src/gptp/gptp_engine_test.cpp @@ -240,8 +240,8 @@ GptpEngineOptions FastOptions() { GptpEngineOptions o; o.iface_name = "lo"; - o.pdelay_warmup_ms = 0; // no warmup — first Pdelay_Req fires immediately - o.pdelay_interval_ms = 10; // 10 ms cycle + o.pdelay_warmup_ms = 0; // no warmup — first Pdelay_Req fires immediately + o.pdelay_req_interval_ms = 10; // 10 ms cycle o.sync_timeout_ms = 3300; o.jump_future_threshold_ns = 500'000'000LL; return o; diff --git a/score/time_slave/tests/reference_integration/BUILD b/score/time_slave/tests/reference_integration/BUILD new file mode 100644 index 00000000..456a3b69 --- /dev/null +++ b/score/time_slave/tests/reference_integration/BUILD @@ -0,0 +1,40 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_itf//:defs.bzl", "py_itf_test") + +py_itf_test( + name = "time_slave_qnx_ref_integ", + size = "enormous", + timeout = "short", + srcs = [ + "tests/__init__.py", + "tests/test_time_slave_qnx.py", + ], + args = [ + "--qemu-config=$(rootpath qemu_config.json)", + "--qemu-image=$(rootpath //score/time_slave/tests/reference_integration/qnx:ifs)", + ], + data = [ + "qemu_config.json", + "//score/time_slave/tests/reference_integration/qnx:ifs", + ], + plugins = [ + "@score_itf//score/itf/plugins:qemu_plugin", + ], + tags = [ + "integration", + "manual", + "qemu", + ], +) diff --git a/score/time_slave/tests/reference_integration/qemu_config.json b/score/time_slave/tests/reference_integration/qemu_config.json new file mode 100644 index 00000000..5e40974b --- /dev/null +++ b/score/time_slave/tests/reference_integration/qemu_config.json @@ -0,0 +1,18 @@ +{ + "networks": [ + { + "name": "lo", + "ip_address": "127.0.0.1", + "gateway": "127.0.0.1" + } + ], + "ssh_port": 2222, + "qemu_num_cores": 2, + "qemu_ram_size": "1G", + "port_forwarding": [ + { + "host_port": 2222, + "guest_port": 22 + } + ] +} diff --git a/score/time_slave/tests/reference_integration/qnx/BUILD b/score/time_slave/tests/reference_integration/qnx/BUILD new file mode 100644 index 00000000..6919ab8b --- /dev/null +++ b/score/time_slave/tests/reference_integration/qnx/BUILD @@ -0,0 +1,45 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files", "strip_prefix") +load("@score_rules_imagefs//rules/qnx:ifs.bzl", "qnx_ifs") + +pkg_files( + name = "time_slave_bin", + srcs = ["//score/time_slave"], + attributes = pkg_attributes(mode = "0755"), + prefix = "/opt/time_slave/bin", + strip_prefix = strip_prefix.files_only(), + target_compatible_with = ["@platforms//os:qnx"], +) + +pkg_files( + name = "time_slave_etc", + srcs = ["time_slave_config.json"], + attributes = pkg_attributes(mode = "0644"), + prefix = "/opt/time_slave/etc", + target_compatible_with = ["@platforms//os:qnx"], +) + +qnx_ifs( + name = "ifs", + srcs = [ + ":time_slave_bin", + ":time_slave_etc", + ], + build_file = "init.build", + extra_build_files = ["tools.build"], + tags = ["manual"], + target_compatible_with = ["@platforms//os:qnx"], + visibility = ["//visibility:public"], +) diff --git a/score/time_slave/tests/reference_integration/qnx/init.build b/score/time_slave/tests/reference_integration/qnx/init.build new file mode 100644 index 00000000..31c8e0d7 --- /dev/null +++ b/score/time_slave/tests/reference_integration/qnx/init.build @@ -0,0 +1,104 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +[-optional] + +[image=0x5000000] +[virtual=x86_64,multiboot] boot = { + startup-x86 -v -D8250..115200 -zz + PATH=/proc/boot:/usr/bin:/usr/sbin + LD_LIBRARY_PATH=/proc/boot:/usr/lib + [+keeplinked] procnto-smp-instr +} + +[+script] startup-script = { + procmgr_symlink /dev/shmem /tmp + + display_msg Welcome to QNX OS 8.0 on x86_64 for S-CORE time_slave tests! + + SYSNAME=nto + TERM=qansi + + devc-ser8250 & + waitfor /dev/ser1 + reopen /dev/ser1 + + /proc/boot/startup.sh + + [+session] /bin/sh & +} + +[uid=0 gid=0 perms=0700] startup.sh = { + echo "---> Starting slogger2" + slogger2 -s 4096 + waitfor /dev/slog + + echo "---> Starting PCI Services" + pci-server --config=/proc/boot/pci_server.cfg + waitfor /dev/pci + + echo "---> Starting Pipe" + pipe + waitfor /dev/pipe + + echo "---> Starting Random" + random + waitfor /dev/random + + echo "---> Starting fsevmgr" + fsevmgr + waitfor /dev/fsnotify + + echo "---> Starting devb-ram" + devb-ram ram capacity=1 blk ramdisk=10m,cache=512k,vnode=256 2>/dev/null + waitfor /dev/ram0 + + echo "---> mounting ram disk" + mkqnx6fs -q /dev/ram0 + waitfor /dev/ram0 + mount -t qnx6 /dev/ram0 /tmp_ram + + echo "---> Starting mqueue" + mqueue + waitfor /dev/mqueue + + echo "---> Starting devc-pty" + devc-pty -n 32 + + echo "---> Starting Networking" + io-sock -m phy -m pci -d vtnet_pci + waitfor /dev/socket + + echo "---> Configuring network interface" + ifconfig lo0 127.0.0.1 up + if_up -p vtnet0 + ifconfig vtnet0 10.0.2.15 netmask 255.255.255.0 + route add default 10.0.2.2 + sysctl -w net.inet.icmp.bmcastecho=1 + + echo "---> Setting hostname" + hostname qnx-score-time + + echo "---> Starting sshd" + /proc/boot/sshd -E /tmp/sshd.log -f /var/ssh/sshd_config & + sleep 3 + echo "SSH_READY" +} + +############################################# +### TimeSlave deployment directories ### +############################################# +[dperms=755 type=dir] /opt +[dperms=755 type=dir] /opt/time_slave +[dperms=755 type=dir] /opt/time_slave/bin +[dperms=755 type=dir] /opt/time_slave/etc diff --git a/score/time_slave/tests/reference_integration/qnx/time_slave_config.json b/score/time_slave/tests/reference_integration/qnx/time_slave_config.json new file mode 100644 index 00000000..529920d3 --- /dev/null +++ b/score/time_slave/tests/reference_integration/qnx/time_slave_config.json @@ -0,0 +1,16 @@ +{ + "iface_name": "vtnet0", + "domain_number": 0, + "pdelay_req_interval_ms": 1000, + "pdelay_warmup_ms": 2000, + "sync_timeout_ms": 3300, + "jump_future_threshold_ns": 500000000, + "shm_path": "/gptp_shmem", + "phc": { + "enabled": false + }, + "qnx": { + "bpf_device_prefix": "/dev/bpf", + "see_sent": true + } +} diff --git a/score/time_slave/tests/reference_integration/qnx/tools.build b/score/time_slave/tests/reference_integration/qnx/tools.build new file mode 100644 index 00000000..b447a89e --- /dev/null +++ b/score/time_slave/tests/reference_integration/qnx/tools.build @@ -0,0 +1,356 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +############################################# +### SHARED LIBRARIES ### +############################################# +/usr/lib/ldqnx-64.so.2=ldqnx-64.so.2 # 64-bit QNX dynamic linker (loads shared libraries) + +cam-cdrom.so # CAM (Common Access Method) CD-ROM driver +cam-disk.so # CAM disk driver for storage devices +fs-dos.so # DOS file system support +fs-qnx6.so # QNX6 file system support +io-blk.so # Block I/O manager +libbz2.so.1 # BZip2 compression library +libc++.so.2 # C++ standard library +libc.so # C standard library (essential) +libc.so.6 # Main C library (malloc, printf, file I/O, etc.) +libcam.so.2 # CAM library for device access +libcatalog.so.1 # Message catalog support +libcrypto.so.3 # OpenSSL crypto library +libexpat.so.2 # Expat XML parser library +libfscrypto.so.1 # File system encryption support +libfsnotify.so.1 # File system notification library +libgcc_s.so.1 # GCC runtime support library +libiconv.so.1 # Character encoding conversion +libjail.so.1 # QNX jail library for process containment +liblzma.so.5 # LZMA compression library +libm.so # Math library +libpam.so.2 # Pluggable Authentication Modules library +libpci.so.3.0 # PCI library v3.0 for hardware access +libqcrypto.so.1.0 # QNX cryptographic library +libqh.so # QNX helper library +libregex.so.1 # Regular expression library +libsecpol.so.1 # Security policy library +libslog2.so.1 # System logging library (slog2_* functions) +libslog2parse.so.1 # System log parsing library +libsocket.so.4 # Socket library (TCP/IP, UDP, network I/O functions) +libssl.so.3 # OpenSSL SSL/TLS library +libtracelog.so.1 # Trace logging library +libxml2.so.2 # XML parsing library +libz.so.2 # Zlib compression library (versioned SONAME required by sshd) +[type=link] libz.so=libz.so.2 # Version-neutral symlink +qcrypto-openssl-3.so # QNX cryptographic library with OpenSSL 3 support + +[type=link] libslog2.so=libslog2.so.1 # Create version-neutral symbolic link +[type=link] libsocket.so=libsocket.so.4 # Create version-neutral symbolic link +[type=link] qcrypto-openssl.so=qcrypto-openssl-3.so + + +############################################# +### Tools ### +############################################# +[type=link] /bin/ls=/proc/boot/ls # Link ls to IFS version for compatibility +toybox # Minimal Unix utilities collection (replaces many GNU tools) +[type=link] cp=toybox # Copy files and directories +[type=link] ls=toybox # List directory contents +[type=link] cat=toybox # Display file contents +[type=link] chmod=toybox # Change file permissions +[type=link] rm=toybox # Remove files and directories +[type=link] dd=toybox # Convert and copy files with specified I/O block size +[type=link] echo=toybox # Display text +[type=link] grep=toybox # Search text patterns in files +[type=link] mkdir=toybox # Create directories +[type=link] ascii=toybox # Display ASCII character set +[type=link] base64=toybox # Base64 encoding/decoding utility +[type=link] basename=toybox # Extract filename from path +[type=link] bc=toybox # Basic calculator +[type=link] bunzip2=toybox # Decompress bzip2 files +[type=link] bzcat=toybox # Display contents of bzip2 files +[type=link] cal=toybox # Display calendar +[type=link] chgrp=toybox # Change group ownership +[type=link] chown=toybox # Change file ownership +[type=link] cksum=toybox # Calculate checksums +[type=link] clear=toybox # Clear terminal screen +[type=link] cmp=toybox # Compare files byte by byte +[type=link] comm=toybox # Compare sorted files line by line +[type=link] cpio=toybox # Copy files to/from archives +[type=link] crc32=toybox # Calculate CRC32 checksums +[type=link] cut=toybox # Extract columns from files +[type=link] date=toybox # Display or set system date +[type=link] diff=toybox # Compare files line by line +[type=link] dirname=toybox # Extract directory from path +[type=link] dos2unix=toybox # Convert DOS line endings to Unix +[type=link] du=toybox # Display disk usage +[type=link] egrep=toybox # Extended grep with regular expressions +[type=link] env=toybox # Display or set environment variables +[type=link] expand=toybox # Convert tabs to spaces +[type=link] expr=toybox # Evaluate expressions +[type=link] false=toybox # Return false status +[type=link] fgrep=toybox # Fast grep for fixed strings +[type=link] file=toybox # Determine file type +[type=link] find=toybox # Search for files and directories +[type=link] fmt=toybox # Format text paragraphs +[type=link] groups=toybox # Display user group membership +[type=link] gunzip=toybox # Decompress gzip files +[type=link] gzip=toybox # Compress files with gzip +[type=link] hd=toybox # Hexadecimal dump (alias for hexdump) +[type=link] head=toybox # Display first lines of files +[type=link] hexdump=toybox # Display files in hexadecimal format +[type=link] id=toybox # Display user and group IDs +[type=link] install=toybox # Copy files and set attributes +[type=link] link=toybox # Create hard links +[type=link] logname=toybox # Display login name +[type=link] md5sum=toybox # Calculate MD5 checksums +[type=link] mkfifo=toybox # Create named pipes (FIFOs) +[type=link] mktemp=toybox # Create temporary files/directories +[type=link] more=toybox # Display files page by page +[type=link] mv=toybox # Move/rename files and directories +[type=link] nl=toybox # Number lines in files +[type=link] nohup=toybox # Run commands immune to hangups +[type=link] od=toybox # Dump files in octal format +[type=link] paste=toybox # Merge lines from files +[type=link] patch=toybox # Apply patches to files +[type=link] printenv=toybox # Print environment variables +[type=link] printf=toybox # Format and print data +[type=link] pwd=toybox # Print working directory +[type=link] readlink=toybox # Display target of symbolic links +[type=link] realpath=toybox # Display absolute path +[type=link] rmdir=toybox # Remove empty directories +[type=link] sed=toybox # Stream editor for filtering/transforming text +[type=link] seq=toybox # Generate sequences of numbers +[type=link] sha1sum=toybox # Calculate SHA1 checksums +[type=link] sleep=toybox # Suspend execution for specified time +[type=link] sort=toybox # Sort lines in text files +[type=link] split=toybox # Split files into pieces +[type=link] stat=toybox # Display file/filesystem status +[type=link] strings=toybox # Extract printable strings from files +[type=link] tail=toybox # Display last lines of files +[type=link] tee=toybox # Copy input to files and stdout +[type=link] test=toybox # Evaluate conditional expressions +[type=link] time=toybox # Time command execution +[type=link] timeout=toybox # Run command with time limit +[type=link] touch=toybox # Update file timestamps +[type=link] tr=toybox # Translate or delete characters +[type=link] true=toybox # Return true status +[type=link] truncate=toybox # Truncate files to specified size +[type=link] tty=toybox # Display terminal name +[type=link] uname=toybox # Display system information +[type=link] uniq=toybox # Remove duplicate lines +[type=link] unix2dos=toybox # Convert Unix line endings to DOS +[type=link] unlink=toybox # Remove files (system call interface) +[type=link] uudecode=toybox # Decode uuencoded files +[type=link] uuencode=toybox # Encode files using uuencoding +[type=link] uuidgen=toybox # Generate UUIDs +[type=link] wc=toybox # Count lines, words, and characters +[type=link] which=toybox # Locate commands in PATH +[type=link] whoami=toybox # Display current username +[type=link] xargs=toybox # Execute commands from standard input +[type=link] xxd=toybox # Make hexdump or reverse +[type=link] yes=toybox # Output string repeatedly +[type=link] zcat=toybox # Display contents of compressed files +[type=link] nc=toybox # Netcat for network connections and packet streaming +[type=link] netcat=toybox # Netcat alias for network connections +[type=link] pkill=slay # Process killer Linux compatibility layer + +[type=link] waitfor=on # Create symbolic link: waitfor -> on (waits for resources to become available) +[type=link] ability=on # Create symbolic link: ability -> on (manages process abilities/privileges) +[type=link] /bin/sh=/proc/boot/ksh # Create symbolic link: sh -> ksh (standard shell link) +[type=link] setconf=getconf # Create symbolic link: setconf -> getconf (sets configuration parameters) + +awk # Text processing and pattern scanning +devb-eide # Block device driver for IDE/SATA hard drives and SSDs +devb-ram # RAM disk block device manager +devc-pty # Pseudo-terminal device manager +devc-ser8250 # Serial port driver for 8250/16550 UART controllers (console access) +dhcpcd # DHCP client daemon for automatic network configuration +fsencrypt # File system encryption utility +fsevmgr # File system event manager +getconf # System configuration query utility - retrieves system parameters +hostname # Set or display system hostname +if_up # Network interface configuration +ifconfig # Network interface configuration tool +ksh # Korn shell - provides command-line interface and scripting +ln # Create file links +mkqnx6fs # Create QNX6 file systems +mount # File system mount utility - mounts/unmounts file systems +mount_ifs # Mount Image File System (IFS) +mqueue # POSIX message queue manager +on # Command execution utility - runs commands with specific options +openssl # SSL/TLS cryptographic toolkit +pci-server # PCI bus server +pdebug # Process debugger +pfctl # Packet filter control utility +pidin # Process information display - shows running processes (like 'ps' on Linux) +pipe # Named pipe manager +random # Random number generator service +route # Network routing table management utility +shutdown # System shutdown utility +slay # Process termination utility +slog2info # System log viewer - displays logged messages +slogger2 # System logging daemon - collects and manages log messages +ssh # SSH client for remote connections +ssh-keygen # SSH key generation utility +sshd # SSH daemon for remote access +ptpd2 # IEEE 1588 PTP daemon for the QNX E2E master +/usr/libexec/sshd-session=${QNX_TARGET}/${PROCESSOR}/usr/libexec/sshd-session # SSH session handler (required by sshd) +sync # Synchronize file system buffers to disk +sysctl # Configure kernel parameters at runtime +tar # Archive utility for creating/extracting tar files +tcpdump # Network packet capture tool for Wireshark analysis +umount # Unmount file systems + +/usr/lib/ssh/sftp-server=${QNX_TARGET}/${PROCESSOR}/usr/libexec/sftp-server # File transfer server to enable scp + + +############################################# +### NETWORKING COMPONENTS ### +############################################# +io-sock # Network socket manager +mods-pci.so # PCI module support for network hardware +mods-phy.so # Physical layer module for network interfaces +mods-usb.so # USB module support +libfdt.so.1 # Flattened Device Tree library +libusbdci.so.2 # USB device controller interface library +devs-vtnet_pci.so # VirtIO network device driver for QEMU/KVM +librpc.so.2 # For TCP dump + + +############################################# +### PCI COMPONENTS ### +############################################# +# PCI-related shared libraries and modules +pci/pci_hw-Intel_x86.so # Intel x86 PCI hardware support +pci/pci_slog2.so # PCI system logging support +pci/pci_cap-0x05.so # PCI capability handler for MSI +pci/pci_cap-0x10.so # PCI Express capability handler +pci/pci_cap-0x11.so # MSI-X capability handler +pci/pci_strings.so # PCI device string database +pci/pci_bkwd_compat.so # Backward compatibility support +pci/pci_debug2.so # Enhanced PCI debugging support + + +############################################# +### SYSTEM DIRECTORIES ### +############################################# +# Create SSH and system directories with appropriate permissions +[gid=0 uid=0 dperms=755 type=dir] /var/chroot/sshd # SSH chroot directory for privilege separation +[gid=0 uid=0 dperms=700 type=dir] /var/ssh # SSH configuration and key storage directory +[gid=0 uid=0 dperms=755 type=dir] /usr/bin # Directory for SUT binaries +[gid=0 uid=0 dperms=755 type=dir] /usr/sbin # Directory for system daemons (e.g. ptpd2) + +[perms=0444] pci_server.cfg = { +[buscfg] +DO_BUS_CONFIG=no + +[envars] +PCI_DEBUG_MODULE=pci_debug2.so +PCI_HW_MODULE=pci_hw-Intel_x86.so +PCI_HW_CONFIG_FILE=/proc/boot/pci_hw.cfg +} + +[perms=0444] pci_hw.cfg = { +[interrupts] +B0:D17:F0 A 18 +B0:D17:F0 B 19 +B0:D17:F0 C 16 +B0:D17:F0 D 17 +B3:D0:F0 A 18 +B3:D0:F0 B 19 +B3:D0:F0 C 16 +B3:D0:F0 D 17 +} + +[uid=0 gid=0 perms=0444] qcrypto.conf = { +openssl tags=* +} + +[uid=0 gid=0 perms=0444] /etc/passwd = { +root::0:0:Superuser:/:/bin/sh +qnxuser::1000:1000:QNX User:/:/bin/sh +sshd::15:6:sshd:/:/bin/false +} + +[uid=0 gid=0 perms=0444] /etc/group = { +root::0:root +qnxuser::1000:qnxuser +sshd::6:sshd +} + +[uid=0 gid=0 perms=0444] /etc/profile = { +export TERM=qansi +export PATH=/proc/boot:/usr/bin:/usr/sbin +export LD_LIBRARY_PATH=/proc/boot:/usr/lib +export MAGIC=/system/etc/magic +} + +[uid=0 gid=0 perms=0400] /var/ssh/ssh_host_rsa_key = { +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAYEArR4LKpPGS2mqmnj+d4Y/0DLdYUVQ7hBaR/aYnNbZFUvO3nOFixa4 +u8pjRQbNFsERvY8o3Q1+SEg/xq+WC4vxhS9bjfMOxO29ncicvcDZR9kVOc/dNCLv6amdOy +9V/wN8rb7vy5KhPKF/uubj/HmcwRYZAYV3spR7C4OpN1tSZFBu/rcOYYYC7VxKcshEj8Yt +8QX7Bil+MIJHPLu7HTktgxDSp4Y3NUORoV9In1qM+rTJsxDHr3gfCOaN4OnQElwS7RfXx2 +BFcvA3d4JreynHocpLWUO6rrs1qjCsQZFPqQwBC7SDn8eiKy0XH+yPpioUaJ6L+YFqrk3c +KxijWKP5/aDuCQrw230mLtWatspHpNUXQSN1FQby6kqLUSV8gXX7lS91AMjdb3sqJ9ebxJ +xGARbp8ow1yRtTfqk6H04q2QgVQZ/Idso3TzQokt0K/feEdj7SwH3LsaAeMWPyo0WLUOk1 +8j46LUEayluF4UcIm8dnjoTywW9A/MS1C+2p4eTbAAAFeAU74GcFO+BnAAAAB3NzaC1yc2 +EAAAGBAK0eCyqTxktpqpp4/neGP9Ay3WFFUO4QWkf2mJzW2RVLzt5zhYsWuLvKY0UGzRbB +Eb2PKN0NfkhIP8avlguL8YUvW43zDsTtvZ3InL3A2UfZFTnP3TQi7+mpnTsvVf8DfK2+78 +uSoTyhf7rm4/x5nMEWGQGFd7KUewuDqTdbUmRQbv63DmGGAu1cSnLIRI/GLfEF+wYpfjCC +Rzy7ux05LYMQ0qeGNzVDkaFfSJ9ajPq0ybMQx694HwjmjeDp0BJcEu0X18dgRXLwN3eCa3 +spx6HKS1lDuq67NaowrEGRT6kMAQu0g5/HoistFx/sj6YqFGiei/mBaq5N3CsYo1ij+f2g +7gkK8Nt9Ji7VmrbKR6TVF0EjdRUG8upKi1ElfIF1+5UvdQDI3W97KifXm8ScRgEW6fKMNc +kbU36pOh9OKtkIFUGfyHbKN080KJLdCv33hHY+0sB9y7GgHjFj8qNFi1DpNfI+Oi1BGspb +heFHCJvHZ46E8sFvQPzEtQvtqeHk2wAAAAMBAAEAAAGAQMkziJWQ6fv7Wp/ZK0XUb8f5TU +Oxi8YW40OHzXoh93RNULaOzYSNUcnl6Jko+1D5oKUIt+Eq10Yih+qCDoQquJsGelLxvgTy +py/CaMjZB6hX5zDBKZfBjQJq0xFd73eQmz0PZHHVYWlW8c0imQOyBBiO9yDJsM0cVyzIkO +zeIqhvQWekPB74zXdybQ5BikSyQLbqQF4a2XCH1FS1K7SQMbKEAymZU5eb0nZkKS6r/87U +hOzMrgAYLS6K/hbCRXyrAlz61x2hMKTngb/ERWWilqJSGlF8Q4p2LmIxyUnll/C2cq2dvU +gMARPvZ3DL/QFl4fhLa20vTg83CQNaw6zuEAhTP8lmvDP+4DhtqUPno1T7161OpEHD/ZRb +oiwXmkwg+0yiR8a9OFiolAhwiqPhqC1W39+coEutjpbXQ1uJVoC0kWHMY9biBa7cb9Cxrq +boYLOs31sLhTWTrHgIgpsf/FSWYLF5hknZKpVhWQsfowyCtjn6uvVh+bcfUwOMCDthAAAA +wQDSHJTmW6o6uK53KE6t4c7x+G/qO2Y6FSRQuAbojL1o7gG0SYDFRR3k5fMQ/zqQ1pWrqR +5Im+InsfO5KG9489z/SCIXwPq6zev8WJjZV2P3A8qTBPzEeYmicZtJGH/sBuk8Oj/CdOKV ++GBzkqJdxruiNzLEIGS1gjserT6YJxmA5Q/j1Hiz+bSf66cHhGzmQ092tQ+gGvWPtbFn+D +YQVbp1GW8E6lbAOBew7IDcWp7LVDZuShBrDvFWtsaupZ7wYxoAAADBAOXzYeYNyrKsFCju +Qh1Bj0uVOf8lVvygGOzFA/OdqWuRr/h2aPsNYJFbdIiP61mvVd1umJ8BQDiXNlm/YpXOXu +jOm9a8BVKpMTtoohEkUbqBtv9vx+XHnhLCFYYL4GHJ9Dhj50L3djf++/HkahtZJlTkpSbt +/ukoZKI1m8MgpyC3xk2UDu1yWNizT2l1L7RnE+ZO4b5U9cONANiYBD8jcnZAvh4ld3ojo0 +iNXEVEGAKIodMUsgASxGwmoxuX3ugJIwAAAMEAwLp7eVBjg27eflRNYuW8akfDyq9zB1rp +YGo3GbPytb14WVJuyNL1tJ/B/rdFBg0IXf1FQJEL99kKJP5yDxjOx9kyXBhIWjm8arz39h +uw0wrsuRl2rrCfMABGS51GohtYmKETZ6x7/2n3l6iz/5mhRN8hW0JluJhKtj6ogO6GZERr +PTYwQs+9Q7QI1cPx7G5cEPhWhEd+OmKZuXRjhMQQ+ADj/lnUAhhGQatM9/bohJOdObWCRl +Fu+/MLbJQuA1zpAAAAAAEC +-----END OPENSSH PRIVATE KEY----- +} + +[uid=0 gid=0 perms=0444] /var/ssh/sshd_config = { +Port 22 +AddressFamily inet +ListenAddress 0.0.0.0 +HostKey /var/ssh/ssh_host_rsa_key +Subsystem sftp /usr/lib/ssh/sftp-server +PasswordAuthentication yes +PermitEmptyPasswords yes +PermitRootLogin yes +PermitUserEnvironment yes +StrictModes no +UseDNS no +MaxAuthTries 6 +MaxStartups 100:30:200 +LoginGraceTime 60 +AllowTcpForwarding yes +PidFile none +LogLevel DEBUG +} diff --git a/score/time_slave/tests/reference_integration/tests/__init__.py b/score/time_slave/tests/reference_integration/tests/__init__.py new file mode 100644 index 00000000..ca5de742 --- /dev/null +++ b/score/time_slave/tests/reference_integration/tests/__init__.py @@ -0,0 +1,12 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* diff --git a/score/time_slave/tests/reference_integration/tests/test_time_slave_qnx.py b/score/time_slave/tests/reference_integration/tests/test_time_slave_qnx.py new file mode 100644 index 00000000..1068059d --- /dev/null +++ b/score/time_slave/tests/reference_integration/tests/test_time_slave_qnx.py @@ -0,0 +1,69 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""Reference integration test: TimeSlave configures, builds and boots on QNX. + +Showcase-level smoke test: verifies the QNX binary and JSON config are +packaged into the IFS image, the image boots under QEMU, the expected +network interface exists, and the binary locates and parses the deployed +config file. Full gPTP synchronisation is covered by the SIT. +""" + +import pytest + + +GPTP_IFACE = "vtnet0" + + +def test_config_file_present(target): + """The JSON config is deployed to the expected path.""" + exit_code, output = target.execute( + "ls -l /opt/time_slave/etc/time_slave_config.json" + ) + assert exit_code == 0, f"config file missing: {output!r}" + + +def test_binary_present(target): + """The time_slave binary is deployed and executable.""" + exit_code, output = target.execute("ls -l /opt/time_slave/bin/time_slave") + assert exit_code == 0, f"binary missing: {output!r}" + + +def test_interface_exists(target): + """The expected gPTP network interface is available inside QEMU.""" + exit_code, output = target.execute(f"ifconfig {GPTP_IFACE}") + if exit_code != 0: + decoded = ( + output.decode("utf-8", errors="replace") + if isinstance(output, bytes) + else output + ) + pytest.skip(f"Interface '{GPTP_IFACE}' not available: {decoded}") + + +def test_config_loaded(target): + """The binary locates and parses the deployed JSON config. + + Runs time_slave briefly; the expected failure mode is ClockIdentity + resolution, which proves the config file was read and its iface_name + applied before the gPTP engine attempted initialisation. + """ + cmd = "cd /opt/time_slave && ./bin/time_slave 2>&1 ; true" + _, output = target.execute(cmd) + output_str = ( + output.decode("utf-8", errors="replace") + if isinstance(output, bytes) + else output + ) + assert GPTP_IFACE in output_str, ( + f"Expected iface_name '{GPTP_IFACE}' from config in output, got: {output_str!r}" + ) diff --git a/third_party/json_schema_validator/BUILD b/third_party/json_schema_validator/BUILD new file mode 100644 index 00000000..ca5de742 --- /dev/null +++ b/third_party/json_schema_validator/BUILD @@ -0,0 +1,12 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* diff --git a/third_party/json_schema_validator/json_schema_validator.BUILD b/third_party/json_schema_validator/json_schema_validator.BUILD new file mode 100644 index 00000000..34d94764 --- /dev/null +++ b/third_party/json_schema_validator/json_schema_validator.BUILD @@ -0,0 +1,16 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "json_schema_validator_lib", + srcs = glob(["src/*"]), + hdrs = ["src/nlohmann/json-schema.hpp"], + features = ["third_party_warnings", "-treat_warnings_as_errors"], + includes = ["src"], + deps = ["@nlohmann_json//:json"], +) + +cc_binary( + name = "json_schema_validator", + srcs = ["app/json-schema-validate.cpp"], + deps = [":json_schema_validator_lib"], +)