diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a8af845 --- /dev/null +++ b/CLAUDE.md @@ -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//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_` 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_()` in +`src/hal/.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__board, gather__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`.