|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## What this is |
| 6 | + |
| 7 | +`ipctool` is a single static C99 binary that runs *on* an IP camera or DVR and |
| 8 | +reports its hardware as YAML: SoC, board, sensor, flash layout, RAM, firmware, |
| 9 | +clocks. It probes hardware directly (`/dev/mem`, I2C/SPI, `/proc`, MTD), so |
| 10 | +almost nothing useful executes on an x86 host. The same tree also builds |
| 11 | +`libipchw` (a small static library exposing chip/sensor identity, see |
| 12 | +`include/ipchw.h`) and `ipcinfo` (`example/ipcinfo.c`), a minimal consumer of it. |
| 13 | + |
| 14 | +Shipped binaries are static musl builds for arm32 (the canonical target), |
| 15 | +mips32 and arm64, and are UPX-packed before release: the raw static-musl binary |
| 16 | +crashes at startup on legacy kernels (Linux <= 3.18, i.e. XiongMai and old |
| 17 | +HiSilicon SDK firmware), and the UPX stub sidesteps that. |
| 18 | + |
| 19 | +## Build |
| 20 | + |
| 21 | +Native build (compiles everything, runs the unit test; hardware paths return |
| 22 | +nothing on a PC): |
| 23 | + |
| 24 | +```sh |
| 25 | +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release |
| 26 | +cmake --build build -j |
| 27 | +./build/cYAML_test # exit 0 == all cases passed |
| 28 | +``` |
| 29 | + |
| 30 | +Cross build, exactly as CI does it (PR check and release both use these |
| 31 | +toolchains): |
| 32 | + |
| 33 | +```sh |
| 34 | +# arm32 (HiSilicon/Goke V1..V5, XM, SigmaStar, ...): OpenIPC hi3516cv100 musleabi toolchain |
| 35 | +# mips32 (Ingenic): OpenIPC ingenic-t31 musl toolchain |
| 36 | +# arm64 (Hi3519DV500 etc.): Bootlin aarch64--musl--stable toolchain |
| 37 | +export PATH=/opt/<toolchain_dir>/bin:$PATH |
| 38 | +cmake -S . -B build-arm -DCMAKE_C_COMPILER=arm-openipc-linux-musleabi-gcc -DCMAKE_BUILD_TYPE=Release |
| 39 | +cmake --build build-arm |
| 40 | +upx build-arm/ipctool # match the release artefact before testing on a camera |
| 41 | +``` |
| 42 | + |
| 43 | +Toolchain URLs and directory names are in `.github/workflows/pr-build-check.yml`. |
| 44 | +`build*` and `build-arm*` are gitignored. |
| 45 | + |
| 46 | +CMake knobs worth knowing: |
| 47 | + |
| 48 | +- `CMAKE_C_FLAGS` is hard-reset to `-std=gnu99` at the top of `CMakeLists.txt`, |
| 49 | + so `-DCMAKE_C_FLAGS=...` on the command line does **not** survive. Add flags |
| 50 | + in `CMakeLists.txt` (guarded by a cache option) instead. |
| 51 | +- `-DBUILD_SHARED_LIBS=ON` is what drops the global `-static`. Needed for a |
| 52 | + dynamic/ASAN build (use a glibc cross toolchain that ships libasan; musl |
| 53 | + toolchains do not). |
| 54 | +- `-DIPCHW_VENDORS=all|none|"sstar;ingenic"` selects which vendor HALs go into |
| 55 | + `libipchw`. HiSilicon is always in. The `ipctool` executable always carries |
| 56 | + every vendor. |
| 57 | +- `-DONLY_LIBRARY=ON` builds just `libipchw`. `-DSKIP_VERSION=ON` skips the |
| 58 | + git-derived `version.c` (generated by `cmake/version.cmake` on every build). |
| 59 | + `SKIP_FUNDING` removes the sponsorship banner from `-h`. |
| 60 | +- Release flags are `-static -s -Os -ffunction-sections -Wl,--gc-sections -Wextra`; |
| 61 | + binary size matters, these run from tmpfs on cameras with a few MB free. |
| 62 | + |
| 63 | +## Tests and CI |
| 64 | + |
| 65 | +- `./build/cYAML_test`: the only unit-test binary. Covers the JSON-to-YAML |
| 66 | + printer (`src/cjson/cYAML.c`), including UTF-8 and invalid-byte escaping. |
| 67 | +- `tools/test_pipeline.sh`: hardware-free end-to-end check of the sensor |
| 68 | + driver extraction pipeline (`trace_segment.py` -> `trace_to_driver.py` -> |
| 69 | + `gcc -fsyntax-only` -> `trace_diff.py`). Needs only python3 and gcc. |
| 70 | +- `.github/workflows/pr-build-check.yml`: every PR must build clean on arm32, |
| 71 | + mips32 **and** arm64 and pass `test_pipeline.sh`. Keep `#ifdef __arm__` / |
| 72 | + `__mips__` / `__aarch64__` guards consistent when touching arch-specific code. |
| 73 | +- `.github/workflows/release.yml`: push to master publishes a rolling `latest` |
| 74 | + prerelease; a `v*` tag publishes a real release. Assets are `ipctool`, |
| 75 | + `ipctool-mips32`, `ipctool-arm64` plus `ipcinfo*`. |
| 76 | +- `.github/workflows/ci-tests.yml`: pytest against real lab cameras, triggered |
| 77 | + by `repository_dispatch` only. Not runnable locally. |
| 78 | + |
| 79 | +Real verification of hardware code means running the binary on a camera. |
| 80 | +Cameras usually lack `sftp-server`, so copy with `scp -O`; `/tmp` is tmpfs. |
| 81 | + |
| 82 | +## Formatting |
| 83 | + |
| 84 | +`.clang-format` is LLVM style with 4-space indent. `contributors.md` asks for |
| 85 | +the hook: `./scripts/git-pre-commit-format install`. `scripts/apply-format` |
| 86 | +reformats only the changed hunks of a diff. |
| 87 | + |
| 88 | +## Architecture |
| 89 | + |
| 90 | +### Detection flow |
| 91 | + |
| 92 | +1. `src/main.c` hand-dispatches subcommands (`gpio`, `reginfo`, `i2c*`/`spi*`, |
| 93 | + `clocks`, `trace`, ...) *before* getopt, then handles `-c/-s/-t`, then |
| 94 | + `backup`/`upload`, and with no arguments builds the full YAML report. |
| 95 | +2. The report is a cJSON tree: `build_yaml()` calls one `detect_*()` per |
| 96 | + section (`detect_chip`, `detect_board`, `detect_ethernet`, `get_mtd_info`, |
| 97 | + `detect_ram`, `detect_firmare`, `detect_sensors`, `clocks_build_json`) and |
| 98 | + `cYAML_Print()` renders it. Empty sections are dropped. New output goes |
| 99 | + into a cJSON object via the `ADD_PARAM*` macros in `src/tools.h`, which |
| 100 | + assume a local named `j_inner`. |
| 101 | +3. `getchipname()` in `src/chipid.c` is the "make sure detection ran" call and |
| 102 | + is memoised. It installs the generic HAL (`setup_hal_fallback()`), then |
| 103 | + `hw_detect_system()` reads the UART0 base from `/proc/iomem` to recognise |
| 104 | + HiSilicon/Goke and XM SoCs by register base, falling back to |
| 105 | + `generic_detect_cpu()`, which walks the `manufacturers[]` table against the |
| 106 | + `/proc/cpuinfo` Hardware line. Each entry is `detect_fn` + `setup_hal_fn`, |
| 107 | + compiled in per architecture and per `IPCHW_VENDOR_<NAME>` macro. |
| 108 | + |
| 109 | +### HAL: global function pointers, not vtables |
| 110 | + |
| 111 | +`src/hal/common.h` declares one global function pointer per hardware operation |
| 112 | +(`open_i2c_sensor_fd`, `i2c_read_register`, `i2c_change_addr`, |
| 113 | +`hal_temperature`, `hal_totalmem`, `hal_chip_properties`, `hal_firmware_props`, |
| 114 | +`hal_enable_sensor_clock`, ...). `setup_hal_fallback()` fills them with generic |
| 115 | +`/dev/i2c-N` implementations; each vendor's `setup_hal_<vendor>()` in |
| 116 | +`src/hal/<vendor>.c` overrides what that SoC needs and sets two globals every |
| 117 | +consumer depends on: |
| 118 | + |
| 119 | +- `chip_generation`: the switch key used by `reginfo.c` (pinmux tables), |
| 120 | + `clocks.c` (PLL/DDR decoders per HiSilicon family), `bootrom.c`, `ptrace.c`, |
| 121 | + `watchdog.c`, `hal/hisi/ispreg.c`, etc. HiSilicon values are the `HISI_V*` |
| 122 | + constants in `src/hal/hisi/hal_hisi.h`; other vendors use their own small |
| 123 | + integers. `getchipfamily()` maps them to family names. |
| 124 | +- `possible_i2c_addrs`: the per-vendor list of (sensor family, I2C addresses) |
| 125 | + that sensor probing iterates. |
| 126 | + |
| 127 | +Adding an SoC therefore means: a detect function that fills `chip_name` and |
| 128 | +`chip_generation`, a `setup_hal_*` that installs bus access, and then the |
| 129 | +per-generation tables in each subcommand that should support it. Adding a new |
| 130 | +vendor also needs an entry in `IPCHW_OPTIONAL_VENDORS` in `CMakeLists.txt`, an |
| 131 | +include in `hal/common.h`, and a guarded row in `manufacturers[]`. |
| 132 | + |
| 133 | +Sensor I2C addresses in tables are the 8-bit (write) form; the default |
| 134 | +`i2c_change_addr` shifts right by one for the kernel, and some HALs install |
| 135 | +`i2c_change_plain_addr` instead. Ingenic gates the sensor clock, which is why |
| 136 | +`hal_enable_sensor_clock` runs before *every* probe rather than once. |
| 137 | + |
| 138 | +### Sensors |
| 139 | + |
| 140 | +`src/sensors.c` runs `detect_possible_sensors()`: for each address in |
| 141 | +`possible_i2c_addrs` it calls the vendor-family probe (`detect_sony_sensor`, |
| 142 | +`detect_smartsens_sensor`, ...) which reads ID registers through the HAL |
| 143 | +pointers and fills a `sensor_ctx_t`. Several parts share IDs or latch into |
| 144 | +each other after WDR cycles (IMX335/IMX347/IMX415, SP2305/OV2735), so probes |
| 145 | +carry fingerprint logic; read the surrounding comments before reordering |
| 146 | +them. `getsensoridentity()`/`getsensorshort()` are the memoised, mutex-guarded |
| 147 | +public entry points used by `libipchw` consumers. |
| 148 | + |
| 149 | +### Boards |
| 150 | + |
| 151 | +`src/boards/common.c` holds a table of `(is_<vendor>_board, gather_<vendor>_board_info)` |
| 152 | +pairs; the first detector that matches *and* gathers successfully wins. |
| 153 | + |
| 154 | +### `STANDALONE_LIBRARY` |
| 155 | + |
| 156 | +`libipchw` compiles `chipid.c`, `sensors.c`, `hal/common.c`, `hal/hisi/*` and |
| 157 | +the selected vendor HALs with `-DSTANDALONE_LIBRARY`. Anything that touches |
| 158 | +cJSON or prints diagnostics in those shared files must sit inside |
| 159 | +`#ifndef STANDALONE_LIBRARY`, or the library build breaks. |
| 160 | + |
| 161 | +### Architecture-specific code |
| 162 | + |
| 163 | +- `ipctool trace` (`src/ptrace.c`, `src/hal/hisi/ptrace.c`) is a ptrace-based |
| 164 | + syscall decoder for camera I/O; the syscall table is hard-coded for 32-bit |
| 165 | + ARM EABI and the command is compiled only under `__arm__`. |
| 166 | +- Register access goes through `mem_reg()` in `src/tools.c`, which mmaps |
| 167 | + `/dev/mem` in 64 KiB windows and falls back to `PAGE_SIZE` when the kernel |
| 168 | + rejects the larger window. Kernels built with strict devmem filtering can |
| 169 | + refuse some ranges entirely. |
| 170 | +- `src/fake_symbols.c` holds empty definitions of HiSilicon SDK audio symbols, |
| 171 | + added when the Hi3518EV100 SDK was linked in; nothing in the current tree |
| 172 | + references them. `src/stack.c` is a stack-protector shim and is not in the |
| 173 | + build. |
| 174 | + |
| 175 | +### Host-side tooling |
| 176 | + |
| 177 | +`tools/` holds the Python post-processing for `ipctool trace` |
| 178 | +(`trace_segment.py`, `trace_to_driver.py`, `trace_diff.py`), |
| 179 | +`capture_sensor.sh` (builds ipctool for ARM and captures a trace from a |
| 180 | +Majestic or Sofia camera over ssh/telnet), and firmware helpers |
| 181 | +(`upgrade_bundle.py`, `binwalk.py`, `telnet_upload.py`). The full workflow, |
| 182 | +per-family decoder coverage and troubleshooting live in |
| 183 | +`docs/sensor-driver-extraction.md`. |
0 commit comments