Skip to content

Latest commit

 

History

History
250 lines (199 loc) · 10.7 KB

DETAILS.md

File metadata and controls

250 lines (199 loc) · 10.7 KB

peek-carthing — details

The nitty gritty. See README.md for the one-page intro.

Contents

Building from source

For the kmod you need a kernel build tree matching the running kernel.

Option A: Joey's spotify-kernel (recommended)

JoeyEamigh/spotify-kernel ships the Car Thing's kernel-common source with superbird_defconfig and a crosstool-NG-built GCC 6 toolchain that matches the original stock build.

git clone --depth 1 https://github.com/JoeyEamigh/spotify-kernel.git
cd spotify-kernel
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     HOSTCFLAGS="-fcommon -Wno-error" \
     KCFLAGS="-Wno-error" \
     superbird_defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     HOSTCFLAGS="-fcommon -Wno-error" \
     KCFLAGS="-Wno-error" \
     -j$(nproc) vmlinux modules
# vmlinux link may fail at the very end (undefined of_resolve_phandles
# in aml_bl_load_overlay) — that's harmless. Module.symvers is what
# matters and it's already been generated by modpost.

Then build both kmods against it:

cd /path/to/peek-carthing
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     KDIR=/path/to/spotify-kernel
aarch64-linux-gnu-strip --strip-unneeded peek_mmio.ko peek_mmio_slim.ko

The default Makefile has obj-m := peek_mmio.o peek_mmio_slim.o, so one invocation produces both .ko files.

Option B: any other matching kernel source

spsgsb/kernel-common works too, with the same flags. The key requirement is that Module.symvers exists in the kernel tree before you build the module (run a full make modules once).

Patch the module_layout CRC to stock's value

This is the trick that makes the kmod loadable on the stock kernel: out-of-tree builds produce a module_layout CRC that doesn't match the stock kernel's, but we can post-link-patch the __versions section to use stock's known-good CRC (harvested from mali.ko):

import struct, subprocess

KO = "peek_mmio.ko"   # or "peek_mmio_slim.ko"
subprocess.run(["aarch64-linux-gnu-objcopy", "--dump-section",
                "__versions=/tmp/v.bin", KO], check=True)
data = bytearray(open("/tmp/v.bin", "rb").read())
for i in range(0, len(data), 64):
    name = bytes(data[i+8:i+64]).split(b"\x00", 1)[0].decode("ascii", "replace")
    if name == "module_layout":
        struct.pack_into("<I", data, i, 0x8b79d6c0)  # stock kernel CRC
open("/tmp/v.bin", "wb").write(bytes(data))
subprocess.run(["aarch64-linux-gnu-objcopy", "--update-section",
                "__versions=/tmp/v.bin", KO], check=True)

This step is not needed if you're loading the module on a kernel you built yourself (CRCs will already match).

Running it manually

adb push peek_mmio.ko    /tmp/peek_mmio.ko
adb push peek-collect.py /tmp/peek-collect.py
adb shell python3 /tmp/peek-collect.py > hwinfo.json

If you want just the raw dmesg dump (no JSON aggregation):

adb shell insmod /tmp/peek_mmio.ko
adb shell 'dmesg | grep peek_mmio:'

The module returns -EBUSY from init by design so it doesn't stay loaded — it's a one-shot dumper. The cost: lsmod will show it as [permanent] Loading until the next reboot, and re-running insmod gets "Device or resource busy". The python aggregators handle this automatically (they re-parse dmesg from the previous run).

What's in each MMIO section

The full kmod dumps everything below; the slim kmod skips the rows marked (slim drops).

section addr size what's inside
HHI clock controller 0xff63c000 1 KiB All G12A PLLs, video clock dividers, MIPI analog DPHY bias at +0x000..0x008 (MIPI_CNTL0/1/2), tsensor clk, MIPI DSI phy clk
MHU mailbox (slim drops) 0xff63c400 128 B SCP coprocessor IPC (usually idle = all zero)
DMC / DDR controller (slim drops) 0xff638000 512 B Per-byte-lane DLL training at +0x180..+0x1f0 (different across RAM vendors)
Mali Bifrost GPU 0xffe40000 256 B GPU_ID, shader count, power state. Decoded → product name
eMMC controller 0xffe07000 256 B SD_EMMC_CLOCK @ +0x00, SD_EMMC_START/CFG/STATUS/IRQ_EN @ +0x40..+0x4c — live bus speed, mode, bus width
Audio bus (slim drops) 0xff642000 512 B Live PDM mic TODDR ringbuffer addresses in DRAM — write pointer advances real-time
t9015 audio codec (slim drops) 0xff632000 256 B Internal analog DAC (unused on Car Thing — no jack)
PWM AB 0xffd1b000 64 B Backlight PWM channel A
PWM EF (slim drops) 0xffd19000 64 B Unused PWM block
AO bus PWM (slim drops) 0xff802000 64 B Separate AO-domain PWM block
VPU near-ENCL 0xff907000 1.25 KiB Display timing: htotal/vtotal/hsync/vsync, framebuffer pointer, OSD compositor state
VPU VIU_OSD1 0xff906840 576 B OSD1 CTRL_STAT + MATRIX area (word 0x1a10..0x1a9f)
DSI host 0xffd07000 1 KiB DWC DSI v1.21 host + Amlogic TOP wrapper
MIPI digital DPHY 0xffd44000 256 B (usually all zero — clocking is HHI-side on G12A)
Pinmux / GPIO state 0xff634480, 0xff634440 512 B each Pad function select + live GPIO levels
SARADC 0xff809000 64 B Raw ADC regs (decoded values via IIO sysfs)
USB2 PHY21 (slim drops) 0xff63a000 small USB phy state + tuning
USB2 PHY (slim drops) 0xffe09000 small USB phy state + tuning
DWC3 USB3 controller (slim drops) 0xff500000 256 B USB3 events, GSTS, link status
Reset controller (slim drops) 0xff634404 64 B Per-peripheral reset state
Tsensors (slim drops) inline 16 B PLL + DDR thermal sensor raw cfg/stat

