Skip to content

Commit a090ef2

Browse files
committed
Add factory-compatible source audio passthrough
1 parent ed1f488 commit a090ef2

12 files changed

Lines changed: 1047 additions & 27 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,12 @@ An additional opt-in transition target now links the open application directly
9999
for the factory engine-slot ABI: vectors at ITCM zero, DTCM stack, pinned
100100
`SystemInit`, and a hard `0x1e000` copy limit. The recovered OEM updater packer
101101
can wrap it in a validated BINA stream and emits a byte-identical Metal restore
102-
alongside it. This is an offline packaging milestone, not a flash candidate:
103-
the source image does not initialize audio or any target-visible diagnostics.
102+
alongside it. An additional opt-in source path now reproduces the executed
103+
factory SAI1/eDMA contract, including the PLL, pins, four-slot framing,
104+
ping-pong TCD rings, IRQ0 handler, and a weak passthrough/DSP hook. It passes
105+
offline reset/register emulation and a synthetic DMA-block copy. This remains
106+
an offline packaging milestone, not a flash candidate, until the external
107+
analog mute/bypass sequencing is recovered.
104108

105109
The application now has an allocation-free extensible effect registry,
106110
caller-sized processing chains, validated program/bank catalogs, and a

‎docs/hardware/FACTORY_AUDIO.md‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,38 @@ python3 tools/emulate_factory_audio.py \
149149
Successful execution stops at Metal ITCM address `0x8110`, immediately after
150150
the factory initializer enables both SAI1 directions and their DMA requests.
151151

152+
The source implementation can be built and checked independently:
153+
154+
```sh
155+
cmake -S firmware -B build/factory-audio -G Ninja \
156+
-DCMAKE_TOOLCHAIN_FILE="$PWD/firmware/cmake/arm-none-eabi-toolchain.cmake" \
157+
-DCMAKE_BUILD_TYPE=Release \
158+
-DNCR2_BUILD_FACTORY_SLOT_APP=ON \
159+
-DNCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH=ON \
160+
-DNCR2_MCUX_SDK_ROOT="$PWD/third_party/mcux-sdk-workspace"
161+
cmake --build build/factory-audio --target ncr2_factory_slot_app
162+
python3 tools/check_factory_slot.py \
163+
build/factory-audio/ncr2_factory_slot_app.elf
164+
PYTHONPATH=/path/to/unicorn \
165+
python3 tools/emulate_source_audio.py \
166+
build/factory-audio/ncr2_factory_slot_app.elf
167+
```
168+
169+
The source emulator validates the exact SAI1, PLL, pin, DMAMUX, and hardware
170+
TCD state and executes one synthetic RX-completion interrupt through the
171+
weak passthrough block hook. This is a strong digital-contract check, but it
172+
does not model the external converter or analog switching.
173+
152174
## Remaining hardware gates
153175

154-
This is enough to implement the digital audio loop. It is not by itself enough
155-
to declare an image safe to flash. Before hardware deployment, the source
156-
target must also reproduce or conservatively preserve:
176+
The digital audio loop is now implemented. That is not by itself enough to
177+
declare an image safe to flash. Before hardware deployment, the source target
178+
must also reproduce or conservatively preserve:
157179

158180
1. the analog mute/bypass/relay sequencing;
159-
2. cache coherency for DMA buffers;
160-
3. the eDMA IRQ vector and acknowledgement path;
161-
4. a bounded failure mode that leaves the analog path muted or bypassed;
162-
5. a byte-identical known-good Metal restore container.
181+
2. a bounded failure mode that leaves the analog path muted or bypassed;
182+
3. a byte-identical known-good Metal restore container.
183+
184+
The current source buffers and TCDs are in DTCM, so data-cache coherency is
185+
not involved. External IRQ0 and the eDMA acknowledgement/copy path are
186+
validated by the post-link checker and source emulator.

‎docs/hardware/FACTORY_SLOT_APP.md‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ The source target therefore:
2626
- initializes `.data` and `.bss`; and
2727
- enforces the `0x1e000` load budget in the linker and post-link checker.
2828

29-
The current payload is the hardware-neutral application/runtime skeleton. It
30-
does not initialize the audio codec, SAI, eDMA, GPIO, mute, or LEDs. Flashing
31-
it would at best produce a silent diagnostic loop. It is deliberately marked
32-
**offline only** until the source audio path and a non-destructive hardware
33-
diagnostic are implemented and reviewed.
29+
The default payload remains a hardware-neutral application/runtime skeleton.
30+
An additional, separately gated build reproduces the executed factory
31+
SAI1/eDMA contract and runs a four-slot, 32-bit passthrough with two 128-byte
32+
DTCM buffer halves. The source audio path has passed register-level emulation
33+
and an emulated DMA-block copy, but it is still marked **offline only**:
34+
the board's analog mute/bypass sequencing has not yet been recovered.
3435

3536
## Build and verify
3637

@@ -44,13 +45,25 @@ cmake -S firmware -B build/factory-slot -G Ninja \
4445
-DCMAKE_TOOLCHAIN_FILE="$PWD/firmware/cmake/arm-none-eabi-toolchain.cmake" \
4546
-DCMAKE_BUILD_TYPE=Release \
4647
-DNCR2_BUILD_FACTORY_SLOT_APP=ON \
48+
-DNCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH=ON \
4749
-DNCR2_MCUX_SDK_ROOT="$PWD/third_party/mcux-sdk-workspace"
4850

4951
cmake --build build/factory-slot --target ncr2_factory_slot_app
5052
python3 tools/check_factory_slot.py \
5153
build/factory-slot/ncr2_factory_slot_app.elf
54+
55+
PYTHONPATH=/path/to/unicorn \
56+
python3 tools/emulate_source_audio.py \
57+
build/factory-slot/ncr2_factory_slot_app.elf
5258
```
5359

60+
Omit `NCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH=ON` to produce the deliberately
61+
hardware-inert default. The post-link checker validates the complete vector
62+
table, including external IRQ0 targeting the shared eDMA channel 0/16
63+
handler. The source emulator executes reset and initialization, checks the
64+
factory SAI1, PLL, pin, DMAMUX, and TCD values, injects one RX block, invokes
65+
the source ISR, and verifies the unchanged TX block. It does not access USB.
66+
5467
## Build an offline OEM-DFU package
5568

5669
The packer starts from the verified private dump because the factory HID
@@ -76,15 +89,16 @@ refuses to overwrite existing artifacts.
7689

7790
For the verified local dump, the generated Metal restore is byte-identical to
7891
the previously live-tested `eng3-slot1.bina`. The source package is still not
79-
approved for streaming: it lacks audio and target-visible diagnostics.
92+
approved for streaming: digital passthrough is implemented, but the analog
93+
mute/bypass state and live slot semantics remain unverified.
8094

8195
## Gates before first source-slot hardware test
8296

83-
1. Recover and source-control exact audio clocks, pads, SAI framing, eDMA
84-
channels, and ping-pong buffer geometry.
97+
1. ~~Recover and source-control exact audio clocks, pads, SAI framing, eDMA
98+
channels, and ping-pong buffer geometry.~~
8599
2. Identify the converter/codec and prove mute/bypass sequencing.
86-
3. Implement a bounded passthrough path with DTCM DMA buffers and explicit
87-
cache policy.
100+
3. ~~Implement a bounded passthrough path with DTCM DMA buffers and explicit
101+
cache policy.~~ The first image keeps DMA state in uncached DTCM.
88102
4. Add a safe target-visible heartbeat that cannot drive an unknown output.
89103
5. Review the generated write range and exact BINA hash.
90104
6. Keep the byte-identical Metal restore package available before testing.

‎firmware/CMakeLists.txt‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ option(
3131
"Build the unapproved source app for the recovered factory slot ABI"
3232
OFF
3333
)
34+
option(
35+
NCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH
36+
"Enable the recovered unapproved SAI1/eDMA factory-slot passthrough"
37+
OFF
38+
)
3439
option(
3540
NCR2_HARDWARE_RECOVERY_WRITE_ENABLE
3641
"Enable physical NOR mutation in the unapproved hardware bootloader"
@@ -224,36 +229,47 @@ if(NCR2_BUILD_FACTORY_SLOT_APP)
224229
NCR2_FACTORY_SLOT_SYSTEM_SOURCE
225230
${NCR2_MCUX_SDK_ROOT}/core/devices/MIMXRT1051/system_MIMXRT1051.c
226231
)
232+
set(
233+
NCR2_FACTORY_SLOT_CLOCK_SOURCE
234+
${NCR2_MCUX_SDK_ROOT}/core/devices/MIMXRT1051/drivers/fsl_clock.c
235+
)
227236
add_executable(
228237
ncr2_factory_slot_app
229238
EXCLUDE_FROM_ALL
230239
factory_slot/src/startup.S
240+
factory_slot/src/audio_passthrough.c
241+
factory_slot/src/main.c
231242
app/src/effect_runtime.c
232243
app/src/effects_basic.c
233-
app/src/main.c
234244
app/src/program_runtime.c
235245
app/src/program_selector.c
236246
app/src/programs_builtin.c
237247
${NCR2_FACTORY_SLOT_SYSTEM_SOURCE}
248+
${NCR2_FACTORY_SLOT_CLOCK_SOURCE}
238249
)
239250
target_include_directories(
240251
ncr2_factory_slot_app
241252
PRIVATE
242253
${CMAKE_CURRENT_SOURCE_DIR}/app/include
254+
${CMAKE_CURRENT_SOURCE_DIR}/factory_slot/include
243255
)
244256
target_include_directories(
245257
ncr2_factory_slot_app
246258
SYSTEM PRIVATE
247259
${NCR2_MCUX_SDK_ROOT}/core/CMSIS/Core/Include
248260
${NCR2_MCUX_SDK_ROOT}/core/devices/MIMXRT1051
261+
${NCR2_MCUX_SDK_ROOT}/core/devices/MIMXRT1051/drivers
262+
${NCR2_MCUX_SDK_ROOT}/core/drivers/common
249263
)
250264
target_compile_definitions(
251265
ncr2_factory_slot_app
252266
PRIVATE
253267
CPU_MIMXRT1051DVL6B
268+
NCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH=$<BOOL:${NCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH}>
254269
)
255270
set_source_files_properties(
256271
${NCR2_FACTORY_SLOT_SYSTEM_SOURCE}
272+
${NCR2_FACTORY_SLOT_CLOCK_SOURCE}
257273
PROPERTIES COMPILE_OPTIONS "-w"
258274
)
259275
ncr2_firmware_target(

‎firmware/README.md‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ Implemented:
6262
preserved vectors, copies the engine to ITCM, and reproduces the stock
6363
handoff without embedding factory bytes.
6464
- an opt-in source application linked to the recovered factory ITCM slot ABI,
65-
with a strict post-link checker and paired OEM-DFU/Metal-restore packer.
65+
with a strict post-link checker and paired OEM-DFU/Metal-restore packer;
66+
- an opt-in factory-compatible SAI1/eDMA passthrough with DTCM ping-pong
67+
buffers, a weak source DSP hook, and offline register/ISR emulation.
6668

6769
Not implemented:
6870

@@ -73,7 +75,9 @@ Not implemented:
7375
and watchdog confirmation/rollback;
7476
- source-controlled MPU/data-cache policy for the full open application;
7577
- source SEMC initialization;
76-
- GPIO diagnostics or audio.
78+
- physical validation of the source audio path and analog mute/bypass
79+
sequencing;
80+
- GPIO diagnostics.
7781

7882
## Build
7983

@@ -233,19 +237,23 @@ audit and corrected metadata layout.
233237
## Build the source factory-slot application
234238

235239
This transition target runs source-owned code through the still-recoverable
236-
factory launcher. It is excluded from the default build and currently has no
237-
audio or target-visible diagnostics:
240+
factory launcher. It is excluded from the default build. Its audio path is a
241+
second explicit opt-in because analog mute/bypass control is not yet known:
238242

239243
```sh
240244
cmake -S firmware -B build/factory-slot -G Ninja \
241245
-DCMAKE_MAKE_PROGRAM="$(command -v ninja)" \
242246
-DCMAKE_TOOLCHAIN_FILE="$PWD/firmware/cmake/arm-none-eabi-toolchain.cmake" \
243247
-DCMAKE_BUILD_TYPE=Release \
244248
-DNCR2_BUILD_FACTORY_SLOT_APP=ON \
249+
-DNCR2_FACTORY_SLOT_AUDIO_PASSTHROUGH=ON \
245250
-DNCR2_MCUX_SDK_ROOT="$PWD/third_party/mcux-sdk-workspace"
246251
cmake --build build/factory-slot --target ncr2_factory_slot_app
247252
python3 tools/check_factory_slot.py \
248253
build/factory-slot/ncr2_factory_slot_app.elf
254+
PYTHONPATH=/path/to/unicorn \
255+
python3 tools/emulate_source_audio.py \
256+
build/factory-slot/ncr2_factory_slot_app.elf
249257
```
250258

251259
When the private verified dump is present, an offline BINA plus a paired
@@ -259,8 +267,9 @@ python3 tools/nux_dfu.py make-factory-slot \
259267
build/factory-slot/metal-restore.bina
260268
```
261269

262-
Do not stream the source package yet. It is a structurally valid launch image,
263-
not a useful or approved pedal image. See
270+
Do not stream the source package yet. It is a structurally valid digital
271+
passthrough image, but it is not approved until analog mute/bypass sequencing
272+
has been recovered and reviewed. See
264273
[FACTORY_SLOT_APP.md](../docs/hardware/FACTORY_SLOT_APP.md).
265274

266275
## Build a guarded offline full image
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#ifndef NCR2_FACTORY_AUDIO_H
2+
#define NCR2_FACTORY_AUDIO_H
3+
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
enum
8+
{
9+
NCR2_FACTORY_AUDIO_OK = 0,
10+
NCR2_FACTORY_AUDIO_DISABLED = 1,
11+
NCR2_FACTORY_AUDIO_BAD_STATE = 2,
12+
};
13+
14+
enum
15+
{
16+
NCR2_FACTORY_AUDIO_SAMPLE_RATE_HZ = 48000,
17+
NCR2_FACTORY_AUDIO_SLOTS = 4,
18+
NCR2_FACTORY_AUDIO_FRAMES_PER_BLOCK = 8,
19+
NCR2_FACTORY_AUDIO_WORDS_PER_BLOCK =
20+
NCR2_FACTORY_AUDIO_SLOTS *
21+
NCR2_FACTORY_AUDIO_FRAMES_PER_BLOCK,
22+
};
23+
24+
typedef struct ncr2_factory_audio_counters
25+
{
26+
volatile uint32_t rx_blocks;
27+
volatile uint32_t copied_blocks;
28+
volatile uint32_t unexpected_interrupts;
29+
} ncr2_factory_audio_counters_t;
30+
31+
extern ncr2_factory_audio_counters_t
32+
g_ncr2_factory_audio_counters;
33+
34+
uint16_t ncr2_factory_audio_init(void);
35+
36+
/*
37+
* The weak default implementation copies all four factory slots unchanged.
38+
* A source DSP runtime can override this symbol without changing the ISR or
39+
* the hardware contract.
40+
*/
41+
void ncr2_factory_audio_process_block(
42+
const int32_t *input,
43+
int32_t *output,
44+
size_t frames);
45+
46+
void DMA0_DMA16_IRQHandler(void);
47+
48+
#endif

0 commit comments

Comments
 (0)