Skip to content

trace: decode /dev/ive IVE ops (motion pipeline + XNN model geometry) - #227

Merged
widgetii merged 1 commit into
masterfrom
trace-ive-xnn
Sep 29, 2026
Merged

widgetii merged 1 commit into
masterfrom
trace-ive-xnn

Conversation

@widgetii

Copy link
Copy Markdown
Member

What

ipctool trace is a ptrace syscall decoder that already turns a source-less streamer's i2c/spi/mipi/gpio/mtd traffic into readable pseudocode (see docs/sensor-driver-extraction.md). This teaches it /dev/ive.

V4 parts (Hi3516EV200/EV300, Goke) have no NNIE — CNN inference runs on the IVE's built-in XNN unit through /dev/ive and the private mpi_ive_xnn_* API. Vendor detectors like XiongMai Sofia's SSH face model (/usr/res/fd.bin) ship only int8 weights on disk (no header, not OMS), so the network's input geometry and layer list exist only on the wire, in the OMS the streamer hands to the loadmodel ioctl.

This decodes that ioctl (0xc8a04636): read the model buffer's virtual address from the ioctl arg, copy the OMS out of the tracee, and print the input WxHxC (from the OMS Preproc layer) plus a conv/fc/... layer summary.

Expected output line, interleaved with the existing trace:

/* ===== IVE XNN loadmodel: <bytes> bytes, <N> layer(s), 1 in / 1 out ===== */
  input 640x360x1 (WxHxC)
  [0] conv 640x360x1 -> 16c  k=3
  ...

Provenance

Buffer offsets (model_virt @ +8, model_size @ +16) and the OMS segment-header + layer-descriptor layout come from two authoritative sources that agree:

  • OpenIPC/openhisilicon kernel/ive_neo/ive_neo.c (ive_xnn_loadmodel, ive_build_task_nodes) — the clean-room driver;
  • the XNN ioctl RE (test-xnn.c in OpenIPC/qemu-hisilicon).

All reads are bounds-gated (sane src/dst/layer counts; every descriptor within the copied range), so a partial or non-OMS buffer is skipped, never misread.

Scope / safety

  • src/ptrace.c is wholly #ifdef __arm__, so this is arm32-only; no change on mips/arm64.
  • One new /dev/ive branch in the open dispatch + a self-contained decoder. No effect on existing i2c/spi/mipi/mtd paths.

Testing

  • Builds clean on arm32 and arm64; cYAML_test/reginfo_test and tools/test_pipeline.sh pass.
  • On-target (Hi3516EV300, XM Sofia, firmware 000529B2 2021-03-03): confirmed ipctool trace /usr/bin/Sofia runs and captures the streamer's bus traffic (1.5 MB, 150+ sensor_write lines). Caveat: under ptrace Sofia starts ~100× slower and its AI init is deep in startup, so the XNN loadmodel line itself was not reached within the traced window — the decoder is verified by construction against the two sources above, and live capture of the decode is still to be confirmed. Flagging that honestly for review.

Closes nothing; complements the sensor-driver-extraction workflow.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Decode IVE XNN loadmodel geometry in ARM traces

✨ Enhancement 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Decode /dev/ive XNN loadmodel calls to recover CNN input geometry unavailable in weight files.
• Summarize OMS layers alongside existing trace output, with bounds checks on copied data.
• Document the new trace output and its relevance to V4 camera firmware.
Diagram

