Skip to content

Cross-platform: Sony IMX init pattern + gen2 LE write decoder + V2 ioctls#152

Merged
widgetii merged 2 commits intomasterfrom
feat/sensor-family-init-patterns
May 3, 2026
Merged

Cross-platform: Sony IMX init pattern + gen2 LE write decoder + V2 ioctls#152
widgetii merged 2 commits intomasterfrom
feat/sensor-family-init-patterns

Conversation

@widgetii
Copy link
Copy Markdown
Member

@widgetii widgetii commented May 3, 2026

Cross-platform sweep of Majestic captures on three sibling cameras (hi3516cv300+IMX291, hi3516av200+IMX385, hi3518ev200+jxf22) revealed several distinct platform-handling issues. All five fixes are useful independently; the open question on jxf22 specifically is documented as a diagnostic checklist.

Fixes

1. Segmenter: detect sensor family by init pattern

Was hardcoded for SmartSens's 0x100=0 ... 0x100=1 cycle. Sony IMX uses the reverse polarity on a different register: 0x3000=1 (standby) → 0x3000=0 (stream-on). Refactored to a small per-family table:

INIT_PATTERNS = [
    (\"smartsens\", 0x100,  0, 1),  # SC2315E, SC2335, SC*
    (\"sony_imx\",  0x3000, 1, 0),  # IMX291, IMX385, IMX307, ...
]

2. Decoder: gen2 little-endian writes

hisi_gen2_sensor_write_register (V1/V2/V2A) packs reg_addr little-endian via write(fd, buf, reg_width + data_width); hisi_sensor_write_register (V3+) packs big-endian. i2c_write_exit_cb was hardcoded for V3+'s 2+1-byte big-endian shape, so any V2 driver writing 1+1 bytes (e.g. JXF22-class 8-bit-reg sensors) or any V2 driver writing 2+1 LE produced wrong values or was silently dropped. Now handles 1+1 / 2+1 / 2+2 byte forms with chip-family-aware endianness.

3. Decoder: V2 I2C_SLAVE_FORCE handler

New hisi_gen2_ioctl_exit_cb decodes I2C_SLAVE_FORCE on V2/V2A so sensor_i2c_change_addr lines surface (V3+ had this via hisi_i2c_read_exit_cb).

4. Decoder: writev support

uClibc on some V1/V2 firmwares maps the libc write() function to __NR_writev (146) with a single iovec instead of __NR_write (4). Without this handler, sensor I/O on those targets is invisible. New syscall_writev_exit reads the iovec from the tracee, caps iovcnt at 8, and forwards to the same fd write_exit callback as plain write() with iov[0].base/len. Multi-iovec stdio writes (puts/printf) flow through but the i2c decoder's nbyte 2..4 check filters them.

5. CLONE_FILES sibling fd-state broadcast

Threads cloned with CLONE_FILES share the kernel fd table — opening in one thread makes the fd usable in all peers. ipctool tracked fds per process_t, so an open in the parent left peer->fds[N] empty and any peer write on fdN silently dropped to no callback. New broadcast_fd_open / broadcast_fd_close mirror open/close events to every tracked process, restoring the invariant.

6. PTRACE_O_TRACEFORK / TRACEVFORK

Added alongside TRACECLONE for defensive coverage of streamers that spawn workers via genuine fork(). Most use threads via CLONE; this is just-in-case.

Cross-platform validation

platform sensor matched pattern init events scaffold compiles?
hi3516cv300 IMX291 sony_imx 65 ✓ — full LVDS attr struct in #if 0 block
hi3516av200 IMX385 sony_imx 54 ✓ — full MIPI attr struct in #if 0 block
hi3516ev200 SC2315E smartsens 173 ✓ — diff vs widgetii/smart_sc2315e unchanged at 100/100/100% (regression)
hi3518ev200 jxf22 (none) 0 scaffold not emitted — diagnostic checklist below

/bin/echo on hi3518ev200 traces correctly (syscall=4 fires) — the trace path itself is sound on this kernel.

hi3518ev200 + jxf22 status

Source code at OpenIPC/glutinium/hi35xx_sensor_jxf22 confirms the driver uses write(g_fd, buf, idx) after the I2C_SLAVE_FORCE / I2C_16BIT_REG / I2C_16BIT_DATA setup. ipctool now decodes that path correctly. But on the specific test camera I have, the trace still shows zero sensor_write_register lines despite the streamer reporting init success. Best reading: the .so on this camera takes a path our dispatch doesn't see — I observe /dev/sys (char major 218 minor 8, HiSilicon-specific) being opened by Majestic at runtime, and our trace ends shortly after the i2c-N banner. Diagnostic checklist now in docs/sensor-driver-extraction.md so the next researcher recognises the failure mode quickly.

Test plan

  • tools/test_pipeline.sh extended with a Sony fixture (asserts init_pattern == \"sony_imx\" and successful scaffold generation)
  • All three working captures (cv300/av200/ev200) regenerate clean scaffolds
  • SC2315E regression diff against widgetii/smart_sc2315e unchanged at 100/100/100%
  • CI test-extraction-pipeline passes both old and new fixture assertions
  • No-pattern fallback (jxf22) gracefully emits pre_sensor only

🤖 Generated with Claude Code

Cross-platform sweep (sibling Majestic captures on hi3516cv300+IMX291
and hi3516av200+IMX385) showed the segmenter always falling back to
pre_sensor-only. Root cause: find_init_bounds() was hardcoded to look
for SmartSens's `0x100=0 ... 0x100=1` cycle. Sony IMX uses the reverse
polarity on a different register: `0x3000=1` (standby) followed by
`0x3000=0` (stream-on).

Refactored to a small table of (family, reg, init_val, stream_val)
patterns. find_init_bounds() tries each in order, returns the first
that finds both endpoints, and reports the matched pattern. Mode-switch
detection takes the same pattern (so a Sony sensor that re-cycles
0x3000 mid-trace correctly registers as a mode_switch_N).

Two families seeded:
* smartsens - 0x100=0/0x100=1 (SC2315E, SC2335, SC*)
* sony_imx  - 0x3000=1/0x3000=0 (IMX291, IMX385, IMX307, ...)

Validated end-to-end on real captures from three sibling Majestic
cameras:
* hi3516cv300 + IMX291: 65 init writes, sony_imx detected, scaffold
  compiles with full LVDS attr struct dump
* hi3516av200 + IMX385: 54 init writes, sony_imx detected, scaffold
  compiles with full MIPI attr struct dump
* hi3516ev200 + SC2315E: 173 init events, smartsens detected, diff
  vs widgetii/smart_sc2315e unchanged at 100/100/100% (regression)

Pipeline test extended with a Sony fixture asserting both pattern
detection and successful scaffold generation. Docs updated with the
"Sensor-family init patterns" section listing the table and noting
how to add new families.

Also documented a separate platform limitation discovered in the
sweep: hi3518ev200 + jxf22 produces an empty trace because
libsns_jxf22.so writes the SoC I2C controller via mmap'd /dev/mem
instead of /dev/i2c-X. ipctool's ptrace can't decode that path;
flagged in docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@widgetii widgetii changed the title Detect sensor family by init pattern, support Sony IMX Cross-platform: Sony IMX init pattern + gen2 LE write decoder + V2 ioctls May 3, 2026
and CLONE_FILES sibling fd state

Cross-platform sweep flagged hi3518ev200 + jxf22 as producing an empty
trace. Multiple decoder issues, all separately useful:

* i2c_write_exit_cb was hardcoded for V3+'s 2+1-byte big-endian shape.
  hisi_gen2_sensor_write_register on V1/V2/V2A packs reg_addr little-
  endian and supports 1+1 / 2+1 / 2+2 byte forms (set via I2C_16BIT_REG
  / I2C_16BIT_DATA ioctls). Now infers widths from nbyte and picks
  endianness from chip_generation, matching both
  hisi_gen2_sensor_write_register and hisi_sensor_write_register in
  hal_hisi.c.

* New hisi_gen2_ioctl_exit_cb decodes I2C_SLAVE_FORCE on V2/V2A so
  `sensor_i2c_change_addr` lines surface there too (V3+ already had
  this via hisi_i2c_read_exit_cb).

* uClibc on some V1/V2 firmwares maps the libc write() function to
  __NR_writev (146) with a single iovec instead of __NR_write (4),
  silently dropping any sensor I/O from our previous decoder. New
  syscall_writev_exit reads the iovec from the tracee, validates
  iovcnt (cap 8), and forwards to the same write_exit callback as
  plain write() with iov[0].base/len. Multi-iovec stdio writes
  (puts/printf) still flow through but the i2c decoder's nbyte 2..4
  sanity check filters them out.

* PTRACE_O_TRACEFORK / TRACEVFORK added alongside TRACECLONE for
  defensive coverage of streamers that spawn workers via genuine
  fork() (most use threads via CLONE; this is just-in-case).

* CLONE_FILES siblings share the kernel fd table; ipctool tracked
  fds per process_t, so an open in the parent left peer->fds[N]
  empty and any peer write on fdN dropped to no callback. New
  broadcast_fd_open / broadcast_fd_close mirror open/close events
  to every tracked process_t, restoring the invariant.

Verified end-to-end:
* SC2315E + Majestic regression: still 100/100/100% diff against
  widgetii/smart_sc2315e.
* IMX291 (cv300, sony_imx pattern), IMX385 (av200, sony_imx pattern):
  scaffolds still compile.
* /bin/echo on hi3518ev200 traces correctly (sanity check that the
  syscall path works on V2 kernel).

The hi3518ev200 + jxf22 specific case empirically still produces
an empty trace despite the published source using write() - the
.so on that camera appears to take a path our dispatch doesn't see.
Documented the diagnostic checklist (compare /proc/<pid>/fd with
the trace's i2c-N banner; look for unfamiliar /dev/* devices) so
the next researcher recognises the failure mode quickly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@widgetii widgetii force-pushed the feat/sensor-family-init-patterns branch from c193e7a to a4b7709 Compare May 3, 2026 17:56
@widgetii widgetii merged commit 87dc705 into master May 3, 2026
3 checks passed
@widgetii widgetii deleted the feat/sensor-family-init-patterns branch May 3, 2026 18:00
widgetii added a commit that referenced this pull request May 3, 2026
…race

While investigating an empty trace from libsns_jxf22.so on hi3518ev200,
two real bugs in the wait loop turned up that are worth fixing
independently of jxf22's specific issue.

* Signal forwarding. The loop ended every iteration with
  ptrace(PTRACE_SYSCALL, pid, 1, NULL). The fourth arg is the signal
  to inject when resuming the tracee, and NULL meant "drop the
  signal entirely". So if a child stopped on a real signal (anything
  other than SIGTRAP - SIGCHLD, SIGRT*, SIGUSR*, etc.), ipctool
  swallowed it instead of forwarding it. The HiSilicon SDK uses
  realtime signals heavily for video pipeline coordination; dropping
  them under trace can deadlock a streamer.

  Now: if the stop signal is SIGTRAP it stays at 0 (nothing to
  forward); if it's a genuine signal-delivery stop, the original
  signal gets re-injected when the tracee resumes.

* PTRACE_EVENT_FORK / PTRACE_EVENT_VFORK weren't handled. #152 added
  the matching PTRACE_O_TRACEFORK/VFORK options but the wait loop
  only matched PTRACE_EVENT_CLONE. So a forked child fired
  PTRACE_EVENT_FORK in its parent (ignored), then on its first
  syscall stop the lookup against `pids` returned NULL and we hit
  the "BAD lookup" branch which `break`'d out of the wait loop -
  killing the whole trace.

  Now: the same CLONE handling block matches all three events
  (CLONE | FORK | VFORK). Plus the BAD-lookup case no longer
  breaks - it just continues, since under TRACEFORK there's a brief
  window where a child can hit a syscall stop before its parent's
  EVENT_FORK arrives and we register it.

* Exit handling for unknown PIDs no longer breaks the loop either.
  If a child exits before we observed its creation event, we just
  skip the bookkeeping and keep tracing the rest.

tools/sns_init_probe.c added: a tiny dlopen+dlsym wrapper that
loads a libsns_*.so directly and calls its sensor init function.
Lets a future researcher exercise sensor I/O paths in isolation
from the streamer (handy for narrowing down "empty trace" issues
to the .so vs the surrounding application). Build instructions in
the file header.

Verified:
* SC2315E + Majestic regression: 100/100/100% diff against
  widgetii/smart_sc2315e unchanged.
* hi3518ev200 + jxf22 still produces an empty trace despite the
  signal/fork fixes. Strace confirms the streamer DOES make 79
  write() calls of 2 bytes to a /dev/i2c-0 fd (opened TWICE: first
  at fd 18 by the probe code, then a second open at fd 25 by
  libsns_jxf22.so itself - that second open is what we're missing).
  The bug is somewhere else in the trace path on this specific
  camera/build combo; tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
widgetii added a commit that referenced this pull request May 3, 2026
…race (#153)

While investigating an empty trace from libsns_jxf22.so on hi3518ev200,
two real bugs in the wait loop turned up that are worth fixing
independently of jxf22's specific issue.

* Signal forwarding. The loop ended every iteration with
  ptrace(PTRACE_SYSCALL, pid, 1, NULL). The fourth arg is the signal
  to inject when resuming the tracee, and NULL meant "drop the
  signal entirely". So if a child stopped on a real signal (anything
  other than SIGTRAP - SIGCHLD, SIGRT*, SIGUSR*, etc.), ipctool
  swallowed it instead of forwarding it. The HiSilicon SDK uses
  realtime signals heavily for video pipeline coordination; dropping
  them under trace can deadlock a streamer.

  Now: if the stop signal is SIGTRAP it stays at 0 (nothing to
  forward); if it's a genuine signal-delivery stop, the original
  signal gets re-injected when the tracee resumes.

* PTRACE_EVENT_FORK / PTRACE_EVENT_VFORK weren't handled. #152 added
  the matching PTRACE_O_TRACEFORK/VFORK options but the wait loop
  only matched PTRACE_EVENT_CLONE. So a forked child fired
  PTRACE_EVENT_FORK in its parent (ignored), then on its first
  syscall stop the lookup against `pids` returned NULL and we hit
  the "BAD lookup" branch which `break`'d out of the wait loop -
  killing the whole trace.

  Now: the same CLONE handling block matches all three events
  (CLONE | FORK | VFORK). Plus the BAD-lookup case no longer
  breaks - it just continues, since under TRACEFORK there's a brief
  window where a child can hit a syscall stop before its parent's
  EVENT_FORK arrives and we register it.

* Exit handling for unknown PIDs no longer breaks the loop either.
  If a child exits before we observed its creation event, we just
  skip the bookkeeping and keep tracing the rest.

tools/sns_init_probe.c added: a tiny dlopen+dlsym wrapper that
loads a libsns_*.so directly and calls its sensor init function.
Lets a future researcher exercise sensor I/O paths in isolation
from the streamer (handy for narrowing down "empty trace" issues
to the .so vs the surrounding application). Build instructions in
the file header.

Verified:
* SC2315E + Majestic regression: 100/100/100% diff against
  widgetii/smart_sc2315e unchanged.
* hi3518ev200 + jxf22 still produces an empty trace despite the
  signal/fork fixes. Strace confirms the streamer DOES make 79
  write() calls of 2 bytes to a /dev/i2c-0 fd (opened TWICE: first
  at fd 18 by the probe code, then a second open at fd 25 by
  libsns_jxf22.so itself - that second open is what we're missing).
  The bug is somewhere else in the trace path on this specific
  camera/build combo; tracked separately.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
widgetii added a commit that referenced this pull request May 4, 2026
#156)

Documents what's actually needed to capture sensor-mode reconfiguration
on a XiongMai Sofia build, after we walked through the false leads:

* Sofia's runtime DVR-IP `BroadTrends.AutoGain` knob does NOT
  reconfigure the sensor. The path is `Public_LinearWDR_Switch` ->
  `LibXmCap_System_switchWdrMode` -> `XmCap_IspVi_switchWdrMode`,
  which calls `HI_MPI_ISP_GetPubAttr`, optionally does a VI
  unbind/rebind, and persists the new mode to
  `/mnt/mtd/Config/wdrmode.dat` via `libdvr.so`'s `SetWdrMode`
  (a thin `fopen + fwrite`). It does not call
  `HI_MPI_ISP_SetWDRMode` and does not invoke any sensor-driver
  code. Confirmed empirically on two boards: zero `0x100` (SmartSens)
  / `0x3000` (Sony) cycles after `AutoGain` toggle.

* The actual sensor-side mode dispatch happens at Sofia startup:
  Sofia reads wdrmode.dat (4-byte LE int) and calls
  `cmos_set_image_mode(mode)` inside the per-sensor driver. For
  Sony IMX29x the case-set is 0=linear, 2=line-WDR (DOL),
  3=half-frame WDR, 4=full-frame WDR.

* Procedure: write the desired mode to wdrmode.dat with `printf
  '\x02\x00\x00\x00' > ...`, kill Sofia, restart under the bind-mount
  trace wrapper. Sofia at startup picks up the new mode and the trace
  captures the alternative init register sequence.

* Worked example: Hi3516CV300 + IMX291 + Sofia. Mode 0 (linear)
  produced 70 writes; mode 2 (line-WDR) produced 140 writes with 17
  WDR-only register values (`0x3007=0x00`, `0x3009=0x02`,
  `0x3018=0x6D`, `0x3046=0xE1`, etc. - matching Sony's IMX29x DOL
  configuration). `trace_segment.py`'s existing `find_mode_switches`
  heuristic catches the two-pass init as `init` + `mode_switch_1`
  with no changes; `trace_to_driver.py` emits the WDR body as
  `<sensor>_set_mode_1`.

* Diagnostic: which sensors in your Sofia binary have WDR firmware
  vs being linear-only? Grep the binary for `_init  ` debug markers
  and `LINE Init OK` banners. A sensor with both has multi-mode
  drivers; one with only `_init` is linear-only and `wdrmode.dat`
  changes are no-ops at the sensor level (the file still gets
  persisted, but no sensor-driver code path responds).

Also generalises the existing "Segmenter heuristic" note from the
hardcoded SmartSens `0x100` pair to "the stream-control register pair
the matched `init_pattern` used", with explicit Sony IMX and SOI/JX
examples - reflects the actual code in `trace_segment.py` post-#152.

No tooling changes; CI `test-extraction-pipeline` passes unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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