Skip to content

Add s3l_ultimate_amcrest-ip4m1026e (Ambarella S3L, OV4689) - #228

Open
DeathCamel58 wants to merge 1 commit into
OpenIPC:masterfrom
DeathCamel58:s3l-amcrest-ip4m1026e
Open

DeathCamel58 wants to merge 1 commit into
OpenIPC:masterfrom
DeathCamel58:s3l-amcrest-ip4m1026e

Conversation

@DeathCamel58

Copy link
Copy Markdown

Adds the Amcrest IP4M-1026E as s3l_ultimate_amcrest-ip4m1026e, the first Ambarella device in builder. It's a Dahua IPC-HFW1430SN inside (hwid family IPC-HX1X3X-LEXUS): Ambarella S3L, OV4689 over MIPI, 32 MiB SPI-NOR, Ethernet only, IR-cut and IR LEDs, and an analog light sensor. Tested on an IP4M-1026EW-28MM (white, 2.8 mm). The black EB and the 3.6 mm lens versions run the same Amcrest firmware.

What is in it

  • Defconfig: firmware's s3l_ultimate (Majestic with its fonts and web UI) with BR2_OPENIPC_FLASH_SIZE="32" and this board's kernel fragment.
  • Kernel fragment:
  • Flash layout: rootfs_data ends at 0x1a70000. The rest of the flash stays stock and read-only (dahua), because the Dahua U-Boot writes a boot-state record into the stock updateflag partition on every boot.
  • customizer.sh:
    • names the sensor (ov4689), since it's held in reset until its driver loads;
    • sets the upgrade URL;
    • sets sysbackup=0 in the Dahua U-Boot environment (see below);
    • sets up Majestic's night mode: IR-cut on 113/111 and IR LEDs on 5;
    • turns on day/night from the light sensor on ADC channel 1. It reads higher in light (about 2900 in a lit room, 15–200 covered), hence adcInvert.
  • gpio.conf: the reset button (34), IR-cut, IR LEDs and the light sensor, each checked on the camera.
  • Excludes (s3l_ultimate.list): the sensor and video-out modules this board doesn't use; only ov4689_mipi is left.
  • ci-matrix.py: the device is added to SMOKE_TARGETS, because --self-test requires one target per SoC vendor and there was no Ambarella one. Touching that file widens this PR's CI to the full matrix.

Testing

On the IP4M-1026EW-28MM, with firmware master 35c75dc3:

  • Image size: the NOR build fits: uImage 1604K of 2048K, rootfs 5128K of 8192K.

  • Night-mode settings: netbooted from the profile, the customizer writes them into /etc/majestic.yaml: adcReadout, adcChannel: 1, adcInvert, thresholds 256/672 and lightMonitor.

  • Video: with Majestic master+f2fa260, Majestic starts preview from a fresh boot and streams H.265 2688×1512 at 30 fps with its 3A (ambarella: s3l: replace s3l_lite with s3l_ultimate firmware#2787).

  • IR-cut and IR LEDs: /night/on and /night/off move the IR-cut filter and switch the IR LEDs.

  • Automatic day/night: Majestic reads the light sensor (night_mode_source 3). Covering it (ADC ~15) switches to night: IR-cut out, IR LEDs on, grayscale. Uncovering it (~2900) switches back to day. That's one switch each way, with no flapping.

  • Flashed: the NOR build of this profile, written through the Dahua U-Boot's TFTP upgrade with every write read back:

    • it boots with its own layout, with the overlay on rootfs_data;
    • the customizer's settings are in the environment (sensor, upgrade, sysbackup) and in /etc/majestic.yaml;
    • a test file survives reboots;
    • from a fresh boot, Majestic (master+e6ba340) streams H.265 2688×1512 at 30 fps and reads the light sensor (night_mode_source 3).

    An earlier lite build of the profile also went stock → OpenIPC → stock → OpenIPC.

  • Boot counter: works on the flashed image; see below.

  • Repo checks: ci-matrix.py --self-test, lint-cli-paths.py, lint-workflow-shell.py and check-firmware-drift.py --self-test pass locally.

The Dahua boot counter

The Dahua U-Boot counts boots in one byte at flash 0x1a80000. When it reads 5, it boots Dahua's backup system from the stock backupker/backupfs instead of OpenIPC. The stock firmware clears the count after every good boot; OpenIPC doesn't, so from the factory value (2) the camera would fall back within a few reboots.

sysbackup=0 turns the counting and the backup boot off. The customizer sets it on first boot with fw_setenv. It changes only the environment sector, not U-Boot, and U-Boot's TFTP upgrade path doesn't read it.

Tested on the flashed image, with the flash set leaving sysbackup=1 and the count at 0:

  • U-Boot counted the first boot (state:ff,err_count:00, leaving 01).
  • The customizer set sysbackup=0 on that boot.
  • Over the next three boots, U-Boot printed no err_count, and the count stayed at 01.

Related: OpenIPC/firmware#2781 (overlayfs), #2782 (SDK microcode), #2787 (s3l_ultimate with Majestic), OpenIPC/linux#177 and #178.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add Amcrest IP4M-1026E Ambarella S3L device profile

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add an Ambarella S3L profile for the Amcrest IP4M-1026E with Majestic and OV4689 support.
• Preserve Dahua boot partitions and disable its incompatible boot counter to prevent fallback.
• Configure automatic day/night operation and add the device to documentation and CI smoke coverage.
Diagram

graph TD
  UBoot["Dahua U-Boot"] --> Kernel["S3L kernel"] --> Rootfs["OpenIPC rootfs"] --> Customizer["Board customizer"] --> Majestic["Majestic streamer"] --> IO["Camera hardware"]
  Kernel --> Flash[("NOR flash")]
  Customizer -->|"sysbackup setting"| UBoot
  IO -->|"ADC light reading"| Majestic
Loading
High-Level Assessment

Use the dedicated board profile: its bootloader layout, appended DTB, sensor reset behavior, and GPIO assignments make a generic S3L configuration unsuitable. Preserving Dahua's stock flash regions and disabling fallback through its environment is preferable to replacing bootloader or backup partitions.

Files changed (7) +157 / -0

Enhancement (2) +108 / -0
s3l_ultimate_amcrest-ip4m1026e_defconfigAdd the S3L ultimate build profile +66/-0

Add the S3L ultimate build profile

• Configures the ARM toolchain, kernel fragment, 32 MiB NOR image, SquashFS root filesystem, Ambarella drivers, and Majestic with fonts and web UI.

devices/s3l_ultimate_amcrest-ip4m1026e/br-ext-chip-ambarella/configs/s3l_ultimate_amcrest-ip4m1026e_defconfig

customizer.shInitialize boot safety and night mode +42/-0

Initialize boot safety and night mode

• Sets the upgrade URL and OV4689 sensor name, then disables Dahua's backup-boot counter through its environment. Configures Majestic's IR-cut, IR LED, and inverted ADC-based automatic day/night settings.

devices/s3l_ultimate_amcrest-ip4m1026e/general/overlay/usr/share/openipc/customizer.sh

Documentation (1) +1 / -0
README.mdList Amcrest IP4M-1026E support +1/-0

List Amcrest IP4M-1026E support

• Documents the S3L chipset, OV4689 sensor, 32 MiB NOR flash, and tested video and day/night capabilities.

README.md

Other (4) +48 / -0
ci-matrix.pyCover Ambarella in CI smoke targets +1/-0

Cover Ambarella in CI smoke targets

• Adds the new profile as the Ambarella representative required by the smoke-target self-test. Changing the matrix script also causes CI to run the full matrix for this PR.

.github/scripts/ci-matrix.py

amcrest-ip4m1026e.configDefine board kernel boot and flash settings +17/-0

Define board kernel boot and flash settings

• Enables SPI-NOR and MTD block access and appends the board DTB for Dahua's ATAG-based bootloader. Defines partitions that place the kernel at its stock offset while keeping boot metadata and the remaining Dahua flash region intact.

devices/s3l_ultimate_amcrest-ip4m1026e/br-ext-chip-ambarella/board/s3l/fragments/amcrest-ip4m1026e.config

gpio.confMap camera button and night-mode hardware +13/-0

Map camera button and night-mode hardware

• Records the verified reset-button, IR-cut, and IR LED pins, and identifies the light sensor as ADC channel 1. Marks unsupported GPIO peripherals as absent.

devices/s3l_ultimate_amcrest-ip4m1026e/general/overlay/usr/share/openipc/gpio.conf

s3l_ultimate.listExclude unused Ambarella modules +17/-0

Exclude unused Ambarella modules

• Removes sensor drivers other than the board's OV4689 driver and unused video-output modules from the device image.

devices/s3l_ultimate_amcrest-ip4m1026e/general/scripts/excludes/s3l_ultimate.list

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Image uses the larger flavor without a size reason 📘 Rule violation ≡ Correctness
Description
The defconfig sets BR2_OPENIPC_VARIANT="ultimate" for a new device, although new devices are
expected to use the lite flavor unless flash size requires otherwise. The justification given is
Majestic, fonts and web UI rather than a flash limit, and a 32 MiB flash fits the NOR build, so a
later maintainer cannot tell whether ultimate is intentional or an oversight.
Code

devices/s3l_ultimate_amcrest-ip4m1026e/br-ext-chip-ambarella/configs/s3l_ultimate_amcrest-ip4m1026e_defconfig[R45-46]

+BR2_OPENIPC_VARIANT="ultimate"
+BR2_OPENIPC_FLASH_SIZE="32"
Evidence
Rule 2 requires new devices to use the lite flavor unless flash size requires otherwise. The
defconfig selects ultimate alongside BR2_OPENIPC_FLASH_SIZE="32". The PR description justifies
ultimate by the Majestic streamer, its fonts and its web UI, not by a flash-size constraint.

CLAUDE.md: Match Device Configuration to Hardware: CLAUDE.md: Match Device Configuration to Hardware
devices/s3l_ultimate_amcrest-ip4m1026e/br-ext-chip-ambarella/configs/s3l_ultimate_amcrest-ip4m1026e_defconfig[45-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new device `s3l_ultimate_amcrest-ip4m1026e` selects `BR2_OPENIPC_VARIANT="ultimate"`. New devices are expected to use the `lite` flavor unless flash size requires otherwise. The stated reason for `ultimate` is Majestic, fonts and web UI, which is not a flash-size requirement.
## Fix Focus Areas
- devices/s3l_ultimate_amcrest-ip4m1026e/br-ext-chip-ambarella/configs/s3l_ultimate_amcrest-ip4m1026e_defconfig[45-46]
## Recommended Fix
Either document in the PR description and in a defconfig comment why the `ultimate` flavor is required for this board, or change the variant to the lite flavor and re-verify that the image builds and fits the flash layout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@openipc-ai openipc-ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. The layout and the boot-counter handling are well reasoned, and the testing is thorough. I checked:

  • Offsets. 768k + 128k + 64k = 0xf0000 for the kernel, then 2048k + 8192k + 15872k puts the end of rootfs_data at 0x1a70000, so the counter byte at 0x1a80000 sits inside the read-only dahua partition.
  • Slot sizes. FLASH_SIZE="32" takes firmware's ≥16 MB repack branch: the same 2048K / 8192K slots as mtdparts.
  • Excludes name. s3l_ultimate.list matches the ${OPENIPC_SOC_MODEL}_${OPENIPC_VARIANT} lookup in rootfs_script.sh.
  • Customizer. It runs once (gated on /etc/custom.ok), and sysbackup is in the U-Boot env, so it survives firstboot.
  • Defconfig. It diverges from firmware's s3l_ultimate only where it should: 32 MB, no UBI, no WiFi or motors, and the fragment.

A few things before merge:

  1. An OpenIPC-to-OpenIPC upgrade. All the flashing so far went through the Dahua U-Boot's TFTP path. Every owner will update with sysupgrade, using the URL the customizer sets, and this layout puts the kernel at 0xf0000 rather than the usual 0x50000. Could you run one sysupgrade on the camera (local files are fine, e.g. sysupgrade --kernel=... --rootfs=...) and paste the output, plus the boot that follows?
  2. BR2_LINUX_KERNEL_UIMAGE_LOADADDR="0x00208000". Firmware's s3l_ultimate doesn't set it. Please add a one-line comment saying why this board needs it (I assume it's where the Dahua U-Boot's bootcmd loads the kernel).
  3. sysbackup failure mode, FYI rather than a request. S30customizer touches custom.ok whatever the script's exit status, so if fw_setenv sysbackup 0 ever failed it would not be retried. The counter would keep running and the camera would drop to Dahua's backup system after ~4 boots. It worked on your camera, so this is low risk; just noting that it has no retry.
  4. Nits: a double blank line in the kernel fragment, and a stray # and blank line before exit 0 in customizer.sh.

The Amcrest IP4M-1026E (Dahua IPC-HFW1430SN): S3L, OV4689 over MIPI,
32 MiB SPI-NOR, Ethernet only, IR-cut and IR LEDs with an ADC light
sensor.

- s3l_ultimate with this board's kernel fragment: the SPI-NOR
  controller, the board DTB appended to the uImage (the Dahua U-Boot
  passes ATAGs), and a command line whose layout keeps the Dahua boot
  area, its environment and partition table, puts the kernel at 0xf0000
  where its U-Boot reads it, and ends rootfs_data at 0x1a70000. The
  flash after that stays stock (dahua, ro): the Dahua U-Boot writes a
  boot-state record into the stock updateflag partition there on every
  boot.
- Majestic as the streamer, with its fonts and web UI, as s3l_ultimate
  has them.
- The customizer names the sensor (it is held in reset until its driver
  loads), sets the upgrade URL, sets sysbackup=0 in the Dahua U-Boot
  env, and sets up Majestic's night mode:
  IR-cut 113/111, IR LEDs on 5, and day/night from the light sensor on
  ADC channel 1 (which reads higher in light, hence adcInvert).
- gpio.conf for the button, IR-cut, IR LEDs and light sensor.
- Excludes drop the sensor and video-out modules this board can't use.

Tested on the IP4M-1026E with firmware master (35c75dc3): the NOR build
fits (uImage 1604K/2048K, rootfs 5072K/8192K) with only ov4689_mipi
left of the sensor drivers; netbooted, the customizer writes Majestic's
night-mode settings, and /night/on and /night/off move the IR-cut filter
and IR LEDs. With Majestic master+f2fa260, it starts preview from a fresh
boot and streams H.265 2688x1512 at 30 fps with its 3A; it reads the
light sensor (night_mode_source 3), and covering the sensor (ADC ~15)
switches to night and uncovering it (~2870) back to day.

Flashed over the Dahua U-Boot's TFTP upgrade (every write read back),
the NOR build of this profile boots with its own layout: the overlay on
rootfs_data, the customizer's settings in fw_env and majestic.yaml, and
a test file surviving reboots. From a fresh boot, Majestic
(master+e6ba340) streams H.265 2688x1512 at 30 fps and reads the light
sensor. An OpenIPC-to-OpenIPC sysupgrade from
the profile's archive (--archive, -z) wrote and verified the kernel at
0xf0000 and the rootfs at 0x2f0000 and rebooted into them with the
settings, sysbackup=0 and the counter kept. An earlier lite build of
the profile also went stock -> OpenIPC -> stock -> OpenIPC.

It is the first Ambarella device here, so ci-matrix.py gets it in
SMOKE_TARGETS: --self-test requires one target per SoC vendor.

sysbackup=0: the Dahua U-Boot counts boots in a byte at 0x1a80000 and,
on reading 5, boots its backup system from the stock backupker/backupfs
instead of OpenIPC. Stock clears the count after every good boot;
OpenIPC does not, so from the factory value (2) it would fall back
within a few reboots. sysbackup=0 turns the counting and the backup boot
off; U-Boot's TFTP upgrade path does not read it. Tested on the
flashed image: with the flash set leaving sysbackup=1 and the count at
0, U-Boot counted the first boot (err_count 00 -> 01), the customizer
set sysbackup=0, and over the next three boots U-Boot printed no
err_count and the count stayed at 01.
@DeathCamel58
DeathCamel58 force-pushed the s3l-amcrest-ip4m1026e branch from 9b07731 to 57c043e Compare November 7, 2026 21:23
@DeathCamel58

Copy link
Copy Markdown
Author

Thanks for the careful review. I've force-pushed an updated commit.

1. OpenIPC-to-OpenIPC upgrade. I ran it on the camera from the profile's own archive, the same .tgz the upgrade URL serves: sysupgrade --archive=/tmp/fw.tgz -z. Both my local builds carry the same version stamp (local-20261107-build), so the first run updated only the kernel and skipped the rootfs with "Same version, nothing to update". A second run with --force_ver wrote both:

Verifying rootfs from /tmp/rootfs.squashfs.s3l
Skip SoC validation
Skip version checking
Kernel
Update kernel from /tmp/uImage.s3l
Erasing block: 26/26 (100%)
Writing kb: 1604/1604 (100%)
Verifying kb: 1604/1604 (100%)
Kernel updated to 20:42:21 2026-11-07
RootFS
Update rootfs from /tmp/rootfs.squashfs.s3l
Erasing block: 81/81 (100%)
Writing kb: 5128/5128 (100%)
Verifying kb: 5128/5128 (100%)
RootFS updated to local+build, 2026-11-07
Unconditional reboot

The first run's SoC check passed (SoC OK). The boot that follows:

## Booting kernel from Legacy Image at 02000000 ...
   Image Name:   Linux-3.10.104-s3l
   Created:      2026-11-07  20:42:21 UTC
   Load Address: 00208000
   Verifying Checksum ... OK
[    0.000000] Kernel command line: console=ttyS0,115200 panic=20 root=/dev/mtdblock4 rootfstype=squashfs init=/init mtdparts=amba_spinor:768k(boot)ro,128k(env),64k(partition)ro,2048k(kernel),8192k(rootfs),15872k(rootfs_data),-(dahua)ro
[    0.749163] VFS: Mounted root (squashfs filesystem) readonly on device 31:4.
Starting majestic: OK

After it:

  • the overlay on rootfs_data is mounted, and a test file written before the upgrade is still there;
  • sysbackup=0, and the boot counter at 0x1a80000 is unchanged;
  • Majestic streams H.265 2688×1512 at 30 fps.

sysupgrade writes the kernel and rootfs partitions by name from /proc/mtd, so the kernel landing at 0xf0000 needs nothing from it.

2. BR2_LINUX_KERNEL_UIMAGE_LOADADDR. It turned out to be redundant: the S3L kernel sets CONFIG_AMBARELLA_ZRELADDR=0x00208000, which mach-ambarella/Makefile.boot uses for the uImage load address. That's why firmware's s3l_ultimate doesn't set it. I removed the line. The rebuilt uImage still loads and enters at 0x00208000 (the boot above).

3. sysbackup with no retry. Agreed, and noted. It has worked on every boot here. If it ever failed, the fallback is Dahua's own backup system, and its TFTP recovery still works for reflashing.

4. Nits. Fixed: the double blank line in the fragment, and the stray # and blank line before exit 0.

On Qodo's "lite unless flash size requires otherwise": OpenIPC replaced s3l_lite with s3l_ultimate for S3L in OpenIPC/firmware#2787, and this board has 32 MB of flash, so the ultimate image fits comfortably (uImage 1604K of 2048K, rootfs 5128K of 8192K).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants