Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

`ipctool` is a single static C99 binary that runs *on* an IP camera or DVR and
reports its hardware as YAML: SoC, board, sensor, flash layout, RAM, firmware,
clocks. It probes hardware directly (`/dev/mem`, I2C/SPI, `/proc`, MTD), so
almost nothing useful executes on an x86 host. The same tree also builds
`libipchw` (a small static library exposing chip/sensor identity, see
`include/ipchw.h`) and `ipcinfo` (`example/ipcinfo.c`), a minimal consumer of it.

Shipped binaries are static musl builds for arm32 (the canonical target),
mips32 and arm64, and are UPX-packed before release: the raw static-musl binary
crashes at startup on legacy kernels (Linux <= 3.18, i.e. XiongMai and old
HiSilicon SDK firmware), and the UPX stub sidesteps that.

## Build

Native build (compiles everything, runs the unit test; hardware paths return
nothing on a PC):

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/cYAML_test # exit 0 == all cases passed
```

Cross build, exactly as CI does it (PR check and release both use these
toolchains):

```sh
# arm32 (HiSilicon/Goke V1..V5, XM, SigmaStar, ...): OpenIPC hi3516cv100 musleabi toolchain
# mips32 (Ingenic): OpenIPC ingenic-t31 musl toolchain
# arm64 (Hi3519DV500 etc.): Bootlin aarch64--musl--stable toolchain
export PATH=/opt/<toolchain_dir>/bin:$PATH
cmake -S . -B build-arm -DCMAKE_C_COMPILER=arm-openipc-linux-musleabi-gcc -DCMAKE_BUILD_TYPE=Release
cmake --build build-arm
upx build-arm/ipctool # match the release artefact before testing on a camera
```

Toolchain URLs and directory names are in `.github/workflows/pr-build-check.yml`.
`build*` and `build-arm*` are gitignored.

CMake knobs worth knowing:

- `CMAKE_C_FLAGS` is hard-reset to `-std=gnu99` at the top of `CMakeLists.txt`,
so `-DCMAKE_C_FLAGS=...` on the command line does **not** survive. Add flags
in `CMakeLists.txt` (guarded by a cache option) instead.
- `-DBUILD_SHARED_LIBS=ON` is what drops the global `-static`. Needed for a
dynamic/ASAN build (use a glibc cross toolchain that ships libasan; musl
toolchains do not).
- `-DIPCHW_VENDORS=all|none|"sstar;ingenic"` selects which vendor HALs go into
`libipchw`. HiSilicon is always in. The `ipctool` executable always carries
every vendor.
- `-DONLY_LIBRARY=ON` builds just `libipchw`. `-DSKIP_VERSION=ON` skips the
git-derived `version.c` (generated by `cmake/version.cmake` on every build).
`SKIP_FUNDING` removes the sponsorship banner from `-h`.
- Release flags are `-static -s -Os -ffunction-sections -Wl,--gc-sections -Wextra`;
binary size matters, these run from tmpfs on cameras with a few MB free.

## Tests and CI

- `./build/cYAML_test`: the only unit-test binary. Covers the JSON-to-YAML
printer (`src/cjson/cYAML.c`), including UTF-8 and invalid-byte escaping.
- `tools/test_pipeline.sh`: hardware-free end-to-end check of the sensor
driver extraction pipeline (`trace_segment.py` -> `trace_to_driver.py` ->
`gcc -fsyntax-only` -> `trace_diff.py`). Needs only python3 and gcc.
- `.github/workflows/pr-build-check.yml`: every PR must build clean on arm32,
mips32 **and** arm64 and pass `test_pipeline.sh`. Keep `#ifdef __arm__` /
`__mips__` / `__aarch64__` guards consistent when touching arch-specific code.
- `.github/workflows/release.yml`: push to master publishes a rolling `latest`
prerelease; a `v*` tag publishes a real release. Assets are `ipctool`,
`ipctool-mips32`, `ipctool-arm64` plus `ipcinfo*`.
- `.github/workflows/ci-tests.yml`: pytest against real lab cameras, triggered
by `repository_dispatch` only. Not runnable locally.

Real verification of hardware code means running the binary on a camera.
Cameras usually lack `sftp-server`, so copy with `scp -O`; `/tmp` is tmpfs.

## Formatting

`.clang-format` is LLVM style with 4-space indent. `contributors.md` asks for
the hook: `./scripts/git-pre-commit-format install`. `scripts/apply-format`
reformats only the changed hunks of a diff.

## Architecture

### Detection flow

