Skip to content

ambarella: s3l: replace s3l_lite with s3l_ultimate - #2787

Merged
openipc-ai merged 1 commit into
masterfrom
ambarella-s3l-ultimate
Nov 7, 2026
Merged

openipc-ai merged 1 commit into
masterfrom
ambarella-s3l-ultimate

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

Problem

master doesn't build S3L:

- uImage: [1587KB/2048KB]
- rootfs.squashfs: [5516KB/5120KB]
-- size exceeded by: 396KB
make[1]: *** [Makefile:202: repack] Error 1

#2782 (microcode) and #2785 (Majestic) each passed CI, because each was tested without the other. That first measurement had #2781 applied, which only changes the kernel. The next nightly's S3L job would fail, as Firmware (s3l_lite) already has on #2786.

Change

s3l_lite becomes s3l_ultimate: BR2_OPENIPC_VARIANT="ultimate" and BR2_OPENIPC_FLASH_SIZE="16". ci-matrix.py and toolchain.yml follow. Nothing else names the old board.

lite means an 8 MB board, so raising its flash size isn't an option. The split NOR layout doesn't fit either:

KB
uImage + rootfs 1587 + 5516 = 7103
Split cap with the firmware at 0xF0000, as on the IP4M-1026E 8192 - 960 - 256 = 6976

