Skip to content

Commit 24ceaea

Browse files
authored
docs: add CLAUDE.md with build, test and architecture guidance (#189)
Documents the native and cross build commands as CI runs them, the CMake knobs that matter (the hard-reset of CMAKE_C_FLAGS, BUILD_SHARED_LIBS as the way to drop -static, IPCHW_VENDORS), the two hardware-free tests, the release tagging scheme, and the detection flow: getchipname() through the UART-base and /proc/cpuinfo tables, the HAL as global function pointers, chip_generation as the dispatch key, sensor probing over possible_i2c_addrs, and the STANDALONE_LIBRARY split between ipctool and libipchw.
1 parent 76bf277 commit 24ceaea

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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

Comments
 (0)