From c224b7455900931a5717af4d39b1abfc4d052c2f Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 6 Nov 2026 08:15:33 +0000 Subject: [PATCH 1/3] ambarella: what a second S3L showed, on a Hikvision DS-2CD2423G0-I Run on a lab DS-2CD2423G0-I (S3Lm, 256 MiB, NAND, AR0237) with its stock firmware. Detection worked, but the sensor was missing and media RAM was wrong. The sensor sits at 8-bit 0x30, not the SDK default 0x20, and its 0x3000 reads 0x0950, not the ID ipctool knew. The vendor driver reads that register without checking it, so the ID rests on the firmware driving this sensor with its ar0237 module, not on a datasheet. Media read 192M where the camera has 144M. Hikvision's kernel moves the split at boot and leaves /iavmem stale: it says 0x04000000 while Linux runs to 0x07000000. The end of the carve-out still holds, so total stays right, and media is now what lies above Linux's RAM in /proc/iomem. /proc/ambarella/iav names the part once iav.ko is loaded, so the report adds family: S3Lm. The model stays S3L, which ipcinfo and the firmware's hostname use. --- CLAUDE.md | 5 ++++- src/hal/ambarella.c | 46 ++++++++++++++++++++++++++++++++++++---- src/hal/ambarella.h | 3 +++ src/hal/ambarella_test.c | 45 +++++++++++++++++++++++++++++++++++++++ src/sensors.c | 6 ++++++ src/sensors_golden.txt | 1 + 6 files changed, 101 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7542cff..8b4d3ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -217,11 +217,14 @@ is also what lets detection survive a kernel that will not hand over Ambarella (`src/hal/ambarella.c`) stops at the machine string on purpose. The exact part (S3L33M, S3L55M, S3L66, ...) is a word the DSP firmware writes, -reachable only through `/dev/iav`, so the report says `S3L`. Total RAM is the +reachable only through `/dev/iav`, so the report says `S3L`, plus `family: S3Lm` +when `iav.ko` is loaded. Total RAM is the top of the DSP carve-out, because `mem=` keeps the kernel far below it: 118 MiB of 512 MiB on the camera in #266, where the DRAM marking confirmed the figure. The carve-out comes from `/proc/device-tree/iavmem/reg`, or from the boot log's `IAVMEM` line on kernels with no `/proc/device-tree`, which Dahua's 3.10.73 is. +Only its end is trusted: Hikvision's kernel moves the split at boot and leaves +`/iavmem` stale, so media is what lies above Linux's RAM in `/proc/iomem`. That line is printed once, so if the ring buffer has dropped it the report falls back to what the kernel sees. Stock Dahua firmware has no `/dev/i2c-*` either: its own `sensor.ko` owns the bus, so no sensor is reported there, and reaching diff --git a/src/hal/ambarella.c b/src/hal/ambarella.c index 0bd0622..ace7460 100644 --- a/src/hal/ambarella.c +++ b/src/hal/ambarella.c @@ -11,9 +11,10 @@ /* Defaults of the `addr` module parameter across the S3L SDK's VIN drivers, * the 8-bit form ipctool's tables use. AR0521 is the one part left out: it * answers on 0x6C, and the onsemi probe runs before the OmniVision one, so - * listing it would have that probe reading an OV4689. */ + * listing it would have that probe reading an OV4689. 0x30 is not a default: + * the Hikvision DS-2CD2423G0-I strings its AR0237 there. */ static unsigned char sony_addrs[] = {0x34, 0}; -static unsigned char onsemi_addrs[] = {0x20, 0}; +static unsigned char onsemi_addrs[] = {0x20, 0x30, 0}; static unsigned char omni_addrs[] = {0x6c, 0}; static sensor_addr_t ambarella_possible_i2c_addrs[] = { @@ -88,6 +89,42 @@ bool ambarella_iavmem_from_log(const char *log, uint32_t *start, end == '\n'; } +/* The carve-out's end is the top of DRAM on every board seen, but its start + * cannot be trusted: Hikvision's kernel moves the split at boot, and leaves + * /iavmem saying 0x04000000 where it gave Linux up to 0x07000000. What is not + * Linux's is the DSP's, so the size comes from what Linux ended up with. */ +unsigned long ambarella_media_kb(const char *iomem, uint32_t start, + uint32_t size) { + uint64_t end = (uint64_t)start + size; + char buf[32]; + + if (line_from_file(iomem, "^\\w+-(\\w+) *: *System RAM", buf, + sizeof(buf))) { + uint64_t top = strtoull(buf, NULL, 16) + 1; + + if (top > start && top < end) + return (end - top) / 1024; + } + return size / 1024; +} + +/* iav's own view of the part, there once iav.ko is loaded: "S3Lm", which the + * machine string cannot tell from S3L. */ +bool ambarella_family(const char *iav, char *out, size_t outlen) { + if (!outlen || !line_from_file(iav, "AMBARELLA_CHIP=(\\w+)", out, outlen)) + return false; + + out[outlen - 1] = '\0'; + return true; +} + +static void ambarella_chip_properties(cJSON *j_inner) { + char family[32]; + + if (ambarella_family("/proc/ambarella/iav", family, sizeof(family))) + ADD_PARAM("family", family); +} + static unsigned long ambarella_totalmem(unsigned long *media_mem) { uint32_t start = 0, size = 0; bool found = ambarella_iavmem_from_dt("/proc/device-tree/iavmem/reg", @@ -113,8 +150,8 @@ static unsigned long ambarella_totalmem(unsigned long *media_mem) { if (!found || !size || start + size < start) return kernel_mem(); - *media_mem = size / 1024; - return (unsigned long)(start / 1024) + *media_mem; + *media_mem = ambarella_media_kb("/proc/iomem", start, size); + return (unsigned long)(((uint64_t)start + size) / 1024); } #endif @@ -122,5 +159,6 @@ void ambarella_setup_hal() { possible_i2c_addrs = ambarella_possible_i2c_addrs; #ifndef STANDALONE_LIBRARY hal_totalmem = ambarella_totalmem; + hal_chip_properties = ambarella_chip_properties; #endif } diff --git a/src/hal/ambarella.h b/src/hal/ambarella.h index 0b76ad6..784dd9e 100644 --- a/src/hal/ambarella.h +++ b/src/hal/ambarella.h @@ -16,5 +16,8 @@ bool ambarella_iavmem_from_dt(const char *path, uint32_t *start, uint32_t *size); bool ambarella_iavmem_from_log(const char *log, uint32_t *start, uint32_t *size); +unsigned long ambarella_media_kb(const char *iomem, uint32_t start, + uint32_t size); +bool ambarella_family(const char *iav, char *out, size_t outlen); #endif /* HAL_AMBARELLA_H */ diff --git a/src/hal/ambarella_test.c b/src/hal/ambarella_test.c index 63ca1c7..f344f1d 100644 --- a/src/hal/ambarella_test.c +++ b/src/hal/ambarella_test.c @@ -1,6 +1,7 @@ /* Hardware-free cover for the Ambarella machine-string parser. The strings * are the DT_MACHINE_START names in the vendor kernel, and the S3L cpuinfo is * the one from issue #266. */ +#include #include #include #include @@ -101,6 +102,50 @@ int main() { CHECK(rounded_num(513) == 1024); CHECK(rounded_num(1) == 1); + /* Two boards. On the DS-2CD2423G0-I /iavmem is stale: the DT says the + * DSP starts at 64 MiB and Linux runs to 112 MiB. On the IP4M-1026E the + * two agree. */ + { + char iomem[] = "/tmp/ambarella_test.XXXXXX"; + const char *hik = "00200000-06ffffff : System RAM\n" + " 00208000-00719667 : Kernel code\n"; + const char *dahua = "00200000-077fffff : System RAM\n"; + int ifd = mkstemp(iomem); + + CHECK(ifd >= 0); + CHECK(write(ifd, hik, strlen(hik)) == (ssize_t)strlen(hik)); + close(ifd); + CHECK(ambarella_media_kb(iomem, 0x04000000, 0x0c000000) == + 144 * 1024); + + ifd = open(iomem, O_WRONLY | O_TRUNC); + CHECK(ifd >= 0); + CHECK(write(ifd, dahua, strlen(dahua)) == (ssize_t)strlen(dahua)); + close(ifd); + CHECK(ambarella_media_kb(iomem, 0x07800000, 0x18800000) == + 392 * 1024); + + /* RAM past the carve-out, or none listed: take the size as given */ + CHECK(ambarella_media_kb("/nonexistent", 0x07800000, 0x18800000) == + 392 * 1024); + unlink(iomem); + } + + { + char iav[] = "/tmp/ambarella_test.XXXXXX"; + const char *text = "AMBARELLA_CHIP=S3Lm\ndsp_op_mode: 1\n"; + char family[16]; + int ifd = mkstemp(iav); + + CHECK(ifd >= 0); + CHECK(write(ifd, text, strlen(text)) == (ssize_t)strlen(text)); + close(ifd); + CHECK(ambarella_family(iav, family, sizeof(family))); + CHECK(!strcmp(family, "S3Lm")); + unlink(iav); + CHECK(!ambarella_family(iav, family, sizeof(family))); + } + if (failures) return EXIT_FAILURE; puts("ambarella_test: all cases passed"); diff --git a/src/sensors.c b/src/sensors.c index 02e3535..5c85c6d 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -451,6 +451,12 @@ static int detect_onsemi_sensor(sensor_ctx_t *ctx, int fd, case 0x256: sid = 0x0237; break; + /* Register 0x3000 reads 0x0950 on the DS-2CD2423G0-I, whose firmware + * drives that sensor with its ar0237 module. No datasheet to hand: the + * vendor driver reads this register but does not check it. */ + case 0x5009: + sid = 0x0237; + break; case 0x2602: sid = 0x0331; break; diff --git a/src/sensors_golden.txt b/src/sensors_golden.txt index cba0f7f..ef39b9a 100644 --- a/src/sensors_golden.txt +++ b/src/sensors_golden.txt @@ -1102,6 +1102,7 @@ onsemi 0256 AR0237 onsemi 2402 AR0130 onsemi 2602 AR0331 onsemi 2604 AR0330 +onsemi 5009 AR0237 omni 2710 OV2710 omni 2715 OV2715 omni 2718 OV2718 From 60c2982da5c961718d5659bf0ae22730affaeaaf Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 6 Nov 2026 08:36:16 +0000 Subject: [PATCH 2/3] ambarella: media from every RAM range, and none when it cannot be measured Review of #270. Only the first System RAM line was read, so a split layout overstated media. With /proc/iomem unusable it fell back to a size this PR had just shown to be stale, so it now leaves media out. And Linux ending exactly at the carve-out only came right through that fallback, so the arithmetic no longer depends on the start at all. --- CLAUDE.md | 3 ++- src/hal/ambarella.c | 38 +++++++++++++++++++-------------- src/hal/ambarella.h | 3 +-- src/hal/ambarella_test.c | 45 ++++++++++++++++++++++++++-------------- 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8b4d3ba..32009f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -224,7 +224,8 @@ of 512 MiB on the camera in #266, where the DRAM marking confirmed the figure. The carve-out comes from `/proc/device-tree/iavmem/reg`, or from the boot log's `IAVMEM` line on kernels with no `/proc/device-tree`, which Dahua's 3.10.73 is. Only its end is trusted: Hikvision's kernel moves the split at boot and leaves -`/iavmem` stale, so media is what lies above Linux's RAM in `/proc/iomem`. +`/iavmem` stale, so media is what lies above Linux's RAM in `/proc/iomem`, and +with no such RAM listed it is left out, not guessed. That line is printed once, so if the ring buffer has dropped it the report falls back to what the kernel sees. Stock Dahua firmware has no `/dev/i2c-*` either: its own `sensor.ko` owns the bus, so no sensor is reported there, and reaching diff --git a/src/hal/ambarella.c b/src/hal/ambarella.c index ace7460..7a858e4 100644 --- a/src/hal/ambarella.c +++ b/src/hal/ambarella.c @@ -91,21 +91,29 @@ bool ambarella_iavmem_from_log(const char *log, uint32_t *start, /* The carve-out's end is the top of DRAM on every board seen, but its start * cannot be trusted: Hikvision's kernel moves the split at boot, and leaves - * /iavmem saying 0x04000000 where it gave Linux up to 0x07000000. What is not - * Linux's is the DSP's, so the size comes from what Linux ended up with. */ -unsigned long ambarella_media_kb(const char *iomem, uint32_t start, - uint32_t size) { - uint64_t end = (uint64_t)start + size; - char buf[32]; - - if (line_from_file(iomem, "^\\w+-(\\w+) *: *System RAM", buf, - sizeof(buf))) { - uint64_t top = strtoull(buf, NULL, 16) + 1; - - if (top > start && top < end) - return (end - top) / 1024; + * /iavmem saying 0x04000000 where it gave Linux up to 0x07000000. So media is + * what lies between the top of Linux's RAM and that end, and with no RAM listed + * to measure from it is unknown: 0, which the report leaves out. */ +unsigned long ambarella_media_kb(const char *iomem, uint64_t end) { + FILE *f = fopen(iomem, "r"); + uint64_t top = 0; + char line[128]; + + if (!f) + return 0; + + /* Top level only: "Kernel code" and the like are indented inside it */ + while (fgets(line, sizeof(line), f)) { + unsigned long long from, to; + int name = 0; + + if (sscanf(line, "%llx-%llx : %n", &from, &to, &name) == 2 && name && + !strncmp(line + name, "System RAM", 10) && to + 1 > top) + top = to + 1; } - return size / 1024; + fclose(f); + + return top && top < end ? (end - top) / 1024 : 0; } /* iav's own view of the part, there once iav.ko is loaded: "S3Lm", which the @@ -150,7 +158,7 @@ static unsigned long ambarella_totalmem(unsigned long *media_mem) { if (!found || !size || start + size < start) return kernel_mem(); - *media_mem = ambarella_media_kb("/proc/iomem", start, size); + *media_mem = ambarella_media_kb("/proc/iomem", (uint64_t)start + size); return (unsigned long)(((uint64_t)start + size) / 1024); } #endif diff --git a/src/hal/ambarella.h b/src/hal/ambarella.h index 784dd9e..56018fa 100644 --- a/src/hal/ambarella.h +++ b/src/hal/ambarella.h @@ -16,8 +16,7 @@ bool ambarella_iavmem_from_dt(const char *path, uint32_t *start, uint32_t *size); bool ambarella_iavmem_from_log(const char *log, uint32_t *start, uint32_t *size); -unsigned long ambarella_media_kb(const char *iomem, uint32_t start, - uint32_t size); +unsigned long ambarella_media_kb(const char *iomem, uint64_t end); bool ambarella_family(const char *iav, char *out, size_t outlen); #endif /* HAL_AMBARELLA_H */ diff --git a/src/hal/ambarella_test.c b/src/hal/ambarella_test.c index f344f1d..7480d02 100644 --- a/src/hal/ambarella_test.c +++ b/src/hal/ambarella_test.c @@ -104,31 +104,46 @@ int main() { /* Two boards. On the DS-2CD2423G0-I /iavmem is stale: the DT says the * DSP starts at 64 MiB and Linux runs to 112 MiB. On the IP4M-1026E the - * two agree. */ + * two agree, Linux ending exactly where the DSP starts. */ { char iomem[] = "/tmp/ambarella_test.XXXXXX"; const char *hik = "00200000-06ffffff : System RAM\n" " 00208000-00719667 : Kernel code\n"; const char *dahua = "00200000-077fffff : System RAM\n"; + /* Split, the higher range last, and an indented line that must not + * count however high it goes */ + const char *split = "00200000-03ffffff : System RAM\n" + " 00208000-00719667 : Kernel code\n" + "04100000-06ffffff : System RAM\n" + " 0ff00000-0fffffff : System RAM\n"; + const char *none = "e8005000-e8005fff : uart\n"; + const uint64_t top = 0x10000000; int ifd = mkstemp(iomem); CHECK(ifd >= 0); - CHECK(write(ifd, hik, strlen(hik)) == (ssize_t)strlen(hik)); close(ifd); - CHECK(ambarella_media_kb(iomem, 0x04000000, 0x0c000000) == - 144 * 1024); - - ifd = open(iomem, O_WRONLY | O_TRUNC); - CHECK(ifd >= 0); - CHECK(write(ifd, dahua, strlen(dahua)) == (ssize_t)strlen(dahua)); - close(ifd); - CHECK(ambarella_media_kb(iomem, 0x07800000, 0x18800000) == - 392 * 1024); - - /* RAM past the carve-out, or none listed: take the size as given */ - CHECK(ambarella_media_kb("/nonexistent", 0x07800000, 0x18800000) == - 392 * 1024); +#define IOMEM(text) \ + do { \ + int w = open(iomem, O_WRONLY | O_TRUNC); \ + CHECK(w >= 0); \ + CHECK(write(w, text, strlen(text)) == (ssize_t)strlen(text)); \ + close(w); \ + } while (0) + IOMEM(hik); + CHECK(ambarella_media_kb(iomem, top) == 144 * 1024); + IOMEM(dahua); + CHECK(ambarella_media_kb(iomem, 0x20000000) == 392 * 1024); + IOMEM(split); + CHECK(ambarella_media_kb(iomem, top) == 144 * 1024); + + /* Nothing to measure from, or Linux past the end: unknown */ + IOMEM(none); + CHECK(ambarella_media_kb(iomem, top) == 0); + IOMEM(hik); + CHECK(ambarella_media_kb(iomem, 0x07000000) == 0); unlink(iomem); + CHECK(ambarella_media_kb(iomem, top) == 0); +#undef IOMEM } { From d8417936563b077c2bde37e45864e1680a812f0a Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 6 Nov 2026 08:39:06 +0000 Subject: [PATCH 3/3] ambarella: only top-level lines of /proc/iomem are RAM ranges sscanf %llx skips leading space, so an indented line matching the format counted. ambarella_test caught it. --- src/hal/ambarella.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hal/ambarella.c b/src/hal/ambarella.c index 7a858e4..98954e1 100644 --- a/src/hal/ambarella.c +++ b/src/hal/ambarella.c @@ -102,12 +102,14 @@ unsigned long ambarella_media_kb(const char *iomem, uint64_t end) { if (!f) return 0; - /* Top level only: "Kernel code" and the like are indented inside it */ + /* Top level only: "Kernel code" and the like are indented inside it, + * and %llx would skip that indent */ while (fgets(line, sizeof(line), f)) { unsigned long long from, to; int name = 0; - if (sscanf(line, "%llx-%llx : %n", &from, &to, &name) == 2 && name && + if (*line != ' ' && + sscanf(line, "%llx-%llx : %n", &from, &to, &name) == 2 && name && !strncmp(line + name, "System RAM", 10) && to + 1 > top) top = to + 1; }