From c6fba9743e6b3924a3652d72fbcbdf564cbd33bb Mon Sep 17 00:00:00 2001 From: Daniel Piers Date: Tue, 13 Oct 2026 17:03:11 -0700 Subject: [PATCH] Add optional NT98513 MIPS diagnostic tools --- CMakeLists.txt | 6 + tools/nt98513/CMakeLists.txt | 68 +++++++ tools/nt98513/LICENSE | 26 +++ tools/nt98513/README.md | 82 +++++++++ tools/nt98513/cmake/mipsel-linux-gnu.cmake | 6 + tools/nt98513/include/nt98513_client.h | 67 +++++++ tools/nt98513/include/nt98513_control.h | 93 ++++++++++ tools/nt98513/include/nt98513_probe.h | 47 +++++ tools/nt98513/include/nt98513_sensor.h | 44 +++++ tools/nt98513/include/nt98513_sensor_cli.h | 44 +++++ tools/nt98513/include/nt98513_sensor_query.h | 51 ++++++ tools/nt98513/include/nt98513_stream.h | 97 ++++++++++ tools/nt98513/src/nt98513_client.c | 79 ++++++++ tools/nt98513/src/nt98513_client_cli.c | 182 +++++++++++++++++++ tools/nt98513/src/nt98513_client_mips.S | 50 +++++ tools/nt98513/src/nt98513_client_mips.c | 34 ++++ tools/nt98513/src/nt98513_control.c | 130 +++++++++++++ tools/nt98513/src/nt98513_probe.c | 141 ++++++++++++++ tools/nt98513/src/nt98513_probe_mips.S | 49 +++++ tools/nt98513/src/nt98513_probe_mips.c | 21 +++ tools/nt98513/src/nt98513_sensor.c | 34 ++++ tools/nt98513/src/nt98513_sensor_cli.c | 130 +++++++++++++ tools/nt98513/src/nt98513_sensor_cli_mips.c | 14 ++ tools/nt98513/src/nt98513_sensor_query.c | 40 ++++ tools/nt98513/src/nt98513_stream.c | 130 +++++++++++++ 25 files changed, 1665 insertions(+) create mode 100644 tools/nt98513/CMakeLists.txt create mode 100644 tools/nt98513/LICENSE create mode 100644 tools/nt98513/README.md create mode 100644 tools/nt98513/cmake/mipsel-linux-gnu.cmake create mode 100644 tools/nt98513/include/nt98513_client.h create mode 100644 tools/nt98513/include/nt98513_control.h create mode 100644 tools/nt98513/include/nt98513_probe.h create mode 100644 tools/nt98513/include/nt98513_sensor.h create mode 100644 tools/nt98513/include/nt98513_sensor_cli.h create mode 100644 tools/nt98513/include/nt98513_sensor_query.h create mode 100644 tools/nt98513/include/nt98513_stream.h create mode 100644 tools/nt98513/src/nt98513_client.c create mode 100644 tools/nt98513/src/nt98513_client_cli.c create mode 100644 tools/nt98513/src/nt98513_client_mips.S create mode 100644 tools/nt98513/src/nt98513_client_mips.c create mode 100644 tools/nt98513/src/nt98513_control.c create mode 100644 tools/nt98513/src/nt98513_probe.c create mode 100644 tools/nt98513/src/nt98513_probe_mips.S create mode 100644 tools/nt98513/src/nt98513_probe_mips.c create mode 100644 tools/nt98513/src/nt98513_sensor.c create mode 100644 tools/nt98513/src/nt98513_sensor_cli.c create mode 100644 tools/nt98513/src/nt98513_sensor_cli_mips.c create mode 100644 tools/nt98513/src/nt98513_sensor_query.c create mode 100644 tools/nt98513/src/nt98513_stream.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 19dc363..8794e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -526,3 +526,9 @@ if(NOT ONLY_LIBRARY) add_executable(nand_layout_test src/nand_layout_test.c src/nand_layout.c) target_include_directories(nand_layout_test PRIVATE include src) endif() + +# Optional standalone diagnostics; the default does not enable ASM or cross checks. +option(BUILD_NT98513_DIAGNOSTICS "Build source-owned NT98513 Linux MIPS diagnostics" OFF) +if(BUILD_NT98513_DIAGNOSTICS) + add_subdirectory(tools/nt98513) +endif() diff --git a/tools/nt98513/CMakeLists.txt b/tools/nt98513/CMakeLists.txt new file mode 100644 index 0000000..e4748ac --- /dev/null +++ b/tools/nt98513/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.10) + +# Standalone configuration does not need a target libc for compiler checks. +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + project(nt98513_diagnostics C) +endif() + +# The parent intentionally sets global -s/--gc-sections/Release flags. Keep +# these freestanding tools independent without changing the parent's scope. +set(CMAKE_C_FLAGS "") +set(CMAKE_ASM_FLAGS "") +set(CMAKE_EXE_LINKER_FLAGS "") +foreach(_config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${_config}" _upper) + set(CMAKE_C_FLAGS_${_upper} "") + set(CMAKE_ASM_FLAGS_${_upper} "") + set(CMAKE_EXE_LINKER_FLAGS_${_upper} "") +endforeach() + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + NOT CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang)$") + message(FATAL_ERROR "NT98513 diagnostics require a GNU/Clang Linux MIPS little-endian O32 toolchain") +endif() + +set(_nt98513_flags -EL -march=mips32r2 -mabi=32 -msoft-float -mno-abicalls + -fno-pic -G0 -ffreestanding -fno-builtin -fno-stack-protector -O2) +include(CheckCSourceCompiles) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${_nt98513_flags}") +set(CMAKE_REQUIRED_LIBRARIES "") +set(CMAKE_REQUIRED_INCLUDES "") +set(CMAKE_REQUIRED_DEFINITIONS "") +# Check actual compiler macros on every enabled configure, never host-name hints. +unset(NT98513_COMPILER_ABI_OK CACHE) +check_c_source_compiles(" +#if !defined(__mips__) || !defined(__MIPSEL__) || !defined(__mips_soft_float) +#error incompatible target +#endif +#if __mips != 32 || __mips_isa_rev != 2 || _MIPS_SIM != 1 || __SIZEOF_POINTER__ != 4 +#error incompatible MIPS ISA or ABI +#endif +int nt98513_compiler_contract(void) { return 0; } +" NT98513_COMPILER_ABI_OK) +if(NOT NT98513_COMPILER_ABI_OK) + message(FATAL_ERROR "NT98513 diagnostics require a compiler accepting little-endian MIPS32r2 O32 soft-float freestanding flags; use a compatible toolchain file") +endif() +enable_language(ASM) + +set(_nt98513_client src/nt98513_control.c src/nt98513_client.c + src/nt98513_client_cli.c src/nt98513_client_mips.c src/nt98513_client_mips.S) +add_executable(nt98513-descriptor-probe src/nt98513_stream.c src/nt98513_probe.c + src/nt98513_probe_mips.c src/nt98513_probe_mips.S) +add_executable(nt98513-client ${_nt98513_client}) +add_executable(nt98513-sensor ${_nt98513_client} src/nt98513_sensor.c + src/nt98513_sensor_query.c src/nt98513_sensor_cli.c src/nt98513_sensor_cli_mips.c) +target_compile_definitions(nt98513-sensor PRIVATE + NT98513_CLIENT_ENTRY=nt98513_sensor_cli_main) + +foreach(_target nt98513-descriptor-probe nt98513-client nt98513-sensor) + target_include_directories(${_target} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") + target_compile_options(${_target} PRIVATE ${_nt98513_flags} -Wall -Wextra -Werror) + set_target_properties(${_target} PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED YES + C_EXTENSIONS NO LINK_FLAGS "-nostdlib -static -no-pie -Wl,-e,_start,--build-id=none") + install(TARGETS ${_target} RUNTIME DESTINATION bin COMPONENT NT98513Diagnostics) +endforeach() +install(FILES README.md LICENSE DESTINATION share/doc/nt98513-diagnostics + COMPONENT NT98513Diagnostics) diff --git a/tools/nt98513/LICENSE b/tools/nt98513/LICENSE new file mode 100644 index 0000000..ae35a29 --- /dev/null +++ b/tools/nt98513/LICENSE @@ -0,0 +1,26 @@ +MIT License + +Copyright (c) 2026 NT98513 diagnostics project contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This notice covers only the independently written files explicitly included +in this diagnostic source distribution. Existing SPDX identifiers are retained. +It does not license any enclosing firmware dump, vendor SDK, binary, module, +tuning data, compiler, container image or third-party dependency. diff --git a/tools/nt98513/README.md b/tools/nt98513/README.md new file mode 100644 index 0000000..3af6c9e --- /dev/null +++ b/tools/nt98513/README.md @@ -0,0 +1,82 @@ +# Optional NT98513 diagnostics + +These source-owned diagnostics use recovered Linux MIPS O32 interfaces. They +are separate executables, not ipctool hardware detection, a bootable OpenIPC +platform, a capture backend or a service. The twenty C/header/assembly files are +unchanged from the independently reviewed diagnostic source bundle. The full +MIT notice is in LICENSE; no vendor firmware, SDK, module or tuning data is +included. + +## Build + +The parent option `BUILD_NT98513_DIAGNOSTICS` defaults to `OFF`. Leaving it off +adds no diagnostic targets, ASM compiler check, library or download dependency. +The configure commands shown below need CMake 3.13+ (validated with 3.25.1), +a build tool and a compatible GNU or Clang +Linux MIPS compiler with its freestanding stdint/stddef headers. It does not use +Docker, install a compiler or download anything. For an installed GCC in PATH: + +```sh +cmake -S tools/nt98513 -B build-nt98513 \ + -DCMAKE_TOOLCHAIN_FILE=cmake/mipsel-linux-gnu.cmake +cmake --build build-nt98513 +``` + +Use a caller-controlled new build directory. A custom toolchain file may set +compiler paths, compiler target and `CMAKE_SYSROOT` as usual. Target compilation +checks actual compiler macros with explicit little-endian MIPS32r2, O32 and +soft-float flags. Unsupported enabled configurations fail at configuration. +The tools are freestanding, static and have no libc/startup linkage. Their +accepted syscall entries supply `_start`. Local build flags isolate them from +the parent's stripping, garbage collection and optimization flags; the parent +flags and existing targets are not changed. + +A parent build may opt in with `-DBUILD_NT98513_DIAGNOSTICS=ON` and the same +compatible compiler. Unlike the standalone subtree, configuring/building the +parent also needs its existing libc, pthread and other platform dependencies. +A successful subtree build does not establish that those dependencies exist. +The example toolchain selects a compiler; it is not a matching camera BSP. + +## Private staging + +Install only this component to a caller-selected empty private staging root: + +```sh +DESTDIR=/private/work/stage cmake -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_COMPONENT=NT98513Diagnostics \ + -P build-nt98513/cmake_install.cmake +``` + +Standard CMake installation semantics apply: it can replace existing files. +Choose a new private staging root and never use the host root for offline +validation. No install happens during configure or build. Relative destinations +are `bin/nt98513-descriptor-probe`, `bin/nt98513-client`, `bin/nt98513-sensor` +and `share/doc/nt98513-diagnostics/{README.md,LICENSE}` under the prefix. The +component avoids invoking the parent's other install rules; those are unchanged. + +## Operations and limits + +No arguments or `--help` prints usage without device I/O. The three tools are: + +* `nt98513-descriptor-probe --descriptors CHANNEL COUNT TIMEOUT_MS`: descriptor + and range metadata only; no payload mapping, copy or ownership lease. +* `nt98513-client command TEXT`, `get TEXT BYTE_COUNT`, or `set TEXT HEX_BYTES`: + one explicit raw transaction. Arbitrary commands can mutate camera state. +* `nt98513-sensor active`: one fixed query, typed JSON and distinct CMOS_OFF. + The result is mutable RTOS software selection, not fitted hardware/readiness. + +The sensor entry selects `nt98513_sensor_cli_main`. It deliberately retains the +generic client CLI/main and its 40,960-byte workspace because the unchanged +adapter references them. The client and probe select their own existing mains. + +The fixed sensor command clears shared usys staging. Prezeroed reply buffers +have no remote produced-text-length receipt; bounded text is not proof of fresh +complete remote production. Driver calls can block indefinitely; the probe's +nonzero timeout is not a whole-operation deadline. Output may be partial on I/O +failure, and process termination can interrupt cleanup. Compatible kernel/RTOS/ +module initialization, permissions, address semantics and watchdog ownership +remain external prerequisites. Build and isolated missing-device smoke do not +validate target compatibility, cold boot, physical clocks, DMA visibility, +payload lifetime, capture or recovery. No device should be passed into the +offline validation environment. No default command, autostart, module/node setup +or firmware installation is supplied. diff --git a/tools/nt98513/cmake/mipsel-linux-gnu.cmake b/tools/nt98513/cmake/mipsel-linux-gnu.cmake new file mode 100644 index 0000000..818c4ea --- /dev/null +++ b/tools/nt98513/cmake/mipsel-linux-gnu.cmake @@ -0,0 +1,6 @@ +# Example for a compatible GCC already installed in PATH; no download/bootstrap. +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR mips) +set(CMAKE_C_COMPILER mipsel-linux-gnu-gcc) +set(CMAKE_ASM_COMPILER mipsel-linux-gnu-gcc) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) diff --git a/tools/nt98513/include/nt98513_client.h b/tools/nt98513/include/nt98513_client.h new file mode 100644 index 0000000..85844f0 --- /dev/null +++ b/tools/nt98513/include/nt98513_client.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_CLIENT_H +#define NT98513_CLIENT_H +#include "nt98513_control.h" + +/* One synchronous command/config transaction. No automatic command retries, + * readiness checks or initialization. Stock ioctl may wait indefinitely. */ +#define NT98513_CLIENT_DEVICE "/dev/nvt_uctrl" +#define NT98513_CLIENT_OPEN_FLAGS 0x12u +#define NT98513_CLIENT_RESPONSE_SIZE 256u + +/* All callbacks are synchronous, returning negative errno on syscall failure. + * address supplies a non-owning 32-bit target address for the complete buffer; + * it must not allocate resources and all mappings last through the ioctl. + * ioctl success is EXACTLY zero; a positive copy residual is an error. + * close is called once, never retried. write may return a short positive count. + * No native host device adapter is provided. */ +struct nt98513_client_io { + void *context; + int32_t (*address)(void *, const void *, size_t, uint32_t *); + int32_t (*open)(void *, const char *, uint32_t); + int32_t (*ioctl)(void *, int32_t, uint32_t, uint8_t *, size_t); + int32_t (*close)(void *, int32_t); + int32_t (*write)(void *, int32_t, const uint8_t *, size_t); +}; +struct nt98513_client_request { + enum nt98513_control_operation operation; + const char *command; /* NUL terminated, 1..255 bytes before NUL. */ + uint8_t *config; /* Caller owned; GET may modify it even on failure. */ + size_t config_size; +}; +enum nt98513_client_status { + NT98513_CLIENT_OK = 0, NT98513_CLIENT_USAGE = 2, + NT98513_CLIENT_ADDRESS = 3, NT98513_CLIENT_OPEN = 4, + NT98513_CLIENT_IOCTL = 5, NT98513_CLIENT_REMOTE = 6, + NT98513_CLIENT_CLOSE = 7, NT98513_CLIENT_WRITE = 8, + NT98513_CLIENT_RESPONSE = 9 +}; +struct nt98513_client_result { + enum nt98513_client_status status; + enum nt98513_control_status codec_status; + int32_t address_return, open_return, ioctl_return, close_return; + unsigned address_called, open_called, opened, ioctl_called, closed; + unsigned remote_valid, config_valid, response_terminated; + struct nt98513_control_reply reply; + uint8_t response[NT98513_CLIENT_RESPONSE_SIZE]; + size_t response_size; +}; +/* Input, config and result storage must not overlap. Remote status must equal + * zero for success; positive and negative statuses are preserved as failures. + * config_valid describes GET bytes only and requires transport+remote success + * and a terminated response. It does not establish complete command-specific + * fill or meaning: the driver returns the requested byte count with no actual + * produced-length field. GET storage is zeroed before opening the device. + * A later close/output failure does not undo an already executed command. */ +enum nt98513_client_status nt98513_client_run( + const struct nt98513_client_request *, const struct nt98513_client_io *, + struct nt98513_client_result *); + +/* One JSON line for an attempted operation; hex preserves arbitrary bytes. + * --help/no args do not open a device. Syntax: + * command TEXT | get TEXT BYTE_COUNT | set TEXT HEX_BYTES + * GET outputs config bytes only when config_valid. Notifications unsupported. + * Work storage is caller owned and at least NT98513_CONFIG_MAX bytes. */ +int nt98513_client_cli(int argc, const char *const *argv, + const struct nt98513_client_io *, uint8_t *work, size_t work_size); +#endif diff --git a/tools/nt98513/include/nt98513_control.h b/tools/nt98513/include/nt98513_control.h new file mode 100644 index 0000000..07e3f42 --- /dev/null +++ b/tools/nt98513/include/nt98513_control.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_CONTROL_H +#define NT98513_CONTROL_H + +#include +#include + +/* Independently written byte codec for the observed August 828 transport. + * REA ev_3a2c1745a2b552d76523d83e66e0aa8de1470ec81c378173b6b1c1f0da626dfd + * and evidence/nvt-uctrl.asm (.text+0xbc dispatcher) establish these records. + * Addresses are target-process 32-bit virtual addresses, never host pointers. + * This API does not execute ioctls. Command/config ioctls can block indefinitely + * in the stock kernel's wait_for_completion; there is no timeout record field. + */ +#define NT98513_CONTROL_SIZE 28u +#define NT98513_NOTIFY_SIZE 12u +#define NT98513_COMMAND_MAX 255u +#define NT98513_CONFIG_MAX 40960u +#define NT98513_VENC_SIZE 324u +#define NT98513_VENC_WORDS 81u + +enum nt98513_control_status { + NT98513_CONTROL_OK = 0, + NT98513_CONTROL_ARGUMENT = -1, + NT98513_CONTROL_SIZE_ERROR = -2, + NT98513_CONTROL_OPERATION = -3, + NT98513_CONTROL_RANGE = -4, + NT98513_CONTROL_LIMIT = -5, + NT98513_CONTROL_IOCTL = -6 +}; + +enum nt98513_control_operation { + NT98513_SEND_COMMAND = 1, + NT98513_GET_CONFIG, + NT98513_SET_CONFIG, + NT98513_GET_NOTIFICATION, + NT98513_GET_RAW_NOTIFICATION +}; + +struct nt98513_control_request { + uint32_t command_address; + uint32_t command_length; /* 1..255 bytes, excludes NUL; driver appends NUL. */ + uint32_t config_address; + uint32_t config_length; /* 1..40960 for GET/SET; otherwise both zero. */ + uint32_t response_address; + uint32_t response_capacity; +}; + +/* All words preserved. Count is 7 or 3; unused words in notify replies are zero. + * A successful parse does NOT imply successful remote command execution. + * Inspect remote_status separately. Notify word 2 is capacity for text, or the + * updated raw length for raw notifications. Text response length is not given. + */ +struct nt98513_control_reply { + uint32_t words[7]; + uint32_t word_count; + int32_t remote_status; +}; + +/* Outputs unchanged on failure. Input/output storage must not overlap. + * Numeric span checks establish no process mapping/permissions. Optional reply + * address/capacity must both be zero or both nonzero; notifications require both. + * Capacities are limited to INT32_MAX to avoid driver's signed-length paths. + */ +enum nt98513_control_status nt98513_control_prepare( + enum nt98513_control_operation operation, uint8_t *wire, size_t size, + const struct nt98513_control_request *request, uint32_t *ioctl_request); + +/* Reject any nonzero ioctl return, including positive copy residuals; errno is + * managed by the caller. Returned remote errors remain successful decodes. + */ +enum nt98513_control_status nt98513_control_parse_reply( + enum nt98513_control_operation operation, const uint8_t *wire, size_t size, + int ioctl_return, struct nt98513_control_reply *reply); + +/* Read-only view; preserve unknown words and never synthesize a setting blob. + * Geometry may represent source size when independent ISP crop is active. + * frame_delay_divisor is the word used by exec_venc_run's 1000000/divisor delay, + * not a measured frame rate. Its meaning is known only for rate modes 1..4. + * REA ev_4092b6c3efecbd6bf052e9720fa906bd9827ff1d4eb36f2be9cbdf2e494ebd34; + * see research/control-transport.md for function-boundary qualifications. + */ +struct nt98513_venc_settings { + uint32_t words[NT98513_VENC_WORDS]; + uint32_t input_source, codec, profile, width, height, rate_mode; + uint32_t frame_delay_divisor; + int frame_delay_divisor_known; +}; + +enum nt98513_control_status nt98513_venc_settings_decode( + const uint8_t *wire, size_t size, struct nt98513_venc_settings *settings); + +#endif diff --git a/tools/nt98513/include/nt98513_probe.h b/tools/nt98513/include/nt98513_probe.h new file mode 100644 index 0000000..5bd1b04 --- /dev/null +++ b/tools/nt98513/include/nt98513_probe.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_PROBE_H +#define NT98513_PROBE_H +#include "nt98513_stream.h" + +#define NT98513_PROBE_DEVICE "/dev/nvt_stream_sender" +/* Probe policy limits, not silicon or driver limits. */ +#define NT98513_PROBE_MAX_COUNT 1000u +#define NT98513_PROBE_MAX_TIMEOUT_MS 60000u +#define NT98513_PROBE_OPEN_FLAGS 0u + +/* Normalized syscall contract: negative errno on error. ioctl must return + * exactly zero; a positive copy residual is not success. close is called once + * and never retried. write may return a positive short count. No payload I/O. */ +struct nt98513_probe_io { + void *context; + int32_t (*open)(void *, const char *, uint32_t); + int32_t (*ioctl)(void *, int32_t, uint32_t, uint8_t *, size_t); + int32_t (*write)(void *, int32_t, const uint8_t *, size_t); + int32_t (*close)(void *, int32_t); +}; +struct nt98513_probe_options { uint32_t channel, count, timeout_ms; }; +enum nt98513_probe_status { + NT98513_PROBE_OK = 0, NT98513_PROBE_USAGE = 2, + NT98513_PROBE_OPEN = 3, NT98513_PROBE_RANGE = 4, + NT98513_PROBE_STREAM = 5, NT98513_PROBE_DESCRIPTOR = 6, + NT98513_PROBE_RANGE_CHANGED = 7, NT98513_PROBE_WRITE = 8, + NT98513_PROBE_CLOSE = 9, NT98513_PROBE_TIMEOUT = 10 +}; +struct nt98513_probe_result { + enum nt98513_probe_status status; + int32_t io_return, close_return; + uint32_t received, emitted; +}; +/* Finite request count and nonzero timeout, NOT a wall-clock guarantee. + * Output includes all raw descriptor bytes, range checks and sequence + * observations; these do not establish atomic metadata or payload lifetime. + * The range is re-read after each descriptor; a detected change fails closed. + * Equal observations cannot exclude a change-and-change-back race. */ +enum nt98513_probe_status nt98513_probe_run( + const struct nt98513_probe_options *, const struct nt98513_probe_io *, + struct nt98513_probe_result *); +/* No args/--help writes usage only. Explicit syntax: + * --descriptors CHANNEL COUNT TIMEOUT_MS (decimal, within policy above). */ +int nt98513_probe_cli(int argc, const char *const *argv, + const struct nt98513_probe_io *io); +#endif diff --git a/tools/nt98513/include/nt98513_sensor.h b/tools/nt98513/include/nt98513_sensor.h new file mode 100644 index 0000000..86b0117 --- /dev/null +++ b/tools/nt98513/include/nt98513_sensor.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_SENSOR_H +#define NT98513_SENSOR_H + +#include "nt98513_control.h" + +/* Library semantic values, NOT stock registry IDs or persisted fw_resv_0. */ +enum nt98513_sensor_selection { + NT98513_SENSOR_OFF = 0, + NT98513_SENSOR_OS05A10M = 1, + NT98513_SENSOR_GC2053M = 2, + NT98513_SENSOR_JXK02M = 3, + NT98513_SENSOR_JXK03M = 4 +}; + +struct nt98513_sensor_active { + uint32_t logical_index; + enum nt98513_sensor_selection selection; +}; + +/* Pure strict validation of caller-owned reply content, including exactly + * one final NUL. The only accepted complete strings are: + * "0 CMOS_OFF success", "0 CMOS_OS05A10M success", + * "0 CMOS_GC2053M success", "0 CMOS_JXK02M success", + * "0 CMOS_JXK03M success". logical_index is always 0. + * + * reply_size is actual content length INCLUDING NUL, never buffer capacity. + * The caller must first locate that NUL within its valid owned bounded span. + * A nonnull reply describes a valid immutable byte span of reply_size bytes; + * out is a valid writable object disjoint from that span. Unaligned input is + * allowed; no overlap/concurrency detection. Null reply/out -> ARGUMENT; + * size outside 1..256 -> SIZE_ERROR before any input read; any unmatched + * byte string -> RANGE. All failures leave out and input bytes unchanged. + * + * OFF is successfully recognized NOT_SELECTED, never a usable selection. + * Another name proves only parsed RTOS software selection, not chip identity, + * probe success, preset compatibility, applied mode, or readiness. No borrowed + * views or firmware pointers are returned. No heap, I/O, commands or fallback. + * Original selection/probe limits: research/rtos-sensor-selection.md. + */ +enum nt98513_control_status nt98513_sensor_active_parse( + const void *reply, size_t reply_size, struct nt98513_sensor_active *out); + +#endif diff --git a/tools/nt98513/include/nt98513_sensor_cli.h b/tools/nt98513/include/nt98513_sensor_cli.h new file mode 100644 index 0000000..70b6723 --- /dev/null +++ b/tools/nt98513/include/nt98513_sensor_cli.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_SENSOR_CLI_H +#define NT98513_SENSOR_CLI_H +#include "nt98513_sensor_query.h" +enum nt98513_sensor_cli_exit { + NT98513_SENSOR_CLI_OK = 0, + NT98513_SENSOR_CLI_NOT_SELECTED = 1, + NT98513_SENSOR_CLI_USAGE = 2, + NT98513_SENSOR_CLI_CLIENT_ERROR = 3, + NT98513_SENSOR_CLI_REPLY_ERROR = 4, + NT98513_SENSOR_CLI_WRITE_ERROR = 8 +}; +#define NT98513_SENSOR_CLI_WRITE_MAX 128u +/* Standalone portable presentation; external I/O is entirely injected. + * Syntax: nt98513-sensor active. argc 0/1 or exact --help emits + * "usage: nt98513-sensor active\n" to stdout, exit0, no device callbacks. + * Any other syntax emits that usage to stderr, exit2, no device callbacks. + * Missing io/write -> exit8 before any output/query. argv strings and io must + * be valid immutable caller objects when inspected; argv[0] is ignored. + * + * Exact active invokes actual nt98513_sensor_active_query once. Its structural + * ARGUMENT outcome emits null unavailable diagnostic fields without reading + * its untouched diagnostic object. Other outcomes preserve owned diagnostics. + * JSON is one compact line with fixed key order documented in the literal + * fixture contract; selection is null on all errors, a three-field object + * {logical_index,value,name} on OK/NOT_SELECTED. Names are fixed library strings. + * No field denotes physical readiness. Numeric status values remain distinct + * from the nonnegative shell exits above. + * + * All query/close work finishes before output. Chunks are at most128 bytes; + * writes must return 1..requested bytes. Zero/negative/overlarge -> exit8, + * no retry or further output. A partial JSON record may already have escaped. + * Output failure does not undo or repeat an already attempted transaction. + * The command, lower client/codec/parser and generic CLI are not altered. + * + * Stock usys clears shared staging; a name may reflect a failed earlier probe. + * Prezeroed response tails and no produced length limit publication/freshness + * claims. Stock ioctl may block indefinitely; watchdog, BSP, readiness, + * acquisition/retention, capture and recovery remain outside this operation. + */ +int nt98513_sensor_cli(int argc,const char *const *argv, + const struct nt98513_client_io *io); +#endif + diff --git a/tools/nt98513/include/nt98513_sensor_query.h b/tools/nt98513/include/nt98513_sensor_query.h new file mode 100644 index 0000000..4fc6140 --- /dev/null +++ b/tools/nt98513/include/nt98513_sensor_query.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_SENSOR_QUERY_H +#define NT98513_SENSOR_QUERY_H +#include "nt98513_client.h" +#include "nt98513_sensor.h" + +enum nt98513_sensor_query_status { + NT98513_SENSOR_QUERY_OK = 0, + NT98513_SENSOR_QUERY_NOT_SELECTED = 1, + NT98513_SENSOR_QUERY_ARGUMENT = -1, + NT98513_SENSOR_QUERY_CLIENT_ERROR = -2, + NT98513_SENSOR_QUERY_REPLY_ERROR = -3 +}; +struct nt98513_sensor_query_diagnostic { + struct nt98513_client_result client; + unsigned parse_called; + enum nt98513_control_status parser_status; +}; +/* One synchronous SEND_COMMAND "usys -sensor_active 0", with no config. + * Uses the actual client and strict parser. No heap, adapter, CLI, retry, + * polling, sensor probe, selection change, preset or readiness operation. + * + * io, selection and diagnostic must be valid disjoint caller-owned objects. + * Input/callback state and output objects must not overlap, and callbacks + * must obey nt98513_client_io's synchronous mapping/lifetime contract. No + * overlap or concurrency detection. context may be NULL when callbacks allow + * it; write is optional and never called. address/open/ioctl/close required. + * NULL arguments or missing required callbacks -> ARGUMENT, no I/O, all + * supplied outputs unchanged. After valid arguments diagnostic records the + * actual attempted transaction, including failures; parse_called is zero + * and parser_status is ARGUMENT until strict parsing is called. + * + * Selection is published only after client success INCLUDING close, exact + * zero ioctl/remote status, valid bounded terminated response, and successful + * parsing of response_size+1 bytes. OK publishes a named software selection; + * NOT_SELECTED publishes OFF. Every error leaves selection unchanged. + * Diagnostic owns the raw client result; no borrowed buffer views returned. + * Parser status is meaningful only when parse_called is nonzero. + * + * This query clears shared RTOS usys staging and is not globally read-only. + * A returned name may reflect a failed earlier probe, not fitted hardware. + * Client prezeroing plus no wire produced-text length means a found NUL does + * not prove the remote wrote a complete reply. ioctl may block indefinitely; + * watchdog ownership remains an operational dependency. No physical sensor, + * mode/preset compatibility, buffer lease, capture or recovery is established. + */ +enum nt98513_sensor_query_status nt98513_sensor_active_query( + const struct nt98513_client_io *io, + struct nt98513_sensor_active *selection, + struct nt98513_sensor_query_diagnostic *diagnostic); +#endif diff --git a/tools/nt98513/include/nt98513_stream.h b/tools/nt98513/include/nt98513_stream.h new file mode 100644 index 0000000..aa22cfa --- /dev/null +++ b/tools/nt98513/include/nt98513_stream.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef NT98513_STREAM_H +#define NT98513_STREAM_H + +#include +#include + +/* Offline ABI helpers, not an encoder or hardware driver. The byte contracts + * below are observed in August mipc (SHA-256 4caa14dec2bcdf2316440f065a69d3de + * 3438ff139841aa003b7c3fcfdced412c). See research/media-findings.md and retained + * evidence/rea-stream-interface.json: + * GetVStream ev_443ac7f8bc202fae3dac5a9235592eb9902457380d37e161963314b75f9cca16 + * Open ev_ac83c2e7c6bbb1d27112524db891e0085ce562303658f05ac908b46e114a2a60 + * FlushCache ev_e54b438eca199ae6349a44c2d7181585b886bdb91ded82048333d8725e8af004 + * Release ev_3721eaa692bd1580cbb517415dbce7fd8b64de8b9e74d0669a9b8cf96a54b328 + * The independent kernel review in research/stream-driver-review.md resolves + * GETITEM type, millisecond timeout, cursors and the complete ioctl dispatcher. + * There is no payload-release ioctl in that dispatcher; RTOS reuse is unknown. + * None of these helpers accesses a device, shared memory, or mapped payload. + */ + +#define NT98513_STREAM_BYTES 56u +#define NT98513_RANGE_BYTES 8u +#define NT98513_CHANNEL_COUNT 16u + +/* Observed MIPS constants, deliberately independent of host _IO* macros. */ +#define NT98513_IOCTL_GET_STREAM UINT32_C(0x40385301) +#define NT98513_IOCTL_GET_BUFFER_RANGE UINT32_C(0x40085303) +#define NT98513_IOCTL_FLUSH_CACHE UINT32_C(0x80084310) + +enum nt98513_status { + NT98513_OK = 0, + NT98513_ERR_ARGUMENT = -1, + NT98513_ERR_SIZE = -2, + NT98513_ERR_CHANNEL = -3, + NT98513_ERR_EMPTY = -4, + NT98513_ERR_RANGE = -5 +}; + +/* Host representation only: never pass this struct directly to ioctl. Unknown + * words and the complete metadata word are preserved. Historic opaque/raw + * member names remain stable. Kernel GETITEM uses opaque_00 as type (0 video, + * nonzero audio), timeout_raw as milliseconds, and zero-extends two 32-bit + * timestamp inputs into the slots ending at opaque_2c/opaque_34. Timestamp + * units, metadata enums and RTOS payload lifetime remain unresolved. + */ +struct nt98513_stream_descriptor { + uint32_t opaque_00; + uint32_t channel; + uint32_t timeout_raw; + uint32_t opaque_0c; + uint32_t sequence_low; + uint32_t sequence_high; + uint32_t payload_address; + uint32_t payload_length; + uint32_t metadata; + uint32_t opaque_24; + uint32_t timestamp_0; + uint32_t opaque_2c; + uint32_t timestamp_1; + uint32_t opaque_34; +}; + +struct nt98513_buffer_range { + uint32_t address; + uint32_t length; +}; + +/* All wire buffers use little-endian bytes and require exactly the stated + * size. Null arguments fail. Errors leave output objects/buffers unchanged. + * Request selects video (type 0), channel 0..15. Timeout is milliseconds; + * zero means an indefinite interruptible GETITEM wait, not a nonblocking poll. + * This byte codec performs no waiting and imposes no additional timeout policy. + */ +enum nt98513_status nt98513_stream_request(uint8_t *wire, size_t size, + uint32_t channel, uint32_t timeout_raw); +enum nt98513_status nt98513_stream_decode(const uint8_t *wire, size_t size, + struct nt98513_stream_descriptor *out); +enum nt98513_status nt98513_buffer_range_decode(const uint8_t *wire, size_t size, + struct nt98513_buffer_range *out); + +/* Validates the full nonempty payload span against both the device range and + * actual mapping length, returning only a byte offset. A mapping may be longer + * than the device range but never shorter. Range endpoints may equal 2^32; + * addresses and lengths are each 32-bit and must not wrap past that endpoint. + * No pointer is constructed or payload read, and cache coherency is not proved. + */ +enum nt98513_status nt98513_stream_payload_offset( + const struct nt98513_buffer_range *range, + const struct nt98513_stream_descriptor *stream, + size_t mapped_size, size_t *offset); + +/* Builds the observed cache request {target_virtual_address, length}. */ +enum nt98513_status nt98513_cache_request(uint8_t *wire, size_t size, + uint32_t address, uint32_t length); + +#endif diff --git a/tools/nt98513/src/nt98513_client.c b/tools/nt98513/src/nt98513_client.c new file mode 100644 index 0000000..81f9255 --- /dev/null +++ b/tools/nt98513/src/nt98513_client.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_client.h" +static void zero(void *p,size_t n) +{ + volatile uint8_t *b=p; + while(n--) *b++=0; +} + +enum nt98513_client_status nt98513_client_run( + const struct nt98513_client_request *request, const struct nt98513_client_io *io, + struct nt98513_client_result *result) +{ + struct nt98513_control_request raw; + uint8_t wire[NT98513_CONTROL_SIZE]; + uint32_t number; + size_t length=0; + if(result==NULL) return NT98513_CLIENT_USAGE; + zero(result,sizeof(*result)); + result->status=NT98513_CLIENT_USAGE; + if(request==NULL || io==NULL || request->command==NULL || + io->address==NULL || io->open==NULL || io->ioctl==NULL || io->close==NULL) + return result->status; + if(request->operation!=NT98513_SEND_COMMAND && + request->operation!=NT98513_GET_CONFIG && request->operation!=NT98513_SET_CONFIG) + return result->status; + while(length<=NT98513_COMMAND_MAX && request->command[length]) ++length; + if(length==0 || length>NT98513_COMMAND_MAX) return result->status; + if(request->operation==NT98513_SEND_COMMAND) { + if(request->config!=NULL || request->config_size!=0) return result->status; + } else if(request->config==NULL || request->config_size==0 || + request->config_size>NT98513_CONFIG_MAX) return result->status; + zero(&raw,sizeof(raw)); + raw.command_length=(uint32_t)length; + raw.config_length=(uint32_t)request->config_size; + raw.response_capacity=NT98513_CLIENT_RESPONSE_SIZE; + result->status=NT98513_CLIENT_ADDRESS; + result->address_called=1; + result->address_return=io->address(io->context,request->command,length,&raw.command_address); + if(result->address_return!=0) return result->status; + if(request->config_size) { + result->address_return=io->address(io->context,request->config, + request->config_size,&raw.config_address); + if(result->address_return!=0) return result->status; + } + result->address_return=io->address(io->context,result->response, + sizeof(result->response),&raw.response_address); + if(result->address_return!=0) return result->status; + result->codec_status=nt98513_control_prepare(request->operation,wire,sizeof(wire),&raw,&number); + if(result->codec_status!=NT98513_CONTROL_OK) return result->status; + if(request->operation==NT98513_GET_CONFIG) zero(request->config,request->config_size); + result->open_called=1; + result->open_return=io->open(io->context,NT98513_CLIENT_DEVICE,NT98513_CLIENT_OPEN_FLAGS); + if(result->open_return<0) { + result->status=NT98513_CLIENT_OPEN; + return result->status; + } + result->opened=1; + result->ioctl_called=1; + result->ioctl_return=io->ioctl(io->context,result->open_return,number,wire,sizeof(wire)); + result->codec_status=nt98513_control_parse_reply(request->operation,wire,sizeof(wire), + result->ioctl_return,&result->reply); + if(result->codec_status!=NT98513_CONTROL_OK) result->status=NT98513_CLIENT_IOCTL; + else { + result->remote_valid=1; + while(result->response_sizeresponse) && + result->response[result->response_size]) ++result->response_size; + result->response_terminated=result->response_sizeresponse); + result->status=result->reply.remote_status==0?NT98513_CLIENT_OK:NT98513_CLIENT_REMOTE; + if(result->response_size==sizeof(result->response)) { + if(result->status==NT98513_CLIENT_OK) result->status=NT98513_CLIENT_RESPONSE; + } else if(result->status==NT98513_CLIENT_OK && request->operation==NT98513_GET_CONFIG) + result->config_valid=1; + } + result->close_return=io->close(io->context,result->open_return); + result->closed=1; + if(result->close_return!=0 && result->status==NT98513_CLIENT_OK) + result->status=NT98513_CLIENT_CLOSE; + return result->status; +} diff --git a/tools/nt98513/src/nt98513_client_cli.c b/tools/nt98513/src/nt98513_client_cli.c new file mode 100644 index 0000000..cfab34a --- /dev/null +++ b/tools/nt98513/src/nt98513_client_cli.c @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_client.h" + +/* This CLI has no device adapter and uses no libc. The caller supplies all I/O. + * Writes are bounded and require positive progress. A write failure never + * repeats the transaction (which may already have changed remote state). */ +#define OUTPUT_CHUNK 128u +struct output { + const struct nt98513_client_io *io; + int32_t fd; + int failed; +}; +static int same(const char *a,const char *b) +{ + if(a==NULL || b==NULL) return 0; + while(*a && *a==*b) { ++a; ++b; } + return *a==*b; +} +static size_t text_size(const char *s) +{ + size_t n=0; + while(s[n]) ++n; + return n; +} +static void bytes(struct output *out,const uint8_t *p,size_t n) +{ + while(n && !out->failed) { + size_t count=n>OUTPUT_CHUNK?OUTPUT_CHUNK:n; + int32_t written=out->io->write(out->io->context,out->fd,p,count); + if(written<=0 || (uint32_t)written>count) { + out->failed=1; + return; + } + p+=(uint32_t)written; + n-=(uint32_t)written; + } +} +static void text(struct output *out,const char *s) +{ + bytes(out,(const uint8_t *)s,text_size(s)); +} +static void number(struct output *out,int32_t value) +{ + uint8_t buffer[11]; + size_t begin=sizeof(buffer); + uint32_t magnitude=(uint32_t)value; + if(value<0) magnitude=0u-magnitude; + do { + buffer[--begin]=(uint8_t)('0'+magnitude%10u); + magnitude/=10u; + } while(magnitude); + if(value<0) buffer[--begin]='-'; + bytes(out,buffer+begin,sizeof(buffer)-begin); +} +static void boolean(struct output *out,unsigned value) +{ + text(out,value?"true":"false"); +} +static void hex(struct output *out,const uint8_t *p,size_t size) +{ + static const char alphabet[]="0123456789abcdef"; + uint8_t buffer[OUTPUT_CHUNK]; + while(size && !out->failed) { + size_t n=size>OUTPUT_CHUNK/2?OUTPUT_CHUNK/2:size; + size_t i; + for(i=0;i>4]; + buffer[i*2+1]=(uint8_t)alphabet[p[i]&15u]; + } + bytes(out,buffer,n*2); + p+=n; size-=n; + } +} +static int nibble(char c) +{ + if(c>='0' && c<='9') return c-'0'; + if(c>='a' && c<='f') return c-'a'+10; + if(c>='A' && c<='F') return c-'A'+10; + return -1; +} +static int decimal(const char *s,size_t *value) +{ + size_t n=0; + if(s==NULL || *s==0) return 0; + while(*s) { + unsigned digit; + if(*s<'0' || *s>'9') return 0; + digit=(unsigned)(*s-'0'); + if(n>(NT98513_CONFIG_MAX-digit)/10u) return 0; + n=n*10u+digit; ++s; + } + if(n==0) return 0; + *value=n; + return 1; +} +static int hex_size(const char *s,size_t *size) +{ + size_t n=0; + if(s==NULL) return 0; + while(s[n]) { + if(n>=NT98513_CONFIG_MAX*2u || nibble(s[n])<0) return 0; + ++n; + } + if(n==0 || n%2u) return 0; + *size=n/2u; + return 1; +} +static int command_valid(const char *s) +{ + size_t n=0; + if(s==NULL) return 0; + while(n<=NT98513_COMMAND_MAX && s[n]) ++n; + return n>0 && n<=NT98513_COMMAND_MAX; +} +static int usage(struct output *out,int status) +{ + out->fd=status==0?1:2; + text(out,"usage: nt98513-client command TEXT | get TEXT BYTE_COUNT | set TEXT HEX_BYTES\n"); + return out->failed?NT98513_CLIENT_WRITE:status; +} +static void outcome(struct output *out,const struct nt98513_client_result *r, + const struct nt98513_client_request *request) +{ + size_t response_size=r->response_size; + if(response_size>sizeof(r->response)) response_size=sizeof(r->response); + text(out,"{\"status\":"); number(out,(int32_t)r->status); + text(out,",\"codec_status\":"); number(out,(int32_t)r->codec_status); + text(out,",\"address_return\":"); number(out,r->address_return); + text(out,",\"address_called\":"); boolean(out,r->address_called); + text(out,",\"open_return\":"); number(out,r->open_return); + text(out,",\"open_called\":"); boolean(out,r->open_called); + text(out,",\"opened\":"); boolean(out,r->opened); + text(out,",\"ioctl_return\":"); number(out,r->ioctl_return); + text(out,",\"ioctl_called\":"); boolean(out,r->ioctl_called); + text(out,",\"close_return\":"); number(out,r->close_return); + text(out,",\"closed\":"); boolean(out,r->closed); + text(out,",\"remote_valid\":"); boolean(out,r->remote_valid); + text(out,",\"remote_status\":"); + if(r->remote_valid) number(out,r->reply.remote_status); + else text(out,"null"); + text(out,",\"response_hex\":\""); hex(out,r->response,response_size); + text(out,"\",\"response_terminated\":"); boolean(out,r->response_terminated); + text(out,",\"requested_config_length\":"); number(out,(int32_t)request->config_size); + text(out,",\"config_valid\":"); boolean(out,r->config_valid); + if(request->operation==NT98513_GET_CONFIG && r->config_valid) { + text(out,",\"config_hex\":\""); hex(out,request->config,request->config_size); + text(out,"\""); + } + text(out,"}\n"); +} +int nt98513_client_cli(int argc,const char *const *argv, + const struct nt98513_client_io *io,uint8_t *work,size_t work_size) +{ + struct nt98513_client_request request; + struct nt98513_client_result result; + struct output out; + size_t config_size=0,i; + int status; + if(io==NULL || io->write==NULL) return NT98513_CLIENT_WRITE; + out.io=io; out.fd=1; out.failed=0; + if(argc==0 || argc==1 || + (argc==2 && argv!=NULL && same(argv[1],"--help"))) return usage(&out,0); + if(argc<0 || argv==NULL || argc<3 || argc>4 || + work==NULL || work_sizeaddress==NULL || io->open==NULL || io->ioctl==NULL || io->close==NULL || + !command_valid(argv[2])) return usage(&out,NT98513_CLIENT_USAGE); + if(same(argv[1],"command") && argc==3) request.operation=NT98513_SEND_COMMAND; + else if(same(argv[1],"get") && argc==4 && decimal(argv[3],&config_size)) + request.operation=NT98513_GET_CONFIG; + else if(same(argv[1],"set") && argc==4 && hex_size(argv[3],&config_size)) + request.operation=NT98513_SET_CONFIG; + else return usage(&out,NT98513_CLIENT_USAGE); + /* Finish validating all input before decoding or invoking the core. */ + request.command=argv[2]; request.config_size=config_size; + request.config=config_size?work:NULL; + if(request.operation==NT98513_SET_CONFIG) + for(i=0;iUINT32_MAX-start) + return -22; + *address=start; + return 0; +} +static int32_t device_open(void *ctx,const char *path,uint32_t flags) +{ (void)ctx; return nt98513_client_syscall3(4005,(uint32_t)(uintptr_t)path,flags,0); } +static int32_t device_ioctl(void *ctx,int32_t fd,uint32_t request,uint8_t *bytes,size_t size) +{ (void)ctx; (void)size; return nt98513_client_syscall3(4054,(uint32_t)fd,request,(uint32_t)(uintptr_t)bytes); } +static int32_t device_close(void *ctx,int32_t fd) +{ (void)ctx; return nt98513_client_syscall3(4006,(uint32_t)fd,0,0); } +static int32_t output_write(void *ctx,int32_t fd,const uint8_t *bytes,size_t size) +{ (void)ctx; return nt98513_client_syscall3(4004,(uint32_t)fd,(uint32_t)(uintptr_t)bytes,(uint32_t)size); } +const struct nt98513_client_io nt98513_client_mips_io={ + 0,buffer_address,device_open,device_ioctl,device_close,output_write +}; +int nt98513_client_main(int argc,const char *const *argv) +{ + static uint8_t workspace[NT98513_CONFIG_MAX]; + return nt98513_client_cli(argc,argv,&nt98513_client_mips_io,workspace,sizeof(workspace)); +} diff --git a/tools/nt98513/src/nt98513_control.c b/tools/nt98513/src/nt98513_control.c new file mode 100644 index 0000000..20567dd --- /dev/null +++ b/tools/nt98513/src/nt98513_control.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_control.h" + +static uint32_t read_le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void write_le32(uint8_t *p, uint32_t value) +{ + p[0]=(uint8_t)value; p[1]=(uint8_t)(value>>8); + p[2]=(uint8_t)(value>>16); p[3]=(uint8_t)(value>>24); +} + +static size_t operation_size(enum nt98513_control_operation op) +{ + switch (op) { + case NT98513_SEND_COMMAND: + case NT98513_GET_CONFIG: + case NT98513_SET_CONFIG: return NT98513_CONTROL_SIZE; + case NT98513_GET_NOTIFICATION: + case NT98513_GET_RAW_NOTIFICATION: return NT98513_NOTIFY_SIZE; + default: return 0; + } +} + +static int valid_span(uint32_t address, uint32_t length) +{ + return address != 0 && length != 0 && length-1 <= UINT32_MAX-address; +} + +enum nt98513_control_status nt98513_control_prepare( + enum nt98513_control_operation op, uint8_t *wire, size_t size, + const struct nt98513_control_request *request, uint32_t *command) +{ + size_t required = operation_size(op), i; + uint32_t number; + if (wire == NULL || request == NULL || command == NULL) + return NT98513_CONTROL_ARGUMENT; + if (required == 0) return NT98513_CONTROL_OPERATION; + if (size != required) return NT98513_CONTROL_SIZE_ERROR; + if (required == NT98513_CONTROL_SIZE) { + if (request->command_length == 0 || request->command_length > NT98513_COMMAND_MAX) + return NT98513_CONTROL_LIMIT; + if (!valid_span(request->command_address,request->command_length)) + return NT98513_CONTROL_RANGE; + if (op == NT98513_SEND_COMMAND) { + if (request->config_address != 0 || request->config_length != 0) + return NT98513_CONTROL_ARGUMENT; + } else { + if (request->config_length == 0 || request->config_length > NT98513_CONFIG_MAX) + return NT98513_CONTROL_LIMIT; + if (!valid_span(request->config_address,request->config_length)) + return NT98513_CONTROL_RANGE; + } + } else if (request->command_address != 0 || request->command_length != 0 || + request->config_address != 0 || request->config_length != 0) { + return NT98513_CONTROL_ARGUMENT; + } + /* Keep capacities positive in the driver's signed comparison paths. */ + if (request->response_capacity > INT32_MAX) return NT98513_CONTROL_LIMIT; + if (request->response_address != 0 || request->response_capacity != 0 || + required == NT98513_NOTIFY_SIZE) { + if (!valid_span(request->response_address,request->response_capacity)) + return NT98513_CONTROL_RANGE; + } + switch (op) { + case NT98513_SEND_COMMAND: number=UINT32_C(0xc01c552d); break; + case NT98513_GET_CONFIG: number=UINT32_C(0xc01c552f); break; + case NT98513_SET_CONFIG: number=UINT32_C(0xc01c5530); break; + case NT98513_GET_NOTIFICATION: number=UINT32_C(0xc00c552e); break; + default: number=UINT32_C(0xc00c5531); break; + } + for (i=0;icommand_address); + write_le32(wire+4,request->command_length); + write_le32(wire+8,request->config_address); + write_le32(wire+12,request->config_length); + write_le32(wire+20,request->response_address); + write_le32(wire+24,request->response_capacity); + } else { + write_le32(wire+4,request->response_address); + write_le32(wire+8,request->response_capacity); + } + *command=number; + return NT98513_CONTROL_OK; +} + +enum nt98513_control_status nt98513_control_parse_reply( + enum nt98513_control_operation op, const uint8_t *wire, size_t size, + int result, struct nt98513_control_reply *reply) +{ + size_t required=operation_size(op), i; + uint32_t status; + if (wire == NULL || reply == NULL) return NT98513_CONTROL_ARGUMENT; + if (required == 0) return NT98513_CONTROL_OPERATION; + if (size != required) return NT98513_CONTROL_SIZE_ERROR; + if (result != 0) return NT98513_CONTROL_IOCTL; + status=read_le32(wire+(required == NT98513_CONTROL_SIZE ? 16 : 0)); + for (i=0;i<7;++i) reply->words[i]=iword_count=(uint32_t)(required/4); + /* Avoid implementation-defined unsigned-to-signed out-of-range casts. */ + reply->remote_status=status<=INT32_MAX ? (int32_t)status : + -1-(int32_t)(UINT32_MAX-status); + return NT98513_CONTROL_OK; +} + +enum nt98513_control_status nt98513_venc_settings_decode( + const uint8_t *wire, size_t size, struct nt98513_venc_settings *out) +{ + size_t i; + if (wire == NULL || out == NULL) return NT98513_CONTROL_ARGUMENT; + if (size != NT98513_VENC_SIZE) return NT98513_CONTROL_SIZE_ERROR; + for (i=0;iwords[i]=read_le32(wire+4*i); + out->input_source=out->words[0]; out->codec=out->words[1]; + out->profile=out->words[2]; out->width=out->words[3]; + out->height=out->words[4]; out->rate_mode=out->words[5]; + out->frame_delay_divisor_known=1; + switch (out->rate_mode) { + case 1: out->frame_delay_divisor=out->words[0x19]; break; + case 2: out->frame_delay_divisor=out->words[9]; break; + case 3: out->frame_delay_divisor=out->words[0x26]; break; + case 4: out->frame_delay_divisor=out->words[0x2e]; break; + default: + out->frame_delay_divisor=0; out->frame_delay_divisor_known=0; break; + } + return NT98513_CONTROL_OK; +} diff --git a/tools/nt98513/src/nt98513_probe.c b/tools/nt98513/src/nt98513_probe.c new file mode 100644 index 0000000..f8a34dd --- /dev/null +++ b/tools/nt98513/src/nt98513_probe.c @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_probe.h" + +struct line { uint8_t bytes[512]; size_t length; }; +static void text(struct line *l, const char *s) +{ + while (*s && l->length < sizeof(l->bytes)) l->bytes[l->length++]=(uint8_t)*s++; +} +static void hex(struct line *l, uint32_t n) +{ + unsigned i; static const char digits[]="0123456789abcdef"; + for(i=0;i<8;++i) l->bytes[l->length++]=(uint8_t)digits[(n>>(28-i*4))&15]; +} +/* Every call formats fixed fields; the largest line is under 256 bytes. */ +static int32_t write_line(const struct nt98513_probe_io *io,int32_t fd,struct line *l) +{ + size_t done=0; + while(donelength) { + int32_t n=io->write(io->context,fd,l->bytes+done,l->length-done); + if(n==0) return -5; /* no progress: normalized EIO */ + if(n<0 || (uint32_t)n>l->length-done) return n; + done+=(size_t)n; + } + return 0; +} +static int valid_io(const struct nt98513_probe_io *io) +{ return io && io->open && io->ioctl && io->write && io->close; } +static int valid_options(const struct nt98513_probe_options *o) +{ + return o && o->channelcount>0 && + o->count<=NT98513_PROBE_MAX_COUNT && o->timeout_ms>0 && + o->timeout_ms<=NT98513_PROBE_MAX_TIMEOUT_MS; +} +static int32_t get_range(const struct nt98513_probe_io *io,int32_t fd, + struct nt98513_buffer_range *range, int *valid) +{ + uint8_t bytes[8]; size_t i; int32_t rc; + for(i=0;iioctl(io->context,fd,NT98513_IOCTL_GET_BUFFER_RANGE,bytes,sizeof(bytes)); + *valid=rc==0 && nt98513_buffer_range_decode(bytes,sizeof(bytes),range)==NT98513_OK; + return rc; +} +static const char *sequence(uint64_t old,uint64_t current,int have_old) +{ + if(!have_old) return "first"; + if(current==old) return "duplicate"; + if(currentlength=0;text(l,"descriptor raw="); + for(i=0;ibytes[l->length++]=(uint8_t)digits[bytes[i]>>4]; + l->bytes[l->length++]=(uint8_t)digits[bytes[i]&15]; + } + text(l,valid?" span=valid offset=":" span=invalid offset=");hex(l,(uint32_t)offset); + text(l," sequence=");text(l,seq);text(l,"\n"); +} +enum nt98513_probe_status nt98513_probe_run(const struct nt98513_probe_options *o, + const struct nt98513_probe_io *io,struct nt98513_probe_result *r) +{ + int32_t fd,rc;int valid,have_old=0;uint32_t i;uint64_t previous=0; + struct nt98513_buffer_range range,after;struct line l; + if(!r) return NT98513_PROBE_USAGE; + r->status=NT98513_PROBE_USAGE;r->io_return=0;r->close_return=0;r->received=0;r->emitted=0; + if(!valid_options(o) || !valid_io(io)) return r->status; + fd=io->open(io->context,NT98513_PROBE_DEVICE,NT98513_PROBE_OPEN_FLAGS); + if(fd<0) { r->io_return=fd;r->status=NT98513_PROBE_OPEN;return r->status; } + rc=get_range(io,fd,&range,&valid); + if(!valid) { r->io_return=rc;r->status=NT98513_PROBE_RANGE;goto close; } + l.length=0;text(&l,"range address=");hex(&l,range.address);text(&l," length=");hex(&l,range.length);text(&l,"\n"); + rc=write_line(io,1,&l); + if(rc!=0) { r->io_return=rc;r->status=NT98513_PROBE_WRITE;goto close; } + r->status=NT98513_PROBE_OK; + for(i=0;icount;++i) { + uint8_t bytes[NT98513_STREAM_BYTES];struct nt98513_stream_descriptor d; + size_t offset=0;uint64_t current; + (void)nt98513_stream_request(bytes,sizeof(bytes),o->channel,o->timeout_ms); + rc=io->ioctl(io->context,fd,NT98513_IOCTL_GET_STREAM,bytes,sizeof(bytes)); + if(rc!=0) { r->io_return=rc;r->status=rc==-62?NT98513_PROBE_TIMEOUT:NT98513_PROBE_STREAM;break; } + ++r->received; + (void)nt98513_stream_decode(bytes,sizeof(bytes),&d); + current=((uint64_t)d.sequence_high<<32)|d.sequence_low; + valid=d.opaque_00==0 && d.channel==o->channel && d.timeout_raw==o->timeout_ms && + nt98513_stream_payload_offset(&range,&d,(size_t)range.length,&offset)==NT98513_OK; + if(!valid) r->status=NT98513_PROBE_DESCRIPTOR; + else { + rc=get_range(io,fd,&after,&valid); + if(!valid) { r->io_return=rc;r->status=NT98513_PROBE_RANGE; } + else if(after.address!=range.address || after.length!=range.length) { + valid=0;r->status=NT98513_PROBE_RANGE_CHANGED; + } + } + descriptor_line(&l,bytes,offset,valid,sequence(previous,current,have_old)); + rc=write_line(io,1,&l); + if(rc!=0) { r->io_return=rc;r->status=NT98513_PROBE_WRITE;break; } + if(r->status!=NT98513_PROBE_OK) break; + ++r->emitted;previous=current;have_old=1; + } +close: + r->close_return=io->close(io->context,fd); + if(r->status==NT98513_PROBE_OK && r->close_return!=0) r->status=NT98513_PROBE_CLOSE; + return r->status; +} +static int same(const char *a,const char *b) +{ if(!a || !b) return 0;while(*a && *a==*b) { ++a;++b; }return *a==*b; } +static int number(const char *s,uint32_t *out) +{ + uint32_t n=0;if(!s || !*s) return 0; + while(*s) { + unsigned digit=(unsigned)((unsigned char)*s++-'0'); + if(digit>9 || n>(UINT32_MAX-digit)/10) return 0; + n=n*10+digit; + } + *out=n;return 1; +} +int nt98513_probe_cli(int argc,const char *const *argv,const struct nt98513_probe_io *io) +{ + struct nt98513_probe_options o;struct nt98513_probe_result r;struct line l; + int help=argc==1 || (argc==2 && argv && same(argv[1],"--help")); + if(!valid_io(io)) return NT98513_PROBE_USAGE; + if(help || argc!=5 || !argv || !same(argv[1],"--descriptors") || + !number(argv[2],&o.channel) || !number(argv[3],&o.count) || + !number(argv[4],&o.timeout_ms) || !valid_options(&o)) { + l.length=0;text(&l,"Usage: nt98513-descriptor-probe --descriptors CHANNEL COUNT TIMEOUT_MS\n" + "CHANNEL 0..15; COUNT 1..1000; TIMEOUT_MS 1..60000.\n" + "Descriptor metadata only; no payload lease or capture. Timeout is not a wall-clock bound.\n"); + if(write_line(io,help?1:2,&l)!=0) return NT98513_PROBE_WRITE; + return help?0:NT98513_PROBE_USAGE; + } + (void)nt98513_probe_run(&o,io,&r); + l.length=0;text(&l,"result code=");hex(&l,(uint32_t)r.status);text(&l," io=");hex(&l,(uint32_t)r.io_return); + text(&l," close=");hex(&l,(uint32_t)r.close_return);text(&l," received=");hex(&l,r.received); + text(&l," emitted=");hex(&l,r.emitted);text(&l,"\n"); + if(write_line(io,2,&l)!=0) return NT98513_PROBE_WRITE; + return (int)r.status; +} diff --git a/tools/nt98513/src/nt98513_probe_mips.S b/tools/nt98513/src/nt98513_probe_mips.S new file mode 100644 index 0000000..b627b1b --- /dev/null +++ b/tools/nt98513/src/nt98513_probe_mips.S @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: MIT + * Linux MIPS O32 only. See research/descriptor-probe.md for byte-backed review. + * C signature: int32_t nt98513_syscall3(nr, arg0, arg1, arg2). + */ + .set noreorder + .text + .align 2 + .globl nt98513_syscall3 + .type nt98513_syscall3,@function +nt98513_syscall3: + addiu $sp,$sp,-32 + sw $zero,16($sp) + sw $zero,20($sp) + sw $zero,24($sp) + sw $zero,28($sp) + move $v0,$a0 + move $a0,$a1 + move $a1,$a2 + move $a2,$a3 + syscall + beq $a3,$zero,1f + nop + subu $v0,$zero,$v0 +1: + addiu $sp,$sp,32 + jr $ra + nop + .size nt98513_syscall3,.-nt98513_syscall3 + + .globl _start + .type _start,@function +_start: + lw $a0,0($sp) + addiu $a1,$sp,4 + addiu $sp,$sp,-32 + jal nt98513_probe_main + nop + move $a1,$v0 + li $a0,4001 + move $a2,$zero + move $a3,$zero + jal nt98513_syscall3 + nop +2: + break 0 + b 2b + nop + .size _start,.-_start + .section .note.GNU-stack,"",@progbits diff --git a/tools/nt98513/src/nt98513_probe_mips.c b/tools/nt98513/src/nt98513_probe_mips.c new file mode 100644 index 0000000..de70c7d --- /dev/null +++ b/tools/nt98513/src/nt98513_probe_mips.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_probe.h" +_Static_assert(sizeof(void *)==4 && sizeof(size_t)==4, "MIPS O32 required"); +#if !defined(__mips__) || !defined(__MIPSEL__) +#error This adapter is only for Linux MIPS little-endian O32 +#endif +extern int32_t nt98513_syscall3(uint32_t,uint32_t,uint32_t,uint32_t); +static int32_t device_open(void *ctx,const char *path,uint32_t flags) +{ (void)ctx;return nt98513_syscall3(4005,(uint32_t)(uintptr_t)path,flags,0); } +static int32_t device_ioctl(void *ctx,int32_t fd,uint32_t request,uint8_t *bytes,size_t size) +{ (void)ctx;(void)size;return nt98513_syscall3(4054,(uint32_t)fd,request,(uint32_t)(uintptr_t)bytes); } +static int32_t output_write(void *ctx,int32_t fd,const uint8_t *bytes,size_t size) +{ (void)ctx;return nt98513_syscall3(4004,(uint32_t)fd,(uint32_t)(uintptr_t)bytes,(uint32_t)size); } +static int32_t device_close(void *ctx,int32_t fd) +{ (void)ctx;return nt98513_syscall3(4006,(uint32_t)fd,0,0); } +int nt98513_probe_main(int argc,const char *const *argv) +{ + struct nt98513_probe_io io; + io.context=0;io.open=device_open;io.ioctl=device_ioctl;io.write=output_write;io.close=device_close; + return nt98513_probe_cli(argc,argv,&io); +} diff --git a/tools/nt98513/src/nt98513_sensor.c b/tools/nt98513/src/nt98513_sensor.c new file mode 100644 index 0000000..8dc2097 --- /dev/null +++ b/tools/nt98513/src/nt98513_sensor.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_sensor.h" + +enum nt98513_control_status nt98513_sensor_active_parse( + const void *reply, size_t reply_size, struct nt98513_sensor_active *out) +{ + static const struct canonical { + const char *bytes; + size_t size; + enum nt98513_sensor_selection selection; + } accepted[] = { + { "0 CMOS_OFF success", sizeof("0 CMOS_OFF success"), NT98513_SENSOR_OFF }, + { "0 CMOS_OS05A10M success", sizeof("0 CMOS_OS05A10M success"), NT98513_SENSOR_OS05A10M }, + { "0 CMOS_GC2053M success", sizeof("0 CMOS_GC2053M success"), NT98513_SENSOR_GC2053M }, + { "0 CMOS_JXK02M success", sizeof("0 CMOS_JXK02M success"), NT98513_SENSOR_JXK02M }, + { "0 CMOS_JXK03M success", sizeof("0 CMOS_JXK03M success"), NT98513_SENSOR_JXK03M } + }; + const unsigned char *bytes = reply; + size_t candidate, i; + + if (reply == NULL || out == NULL) return NT98513_CONTROL_ARGUMENT; + if (reply_size == 0u || reply_size > 256u) return NT98513_CONTROL_SIZE_ERROR; + for (candidate = 0; candidate < sizeof accepted / sizeof accepted[0]; ++candidate) { + if (reply_size != accepted[candidate].size) continue; + for (i = 0; i < reply_size; ++i) + if (bytes[i] != (unsigned char)accepted[candidate].bytes[i]) break; + if (i == reply_size) { + out->logical_index = 0; + out->selection = accepted[candidate].selection; + return NT98513_CONTROL_OK; + } + } + return NT98513_CONTROL_RANGE; +} diff --git a/tools/nt98513/src/nt98513_sensor_cli.c b/tools/nt98513/src/nt98513_sensor_cli.c new file mode 100644 index 0000000..b926078 --- /dev/null +++ b/tools/nt98513/src/nt98513_sensor_cli.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_sensor_cli.h" +struct writer { const struct nt98513_client_io *io; int32_t fd; unsigned failed; }; +static void bytes(struct writer *w,const uint8_t *p,size_t n) +{ + while(n && !w->failed) { + size_t chunk=n>NT98513_SENSOR_CLI_WRITE_MAX?NT98513_SENSOR_CLI_WRITE_MAX:n; + int32_t done=w->io->write(w->io->context,w->fd,p,chunk); + if(done<=0 || (uint32_t)done>chunk) { w->failed=1; return; } + p+=(size_t)done; n-=(size_t)done; + } +} +static void text(struct writer *w,const char *s) +{ + size_t n=0;while(s[n])++n;bytes(w,(const uint8_t *)s,n); +} +static void number(struct writer *w,uint32_t v) +{ + uint8_t buf[10];size_t n=sizeof(buf); + do { buf[--n]=(uint8_t)('0'+v%10u);v/=10u; } while(v); + bytes(w,buf+n,sizeof(buf)-n); +} +static void signed_number(struct writer *w,int32_t v) +{ + uint32_t magnitude=(uint32_t)v; + if(v<0) { text(w,"-");magnitude=0u-magnitude; } + number(w,magnitude); +} +static void boolean(struct writer *w,unsigned v) { text(w,v?"true":"false"); } +static void nullable_signed(struct writer *w,unsigned valid,int32_t v) +{ if(valid)signed_number(w,v);else text(w,"null"); } +static void nullable_bool(struct writer *w,unsigned valid,unsigned v) +{ if(valid)boolean(w,v);else text(w,"null"); } +static int equal(const char *a,const char *b) +{ + if(a==NULL)return 0; + while(*a && *a==*b){++a;++b;}return *a==*b; +} +static const char *sensor_name(enum nt98513_sensor_selection s) +{ + switch(s) { + case NT98513_SENSOR_OFF:return "CMOS_OFF"; + case NT98513_SENSOR_OS05A10M:return "CMOS_OS05A10M"; + case NT98513_SENSOR_GC2053M:return "CMOS_GC2053M"; + case NT98513_SENSOR_JXK02M:return "CMOS_JXK02M"; + case NT98513_SENSOR_JXK03M:return "CMOS_JXK03M"; + } + return ""; /* The actual strict query publishes only the five enums. */ +} +static void hex(struct writer *w,const uint8_t *p,size_t n) +{ + static const char digits[]="0123456789abcdef"; + uint8_t buf[128];size_t i; + while(n && !w->failed) { + size_t count=n>sizeof(buf)/2?sizeof(buf)/2:n; + for(i=0;i>4];buf[i*2+1]=(uint8_t)digits[p[i]&15];} + bytes(w,buf,count*2);p+=count;n-=count; + } +} +static void record(struct writer *w,enum nt98513_sensor_query_status status, + const struct nt98513_sensor_active *s,const struct nt98513_sensor_query_diagnostic *d) +{ + /* ARGUMENT leaves d untouched. Every expression below that reads it is + * guarded by valid; a conditional operator evaluates just its chosen arm. */ + unsigned valid=status!=NT98513_SENSOR_QUERY_ARGUMENT; + unsigned remote=valid?d->client.remote_valid:0; + unsigned parsed=valid?d->parse_called:0; + unsigned i; + text(w,"{\"query_status\":");signed_number(w,(int32_t)status); + text(w,",\"diagnostic_valid\":");boolean(w,valid); + text(w,",\"selection\":"); + if(status==NT98513_SENSOR_QUERY_OK || status==NT98513_SENSOR_QUERY_NOT_SELECTED) { + text(w,"{\"logical_index\":");number(w,s->logical_index); + text(w,",\"value\":");number(w,(uint32_t)s->selection); + text(w,",\"name\":\"");text(w,sensor_name(s->selection));text(w,"\"}"); + } else text(w,"null"); + text(w,",\"client_status\":");nullable_signed(w,valid,valid?(int32_t)d->client.status:0); + text(w,",\"codec_status\":");nullable_signed(w,valid,valid?(int32_t)d->client.codec_status:0); + text(w,",\"address_return\":");nullable_signed(w,valid,valid?d->client.address_return:0); + text(w,",\"address_called\":");nullable_bool(w,valid,valid?d->client.address_called:0); + text(w,",\"open_return\":");nullable_signed(w,valid,valid?d->client.open_return:0); + text(w,",\"open_called\":");nullable_bool(w,valid,valid?d->client.open_called:0); + text(w,",\"opened\":");nullable_bool(w,valid,valid?d->client.opened:0); + text(w,",\"ioctl_return\":");nullable_signed(w,valid,valid?d->client.ioctl_return:0); + text(w,",\"ioctl_called\":");nullable_bool(w,valid,valid?d->client.ioctl_called:0); + text(w,",\"close_return\":");nullable_signed(w,valid,valid?d->client.close_return:0); + text(w,",\"closed\":");nullable_bool(w,valid,valid?d->client.closed:0); + text(w,",\"remote_valid\":");nullable_bool(w,valid,remote); + text(w,",\"remote_status\":");nullable_signed(w,remote,remote?d->client.reply.remote_status:0); + text(w,",\"config_valid\":");nullable_bool(w,valid,valid?d->client.config_valid:0); + text(w,",\"parse_called\":");boolean(w,parsed); + text(w,",\"parser_status\":");nullable_signed(w,parsed,parsed?(int32_t)d->parser_status:0); + text(w,",\"reply_words\":"); + if(remote) {text(w,"[");for(i=0;i<7;++i){if(i)text(w,",");number(w,d->client.reply.words[i]);}text(w,"]");} + else text(w,"null"); + text(w,",\"response_size\":"); + /* The actual client can publish only an owned count in 0..256. */ + if(valid)number(w,(uint32_t)d->client.response_size);else text(w,"null"); + text(w,",\"response_hex\":"); + if(valid){size_t n=d->client.response_size;text(w,"\"");if(n>sizeof(d->client.response))n=sizeof(d->client.response);hex(w,d->client.response,n);text(w,"\"");} + else text(w,"null"); + text(w,",\"response_terminated\":");nullable_bool(w,valid,valid?d->client.response_terminated:0); + text(w,"}\n"); +} +int nt98513_sensor_cli(int argc,const char *const *argv, + const struct nt98513_client_io *io) +{ + static const char usage[]="usage: nt98513-sensor active\n"; + struct writer w;struct nt98513_sensor_active selection; + struct nt98513_sensor_query_diagnostic diagnostic; + enum nt98513_sensor_query_status status;int result; + if(io==NULL || io->write==NULL)return NT98513_SENSOR_CLI_WRITE_ERROR; + w.io=io;w.fd=1;w.failed=0; + if(argc==0 || argc==1 || (argc==2 && argv && equal(argv[1],"--help"))) { + text(&w,usage);return w.failed?NT98513_SENSOR_CLI_WRITE_ERROR:NT98513_SENSOR_CLI_OK; + } + if(argc!=2 || argv==NULL || !equal(argv[1],"active")) { + w.fd=2;text(&w,usage);return w.failed?NT98513_SENSOR_CLI_WRITE_ERROR:NT98513_SENSOR_CLI_USAGE; + } + status=nt98513_sensor_active_query(io,&selection,&diagnostic); + record(&w,status,&selection,&diagnostic); + switch(status) { + case NT98513_SENSOR_QUERY_OK:result=NT98513_SENSOR_CLI_OK;break; + case NT98513_SENSOR_QUERY_NOT_SELECTED:result=NT98513_SENSOR_CLI_NOT_SELECTED;break; + case NT98513_SENSOR_QUERY_ARGUMENT:result=NT98513_SENSOR_CLI_USAGE;break; + case NT98513_SENSOR_QUERY_CLIENT_ERROR:result=NT98513_SENSOR_CLI_CLIENT_ERROR;break; + default:result=NT98513_SENSOR_CLI_REPLY_ERROR;break; + } + return w.failed?NT98513_SENSOR_CLI_WRITE_ERROR:result; +} diff --git a/tools/nt98513/src/nt98513_sensor_cli_mips.c b/tools/nt98513/src/nt98513_sensor_cli_mips.c new file mode 100644 index 0000000..96bccb5 --- /dev/null +++ b/tools/nt98513/src/nt98513_sensor_cli_mips.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_sensor_cli.h" +_Static_assert(sizeof(void *)==4 && sizeof(size_t)==4,"MIPS O32 required"); +#if !defined(__mips__) || !defined(__MIPSEL__) +#error This entry is only for Linux MIPS little-endian O32 +#endif +extern const struct nt98513_client_io nt98513_client_mips_io; +/* Link the unchanged generic CLI too: the accepted adapter retains its own + * generic main reference. No reliance on linker section garbage collection. */ +int nt98513_sensor_cli_main(int argc,const char *const *argv) +{ + return nt98513_sensor_cli(argc,argv,&nt98513_client_mips_io); +} + diff --git a/tools/nt98513/src/nt98513_sensor_query.c b/tools/nt98513/src/nt98513_sensor_query.c new file mode 100644 index 0000000..b064a9d --- /dev/null +++ b/tools/nt98513/src/nt98513_sensor_query.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_sensor_query.h" + +enum nt98513_sensor_query_status nt98513_sensor_active_query( + const struct nt98513_client_io *io, + struct nt98513_sensor_active *selection, + struct nt98513_sensor_query_diagnostic *diagnostic) +{ + const struct nt98513_client_request request = { + NT98513_SEND_COMMAND, "usys -sensor_active 0", NULL, 0 + }; + struct nt98513_sensor_active parsed; + enum nt98513_client_status status; + + if (io == NULL || selection == NULL || diagnostic == NULL || + io->address == NULL || io->open == NULL || + io->ioctl == NULL || io->close == NULL) + return NT98513_SENSOR_QUERY_ARGUMENT; + + diagnostic->parse_called = 0; + diagnostic->parser_status = NT98513_CONTROL_ARGUMENT; + status = nt98513_client_run(&request, io, &diagnostic->client); + /* The actual client includes close in success and preserves raw failures. + * Never parse or publish a reply from an unsuccessful transaction. */ + if (status != NT98513_CLIENT_OK || diagnostic->client.ioctl_return != 0 || + !diagnostic->client.remote_valid || diagnostic->client.reply.remote_status != 0 || + !diagnostic->client.response_terminated || + diagnostic->client.response_size >= sizeof(diagnostic->client.response)) + return NT98513_SENSOR_QUERY_CLIENT_ERROR; + + diagnostic->parse_called = 1; + diagnostic->parser_status = nt98513_sensor_active_parse( + diagnostic->client.response, diagnostic->client.response_size + 1, &parsed); + if (diagnostic->parser_status != NT98513_CONTROL_OK) + return NT98513_SENSOR_QUERY_REPLY_ERROR; + + *selection = parsed; + return parsed.selection == NT98513_SENSOR_OFF ? + NT98513_SENSOR_QUERY_NOT_SELECTED : NT98513_SENSOR_QUERY_OK; +} diff --git a/tools/nt98513/src/nt98513_stream.c b/tools/nt98513/src/nt98513_stream.c new file mode 100644 index 0000000..c7d1f01 --- /dev/null +++ b/tools/nt98513/src/nt98513_stream.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: MIT */ +#include "nt98513_stream.h" + +static uint32_t read_le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void write_le32(uint8_t *p, uint32_t value) +{ + p[0] = (uint8_t)value; + p[1] = (uint8_t)(value >> 8); + p[2] = (uint8_t)(value >> 16); + p[3] = (uint8_t)(value >> 24); +} + +/* Compare the last included byte, avoiding overflowing a 32-bit endpoint. */ +static enum nt98513_status check_span(uint32_t address, uint32_t length) +{ + if (length == 0) + return NT98513_ERR_EMPTY; + if (length - 1 > UINT32_MAX - address) + return NT98513_ERR_RANGE; + return NT98513_OK; +} + +enum nt98513_status nt98513_stream_request(uint8_t *wire, size_t size, + uint32_t channel, uint32_t timeout_raw) +{ + size_t i; + if (wire == NULL) + return NT98513_ERR_ARGUMENT; + if (size != NT98513_STREAM_BYTES) + return NT98513_ERR_SIZE; + if (channel >= NT98513_CHANNEL_COUNT) + return NT98513_ERR_CHANNEL; + for (i = 0; i < NT98513_STREAM_BYTES; ++i) + wire[i] = 0; + write_le32(wire + 4, channel); + write_le32(wire + 8, timeout_raw); + return NT98513_OK; +} + +enum nt98513_status nt98513_stream_decode(const uint8_t *wire, size_t size, + struct nt98513_stream_descriptor *out) +{ + uint32_t words[14]; + size_t i; + if (wire == NULL || out == NULL) + return NT98513_ERR_ARGUMENT; + if (size != NT98513_STREAM_BYTES) + return NT98513_ERR_SIZE; + for (i = 0; i < 14; ++i) + words[i] = read_le32(wire + i * 4); + out->opaque_00 = words[0]; + out->channel = words[1]; + out->timeout_raw = words[2]; + out->opaque_0c = words[3]; + out->sequence_low = words[4]; + out->sequence_high = words[5]; + out->payload_address = words[6]; + out->payload_length = words[7]; + out->metadata = words[8]; + out->opaque_24 = words[9]; + out->timestamp_0 = words[10]; + out->opaque_2c = words[11]; + out->timestamp_1 = words[12]; + out->opaque_34 = words[13]; + return NT98513_OK; +} + +enum nt98513_status nt98513_buffer_range_decode(const uint8_t *wire, size_t size, + struct nt98513_buffer_range *out) +{ + uint32_t address, length; + enum nt98513_status status; + if (wire == NULL || out == NULL) + return NT98513_ERR_ARGUMENT; + if (size != NT98513_RANGE_BYTES) + return NT98513_ERR_SIZE; + address = read_le32(wire); + length = read_le32(wire + 4); + status = check_span(address, length); + if (status != NT98513_OK) + return status; + out->address = address; + out->length = length; + return NT98513_OK; +} + +enum nt98513_status nt98513_stream_payload_offset( + const struct nt98513_buffer_range *range, + const struct nt98513_stream_descriptor *stream, + size_t mapped_size, size_t *offset) +{ + uint32_t delta; + enum nt98513_status status; + if (range == NULL || stream == NULL || offset == NULL) + return NT98513_ERR_ARGUMENT; + status = check_span(range->address, range->length); + if (status != NT98513_OK) + return status; + status = check_span(stream->payload_address, stream->payload_length); + if (status != NT98513_OK) + return status; + if (range->length > mapped_size || stream->payload_address < range->address) + return NT98513_ERR_RANGE; + delta = stream->payload_address - range->address; + if (delta >= range->length || stream->payload_length > range->length - delta) + return NT98513_ERR_RANGE; + *offset = (size_t)delta; + return NT98513_OK; +} + +enum nt98513_status nt98513_cache_request(uint8_t *wire, size_t size, + uint32_t address, uint32_t length) +{ + enum nt98513_status status; + if (wire == NULL) + return NT98513_ERR_ARGUMENT; + if (size != NT98513_RANGE_BYTES) + return NT98513_ERR_SIZE; + status = check_span(address, length); + if (status != NT98513_OK) + return status; + write_le32(wire, address); + write_le32(wire + 4, length); + return NT98513_OK; +}