The slim kmod's section labels and dmesg prefix (peek_mmio:) are identical to the full kmod's, so the parsers in either python script handle either kmod transparently.

Slim variant deep dive

What it drops

Compared to the full kmod, peek_mmio_slim.ko drops MHU, DMC / DDR controller, audio bus, t9015 codec, PWM EF, AO bus PWM, both USB2 PHYs, DWC3 USB3, reset controller, and tsensors. Net: about half the dmesg footprint.

Stripped, peek_mmio_slim.ko lands around 7 KB on disk vs. ~99 KB for the unstripped full kmod (most of the size difference is debug info, not text).

Slim JSON sections

meta        tool / version / timestamp + canonical device serial
            + raw_encoding (int-array | hex-blob)
system      uname, kernel banner, /proc/cmdline, hostname, uptime
soc         MIDR-decoded CPU info, on-die serial, features
serials     every serial source consulted, canonical pick + provenance
emmc        every mmc host + CID/CSD/EXT_CSD + controller MMIO (kmod)
            + boot_partition_sizes (catches the 2 MiB vs 4 MiB variant)
gpu         Mali sysfs + decoded GPU_ID from kmod (Mali-G31 r0p0 etc)
display     DRM debugfs (mode/fb/connectors/EDID) + decoded panel
            timing + MIPI bias + DSI host + PWM AB (kmod)
panel_id    TLSC6X probe → BOE / Wily / Holitech + vendor_id / hw_id
board_rev   SARADC channel 1 mV (via _input) + all 8 channels for
            context (PCB / BT / DRAM NTCs etc.)

The kmod is optional for the slim flavor — userspace-only paths still produce ~80 % of the fingerprint. If peek_mmio_slim.ko (or peek_mmio.ko) is loaded, its dmesg is parsed and filtered down to just the slim topics.

Panel probe

peek-slim.py integrates a TLSC6X-based panel-variant probe. It talks to the touch controller at I²C 0x2e via I2C_SLAVE_FORCE, which claims the slave even while the kernel touch driver is bound. The sequence:

  1. Write 6-byte DMA-enable command to reg 0x42bd; poll reg 0x0001 until the chip echoes its own address and reports DMA mode = 1.
  2. Sniff MCCODE from reg 0x8000 (or 0x0009 fallback) to pick which config slot to read (0xd6e0 vs 0x9e00).
  3. Read 204 bytes of config NVM, extract vendor_id = (ver >> 9) & 0x7f and hw_id = u16 at byte 106.
  4. Restore normal touch-reporting mode by writing 0x05 to reg 0x03 (the kernel driver's tlsc6x_set_nor_mode_nolock sequence).

Net effect: kernel touch driver undisturbed, no unbind needed.

vendor_id hw_id variant panel_type
0x11 0x065c BOE lcd_8
0x70 0x2d5c Wily lcd_9
0x11 0x0d5c Holitech lcd_10

Compact + gzip output

The slim aggregator has two size-reduction flags:

python3 peek-slim.py             > h.json     # ~45 KB (indented)
python3 peek-slim.py --compact   > h.json     # ~20 KB (no indent + hex blobs)
python3 peek-slim.py --gzip      > h.json.gz  # ~4  KB (compact + gzip)

--compact does two things:

  1. Drops JSON indentation.
  2. Repacks each MMIO block's raw rows from a dict-of-int-arrays ({"0x0040": [w0,w1,w2,w3], ...}) into a single big-endian-ordered, space-separated hex string. Trivial to slice back to rows.

The meta.raw_encoding field records which encoding you're looking at ("int-array" for the default, "hex-blob" for compact/gzipped).

--gzip implies --compact and writes binary to stdout.

Known limitations

  • Stock kernel CONFIG_MODULE_FORCE_LOAD=n — IGNORE_MODVERSIONS via finit_module returns -ENOEXEC. The CRC patch above is the only path that works.
  • AO_SEC_SD_CFG / AO_SEC_GP_CFG are intentionally skipped — they're secure-world-only on G12A and reading from EL1 = sync abort
    • kernel oops. The boot source / reboot reason fields live in /proc/cmdline via bootargs Spotify u-boot injects.
  • eFuse SMC reads are skipped — stock BL31 doesn't respond to vendor SMC IDs 0x82000030 / 0x82000041, calling them hangs the CPU and leaves the kmod permanently stuck. Get usid / f_serial from /proc/cmdline or via fastboot oem run printenv if you're running a custom u-boot.
  • /dev/mem is disabled on the stock kernel (CONFIG_DEVMEM=n), which is why we need the kmod at all.

What's NOT in the JSON (intentional)

Get these via separate tools:

  • Vendor BL2 / FIP body bytes — use superbird-fip-tools/aml_decrypt.py.
  • eMMC EXT_CSD bytes — cat /sys/kernel/debug/mmc*/mmc*:*/ext_csd directly works (we only print the path hint in the JSON to avoid blocking on the host mutex).