1. `src/main.c` hand-dispatches subcommands (`gpio`, `reginfo`, `i2c*`/`spi*`,
`clocks`, `trace`, ...) *before* getopt, then handles `-c/-s/-t`, then
`backup`/`upload`, and with no arguments builds the full YAML report.
2. The report is a cJSON tree: `build_yaml()` calls one `detect_*()` per
section (`detect_chip`, `detect_board`, `detect_ethernet`, `get_mtd_info`,
`detect_ram`, `detect_firmare`, `detect_sensors`, `clocks_build_json`) and
`cYAML_Print()` renders it. Empty sections are dropped. New output goes
into a cJSON object via the `ADD_PARAM*` macros in `src/tools.h`, which
assume a local named `j_inner`.
3. `getchipname()` in `src/chipid.c` is the "make sure detection ran" call and
is memoised. It installs the generic HAL (`setup_hal_fallback()`), then
`hw_detect_system()` reads the UART0 base from `/proc/iomem` to recognise
HiSilicon/Goke and XM SoCs by register base, falling back to
`generic_detect_cpu()`, which walks the `manufacturers[]` table against the
`/proc/cpuinfo` Hardware line. Each entry is `detect_fn` + `setup_hal_fn`,
compiled in per architecture and per `IPCHW_VENDOR_<NAME>` macro.

### HAL: global function pointers, not vtables

`src/hal/common.h` declares one global function pointer per hardware operation
(`open_i2c_sensor_fd`, `i2c_read_register`, `i2c_change_addr`,
`hal_temperature`, `hal_totalmem`, `hal_chip_properties`, `hal_firmware_props`,
`hal_enable_sensor_clock`, ...). `setup_hal_fallback()` fills them with generic
`/dev/i2c-N` implementations; each vendor's `setup_hal_<vendor>()` in
`src/hal/<vendor>.c` overrides what that SoC needs and sets two globals every
consumer depends on:

- `chip_generation`: the switch key used by `reginfo.c` (pinmux tables),
`clocks.c` (PLL/DDR decoders per HiSilicon family), `bootrom.c`, `ptrace.c`,
`watchdog.c`, `hal/hisi/ispreg.c`, etc. HiSilicon values are the `HISI_V*`
constants in `src/hal/hisi/hal_hisi.h`; other vendors use their own small
integers. `getchipfamily()` maps them to family names.
- `possible_i2c_addrs`: the per-vendor list of (sensor family, I2C addresses)
that sensor probing iterates.

Adding an SoC therefore means: a detect function that fills `chip_name` and
`chip_generation`, a `setup_hal_*` that installs bus access, and then the
per-generation tables in each subcommand that should support it. Adding a new
vendor also needs an entry in `IPCHW_OPTIONAL_VENDORS` in `CMakeLists.txt`, an
include in `hal/common.h`, and a guarded row in `manufacturers[]`.

Sensor I2C addresses in tables are the 8-bit (write) form; the default
`i2c_change_addr` shifts right by one for the kernel, and some HALs install
`i2c_change_plain_addr` instead. Ingenic gates the sensor clock, which is why
`hal_enable_sensor_clock` runs before *every* probe rather than once.

### Sensors

`src/sensors.c` runs `detect_possible_sensors()`: for each address in
`possible_i2c_addrs` it calls the vendor-family probe (`detect_sony_sensor`,
`detect_smartsens_sensor`, ...) which reads ID registers through the HAL
pointers and fills a `sensor_ctx_t`. Several parts share IDs or latch into
each other after WDR cycles (IMX335/IMX347/IMX415, SP2305/OV2735), so probes
carry fingerprint logic; read the surrounding comments before reordering
them. `getsensoridentity()`/`getsensorshort()` are the memoised, mutex-guarded
public entry points used by `libipchw` consumers.

### Boards

`src/boards/common.c` holds a table of `(is_<vendor>_board, gather_<vendor>_board_info)`
pairs; the first detector that matches *and* gathers successfully wins.

### `STANDALONE_LIBRARY`

`libipchw` compiles `chipid.c`, `sensors.c`, `hal/common.c`, `hal/hisi/*` and
the selected vendor HALs with `-DSTANDALONE_LIBRARY`. Anything that touches
cJSON or prints diagnostics in those shared files must sit inside
`#ifndef STANDALONE_LIBRARY`, or the library build breaks.

### Architecture-specific code

- `ipctool trace` (`src/ptrace.c`, `src/hal/hisi/ptrace.c`) is a ptrace-based
syscall decoder for camera I/O; the syscall table is hard-coded for 32-bit
ARM EABI and the command is compiled only under `__arm__`.
- Register access goes through `mem_reg()` in `src/tools.c`, which mmaps
`/dev/mem` in 64 KiB windows and falls back to `PAGE_SIZE` when the kernel
rejects the larger window. Kernels built with strict devmem filtering can
refuse some ranges entirely.
- `src/fake_symbols.c` holds empty definitions of HiSilicon SDK audio symbols,
added when the Hi3518EV100 SDK was linked in; nothing in the current tree
references them. `src/stack.c` is a stack-protector shim and is not in the
build.

### Host-side tooling

`tools/` holds the Python post-processing for `ipctool trace`
(`trace_segment.py`, `trace_to_driver.py`, `trace_diff.py`),
`capture_sensor.sh` (builds ipctool for ARM and captures a trace from a
Majestic or Sofia camera over ssh/telnet), and firmware helpers
(`upgrade_bundle.py`, `binwalk.py`, `telnet_upload.py`). The full workflow,
per-family decoder coverage and troubleshooting live in
`docs/sensor-driver-extraction.md`.