From c1dece3b8e60fd02b18fd6ee23ee552b65aa7845 Mon Sep 17 00:00:00 2001 From: qiu liwei Date: Tue, 27 Oct 2026 04:26:02 +0000 Subject: [PATCH 1/2] refactor(platform): unify typed assembly and cold lifecycle contracts Use immutable named resolution and direct constructor calls throughout the maintained providers. Remove legacy JSON assembly input and mapping adapters, keep one-way audit projections, and version the private typed source contract. Move selected STM32/GD32 cold lifecycle operations into tested C owners, preserve retryable resource responsibility, and split measured ADC/PWM hardware descriptions from mutable state. Verify exact candidate evidence in capability queries, validate complete local software-plan receipts, and align explicit documentation evidence references, architecture diagrams and reviewed bilingual documentation. --- .github/workflows/test_platform_ci.py | 6 +- AGENTS.md | 9 +- cmake/README.md | 2 +- dependencies/platform-compatibility.json | 2 +- docs/assets/diagrams/README.md | 7 +- .../diagrams/configuration-generation.dot | 17 +- .../diagrams/configuration-generation.svg | 3272 ++++++++++------- docs/assets/diagrams/diagram-index.csv | 2 +- docs/assets/diagrams/labels.json | 38 +- .../localized/en/configuration-generation.dot | 21 +- .../localized/en/configuration-generation.svg | 2009 +++++----- .../zh_CN/configuration-generation.dot | 17 +- .../zh_CN/configuration-generation.svg | 1378 ++++--- docs/delivery/architecture.md | 22 +- docs/delivery/capabilities.md | 26 +- docs/delivery/migration.md | 5 +- docs/design/documentation-system.md | 11 +- docs/design/official-stm32-provider.md | 38 +- docs/design/source-compatibility.md | 8 +- docs/development/workflow.md | 25 + docs/explanation/platform-architecture.md | 31 +- docs/guides/migration.md | 7 +- docs/guides/provider-porting.md | 7 +- .../en/LC_MESSAGES/assets/diagrams/README.po | 41 +- .../en/LC_MESSAGES/delivery/architecture.po | 109 +- .../en/LC_MESSAGES/delivery/capabilities.po | 109 +- .../en/LC_MESSAGES/delivery/migration.po | 26 +- .../design/documentation-system.po | 64 +- .../design/source-compatibility.po | 35 +- .../reference/documentation-delivery.po | 80 +- docs/locale/en/LC_MESSAGES/status/ledgers.po | 38 +- .../status/ledgers/soc-ip-layout.po | 1842 ++++++++++ .../status/software-verification.po | 57 +- .../LC_MESSAGES/assets/diagrams/README.po | 35 +- .../LC_MESSAGES/delivery/architecture.po | 228 +- .../LC_MESSAGES/delivery/capabilities.po | 89 +- .../zh_CN/LC_MESSAGES/delivery/migration.po | 19 +- .../design/documentation-system.po | 42 +- .../design/official-stm32-provider.po | 180 +- .../design/source-compatibility.po | 27 +- .../zh_CN/LC_MESSAGES/development/workflow.po | 46 +- .../explanation/platform-architecture.po | 257 +- .../zh_CN/LC_MESSAGES/guides/migration.po | 23 +- .../LC_MESSAGES/guides/provider-porting.po | 258 +- .../reference/configuration/assembly.po | 36 +- .../zh_CN/LC_MESSAGES/status/ledgers.po | 27 +- .../status/ledgers/soc-ip-layout.po | 1667 +++++++++ .../status/software-verification.po | 46 +- docs/reference/configuration/assembly.md | 10 +- docs/reference/documentation-delivery.md | 30 +- docs/status/ledgers.md | 7 +- docs/status/ledgers/soc-ip-layout.md | 797 ++++ docs/status/software-verification.md | 10 +- drivers/adc/legacy_adc/legacy_adc.c | 78 +- drivers/adc/legacy_adc/legacy_adc.h | 12 +- drivers/timer/legacy_pwm/legacy_pwm.c | 101 +- drivers/timer/legacy_pwm/legacy_pwm.h | 13 +- io/private/README.md | 3 +- soc/gd32/gd32f303/configure/bindings.py | 52 +- soc/gd32/gd32f303/configure/emission.py | 10 +- soc/gd32/gd32f303/drivers/devices.c | 44 +- soc/gd32/gd32f470/configure/bindings.py | 283 +- soc/gd32/gd32f470/configure/emission.py | 20 +- soc/gd32/gd32f470/configure/preflight.py | 10 +- soc/gd32/gd32f470/configure/secondary.py | 17 - soc/gd32/gd32f470/drivers/exti.c | 8 + soc/gd32/gd32f470/drivers/spi.c | 193 +- soc/gd32/gd32f470/drivers/spi_dma.c | 123 + soc/gd32/gd32f470/private/gd32f470_provider.h | 6 + soc/gd32/gd32f470/private/gd32f470_spi_cold.h | 89 + soc/gd32/gd32f470/private/gd32f470_spi_dma.h | 37 +- soc/native/configure/bindings.py | 92 +- soc/native/configure/emission.py | 12 +- soc/native/drivers/rtc_lptim.c | 19 + soc/native/drivers/rtc_lptim_provider.h | 18 + soc/stm32/stm32f103/configure/bindings.py | 47 +- soc/stm32/stm32f103/configure/emission.py | 10 +- soc/stm32/stm32f103/drivers/devices.c | 44 +- soc/stm32/stm32f407/configure/bindings.py | 354 +- soc/stm32/stm32f407/configure/emission.py | 18 +- soc/stm32/stm32f407/configure/preflight.py | 10 +- soc/stm32/stm32f407/configure/secondary.py | 20 - soc/stm32/stm32f407/drivers/spi.c | 239 ++ soc/stm32/stm32f407/drivers/spi_dma.c | 250 ++ soc/stm32/stm32f407/private/stm32f407_dma.h | 64 + .../stm32f407/private/stm32f407_spi_cold.h | 101 + soc/stm32/stm32g474/configure/adc_group.py | 5 +- soc/stm32/stm32g474/configure/bindings.py | 77 +- soc/stm32/stm32g474/configure/coupled.py | 9 +- soc/stm32/stm32g474/configure/preflight.py | 4 +- soc/stm32/stm32g474/configure/pwm_shared.py | 5 +- soc/stm32/stm32g474/configure/timer.py | 7 +- soc/stm32/stm32h743/configure/bindings.py | 102 +- soc/stm32/stm32h743/configure/contract.py | 2 +- soc/stm32/stm32h743/configure/emission.py | 12 +- soc/stm32/stm32h743/configure/memory.py | 2 +- soc/stm32/stm32h743/configure/preflight.py | 10 +- .../stm32h743/configure/runtime_power.py | 18 +- .../stm32h743/configure/selected_support.py | 8 +- soc/stm32/stm32u575/configure/bindings.py | 102 +- soc/stm32/stm32u575/configure/contract.py | 4 +- soc/stm32/stm32u575/configure/emission.py | 17 +- soc/stm32/stm32u575/configure/preflight.py | 17 +- soc/stm32/stm32u575/drivers/uart_dma_chain.c | 39 +- .../stm32u575/private/stm32u575_uart_gpdma.h | 26 + .../private/stm32u575_uart_gpdma_chain.h | 10 + .../private/stm32u575_uart_gpdma_impl.h | 71 + tests/contracts/gd32f303_model/model.c | 10 +- .../stm32_model/generate_constructors.py | 22 +- tests/contracts/stm32f103_model/model.c | 10 +- tests/contracts/test_evidence_candidate.py | 72 + .../test_evidence_float_abi_consumers.py | 9 +- tests/contracts/test_measurement_compare.py | 76 +- tests/contracts/test_source_sdk.py | 4 +- tests/google/CMakeLists.txt | 1 + tests/google/dma_multi_controller.cmake | 4 + tests/google/gd32_exti_test.cpp | 24 + tests/google/legacy_devices_factory_test.cpp | 4 +- tests/google/legacy_devices_test.cpp | 130 +- tests/google/rtc_lptim_start_test.cpp | 73 + tests/google/single_spi_factory_test.cpp | 165 + tests/google/spi_transaction.cmake | 2 + tests/google/u575_uart_chain.cmake | 1 + tests/google/u575_uart_chain_test.cpp | 36 + tests/google/u575_uart_gpdma.cmake | 1 + tests/google/u575_uart_gpdma_test.cpp | 67 + tools/configure/README.md | 44 +- tools/configure/authored.py | 37 +- tools/configure/bindings.py | 16 +- tools/configure/calls.py | 92 + tools/configure/can.py | 31 +- tools/configure/configure.py | 201 +- tools/configure/cpu.py | 2 +- tools/configure/dependencies.py | 2 +- tools/configure/documentation.py | 49 +- tools/configure/emission.py | 46 +- tools/configure/enhanced_endpoints.py | 17 +- tools/configure/ethernet.py | 9 +- tools/configure/experience.py | 7 +- tools/configure/factory.py | 5 +- tools/configure/gpio_phase.py | 16 +- tools/configure/i2c_timing.py | 10 +- tools/configure/i2c_timing_selection.py | 16 +- tools/configure/ir.py | 345 +- tools/configure/kernel.py | 25 +- tools/configure/matrix.py | 10 +- tools/configure/memory.py | 140 +- tools/configure/onboarding.py | 12 +- tools/configure/providers/common.py | 56 +- tools/configure/providers/descriptor.py | 2 + tools/configure/providers/legacy_transport.py | 22 +- tools/configure/resource_graph.py | 15 +- tools/configure/runtime.py | 4 +- tools/configure/runtime_power.py | 2 +- tools/configure/secondary_controllers.py | 27 +- tools/configure/selected_contracts.py | 2 +- tools/configure/selection.py | 13 +- tools/configure/test_bindings.py | 13 +- tools/configure/test_bundle_transaction.py | 13 +- tools/configure/test_can.py | 2 +- tools/configure/test_cold_preflight.py | 14 +- tools/configure/test_configure.py | 75 +- tools/configure/test_construction_contract.py | 14 +- .../configure/test_controller_integration.py | 10 +- tools/configure/test_descriptors.py | 8 + tools/configure/test_documentation.py | 14 + tools/configure/test_ethernet.py | 4 +- .../test_ethernet_frames_configuration.py | 4 +- tools/configure/test_exti_edge_types.py | 8 +- tools/configure/test_f407_i2s2.py | 8 +- .../configure/test_f407_i2s2_configuration.py | 14 +- tools/configure/test_fact_references.py | 6 +- tools/configure/test_factory_features.py | 10 +- tools/configure/test_family_loading.py | 4 +- tools/configure/test_fixture_assembly.py | 49 + tools/configure/test_flash_polling_irq.py | 20 +- tools/configure/test_g474.py | 18 +- .../test_g474_adc_group_configuration.py | 20 +- .../test_g474_control_configuration.py | 2 +- .../test_g474_coupled_io_configuration.py | 22 +- tools/configure/test_g474_exti.py | 6 +- .../test_g474_pwm_shared_configuration.py | 7 +- tools/configure/test_gd32_exti_generation.py | 4 +- tools/configure/test_gpio_phase.py | 9 +- tools/configure/test_h743_clock.py | 4 +- .../test_h743_high_clock_configuration.py | 10 +- tools/configure/test_i2c_modes.py | 2 +- .../test_i2c_timing_configuration.py | 12 + tools/configure/test_import_dependencies.py | 6 +- .../test_io_evolution_configuration.py | 47 +- tools/configure/test_io_traits.py | 4 +- tools/configure/test_ir_unification.py | 80 + tools/configure/test_irq_profiles.py | 10 +- tools/configure/test_kernel.py | 2 +- tools/configure/test_kernel_assembly.py | 10 +- tools/configure/test_legacy_devices.py | 25 +- tools/configure/test_legacy_transport.py | 2 +- tools/configure/test_matrix.py | 26 +- tools/configure/test_memory_regions.py | 18 +- tools/configure/test_metadata_binding.py | 4 +- tools/configure/test_mpu_gpio_policy.py | 10 +- tools/configure/test_mpu_memory.py | 37 +- tools/configure/test_part_facts.py | 29 +- tools/configure/test_physical_facts.py | 16 +- tools/configure/test_pin_states.py | 16 +- tools/configure/test_providers.py | 28 +- tools/configure/test_rtc_lptim.py | 23 +- tools/configure/test_runtime_power.py | 20 +- tools/configure/test_secondary_controllers.py | 12 +- .../test_selected_contract_authority.py | 12 +- tools/configure/test_selected_io_plans.py | 4 +- tools/configure/test_soc_facts.py | 10 +- tools/configure/test_static_calls.py | 90 + tools/configure/test_stream_modes.py | 6 +- tools/configure/test_threadx.py | 12 +- tools/configure/test_timer_advanced.py | 4 +- tools/configure/test_toml.py | 78 +- tools/configure/test_u575_mpu_geometry.py | 4 +- tools/configure/test_u575_sram4_memory.py | 2 +- .../test_uart_format_configuration.py | 7 +- .../test_uart_rtscts_configuration.py | 14 +- tools/configure/test_usb_device.py | 8 +- tools/configure/tests/assembly_fixture.py | 88 + .../tests/providers/gd32f303/test_provider.py | 24 +- .../providers/stm32f103/test_provider.py | 46 +- .../providers/stm32g474/test_acquisition.py | 4 +- .../providers/stm32g474/test_mainstream.py | 17 +- .../providers/stm32g474/test_provider.py | 37 +- .../providers/stm32h743/test_peripherals.py | 2 +- .../tests/providers/stm32h743/test_spi_dma.py | 2 +- .../stm32h743/test_uart_continuous.py | 2 +- .../providers/stm32h743/test_uart_duplex.py | 4 +- .../tests/providers/stm32u575/test_devices.py | 6 +- .../tests/providers/stm32u575/test_lptim.py | 2 +- .../providers/stm32u575/test_provider.py | 30 +- .../tests/providers/stm32u575/test_rtc.py | 2 +- .../providers/stm32u575/test_uart_chain.py | 4 +- tools/configure/timer_advanced.py | 17 +- tools/configure/usb_device.py | 13 +- tools/dev/projects.py | 4 +- tools/dev/qualification.py | 138 +- tools/documentation/check.py | 6 +- tools/documentation/cli.py | 6 +- tools/documentation/delivery.py | 117 +- tools/documentation/status.py | 1 + tools/documentation/test_delivery.py | 105 +- tools/documentation/test_status.py | 24 + .../documentation/test_verify_publication.py | 2 +- tools/evidence/candidate.py | 67 +- tools/evidence/common.py | 5 + tools/maintenance/README.md | 56 + tools/maintenance/capabilities.py | 66 +- tools/maintenance/compatibility.py | 23 +- tools/maintenance/local_ci.py | 130 + tools/maintenance/work_packages.py | 8 +- tools/measurement/README.md | 5 + tools/measurement/compare.py | 68 +- tools/measurement/resources.py | 4 +- tools/testing/test_compatibility.py | 31 + tools/testing/test_h743_flash_ram_closure.py | 8 +- tools/testing/test_local_ci.py | 265 ++ tools/testing/test_maintenance.py | 10 +- tools/testing/test_support_evidence.py | 178 + 263 files changed, 15457 insertions(+), 5377 deletions(-) create mode 100644 docs/locale/en/LC_MESSAGES/status/ledgers/soc-ip-layout.po create mode 100644 docs/locale/zh_CN/LC_MESSAGES/status/ledgers/soc-ip-layout.po create mode 100644 docs/status/ledgers/soc-ip-layout.md create mode 100644 soc/gd32/gd32f470/private/gd32f470_spi_cold.h create mode 100644 soc/stm32/stm32f407/private/stm32f407_spi_cold.h create mode 100644 tools/configure/calls.py create mode 100644 tools/configure/test_fixture_assembly.py create mode 100644 tools/configure/test_ir_unification.py create mode 100644 tools/configure/test_static_calls.py create mode 100644 tools/configure/tests/assembly_fixture.py create mode 100644 tools/maintenance/local_ci.py create mode 100644 tools/testing/test_local_ci.py create mode 100644 tools/testing/test_support_evidence.py diff --git a/.github/workflows/test_platform_ci.py b/.github/workflows/test_platform_ci.py index fa7025f97..42aa2ac36 100644 --- a/.github/workflows/test_platform_ci.py +++ b/.github/workflows/test_platform_ci.py @@ -1327,11 +1327,11 @@ def test_offline_navigation_is_sealed_opaque_before_actual_host_identity(self): q.mkdir(parents=True) env.update(Q=str(q), NEXUS_CHECKOUT_VALIDATED='true', NEXUS_JOB_RESULT='success', REAL_EVIDENCE_STORE='true') - # Use the actual maintained resolver and its canonical full IR. + # Seal the real resolver's one-way serialized artifact projection. navigation = {} - resolved = configure.resolve( + resolved = configure.resolve_ir( ROOT / 'tools/configure/assemblies/qiming-baremetal.toml', - ROOT, input_paths=navigation) + ROOT, input_paths=navigation).to_dict() report_argument = re.search(r'--report "([^"\n]+)"', self.step('reproduce')['run']).group(1) report_path = Path(report_argument.replace('$Q', str(q)).replace('$E', env['E'])) diff --git a/AGENTS.md b/AGENTS.md index c53cd0b0d..6ef19607f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,8 +29,13 @@ They do not establish current support or qualification. - SoC facts and Board `board.json` are machine-maintained JSON. One authored TOML assembly resolves through a validated typed IR into one atomic bundle. CMake targets own software dependencies. Do not introduce Kconfig/cache fallback, - competing configuration authorities or a second source build graph. Legacy - schema-1 JSON fixtures may be read during migration, never silently mixed. + competing configuration authorities or a second source build graph. Authored + platform input is TOML schema 2 only; reject legacy JSON assemblies. Production + resolution and emitters use immutable named records, without a mapping facade + or duplicate decision values. JSON facts and keyed immutable indexes remain + explicit leaf data; resolved JSON is a one-way audit/artifact projection. + Frozen historical evidence retains its original bytes and read-only verifiers; + that retention never reopens a legacy production configuration path. - Reject unknown fields, unsupported modes, resource conflicts and stale inputs. Never repair configuration by silently selecting a different mode or Board. - Distinguish a chip capability from a reviewed PCB route and physical HIL. diff --git a/cmake/README.md b/cmake/README.md index 849d4ffee..be26e6059 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -57,6 +57,6 @@ Actual include-closure, selected ARM and relocated-SDK checks establish the migration's software scope; the version inventory alone establishes none of it. Board packages are read-only `board.json` facts with provenance and reviewed -wiring. SoC resources and route catalogs live in `soc/`. Unreviewed +wiring. SoC resources and route catalogs live in `soc//`. Unreviewed physical connections cannot be enabled by claiming chip-level capability. See [integration contracts](../docs/design/integration-contracts.md). diff --git a/dependencies/platform-compatibility.json b/dependencies/platform-compatibility.json index 01c9f1636..e5bc1a631 100644 --- a/dependencies/platform-compatibility.json +++ b/dependencies/platform-compatibility.json @@ -11,7 +11,7 @@ "schemas": { "assembly_authoring": 2, "assembly_projection": 1, - "typed_ir": 1, + "typed_ir": 2, "board": 1, "soc": 1, "routes": 1, diff --git a/docs/assets/diagrams/README.md b/docs/assets/diagrams/README.md index f8f6f3f93..d42ef7a9b 100644 --- a/docs/assets/diagrams/README.md +++ b/docs/assets/diagrams/README.md @@ -69,7 +69,12 @@ python tools/dev/dev.py docs check --full \ ![FIG-03:构建输入/产物链,生成失败使owned bundle失效](configuration-generation.svg) -边表示构建时输入和产物传递,不表示运行时调用。严格解析不能修复非法字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。ELF/资源报告不构成实板资格。 +边表示构建时输入和产物传递,不表示运行时调用。TOML 唯一入口先得到冻结 +`AuthoredAssembly` 意图,再与 SoC/Board 事实校验得到具名 `ConfigurationIR`。 +同一 IR 驱动 SoC 的静态定义/冷态调用与单向 JSON/配置/内存投影;全部经过 +同一 bundle 事务发布。`resolved.json` 不回流为装配输入。严格解析不能修复非法 +字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。 +ELF/资源报告不构成实板资格。 依据:[配置器](../../../tools/configure/configure.py)、[不可变IR](../../../tools/configure/ir.py)、[bundle事务](../../../tools/configure/bundle.py)、[构建合同](../../design/build-system.md)。 [可编辑DOT](configuration-generation.dot);实施关联:DOC-45。 diff --git a/docs/assets/diagrams/configuration-generation.dot b/docs/assets/diagrams/configuration-generation.dot index 3d4ae7d49..449042a6d 100644 --- a/docs/assets/diagrams/configuration-generation.dot +++ b/docs/assets/diagrams/configuration-generation.dot @@ -1,5 +1,5 @@ // FIG-03 current: build-time inputs and artifacts. -// Code basis: 0111c8d3b9c7b37d258b9c401070305b13b6f3a1. +// Code basis: tools/configure/authored.py, configure.py, ir.py and bindings.py. // Edges denote configuration/production flow, not runtime I/O. digraph configurationGeneration { graph [label="FIG-03 当前配置与原子生成\nFIG-03 Current configuration and generation", fontname="Noto Sans CJK SC", fontsize=15, labelloc=t, rankdir=TB, bgcolor="white", pad=0.25, nodesep=0.30, ranksep=0.50]; @@ -11,24 +11,31 @@ digraph configurationGeneration { assembly [label="一份 authored TOML\none authored TOML assembly", fillcolor="#f8fafc"]; socFacts [label="SoC / CPU / routes 事实\nSoC / CPU / route facts", fillcolor="#f8fafc"]; boardFacts [label="Board 接线与来源事实\nBoard wiring and provenance", fillcolor="#f8fafc"]; + authored [label="严格 TOML 字段检查 → 冻结用户意图\nstrict TOML fields -> frozen AuthoredAssembly"]; resolver [label="严格解析:字段、模式、资源、输入身份\nstrict field / mode / resource / identity checks"]; - typedIr [label="不可变 typed IR\nimmutable typed IR"]; + typedIr [label="不可变具名 IR:每个决策只存一次\nimmutable named IR: each decision stored once"]; + construction [label="SoC 构造:静态定义与冷态调用\nSoC Construction: static definitions and cold calls"]; bundle [label="事务生成与原子发布\ntransactional generation and atomic publish"]; - outputs [label="静态实例 / factory / 配置 / 内存预算\nstatic instances / factory / config / budgets"]; + outputs [label="静态实例 / factory / 配置 / 内存预算 / resolved.json\nstatic instances / factory / config / budgets / resolved.json"]; targets [label="CMake:唯一软件目标图\nCMake: sole software target graph"]; ninja [label="Ninja:编译、链接、资源检查\nNinja: compile / link / resource checks"]; firmware [label="ELF / BIN / map / 资源报告\nELF / BIN / map / resource reports", fillcolor="#f0fdf4"]; rejected [label="拒绝 / 生成失败\nrejected input or generation failure", fillcolor="#fff7ed"]; invalidated [label="owned bundle 失效,不暴露半成品\ninvalidate owned bundle, no partial outputs", fillcolor="#fff7ed"]; - assembly -> resolver [label="产品选择\nproduct selection"]; + assembly -> authored [label="唯一装配入口\nsole assembly input"]; + authored -> resolver [label="尚未准入的选择\nunresolved selections"]; socFacts -> resolver [label="芯片能力\nsilicon capability"]; boardFacts -> resolver [label="Board 授权\nBoard authorization"]; resolver -> typedIr [label="验证通过\nvalidated"]; - typedIr -> bundle [label="生成选定内容\ngenerate selected content"]; + typedIr -> construction [label="具名字段\nnamed fields"]; + construction -> bundle [label="生成 binding C 字节\nrender binding C bytes"]; + typedIr -> bundle [label="单向 JSON / 配置 / 内存投影\none-way JSON / config / memory projection"]; bundle -> outputs [label="成功发布\natomic publish"]; outputs -> targets [label="配置与生成源\nconfiguration and generated sources"]; targets -> ninja [label="执行目标图\nexecute target graph"]; ninja -> firmware [label="本次实际产物\nactual artifacts of this execution"]; + authored -> rejected [label="语法 / 字段非法\ninvalid syntax / fields"]; + construction -> rejected [label="生成失败\nemission failure"]; resolver -> rejected [label="非法 / 冲突 / 变化\ninvalid / conflict / input changed"]; bundle -> rejected [label="生成或发布失败\ngeneration or publish failure"]; rejected -> invalidated [label="仅清理拥有的输出\ninvalidate owned outputs only"]; diff --git a/docs/assets/diagrams/configuration-generation.svg b/docs/assets/diagrams/configuration-generation.svg index 960e22d21..afe1288ac 100644 --- a/docs/assets/diagrams/configuration-generation.svg +++ b/docs/assets/diagrams/configuration-generation.svg @@ -1,28 +1,35 @@ - + FIG-03 当前配置与原子生成 FIG-03 Current configuration and generation一份 authored TOML one authored TOML assembly; SoC / CPU / routes 事实 SoC / CPU / route facts; Board 接线与来源事实 -Board wiring and provenance; 严格解析:字段、模式、资源、输入身份 -strict field / mode / resource / identity checks; 不可变 typed IR -immutable typed IR; 事务生成与原子发布 -transactional generation and atomic publish; 静态实例 / factory / 配置 / 内存预算 -static instances / factory / config / budgets; CMake:唯一软件目标图 +Board wiring and provenance; 严格 TOML 字段检查 → 冻结用户意图 +strict TOML fields -> frozen AuthoredAssembly; 严格解析:字段、模式、资源、输入身份 +strict field / mode / resource / identity checks; 不可变具名 IR:每个决策只存一次 +immutable named IR: each decision stored once; SoC 构造:静态定义与冷态调用 +SoC Construction: static definitions and cold calls; 事务生成与原子发布 +transactional generation and atomic publish; 静态实例 / factory / 配置 / 内存预算 / resolved.json +static instances / factory / config / budgets / resolved.json; CMake:唯一软件目标图 CMake: sole software target graph; Ninja:编译、链接、资源检查 Ninja: compile / link / resource checks; ELF / BIN / map / 资源报告 ELF / BIN / map / resource reports; 拒绝 / 生成失败 rejected input or generation failure; owned bundle 失效,不暴露半成品 -invalidate owned bundle, no partial outputs; 产品选择 -product selection; 芯片能力 +invalidate owned bundle, no partial outputs; 唯一装配入口 +sole assembly input; 尚未准入的选择 +unresolved selections; 芯片能力 silicon capability; Board 授权 Board authorization; 验证通过 -validated; 生成选定内容 -generate selected content; 成功发布 +validated; 具名字段 +named fields; 生成 binding C 字节 +render binding C bytes; 单向 JSON / 配置 / 内存投影 +one-way JSON / config / memory projection; 成功发布 atomic publish; 配置与生成源 configuration and generated sources; 执行目标图 execute target graph; 本次实际产物 -actual artifacts of this execution; 非法 / 冲突 / 变化 +actual artifacts of this execution; 语法 / 字段非法 +invalid syntax / fields; 生成失败 +emission failure; 非法 / 冲突 / 变化 invalid / conflict / input changed; 生成或发布失败 generation or publish failure; 仅清理拥有的输出 invalidate owned outputs only @@ -182,300 +189,375 @@ invalidate owned outputs only - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -484,1381 +566,1871 @@ invalidate owned outputs only - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/diagrams/diagram-index.csv b/docs/assets/diagrams/diagram-index.csv index 93642fd19..e0a5671da 100644 --- a/docs/assets/diagrams/diagram-index.csv +++ b/docs/assets/diagrams/diagram-index.csv @@ -1,7 +1,7 @@ diagram_id,title,kind,source_dot,rendered_svg,render_status,review_status,code_basis,task_id,scope FIG-01,全平台分层与外部边界,current,platform-architecture.dot,platform-architecture.svg,rendered,source_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-43,产品外置;可选组件与OS;运行访问和构建事实逐边区分 FIG-02,选定编译目标依赖,reference,build-targets.dot,build-targets.svg,rendered,reference_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-44,H743 baremetal IRQ UART;准确PUBLIC/PRIVATE/INTERFACE/OBJECT关系 -FIG-03,配置与原子生成,current,configuration-generation.dot,configuration-generation.svg,rendered,source_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-45,构建输入/产物链,生成失败使owned bundle失效 +FIG-03,配置与原子生成,current,configuration-generation.dot,configuration-generation.svg,rendered,source_reviewed,tools/configure/authored.py; configure.py; ir.py; bindings.py; bundle.py,DOC-45,TOML唯一入口、冻结用户意图、具名IR、单向投影与原子发布;不表示运行时调用或硬件资格 FIG-04,factory与静态实例,reference,factory-instances.dot,factory-instances.svg,rendered,reference_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-46,按需选入的两同类实例示意;同模式ops共享,controller/endpoint独立 FIG-05,显式启动停机与失败撤回,current,platform-lifecycle.dot,platform-lifecycle.svg,rendered,source_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-47,生成生命周期骨架;每个SoC私有启动/清理保持具体合同 FIG-06,Core请求状态与借用,current,request-lifecycle.dot,request-lifecycle.svg,rendered,source_reviewed,0111c8d3b9c7b37d258b9c401070305b13b6f3a1,DOC-48,Core helper允许的迁移与调用前提;不承诺各provider恢复API diff --git a/docs/assets/diagrams/labels.json b/docs/assets/diagrams/labels.json index e5203f88a..01fd8bee7 100644 --- a/docs/assets/diagrams/labels.json +++ b/docs/assets/diagrams/labels.json @@ -25,19 +25,15 @@ "SoC / CPU / routes 事实\\nSoC / CPU / route facts": "SoC / CPU / route facts", "Board 接线与来源事实\\nBoard wiring and provenance": "Board wiring and provenance", "严格解析:字段、模式、资源、输入身份\\nstrict field / mode / resource / identity checks": "strict field / mode / resource / identity checks", - "不可变 typed IR\\nimmutable typed IR": "immutable typed IR", "事务生成与原子发布\\ntransactional generation and atomic publish": "transactional generation and atomic publish", - "静态实例 / factory / 配置 / 内存预算\\nstatic instances / factory / config / budgets": "static instances / factory / config / budgets", "CMake:唯一软件目标图\\nCMake: sole software target graph": "CMake: sole software target graph", "Ninja:编译、链接、资源检查\\nNinja: compile / link / resource checks": "Ninja: compile / link / resource checks", "ELF / BIN / map / 资源报告\\nELF / BIN / map / resource reports": "ELF / BIN / map / resource reports", "拒绝 / 生成失败\\nrejected input or generation failure": "rejected input or generation failure", "owned bundle 失效,不暴露半成品\\ninvalidate owned bundle, no partial outputs": "invalidate owned bundle, no partial outputs", - "产品选择\\nproduct selection": "product selection", - "芯片能力\\nsilicon capability": "silicon capability", - "Board 授权\\nBoard authorization": "Board authorization", + "芯片能力\\nsilicon capability": "silicon\ncapability", + "Board 授权\\nBoard authorization": "Board\nauthorization", "验证通过\\nvalidated": "validated", - "生成选定内容\\ngenerate selected content": "generate selected content", "成功发布\\natomic publish": "atomic publish", "配置与生成源\\nconfiguration and generated sources": "configuration and generated sources", "执行目标图\\nexecute target graph": "execute target graph", @@ -909,7 +905,18 @@ "Yes: reviewed compatibility": "Yes: reviewed compatibility", "No: retain exact scope": "No: retain exact scope", "Layout / flags / order / reset compatibility reviewed;\\nevery chip erratum explicitly handled?": "Layout / flags / order / reset compatibility reviewed;\nevery chip erratum explicitly handled?", - "soc///drivers/\\nExact or not-yet-reviewed protocol": "soc///drivers/\nExact or not-yet-reviewed protocol" + "soc///drivers/\\nExact or not-yet-reviewed protocol": "soc///drivers/\nExact or not-yet-reviewed protocol", + "SoC 构造:静态定义与冷态调用\\nSoC Construction: static definitions and cold calls": "SoC Construction: static definitions and cold calls", + "尚未准入的选择\\nunresolved selections": "unresolved\nselections", + "静态实例 / factory / 配置 / 内存预算 / resolved.json\\nstatic instances / factory / config / budgets / resolved.json": "static instances / factory / config / budgets / resolved.json", + "唯一装配入口\\nsole assembly input": "sole assembly input", + "生成失败\\nemission failure": "emission failure", + "具名字段\\nnamed fields": "named fields", + "生成 binding C 字节\\nrender binding C bytes": "render binding C bytes", + "语法 / 字段非法\\ninvalid syntax / fields": "invalid syntax / fields", + "单向 JSON / 配置 / 内存投影\\none-way JSON / config / memory projection": "one-way JSON /\nconfig / memory projection", + "严格 TOML 字段检查 → 冻结用户意图\\nstrict TOML fields -> frozen AuthoredAssembly": "strict TOML fields -> frozen AuthoredAssembly", + "不可变具名 IR:每个决策只存一次\\nimmutable named IR: each decision stored once": "immutable named IR: each decision stored once" }, "zh_CN": { "FIG-02 H743 / baremetal / IRQ UART:选定目标依赖\n箭头:依赖者 → 被依赖目标;不是运行调用": "FIG-02 H743 / baremetal / IRQ UART:选定目标依赖\n箭头:依赖者 → 被依赖目标;不是运行调用", @@ -936,19 +943,15 @@ "SoC / CPU / routes 事实\\nSoC / CPU / route facts": "SoC / CPU / routes 事实", "Board 接线与来源事实\\nBoard wiring and provenance": "Board 接线与来源事实", "严格解析:字段、模式、资源、输入身份\\nstrict field / mode / resource / identity checks": "严格解析:字段、模式、资源、输入身份", - "不可变 typed IR\\nimmutable typed IR": "不可变 typed IR", "事务生成与原子发布\\ntransactional generation and atomic publish": "事务生成与原子发布", - "静态实例 / factory / 配置 / 内存预算\\nstatic instances / factory / config / budgets": "静态实例 / factory / 配置 / 内存预算", "CMake:唯一软件目标图\\nCMake: sole software target graph": "CMake:唯一软件目标图", "Ninja:编译、链接、资源检查\\nNinja: compile / link / resource checks": "Ninja:编译、链接、资源检查", "ELF / BIN / map / 资源报告\\nELF / BIN / map / resource reports": "ELF / BIN / map / 资源报告", "拒绝 / 生成失败\\nrejected input or generation failure": "拒绝 / 生成失败", "owned bundle 失效,不暴露半成品\\ninvalidate owned bundle, no partial outputs": "owned bundle 失效,不暴露半成品", - "产品选择\\nproduct selection": "产品选择", "芯片能力\\nsilicon capability": "芯片能力", "Board 授权\\nBoard authorization": "Board 授权", "验证通过\\nvalidated": "验证通过", - "生成选定内容\\ngenerate selected content": "生成选定内容", "成功发布\\natomic publish": "成功发布", "配置与生成源\\nconfiguration and generated sources": "配置与生成源", "执行目标图\\nexecute target graph": "执行目标图", @@ -1820,6 +1823,17 @@ "Yes: reviewed compatibility": "是:兼容性已复核", "No: retain exact scope": "否:保留精确范围", "Layout / flags / order / reset compatibility reviewed;\\nevery chip erratum explicitly handled?": "布局/位/顺序/复位兼容性已复核;\n各芯片勘误均已显式处理?", - "soc///drivers/\\nExact or not-yet-reviewed protocol": "soc///drivers/\n精确或尚未复核的协议" + "soc///drivers/\\nExact or not-yet-reviewed protocol": "soc///drivers/\n精确或尚未复核的协议", + "SoC 构造:静态定义与冷态调用\\nSoC Construction: static definitions and cold calls": "SoC 构造:静态定义与冷态调用", + "尚未准入的选择\\nunresolved selections": "尚未准入的选择", + "静态实例 / factory / 配置 / 内存预算 / resolved.json\\nstatic instances / factory / config / budgets / resolved.json": "静态实例 / factory / 配置 / 内存预算 / resolved.json", + "唯一装配入口\\nsole assembly input": "唯一装配入口", + "生成失败\\nemission failure": "生成失败", + "具名字段\\nnamed fields": "具名字段", + "生成 binding C 字节\\nrender binding C bytes": "生成 binding C 字节", + "语法 / 字段非法\\ninvalid syntax / fields": "语法 / 字段非法", + "单向 JSON / 配置 / 内存投影\\none-way JSON / config / memory projection": "单向 JSON / 配置 / 内存投影", + "严格 TOML 字段检查 → 冻结用户意图\\nstrict TOML fields -> frozen AuthoredAssembly": "严格 TOML 字段检查 → 冻结用户意图", + "不可变具名 IR:每个决策只存一次\\nimmutable named IR: each decision stored once": "不可变具名 IR:每个决策只存一次" } } diff --git a/docs/assets/diagrams/localized/en/configuration-generation.dot b/docs/assets/diagrams/localized/en/configuration-generation.dot index 8e4f28389..8eee30d4a 100644 --- a/docs/assets/diagrams/localized/en/configuration-generation.dot +++ b/docs/assets/diagrams/localized/en/configuration-generation.dot @@ -1,5 +1,5 @@ // FIG-03 current: build-time inputs and artifacts. -// Code basis: 0111c8d3b9c7b37d258b9c401070305b13b6f3a1. +// Code basis: tools/configure/authored.py, configure.py, ir.py and bindings.py. // Edges denote configuration/production flow, not runtime I/O. digraph configurationGeneration { graph [label="FIG-03 Current configuration and generation", fontname="Noto Sans CJK SC", fontsize=15, labelloc=t, rankdir=TB, bgcolor="white", pad=0.25, nodesep=0.30, ranksep=0.50]; @@ -11,24 +11,31 @@ digraph configurationGeneration { assembly [label="one authored TOML assembly", fillcolor="#f8fafc"]; socFacts [label="SoC / CPU / route facts", fillcolor="#f8fafc"]; boardFacts [label="Board wiring and provenance", fillcolor="#f8fafc"]; + authored [label="strict TOML fields -> frozen AuthoredAssembly"]; resolver [label="strict field / mode / resource / identity checks"]; - typedIr [label="immutable typed IR"]; + typedIr [label="immutable named IR: each decision stored once"]; + construction [label="SoC Construction: static definitions and cold calls"]; bundle [label="transactional generation and atomic publish"]; - outputs [label="static instances / factory / config / budgets"]; + outputs [label="static instances / factory / config / budgets / resolved.json"]; targets [label="CMake: sole software target graph"]; ninja [label="Ninja: compile / link / resource checks"]; firmware [label="ELF / BIN / map / resource reports", fillcolor="#f0fdf4"]; rejected [label="rejected input or generation failure", fillcolor="#fff7ed"]; invalidated [label="invalidate owned bundle, no partial outputs", fillcolor="#fff7ed"]; - assembly -> resolver [label="product selection"]; - socFacts -> resolver [label="silicon capability"]; - boardFacts -> resolver [label="Board authorization"]; + assembly -> authored [label="sole assembly input"]; + authored -> resolver [label="unresolved\nselections"]; + socFacts -> resolver [label="silicon\ncapability"]; + boardFacts -> resolver [label="Board\nauthorization"]; resolver -> typedIr [label="validated"]; - typedIr -> bundle [label="generate selected content"]; + typedIr -> construction [label="named fields"]; + construction -> bundle [label="render binding C bytes"]; + typedIr -> bundle [label="one-way JSON /\nconfig / memory projection"]; bundle -> outputs [label="atomic publish"]; outputs -> targets [label="configuration and generated sources"]; targets -> ninja [label="execute target graph"]; ninja -> firmware [label="actual artifacts of this execution"]; + authored -> rejected [label="invalid syntax / fields"]; + construction -> rejected [label="emission failure"]; resolver -> rejected [label="invalid / conflict / input changed"]; bundle -> rejected [label="generation or publish failure"]; rejected -> invalidated [label="invalidate owned outputs only"]; diff --git a/docs/assets/diagrams/localized/en/configuration-generation.svg b/docs/assets/diagrams/localized/en/configuration-generation.svg index b640df9e6..1be124b56 100644 --- a/docs/assets/diagrams/localized/en/configuration-generation.svg +++ b/docs/assets/diagrams/localized/en/configuration-generation.svg @@ -1,6 +1,10 @@ - -FIG-03 Current configuration and generationone authored TOML assembly; SoC / CPU / route facts; Board wiring and provenance; strict field / mode / resource / identity checks; immutable typed IR; transactional generation and atomic publish; static instances / factory / config / budgets; CMake: sole software target graph; Ninja: compile / link / resource checks; ELF / BIN / map / resource reports; rejected input or generation failure; invalidate owned bundle, no partial outputs; product selection; silicon capability; Board authorization; validated; generate selected content; atomic publish; configuration and generated sources; execute target graph; actual artifacts of this execution; invalid / conflict / input changed; generation or publish failure; invalidate owned outputs only + +FIG-03 Current configuration and generationone authored TOML assembly; SoC / CPU / route facts; Board wiring and provenance; strict TOML fields -> frozen AuthoredAssembly; strict field / mode / resource / identity checks; immutable named IR: each decision stored once; SoC Construction: static definitions and cold calls; transactional generation and atomic publish; static instances / factory / config / budgets / resolved.json; CMake: sole software target graph; Ninja: compile / link / resource checks; ELF / BIN / map / resource reports; rejected input or generation failure; invalidate owned bundle, no partial outputs; sole assembly input; unresolved +selections; silicon +capability; Board +authorization; validated; named fields; render binding C bytes; one-way JSON / +config / memory projection; atomic publish; configuration and generated sources; execute target graph; actual artifacts of this execution; invalid syntax / fields; emission failure; invalid / conflict / input changed; generation or publish failure; invalidate owned outputs only @@ -127,971 +131,1288 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + - + - + - + - + - - + - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/diagrams/localized/zh_CN/configuration-generation.dot b/docs/assets/diagrams/localized/zh_CN/configuration-generation.dot index cb35f16c1..3bd1ef791 100644 --- a/docs/assets/diagrams/localized/zh_CN/configuration-generation.dot +++ b/docs/assets/diagrams/localized/zh_CN/configuration-generation.dot @@ -1,5 +1,5 @@ // FIG-03 current: build-time inputs and artifacts. -// Code basis: 0111c8d3b9c7b37d258b9c401070305b13b6f3a1. +// Code basis: tools/configure/authored.py, configure.py, ir.py and bindings.py. // Edges denote configuration/production flow, not runtime I/O. digraph configurationGeneration { graph [label="FIG-03 当前配置与原子生成", fontname="Noto Sans CJK SC", fontsize=15, labelloc=t, rankdir=TB, bgcolor="white", pad=0.25, nodesep=0.30, ranksep=0.50]; @@ -11,24 +11,31 @@ digraph configurationGeneration { assembly [label="一份 authored TOML", fillcolor="#f8fafc"]; socFacts [label="SoC / CPU / routes 事实", fillcolor="#f8fafc"]; boardFacts [label="Board 接线与来源事实", fillcolor="#f8fafc"]; + authored [label="严格 TOML 字段检查 → 冻结用户意图"]; resolver [label="严格解析:字段、模式、资源、输入身份"]; - typedIr [label="不可变 typed IR"]; + typedIr [label="不可变具名 IR:每个决策只存一次"]; + construction [label="SoC 构造:静态定义与冷态调用"]; bundle [label="事务生成与原子发布"]; - outputs [label="静态实例 / factory / 配置 / 内存预算"]; + outputs [label="静态实例 / factory / 配置 / 内存预算 / resolved.json"]; targets [label="CMake:唯一软件目标图"]; ninja [label="Ninja:编译、链接、资源检查"]; firmware [label="ELF / BIN / map / 资源报告", fillcolor="#f0fdf4"]; rejected [label="拒绝 / 生成失败", fillcolor="#fff7ed"]; invalidated [label="owned bundle 失效,不暴露半成品", fillcolor="#fff7ed"]; - assembly -> resolver [label="产品选择"]; + assembly -> authored [label="唯一装配入口"]; + authored -> resolver [label="尚未准入的选择"]; socFacts -> resolver [label="芯片能力"]; boardFacts -> resolver [label="Board 授权"]; resolver -> typedIr [label="验证通过"]; - typedIr -> bundle [label="生成选定内容"]; + typedIr -> construction [label="具名字段"]; + construction -> bundle [label="生成 binding C 字节"]; + typedIr -> bundle [label="单向 JSON / 配置 / 内存投影"]; bundle -> outputs [label="成功发布"]; outputs -> targets [label="配置与生成源"]; targets -> ninja [label="执行目标图"]; ninja -> firmware [label="本次实际产物"]; + authored -> rejected [label="语法 / 字段非法"]; + construction -> rejected [label="生成失败"]; resolver -> rejected [label="非法 / 冲突 / 变化"]; bundle -> rejected [label="生成或发布失败"]; rejected -> invalidated [label="仅清理拥有的输出"]; diff --git a/docs/assets/diagrams/localized/zh_CN/configuration-generation.svg b/docs/assets/diagrams/localized/zh_CN/configuration-generation.svg index 2b49e0776..2910c0182 100644 --- a/docs/assets/diagrams/localized/zh_CN/configuration-generation.svg +++ b/docs/assets/diagrams/localized/zh_CN/configuration-generation.svg @@ -1,6 +1,6 @@ - -FIG-03 当前配置与原子生成一份 authored TOML; SoC / CPU / routes 事实; Board 接线与来源事实; 严格解析:字段、模式、资源、输入身份; 不可变 typed IR; 事务生成与原子发布; 静态实例 / factory / 配置 / 内存预算; CMake:唯一软件目标图; Ninja:编译、链接、资源检查; ELF / BIN / map / 资源报告; 拒绝 / 生成失败; owned bundle 失效,不暴露半成品; 产品选择; 芯片能力; Board 授权; 验证通过; 生成选定内容; 成功发布; 配置与生成源; 执行目标图; 本次实际产物; 非法 / 冲突 / 变化; 生成或发布失败; 仅清理拥有的输出 + +FIG-03 当前配置与原子生成一份 authored TOML; SoC / CPU / routes 事实; Board 接线与来源事实; 严格 TOML 字段检查 → 冻结用户意图; 严格解析:字段、模式、资源、输入身份; 不可变具名 IR:每个决策只存一次; SoC 构造:静态定义与冷态调用; 事务生成与原子发布; 静态实例 / factory / 配置 / 内存预算 / resolved.json; CMake:唯一软件目标图; Ninja:编译、链接、资源检查; ELF / BIN / map / 资源报告; 拒绝 / 生成失败; owned bundle 失效,不暴露半成品; 唯一装配入口; 尚未准入的选择; 芯片能力; Board 授权; 验证通过; 具名字段; 生成 binding C 字节; 单向 JSON / 配置 / 内存投影; 成功发布; 配置与生成源; 执行目标图; 本次实际产物; 语法 / 字段非法; 生成失败; 非法 / 冲突 / 变化; 生成或发布失败; 仅清理拥有的输出 @@ -100,837 +100,1139 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + + - + - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/delivery/architecture.md b/docs/delivery/architecture.md index 0b998553c..89b98a47c 100644 --- a/docs/delivery/architecture.md +++ b/docs/delivery/architecture.md @@ -100,13 +100,21 @@ flowchart LR 代码或软件依赖图。未知键、失效输入、非法模式、引脚/IRQ/timer 冲突、越界容量 都会拒绝;失败重配置使旧 bundle 失效,不静默使用上一次成功结果。 -SoC/Board 事实采用 JSON,使用者编写 schema-2 TOML。严格 resolver 产生不可变 -typed IR;各生成器消费同一结果。`resolved.json` 是该结果的序列化,不能作为手工 -配置回填。迁移期 schema-1 JSON 夹具有显式入口,不与 TOML 混合叠加。 - -IR 身份、预算和各类 controller options 使用冻结 record,IRQ 为嵌套 record, -ADC channels/sample times 为 tuple;完整 JSON 事实投影仍保留只读 mapping, -不能声称所有字段均有名义强类型。`soc///configure/`(Native 为 +SoC/Board 事实采用 JSON,平台装配只接受 schema-2 TOML。`AuthoredAssembly` +保存冻结的用户意图;`resolve_ir()` 严格校验事实、模式和资源后直接产生不可变 +`ConfigurationIR`。生产 resolver、factory 和 emitter 使用具名字段,不提供 +Mapping 接口或保存第二份 `values` 决策副本。`to_dict()` 派生 `resolved.json` +供审计与独立产物检查,不能回填为配置;旧 schema-1 JSON 装配入口已删除。 +历史证据仍保持原格式和只读校验,不建立当前代码资格。 + +IR 身份、预算和 controller/endpoint/CPU/kernel/resource options 使用冻结 +record;IRQ 为嵌套 record,ADC channels/sample times 为 tuple。原 JSON 硬件 +事实与按身份索引的只读 mapping 仍是明确的叶数据,resolver 内局部校验草稿也仍 +可使用 dict;这不等于每个 JSON 字段都有名义类型。`source_fields` 只记录投影 +字段存在性,不保存决策值副本。Python typed source epoch 为 2,JSON 投影仍为 +schema 1;两者各自计代,不改变 MCU C factory 接口。 + +`soc///configure/`(Native 为 `soc/native/configure/`)明确 拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 diff --git a/docs/delivery/capabilities.md b/docs/delivery/capabilities.md index 16990a213..5b99ded9b 100644 --- a/docs/delivery/capabilities.md +++ b/docs/delivery/capabilities.md @@ -41,7 +41,7 @@ python tools/maintenance/capabilities.py --assembly tools/configure/assemblies/q 本次解析,不从同系列其他路线补齐。上例是 F407ZG 的 USART1 有限 `dma-tx` 选择,不能扩展为 F407 全外设或全部 UART 模式支持。 -`validation` 分开列出以下阶段。查询只执行生产 resolver 和合同投影,不搜索旧 +`validation` 分开列出以下阶段。默认查询只执行生产 resolver 和合同投影,不搜索旧 报告,也不运行编译、测试、烧录或测量。因此,下面的 `not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。 @@ -61,6 +61,30 @@ python tools/maintenance/capabilities.py --assembly tools/configure/assemblies/q 以各记录中的不可变源码为准。配置、软件和物理阶段是不同证据,并非会自动晋级的 单个“支持等级”。 +## 显式关联已封存候选 + +需要核对已有执行证据时,在同一查询追加原候选路径: + +```sh +python tools/maintenance/capabilities.py --assembly tools/configure/assemblies/qiming-baremetal-uart-dma.toml --candidate /path/to/sealed/candidate.json +``` + +工具调用原候选 verifier 核对全部 payload、qualification 和链接产物,不运行新的 +测试。报告同时保存 `current_source` 和 `evidence.reference.source`,不会用被引用 +源码覆盖当前 checkout。`evidence.relation` 只在干净 commit/tree/submodules 与 +精确解析配置全部相同时为 `exact_source_and_configuration`;此时才把原候选的 +四类软件阶段投影为 `passed`。这是原执行的只读关联,不是本次新执行。 + +有效候选属于其他源码时标 `historical_source`,配置不同时标 +`configuration_mismatch`;这两种情况保留当前选择的 `not_assessed`。原件缺失、 +被修改或校验失败会拒绝查询,不能降级为已验证引用。未提供 `--candidate` 时 +无需 Git 或证据观察。可搬移候选需先用现有 artifact 工具恢复原 closure,再提供 +其中的 `candidate.json`;只有包摘要或下载位置不能建立通过状态。 + +任何软件候选都不改变 `physical_hil`、`physical_qualified=false` 或下方聚合表的 +family/mode 行。硬件状态仍需要真实 station 和同一镜像的实际观察;一个精确 +配置通过不等于所有 Board、backend、时钟或模式通过。 + | SoC | Controller | Kind | Mode | Reviewed SoC routes | Declared reference Boards | Resolver fixtures | Selected production source | | --- | --- | --- | --- | --- | --- | --- | --- | diff --git a/docs/delivery/migration.md b/docs/delivery/migration.md index f62ce90c7..6e2bd8efd 100644 --- a/docs/delivery/migration.md +++ b/docs/delivery/migration.md @@ -24,8 +24,9 @@ Nexus 头和生成 `nexus_factory.h`/`nexus_bindings.h`,不读取 private pr register 类型。SPI/I2C controller 和子设备 endpoint 分开获取,controller factory 不得返回第一个子设备的别名。无效 ID 返回 NULL;factory 不初始化、不分配内存。 -维护的 assembly 已统一为 TOML schema 2。schema 1 JSON 读取仅供明确迁移与工具 -派生输入,不与 TOML 维护同一份配置。冷启动仍显式执行 `nx_platform_start()`, +平台输入只接受 TOML schema 2,旧 schema 1 JSON 装配必须迁为完整 TOML。 +`resolved.json` 仅是审计/独立产物检查的单向输出,不能回填生成器。历史证据保留 +原字节及只读校验,不重新开放旧配置入口。冷启动仍显式执行 `nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED 时保持状态和所有借用存储。 在 start 前可以保存 face;在成功 stop 后 face 的身份不变,provider 拒绝操作。 diff --git a/docs/design/documentation-system.md b/docs/design/documentation-system.md index 80b5f92f7..18eda25dc 100644 --- a/docs/design/documentation-system.md +++ b/docs/design/documentation-system.md @@ -255,7 +255,7 @@ Breathe XML 路径由本次输出根注入,不能继续固定读取 docs/api ## 10. 版本和发布身份 -文档 manifest 复用既有 identity 和证据定位结构。下表只映射实际 schema-1 字段, +文档 manifest 复用既有 identity 和证据定位结构。下表映射当前 schema-2 字段, 完整格式以[唯一交付合同](../reference/documentation-delivery.md)和生产打包器为准: | 字段 | 来源与意义 | @@ -263,16 +263,17 @@ Breathe XML 路径由本次输出根注入,不能继续固定读取 docs/api | `version` | 严格 loader 读取唯一人工维护的兼容性 JSON;CMake 派生同值,不另写版本常量 | | `code_source.commit`、`code_source.tree` | 被文档描述的实际代码源码 | | `documentation_source.commit`、`documentation_source.tree` | 本次正文、配置和翻译源码 | -| `qualified_code.commit`、`qualified_code.tree` | 当前引用资格的精确源码,保留原scope | -| `qualified_code.index`、`qualified_code.index_sha256` | 既有结果索引路径和摘要,保留其配置与执行身份 | +| `qualification_references[].source` | 显式选入的每份原候选源码;不继承为文档 HEAD 资格 | +| `qualification_references[].candidate`、`configuration_sha256`、`artifacts`、`qualified_scopes` | 经原 verifier 核对的候选定位、摘要和精确范围;默认列表为空 | | `tool_versions` | 实际工具、安装回执及依赖锁身份,不由版本字符串推断执行 | | `languages` | 实际构建的locale数组;逐页翻译审查状态记录在构建结果中 | | `files` | 成功payload的`path`、`bytes`、`sha256`;不列manifest/checksums/archive/外层receipt | 三种源码身份不能互相覆盖。文档更新可引用经过验证的旧代码记录,不把该记录 重新标记为当前 HEAD 已执行。若描述的代码与资格不同,页面显示各自来源和边界。 -本方案描述的起点是 docs HEAD 7abde0e4、qualified code cc6a065c;历史 SoC -记录保持独立。计划工具一旦实现变更,按新源码执行其门禁。 +原方案起点 docs HEAD 7abde0e4 与 qualified code cc6a065c 保持历史身份。 +当前 schema 2 显式引用多份原候选,schema 1 仅保留原包的只读验证/恢复。 +工具实现变化按新源码执行门禁,不改写已有证据字节。 版本 URL 结构如下;具体已发布版本以实际发布记录为准: diff --git a/docs/design/official-stm32-provider.md b/docs/design/official-stm32-provider.md index dd8d07dd4..9e1a87b97 100644 --- a/docs/design/official-stm32-provider.md +++ b/docs/design/official-stm32-provider.md @@ -72,7 +72,15 @@ Each constructor returns the frozen `Construction` record from `state_type`, `definitions`, `initialize`, `stop`, `retain_on_failed_init`, `exports` and `pin_snapshots`. The common emitter consumes these decisions rather than reconstructing state types or failed-init responsibility from a family name. -This is a Python generation contract; no descriptor or dispatch layer is emitted +`initialize` is an immutable `DirectCall`; `stop` is a tuple of these calls. +Arguments are explicit static `Symbol` or `Address` values, uint32 constants, +booleans, NULL, or nested direct calls such as absolute-deadline construction. +Free-form expressions, casts, assignments and function-pointer/member-call +expressions reject. +`definitions`, `exports` and `face_definitions` remain C source strings; they can +still contain selected helper bodies. The real C compiler establishes symbol +and parameter types. This is a narrow Python generation contract, not a complete +C AST; these Python records introduce no additional runtime metadata or dispatch into the MCU image. The family owns numerical limits, supported modes, exact routes and construction @@ -163,12 +171,28 @@ gate relationships, system-clock state and chip-specific cold checks. Family generation still validates the selected Board/mode and supplies its chosen values. The existing shared `nx_legacy_*_close` paths retain stop and drain responsibility. -This moves the selected cold integration out of generated statement sequences; -it does not turn the legacy drivers into the I2C V2 configuration type. ADC, PWM -and Flash retain their separate private contracts. Other GPIO, IRQ UART, SPI, -watchdog and EXTI paths can still contain concrete behavior emitted by their -family adapter. Review each selected mode's real start/stop path rather than -inferring a platform-wide lifecycle change from the directory layout. +The ADC and PWM owners now reference their own readonly hardware descriptions, +`nx_legacy_adc_config_t` and `nx_legacy_pwm_config_t`, supplied by each exact SoC. +Register addresses, gates and fixed pad facts move out of mutable owner storage. +ADC reference voltage and PWM tick/period/duty/inactive level remain selected +owner state; they are not promoted into shared hardware facts. Configuration +must outlive the owner and cannot overlap its state or API output storage. +This does not turn these drivers into the I2C V2 type; Flash keeps its separate +private contract. Actual linked costs depend on the exact selected image. + +Selected U575 USART1 `dma-tx` and `dma-chain-tx` use static cold configuration and +concrete C preparation. Their C adapters own admission, binding and pin preparation +before existing finite/chain initialization. Selected F407 SPI polling/DMA and paired-controller DMA, plus +GD470 SPI0/SPI4 polling and finite full-duplex DMA, move prepare/close behavior +into concrete C while preserving the same Platform snapshot slots and retained +cleanup responsibility. GD470 also moves its conditional EXTI close into C. +The hot transfer engines, public faces and direct IRQ ownership are unchanged. + +This is a bounded cold-path migration. Other GPIO, IRQ UART, ADC, PWM, EXTI and +additional mode-specific paths may still emit C behavior in family helpers. +Review each selected mode's real start/stop path; neither a typed call field nor +a new directory establishes that all lifecycle behavior has moved out of the +generator. Physical timing and final software qualification remain separate. ## Conditions for full HAL transport diff --git a/docs/design/source-compatibility.md b/docs/design/source-compatibility.md index 7890c6682..66df49bdf 100644 --- a/docs/design/source-compatibility.md +++ b/docs/design/source-compatibility.md @@ -17,7 +17,7 @@ provider、生成 bindings 和调用者;当前不交付稳定二进制 ABI。 | `factory_abi_epoch` | `1` | 当前唯一认可的生成源码接口代际;不是 binary ABI 保证 | | source SDK | `source_only` / `exact` | CMake package 只接受相同完整版本,拒绝 ranges | | authored assembly | `2` | 简化的 TOML 选择面;不改变 Board/SoC 事实 | -| projection / typed IR | `1` / `1` | 严格 resolver 与实际 emitter 的源码格式 | +| resolved JSON projection / typed IR | `1` / `2` | 单向 JSON 输出格式与私有 Python 类型化源码接口分别计代 | | Board / SoC / routes | `1` / `1` / `1` | 物理事实文件及生产 validator 必须一致 | | CPU runtime assembly / facts / resolved | `1` / `1` / `1` | CPU 软件配置,不能升格 SoC/Board 资格 | | installed source SDK manifest | `1` | 既有完整 file-map/源依赖/package-export 校验 | @@ -27,6 +27,12 @@ provider、生成 bindings 和调用者;当前不交付稳定二进制 ABI。 使尚未实现的 schema 获得支持。SDK manifest 增加一个被 file-map 覆盖的源码文件, 不等于修改 manifest 格式。 +类型化源码接口第 2 代只接受严格 TOML 解析后的不可变装配对象;生产 resolver、 +factory 和 emitter 使用具名字段,不提供字典接口或 schema-1 JSON 装配兼容。 +`resolved.json` 仍是第 1 版单向输出,用于审计和独立产物检查,不是可信配置入口。 +`source_epoch` 是 Python 类级合同,不写入 JSON,也不增加 MCU 状态或改变公共 +C factory 接口。维护工具直接观察实际 IR 类的两项声明,并核对唯一版本策略。 + 当前 `0.2.0` 用于开发与软件候选准备,不表示已公开发行或已通过候选验收。 本次 semantic version 变化保留 epoch `1`、exact 匹配和 authored schema `2`; 版本值不把旧 SDK/候选的资格转移到新源码。新候选仍须独立绑定实际 clean source、 diff --git a/docs/development/workflow.md b/docs/development/workflow.md index 84e2d0b41..8442881a3 100644 --- a/docs/development/workflow.md +++ b/docs/development/workflow.md @@ -67,6 +67,31 @@ Automation proves execution, while retained development logs document historical RED/GREEN order. Pure prose or diagram edits need corresponding document checks and semantic review rather than mirrored implementation tests. +## Associate a complete local software plan with a pull request + +When GitHub Actions is unavailable, `tools/maintenance/local_ci.py` checks an +existing execution of the maintained `tools/dev/plans/software.json`. The default +mode is read-only: it verifies actual complete stages, command receipts, clean +source, inputs and the same plan before reporting the exact source and scope. +It does not run the plan or convert interrupted or partial reports into success. + +```sh +python tools/maintenance/local_ci.py --report /path/to/software/report.json +``` + +Explicit `--publish` uses an already configured authorized GitHub credential and +an existing open PR. It checks the PR head/base twice and publishes only to the +verified head SHA under `nexus/local-software-plan`; a later head cannot inherit +that status. Its retained receipt distinguishes pending, confirmed and unconfirmed +publication. Observations do not form an atomic GitHub merge transaction. + +This status establishes only the maintained local plan scope. It is not a GitHub +Actions job, a sealed software candidate, a configured branch rule or a product +release. Server rules need their own real readback; independent agent review does +not invent a CODEOWNERS account or a human approval. The existing release workflow +still requires its declared final-main CI jobs and trusted publication checks; +a local status cannot silently replace those requirements. + ## Keep feedback fast and evidence complete Use a focused development run for the behavior being changed, then execute the diff --git a/docs/explanation/platform-architecture.md b/docs/explanation/platform-architecture.md index cac6dcb36..e51920b39 100644 --- a/docs/explanation/platform-architecture.md +++ b/docs/explanation/platform-architecture.md @@ -78,10 +78,22 @@ object pool or hidden scheduling task is supplied. ## Build-time decisions and runtime work -The resolver combines authored TOML with reviewed SoC/Board facts into immutable -typed IR and an atomic bundle. It validates modes, resources, clocks, IRQs, ABI, -DMA reachability and budgets. CMake owns the source dependency graph; the resolver -does not discover software from device names or generate product workers. +TOML schema 2 is the only authored platform input. The parser freezes user intent +as `AuthoredAssembly`; `resolve_ir()` combines it with reviewed SoC/Board facts +and validates modes, resources, clocks, IRQs, ABI, DMA reachability and budgets. +It returns `ConfigurationIR` directly. Named frozen records hold each decision +once; they do not expose a Mapping facade or a duplicate `values` store. Hardware +fact leaves and keyed indexes still use immutable mappings, and local resolver +validation drafts may use dictionaries. These are explicit data boundaries, +not a promise to assign a separate nominal type to every JSON field. + +Factory and emitters consume the same typed result. `to_dict()` derives the +schema-1 `resolved.json` audit/artifact projection; it is never read back as an +authored platform input. Private typed source epoch 2 is separate from that +serialized format and from the public C factory epoch. Old JSON assemblies are +rejected. Historical evidence keeps its original bytes and read-only verification. +CMake owns the source dependency graph; resolution does not discover software +from device names or generate product workers. ![Actual compile-target dependency relationships](../assets/diagrams/build-targets.svg) @@ -97,8 +109,15 @@ its shared ops table with its instance context. IRQ dispatch is fixed to provide instances and does not repeat factory lookup for every byte. The constructor contract names private state, definitions, initialization, stop -actions and failed-init responsibility explicitly. Common generation does not -infer these from family names. Selected STM32F4 official LL operations remain +actions and failed-init responsibility explicitly. Its initializer and stop list +use immutable `DirectCall` values with explicit static references or constants; +no free-form C expression or function-pointer/member-call expression can enter +those fields. +Definitions and some remaining cold helper bodies are still emitted as C source. +The C compiler establishes actual symbol and parameter types. Model tests and +linked-image checks remain necessary: this narrow representation is not a complete +typed C AST. Common generation does not infer +these decisions from family names. Selected STM32F4 official LL operations remain inside private SoC state machines; factory and public I/O contracts are unchanged. Full HAL Handle/callback transport requires separate mode-specific evaluation. See [official driver integration](../design/official-stm32-provider.md). diff --git a/docs/guides/migration.md b/docs/guides/migration.md index 89468e89c..e57eb26c8 100644 --- a/docs/guides/migration.md +++ b/docs/guides/migration.md @@ -46,8 +46,11 @@ python tools/configure/configure.py generate --assembly tools/configure/asse Use a new output path you own. Check `resolved.json` and generated headers; never author the resolved output or reuse stale generated files after a failure. -Legacy schema-1 JSON exists only for explicit migration fixtures. A failed TOML -does not fall back to JSON, Kconfig, old headers or cache values. +Legacy schema-1 JSON is no longer a platform input. Convert it to a complete +schema-2 TOML assembly; generated `resolved.json` remains audit output only. +A failed TOML does not fall back to JSON, Kconfig, old headers or cache values. +Frozen historical evidence remains verifiable under its original format; this +read-only retention does not reopen old configuration or generation paths. ## Move access and lifecycle together diff --git a/docs/guides/provider-porting.md b/docs/guides/provider-porting.md index bb58bcd59..ad9fec2f7 100644 --- a/docs/guides/provider-porting.md +++ b/docs/guides/provider-porting.md @@ -76,7 +76,9 @@ forwarder merely to match a source directory. its configuration tests in `tools/configure/tests/providers//`. Define supported modes, numerical limits, constructors, rollback and IRQ wiring. Return an explicit frozen `Construction` record, including the private state - type and failed-init cleanup responsibility; do not require family-name + type and failed-init cleanup responsibility. Initialize and stop actions use + `DirectCall` values under the [constructor contract](../design/official-stm32-provider.md#build-time-provider-contract); + raw C expressions are not accepted in those fields. Do not require family-name inference in common generation. Unsupported modes reject; there is no runtime registration or plugin discovery. 5. Add explicit CMake targets and private SDK dependencies. Update declared @@ -104,7 +106,8 @@ directly; `__init__.py` explicitly exports it with constructors and emitters. The leaf `providers/common.py` owns validation helpers and immutable `Construction`; leaf contracts and common helpers do not import the top-level resolver or emitter. `selection.py` checks cross-domain options, clocks and IRQ -policy, `ir.py` adapts typed IR, and `matrix.py` orchestrates support catalogues. +policy, `ir.py` owns named immutable decisions and their one-way JSON projection, +and `matrix.py` orchestrates support catalogues. The descriptor explicitly lists `configuration_modules`, including `contract.py`, so edits trigger reconfiguration and remain in the Source SDK closure. diff --git a/docs/locale/en/LC_MESSAGES/assets/diagrams/README.po b/docs/locale/en/LC_MESSAGES/assets/diagrams/README.po index 9a998bad0..4c2aaa1f7 100644 --- a/docs/locale/en/LC_MESSAGES/assets/diagrams/README.po +++ b/docs/locale/en/LC_MESSAGES/assets/diagrams/README.po @@ -1,6 +1,13 @@ -# Exact messages independently reviewed; approval bound by companion receipt. +# Independently reviewed translations; exact canonical source identity below. msgid "" -msgstr "Project-Id-Version: Nexus source documentation\nLanguage: en\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nX-Nexus-Reviewed: true\nX-Nexus-Source-SHA256: e9bf8514d8b46f9f905ccbea68dfe2473c7292eb9eae4af32f04399ea55e0273\n" +msgstr "" +"Project-Id-Version: Nexus source documentation\n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Nexus-Reviewed: true\n" +"X-Nexus-Source-SHA256: b7ddb348c7b95d010390b39fb5df5693213e8e0016fe196522f58e0c2ec588b5\n" msgid "Nexus 平台图册" msgstr "Nexus platform diagram atlas" @@ -98,8 +105,22 @@ msgstr "![FIG-03: Build-input/artifact chain; generation failure invalidates the msgid "FIG-03:构建输入/产物链,生成失败使owned bundle失效" msgstr "FIG-03: Build-input/artifact chain; generation failure invalidates the owned bundle." -msgid "边表示构建时输入和产物传递,不表示运行时调用。严格解析不能修复非法字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。ELF/资源报告不构成实板资格。" -msgstr "Edges represent build-time input and artifact flow, not runtime calls. Strict parsing cannot repair invalid fields, conflicts or stale inputs. Failure withdraws only owned outputs: it neither clears unowned directories nor exposes partial artifacts. ELF/resource reports do not establish physical-board qualification." +msgid "" +"边表示构建时输入和产物传递,不表示运行时调用。TOML 唯一入口先得到冻结 `AuthoredAssembly` 意图,再与 SoC/Board" +" 事实校验得到具名 `ConfigurationIR`。 同一 IR 驱动 SoC 的静态定义/冷态调用与单向 " +"JSON/配置/内存投影;全部经过 同一 bundle 事务发布。`resolved.json` 不回流为装配输入。严格解析不能修复非法 " +"字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。 ELF/资源报告不构成实板资格。" +msgstr "" +"Edges represent build-time input and artifact transfer, not runtime " +"calls. The sole TOML entry first produces frozen `AuthoredAssembly` " +"intent; validation with SoC/Board facts then produces named " +"`ConfigurationIR`. The same IR drives SoC static definitions/cold calls " +"and one-way JSON/configuration/memory projections; all are published " +"through the same bundle transaction. `resolved.json` never flows back as" +" an assembly input. Strict resolution cannot repair invalid fields, " +"conflicts or stale inputs. Failure withdraws only owned output, never " +"cleans unowned directories and never exposes partial products. " +"ELF/resource reports do not establish physical qualification." msgid "依据:[配置器](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/configure.py)、[不可变IR](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/ir.py)、[bundle事务](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bundle.py)、[构建合同](../../design/build-system.md)。 {download}`可编辑DOT `;实施关联:DOC-45。" msgstr "Sources: [configurator](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/configure.py), [immutable IR](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/ir.py), [bundle transaction](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bundle.py), [build contract](../../design/build-system.md). {download}`editable DOT `; Implementation item: DOC-45." @@ -119,6 +140,9 @@ msgstr "FIG-04: Illustrative two selected instances of one class; same-mode ops msgid "结构图展示由生成器支持的按配置实例布局,不声称每块PCB均授权这些组合。同provider同模式实例共享readonly ops,各自context/state/storage独立。不同provider或模式可使用不同ops;调用方法接收本次context。controller和endpoint有独立factory命名空间,endpoint不冒充控制器。ID只在当前assembly有效,默认uint16_t typedef不提供语言级跨类防混用。" msgstr "The structure shows configuration-selected layouts supported by the generator; it does not claim every PCB authorizes those combinations. Same-provider, same-mode instances share read-only ops while each context/state/storage remains independent. Different providers or modes can have different ops; a method receives the current context. Controllers and endpoints have distinct factory namespaces, and an endpoint cannot impersonate its controller. IDs are valid only in the current assembly; the default uint16_t typedef does not provide language-level protection against mixing classes." +msgid "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" +msgstr "Sources: [typed factory generation](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py), [face/state assembly](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py), [public SPI interface](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h), [Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py). {download}`editable DOT `; Implementation item: DOC-46." + msgid "FIG-05 显式启动停机与失败撤回" msgstr "FIG-05 Explicit start, stop and failure cleanup" @@ -511,6 +535,9 @@ msgstr "Native RTC/LPTIM build-time assembly, runtime calls, final access and pr msgid "构建边、运行调用和请求源推进均有独立标签。factory 只返回已有接口面;生成的 Platform 首次准备构造一次,后续对已构造 owner 调用私有 start。stop 先关闭准入, 撤源失败仍保留请求和 owner;私有 start 优先拒绝保留责任。真实撤源并完成外部 join 后,重新开放先推进代次再开放准入,保留日历、速率、时钟与外部故障。模型推进不 发布请求结果。虚线硬件适配需要独立 SoC/时钟/备份域/IRQ/STOP/VBAT 合同, 不表示已有物理驱动或 HIL 通过。" msgstr "Build edges, runtime calls and source progression have distinct labels. Factory only returns existing faces; generated Platform first constructs once and later calls private start on a constructed owner. Stop first closes admission; failed withdrawal retains request and owner, and private start gives retained responsibility priority. After actual withdrawal and external joins, reopen advances generation before opening admission and preserves calendar, rate, clock and external fault. Model progression does not publish requests. Dashed hardware adaptation needs separate SoC/clock/backup-domain/IRQ/STOP/VBAT contracts and does not establish a physical driver or HIL pass." +msgid "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、 [公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、 [公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、 [Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、 [Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/ 候选/实板资格分别判断,不能从该图或渲染成功推断。" +msgstr "Basis: [RTC/LPTIM explanation](../../explanation/rtc-lptim.md), [public RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h), [public LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h), [Native implementation](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c), [Native assembly](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py). {download}`Editable DOT `; implementation link: IO-36. Assess development software evidence separately from main/SDK/candidate/hardware qualification; neither this diagram nor successful rendering establishes those results." + msgid "FIG-22 G474 命名 GPIO 状态与保留租约" msgstr "FIG-22 G474 named GPIO states and retained lease" @@ -698,12 +725,6 @@ msgstr "" "external clocks or proves real audio or HIL. IO-41, full SDK/formal/OCI and " "physical exit conditions remain independently open." -msgid "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" -msgstr "Sources: [typed factory generation](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py), [face/state assembly](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py), [public SPI interface](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h), [Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py). {download}`editable DOT `; Implementation item: DOC-46." - -msgid "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、 [公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、 [公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、 [Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、 [Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/ 候选/实板资格分别判断,不能从该图或渲染成功推断。" -msgstr "Basis: [RTC/LPTIM explanation](../../explanation/rtc-lptim.md), [public RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h), [public LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h), [Native implementation](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c), [Native assembly](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py). {download}`Editable DOT `; implementation link: IO-36. Assess development software evidence separately from main/SDK/candidate/hardware qualification; neither this diagram nor successful rendering establishes those results." - msgid "FIG-31 共享 IP 与精确 SoC 源码归属" msgstr "FIG-31 Shared IP or exact SoC source ownership" diff --git a/docs/locale/en/LC_MESSAGES/delivery/architecture.po b/docs/locale/en/LC_MESSAGES/delivery/architecture.po index d1ea1a41d..b44a5c6c0 100644 --- a/docs/locale/en/LC_MESSAGES/delivery/architecture.po +++ b/docs/locale/en/LC_MESSAGES/delivery/architecture.po @@ -1,4 +1,4 @@ -# Authored translation; review state is explicit in catalog metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 51776ec62d9482ab03cca988e7d9f875a37aa437ac65bb1eb637dfb5db448289\n" +"X-Nexus-Source-SHA256: 77731080a72496622d8699797ce669cda9eeb5e35df86a6ac509513835477f49\n" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/architecture.md:1 msgid "当前系统架构" @@ -80,6 +80,15 @@ msgid "私有 SDK 与私有 provider storage;各家族只开放已维护模式 msgstr "" "Private SDK/provider storage; each family exposes only maintained modes" +msgid "`drivers///`" +msgstr "`drivers///`" + +msgid "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" +msgstr "Shared UART, I2C, ADC, timer and Flash register-protocol algorithms reviewed for each participating chip" + +msgid "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" +msgstr "Compiled privately by the selected SoC target; no ownership of chip routes, clocks, Board or runtime registration" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/architecture.md:0 msgid "`boards//board.json`" msgstr "`boards//board.json`" @@ -362,17 +371,67 @@ msgstr "" "reconfiguration invalidates the old bundle instead of silently reusing a " "previous success." -#: ../../../../tmp/nexus-quality-20261019/docs/delivery/architecture.md:102 msgid "" -"SoC/Board 事实采用 JSON,使用者编写 schema-2 TOML。严格 resolver 产生不可变 typed " -"IR;各生成器消费同一结果。`resolved.json` 是该结果的序列化,不能作为手工 配置回填。迁移期 schema-1 JSON " -"夹具有显式入口,不与 TOML 混合叠加。" -msgstr "" -"SoC/Board facts use JSON and consumers author schema-2 TOML. A strict " -"resolver produces immutable typed IR consumed by every generator. " -"`resolved.json` serializes that result and is not hand-authored " -"configuration input. Migration schema-1 JSON fixtures have explicit entry " -"points and are not overlaid with TOML." +"SoC/Board 事实采用 JSON,平台装配只接受 schema-2 TOML。`AuthoredAssembly` " +"保存冻结的用户意图;`resolve_ir()` 严格校验事实、模式和资源后直接产生不可变 `ConfigurationIR`。生产 " +"resolver、factory 和 emitter 使用具名字段,不提供 Mapping 接口或保存第二份 `values` " +"决策副本。`to_dict()` 派生 `resolved.json` 供审计与独立产物检查,不能回填为配置;旧 schema-1 JSON " +"装配入口已删除。 历史证据仍保持原格式和只读校验,不建立当前代码资格。" +msgstr "" +"SoC/Board facts use JSON; platform assemblies accept only schema-2 TOML." +" `AuthoredAssembly` retains frozen user intent. After strict validation " +"of facts, modes and resources, `resolve_ir()` directly produces " +"immutable `ConfigurationIR`. Production resolver, factory and emitters " +"use named fields, without a Mapping interface or a second `values` copy " +"of decisions. `to_dict()` derives `resolved.json` for audits and " +"independent artifact checks; it cannot be fed back as configuration. The" +" legacy schema-1 JSON assembly entry has been removed. Historical " +"evidence retains its original format and read-only verification; it " +"grants no qualification to current code." + +msgid "" +"IR 身份、预算和 controller/endpoint/CPU/kernel/resource options 使用冻结 " +"record;IRQ 为嵌套 record,ADC channels/sample times 为 tuple。原 JSON 硬件 " +"事实与按身份索引的只读 mapping 仍是明确的叶数据,resolver 内局部校验草稿也仍 可使用 dict;这不等于每个 JSON " +"字段都有名义类型。`source_fields` 只记录投影 字段存在性,不保存决策值副本。Python typed source epoch " +"为 2,JSON 投影仍为 schema 1;两者各自计代,不改变 MCU C factory 接口。" +msgstr "" +"IR identity, budgets and controller/endpoint/CPU/kernel/resource options" +" use frozen records; IRQ is a nested record, and ADC channels/sample " +"times are tuples. Original JSON hardware facts and identity-keyed " +"readonly mappings remain explicit leaf data, and local resolver " +"validation drafts may still use dictionaries. This does not give every " +"JSON field a nominal type. `source_fields` records only projection-field" +" presence, not duplicate decision values. Python typed source epoch is " +"2, while JSON projection remains schema 1; they have separate " +"generations and do not change MCU C factory interfaces." + +msgid "" +"`soc///configure/`(Native 为 `soc/native/configure/`)明确 " +"拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 " +"constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共" +" binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 " +"CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board " +"复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" +msgstr "" +"`soc///configure/` (`soc/native/configure/` for Native) " +"explicitly owns ABI, mode-to-implementation decisions, DMA routes, " +"triggers, shared IRQ and CS contracts, constructors/rollback/IRQ and " +"system-clock emission. The common resolver retains schema, facts, " +"resources, input identity and output; common bindings orchestrate Nexus " +"faces and uniform lifecycle progress. A new SoC requires explicit " +"registration of its real provider modules and maintained CMake/SDK " +"required files. Unknown families and modes reject without inheriting " +"Native or Cortex-M4 defaults. An ordinary Board reuses existing " +"implementations rather than copying drivers; documentation matrices " +"cannot make configuration decisions." + +msgid "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" +msgstr "The explicit provider map and shared leaf contracts remain in `tools/configure/providers/`; configuration tests remain in `tools/configure/tests/providers//`. Shared IP source selection, configuration package placement and physical qualification are reviewed separately. This layout refactor does not expand supported modes; physical status remains `not_executed`." + +msgid "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" +msgstr "`ConfigurationIR.physical_part` and `ControllerIR.integration` are named views of existing frozen facts and resource plans. They reuse the original Flash/memory, resource claim, DMA lane, IRQ and clock objects; they add neither JSON/configuration-identity fields nor another route resolver. F407 SPI and paired secondary-controller paths consume the integration, while other constructors can retain their existing adapters. A nominal APB frequency does not replace the reviewed clock-domain envelope. See the [provider porting guide](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct) for field responsibilities and new-part checks." + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/architecture.md:116 msgid "" @@ -404,6 +463,10 @@ msgstr "" "ABI. External firmware explicitly adds startup/system/linker/resource checks" " through `nexus_add_firmware()`." +# CMSIS6 actual-POT delta; independent review pending. +msgid "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" +msgstr "The seven SoC family entries select the pinned official CMSIS-Core v6.4.0 CPU headers, while each family retains its locked vendor Device/startup and HAL/LL sources. The build target graph owns the internal `nexus_cmsis_core` and `nexus_use_cmsis_core(target)`, distinct from the public neutral `Nexus::Core` mechanisms. Ordinary public consumers do not inherit vendor Core headers or types. The old Core bytes and notices in the GD32F470 import remain; the selected source is shared, and directory presence alone does not establish a compiler dependency. See [vendor versions](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md) for version and license identities. This dependency migration does not change public Arch context, ownership or mask-restoration contracts, or automatically enable a new chip, profile or security domain. Actual include closure, ARM links, relocated-SDK consumption and physical qualification have separate acceptance scopes." + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/architecture.md:127 msgid "固定对象与执行" msgstr "Fixed objects and execution" @@ -925,25 +988,3 @@ msgstr "" "documentation does not replace tested-code identity. New candidates have " "independent exit conditions. No hardware is connected; physical results " "remain `not_executed`." - -# CMSIS6 actual-POT delta; independent review pending. -msgid "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" -msgstr "The seven SoC family entries select the pinned official CMSIS-Core v6.4.0 CPU headers, while each family retains its locked vendor Device/startup and HAL/LL sources. The build target graph owns the internal `nexus_cmsis_core` and `nexus_use_cmsis_core(target)`, distinct from the public neutral `Nexus::Core` mechanisms. Ordinary public consumers do not inherit vendor Core headers or types. The old Core bytes and notices in the GD32F470 import remain; the selected source is shared, and directory presence alone does not establish a compiler dependency. See [vendor versions](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md) for version and license identities. This dependency migration does not change public Arch context, ownership or mask-restoration contracts, or automatically enable a new chip, profile or security domain. Actual include closure, ARM links, relocated-SDK consumption and physical qualification have separate acceptance scopes." - -msgid "`drivers///`" -msgstr "`drivers///`" - -msgid "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" -msgstr "Shared UART, I2C, ADC, timer and Flash register-protocol algorithms reviewed for each participating chip" - -msgid "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" -msgstr "Compiled privately by the selected SoC target; no ownership of chip routes, clocks, Board or runtime registration" - -msgid "IR 身份、预算和各类 controller options 使用冻结 record,IRQ 为嵌套 record, ADC channels/sample times 为 tuple;完整 JSON 事实投影仍保留只读 mapping, 不能声称所有字段均有名义强类型。`soc///configure/`(Native 为 `soc/native/configure/`)明确 拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共 binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board 复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" -msgstr "IR identity, budgets and controller options use frozen records; IRQ is a nested record, and ADC channels/sample times are tuples. The complete JSON fact projection still retains a readonly mapping, so not every field has a nominal strong type. `soc///configure/` (Native: `soc/native/configure/`) explicitly owns ABI, mode-to-implementation mapping, DMA routes, triggers, shared IRQ and CS contracts, constructors, rollback, IRQ and system-clock emission. The common resolver retains schema, facts, resources, input identity and outputs; common bindings assemble Nexus faces and shared lifecycle progress. A new SoC requires explicit real provider modules and maintained CMake/SDK required files. Unknown families and modes reject without Native or Cortex-M4 defaults. Ordinary Boards reuse existing implementations without copying drivers; documentation matrices cannot determine configuration." - -msgid "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" -msgstr "The explicit provider map and shared leaf contracts remain in `tools/configure/providers/`; configuration tests remain in `tools/configure/tests/providers//`. Shared IP source selection, configuration package placement and physical qualification are reviewed separately. This layout refactor does not expand supported modes; physical status remains `not_executed`." - -msgid "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" -msgstr "`ConfigurationIR.physical_part` and `ControllerIR.integration` are named views of existing frozen facts and resource plans. They reuse the original Flash/memory, resource claim, DMA lane, IRQ and clock objects; they add neither JSON/configuration-identity fields nor another route resolver. F407 SPI and paired secondary-controller paths consume the integration, while other constructors can retain their existing adapters. A nominal APB frequency does not replace the reviewed clock-domain envelope. See the [provider porting guide](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct) for field responsibilities and new-part checks." diff --git a/docs/locale/en/LC_MESSAGES/delivery/capabilities.po b/docs/locale/en/LC_MESSAGES/delivery/capabilities.po index 57baa4edb..a656b432f 100644 --- a/docs/locale/en/LC_MESSAGES/delivery/capabilities.po +++ b/docs/locale/en/LC_MESSAGES/delivery/capabilities.po @@ -1,13 +1,13 @@ -# Authored translation; independent review is recorded in metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: b1fce9a4e31432cd1323234763e83f60e26e3700dc541d5bf88044c93b596330\n" +"X-Nexus-Source-SHA256: a6b4e8651695e4de512a08ad3ce897e59317b3e8e124de58598da6903d2f6ab0\n" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:1 msgid "派生能力与接入矩阵" @@ -115,16 +115,15 @@ msgstr "" "finite USART1 `dma-tx` on F407ZG; it does not extend support to every F407 " "peripheral or UART mode." -#: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:44 msgid "" -"`validation` 分开列出以下阶段。查询只执行生产 resolver 和合同投影,不搜索旧 报告,也不运行编译、测试、烧录或测量。因此,下面的 " -"`not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" +"`validation` 分开列出以下阶段。默认查询只执行生产 resolver 和合同投影,不搜索旧 " +"报告,也不运行编译、测试、烧录或测量。因此,下面的 `not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" msgstr "" -"`validation` separates the stages below. The query only runs the production " -"resolver and contract projection; it does not search historical reports or " -"compile, test, flash or measure. Therefore `not_assessed` means this query " -"did not assess that stage, not that implementation failed or execution has " -"never occurred." +"`validation` separates the stages below. By default, the query only runs" +" the production resolver and contract projection; it neither searches " +"old reports nor compiles, tests, flashes or measures. Thus " +"`not_assessed` means this query did not assess that stage, not that the " +"implementation failed or was never executed historically." #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "阶段" @@ -216,6 +215,57 @@ msgstr "" "immutable source and actual scope. Configuration, software and physical " "stages are separate evidence, not one automatically promoted support level." +msgid "显式关联已封存候选" +msgstr "Explicitly associate a sealed candidate" + +msgid "需要核对已有执行证据时,在同一查询追加原候选路径:" +msgstr "" +"To inspect existing execution evidence, append the original candidate " +"path to the same query:" + +msgid "" +"工具调用原候选 verifier 核对全部 payload、qualification 和链接产物,不运行新的 测试。报告同时保存 " +"`current_source` 和 `evidence.reference.source`,不会用被引用 源码覆盖当前 " +"checkout。`evidence.relation` 只在干净 commit/tree/submodules 与 精确解析配置全部相同时为 " +"`exact_source_and_configuration`;此时才把原候选的 四类软件阶段投影为 " +"`passed`。这是原执行的只读关联,不是本次新执行。" +msgstr "" +"The tool invokes the original candidate verifier to check all payloads, " +"qualifications and linked artifacts, without running new tests. The " +"report keeps both `current_source` and `evidence.reference.source`; the " +"referenced source does not replace the current checkout. " +"`evidence.relation` becomes `exact_source_and_configuration` only when " +"clean commit/tree/submodules and the complete exact resolved " +"configuration match; only then are the original candidate’s four " +"software stages projected as `passed`. This is a read-only association " +"with original execution, not a new execution." + +msgid "" +"有效候选属于其他源码时标 `historical_source`,配置不同时标 " +"`configuration_mismatch`;这两种情况保留当前选择的 `not_assessed`。原件缺失、 " +"被修改或校验失败会拒绝查询,不能降级为已验证引用。未提供 `--candidate` 时 无需 Git 或证据观察。可搬移候选需先用现有 " +"artifact 工具恢复原 closure,再提供 其中的 `candidate.json`;只有包摘要或下载位置不能建立通过状态。" +msgstr "" +"A valid candidate from another source is marked `historical_source`; a " +"different configuration is marked `configuration_mismatch`. Both retain " +"`not_assessed` for the current selection. Missing, modified or invalid " +"original evidence rejects the query rather than downgrading to a " +"verified reference. Without `--candidate`, no Git or evidence " +"observation is needed. Restore a portable candidate’s original closure " +"using the existing artifact tools before providing its `candidate.json`;" +" a package digest or download location alone cannot establish a passing " +"state." + +msgid "" +"任何软件候选都不改变 `physical_hil`、`physical_qualified=false` 或下方聚合表的 family/mode" +" 行。硬件状态仍需要真实 station 和同一镜像的实际观察;一个精确 配置通过不等于所有 Board、backend、时钟或模式通过。" +msgstr "" +"No software candidate changes `physical_hil`, " +"`physical_qualified=false`, or family/mode rows in the aggregate table " +"below. Hardware status still requires an actual station and observations" +" of the same image; one passing exact configuration does not imply all " +"Boards, backends, clocks or modes passed." + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "SoC" msgstr "SoC" @@ -280,6 +330,9 @@ msgstr "" "`tests/contracts/mainstream_assembly/gd303-devices-freertos.toml`, " "`tests/contracts/mainstream_assembly/gd303-devices.toml`" +msgid "`drivers/adc/legacy_adc/legacy_adc.c`" +msgstr "`drivers/adc/legacy_adc/legacy_adc.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "EXTI0" msgstr "EXTI0" @@ -328,6 +381,9 @@ msgstr "poll-word" msgid "`flash-internal`" msgstr "`flash-internal`" +msgid "`drivers/flash/legacy_flash/legacy_flash.c`" +msgstr "`drivers/flash/legacy_flash/legacy_flash.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "FWDGT" msgstr "FWDGT" @@ -420,6 +476,9 @@ msgstr "" "`tests/contracts/legacy_transport_assembly/gd32f303-baremetal-i2c.toml`, " "`tests/contracts/legacy_transport_assembly/gd32f303-freertos-i2c.toml`" +msgid "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" +msgstr "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "SPI0" msgstr "SPI0" @@ -456,6 +515,9 @@ msgstr "fixed-pwm" msgid "`pwm-pa6`" msgstr "`pwm-pa6`" +msgid "`drivers/timer/legacy_pwm/legacy_pwm.c`" +msgstr "`drivers/timer/legacy_pwm/legacy_pwm.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "USART0" msgstr "USART0" @@ -482,6 +544,9 @@ msgstr "" "duplex.toml`, `tests/contracts/legacy_transport_assembly/gd32f303-freertos-" "dma-duplex.toml`" +msgid "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" +msgstr "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "dma-tx" msgstr "dma-tx" @@ -2126,6 +2191,10 @@ msgstr "`gpio-pg10`" msgid "`i2c1-pb8-pb9`" msgstr "`i2c1-pb8-pb9`" +msgid "`drivers/i2c/i2c_v2/i2c_v2.c`" +msgstr "`drivers/i2c/i2c_v2/i2c_v2.c`" + + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "" "`tests/contracts/g474_assembly/spi-freertos.toml`, " @@ -3021,21 +3090,3 @@ msgstr "`soc/native/drivers/uart_word.c`" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "UART1" msgstr "UART1" - -msgid "`drivers/adc/legacy_adc/legacy_adc.c`" -msgstr "`drivers/adc/legacy_adc/legacy_adc.c`" - -msgid "`drivers/flash/legacy_flash/legacy_flash.c`" -msgstr "`drivers/flash/legacy_flash/legacy_flash.c`" - -msgid "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" -msgstr "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" - -msgid "`drivers/timer/legacy_pwm/legacy_pwm.c`" -msgstr "`drivers/timer/legacy_pwm/legacy_pwm.c`" - -msgid "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" -msgstr "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" - -msgid "`drivers/i2c/i2c_v2/i2c_v2.c`" -msgstr "`drivers/i2c/i2c_v2/i2c_v2.c`" diff --git a/docs/locale/en/LC_MESSAGES/delivery/migration.po b/docs/locale/en/LC_MESSAGES/delivery/migration.po index 4139a48b5..b7e230b3c 100644 --- a/docs/locale/en/LC_MESSAGES/delivery/migration.po +++ b/docs/locale/en/LC_MESSAGES/delivery/migration.po @@ -1,4 +1,4 @@ -# Authored translation; independent semantic review pending. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 1c65f485722223215c7aa57d5bf99e751eabb0a5482118abc3bec65068d922a4\n" +"X-Nexus-Source-SHA256: d0ae571f3de5c3208756e72c5f5b04bceb1cd55231657ab6ab9386f6f4aa7e79\n" #: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:1 msgid "外部工程迁移" @@ -130,17 +130,21 @@ msgstr "" "obtained separately; a controller factory must not return an alias of the first child. Invalid IDs" " return NULL. Factory lookup neither initializes nor allocates memory." -#: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:27 msgid "" -"维护的 assembly 已统一为 TOML schema 2。schema 1 JSON 读取仅供明确迁移与工具 派生输入,不与 TOML 维护同一份配置。冷启动仍显式执行 " -"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED 时保持状态和所有借用存储。 在 start 前可以保存 " -"face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" +"平台输入只接受 TOML schema 2,旧 schema 1 JSON 装配必须迁为完整 TOML。 `resolved.json` " +"仅是审计/独立产物检查的单向输出,不能回填生成器。历史证据保留 原字节及只读校验,不重新开放旧配置入口。冷启动仍显式执行 " +"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED " +"时保持状态和所有借用存储。 在 start 前可以保存 face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" msgstr "" -"Maintained assemblies use TOML schema 2. Schema-1 JSON reading is only for explicit migration and " -"tool-derived input, not a parallel authored copy of TOML configuration. Cold startup still " -"explicitly calls `nx_platform_start()` and shutdown calls `nx_platform_stop()`. BUSY/QUARANTINED " -"retain state and all borrowed storage. Faces may be retained before start. Successful stop " -"preserves face identity while the provider rejects operations." +"Platform input accepts only TOML schema 2; legacy schema 1 JSON " +"assemblies must migrate to complete TOML. `resolved.json` is a one-way " +"output for audit/independent artifact checks and cannot be fed back to " +"the generator. Historical evidence retains original bytes and read-only " +"verification without reopening old configuration entries. Cold startup " +"still explicitly calls `nx_platform_start()` and shutdown calls " +"`nx_platform_stop()`. Preserve state and all borrowed storage on " +"BUSY/QUARANTINED. Faces may be retained before start; after successful " +"stop their identity is unchanged and the provider rejects operations." #: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:32 msgid "" diff --git a/docs/locale/en/LC_MESSAGES/design/documentation-system.po b/docs/locale/en/LC_MESSAGES/design/documentation-system.po index 8b31954c5..b9e15b2b6 100644 --- a/docs/locale/en/LC_MESSAGES/design/documentation-system.po +++ b/docs/locale/en/LC_MESSAGES/design/documentation-system.po @@ -1,14 +1,13 @@ -# +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 6b89c334b35f7f1475e81552cda98f43030879841233b557f4f7efbb804bf495\n" +"X-Nexus-Source-SHA256: 37a3be649a7d680cba2fa10020334d14f1a09719b4f8c4bec5eb3c6472d650fa\n" #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:1 msgid "Nexus 文档系统架构与交付规划" @@ -928,13 +927,13 @@ msgid "10. 版本和发布身份" msgstr "10. Version/publication identity" msgid "" -"文档 manifest 复用既有 identity 和证据定位结构。下表只映射实际 schema-1 字段, " +"文档 manifest 复用既有 identity 和证据定位结构。下表映射当前 schema-2 字段, " "完整格式以[唯一交付合同](../reference/documentation-delivery.md)和生产打包器为准:" msgstr "" -"Documentation manifests reuse existing identity and evidence-location " -"structures. The table maps actual schema-1 fields only; the [single delivery" -" contract](../reference/documentation-delivery.md) and production packager " -"define the complete format:" +"The documentation manifest reuses existing identity and evidence-locator" +" structures. The table maps current schema-2 fields; the [sole delivery " +"contract](../reference/documentation-delivery.md) and production " +"packager define the complete format:" #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:0 msgid "字段" @@ -965,19 +964,25 @@ msgstr "`documentation_source.commit`、`documentation_source.tree`" msgid "本次正文、配置和翻译源码" msgstr "Source for this prose, configuration and translation build." -msgid "`qualified_code.commit`、`qualified_code.tree`" -msgstr "`qualified_code.commit`、`qualified_code.tree`" +msgid "`qualification_references[].source`" +msgstr "`qualification_references[].source`" -msgid "当前引用资格的精确源码,保留原scope" -msgstr "Exact referenced qualification source, retaining its original scope." +msgid "显式选入的每份原候选源码;不继承为文档 HEAD 资格" +msgstr "" +"Source of each explicitly selected original candidate; qualification is " +"not inherited by the documentation HEAD" -msgid "`qualified_code.index`、`qualified_code.index_sha256`" -msgstr "`qualified_code.index`、`qualified_code.index_sha256`" +msgid "" +"`qualification_references[].candidate`、`configuration_sha256`、`artifacts" +"`、`qualified_scopes`" +msgstr "" +"`qualification_references[].candidate`、`configuration_sha256`、`artifacts" +"`、`qualified_scopes`" -msgid "既有结果索引路径和摘要,保留其配置与执行身份" +msgid "经原 verifier 核对的候选定位、摘要和精确范围;默认列表为空" msgstr "" -"Existing results-index path and digest, retaining configuration and " -"execution identity." +"Candidate locator, digest and exact scope checked by the original " +"verifier; the default list is empty" msgid "`tool_versions`" msgstr "`tool_versions`" @@ -1004,17 +1009,22 @@ msgstr "" "Successful payload `path`, `bytes` and `sha256`; exclude " "manifest/checksums/archive/outer receipts." -#: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:271 msgid "" "三种源码身份不能互相覆盖。文档更新可引用经过验证的旧代码记录,不把该记录 重新标记为当前 HEAD " -"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 本方案描述的起点是 docs HEAD 7abde0e4、qualified code " -"cc6a065c;历史 SoC 记录保持独立。计划工具一旦实现变更,按新源码执行其门禁。" -msgstr "" -"The three source identities never overwrite one another. Docs may cite " -"verified older code without labeling its execution as current HEAD. Display " -"described/qualified sources separately where different. This plan began at " -"docs HEAD 7abde0e4 and qualified code cc6a065c; historical SoC records " -"remain independent. New implemented tools execute gates on their new source." +"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 原方案起点 docs HEAD 7abde0e4 与 qualified code " +"cc6a065c 保持历史身份。 当前 schema 2 显式引用多份原候选,schema 1 仅保留原包的只读验证/恢复。 " +"工具实现变化按新源码执行门禁,不改写已有证据字节。" +msgstr "" +"The three source identities cannot overwrite each other. A documentation" +" update may reference verified older code records without relabeling " +"them as execution of the current HEAD. If described code and qualified " +"code differ, the page shows their respective sources and boundaries. The" +" original design baseline docs HEAD 7abde0e4 and qualified code cc6a065c" +" retain their historical identities. Current schema 2 explicitly " +"references multiple original candidates; schema 1 retains only read-only" +" verification/restoration of original packages. Tool implementation " +"changes run gates on their new source without rewriting existing " +"evidence bytes." #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:276 msgid "版本 URL 结构如下;具体已发布版本以实际发布记录为准:" diff --git a/docs/locale/en/LC_MESSAGES/design/source-compatibility.po b/docs/locale/en/LC_MESSAGES/design/source-compatibility.po index 8c06a9771..dec1ee521 100644 --- a/docs/locale/en/LC_MESSAGES/design/source-compatibility.po +++ b/docs/locale/en/LC_MESSAGES/design/source-compatibility.po @@ -1,4 +1,4 @@ -# Authored translation; independent review pending. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: c47d1abb01a83b8381f55a90a8867e31dbc46c564537f14ff48e4bda2200a9be\n" +"X-Nexus-Source-SHA256: f44842ae8941b596eb264d54755503c100356c58ede683468ad7c5652f38f372\n" msgid "源码版本、factory 代际与依赖维护" msgstr "Source versions, factory generations and dependency maintenance" @@ -87,14 +87,16 @@ msgstr "`2`" msgid "简化的 TOML 选择面;不改变 Board/SoC 事实" msgstr "Simplified TOML selection surface; no change to Board/SoC facts" -msgid "projection / typed IR" -msgstr "projection / typed IR" +msgid "resolved JSON projection / typed IR" +msgstr "resolved JSON projection / typed IR" -msgid "`1` / `1`" -msgstr "`1` / `1`" +msgid "`1` / `2`" +msgstr "`1` / `2`" -msgid "严格 resolver 与实际 emitter 的源码格式" -msgstr "Source format of the strict resolver and actual emitter" +msgid "单向 JSON 输出格式与私有 Python 类型化源码接口分别计代" +msgstr "" +"The one-way JSON output format and private typed Python source interface" +" have separate generations" msgid "Board / SoC / routes" msgstr "Board / SoC / routes" @@ -130,6 +132,23 @@ msgstr "" " an unimplemented schema. Adding a file covered by the SDK manifest file-map" " does not change the manifest format." +msgid "" +"类型化源码接口第 2 代只接受严格 TOML 解析后的不可变装配对象;生产 resolver、 factory 和 emitter " +"使用具名字段,不提供字典接口或 schema-1 JSON 装配兼容。 `resolved.json` 仍是第 1 " +"版单向输出,用于审计和独立产物检查,不是可信配置入口。 `source_epoch` 是 Python 类级合同,不写入 JSON,也不增加 " +"MCU 状态或改变公共 C factory 接口。维护工具直接观察实际 IR 类的两项声明,并核对唯一版本策略。" +msgstr "" +"Typed source interface generation 2 accepts only immutable assembly " +"objects produced by strict TOML parsing. Production resolver, factory " +"and emitters use named fields, without a dictionary interface or " +"schema-1 JSON assembly compatibility. `resolved.json` remains a " +"version-1 one-way output for audits and independent artifact checks, not" +" a trusted configuration input. `source_epoch` is a Python class " +"contract; it is not serialized to JSON and adds no MCU state or change " +"to public C factory interfaces. Maintenance tools observe both " +"declarations on the actual IR class and check them against the sole " +"version policy." + msgid "" "当前 `0.2.0` 用于开发与软件候选准备,不表示已公开发行或已通过候选验收。 本次 semantic version 变化保留 epoch " "`1`、exact 匹配和 authored schema `2`; 版本值不把旧 SDK/候选的资格转移到新源码。新候选仍须独立绑定实际 clean " diff --git a/docs/locale/en/LC_MESSAGES/reference/documentation-delivery.po b/docs/locale/en/LC_MESSAGES/reference/documentation-delivery.po index d497933a6..2e2edb59b 100644 --- a/docs/locale/en/LC_MESSAGES/reference/documentation-delivery.po +++ b/docs/locale/en/LC_MESSAGES/reference/documentation-delivery.po @@ -1,14 +1,13 @@ -# +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source docs\n" +"Project-Id-Version: Nexus documentation\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: b2071f7fe314150901b2608965be704ae125409d596e4ddd46e0e84a610f2665\n" +"X-Nexus-Source-SHA256: 8200f0266dff732c36cb6285ad11c9d7f40821ceac9abcdeb31e49072a57c3d4\n" #: ../../../workspace/nexus-build-system-20261012/docs/reference/documentation-delivery.md:1 msgid "文档包、发布与独立恢复" @@ -151,21 +150,62 @@ msgstr "" "`code_source`: the full commit/tree of the same checkout and the scoped tree" " digest of platform code directories." -#: ../../../workspace/nexus-build-system-20261012/docs/reference/documentation-delivery.md:40 -msgid "`qualified_code`:当前交付索引中原已验证代码的 commit/tree 及索引文件摘要。" +msgid "" +"`qualification_references[]`:显式选入的原候选;每项保留自身源码、配置摘要、 ELF/BIN/map " +"摘要及字节数、六个 qualified scopes 和候选文件身份。" msgstr "" -"`qualified_code`: the commit/tree of previously verified code in the " -"delivery index, together with the index-file digest." +"`qualification_references[]`: explicitly selected original candidates; " +"each retains its own source, configuration digest, ELF/BIN/map digests " +"and sizes, six qualified scopes and candidate file identity." + +msgid "" +"前两项可能来自同一 Git 提交,但覆盖的目录不同。schema 2 的第三项是显式列表, " +"默认没有引用;不再从固定历史结果索引推断资格。每份候选必须先通过现有 " +"`tools.evidence.candidate.verified_projection()` 的完整原件校验;缺失或篡改原件、 " +"重复候选与非法 scope 在输出前拒绝。不同候选保持各自源码、配置及产物身份, 不能合并成全平台通过。`physical_status` " +"固定为 `not_executed`。" +msgstr "" +"The first two identities may come from the same Git commit but cover " +"different directories. The third identity in schema 2 is an explicit " +"list, empty by default; qualification is no longer inferred from a fixed" +" historical result index. Each candidate must first pass the existing " +"`tools.evidence.candidate.verified_projection()` verification of the " +"complete original closure; missing or tampered originals, duplicate " +"candidates and invalid scopes are rejected before output. Different " +"candidates keep their own source, configuration and artifact identities;" +" they cannot be merged into a whole-platform pass. `physical_status` " +"remains `not_executed`." -#: ../../../workspace/nexus-build-system-20261012/docs/reference/documentation-delivery.md:42 msgid "" -"前两项可能来自同一 Git 提交,但覆盖的目录不同。第三项引用既有软件证据,不能 因文档提交而变成当前 HEAD " -"的新固件验证。`physical_status` 固定为 `not_executed`。" +"文档包只保存已校验的精简引用,不复制候选配置、源码 SDK 或大型原始日志。 离线 `verify()` " +"校验该引用的结构和被封存字节,不重新校验未随包复制的候选闭包, 也不赋予当前 HEAD 新软件资格。原 candidate locator " +"是原主机位置,不是下载 URL。 若需要重新确认软件候选,必须取回原件并运行原 candidate verifier。 每项 " +"`verification.at_packaging` 为 `original_candidate_closure`, " +"`verification.offline` 为 `reference_metadata_only`, " +"`verification.original_evidence_included` 为 `false`;离线包不伪报原件重验成功。" +msgstr "" +"The documentation package retains only verified compact references, " +"without copying candidate configurations, source SDKs or large raw logs." +" Offline `verify()` checks the structure and sealed bytes of those " +"references; it does not reverify the candidate closure omitted from the " +"package or grant new software qualification to the current HEAD. The " +"original candidate locator identifies a location on the original host, " +"not a download URL. To recheck a software candidate, recover its " +"originals and run the original candidate verifier. Each entry sets " +"`verification.at_packaging` to `original_candidate_closure`, " +"`verification.offline` to `reference_metadata_only`, and " +"`verification.original_evidence_included` to `false`; an offline package" +" does not falsely report successful reverification of originals." + +msgid "" +"旧 schema 1 包仅保留只读验证/恢复,仍按原 `qualified_code` 字段解释。 新打包只产生 schema " +"2,不能把旧索引身份默认为新交付的受测代码。" msgstr "" -"The first two may share a Git commit but cover different paths. The third " -"references existing software evidence; a documentation commit cannot turn it" -" into new firmware verification of the current HEAD. `physical_status` is " -"fixed to `not_executed`." +"Historical schema 1 packages retain only read-only " +"verification/restoration and are interpreted using their original " +"`qualified_code`. New packaging produces schema 2 only; a historical " +"index identity cannot implicitly become the tested code of a new " +"delivery." #: ../../../workspace/nexus-build-system-20261012/docs/reference/documentation-delivery.md:45 msgid "文件与摘要合同" @@ -276,6 +316,16 @@ msgstr "" "The Python interface is below; the unified docs command provides the CLI " "entry:" +msgid "" +"CLI 的 `--qualification-reference /path/to/candidate.json` 可重复使用,逐项核对 " +"不同精确候选;不提供该选项时保存空列表。显式引用原件缺失时应修复定位或明确 不选入,工具不会悄悄降级为已验证引用。" +msgstr "" +"The CLI option `--qualification-reference /path/to/candidate.json` may " +"be repeated to verify each distinct exact candidate. Without this option" +" an empty list is retained. If an explicitly referenced original is " +"missing, correct its locator or explicitly omit it; the tool does not " +"silently downgrade it to a verified reference." + #: ../../../workspace/nexus-build-system-20261012/docs/reference/documentation-delivery.md:91 msgid "这是接口说明片段,路径由调用方显式提供;可执行命令和实际验收结果以统一 docs CLI 及其本次交付 receipt 为准。" msgstr "" diff --git a/docs/locale/en/LC_MESSAGES/status/ledgers.po b/docs/locale/en/LC_MESSAGES/status/ledgers.po index 4059d80fe..77cd2e25a 100644 --- a/docs/locale/en/LC_MESSAGES/status/ledgers.po +++ b/docs/locale/en/LC_MESSAGES/status/ledgers.po @@ -1,32 +1,40 @@ -# Authored translation; independently semantically reviewed. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source docs\n" -"Report-Msgid-Bugs-To: \n" -"Language-Team: Nexus agent-reviewed documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 7bacb1fc91ca7e6c543cb487ce7a2e9839c31e182fd09abdbfed1b06d8122250\n" +"X-Nexus-Source-SHA256: 5b9deebacf91a5006a4eef30beaefd1c00fc31f990560f7a03091450cf7f9bc9\n" #: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:9 msgid "原台账、退出条件与派生视图" msgstr "Original ledgers, exit conditions and derived views" -#: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:11 msgid "" -"下面的页面来自十份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源 " -"SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" +"下面的页面来自十一份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源" +" SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" msgstr "" -"The pages below derive from ten original CSV files; they do not establish a " -"second status database. Every task preserves its original ID, order, fields," -" exit conditions, implementation, software/physical state and evidence " -"locator, with a verifiable source SHA-256. When an original CSV has no " -"separate exit field, the page says so; other ledgers cannot be combined to " -"invent a passed condition." +"The following pages derive from eleven original CSV files; they do not " +"establish a second status database. Each task preserves its original " +"identifier, order, all fields, exit conditions, implementation status, " +"software/physical status and original evidence locators; the source " +"SHA-256 is available for comparison. If an original CSV has no separate " +"exit field, this is explicit; no passed condition may be assembled from " +"another ledger." + +msgid "" +"`soc-ip-layout` 的 R01–R48 保留目录和共享 IP 重构的原状态。R43–R48 记录 精确 P " +"的软件执行、分段计划、SDK、资源与候选;其 `not_executed` 物理值不因 页面生成改变。最新结果导航见 " +"[软件证据入口](software-verification.md)。" +msgstr "" +"R01–R48 in `soc-ip-layout` preserve original directory and shared-IP " +"refactor states. R43–R48 record software execution, segmented plan " +"coverage, SDK, resources and the candidate for exact P; generating this " +"page does not change physical `not_executed` values. Find the latest " +"results through the [software evidence entry](software-verification.md)." #: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:30 msgid "正确解读状态" diff --git a/docs/locale/en/LC_MESSAGES/status/ledgers/soc-ip-layout.po b/docs/locale/en/LC_MESSAGES/status/ledgers/soc-ip-layout.po new file mode 100644 index 000000000..540c1d8d6 --- /dev/null +++ b/docs/locale/en/LC_MESSAGES/status/ledgers/soc-ip-layout.po @@ -0,0 +1,1842 @@ +# Independently reviewed translations; exact canonical source identity below. +msgid "" +msgstr "" +"Project-Id-Version: Nexus documentation\n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Nexus-Reviewed: true\n" +"X-Nexus-Source-SHA256: beb170c63e3a351784068ab52d482c5384f8d96eb4ea65e5912dc2b05a5f2e59\n" + +msgid "SoC/IP 目录整理台账" +msgstr "SoC/IP layout ledger" + +msgid "" +"本页由原 CSV 派生,不是新的状态权威、执行记录或软件资格。 原编号、顺序和全部原字段来自 {download}`原台账 " +"<../../design/soc-ip-layout-execution.csv>`。 源文件:`docs/design/soc-ip-lay" +"out-execution.csv`;SHA-256:`0564d12a1125e718b7560defc8a7cbb184c77ebeb1b1" +"3ba1eea1331b2cac8fa4`。" +msgstr "" +"This page derives from the original CSV; it is not a new status " +"authority, execution record or software qualification. Original " +"identifiers, order and all original fields come from the " +"{download}`original ledger <../../design/soc-ip-layout-execution.csv>`. " +"Source file: `docs/design/soc-ip-layout-execution.csv`; SHA-256: " +"`0564d12a1125e718b7560defc8a7cbb184c77ebeb1b13ba1eea1331b2cac8fa4`." + +msgid "" +"不同台账范围重叠,不相加数量或计算全项目完成率。实现状态与软件、 物理状态分别解释;`development_passed` 不表示当前文档 " +"HEAD 已执行。 `qualification_record_authority` 要回到精确结果索引判断源码和范围。 绝对 " +"`/workspace`、`/dev/shm` 定位保留原字节,仅说明原执行主机位置; 可取得归档及 member 定位见 " +"[软件证据入口](../software-verification.md)。" +msgstr "" +"Different ledgers overlap in scope; do not add their counts or calculate" +" a project-wide completion rate. Interpret implementation, software and " +"physical status separately. `development_passed` does not mean execution" +" at the current documentation HEAD. Consult the exact results index for " +"the source and scope of `qualification_record_authority`. Absolute " +"`/workspace` and `/dev/shm` locators retain their original bytes and " +"only identify the original execution host. See the [software-evidence " +"entry](../software-verification.md) for retrievable archives and member " +"locations." + +msgid "**原退出字段**只有原 CSV 中实际存在的字段;任务、资格前提和合同链接 也按原文保留,不能据此补造某次执行。" +msgstr "" +"**Original exit fields** are only fields actually present in the " +"original CSV. Tasks, qualification prerequisites and contract links also" +" retain their original wording; they cannot be used to invent an " +"execution." + +msgid "本表包含 48 条原记录,仅表示来源行数。" +msgstr "" +"This table contains 48 original records; this is only the source row " +"count." + +msgid "R01" +msgstr "R01" + +msgid "原字段" +msgstr "Original field" + +msgid "原值" +msgstr "Original value" + +msgid "`id`" +msgstr "`id`" + +msgid "`work_package`" +msgstr "`work_package`" + +msgid "baseline" +msgstr "baseline" + +msgid "`requirement`" +msgstr "`requirement`" + +msgid "Branch and source baseline" +msgstr "Branch and source baseline" + +msgid "`exit_condition`" +msgstr "`exit_condition`" + +msgid "Record actual source revision and branch before edits" +msgstr "Record actual source revision and branch before edits" + +msgid "`implementation_paths`" +msgstr "`implementation_paths`" + +msgid "AGENTS.md" +msgstr "AGENTS.md" + +msgid "`implementation_status`" +msgstr "`implementation_status`" + +msgid "implemented" +msgstr "implemented" + +msgid "`software_status`" +msgstr "`software_status`" + +msgid "reviewed" +msgstr "reviewed" + +msgid "`hardware_status`" +msgstr "`hardware_status`" + +msgid "not_executed" +msgstr "not_executed" + +msgid "`evidence_paths`" +msgstr "`evidence_paths`" + +msgid ".git/local-recovery/soc-ip-layout/baseline.json" +msgstr ".git/local-recovery/soc-ip-layout/baseline.json" + +msgid "`notes`" +msgstr "`notes`" + +msgid "Local and GitHub refactor/soc-ip-layout branches start at 890a6026384b." +msgstr "" +"Local and GitHub refactor/soc-ip-layout branches start at 890a6026384b." + +msgid "R02" +msgstr "R02" + +msgid "Per-file responsibility and destination map" +msgstr "Per-file responsibility and destination map" + +msgid "Review every moved file and distinguish shared IP from SoC integration" +msgstr "Review every moved file and distinguish shared IP from SoC integration" + +msgid "soc;drivers;tools/configure" +msgstr "soc;drivers;tools/configure" + +msgid ".git/local-recovery/soc-ip-layout/path-map.json" +msgstr ".git/local-recovery/soc-ip-layout/path-map.json" + +msgid "" +"Shared IP and 35 family production modules reviewed; import-normalized " +"family AST unchanged. These are original migration-baseline identities " +"and counts, not current P totals; final execution is recorded in " +"R43-R48." +msgstr "" +"Shared IP and 35 family production modules reviewed; import-normalized " +"family AST unchanged. These are original migration-baseline identities " +"and counts, not current P totals; final execution is recorded in " +"R43-R48." + +msgid "R03" +msgstr "R03" + +msgid "Exact platform mode resource and rejection baseline" +msgstr "Exact platform mode resource and rejection baseline" + +msgid "" +"Record maintained selections and rejection behavior without expanding " +"support" +msgstr "" +"Record maintained selections and rejection behavior without expanding " +"support" + +msgid "tools/configure;tests/contracts" +msgstr "tools/configure;tests/contracts" + +msgid "focused_passed" +msgstr "focused_passed" + +msgid "build/soc-ip-layout/baseline/configurations" +msgstr "build/soc-ip-layout/baseline/configurations" + +msgid "" +"136 exact assemblies including all 71 catalogue entries resolved in both" +" source trees; no effective decision changes. Existing negative " +"contracts remain enforced. These are original migration-baseline " +"identities and counts, not current P totals; final execution is recorded" +" in R43-R48." +msgstr "" +"136 exact assemblies including all 71 catalogue entries resolved in both" +" source trees; no effective decision changes. Existing negative " +"contracts remain enforced. These are original migration-baseline " +"identities and counts, not current P totals; final execution is recorded" +" in R43-R48." + +msgid "R04" +msgstr "R04" + +msgid "Test identities and execution domains" +msgstr "Test identities and execution domains" + +msgid "Map retained cases to Native; tool; ARM and physical domains" +msgstr "Map retained cases to Native; tool; ARM and physical domains" + +msgid "tests;tools/configure/tests;tools/testing" +msgstr "tests;tools/configure/tests;tools/testing" + +msgid "build/soc-ip-layout/configuration/case-mapping.json" +msgstr "build/soc-ip-layout/configuration/case-mapping.json" + +msgid "" +"Original 1035 configure cases retained one-to-one; 132 family cases " +"relocated; 8 migration regressions added. These are original " +"migration-baseline identities and counts, not current P totals; final " +"execution is recorded in R43-R48." +msgstr "" +"Original 1035 configure cases retained one-to-one; 132 family cases " +"relocated; 8 migration regressions added. These are original " +"migration-baseline identities and counts, not current P totals; final " +"execution is recorded in R43-R48." + +msgid "R05" +msgstr "R05" + +msgid "Flash RAM ops and IRQ baseline" +msgstr "Flash RAM ops and IRQ baseline" + +msgid "Record actual linked resource and retention baseline for comparisons" +msgstr "Record actual linked resource and retention baseline for comparisons" + +msgid "tools/measurement;tests/contracts" +msgstr "tools/measurement;tests/contracts" + +msgid "build/soc-ip-layout/baseline/resources" +msgstr "build/soc-ip-layout/baseline/resources" + +msgid "" +"32 prior resource reports from 16 presets retained with matching ELF " +"hash/size. Historical baseline capture; not new execution or new-source " +"resource qualification. Only resource reports were copied; ELF/map " +"identities were observed at capture, not retained as new build evidence." +" Final P comparisons are recorded in R46." +msgstr "" +"32 prior resource reports from 16 presets retained with matching ELF " +"hash/size. Historical baseline capture; not new execution or new-source " +"resource qualification. Only resource reports were copied; ELF/map " +"identities were observed at capture, not retained as new build evidence." +" Final P comparisons are recorded in R46." + +msgid "R06" +msgstr "R06" + +msgid "Single execution ledger" +msgstr "Single execution ledger" + +msgid "Maintain exactly R01-R48 with implementation and execution separated" +msgstr "Maintain exactly R01-R48 with implementation and execution separated" + +msgid "docs/design/soc-ip-layout-execution.csv" +msgstr "docs/design/soc-ip-layout-execution.csv" + +msgid "" +"One R01-R48 task record; implementation, software and physical status " +"stay separate. Final software " +"tested_source=P=92e669233957a2e450a528394e8f4dd0794c7049, " +"tree=6e38d4c43d59330a0a9a9b7cac21f591d9c2719f; integrated " +"M=c79fd9672a9fe56379744d834cfccd822bc40be1 has the same tree. This " +"documentation update records P and does not claim its own software " +"qualification." +msgstr "" +"One R01-R48 task record; implementation, software and physical status " +"stay separate. Final software " +"tested_source=P=92e669233957a2e450a528394e8f4dd0794c7049, " +"tree=6e38d4c43d59330a0a9a9b7cac21f591d9c2719f; integrated " +"M=c79fd9672a9fe56379744d834cfccd822bc40be1 has the same tree. This " +"documentation update records P and does not claim its own software " +"qualification." + +msgid "R07" +msgstr "R07" + +msgid "infrastructure" +msgstr "infrastructure" + +msgid "Explicit family descriptors" +msgstr "Explicit family descriptors" + +msgid "" +"Declare canonical source directories and configuration modules without " +"discovery" +msgstr "" +"Declare canonical source directories and configuration modules without " +"discovery" + +msgid "tools/configure/providers/descriptor.py" +msgstr "tools/configure/providers/descriptor.py" + +msgid "build/soc-ip-layout/configuration" +msgstr "build/soc-ip-layout/configuration" + +msgid "" +"Explicit vendor/source/configuration directories; no family-prefix path " +"inference." +msgstr "" +"Explicit vendor/source/configuration directories; no family-prefix path " +"inference." + +msgid "R08" +msgstr "R08" + +msgid "Module loading and type identity" +msgstr "Module loading and type identity" + +msgid "" +"Explicit loading preserves common Python type identity and import " +"boundaries" +msgstr "" +"Explicit loading preserves common Python type identity and import " +"boundaries" + +msgid "tools/configure/providers;soc" +msgstr "tools/configure/providers;soc" + +msgid "" +"Normal selected SoC packages and canonical shared types; six actual " +"RED/GREEN loading regressions." +msgstr "" +"Normal selected SoC packages and canonical shared types; six actual " +"RED/GREEN loading regressions." + +msgid "R09" +msgstr "R09" + +msgid "Input closure and freshness" +msgstr "Input closure and freshness" + +msgid "" +"Moved configuration dependencies remain in identity and stale-input " +"rejection" +msgstr "" +"Moved configuration dependencies remain in identity and stale-input " +"rejection" + +msgid "tools/configure/dependencies.py;tools/configure/bundle.py" +msgstr "tools/configure/dependencies.py;tools/configure/bundle.py" + +msgid "" +"All executing package initializers/helpers hashed; migrated sources " +"protected from owned output overwrite." +msgstr "" +"All executing package initializers/helpers hashed; migrated sources " +"protected from owned output overwrite." + +msgid "R10" +msgstr "R10" + +msgid "SDK roots and required members" +msgstr "SDK roots and required members" + +msgid "Source SDK includes declared SoC configuration and IP driver inputs" +msgstr "Source SDK includes declared SoC configuration and IP driver inputs" + +msgid "cmake/package" +msgstr "cmake/package" + +msgid "" +"build/soc-ip-layout/sdk-driver-red.log;build/soc-ip-layout/sdk-driver-gr" +"een.log;build/soc-ip-layout/sdk-payload.log;build/soc-ip-layout/sdk-sele" +"ction.log" +msgstr "" +"build/soc-ip-layout/sdk-driver-red.log;build/soc-ip-layout/sdk-driver-gr" +"een.log;build/soc-ip-layout/sdk-payload.log;build/soc-ip-layout/sdk-sele" +"ction.log" + +msgid "" +"New shared driver transport regression RED then GREEN; 20 device payload" +" and 45 selection/portable tests pass. Actual final P installed " +"consumers are recorded in R45." +msgstr "" +"New shared driver transport regression RED then GREEN; 20 device payload" +" and 45 selection/portable tests pass. Actual final P installed " +"consumers are recorded in R45." + +msgid "R11" +msgstr "R11" + +msgid "Family test discovery" +msgstr "Family test discovery" + +msgid "" +"Retain and execute tests under " +"tools/configure/tests/providers/<family>" +msgstr "" +"Retain and execute tests under " +"tools/configure/tests/providers/<family>" + +msgid "tools/configure/tests/providers" +msgstr "tools/configure/tests/providers" + +msgid "" +"Family tests remain in existing tools/configure discovery domain; no new" +" runner." +msgstr "" +"Family tests remain in existing tools/configure discovery domain; no new" +" runner." + +msgid "R12" +msgstr "R12" + +msgid "Style source capability and measurement coverage" +msgstr "Style source capability and measurement coverage" + +msgid "Include drivers and SoC configuration in existing observers and gates" +msgstr "Include drivers and SoC configuration in existing observers and gates" + +msgid "tools/maintenance;tools/measurement;scripts/validation" +msgstr "tools/maintenance;tools/measurement;scripts/validation" + +msgid "" +"build/soc-ip-layout/baseline/quality-tools-green.log;build/soc-ip-layout" +"/baseline/io-lifecycle-green.log;build/soc-ip-layout/capabilities-green." +"log" +msgstr "" +"build/soc-ip-layout/baseline/quality-tools-green.log;build/soc-ip-layout" +"/baseline/io-lifecycle-green.log;build/soc-ip-layout/capabilities-green." +"log" + +msgid "" +"New drivers remain covered by style, private include, " +"ops/source/retention, static analysis and target-local lifecycle checks;" +" independent review closed all migration findings." +msgstr "" +"New drivers remain covered by style, private include, " +"ops/source/retention, static analysis and target-local lifecycle checks;" +" independent review closed all migration findings." + +msgid "R13" +msgstr "R13" + +msgid "configuration_providers" +msgstr "configuration_providers" + +msgid "Move Native configuration" +msgstr "Move Native configuration" + +msgid "Use soc/native/configure with explicit imports and retained tests" +msgstr "Use soc/native/configure with explicit imports and retained tests" + +msgid "soc/native/configure" +msgstr "soc/native/configure" + +msgid "phase1_complete_index_passed" +msgstr "phase1_complete_index_passed" + +msgid "" +"build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync" +"-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/pha" +"se2-hook-evidence/manifest.json" +msgstr "" +"build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync" +"-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/pha" +"se2-hook-evidence/manifest.json" + +msgid "" +"Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical " +"commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index" +" gates: 3215 tool tests and 352 programs / 6352 Google cases. This " +"result does not qualify subsequent behavior changes. The first outer " +"hook log and sync receipt retain the historical assertion; reused " +"build/native-debug/tdd bytes are not its original detailed evidence. " +"Final P gates and execution are recorded in R43-R48." +msgstr "" +"Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical " +"commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index" +" gates: 3215 tool tests and 352 programs / 6352 Google cases. This " +"result does not qualify subsequent behavior changes. The first outer " +"hook log and sync receipt retain the historical assertion; reused " +"build/native-debug/tdd bytes are not its original detailed evidence. " +"Final P gates and execution are recorded in R43-R48." + +msgid "R14" +msgstr "R14" + +msgid "Move F103 and GD303 configuration" +msgstr "Move F103 and GD303 configuration" + +msgid "Use each exact family configure package and retained tests" +msgstr "Use each exact family configure package and retained tests" + +msgid "soc/stm32/stm32f103/configure;soc/gd32/gd32f303/configure" +msgstr "soc/stm32/stm32f103/configure;soc/gd32/gd32f303/configure" + +msgid "R15" +msgstr "R15" + +msgid "Move F407 and GD470 configuration" +msgstr "Move F407 and GD470 configuration" + +msgid "soc/stm32/stm32f407/configure;soc/gd32/gd32f470/configure" +msgstr "soc/stm32/stm32f407/configure;soc/gd32/gd32f470/configure" + +msgid "R16" +msgstr "R16" + +msgid "Move G474 configuration and helpers" +msgstr "Move G474 configuration and helpers" + +msgid "Co-locate G474 contract; construction; emission and selected helpers" +msgstr "Co-locate G474 contract; construction; emission and selected helpers" + +msgid "soc/stm32/stm32g474/configure" +msgstr "soc/stm32/stm32g474/configure" + +msgid "R17" +msgstr "R17" + +msgid "Move H743 configuration" +msgstr "Move H743 configuration" + +msgid "Co-locate H743 contract; construction; emission and selected helpers" +msgstr "Co-locate H743 contract; construction; emission and selected helpers" + +msgid "soc/stm32/stm32h743/configure" +msgstr "soc/stm32/stm32h743/configure" + +msgid "R18" +msgstr "R18" + +msgid "Move U575 configuration and clear old production paths" +msgstr "Move U575 configuration and clear old production paths" + +msgid "Co-locate U575 policy and remove obsolete family production packages" +msgstr "Co-locate U575 policy and remove obsolete family production packages" + +msgid "soc/stm32/stm32u575/configure;tools/configure/providers" +msgstr "soc/stm32/stm32u575/configure;tools/configure/providers" + +msgid "R19" +msgstr "R19" + +msgid "shared_drivers" +msgstr "shared_drivers" + +msgid "Move UART V1 channel DMA engine" +msgstr "Move UART V1 channel DMA engine" + +msgid "Select reviewed algorithm and private headers from drivers" +msgstr "Select reviewed algorithm and private headers from drivers" + +msgid "drivers/uart/uart_v1_channel_dma" +msgstr "drivers/uart/uart_v1_channel_dma" + +msgid "" +"build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-de" +"bug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-" +"debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json" +msgstr "" +"build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-de" +"bug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-" +"debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json" + +msgid "" +"Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero " +"skipped. Aggregate scope is not a per-row test count or complete " +"qualification. Physical HIL not executed. The preserved shared-drivers " +"XML matches the original report hash; its embedded generic Native XML " +"path was later reused. Final P execution is separately recorded in " +"R43-R48." +msgstr "" +"Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero " +"skipped. Aggregate scope is not a per-row test count or complete " +"qualification. Physical HIL not executed. The preserved shared-drivers " +"XML matches the original report hash; its embedded generic Native XML " +"path was later reused. Final P execution is separately recorded in " +"R43-R48." + +msgid "R20" +msgstr "R20" + +msgid "Move I2C V1 asynchronous engine" +msgstr "Move I2C V1 asynchronous engine" + +msgid "Select reviewed IRQ/DMA algorithm and private headers from drivers" +msgstr "Select reviewed IRQ/DMA algorithm and private headers from drivers" + +msgid "drivers/i2c/i2c_v1_async" +msgstr "drivers/i2c/i2c_v1_async" + +msgid "R21" +msgstr "R21" + +msgid "Move I2C V2 engine" +msgstr "Move I2C V2 engine" + +msgid "Select reviewed polling algorithm and private headers from drivers" +msgstr "Select reviewed polling algorithm and private headers from drivers" + +msgid "drivers/i2c/i2c_v2" +msgstr "drivers/i2c/i2c_v2" + +msgid "R22" +msgstr "R22" + +msgid "Split legacy_devices and move ADC" +msgstr "Split legacy_devices and move ADC" + +msgid "" +"Separate private legacy interfaces and select the ADC algorithm from " +"drivers" +msgstr "" +"Separate private legacy interfaces and select the ADC algorithm from " +"drivers" + +msgid "drivers/adc/legacy_adc" +msgstr "drivers/adc/legacy_adc" + +msgid "R23" +msgstr "R23" + +msgid "Move legacy PWM" +msgstr "Move legacy PWM" + +msgid "Select fixed PWM algorithm and narrow private contract from drivers" +msgstr "Select fixed PWM algorithm and narrow private contract from drivers" + +msgid "drivers/timer/legacy_pwm" +msgstr "drivers/timer/legacy_pwm" + +msgid "R24" +msgstr "R24" + +msgid "Move legacy Flash and SoC helpers" +msgstr "Move legacy Flash and SoC helpers" + +msgid "" +"Select Flash algorithm; place STM32 identity helper; retain UART " +"collaboration and update READMEs" +msgstr "" +"Select Flash algorithm; place STM32 identity helper; retain UART " +"collaboration and update READMEs" + +msgid "" +"drivers/flash/legacy_flash;soc/stm32/common/stm32_identity.h;soc/common/" +"uart_control.h;drivers/README.md;soc/common/README.md" +msgstr "" +"drivers/flash/legacy_flash;soc/stm32/common/stm32_identity.h;soc/common/" +"uart_control.h;drivers/README.md;soc/common/README.md" + +msgid "R25" +msgstr "R25" + +msgid "facts_and_ir" +msgstr "facts_and_ir" + +msgid "Centralize part package RAM and Flash geometry" +msgstr "Centralize part package RAM and Flash geometry" + +msgid "" +"Use one exact fact authority for part; package; memory and physical " +"geometry" +msgstr "" +"Use one exact fact authority for part; package; memory and physical " +"geometry" + +msgid "soc;tools/configure" +msgstr "soc;tools/configure" + +msgid "" +"build/soc-ip-layout/facts-ir/part-red.log;build/soc-ip-layout/facts-ir/c" +"lock-red.log;build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/s" +"oc-ip-layout/facts-ir/f407-flash.xml;build/soc-ip-layout/facts-ir/final-" +"focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;bu" +"ild/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/f" +"acts-ir/all-flash-red.log;build/soc-ip-layout/flash-geometry/summary.jso" +"n" +msgstr "" +"build/soc-ip-layout/facts-ir/part-red.log;build/soc-ip-layout/facts-ir/c" +"lock-red.log;build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/s" +"oc-ip-layout/facts-ir/f407-flash.xml;build/soc-ip-layout/facts-ir/final-" +"focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;bu" +"ild/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/f" +"acts-ir/all-flash-red.log;build/soc-ip-layout/flash-geometry/summary.jso" +"n" + +msgid "" +"Exact PartPolicy and optional-memory admission; physical numbers remain " +"in soc.json and frozen PhysicalPartIR views. All seven MCU Flash " +"geometries derive readonly C from validated facts; six retain global " +"symbols and F407 uses selected static geometry. Final focused " +"configuration suite: 364 tests; separate integration/bundle suite: 143 " +"tests. These overlapping scopes are not additive or final-source " +"qualification." +msgstr "" +"Exact PartPolicy and optional-memory admission; physical numbers remain " +"in soc.json and frozen PhysicalPartIR views. All seven MCU Flash " +"geometries derive readonly C from validated facts; six retain global " +"symbols and F407 uses selected static geometry. Final focused " +"configuration suite: 364 tests; separate integration/bundle suite: 143 " +"tests. These overlapping scopes are not additive or final-source " +"qualification." + +msgid "R26" +msgstr "R26" + +msgid "Typed controller integration record" +msgstr "Typed controller integration record" + +msgid "Use explicit typed integration data without family-name inference" +msgstr "Use explicit typed integration data without family-name inference" + +msgid "soc;tools/configure/providers/common.py" +msgstr "soc;tools/configure/providers/common.py" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/integration-red.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/integration-red.log" + +msgid "" +"ControllerIntegrationIR composes existing canonical " +"claims/DMA/IRQ/clock/pin/hardware objects; PhysicalPartIR reuses frozen " +"facts. No new JSON/hash field or route resolver. Actual production " +"consumers: F407 primary SPI and F407/GD470 paired secondary paths. Other" +" constructors and explicit legacy fixture adapters remain. 364-test " +"focused suite and overlapping 52-test final closure passed; no " +"final-source qualification." +msgstr "" +"ControllerIntegrationIR composes existing canonical " +"claims/DMA/IRQ/clock/pin/hardware objects; PhysicalPartIR reuses frozen " +"facts. No new JSON/hash field or route resolver. Actual production " +"consumers: F407 primary SPI and F407/GD470 paired secondary paths. Other" +" constructors and explicit legacy fixture adapters remain. 364-test " +"focused suite and overlapping 52-test final closure passed; no " +"final-source qualification." + +msgid "R27" +msgstr "R27" + +msgid "Clock reset pin and state integration" +msgstr "Clock reset pin and state integration" + +msgid "Make integration ownership explicit without hidden side effects" +msgstr "Make integration ownership explicit without hidden side effects" + +msgid "soc;drivers" +msgstr "soc;drivers" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log" + +msgid "" +"Exact family clock, GPIO/preflight, ControllerBinding and secondary cold" +" hooks own chip integration. Common resource leases/IRQ/drain ordering " +"remain. Focused configuration and bundle suites passed; no runtime " +"registration or broader clock/Board/backend qualification." +msgstr "" +"Exact family clock, GPIO/preflight, ControllerBinding and secondary cold" +" hooks own chip integration. Common resource leases/IRQ/drain ordering " +"remain. Focused configuration and bundle suites passed; no runtime " +"registration or broader clock/Board/backend qualification." + +msgid "R28" +msgstr "R28" + +msgid "Consume existing IRQ DMA and memory IR consistently" +msgstr "Consume existing IRQ DMA and memory IR consistently" + +msgid "Use reviewed immutable IR instead of duplicated derived facts" +msgstr "Use reviewed immutable IR instead of duplicated derived facts" + +msgid "soc;tools/configure/ir.py;tools/configure/memory.py" +msgstr "soc;tools/configure/ir.py;tools/configure/memory.py" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/claims-red.log;build/soc-ip-l" +"ayout/facts-ir/primary-dma-red.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/claims-red.log;build/soc-ip-l" +"ayout/facts-ir/primary-dma-red.log" + +msgid "" +"Typed owned_claims uses canonical ResourcePlanIR; selected " +"hardware/DMA/IRQ/clock/pin consumers use ControllerIntegrationIR rather " +"than reparsing routes. Meaningful omitted-lane/claim regressions " +"recorded; other maintained typed or explicit legacy adapters remain. " +"Focused suites are not final ARM/SDK qualification." +msgstr "" +"Typed owned_claims uses canonical ResourcePlanIR; selected " +"hardware/DMA/IRQ/clock/pin consumers use ControllerIntegrationIR rather " +"than reparsing routes. Meaningful omitted-lane/claim regressions " +"recorded; other maintained typed or explicit legacy adapters remain. " +"Focused suites are not final ARM/SDK qualification." + +msgid "R29" +msgstr "R29" + +msgid "Move family special cases out of common tools" +msgstr "Move family special cases out of common tools" + +msgid "" +"Keep family policy in exact SoC configure packages and common mechanisms" +" shared" +msgstr "" +"Keep family policy in exact SoC configure packages and common mechanisms" +" shared" + +msgid "" +"build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/" +"r30/green-final.log;build/soc-ip-layout/facts-ir/final-focused.log;build" +"/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout" +"/r29-hardware;build/soc-ip-layout/r29-public-policies;build/soc-ip-layou" +"t/facts-ir/final-closure-green.log;build/quality/soc-ip-config-r32/profi" +"le-python-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/" +"r30/green-final.log;build/soc-ip-layout/facts-ir/final-focused.log;build" +"/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout" +"/r29-hardware;build/soc-ip-layout/r29-public-policies;build/soc-ip-layou" +"t/facts-ir/final-closure-green.log;build/quality/soc-ip-config-r32/profi" +"le-python-green.log" + +msgid "" +"Explicit SoC hardware/secondary, clock/linker/runtime-power, " +"GPIO/preflight, selected support, UART/stream/endpoint and " +"CAN/coupled/pin policies implemented. Generated-output equivalence " +"checked in separately recorded 18-, 23- and 8-configuration scopes; test" +" counts overlap and must not be added. Remaining common family literals " +"are explicit dependency paths and ThreadX cross-Board/backend OS " +"admission. F103/GD303 frozen LegacyTransportProfile supplies exact " +"wiring without family fallback; its 38-test focused run is separate and " +"overlaps related suites." +msgstr "" +"Explicit SoC hardware/secondary, clock/linker/runtime-power, " +"GPIO/preflight, selected support, UART/stream/endpoint and " +"CAN/coupled/pin policies implemented. Generated-output equivalence " +"checked in separately recorded 18-, 23- and 8-configuration scopes; test" +" counts overlap and must not be added. Remaining common family literals " +"are explicit dependency paths and ThreadX cross-Board/backend OS " +"admission. F103/GD303 frozen LegacyTransportProfile supplies exact " +"wiring without family fallback; its 38-test focused run is separate and " +"overlaps related suites." + +msgid "R30" +msgstr "R30" + +msgid "Capability revision and support projection" +msgstr "Capability revision and support projection" + +msgid "" +"Derive current views from exact reviewed facts without new support " +"claims" +msgstr "" +"Derive current views from exact reviewed facts without new support " +"claims" + +msgid "tools/maintenance;docs/delivery/capabilities.md" +msgstr "tools/maintenance;docs/delivery/capabilities.md" + +msgid "" +"build/soc-ip-layout/r30/red.log;build/soc-ip-layout/r30/owner-red.log;bu" +"ild/soc-ip-layout/r30/green-final.log" +msgstr "" +"build/soc-ip-layout/r30/red.log;build/soc-ip-layout/r30/owner-red.log;bu" +"ild/soc-ip-layout/r30/green-final.log" + +msgid "" +"98 tests passed across 10 focused modules. Existing silicon policy/IR " +"projects separately for clock and controller owners; absent review stays" +" null and companion/endpoint policy is not inherited. No broader support" +" or hardware observation." +msgstr "" +"98 tests passed across 10 focused modules. Existing silicon policy/IR " +"projects separately for clock and controller owners; absent review stays" +" null and companion/endpoint policy is not inherited. No broader support" +" or hardware observation." + +msgid "R31" +msgstr "R31" + +msgid "static_configuration_and_lifecycle" +msgstr "static_configuration_and_lifecycle" + +msgid "I2C V2 const configuration pilot" +msgstr "I2C V2 const configuration pilot" + +msgid "Separate immutable configuration from mutable transfer state" +msgstr "Separate immutable configuration from mutable transfer state" + +msgid "drivers/i2c/i2c_v2;soc" +msgstr "drivers/i2c/i2c_v2;soc" + +msgid "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log" +msgstr "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log" + +msgid "" +"Selected G474/H743/U575 const configuration and separate BSS runtime " +"implemented. Shared focused execution: 9 targets / 121 Google cases and " +"38 related Python tests passed; not per-row counts. Development ARM cost" +" comparison is recorded in R46; final P complete gates and scoped " +"execution are recorded in R43-R48." +msgstr "" +"Selected G474/H743/U575 const configuration and separate BSS runtime " +"implemented. Shared focused execution: 9 targets / 121 Google cases and " +"38 related Python tests passed; not per-row counts. Development ARM cost" +" comparison is recorded in R46; final P complete gates and scoped " +"execution are recorded in R43-R48." + +msgid "R32" +msgstr "R32" + +msgid "UART DMA const configuration" +msgstr "UART DMA const configuration" + +msgid "Separate immutable configuration from mutable request and block state" +msgstr "Separate immutable configuration from mutable request and block state" + +msgid "drivers/uart/uart_v1_channel_dma;soc" +msgstr "drivers/uart/uart_v1_channel_dma;soc" + +msgid "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green" +msgstr "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green" + +msgid "" +"Selected F103/GD303 UART DMA immutable config and BSS runtime " +"implemented; duplex receive extension remains separate. Static " +"config/resources share runtime lifetime and successful stop preserves " +"binding. Final focused run: 3 targets / 65 Google cases plus 38 Python " +"tests. Host execution covers actual SoC wrapper/engine/GPIO; generated " +"DMA factory runtime is not claimed. Development ARM comparison is " +"recorded separately in R46; final P complete gates and scoped execution " +"are recorded in R43-R48." +msgstr "" +"Selected F103/GD303 UART DMA immutable config and BSS runtime " +"implemented; duplex receive extension remains separate. Static " +"config/resources share runtime lifetime and successful stop preserves " +"binding. Final focused run: 3 targets / 65 Google cases plus 38 Python " +"tests. Host execution covers actual SoC wrapper/engine/GPIO; generated " +"DMA factory runtime is not claimed. Development ARM comparison is " +"recorded separately in R46; final P complete gates and scoped execution " +"are recorded in R43-R48." + +msgid "R33" +msgstr "R33" + +msgid "Shared ops immutable faces and direct IRQ" +msgstr "Shared ops immutable faces and direct IRQ" + +msgid "" +"Preserve shared readonly methods; independent contexts and direct IRQ " +"entry" +msgstr "" +"Preserve shared readonly methods; independent contexts and direct IRQ " +"entry" + +msgid "drivers;soc;io" +msgstr "drivers;soc;io" + +msgid "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green;build/quality/soc-ip-config-r31/i2c-green" +msgstr "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green;build/quality/soc-ip-config-r31/i2c-green" + +msgid "" +"Shared readonly ops, immutable public faces, distinct static contexts " +"and direct IRQ entry symbols retained for the selected I2C V2 and UART " +"DMA changes; no hot-path dispatch layer. Focused UART " +"wrapper/engine/GPIO and I2C model suites passed. Development ARM linked " +"retention/costs are recorded in R46; final P scoped validation is " +"recorded in R43-R48." +msgstr "" +"Shared readonly ops, immutable public faces, distinct static contexts " +"and direct IRQ entry symbols retained for the selected I2C V2 and UART " +"DMA changes; no hot-path dispatch layer. Focused UART " +"wrapper/engine/GPIO and I2C model suites passed. Development ARM linked " +"retention/costs are recorded in R46; final P scoped validation is " +"recorded in R43-R48." + +msgid "R34" +msgstr "R34" + +msgid "Move initialization and stop logic into C" +msgstr "Move initialization and stop logic into C" + +msgid "" +"Keep explicit lifecycle ordering and cleanup responsibilities in " +"concrete code" +msgstr "" +"Keep explicit lifecycle ordering and cleanup responsibilities in " +"concrete code" + +msgid "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-" +"config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-re" +"d;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-con" +"fig-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ri" +"ng-red;build/quality/soc-ip-config-r32/uart-ring-green" +msgstr "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-" +"config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-re" +"d;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-con" +"fig-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ri" +"ng-red;build/quality/soc-ip-config-r32/uart-ring-green" + +msgid "" +"Selected I2C V2, F103/GD303 UART DMA and legacy ADC/PWM/Flash cold " +"integration lives in concrete C preparation with existing stop paths. " +"U575 engine stop precedes clock release. UART AHB DMA gate and Platform " +"ordering remain generated; legacy I2C V1 and other peripherals retain " +"family-emitted behavior. Focused suites overlap and are not additive or " +"whole-platform qualification." +msgstr "" +"Selected I2C V2, F103/GD303 UART DMA and legacy ADC/PWM/Flash cold " +"integration lives in concrete C preparation with existing stop paths. " +"U575 engine stop precedes clock release. UART AHB DMA gate and Platform " +"ordering remain generated; legacy I2C V1 and other peripherals retain " +"family-emitted behavior. Focused suites overlap and are not additive or " +"whole-platform qualification." + +msgid "R35" +msgstr "R35" + +msgid "Retained failure and retry contracts" +msgstr "Retained failure and retry contracts" + +msgid "" +"Preserve partial effects and borrows until proved drain or explicit " +"retry" +msgstr "" +"Preserve partial effects and borrows until proved drain or explicit " +"retry" + +msgid "soc;drivers;tests/google" +msgstr "soc;drivers;tests/google" + +msgid "" +"Selected I2C V2, UART DMA and legacy ADC/PWM/Flash live/retained guards," +" failed-drain responsibility and stop/retry contracts passed focused " +"regressions. UART guards precede config/pin mutation and include " +"outstanding wake/RX loans; failed withdrawal retains config and borrowed" +" storage. Counts across focused suites overlap; final-source and " +"physical qualification are separate." +msgstr "" +"Selected I2C V2, UART DMA and legacy ADC/PWM/Flash live/retained guards," +" failed-drain responsibility and stop/retry contracts passed focused " +"regressions. UART guards precede config/pin mutation and include " +"outstanding wake/RX loans; failed withdrawal retains config and borrowed" +" storage. Counts across focused suites overlap; final-source and " +"physical qualification are separate." + +msgid "R36" +msgstr "R36" + +msgid "Split legacy ADC PWM and Flash integration" +msgstr "Split legacy ADC PWM and Flash integration" + +msgid "" +"Separate exact SoC integration from reusable algorithms and narrow " +"contracts" +msgstr "" +"Separate exact SoC integration from reusable algorithms and narrow " +"contracts" + +msgid "" +"soc/stm32/stm32f103/drivers/devices.c;soc/gd32/gd32f303/drivers/devices." +"c;drivers/adc/legacy_adc;drivers/timer/legacy_pwm;drivers/flash/legacy_f" +"lash" +msgstr "" +"soc/stm32/stm32f103/drivers/devices.c;soc/gd32/gd32f303/drivers/devices." +"c;drivers/adc/legacy_adc;drivers/timer/legacy_pwm;drivers/flash/legacy_f" +"lash" + +msgid "final_P_native_arm_passed" +msgstr "final_P_native_arm_passed" + +msgid "" +"build/quality/soc-ip-config-r36/legacy-high-green;build/quality/soc-ip-c" +"onfig-r36/legacy-python-green.log;build/quality/soc-ip-config-r36/legacy" +"-final-green;build/quality/soc-ip-candidate/legacy-devices/report.json;b" +"uild/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quali" +"ty/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip" +"-candidate/completion-summary.json;build/quality/soc-ip-candidate/source" +"-snapshot.json;/workspace/nexus-examples/build/platform-software-recover" +"y-92e669233957/providers/mainstream_assembly/report.json;/workspace/nexu" +"s-examples/build/platform-candidate-92e669233957/completion-2/host_contr" +"acts.json" +msgstr "" +"build/quality/soc-ip-config-r36/legacy-high-green;build/quality/soc-ip-c" +"onfig-r36/legacy-python-green.log;build/quality/soc-ip-config-r36/legacy" +"-final-green;build/quality/soc-ip-candidate/legacy-devices/report.json;b" +"uild/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quali" +"ty/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip" +"-candidate/completion-summary.json;build/quality/soc-ip-candidate/source" +"-snapshot.json;/workspace/nexus-examples/build/platform-software-recover" +"y-92e669233957/providers/mainstream_assembly/report.json;/workspace/nexu" +"s-examples/build/platform-candidate-92e669233957/completion-2/host_contr" +"acts.json" + +msgid "" +"F103/GD303 concrete C ADC/PWM/Flash prepare entries and existing shared " +"close paths implemented. Final two factory targets: 11 Google cases, " +"including full independent 128-sector geometry, live rebind, failed " +"stop/retry and failed initialization cleanup/restart; GD303 foreign " +"oversampling stays unchanged on BUSY. Earlier 6-target/97-case and " +"40-Python results overlap and are not additive. Four exact legacy ARM " +"builds and two independent Flash-only retention consumers also passed on" +" the 184-file dirty development overlay. This is development evidence, " +"not final clean-source qualification; other peripheral emission is " +"unchanged. Final clean P separately passed all four Native modes and the" +" four f103/gd303 devices baremetal/FreeRTOS fixtures at " +"mainstream_assembly indices 0/1/8/9. The two extra Flash-only consumers " +"remain development observations; they were not rerun or relabeled as P." +msgstr "" +"F103/GD303 concrete C ADC/PWM/Flash prepare entries and existing shared " +"close paths implemented. Final two factory targets: 11 Google cases, " +"including full independent 128-sector geometry, live rebind, failed " +"stop/retry and failed initialization cleanup/restart; GD303 foreign " +"oversampling stays unchanged on BUSY. Earlier 6-target/97-case and " +"40-Python results overlap and are not additive. Four exact legacy ARM " +"builds and two independent Flash-only retention consumers also passed on" +" the 184-file dirty development overlay. This is development evidence, " +"not final clean-source qualification; other peripheral emission is " +"unchanged. Final clean P separately passed all four Native modes and the" +" four f103/gd303 devices baremetal/FreeRTOS fixtures at " +"mainstream_assembly indices 0/1/8/9. The two extra Flash-only consumers " +"remain development observations; they were not rerun or relabeled as P." + +msgid "R37" +msgstr "R37" + +msgid "build_integration_and_docs" +msgstr "build_integration_and_docs" + +msgid "Review CMake private includes and Runtime boundary" +msgstr "Review CMake private includes and Runtime boundary" + +msgid "" +"Selected targets retain source ownership and vendor includes stay " +"private" +msgstr "" +"Selected targets retain source ownership and vendor includes stay " +"private" + +msgid "soc;drivers;cmake" +msgstr "soc;drivers;cmake" + +msgid "" +"build/soc-ip-layout/integration-audit/runtime-green.log;build/soc-ip-lay" +"out/integration-audit/cmake/observations.json" +msgstr "" +"build/soc-ip-layout/integration-audit/runtime-green.log;build/soc-ip-lay" +"out/integration-audit/cmake/observations.json" + +msgid "" +"Three real RED Runtime inclusion regressions then 18 tests GREEN. Eight " +"family consumer include closures show no private leakage; two Cortex-M4 " +"baremetal/FreeRTOS Ninja/ARM configurations exclude SoC/driver/Board " +"objects. This audit configured targets; it did not build production " +"firmware." +msgstr "" +"Three real RED Runtime inclusion regressions then 18 tests GREEN. Eight " +"family consumer include closures show no private leakage; two Cortex-M4 " +"baremetal/FreeRTOS Ninja/ARM configurations exclude SoC/driver/Board " +"objects. This audit configured targets; it did not build production " +"firmware." + +msgid "R38" +msgstr "R38" + +msgid "Converge SDK transport manifest" +msgstr "Converge SDK transport manifest" + +msgid "Use required payload closure without redundant source authorities" +msgstr "Use required payload closure without redundant source authorities" + +msgid "" +"build/soc-ip-layout/integration-audit/sdk-closure-red.log;build/soc-ip-l" +"ayout/integration-audit/sdk-closure-green.log;build/soc-ip-layout/integr" +"ation-audit/required-comparison.json" +msgstr "" +"build/soc-ip-layout/integration-audit/sdk-closure-red.log;build/soc-ip-l" +"ayout/integration-audit/sdk-closure-green.log;build/soc-ip-layout/integr" +"ation-audit/required-comparison.json" + +msgid "" +"48 selection/admission/descriptor tests passed. SDK requirements reuse " +"public source_names and descriptor facts/payload; no prior required " +"member removed. Additional declared family helpers are included " +"dynamically. Actual final P relocated SDK execution is recorded in R45." +msgstr "" +"48 selection/admission/descriptor tests passed. SDK requirements reuse " +"public source_names and descriptor facts/payload; no prior required " +"member removed. Additional declared family helpers are included " +"dynamically. Actual final P relocated SDK execution is recorded in R45." + +msgid "R39" +msgstr "R39" + +msgid "Incremental configuration transaction regressions" +msgstr "Incremental configuration transaction regressions" + +msgid "" +"Exercise changed-module invalidation and failed reconfiguration through " +"existing gates" +msgstr "" +"Exercise changed-module invalidation and failed reconfiguration through " +"existing gates" + +msgid "tools/configure/tests;tools/testing" +msgstr "tools/configure/tests;tools/testing" + +msgid "" +"build/soc-ip-layout/facts-ir/incremental-green.log;build/soc-ip-layout/f" +"acts-ir/incremental-receipt.json;build/soc-ip-layout/facts-ir/integratio" +"n-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/incremental-green.log;build/soc-ip-layout/f" +"acts-ir/incremental-receipt.json;build/soc-ip-layout/facts-ir/integratio" +"n-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log" + +msgid "" +"Fresh Native consumer CMake/Ninja configure/build and no-change rebuild " +"executed; changing a declared family module triggered CMake and selected" +" regeneration. All receipt commands exit zero. Separate 143-test " +"integration/bundle suite covers transaction boundaries; not a fresh " +"final-source SDK or full firmware matrix qualification." +msgstr "" +"Fresh Native consumer CMake/Ninja configure/build and no-change rebuild " +"executed; changing a declared family module triggered CMake and selected" +" regeneration. All receipt commands exit zero. Separate 143-test " +"integration/bundle suite covers transaction boundaries; not a fresh " +"final-source SDK or full firmware matrix qualification." + +msgid "R40" +msgstr "R40" + +msgid "Existing CLI diagnostics" +msgstr "Existing CLI diagnostics" + +msgid "" +"Existing commands report exact provider inputs and failures without a " +"new framework" +msgstr "" +"Existing commands report exact provider inputs and failures without a " +"new framework" + +msgid "tools/configure/configure.py;tools/configure/experience.py" +msgstr "tools/configure/configure.py;tools/configure/experience.py" + +msgid "" +"build/soc-ip-layout/integration-audit/diagnostic-red.log;build/soc-ip-la" +"yout/integration-audit/diagnostic-green.log;build/soc-ip-layout/integrat" +"ion-audit/cli/summary.json" +msgstr "" +"build/soc-ip-layout/integration-audit/diagnostic-red.log;build/soc-ip-la" +"yout/integration-audit/diagnostic-green.log;build/soc-ip-layout/integrat" +"ion-audit/cli/summary.json" + +msgid "" +"35 focused tests passed; eight exact provider-check runs emitted static " +"bundles and identity mismatch rejected. provider_module_missing applies " +"only to the selected entry/parent package; other import errors remain " +"explicit. No compile/link or physical qualification." +msgstr "" +"35 focused tests passed; eight exact provider-check runs emitted static " +"bundles and identity mismatch rejected. provider_module_missing applies " +"only to the selected entry/parent package; other import errors remain " +"explicit. No compile/link or physical qualification." + +msgid "R41" +msgstr "R41" + +msgid "Bilingual documentation diagrams and porting migration" +msgstr "Bilingual documentation diagrams and porting migration" + +msgid "" +"Current source maps; diagram links; READMEs and reviewed gettext match " +"implementation" +msgstr "" +"Current source maps; diagram links; READMEs and reviewed gettext match " +"implementation" + +msgid "" +"README.md;README_CN.md;drivers/README.md;soc/README.md;soc/common/README" +".md;tools/configure/README.md;docs/explanation;docs/guides/provider-port" +"ing.md;docs/design;docs/delivery/architecture.md;docs/assets/diagrams/RE" +"ADME.md;docs/locale" +msgstr "" +"README.md;README_CN.md;drivers/README.md;soc/README.md;soc/common/README" +".md;tools/configure/README.md;docs/explanation;docs/guides/provider-port" +"ing.md;docs/design;docs/delivery/architecture.md;docs/assets/diagrams/RE" +"ADME.md;docs/locale" + +msgid "clean_P_docs_and_D_review_passed" +msgstr "clean_P_docs_and_D_review_passed" + +msgid "" +"build/soc-ip-layout/documentation/phase1-clean-build.json;build/soc-ip-l" +"ayout-docs-review.json;build/soc-ip-layout/documentation/phase2-source-s" +"can-final.json;build/soc-ip-layout/documentation/phase2-diagram-check.js" +"on;build/soc-ip-layout/documentation/phase2-diagram-review.json;build/so" +"c-ip-layout/documentation/phase2-po-independent-review.json;build/soc-ip" +"-layout/documentation/phase2-review-admission.json;build/soc-ip-layout/d" +"ocumentation/phase2-translations-final.json;build/soc-ip-layout-docs-92e" +"669233957/documentation-build.json;build/soc-ip-layout/documentation/fin" +"al-d-extraction/extraction.json;build/soc-ip-layout/documentation/final-" +"d-source-check.json;build/soc-ip-layout/documentation/final-d-po-indepen" +"dent-review.json;build/soc-ip-layout/documentation/final-d-review-admiss" +"ion.json;build/soc-ip-layout/documentation/final-d-translations.json" +msgstr "" +"build/soc-ip-layout/documentation/phase1-clean-build.json;build/soc-ip-l" +"ayout-docs-review.json;build/soc-ip-layout/documentation/phase2-source-s" +"can-final.json;build/soc-ip-layout/documentation/phase2-diagram-check.js" +"on;build/soc-ip-layout/documentation/phase2-diagram-review.json;build/so" +"c-ip-layout/documentation/phase2-po-independent-review.json;build/soc-ip" +"-layout/documentation/phase2-review-admission.json;build/soc-ip-layout/d" +"ocumentation/phase2-translations-final.json;build/soc-ip-layout-docs-92e" +"669233957/documentation-build.json;build/soc-ip-layout/documentation/fin" +"al-d-extraction/extraction.json;build/soc-ip-layout/documentation/final-" +"d-source-check.json;build/soc-ip-layout/documentation/final-d-po-indepen" +"dent-review.json;build/soc-ip-layout/documentation/final-d-review-admiss" +"ion.json;build/soc-ip-layout/documentation/final-d-translations.json" + +msgid "" +"Phase-1 clean 7b985 strict bilingual build and independent 18-catalog " +"review retained. Phase-2 current content complete: FIG-31 independent " +"source/visual review, locked diagram verification, 131-page source scan," +" actual gettext extraction and whole-site 262 locale checks passed. Nine" +" catalogs independently reviewed: 101 new messages and 945 unchanged " +"reviewed entries. Clean P strict bilingual build passed with 262 locale " +"checks; its receipt has qualification=false and SHA256 " +"acdca47f4ccf9f1438a8c50f25dd8bea4135821922da202181a5237738ff8166. This " +"later documentation-only update records P; its actual gettext/review and" +" clean-D build are separate documentation evidence, not firmware or " +"physical qualification. D content validation actually passed: fresh " +"locked gettext, 131-page source scan, independent semantic review of 22 " +"new messages with 127 baseline pairs unchanged, and 262 whole-site " +"locale checks. Clean-D strict build is a separate postcommit receipt; no" +" future success or D self-hash is recorded here." +msgstr "" +"Phase-1 clean 7b985 strict bilingual build and independent 18-catalog " +"review retained. Phase-2 current content complete: FIG-31 independent " +"source/visual review, locked diagram verification, 131-page source scan," +" actual gettext extraction and whole-site 262 locale checks passed. Nine" +" catalogs independently reviewed: 101 new messages and 945 unchanged " +"reviewed entries. Clean P strict bilingual build passed with 262 locale " +"checks; its receipt has qualification=false and SHA256 " +"acdca47f4ccf9f1438a8c50f25dd8bea4135821922da202181a5237738ff8166. This " +"later documentation-only update records P; its actual gettext/review and" +" clean-D build are separate documentation evidence, not firmware or " +"physical qualification. D content validation actually passed: fresh " +"locked gettext, 131-page source scan, independent semantic review of 22 " +"new messages with 127 baseline pairs unchanged, and 262 whole-site " +"locale checks. Clean-D strict build is a separate postcommit receipt; no" +" future success or D self-hash is recorded here." + +msgid "R42" +msgstr "R42" + +msgid "Examples candidate consumption" +msgstr "Examples candidate consumption" + +msgid "Consume the concrete candidate in the external examples repository" +msgstr "Consume the concrete candidate in the external examples repository" + +msgid "X-Gen-Lab/nexus-examples" +msgstr "X-Gen-Lab/nexus-examples" + +msgid "executed_passed" +msgstr "executed_passed" + +msgid "" +"/workspace/nexus-examples/build/local-ci-soc-ip-layout-92e669233957/exec" +"ution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e6" +"69233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples" +"/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json;" +".git/local-recovery/soc-ip-layout/examples-main-merge.json" +msgstr "" +"/workspace/nexus-examples/build/local-ci-soc-ip-layout-92e669233957/exec" +"ution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e6" +"69233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples" +"/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json;" +".git/local-recovery/soc-ip-layout/examples-main-merge.json" + +msgid "" +"Exact P consumed by clean examples " +"0f15022e4d5f5c51991b134d0b9d6082dcf88de9, tree " +"bb60f413b911ec212b59b77cea1734971eddbd9f. Complete canonical local CI " +"passed: Native, installed SDK consumer, 16 ARM presets and 32 " +"main-matrix ELFs for platform_reference/uart_owner_runtime; dedicated " +"example targets remain separately recorded. Formal lock and Gitlink " +"match P; candidate_validation=false. Ordinary hook retained 80 tool " +"tests and 15 CTest / 69 Google cases with no skips. PR15 merged as " +"bd3b90f135600b6405459347cb9e2cff5692d2e3 with the same examples tree. " +"This is exact code-candidate consumption, not physical execution." +msgstr "" +"Exact P consumed by clean examples " +"0f15022e4d5f5c51991b134d0b9d6082dcf88de9, tree " +"bb60f413b911ec212b59b77cea1734971eddbd9f. Complete canonical local CI " +"passed: Native, installed SDK consumer, 16 ARM presets and 32 " +"main-matrix ELFs for platform_reference/uart_owner_runtime; dedicated " +"example targets remain separately recorded. Formal lock and Gitlink " +"match P; candidate_validation=false. Ordinary hook retained 80 tool " +"tests and 15 CTest / 69 Google cases with no skips. PR15 merged as " +"bd3b90f135600b6405459347cb9e2cff5692d2e3 with the same examples tree. " +"This is exact code-candidate consumption, not physical execution." + +msgid "R43" +msgstr "R43" + +msgid "verification_and_delivery" +msgstr "verification_and_delivery" + +msgid "Four Native modes and complete tool suites" +msgstr "Four Native modes and complete tool suites" + +msgid "" +"Execute maintained modes and complete applicable tool domains with fresh" +" nonempty reports" +msgstr "" +"Execute maintained modes and complete applicable tool domains with fresh" +" nonempty reports" + +msgid "tests/google;tools/dev;tools/configure/tests;tools/testing" +msgstr "tests/google;tools/dev;tools/configure/tests;tools/testing" + +msgid "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/tool-tests.json;/workspace/nexus-examples/build/platform-sof" +"tware-cleanenv-92e669233957/configure-tests.json;/workspace/nexus-exampl" +"es/build/platform-software-cleanenv-92e669233957/ci-tests.json;/workspac" +"e/nexus-examples/build/platform-software-cleanenv-92e669233957/workflow-" +"tests.json;/workspace/nexus-examples/build/platform-candidate-92e6692339" +"57/completion-2/host_contracts.json;build/soc-ip-layout/phase2-hook-evid" +"ence/manifest.json" +msgstr "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/tool-tests.json;/workspace/nexus-examples/build/platform-sof" +"tware-cleanenv-92e669233957/configure-tests.json;/workspace/nexus-exampl" +"es/build/platform-software-cleanenv-92e669233957/ci-tests.json;/workspac" +"e/nexus-examples/build/platform-software-cleanenv-92e669233957/workflow-" +"tests.json;/workspace/nexus-examples/build/platform-candidate-92e6692339" +"57/completion-2/host_contracts.json;build/soc-ip-layout/phase2-hook-evid" +"ence/manifest.json" + +msgid "" +"Tested clean P=92e669233957a2e450a528394e8f4dd0794c7049. All four Native" +" modes debug/release/ASan-UBSan/TSan actually passed: each 372 CTest and" +" 6396 Google cases, zero failures/errors/skips. Complete applicable tool" +" domains passed: testing 1527, configure 1071, ci 490, workflows 159 " +"(3247 total); other tool domains are not inferred from these four. " +"Original executions are retained in candidate evidence. A remains " +"interrupted/running after its 90 completed stages; this row closes the " +"actual completed Native/tool scopes, not a single full-plan pass. The " +"separate ordinary complete-index hook archive records 3247 tools and 352" +" Google programs / 6396 cases." +msgstr "" +"Tested clean P=92e669233957a2e450a528394e8f4dd0794c7049. All four Native" +" modes debug/release/ASan-UBSan/TSan actually passed: each 372 CTest and" +" 6396 Google cases, zero failures/errors/skips. Complete applicable tool" +" domains passed: testing 1527, configure 1071, ci 490, workflows 159 " +"(3247 total); other tool domains are not inferred from these four. " +"Original executions are retained in candidate evidence. A remains " +"interrupted/running after its 90 completed stages; this row closes the " +"actual completed Native/tool scopes, not a single full-plan pass. The " +"separate ordinary complete-index hook archive records 3247 tools and 352" +" Google programs / 6396 cases." + +msgid "R44" +msgstr "R44" + +msgid "ARM and Runtime matrix" +msgstr "ARM and Runtime matrix" + +msgid "Compile and link exact maintained ARM and CPU Runtime configurations" +msgstr "Compile and link exact maintained ARM and CPU Runtime configurations" + +msgid "tests/contracts;tools/measurement" +msgstr "tests/contracts;tools/measurement" + +msgid "segmented_execution_passed" +msgstr "segmented_execution_passed" + +msgid "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/interruption-observation.json;/workspace/nexus-examples/buil" +"d/platform-software-cleanenv-92e669233957/cortex_runtime/report.json;/wo" +"rkspace/nexus-examples/build/platform-software-recovery-92e669233957/rep" +"ort.json;/workspace/nexus-examples/build/platform-candidate-92e669233957" +"/completion-2/host_contracts.json;/workspace/nexus-examples/build/platfo" +"rm-candidate-92e669233957/completion-2/candidate-portable.json" +msgstr "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/interruption-observation.json;/workspace/nexus-examples/buil" +"d/platform-software-cleanenv-92e669233957/cortex_runtime/report.json;/wo" +"rkspace/nexus-examples/build/platform-software-recovery-92e669233957/rep" +"ort.json;/workspace/nexus-examples/build/platform-candidate-92e669233957" +"/completion-2/host_contracts.json;/workspace/nexus-examples/build/platfo" +"rm-candidate-92e669233957/completion-2/candidate-portable.json" + +msgid "" +"Tested P=92e669233957a2e450a528394e8f4dd0794c7049: original maintained " +"plan has actual exact segmented coverage A90+B69. A retains running and " +"no source_after after environment interruption; B executed_passed with " +"the exact original suffix, zero stage exits and unchanged clean source. " +"B report SHA256 " +"f8ecf185eadbef7e33d7a667366da7de34241ce243a5d417b5b453ae3bb99694. All " +"original names/order/checkers/args/timeouts and raw identities were " +"checked; no merged executed_passed report was invented. B reran all 865 " +"tidy and 865 cppcheck contexts. CPU Runtime passed 104 variants with " +"source_inputs_stable=true; its full_platform_status stays not_qualified " +"and expected rejections are separate from positive images. " +"ARM/provider/retention/resource fixtures retain their exact original " +"reports; linked software does not establish MCU execution." +msgstr "" +"Tested P=92e669233957a2e450a528394e8f4dd0794c7049: original maintained " +"plan has actual exact segmented coverage A90+B69. A retains running and " +"no source_after after environment interruption; B executed_passed with " +"the exact original suffix, zero stage exits and unchanged clean source. " +"B report SHA256 " +"f8ecf185eadbef7e33d7a667366da7de34241ce243a5d417b5b453ae3bb99694. All " +"original names/order/checkers/args/timeouts and raw identities were " +"checked; no merged executed_passed report was invented. B reran all 865 " +"tidy and 865 cppcheck contexts. CPU Runtime passed 104 variants with " +"source_inputs_stable=true; its full_platform_status stays not_qualified " +"and expected rejections are separate from positive images. " +"ARM/provider/retention/resource fixtures retain their exact original " +"reports; linked software does not establish MCU execution." + +msgid "R45" +msgstr "R45" + +msgid "Relocated SDK integrity" +msgstr "Relocated SDK integrity" + +msgid "Verify required installed members and independent relocated consumers" +msgstr "Verify required installed members and independent relocated consumers" + +msgid "" +"build/soc-ip-layout/sdk-relocation-92e669233957/execution.json;/workspac" +"e/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-example" +"s-validation-evidence.json;/workspace/nexus-examples/build/platform-cand" +"idate-92e669233957/completion-2/source_sdk.json;/workspace/nexus-example" +"s/build/platform-candidate-92e669233957/completion-2/verify_relocated_sd" +"k.command.json;/workspace/nexus-examples/build/platform-candidate-92e669" +"233957/completion-2/candidate-portable.json" +msgstr "" +"build/soc-ip-layout/sdk-relocation-92e669233957/execution.json;/workspac" +"e/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-example" +"s-validation-evidence.json;/workspace/nexus-examples/build/platform-cand" +"idate-92e669233957/completion-2/source_sdk.json;/workspace/nexus-example" +"s/build/platform-candidate-92e669233957/completion-2/verify_relocated_sd" +"k.command.json;/workspace/nexus-examples/build/platform-candidate-92e669" +"233957/completion-2/candidate-portable.json" + +msgid "" +"Publishable source SDK binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049 and its exact tree. Actual " +"package verification and independent relocated consumers passed at " +"platform-sdk-92e669233957/SDK relocated, including Native C/C++, Runtime" +" and maintained firmware selections; source closure and exported inputs " +"retain original member hashes. The independent SDK/examples archive " +"preserves those executions. Candidate completion-2 reverified the " +"relocated SDK with exit 0. Initial completion used an incorrect verifier" +" root and failed; its original failed receipt remains, followed by the " +"actual corrected execution. No source change or guard weakening was " +"used." +msgstr "" +"Publishable source SDK binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049 and its exact tree. Actual " +"package verification and independent relocated consumers passed at " +"platform-sdk-92e669233957/SDK relocated, including Native C/C++, Runtime" +" and maintained firmware selections; source closure and exported inputs " +"retain original member hashes. The independent SDK/examples archive " +"preserves those executions. Candidate completion-2 reverified the " +"relocated SDK with exit 0. Initial completion used an incorrect verifier" +" root and failed; its original failed receipt remains, followed by the " +"actual corrected execution. No source change or guard weakening was " +"used." + +msgid "R46" +msgstr "R46" + +msgid "Resource retention and cost comparison" +msgstr "Resource retention and cost comparison" + +msgid "Compare actual Flash RAM ops and IRQ retention for the same selections" +msgstr "Compare actual Flash RAM ops and IRQ retention for the same selections" + +msgid "final_P_comparison_passed" +msgstr "final_P_comparison_passed" + +msgid "" +"build/quality/soc-ip-candidate/cost-summary.json;build/quality/soc-ip-ca" +"ndidate/uart/report.json;build/quality/soc-ip-candidate/legacy-devices/r" +"eport.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json" +";build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/qu" +"ality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-cand" +"idate/source-snapshot.json;build/quality/soc-ip-final-p-92e669233957/fin" +"al-cost-summary.json;build/quality/soc-ip-final-p-92e669233957/final-cos" +"t-summary.csv;build/quality/soc-ip-cost-package-92e669233957/package.jso" +"n;build/quality/soc-ip-cost-package-92e669233957/artifact-summary.json" +msgstr "" +"build/quality/soc-ip-candidate/cost-summary.json;build/quality/soc-ip-ca" +"ndidate/uart/report.json;build/quality/soc-ip-candidate/legacy-devices/r" +"eport.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json" +";build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/qu" +"ality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-cand" +"idate/source-snapshot.json;build/quality/soc-ip-final-p-92e669233957/fin" +"al-cost-summary.json;build/quality/soc-ip-final-p-92e669233957/final-cos" +"t-summary.csv;build/quality/soc-ip-cost-package-92e669233957/package.jso" +"n;build/quality/soc-ip-cost-package-92e669233957/artifact-summary.json" + +msgid "" +"Historical 184-file dirty development overlay comparisons remain " +"separately identified in their original reports. Final clean P versus " +"7b985: all 11 strict comparisons passed from actual A/B build artifacts " +"with independent ELF/map/IRQ review; no rebuild for path normalization. " +"I2C total loaded Flash delta G474/H743/U575 = -4/+40/+64 B, composed of " +"code/alignment +108/+152/+224 B, readonly config +24 B each, " +"path-pool/alignment -136/-136/-184 B; reserved RAM -24/-16/-24 B and " +"state 28 to 8 B. Eight UART totals range +168 to +392 B Flash, with " +"code/alignment +336 to +384 B, readonly config 60 B and negative " +"source-path/alignment contributions; all reserve 48 B less RAM, TX state" +" 96 to 48 B and duplex 144 to 96 B. Source-root string reductions are " +"not architecture savings. UART selected IRQ code at most +2 B/one static" +" instruction, up to two config loads; duplex channel local push frame " +"grows 16 to 24 B while MSP reservation is unchanged. No new selected IRQ" +" indirect call; cycles/WCET/dynamic stack high-water are not measured. " +"Portable cost evidence archive SHA256 " +"fb0f63bcae83f7a5b278a311f67107bf4f8c99fcee0014638b0fb26c0936567f, " +"4035953 B / 1184 files; original source/tool/SDK inputs remain explicit " +"external dependencies." +msgstr "" +"Historical 184-file dirty development overlay comparisons remain " +"separately identified in their original reports. Final clean P versus " +"7b985: all 11 strict comparisons passed from actual A/B build artifacts " +"with independent ELF/map/IRQ review; no rebuild for path normalization. " +"I2C total loaded Flash delta G474/H743/U575 = -4/+40/+64 B, composed of " +"code/alignment +108/+152/+224 B, readonly config +24 B each, " +"path-pool/alignment -136/-136/-184 B; reserved RAM -24/-16/-24 B and " +"state 28 to 8 B. Eight UART totals range +168 to +392 B Flash, with " +"code/alignment +336 to +384 B, readonly config 60 B and negative " +"source-path/alignment contributions; all reserve 48 B less RAM, TX state" +" 96 to 48 B and duplex 144 to 96 B. Source-root string reductions are " +"not architecture savings. UART selected IRQ code at most +2 B/one static" +" instruction, up to two config loads; duplex channel local push frame " +"grows 16 to 24 B while MSP reservation is unchanged. No new selected IRQ" +" indirect call; cycles/WCET/dynamic stack high-water are not measured. " +"Portable cost evidence archive SHA256 " +"fb0f63bcae83f7a5b278a311f67107bf4f8c99fcee0014638b0fb26c0936567f, " +"4035953 B / 1184 files; original source/tool/SDK inputs remain explicit " +"external dependencies." + +msgid "R47" +msgstr "R47" + +msgid "Independent review main integration and formal examples pin" +msgstr "Independent review main integration and formal examples pin" + +msgid "" +"Complete review and ordinary gates before approved integration and exact" +" examples pin" +msgstr "" +"Complete review and ordinary gates before approved integration and exact" +" examples pin" + +msgid "AGENTS.md;X-Gen-Lab/nexus-examples" +msgstr "AGENTS.md;X-Gen-Lab/nexus-examples" + +msgid "integrated_reviewed" +msgstr "integrated_reviewed" + +msgid "" +"build/soc-ip-layout/r47-independent-review.json;build/soc-ip-layout/phas" +"e2-hook-evidence/manifest.json;build/soc-ip-layout/codeql-pr36-review/re" +"view.json;build/soc-ip-layout/codeql-pr36-review/comments.json;build/soc" +"-ip-layout/codeql-pr36-review/remote-status.json;.git/local-recovery/soc" +"-ip-layout/platform-main-merge.json;.git/local-recovery/soc-ip-layout/ex" +"amples-main-merge.json;.git/local-recovery/soc-ip-layout/platform-merge-" +"import-apply.json;.git/local-recovery/soc-ip-layout/examples-merge-impor" +"t-apply.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e" +"669233957/ordinary-hook-evidence.json" +msgstr "" +"build/soc-ip-layout/r47-independent-review.json;build/soc-ip-layout/phas" +"e2-hook-evidence/manifest.json;build/soc-ip-layout/codeql-pr36-review/re" +"view.json;build/soc-ip-layout/codeql-pr36-review/comments.json;build/soc" +"-ip-layout/codeql-pr36-review/remote-status.json;.git/local-recovery/soc" +"-ip-layout/platform-main-merge.json;.git/local-recovery/soc-ip-layout/ex" +"amples-main-merge.json;.git/local-recovery/soc-ip-layout/platform-merge-" +"import-apply.json;.git/local-recovery/soc-ip-layout/examples-merge-impor" +"t-apply.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e" +"669233957/ordinary-hook-evidence.json" + +msgid "" +"Independent production review completed without blocking findings; " +"normal complete-index hooks passed on the tree integrated as P. PR36 " +"actually merged to M=c79fd9672a9fe56379744d834cfccd822bc40be1, tree " +"exactly equal to P; PR15 merged to " +"bd3b90f135600b6405459347cb9e2cff5692d2e3, tree equal to tested examples " +"bb60f413b911ec212b59b77cea1734971eddbd9f, with formal lock/Gitlink P. " +"Actual signed GitHub commit objects and local lease/import receipts " +"retained. All 14 CodeQL review comments were assessed: no confirmed " +"production dangling/late binding or exercised test UAF, with nonblocking" +" maintenance suggestions retained; they are not all labeled false " +"positives. Remote Security Analysis 40388967544 CPP/Python jobs passed, " +"not a local CodeQL run or zero-alert/zero-defect claim. This " +"documentation-only follow-up does not retest M as a new software source." +msgstr "" +"Independent production review completed without blocking findings; " +"normal complete-index hooks passed on the tree integrated as P. PR36 " +"actually merged to M=c79fd9672a9fe56379744d834cfccd822bc40be1, tree " +"exactly equal to P; PR15 merged to " +"bd3b90f135600b6405459347cb9e2cff5692d2e3, tree equal to tested examples " +"bb60f413b911ec212b59b77cea1734971eddbd9f, with formal lock/Gitlink P. " +"Actual signed GitHub commit objects and local lease/import receipts " +"retained. All 14 CodeQL review comments were assessed: no confirmed " +"production dangling/late binding or exercised test UAF, with nonblocking" +" maintenance suggestions retained; they are not all labeled false " +"positives. Remote Security Analysis 40388967544 CPP/Python jobs passed, " +"not a local CodeQL run or zero-alert/zero-defect claim. This " +"documentation-only follow-up does not retest M as a new software source." + +msgid "R48" +msgstr "R48" + +msgid "Clean software delivery and two fresh offline reproductions" +msgstr "Clean software delivery and two fresh offline reproductions" + +msgid "" +"Bind clean source and actual reproducible artifacts; physical HIL stays " +"not_executed" +msgstr "" +"Bind clean source and actual reproducible artifacts; physical HIL stays " +"not_executed" + +msgid "tools/evidence;docs/design/soc-ip-layout-execution.csv" +msgstr "tools/evidence;docs/design/soc-ip-layout-execution.csv" + +msgid "software_qualified" +msgstr "software_qualified" + +msgid "" +"/workspace/nexus-examples/build/platform-candidate-92e669233957/reproduc" +"tion-quiet1.json;/workspace/nexus-examples/build/platform-candidate-92e6" +"69233957/completion-2/candidate/candidate.json;/workspace/nexus-examples" +"/build/platform-candidate-92e669233957/completion-2/candidate_verify.com" +"mand.json;/workspace/nexus-examples/build/platform-candidate-92e66923395" +"7/finish-candidate-r48-2.command.json;/workspace/nexus-examples/build/pl" +"atform-candidate-92e669233957/completion-2/candidate-portable.json;/work" +"space/nexus-examples/build/platform-candidate-92e669233957/completion-2/" +"execution-receipts.json;build/soc-ip-layout/qualification-timeout-92e669" +"233957/manifest.json;/workspace/nexus-examples/build/platform-software-c" +"leanenv-92e669233957/interruption-observation.json;/workspace/nexus-exam" +"ples/build/platform-software-recovery-92e669233957/report.json;/workspac" +"e/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-" +"r48.command.json;/workspace/nexus-examples/build/platform-software-final" +"-92e669233957/report.json;build/soc-ip-layout/software-92e669233957/repo" +"rt.json" +msgstr "" +"/workspace/nexus-examples/build/platform-candidate-92e669233957/reproduc" +"tion-quiet1.json;/workspace/nexus-examples/build/platform-candidate-92e6" +"69233957/completion-2/candidate/candidate.json;/workspace/nexus-examples" +"/build/platform-candidate-92e669233957/completion-2/candidate_verify.com" +"mand.json;/workspace/nexus-examples/build/platform-candidate-92e66923395" +"7/finish-candidate-r48-2.command.json;/workspace/nexus-examples/build/pl" +"atform-candidate-92e669233957/completion-2/candidate-portable.json;/work" +"space/nexus-examples/build/platform-candidate-92e669233957/completion-2/" +"execution-receipts.json;build/soc-ip-layout/qualification-timeout-92e669" +"233957/manifest.json;/workspace/nexus-examples/build/platform-software-c" +"leanenv-92e669233957/interruption-observation.json;/workspace/nexus-exam" +"ples/build/platform-software-recovery-92e669233957/report.json;/workspac" +"e/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-" +"r48.command.json;/workspace/nexus-examples/build/platform-software-final" +"-92e669233957/report.json;build/soc-ip-layout/software-92e669233957/repo" +"rt.json" + +msgid "" +"Sealed software candidate binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049, exact configuration and " +"actual same ELF/BIN/map inputs. Existing six scopes passed: " +"host_contracts, arm_link, resource_budget, source_sdk, reproducibility, " +"hil_tooling; owned candidate verifier and parent completion-2 process " +"exit 0. Two fresh distinct network-none builds passed declared " +"container-boundary verification and bit_exact_elf_bin with no differing " +"artifacts; map bit equality is not claimed. Candidate SHA256 " +"35c8301ac95cdc7eee6b125e5ed8170ef44800bb6eb4995361d02c364660c060. " +"Portable archive SHA256 " +"7f89e8ec33a543e150e7165a3e4252aa11a96d62fe5d3187a9b0d4791aa618b4, " +"433850915 B / 6401 files. Signature unsigned; product promotion " +"not_performed; HIL not_executed. Maintained-plan evidence is exact " +"A90+B69 segmented coverage, not a single successful 159-stage run. " +"Original output-boundary, timeout, GIT_PAGER-admission, interruption and" +" initial SDK-root failures retain original outcomes; none was rewritten " +"as passed. D only records P and obtains its own separate documentation " +"receipt." +msgstr "" +"Sealed software candidate binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049, exact configuration and " +"actual same ELF/BIN/map inputs. Existing six scopes passed: " +"host_contracts, arm_link, resource_budget, source_sdk, reproducibility, " +"hil_tooling; owned candidate verifier and parent completion-2 process " +"exit 0. Two fresh distinct network-none builds passed declared " +"container-boundary verification and bit_exact_elf_bin with no differing " +"artifacts; map bit equality is not claimed. Candidate SHA256 " +"35c8301ac95cdc7eee6b125e5ed8170ef44800bb6eb4995361d02c364660c060. " +"Portable archive SHA256 " +"7f89e8ec33a543e150e7165a3e4252aa11a96d62fe5d3187a9b0d4791aa618b4, " +"433850915 B / 6401 files. Signature unsigned; product promotion " +"not_performed; HIL not_executed. Maintained-plan evidence is exact " +"A90+B69 segmented coverage, not a single successful 159-stage run. " +"Original output-boundary, timeout, GIT_PAGER-admission, interruption and" +" initial SDK-root failures retain original outcomes; none was rewritten " +"as passed. D only records P and obtains its own separate documentation " +"receipt." diff --git a/docs/locale/en/LC_MESSAGES/status/software-verification.po b/docs/locale/en/LC_MESSAGES/status/software-verification.po index e48addfd2..53b8e87ef 100644 --- a/docs/locale/en/LC_MESSAGES/status/software-verification.po +++ b/docs/locale/en/LC_MESSAGES/status/software-verification.po @@ -1,16 +1,13 @@ -# Authored translation; independently semantically reviewed. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source docs\n" -"Report-Msgid-Bugs-To: \n" -"Language-Team: Nexus agent-reviewed documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 11978a6523f423d8d4aa48b8442265713c9dbf09a7c8b03adebe7acbc9069091\n" +"X-Nexus-Source-SHA256: a1b25e155c63dd34ef73caa0cc272c9ace92fb7d59c8a3c304240cce142b64d7\n" #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:9 msgid "软件验证、源码身份与证据" @@ -84,9 +81,10 @@ msgstr "" msgid "被引用资格源码" msgstr "Referenced qualification source" -#: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 -msgid "同一 manifest 的 `qualified_code` 与其结果索引" -msgstr "`qualified_code` and its results index in the same manifest" +msgid "schema 2 manifest 的 `qualification_references[]`;每项保存原候选身份与 scope" +msgstr "" +"`qualification_references[]` in the schema 2 manifest; each entry " +"retains its original candidate identity and scope" #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 msgid "旧的精确代码执行;不是当前代码/文档 HEAD 自动继承的资格" @@ -106,6 +104,26 @@ msgstr "" "constant. An unpackaged working tree can run `git rev-parse HEAD` to check " "its checkout, but that command output alone is not a qualification receipt." +msgid "" +"schema 2 不再隐式引用某个历史索引。打包者逐个显式选入原封存候选,工具调用 同一候选 verifier 核对原始闭包后记录 " +"source、configuration、ELF/BIN/map、 qualified scopes " +"与候选文件摘要。没有选入候选时列表为空,不表示软件失败。 文档包离线校验保护这份引用的字节和结构,不重新执行或代替原候选资格校验。 原 " +"locator 只说明当时的存储位置,不是公开下载地址。schema 1 历史包保留原 `qualified_code` " +"的只读校验,不能用它生产新的默认资格引用。" +msgstr "" +"Schema 2 no longer implicitly references a historical index. The " +"packager explicitly selects each original sealed candidate; the tool " +"invokes the same candidate verifier to check its original closure before" +" recording source, configuration, ELF/BIN/map, qualified scopes and the " +"candidate file digest. With no candidate selected, the list is empty; " +"this does not mean software failed. Offline documentation verification " +"protects these reference bytes and structure; it neither reexecutes nor " +"replaces original candidate qualification verification. The original " +"locator identifies its storage location at that time, not a public " +"download URL. Historical schema 1 packages retain read-only verification" +" of their original `qualified_code`; it cannot produce a new default " +"qualification reference." + #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:31 msgid "已有独立结果索引" msgstr "Existing independent results indexes" @@ -122,6 +140,27 @@ msgstr "Exact code source" msgid "原权威及执行说明" msgstr "Original authority and execution explanation" +msgid "SoC/IP 目录整理与原候选" +msgstr "SoC/IP layout refactor and original candidate" + +msgid "" +"P `92e669233957a2e450a528394e8f4dd0794c7049`;E " +"`0f15022e4d5f5c51991b134d0b9d6082dcf88de9`" +msgstr "" +"P `92e669233957a2e450a528394e8f4dd0794c7049`; E " +"`0f15022e4d5f5c51991b134d0b9d6082dcf88de9`" + +msgid "" +"[执行说明](../delivery/verification.md)、{download}`48 项原台账 <../design/soc-ip" +"-layout-execution.csv>`、[派生视图](ledgers/soc-ip-layout.md);同一 P 的 A90+B69 " +"分段覆盖,原候选只覆盖其精确配置,物理 HIL 未执行" +msgstr "" +"[Execution explanation](../delivery/verification.md), {download}`48 " +"original ledger items <../design/soc-ip-layout-execution.csv>` and " +"[derived view](ledgers/soc-ip-layout.md); A90+B69 segmented coverage of " +"the same P, the original candidate covers only its exact configuration, " +"and physical HIL was not executed" + #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 msgid "开发体验迭代 P 与示例 E" msgstr "Developer-experience iteration P and examples E" diff --git a/docs/locale/zh_CN/LC_MESSAGES/assets/diagrams/README.po b/docs/locale/zh_CN/LC_MESSAGES/assets/diagrams/README.po index aff021933..d113ceb1f 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/assets/diagrams/README.po +++ b/docs/locale/zh_CN/LC_MESSAGES/assets/diagrams/README.po @@ -1,6 +1,13 @@ -# Exact messages independently reviewed; approval bound by companion receipt. +# Independently reviewed translations; exact canonical source identity below. msgid "" -msgstr "Project-Id-Version: Nexus source documentation\nLanguage: zh_CN\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nX-Nexus-Reviewed: true\nX-Nexus-Source-SHA256: e9bf8514d8b46f9f905ccbea68dfe2473c7292eb9eae4af32f04399ea55e0273\n" +msgstr "" +"Project-Id-Version: Nexus source documentation\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Nexus-Reviewed: true\n" +"X-Nexus-Source-SHA256: b7ddb348c7b95d010390b39fb5df5693213e8e0016fe196522f58e0c2ec588b5\n" msgid "Nexus 平台图册" msgstr "Nexus 平台图册" @@ -96,8 +103,16 @@ msgstr "![FIG-03:构建输入/产物链,生成失败使owned bundle失效](c msgid "FIG-03:构建输入/产物链,生成失败使owned bundle失效" msgstr "FIG-03:构建输入/产物链,生成失败使owned bundle失效" -msgid "边表示构建时输入和产物传递,不表示运行时调用。严格解析不能修复非法字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。ELF/资源报告不构成实板资格。" -msgstr "边表示构建时输入和产物传递,不表示运行时调用。严格解析不能修复非法字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。ELF/资源报告不构成实板资格。" +msgid "" +"边表示构建时输入和产物传递,不表示运行时调用。TOML 唯一入口先得到冻结 `AuthoredAssembly` 意图,再与 SoC/Board" +" 事实校验得到具名 `ConfigurationIR`。 同一 IR 驱动 SoC 的静态定义/冷态调用与单向 " +"JSON/配置/内存投影;全部经过 同一 bundle 事务发布。`resolved.json` 不回流为装配输入。严格解析不能修复非法 " +"字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。 ELF/资源报告不构成实板资格。" +msgstr "" +"边表示构建时输入和产物传递,不表示运行时调用。TOML 唯一入口先得到冻结 `AuthoredAssembly` 意图,再与 SoC/Board" +" 事实校验得到具名 `ConfigurationIR`。 同一 IR 驱动 SoC 的静态定义/冷态调用与单向 " +"JSON/配置/内存投影;全部经过 同一 bundle 事务发布。`resolved.json` 不回流为装配输入。严格解析不能修复非法 " +"字段、冲突或陈旧输入;失败只撤回拥有的输出,不清理未拥有目录,也不暴露半成品。 ELF/资源报告不构成实板资格。" msgid "依据:[配置器](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/configure.py)、[不可变IR](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/ir.py)、[bundle事务](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bundle.py)、[构建合同](../../design/build-system.md)。 {download}`可编辑DOT `;实施关联:DOC-45。" msgstr "依据:[配置器](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/configure.py)、[不可变IR](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/ir.py)、[bundle事务](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bundle.py)、[构建合同](../../design/build-system.md)。 {download}`可编辑DOT `;实施关联:DOC-45。" @@ -117,6 +132,9 @@ msgstr "FIG-04:按需选入的两同类实例示意;同模式ops共享,con msgid "结构图展示由生成器支持的按配置实例布局,不声称每块PCB均授权这些组合。同provider同模式实例共享readonly ops,各自context/state/storage独立。不同provider或模式可使用不同ops;调用方法接收本次context。controller和endpoint有独立factory命名空间,endpoint不冒充控制器。ID只在当前assembly有效,默认uint16_t typedef不提供语言级跨类防混用。" msgstr "结构图展示由生成器支持的按配置实例布局,不声称每块PCB均授权这些组合。同provider同模式实例共享readonly ops,各自context/state/storage独立。不同provider或模式可使用不同ops;调用方法接收本次context。controller和endpoint有独立factory命名空间,endpoint不冒充控制器。ID只在当前assembly有效,默认uint16_t typedef不提供语言级跨类防混用。" +msgid "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" +msgstr "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" + msgid "FIG-05 显式启动停机与失败撤回" msgstr "FIG-05 显式启动停机与失败撤回" @@ -506,6 +524,9 @@ msgstr "Native RTC/LPTIM 的构建期装配、运行调用、最终访问和私 msgid "构建边、运行调用和请求源推进均有独立标签。factory 只返回已有接口面;生成的 Platform 首次准备构造一次,后续对已构造 owner 调用私有 start。stop 先关闭准入, 撤源失败仍保留请求和 owner;私有 start 优先拒绝保留责任。真实撤源并完成外部 join 后,重新开放先推进代次再开放准入,保留日历、速率、时钟与外部故障。模型推进不 发布请求结果。虚线硬件适配需要独立 SoC/时钟/备份域/IRQ/STOP/VBAT 合同, 不表示已有物理驱动或 HIL 通过。" msgstr "构建边、运行调用和请求源推进均有独立标签。factory 只返回已有接口面;生成的 Platform 首次准备构造一次,后续对已构造 owner 调用私有 start。stop 先关闭准入,撤源失败仍保留请求和 owner;私有 start 优先拒绝保留责任。真实撤源并完成外部 join 后,重新开放先推进代次再开放准入,保留日历、速率、时钟与外部故障。模型推进不发布请求结果。虚线硬件适配需要独立 SoC/时钟/备份域/IRQ/STOP/VBAT 合同,不表示已有物理驱动或 HIL 通过。" +msgid "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、 [公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、 [公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、 [Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、 [Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/ 候选/实板资格分别判断,不能从该图或渲染成功推断。" +msgstr "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、[公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、[公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、[Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、[Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。{download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/候选/实板资格分别判断,不能从该图或渲染成功推断。" + msgid "FIG-22 G474 命名 GPIO 状态与保留租约" msgstr "FIG-22 G474 命名 GPIO 状态与保留租约" @@ -682,12 +703,6 @@ msgstr "" "development SDK consumer 是有范围的软件执行;图和静态链接不执行外部时钟、不证明真实音频或 HIL。IO-41、完整 " "SDK/formal/OCI 与物理退出仍分别开放。" -msgid "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" -msgstr "依据:[typed factory生成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/factory.py)、[face/state装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/configure/bindings.py)、[SPI公开接口](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/spi.h)、[Native provider](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑DOT `;实施关联:DOC-46。" - -msgid "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、 [公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、 [公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、 [Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、 [Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。 {download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/ 候选/实板资格分别判断,不能从该图或渲染成功推断。" -msgstr "依据:[RTC/LPTIM 说明](../../explanation/rtc-lptim.md)、[公共 RTC](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/rtc.h)、[公共 LPTIM](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/io/include/nexus/io/lptim.h)、[Native 实现](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/drivers/rtc_lptim.c)、[Native 装配](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/native/configure/bindings.py)。{download}`可编辑 DOT `;实施关联:IO-36。软件开发证据与 main/SDK/候选/实板资格分别判断,不能从该图或渲染成功推断。" - msgid "FIG-31 共享 IP 与精确 SoC 源码归属" msgstr "FIG-31 共享 IP 与精确 SoC 源码归属" diff --git a/docs/locale/zh_CN/LC_MESSAGES/delivery/architecture.po b/docs/locale/zh_CN/LC_MESSAGES/delivery/architecture.po index 4c988cd19..09d8ee0d0 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/delivery/architecture.po +++ b/docs/locale/zh_CN/LC_MESSAGES/delivery/architecture.po @@ -1,4 +1,4 @@ -# Authored translation; review state is explicit in catalog metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 51776ec62d9482ab03cca988e7d9f875a37aa437ac65bb1eb637dfb5db448289\n" +"X-Nexus-Source-SHA256: 77731080a72496622d8699797ce669cda9eeb5e35df86a6ac509513835477f49\n" msgid "当前系统架构" msgstr "当前系统架构" @@ -33,6 +33,10 @@ msgstr "中性结果、绝对 monotonic deadline、caller-owned request 状态" msgid "C11;无 Board、RTOS、SDK" msgstr "C11;无 Board、RTOS、SDK" +#: docs/delivery/architecture.md:0 +msgid "`arch/arm/cortex_m/`、`arch/native/`" +msgstr "`arch/arm/cortex_m/`、`arch/native/`" + msgid "" "编译期 Cortex-M/Native 原语、异常/特权/掩码查询、中断保存恢复、屏障、cycle snapshot;显式选入的 " "Cache/MPU/SAU 与 Secure CMSE 权限观察" @@ -40,9 +44,38 @@ msgstr "" "编译期 Cortex-M/Native 原语、异常/特权/掩码查询、中断保存恢复、屏障、cycle snapshot;显式选入的 " "Cache/MPU/SAU 与 Secure CMSE 权限观察" +#: docs/delivery/architecture.md:0 +msgid "公共头仍位于 `arch/include/nexus/arch/`;无 SDK/OS/SoC 政策,原语编译与整平台资格分别记录" +msgstr "" +"Public headers remain in `arch/include/nexus/arch/`; no SDK/OS/SoC policy; " +"primitive compilation and whole-platform qualification are recorded " +"separately" + +#: docs/delivery/architecture.md:0 +msgid "`soc///`" +msgstr "`soc///`" + +#: docs/delivery/architecture.md:0 +msgid "" +"`stm32`/`gd32` 下 F103、GD303、F407、F470、G474、H743、U575 " +"的精确内存/clock/IRQ/startup、timebase、typed controller 实现" +msgstr "" +"Exact memory/clock/IRQ/startup, timebase and typed controller " +"implementations for F103, GD303, F407, F470, G474, H743 and U575 under " +"`stm32`/`gd32`" + msgid "私有 SDK 与私有 provider storage;各家族只开放已维护模式" msgstr "私有 SDK 与私有 provider storage;各家族只开放已维护模式" +msgid "`drivers///`" +msgstr "`drivers///`" + +msgid "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" +msgstr "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" + +msgid "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" +msgstr "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" + msgid "`boards//board.json`" msgstr "`boards//board.json`" @@ -55,6 +88,21 @@ msgstr "只读资源包;不创建产品任务" msgid "`io/`" msgstr "`io/`" +#: docs/delivery/architecture.md:0 +msgid "" +"公共 include、`src/api` 检查分派、`src/mechanisms` 共用规则、`src/controllers` " +"控制器引擎;private 硬件边界" +msgstr "" +"Public includes, checked dispatch in `src/api`, shared rules in " +"`src/mechanisms`, controller engines in `src/controllers`; private hardware " +"boundaries" + +#: docs/delivery/architecture.md:0 +msgid "公共头不含厂商类型;Native provider 位于 `soc/native/drivers`,建立行为模型" +msgstr "" +"Public headers contain no vendor types; Native providers in " +"`soc/native/drivers` establish behavior models" + msgid "`os/`" msgstr "`os/`" @@ -67,6 +115,21 @@ msgstr "driver 不依赖 OS;不创建应用 worker" msgid "`components/`" msgstr "`components/`" +#: docs/delivery/architecture.md:0 +msgid "" +"显式选入的 owner、BMP280、Modbus、Storage、Log 等通用机制;`components/soc/stm32` " +"分类维护精确芯片扩展" +msgstr "" +"Explicitly selected generic owner, BMP280, Modbus, Storage and Log " +"mechanisms; exact-chip extensions are maintained under " +"`components/soc/stm32`" + +#: docs/delivery/architecture.md:0 +msgid "通过窄端口访问设备、时间与外部政策;芯片扩展不扩大其他型号支持" +msgstr "" +"Access devices, time and external policy through narrow ports; chip-specific" +" extensions do not broaden support for other parts" + msgid "`tools/`、`cmake/`" msgstr "`tools/`、`cmake/`" @@ -85,6 +148,21 @@ msgstr "产品 composition、任务、策略、布局、预算、恢复与业务 msgid "pin 一个明确平台版本,拥有实际执行进展" msgstr "pin 一个明确平台版本,拥有实际执行进展" +#: docs/delivery/architecture.md:17 +msgid "" +"完整物理目录与相邻 README 以[源码合同入口](../explanation/source-map.md)为准。 厂家与 ISA " +"分组只改变构建期源码所有权;公共 include、CMake target 和装配中的 family " +"标识保持。公共等待/唤醒端口可跨后端连接,Task/Queue/join/静态控制块 仍使用后端专用 API;切换 RTOS " +"的产品显式适配其生命周期,不引入通用最大池。" +msgstr "" +"The [source contracts](../explanation/source-map.md) own the complete " +"physical layout and adjacent READMEs. Vendor and ISA grouping changes only " +"build-time source ownership; public includes, CMake targets and authored " +"family identifiers remain stable. The public wait/wake port can connect " +"across backends, while Task/Queue/join/static control blocks retain backend-" +"specific APIs. Products switching RTOS explicitly adapt their lifecycle; no " +"universal maximum pool is introduced." + msgid "" "SoC 按 controller kind/mode 组织 translation unit,同一实现可服务多个独立实例。 选入一个 UART " "不会默认驻留所有 外设、RTOS 队列或最大对象池。启动与 system source 是显式 firmware 输入,不依靠 静态 archive " @@ -208,13 +286,52 @@ msgstr "" "冲突、越界容量 都会拒绝;失败重配置使旧 bundle 失效,不静默使用上一次成功结果。" msgid "" -"SoC/Board 事实采用 JSON,使用者编写 schema-2 TOML。严格 resolver 产生不可变 typed " -"IR;各生成器消费同一结果。`resolved.json` 是该结果的序列化,不能作为手工 配置回填。迁移期 schema-1 JSON " -"夹具有显式入口,不与 TOML 混合叠加。" +"SoC/Board 事实采用 JSON,平台装配只接受 schema-2 TOML。`AuthoredAssembly` " +"保存冻结的用户意图;`resolve_ir()` 严格校验事实、模式和资源后直接产生不可变 `ConfigurationIR`。生产 " +"resolver、factory 和 emitter 使用具名字段,不提供 Mapping 接口或保存第二份 `values` " +"决策副本。`to_dict()` 派生 `resolved.json` 供审计与独立产物检查,不能回填为配置;旧 schema-1 JSON " +"装配入口已删除。 历史证据仍保持原格式和只读校验,不建立当前代码资格。" +msgstr "" +"SoC/Board 事实采用 JSON,平台装配只接受 schema-2 TOML。`AuthoredAssembly` " +"保存冻结的用户意图;`resolve_ir()` 严格校验事实、模式和资源后直接产生不可变 `ConfigurationIR`。生产 " +"resolver、factory 和 emitter 使用具名字段,不提供 Mapping 接口或保存第二份 `values` " +"决策副本。`to_dict()` 派生 `resolved.json` 供审计与独立产物检查,不能回填为配置;旧 schema-1 JSON " +"装配入口已删除。 历史证据仍保持原格式和只读校验,不建立当前代码资格。" + +msgid "" +"IR 身份、预算和 controller/endpoint/CPU/kernel/resource options 使用冻结 " +"record;IRQ 为嵌套 record,ADC channels/sample times 为 tuple。原 JSON 硬件 " +"事实与按身份索引的只读 mapping 仍是明确的叶数据,resolver 内局部校验草稿也仍 可使用 dict;这不等于每个 JSON " +"字段都有名义类型。`source_fields` 只记录投影 字段存在性,不保存决策值副本。Python typed source epoch " +"为 2,JSON 投影仍为 schema 1;两者各自计代,不改变 MCU C factory 接口。" msgstr "" -"SoC/Board 事实采用 JSON,使用者编写 schema-2 TOML。严格 resolver 产生不可变 typed " -"IR;各生成器消费同一结果。`resolved.json` 是该结果的序列化,不能作为手工 配置回填。迁移期 schema-1 JSON " -"夹具有显式入口,不与 TOML 混合叠加。" +"IR 身份、预算和 controller/endpoint/CPU/kernel/resource options 使用冻结 " +"record;IRQ 为嵌套 record,ADC channels/sample times 为 tuple。原 JSON 硬件 " +"事实与按身份索引的只读 mapping 仍是明确的叶数据,resolver 内局部校验草稿也仍 可使用 dict;这不等于每个 JSON " +"字段都有名义类型。`source_fields` 只记录投影 字段存在性,不保存决策值副本。Python typed source epoch " +"为 2,JSON 投影仍为 schema 1;两者各自计代,不改变 MCU C factory 接口。" + +msgid "" +"`soc///configure/`(Native 为 `soc/native/configure/`)明确 " +"拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 " +"constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共" +" binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 " +"CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board " +"复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" +msgstr "" +"`soc///configure/`(Native 为 `soc/native/configure/`)明确 " +"拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 " +"constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共" +" binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 " +"CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board " +"复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" + +msgid "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" +msgstr "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" + +msgid "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" +msgstr "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" + msgid "" "不可变 family descriptor 统一配置入口、依赖身份和 SDK 必需 payload;controller descriptor " @@ -238,6 +355,10 @@ msgstr "" "storage 只对实现与生成 binding TU 可见。公共消费路径没有厂商 SDK ABI。 外部 firmware 通过 " "`nexus_add_firmware()` 显式装入 startup/system/linker 和资源校验。" +# CMSIS6 actual-POT delta; independent review pending. +msgid "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" +msgstr "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" + msgid "固定对象与执行" msgstr "固定对象与执行" @@ -602,94 +723,3 @@ msgstr "" "verification.md) 绑定其历史 code revision, 不覆盖后续扩展;[扩展执行记录](soc-extensions-" "verification.md) 独立绑定对应 源码、执行结果和原始证据。主机门禁、ARM 矩阵、安装 SDK、外部示例与离线 " "复现各自核验,文档提交不替换被测 code 身份。新候选仍有独立退出条件;全部实板 尚未接入,物理结果保持 `not_executed`。" - -#: docs/delivery/architecture.md:0 -msgid "`arch/arm/cortex_m/`、`arch/native/`" -msgstr "`arch/arm/cortex_m/`、`arch/native/`" - -#: docs/delivery/architecture.md:0 -msgid "公共头仍位于 `arch/include/nexus/arch/`;无 SDK/OS/SoC 政策,原语编译与整平台资格分别记录" -msgstr "" -"Public headers remain in `arch/include/nexus/arch/`; no SDK/OS/SoC policy; " -"primitive compilation and whole-platform qualification are recorded " -"separately" - -#: docs/delivery/architecture.md:0 -msgid "`soc///`" -msgstr "`soc///`" - -#: docs/delivery/architecture.md:0 -msgid "" -"`stm32`/`gd32` 下 F103、GD303、F407、F470、G474、H743、U575 " -"的精确内存/clock/IRQ/startup、timebase、typed controller 实现" -msgstr "" -"Exact memory/clock/IRQ/startup, timebase and typed controller " -"implementations for F103, GD303, F407, F470, G474, H743 and U575 under " -"`stm32`/`gd32`" - -#: docs/delivery/architecture.md:0 -msgid "" -"公共 include、`src/api` 检查分派、`src/mechanisms` 共用规则、`src/controllers` " -"控制器引擎;private 硬件边界" -msgstr "" -"Public includes, checked dispatch in `src/api`, shared rules in " -"`src/mechanisms`, controller engines in `src/controllers`; private hardware " -"boundaries" - -#: docs/delivery/architecture.md:0 -msgid "公共头不含厂商类型;Native provider 位于 `soc/native/drivers`,建立行为模型" -msgstr "" -"Public headers contain no vendor types; Native providers in " -"`soc/native/drivers` establish behavior models" - -#: docs/delivery/architecture.md:0 -msgid "" -"显式选入的 owner、BMP280、Modbus、Storage、Log 等通用机制;`components/soc/stm32` " -"分类维护精确芯片扩展" -msgstr "" -"Explicitly selected generic owner, BMP280, Modbus, Storage and Log " -"mechanisms; exact-chip extensions are maintained under " -"`components/soc/stm32`" - -#: docs/delivery/architecture.md:0 -msgid "通过窄端口访问设备、时间与外部政策;芯片扩展不扩大其他型号支持" -msgstr "" -"Access devices, time and external policy through narrow ports; chip-specific" -" extensions do not broaden support for other parts" - -#: docs/delivery/architecture.md:17 -msgid "" -"完整物理目录与相邻 README 以[源码合同入口](../explanation/source-map.md)为准。 厂家与 ISA " -"分组只改变构建期源码所有权;公共 include、CMake target 和装配中的 family " -"标识保持。公共等待/唤醒端口可跨后端连接,Task/Queue/join/静态控制块 仍使用后端专用 API;切换 RTOS " -"的产品显式适配其生命周期,不引入通用最大池。" -msgstr "" -"The [source contracts](../explanation/source-map.md) own the complete " -"physical layout and adjacent READMEs. Vendor and ISA grouping changes only " -"build-time source ownership; public includes, CMake targets and authored " -"family identifiers remain stable. The public wait/wake port can connect " -"across backends, while Task/Queue/join/static control blocks retain backend-" -"specific APIs. Products switching RTOS explicitly adapt their lifecycle; no " -"universal maximum pool is introduced." - -# CMSIS6 actual-POT delta; independent review pending. -msgid "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" -msgstr "七个 SoC family 的 CPU 头统一选用官方 CMSIS-Core v6.4.0 pin,Device、startup 及 HAL/LL 仍由各 family 的锁定供应商来源维护。内部 `nexus_cmsis_core` 和 `nexus_use_cmsis_core(target)` 归构建目标图拥有,与公共 `Nexus::Core` 中性机制 不同;普通公共消费路径不导出厂商 Core 头或类型。GD32F470 原导入中的旧 Core 字节与通知保留,实际选入的是共享来源,不能仅凭目录存在判断编译依赖。版本和 许可证身份见[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)。这一依赖迁移不改变公共 Arch 的上下文、所有权与屏蔽恢复合同,不自动启用新的芯片、profile 或安全域; 实际 include 闭包、ARM 链接、移位 SDK 消费与物理资格分别验收。" - -msgid "`drivers///`" -msgstr "`drivers///`" - -msgid "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" -msgstr "经逐芯片复核的共享 UART、I2C、ADC、timer 与 Flash 寄存器协议算法" - -msgid "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" -msgstr "所选 SoC target 私有编译;不拥有芯片路由、时钟、Board 或运行时注册" - -msgid "IR 身份、预算和各类 controller options 使用冻结 record,IRQ 为嵌套 record, ADC channels/sample times 为 tuple;完整 JSON 事实投影仍保留只读 mapping, 不能声称所有字段均有名义强类型。`soc///configure/`(Native 为 `soc/native/configure/`)明确 拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共 binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board 复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" -msgstr "IR 身份、预算和各类 controller options 使用冻结 record,IRQ 为嵌套 record, ADC channels/sample times 为 tuple;完整 JSON 事实投影仍保留只读 mapping, 不能声称所有字段均有名义强类型。`soc///configure/`(Native 为 `soc/native/configure/`)明确 拥有 ABI、mode→implementation、DMA 路线、trigger、共享 IRQ 与 CS 合同,以及 constructor/rollback/IRQ 和系统时钟生成。公共 resolver 保留 schema、事实、 资源、输入身份与输出,公共 binding 编排 Nexus face 与统一生命周期进度。 新增 SoC 需显式登记真实 provider 模块并维护 CMake/SDK 必需文件;未知 family 和 mode 拒绝,不继承 Native 或 Cortex-M4 默认值。普通 Board 复用已有实现, 不复制驱动;文档矩阵不能反向决定配置。" - -msgid "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" -msgstr "显式 provider map 与共享 leaf 合同仍在 `tools/configure/providers/`,配置测试仍在 `tools/configure/tests/providers//`。共享 IP 源码选编、配置包位置与物理资格 分别核对;本轮路径整理不扩大模式支持,实板状态仍为 `not_executed`。" - -msgid "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" -msgstr "`ConfigurationIR.physical_part` 与 `ControllerIR.integration` 是现有冻结事实和资源计划 的具名视图,复用原 Flash/memory、resource claim、DMA lane、IRQ 和 clock 对象; 不增加 JSON/配置身份字段或另一套路由解析。F407 SPI 与成对 secondary-controller 路径已消费该 integration,其他构造仍可使用既有适配。名义 APB 频率不替代经复核 的 clock domain envelope。字段职责和新增料号检查见 [provider 接入指南](../guides/provider-porting.md#keep-facts-and-admission-policy-distinct)。" diff --git a/docs/locale/zh_CN/LC_MESSAGES/delivery/capabilities.po b/docs/locale/zh_CN/LC_MESSAGES/delivery/capabilities.po index e170aa80e..fbc764a26 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/delivery/capabilities.po +++ b/docs/locale/zh_CN/LC_MESSAGES/delivery/capabilities.po @@ -1,13 +1,13 @@ -# Authored translation; independent review is recorded in metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: b1fce9a4e31432cd1323234763e83f60e26e3700dc541d5bf88044c93b596330\n" +"X-Nexus-Source-SHA256: a6b4e8651695e4de512a08ad3ce897e59317b3e8e124de58598da6903d2f6ab0\n" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:1 msgid "派生能力与接入矩阵" @@ -91,13 +91,12 @@ msgstr "" "endpoint。DMA 与引脚来自 本次解析,不从同系列其他路线补齐。上例是 F407ZG 的 USART1 有限 `dma-tx` 选择,不能扩展为" " F407 全外设或全部 UART 模式支持。" -#: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:44 msgid "" -"`validation` 分开列出以下阶段。查询只执行生产 resolver 和合同投影,不搜索旧 报告,也不运行编译、测试、烧录或测量。因此,下面的 " -"`not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" +"`validation` 分开列出以下阶段。默认查询只执行生产 resolver 和合同投影,不搜索旧 " +"报告,也不运行编译、测试、烧录或测量。因此,下面的 `not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" msgstr "" -"`validation` 分开列出以下阶段。查询只执行生产 resolver 和合同投影,不搜索旧 报告,也不运行编译、测试、烧录或测量。因此,下面的 " -"`not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" +"`validation` 分开列出以下阶段。默认查询只执行生产 resolver 和合同投影,不搜索旧 " +"报告,也不运行编译、测试、烧录或测量。因此,下面的 `not_assessed` 表示本次查询 没有评估该阶段,不表示实现失败或历史上从未执行。" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "阶段" @@ -183,6 +182,43 @@ msgstr "" "`false`;未知 PCB 事实不会因解析成功变为已验证。 现有记录从[软件证据入口](../status/software-" "verification.md)进入,实际执行范围 以各记录中的不可变源码为准。配置、软件和物理阶段是不同证据,并非会自动晋级的 单个“支持等级”。" +msgid "显式关联已封存候选" +msgstr "显式关联已封存候选" + +msgid "需要核对已有执行证据时,在同一查询追加原候选路径:" +msgstr "需要核对已有执行证据时,在同一查询追加原候选路径:" + +msgid "" +"工具调用原候选 verifier 核对全部 payload、qualification 和链接产物,不运行新的 测试。报告同时保存 " +"`current_source` 和 `evidence.reference.source`,不会用被引用 源码覆盖当前 " +"checkout。`evidence.relation` 只在干净 commit/tree/submodules 与 精确解析配置全部相同时为 " +"`exact_source_and_configuration`;此时才把原候选的 四类软件阶段投影为 " +"`passed`。这是原执行的只读关联,不是本次新执行。" +msgstr "" +"工具调用原候选 verifier 核对全部 payload、qualification 和链接产物,不运行新的 测试。报告同时保存 " +"`current_source` 和 `evidence.reference.source`,不会用被引用 源码覆盖当前 " +"checkout。`evidence.relation` 只在干净 commit/tree/submodules 与 精确解析配置全部相同时为 " +"`exact_source_and_configuration`;此时才把原候选的 四类软件阶段投影为 " +"`passed`。这是原执行的只读关联,不是本次新执行。" + +msgid "" +"有效候选属于其他源码时标 `historical_source`,配置不同时标 " +"`configuration_mismatch`;这两种情况保留当前选择的 `not_assessed`。原件缺失、 " +"被修改或校验失败会拒绝查询,不能降级为已验证引用。未提供 `--candidate` 时 无需 Git 或证据观察。可搬移候选需先用现有 " +"artifact 工具恢复原 closure,再提供 其中的 `candidate.json`;只有包摘要或下载位置不能建立通过状态。" +msgstr "" +"有效候选属于其他源码时标 `historical_source`,配置不同时标 " +"`configuration_mismatch`;这两种情况保留当前选择的 `not_assessed`。原件缺失、 " +"被修改或校验失败会拒绝查询,不能降级为已验证引用。未提供 `--candidate` 时 无需 Git 或证据观察。可搬移候选需先用现有 " +"artifact 工具恢复原 closure,再提供 其中的 `candidate.json`;只有包摘要或下载位置不能建立通过状态。" + +msgid "" +"任何软件候选都不改变 `physical_hil`、`physical_qualified=false` 或下方聚合表的 family/mode" +" 行。硬件状态仍需要真实 station 和同一镜像的实际观察;一个精确 配置通过不等于所有 Board、backend、时钟或模式通过。" +msgstr "" +"任何软件候选都不改变 `physical_hil`、`physical_qualified=false` 或下方聚合表的 family/mode" +" 行。硬件状态仍需要真实 station 和同一镜像的实际观察;一个精确 配置通过不等于所有 Board、backend、时钟或模式通过。" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "SoC" msgstr "SoC" @@ -247,6 +283,9 @@ msgstr "" "`tests/contracts/mainstream_assembly/gd303-devices-freertos.toml`, " "`tests/contracts/mainstream_assembly/gd303-devices.toml`" +msgid "`drivers/adc/legacy_adc/legacy_adc.c`" +msgstr "`drivers/adc/legacy_adc/legacy_adc.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "EXTI0" msgstr "EXTI0" @@ -295,6 +334,9 @@ msgstr "poll-word" msgid "`flash-internal`" msgstr "`flash-internal`" +msgid "`drivers/flash/legacy_flash/legacy_flash.c`" +msgstr "`drivers/flash/legacy_flash/legacy_flash.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "FWDGT" msgstr "FWDGT" @@ -387,6 +429,9 @@ msgstr "" "`tests/contracts/legacy_transport_assembly/gd32f303-baremetal-i2c.toml`, " "`tests/contracts/legacy_transport_assembly/gd32f303-freertos-i2c.toml`" +msgid "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" +msgstr "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "SPI0" msgstr "SPI0" @@ -423,6 +468,9 @@ msgstr "fixed-pwm" msgid "`pwm-pa6`" msgstr "`pwm-pa6`" +msgid "`drivers/timer/legacy_pwm/legacy_pwm.c`" +msgstr "`drivers/timer/legacy_pwm/legacy_pwm.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "USART0" msgstr "USART0" @@ -449,6 +497,9 @@ msgstr "" "duplex.toml`, `tests/contracts/legacy_transport_assembly/gd32f303-freertos-" "dma-duplex.toml`" +msgid "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" +msgstr "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "dma-tx" msgstr "dma-tx" @@ -2093,6 +2144,10 @@ msgstr "`gpio-pg10`" msgid "`i2c1-pb8-pb9`" msgstr "`i2c1-pb8-pb9`" +msgid "`drivers/i2c/i2c_v2/i2c_v2.c`" +msgstr "`drivers/i2c/i2c_v2/i2c_v2.c`" + + #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "" "`tests/contracts/g474_assembly/spi-freertos.toml`, " @@ -2988,21 +3043,3 @@ msgstr "`soc/native/drivers/uart_word.c`" #: ../../../../tmp/nexus-quality-20261019/docs/delivery/capabilities.md:0 msgid "UART1" msgstr "UART1" - -msgid "`drivers/adc/legacy_adc/legacy_adc.c`" -msgstr "`drivers/adc/legacy_adc/legacy_adc.c`" - -msgid "`drivers/flash/legacy_flash/legacy_flash.c`" -msgstr "`drivers/flash/legacy_flash/legacy_flash.c`" - -msgid "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" -msgstr "`drivers/i2c/i2c_v1_async/i2c_v1_async.c`" - -msgid "`drivers/timer/legacy_pwm/legacy_pwm.c`" -msgstr "`drivers/timer/legacy_pwm/legacy_pwm.c`" - -msgid "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" -msgstr "`drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.c`" - -msgid "`drivers/i2c/i2c_v2/i2c_v2.c`" -msgstr "`drivers/i2c/i2c_v2/i2c_v2.c`" diff --git a/docs/locale/zh_CN/LC_MESSAGES/delivery/migration.po b/docs/locale/zh_CN/LC_MESSAGES/delivery/migration.po index b5ba827b9..57a2fd421 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/delivery/migration.po +++ b/docs/locale/zh_CN/LC_MESSAGES/delivery/migration.po @@ -1,4 +1,4 @@ -# Authored translation; independent semantic review pending. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 1c65f485722223215c7aa57d5bf99e751eabb0a5482118abc3bec65068d922a4\n" +"X-Nexus-Source-SHA256: d0ae571f3de5c3208756e72c5f5b04bceb1cd55231657ab6ab9386f6f4aa7e79\n" #: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:1 msgid "外部工程迁移" @@ -121,15 +121,16 @@ msgstr "" "private provider storage 或 SDK register 类型。SPI/I2C controller 和子设备 endpoint 分开获取,controller " "factory 不得返回第一个子设备的别名。无效 ID 返回 NULL;factory 不初始化、不分配内存。" -#: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:27 msgid "" -"维护的 assembly 已统一为 TOML schema 2。schema 1 JSON 读取仅供明确迁移与工具 派生输入,不与 TOML 维护同一份配置。冷启动仍显式执行 " -"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED 时保持状态和所有借用存储。 在 start 前可以保存 " -"face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" +"平台输入只接受 TOML schema 2,旧 schema 1 JSON 装配必须迁为完整 TOML。 `resolved.json` " +"仅是审计/独立产物检查的单向输出,不能回填生成器。历史证据保留 原字节及只读校验,不重新开放旧配置入口。冷启动仍显式执行 " +"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED " +"时保持状态和所有借用存储。 在 start 前可以保存 face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" msgstr "" -"维护的 assembly 已统一为 TOML schema 2。schema 1 JSON 读取仅供明确迁移与工具 派生输入,不与 TOML 维护同一份配置。冷启动仍显式执行 " -"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED 时保持状态和所有借用存储。 在 start 前可以保存 " -"face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" +"平台输入只接受 TOML schema 2,旧 schema 1 JSON 装配必须迁为完整 TOML。 `resolved.json` " +"仅是审计/独立产物检查的单向输出,不能回填生成器。历史证据保留 原字节及只读校验,不重新开放旧配置入口。冷启动仍显式执行 " +"`nx_platform_start()`, 停机执行 `nx_platform_stop()`;BUSY/QUARANTINED " +"时保持状态和所有借用存储。 在 start 前可以保存 face;在成功 stop 后 face 的身份不变,provider 拒绝操作。" #: ../../../workspace/nexus-build-system-20261012/docs/delivery/migration.md:32 msgid "" diff --git a/docs/locale/zh_CN/LC_MESSAGES/design/documentation-system.po b/docs/locale/zh_CN/LC_MESSAGES/design/documentation-system.po index 620f7460a..42f9af76b 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/design/documentation-system.po +++ b/docs/locale/zh_CN/LC_MESSAGES/design/documentation-system.po @@ -1,14 +1,13 @@ -# +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 6b89c334b35f7f1475e81552cda98f43030879841233b557f4f7efbb804bf495\n" +"X-Nexus-Source-SHA256: 37a3be649a7d680cba2fa10020334d14f1a09719b4f8c4bec5eb3c6472d650fa\n" #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:1 msgid "Nexus 文档系统架构与交付规划" @@ -821,10 +820,10 @@ msgid "10. 版本和发布身份" msgstr "10. 版本和发布身份" msgid "" -"文档 manifest 复用既有 identity 和证据定位结构。下表只映射实际 schema-1 字段, " +"文档 manifest 复用既有 identity 和证据定位结构。下表映射当前 schema-2 字段, " "完整格式以[唯一交付合同](../reference/documentation-delivery.md)和生产打包器为准:" msgstr "" -"文档 manifest 复用既有 identity 和证据定位结构。下表只映射实际 schema-1 字段, " +"文档 manifest 复用既有 identity 和证据定位结构。下表映射当前 schema-2 字段, " "完整格式以[唯一交付合同](../reference/documentation-delivery.md)和生产打包器为准:" #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:0 @@ -854,17 +853,21 @@ msgstr "`documentation_source.commit`、`documentation_source.tree`" msgid "本次正文、配置和翻译源码" msgstr "本次正文、配置和翻译源码" -msgid "`qualified_code.commit`、`qualified_code.tree`" -msgstr "`qualified_code.commit`、`qualified_code.tree`" +msgid "`qualification_references[].source`" +msgstr "`qualification_references[].source`" -msgid "当前引用资格的精确源码,保留原scope" -msgstr "当前引用资格的精确源码,保留原scope" +msgid "显式选入的每份原候选源码;不继承为文档 HEAD 资格" +msgstr "显式选入的每份原候选源码;不继承为文档 HEAD 资格" -msgid "`qualified_code.index`、`qualified_code.index_sha256`" -msgstr "`qualified_code.index`、`qualified_code.index_sha256`" +msgid "" +"`qualification_references[].candidate`、`configuration_sha256`、`artifacts" +"`、`qualified_scopes`" +msgstr "" +"`qualification_references[].candidate`、`configuration_sha256`、`artifacts" +"`、`qualified_scopes`" -msgid "既有结果索引路径和摘要,保留其配置与执行身份" -msgstr "既有结果索引路径和摘要,保留其配置与执行身份" +msgid "经原 verifier 核对的候选定位、摘要和精确范围;默认列表为空" +msgstr "经原 verifier 核对的候选定位、摘要和精确范围;默认列表为空" msgid "`tool_versions`" msgstr "`tool_versions`" @@ -886,15 +889,16 @@ msgid "" msgstr "" "成功payload的`path`、`bytes`、`sha256`;不列manifest/checksums/archive/外层receipt" -#: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:271 msgid "" "三种源码身份不能互相覆盖。文档更新可引用经过验证的旧代码记录,不把该记录 重新标记为当前 HEAD " -"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 本方案描述的起点是 docs HEAD 7abde0e4、qualified code " -"cc6a065c;历史 SoC 记录保持独立。计划工具一旦实现变更,按新源码执行其门禁。" +"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 原方案起点 docs HEAD 7abde0e4 与 qualified code " +"cc6a065c 保持历史身份。 当前 schema 2 显式引用多份原候选,schema 1 仅保留原包的只读验证/恢复。 " +"工具实现变化按新源码执行门禁,不改写已有证据字节。" msgstr "" "三种源码身份不能互相覆盖。文档更新可引用经过验证的旧代码记录,不把该记录 重新标记为当前 HEAD " -"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 本方案描述的起点是 docs HEAD 7abde0e4、qualified code " -"cc6a065c;历史 SoC 记录保持独立。计划工具一旦实现变更,按新源码执行其门禁。" +"已执行。若描述的代码与资格不同,页面显示各自来源和边界。 原方案起点 docs HEAD 7abde0e4 与 qualified code " +"cc6a065c 保持历史身份。 当前 schema 2 显式引用多份原候选,schema 1 仅保留原包的只读验证/恢复。 " +"工具实现变化按新源码执行门禁,不改写已有证据字节。" #: ../../../workspace/nexus-build-system-20261012/docs/design/documentation-system.md:276 msgid "版本 URL 结构如下;具体已发布版本以实际发布记录为准:" diff --git a/docs/locale/zh_CN/LC_MESSAGES/design/official-stm32-provider.po b/docs/locale/zh_CN/LC_MESSAGES/design/official-stm32-provider.po index 2a39e67f7..c43207756 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/design/official-stm32-provider.po +++ b/docs/locale/zh_CN/LC_MESSAGES/design/official-stm32-provider.po @@ -1,4 +1,4 @@ -# +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: e620ed5c7a5d445115a60b3037c1b29f7c065c7bacc7146f013283553971d253\n" +"X-Nexus-Source-SHA256: 97e789c2e703cfbaef9df4d47cc1c744bf286e9a47740f91a745a681bb5e3127\n" msgid "Official drivers and provider iteration" msgstr "官方驱动与 provider 迭代" @@ -61,6 +61,10 @@ msgstr "" "DOCUMENTATION-SOURCE/dependencies/stm32f4-hal.lock.json),校验时对照所选 Git 依赖与导入 " "SDK 字节。锁文件记录已审查的 LL 头和许可证,不表示全部 HAL 模块均已编译、支持或验证。" +# CMSIS6 actual-POT delta; independent review pending. +msgid "CMSIS Device pairing is separate from the shared Arm CMSIS-Core source. The Core migration pins official v6.4.0 for the seven maintained STM32/GD32 family entries while keeping their Device/startup, HAL/LL and imported vendor bytes. The internal `nexus_cmsis_core` target supplies CPU headers; it does not change public Nexus types, Arch context/ownership contracts or the selected peripheral state machine. [Vendor versions](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md) distinguishes the release, Core version macros, literal pack metadata and license identities. Actual include closure, firmware links and installed-SDK consumption require current-source evidence; a common Core pin does not transfer F4 qualification to G4, H7, U5 or GD32." +msgstr "CMSIS Device 配对与共享的 Arm CMSIS-Core 来源分别维护。Core 迁移为七个维护中的 STM32/GD32 family 入口锁定官方 v6.4.0,同时保持各自的 Device/startup、HAL/LL 和供应商导入字节。内部 `nexus_cmsis_core` 目标提供 CPU 头,不改变公共 Nexus 类型、Arch 上下文/所有权合同或已选外设状态机。[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)分别记录发布版本、Core 版本宏、原始 pack 元数据和许可证身份。实际 include 闭包、固件链接和安装后 SDK 消费都需要当前源码的执行证据;使用同一 Core pin 不会把 F4 的资格转移给 G4、H7、U5 或 GD32。" + msgid "" "The selected production approach uses official LL operations inside existing" " F407 provider state machines. It retains request admission, absolute " @@ -127,21 +131,33 @@ msgid "Build-time provider contract" msgstr "构建期 provider 合同" msgid "" -"Each constructor returns the frozen `Construction` record from [provider " -"common contracts](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-" -"DOCUMENTATION-SOURCE/tools/configure/providers/common.py). It names " -"`state_type`, `definitions`, `initialize`, `stop`, `retain_on_failed_init`, " -"`exports` and `pin_snapshots`. The common emitter consumes these decisions " -"rather than reconstructing state types or failed-init responsibility from a " -"family name. This is a Python generation contract; no descriptor or dispatch" -" layer is emitted into the MCU image." +"Each constructor returns the frozen `Construction` record from [provider" +" common contracts](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMEN" +"TATION-SOURCE/tools/configure/providers/common.py). It names " +"`state_type`, `definitions`, `initialize`, `stop`, " +"`retain_on_failed_init`, `exports` and `pin_snapshots`. The common " +"emitter consumes these decisions rather than reconstructing state types " +"or failed-init responsibility from a family name. `initialize` is an " +"immutable `DirectCall`; `stop` is a tuple of these calls. Arguments are " +"explicit static `Symbol` or `Address` values, uint32 constants, " +"booleans, NULL, or nested direct calls such as absolute-deadline " +"construction. Free-form expressions, casts, assignments and " +"function-pointer/member-call expressions reject. `definitions`, " +"`exports` and `face_definitions` remain C source strings; they can still" +" contain selected helper bodies. The real C compiler establishes symbol " +"and parameter types. This is a narrow Python generation contract, not a " +"complete C AST; these Python records introduce no additional runtime " +"metadata or dispatch into the MCU image." msgstr "" -"每个构造器返回[provider 公共合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-" -"DOCUMENTATION-SOURCE/tools/configure/providers/common.py)中的冻结 `Construction`" -" 记录,明确给出 " -"`state_type`、`definitions`、`initialize`、`stop`、`retain_on_failed_init`、`exports`" -" 和 `pin_snapshots`。公共生成器消费这些决策,不再从家族名称重建状态类型或初始化失败责任。这是 Python 生成合同,不会向 MCU " -"镜像增加描述符或分派层。" +"每个构造器返回[provider 公共合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOC" +"UMENTATION-SOURCE/tools/configure/providers/common.py)中的冻结 " +"`Construction` 记录,明确 `state_type`、`definitions`、`initialize`、`stop`、`ret" +"ain_on_failed_init`、`exports` 和 `pin_snapshots`。公共 emitter " +"消费这些决策,不从系列名称重建状态类型或初始化失败责任。`initialize` 是不可变 `DirectCall`;`stop` 是这些调用的" +" tuple。参数只能是显式静态 `Symbol` 或 `Address`、uint32 常量、布尔值、NULL,或构造绝对期限等嵌套直接调用。" +"任意表达式、强制转换、赋值以及函数指针/成员调用表达式均拒绝。`definitions`、`exports` 和 " +"`face_definitions` 仍是 C 源码字符串,可包含选定辅助函数体。真实 C 编译器建立符号与参数类型。这是窄 Python " +"生成合同,不是完整 C AST;这些 Python 记录不会向 MCU 镜像添加额外的运行时元数据或分发。" msgid "" "The family owns numerical limits, supported modes, exact routes and " @@ -154,6 +170,92 @@ msgstr "" "家族负责数值限制、支持模式、精确路线与构造语义;resolver 负责严格字段、不可变输入和资源冲突;CMake " "负责源码选择和目标依赖。必须保留这些独立权威:生成器不得创建第二份源码构建图,配置语法正确也不能表示尚未实现的硬件已经可用。" +msgid "Selected immutable I2C V2 configuration" +msgstr "选定 I2C V2 的不可变配置" + +msgid "The selected G474, H743 and U575 polling I2C V2 providers separate the private `nx_i2c_v2_config_t` from `nx_i2c_v2_state_t`. The generated readonly configuration holds register/reset addresses, the reset mask, timing, finite polling budget and the explicitly selected BERR and one-byte RX reload quirks. Zero-initialized runtime storage keeps a configuration pointer and `initialized`, `retained`, `active` and `fault` state. Public faces, shared ops and factory lookup stay the same. This split is not a measured Flash or RAM saving." +msgstr "选定 G474、H743 与 U575 轮询 I2C V2 provider 将私有 `nx_i2c_v2_config_t` 与 `nx_i2c_v2_state_t` 分开。生成的只读配置保存寄存器/复位地址、复位掩码、timing、有限轮询预算,以及显式选择的 BERR 和单字节 RX reload 勘误选项。零初始化的运行时存储保留配置指针和 `initialized`、`retained`、`active`、`fault` 状态。公共 face、共享 ops 和 factory 查找保持不变。该拆分不等于已经测得 Flash 或 RAM 节省。" + +msgid "The private `nx_i2c_v2_bind` runs under the serialized privileged task owner with interrupts unmasked. It validates the configuration and rejects overlap with runtime or hardware storage. An initialized, retained or active owner rejects rebinding without mutation; successful binding itself has no hardware effects. The immutable configuration must live as long as its runtime owner; successful stop releases hardware ownership but preserves the binding. Do not reset a live owner merely to replace its configuration." +msgstr "私有 `nx_i2c_v2_bind` 在串行化的特权 Task owner 下执行,且要求中断未屏蔽。它校验配置,并拒绝与运行时或硬件存储重叠。已 initialized、retained 或 active 的 owner 在不修改状态的前提下拒绝重新绑定;成功绑定本身没有硬件效果。不可变配置必须与运行时 owner 同寿命;成功 stop 释放硬件所有权,但保留绑定。不得只为替换配置而重置仍在使用的 owner。" + +msgid "Concrete G474/H743 `i2c_prepare` entries bind first, then prepare pins and initialize the engine. U575's separate mutable clock owner rejects a retained clock before binding, and acquires the selected clock before engine initialization. Its `i2c_close` stops the engine first; it releases the clock only after that stop succeeds. Failed drain retains the relevant runtime and clock responsibility for explicit cleanup or retry." +msgstr "具体 G474/H743 `i2c_prepare` 入口先绑定,再准备引脚并初始化引擎。U575 独立的可变 clock owner 在绑定前拒绝 retained clock,并在引擎初始化前取得所选时钟。其 `i2c_close` 先停止引擎;只有该 stop 成功后才释放时钟。排空失败保留相关运行时与时钟责任,供显式清理或重试。" + +msgid "The family emitter now supplies static configuration and calls these concrete C entries. The existing `Construction` record and generated Platform still order resource phases, controller starts and reverse cleanup. Other peripheral modes retain their corresponding family emission paths; this selected refactor does not move every platform lifecycle into a new common abstraction." +msgstr "家族 emitter 现在提供静态配置并调用这些具体 C 入口。既有 `Construction` record 与生成的 Platform 仍编排资源阶段、控制器启动和逆序清理。其他外设模式保留各自的家族生成路径;本次选定重构不会把所有平台生命周期移入新的公共抽象。" + +msgid "See the [private shared contract](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/i2c/i2c_v2/i2c_v2.h) and the [U575 integration](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/stm32/stm32u575/drivers/i2c.c) for exact parameters and failure ordering. The {download}`refactor ledger ` separates focused software evidence, final linked cost comparison and physical execution." +msgstr "精确参数与失败顺序见[私有共享合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/i2c/i2c_v2/i2c_v2.h)及 [U575 集成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/stm32/stm32u575/drivers/i2c.c)。{download}`重构台账 `分别记录聚焦软件证据、最终链接成本比较和物理执行。" + +msgid "Selected immutable UART DMA configuration" +msgstr "选定 UART DMA 的不可变配置" + +msgid "The selected F103 and GD303 UART DMA modes use `nx_uart_v1_dma_config_t` for register/channel addresses, DMA memory domains, IRQ withdrawal and wake-validation callbacks, byte-ring or duplex receive storage, capacity, baud, IRQ policy and channel shifts. The selected profile identifies the active storage union member. `nx_uart_v1_dma_state_t` retains the configuration pointer, deadlines, active request and wake references, ring cursors, publication state and lifecycle flags. Duplex embeds its own mutable receive extension; byte mode does not reserve one. Configuration and its referenced resources have the runtime owner's lifetime. Successful stop preserves the binding, and zero-initialized runtime binds only during cold admission." +msgstr "选定 F103 与 GD303 UART DMA 模式使用 `nx_uart_v1_dma_config_t` 保存寄存器/channel 地址、DMA 内存域、IRQ 撤下与 wake 校验 callback、字节 ring 或 duplex receive 存储、容量、波特率、IRQ 策略和 channel shift。所选 profile 标识存储 union 的有效成员。`nx_uart_v1_dma_state_t` 保留配置指针、deadline、active request 与 wake 引用、ring 游标、发布状态和生命周期标志。Duplex 内嵌自身可变 receive 扩展;字节模式不预留该扩展。配置及其引用资源与运行时 owner 同寿命。成功 stop 保留绑定,零初始化运行时仅在冷态准入期间绑定。" + +msgid "Each family's concrete `uart_dma_prepare` and `uart_duplex_prepare` requires a serialized privileged task with interrupts unmasked. After null/profile checks, it checks the existing cold owner before preparing exact pins and calling the initializer. Retained hardware, active requests, wake publishers and duplex RX loans prevent rebinding; the candidate receive extension is checked even before a configuration is bound. Failed withdrawal retains the same configuration and outstanding loans for ordinary stop or retry. The shared readonly ops, public faces and direct IRQ symbols remain unchanged; no hot-path dispatch layer is added." +msgstr "每个家族的具体 `uart_dma_prepare` 与 `uart_duplex_prepare` 要求串行化特权 Task,且中断未屏蔽。在 null/profile 检查后,先检查既有冷态 owner,再准备精确引脚并调用 initializer。Retained hardware、active request、wake publisher 与 duplex RX loan 阻止重新绑定;即使尚未绑定配置,也会检查候选 receive 扩展。撤下失败保留同一配置及未归还 loan,供普通 stop 或重试。共享只读 ops、公共 face 与直接 IRQ symbol 保持不变;不增加热路径 dispatch 层。" + +msgid "The family's frozen `LegacyTransportProfile` supplies exact wiring to the existing emitter without family-name fallback. For these UART modes the selected `Construction.initialize` calls concrete C preparation; generation supplies static configuration, faces and IRQ forwarding. AHB DMA gates and Platform phase ordering remain in the existing SoC emission and shared template. Legacy I2C V1 and other peripheral cold paths retain their selected generated behavior." +msgstr "家族冻结的 `LegacyTransportProfile` 向既有 emitter 提供精确 wiring,不使用家族名称 fallback。对于这些 UART 模式,所选 `Construction.initialize` 调用具体 C prepare;生成提供静态配置、face 与 IRQ forwarding。AHB DMA gate 与 Platform phase 顺序保留在既有 SoC emission 和共享 template 中。Legacy I2C V1 与其他外设冷态路径保留各自选定的生成行为。" + +msgid "See the [UART private contract](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.h) for the exact storage, callback and lifetime requirements. Host tests exercise the actual SoC wrapper, shared engine and GPIO model; generated factory validation and final ARM resource measurements remain separate evidence scopes." +msgstr "精确存储、callback 与寿命要求见 [UART 私有合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.h)。主机测试执行实际 SoC wrapper、共享 engine 与 GPIO model;生成 factory 校验和最终 ARM 资源测量仍是独立证据范围。" + + +msgid "Selected legacy cold integration" +msgstr "选定 legacy 冷态集成" + +msgid "F103 and GD303 keep concrete ADC, PWM and Flash `prepare` entries in their respective `drivers/devices.c`. These privileged task entries require interrupts unmasked and reject the relevant live, retained or faulted owner before replacing its resource binding. The exact C adapter owns register addresses, fixed pin and gate relationships, system-clock state and chip-specific cold checks. Family generation still validates the selected Board/mode and supplies its chosen values. The existing shared `nx_legacy_*_close` paths retain stop and drain responsibility." +msgstr "F103 与 GD303 在各自 `drivers/devices.c` 保留具体 ADC、PWM 和 Flash `prepare` 入口。这些特权 Task 入口要求中断未屏蔽,并在替换资源绑定前拒绝相关 live、retained 或 faulted owner。精确 C adapter 拥有寄存器地址、固定引脚与 gate 关系、system-clock 状态和芯片特定冷态检查。家族生成仍校验所选 Board/模式并提供选定数值。既有共享 `nx_legacy_*_close` 路径保留 stop 与 drain 责任。" + +msgid "" +"The ADC and PWM owners now reference their own readonly hardware " +"descriptions, `nx_legacy_adc_config_t` and `nx_legacy_pwm_config_t`, " +"supplied by each exact SoC. Register addresses, gates and fixed pad " +"facts move out of mutable owner storage. ADC reference voltage and PWM " +"tick/period/duty/inactive level remain selected owner state; they are " +"not promoted into shared hardware facts. Configuration must outlive the " +"owner and cannot overlap its state or API output storage. This does not " +"turn these drivers into the I2C V2 type; Flash keeps its separate " +"private contract. Actual linked costs depend on the exact selected " +"image." +msgstr "" +"ADC 和 PWM owner 现在引用各精确 SoC 提供的只读硬件描述 `nx_legacy_adc_config_t` 与 " +"`nx_legacy_pwm_config_t`。寄存器地址、gate 和固定 pad 事实移出可变 owner 存储。ADC 参考电压及 " +"PWM tick/period/duty/inactive level 仍为选定 owner 状态,不提升为共享硬件事实。配置生命周期必须覆盖 " +"owner,且不能与其 state 或 API 输出存储重叠。这不会把驱动变成 I2C V2 类型;Flash " +"保留独立私有合同。实际链接成本取决于精确选定镜像。" + +msgid "" +"Selected U575 USART1 `dma-tx` and `dma-chain-tx` use static cold " +"configuration and concrete C preparation. Their C adapters own " +"admission, binding and pin preparation before existing finite/chain " +"initialization. Selected F407 SPI polling/DMA and paired-controller DMA," +" plus GD470 SPI0/SPI4 polling and finite full-duplex DMA, move " +"prepare/close behavior into concrete C while preserving the same " +"Platform snapshot slots and retained cleanup responsibility. GD470 also " +"moves its conditional EXTI close into C. The hot transfer engines, " +"public faces and direct IRQ ownership are unchanged." +msgstr "" +"选定 U575 USART1 `dma-tx` 与 `dma-chain-tx` 使用静态冷态配置和具体 C prepare。C 适配器在既有 " +"finite/chain 初始化前负责准入、绑定与引脚准备。选定 F407 SPI 轮询/DMA、成对控制器 DMA,以及 GD470 " +"SPI0/SPI4 轮询和有限全双工 DMA,将 prepare/close 行为移入具体 C,同时保留原 Platform snapshot " +"槽及失败后保留的清理责任。GD470 的条件 EXTI close 也迁入 C。热路径传输引擎、公共 face 和直接 IRQ 所有权不变。" + +msgid "" +"This is a bounded cold-path migration. Other GPIO, IRQ UART, ADC, PWM, " +"EXTI and additional mode-specific paths may still emit C behavior in " +"family helpers. Review each selected mode's real start/stop path; " +"neither a typed call field nor a new directory establishes that all " +"lifecycle behavior has moved out of the generator. Physical timing and " +"final software qualification remain separate." +msgstr "" +"这是有界冷态迁移。其他 GPIO、IRQ UART、ADC、PWM、EXTI 以及附加模式专用路径仍可能在系列辅助函数中生成 C " +"行为。必须审查每个选定模式的真实 start/stop " +"路径;类型化调用字段或新目录都不能证明全部生命周期行为已移出生成器。物理时序与最终软件资格分别建立。" + msgid "Conditions for full HAL transport" msgstr "完整 HAL transport 的接入条件" @@ -415,49 +517,3 @@ msgid "" msgstr "" "Bootloader 交接、动态变频、deep STOP、更广的 H743 DMA 和通用 U575 跨域 I/O,继续保留[SoC " "完善台账](soc-refinement.md)中各自未实现或部分实现的退出条件。官方驱动不会自动提供这些功能需要的平台合同。" - -# CMSIS6 actual-POT delta; independent review pending. -msgid "CMSIS Device pairing is separate from the shared Arm CMSIS-Core source. The Core migration pins official v6.4.0 for the seven maintained STM32/GD32 family entries while keeping their Device/startup, HAL/LL and imported vendor bytes. The internal `nexus_cmsis_core` target supplies CPU headers; it does not change public Nexus types, Arch context/ownership contracts or the selected peripheral state machine. [Vendor versions](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md) distinguishes the release, Core version macros, literal pack metadata and license identities. Actual include closure, firmware links and installed-SDK consumption require current-source evidence; a common Core pin does not transfer F4 qualification to G4, H7, U5 or GD32." -msgstr "CMSIS Device 配对与共享的 Arm CMSIS-Core 来源分别维护。Core 迁移为七个维护中的 STM32/GD32 family 入口锁定官方 v6.4.0,同时保持各自的 Device/startup、HAL/LL 和供应商导入字节。内部 `nexus_cmsis_core` 目标提供 CPU 头,不改变公共 Nexus 类型、Arch 上下文/所有权合同或已选外设状态机。[供应商版本](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/vendors/VERSIONS.md)分别记录发布版本、Core 版本宏、原始 pack 元数据和许可证身份。实际 include 闭包、固件链接和安装后 SDK 消费都需要当前源码的执行证据;使用同一 Core pin 不会把 F4 的资格转移给 G4、H7、U5 或 GD32。" - -msgid "Selected immutable I2C V2 configuration" -msgstr "选定 I2C V2 的不可变配置" - -msgid "The selected G474, H743 and U575 polling I2C V2 providers separate the private `nx_i2c_v2_config_t` from `nx_i2c_v2_state_t`. The generated readonly configuration holds register/reset addresses, the reset mask, timing, finite polling budget and the explicitly selected BERR and one-byte RX reload quirks. Zero-initialized runtime storage keeps a configuration pointer and `initialized`, `retained`, `active` and `fault` state. Public faces, shared ops and factory lookup stay the same. This split is not a measured Flash or RAM saving." -msgstr "选定 G474、H743 与 U575 轮询 I2C V2 provider 将私有 `nx_i2c_v2_config_t` 与 `nx_i2c_v2_state_t` 分开。生成的只读配置保存寄存器/复位地址、复位掩码、timing、有限轮询预算,以及显式选择的 BERR 和单字节 RX reload 勘误选项。零初始化的运行时存储保留配置指针和 `initialized`、`retained`、`active`、`fault` 状态。公共 face、共享 ops 和 factory 查找保持不变。该拆分不等于已经测得 Flash 或 RAM 节省。" - -msgid "Concrete G474/H743 `i2c_prepare` entries bind first, then prepare pins and initialize the engine. U575's separate mutable clock owner rejects a retained clock before binding, and acquires the selected clock before engine initialization. Its `i2c_close` stops the engine first; it releases the clock only after that stop succeeds. Failed drain retains the relevant runtime and clock responsibility for explicit cleanup or retry." -msgstr "具体 G474/H743 `i2c_prepare` 入口先绑定,再准备引脚并初始化引擎。U575 独立的可变 clock owner 在绑定前拒绝 retained clock,并在引擎初始化前取得所选时钟。其 `i2c_close` 先停止引擎;只有该 stop 成功后才释放时钟。排空失败保留相关运行时与时钟责任,供显式清理或重试。" - -msgid "The family emitter now supplies static configuration and calls these concrete C entries. The existing `Construction` record and generated Platform still order resource phases, controller starts and reverse cleanup. Other peripheral modes retain their corresponding family emission paths; this selected refactor does not move every platform lifecycle into a new common abstraction." -msgstr "家族 emitter 现在提供静态配置并调用这些具体 C 入口。既有 `Construction` record 与生成的 Platform 仍编排资源阶段、控制器启动和逆序清理。其他外设模式保留各自的家族生成路径;本次选定重构不会把所有平台生命周期移入新的公共抽象。" - -msgid "See the [private shared contract](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/i2c/i2c_v2/i2c_v2.h) and the [U575 integration](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/stm32/stm32u575/drivers/i2c.c) for exact parameters and failure ordering. The {download}`refactor ledger ` separates focused software evidence, final linked cost comparison and physical execution." -msgstr "精确参数与失败顺序见[私有共享合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/i2c/i2c_v2/i2c_v2.h)及 [U575 集成](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/soc/stm32/stm32u575/drivers/i2c.c)。{download}`重构台账 `分别记录聚焦软件证据、最终链接成本比较和物理执行。" - -msgid "Selected legacy cold integration" -msgstr "选定 legacy 冷态集成" - -msgid "F103 and GD303 keep concrete ADC, PWM and Flash `prepare` entries in their respective `drivers/devices.c`. These privileged task entries require interrupts unmasked and reject the relevant live, retained or faulted owner before replacing its resource binding. The exact C adapter owns register addresses, fixed pin and gate relationships, system-clock state and chip-specific cold checks. Family generation still validates the selected Board/mode and supplies its chosen values. The existing shared `nx_legacy_*_close` paths retain stop and drain responsibility." -msgstr "F103 与 GD303 在各自 `drivers/devices.c` 保留具体 ADC、PWM 和 Flash `prepare` 入口。这些特权 Task 入口要求中断未屏蔽,并在替换资源绑定前拒绝相关 live、retained 或 faulted owner。精确 C adapter 拥有寄存器地址、固定引脚与 gate 关系、system-clock 状态和芯片特定冷态检查。家族生成仍校验所选 Board/模式并提供选定数值。既有共享 `nx_legacy_*_close` 路径保留 stop 与 drain 责任。" - -msgid "This moves the selected cold integration out of generated statement sequences; it does not turn the legacy drivers into the I2C V2 configuration type. ADC, PWM and Flash retain their separate private contracts. Other GPIO, IRQ UART, SPI, watchdog and EXTI paths can still contain concrete behavior emitted by their family adapter. Review each selected mode's real start/stop path rather than inferring a platform-wide lifecycle change from the directory layout." -msgstr "这将选定冷态集成移出生成语句序列;不会把 legacy driver 转成 I2C V2 配置类型。ADC、PWM 和 Flash 保留各自的私有合同。其他 GPIO、IRQ UART、SPI、watchdog 与 EXTI 路径仍可包含家族 adapter 生成的具体行为。应复核每个所选模式的实际 start/stop 路径,不得由目录布局推断整个平台的生命周期变更。" - -msgid "The private `nx_i2c_v2_bind` runs under the serialized privileged task owner with interrupts unmasked. It validates the configuration and rejects overlap with runtime or hardware storage. An initialized, retained or active owner rejects rebinding without mutation; successful binding itself has no hardware effects. The immutable configuration must live as long as its runtime owner; successful stop releases hardware ownership but preserves the binding. Do not reset a live owner merely to replace its configuration." -msgstr "私有 `nx_i2c_v2_bind` 在串行化的特权 Task owner 下执行,且要求中断未屏蔽。它校验配置,并拒绝与运行时或硬件存储重叠。已 initialized、retained 或 active 的 owner 在不修改状态的前提下拒绝重新绑定;成功绑定本身没有硬件效果。不可变配置必须与运行时 owner 同寿命;成功 stop 释放硬件所有权,但保留绑定。不得只为替换配置而重置仍在使用的 owner。" - -msgid "Selected immutable UART DMA configuration" -msgstr "选定 UART DMA 的不可变配置" - -msgid "The selected F103 and GD303 UART DMA modes use `nx_uart_v1_dma_config_t` for register/channel addresses, DMA memory domains, IRQ withdrawal and wake-validation callbacks, byte-ring or duplex receive storage, capacity, baud, IRQ policy and channel shifts. The selected profile identifies the active storage union member. `nx_uart_v1_dma_state_t` retains the configuration pointer, deadlines, active request and wake references, ring cursors, publication state and lifecycle flags. Duplex embeds its own mutable receive extension; byte mode does not reserve one. Configuration and its referenced resources have the runtime owner's lifetime. Successful stop preserves the binding, and zero-initialized runtime binds only during cold admission." -msgstr "选定 F103 与 GD303 UART DMA 模式使用 `nx_uart_v1_dma_config_t` 保存寄存器/channel 地址、DMA 内存域、IRQ 撤下与 wake 校验 callback、字节 ring 或 duplex receive 存储、容量、波特率、IRQ 策略和 channel shift。所选 profile 标识存储 union 的有效成员。`nx_uart_v1_dma_state_t` 保留配置指针、deadline、active request 与 wake 引用、ring 游标、发布状态和生命周期标志。Duplex 内嵌自身可变 receive 扩展;字节模式不预留该扩展。配置及其引用资源与运行时 owner 同寿命。成功 stop 保留绑定,零初始化运行时仅在冷态准入期间绑定。" - -msgid "Each family's concrete `uart_dma_prepare` and `uart_duplex_prepare` requires a serialized privileged task with interrupts unmasked. After null/profile checks, it checks the existing cold owner before preparing exact pins and calling the initializer. Retained hardware, active requests, wake publishers and duplex RX loans prevent rebinding; the candidate receive extension is checked even before a configuration is bound. Failed withdrawal retains the same configuration and outstanding loans for ordinary stop or retry. The shared readonly ops, public faces and direct IRQ symbols remain unchanged; no hot-path dispatch layer is added." -msgstr "每个家族的具体 `uart_dma_prepare` 与 `uart_duplex_prepare` 要求串行化特权 Task,且中断未屏蔽。在 null/profile 检查后,先检查既有冷态 owner,再准备精确引脚并调用 initializer。Retained hardware、active request、wake publisher 与 duplex RX loan 阻止重新绑定;即使尚未绑定配置,也会检查候选 receive 扩展。撤下失败保留同一配置及未归还 loan,供普通 stop 或重试。共享只读 ops、公共 face 与直接 IRQ symbol 保持不变;不增加热路径 dispatch 层。" - -msgid "The family's frozen `LegacyTransportProfile` supplies exact wiring to the existing emitter without family-name fallback. For these UART modes the selected `Construction.initialize` calls concrete C preparation; generation supplies static configuration, faces and IRQ forwarding. AHB DMA gates and Platform phase ordering remain in the existing SoC emission and shared template. Legacy I2C V1 and other peripheral cold paths retain their selected generated behavior." -msgstr "家族冻结的 `LegacyTransportProfile` 向既有 emitter 提供精确 wiring,不使用家族名称 fallback。对于这些 UART 模式,所选 `Construction.initialize` 调用具体 C prepare;生成提供静态配置、face 与 IRQ forwarding。AHB DMA gate 与 Platform phase 顺序保留在既有 SoC emission 和共享 template 中。Legacy I2C V1 与其他外设冷态路径保留各自选定的生成行为。" - -msgid "See the [UART private contract](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.h) for the exact storage, callback and lifetime requirements. Host tests exercise the actual SoC wrapper, shared engine and GPIO model; generated factory validation and final ARM resource measurements remain separate evidence scopes." -msgstr "精确存储、callback 与寿命要求见 [UART 私有合同](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/drivers/uart/uart_v1_channel_dma/uart_v1_channel_dma.h)。主机测试执行实际 SoC wrapper、共享 engine 与 GPIO model;生成 factory 校验和最终 ARM 资源测量仍是独立证据范围。" diff --git a/docs/locale/zh_CN/LC_MESSAGES/design/source-compatibility.po b/docs/locale/zh_CN/LC_MESSAGES/design/source-compatibility.po index daef0d18b..3f90ede90 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/design/source-compatibility.po +++ b/docs/locale/zh_CN/LC_MESSAGES/design/source-compatibility.po @@ -1,4 +1,4 @@ -# Same-source-language companion for mixed prose; independent review pending. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: c47d1abb01a83b8381f55a90a8867e31dbc46c564537f14ff48e4bda2200a9be\n" +"X-Nexus-Source-SHA256: f44842ae8941b596eb264d54755503c100356c58ede683468ad7c5652f38f372\n" msgid "源码版本、factory 代际与依赖维护" msgstr "源码版本、factory 代际与依赖维护" @@ -78,14 +78,14 @@ msgstr "`2`" msgid "简化的 TOML 选择面;不改变 Board/SoC 事实" msgstr "简化的 TOML 选择面;不改变 Board/SoC 事实" -msgid "projection / typed IR" -msgstr "投影/类型化 IR" +msgid "resolved JSON projection / typed IR" +msgstr "resolved JSON 投影/typed IR" -msgid "`1` / `1`" -msgstr "`1` / `1`" +msgid "`1` / `2`" +msgstr "`1` / `2`" -msgid "严格 resolver 与实际 emitter 的源码格式" -msgstr "严格 resolver 与实际 emitter 的源码格式" +msgid "单向 JSON 输出格式与私有 Python 类型化源码接口分别计代" +msgstr "单向 JSON 输出格式与私有 Python 类型化源码接口分别计代" msgid "Board / SoC / routes" msgstr "Board/SoC/路由" @@ -117,6 +117,17 @@ msgstr "" "明确维护了新的格式,并更新策略断言与负测。策略不能 使尚未实现的 schema 获得支持。SDK manifest 增加一个被 file-map " "覆盖的源码文件, 不等于修改 manifest 格式。" +msgid "" +"类型化源码接口第 2 代只接受严格 TOML 解析后的不可变装配对象;生产 resolver、 factory 和 emitter " +"使用具名字段,不提供字典接口或 schema-1 JSON 装配兼容。 `resolved.json` 仍是第 1 " +"版单向输出,用于审计和独立产物检查,不是可信配置入口。 `source_epoch` 是 Python 类级合同,不写入 JSON,也不增加 " +"MCU 状态或改变公共 C factory 接口。维护工具直接观察实际 IR 类的两项声明,并核对唯一版本策略。" +msgstr "" +"类型化源码接口第 2 代只接受严格 TOML 解析后的不可变装配对象;生产 resolver、 factory 和 emitter " +"使用具名字段,不提供字典接口或 schema-1 JSON 装配兼容。 `resolved.json` 仍是第 1 " +"版单向输出,用于审计和独立产物检查,不是可信配置入口。 `source_epoch` 是 Python 类级合同,不写入 JSON,也不增加 " +"MCU 状态或改变公共 C factory 接口。维护工具直接观察实际 IR 类的两项声明,并核对唯一版本策略。" + msgid "" "当前 `0.2.0` 用于开发与软件候选准备,不表示已公开发行或已通过候选验收。 本次 semantic version 变化保留 epoch " "`1`、exact 匹配和 authored schema `2`; 版本值不把旧 SDK/候选的资格转移到新源码。新候选仍须独立绑定实际 clean " diff --git a/docs/locale/zh_CN/LC_MESSAGES/development/workflow.po b/docs/locale/zh_CN/LC_MESSAGES/development/workflow.po index 9931e4bda..99f062aa9 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/development/workflow.po +++ b/docs/locale/zh_CN/LC_MESSAGES/development/workflow.po @@ -1,4 +1,4 @@ -# Authored translation; independent semantic review pending. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source docs\n" @@ -8,7 +8,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: c711089cbe43ec0a8e661af4a3faf17fd2281993f111982b2f7222dd9fca8f47\n" +"X-Nexus-Source-SHA256: 92b2768858bcffa19e1f36393d69023c5a2cf540edda6e5b5c2009f2434feb44\n" #: ../../../../tmp/nexus-quality-20261019/docs/development/workflow.md:9 msgid "Develop with TDD and normal commit gates" @@ -108,6 +108,48 @@ msgstr "" "filter/shard。空、跳过、部分、陈旧或变化输入均失败。钩子核对暂存树保持不变;CI 独立执行门禁。框架仅用于主机 C++17;固件保持 " "C11。自动化证明执行,保留的开发日志记录历史 RED/GREEN 顺序。纯正文或图源修改需要对应文档检查和语义审查,不需要镜像实现测试。" +msgid "Associate a complete local software plan with a pull request" +msgstr "将完整本地软件计划关联到 Pull Request" + +msgid "" +"When GitHub Actions is unavailable, `tools/maintenance/local_ci.py` " +"checks an existing execution of the maintained " +"`tools/dev/plans/software.json`. The default mode is read-only: it " +"verifies actual complete stages, command receipts, clean source, inputs " +"and the same plan before reporting the exact source and scope. It does " +"not run the plan or convert interrupted or partial reports into success." +msgstr "" +"GitHub Actions 不可用时,`tools/maintenance/local_ci.py` 校验维护中的 " +"`tools/dev/plans/software.json` " +"的既有执行。默认只读:报告精确源码与范围前,检查实际完整阶段、命令收条、干净源码、输入以及同一计划。它不运行计划,也不把中断或部分报告改成成功。" + +msgid "" +"Explicit `--publish` uses an already configured authorized GitHub " +"credential and an existing open PR. It checks the PR head/base twice and" +" publishes only to the verified head SHA under " +"`nexus/local-software-plan`; a later head cannot inherit that status. " +"Its retained receipt distinguishes pending, confirmed and unconfirmed " +"publication. Observations do not form an atomic GitHub merge " +"transaction." +msgstr "" +"显式 `--publish` 使用已配置且获授权的 GitHub 凭据和既有开放 PR。它两次核对 PR head/base,只向已验证 " +"head SHA 发布 `nexus/local-software-plan`;后来的 head 不能继承该状态。保留的收条区分 " +"pending、confirmed 和 unconfirmed 发布。两次观察不构成原子的 GitHub 合并事务。" + +msgid "" +"This status establishes only the maintained local plan scope. It is not " +"a GitHub Actions job, a sealed software candidate, a configured branch " +"rule or a product release. Server rules need their own real readback; " +"independent agent review does not invent a CODEOWNERS account or a human" +" approval. The existing release workflow still requires its declared " +"final-main CI jobs and trusted publication checks; a local status cannot" +" silently replace those requirements." +msgstr "" +"此状态只证明维护中的本地计划范围。它不是 GitHub Actions " +"job、封存软件候选、已配置分支规则或产品发布。服务器规则需单独实际回读;独立 agent 审查不虚构 CODEOWNERS " +"账号或人工批准。现有 release workflow 仍要求其声明的最终 main CI jobs " +"和可信发布检查;本地状态不能悄悄替代这些要求。" + #: ../../../../tmp/nexus-quality-20261019/docs/development/workflow.md:70 msgid "Keep feedback fast and evidence complete" msgstr "快速反馈与完整证据" diff --git a/docs/locale/zh_CN/LC_MESSAGES/explanation/platform-architecture.po b/docs/locale/zh_CN/LC_MESSAGES/explanation/platform-architecture.po index dd7e93296..3b9721dc4 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/explanation/platform-architecture.po +++ b/docs/locale/zh_CN/LC_MESSAGES/explanation/platform-architecture.po @@ -1,4 +1,4 @@ -# Authored translation; review state is explicit in catalog metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 194da1156f50c74ece168a0998e57343bf15650c3dbee8c7aaeda0f8cba7d327\n" +"X-Nexus-Source-SHA256: 55aec84366db512be2ec6b95279f9a9b87e2eef4a60bafe9776b6821a82eefdd\n" msgid "How the platform is assembled and executed" msgstr "平台如何装配与执行" @@ -92,18 +92,135 @@ msgstr "外部产品" msgid "Main, workers, roles, private PCB, budgets, partitions and recovery" msgstr "main、执行任务、角色、私有 PCB、预算、分区与恢复" +#: docs/explanation/platform-architecture.md:33 +msgid "Current source organization" +msgstr "当前源码组织" + +#: docs/explanation/platform-architecture.md:35 +msgid "" +"The source tree groups MCU families by vendor and CPU implementations by " +"instruction-set architecture. These directories describe ownership at build " +"time; they add no runtime dispatch or lookup layer. Public include paths, " +"target names and authored family identifiers remain stable." +msgstr "" +"源码树按厂家组织 MCU 系列,按指令集架构组织 CPU " +"实现。这些目录表达构建期所有权,不增加运行时分派或查找层。公共头文件路径、目标名称和人工配置中的系列标识保持稳定。" + +msgid "The [source-adjacent contracts](source-map.md) own the directory map and layer README content. Native models implement the same public I/O contracts under the Native SoC package. Shared register-protocol algorithms live under `drivers///` only when the participating wrappers independently establish their silicon semantics. Selected SoC targets compile these private sources directly; the directory adds no runtime layer to FIG-01. Cross-vendor SoC collaboration remains in `soc/common/`, while STM32 identity access remains under `soc/stm32/common/`." +msgstr "[源码相邻合同](source-map.md) 维护目录映射和各层 README 正文。Native 模型在 Native SoC 包下实现相同的公共 I/O 合同。共享寄存器协议算法只有在参与的 wrapper 分别确认其硅片语义后,才位于 `drivers///`。所选 SoC target 直接编译这些私有源文件;目录不会给 FIG-01 增加运行时层。跨厂家 SoC 协作保留在 `soc/common/`,STM32 identity 访问保留在 `soc/stm32/common/`。" + +msgid "Each family's `configure/` package owns its capability policy, constructors and lifecycle emission; Native uses `soc/native/configure/`. The explicit provider map and shared leaf contracts remain in `tools/configure/providers/`. Common parsing, resource arbitration, typed IR and atomic publication stay in `tools/configure/`. Configuration tests stay under `tools/configure/tests/providers//`. These are build-time Python boundaries, independent of the C execution path." +msgstr "每个家族的 `configure/` 包拥有能力策略、构造器与生命周期生成;Native 使用 `soc/native/configure/`。显式 provider map 与共享叶子合同保留在 `tools/configure/providers/`。通用解析、资源仲裁、typed IR 和原子发布仍在 `tools/configure/`。配置测试仍在 `tools/configure/tests/providers//`。这些是构建期 Python 边界,独立于 C 执行路径。" + + +#: docs/explanation/platform-architecture.md:46 +msgid "" +"Common components and selected STM32 extensions have distinct source " +"ownership. Components are explicit targets; a chip-specific target still " +"requires its exact SoC, mode and backend, and its presence does not extend " +"the public support matrix." +msgstr "" +"通用组件与选定的 STM32 扩展具有不同的源码所有权。组件都是显式目标;芯片专用目标仍要求精确 SoC、模式和后端,其存在不会扩大公共支持矩阵。" + +#: docs/explanation/platform-architecture.md:51 +msgid "Platform and CPU-only Runtime" +msgstr "Platform 与仅 CPU 的 Runtime" + +#: docs/explanation/platform-architecture.md:0 +msgid "Consumption entry" +msgstr "消费入口" + +#: docs/explanation/platform-architecture.md:0 +msgid "Provided assembly" +msgstr "提供的装配" + +#: docs/explanation/platform-architecture.md:0 +msgid "External responsibility" +msgstr "外部责任" + +#: docs/explanation/platform-architecture.md:0 +msgid "`Nexus::Platform`" +msgstr "`Nexus::Platform`" + +#: docs/explanation/platform-architecture.md:0 +msgid "" +"Exact SoC/Board facts, selected providers, static factory and explicit " +"lifecycle" +msgstr "精确 SoC/Board 事实、所选 provider、静态 factory 与显式生命周期" + +#: docs/explanation/platform-architecture.md:0 +msgid "Product sources, workers, budgets, storage regions and recovery" +msgstr "产品源码、执行任务、预算、存储区域与恢复" + +#: docs/explanation/platform-architecture.md:0 +msgid "`Nexus::Runtime`" +msgstr "`Nexus::Runtime`" + +#: docs/explanation/platform-architecture.md:0 +msgid "Core, Arch, public I/O faces and the explicitly selected OS adapter" +msgstr "Core、Arch、公共 I/O 接口面与显式选择的 OS adapter" + +#: docs/explanation/platform-architecture.md:0 +msgid "" +"Startup, vectors, memory/clock facts, Board wiring and concrete device " +"providers" +msgstr "启动、向量、内存/时钟事实、Board 接线及具体设备 provider" + +#: docs/explanation/platform-architecture.md:58 +msgid "" +"One build directory admits one resolved assembly. Use separate build " +"directories for different boards or CPU/security images; a product build " +"coordinator or CI matrix owns their orchestration. Runtime and Platform " +"reuse the same production mechanisms but are separate consumption modes." +msgstr "" +"一个构建目录只接纳一份解析后的装配。不同板卡或 CPU/安全域镜像使用独立构建目录,由产品构建协调器或 CI 矩阵编排。Runtime 与 " +"Platform 复用相同生产机制,但属于不同的消费模式。" + +#: docs/explanation/platform-architecture.md:63 +msgid "" +"The common OS boundary is the wait/wake port. Task, queue, join and storage " +"APIs remain backend-specific, including the real kernel control-block types." +" Products switching kernels must adapt that lifecycle explicitly; no " +"universal object pool or hidden scheduling task is supplied." +msgstr "" +"共同 OS 边界是等待/唤醒端口。任务、队列、join 和存储 API " +"仍为后端专用,包括真实内核控制块类型。产品切换内核时必须显式适配生命周期;平台不提供统一对象池或隐藏调度任务。" + msgid "Build-time decisions and runtime work" msgstr "构建期决策与运行时工作" msgid "" -"The resolver combines authored TOML with reviewed SoC/Board facts into " -"immutable typed IR and an atomic bundle. It validates modes, resources, " -"clocks, IRQs, ABI, DMA reachability and budgets. CMake owns the source " -"dependency graph; the resolver does not discover software from device names " -"or generate product workers." +"TOML schema 2 is the only authored platform input. The parser freezes " +"user intent as `AuthoredAssembly`; `resolve_ir()` combines it with " +"reviewed SoC/Board facts and validates modes, resources, clocks, IRQs, " +"ABI, DMA reachability and budgets. It returns `ConfigurationIR` " +"directly. Named frozen records hold each decision once; they do not " +"expose a Mapping facade or a duplicate `values` store. Hardware fact " +"leaves and keyed indexes still use immutable mappings, and local " +"resolver validation drafts may use dictionaries. These are explicit data" +" boundaries, not a promise to assign a separate nominal type to every " +"JSON field." +msgstr "" +"TOML schema 2 是平台唯一的人工编写输入。解析器将用户意图冻结为 `AuthoredAssembly`;`resolve_ir()`" +" 结合经审查的 SoC/Board 事实,校验模式、资源、时钟、IRQ、ABI、DMA 可达性与预算,并直接返回 " +"`ConfigurationIR`。具名冻结记录为每个决策只保存一份值,不暴露 Mapping 包装接口,也不保存重复的 " +"`values`。硬件事实叶数据和按键索引仍使用不可变 mapping,resolver " +"的局部校验草稿也可用字典。这些是明确的数据边界,不承诺为每个 JSON 字段创建独立名义类型。" + +msgid "" +"Factory and emitters consume the same typed result. `to_dict()` derives " +"the schema-1 `resolved.json` audit/artifact projection; it is never read" +" back as an authored platform input. Private typed source epoch 2 is " +"separate from that serialized format and from the public C factory " +"epoch. Old JSON assemblies are rejected. Historical evidence keeps its " +"original bytes and read-only verification. CMake owns the source " +"dependency graph; resolution does not discover software from device " +"names or generate product workers." msgstr "" -"解析器将人工编写的 TOML 与已审查 SoC/Board 事实组合为不可变类型化 IR 及原子生成包。它验证模式、资源、时钟、IRQ、ABI、DMA " -"可达性与预算。CMake 拥有源码依赖图;解析器不根据设备名称发现软件,也不生成产品执行任务。" +"Factory 和 emitter 消费同一类型化结果。`to_dict()` 派生 schema-1 `resolved.json` " +"审计/产物投影,绝不会将其读回为人工编写的平台输入。私有 typed source epoch 2 与该序列化格式、公共 C factory " +"epoch 分别计代。旧 JSON 装配会被拒绝。历史证据保留原字节与只读校验。CMake " +"拥有源码依赖图;解析不会从设备名称发现软件,也不会生成产品 worker。" msgid "" "![Actual compile-target dependency relationships](../assets/diagrams/build-" @@ -140,17 +257,28 @@ msgstr "" "分发固定到提供者实例,不为每个字节重复执行工厂查找。" msgid "" -"The constructor contract names private state, definitions, initialization, " -"stop actions and failed-init responsibility explicitly. Common generation " -"does not infer these from family names. Selected STM32F4 official LL " -"operations remain inside private SoC state machines; factory and public I/O " -"contracts are unchanged. Full HAL Handle/callback transport requires " -"separate mode-specific evaluation. See [official driver " +"The constructor contract names private state, definitions, " +"initialization, stop actions and failed-init responsibility explicitly. " +"Its initializer and stop list use immutable `DirectCall` values with " +"explicit static references or constants; no free-form C expression or " +"function-pointer/member-call expression can enter those fields. " +"Definitions and some remaining cold helper bodies are still emitted as C" +" source. The C compiler establishes actual symbol and parameter types. " +"Model tests and linked-image checks remain necessary: this narrow " +"representation is not a complete typed C AST. Common generation does not" +" infer these decisions from family names. Selected STM32F4 official LL " +"operations remain inside private SoC state machines; factory and public " +"I/O contracts are unchanged. Full HAL Handle/callback transport requires" +" separate mode-specific evaluation. See [official driver " "integration](../design/official-stm32-provider.md)." msgstr "" -"构造器合同明确给出私有状态、定义、初始化、停止动作与初始化失败责任。公共生成器不从家族名称推断这些内容。所选 STM32F4 官方 LL " -"操作继续位于私有 SoC 状态机内,factory 与公共 I/O 合同不变。完整 HAL Handle/callback transport " -"需要逐模式独立评估。见[官方驱动接入](../design/official-stm32-provider.md)。" +"构造合同明确命名私有状态、定义、初始化、停止动作和初始化失败责任。initializer 与 stop 列表使用不可变 " +"`DirectCall`,参数为显式静态引用或常量;这些字段不接受任意 C " +"表达式或函数指针/成员调用表达式。定义及部分尚存的冷态辅助函数体仍作为 C 源码生成。C " +"编译器建立实际符号与参数类型。模型测试和链接镜像检查仍不可省略:这种窄表示不是完整的类型化 C AST。公共生成器不从系列名称推断这些决策。选定" +" STM32F4 官方 LL 操作仍在私有 SoC 状态机内部,factory 与公共 I/O 合同不变。完整 HAL " +"Handle/callback " +"传输需要按具体模式单独评估。参见[官方驱动集成](../design/official-stm32-provider.md)。" msgid "" "The legacy F103/GD303 selections share private algorithms only after exact " @@ -260,96 +388,3 @@ msgstr "" "阅读[类型化工厂](factory.md)、[所有权](ownership.md)、[接入合同](../design/integration-" "contracts.md)和[精确支持范围](../reference/platforms/support.md)。架构图解释机制;测试、镜像检查和 " "HIL 分别建立各自执行范围。" - -#: docs/explanation/platform-architecture.md:33 -msgid "Current source organization" -msgstr "当前源码组织" - -#: docs/explanation/platform-architecture.md:35 -msgid "" -"The source tree groups MCU families by vendor and CPU implementations by " -"instruction-set architecture. These directories describe ownership at build " -"time; they add no runtime dispatch or lookup layer. Public include paths, " -"target names and authored family identifiers remain stable." -msgstr "" -"源码树按厂家组织 MCU 系列,按指令集架构组织 CPU " -"实现。这些目录表达构建期所有权,不增加运行时分派或查找层。公共头文件路径、目标名称和人工配置中的系列标识保持稳定。" - -#: docs/explanation/platform-architecture.md:46 -msgid "" -"Common components and selected STM32 extensions have distinct source " -"ownership. Components are explicit targets; a chip-specific target still " -"requires its exact SoC, mode and backend, and its presence does not extend " -"the public support matrix." -msgstr "" -"通用组件与选定的 STM32 扩展具有不同的源码所有权。组件都是显式目标;芯片专用目标仍要求精确 SoC、模式和后端,其存在不会扩大公共支持矩阵。" - -#: docs/explanation/platform-architecture.md:51 -msgid "Platform and CPU-only Runtime" -msgstr "Platform 与仅 CPU 的 Runtime" - -#: docs/explanation/platform-architecture.md:0 -msgid "Consumption entry" -msgstr "消费入口" - -#: docs/explanation/platform-architecture.md:0 -msgid "Provided assembly" -msgstr "提供的装配" - -#: docs/explanation/platform-architecture.md:0 -msgid "External responsibility" -msgstr "外部责任" - -#: docs/explanation/platform-architecture.md:0 -msgid "`Nexus::Platform`" -msgstr "`Nexus::Platform`" - -#: docs/explanation/platform-architecture.md:0 -msgid "" -"Exact SoC/Board facts, selected providers, static factory and explicit " -"lifecycle" -msgstr "精确 SoC/Board 事实、所选 provider、静态 factory 与显式生命周期" - -#: docs/explanation/platform-architecture.md:0 -msgid "Product sources, workers, budgets, storage regions and recovery" -msgstr "产品源码、执行任务、预算、存储区域与恢复" - -#: docs/explanation/platform-architecture.md:0 -msgid "`Nexus::Runtime`" -msgstr "`Nexus::Runtime`" - -#: docs/explanation/platform-architecture.md:0 -msgid "Core, Arch, public I/O faces and the explicitly selected OS adapter" -msgstr "Core、Arch、公共 I/O 接口面与显式选择的 OS adapter" - -#: docs/explanation/platform-architecture.md:0 -msgid "" -"Startup, vectors, memory/clock facts, Board wiring and concrete device " -"providers" -msgstr "启动、向量、内存/时钟事实、Board 接线及具体设备 provider" - -#: docs/explanation/platform-architecture.md:58 -msgid "" -"One build directory admits one resolved assembly. Use separate build " -"directories for different boards or CPU/security images; a product build " -"coordinator or CI matrix owns their orchestration. Runtime and Platform " -"reuse the same production mechanisms but are separate consumption modes." -msgstr "" -"一个构建目录只接纳一份解析后的装配。不同板卡或 CPU/安全域镜像使用独立构建目录,由产品构建协调器或 CI 矩阵编排。Runtime 与 " -"Platform 复用相同生产机制,但属于不同的消费模式。" - -#: docs/explanation/platform-architecture.md:63 -msgid "" -"The common OS boundary is the wait/wake port. Task, queue, join and storage " -"APIs remain backend-specific, including the real kernel control-block types." -" Products switching kernels must adapt that lifecycle explicitly; no " -"universal object pool or hidden scheduling task is supplied." -msgstr "" -"共同 OS 边界是等待/唤醒端口。任务、队列、join 和存储 API " -"仍为后端专用,包括真实内核控制块类型。产品切换内核时必须显式适配生命周期;平台不提供统一对象池或隐藏调度任务。" - -msgid "The [source-adjacent contracts](source-map.md) own the directory map and layer README content. Native models implement the same public I/O contracts under the Native SoC package. Shared register-protocol algorithms live under `drivers///` only when the participating wrappers independently establish their silicon semantics. Selected SoC targets compile these private sources directly; the directory adds no runtime layer to FIG-01. Cross-vendor SoC collaboration remains in `soc/common/`, while STM32 identity access remains under `soc/stm32/common/`." -msgstr "[源码相邻合同](source-map.md) 维护目录映射和各层 README 正文。Native 模型在 Native SoC 包下实现相同的公共 I/O 合同。共享寄存器协议算法只有在参与的 wrapper 分别确认其硅片语义后,才位于 `drivers///`。所选 SoC target 直接编译这些私有源文件;目录不会给 FIG-01 增加运行时层。跨厂家 SoC 协作保留在 `soc/common/`,STM32 identity 访问保留在 `soc/stm32/common/`。" - -msgid "Each family's `configure/` package owns its capability policy, constructors and lifecycle emission; Native uses `soc/native/configure/`. The explicit provider map and shared leaf contracts remain in `tools/configure/providers/`. Common parsing, resource arbitration, typed IR and atomic publication stay in `tools/configure/`. Configuration tests stay under `tools/configure/tests/providers//`. These are build-time Python boundaries, independent of the C execution path." -msgstr "每个家族的 `configure/` 包拥有能力策略、构造器与生命周期生成;Native 使用 `soc/native/configure/`。显式 provider map 与共享叶子合同保留在 `tools/configure/providers/`。通用解析、资源仲裁、typed IR 和原子发布仍在 `tools/configure/`。配置测试仍在 `tools/configure/tests/providers//`。这些是构建期 Python 边界,独立于 C 执行路径。" diff --git a/docs/locale/zh_CN/LC_MESSAGES/guides/migration.po b/docs/locale/zh_CN/LC_MESSAGES/guides/migration.po index d3c9f7098..d20494210 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/guides/migration.po +++ b/docs/locale/zh_CN/LC_MESSAGES/guides/migration.po @@ -1,4 +1,4 @@ -# +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source docs\n" @@ -8,7 +8,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 2fe2c5c99c3b9d10dcb26fcda33d417f0cd413daa55592829b6c828ff1d54f0e\n" +"X-Nexus-Source-SHA256: 3febdbd9bcdfc0ac5e68205f8114ab81ad7483dfa7b96b856d71e5160d98aec8\n" msgid "Migrate legacy configuration and interface access" msgstr "迁移旧配置与接口访问" @@ -94,14 +94,19 @@ msgid "" msgstr "从仓库根执行实际 parser 与生成路径:" msgid "" -"Use a new output path you own. Check `resolved.json` and generated headers; " -"never author the resolved output or reuse stale generated files after a " -"failure. Legacy schema-1 JSON exists only for explicit migration fixtures. A" -" failed TOML does not fall back to JSON, Kconfig, old headers or cache " -"values." +"Use a new output path you own. Check `resolved.json` and generated " +"headers; never author the resolved output or reuse stale generated files" +" after a failure. Legacy schema-1 JSON is no longer a platform input. " +"Convert it to a complete schema-2 TOML assembly; generated " +"`resolved.json` remains audit output only. A failed TOML does not fall " +"back to JSON, Kconfig, old headers or cache values. Frozen historical " +"evidence remains verifiable under its original format; this read-only " +"retention does not reopen old configuration or generation paths." msgstr "" -"使用自己拥有的新输出路径。检查 `resolved.json` 和生成头文件;不得编写 resolved 输出,失败后不得复用过期生成文件。旧 " -"schema-1 JSON 仅用于明确的迁移夹具。TOML 失败不回退 JSON、Kconfig、旧头文件或缓存值。" +"使用由你拥有的新输出路径。检查 `resolved.json` 和生成头文件;不要手写解析结果,也不要在失败后复用陈旧生成文件。旧 " +"schema-1 JSON 已不再是平台输入,必须迁为完整 schema-2 TOML assembly;生成的 `resolved.json`" +" 只作审计输出。TOML 失败不会回退到 JSON、Kconfig、旧头文件或 cache " +"值。冻结历史证据仍可按原格式校验;只读保留不重新开放旧配置或生成路径。" msgid "Move access and lifecycle together" msgstr "同时迁移访问与生命周期" diff --git a/docs/locale/zh_CN/LC_MESSAGES/guides/provider-porting.po b/docs/locale/zh_CN/LC_MESSAGES/guides/provider-porting.po index 36ef1eb24..6c3e7a860 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/guides/provider-porting.po +++ b/docs/locale/zh_CN/LC_MESSAGES/guides/provider-porting.po @@ -1,4 +1,4 @@ -# Authored translation; independent review is recorded in metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source documentation\n" @@ -7,7 +7,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 38bf5ff725d9469f952bba6a94866ef6509ab77bb2c3dad9a2c107d7a20dee24\n" +"X-Nexus-Source-SHA256: 0d14a3946f9b0770804d021585dde09de0f5583b88680ae8d0e8c30908132712\n" #: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:9 msgid "Add a SoC, provider or reusable component" @@ -95,6 +95,57 @@ msgstr "" "architecture.md)、[接入合同](../design/integration-" "contracts.md)和[提供者装配合同](../design/provider-assemblies.md)。" +msgid "Choose the source owner" +msgstr "确定源码归属" + +msgid "![Current decision for shared register algorithms and exact SoC integration](../assets/diagrams/driver-source-ownership.svg)" +msgstr "![共享寄存器算法与精确 SoC 集成的当前判定](../assets/diagrams/driver-source-ownership.svg)" + +msgid "Current decision for shared register algorithms and exact SoC integration" +msgstr "共享寄存器算法与精确 SoC 集成的当前判定" + +msgid "FIG-31 classifies private controller work. Arrows are maintenance decisions, not runtime calls. Sharing requires evidence for every selecting chip's register layout, flags, ordering and reset compatibility, plus explicit handling of its errata. Similar names or a successful compile do not establish that evidence. Keep an unreviewed or incompatible protocol in its exact SoC implementation." +msgstr "FIG-31 对私有控制器工作作归属判定。箭头表示维护决策,不是运行时调用。共享需要每个选入芯片的寄存器布局、位含义、访问顺序及复位兼容性证据,并显式处理其勘误。名称相似或编译成功不能建立这些证据。尚未复核或不兼容的协议保留在精确 SoC 实现中。" + +msgid "Change" +msgstr "变更" + +msgid "Source owner and required check" +msgstr "源码归属与必需检查" + +msgid "Public I/O contract or portable I/O rule" +msgstr "公共 I/O 合同或可移植 I/O 规则" + +msgid "`io/`; preserve the public ownership and failure contract" +msgstr "`io/`;保持公共所有权与失败合同" + +msgid "Reusable register-protocol algorithm" +msgstr "可复用寄存器协议算法" + +msgid "`drivers///`; each SoC wrapper establishes the actual silicon contract" +msgstr "`drivers///`;每个 SoC wrapper 确认实际硅片合同" + +msgid "Part, memory, clock, reset, pin, IRQ or DMA integration" +msgstr "料号、内存、时钟、复位、引脚、IRQ 或 DMA 集成" + +msgid "Exact `soc///`; facts, configuration policy and private C retain distinct jobs" +msgstr "精确 `soc///`;事实、配置策略与私有 C 保持各自职责" + +msgid "Private SoC collaboration helper" +msgstr "私有 SoC 协作辅助代码" + +msgid "Narrowest reviewed SoC scope; `soc/common/uart_control.h` is cross-vendor, while STM32 identity access is vendor-specific" +msgstr "最窄的已复核 SoC 范围;`soc/common/uart_control.h` 跨厂家共享,STM32 identity 访问则属于厂家特定范围" + +msgid "External device, protocol or product policy" +msgstr "外部器件、协议或产品策略" + +msgid "Existing component ports or the external product, according to the [integration contract](../design/integration-contracts.md)" +msgstr "按[接入合同](../design/integration-contracts.md)放入既有组件端口或外部产品" + +msgid "The SoC target selects shared sources through explicit CMake entries and private includes. The driver directory does not choose routes, clock profiles or an ABI, and its presence does not add a maintained mode. Never introduce a runtime forwarder merely to match a source directory." +msgstr "SoC target 通过显式 CMake 条目和私有 include 选择共享源码。driver 目录不选择路由、时钟 profile 或 ABI,目录存在也不增加维护中的模式。不得只为匹配源码目录而引入运行时转发层。" + #: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:31 msgid "Implement in a reviewable sequence" msgstr "按可评审的顺序实现" @@ -117,6 +168,33 @@ msgstr "" "为当前生产路径新增并执行有意义的 RED GoogleTest/GoogleMock 合同。保留实际命令、非零执行及预期失败断言。通过 mock 测试 " "MMIO/CPU/依赖端口;Python 解析器行为使用标准库 unittest。工具缺失或只有编译错误,不能当作预期的行为 RED 结果。" +msgid "Add exact `soc.json`/`routes.json` facts and documented provenance. Feed the shared CPU resolver; do not duplicate CPU flags or infer optional features. Place MCU implementations under `soc///` and declare that canonical `source_directory` in the maintained family descriptor. Public family identifiers stay independent of the directory. CPU implementation sources belong under their ISA, such as `arch/arm/cortex_m/`; reviewed cross-vendor register algorithms belong in `drivers///` only after checking each participating silicon contract. Cross-vendor SoC collaboration stays in `soc/common/`; STM32 identity helpers stay in `soc/stm32/common/`." +msgstr "添加精确的 `soc.json`/`routes.json` 事实并记录来源。使用共享 CPU resolver,不重复 CPU flags,也不推断可选功能。将 MCU 实现放在 `soc///`,并在维护中的家族 descriptor 声明该规范 `source_directory`。公共家族标识独立于目录。CPU 实现按 ISA 放置,例如 `arch/arm/cortex_m/`;经复核的跨厂家寄存器算法只有在核对各参与芯片合同后才能放入 `drivers///`。跨厂家 SoC 协作保留在 `soc/common/`;STM32 identity helper 保留在 `soc/stm32/common/`。" + +msgid "" +"Add the family to the explicit maintained provider map in " +"`tools/configure/`. Place its production configuration package in " +"`soc///configure/` (Native uses `soc/native/configure/`)" +" and its configuration tests in " +"`tools/configure/tests/providers//`. Define supported modes, " +"numerical limits, constructors, rollback and IRQ wiring. Return an " +"explicit frozen `Construction` record, including the private state type " +"and failed-init cleanup responsibility. Initialize and stop actions use " +"`DirectCall` values under the [constructor contract](../design/official-" +"stm32-provider.md#build-time-provider-contract); raw C expressions are " +"not accepted in those fields. Do not require family-name inference in " +"common generation. Unsupported modes reject; there is no runtime " +"registration or plugin discovery." +msgstr "" +"在 `tools/configure/` 的显式维护 provider 表中添加该系列。生产配置包放在 " +"`soc///configure/`(Native 使用 " +"`soc/native/configure/`),配置测试放在 " +"`tools/configure/tests/providers//`。定义支持模式、数值限制、构造器、回滚与 IRQ " +"接线。返回显式冻结的 `Construction` 记录,包含私有状态类型及初始化失败后的清理责任。Initialize 和 stop 动作使用" +"[构造合同](../design/official-stm32-provider.md#build-time-provider-contract" +")中的 `DirectCall` 值;这些字段不接受原始 C " +"表达式。公共生成器不依赖系列名称推断。不支持的模式应拒绝;不引入运行时注册或插件发现。" + #: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:54 msgid "" "Add explicit CMake targets and private SDK dependencies. Update declared " @@ -163,103 +241,26 @@ msgstr "" "组件通过显式总线、GPIO、时间和可选 OS 端口工作。不得创建执行任务、探测设备、虚构应用角色或提供最大对象池。Flash " "驱动暴露物理几何;持久化和升级策略仍在外部。" -#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:83 msgid "" -"The read-only [work-package view](https://github.com/X-Gen-" -"Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/maintenance/README.md) " -"connects exact maintained modes to resolver fixtures, selected ops sources " -"and reviewed lifecycle properties. Its entries are navigation, not executed " -"tests or new support declarations. Run actual applicable gates separately." -msgstr "" -"只读[工作包视图](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-" -"SOURCE/tools/maintenance/README.md)将精确维护模式关联到 resolver 夹具、所选 ops " -"源码与已审生命周期性质。视图条目用于导航,不是已执行测试或新增支持声明。实际适用门禁需要另行运行。" - -#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:88 -msgid "Review performance and ownership" -msgstr "审查性能与所有权" - -#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:90 -msgid "" -"Measure the selected empty image and the same fixture after enabling the new" -" mode. Report Flash LOAD, Flash footprint, each RAM domain and reserved " -"task/IO storage separately. Hot IRQ work should directly access its fixed " -"provider; an interface lookup is a cold action. Keep bounded admission and " -"stream metadata operations independent of queue length where their contracts" -" require it." +"Within each SoC's `configure/` package, `contract.py` owns mode " +"capabilities, ABI/limits and family admission policy. Sibling modules " +"import that contract directly; `__init__.py` explicitly exports it with " +"constructors and emitters. The leaf `providers/common.py` owns " +"validation helpers and immutable `Construction`; leaf contracts and " +"common helpers do not import the top-level resolver or emitter. " +"`selection.py` checks cross-domain options, clocks and IRQ policy, " +"`ir.py` owns named immutable decisions and their one-way JSON " +"projection, and `matrix.py` orchestrates support catalogues. The " +"descriptor explicitly lists `configuration_modules`, including " +"`contract.py`, so edits trigger reconfiguration and remain in the Source" +" SDK closure." msgstr "" -"测量所选空镜像和同一夹具启用新模式后的镜像。分别报告 Flash LOAD、Flash 占用范围、各 RAM 域和预留任务/I/O 存储。高频 IRQ " -"应直接访问固定提供者;接口查找属于冷路径。在合同要求时,准入判定和流元数据操作保持有界,并且不随队列长度增长。" - -#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:96 -msgid "" -"A passing host register model proves its modeled software scope. New CPU, " -"Cache, FPU/MVE, security or low-power behavior needs its own actual " -"execution boundary; [platform support](../reference/platforms/support.md) " -"does not infer it from a name." -msgstr "" -"通过的主机寄存器模型只证明其建模的软件范围。新增 " -"CPU、缓存、FPU/MVE、安全或低功耗行为必须建立自己的实际执行边界;[平台支持](../reference/platforms/support.md)不能仅凭名称推导它们。" - -msgid "Add exact `soc.json`/`routes.json` facts and documented provenance. Feed the shared CPU resolver; do not duplicate CPU flags or infer optional features. Place MCU implementations under `soc///` and declare that canonical `source_directory` in the maintained family descriptor. Public family identifiers stay independent of the directory. CPU implementation sources belong under their ISA, such as `arch/arm/cortex_m/`; reviewed cross-vendor register algorithms belong in `drivers///` only after checking each participating silicon contract. Cross-vendor SoC collaboration stays in `soc/common/`; STM32 identity helpers stay in `soc/stm32/common/`." -msgstr "添加精确的 `soc.json`/`routes.json` 事实并记录来源。使用共享 CPU resolver,不重复 CPU flags,也不推断可选功能。将 MCU 实现放在 `soc///`,并在维护中的家族 descriptor 声明该规范 `source_directory`。公共家族标识独立于目录。CPU 实现按 ISA 放置,例如 `arch/arm/cortex_m/`;经复核的跨厂家寄存器算法只有在核对各参与芯片合同后才能放入 `drivers///`。跨厂家 SoC 协作保留在 `soc/common/`;STM32 identity helper 保留在 `soc/stm32/common/`。" - -msgid "Add the family to the explicit maintained provider map in `tools/configure/`. Place its production configuration package in `soc///configure/` (Native uses `soc/native/configure/`) and its configuration tests in `tools/configure/tests/providers//`. Define supported modes, numerical limits, constructors, rollback and IRQ wiring. Return an explicit frozen `Construction` record, including the private state type and failed-init cleanup responsibility; do not require family-name inference in common generation. Unsupported modes reject; there is no runtime registration or plugin discovery." -msgstr "将家族加入 `tools/configure/` 中显式维护的 provider map。生产配置包放在 `soc///configure/`(Native 使用 `soc/native/configure/`),配置测试放在 `tools/configure/tests/providers//`。定义支持的模式、数值限制、构造器、撤回和 IRQ 连接。返回显式冻结的 `Construction` record,包括私有状态类型和初始化失败后的清理责任;公共生成逻辑不得依靠家族名推断。未支持模式必须拒绝,不提供运行时注册或插件发现。" - -msgid "Within each SoC's `configure/` package, `contract.py` owns mode capabilities, ABI/limits and family admission policy. Sibling modules import that contract directly; `__init__.py` explicitly exports it with constructors and emitters. The leaf `providers/common.py` owns validation helpers and immutable `Construction`; leaf contracts and common helpers do not import the top-level resolver or emitter. `selection.py` checks cross-domain options, clocks and IRQ policy, `ir.py` adapts typed IR, and `matrix.py` orchestrates support catalogues. The descriptor explicitly lists `configuration_modules`, including `contract.py`, so edits trigger reconfiguration and remain in the Source SDK closure." -msgstr "每个 SoC 的 `configure/` 包中,`contract.py` 拥有模式能力、ABI/限制与家族准入策略。同级模块直接导入该合同;`__init__.py` 将其与构造器、生成器一起显式导出。叶子模块 `providers/common.py` 拥有校验辅助逻辑和不可变 `Construction`;叶子合同与公共辅助模块不导入顶层 resolver 或 emitter。`selection.py` 检查跨域选项、时钟和 IRQ 策略,`ir.py` 适配 typed IR,`matrix.py` 编排支持目录。descriptor 显式列出包含 `contract.py` 的 `configuration_modules`,使编辑能够触发重新配置,并纳入 Source SDK 闭包。" - -msgid "Choose the source owner" -msgstr "确定源码归属" - -msgid "![Current decision for shared register algorithms and exact SoC integration](../assets/diagrams/driver-source-ownership.svg)" -msgstr "![共享寄存器算法与精确 SoC 集成的当前判定](../assets/diagrams/driver-source-ownership.svg)" - -msgid "Current decision for shared register algorithms and exact SoC integration" -msgstr "共享寄存器算法与精确 SoC 集成的当前判定" - -msgid "FIG-31 classifies private controller work. Arrows are maintenance decisions, not runtime calls. Sharing requires evidence for every selecting chip's register layout, flags, ordering and reset compatibility, plus explicit handling of its errata. Similar names or a successful compile do not establish that evidence. Keep an unreviewed or incompatible protocol in its exact SoC implementation." -msgstr "FIG-31 对私有控制器工作作归属判定。箭头表示维护决策,不是运行时调用。共享需要每个选入芯片的寄存器布局、位含义、访问顺序及复位兼容性证据,并显式处理其勘误。名称相似或编译成功不能建立这些证据。尚未复核或不兼容的协议保留在精确 SoC 实现中。" - -msgid "Change" -msgstr "变更" - -msgid "Source owner and required check" -msgstr "源码归属与必需检查" - -msgid "Public I/O contract or portable I/O rule" -msgstr "公共 I/O 合同或可移植 I/O 规则" - -msgid "`io/`; preserve the public ownership and failure contract" -msgstr "`io/`;保持公共所有权与失败合同" - -msgid "Reusable register-protocol algorithm" -msgstr "可复用寄存器协议算法" - -msgid "`drivers///`; each SoC wrapper establishes the actual silicon contract" -msgstr "`drivers///`;每个 SoC wrapper 确认实际硅片合同" - -msgid "Part, memory, clock, reset, pin, IRQ or DMA integration" -msgstr "料号、内存、时钟、复位、引脚、IRQ 或 DMA 集成" - -msgid "Exact `soc///`; facts, configuration policy and private C retain distinct jobs" -msgstr "精确 `soc///`;事实、配置策略与私有 C 保持各自职责" - -msgid "Private SoC collaboration helper" -msgstr "私有 SoC 协作辅助代码" - -msgid "Narrowest reviewed SoC scope; `soc/common/uart_control.h` is cross-vendor, while STM32 identity access is vendor-specific" -msgstr "最窄的已复核 SoC 范围;`soc/common/uart_control.h` 跨厂家共享,STM32 identity 访问则属于厂家特定范围" - -msgid "External device, protocol or product policy" -msgstr "外部器件、协议或产品策略" - -msgid "Existing component ports or the external product, according to the [integration contract](../design/integration-contracts.md)" -msgstr "按[接入合同](../design/integration-contracts.md)放入既有组件端口或外部产品" - -msgid "The SoC target selects shared sources through explicit CMake entries and private includes. The driver directory does not choose routes, clock profiles or an ABI, and its presence does not add a maintained mode. Never introduce a runtime forwarder merely to match a source directory." -msgstr "SoC target 通过显式 CMake 条目和私有 include 选择共享源码。driver 目录不选择路由、时钟 profile 或 ABI,目录存在也不增加维护中的模式。不得只为匹配源码目录而引入运行时转发层。" +"每个 SoC 的 `configure/` 包中,`contract.py` " +"拥有模式能力、ABI/限制与系列准入策略。相邻模块直接导入该合同;`__init__.py` 将其与构造器、emitter 显式导出。叶模块 " +"`providers/common.py` 拥有校验辅助函数与不可变 `Construction`;叶合同及公共辅助模块不导入顶层 " +"resolver 或 emitter。`selection.py` 检查跨域选项、时钟和 IRQ 策略;`ir.py` " +"拥有具名不可变决策及其单向 JSON 投影;`matrix.py` 编排支持目录。Descriptor 显式列出包含 `contract.py`" +" 在内的 `configuration_modules`,确保编辑触发重新配置,并保留在 Source SDK 闭包中。" msgid "Keep facts and admission policy distinct" msgstr "区分事实与准入策略" @@ -306,18 +307,6 @@ msgstr "所选限制、格式和硅片策略" msgid "Metadata belongs to the exact selected owner; missing review remains explicit and does not admit a mode" msgstr "元数据属于精确选定 owner;缺失复核保持明确,不因此准入模式" -msgid "Generated static C and private provider" -msgstr "生成的静态 C 与私有 provider" - -msgid "Consume validated values and retain independent SDK/register assertions and runtime failure checks" -msgstr "消费已校验数值,并保留独立 SDK/寄存器断言与运行时失败检查" - -msgid "F407 Flash generation illustrates the boundary: its selected sector array and readonly geometry come from validated `result.flash.erase_blocks`, origin and size. The C SDK assertion still checks the physical base, and independent register-model tests retain the reference geometry. Removing duplicate production tables does not make configuration a substitute for hardware validation." -msgstr "F407 Flash 生成展示了该边界:所选 sector 数组与只读 geometry 来自已校验的 `result.flash.erase_blocks`、起始地址和大小。C SDK 断言仍核对物理基址,独立寄存器模型测试保留参考几何。删除重复生产表不会让配置取代硬件校验。" - -msgid "Family policy hooks remain declared configuration inputs. H743's reviewed clock domain relationships and selected Flash linker fragments live in its `configure/` package; generic tools still own parsing, typed IR, resource arbitration and atomic publication. A hook must not create a second source graph or silently choose a different part, route or profile." -msgstr "家族策略 hook 仍是声明的配置输入。H743 经复核的时钟域关系与所选 Flash linker fragment 位于其 `configure/` 包;通用工具仍拥有解析、typed IR、资源仲裁与原子发布。hook 不得创建第二份源码图,也不得静默选择不同料号、路由或 profile。" - msgid "`PhysicalPartIR`" msgstr "`PhysicalPartIR`" @@ -336,11 +325,62 @@ msgstr "家族 `UART_FORMATS`、`STREAM_GEOMETRY` 与 `ENDPOINT_POLICY`" msgid "Explicit reviewed formats, stream geometry and endpoint capability; a vendor alias or name prefix does not select a policy" msgstr "显式经复核的格式、stream geometry 与 endpoint 能力;厂家别名或名称前缀不选择策略" +msgid "Generated static C and private provider" +msgstr "生成的静态 C 与私有 provider" + +msgid "Consume validated values and retain independent SDK/register assertions and runtime failure checks" +msgstr "消费已校验数值,并保留独立 SDK/寄存器断言与运行时失败检查" + +msgid "F407 Flash generation illustrates the boundary: its selected sector array and readonly geometry come from validated `result.flash.erase_blocks`, origin and size. The C SDK assertion still checks the physical base, and independent register-model tests retain the reference geometry. Removing duplicate production tables does not make configuration a substitute for hardware validation." +msgstr "F407 Flash 生成展示了该边界:所选 sector 数组与只读 geometry 来自已校验的 `result.flash.erase_blocks`、起始地址和大小。C SDK 断言仍核对物理基址,独立寄存器模型测试保留参考几何。删除重复生产表不会让配置取代硬件校验。" + msgid "All seven maintained MCU families obtain generated readonly Flash geometry from the validated facts through `memory.flash_geometry`. Six retain their existing global geometry symbols; F407 emits selected static geometry. Native remains a host model. The exact source and independently maintained test oracle still need the applicable provider and linked-image checks." msgstr "维护中的七个 MCU 家族都通过 `memory.flash_geometry`,从已校验事实生成只读 Flash geometry。六个家族保留既有全局 geometry symbol;F407 生成所选静态 geometry。Native 仍是主机模型。精确源码与独立维护的测试 oracle 仍须执行适用的 provider 和最终链接映像检查。" msgid "`ConfigurationIR.physical_part` views the existing frozen Flash and memory objects. `ControllerIR.integration` composes existing `ResourcePlanIR` claims, `DmaTopologyIR` lanes, IRQ options and profile, clock plan/inputs, selected pins and a family `ControllerBinding`. Its `clock_hz` is only the paired nominal APB input; a missing value is not guessed, and reviewed frequency bounds come from `clock_plan`. F407 SPI and the paired secondary-controller paths consume this view; other bindings can still use their existing typed or legacy-fixture adapter. Do not describe the view as a new route resolver or a completed rewrite of every constructor." msgstr "`ConfigurationIR.physical_part` 查看既有冻结 Flash 与 memory 对象。`ControllerIR.integration` 组合既有 `ResourcePlanIR` claim、`DmaTopologyIR` lane、IRQ 选项与 profile、clock plan/inputs、所选引脚和家族 `ControllerBinding`。其 `clock_hz` 仅为成对控制器的名义 APB 输入;缺值不作猜测,经复核的频率边界来自 `clock_plan`。F407 SPI 与成对 secondary-controller 路径消费该视图;其他 binding 仍可使用既有 typed 或旧 fixture 适配器。不得将该视图描述为新的路由 resolver,或所有构造器已完成重写。" +msgid "Family policy hooks remain declared configuration inputs. H743's reviewed clock domain relationships and selected Flash linker fragments live in its `configure/` package; generic tools still own parsing, typed IR, resource arbitration and atomic publication. A hook must not create a second source graph or silently choose a different part, route or profile." +msgstr "家族策略 hook 仍是声明的配置输入。H743 经复核的时钟域关系与所选 Flash linker fragment 位于其 `configure/` 包;通用工具仍拥有解析、typed IR、资源仲裁与原子发布。hook 不得创建第二份源码图,也不得静默选择不同料号、路由或 profile。" + msgid "The paired F407/GD470 controller bindings, DMA vector maps and concrete secondary controller hooks live with their SoC. Common code keeps resource leases, fixed IRQ wiring and drain ordering while consuming the selected typed integration. Coupled personalities and named pin states likewise require explicit provider policy. Cross-Board/backend qualification remains in its existing OS contract; moving family mechanisms does not broaden those admitted combinations." msgstr "成对 F407/GD470 控制器的 binding、DMA vector map 与具体 secondary controller hook 位于各自 SoC。公共代码消费所选 typed integration,并保留资源 lease、固定 IRQ wiring 和 drain 顺序。耦合 personality 与命名引脚状态同样要求显式 provider 策略。跨 Board/backend 资格仍属于既有 OS 合同;搬移家族机制不会扩大已准入组合。" + + +#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:83 +msgid "" +"The read-only [work-package view](https://github.com/X-Gen-" +"Lab/nexus/blob/NEXUS-DOCUMENTATION-SOURCE/tools/maintenance/README.md) " +"connects exact maintained modes to resolver fixtures, selected ops sources " +"and reviewed lifecycle properties. Its entries are navigation, not executed " +"tests or new support declarations. Run actual applicable gates separately." +msgstr "" +"只读[工作包视图](https://github.com/X-Gen-Lab/nexus/blob/NEXUS-DOCUMENTATION-" +"SOURCE/tools/maintenance/README.md)将精确维护模式关联到 resolver 夹具、所选 ops " +"源码与已审生命周期性质。视图条目用于导航,不是已执行测试或新增支持声明。实际适用门禁需要另行运行。" + +#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:88 +msgid "Review performance and ownership" +msgstr "审查性能与所有权" + +#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:90 +msgid "" +"Measure the selected empty image and the same fixture after enabling the new" +" mode. Report Flash LOAD, Flash footprint, each RAM domain and reserved " +"task/IO storage separately. Hot IRQ work should directly access its fixed " +"provider; an interface lookup is a cold action. Keep bounded admission and " +"stream metadata operations independent of queue length where their contracts" +" require it." +msgstr "" +"测量所选空镜像和同一夹具启用新模式后的镜像。分别报告 Flash LOAD、Flash 占用范围、各 RAM 域和预留任务/I/O 存储。高频 IRQ " +"应直接访问固定提供者;接口查找属于冷路径。在合同要求时,准入判定和流元数据操作保持有界,并且不随队列长度增长。" + +#: ../../../../tmp/nexus-quality-20261019/docs/guides/provider-porting.md:96 +msgid "" +"A passing host register model proves its modeled software scope. New CPU, " +"Cache, FPU/MVE, security or low-power behavior needs its own actual " +"execution boundary; [platform support](../reference/platforms/support.md) " +"does not infer it from a name." +msgstr "" +"通过的主机寄存器模型只证明其建模的软件范围。新增 " +"CPU、缓存、FPU/MVE、安全或低功耗行为必须建立自己的实际执行边界;[平台支持](../reference/platforms/support.md)不能仅凭名称推导它们。" diff --git a/docs/locale/zh_CN/LC_MESSAGES/reference/configuration/assembly.po b/docs/locale/zh_CN/LC_MESSAGES/reference/configuration/assembly.po index 2aeb754b7..b6a262de9 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/reference/configuration/assembly.po +++ b/docs/locale/zh_CN/LC_MESSAGES/reference/configuration/assembly.po @@ -1,4 +1,4 @@ -# Authored translation; independent review is recorded in metadata. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" "Project-Id-Version: Nexus source docs\n" @@ -8,23 +8,37 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: e3e0f46b508706832312e3520c9bc626927ca3e776e6e87b245e4812b4422e0c\n" +"X-Nexus-Source-SHA256: fcb2244e8408c5ae4fa37a25a1ff1a548be3f6c265317b6105d36ed0d7b1aaa8\n" #: ../../../../tmp/nexus-quality-20261019/docs/reference/configuration/assembly.md:9 msgid "Assembly input and rejected selections" msgstr "装配输入与拒绝规则" -#: ../../../../tmp/nexus-quality-20261019/docs/reference/configuration/assembly.md:11 msgid "" -"The authored configuration authority is one schema-2 TOML assembly. SoC and " -"Board facts remain maintained JSON; `resolved.json` is output, not an " -"authored alternative. CMake owns software dependencies. Failed TOML " -"resolution never falls back to Kconfig, cache variables, schema-1 JSON or " -"old generated headers." +"The authored configuration authority is one schema-2 TOML assembly. SoC " +"and Board facts remain maintained JSON; `resolved.json` is output, not " +"an authored alternative. CMake owns software dependencies. Failed TOML " +"resolution never falls back to Kconfig, cache variables, schema-1 JSON " +"or old generated headers. Legacy JSON assemblies are rejected as inputs," +" not retained as an alternate authoring interface." msgstr "" -"配置权威是一份人工编写的 schema-2 TOML 装配。SoC 和 Board 事实保持维护中的 JSON;`resolved.json` " -"是输出,不是另一份人工编写的输入。CMake 拥有软件依赖。TOML 解析失败绝不回退 Kconfig、缓存变量、schema-1 JSON " -"或旧生成头。" +"人工编写的配置权威是一份 schema-2 TOML assembly。SoC 和 Board facts 仍为维护的 " +"JSON;`resolved.json` 是输出,不是另一种人工编写入口。CMake 拥有软件依赖。TOML 解析失败不会回退到 " +"Kconfig、cache 变量、schema-1 JSON 或旧生成头文件。旧 JSON 装配作为输入会被拒绝,不保留为另一种编写接口。" + +msgid "" +"The parser creates immutable `AuthoredAssembly` intent. Production " +"consumers use `resolve_ir()` and named immutable `ConfigurationIR` " +"decisions; only audit and artifact-boundary consumers request " +"`to_dict()`. Typed source epoch 2 and the schema-1 JSON output " +"projection are separate contracts. The [source compatibility " +"policy](../../design/source-compatibility.md) owns their version " +"declarations; neither introduces firmware metadata or runtime dispatch." +msgstr "" +"解析器创建不可变 `AuthoredAssembly` 意图。生产消费者使用 `resolve_ir()` 与具名、不可变的 " +"`ConfigurationIR` 决策;只有审计和产物边界消费者调用 `to_dict()`。Typed source epoch 2 与 " +"schema-1 JSON 输出投影是不同合同,其版本声明由[源码兼容策略](../../design/source-compatibility" +".md)维护;两者均不引入固件元数据或运行时分发。" #: ../../../../tmp/nexus-quality-20261019/docs/reference/configuration/assembly.md:16 msgid "" diff --git a/docs/locale/zh_CN/LC_MESSAGES/status/ledgers.po b/docs/locale/zh_CN/LC_MESSAGES/status/ledgers.po index d5fc5b88e..4709afe8f 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/status/ledgers.po +++ b/docs/locale/zh_CN/LC_MESSAGES/status/ledgers.po @@ -1,28 +1,33 @@ -# Authored translation; independently semantically reviewed. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source docs\n" -"Report-Msgid-Bugs-To: \n" -"Language-Team: Nexus agent-reviewed documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 7bacb1fc91ca7e6c543cb487ce7a2e9839c31e182fd09abdbfed1b06d8122250\n" +"X-Nexus-Source-SHA256: 5b9deebacf91a5006a4eef30beaefd1c00fc31f990560f7a03091450cf7f9bc9\n" #: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:9 msgid "原台账、退出条件与派生视图" msgstr "原台账、退出条件与派生视图" -#: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:11 msgid "" -"下面的页面来自十份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源 " -"SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" +"下面的页面来自十一份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源" +" SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" msgstr "" -"下面的页面来自十份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源 " -"SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" +"下面的页面来自十一份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源" +" SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。" + +msgid "" +"`soc-ip-layout` 的 R01–R48 保留目录和共享 IP 重构的原状态。R43–R48 记录 精确 P " +"的软件执行、分段计划、SDK、资源与候选;其 `not_executed` 物理值不因 页面生成改变。最新结果导航见 " +"[软件证据入口](software-verification.md)。" +msgstr "" +"`soc-ip-layout` 的 R01–R48 保留目录和共享 IP 重构的原状态。R43–R48 记录 精确 P " +"的软件执行、分段计划、SDK、资源与候选;其 `not_executed` 物理值不因 页面生成改变。最新结果导航见 " +"[软件证据入口](software-verification.md)。" #: ../../../../tmp/nexus-quality-20261019/docs/status/ledgers.md:30 msgid "正确解读状态" diff --git a/docs/locale/zh_CN/LC_MESSAGES/status/ledgers/soc-ip-layout.po b/docs/locale/zh_CN/LC_MESSAGES/status/ledgers/soc-ip-layout.po new file mode 100644 index 000000000..e5628d2ba --- /dev/null +++ b/docs/locale/zh_CN/LC_MESSAGES/status/ledgers/soc-ip-layout.po @@ -0,0 +1,1667 @@ +# Independently reviewed translations; exact canonical source identity below. +msgid "" +msgstr "" +"Project-Id-Version: Nexus documentation\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Nexus-Reviewed: true\n" +"X-Nexus-Source-SHA256: beb170c63e3a351784068ab52d482c5384f8d96eb4ea65e5912dc2b05a5f2e59\n" + +msgid "SoC/IP 目录整理台账" +msgstr "SoC/IP 目录整理台账" + +msgid "" +"本页由原 CSV 派生,不是新的状态权威、执行记录或软件资格。 原编号、顺序和全部原字段来自 {download}`原台账 " +"<../../design/soc-ip-layout-execution.csv>`。 源文件:`docs/design/soc-ip-lay" +"out-execution.csv`;SHA-256:`0564d12a1125e718b7560defc8a7cbb184c77ebeb1b1" +"3ba1eea1331b2cac8fa4`。" +msgstr "" +"本页由原 CSV 派生,不是新的状态权威、执行记录或软件资格。 原编号、顺序和全部原字段来自 {download}`原台账 " +"<../../design/soc-ip-layout-execution.csv>`。 源文件:`docs/design/soc-ip-lay" +"out-execution.csv`;SHA-256:`0564d12a1125e718b7560defc8a7cbb184c77ebeb1b1" +"3ba1eea1331b2cac8fa4`。" + +msgid "" +"不同台账范围重叠,不相加数量或计算全项目完成率。实现状态与软件、 物理状态分别解释;`development_passed` 不表示当前文档 " +"HEAD 已执行。 `qualification_record_authority` 要回到精确结果索引判断源码和范围。 绝对 " +"`/workspace`、`/dev/shm` 定位保留原字节,仅说明原执行主机位置; 可取得归档及 member 定位见 " +"[软件证据入口](../software-verification.md)。" +msgstr "" +"不同台账范围重叠,不相加数量或计算全项目完成率。实现状态与软件、 物理状态分别解释;`development_passed` 不表示当前文档 " +"HEAD 已执行。 `qualification_record_authority` 要回到精确结果索引判断源码和范围。 绝对 " +"`/workspace`、`/dev/shm` 定位保留原字节,仅说明原执行主机位置; 可取得归档及 member 定位见 " +"[软件证据入口](../software-verification.md)。" + +msgid "**原退出字段**只有原 CSV 中实际存在的字段;任务、资格前提和合同链接 也按原文保留,不能据此补造某次执行。" +msgstr "**原退出字段**只有原 CSV 中实际存在的字段;任务、资格前提和合同链接 也按原文保留,不能据此补造某次执行。" + +msgid "本表包含 48 条原记录,仅表示来源行数。" +msgstr "本表包含 48 条原记录,仅表示来源行数。" + +msgid "R01" +msgstr "R01" + +msgid "原字段" +msgstr "原字段" + +msgid "原值" +msgstr "原值" + +msgid "`id`" +msgstr "`id`" + +msgid "`work_package`" +msgstr "`work_package`" + +msgid "baseline" +msgstr "baseline" + +msgid "`requirement`" +msgstr "`requirement`" + +msgid "Branch and source baseline" +msgstr "分支与源码基线" + +msgid "`exit_condition`" +msgstr "`exit_condition`" + +msgid "Record actual source revision and branch before edits" +msgstr "修改前记录实际源码版本和分支" + +msgid "`implementation_paths`" +msgstr "`implementation_paths`" + +msgid "AGENTS.md" +msgstr "AGENTS.md" + +msgid "`implementation_status`" +msgstr "`implementation_status`" + +msgid "implemented" +msgstr "implemented" + +msgid "`software_status`" +msgstr "`software_status`" + +msgid "reviewed" +msgstr "reviewed" + +msgid "`hardware_status`" +msgstr "`hardware_status`" + +msgid "not_executed" +msgstr "not_executed" + +msgid "`evidence_paths`" +msgstr "`evidence_paths`" + +msgid ".git/local-recovery/soc-ip-layout/baseline.json" +msgstr ".git/local-recovery/soc-ip-layout/baseline.json" + +msgid "`notes`" +msgstr "`notes`" + +msgid "Local and GitHub refactor/soc-ip-layout branches start at 890a6026384b." +msgstr "本地与 GitHub 的 refactor/soc-ip-layout 分支均从 890a6026384b 开始。" + +msgid "R02" +msgstr "R02" + +msgid "Per-file responsibility and destination map" +msgstr "逐文件职责和目标位置映射" + +msgid "Review every moved file and distinguish shared IP from SoC integration" +msgstr "审查每个迁移文件,区分共享 IP 与 SoC 集成" + +msgid "soc;drivers;tools/configure" +msgstr "soc;drivers;tools/configure" + +msgid ".git/local-recovery/soc-ip-layout/path-map.json" +msgstr ".git/local-recovery/soc-ip-layout/path-map.json" + +msgid "" +"Shared IP and 35 family production modules reviewed; import-normalized " +"family AST unchanged. These are original migration-baseline identities " +"and counts, not current P totals; final execution is recorded in " +"R43-R48." +msgstr "" +"已审查共享 IP 和 35 个系列生产模块;导入规范化后的系列 AST 未改变。这些是原迁移基线的身份与计数,不是当前 P " +"的总计;最终执行记录在 R43–R48。" + +msgid "R03" +msgstr "R03" + +msgid "Exact platform mode resource and rejection baseline" +msgstr "精确平台模式、资源与拒绝行为基线" + +msgid "" +"Record maintained selections and rejection behavior without expanding " +"support" +msgstr "记录维护的选择与拒绝行为,不扩大支持范围" + +msgid "tools/configure;tests/contracts" +msgstr "tools/configure;tests/contracts" + +msgid "focused_passed" +msgstr "focused_passed" + +msgid "build/soc-ip-layout/baseline/configurations" +msgstr "build/soc-ip-layout/baseline/configurations" + +msgid "" +"136 exact assemblies including all 71 catalogue entries resolved in both" +" source trees; no effective decision changes. Existing negative " +"contracts remain enforced. These are original migration-baseline " +"identities and counts, not current P totals; final execution is recorded" +" in R43-R48." +msgstr "" +"136 份精确装配(包括目录中的全部 71 " +"项)在两份源码树中均完成解析,实际选择决策未变。既有负向合同继续执行。这些是原迁移基线身份与计数,不是当前 P 的总计;最终执行记录在 " +"R43–R48。" + +msgid "R04" +msgstr "R04" + +msgid "Test identities and execution domains" +msgstr "测试身份与执行域" + +msgid "Map retained cases to Native; tool; ARM and physical domains" +msgstr "将保留的用例映射到 Native、工具、ARM 与物理执行域" + +msgid "tests;tools/configure/tests;tools/testing" +msgstr "tests;tools/configure/tests;tools/testing" + +msgid "build/soc-ip-layout/configuration/case-mapping.json" +msgstr "build/soc-ip-layout/configuration/case-mapping.json" + +msgid "" +"Original 1035 configure cases retained one-to-one; 132 family cases " +"relocated; 8 migration regressions added. These are original " +"migration-baseline identities and counts, not current P totals; final " +"execution is recorded in R43-R48." +msgstr "" +"原 1035 个 configure 用例逐一保留;132 个系列用例迁移;新增 8 个迁移回归。这些是原迁移基线身份与计数,不是当前 P " +"的总计;最终执行记录在 R43–R48。" + +msgid "R05" +msgstr "R05" + +msgid "Flash RAM ops and IRQ baseline" +msgstr "Flash、RAM、ops 与 IRQ 基线" + +msgid "Record actual linked resource and retention baseline for comparisons" +msgstr "记录实际链接的资源和保留基线,供后续比较" + +msgid "tools/measurement;tests/contracts" +msgstr "tools/measurement;tests/contracts" + +msgid "build/soc-ip-layout/baseline/resources" +msgstr "build/soc-ip-layout/baseline/resources" + +msgid "" +"32 prior resource reports from 16 presets retained with matching ELF " +"hash/size. Historical baseline capture; not new execution or new-source " +"resource qualification. Only resource reports were copied; ELF/map " +"identities were observed at capture, not retained as new build evidence." +" Final P comparisons are recorded in R46." +msgstr "" +"保留来自 16 个 preset 的 32 份历史资源报告,ELF " +"摘要与大小匹配。这是历史基线采集,不是新执行或新源码的资源资格。只复制了资源报告;ELF/map " +"身份在采集时观察,并未作为新的构建证据保留。最终 P 比较记录在 R46。" + +msgid "R06" +msgstr "R06" + +msgid "Single execution ledger" +msgstr "唯一执行台账" + +msgid "Maintain exactly R01-R48 with implementation and execution separated" +msgstr "严格维护 R01–R48,将实现与执行状态分开" + +msgid "docs/design/soc-ip-layout-execution.csv" +msgstr "docs/design/soc-ip-layout-execution.csv" + +msgid "" +"One R01-R48 task record; implementation, software and physical status " +"stay separate. Final software " +"tested_source=P=92e669233957a2e450a528394e8f4dd0794c7049, " +"tree=6e38d4c43d59330a0a9a9b7cac21f591d9c2719f; integrated " +"M=c79fd9672a9fe56379744d834cfccd822bc40be1 has the same tree. This " +"documentation update records P and does not claim its own software " +"qualification." +msgstr "" +"唯一的 R01–R48 任务记录;实现、软件和物理状态分别维护。最终软件受测源码 tested_source=P=92e669233957a2e" +"450a528394e8f4dd0794c7049,tree=6e38d4c43d59330a0a9a9b7cac21f591d9c2719f;" +"已集成 M=c79fd9672a9fe56379744d834cfccd822bc40be1 的 tree 相同。本次文档更新只记录 " +"P,不声称文档自身取得软件资格。" + +msgid "R07" +msgstr "R07" + +msgid "infrastructure" +msgstr "infrastructure" + +msgid "Explicit family descriptors" +msgstr "显式系列描述符" + +msgid "" +"Declare canonical source directories and configuration modules without " +"discovery" +msgstr "显式声明规范源码目录与配置模块,不进行发现" + +msgid "tools/configure/providers/descriptor.py" +msgstr "tools/configure/providers/descriptor.py" + +msgid "build/soc-ip-layout/configuration" +msgstr "build/soc-ip-layout/configuration" + +msgid "" +"Explicit vendor/source/configuration directories; no family-prefix path " +"inference." +msgstr "显式厂家、源码与配置目录;不从系列名称前缀推断路径。" + +msgid "R08" +msgstr "R08" + +msgid "Module loading and type identity" +msgstr "模块加载与类型身份" + +msgid "" +"Explicit loading preserves common Python type identity and import " +"boundaries" +msgstr "显式加载保留公共 Python 类型身份和导入边界" + +msgid "tools/configure/providers;soc" +msgstr "tools/configure/providers;soc" + +msgid "" +"Normal selected SoC packages and canonical shared types; six actual " +"RED/GREEN loading regressions." +msgstr "采用普通的选定 SoC 包和规范共享类型;实际执行了六个加载回归的 RED/GREEN。" + +msgid "R09" +msgstr "R09" + +msgid "Input closure and freshness" +msgstr "输入闭包与新鲜度" + +msgid "" +"Moved configuration dependencies remain in identity and stale-input " +"rejection" +msgstr "迁移后的配置依赖继续纳入身份和陈旧输入拒绝" + +msgid "tools/configure/dependencies.py;tools/configure/bundle.py" +msgstr "tools/configure/dependencies.py;tools/configure/bundle.py" + +msgid "" +"All executing package initializers/helpers hashed; migrated sources " +"protected from owned output overwrite." +msgstr "全部实际执行的包初始化模块和辅助模块均计算摘要;迁移后的源码受输出所有权保护,不会被覆盖。" + +msgid "R10" +msgstr "R10" + +msgid "SDK roots and required members" +msgstr "SDK 根目录与必需成员" + +msgid "Source SDK includes declared SoC configuration and IP driver inputs" +msgstr "源码 SDK 包含声明的 SoC 配置与 IP 驱动输入" + +msgid "cmake/package" +msgstr "cmake/package" + +msgid "" +"build/soc-ip-layout/sdk-driver-red.log;build/soc-ip-layout/sdk-driver-gr" +"een.log;build/soc-ip-layout/sdk-payload.log;build/soc-ip-layout/sdk-sele" +"ction.log" +msgstr "" +"build/soc-ip-layout/sdk-driver-red.log;build/soc-ip-layout/sdk-driver-gr" +"een.log;build/soc-ip-layout/sdk-payload.log;build/soc-ip-layout/sdk-sele" +"ction.log" + +msgid "" +"New shared driver transport regression RED then GREEN; 20 device payload" +" and 45 selection/portable tests pass. Actual final P installed " +"consumers are recorded in R45." +msgstr "" +"新共享驱动传输回归先 RED 后 GREEN;20 个设备 payload 测试和 45 个选择/可搬移测试通过。最终 P 的实际安装消费记录在" +" R45。" + +msgid "R11" +msgstr "R11" + +msgid "Family test discovery" +msgstr "系列测试发现" + +msgid "" +"Retain and execute tests under " +"tools/configure/tests/providers/<family>" +msgstr "保留并执行 tools/configure/tests/providers/<family> 下的测试" + +msgid "tools/configure/tests/providers" +msgstr "tools/configure/tests/providers" + +msgid "" +"Family tests remain in existing tools/configure discovery domain; no new" +" runner." +msgstr "系列测试保留在现有 tools/configure 发现域;没有新增 runner。" + +msgid "R12" +msgstr "R12" + +msgid "Style source capability and measurement coverage" +msgstr "风格、源码、能力和测量覆盖" + +msgid "Include drivers and SoC configuration in existing observers and gates" +msgstr "把 drivers 和 SoC 配置纳入已有观察工具与门禁" + +msgid "tools/maintenance;tools/measurement;scripts/validation" +msgstr "tools/maintenance;tools/measurement;scripts/validation" + +msgid "" +"build/soc-ip-layout/baseline/quality-tools-green.log;build/soc-ip-layout" +"/baseline/io-lifecycle-green.log;build/soc-ip-layout/capabilities-green." +"log" +msgstr "" +"build/soc-ip-layout/baseline/quality-tools-green.log;build/soc-ip-layout" +"/baseline/io-lifecycle-green.log;build/soc-ip-layout/capabilities-green." +"log" + +msgid "" +"New drivers remain covered by style, private include, " +"ops/source/retention, static analysis and target-local lifecycle checks;" +" independent review closed all migration findings." +msgstr "新驱动继续受风格、私有 include、ops/源码/链接保留、静态分析和目标局部生命周期检查覆盖;独立审查已关闭全部迁移发现。" + +msgid "R13" +msgstr "R13" + +msgid "configuration_providers" +msgstr "configuration_providers" + +msgid "Move Native configuration" +msgstr "迁移 Native 配置" + +msgid "Use soc/native/configure with explicit imports and retained tests" +msgstr "使用 soc/native/configure,保留显式导入与测试" + +msgid "soc/native/configure" +msgstr "soc/native/configure" + +msgid "phase1_complete_index_passed" +msgstr "phase1_complete_index_passed" + +msgid "" +"build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync" +"-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/pha" +"se2-hook-evidence/manifest.json" +msgstr "" +"build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync" +"-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/pha" +"se2-hook-evidence/manifest.json" + +msgid "" +"Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical " +"commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index" +" gates: 3215 tool tests and 352 programs / 6352 Google cases. This " +"result does not qualify subsequent behavior changes. The first outer " +"hook log and sync receipt retain the historical assertion; reused " +"build/native-debug/tdd bytes are not its original detailed evidence. " +"Final P gates and execution are recorded in R43-R48." +msgstr "" +"第一阶段精确 tree 为 d9270fefe377be519e2fb10d223eed8125566223;规范提交为 " +"7b985c4c619a966c886c3cbf01902ccbfb68c955。普通完整暂存区门禁执行了 3215 个工具测试和 352 " +"个程序/6352 个 Google 用例。该结果不为后续行为变更建立资格。最初外层 hook 日志和同步收条保留历史声明;后来复用的 " +"build/native-debug/tdd 字节不是原始详细证据。最终 P 门禁与执行记录在 R43–R48。" + +msgid "R14" +msgstr "R14" + +msgid "Move F103 and GD303 configuration" +msgstr "迁移 F103 与 GD303 配置" + +msgid "Use each exact family configure package and retained tests" +msgstr "使用各精确系列的 configure 包并保留测试" + +msgid "soc/stm32/stm32f103/configure;soc/gd32/gd32f303/configure" +msgstr "soc/stm32/stm32f103/configure;soc/gd32/gd32f303/configure" + +msgid "R15" +msgstr "R15" + +msgid "Move F407 and GD470 configuration" +msgstr "迁移 F407 与 GD470 配置" + +msgid "soc/stm32/stm32f407/configure;soc/gd32/gd32f470/configure" +msgstr "soc/stm32/stm32f407/configure;soc/gd32/gd32f470/configure" + +msgid "R16" +msgstr "R16" + +msgid "Move G474 configuration and helpers" +msgstr "迁移 G474 配置与辅助模块" + +msgid "Co-locate G474 contract; construction; emission and selected helpers" +msgstr "就近放置 G474 合同、构造、生成和选定辅助模块" + +msgid "soc/stm32/stm32g474/configure" +msgstr "soc/stm32/stm32g474/configure" + +msgid "R17" +msgstr "R17" + +msgid "Move H743 configuration" +msgstr "迁移 H743 配置" + +msgid "Co-locate H743 contract; construction; emission and selected helpers" +msgstr "就近放置 H743 合同、构造、生成和选定辅助模块" + +msgid "soc/stm32/stm32h743/configure" +msgstr "soc/stm32/stm32h743/configure" + +msgid "R18" +msgstr "R18" + +msgid "Move U575 configuration and clear old production paths" +msgstr "迁移 U575 配置并清理旧生产路径" + +msgid "Co-locate U575 policy and remove obsolete family production packages" +msgstr "就近放置 U575 策略,移除废弃系列生产包" + +msgid "soc/stm32/stm32u575/configure;tools/configure/providers" +msgstr "soc/stm32/stm32u575/configure;tools/configure/providers" + +msgid "R19" +msgstr "R19" + +msgid "shared_drivers" +msgstr "shared_drivers" + +msgid "Move UART V1 channel DMA engine" +msgstr "迁移 UART V1 channel DMA 引擎" + +msgid "Select reviewed algorithm and private headers from drivers" +msgstr "从 drivers 选择经审查的算法与私有头文件" + +msgid "drivers/uart/uart_v1_channel_dma" +msgstr "drivers/uart/uart_v1_channel_dma" + +msgid "" +"build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-de" +"bug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-" +"debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json" +msgstr "" +"build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-de" +"bug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-" +"debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json" + +msgid "" +"Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero " +"skipped. Aggregate scope is not a per-row test count or complete " +"qualification. Physical HIL not executed. The preserved shared-drivers " +"XML matches the original report hash; its embedded generic Native XML " +"path was later reused. Final P execution is separately recorded in " +"R43-R48." +msgstr "" +"共享定向执行:17 个目标、17/17 CTest、267 个 Google 用例、零跳过。聚合范围不是逐行测试数量或完整资格。物理 HIL " +"未执行。保留的 shared-drivers XML 与原报告摘要匹配;其中嵌入的通用 Native XML 路径后来被复用。最终 P " +"的执行另记在 R43–R48。" + +msgid "R20" +msgstr "R20" + +msgid "Move I2C V1 asynchronous engine" +msgstr "迁移 I2C V1 异步引擎" + +msgid "Select reviewed IRQ/DMA algorithm and private headers from drivers" +msgstr "从 drivers 选择经审查的 IRQ/DMA 算法与私有头文件" + +msgid "drivers/i2c/i2c_v1_async" +msgstr "drivers/i2c/i2c_v1_async" + +msgid "R21" +msgstr "R21" + +msgid "Move I2C V2 engine" +msgstr "迁移 I2C V2 引擎" + +msgid "Select reviewed polling algorithm and private headers from drivers" +msgstr "从 drivers 选择经审查的轮询算法与私有头文件" + +msgid "drivers/i2c/i2c_v2" +msgstr "drivers/i2c/i2c_v2" + +msgid "R22" +msgstr "R22" + +msgid "Split legacy_devices and move ADC" +msgstr "拆分 legacy_devices 并迁移 ADC" + +msgid "" +"Separate private legacy interfaces and select the ADC algorithm from " +"drivers" +msgstr "拆开私有 legacy 接口,从 drivers 选择 ADC 算法" + +msgid "drivers/adc/legacy_adc" +msgstr "drivers/adc/legacy_adc" + +msgid "R23" +msgstr "R23" + +msgid "Move legacy PWM" +msgstr "迁移 legacy PWM" + +msgid "Select fixed PWM algorithm and narrow private contract from drivers" +msgstr "从 drivers 选择固定 PWM 算法及窄私有合同" + +msgid "drivers/timer/legacy_pwm" +msgstr "drivers/timer/legacy_pwm" + +msgid "R24" +msgstr "R24" + +msgid "Move legacy Flash and SoC helpers" +msgstr "迁移 legacy Flash 与 SoC 辅助模块" + +msgid "" +"Select Flash algorithm; place STM32 identity helper; retain UART " +"collaboration and update READMEs" +msgstr "选择 Flash 算法,放置 STM32 身份辅助模块,保留 UART 协作并更新 README" + +msgid "" +"drivers/flash/legacy_flash;soc/stm32/common/stm32_identity.h;soc/common/" +"uart_control.h;drivers/README.md;soc/common/README.md" +msgstr "" +"drivers/flash/legacy_flash;soc/stm32/common/stm32_identity.h;soc/common/" +"uart_control.h;drivers/README.md;soc/common/README.md" + +msgid "R25" +msgstr "R25" + +msgid "facts_and_ir" +msgstr "facts_and_ir" + +msgid "Centralize part package RAM and Flash geometry" +msgstr "集中维护料号、封装、RAM 和 Flash 几何" + +msgid "" +"Use one exact fact authority for part; package; memory and physical " +"geometry" +msgstr "为料号、封装、内存与物理几何使用唯一精确事实权威" + +msgid "soc;tools/configure" +msgstr "soc;tools/configure" + +msgid "" +"build/soc-ip-layout/facts-ir/part-red.log;build/soc-ip-layout/facts-ir/c" +"lock-red.log;build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/s" +"oc-ip-layout/facts-ir/f407-flash.xml;build/soc-ip-layout/facts-ir/final-" +"focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;bu" +"ild/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/f" +"acts-ir/all-flash-red.log;build/soc-ip-layout/flash-geometry/summary.jso" +"n" +msgstr "" +"build/soc-ip-layout/facts-ir/part-red.log;build/soc-ip-layout/facts-ir/c" +"lock-red.log;build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/s" +"oc-ip-layout/facts-ir/f407-flash.xml;build/soc-ip-layout/facts-ir/final-" +"focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;bu" +"ild/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/f" +"acts-ir/all-flash-red.log;build/soc-ip-layout/flash-geometry/summary.jso" +"n" + +msgid "" +"Exact PartPolicy and optional-memory admission; physical numbers remain " +"in soc.json and frozen PhysicalPartIR views. All seven MCU Flash " +"geometries derive readonly C from validated facts; six retain global " +"symbols and F407 uses selected static geometry. Final focused " +"configuration suite: 364 tests; separate integration/bundle suite: 143 " +"tests. These overlapping scopes are not additive or final-source " +"qualification." +msgstr "" +"实现精确 PartPolicy 与可选内存准入;物理数值仍在 soc.json 及冻结 PhysicalPartIR 视图。七款 MCU 的 " +"Flash 几何均从已校验事实生成只读 C;六款保留全局符号,F407 使用选定的静态几何。最终定向配置套件有 364 " +"个测试;独立集成/bundle 套件有 143 个测试。这些范围重叠,不能相加,也不是最终源码资格。" + +msgid "R26" +msgstr "R26" + +msgid "Typed controller integration record" +msgstr "类型化控制器集成记录" + +msgid "Use explicit typed integration data without family-name inference" +msgstr "使用显式类型化集成数据,不从系列名称推断" + +msgid "soc;tools/configure/providers/common.py" +msgstr "soc;tools/configure/providers/common.py" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/integration-red.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/integration-red.log" + +msgid "" +"ControllerIntegrationIR composes existing canonical " +"claims/DMA/IRQ/clock/pin/hardware objects; PhysicalPartIR reuses frozen " +"facts. No new JSON/hash field or route resolver. Actual production " +"consumers: F407 primary SPI and F407/GD470 paired secondary paths. Other" +" constructors and explicit legacy fixture adapters remain. 364-test " +"focused suite and overlapping 52-test final closure passed; no " +"final-source qualification." +msgstr "" +"ControllerIntegrationIR 组合已有规范 claims/DMA/IRQ/clock/pin/hardware " +"对象;PhysicalPartIR 复用冻结事实。没有新增 JSON/hash 字段或 route resolver。实际生产消费点是 F407" +" 主 SPI 以及 F407/GD470 配对次级路径。其他构造器和显式 legacy 夹具适配仍保留。364 项定向套件及与之重叠的 52 " +"项最终闭包测试通过;不建立最终源码资格。" + +msgid "R27" +msgstr "R27" + +msgid "Clock reset pin and state integration" +msgstr "时钟、复位、引脚与状态集成" + +msgid "Make integration ownership explicit without hidden side effects" +msgstr "显式表达集成所有权,不引入隐藏副作用" + +msgid "soc;drivers" +msgstr "soc;drivers" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log" + +msgid "" +"Exact family clock, GPIO/preflight, ControllerBinding and secondary cold" +" hooks own chip integration. Common resource leases/IRQ/drain ordering " +"remain. Focused configuration and bundle suites passed; no runtime " +"registration or broader clock/Board/backend qualification." +msgstr "" +"精确系列的 clock、GPIO/preflight、ControllerBinding 和次级冷态 hook " +"负责芯片集成。公共资源租约/IRQ/排空顺序保留。定向配置与 bundle 套件通过;没有运行时注册,也不扩大时钟/Board/backend " +"资格。" + +msgid "R28" +msgstr "R28" + +msgid "Consume existing IRQ DMA and memory IR consistently" +msgstr "一致消费已有 IRQ、DMA 和内存 IR" + +msgid "Use reviewed immutable IR instead of duplicated derived facts" +msgstr "使用经审查的不可变 IR,避免重复派生事实" + +msgid "soc;tools/configure/ir.py;tools/configure/memory.py" +msgstr "soc;tools/configure/ir.py;tools/configure/memory.py" + +msgid "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/claims-red.log;build/soc-ip-l" +"ayout/facts-ir/primary-dma-red.log" +msgstr "" +"build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts" +"-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-clos" +"ure-green.log;build/soc-ip-layout/facts-ir/claims-red.log;build/soc-ip-l" +"ayout/facts-ir/primary-dma-red.log" + +msgid "" +"Typed owned_claims uses canonical ResourcePlanIR; selected " +"hardware/DMA/IRQ/clock/pin consumers use ControllerIntegrationIR rather " +"than reparsing routes. Meaningful omitted-lane/claim regressions " +"recorded; other maintained typed or explicit legacy adapters remain. " +"Focused suites are not final ARM/SDK qualification." +msgstr "" +"类型化 owned_claims 使用规范 ResourcePlanIR;选定 hardware/DMA/IRQ/clock/pin 消费者使用" +" ControllerIntegrationIR,不重新解析 routes。已记录有意义的漏 lane/claim " +"回归;其他维护的类型化适配或显式 legacy 适配仍保留。定向套件不是最终 ARM/SDK 资格。" + +msgid "R29" +msgstr "R29" + +msgid "Move family special cases out of common tools" +msgstr "将系列特例移出公共工具" + +msgid "" +"Keep family policy in exact SoC configure packages and common mechanisms" +" shared" +msgstr "把系列策略放入精确 SoC configure 包,公共机制继续共享" + +msgid "" +"build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/" +"r30/green-final.log;build/soc-ip-layout/facts-ir/final-focused.log;build" +"/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout" +"/r29-hardware;build/soc-ip-layout/r29-public-policies;build/soc-ip-layou" +"t/facts-ir/final-closure-green.log;build/quality/soc-ip-config-r32/profi" +"le-python-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/" +"r30/green-final.log;build/soc-ip-layout/facts-ir/final-focused.log;build" +"/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout" +"/r29-hardware;build/soc-ip-layout/r29-public-policies;build/soc-ip-layou" +"t/facts-ir/final-closure-green.log;build/quality/soc-ip-config-r32/profi" +"le-python-green.log" + +msgid "" +"Explicit SoC hardware/secondary, clock/linker/runtime-power, " +"GPIO/preflight, selected support, UART/stream/endpoint and " +"CAN/coupled/pin policies implemented. Generated-output equivalence " +"checked in separately recorded 18-, 23- and 8-configuration scopes; test" +" counts overlap and must not be added. Remaining common family literals " +"are explicit dependency paths and ThreadX cross-Board/backend OS " +"admission. F103/GD303 frozen LegacyTransportProfile supplies exact " +"wiring without family fallback; its 38-test focused run is separate and " +"overlaps related suites." +msgstr "" +"已实现显式 SoC " +"hardware/secondary、clock/linker/runtime-power、GPIO/preflight、selected " +"support、UART/stream/endpoint 与 CAN/coupled/pin 策略。生成输出等价性在分别记录的 18、23、8 " +"个配置范围检查;测试计数重叠,不得相加。剩余公共系列字面量是显式依赖路径和 ThreadX 跨 Board/backend OS " +"准入。F103/GD303 冻结 LegacyTransportProfile 提供精确接线,不使用系列回退;其 38 " +"项定向执行单独记录,与相关套件重叠。" + +msgid "R30" +msgstr "R30" + +msgid "Capability revision and support projection" +msgstr "能力、revision 与支持投影" + +msgid "" +"Derive current views from exact reviewed facts without new support " +"claims" +msgstr "从经审查的精确事实派生当前视图,不作新支持声明" + +msgid "tools/maintenance;docs/delivery/capabilities.md" +msgstr "tools/maintenance;docs/delivery/capabilities.md" + +msgid "" +"build/soc-ip-layout/r30/red.log;build/soc-ip-layout/r30/owner-red.log;bu" +"ild/soc-ip-layout/r30/green-final.log" +msgstr "" +"build/soc-ip-layout/r30/red.log;build/soc-ip-layout/r30/owner-red.log;bu" +"ild/soc-ip-layout/r30/green-final.log" + +msgid "" +"98 tests passed across 10 focused modules. Existing silicon policy/IR " +"projects separately for clock and controller owners; absent review stays" +" null and companion/endpoint policy is not inherited. No broader support" +" or hardware observation." +msgstr "" +"十个定向模块的 98 个测试通过。已有硅片策略/IR 分别投影给 clock 与 controller owner;缺少审查时保留 " +"null,companion/endpoint 策略不继承。不扩大支持,也没有硬件观察。" + +msgid "R31" +msgstr "R31" + +msgid "static_configuration_and_lifecycle" +msgstr "static_configuration_and_lifecycle" + +msgid "I2C V2 const configuration pilot" +msgstr "I2C V2 常量配置试点" + +msgid "Separate immutable configuration from mutable transfer state" +msgstr "把不可变配置与可变传输状态分离" + +msgid "drivers/i2c/i2c_v2;soc" +msgstr "drivers/i2c/i2c_v2;soc" + +msgid "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log" +msgstr "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log" + +msgid "" +"Selected G474/H743/U575 const configuration and separate BSS runtime " +"implemented. Shared focused execution: 9 targets / 121 Google cases and " +"38 related Python tests passed; not per-row counts. Development ARM cost" +" comparison is recorded in R46; final P complete gates and scoped " +"execution are recorded in R43-R48." +msgstr "" +"已实现选定 G474/H743/U575 常量配置及独立 BSS 运行态。共享定向执行中 9 个目标/121 个 Google 用例及 38 " +"个相关 Python 测试通过;不是逐行计数。开发 ARM 成本比较记在 R46;最终 P 完整门禁与限定范围执行记在 R43–R48。" + +msgid "R32" +msgstr "R32" + +msgid "UART DMA const configuration" +msgstr "UART DMA 常量配置" + +msgid "Separate immutable configuration from mutable request and block state" +msgstr "把不可变配置与可变 request/block 状态分离" + +msgid "drivers/uart/uart_v1_channel_dma;soc" +msgstr "drivers/uart/uart_v1_channel_dma;soc" + +msgid "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green" +msgstr "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green" + +msgid "" +"Selected F103/GD303 UART DMA immutable config and BSS runtime " +"implemented; duplex receive extension remains separate. Static " +"config/resources share runtime lifetime and successful stop preserves " +"binding. Final focused run: 3 targets / 65 Google cases plus 38 Python " +"tests. Host execution covers actual SoC wrapper/engine/GPIO; generated " +"DMA factory runtime is not claimed. Development ARM comparison is " +"recorded separately in R46; final P complete gates and scoped execution " +"are recorded in R43-R48." +msgstr "" +"已实现选定 F103/GD303 UART DMA 不可变配置与 BSS " +"运行态;双向接收扩展保持独立。静态配置/资源与运行态具有同一生命周期,成功停止后保留绑定。最终定向执行为 3 个目标/65 个 Google " +"用例加 38 个 Python 测试。主机执行覆盖真实 SoC wrapper/engine/GPIO;不声称生成 DMA factory " +"的运行时验证。开发 ARM 比较另记在 R46;最终 P 完整门禁与限定范围执行记在 R43–R48。" + +msgid "R33" +msgstr "R33" + +msgid "Shared ops immutable faces and direct IRQ" +msgstr "共享 ops、不可变 face 与直接 IRQ" + +msgid "" +"Preserve shared readonly methods; independent contexts and direct IRQ " +"entry" +msgstr "保留共享只读方法、独立 context 与直接 IRQ 入口" + +msgid "drivers;soc;io" +msgstr "drivers;soc;io" + +msgid "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green;build/quality/soc-ip-config-r31/i2c-green" +msgstr "" +"build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-" +"r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.l" +"og;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-co" +"nfig-r32/uart-ring-green;build/quality/soc-ip-config-r31/i2c-green" + +msgid "" +"Shared readonly ops, immutable public faces, distinct static contexts " +"and direct IRQ entry symbols retained for the selected I2C V2 and UART " +"DMA changes; no hot-path dispatch layer. Focused UART " +"wrapper/engine/GPIO and I2C model suites passed. Development ARM linked " +"retention/costs are recorded in R46; final P scoped validation is " +"recorded in R43-R48." +msgstr "" +"选定 I2C V2 和 UART DMA 变更保留共享只读 ops、不可变公共 face、不同静态 context 与直接 IRQ " +"入口符号;没有热路径分发层。定向 UART wrapper/engine/GPIO 与 I2C 模型套件通过。开发 ARM 链接保留/成本记在 " +"R46;最终 P 的限定验证记在 R43–R48。" + +msgid "R34" +msgstr "R34" + +msgid "Move initialization and stop logic into C" +msgstr "将初始化和停止逻辑迁入 C" + +msgid "" +"Keep explicit lifecycle ordering and cleanup responsibilities in " +"concrete code" +msgstr "在具体代码中保留显式生命周期顺序与清理责任" + +msgid "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-" +"config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-re" +"d;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-con" +"fig-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ri" +"ng-red;build/quality/soc-ip-config-r32/uart-ring-green" +msgstr "" +"build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config" +"-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;bu" +"ild/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-" +"config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-re" +"d;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-con" +"fig-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ri" +"ng-red;build/quality/soc-ip-config-r32/uart-ring-green" + +msgid "" +"Selected I2C V2, F103/GD303 UART DMA and legacy ADC/PWM/Flash cold " +"integration lives in concrete C preparation with existing stop paths. " +"U575 engine stop precedes clock release. UART AHB DMA gate and Platform " +"ordering remain generated; legacy I2C V1 and other peripherals retain " +"family-emitted behavior. Focused suites overlap and are not additive or " +"whole-platform qualification." +msgstr "" +"选定 I2C V2、F103/GD303 UART DMA 和 legacy ADC/PWM/Flash 冷态集成已进入具体 C " +"prepare,保留已有 stop 路径。U575 引擎停止先于时钟归还。UART AHB DMA 门控及 Platform " +"顺序仍由生成器产生;legacy I2C V1 和其他外设仍由系列生成行为。定向套件重叠,不能相加或视为整个平台资格。" + +msgid "R35" +msgstr "R35" + +msgid "Retained failure and retry contracts" +msgstr "失败后保留与重试合同" + +msgid "" +"Preserve partial effects and borrows until proved drain or explicit " +"retry" +msgstr "直到证明已排空或显式重试前,保留部分副作用与借用" + +msgid "soc;drivers;tests/google" +msgstr "soc;drivers;tests/google" + +msgid "" +"Selected I2C V2, UART DMA and legacy ADC/PWM/Flash live/retained guards," +" failed-drain responsibility and stop/retry contracts passed focused " +"regressions. UART guards precede config/pin mutation and include " +"outstanding wake/RX loans; failed withdrawal retains config and borrowed" +" storage. Counts across focused suites overlap; final-source and " +"physical qualification are separate." +msgstr "" +"选定 I2C V2、UART DMA 和 legacy ADC/PWM/Flash 的 live/retained 守卫、排空失败责任及 " +"stop/retry 合同通过定向回归。UART 守卫先于配置/引脚变更,并包含尚存 wake/RX " +"loan;撤销失败保留配置和借用存储。定向套件计数重叠;最终源码与物理资格分别建立。" + +msgid "R36" +msgstr "R36" + +msgid "Split legacy ADC PWM and Flash integration" +msgstr "拆分 legacy ADC、PWM 与 Flash 集成" + +msgid "" +"Separate exact SoC integration from reusable algorithms and narrow " +"contracts" +msgstr "分离精确 SoC 集成与可复用算法,缩窄合同" + +msgid "" +"soc/stm32/stm32f103/drivers/devices.c;soc/gd32/gd32f303/drivers/devices." +"c;drivers/adc/legacy_adc;drivers/timer/legacy_pwm;drivers/flash/legacy_f" +"lash" +msgstr "" +"soc/stm32/stm32f103/drivers/devices.c;soc/gd32/gd32f303/drivers/devices." +"c;drivers/adc/legacy_adc;drivers/timer/legacy_pwm;drivers/flash/legacy_f" +"lash" + +msgid "final_P_native_arm_passed" +msgstr "final_P_native_arm_passed" + +msgid "" +"build/quality/soc-ip-config-r36/legacy-high-green;build/quality/soc-ip-c" +"onfig-r36/legacy-python-green.log;build/quality/soc-ip-config-r36/legacy" +"-final-green;build/quality/soc-ip-candidate/legacy-devices/report.json;b" +"uild/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quali" +"ty/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip" +"-candidate/completion-summary.json;build/quality/soc-ip-candidate/source" +"-snapshot.json;/workspace/nexus-examples/build/platform-software-recover" +"y-92e669233957/providers/mainstream_assembly/report.json;/workspace/nexu" +"s-examples/build/platform-candidate-92e669233957/completion-2/host_contr" +"acts.json" +msgstr "" +"build/quality/soc-ip-config-r36/legacy-high-green;build/quality/soc-ip-c" +"onfig-r36/legacy-python-green.log;build/quality/soc-ip-config-r36/legacy" +"-final-green;build/quality/soc-ip-candidate/legacy-devices/report.json;b" +"uild/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quali" +"ty/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip" +"-candidate/completion-summary.json;build/quality/soc-ip-candidate/source" +"-snapshot.json;/workspace/nexus-examples/build/platform-software-recover" +"y-92e669233957/providers/mainstream_assembly/report.json;/workspace/nexu" +"s-examples/build/platform-candidate-92e669233957/completion-2/host_contr" +"acts.json" + +msgid "" +"F103/GD303 concrete C ADC/PWM/Flash prepare entries and existing shared " +"close paths implemented. Final two factory targets: 11 Google cases, " +"including full independent 128-sector geometry, live rebind, failed " +"stop/retry and failed initialization cleanup/restart; GD303 foreign " +"oversampling stays unchanged on BUSY. Earlier 6-target/97-case and " +"40-Python results overlap and are not additive. Four exact legacy ARM " +"builds and two independent Flash-only retention consumers also passed on" +" the 184-file dirty development overlay. This is development evidence, " +"not final clean-source qualification; other peripheral emission is " +"unchanged. Final clean P separately passed all four Native modes and the" +" four f103/gd303 devices baremetal/FreeRTOS fixtures at " +"mainstream_assembly indices 0/1/8/9. The two extra Flash-only consumers " +"remain development observations; they were not rerun or relabeled as P." +msgstr "" +"已实现 F103/GD303 具体 C ADC/PWM/Flash prepare 入口和已有共享 close 路径。最终两个 factory " +"目标执行 11 个 Google 用例,包括完整独立 128 扇区几何、活动重绑定、停止失败/重试、初始化失败清理/重启;GD303 外部 " +"oversampling 在 BUSY 时保持不变。此前 6 目标/97 用例与 40 个 Python 结果存在重叠,不能相加。四份精确 " +"legacy ARM 构建及两个独立 Flash-only 链接保留消费者也在 184 文件脏开发覆盖层上通过。这是开发证据,不是最终 " +"clean-source 资格;其他外设生成逻辑未变。最终干净 P 另通过全部四种 Native 模式,以及 " +"mainstream_assembly 索引 0/1/8/9 的四份 f103/gd303 devices 裸机/FreeRTOS " +"夹具。额外两个 Flash-only 消费者保留为开发观察,没有重跑或重标为 P。" + +msgid "R37" +msgstr "R37" + +msgid "build_integration_and_docs" +msgstr "build_integration_and_docs" + +msgid "Review CMake private includes and Runtime boundary" +msgstr "审查 CMake 私有 include 与 Runtime 边界" + +msgid "" +"Selected targets retain source ownership and vendor includes stay " +"private" +msgstr "选定 target 保持源码所有权,厂家 include 留在私有范围" + +msgid "soc;drivers;cmake" +msgstr "soc;drivers;cmake" + +msgid "" +"build/soc-ip-layout/integration-audit/runtime-green.log;build/soc-ip-lay" +"out/integration-audit/cmake/observations.json" +msgstr "" +"build/soc-ip-layout/integration-audit/runtime-green.log;build/soc-ip-lay" +"out/integration-audit/cmake/observations.json" + +msgid "" +"Three real RED Runtime inclusion regressions then 18 tests GREEN. Eight " +"family consumer include closures show no private leakage; two Cortex-M4 " +"baremetal/FreeRTOS Ninja/ARM configurations exclude SoC/driver/Board " +"objects. This audit configured targets; it did not build production " +"firmware." +msgstr "" +"实际执行三个 Runtime 纳入回归的 RED,随后 18 个测试 GREEN。八个系列消费者 include 闭包无私有泄漏;两个 " +"Cortex-M4 裸机/FreeRTOS Ninja/ARM 配置排除 SoC/driver/Board " +"对象。本次审计配置了目标,没有构建生产固件。" + +msgid "R38" +msgstr "R38" + +msgid "Converge SDK transport manifest" +msgstr "收敛 SDK 传输清单" + +msgid "Use required payload closure without redundant source authorities" +msgstr "使用必需 payload 闭包,不增加重复源码权威" + +msgid "" +"build/soc-ip-layout/integration-audit/sdk-closure-red.log;build/soc-ip-l" +"ayout/integration-audit/sdk-closure-green.log;build/soc-ip-layout/integr" +"ation-audit/required-comparison.json" +msgstr "" +"build/soc-ip-layout/integration-audit/sdk-closure-red.log;build/soc-ip-l" +"ayout/integration-audit/sdk-closure-green.log;build/soc-ip-layout/integr" +"ation-audit/required-comparison.json" + +msgid "" +"48 selection/admission/descriptor tests passed. SDK requirements reuse " +"public source_names and descriptor facts/payload; no prior required " +"member removed. Additional declared family helpers are included " +"dynamically. Actual final P relocated SDK execution is recorded in R45." +msgstr "" +"48 个 selection/admission/descriptor 测试通过。SDK 必需项复用公共 source_names 和 " +"descriptor facts/payload;未删除任何旧必需成员。额外声明的系列辅助模块动态纳入。最终 P 的实际搬移 SDK 执行记在 " +"R45。" + +msgid "R39" +msgstr "R39" + +msgid "Incremental configuration transaction regressions" +msgstr "增量配置事务回归" + +msgid "" +"Exercise changed-module invalidation and failed reconfiguration through " +"existing gates" +msgstr "通过已有门禁覆盖模块变更失效及重新配置失败" + +msgid "tools/configure/tests;tools/testing" +msgstr "tools/configure/tests;tools/testing" + +msgid "" +"build/soc-ip-layout/facts-ir/incremental-green.log;build/soc-ip-layout/f" +"acts-ir/incremental-receipt.json;build/soc-ip-layout/facts-ir/integratio" +"n-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log" +msgstr "" +"build/soc-ip-layout/facts-ir/incremental-green.log;build/soc-ip-layout/f" +"acts-ir/incremental-receipt.json;build/soc-ip-layout/facts-ir/integratio" +"n-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log" + +msgid "" +"Fresh Native consumer CMake/Ninja configure/build and no-change rebuild " +"executed; changing a declared family module triggered CMake and selected" +" regeneration. All receipt commands exit zero. Separate 143-test " +"integration/bundle suite covers transaction boundaries; not a fresh " +"final-source SDK or full firmware matrix qualification." +msgstr "" +"实际执行了 fresh Native consumer 的 CMake/Ninja 配置/构建和无变更重建;修改已声明系列模块会触发 CMake" +" 与选定重新生成。全部收条中的命令退出为零。独立 143 项 integration/bundle 套件覆盖事务边界;这不是最终源码的 " +"fresh SDK 或完整固件矩阵资格。" + +msgid "R40" +msgstr "R40" + +msgid "Existing CLI diagnostics" +msgstr "已有 CLI 诊断" + +msgid "" +"Existing commands report exact provider inputs and failures without a " +"new framework" +msgstr "已有命令报告精确 provider 输入和失败,不增加新框架" + +msgid "tools/configure/configure.py;tools/configure/experience.py" +msgstr "tools/configure/configure.py;tools/configure/experience.py" + +msgid "" +"build/soc-ip-layout/integration-audit/diagnostic-red.log;build/soc-ip-la" +"yout/integration-audit/diagnostic-green.log;build/soc-ip-layout/integrat" +"ion-audit/cli/summary.json" +msgstr "" +"build/soc-ip-layout/integration-audit/diagnostic-red.log;build/soc-ip-la" +"yout/integration-audit/diagnostic-green.log;build/soc-ip-layout/integrat" +"ion-audit/cli/summary.json" + +msgid "" +"35 focused tests passed; eight exact provider-check runs emitted static " +"bundles and identity mismatch rejected. provider_module_missing applies " +"only to the selected entry/parent package; other import errors remain " +"explicit. No compile/link or physical qualification." +msgstr "" +"35 项定向测试通过;八个精确 provider-check 实际生成静态 " +"bundle,并拒绝身份不匹配。provider_module_missing " +"只适用于选定入口/父包;其他导入错误仍显式报告。不建立编译/链接或物理资格。" + +msgid "R41" +msgstr "R41" + +msgid "Bilingual documentation diagrams and porting migration" +msgstr "双语文档、图与接入迁移" + +msgid "" +"Current source maps; diagram links; READMEs and reviewed gettext match " +"implementation" +msgstr "当前源码映射、图链接、README 和经审查 gettext 与实现一致" + +msgid "" +"README.md;README_CN.md;drivers/README.md;soc/README.md;soc/common/README" +".md;tools/configure/README.md;docs/explanation;docs/guides/provider-port" +"ing.md;docs/design;docs/delivery/architecture.md;docs/assets/diagrams/RE" +"ADME.md;docs/locale" +msgstr "" +"README.md;README_CN.md;drivers/README.md;soc/README.md;soc/common/README" +".md;tools/configure/README.md;docs/explanation;docs/guides/provider-port" +"ing.md;docs/design;docs/delivery/architecture.md;docs/assets/diagrams/RE" +"ADME.md;docs/locale" + +msgid "clean_P_docs_and_D_review_passed" +msgstr "clean_P_docs_and_D_review_passed" + +msgid "" +"build/soc-ip-layout/documentation/phase1-clean-build.json;build/soc-ip-l" +"ayout-docs-review.json;build/soc-ip-layout/documentation/phase2-source-s" +"can-final.json;build/soc-ip-layout/documentation/phase2-diagram-check.js" +"on;build/soc-ip-layout/documentation/phase2-diagram-review.json;build/so" +"c-ip-layout/documentation/phase2-po-independent-review.json;build/soc-ip" +"-layout/documentation/phase2-review-admission.json;build/soc-ip-layout/d" +"ocumentation/phase2-translations-final.json;build/soc-ip-layout-docs-92e" +"669233957/documentation-build.json;build/soc-ip-layout/documentation/fin" +"al-d-extraction/extraction.json;build/soc-ip-layout/documentation/final-" +"d-source-check.json;build/soc-ip-layout/documentation/final-d-po-indepen" +"dent-review.json;build/soc-ip-layout/documentation/final-d-review-admiss" +"ion.json;build/soc-ip-layout/documentation/final-d-translations.json" +msgstr "" +"build/soc-ip-layout/documentation/phase1-clean-build.json;build/soc-ip-l" +"ayout-docs-review.json;build/soc-ip-layout/documentation/phase2-source-s" +"can-final.json;build/soc-ip-layout/documentation/phase2-diagram-check.js" +"on;build/soc-ip-layout/documentation/phase2-diagram-review.json;build/so" +"c-ip-layout/documentation/phase2-po-independent-review.json;build/soc-ip" +"-layout/documentation/phase2-review-admission.json;build/soc-ip-layout/d" +"ocumentation/phase2-translations-final.json;build/soc-ip-layout-docs-92e" +"669233957/documentation-build.json;build/soc-ip-layout/documentation/fin" +"al-d-extraction/extraction.json;build/soc-ip-layout/documentation/final-" +"d-source-check.json;build/soc-ip-layout/documentation/final-d-po-indepen" +"dent-review.json;build/soc-ip-layout/documentation/final-d-review-admiss" +"ion.json;build/soc-ip-layout/documentation/final-d-translations.json" + +msgid "" +"Phase-1 clean 7b985 strict bilingual build and independent 18-catalog " +"review retained. Phase-2 current content complete: FIG-31 independent " +"source/visual review, locked diagram verification, 131-page source scan," +" actual gettext extraction and whole-site 262 locale checks passed. Nine" +" catalogs independently reviewed: 101 new messages and 945 unchanged " +"reviewed entries. Clean P strict bilingual build passed with 262 locale " +"checks; its receipt has qualification=false and SHA256 " +"acdca47f4ccf9f1438a8c50f25dd8bea4135821922da202181a5237738ff8166. This " +"later documentation-only update records P; its actual gettext/review and" +" clean-D build are separate documentation evidence, not firmware or " +"physical qualification. D content validation actually passed: fresh " +"locked gettext, 131-page source scan, independent semantic review of 22 " +"new messages with 127 baseline pairs unchanged, and 262 whole-site " +"locale checks. Clean-D strict build is a separate postcommit receipt; no" +" future success or D self-hash is recorded here." +msgstr "" +"保留第一阶段干净 7b985 严格双语构建与独立 18 份 catalog 审查。第二阶段当前内容完成:FIG-31 " +"独立源码/视觉审查、锁定图验证、131 页源码扫描、实际 gettext 提取及全站 262 项 locale 检查通过。九份 catalog " +"独立审查:101 条新消息及 945 条未变的已审条目。干净 P 严格双语构建及 262 项 locale 检查通过;收条 " +"qualification=false,SHA256 为 acdca47f4ccf9f1438a8c50f25dd8bea4135821922d" +"a202181a5237738ff8166。后来的纯文档更新只记录 P;其实际 gettext/审查和 clean-D " +"构建是独立文档证据,不是固件或物理资格。D 内容校验实际通过:fresh locked gettext、131 页源码扫描、独立语义审查 22 " +"条新消息(127 条基线消息对未变)和 262 项全站 locale 检查。Clean-D 严格构建是独立提交后收条;此处未预先记录未来成功或 " +"D 自身摘要。" + +msgid "R42" +msgstr "R42" + +msgid "Examples candidate consumption" +msgstr "示例消费候选" + +msgid "Consume the concrete candidate in the external examples repository" +msgstr "在独立外部示例仓库消费具体候选" + +msgid "X-Gen-Lab/nexus-examples" +msgstr "X-Gen-Lab/nexus-examples" + +msgid "executed_passed" +msgstr "executed_passed" + +msgid "" +"/workspace/nexus-examples/build/local-ci-soc-ip-layout-92e669233957/exec" +"ution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e6" +"69233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples" +"/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json;" +".git/local-recovery/soc-ip-layout/examples-main-merge.json" +msgstr "" +"/workspace/nexus-examples/build/local-ci-soc-ip-layout-92e669233957/exec" +"ution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e6" +"69233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples" +"/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json;" +".git/local-recovery/soc-ip-layout/examples-main-merge.json" + +msgid "" +"Exact P consumed by clean examples " +"0f15022e4d5f5c51991b134d0b9d6082dcf88de9, tree " +"bb60f413b911ec212b59b77cea1734971eddbd9f. Complete canonical local CI " +"passed: Native, installed SDK consumer, 16 ARM presets and 32 " +"main-matrix ELFs for platform_reference/uart_owner_runtime; dedicated " +"example targets remain separately recorded. Formal lock and Gitlink " +"match P; candidate_validation=false. Ordinary hook retained 80 tool " +"tests and 15 CTest / 69 Google cases with no skips. PR15 merged as " +"bd3b90f135600b6405459347cb9e2cff5692d2e3 with the same examples tree. " +"This is exact code-candidate consumption, not physical execution." +msgstr "" +"干净 examples 0f15022e4d5f5c51991b134d0b9d6082dcf88de9(tree " +"bb60f413b911ec212b59b77cea1734971eddbd9f)消费精确 P。完整规范本地 CI 通过:Native、已安装 " +"SDK consumer、16 个 ARM preset 及 platform_reference/uart_owner_runtime 的 " +"32 个主矩阵 ELF;专用示例目标单独记录。正式 lock 与 Gitlink 匹配 " +"P;candidate_validation=false。普通 hook 保留 80 个工具测试和 15 CTest/69 Google " +"用例,零跳过。PR15 合并为 bd3b90f135600b6405459347cb9e2cff5692d2e3,examples tree " +"相同。这是精确代码候选消费,不是物理执行。" + +msgid "R43" +msgstr "R43" + +msgid "verification_and_delivery" +msgstr "verification_and_delivery" + +msgid "Four Native modes and complete tool suites" +msgstr "四种 Native 模式与完整工具套件" + +msgid "" +"Execute maintained modes and complete applicable tool domains with fresh" +" nonempty reports" +msgstr "执行维护模式和全部适用工具域,使用新鲜且非空报告" + +msgid "tests/google;tools/dev;tools/configure/tests;tools/testing" +msgstr "tests/google;tools/dev;tools/configure/tests;tools/testing" + +msgid "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/tool-tests.json;/workspace/nexus-examples/build/platform-sof" +"tware-cleanenv-92e669233957/configure-tests.json;/workspace/nexus-exampl" +"es/build/platform-software-cleanenv-92e669233957/ci-tests.json;/workspac" +"e/nexus-examples/build/platform-software-cleanenv-92e669233957/workflow-" +"tests.json;/workspace/nexus-examples/build/platform-candidate-92e6692339" +"57/completion-2/host_contracts.json;build/soc-ip-layout/phase2-hook-evid" +"ence/manifest.json" +msgstr "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/tool-tests.json;/workspace/nexus-examples/build/platform-sof" +"tware-cleanenv-92e669233957/configure-tests.json;/workspace/nexus-exampl" +"es/build/platform-software-cleanenv-92e669233957/ci-tests.json;/workspac" +"e/nexus-examples/build/platform-software-cleanenv-92e669233957/workflow-" +"tests.json;/workspace/nexus-examples/build/platform-candidate-92e6692339" +"57/completion-2/host_contracts.json;build/soc-ip-layout/phase2-hook-evid" +"ence/manifest.json" + +msgid "" +"Tested clean P=92e669233957a2e450a528394e8f4dd0794c7049. All four Native" +" modes debug/release/ASan-UBSan/TSan actually passed: each 372 CTest and" +" 6396 Google cases, zero failures/errors/skips. Complete applicable tool" +" domains passed: testing 1527, configure 1071, ci 490, workflows 159 " +"(3247 total); other tool domains are not inferred from these four. " +"Original executions are retained in candidate evidence. A remains " +"interrupted/running after its 90 completed stages; this row closes the " +"actual completed Native/tool scopes, not a single full-plan pass. The " +"separate ordinary complete-index hook archive records 3247 tools and 352" +" Google programs / 6396 cases." +msgstr "" +"受测干净 P=92e669233957a2e450a528394e8f4dd0794c7049。四种 Native 模式 " +"debug/release/ASan-UBSan/TSan 均实际通过:每种 372 CTest 和 6396 Google " +"用例,零失败/错误/跳过。全部适用工具域通过:testing 1527、configure 1071、ci 490、workflows " +"159(共 3247);不从这四域推断其他工具域。原始执行保存在候选证据中。A 在完成 90 阶段后仍保留 " +"interrupted/running;本行关闭已实际完成的 Native/工具范围,不表示一次完整计划通过。独立普通完整暂存区 hook " +"归档记录 3247 个工具测试和 352 个 Google 程序/6396 个用例。" + +msgid "R44" +msgstr "R44" + +msgid "ARM and Runtime matrix" +msgstr "ARM 与 Runtime 矩阵" + +msgid "Compile and link exact maintained ARM and CPU Runtime configurations" +msgstr "编译链接精确维护的 ARM 与 CPU Runtime 配置" + +msgid "tests/contracts;tools/measurement" +msgstr "tests/contracts;tools/measurement" + +msgid "segmented_execution_passed" +msgstr "segmented_execution_passed" + +msgid "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/interruption-observation.json;/workspace/nexus-examples/buil" +"d/platform-software-cleanenv-92e669233957/cortex_runtime/report.json;/wo" +"rkspace/nexus-examples/build/platform-software-recovery-92e669233957/rep" +"ort.json;/workspace/nexus-examples/build/platform-candidate-92e669233957" +"/completion-2/host_contracts.json;/workspace/nexus-examples/build/platfo" +"rm-candidate-92e669233957/completion-2/candidate-portable.json" +msgstr "" +"/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/" +"report.json;/workspace/nexus-examples/build/platform-software-cleanenv-9" +"2e669233957/interruption-observation.json;/workspace/nexus-examples/buil" +"d/platform-software-cleanenv-92e669233957/cortex_runtime/report.json;/wo" +"rkspace/nexus-examples/build/platform-software-recovery-92e669233957/rep" +"ort.json;/workspace/nexus-examples/build/platform-candidate-92e669233957" +"/completion-2/host_contracts.json;/workspace/nexus-examples/build/platfo" +"rm-candidate-92e669233957/completion-2/candidate-portable.json" + +msgid "" +"Tested P=92e669233957a2e450a528394e8f4dd0794c7049: original maintained " +"plan has actual exact segmented coverage A90+B69. A retains running and " +"no source_after after environment interruption; B executed_passed with " +"the exact original suffix, zero stage exits and unchanged clean source. " +"B report SHA256 " +"f8ecf185eadbef7e33d7a667366da7de34241ce243a5d417b5b453ae3bb99694. All " +"original names/order/checkers/args/timeouts and raw identities were " +"checked; no merged executed_passed report was invented. B reran all 865 " +"tidy and 865 cppcheck contexts. CPU Runtime passed 104 variants with " +"source_inputs_stable=true; its full_platform_status stays not_qualified " +"and expected rejections are separate from positive images. " +"ARM/provider/retention/resource fixtures retain their exact original " +"reports; linked software does not establish MCU execution." +msgstr "" +"受测 P=92e669233957a2e450a528394e8f4dd0794c7049:原维护计划具有实际精确分段覆盖 " +"A90+B69。环境中断后 A 保留 running 且没有 source_after;B 按原精确后缀 " +"executed_passed,阶段退出全部为零,干净源码未变。B 报告 SHA256 为 f8ecf185eadbef7e33d7a66736" +"6da7de34241ce243a5d417b5b453ae3bb99694。全部原名称/顺序/checker/参数/超时和原始身份已核对,没有" +"构造合并 executed_passed 报告。B 重跑全部 865 个 tidy 和 865 个 cppcheck 上下文。CPU " +"Runtime 104 个变体通过,source_inputs_stable=true;full_platform_status 仍为 " +"not_qualified,预期拒绝与正向镜像分开。ARM/provider/retention/resource " +"夹具保留精确原报告;链接通过不建立 MCU 执行。" + +msgid "R45" +msgstr "R45" + +msgid "Relocated SDK integrity" +msgstr "搬移 SDK 完整性" + +msgid "Verify required installed members and independent relocated consumers" +msgstr "验证必需已安装成员及独立搬移消费者" + +msgid "" +"build/soc-ip-layout/sdk-relocation-92e669233957/execution.json;/workspac" +"e/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-example" +"s-validation-evidence.json;/workspace/nexus-examples/build/platform-cand" +"idate-92e669233957/completion-2/source_sdk.json;/workspace/nexus-example" +"s/build/platform-candidate-92e669233957/completion-2/verify_relocated_sd" +"k.command.json;/workspace/nexus-examples/build/platform-candidate-92e669" +"233957/completion-2/candidate-portable.json" +msgstr "" +"build/soc-ip-layout/sdk-relocation-92e669233957/execution.json;/workspac" +"e/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-example" +"s-validation-evidence.json;/workspace/nexus-examples/build/platform-cand" +"idate-92e669233957/completion-2/source_sdk.json;/workspace/nexus-example" +"s/build/platform-candidate-92e669233957/completion-2/verify_relocated_sd" +"k.command.json;/workspace/nexus-examples/build/platform-candidate-92e669" +"233957/completion-2/candidate-portable.json" + +msgid "" +"Publishable source SDK binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049 and its exact tree. Actual " +"package verification and independent relocated consumers passed at " +"platform-sdk-92e669233957/SDK relocated, including Native C/C++, Runtime" +" and maintained firmware selections; source closure and exported inputs " +"retain original member hashes. The independent SDK/examples archive " +"preserves those executions. Candidate completion-2 reverified the " +"relocated SDK with exit 0. Initial completion used an incorrect verifier" +" root and failed; its original failed receipt remains, followed by the " +"actual corrected execution. No source change or guard weakening was " +"used." +msgstr "" +"可发布源码 SDK 绑定干净 P=92e669233957a2e450a528394e8f4dd0794c7049 及其精确 tree。在 " +"platform-sdk-92e669233957/SDK relocated 实际包验证与独立搬移消费通过,覆盖 Native " +"C/C++、Runtime 和维护的固件选择;源码闭包与导出输入保留原成员摘要。独立 SDK/examples 归档保留这些执行。候选 " +"completion-2 以退出码 0 再验证搬移 SDK。最初 completion 使用错误 verifier " +"根目录并失败;原失败收条保留,随后是实际修正执行。没有源码变化或守卫削弱。" + +msgid "R46" +msgstr "R46" + +msgid "Resource retention and cost comparison" +msgstr "资源保留与成本比较" + +msgid "Compare actual Flash RAM ops and IRQ retention for the same selections" +msgstr "对相同选择比较实际 Flash、RAM、ops 与 IRQ 保留" + +msgid "final_P_comparison_passed" +msgstr "final_P_comparison_passed" + +msgid "" +"build/quality/soc-ip-candidate/cost-summary.json;build/quality/soc-ip-ca" +"ndidate/uart/report.json;build/quality/soc-ip-candidate/legacy-devices/r" +"eport.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json" +";build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/qu" +"ality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-cand" +"idate/source-snapshot.json;build/quality/soc-ip-final-p-92e669233957/fin" +"al-cost-summary.json;build/quality/soc-ip-final-p-92e669233957/final-cos" +"t-summary.csv;build/quality/soc-ip-cost-package-92e669233957/package.jso" +"n;build/quality/soc-ip-cost-package-92e669233957/artifact-summary.json" +msgstr "" +"build/quality/soc-ip-candidate/cost-summary.json;build/quality/soc-ip-ca" +"ndidate/uart/report.json;build/quality/soc-ip-candidate/legacy-devices/r" +"eport.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json" +";build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/qu" +"ality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-cand" +"idate/source-snapshot.json;build/quality/soc-ip-final-p-92e669233957/fin" +"al-cost-summary.json;build/quality/soc-ip-final-p-92e669233957/final-cos" +"t-summary.csv;build/quality/soc-ip-cost-package-92e669233957/package.jso" +"n;build/quality/soc-ip-cost-package-92e669233957/artifact-summary.json" + +msgid "" +"Historical 184-file dirty development overlay comparisons remain " +"separately identified in their original reports. Final clean P versus " +"7b985: all 11 strict comparisons passed from actual A/B build artifacts " +"with independent ELF/map/IRQ review; no rebuild for path normalization. " +"I2C total loaded Flash delta G474/H743/U575 = -4/+40/+64 B, composed of " +"code/alignment +108/+152/+224 B, readonly config +24 B each, " +"path-pool/alignment -136/-136/-184 B; reserved RAM -24/-16/-24 B and " +"state 28 to 8 B. Eight UART totals range +168 to +392 B Flash, with " +"code/alignment +336 to +384 B, readonly config 60 B and negative " +"source-path/alignment contributions; all reserve 48 B less RAM, TX state" +" 96 to 48 B and duplex 144 to 96 B. Source-root string reductions are " +"not architecture savings. UART selected IRQ code at most +2 B/one static" +" instruction, up to two config loads; duplex channel local push frame " +"grows 16 to 24 B while MSP reservation is unchanged. No new selected IRQ" +" indirect call; cycles/WCET/dynamic stack high-water are not measured. " +"Portable cost evidence archive SHA256 " +"fb0f63bcae83f7a5b278a311f67107bf4f8c99fcee0014638b0fb26c0936567f, " +"4035953 B / 1184 files; original source/tool/SDK inputs remain explicit " +"external dependencies." +msgstr "" +"184 文件脏开发覆盖层的历史比较在原报告中单独标识。最终干净 P 对比 7b985:11 组严格比较均基于实际 A/B 构建产物通过,并经独立" +" ELF/map/IRQ 审查;未为了路径规范化重建。I2C 加载 Flash 总变化 G474/H743/U575 = -4/+40/+64 " +"B,组成为代码/对齐 +108/+152/+224 B、每项只读配置 +24 B、路径池/对齐 -136/-136/-184 B;预留 RAM " +"-24/-16/-24 B,状态 28→8 B。八组 UART 的 Flash 总变化为 +168 至 +392 B,含代码/对齐 +336 至" +" +384 B、只读配置 60 B,以及负的源码路径/对齐贡献;预留 RAM 均减少 48 B,TX 状态 96→48 B、双向状态 " +"144→96 B。源码根路径字符串缩短不是架构节省。UART 选定 IRQ 代码最多增加 2 B/一条静态指令、最多两次配置读取;双向 " +"channel 的局部 push 栈帧从 16 增至 24 B,MSP 预留不变。选定 IRQ " +"没有新增间接调用;cycles/WCET/动态栈高水位未测量。可搬移成本证据归档 SHA256 为 " +"fb0f63bcae83f7a5b278a311f67107bf4f8c99fcee0014638b0fb26c0936567f,4035953" +" B/1184 文件;原源码/工具/SDK 输入仍是显式外部依赖。" + +msgid "R47" +msgstr "R47" + +msgid "Independent review main integration and formal examples pin" +msgstr "独立审查、主干集成与正式示例 pin" + +msgid "" +"Complete review and ordinary gates before approved integration and exact" +" examples pin" +msgstr "批准集成和精确示例 pin 前完成审查与普通门禁" + +msgid "AGENTS.md;X-Gen-Lab/nexus-examples" +msgstr "AGENTS.md;X-Gen-Lab/nexus-examples" + +msgid "integrated_reviewed" +msgstr "integrated_reviewed" + +msgid "" +"build/soc-ip-layout/r47-independent-review.json;build/soc-ip-layout/phas" +"e2-hook-evidence/manifest.json;build/soc-ip-layout/codeql-pr36-review/re" +"view.json;build/soc-ip-layout/codeql-pr36-review/comments.json;build/soc" +"-ip-layout/codeql-pr36-review/remote-status.json;.git/local-recovery/soc" +"-ip-layout/platform-main-merge.json;.git/local-recovery/soc-ip-layout/ex" +"amples-main-merge.json;.git/local-recovery/soc-ip-layout/platform-merge-" +"import-apply.json;.git/local-recovery/soc-ip-layout/examples-merge-impor" +"t-apply.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e" +"669233957/ordinary-hook-evidence.json" +msgstr "" +"build/soc-ip-layout/r47-independent-review.json;build/soc-ip-layout/phas" +"e2-hook-evidence/manifest.json;build/soc-ip-layout/codeql-pr36-review/re" +"view.json;build/soc-ip-layout/codeql-pr36-review/comments.json;build/soc" +"-ip-layout/codeql-pr36-review/remote-status.json;.git/local-recovery/soc" +"-ip-layout/platform-main-merge.json;.git/local-recovery/soc-ip-layout/ex" +"amples-main-merge.json;.git/local-recovery/soc-ip-layout/platform-merge-" +"import-apply.json;.git/local-recovery/soc-ip-layout/examples-merge-impor" +"t-apply.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e" +"669233957/ordinary-hook-evidence.json" + +msgid "" +"Independent production review completed without blocking findings; " +"normal complete-index hooks passed on the tree integrated as P. PR36 " +"actually merged to M=c79fd9672a9fe56379744d834cfccd822bc40be1, tree " +"exactly equal to P; PR15 merged to " +"bd3b90f135600b6405459347cb9e2cff5692d2e3, tree equal to tested examples " +"bb60f413b911ec212b59b77cea1734971eddbd9f, with formal lock/Gitlink P. " +"Actual signed GitHub commit objects and local lease/import receipts " +"retained. All 14 CodeQL review comments were assessed: no confirmed " +"production dangling/late binding or exercised test UAF, with nonblocking" +" maintenance suggestions retained; they are not all labeled false " +"positives. Remote Security Analysis 40388967544 CPP/Python jobs passed, " +"not a local CodeQL run or zero-alert/zero-defect claim. This " +"documentation-only follow-up does not retest M as a new software source." +msgstr "" +"独立生产审查完成,无阻塞发现;普通完整暂存区 hook 在集成为 P 的 tree 上通过。PR36 实际合并为 " +"M=c79fd9672a9fe56379744d834cfccd822bc40be1,tree 与 P 完全相同;PR15 合并为 " +"bd3b90f135600b6405459347cb9e2cff5692d2e3,tree 与受测 examples " +"bb60f413b911ec212b59b77cea1734971eddbd9f 相同,正式 lock/Gitlink 指向 P。保留实际签名 " +"GitHub commit 对象与本地 lease/import 收条。14 条 CodeQL " +"审查意见均已评估:未确认生产悬空/晚绑定或已执行测试 UAF,保留非阻塞维护建议;没有把全部意见标为误报。远程 Security " +"Analysis 40388967544 的 CPP/Python jobs 通过,这不是本地 CodeQL " +"执行或零告警/零缺陷声明。纯文档后续更新没有把 M 当新软件源码重新测试。" + +msgid "R48" +msgstr "R48" + +msgid "Clean software delivery and two fresh offline reproductions" +msgstr "干净软件交付与两次 fresh 离线复现" + +msgid "" +"Bind clean source and actual reproducible artifacts; physical HIL stays " +"not_executed" +msgstr "绑定干净源码和实际可复现产物;物理 HIL 保持 not_executed" + +msgid "tools/evidence;docs/design/soc-ip-layout-execution.csv" +msgstr "tools/evidence;docs/design/soc-ip-layout-execution.csv" + +msgid "software_qualified" +msgstr "software_qualified" + +msgid "" +"/workspace/nexus-examples/build/platform-candidate-92e669233957/reproduc" +"tion-quiet1.json;/workspace/nexus-examples/build/platform-candidate-92e6" +"69233957/completion-2/candidate/candidate.json;/workspace/nexus-examples" +"/build/platform-candidate-92e669233957/completion-2/candidate_verify.com" +"mand.json;/workspace/nexus-examples/build/platform-candidate-92e66923395" +"7/finish-candidate-r48-2.command.json;/workspace/nexus-examples/build/pl" +"atform-candidate-92e669233957/completion-2/candidate-portable.json;/work" +"space/nexus-examples/build/platform-candidate-92e669233957/completion-2/" +"execution-receipts.json;build/soc-ip-layout/qualification-timeout-92e669" +"233957/manifest.json;/workspace/nexus-examples/build/platform-software-c" +"leanenv-92e669233957/interruption-observation.json;/workspace/nexus-exam" +"ples/build/platform-software-recovery-92e669233957/report.json;/workspac" +"e/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-" +"r48.command.json;/workspace/nexus-examples/build/platform-software-final" +"-92e669233957/report.json;build/soc-ip-layout/software-92e669233957/repo" +"rt.json" +msgstr "" +"/workspace/nexus-examples/build/platform-candidate-92e669233957/reproduc" +"tion-quiet1.json;/workspace/nexus-examples/build/platform-candidate-92e6" +"69233957/completion-2/candidate/candidate.json;/workspace/nexus-examples" +"/build/platform-candidate-92e669233957/completion-2/candidate_verify.com" +"mand.json;/workspace/nexus-examples/build/platform-candidate-92e66923395" +"7/finish-candidate-r48-2.command.json;/workspace/nexus-examples/build/pl" +"atform-candidate-92e669233957/completion-2/candidate-portable.json;/work" +"space/nexus-examples/build/platform-candidate-92e669233957/completion-2/" +"execution-receipts.json;build/soc-ip-layout/qualification-timeout-92e669" +"233957/manifest.json;/workspace/nexus-examples/build/platform-software-c" +"leanenv-92e669233957/interruption-observation.json;/workspace/nexus-exam" +"ples/build/platform-software-recovery-92e669233957/report.json;/workspac" +"e/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-" +"r48.command.json;/workspace/nexus-examples/build/platform-software-final" +"-92e669233957/report.json;build/soc-ip-layout/software-92e669233957/repo" +"rt.json" + +msgid "" +"Sealed software candidate binds clean " +"P=92e669233957a2e450a528394e8f4dd0794c7049, exact configuration and " +"actual same ELF/BIN/map inputs. Existing six scopes passed: " +"host_contracts, arm_link, resource_budget, source_sdk, reproducibility, " +"hil_tooling; owned candidate verifier and parent completion-2 process " +"exit 0. Two fresh distinct network-none builds passed declared " +"container-boundary verification and bit_exact_elf_bin with no differing " +"artifacts; map bit equality is not claimed. Candidate SHA256 " +"35c8301ac95cdc7eee6b125e5ed8170ef44800bb6eb4995361d02c364660c060. " +"Portable archive SHA256 " +"7f89e8ec33a543e150e7165a3e4252aa11a96d62fe5d3187a9b0d4791aa618b4, " +"433850915 B / 6401 files. Signature unsigned; product promotion " +"not_performed; HIL not_executed. Maintained-plan evidence is exact " +"A90+B69 segmented coverage, not a single successful 159-stage run. " +"Original output-boundary, timeout, GIT_PAGER-admission, interruption and" +" initial SDK-root failures retain original outcomes; none was rewritten " +"as passed. D only records P and obtains its own separate documentation " +"receipt." +msgstr "" +"封存软件候选绑定干净 P=92e669233957a2e450a528394e8f4dd0794c7049、精确配置和实际同一 " +"ELF/BIN/map 输入。已有六个 scope 通过:host_contracts、arm_link、resource_budget、sou" +"rce_sdk、reproducibility、hil_tooling;owned candidate verifier 和父 " +"completion-2 进程退出码均为 0。两次 fresh、独立 network-none 构建通过声明的容器边界验证及 " +"bit_exact_elf_bin,产物无差异;不声称 map 字节相同。候选 SHA256 为 " +"35c8301ac95cdc7eee6b125e5ed8170ef44800bb6eb4995361d02c364660c060。可搬移归档 " +"SHA256 为 7f89e8ec33a543e150e7165a3e4252aa11a96d62fe5d3187a9b0d4791aa618b" +"4,433850915 B/6401 文件。签名 unsigned;产品提升 not_performed;HIL " +"not_executed。维护计划证据是精确 A90+B69 分段覆盖,不是一次成功的 159 阶段执行。原输出边界、超时、GIT_PAGER " +"准入、中断和初始 SDK 根路径失败保留原结果;没有重写为 passed。D 只记录 P,并取得自身独立文档收条。" diff --git a/docs/locale/zh_CN/LC_MESSAGES/status/software-verification.po b/docs/locale/zh_CN/LC_MESSAGES/status/software-verification.po index ce6ce5009..413949668 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/status/software-verification.po +++ b/docs/locale/zh_CN/LC_MESSAGES/status/software-verification.po @@ -1,16 +1,13 @@ -# Authored translation; independently semantically reviewed. +# Independently reviewed translations; exact canonical source identity below. msgid "" msgstr "" -"Project-Id-Version: Nexus source docs\n" -"Report-Msgid-Bugs-To: \n" -"Language-Team: Nexus agent-reviewed documentation\n" +"Project-Id-Version: Nexus documentation\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" "X-Nexus-Reviewed: true\n" -"X-Nexus-Source-SHA256: 11978a6523f423d8d4aa48b8442265713c9dbf09a7c8b03adebe7acbc9069091\n" +"X-Nexus-Source-SHA256: a1b25e155c63dd34ef73caa0cc272c9ace92fb7d59c8a3c304240cce142b64d7\n" #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:9 msgid "软件验证、源码身份与证据" @@ -76,9 +73,8 @@ msgstr "本次正式文档包的精确 commit/tree;不替代预览构建记录 msgid "被引用资格源码" msgstr "被引用资格源码" -#: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 -msgid "同一 manifest 的 `qualified_code` 与其结果索引" -msgstr "同一 manifest 的 `qualified_code` 与其结果索引" +msgid "schema 2 manifest 的 `qualification_references[]`;每项保存原候选身份与 scope" +msgstr "schema 2 manifest 的 `qualification_references[]`;每项保存原候选身份与 scope" #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 msgid "旧的精确代码执行;不是当前代码/文档 HEAD 自动继承的资格" @@ -94,6 +90,19 @@ msgstr "" "常量。未打包的工作树可执行 `git rev-parse HEAD` 核对 checkout,但命令输出本身不是 qualification " "receipt。" +msgid "" +"schema 2 不再隐式引用某个历史索引。打包者逐个显式选入原封存候选,工具调用 同一候选 verifier 核对原始闭包后记录 " +"source、configuration、ELF/BIN/map、 qualified scopes " +"与候选文件摘要。没有选入候选时列表为空,不表示软件失败。 文档包离线校验保护这份引用的字节和结构,不重新执行或代替原候选资格校验。 原 " +"locator 只说明当时的存储位置,不是公开下载地址。schema 1 历史包保留原 `qualified_code` " +"的只读校验,不能用它生产新的默认资格引用。" +msgstr "" +"schema 2 不再隐式引用某个历史索引。打包者逐个显式选入原封存候选,工具调用 同一候选 verifier 核对原始闭包后记录 " +"source、configuration、ELF/BIN/map、 qualified scopes " +"与候选文件摘要。没有选入候选时列表为空,不表示软件失败。 文档包离线校验保护这份引用的字节和结构,不重新执行或代替原候选资格校验。 原 " +"locator 只说明当时的存储位置,不是公开下载地址。schema 1 历史包保留原 `qualified_code` " +"的只读校验,不能用它生产新的默认资格引用。" + #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:31 msgid "已有独立结果索引" msgstr "已有独立结果索引" @@ -110,6 +119,25 @@ msgstr "精确代码源码" msgid "原权威及执行说明" msgstr "原权威及执行说明" +msgid "SoC/IP 目录整理与原候选" +msgstr "SoC/IP 目录整理与原候选" + +msgid "" +"P `92e669233957a2e450a528394e8f4dd0794c7049`;E " +"`0f15022e4d5f5c51991b134d0b9d6082dcf88de9`" +msgstr "" +"P `92e669233957a2e450a528394e8f4dd0794c7049`;E " +"`0f15022e4d5f5c51991b134d0b9d6082dcf88de9`" + +msgid "" +"[执行说明](../delivery/verification.md)、{download}`48 项原台账 <../design/soc-ip" +"-layout-execution.csv>`、[派生视图](ledgers/soc-ip-layout.md);同一 P 的 A90+B69 " +"分段覆盖,原候选只覆盖其精确配置,物理 HIL 未执行" +msgstr "" +"[执行说明](../delivery/verification.md)、{download}`48 项原台账 <../design/soc-ip" +"-layout-execution.csv>`、[派生视图](ledgers/soc-ip-layout.md);同一 P 的 A90+B69 " +"分段覆盖,原候选只覆盖其精确配置,物理 HIL 未执行" + #: ../../../../tmp/nexus-quality-20261019/docs/status/software-verification.md:0 msgid "开发体验迭代 P 与示例 E" msgstr "开发体验迭代 P 与示例 E" diff --git a/docs/reference/configuration/assembly.md b/docs/reference/configuration/assembly.md index b33db6c99..4bef8aa31 100644 --- a/docs/reference/configuration/assembly.md +++ b/docs/reference/configuration/assembly.md @@ -11,7 +11,15 @@ source_language: en The authored configuration authority is one schema-2 TOML assembly. SoC and Board facts remain maintained JSON; `resolved.json` is output, not an authored alternative. CMake owns software dependencies. Failed TOML resolution never falls back to Kconfig, -cache variables, schema-1 JSON or old generated headers. +cache variables, schema-1 JSON or old generated headers. Legacy JSON assemblies +are rejected as inputs, not retained as an alternate authoring interface. + +The parser creates immutable `AuthoredAssembly` intent. Production consumers use +`resolve_ir()` and named immutable `ConfigurationIR` decisions; only audit and +artifact-boundary consumers request `to_dict()`. Typed source epoch 2 and the +schema-1 JSON output projection are separate contracts. The +[source compatibility policy](../../design/source-compatibility.md) owns their +version declarations; neither introduces firmware metadata or runtime dispatch. Before running the resolver, prepare the [fixed source and recursive dependencies](../../guides/source-checkout.md). diff --git a/docs/reference/documentation-delivery.md b/docs/reference/documentation-delivery.md index 2c8e3a215..7cd868752 100644 --- a/docs/reference/documentation-delivery.md +++ b/docs/reference/documentation-delivery.md @@ -39,10 +39,25 @@ manifest 分开记录三类身份: - `documentation_source`:构建时完整 commit/tree 和文档目录的限定树摘要。 - `code_source`:同一 checkout 的完整 commit/tree 和平台代码目录的限定树摘要。 -- `qualified_code`:当前交付索引中原已验证代码的 commit/tree 及索引文件摘要。 - -前两项可能来自同一 Git 提交,但覆盖的目录不同。第三项引用既有软件证据,不能 -因文档提交而变成当前 HEAD 的新固件验证。`physical_status` 固定为 `not_executed`。 +- `qualification_references[]`:显式选入的原候选;每项保留自身源码、配置摘要、 + ELF/BIN/map 摘要及字节数、六个 qualified scopes 和候选文件身份。 + +前两项可能来自同一 Git 提交,但覆盖的目录不同。schema 2 的第三项是显式列表, +默认没有引用;不再从固定历史结果索引推断资格。每份候选必须先通过现有 +`tools.evidence.candidate.verified_projection()` 的完整原件校验;缺失或篡改原件、 +重复候选与非法 scope 在输出前拒绝。不同候选保持各自源码、配置及产物身份, +不能合并成全平台通过。`physical_status` 固定为 `not_executed`。 + +文档包只保存已校验的精简引用,不复制候选配置、源码 SDK 或大型原始日志。 +离线 `verify()` 校验该引用的结构和被封存字节,不重新校验未随包复制的候选闭包, +也不赋予当前 HEAD 新软件资格。原 candidate locator 是原主机位置,不是下载 URL。 +若需要重新确认软件候选,必须取回原件并运行原 candidate verifier。 +每项 `verification.at_packaging` 为 `original_candidate_closure`, +`verification.offline` 为 `reference_metadata_only`, +`verification.original_evidence_included` 为 `false`;离线包不伪报原件重验成功。 + +旧 schema 1 包仅保留只读验证/恢复,仍按原 `qualified_code` 字段解释。 +新打包只产生 schema 2,不能把旧索引身份默认为新交付的受测代码。 ## 文件与摘要合同 @@ -83,13 +98,18 @@ Python 调用接口如下,CLI 入口由统一 docs 命令提供: from tools.documentation import delivery result = delivery.package(build_dir, output_dir, source_root=source_root, - release_id=release_id) + release_id=release_id, + qualification_references=(candidate_path,)) manifest = delivery.verify(result["archive"], expected_manifest_sha256=result["manifest_sha256"]) delivery.restore(result["archive"], independent_root, expected_manifest_sha256=result["manifest_sha256"]) ``` +CLI 的 `--qualification-reference /path/to/candidate.json` 可重复使用,逐项核对 +不同精确候选;不提供该选项时保存空列表。显式引用原件缺失时应修复定位或明确 +不选入,工具不会悄悄降级为已验证引用。 + 这是接口说明片段,路径由调用方显式提供;可执行命令和实际验收结果以统一 docs CLI 及其本次交付 receipt 为准。 diff --git a/docs/status/ledgers.md b/docs/status/ledgers.md index ba1cc630c..9f2fde646 100644 --- a/docs/status/ledgers.md +++ b/docs/status/ledgers.md @@ -8,7 +8,7 @@ source_language: zh_CN (status-ledgers)= # 原台账、退出条件与派生视图 -下面的页面来自十份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 +下面的页面来自十一份原 CSV,不建立第二套状态数据库。每个任务保留原编号、顺序、 所有字段、退出条件、实现状态、软件/物理状态和原证据定位;源 SHA-256 可核对。 原 CSV 没有单独退出字段时明确说明,不能从其他台账拼造一条“已通过”的条件。 @@ -21,12 +21,17 @@ ledgers/os ledgers/platform-refinement ledgers/io-evolution ledgers/soc-refinement +ledgers/soc-ip-layout ledgers/build-system ledgers/documentation-system ledgers/developer-experience ledgers/platform-delivery-completion ``` +`soc-ip-layout` 的 R01–R48 保留目录和共享 IP 重构的原状态。R43–R48 记录 +精确 P 的软件执行、分段计划、SDK、资源与候选;其 `not_executed` 物理值不因 +页面生成改变。最新结果导航见 [软件证据入口](software-verification.md)。 + ## 正确解读状态 - `implemented` 表示实现存在;不保证当前 checkout 的完整软件资格。 diff --git a/docs/status/ledgers/soc-ip-layout.md b/docs/status/ledgers/soc-ip-layout.md new file mode 100644 index 000000000..1db42cb09 --- /dev/null +++ b/docs/status/ledgers/soc-ip-layout.md @@ -0,0 +1,797 @@ +--- +id: status.ledgers.soc-ip-layout +type: status +audience: maintainer +source_language: zh_CN +--- + +(status-ledgers-soc-ip-layout)= +# SoC/IP 目录整理台账 + +本页由原 CSV 派生,不是新的状态权威、执行记录或软件资格。 +原编号、顺序和全部原字段来自 [原台账](../../design/soc-ip-layout-execution.csv)。 +源文件:`docs/design/soc-ip-layout-execution.csv`;SHA-256:`0564d12a1125e718b7560defc8a7cbb184c77ebeb1b13ba1eea1331b2cac8fa4`。 + +不同台账范围重叠,不相加数量或计算全项目完成率。实现状态与软件、 +物理状态分别解释;`development_passed` 不表示当前文档 HEAD 已执行。 +`qualification_record_authority` 要回到精确结果索引判断源码和范围。 +绝对 `/workspace`、`/dev/shm` 定位保留原字节,仅说明原执行主机位置; +可取得归档及 member 定位见 [软件证据入口](../software-verification.md)。 + +**原退出字段**只有原 CSV 中实际存在的字段;任务、资格前提和合同链接 +也按原文保留,不能据此补造某次执行。 + +本表包含 48 条原记录,仅表示来源行数。 + +```{contents} +:local: +:depth: 1 +``` + +(status-soc-ip-layout-r01)= +## R01 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R01 | +| `work_package` | baseline | +| `requirement` | Branch and source baseline | +| `exit_condition` | Record actual source revision and branch before edits | +| `implementation_paths` | AGENTS.md | +| `implementation_status` | implemented | +| `software_status` | reviewed | +| `hardware_status` | not_executed | +| `evidence_paths` | .git/local-recovery/soc-ip-layout/baseline.json | +| `notes` | Local and GitHub refactor/soc-ip-layout branches start at 890a6026384b. | + +(status-soc-ip-layout-r02)= +## R02 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R02 | +| `work_package` | baseline | +| `requirement` | Per-file responsibility and destination map | +| `exit_condition` | Review every moved file and distinguish shared IP from SoC integration | +| `implementation_paths` | soc;drivers;tools/configure | +| `implementation_status` | implemented | +| `software_status` | reviewed | +| `hardware_status` | not_executed | +| `evidence_paths` | .git/local-recovery/soc-ip-layout/path-map.json | +| `notes` | Shared IP and 35 family production modules reviewed; import-normalized family AST unchanged. These are original migration-baseline identities and counts, not current P totals; final execution is recorded in R43-R48. | + +(status-soc-ip-layout-r03)= +## R03 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R03 | +| `work_package` | baseline | +| `requirement` | Exact platform mode resource and rejection baseline | +| `exit_condition` | Record maintained selections and rejection behavior without expanding support | +| `implementation_paths` | tools/configure;tests/contracts | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/baseline/configurations | +| `notes` | 136 exact assemblies including all 71 catalogue entries resolved in both source trees; no effective decision changes. Existing negative contracts remain enforced. These are original migration-baseline identities and counts, not current P totals; final execution is recorded in R43-R48. | + +(status-soc-ip-layout-r04)= +## R04 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R04 | +| `work_package` | baseline | +| `requirement` | Test identities and execution domains | +| `exit_condition` | Map retained cases to Native; tool; ARM and physical domains | +| `implementation_paths` | tests;tools/configure/tests;tools/testing | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration/case-mapping.json | +| `notes` | Original 1035 configure cases retained one-to-one; 132 family cases relocated; 8 migration regressions added. These are original migration-baseline identities and counts, not current P totals; final execution is recorded in R43-R48. | + +(status-soc-ip-layout-r05)= +## R05 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R05 | +| `work_package` | baseline | +| `requirement` | Flash RAM ops and IRQ baseline | +| `exit_condition` | Record actual linked resource and retention baseline for comparisons | +| `implementation_paths` | tools/measurement;tests/contracts | +| `implementation_status` | implemented | +| `software_status` | reviewed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/baseline/resources | +| `notes` | 32 prior resource reports from 16 presets retained with matching ELF hash/size. Historical baseline capture; not new execution or new-source resource qualification. Only resource reports were copied; ELF/map identities were observed at capture, not retained as new build evidence. Final P comparisons are recorded in R46. | + +(status-soc-ip-layout-r06)= +## R06 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R06 | +| `work_package` | baseline | +| `requirement` | Single execution ledger | +| `exit_condition` | Maintain exactly R01-R48 with implementation and execution separated | +| `implementation_paths` | docs/design/soc-ip-layout-execution.csv | +| `implementation_status` | implemented | +| `software_status` | reviewed | +| `hardware_status` | not_executed | +| `evidence_paths` | docs/design/soc-ip-layout-execution.csv | +| `notes` | One R01-R48 task record; implementation, software and physical status stay separate. Final software tested_source=P=92e669233957a2e450a528394e8f4dd0794c7049, tree=6e38d4c43d59330a0a9a9b7cac21f591d9c2719f; integrated M=c79fd9672a9fe56379744d834cfccd822bc40be1 has the same tree. This documentation update records P and does not claim its own software qualification. | + +(status-soc-ip-layout-r07)= +## R07 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R07 | +| `work_package` | infrastructure | +| `requirement` | Explicit family descriptors | +| `exit_condition` | Declare canonical source directories and configuration modules without discovery | +| `implementation_paths` | tools/configure/providers/descriptor.py | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration | +| `notes` | Explicit vendor/source/configuration directories; no family-prefix path inference. | + +(status-soc-ip-layout-r08)= +## R08 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R08 | +| `work_package` | infrastructure | +| `requirement` | Module loading and type identity | +| `exit_condition` | Explicit loading preserves common Python type identity and import boundaries | +| `implementation_paths` | tools/configure/providers;soc | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration | +| `notes` | Normal selected SoC packages and canonical shared types; six actual RED/GREEN loading regressions. | + +(status-soc-ip-layout-r09)= +## R09 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R09 | +| `work_package` | infrastructure | +| `requirement` | Input closure and freshness | +| `exit_condition` | Moved configuration dependencies remain in identity and stale-input rejection | +| `implementation_paths` | tools/configure/dependencies.py;tools/configure/bundle.py | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration | +| `notes` | All executing package initializers/helpers hashed; migrated sources protected from owned output overwrite. | + +(status-soc-ip-layout-r10)= +## R10 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R10 | +| `work_package` | infrastructure | +| `requirement` | SDK roots and required members | +| `exit_condition` | Source SDK includes declared SoC configuration and IP driver inputs | +| `implementation_paths` | cmake/package | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/sdk-driver-red.log;build/soc-ip-layout/sdk-driver-green.log;build/soc-ip-layout/sdk-payload.log;build/soc-ip-layout/sdk-selection.log | +| `notes` | New shared driver transport regression RED then GREEN; 20 device payload and 45 selection/portable tests pass. Actual final P installed consumers are recorded in R45. | + +(status-soc-ip-layout-r11)= +## R11 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R11 | +| `work_package` | infrastructure | +| `requirement` | Family test discovery | +| `exit_condition` | Retain and execute tests under tools/configure/tests/providers/<family> | +| `implementation_paths` | tools/configure/tests/providers | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration/case-mapping.json | +| `notes` | Family tests remain in existing tools/configure discovery domain; no new runner. | + +(status-soc-ip-layout-r12)= +## R12 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R12 | +| `work_package` | infrastructure | +| `requirement` | Style source capability and measurement coverage | +| `exit_condition` | Include drivers and SoC configuration in existing observers and gates | +| `implementation_paths` | tools/maintenance;tools/measurement;scripts/validation | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/baseline/quality-tools-green.log;build/soc-ip-layout/baseline/io-lifecycle-green.log;build/soc-ip-layout/capabilities-green.log | +| `notes` | New drivers remain covered by style, private include, ops/source/retention, static analysis and target-local lifecycle checks; independent review closed all migration findings. | + +(status-soc-ip-layout-r13)= +## R13 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R13 | +| `work_package` | configuration_providers | +| `requirement` | Move Native configuration | +| `exit_condition` | Use soc/native/configure with explicit imports and retained tests | +| `implementation_paths` | soc/native/configure | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r14)= +## R14 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R14 | +| `work_package` | configuration_providers | +| `requirement` | Move F103 and GD303 configuration | +| `exit_condition` | Use each exact family configure package and retained tests | +| `implementation_paths` | soc/stm32/stm32f103/configure;soc/gd32/gd32f303/configure | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r15)= +## R15 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R15 | +| `work_package` | configuration_providers | +| `requirement` | Move F407 and GD470 configuration | +| `exit_condition` | Use each exact family configure package and retained tests | +| `implementation_paths` | soc/stm32/stm32f407/configure;soc/gd32/gd32f470/configure | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r16)= +## R16 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R16 | +| `work_package` | configuration_providers | +| `requirement` | Move G474 configuration and helpers | +| `exit_condition` | Co-locate G474 contract; construction; emission and selected helpers | +| `implementation_paths` | soc/stm32/stm32g474/configure | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r17)= +## R17 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R17 | +| `work_package` | configuration_providers | +| `requirement` | Move H743 configuration | +| `exit_condition` | Co-locate H743 contract; construction; emission and selected helpers | +| `implementation_paths` | soc/stm32/stm32h743/configure | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r18)= +## R18 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R18 | +| `work_package` | configuration_providers | +| `requirement` | Move U575 configuration and clear old production paths | +| `exit_condition` | Co-locate U575 policy and remove obsolete family production packages | +| `implementation_paths` | soc/stm32/stm32u575/configure;tools/configure/providers | +| `implementation_status` | implemented | +| `software_status` | phase1_complete_index_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/configuration;.git/local-recovery/soc-ip-layout/sync-first.json;build/soc-ip-layout/commit-gates.log;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Phase-1 exact tree d9270fefe377be519e2fb10d223eed8125566223; canonical commit 7b985c4c619a966c886c3cbf01902ccbfb68c955. Ordinary complete-index gates: 3215 tool tests and 352 programs / 6352 Google cases. This result does not qualify subsequent behavior changes. The first outer hook log and sync receipt retain the historical assertion; reused build/native-debug/tdd bytes are not its original detailed evidence. Final P gates and execution are recorded in R43-R48. | + +(status-soc-ip-layout-r19)= +## R19 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R19 | +| `work_package` | shared_drivers | +| `requirement` | Move UART V1 channel DMA engine | +| `exit_condition` | Select reviewed algorithm and private headers from drivers | +| `implementation_paths` | drivers/uart/uart_v1_channel_dma | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r20)= +## R20 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R20 | +| `work_package` | shared_drivers | +| `requirement` | Move I2C V1 asynchronous engine | +| `exit_condition` | Select reviewed IRQ/DMA algorithm and private headers from drivers | +| `implementation_paths` | drivers/i2c/i2c_v1_async | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r21)= +## R21 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R21 | +| `work_package` | shared_drivers | +| `requirement` | Move I2C V2 engine | +| `exit_condition` | Select reviewed polling algorithm and private headers from drivers | +| `implementation_paths` | drivers/i2c/i2c_v2 | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r22)= +## R22 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R22 | +| `work_package` | shared_drivers | +| `requirement` | Split legacy_devices and move ADC | +| `exit_condition` | Separate private legacy interfaces and select the ADC algorithm from drivers | +| `implementation_paths` | drivers/adc/legacy_adc | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r23)= +## R23 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R23 | +| `work_package` | shared_drivers | +| `requirement` | Move legacy PWM | +| `exit_condition` | Select fixed PWM algorithm and narrow private contract from drivers | +| `implementation_paths` | drivers/timer/legacy_pwm | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r24)= +## R24 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R24 | +| `work_package` | shared_drivers | +| `requirement` | Move legacy Flash and SoC helpers | +| `exit_condition` | Select Flash algorithm; place STM32 identity helper; retain UART collaboration and update READMEs | +| `implementation_paths` | drivers/flash/legacy_flash;soc/stm32/common/stm32_identity.h;soc/common/uart_control.h;drivers/README.md;soc/common/README.md | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/native-debug/quality/soc-ip-layout-shared-drivers/;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.xml;build/native-debug/quality/soc-ip-layout-shared-drivers/ctest-results.execution.json | +| `notes` | Shared focused run: 17 targets; 17/17 CTest; 267 Google cases; zero skipped. Aggregate scope is not a per-row test count or complete qualification. Physical HIL not executed. The preserved shared-drivers XML matches the original report hash; its embedded generic Native XML path was later reused. Final P execution is separately recorded in R43-R48. | + +(status-soc-ip-layout-r25)= +## R25 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R25 | +| `work_package` | facts_and_ir | +| `requirement` | Centralize part package RAM and Flash geometry | +| `exit_condition` | Use one exact fact authority for part; package; memory and physical geometry | +| `implementation_paths` | soc;tools/configure | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/part-red.log;build/soc-ip-layout/facts-ir/clock-red.log;build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/facts-ir/f407-flash.xml;build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/facts-ir/all-flash-red.log;build/soc-ip-layout/flash-geometry/summary.json | +| `notes` | Exact PartPolicy and optional-memory admission; physical numbers remain in soc.json and frozen PhysicalPartIR views. All seven MCU Flash geometries derive readonly C from validated facts; six retain global symbols and F407 uses selected static geometry. Final focused configuration suite: 364 tests; separate integration/bundle suite: 143 tests. These overlapping scopes are not additive or final-source qualification. | + +(status-soc-ip-layout-r26)= +## R26 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R26 | +| `work_package` | facts_and_ir | +| `requirement` | Typed controller integration record | +| `exit_condition` | Use explicit typed integration data without family-name inference | +| `implementation_paths` | soc;tools/configure/providers/common.py | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/facts-ir/integration-red.log | +| `notes` | ControllerIntegrationIR composes existing canonical claims/DMA/IRQ/clock/pin/hardware objects; PhysicalPartIR reuses frozen facts. No new JSON/hash field or route resolver. Actual production consumers: F407 primary SPI and F407/GD470 paired secondary paths. Other constructors and explicit legacy fixture adapters remain. 364-test focused suite and overlapping 52-test final closure passed; no final-source qualification. | + +(status-soc-ip-layout-r27)= +## R27 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R27 | +| `work_package` | facts_and_ir | +| `requirement` | Clock reset pin and state integration | +| `exit_condition` | Make integration ownership explicit without hidden side effects | +| `implementation_paths` | soc;drivers | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log | +| `notes` | Exact family clock, GPIO/preflight, ControllerBinding and secondary cold hooks own chip integration. Common resource leases/IRQ/drain ordering remain. Focused configuration and bundle suites passed; no runtime registration or broader clock/Board/backend qualification. | + +(status-soc-ip-layout-r28)= +## R28 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R28 | +| `work_package` | facts_and_ir | +| `requirement` | Consume existing IRQ DMA and memory IR consistently | +| `exit_condition` | Use reviewed immutable IR instead of duplicated derived facts | +| `implementation_paths` | soc;tools/configure/ir.py;tools/configure/memory.py | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log;build/soc-ip-layout/facts-ir/claims-red.log;build/soc-ip-layout/facts-ir/primary-dma-red.log | +| `notes` | Typed owned_claims uses canonical ResourcePlanIR; selected hardware/DMA/IRQ/clock/pin consumers use ControllerIntegrationIR rather than reparsing routes. Meaningful omitted-lane/claim regressions recorded; other maintained typed or explicit legacy adapters remain. Focused suites are not final ARM/SDK qualification. | + +(status-soc-ip-layout-r29)= +## R29 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R29 | +| `work_package` | facts_and_ir | +| `requirement` | Move family special cases out of common tools | +| `exit_condition` | Keep family policy in exact SoC configure packages and common mechanisms shared | +| `implementation_paths` | soc;tools/configure | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/parts-clocks-green.log;build/soc-ip-layout/r30/green-final.log;build/soc-ip-layout/facts-ir/final-focused.log;build/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout/r29-hardware;build/soc-ip-layout/r29-public-policies;build/soc-ip-layout/facts-ir/final-closure-green.log;build/quality/soc-ip-config-r32/profile-python-green.log | +| `notes` | Explicit SoC hardware/secondary, clock/linker/runtime-power, GPIO/preflight, selected support, UART/stream/endpoint and CAN/coupled/pin policies implemented. Generated-output equivalence checked in separately recorded 18-, 23- and 8-configuration scopes; test counts overlap and must not be added. Remaining common family literals are explicit dependency paths and ThreadX cross-Board/backend OS admission. F103/GD303 frozen LegacyTransportProfile supplies exact wiring without family fallback; its 38-test focused run is separate and overlaps related suites. | + +(status-soc-ip-layout-r30)= +## R30 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R30 | +| `work_package` | facts_and_ir | +| `requirement` | Capability revision and support projection | +| `exit_condition` | Derive current views from exact reviewed facts without new support claims | +| `implementation_paths` | tools/maintenance;docs/delivery/capabilities.md | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/r30/red.log;build/soc-ip-layout/r30/owner-red.log;build/soc-ip-layout/r30/green-final.log | +| `notes` | 98 tests passed across 10 focused modules. Existing silicon policy/IR projects separately for clock and controller owners; absent review stays null and companion/endpoint policy is not inherited. No broader support or hardware observation. | + +(status-soc-ip-layout-r31)= +## R31 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R31 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | I2C V2 const configuration pilot | +| `exit_condition` | Separate immutable configuration from mutable transfer state | +| `implementation_paths` | drivers/i2c/i2c_v2;soc | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;build/quality/soc-ip-config-r31/i2c-python-green.log | +| `notes` | Selected G474/H743/U575 const configuration and separate BSS runtime implemented. Shared focused execution: 9 targets / 121 Google cases and 38 related Python tests passed; not per-row counts. Development ARM cost comparison is recorded in R46; final P complete gates and scoped execution are recorded in R43-R48. | + +(status-soc-ip-layout-r32)= +## R32 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R32 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | UART DMA const configuration | +| `exit_condition` | Separate immutable configuration from mutable request and block state | +| `implementation_paths` | drivers/uart/uart_v1_channel_dma;soc | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-config-r32/uart-ring-green | +| `notes` | Selected F103/GD303 UART DMA immutable config and BSS runtime implemented; duplex receive extension remains separate. Static config/resources share runtime lifetime and successful stop preserves binding. Final focused run: 3 targets / 65 Google cases plus 38 Python tests. Host execution covers actual SoC wrapper/engine/GPIO; generated DMA factory runtime is not claimed. Development ARM comparison is recorded separately in R46; final P complete gates and scoped execution are recorded in R43-R48. | + +(status-soc-ip-layout-r33)= +## R33 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R33 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | Shared ops immutable faces and direct IRQ | +| `exit_condition` | Preserve shared readonly methods; independent contexts and direct IRQ entry | +| `implementation_paths` | drivers;soc;io | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-config-r32/uart-ring-green;build/quality/soc-ip-config-r31/i2c-green | +| `notes` | Shared readonly ops, immutable public faces, distinct static contexts and direct IRQ entry symbols retained for the selected I2C V2 and UART DMA changes; no hot-path dispatch layer. Focused UART wrapper/engine/GPIO and I2C model suites passed. Development ARM linked retention/costs are recorded in R46; final P scoped validation is recorded in R43-R48. | + +(status-soc-ip-layout-r34)= +## R34 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R34 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | Move initialization and stop logic into C | +| `exit_condition` | Keep explicit lifecycle ordering and cleanup responsibilities in concrete code | +| `implementation_paths` | soc;drivers;tools/configure | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;build/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-config-r32/uart-ring-green | +| `notes` | Selected I2C V2, F103/GD303 UART DMA and legacy ADC/PWM/Flash cold integration lives in concrete C preparation with existing stop paths. U575 engine stop precedes clock release. UART AHB DMA gate and Platform ordering remain generated; legacy I2C V1 and other peripherals retain family-emitted behavior. Focused suites overlap and are not additive or whole-platform qualification. | + +(status-soc-ip-layout-r35)= +## R35 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R35 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | Retained failure and retry contracts | +| `exit_condition` | Preserve partial effects and borrows until proved drain or explicit retry | +| `implementation_paths` | soc;drivers;tests/google | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r31/i2c-bind-red;build/quality/soc-ip-config-r31/i2c-config-overlap-red;build/quality/soc-ip-config-r31/i2c-green;build/quality/soc-ip-config-r31/i2c-python-green.log;build/quality/soc-ip-config-r36/legacy-final-green;build/quality/soc-ip-config-r32/prepare-red;build/quality/soc-ip-config-r32/prepare-green;build/quality/soc-ip-config-r32/profile-python-green.log;build/quality/soc-ip-config-r32/uart-ring-red;build/quality/soc-ip-config-r32/uart-ring-green | +| `notes` | Selected I2C V2, UART DMA and legacy ADC/PWM/Flash live/retained guards, failed-drain responsibility and stop/retry contracts passed focused regressions. UART guards precede config/pin mutation and include outstanding wake/RX loans; failed withdrawal retains config and borrowed storage. Counts across focused suites overlap; final-source and physical qualification are separate. | + +(status-soc-ip-layout-r36)= +## R36 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R36 | +| `work_package` | static_configuration_and_lifecycle | +| `requirement` | Split legacy ADC PWM and Flash integration | +| `exit_condition` | Separate exact SoC integration from reusable algorithms and narrow contracts | +| `implementation_paths` | soc/stm32/stm32f103/drivers/devices.c;soc/gd32/gd32f303/drivers/devices.c;drivers/adc/legacy_adc;drivers/timer/legacy_pwm;drivers/flash/legacy_flash | +| `implementation_status` | implemented | +| `software_status` | final_P_native_arm_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-config-r36/legacy-high-green;build/quality/soc-ip-config-r36/legacy-python-green.log;build/quality/soc-ip-config-r36/legacy-final-green;build/quality/soc-ip-candidate/legacy-devices/report.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-candidate/source-snapshot.json;/workspace/nexus-examples/build/platform-software-recovery-92e669233957/providers/mainstream_assembly/report.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/host_contracts.json | +| `notes` | F103/GD303 concrete C ADC/PWM/Flash prepare entries and existing shared close paths implemented. Final two factory targets: 11 Google cases, including full independent 128-sector geometry, live rebind, failed stop/retry and failed initialization cleanup/restart; GD303 foreign oversampling stays unchanged on BUSY. Earlier 6-target/97-case and 40-Python results overlap and are not additive. Four exact legacy ARM builds and two independent Flash-only retention consumers also passed on the 184-file dirty development overlay. This is development evidence, not final clean-source qualification; other peripheral emission is unchanged. Final clean P separately passed all four Native modes and the four f103/gd303 devices baremetal/FreeRTOS fixtures at mainstream_assembly indices 0/1/8/9. The two extra Flash-only consumers remain development observations; they were not rerun or relabeled as P. | + +(status-soc-ip-layout-r37)= +## R37 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R37 | +| `work_package` | build_integration_and_docs | +| `requirement` | Review CMake private includes and Runtime boundary | +| `exit_condition` | Selected targets retain source ownership and vendor includes stay private | +| `implementation_paths` | soc;drivers;cmake | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/integration-audit/runtime-green.log;build/soc-ip-layout/integration-audit/cmake/observations.json | +| `notes` | Three real RED Runtime inclusion regressions then 18 tests GREEN. Eight family consumer include closures show no private leakage; two Cortex-M4 baremetal/FreeRTOS Ninja/ARM configurations exclude SoC/driver/Board objects. This audit configured targets; it did not build production firmware. | + +(status-soc-ip-layout-r38)= +## R38 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R38 | +| `work_package` | build_integration_and_docs | +| `requirement` | Converge SDK transport manifest | +| `exit_condition` | Use required payload closure without redundant source authorities | +| `implementation_paths` | cmake/package | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/integration-audit/sdk-closure-red.log;build/soc-ip-layout/integration-audit/sdk-closure-green.log;build/soc-ip-layout/integration-audit/required-comparison.json | +| `notes` | 48 selection/admission/descriptor tests passed. SDK requirements reuse public source_names and descriptor facts/payload; no prior required member removed. Additional declared family helpers are included dynamically. Actual final P relocated SDK execution is recorded in R45. | + +(status-soc-ip-layout-r39)= +## R39 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R39 | +| `work_package` | build_integration_and_docs | +| `requirement` | Incremental configuration transaction regressions | +| `exit_condition` | Exercise changed-module invalidation and failed reconfiguration through existing gates | +| `implementation_paths` | tools/configure/tests;tools/testing | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/facts-ir/incremental-green.log;build/soc-ip-layout/facts-ir/incremental-receipt.json;build/soc-ip-layout/facts-ir/integration-bundle-green.log;build/soc-ip-layout/facts-ir/final-closure-green.log | +| `notes` | Fresh Native consumer CMake/Ninja configure/build and no-change rebuild executed; changing a declared family module triggered CMake and selected regeneration. All receipt commands exit zero. Separate 143-test integration/bundle suite covers transaction boundaries; not a fresh final-source SDK or full firmware matrix qualification. | + +(status-soc-ip-layout-r40)= +## R40 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R40 | +| `work_package` | build_integration_and_docs | +| `requirement` | Existing CLI diagnostics | +| `exit_condition` | Existing commands report exact provider inputs and failures without a new framework | +| `implementation_paths` | tools/configure/configure.py;tools/configure/experience.py | +| `implementation_status` | implemented | +| `software_status` | focused_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/integration-audit/diagnostic-red.log;build/soc-ip-layout/integration-audit/diagnostic-green.log;build/soc-ip-layout/integration-audit/cli/summary.json | +| `notes` | 35 focused tests passed; eight exact provider-check runs emitted static bundles and identity mismatch rejected. provider_module_missing applies only to the selected entry/parent package; other import errors remain explicit. No compile/link or physical qualification. | + +(status-soc-ip-layout-r41)= +## R41 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R41 | +| `work_package` | build_integration_and_docs | +| `requirement` | Bilingual documentation diagrams and porting migration | +| `exit_condition` | Current source maps; diagram links; READMEs and reviewed gettext match implementation | +| `implementation_paths` | README.md;README_CN.md;drivers/README.md;soc/README.md;soc/common/README.md;tools/configure/README.md;docs/explanation;docs/guides/provider-porting.md;docs/design;docs/delivery/architecture.md;docs/assets/diagrams/README.md;docs/locale | +| `implementation_status` | implemented | +| `software_status` | clean_P_docs_and_D_review_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/documentation/phase1-clean-build.json;build/soc-ip-layout-docs-review.json;build/soc-ip-layout/documentation/phase2-source-scan-final.json;build/soc-ip-layout/documentation/phase2-diagram-check.json;build/soc-ip-layout/documentation/phase2-diagram-review.json;build/soc-ip-layout/documentation/phase2-po-independent-review.json;build/soc-ip-layout/documentation/phase2-review-admission.json;build/soc-ip-layout/documentation/phase2-translations-final.json;build/soc-ip-layout-docs-92e669233957/documentation-build.json;build/soc-ip-layout/documentation/final-d-extraction/extraction.json;build/soc-ip-layout/documentation/final-d-source-check.json;build/soc-ip-layout/documentation/final-d-po-independent-review.json;build/soc-ip-layout/documentation/final-d-review-admission.json;build/soc-ip-layout/documentation/final-d-translations.json | +| `notes` | Phase-1 clean 7b985 strict bilingual build and independent 18-catalog review retained. Phase-2 current content complete: FIG-31 independent source/visual review, locked diagram verification, 131-page source scan, actual gettext extraction and whole-site 262 locale checks passed. Nine catalogs independently reviewed: 101 new messages and 945 unchanged reviewed entries. Clean P strict bilingual build passed with 262 locale checks; its receipt has qualification=false and SHA256 acdca47f4ccf9f1438a8c50f25dd8bea4135821922da202181a5237738ff8166. This later documentation-only update records P; its actual gettext/review and clean-D build are separate documentation evidence, not firmware or physical qualification. D content validation actually passed: fresh locked gettext, 131-page source scan, independent semantic review of 22 new messages with 127 baseline pairs unchanged, and 262 whole-site locale checks. Clean-D strict build is a separate postcommit receipt; no future success or D self-hash is recorded here. | + +(status-soc-ip-layout-r42)= +## R42 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R42 | +| `work_package` | build_integration_and_docs | +| `requirement` | Examples candidate consumption | +| `exit_condition` | Consume the concrete candidate in the external examples repository | +| `implementation_paths` | X-Gen-Lab/nexus-examples | +| `implementation_status` | implemented | +| `software_status` | executed_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | /workspace/nexus-examples/build/local-ci-soc-ip-layout-92e669233957/execution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json;.git/local-recovery/soc-ip-layout/examples-main-merge.json | +| `notes` | Exact P consumed by clean examples 0f15022e4d5f5c51991b134d0b9d6082dcf88de9, tree bb60f413b911ec212b59b77cea1734971eddbd9f. Complete canonical local CI passed: Native, installed SDK consumer, 16 ARM presets and 32 main-matrix ELFs for platform_reference/uart_owner_runtime; dedicated example targets remain separately recorded. Formal lock and Gitlink match P; candidate_validation=false. Ordinary hook retained 80 tool tests and 15 CTest / 69 Google cases with no skips. PR15 merged as bd3b90f135600b6405459347cb9e2cff5692d2e3 with the same examples tree. This is exact code-candidate consumption, not physical execution. | + +(status-soc-ip-layout-r43)= +## R43 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R43 | +| `work_package` | verification_and_delivery | +| `requirement` | Four Native modes and complete tool suites | +| `exit_condition` | Execute maintained modes and complete applicable tool domains with fresh nonempty reports | +| `implementation_paths` | tests/google;tools/dev;tools/configure/tests;tools/testing | +| `implementation_status` | implemented | +| `software_status` | executed_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | /workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/report.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/tool-tests.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/configure-tests.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/ci-tests.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/workflow-tests.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/host_contracts.json;build/soc-ip-layout/phase2-hook-evidence/manifest.json | +| `notes` | Tested clean P=92e669233957a2e450a528394e8f4dd0794c7049. All four Native modes debug/release/ASan-UBSan/TSan actually passed: each 372 CTest and 6396 Google cases, zero failures/errors/skips. Complete applicable tool domains passed: testing 1527, configure 1071, ci 490, workflows 159 (3247 total); other tool domains are not inferred from these four. Original executions are retained in candidate evidence. A remains interrupted/running after its 90 completed stages; this row closes the actual completed Native/tool scopes, not a single full-plan pass. The separate ordinary complete-index hook archive records 3247 tools and 352 Google programs / 6396 cases. | + +(status-soc-ip-layout-r44)= +## R44 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R44 | +| `work_package` | verification_and_delivery | +| `requirement` | ARM and Runtime matrix | +| `exit_condition` | Compile and link exact maintained ARM and CPU Runtime configurations | +| `implementation_paths` | tests/contracts;tools/measurement | +| `implementation_status` | implemented | +| `software_status` | segmented_execution_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | /workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/report.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/interruption-observation.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/cortex_runtime/report.json;/workspace/nexus-examples/build/platform-software-recovery-92e669233957/report.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/host_contracts.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/candidate-portable.json | +| `notes` | Tested P=92e669233957a2e450a528394e8f4dd0794c7049: original maintained plan has actual exact segmented coverage A90+B69. A retains running and no source_after after environment interruption; B executed_passed with the exact original suffix, zero stage exits and unchanged clean source. B report SHA256 f8ecf185eadbef7e33d7a667366da7de34241ce243a5d417b5b453ae3bb99694. All original names/order/checkers/args/timeouts and raw identities were checked; no merged executed_passed report was invented. B reran all 865 tidy and 865 cppcheck contexts. CPU Runtime passed 104 variants with source_inputs_stable=true; its full_platform_status stays not_qualified and expected rejections are separate from positive images. ARM/provider/retention/resource fixtures retain their exact original reports; linked software does not establish MCU execution. | + +(status-soc-ip-layout-r45)= +## R45 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R45 | +| `work_package` | verification_and_delivery | +| `requirement` | Relocated SDK integrity | +| `exit_condition` | Verify required installed members and independent relocated consumers | +| `implementation_paths` | cmake/package | +| `implementation_status` | implemented | +| `software_status` | executed_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/sdk-relocation-92e669233957/execution.json;/workspace/nexus-examples/build/soc-ip-layout-validation-92e669233957/sdk-examples-validation-evidence.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/source_sdk.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/verify_relocated_sdk.command.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/candidate-portable.json | +| `notes` | Publishable source SDK binds clean P=92e669233957a2e450a528394e8f4dd0794c7049 and its exact tree. Actual package verification and independent relocated consumers passed at platform-sdk-92e669233957/SDK relocated, including Native C/C++, Runtime and maintained firmware selections; source closure and exported inputs retain original member hashes. The independent SDK/examples archive preserves those executions. Candidate completion-2 reverified the relocated SDK with exit 0. Initial completion used an incorrect verifier root and failed; its original failed receipt remains, followed by the actual corrected execution. No source change or guard weakening was used. | + +(status-soc-ip-layout-r46)= +## R46 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R46 | +| `work_package` | verification_and_delivery | +| `requirement` | Resource retention and cost comparison | +| `exit_condition` | Compare actual Flash RAM ops and IRQ retention for the same selections | +| `implementation_paths` | tools/measurement;tests/contracts | +| `implementation_status` | implemented | +| `software_status` | final_P_comparison_passed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/quality/soc-ip-candidate/cost-summary.json;build/quality/soc-ip-candidate/uart/report.json;build/quality/soc-ip-candidate/legacy-devices/report.json;build/quality/soc-ip-candidate/f103-flash-only-retention.json;build/quality/soc-ip-candidate/gd303-flash-only-retention.json;build/quality/soc-ip-candidate/completion-summary.json;build/quality/soc-ip-candidate/source-snapshot.json;build/quality/soc-ip-final-p-92e669233957/final-cost-summary.json;build/quality/soc-ip-final-p-92e669233957/final-cost-summary.csv;build/quality/soc-ip-cost-package-92e669233957/package.json;build/quality/soc-ip-cost-package-92e669233957/artifact-summary.json | +| `notes` | Historical 184-file dirty development overlay comparisons remain separately identified in their original reports. Final clean P versus 7b985: all 11 strict comparisons passed from actual A/B build artifacts with independent ELF/map/IRQ review; no rebuild for path normalization. I2C total loaded Flash delta G474/H743/U575 = -4/+40/+64 B, composed of code/alignment +108/+152/+224 B, readonly config +24 B each, path-pool/alignment -136/-136/-184 B; reserved RAM -24/-16/-24 B and state 28 to 8 B. Eight UART totals range +168 to +392 B Flash, with code/alignment +336 to +384 B, readonly config 60 B and negative source-path/alignment contributions; all reserve 48 B less RAM, TX state 96 to 48 B and duplex 144 to 96 B. Source-root string reductions are not architecture savings. UART selected IRQ code at most +2 B/one static instruction, up to two config loads; duplex channel local push frame grows 16 to 24 B while MSP reservation is unchanged. No new selected IRQ indirect call; cycles/WCET/dynamic stack high-water are not measured. Portable cost evidence archive SHA256 fb0f63bcae83f7a5b278a311f67107bf4f8c99fcee0014638b0fb26c0936567f, 4035953 B / 1184 files; original source/tool/SDK inputs remain explicit external dependencies. | + +(status-soc-ip-layout-r47)= +## R47 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R47 | +| `work_package` | verification_and_delivery | +| `requirement` | Independent review main integration and formal examples pin | +| `exit_condition` | Complete review and ordinary gates before approved integration and exact examples pin | +| `implementation_paths` | AGENTS.md;X-Gen-Lab/nexus-examples | +| `implementation_status` | implemented | +| `software_status` | integrated_reviewed | +| `hardware_status` | not_executed | +| `evidence_paths` | build/soc-ip-layout/r47-independent-review.json;build/soc-ip-layout/phase2-hook-evidence/manifest.json;build/soc-ip-layout/codeql-pr36-review/review.json;build/soc-ip-layout/codeql-pr36-review/comments.json;build/soc-ip-layout/codeql-pr36-review/remote-status.json;.git/local-recovery/soc-ip-layout/platform-main-merge.json;.git/local-recovery/soc-ip-layout/examples-main-merge.json;.git/local-recovery/soc-ip-layout/platform-merge-import-apply.json;.git/local-recovery/soc-ip-layout/examples-merge-import-apply.json;/workspace/nexus-examples/build/soc-ip-layout-candidate-92e669233957/ordinary-hook-evidence.json | +| `notes` | Independent production review completed without blocking findings; normal complete-index hooks passed on the tree integrated as P. PR36 actually merged to M=c79fd9672a9fe56379744d834cfccd822bc40be1, tree exactly equal to P; PR15 merged to bd3b90f135600b6405459347cb9e2cff5692d2e3, tree equal to tested examples bb60f413b911ec212b59b77cea1734971eddbd9f, with formal lock/Gitlink P. Actual signed GitHub commit objects and local lease/import receipts retained. All 14 CodeQL review comments were assessed: no confirmed production dangling/late binding or exercised test UAF, with nonblocking maintenance suggestions retained; they are not all labeled false positives. Remote Security Analysis 40388967544 CPP/Python jobs passed, not a local CodeQL run or zero-alert/zero-defect claim. This documentation-only follow-up does not retest M as a new software source. | + +(status-soc-ip-layout-r48)= +## R48 + +| 原字段 | 原值 | +| --- | --- | +| `id` | R48 | +| `work_package` | verification_and_delivery | +| `requirement` | Clean software delivery and two fresh offline reproductions | +| `exit_condition` | Bind clean source and actual reproducible artifacts; physical HIL stays not_executed | +| `implementation_paths` | tools/evidence;docs/design/soc-ip-layout-execution.csv | +| `implementation_status` | implemented | +| `software_status` | software_qualified | +| `hardware_status` | not_executed | +| `evidence_paths` | /workspace/nexus-examples/build/platform-candidate-92e669233957/reproduction-quiet1.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/candidate/candidate.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/candidate_verify.command.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-r48-2.command.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/candidate-portable.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/completion-2/execution-receipts.json;build/soc-ip-layout/qualification-timeout-92e669233957/manifest.json;/workspace/nexus-examples/build/platform-software-cleanenv-92e669233957/interruption-observation.json;/workspace/nexus-examples/build/platform-software-recovery-92e669233957/report.json;/workspace/nexus-examples/build/platform-candidate-92e669233957/finish-candidate-r48.command.json;/workspace/nexus-examples/build/platform-software-final-92e669233957/report.json;build/soc-ip-layout/software-92e669233957/report.json | +| `notes` | Sealed software candidate binds clean P=92e669233957a2e450a528394e8f4dd0794c7049, exact configuration and actual same ELF/BIN/map inputs. Existing six scopes passed: host_contracts, arm_link, resource_budget, source_sdk, reproducibility, hil_tooling; owned candidate verifier and parent completion-2 process exit 0. Two fresh distinct network-none builds passed declared container-boundary verification and bit_exact_elf_bin with no differing artifacts; map bit equality is not claimed. Candidate SHA256 35c8301ac95cdc7eee6b125e5ed8170ef44800bb6eb4995361d02c364660c060. Portable archive SHA256 7f89e8ec33a543e150e7165a3e4252aa11a96d62fe5d3187a9b0d4791aa618b4, 433850915 B / 6401 files. Signature unsigned; product promotion not_performed; HIL not_executed. Maintained-plan evidence is exact A90+B69 segmented coverage, not a single successful 159-stage run. Original output-boundary, timeout, GIT_PAGER-admission, interruption and initial SDK-root failures retain original outcomes; none was rewritten as passed. D only records P and obtains its own separate documentation receipt. | diff --git a/docs/status/software-verification.md b/docs/status/software-verification.md index f16649078..cb888a3d1 100644 --- a/docs/status/software-verification.md +++ b/docs/status/software-verification.md @@ -22,16 +22,24 @@ source_language: zh_CN | --- | --- | --- | | 当前代码源码 | 站点 `documentation-manifest.json` 的 `code_source` | 本站描述的 checkout、tree、dirty 状态和代码路径 | | 当前文档源码 | 同一 manifest 的 `documentation_source` | 本次正式文档包的精确 commit/tree;不替代预览构建记录 | -| 被引用资格源码 | 同一 manifest 的 `qualified_code` 与其结果索引 | 旧的精确代码执行;不是当前代码/文档 HEAD 自动继承的资格 | +| 被引用资格源码 | schema 2 manifest 的 `qualification_references[]`;每项保存原候选身份与 scope | 旧的精确代码执行;不是当前代码/文档 HEAD 自动继承的资格 | 成功打包后,公开站点和离线包的 manifest 由实际构建输入生成。Git HEAD 可以因文档提交改变; 这里不手填一个很快漂移的 HEAD 常量。未打包的工作树可执行 `git rev-parse HEAD` 核对 checkout,但命令输出本身不是 qualification receipt。 +schema 2 不再隐式引用某个历史索引。打包者逐个显式选入原封存候选,工具调用 +同一候选 verifier 核对原始闭包后记录 source、configuration、ELF/BIN/map、 +qualified scopes 与候选文件摘要。没有选入候选时列表为空,不表示软件失败。 +文档包离线校验保护这份引用的字节和结构,不重新执行或代替原候选资格校验。 +原 locator 只说明当时的存储位置,不是公开下载地址。schema 1 历史包保留原 +`qualified_code` 的只读校验,不能用它生产新的默认资格引用。 + ## 已有独立结果索引 | 范围 | 精确代码源码 | 原权威及执行说明 | | --- | --- | --- | +| SoC/IP 目录整理与原候选 | P `92e669233957a2e450a528394e8f4dd0794c7049`;E `0f15022e4d5f5c51991b134d0b9d6082dcf88de9` | [执行说明](../delivery/verification.md)、[48 项原台账](../design/soc-ip-layout-execution.csv)、[派生视图](ledgers/soc-ip-layout.md);同一 P 的 A90+B69 分段覆盖,原候选只覆盖其精确配置,物理 HIL 未执行 | | 开发体验迭代 P 与示例 E | P `c8df1c49d4f771a55490b3a8be32a9d1f24faa1e`;E `acb923cae407c8cfc4ac5788f8026d35c84cc841` | [结果索引](../delivery/developer-experience-results.json)保留本地完整 gate、SDK 独立消费、示例 ARM 和原始归档;不是正式软件候选 | | 构建系统及其实际软件 scopes | `cc6a065c002041234a212a964be863a69198f9b4` | [结果索引](../delivery/build-system-results.json)、[执行记录](../delivery/build-system-verification.md) | | SoC 扩展软件切片 | `98270512ea2ff7eefe8ff79720018e582be3a949` | [结果索引](../delivery/soc-extensions-results.json)、[执行记录](../delivery/soc-extensions-verification.md) | diff --git a/drivers/adc/legacy_adc/legacy_adc.c b/drivers/adc/legacy_adc/legacy_adc.c index 54a887628..90cce8e01 100644 --- a/drivers/adc/legacy_adc/legacy_adc.c +++ b/drivers/adc/legacy_adc/legacy_adc.c @@ -18,47 +18,60 @@ static bool task_context(void) { return nx_arch_is_privileged() && !nx_arch_in_isr() && !nx_arch_irq_is_masked(); } +static bool overlap(const void* a, size_t as, const void* b, size_t bs) { + return (uintptr_t)a < (uintptr_t)b + bs && (uintptr_t)b < (uintptr_t)a + as; +} static bool valid(const nx_legacy_adc_state_t* p) { - return p != NULL && p->registers != NULL && p->gate != NULL && - p->divider != NULL && p->pin_mode != NULL && p->pin_lock != NULL && - p->clock_started != NULL && p->gate_mask != 0U && - p->reference_mv >= 2700U && p->reference_mv <= 3600U && - p->poll_limit != 0U && p->startup_delay_us >= 4U && - p->startup_delay_us <= 2000U; + if (p == NULL || p->config == NULL || + (uintptr_t)p->config > UINTPTR_MAX - sizeof(*p->config) || + (uintptr_t)p->config % _Alignof(nx_legacy_adc_config_t) != 0U || + overlap(p, sizeof(*p), p->config, sizeof(*p->config))) + return false; + return p->config->registers != NULL && p->config->gate != NULL && + p->config->divider != NULL && p->config->pin_mode != NULL && + p->config->pin_lock != NULL && p->config->clock_started != NULL && + p->config->gate_mask != 0U && p->reference_mv >= 2700U && + p->reference_mv <= 3600U && p->config->poll_limit != 0U && + p->config->startup_delay_us >= 4U && + p->config->startup_delay_us <= 2000U; } static bool clock_ready(const nx_legacy_adc_state_t* p) { - return *p->clock_started && - (NX_LEGACY_READ(p->gate) & p->gate_mask) == p->gate_mask && - (NX_LEGACY_READ(p->divider) & p->divider_mask) == 0U; + return *p->config->clock_started && + (NX_LEGACY_READ(p->config->gate) & p->config->gate_mask) == + p->config->gate_mask && + (NX_LEGACY_READ(p->config->divider) & p->config->divider_mask) == 0U; } static bool write_checked(volatile uint32_t* r, uint32_t value) { NX_LEGACY_WRITE(r, value); nx_arch_dsb(); return NX_LEGACY_READ(r) == value; } -static bool overlap(const void* a, size_t as, const void* b, size_t bs) { - return (uintptr_t)a < (uintptr_t)b + bs && (uintptr_t)b < (uintptr_t)a + as; -} static bool alias(const nx_legacy_adc_state_t* p, const void* ptr, size_t size) { return ptr == NULL || (uintptr_t)ptr > UINTPTR_MAX - size || overlap(ptr, size, p, sizeof(*p)) || - overlap(ptr, size, p->registers, sizeof(*p->registers)) || - overlap(ptr, size, (const void*)p->gate, sizeof(*p->gate)) || - overlap(ptr, size, (const void*)p->divider, sizeof(*p->divider)) || - overlap(ptr, size, (const void*)p->pin_mode, sizeof(*p->pin_mode)) || - overlap(ptr, size, (const void*)p->pin_lock, sizeof(*p->pin_lock)) || - overlap(ptr, size, (const void*)p->clock_started, - sizeof(*p->clock_started)); + overlap(ptr, size, p->config, sizeof(*p->config)) || + overlap(ptr, size, p->config->registers, + sizeof(*p->config->registers)) || + overlap(ptr, size, (const void*)p->config->gate, + sizeof(*p->config->gate)) || + overlap(ptr, size, (const void*)p->config->divider, + sizeof(*p->config->divider)) || + overlap(ptr, size, (const void*)p->config->pin_mode, + sizeof(*p->config->pin_mode)) || + overlap(ptr, size, (const void*)p->config->pin_lock, + sizeof(*p->config->pin_lock)) || + overlap(ptr, size, (const void*)p->config->clock_started, + sizeof(*p->config->clock_started)); } /** \brief Bounded calibration waits never assume a progressing converter. */ static nx_result_t calibration(nx_legacy_adc_state_t* p, uint32_t command) { - NX_LEGACY_WRITE(&p->registers->control2, ADC_POWER | command); + NX_LEGACY_WRITE(&p->config->registers->control2, ADC_POWER | command); nx_arch_dsb(); - uint32_t remaining = p->poll_limit; + uint32_t remaining = p->config->poll_limit; do { NX_LEGACY_POLL(1U, p); - if ((NX_LEGACY_READ(&p->registers->control2) & command) == 0U) + if ((NX_LEGACY_READ(&p->config->registers->control2) & command) == 0U) return NX_SUCCESS; } while (--remaining != 0U); return NX_ERROR_TIMEOUT; @@ -70,20 +83,21 @@ nx_result_t nx_legacy_adc_initialize(nx_legacy_adc_state_t* p) { return NX_ERROR_INVALID; if (!clock_ready(p)) return NX_ERROR_STATE; - nx_legacy_adc_registers_t* r = p->registers; - uint32_t pin_mode = NX_LEGACY_READ(p->pin_mode) & 15U; + nx_legacy_adc_registers_t* r = p->config->registers; + uint32_t pin_mode = NX_LEGACY_READ(p->config->pin_mode) & 15U; if (p->retained || p->initialized || p->active || NX_LEGACY_READ(&r->control1) != 0U || NX_LEGACY_READ(&r->control2) != 0U || NX_LEGACY_READ(&r->sequence1) != 0U || NX_LEGACY_READ(&r->sequence2) != 0U || NX_LEGACY_READ(&r->sequence3) != 0U || - (NX_LEGACY_READ(p->pin_lock) & 1U) != 0U || + (NX_LEGACY_READ(p->config->pin_lock) & 1U) != 0U || (pin_mode != 0U && pin_mode != 4U)) return NX_ERROR_BUSY; p->retained = true; p->fault = false; - if (!write_checked(p->pin_mode, NX_LEGACY_READ(p->pin_mode) & ~15U) || + if (!write_checked(p->config->pin_mode, + NX_LEGACY_READ(p->config->pin_mode) & ~15U) || !write_checked(&r->sample2, (NX_LEGACY_READ(&r->sample2) & ~7U) | 7U) || !write_checked(&r->control2, ADC_POWER)) return NX_ERROR_IO; @@ -91,8 +105,8 @@ nx_result_t nx_legacy_adc_initialize(nx_legacy_adc_state_t* p) { * minimum, with margin for the reviewed IRC8 factory-trim tolerance. * Supply decoupling and bead workarounds remain Board responsibilities. */ nx_time_us_t stable = - nx_deadline_after(nx_time_now_us(), p->startup_delay_us); - uint32_t remaining = p->poll_limit; + nx_deadline_after(nx_time_now_us(), p->config->startup_delay_us); + uint32_t remaining = p->config->poll_limit; while (!nx_deadline_expired(stable, nx_time_now_us())) { NX_LEGACY_POLL(1U, p); if (--remaining == 0U) @@ -116,10 +130,10 @@ nx_result_t nx_legacy_adc_close(nx_legacy_adc_state_t* p) { return NX_ERROR_BUSY; if (!clock_ready(p)) return NX_ERROR_STATE; - if (!write_checked(&p->registers->control2, 0U) || - !write_checked(&p->registers->control1, 0U)) + if (!write_checked(&p->config->registers->control2, 0U) || + !write_checked(&p->config->registers->control1, 0U)) return NX_ERROR_IO; - NX_LEGACY_WRITE(&p->registers->status, 0U); + NX_LEGACY_WRITE(&p->config->registers->status, 0U); p->initialized = false; p->retained = false; p->fault = false; @@ -170,7 +184,7 @@ static nx_result_t shot(void* context, uint16_t* output, size_t capacity, *count = 0U; if (nx_deadline_expired(deadline, nx_time_now_us())) return NX_ERROR_TIMEOUT; - nx_legacy_adc_registers_t* r = p->registers; + nx_legacy_adc_registers_t* r = p->config->registers; if (NX_LEGACY_READ(&r->control1) != 0U || NX_LEGACY_READ(&r->control2) != ADC_POWER || NX_LEGACY_READ(&r->sequence1) != 0U || diff --git a/drivers/adc/legacy_adc/legacy_adc.h b/drivers/adc/legacy_adc/legacy_adc.h index 30eee0c2f..748571e1b 100644 --- a/drivers/adc/legacy_adc/legacy_adc.h +++ b/drivers/adc/legacy_adc/legacy_adc.h @@ -20,7 +20,7 @@ typedef struct { volatile uint32_t high, low, sequence1, sequence2, sequence3; volatile uint32_t injected_sequence, injected_data[4], data; } nx_legacy_adc_registers_t; -/** \brief Independent channel zero polling, no DMA or retained output. */ +/** \brief Immutable hardware description, borrowed for the owner lifetime. */ typedef struct { nx_legacy_adc_registers_t* registers; volatile uint32_t* gate; @@ -28,8 +28,16 @@ typedef struct { volatile uint32_t* pin_mode; volatile uint32_t* pin_lock; const volatile bool* clock_started; - uint32_t gate_mask, divider_mask, reference_mv, poll_limit; + uint32_t gate_mask, divider_mask, poll_limit; uint32_t startup_delay_us; +} nx_legacy_adc_config_t; +/** \brief Independent channel zero polling, no DMA or retained output. + * \note The static config cannot overlap state or API output storage. + * Reference voltage is bound at preparation and unchanged while retained. + */ +typedef struct { + const nx_legacy_adc_config_t* config; + uint32_t reference_mv; bool initialized, retained, active, fault; } nx_legacy_adc_state_t; extern const nx_adc_ops_t nx_legacy_adc_ops; diff --git a/drivers/timer/legacy_pwm/legacy_pwm.c b/drivers/timer/legacy_pwm/legacy_pwm.c index 4e53dec91..bd5946a4a 100644 --- a/drivers/timer/legacy_pwm/legacy_pwm.c +++ b/drivers/timer/legacy_pwm/legacy_pwm.c @@ -17,19 +17,28 @@ static bool task_context(void) { return nx_arch_is_privileged() && !nx_arch_in_isr() && !nx_arch_irq_is_masked(); } +static bool overlap(const void* a, size_t as, const void* b, size_t bs) { + return (uintptr_t)a < (uintptr_t)b + bs && (uintptr_t)b < (uintptr_t)a + as; +} static bool valid(const nx_legacy_pwm_state_t* p) { - return p != NULL && p->registers != NULL && p->gate != NULL && - p->pin_mode != NULL && p->pin_lock != NULL && p->pin_latch != NULL && - p->pin_set != NULL && p->clock_started != NULL && - p->gate_mask != 0U && p->tick_hz != 0U && - 8000000U % p->tick_hz == 0U && 8000000U / p->tick_hz <= 65536U && - p->period_ticks != 0U && p->period_ticks <= 65535U && - p->duty_ticks <= p->period_ticks && (p->inactive & ~PIN_BIT) == 0U && - p->poll_limit != 0U; + if (p == NULL || p->config == NULL || + (uintptr_t)p->config > UINTPTR_MAX - sizeof(*p->config) || + (uintptr_t)p->config % _Alignof(nx_legacy_pwm_config_t) != 0U || + overlap(p, sizeof(*p), p->config, sizeof(*p->config))) + return false; + return p->config->registers != NULL && p->config->gate != NULL && + p->config->pin_mode != NULL && p->config->pin_lock != NULL && + p->config->pin_latch != NULL && p->config->pin_set != NULL && + p->config->clock_started != NULL && p->config->gate_mask != 0U && + p->tick_hz != 0U && 8000000U % p->tick_hz == 0U && + 8000000U / p->tick_hz <= 65536U && p->period_ticks != 0U && + p->period_ticks <= 65535U && p->duty_ticks <= p->period_ticks && + (p->inactive & ~PIN_BIT) == 0U && p->config->poll_limit != 0U; } static bool clock_ready(const nx_legacy_pwm_state_t* p) { - return *p->clock_started && - (NX_LEGACY_READ(p->gate) & p->gate_mask) == p->gate_mask; + return *p->config->clock_started && + (NX_LEGACY_READ(p->config->gate) & p->config->gate_mask) == + p->config->gate_mask; } static bool write_checked(volatile uint32_t* r, uint32_t value) { NX_LEGACY_WRITE(r, value); @@ -37,20 +46,20 @@ static bool write_checked(volatile uint32_t* r, uint32_t value) { return NX_LEGACY_READ(r) == value; } static bool idle(nx_legacy_pwm_state_t* p) { - NX_LEGACY_WRITE(p->pin_set, + NX_LEGACY_WRITE(p->config->pin_set, p->inactive | ((PIN_BIT & ~p->inactive) << 16U)); nx_arch_dsb(); - if ((NX_LEGACY_READ(p->pin_latch) & PIN_BIT) != p->inactive) + if ((NX_LEGACY_READ(p->config->pin_latch) & PIN_BIT) != p->inactive) return false; nx_arch_irq_state_t saved = nx_arch_irq_save(); - bool done = - write_checked(p->pin_mode, (NX_LEGACY_READ(p->pin_mode) & ~PIN_MASK) | - (2U << PIN_SHIFT)); + bool done = write_checked( + p->config->pin_mode, + (NX_LEGACY_READ(p->config->pin_mode) & ~PIN_MASK) | (2U << PIN_SHIFT)); nx_arch_irq_restore(saved); return done; } static bool cold(const nx_legacy_pwm_state_t* p) { - nx_legacy_pwm_registers_t* r = p->registers; + nx_legacy_pwm_registers_t* r = p->config->registers; return NX_LEGACY_READ(&r->control1) == 0U && NX_LEGACY_READ(&r->control2) == 0U && NX_LEGACY_READ(&r->slave) == 0U && @@ -61,12 +70,12 @@ static bool cold(const nx_legacy_pwm_state_t* p) { } /** \brief Hold TRGO on disabled CEN while UG loads shadow registers. */ static nx_result_t epoch(nx_legacy_pwm_state_t* p) { - nx_legacy_pwm_registers_t* r = p->registers; + nx_legacy_pwm_registers_t* r = p->config->registers; if (!write_checked(&r->control2, 16U) || !write_checked(&r->status, 0U)) return NX_ERROR_IO; NX_LEGACY_WRITE(&r->event, 1U); nx_arch_dsb(); - uint32_t remaining = p->poll_limit; + uint32_t remaining = p->config->poll_limit; do { NX_LEGACY_POLL(2U, p); if ((NX_LEGACY_READ(&r->status) & 1U) != 0U) @@ -86,14 +95,14 @@ nx_result_t nx_legacy_pwm_initialize(nx_legacy_pwm_state_t* p) { return NX_ERROR_INVALID; if (!clock_ready(p)) return NX_ERROR_STATE; - uint32_t mode = (NX_LEGACY_READ(p->pin_mode) >> PIN_SHIFT) & 15U; + uint32_t mode = (NX_LEGACY_READ(p->config->pin_mode) >> PIN_SHIFT) & 15U; if (p->retained || p->initialized || !cold(p) || - (NX_LEGACY_READ(p->pin_lock) & PIN_BIT) != 0U || + (NX_LEGACY_READ(p->config->pin_lock) & PIN_BIT) != 0U || (mode != 0U && mode != 4U)) return NX_ERROR_BUSY; p->retained = true; p->fault = p->running = false; - nx_legacy_pwm_registers_t* r = p->registers; + nx_legacy_pwm_registers_t* r = p->config->registers; if (!idle(p) || !write_checked(&r->control1, PWM_PRELOAD) || !write_checked(&r->prescaler, 8000000U / p->tick_hz - 1U) || !write_checked(&r->reload, p->period_ticks - 1U) || @@ -114,7 +123,7 @@ nx_result_t nx_legacy_pwm_close(nx_legacy_pwm_state_t* p) { return NX_SUCCESS; if (!clock_ready(p)) return NX_ERROR_STATE; - nx_legacy_pwm_registers_t* r = p->registers; + nx_legacy_pwm_registers_t* r = p->config->registers; /* Detach the pad at its preloaded Board level even if a controller leaf * refuses withdrawal. Still attempt every independent source cleanup. */ bool valid = idle(p); @@ -132,7 +141,7 @@ nx_result_t nx_legacy_pwm_close(nx_legacy_pwm_state_t* p) { return NX_SUCCESS; } static bool base_matches(const nx_legacy_pwm_state_t* p) { - nx_legacy_pwm_registers_t* r = p->registers; + nx_legacy_pwm_registers_t* r = p->config->registers; return NX_LEGACY_READ(&r->prescaler) == 8000000U / p->tick_hz - 1U && NX_LEGACY_READ(&r->reload) == p->period_ticks - 1U && NX_LEGACY_READ(&r->channel_mode1) == PWM_MODE && @@ -150,7 +159,7 @@ static nx_result_t set(void* context, uint32_t period, uint32_t duty) { if (!p->initialized || p->fault || !clock_ready(p) || period != p->period_ticks || !base_matches(p)) return NX_ERROR_STATE; - if (!write_checked(&p->registers->compare1, duty)) + if (!write_checked(&p->config->registers->compare1, duty)) return NX_ERROR_IO; p->duty_ticks = duty; return NX_SUCCESS; @@ -164,8 +173,8 @@ static nx_result_t stop(void* context) { if (!clock_ready(p)) return NX_ERROR_STATE; bool safe = idle(p); - bool channel = write_checked(&p->registers->enable, 0U); - bool counter = write_checked(&p->registers->control1, PWM_PRELOAD); + bool channel = write_checked(&p->config->registers->enable, 0U); + bool counter = write_checked(&p->config->registers->control1, PWM_PRELOAD); if (!safe || !channel || !counter) { p->fault = true; return NX_ERROR_IO; @@ -184,10 +193,12 @@ static nx_result_t start(void* context) { return NX_SUCCESS; nx_result_t result = epoch(p); if (result == NX_SUCCESS && - (!write_checked(p->pin_mode, (NX_LEGACY_READ(p->pin_mode) & ~PIN_MASK) | - (10U << PIN_SHIFT)) || - !write_checked(&p->registers->enable, PWM_ENABLE) || - !write_checked(&p->registers->control1, PWM_PRELOAD | PWM_ENABLE))) + (!write_checked(p->config->pin_mode, + (NX_LEGACY_READ(p->config->pin_mode) & ~PIN_MASK) | + (10U << PIN_SHIFT)) || + !write_checked(&p->config->registers->enable, PWM_ENABLE) || + !write_checked(&p->config->registers->control1, + PWM_PRELOAD | PWM_ENABLE))) result = NX_ERROR_IO; if (result != NX_SUCCESS) { p->fault = true; @@ -197,27 +208,27 @@ static nx_result_t start(void* context) { p->running = true; return NX_SUCCESS; } -static bool overlap(const void* a, size_t as, const void* b, size_t bs) { - return (uintptr_t)a < (uintptr_t)b + bs && (uintptr_t)b < (uintptr_t)a + as; -} static nx_result_t state(const void* context, nx_pwm_state_t* out) { const nx_legacy_pwm_state_t* p = context; if (!valid(p) || out == NULL || (uintptr_t)out % _Alignof(nx_pwm_state_t) != 0U || (uintptr_t)out > UINTPTR_MAX - sizeof(*out) || overlap(out, sizeof(*out), p, sizeof(*p)) || - overlap(out, sizeof(*out), p->registers, sizeof(*p->registers)) || - overlap(out, sizeof(*out), (const void*)p->gate, sizeof(*p->gate)) || - overlap(out, sizeof(*out), (const void*)p->pin_mode, - sizeof(*p->pin_mode)) || - overlap(out, sizeof(*out), (const void*)p->pin_lock, - sizeof(*p->pin_lock)) || - overlap(out, sizeof(*out), (const void*)p->pin_latch, - sizeof(*p->pin_latch)) || - overlap(out, sizeof(*out), (const void*)p->pin_set, - sizeof(*p->pin_set)) || - overlap(out, sizeof(*out), (const void*)p->clock_started, - sizeof(*p->clock_started))) + overlap(out, sizeof(*out), p->config, sizeof(*p->config)) || + overlap(out, sizeof(*out), p->config->registers, + sizeof(*p->config->registers)) || + overlap(out, sizeof(*out), (const void*)p->config->gate, + sizeof(*p->config->gate)) || + overlap(out, sizeof(*out), (const void*)p->config->pin_mode, + sizeof(*p->config->pin_mode)) || + overlap(out, sizeof(*out), (const void*)p->config->pin_lock, + sizeof(*p->config->pin_lock)) || + overlap(out, sizeof(*out), (const void*)p->config->pin_latch, + sizeof(*p->config->pin_latch)) || + overlap(out, sizeof(*out), (const void*)p->config->pin_set, + sizeof(*p->config->pin_set)) || + overlap(out, sizeof(*out), (const void*)p->config->clock_started, + sizeof(*p->config->clock_started))) return NX_ERROR_INVALID; if (!p->initialized) return NX_ERROR_STATE; diff --git a/drivers/timer/legacy_pwm/legacy_pwm.h b/drivers/timer/legacy_pwm/legacy_pwm.h index 498a5cab6..6ab7660b9 100644 --- a/drivers/timer/legacy_pwm/legacy_pwm.h +++ b/drivers/timer/legacy_pwm/legacy_pwm.h @@ -19,7 +19,7 @@ typedef struct { volatile uint32_t channel_mode1, channel_mode2, enable, counter; volatile uint32_t prescaler, reload, repetition, compare1; } nx_legacy_pwm_registers_t; -/** \brief One fixed PA6 channel with Board-declared inactive level. */ +/** \brief Immutable reviewed timer and pad resources for the owner lifetime. */ typedef struct { nx_legacy_pwm_registers_t* registers; volatile uint32_t* gate; @@ -28,8 +28,15 @@ typedef struct { volatile uint32_t* pin_latch; volatile uint32_t* pin_set; const volatile bool* clock_started; - uint32_t gate_mask, tick_hz, period_ticks, duty_ticks, inactive; - uint32_t poll_limit; + uint32_t gate_mask, poll_limit; +} nx_legacy_pwm_config_t; +/** \brief One fixed PA6 channel with Board-declared inactive level. + * \note Config cannot overlap state or API output. Period, duty and safe + * level belong to this selected owner, never to the shared hardware facts. + */ +typedef struct { + const nx_legacy_pwm_config_t* config; + uint32_t tick_hz, period_ticks, duty_ticks, inactive; bool initialized, retained, running, fault; } nx_legacy_pwm_state_t; extern const nx_pwm_ops_t nx_legacy_pwm_ops; diff --git a/io/private/README.md b/io/private/README.md index 1b753cc6a..4b3105339 100644 --- a/io/private/README.md +++ b/io/private/README.md @@ -11,8 +11,7 @@ and clock facts, IRQ/DMA bindings and checked silicon semantics. Textual provide templates bind direct operations macros to concrete readonly tables; they do not create a runtime mode registry or a forwarding layer. -Use `soc/common/` for a shared, silicon-specific register algorithm such as I2C -V1. `io/src/api/` owns public checked dispatch, `io/src/mechanisms/` owns reusable +Use `drivers/` for a reviewed shared register algorithm such as I2C V1. `io/src/api/` owns public checked dispatch, `io/src/mechanisms/` owns reusable I/O rules, and `io/src/controllers/` owns shared controller engines. Use this directory for private structures, external hardware boundaries or direct provider templates. Native models live in `soc/native/drivers/`; MCU wrappers live under diff --git a/soc/gd32/gd32f303/configure/bindings.py b/soc/gd32/gd32f303/configure/bindings.py index 179cf92df..9dc886e9d 100644 --- a/soc/gd32/gd32f303/configure/bindings.py +++ b/soc/gd32/gd32f303/configure/bindings.py @@ -1,23 +1,23 @@ """One selected immutable face, actual shared ops and bounded private storage.""" from .transport import PROFILE -from tools.configure.ir import controller_ir +from tools.configure.calls import Address, DirectCall +from tools.configure.ir import ControllerIR from tools.configure.providers.common import Construction, fail, kernel_policy_constant def constructor(item, result, board_bindings, routes, devices): """Guard old ownership before updating any static constructor input.""" - from .contract import validate_selection - item = controller_ir(item, "gd32f303") + if not isinstance(item, ControllerIR): + fail("gd32f303 constructor requires a typed controller decision") if item.provider != "gd32f303": fail("GD303 constructor cannot reinterpret a foreign provider") - validate_selection(item, item) if item.kind == "i2c" or (item.kind == "uart" and item.mode != "irq-bytes"): from tools.configure.providers.legacy_transport import constructor as transport_constructor return transport_constructor(item, result, devices, PROFILE) name = item.id.replace("-", "_") state = "s_nx_port_" + name definitions = [] - initialize = f"prepare_{name}()" + initialize = DirectCall(f"prepare_{name}") if item.kind == "gpio": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized) {{ return NX_ERROR_BUSY; }}", @@ -25,12 +25,12 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.output = {'true' if item.mode == 'output' else 'false'};", f" return nx_gd32f303_gpio_initialize(&{state}, {item.options.initial}u);", "}"] - stop = f"nx_gd32f303_gpio_stop(&{state}, {item.options.initial}u)" + stop = DirectCall("nx_gd32f303_gpio_stop", (Address(state), item.options.initial)) elif item.kind == "uart": capacity = item.options.rx_capacity if type(capacity) is not int or not 1 <= capacity <= 4096: fail("GD303 requires an explicit bounded byte ring") - irq = result["irq"] + irq = result.irq definitions += [f"static uint8_t s_nx_rx_{name}[{capacity}];", f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active != NULL || {state}.wake != NULL || {state}.wake_publishers != 0u) {{ return NX_ERROR_BUSY; }}", @@ -40,22 +40,22 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.baud = {item.options.baud}u;", f" {state}.irq = USART0_IRQn;", f" {state}.profile = NX_UART_RX_BYTES;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", " nx_result_t status = nx_gd32f303_usart0_pins_prepare();", " if (status != NX_SUCCESS) { return status; }", f" return nx_gd32f303_uart_initialize(&{state}, 8000000u, 1000000u);", "}", "void USART0_IRQHandler(void);", r"/** \brief Exact selected USART0; at most one TX/RX byte. */", f"void USART0_IRQHandler(void) {{ nx_gd32f303_uart_irq(&{state}); }}"] - stop = f"nx_uart_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "watchdog": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.initialized || {state}.state.enabled || {state}.preparation_retained) {{ return NX_ERROR_BUSY; }}", f" {state}.poll_limit = 1000000u;", f" return nx_gd32f303_watchdog_initialize(&{state});", "}"] - stop = f"nx_gd32f303_watchdog_stop(&{state})" + stop = DirectCall("nx_gd32f303_watchdog_stop", (Address(state),)) elif item.kind == "spi": - children = [child for child in devices if child["controller"] == item.id] + children = [child for child in devices if child.controller == item.id] if not children: fail("GD303 SPI0 requires an explicit reviewed CS endpoint") body = [f"static nx_result_t prepare_{name}(void) {{", @@ -69,17 +69,17 @@ def constructor(item, result, board_bindings, routes, devices): f" nx_result_t status = nx_spi_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }"] for child in children: - child_name = child["id"].replace("-", "_") - binding = board_bindings[child["cs_binding"]] + child_name = child.id.replace("-", "_") + binding = board_bindings[child.options.cs_binding] pins = routes[binding["route"]]["pins"] if tuple((pin["pin"], pin["af"]) for pin in pins) != (("PA4", 0),): fail("GD303 SPI fixture requires its exact PA4 CS") if binding.get("initial") != 16 or binding.get("mask") != 16: fail("GD303 SPI active-low CS must initially be high") - rate = child.get("max_hz", item.options.max_hz) + rate = child.options.max_hz if type(rate) is not int or not 31250 <= rate <= item.options.max_hz: fail("GD303 endpoint rate differs from actual SPI divider") - mode = child.get("mode", 0) + mode = child.options.mode if type(mode) is not int or not 0 <= mode <= 3: fail("GD303 endpoint mode must be 0..3") cs = f"s_nx_cs_{child_name}" @@ -99,10 +99,10 @@ def constructor(item, result, board_bindings, routes, devices): " if (status != NX_SUCCESS) { return status; }", " }"] definitions += body + [" return nx_gd32f303_spi0_pins_prepare();", "}"] definitions += stops + [" return NX_SUCCESS;", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "exti": options = item.options - irq = result["irq"] + irq = result.irq line = int(item.controller[-1]) definitions += [ f"static nx_exti_event_t s_nx_events_{name}[{options.event_capacity}];", @@ -116,29 +116,29 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.line = {line}u;", f" {state}.priority = {options.irq.priority}u;", f" {state}.edge = NX_EXTI_{options.edge.upper()};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_gd32f303_exti_initialize(&{state});", "}", f"void EXTI{line}_IRQHandler(void);", r"/** \brief One exact line and one bounded coalesced fact. */", f"void EXTI{line}_IRQHandler(void) {{ nx_gd32f303_exti_irq(&{state}); }}"] - stop = f"nx_exti_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_exti_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind in {"adc", "pwm", "flash"}: if item.kind == "flash": supply = board_bindings[item.binding].get("supply_mv") if type(supply) is not int or not 2700 <= supply <= 3600: fail("GD303 Flash requires explicit Board supply_mv2700..3600") - initialize = f"nx_gd32f303_flash_prepare(&{state}, {supply}u)" + initialize = DirectCall("nx_gd32f303_flash_prepare", (Address(state), supply)) elif item.kind == "adc": - initialize = (f"nx_gd32f303_adc_prepare(&{state}, " - f"{item.options.reference_mv}u)") + initialize = DirectCall("nx_gd32f303_adc_prepare", + (Address(state), item.options.reference_mv)) else: initial = board_bindings[item.binding].get("initial") if type(initial) is not int or initial not in {0, 64}: fail("GD303 PWM requires explicit PA6 inactive level") - initialize = (f"nx_gd32f303_pwm_prepare(&{state}, " - f"{item.options.tick_hz}u, {item.options.period_ticks}u, " - f"{item.options.duty_ticks}u, {initial}u)") - stop = f"nx_legacy_{item.kind}_close(&{state})" + initialize = DirectCall("nx_gd32f303_pwm_prepare", + (Address(state), item.options.tick_hz, item.options.period_ticks, + item.options.duty_ticks, initial)) + stop = DirectCall(f"nx_legacy_{item.kind}_close", (Address(state),)) else: fail("Unmaintained GD303 constructor") return Construction(state_type=f"nx_gd32f303_{item.kind}_state_t", diff --git a/soc/gd32/gd32f303/configure/emission.py b/soc/gd32/gd32f303/configure/emission.py index f37821cbd..5819abb14 100644 --- a/soc/gd32/gd32f303/configure/emission.py +++ b/soc/gd32/gd32f303/configure/emission.py @@ -39,12 +39,12 @@ def shared_irqs(result): def _pins(result): numbers = {int(pin['pin'][2:]) for item in result.controllers - for pin in item['pins'] if pin['pin'].startswith('PA')} - for child in result.get("devices", ()): - if "cs_binding" in child: - binding = result["board_bindings"][child["cs_binding"]] + for pin in item.pins if pin['pin'].startswith('PA')} + for child in result.devices: + if child.kind == "spi" and child.options.cs_binding is not None: + binding = result.board_bindings[child.options.cs_binding] numbers.update(int(pin['pin'][2:]) - for pin in result["routes"][binding["route"]]["pins"]) + for pin in result.routes[binding["route"]]["pins"]) return sum(1 << number for number in sorted(numbers)) diff --git a/soc/gd32/gd32f303/drivers/devices.c b/soc/gd32/gd32f303/drivers/devices.c index 3be265d51..841a67f4d 100644 --- a/soc/gd32/gd32f303/drivers/devices.c +++ b/soc/gd32/gd32f303/drivers/devices.c @@ -31,6 +31,17 @@ static bool task_context(void) { return nx_arch_is_privileged() && !nx_arch_in_isr() && !nx_arch_irq_is_masked(); } +static const nx_legacy_adc_config_t s_adc_config = { + .registers = (nx_legacy_adc_registers_t*)(uintptr_t)ADC0, + .gate = &RCU_APB2EN, + .divider = &RCU_CFG0, + .pin_mode = &GPIO_CTL0(GPIOA), + .pin_lock = &GPIO_LOCK(GPIOA), + .clock_started = &g_nx_gd32f303_system.started, + .gate_mask = RCU_APB2EN_ADC0EN, + .divider_mask = RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2, + .poll_limit = 1000000U, + .startup_delay_us = 2000U}; nx_result_t nx_gd32f303_adc_prepare(nx_gd32f303_adc_state_t* port, uint32_t reference_mv) { if (!task_context()) @@ -39,17 +50,10 @@ nx_result_t nx_gd32f303_adc_prepare(nx_gd32f303_adc_state_t* port, return NX_ERROR_INVALID; if (port->retained || port->initialized || port->active) return NX_ERROR_BUSY; - port->clock_started = &g_nx_gd32f303_system.started; - port->pin_mode = &GPIO_CTL0(GPIOA); - port->pin_lock = &GPIO_LOCK(GPIOA); - port->poll_limit = 1000000U; - port->registers = (nx_legacy_adc_registers_t*)(uintptr_t)ADC0; - port->gate = &RCU_APB2EN; - port->gate_mask = RCU_APB2EN_ADC0EN; - port->divider = &RCU_CFG0; - port->divider_mask = RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2; + if (reference_mv < 2700U || reference_mv > 3600U) + return NX_ERROR_INVALID; + port->config = &s_adc_config; port->reference_mv = reference_mv; - port->startup_delay_us = 2000U; /* The extra GD303 divider and oversampling controls are outside the * shared reviewed register prefix and must remain cold. */ if ((NX_GD303_READ(&RCU_CFG1) & RCU_CFG1_ADCPSC_3) != 0U || @@ -57,6 +61,16 @@ nx_result_t nx_gd32f303_adc_prepare(nx_gd32f303_adc_state_t* port, return NX_ERROR_BUSY; return nx_legacy_adc_initialize(port); } +static const nx_legacy_pwm_config_t s_pwm_config = { + .registers = (nx_legacy_pwm_registers_t*)(uintptr_t)TIMER2, + .gate = &RCU_APB1EN, + .pin_mode = &GPIO_CTL0(GPIOA), + .pin_lock = &GPIO_LOCK(GPIOA), + .pin_latch = &GPIO_OCTL(GPIOA), + .pin_set = &GPIO_BOP(GPIOA), + .clock_started = &g_nx_gd32f303_system.started, + .gate_mask = RCU_APB1EN_TIMER2EN, + .poll_limit = 1000000U}; nx_result_t nx_gd32f303_pwm_prepare(nx_gd32f303_pwm_state_t* port, uint32_t tick_hz, uint32_t period_ticks, uint32_t duty_ticks, uint32_t inactive) { @@ -66,15 +80,7 @@ nx_result_t nx_gd32f303_pwm_prepare(nx_gd32f303_pwm_state_t* port, return NX_ERROR_INVALID; if (port->retained || port->initialized || port->running) return NX_ERROR_BUSY; - port->clock_started = &g_nx_gd32f303_system.started; - port->pin_mode = &GPIO_CTL0(GPIOA); - port->pin_lock = &GPIO_LOCK(GPIOA); - port->poll_limit = 1000000U; - port->registers = (nx_legacy_pwm_registers_t*)(uintptr_t)TIMER2; - port->gate = &RCU_APB1EN; - port->gate_mask = RCU_APB1EN_TIMER2EN; - port->pin_latch = &GPIO_OCTL(GPIOA); - port->pin_set = &GPIO_BOP(GPIOA); + port->config = &s_pwm_config; port->inactive = inactive; port->tick_hz = tick_hz; port->period_ticks = period_ticks; diff --git a/soc/gd32/gd32f470/configure/bindings.py b/soc/gd32/gd32f470/configure/bindings.py index cc1221c89..e33a2e63e 100644 --- a/soc/gd32/gd32f470/configure/bindings.py +++ b/soc/gd32/gd32f470/configure/bindings.py @@ -1,5 +1,6 @@ """Reviewed GD32 fixed construction with no dynamic controller discovery.""" from tools.configure.ir import controller_ir +from tools.configure.calls import Address, DirectCall, Symbol from tools.configure.memory import dma_regions from tools.configure.gpio_phase import rollback_pin, withdraw_pin from tools.configure import enhanced_endpoints, uart_formats @@ -22,7 +23,7 @@ def _integer(value, minimum, maximum, description): def _pins(item, expected): actual = [(pin["pin"], pin["function"], pin["af"]) - for pin in item["pins"]] + for pin in item.pins] if actual != expected: raise ValueError("GD32 fixed provider requires its exact reviewed route") @@ -88,6 +89,95 @@ def released(name, pins, stop, cs_pins=()): return lines + [" return NX_SUCCESS;", "}"] +def _spi_constructor(item, result, board_bindings, routes, devices): + """Emit static exact connections; GD470 C owns every SPI cold transition.""" + selected = item.integration + if selected is None or selected.clock_hz != 100000000: + raise ValueError("SPI requires its exact resolved APB2 integration") + if item.controller not in {"SPI0", "SPI4"}: + raise ValueError("GD470 SPI requires its reviewed SPI0/SPI4 identity") + name = _symbol(item.id) + dma = item.mode == "dma-full-duplex" + implementation = "spi_dma" if dma else "spi" + children = [child for child in devices if child.controller == item.id] + pins = [pin for pin in item.pins if pin["function"] != "cs"] + if len(pins) != 3 or not children: + raise ValueError("SPI requires three signals and explicit CS children") + state, config = f"s_nx_port_{name}", f"s_nx_spi_config_{name}" + saved, cold = f"s_nx_spi_pin_state_{name}", f"s_nx_spi_cold_{name}" + definitions = [f"static nx_gd32_{implementation}_cold_t {cold};"] + endpoints = [] + for index, child in enumerate(children): + child_name = _symbol(child.id) + if child.options.cs_binding is None: + cs_pin, = [pin for pin in item.pins if pin["function"] == "cs"] + else: + binding = board_bindings[child.options.cs_binding] + cs_pin, = routes[binding["route"]]["pins"] + if binding.get("initial") != 1 << int(cs_pin["pin"][2:]): + raise ValueError("SPI active-low CS requires initial high") + pins.append(cs_pin) + endpoint = (f"s_nx_endpoint_{name}" if index == 0 else + f"s_nx_endpoint_{child_name}") + if index: + definitions.append( + f"static nx_gd32_{implementation}_endpoint_state_t {endpoint};") + definitions += enhanced_endpoints.face( + child, endpoint, f"nx_gd32_{implementation}_endpoint_ops", "gd32f470") + base = endpoint + (".endpoint" if dma else "") + endpoints.append( + f" {{ &{base}, {child.options.max_hz}u, {child.options.mode}u }}") + if len(pins) > 16: + raise ValueError("GD470 static SPI lease supports at most sixteen pads") + definitions += [f"static uint16_t {saved}[{len(pins)}];", + f"static const nx_gd32_spi_pin_config_t s_nx_spi_pins_{name}[] = {{"] + snapshots = {} + for index, pin in enumerate(pins): + wire = pin["pin"] + slot = f"{saved}[{index}u]" + snapshots[wire] = slot + definitions.append( + f" {{ GPIO{wire[1]}, &{slot}, {ord(wire[1]) - ord('A')}u, " + f"{int(wire[2:])}u, {pin['af']}u }},") + definitions += ["};", + f"static const nx_gd32_spi_endpoint_config_t s_nx_spi_endpoints_{name}[] = {{", + ",\n".join(endpoints), "};"] + if dma: + if selected.irq is None or len(selected.dma) != 2: + raise ValueError("SPI DMA requires both exact lanes and IRQ policy") + secondary_controllers.dma_paths(item) + regions, count = dma_regions(name, result, include_flash=True) + definitions += regions + definitions += [f"static const nx_gd32_spi_dma_config_t {config} = {{", + " .spi = {"] + else: + definitions += [f"static const nx_gd32_spi_config_t {config} = {{"] + definitions += [f" .controller = &nx_gd32_{item.controller.lower()}_controller,", + f" .pins = s_nx_spi_pins_{name}, .pin_count = {len(pins)}u,", + f" .endpoints = s_nx_spi_endpoints_{name}, .endpoint_count = {len(children)}u"] + if dma: + definitions += [" },", + f" .regions = s_nx_dma_regions_{name}, .region_count = {count}u,", + f" .priority = {selected.irq.priority}u,", + f" .kernel_bound = {'true' if result.kernel is not None else 'false'}"] + definitions += ["};"] + arguments = (Address(state), Address(config), Address(cold)) + stop = [] + if dma: + stop.append(DirectCall("nx_spi_port_stop", (Symbol(f"nx_binding_{name}"),))) + for index, path in enumerate(hardware.maintained("gd32f470", item.controller).dma(item.mode)): + vector = hardware.dma_vector("gd32f470", path) + receive = "true" if index == 1 else "false" + definitions += [f"void {vector}_IRQHandler(void);", + r"/** \brief Exact selected GD SPI DMA terminal observation. */", + f"void {vector}_IRQHandler(void) {{ nx_gd32_spi_dma_irq(&{state}, {receive}); }}"] + stop.append(DirectCall(f"nx_gd32_{implementation}_close", arguments)) + return Construction( + state_type=f"nx_gd32_{implementation}_state_t", definitions=definitions, + initialize=DirectCall(f"nx_gd32_{implementation}_prepare", arguments), + stop=stop, retain_on_failed_init=True, pin_snapshots=snapshots) + + def _controller_constructor(item, result, board_bindings, routes, devices): """Emit only maintained controllers and explicit package-reviewed routes. @@ -96,15 +186,15 @@ def _controller_constructor(item, result, board_bindings, routes, devices): qualify any unmeasured PCB, external pull-up or peripheral device. """ item = controller_ir(item, 'gd32f470') - name = _symbol(item["id"]) - kind = item["kind"] + name = _symbol(item.id) + kind = item.kind declarations = [] children = [child for child in devices - if child["controller"] == item["id"]] + if child.controller == item.id] if kind == "uart": - if item["controller"] not in {"USART0", "USART1"}: + if item.controller not in {"USART0", "USART1"}: raise ValueError("GD32 maintains USART0/USART1 IRQ UART") - name = _symbol(item["id"]) + name = _symbol(item.id) traits = mode_traits(kind, item.mode) blocks, receive = traits.block_stream, traits.rx_dma words = item.mode == "irq-words" @@ -114,7 +204,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): capacity = (item.options.rx_capacity or 0) if not blocks: declarations.append(f"static {storage} s_nx_rx_{name}[{capacity}];") - descriptor = item["controller"].lower() + descriptor = item.controller.lower() dma = traits.tx_dma prefix = [] if words: @@ -123,7 +213,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): f"s_nx_rx_{name}, {capacity}u, {item.options.irq.priority}u)") elif receive: from .contract import _FIXED_DMA - if tuple(item.get("dma", ())) != _FIXED_DMA.get((item["controller"], item.mode)): + if tuple(item.dma) != _FIXED_DMA.get((item.controller, item.mode)): raise ValueError("UART DMA differs from its reviewed fixed route") region_lines, region_count = dma_regions(name, result) declarations += region_lines @@ -153,7 +243,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): f"{item.options.baud}u, {profile}, s_nx_rx_{name}, " f"{capacity}u, {item.options.irq.priority}u, " f"{uart_formats.expression(item.options.frame)})") - prepare = prepared(name, [(pin, 2, 1, False, None) for pin in item["pins"]], call) + prepare = prepared(name, [(pin, 2, 1, False, None) for pin in item.pins], call) position = prepare.index(f" status = {call};") prepare[position:position] = prefix if words or uart_formats.changed(item): @@ -164,9 +254,9 @@ def _controller_constructor(item, result, board_bindings, routes, devices): " goto rollback;", " }"] declarations += prepare stop_function = "nx_uart_word_port_stop" if words else "nx_uart_port_stop" - declarations += released(name, item["pins"], + declarations += released(name, item.pins, f"{stop_function}(&s_nx_face_{name})") - vector = item["controller"] + "_IRQHandler" + vector = item.controller + "_IRQHandler" irq_implementation = ("uart_word" if words else "uart_format" if uart_formats.changed(item) else "uart_duplex" if receive and dma else @@ -189,81 +279,13 @@ def _controller_constructor(item, result, board_bindings, routes, devices): declarations += [f"void {vector}_IRQHandler(void);", "/** \\brief Exact GD USART0 TX DMA terminal observation. */", f"void {vector}_IRQHandler(void) {{ nx_gd32_uart_{'duplex_dma' if receive else 'dma'}_irq(&s_nx_port_{name}); }}"] - initialize = f"prepare_{name}()" - stop = [f"stop_{name}()"] - elif kind == "spi": - dma = item["mode"] == "dma-full-duplex" - implementation = "spi_dma" if dma else "spi" - bus = f"s_nx_port_{name}" + (".spi" if dma else "") - if item["controller"] not in {"SPI0", "SPI4"} or not children: - raise ValueError("GD32 SPI requires maintained controller and explicit child") - descriptor = item["controller"].lower() - pin_settings = [(pin, 2, 0, False, None) for pin in item["pins"] if pin["function"] != "cs"] - cs_pins = [] - after = [] - first_call = None - prefix = [] - if dma: - region_lines, region_count = dma_regions(name, result) - declarations += region_lines - prefix = [f" s_nx_port_{name}.regions = s_nx_dma_regions_{name};", - f" s_nx_port_{name}.region_count = {region_count}u;"] - for index, child in enumerate(children): - child_name = _symbol(child["id"]) - if child.get("cs_binding"): - cs_route = routes[board_bindings[child["cs_binding"]]["route"]] - cs_pin, = cs_route["pins"] - else: - cs_pin, = [pin for pin in item["pins"] if pin["function"] == "cs"] - cs_pins.append(cs_pin) - pin_settings.append((cs_pin, 1, 0, False, True)) - wire = cs_pin["pin"] - cs_gpio = f"GPIO{wire[1]}" - cs_mask = 1 << int(wire[2:]) - maximum = child.options.max_hz - mode = child.options.mode - endpoint = f"s_nx_endpoint_{name}" if index == 0 else f"s_nx_endpoint_{child_name}" - if index: - declarations.append(f"static nx_gd32_{implementation}_endpoint_state_t {endpoint};") - declarations += enhanced_endpoints.face( - child, endpoint, f"nx_gd32_{implementation}_endpoint_ops", "gd32f470") - base = endpoint + (".endpoint" if dma else "") - # Every endpoint retains its own reviewed CS. No first-child bus alias. - after += [f" {base}.cs_gpio = {cs_gpio};", f" {base}.cs_mask = {cs_mask}u;"] - if dma: - after.append(f" {endpoint}.dma = &s_nx_port_{name};") - if index == 0: - if dma: - first_call = (f"nx_gd32_spi_dma_initialize(&s_nx_port_{name}, &{endpoint}, " - f"{cs_gpio}, {cs_mask}u, {maximum}u, {mode}u, {item.options.irq.priority}u)") - else: - first_call = (f"nx_gd32_spi_initialize_at(&s_nx_port_{name}, &{endpoint}, " - f"&nx_gd32_{descriptor}_controller, {cs_gpio}, {cs_mask}u, {maximum}u, {mode}u)") - else: - after += [f" status = nx_gd32_spi_endpoint_initialize(&{bus}, &{base}, {cs_gpio}, {cs_mask}u, {maximum}u, {mode}u);", - " if (status != NX_SUCCESS) { return status; }"] - prepare = prepared(name, pin_settings, first_call, after) - position = prepare.index(f" status = {first_call};") - prepare[position:position] = prefix - declarations += prepare - pins = [pin for pin, *_ in pin_settings] - stop_expression = (f"nx_spi_port_stop(nx_binding_{name})" if dma else - f"nx_gd32_spi_stop(&s_nx_port_{name})") - declarations += released(name, pins, stop_expression, cs_pins) - if dma: - tx, rx = hardware.maintained("gd32f470", item.controller).dma(item.mode) - for path, receive in ((rx, "true"), (tx, "false")): - vector = hardware.dma_vector("gd32f470", path) - declarations += [f"void {vector}_IRQHandler(void);", - "/** \\brief Exact selected GD SPI DMA terminal observation. */", - f"void {vector}_IRQHandler(void) {{ nx_gd32_spi_dma_irq(&s_nx_port_{name}, {receive}); }}"] - initialize = f"prepare_{name}()" - stop = [f"stop_{name}()"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall(f"stop_{name}")] elif kind == "i2c" and mode_traits(kind, item.mode).irq: - if item["controller"] != "I2C0": + if item.controller != "I2C0": raise ValueError("Only reviewed I2C0 asynchronous mode is maintained") - port = item["pins"][0]["pin"][1] - mask = sum(1 << int(pin["pin"][2:]) for pin in item["pins"]) + port = item.pins[0]["pin"][1] + mask = sum(1 << int(pin["pin"][2:]) for pin in item.pins) before = [f" s_nx_port_{name}.registers = (volatile uint32_t*)I2C0;", f" s_nx_port_{name}.line_inputs = &GPIO_ISTAT(GPIO{port});", f" s_nx_port_{name}.line_mask = {mask}u;", @@ -278,14 +300,14 @@ def _controller_constructor(item, result, board_bindings, routes, devices): else: before.append(f" s_nx_port_{name}.dma = NULL;") for index, child in enumerate(children): - child_name = _symbol(child["id"]) + child_name = _symbol(child.id) endpoint = f"s_nx_endpoint_{name}" if not index else f"s_nx_endpoint_{child_name}" if index: - declarations.append(f"static nx_gd32_i2c_async_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child['address']}u }};") + declarations.append(f"static nx_gd32_i2c_async_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child.options.address}u }};") declarations += _endpoint_face("i2c", child_name, endpoint, "i2c_async") call = f"nx_gd32_i2c_async_initialize(&s_nx_port_{name}, {item.options.irq.priority}u)" - declarations += prepared(name, [(pin, 2, 1, True, None) for pin in item["pins"]], call, before=before) - declarations += released(name, item["pins"], f"nx_i2c_port_stop(&s_nx_face_{name})") + declarations += prepared(name, [(pin, 2, 1, True, None) for pin in item.pins], call, before=before) + declarations += released(name, item.pins, f"nx_i2c_port_stop(&s_nx_face_{name})") port_binding = hardware.maintained("gd32f470", item.controller) for vector, _ in port_binding.irqs: declarations += [f"void {vector}_IRQHandler(void);", @@ -297,39 +319,39 @@ def _controller_constructor(item, result, board_bindings, routes, devices): declarations += [f"void {vector}_IRQHandler(void);", r"/** \brief Selected I2C DMA boundary publisher. */", f"void {vector}_IRQHandler(void) {{ nx_i2c_v1_dma_irq(&s_nx_port_{name}, {receive}); }}"] - initialize = f"prepare_{name}()" - stop = [f"stop_{name}()"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall(f"stop_{name}")] elif kind == "i2c": - if item["controller"] not in {"I2C0", "I2C1"} or not children: + if item.controller not in {"I2C0", "I2C1"} or not children: raise ValueError("GD32 I2C requires maintained controller and explicit address") - descriptor = item["controller"].lower() + descriptor = item.controller.lower() after = [] for index, child in enumerate(children): - child_name = _symbol(child["id"]) + child_name = _symbol(child.id) endpoint = f"s_nx_endpoint_{name}" if index == 0 else f"s_nx_endpoint_{child_name}" if index: - declarations.append(f"static nx_gd32_i2c_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child['address']}u }};") + declarations.append(f"static nx_gd32_i2c_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child.options.address}u }};") declarations += _endpoint_face("i2c", child_name, endpoint) - line_gpio = item["pins"][0]["pin"][1] - line_mask = sum(1 << int(pin["pin"][2:]) for pin in item["pins"]) + line_gpio = item.pins[0]["pin"][1] + line_mask = sum(1 << int(pin["pin"][2:]) for pin in item.pins) call = (f"nx_gd32_i2c_initialize_at(&s_nx_port_{name}, &s_nx_endpoint_{name}, " - f"&nx_gd32_{descriptor}_controller, {children[0]['address']}u, 100000u, GPIO{line_gpio}, {line_mask}u)") - declarations += prepared(name, [(pin, 2, 1, True, None) for pin in item["pins"]], call, after) - declarations += released(name, item["pins"], f"nx_gd32_i2c_stop(&s_nx_port_{name})") - initialize = f"prepare_{name}()" - stop = [f"stop_{name}()"] + f"&nx_gd32_{descriptor}_controller, {children[0].options.address}u, 100000u, GPIO{line_gpio}, {line_mask}u)") + declarations += prepared(name, [(pin, 2, 1, True, None) for pin in item.pins], call, after) + declarations += released(name, item.pins, f"nx_gd32_i2c_stop(&s_nx_port_{name})") + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall(f"stop_{name}")] elif kind == "flash": - if item["controller"] != "FMC" or item["pins"]: + if item.controller != "FMC" or item.pins: raise ValueError("GD32 maintains only its complete internal FMC") - initialize = f"nx_gd32_flash_initialize(&s_nx_port_{name})" - stop = [f"nx_gd32_flash_stop(&s_nx_port_{name})"] + initialize = DirectCall("nx_gd32_flash_initialize", (Address(f"s_nx_port_{name}"),)) + stop = [DirectCall("nx_gd32_flash_stop", (Address(f"s_nx_port_{name}"),))] elif kind == "watchdog": - if item["controller"] != "FWDGT" or item["pins"]: + if item.controller != "FWDGT" or item.pins: raise ValueError("GD32 maintains only independent FWDGT") - initialize = f"nx_gd32_watchdog_initialize(&s_nx_port_{name})" - stop = [f"nx_gd32_watchdog_stop(&s_nx_port_{name})"] + initialize = DirectCall("nx_gd32_watchdog_initialize", (Address(f"s_nx_port_{name}"),)) + stop = [DirectCall("nx_gd32_watchdog_stop", (Address(f"s_nx_port_{name}"),))] elif kind == "exti": - if item["controller"] != "EXTI3": + if item.controller != "EXTI3": raise ValueError("GD32 first-release route is EXTI3 PE3 only") _pins(item, [("PE3", "input", 0)]) edge = {"rising": "NX_EXTI_RISING", "falling": "NX_EXTI_FALLING", @@ -341,15 +363,14 @@ def _controller_constructor(item, result, board_bindings, routes, devices): priority = _integer(item.options.irq.priority, 0, 15, "EXTI requires explicit IRQ priority") declarations.append(f"static nx_exti_event_t s_nx_events_{name}[{capacity}];") - initialize = (f"nx_gd32_exti_initialize(&s_nx_port_{name}, " - f"&s_nx_exti_domain, 3u, 4u, {edge}, " - f"s_nx_events_{name}, {capacity}u, {priority}u)") + initialize = DirectCall("nx_gd32_exti_initialize", ( + Address(f"s_nx_port_{name}"), Address("s_nx_exti_domain"), 3, 4, + Symbol(edge), Symbol(f"s_nx_events_{name}"), capacity, priority)) # Admission can publish ownership before a terminal enable observation # fails. Cold rejection owns nothing; retained failure needs real stop. - stop = [f"s_nx_port_{name}.initialized ? " - f"nx_exti_port_stop(&s_nx_face_{name}) : NX_SUCCESS"] + stop = [DirectCall("nx_gd32_exti_close", (Address(f"s_nx_port_{name}"),))] elif kind == "pwm": - if item["controller"] != "TIMER2": + if item.controller != "TIMER2": raise ValueError("GD32 maintains TIMER2 channel 0 only") _pins(item, [("PA6", "ch0", 2)]) period = _integer(item.options.period_ticks, 1, LIMITS["pwm_period"], @@ -361,19 +382,19 @@ def _controller_constructor(item, result, board_bindings, routes, devices): "PWM requires an explicit tick_hz") if timer_hz % tick != 0 or not 1 <= timer_hz // tick <= 65536: raise ValueError(f"PWM tick_hz must exactly divide the {timer_hz} Hz timer") - if item["initial"] != 0: + if item.initial != 0: raise ValueError("GD32 fixed PWM route supports inactive low only") declarations += [f"static nx_result_t prepare_{name}(void) {{", f" nx_result_t status = nx_gd32_pwm_initialize(&s_nx_port_{name}, {period}u, {tick}u);", " if (status != NX_SUCCESS) { return status; }", f" return nx_pwm_port_set(&s_nx_face_{name}, {period}u, {duty}u);", "}"] - initialize = f"prepare_{name}()" - stop = [f"nx_gd32_pwm_release(&s_nx_port_{name})"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall("nx_gd32_pwm_release", (Address(f"s_nx_port_{name}"),))] elif kind == "adc": traits = mode_traits(kind, item.mode) stream, continuous = traits.dma, traits.continuous stream_state = f"s_nx_port_{name}" + (".base" if continuous else "") - if item["controller"] != "ADC0": + if item.controller != "ADC0": raise ValueError("ADC0 is the maintained GD32 controller") channels = list(item.options.channels) if tuple(channels) not in ADC_CHANNEL_SEQUENCES: @@ -410,11 +431,14 @@ def _controller_constructor(item, result, board_bindings, routes, devices): if continuous: prepare = declarations.index(f"static nx_result_t prepare_{name}(void) {{") declarations.insert(prepare + 1, f" s_nx_port_{name}.sample_times = s_nx_adc_samples_{name};") - initialize = f"prepare_{name}()" - stop = [f"nx_gd32_adc_{'continuous' if continuous else 'stream'}_stop(&s_nx_port_{name})"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall(f"nx_gd32_adc_{'continuous' if continuous else 'stream'}_stop", (Address(f"s_nx_port_{name}"),))] else: - initialize = f"nx_gd32_adc_initialize({arguments})" - stop = [f"nx_gd32_adc_stop(&s_nx_port_{name})"] + initialize = DirectCall("nx_gd32_adc_initialize", ( + Address(f"s_nx_port_{name}"), Symbol(f"s_nx_adc_channels_{name}"), + len(channels), reference, DirectCall("nx_deadline_after", ( + DirectCall("nx_time_now_us"), timeout * 1000)))) + stop = [DirectCall("nx_gd32_adc_stop", (Address(f"s_nx_port_{name}"),))] else: raise ValueError(f"No maintained GD32 constructor for {kind}") from .contract import mode_implementation @@ -430,6 +454,9 @@ def _controller_constructor(item, result, board_bindings, routes, devices): def constructor(item, result, board_bindings, routes, devices): """Emit one reviewed GD32 instance, including explicit GPIO construction.""" item = controller_ir(item, "gd32f470") + if item.kind == "spi": + construction = _spi_constructor(item, result, board_bindings, routes, devices) + return enhanced_endpoints.dma_construction(item, construction, "gd32") if secondary_controllers.applies(item, result, "gd32"): construction = secondary_controllers.constructor(item, result, "gd32") return enhanced_endpoints.dma_construction(item, construction, "gd32") @@ -446,14 +473,14 @@ def constructor(item, result, board_bindings, routes, devices): item, result, board_bindings, routes, devices) return enhanced_endpoints.dma_construction(item, construction, "gd32") name = _symbol(item.id) - index = ord(item["pins"][0]["pin"][1]) - ord("A") + index = ord(item.pins[0]["pin"][1]) - ord("A") mask = item.options.mask or sum(1 << int(pin["pin"][2:]) - for pin in item["pins"]) + for pin in item.pins) initial = item.options.initial - output = "true" if item.mode == "output" else "false" return Construction( state_type="nx_gd32_gpio_state_t", definitions=[], - initialize=(f"nx_gd32_gpio_initialize(&s_nx_port_{name}, " - f"{index}u, {mask}u, {initial}u, {output})"), - stop=[f"nx_gd32_gpio_stop(&s_nx_port_{name}, {initial}u)"], + initialize=DirectCall("nx_gd32_gpio_initialize", ( + Address(f"s_nx_port_{name}"), index, mask, initial, + item.mode == "output")), + stop=[DirectCall("nx_gd32_gpio_stop", (Address(f"s_nx_port_{name}"), initial))], retain_on_failed_init=False) diff --git a/soc/gd32/gd32f470/configure/emission.py b/soc/gd32/gd32f470/configure/emission.py index 64a248c06..574564931 100644 --- a/soc/gd32/gd32f470/configure/emission.py +++ b/soc/gd32/gd32f470/configure/emission.py @@ -38,14 +38,16 @@ def includes(result): ' nx_gd32_spi_dma_sequence_backend;'] if gpio_phase.applies(result): source += ['#include "gd32f4xx_gpio.h"', '#include "gd32f4xx_rcu.h"'] - if any(item["kind"] == "uart" and item["mode"] == "dma-tx" for item in selections): + if any(item.kind == "uart" and item.mode == "dma-tx" for item in selections): source.append('#include "gd32f470_dma.h"') - if any(item["kind"] == "spi" and item["mode"] == "dma-full-duplex" for item in selections): + if any(item.kind == "spi" for item in selections): + source.append('#include "gd32f470_spi_cold.h"') + if any(item.kind == "spi" and item.mode == "dma-full-duplex" for item in selections): source.append('#include "gd32f470_spi_dma.h"') if any(item.kind == "adc" and mode_traits(item.kind, item.mode).dma for item in selections): source.append('#include "gd32f470_adc_stream.h"') - if any(item["kind"] == "uart" and item["mode"] == "irq-blocks" for item in selections): + if any(item.kind == "uart" and item.mode == "irq-blocks" for item in selections): source.append('#include "gd32f470_uart_stream.h"') if any(item.kind == "uart" and mode_traits(item.kind, item.mode).rx_dma for item in selections): @@ -79,9 +81,9 @@ def instance_declarations(item, result): if kind in {"spi", "i2c"}: initializer = "" if kind == "i2c": - child = next((device for device in result.devices if device["controller"] == item.id), None) + child = next((device for device in result.devices if device.controller == item.id), None) if child is not None: - initializer = f" = {{ .port = &s_nx_port_{name}, .address = {child['address']}u }}" + initializer = f" = {{ .port = &s_nx_port_{name}, .address = {child.options.address}u }}" source.append('static nx_gd32' f"_{implementation(item)}_endpoint_state_t " f"s_nx_endpoint_{name}{initializer};") @@ -95,7 +97,7 @@ def has_dma(result): def _dma_clock_mask(result): controllers = {dma.split(":", 1)[0] - for item in result.controllers for dma in item.get("dma", ())} + for item in result.controllers for dma in item.dma} return " | ".join(f"RCU_AHB1EN_{name}EN" for name in sorted(controllers)) @@ -114,7 +116,7 @@ def state_declarations(result): def shared_irqs(result): selected = sorted((item for item in result.controllers if item.kind == "exti"), - key=lambda item: int(item["pins"][0]["pin"][2:])) + key=lambda item: int(item.pins[0]["pin"][2:])) if not selected: return [] pointers = ", ".join("&s_nx_port_" + item.id.replace("-", "_") @@ -125,7 +127,7 @@ def shared_irqs(result): f"{{ s_nx_exti_ports, {len(selected)}u }};"] vectors = {} for item in selected: - line = int(item["pins"][0]["pin"][2:]) + line = int(item.pins[0]["pin"][2:]) vector = (f"EXTI{line}" if line < 5 else "EXTI5_9" if line < 10 else "EXTI10_15") vectors[vector] = vectors.get(vector, 0) | (1 << line) @@ -164,7 +166,7 @@ def stop_system(result, *, system_phase=None): def start_system(result): dma_clock = has_dma(result) source = preflight.emit(result) - if result.get("can_domain") is not None: + if result.can_domain is not None: source.append(" s_nx_can_gpio_clocks_added = 0u;") if dma_clock: source.append(' s_nx_platform_dma_clocks_added = 0u;') diff --git a/soc/gd32/gd32f470/configure/preflight.py b/soc/gd32/gd32f470/configure/preflight.py index a1185b205..ea22a870a 100644 --- a/soc/gd32/gd32f470/configure/preflight.py +++ b/soc/gd32/gd32f470/configure/preflight.py @@ -44,23 +44,23 @@ def selected_preflight(result, *, dma_conditions=None): if item.kind == 'eth_frames': clocks.add(('RCU_AHB1EN', 'RCU_AHB1EN_ENETPTPEN')) elif item.kind == 'exti': - line = int(item['pins'][0]['pin'][2:]) + line = int(item.pins[0]['pin'][2:]) registers = 'EXTI_INTEN | EXTI_EVEN | EXTI_PD' source += _reject(f'(({registers}) & {1 << line}u) != 0u') for item in claims: - timer = item['key'].removeprefix('controller:') + timer = item.key.removeprefix('controller:') if re.fullmatch('TIM[1349]|TIMER[0238]', timer): clocks.add(_clock(timer)) - if result.get('can_domain') is not None: + if result.can_domain is not None: clocks.add(_clock('CAN0')) for register, mask in sorted(clocks): source += _reject(f'({register} & ({mask})) != 0u') - for key in sorted({item['key'] for item in claims if item['key'].startswith('irq:')}): + for key in sorted({item.key for item in claims if item.key.startswith('irq:')}): vector = key.removeprefix('irq:') + '_IRQn' bank = f'(uint32_t){vector} / 32u' mask = f'(1u << ((uint32_t){vector} % 32u))' source += _reject(f'((NVIC->ISER[{bank}] | NVIC->ISPR[{bank}] | NVIC->IABR[{bank}]) & {mask}) != 0u') - for key in sorted({item['key'] for item in claims if item['key'].startswith('dma:')}): + for key in sorted({item.key for item in claims if item.key.startswith('dma:')}): match = re.fullmatch('dma:(DMA[0-9]+):stream([0-7])', key) if match is None: fail('No maintained selected DMA lane admission: ' + key) diff --git a/soc/gd32/gd32f470/configure/secondary.py b/soc/gd32/gd32f470/configure/secondary.py index 71cd54198..34cf6653e 100644 --- a/soc/gd32/gd32f470/configure/secondary.py +++ b/soc/gd32/gd32f470/configure/secondary.py @@ -28,23 +28,6 @@ def busy_condition(controller, kind): return None -def spi_endpoint(name, child, endpoint, wire, mask, mode, rate): - return [], [ - f" status = nx_gd32_spi_endpoint_initialize(&s_nx_port_{name}.spi, &{endpoint}.endpoint, GPIO{wire[1]}, {mask}u, {rate}u, {mode}u);", - " if (status != NX_SUCCESS) { return status; }", - f" {endpoint}.dma = &s_nx_port_{name};"] - - def setup_uart(item, name, state, paths, clock_hz): return [], ( f"nx_gd32_uart_dma_initialize_at(&{state}, &nx_gd32_{item.controller.lower()}_controller, {item.options.baud}u, NX_UART_RX_{item.options.rx_profile.upper()}, s_nx_rx_{name}, {item.options.rx_capacity}u, {item.options.irq.priority}u)") - - -def setup_spi(item, result, name, state, paths, clock_hz, children, endpoints): - device = children[0] - wire = (result["routes"][result["board_bindings"][device["cs_binding"]] - ["route"]]["pins"][0]["pin"] if "cs_binding" in device else - next(pin["pin"] for pin in item["pins"] if pin["function"] == "cs")) - call = ( - f"nx_gd32_spi_dma_initialize_at(&{state}, &s_nx_endpoint_{name}, &nx_gd32_{item.controller.lower()}_controller, GPIO{wire[1]}, {1 << int(wire[2:])}u, {device.options.max_hz}u, {device.options.mode}u, {item.options.irq.priority}u)") - return [], call, endpoints diff --git a/soc/gd32/gd32f470/drivers/exti.c b/soc/gd32/gd32f470/drivers/exti.c index e4c1ca95a..2e3aa7b74 100644 --- a/soc/gd32/gd32f470/drivers/exti.c +++ b/soc/gd32/gd32f470/drivers/exti.c @@ -208,6 +208,14 @@ nx_result_t nx_gd32_exti_read(void* context, nx_exti_event_t* events, return *count ? NX_SUCCESS : NX_ERROR_EMPTY; } +/** \brief Cold cleanup skips rejected storage but retains live IRQ ownership. + */ +nx_result_t nx_gd32_exti_close(nx_gd32_exti_state_t* port) { + if (port == NULL) + return NX_ERROR_INVALID; + return port->initialized ? nx_gd32_exti_stop(port) : NX_SUCCESS; +} + /** \brief Withdraw owned sources before releasing borrowed storage. * \note Refused writes retain domain, queue and wake obligations; * a later serialized stop retries without disturbing peers. diff --git a/soc/gd32/gd32f470/drivers/spi.c b/soc/gd32/gd32f470/drivers/spi.c index 8f552edc0..81f40802a 100644 --- a/soc/gd32/gd32f470/drivers/spi.c +++ b/soc/gd32/gd32f470/drivers/spi.c @@ -7,6 +7,7 @@ * \copyright Copyright (c) 2026 Nexus Team */ #include "gd32f470_provider.h" +#include "gd32f470_spi_cold.h" #include "gd32f4xx.h" #include "nexus/io/spi_transaction.h" #include "private/system.h" @@ -16,6 +17,182 @@ const nx_gd32_spi_controller_t nx_gd32_spi0_controller = {SPI0, 100000000U, const nx_gd32_spi_controller_t nx_gd32_spi4_controller = {SPI4, 100000000U, RCU_SPI4}; +#ifndef NX_PLATFORM_REGISTER_WRITE +#define NX_PLATFORM_REGISTER_WRITE(reg, value) ((reg) = (uint32_t)(value)) +#endif + +static bool cold_context(void) { + return nx_arch_is_privileged() && !nx_gd32_in_isr() && + !nx_gd32_irq_masked(); +} + +bool nx_gd32_spi_config_valid(const nx_gd32_spi_config_t* config) { + if (config == NULL || config->pins == NULL || config->endpoints == NULL || + config->endpoint_count == 0U || config->endpoint_count > 13U || + config->pin_count != 3U + config->endpoint_count || + (config->controller != &nx_gd32_spi0_controller && + config->controller != &nx_gd32_spi4_controller)) + return false; + for (size_t i = 0U; i < config->pin_count; ++i) { + const nx_gd32_spi_pin_config_t* pin = &config->pins[i]; + if (pin->snapshot == NULL || pin->port_index > 8U || pin->pin > 15U || + pin->alternate > 15U || + pin->registers != GPIOA + (uint32_t)pin->port_index * 0x400U) + return false; + for (size_t prior = 0U; prior < i; ++prior) { + const nx_gd32_spi_pin_config_t* other = &config->pins[prior]; + if (pin->snapshot == other->snapshot || + (pin->registers == other->registers && pin->pin == other->pin)) + return false; + } + } + for (size_t i = 0U; i < config->endpoint_count; ++i) { + const nx_gd32_spi_endpoint_config_t* endpoint = &config->endpoints[i]; + if (endpoint->endpoint == NULL || endpoint->mode > 3U || + endpoint->frequency_hz < (config->controller->clock_hz >> 8U) || + endpoint->frequency_hz > config->controller->clock_hz / 2U) + return false; + for (size_t prior = 0U; prior < i; ++prior) { + if (endpoint->endpoint == config->endpoints[prior].endpoint) + return false; + } + } + return true; +} + +nx_result_t nx_gd32_spi_pins_prepare(const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (cold == NULL || !nx_gd32_spi_config_valid(config)) + return NX_ERROR_INVALID; + if (cold->pin_leases != 0U || cold->constructed || cold->clock_owned) + return NX_ERROR_BUSY; + const uint32_t clock = config->controller->clock; + if ((RCU_REG_VAL(clock) & BIT(RCU_BIT_POS(clock))) != 0U) + return NX_ERROR_BUSY; + for (size_t i = 0U; i < config->pin_count; ++i) { + const nx_gd32_spi_pin_config_t* pin = &config->pins[i]; + nx_result_t status = nx_gd32_gpio_clock_enable(pin->port_index); + if (status != NX_SUCCESS) + return status; + *pin->snapshot = nx_gd32_pin_capture(pin->registers, pin->pin); + cold->pin_leases |= (uint16_t)(1U << i); + if (i >= 3U) { + const uint32_t bit = 1U << pin->pin; + NX_PLATFORM_REGISTER_WRITE(GPIO_BOP(pin->registers), bit); + nx_arch_dsb(); + if ((GPIO_OCTL(pin->registers) & bit) == 0U) + return NX_ERROR_IO; + } + status = + nx_gd32_pin_configure(pin->registers, pin->pin, i >= 3U ? 1U : 2U, + pin->alternate, 0U, false); + if (status != NX_SUCCESS) + return status; + } + return NX_SUCCESS; +} + +nx_result_t nx_gd32_spi_pins_close(const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (cold == NULL || !nx_gd32_spi_config_valid(config)) + return NX_ERROR_INVALID; + /* Prove every acquired CS inactive before restoring any selected pad. */ + for (size_t i = 3U; i < config->pin_count; ++i) { + if ((cold->pin_leases & (1U << i)) == 0U) + continue; + const nx_gd32_spi_pin_config_t* pin = &config->pins[i]; + const uint32_t bit = 1U << pin->pin; + NX_PLATFORM_REGISTER_WRITE(GPIO_BOP(pin->registers), bit); + nx_arch_dsb(); + if ((GPIO_OCTL(pin->registers) & bit) == 0U) + return NX_ERROR_IO; + } + for (size_t count = config->pin_count; count != 0U; --count) { + const uint16_t owned = (uint16_t)(1U << (count - 1U)); + if ((cold->pin_leases & owned) == 0U) + continue; + const nx_gd32_spi_pin_config_t* pin = &config->pins[count - 1U]; + const uint32_t mask = 3U << (pin->pin * 2U); + const uint32_t mode = GPIO_CTL(pin->registers) & mask; + if (mode != 0U && mode != mask) { + NX_PLATFORM_REGISTER_WRITE(GPIO_CTL(pin->registers), + GPIO_CTL(pin->registers) | mask); + nx_arch_dsb(); + if ((GPIO_CTL(pin->registers) & mask) != mask) + return NX_ERROR_IO; + } + nx_gd32_pin_restore(pin->registers, pin->pin, *pin->snapshot); + nx_arch_dsb(); + if (nx_gd32_pin_capture(pin->registers, pin->pin) != *pin->snapshot) + return NX_ERROR_IO; + cold->pin_leases &= (uint16_t)~owned; + } + if (cold->clock_owned) { + const uint32_t clock = config->controller->clock; + const uint32_t mask = BIT(RCU_BIT_POS(clock)); + NX_PLATFORM_REGISTER_WRITE(RCU_REG_VAL(clock), + RCU_REG_VAL(clock) & ~mask); + nx_arch_dsb(); + if ((RCU_REG_VAL(clock) & mask) != 0U) + return NX_ERROR_IO; + cold->clock_owned = false; + } + return NX_SUCCESS; +} + +nx_result_t nx_gd32_spi_prepare(nx_gd32_spi_state_t* port, + const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (port == NULL || cold == NULL) + return NX_ERROR_INVALID; + if (port->initialized || port->active) + return NX_ERROR_BUSY; + nx_result_t status = nx_gd32_spi_pins_prepare(config, cold); + if (status != NX_SUCCESS) + return status; + const nx_gd32_spi_endpoint_config_t* first = &config->endpoints[0]; + const nx_gd32_spi_pin_config_t* cs = &config->pins[3]; + status = nx_gd32_spi_initialize_at( + port, first->endpoint, config->controller, cs->registers, 1U << cs->pin, + first->frequency_hz, first->mode); + if (status != NX_SUCCESS) + return status; + cold->constructed = true; + cold->clock_owned = true; + for (size_t i = 1U; i < config->endpoint_count; ++i) { + const nx_gd32_spi_endpoint_config_t* endpoint = &config->endpoints[i]; + cs = &config->pins[3U + i]; + status = nx_gd32_spi_endpoint_initialize( + port, endpoint->endpoint, cs->registers, 1U << cs->pin, + endpoint->frequency_hz, endpoint->mode); + if (status != NX_SUCCESS) + return status; + } + return NX_SUCCESS; +} + +nx_result_t nx_gd32_spi_close(nx_gd32_spi_state_t* port, + const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (port == NULL || cold == NULL || !nx_gd32_spi_config_valid(config)) + return NX_ERROR_INVALID; + if (cold->constructed) { + nx_result_t status = nx_gd32_spi_stop(port); + if (status != NX_SUCCESS) + return status; + cold->constructed = false; + } + return nx_gd32_spi_pins_close(config, cold); +} + /** \brief Poll a hardware fact without silently ignoring faults. */ static nx_result_t wait_status(nx_gd32_spi_state_t* port, uint32_t mask, bool set, nx_time_us_t deadline) { @@ -105,7 +282,13 @@ nx_result_t nx_gd32_spi_initialize_at( ((uint32_t)divisor << 3U) | ((mode & 1U) != 0U ? SPI_CTL0_CKPH : 0U) | ((mode & 2U) != 0U ? SPI_CTL0_CKPL : 0U); - rcu_periph_clock_enable((rcu_periph_enum)controller->clock); + const uint32_t clock = controller->clock; + const uint32_t gate = BIT(RCU_BIT_POS(clock)); + NX_PLATFORM_REGISTER_WRITE(RCU_REG_VAL(clock), RCU_REG_VAL(clock) | gate); + nx_gd32_peripheral_barrier(); + if ((RCU_REG_VAL(clock) & gate) == 0U) { + return NX_ERROR_IO; + } spi_i2s_deinit(controller->registers); SPI_CTL0(controller->registers) = control; SPI_CTL1(controller->registers) = 0U; @@ -219,8 +402,14 @@ nx_result_t nx_gd32_spi_stop(nx_gd32_spi_state_t* port) { } spi_i2s_deinit(port->controller->registers); nx_gd32_peripheral_barrier(); + const uint32_t clock = port->controller->clock; + const uint32_t gate = BIT(RCU_BIT_POS(clock)); + NX_PLATFORM_REGISTER_WRITE(RCU_REG_VAL(clock), RCU_REG_VAL(clock) & ~gate); + nx_gd32_peripheral_barrier(); + if ((RCU_REG_VAL(clock) & gate) != 0U) { + return NX_ERROR_IO; + } port->initialized = false; - rcu_periph_clock_disable((rcu_periph_enum)port->controller->clock); return NX_SUCCESS; } diff --git a/soc/gd32/gd32f470/drivers/spi_dma.c b/soc/gd32/gd32f470/drivers/spi_dma.c index 83bcc1062..0bb602ec0 100644 --- a/soc/gd32/gd32f470/drivers/spi_dma.c +++ b/soc/gd32/gd32f470/drivers/spi_dma.c @@ -583,6 +583,129 @@ static nx_result_t stop(void* context) { return NX_SUCCESS; } +#ifndef NX_PLATFORM_REGISTER_WRITE +#define NX_PLATFORM_REGISTER_WRITE(reg, value) ((reg) = (uint32_t)(value)) +#endif +#ifndef NX_PLATFORM_PRIORITY_WRITE +#define NX_PLATFORM_PRIORITY_WRITE(reg, value) ((reg) = (uint8_t)(value)) +#endif + +static bool cold_config(const nx_gd32_spi_dma_config_t* config) { + return config != NULL && nx_gd32_spi_config_valid(&config->spi) && + config->regions != NULL && config->region_count != 0U && + config->priority <= 15U; +} + +nx_result_t nx_gd32_spi_dma_prepare(nx_gd32_spi_dma_state_t* state, + const nx_gd32_spi_dma_config_t* config, + nx_gd32_spi_dma_cold_t* cold) { + if (!nx_arch_is_privileged() || nx_gd32_in_isr() || nx_gd32_irq_masked()) + return NX_ERROR_CONTEXT; + if (state == NULL || cold == NULL || !cold_config(config)) + return NX_ERROR_INVALID; + if (cold->vectors || cold->spi.constructed || cold->spi.pin_leases != 0U || + cold->spi.clock_owned || state->initialized || state->spi.initialized || + state->spi.active || state->active != NULL || state->wake != NULL) + return NX_ERROR_BUSY; + const nx_gd32_spi_controller_t* controller = config->spi.controller; + const unsigned vectors[2] = {(unsigned)dma_irq_at(controller, false), + (unsigned)dma_irq_at(controller, true)}; + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint32_t mask = 1UL << (irq % 32U); + if (((NVIC->ISER[irq / 32U] | NVIC->ISPR[irq / 32U] | + NVIC->IABR[irq / 32U]) & + mask) != 0U) + return NX_ERROR_BUSY; + } + if (((*engine_at(controller, true) | *engine_at(controller, false)) & + DMA_CHXCTL_CHEN) != 0U) + return NX_ERROR_BUSY; + if (config->kernel_bound && NVIC_GetPriorityGrouping() > 3U) + return NX_ERROR_PERMISSION; + const uint32_t clock = controller->clock; + if ((RCU_REG_VAL(clock) & BIT(RCU_BIT_POS(clock))) != 0U) + return NX_ERROR_BUSY; + for (unsigned i = 0U; i < 2U; ++i) + cold->priorities[i] = NVIC->IPR[vectors[i]]; + cold->vectors = true; + nx_result_t status = nx_gd32_spi_pins_prepare(&config->spi, &cold->spi); + if (status != NX_SUCCESS) + return status; + state->regions = config->regions; + state->region_count = config->region_count; + const nx_gd32_spi_endpoint_config_t* first = &config->spi.endpoints[0]; + const nx_gd32_spi_pin_config_t* cs = &config->spi.pins[3]; + status = nx_gd32_spi_dma_initialize_at( + state, (nx_gd32_spi_dma_endpoint_state_t*)first->endpoint, controller, + cs->registers, 1U << cs->pin, first->frequency_hz, first->mode, + config->priority); + if (status != NX_SUCCESS) + return status; + cold->spi.constructed = true; + cold->spi.clock_owned = true; + for (size_t i = 1U; i < config->spi.endpoint_count; ++i) { + const nx_gd32_spi_endpoint_config_t* endpoint = + &config->spi.endpoints[i]; + cs = &config->spi.pins[3U + i]; + status = nx_gd32_spi_endpoint_initialize( + &state->spi, endpoint->endpoint, cs->registers, 1U << cs->pin, + endpoint->frequency_hz, endpoint->mode); + if (status != NX_SUCCESS) + return status; + ((nx_gd32_spi_dma_endpoint_state_t*)endpoint->endpoint)->dma = state; + } + if ((RCU_REG_VAL(clock) & BIT(RCU_BIT_POS(clock))) == 0U) + return NX_ERROR_IO; + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint8_t priority = (uint8_t)(config->priority << 4U); + NX_PLATFORM_PRIORITY_WRITE(NVIC->IPR[irq], priority); + nx_arch_dsb(); + if (NVIC->IPR[irq] != priority || + (NVIC->ISER[irq / 32U] & (1UL << (irq % 32U))) == 0U) + return NX_ERROR_IO; + } + return NX_SUCCESS; +} + +nx_result_t nx_gd32_spi_dma_close(nx_gd32_spi_dma_state_t* state, + const nx_gd32_spi_dma_config_t* config, + nx_gd32_spi_dma_cold_t* cold) { + if (!nx_arch_is_privileged() || nx_gd32_in_isr() || nx_gd32_irq_masked()) + return NX_ERROR_CONTEXT; + if (state == NULL || cold == NULL || !cold_config(config)) + return NX_ERROR_INVALID; + if (cold->spi.constructed) { + nx_result_t status = stop(state); + if (status != NX_SUCCESS) + return status; + cold->spi.constructed = false; + } + if (cold->vectors) { + const nx_gd32_spi_controller_t* controller = config->spi.controller; + const unsigned vectors[2] = {(unsigned)dma_irq_at(controller, false), + (unsigned)dma_irq_at(controller, true)}; + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint32_t mask = 1UL << (irq % 32U); + NX_PLATFORM_REGISTER_WRITE(NVIC->ICER[irq / 32U], mask); + NX_PLATFORM_REGISTER_WRITE(NVIC->ICPR[irq / 32U], mask); + nx_arch_dsb(); + if (((NVIC->ISER[irq / 32U] | NVIC->ISPR[irq / 32U] | + NVIC->IABR[irq / 32U]) & + mask) != 0U) + return NX_ERROR_BUSY; + NX_PLATFORM_PRIORITY_WRITE(NVIC->IPR[irq], cold->priorities[i]); + nx_arch_dsb(); + if (NVIC->IPR[irq] != cold->priorities[i]) + return NX_ERROR_IO; + } + cold->vectors = false; + } + return nx_gd32_spi_pins_close(&config->spi, &cold->spi); +} + /** \brief Explicit idle recovery clears a fault without replay or clock theft. */ static nx_result_t recover(void* context) { diff --git a/soc/gd32/gd32f470/private/gd32f470_provider.h b/soc/gd32/gd32f470/private/gd32f470_provider.h index 4d1153fa0..22a463616 100644 --- a/soc/gd32/gd32f470/private/gd32f470_provider.h +++ b/soc/gd32/gd32f470/private/gd32f470_provider.h @@ -481,6 +481,12 @@ nx_result_t nx_gd32_watchdog_state(const void* context, extern const nx_exti_ops_t nx_gd32_exti_ops; nx_result_t nx_gd32_exti_read(void* context, nx_exti_event_t* events, size_t capacity, size_t* count); +/** \brief Close an accepted cold EXTI instance or a side-effect-free rejection. + * \param[in,out] port: Serialized static owner retained after failed admission. + * \return Success if cold; active IRQ or failed withdrawal keeps its lease. + * \note Same task context and ownership rules as stop for initialized storage. + */ +nx_result_t nx_gd32_exti_close(nx_gd32_exti_state_t* port); nx_result_t nx_gd32_exti_stop(void* context); extern const nx_pwm_ops_t nx_gd32_pwm_ops; nx_result_t nx_gd32_pwm_set(void* context, uint32_t period_ticks, diff --git a/soc/gd32/gd32f470/private/gd32f470_spi_cold.h b/soc/gd32/gd32f470/private/gd32f470_spi_cold.h new file mode 100644 index 000000000..dedace7e8 --- /dev/null +++ b/soc/gd32/gd32f470/private/gd32f470_spi_cold.h @@ -0,0 +1,89 @@ +/** + * \file gd32f470_spi_cold.h + * \brief Static GD470 SPI resources and retained cold pin leases + * \author Nexus Team + */ +#ifndef NEXUS_GD32F470_SPI_COLD_H +#define NEXUS_GD32F470_SPI_COLD_H + +#include "gd32f470_provider.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \brief Exact pad and the same snapshot slot owned by Platform cleanup. */ +typedef struct { + uint32_t registers; + uint16_t* snapshot; + uint8_t port_index; + uint8_t pin; + uint8_t alternate; +} nx_gd32_spi_pin_config_t; + +/** \brief One immutable endpoint bound to the corresponding CS pad. */ +typedef struct { + nx_gd32_spi_endpoint_state_t* endpoint; + uint32_t frequency_hz; + uint8_t mode; +} nx_gd32_spi_endpoint_config_t; + +/** \brief Three signal pads followed by one pad per endpoint. */ +typedef struct { + const nx_gd32_spi_controller_t* controller; + const nx_gd32_spi_pin_config_t* pins; + size_t pin_count; + const nx_gd32_spi_endpoint_config_t* endpoints; + size_t endpoint_count; +} nx_gd32_spi_config_t; + +/** \brief Actual acquired effects retained until checked close succeeds. */ +typedef struct { + uint16_t pin_leases; + bool constructed; + bool clock_owned; +} nx_gd32_spi_cold_t; + +/** \brief Validate static endpoint and pin identities without hardware effects. + */ +bool nx_gd32_spi_config_valid(const nx_gd32_spi_config_t* config); +/** \brief Acquire exact signal and CS pads for a cold selected controller. + * \param[in] config: Static resources with distinct Platform snapshot slots. + * \param[in,out] cold: Initially zero acquisition receipt, retained on failure. + * \return Success or error; after any failure close must consume actual leases. + * \note Privileged serialized task with interrupts unmasked; no allocation. + */ +nx_result_t nx_gd32_spi_pins_prepare(const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold); +/** \brief Prove CS inactive, restore leased pads, then release the owned gate. + * \param[in] config: The same static resources supplied to prepare. + * \param[in,out] cold: Actual receipt; failed restoration remains owned. + * \return Success or error retaining responsibility for explicit retry. + */ +nx_result_t nx_gd32_spi_pins_close(const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold); +/** \brief Prepare polling SPI and all selected endpoints without discovery. + * \param[in,out] port: Initially cold static provider in serialized task + * context. \param[in] config: Exact immutable controller, pad and endpoint + * connections. \param[in,out] cold: Initially zero receipt retained until + * successful close. \return Success or error; retained failure requires close + * even before start. + */ +nx_result_t nx_gd32_spi_prepare(nx_gd32_spi_state_t* port, + const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold); +/** \brief Stop idle polling SPI and release actual cold acquisition effects. + * \param[in,out] port: Same owner; all I/O calls must already be joined. + * \param[in] config: Unchanged static description used during preparation. + * \param[in,out] cold: Acquired receipt, retained across failed cleanup + * retries. \return Success or BUSY/IO retaining actual unfinished + * responsibilities. + */ +nx_result_t nx_gd32_spi_close(nx_gd32_spi_state_t* port, + const nx_gd32_spi_config_t* config, + nx_gd32_spi_cold_t* cold); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/soc/gd32/gd32f470/private/gd32f470_spi_dma.h b/soc/gd32/gd32f470/private/gd32f470_spi_dma.h index ca44f539c..650c9e882 100644 --- a/soc/gd32/gd32f470/private/gd32f470_spi_dma.h +++ b/soc/gd32/gd32f470/private/gd32f470_spi_dma.h @@ -6,7 +6,7 @@ #ifndef NEXUS_GD32F470_SPI_DMA_H #define NEXUS_GD32F470_SPI_DMA_H -#include "gd32f470_provider.h" +#include "gd32f470_spi_cold.h" #include "nexus/io/dma.h" typedef struct nx_gd32_spi_dma_endpoint_state nx_gd32_spi_dma_endpoint_state_t; @@ -42,9 +42,44 @@ struct nx_gd32_spi_dma_endpoint_state { nx_gd32_spi_dma_state_t* dma; }; +/** \brief Immutable selected DMA domains and publisher priority. */ +typedef struct { + nx_gd32_spi_config_t spi; + const nx_dma_memory_region_t* regions; + size_t region_count; + uint8_t priority; + bool kernel_bound; +} nx_gd32_spi_dma_config_t; + +/** \brief Pins and IRQ priority leases remain until actual close succeeds. */ +typedef struct { + nx_gd32_spi_cold_t spi; + uint8_t priorities[2]; + bool vectors; +} nx_gd32_spi_dma_cold_t; + #ifdef __cplusplus extern "C" { #endif +/** \brief Prepare exact SPI0/SPI4 pads, endpoints and DMA publishers. + * \param[in,out] state: Cold serialized owner with no outstanding request. + * \param[in] config: Immutable exact resources, retained through close. + * \param[in,out] cold: Zero initial receipt; failed admission retains effects. + * \return Success or error requiring close to consume any actual acquisitions. + * \note Privileged task, interrupts unmasked; no allocation or discovery. + */ +nx_result_t nx_gd32_spi_dma_prepare(nx_gd32_spi_dma_state_t* state, + const nx_gd32_spi_dma_config_t* config, + nx_gd32_spi_dma_cold_t* cold); +/** \brief Drain provider, withdraw vectors and restore exact cold resources. + * \param[in,out] state: Same owner after external callers have been joined. + * \param[in] config: Unchanged selected resource description. + * \param[in,out] cold: Actual receipt retained until every effect is restored. + * \return Success or BUSY/IO retaining responsibility for explicit retry. + */ +nx_result_t nx_gd32_spi_dma_close(nx_gd32_spi_dma_state_t* state, + const nx_gd32_spi_dma_config_t* config, + nx_gd32_spi_dma_cold_t* cold); extern const nx_spi_ops_t nx_gd32_spi_dma_ops; extern const nx_spi_endpoint_ops_t nx_gd32_spi_dma_endpoint_ops; /** diff --git a/soc/native/configure/bindings.py b/soc/native/configure/bindings.py index 2e531781f..133b25ace 100644 --- a/soc/native/configure/bindings.py +++ b/soc/native/configure/bindings.py @@ -1,5 +1,6 @@ """Emit exact host model storage and wiring, without runtime hardware policy.""" -from tools.configure.ir import controller_ir +from tools.configure.ir import ControllerIR +from tools.configure.calls import Address, DirectCall, Symbol from tools.configure.providers.common import Construction @@ -11,40 +12,42 @@ def symbol(name): def constructor(item, result, board_bindings, routes, devices): """Construct one validated static instance using narrow provider calls.""" del board_bindings, routes - item = controller_ir(item, 'native') - name = symbol(item["id"]) - kind = item["kind"] + if not isinstance(item, ControllerIR) or item.provider != "native": + raise ValueError("Native construction requires its resolved controller") + name = symbol(item.id) + kind = item.kind state = f"&s_nx_port_{name}" face = f"&s_nx_face_{name}" + state_address = Address(f"s_nx_port_{name}") + face_address = Address(f"s_nx_face_{name}") definitions = [] stop = [] if kind in {"rtc", "lptim"}: - extra = f", {item.options.tick_hz}u" if kind == "lptim" else "" + arguments = (state_address, Address("s_nx_native_time_clock")) + if kind == "lptim": + arguments += (item.options.tick_hz,) return Construction( state_type=f"nx_native_{kind}_state_t", definitions=[], - initialize=(f"(s_nx_port_{name}.constructed ? " - f"nx_native_{kind}_start({state}) : " - f"nx_native_{kind}_construct({state}, " - f"&s_nx_native_time_clock{extra}))"), - stop=[f"nx_{kind}_port_stop({face})"], + initialize=DirectCall(f"nx_native_{kind}_prepare", arguments), + stop=[DirectCall(f"nx_{kind}_port_stop", (face_address,))], retain_on_failed_init=False) if kind == "gpio": - mask = item["mask"] or sum(1 << int(pin["pin"][2:]) - for pin in item["pins"]) - initial = item["initial"] - output = "true" if item["mode"] == "output" else "false" - initialize = (f"nx_native_gpio_configure_instance({state}, {mask}u, " - f"{initial}u, {output})") - stop = [f"nx_native_gpio_stop_instance({state}, {initial}u)"] + mask = item.mask or sum(1 << int(pin["pin"][2:]) + for pin in item.pins) + initial = item.initial + initialize = DirectCall("nx_native_gpio_configure_instance", + (state_address, mask, initial, item.mode == "output")) + stop = [DirectCall("nx_native_gpio_stop_instance", + (state_address, initial))] elif kind == "uart": capacity = item.options.rx_capacity if item.mode == "irq-words": return Construction( state_type="nx_native_uart_word_state_t", definitions=[f"static uint16_t s_nx_rx_{name}[{capacity}];"], - initialize=(f"nx_native_uart_word_initialize({state}, " - f"s_nx_rx_{name}, {capacity}u, NULL, 0u)"), - stop=[f"nx_uart_word_port_stop({face})"], + initialize=DirectCall("nx_native_uart_word_initialize", + (state_address, Symbol(f"s_nx_rx_{name}"), capacity, None, 0)), + stop=[DirectCall("nx_uart_word_port_stop", (face_address,))], retain_on_failed_init=False) events = item.options.rx_profile == "events" data_type = "nx_uart_rx_event_t" if events else "uint8_t" @@ -54,9 +57,9 @@ def constructor(item, result, board_bindings, routes, devices): f" .profile = {profile}, .rx_storage = s_nx_rx_{name},", f" .rx_capacity = {capacity}u, .automatic_irq = true", "};"] - initialize = (f"nx_native_uart_configure_instance({state}, " - f"&s_nx_config_{name})") - stop = [f"nx_uart_port_stop({face})"] + initialize = DirectCall("nx_native_uart_configure_instance", + (state_address, Address(f"s_nx_config_{name}"))) + stop = [DirectCall("nx_uart_port_stop", (face_address,))] elif kind == "spi" and item.interface_kind == "spi_dma_transaction": sequence = f"s_nx_sequence_{name}" return Construction( @@ -72,8 +75,8 @@ def constructor(item, result, board_bindings, routes, devices): " if (status != NX_SUCCESS) { return status; }", f" return nx_spi_dma_sequence_initialize(&{sequence},", f" &nx_native_spi_dma_sequence_backend, {state});", "}"], - initialize=f"prepare_{name}()", - stop=[f"nx_spi_dma_transaction_stop({face})"], + initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall("nx_spi_dma_transaction_stop", (face_address,))], retain_on_failed_init=True) elif kind in {"spi", "i2c"}: definitions = [f"static nx_result_t prepare_{name}(void) {{", @@ -90,11 +93,11 @@ def constructor(item, result, board_bindings, routes, devices): f" &s_nx_endpoint_{child}, {state}, {memory}, {size}u{address});", " if (status != NX_SUCCESS) { return status; }"] definitions += [" return NX_SUCCESS;", "}"] - initialize = f"prepare_{name}()" - stop = [f"nx_spi_port_stop({face})" if kind == "spi" - else f"nx_native_i2c_stop_instance({state})"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall("nx_spi_port_stop", (face_address,)) if kind == "spi" + else DirectCall("nx_native_i2c_stop_instance", (state_address,))] elif kind == "flash": - flash = result["flash"] + flash = result.flash offset = 0 definitions = [f"static uint8_t s_nx_model_flash_{name}[{flash['size']}];", f"static const nx_flash_sector_t s_nx_sectors_{name}[] = {{"] @@ -106,21 +109,24 @@ def constructor(item, result, board_bindings, routes, devices): f" {flash['origin']}u, {flash['size']}u, 1u,", f" s_nx_sectors_{name}, {len(flash['erase_blocks'])}u, 0u", "};"] - initialize = (f"nx_native_flash_initialize_instance({state}, " - f"s_nx_model_flash_{name}, &s_nx_geometry_{name})") - stop = [f"nx_native_flash_stop_instance({state})"] + initialize = DirectCall("nx_native_flash_initialize_instance", + (state_address, Symbol(f"s_nx_model_flash_{name}"), + Address(f"s_nx_geometry_{name}"))) + stop = [DirectCall("nx_native_flash_stop_instance", (state_address,))] elif kind == "watchdog": - initialize = f"nx_native_watchdog_initialize_instance({state})" + initialize = DirectCall("nx_native_watchdog_initialize_instance", + (state_address,)) # Independent watchdog effects intentionally survive platform stop. elif kind == "exti": capacity = item.options.event_capacity edge = {"rising": "NX_EXTI_RISING", "falling": "NX_EXTI_FALLING", "both": "NX_EXTI_BOTH"}[item.options.edge] - line = int(item["pins"][0]["pin"][2:]) + line = int(item.pins[0]["pin"][2:]) definitions = [f"static nx_exti_event_t s_nx_events_{name}[{capacity}];"] - initialize = (f"nx_native_exti_configure_instance({state}, {line}u, " - f"{edge}, s_nx_events_{name}, {capacity}u)") - stop = [f"nx_exti_port_stop({face})"] + initialize = DirectCall("nx_native_exti_configure_instance", + (state_address, line, Symbol(edge), Symbol(f"s_nx_events_{name}"), + capacity)) + stop = [DirectCall("nx_exti_port_stop", (face_address,))] elif kind == "pwm": definitions = [f"static nx_result_t prepare_{name}(void) {{", f" nx_result_t status = nx_native_pwm_configure_instance({state},", @@ -128,15 +134,15 @@ def constructor(item, result, board_bindings, routes, devices): " if (status != NX_SUCCESS) { return status; }", f" return nx_pwm_port_set({face}, {item.options.period_ticks}u, {item.options.duty_ticks}u);", "}"] - initialize = f"prepare_{name}()" - stop = [f"nx_native_pwm_stop_instance({state})"] + initialize = DirectCall(f"prepare_{name}") + stop = [DirectCall("nx_native_pwm_stop_instance", (state_address,))] elif kind == "adc": channels = item.options.channels definitions = [f"static const uint16_t s_nx_adc_model_{name}[{len(channels)}] = {{0}};"] - initialize = (f"nx_native_adc_configure_instance({state}, " - f"s_nx_adc_model_{name}, {len(channels)}u, 12u, " - f"{item.options.reference_mv}u)") - stop = [f"nx_native_adc_stop_instance({state})"] + initialize = DirectCall("nx_native_adc_configure_instance", + (state_address, Symbol(f"s_nx_adc_model_{name}"), len(channels), + 12, item.options.reference_mv)) + stop = [DirectCall("nx_native_adc_stop_instance", (state_address,))] else: raise ValueError(f"Native construction is not maintained for {kind}") return Construction( diff --git a/soc/native/configure/emission.py b/soc/native/configure/emission.py index 38b9081a0..1d28e65f1 100644 --- a/soc/native/configure/emission.py +++ b/soc/native/configure/emission.py @@ -33,10 +33,10 @@ def instance_declarations(item, result): source = [] kind = item.kind if kind in {"spi", "i2c"}: - for device in result["devices"]: - if device["controller"] != item["id"]: + for device in result.devices: + if device.controller != item.id: continue - child_name = device["id"].replace("-", "_") + child_name = device.id.replace("-", "_") if item.interface_kind == "spi_dma_transaction": name = item.id.replace("-", "_") source += [f"static nx_native_spi_dma_endpoint_state_t s_nx_endpoint_{child_name} = {{ .controller = &s_nx_port_{name} }};"] @@ -46,9 +46,9 @@ def instance_declarations(item, result): initializer = "" if kind == "i2c": name = item.id.replace("-", "_") - initializer = f" = {{ .port = &s_nx_port_{name}, .address = {device['address']}u }}" - if "model_bytes" in device: - source.append(f'static uint8_t s_nx_model_{child_name}[{device["model_bytes"]}];') + initializer = f" = {{ .port = &s_nx_port_{name}, .address = {device.options.address}u }}" + if device.options.model_bytes is not None: + source.append(f'static uint8_t s_nx_model_{child_name}[{device.options.model_bytes}];') source += [f'static nx_native_{kind}_endpoint_state_t s_nx_endpoint_{child_name}{initializer};'] source += enhanced_endpoints.face( device, f"s_nx_endpoint_{child_name}", diff --git a/soc/native/drivers/rtc_lptim.c b/soc/native/drivers/rtc_lptim.c index 48498fa51..9c6f346bd 100644 --- a/soc/native/drivers/rtc_lptim.c +++ b/soc/native/drivers/rtc_lptim.c @@ -118,6 +118,25 @@ nx_result_t nx_native_lptim_start(nx_native_lptim_state_t* state) { state->opened = true; return NX_SUCCESS; } +/** \brief Keep owner lifecycle selection in the tested provider. */ +nx_result_t nx_native_rtc_prepare(nx_native_rtc_state_t* state, + const nx_clock_t* clock) { + if (!rtc_owner(state)) { + return NX_ERROR_INVALID; + } + return state->constructed ? nx_native_rtc_start(state) + : nx_native_rtc_construct(state, clock); +} +/** \brief Never reset the fixed source binding or overwrite retained loans. */ +nx_result_t nx_native_lptim_prepare(nx_native_lptim_state_t* state, + const nx_clock_t* clock, uint32_t tick_hz) { + if (!lptim_owner(state)) { + return NX_ERROR_INVALID; + } + return state->constructed + ? nx_native_lptim_start(state) + : nx_native_lptim_construct(state, clock, tick_hz); +} /** \brief Copy a coherent setting without an implicit clock or date policy. */ static nx_result_t rtc_read(const void* context, nx_rtc_snapshot_t* snapshot) { const nx_native_rtc_state_t* state = context; diff --git a/soc/native/drivers/rtc_lptim_provider.h b/soc/native/drivers/rtc_lptim_provider.h index b66cb2e1a..6e8e35255 100644 --- a/soc/native/drivers/rtc_lptim_provider.h +++ b/soc/native/drivers/rtc_lptim_provider.h @@ -85,6 +85,24 @@ nx_result_t nx_native_rtc_start(nx_native_rtc_state_t* state); * advance, global identity, pool or implicit reconstruction is performed. */ nx_result_t nx_native_lptim_start(nx_native_lptim_state_t* state); +/** \brief Select fresh construction or joined reopen on the cold path. + * \param[in,out] state: Stable owner; validate its span before reading state. + * \param[in] clock: Copied only for fresh construction, ignored on reopen. + * \return The constructor/start result; rejection preserves owner bytes. + * \note Reopen preserves the original clock and calendar, never reconstructs + * the owner or resets its epoch, and honors retained BUSY responsibility. + */ +nx_result_t nx_native_rtc_prepare(nx_native_rtc_state_t* state, + const nx_clock_t* clock); +/** \brief Select fresh construction or joined reopen without rebinding units. + * \param[in,out] state: Stable owner validated before reading its lifecycle. + * \param[in] clock: Copied only on first construction, ignored on reopen. + * \param[in] tick_hz: Positive initial rate, ignored on same-owner reopen. + * \return Constructor/start result, with no writes on rejection. + * \note The original rate, clock, faults and nonwrapping epoch survive stop. + */ +nx_result_t nx_native_lptim_prepare(nx_native_lptim_state_t* state, + const nx_clock_t* clock, uint32_t tick_hz); #ifdef __cplusplus } #endif diff --git a/soc/stm32/stm32f103/configure/bindings.py b/soc/stm32/stm32f103/configure/bindings.py index 537febfe6..234c31d1a 100644 --- a/soc/stm32/stm32f103/configure/bindings.py +++ b/soc/stm32/stm32f103/configure/bindings.py @@ -1,23 +1,24 @@ """Cold static F103 contexts; typed factory never constructs on the I/O path.""" from .transport import PROFILE -from tools.configure.ir import controller_ir +from tools.configure.calls import Address, DirectCall +from tools.configure.ir import ControllerIR from tools.configure.providers.common import Construction, fail, kernel_policy_constant def constructor(item, result, board_bindings, routes, devices): """Construct exact static states and child endpoints outside the I/O path.""" - from .contract import LIMITS, validate_selection - item = controller_ir(item, "stm32f103") + from .contract import LIMITS + if not isinstance(item, ControllerIR): + fail("stm32f103 constructor requires a typed controller decision") if item.provider != "stm32f103": fail("F103 constructor cannot reinterpret a foreign provider decision") - validate_selection(item, item) if item.kind == "i2c" or (item.kind == "uart" and item.mode != "irq-bytes"): from tools.configure.providers.legacy_transport import constructor as transport_constructor return transport_constructor(item, result, devices, PROFILE) name = item.id.replace("-", "_") state = "s_nx_port_" + name definitions = [] - initialize = f"prepare_{name}()" + initialize = DirectCall(f"prepare_{name}") if item.kind == "gpio": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized) {{ return NX_ERROR_BUSY; }}", @@ -26,13 +27,13 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.output = {'true' if item.mode == 'output' else 'false'};", f" return nx_stm32f103_gpio_initialize(&{state}, {item.options.initial}u);", "}"] - stop = f"nx_stm32f103_gpio_stop(&{state}, {item.options.initial}u)" + stop = DirectCall("nx_stm32f103_gpio_stop", (Address(state), item.options.initial)) elif item.kind == "uart": options = item.options capacity = options.rx_capacity if type(capacity) is not int or not 1 <= capacity <= 4096: raise ValueError("F103 USART requires an explicit bounded byte ring") - irq = result["irq"] + irq = result.irq definitions += [f"static uint8_t s_nx_rx_{name}[{capacity}];", f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active != NULL || {state}.wake != NULL || {state}.wake_publishers != 0u) {{ return NX_ERROR_BUSY; }}", @@ -42,21 +43,21 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.baud = {options.baud}u;", f" {state}.irq = {item.controller}_IRQn;", f" {state}.profile = NX_UART_RX_BYTES;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", " nx_result_t status = nx_stm32f103_usart2_pins_prepare();", " if (status != NX_SUCCESS) { return status; }", f" return nx_stm32f103_uart_initialize(&{state}, 8000000u, 1000000u);", "}", f"void {item.controller}_IRQHandler(void);", r"/** \brief One selected controller, at most one TX/RX byte. */", f"void {item.controller}_IRQHandler(void) {{ nx_stm32f103_uart_irq(&{state}); }}"] - stop = f"nx_uart_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "spi": - children = [child for child in devices if child["controller"] == item.id] + children = [child for child in devices if child.controller == item.id] if len(children) != 1: fail("F103 SPI1 requires one explicit PA4 CS endpoint") child = children[0] child_name = child.id.replace("-", "_") - binding = board_bindings[child["cs_binding"]] + binding = board_bindings[child.options.cs_binding] cs_route = routes[binding["route"]] if (binding.get("mask") != 16 or binding.get("initial") != 16 or tuple((pin["pin"], pin["function"], pin["af"]) @@ -87,7 +88,7 @@ def constructor(item, result, board_bindings, routes, devices): f" nx_result_t status = nx_spi_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }", f" return nx_stm32f103_gpio_stop(&{cs}, 16u);", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "watchdog": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.state.enabled || {state}.preparation_retained) {{ return NX_ERROR_BUSY; }}", @@ -97,10 +98,10 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.debug = DBGMCU;", f" {state}.poll_limit = 1000000u;", " return NX_SUCCESS;", "}"] - stop = f"nx_stm32f103_watchdog_stop(&{state})" + stop = DirectCall("nx_stm32f103_watchdog_stop", (Address(state),)) elif item.kind == "exti": options = item.options - irq = result["irq"] + irq = result.irq line = int(item.controller[-1]) definitions += [f"static nx_exti_event_t s_nx_events_{name}[{options.event_capacity}];", f"static nx_result_t prepare_{name}(void) {{", @@ -113,29 +114,29 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.line = {line}u;", f" {state}.priority = {options.irq.priority}u;", f" {state}.edge = NX_EXTI_{options.edge.upper()};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_stm32f103_exti_initialize(&{state});", "}", f"void EXTI{line}_IRQHandler(void);", r"/** \brief One selected line, one bounded coalesced edge fact. */", f"void EXTI{line}_IRQHandler(void) {{ nx_stm32f103_exti_irq(&{state}); }}"] - stop = f"nx_exti_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_exti_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind in {"adc", "pwm", "flash"}: if item.kind == "flash": supply = board_bindings[item.binding].get("supply_mv") if type(supply) is not int or not 2700 <= supply <= 3600: fail("F103 Flash requires explicit Board supply_mv2700..3600") - initialize = f"nx_stm32f103_flash_prepare(&{state}, {supply}u)" + initialize = DirectCall("nx_stm32f103_flash_prepare", (Address(state), supply)) elif item.kind == "adc": - initialize = (f"nx_stm32f103_adc_prepare(&{state}, " - f"{item.options.reference_mv}u)") + initialize = DirectCall("nx_stm32f103_adc_prepare", + (Address(state), item.options.reference_mv)) else: initial = board_bindings[item.binding].get("initial") if type(initial) is not int or initial not in {0, 64}: fail("F103 PWM requires explicit PA6 inactive level") - initialize = (f"nx_stm32f103_pwm_prepare(&{state}, " - f"{item.options.tick_hz}u, {item.options.period_ticks}u, " - f"{item.options.duty_ticks}u, {initial}u)") - stop = f"nx_legacy_{item.kind}_close(&{state})" + initialize = DirectCall("nx_stm32f103_pwm_prepare", + (Address(state), item.options.tick_hz, item.options.period_ticks, + item.options.duty_ticks, initial)) + stop = DirectCall(f"nx_legacy_{item.kind}_close", (Address(state),)) else: raise ValueError("Unmaintained F103 constructor") return Construction( diff --git a/soc/stm32/stm32f103/configure/emission.py b/soc/stm32/stm32f103/configure/emission.py index 20b3906bc..b8eb0684e 100644 --- a/soc/stm32/stm32f103/configure/emission.py +++ b/soc/stm32/stm32f103/configure/emission.py @@ -64,13 +64,13 @@ def _apb1(result): def _pins(result): pins = {int(pin["pin"][2:]) - for item in result.controllers for pin in item["pins"] + for item in result.controllers for pin in item.pins if pin["pin"].startswith("PA")} - for device in result["devices"]: - if device.get("cs_binding") is not None: - binding = result["board_bindings"][device["cs_binding"]] + for device in result.devices: + if device.kind == "spi" and device.options.cs_binding is not None: + binding = result.board_bindings[device.options.cs_binding] pins.update(int(pin["pin"][2:]) - for pin in result["routes"][binding["route"]]["pins"]) + for pin in result.routes[binding["route"]]["pins"]) return tuple(sorted(pins)) diff --git a/soc/stm32/stm32f103/drivers/devices.c b/soc/stm32/stm32f103/drivers/devices.c index 927aa9846..1c05569f5 100644 --- a/soc/stm32/stm32f103/drivers/devices.c +++ b/soc/stm32/stm32f103/drivers/devices.c @@ -54,6 +54,17 @@ static bool task_context(void) { return nx_arch_is_privileged() && !nx_arch_in_isr() && !nx_arch_irq_is_masked(); } +static const nx_legacy_adc_config_t s_adc_config = { + .registers = (nx_legacy_adc_registers_t*)ADC1, + .gate = &RCC->APB2ENR, + .divider = &RCC->CFGR, + .pin_mode = &GPIOA->CRL, + .pin_lock = &GPIOA->LCKR, + .clock_started = &g_nx_stm32f103_system.started, + .gate_mask = RCC_APB2ENR_ADC1EN, + .divider_mask = RCC_CFGR_ADCPRE, + .poll_limit = 1000000U, + .startup_delay_us = 4U}; nx_result_t nx_stm32f103_adc_prepare(nx_stm32f103_adc_state_t* port, uint32_t reference_mv) { if (!task_context()) @@ -62,19 +73,22 @@ nx_result_t nx_stm32f103_adc_prepare(nx_stm32f103_adc_state_t* port, return NX_ERROR_INVALID; if (port->retained || port->initialized || port->active) return NX_ERROR_BUSY; - port->clock_started = &g_nx_stm32f103_system.started; - port->pin_mode = &GPIOA->CRL; - port->pin_lock = &GPIOA->LCKR; - port->poll_limit = 1000000U; - port->registers = (nx_legacy_adc_registers_t*)ADC1; - port->gate = &RCC->APB2ENR; - port->gate_mask = RCC_APB2ENR_ADC1EN; - port->divider = &RCC->CFGR; - port->divider_mask = RCC_CFGR_ADCPRE; + if (reference_mv < 2700U || reference_mv > 3600U) + return NX_ERROR_INVALID; + port->config = &s_adc_config; port->reference_mv = reference_mv; - port->startup_delay_us = 4U; return nx_legacy_adc_initialize(port); } +static const nx_legacy_pwm_config_t s_pwm_config = { + .registers = (nx_legacy_pwm_registers_t*)TIM3, + .gate = &RCC->APB1ENR, + .pin_mode = &GPIOA->CRL, + .pin_lock = &GPIOA->LCKR, + .pin_latch = &GPIOA->ODR, + .pin_set = &GPIOA->BSRR, + .clock_started = &g_nx_stm32f103_system.started, + .gate_mask = RCC_APB1ENR_TIM3EN, + .poll_limit = 1000000U}; nx_result_t nx_stm32f103_pwm_prepare(nx_stm32f103_pwm_state_t* port, uint32_t tick_hz, uint32_t period_ticks, uint32_t duty_ticks, uint32_t inactive) { @@ -84,15 +98,7 @@ nx_result_t nx_stm32f103_pwm_prepare(nx_stm32f103_pwm_state_t* port, return NX_ERROR_INVALID; if (port->retained || port->initialized || port->running) return NX_ERROR_BUSY; - port->clock_started = &g_nx_stm32f103_system.started; - port->pin_mode = &GPIOA->CRL; - port->pin_lock = &GPIOA->LCKR; - port->poll_limit = 1000000U; - port->registers = (nx_legacy_pwm_registers_t*)TIM3; - port->gate = &RCC->APB1ENR; - port->gate_mask = RCC_APB1ENR_TIM3EN; - port->pin_latch = &GPIOA->ODR; - port->pin_set = &GPIOA->BSRR; + port->config = &s_pwm_config; port->inactive = inactive; port->tick_hz = tick_hz; port->period_ticks = period_ticks; diff --git a/soc/stm32/stm32f407/configure/bindings.py b/soc/stm32/stm32f407/configure/bindings.py index 3c4544f82..1c1088c14 100644 --- a/soc/stm32/stm32f407/configure/bindings.py +++ b/soc/stm32/stm32f407/configure/bindings.py @@ -1,5 +1,6 @@ """Reviewed STM32 fixed construction, never a runtime resource allocator.""" from tools.configure.ir import controller_ir +from tools.configure.calls import DirectCall, Address, Symbol from tools.configure.memory import dma_regions, flash_geometry from tools.configure.gpio_phase import rollback_pin, withdraw_pin from tools.configure import enhanced_endpoints, uart_formats @@ -55,6 +56,108 @@ def _endpoint_face(kind, name, state, implementation=None): f"const nx_{kind}_endpoint_t* const nx_device_{name} = &s_nx_device_face_{name};"] +def _spi_constructor(item, result, board_bindings, routes, devices, *, paired=False): + """Describe exact static connections; C owns cold hardware transitions.""" + selected = item.integration + if selected is None or selected.clock_hz is None: + raise ValueError("SPI requires its resolved controller integration") + if item.controller not in {"SPI1", "SPI2"} or len(item.pins) != 3: + raise ValueError("SPI requires its reviewed three-pad controller route") + name = _symbol(item.id) + dma = item.mode == "dma" + if item.mode not in {"short-poll", "dma"} or (item.controller == "SPI2" and not paired): + raise ValueError("SPI requires its maintained polling or paired DMA mode") + implementation = "spi_dma" if dma else "spi" + children = [child for child in devices if child.controller == item.id] + if not children: + raise ValueError("STM32 SPI requires a reviewed CS endpoint") + config = f"s_nx_spi_config_{name}" + state = f"s_nx_port_{name}" + saved = f"s_nx_spi_pin_state_{name}" + pin_records = list(item.pins) + endpoint_records = [] + definitions = [] + cold_type = "nx_stm32_spi_dma_cold_t" if paired else "nx_stm32_spi_cold_receipt_t" + definitions.append(f"static {cold_type} s_nx_spi_cold_{name};") + for index, child in enumerate(children): + child_name = _symbol(child.id) + binding = board_bindings[child.options.cs_binding] + cs_pins = routes[binding["route"]]["pins"] + if len(cs_pins) != 1: + raise ValueError("SPI CS requires exactly one reviewed pin") + pin = cs_pins[0] + if binding.get("initial") != 1 << int(pin["pin"][2:]): + raise ValueError("SPI active-low CS must declare initial high") + pin_records.append(pin) + endpoint = (f"s_nx_endpoint_{name}" if index == 0 else + f"s_nx_endpoint_{child_name}") + if index: + definitions.append( + f"static nx_stm32_{implementation}_endpoint_state_t {endpoint};") + chip_select = f"s_nx_cs_{child_name}" + definitions.append(f"static nx_stm32_gpio_state_t {chip_select};") + definitions += enhanced_endpoints.face( + child, endpoint, f"nx_stm32_{implementation}_endpoint_ops", + "stm32f407") + base = endpoint + (".endpoint" if dma else "") + endpoint_records.append( + f" {{ &{base}, &{chip_select}, {child.options.max_hz}u, " + f"{child.options.mode}u }}") + definitions += [f"static uint16_t {saved}[{len(pin_records)}];", + f"static const nx_stm32_spi_pin_config_t s_nx_spi_pins_{name}[] = {{"] + snapshots = {} + for index, pin in enumerate(pin_records): + wire = pin["pin"] + slot = f"{saved}[{index}u]" + snapshots[wire] = slot + definitions.append( + f" {{ GPIO{wire[1]}, &{slot}, {ord(wire[1]) - ord('A')}u, " + f"{int(wire[2:])}u, {pin['af']}u }},") + definitions += ["};", + f"static const nx_stm32_spi_endpoint_config_t s_nx_spi_endpoints_{name}[] = {{", + ",\n".join(endpoint_records), "};"] + if dma: + if len(selected.dma) != 2 or selected.irq is None: + raise ValueError("SPI DMA requires two selected lanes and IRQ policy") + secondary_controllers.dma_paths(item) + tx, rx = selected.dma + tx_vector = f"{tx.engine}_Stream{tx.lane}" + rx_vector = f"{rx.engine}_Stream{rx.lane}" + regions, count = dma_regions(name, result, include_flash=True) + definitions += regions + definitions += [f"static const nx_stm32_spi_dma_config_t {config} = {{", + " .spi = {"] + else: + definitions += [f"static const nx_stm32_spi_config_t {config} = {{"] + definitions += [f" .registers = {item.controller}, .clock_hz = {selected.clock_hz}u,", + f" .pins = s_nx_spi_pins_{name}, .pin_count = {len(pin_records)}u,", + f" .endpoints = s_nx_spi_endpoints_{name}, .endpoint_count = {len(children)}u"] + if dma: + definitions += [" },", + f" .dma = {tx.engine}, .tx = {tx_vector}, .rx = {rx_vector},", + f" .regions = s_nx_dma_regions_{name}, .region_count = {count}u,", + f" .priority = {selected.irq.priority}u,", + f" .kernel_bound = {'true' if result.kernel is not None else 'false'}"] + suffix = "_pair" if paired else "" + arguments = (Address(state), Address(config), + Address(f"s_nx_spi_cold_{name}")) + initialize = DirectCall(f"nx_stm32_{implementation}{suffix}_prepare", arguments) + stop = ([DirectCall("nx_spi_port_stop", (Symbol(f"nx_binding_{name}"),))] + if dma else []) + stop.append(DirectCall(f"nx_stm32_{implementation}{suffix}_close", arguments)) + definitions += ["};"] + if dma: + for vector, receive in ((tx_vector, "false"), (rx_vector, "true")): + definitions += [f"void {vector}_IRQHandler(void);", + r"/** \brief Exact selected SPI DMA terminal observation. */", + f"void {vector}_IRQHandler(void) {{ nx_stm32_spi_dma_irq(&{state}, {receive}); }}"] + return Construction( + state_type=f"nx_stm32_{implementation}_state_t", + definitions=definitions, initialize=initialize, + stop=stop, retain_on_failed_init=True, + pin_snapshots=snapshots) + + def _controller_constructor(item, result, board_bindings, routes, devices): """Emit static contexts after resolver resource and mode validation. @@ -63,145 +166,26 @@ def _controller_constructor(item, result, board_bindings, routes, devices): controller, channel or incomplete electrical/resource description fails. """ item = controller_ir(item, 'stm32f407') - name = _symbol(item["id"]) - kind = item["kind"] + name = _symbol(item.id) + kind = item.kind declarations = [] body = [f"static nx_result_t prepare_{name}(void) {{", " nx_result_t status = NX_SUCCESS;"] stop = [] - captured = [pin["pin"] for pin in item["pins"]] + captured = [pin["pin"] for pin in item.pins] rollback_extra = [] clock_bits = {"APB1ENR": "0u", "APB2ENR": "0u"} - if kind == "spi": - dma = item["mode"] == "dma" - selected = item.integration - clock_hz = (selected.clock_hz if selected is not None else - result["clock"]["apb2_hz"]) - bus = f"s_nx_port_{name}" + (".spi" if dma else "") - if item["controller"] != "SPI1": - raise ValueError("Only reviewed SPI1 is maintained") - children = [child for child in devices - if child["controller"] == item["id"]] - if not children: - raise ValueError("STM32 SPI requires a reviewed CS endpoint") - clock_bits["APB2ENR"] = "RCC_APB2ENR_SPI1EN" - body += [" RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;", - " nx_arch_dsb();", - f" {bus}.registers = SPI1;", - f" {bus}.rcc = RCC;", - f" {bus}.clock_hz = {clock_hz}u;", - " saved_spi_cr1 = SPI1->CR1;", - " saved_spi_cr2 = SPI1->CR2;", - " captured_spi = true;", - " if ((saved_spi_cr1 & SPI_CR1_SPE) != 0u) {", - " status = NX_ERROR_BUSY; goto rollback;", " }", - " SPI1->CR1 = 0u;", " SPI1->CR2 = 0u;"] - rollback_extra += [" if (captured_spi) { SPI1->CR1 = saved_spi_cr1; SPI1->CR2 = saved_spi_cr2; }"] - for pin in item["pins"]: - _pin(body, pin, 2) - for index, child in enumerate(children): - child_name = _symbol(child["id"]) - binding = board_bindings[child["cs_binding"]] - cs_route = routes[binding["route"]] - if len(cs_route["pins"]) != 1: - raise ValueError("SPI CS requires exactly one reviewed pin") - cs_pin = cs_route["pins"][0]["pin"] - captured.append(cs_pin) - bit = 1 << int(cs_pin[2:]) - if binding.get("initial") != bit: - raise ValueError("SPI active-low CS must declare initial high") - endpoint = f"s_nx_endpoint_{name}" if index == 0 else f"s_nx_endpoint_{child_name}" - implementation = "spi_dma" if dma else "spi" - if index != 0: - declarations.append(f"static nx_stm32_{implementation}_endpoint_state_t {endpoint};") - declarations += [f"static nx_stm32_gpio_state_t s_nx_cs_{child_name};", - *enhanced_endpoints.face( - child, endpoint, - f"nx_stm32_{implementation}_endpoint_ops", - "stm32f407")] - _check(body, f"nx_stm32_gpio_clock_enable({ord(cs_pin[1]) - ord('A')}u)") - _capture(body, cs_pin) - rollback_extra.append(f" s_nx_cs_{child_name}.initialized = false;") - body += [f" s_nx_cs_{child_name}.registers = GPIO{cs_pin[1]};", - f" s_nx_cs_{child_name}.mask = {bit}u;", - f" s_nx_cs_{child_name}.output = true;"] - _check(body, f"nx_stm32_gpio_initialize(&s_nx_cs_{child_name}, {bit}u)") - mode = child.options.mode - maximum = child.options.max_hz - if not isinstance(mode, int) or isinstance(mode, bool) or not 0 <= mode <= 3: - raise ValueError("SPI mode must be 0 through 3") - minimum, upper = LIMITS["spi_hz"] - if type(maximum) is not int or not minimum <= maximum <= upper: - raise ValueError(f"SPI1 maintained frequency is {minimum} through {upper} Hz") - base = endpoint + (".endpoint" if dma else "") - body += [f" {base}.port = &{bus};", - f" {base}.cs = &s_nx_cs_{child_name};", - f" {base}.cs_mask = {bit}u;", - f" {base}.mode = {mode}u;", - f" {base}.frequency_hz = {maximum}u;"] - if dma: - body.append(f" {endpoint}.dma = &s_nx_port_{name};") - if dma: - tx, rx = secondary_controllers.dma_paths(item) - if selected is None: - # Direct legacy constructor fixtures have no configuration IR. - # Production always consumes its selected canonical DMA lanes. - dma_bank = f"DMA{tx[0]}" - tx_vector = hardware.dma_vector("stm32f407", tx) - rx_vector = hardware.dma_vector("stm32f407", rx) - irq = item.options.irq - else: - if len(selected.dma) != 2 or selected.irq is None: - raise ValueError("SPI DMA requires two selected lanes and IRQ policy") - tx_lane, rx_lane = selected.dma - dma_bank = tx_lane.engine - tx_vector = f"{tx_lane.engine}_Stream{tx_lane.lane}" - rx_vector = f"{rx_lane.engine}_Stream{rx_lane.lane}" - irq = selected.irq - region_lines, region_count = dma_regions(name, result, include_flash=True) - declarations += region_lines - body += [f" s_nx_port_{name}.dma = {dma_bank};", - f" s_nx_port_{name}.tx = {tx_vector};", - f" s_nx_port_{name}.rx = {rx_vector};", - f" s_nx_port_{name}.regions = s_nx_dma_regions_{name};", - f" s_nx_port_{name}.region_count = {region_count}u;"] - _check(body, f"nx_stm32_spi_dma_initialize(&s_nx_port_{name})") - body += [f" NVIC_SetPriority({tx_vector}_IRQn, {irq.priority}u);", - f" NVIC_SetPriority({rx_vector}_IRQn, {irq.priority}u);"] - for vector, receive in ((tx_vector, "false"), (rx_vector, "true")): - declarations += [f"void {vector}_IRQHandler(void);", - "/** \\brief Exact selected SPI DMA terminal observation. */", - f"void {vector}_IRQHandler(void) {{ nx_stm32_spi_dma_irq(&s_nx_port_{name}, {receive}); }}"] - stop_name = f"stop_{name}" - stop_body = [f"static nx_result_t {stop_name}(void) {{"] - if dma: - stop_body += [f" nx_result_t stopped = nx_spi_port_stop(nx_binding_{name});", - " if (stopped != NX_SUCCESS) { return stopped; }"] - else: - stop_body.append(f" if ({bus}.active) {{ return NX_ERROR_BUSY; }}") - stop_body.append(" SPI1->CR1 = 0u;") - for child in children: - child_name = _symbol(child["id"]) - binding = board_bindings[child["cs_binding"]] - stop_body += [f" nx_result_t result_{child_name} = nx_stm32_gpio_stop(", - f" &s_nx_cs_{child_name}, {binding['initial']}u);", - f" if (result_{child_name} != NX_SUCCESS) {{ return result_{child_name}; }}"] - stop_body += _release(item["pins"]) - stop_body += [" RCC->APB2ENR &= ~(uint32_t)RCC_APB2ENR_SPI1EN;", - " nx_arch_dsb();", " return NX_SUCCESS;", "}"] - declarations += stop_body - stop = [f"{stop_name}()"] - elif kind == "i2c" and mode_traits(kind, item.mode).irq: - if item["controller"] != "I2C1": + if kind == "i2c" and mode_traits(kind, item.mode).irq: + if item.controller != "I2C1": raise ValueError("Only reviewed I2C1 asynchronous mode is maintained") children = [child for child in devices - if child["controller"] == item.id] + if child.controller == item.id] clock_bits["APB1ENR"] = "RCC_APB1ENR_I2C1EN" body += [" RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;", " nx_arch_dsb();"] - for pin in item["pins"]: + for pin in item.pins: _pin(body, pin, 2, open_drain=True) - port = item["pins"][0]["pin"][1] - mask = sum(1 << int(pin["pin"][2:]) for pin in item["pins"]) + port = item.pins[0]["pin"][1] + mask = sum(1 << int(pin["pin"][2:]) for pin in item.pins) body += [f" s_nx_port_{name}.registers = (volatile uint32_t*)I2C1;", f" s_nx_port_{name}.line_inputs = &GPIO{port}->IDR;", f" s_nx_port_{name}.line_mask = {mask}u;", @@ -216,16 +200,16 @@ def _controller_constructor(item, result, board_bindings, routes, devices): else: body.append(f" s_nx_port_{name}.dma = NULL;") for index, child in enumerate(children): - child_name = _symbol(child["id"]) + child_name = _symbol(child.id) endpoint = f"s_nx_endpoint_{name}" if not index else f"s_nx_endpoint_{child_name}" if index: - declarations.append(f"static nx_stm32_i2c_async_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child['address']}u }};") + declarations.append(f"static nx_stm32_i2c_async_endpoint_state_t {endpoint} = {{ .port = &s_nx_port_{name}, .address = {child.options.address}u }};") declarations += _endpoint_face("i2c", child_name, endpoint, "i2c_async") _check(body, f"nx_stm32_i2c_async_initialize(&s_nx_port_{name}, {item.options.irq.priority}u)") declarations += [f"static nx_result_t stop_{name}(void) {{", f" nx_result_t status = nx_i2c_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }"] - declarations += _release(item["pins"]) + declarations += _release(item.pins) declarations += [" RCC->APB1ENR &= ~(uint32_t)RCC_APB1ENR_I2C1EN;", " nx_arch_dsb();", " return NX_SUCCESS;", "}"] port_binding = hardware.maintained("stm32f407", item.controller) @@ -239,36 +223,36 @@ def _controller_constructor(item, result, board_bindings, routes, devices): declarations += [f"void {vector}_IRQHandler(void);", r"/** \brief Selected I2C DMA boundary publisher. */", f"void {vector}_IRQHandler(void) {{ nx_i2c_v1_dma_irq(&s_nx_port_{name}, {receive}); }}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "i2c": - if item["controller"] != "I2C1" or item.get("max_hz", 100000) != 100000: + if item.controller != "I2C1" or item.options.max_hz != 100000: raise ValueError("Only I2C1 100 kHz standard mode is maintained") children = [child for child in devices - if child["controller"] == item["id"]] + if child.controller == item.id] if not children: raise ValueError("I2C requires at least one reviewed 7-bit address endpoint") clock_bits["APB1ENR"] = "RCC_APB1ENR_I2C1EN" body += [" RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;", " nx_arch_dsb();", " if ((I2C1->CR1 & I2C_CR1_PE) != 0u) {", " status = NX_ERROR_BUSY; goto rollback;", " }"] - for pin in item["pins"]: + for pin in item.pins: # External pull-ups are a reviewed Board requirement; enabling an # internal pull-up does not stand in for that electrical contract. _pin(body, pin, 2, open_drain=True) # The maintained route has two lines on one GPIO port. Retain the # resolved facts for physical line-level observation during recover. - line_ports = {pin["pin"][1] for pin in item["pins"]} - if len(item["pins"]) != 2 or len(line_ports) != 1: + line_ports = {pin["pin"][1] for pin in item.pins} + if len(item.pins) != 2 or len(line_ports) != 1: raise ValueError("I2C requires two reviewed lines on one GPIO port") line_port = next(iter(line_ports)) - line_mask = sum(1 << int(pin["pin"][2:]) for pin in item["pins"]) + line_mask = sum(1 << int(pin["pin"][2:]) for pin in item.pins) body += [f" s_nx_port_{name}.registers = I2C1;", f" s_nx_port_{name}.line_gpio = GPIO{line_port};", f" s_nx_port_{name}.line_mask = {line_mask}u;", f" s_nx_port_{name}.peripheral_mhz = 42u;", f" s_nx_port_{name}.rate_hz = 100000u;"] for index, child in enumerate(children): - child_name = _symbol(child["id"]) + child_name = _symbol(child.id) address = child.options.address if not 8 <= address <= 119: raise ValueError("Reserved I2C addresses are not maintained") @@ -280,16 +264,15 @@ def _controller_constructor(item, result, board_bindings, routes, devices): declarations += [f"static nx_result_t stop_{name}(void) {{", f" if (s_nx_port_{name}.active) {{ return NX_ERROR_BUSY; }}", " I2C1->CR1 = 0u;", - f" s_nx_port_{name}.initialized = false;"] + _release(item["pins"]) + [ + f" s_nx_port_{name}.initialized = false;"] + _release(item.pins) + [ " RCC->APB1ENR &= ~(uint32_t)RCC_APB1ENR_I2C1EN;", " nx_arch_dsb();", " return NX_SUCCESS;", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "flash": - if item["controller"] != "FLASH": + if item.controller != "FLASH": raise ValueError("Only the full internal Flash provider is maintained") - exact_part(PARTS, result["part"]) - physical = getattr(result, "physical_part", None) - flash = physical.flash if physical is not None else result["flash"] + exact_part(PARTS, result.part) + flash = result.physical_part.flash sectors = f"s_nx_flash_sectors_{name}" geometry = f"s_nx_flash_geometry_{name}" declarations += flash_geometry( @@ -305,21 +288,21 @@ def _controller_constructor(item, result, board_bindings, routes, devices): declarations += [f"static nx_result_t stop_{name}(void) {{", f" return s_nx_port_{name}.active || (FLASH->SR & FLASH_SR_BSY) != 0u", " ? NX_ERROR_BUSY : NX_SUCCESS;", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "watchdog": - if item["controller"] != "IWDG": + if item.controller != "IWDG": raise ValueError("Only independent IWDG is maintained") body += [f" s_nx_port_{name}.registers = IWDG;", f" s_nx_port_{name}.flash = FLASH;", f" s_nx_port_{name}.debug = DBGMCU;", f" s_nx_port_{name}.rcc = RCC;", f" s_nx_port_{name}.poll_limit = 1000000u;"] declarations += [f"static nx_result_t stop_{name}(void) {{", f" return nx_stm32_watchdog_stop(&s_nx_port_{name});", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "exti": - if item["controller"] not in {"EXTI0", "EXTI5", "EXTI6"} or len(item["pins"]) != 1: + if item.controller not in {"EXTI0", "EXTI5", "EXTI6"} or len(item.pins) != 1: raise ValueError("EXTI line must use a reviewed package route") - line = int(item["controller"][4:]) - pin = item["pins"][0] + line = int(item.controller[4:]) + pin = item.pins[0] if int(pin["pin"][2:]) != line: raise ValueError("EXTI pin and mux line mismatch") irq = "EXTI0_IRQn" if line == 0 else "EXTI9_5_IRQn" @@ -361,11 +344,11 @@ def _controller_constructor(item, result, board_bindings, routes, devices): f" bool owned = s_nx_port_{name}.initialized;", f" if (!owned) {{ return NX_SUCCESS; }}", f" nx_result_t status = nx_exti_port_stop(&s_nx_face_{name});", - " if (status != NX_SUCCESS) { return status; }"] + _release(item["pins"]) + [ + " if (status != NX_SUCCESS) { return status; }"] + _release(item.pins) + [ " return NX_SUCCESS;", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "pwm": - if item["controller"] != "TIM3" or [pin["pin"] for pin in item["pins"]] != ["PA6"]: + if item.controller != "TIM3" or [pin["pin"] for pin in item.pins] != ["PA6"]: raise ValueError("Only reviewed TIM3 CH1 PA6 PWM route is maintained") period, duty, tick = (item.options.period_ticks, item.options.duty_ticks, item.options.tick_hz) @@ -376,7 +359,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): timer_hz = LIMITS["timer_hz"] if timer_hz % tick != 0 or not 1 <= timer_hz // tick <= 65536: raise ValueError("PWM tick_hz is not an exact reviewed TIM3 divider") - inactive = item["initial"] + inactive = item.initial if inactive not in (0, 64): raise ValueError("PWM idle must be explicit PA6 masked output level") clock_bits["APB1ENR"] = "RCC_APB1ENR_TIM3EN" @@ -408,15 +391,15 @@ def _controller_constructor(item, result, board_bindings, routes, devices): " if ((TIM3->CCER & 0x1111u) == 0u) {", " RCC->APB1ENR &= ~(uint32_t)RCC_APB1ENR_TIM3EN;", " }", " nx_arch_dsb();", " return NX_SUCCESS;", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] elif kind == "adc": traits = mode_traits(kind, item.mode) stream, continuous = traits.dma, traits.continuous stream_state = f"s_nx_port_{name}" + (".base" if continuous else "") adc = stream_state + (".adc" if stream else "") channels = list(item.options.channels) - reviewed = [int(pin["pin"][2:]) for pin in item["pins"]] - if item["controller"] != "ADC1" or channels != reviewed or tuple(channels) not in ADC_CHANNEL_SEQUENCES: + reviewed = [int(pin["pin"][2:]) for pin in item.pins] + if item.controller != "ADC1" or channels != reviewed or tuple(channels) not in ADC_CHANNEL_SEQUENCES: raise ValueError("ADC channel sequence must match the reviewed PA0/PA1 route") sampling = list(item.options.sample_times) reference = item.options.reference_mv @@ -434,7 +417,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): body += [" RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;", " nx_arch_dsb();", " if ((ADC1->CR2 & ADC_CR2_ADON) != 0u) {", " status = NX_ERROR_BUSY; goto rollback;", " }"] - for pin in item["pins"]: + for pin in item.pins: _pin(body, pin, 3) body += [ f" {adc}.registers = ADC1;", f" {adc}.common = ADC;", @@ -472,10 +455,10 @@ def _controller_constructor(item, result, board_bindings, routes, devices): " ADC1->CR2 = 0u;", f" {adc}.initialized = false;"] if stream: stop_body.append(" RCC->APB1ENR &= ~(uint32_t)RCC_APB1ENR_TIM3EN;") - declarations += stop_body + _release(item["pins"]) + [ + declarations += stop_body + _release(item.pins) + [ " RCC->APB2ENR &= ~(uint32_t)RCC_APB2ENR_ADC1EN;", " nx_arch_dsb();", " return NX_SUCCESS;", "}"] - stop = [f"stop_{name}()"] + stop = [DirectCall(f"stop_{name}")] else: raise ValueError(f"No maintained STM32 constructor for {kind}") body.append(" return status;") @@ -492,9 +475,6 @@ def _controller_constructor(item, result, board_bindings, routes, devices): for pin in pins: locals_ += [f" uint16_t saved_{pin} = 0u;", f" bool captured_{pin} = false;"] - if kind == "spi": - locals_ += [" uint32_t saved_spi_cr1 = 0u, saved_spi_cr2 = 0u;", - " bool captured_spi = false;"] body[2:2] = locals_ body += ["rollback:"] + rollback_extra for pin in reversed(pins): @@ -510,7 +490,7 @@ def _controller_constructor(item, result, board_bindings, routes, devices): implementation = mode_implementation(item.kind, item.mode) return Construction( state_type=f"nx_stm32_{implementation}_state_t", - definitions=declarations + body, initialize=f"prepare_{name}()", + definitions=declarations + body, initialize=DirectCall(f"prepare_{name}"), stop=stop, retain_on_failed_init=kind == "exti") @@ -519,18 +499,18 @@ def shared_irq_definitions(selections): selections = [controller_ir(item, "stm32f407") for item in selections] groups = {} for item in selections: - if item["kind"] != "exti": + if item.kind != "exti": continue - line = int(item["controller"][4:]) + line = int(item.controller[4:]) vector = "EXTI0" if line == 0 else "EXTI9_5" groups.setdefault(vector, []).append(item) lines = [] for vector, items in groups.items(): if len({item.options.irq.priority for item in items}) != 1: raise ValueError("Shared EXTI vector priorities must agree") - pointers = ", ".join("&s_nx_port_" + _symbol(item["id"]) for item in items) + pointers = ", ".join("&s_nx_port_" + _symbol(item.id) for item in items) array = "s_nx_irq_" + vector.lower() + "_ports" - mask = sum(1 << int(item["controller"][4:]) for item in items) + mask = sum(1 << int(item.controller[4:]) for item in items) lines.extend([f"static nx_stm32_exti_state_t* const {array}[] = {{ {pointers} }};", f"void {vector}_IRQHandler(void);", "/** \\brief Static bounded EXTI shared-vector dispatch. */", @@ -542,36 +522,37 @@ def shared_irq_definitions(selections): def _gpio_constructor(item): name = _symbol(item.id) - port = item["pins"][0]["pin"][1] + port = item.pins[0]["pin"][1] index = ord(port) - ord("A") mask = item.options.mask or sum(1 << int(pin["pin"][2:]) - for pin in item["pins"]) + for pin in item.pins) initial = item.options.initial definitions = [] - definitions += [f'/* Fixed {item["controller"]} authorization. */', + definitions += [f'/* Fixed {item.controller} authorization. */', f'static nx_result_t prepare_{name}(void) {{', f' s_nx_port_{name}.registers = GPIO{port};', f' s_nx_port_{name}.mask = {mask}u;', - f' s_nx_port_{name}.output = {"true" if item["mode"] == "output" else "false"};', + f' s_nx_port_{name}.output = {"true" if item.mode == "output" else "false"};', f' uint32_t saved_clock = RCC->AHB1ENR & {1 << index}u;', f' nx_result_t clock = nx_stm32_gpio_clock_enable({index}u);', f' if (clock != NX_SUCCESS) {{ RCC->AHB1ENR = (RCC->AHB1ENR & ~{1 << index}u) | saved_clock; return clock; }}'] - for pin in item["pins"]: + for pin in item.pins: number = pin["pin"][2:] definitions.append(f' uint16_t saved_{number} = nx_stm32_pin_capture(GPIO{port}, {number}u);') definitions += [f' nx_result_t status = nx_stm32_gpio_initialize(&s_nx_port_{name}, {initial}u);', ' if (status != NX_SUCCESS) {', ' uint32_t retained_gpio_clocks = 0u;'] - for pin in item["pins"]: + for pin in item.pins: number = pin["pin"][2:] definitions += rollback_pin("stm32f407", pin["pin"], f"saved_{number}") definitions += [f' RCC->AHB1ENR = (RCC->AHB1ENR & ~{1 << index}u) | saved_clock | retained_gpio_clocks;', ' nx_arch_dsb();', ' }', ' return status;', '}'] - init = f'prepare_{name}()' + init = DirectCall(f"prepare_{name}") return Construction( state_type="nx_stm32_gpio_state_t", definitions=definitions, initialize=init, - stop=[f"nx_stm32_gpio_stop(&s_nx_port_{name}, {initial}u)"], + stop=[DirectCall("nx_stm32_gpio_stop", + (Address(f"s_nx_port_{name}"), initial))], retain_on_failed_init=False) @@ -603,7 +584,7 @@ def _uart_constructor(item, result): else dma_state) rx_storage = 'NULL' if block_uart else f's_nx_rx_{name}' definitions += [f'static nx_result_t prepare_{name}(void) {{', - f' {uart_state}.registers = {item["controller"]};'] + f' {uart_state}.registers = {item.controller};'] if not words: definitions += [f' {uart_state}.rx_storage = {rx_storage};', f' {uart_state}.rx_capacity = {capacity}u;', @@ -618,7 +599,7 @@ def _uart_constructor(item, result): initialize = (f"nx_stm32_uart_initialize_format(&s_nx_port_{name}, " f"84000000u, {uart_formats.expression(options.frame)})") stop_function = "nx_uart_word_port_stop" if words else "nx_uart_port_stop" - definitions += [f' {uart_state}.irq = {item["controller"]}_IRQn;', + definitions += [f' {uart_state}.irq = {item.controller}_IRQn;', ' uint32_t saved_clock = RCC->AHB1ENR & RCC_AHB1ENR_GPIOAEN;', ' uint32_t saved_uart_clock = RCC->APB2ENR & RCC_APB2ENR_USART1EN;', ' nx_result_t clock = nx_stm32_gpio_clock_enable(0u);', @@ -649,8 +630,8 @@ def _uart_constructor(item, result): ' (void)nx_stm32_pin_configure(GPIOA, 10u, 0u, 0u, 0u, false);', ' RCC->APB2ENR &= ~(uint32_t)RCC_APB2ENR_USART1EN;', ' nx_arch_dsb();', ' return NX_SUCCESS;', '}'] - init = f'prepare_{name}()' - irq = item["controller"] + '_IRQHandler' + init = DirectCall(f"prepare_{name}") + irq = item.controller + '_IRQHandler' irq_implementation = ("uart_word" if words else "uart_format" if uart_formats.changed(item) else "uart_duplex" if receive_uart and dma_uart else @@ -666,8 +647,8 @@ def _uart_constructor(item, result): f' if (s_nx_port_{name}.initialized) {{ return uart; }}'] if traits.dma: from .contract import _FIXED_DMA - expected = _FIXED_DMA.get((item["controller"], item.mode)) - if tuple(item.get("dma", ())) != expected: + expected = _FIXED_DMA.get((item.controller, item.mode)) + if tuple(item.dma) != expected: raise ValueError("UART DMA differs from its reviewed fixed route") region_lines, region_count = dma_regions( name, result, include_flash=traits.tx_dma) @@ -708,13 +689,18 @@ def _uart_constructor(item, result): definitions[priority_line + 1:priority_line + 1] = priorities return Construction( state_type=f"nx_stm32_{implementation}_state_t", - definitions=definitions, initialize=init, stop=[f"stop_{name}()"], + definitions=definitions, initialize=init, stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=words or uart_formats.changed(item)) def constructor(item, result, board_bindings, routes, devices): """Emit one reviewed STM32 instance, including its rollback and stop.""" item = controller_ir(item, "stm32f407") + if item.kind == "spi": + construction = _spi_constructor( + item, result, board_bindings, routes, devices, + paired=secondary_controllers.applies(item, result, "stm32")) + return enhanced_endpoints.dma_construction(item, construction, "stm32") if secondary_controllers.applies(item, result, "stm32"): construction = secondary_controllers.constructor(item, result, "stm32") return enhanced_endpoints.dma_construction(item, construction, "stm32") diff --git a/soc/stm32/stm32f407/configure/emission.py b/soc/stm32/stm32f407/configure/emission.py index 3bc00c282..175cb021d 100644 --- a/soc/stm32/stm32f407/configure/emission.py +++ b/soc/stm32/stm32f407/configure/emission.py @@ -29,6 +29,8 @@ def includes(result): selections = result.controllers source = [] source.append('#include "stm32f407_provider.h"') + if any(item.kind == "spi" for item in selections): + source.append('#include "stm32f407_spi_cold.h"') if any(item.mode == "irq-words" for item in selections): source.append('#include "stm32f407_uart_word.h"') if any(item.interface_kind == "spi_dma_transaction" for item in selections): @@ -70,9 +72,9 @@ def instance_declarations(item, result): if kind in {"spi", "i2c"}: initializer = "" if kind == "i2c": - child = next((device for device in result.devices if device["controller"] == item.id), None) + child = next((device for device in result.devices if device.controller == item.id), None) if child is not None: - initializer = f" = {{ .port = &s_nx_port_{name}, .address = {child['address']}u }}" + initializer = f" = {{ .port = &s_nx_port_{name}, .address = {child.options.address}u }}" source.append('static nx_stm32' f"_{implementation(item)}_endpoint_state_t " f"s_nx_endpoint_{name}{initializer};") @@ -86,7 +88,7 @@ def has_dma(result): def _dma_clock_mask(result): controllers = {dma.split(":", 1)[0] - for item in result.controllers for dma in item.get("dma", ())} + for item in result.controllers for dma in item.dma} return " | ".join(f"RCC_AHB1ENR_{name}EN" for name in sorted(controllers)) @@ -95,7 +97,7 @@ def state_declarations(result): source = [] if has_dma(result): source.append("static uint32_t s_nx_platform_dma_clocks_added;") - if any(item["kind"] == "exti" for item in selections): + if any(item.kind == "exti" for item in selections): source.append('static uint32_t s_nx_platform_syscfg_clock_added;') source.extend(can.state_declarations(result, "stm32")) source.extend(ethernet.state_declarations(result, "stm32")) @@ -116,7 +118,7 @@ def stop_system(result, *, system_phase=None): source.extend(timer_advanced.stop_system(result, "stm32")) source.extend(secondary_controllers.stop_system(result, "stm32")) source += gpio_phase.release(result) - if any(item["kind"] == "exti" for item in selections): + if any(item.kind == "exti" for item in selections): source += [' if (s_nx_platform_started) { RCC->APB2ENR &= ~s_nx_platform_syscfg_clock_added; }'] if dma_clock: source += [' if (s_nx_platform_started) {', @@ -141,9 +143,9 @@ def start_system(result): source = preflight.emit(result) if dma_clock: source.append(' s_nx_platform_dma_clocks_added = 0u;') - if any(item["kind"] == "exti" for item in selections): + if any(item.kind == "exti" for item in selections): source.append(' s_nx_platform_syscfg_clock_added = 0u;') - source += [f' result.primary = nx_stm32_validate_identity({result["flash"]["size"]}u);', + source += [f' result.primary = nx_stm32_validate_identity({result.flash["size"]}u);', ' if (result.primary != NX_SUCCESS) { return result; }'] source += gpio_phase.acquire(result) source += [' nx_stm32_start_result_t system = nx_stm32_system_start(&g_nx_stm32_system, 1000000u);', @@ -168,6 +170,6 @@ def start_resources(result, *, cleanup="nx_platform_stop()"): source += [f' s_nx_platform_dma_clocks_added = ~RCC->AHB1ENR & ({mask});', f' RCC->AHB1ENR |= ({mask});', ' (void)RCC->AHB1ENR;', ' nx_arch_dsb();'] - if any(item["kind"] == "exti" for item in selections): + if any(item.kind == "exti" for item in selections): source.append(' s_nx_platform_syscfg_clock_added = ~RCC->APB2ENR & RCC_APB2ENR_SYSCFGEN;') return source diff --git a/soc/stm32/stm32f407/configure/preflight.py b/soc/stm32/stm32f407/configure/preflight.py index 3ed486713..1954fc1e1 100644 --- a/soc/stm32/stm32f407/configure/preflight.py +++ b/soc/stm32/stm32f407/configure/preflight.py @@ -44,23 +44,23 @@ def selected_preflight(result, *, dma_conditions=None): if item.kind == 'eth_frames': clocks.add(('RCC->AHB1ENR', 'RCC_AHB1ENR_ETHMACPTPEN')) elif item.kind == 'exti': - line = int(item['pins'][0]['pin'][2:]) + line = int(item.pins[0]['pin'][2:]) registers = 'EXTI->IMR | EXTI->EMR | EXTI->PR' source += _reject(f'(({registers}) & {1 << line}u) != 0u') for item in claims: - timer = item['key'].removeprefix('controller:') + timer = item.key.removeprefix('controller:') if re.fullmatch('TIM[1349]|TIMER[0238]', timer): clocks.add(_clock(timer)) - if result.get('can_domain') is not None: + if result.can_domain is not None: clocks.add(_clock('CAN1')) for register, mask in sorted(clocks): source += _reject(f'({register} & ({mask})) != 0u') - for key in sorted({item['key'] for item in claims if item['key'].startswith('irq:')}): + for key in sorted({item.key for item in claims if item.key.startswith('irq:')}): vector = key.removeprefix('irq:') + '_IRQn' bank = f'(uint32_t){vector} / 32u' mask = f'(1u << ((uint32_t){vector} % 32u))' source += _reject(f'((NVIC->ISER[{bank}] | NVIC->ISPR[{bank}] | NVIC->IABR[{bank}]) & {mask}) != 0u') - for key in sorted({item['key'] for item in claims if item['key'].startswith('dma:')}): + for key in sorted({item.key for item in claims if item.key.startswith('dma:')}): match = re.fullmatch('dma:(DMA[0-9]+):stream([0-7])', key) if match is None: fail('No maintained selected DMA lane admission: ' + key) diff --git a/soc/stm32/stm32f407/configure/secondary.py b/soc/stm32/stm32f407/configure/secondary.py index 315d19d02..786c3b0c6 100644 --- a/soc/stm32/stm32f407/configure/secondary.py +++ b/soc/stm32/stm32f407/configure/secondary.py @@ -26,14 +26,6 @@ def busy_condition(controller, kind): return f"({controller}->SR & SPI_SR_BSY) != 0u" if kind == "spi" else None -def spi_endpoint(name, child, endpoint, wire, mask, mode, rate): - cs = "s_nx_cs_" + child - return ([f"static nx_stm32_gpio_state_t {cs};"], [ - f" {cs} = (nx_stm32_gpio_state_t) {{ .registers = GPIO{wire[1]}, .mask = {mask}u, .output = true, .initialized = true }};", - f" {endpoint}.endpoint = (nx_stm32_spi_endpoint_state_t) {{ .port = &s_nx_port_{name}.spi, .cs = &{cs}, .cs_mask = {mask}u, .mode = {mode}u, .frequency_hz = {rate}u }};", - f" {endpoint}.dma = &s_nx_port_{name};"]) - - def setup_uart(item, name, state, paths, clock_hz): dma, stream, channel = paths[0] controller = item.controller @@ -50,15 +42,3 @@ def setup_uart(item, name, state, paths, clock_hz): f" {state}.transmit.stream = {stream}u;", f" {state}.transmit.channel = {channel}u;"], f"nx_stm32_uart_dma_initialize(&{state}, {clock_hz}u)") - - -def setup_spi(item, result, name, state, paths, clock_hz, children, endpoints): - tx, rx = paths - before = [ - f" {state}.spi.registers = {item.controller};", - f" {state}.spi.rcc = RCC;", - f" {state}.spi.clock_hz = {clock_hz}u;", - f" {state}.dma = DMA{tx[0]};", - f" {state}.tx = DMA{tx[0]}_Stream{tx[1]};", - f" {state}.rx = DMA{rx[0]}_Stream{rx[1]};", *endpoints] - return before, f"nx_stm32_spi_dma_initialize(&{state})", [] diff --git a/soc/stm32/stm32f407/drivers/spi.c b/soc/stm32/stm32f407/drivers/spi.c index 17ad022d8..c6ee9a909 100644 --- a/soc/stm32/stm32f407/drivers/spi.c +++ b/soc/stm32/stm32f407/drivers/spi.c @@ -8,11 +8,250 @@ */ #include "nexus/io/spi_transaction.h" #include "stm32f407_provider.h" +#include "stm32f407_spi_cold.h" #include "stm32f4xx_ll_spi.h" #ifndef NX_STM32_IO_POLL #define NX_STM32_IO_POLL(kind, port) ((void)(kind), (void)(port)) #endif + +#ifndef NX_PLATFORM_REGISTER_WRITE +#define NX_PLATFORM_REGISTER_WRITE(reg, value) ((reg) = (uint32_t)(value)) +#endif + +static bool cold_context(void) { + return nx_arch_is_privileged() && !nx_arch_in_isr() && + !nx_arch_irq_is_masked(); +} + +static volatile uint32_t* cold_gate(const nx_stm32_spi_config_t* config) { + return config->registers == SPI1 ? &RCC->APB2ENR : &RCC->APB1ENR; +} + +static uint32_t cold_gate_mask(const nx_stm32_spi_config_t* config) { + return config->registers == SPI1 ? RCC_APB2ENR_SPI1EN : RCC_APB1ENR_SPI2EN; +} + +/** \brief Withdraw electrical drive before altering a saved latch or mux. */ +static bool cold_withdraw(const nx_stm32_spi_pin_config_t* pin) { + uint32_t mask = 3U << (pin->pin * 2U); + uint32_t mode = pin->registers->MODER & mask; + if (mode != 0U && mode != mask) { + NX_PLATFORM_REGISTER_WRITE(pin->registers->MODER, + pin->registers->MODER | mask); + nx_arch_dsb(); + if ((pin->registers->MODER & mask) != mask) + return false; + } + return true; +} + +bool nx_stm32_spi_config_valid(const nx_stm32_spi_config_t* config) { + if (config == NULL || config->pins == NULL || config->endpoints == NULL || + config->endpoint_count == 0U || + config->endpoint_count > SIZE_MAX - 3U || + config->pin_count != 3U + config->endpoint_count || + (config->registers != SPI1 && config->registers != SPI2) || + config->clock_hz != (config->registers == SPI1 ? 84000000U : 42000000U)) + return false; + for (size_t i = 0U; i < config->pin_count; ++i) { + const nx_stm32_spi_pin_config_t* pin = &config->pins[i]; + if (pin->snapshot == NULL || pin->port_index > 8U || pin->pin > 15U || + pin->alternate > 15U || + (uintptr_t)pin->registers != + GPIOA_BASE + (uintptr_t)pin->port_index * 0x400U) + return false; + for (size_t prior = 0U; prior < i; ++prior) { + const nx_stm32_spi_pin_config_t* other = &config->pins[prior]; + if (pin->snapshot == other->snapshot || + (pin->registers == other->registers && pin->pin == other->pin)) + return false; + } + } + for (size_t i = 0U; i < config->endpoint_count; ++i) { + const nx_stm32_spi_endpoint_config_t* endpoint = &config->endpoints[i]; + if (endpoint->endpoint == NULL || endpoint->chip_select == NULL || + endpoint->mode > 3U || endpoint->frequency_hz == 0U || + endpoint->frequency_hz > config->clock_hz / 2U) + return false; + } + return true; +} + +nx_result_t nx_stm32_spi_cold_rollback(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + if (!receipt->rollback_pending) + return NX_SUCCESS; + if (receipt->registers_captured) { + NX_PLATFORM_REGISTER_WRITE(config->registers->CR2, receipt->cr2); + NX_PLATFORM_REGISTER_WRITE(config->registers->CR1, receipt->cr1); + nx_arch_dsb(); + if (config->registers->CR1 != receipt->cr1 || + config->registers->CR2 != receipt->cr2) + return NX_ERROR_IO; + receipt->registers_captured = false; + } + volatile uint32_t* gate = cold_gate(config); + uint32_t mask = cold_gate_mask(config); + NX_PLATFORM_REGISTER_WRITE(*gate, + (*gate & ~mask) | receipt->peripheral_clock); + nx_arch_dsb(); + if ((*gate & mask) != receipt->peripheral_clock) + return NX_ERROR_IO; + while (receipt->captured != 0U) { + size_t index = receipt->captured - 1U; + const nx_stm32_spi_pin_config_t* pin = &config->pins[index]; + if (!cold_withdraw(pin)) + return NX_ERROR_IO; + nx_stm32_pin_restore(pin->registers, pin->pin, *pin->snapshot); + nx_arch_dsb(); + if (nx_stm32_pin_capture(pin->registers, pin->pin) != *pin->snapshot) + return NX_ERROR_IO; + if (index >= 3U) + config->endpoints[index - 3U].chip_select->initialized = false; + --receipt->captured; + } + NX_PLATFORM_REGISTER_WRITE(RCC->AHB1ENR, + (RCC->AHB1ENR & ~receipt->gpio_mask) | + receipt->gpio_clocks); + nx_arch_dsb(); + if ((RCC->AHB1ENR & receipt->gpio_mask) != receipt->gpio_clocks) + return NX_ERROR_IO; + *receipt = (nx_stm32_spi_cold_receipt_t){0}; + port->registers = NULL; + return NX_SUCCESS; +} + +nx_result_t nx_stm32_spi_cold_acquire(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (port == NULL || receipt == NULL) + return NX_ERROR_INVALID; + if (port->registers != NULL || port->active || receipt->rollback_pending) + return NX_ERROR_BUSY; + if (!nx_stm32_spi_config_valid(config)) + return NX_ERROR_INVALID; + for (size_t i = 0U; i < config->endpoint_count; ++i) { + if (config->endpoints[i].chip_select->initialized) + return NX_ERROR_BUSY; + } + *receipt = (nx_stm32_spi_cold_receipt_t){0}; + for (size_t i = 0U; i < config->pin_count; ++i) + receipt->gpio_mask |= 1U << config->pins[i].port_index; + receipt->gpio_clocks = RCC->AHB1ENR & receipt->gpio_mask; + volatile uint32_t* gate = cold_gate(config); + receipt->peripheral_clock = *gate & cold_gate_mask(config); + receipt->rollback_pending = true; + port->registers = config->registers; + port->rcc = RCC; + port->clock_hz = config->clock_hz; + NX_PLATFORM_REGISTER_WRITE(*gate, *gate | cold_gate_mask(config)); + nx_arch_dsb(); + nx_result_t status = NX_ERROR_IO; + if ((*gate & cold_gate_mask(config)) == 0U) + goto failed; + receipt->cr1 = config->registers->CR1; + receipt->cr2 = config->registers->CR2; + status = NX_ERROR_BUSY; + if ((receipt->cr1 & SPI_CR1_SPE) != 0U) + goto failed; + receipt->registers_captured = true; + NX_PLATFORM_REGISTER_WRITE(config->registers->CR1, 0U); + NX_PLATFORM_REGISTER_WRITE(config->registers->CR2, 0U); + nx_arch_dsb(); + status = NX_ERROR_IO; + if (config->registers->CR1 != 0U || config->registers->CR2 != 0U) + goto failed; + for (size_t i = 0U; i < config->pin_count; ++i) { + const nx_stm32_spi_pin_config_t* pin = &config->pins[i]; + status = nx_stm32_gpio_clock_enable(pin->port_index); + if (status != NX_SUCCESS) + goto failed; + *pin->snapshot = nx_stm32_pin_capture(pin->registers, pin->pin); + receipt->captured = i + 1U; + if (i < 3U) { + status = nx_stm32_pin_configure(pin->registers, pin->pin, 2U, + pin->alternate, 0U, false); + } else { + const nx_stm32_spi_endpoint_config_t* endpoint = + &config->endpoints[i - 3U]; + nx_stm32_gpio_state_t* cs = endpoint->chip_select; + cs->registers = pin->registers; + cs->mask = 1U << pin->pin; + cs->output = true; + status = nx_stm32_gpio_initialize(cs, cs->mask); + if (status == NX_SUCCESS) { + endpoint->endpoint->port = port; + endpoint->endpoint->cs = cs; + endpoint->endpoint->cs_mask = cs->mask; + endpoint->endpoint->mode = endpoint->mode; + endpoint->endpoint->frequency_hz = endpoint->frequency_hz; + } + } + if (status != NX_SUCCESS) + goto failed; + } + receipt->rollback_pending = false; + return NX_SUCCESS; +failed: + (void)nx_stm32_spi_cold_rollback(port, config, receipt); + return status; +} + +nx_result_t nx_stm32_spi_prepare(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + return nx_stm32_spi_cold_acquire(port, config, receipt); +} + +nx_result_t nx_stm32_spi_close(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + if (!cold_context()) + return NX_ERROR_CONTEXT; + if (port == NULL || receipt == NULL) + return NX_ERROR_INVALID; + if (port->registers == NULL) + return NX_SUCCESS; + if (!nx_stm32_spi_config_valid(config) || + port->registers != config->registers) + return NX_ERROR_INVALID; + if (port->active) + return NX_ERROR_BUSY; + if (receipt->rollback_pending) + return nx_stm32_spi_cold_rollback(port, config, receipt); + NX_PLATFORM_REGISTER_WRITE(port->registers->CR1, 0U); + NX_PLATFORM_REGISTER_WRITE(port->registers->CR2, 0U); + nx_arch_dsb(); + if (port->registers->CR1 != 0U || port->registers->CR2 != 0U) + return NX_ERROR_IO; + for (size_t i = 0U; i < config->endpoint_count; ++i) { + nx_stm32_gpio_state_t* cs = config->endpoints[i].chip_select; + nx_result_t result = nx_stm32_gpio_stop(cs, cs->mask); + if (result != NX_SUCCESS) + return result; + } + for (size_t i = 0U; i < 3U; ++i) { + const nx_stm32_spi_pin_config_t* pin = &config->pins[i]; + if (!cold_withdraw(pin)) + return NX_ERROR_IO; + (void)nx_stm32_pin_configure(pin->registers, pin->pin, 0U, 0U, 0U, + false); + } + volatile uint32_t* gate = cold_gate(config); + uint32_t mask = cold_gate_mask(config); + NX_PLATFORM_REGISTER_WRITE(*gate, + (*gate & ~mask) | receipt->peripheral_clock); + nx_arch_dsb(); + if ((*gate & mask) != receipt->peripheral_clock) + return NX_ERROR_IO; + *receipt = (nx_stm32_spi_cold_receipt_t){0}; + port->registers = NULL; + return NX_SUCCESS; +} #ifndef NX_STM32_SPI_RESET #define NX_STM32_SPI_RESET(reset, mask, active) \ do { \ diff --git a/soc/stm32/stm32f407/drivers/spi_dma.c b/soc/stm32/stm32f407/drivers/spi_dma.c index ada3d7c83..9e8d8a15a 100644 --- a/soc/stm32/stm32f407/drivers/spi_dma.c +++ b/soc/stm32/stm32f407/drivers/spi_dma.c @@ -27,6 +27,12 @@ #ifndef NX_SPI_DMA_SEQUENCE_ENGINE_READ #define NX_SPI_DMA_SEQUENCE_ENGINE_READ(control) (*(control)) #endif +#ifndef NX_PLATFORM_REGISTER_WRITE +#define NX_PLATFORM_REGISTER_WRITE(reg, value) ((reg) = (uint32_t)(value)) +#endif +#ifndef NX_PLATFORM_PRIORITY_WRITE +#define NX_PLATFORM_PRIORITY_WRITE(reg, value) ((reg) = (uint8_t)(value)) +#endif /** \brief Selected register identity determines the immutable reviewed route. */ @@ -530,6 +536,250 @@ static nx_result_t stop(void* context) { return NX_SUCCESS; } +nx_result_t nx_stm32_spi_dma_prepare(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + if (!nx_arch_is_privileged() || nx_arch_in_isr() || nx_arch_irq_is_masked()) + return NX_ERROR_CONTEXT; + if (state == NULL || receipt == NULL) + return NX_ERROR_INVALID; + if (state->initialized || state->spi.registers != NULL || + state->active != NULL || state->spi.active || state->wake != NULL) + return NX_ERROR_BUSY; + if (config == NULL || config->regions == NULL || + config->region_count == 0U || config->priority > 15U) + return NX_ERROR_INVALID; + bool first = config->spi.registers == SPI1; + if ((!first && config->spi.registers != SPI2) || + config->dma != (first ? DMA2 : DMA1) || + config->tx != (first ? DMA2_Stream3 : DMA1_Stream4) || + config->rx != (first ? DMA2_Stream0 : DMA1_Stream3)) + return NX_ERROR_UNSUPPORTED; + nx_result_t result = + nx_stm32_spi_cold_acquire(&state->spi, &config->spi, receipt); + if (result != NX_SUCCESS) + return result; + state->dma = config->dma; + state->tx = config->tx; + state->rx = config->rx; + state->regions = config->regions; + state->region_count = config->region_count; + for (size_t i = 0U; i < config->spi.endpoint_count; ++i) { + nx_stm32_spi_dma_endpoint_state_t* endpoint = + (nx_stm32_spi_dma_endpoint_state_t*)config->spi.endpoints[i] + .endpoint; + endpoint->dma = state; + } + result = nx_stm32_spi_dma_initialize(state); + if (result != NX_SUCCESS) { + receipt->rollback_pending = true; + (void)nx_stm32_spi_cold_rollback(&state->spi, &config->spi, receipt); + return result; + } +#ifndef NEXUS_STM32_MODEL + NVIC_SetPriority(dma_irq(state, true), config->priority); + NVIC_SetPriority(dma_irq(state, false), config->priority); +#endif + return NX_SUCCESS; +} + +nx_result_t nx_stm32_spi_dma_close(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt) { + if (state == NULL || config == NULL || receipt == NULL) + return NX_ERROR_INVALID; + nx_result_t result = stop(state); + return result == NX_SUCCESS + ? nx_stm32_spi_close(&state->spi, &config->spi, receipt) + : result; +} + +static bool pair_config(const nx_stm32_spi_dma_config_t* config) { + if (config == NULL || !nx_stm32_spi_config_valid(&config->spi) || + config->spi.pin_count > 16U || config->regions == NULL || + config->region_count == 0U || config->priority > 15U) + return false; + bool first = config->spi.registers == SPI1; + return config->dma == (first ? DMA2 : DMA1) && + config->tx == (first ? DMA2_Stream3 : DMA1_Stream4) && + config->rx == (first ? DMA2_Stream0 : DMA1_Stream3); +} + +nx_result_t +nx_stm32_spi_dma_pair_prepare(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_dma_cold_t* cold) { + if (!nx_arch_is_privileged() || nx_arch_in_isr() || nx_arch_irq_is_masked()) + return NX_ERROR_CONTEXT; + if (state == NULL || cold == NULL || !pair_config(config)) + return NX_ERROR_INVALID; + if (cold->constructed || cold->vectors || cold->pin_leases != 0U || + cold->clock_added != 0U || state->initialized || + state->active != NULL || state->spi.active || state->wake != NULL) + return NX_ERROR_BUSY; + const bool first = config->spi.registers == SPI1; + const unsigned vectors[2] = {first ? DMA2_Stream3_IRQn : DMA1_Stream4_IRQn, + first ? DMA2_Stream0_IRQn : DMA1_Stream3_IRQn}; + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint32_t mask = 1UL << (irq % 32U); + if (((NVIC->ISER[irq / 32U] | NVIC->ISPR[irq / 32U] | + NVIC->IABR[irq / 32U]) & + mask) != 0U) + return NX_ERROR_BUSY; + } + if (((config->tx->CR | config->rx->CR) & DMA_SxCR_EN) != 0U) + return NX_ERROR_BUSY; + if (config->kernel_bound && NVIC_GetPriorityGrouping() > 3U) + return NX_ERROR_PERMISSION; + volatile uint32_t* gate = first ? &RCC->APB2ENR : &RCC->APB1ENR; + const uint32_t mask = first ? RCC_APB2ENR_SPI1EN : RCC_APB1ENR_SPI2EN; + cold->clock_added = ~*gate & mask; + NX_PLATFORM_REGISTER_WRITE(*gate, *gate | mask); + nx_arch_dsb(); + if ((*gate & mask) == 0U) + return NX_ERROR_IO; + if ((config->spi.registers->CR1 & SPI_CR1_SPE) != 0U || + (config->spi.registers->SR & SPI_SR_BSY) != 0U) + return NX_ERROR_BUSY; + for (unsigned i = 0U; i < 2U; ++i) + cold->priorities[i] = NVIC->IPR[vectors[i]]; + cold->vectors = true; + for (size_t i = 0U; i < config->spi.pin_count; ++i) { + const nx_stm32_spi_pin_config_t* pin = &config->spi.pins[i]; + nx_result_t status = nx_stm32_gpio_clock_enable(pin->port_index); + if (status != NX_SUCCESS) + return status; + *pin->snapshot = nx_stm32_pin_capture(pin->registers, pin->pin); + cold->pin_leases |= (uint16_t)(1U << i); + if (i >= 3U) { + const uint32_t bit = 1U << pin->pin; + NX_PLATFORM_REGISTER_WRITE(pin->registers->BSRR, bit); + nx_arch_dsb(); + if ((pin->registers->ODR & bit) == 0U) + return NX_ERROR_IO; + } + status = + nx_stm32_pin_configure(pin->registers, pin->pin, i >= 3U ? 1U : 2U, + pin->alternate, 0U, false); + if (status != NX_SUCCESS) + return status; + } + state->spi.registers = config->spi.registers; + state->spi.rcc = RCC; + state->spi.clock_hz = config->spi.clock_hz; + state->dma = config->dma; + state->tx = config->tx; + state->rx = config->rx; + state->regions = config->regions; + state->region_count = config->region_count; + for (size_t i = 0U; i < config->spi.endpoint_count; ++i) { + const nx_stm32_spi_endpoint_config_t* entry = &config->spi.endpoints[i]; + const nx_stm32_spi_pin_config_t* pin = &config->spi.pins[3U + i]; + *entry->chip_select = + (nx_stm32_gpio_state_t){pin->registers, 1U << pin->pin, true, true}; + *entry->endpoint = (nx_stm32_spi_endpoint_state_t){ + &state->spi, entry->chip_select, 1U << pin->pin, + entry->frequency_hz, entry->mode}; + ((nx_stm32_spi_dma_endpoint_state_t*)entry->endpoint)->dma = state; + } + nx_result_t status = nx_stm32_spi_dma_initialize(state); + if (status != NX_SUCCESS) + return status; + cold->constructed = true; + if ((*gate & mask) == 0U) + return NX_ERROR_IO; + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint8_t priority = (uint8_t)(config->priority << 4U); + NX_PLATFORM_PRIORITY_WRITE(NVIC->IPR[irq], priority); + nx_arch_dsb(); + if (NVIC->IPR[irq] != priority || + (NVIC->ISER[irq / 32U] & (1UL << (irq % 32U))) == 0U) + return NX_ERROR_IO; + } + return NX_SUCCESS; +} + +nx_result_t nx_stm32_spi_dma_pair_close(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_dma_cold_t* cold) { + if (!nx_arch_is_privileged() || nx_arch_in_isr() || nx_arch_irq_is_masked()) + return NX_ERROR_CONTEXT; + if (state == NULL || cold == NULL || !pair_config(config)) + return NX_ERROR_INVALID; + if (cold->constructed) { + nx_result_t status = stop(state); + if (status != NX_SUCCESS) + return status; + cold->constructed = false; + } + const bool first = config->spi.registers == SPI1; + const unsigned vectors[2] = {first ? DMA2_Stream3_IRQn : DMA1_Stream4_IRQn, + first ? DMA2_Stream0_IRQn : DMA1_Stream3_IRQn}; + if (cold->vectors) { + for (unsigned i = 0U; i < 2U; ++i) { + const unsigned irq = vectors[i]; + const uint32_t mask = 1UL << (irq % 32U); + NX_PLATFORM_REGISTER_WRITE(NVIC->ICER[irq / 32U], mask); + NX_PLATFORM_REGISTER_WRITE(NVIC->ICPR[irq / 32U], mask); + nx_arch_dsb(); + if (((NVIC->ISER[irq / 32U] | NVIC->ISPR[irq / 32U] | + NVIC->IABR[irq / 32U]) & + mask) != 0U) + return NX_ERROR_BUSY; + NX_PLATFORM_PRIORITY_WRITE(NVIC->IPR[irq], cold->priorities[i]); + nx_arch_dsb(); + if (NVIC->IPR[irq] != cold->priorities[i]) + return NX_ERROR_IO; + } + cold->vectors = false; + } + /* Every retained CS must prove inactive before any selected pad is + * restored. A failed latch write never downgrades its recovery lease. */ + for (size_t i = 3U; i < config->spi.pin_count; ++i) { + if ((cold->pin_leases & (1U << i)) == 0U) + continue; + const nx_stm32_spi_pin_config_t* pin = &config->spi.pins[i]; + const uint32_t bit = 1U << pin->pin; + NX_PLATFORM_REGISTER_WRITE(pin->registers->BSRR, bit); + nx_arch_dsb(); + if ((pin->registers->ODR & bit) == 0U) + return NX_ERROR_IO; + } + for (size_t count = config->spi.pin_count; count != 0U; --count) { + const uint16_t owned = (uint16_t)(1U << (count - 1U)); + if ((cold->pin_leases & owned) == 0U) + continue; + const nx_stm32_spi_pin_config_t* pin = &config->spi.pins[count - 1U]; + const uint32_t mask = 3U << (pin->pin * 2U); + const uint32_t mode = pin->registers->MODER & mask; + if (mode != 0U && mode != mask) { + NX_PLATFORM_REGISTER_WRITE(pin->registers->MODER, + pin->registers->MODER | mask); + nx_arch_dsb(); + if ((pin->registers->MODER & mask) != mask) + return NX_ERROR_IO; + } + nx_stm32_pin_restore(pin->registers, pin->pin, *pin->snapshot); + if (nx_stm32_pin_capture(pin->registers, pin->pin) != *pin->snapshot) + return NX_ERROR_IO; + cold->pin_leases &= (uint16_t)~owned; + if (count > 3U) + config->spi.endpoints[count - 4U].chip_select->initialized = false; + } + if (cold->clock_added != 0U) { + volatile uint32_t* gate = first ? &RCC->APB2ENR : &RCC->APB1ENR; + NX_PLATFORM_REGISTER_WRITE(*gate, *gate & ~cold->clock_added); + nx_arch_dsb(); + if ((*gate & cold->clock_added) != 0U) + return NX_ERROR_IO; + cold->clock_added = 0U; + } + state->spi.registers = NULL; + return NX_SUCCESS; +} + /** \brief A recovered idle bus never replays the failed request. */ static nx_result_t recover(void* context) { nx_stm32_spi_dma_state_t* state = context; diff --git a/soc/stm32/stm32f407/private/stm32f407_dma.h b/soc/stm32/stm32f407/private/stm32f407_dma.h index 2518df85e..d15716460 100644 --- a/soc/stm32/stm32f407/private/stm32f407_dma.h +++ b/soc/stm32/stm32f407/private/stm32f407_dma.h @@ -10,6 +10,7 @@ #include "nexus/io/dma.h" #include "stm32f407_provider.h" +#include "stm32f407_spi_cold.h" /** * \brief Exact caller-owned DMA TX instance; no stream allocator. @@ -74,6 +75,30 @@ struct nx_stm32_spi_dma_endpoint_state { nx_stm32_spi_dma_state_t* dma; }; +/** \brief Immutable selected SPI assembly and DMA resources. */ +typedef struct { + nx_stm32_spi_config_t spi; + DMA_TypeDef* dma; + DMA_Stream_TypeDef* tx; + DMA_Stream_TypeDef* rx; + const nx_dma_memory_region_t* regions; + size_t region_count; + uint8_t priority; + bool kernel_bound; +} nx_stm32_spi_dma_config_t; + +/** \brief Exact paired-controller cold effects; pin snapshots stay shared. + * \note Each bit names a selected pad, never a maximum-size object pool. + * A failed pin, vector, priority or gate withdrawal retains its marker. + */ +typedef struct { + uint32_t clock_added; + uint16_t pin_leases; + uint8_t priorities[2]; + bool vectors; + bool constructed; +} nx_stm32_spi_dma_cold_t; + #ifdef __cplusplus extern "C" { #endif @@ -96,6 +121,45 @@ extern const nx_spi_endpoint_ops_t nx_stm32_spi_dma_endpoint_ops; */ nx_result_t nx_stm32_spi_dma_initialize(nx_stm32_spi_dma_state_t* state); +/** \brief Prepare exact static SPI pins, endpoints and selected DMA vectors. + * \param[in,out] state: Cold exclusive privileged task owner, no active loans. + * \param[in] config: Static facts and the enclosing Platform pin snapshots. + * \param[in,out] receipt: Static cold effects retained through failed cleanup. + * \return Success or error; failed withdrawal keeps Platform responsible. + */ +nx_result_t nx_stm32_spi_dma_prepare(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); +/** \brief Join DMA and wire references before returning pins and SPI clock. + * \param[in,out] state: The same serialized owner as prepare. + * \param[in] config: Unchanged static assembly resources. + * \param[in,out] receipt: The same retained cold effects supplied to prepare. + * \return Success only after complete driver and cold-resource withdrawal. + */ +nx_result_t nx_stm32_spi_dma_close(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); + +/** \brief Acquire one member of a selected independent SPI controller pair. + * \param[in,out] state: Serialized cold owner, no active requests or vectors. + * \param[in] config: Exact controller, route and shared Platform snapshots. + * \param[in,out] cold: Zero or fully withdrawn cold effects, private per owner. + * \return Success or error; every partial effect remains in cold until close. + */ +nx_result_t +nx_stm32_spi_dma_pair_prepare(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_dma_cold_t* cold); +/** \brief Join publisher, IRQ, priority, pin and gate ownership in that order. + * \param[in,out] state: Same exclusive owner as prepare. + * \param[in] config: Unchanged selected static resources. + * \param[in,out] cold: Actual retained markers from prepare or failed close. + * \return Success only after every marker is withdrawn; failure allows retry. + */ +nx_result_t nx_stm32_spi_dma_pair_close(nx_stm32_spi_dma_state_t* state, + const nx_stm32_spi_dma_config_t* config, + nx_stm32_spi_dma_cold_t* cold); + /** * \brief Observe one selected finite DMA source without polling * waits. diff --git a/soc/stm32/stm32f407/private/stm32f407_spi_cold.h b/soc/stm32/stm32f407/private/stm32f407_spi_cold.h new file mode 100644 index 000000000..518dc1fc8 --- /dev/null +++ b/soc/stm32/stm32f407/private/stm32f407_spi_cold.h @@ -0,0 +1,101 @@ +/** + * \file stm32f407_spi_cold.h + * \brief Static SPI pin and endpoint assembly with checked rollback + * \author Nexus Team + */ +#ifndef NX_STM32F407_SPI_COLD_H +#define NX_STM32F407_SPI_COLD_H + +#include "stm32f407_provider.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \brief One exact pad and its existing Platform-owned snapshot slot. */ +typedef struct { + GPIO_TypeDef* registers; + uint16_t* snapshot; + uint8_t port_index; + uint8_t pin; + uint8_t alternate; +} nx_stm32_spi_pin_config_t; + +/** \brief One immutable CS association, separate from controller identity. */ +typedef struct { + nx_stm32_spi_endpoint_state_t* endpoint; + nx_stm32_gpio_state_t* chip_select; + uint32_t frequency_hz; + uint8_t mode; +} nx_stm32_spi_endpoint_config_t; + +/** \brief Exact static resources; three signal pads precede one pad per CS. + * \note Platform captures and owns the same snapshot slots until its final + * resource cleanup. This description adds no runtime dispatch or worker. + */ +typedef struct { + SPI_TypeDef* registers; + uint32_t clock_hz; + const nx_stm32_spi_pin_config_t* pins; + size_t pin_count; + const nx_stm32_spi_endpoint_config_t* endpoints; + size_t endpoint_count; +} nx_stm32_spi_config_t; + +/** \brief Retained cold effects; shared pin snapshots stay Platform-owned. */ +typedef struct { + uint32_t gpio_mask; + uint32_t gpio_clocks; + uint32_t peripheral_clock; + uint32_t cr1; + uint32_t cr2; + size_t captured; + bool registers_captured; + bool rollback_pending; +} nx_stm32_spi_cold_receipt_t; + +/** \brief Validate static pad/endpoint shape without hardware effects. */ +bool nx_stm32_spi_config_valid(const nx_stm32_spi_config_t* config); + +/** \brief Assemble a cold polling controller and its exact static endpoints. + * \param[in,out] port: Serialized unmasked privileged task, initially cold. + * \param[in] config: Static selected resources with disjoint snapshot slots. + * \param[in,out] receipt: Static cold effects retained through failed cleanup. + * \return Success or error; failed pad withdrawal retains the Platform lease. + */ +nx_result_t nx_stm32_spi_prepare(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); +/** \brief Withdraw idle SPI and CS before Platform restores original pads. + * \param[in,out] port: Owner with all calls and loans already joined. + * \param[in] config: The unchanged resources used by prepare. + * \param[in,out] receipt: The same retained cold effects supplied to prepare. + * \return Success or error retaining the owner and its resource lease. + */ +nx_result_t nx_stm32_spi_close(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); +/** \brief Shared cold acquisition used directly by the selected DMA wrapper. + * \param[in,out] port: Initially cold serialized owner. + * \param[in] config: Exact static resources owned by the Platform pin lease. + * \param[in,out] receipt: Static cold receipt retained through failed cleanup. + * \return Success or error after attempted restoration of acquired effects. + */ +nx_result_t nx_stm32_spi_cold_acquire(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); +/** \brief Undo a failed cold acquisition without releasing unproved pads. + * \param[in,out] port: Cold owner whose DMA/IRQ acquisition did not succeed. + * \param[in] config: The resources supplied to cold acquire. + * \param[in,out] receipt: Actual effects, cleared only after observed recovery. + * \return Success only after all acquired effects are observed restored. + * \note Failed restoration preserves this receipt and the Platform lease. + */ +nx_result_t nx_stm32_spi_cold_rollback(nx_stm32_spi_state_t* port, + const nx_stm32_spi_config_t* config, + nx_stm32_spi_cold_receipt_t* receipt); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/soc/stm32/stm32g474/configure/adc_group.py b/soc/stm32/stm32g474/configure/adc_group.py index 1a3d74ca6..e26967605 100644 --- a/soc/stm32/stm32g474/configure/adc_group.py +++ b/soc/stm32/stm32g474/configure/adc_group.py @@ -5,6 +5,7 @@ """ import re +from tools.configure.calls import Address, DirectCall from tools.configure.providers.common import Construction, fail, integer, sequence from tools.configure.memory import dma_regions @@ -227,5 +228,5 @@ def constructor(item, result, binding): "void DMA1_Channel1_IRQHandler(void) { " f"nx_stm32g474_adc_group_dma_irq(&{state}); }}"] return Construction("nx_stm32g474_adc_group_state_t", tuple(lines), - f"prepare_{name}()", - (f"nx_stm32g474_adc_group_close(&{state})",), True) + DirectCall(f"prepare_{name}"), + (DirectCall("nx_stm32g474_adc_group_close", (Address(state),)),), True) diff --git a/soc/stm32/stm32g474/configure/bindings.py b/soc/stm32/stm32g474/configure/bindings.py index d6ea19495..ac23490aa 100644 --- a/soc/stm32/stm32g474/configure/bindings.py +++ b/soc/stm32/stm32g474/configure/bindings.py @@ -1,5 +1,6 @@ """Cold assembly selects static contexts; hot I/O uses the one actual ops table.""" -from tools.configure.ir import controller_ir +from tools.configure.calls import Address, DirectCall +from tools.configure.ir import ControllerIR from tools.configure import enhanced_endpoints from tools.configure.providers.common import Construction, fail, kernel_policy_constant from tools.configure.memory import dma_regions @@ -8,8 +9,8 @@ def constructor(item, result, board_bindings, routes, devices): """Construct exact static controller state, endpoints and bounded storage.""" - from .contract import validate_selection - item = controller_ir(item, "stm32g474") + if not isinstance(item, ControllerIR): + fail("stm32g474 constructor requires a resolved controller") if item.provider != "stm32g474": fail("G474 constructor cannot reinterpret a foreign provider decision") if item.kind == "pwm" and item.mode == "shared-fixed-pwm": @@ -17,8 +18,6 @@ def constructor(item, result, board_bindings, routes, devices): if item.kind == "adc_group": from . import adc_group return adc_group.constructor(item, result, board_bindings[item.binding]) - validate_selection(item, routes[item["route"]] if item.kind in { - "pwm_adc", "can_fd"} else item) if item.kind in {"pwm_adc", "can_fd"}: from . import coupled return coupled.constructor(item, result, board_bindings) @@ -27,7 +26,7 @@ def constructor(item, result, board_bindings, routes, devices): name = item.id.replace("-", "_") state = "s_nx_port_" + name definitions = [] - initialize = f"prepare_{name}()" + initialize = DirectCall(f"prepare_{name}") if item.kind == "gpio" and item.mode == "named-states": options = item.options plan = "s_nx_pin_plan_" + name @@ -46,8 +45,8 @@ def constructor(item, result, board_bindings, routes, devices): "}"] return Construction( state_type="nx_stm32g474_pin_state_t", definitions=definitions, - initialize=f"prepare_{name}()", - stop=[f"nx_stm32g474_pin_state_stop(&{state})"], + initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall("nx_stm32g474_pin_state_stop", (Address(state),))], retain_on_failed_init=True) if item.kind == "gpio": definitions += [f"static nx_result_t prepare_{name}(void) {{", @@ -57,14 +56,15 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.output = {'true' if item.mode == 'output' else 'false'};", f" return nx_stm32g474_gpio_initialize(&{state}, {item.options.initial}u);", "}"] - stop = f"nx_stm32g474_gpio_stop(&{state}, {item.options.initial}u)" + stop = DirectCall("nx_stm32g474_gpio_stop", + (Address(state), item.options.initial)) elif item.kind == "uart": options = item.options capacity = options.rx_capacity if type(capacity) is not int or not 1 <= capacity <= 4096: raise ValueError("G474 LPUART requires an explicit bounded byte ring") - irq = result["irq"] - tx, rx = item["pins"] + irq = result.irq + tx, rx = item.pins definitions += [f"static uint8_t s_nx_rx_{name}[{capacity}];", f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active != NULL || {state}.wake != NULL || {state}.wake_publishers != 0u) {{ return NX_ERROR_BUSY; }}", @@ -74,7 +74,7 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.baud = {options.baud}u;", f" {state}.irq = {item.controller}_IRQn;", f" {state}.profile = NX_UART_RX_BYTES;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", (" nx_result_t status = nx_stm32g474_lpuart1_pins_prepare();" if item.controller == "LPUART1" else f" nx_result_t status = nx_stm32g474_uart_pins_prepare(GPIO{tx['pin'][1]}, {int(tx['pin'][2:])}u, GPIO{rx['pin'][1]}, {int(rx['pin'][2:])}u, {tx['af']}u);"), @@ -83,9 +83,9 @@ def constructor(item, result, board_bindings, routes, devices): "}", f"void {item.controller}_IRQHandler(void);", r"/** \brief One selected controller, at most one TX/RX byte. */", f"void {item.controller}_IRQHandler(void) {{ nx_stm32g474_uart_irq(&{state}); }}"] - stop = f"nx_uart_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "spi": - children = [child for child in devices if child["controller"] == item.id] + children = [child for child in devices if child.controller == item.id] if not children: fail("G474 SPI requires a reviewed active-low CS endpoint") body = [f"static nx_result_t prepare_{name}(void) {{", @@ -102,10 +102,10 @@ def constructor(item, result, board_bindings, routes, devices): f" nx_result_t status = nx_spi_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }"] for child in children: - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") cs = f"s_nx_cs_{child_name}" endpoint = f"s_nx_endpoint_{child_name}" - binding = board_bindings[child["cs_binding"]] + binding = board_bindings[child.options.cs_binding] pins = routes[binding["route"]]["pins"] if len(pins) != 1: fail("G474 SPI endpoint CS requires one reviewed pad") @@ -113,10 +113,10 @@ def constructor(item, result, board_bindings, routes, devices): mask = 1 << int(pin[2:]) if binding.get("initial") != mask: fail("G474 SPI active-low CS requires an initial high latch") - rate = child.get("max_hz", item.options.max_hz) + rate = child.options.max_hz if type(rate) is not int or not 664063 <= rate <= item.options.max_hz: fail("G474 SPI endpoint rate is outside its hardware divider") - mode = child.get("mode", 0) + mode = child.options.mode if type(mode) is not int or not 0 <= mode <= 3: fail("G474 SPI endpoint mode must be 0 through 3") definitions += [f"static nx_stm32g474_gpio_state_t {cs};", @@ -135,13 +135,13 @@ def constructor(item, result, board_bindings, routes, devices): f" {endpoint}.mode = {mode}u;"] stops += [f" status = nx_stm32g474_gpio_stop(&{cs}, {mask}u);", " if (status != NX_SUCCESS) { return status; }"] - for pin in item["pins"]: + for pin in item.pins: wire = pin["pin"] body += [f" status = nx_stm32g474_pin_alternate(GPIO{wire[1]}, {int(wire[2:])}u, {pin['af']}u);", " if (status != NX_SUCCESS) { return status; }"] definitions += body + [" return NX_SUCCESS;", "}"] definitions += stops + [" return NX_SUCCESS;", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "pwm_group": if board_bindings[item.binding].get("mask") != 1810 or board_bindings[item.binding].get("initial") != 0: fail("G474 coherent PWM requires explicit three-pin inactive-low Board facts") @@ -214,11 +214,11 @@ def constructor(item, result, board_bindings, routes, devices): f" if ((NX_G474_READ(&COMP1->CSR) & ~(uint32_t)COMP_CSR_VALUE) != {comp}.saved) {{ return NX_ERROR_IO; }}", f" {comp}.retained = false;", " }", " return NX_SUCCESS;", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "pwm": - pin = item["pins"][0]["pin"] + pin = item.pins[0]["pin"] idle = "s_nx_idle_" + name - binding = board_bindings.get(item["binding"], {}) + binding = board_bindings.get(item.binding, {}) mask = 1 << int(pin[2:]) initial = binding.get("initial") if type(initial) is not int or initial not in {0, mask} or binding.get("mask") != mask: @@ -240,7 +240,7 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.tick_hz = {item.options.tick_hz}u;", f" {state}.poll_limit = 1000000u;", f" return nx_stm32g474_pwm_initialize(&{state});", "}"] - stop = f"nx_stm32g474_pwm_close(&{state})" + stop = DirectCall("nx_stm32g474_pwm_close", (Address(state),)) elif item.kind == "i2c": timing = (f"0x{item.options.timing.timingr:08x}" if item.options.timing is not None else "0x30420f13") @@ -251,16 +251,17 @@ def constructor(item, result, board_bindings, routes, devices): f" .timing = {timing}u, .poll_limit = 1000000u,", " .ignore_master_berr = true, .receive_reload_one = false", "};"] - initialize = f"nx_stm32g474_i2c_prepare(&{state}, &{config})" + initialize = DirectCall("nx_stm32g474_i2c_prepare", + (Address(state), Address(config))) for child in devices: - if child["controller"] != item.id: + if child.controller != item.id: continue - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") endpoint = "s_nx_endpoint_" + child_name - definitions += [f"static nx_i2c_v2_endpoint_state_t {endpoint} = {{ &{state}, {child['address']}u }};", + definitions += [f"static nx_i2c_v2_endpoint_state_t {endpoint} = {{ &{state}, {child.options.address}u }};", *enhanced_endpoints.face(child, endpoint, "nx_i2c_v2_endpoint_ops")] - stop = f"nx_i2c_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_i2c_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "flash": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.active || {state}.restore_pending || (FLASH->SR & FLASH_SR_BSY) != 0u) {{ return NX_ERROR_BUSY; }}", @@ -268,7 +269,7 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.memory = (volatile uint8_t*)FLASH_BASE;", f" {state}.supply_mv = 3300u;", " return NX_SUCCESS;", "}"] - stop = f"nx_stm32g474_flash_close(&{state})" + stop = DirectCall("nx_stm32g474_flash_close", (Address(state),)) elif item.kind == "adc": stream = item.mode == "trigger-dma" target = state + (".adc" if stream else "") @@ -289,22 +290,22 @@ def constructor(item, result, board_bindings, routes, devices): " nx_arch_dsb();", " if ((NX_G474_READ(&GPIOA->MODER) & 3u) != 3u || (NX_G474_READ(&GPIOA->PUPDR) & 3u) != 0u) { return NX_ERROR_IO; }"] if stream: - irq = result["irq"] + irq = result.irq definitions += [f" {state}.timer = TIM6;", f" {state}.dma = DMA1;", f" {state}.channel = DMA1_Channel1;", f" {state}.mux = DMAMUX1_Channel0;", f" {state}.regions = s_nx_dma_regions_{name};", f" {state}.region_count = {count}u;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_stm32g474_adc_stream_initialize(&{state});", "}", "void DMA1_Channel1_IRQHandler(void);", r"/** \brief One selected finite ADC block terminal observation. */", f"void DMA1_Channel1_IRQHandler(void) {{ nx_stm32g474_adc_stream_irq(&{state}); }}"] - stop = f"nx_stm32g474_adc_stream_close(&{state})" + stop = DirectCall("nx_stm32g474_adc_stream_close", (Address(state),)) else: definitions += [f" return nx_stm32g474_adc_initialize(&{state});", "}"] - stop = f"nx_stm32g474_adc_close(&{state})" + stop = DirectCall("nx_stm32g474_adc_close", (Address(state),)) elif item.kind == "watchdog": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.state.enabled || {state}.lsi_added || {state}.debug_pending) {{ return NX_ERROR_BUSY; }}", @@ -317,10 +318,10 @@ def constructor(item, result, board_bindings, routes, devices): f"static nx_result_t stop_{name}(void) {{", f" return nx_stm32g474_watchdog_stop(&{state});", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "exti": options = item.options - irq = result["irq"] + irq = result.irq line = int(item.controller[-1]) definitions += [f"static nx_exti_event_t s_nx_events_{name}[{options.event_capacity}];", f"static nx_result_t prepare_{name}(void) {{", @@ -333,12 +334,12 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.line = {line}u;", f" {state}.priority = {options.irq.priority}u;", f" {state}.edge = NX_EXTI_{options.edge.upper()};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_stm32g474_exti_initialize(&{state});", "}", f"void EXTI{line}_IRQHandler(void);", r"/** \brief One selected line, one bounded coalesced edge fact. */", f"void EXTI{line}_IRQHandler(void) {{ nx_stm32g474_exti_irq(&{state}); }}"] - stop = f"nx_exti_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_exti_port_stop", (Address(f"s_nx_face_{name}"),)) else: raise ValueError("Unmaintained G474 constructor") state_type = ("nx_i2c_v2_state_t" if item.kind == "i2c" else diff --git a/soc/stm32/stm32g474/configure/coupled.py b/soc/stm32/stm32g474/configure/coupled.py index 2ea384509..5e7ebbafb 100644 --- a/soc/stm32/stm32g474/configure/coupled.py +++ b/soc/stm32/stm32g474/configure/coupled.py @@ -1,4 +1,5 @@ """Cold-only generated glue for two independently selected G474 owners.""" +from tools.configure.calls import DirectCall from collections.abc import Mapping from tools.configure.providers.common import ( @@ -92,7 +93,7 @@ def _pwm_adc(item, result, binding): " return NX_SUCCESS;", "}", ] return Construction("nx_stm32g474_pwm_adc_state_t", tuple(lines), - f"prepare_{name}()", (f"stop_{name}()",), True) + DirectCall(f"prepare_{name}"), (DirectCall(f"stop_{name}"),), True) def _can_fd(item, result, binding): @@ -114,7 +115,7 @@ def _can_fd(item, result, binding): for value in options.filters: lines.append(f" {{ {value.id}u, {value.mask}u, NX_CAN_{value.format.upper()}, {value.fifo}u }},") lines.append("};") - irq = result["irq"] + irq = result.irq lines += [ f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {domain}.retained || {domain}.acquired_mask != 0u) {{ return NX_ERROR_BUSY; }}", @@ -125,7 +126,7 @@ def _can_fd(item, result, binding): f" {state}.filter_count = {len(options.filters)}u;", f" {state}.events = s_nx_events_{name};", f" {state}.capacity = {options.event_capacity}u;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" {state}.drain_timeout_us = {options.drain_timeout_us}u;", f" {state}.poll_limit = {options.poll_limit}u;", f" nx_result_t status = nx_stm32g474_fdcan_domain_initialize(&{domain});", @@ -149,7 +150,7 @@ def _can_fd(item, result, binding): f" return nx_stm32g474_fdcan_domain_stop(&{domain});", "}", ] return Construction("nx_stm32g474_fdcan_state_t", tuple(lines), - f"prepare_{name}()", (f"stop_{name}()",), True) + DirectCall(f"prepare_{name}"), (DirectCall(f"stop_{name}"),), True) MODES = {"pwm_adc": ("finite-regular-dma",), diff --git a/soc/stm32/stm32g474/configure/preflight.py b/soc/stm32/stm32g474/configure/preflight.py index ef179f4ab..917259e50 100644 --- a/soc/stm32/stm32g474/configure/preflight.py +++ b/soc/stm32/stm32g474/configure/preflight.py @@ -50,10 +50,10 @@ def selected_preflight(result, *, dma_conditions=None): if item.kind == 'exti': line = int(item.controller.removeprefix('EXTI')) source += _reject(f'((NX_G474_READ(&EXTI->IMR1) | NX_G474_READ(&EXTI->EMR1) | NX_G474_READ(&EXTI->RTSR1) | NX_G474_READ(&EXTI->FTSR1) | NX_G474_READ(&EXTI->PR1)) & {1 << line}u) != 0u') - for key in sorted({item['key'] for item in claims if item['key'].startswith('irq:')}): + for key in sorted({item.key for item in claims if item.key.startswith('irq:')}): vector = key.removeprefix('irq:') + '_IRQn' source += _reject(f'!NX_G474_IRQ_AVAILABLE((IRQn_Type){vector})') - for key in sorted({item['key'] for item in claims if item['key'].startswith('dma:')}): + for key in sorted({item.key for item in claims if item.key.startswith('dma:')}): match = re.fullmatch('dma:(DMA[0-9]+):stream([0-7])', key) if match is None: fail('No maintained selected DMA lane admission: ' + key) diff --git a/soc/stm32/stm32g474/configure/pwm_shared.py b/soc/stm32/stm32g474/configure/pwm_shared.py index bd9208462..38810c9c4 100644 --- a/soc/stm32/stm32g474/configure/pwm_shared.py +++ b/soc/stm32/stm32g474/configure/pwm_shared.py @@ -1,4 +1,5 @@ """One selected physical TIM3 construction, two existing readonly PWM faces.""" +from tools.configure.calls import Address, DirectCall from tools.configure.providers.common import Construction, fail, integer @@ -117,8 +118,8 @@ def constructor(owner): ] return Construction( state_type="nx_stm32g474_pwm_shared_state_t", - definitions=definitions, initialize=f"nx_prepare_physical_{name}()", - stop=(f"nx_stm32g474_pwm_shared_close(&{state})",), + definitions=definitions, initialize=DirectCall(f"nx_prepare_physical_{name}"), + stop=(DirectCall("nx_stm32g474_pwm_shared_close", (Address(state),)),), retain_on_failed_init=True) diff --git a/soc/stm32/stm32g474/configure/timer.py b/soc/stm32/stm32g474/configure/timer.py index 3e20fb343..07d01e6be 100644 --- a/soc/stm32/stm32g474/configure/timer.py +++ b/soc/stm32/stm32g474/configure/timer.py @@ -1,4 +1,5 @@ """STM32G474 checked timer-capture and encoder construction.""" +from tools.configure.calls import DirectCall from tools.configure.providers.common import Construction @@ -24,7 +25,7 @@ def constructor(item): lines += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.config != NULL || {state}.initialized) {{ return NX_ERROR_BUSY; }}", " nx_result_t status;"] - for pin in item["pins"]: + for pin in item.pins: lines += [f" status = nx_stm32g474_pin_alternate(GPIOA, {int(pin['pin'][2:])}u, 2u);", " if (status != NX_SUCCESS) { return status; }"] kind = item.kind @@ -59,5 +60,5 @@ def constructor(item): return Construction( state_type=("nx_timer_capture_controller_t" if capture else "nx_stm32g474_encoder_state_t"), - definitions=lines, initialize=f"prepare_{name}()", - stop=[f"stop_{name}()"], retain_on_failed_init=True) + definitions=lines, initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True) diff --git a/soc/stm32/stm32h743/configure/bindings.py b/soc/stm32/stm32h743/configure/bindings.py index 1004b12de..03ff1d11f 100644 --- a/soc/stm32/stm32h743/configure/bindings.py +++ b/soc/stm32/stm32h743/configure/bindings.py @@ -1,31 +1,32 @@ """Cold assembly selects static contexts; hot I/O uses the one actual ops table.""" -from tools.configure.ir import controller_ir +from tools.configure.calls import Address, DirectCall +from tools.configure.ir import ControllerIR from tools.configure import enhanced_endpoints, uart_formats from tools.configure.providers.common import Construction, fail, kernel_policy_constant def spi_dma_constructor(item, result, board_bindings, routes, devices): """One exact static owner; Platform already holds its gates and pads.""" - from tools.configure.memory import dma_regions + from tools.configure.memory import dma_regions_from_facts name = item.id.replace("-", "_") state = "s_nx_port_" + name children = [child for child in devices if child.controller == item.id] if len(children) != 1: fail("H743 SPI DMA requires one exact PE11 endpoint") child = children[0] - binding = board_bindings[child["cs_binding"]] + binding = board_bindings[child.options.cs_binding] pins = routes[binding["route"]]["pins"] if (len(pins) != 1 or pins[0]["pin"] != "PE11" or binding.get("initial") != 2048 or binding.get("mask") != 2048 or - child.get("mode", 0) != 0 or child.driver != "spi-endpoint"): + child.options.mode != 0 or child.driver != "spi-endpoint"): fail("H743 SPI DMA requires mode0 and PE11 initial-high CS") - rate = child.get("max_hz", item.options.max_hz) + rate = child.options.max_hz if (type(rate) is not int or not 253907 <= rate <= item.options.max_hz or item.options.max_hz * 256 < 65000000): fail("H743 SPI DMA rate is outside its hardware divider") - memory = [region for region in result["memory"] + memory = [region for region in result.memory if region["id"] == "axi-sram" and region["linker"]] - region_lines, count = dma_regions(name, {"memory": memory}, engine="DMA1") + region_lines, count = dma_regions_from_facts(name, memory, engine="DMA1") if count != 1: fail("H743 SPI DMA requires initialized AXI SRAM only") child_name = child.id.replace("-", "_") @@ -84,23 +85,23 @@ def spi_dma_constructor(item, result, board_bindings, routes, devices): r"/** \brief Selected bounded observer; task owns final withdrawal. */", f"void {vector}_IRQHandler(void) {{ {call}; }}"] return Construction("nx_stm32h743_spi_dma_state_t", definitions, - f"prepare_{name}()", [f"stop_{name}()"], True) + DirectCall(f"prepare_{name}"), [DirectCall(f"stop_{name}")], True) def constructor(item, result, board_bindings, routes, devices): """Construct only the exact GPIOB/byte USART3 implementation.""" - from .contract import mode_implementation, validate_selection - item = controller_ir(item, "stm32h743") + from .contract import mode_implementation + if not isinstance(item, ControllerIR): + fail("stm32h743 constructor requires a resolved controller") if item.provider != "stm32h743": fail("H743 constructor cannot reinterpret a foreign provider decision") - validate_selection(item, item) implementation = mode_implementation(item.kind, item.mode) if implementation == "spi_dma": return spi_dma_constructor(item, result, board_bindings, routes, devices) name = item.id.replace("-", "_") state = "s_nx_port_" + name definitions = [] - initialize = f"prepare_{name}()" + initialize = DirectCall(f"prepare_{name}") if item.kind == "gpio": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized) {{ return NX_ERROR_BUSY; }}", @@ -109,7 +110,8 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.output = {'true' if item.mode == 'output' else 'false'};", f" return nx_stm32h743_gpio_initialize(&{state}, {item.options.initial}u);", "}"] - stop = f"nx_stm32h743_gpio_stop(&{state}, {item.options.initial}u)" + stop = DirectCall("nx_stm32h743_gpio_stop", + (Address(state), item.options.initial)) elif item.kind == "uart": options = item.options duplex = item.mode == "dma-duplex-continuous" @@ -128,19 +130,19 @@ def constructor(item, result, board_bindings, routes, devices): state += ".uart" if fifo: definitions += uart_formats.fifo_definitions(item) - plan = result.get("clock_plan") - clock = (plan["domains"]["usart3"]["nominal_hz"] if plan is not None - else result["clock"]["apb1_hz"]) + plan = result.clock_plan + clock = (plan.domains["usart3"].nominal_hz if plan is not None + else result.clock["apb1_hz"]) divisor = (clock + options.baud // 2) // options.baud if not 16 <= divisor <= 0xffff: fail("USART baud is not representable by the selected local kernel") if dma: - from tools.configure.memory import dma_regions + from tools.configure.memory import dma_regions_from_facts # Reachability does not authorize unselected, uninitialized RAM. allowed_memory = {"axi-sram"} if continuous else {"axi-sram", "sram1"} - memory = [region for region in result["memory"] + memory = [region for region in result.memory if region["id"] in allowed_memory and region["linker"]] - region_lines, region_count = dma_regions(name, {"memory": memory}, + region_lines, region_count = dma_regions_from_facts(name, memory, engine="DMA1") if region_count != len(memory) or not 1 <= region_count <= 2: fail("H743 DMA requires selected initialized AXI/SRAM1") @@ -148,7 +150,7 @@ def constructor(item, result, board_bindings, routes, devices): capacity = options.rx_capacity if not receive and (type(capacity) is not int or not 1 <= capacity <= 4096): raise ValueError("H743 USART3 requires an explicit bounded byte ring") - irq = result["irq"] + irq = result.irq if not receive: definitions += [f"static uint8_t s_nx_rx_{name}[{capacity}];"] definitions += [f"static nx_result_t prepare_{name}(void) {{", @@ -159,7 +161,7 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.baud = {options.baud}u;", f" {state}.irq = USART3_IRQn;", f" {state}.profile = {'NX_UART_RX_BLOCKS' if receive else 'NX_UART_RX_BYTES'};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", " nx_result_t status = nx_stm32h743_usart3" + ("_rtscts" if flow else "") + "_pins_prepare();", " if (status != NX_SUCCESS) { return status; }", @@ -209,15 +211,15 @@ def constructor(item, result, board_bindings, routes, devices): definitions += ["void DMA1_Stream0_IRQHandler(void);", r"/** \brief Independent selected TX DMA terminal observation. */", f"void DMA1_Stream0_IRQHandler(void) {{ nx_stm32h743_uart_duplex_dma_irq(&{owner}); }}"] - stop = f"nx_uart_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "spi": - children = [child for child in devices if child["controller"] == item.id] + children = [child for child in devices if child.controller == item.id] if not children: fail("H743 SPI requires a reviewed active-low CS endpoint") - plan = result.get("clock_plan") - source_max = (plan["domains"]["spi4"]["maximum_hz"] if plan is not None + plan = result.clock_plan + source_max = (plan.domains["spi4"].maximum_hz if plan is not None else {32000000: 32500000, 64000000: 65000000}.get( - result["clock"]["apb2_hz"])) + result.clock["apb2_hz"])) if source_max is None: fail("H743 SPI requires an exact reviewed kernel profile") if item.options.max_hz * 256 < source_max: @@ -225,7 +227,7 @@ def constructor(item, result, board_bindings, routes, devices): body = [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active) {{ return NX_ERROR_BUSY; }}", f" {state}.registers = {item.controller};", - f" {state}.clock_hz = {result['clock']['apb2_hz']}u;", + f" {state}.clock_hz = {result.clock['apb2_hz']}u;", f" {state}.poll_limit = 1000000u;"] register = "APB2RSTR" body += [f" {state}.reset = &RCC->{register};", @@ -236,10 +238,10 @@ def constructor(item, result, board_bindings, routes, devices): f" nx_result_t status = nx_spi_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }"] for child in children: - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") cs = f"s_nx_cs_{child_name}" endpoint = f"s_nx_endpoint_{child_name}" - binding = board_bindings[child["cs_binding"]] + binding = board_bindings[child.options.cs_binding] pins = routes[binding["route"]]["pins"] if len(pins) != 1: fail("H743 SPI endpoint CS requires one reviewed pad") @@ -247,12 +249,12 @@ def constructor(item, result, board_bindings, routes, devices): mask = 1 << int(pin[2:]) if binding.get("initial") != mask: fail("H743 SPI active-low CS requires an initial high latch") - rate = child.get("max_hz", item.options.max_hz) + rate = child.options.max_hz if (type(rate) is not int or not 250000 <= rate <= item.options.max_hz or rate * 256 < source_max): fail("H743 SPI endpoint rate is outside its hardware divider") - mode = child.get("mode", 0) + mode = child.options.mode if type(mode) is not int or not 0 <= mode <= 3: fail("H743 SPI endpoint mode must be 0 through 3") definitions += [f"static nx_stm32h743_gpio_state_t {cs};", @@ -271,17 +273,17 @@ def constructor(item, result, board_bindings, routes, devices): f" {endpoint}.mode = {mode}u;"] stops += [f" status = nx_stm32h743_gpio_stop(&{cs}, {mask}u);", " if (status != NX_SUCCESS) { return status; }"] - for pin in item["pins"]: + for pin in item.pins: wire = pin["pin"] body += [f" status = nx_stm32h743_pin_alternate(GPIO{wire[1]}, {int(wire[2:])}u, {pin['af']}u);", " if (status != NX_SUCCESS) { return status; }"] definitions += body + [" return NX_SUCCESS;", "}"] definitions += stops + [" return NX_SUCCESS;", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "pwm": - pin = item["pins"][0]["pin"] + pin = item.pins[0]["pin"] idle = "s_nx_idle_" + name - binding = board_bindings.get(item["binding"], {}) + binding = board_bindings.get(item.binding, {}) mask = 1 << int(pin[2:]) initial = binding.get("initial") if type(initial) is not int or initial not in {0, mask} or binding.get("mask") != mask: @@ -303,9 +305,9 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.tick_hz = {item.options.tick_hz}u;", f" {state}.poll_limit = 1000000u;", f" return nx_stm32h743_pwm_initialize(&{state});", "}"] - stop = f"nx_stm32h743_pwm_close(&{state})" + stop = DirectCall("nx_stm32h743_pwm_close", (Address(state),)) elif item.kind == "i2c": - timing = "0x70f22727" if result["clock"]["apb1_hz"] == 64000000 else "0x30f22727" + timing = "0x70f22727" if result.clock["apb1_hz"] == 64000000 else "0x30f22727" if item.options.timing is not None: timing = f"0x{item.options.timing.timingr:08x}" config = "s_nx_config_" + name @@ -315,16 +317,17 @@ def constructor(item, result, board_bindings, routes, devices): f" .timing = {timing}u, .poll_limit = 1000000u,", " .ignore_master_berr = true, .receive_reload_one = true", "};"] - initialize = f"nx_stm32h743_i2c_prepare(&{state}, &{config})" + initialize = DirectCall("nx_stm32h743_i2c_prepare", + (Address(state), Address(config))) for child in devices: - if child["controller"] != item.id: + if child.controller != item.id: continue - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") endpoint = "s_nx_endpoint_" + child_name - definitions += [f"static nx_i2c_v2_endpoint_state_t {endpoint} = {{ &{state}, {child['address']}u }};", + definitions += [f"static nx_i2c_v2_endpoint_state_t {endpoint} = {{ &{state}, {child.options.address}u }};", *enhanced_endpoints.face(child, endpoint, "nx_i2c_v2_endpoint_ops")] - stop = f"nx_i2c_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_i2c_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "adc": supply = board_bindings.get(item.binding, {}).get("supply_mv") if type(supply) is not int or not 2400 <= supply <= 3300 or not 2000 <= item.options.reference_mv <= supply: @@ -339,12 +342,12 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.supply_mv = {supply}u;", f" {state}.poll_limit = 1000000u;", f" return nx_stm32h743_adc_initialize(&{state});", "}"] - stop = f"nx_stm32h743_adc_stop(&{state})" + stop = DirectCall("nx_stm32h743_adc_stop", (Address(state),)) elif item.kind == "flash": supply = board_bindings.get(item.binding, {}).get("supply_mv") if type(supply) is not int or not 2700 <= supply <= 3600: fail("H743 Flash requires explicit Board supply_mv2700..3600") - if not result["clock_profile"].endswith("-cache-off"): + if not result.clock_profile.endswith("-cache-off"): fail("H743 Flash pulse mode requires the reviewed Cache-off profile") definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.active) {{ return NX_ERROR_BUSY; }}", @@ -353,7 +356,8 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.supply_mv = {supply}u;", f" {state}.poll_limit = 500000000u;", f" return nx_stm32h743_flash_ram_initialize(&{state}, &g_nx_stm32h743_flash_ram_workspace);", "}"] - stop = "nx_stm32h743_flash_ram_stop(&g_nx_stm32h743_flash_ram_workspace)" + stop = DirectCall("nx_stm32h743_flash_ram_stop", + (Address("g_nx_stm32h743_flash_ram_workspace"),)) elif item.kind == "watchdog": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.state.enabled || {state}.lsi_added || {state}.debug_pending) {{ return NX_ERROR_BUSY; }}", @@ -366,10 +370,10 @@ def constructor(item, result, board_bindings, routes, devices): f"static nx_result_t stop_{name}(void) {{", f" return nx_stm32h743_watchdog_stop(&{state});", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "exti": options = item.options - irq = result["irq"] + irq = result.irq line = int(item.controller[-1]) definitions += [f"static nx_exti_event_t s_nx_events_{name}[{options.event_capacity}];", f"static nx_result_t prepare_{name}(void) {{", @@ -382,12 +386,12 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.line = {line}u;", f" {state}.priority = {options.irq.priority}u;", f" {state}.edge = NX_EXTI_{options.edge.upper()};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_stm32h743_exti_initialize(&{state});", "}", f"void EXTI{line}_IRQHandler(void);", r"/** \brief One selected line, one bounded coalesced edge fact. */", f"void EXTI{line}_IRQHandler(void) {{ nx_stm32h743_exti_irq(&{state}); }}"] - stop = f"nx_exti_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_exti_port_stop", (Address(f"s_nx_face_{name}"),)) else: raise ValueError("Unmaintained H743 constructor") return Construction( diff --git a/soc/stm32/stm32h743/configure/contract.py b/soc/stm32/stm32h743/configure/contract.py index dcce2f875..0fb863280 100644 --- a/soc/stm32/stm32h743/configure/contract.py +++ b/soc/stm32/stm32h743/configure/contract.py @@ -246,4 +246,4 @@ def validate_endpoint_clock(speed, *, clock_plan): def configuration_header(result): - return [f'#define NEXUS_SOC_CACHE_MASK {cache_mask(result["clock_profile"])}u'] + return [f'#define NEXUS_SOC_CACHE_MASK {cache_mask(result.clock_profile)}u'] diff --git a/soc/stm32/stm32h743/configure/emission.py b/soc/stm32/stm32h743/configure/emission.py index 72f4f48f2..6823c8dea 100644 --- a/soc/stm32/stm32h743/configure/emission.py +++ b/soc/stm32/stm32h743/configure/emission.py @@ -60,13 +60,13 @@ def shared_irqs(result): def _ports(result): ports = {} for item in result.controllers: - for pin in item["pins"]: + for pin in item.pins: bank = pin["pin"][1] ports.setdefault(bank, set()).add(int(pin["pin"][2:])) - for item in result.get("devices", ()): - binding = result["board_bindings"].get(item.get("cs_binding")) + for item in result.devices: + binding = result.board_bindings.get(getattr(item.options, "cs_binding", None)) if binding is not None: - for pin in result["routes"][binding["route"]]["pins"]: + for pin in result.routes[binding["route"]]["pins"]: ports.setdefault(pin["pin"][1], set()).add(int(pin["pin"][2:])) return tuple((bank, tuple(sorted(pins))) for bank, pins in sorted(ports.items())) @@ -172,7 +172,7 @@ def state_declarations(result): def start_system(result): from .contract import cache_mask - cache = f'{cache_mask(result["clock_profile"])}u' + cache = f'{cache_mask(result.clock_profile)}u' if result.clock_plan is not None: return preflight.emit(result, dma_conditions=dma_admission_conditions) + [ ' result.primary = nx_stm32h743_validate_identity();', @@ -194,7 +194,7 @@ def start_system(result): source = preflight.emit(result, dma_conditions=dma_admission_conditions) + [ ' result.primary = nx_stm32h743_validate_identity();', ' if (result.primary != NX_SUCCESS) { return result; }', - f' nx_stm32h743_start_result_t system = nx_stm32h743_system_start(&g_nx_stm32h743_system, {result["clock"]["core_hz"]}u, {cache}, 1000000u);', + f' nx_stm32h743_start_result_t system = nx_stm32h743_system_start(&g_nx_stm32h743_system, {result.clock["core_hz"]}u, {cache}, 1000000u);', ' if (system.primary != NX_SUCCESS) {', ' result.primary = system.primary;', ' result.cleanup = system.cleanup;', diff --git a/soc/stm32/stm32h743/configure/memory.py b/soc/stm32/stm32h743/configure/memory.py index 8f41e88c6..4ffe3c52d 100644 --- a/soc/stm32/stm32h743/configure/memory.py +++ b/soc/stm32/stm32h743/configure/memory.py @@ -2,7 +2,7 @@ def linker_fragments(result): - if not any(item["kind"] == "flash" for item in result["controllers"]): + if not any(item.kind == "flash" for item in result.controllers): return ("", "", "", "") flash_image_record = """ .nx_flash_image (READONLY) : ALIGN(4) { g_nx_stm32h743_flash_image = .; diff --git a/soc/stm32/stm32h743/configure/preflight.py b/soc/stm32/stm32h743/configure/preflight.py index ae863f336..918cd5567 100644 --- a/soc/stm32/stm32h743/configure/preflight.py +++ b/soc/stm32/stm32h743/configure/preflight.py @@ -53,21 +53,21 @@ def selected_preflight(result, *, dma_conditions=None): source += _reject(f'(NX_{hook}_READ(&RCC->{gate_register}) & RCC_{gate_register}_{uart}EN) != 0u') source += _reject(f'(NX_{hook}_READ(&RCC->{mux_register}) & {mux}) != 0u') source += _reject(f'!NX_{hook}_IRQ_AVAILABLE((IRQn_Type){uart}_IRQn)') - if item.get('mode') in {'dma-tx', 'rx-dma-idle', 'rx-dma-continuous', 'dma-duplex-continuous'}: - lanes = (0, 1) if item.get('mode') == 'dma-duplex-continuous' else (0,) if item.get('mode') == 'dma-tx' else (1,) + if item.mode in {'dma-tx', 'rx-dma-idle', 'rx-dma-continuous', 'dma-duplex-continuous'}: + lanes = (0, 1) if item.mode == 'dma-duplex-continuous' else (0,) if item.mode == 'dma-tx' else (1,) for lane in lanes: source += _reject(f'!NX_H743_IRQ_AVAILABLE(DMA1_Stream{lane}_IRQn)') source.append(' if ((NX_H743_READ(&RCC->AHB1ENR) & RCC_AHB1ENR_DMA1EN) != 0u) {') for lane in lanes: source += _reject(f'NX_H743_READ(&DMA1_Stream{lane}->CR) != 0u || NX_H743_READ(&DMAMUX1_Channel{lane}->CCR) != 0u', indent=8) source.append(' }') - for pin in item['pins']: + for pin in item.pins: name = pin['pin'] pads.setdefault(name[1], set()).add(int(name[2:])) for item in result.devices: - binding = result['board_bindings'].get(item.get('cs_binding')) + binding = result.board_bindings.get(getattr(item.options, "cs_binding", None)) if binding is not None: - for pin in result['routes'][binding['route']]['pins']: + for pin in result.routes[binding['route']]['pins']: name = pin['pin'] pads.setdefault(name[1], set()).add(int(name[2:])) for port, numbers in sorted(pads.items()): diff --git a/soc/stm32/stm32h743/configure/runtime_power.py b/soc/stm32/stm32h743/configure/runtime_power.py index fde1b1c65..f2fa7ca18 100644 --- a/soc/stm32/stm32h743/configure/runtime_power.py +++ b/soc/stm32/stm32h743/configure/runtime_power.py @@ -9,15 +9,15 @@ def resolve_runtime_power(value, result): value['mode'] != 'fixed-run' or value['controllers'] != ['bus4']): fail('power requires fixed-run and exactly controllers=["bus4"]') - controllers = result['controllers'] - if (result['soc_family'] != 'stm32h743' or result['backend'] != 'baremetal' or - result['clock_profile'] != 'hsi64-fixed-cache-off' or - len(controllers) != 1 or controllers[0]['id'] != 'bus4' or - controllers[0]['kind'] != 'spi' or controllers[0]['mode'] != 'dma' or - controllers[0]['controller'] != 'SPI4' or - len(result['devices']) != 1 or - result['devices'][0]['controller'] != 'bus4' or - result['devices'][0].get('mode', 0) != 0): + controllers = result.controllers + if (result.soc_family != 'stm32h743' or result.backend != 'baremetal' or + result.clock_profile != 'hsi64-fixed-cache-off' or + len(controllers) != 1 or controllers[0].id != 'bus4' or + controllers[0].kind != 'spi' or controllers[0].mode != 'dma' or + controllers[0].controller != 'SPI4' or + len(result.devices) != 1 or + result.devices[0].controller != 'bus4' or + result.devices[0].options.mode != 0): fail('power supports only baremetal H743 fixed64 cache-off SPI4 finite DMA') return RuntimePowerIR('fixed-run', ('bus4',)) diff --git a/soc/stm32/stm32h743/configure/selected_support.py b/soc/stm32/stm32h743/configure/selected_support.py index ffdafa423..a84b42c2e 100644 --- a/soc/stm32/stm32h743/configure/selected_support.py +++ b/soc/stm32/stm32h743/configure/selected_support.py @@ -34,15 +34,15 @@ def silicon_policy(result, item=None): if item.kind != "spi" or item.mode != "dma": return None if (result.backend != "baremetal" or - result["clock_profile"] not in { + result.clock_profile not in { "hsi64-fixed-cache-off", "hsi64-fixed-cache-on"}): fail("Selected SPI DMA silicon expectation requires its exact profile") - cache = "on" if result["clock_profile"].endswith("-cache-on") else "off" - return SiliconPolicy(result["part"], result.soc_family, + cache = "on" if result.clock_profile.endswith("-cache-on") else "off" + return SiliconPolicy(result.part, result.soc_family, f"spi4-dma-hsi64-cache{cache}-dev450-v2003-trim64", (0x2003,), "selected_spi_dma_mode") plan = result.clock_plan if plan is None: return None - return SiliconPolicy(result["part"], result.soc_family, plan.profile, + return SiliconPolicy(result.part, result.soc_family, plan.profile, plan.reviewed_revision_ids, "selected_clock_profile") diff --git a/soc/stm32/stm32u575/configure/bindings.py b/soc/stm32/stm32u575/configure/bindings.py index caaa13e47..f8790619c 100644 --- a/soc/stm32/stm32u575/configure/bindings.py +++ b/soc/stm32/stm32u575/configure/bindings.py @@ -1,16 +1,18 @@ """Cold assembly selects static contexts; hot I/O uses the one actual ops table.""" from tools.configure.ir import controller_ir +from tools.configure.calls import DirectCall, Address from tools.configure import enhanced_endpoints, uart_formats from tools.configure.providers.common import Construction, fail, kernel_policy_constant def _gpdma_uart(item, result, state, name, capacity): """One selected finite-TX extension; ordinary byte state stays unchanged.""" - options, irq = item.options, result["irq"] + options, irq = item.options, result.irq facts = "s_nx_uart_gpdma_" + name chain = item.mode == "dma-chain-tx" provider = "uart_chain" if chain else "uart_gpdma" node = "s_nx_uart_chain_nodes_" + name + config = "s_nx_uart_config_" + name definitions = [f"static uint8_t s_nx_rx_{name}[{capacity}];", *([f"_Alignas(32) static nx_stm32u575_uart_chain_node_t {node}[2];"] if chain else []), @@ -20,21 +22,13 @@ def _gpdma_uart(item, result, state, name, capacity): " .irq = GPDMA1_Channel0_IRQn, .request = 25u,", f" .priority = {options.irq.priority}u" + (f" }}, .nodes = {node} }};" if chain else " };"), - f"static nx_result_t prepare_{name}(void) {{", - f" if ({state}.uart.retained || {state}.uart.initialized || {state}.uart.active != NULL || {state}.uart.wake != NULL || {state}.uart.wake_publishers != 0u) {{ return NX_ERROR_BUSY; }}", - f" {state}.uart.registers = USART1;", - f" {state}.uart.rx_storage = s_nx_rx_{name};", - f" {state}.uart.rx_capacity = {capacity}u;", - f" {state}.uart.baud = {options.baud}u;", - f" {state}.uart.irq = USART1_IRQn;", - f" {state}.uart.profile = NX_UART_RX_BYTES;", - f" {state}.uart.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", - f" {state}.dma_irq_policy = {state}.uart.irq_policy;", - f" {state}.facts = &{facts}" + (".base;" if chain else ";"), - " nx_result_t status = nx_stm32u575_usart1_pins_prepare();", - " if (status != NX_SUCCESS) { return status; }", - f" return nx_stm32u575_{provider}_initialize(&{state}, 4000000u, 1000000u);", - "}", "void USART1_IRQHandler(void);", + f"static const nx_stm32u575_uart_gpdma_config_t {config} = {{", + f" .facts = &{facts}" + (".base," if chain else ","), + f" .rx_storage = s_nx_rx_{name}, .rx_capacity = {capacity}u,", + f" .baud = {options.baud}u,", + f" .irq_policy = {{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }},", + " .clock_hz = 4000000u, .poll_limit = 1000000u", + "};", "void USART1_IRQHandler(void);", r"/** \brief One selected RX-byte and independent USART wire fact source. */", "void USART1_IRQHandler(void) { " f"nx_stm32u575_{provider}_uart_irq(&{state}); }}", @@ -43,14 +37,16 @@ def _gpdma_uart(item, result, state, name, capacity): "void GPDMA1_Channel0_IRQHandler(void) { " f"nx_stm32u575_{provider}_irq(&{state}); }}"] return Construction(state_type="nx_stm32u575_uart_gpdma_state_t", - definitions=definitions, initialize=f"prepare_{name}()", - stop=[f"nx_uart_port_stop(&s_nx_face_{name})"], + definitions=definitions, + initialize=DirectCall(f"nx_stm32u575_{provider}_prepare", + (Address(state), Address(config))), + stop=[DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),))], retain_on_failed_init=True) def _lptim(item, result): """Construct the exact pinless RUN timer beside the other static faces.""" - if result["backend"] != "baremetal": + if result.backend != "baremetal": fail("U575 LPTIM RUN reference requires baremetal") name = item.id.replace("-", "_") state = f"s_nx_port_{name}" @@ -61,17 +57,17 @@ def _lptim(item, result): r"/** \brief Exact UE observation; settlement remains task-owned. */", f"void LPTIM2_IRQHandler(void) {{ nx_stm32u575_lptim_irq(&{state}); }}"] return Construction(state_type="nx_stm32u575_lptim_state_t", - definitions=definitions, initialize=f"prepare_{name}()", - stop=[f"nx_lptim_port_stop(&s_nx_face_{name})"], + definitions=definitions, initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall("nx_lptim_port_stop", (Address(f"s_nx_face_{name}"),))], retain_on_failed_init=True) def constructor(item, result, board_bindings, routes, devices): """Construct the selected exact static provider and cold resource leases.""" - from .contract import mode_implementation, validate_selection + from .contract import mode_implementation item = controller_ir(item, "stm32u575") if item.kind == "rtc": - if result["backend"] != "baremetal": + if result.backend != "baremetal": fail("U575 retained RTC RUN reference requires baremetal") name = item.id.replace("-", "_") state = f"s_nx_port_{name}" @@ -83,19 +79,18 @@ def constructor(item, result, board_bindings, routes, devices): f" if ({state}.registers == NULL) {{ {state}.registers = RTC; {state}.power = PWR; }}", f" return nx_stm32u575_rtc_initialize(&{state}, &clock_face_{name});", "}"] return Construction(state_type="nx_stm32u575_rtc_state_t", - definitions=definitions, initialize=f"prepare_{name}()", - stop=[f"nx_rtc_port_stop(&s_nx_face_{name})"], + definitions=definitions, initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall("nx_rtc_port_stop", (Address(f"s_nx_face_{name}"),))], retain_on_failed_init=False) if item.kind == "lptim": return _lptim(item, result) if item.provider != "stm32u575": fail("U575 constructor cannot reinterpret a foreign provider decision") - validate_selection(item, item) implementation = mode_implementation(item.kind, item.mode) name = item.id.replace("-", "_") state = "s_nx_port_" + name definitions = [] - initialize = f"prepare_{name}()" + initialize = DirectCall(f"prepare_{name}") if item.kind == "gpio": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized) {{ return NX_ERROR_BUSY; }}", @@ -104,7 +99,8 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.output = {'true' if item.mode == 'output' else 'false'};", f" return nx_stm32u575_gpio_initialize(&{state}, {item.options.initial}u);", "}"] - stop = f"nx_stm32u575_gpio_stop(&{state}, {item.options.initial}u)" + stop = DirectCall("nx_stm32u575_gpio_stop", + (Address(state), item.options.initial)) elif item.kind == "uart": options = item.options capacity = options.rx_capacity @@ -118,7 +114,7 @@ def constructor(item, result, board_bindings, routes, devices): if fifo: definitions += uart_formats.fifo_definitions(item) state += ".uart" - irq = result["irq"] + irq = result.irq definitions += [f"static uint8_t s_nx_rx_{name}[{capacity}];", f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active != NULL || {state}.wake != NULL || {state}.wake_publishers != 0u) {{ return NX_ERROR_BUSY; }}", @@ -128,7 +124,7 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.baud = {options.baud}u;", f" {state}.irq = {item.controller}_IRQn;", f" {state}.profile = NX_UART_RX_BYTES;", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" nx_result_t status = nx_stm32u575_{item.controller.lower()}" + ("_rtscts" if flow else "") + "_pins_prepare();", " if (status != NX_SUCCESS) { return status; }"] @@ -144,9 +140,9 @@ def constructor(item, result, board_bindings, routes, devices): definitions += ["}", f"void {item.controller}_IRQHandler(void);", "/** \\brief " + brief + " */", f"void {item.controller}_IRQHandler(void) {{ nx_stm32u575_{handler}_irq(&{owner}); }}"] - stop = f"nx_uart_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "spi": - children = [child for child in devices if child["controller"] == item.id] + children = [child for child in devices if child.controller == item.id] if not children: fail("U575 SPI requires a reviewed active-low CS endpoint") body = [f"static nx_result_t prepare_{name}(void) {{", @@ -163,10 +159,10 @@ def constructor(item, result, board_bindings, routes, devices): f" nx_result_t status = nx_spi_port_stop(&s_nx_face_{name});", " if (status != NX_SUCCESS) { return status; }"] for child in children: - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") cs = f"s_nx_cs_{child_name}" endpoint = f"s_nx_endpoint_{child_name}" - binding = board_bindings[child["cs_binding"]] + binding = board_bindings[child.options.cs_binding] pins = routes[binding["route"]]["pins"] if len(pins) != 1: fail("U575 SPI endpoint CS requires one reviewed pad") @@ -174,10 +170,10 @@ def constructor(item, result, board_bindings, routes, devices): mask = 1 << int(pin[2:]) if binding.get("initial") != mask: fail("U575 SPI active-low CS requires an initial high latch") - rate = child.get("max_hz", item.options.max_hz) + rate = child.options.max_hz if type(rate) is not int or not 15625 <= rate <= item.options.max_hz: fail("U575 SPI endpoint rate is outside its hardware divider") - mode = child.get("mode", 0) + mode = child.options.mode if type(mode) is not int or not 0 <= mode <= 3: fail("U575 SPI endpoint mode must be 0 through 3") definitions += [f"static nx_stm32u575_gpio_state_t {cs};", @@ -196,17 +192,17 @@ def constructor(item, result, board_bindings, routes, devices): f" {endpoint}.mode = {mode}u;"] stops += [f" status = nx_stm32u575_gpio_stop(&{cs}, {mask}u);", " if (status != NX_SUCCESS) { return status; }"] - for pin in item["pins"]: + for pin in item.pins: wire = pin["pin"] body += [f" status = nx_stm32u575_pin_alternate(GPIO{wire[1]}, {int(wire[2:])}u, {pin['af']}u);", " if (status != NX_SUCCESS) { return status; }"] definitions += body + [" return NX_SUCCESS;", "}"] definitions += stops + [" return NX_SUCCESS;", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") elif item.kind == "pwm": - pin = item["pins"][0]["pin"] + pin = item.pins[0]["pin"] idle = "s_nx_idle_" + name - binding = board_bindings.get(item["binding"], {}) + binding = board_bindings.get(item.binding, {}) mask = 1 << int(pin[2:]) initial = binding.get("initial") if type(initial) is not int or initial not in {0, mask} or binding.get("mask") != mask: @@ -228,10 +224,10 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.tick_hz = {item.options.tick_hz}u;", f" {state}.poll_limit = 1000000u;", f" return nx_stm32u575_pwm_initialize(&{state});", "}"] - stop = f"nx_stm32u575_pwm_close(&{state})" + stop = DirectCall("nx_stm32u575_pwm_close", (Address(state),)) elif item.kind == "exti": options = item.options - irq = result["irq"] + irq = result.irq line = int(item.controller[-1]) definitions += [f"static nx_exti_event_t s_nx_events_{name}[{options.event_capacity}];", f"static nx_result_t prepare_{name}(void) {{", @@ -244,12 +240,12 @@ def constructor(item, result, board_bindings, routes, devices): f" {state}.line = {line}u;", f" {state}.priority = {options.irq.priority}u;", f" {state}.edge = NX_EXTI_{options.edge.upper()};", - f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq['external_count']}u, {irq['priority_bits']}u, {irq['syscall_priority'] or 0}u }};", + f" {state}.irq_policy = (nx_irq_policy_t){{ {kernel_policy_constant(result)}, {irq.external_count}u, {irq.priority_bits}u, {irq.syscall_priority or 0}u }};", f" return nx_stm32u575_exti_initialize(&{state});", "}", f"void EXTI{line}_IRQHandler(void);", r"/** \brief One selected line, one bounded coalesced edge fact. */", f"void EXTI{line}_IRQHandler(void) {{ nx_stm32u575_exti_irq(&{state}); }}"] - stop = f"nx_exti_port_stop(&s_nx_face_{name})" + stop = DirectCall("nx_exti_port_stop", (Address(f"s_nx_face_{name}"),)) elif item.kind == "i2c": clock = f"s_nx_i2c_clock_{name}" config = f"s_nx_config_{name}" @@ -262,18 +258,20 @@ def constructor(item, result, board_bindings, routes, devices): f" .timing = {timing}u, .poll_limit = 1000000u,", " .ignore_master_berr = true, .receive_reload_one = false", "};"] - initialize = f"nx_stm32u575_i2c_prepare(&{state}, &{clock}, &{config})" - children = [child for child in devices if child["controller"] == item.id] + initialize = DirectCall("nx_stm32u575_i2c_prepare", + (Address(state), Address(clock), Address(config))) + children = [child for child in devices if child.controller == item.id] for child in children: - child_name = child["id"].replace("-", "_") + child_name = child.id.replace("-", "_") endpoint = f"s_nx_endpoint_{child_name}" - address = child["address"] + address = child.options.address if type(address) is not int or not 8 <= address <= 119: fail("U575 I2C requires an explicit nonreserved7bit address") definitions += [f"static nx_stm32u575_i2c_endpoint_state_t {endpoint} = {{ &{state}, {address}u }};", *enhanced_endpoints.face(child, endpoint, "nx_i2c_v2_endpoint_ops")] - stop = f"nx_stm32u575_i2c_close(&{state}, &{clock})" + stop = DirectCall("nx_stm32u575_i2c_close", + (Address(state), Address(clock))) elif item.kind == "adc": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active) {{ return NX_ERROR_BUSY; }}", @@ -286,14 +284,14 @@ def constructor(item, result, board_bindings, routes, devices): " nx_arch_dsb();", " if ((NX_U575_READ(&GPIOC->MODER) & 3u) != 3u || (NX_U575_READ(&GPIOC->PUPDR) & 3u) != 0u) { return NX_ERROR_IO; }", f" return nx_stm32u575_adc_initialize(&{state});", "}"] - stop = f"nx_stm32u575_adc_close(&{state})" + stop = DirectCall("nx_stm32u575_adc_close", (Address(state),)) elif item.kind == "flash": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.retained || {state}.initialized || {state}.active) {{ return NX_ERROR_BUSY; }}", f" {state}.registers = FLASH;", f" {state}.memory = (volatile uint8_t*)0x08000000u;", f" return nx_stm32u575_flash_initialize(&{state});", "}"] - stop = f"nx_stm32u575_flash_close(&{state})" + stop = DirectCall("nx_stm32u575_flash_close", (Address(state),)) elif item.kind == "watchdog": definitions += [f"static nx_result_t prepare_{name}(void) {{", f" if ({state}.state.enabled || {state}.lsi_added || {state}.debug_pending) {{ return NX_ERROR_BUSY; }}", @@ -306,7 +304,7 @@ def constructor(item, result, board_bindings, routes, devices): f"static nx_result_t stop_{name}(void) {{", f" return nx_stm32u575_watchdog_stop(&{state});", "}"] - stop = f"stop_{name}()" + stop = DirectCall(f"stop_{name}") else: raise ValueError("Unmaintained U575 constructor") return Construction( diff --git a/soc/stm32/stm32u575/configure/contract.py b/soc/stm32/stm32u575/configure/contract.py index 34b3808b6..0f53d9c5a 100644 --- a/soc/stm32/stm32u575/configure/contract.py +++ b/soc/stm32/stm32u575/configure/contract.py @@ -267,8 +267,8 @@ def validate_mode_context(selection, route, *, backend, clock_profile): def validate_user_io_context(assembly, part): - if (part != "STM32U575ZIT6Q" or assembly["clock_profile"] != "msis4" or - assembly["layout"] is not None or "power" in assembly): + if (part != "STM32U575ZIT6Q" or assembly.clock_profile != "msis4" or + assembly.layout is not None or assembly.power is not None): common.fail("GPIO user I/O requires the exact U575 TZEN0 MSIS4 reference") diff --git a/soc/stm32/stm32u575/configure/emission.py b/soc/stm32/stm32u575/configure/emission.py index d49d60c89..780a1ccb8 100644 --- a/soc/stm32/stm32u575/configure/emission.py +++ b/soc/stm32/stm32u575/configure/emission.py @@ -20,7 +20,7 @@ def includes(result): for item in result.controllers) else [] if any(item.kind == "rtc" for item in result.controllers): if any(pin["pin"] in {"PC14", "PC15"} - for item in result.controllers for pin in item["pins"]): + for item in result.controllers for pin in item.pins): raise ValueError("Retained LSE RTC conflicts with selected PC14/PC15 owner") selected.append('#include "stm32u575_rtc.h"') if any(item.kind == "lptim" for item in result.controllers): @@ -53,16 +53,15 @@ def shared_irqs(result): def _leases(result): groups = {} for item in result.controllers: - for pin in item['pins']: + for pin in item.pins: name, number = pin['pin'][1], int(pin['pin'][2:]) groups.setdefault(name, set()).add(number) - if "claims" in result: - owners = {item.id for item in result.controllers} - owners.update(item["id"] for item in result["devices"]) - for claim in owned_claims(result, owners): - if claim["key"].startswith("pin:"): - pin = claim["key"][4:] - groups.setdefault(pin[1], set()).add(int(pin[2:])) + owners = {item.id for item in result.controllers} + owners.update(item.id for item in result.devices) + for claim in owned_claims(result, owners): + if claim.key.startswith("pin:"): + pin = claim.key[4:] + groups.setdefault(pin[1], set()).add(int(pin[2:])) return [(name, sum(1 << n for n in numbers), sum(3 << (2 * n) for n in numbers), sum(15 << (4 * n) for n in numbers if n < 8), diff --git a/soc/stm32/stm32u575/configure/preflight.py b/soc/stm32/stm32u575/configure/preflight.py index db58c049a..60f526d4b 100644 --- a/soc/stm32/stm32u575/configure/preflight.py +++ b/soc/stm32/stm32u575/configure/preflight.py @@ -43,7 +43,7 @@ def selected_preflight(result, *, dma_conditions=None): source += _reject(f'(NX_{hook}_READ(&RCC->{gate_register}) & RCC_{gate_register}_{uart}EN) != 0u') source += _reject(f'(NX_{hook}_READ(&RCC->{mux_register}) & {mux}) != 0u') source += _reject(f'!NX_{hook}_IRQ_AVAILABLE((IRQn_Type){uart}_IRQn)') - if item.get('mode') in {'dma-tx', 'dma-chain-tx'}: + if item.mode in {'dma-tx', 'dma-chain-tx'}: source += _reject('!NX_U575_IRQ_AVAILABLE(GPDMA1_Channel0_IRQn)') elif item.kind in {'spi', 'pwm', 'i2c', 'adc'}: register = 'APB2ENR' if item.controller == 'SPI1' else 'AHB3ENR' if item.controller == 'ADC4' else 'APB1ENR1' @@ -61,16 +61,15 @@ def selected_preflight(result, *, dma_conditions=None): line = int(item.controller[-1]) source += _reject(f'!NX_U575_IRQ_AVAILABLE((IRQn_Type)EXTI{line}_IRQn)') source += _reject(f'((NX_U575_READ(&EXTI->IMR1) | NX_U575_READ(&EXTI->EMR1) | NX_U575_READ(&EXTI->RTSR1) | NX_U575_READ(&EXTI->FTSR1) | NX_U575_READ(&EXTI->RPR1) | NX_U575_READ(&EXTI->FPR1)) & {1 << line}u) != 0u') - for pin in item['pins']: + for pin in item.pins: name = pin['pin'] pads.setdefault(name[1], set()).add(int(name[2:])) - if 'claims' in result: - owners = {item.id for item in result.controllers} - owners.update((item['id'] for item in result['devices'])) - for claim in owned_claims(result, owners): - if claim['key'].startswith('pin:'): - pin = claim['key'][4:] - pads.setdefault(pin[1], set()).add(int(pin[2:])) + owners = {item.id for item in result.controllers} + owners.update((item.id for item in result.devices)) + for claim in owned_claims(result, owners): + if claim.key.startswith('pin:'): + pin = claim.key[4:] + pads.setdefault(pin[1], set()).add(int(pin[2:])) for port, numbers in sorted(pads.items()): mask = sum((1 << number for number in numbers)) if port == 'G' and mask & ~3: diff --git a/soc/stm32/stm32u575/drivers/uart_dma_chain.c b/soc/stm32/stm32u575/drivers/uart_dma_chain.c index 10712ebb6..023153685 100644 --- a/soc/stm32/stm32u575/drivers/uart_dma_chain.c +++ b/soc/stm32/stm32u575/drivers/uart_dma_chain.c @@ -75,6 +75,32 @@ static bool chain_layout(const nx_stm32u575_uart_gpdma_state_t* s) { sizeof(*facts)); } +static bool chain_cold_layout(const nx_stm32u575_uart_gpdma_state_t* s, + const nx_stm32u575_uart_gpdma_config_t* config) { + const nx_stm32u575_uart_chain_facts_t* facts = + (const nx_stm32u575_uart_chain_facts_t*)config->facts; + if (facts->nodes == NULL || (uintptr_t)facts->nodes % 32U != 0U || + (uintptr_t)facts->nodes > UINTPTR_MAX - CHAIN_BYTES) + return false; + const uintptr_t begin = NX_U575_CHAIN_NODE_BUS(facts->nodes); + if (begin < CHAIN_SRAM_BEGIN || begin >= CHAIN_SRAM_END || + begin % 32U != 0U || CHAIN_BYTES > CHAIN_SRAM_END - begin || + (begin >> 16U) != ((begin + CHAIN_BYTES - 1U) >> 16U)) + return false; + const void* spans[2] = {facts->nodes, (const void*)begin}; + for (unsigned i = 0U; i < 2U; ++i) { + if (chain_overlap(spans[i], CHAIN_BYTES, s, sizeof(*s)) || + chain_overlap(spans[i], CHAIN_BYTES, config, sizeof(*config)) || + chain_overlap(spans[i], CHAIN_BYTES, facts, sizeof(*facts)) || + chain_overlap(spans[i], CHAIN_BYTES, config->rx_storage, + config->rx_capacity) || + chain_overlap(spans[i], CHAIN_BYTES, &g_nx_stm32u575_system, + sizeof(g_nx_stm32u575_system))) + return false; + } + return true; +} + static uint32_t chain_address(const nx_stm32u575_uart_chain_node_t* node) { return (uint32_t)NX_U575_CHAIN_NODE_BUS(node) & DMA_CLLR_LA; } @@ -144,11 +170,14 @@ static bool chain_withdraw(nx_stm32u575_uart_gpdma_state_t* s) { #define NX_U575_GPDMA_COUNT(request) \ ((uint32_t)((request)->length > UINT16_MAX ? UINT16_MAX \ : (request)->length)) -#define NX_U575_GPDMA_TCEM DMA_CTR2_TCEM -#define NX_U575_GPDMA_WITHDRAW(s) chain_withdraw(s) -#define nx_stm32u575_uart_gpdma_initialize nx_stm32u575_uart_chain_initialize -#define nx_stm32u575_uart_gpdma_irq nx_stm32u575_uart_chain_irq -#define nx_stm32u575_uart_gpdma_uart_irq nx_stm32u575_uart_chain_uart_irq +#define NX_U575_GPDMA_TCEM DMA_CTR2_TCEM +#define NX_U575_GPDMA_WITHDRAW(s) chain_withdraw(s) +#define NX_U575_GPDMA_FACTS_BYTES sizeof(nx_stm32u575_uart_chain_facts_t) +#define NX_U575_GPDMA_COLD_VALID(s, config) chain_cold_layout((s), (config)) +#define nx_stm32u575_uart_gpdma_prepare nx_stm32u575_uart_chain_prepare +#define nx_stm32u575_uart_gpdma_initialize nx_stm32u575_uart_chain_initialize +#define nx_stm32u575_uart_gpdma_irq nx_stm32u575_uart_chain_irq +#define nx_stm32u575_uart_gpdma_uart_irq nx_stm32u575_uart_chain_uart_irq #include "stm32u575_uart_gpdma_impl.h" const nx_uart_ops_t nx_stm32u575_uart_chain_ops = { diff --git a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma.h b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma.h index a0ea6c3f9..966e88436 100644 --- a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma.h +++ b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma.h @@ -27,6 +27,20 @@ typedef struct { uint8_t priority; } nx_stm32u575_uart_gpdma_facts_t; +/** \brief Immutable cold assembly; no descriptor is retained at runtime. + * \note Facts and RX storage outlive the owner. The descriptor itself is read + * only during prepare; it must not alias mutable state or RX storage. + */ +typedef struct { + const nx_stm32u575_uart_gpdma_facts_t* facts; + uint8_t* rx_storage; + size_t rx_capacity; + uint32_t baud; + nx_irq_policy_t irq_policy; + uint32_t clock_hz; + uint32_t poll_limit; +} nx_stm32u575_uart_gpdma_config_t; + /** \brief Selected-only extension, one UART hardware owner and wake sink. * \note The ordinary IRQ-byte state/ops are unchanged. The prefix owns IRQ RX * metadata but ordinary byte TX IRQ/service must not process DMA TX. @@ -48,6 +62,18 @@ typedef struct { extern const nx_uart_ops_t nx_stm32u575_uart_gpdma_ops; +/** \brief Bind a cold owner and prepare its exact USART1 pins and hardware. + * \param[in,out] port: Serialized privileged task owner without retained loans. + * \param[in] config: Explicit resources, byte ring and finite ACK budget. + * \return Software rejection leaves binding unchanged. Hardware failure may + * retain effects and requires the ordinary checked stop path. + * \note Platform owns the enclosing pin lease. Keep it until checked whole + * stop; prepare does not revoke a request or an outstanding publisher. + */ +nx_result_t +nx_stm32u575_uart_gpdma_prepare(nx_stm32u575_uart_gpdma_state_t* port, + const nx_stm32u575_uart_gpdma_config_t* config); + /** \brief Cold exclusive TZEN0 USART1 owner with persistent DMAT request gate. * \param[in,out] port: Static state, exact facts and disjoint byte RX ring. * \param[in] clock_hz: Existing fixed PCLK4MHz profile only. diff --git a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_chain.h b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_chain.h index 385567121..86f04ddac 100644 --- a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_chain.h +++ b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_chain.h @@ -38,6 +38,16 @@ typedef struct { extern const nx_uart_ops_t nx_stm32u575_uart_chain_ops; +/** \brief Bind exact chain resources before any pin or UART effects. + * \param[in,out] port: Cold privileged task owner without retained loans. + * \param[in] config: Facts point at chain facts.base and two disjoint nodes. + * \return Invalid or busy without rebinding; partial hardware failure retains + * the ordinary whole-stop responsibility and enclosing pin lease. + */ +nx_result_t +nx_stm32u575_uart_chain_prepare(nx_stm32u575_uart_gpdma_state_t* port, + const nx_stm32u575_uart_gpdma_config_t* config); + /** \brief Cold TZEN0/MSIS4 USART1 finite hardware-chain owner. * \param[in,out] port: Same private prefix; facts points to chain facts.base. * \param[in] clock_hz: Existing 4 MHz PCLK profile. diff --git a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_impl.h b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_impl.h index ac83eb7f7..82db18f2b 100644 --- a/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_impl.h +++ b/soc/stm32/stm32u575/private/stm32u575_uart_gpdma_impl.h @@ -25,6 +25,10 @@ #define NX_U575_GPDMA_TCEM 0U #define NX_U575_GPDMA_WITHDRAW(s) true #endif +#ifndef NX_U575_GPDMA_FACTS_BYTES +#define NX_U575_GPDMA_FACTS_BYTES sizeof(nx_stm32u575_uart_gpdma_facts_t) +#define NX_U575_GPDMA_COLD_VALID(s, config) true +#endif #if !defined(NX_U575_GPDMA_ENABLE) || !defined(NX_U575_GPDMA_SUSPEND) || \ !defined(NX_U575_GPDMA_RESET) @@ -807,4 +811,71 @@ nx_stm32u575_uart_gpdma_initialize(nx_stm32u575_uart_gpdma_state_t* s, return result; } +nx_result_t nx_stm32u575_uart_gpdma_prepare( + nx_stm32u575_uart_gpdma_state_t* s, + const nx_stm32u575_uart_gpdma_config_t* config) { + if (!task_context()) + return NX_ERROR_CONTEXT; + if (s == NULL || !span(config, sizeof(*config)) || + overlap(config, sizeof(*config), s, sizeof(*s))) + return NX_ERROR_INVALID; + nx_stm32u575_uart_state_t* u = &s->uart; + /* Check old ownership before reading a proposed binding or changing any + * field. A stopped owner can be reused; retained work can only be joined. + */ + if (u->retained || u->initialized || u->active != NULL || u->wake != NULL || + u->wake_publishers != 0U || s->effects != 0U) + return NX_ERROR_BUSY; + if (!span(config->facts, NX_U575_GPDMA_FACTS_BYTES) || + !span(config->rx_storage, config->rx_capacity) || + config->rx_capacity > 4096U || config->poll_limit == 0U || + overlap(config->facts, NX_U575_GPDMA_FACTS_BYTES, s, sizeof(*s)) || + overlap(config->rx_storage, config->rx_capacity, s, sizeof(*s)) || + overlap(config->rx_storage, config->rx_capacity, config, + sizeof(*config)) || + overlap(config->rx_storage, config->rx_capacity, config->facts, + NX_U575_GPDMA_FACTS_BYTES) || + overlap(config->rx_storage, config->rx_capacity, USART1_NS, + sizeof(*USART1_NS)) || + overlap(config->rx_storage, config->rx_capacity, GPDMA1_NS, + DMA_CONTROL_BYTES) || + overlap(config->rx_storage, config->rx_capacity, NVIC, sizeof(*NVIC)) || + overlap(config->rx_storage, config->rx_capacity, &g_nx_stm32u575_system, + sizeof(g_nx_stm32u575_system)) || + overlap(config->rx_storage, config->rx_capacity, + g_nx_stm32u575_system.rcc, + sizeof(*g_nx_stm32u575_system.rcc)) || + !NX_U575_GPDMA_COLD_VALID(s, config)) + return NX_ERROR_INVALID; + const nx_stm32u575_uart_gpdma_facts_t* facts = config->facts; + if (facts->engine != GPDMA1_NS || facts->channel != GPDMA1_Channel0_NS || + facts->irq != GPDMA1_Channel0_IRQn || facts->request != 25U || + facts->priority != 5U || config->clock_hz != 4000000U || + config->baud < 62U || config->baud > 250000U) + return NX_ERROR_UNSUPPORTED; + const uint8_t group = (uint8_t)NX_U575_IRQ_GROUP(); + const nx_irq_source_t uart_source = {(int16_t)USART1_IRQn, 5U, group}; + const nx_irq_source_t dma_source = {(int16_t)facts->irq, facts->priority, + group}; + nx_result_t result = + nx_irq_wake_validate(NULL, &config->irq_policy, &uart_source); + if (result == NX_SUCCESS) + result = nx_irq_wake_validate(NULL, &config->irq_policy, &dma_source); + if (result != NX_SUCCESS) + return result; + u->registers = USART1_NS; + u->rx_storage = config->rx_storage; + u->rx_capacity = config->rx_capacity; + u->baud = config->baud; + u->irq = USART1_IRQn; + u->profile = NX_UART_RX_BYTES; + u->irq_policy = config->irq_policy; + s->dma_irq_policy = config->irq_policy; + s->facts = facts; + result = nx_stm32u575_usart1_pins_prepare(); + return result == NX_SUCCESS ? nx_stm32u575_uart_gpdma_initialize( + s, config->clock_hz, config->poll_limit) + : result; +} + #endif /* NX_STM32U575_UART_GPDMA_IMPL_H */ diff --git a/tests/contracts/gd32f303_model/model.c b/tests/contracts/gd32f303_model/model.c index 82f248d79..e0ba89669 100644 --- a/tests/contracts/gd32f303_model/model.c +++ b/tests/contracts/gd32f303_model/model.c @@ -141,11 +141,11 @@ void nx_gd303_model_device_poll(unsigned kind, void* port) { } if (kind == 1U) { nx_legacy_adc_state_t* p = port; - p->registers->control2 &= ~12U; - if ((p->registers->control2 & (1U << 22U)) != 0U) { - p->registers->control2 &= ~(1U << 22U); - p->registers->data = 1234U; - p->registers->status |= 2U; + p->config->registers->control2 &= ~12U; + if ((p->config->registers->control2 & (1U << 22U)) != 0U) { + p->config->registers->control2 &= ~(1U << 22U); + p->config->registers->data = 1234U; + p->config->registers->status |= 2U; } } } diff --git a/tests/contracts/stm32_model/generate_constructors.py b/tests/contracts/stm32_model/generate_constructors.py index baffa6a88..6ad557fda 100644 --- a/tests/contracts/stm32_model/generate_constructors.py +++ b/tests/contracts/stm32_model/generate_constructors.py @@ -15,16 +15,16 @@ "#endif"] for fixture in ("ve-exti.toml", "ve-pwm.toml", "ve-spi.toml", "ve-i2c.toml"): result = resolve_ir(root / "tests/contracts/stm32_assembly" / fixture, root) - for item in result["controllers"]: - name = item["id"].replace("-", "_") - lines.append(f"static nx_stm32_{item['kind']}_state_t s_nx_port_{name};") - if item["kind"] in {"spi", "i2c"}: - lines.append(f"static nx_stm32_{item['kind']}_endpoint_state_t s_nx_endpoint_{name};") - if item["kind"] in {"pwm", "exti"}: - kind = item["kind"] + for item in result.controllers: + name = item.id.replace("-", "_") + lines.append(f"static nx_stm32_{item.kind}_state_t s_nx_port_{name};") + if item.kind in {"spi", "i2c"}: + lines.append(f"static nx_stm32_{item.kind}_endpoint_state_t s_nx_endpoint_{name};") + if item.kind in {"pwm", "exti"}: + kind = item.kind lines.append(f"static const nx_{kind}_port_t s_nx_face_{name} = {{&nx_stm32_{kind}_ops, &s_nx_port_{name}}};") - generated = constructor(item, result, result["board_bindings"], - result["routes"], result["devices"]) - lines += generated["definitions"] - lines += shared_irq_definitions(result["controllers"]) + generated = constructor(item, result, result.board_bindings, + result.routes, result.devices) + lines += generated.definitions + lines += shared_irq_definitions(result.controllers) output.write_text("\n".join(lines) + "\n", encoding="utf-8") diff --git a/tests/contracts/stm32f103_model/model.c b/tests/contracts/stm32f103_model/model.c index 055959722..3b03213ff 100644 --- a/tests/contracts/stm32f103_model/model.c +++ b/tests/contracts/stm32f103_model/model.c @@ -226,11 +226,11 @@ void nx_f103_model_device_poll(unsigned kind, void* port) { ++g_f103_model.timer.CNT; if (kind == 1U) { nx_legacy_adc_state_t* p = port; - p->registers->control2 &= ~12U; - if ((p->registers->control2 & (1U << 22U)) != 0U) { - p->registers->control2 &= ~(1U << 22U); - p->registers->data = 1234U; - p->registers->status |= 2U; + p->config->registers->control2 &= ~12U; + if ((p->config->registers->control2 & (1U << 22U)) != 0U) { + p->config->registers->control2 &= ~(1U << 22U); + p->config->registers->data = 1234U; + p->config->registers->status |= 2U; } } } diff --git a/tests/contracts/test_evidence_candidate.py b/tests/contracts/test_evidence_candidate.py index 529ab68b4..fba761f69 100644 --- a/tests/contracts/test_evidence_candidate.py +++ b/tests/contracts/test_evidence_candidate.py @@ -9,6 +9,7 @@ import unittest from unittest.mock import patch +from tools.evidence import candidate as candidate_tools from tools.evidence.candidate import (REQUIRED_SCOPES, collect_evidence_closure, collect_identities, seal, validate_manifest, validate_qualification, verify) from tools.evidence.common import EvidenceError, atomic_json, file_identity, load_json @@ -217,6 +218,77 @@ def test_missing_raw_payload_rejected_after_seal(self): with self.assertRaises(EvidenceError): verify(destination / "candidate.json") + def projection_fixture(self): + manifest, _, resolved = self.prepared() + manifest_path = self.root / "manifest.json" + atomic_json(manifest_path, manifest) + # External OCI/scope trust is isolated in this byte-verifier fixture. + # Its genuine linked artifacts and receipts are not platform evidence. + with patch.object(candidate_tools, "verify_environment", return_value={}), \ + patch.object(candidate_tools, "validate_scope_proof", + return_value=([], None)): + seal(manifest_path, self.root / "sealed") + return self.root / "sealed/candidate.json", manifest, load_json(resolved) + + def test_verified_projection_preserves_exact_source_config_and_artifacts(self): + path, manifest, resolved = self.projection_fixture() + before = path.read_bytes() + with patch.object(candidate_tools, "validate_scope_proof", + return_value=([], None)): + projected = candidate_tools.verified_projection(path) + self.assertEqual(projected["kind"], "verified_software_candidate_reference") + self.assertEqual(projected["candidate"], file_identity(path)) + self.assertEqual(projected["source"], self.source) + self.assertEqual(projected["resolved"], resolved) + self.assertEqual(projected["configuration_sha256"], + resolved["configuration_sha256"]) + self.assertEqual(projected["qualified_scopes"], sorted(REQUIRED_SCOPES)) + self.assertEqual(projected["physical_status"], "not_executed") + self.assertEqual(projected["product_promotion"], "not_performed") + self.assertEqual(projected["signature_status"], "unsigned") + for role, identity in projected["artifacts"].items(): + self.assertEqual(identity["sha256"], manifest["artifacts"][role]["sha256"]) + self.assertEqual(identity, file_identity(identity["path"])) + self.assertEqual(path.read_bytes(), before) + + def test_projection_rejects_missing_raw_even_when_summary_says_qualified(self): + path, _, _ = self.projection_fixture() + (path.parent / "payloads" / self.checks[0]["raw"]["sha256"]).unlink() + with self.assertRaises(EvidenceError): + candidate_tools.verified_projection(path) + + def test_projection_rejects_unbound_identity_and_unverified_status_claims(self): + path, _, _ = self.projection_fixture() + original = load_json(path) + mutations = (("candidate_id", "another-valid-identity"), + ("candidate_id", "invalid identity"), + ("signature_status", "verified"), + ("product_promotion", "performed"), + ("qualified_scopes", [*original["qualified_scopes"], + original["qualified_scopes"][0]])) + for field, value in mutations: + atomic_json(path, {**original, field: value}) + with self.subTest(field=field, value=value), \ + patch.object(candidate_tools, "validate_scope_proof", + return_value=([], None)), \ + self.assertRaises(EvidenceError): + candidate_tools.verified_projection(path) + + def test_projection_rejects_candidate_change_during_verification(self): + path, _, _ = self.projection_fixture() + original_verify = candidate_tools.verify + + def changed_candidate(candidate_path): + result = original_verify(candidate_path) + candidate_path.write_bytes(candidate_path.read_bytes() + b"\n") + return result + + with patch.object(candidate_tools, "validate_scope_proof", + return_value=([], None)), \ + patch.object(candidate_tools, "verify", side_effect=changed_candidate): + with self.assertRaisesRegex(EvidenceError, "changed"): + candidate_tools.verified_projection(path) + if __name__ == "__main__": unittest.main() diff --git a/tests/contracts/test_evidence_float_abi_consumers.py b/tests/contracts/test_evidence_float_abi_consumers.py index 108bb00c1..3da5fd97a 100644 --- a/tests/contracts/test_evidence_float_abi_consumers.py +++ b/tests/contracts/test_evidence_float_abi_consumers.py @@ -225,9 +225,12 @@ def matrix_fixture(self, declared, *, extra_source="", **kwargs): subprocess.run(["git", "-C", str(source), "add", "."], check=True) subprocess.run(["git", "-C", str(source), "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"], check=True) - assembly = self.root / "fixture.json" - atomic_json(assembly, {"board_package": "board.json"}) - atomic_json(self.root / "fixture-empty.json", {"board_package": "board.json"}) + assembly = self.root / "fixture.toml" + contents = ('schema = 2\nboard_package = "board"\n' + 'backend = "baremetal"\nclock = "fixed"\n' + '[memory]\nmain_stack_bytes = 2048\n') + assembly.write_text(contents) + (self.root / "fixture-empty.toml").write_text(contents) def build_boundary(argv, log): if "-B" in argv: build_layout(Path(argv[argv.index("-B") + 1]), declared, diff --git a/tests/contracts/test_measurement_compare.py b/tests/contracts/test_measurement_compare.py index 9a8b5b42a..ec1e3a099 100644 --- a/tests/contracts/test_measurement_compare.py +++ b/tests/contracts/test_measurement_compare.py @@ -5,12 +5,24 @@ import subprocess import sys import tempfile +import tomllib import unittest from tools.evidence.common import EvidenceError, atomic_json from tools.measurement.compare import compare, tool_identity, profile_input +def authored_fixture(root): + assembly = root / "fixture.toml" + contents = ('schema = 2\nboard_package = "board"\n' + 'backend = "baremetal"\nclock = "fixed"\n' + 'optimization = "O3"\n' + '[memory]\nmain_stack_bytes = 2048\n') + assembly.write_text(contents) + (root / "fixture-empty.toml").write_text(contents) + return assembly + + class MeasurementMatrixTests(unittest.TestCase): def test_parallelism_rejects_unbounded_or_noninteger_work_before_build(self): with tempfile.TemporaryDirectory() as directory: @@ -43,9 +55,7 @@ def test_actual_build_respects_explicit_single_job_and_retains_failure(self): subprocess.run(["git", "-C", str(source), "add", "."], check=True) subprocess.run(["git", "-C", str(source), "-c", "user.name=Test", "-c", "user.email=test@example.invalid", "commit", "-qm", "fixture"], check=True) - assembly = root / "fixture.json" - atomic_json(assembly, {"board_package": "board.json"}) - atomic_json(root / "fixture-empty.json", {"board_package": "board.json"}) + assembly = authored_fixture(root) report = compare(source, assembly, root / "build", compiler=str(Path(sys.executable).resolve()), cmake=str(Path(cmake).resolve()), ninja=str(Path(ninja).resolve()), @@ -56,6 +66,13 @@ def test_actual_build_respects_explicit_single_job_and_retains_failure(self): self.assertNotEqual(commands[1]["exit_code"], 0) self.assertEqual(commands[1]["argv"][-2:], ["--parallel", "1"]) self.assertEqual(report["build_parallelism"], 1) + selected = Path(next(argument.split("=", 1)[1] + for argument in commands[0]["argv"] + if argument.startswith("-DNEXUS_ASSEMBLY_FILE="))) + self.assertEqual(selected.suffix, ".toml") + document = tomllib.loads(selected.read_text()) + self.assertEqual(document["schema"], 2) + self.assertEqual(document["optimization"], "Os") def test_authored_toml_profiles_preserve_endpoint_and_board_paths(self): with tempfile.TemporaryDirectory() as directory: @@ -69,11 +86,52 @@ def test_authored_toml_profiles_preserve_endpoint_and_board_paths(self): '[spi.bus]\nbinding = "spi0"\nmode = "short-poll"\n' '[spi_device.sensor]\ncontroller = "bus"\n' 'cs_binding = "cs0"\nmode = 3\n') - result = profile_input(root, base, "spi", root / "output") - self.assertEqual(result["devices"][0]["id"], "sensor") - self.assertEqual(result["devices"][0]["mode"], 3) + result = tomllib.loads(profile_input( + root, base, "spi", root / "output")) + self.assertEqual(result["spi_device"]["sensor"]["mode"], 3) self.assertEqual(result["board_package"], "../board with spaces") - self.assertEqual(result["controllers"][0]["id"], "bus") + self.assertEqual(result["spi"]["bus"]["binding"], "spi0") + + def test_root_path_relocation_preserves_quoted_and_multiline_source(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + base = root / "profile.toml" + selected = root / "profile-empty.toml" + body = ('[memory]\nmain_stack_bytes = 2048\n' + '# board_package = "nested comment remains"\n') + selected.write_text('schema = 2\n"board_package" = """\\\n' + 'board with # and spaces\\\n"""\n' + "'layout' = '''flash layout.json'''\n" + 'backend = "baremetal"\nclock = "fixed"\n' + 'optimization = "O2"\n' + body) + rendered = profile_input(root, base, "empty", root / "output") + result = tomllib.loads(rendered) + self.assertEqual(result["board_package"], + "../board with # and spaces") + self.assertEqual(result["layout"], "../flash layout.json") + self.assertEqual(result["optimization"], "O2") + self.assertTrue(rendered.endswith(body)) + + def test_json_profile_cannot_bypass_the_production_toml_boundary(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + atomic_json(root / "profile-empty.json", {"board_package": "board"}) + with self.assertRaisesRegex(ValueError, "TOML"): + profile_input(root, root / "profile.json", "empty", root / "out") + + def test_board_id_becomes_one_explicit_relocated_package(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + package = root / "boards/reviewed" + package.mkdir(parents=True) + (package / "board.json").write_text("{}") + (root / "profile-empty.toml").write_text( + 'schema = 2\nboard = "reviewed"\nbackend = "baremetal"\n' + 'clock = "fixed"\n[memory]\nmain_stack_bytes = 2048\n') + result = tomllib.loads(profile_input( + root, root / "profile.toml", "empty", root / "out")) + self.assertNotIn("board", result) + self.assertEqual(result["board_package"], "../boards/reviewed") def test_unknown_toml_profile_fields_fail_before_external_build(self): with tempfile.TemporaryDirectory() as directory: @@ -104,9 +162,7 @@ def test_actual_cmake_failure_retains_nonzero_raw_evidence(self): subprocess.run(["git", "-C", str(source), "add", "."], check=True) subprocess.run(["git", "-C", str(source), "-c", "user.name=Test", "-c", "user.email=test@example.invalid", "commit", "-qm", "fixture"], check=True) - assembly = root / "fixture.json" - atomic_json(assembly, {"board_package": "board.json"}) - atomic_json(root / "fixture-empty.json", {"board_package": "board.json"}) + assembly = authored_fixture(root) report = compare(source, assembly, root / "build", compiler=str(Path(sys.executable).resolve()), cmake=str(Path(cmake).resolve()), ninja=str(Path(ninja).resolve()), workloads=("empty",), optimizations=("Os",)) diff --git a/tests/contracts/test_source_sdk.py b/tests/contracts/test_source_sdk.py index 795b527c1..fde7fdd6c 100644 --- a/tests/contracts/test_source_sdk.py +++ b/tests/contracts/test_source_sdk.py @@ -699,8 +699,8 @@ def test_canonical_inputs_resolve_and_old_authored_copies_are_removed(self): for current, previous in moves.items(): with self.subTest(assembly=current): self.assertTrue((directory / current).is_file()) - result = configure.resolve(directory / current, ROOT) - self.assertEqual(result["backend"], "baremetal") + result = configure.resolve_ir(directory / current, ROOT) + self.assertEqual(result.backend, "baremetal") self.assertFalse((ROOT / "tests/contracts" / previous).exists()) for current, previous in ( ("g474-fdcan", "fdcan_board"), diff --git a/tests/google/CMakeLists.txt b/tests/google/CMakeLists.txt index d6c262367..532c9e718 100644 --- a/tests/google/CMakeLists.txt +++ b/tests/google/CMakeLists.txt @@ -321,6 +321,7 @@ target_sources(nexus_stm32_uart_stream_test PRIVATE nexus_google_test(nexus_gd32_spi_dma_test SOURCES gd32_spi_dma_test.cpp "${gd_multi_root}/drivers/spi.c" "${gd_multi_root}/drivers/spi_dma.c" + "${gd_multi_root}/drivers/gpio.c" "${NEXUS_SOURCE_DIR}/tests/contracts/gd32_model/model.c" LIBRARIES Nexus::IO INCLUDES "${gd_multi_root}" "${gd_multi_root}/private" diff --git a/tests/google/dma_multi_controller.cmake b/tests/google/dma_multi_controller.cmake index acdefccb9..52d77d3e1 100644 --- a/tests/google/dma_multi_controller.cmake +++ b/tests/google/dma_multi_controller.cmake @@ -34,6 +34,10 @@ foreach(dma_multi_family stm32f407 gd32f470) "${dma_multi_vendor}/GD32F4xx_standard_peripheral/Include") target_compile_definitions(${dma_multi_target} PRIVATE GD32F470 HXTAL_VALUE=25000000) + if(dma_multi_family STREQUAL "gd32f470" AND dma_multi_kind STREQUAL "spi") + target_sources(${dma_multi_target} PRIVATE + "${NEXUS_SOURCE_DIR}/soc/gd32/gd32f470/drivers/gpio.c") + endif() if(dma_multi_family STREQUAL "stm32f407") target_include_directories(${dma_multi_target} SYSTEM PRIVATE "${NEXUS_SOURCE_DIR}/vendors/st/cmsis_device_f4/Include") diff --git a/tests/google/gd32_exti_test.cpp b/tests/google/gd32_exti_test.cpp index c20de60e8..1111192f5 100644 --- a/tests/google/gd32_exti_test.cpp +++ b/tests/google/gd32_exti_test.cpp @@ -139,6 +139,30 @@ class Gd32Exti : public testing::Test { } } }; +TEST_F(Gd32Exti, ColdCloseDoesNotAccessUnownedHardware) { + EXTI_INTEN = 1U << 3U; + EXPECT_EQ(NX_ERROR_INVALID, nx_gd32_exti_close(nullptr)); + EXPECT_EQ(NX_SUCCESS, nx_gd32_exti_close(&first)); + EXPECT_EQ(1U << 3U, EXTI_INTEN); + EXPECT_EQ(0U, s_exti_clear_calls); + EXPECT_EQ(0U, s_nvic_disable_calls); + EXPECT_EQ(0U, s_nvic_pending_calls); +} +TEST_F(Gd32Exti, ColdCloseRetainsAnActiveVectorUntilExplicitRetry) { + ASSERT_EQ(NX_SUCCESS, + nx_gd32_exti_initialize(&first, &domain, 3, 4, NX_EXTI_BOTH, + first_events, 2, 5)); + const unsigned irq = static_cast(EXTI3_IRQn); + const uint32_t mask = 1U << (irq % 32U); + NVIC->IABR[irq / 32U] = mask; + EXPECT_EQ(NX_ERROR_BUSY, nx_gd32_exti_close(&first)); + EXPECT_TRUE(first.initialized); + EXPECT_EQ(first_events, first.events); + NVIC->IABR[irq / 32U] = 0U; + EXPECT_EQ(NX_SUCCESS, nx_gd32_exti_close(&first)); + EXPECT_FALSE(first.initialized); + EXPECT_EQ(NX_SUCCESS, nx_gd32_exti_close(&first)); +} TEST_F(Gd32Exti, ForeignEnabledLineRejectsBeforePinOrMuxChanges) { EXTI_INTEN = 1U << 3U; GPIO_CTL(GPIOE) = 0x12345678U; diff --git a/tests/google/legacy_devices_factory_test.cpp b/tests/google/legacy_devices_factory_test.cpp index bd942ab6e..b2a495767 100644 --- a/tests/google/legacy_devices_factory_test.cpp +++ b/tests/google/legacy_devices_factory_test.cpp @@ -92,9 +92,9 @@ TEST_F(LegacyDeviceFactory, ExactStaticAssemblyStartsAndRestoresEveryLease) { const auto* converter = static_cast( nx_factory_adc(NX_ADC_ID_INPUT)->context); #ifdef NX_LEGACY_FACTORY_F103 - EXPECT_EQ(4U, converter->startup_delay_us); + EXPECT_EQ(4U, converter->config->startup_delay_us); #else - EXPECT_EQ(2000U, converter->startup_delay_us); + EXPECT_EQ(2000U, converter->config->startup_delay_us); #endif nx_pwm_state_t pwm{}; ASSERT_EQ(NX_SUCCESS, diff --git a/tests/google/legacy_devices_test.cpp b/tests/google/legacy_devices_test.cpp index ce6477f7d..0f05e5d2e 100644 --- a/tests/google/legacy_devices_test.cpp +++ b/tests/google/legacy_devices_test.cpp @@ -52,12 +52,12 @@ extern "C" void nx_legacy_model_write(volatile uint32_t* r, uint32_t v) { if (flash_port != nullptr && r == &flash_port->registers->control && (v & 128U) != 0U) nx_native_clock_advance(advance_on_relock); - if (pwm_port != nullptr && r == pwm_port->pin_set) - *pwm_port->pin_latch = - (*pwm_port->pin_latch | (v & 0xffffU)) & ~(v >> 16U); - if (pwm_port != nullptr && r == &pwm_port->registers->event && + if (pwm_port != nullptr && r == pwm_port->config->pin_set) + *pwm_port->config->pin_latch = + (*pwm_port->config->pin_latch | (v & 0xffffU)) & ~(v >> 16U); + if (pwm_port != nullptr && r == &pwm_port->config->registers->event && (v & 1U) != 0U && !hold_update) - pwm_port->registers->status |= 1U; + pwm_port->config->registers->status |= 1U; if (flash_port != nullptr && r == &flash_port->registers->control && (v & 64U) != 0U) { flash_port->registers->status |= 1U; @@ -83,11 +83,12 @@ extern "C" void nx_legacy_model_poll(unsigned kind, void* context) { if (kind == 1U) { auto* p = static_cast(context); if (!hold_calibration) - p->registers->control2 &= ~12U; - if ((p->registers->control2 & (1U << 22U)) != 0U && !hold_conversion) { - p->registers->control2 &= ~(1U << 22U); - p->registers->data = adc_value; - p->registers->status |= 2U; + p->config->registers->control2 &= ~12U; + if ((p->config->registers->control2 & (1U << 22U)) != 0U && + !hold_conversion) { + p->config->registers->control2 &= ~(1U << 22U); + p->config->registers->data = adc_value; + p->config->registers->status |= 2U; } } } @@ -139,6 +140,7 @@ class LegacyAdc : public testing::Test { nx_legacy_adc_registers_t registers{}; uint32_t gate = 1U, divider = 0U, mode = 0x44444444U, lock = 0U; volatile bool started = true; + nx_legacy_adc_config_t config{}; nx_legacy_adc_state_t state{}; const nx_adc_port_t face{&nx_legacy_adc_ops, &state}; void SetUp() override { @@ -146,19 +148,20 @@ class LegacyAdc : public testing::Test { flash_port = nullptr; pwm_port = nullptr; adc_port = &state; - state.registers = ®isters; - state.gate = &gate; - state.gate_mask = 1U; - state.divider = ÷r; - state.divider_mask = 0xc000U; - state.pin_mode = &mode; - state.pin_lock = &lock; - state.clock_started = &started; + state.config = &config; + config.registers = ®isters; + config.gate = &gate; + config.gate_mask = 1U; + config.divider = ÷r; + config.divider_mask = 0xc000U; + config.pin_mode = &mode; + config.pin_lock = &lock; + config.clock_started = &started; state.reference_mv = 3300U; - state.poll_limit = 20U; + config.poll_limit = 20U; ignored = nullptr; polls = 0U; - state.startup_delay_us = 4U; + config.startup_delay_us = 4U; hold_calibration = hold_conversion = false; adc_value = 1234U; } @@ -238,17 +241,61 @@ TEST_F(LegacyAdc, AliasedOutputAndNeverDeadlineCannotStartConversion) { EXPECT_EQ(1U, registers.control2); } TEST_F(LegacyAdc, Gd303ErratumDelayRetainsMillisecondMarginBeforeCalibration) { - state.startup_delay_us = 2000U; - state.poll_limit = 4000U; + config.startup_delay_us = 2000U; + config.poll_limit = 4000U; Start(); EXPECT_GE(nx_time_now_us(), 2000U); EXPECT_EQ(1U, registers.control2); } +TEST_F(LegacyAdc, HardwareDescriptionCannotAliasSampleOrInfoOutput) { + Start(); + const auto before = config; + size_t count = 17U; + EXPECT_EQ(NX_ERROR_INVALID, + nx_adc_port_sample(&face, reinterpret_cast(&config), + 1U, 100U, &count)); + EXPECT_EQ( + NX_ERROR_INVALID, + nx_adc_port_info(&face, reinterpret_cast(&config))); + EXPECT_EQ(17U, count); + EXPECT_EQ(0, std::memcmp(&before, &config, sizeof(config))); + EXPECT_EQ(1U, registers.control2); + EXPECT_EQ(NX_SUCCESS, nx_legacy_adc_close(&state)); + EXPECT_EQ(0, std::memcmp(&before, &config, sizeof(config))); +} +TEST_F(LegacyAdc, HardwareDescriptionOverlappingStateRejectsBeforeAccess) { + state.config = reinterpret_cast(&state); + const auto before = state; + EXPECT_EQ(NX_ERROR_INVALID, nx_legacy_adc_initialize(&state)); + EXPECT_EQ(0, std::memcmp(&before, &state, sizeof(state))); + EXPECT_EQ(0U, registers.control2); + EXPECT_EQ(0x44444444U, mode); +} +TEST_F(LegacyAdc, IndependentDescriptionsKeepConverterStateSeparate) { + Start(); + nx_legacy_adc_registers_t other_registers{}; + uint32_t other_mode = 0x44444444U; + auto other_config = config; + other_config.registers = &other_registers; + other_config.pin_mode = &other_mode; + nx_legacy_adc_state_t other{}; + other.config = &other_config; + other.reference_mv = 3000U; + ASSERT_EQ(NX_SUCCESS, nx_legacy_adc_initialize(&other)); + EXPECT_EQ(&config, state.config); + EXPECT_EQ(&other_config, other.config); + ASSERT_EQ(NX_SUCCESS, nx_legacy_adc_close(&state)); + EXPECT_TRUE(other.initialized); + EXPECT_TRUE(other.retained); + EXPECT_EQ(1U, other_registers.control2); + EXPECT_EQ(NX_SUCCESS, nx_legacy_adc_close(&other)); +} class LegacyPwm : public testing::Test { protected: nx_legacy_pwm_registers_t registers{}; uint32_t gate = 2U, mode = 0x44444444U, lock = 0U, latch = 0U, set = 0U; volatile bool started = true; + nx_legacy_pwm_config_t config{}; nx_legacy_pwm_state_t state{}; const nx_pwm_port_t face{&nx_legacy_pwm_ops, &state}; void SetUp() override { @@ -256,19 +303,20 @@ class LegacyPwm : public testing::Test { flash_port = nullptr; adc_port = nullptr; pwm_port = &state; - state.registers = ®isters; - state.gate = &gate; - state.gate_mask = 2U; - state.pin_mode = &mode; - state.pin_lock = &lock; - state.pin_latch = &latch; - state.pin_set = &set; - state.clock_started = &started; + state.config = &config; + config.registers = ®isters; + config.gate = &gate; + config.gate_mask = 2U; + config.pin_mode = &mode; + config.pin_lock = &lock; + config.pin_latch = &latch; + config.pin_set = &set; + config.clock_started = &started; state.tick_hz = 1000000U; state.period_ticks = 1000U; state.duty_ticks = 100U; state.inactive = 0U; - state.poll_limit = 5U; + config.poll_limit = 5U; ignored = nullptr; hold_update = false; } @@ -343,6 +391,26 @@ TEST_F(LegacyPwm, FailedTimerWithdrawalStillDetachesPinAtBoardInactiveLevel) { ignored = nullptr; EXPECT_EQ(NX_SUCCESS, nx_legacy_pwm_close(&state)); } +TEST_F(LegacyPwm, ReadOnlyDescriptionCannotBeUsedAsStateOutput) { + Start(); + const auto before = config; + EXPECT_EQ( + NX_ERROR_INVALID, + nx_pwm_port_state(&face, reinterpret_cast(&config))); + EXPECT_EQ(0, std::memcmp(&before, &config, sizeof(config))); + ASSERT_EQ(NX_SUCCESS, nx_pwm_port_start(&face)); + ASSERT_EQ(NX_SUCCESS, nx_pwm_port_set(&face, 1000U, 200U)); + ASSERT_EQ(NX_SUCCESS, nx_legacy_pwm_close(&state)); + EXPECT_EQ(0, std::memcmp(&before, &config, sizeof(config))); +} +TEST_F(LegacyPwm, DescriptionOverlappingStateRejectsBeforeHardwareAccess) { + state.config = reinterpret_cast(&state); + const auto before = state; + EXPECT_EQ(NX_ERROR_INVALID, nx_legacy_pwm_initialize(&state)); + EXPECT_EQ(0, std::memcmp(&before, &state, sizeof(state))); + EXPECT_EQ(0U, registers.control1); + EXPECT_EQ(0x44444444U, mode); +} TEST_F(LegacyFlash, HalfWordProgramAndWholePageEraseSettleAndRelock) { Start(); const uint8_t bytes[]{0x34U, 0x12U, 0x78U, 0x56U}; diff --git a/tests/google/rtc_lptim_start_test.cpp b/tests/google/rtc_lptim_start_test.cpp index ecb16ca4b..840395891 100644 --- a/tests/google/rtc_lptim_start_test.cpp +++ b/tests/google/rtc_lptim_start_test.cpp @@ -88,6 +88,79 @@ class NativeReopen : public testing::Test { } }; +TEST_F(NativeReopen, PreparationConstructsFreshOwnersWithoutReadingTime) { + nx_native_rtc_state_t fresh_rtc{}; + nx_native_lptim_state_t fresh_counter{}; + EXPECT_EQ(nx_native_rtc_prepare(&fresh_rtc, &clock), NX_SUCCESS); + EXPECT_EQ(nx_native_lptim_prepare(&fresh_counter, &clock, 8192), + NX_SUCCESS); + EXPECT_EQ(fresh_rtc.generation, 1u); + EXPECT_EQ(fresh_counter.generation, 1u); + EXPECT_EQ(fresh_counter.tick_hz, 8192u); + EXPECT_TRUE(fresh_rtc.opened); + EXPECT_TRUE(fresh_counter.opened); +} + +TEST_F(NativeReopen, PreparationPreservesBindingAndEpochAcrossReopen) { + SetCalendar(); + ASSERT_EQ(nx_rtc_port_stop(&rtc), NX_SUCCESS); + ASSERT_EQ(nx_lptim_port_stop(&lptim), NX_SUCCESS); + auto value = Bytes(calendar.calendar); + uint64_t rtc_generation = calendar.generation; + uint64_t counter_generation = counter.generation; + EXPECT_EQ(nx_native_rtc_prepare(&calendar, nullptr), NX_SUCCESS); + EXPECT_EQ(nx_native_lptim_prepare(&counter, nullptr, 1), NX_SUCCESS); + EXPECT_EQ(calendar.generation, rtc_generation + 1); + EXPECT_EQ(counter.generation, counter_generation + 1); + EXPECT_EQ(Bytes(calendar.calendar), value); + EXPECT_EQ(calendar.clock.read, ReadClock); + EXPECT_EQ(counter.clock.context, &source); + EXPECT_EQ(counter.tick_hz, 32768u); +} + +TEST_F(NativeReopen, PreparationCannotOverwriteOpenOrBorrowedState) { + auto rtc_before = Bytes(calendar); + auto counter_before = Bytes(counter); + EXPECT_EQ(nx_native_rtc_prepare(&calendar, &clock), NX_ERROR_STATE); + EXPECT_EQ(nx_native_lptim_prepare(&counter, &clock, 8192), NX_ERROR_STATE); + EXPECT_EQ(Bytes(calendar), rtc_before); + EXPECT_EQ(Bytes(counter), counter_before); + SetCalendar(); + PrepareAlarm(0); + PrepareShot(0); + ASSERT_EQ(nx_rtc_port_submit(&rtc, &alarms[0]), NX_SUCCESS); + ASSERT_EQ(nx_lptim_port_submit(&lptim, &shots[0]), NX_SUCCESS); + rtc_before = Bytes(calendar); + counter_before = Bytes(counter); + EXPECT_EQ(nx_native_rtc_prepare(&calendar, nullptr), NX_ERROR_BUSY); + EXPECT_EQ(nx_native_lptim_prepare(&counter, nullptr, 0), NX_ERROR_BUSY); + EXPECT_EQ(Bytes(calendar), rtc_before); + EXPECT_EQ(Bytes(counter), counter_before); +} + +TEST_F(NativeReopen, PreparationValidatesOwnersBeforeTestingConstructed) { + nx_native_rtc_state_t fresh_rtc{}; + nx_native_lptim_state_t fresh_counter{}; + auto rtc_before = Bytes(fresh_rtc); + auto counter_before = Bytes(fresh_counter); + EXPECT_EQ(nx_native_rtc_prepare(nullptr, &clock), NX_ERROR_INVALID); + EXPECT_EQ(nx_native_lptim_prepare(nullptr, &clock, 8192), NX_ERROR_INVALID); + EXPECT_EQ(nx_native_rtc_prepare(&fresh_rtc, nullptr), NX_ERROR_INVALID); + EXPECT_EQ(nx_native_lptim_prepare(&fresh_counter, &clock, 0), + NX_ERROR_INVALID); + EXPECT_EQ( + nx_native_rtc_prepare(reinterpret_cast( + reinterpret_cast(&fresh_rtc) + 1), + &clock), + NX_ERROR_INVALID); + EXPECT_EQ(nx_native_lptim_prepare( + reinterpret_cast(UINTPTR_MAX - 7), + &clock, 8192), + NX_ERROR_INVALID); + EXPECT_EQ(Bytes(fresh_rtc), rtc_before); + EXPECT_EQ(Bytes(fresh_counter), counter_before); +} + TEST_F(NativeReopen, InvalidAndUnconstructedStorageRejectWithoutWriting) { nx_native_rtc_state_t fresh_rtc{}; nx_native_lptim_state_t fresh_counter{}; diff --git a/tests/google/single_spi_factory_test.cpp b/tests/google/single_spi_factory_test.cpp index 631812b28..717645a42 100644 --- a/tests/google/single_spi_factory_test.cpp +++ b/tests/google/single_spi_factory_test.cpp @@ -12,11 +12,13 @@ extern "C" { #include "nexus_factory.h" #ifdef NX_MULTI_FACTORY_STM32 #include "stm32f407_provider.h" +#include "stm32f407_spi_cold.h" #define GPIO_GATE RCC->AHB1ENR #define SPI_GATE RCC->APB2ENR #define SPI_BIT RCC_APB2ENR_SPI1EN #else #include "gd32f470_provider.h" +#include "gd32f470_spi_cold.h" #include "gd32f4xx_gpio.h" #include "gd32f4xx_rcu.h" #include "gd32f4xx_spi.h" @@ -38,6 +40,12 @@ class SingleSpiPorts { static SingleSpiPorts* s_ports; static bool s_fail_pin_prepare; static bool s_refuse_withdraw; +static bool s_refuse_gate_enable; +static bool s_refuse_gate_disable; +#ifdef NX_MULTI_FACTORY_STM32 +static bool s_refuse_cr_restore; +static bool s_refuse_cr2_restore; +#endif static unsigned s_system_stops; #ifdef NX_MULTI_FACTORY_STM32 static constexpr unsigned FAILED_PORT = 0U; @@ -67,6 +75,20 @@ static void observe_gpio_commands(void) { /** \brief Hardware refusal affects only the selected physical mode bits. */ extern "C" void nx_multi_factory_write(volatile uint32_t* reg, uint32_t value) { observe_gpio_commands(); + if (reg == &SPI_GATE && + ((s_refuse_gate_enable && (value & SPI_BIT) != 0U) || + (s_refuse_gate_disable && (value & SPI_BIT) == 0U))) { + value = (value & ~static_cast(SPI_BIT)) | (*reg & SPI_BIT); + } +#ifdef NX_MULTI_FACTORY_STM32 + if (s_refuse_cr2_restore && reg == &SPI1->CR2 && value == SPI_CR2_SSOE) { + value = *reg; + } + if (s_refuse_cr_restore && reg == &SPI1->CR1 && + value == (SPI_CR1_MSTR | SPI_CR1_CPHA)) { + value = *reg; + } +#endif if (s_refuse_withdraw && reg == &gpio(FAILED_PORT, 0U)) { const uint32_t mask = 3U << (FAILED_PIN * 2U); value = (value & ~mask) | (*reg & mask); @@ -226,6 +248,10 @@ class SingleSpiFactory : public testing::Test { ASSERT_EQ(mapping, reinterpret_cast(UINT32_C(0x40000000))); s_ports = &ports; s_fail_pin_prepare = s_refuse_withdraw = false; + s_refuse_gate_enable = s_refuse_gate_disable = false; +#ifdef NX_MULTI_FACTORY_STM32 + s_refuse_cr_restore = s_refuse_cr2_restore = false; +#endif s_system_stops = 0U; #ifdef NX_MULTI_FACTORY_STM32 g_nx_stm32_system = {}; @@ -237,6 +263,10 @@ class SingleSpiFactory : public testing::Test { } void TearDown() override { s_fail_pin_prepare = s_refuse_withdraw = false; + s_refuse_gate_enable = s_refuse_gate_disable = false; +#ifdef NX_MULTI_FACTORY_STM32 + s_refuse_cr_restore = s_refuse_cr2_restore = false; +#endif EXPECT_EQ(NX_SUCCESS, nx_platform_stop()); EXPECT_EQ(0, munmap(mapping, 0x80000U)); s_ports = nullptr; @@ -251,6 +281,141 @@ TEST_F(SingleSpiFactory, SuccessfulFullStopRestoresTheOriginalSelectedPins) { EXPECT_EQ(0U, capture_pin(FAILED_PORT, FAILED_PIN)); EXPECT_EQ(NX_SUCCESS, nx_platform_start().primary); } +TEST_F(SingleSpiFactory, RefusedClockAcquisitionLeavesNoExposedPins) { + s_refuse_gate_enable = true; + const auto result = nx_platform_start(); + EXPECT_EQ(NX_ERROR_IO, result.primary); + EXPECT_EQ(NX_SUCCESS, result.cleanup); + EXPECT_EQ(0U, result.remaining_effects); + EXPECT_EQ(0U, capture_pin(FAILED_PORT, FAILED_PIN)); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); + s_refuse_gate_enable = false; + EXPECT_EQ(NX_SUCCESS, nx_platform_start().primary); +} + +TEST_F(SingleSpiFactory, RefusedClockWithdrawalRetainsOwnerUntilRetry) { + ASSERT_EQ(NX_SUCCESS, nx_platform_start().primary); + s_refuse_gate_disable = true; + EXPECT_EQ(NX_ERROR_IO, nx_platform_stop()); + EXPECT_NE(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, s_system_stops); + EXPECT_EQ(NX_ERROR_STATE, nx_platform_start().primary); + s_refuse_gate_disable = false; + EXPECT_EQ(NX_SUCCESS, nx_platform_stop()); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(1U, s_system_stops); + EXPECT_EQ(NX_SUCCESS, nx_platform_start().primary); +} + +#ifdef NX_MULTI_FACTORY_STM32 +TEST_F(SingleSpiFactory, RefusedColdRegisterRestoreRetainsReceiptUntilRetry) { + SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_CPHA; + s_fail_pin_prepare = s_refuse_cr_restore = true; + const auto result = nx_platform_start(); + EXPECT_EQ(NX_ERROR_IO, result.primary); + EXPECT_EQ(NX_ERROR_IO, result.cleanup); + EXPECT_NE(0U, result.remaining_effects); + EXPECT_EQ(0U, SPI1->CR1); + EXPECT_NE(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, s_system_stops); + EXPECT_EQ(NX_ERROR_STATE, nx_platform_start().primary); + s_fail_pin_prepare = s_refuse_cr_restore = false; + EXPECT_EQ(NX_SUCCESS, nx_platform_stop()); + EXPECT_EQ(SPI_CR1_MSTR | SPI_CR1_CPHA, SPI1->CR1); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, GPIO_GATE); +} + +TEST_F(SingleSpiFactory, RefusedSecondRegisterRestoreRetainsReceiptUntilRetry) { + SPI1->CR2 = SPI_CR2_SSOE; + s_fail_pin_prepare = s_refuse_cr2_restore = true; + const auto result = nx_platform_start(); + EXPECT_EQ(NX_ERROR_IO, result.primary); + EXPECT_EQ(NX_ERROR_IO, result.cleanup); + EXPECT_NE(0U, result.remaining_effects); + EXPECT_EQ(0U, SPI1->CR2); + EXPECT_NE(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, s_system_stops); + s_fail_pin_prepare = s_refuse_cr2_restore = false; + EXPECT_EQ(NX_SUCCESS, nx_platform_stop()); + EXPECT_EQ(SPI_CR2_SSOE, SPI1->CR2); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); + EXPECT_EQ(0U, GPIO_GATE); +} + +TEST_F(SingleSpiFactory, ForeignEnabledControllerSurvivesFailedGateRestore) { + SPI1->CR1 = SPI_CR1_SPE | SPI_CR1_CPOL; + SPI1->CR2 = SPI_CR2_SSOE; + s_refuse_gate_disable = true; + const auto result = nx_platform_start(); + EXPECT_EQ(NX_ERROR_BUSY, result.primary); + EXPECT_EQ(NX_ERROR_IO, result.cleanup); + EXPECT_NE(0U, result.remaining_effects); + EXPECT_EQ(SPI_CR1_SPE | SPI_CR1_CPOL, SPI1->CR1); + EXPECT_EQ(SPI_CR2_SSOE, SPI1->CR2); + EXPECT_NE(0U, SPI_GATE & SPI_BIT); + /* Platform retains its enclosing GPIO clock lease while SPI cannot close. + */ + EXPECT_EQ(0U, capture_pin(FAILED_PORT, FAILED_PIN)); + s_refuse_gate_disable = false; + EXPECT_EQ(NX_SUCCESS, nx_platform_stop()); + EXPECT_EQ(SPI_CR1_SPE | SPI_CR1_CPOL, SPI1->CR1); + EXPECT_EQ(SPI_CR2_SSOE, SPI1->CR2); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); +} +#endif + +#ifdef NX_MULTI_FACTORY_STM32 +TEST_F(SingleSpiFactory, ColdPrepareRejectsInvalidResourcesWithoutEffects) { + nx_stm32_spi_state_t state{}; + nx_stm32_spi_cold_receipt_t receipt{}; + EXPECT_EQ(NX_ERROR_INVALID, + nx_stm32_spi_prepare(&state, nullptr, &receipt)); + EXPECT_EQ(nullptr, state.registers); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); +} + +TEST_F(SingleSpiFactory, ColdPrepareCannotRebindAnExistingController) { + nx_stm32_spi_state_t state{}; + nx_stm32_spi_cold_receipt_t receipt{}; + state.registers = SPI1; + state.active = true; + const auto old = state; + EXPECT_EQ(NX_ERROR_BUSY, nx_stm32_spi_prepare(&state, nullptr, &receipt)); + EXPECT_EQ(old.registers, state.registers); + EXPECT_TRUE(state.active); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); +} +#else +TEST_F(SingleSpiFactory, ColdPrepareRejectsInvalidResourcesWithoutEffects) { + nx_gd32_spi_state_t state{}; + nx_gd32_spi_cold_t cold{}; + EXPECT_EQ(NX_ERROR_INVALID, nx_gd32_spi_prepare(&state, nullptr, &cold)); + EXPECT_FALSE(state.initialized); + EXPECT_EQ(0U, cold.pin_leases); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); +} + +TEST_F(SingleSpiFactory, ColdPrepareCannotRebindAnExistingController) { + nx_gd32_spi_state_t state{}; + state.controller = &nx_gd32_spi4_controller; + state.initialized = true; + state.active = true; + nx_gd32_spi_cold_t cold{}; + EXPECT_EQ(NX_ERROR_BUSY, nx_gd32_spi_prepare(&state, nullptr, &cold)); + EXPECT_EQ(&nx_gd32_spi4_controller, state.controller); + EXPECT_TRUE(state.initialized); + EXPECT_TRUE(state.active); + EXPECT_EQ(0U, cold.pin_leases); + EXPECT_EQ(0U, GPIO_GATE); + EXPECT_EQ(0U, SPI_GATE & SPI_BIT); +} +#endif TEST_F(SingleSpiFactory, ColdFailureWithdrawsDriveBeforeRestoringOldFields) { s_fail_pin_prepare = true; EXPECT_CALL(ports, Restore(_, _)) diff --git a/tests/google/spi_transaction.cmake b/tests/google/spi_transaction.cmake index 46f9f08c6..f72ea8ac9 100644 --- a/tests/google/spi_transaction.cmake +++ b/tests/google/spi_transaction.cmake @@ -22,6 +22,7 @@ target_compile_options(nexus_stm32_spi_transaction_test PRIVATE nexus_google_test(nexus_gd32_spi_transaction_test SOURCES spi_transaction_provider_test.cpp "${gd_multi_root}/drivers/spi.c" + "${gd_multi_root}/drivers/gpio.c" "${NEXUS_SOURCE_DIR}/tests/contracts/gd32_model/model.c" LIBRARIES Nexus::IO INCLUDES "${gd_multi_root}" "${gd_multi_root}/private" @@ -56,6 +57,7 @@ target_compile_options(nexus_stm32_spi_dma_sequence_test PRIVATE nexus_google_test(nexus_gd32_spi_dma_sequence_test SOURCES spi_dma_sequence_provider_test.cpp "${gd_multi_root}/drivers/spi.c" + "${gd_multi_root}/drivers/gpio.c" "${gd_multi_root}/drivers/spi_dma.c" "${NEXUS_SOURCE_DIR}/tests/contracts/gd32_model/model.c" LIBRARIES Nexus::IO diff --git a/tests/google/u575_uart_chain.cmake b/tests/google/u575_uart_chain.cmake index 9ce15e04c..133b60590 100644 --- a/tests/google/u575_uart_chain.cmake +++ b/tests/google/u575_uart_chain.cmake @@ -21,6 +21,7 @@ nexus_google_test(nexus_u575_uart_chain_test SOURCES u575_uart_chain_test.cpp u575_uart_chain_model.cpp u575_uart_gpdma_model.cpp u575_model.cpp "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/system.c" + "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/gpio.c" "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/uart.c" "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/uart_dma_chain.c" LIBRARIES Nexus::IO Nexus::Arch diff --git a/tests/google/u575_uart_chain_test.cpp b/tests/google/u575_uart_chain_test.cpp index d86ddb108..1e3a4f64c 100644 --- a/tests/google/u575_uart_chain_test.cpp +++ b/tests/google/u575_uart_chain_test.cpp @@ -158,6 +158,42 @@ TEST_F(U575UartChain, PackedNodesAndPersistentDmatPrecedeFirstEnable) { g_u575_uart_gpdma.before_write = nullptr; } +TEST_F(U575UartChain, ColdPrepareBindsTheExactChainWithoutChangingNodes) { + const nx_stm32u575_uart_gpdma_config_t config{ + &facts.base, + ring, + sizeof(ring), + 115200U, + {NX_IRQ_KERNEL_NONE, 126U, 4U, 0U}, + 4000000U, + 8U}; + state = {}; + nodes[0].block = 17U; + ASSERT_EQ(NX_SUCCESS, nx_stm32u575_uart_chain_prepare(&state, &config)); + EXPECT_EQ(&facts.base, state.facts); + EXPECT_EQ(17U, nodes[0].block); + EXPECT_TRUE(state.uart.initialized); +} + +TEST_F(U575UartChain, ColdPrepareRejectsMisalignedNodesBeforeBindingOrPins) { + const nx_stm32u575_uart_gpdma_config_t config{ + &facts.base, + ring, + sizeof(ring), + 115200U, + {NX_IRQ_KERNEL_NONE, 126U, 4U, 0U}, + 4000000U, + 8U}; + const auto before = state; + const auto writes = g_u575_model.writes; + facts.nodes = + reinterpret_cast(kSram + 0x1004U); + EXPECT_EQ(NX_ERROR_INVALID, + nx_stm32u575_uart_chain_prepare(&state, &config)); + EXPECT_EQ(0, std::memcmp(&before, &state, sizeof(state))); + EXPECT_EQ(writes, g_u575_model.writes); +} + TEST_F(U575UartChain, HardwareFetchesNextBlockWithoutServiceOrIntermediateTcf) { ASSERT_NO_FATAL_FAILURE(Open()); ASSERT_EQ(NX_SUCCESS, nx_uart_port_submit(&face, &request)); diff --git a/tests/google/u575_uart_gpdma.cmake b/tests/google/u575_uart_gpdma.cmake index d88a1cc89..04f6a997c 100644 --- a/tests/google/u575_uart_gpdma.cmake +++ b/tests/google/u575_uart_gpdma.cmake @@ -1,6 +1,7 @@ nexus_google_test(nexus_u575_uart_gpdma_test SOURCES u575_uart_gpdma_test.cpp u575_uart_gpdma_model.cpp u575_model.cpp "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/system.c" + "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/gpio.c" "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/uart.c" "${NEXUS_SOURCE_DIR}/soc/stm32/stm32u575/drivers/uart_dma.c" LIBRARIES Nexus::IO Nexus::Arch diff --git a/tests/google/u575_uart_gpdma_test.cpp b/tests/google/u575_uart_gpdma_test.cpp index 0f5ea762f..aeb42cd8c 100644 --- a/tests/google/u575_uart_gpdma_test.cpp +++ b/tests/google/u575_uart_gpdma_test.cpp @@ -158,6 +158,73 @@ TEST_F(U575UartGpdma, ColdGateAndBothIrqsAreProvedBeforeReady) { g_u575_uart_gpdma.before_write = nullptr; } +TEST_F(U575UartGpdma, ColdPrepareBindsAndStartsTheSelectedStaticOwner) { + const nx_stm32u575_uart_gpdma_config_t config{ + &facts, + ring, + sizeof(ring), + 115200U, + {NX_IRQ_KERNEL_NONE, 126U, 4U, 0U}, + 4000000U, + 8U}; + state = {}; + ASSERT_EQ(NX_SUCCESS, nx_stm32u575_uart_gpdma_prepare(&state, &config)); + EXPECT_EQ(&facts, state.facts); + EXPECT_EQ(ring, state.uart.rx_storage); + EXPECT_TRUE(state.uart.initialized); + EXPECT_TRUE(g_u575_model.irq_enabled[USART1_IRQn]); + EXPECT_TRUE(g_u575_model.irq_enabled[GPDMA1_Channel0_IRQn]); +} + +TEST_F(U575UartGpdma, ColdPrepareRefusesLiveAndRetainedRebindingUnchanged) { + const nx_stm32u575_uart_gpdma_config_t config{ + &facts, + ring, + sizeof(ring), + 115200U, + {NX_IRQ_KERNEL_NONE, 126U, 4U, 0U}, + 4000000U, + 8U}; + state.uart.retained = true; + const auto before = state; + const auto writes = g_u575_model.writes; + EXPECT_EQ(NX_ERROR_BUSY, nx_stm32u575_uart_gpdma_prepare(&state, &config)); + EXPECT_EQ(0, std::memcmp(&before, &state, sizeof(state))); + EXPECT_EQ(writes, g_u575_model.writes); + state.uart.retained = false; + state.uart.wake_publishers = 1U; + EXPECT_EQ(NX_ERROR_BUSY, nx_stm32u575_uart_gpdma_prepare(&state, &config)); + EXPECT_EQ(1U, state.uart.wake_publishers); + EXPECT_EQ(writes, g_u575_model.writes); + state.uart.wake_publishers = 0U; +} + +TEST_F(U575UartGpdma, ColdPrepareRejectsForeignRouteAndStorageAliases) { + nx_stm32u575_uart_gpdma_config_t config{&facts, + ring, + sizeof(ring), + 115200U, + {NX_IRQ_KERNEL_NONE, 126U, 4U, 0U}, + 4000000U, + 8U}; + const auto before = state; + const auto writes = g_u575_model.writes; + facts.request = 26U; + EXPECT_EQ(NX_ERROR_UNSUPPORTED, + nx_stm32u575_uart_gpdma_prepare(&state, &config)); + facts.request = 25U; + config.rx_storage = reinterpret_cast(&state); + config.rx_capacity = sizeof(state); + EXPECT_EQ(NX_ERROR_INVALID, + nx_stm32u575_uart_gpdma_prepare(&state, &config)); + config.rx_storage = reinterpret_cast(&config); + config.rx_capacity = sizeof(config); + EXPECT_EQ(NX_ERROR_INVALID, + nx_stm32u575_uart_gpdma_prepare(&state, &config)); + EXPECT_EQ(0, std::memcmp(&before, &state, sizeof(state))); + EXPECT_EQ(writes, g_u575_model.writes); +} + TEST_F(U575UartGpdma, ByteProgrammingAndFreshFlagsPrecedeEveryEnable) { ASSERT_NO_FATAL_FAILURE(Open()); GPDMA1_Channel0_NS->CSR |= DMA_CSR_TCF | DMA_CSR_SUSPF; diff --git a/tools/configure/README.md b/tools/configure/README.md index 22f57339a..a24074799 100644 --- a/tools/configure/README.md +++ b/tools/configure/README.md @@ -262,20 +262,26 @@ Descriptions add no independent acceptance rules. The resolver additionally checks hardware-specific routes, ranges, IRQ priorities, source identities and resources. Schema completion does not establish a chip or PCB capability. -`ir.py` defines frozen `ControllerIR`, `EndpointIR`, `MemoryBudgetIR`, -`CpuProfileIR`, `InterruptProfileIR` and `ConfigurationIR`. Controller options use -frozen GPIO, UART, SPI, I2C, -EXTI, PWM and ADC records. SPI and I2C endpoints also hold frozen mode/speed/CS -and address/model decisions; USB endpoints retain their bounded packet plan. -IRQ priority and kernel-call intent are a nested record; -ADC channel and sample sequences are tuples. Constructor emitters consume these -named decisions directly; reviewed hardware facts retain an immutable Mapping -projection. This does not make C factory ID aliases nominal strong types. -`resolved.json` is their JSON projection, with the same configuration identity. +`authored.py` parses the sole platform input, TOML schema 2, into frozen +`AuthoredAssembly`: unresolved user intent, not admitted hardware. The production +entry `resolve_ir()` validates exact facts, selections and conflicts before +returning `ConfigurationIR`. Its frozen named controller, endpoint, CPU/kernel, +memory and resource records are the sole stored decisions. They do not implement +the dictionary interface or retain a second `values` mapping. Nested IRQ options +are records; ADC channel/sample sequences are tuples. Reviewed JSON hardware facts +and keyed indexes remain explicitly immutable leaf mappings; temporary resolver +drafts remain local validation data. This is not a claim that every JSON key has +its own nominal Python class or that C factory ID aliases are nominal strong types. + +Private typed source epoch 2 and serialized projection schema 1 are distinct. +`to_dict()` derives `resolved.json` for audit, diagnostics and independent artifact +checks; serialization does not become another configuration input. `source_fields` +records projection-field presence, not a duplicate set of decision values. There is no mutable configuration cache or generated software dependency graph. -The schema-1 JSON reader remains for explicit legacy contract fixtures and -migration measurements. Maintained assemblies and source-SDK examples use TOML; -JSON is never consulted as a fallback for a failed TOML assembly. +Legacy JSON platform assemblies are rejected, including former production +fixture entry points. Maintained test fixtures author TOML through test-only +helpers. Historical reports keep their original bytes and read-only verification; +they cannot reenable a legacy platform input. `providers/` explicitly admits Native, STM32F103/F407/G474/H743/U575 and GD32F303/F470. Each retains its documented exact part, route and mode scope. @@ -294,7 +300,15 @@ shared-clock lifecycle emission. Each constructor returns the frozen `Construction` contract with explicit `state_type`, `definitions`, `initialize`, `stop`, `retain_on_failed_init`, `exports` and `pin_snapshots`. The common emitter consumes those named fields instead of inferring private C types or cleanup -responsibility from family names. The record exists only during generation; +responsibility from family names. `initialize` is a `DirectCall`; `stop` is a tuple +of direct calls. Their arguments admit only explicit `Symbol`/`Address` references, +uint32 constants, booleans, NULL and nested direct calls. They reject free-form +expressions, casts, assignments and function-pointer/member-call expressions. +The actual C compiler still validates symbol declarations and parameter types. +This is a narrow call +representation, not a complete C AST: `definitions`, `exports` and +`face_definitions` still contain C source, including remaining family cold +behavior. The record exists only during generation; no runtime registration or additional dispatch is emitted. The public resolver owns schema, reviewed facts, input snapshots and resource conflicts. The public binding emitter owns Nexus faces and common lifecycle progress. There is no package discovery or runtime @@ -312,7 +326,7 @@ This refactor adds no firmware registration, allocation or dispatch layer. Dependency direction stays explicit: `providers/common.py` contains leaf input checks and frozen contracts; cross-domain option/clock/IRQ validation lives in -`selection.py`. `ir.py` owns the legacy-controller-to-typed-IR adapter. DMA region +`selection.py`. `ir.py` owns frozen decisions and their one-way audit projection. DMA region rendering lives with memory policy in `memory.py`; provider bindings do not import the top-level emitter. Preflight receives the selected H743 DMA admission callback from its emitter instead of importing that emitter back. Derived diff --git a/tools/configure/authored.py b/tools/configure/authored.py index 41b85e772..061458ed7 100644 --- a/tools/configure/authored.py +++ b/tools/configure/authored.py @@ -1,5 +1,38 @@ """Small, strict TOML authoring surface; hardware facts remain JSON.""" import tomllib +from dataclasses import dataclass +from collections.abc import Mapping + +from tools.configure.ir import Record, freeze + + +@dataclass(frozen=True) +class AuthoredAssembly(Record): + """Parsed user intent; unresolved choices are never hardware decisions.""" + board_package: str + backend: str + clock_profile: str + controllers: tuple[Mapping, ...] + devices: tuple[Mapping, ...] + memory_budgets: Mapping + layout: str | None + source_fields: tuple[str, ...] + abi: str | None = None + optimization: str = "Os" + components: tuple[str, ...] = () + os: Mapping | None = None + factory: Mapping | None = None + can_domain: Mapping | None = None + power: Mapping | None = None + i2s2: Mapping | None = None + + @classmethod + def create(cls, values): + return cls(**{key: freeze(value) for key, value in values.items()}, + source_fields=tuple(values)) + + def projection(self): + return {name: getattr(self, name) for name in self.source_fields} INSTANCE_FIELDS = { "rtc": {"binding", "mode"}, @@ -126,7 +159,7 @@ def normalize(data, path, root, fail, obj, integer, text): "USB endpoint " + name) devices.append({**definition, "id": name, "driver": "usb-fs-endpoint", "authored_kind": "usb_device"}) - result = {"schema_version": 1, "board_package": package, + result = {"board_package": package, "backend": data["backend"], "clock_profile": data["clock"], "controllers": controllers, "devices": devices, "memory_budgets": budgets, "layout": data.get("layout")} @@ -136,7 +169,7 @@ def normalize(data, path, root, fail, obj, integer, text): if "i2s2" in data: result["i2s2"] = dict(obj(data["i2s2"], {"profile"}, (), "I2S2 chip hardware role")) - return result + return AuthoredAssembly.create(result) def read(path, snapshots, root, fail, obj, integer, text): diff --git a/tools/configure/bindings.py b/tools/configure/bindings.py index 22791b900..2ff131620 100644 --- a/tools/configure/bindings.py +++ b/tools/configure/bindings.py @@ -39,9 +39,9 @@ def diagnostic_helpers(): def emit(result): - provider = maintained(result["soc_family"]) if not isinstance(result, ConfigurationIR): - result = ConfigurationIR.from_validated(result) + raise TypeError("Bindings emission requires ConfigurationIR") + provider = maintained(result.soc_family) runtime = result.runtime_power is not None selections = result.controllers context_guard = "nx_arch_in_isr() || nx_arch_irq_is_masked()" @@ -161,16 +161,16 @@ def emit(result): construction = pwm_shared.constructor(owner) source.append(f'static {construction.state_type} {physical};') source.extend(construction.definitions) - initialize.append(construction.initialize) + initialize.append(construction.initialize.render()) initialization_indices.append(selection_index) retained_initialization.append(construction.retain_on_failed_init) - stop.extend((len(initialize), expression) + stop.extend((len(initialize), expression.render()) for expression in construction.stop) physical_constructed.add(physical) source.extend(pwm_shared.face(item, owner)) continue - construction = provider.constructor(item, result, result["board_bindings"], - result["routes"], result.devices) + construction = provider.constructor(item, result, result.board_bindings, + result.routes, result.devices) source += [f'static {construction.state_type} s_nx_port_{name};'] source.extend(construction.face_definitions) face_context = construction.face_context or f"&s_nx_port_{name}" @@ -186,10 +186,10 @@ def emit(result): gpio_phase.add_snapshots(result, item, construction.pin_snapshots, pin_snapshots) - initialize.append(construction.initialize) + initialize.append(construction.initialize.render()) initialization_indices.append(selection_index) retained_initialization.append(construction.retain_on_failed_init) - stop.extend((len(initialize), expression) + stop.extend((len(initialize), expression.render()) for expression in construction.stop) for device in result.devices: kind = device.kind diff --git a/tools/configure/calls.py b/tools/configure/calls.py new file mode 100644 index 000000000..8b428cb81 --- /dev/null +++ b/tools/configure/calls.py @@ -0,0 +1,92 @@ +"""Fixed C calls and static references, never arbitrary C expression fragments. + +These immutable build-time values describe selected direct calls only. They do +not add firmware metadata, dispatch tables, runtime lookup or a C interpreter. +The C compiler still establishes actual declarations and parameter types. +""" +from dataclasses import dataclass +import re + + +_IDENTIFIER = r"[A-Za-z_][A-Za-z0-9_]*" +C_KEYWORDS = frozenset(""" +auto break case char const continue default do double else enum extern float +for goto if inline int long register restrict return short signed sizeof static +struct switch typedef union unsigned void volatile while _Alignas _Alignof +_Atomic _Bool _Complex _Generic _Imaginary _Noreturn _Static_assert _Thread_local +""".split()) + + +def _name(value, pattern, purpose): + if (not isinstance(value, str) or re.fullmatch(pattern, value) is None or + any(part in C_KEYWORDS for part in value.split("."))): + raise ValueError(f"{purpose} requires an explicit C identifier") + + +@dataclass(frozen=True) +class Symbol: + """A declared static array, object or enum name used without dereference.""" + + name: str + + def __post_init__(self): + _name(self.name, _IDENTIFIER, "Static symbol") + + def render(self): + return self.name + + +@dataclass(frozen=True) +class Address: + """Address of one static object or its fixed member path.""" + + name: str + + def __post_init__(self): + _name(self.name, _IDENTIFIER + r"(?:\." + _IDENTIFIER + r")*", + "Static object address") + + def render(self): + return "&" + self.name + + +@dataclass(frozen=True) +class DirectCall: + """Call an explicit C symbol with references, constants or direct calls. + + Integers are unsigned32 values; bool and None emit true/false and NULL. + A nested call preserves explicit runtime argument evaluation, such as + forming an absolute deadline at startup. No branch, assignment, cast, + operator, string literal or function-pointer expression is accepted. + """ + + function: str + arguments: tuple = () + + def __post_init__(self): + _name(self.function, _IDENTIFIER, "Direct call") + if not isinstance(self.arguments, (tuple, list)): + raise ValueError("Direct call requires an ordered argument sequence") + arguments = tuple(self.arguments) + for argument in arguments: + if isinstance(argument, (Address, Symbol, DirectCall)): + continue + if argument is None or type(argument) is bool: + continue + if type(argument) is int and 0 <= argument <= 0xffffffff: + continue + raise ValueError("Direct call requires typed references or constants") + object.__setattr__(self, "arguments", arguments) + + def render(self): + arguments = [] + for value in self.arguments: + if isinstance(value, (Address, Symbol, DirectCall)): + arguments.append(value.render()) + elif value is None: + arguments.append("NULL") + elif type(value) is bool: + arguments.append("true" if value else "false") + else: + arguments.append(str(value) + "u") + return self.function + "(" + ", ".join(arguments) + ")" diff --git a/tools/configure/can.py b/tools/configure/can.py index 1dbf96ee8..a7e6da0ea 100644 --- a/tools/configure/can.py +++ b/tools/configure/can.py @@ -4,6 +4,7 @@ The emitted C calls the real common controller directly; no runtime registry, forwarding face, protocol retry policy or inferred transceiver is introduced. """ +from tools.configure.calls import DirectCall from dataclasses import dataclass from tools.configure.gpio_phase import withdraw_pin @@ -135,24 +136,24 @@ def resolve_domain(authored, selected, soc, provider, claim, claims): def state_declarations(result, prefix): - domain = result.get("can_domain") + domain = result.can_domain if domain is None: return [] policy = _integration(result, prefix) - filters = domain["filters"] + filters = domain.filters lines = [] if filters: lines += ["static const nx_can_filter_t s_nx_can_filters[] = {"] for item in filters: - values = [f"{item[name]}u" for name in ("id", "mask", "bank")] - values += [f"{item['controller_index']}u", f"{item['fifo']}u", - "NX_CAN_" + item["format"].upper(), - str(item["remote"]).lower(), str(item["match_remote"]).lower()] + values = [f"{getattr(item, name)}u" for name in ("id", "mask", "bank")] + values += [f"{item.controller_index}u", f"{item.fifo}u", + "NX_CAN_" + item.format.upper(), + str(item.remote).lower(), str(item.match_remote).lower()] lines.append(" { " + ", ".join(values) + " },") lines.append("};") lines += ["static nx_can_filter_domain_t s_nx_can_domain = {", f" .filters = {'s_nx_can_filters' if filters else 'NULL'},", - f" .filter_count = {len(filters)}u, .split = {domain['split']}u", "};"] + f" .filter_count = {len(filters)}u, .split = {domain.split}u", "};"] if policy.gpio_clock is not None: lines.append("static uint32_t s_nx_can_gpio_clocks_added;") return lines @@ -166,7 +167,7 @@ def constructor(item, result, prefix): name = item.id.replace("-", "_") options = item.options state = "s_nx_port_" + name - pins = item["pins"] + pins = item.pins clock = integration.gpio_clock mask = sum(1 << (ord(port) - ord('A')) for port in {p['pin'][1] for p in pins}) lines = [f"static nx_can_event_t s_nx_can_events_{name}[{options.event_capacity}];", @@ -183,10 +184,10 @@ def constructor(item, result, prefix): f" s_nx_can_pin_leases_{name} &= (uint8_t)~{1 << index}u;", " }"] lines += [" return NX_SUCCESS;", "}", f"static nx_result_t prepare_{name}(void) {{", " nx_result_t status;"] - policy = result['irq'] + policy = result.irq kernel = kernel_policy_constant(result) - ceiling = policy['syscall_priority'] or 0 - controller_index = result['can_controllers'].index(item['controller']) + ceiling = policy.syscall_priority or 0 + controller_index = result.can_controllers.index(item.controller) t = options.timing lines += [f" {state} = (nx_can_controller_t){{", " .domain = &s_nx_can_domain,", @@ -195,7 +196,7 @@ def constructor(item, result, prefix): f" .drain_timeout_us = {options.drain_timeout_us}u, .poll_limit = {options.poll_limit}u,", f" .mode = {OPERATIONS[options.operation]}, .index = {controller_index}u,", f" .fifo_order = {str(options.fifo_order).lower()},", - f" .irq_policy = {{ {kernel}, {policy['external_count']}u, {policy['priority_bits']}u, {ceiling}u }},", + f" .irq_policy = {{ {kernel}, {policy.external_count}u, {policy.priority_bits}u, {ceiling}u }},", " .sources = { " + ', '.join('{ .priority = '+str(options.irq.priority)+'u }' for _ in range(4)) + " }", " };"] if clock is not None: lines.append(f" s_nx_can_gpio_clocks_added |= ~{clock} & {mask}u;") @@ -211,13 +212,13 @@ def constructor(item, result, prefix): f" status = nx_{prefix}_can_domain_initialize(&s_nx_can_domain);", " if (status != NX_SUCCESS) { return status; }", " }", f" return nx_{prefix}_can_initialize(&{state});", "}"] - for vector in item['can_irqs']: + for vector in item.can_irqs: lines += [f"void {vector}_IRQHandler(void);", "/** \\brief Dispatch the exact selected classic CAN hardware source. */", f"void {vector}_IRQHandler(void) {{ nx_can_controller_irq(&{state}); }}"] return Construction( state_type="nx_can_controller_t", definitions=lines, - initialize=f"prepare_{name}()", stop=[f"stop_{name}()"], + initialize=DirectCall(f"prepare_{name}"), stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True, pin_snapshots={pin["pin"]: f"s_nx_can_pin_snapshots_{name}[{index}]" @@ -225,7 +226,7 @@ def constructor(item, result, prefix): def stop_domain(result, prefix): - if result.get('can_domain') is None: + if result.can_domain is None: return [] clock = _integration(result, prefix).gpio_clock lines = [" if (s_nx_can_domain.primary != 0u) {", diff --git a/tools/configure/configure.py b/tools/configure/configure.py index 652f086f1..4d4c03cec 100644 --- a/tools/configure/configure.py +++ b/tools/configure/configure.py @@ -4,6 +4,7 @@ CMake remains the authority for source targets. This tool never discovers source code, creates workers, selects application policy, or invents a hardware route. """ +from collections.abc import Mapping from contextlib import contextmanager from functools import lru_cache import hashlib @@ -22,11 +23,13 @@ from tools.configure import cpu as cpu_profiles from tools.configure import bundle, dependencies, sdk, i2s2 +from tools.configure.calls import C_KEYWORDS as C11_KEYWORDS from tools.configure import resource_graph, trigger_graph, dma_topology from tools.configure.contracts import COMPONENTS from tools.configure import kernel, memory as memory_regions, metadata, factory, can, threadx, usb_device, ethernet, timer_advanced, secondary_controllers, fact_references, clocks, physical_facts from tools.configure.providers import descriptor, maintained from tools.configure.selection import (validate_clock_profiles, resolve_interrupts, + validate_interrupt_profile, validate_selection) from tools.configure.providers.common import (ConfigurationError, cpu_abi, fail, integer, mode_traits, route_dma, @@ -39,7 +42,7 @@ KINDS = {"rtc", "lptim", "gpio", "uart", "spi", "i2c", "flash", "watchdog", "exti", "pwm", "adc", "can", "usb_device", "eth", "eth_frames", "timer_capture", "advanced_pwm", "pwm_group", "encoder", "pwm_adc", "can_fd", "adc_group"} -C_KEYWORDS = set("auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while _Alignas _Alignof _Atomic _Bool _Complex _Generic _Imaginary _Noreturn _Static_assert _Thread_local main".split()) +C_KEYWORDS = C11_KEYWORDS | {"main"} @contextmanager @@ -382,8 +385,6 @@ def load_assembly(path, root=ROOT, snapshots=None): if path.suffix == ".toml": from tools.configure import authored return authored.read(path, snapshots, root.resolve(), fail, obj, integer, text) - if path.suffix == ".json": - return load(path, snapshots) fail("Assembly must use authored TOML (.toml)") @@ -535,14 +536,16 @@ def bind_input_identities(inputs, root, snapshots, input_paths): def resolve_components(assembly): """Validate component intent; CMake chooses its source targets.""" - components = sequence(assembly.get("components", []), "components") + components = sequence(list(assembly.components) if + isinstance(assembly.components, tuple) else + assembly.components, "components") for component in components: text(component, "component") unique(components, "components") for component in components: if component not in COMPONENTS: fail(f"Unknown maintained component: {component}") - if any(device["driver"] == "bmp280" for device in assembly["devices"]): + if any(device["driver"] == "bmp280" for device in assembly.devices): if "bmp280-spi" not in components: fail("BMP280 SPI device requires the maintained bmp280-spi component") return components @@ -606,7 +609,7 @@ def board_directory(path): return path.resolve() -def resolve(assembly_path, root=ROOT, *, input_paths=None): +def _resolve(assembly_path, root=ROOT, *, input_paths=None): """Validate all input facts before producing the sole resolved decision.""" root = root.resolve() verified_sdk = sdk.verify(root) @@ -623,18 +626,16 @@ def source_dependency(relative, expected=None): policy_path = contained_file(root, metadata.POLICY) platform_metadata = metadata.policy_contract( read(policy_path), expected_factory_epoch=factory.FACTORY_ABI_EPOCH) - assembly_input = load_assembly(assembly_path, root, snapshots) - assembly = obj(assembly_input, - {"schema_version", "board_package", "backend", "clock_profile", - "controllers", "devices", "memory_budgets", "layout"}, - {"abi", "optimization", "components", "os", "factory", "can_domain", "power", "i2s2"}, "assembly") - integer(assembly["schema_version"], 1, 1, "assembly schema") - factory_options = obj(assembly.get("factory", {}), (), + assembly = load_assembly(assembly_path, root, snapshots) + from tools.configure.ir import thaw + controller_selections = thaw(assembly.controllers) + endpoint_selections = thaw(assembly.devices) + factory_options = obj(thaw(assembly.factory) if assembly.factory is not None else {}, (), {"capabilities"}, "factory policy") if ("capabilities" in factory_options and type(factory_options["capabilities"]) is not bool): fail("Factory capabilities must be boolean") - board_name = text(assembly["board_package"], "Board package") + board_name = text(assembly.board_package, "Board package") board_dir = board_directory(assembly_path.parent / board_name) board_path = board_dir / "board.json" board = obj(read(board_path), @@ -665,7 +666,7 @@ def source_dependency(relative, expected=None): integer(routes_doc["schema_version"], 1, 1, "routes schema") routes = resolve_routes(routes_doc, soc, part, provider) validate_board_context(board) - backend = assembly["backend"] + backend = assembly.backend if backend not in {"baremetal", "freertos", "native", "threadx"}: fail(f"Unsupported OS backend: {backend}") host = soc["cpu"]["arch"] == "native" @@ -674,33 +675,33 @@ def source_dependency(relative, expected=None): fail("Native is a host model; its backend cannot qualify MCU execution") if backend == "threadx": threadx.validate_platform(board["id"], family) - resolve_interrupts(soc["cpu"], soc["irq"], backend, provider) cpu_profile = cpu_profiles.resolve(soc["cpu"], soc["irq"], backend, enum_abi=provider.ENUM_ABI) - clock = soc["clock_profiles"].get(assembly["clock_profile"]) + validate_interrupt_profile(cpu_profile, provider) + clock = soc["clock_profiles"].get(assembly.clock_profile) if clock is None: - fail(f"Unknown clock profile: {assembly['clock_profile']}") - clock_plan = provider.clock_plan(assembly["clock_profile"], clock) + fail(f"Unknown clock profile: {assembly.clock_profile}") + clock_plan = provider.clock_plan(assembly.clock_profile, clock) clock = provider.normalize_clock(clock) clocks.match_board(clock, board["clocks"]) - i2s2_role = i2s2.resolve(assembly.get("i2s2"), family=family, + i2s2_role = i2s2.resolve(thaw(assembly.i2s2), family=family, part=part, backend=backend, - clock_profile=assembly["clock_profile"]) - if "os" in assembly and not isinstance(assembly["os"], dict): + clock_profile=assembly.clock_profile) + if assembly.os is not None and not isinstance(assembly.os, Mapping): fail("OS policy: expected object") - kernel_profile = kernel.resolve(assembly.get("os"), cpu_profile, + kernel_profile = kernel.resolve(thaw(assembly.os), cpu_profile, clock["core_hz"]) if (backend == "freertos" and kernel_profile.lowpower_port == "timebase" and not provider.TIMEBASE_ALARM): fail("Timebase lowpower requires the maintained STM32F407/GD32F470 SoC alarm") cpu_profile = kernel.bind(cpu_profile, kernel_profile) - irq_profile = cpu_profile.to_dict()["irq"] - if "abi" in assembly and assembly["abi"] != cpu_abi(soc["cpu"]): + irq_profile = cpu_profile.irq + if assembly.abi is not None and assembly.abi != cpu_abi(soc["cpu"]): fail("Assembly ABI contradicts the selected SoC") - optimization = assembly.get("optimization", "Os") + optimization = assembly.optimization if optimization not in {"O0", "Og", "O2", "Os", "O3", "O2-lto", "Os-lto", "O3-lto"}: fail(f"Unsupported optimization profile: {optimization}") - budgets = obj(assembly["memory_budgets"], {"main_stack_bytes"}, + budgets = obj(thaw(assembly.memory_budgets), {"main_stack_bytes"}, {"flash_load_bytes", "static_ram_bytes", "libc_heap_bytes", "regions", "mpu"}, "memory budgets") for name, value in budgets.items(): @@ -756,17 +757,17 @@ def source_dependency(relative, expected=None): if backend == "freertos": for irq in ("SysTick", "PendSV", "SVC"): claim("irq:" + irq, "freertos", "kernel", - "FreeRTOS " + irq_profile["kernel_port"]) + "FreeRTOS " + irq_profile.kernel_port) elif backend == "threadx" and not host: for irq in ("PendSV", *(["SysTick"] if kernel_profile.tick_source == "systick" else [])): claim("irq:" + irq, "threadx", "kernel", - "ThreadX " + irq_profile["kernel_port"]) + "ThreadX " + irq_profile.kernel_port) selected = [] adc_group_contract = None instance_ids = [] irq_owners = {} - for selection in sequence(assembly["controllers"], "selected controllers"): + for selection in sequence(controller_selections, "selected controllers"): location = (selection.get("id", "") if isinstance(selection, dict) else "") binding = selection.get("binding") if isinstance(selection, dict) else None @@ -808,10 +809,10 @@ def source_dependency(relative, expected=None): resolved_pin_states = pin_states.select( binding, family=family, part=part, controller=route["controller"], backend=backend, - clock=assembly["clock_profile"]) + clock=assembly.clock_profile) provider.validate_mode_context( selection, route, backend=backend, - clock_profile=assembly["clock_profile"]) + clock_profile=assembly.clock_profile) for numeric in {"rx_capacity", "baud", "max_hz", "event_capacity", "period_ticks", "sample_cycles", "timeout_ms"} & selection.keys(): integer(selection[numeric], 1, context=numeric) @@ -821,7 +822,7 @@ def source_dependency(relative, expected=None): fail("PWM duty exceeds period") if "irq_priority" in selection: integer(selection["irq_priority"], 0, - irq_profile["maximum_priority"], "IRQ priority") + irq_profile.maximum_priority, "IRQ priority") if route["kind"] == "spi" and mode == "short-poll" and ({"irq_priority", "calls_os"} & selection.keys()): fail("Polling SPI has no IRQ options") if route["kind"] == "spi" and mode_traits("spi", mode).dma and "irq_priority" not in selection: @@ -831,7 +832,7 @@ def source_dependency(relative, expected=None): if selection.get("calls_os", False) and ( kernel_profile is None or host or kernel_profile.kernel_policy == "none" or - selection.get("irq_priority", 0) < irq_profile["syscall_priority"]): + selection.get("irq_priority", 0) < irq_profile.syscall_priority): fail("OS-calling ISR violates the selected kernel syscall ceiling") if selection.get("max_hz", 0) > hardware.get("max_hz", UINT32_MAX): fail("Controller speed exceeds maintained limit") @@ -895,7 +896,7 @@ def source_dependency(relative, expected=None): if resolved_stream is not None else {}), **({"can_irqs": hardware["irq"]} if route["kind"] == "can" else {})}) selected[-1].update(provider.mode_fields( - selection, route, clock_profile=assembly["clock_profile"])) + selection, route, clock_profile=assembly.clock_profile)) if route["kind"] == "can_fd": selected[-1]["message_ram_words"] = hardware["message_ram_words"] if route["kind"] == "adc_group": @@ -915,7 +916,7 @@ def source_dependency(relative, expected=None): selection, adc_group_review, adc_group_digest, part=part, clock=clock)) provider.validate_controller_set( - selected, clock_profile=assembly["clock_profile"]) + selected, clock_profile=assembly.clock_profile) ethernet.resolve_counter_domains(selected) pwm_shared_owners = [] pwm_sharing = {} @@ -924,18 +925,18 @@ def source_dependency(relative, expected=None): if provider.PWM_SHARED is None: fail("Shared PWM has no maintained physical owner") pwm_shared_owners = provider.PWM_SHARED.resolve( - selected, bindings, soc, clock, assembly["clock_profile"]) + selected, bindings, soc, clock, assembly.clock_profile) physical_claims, pwm_sharing = provider.PWM_SHARED.resources( pwm_shared_owners) claim_list.extend(physical_claims) unique(instance_ids, "controller instance IDs") unique([c_id(name).upper() for name in instance_ids], "generated C identifiers") - can_domain = can.resolve_domain(assembly.get("can_domain"), selected, + can_domain = can.resolve_domain(thaw(assembly.can_domain), selected, soc, provider, claim, claims) device_ids = [] addresses = set() chip_selects = set() - for device in sequence(assembly["devices"], "devices"): + for device in sequence(endpoint_selections, "devices"): location = (device.get("id", "") if isinstance(device, dict) else "") context = f"endpoint {location}" @@ -1021,9 +1022,9 @@ def source_dependency(relative, expected=None): addresses.add(key) unique(device_ids + instance_ids, "assembly instance IDs") unique([c_id(name).upper() for name in device_ids + instance_ids], "generated C identifiers") - usb_device.complete_plan(selected, assembly["devices"], claim) + usb_device.complete_plan(selected, endpoint_selections, claim) for controller in selected: - children = [item for item in assembly["devices"] if item["controller"] == controller["id"]] + children = [item for item in endpoint_selections if item["controller"] == controller["id"]] if (provider.ENDPOINT_POLICY.model_async_echo and controller["kind"] == "spi" and controller["mode"] == "async-model" and not children): @@ -1050,8 +1051,8 @@ def source_dependency(relative, expected=None): inputs.append(reference_path) for name in sequence(board.get("inputs", []), "declared Board inputs"): inputs.append(contained_file(board_dir, name)) - if assembly["layout"] is not None: - layout_path = (assembly_path.parent / text(assembly["layout"], "layout path")).resolve() + if assembly.layout is not None: + layout_path = (assembly_path.parent / text(assembly.layout, "layout path")).resolve() layout_doc = obj(read(layout_path), {"schema_version", "regions"}, (), "layout") integer(layout_doc["schema_version"], 1, 1, "layout schema") boundaries = {variant["flash"]["origin"]} @@ -1113,56 +1114,67 @@ def source_dependency(relative, expected=None): inputs.append(manifest) records = bind_input_identities(inputs, root, snapshots, input_paths) components = resolve_components(assembly) - result = {"schema_version": 1, "platform_metadata": platform_metadata, - "board": board["id"], "soc_family": family, - "part": part, "package": variant["package"], "backend": backend, - "cpu": soc["cpu"], "irq": irq_profile, - "cpu_profile": cpu_profile.to_dict(), - "kernel_profile": (kernel_profile.to_dict() - if kernel_profile is not None else None), - "clock_profile": assembly["clock_profile"], - "clock": clock, "memory": selected_memory, "flash": variant["flash"], - "layout": layout, "controllers": selected, "devices": assembly["devices"], - "can_domain": can_domain, - "can_controllers": list(provider.CAN_CONTROLLERS) if can_domain else [], - "components": components, "claims": claim_list, "memory_budgets": budgets, - "board_bindings": bindings, "routes": routes, - "optimization": optimization, "inputs": records, - "source_dependencies": source_dependencies, - "physical_pcb_revision": board["physical_pcb_revision"], - "limitations": board["unknowns"] + ["Physical qualification not executed"], - "physical_qualified": False, - "enum_abi": provider.ENUM_ABI} - if i2s2_role is not None: - result["i2s2"] = {"profile": i2s2_role.profile, "part": i2s2_role.part, - "claims": list(i2s2_role.claims)} - from tools.configure import runtime_power - runtime = (runtime_power.resolve(assembly["power"], result) - if "power" in assembly else None) - if runtime is not None: - result["runtime_power"] = {"mode": runtime.mode, "controllers": list(runtime.controllers)} - result["factory"] = {"capabilities": factory_options.get("capabilities", False)} - if clock_plan is not None: - result["clock_plan"] = clock_plan - if pwm_shared_owners: - result["pwm_shared_owners"] = pwm_shared_owners - result["resource_plan"] = resource_graph.resolve( - claim_list, shared=pwm_sharing).to_dict() - else: - result["resource_plan"] = resource_graph.resolve(claim_list).to_dict() - result["trigger_plan"] = trigger_graph.resolve_fixed(selected, routes).to_dict() + resources = resource_graph.resolve(claim_list, shared=pwm_sharing) + triggers = trigger_graph.resolve_fixed(selected, routes) for item in selected: if item["kind"] == "adc_group" and item["trigger"] == "tim6-trgo": item["adc_group_resolution"]["trigger_identity"] = next( - index + 1 for index, edge in enumerate( - result["trigger_plan"]["edges"]) - if edge["target"] == item["id"] + ":adc") - result["dma_topology"] = dma_topology.resolve_selected(selected, provider).to_dict() - if references is not None: - result["fact_references"] = references - result["configuration_sha256"] = hashlib.sha256( - json.dumps(result, sort_keys=True, separators=(",", ":")).encode()).hexdigest() - return result + index + 1 for index, edge in enumerate(triggers.edges) + if edge.target == item["id"] + ":adc") + dma_plan = dma_topology.resolve_selected(selected, provider) + from dataclasses import replace + from tools.configure.ir import ( + CanDomainIR, CanFilterIR, ClockPlanIR, ConfigurationIR, ControllerIR, + EndpointIR, FactoryOptions, MemoryBudgetIR, MemoryRegionBudgetIR, + MpuMemoryReservationsIR, PhysicalPartIR, PwmSharedOwnerIR, + RuntimePowerIR, freeze) + controllers = tuple(ControllerIR.create(item, family) for item in selected) + devices = tuple(EndpointIR.create(item, controllers) + for item in endpoint_selections) + memory_budget = MemoryBudgetIR( + budgets["main_stack_bytes"], budgets.get("libc_heap_bytes", 0), + budgets.get("static_ram_bytes"), budgets.get("flash_load_bytes"), + tuple(MemoryRegionBudgetIR(name, item["limit_bytes"]) + for name, item in sorted(budgets.get("regions", {}).items())), + MpuMemoryReservationsIR(**budgets["mpu"]) if "mpu" in budgets else None) + resolved = ConfigurationIR( + board=board["id"], soc_family=family, + cpu_profile=cpu_profile, kernel=kernel_profile, + controllers=controllers, devices=devices, memory_budget=memory_budget, + factory=FactoryOptions(factory_options.get("capabilities", False)), + can_domain=(CanDomainIR(can_domain["split"], tuple( + CanFilterIR(**item) for item in can_domain["filters"])) + if can_domain is not None else None), + resource_plan=resources, trigger_plan=triggers, dma_topology=dma_plan, + clock_plan=(ClockPlanIR.from_validated(clock_plan) + if clock_plan is not None else None), + physical_part=PhysicalPartIR(part, variant["package"], + freeze(variant["flash"]), freeze(selected_memory)), + clock=freeze(clock), clock_profile=assembly.clock_profile, + layout=freeze(layout), board_bindings=freeze(bindings), + routes=freeze(routes), platform_metadata=freeze(platform_metadata), + inputs=freeze(records), source_dependencies=freeze(source_dependencies), + components=tuple(components), + can_controllers=tuple(provider.CAN_CONTROLLERS) if can_domain else (), + optimization=optimization, + physical_pcb_revision=board["physical_pcb_revision"], + limitations=tuple(board["unknowns"]) + ("Physical qualification not executed",), + physical_qualified=False, + fact_references=freeze(references), + pwm_shared_owners=tuple(PwmSharedOwnerIR.from_validated(item) + for item in pwm_shared_owners), + i2s2=i2s2_role) + resolved = resolved.with_integrations() + if assembly.power is not None: + from tools.configure import runtime_power + runtime = runtime_power.resolve(thaw(assembly.power), resolved) + resolved = replace(resolved, runtime_power=RuntimePowerIR( + runtime.mode, tuple(runtime.controllers))) + projection = resolved.to_dict() + del projection["configuration_sha256"] + digest = hashlib.sha256(json.dumps( + projection, sort_keys=True, separators=(",", ":")).encode()).hexdigest() + return replace(resolved, configuration_sha256=digest) def c_id(value): @@ -1174,10 +1186,8 @@ def c_id(value): def resolve_ir(assembly_path, root=ROOT, *, input_paths=None): """Return the immutable typed resolution consumed by every emitter.""" - from tools.configure.ir import ConfigurationIR try: - return ConfigurationIR.from_validated( - resolve(assembly_path, root, input_paths=input_paths)) + return _resolve(assembly_path, root, input_paths=input_paths) except ConfigurationError as error: raise ConfigurationError(f"{assembly_path}: {error}", **error.details) from error @@ -1199,8 +1209,7 @@ def protected_inputs(assembly, root): paths = [assembly, root.resolve(), ROOT, root / metadata.POLICY] try: content = assembly.read_text(encoding="utf-8") - raw = (tomllib.loads(content) if assembly.suffix == ".toml" else - json.loads(content)) + raw = tomllib.loads(content) if assembly.suffix == ".toml" else {} except (OSError, ValueError): return paths if not isinstance(raw, dict): @@ -1248,7 +1257,7 @@ def configure(assembly, output, root=ROOT): dependencies.emit(temporary, code_paths, code_identity) # Publish only if every snapshotted authored or package input still # matches the resolved identity, including compatibility metadata. - for record in result["inputs"]: + for record in result.inputs: if file_digest(Path(input_paths[record["path"]])) != record["sha256"]: fail(f"Input changed during generation: {record['path']}") dependencies.verify(initial_code_paths, initial_code_identity) diff --git a/tools/configure/cpu.py b/tools/configure/cpu.py index 8dd72c906..7fb8cf1af 100644 --- a/tools/configure/cpu.py +++ b/tools/configure/cpu.py @@ -286,7 +286,7 @@ def emit_profile(profile, output, *, clock_hz, optimization="Os", inputhash, fail("CPU emission requires an empty temporary directory") if kernel_profile is None: kernel_profile = kernel.resolve(None, profile, clock_hz) - if kernel_profile is not None and kernel_profile.get("lowpower_port") == "timebase": + if kernel_profile is not None and getattr(kernel_profile, "lowpower_port", None) == "timebase": fail("CPU-only lowpower runtime requires reviewed SoC timer facts") profile = kernel.bind(profile, kernel_profile) resolved = {"schema_version": 1, "platform_metadata": platform_metadata, diff --git a/tools/configure/dependencies.py b/tools/configure/dependencies.py index 5234ecef3..4e51528d8 100644 --- a/tools/configure/dependencies.py +++ b/tools/configure/dependencies.py @@ -8,7 +8,7 @@ COMMON = ( "__init__", "bundle", "dependencies", "sdk", "cpu", "kernel", "metadata", "factory", "ir", "i2s2", "threadx", "lowpower", "providers/__init__", "providers/common", - "providers/descriptor", + "providers/descriptor", "calls", "resource_graph", "trigger_graph", "dma_topology", "io_traits", "selected_contracts", "enhanced_endpoints", "uart_formats", "stream_profiles", "usb_profiles", diff --git a/tools/configure/documentation.py b/tools/configure/documentation.py index ba8115553..5cf49b9ab 100644 --- a/tools/configure/documentation.py +++ b/tools/configure/documentation.py @@ -7,13 +7,39 @@ import tempfile import tomllib -from tools.configure import authored, configure, runtime +from tools.configure import configure, runtime FENCE = re.compile(r"^```(?Ptoml|json)[ \t]*\n" r"(?P.*?)^```[ \t]*$", re.MULTILINE | re.DOTALL) FRAGMENT = re.compile(r"$") +def _toml_value(value): + """Preserve parsed example values while relocating only external paths.""" + if isinstance(value, dict): + return "{" + ", ".join(json.dumps(key) + " = " + _toml_value(item) + for key, item in value.items()) + "}" + if isinstance(value, list): + return "[" + ", ".join(_toml_value(item) for item in value) + "]" + if type(value) in (str, bool, int, float): + return json.dumps(value, ensure_ascii=False, allow_nan=False) + raise ValueError("Unsupported value in authored TOML example") + + +def _assembly(block, document, temporary): + """Relocate document-owned paths without a legacy JSON configuration input.""" + relocated = dict(block) + for key in ("board_package", "layout"): + value = relocated.get(key) + if isinstance(value, str) and not Path(value).is_absolute(): + relocated[key] = str(document.parent / value) + path = temporary / "assembly.toml" + path.write_text("\n".join(json.dumps(key) + " = " + _toml_value(value) + for key, value in relocated.items()) + "\n", + encoding="utf-8") + return path + + def _runtime(block, following, document, temporary): relative = block.get("cpu_facts") if (not isinstance(relative, str) or not relative or @@ -36,7 +62,7 @@ def _runtime(block, following, document, temporary): def check_document(document, root=configure.ROOT, *, contents=None): """Run production resolution; a fragment marker requires a stated reason.""" - document = Path(document) + document = Path(document).absolute() contents = document.read_text(encoding="utf-8") if contents is None else contents openings = len(re.findall(r"^```toml[ \t]*$", contents, re.MULTILINE)) matches = [match for match in FENCE.finditer(contents) @@ -61,22 +87,9 @@ def check_document(document, root=configure.ROOT, *, contents=None): with tempfile.TemporaryDirectory(prefix="nexus-doc-config-") as name: temporary = Path(name) if "schema" in block: - # Preserve authored validation before using the explicit - # legacy projection to relocate external package paths. - normalized = authored.normalize( - block, document, root, configure.fail, configure.obj, - configure.integer, configure.text) - package = Path(normalized["board_package"]) - if not package.is_absolute(): - normalized["board_package"] = str(document.parent / package) - if normalized.get("layout") is not None: - layout = Path(normalized["layout"]) - if not layout.is_absolute(): - normalized["layout"] = str(document.parent / layout) - assembly = temporary / "assembly.json" - assembly.write_text(json.dumps(normalized), encoding="utf-8") - result = configure.resolve(assembly, root) - digest, kind = result["configuration_sha256"], "assembly" + assembly = _assembly(block, document, temporary) + result = configure.resolve_ir(assembly, root) + digest, kind = result.configuration_sha256, "assembly" elif "cpu_facts" in block: _runtime(block, contents[match.end():], document, temporary) assembly = temporary / "runtime.toml" diff --git a/tools/configure/emission.py b/tools/configure/emission.py index a7fc7f5bd..b688b203f 100644 --- a/tools/configure/emission.py +++ b/tools/configure/emission.py @@ -14,27 +14,25 @@ def emit_bundle(result, output): from tools.configure import kernel, memory as memory_regions, metadata, i2s2 from tools.configure.ir import thaw from tools.configure.providers import descriptor, maintained - write = lambda name, data: (output / name).write_text(data, encoding="utf-8") - write("resolved.json", json.dumps(result.to_dict() if hasattr(result, "to_dict") - else result, indent=2, sort_keys=True) + "\n") - # Compatibility fixtures enter through one adapter; normal generation uses - # the already validated immutable CPU/kernel/instance decisions directly. from tools.configure.ir import ConfigurationIR if not isinstance(result, ConfigurationIR): - result = ConfigurationIR.from_validated(result) + raise TypeError("Bundle emission requires ConfigurationIR") + write = lambda name, data: (output / name).write_text(data, encoding="utf-8") + write("resolved.json", json.dumps(result.to_dict(), indent=2, + sort_keys=True) + "\n") profile, policy = result.cpu_profile, result.kernel header = ["/* Generated from the sole resolved configuration. */", "#ifndef NEXUS_CONFIG_H", "#define NEXUS_CONFIG_H", - f'#define NEXUS_CONFIG_SHA256 "{result["configuration_sha256"]}"', - f'#define NEXUS_EXACT_PART "{result["part"]}"', - f'#define NEXUS_CORE_HZ {result["clock"]["core_hz"]}u', - f'#define NEXUS_CLOCK_INPUT_HZ {result["clock"]["input_hz"]}u', - f'#define NEXUS_CLOCK_SOURCE_{result["clock"]["source"].upper()} 1', + f'#define NEXUS_CONFIG_SHA256 "{result.configuration_sha256}"', + f'#define NEXUS_EXACT_PART "{result.part}"', + f'#define NEXUS_CORE_HZ {result.clock["core_hz"]}u', + f'#define NEXUS_CLOCK_INPUT_HZ {result.clock["input_hz"]}u', + f'#define NEXUS_CLOCK_SOURCE_{result.clock["source"].upper()} 1', f'#define NEXUS_MAIN_STACK_BYTES {result.memory_budget.main_stack_bytes}u', - f'#define NEXUS_BACKEND_{result["backend"].upper()} 1', + f'#define NEXUS_BACKEND_{result.backend.upper()} 1', *cpu_profiles.header_lines(profile), *kernel.header_lines(policy), *memory_regions.header_lines(result), - *metadata.header_lines(thaw(result["platform_metadata"]))] + *metadata.header_lines(thaw(result.platform_metadata))] header.extend(i2s2.header_lines(result.i2s2)) probes = any(getattr(getattr(item.options, "irq", None), "probe", False) for item in result.controllers) @@ -43,23 +41,23 @@ def emit_bundle(result, output): '#error "Probe choice must match the selected Platform usage"', "#endif", "#ifndef NX_IO_CYCLE_PROBES", "#define NX_IO_CYCLE_PROBES 1", "#endif"] - if result["clock"]["source"] == "hse": - header.insert(7, f'#define NEXUS_HSE_HZ {result["clock"]["input_hz"]}u') + if result.clock["source"] == "hse": + header.insert(7, f'#define NEXUS_HSE_HZ {result.clock["input_hz"]}u') header.extend(maintained(result.soc_family).configuration_header(result)) - cmake = [f'set(NEXUS_SOC_FAMILY "{result["soc_family"]}")', + cmake = [f'set(NEXUS_SOC_FAMILY "{result.soc_family}")', 'set(NEXUS_SOC_SOURCE_DIRECTORY "' + - descriptor.maintained(result["soc_family"]).source_directory + '")', - f'set(NEXUS_EXACT_PART "{result["part"]}")', + descriptor.maintained(result.soc_family).source_directory + '")', + f'set(NEXUS_EXACT_PART "{result.part}")', 'set(NEXUS_PROVIDER_CONFIG_MODULES "' + ';'.join( - descriptor.maintained(result["soc_family"]).configuration_modules) + descriptor.maintained(result.soc_family).configuration_modules) + '")', *cpu_profiles.selection_lines(profile), *kernel.selection_lines(policy), *memory_regions.selection_lines(result), - f'set(NEXUS_CONFIG_SHA256 "{result["configuration_sha256"]}")', - f'set(NEXUS_OPTIMIZATION "{result["optimization"]}")', - 'set(NEXUS_SELECTED_KINDS "' + ';'.join(sorted({item['kind'] for item in result['controllers']})) + '")', - 'set(NEXUS_SELECTED_COMPONENTS "' + ';'.join(result['components']) + '")'] + f'set(NEXUS_CONFIG_SHA256 "{result.configuration_sha256}")', + f'set(NEXUS_OPTIMIZATION "{result.optimization}")', + 'set(NEXUS_SELECTED_KINDS "' + ';'.join(sorted({item.kind for item in result.controllers})) + '")', + 'set(NEXUS_SELECTED_COMPONENTS "' + ';'.join(result.components) + '")'] # Canonical source directories also serve multi-family host models. These # paths carry no translation-unit graph or additional support decisions. cmake += [f'set(NEXUS_SOC_SOURCE_{item.family} "${{NEXUS_SOURCE_DIR}}/' + @@ -96,4 +94,4 @@ def emit_bundle(result, output): write("memory.ld", memory_regions.linker(result)) write("resource-budget.json", json.dumps(memory_regions.resource_budget(result), indent=2, sort_keys=True) + "\n") - write(".nexus-config-bundle", result["configuration_sha256"] + "\n") + write(".nexus-config-bundle", result.configuration_sha256 + "\n") diff --git a/tools/configure/enhanced_endpoints.py b/tools/configure/enhanced_endpoints.py index d5aced769..69d490eb8 100644 --- a/tools/configure/enhanced_endpoints.py +++ b/tools/configure/enhanced_endpoints.py @@ -2,6 +2,7 @@ from dataclasses import replace from tools.configure import selected_contracts +from tools.configure.calls import DirectCall from tools.configure.providers import maintained from tools.configure.providers.common import fail @@ -68,20 +69,20 @@ def dma_construction(item, construction, prefix): name = item.id.replace("-", "_") sequence = f"s_nx_sequence_{name}" backend = f"nx_{provider.PREFIX}_spi_dma_sequence_backend" - definitions = [line.replace(f"prepare_{name}(", f"prepare_engine_{name}(") - .replace(f"nx_spi_port_stop(nx_binding_{name})", - f"nx_spi_dma_transaction_stop(nx_binding_{name})") - .replace(f"nx_spi_port_stop(&s_nx_face_{name})", - f"nx_spi_dma_transaction_stop(&s_nx_face_{name})") - for line in construction.definitions] - definitions += [f"static nx_result_t prepare_{name}(void) {{", + definitions = list(construction.definitions) + definitions += [f"static nx_result_t prepare_sequence_{name}(void) {{", f" if ({sequence}.active != NULL || {sequence}.cs_held ||", f" {sequence}.pending_release) {{ return NX_ERROR_BUSY; }}", - f" nx_result_t status = prepare_engine_{name}();", + f" nx_result_t status = {construction.initialize.render()};", " if (status != NX_SUCCESS) { return status; }", f" return nx_spi_dma_sequence_initialize(&{sequence},", f" &{backend}, &s_nx_port_{name});", "}"] return replace(construction, definitions=tuple(definitions), + initialize=DirectCall(f"prepare_sequence_{name}"), + stop=tuple(DirectCall("nx_spi_dma_transaction_stop", + call.arguments) + if call.function == "nx_spi_port_stop" else call + for call in construction.stop), face_context="&" + sequence, face_definitions=( f"static nx_spi_dma_sequence_state_t {sequence} = {{", diff --git a/tools/configure/ethernet.py b/tools/configure/ethernet.py index 14b574833..e73671b77 100644 --- a/tools/configure/ethernet.py +++ b/tools/configure/ethernet.py @@ -3,6 +3,7 @@ The MAC bus-master domain is distinct from stream-DMA controller routes. PHY presence, reset, clock, negotiation and network policy remain external facts. """ +from tools.configure.calls import DirectCall from dataclasses import dataclass from tools.configure.gpio_phase import withdraw_pin @@ -100,7 +101,7 @@ def constructor(item, result, prefix): if memory is None or memory.get("dma") is not True: fail("Ethernet requires its reviewed bus-master memory domain") gates = hardware.frame_gates if enhanced else hardware.raw_gates - pins = item["pins"] + pins = item.pins ahb, apb = hardware.ahb, hardware.apb syscfg_gate = hardware.syscfg_gate mux, mux_mask = hardware.mux, hardware.mux_mask @@ -153,7 +154,7 @@ def constructor(item, result, prefix): if enhanced else [ f" .epoch = {state}.epoch, .drain_timeout_us = {options.drain_timeout_us}u,"]), " .address = { " + ", ".join(str(byte) + "u" for byte in options.address) + " },", - f" .irq_policy = {{ {kernel_policy_constant(result)}, {result['irq']['external_count']}u, {result['irq']['priority_bits']}u, {result['irq']['syscall_priority'] or 0}u }},", + f" .irq_policy = {{ {kernel_policy_constant(result)}, {result.irq.external_count}u, {result.irq.priority_bits}u, {result.irq.syscall_priority or 0}u }},", f" .source = {{ {hardware.irq}, {options.irq.priority}u, 3u }}", " };"] for index, pin in enumerate(pins): wire = pin["pin"] @@ -175,7 +176,7 @@ def constructor(item, result, prefix): f"void {hardware.controller}_IRQHandler(void) {{ nx_{stem}_mac_irq(&{state}); }}"] return Construction( state_type=f"nx_{stem}_mac_t", definitions=lines, - initialize=f"prepare_{name}()", stop=[f"stop_{name}()"], + initialize=DirectCall(f"prepare_{name}"), stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True, pin_snapshots={pin["pin"]: f"s_nx_eth_pin_snapshots_{name}[{index}]" @@ -225,7 +226,7 @@ def start_resources(result, prefix): mask = sum(1 << index for index in {ord(pin["pin"][1])-ord('A') for item in result.controllers if item.kind in {"eth", "eth_frames"} - for pin in item["pins"]}) + for pin in item.pins}) lines.append(f" s_nx_eth_gpio_clocks_added = ~{hardware.gpio_clock} & {mask}u;") return lines diff --git a/tools/configure/experience.py b/tools/configure/experience.py index 5ec711560..2c10b74f5 100644 --- a/tools/configure/experience.py +++ b/tools/configure/experience.py @@ -542,9 +542,10 @@ def resolve(path): print(" " + impact["category"] + " " + impact["path"] + ": " + impact["obligation"]) else: - print(f"Resolved {result['part']}/{result['board']}/" - f"{result['backend']}: " - f"{result['configuration_sha256']}") + data = project(result) + print(f"Resolved {data['part']}/{data['board']}/" + f"{data['backend']}: " + f"{data['configuration_sha256']}") return 0 except (gate.ConfigurationError, OSError, ValueError, TypeError, KeyError) as error: diff --git a/tools/configure/factory.py b/tools/configure/factory.py index e8d8868d7..1ba604540 100644 --- a/tools/configure/factory.py +++ b/tools/configure/factory.py @@ -37,8 +37,9 @@ def public_header(kind): def _typed(result): - return (result if isinstance(result, ConfigurationIR) else - ConfigurationIR.from_validated(result)) + if not isinstance(result, ConfigurationIR): + raise TypeError("Factory emission requires ConfigurationIR") + return result def classes(result): diff --git a/tools/configure/gpio_phase.py b/tools/configure/gpio_phase.py index 8f606c203..a566d3387 100644 --- a/tools/configure/gpio_phase.py +++ b/tools/configure/gpio_phase.py @@ -12,11 +12,11 @@ def selected_pins(result): - owners = {item["id"] for item in result.controllers} - owners.update(item["id"] for item in result.devices) - pins = {claim["key"].removeprefix("pin:") + owners = {item.id for item in result.controllers} + owners.update(item.id for item in result.devices) + pins = {claim.key.removeprefix("pin:") for claim in owned_claims(result, owners) - if claim["key"].startswith("pin:")} + if claim.key.startswith("pin:")} return tuple(sorted(pins, key=lambda pin: (pin[1], int(pin[2:])))) @@ -87,11 +87,11 @@ def add_snapshots(result, item, offered, snapshots): if not isinstance(offered, Mapping): fail("Cold pin snapshot metadata must be a mapping") owners = {item.id} - owners.update(device["id"] for device in result.devices - if device["controller"] == item.id) - owned = {claim["key"].removeprefix("pin:") + owners.update(device.id for device in result.devices + if device.controller == item.id) + owned = {claim.key.removeprefix("pin:") for claim in owned_claims(result, owners) - if claim["key"].startswith("pin:")} + if claim.key.startswith("pin:")} for pin, slot in offered.items(): if pin not in owned: fail("Cold snapshot refers to an unselected owner pin") diff --git a/tools/configure/i2c_timing.py b/tools/configure/i2c_timing.py index 95690a87b..919a57adb 100644 --- a/tools/configure/i2c_timing.py +++ b/tools/configure/i2c_timing.py @@ -9,6 +9,7 @@ from dataclasses import asdict, dataclass from fractions import Fraction +from typing import ClassVar from tools.configure.providers.common import fail, integer @@ -25,6 +26,10 @@ @dataclass(frozen=True) class I2cTimingIR: + time_unit: ClassVar[str] = "ns" + rate_scope: ClassVar[str] = "unstretched-master" + reference: ClassVar[str] = "AN4235 DocID024161 Rev2 sections2.2.3/2.2.4" + timingr: int cr1_filter_bits: int bus_hz_min: int @@ -36,9 +41,8 @@ class I2cTimingIR: sdadel_ns_max: int def to_dict(self): - return {**asdict(self), "time_unit": "ns", - "rate_scope": "unstretched-master", - "reference": "AN4235 DocID024161 Rev2 sections2.2.3/2.2.4"} + return {**asdict(self), "time_unit": self.time_unit, + "rate_scope": self.rate_scope, "reference": self.reference} def _ceil(value): diff --git a/tools/configure/i2c_timing_selection.py b/tools/configure/i2c_timing_selection.py index 5fa5cf3d7..27b8c4c53 100644 --- a/tools/configure/i2c_timing_selection.py +++ b/tools/configure/i2c_timing_selection.py @@ -6,6 +6,7 @@ admitted. Its conservative delay model follows the maintained AN4235 review. """ from tools.configure import i2c_timing +from tools.configure.ir import I2cTimingSelectionIR, freeze from tools.configure.providers.common import fail, integer @@ -51,6 +52,15 @@ def resolve(selection, clock, provider, *, clock_plan=None): calculation = i2c_timing.resolve(values) if calculation.cr1_filter_bits != 0: fail("I2C timing requires an unimplemented hardware filter mode") - return {**calculation.to_dict(), "assumptions": values, - "source": source, "provenance": "authored_declared_assumptions", - "physical_qualified": False} + return I2cTimingSelectionIR( + timingr=calculation.timingr, + cr1_filter_bits=calculation.cr1_filter_bits, + provenance="authored_declared_assumptions", physical_qualified=False, + source=source, bus_hz_min=calculation.bus_hz_min, + bus_hz_max=calculation.bus_hz_max, low_ns_min=calculation.low_ns_min, + high_ns_min=calculation.high_ns_min, + scldel_ns_min=calculation.scldel_ns_min, + sdadel_ns_min=calculation.sdadel_ns_min, + sdadel_ns_max=calculation.sdadel_ns_max, + time_unit=calculation.time_unit, rate_scope=calculation.rate_scope, + reference=calculation.reference, assumptions=freeze(values)) diff --git a/tools/configure/ir.py b/tools/configure/ir.py index d26e75403..64d946eaa 100644 --- a/tools/configure/ir.py +++ b/tools/configure/ir.py @@ -4,9 +4,9 @@ module stores that decision; it does not select targets or repair configuration. """ from collections.abc import Mapping -from dataclasses import dataclass, replace +from dataclasses import dataclass, fields, is_dataclass, replace from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import ClassVar, TYPE_CHECKING from tools.configure.resource_graph import ResourceClaimIR, ResourcePlanIR from tools.configure.trigger_graph import TriggerPlanIR @@ -25,6 +25,11 @@ def freeze(value): def thaw(value): + if isinstance(value, Record): + return {key: thaw(item) for key, item in value.projection().items()} + if is_dataclass(value): + return {item.name: thaw(getattr(value, item.name)) + for item in fields(value)} if isinstance(value, Mapping): return {key: thaw(item) for key, item in value.items()} if isinstance(value, tuple): @@ -32,16 +37,13 @@ def thaw(value): return value -class Record(Mapping): - """Read-only projection used by existing narrow constructor emitters.""" - def __getitem__(self, key): - return self.values[key] +class Record: + """Derived read-only projection; named fields are the sole stored values.""" + def projection(self): + return {item.name: getattr(self, item.name) for item in fields(self)} - def __iter__(self): - return iter(self.values) - - def __len__(self): - return len(self.values) + def to_dict(self): + return thaw(self) @dataclass(frozen=True) @@ -63,9 +65,6 @@ class InterruptProfileIR: @dataclass(frozen=True) class CpuProfileIR(Record): - arch: str - fpu: str - float_abi: str enum_abi: str backend: str arch_macro: str @@ -74,16 +73,32 @@ class CpuProfileIR(Record): irq: InterruptProfileIR compile_options: tuple[str, ...] definitions: Mapping - values: Mapping + cpu: Mapping + + @property + def arch(self) -> str: + return self.cpu["arch"] + + @property + def fpu(self) -> str: + return self.cpu["fpu"] + + @property + def float_abi(self) -> str: + return self.cpu["float_abi"] + + def projection(self): + return {**super().projection(), "arch": self.arch, "fpu": self.fpu, + "float_abi": self.float_abi} @classmethod def from_validated(cls, record): return cls(*(record[name] for name in ( - "arch", "fpu", "float_abi", "enum_abi", "backend", "arch_macro", + "enum_abi", "backend", "arch_macro", "has_basepri", "atomic_backend")), InterruptProfileIR(**record["irq"]), tuple(record["compile_options"]), - freeze(record["definitions"]), freeze(record)) + freeze(record["definitions"]), freeze(record["cpu"])) def to_dict(self): return thaw(self) @@ -117,7 +132,6 @@ class KernelProfileIR(Record): min_stack_words: int stack_alignment_bytes: int definitions: Mapping - values: Mapping @classmethod def from_validated(cls, record): @@ -130,7 +144,7 @@ def from_validated(cls, record): "max_task_name_len", "mutexes", "counting_semaphores", "task_notifications", "notification_slots", "trace", "runtime_stats", "tickless", "lowpower_port", "min_stack_words", "stack_alignment_bytes")), - freeze(record["definitions"]), freeze(record)) + freeze(record["definitions"])) def to_dict(self): return thaw(self) @@ -153,7 +167,6 @@ class ThreadXKernelProfileIR(Record): min_stack_bytes: int stack_alignment_bytes: int definitions: Mapping - values: Mapping @classmethod def from_validated(cls, record): @@ -162,7 +175,7 @@ def from_validated(cls, record): "max_priorities", "syscall_priority", "interrupt_mask", "kernel_policy", "kernel_port", "timer_process_in_isr", "min_stack_bytes", "stack_alignment_bytes")), - freeze(record["definitions"]), freeze(record)) + freeze(record["definitions"])) def to_dict(self): return thaw(self) @@ -219,13 +232,10 @@ class StreamProfileIR(Record): physical_qualified: bool source: str on_exhaustion: str - values: Mapping @classmethod def from_validated(cls, values): - fields = cls.__dataclass_fields__.keys() - {"values"} - return cls(**{name: values[name] for name in fields}, - values=freeze(values)) + return cls(**{item.name: values[item.name] for item in fields(cls)}) @dataclass(frozen=True) @@ -262,14 +272,17 @@ class I2cTimingSelectionIR(Record): provenance: str physical_qualified: bool source: str - values: Mapping - - @classmethod - def from_validated(cls, values): - return cls(*(values[name] for name in ( - "timingr", "cr1_filter_bits", "provenance", "physical_qualified", - "source")), freeze(values)) - + bus_hz_min: int + bus_hz_max: int + low_ns_min: int + high_ns_min: int + scldel_ns_min: int + sdadel_ns_min: int + sdadel_ns_max: int + time_unit: str + rate_scope: str + reference: str + assumptions: Mapping @dataclass(frozen=True) class I2cOptions: @@ -639,9 +652,7 @@ def mode_options(values): values.get("dma_cache_line_bytes", 0)) if kind == "i2c": return I2cOptions(values.get("max_hz", 100000), irq, - I2cTimingSelectionIR.from_validated( - values["timing_resolution"]) - if "timing_resolution" in values else None) + values.get("timing_resolution")) if kind == "exti": return ExtiOptions(values["edge"], values["event_capacity"], irq) if kind == "pwm": @@ -661,6 +672,19 @@ def mode_options(values): raise ValueError(f"Unmaintained controller decision: {kind}") +def option_projection(options): + """Export selected options without retaining a parallel input dictionary.""" + result = {item.name: getattr(options, item.name) for item in fields(options)} + irq = result.pop("irq", None) + if irq is not None: + result.update(irq_priority=irq.priority, calls_os=irq.calls_os, + irq_probe=irq.probe) + stream = result.pop("stream_profile", None) + if stream is not None: + result["stream_resolution"] = stream + return result + + @dataclass(frozen=True) class ControllerIR(Record): id: str @@ -670,16 +694,69 @@ class ControllerIR(Record): mode: str provider: str options: ControllerOptions - values: Mapping + pins: tuple[Mapping, ...] + route: str + dma: tuple[str, ...] + can_irqs: tuple[str, ...] + source_fields: tuple[str, ...] + route_mask: int | None = None + route_initial: int | None = None + stream_fields: tuple[str, ...] = () interface_kind: str | None = None integration: "ControllerIntegrationIR | None" = None + @property + def mask(self): + return (self.route_mask if self.route_mask is not None + else self.options.mask) + + @property + def initial(self): + return (self.route_initial if self.route_initial is not None + else self.options.initial) + + def projection(self): + result = {name: getattr(self, name) for name in ( + "id", "kind", "controller", "binding", "mode", "pins", "route", + "dma", "can_irqs", "interface_kind", "mask", "initial")} + result.update(option_projection(self.options)) + if isinstance(self.options, PinStateOptions): + result["pin_state_resolution"] = { + "default": self.options.default, "sleep": self.options.sleep} + if isinstance(self.options, I2cOptions) and self.options.timing is not None: + timing = self.options.timing + result["timing_resolution"] = timing + result["timing"] = {key: timing.assumptions[key] for key in ( + "kernel_hz_min", "kernel_hz_max", "pclk_hz_min", "bus_hz_min", + "rise_ns_max", "fall_ns_max")} + result["timing"]["source"] = timing.source + stream = getattr(self.options, "stream_profile", None) + if stream is not None: + result["stream"] = {name: getattr(stream, name) + for name in self.stream_fields} + if isinstance(self.options, AdcGroupOptions): + result["adc_group_resolution"] = {name: getattr(self.options, name) + for name in ("rank_repeat", "sample_stride", "scan_stride", + "max_frames", "adc_clock_hz", "hclk_hz", "trigger_identity", + "admission")} + return {name: result[name] for name in self.source_fields} + @classmethod def create(cls, values, provider): - return cls(*(values[key] for key in + options = mode_options(values) + result = cls(*(values[key] for key in ("id", "kind", "controller", "binding", "mode")), - provider, mode_options(values), freeze(values), - values.get("interface_kind", values["kind"])) + provider, options, freeze(values["pins"]), values["route"], + tuple(values.get("dma", ())), + tuple(values.get("can_irqs", ())), tuple(values), + route_mask=(None if hasattr(options, "mask") else values["mask"]), + route_initial=(None if hasattr(options, "initial") else values["initial"]), + stream_fields=tuple(values.get("stream", ())), + interface_kind=values.get("interface_kind", values["kind"])) + # The projection is deliberately exhaustive: a new resolved field must + # acquire a typed owner instead of silently disappearing into metadata. + result.projection() + return result @dataclass(frozen=True) @@ -727,7 +804,14 @@ class EndpointIR(Record): options: (SpiEndpointOptions | SpiTransactionEndpointOptions | SpiDMATransactionEndpointOptions | I2cEndpointOptions | I2cLongEndpointOptions | UsbEndpointOptions) - values: Mapping + source_fields: tuple[str, ...] + + def projection(self): + result = {"id": self.id, "controller": self.controller, + "driver": self.driver, **option_projection(self.options)} + if isinstance(self.options, UsbEndpointOptions): + result["endpoint_index"] = self.options.index + return {name: result[name] for name in self.source_fields} @classmethod def create(cls, values, controllers): @@ -754,7 +838,7 @@ def create(cls, values, controllers): else: raise ValueError(f"Unmaintained endpoint decision: {kind}") return cls(values["id"], values["controller"], values["driver"], - kind, options, freeze(values)) + kind, options, tuple(values)) @dataclass(frozen=True) @@ -781,6 +865,20 @@ class MemoryBudgetIR: regions: tuple[MemoryRegionBudgetIR, ...] = () mpu: MpuMemoryReservationsIR | None = None + def projection(self): + result = {"main_stack_bytes": self.main_stack_bytes, + "libc_heap_bytes": self.libc_heap_bytes} + for key, value in (("static_ram_bytes", self.static_ram_limit_bytes), + ("flash_load_bytes", self.flash_load_limit_bytes)): + if value is not None: + result[key] = value + if self.regions: + result["regions"] = {item.id: {"limit_bytes": item.limit_bytes} + for item in self.regions} + if self.mpu is not None: + result["mpu"] = thaw(self.mpu) + return result + @dataclass(frozen=True) class FactoryOptions: @@ -855,10 +953,9 @@ class RuntimePowerIR: @dataclass(frozen=True) class ConfigurationIR(Record): + source_epoch: ClassVar[int] = 2 board: str soc_family: str - backend: str - irq: InterruptProfileIR cpu_profile: CpuProfileIR kernel: KernelProfileIR | ThreadXKernelProfileIR | None controllers: tuple[ControllerIR, ...] @@ -870,89 +967,111 @@ class ConfigurationIR(Record): trigger_plan: TriggerPlanIR dma_topology: DmaTopologyIR clock_plan: ClockPlanIR | None - values: Mapping + physical_part: PhysicalPartIR + clock: Mapping + clock_profile: str + layout: Mapping + board_bindings: Mapping + routes: Mapping + platform_metadata: Mapping + inputs: tuple[Mapping, ...] + source_dependencies: tuple[Mapping, ...] + components: tuple[str, ...] + can_controllers: tuple[str, ...] + optimization: str + physical_pcb_revision: str | None + limitations: tuple[str, ...] + physical_qualified: bool + fact_references: Mapping | None = None pwm_shared_owners: tuple[PwmSharedOwnerIR, ...] = () runtime_power: RuntimePowerIR | None = None i2s2: I2s2RoleIR | None = None - physical_part: PhysicalPartIR | None = None - - @classmethod - def from_validated(cls, result): + configuration_sha256: str = "" + schema_version: int = 1 + + @property + def backend(self): + return self.cpu_profile.backend + + @property + def enum_abi(self): + return self.cpu_profile.enum_abi + + @property + def irq(self): + return self.cpu_profile.irq + + @property + def cpu(self): + return self.cpu_profile.cpu + + @property + def part(self): + return self.physical_part.part + + @property + def package(self): + return self.physical_part.package + + @property + def flash(self): + return self.physical_part.flash + + @property + def memory(self): + return self.physical_part.memory + + @property + def claims(self): + return self.resource_plan.claims + + def projection(self): + result = {item.name: getattr(self, item.name) for item in fields(self) + if item.name not in { + "kernel", "memory_budget", "physical_part", "fact_references", + "pwm_shared_owners", "runtime_power", "i2s2", "clock_plan"}} + result.update(backend=self.backend, enum_abi=self.enum_abi, + irq=thaw(self.irq), cpu=self.cpu, part=self.part, + package=self.package, flash=self.flash, memory=self.memory, + kernel_profile=self.kernel, + memory_budgets=self.memory_budget.projection(), + claims=tuple({name: getattr(claim, name) for name in + ("key", "owner", "mode", "source")} for claim in self.claims)) + for name in ("fact_references", "runtime_power", "i2s2", "clock_plan"): + if getattr(self, name) is not None: + result[name] = getattr(self, name) + if self.pwm_shared_owners: + result["pwm_shared_owners"] = self.pwm_shared_owners + return result + + def with_integrations(self): + """Reference canonical selected plans without copying or re-resolving.""" from tools.configure.providers import maintained - provider = maintained(result["soc_family"]) - resources = ResourcePlanIR.from_validated(result["resource_plan"]) - dma = DmaTopologyIR.from_validated(result["dma_topology"]) - irq = InterruptProfileIR(**result["irq"]) - clock = (ClockPlanIR.from_validated(result["clock_plan"]) - if result.get("clock_plan") is not None else None) - values = dict(freeze(result)) - controllers = tuple(ControllerIR.create(item, result["soc_family"]) - for item in result["controllers"]) + provider = maintained(self.soc_family) integrated = [] - for item in controllers: + for item in self.controllers: hardware = next((binding for binding in getattr(provider, "CONTROLLER_BINDINGS", ()) if binding.controller == item.controller and binding.kind == item.kind), None) integration = ControllerIntegrationIR( item.controller, - tuple(claim for claim in resources.claims + tuple(claim for claim in self.resource_plan.claims if item.id in claim.owner.split(";") and claim.mode not in {"reserved", "kernel"}), - tuple(lane for lane in dma.lanes if lane.owner == item.id), - getattr(item.options, "irq", None), irq, clock, - values["clock"], - (values["clock"][f"apb{hardware.apb}_hz"] - if hardware is not None else None), - item.values["pins"], hardware) + tuple(lane for lane in self.dma_topology.lanes if lane.owner == item.id), + getattr(item.options, "irq", None), self.irq, self.clock_plan, + self.clock, + (self.clock[f"apb{hardware.apb}_hz"] + if hardware is not None else None), item.pins, hardware) integrated.append(replace(item, integration=integration)) - controllers = tuple(integrated) - devices = tuple(EndpointIR.create(item, controllers) - for item in result["devices"]) - values["controllers"] = controllers - values["devices"] = devices - budgets = result["memory_budgets"] - memory = MemoryBudgetIR(budgets["main_stack_bytes"], - budgets.get("libc_heap_bytes", 0), - budgets.get("static_ram_bytes"), - budgets.get("flash_load_bytes"), - tuple(MemoryRegionBudgetIR(name, choice["limit_bytes"]) - for name, choice in sorted( - budgets.get("regions", {}).items())), - (MpuMemoryReservationsIR(**budgets["mpu"]) - if "mpu" in budgets else None)) - cpu = CpuProfileIR.from_validated(result["cpu_profile"]) - kernel_type = (ThreadXKernelProfileIR if result["backend"] == "threadx" - else KernelProfileIR) - kernel = (kernel_type.from_validated(result["kernel_profile"]) - if result.get("kernel_profile") is not None else None) - return cls(result["board"], result["soc_family"], result["backend"], irq, cpu, kernel, - controllers, devices, memory, - FactoryOptions(result.get("factory", {}).get("capabilities", False)), - (CanDomainIR(result["can_domain"]["split"], - tuple(CanFilterIR(**item) for item in - result["can_domain"]["filters"])) - if result.get("can_domain") is not None else None), - resources, - TriggerPlanIR.from_validated(result["trigger_plan"]), - dma, clock, - MappingProxyType(values), - tuple(PwmSharedOwnerIR.from_validated(item) - for item in result.get("pwm_shared_owners", ())), - (RuntimePowerIR(result["runtime_power"]["mode"], - tuple(result["runtime_power"]["controllers"])) - if "runtime_power" in result else None), - (I2s2RoleIR(result["i2s2"]["profile"], result["i2s2"]["part"], - tuple(result["i2s2"]["claims"])) - if "i2s2" in result else None), - PhysicalPartIR(result["part"], result["package"], - values["flash"], values["memory"])) - - def to_dict(self): - return thaw(self) + return replace(self, controllers=tuple(integrated)) def controller_ir(item, family): - """Adapt validated legacy constructor fixtures without re-resolving facts.""" - return (item if isinstance(item, ControllerIR) else - ControllerIR.create(item, family)) + """Require one resolved owner; constructors never reinterpret raw inputs.""" + if not isinstance(item, ControllerIR): + raise TypeError("Provider construction requires ControllerIR") + if item.provider != family: + raise ValueError("Controller provider differs from the selected family") + return item diff --git a/tools/configure/kernel.py b/tools/configure/kernel.py index 9a5a83cd0..7f0d649ad 100644 --- a/tools/configure/kernel.py +++ b/tools/configure/kernel.py @@ -3,6 +3,7 @@ FreeRTOS profiles and the strict ThreadX static-port policy remain independent. They create no tasks, queues, Tick hardware, buffers or product recovery policy. """ +from dataclasses import replace from types import MappingProxyType from tools.configure.ir import CpuProfileIR, KernelProfileIR @@ -86,7 +87,7 @@ def resolve(choices, cpu, clock_hz): fail("Unsupported OS security model") if security == "split" and (cpu.arch not in { "cortex-m23", "cortex-m33", "cortex-m55", "cortex-m85"} or - cpu["cpu"]["security"] != "nonsecure"): + cpu.cpu["security"] != "nonsecure"): fail("Split kernel security requires explicit v8-M Nonsecure CPU facts") if security == "split": port = f"GCC/ARM_CM{cpu.arch[8:]}/non_secure" @@ -106,7 +107,7 @@ def resolve(choices, cpu, clock_hz): fail("Unsupported user I/O policy") if user_io == "gpio-write" and ( not protected or security != "single" or - cpu.arch != "cortex-m33" or cpu["cpu"]["security"] != "single" or + cpu.arch != "cortex-m33" or cpu.cpu["security"] != "single" or cpu.definitions["NEXUS_CPU_MPU_REGIONS"] != 8 or options["tickless"] or options["tick_source"] != "systick"): fail("GPIO user I/O requires the exact single-world MPU-v2 M33 profile") @@ -183,21 +184,11 @@ def bind(cpu, kernel): """Attach selected logical syscall policy without mutating physical facts.""" if kernel is None: return cpu - record = cpu.to_dict() - record["irq"]["syscall_priority"] = kernel.syscall_priority - record["irq"]["kernel_port"] = kernel.kernel_port - record["irq"]["mask_kind"] = kernel.kernel_policy - record["definitions"]["NEXUS_IRQ_KERNEL_POLICY"] = kernel.definitions[ - "NEXUS_IRQ_KERNEL_POLICY"] - return CpuProfileIR.from_validated(record) - - -def from_validated(record): - """Recover the backend-specific typed decision without inventing features.""" - if record.get("backend") == "threadx": - from tools.configure import threadx - return threadx.ThreadXKernelProfileIR.from_validated(record) - return KernelProfileIR.from_validated(record) + irq = replace(cpu.irq, syscall_priority=kernel.syscall_priority, + kernel_port=kernel.kernel_port, mask_kind=kernel.kernel_policy) + definitions = MappingProxyType({**cpu.definitions, + "NEXUS_IRQ_KERNEL_POLICY": kernel.definitions["NEXUS_IRQ_KERNEL_POLICY"]}) + return replace(cpu, irq=irq, definitions=definitions) def header_lines(kernel): diff --git a/tools/configure/matrix.py b/tools/configure/matrix.py index 3695712b2..dfbb26f9b 100644 --- a/tools/configure/matrix.py +++ b/tools/configure/matrix.py @@ -149,16 +149,16 @@ def resolve_catalogue(root=configure.ROOT): references.append({ "assembly": path.relative_to(root).as_posix(), "board": result.board, "family": result.soc_family, - "part": result["part"], "backend": result.backend, - "optimization": result["optimization"], - "configuration_sha256": result["configuration_sha256"], + "part": result.part, "backend": result.backend, + "optimization": result.optimization, + "configuration_sha256": result.configuration_sha256, "selected": [{"id": item.id, "kind": item.kind, "controller": item.controller, "mode": item.mode} for item in result.controllers], "scope": "native_model" if result.soc_family == "native" else "public_reference", "physical_status": "not_executed", - "pcb_revision": result["physical_pcb_revision"], - "limitations": list(result["limitations"]), + "pcb_revision": result.physical_pcb_revision, + "limitations": list(result.limitations), }) return {"schema_version": 1, "identity_kind": "derived-software-support", "contracts": contract, "references": references, diff --git a/tools/configure/memory.py b/tools/configure/memory.py index 0c4edf873..a307f88c1 100644 --- a/tools/configure/memory.py +++ b/tools/configure/memory.py @@ -29,8 +29,7 @@ def select(regions, budgets, maintained, *, obj, integer, fail): def flash_geometry(result, *, symbol, sector_symbol, program_unit, program_flags="0u", static=True): """Emit one readonly physical geometry from the selected immutable facts.""" - physical = getattr(result, "physical_part", None) - flash = physical.flash if physical is not None else result["flash"] + flash = result.physical_part.flash lines = [f"static const nx_flash_sector_t {sector_symbol}[] = {{"] offset = 0 for block in flash["erase_blocks"]: @@ -46,34 +45,52 @@ def flash_geometry(result, *, symbol, sector_symbol, program_unit, f" .program_flags = {program_flags}", "};"] +def _budget_limits(memory, static_limit, regions): + primary = next(region["id"] for region in memory if region["linker"]) + return {region["id"]: ( + static_limit if static_limit is not None else region["size"]) + if region["id"] == primary else regions.get(region["id"], 0) + for region in memory} + + def budget_limits(result): - budgets = result["memory_budgets"] - primary = next(region["id"] for region in result["memory"] - if region["linker"]) - choices = budgets.get("regions", {}) - return {region["id"]: (budgets.get("static_ram_bytes", region["size"]) - if region["id"] == primary else - choices.get(region["id"], {}).get("limit_bytes", 0)) - for region in result["memory"]} + budget = result.memory_budget + return _budget_limits(result.memory, budget.static_ram_limit_bytes, + {item.id: item.limit_bytes for item in budget.regions}) + + +def _resource_budget(flash_limit, ram_limits, stack, heap): + return {"schema_version": 1, "flash_loaded_max": flash_limit, + "flash_footprint_max": flash_limit, "ram_reserved_max": ram_limits, + "msp_max": stack, "heap_max": heap} def resource_budget(result): - """Project the sole resolved reservation and memory-limit contract. + """Export the selected typed reservation and memory-limit contract.""" + budget = result.memory_budget + flash_limit = (budget.flash_load_limit_bytes + if budget.flash_load_limit_bytes is not None + else result.layout["size"]) + return _resource_budget(flash_limit, budget_limits(result), + budget.main_stack_bytes, budget.libc_heap_bytes) - Generation and ELF verification share this pure projection. The emitted - budget is a derived input; editing it cannot widen authored allowances. + +def resource_budget_projection(resolved): + """Read a sealed JSON contract for independent ELF budget verification. + + This never reconstructs executable configuration or admits hardware modes. + The caller validates the physical memory and complete expected budget. """ - budgets = result["memory_budgets"] - flash_limit = budgets.get("flash_load_bytes", result["layout"]["size"]) - return {"schema_version": 1, "flash_loaded_max": flash_limit, - "flash_footprint_max": flash_limit, - "ram_reserved_max": budget_limits(result), - "msp_max": budgets["main_stack_bytes"], - "heap_max": budgets.get("libc_heap_bytes", 0)} + budget = resolved["memory_budgets"] + limits = _budget_limits(resolved["memory"], budget.get("static_ram_bytes"), + {name: item["limit_bytes"] for name, item in budget.get("regions", {}).items()}) + return _resource_budget( + budget.get("flash_load_bytes", resolved["layout"]["size"]), limits, + budget["main_stack_bytes"], budget.get("libc_heap_bytes", 0)) def selections(result): - selected = {region["id"] for region in result["memory"] if region["linker"]} + selected = {region["id"] for region in result.memory if region["linker"]} return [("NEXUS_MEMORY_" + name.upper(), int(name in selected)) for name in ("addsram", "tcm", "ccm", "sram1", "sram4")] @@ -136,23 +153,23 @@ def _mpu_linker(result): User initializers have a distinct cold-start copy/zero ABI; ordinary data, kernel metadata and the boot stack remain in the primary privileged RAM. """ - budgets = result["memory_budgets"] - reservations = budgets["mpu"] - flash = result["layout"] - ram = next(region for region in result["memory"] if region["linker"]) + budgets = result.memory_budget + reservations = budgets.mpu + flash = result.layout + ram = next(region for region in result.memory if region["linker"]) flash_origin = flash["origin"] - svc_origin = flash_origin + reservations["privileged_flash_bytes"] - user_flash_origin = svc_origin + reservations["syscall_flash_bytes"] + svc_origin = flash_origin + reservations.privileged_flash_bytes + user_flash_origin = svc_origin + reservations.syscall_flash_bytes ram_origin = ram["origin"] - user_ram_origin = ram_origin + reservations["privileged_ram_bytes"] + user_ram_origin = ram_origin + reservations.privileged_ram_bytes domains = ( ("PRIV_FLASH", "rx", flash_origin, - reservations["privileged_flash_bytes"]), - ("SVC_FLASH", "rx", svc_origin, reservations["syscall_flash_bytes"]), + reservations.privileged_flash_bytes), + ("SVC_FLASH", "rx", svc_origin, reservations.syscall_flash_bytes), ("USER_FLASH", "rx", user_flash_origin, - reservations["user_flash_bytes"]), - ("PRIV_RAM", "rw", ram_origin, reservations["privileged_ram_bytes"]), - ("USER_RAM", "rw", user_ram_origin, reservations["user_ram_bytes"]), + reservations.user_flash_bytes), + ("PRIV_RAM", "rw", ram_origin, reservations.privileged_ram_bytes), + ("USER_RAM", "rw", user_ram_origin, reservations.user_ram_bytes), ) domain_lines = "\n".join( f" {name} ({access}) : ORIGIN = 0x{origin:08x}, LENGTH = {size}" @@ -177,11 +194,11 @@ def _mpu_linker(result): ("syscalls_flash", "syscall_flash"))) identities = "\n".join( f"__nx_resolved_sha256_{index} = 0x" - f"{result['configuration_sha256'][index * 8:(index + 1) * 8]};" + f"{result.configuration_sha256[index * 8:(index + 1) * 8]};" for index in range(8)) # Optional physical domains are outside this exact policy. Preserve their # existing fail-closed input-section checks in the privileged primary RAM. - extras = [region for region in result["memory"] if region["id"] != ram["id"]] + extras = [region for region in result.memory if region["id"] != ram["id"]] extra_sections = _extra_sections(extras).replace( "> RAM :ram", "> PRIV_RAM :ram") return f'''/* Explicit selected MPU reservations; physical facts unchanged. */ @@ -207,8 +224,8 @@ def _mpu_linker(result): __nx_flash_image_load_start = __nx_flash_start; _estack = ORIGIN(PRIV_RAM) + LENGTH(PRIV_RAM); _sp = _estack; -_Min_Stack_Size = {budgets["main_stack_bytes"]}; -_Min_Heap_Size = {budgets.get("libc_heap_bytes", 0)}; +_Min_Stack_Size = {budgets.main_stack_bytes}; +_Min_Heap_Size = {budgets.libc_heap_bytes}; __nx_msp_start = _estack - _Min_Stack_Size; __nx_msp_end = _estack; {identities} @@ -244,13 +261,13 @@ def _mpu_linker(result): def linker(result): - if result["memory_budgets"].get("mpu") is not None: + if result.memory_budget.mpu is not None: return _mpu_linker(result) - ram = next(region for region in result["memory"] if region["linker"]) - flash = result["layout"] - stack = result["memory_budgets"]["main_stack_bytes"] - heap = result["memory_budgets"].get("libc_heap_bytes", 0) - extras = [region for region in result["memory"] if region["id"] != ram["id"]] + ram = next(region for region in result.memory if region["linker"]) + flash = result.layout + stack = result.memory_budget.main_stack_bytes + heap = result.memory_budget.libc_heap_bytes + extras = [region for region in result.memory if region["id"] != ram["id"]] selected = [region for region in extras if region["linker"]] extra_phdrs = "\n".join(f" {region['id']}_{name} PT_LOAD FLAGS(6);" for region in selected for name in ("data", "ram")) @@ -260,7 +277,7 @@ def linker(result): extra_sections = _extra_sections(extras) # Optional exact SoC sections remain independent from generic placement. from tools.configure.providers import maintained - fragments = getattr(maintained(result["soc_family"]), + fragments = getattr(maintained(result.soc_family), "linker_fragments", None) (flash_image_record, flash_ram_phdr, flash_ram_section, flash_workspace_bss) = (fragments(result) if fragments else ("", "", "", "")) @@ -288,14 +305,14 @@ def linker(result): _Min_Heap_Size = {heap}; __nx_msp_start = _estack - _Min_Stack_Size; __nx_msp_end = _estack; -__nx_resolved_sha256_0 = 0x{result["configuration_sha256"][0:8]}; -__nx_resolved_sha256_1 = 0x{result["configuration_sha256"][8:16]}; -__nx_resolved_sha256_2 = 0x{result["configuration_sha256"][16:24]}; -__nx_resolved_sha256_3 = 0x{result["configuration_sha256"][24:32]}; -__nx_resolved_sha256_4 = 0x{result["configuration_sha256"][32:40]}; -__nx_resolved_sha256_5 = 0x{result["configuration_sha256"][40:48]}; -__nx_resolved_sha256_6 = 0x{result["configuration_sha256"][48:56]}; -__nx_resolved_sha256_7 = 0x{result["configuration_sha256"][56:64]}; +__nx_resolved_sha256_0 = 0x{result.configuration_sha256[0:8]}; +__nx_resolved_sha256_1 = 0x{result.configuration_sha256[8:16]}; +__nx_resolved_sha256_2 = 0x{result.configuration_sha256[16:24]}; +__nx_resolved_sha256_3 = 0x{result.configuration_sha256[24:32]}; +__nx_resolved_sha256_4 = 0x{result.configuration_sha256[32:40]}; +__nx_resolved_sha256_5 = 0x{result.configuration_sha256[40:48]}; +__nx_resolved_sha256_6 = 0x{result.configuration_sha256[48:56]}; +__nx_resolved_sha256_7 = 0x{result.configuration_sha256[56:64]}; SECTIONS {{ .isr_vector : {{ KEEP(*(.isr_vector)) KEEP(*(.vectors)) }} > FLASH :flash .text : {{ *(.text*) *(.rodata*) KEEP(*(.init)) KEEP(*(.fini)) }} > FLASH :flash @@ -320,18 +337,24 @@ def linker(result): def dma_regions(name, result, include_flash=False, *, engine=None): + """Render the selected physical memory without reconstructing a decision.""" + return dma_regions_from_facts(name, result.memory, + flash=result.flash if include_flash else None, engine=engine) + + +def dma_regions_from_facts(name, memory, *, flash=None, engine=None): """Emit exact DMA domains; invalid physical reachability raises ValueError. Keep this module usable by independent ELF budget checkers without provider configuration imports. The configuration entry point owns error reporting. """ - if include_flash and any("dma_engines" in region - for region in result["memory"]): + if (flash is not None) and any("dma_engines" in region + for region in memory): # RAM bus reachability never implies access to the Flash bus. New # heterogeneous engines need reviewed Flash facts before enabling it. raise ValueError("Engine-specific Flash reachability is not declared") regions = [] - for region in result["memory"]: + for region in memory: if "dma_engines" in region: if engine is None or engine not in region["dma_engines"]: raise ValueError( @@ -341,13 +364,12 @@ def dma_regions(name, result, include_flash=False, *, engine=None): reachable = region["dma"] if reachable: regions.append(region) - if not regions and not include_flash: + if not regions and not (flash is not None): return [], 0 lines = [f"static const nx_dma_memory_region_t s_nx_dma_regions_{name}[] = {{"] for region in regions: lines.append(f" {{ (uintptr_t)0x{region['origin']:08x}u, {region['size']}u, NX_DMA_MEMORY_READ | NX_DMA_MEMORY_WRITE }},") - if include_flash: - flash = result["flash"] + if (flash is not None): lines.append(f" {{ (uintptr_t)0x{flash['origin']:08x}u, {flash['size']}u, NX_DMA_MEMORY_READ }},") lines.append("};") - return lines, len(regions) + int(include_flash) + return lines, len(regions) + int((flash is not None)) diff --git a/tools/configure/onboarding.py b/tools/configure/onboarding.py index 0fbe2b9f1..a378000de 100644 --- a/tools/configure/onboarding.py +++ b/tools/configure/onboarding.py @@ -19,10 +19,10 @@ def check(result, *, expected_family=None, expected_part=None): fail("Provider check family differs from the resolved exact family", code="provider_identity_mismatch", expected=expected_family, actual=result.soc_family) - if expected_part is not None and result["part"] != expected_part: + if expected_part is not None and result.part != expected_part: fail("Provider check part differs from the resolved exact part", code="provider_identity_mismatch", expected=expected_part, - actual=result["part"]) + actual=result.part) provider = maintained(result.soc_family) family = descriptor.maintained(result.soc_family) # Use the production emitter once. It exercises provider constructors, @@ -45,12 +45,12 @@ def check(result, *, expected_family=None, expected_part=None): return { "schema_version": 1, "kind": "provider_configuration_check", "checks": ["resolved_typed_ir", "static_bundle_emitted"], - "family": result.soc_family, "part": result["part"], - "board": result["board"], "backend": result["backend"], - "configuration_sha256": result["configuration_sha256"], + "family": result.soc_family, "part": result.part, + "board": result.board, "backend": result.backend, + "configuration_sha256": result.configuration_sha256, "controllers": controllers, "configuration_modules": list(family.configuration_modules), - "fact_inputs": [dict(item) for item in result["inputs"]], + "fact_inputs": [dict(item) for item in result.inputs], "generated_files": generated, "physical_verification": ("not_applicable" if provider.MODEL else "not_established_by_configuration"), diff --git a/tools/configure/providers/common.py b/tools/configure/providers/common.py index 652e3c125..9785137a8 100644 --- a/tools/configure/providers/common.py +++ b/tools/configure/providers/common.py @@ -4,6 +4,8 @@ import re from types import MappingProxyType +from tools.configure.calls import DirectCall + UINT32_MAX = (1 << 32) - 1 @@ -123,40 +125,42 @@ def validate_user_io_context(assembly, part): @dataclass(frozen=True) -class Construction(Mapping): +class Construction: """One provider's explicit static storage and cold lifecycle contract. - Mapping reads serve maintained constructor fixtures. Production assembly - uses the named fields; no family inference or runtime metadata is emitted. + Production assembly uses named fields; no family inference or runtime + metadata is emitted. """ state_type: str definitions: tuple[str, ...] - initialize: str - stop: tuple[str, ...] + initialize: DirectCall + stop: tuple[DirectCall, ...] retain_on_failed_init: bool exports: tuple[str, ...] = () pin_snapshots: Mapping[str, str] = field(default_factory=dict) face_context: str | None = None face_definitions: tuple[str, ...] = () - _FIELDS = ("state_type", "definitions", "initialize", "stop", - "retain_on_failed_init", "exports", "pin_snapshots", - "face_context", "face_definitions") - def __post_init__(self): if (not isinstance(self.state_type, str) or re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", self.state_type) is None): fail("Construction requires an explicit C state type") - if not isinstance(self.initialize, str) or not self.initialize: - fail("Construction requires an explicit initializer") + if not isinstance(self.initialize, DirectCall): + fail("Construction initializer requires an explicit direct call") + if not isinstance(self.stop, (tuple, list)): + fail("Construction stop requires an ordered direct call sequence") + stops = tuple(self.stop) + if any(not isinstance(value, DirectCall) for value in stops): + fail("Construction stop requires explicit direct calls") + object.__setattr__(self, "stop", stops) if type(self.retain_on_failed_init) is not bool: fail("Construction requires boolean failed-init responsibility") if (self.face_context is not None and re.fullmatch(r"&[A-Za-z_][A-Za-z0-9_]*", self.face_context) is None): fail("Construction face context requires an explicit static object") - for name in ("definitions", "stop", "exports", "face_definitions"): + for name in ("definitions", "exports", "face_definitions"): values = tuple(getattr(self, name)) if any(not isinstance(value, str) for value in values): fail(f"Construction {name} requires C source strings") @@ -167,29 +171,11 @@ def __post_init__(self): fail("Construction pin snapshots require explicit names") object.__setattr__(self, "pin_snapshots", MappingProxyType(snapshots)) - def __getitem__(self, key): - if key not in self._FIELDS: - raise KeyError(key) - return getattr(self, key) - - def __iter__(self): - return iter(self._FIELDS) - - def __len__(self): - return len(self._FIELDS) - - def owned_claims(result, owners): """Instance names never acquire Board/SoC reservations or kernel claims.""" - try: - plan = result.resource_plan - except (AttributeError, KeyError): - # Narrow direct-constructor fixtures expose only the legacy mapping. - plan = None - claims = plan.claims if plan is not None else result["claims"] - return (claim for claim in claims - if claim.get("mode") not in {"reserved", "kernel"} and - owners.intersection(claim["owner"].split(";"))) + return (claim for claim in result.resource_plan.claims + if claim.mode not in {"reserved", "kernel"} and + owners.intersection(claim.owner.split(";"))) @dataclass(frozen=True) @@ -278,8 +264,8 @@ def mode_traits(kind, mode): def kernel_policy_constant(result): """Emit the configured kernel policy, not a physical CPU mask primitive.""" - profile = result.get("kernel_profile") - return ("NX_IRQ_KERNEL_" + profile["kernel_policy"].upper() + profile = result.kernel + return ("NX_IRQ_KERNEL_" + profile.kernel_policy.upper() if profile is not None else "NX_IRQ_KERNEL_NONE") diff --git a/tools/configure/providers/descriptor.py b/tools/configure/providers/descriptor.py index 3c3f286e4..8ab45d0e7 100644 --- a/tools/configure/providers/descriptor.py +++ b/tools/configure/providers/descriptor.py @@ -111,6 +111,7 @@ def fact_files(self): "components/soc/stm32/f407_i2s2_slave_tx/include/nexus/components/f407_i2s2_slave_tx.h", "soc/stm32/stm32f407/drivers/i2s2_slave_tx.c", "soc/stm32/stm32f407/private/stm32f407_i2s2_slave_tx.h", + "soc/stm32/stm32f407/private/stm32f407_spi_cold.h", "tools/configure/assemblies/sky-i2s2-slave-tx.toml", "tools/configure/assemblies/qiming-i2s2-slave-tx.toml", "cmake/package/verification/io_evolution/consumers/f407-i2s2-slave-tx.c", @@ -122,6 +123,7 @@ def fact_files(self): import_lock="vendors/gigadevice/gd32f4xx/source.lock.json", references="references.json", configuration_helpers=("soc/gd32/gd32f470/configure/secondary.py", "soc/gd32/gd32f470/configure/integration.py", "soc/gd32/gd32f470/configure/hardware.py", 'soc/gd32/gd32f470/configure/preflight.py', 'soc/gd32/gd32f470/configure/gpio.py'), owned_payload=( + "soc/gd32/gd32f470/private/gd32f470_spi_cold.h", "soc/gd32/gd32f470/drivers/ethernet_frames.c", "soc/gd32/gd32f470/private/gd32f470_eth_frames.h")), "gd32f303": FamilyDescriptor("gd32f303", "gd32", diff --git a/tools/configure/providers/legacy_transport.py b/tools/configure/providers/legacy_transport.py index e5ba0cd5a..07b2f9531 100644 --- a/tools/configure/providers/legacy_transport.py +++ b/tools/configure/providers/legacy_transport.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from tools.configure.providers.common import Construction, fail, kernel_policy_constant +from tools.configure.calls import Address, DirectCall from tools.configure.memory import dma_regions @@ -78,10 +79,10 @@ def constructor(item, result, devices, profile): prefix = "nx_" + profile.family name = item.id.replace("-", "_") state = "s_nx_port_" + name - irq = result["irq"] + irq = result.irq policy = (f"(nx_irq_policy_t){{ {kernel_policy_constant(result)}, " - f"{irq['external_count']}u, {irq['priority_bits']}u, " - f"{irq['syscall_priority'] or 0}u }}") + f"{irq.external_count}u, {irq.priority_bits}u, " + f"{irq.syscall_priority or 0}u }}") lines = [] if item.kind == "uart": duplex = item.mode == "dma-duplex" @@ -127,8 +128,10 @@ def constructor(item, result, devices, profile): return Construction(state_type=prefix + ("_uart_duplex_state_t" if duplex else "_uart_dma_state_t"), definitions=lines, - initialize=f"{prefix}_{'uart_duplex' if duplex else 'uart_dma'}_prepare(&{owner}, &{config})", - stop=[f"nx_uart_port_stop(&s_nx_face_{name})"], + initialize=DirectCall( + f"{prefix}_{'uart_duplex' if duplex else 'uart_dma'}_prepare", + (Address(owner), Address(config))), + stop=[DirectCall("nx_uart_port_stop", (Address(f"s_nx_face_{name}"),))], retain_on_failed_init=True) if item.kind != "i2c": fail("Unsupported legacy transport constructor") @@ -136,10 +139,10 @@ def constructor(item, result, devices, profile): registers = f"(volatile uint32_t*)(uintptr_t){controller}" inputs = profile.i2c_line_inputs for child in devices: - if child["controller"] != item.id: + if child.controller != item.id: continue child_name = child.id.replace("-", "_") - lines += [f"static {prefix}_i2c_async_endpoint_state_t s_nx_endpoint_{child_name} = {{ .port = &{state}.core, .address = {child['address']}u }};", + lines += [f"static {prefix}_i2c_async_endpoint_state_t s_nx_endpoint_{child_name} = {{ .port = &{state}.core, .address = {child.options.address}u }};", f"static const nx_i2c_endpoint_t s_nx_device_face_{child_name} = {{ &nx_i2c_v1_async_endpoint_ops, &s_nx_endpoint_{child_name} }};", f"const nx_i2c_endpoint_t* const nx_device_{child_name} = &s_nx_device_face_{child_name};"] lines += [f"static nx_result_t prepare_{name}(void) {{", @@ -155,5 +158,6 @@ def constructor(item, result, devices, profile): r"/** \brief Exact selected I2C V1 bounded protocol window. */", f"void {vector}_IRQHandler(void) {{ nx_i2c_v1_irq(&{state}.core); }}"] return Construction(state_type=prefix + "_i2c_async_state_t", - definitions=lines, initialize=f"prepare_{name}()", - stop=[f"{prefix}_i2c_async_stop(&{state})"], retain_on_failed_init=True) + definitions=lines, initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall(f"{prefix}_i2c_async_stop", (Address(state),))], + retain_on_failed_init=True) diff --git a/tools/configure/resource_graph.py b/tools/configure/resource_graph.py index d16b4265d..5dc386d60 100644 --- a/tools/configure/resource_graph.py +++ b/tools/configure/resource_graph.py @@ -53,7 +53,7 @@ def _text(value, context): @dataclass(frozen=True) -class ResourceClaimIR(Mapping): +class ResourceClaimIR: key: str owner: str mode: str @@ -62,19 +62,6 @@ class ResourceClaimIR(Mapping): attributes: tuple[tuple[str, int | bool | str], ...] personality: str | None - _FIELDS = ("key", "owner", "mode", "source", "policy", "attributes", - "personality") - - def __getitem__(self, key): - if key not in self._FIELDS: - raise KeyError(key) - return getattr(self, key) - - def __iter__(self): - return iter(self._FIELDS) - - def __len__(self): - return len(self._FIELDS) @dataclass(frozen=True) diff --git a/tools/configure/runtime.py b/tools/configure/runtime.py index f452c73b9..5d4149fd6 100644 --- a/tools/configure/runtime.py +++ b/tools/configure/runtime.py @@ -91,9 +91,9 @@ def resolve(assembly): raise RuntimeError("Unsupported runtime optimization") profile = cpu.resolve(facts["cpu"], facts["irq"], choices["backend"]) policy = kernel.resolve(choices.get("os"), profile, hz) - if policy is not None and policy.get("user_io", "none") != "none": + if policy is not None and getattr(policy, "user_io", "none") != "none": raise RuntimeError("GPIO user I/O requires exact platform memory reservations") - if policy is not None and policy.get("lowpower_port") == "timebase": + if policy is not None and getattr(policy, "lowpower_port", None) == "timebase": raise RuntimeError("CPU-only lowpower runtime requires reviewed SoC timer facts") profile = kernel.bind(profile, policy) digest = hashlib.sha256() diff --git a/tools/configure/runtime_power.py b/tools/configure/runtime_power.py index d7f2ff8b3..417cb24d1 100644 --- a/tools/configure/runtime_power.py +++ b/tools/configure/runtime_power.py @@ -5,7 +5,7 @@ def resolve(value, result): """Optional lifecycle policy belongs to the selected concrete SoC.""" from tools.configure.providers import maintained - resolve_selected = getattr(maintained(result["soc_family"]), + resolve_selected = getattr(maintained(result.soc_family), "resolve_runtime_power", None) if resolve_selected is None: fail("power has no maintained lifecycle for the selected SoC") diff --git a/tools/configure/secondary_controllers.py b/tools/configure/secondary_controllers.py index 48eff2991..5cfc6a8d1 100644 --- a/tools/configure/secondary_controllers.py +++ b/tools/configure/secondary_controllers.py @@ -4,6 +4,7 @@ their existing provider layout and readonly operations tables. It emits no runtime registry, allocator, worker or dispatch layer. """ +from tools.configure.calls import DirectCall from tools.configure.gpio_phase import withdraw_pin from tools.configure.memory import dma_regions from tools.configure.providers.common import Construction, fail, mode_traits @@ -57,13 +58,13 @@ def _gate(item, policy): def _pins(item, result): integration = getattr(item, "integration", None) pins = [dict(pin) for pin in (integration.pins if integration is not None - else item["pins"])] + else item.pins)] if item.kind == "spi": for device in result.devices: - if device["controller"] != item.id or "cs_binding" not in device: + if device.controller != item.id or device.options.cs_binding is None: continue - binding = result["board_bindings"][device["cs_binding"]] - for pin in result["routes"][binding["route"]]["pins"]: + binding = result.board_bindings[device.options.cs_binding] + for pin in result.routes[binding["route"]]["pins"]: pins.append({**dict(pin), "function": "cs"}) return pins @@ -127,16 +128,16 @@ def _clock_hz(item, result): fail("Paired controller has no reviewed nominal clock input") return integration.clock_hz bus = _binding(item).apb - return result["clock"][f"apb{bus}_hz"] + return result.clock[f"apb{bus}_hz"] def _endpoint_faces(item, result, prefix, policy): name = _symbol(item.id) - children = [device for device in result.devices if device["controller"] == item.id] + children = [device for device in result.devices if device.controller == item.id] declarations = [] assignments = [] for index, device in enumerate(children): - child = _symbol(device["id"]) + child = _symbol(device.id) endpoint = "s_nx_endpoint_" + (name if index == 0 else child) implementation = "spi_dma" if item.kind == "spi" else "i2c_async" if index: @@ -148,8 +149,8 @@ def _endpoint_faces(item, result, prefix, policy): declarations += enhanced_endpoints.face(device, endpoint, operations, item.provider) if item.kind != "i2c": - pins = (result["routes"][result["board_bindings"][device["cs_binding"]]["route"]]["pins"] - if "cs_binding" in device else [pin for pin in item["pins"] if pin["function"] == "cs"]) + pins = (result.routes[result.board_bindings[device.options.cs_binding]["route"]]["pins"] + if device.options.cs_binding is not None else [pin for pin in item.pins if pin["function"] == "cs"]) if len(pins) != 1: fail("Paired SPI requires one explicit reviewed CS per child") wire = pins[0]["pin"] @@ -201,7 +202,7 @@ def constructor(item, result, prefix): for dma, stream, _ in dma_paths(item): control = policy.dma_control(dma, stream) body += [f" if (({control} & 1u) != 0u) {{ return NX_ERROR_BUSY; }}"] - if result.get("kernel_profile") is not None: + if result.kernel is not None: body += [" if (((SCB->AIRCR >> 8u) & 7u) > 3u) { return NX_ERROR_PERMISSION; }"] acquire_gate = policy.acquire_gate(item.kind) if not acquire_gate: @@ -247,7 +248,7 @@ def constructor(item, result, prefix): definitions += [f"static const nx_i2c_v1_dma_t s_nx_pair_dma_{name} = {policy.I2C_DMA_PREFIX}_{controller}_DMA(s_nx_dma_regions_{name}, {region_count}u);"] body += [f" {state}.registers = (volatile uint32_t*){controller};", f" {state}.line_inputs = {policy.I2C_LINE_INPUTS};", - f" {state}.line_mask = {sum(1 << int(pin['pin'][2:]) for pin in item['pins'])}u;", + f" {state}.line_mask = {sum(1 << int(pin['pin'][2:]) for pin in item.pins)}u;", f" {state}.peripheral_mhz = {_clock_hz(item, result) // 1000000}u;", f" {state}.dma = {'&s_nx_pair_dma_' + name if mode_traits(item.kind, item.mode).dma else 'NULL'};"] _check(body, f"nx_{prefix}_i2c_async_initialize(&{state}, {item.options.irq.priority}u)") @@ -318,8 +319,8 @@ def constructor(item, result, prefix): "i2c": "i2c_async"}[item.kind] return Construction( state_type=f"nx_{prefix}_{implementation}_state_t", - definitions=definitions, initialize=f"prepare_{name}()", - stop=[f"stop_{name}()"], retain_on_failed_init=True, + definitions=definitions, initialize=DirectCall(f"prepare_{name}"), + stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True, pin_snapshots={pin["pin"]: f"s_nx_pair_pin_saved_{name}[{index}]" for index, pin in enumerate(pins)}) diff --git a/tools/configure/selected_contracts.py b/tools/configure/selected_contracts.py index 643153a4a..15f45916f 100644 --- a/tools/configure/selected_contracts.py +++ b/tools/configure/selected_contracts.py @@ -310,7 +310,7 @@ def describe(item, endpoint=None, *, public_class=None): timestamp_source="unknown", dma_policy="none", cache_line_bytes=0) engines = tuple(sorted({path.split(":", 1)[0] - for path in item.values.get("dma", ())})) + for path in item.dma})) return SelectedContract( id=endpoint.id if endpoint is not None else item.id, provider=item.provider, controller=item.controller, kind=kind, diff --git a/tools/configure/selection.py b/tools/configure/selection.py index cf5981528..33ed150e2 100644 --- a/tools/configure/selection.py +++ b/tools/configure/selection.py @@ -30,10 +30,17 @@ def resolve_interrupts(cpu, irq, backend, provider): facts = {**provider.CPU_FEATURES, **cpu} irq = {"external_count": provider.EXTERNAL_IRQ_COUNT, **irq} profile = profiles.resolve(facts, irq, backend, enum_abi=provider.ENUM_ABI) - result = profile.to_dict()["irq"] - if backend == "freertos" and result["kernel_port"] != provider.FREERTOS_PORT: + return validate_interrupt_profile(profile, provider) + + +def validate_interrupt_profile(profile, provider): + """Bind the resolved CPU once to the exact provider's maintained port.""" + if cpu_abi(profile.cpu) != provider.CPU_ABI: + fail("FreeRTOS kernel CPU ABI differs from the selected SoC") + if (profile.backend == "freertos" and + profile.irq.kernel_port != provider.FREERTOS_PORT): fail(f"Unmaintained FreeRTOS port: {provider.FREERTOS_PORT}") - return result + return profile.irq diff --git a/tools/configure/test_bindings.py b/tools/configure/test_bindings.py index e01d8f03c..2f20cb5f3 100644 --- a/tools/configure/test_bindings.py +++ b/tools/configure/test_bindings.py @@ -7,6 +7,7 @@ import unittest from unittest.mock import patch +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import bindings from tools.configure import configure as gate @@ -16,24 +17,24 @@ def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.addCleanup(self.temporary.cleanup) self.root = Path(self.temporary.name) - self.assembly = self.root / "assembly.json" + self.assembly = self.root / "assembly.toml" self.board = self.root / "board" self.board.mkdir() def fixture(self, filename): path = gate.ROOT / filename - data = gate.load_assembly(path) + data = gate.load_assembly(path).to_dict() source = path.parent / data["board_package"] / "board.json" shutil.copyfile(source, self.board / "board.json") data["board_package"] = str(self.board) return data def write(self, data): - self.assembly.write_text(json.dumps(data), encoding="utf-8") + self.assembly.write_text(render_toml(data), encoding="utf-8") def source(self, data): self.write(data) - result = gate.resolve(self.assembly) + result = gate.resolve_ir(self.assembly) return bindings.emit(result)[1] def assert_unique_storage(self, source): @@ -53,7 +54,7 @@ def add_gpio(self, data, identity): "initial": 16}) board_path.write_text(json.dumps(board), encoding="utf-8") data["controllers"].append({"id": identity, - "binding": "independent-output", + "binding": "independent-output", "authored_kind": "gpio", "mode": "output"}) def test_platform_state_names_remain_legal_instance_ids(self): @@ -79,7 +80,7 @@ def test_gpio_identity_does_not_alias_spi_child_cs_storage(self): self.assert_unique_storage(source) self.assertIn("static nx_stm32_gpio_state_t s_nx_port_sensor_cs;", source) self.assertIn("static nx_stm32_gpio_state_t s_nx_cs_sensor;", source) - self.assertIn(".cs = &s_nx_cs_sensor;", source) + self.assertIn("{ &s_nx_endpoint_spi0, &s_nx_cs_sensor,", source) self.assertIn("nx_binding_sensor_cs = &s_nx_face_sensor_cs;", source) def test_gpio_identity_does_not_alias_pwm_or_adc_storage(self): diff --git a/tools/configure/test_bundle_transaction.py b/tools/configure/test_bundle_transaction.py index d50067437..ee41d1c55 100644 --- a/tools/configure/test_bundle_transaction.py +++ b/tools/configure/test_bundle_transaction.py @@ -9,6 +9,7 @@ import unittest from unittest.mock import patch +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import configure as gate from tools.configure import runtime @@ -24,14 +25,14 @@ def setUp(self): target = self.root / name target.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(gate.ROOT / name, target) - self.assembly = self.root / "assembly.json" + self.assembly = self.root / "assembly.toml" self.authored = { "schema_version": 1, "board_package": "boards/native_reference", "backend": "native", "clock_profile": "model", "controllers": [], "devices": [], "memory_budgets": {"main_stack_bytes": 2048}, "layout": None} - self.assembly.write_text(json.dumps(self.authored)) + self.assembly.write_text(render_toml(self.authored, kinds={"spi0": "spi"})) self.output = self.root / "generated" def configure(self): @@ -50,7 +51,7 @@ def test_same_inputs_preserve_every_generated_file_timestamp(self): def test_owned_marker_never_authorizes_deleting_authored_input(self): self.output.mkdir() (self.output / ".nexus-config-bundle").write_text("owned") - authored = self.output / "assembly.json" + authored = self.output / "assembly.toml" authored.write_bytes(self.assembly.read_bytes()) expected = authored.read_bytes() with self.assertRaisesRegex(gate.ConfigurationError, "input"): @@ -62,7 +63,7 @@ def test_owned_marker_never_authorizes_deleting_board_package(self): shutil.copytree(self.root / "boards/native_reference", package) (package / ".nexus-config-bundle").write_text("owned") self.authored["board_package"] = "external-board" - self.assembly.write_text(json.dumps(self.authored)) + self.assembly.write_text(render_toml(self.authored, kinds={"spi0": "spi"})) expected = (package / "board.json").read_bytes() with self.assertRaisesRegex(gate.ConfigurationError, "input"): gate.configure(self.assembly, package, self.root) @@ -172,7 +173,7 @@ def test_budget_change_rewrites_only_affected_generated_files(self): self.configure() before = self.timestamps() self.authored["memory_budgets"]["main_stack_bytes"] = 4096 - self.assembly.write_text(json.dumps(self.authored)) + self.assembly.write_text(render_toml(self.authored, kinds={"spi0": "spi"})) self.configure() after = self.timestamps() self.assertNotEqual(before["memory.ld"][0], after["memory.ld"][0]) @@ -225,7 +226,7 @@ def test_endpoint_constructor_decisions_are_typed_and_immutable(self): self.authored["devices"] = [{"id": "sensor", "controller": "bus", "driver": "spi-endpoint", "mode": 3, "max_hz": 1000000, "model_bytes": 32}] - self.assembly.write_text(json.dumps(self.authored)) + self.assembly.write_text(render_toml(self.authored, kinds={"spi0": "spi"})) result = gate.resolve_ir(self.assembly, self.root) options = result.devices[0].options self.assertEqual(options.mode, 3) diff --git a/tools/configure/test_can.py b/tools/configure/test_can.py index 8756f5007..9d054f321 100644 --- a/tools/configure/test_can.py +++ b/tools/configure/test_can.py @@ -43,7 +43,7 @@ def test_two_controllers_share_one_immutable_domain_and_distinct_exact_irqs(self self.assertEqual(len(ir.can_domain.filters), 2) with self.assertRaises(FrozenInstanceError): ir.controllers[0].options.timing.prescaler = 7 - claims = {item['key'] for item in ir['claims']} + claims = {item.key for item in ir.claims} self.assertIn('can:filter-domain', claims) self.assertIn('can:filter-bank:0', claims) self.assertIn('can:filter-bank:14', claims) diff --git a/tools/configure/test_cold_preflight.py b/tools/configure/test_cold_preflight.py index 4a508a748..eff53b55f 100644 --- a/tools/configure/test_cold_preflight.py +++ b/tools/configure/test_cold_preflight.py @@ -1,10 +1,10 @@ """Cold assembly rejects observed foreign resources before clock effects.""" -import json from pathlib import Path import tempfile import unittest -from tools.configure import bindings, configure as gate, preflight +from tools.configure.tests.assembly_fixture import render_toml +from tools.configure import bindings, configure as gate, preflight, resource_graph from tools.configure.dma_topology import DmaTopologyIR @@ -38,13 +38,13 @@ def test_kernel_owner_name_does_not_become_external_controller_irq(self): ("threadx", "threadx")): with self.subTest(backend=backend), tempfile.TemporaryDirectory() as directory: data = gate.load_assembly( - gate.ROOT / "tools/configure/assemblies/qiming-baremetal.toml") + gate.ROOT / "tools/configure/assemblies/qiming-baremetal.toml").to_dict() data["backend"] = backend data["controllers"][1]["id"] = identity if backend == "threadx": data["os"] = {"tick_source": "systick"} - path = Path(directory) / "assembly.json" - path.write_text(json.dumps(data)) + path = Path(directory) / "assembly.toml" + path.write_text(render_toml(data)) result = gate.resolve_ir(path) source = "\n".join(preflight.selected(result)) self.assertIn("USART1_IRQn", source) @@ -58,9 +58,13 @@ def test_new_families_project_only_exact_cold_uart_pin_and_irq_boundaries(self): ("stm32u575", "USART1", ("PA9", "PA10"), "U575", "RCC_APB2ENR_USART1EN", "RCC_CCIPR1_USART1SEL")): item = AdmissionInput(id="console", kind="uart", controller=controller, + mode="irq-byte-event", pins=[{"pin": pin} for pin in pins]) result = AdmissionInput(soc_family=family, controllers=(item,), devices=(), dma_topology=DmaTopologyIR(()), + resource_plan=resource_graph.resolve([{ + "key": "irq:" + controller, "owner": "console", + "mode": "irq-byte-event", "source": "projection-test"}]), claims=[{"key": "irq:" + controller, "owner": "console"}]) with self.subTest(family=family): source = "\n".join(preflight.emit(result)) diff --git a/tools/configure/test_configure.py b/tools/configure/test_configure.py index c0361ea52..322fcf543 100644 --- a/tools/configure/test_configure.py +++ b/tools/configure/test_configure.py @@ -5,6 +5,7 @@ import tempfile import unittest +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import configure as gate @@ -19,7 +20,7 @@ def setUp(self): destination = self.root / name destination.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(gate.ROOT / name, destination) - self.assembly = self.root / "assembly.json" + self.assembly = self.root / "assembly.toml" self.data = {"schema_version": 1, "board_package": "boards/native_reference", "backend": "native", "clock_profile": "model", "controllers": [], "devices": [], @@ -30,11 +31,13 @@ def setUp(self): self.routes_path = self.root / "soc/native/routes.json" def write(self, path, data): - path.write_text(json.dumps(data), encoding="utf-8") + path.write_text(render_toml(data, kinds={"led0": "gpio", + "spi0": "spi", "uart0": "uart"}) if path.suffix == ".toml" + else json.dumps(data), encoding="utf-8") def resolve(self): self.write(self.assembly, self.data) - return gate.resolve(self.assembly, self.root) + return gate.resolve_ir(self.assembly, self.root) def mutate(self, path, callback): data = json.loads(path.read_text()) @@ -42,7 +45,8 @@ def mutate(self, path, callback): self.write(path, data) def selected(self, **extra): - return {"id": "led", "binding": "led0", "mode": "output", **extra} + return {"id": "led", "binding": "led0", "mode": "output", + "authored_kind": "gpio", **extra} def rejected(self, match): with self.assertRaisesRegex(gate.ConfigurationError, match): @@ -50,16 +54,16 @@ def rejected(self, match): def test_minimal_has_exact_identity_and_no_tasks(self): result = self.resolve() - self.assertEqual(result["part"], "NATIVE_MODEL") - self.assertFalse(result["physical_qualified"]) - self.assertEqual(result["controllers"], []) - self.assertEqual(len(result["inputs"]), 5) - self.assertNotIn("tasks", result) + self.assertEqual(result.part, "NATIVE_MODEL") + self.assertFalse(result.physical_qualified) + self.assertEqual(result.controllers, ()) + self.assertEqual(len(result.inputs), 5) + self.assertNotIn("tasks", result.to_dict()) def test_duplicate_json_keys_rejected(self): - self.assembly.write_text('{"schema_version":1,"schema_version":1}') + self.soc_path.write_text('{"schema_version":1,"schema_version":1}') with self.assertRaisesRegex(gate.ConfigurationError, "Duplicate"): - gate.resolve(self.assembly, self.root) + gate.resolve_ir(self.assembly, self.root) def test_unknown_assembly_key_rejected(self): self.data["business_worker"] = True @@ -169,7 +173,7 @@ def update(item): binding.pop("mask", None) binding["initial"] = 1 self.mutate(self.board_path, update) - self.assertEqual(self.resolve()["part"], "NATIVE_MODEL") + self.assertEqual(self.resolve().part, "NATIVE_MODEL") def test_gpio_effective_mask_is_materialized_in_the_typed_decision(self): self.mutate(self.board_path, @@ -180,9 +184,9 @@ def test_gpio_effective_mask_is_materialized_in_the_typed_decision(self): self.assertEqual(ir.controllers[0].options.mask, 1) def test_spi_builtin_cs_initial_is_not_a_gpio_bitmask(self): - result = gate.resolve(gate.ROOT / + result = gate.resolve_ir(gate.ROOT / "tools/configure/assemblies/liangshan-baremetal.toml") - self.assertEqual(result["board_bindings"]["spi_flash"]["initial"], 1) + self.assertEqual(result.board_bindings["spi_flash"]["initial"], 1) def test_unknown_nested_pin_key_rejected(self): self.mutate(self.routes_path, lambda item: item["routes"][0]["pins"][0].update(auto=True)) @@ -247,15 +251,15 @@ def test_input_hash_changes_effective_identity(self): before = self.resolve() self.mutate(self.board_path, lambda item: item["unknowns"].append("another limit")) after = self.resolve() - self.assertNotEqual(before["configuration_sha256"], after["configuration_sha256"]) + self.assertNotEqual(before.configuration_sha256, after.configuration_sha256) def test_relocated_identical_input_has_identical_config(self): before = self.resolve() destination = self.root.parent / (self.root.name + "-relocated") shutil.copytree(self.root, destination) self.addCleanup(shutil.rmtree, destination) - after = gate.resolve(destination / "assembly.json", destination) - self.assertEqual(before["configuration_sha256"], after["configuration_sha256"]) + after = gate.resolve_ir(destination / "assembly.toml", destination) + self.assertEqual(before.configuration_sha256, after.configuration_sha256) def test_unsupported_image_offset_rejected(self): self.data["layout"] = "layout.json" @@ -271,9 +275,8 @@ def test_generated_text_has_no_control_characters(self): for char in text), path.name) # Inspect the MCU-specific runtime hooks as well as the Native bundle. from tools.configure import bindings - result = self.resolve() - result["soc_family"] = "stm32f407" - result["part"] = "STM32F407VET6" + result = gate.resolve_ir(gate.ROOT / + "tools/configure/assemblies/qiming-baremetal-empty.toml") _, source = bindings.emit(result) self.assertIn(r"\brief C runtime", source) self.assertNotIn("\x08", source) @@ -289,7 +292,7 @@ def test_budget_schema_generated_without_double_stack(self): def test_irrelevant_controller_settings_rejected(self): self.data["controllers"] = [self.selected(baud=115200)] - self.rejected("Unsupported gpio configuration fields") + self.rejected("gpio.led:.*unknown.*baud") def test_reserved_c_identifier_rejected(self): self.data["controllers"] = [self.selected(id="for")] @@ -309,7 +312,7 @@ def test_polling_does_not_claim_unused_irq(self): self.data["controllers"] = [{"id":"spi", "binding":"spi0", "mode":"short-poll"}] self.mutate(self.soc_path, lambda item: item["controllers"]["SPI0"].update(irq=["SPI0_IRQn"])) result = self.resolve() - self.assertFalse(any(item["key"].startswith("irq:") for item in result["claims"])) + self.assertFalse(any(item.key.startswith("irq:") for item in result.claims)) def test_uart_storage_capacity_bounded(self): self.data["controllers"] = [{"id":"uart", "binding":"uart0", "mode":"irq-byte-event", @@ -329,18 +332,18 @@ class AdvancedResolverTests(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.addCleanup(self.temporary.cleanup) - self.assembly = Path(self.temporary.name) / "assembly.json" + self.assembly = Path(self.temporary.name) / "assembly.toml" def fixture(self, name): directory = gate.ROOT / "tests/contracts/stm32_assembly" - result = gate.load_assembly(directory / ("ve-" + name + ".toml")) + result = gate.load_assembly(directory / ("ve-" + name + ".toml")).to_dict() result["board_package"] = str(directory / result["board_package"]) return result def reject(self, data, message): - self.assembly.write_text(json.dumps(data)) + self.assembly.write_text(render_toml(data)) with self.assertRaisesRegex(gate.ConfigurationError, message): - gate.resolve(self.assembly) + gate.resolve_ir(self.assembly) def test_shared_exti_priority_contradiction_rejected(self): data = self.fixture("exti") @@ -387,7 +390,7 @@ def test_unused_device_capacity_rejected_for_each_transport(self): with self.subTest(transport=transport): data = self.fixture(transport) data["devices"][0]["capacity"] = 32 - self.reject(data, "device:.*unknown.*capacity") + self.reject(data, "device.*unknown.*capacity") def test_i2c_spi_options_rejected(self): for field, value in (("mode", 3), ("max_hz", 50000), @@ -395,7 +398,7 @@ def test_i2c_spi_options_rejected(self): with self.subTest(field=field): data = self.fixture("i2c") data["devices"][0][field] = value - self.reject(data, "I2C device:.*unknown.*" + field) + self.reject(data, "i2c_device.*unknown.*" + field) def test_i2c_endpoint_requires_address(self): data = self.fixture("i2c") @@ -405,31 +408,31 @@ def test_i2c_endpoint_requires_address(self): def test_spi_i2c_address_rejected(self): data = self.fixture("spi") data["devices"][0]["address"] = 80 - self.reject(data, "SPI device:.*unknown.*address") + self.reject(data, "spi_device.*unknown.*address") def test_gd32_builtin_spi_cs_remains_supported(self): directory = gate.ROOT / "tests/contracts/gd32_assembly" - data = gate.load_assembly(directory / "spi.toml") + data = gate.load_assembly(directory / "spi.toml").to_dict() data["board_package"] = str(directory / data["board_package"]) data["devices"] = [{"id": "sensor", "controller": "spi0", "driver": "spi-endpoint", "mode": 3, "max_hz": 1000000}] - self.assembly.write_text(json.dumps(data)) - result = gate.resolve(self.assembly) - self.assertNotIn("cs_binding", result["devices"][0]) - self.assertIn("pin:PF6", {item["key"] for item in result["claims"]}) + self.assembly.write_text(render_toml(data)) + result = gate.resolve_ir(self.assembly) + self.assertIsNone(result.devices[0].options.cs_binding) + self.assertIn("pin:PF6", {item.key for item in result.claims}) def test_invalid_device_reconfiguration_invalidates_old_bundle(self): for transport, field, value in (("spi", "capacity", 32), ("i2c", "mode", 3)): with self.subTest(transport=transport, field=field): data = self.fixture(transport) - self.assembly.write_text(json.dumps(data)) + self.assembly.write_text(render_toml(data)) output = Path(self.temporary.name) / (transport + "-bundle") gate.configure(self.assembly, output) self.assertTrue((output / "bindings.c").is_file()) data["devices"][0][field] = value - self.assembly.write_text(json.dumps(data)) + self.assembly.write_text(render_toml(data)) with self.assertRaisesRegex(gate.ConfigurationError, field): gate.configure(self.assembly, output) self.assertFalse(output.exists()) diff --git a/tools/configure/test_construction_contract.py b/tools/configure/test_construction_contract.py index a1690f05c..44bd058c8 100644 --- a/tools/configure/test_construction_contract.py +++ b/tools/configure/test_construction_contract.py @@ -5,6 +5,7 @@ from unittest.mock import patch from tools.configure import bindings, configure as gate +from tools.configure.calls import DirectCall from tools.configure.providers import common, maintained @@ -14,7 +15,7 @@ def resolve(self, relative): def construction(self, result, item): return maintained(result.soc_family).constructor( - item, result, result["board_bindings"], result["routes"], + item, result, result.board_bindings, result.routes, result.devices) def test_every_family_returns_explicit_immutable_construction(self): @@ -36,8 +37,11 @@ def test_every_family_returns_explicit_immutable_construction(self): bool) self.assertIsInstance(construction.definitions, tuple) self.assertIsInstance(construction.stop, tuple) - self.assertEqual(construction["definitions"], - construction.definitions) + self.assertIsInstance(construction.initialize, DirectCall) + self.assertTrue(all(isinstance(call, DirectCall) + for call in construction.stop)) + with self.assertRaises(TypeError): + construction["definitions"] with self.assertRaises(FrozenInstanceError): construction.state_type = "foreign_state_t" @@ -72,7 +76,7 @@ def test_shared_engines_and_secondary_controllers_keep_explicit_types(self): f"{result.controllers.index(item) + 1}u;", source.index("nx_platform_start(void)")) initialize = source.index( - "result.primary = " + construction.initialize) + "result.primary = " + construction.initialize.render()) self.assertLess(before, initialize) def test_storage_type_is_provider_owned_without_prefix_inference(self): @@ -108,7 +112,7 @@ def test_failed_initializer_is_in_cleanup_only_when_provider_requires(self): source = bindings.emit(result)[1] start = source[source.index("nx_platform_start(void)"):] initialize = start.index( - "result.primary = " + construction.initialize) + "result.primary = " + construction.initialize.render()) self.assertEqual("s_nx_platform_initialized = 1u;" in start[:initialize], retained) self.assertIn("result.cleanup = " diff --git a/tools/configure/test_controller_integration.py b/tools/configure/test_controller_integration.py index 95e468d21..4b566b323 100644 --- a/tools/configure/test_controller_integration.py +++ b/tools/configure/test_controller_integration.py @@ -24,7 +24,7 @@ def test_primary_spi_rejects_foreign_or_reordered_selected_dma(self): item.integration, dma=selected)) with self.assertRaisesRegex(ValueError, "integration"): provider.constructor(changed, result, - result["board_bindings"], result["routes"], result.devices) + result.board_bindings, result.routes, result.devices) def test_cold_resource_projection_reads_the_canonical_plan(self): result = gate.resolve_ir(gate.ROOT / @@ -58,8 +58,8 @@ def test_selected_pair_keeps_resource_dma_and_irq_owners(self): self.assertIs(selected.irq, item.options.irq) self.assertIs(selected.irq_profile, result.irq) self.assertIs(selected.clock_plan, result.clock_plan) - self.assertIs(selected.clock_inputs, result["clock"]) - self.assertIs(selected.pins, item["pins"]) + self.assertIs(selected.clock_inputs, result.clock) + self.assertIs(selected.pins, item.pins) self.assertTrue(any(selected.hardware is binding for binding in maintained(result.soc_family).CONTROLLER_BINDINGS)) with self.assertRaises(FrozenInstanceError): @@ -85,8 +85,8 @@ def test_physical_part_and_selected_pins_reuse_frozen_values(self): self.assertIsNotNone(part) self.assertEqual(part.part, "STM32F407VET6") self.assertEqual(part.package, "LQFP100") - self.assertIs(part.flash, result["flash"]) - self.assertIs(part.memory, result["memory"]) + self.assertIs(part.flash, result.flash) + self.assertIs(part.memory, result.memory) with self.assertRaises(TypeError): part.flash["size"] = 1 self.assertIn(".size = 524288u", bindings.emit(result)[1]) diff --git a/tools/configure/test_descriptors.py b/tools/configure/test_descriptors.py index 423a12a4f..d3cdb6890 100644 --- a/tools/configure/test_descriptors.py +++ b/tools/configure/test_descriptors.py @@ -66,6 +66,14 @@ def test_h743_dma_configuration_binds_private_provider_payload(self): "soc/stm32/stm32h743/private/stm32h743_dma.h"): self.assertIn(name, payload) + def test_gd470_spi_cold_contract_is_owned_without_a_second_build_graph(self): + from tools.configure import dependencies + + header = "soc/gd32/gd32f470/private/gd32f470_spi_cold.h" + self.assertIn(header, descriptor.maintained("gd32f470").owned_payload) + self.assertNotIn(header, descriptor.maintained("native").owned_payload) + self.assertNotIn(header, dependencies.source_names("platform", "gd32f470")) + def test_h743_flash_configuration_binds_the_complete_private_ram_pod(self): from tools.configure import dependencies diff --git a/tools/configure/test_documentation.py b/tools/configure/test_documentation.py index be4e01aa2..ce76b2fa2 100644 --- a/tools/configure/test_documentation.py +++ b/tools/configure/test_documentation.py @@ -27,6 +27,20 @@ def test_complete_assembly_executes_real_resource_checks(self): with self.assertRaisesRegex(ValueError, "Unsupported mode"): self.check("```toml\n" + stale + "```\n") + def test_relative_board_package_stays_relative_to_document(self): + package = self.document.parent / "board with spaces" + package.mkdir() + source = gate.ROOT / "boards/native_reference/board.json" + (package / "board.json").write_bytes(source.read_bytes()) + assembly = (gate.ROOT / + "tools/configure/assemblies/native-gpio.toml").read_text() + assembly = assembly.replace('board = "native_reference"', + '\'board_package\' = "board with spaces"') + self.assertIn("board_package", assembly) + rows = self.check("```toml\n" + assembly + "```\n") + self.assertEqual(rows[0]["kind"], "assembly") + self.assertEqual(len(rows[0]["configuration_sha256"]), 64) + def test_partial_examples_require_explicit_reason(self): fragment = '[gpio.led0]\nbinding="led0"\nmode="output"\n' with self.assertRaisesRegex(ValueError, "fragment"): diff --git a/tools/configure/test_ethernet.py b/tools/configure/test_ethernet.py index cb90514ef..6bfd2e873 100644 --- a/tools/configure/test_ethernet.py +++ b/tools/configure/test_ethernet.py @@ -27,13 +27,13 @@ def test_two_exact_zg_parts_have_one_mac_and_no_stream_dma_claim(self): for label in ("stm32-zg", "gd32"): with self.subTest(label=label): result = gate.resolve_ir(self.assembly(label)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) self.assertEqual(len(result.controllers), 1) self.assertEqual(result.controllers[0].options.address, (2, 0, 0, 0, 0, 1)) with self.assertRaises(FrozenInstanceError): result.controllers[0].options.rx_count = 3 - claims = {item["key"] for item in result["claims"]} + claims = {item.key for item in result.claims} self.assertIn("irq:ENET" if label == "gd32" else "irq:ETH", claims) self.assertIn("pin:PG14", claims) self.assertIn("clock:ETHMAC", claims) diff --git a/tools/configure/test_ethernet_frames_configuration.py b/tools/configure/test_ethernet_frames_configuration.py index cba72e28e..029d565b7 100644 --- a/tools/configure/test_ethernet_frames_configuration.py +++ b/tools/configure/test_ethernet_frames_configuration.py @@ -41,7 +41,7 @@ def test_exact_zg_parts_freeze_storage_and_derive_a_counter_domain(self): with self.subTest(family=family): result = self.resolve(family) item, = result.controllers - self.assertEqual(result['part'], part) + self.assertEqual(result.part, part) self.assertEqual((item.kind, item.controller, item.mode), ('eth_frames', controller, 'rmii-vlan-sg-ptp')) @@ -53,7 +53,7 @@ def test_exact_zg_parts_freeze_storage_and_derive_a_counter_domain(self): self.assertLessEqual(options.clock_domain, 0xffffffff) with self.assertRaises(FrozenInstanceError): options.clock_domain = 5 - self.assertFalse(result['physical_qualified']) + self.assertFalse(result.physical_qualified) def test_factory_uses_distinct_frame_face_and_paired_mdio_without_io(self): for family in ('f407zg', 'gd470'): diff --git a/tools/configure/test_exti_edge_types.py b/tools/configure/test_exti_edge_types.py index 8d8319284..9c93cd75a 100644 --- a/tools/configure/test_exti_edge_types.py +++ b/tools/configure/test_exti_edge_types.py @@ -1,9 +1,9 @@ """Malformed EXTI enums reject before any generated bundle is published.""" -import json from pathlib import Path import tempfile import unittest +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import configure as gate from soc.stm32.stm32g474 import configure as stm32g474 from tools.configure.providers.common import ConfigurationError @@ -15,13 +15,13 @@ def test_unhashable_authored_edge_has_a_configuration_error(self): for edge in ([], {}, ["rising"], {"edge": "both"}): with self.subTest(edge=edge), tempfile.TemporaryDirectory() as value: directory = Path(value) - authored = gate.load_assembly(fixture) + authored = gate.load_assembly(fixture).to_dict() authored["board_package"] = str(fixture.parent / "exti_board") selection = next(item for item in authored["controllers"] if "edge" in item) selection["edge"] = edge - assembly = directory / "assembly.json" - assembly.write_text(json.dumps(authored)) + assembly = directory / "assembly.toml" + assembly.write_text(render_toml(authored)) output = directory / "generated" with self.assertRaisesRegex(ConfigurationError, "EXTI edge"): gate.configure(assembly, output) diff --git a/tools/configure/test_f407_i2s2.py b/tools/configure/test_f407_i2s2.py index db41ed3bf..605f3cb30 100644 --- a/tools/configure/test_f407_i2s2.py +++ b/tools/configure/test_f407_i2s2.py @@ -35,10 +35,10 @@ def normalize(self, selection): def test_authored_role_does_not_select_a_software_library_or_endpoint(self): selected = {"profile": "slave-tx-stereo16-pb12-pb13-pb15"} normalized = self.normalize(selected) - self.assertEqual(normalized["i2s2"], selected) - self.assertEqual(normalized["controllers"], []) - self.assertEqual(normalized["devices"], []) - self.assertNotIn("components", normalized) + self.assertEqual(normalized.i2s2, selected) + self.assertEqual(normalized.controllers, ()) + self.assertEqual(normalized.devices, ()) + self.assertEqual(normalized.components, ()) def test_authored_role_rejects_unreviewed_hardware_properties(self): with self.assertRaises(ValueError): diff --git a/tools/configure/test_f407_i2s2_configuration.py b/tools/configure/test_f407_i2s2_configuration.py index 769a6255a..039cbf08c 100644 --- a/tools/configure/test_f407_i2s2_configuration.py +++ b/tools/configure/test_f407_i2s2_configuration.py @@ -127,12 +127,12 @@ def test_default_empty_assemblies_keep_role_and_hardware_claims_absent(self): with self.subTest(part=part): result = self.resolve(self.assembly(name, selected=False)) self.assertIsNone(result.i2s2) - self.assertEqual(result["part"], part) + self.assertEqual(result.part, part) self.assertEqual(result.controllers, ()) self.assertEqual(result.devices, ()) self.assertFalse(any(claim.owner == "i2s2" for claim in result.resource_plan.claims)) - self.assertEqual(result["components"], ()) + self.assertEqual(result.components, ()) def test_selected_exact_parts_have_immutable_role_and_physical_claims(self): for name, part in FIXTURES: @@ -145,8 +145,8 @@ def test_selected_exact_parts_have_immutable_role_and_physical_claims(self): self.assertIsInstance(role.claims, tuple) with self.assertRaises((AttributeError, TypeError)): role.profile = "unreviewed" - with self.assertRaises(TypeError): - result.values["i2s2"] = None + with self.assertRaises(dataclasses.FrozenInstanceError): + result.i2s2 = None claims = [claim for claim in result.resource_plan.claims if claim.owner == "i2s2"] self.assertEqual({claim.key for claim in claims}, CLAIMS) @@ -158,8 +158,8 @@ def test_selected_exact_parts_have_immutable_role_and_physical_claims(self): {claim.key for claim in claims}) self.assertEqual(result.controllers, ()) self.assertEqual(result.devices, ()) - self.assertEqual(result["components"], ()) - self.assertFalse(result["physical_qualified"]) + self.assertEqual(result.components, ()) + self.assertFalse(result.physical_qualified) def test_role_adds_only_its_seven_normalized_resource_reservations(self): base = self.resolve(self.assembly(selected=False)) @@ -183,7 +183,7 @@ def test_existing_spi2_board_binding_conflicts_in_the_same_normal_ir(self): def test_unreviewed_part_backend_and_clock_are_rejected(self): foreign = self.assembly(name="nucleo-u575-baremetal-empty.toml", selected=False) - self.assertEqual(self.resolve(foreign)["part"], "STM32U575ZIT6Q") + self.assertEqual(self.resolve(foreign).part, "STM32U575ZIT6Q") with self.assertRaisesRegex(ValueError, "I2S2"): self.resolve(self.assembly(name="nucleo-u575-baremetal-empty.toml")) with self.assertRaisesRegex(ValueError, "I2S2"): diff --git a/tools/configure/test_fact_references.py b/tools/configure/test_fact_references.py index ca46fb4fe..93b37d7ea 100644 --- a/tools/configure/test_fact_references.py +++ b/tools/configure/test_fact_references.py @@ -157,15 +157,15 @@ def test_reference_index_is_bound_to_resolved_identity_and_input_closure(self): result = gate.resolve_ir(self.assembly, self.root) self.assertEqual(result.to_dict()["fact_references"], json.loads(self.references.read_text())) - inputs = {item["path"]: item["sha256"] for item in result["inputs"]} + inputs = {item["path"]: item["sha256"] for item in result.inputs} self.assertEqual(inputs["soc/gd32/gd32f470/references.json"], hashlib.sha256(self.references.read_bytes()).hexdigest()) - before = result["configuration_sha256"] + before = result.configuration_sha256 data = json.loads(self.references.read_text()) data["documents"]["datasheet"]["revision"] += "-fixture" self.references.write_text(json.dumps(data)) self.assertNotEqual(before, gate.resolve_ir( - self.assembly, self.root)["configuration_sha256"]) + self.assembly, self.root).configuration_sha256) def test_reference_change_during_generation_revokes_old_bundle(self): output = self.root / "generated" diff --git a/tools/configure/test_factory_features.py b/tools/configure/test_factory_features.py index 33cb5cdce..426de68ef 100644 --- a/tools/configure/test_factory_features.py +++ b/tools/configure/test_factory_features.py @@ -1,11 +1,11 @@ """Optional factory metadata and nominal IDs exercise actual generated C.""" from dataclasses import FrozenInstanceError -import json from pathlib import Path import subprocess import tempfile import unittest +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import configure as gate from tools.configure import factory @@ -92,12 +92,12 @@ def test_mcu_capabilities_execute_compiled_getters_and_exact_bounds(self): "gd32_assembly/blocks.toml"): with self.subTest(fixture=fixture): path = gate.ROOT / "tests/contracts" / fixture - normalized = gate.load_assembly(path) + normalized = gate.load_assembly(path).to_dict() normalized["board_package"] = str( path.parent / normalized["board_package"]) normalized["factory"] = {"capabilities": True} - assembly = self.root / "mcu.json" - assembly.write_text(json.dumps(normalized)) + assembly = self.root / "mcu.toml" + assembly.write_text(render_toml(normalized)) result = gate.resolve_ir(assembly) header, getters = factory.emit(result) (self.root / "nexus_factory.h").write_text(header) @@ -106,7 +106,7 @@ def test_mcu_capabilities_execute_compiled_getters_and_exact_bounds(self): for class_name, shape, instances in factory.classes(result): kind = class_name.removesuffix("_device") for instance in instances: - name = factory.symbol(instance["id"]) + name = factory.symbol(instance.id) alias = ("nx_device_" if shape == "endpoint" else "nx_binding_") + name aliases.append(f"const nx_{kind}_{shape}_t* const " diff --git a/tools/configure/test_family_loading.py b/tools/configure/test_family_loading.py index d682df35c..a0173040d 100644 --- a/tools/configure/test_family_loading.py +++ b/tools/configure/test_family_loading.py @@ -101,8 +101,8 @@ def test_native_construction_and_rejection_share_canonical_types(self): ROOT / "tools/configure/assemblies/native-gpio.toml") provider = maintained("native") construction = provider.constructor( - result.controllers[0], result, result["board_bindings"], - result["routes"], result.devices) + result.controllers[0], result, result.board_bindings, + result.routes, result.devices) self.assertIs(type(construction), common.Construction) with self.assertRaises(common.ConfigurationError): maintained("../native") diff --git a/tools/configure/test_fixture_assembly.py b/tools/configure/test_fixture_assembly.py new file mode 100644 index 000000000..37ddc2df5 --- /dev/null +++ b/tools/configure/test_fixture_assembly.py @@ -0,0 +1,49 @@ +"""Test fixtures exercise the public TOML parser without a JSON input path.""" +from pathlib import Path +import tempfile +import unittest + +from tools.configure import configure +from tools.configure.tests.assembly_fixture import render_toml + + +class FixtureAssemblyTests(unittest.TestCase): + def fixture(self): + return { + "schema_version": 1, + "board_package": str(configure.ROOT / "boards/native_reference"), + "backend": "native", "clock_profile": "model", + "controllers": [{"id": "console", "authored_kind": "uart", + "binding": "uart0", "mode": "irq-byte-event", "baud": 115200, + "irq_priority": 5, "calls_os": False, "rx_profile": "bytes", + "rx_capacity": 32}], + "devices": [], "memory_budgets": {"main_stack_bytes": 2048}, + "layout": None} + + def resolve(self, document): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "assembly.toml" + path.write_text(render_toml(document), encoding="utf-8") + return configure.resolve_ir(path) + + def test_uart_fixture_uses_authored_rx_and_irq_tables(self): + result = self.resolve(self.fixture()) + uart, = result.controllers + self.assertEqual(uart.options.baud, 115200) + self.assertEqual(uart.options.rx_capacity, 32) + self.assertEqual(uart.options.irq.priority, 5) + self.assertFalse(uart.options.irq.calls_os) + + def test_unknown_fields_and_boolean_numbers_reach_production_rejection(self): + document = self.fixture() + document["business_worker"] = True + with self.assertRaisesRegex(ValueError, "unknown.*business_worker"): + self.resolve(document) + document.pop("business_worker") + document["controllers"][0]["baud"] = True + with self.assertRaisesRegex(ValueError, "baud"): + self.resolve(document) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/configure/test_flash_polling_irq.py b/tools/configure/test_flash_polling_irq.py index 3d1b87217..4fbba2565 100644 --- a/tools/configure/test_flash_polling_irq.py +++ b/tools/configure/test_flash_polling_irq.py @@ -51,13 +51,13 @@ def test_selected_physical_controller_owns_named_completion_irq(self): [("physical", controller, "poll-word")], [(item.id, item.controller, item.mode) for item in result.controllers]) - claims = [item for item in result["claims"] - if item["key"] == "irq:" + controller] + claims = [item for item in result.claims + if item.key == "irq:" + controller] self.assertEqual(1, len(claims)) - self.assertEqual("physical", claims[0]["owner"]) - self.assertEqual("poll-word", claims[0]["mode"]) - self.assertNotIn("irq:4", {item["key"] - for item in result["claims"]}) + self.assertEqual("physical", claims[0].owner) + self.assertEqual("poll-word", claims[0].mode) + self.assertNotIn("irq:4", {item.key + for item in result.claims}) self.assertEqual("irq:" + controller, gate.normalize_resource("irq:" + vector)) @@ -99,7 +99,7 @@ def test_unselected_flash_does_not_claim_its_completion_irq(self): self.assembly(label, selected=False), ROOT) self.assertFalse(result.controllers) self.assertNotIn("irq:" + controller, - {item["key"] for item in result["claims"]}) + {item.key for item in result.claims}) def test_unselected_flash_allows_a_board_reserved_completion_irq(self): for label, _, controller, _ in CASES: @@ -109,10 +109,10 @@ def test_unselected_flash_allows_a_board_reserved_completion_irq(self): label, selected=False, reserved="irq:" + controller + suffix), ROOT) self.assertFalse(result.controllers) - claims = [item for item in result["claims"] - if item["key"] == "irq:" + controller] + claims = [item for item in result.claims + if item.key == "irq:" + controller] self.assertEqual(1, len(claims)) - self.assertEqual("board-reserved", claims[0]["owner"]) + self.assertEqual("board-reserved", claims[0].owner) def test_polling_lease_does_not_generate_an_isr_or_irq_capability(self): self.assertFalse(mode_traits("flash", "poll-word").irq) diff --git a/tools/configure/test_g474.py b/tools/configure/test_g474.py index 3ab380e58..a7d8cbbc7 100644 --- a/tools/configure/test_g474.py +++ b/tools/configure/test_g474.py @@ -27,15 +27,15 @@ def resolve(self, path): def test_single_ram_alias_exact_irqs_and_initial_provider_scope(self): result = self.resolve(self.assembly()) - self.assertEqual(result["part"], "STM32G474RET6") - self.assertEqual(result["irq"]["external_count"], 102) - self.assertEqual(result["clock"]["core_hz"], 170000000) - self.assertEqual([(item["origin"], item["size"]) for item in result["memory"]], + self.assertEqual(result.part, "STM32G474RET6") + self.assertEqual(result.irq.external_count, 102) + self.assertEqual(result.clock["core_hz"], 170000000) + self.assertEqual([(item["origin"], item["size"]) for item in result.memory], [(0x20000000, 98304), (0x10000000, 32768)]) self.assertEqual([(item.kind, item.mode) for item in result.controllers], [("gpio", "output"), ("uart", "irq-bytes")]) - self.assertFalse(result["physical_qualified"]) - self.assertEqual(sum(result["flash"]["erase_blocks"]), 524288) + self.assertFalse(result.physical_qualified) + self.assertEqual(sum(result.flash["erase_blocks"]), 524288) def test_g4_source_dependency_uses_g4_checkout_identity(self): with patch.object(gate, "dependency_identity", wraps=gate.dependency_identity) as dependency: @@ -44,9 +44,9 @@ def test_g4_source_dependency_uses_g4_checkout_identity(self): self.assertEqual(paths, ["vendors/arm/CMSIS_6", "vendors/st/cmsis_device_g4", "vendors/st/stm32g4_hal"]) self.assertNotIn("vendors/st/cmsis_device_f4", paths) - self.assertEqual(result["source_dependencies"][1]["commit"], + self.assertEqual(result.source_dependencies[1]["commit"], "626ee412334a5ed2e5b320af5a8d77d69f03a558") - self.assertEqual(result["source_dependencies"][2]["commit"], + self.assertEqual(result.source_dependencies[2]["commit"], "f5929f431f9effe45fbe18f5337e4753ced9ac92") def test_byte_mode_emits_exact_static_ring_and_counts_before_cold_call(self): @@ -74,7 +74,7 @@ def test_freertos_policy_and_exact_clock_rejection(self): result = self.resolve(self.assembly(lambda text: text.replace( 'backend = "baremetal"', 'backend = "freertos"'))) self.assertEqual(result.kernel.kernel_port, "GCC/ARM_CM4F") - self.assertEqual(result["irq"]["syscall_priority"], 5) + self.assertEqual(result.irq.syscall_priority, 5) with self.assertRaises(ValueError): self.resolve(self.assembly(lambda text: text.replace( 'clock = "hse24-pll170"', 'clock = "hse8-pll168"'))) diff --git a/tools/configure/test_g474_adc_group_configuration.py b/tools/configure/test_g474_adc_group_configuration.py index 47f05a9d1..584808047 100644 --- a/tools/configure/test_g474_adc_group_configuration.py +++ b/tools/configure/test_g474_adc_group_configuration.py @@ -5,13 +5,13 @@ """ from dataclasses import FrozenInstanceError import hashlib -import json from pathlib import Path import re import tempfile import tomllib import unittest +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import bindings, configure, factory, selected_contracts @@ -53,16 +53,16 @@ def test_four_inputs_select_one_real_adc1_and_one_nominal_face(self): records = selected_contracts.for_configuration(result) self.assertEqual({row.public_class for row in records}, {"adc_group"}) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) - def test_legacy_json_adc_group_binds_the_validated_route_review(self): + def test_authored_adc_group_binds_the_validated_route_review(self): fixture = self.fixture() assembly = { "schema_version": 1, "board_package": str(fixture.parent / "boards/g474-adc-group"), "backend": "baremetal", "clock_profile": "hse24-pll170", "controllers": [{ - "id": "samples", "binding": "samples", + "id": "samples", "binding": "samples", "authored_kind": "adc_group", "mode": "regular-sequence-dma", "channels": [2, 1], "sample_times": [0, 0], "reference_mv": 3300, "exclusive": True, "silicon_profile": "g474-adc-group-x", @@ -70,14 +70,14 @@ def test_legacy_json_adc_group_binds_the_validated_route_review(self): "devices": [], "memory_budgets": {"main_stack_bytes": 2048}, "layout": None} with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "assembly.json" - path.write_text(json.dumps(assembly)) + path = Path(temporary) / "assembly.toml" + path.write_text(render_toml(assembly)) result = configure.resolve_ir(path, configure.ROOT) item, = result.controllers self.assertEqual((item.kind, item.controller, item.options.channels), ("adc_group", "ADC1", (2, 1))) policy = selected_contracts.silicon_policy(result, item) - review, = (entry for entry in result["inputs"] + review, = (entry for entry in result.inputs if entry["sha256"] == policy.review_sha256) review_bytes = (configure.ROOT / review["path"]).read_bytes() self.assertEqual(hashlib.sha256(review_bytes).hexdigest(), @@ -133,7 +133,7 @@ def test_regular_and_injected_claim_only_their_real_dma_resources(self): for group in ("regular", "injected"): with self.subTest(group=group): result = self.resolve(group + "-software") - keys = {claim["key"] for claim in result["claims"]} + keys = {claim.key for claim in result.claims} self.assertTrue({"controller:ADC1", "controller:ADC2", "controller:ADC345", "irq:ADC1_2"} <= keys) has_dma = group == "regular" @@ -151,7 +151,7 @@ def test_software_and_timer_have_distinct_owned_trigger_contracts(self): for group in ("regular", "injected"): with self.subTest(group=group): result = self.resolve(group + "-timer") - keys = {claim["key"] for claim in result["claims"]} + keys = {claim.key for claim in result.claims} self.assertIn("controller:TIM6", keys) self.assertNotIn("irq:TIM6_DAC", keys) self.assertTrue(result.trigger_plan.edges) @@ -232,7 +232,7 @@ def test_named_review_record_is_an_actual_configuration_input(self): result = self.resolve() item, = result.controllers policy = selected_contracts.silicon_policy(result, item) - review = [entry for entry in result["inputs"] + review = [entry for entry in result.inputs if entry["sha256"] == policy.review_sha256] self.assertEqual(len(review), 1) self.assertTrue(re.fullmatch( diff --git a/tools/configure/test_g474_control_configuration.py b/tools/configure/test_g474_control_configuration.py index 084fb5dee..2eac6711d 100644 --- a/tools/configure/test_g474_control_configuration.py +++ b/tools/configure/test_g474_control_configuration.py @@ -32,7 +32,7 @@ def test_real_kind_has_typed_factory_and_exact_resource_claims(self): self.assertIn("const nx_pwm_group_t* nx_factory_pwm_group(", header) self.assertIn("NX_PWM_GROUP_ID_WAVE", header) self.assertIn("return nx_binding_wave;", getters) - claims = {claim["key"] for claim in result["claims"]} + claims = {claim.key for claim in result.claims} self.assertTrue({"controller:TIM1", "controller:TIM15", "controller:COMP1", "pin:PA1", "pin:PA4", "pin:PA8", "pin:PA9", "pin:PA10"} <= claims) diff --git a/tools/configure/test_g474_coupled_io_configuration.py b/tools/configure/test_g474_coupled_io_configuration.py index cdda8eb96..01a39343b 100644 --- a/tools/configure/test_g474_coupled_io_configuration.py +++ b/tools/configure/test_g474_coupled_io_configuration.py @@ -47,7 +47,7 @@ def test_pwm_adc_is_one_immutable_tim1_personality(self): self.assertEqual(item.options.sample_times, (0,)) with self.assertRaises(FrozenInstanceError): item.options.trigger_ticks = 51 - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_pwm_adc_factory_does_not_expose_independently_startable_subfaces(self): result = self.resolve("pwm-adc") @@ -67,7 +67,7 @@ def test_pwm_adc_factory_does_not_expose_independently_startable_subfaces(self): def test_pwm_adc_owns_every_subresource_and_real_trigger_edge(self): result = self.resolve("pwm-adc") - owners = {claim["key"]: claim["owner"] for claim in result["claims"]} + owners = {claim.key: claim.owner for claim in result.claims} expected = {"controller:TIM1", "controller:ADC1", "controller:ADC2", "controller:ADC345", "controller:COMP1", "controller:TIM15", "dma:DMA1:stream1", "controller:DMAMUX1_Channel0", @@ -134,7 +134,7 @@ def test_can_fd_keeps_its_own_typed_value_and_filter_plan(self): (256, 2047, "standard", 0)) with self.assertRaises(FrozenInstanceError): filter_.mask = 0 - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_can_fd_factory_is_distinct_from_classic_can(self): result = self.resolve("can-fd") @@ -152,7 +152,7 @@ def test_can_fd_factory_is_distinct_from_classic_can(self): def test_can_fd_owns_physical_ram_and_both_vectors_with_one_publisher(self): result = self.resolve("can-fd") - claims = {claim["key"] for claim in result["claims"]} + claims = {claim.key for claim in result.claims} self.assertTrue({"controller:FDCAN1", "message-ram:FDCAN1", "clock:FDCAN", "irq:FDCAN1_IT0", "irq:FDCAN1_IT1"}.issubset(claims)) @@ -202,15 +202,15 @@ def test_can_fd_normal_preserves_the_same_physical_owner_and_filter_plan(self): ("can_fd", "FDCAN1", "iso-fd-normal")) self.assertEqual(item.options, loopback.controllers[0].options) def physical_claims(result): - return [(claim["key"], claim["owner"], claim["source"]) - for claim in result["claims"]] + return [(claim.key, claim.owner, claim.source) + for claim in result.claims] self.assertEqual(physical_claims(normal), physical_claims(loopback)) for result, mode in ((normal, "iso-fd-normal"), (loopback, "iso-fd-loopback")): - self.assertTrue(all(claim["mode"] == mode - for claim in result["claims"] - if claim["owner"] == "fieldbus")) - self.assertFalse(normal["physical_qualified"]) + self.assertTrue(all(claim.mode == mode + for claim in result.claims + if claim.owner == "fieldbus")) + self.assertFalse(normal.physical_qualified) self.assertEqual(item.options.message_ram_words, 212) with self.assertRaises(FrozenInstanceError): item.mode = "iso-fd-loopback" @@ -259,7 +259,7 @@ def test_new_faces_reach_the_atomic_bundle_without_weakening_existing_modes(self (output / "nexus_factory.h").read_text()) for name in ("pwm-group", "timer-capture", "encoder"): result = configure.resolve_ir(self.root / (name + ".toml")) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) if __name__ == "__main__": diff --git a/tools/configure/test_g474_exti.py b/tools/configure/test_g474_exti.py index f09314911..24e59e18c 100644 --- a/tools/configure/test_g474_exti.py +++ b/tools/configure/test_g474_exti.py @@ -26,11 +26,11 @@ def test_exact_two_lines_have_independent_pins_irqs_and_storage(self): for item in result.controllers], [("exti", "EXTI0", "edge-event"), ("exti", "EXTI1", "edge-event")]) - owned = {item["key"] for item in result["claims"] - if item["owner"] in {"edge0", "edge1"}} + owned = {item.key for item in result.claims + if item.owner in {"edge0", "edge1"}} self.assertTrue({"pin:PA0", "pin:PA1", "irq:EXTI0", "irq:EXTI1"} <= owned) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_invalid_edge_capacity_and_irq_are_rejected(self): for old, new in (('edge = "both"', 'edge = "debounced"'), diff --git a/tools/configure/test_g474_pwm_shared_configuration.py b/tools/configure/test_g474_pwm_shared_configuration.py index dbd2b636b..ef4f8493d 100644 --- a/tools/configure/test_g474_pwm_shared_configuration.py +++ b/tools/configure/test_g474_pwm_shared_configuration.py @@ -107,7 +107,7 @@ def test_exact_pair_derives_one_immutable_owner_and_two_public_decisions(self): owner.period_ticks = 2000 with self.assertRaises(FrozenInstanceError): owner.members[1].channel = 1 - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_declaration_order_does_not_select_a_different_physical_owner(self): forward = self.normal() @@ -190,8 +190,9 @@ def test_new_mode_cannot_escape_its_exact_provider_or_channel_routes(self): result = self.normal() item = result.controllers[0] provider = maintained("stm32g474") - selection = dict(item.values) - route = {key: item[key] for key in ("kind", "controller", "pins")} + selection = item.to_dict() + route = {key: getattr(item, key) for key in + ("kind", "controller", "pins")} provider.validate_selection(selection, route) for family in ("stm32f103", "stm32f407", "stm32h743", "gd32f470"): with self.subTest(family=family), self.assertRaises(ValueError): diff --git a/tools/configure/test_gd32_exti_generation.py b/tools/configure/test_gd32_exti_generation.py index d0cc72718..9d04074f8 100644 --- a/tools/configure/test_gd32_exti_generation.py +++ b/tools/configure/test_gd32_exti_generation.py @@ -41,8 +41,8 @@ def test_failed_exti_init_retains_current_instance_for_real_cleanup(self): initialize = start.index("result.primary = nx_gd32_exti_initialize(") self.assertLess(start.index("s_nx_platform_initialized = 1u;"), initialize) - self.assertIn("s_nx_port_edge3.initialized ? " - "nx_exti_port_stop(&s_nx_face_edge3) : NX_SUCCESS", source) + self.assertIn("nx_gd32_exti_close(&s_nx_port_edge3)", source) + self.assertNotIn("s_nx_port_edge3.initialized ?", source) self.assertIn("result.cleanup = " "nx_platform_stop_impl(diagnostic, true);", start) self.assertIn("if (status_0 != NX_SUCCESS) { return status_0; }", source) diff --git a/tools/configure/test_gpio_phase.py b/tools/configure/test_gpio_phase.py index 6ebb9e8bd..454796d31 100644 --- a/tools/configure/test_gpio_phase.py +++ b/tools/configure/test_gpio_phase.py @@ -7,6 +7,7 @@ import unittest from unittest.mock import patch +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import bindings, configure as gate, gpio_phase from tools.configure.providers import maintained @@ -83,7 +84,7 @@ def test_endpoint_cs_is_in_the_cold_lease_without_a_second_claim_source(self): "tests/contracts/multi_controller_assembly/" + label + ".toml") controller_pins = {pin["pin"] for item in result.controllers - for pin in item["pins"]} + for pin in item.pins} self.assertNotIn(hidden, controller_pins) self.assertIn(hidden, gpio_phase.selected_pins(result)) @@ -119,7 +120,7 @@ def test_reserved_owner_names_do_not_transfer_unselected_physical_pins(self): with self.subTest(identity=identity), tempfile.TemporaryDirectory() as directory: root = Path(directory) data = gate.load_assembly( - gate.ROOT / "tools/configure/assemblies/qiming-baremetal.toml") + gate.ROOT / "tools/configure/assemblies/qiming-baremetal.toml").to_dict() board_path = Path(data["board_package"]) / "board.json" board = json.loads(board_path.read_text()) if custom_reservation: @@ -129,8 +130,8 @@ def test_reserved_owner_names_do_not_transfer_unselected_physical_pins(self): (root / "board.json").write_text(json.dumps(board)) data["board_package"] = str(root) data["controllers"][0]["id"] = identity - assembly = root / "assembly.json" - assembly.write_text(json.dumps(data)) + assembly = root / "assembly.toml" + assembly.write_text(render_toml(data)) result = gate.resolve_ir(assembly) self.assertNotIn("PA13", gpio_phase.selected_pins(result)) self.assertNotIn("PA14", gpio_phase.selected_pins(result)) diff --git a/tools/configure/test_h743_clock.py b/tools/configure/test_h743_clock.py index 1da778aa4..941c99a6e 100644 --- a/tools/configure/test_h743_clock.py +++ b/tools/configure/test_h743_clock.py @@ -30,10 +30,10 @@ def test_fixed_internal_input_and_consistent_selected_frequencies(self): with self.subTest(hz=hz): result = gate.resolve_ir(self.assembly( f"hsi{hz // 1000000}-fixed-cache-off")) - self.assertEqual(dict(result["clock"]), { + self.assertEqual(dict(result.clock), { "source": "hsi", "input_hz": 64000000, "core_hz": hz, "apb1_hz": hz, "apb2_hz": hz}) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_cpu_and_uart_initialize_share_selected_clock_for_both_backends(self): for backend in ("baremetal", "freertos"): diff --git a/tools/configure/test_h743_high_clock_configuration.py b/tools/configure/test_h743_high_clock_configuration.py index 60fe4c9e9..98b36e2d2 100644 --- a/tools/configure/test_h743_high_clock_configuration.py +++ b/tools/configure/test_h743_high_clock_configuration.py @@ -82,7 +82,7 @@ def test_cpu_bus_timer_and_local_kernel_sources_are_distinct(self): self.assertEqual((domain.source, domain.nominal_hz, domain.minimum_hz, domain.maximum_hz), expected) self.assertNotIn("adc1", plan.domains) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_legacy_projections_have_one_reviewed_nominal_authority(self): facts = gate.load(gate.ROOT / "soc/stm32/stm32h743/soc.json") @@ -171,7 +171,7 @@ def test_fixed_hsi_profiles_keep_existing_call_shape_and_zero_clock_owner(self): with self.subTest(profile=name): result = self.resolved(name) self.assertIsNone(getattr(result, "clock_plan", None)) - self.assertEqual(dict(result["clock"]), { + self.assertEqual(dict(result.clock), { "source":"hsi", "input_hz":64000000, "core_hz":mhz*1000000, "apb1_hz":mhz*1000000, "apb2_hz":mhz*1000000}) @@ -279,9 +279,9 @@ def test_high_i2c_uses_declared_hsi_and_independent_apb_bounds(self): self.assertEqual(timing.provenance, "authored_declared_assumptions") self.assertFalse(timing.physical_qualified) self.assertEqual(timing.cr1_filter_bits, 0) - self.assertEqual((timing["assumptions"]["kernel_hz_min"], - timing["assumptions"]["kernel_hz_max"], - timing["assumptions"]["pclk_hz_min"]), + self.assertEqual((timing.assumptions["kernel_hz_min"], + timing.assumptions["kernel_hz_max"], + timing.assumptions["pclk_hz_min"]), (62000000, 65000000, 91140000)) self.assertIn(f".timing = 0x{timing.timingr:08x}u,", bindings.emit(result)[1]) diff --git a/tools/configure/test_i2c_modes.py b/tools/configure/test_i2c_modes.py index 02f2bda32..a5dce800a 100644 --- a/tools/configure/test_i2c_modes.py +++ b/tools/configure/test_i2c_modes.py @@ -42,7 +42,7 @@ def test_async_modes_have_immutable_irq_policy_and_exact_directional_claims(self self.assertEqual(item.options.irq.priority, 5) with self.assertRaises(FrozenInstanceError): item.options.irq.priority = 6 - claims = {claim["key"] for claim in ir["claims"]} + claims = {claim.key for claim in ir.claims} controller = "I2C1" if prefix == "stm32" else "I2C0" self.assertIn(f"irq:{controller}_EV", claims) self.assertIn(f"irq:{controller}_ER", claims) diff --git a/tools/configure/test_i2c_timing_configuration.py b/tools/configure/test_i2c_timing_configuration.py index 3621dcf17..1f4a57adc 100644 --- a/tools/configure/test_i2c_timing_configuration.py +++ b/tools/configure/test_i2c_timing_configuration.py @@ -3,6 +3,7 @@ import re import tempfile import unittest +from unittest.mock import patch from tools.configure import bindings, configure @@ -63,6 +64,17 @@ def test_declared_clock_must_contain_selected_source_nominal(self): ValueError, "clock|nominal|source"): self.resolve(**change) + def test_solver_result_reaches_selection_without_json_reconstruction(self): + from tools.configure.i2c_timing import I2cTimingIR + + with patch.object(I2cTimingIR, "to_dict", + side_effect=AssertionError("output-only projection")): + result = self.resolve() + item = next(item for item in result.controllers if item.kind == "i2c") + self.assertGreater(item.options.timing.timingr, 0) + self.assertEqual(item.options.timing.source, + "software test declared envelope; not measured") + def test_unimplemented_filter_choices_cannot_be_calculation_only(self): for changes in (dict(digital_filter_cycles=1), dict(analog_filter_ns_min=0, diff --git a/tools/configure/test_import_dependencies.py b/tools/configure/test_import_dependencies.py index d4fa6b233..a5d750828 100644 --- a/tools/configure/test_import_dependencies.py +++ b/tools/configure/test_import_dependencies.py @@ -11,8 +11,8 @@ class ImportedDependencyContracts(unittest.TestCase): def test_imported_family_also_binds_its_pinned_cmsis_core(self): - result = configure.resolve(ASSEMBLY, ROOT) - records = {item["path"]: item for item in result["source_dependencies"]} + result = configure.resolve_ir(ASSEMBLY, ROOT) + records = {item["path"]: item for item in result.source_dependencies} self.assertEqual(records["vendors/arm/CMSIS_6"]["commit"], "af1b110e46a1720349f6f125e386e126767699bd") self.assertEqual(records["vendors/gigadevice/gd32f30x"]["kind"], @@ -24,7 +24,7 @@ def test_auxiliary_git_identity_failure_cannot_be_ignored(self): "auxiliary CMSIS identity changed")): with self.assertRaisesRegex(configure.ConfigurationError, "auxiliary CMSIS identity changed"): - configure.resolve(ASSEMBLY, ROOT) + configure.resolve_ir(ASSEMBLY, ROOT) if __name__ == "__main__": diff --git a/tools/configure/test_io_evolution_configuration.py b/tools/configure/test_io_evolution_configuration.py index 667393c50..7915f1708 100644 --- a/tools/configure/test_io_evolution_configuration.py +++ b/tools/configure/test_io_evolution_configuration.py @@ -1,11 +1,13 @@ """Enhanced endpoints remain typed through real authored static assembly.""" -from dataclasses import FrozenInstanceError +from dataclasses import FrozenInstanceError, replace from pathlib import Path import re import tempfile import unittest from tools.configure import bindings, configure, enhanced_endpoints, factory +from tools.configure.calls import Address, DirectCall, Symbol +from tools.configure.providers.common import Construction class EnhancedEndpointConfiguration(unittest.TestCase): @@ -72,7 +74,42 @@ def test_spi_dma_owner_is_a_normal_explicit_component(self): selected = self.root / "owner.toml" selected.write_text(contents) result = configure.resolve_ir(selected) - self.assertIn("spi-dma-owner", result["components"]) + self.assertIn("spi-dma-owner", result.components) + + def test_sequence_wraps_selected_direct_engine_call_and_typed_stop(self): + result = configure.resolve_ir(configure.ROOT / + "tests/contracts/io_evolution_assembly/f407-spi-dma-transaction.toml") + item = next(value for value in result.controllers + if value.interface_kind == "spi_dma_transaction") + name = item.id.replace("-", "_") + initialize = DirectCall("nx_test_engine_prepare", + (Address("s_engine"), Address("s_config"))) + close = DirectCall("nx_test_engine_close", (Address("s_engine"),)) + raw = Construction("nx_engine_t", (), initialize, + (DirectCall("nx_spi_port_stop", (Symbol(f"nx_binding_{name}"),)), + close), True) + adapted = enhanced_endpoints.dma_construction(item, raw, "stm32") + self.assertIn("nx_result_t status = " + initialize.render() + ";", + "\n".join(adapted.definitions)) + self.assertEqual(adapted.initialize, + DirectCall(f"prepare_sequence_{name}")) + self.assertEqual(adapted.stop, + (DirectCall("nx_spi_dma_transaction_stop", + (Symbol(f"nx_binding_{name}"),)), close)) + + def test_sequence_does_not_rename_a_declared_engine_prepare(self): + result = configure.resolve_ir(configure.ROOT / + "tests/contracts/io_evolution_assembly/f407-spi-dma-transaction.toml") + item = next(value for value in result.controllers + if value.interface_kind == "spi_dma_transaction") + name = item.id.replace("-", "_") + declaration = f"static nx_result_t prepare_{name}(void) {{ return NX_SUCCESS; }}" + raw = Construction("nx_engine_t", (declaration,), + DirectCall(f"prepare_{name}"), (), True) + adapted = enhanced_endpoints.dma_construction(item, raw, "stm32") + self.assertEqual(adapted.definitions[0], declaration) + self.assertIn(f"nx_result_t status = prepare_{name}();", + "\n".join(adapted.definitions)) def test_dma_owner_binding_uses_exact_physical_provider_membership(self): for family, prefix in (("f407", "stm32"), ("gd470", "gd32")): @@ -238,9 +275,9 @@ def test_enhanced_traits_report_words_and_per_message_limits(self): "nx_factory_i2c_long_device_traits_t")): with self.subTest(driver=driver): result = self.resolve(fixture, driver, kind) - data = result.to_dict() - data["factory"] = {"capabilities": True} - header, source = factory.emit(data) + selected = replace(result, factory=replace(result.factory, + capabilities=True)) + header, source = factory.emit(selected) self.assertIn(expected, header) if kind == "spi": self.assertIn("supported_word_bits", header) diff --git a/tools/configure/test_io_traits.py b/tools/configure/test_io_traits.py index 6947c38d2..ed114be83 100644 --- a/tools/configure/test_io_traits.py +++ b/tools/configure/test_io_traits.py @@ -9,7 +9,9 @@ def controller(kind, mode, provider="stm32f407", **options): values = {"id": "selected", "kind": kind, "controller": "SELECTED", - "binding": "selected", "mode": mode, **options} + "binding": "selected", "mode": mode, + "pins": (), "route": "metadata-unit-fixture", + "mask": 0, "initial": 0, **options} return ControllerIR.create(values, provider) diff --git a/tools/configure/test_ir_unification.py b/tools/configure/test_ir_unification.py new file mode 100644 index 000000000..9d80c65a2 --- /dev/null +++ b/tools/configure/test_ir_unification.py @@ -0,0 +1,80 @@ +"""One typed decision owns both generation and the exported JSON projection.""" +from dataclasses import fields, replace +from collections.abc import Mapping +from pathlib import Path +import tempfile +import unittest + +from tools.configure import configure, emission, factory +from tools.configure.ir import ConfigurationIR + + +class UnifiedIrTests(unittest.TestCase): + def resolve(self): + return configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/native.toml") + + def test_resolver_returns_one_typed_authority(self): + result = configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/native.toml") + self.assertIsInstance(result, ConfigurationIR) + self.assertFalse(hasattr(configure, "resolve")) + + def test_named_decisions_are_the_only_serialized_values(self): + result = self.resolve() + changed = replace(result, board="reviewed-other-board") + self.assertEqual(changed.to_dict()["board"], changed.board) + self.assertNotIn("values", {item.name for item in fields(result)}) + cpu = replace(result.cpu_profile, enum_abi="wide") + self.assertEqual(cpu.to_dict()["enum_abi"], "wide") + self.assertNotIn("values", {item.name for item in fields(cpu)}) + changed_cpu = replace(result, cpu_profile=cpu) + self.assertEqual(changed_cpu.enum_abi, cpu.enum_abi) + + def test_ir_is_not_a_mapping_compatibility_facade(self): + result = self.resolve() + self.assertNotIsInstance(result, Mapping) + self.assertNotIsInstance(result.cpu_profile, Mapping) + with self.assertRaises(TypeError): + result["board"] + + def test_selected_claims_and_option_projection_have_one_owner(self): + result = configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/qiming-baremetal.toml") + self.assertNotIsInstance(result.claims[0], Mapping) + uart = next(item for item in result.controllers if item.kind == "uart") + changed = replace(uart, options=replace(uart.options, baud=57600)) + self.assertEqual(changed.to_dict()["baud"], 57600) + self.assertNotIsInstance(changed, Mapping) + self.assertEqual(changed.integration.resources, uart.integration.resources) + + def test_non_gpio_routes_preserve_their_declared_mask_and_safe_level(self): + h743 = configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/h743-high-peripherals.toml") + pwm = next(item for item in h743.controllers if item.kind == "pwm") + self.assertEqual(pwm.mask, 64) + gd470 = configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/liangshan-baremetal-spi.toml") + spi = next(item for item in gd470.controllers if item.kind == "spi") + self.assertEqual(spi.initial, 1) + self.assertEqual(spi.to_dict()["initial"], 1) + + def test_emitters_reject_exported_mapping_instead_of_reinterpreting_it(self): + projected = self.resolve().to_dict() + with self.assertRaises(TypeError): + factory.emit(projected) + with tempfile.TemporaryDirectory() as name: + with self.assertRaises(TypeError): + emission.emit_bundle(projected, Path(name)) + self.assertEqual(list(Path(name).iterdir()), []) + + def test_json_is_not_an_authored_platform_entry(self): + with tempfile.TemporaryDirectory() as name: + path = Path(name) / "assembly.json" + path.write_text("{}", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "TOML"): + configure.load_assembly(path) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/configure/test_irq_profiles.py b/tools/configure/test_irq_profiles.py index cf7161670..7b5792cbe 100644 --- a/tools/configure/test_irq_profiles.py +++ b/tools/configure/test_irq_profiles.py @@ -46,9 +46,9 @@ def test_kernel_profile_derives_the_irq_range_and_ceiling(self): irq = selection.resolve_interrupts(provider.CPU_ABI, {"priority_bits": bits}, "freertos", provider) - self.assertEqual(irq["maximum_priority"], (1 << bits) - 1) - self.assertEqual(irq["syscall_priority"], 10 if bits == 8 else 5) - self.assertEqual(irq["kernel_port"], "GCC/ARM_CM4F") + self.assertEqual(irq.maximum_priority, (1 << bits) - 1) + self.assertEqual(irq.syscall_priority, 10 if bits == 8 else 5) + self.assertEqual(irq.kernel_port, "GCC/ARM_CM4F") with self.assertRaisesRegex(gate.ConfigurationError, "IRQ priority bits"): selection.resolve_interrupts(provider.CPU_ABI, @@ -81,7 +81,7 @@ def test_irq_policy_is_typed_immutable_and_exported_for_each_backend(self): self.assertEqual(ir.irq.maximum_priority, 15) self.assertEqual(ir.irq.syscall_priority, 5 if backend == "freertos" else None) - self.assertEqual(ir["cpu"]["dwt_cyccnt"], dwt) + self.assertEqual(ir.cpu["dwt_cyccnt"], dwt) with self.assertRaises(FrozenInstanceError): ir.irq.priority_bits = 2 output = Path(temp) / "bundle" @@ -107,7 +107,7 @@ def test_abi_assertion_remains_separate_from_cpu_capability_facts(self): 'float_abi="native"\n' '[memory]\nmain_stack_bytes=2048\n') ir = gate.resolve_ir(source) - self.assertFalse(ir["cpu"]["dwt_cyccnt"]) + self.assertFalse(ir.cpu["dwt_cyccnt"]) def test_selected_irq_checks_fact_range_and_os_policy_before_generation(self): original = gate.ROOT / "tools/configure/assemblies/qiming-freertos-uart.toml" diff --git a/tools/configure/test_kernel.py b/tools/configure/test_kernel.py index 806d41a9c..6df1d04d1 100644 --- a/tools/configure/test_kernel.py +++ b/tools/configure/test_kernel.py @@ -278,7 +278,7 @@ def test_bind_updates_irq_policy_without_changing_cpu_abi(self): bound = module.bind(profile, policy) self.assertEqual(bound.irq.syscall_priority, 7) self.assertEqual(bound.compile_options, profile.compile_options) - self.assertEqual(bound["cpu"], profile["cpu"]) + self.assertEqual(bound.cpu, profile.cpu) class KernelAssemblyTests(unittest.TestCase): diff --git a/tools/configure/test_kernel_assembly.py b/tools/configure/test_kernel_assembly.py index dbe082563..2d2a5edd2 100644 --- a/tools/configure/test_kernel_assembly.py +++ b/tools/configure/test_kernel_assembly.py @@ -47,23 +47,23 @@ def test_threadx_assembly_retains_distinct_type_claims_and_source_identity(self) with self.subTest(board=board): result = self.resolve(self.assembly(board=board)) self.assertEqual(result.kernel.backend, "threadx") - self.assertNotIn("idle_stack_words", result.kernel) - claims = {item["key"] for item in result["claims"]} + self.assertFalse(hasattr(result.kernel, "idle_stack_words")) + claims = {item.key for item in result.claims} self.assertIn("irq:PendSV", claims) self.assertIn("irq:SysTick", claims) self.assertNotIn("irq:SVC", claims) self.assertIn("ext/threadx", {item["path"] for item in - result["source_dependencies"]}) + result.source_dependencies}) def test_threadx_native_is_host_architecture_with_no_fake_exceptions(self): result = self.resolve(self.assembly(board="native_reference", uart=False)) self.assertEqual(result.cpu_profile.arch, "native") self.assertEqual(result.kernel.tick_source, "host") - self.assertFalse(any(item["key"].startswith("irq:") for item in result["claims"])) + self.assertFalse(any(item.key.startswith("irq:") for item in result.claims)) def test_external_threadx_tick_does_not_claim_systick(self): result = self.resolve(self.assembly(options='tick_source="external"\n')) - claims = {item["key"] for item in result["claims"]} + claims = {item.key for item in result.claims} self.assertIn("irq:PendSV", claims) self.assertNotIn("irq:SysTick", claims) diff --git a/tools/configure/test_legacy_devices.py b/tools/configure/test_legacy_devices.py index 9d94a2945..e01b7d3d9 100644 --- a/tools/configure/test_legacy_devices.py +++ b/tools/configure/test_legacy_devices.py @@ -15,10 +15,17 @@ class LegacyDevicesTests(unittest.TestCase): def fixture(self, family): return ROOT / f"tests/contracts/mainstream_assembly/{family}-devices.toml" + def test_constructor_rejects_unresolved_mapping(self): + for family in ("stm32f103", "gd32f303"): + with self.subTest(family=family): + provider = maintained(family) + with self.assertRaisesRegex(ConfigurationError, "typed"): + provider.constructor({}, None, {}, {}, ()) + def test_exact_devices_generate_private_static_states(self): for family in ("f103", "gd303"): with self.subTest(family=family): - result = configure.resolve(self.fixture(family), ROOT) + result = configure.resolve_ir(self.fixture(family), ROOT) _, source = bindings.emit(result) prefix = "stm32f103" if family == "f103" else "gd32f303" self.assertIn(f"nx_{prefix}_adc_prepare", source) @@ -26,12 +33,12 @@ def test_exact_devices_generate_private_static_states(self): self.assertIn(f"nx_{prefix}_flash_prepare", source) self.assertNotIn(".startup_delay_us = ", source) self.assertNotIn("malloc(", source) - physical = next(item for item in result["controllers"] - if item["kind"] == "flash") - provider = maintained(result["soc_family"]) + physical = next(item for item in result.controllers + if item.kind == "flash") + provider = maintained(result.soc_family) construction = provider.constructor( - physical, result, result["board_bindings"], - result["routes"], result["devices"]) + physical, result, result.board_bindings, + result.routes, result.devices) self.assertFalse(construction.retain_on_failed_init) self.assertEqual(provider.FLASH_PROGRAM_UNIT, 2) self.assertEqual(provider.FLASH_INPUT_ALIGNMENT, 1) @@ -50,7 +57,7 @@ def test_adc_rejects_unreviewed_channel_sample_and_reference(self): path = Path(directory) / "invalid.toml" path.write_text(text.replace(old, new)) with self.assertRaises(ConfigurationError): - configure.resolve(path, ROOT) + configure.resolve_ir(path, ROOT) def test_non_exact_timer_divider_is_rejected(self): for family in ("f103", "gd303"): @@ -60,7 +67,7 @@ def test_non_exact_timer_divider_is_rejected(self): path = Path(directory) / "invalid.toml" path.write_text(text) with self.assertRaises(ConfigurationError): - configure.resolve(path, ROOT) + configure.resolve_ir(path, ROOT) def test_source_sdk_requires_each_new_gd303_device_header(self): from tools.configure.providers import descriptor @@ -72,7 +79,7 @@ def test_source_sdk_requires_each_new_gd303_device_header(self): def test_physical_flash_factory_rejects_missing_exact_provider_contract(self): from tools.configure import factory - result = configure.resolve(self.fixture("f103"), ROOT) + result = configure.resolve_ir(self.fixture("f103"), ROOT) provider = maintained("stm32f103") for attribute in ("FLASH_PROGRAM_UNIT", "FLASH_INPUT_ALIGNMENT"): with self.subTest(attribute=attribute): diff --git a/tools/configure/test_legacy_transport.py b/tools/configure/test_legacy_transport.py index 402584ea9..d6a63128c 100644 --- a/tools/configure/test_legacy_transport.py +++ b/tools/configure/test_legacy_transport.py @@ -38,7 +38,7 @@ def test_both_precise_families_generate_direct_dma_faces(self): path = self.assembly(family, mode) ir = configure.resolve_ir(path) self.assertEqual(ir.controllers[0].mode, mode) - keys = {claim["key"] for claim in ir["claims"]} + keys = {claim.key for claim in ir.claims} tx = ("dma:DMA1:stream7" if family == "stm32f103" else "dma:DMA0:stream3") rx = ("dma:DMA1:stream6" if family == "stm32f103" else diff --git a/tools/configure/test_matrix.py b/tools/configure/test_matrix.py index 8047cabeb..39d2a4302 100644 --- a/tools/configure/test_matrix.py +++ b/tools/configure/test_matrix.py @@ -135,9 +135,9 @@ def test_h743_flash_reference_has_one_canonical_high_profile(self): ("adc", "ADC1", "single-shot"), ("flash", "FLASH", "poll-word")}) result = configure.resolve_ir(configure.ROOT / reference) - self.assertEqual(result["clock_profile"], "hsi64-pll384-cache-off") + self.assertEqual(result.clock_profile, "hsi64-pll384-cache-off") self.assertEqual(result.clock_plan.reviewed_revision_ids, (0x2003,)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_u575_sram4_copy_reference_is_exact_and_not_physical_qualification(self): reference = "tools/configure/assemblies/u575-lpdma-copy-stop2.toml" @@ -178,8 +178,8 @@ def test_g474_named_state_reference_is_exact_and_not_physical_qualification(self for item in row["selected"]], [("gpio", "GPIOA", "named-states")]) result = configure.resolve_ir(configure.ROOT / reference) - self.assertEqual(result["clock_profile"], "hse24-pll170") - self.assertFalse(result["physical_qualified"]) + self.assertEqual(result.clock_profile, "hse24-pll170") + self.assertFalse(result.physical_qualified) classes = {name: items for name, _, items in factory.classes(result)} self.assertEqual([item.id for item in classes["pin_state"]], ["status"]) self.assertEqual(classes["gpio"], []) @@ -194,13 +194,13 @@ def test_h743_spi_dma_references_keep_exact_finite_software_scope(self): self.assertEqual(reference["scope"], "public_reference") self.assertEqual(reference["physical_status"], "not_executed") ir = configure.resolve_ir(configure.ROOT / path) - self.assertEqual((ir.board, ir.soc_family, ir["part"], ir.backend, - ir["clock_profile"]), + self.assertEqual((ir.board, ir.soc_family, ir.part, ir.backend, + ir.clock_profile), ("stm32h743zi-hsi-reference", "stm32h743", "STM32H743ZIT6", "baremetal", "hsi64-fixed-cache-on" if path.endswith( "cache-on.toml") else "hsi64-fixed-cache-off")) - self.assertFalse(ir["physical_qualified"]) + self.assertFalse(ir.physical_qualified) controllers = {item.id: item for item in ir.controllers} self.assertEqual(set(controllers), {"bus4"} | ({"console"} if mode else set())) @@ -208,26 +208,26 @@ def test_h743_spi_dma_references_keep_exact_finite_software_scope(self): self.assertEqual((spi.kind, spi.controller, spi.mode), ("spi", "SPI4", "dma")) self.assertEqual([(pin["pin"], pin["function"], pin["af"]) - for pin in spi["pins"]], + for pin in spi.pins], [("PE12", "sck", 5), ("PE13", "miso", 5), ("PE14", "mosi", 5)]) - self.assertEqual(tuple(spi["dma"]), + self.assertEqual(tuple(spi.dma), ("DMA1:stream2:channel83", "DMA1:stream3:channel84")) self.assertEqual((spi.options.max_hz, spi.options.irq.priority, spi.options.irq.calls_os), (1000000, 5, False)) endpoint, = ir.devices - self.assertEqual((endpoint["controller"], endpoint["driver"], - endpoint["cs_binding"], endpoint["mode"]), + self.assertEqual((endpoint.controller, endpoint.driver, + endpoint.options.cs_binding, endpoint.options.mode), ("bus4", "spi-endpoint", "spi4-cs", 0)) if mode: uart = controllers["console"] self.assertEqual((uart.kind, uart.controller, uart.mode), ("uart", "USART3", mode)) self.assertEqual([(pin["pin"], pin["function"], pin["af"]) - for pin in uart["pins"]], + for pin in uart.pins], [("PD8", "tx", 7), ("PD9", "rx", 7)]) - self.assertEqual(tuple(uart["dma"]), + self.assertEqual(tuple(uart.dma), ("DMA1:stream0:channel46",) if mode == "dma-tx" else ("DMA1:stream1:channel45",)) diff --git a/tools/configure/test_memory_regions.py b/tools/configure/test_memory_regions.py index 94683f105..5d6538cb6 100644 --- a/tools/configure/test_memory_regions.py +++ b/tools/configure/test_memory_regions.py @@ -39,7 +39,7 @@ def test_copied_budget_projection_needs_no_provider_tree(self): script = ( "import json, runpy, sys\n" "module = runpy.run_path(sys.argv[1])\n" - "budget = module['resource_budget'](json.loads(sys.argv[2]))\n" + "budget = module['resource_budget_projection'](json.loads(sys.argv[2]))\n" "print(json.dumps(budget))\n" ) result = subprocess.run( @@ -58,16 +58,16 @@ def test_copied_dma_renderer_keeps_exact_reachability_errors(self): shutil.copyfile(Path(memory.__file__), copied) script = textwrap.dedent("""\ import json, runpy, sys - emit = runpy.run_path(sys.argv[1])["dma_regions"] + emit = runpy.run_path(sys.argv[1])["dma_regions_from_facts"] facts = {"memory": [{"origin": 0x20000000, "size": 1024, "dma_engines": {"DMA1": True, "BDMA": False}}]} - rendered, count = emit("port", facts, engine="DMA1") - unreachable = emit("port", facts, engine="BDMA") + rendered, count = emit("port", facts["memory"], engine="DMA1") + unreachable = emit("port", facts["memory"], engine="BDMA") errors = [] for options in ({}, {"engine": "unknown"}, - {"engine": "DMA1", "include_flash": True}): + {"engine": "DMA1", "flash": {}}): try: - emit("port", facts, **options) + emit("port", facts["memory"], **options) except ValueError as error: errors.append(str(error)) else: @@ -98,7 +98,7 @@ def test_extra_domains_have_separate_immutable_limits_and_physical_facts(self): self.assertEqual(selected, {"addsram": 128, "tcm": 64}) with self.assertRaises(FrozenInstanceError): ir.memory_budget.regions[0].limit_bytes = 256 - domains = {item["id"]: item for item in ir["memory"]} + domains = {item["id"]: item for item in ir.memory} self.assertTrue(domains["addsram"]["linker"]) self.assertTrue(domains["tcm"]["linker"]) self.assertFalse(domains["tcm"]["dma"]) @@ -135,7 +135,7 @@ def test_default_has_no_selected_extra_budget_and_rejects_live_sections(self): self.assertIn("set(NEXUS_MEMORY_TCM 0)", cmake) ir = gate.resolve_ir(path) self.assertEqual(ir.memory_budget.regions, ()) - self.assertEqual([item["id"] for item in ir["memory"] if item["linker"]], + self.assertEqual([item["id"] for item in ir.memory if item["linker"]], ["sram"]) def test_extra_selection_cannot_replace_the_authoritative_primary_ram(self): @@ -174,7 +174,7 @@ def test_f407_ccm_has_explicit_budget_and_remains_cpu_only(self): output = self.root / board gate.configure(path, output) ir = gate.resolve_ir(path) - domains = {item["id"]: item for item in ir["memory"]} + domains = {item["id"]: item for item in ir.memory} self.assertTrue(domains["ccm"]["linker"]) self.assertFalse(domains["ccm"]["dma"]) declarations, count = emission.dma_regions("selected", ir, diff --git a/tools/configure/test_metadata_binding.py b/tools/configure/test_metadata_binding.py index cd32bd1f6..4189b1dd0 100644 --- a/tools/configure/test_metadata_binding.py +++ b/tools/configure/test_metadata_binding.py @@ -45,7 +45,7 @@ def test_owner_observer_is_an_explicit_optional_component(self): self.assembly.write_text(self.assembly.read_text().replace( '[memory]', 'components=["owner-observer"]\n[memory]')) result = gate.resolve_ir(self.assembly, self.root) - self.assertEqual(result["components"], ("owner-observer",)) + self.assertEqual(result.components, ("owner-observer",)) def test_assembly_header_and_input_closure_bind_policy(self): output = self.root / "platform" @@ -60,7 +60,7 @@ def test_assembly_header_and_input_closure_bind_policy(self): self.assertEqual(factory.FACTORY_ABI_EPOCH, 1) def test_assembly_policy_change_invalidates_configuration_identity(self): - before = gate.resolve_ir(self.assembly, self.root)["configuration_sha256"] + before = gate.resolve_ir(self.assembly, self.root).configuration_sha256 self.change_policy(semantic_version=self.other_version) result = gate.configure(self.assembly, self.root / "changed", self.root) self.assertNotEqual(before, result["configuration_sha256"]) diff --git a/tools/configure/test_mpu_gpio_policy.py b/tools/configure/test_mpu_gpio_policy.py index 8d2c46072..959aecc9a 100644 --- a/tools/configure/test_mpu_gpio_policy.py +++ b/tools/configure/test_mpu_gpio_policy.py @@ -60,9 +60,9 @@ def test_requires_protected_single_world_running_tick_reference(self): def test_real_normal_ir_contains_exact_explicit_reservations(self): result = self.resolve(self.assembly()) - self.assertEqual(dict(result["memory_budgets"]["mpu"]), SIZES) + self.assertEqual({key: getattr(result.memory_budget.mpu, key) for key in SIZES}, SIZES) self.assertEqual(result.kernel.user_io, "gpio-write") - script = memory.linker(result.to_dict()) + script = memory.linker(result) for name in ("privileged_flash", "privileged_ram", "user_flash", "user_ram", "syscall_flash"): self.assertIn("__nexus_" + name + "_start__", script) @@ -78,7 +78,7 @@ def test_explicit_underfill_leaves_physical_tails_unmapped(self): result = self.resolve(text) self.assertEqual(result.memory_budget.mpu.user_flash_bytes, 4096) self.assertEqual(result.memory_budget.mpu.user_ram_bytes, 8192) - script = memory.linker(result.to_dict()) + script = memory.linker(result) self.assertIn('USER_FLASH (rx) : ORIGIN = 0x08020100, LENGTH = 4096', script) self.assertIn('USER_RAM (rw) : ORIGIN = 0x20010000, LENGTH = 8192', @@ -131,8 +131,8 @@ def test_cpu_only_runtime_cannot_claim_the_physical_gateway_profile(self): with tempfile.TemporaryDirectory() as value: directory = Path(value) (directory / "cpu.json").write_text(json.dumps({ - "schema_version": 1, "cpu": dict(resolved["cpu"]), - "irq": {key: resolved["irq"][key] + "schema_version": 1, "cpu": dict(resolved.cpu), + "irq": {key: getattr(resolved.irq, key) for key in ("priority_bits", "external_count")}, "clock_hz": 4000000})) assembly = directory / "runtime.toml" diff --git a/tools/configure/test_mpu_memory.py b/tools/configure/test_mpu_memory.py index 6c27bc10b..499c4b5c4 100644 --- a/tools/configure/test_mpu_memory.py +++ b/tools/configure/test_mpu_memory.py @@ -1,30 +1,29 @@ """Protected U575 memory emits exact domains for the real reset startup.""" +from dataclasses import replace import re import unittest -from tools.configure import memory +from tools.configure import configure, memory +from tools.configure.ir import MemoryBudgetIR, MpuMemoryReservationsIR, freeze def resolved(protected=True): - budgets = {"main_stack_bytes": 2048, "libc_heap_bytes": 0} - if protected: - budgets["mpu"] = { - "privileged_flash_bytes": 131072, - "syscall_flash_bytes": 256, - "user_flash_bytes": 2097152 - 131072 - 256, - "privileged_ram_bytes": 163840, - "user_ram_bytes": 32768, - } - return { - "soc_family": "stm32u575", "controllers": [], - "layout": {"origin": 0x08000000, "size": 2097152}, - "memory": [{"id": "sram1", "origin": 0x20000000, - "size": 196608, "linker": True}, - {"id": "sram4", "origin": 0x28000000, - "size": 16384, "linker": False}], - "memory_budgets": budgets, "configuration_sha256": "12" * 32, - } + result = configure.resolve_ir(configure.ROOT / + "tools/configure/assemblies/nucleo-u575-baremetal-empty.toml") + reservations = MpuMemoryReservationsIR( + privileged_flash_bytes=131072, syscall_flash_bytes=256, + user_flash_bytes=2097152 - 131072 - 256, + privileged_ram_bytes=163840, user_ram_bytes=32768) if protected else None + return replace(result, + layout=freeze({"origin": 0x08000000, "size": 2097152}), + physical_part=replace(result.physical_part, memory=freeze([ + {"id": "sram1", "origin": 0x20000000, + "size": 196608, "linker": True}, + {"id": "sram4", "origin": 0x28000000, + "size": 16384, "linker": False}])), + memory_budget=MemoryBudgetIR(2048, 0, None, None, mpu=reservations), + configuration_sha256="12" * 32) class MpuMemoryLinkerTests(unittest.TestCase): diff --git a/tools/configure/test_part_facts.py b/tools/configure/test_part_facts.py index c54d14d5c..21d7c6db2 100644 --- a/tools/configure/test_part_facts.py +++ b/tools/configure/test_part_facts.py @@ -1,8 +1,10 @@ """Exact part policy and emitted physical geometry share validated facts.""" import copy +from dataclasses import replace import unittest from tools.configure import bindings, configure as gate +from tools.configure.ir import freeze from tools.configure.providers import descriptor @@ -10,14 +12,20 @@ class PartFactsTests(unittest.TestCase): def test_ethernet_memory_window_uses_the_selected_physical_domain(self): for fixture in ("stm32-zg", "gd32"): with self.subTest(fixture=fixture): - result = gate.resolve(gate.ROOT / + result = gate.resolve_ir(gate.ROOT / f"tests/contracts/ethernet_assembly/{fixture}.toml") - memory = next(region for region in result["memory"] - if region["id"] == "sram") - memory["size"] = 65536 + regions = tuple(freeze({**region, "size": 65536}) + if region["id"] == "sram" else region + for region in result.physical_part.memory) + result = replace(result, physical_part=replace( + result.physical_part, memory=regions)) source = bindings.emit(result)[1] self.assertTrue("{ 0x20000000u, 65536u," in source) - memory["dma"] = False + regions = tuple(freeze({**region, "dma": False}) + if region["id"] == "sram" else region + for region in result.physical_part.memory) + result = replace(result, physical_part=replace( + result.physical_part, memory=regions)) with self.assertRaisesRegex(ValueError, "Ethernet.*memory"): bindings.emit(result) @@ -32,7 +40,7 @@ def test_fixed_flash_exports_are_defined_from_selected_facts(self): ) for family, fixture, size, count, unit in fixtures: with self.subTest(family=family): - result = gate.resolve(gate.ROOT / "tests/contracts" / fixture) + result = gate.resolve_ir(gate.ROOT / "tests/contracts" / fixture) source = bindings.emit(result)[1] symbol = "g_nx_" + family + "_flash_geometry" self.assertTrue(f"const nx_flash_geometry_t {symbol} = {{" @@ -58,9 +66,10 @@ def test_package_identity_cannot_drift_from_exact_part(self): gate.validate_soc(facts, "STM32F407VET6") def test_unknown_f407_part_cannot_fall_through_to_zg_geometry(self): - result = gate.resolve(gate.ROOT / + result = gate.resolve_ir(gate.ROOT / "tests/contracts/stm32_assembly/ve-flash.toml") - result["part"] = "UNREVIEWED_PART" + result = replace(result, physical_part=replace( + result.physical_part, part="UNREVIEWED_PART")) with self.assertRaisesRegex(ValueError, "part"): bindings.emit(result) @@ -68,7 +77,7 @@ def test_f407_emits_selected_geometry_from_physical_facts(self): for density, size, count in (("ve", 524288, 8), ("zg", 1048576, 12)): with self.subTest(density=density): - result = gate.resolve(gate.ROOT / + result = gate.resolve_ir(gate.ROOT / f"tests/contracts/stm32_assembly/{density}-flash.toml") source = bindings.emit(result)[1] self.assertIn("static const nx_flash_sector_t " @@ -77,7 +86,7 @@ def test_f407_emits_selected_geometry_from_physical_facts(self): self.assertIn(f".size = {size}u", source) self.assertIn(f".sector_count = {count}u", source) offset = 0 - for block in result["flash"]["erase_blocks"]: + for block in result.flash["erase_blocks"]: self.assertIn(f"{{ {offset}u, {block}u }}", source) offset += block self.assertEqual(offset, size) diff --git a/tools/configure/test_physical_facts.py b/tools/configure/test_physical_facts.py index 741027dc4..09a099f4c 100644 --- a/tools/configure/test_physical_facts.py +++ b/tools/configure/test_physical_facts.py @@ -4,22 +4,22 @@ class PhysicalFactBoundaryTests(unittest.TestCase): def test_mixed_dma_reachability_requires_an_explicit_engine(self): - from tools.configure import emission + from tools.configure import memory as emission result = {"memory": [{"id": "dtcm", "origin": 0x20000000, "size": 131072, "linker": True, "dma_engines": {"DMA1": False, "MDMA": True}}]} with self.assertRaisesRegex(ValueError, "engine"): - emission.dma_regions("port", result) - lines, count = emission.dma_regions("port", result, engine="MDMA") + emission.dma_regions_from_facts("port", result["memory"]) + lines, count = emission.dma_regions_from_facts("port", result["memory"], engine="MDMA") self.assertEqual(count, 1) self.assertIn("0x20000000", "\n".join(lines)) - _, count = emission.dma_regions("port", result, engine="DMA1") + _, count = emission.dma_regions_from_facts("port", result["memory"], engine="DMA1") self.assertEqual(count, 0) with self.assertRaises(ValueError): - emission.dma_regions("port", result, engine="nearest-DMA") + emission.dma_regions_from_facts("port", result["memory"], engine="nearest-DMA") def test_engine_ram_facts_do_not_authorize_flash_read_access(self): - from tools.configure import emission + from tools.configure import memory as emission result = {"memory": [{"id": "sram4", "origin": 0x38000000, "size": 65536, "dma_engines": {"BDMA": True, "MDMA": True}}], @@ -27,12 +27,12 @@ def test_engine_ram_facts_do_not_authorize_flash_read_access(self): for engine in ("BDMA", "MDMA"): with self.subTest(engine=engine): with self.assertRaisesRegex(ValueError, "Flash.*reachability"): - emission.dma_regions("port", result, include_flash=True, + emission.dma_regions_from_facts("port", result["memory"], flash=result["flash"], engine=engine) # Existing shared-reachability families keep their reviewed Flash path. result["memory"][0] = {"origin": 0x20000000, "size": 131072, "dma": True} - lines, count = emission.dma_regions("port", result, include_flash=True) + lines, count = emission.dma_regions_from_facts("port", result["memory"], flash=result["flash"]) self.assertEqual(count, 2) self.assertIn("0x08000000", "\n".join(lines)) diff --git a/tools/configure/test_pin_states.py b/tools/configure/test_pin_states.py index ebb10c896..626a37cbc 100644 --- a/tools/configure/test_pin_states.py +++ b/tools/configure/test_pin_states.py @@ -6,6 +6,7 @@ import tempfile import unittest +from tools.configure.tests.assembly_fixture import render_toml from tools.configure import matrix, bindings, configure, dependencies, factory @@ -17,21 +18,21 @@ class PinStateConfigurationTests(unittest.TestCase): def resolve(self, change=None): if change is None: return configure.resolve_ir(configure.ROOT / CANONICAL) - data = configure.load_assembly(configure.ROOT / CANONICAL) + data = configure.load_assembly(configure.ROOT / CANONICAL).to_dict() board = json.loads((configure.ROOT / BOARD).read_text()) change(data, board) with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / "board.json").write_text(json.dumps(board)) data["board_package"] = str(root) - path = root / "assembly.json" - path.write_text(json.dumps(data)) + path = root / "assembly.toml" + path.write_text(render_toml(data)) return configure.resolve_ir(path) def test_exact_canonical_options_are_immutable_register_values(self): result = self.resolve() - self.assertEqual((result.soc_family, result["part"], result.backend, - result["clock_profile"]), + self.assertEqual((result.soc_family, result.part, result.backend, + result.clock_profile), ("stm32g474", "STM32G474RET6", "baremetal", "hse24-pll170")) item, = result.controllers @@ -47,7 +48,7 @@ def test_exact_canonical_options_are_immutable_register_values(self): self.assertEqual(item.options.sleep.mode, 3072) with self.assertRaises(FrozenInstanceError): item.options.mask = 1 - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_face_is_selected_only_and_does_not_compete_with_gpio(self): selected = self.resolve() @@ -150,7 +151,8 @@ def change(data, board): board["bindings"].append({"id": "other", "controller": "GPIOA", "route": "gpio-pa0", "initial": 0}) data["controllers"].append({"id": "other", "binding": "other", - "mode": "output"}) + "mode": "output", + "authored_kind": "gpio"}) result = self.resolve(change) self.assertEqual({item.id for item in result.controllers}, {"status", "other"}) diff --git a/tools/configure/test_providers.py b/tools/configure/test_providers.py index 0d8a57def..60ef0e3f0 100644 --- a/tools/configure/test_providers.py +++ b/tools/configure/test_providers.py @@ -1,6 +1,6 @@ """Maintained provider boundaries and immutable mode decisions.""" from tools.configure.providers.descriptor import source_directory -from dataclasses import FrozenInstanceError +from dataclasses import FrozenInstanceError, replace import json from pathlib import Path import subprocess @@ -136,9 +136,9 @@ def test_unknown_family_cannot_inherit_cortex_m4_abi(self): gate.validate_soc(soc, "STM32F407ZGT6") def test_unknown_emitter_cannot_fall_back_to_native(self): - result = gate.resolve(gate.ROOT / + result = gate.resolve_ir(gate.ROOT / "tools/configure/assemblies/native-empty.toml") - result["soc_family"] = "unmaintained" + result = replace(result, soc_family="unmaintained") with self.assertRaisesRegex(ValueError, "Unmaintained SoC provider"): bindings.emit(result) @@ -169,16 +169,16 @@ def test_uart_decision_is_named_and_immutable(self): ir = gate.resolve_ir(gate.ROOT / "tools/configure/assemblies/native-uart.toml") uart, = ir.controllers - self.assertEqual(uart.options.baud, uart["baud"]) - self.assertEqual(uart.options.rx_profile, uart["rx_profile"]) - self.assertEqual(uart.options.rx_capacity, uart["rx_capacity"]) - self.assertEqual(uart.options.irq.priority, uart["irq_priority"]) + self.assertEqual(uart.options.baud, uart.to_dict()["baud"]) + self.assertEqual(uart.options.rx_profile, uart.to_dict()["rx_profile"]) + self.assertEqual(uart.options.rx_capacity, uart.to_dict()["rx_capacity"]) + self.assertEqual(uart.options.irq.priority, uart.to_dict()["irq_priority"]) with self.assertRaises(FrozenInstanceError): uart.options.baud = 9600 with self.assertRaises(FrozenInstanceError): uart.options.irq.priority = 0 - self.assertEqual(ir.to_dict(), gate.resolve(gate.ROOT / - "tools/configure/assemblies/native-uart.toml")) + self.assertEqual(ir.to_dict(), gate.resolve_ir(gate.ROOT / + "tools/configure/assemblies/native-uart.toml").to_dict()) def test_adc_and_pwm_decisions_preserve_json_projection(self): for path, kind in (("stm32_assembly/ve-adc-scan.toml", "adc"), @@ -189,18 +189,18 @@ def test_adc_and_pwm_decisions_preserve_json_projection(self): item, = (item for item in ir.controllers if item.kind == kind) if kind == "adc": self.assertEqual(item.options.channels, - tuple(item["channels"])) + tuple(item.to_dict()["channels"])) self.assertEqual(item.options.sample_times, - tuple(item["sample_times"])) + tuple(item.to_dict()["sample_times"])) else: - self.assertEqual(item.options.tick_hz, item["tick_hz"]) + self.assertEqual(item.options.tick_hz, item.to_dict()["tick_hz"]) with self.assertRaises(FrozenInstanceError): if kind == "adc": item.options.reference_mv = 0 else: item.options.period_ticks = 1 - self.assertEqual(ir.to_dict(), gate.resolve( - gate.ROOT / "tests/contracts" / path)) + self.assertEqual(ir.to_dict(), gate.resolve_ir( + gate.ROOT / "tests/contracts" / path).to_dict()) def test_rejected_family_invalidates_previous_bundle(self): with tempfile.TemporaryDirectory() as directory: diff --git a/tools/configure/test_rtc_lptim.py b/tools/configure/test_rtc_lptim.py index 1591ae270..626d9e995 100644 --- a/tools/configure/test_rtc_lptim.py +++ b/tools/configure/test_rtc_lptim.py @@ -38,27 +38,30 @@ def test_pair_uses_existing_immutable_controller_ir_and_claims(self): self.assertEqual(type(records["calendar_a"].options).__name__, "NoOptions") with self.assertRaises(FrozenInstanceError): records["counter_a"].options.tick_hz = 7 - claims = [entry for entry in result["claims"] - if entry["owner"] in records] - self.assertEqual({entry["key"] for entry in claims}, + claims = [entry for entry in result.claims + if entry.owner in records] + self.assertEqual({entry.key for entry in claims}, {"controller:RTC0", "controller:RTC1", "controller:LPTIM0", "controller:LPTIM1"}) for item in result.controllers: - self.assertEqual(tuple(item["pins"]), ()) - self.assertNotIn("irq_priority", item) - self.assertNotIn("dma", item) + self.assertEqual(item.pins, ()) + self.assertIsNone(getattr(item.options, "irq", None)) + self.assertEqual(item.dma, ()) - def test_existing_lifecycle_emits_construct_or_explicit_reopen(self): + def test_lifecycle_delegates_prepare_and_stop_to_each_selected_time_provider(self): result = self.resolve() _, source = bindings.emit(result) self.assertEqual(source.count("static const nx_clock_t s_nx_native_time_clock"), 1) self.assertIn("nx_time_now_us()", source) for kind, name in [("rtc", "calendar_a"), ("rtc", "calendar_b"), ("lptim", "counter_a"), ("lptim", "counter_b")]: - self.assertIn("s_nx_port_" + name + ".constructed", source) - self.assertIn("nx_native_" + kind + "_start(&s_nx_port_" + name, source) - self.assertIn("nx_native_" + kind + "_construct(&s_nx_port_" + name, source) + self.assertIn("nx_native_" + kind + "_prepare(&s_nx_port_" + name + + ", &s_nx_native_time_clock", source) self.assertIn("nx_" + kind + "_port_stop(&s_nx_face_" + name, source) + self.assertIn("&s_nx_port_counter_a, &s_nx_native_time_clock, 32768u)", + source) + self.assertIn("&s_nx_port_counter_b, &s_nx_native_time_clock, 1000u)", + source) self.assertNotIn("memset(", source) self.assertNotIn("NativeSystem", source) self.assertNotIn("registry", source) diff --git a/tools/configure/test_runtime_power.py b/tools/configure/test_runtime_power.py index 824fa367d..c79d64dd6 100644 --- a/tools/configure/test_runtime_power.py +++ b/tools/configure/test_runtime_power.py @@ -1,7 +1,6 @@ """Exact selected lifecycle lease, with System time retained during I/O pause.""" from dataclasses import FrozenInstanceError from pathlib import Path -import json import tempfile import unittest @@ -34,8 +33,9 @@ def test_exact_selection_resolves_one_immutable_lifecycle_domain(self): self.assertEqual(result.runtime_power.controllers, ('bus4',)) with self.assertRaises(FrozenInstanceError): result.runtime_power.mode = 'stop' - self.assertEqual(result['runtime_power']['controllers'], ('bus4',)) - self.assertFalse(result['physical_qualified']) + self.assertEqual(result.to_dict()['runtime_power']['controllers'], + ['bus4']) + self.assertFalse(result.physical_qualified) def test_selected_generated_api_uses_existing_clock_domain(self): header, source = bindings.emit(self.selected()) @@ -52,7 +52,7 @@ def test_selected_generated_api_uses_existing_clock_domain(self): def test_unselected_default_keeps_no_runtime_state_or_public_api(self): result = gate.resolve_ir(self.assembly('')) header, source = bindings.emit(result) - self.assertNotIn('runtime_power', result) + self.assertIsNone(result.runtime_power) for name in ('nx_platform_runtime_', 'nx_clock_domain_', 'nx_clock_lease_t'): self.assertNotIn(name, header + source) @@ -89,13 +89,11 @@ def test_unenrolled_additional_controller_is_rejected(self): with self.assertRaises(ValueError): gate.resolve_ir(self.assembly(base=self.base + additional)) - def test_explicit_null_power_is_not_an_absent_selection(self): - normalized = gate.load_assembly(self.assembly(), gate.ROOT, {}) - normalized['power'] = None - path = self.directory / 'null-power.json' - path.write_text(json.dumps(normalized)) - with self.assertRaises(ValueError): - gate.resolve_ir(path) + def test_explicit_non_table_power_is_not_an_absent_selection(self): + for value in ("[]", "true", '"fixed-run"'): + with self.subTest(value=value), self.assertRaises(ValueError): + gate.resolve_ir(self.assembly("", base= + "power = " + value + "\n" + self.base)) def test_failure_never_publishes_partial_runtime_bundle(self): output = self.directory / 'out' diff --git a/tools/configure/test_secondary_controllers.py b/tools/configure/test_secondary_controllers.py index 9f9f205c5..f988be86a 100644 --- a/tools/configure/test_secondary_controllers.py +++ b/tools/configure/test_secondary_controllers.py @@ -31,12 +31,12 @@ def test_three_parts_bind_five_independent_exact_controllers(self): for label in ("stm32-zg", "stm32-ve", "gd32"): with self.subTest(label=label): result = gate.resolve_ir(self.assembly(label)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) self.assertEqual(len(result.controllers), 5) expected = {"USART0", "USART1", "SPI4", "SPI0", "I2C1"} if label == "gd32" else { "USART1", "USART2", "SPI1", "SPI2", "I2C2"} self.assertEqual({item.controller for item in result.controllers}, expected) - claims = {item["key"] for item in result["claims"]} + claims = {item.key for item in result.claims} self.assertIn("dma:DMA0:stream6" if label == "gd32" else "dma:DMA1:stream6", claims) self.assertIn("pin:PB10", claims) self.assertIn("pin:PB11", claims) @@ -80,7 +80,7 @@ def test_maintained_constructor_model_uses_the_formal_typed_assembly(self): cwd=gate.ROOT, capture_output=True, text=True, check=False) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) source = output.read_text() - self.assertIn("prepare_spi0", source) + self.assertIn("nx_stm32_spi_config_t s_nx_spi_config_spi0", source) self.assertNotIn("s_nx_pair_clock_added", source) def test_two_i2c_controllers_share_only_readonly_algorithms_and_keep_local_addresses(self): @@ -90,7 +90,7 @@ def test_two_i2c_controllers_share_only_readonly_algorithms_and_keep_local_addre path = self.assembly(label, "-i2c-pair", lambda text: text.replace("dma-messages", mode)) result = gate.resolve_ir(path) self.assertEqual(len(result.controllers), 2) - self.assertEqual([item["address"] for item in result.devices], [80, 80]) + self.assertEqual([item.options.address for item in result.devices], [80, 80]) output = self.directory / (label + mode) gate.configure(path, output) source = (output / "bindings.c").read_text() @@ -98,7 +98,7 @@ def test_two_i2c_controllers_share_only_readonly_algorithms_and_keep_local_addre self.assertIn("s_nx_port_primary_i2c", source) self.assertIn("s_nx_port_secondary_i2c", source) self.assertIn("I2C1_EV_IRQHandler" if label == "gd32" else "I2C2_EV_IRQHandler", source) - self.assertEqual(any(item["key"].startswith("dma:") for item in result["claims"]), mode == "dma-messages") + self.assertEqual(any(item.key.startswith("dma:") for item in result.claims), mode == "dma-messages") def test_selected_factory_has_separate_faces_endpoints_and_exact_irq_contexts(self): for label in ("stm32-zg", "gd32"): @@ -146,7 +146,7 @@ def test_both_authored_backends_resolve_the_same_physical_controller_routes(self rtos = gate.resolve_ir(self.assembly(label, "-freertos")) self.assertEqual([(item.controller, item.mode) for item in bare.controllers], [(item.controller, item.mode) for item in rtos.controllers]) - self.assertEqual(rtos["irq"]["kernel_port"], "GCC/ARM_CM4F") + self.assertEqual(rtos.irq.kernel_port, "GCC/ARM_CM4F") self.assertTrue(all(not item.options.irq.calls_os for item in rtos.controllers)) diff --git a/tools/configure/test_selected_contract_authority.py b/tools/configure/test_selected_contract_authority.py index 9a3828d24..057215171 100644 --- a/tools/configure/test_selected_contract_authority.py +++ b/tools/configure/test_selected_contract_authority.py @@ -28,14 +28,14 @@ def test_seven_canonical_inputs_keep_selected_mechanisms(self): root = "cmake/package/verification/io_evolution/" choices = ( ("native-time-pair", {"rtc", "lptim"}, - "nx_native_lptim_construct("), + "nx_native_lptim_prepare("), ("native-time-empty", set(), "nx_platform_start_impl("), ("h743-high-empty", set(), "nx_stm32h743_pll384_system_start("), ("h743-high-uart", {"gpio", "uart"}, "nx_stm32h743_pll384_system_start("), ("u575-uart-gpdma", {"uart"}, - "nx_stm32u575_uart_gpdma_initialize("), + "nx_stm32u575_uart_gpdma_prepare("), ("g474-can-fd-loopback", {"can_fd"}, ".mode = NX_STM32G474_FDCAN_INTERNAL_LOOPBACK"), ("g474-can-fd-normal", {"can_fd"}, @@ -62,7 +62,7 @@ def test_seven_canonical_inputs_keep_selected_mechanisms(self): self.assertEqual({row.public_class for row in records}, expected) self.assertIn(mechanism, bindings.emit(result)[1]) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_canonical_h743_high_flash_keeps_exact_public_interfaces(self): result, records = self.selected( @@ -72,7 +72,7 @@ def test_canonical_h743_high_flash_keeps_exact_public_interfaces(self): "i2c_device", "adc", "flash"}) self.assertIn("nx_stm32h743_pll384_system_start(", bindings.emit(result)[1]) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_catalogue_has_eight_exact_parts_without_family_inference(self): rows = matrix.support_catalogue(configure.ROOT) @@ -333,7 +333,9 @@ def test_factory_metadata_requires_an_implemented_provider_mode(self): # A known public shape alone cannot authorize this actual provider. item = ControllerIR.create({ "id": "capture", "binding": "capture", "kind": "timer_capture", + "pins": (), "route": "capability-rejection-fixture", "controller": "TIM5", "mode": "edge-capture", + "mask": 0, "initial": 0, "tick_hz": 1000000, "edge": "rising", "filter": 0, "exclusive": True, "irq_priority": 5}, "stm32f103") with self.assertRaisesRegex(ValueError, "Unmaintained provider mode"): @@ -342,6 +344,7 @@ def test_factory_metadata_requires_an_implemented_provider_mode(self): def test_factory_model_flag_cannot_disagree_with_its_actual_provider(self): item = ControllerIR.create({ "id": "pin", "binding": "pin", "kind": "gpio", + "pins": (), "route": "capability-rejection-fixture", "controller": "GPIO0", "mode": "input", "mask": 1, "initial": 0}, "native") with self.assertRaisesRegex(ValueError, "model.*provider"): @@ -350,6 +353,7 @@ def test_factory_model_flag_cannot_disagree_with_its_actual_provider(self): def test_capability_class_cannot_invent_a_face_for_another_kind(self): item = ControllerIR.create({ "id": "pin", "binding": "pin", "kind": "gpio", + "pins": (), "route": "capability-rejection-fixture", "controller": "GPIO0", "mode": "input", "mask": 1, "initial": 0}, "native") with self.assertRaisesRegex(ValueError, "class.*selected"): diff --git a/tools/configure/test_selected_io_plans.py b/tools/configure/test_selected_io_plans.py index e48e7d027..aa8c21a0e 100644 --- a/tools/configure/test_selected_io_plans.py +++ b/tools/configure/test_selected_io_plans.py @@ -12,7 +12,7 @@ def test_resource_plan_is_the_immutable_selected_claim_projection(self): result = configure.resolve_ir( configure.ROOT / "tools/configure/assemblies/native-gpio.toml") self.assertEqual(len(result.resource_plan.claims), - len(result["claims"])) + len(result.claims)) self.assertEqual(result.resource_plan.to_dict(), result.to_dict()["resource_plan"]) with self.assertRaises(FrozenInstanceError): @@ -28,7 +28,7 @@ def test_fixed_adc_trigger_and_dma_are_resolved_not_invented(self): result.to_dict()["trigger_plan"]) self.assertEqual(result.dma_topology.to_dict(), result.to_dict()["dma_topology"]) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_empty_selection_has_explicit_empty_plans(self): result = configure.resolve_ir( diff --git a/tools/configure/test_soc_facts.py b/tools/configure/test_soc_facts.py index 25e4fd5a3..056473464 100644 --- a/tools/configure/test_soc_facts.py +++ b/tools/configure/test_soc_facts.py @@ -151,13 +151,13 @@ def lock(self): def test_import_has_a_typed_dependency_and_same_locked_input_digest(self): result = self.resolve() digest = hashlib.sha256(self.lock_path.read_bytes()).hexdigest() - entry = next((item for item in result["source_dependencies"] + entry = next((item for item in result.source_dependencies if item["path"] == "vendors/gigadevice/gd32f4xx"), None) self.assertIsNotNone(entry, "Imported SDK must retain source identity") self.assertEqual(entry["kind"], "vendor-source-import") self.assertEqual(entry["lock_sha256"], digest) self.assertEqual(entry["version"], "3.3.3") - inputs = {item["path"]: item["sha256"] for item in result["inputs"]} + inputs = {item["path"]: item["sha256"] for item in result.inputs} self.assertEqual(inputs["vendors/gigadevice/gd32f4xx/source.lock.json"], digest) @@ -190,9 +190,9 @@ def test_lock_metadata_change_changes_configuration_identity(self): lock["version"] = "3.3.3-reviewed-fixture" self.lock_path.write_text(json.dumps(lock)) after = self.resolve() - self.assertNotEqual(before["configuration_sha256"], - after["configuration_sha256"]) - imports = [item for item in after["source_dependencies"] + self.assertNotEqual(before.configuration_sha256, + after.configuration_sha256) + imports = [item for item in after.source_dependencies if item.get("kind") == "vendor-source-import"] self.assertEqual(len(imports), 1, "Changed lock must retain its import") self.assertEqual(imports[0]["version"], "3.3.3-reviewed-fixture") diff --git a/tools/configure/test_static_calls.py b/tools/configure/test_static_calls.py new file mode 100644 index 000000000..22b0d2f36 --- /dev/null +++ b/tools/configure/test_static_calls.py @@ -0,0 +1,90 @@ +"""Lifecycle call plans reject executable fragments before C rendering.""" +from dataclasses import FrozenInstanceError +import unittest + +from tools.configure.calls import Address, DirectCall, Symbol +from tools.configure.providers.common import Construction + + +class StaticCallTests(unittest.TestCase): + def test_direct_typed_arguments_preserve_selected_call(self): + call = DirectCall("nx_prepare", (Address("s_port.tx"), + Symbol("s_storage"), 64, True, False, None)) + self.assertEqual(call.render(), + "nx_prepare(&s_port.tx, s_storage, 64u, true, false, NULL)") + + def test_absolute_deadline_uses_explicit_nested_direct_calls(self): + deadline = DirectCall("nx_deadline_after", ( + DirectCall("nx_time_now_us"), 100000)) + call = DirectCall("nx_initialize", (Address("s_port"), deadline)) + self.assertEqual(call.render(), + "nx_initialize(&s_port, nx_deadline_after(nx_time_now_us(), 100000u))") + + def test_callee_cannot_be_pointer_expression_or_statement(self): + for value in ("p->initialize", "(*callback)", "good(); evil", "", + "namespace.start", "func\n", 12, None): + with self.subTest(value=value), self.assertRaises(ValueError): + DirectCall(value) + + def test_arguments_cannot_hide_arbitrary_c_source(self): + for value in ("&s_port", "0u", "NULL", "true", "a ? b : c", + "(type){0}", {"symbol": "s_port"}, -1, 2**32, 1.5): + with self.subTest(value=value), self.assertRaises(ValueError): + DirectCall("nx_prepare", (value,)) + + def test_addresses_are_static_object_paths(self): + for value in ("&s_port", "p->child", "ports[0]", "*port", "port+1", + "port..child", "port;", "", None): + with self.subTest(value=value), self.assertRaises(ValueError): + Address(value) + for value in ("p.member", "A | B", "1", "", None): + with self.subTest(value=value), self.assertRaises(ValueError): + Symbol(value) + + def test_plan_captures_argument_sequence_and_is_immutable(self): + arguments = [Address("s_port"), 7] + call = DirectCall("nx_prepare", arguments) + arguments.clear() + self.assertEqual(call.render(), "nx_prepare(&s_port, 7u)") + with self.assertRaises(FrozenInstanceError): + call.function = "foreign" + + def test_argument_containers_cannot_discard_or_reorder_values(self): + for values in ("", b"", {}, set(), {1, 2}, {1: 2}, iter([1, 2])): + with self.subTest(values=values), self.assertRaises(ValueError): + DirectCall("nx_prepare", values) + + def test_c_keywords_cannot_be_declared_call_or_reference_names(self): + for value in ("return", "int", "_Atomic"): + for constructor in (DirectCall, Address, Symbol): + with self.subTest(value=value, constructor=constructor): + with self.assertRaises(ValueError): + constructor(value) + with self.assertRaises(ValueError): + Address("state.if") + + def test_call_source_is_in_configuration_and_sdk_input_closure(self): + from tools.configure.dependencies import source_names + for mode in ("platform", "runtime"): + self.assertIn("tools/configure/calls.py", + source_names(mode, "stm32f407", sdk=True)) + + def test_construction_rejects_legacy_initializer_and_stop_fragments(self): + for initialize, stop in ( + ("nx_prepare()", ()), + (DirectCall("nx_prepare"), ("nx_stop()",)), + (DirectCall("nx_prepare"), ("ready ? nx_stop() : NX_SUCCESS",))): + with self.subTest(initialize=initialize, stop=stop): + with self.assertRaisesRegex(ValueError, "direct call"): + Construction("nx_state_t", (), initialize, stop, False) + + def test_construction_stop_order_is_an_explicit_sequence(self): + for values in ({DirectCall("nx_stop")}, {}, iter(()), "", None): + with self.subTest(values=values): + with self.assertRaisesRegex(ValueError, "ordered"): + Construction("nx_state_t", (), DirectCall("nx_prepare"), + values, False) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/configure/test_stream_modes.py b/tools/configure/test_stream_modes.py index 854083cc6..c037aeb7a 100644 --- a/tools/configure/test_stream_modes.py +++ b/tools/configure/test_stream_modes.py @@ -49,7 +49,7 @@ def test_continuous_keeps_reviewed_dma_timer_and_immutable_sample_codes(self): self.assertEqual(adc.options.sample_times, (0, 7)) with self.assertRaises(FrozenInstanceError): adc.options.sample_times = (1, 1) - claims = {item["key"] for item in ir["claims"]} + claims = {item.key for item in ir.claims} self.assertIn("controller:" + timer, claims) self.assertIn("dma:" + dma, claims) output = self.root / family @@ -127,9 +127,9 @@ def test_each_receive_mode_claims_only_its_reviewed_directional_engines(self): duplex = mode.startswith("dma-duplex") expected = ((f"{dma}:stream7:channel4",) if duplex else ()) expected += (f"{dma}:stream2:channel4",) - self.assertEqual(uart["dma"], expected) + self.assertEqual(uart.dma, expected) self.assertIsNone(uart.options.rx_capacity) - claims = {item["key"] for item in ir["claims"]} + claims = {item.key for item in ir.claims} self.assertIn(f"dma:{dma}:stream2", claims) self.assertEqual(f"dma:{dma}:stream7" in claims, duplex) output = self.root / (family + "-" + mode) diff --git a/tools/configure/test_threadx.py b/tools/configure/test_threadx.py index fc8d23e21..e31a38dac 100644 --- a/tools/configure/test_threadx.py +++ b/tools/configure/test_threadx.py @@ -60,8 +60,8 @@ def test_default_m4_policy_uses_real_static_port_facts(self): self.assertEqual(policy.tick_reload, 167999) self.assertEqual(policy.interrupt_mask, "basepri") self.assertEqual(policy.syscall_priority, 5) - self.assertNotIn("idle_stack_words", policy) - self.assertNotIn("memory_protection", policy) + self.assertFalse(hasattr(policy, "idle_stack_words")) + self.assertFalse(hasattr(policy, "memory_protection")) def test_native_clock_is_explicit_host_monotonic_not_fake_systick(self): policy = self.resolve(arch="native", hz=1) @@ -136,12 +136,10 @@ def test_policy_and_macros_are_immutable(self): with self.assertRaises(TypeError): policy.definitions["NEXUS_OS_TICK_HZ"] = 1 - def test_roundtrip_dispatch_retains_threadx_type_and_identity(self): + def test_policy_keeps_backend_specific_identity(self): policy = self.resolve() - restored = kernel.from_validated(policy.to_dict()) - self.assertEqual(restored, policy) - self.assertEqual(restored.backend, "threadx") - self.assertNotIn("idle_stack_words", restored) + self.assertEqual(policy.backend, "threadx") + self.assertFalse(hasattr(policy, "idle_stack_words")) def test_generated_policy_has_exact_threadx_contracts(self): policy = self.resolve({"syscall_priority": 6}) diff --git a/tools/configure/test_timer_advanced.py b/tools/configure/test_timer_advanced.py index e6cff6f9a..a050e1b20 100644 --- a/tools/configure/test_timer_advanced.py +++ b/tools/configure/test_timer_advanced.py @@ -27,14 +27,14 @@ def test_three_exact_parts_bind_two_exclusive_controllers_and_shared_break_vecto for label in ("stm32-zg", "stm32-ve", "gd32"): with self.subTest(label=label): result = gate.resolve_ir(self.assembly(label)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) self.assertEqual(len(result.controllers), 2) capture = next(item for item in result.controllers if item.kind == "timer_capture") self.assertEqual(capture.options.edge, "both") self.assertEqual(capture.options.filter, 3) with self.assertRaises(FrozenInstanceError): capture.options.tick_hz = 2 - claims = {item["key"] for item in result["claims"]} + claims = {item.key for item in result.claims} self.assertIn("pin:PB6", claims) self.assertIn("pin:PB12", claims) self.assertIn("pin:PB13", claims) diff --git a/tools/configure/test_toml.py b/tools/configure/test_toml.py index 55d06ef9d..12c6c7614 100644 --- a/tools/configure/test_toml.py +++ b/tools/configure/test_toml.py @@ -1,4 +1,5 @@ """Authored configuration and typed factory behavioral contracts.""" +from tools.configure.tests.assembly_fixture import render_toml from tools.configure.providers.descriptor import source_directory from contextlib import redirect_stdout from dataclasses import FrozenInstanceError @@ -59,7 +60,7 @@ def test_minimal_board_id_resolves_to_typed_ir(self): self.assertEqual(ir.board, "native-reference") self.assertEqual(ir.memory_budget.main_stack_bytes, 2048) self.assertEqual(ir.controllers, ()) - self.assertFalse(ir["physical_qualified"]) + self.assertFalse(ir.physical_qualified) with self.assertRaises(FrozenInstanceError): ir.board = "other" @@ -79,8 +80,8 @@ def test_nested_uart_rx_and_irq_contract(self): uart, = ir.controllers self.assertEqual((uart.id, uart.kind, uart.controller), ("link0", "uart", "UART0")) - self.assertEqual(uart["mode"], "irq-byte-event") - self.assertEqual(uart["rx_capacity"], 64) + self.assertEqual(uart.mode, "irq-byte-event") + self.assertEqual(uart.options.rx_capacity, 64) self.assertEqual(uart.provider, "native") def test_authored_kind_must_match_board_binding(self): @@ -179,7 +180,7 @@ def test_native_register_model_storage_is_exact_and_per_endpoint(self): controller="bus" model_bytes=128 """) - self.assertEqual([device["model_bytes"] for device in ir.devices], + self.assertEqual([device.options.model_bytes for device in ir.devices], [64, 128]) output = self.root / "bundle" gate.configure(self.assembly, output, self.root) @@ -234,8 +235,8 @@ def test_reviewed_uart_dma_emits_internal_resource_and_irq_wiring(self): 'mode = "dma-tx"')) ir = gate.resolve_ir(self.assembly, gate.ROOT) uart = next(item for item in ir.controllers if item.kind == "uart") - self.assertEqual(uart["dma"], ("DMA2:stream7:channel4",)) - self.assertIn("dma:DMA2:stream7", {item["key"] for item in ir["claims"]}) + self.assertEqual(uart.dma, ("DMA2:stream7:channel4",)) + self.assertIn("dma:DMA2:stream7", {item.key for item in ir.claims}) output = self.root / "bundle" gate.configure(self.assembly, output, gate.ROOT) generated = (output / "bindings.c").read_text() @@ -276,8 +277,8 @@ def test_gd32_multi_controller_emits_explicit_at_and_vector_bindings(self): self.assertIn("void USART0_IRQHandler(void)", generated) self.assertIn("void USART1_IRQHandler(void)", generated) self.assertIn("nx_gd32_uart_irq(&s_nx_port_link_b)", generated) - self.assertIn(".cs_gpio = GPIOB", generated) - self.assertIn(".cs_mask = 1u", generated) + self.assertIn("{ GPIOB, &s_nx_spi_pin_state_bus_spi_b[3u], " + "1u, 0u, 0u }", generated) self.assertIn("nx_gd32_pin_restore", generated) def test_stm32_spi_dma_uses_exact_streams_and_endpoint_provider(self): @@ -291,11 +292,12 @@ def test_stm32_spi_dma_uses_exact_streams_and_endpoint_provider(self): gate.configure(self.assembly, output) generated = (output / "bindings.c").read_text() for text in ("nx_stm32_spi_dma_state_t", "nx_stm32_spi_dma_endpoint_state_t", - "nx_stm32_spi_dma_initialize", "&nx_stm32_spi_dma_endpoint_ops", + "nx_stm32_spi_dma_prepare", "&nx_stm32_spi_dma_endpoint_ops", + "nx_stm32_spi_dma_close", "void DMA2_Stream3_IRQHandler(void)", "void DMA2_Stream0_IRQHandler(void)", "nx_spi_port_stop(nx_binding_spi0)"): self.assertIn(text, generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertTrue({"dma:DMA2:stream3", "dma:DMA2:stream0"} <= claims) def test_gd32_uart_dma_uses_vendor_channel_vector_and_shared_clock(self): @@ -311,7 +313,7 @@ def test_gd32_uart_dma_uses_vendor_channel_vector_and_shared_clock(self): "nx_gd32_uart_dma_uart_irq(&s_nx_port_link_a)", "RCU_AHB1EN_DMA1EN"): self.assertIn(text, generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertIn("irq:DMA1_Channel7", claims) self.assertNotIn("irq:DMA1_Stream7", claims) @@ -327,11 +329,11 @@ def test_gd32_spi_dma_and_uart_claim_disjoint_physical_engines(self): gate.configure(self.assembly, output) generated = (output / "bindings.c").read_text() for text in ("nx_gd32_spi_dma_state_t", "nx_gd32_spi_dma_endpoint_state_t", - "&nx_gd32_spi_dma_endpoint_ops", "nx_gd32_spi_dma_initialize", + "&nx_gd32_spi_dma_endpoint_ops", "nx_gd32_spi_dma_prepare", "void DMA1_Channel3_IRQHandler(void)", "void DMA1_Channel4_IRQHandler(void)", - "stop_bus_spi_a()", "nx_spi_port_stop(&s_nx_face_bus_spi_a)"): + "nx_gd32_spi_dma_close", "nx_spi_port_stop(nx_binding_bus_spi_a)"): self.assertIn(text, generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertTrue({"dma:DMA1:stream3", "dma:DMA1:stream4", "dma:DMA1:stream7"} <= claims) def test_uart_blocks_have_caller_storage_and_mode_specific_ops(self): @@ -348,7 +350,7 @@ def test_uart_blocks_have_caller_storage_and_mode_specific_ops(self): "nx_stm32_uart_stream_irq(&s_nx_port_uart1)"): self.assertIn(text, generated) self.assertNotIn("s_nx_rx_uart1[", generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertNotIn("dma:DMA2:stream7", claims) with self.assertRaisesRegex(gate.ConfigurationError, "capacity"): self.assembly.write_text(self.assembly.read_text().replace('format = "blocks"', 'format = "blocks"\ncapacity = 8')) @@ -375,7 +377,7 @@ def test_adc_trigger_dma_reserves_exact_timer_and_dma_without_block_storage(self "nx_stm32_adc_stream_initialize", "DMA2_Stream4_IRQHandler", "nx_adc_port_stream_stop(&s_nx_face_adc0)"): self.assertIn(text, generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertTrue({"controller:TIM3", "dma:DMA2:stream4"} <= claims) self.assembly.write_text(self.assembly.read_text() + '\n[pwm.pwm0]\nbinding="pwm0"\nmode="fixed-pwm"\nperiod_ticks=1000\nduty_ticks=100\ntick_hz=1000000\n') @@ -397,7 +399,7 @@ def test_gd32_adc_trigger_dma_exact_sample_contract_and_timer_claim(self): "nx_gd32_adc_stream_initialize", "void DMA1_Channel0_IRQHandler(void)", "nx_gd32_adc_stream_stop(&s_nx_port_adc0)"): self.assertIn(text, generated) - claims = {claim["key"] for claim in gate.resolve_ir(self.assembly)["claims"]} + claims = {claim.key for claim in gate.resolve_ir(self.assembly).claims} self.assertTrue({"controller:TIMER2", "dma:DMA1:stream0"} <= claims) self.assembly.write_text(contents.replace('sample_times=[1]', 'sample_times=[7]')) with self.assertRaisesRegex(gate.ConfigurationError, "fifteen cycles"): @@ -434,13 +436,13 @@ def test_fixed_dma_providers_reject_other_valid_physical_routes(self): with self.subTest(provider=(family, controller)): package = self.copy_soc_package(family) source = gate.ROOT / "tests/contracts" / fixture - normalized = gate.load_assembly(source) + normalized = gate.load_assembly(source).to_dict() normalized["board_package"] = str(source.parent / normalized["board_package"]) - assembly = self.root / "canonical.json" - assembly.write_text(json.dumps(normalized)) + assembly = self.root / "canonical.toml" + assembly.write_text(render_toml(normalized)) ir = gate.resolve_ir(assembly, self.root) - selected_route = next(item["route"] for item in ir.controllers - if item["controller"] == controller) + selected_route = next(item.route for item in ir.controllers + if item.controller == controller) routes = json.loads((package / "routes.json").read_text()) route = next(route for route in routes["routes"] if route["id"] == selected_route) route["dma"] = [f"{dma.split(':')[0]}:stream{6-index}:{dma.rsplit(':', 1)[1]}" @@ -459,7 +461,7 @@ def test_timer_reservation_alias_excludes_adc_and_pwm(self): with self.subTest(family=family, kind=kind): filename = ((stem + "-") if stem else "") + kind + ".toml" source = gate.ROOT / "tests/contracts" / family / filename - normalized = gate.load_assembly(source) + normalized = gate.load_assembly(source).to_dict() original_board = source.parent / normalized["board_package"] board = json.loads((original_board / "board.json").read_text()) board["reserved_resources"].append("timer:" + timer) @@ -467,8 +469,8 @@ def test_timer_reservation_alias_excludes_adc_and_pwm(self): board_package.mkdir(exist_ok=True) (board_package / "board.json").write_text(json.dumps(board)) normalized["board_package"] = str(board_package) - assembly = self.root / "timer.json" - assembly.write_text(json.dumps(normalized)) + assembly = self.root / "timer.toml" + assembly.write_text(render_toml(normalized)) with self.assertRaisesRegex(gate.ConfigurationError, "Resource conflict controller:" + timer): gate.resolve_ir(assembly) @@ -481,14 +483,14 @@ def test_fixed_adc_trigger_cannot_omit_its_physical_timer_claim(self): with self.subTest(family=family): package = self.copy_soc_package(family) source = gate.ROOT / "tests/contracts" / fixture - normalized = gate.load_assembly(source) + normalized = gate.load_assembly(source).to_dict() normalized["board_package"] = str(source.parent / normalized["board_package"]) - assembly = self.root / "trigger.json" - assembly.write_text(json.dumps(normalized)) + assembly = self.root / "trigger.toml" + assembly.write_text(render_toml(normalized)) ir = gate.resolve_ir(assembly, self.root) - selected = next(item for item in ir.controllers if item["kind"] == "adc") + selected = next(item for item in ir.controllers if item.kind == "adc") routes = json.loads((package / "routes.json").read_text()) - route = next(route for route in routes["routes"] if route["id"] == selected["route"]) + route = next(route for route in routes["routes"] if route["id"] == selected.route) route["mode_resources"] = {} (package / "routes.json").write_text(json.dumps(routes)) with self.assertRaisesRegex(gate.ConfigurationError, "Trigger resource differs from fixed provider"): @@ -496,16 +498,16 @@ def test_fixed_adc_trigger_cannot_omit_its_physical_timer_claim(self): def test_gd32_timebase_is_reserved_by_soc_for_every_board(self): source = gate.ROOT / "tools/configure/assemblies/liangshan-baremetal-empty.toml" - normalized = gate.load_assembly(source) + normalized = gate.load_assembly(source).to_dict() board = json.loads((Path(normalized["board_package"]) / "board.json").read_text()) board["reserved_resources"] = [] package = self.root / "timebase_board" package.mkdir() (package / "board.json").write_text(json.dumps(board)) normalized["board_package"] = str(package) - assembly = self.root / "timebase.json" - assembly.write_text(json.dumps(normalized)) - claims = {claim["key"]: claim["owner"] for claim in gate.resolve_ir(assembly)["claims"]} + assembly = self.root / "timebase.toml" + assembly.write_text(render_toml(normalized)) + claims = {claim.key: claim.owner for claim in gate.resolve_ir(assembly).claims} self.assertEqual(claims.get("controller:TIMER1"), "soc-timebase") self.assertEqual(claims.get("irq:TIMER1"), "soc-timebase") board["reserved_resources"] = ["TIMER1", "timer:TIMER1", "irq:TIMER1_IRQn"] @@ -514,11 +516,11 @@ def test_gd32_timebase_is_reserved_by_soc_for_every_board(self): def test_spi_child_frequency_below_provider_divider_is_rejected(self): source = gate.ROOT / "tests/contracts/gd32_assembly/spi.toml" - normalized = gate.load_assembly(source) + normalized = gate.load_assembly(source).to_dict() normalized["board_package"] = str(source.parent / normalized["board_package"]) normalized["devices"][0]["max_hz"] = 1000 - assembly = self.root / "canonical.json" - assembly.write_text(json.dumps(normalized)) + assembly = self.root / "canonical.toml" + assembly.write_text(render_toml(normalized)) with self.assertRaisesRegex(gate.ConfigurationError, "SPI endpoint speed"): gate.resolve_ir(assembly) @@ -538,4 +540,4 @@ def test_typed_ir_and_resolved_json_share_one_identity(self): emitted = json.loads((output / "resolved.json").read_text()) self.assertEqual(emitted, ir.to_dict()) self.assertEqual(emitted["configuration_sha256"], - ir["configuration_sha256"]) + ir.configuration_sha256) diff --git a/tools/configure/test_u575_mpu_geometry.py b/tools/configure/test_u575_mpu_geometry.py index 04acf665a..d305a089d 100644 --- a/tools/configure/test_u575_mpu_geometry.py +++ b/tools/configure/test_u575_mpu_geometry.py @@ -29,7 +29,7 @@ def test_both_backends_resolve_the_same_exact_geometry(self): result = configure.resolve_ir(self.fixture(backend)) self.assertEqual(result.cpu_profile.definitions[ "NEXUS_CPU_MPU_REGIONS"], 8) - self.assertEqual(result["cpu"]["mpu_regions"], 8) + self.assertEqual(result.cpu["mpu_regions"], 8) def test_generated_header_and_cmake_share_exact_eight(self): for backend in ("baremetal", "freertos"): @@ -56,7 +56,7 @@ def test_unreviewed_sixteen_cannot_override_exact_part_fact(self): def test_protected_kernel_keeps_exact_cpu_geometry(self): result = configure.resolve_ir(self.fixture("freertos")) protected = kernel.resolve({"memory_protection": True}, - result.cpu_profile, result["clock"]["core_hz"]) + result.cpu_profile, result.clock["core_hz"]) self.assertTrue(protected.memory_protection) self.assertEqual(result.cpu_profile.definitions[ "NEXUS_CPU_MPU_REGIONS"], 8) diff --git a/tools/configure/test_u575_sram4_memory.py b/tools/configure/test_u575_sram4_memory.py index 9d06c729d..13e661754 100644 --- a/tools/configure/test_u575_sram4_memory.py +++ b/tools/configure/test_u575_sram4_memory.py @@ -60,7 +60,7 @@ def test_selected_sram4_has_exact_linker_symbols_and_independent_budget(self): (output / "nexus_config.h").read_text()) self.assertIn("set(NEXUS_MEMORY_SRAM4 1)", (output / "selection.cmake").read_text()) - declarations, count = emission.dma_regions("copy", result, + declarations, count = emission.dma_regions("copy", gate.resolve_ir(self.root / "assembly.toml"), engine="LPDMA1") self.assertEqual(count, 1) self.assertIn("0x28000000u, 16384u", "\n".join(declarations)) diff --git a/tools/configure/test_uart_format_configuration.py b/tools/configure/test_uart_format_configuration.py index f2a9712de..3caf38365 100644 --- a/tools/configure/test_uart_format_configuration.py +++ b/tools/configure/test_uart_format_configuration.py @@ -1,4 +1,5 @@ """Authored UART frames and word interfaces keep their real public types.""" +from dataclasses import replace from pathlib import Path import re import tempfile @@ -163,9 +164,9 @@ def test_word_format_requires_the_reviewed_9n1_selection(self): def test_word_capabilities_use_word_units_and_nominal_traits_ids(self): frame = 'data_bits=9\nparity="none"\nstop_bits=1\nflow="none"' result = self.resolve("native-uart.toml", frame, words=True) - data = result.to_dict() - data["factory"] = {"capabilities": True} - header, source = factory.emit(data) + selected = replace(result, factory=replace(result.factory, + capabilities=True)) + header, source = factory.emit(selected) self.assertIn("nx_factory_uart_word_traits_t", header) self.assertIn("nx_factory_uart_word_capability", header) self.assertIn("NX_UART_WORD_ID_UART0", source) diff --git a/tools/configure/test_uart_rtscts_configuration.py b/tools/configure/test_uart_rtscts_configuration.py index 75fd0aeaa..90e9ce464 100644 --- a/tools/configure/test_uart_rtscts_configuration.py +++ b/tools/configure/test_uart_rtscts_configuration.py @@ -76,7 +76,7 @@ def test_normal_inputs_derive_one_frozen_8n1_flow_byte_personality(self): with self.assertRaises(FrozenInstanceError): frame.flow = "none" self.assertEqual(item.options.rx_capacity, 64) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_explicit_complete_frame_matches_the_mode_derived_frame(self): for family in PADS: @@ -171,12 +171,12 @@ def test_four_pads_are_exclusive_claims_with_no_fabricated_pcb_revision(self): for family, pads in PADS.items(): with self.subTest(family=family): result = self.normal(family) - selected = [claim for claim in result["claims"] - if claim["owner"] == "console"] + selected = [claim for claim in result.claims + if claim.owner == "console"] self.assertTrue({"pin:" + pin for pin in pads}.issubset( - {claim["key"] for claim in selected})) + {claim.key for claim in selected})) self.assertEqual(tuple(pin["function"] for pin in - result.controllers[0]["pins"]), + result.controllers[0].pins), ("tx", "rx", "cts", "rts")) package = configure.ROOT / "boards" / ( "stm32" + family + "zi_rtscts_reference") @@ -198,8 +198,8 @@ def test_only_the_two_exact_controllers_and_ordered_four_af7_pads_pass(self): result = self.normal(family) item, = result.controllers provider = maintained("stm32" + family) - route = {key: item[key] for key in - ("controller", "kind", "pins")} + route = {"controller": item.controller, "kind": item.kind, + "pins": item.pins} selection = {"mode": MODE, "baud": 115200, "rx_profile": "bytes", "irq_priority": 5} provider.validate_selection(selection, route) diff --git a/tools/configure/test_usb_device.py b/tools/configure/test_usb_device.py index 7f1d127c6..101d4deac 100644 --- a/tools/configure/test_usb_device.py +++ b/tools/configure/test_usb_device.py @@ -30,14 +30,14 @@ def test_three_parts_resolve_exact_software_only_fs_irq_and_no_dma_claim(self): for family in ("stm32-zg", "stm32-ve", "gd32"): with self.subTest(family=family): ir = gate.resolve_ir(self.assembly(family)) - self.assertFalse(ir["physical_qualified"]) + self.assertFalse(ir.physical_qualified) self.assertEqual(len(ir.controllers), 1) self.assertEqual(len(ir.devices), 4) self.assertEqual(ir.controllers[0].options.rx_fifo_words, 96) self.assertFalse(ir.controllers[0].options.vbus_sensing) with self.assertRaises(FrozenInstanceError): ir.controllers[0].options.rx_fifo_words = 100 - claims = {item["key"] for item in ir["claims"]} + claims = {item.key for item in ir.claims} self.assertIn("pin:PA11", claims) self.assertIn("pin:PA12", claims) self.assertIn("irq:USBFS" if family == "gd32" else "irq:OTG_FS", claims) @@ -70,10 +70,10 @@ def test_interrupt_in_preserves_exact_typed_plan_and_existing_bulk_ports(self): options.fifo_words), (2, "in", "interrupt", 10, 160, 16)) self.assertEqual(len(ir.devices), 5) - self.assertFalse(ir["physical_qualified"]) + self.assertFalse(ir.physical_qualified) with self.assertRaises(FrozenInstanceError): options.transfer = "bulk" - claims = {item["key"] for item in ir["claims"]} + claims = {item.key for item in ir.claims} controller = "USBFS" if family == "gd32" else "OTG_FS" self.assertIn("usb:" + controller + ":endpoint:2:in", claims) self.assertFalse(any(item.startswith("dma:") for item in claims)) diff --git a/tools/configure/tests/assembly_fixture.py b/tools/configure/tests/assembly_fixture.py new file mode 100644 index 000000000..70c143146 --- /dev/null +++ b/tools/configure/tests/assembly_fixture.py @@ -0,0 +1,88 @@ +"""Write mutable test intentions through the sole production TOML boundary. + +This helper is not part of the configuration input closure or installed SDK. +Kinds are explicit test facts, never inferred from a chip or binding name. +Unknown and invalid values are retained for production rejection tests. +""" +import json + + +def _value(value): + if isinstance(value, str): + return json.dumps(value, ensure_ascii=False) + if type(value) is bool: + return "true" if value else "false" + if type(value) in (int, float): + return str(value) + if isinstance(value, (list, tuple)): + return "[" + ", ".join(_value(item) for item in value) + "]" + if isinstance(value, dict): + return "{ " + ", ".join( + json.dumps(key) + " = " + _value(item) + for key, item in value.items()) + " }" + raise TypeError(f"TOML fixture cannot represent {type(value).__name__}") + + +def render_toml(document, *, kinds=None): + """Project detached test intentions to schema-2 source without validation.""" + kinds = {} if kinds is None else kinds + renamed = {"schema_version": "schema", "clock_profile": "clock", + "memory_budgets": "memory"} + memory_names = {"static_ram_bytes": "static_ram_limit_bytes", + "flash_load_bytes": "flash_load_limit_bytes"} + result = {"schema": 2} + for name, value in document.items(): + if name in {"controllers", "devices"} or ( + name == "layout" and value is None): + continue + if name == "schema_version" and type(value) is int and value == 1: + value = 2 + if name == "memory_budgets" and isinstance(value, dict): + value = {memory_names.get(key, key): item + for key, item in value.items()} + result[renamed.get(name, name)] = value + lines = [json.dumps(name) + " = " + _value(value) + for name, value in result.items()] + controller_kinds = {} + for original in document.get("controllers", ()): + item = dict(original) + identity = item.pop("id") + kind = item.pop("authored_kind", None) + if kind is None: + kind = kinds[item["binding"]] + controller_kinds[identity] = kind + if kind == "uart": + rx = {} + for source, target in (("rx_profile", "format"), + ("rx_capacity", "capacity")): + if source in item: + rx[target] = item.pop(source) + item["rx"] = rx + irq = {} + for source, target in (("irq_priority", "priority"), + ("calls_os", "calls_os"), + ("irq_probe", "probe")): + if source in item: + irq[target] = item.pop(source) + if irq or kind == "uart": + item["irq"] = irq + lines.extend(("", "[" + json.dumps(kind) + "." + + json.dumps(identity) + "]")) + lines.extend(json.dumps(name) + " = " + _value(value) + for name, value in item.items()) + for original in document.get("devices", ()): + item = dict(original) + identity = item.pop("id") + kind = item.pop("authored_kind", None) + if kind is None: + kind = controller_kinds[item["controller"]] + if kind == "usb_device": + section = "usb_endpoint" + item.pop("driver", None) + else: + section = kind + "_device" + lines.extend(("", "[" + json.dumps(section) + "." + + json.dumps(identity) + "]")) + lines.extend(json.dumps(name) + " = " + _value(value) + for name, value in item.items()) + return "\n".join(lines) + "\n" diff --git a/tools/configure/tests/providers/gd32f303/test_provider.py b/tools/configure/tests/providers/gd32f303/test_provider.py index 75cb1d0e5..dc7c94832 100644 --- a/tools/configure/tests/providers/gd32f303/test_provider.py +++ b/tools/configure/tests/providers/gd32f303/test_provider.py @@ -1,9 +1,12 @@ """Exact GD303 geometry, silicon routes and static construction boundaries.""" +from dataclasses import replace import importlib import json from pathlib import Path import unittest +from tools.configure import configure +from tools.configure.calls import Address, DirectCall from tools.configure.ir import ControllerIR from tools.configure.providers.common import ConfigurationError @@ -13,7 +16,8 @@ def uart(**changes): return {**{"id": "serial", "binding": "uart0", "kind": "uart", - "controller": "USART0", "mode": "irq-bytes", "baud": 115200, + "controller": "USART0", "route": "usart0-pa9-pa10", + "mask": 1536, "initial": 0, "mode": "irq-bytes", "baud": 115200, "rx_profile": "bytes", "rx_capacity": 64, "irq_priority": 5, "calls_os": False, "pins": [ {"pin": "PA9", "function": "tx", "af": 0}, @@ -22,18 +26,16 @@ def uart(**changes): def gpio(**changes): return {**{"id": "output", "binding": "gpio0", "kind": "gpio", - "controller": "GPIOA", "mode": "output", "mask": 32, "initial": 0, + "controller": "GPIOA", "route": "gpio-pa5", + "mode": "output", "mask": 32, "initial": 0, "pins": [{"pin": "PA5", "function": "output", "af": 0}]}, **changes} -class Result(dict): - def __init__(self, *items, backend="baremetal"): - super().__init__(irq={"external_count": 60, "priority_bits": 4, - "syscall_priority": 5 if backend == "freertos" else None}, - devices=[], board_bindings={}, routes={}, kernel_profile=( - {"kernel_policy": "basepri"} if backend == "freertos" else None)) - self.controllers = tuple(ControllerIR.create(item, "gd32f303") - for item in items) +def Result(*items, backend="baremetal"): + baseline = configure.resolve_ir(ROOT / "tools/configure/assemblies" / + f"gd32-f303-{backend}-empty.toml") + return replace(baseline, controllers=tuple( + ControllerIR.create(item, "gd32f303") for item in items)) class GD303ProviderTests(unittest.TestCase): @@ -105,7 +107,7 @@ def test_constructor_retained_guard_precedes_state_inputs(self): self.assertIn("nx_gd32f303_usart0_pins_prepare()", source) self.assertIn("8000000u", source) self.assertEqual(construction.stop, - ("nx_uart_port_stop(&s_nx_face_serial)",)) + (DirectCall("nx_uart_port_stop", (Address("s_nx_face_serial"),)),)) self.assertTrue(construction.retain_on_failed_init) def test_foreign_provider_and_mode_cannot_bypass_constructor(self): diff --git a/tools/configure/tests/providers/stm32f103/test_provider.py b/tools/configure/tests/providers/stm32f103/test_provider.py index ca1a90803..d979fffc3 100644 --- a/tools/configure/tests/providers/stm32f103/test_provider.py +++ b/tools/configure/tests/providers/stm32f103/test_provider.py @@ -2,11 +2,12 @@ import importlib import json from pathlib import Path +from types import SimpleNamespace import unittest import tempfile from tools.configure import bindings, configure -from tools.configure.ir import ControllerIR +from tools.configure.ir import ControllerIR, InterruptProfileIR from tools.configure.providers.common import ConfigurationError from tools.configure.selection import validate_clock_profiles @@ -15,7 +16,9 @@ def uart(**changes): return {**{"id": "console", "binding": "uart1", "kind": "uart", - "controller": "USART2", "mode": "irq-bytes", "baud": 115200, + "controller": "USART2", "route": "usart2-pa2-pa3", + "mode": "irq-bytes", "baud": 115200, + "mask": 0, "initial": 0, "rx_profile": "bytes", "rx_capacity": 64, "irq_priority": 5, "calls_os": False, "pins": [ {"pin": "PA2", "function": "tx", "af": 0}, @@ -24,19 +27,23 @@ def uart(**changes): def gpio(**changes): return {**{"id": "led", "binding": "led0", "kind": "gpio", - "controller": "GPIOA", "mode": "output", "mask": 32, + "controller": "GPIOA", "route": "gpio-pa5", + "mode": "output", "mask": 32, "initial": 0, "pins": [ {"pin": "PA5", "function": "output", "af": 0}]}, **changes} -class Result(dict): +class Result: def __init__(self, *items, backend="baremetal"): - super().__init__(irq={"external_count": 43, "priority_bits": 4, - "syscall_priority": 5 if backend == "freertos" - else None}, devices=[], board_bindings={}, - routes={}, kernel_profile=( - {"kernel_policy": "basepri"} - if backend == "freertos" else None)) + self.irq = InterruptProfileIR(4, 15, + 5 if backend == "freertos" else None, + "GCC/ARM_CM3" if backend == "freertos" else None, 43, + "basepri" if backend == "freertos" else "primask") + self.devices = () + self.board_bindings = {} + self.routes = {} + self.kernel = (SimpleNamespace(kernel_policy="basepri") + if backend == "freertos" else None) self.controllers = tuple(ControllerIR.create(item, "stm32f103") for item in items) @@ -122,7 +129,7 @@ def test_constructor_guard_precedes_state_mutation(self): self.assertIn("static uint8_t s_nx_rx_console[64];", source) self.assertIn("8000000u, 1000000u", source) self.assertTrue(construction.retain_on_failed_init) - self.assertEqual(construction.stop, + self.assertEqual(tuple(call.render() for call in construction.stop), ("nx_uart_port_stop(&s_nx_face_console)",)) def test_constructor_uses_explicit_kernel_irq_policy(self): @@ -185,17 +192,20 @@ def test_board_does_not_invent_hse_or_physical_inspection(self): def test_watchdog_constructor_cannot_forget_pending_lsi_debug_lease(self): item = {"id": "iwdg", "binding": "iwdg", "kind": "watchdog", - "controller": "IWDG", "mode": "independent", "pins": []} + "controller": "IWDG", "route": "iwdg", + "mode": "independent", "pins": [], "mask": 0, "initial": 0} ir = ControllerIR.create(item, "stm32f103") construction = self.provider.constructor(ir, Result(item), {}, {}, ()) source = "\n".join(construction.definitions) self.assertIn(".preparation_retained", source) - self.assertEqual(construction.stop, + self.assertEqual(tuple(call.render() for call in construction.stop), ("nx_stm32f103_watchdog_stop(&s_nx_port_iwdg)",)) def test_exti_raw_edge_capacity_and_exact_line_are_checked(self): item = {"id": "edge0", "binding": "exti0", "kind": "exti", - "controller": "EXTI0", "mode": "edge-event", "edge": "both", + "controller": "EXTI0", "route": "exti0-pa0", + "mode": "edge-event", "edge": "both", + "mask": 0, "initial": 0, "event_capacity": 3, "irq_priority": 5, "calls_os": False, "pins": [{"pin": "PA0", "function": "input", "af": 0}]} self.provider.validate_selection(item, item) @@ -213,7 +223,7 @@ def test_exti_raw_edge_capacity_and_exact_line_are_checked(self): def test_spi_endpoint_and_cs_are_static_and_one_owned_lease(self): path = ROOT / "tools/configure/assemblies/stm32-f103-spi.toml" - result = configure.resolve(path, ROOT) + result = configure.resolve_ir(path, ROOT) _, source = bindings.emit(result) self.assertIn("nx_stm32f103_spi_endpoint_ops", source) self.assertIn("s_nx_cs_peripheral", source) @@ -230,12 +240,12 @@ def test_spi_sck_and_led_pin_conflict_is_not_silently_repaired(self): assembly = Path(directory) / "conflict.toml" assembly.write_text(text) with self.assertRaisesRegex(ConfigurationError, "conflict"): - configure.resolve(assembly, ROOT) + configure.resolve_ir(assembly, ROOT) def test_maintained_assembly_resolves_static_typed_factory(self): assembly = ROOT / "tools/configure/assemblies/stm32-f103-baremetal.toml" - result = configure.resolve(assembly, ROOT) - self.assertEqual(result["soc_family"], "stm32f103") + result = configure.resolve_ir(assembly, ROOT) + self.assertEqual(result.soc_family, "stm32f103") _, source = bindings.emit(result) self.assertIn("nx_stm32f103_uart_ops", source) self.assertIn("s_nx_port_uart1", source) diff --git a/tools/configure/tests/providers/stm32g474/test_acquisition.py b/tools/configure/tests/providers/stm32g474/test_acquisition.py index ae0030f19..a578e3b65 100644 --- a/tools/configure/tests/providers/stm32g474/test_acquisition.py +++ b/tools/configure/tests/providers/stm32g474/test_acquisition.py @@ -38,7 +38,7 @@ def test_real_factory_has_exact_adc_flash_and_i2c_construction(self): self.assertIn("RCC_CCIPR_ADC12SEL_1", source) self.assertIn("NX_G474_FLASH_REMAP()", source) self.assertIn("RCC_APB2ENR_SYSCFGEN", source) - self.assertIn("irq:FLASH", {claim["key"] for claim in result["claims"]}) + self.assertIn("irq:FLASH", {claim.key for claim in result.resource_plan.claims}) self.assertIn("NX_G474_IRQ_AVAILABLE((IRQn_Type)FLASH_IRQn)", source) self.assertIn("FLASH_ECCR_ECCIE", source) self.assertNotIn("DMA1_Channel1_IRQHandler", source) @@ -56,7 +56,7 @@ def test_finite_dma_factory_has_one_normal_lane_and_no_ccm_domain(self): self.assertIn("98304u, NX_DMA_MEMORY_READ | NX_DMA_MEMORY_WRITE", source) self.assertNotIn("(uintptr_t)0x10000000u", source) self.assertIn("DMA1_Channel1_IRQHandler", source) - claims = {claim["key"] for claim in result["claims"]} + claims = {claim.key for claim in result.resource_plan.claims} self.assertIn("controller:TIM6", claims) self.assertIn("dma:DMA1:stream1", claims) diff --git a/tools/configure/tests/providers/stm32g474/test_mainstream.py b/tools/configure/tests/providers/stm32g474/test_mainstream.py index 472a761b4..c3294cc48 100644 --- a/tools/configure/tests/providers/stm32g474/test_mainstream.py +++ b/tools/configure/tests/providers/stm32g474/test_mainstream.py @@ -8,8 +8,7 @@ from tools.configure import bindings, configure from tools.configure.providers.common import ConfigurationError -from tools.configure.ir import ControllerIR -from .test_provider import Result, gpio, uart +from .test_provider import Result, fixture_controller, gpio, uart class G474MainstreamTests(unittest.TestCase): @@ -29,9 +28,9 @@ def test_general_uart_controllers_accept_exact_source_routes(self): {"pin": rx, "function": "rx", "af": af}]) with self.subTest(controller=controller): self.provider.validate_selection(values, values) - item = ControllerIR.create(values, "stm32g474") + item = fixture_controller(values) code = "\n".join(self.provider.constructor( - item, Result(values), {}, {}, ())["definitions"]) + item, Result(values), {}, {}, ()).definitions) self.assertIn(f".registers = {controller};", code) self.assertIn(f".irq = {controller}_IRQn;", code) self.assertIn(f"void {controller}_IRQHandler", code) @@ -43,9 +42,9 @@ def test_gpio_mask_must_equal_route_across_package_banks(self): {"pin": f"P{bank}{pin}", "function": "output", "af": 0}]) with self.subTest(bank=bank): self.provider.validate_selection(values, values) - item = ControllerIR.create(values, "stm32g474") + item = fixture_controller(values) code = "\n".join(self.provider.constructor( - item, Result(values), {}, {}, ())["definitions"]) + item, Result(values), {}, {}, ()).definitions) self.assertIn(f".registers = GPIO{bank};", code) def test_multibank_factory_has_per_controller_gates_and_pin_snapshots(self): @@ -135,14 +134,14 @@ def test_fixed_pwm_has_exact_independent_ch1_routes_and_static_storage(self): self.assertEqual("pwm", self.provider.mode_implementation( "pwm", "fixed-pwm")) self.provider.validate_selection(values, values) - item = ControllerIR.create(values, "stm32g474") + item = fixture_controller(values) construction = self.provider.constructor( item, Result(values), {"pwm": {"mask": 64, "initial": 0}}, {}, ()) - code = "\n".join(construction["definitions"]) + code = "\n".join(construction.definitions) self.assertIn(f".registers = {controller};", code) self.assertIn(f"RCC_APB1RSTR1_{controller}RST", code) self.assertIn("nx_stm32g474_pwm_initialize", code) - self.assertIn("nx_stm32g474_pwm_close", "\n".join(construction["stop"])) + self.assertIn("nx_stm32g474_pwm_close", "\n".join(call.render() for call in construction.stop)) def test_fixed_pwm_real_factory_has_both_gates_and_rejects_invalid_base(self): root = Path(__file__).resolve().parents[5] diff --git a/tools/configure/tests/providers/stm32g474/test_provider.py b/tools/configure/tests/providers/stm32g474/test_provider.py index 84b24b2e7..0fd93ceeb 100644 --- a/tools/configure/tests/providers/stm32g474/test_provider.py +++ b/tools/configure/tests/providers/stm32g474/test_provider.py @@ -1,5 +1,6 @@ """Fixed G474 capability, route and cold emitter boundary contracts.""" import importlib +from dataclasses import replace import json from pathlib import Path import unittest @@ -31,16 +32,20 @@ def gpio(**changes): **changes} -class Result(dict): - def __init__(self, *items, backend="baremetal"): - super().__init__(irq={"external_count": 102, "priority_bits": 4, - "syscall_priority": 5 if backend == "freertos" - else None}, devices=[], board_bindings={}, - routes={}, kernel_profile=( - {"kernel_policy": "basepri"} - if backend == "freertos" else None)) - self.controllers = tuple(ControllerIR.create(item, "stm32g474") - for item in items) +def fixture_controller(values, provider="stm32g474"): + """Provide explicit resolved route facts for isolated constructor tests.""" + return ControllerIR.create({"route": "fixture-route", "mask": 0, + "initial": 0, **values}, provider) + + +def Result(*items, backend="baremetal"): + """Use actual resolved profile facts with isolated constructor selections.""" + profile = ("mainstream-freertos.toml" if backend == "freertos" + else "empty.toml") + result = configure.resolve_ir(ROOT / "tests/contracts/g474_assembly" / + profile) + return replace(result, controllers=tuple( + fixture_controller(item) for item in items)) class G474ProviderTests(unittest.TestCase): @@ -152,15 +157,15 @@ def test_reference_board_is_explicit_about_physical_uncertainty(self): self.assertEqual(board["bindings"][0]["initial"], 0) def test_uart_ctor_is_guarded_before_overwriting_retained_state(self): - item = ControllerIR.create(uart(), "stm32g474") + item = fixture_controller(uart()) construction = self.provider.constructor(item, Result(uart()), {}, {}, ()) - source = "\n".join(construction["definitions"]) + source = "\n".join(construction.definitions) self.assertLess(source.index(".retained"), source.index(".registers =")) self.assertIn("NX_UART_RX_BYTES", source) self.assertIn("static uint8_t s_nx_rx_console[64];", source) self.assertIn("nx_stm32g474_lpuart1_pins_prepare()", source) self.assertIn("170000000u, 1000000u", source) - self.assertEqual(construction["stop"], + self.assertEqual(tuple(call.render() for call in construction.stop), ("nx_uart_port_stop(&s_nx_face_console)",)) self.assertIn("LPUART1_IRQHandler", source) self.assertNotIn("DMA", source) @@ -173,7 +178,7 @@ def test_constructor_projection_cannot_bypass_fixed_mode_validation(self): self.provider.constructor(uart(**changes), Result(), {}, {}, ()) def test_foreign_typed_provider_decision_cannot_be_reinterpreted(self): - item = ControllerIR.create(uart(), "stm32f407") + item = fixture_controller(uart(), "stm32f407") with self.assertRaisesRegex(ConfigurationError, "provider"): self.provider.constructor(item, Result(), {}, {}, ()) @@ -182,7 +187,7 @@ def test_uart_ctor_uses_selected_kernel_policy_without_hidden_os(self): ("freertos", "BASEPRI", 5)): result = Result(uart(), backend=backend) source = "\n".join(self.provider.constructor( - result.controllers[0], result, {}, {}, ())["definitions"]) + result.controllers[0], result, {}, {}, ()).definitions) self.assertIn(f"NX_IRQ_KERNEL_{policy}, 102u, 4u, {ceiling}u", source) self.assertNotIn("xTaskCreate", source) @@ -222,7 +227,7 @@ def test_gpio_mask_snapshot_precedes_any_pin_effect_and_has_readback(self): def test_gpios_and_uart_use_real_readonly_tables_without_forwarders(self): for values in (gpio(), uart()): - item = ControllerIR.create(values, "stm32g474") + item = fixture_controller(values) self.assertEqual(self.provider.operations(item), f"nx_stm32g474_{item.kind}_ops") self.assertEqual(self.provider.instance_declarations(item, Result()), []) diff --git a/tools/configure/tests/providers/stm32h743/test_peripherals.py b/tools/configure/tests/providers/stm32h743/test_peripherals.py index 078de734f..d510e7679 100644 --- a/tools/configure/tests/providers/stm32h743/test_peripherals.py +++ b/tools/configure/tests/providers/stm32h743/test_peripherals.py @@ -76,7 +76,7 @@ def test_sram1_requires_explicit_budget_and_extends_only_selected_dma(self): source = Path(directory) / "assembly.toml" source.write_text(original + '\n[memory.regions.sram1]\nlimit_bytes=256\n') ir = configure.resolve_ir(source) - domains = {item["id"]: item for item in ir["memory"]} + domains = {item["id"]: item for item in ir.memory} self.assertTrue(domains["sram1"]["linker"]) self.assertFalse(domains["sram2"]["linker"]) self.assertFalse(domains["dtcm"]["linker"]) diff --git a/tools/configure/tests/providers/stm32h743/test_spi_dma.py b/tools/configure/tests/providers/stm32h743/test_spi_dma.py index 9714d9d8a..d53677318 100644 --- a/tools/configure/tests/providers/stm32h743/test_spi_dma.py +++ b/tools/configure/tests/providers/stm32h743/test_spi_dma.py @@ -71,7 +71,7 @@ def test_immutable_ir_and_exact_shared_lane_selection(self): self.assertFalse(spi.options.irq.calls_os) with self.assertRaises(FrozenInstanceError): spi.options.max_hz = 1 - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_static_emission_uses_dedicated_faces_and_three_irqs(self): with tempfile.TemporaryDirectory() as directory: diff --git a/tools/configure/tests/providers/stm32h743/test_uart_continuous.py b/tools/configure/tests/providers/stm32h743/test_uart_continuous.py index 9ea016a4c..c7b4ff342 100644 --- a/tools/configure/tests/providers/stm32h743/test_uart_continuous.py +++ b/tools/configure/tests/providers/stm32h743/test_uart_continuous.py @@ -34,7 +34,7 @@ def test_selected_cache_policy_is_derived_without_an_author_field(self): self.assertEqual((1, "UINT32_MAX"), (record.buffer_alignment, record.max_transfer_units)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) with tempfile.TemporaryDirectory() as directory: path = self.source(directory, lambda text: text.replace( 'mode = "rx-dma-continuous"', diff --git a/tools/configure/tests/providers/stm32h743/test_uart_duplex.py b/tools/configure/tests/providers/stm32h743/test_uart_duplex.py index f06912a40..7e61fd574 100644 --- a/tools/configure/tests/providers/stm32h743/test_uart_duplex.py +++ b/tools/configure/tests/providers/stm32h743/test_uart_duplex.py @@ -31,14 +31,14 @@ def test_duplex_resolves_two_separate_lanes_and_finite_tx_limits(self): result = configure.resolve_ir(source) item = result.controllers[0] self.assertEqual(("DMA1:stream0:channel46", - "DMA1:stream1:channel45"), tuple(item["dma"])) + "DMA1:stream1:channel45"), item.dma) record = selected_contracts.describe(item) self.assertEqual("double_buffer", record.continuity) self.assertEqual((32, "65504") if cached else (1, "UINT16_MAX"), (record.buffer_alignment, record.max_transfer_units)) - self.assertFalse(result["physical_qualified"]) + self.assertFalse(result.physical_qualified) def test_generation_has_one_uart_owner_and_both_explicit_vectors(self): with tempfile.TemporaryDirectory() as directory: diff --git a/tools/configure/tests/providers/stm32u575/test_devices.py b/tools/configure/tests/providers/stm32u575/test_devices.py index ba0d5755a..441ab4ea3 100644 --- a/tools/configure/tests/providers/stm32u575/test_devices.py +++ b/tools/configure/tests/providers/stm32u575/test_devices.py @@ -19,7 +19,7 @@ def test_polling_completion_irq_and_independent_clock_are_real_claims(self): root = Path(__file__).resolve().parents[5] result = configure.resolve_ir( root / "tests/contracts/u575_assembly/peripherals.toml", root) - claims = {item["key"]: item["owner"] for item in result["claims"]} + claims = {item.key: item.owner for item in result.claims} self.assertEqual("storage", claims.get("irq:FLASH")) self.assertEqual("control", claims.get("clock:HSI16")) self.assertEqual("control", claims.get("clock-mux:I2C1")) @@ -34,8 +34,8 @@ def test_shared_i2c_operations_keep_their_actual_linked_identity(self): self.assertEqual(construction.state_type, "nx_i2c_v2_state_t") source = "\n".join(construction.definitions) self.assertIn("static const nx_i2c_v2_config_t", source) - self.assertIn("nx_stm32u575_i2c_prepare", construction.initialize) - self.assertIn("nx_stm32u575_i2c_close", construction.stop[0]) + self.assertIn("nx_stm32u575_i2c_prepare", construction.initialize.render()) + self.assertIn("nx_stm32u575_i2c_close", construction.stop[0].render()) def test_exact_selected_modes_and_pin_routes(self): items = [ diff --git a/tools/configure/tests/providers/stm32u575/test_lptim.py b/tools/configure/tests/providers/stm32u575/test_lptim.py index ecfe70335..5b9ad43df 100644 --- a/tools/configure/tests/providers/stm32u575/test_lptim.py +++ b/tools/configure/tests/providers/stm32u575/test_lptim.py @@ -47,7 +47,7 @@ def test_exact_irq_mode_resolves_and_constructs_static_public_face(self): (item.kind, item.controller, item.mode)) self.assertEqual(1000000, item.options.tick_hz) self.assertEqual(5, item.options.irq.priority) - self.assertEqual((), tuple(item['pins'])) + self.assertEqual((), item.pins) _, source = bindings.emit(result) self.assertIn('nx_stm32u575_lptim_initialize(', source) self.assertIn('nx_stm32u575_lptim_irq(', source) diff --git a/tools/configure/tests/providers/stm32u575/test_provider.py b/tools/configure/tests/providers/stm32u575/test_provider.py index e3564959b..a5b11bcc9 100644 --- a/tools/configure/tests/providers/stm32u575/test_provider.py +++ b/tools/configure/tests/providers/stm32u575/test_provider.py @@ -5,7 +5,8 @@ from pathlib import Path import unittest -from tools.configure.ir import ControllerIR +from tools.configure.ir import ControllerIR, InterruptProfileIR +from tools.configure.resource_graph import ResourcePlanIR from tools.configure.providers.common import ConfigurationError ROOT = Path(__file__).resolve().parents[5] @@ -25,12 +26,16 @@ def gpio(**changes): "pins": [{"pin": "PC7", "function": "output", "af": 0}]}, **changes} -class Result(dict): +class Result: def __init__(self, *items): - super().__init__(irq={"external_count": 126, "priority_bits": 4, - "syscall_priority": None}, devices=[]) - self.controllers = tuple(ControllerIR.create(item, "stm32u575") + self.controllers = tuple(ControllerIR.create( + {"route": item["binding"], "mask": 0, "initial": 0, **item}, "stm32u575") for item in items) + self.irq = InterruptProfileIR(4, 15, None, None, 126, "primask") + self.backend = "baremetal" + self.kernel = None + self.devices = () + self.resource_plan = ResourcePlanIR(()) class U575ProviderTests(unittest.TestCase): @@ -187,7 +192,7 @@ def test_generated_factory_uses_static_real_face_and_context(self): result = Result(gpio(), uart()) for item in result.controllers: construction = self.provider.constructor(item, result, {}, {}, ()) - source = "\n".join(construction["definitions"]) + source = "\n".join(construction.definitions) self.assertLess(source.index(".retained"), source.index(".registers =")) self.assertEqual(self.provider.operations(item), @@ -317,11 +322,11 @@ def test_authored_toml_projects_exact_two_vectors_and_pinned_ll_source(self): result = self.resolve() port = next(item for item in result.controllers if item.kind == "uart") self.assertEqual((port.controller, port.mode), ("USART1", "dma-tx")) - claims = {item["key"] for item in result["claims"]} + claims = {item.key for item in result.claims} self.assertTrue({"dma:GPDMA1:channel0", "irq:GPDMA1_Channel0", "irq:USART1"}.issubset(claims)) self.assertEqual(result.dma_topology.lanes[0].engine, "GPDMA1") - dependencies = [entry for entry in result["source_dependencies"] + dependencies = [entry for entry in result.source_dependencies if entry["path"] == "vendors/st/stm32u5_hal"] self.assertEqual(len(dependencies), 1) self.assertEqual(dependencies[0]["commit"], @@ -337,10 +342,11 @@ def test_generated_vectors_tables_and_cold_retention_select_real_provider(self): self.assertIn("GPDMA1_Channel0_IRQHandler", source) self.assertIn("nx_stm32u575_uart_gpdma_irq(&s_nx_port_console)", source) self.assertIn("nx_stm32u575_uart_gpdma_uart_irq(&s_nx_port_console)", source) - self.assertIn(".dma_irq_policy", source) - self.assertLess(source.index(".uart.retained"), - source.index(".uart.registers = USART1")) - self.assertIn("nx_stm32u575_uart_gpdma_initialize", source) + self.assertIn("static const nx_stm32u575_uart_gpdma_config_t", source) + self.assertIn("nx_stm32u575_uart_gpdma_prepare(&s_nx_port_console", source) + self.assertNotIn(".uart.registers =", source) + self.assertNotIn(".uart.retained", source) + self.assertNotIn("nx_stm32u575_usart1_pins_prepare", source) self.assertNotIn("nx_stm32u575_uart_irq(&s_nx_port_console)", source) def test_preflight_claims_vector_without_reading_unclocked_gpdma(self): diff --git a/tools/configure/tests/providers/stm32u575/test_rtc.py b/tools/configure/tests/providers/stm32u575/test_rtc.py index b81e6f253..d710c7664 100644 --- a/tools/configure/tests/providers/stm32u575/test_rtc.py +++ b/tools/configure/tests/providers/stm32u575/test_rtc.py @@ -16,7 +16,7 @@ def test_exact_pinless_mode_has_static_face_and_early_readonly_preflight(self): item, = result.controllers self.assertEqual(('rtc', 'RTC0', 'retained-calendar-alarm'), (item.kind, item.controller, item.mode)) - self.assertEqual((), tuple(item['pins'])) + self.assertEqual((), item.pins) _, source = bindings.emit(result) self.assertIn('nx_stm32u575_rtc_initialize(', source) self.assertIn('nx_rtc_port_stop(', source) diff --git a/tools/configure/tests/providers/stm32u575/test_uart_chain.py b/tools/configure/tests/providers/stm32u575/test_uart_chain.py index 7d910d3d3..0510cc320 100644 --- a/tools/configure/tests/providers/stm32u575/test_uart_chain.py +++ b/tools/configure/tests/providers/stm32u575/test_uart_chain.py @@ -27,7 +27,9 @@ def test_real_chain_mode_keeps_one_public_uart_face(self): source = "\n".join(construction.definitions) self.assertIn("_Alignas(32)", source) self.assertIn("nx_stm32u575_uart_chain_node_t", source) - self.assertIn("nx_stm32u575_uart_chain_initialize", source) + self.assertIn("nx_stm32u575_uart_gpdma_config_t", source) + self.assertIn("nx_stm32u575_uart_chain_prepare", construction.initialize.render()) + self.assertNotIn(".uart.registers =", source) def test_selected_plan_names_symbolic_placement_not_guessed_addresses(self): item = self.item() diff --git a/tools/configure/timer_advanced.py b/tools/configure/timer_advanced.py index 663bd0d07..db3c803b6 100644 --- a/tools/configure/timer_advanced.py +++ b/tools/configure/timer_advanced.py @@ -3,6 +3,7 @@ Idle levels are explicit assembly policy, not a claim about an output stage or physical break circuit. Capture ring storage remains supplied by the caller. """ +from tools.configure.calls import DirectCall from dataclasses import dataclass from tools.configure.gpio_phase import withdraw_pin @@ -123,7 +124,7 @@ def constructor(item, result, prefix): gpio = maintained(result.soc_family).GPIO_PHASE options = item.options lines = [f"static uint32_t s_nx_timer_clock_added_{name};", - f"static uint16_t s_nx_timer_pin_saved_{name}[{len(item['pins'])}];", + f"static uint16_t s_nx_timer_pin_saved_{name}[{len(item.pins)}];", f"static uint8_t s_nx_timer_pin_leases_{name};", f"static uint8_t s_nx_timer_priority_saved_{name};", f"static bool s_nx_timer_irq_leased_{name};"] @@ -154,7 +155,7 @@ def constructor(item, result, prefix): # Both outputs must have observed inactive drive before any pin lease # is released. A refused second store retains the entire output group. if not capture: - for index, pin in enumerate(item["pins"]): + for index, pin in enumerate(item.pins): if pin["function"] == "break": continue wire = pin["pin"] @@ -177,7 +178,7 @@ def constructor(item, result, prefix): f" if (({mode} & {mode_mask}u) != {output_mode}u) {{ return NX_ERROR_IO; }}", f" if (({odr} & {1 << number}u) != {1 << number if high else 0}u) {{ return NX_ERROR_IO; }}", " }"] - for index, pin in reversed(list(enumerate(item["pins"]))): + for index, pin in reversed(list(enumerate(item.pins))): wire = pin["pin"] number = int(wire[2:]) lease = 1 << index @@ -193,7 +194,7 @@ def constructor(item, result, prefix): f"static nx_result_t prepare_{name}(void) {{", " nx_result_t status;", f" if ({state}.config != NULL) {{ return NX_ERROR_BUSY; }}", f" if (((NVIC->ISER[{bank}] | NVIC->ISPR[{bank}] | NVIC->IABR[{bank}]) & {bit}u) != 0u) {{ return NX_ERROR_BUSY; }}"] - if result.get("kernel_profile") is not None: + if result.kernel is not None: lines += [" if (((SCB->AIRCR >> 8u) & 7u) > 3u) { return NX_ERROR_PERMISSION; }"] lines += [f" s_nx_timer_clock_added_{name} = ~{apb} & {gate};", f" NX_PLATFORM_REGISTER_WRITE({apb}, {apb} | {gate});", " nx_arch_dsb();", @@ -205,7 +206,7 @@ def constructor(item, result, prefix): lines += [f" if ({shared} != 0u) {{ return NX_ERROR_BUSY; }}"] lines += [f" s_nx_timer_priority_saved_{name} = NVIC->IPR[{irq}];", f" s_nx_timer_irq_leased_{name} = true;"] - for index, pin in enumerate(item["pins"]): + for index, pin in enumerate(item.pins): wire = pin["pin"] number = int(wire[2:]) lines += [f" status = nx_{prefix}_gpio_clock_enable({ord(wire[1])-ord('A')}u);", @@ -225,17 +226,17 @@ def constructor(item, result, prefix): f"void {vector}_IRQHandler(void) {{ nx_{prefix}_{kind}_irq(&{state}); }}"] return Construction( state_type=f"nx_{kind}_controller_t", definitions=lines, - initialize=f"prepare_{name}()", stop=[f"stop_{name}()"], + initialize=DirectCall(f"prepare_{name}"), stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True, pin_snapshots={pin["pin"]: f"s_nx_timer_pin_saved_{name}[{index}]" - for index, pin in enumerate(item["pins"])}) + for index, pin in enumerate(item.pins)}) def _gpio_mask(result): return sum(1 << (ord(port) - ord('A')) for port in { pin["pin"][1] for item in result.controllers if item.kind in KINDS - for pin in item["pins"]}) + for pin in item.pins}) def _gpio_clock(result): diff --git a/tools/configure/usb_device.py b/tools/configure/usb_device.py index a09511906..60db2252c 100644 --- a/tools/configure/usb_device.py +++ b/tools/configure/usb_device.py @@ -3,6 +3,7 @@ This mechanism does not infer a Board connector, powered role, class protocol or physical qualification. Selected endpoints are separate static typed identities. """ +from tools.configure.calls import DirectCall from dataclasses import dataclass from tools.configure.gpio_phase import withdraw_pin @@ -137,7 +138,7 @@ def constructor(item, result, prefix): state = "s_nx_port_" + name children = [child for child in result.devices if child.controller == item.id] options = item.options - pins = item["pins"] + pins = item.pins lines = [f"static nx_{prefix}_usb_clock_state_t s_nx_usb_clock_{name};", f"static nx_usb_endpoint_t s_nx_usb_endpoints_{name}[{len(children)}];", f"static nx_usb_event_t s_nx_usb_events_{name}[{options.event_capacity}];", @@ -176,14 +177,14 @@ def constructor(item, result, prefix): f" .transfer = {transfer}, " f".max_packet = {option.max_packet}u,", f" .fifo_start = {option.fifo_start}u, .fifo_words = {option.fifo_words}u", " };"] - policy = result["irq"] + policy = result.irq kernel = kernel_policy_constant(result) - ceiling = policy["syscall_priority"] or 0 + ceiling = policy.syscall_priority or 0 lines += [f" {state} = (nx_usb_controller_t){{", f" .endpoints = s_nx_usb_endpoints_{name}, .endpoint_count = {len(children)}u,", f" .events = s_nx_usb_events_{name}, .capacity = {options.event_capacity}u,", f" .snapshot = {{ .epoch = {state}.snapshot.epoch }},", - f" .irq_policy = {{ {kernel}, {policy['external_count']}u, {policy['priority_bits']}u, {ceiling}u }},", + f" .irq_policy = {{ {kernel}, {policy.external_count}u, {policy.priority_bits}u, {ceiling}u }},", f" .irq = {{ .priority = {options.irq.priority}u }},", f" .poll_limit = {options.poll_limit}u, .drain_timeout_us = {options.drain_timeout_us}u,", f" .rx_fifo_words = {options.rx_fifo_words}u, .vbus_sensing = false", " };"] @@ -205,7 +206,7 @@ def constructor(item, result, prefix): f"void {vector}_IRQHandler(void) {{ nx_usb_controller_irq(&{state}); }}"] return Construction( state_type="nx_usb_controller_t", definitions=lines, - initialize=f"prepare_{name}()", stop=[f"stop_{name}()"], + initialize=DirectCall(f"prepare_{name}"), stop=[DirectCall(f"stop_{name}")], retain_on_failed_init=True, pin_snapshots={pin["pin"]: f"s_nx_usb_pin_snapshots_{name}[{index}]" @@ -214,7 +215,7 @@ def constructor(item, result, prefix): def gpio_clock_mask(result): ports = {pin["pin"][1] for item in result.controllers - if item.kind == "usb_device" for pin in item["pins"]} + if item.kind == "usb_device" for pin in item.pins} return sum(1 << (ord(port) - ord('A')) for port in ports) diff --git a/tools/dev/projects.py b/tools/dev/projects.py index e52eaea59..825fa65bf 100644 --- a/tools/dev/projects.py +++ b/tools/dev/projects.py @@ -163,9 +163,9 @@ def create(templates, revision, template, output, *, source_root=ROOT): result["validation"] = { "scope": "current resolver and declared inputs only", "source_root": str(source_root), - "configuration_sha256": resolved["configuration_sha256"], + "configuration_sha256": resolved.configuration_sha256, "inputs": [file_identity(input_paths[item["path"]]) - for item in resolved["inputs"]], + for item in resolved.inputs], "resolver": file_identity(source_root / "tools/configure/configure.py"), } diff --git a/tools/dev/qualification.py b/tools/dev/qualification.py index e54157a8f..e75f530f5 100644 --- a/tools/dev/qualification.py +++ b/tools/dev/qualification.py @@ -4,6 +4,8 @@ from __future__ import annotations import argparse +import hashlib +import math import os from pathlib import Path import subprocess @@ -14,7 +16,8 @@ from tools.dev.process import run as run_command from tools.evidence.common import (EvidenceError, atomic_json, command, fields, - file_identity, load_json, regular_file, stream_digest, verify_file_identity) + file_identity, load_json, parse_json, regular_file, sha256_value, + stream_digest, verify_file_identity) from tools.evidence.identity import canonical_digest, observe_git_source CHECKERS = { @@ -144,6 +147,139 @@ def execute(plan: dict, root: Path, output: Path, *, require_candidate: bool = F return report +def verify_execution(plan: dict, report_path: Path, root: Path) -> dict: + """Verify a complete original execution for the current clean source. + + This admits an existing maintained plan's commands, not a new execution or + software candidate. Partial plans and interrupted reports are not merged. + Original attempt receipts and even empty actual output remain mandatory. + """ + from tools.dev import timings + + validate_plan(plan) + if plan.get("formal") is not True: + raise EvidenceError("local software gate requires a formal plan") + root = Path(root).resolve() + report_path = regular_file(report_path) + output = report_path.parent + observed = {} + + def observe_file(identity, *, empty=False): + fields(identity, {"path", "size", "sha256"}) + sha256_value(identity["sha256"]) + if type(identity["size"]) is not int or identity["size"] < int(not empty): + raise EvidenceError("invalid execution evidence byte count") + path = regular_file(identity["path"], nonempty=not empty) + with path.open("rb") as stream: + digest = stream_digest(stream) + if path.stat().st_size != identity["size"] or digest != identity["sha256"]: + raise EvidenceError("execution evidence changed") + previous = observed.setdefault(str(path), identity) + if previous != identity: + raise EvidenceError("execution evidence has conflicting identities") + return path + + def read_json(identity): + path = observe_file(identity) + with path.open("rb") as stream: + contents = stream.read(4 * 1024 * 1024 + 1) + if (len(contents) > 4 * 1024 * 1024 or + len(contents) != identity["size"] or + hashlib.sha256(contents).hexdigest() != identity["sha256"]): + raise EvidenceError("execution JSON changed or exceeds its bound") + return parse_json(contents) + + report_identity = file_identity(report_path) + report = read_json(report_identity) + fields(report, {"schema_version", "kind", "status", "source_before", + "source_after", "plan_sha256", "formal", "stages", + "physical_status", "software_qualified"}) + source = observe_git_source(root) + if (source["dirty"] is not False or + any(not line.startswith(" ") for line in source["submodules"]) or + report["source_before"] != source or report["source_after"] != source): + raise EvidenceError("local software gate requires the same clean source") + if (type(report["schema_version"]) is not int or report["schema_version"] != 1 or + report["kind"] != "qualification_plan_execution" or + report["status"] != "executed_passed" or report["formal"] is not True or + report["plan_sha256"] != canonical_digest(plan) or + report["physical_status"] != "not_executed" or + report["software_qualified"] is not False or + not isinstance(report["stages"], list) or + len(report["stages"]) != len(plan["stages"])): + raise EvidenceError("complete matching formal execution required") + for stage, actual in zip(plan["stages"], report["stages"]): + fields(actual, {"name", "checker", "argv", "checker_source", "exit_code", + "duration_s", "status", "raw", "command_receipt"}) + script, *prefix = CHECKERS[stage["checker"]] + expected_argv = command( + [sys.executable, "-B", str(root / script), *prefix, *stage["args"]], + {"root": str(root), "output": str(output)}) + if (actual["name"] != stage["name"] or + actual["checker"] != stage["checker"] or + actual["argv"] != expected_argv or actual["status"] != "passed" or + type(actual["exit_code"]) is not int or actual["exit_code"] != 0 or + type(actual["duration_s"]) not in (int, float) or + not math.isfinite(actual["duration_s"]) or actual["duration_s"] < 0 or + actual["checker_source"] != file_identity(root / script)): + raise EvidenceError("stage differs from the executed owned checker") + observe_file(actual["checker_source"]) + raw = output / (stage["name"] + ".raw") + if (actual["raw"]["path"] != str(raw) or + actual["command_receipt"]["path"] != + str(raw.with_suffix(".command.json"))): + raise EvidenceError("stage evidence navigation differs from execution") + observe_file(actual["raw"], empty=True) + receipt = read_json(actual["command_receipt"]) + attempt_path = Path(receipt.get("attempt_receipt", "")) + attempt_root = output / (raw.stem + ".attempts") + if (attempt_path.name != "receipt.json" or + attempt_path.parent.parent != attempt_root): + raise EvidenceError("original unique attempt receipt required") + attempt = read_json(file_identity(attempt_path)) + if attempt != {key: value for key, value in receipt.items() + if key != "attempt_receipt"}: + raise EvidenceError("command view differs from its original attempt") + if (receipt.get("command") != expected_argv or + receipt.get("cwd") != str(root) or + receipt.get("stage") != stage["checker"] or + receipt.get("outcome") != "completed" or + type(receipt.get("exit_code")) is not int or + receipt["exit_code"] != 0 or + type(receipt.get("child_returncode")) is not int or + receipt["child_returncode"] != 0 or + type(receipt.get("timeout_seconds")) not in (int, float) or + receipt.get("timeout_seconds") != stage.get("timeout_s", 1800) or + receipt.get("withdrawal_complete") is not True or + receipt.get("withdrawal_stable") is not True or + receipt.get("withdrawn_processes") != []): + raise EvidenceError("completed owned checker receipt required") + for name in ("stdout", "stderr"): + identity = receipt.get(name, {}) + fields(identity, {"path", "bytes", "sha256"}) + if identity["path"] != str(attempt_path.parent / (name + ".txt")): + raise EvidenceError("attempt output navigation differs") + observe_file({"path": identity["path"], "size": identity["bytes"], + "sha256": identity["sha256"]}, empty=True) + # The runner's conventional log is the decoded stdout followed by + # stderr; a newly hashed replacement summary is not that execution. + actual_output = "".join(Path(receipt[name]["path"]).read_text( + errors="replace") for name in ("stdout", "stderr")) + if raw.read_text() != actual_output: + raise EvidenceError("execution log differs from actual output") + # Reuse the receipt reader's timestamp, cost and raw-output contract. + timings.summarize([attempt_path]) + for identity in tuple(observed.values()): + observe_file(identity, empty=identity["size"] == 0) + if observe_git_source(root) != source: + raise EvidenceError("source changed during execution verification") + return {"schema_version": 1, "kind": "verified_qualification_plan_execution", + "status": "verified", "source": source, "report": report_identity, + "plan_sha256": canonical_digest(plan), + "stage_count": len(plan["stages"]), "software_qualified": False, + "physical_status": "not_executed"} + + def provenance(source: dict, inputs: list[Path], artifacts: list[Path], commands: list[list[str]], *, configuration: Path | None = None, environment: Path | None = None) -> dict: diff --git a/tools/documentation/check.py b/tools/documentation/check.py index 7470bdf12..ff94deb27 100644 --- a/tools/documentation/check.py +++ b/tools/documentation/check.py @@ -377,8 +377,10 @@ def scan(root, *, check_diagrams=True, check_configuration=True, from tools.configure import configure, runtime import tomllib raw = tomllib.loads(destination.read_text()) - result = (configure.resolve(destination, root) if "schema" in raw - else runtime.resolve(destination)) + if "schema" in raw: + configure.resolve_ir(destination, root) + else: + runtime.resolve(destination) config_records.append({"source": str(destination), "kind": "literalinclude", "resolved": True}) plain = FENCE.sub("", contents) diff --git a/tools/documentation/cli.py b/tools/documentation/cli.py index 3fe8ffc77..23b96b1cd 100644 --- a/tools/documentation/cli.py +++ b/tools/documentation/cli.py @@ -313,6 +313,9 @@ def main(arguments=None, *, root=ROOT): command.add_argument("--output", type=Path, default=Path(root).parent / ".nexus-documentation-artifacts" / Path(root).name / "packages") command.add_argument("--release-id") + command.add_argument("--qualification-reference", type=Path, + action="append", default=[], + help="Verify and reference an original sealed candidate; repeat for multiple scopes") elif name == "preview": command.add_argument("--build", type=Path, default=Path(root) / "build/documentation") command.add_argument("--host", default="127.0.0.1") @@ -372,7 +375,8 @@ def main(arguments=None, *, root=ROOT): revision = observe_git_source(root)["commit"] release_id = args.release_id or "docs-" + revision[:12] print(json.dumps(delivery.package(args.build, args.output, - source_root=root, release_id=release_id), indent=2)) + source_root=root, release_id=release_id, + qualification_references=args.qualification_reference), indent=2)) elif args.action in {"verify", "restore"}: from tools.documentation import delivery record = (delivery.verify(args.path) if args.action == "verify" else diff --git a/tools/documentation/delivery.py b/tools/documentation/delivery.py index d9bf6eee4..1dd97bd7f 100644 --- a/tools/documentation/delivery.py +++ b/tools/documentation/delivery.py @@ -39,8 +39,13 @@ "api/index.html", "licenses/LICENSE.txt", BUILD_METADATA) MAX_FILES = 100000 MAX_BYTES = 1024 * 1024 * 1024 +REFERENCE_VERIFICATION = { + "at_packaging": "original_candidate_closure", + "offline": "reference_metadata_only", + "original_evidence_included": False, +} CODE_PATHS = ("CMakeLists.txt", "arch", "boards", "cmake", "components", - "core", "dependencies", "io", "os", "soc") + "core", "dependencies", "drivers", "io", "os", "soc") def _json_bytes(value): @@ -507,7 +512,7 @@ def _manifest(payload, expected_manifest_sha256=None): raise ValueError("Documentation manifest digest mismatch") manifest = _json(raw, "manifest") if (type(manifest.get("schema_version")) is not int or - manifest.get("schema_version") != 1 or + manifest.get("schema_version") not in (1, 2) or manifest.get("kind") != "nexus_documentation_package"): raise ValueError("Unsupported documentation manifest schema") _release_id(manifest.get("release_id")) @@ -594,6 +599,18 @@ def _manifest_identity(manifest): if (_identity(manifest["code_source"]) != _identity(manifest["documentation_source"])): raise ValueError("Documentation code and source snapshots differ") + if manifest["schema_version"] == 2: + if "qualified_code" in manifest: + raise ValueError("Legacy qualification cannot enter schema-2 delivery") + _qualification_references(manifest.get("qualification_references")) + else: + _legacy_qualification_reference(manifest) + + +def _legacy_qualification_reference(manifest): + """Read frozen schema-1 references without producing new legacy packages.""" + if "qualification_references" in manifest: + raise ValueError("Schema-1 delivery cannot carry new qualification references") qualified = manifest.get("qualified_code") if not isinstance(qualified, dict): raise ValueError("Documentation qualified code reference missing") @@ -605,6 +622,84 @@ def _manifest_identity(manifest): raise ValueError("Documentation qualified code evidence identity missing") +def _qualification_references(references): + """Check sealed navigation metadata, not original firmware execution bytes.""" + from tools.evidence.common import SOFTWARE_QUALIFICATION_SCOPES, identifier + + if not isinstance(references, list) or len(references) > 256: + raise ValueError("Documentation qualification references must be bounded") + seen = set() + fields = {"candidate", "candidate_id", "source", "configuration_sha256", + "artifacts", "qualified_scopes", "physical_status", + "product_promotion", "signature_status", "verification"} + for reference in references: + if not isinstance(reference, dict) or set(reference) != fields: + raise ValueError("Documentation qualification reference fields differ") + identifier(reference["candidate_id"], "candidate id") + source = reference["source"] + if (not isinstance(source, dict) or source.get("dirty") is not False or + any(not re.fullmatch(r"[0-9a-f]{40}", str(source.get(key, ""))) + for key in ("commit", "tree"))): + raise ValueError("Documentation qualification source identity invalid") + modules = source.get("submodules") + if (not isinstance(modules, list) or + any(not isinstance(item, str) or item.startswith(("-", "+", "U")) + for item in modules)): + raise ValueError("Documentation qualification dependency identity invalid") + if (not re.fullmatch(r"[0-9a-f]{64}", + str(reference["configuration_sha256"])) or + reference["qualified_scopes"] != sorted(SOFTWARE_QUALIFICATION_SCOPES) or + reference["physical_status"] != "not_executed" or + reference["product_promotion"] != "not_performed" or + reference["signature_status"] != "unsigned" or + reference["verification"] != REFERENCE_VERIFICATION): + raise ValueError("Documentation qualification scope/boundary invalid") + candidate = reference["candidate"] + if (not isinstance(candidate, dict) or + set(candidate) != {"path", "sha256", "size"} or + not isinstance(candidate["path"], str) or + not candidate["path"].strip() or + any(ord(character) < 32 for character in candidate["path"])): + raise ValueError("Documentation candidate locator identity invalid") + artifacts = reference["artifacts"] + if not isinstance(artifacts, dict) or set(artifacts) != {"elf", "bin", "map"}: + raise ValueError("Documentation qualification artifacts incomplete") + for identity in [candidate, *artifacts.values()]: + if (not isinstance(identity, dict) or + type(identity.get("size")) is not int or identity["size"] <= 0 or + not re.fullmatch(r"[0-9a-f]{64}", + str(identity.get("sha256", "")))): + raise ValueError("Documentation qualification digest/bytes invalid") + if any(set(identity) != {"sha256", "size"} + for identity in artifacts.values()): + raise ValueError("Documentation artifact reference fields differ") + if candidate["sha256"] in seen: + raise ValueError("Documentation duplicate qualification reference") + seen.add(candidate["sha256"]) + + +def _verified_qualification_references(paths): + """Verify explicitly selected original candidates before retaining references.""" + if not paths: + return [] + from tools.evidence.candidate import verified_projection + + references = [] + for path in paths: + projection = verified_projection(_no_symlink(path)) + reference = {key: projection[key] for key in + ("candidate", "candidate_id", "source", + "configuration_sha256", "qualified_scopes", + "physical_status", "product_promotion", "signature_status")} + reference["artifacts"] = { + role: {key: identity[key] for key in ("sha256", "size")} + for role, identity in projection["artifacts"].items()} + reference["verification"] = dict(REFERENCE_VERIFICATION) + references.append(reference) + _qualification_references(references) + return references + + def _checksums(payload): return "".join(f"{_digest(payload[name])} {name}\n" for name in sorted(payload) if name != CHECKSUMS).encode("utf-8") @@ -657,7 +752,8 @@ def _identity(source): return {key: source[key] for key in ("commit", "tree", "dirty", "submodules")} -def package(build_dir, output_dir, *, source_root, release_id, toolchain=None): +def package(build_dir, output_dir, *, source_root, release_id, toolchain=None, + qualification_references=()): """Bind a clean, unchanged source and complete built payload to one release ID.""" release_id = _release_id(release_id) build_dir = _no_symlink(build_dir) @@ -695,26 +791,17 @@ def package(build_dir, output_dir, *, source_root, release_id, toolchain=None): source_interface = dict(policy) source_interface["policy_sha256"] = _digest( (source_root / "dependencies/platform-compatibility.json").read_bytes()) - qualification_path = source_root / "docs/delivery/build-system-results.json" - qualification_bytes = qualification_path.read_bytes() - qualification = _json(qualification_bytes, "qualification index") - qualified = qualification.get("source", {}) - if any(not re.fullmatch(r"[0-9a-f]{40}", str(qualified.get(key, ""))) - for key in ("canonical_code_commit", "code_tree")): - raise ValueError("Missing exact qualified code source") + references = _verified_qualification_references(qualification_references) documentation = _identity(source) documentation.update(_git_tree(source_root, ("docs", "tools/documentation"))) code = _identity(source) code.update(_git_tree(source_root, CODE_PATHS)) manifest = { - "schema_version": 1, "kind": "nexus_documentation_package", + "schema_version": 2, "kind": "nexus_documentation_package", "release_id": release_id, "version": policy["semantic_version"], "source_interface": source_interface, "documentation_source": documentation, "code_source": code, - "qualified_code": {"commit": qualified["canonical_code_commit"], - "tree": qualified["code_tree"], - "index": "docs/delivery/build-system-results.json", - "index_sha256": _digest(qualification_bytes)}, + "qualification_references": references, "physical_status": "not_executed", "languages": ["en", "zh_CN"], "tool_versions": metadata["tools"], "qualification_boundary": "Documentation delivery grants no new firmware or HIL qualification.", diff --git a/tools/documentation/status.py b/tools/documentation/status.py index 6b53b0479..fabc347e0 100644 --- a/tools/documentation/status.py +++ b/tools/documentation/status.py @@ -20,6 +20,7 @@ ("platform-refinement-execution.csv", "platform-refinement", "I/O 完善台账"), ("io-evolution-execution.csv", "io-evolution", "I/O 增量实施台账"), ("soc-refinement-execution.csv", "soc-refinement", "SoC 原专项台账"), + ("soc-ip-layout-execution.csv", "soc-ip-layout", "SoC/IP 目录整理台账"), ("build-system-execution.csv", "build-system", "构建系统原专项台账"), ("documentation-system-execution.csv", "documentation-system", "文档系统台账"), ("developer-experience-execution.csv", "developer-experience", "开发体验75 项台账"), diff --git a/tools/documentation/test_delivery.py b/tools/documentation/test_delivery.py index 938bf7b68..6a86ac7e9 100644 --- a/tools/documentation/test_delivery.py +++ b/tools/documentation/test_delivery.py @@ -12,6 +12,7 @@ import tempfile import threading import unittest +from unittest.mock import patch from tools.documentation import delivery @@ -67,13 +68,12 @@ def package(self): source_root=self.source, release_id="1.0.0-docs-test") - def test_package_records_distinct_source_and_qualification(self): + def test_package_has_no_implicit_historical_qualification(self): result = self.package() manifest = delivery.verify(result["directory"]) - self.assertEqual(manifest["version"], "1.0.0") - self.assertEqual(manifest["qualified_code"]["commit"], "a" * 40) - self.assertNotEqual(manifest["documentation_source"]["commit"], - manifest["qualified_code"]["commit"]) + self.assertEqual(manifest["schema_version"], 2) + self.assertEqual(manifest["qualification_references"], []) + self.assertNotIn("qualified_code", manifest) self.assertEqual(manifest["physical_status"], "not_executed") paths = [record["path"] for record in manifest["files"]] self.assertNotIn(delivery.MANIFEST, paths) @@ -81,6 +81,101 @@ def test_package_records_distinct_source_and_qualification(self): self.assertIn("api/index.html", paths) self.assertTrue(Path(result["archive"]).is_file()) + def test_explicit_candidate_references_preserve_separate_scope_and_source(self): + from tools.evidence import candidate + first = self.base / "first-candidate.json" + second = self.base / "second-candidate.json" + projections = [self.candidate_projection(first, "a"), + self.candidate_projection(second, "b")] + projections[0]["candidate_id"] = "fixture:qualified-a" + with patch.object(candidate, "verified_projection", + side_effect=projections) as verifier: + result = delivery.package(self.build, self.base / "output", + source_root=self.source, release_id="1.0.0-docs-test", + qualification_references=(first, second)) + self.assertEqual(verifier.call_count, 2) + manifest = delivery.verify(result["directory"]) + references = manifest["qualification_references"] + self.assertEqual(len(references), 2) + self.assertEqual(references[0]["candidate_id"], "fixture:qualified-a") + self.assertEqual([item["source"]["commit"] for item in references], + ["a" * 40, "b" * 40]) + self.assertNotEqual(manifest["documentation_source"]["commit"], + references[0]["source"]["commit"]) + self.assertEqual(references[0]["configuration_sha256"], "a" * 64) + self.assertEqual(references[0]["physical_status"], "not_executed") + self.assertEqual(references[0]["qualified_scopes"], + sorted(candidate.REQUIRED_SCOPES)) + self.assertNotIn("resolved", references[0]) + self.assertEqual(references[0]["verification"]["offline"], + "reference_metadata_only") + self.assertFalse(references[0]["verification"]["original_evidence_included"]) + + def candidate_projection(self, path, letter): + from tools.evidence import candidate + return {"candidate": {"path": str(path), "sha256": letter * 64, + "size": 123}, + "candidate_id": "fixture-" + letter, + "source": {"commit": letter * 40, "tree": letter * 40, + "dirty": False, "submodules": []}, + "configuration_sha256": letter * 64, + "artifacts": {role: {"sha256": letter * 64, "size": 12} + for role in ("elf", "bin", "map")}, + "qualified_scopes": sorted(candidate.REQUIRED_SCOPES), + "physical_status": "not_executed", + "product_promotion": "not_performed", + "signature_status": "unsigned", "resolved": {}} + + def test_unverified_or_duplicate_candidate_is_rejected_before_output(self): + from tools.evidence import candidate + first = self.base / "first-candidate.json" + with patch.object(candidate, "verified_projection", + side_effect=ValueError("missing original evidence")): + with self.assertRaisesRegex(ValueError, "missing original"): + delivery.package(self.build, self.base / "output", + source_root=self.source, release_id="1.0.0-docs-test", + qualification_references=(first,)) + self.assertFalse((self.base / "output").exists()) + + projection = self.candidate_projection(first, "a") + with patch.object(candidate, "verified_projection", return_value=projection): + with self.assertRaisesRegex(ValueError, "duplicate"): + delivery.package(self.build, self.base / "output", + source_root=self.source, release_id="1.0.0-docs-test", + qualification_references=(first, first)) + self.assertFalse((self.base / "output").exists()) + + def test_rehashed_reference_cannot_claim_new_scope_or_physical_acceptance(self): + projection = self.candidate_projection(self.base / "candidate.json", "a") + projection.pop("resolved") + projection["verification"] = dict(delivery.REFERENCE_VERIFICATION) + for key, value in (("physical_status", "passed"), + ("qualified_scopes", ["all_platforms"]), + ("configuration_sha256", "")): + with self.subTest(key=key): + bad = json.loads(json.dumps(projection)) + bad[key] = value + with self.assertRaisesRegex(ValueError, "scope|boundary"): + delivery._qualification_references([bad]) + + def test_historical_schema_one_remains_verifiable_without_new_legacy_writer(self): + result = self.package() + directory = Path(result["directory"]) + manifest_path = directory / delivery.MANIFEST + manifest = json.loads(manifest_path.read_bytes()) + manifest["schema_version"] = 1 + manifest.pop("qualification_references") + manifest["qualified_code"] = { + "commit": "a" * 40, "tree": "b" * 40, + "index": "docs/delivery/build-system-results.json", + "index_sha256": "c" * 64} + manifest_path.write_bytes(delivery._json_bytes(manifest)) + payload = delivery._payload(directory) + (directory / delivery.CHECKSUMS).write_bytes(delivery._checksums(payload)) + restored = delivery.verify(directory) + self.assertEqual(restored["schema_version"], 1) + self.assertEqual(restored["qualified_code"]["commit"], "a" * 40) + def test_source_interface_version_and_epochs_use_checked_policy(self): result = self.package() manifest = delivery.verify(result["directory"]) diff --git a/tools/documentation/test_status.py b/tools/documentation/test_status.py index 674a327c1..5ea1c78f4 100644 --- a/tools/documentation/test_status.py +++ b/tools/documentation/test_status.py @@ -112,6 +112,30 @@ def test_markdown_special_characters_remain_literal(self): self.assertIn("a\\|b", view) self.assertIn("<tag>", view) + def test_soc_ip_ledger_is_registered_without_changing_original_status(self): + specification = next((item for item in status.SPECIFICATIONS + if item[0] == "soc-ip-layout-execution.csv"), None) + self.assertIsNotNone(specification, + "completed SoC/IP ledger must enter default views") + source = self.root / "docs/design" / specification[0] + source.write_text( + "task_id,software_status,physical_status,evidence_paths\n" + "R44,segmented_execution_passed,not_executed,original/A;original/B\n" + "R48,software_qualified,not_executed,original/candidate.json\n") + original = source.read_bytes() + report = status.generate(self.root, specifications=(specification,)) + self.assertEqual(report["status"], "passed") + output = self.root / "docs/status/ledgers/soc-ip-layout.md" + self.assertIn("segmented_execution_passed", output.read_text()) + self.assertIn("original/A;original/B", output.read_text()) + self.assertIn("not_executed", output.read_text()) + self.assertEqual(source.read_bytes(), original) + output.write_text(output.read_text().replace("not_executed", "passed")) + checked = status.generate(self.root, check=True, + specifications=(specification,)) + self.assertEqual(checked["status"], "failed") + self.assertEqual(source.read_bytes(), original) + def test_current_io_ledger_is_generated_and_checked_without_promotion(self): fields = ("task_id", "priority", "phase", "task", "dependencies", "implementation_status", "validation_status", diff --git a/tools/documentation/test_verify_publication.py b/tools/documentation/test_verify_publication.py index 7703be7eb..dc5a6db3f 100644 --- a/tools/documentation/test_verify_publication.py +++ b/tools/documentation/test_verify_publication.py @@ -19,7 +19,7 @@ class PublicationTests(unittest.TestCase): def setUp(self): self.fixture = fixture.DeliveryTests( - "test_package_records_distinct_source_and_qualification") + "test_package_has_no_implicit_historical_qualification") self.fixture.setUp() self.addCleanup(self.fixture.doCleanups) self.root = self.fixture.base diff --git a/tools/evidence/candidate.py b/tools/evidence/candidate.py index 6d8ae1510..31c91d832 100644 --- a/tools/evidence/candidate.py +++ b/tools/evidence/candidate.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse +import hashlib from pathlib import Path import shutil import sys @@ -12,16 +13,15 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from tools.evidence.common import (EvidenceError, atomic_json, digest, fields, - file_identity, identifier, load_json, stream_digest, verify_file_identity) + file_identity, identifier, load_json, parse_json, regular_file, stream_digest, + verify_file_identity) +from tools.evidence.common import SOFTWARE_QUALIFICATION_SCOPES as REQUIRED_SCOPES from tools.evidence.identity import resolved_identity, verify_source from tools.evidence.container import verify as verify_environment from tools.measurement.elf import Elf32, binary_from_elf from tools.measurement.resources import resolved_float_abi, validate_elf_configuration from tools.evidence.proof import validate_scope_proof, verify_sdk_file -REQUIRED_SCOPES = frozenset({"host_contracts", "arm_link", "resource_budget", - "source_sdk", "reproducibility", "hil_tooling"}) - def collect_identities(value, *, identity_verifier=verify_file_identity, allow_unbound=False) -> list[dict]: @@ -219,11 +219,16 @@ def verify(candidate_path: Path) -> dict: "configuration_sha256", "input_manifest_sha256", "artifacts", "payloads", "qualified_scopes", "sdk_packages", "physical_status", "product_promotion", "signature_status", "limitations"}) + identifier(candidate["candidate_id"], "candidate id") if (type(candidate["schema_version"]) is not int or candidate["schema_version"] != 1 or candidate["kind"] != "sealed_software_candidate" or candidate["status"] != "software_qualified" or - set(candidate["qualified_scopes"]) != REQUIRED_SCOPES or - candidate["physical_status"] != "not_executed"): + not isinstance(candidate["qualified_scopes"], list) or + any(not isinstance(scope, str) for scope in candidate["qualified_scopes"]) or + sorted(candidate["qualified_scopes"]) != sorted(REQUIRED_SCOPES) or + candidate["physical_status"] != "not_executed" or + candidate["product_promotion"] != "not_performed" or + candidate["signature_status"] != "unsigned"): raise EvidenceError("unsupported or incomplete software candidate") root = candidate_path.parent identities = {} @@ -240,6 +245,8 @@ def verify(candidate_path: Path) -> dict: raise EvidenceError("duplicate candidate payload") identities[identity["sha256"]] = path manifest = load_json(identities[candidate["input_manifest_sha256"]]) + if manifest["candidate_id"] != candidate["candidate_id"]: + raise EvidenceError("sealed candidate identity differs from its manifest") if manifest["source"] != candidate["source"] or manifest["source"]["dirty"]: raise EvidenceError("sealed candidate source mismatch or dirty source") def packed(identity): @@ -280,6 +287,54 @@ def packed(identity): return candidate +def verified_projection(candidate_path: Path) -> dict: + """Project an existing verified seal; never execute or confer new scopes. + + Navigation consumers share the complete candidate verifier. The manifest + and configuration are parsed from the same bytes whose identities are + checked, rather than trusting a second mutable JSON read after verification. + The result binds an observation, not a lock on future filesystem changes. + """ + candidate_path = Path(candidate_path).absolute() + before = file_identity(candidate_path) + sealed = verify(candidate_path) + payloads = {item["sha256"]: item for item in sealed["payloads"]} + + def packed_identity(sha): + item = payloads[sha] + return {**item, "path": str(candidate_path.parent / item["path"])} + + def packed_json(sha): + identity = packed_identity(sha) + path = regular_file(identity["path"]) + with path.open("rb") as stream: + contents = stream.read(4 * 1024 * 1024 + 1) + if (len(contents) > 4 * 1024 * 1024 or + len(contents) != identity["size"] or + hashlib.sha256(contents).hexdigest() != identity["sha256"]): + raise EvidenceError("candidate projection payload changed") + return parse_json(contents) + + manifest = packed_json(sealed["input_manifest_sha256"]) + resolved = packed_json(manifest["configuration"]["sha256"]) + artifacts = {role: packed_identity(item["sha256"]) + for role, item in sealed["artifacts"].items()} + for identity in artifacts.values(): + verify_file_identity(identity) + if file_identity(candidate_path) != before: + raise EvidenceError("candidate changed during projection verification") + return {"schema_version": 1, + "kind": "verified_software_candidate_reference", + "candidate": before, "candidate_id": sealed["candidate_id"], + "source": sealed["source"], + "configuration_sha256": sealed["configuration_sha256"], + "resolved": resolved, "artifacts": artifacts, + "qualified_scopes": sealed["qualified_scopes"], + "physical_status": sealed["physical_status"], + "product_promotion": sealed["product_promotion"], + "signature_status": sealed["signature_status"]} + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("operation", choices=("seal", "verify"), nargs="?", default="seal") diff --git a/tools/evidence/common.py b/tools/evidence/common.py index e6a2f1a3f..d777fce0c 100644 --- a/tools/evidence/common.py +++ b/tools/evidence/common.py @@ -13,6 +13,11 @@ import time from typing import Any +SOFTWARE_QUALIFICATION_SCOPES = frozenset({ + "host_contracts", "arm_link", "resource_budget", "source_sdk", + "reproducibility", "hil_tooling", +}) + class EvidenceError(ValueError): """A required delivery invariant could not be established.""" diff --git a/tools/maintenance/README.md b/tools/maintenance/README.md index ce36aa56d..367b829e5 100644 --- a/tools/maintenance/README.md +++ b/tools/maintenance/README.md @@ -16,14 +16,70 @@ inherit another owner's policy. H743 high-clock and SPI DMA reviews and G474 ADC review/workaround records keep their exact profile, revision and scope fields. They neither admit an unknown revision nor establish physical qualification. +Add `--candidate /path/to/sealed/candidate.json` to associate an existing +software candidate with this exact assembly. The query runs the existing full +candidate verifier, including referenced payloads, qualifications and linked +artifacts. It does not execute new tests or publish a candidate. Both the +observed current Git source and the referenced tested source are reported; +moving a checkout does not change its commit/tree identity. A dirty checkout, +changed source or different recursive dependency state cannot inherit a pass. + +The `evidence.relation` is `exact_source_and_configuration` only when clean +source identity and the complete resolved configuration match. That relation +projects the candidate's existing software scopes into `validation`. A valid +candidate with another source is `historical_source`; a different configuration +is `configuration_mismatch`. Both retain `not_assessed` for the current software +selection. Missing, changed or invalid evidence rejects the query rather than +becoming historical success. Physical HIL remains separately unassessed, even +when the software candidate is valid. No candidate changes the aggregate family +table or qualifies other Boards, controllers, modes or routes. + +Without `--candidate`, queries keep their configuration-only behavior and need +no Git or evidence observation. A portable candidate must first be restored with +the existing artifact tools; this command reads the seal and its payloads without +modifying them. Candidate validation may use disposable temporary SDK files. + ```sh python tools/maintenance/capabilities.py --check python tools/maintenance/capabilities.py --write python tools/maintenance/capabilities.py \ --assembly tools/configure/assemblies/h743-high-peripherals.toml +python tools/maintenance/capabilities.py \ + --assembly tools/configure/assemblies/qiming-baremetal-uart-dma.toml \ + --candidate /path/to/sealed/candidate.json python -B -m unittest tools.testing.test_maintenance -v ``` +`local_ci.py --report /path/to/report.json` verifies an existing complete +execution of the checked-in `tools/dev/plans/software.json`. Use the Python +interpreter recorded by that execution. The verifier requires the same clean +source before, after and now; exact plan order, arguments and checker bytes; +actual successful command and unique attempt receipts; and unchanged stdout, +stderr and logs. Empty actual output is accepted only with its completed +receipt. Partial, interrupted, skipped, changed or combined historical plans +cannot publish a pass for a new commit. + +The default operation is offline and read-only. Explicit `--publish` uses the +existing GitHub transport and configured `GH_TOKEN` to post the fixed status +`nexus/local-software-plan` to the verified commit. It observes an open PR to +this repository's `main` before and after verification, rejecting ordinary +head/base drift. Status creation is bound to that SHA; it is not an atomic PR +merge operation and a later head cannot inherit the old status. A new receipt +retains the request and confirmed response, or an unconfirmed outcome if the +remote operation fails. No credentials are included in the receipt. + +```sh +.venv/bin/python tools/maintenance/local_ci.py --report /path/to/report.json +.venv/bin/python tools/maintenance/local_ci.py --report /path/to/report.json \ + --publish --repository X-Gen-Lab/nexus --pull-request "$NEXUS_PR_NUMBER" \ + --output /path/to/new-status-receipt.json +``` + +This status means only the maintained local software plan passed for its exact +source. It neither represents GitHub Actions jobs nor establishes a sealed +candidate, physical HIL, release publication or branch protection. Required +checks must name this actual producer and be verified separately on the server. + `work_packages.py` derives a review/navigation plan from existing provider, capability, resolver fixture, SDK-required-source and reviewed lifecycle-test authorities. Each entry has an exact family/controller/mode identity; these are diff --git a/tools/maintenance/capabilities.py b/tools/maintenance/capabilities.py index 00e8163ab..e36ca3278 100644 --- a/tools/maintenance/capabilities.py +++ b/tools/maintenance/capabilities.py @@ -23,7 +23,7 @@ END = "" -def selection(assembly, root=ROOT): +def selection(assembly, root=ROOT, *, candidate_path=None): """Describe one validated selection; execution has a separate authority. Family declarations cannot establish a selected Board/backend/clock tuple. @@ -31,12 +31,15 @@ def selection(assembly, root=ROOT): contracts. No test receipt, linked image or physical observation is inferred from an accepted configuration or a model implementation. """ - from tools.configure.ir import ConfigurationIR from tools.configure import selected_contracts from tools.configure.resource_graph import json_value + from tools.evidence import identity - data = configure.resolve(Path(assembly), Path(root).resolve()) - typed = ConfigurationIR.from_validated(data) + root = Path(root).resolve() + source = (identity.observe_git_source(root) + if candidate_path is not None else None) + typed = configure.resolve_ir(Path(assembly), root) + data = typed.to_dict() contracts = selected_contracts.for_configuration(typed) policies = selected_contracts.silicon_policies(typed) silicon = {owner: (json_value(asdict(policy)) if policy is not None else None) @@ -69,7 +72,7 @@ def selection(assembly, root=ROOT): validation["configuration"] = "validated" if typed.soc_family == "native": validation["physical_hil"] = "not_applicable" - return {"schema_version": 1, "kind": "selected_support", + result = {"schema_version": 1, "kind": "selected_support", "selection": {key: data[key] for key in ("board", "soc_family", "part", "package", "backend", "clock_profile", "cpu", "irq", @@ -89,6 +92,44 @@ def selection(assembly, root=ROOT): "hardware qualification.", *[item for item in data.get("limitations", []) if item != "Physical qualification not executed"]]} + if candidate_path is not None: + _associate_candidate(result, data, source, Path(candidate_path)) + if identity.observe_git_source(root) != source: + from tools.evidence.common import EvidenceError + raise EvidenceError("source changed during support query") + return result + + +def _associate_candidate(result, resolved, source, candidate_path): + """Only an exact verified source/configuration can support this selection.""" + from tools.evidence import candidate + + reference = candidate.verified_projection(candidate_path) + source_keys = ("commit", "tree", "dirty", "submodules") + clean_dependencies = all(line.startswith(" ") + for line in source["submodules"]) + source_match = (source["dirty"] is False and clean_dependencies and + all(source[key] == reference["source"][key] + for key in source_keys)) + configuration_match = ( + reference["configuration_sha256"] == resolved["configuration_sha256"] + and reference["resolved"] == resolved) + relation = ("configuration_mismatch" if not configuration_match else + "exact_source_and_configuration" if source_match else + "historical_source") + result["current_source"] = source + result["evidence"] = { + "relation": relation, "source_match": source_match, + "configuration_match": configuration_match, + "reference": {key: value for key, value in reference.items() + if key != "resolved"}} + if source_match and configuration_match: + for name in ("host_models", "arm_image", "installed_sdk", + "software_candidate"): + result["validation"][name] = "passed" + result["limitations"].append( + "Evidence association verifies an existing candidate; it executes no " + "new checks and does not assess physical HIL or qualify other selections.") def c_code(source): @@ -182,12 +223,13 @@ def collect(root=ROOT): fixture_paths = sorted(set(assembly_paths) | set(contract_paths) | set(sdk_paths)) for fixture in fixture_paths: - result = configure.resolve(fixture, root) + typed = configure.resolve_ir(fixture, root) + result = typed.to_dict() if result["soc_family"] == "native": provider = configure.maintained(result["soc_family"]) if provider.MODEL is not True or result["physical_qualified"] is not False: raise ValueError("Native source provenance requires an unqualified MODEL") - source = c_code(bindings.emit(result)[1]) + source = c_code(bindings.emit(typed)[1]) for item in result["controllers"]: name = bindings.symbol(item["id"]) face_name = re.escape("s_nx_face_" + name) @@ -274,11 +316,17 @@ def main(arguments=None): action.add_argument("--assembly", type=Path, help="print exact selected support JSON, not execution " "qualification") + parser.add_argument("--candidate", type=Path, + help="verify and associate an existing sealed " + "candidate.json with --assembly") parser.add_argument("--root", type=Path, default=ROOT) args = parser.parse_args(arguments) + if args.candidate is not None and args.assembly is None: + parser.error("--candidate requires --assembly") try: if args.assembly is not None: - print(json.dumps(selection(args.assembly, args.root), indent=2, + print(json.dumps(selection(args.assembly, args.root, + candidate_path=args.candidate), indent=2, sort_keys=True)) return 0 examples = documentation.check(args.root) @@ -300,7 +348,7 @@ def main(arguments=None): print(f"Resolved {sum(row['kind'] != 'fragment' for row in examples)} " "TOML examples; " f"{sum(row['kind'] == 'fragment' for row in examples)} explicit fragments") - except (OSError, ValueError) as error: + except (OSError, ValueError, KeyError, TypeError) as error: print(f"Capability consistency rejected: {error}", file=sys.stderr) return 1 return 0 diff --git a/tools/maintenance/compatibility.py b/tools/maintenance/compatibility.py index cd7e4fb94..87b966ae6 100644 --- a/tools/maintenance/compatibility.py +++ b/tools/maintenance/compatibility.py @@ -70,6 +70,22 @@ def dict_schema(tree, field, required_keys): return one(values, field + " dictionary") +def class_epoch(tree, class_name, field): + """Read the actual typed contract, not an obsolete intermediate mapping.""" + values = [] + for node in tree.body: + if not isinstance(node, ast.ClassDef) or node.name != class_name: + continue + for declaration in node.body: + if (isinstance(declaration, ast.AnnAssign) and + isinstance(declaration.target, ast.Name) and + declaration.target.id == field and + isinstance(declaration.value, ast.Constant) and + type(declaration.value.value) is int): + values.append(declaration.value.value) + return one(values, class_name + "." + field + " schema epoch") + + def guard_schema(tree, variable): values = [] for node in ast.walk(tree): @@ -129,6 +145,7 @@ def source(relative): version = load_policy(root)["semantic_version"] authored = ast.parse(source("tools/configure/authored.py")) configured = ast.parse(source("tools/configure/configure.py")) + typed = ast.parse(source("tools/configure/ir.py")) runtime = ast.parse(source("tools/configure/runtime.py")) package = ast.parse(source("cmake/package/package_source_sdk.py")) factory = ast.parse(source("tools/configure/factory.py")) @@ -137,8 +154,10 @@ def source(relative): if editor["properties"]["schema"]["const"] != author_epoch: raise ValueError("Editor schema differs from actual authoring validator") schemas = {"assembly_authoring": author_epoch, - "assembly_projection": dict_schema(authored, "schema_version", {"board_package"}), - "typed_ir": dict_schema(configured, "schema_version", {"board", "soc_family"}), + "assembly_projection": class_epoch( + typed, "ConfigurationIR", "schema_version"), + "typed_ir": class_epoch( + typed, "ConfigurationIR", "source_epoch"), "board": integer_call(configured, "Board schema"), "soc": integer_call(configured, "SoC schema"), "routes": integer_call(configured, "routes schema"), diff --git a/tools/maintenance/local_ci.py b/tools/maintenance/local_ci.py new file mode 100644 index 000000000..f5621e5eb --- /dev/null +++ b/tools/maintenance/local_ci.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Verify the maintained local software plan and optionally publish its status.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import re +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from tools.dev.qualification import verify_execution +from tools.evidence.common import (EvidenceError, atomic_json, file_identity, + load_json, separate_outputs) +from tools.evidence.identity import observe_git_source +from tools.release.delivery import GitHub + +CONTEXT = "nexus/local-software-plan" +PLAN = "tools/dev/plans/software.json" + + +def verify(report_path, root=ROOT): + """Only the checked-in complete software plan can produce this status.""" + root = Path(root).resolve() + plan_path = root / PLAN + before = file_identity(plan_path) + report = verify_execution(load_json(plan_path), Path(report_path), root) + if file_identity(plan_path) != before: + raise EvidenceError("maintained software plan changed during verification") + return {**report, "plan": before, "context": CONTEXT} + + +def _pull(api, number): + value = api.get("/pulls/" + str(number)) + if (not isinstance(value, dict) or value.get("state") != "open" or + value.get("merged") is not False or value.get("number") != number): + raise EvidenceError("an existing open pull request is required") + if (value["base"]["repo"]["full_name"] != api.repository or + value["base"]["ref"] != "main"): + raise EvidenceError("pull request must target this repository main") + for key in ("head", "base"): + if not re.fullmatch(r"[0-9a-f]{40}", str(value[key].get("sha", ""))): + raise EvidenceError("pull request requires exact head and base commits") + return {"head": value["head"]["sha"], "base": value["base"]["sha"]} + + +def publish(report_path, root, api, pull_number, output): + """Publish only to the verified SHA; a later PR head receives no old pass. + + Two PR observations detect ordinary head/base drift. They do not claim an + atomic transaction with GitHub's subsequent status creation or merge rules. + """ + root, report_path, output = (Path(root).resolve(), Path(report_path), + Path(output).absolute()) + if type(pull_number) is not int or pull_number <= 0: + raise EvidenceError("pull request number must be positive") + separate_outputs([output], [report_path], [root, report_path.parent]) + if output.exists(): + raise EvidenceError("publication receipt must be a new path") + initial = _pull(api, pull_number) + gate = verify(report_path, root) + if initial["head"] != gate["source"]["commit"]: + raise EvidenceError("pull request head differs from verified source") + if _pull(api, pull_number) != initial: + raise EvidenceError("pull request head or base changed during verification") + if observe_git_source(root) != gate["source"]: + raise EvidenceError("source changed before status publication") + payload = {"state": "success", "context": CONTEXT, + "description": "Local software plan: " + str(gate["stage_count"]) + + " stages; observed base " + initial["base"][:12]} + endpoint = api.base + "/statuses/" + initial["head"] + result = {"schema_version": 1, "kind": "local_software_plan_status", + "repository": api.repository, "pull_request": pull_number, + "observed_refs": initial, "verification": gate, + "request": {"method": "POST", "endpoint": endpoint, + "payload": payload}, + "publication": "pending", "software_qualified": False, + "scope": "maintained local software plan; no Actions or release claim"} + atomic_json(output, result) + try: + response = api.request("POST", endpoint, payload) + if (not isinstance(response, dict) or + response.get("state") != payload["state"] or + response.get("context") != CONTEXT or + type(response.get("id")) is not int or response["id"] <= 0): + raise EvidenceError("GitHub did not confirm the requested status") + result.update(publication="confirmed", response={key: response.get(key) + for key in ("id", "url", "state", "context", "description")}) + atomic_json(output, result) + except (OSError, ValueError, KeyError, TypeError) as error: + result.update(publication="unconfirmed", failure=str(error)) + atomic_json(output, result) + raise + return result + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=ROOT) + parser.add_argument("--report", type=Path, required=True) + parser.add_argument("--publish", action="store_true") + parser.add_argument("--repository") + parser.add_argument("--pull-request", type=int) + parser.add_argument("--output", type=Path) + args = parser.parse_args(argv) + if args.publish and not all((args.repository, args.pull_request, args.output)): + parser.error("--publish requires --repository, --pull-request and --output") + if not args.publish and any((args.repository, args.pull_request, args.output)): + parser.error("publication options require --publish") + try: + if args.publish: + if not os.environ.get("GH_TOKEN"): + raise EvidenceError("GitHub publication requires configured GH_TOKEN") + report = publish(args.report, args.root, GitHub(args.repository), + args.pull_request, args.output) + else: + report = verify(args.report, args.root) + print(json.dumps(report, indent=2, sort_keys=True)) + return 0 + except (OSError, ValueError, KeyError, TypeError) as error: + print("Local software plan rejected: " + str(error), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/maintenance/work_packages.py b/tools/maintenance/work_packages.py index 1bd89c5dc..620f01d7c 100644 --- a/tools/maintenance/work_packages.py +++ b/tools/maintenance/work_packages.py @@ -258,11 +258,11 @@ def collect(root=ROOT): configurations = [] expected_inputs = {} for name in fixtures: - resolved = configure.resolve(root / name, root) + resolved = configure.resolve_ir(root / name, root) configurations.append({"assembly": name, - "configuration_sha256": resolved["configuration_sha256"], - "inputs": copy.deepcopy(resolved["inputs"])}) - for item in resolved["inputs"]: + "configuration_sha256": resolved.configuration_sha256, + "inputs": [dict(item) for item in resolved.inputs]}) + for item in resolved.inputs: source_name(item["path"]) prior = expected_inputs.setdefault(item["path"], item["sha256"]) if prior != item["sha256"]: diff --git a/tools/measurement/README.md b/tools/measurement/README.md index 0ef1f530b..16984ee88 100644 --- a/tools/measurement/README.md +++ b/tools/measurement/README.md @@ -26,11 +26,16 @@ missing reviewed routes are not invented to complete a benchmark matrix. Different modes or workloads may show marginal linked cost, but they are not equivalent-profile optimization regressions. Keep those comparisons separate from `soc_costs.py compare`, which enforces equivalent recorded profiles. +Profiles use the production TOML schema-2 entry only. Derived inputs preserve +the authored tables while relocating root Board/layout paths and replacing the +explicit optimization choice; they never re-enter through a JSON assembly. `compare.py --parallel 1` 显式限制构建并发并将其写入矩阵,默认只用一个 job。 每个负载和优化组合使用新构建目录与真实 ELF;所需板级配置必须已经存在, 不会为了凑齐矩阵而虚构路线。不同模式或负载的差量可以说明链接成本,不能当作 等价条件下的优化回归;等价比较仍由 `soc_costs.py compare` 严格核对记录条件。 +配置只走生产 TOML schema 2 入口。派生输入保留原有表,仅迁移根级 Board/layout +路径并替换显式优化选择,不再通过 JSON 装配入口重新进入解析器。 `soc_costs.py observe` binds the exact ELF, resolved configuration, resource budget, complete actual compile database, selected source bytes and tools. Its diff --git a/tools/measurement/compare.py b/tools/measurement/compare.py index eb7a922b8..3043356f5 100644 --- a/tools/measurement/compare.py +++ b/tools/measurement/compare.py @@ -4,17 +4,19 @@ from __future__ import annotations import argparse +import json import os import re from pathlib import Path import shutil import subprocess import sys +import tomllib sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from tools.evidence.common import (EvidenceError, atomic_json, file_identity, - load_json, regular_file) + regular_file) from tools.evidence.identity import git_source, resolved_identity from tools.evidence.reproduce import run_logged from tools.measurement.resources import (measure, resolved_float_abi, @@ -27,21 +29,56 @@ def profile_input(source: Path, assembly_path: Path, workload: str, - build_root: Path) -> dict: + build_root: Path) -> str: """Use the same authoring front end; temporary profiles are derived inputs.""" + from tools.configure.configure import load_assembly selected_path = assembly_path.with_name( assembly_path.stem + "-" + workload + assembly_path.suffix) - if selected_path.suffix == ".toml": - from tools.configure.configure import load_assembly - assembly = load_assembly(selected_path, source) + snapshots = {} + assembly = load_assembly(selected_path, source, snapshots) + board = (selected_path.parent / assembly.board_package).resolve() + replacements = {"board_package": os.path.relpath(board, + build_root.resolve())} + if assembly.layout is not None: + layout = (selected_path.parent / assembly.layout).resolve() + replacements["layout"] = os.path.relpath(layout, build_root.resolve()) + contents = snapshots[selected_path.resolve()].decode("utf-8") + return _root_values(contents, replacements, remove=("board",)) + + +def _root_values(contents: str, replacements: dict[str, str], + *, remove: tuple[str, ...] = ()) -> str: + """Replace complete root assignments, preserving every nested table byte. + + Parsing complete statements avoids mistaking a quoted key, comment or a + multiline string for a table boundary. This is a narrow source edit, not a + second TOML serializer or configuration resolver. + """ + tomllib.loads(contents) + names = set(replacements).union(remove) + lines = contents.splitlines(keepends=True) + kept = [] + pending = "" + for index, line in enumerate(lines): + if not pending and line.lstrip().startswith("["): + kept.extend(lines[index:]) + break + pending += line + try: + statement = tomllib.loads(pending) + except tomllib.TOMLDecodeError: + continue + if not names.intersection(statement): + kept.append(pending) + pending = "" else: - assembly = load_json(selected_path) - board = (selected_path.parent / assembly["board_package"]).resolve() - if assembly.get("layout") is not None: - layout = (selected_path.parent / assembly["layout"]).resolve() - assembly["layout"] = os.path.relpath(layout, build_root.resolve()) - assembly["board_package"] = os.path.relpath(board, build_root.resolve()) - return assembly + if pending: + raise ValueError("Incomplete authored TOML root assignment") + prefix = "".join(key + " = " + json.dumps(value) + "\n" + for key, value in replacements.items()) + result = prefix + "".join(kept) + tomllib.loads(result) + return result def tool_identity(executable: str, version_args: list[str]) -> dict: @@ -88,9 +125,10 @@ def compare(source: Path, assembly_path: Path, build_root: Path, for optimization in optimizations: for workload in workloads: profile = build_root / (optimization + "-" + workload) - selected = {**profile_inputs[workload], "optimization": optimization} - selected_path = build_root / (optimization + "-" + workload + ".json") - atomic_json(selected_path, selected) + selected = _root_values(profile_inputs[workload], + {"optimization": optimization}) + selected_path = build_root / (optimization + "-" + workload + ".toml") + selected_path.write_text(selected, encoding="utf-8") commands = [[tools["cmake"]["file"]["path"], "-S", str(source), "-B", str(profile), "-G", "Ninja", "-DCMAKE_MAKE_PROGRAM=" + tools["ninja"]["file"]["path"], diff --git a/tools/measurement/resources.py b/tools/measurement/resources.py index 1e54aad2d..04ed6c209 100644 --- a/tools/measurement/resources.py +++ b/tools/measurement/resources.py @@ -14,7 +14,7 @@ file_identity, identifier, load_json, regular_file) from tools.evidence.identity import resolved_identity, resolved_input_files from tools.measurement.elf import Elf32, interval_bytes, intervals_union -from tools.configure.memory import resource_budget +from tools.configure.memory import resource_budget_projection def integer(value, name: str, *, minimum: int = 0) -> int: @@ -195,7 +195,7 @@ def measure_data(elf: Elf32, resolved: dict, budget: dict) -> tuple[dict, list]: validate_budget(budget, memory) if "memory_budgets" in resolved: try: - expected = resource_budget(resolved) + expected = resource_budget_projection(resolved) except (KeyError, TypeError, StopIteration, ValueError) as error: raise EvidenceError("invalid resolved memory budget contract") from error if budget != expected: diff --git a/tools/testing/test_compatibility.py b/tools/testing/test_compatibility.py index 447018c27..d31d0be87 100644 --- a/tools/testing/test_compatibility.py +++ b/tools/testing/test_compatibility.py @@ -29,6 +29,36 @@ def test_current_policy_matches_actual_authorities(self): self.assertFalse(report["physical_qualification"]) self.assertGreaterEqual(len(report["inputs"]), 7) + def test_typed_source_epoch_is_separate_from_json_projection(self): + from tools.configure import configure + from tools.configure.ir import ConfigurationIR + + result = configure.resolve_ir( + ROOT / "tools/configure/assemblies/native-gpio.toml") + observed = compatibility.observe(ROOT) + self.assertIsInstance(result, ConfigurationIR) + self.assertEqual(result.source_epoch, 2) + self.assertEqual(result.to_dict()["schema_version"], 1) + self.assertNotIn("source_epoch", result.to_dict()) + self.assertEqual(observed["schemas"]["typed_ir"], 2) + self.assertEqual(observed["schemas"]["assembly_projection"], 1) + + def test_changed_ir_epoch_or_projection_is_rejected(self): + for before, after in ( + ("source_epoch: ClassVar[int] = 2", + "source_epoch: ClassVar[int] = 3"), + ("schema_version: int = 1", "schema_version: int = 2"), + ("source_epoch: ClassVar[int] = 2", + "source_epoch: ClassVar[int] = True")): + with self.subTest(after=after), tempfile.TemporaryDirectory() as directory: + root = self.fixture(directory) + path = root / "tools/configure/ir.py" + source = path.read_text() + self.assertTrue(before in source, "IR epoch declaration missing") + path.write_text(source.replace(before, after)) + with self.assertRaisesRegex(ValueError, "schema|epoch"): + compatibility.check(root) + def test_snapshot_policy_checks_actual_supported_factory_epoch(self): self.assertIs(metadata.policy_contract(self.policy, expected_factory_epoch=1), self.policy) for epoch in (0, 2, True, "1"): @@ -178,6 +208,7 @@ def fixture(self, directory): root = Path(directory) names = [compatibility.POLICY, "CMakeLists.txt", "tools/configure/authored.py", "tools/configure/configure.py", "tools/configure/runtime.py", + "tools/configure/ir.py", "tools/configure/assembly.schema.json", "tools/configure/factory.py", "cmake/package/package_source_sdk.py", "cmake/package/NexusConfigVersion.cmake.in", "boards/native_reference/board.json", "soc/native/soc.json", "soc/native/routes.json"] diff --git a/tools/testing/test_h743_flash_ram_closure.py b/tools/testing/test_h743_flash_ram_closure.py index 7e205d3ee..56568ee82 100644 --- a/tools/testing/test_h743_flash_ram_closure.py +++ b/tools/testing/test_h743_flash_ram_closure.py @@ -56,11 +56,11 @@ def setUp(self): self.addCleanup(self.storage.cleanup) self.directory = Path(self.storage.name) self.images = 0 - self.selected = gate.resolve( + self.selected = gate.resolve_ir( ROOT / "tools/configure/assemblies/h743-high-peripherals.toml", - root=ROOT) - self.unselected = gate.resolve( - ASSEMBLIES / "h743-high-empty.toml", root=ROOT) + root=ROOT).to_dict() + self.unselected = gate.resolve_ir( + ASSEMBLIES / "h743-high-empty.toml", root=ROOT).to_dict() def command(self, argv, directory): result = subprocess.run(argv, cwd=directory, capture_output=True, diff --git a/tools/testing/test_local_ci.py b/tools/testing/test_local_ci.py new file mode 100644 index 000000000..b7a96283b --- /dev/null +++ b/tools/testing/test_local_ci.py @@ -0,0 +1,265 @@ +"""Local plan verification uses actual small commands, never a platform CI pass.""" + +import copy +from contextlib import redirect_stdout +import io +import json +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest.mock import patch + +from tools.dev import qualification +from tools.evidence.common import EvidenceError, atomic_json, file_identity + + +class LocalPlanFixture(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix="nexus-local-plan-") + self.addCleanup(self.temporary.cleanup) + self.directory = Path(self.temporary.name) + self.root = self.directory / "source" + self.root.mkdir() + checker = self.root / "tools/testing/run_tool_tests.py" + checker.parent.mkdir(parents=True) + checker.write_text("import sys\nprint('small verifier model', sys.argv[1:])\n") + self.plan = {"schema_version": 1, "formal": True, "stages": [ + {"name": "first", "checker": "tool_tests", "args": ["first"]}, + {"name": "second", "checker": "tool_tests", "args": ["second"]}]} + plan_path = self.root / "tools/dev/plans/software.json" + plan_path.parent.mkdir(parents=True) + atomic_json(plan_path, self.plan) + self.git("init", "-q") + self.git("add", ".") + self.git("-c", "user.name=Local CI Test", "-c", + "user.email=local-ci@example.invalid", "commit", "-qm", "fixture") + self.output = self.directory / "execution" + self.report = qualification.execute(self.plan, self.root, self.output) + self.path = self.output / "report.json" + self.assertEqual(self.report["status"], "executed_passed") + + def git(self, *args): + subprocess.run(["git", "-C", str(self.root), *args], + check=True, capture_output=True) + + def verify(self): + return qualification.verify_execution(self.plan, self.path, self.root) + + def write_report(self, value): + atomic_json(self.path, value) + + +class LocalPlanTests(LocalPlanFixture): + def test_actual_complete_plan_binds_source_and_preserves_report_bytes(self): + before = self.path.read_bytes() + result = self.verify() + self.assertEqual(result["status"], "verified") + self.assertEqual(result["source"], self.report["source_before"]) + self.assertEqual(result["stage_count"], 2) + self.assertEqual(result["report"], file_identity(self.path)) + self.assertEqual(result["plan_sha256"], self.report["plan_sha256"]) + self.assertFalse(result["software_qualified"]) + self.assertEqual(self.path.read_bytes(), before) + + def test_partial_reordered_skipped_and_forged_success_are_rejected(self): + for mutation in ("missing-stage", "reordered", "duplicate", "plan", + "running", "failed", "dirty", "source-after", + "formal", "skipped", "exit", "boolean-exit"): + value = copy.deepcopy(self.report) + if mutation == "missing-stage": + value["stages"].pop() + elif mutation == "reordered": + value["stages"].reverse() + elif mutation == "duplicate": + value["stages"][1] = value["stages"][0] + elif mutation == "plan": + value["plan_sha256"] = "0" * 64 + elif mutation in ("running", "failed"): + value["status"] = mutation + elif mutation == "dirty": + value["source_before"]["dirty"] = True + elif mutation == "source-after": + value["source_after"]["tree"] = "0" * 40 + elif mutation == "formal": + value["formal"] = False + elif mutation == "skipped": + value["stages"][0]["status"] = "skipped" + else: + value["stages"][0]["exit_code"] = False if mutation == "boolean-exit" else 7 + self.write_report(value) + with self.subTest(mutation=mutation), self.assertRaises(EvidenceError): + self.verify() + + def test_changed_checker_or_current_source_cannot_inherit_a_pass(self): + checker = self.root / "tools/testing/run_tool_tests.py" + checker.write_text("raise SystemExit(0)\n") + with self.assertRaises(EvidenceError): + self.verify() + + def test_raw_receipt_and_unique_attempt_must_all_survive(self): + stage = self.report["stages"][0] + alias = Path(stage["command_receipt"]["path"]) + receipt = json.loads(alias.read_text()) + paths = [Path(stage["raw"]["path"]), alias, + Path(receipt["attempt_receipt"]), Path(receipt["stdout"]["path"]), + Path(receipt["stderr"]["path"])] + for path in paths: + original = path.read_bytes() + path.write_bytes(original + b"changed") + with self.subTest(path=path.name), self.assertRaises(EvidenceError): + self.verify() + path.write_bytes(original) + + def test_mutated_command_semantics_cannot_be_rehashed_into_success(self): + original = copy.deepcopy(self.report) + identity = original["stages"][0]["command_receipt"] + alias = Path(identity["path"]) + alias_bytes = alias.read_bytes() + actual = json.loads(alias_bytes) + attempt = Path(actual["attempt_receipt"]) + attempt_bytes = attempt.read_bytes() + for field, value in (("outcome", "timed_out"), ("child_returncode", 7), + ("cwd", str(self.directory)), ("timeout_seconds", 1), + ("withdrawal_complete", False), + ("command", ["true"])): + changed = {**actual, field: value} + atomic_json(alias, changed) + atomic_json(attempt, {key: value for key, value in changed.items() + if key != "attempt_receipt"}) + report = copy.deepcopy(original) + report["stages"][0]["command_receipt"] = file_identity(alias) + self.write_report(report) + with self.subTest(field=field), self.assertRaises(EvidenceError): + self.verify() + alias.write_bytes(alias_bytes) + attempt.write_bytes(attempt_bytes) + + def test_empty_actual_output_is_valid_with_completed_command_receipts(self): + checker = self.root / "tools/testing/run_tool_tests.py" + checker.write_text("pass\n") + self.git("add", ".") + self.git("-c", "user.name=Local CI Test", "-c", + "user.email=local-ci@example.invalid", "commit", "-qm", "silent") + self.output = self.directory / "silent-execution" + self.report = qualification.execute(self.plan, self.root, self.output) + self.path = self.output / "report.json" + self.assertEqual(self.report["stages"][0]["raw"]["size"], 0) + self.assertEqual(self.verify()["status"], "verified") + + def test_checker_argv_and_source_receipt_must_match_the_owned_plan(self): + for field, value in (("argv", ["true"]), + ("checker_source", file_identity(self.path))): + report = copy.deepcopy(self.report) + report["stages"][0][field] = value + self.write_report(report) + with self.subTest(field=field), self.assertRaises(EvidenceError): + self.verify() + + def test_rehashed_log_cannot_disagree_with_its_actual_stdout_and_stderr(self): + report = copy.deepcopy(self.report) + raw = Path(report["stages"][0]["raw"]["path"]) + raw.write_text("replacement summary without actual output\n") + report["stages"][0]["raw"] = file_identity(raw) + self.write_report(report) + with self.assertRaisesRegex(EvidenceError, "output"): + self.verify() + + +class StatusPublicationTests(LocalPlanFixture): + """The remote API is a model port; no status is sent to GitHub by tests.""" + + def api(self, *, changed=None): + commit = self.report["source_before"]["commit"] + snapshot = {"number": 3, "state": "open", "merged": False, + "head": {"sha": commit, + "repo": {"full_name": "model/platform"}}, + "base": {"sha": "a" * 40, "ref": "main", + "repo": {"full_name": "model/platform"}}} + + class Api: + repository = "model/platform" + base = "https://api.github.com/repos/model/platform" + + def __init__(self): + self.gets = [] + self.posts = [] + + def get(self, path): + self.gets.append(path) + return copy.deepcopy(changed if changed and len(self.gets) > 1 + else snapshot) + + def request(self, method, url, payload=None): + self.posts.append((method, url, payload)) + return {"id": 17, "state": payload["state"], + "context": payload["context"], + "url": url, "description": payload["description"]} + + return Api(), snapshot + + def test_publisher_uses_exact_sha_fixed_context_and_two_pr_observations(self): + from tools.maintenance import local_ci + api, snapshot = self.api() + output = self.directory / "publication.json" + result = local_ci.publish(self.path, self.root, api, 3, output) + self.assertEqual(api.gets, ["/pulls/3", "/pulls/3"]) + self.assertEqual(len(api.posts), 1) + method, url, payload = api.posts[0] + self.assertEqual((method, url), ( + "POST", api.base + "/statuses/" + snapshot["head"]["sha"])) + self.assertEqual(payload["context"], "nexus/local-software-plan") + self.assertEqual(payload["state"], "success") + self.assertEqual(result["publication"], "confirmed") + self.assertEqual(result, json.loads(output.read_text())) + self.assertFalse(result["software_qualified"]) + + def test_pr_head_or_base_change_prevents_any_success_post(self): + from tools.maintenance import local_ci + _, initial = self.api() + for key in ("head", "base"): + changed = copy.deepcopy(initial) + changed[key]["sha"] = "b" * 40 + api, _ = self.api(changed=changed) + with self.subTest(key=key), self.assertRaises(EvidenceError): + local_ci.publish(self.path, self.root, api, 3, + self.directory / (key + ".json")) + self.assertEqual(api.posts, []) + + def test_failed_plan_prevents_remote_publication(self): + from tools.maintenance import local_ci + self.write_report({**self.report, "status": "failed"}) + api, _ = self.api() + with self.assertRaises(EvidenceError): + local_ci.publish(self.path, self.root, api, 3, + self.directory / "failed.json") + self.assertEqual(api.posts, []) + + def test_offline_cli_uses_only_the_maintained_plan(self): + from tools.maintenance import local_ci + stream = io.StringIO() + with patch.object(local_ci, "GitHub", + side_effect=AssertionError("unexpected network")), \ + redirect_stdout(stream): + result = local_ci.main(["--root", str(self.root), + "--report", str(self.path)]) + self.assertEqual(result, 0) + self.assertEqual(json.loads(stream.getvalue())["status"], "verified") + + def test_remote_rejection_retains_request_without_claiming_publication(self): + from tools.maintenance import local_ci + api, _ = self.api() + output = self.directory / "denied.json" + with patch.object(api, "request", side_effect=ValueError("GitHub 403")): + with self.assertRaisesRegex(ValueError, "403"): + local_ci.publish(self.path, self.root, api, 3, output) + record = json.loads(output.read_text()) + self.assertEqual(record["publication"], "unconfirmed") + self.assertEqual(record["request"]["payload"]["context"], + "nexus/local-software-plan") + self.assertEqual(record["failure"], "GitHub 403") + self.assertNotIn("response", record) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/testing/test_maintenance.py b/tools/testing/test_maintenance.py index a92f46cbd..9bddde1da 100644 --- a/tools/testing/test_maintenance.py +++ b/tools/testing/test_maintenance.py @@ -105,15 +105,15 @@ def test_redistributed_references_preserve_selected_source_provenance(self): self.assertTrue(fixtures) rows = capabilities.collect(ROOT) for fixture in fixtures: - resolved = capabilities.configure.resolve(fixture, ROOT) + resolved = capabilities.configure.resolve_ir(fixture, ROOT) relative = fixture.relative_to(ROOT).as_posix() - for item in resolved["controllers"]: - with self.subTest(fixture=fixture.name, device=item["id"]): + for item in resolved.controllers: + with self.subTest(fixture=fixture.name, device=item.id): selected = [row for row in rows if (row["family"], row["controller"], row["kind"], row["mode"]) == - (resolved["soc_family"], item["controller"], - item["kind"], item["mode"])] + (resolved.soc_family, item.controller, + item.kind, item.mode)] self.assertEqual(len(selected), 1) self.assertIn(relative, selected[0]["fixtures"]) self.assertTrue(selected[0]["exports"]) diff --git a/tools/testing/test_support_evidence.py b/tools/testing/test_support_evidence.py new file mode 100644 index 000000000..5ec0c6938 --- /dev/null +++ b/tools/testing/test_support_evidence.py @@ -0,0 +1,178 @@ +"""Exact support queries associate verified bytes without widening their scope.""" + +import copy +from contextlib import redirect_stderr, redirect_stdout +import io +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from tools.configure import configure +from tools.evidence import candidate, identity +from tools.evidence.common import EvidenceError +from tools.maintenance import capabilities + + +ROOT = Path(__file__).resolve().parents[2] +ASSEMBLY = ROOT / "tools/configure/assemblies/qiming-baremetal-uart-dma.toml" + + +class SelectedEvidenceTests(unittest.TestCase): + """Git and candidate I/O are ports; resolution and association remain real.""" + + def setUp(self): + self.resolved = configure.resolve_ir(ASSEMBLY, ROOT).to_dict() + self.source = {"root": str(ROOT), "commit": "1" * 40, + "tree": "2" * 40, "dirty": False, "submodules": []} + # This model projection isolates the association boundary. It is never + # written as a candidate or used as software qualification evidence. + self.reference = { + "schema_version": 1, + "kind": "verified_software_candidate_reference", + "candidate": {"path": "/model/candidate.json", + "sha256": "3" * 64, "size": 123}, + "candidate_id": "association-model", + "source": copy.deepcopy(self.source), + "configuration_sha256": self.resolved["configuration_sha256"], + "resolved": copy.deepcopy(self.resolved), + "artifacts": {role: {"sha256": str(index) * 64, "size": 32} + for index, role in enumerate(("elf", "bin", "map"), 4)}, + "qualified_scopes": sorted(candidate.REQUIRED_SCOPES), + "physical_status": "not_executed", + "product_promotion": "not_performed", + "signature_status": "unsigned"} + + def query(self, reference=None, observations=None): + with patch.object(candidate, "verified_projection", create=True, + return_value=reference or self.reference), \ + patch.object(identity, "observe_git_source", + side_effect=observations or [self.source, self.source]): + return capabilities.selection(ASSEMBLY, ROOT, + candidate_path=Path("/model/candidate.json")) + + def test_same_source_and_configuration_reference_exact_software_scopes(self): + result = self.query() + self.assertEqual(result["current_source"], self.source) + evidence = result["evidence"] + self.assertEqual(evidence["relation"], "exact_source_and_configuration") + self.assertTrue(evidence["source_match"]) + self.assertTrue(evidence["configuration_match"]) + self.assertEqual(evidence["reference"]["source"], self.source) + self.assertNotIn("resolved", evidence["reference"]) + self.assertEqual(evidence["reference"]["artifacts"], + self.reference["artifacts"]) + for scope in ("host_models", "arm_image", "installed_sdk", + "software_candidate"): + self.assertEqual(result["validation"][scope], "passed") + self.assertEqual(result["validation"]["physical_hil"], "not_assessed") + self.assertFalse(result["physical_qualified"]) + + def test_relocated_checkout_does_not_change_git_source_identity(self): + reference = copy.deepcopy(self.reference) + reference["source"]["root"] = "/original/source" + result = self.query(reference) + self.assertEqual(result["evidence"]["relation"], + "exact_source_and_configuration") + + def test_historical_source_is_navigation_without_current_qualification(self): + reference = copy.deepcopy(self.reference) + reference["source"]["commit"] = "7" * 40 + result = self.query(reference) + self.assertEqual(result["evidence"]["relation"], "historical_source") + self.assertTrue(result["evidence"]["configuration_match"]) + self.assertFalse(result["evidence"]["source_match"]) + self.assertEqual(result["validation"]["software_candidate"], "not_assessed") + + def test_dirty_or_different_dependency_source_cannot_inherit_qualification(self): + original = copy.deepcopy(self.source) + for field, value in (("dirty", True), + ("tree", "8" * 40), + ("submodules", ["-" + "9" * 40 + " ext/model"])): + with self.subTest(field=field): + self.source = {**original, field: value} + result = self.query() + self.assertFalse(result["evidence"]["source_match"]) + self.assertEqual(result["validation"]["host_models"], + "not_assessed") + + def test_configuration_hash_mismatch_is_explicit_and_never_qualifies(self): + reference = copy.deepcopy(self.reference) + reference["configuration_sha256"] = "a" * 64 + reference["resolved"]["configuration_sha256"] = "a" * 64 + result = self.query(reference) + self.assertEqual(result["evidence"]["relation"], "configuration_mismatch") + self.assertFalse(result["evidence"]["configuration_match"]) + self.assertEqual(result["validation"]["arm_image"], "not_assessed") + + def test_display_fields_cannot_substitute_for_exact_resolved_bytes(self): + # Even an accidentally reused digest from the boundary must not make a + # changed route, backend or controller mode look like an exact match. + for path, value in ((["backend"], "freertos"), + (["board"], "another-board"), + (["controllers", 0, "mode"], "dma-duplex"), + (["controllers", 0, "binding"], "another-route")): + with self.subTest(path=path): + reference = copy.deepcopy(self.reference) + node = reference["resolved"] + for key in path[:-1]: + node = node[key] + node[path[-1]] = value + result = self.query(reference) + self.assertFalse(result["evidence"]["configuration_match"]) + self.assertEqual(result["validation"]["software_candidate"], + "not_assessed") + + def test_invalid_candidate_is_not_reported_as_historical_success(self): + with patch.object(candidate, "verified_projection", create=True, + side_effect=EvidenceError("changed raw receipt")), \ + patch.object(identity, "observe_git_source", return_value=self.source): + with self.assertRaisesRegex(EvidenceError, "changed raw receipt"): + capabilities.selection(ASSEMBLY, ROOT, + candidate_path=Path("/model/candidate.json")) + + def test_source_change_during_query_rejects_the_report(self): + changed = {**self.source, "tree": "9" * 40} + with self.assertRaisesRegex(EvidenceError, "changed during"): + self.query(observations=[self.source, changed]) + + def test_no_evidence_does_not_observe_git_or_change_default_status(self): + with patch.object(identity, "observe_git_source", + side_effect=AssertionError("unrequested Git observation")): + result = capabilities.selection(ASSEMBLY, ROOT) + self.assertNotIn("evidence", result) + self.assertNotIn("current_source", result) + self.assertEqual(result["validation"]["software_candidate"], "not_assessed") + + def test_cli_accepts_candidate_only_with_an_exact_assembly(self): + for arguments in (["--candidate", "/model/candidate.json"], + ["--write", "--candidate", "/model/candidate.json"]): + with self.subTest(arguments=arguments), redirect_stderr(io.StringIO()): + with self.assertRaises(SystemExit) as raised: + capabilities.main(arguments) + self.assertEqual(raised.exception.code, 2) + stream = io.StringIO() + with patch.object(candidate, "verified_projection", create=True, + return_value=self.reference), \ + patch.object(identity, "observe_git_source", return_value=self.source), \ + redirect_stdout(stream): + result = capabilities.main(["--assembly", str(ASSEMBLY), + "--candidate", "/model/candidate.json"]) + self.assertEqual(result, 0) + self.assertEqual(json.loads(stream.getvalue())["evidence"]["relation"], + "exact_source_and_configuration") + + def test_missing_candidate_fails_without_printing_a_success_report(self): + with tempfile.TemporaryDirectory() as directory: + stream = io.StringIO() + with patch.object(identity, "observe_git_source", return_value=self.source), \ + redirect_stdout(stream), redirect_stderr(io.StringIO()): + result = capabilities.main(["--assembly", str(ASSEMBLY), + "--candidate", str(Path(directory) / "missing.json")]) + self.assertEqual(result, 1) + self.assertEqual(stream.getvalue(), "") + + +if __name__ == "__main__": + unittest.main() From fc01d863c3374643c276195ae19a24e6f8b8c8ee Mon Sep 17 00:00:00 2001 From: qiu liwei Date: Tue, 27 Oct 2026 04:59:19 +0000 Subject: [PATCH 2/2] test(stm32): bind constructor model to typed cold calls Generate test-only calls from Construction and share physical MMIO addresses across the complete constructor and driver translation units. Preserve rollback, shared IRQ and line ownership assertions. --- tests/contracts/stm32_constructor_test.c | 135 ++++++++---------- tests/contracts/stm32_model/CMakeLists.txt | 5 +- .../contracts/stm32_model/constructor_model.h | 19 +++ .../stm32_model/generate_constructors.py | 22 ++- 4 files changed, 101 insertions(+), 80 deletions(-) create mode 100644 tests/contracts/stm32_model/constructor_model.h diff --git a/tests/contracts/stm32_constructor_test.c b/tests/contracts/stm32_constructor_test.c index 5af66470f..a9f43244d 100644 --- a/tests/contracts/stm32_constructor_test.c +++ b/tests/contracts/stm32_constructor_test.c @@ -9,41 +9,14 @@ #include "stm32f407_provider.h" #include #include +#include nx_stm32_system_t g_nx_stm32_system; -GPIO_TypeDef g_nx_stm32_gpioa_model; nx_stm32_io_nvic_model_t g_nx_stm32_io_nvic; -static RCC_TypeDef s_rcc; -static EXTI_TypeDef s_exti; -static SYSCFG_TypeDef s_mux; -static TIM_TypeDef s_timer; -static GPIO_TypeDef s_gpioe; -static SPI_TypeDef s_spi; -static I2C_TypeDef s_i2c; -static GPIO_TypeDef s_gpiob; static unsigned s_disable; static unsigned s_clear; static unsigned s_clock_failure; -#undef RCC -#define RCC (&s_rcc) -#undef EXTI -#define EXTI (&s_exti) -#undef SYSCFG -#define SYSCFG (&s_mux) -#undef TIM3 -#define TIM3 (&s_timer) -#undef GPIOE -#define GPIOE (&s_gpioe) -#undef SPI1 -#define SPI1 (&s_spi) -#undef I2C1 -#define I2C1 (&s_i2c) -#undef GPIOB -#define GPIOB (&s_gpiob) -#undef NVIC_ClearPendingIRQ -#define NVIC_ClearPendingIRQ(irq) nx_stm32_model_nvic_clear((int)(irq)) - #include "stm32_constructors.h" /** \brief Inject a late GPIO readback failure after earlier pin mutation. */ @@ -69,6 +42,10 @@ nx_arch_irq_state_t nx_arch_irq_save(void) { void nx_arch_irq_restore(nx_arch_irq_state_t state) { (void)state; } +/** \brief Cold hardware access uses a privileged model owner. */ +bool nx_arch_is_privileged(void) { + return true; +} /** \brief Constructors are tested only from task context. */ bool nx_arch_in_isr(void) { return false; @@ -102,106 +79,112 @@ void nx_stm32_model_nvic_clear(int irq) { /** \brief Force a late line conflict after GPIO/clock acquisition. */ static void exti_rollback_test(void) { - s_rcc.AHB1ENR = 1U << 2U; - s_rcc.APB2ENR = RCC_APB2ENR_USART1EN; - g_nx_stm32_gpioa_model.MODER = 0x55555555U; - g_nx_stm32_gpioa_model.OTYPER = 0xA5A5U; - g_nx_stm32_gpioa_model.OSPEEDR = 0xAAAAAAAAU; - g_nx_stm32_gpioa_model.PUPDR = 0x55555555U; - g_nx_stm32_gpioa_model.AFR[0] = 0x12345678U; + RCC->AHB1ENR = 1U << 2U; + RCC->APB2ENR = RCC_APB2ENR_USART1EN; + GPIOA->MODER = 0x55555555U; + GPIOA->OTYPER = 0xA5A5U; + GPIOA->OSPEEDR = 0xAAAAAAAAU; + GPIOA->PUPDR = 0x55555555U; + GPIOA->AFR[0] = 0x12345678U; uint16_t before = nx_stm32_pin_capture(GPIOA, 6U); - s_exti.IMR = 1U << 6U; - assert(prepare_edge6() == NX_ERROR_BUSY); + EXTI->IMR = 1U << 6U; + assert(nx_test_prepare_edge6() == NX_ERROR_BUSY); assert(!s_nx_port_edge6.initialized); assert(nx_stm32_pin_capture(GPIOA, 6U) == before); - assert(s_rcc.AHB1ENR == (1U << 2U)); - assert(s_rcc.APB2ENR == RCC_APB2ENR_USART1EN); - assert(s_exti.IMR == (1U << 6U)); - s_exti.IMR = 0U; + assert(RCC->AHB1ENR == (1U << 2U)); + assert(RCC->APB2ENR == RCC_APB2ENR_USART1EN); + assert(EXTI->IMR == (1U << 6U)); + EXTI->IMR = 0U; /* A real edge can become pending immediately after source publication. */ NVIC->pending_on_enable = 1U; - assert(prepare_edge5() == NX_SUCCESS); + assert(nx_test_prepare_edge5() == NX_SUCCESS); assert(NVIC_GetEnableIRQ(EXTI9_5_IRQn) == 1U); assert(NVIC_GetPendingIRQ(EXTI9_5_IRQn) == 1U); NVIC->pending_on_enable = 0U; - assert(prepare_edge6() == NX_SUCCESS); + assert(nx_test_prepare_edge6() == NX_SUCCESS); assert(NVIC_GetPendingIRQ(EXTI9_5_IRQn) == 1U); - s_exti.PR = (1U << 5U) | (1U << 6U); + EXTI->PR = (1U << 5U) | (1U << 6U); EXTI9_5_IRQHandler(); assert(s_nx_port_edge5.count == 1U && s_nx_port_edge6.count == 1U); s_disable = 0U; s_clear = 0U; - assert(stop_edge6() == NX_SUCCESS && s_disable == 0U); + assert(nx_test_stop_edge6() == NX_SUCCESS && s_disable == 0U); assert(NVIC_GetEnableIRQ(EXTI9_5_IRQn) == 1U && NVIC_GetPendingIRQ(EXTI9_5_IRQn) == 1U); - assert(stop_edge5() == NX_SUCCESS && s_disable == 1U && s_clear == 1U); + assert(nx_test_stop_edge5() == NX_SUCCESS && s_disable == 1U && + s_clear == 1U); assert(NVIC_GetEnableIRQ(EXTI9_5_IRQn) == 0U && NVIC_GetPendingIRQ(EXTI9_5_IRQn) == 0U); } /** \brief Preserve a conflicting live timer and restore the acquired pin. */ static void pwm_rollback_test(void) { - s_rcc.APB1ENR = RCC_APB1ENR_TIM2EN; - s_rcc.AHB1ENR = 1U << 2U; + RCC->APB1ENR = RCC_APB1ENR_TIM2EN; + RCC->AHB1ENR = 1U << 2U; uint16_t before = nx_stm32_pin_capture(GPIOA, 6U); - s_timer.CR1 = TIM_CR1_CEN; - s_timer.PSC = 12U; - s_timer.ARR = 10U; - assert(prepare_pwm0() == NX_ERROR_BUSY); + TIM3->CR1 = TIM_CR1_CEN; + TIM3->PSC = 12U; + TIM3->ARR = 10U; + assert(nx_test_prepare_pwm0() == NX_ERROR_BUSY); assert(!s_nx_port_pwm0.initialized && !s_nx_idle_pwm0.initialized); assert(nx_stm32_pin_capture(GPIOA, 6U) == before); - assert(s_timer.CR1 == TIM_CR1_CEN && s_timer.PSC == 12U && - s_timer.ARR == 10U); - assert(s_rcc.APB1ENR == RCC_APB1ENR_TIM2EN); - assert(s_rcc.AHB1ENR == (1U << 2U)); - s_timer.CR1 = 0U; - assert(prepare_pwm0() == NX_SUCCESS); + assert(TIM3->CR1 == TIM_CR1_CEN && TIM3->PSC == 12U && TIM3->ARR == 10U); + assert(RCC->APB1ENR == RCC_APB1ENR_TIM2EN); + assert(RCC->AHB1ENR == (1U << 2U)); + TIM3->CR1 = 0U; + assert(nx_test_prepare_pwm0() == NX_SUCCESS); assert(nx_pwm_port_start(&s_nx_face_pwm0) == NX_SUCCESS); - assert(stop_pwm0() == NX_SUCCESS); + assert(nx_test_stop_pwm0() == NX_SUCCESS); assert(!s_nx_port_pwm0.initialized && !s_nx_idle_pwm0.initialized); assert((GPIOA->MODER & (3U << 12U)) == (1U << 12U)); assert(GPIOA->BSRR == (1U << 22U)); - assert((s_rcc.APB1ENR & RCC_APB1ENR_TIM3EN) == 0U); + assert((RCC->APB1ENR & RCC_APB1ENR_TIM3EN) == 0U); } /** \brief Restore previously changed pins when a later acquisition fails. */ static void spi_rollback_test(void) { - s_rcc.AHB1ENR = 1U << 2U; - s_rcc.APB2ENR = RCC_APB2ENR_USART1EN; + RCC->AHB1ENR = 1U << 2U; + RCC->APB2ENR = RCC_APB2ENR_USART1EN; uint16_t before = nx_stm32_pin_capture(GPIOA, 5U); s_clock_failure = 2U; - assert(prepare_spi0() == NX_ERROR_IO); + assert(nx_test_prepare_spi0() == NX_ERROR_IO); assert(nx_stm32_pin_capture(GPIOA, 5U) == before); - assert(s_rcc.AHB1ENR == (1U << 2U)); - assert(s_rcc.APB2ENR == RCC_APB2ENR_USART1EN); + assert(RCC->AHB1ENR == (1U << 2U)); + assert(RCC->APB2ENR == RCC_APB2ENR_USART1EN); assert(!s_nx_cs_sensor0.initialized); - assert(prepare_spi0() == NX_SUCCESS); - assert(s_nx_cs_sensor0.initialized && s_spi.CR1 == 0U); - assert(stop_spi0() == NX_SUCCESS); - assert(!s_nx_cs_sensor0.initialized && s_gpioe.BSRR == 8U); - assert((s_rcc.APB2ENR & RCC_APB2ENR_SPI1EN) == 0U); + assert(nx_test_prepare_spi0() == NX_SUCCESS); + assert(s_nx_cs_sensor0.initialized && SPI1->CR1 == 0U); + assert(nx_test_stop_spi0() == NX_SUCCESS); + assert(!s_nx_cs_sensor0.initialized && GPIOE->BSRR == 8U); + assert((RCC->APB2ENR & RCC_APB2ENR_SPI1EN) == 0U); } /** \brief The generated I2C owner retains exactly its reviewed line facts. */ static void i2c_route_test(void) { - s_rcc.APB1ENR = RCC_APB1ENR_TIM2EN; - s_rcc.AHB1ENR = 1U << 2U; - assert(prepare_i2c0() == NX_SUCCESS); + RCC->APB1ENR = RCC_APB1ENR_TIM2EN; + RCC->AHB1ENR = 1U << 2U; + assert(nx_test_prepare_i2c0() == NX_SUCCESS); assert(s_nx_port_i2c0.line_gpio == GPIOB); assert(s_nx_port_i2c0.line_mask == ((1U << 6U) | (1U << 7U))); assert(s_nx_port_i2c0.registers == I2C1); - assert(stop_i2c0() == NX_SUCCESS); + assert(nx_test_stop_i2c0() == NX_SUCCESS); assert(!s_nx_port_i2c0.initialized); - assert(s_rcc.APB1ENR == RCC_APB1ENR_TIM2EN); + assert(RCC->APB1ENR == RCC_APB1ENR_TIM2EN); } /** \brief Execute actual generated construction, failure and release paths. */ int main(void) { - g_nx_stm32_system.rcc = &s_rcc; + const size_t length = 0x80000U; + void* mapping = + mmap((void*)UINT32_C(0x40000000), length, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + assert(mapping == (void*)UINT32_C(0x40000000)); + g_nx_stm32_system.rcc = RCC; exti_rollback_test(); pwm_rollback_test(); spi_rollback_test(); i2c_route_test(); + assert(munmap(mapping, length) == 0); puts("STM32 generated constructor rollback and release model passed"); return 0; } diff --git a/tests/contracts/stm32_model/CMakeLists.txt b/tests/contracts/stm32_model/CMakeLists.txt index bb4fde1d2..e14bbb202 100644 --- a/tests/contracts/stm32_model/CMakeLists.txt +++ b/tests/contracts/stm32_model/CMakeLists.txt @@ -17,6 +17,8 @@ execute_process(COMMAND "${Python3_EXECUTABLE}" COMMAND_ERROR_IS_FATAL ANY) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${NEXUS_SOURCE_DIR}/soc/stm32/stm32f407/configure/bindings.py" + "${NEXUS_SOURCE_DIR}/soc/stm32/stm32f407/configure/emission.py" + "${NEXUS_SOURCE_DIR}/tools/configure/calls.py" "${CMAKE_CURRENT_SOURCE_DIR}/generate_constructors.py") add_executable(contract_stm32_constructor_model "${stm32_root}/drivers/gpio.c" "${stm32_root}/drivers/exti.c" @@ -47,5 +49,6 @@ target_compile_options(contract_stm32_system_model PRIVATE "-include${CMAKE_CURRENT_SOURCE_DIR}/model_poll.h") target_compile_options(contract_stm32_io_model PRIVATE "-include${CMAKE_CURRENT_SOURCE_DIR}/io_poll.h") +target_compile_definitions(contract_stm32_constructor_model PRIVATE _GNU_SOURCE) target_compile_options(contract_stm32_constructor_model PRIVATE - "-include${CMAKE_CURRENT_SOURCE_DIR}/io_poll.h") + "-include${CMAKE_CURRENT_SOURCE_DIR}/constructor_model.h") diff --git a/tests/contracts/stm32_model/constructor_model.h b/tests/contracts/stm32_model/constructor_model.h new file mode 100644 index 000000000..418a70325 --- /dev/null +++ b/tests/contracts/stm32_model/constructor_model.h @@ -0,0 +1,19 @@ +/** + * \file constructor_model.h + * \brief Exact MMIO addresses shared by constructor and driver TUs + * \author Nexus Team + */ +#ifndef NX_STM32_CONSTRUCTOR_MODEL_H +#define NX_STM32_CONSTRUCTOR_MODEL_H + +#include "io_poll.h" +#include "stm32f407_provider.h" + +/* Cold C providers validate physical routes. The harness maps those addresses + * for every translation unit; only CPU/NVIC leaves and fault injection differ. + * The ordinary I/O model keeps its separate local GPIOA fixture unchanged. + */ +#undef GPIOA +#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE) + +#endif diff --git a/tests/contracts/stm32_model/generate_constructors.py b/tests/contracts/stm32_model/generate_constructors.py index 6ad557fda..c9c907943 100644 --- a/tests/contracts/stm32_model/generate_constructors.py +++ b/tests/contracts/stm32_model/generate_constructors.py @@ -6,6 +6,7 @@ output = Path(sys.argv[2]) sys.path.insert(0, str(root)) from tools.configure.configure import resolve_ir # noqa: E402 +from soc.stm32.stm32f407.configure import emission # noqa: E402 from soc.stm32.stm32f407.configure.bindings import ( # noqa: E402 constructor, shared_irq_definitions, ) @@ -15,16 +16,31 @@ "#endif"] for fixture in ("ve-exti.toml", "ve-pwm.toml", "ve-spi.toml", "ve-i2c.toml"): result = resolve_ir(root / "tests/contracts/stm32_assembly" / fixture, root) + # Include exactly the private types selected by the production emitter; + # runtime hooks belong to the firmware and are not part of this harness. + lines += [line for line in emission.includes(result) + if line.startswith('#include ')] for item in result.controllers: + generated = constructor(item, result, result.board_bindings, + result.routes, result.devices) name = item.id.replace("-", "_") - lines.append(f"static nx_stm32_{item.kind}_state_t s_nx_port_{name};") + lines.append(f"static {generated.state_type} s_nx_port_{name};") if item.kind in {"spi", "i2c"}: lines.append(f"static nx_stm32_{item.kind}_endpoint_state_t s_nx_endpoint_{name};") if item.kind in {"pwm", "exti"}: kind = item.kind lines.append(f"static const nx_{kind}_port_t s_nx_face_{name} = {{&nx_stm32_{kind}_ops, &s_nx_port_{name}}};") - generated = constructor(item, result, result.board_bindings, - result.routes, result.devices) lines += generated.definitions + # This test-only facade follows the actual typed call slots, including + # ordered multi-call cleanup. It is not a production compatibility API. + lines += [f"static nx_result_t nx_test_prepare_{name}(void) {{", + f" return {generated.initialize.render()};", "}", + f"static nx_result_t nx_test_stop_{name}(void) {{"] + if generated.stop: + lines.append(" nx_result_t status;") + for call in generated.stop: + lines += [f" status = {call.render()};", + " if (status != NX_SUCCESS) { return status; }"] + lines += [" return NX_SUCCESS;", "}"] lines += shared_irq_definitions(result.controllers) output.write_text("\n".join(lines) + "\n", encoding="utf-8")