From 3635e9f8f00e5a156d2b2304d4350f880b4712e6 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 6 Nov 2026 04:49:00 +0000 Subject: [PATCH 1/2] ambarella: report total RAM from the DSP carve-out An S3L camera reported 128M with 512 MiB fitted (#266): mem=118M keeps the kernel far below the top of DRAM, and the DSP has the rest. The reporter read the DRAM marking, a 4 Gbit part on a 16-bit bus, and checked that memory above 256 MiB does not alias, so the 256 MiB in the issue was wrong and IAVMEM ending at 512 MiB was right. This replaces the note that said not to trust it. Total is now the top of the carve-out and media is its size, from /proc/device-tree/iavmem/reg, or from the boot log's IAVMEM line where there is no /proc/device-tree, as on Dahua's 3.10.73. That line is printed once, so a ring buffer that dropped it falls back to what the kernel sees. Stock Dahua firmware has no /dev/i2c-*, because its own sensor.ko owns the bus, so no sensor is reported there. CLAUDE.md records that. The parsers are tested with the reporter's log. It has not run on a camera. --- CLAUDE.md | 16 ++++++---- src/hal/ambarella.c | 64 ++++++++++++++++++++++++++++++++++++++++ src/hal/ambarella.h | 9 ++++++ src/hal/ambarella_test.c | 25 ++++++++++++++++ 4 files changed, 109 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 850db73..7542cff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -217,11 +217,17 @@ 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`. Do not derive -total RAM from the boot log's `IAVMEM` line: Amboot computes it from a -compile-time `DRAM_SIZE`, and #266 has it ending at 512 MiB on a camera -reported as 256 MiB. That Dahua kernel also refuses `execve()` from writable -filesystems, so `/tmp/ipctool` fails there with "Success" and exit code 2. +reachable only through `/dev/iav`, so the report says `S3L`. 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. +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 +for the controller registers would race that driver. The same kernel refuses +`execve()` from writable filesystems, so `/tmp/ipctool` fails with "Success" +and exit code 2. 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 diff --git a/src/hal/ambarella.c b/src/hal/ambarella.c index c73113d..48a4b2f 100644 --- a/src/hal/ambarella.c +++ b/src/hal/ambarella.c @@ -1,5 +1,8 @@ #include "hal/ambarella.h" +#include +#include +#include #include #include "hal/common.h" @@ -49,6 +52,67 @@ bool ambarella_detect_cpu(char *chip_name) { return true; } +#ifndef STANDALONE_LIBRARY +/* /iavmem is what the kernel itself parses, two big-endian cells. Dahua's + * 3.10.73 has no /proc/device-tree at all (#266), hence the log below. */ +bool ambarella_iavmem_from_dt(const char *path, uint32_t *start, + uint32_t *size) { + uint32_t cells[2]; + FILE *f = fopen(path, "rb"); + + if (!f) + return false; + bool res = fread(cells, sizeof(cells), 1, f) == 1; + fclose(f); + if (!res) + return false; + + *start = ntohl(cells[0]); + *size = ntohl(cells[1]); + return true; +} + +/* "Ambarella: IAVMEM = 0x07800000[ ],0x18800000". Printed once at + * boot, so a long-lived ring buffer may have dropped it. */ +bool ambarella_iavmem_from_log(const char *log, uint32_t *start, + uint32_t *size) { + const char *line = strstr(log, "IAVMEM = "); + + if (!line) + return false; + return sscanf(line, "IAVMEM = 0x%x[%*[^]]],0x%x", start, size) == 2; +} + +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", + &start, &size); + + if (!found) { + char *log = NULL; + ssize_t len = get_dmesg_buf(&log); + + /* klogctl() does not terminate what it returns, and a full ring + * leaves no room after it. The last byte of a log is no loss. */ + if (log && len > 0) { + log[len - 1] = '\0'; + found = ambarella_iavmem_from_log(log, &start, &size); + } + free(log); + } + + /* Nothing to go on, or a carve-out that wraps: what the kernel sees. */ + if (!found || !size || start + size < start) + return kernel_mem(); + + *media_mem = size / 1024; + return (unsigned long)(start / 1024) + *media_mem; +} +#endif + void ambarella_setup_hal() { possible_i2c_addrs = ambarella_possible_i2c_addrs; +#ifndef STANDALONE_LIBRARY + hal_totalmem = ambarella_totalmem; +#endif } diff --git a/src/hal/ambarella.h b/src/hal/ambarella.h index 963c5b8..0b76ad6 100644 --- a/src/hal/ambarella.h +++ b/src/hal/ambarella.h @@ -3,9 +3,18 @@ #include #include +#include bool ambarella_machine_name(const char *cpuinfo, char *out, size_t outlen); bool ambarella_detect_cpu(char *chip_name); void ambarella_setup_hal(); +/* The DSP carve-out, as a physical start and a size in bytes. It runs to the + * top of DRAM, which is the only place total RAM can be read from here: the + * kernel is capped well below it by mem= on the command line. */ +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); + #endif /* HAL_AMBARELLA_H */ diff --git a/src/hal/ambarella_test.c b/src/hal/ambarella_test.c index 46ca45e..44c6417 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 @@ -62,6 +63,30 @@ int main() { guarded + 1, 0)); CHECK(guarded[0] == 'x' && guarded[1] == 'y' && guarded[2] == 'z'); + /* The reporter's board: 512 MiB, with 392 MiB of it the DSP's. */ + uint32_t start = 0, size = 0; + CHECK(ambarella_iavmem_from_log( + "Ambarella: DRAMC = 0xdffe0000[0xef000000],0x00020000 0\n" + "Ambarella: IAVMEM = 0x07800000[ ],0x18800000\n" + "Ambarella: FBMEM = 0x00000000[ ],0x00000000\n", + &start, &size)); + CHECK(start == 0x07800000 && size == 0x18800000); + CHECK(!ambarella_iavmem_from_log("Linux version 3.10.73\n", &start, &size)); + + char path[] = "/tmp/ambarella_test.XXXXXX"; + int fd = mkstemp(path); + const unsigned char reg[] = {0x0e, 0x40, 0x00, 0x00, + 0x11, 0xc0, 0x00, 0x00}; + CHECK(fd >= 0); + CHECK(write(fd, reg, sizeof(reg)) == (ssize_t)sizeof(reg)); + close(fd); + CHECK(ambarella_iavmem_from_dt(path, &start, &size)); + CHECK(start == 0x0e400000 && size == 0x11c00000); + CHECK(truncate(path, 4) == 0); + CHECK(!ambarella_iavmem_from_dt(path, &start, &size)); + unlink(path); + CHECK(!ambarella_iavmem_from_dt(path, &start, &size)); + if (failures) return EXIT_FAILURE; puts("ambarella_test: all cases passed"); From 3e92675714adf849750d48d85b46e564d942c638 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 6 Nov 2026 04:51:26 +0000 Subject: [PATCH 2/2] ambarella: a log cut off inside the size must not parse Review of #268. Overwriting the last byte of the log could shorten the IAVMEM size if the log ended on it, and the parser took the shorter number. The buffer now gets a real terminator, and the line has to end in a newline to count. --- src/hal/ambarella.c | 20 ++++++++++++++------ src/hal/ambarella_test.c | 6 ++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/hal/ambarella.c b/src/hal/ambarella.c index 48a4b2f..0bd0622 100644 --- a/src/hal/ambarella.c +++ b/src/hal/ambarella.c @@ -73,14 +73,19 @@ bool ambarella_iavmem_from_dt(const char *path, uint32_t *start, } /* "Ambarella: IAVMEM = 0x07800000[ ],0x18800000". Printed once at - * boot, so a long-lived ring buffer may have dropped it. */ + * boot, so a long-lived ring buffer may have dropped it. The newline is part + * of the match: without it a log cut off inside the size would still parse, + * as a smaller number. */ bool ambarella_iavmem_from_log(const char *log, uint32_t *start, uint32_t *size) { const char *line = strstr(log, "IAVMEM = "); + char end = 0; if (!line) return false; - return sscanf(line, "IAVMEM = 0x%x[%*[^]]],0x%x", start, size) == 2; + return sscanf(line, "IAVMEM = 0x%x[%*[^]]],0x%x%c", start, size, &end) == + 3 && + end == '\n'; } static unsigned long ambarella_totalmem(unsigned long *media_mem) { @@ -92,10 +97,13 @@ static unsigned long ambarella_totalmem(unsigned long *media_mem) { char *log = NULL; ssize_t len = get_dmesg_buf(&log); - /* klogctl() does not terminate what it returns, and a full ring - * leaves no room after it. The last byte of a log is no loss. */ - if (log && len > 0) { - log[len - 1] = '\0'; + /* klogctl() does not terminate what it returns, and the buffer is + * exactly the size of a full ring. */ + char *text = log && len > 0 ? realloc(log, len + 1) : NULL; + + if (text) { + log = text; + log[len] = '\0'; found = ambarella_iavmem_from_log(log, &start, &size); } free(log); diff --git a/src/hal/ambarella_test.c b/src/hal/ambarella_test.c index 44c6417..d8c6518 100644 --- a/src/hal/ambarella_test.c +++ b/src/hal/ambarella_test.c @@ -72,6 +72,12 @@ int main() { &start, &size)); CHECK(start == 0x07800000 && size == 0x18800000); CHECK(!ambarella_iavmem_from_log("Linux version 3.10.73\n", &start, &size)); + /* Cut off inside the size: 0x1880 must not be taken for the answer. */ + CHECK(!ambarella_iavmem_from_log( + "Ambarella: IAVMEM = 0x07800000[ ],0x1880", &start, &size)); + CHECK(!ambarella_iavmem_from_log( + "Ambarella: IAVMEM = 0x07800000[ ],0x18800000", &start, + &size)); char path[] = "/tmp/ambarella_test.XXXXXX"; int fd = mkstemp(path);