graph TD
    Streamer["Vendor streamer"] --> Device["/dev/ive"] --> Dispatch["Open dispatch"] --> Hook["XNN ioctl hook"] --> Buffer[("OMS buffer")] --> Decoder["OMS decoder"] --> Output["Trace output"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Incrementally read OMS descriptors
  • ➕ Could report every layer even when descriptors extend beyond the current 1 KiB copy.
  • ➕ Avoids copying an entire model when only descriptors are needed.
  • ➖ Adds ptrace reads and parsing complexity to an already slow traced startup.

Recommendation: The bounded-prefix approach is a reasonable first step for recovering input geometry with limited tracing overhead. It can silently stop the layer summary at 1 KiB, however; use incremental reads if a complete layer list becomes a requirement.

Files changed (2) +114 / -0

Enhancement (1) +108 / -0
ptrace.cDecode XNN loadmodel ioctls from traced processes +108/-0

Decode XNN loadmodel ioctls from traced processes

• Registers an ioctl-exit callback for '/dev/ive', reads the loadmodel argument and a bounded OMS prefix, and prints Preproc input geometry and layer summaries. Checks model size, counts, and descriptor bounds before reading fields.

src/ptrace.c

Documentation (1) +6 / -0
sensor-driver-extraction.mdDocument IVE XNN model decoding +6/-0

Document IVE XNN model decoding

• Adds '/dev/ive' to the trace capabilities and explains why the loadmodel OMS reveals geometry absent from on-disk weight files.

docs/sensor-driver-extraction.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Large models lose their layer list 🐞 Bug ≡ Correctness
Description
ive_xnn_loadmodel_decode copies at most 0x400 bytes of the OMS, then stops walking descriptors
when it reaches the end of that copy. A model whose descriptors extend past that point gets an
incomplete list or no input geometry; even the accepted case of 16 inputs and 16 outputs puts the
first descriptor at byte 1168.
Code

src/ptrace.c[R564-566]

+    unsigned char oms[0x400] = {0};
+    size_t n = model_size < sizeof oms ? model_size : sizeof oms;
+    if (!copy_from_process(child, model_virt, oms, n))
Evidence
The code accepts model sizes up to 8 MiB and source and destination counts up to 16 each, but limits
the copy to 1024 bytes. Its offset formula yields 1168 for 16 inputs and 16 outputs, while the
parsing loop requires the offset to be within the copied range.

src/ptrace.c[549-551]
src/ptrace.c[561-566]
src/ptrace.c[569-585]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fixed 0x400-byte OMS copy truncates descriptor parsing and can prevent the decoder from printing input geometry or the full layer list.
## Fix Focus Areas
- src/ptrace.c[564-566]
- src/ptrace.c[580-602]
## Recommended Fix
Read descriptor data as needed up to the validated model size, or allocate a bounded buffer for the model, and distinguish a truncated or invalid descriptor stream from a complete decode.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Failed model loads appear in the trace 🐞 Bug ≡ Correctness
Description
ive_xnn_ioctl_exit_cb calls the decoder for the loadmodel command without checking sysret. If
the ioctl fails but its argument and model buffer remain readable, the callback still prints the
normal loadmodel banner and any descriptors it can parse.
Code

src/ptrace.c[R607-608]

+    if (cmd == IVE_XNN_LOADMODEL)
+        ive_xnn_loadmodel_decode(proc->pid, arg);
Evidence
The ioctl exit path passes the syscall return value to the callback, but the new callback ignores
it. The decoder reads the argument and model memory and prints a loadmodel banner whenever its data
checks pass.

src/ptrace.c[1227-1235]
src/ptrace.c[605-609]
src/ptrace.c[554-578]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The loadmodel callback reports readable model data even when the ioctl returned an error.
## Fix Focus Areas
- src/ptrace.c[605-608]
## Recommended Fix
Check `sysret` before decoding a successful load, or explicitly label failed attempts rather than printing them as completed loads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/ptrace.c Outdated
Comment on lines +564 to +566
unsigned char oms[0x400] = {0};
size_t n = model_size < sizeof oms ? model_size : sizeof oms;
if (!copy_from_process(child, model_virt, oms, n))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Large models lose their layer list 🐞 Bug ≡ Correctness

ive_xnn_loadmodel_decode copies at most 0x400 bytes of the OMS, then stops walking descriptors
when it reaches the end of that copy. A model whose descriptors extend past that point gets an
incomplete list or no input geometry; even the accepted case of 16 inputs and 16 outputs puts the
first descriptor at byte 1168.
Agent Prompt
## Issue description
The fixed 0x400-byte OMS copy truncates descriptor parsing and can prevent the decoder from printing input geometry or the full layer list.
## Fix Focus Areas
- src/ptrace.c[564-566]
- src/ptrace.c[580-602]
## Recommended Fix
Read descriptor data as needed up to the validated model size, or allocate a bounded buffer for the model, and distinguish a truncated or invalid descriptor stream from a complete decode.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/ptrace.c
Comment on lines +607 to +608
if (cmd == IVE_XNN_LOADMODEL)
ive_xnn_loadmodel_decode(proc->pid, arg);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Failed model loads appear in the trace 🐞 Bug ≡ Correctness

ive_xnn_ioctl_exit_cb calls the decoder for the loadmodel command without checking sysret. If
the ioctl fails but its argument and model buffer remain readable, the callback still prints the
normal loadmodel banner and any descriptors it can parse.
Agent Prompt
## Issue description
The loadmodel callback reports readable model data even when the ioctl returned an error.
## Fix Focus Areas
- src/ptrace.c[605-608]
## Recommended Fix
Check `sysret` before decoding a successful load, or explicitly label failed attempts rather than printing them as completed loads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@widgetii
widgetii force-pushed the trace-ive-xnn branch 2 times, most recently from fdb9dff to 1ea5743 Compare September 29, 2026 19:01
@widgetii widgetii changed the title trace: decode IVE XNN loadmodel to recover on-device CNN geometry trace: decode /dev/ive IVE ops (motion pipeline + XNN model geometry) Sep 29, 2026
@widgetii

Copy link
Copy Markdown
Member Author

On-camera proof (Hi3516EV300 + IMX335, OpenIPC/majestic)

Ran the ptrace decoder against majestic with motionDetect: enabled:

ipctool trace --output=/tmp/trace.log -- /usr/bin/majestic -s

It intercepts majestic's live /dev/ive traffic — the motion-detection pipeline, decoded op by op:

ive_op(DMA);  /* cmd=0xc0684600 */    x2440
ive_op(CCL);  /* cmd=0xc0784617 */   x1078
ive_op(ADD);  /* cmd=0xc0e84614 */   x539

(DMA = frame load, ADD = background-model blend, CCL = connected-components blob labeling — matching docs/ive-registers.md's motion pipeline; majestic logged motion_detect_thread: analysis 656x480, main 2592x1944 / 4.) This confirms the mechanism end to end: ptrace catches the ioctls at the kernel boundary, which LD_PRELOAD on a vendor daemon can't.

Scope + a correction

  • Generalised the decoder to name every IVE op (not just XNN loadmodel), so a streamer's motion pipeline is visible too.
  • Fixed a wrong nr in the op table found from the live sample: QUERY is 0x2c, not 0x3c.
  • Correction to my earlier assumption: majestic's personDetect is an ARM NEON CPU path, not an IVE/XNN model — it never touches /dev/ive. So majestic exercises /dev/ive only for motion (classic IVE ops, proven above). The XNN loadmodel geometry decode is therefore still verified only by construction (against openhisilicon/kernel/ive_neo); a live XNN consumer on this silicon is XM Sofia's fd.bin, which uses the IVE XNN — capturing that under ptrace is the remaining end-to-end validation.

Camera restored to normal (majestic restarted) after the test.

V4 parts (Hi3516EV200/EV300, Goke) have no NNIE; the IVE block at /dev/ive
serves both the classic pixel ops that back motion detection (DMA/SUB/THRESH/
ADD/CCL) and, via the private mpi_ive_xnn_* API, CNN inference on the built-in
XNN unit. `ipctool trace` already ptrace-decodes i2c/spi/mipi/gpio/mtd; add
/dev/ive so its ioctls are decoded too.

- Every IVE ioctl (magic 'F') is named by its nr (DMA/CCL/ADD/... and the
  XNN_* range), so a ptraced streamer's motion pipeline is visible line by line.
- For XNN loadmodel (0xc8a04636) the model buffer is read out of the tracee and
  the OMS parsed to print the network input WxHxC (Preproc layer) and a
  conv/fc/... layer summary — the geometry a source-less vendor detector
  (e.g. XiongMai Sofia's SSH face model /usr/res/fd.bin) never stores on disk.

Buffer offsets (model_virt @ +8, size @ +16) and the OMS segment-header /
layer-descriptor layout are from OpenIPC/openhisilicon kernel/ive_neo
(ive_xnn_loadmodel) and the XNN ioctl RE; reads are bounds-gated. ptrace.c is
__arm__-only, so no change on mips/arm64.

Verified on a Hi3516EV300 + IMX335 OpenIPC camera: `ipctool trace` on majestic
with motionDetect enabled intercepts the live IVE pipeline (thousands of
ive_op(DMA)/ive_op(CCL)/ive_op(ADD) at 656x480, main/4). majestic's personDetect
is a NEON CPU path and does not touch /dev/ive, so the XNN geometry decode is
verified by construction against ive_neo and awaits a live XNN consumer (XM
Sofia) to confirm end to end.
@widgetii

Copy link
Copy Markdown
Member Author

Addressed both findings in acb42be:

  1. Large models lose their layer list — the OMS prefix copy was 0x400 (1 KiB); for 16in/16out the first descriptor is already at byte 1168, so geometry could be missed entirely. Bumped the bounded copy to 0x4000 (16 KiB) — the descriptor region (header + per-layer descriptors, which precede the multi-MB weight blob) fits for any src/dst≤16 plus a long layer list. Added explicit handling: if descriptors start past the prefix, or the layer walk runs past it, the decoder now prints (… N of M layers; descriptor stream exceeds the trace prefix) instead of silently stopping. Weights are still not copied (not needed, and copying MBs over ptrace would be slow).

  2. Failed model loads appear in the trace — ive_ioctl_exit_cb now checks sysret: a failed IVE ioctl is shown as ive_op(NAME) = <ret> [failed] and its buffer is not decoded as a loaded model. Only sysret >= 0 loadmodels are parsed.

@widgetii

Copy link
Copy Markdown
Member Author

The latest Qodo pass re-lists the same two items but against the pre-fix revision — it cites ive_xnn_ioctl_exit_cb (renamed to ive_ioctl_exit_cb in acb42be) and the old line numbers. Both are fixed in acb42be: the OMS prefix is now 0x4000 with explicit truncation reporting, and sysret < 0 loadmodels are shown as [failed] and not decoded. Merging.

@widgetii
widgetii merged commit 9cd8267 into master Sep 29, 2026
5 checks passed
@widgetii
widgetii deleted the trace-ive-xnn branch September 29, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant