Skip to content

ambarella-osdrv-s3l: build an open-source load_ucode - #2766

Merged
openipc-ai merged 3 commits into
OpenIPC:masterfrom
DeathCamel58:s3l-load-ucode
Nov 6, 2026
Merged

openipc-ai merged 3 commits into
OpenIPC:masterfrom
DeathCamel58:s3l-load-ucode

Conversation

@DeathCamel58

Copy link
Copy Markdown
Contributor

Problem

The S3L DSP boots from microcode (orccode.bin, orcme.bin, default_binary.bin) that has to be copied into the window dsp.ko exposes through /dev/ucode. Nothing in ambarella-osdrv-s3l does that, so on an OpenIPC image the DSP never starts. The vendor's load_ucode can't be shipped, and it is a glibc armhf binary that would not run on the musl s3l_lite image anyway.

This adds load_ucode, built from source in the package. It asks dsp.ko for the layout (UCODE_GET_INFO), maps the window, copies the three files to their offsets, tells the driver they are loaded, and prints the version. It takes the same load_ucode DIR argument as the vendor tool, so load_ambarella from #2764 picks it up unchanged. Every file is checked against its slot before any is written, so a bad set cannot leave old and new microcode mixed in the window.

The microcode itself is still not part of this package; how it is shipped stays the licensing question from #2764.

Hardware tested on

Ambarella S3L, Amcrest IP4M-1026E (Dahua IPC-HFW1430SN), OV4689. Netbooted image: 3.10.104 from s3l.generic.config with this package's modules, and the microcode from the camera's own stock firmware. load_ucode was built static with the CI musl toolchain (toolchain.hisilicon-hi3516cv100). The package also builds through Buildroot with a glibc armhf toolchain. S3L is in UNBUILT_FAMILIES, so CI does not build it.

Evidence

Each run is a power-on boot (power removed), so the window starts with no microcode in it. Window contents are the md5 of each slot read back from the DSP window, compared with the files.

Before (no loader on the image):

### load_ucode on the image: none
### window after power-on, no loader run:
  orccode.bin          window=bea2b9e2cab6 differs  (vs microcode)
  orcme.bin            window=dbec452e4d92 differs  (vs microcode)
  default_binary.bin   window=2c5e9c77e0b4 differs  (vs microcode)
### enter idle with no microcode loaded
dsp_set_op_mode(381): [debug]: dsp_set_op_mode: dsp->op_mode(255), op_mode(0).
dsp_set_op_mode(408): Entering DSP mode [0]...
dsp_set_op_mode(413): [TIMEOUT] Enter mode event, current op_mode 255.

After (next power-on, load_ucode before the first DSP start):

### window after power-on:
  orccode.bin          window=b96d9872eb43 differs  (vs microcode)
  orcme.bin            window=212174959532 differs  (vs microcode)
  default_binary.bin   window=2654fb2a6773 differs  (vs microcode)
### load_ucode:
orccode.bin: 1648217 bytes at +0x000000
orcme.bin: 201712 bytes at +0x300000
default_binary.bin: 399808 bytes at +0x3a0000
ucode version 2017/2/22 267837.266889
  orccode.bin          window=cc0eb38c5688 MATCH    (vs microcode)
  orcme.bin            window=26a9a5ce7744 MATCH    (vs microcode)
  default_binary.bin   window=c4d731dcc9bc MATCH    (vs microcode)
### first DSP start:
dsp_set_op_mode(381): [debug]: dsp_set_op_mode: dsp->op_mode(255), op_mode(0).
dsp_set_op_mode(408): Entering DSP mode [0]...
dsp_set_op_mode(416): [Done] Entered mode [0].

The version line matches what the vendor's load_ucode prints for the same files. With the microcode loaded this way the pipeline streams. From a reboot where the vendor tool was not on the camera:

dsp_set_op_mode(416): [Done] Entered mode [0].
dsp_set_op_mode(416): [Done] Entered mode [1].
    START_ENCODE           0x40045613 -> ok
 46:        171       VIC  vcap
 46:        201       VIC  vcap
luma (1/64 sampled): min=0 max=134 avg=24

Error paths:

# load_ucode /tmp/nonexistent
load_ucode: /tmp/nonexistent/orccode.bin: No such file or directory          (exit 1)
# load_ucode /tmp/t/big        (orcme.bin one byte larger than its 0xa0000 slot)
load_ucode: /tmp/t/big/orcme.bin is 655361 bytes, only 655360 fit            (exit 1, window unchanged)

Scope

  • No kernel patches under general/package/all-patches/linux/
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source

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

Copy link
Copy Markdown

PR Summary by Qodo

Build an open-source S3L DSP microcode loader

✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Build and install a source-based S3L loader compatible with the existing load_ucode DIR
 interface.
• Use the DSP driver’s layout to place microcode, checking file sizes before writing.
• Leave the microcode files outside the package; their distribution remains a separate licensing
 question.
Diagram

graph TD
  D["Microcode directory"] --> L["load_ucode"] --> I["Driver layout"] --> V{"Files fit?"} -->|yes| W["DSP window"] --> N["Notify driver"] --> P["Read version"]
  V -->|no| E["Exit failure"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Stage every file in memory before writing
  • ➕ Also prevents read failures during copying from leaving a partially updated window.
  • ➖ Requires additional RAM for the full microcode set on an embedded device.

Recommendation: The current preflight-and-stream approach is a reasonable memory-conscious choice for S3L. Its guarantee is limited to failures detected before copying: a read error during copying can still leave mixed or partial contents, so staging is worth considering if that failure mode must be excluded.

Files changed (3) +198 / -2

Enhancement (3) +198 / -2
ambarella-osdrv-s3l.mkBuild and install the S3L microcode loader +7/-2

Build and install the S3L microcode loader

• Sets the package source to its local src directory, builds load_ucode with the target toolchain, and installs it in /usr/bin. The microcode files themselves are not packaged.

general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk

MakefileAdd a target-toolchain build rule for load_ucode +2/-0

Add a target-toolchain build rule for load_ucode

• Compiles load_ucode.c using the supplied compiler, flags, and linker flags.

general/package/legacy/ambarella-osdrv-s3l/src/Makefile

load_ucode.cLoad DSP microcode through the driver-provided window +189/-0

Load DSP microcode through the driver-provided window

• Implements the 'load_ucode DIR' utility using /dev/ucode ioctls to obtain the layout, map the window, copy files, signal completion, and report the version. It checks file availability and slot sizes before mapping, and reports errors with a nonzero exit status.

general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. A failed read leaves mixed microcode ✓ Resolved
Description
copy_file() writes each read directly into the mapped DSP window, so its error path cannot undo
bytes already copied. If a file is truncated after preflight or a read fails, the loader exits
without marking the set loaded but leaves earlier files or part of the current file in the window.

2. Empty files can be reported as loaded ✓ Resolved
Description
open_file() rejects files larger than their slots but accepts a zero-byte file, for which
copy_file() performs no read. If the driver accepts UCODE_LOADED, the loader returns success
despite leaving that slot untouched; a zero-length microcode file therefore reaches the DSP start
path without being copied.

3. License collection misses the new source 🐞 Bug ≡ Correctness
Description
AMBARELLA_OSDRV_S3L_SITE now points to src, while the package still declares LICENSE as its
license file and that source directory contains only the Makefile and C source. When Buildroot
collects this package's license files, it cannot find the declared file in the package source.
Code

general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk[R7-8]

+AMBARELLA_OSDRV_S3L_SITE_METHOD = local
+AMBARELLA_OSDRV_S3L_SITE = $(AMBARELLA_OSDRV_S3L_PKGDIR)/src
Evidence
The changed local SITE selects the src directory, but the declared LICENSE file is absent there; the
repository's root LICENSE is outside that local package source.

general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk[7-10]
general/package/legacy/ambarella-osdrv-s3l/src/Makefile[1-2]

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 local package source omits the LICENSE file named by the package recipe.
## Fix Focus Areas
- general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk[7-10]
- general/package/legacy/ambarella-osdrv-s3l/src/Makefile[1-2]
## Recommended Fix
Add the applicable LICENSE file under src so it is present in Buildroot's package source directory, or adjust the package layout and license declaration together so license collection reads the correct file.

ⓘ 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 switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@DeathCamel58

Copy link
Copy Markdown
Contributor Author

On finding 3 (license collection): this is resolved in 0c1c1d7, and the finding above still quotes the old state. The package no longer declares AMBARELLA_OSDRV_S3L_LICENSE_FILES, so legal-info has no missing file to copy.

That follows the tree's convention for in-tree OpenIPC code. openipc-failsafe, openipc-precision-time, reset-button, usb-dual-role and gpiostep-openipc all declare LICENSE = MIT with no LICENSE_FILES, because the code is covered by the repository's root LICENSE. load_ucode.c also carries SPDX-License-Identifier: MIT. A package ships its own license file only when its license differs from the repository's, as majestic-fonts does for its font.

The S3L DSP boots from microcode that has to be copied into a window dsp.ko
exposes through /dev/ucode, and nothing in the package did that. The vendor's
load_ucode can't be shipped, and it is a glibc armhf binary that would not
run on the musl s3l_lite image anyway.

load_ucode asks dsp.ko for the layout (UCODE_GET_INFO), maps the window,
copies orccode.bin, orcme.bin and default_binary.bin to their offsets, tells
the driver they are loaded and prints the version. It takes the same
`load_ucode DIR` argument as the vendor tool, so load_ambarella can call it
unchanged. Every file is checked against its slot before any is written, so
a bad set cannot leave old and new microcode mixed in the window.

The microcode itself is still not part of this package.
… files

load_ucode checked each file's size before touching the window but then
streamed it in with read(), so a read that failed after the check (a file
truncated meanwhile, or not a regular file) still left old and new microcode
mixed in the window. Read every file into memory first and copy into the
window only once all of them are in; the set is about 2.2 MB.

A zero-byte file passed the size check and left its slot untouched while the
tool reported success. Reject it.

AMBARELLA_OSDRV_S3L_LICENSE_FILES named a LICENSE that the package source
(now src/) does not contain, so legal-info would fail to copy it. Drop it, as
the tree's other local-source packages do; load_ucode.c carries an SPDX
identifier.
…ckage

The header said the load_ucode tool was Ambarella's and not part of the
package. The package now builds it from src/, so only the microcode still
has to be put in /lib/firmware.

@openipc-ai openipc-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks right, and it removes the glibc blocker. I tested it independently, since S3L's toolchain now exists.

Code. The structs and ioctl numbers match the driver's interface, and the files are read in full before the window is touched. On the Qodo license finding your reasoning holds: none of those five packages declares LICENSE_FILES. One small correction: gpiostep-openipc is GPL-2.0, not MIT, which doesn't affect the point.

Run against the real dsp.ko, on the new s3l QEMU machine (OpenIPC/qemu-bench#233), built with the published toolchain.ambarella-s3l:

  • All three files land at +0x000000, +0x300000 and +0x3a0000, and it prints the version I stamped into a test file, 2017/2/22 267837.266889. That confirms the ABI end to end.
  • Five bad sets (missing directory, missing file, empty file, and oversize orcme.bin and orccode.bin) each exit 1 with a clear message, and a checksum of the whole window is identical after each.

Full make BOARD=s3l_lite at this commit, using the published toolchain. This is the first time that's been possible, and it took 3 min 36 s:

  • uImage is 1582 of 2048 KB, and rootfs.squashfs is 3384 of 5120 KB, so it fits the 8 MB layout.
  • load_ucode and the modules are in the image.
  • The toolchain targets cortex-a9 with neon. A scan of busybox and libc.so for sdiv, udiv and VFPv4 instructions found no real ones: the 16 raw hits were jump tables and float constants read as code, and gcc emits none at these flags even with -O3 -ffast-math.
  • That image boots on the QEMU machine to openipc-s3l login:. S70vendor ran load_ambarella by itself, which reported the missing microcode, with no oops or illegal instruction. I only unpacked the rootfs into an initramfs to run it there.

Not covered here: my microcode was made up, so this proves the loader and not a DSP boot; your power-on logs are the evidence for that. Your hardware runs were also glibc armhf, so the musl softfp image hasn't run on a real S3L. That's still worth doing, as you offered.

The toolchain is now published, so you don't need to build your own for that test. The two remaining things to settle are the microcode licensing question and whether the 3A work needs the stock glibc libraries, because that would change s3l_lite itself.

@DeathCamel58

Copy link
Copy Markdown
Contributor Author

This is tested and working on hardware with real .bins. Including microcode would be a separate PR once discussed and decided on. The 3A work does not need the stock glibc libraries anymore. That will likely be rolled into another app/service or divinus

@openipc-ai
openipc-ai merged commit d6783f3 into OpenIPC:master Nov 6, 2026
48 checks passed
@openipc-ai

Copy link
Copy Markdown
Collaborator

Merged, thanks. Your hardware runs are the evidence for the DSP boot, and they cover what my made-up microcode couldn't.

Good news on 3A: if it no longer needs the glibc libraries, musl softfp stays the right target, and the published toolchain needs no rebuild.

Agreed that microcode gets its own PR. One thing for that discussion: yours is 2017/2/22 267837.266889 from the camera's stock firmware, and that's the version your runs prove with these prebuilt modules. If a different version is proposed, it would need its own test on hardware.

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