ambarella-s3l also has no split code. The 3.10 port on gk710x (OpenIPC/linux#152) was 344 lines.

Not the same as other ultimate boards:

  • BR2_OPENIPC_MAJESTIC stays at its default, lite, because that's the only S3L build published: majestic.s3l.ultimate.master.tar.bz2 returns 403.
  • This PR adds none of the extra packages some ultimate boards carry.

Effect on images: the published names become openipc.s3l-nor-ultimate.tgz and openipc.s3l-nand-ultimate.tgz, and S3L cameras with only 8 MB NOR are no longer covered. The cameras in this work don't need that: the IP4M-1026E has an 8192 KB rootfs slot, and the Hikvision S3Lm is NAND. No S3L image has ever been published, so there are no upgrades to carry over.

@DeathCamel58 your s3l_lite_amcrest-ip4m1026e profile will want renaming to match when it goes upstream.

Hardware tested on

Not run on a camera. Same kernel config and packages as before, apart from the variant and the flash size. @DeathCamel58's runs on #2781 and #2782 used a 16 MB layout of the same size on the IP4M-1026E, but with different builds.

Evidence

Full make BOARD=s3l_ultimate on this branch, without #2781:

- uImage: [1582KB/2048KB]
- rootfs.squashfs: [5576KB/8192KB]
- uImage: [1582KB/4096KB]
- rootfs.ubi: [11392KB/16384KB]
openipc.s3l-nand-ultimate.tgz
openipc.s3l-nor-ultimate.tgz
BUILD_OPTION=ultimate
BUILD_PLATFORM=s3l_ultimate
BR2_OPENIPC_MAJESTIC="lite"

ci-matrix.py --self-test:

ci-matrix: self-test ok (124 boards, 164 packages, 76 cases)

Scope

  • No kernel patches under general/package/all-patches/linux/ (those go to OpenIPC/linux)
  • No files specific to a single retail camera model (those go to OpenIPC/builder)
  • No probing or bring-up tooling (that goes to OpenIPC/ipctool)
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • Package sources come from an OpenIPC repository, and any version bump keeps at least the specificity of the pin it replaces (a new package should pin a full 40-character SHA)
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source
  • New code is selected by a defconfig, so CI actually builds it

The microcode from #2782 and Majestic from #2785 together overflow the lite
rootfs slot, 5516 KB against 5120 KB, so make BOARD=s3l_lite fails on master.
Each passed CI alone, because neither base had the other.

lite means an 8 MB board, so its flash size can't grow. The split NOR layout
doesn't rescue it: with the firmware at 0xF0000, as on the IP4M-1026E, it caps
uImage and rootfs at 6976 KB against 7103 KB, and this kernel has no split code.

Majestic stays the lite build, the only one published for S3L.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Replace S3L lite with a 16 MB ultimate build

🐞 Bug fix ⚙️ Configuration changes 🕐 10-20 Minutes

Grey Divider

AI Description

• Replace the overflowing 8 MB S3L build with a 16 MB ultimate configuration.
• Update CI and toolchain selections to build the renamed board.
• Keep S3L’s existing packages and lite Majestic build; NOR and NAND archive names change.
Diagram

graph TD
  Matrix["CI matrix"] --> Build["S3L build"] --> Repack["Image packaging"] --> NOR["Ultimate NOR"]
  Repack --> NAND["Ultimate NAND"]
  Toolchain["Toolchain workflow"] --> Build
  Config["S3L defconfig"] --> Build
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Reduce the lite image
  • ➕ Could retain support for 8 MB S3L NOR boards and the lite image name.
  • ➖ Would require removing or shrinking content to recover at least 396 KB of rootfs space.
2. Add split NOR support
  • ➕ Could avoid fixed kernel and rootfs slots on suitable hardware.
  • ➖ Requires S3L-specific implementation, and the described 8 MB layout still cannot hold the current combined images.

Recommendation: Use the 16 MB ultimate configuration: it addresses the measured overflow without changing the S3L package set or adding unsupported split-layout code. Reviewers should explicitly accept that 8 MB NOR cameras are no longer covered and that published image names change.

Files changed (3) +5 / -5

Bug fix (1) +2 / -2
s3l_ultimate_defconfigConfigure S3L for the 16 MB ultimate variant +2/-2

Configure S3L for the 16 MB ultimate variant

• Sets the S3L variant to ultimate and flash size to 16 MB, providing room for the current rootfs and changing the generated archive names. The existing kernel and package selections remain unchanged, including the default lite Majestic build.

br-ext-chip-ambarella/configs/s3l_ultimate_defconfig

Other (2) +3 / -3
ci-matrix.pySelect s3l_ultimate in firmware CI +2/-2

Select s3l_ultimate in firmware CI

• Replaces s3l_lite in the complete build matrix and smoke-board set. This keeps S3L represented in firmware CI after the board rename.

.github/scripts/ci-matrix.py

toolchain.ymlTarget s3l_ultimate in toolchain CI +1/-1

Target s3l_ultimate in toolchain CI

• Changes the Ambarella board selection from s3l_lite to s3l_ultimate so toolchain CI follows the renamed configuration.

.github/workflows/toolchain.yml

@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. S3L image layout changes with no camera boot test 📘 Rule violation ☼ Reliability
Description
The s3l_ultimate defconfig changes BR2_OPENIPC_VARIANT from lite to ultimate and
BR2_OPENIPC_FLASH_SIZE from 8 to 16, altering the firmware image’s flash partition layout and
rootfs size limit. Because the PR states “Not run on a camera” and provides only a host-side
image-size check and a ci-matrix.py self-test, there is no evidence that the resulting image
boots, mounts its rootfs, or streams on an affected S3L board.
Code

br-ext-chip-ambarella/configs/s3l_ultimate_defconfig[R46-47]

+BR2_OPENIPC_VARIANT="ultimate"
+BR2_OPENIPC_FLASH_SIZE="16"
Evidence
The cited defconfig changes the variant and flash size used to build the image layout, so this is a
camera-firmware change rather than CI-only selection or metadata. The PR description explicitly says
it was not run on a camera and cites only build-size and CI self-test results; those results do not
establish that an affected board can boot, mount the rootfs, and stream with the changed layout.

Rule 3: Hardware evidence is present and honest
br-ext-chip-ambarella/configs/s3l_ultimate_defconfig[46-47]
Best Practice: Repository guidelines
Best Practice: Repository guidelines

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 `s3l_ultimate` defconfig changes the variant to `ultimate` and flash size to 16 MB, altering the flash layout and rootfs size limit without a documented run on an affected camera.
## Fix Focus Areas
- br-ext-chip-ambarella/configs/s3l_ultimate_defconfig[46-47]
## Recommended Fix
Before merge, flash an image built from this defconfig, such as `openipc.s3l-nor-ultimate.tgz` or the applicable NAND image, to an affected S3L board such as the IP4M-1026E. Confirm the flash partition layout, boot and rootfs mount, and majestic streaming; record the boot log or dmesg, stream-check result, and before/after image sizes in the PR description. Mark verification steps complete only if they were run. If hardware testing is not possible, state that plainly and provide the available build-level evidence without presenting it as a camera test.

ⓘ 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 merged commit df8d098 into master Nov 7, 2026
44 of 45 checks passed
@openipc-ai
openipc-ai deleted the ambarella-s3l-ultimate branch November 7, 2026 08:43
@DeathCamel58

Copy link
Copy Markdown
Contributor

Tested on the IP4M-1026E with what master now ships for S3L: Majestic Lite (master+40b052d) with the SDK microcode. It streams, but only after a workaround on this board, because Majestic can't start preview on a fresh boot here.

How it was run. My IP4M-1026E profile on master 35c75dc3, switched to BR2_OPENIPC_VARIANT="ultimate" with Majestic, its fonts and web UI, and Ogg/Opus in place of Divinus, the way s3l_ultimate has them. The flash size stays 32 MB, since this board is 32 MB. It was netbooted from RAM, with my bench additions (initramfs, dev tools). The kernel has overlayfs (#2781) and the board DTB (OpenIPC/linux#178), and the boot log shows ambarella: ucode version 2018/10/28 284712.282649 (#2786).

Fresh boot: Majestic can't start preview.

majestic: Sensor ov4689, 2688x1512 at 30 fps
majestic: Chip s3l (IAV S3L55M), sensor ov4689
majestic: Cannot start the preview: Resource temporarily unavailable
majestic: Cannot start SDK
kernel: #iav_error# iav_check_vout(1098): preview B enabled, but VOUT 1 not configured

After the microcode loads, IAV's source buffers keep their default types, and on this board buffer 2 is a VOUT preview buffer:

source buffer 0: type 1
source buffer 1: type 0
source buffer 2: type 2 (preview)
source buffer 3: type 0

This board has no VOUT configured, so ENABLE_PREVIEW refuses. Majestic apparently leaves the buffer types alone, and a board with a VOUT set up wouldn't show this.

With the preview type cleared, Majestic works. While IAV is idle, I read the source-buffer setup, cleared the preview type and wrote it back (GET_SRCBUF_SETUP / SET_SRCBUF_SETUP with buffer 2's type set to 0, which is what the Divinus S3L backend does before every start). Then I restarted Majestic:

  • preview started (Entered mode [1]), and init 3A done, 3A running;
  • RTSP stream=0: H.265 2688×1512 at 30 fps, and a 6 s decode with no errors;
  • the picture is correctly exposed and white-balanced, with the timestamp OSD;
  • /image.jpg returns a snapshot.

IAV keeps the types across runs, so once they're cleared Majestic also starts on later restarts. After a reboot it needs clearing again. A one-shot step before S95majestic works around it today, but the fix belongs in Majestic: clear or ignore the VOUT-preview type before ENABLE_PREVIEW on boards with no VOUT. @widgetii, for when you're next in the S3L backend.

Statistics: on #2767 I mentioned that a streamer setting up 3A statistics after the first preview gets none on 284712. That doesn't apply to Majestic as it stands: it sends no AAA_STATISTICS_SETUP on this path. Its first preview after boot came up with 3A running and a correct picture, and the driver logged no statistics timeouts.

Minor, from the web UI: the dashboard's ISP panel says "This SoC reports no ISP metrics", so Majestic's S3L backend doesn't report exposure, gain or white balance. The picture is fine; it's just empty. The driver has the values: the VIN's GET_SHUTTER/GET_AGC ioctls give the shutter and gain, which is where the Divinus S3L backend reads them for its own 3A.

Timestamp OSD flickers. The date/time overlay flickers visibly. Measured on stream 0 at 30 fps: the mean brightness of the OSD box is about 112, but every 6th frame it drops to about 100 (25 of 151 frames in 5 s, evenly spaced). In those frames the box background is darker and more opaque, the text has lost its outline, and there's a torn strip along the top edge. That looks like the bitmap being redrawn about 5 times a second in place, in the overlay buffer the DSP is reading. Writing to an idle copy and then pointing the area at it (two pages per region, as the Divinus S3L backend does) avoids it.

Night mode: the IR-cut and IR LED pins work: /night/on and /night/off move the filter and LEDs correctly with irCutPin1 113, irCutPin2 111 and backlightPin 5. Automatic switching doesn't work on this board yet:

  • this board's light sensor is analog (ADC channel 1), and the S3L build has no ADC source (night_mode_source never reaches 3);
  • with lightMonitor: true it falls back to gain thresholds (source 2), and since the S3L backend reports no gain, it went to night straight away in a lit room.

IAV left stuck after a restart (seen once). Restarting Majestic (S95majestic restart) left IAV in state 0x7 with iav_disable_preview(1645): Failed to enter IDLE mode. Every later start failed with Cannot put the encoder in idle: Operation not permitted until a reboot. The driver waits for the DSP interruptibly, so a signal arriving during an idle/preview/encode state change can leave it like this. The Divinus S3L backend blocks signals around those ioctls for that reason.

@widgetii

widgetii commented Nov 7, 2026

Copy link
Copy Markdown
Member

@DeathCamel58 thanks for the thorough report. widgetii/majestic#1216 addresses it. It's merged, and majestic.s3l.lite.master.tar.bz2 has been rebuilt from it (master+f2fa260).

Preview on a fresh boot. Majestic now turns off source buffers 3–5 before ENABLE_PREVIEW. It only uses the first two, and the driver's VOUT check looks at buffers 3, 4 and 5 (PB, PA, PD). This replaces the one-shot step before S95majestic. I couldn't reproduce the failure on my S3Lm: its kernel refuses a preview type on buffer 2 (EINVAL). So it's tested on your workaround's evidence; please confirm on the IP4M-1026E.

IAV stuck after a restart. The idle, preview and encode ioctls now run with signals blocked. The 3A netlink thread, and the image-library threads it starts, never take a signal. On my camera, 30 starts each sent SIGTERM between 0 and 2.4 s in gave no refused state change.

ISP panel / night thresholds. /metrics now has three new gauges:

  • isp_again: AE's total gain in Q10, where 1024 = 1x. This is the number minThreshold/maxThreshold compare against, so pick thresholds from it.
  • isp_exptime
  • isp_exposureismax

The exposure had a bug too. img_get_sensor_shutter() returns sensor rows, so %{exposure} read a couple of microseconds. It's now rows × the VIN's row time, about 20 ms indoors here.

Light sensor on the ADC. Set nightMode.adcReadout: true and nightMode.adcChannel: 1, plus a minThreshold/maxThreshold pair. Majestic reads the driver's adcsys sysfs file, and the raw value shows as night_adc_raw on /metrics. Use adcInvert if your photocell reads high in the dark.

OSD flicker. Majestic already double-buffers. It draws into the half the encoder isn't reading, then points the area at it. The timer runs once a second unless the template uses %{ms}, so in-place redraws at 5 Hz aren't it.

On my camera I measured the OSD box's mean luma per frame on stream A. Without osd_insert_always it dipped about 1.7 levels twice a second, at fixed places in each GOP. It did so even with a static template and with motion detection off, so nothing was being redrawn. The PR sets osd_insert_always, and that rhythm is gone.

Your dips were 12 levels at 2688×1512p30, which I can't test here. Could you repeat your measurement with the new build?

@DeathCamel58

Copy link
Copy Markdown
Contributor

@widgetii thanks, that was quick. I tested master+f2fa260 on the IP4M-1026E (netbooted, freshly downloaded majestic.s3l.lite.master.tar.bz2, no workaround step anywhere):

Preview on a fresh boot: works now. From power-on, Majestic enters preview (Entered mode [1]), logs init 3A done / 3A running, and streams H.265 2688×1512 at 30 fps. There's no iav_check_vout error.

IAV stuck after a restart: couldn't reproduce it. I ran five S95majestic restarts, each stopping Majestic 0, 1, 2, 3 or 5 s into start-up. Every time, preview started again with no Failed to enter IDLE and no Cannot put the encoder in idle, and the stream came back.

OSD flicker: gone. I repeated the same measurement as before: the mean luma of the timestamp box on stream 0, 151 frames at 30 fps. The median is 92, and every frame is within about ±2 levels of it apart from one at +3.4, around a seconds change. Before, every 6th frame dropped by about 12 levels.

ISP metrics: isp_again and isp_exptime are there and track the scene: isp_again 6012 (about 5.9×) and isp_exptime 19997 µs indoors, with isp_again at 4897 a few minutes later.

ADC light sensor: it works, with two notes.

  • With adcReadout: true, adcChannel: 1 and thresholds of 256/672, night_mode_source stayed at 0 (manual) until I also set lightMonitor: true. Then it reads the ADC (night_mode_source 3), and night_adc_raw 2914 matches the driver's adcsys file.
  • Direction: this board's photocell reads high in light (about 2900 in a lit room, 15–200 covered), yet without adcInvert it switched to night in a lit room. With adcInvert: true it went back to day. So as far as I can tell, the default treats high as dark, the opposite of your note ("Use adcInvert if your photocell reads high in the dark"). Either the note or the default is the other way round.

With adcInvert: true, covering the sensor works both ways:

12:15:20  day    night_adc_raw 2834
12:15:26  night  night_adc_raw 15     (covered: IR-cut, IR LEDs, grayscale)
12:16:00  day    night_adc_raw 2865   (uncovered)

Each switch shows up in the first /metrics sample after the reading changes; the ADC value itself updates about every 5 s.

@widgetii

widgetii commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

@DeathCamel58 thanks for testing all of it, and on both ADC points you're right.

  • Direction: my note was backwards. With adcInvert: false (the default), majestic treats a high reading as dark: night above maxThreshold, day again below minThreshold. adcInvert: true is for a photocell that reads high in light, like the IP4M-1026E's (about 2900 lit, 15–200 covered). The settings form says it the right way round ("ADC light sensor reads high in daylight"); only my comment had it reversed.
  • lightMonitor: yes, that's by design and I left it out. lightMonitor is the master switch for every automatic source (GPIO pin, gain thresholds, ADC, automatic). With it off, night mode is manual whatever adcReadout says.

So for this board the full setup is:

nightMode:
  lightMonitor: true
  adcReadout: true
  adcChannel: 1
  adcInvert: true
  minThreshold: 256
  maxThreshold: 672

With adcInvert: true, it goes to night below 256 and back to day above 672. The 5 s between readings is nightMode.monitorDelay, which defaults to 5 s (1–60); lower it for a quicker reaction.

Good to hear the preview, the restarts and the OSD are sorted on the OV4689 board too.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants