From 5b6269ec5e2b4d13161ac5e82119cfe5c0f37c7a Mon Sep 17 00:00:00 2001 From: Dylan Corrales Date: Fri, 6 Nov 2026 00:40:15 -0500 Subject: [PATCH 1/3] ambarella-osdrv-s3l: build an open-source load_ucode 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. --- .../ambarella-osdrv-s3l.mk | 9 +- .../legacy/ambarella-osdrv-s3l/src/Makefile | 2 + .../ambarella-osdrv-s3l/src/load_ucode.c | 189 ++++++++++++++++++ 3 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 general/package/legacy/ambarella-osdrv-s3l/src/Makefile create mode 100644 general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c diff --git a/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk b/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk index 79e63f6ca9..665618b576 100644 --- a/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk +++ b/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk @@ -4,11 +4,15 @@ # ################################################################################ -AMBARELLA_OSDRV_S3L_VERSION = -AMBARELLA_OSDRV_S3L_SITE = +AMBARELLA_OSDRV_S3L_SITE_METHOD = local +AMBARELLA_OSDRV_S3L_SITE = $(AMBARELLA_OSDRV_S3L_PKGDIR)/src AMBARELLA_OSDRV_S3L_LICENSE = MIT AMBARELLA_OSDRV_S3L_LICENSE_FILES = LICENSE +# load_ucode puts the DSP microcode in place; the microcode itself is not shipped. +define AMBARELLA_OSDRV_S3L_BUILD_CMDS + $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) load_ucode +endef define AMBARELLA_OSDRV_S3L_INSTALL_TARGET_CMDS $(INSTALL) -m 755 -d $(TARGET_DIR)/etc/sensors @@ -19,6 +23,7 @@ define AMBARELLA_OSDRV_S3L_INSTALL_TARGET_CMDS $(INSTALL) -m 755 -d $(TARGET_DIR)/usr/bin $(INSTALL) -m 755 -t $(TARGET_DIR)/usr/bin $(AMBARELLA_OSDRV_S3L_PKGDIR)/files/script/load* + $(INSTALL) -m 755 -t $(TARGET_DIR)/usr/bin $(@D)/load_ucode $(INSTALL) -m 755 -d $(TARGET_DIR)/usr/lib/sensors # $(INSTALL) -m 644 -t $(TARGET_DIR)/usr/lib/sensors $(AMBARELLA_OSDRV_S3L_PKGDIR)/files/sensor/*.so diff --git a/general/package/legacy/ambarella-osdrv-s3l/src/Makefile b/general/package/legacy/ambarella-osdrv-s3l/src/Makefile new file mode 100644 index 0000000000..8f9155cc95 --- /dev/null +++ b/general/package/legacy/ambarella-osdrv-s3l/src/Makefile @@ -0,0 +1,2 @@ +load_ucode: load_ucode.c + $(CC) $(CFLAGS) -Wall -Wextra -o $@ $< $(LDFLAGS) diff --git a/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c b/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c new file mode 100644 index 0000000000..277d47e06f --- /dev/null +++ b/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: MIT +/* + * Load the Ambarella S3L DSP microcode through /dev/ucode. + * + * load_ucode DIR + * + * A drop-in for the vendor's load_ucode: dsp.ko describes where each file + * goes (UCODE_GET_INFO), the files are copied into the microcode window it + * maps, and the version the DSP will run is printed. The DSP itself is not + * started here; it boots when IAV first enters the idle state. + * + * The window is mapped uncached by dsp.ko, so no flush is needed. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UCODE_DEV "/dev/ucode" +#define UCODE_MAX_ITEMS 4 + +struct ucode_item { + uint32_t addr_offset; + char filename[24]; +}; + +struct ucode_info { + uint32_t map_size; + uint32_t nr_item; + struct ucode_item item[UCODE_MAX_ITEMS]; +}; + +struct ucode_version { + uint16_t year; + uint8_t month; + uint8_t day; + uint32_t edition_num; + uint32_t edition_ver; + uint32_t arch; +}; + +#define UCODE_GET_INFO _IOR('u', 0, struct ucode_info) +#define UCODE_GET_VERSION _IOR('u', 1, struct ucode_version) +#define UCODE_LOADED _IOW('u', 2, uint32_t) + +struct ucode_file { + int fd; + size_t size; +}; + +static int open_file(const char *dir, const char *name, size_t room, + struct ucode_file *f) +{ + char path[256]; + struct stat st; + + snprintf(path, sizeof(path), "%s/%s", dir, name); + if ((f->fd = open(path, O_RDONLY)) < 0 || fstat(f->fd, &st) < 0) { + fprintf(stderr, "load_ucode: %s: %s\n", path, strerror(errno)); + return -1; + } + if ((size_t)st.st_size > room) { + fprintf(stderr, "load_ucode: %s is %lld bytes, only %zu fit\n", + path, (long long)st.st_size, room); + return -1; + } + f->size = st.st_size; + return 0; +} + +static int copy_file(const struct ucode_file *f, const char *name, + uint8_t *dst) +{ + size_t done = 0; + + while (done < f->size) { + ssize_t n = read(f->fd, dst + done, f->size - done); + if (n <= 0) { + fprintf(stderr, "load_ucode: reading %s: %s\n", name, + n ? strerror(errno) : "short file"); + return -1; + } + done += n; + } + return 0; +} + +int main(int argc, char **argv) +{ + struct ucode_info info; + struct ucode_version ver; + struct ucode_file files[UCODE_MAX_ITEMS]; + uint8_t *map; + int fd, ret = 1; + + for (int i = 0; i < UCODE_MAX_ITEMS; i++) + files[i].fd = -1; + + if (argc != 2) { + fprintf(stderr, "Usage: %s DIR\n", argv[0]); + return 1; + } + + if ((fd = open(UCODE_DEV, O_RDWR)) < 0) { + fprintf(stderr, "load_ucode: %s: %s\n", UCODE_DEV, + strerror(errno)); + return 1; + } + + memset(&info, 0, sizeof(info)); + if (ioctl(fd, UCODE_GET_INFO, &info) < 0) { + fprintf(stderr, "load_ucode: UCODE_GET_INFO: %s\n", + strerror(errno)); + goto out; + } + if (!info.map_size || !info.nr_item || + info.nr_item > UCODE_MAX_ITEMS) { + fprintf(stderr, "load_ucode: bad layout: %u items in 0x%x\n", + info.nr_item, info.map_size); + goto out; + } + + /* Every file is checked before any is written: a set that fails half + * way would leave old and new microcode mixed in the window. */ + for (uint32_t i = 0; i < info.nr_item; i++) { + struct ucode_item *it = &info.item[i]; + /* Each file may run up to the next one's offset, the last one + * to the end of the window. */ + uint32_t end = info.map_size; + + for (uint32_t j = 0; j < info.nr_item; j++) + if (info.item[j].addr_offset > it->addr_offset && + info.item[j].addr_offset < end) + end = info.item[j].addr_offset; + + it->filename[sizeof(it->filename) - 1] = '\0'; + if (it->addr_offset >= info.map_size) { + fprintf(stderr, "load_ucode: %s at 0x%x is outside 0x%x\n", + it->filename, it->addr_offset, info.map_size); + goto out; + } + if (open_file(argv[1], it->filename, end - it->addr_offset, + &files[i])) + goto out; + } + + map = mmap(NULL, info.map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (map == MAP_FAILED) { + fprintf(stderr, "load_ucode: mmap 0x%x: %s\n", info.map_size, + strerror(errno)); + goto out; + } + + for (uint32_t i = 0; i < info.nr_item; i++) { + struct ucode_item *it = &info.item[i]; + + if (copy_file(&files[i], it->filename, map + it->addr_offset)) + goto unmap; + printf("%s: %zu bytes at +0x%06x\n", it->filename, + files[i].size, it->addr_offset); + } + + if (ioctl(fd, UCODE_LOADED, 0) < 0) { + fprintf(stderr, "load_ucode: UCODE_LOADED: %s\n", + strerror(errno)); + goto unmap; + } + + if (ioctl(fd, UCODE_GET_VERSION, &ver) == 0) + printf("ucode version %u/%u/%u %u.%u\n", ver.year, ver.month, + ver.day, ver.edition_num, ver.edition_ver); + ret = 0; + +unmap: + munmap(map, info.map_size); +out: + for (int i = 0; i < UCODE_MAX_ITEMS; i++) + if (files[i].fd >= 0) + close(files[i].fd); + close(fd); + return ret; +} From c24506dfac2102d8405cc385d8a523a7b75c76fd Mon Sep 17 00:00:00 2001 From: Dylan Corrales Date: Fri, 6 Nov 2026 01:01:09 -0500 Subject: [PATCH 2/3] ambarella-osdrv-s3l: stage the microcode before writing, refuse empty 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. --- .../ambarella-osdrv-s3l.mk | 1 - .../ambarella-osdrv-s3l/src/load_ucode.c | 59 +++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk b/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk index 665618b576..0c896dc3af 100644 --- a/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk +++ b/general/package/legacy/ambarella-osdrv-s3l/ambarella-osdrv-s3l.mk @@ -7,7 +7,6 @@ AMBARELLA_OSDRV_S3L_SITE_METHOD = local AMBARELLA_OSDRV_S3L_SITE = $(AMBARELLA_OSDRV_S3L_PKGDIR)/src AMBARELLA_OSDRV_S3L_LICENSE = MIT -AMBARELLA_OSDRV_S3L_LICENSE_FILES = LICENSE # load_ucode puts the DSP microcode in place; the microcode itself is not shipped. define AMBARELLA_OSDRV_S3L_BUILD_CMDS diff --git a/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c b/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c index 277d47e06f..f69befe8fd 100644 --- a/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c +++ b/general/package/legacy/ambarella-osdrv-s3l/src/load_ucode.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -50,58 +51,58 @@ struct ucode_version { #define UCODE_LOADED _IOW('u', 2, uint32_t) struct ucode_file { - int fd; + uint8_t *data; size_t size; }; -static int open_file(const char *dir, const char *name, size_t room, +static int read_file(const char *dir, const char *name, size_t room, struct ucode_file *f) { char path[256]; struct stat st; + size_t done = 0; + int fd, ret = -1; snprintf(path, sizeof(path), "%s/%s", dir, name); - if ((f->fd = open(path, O_RDONLY)) < 0 || fstat(f->fd, &st) < 0) { + if ((fd = open(path, O_RDONLY)) < 0 || fstat(fd, &st) < 0) { fprintf(stderr, "load_ucode: %s: %s\n", path, strerror(errno)); - return -1; + goto out; } - if ((size_t)st.st_size > room) { - fprintf(stderr, "load_ucode: %s is %lld bytes, only %zu fit\n", + if (st.st_size <= 0 || (size_t)st.st_size > room) { + fprintf(stderr, "load_ucode: %s is %lld bytes, needs 1..%zu\n", path, (long long)st.st_size, room); - return -1; + goto out; } f->size = st.st_size; - return 0; -} - -static int copy_file(const struct ucode_file *f, const char *name, - uint8_t *dst) -{ - size_t done = 0; + if (!(f->data = malloc(f->size))) { + fprintf(stderr, "load_ucode: %s: %s\n", path, strerror(errno)); + goto out; + } while (done < f->size) { - ssize_t n = read(f->fd, dst + done, f->size - done); + ssize_t n = read(fd, f->data + done, f->size - done); if (n <= 0) { - fprintf(stderr, "load_ucode: reading %s: %s\n", name, - n ? strerror(errno) : "short file"); - return -1; + fprintf(stderr, "load_ucode: reading %s: %s\n", path, + n ? strerror(errno) : "file shrank"); + goto out; } done += n; } - return 0; + ret = 0; +out: + if (fd >= 0) + close(fd); + return ret; } int main(int argc, char **argv) { struct ucode_info info; struct ucode_version ver; - struct ucode_file files[UCODE_MAX_ITEMS]; + struct ucode_file files[UCODE_MAX_ITEMS] = { 0 }; uint8_t *map; int fd, ret = 1; - for (int i = 0; i < UCODE_MAX_ITEMS; i++) - files[i].fd = -1; - if (argc != 2) { fprintf(stderr, "Usage: %s DIR\n", argv[0]); return 1; @@ -126,8 +127,8 @@ int main(int argc, char **argv) goto out; } - /* Every file is checked before any is written: a set that fails half - * way would leave old and new microcode mixed in the window. */ + /* Every file is read in full before the window is touched: a set that + * failed half way would leave old and new microcode mixed in it. */ for (uint32_t i = 0; i < info.nr_item; i++) { struct ucode_item *it = &info.item[i]; /* Each file may run up to the next one's offset, the last one @@ -145,7 +146,7 @@ int main(int argc, char **argv) it->filename, it->addr_offset, info.map_size); goto out; } - if (open_file(argv[1], it->filename, end - it->addr_offset, + if (read_file(argv[1], it->filename, end - it->addr_offset, &files[i])) goto out; } @@ -161,8 +162,7 @@ int main(int argc, char **argv) for (uint32_t i = 0; i < info.nr_item; i++) { struct ucode_item *it = &info.item[i]; - if (copy_file(&files[i], it->filename, map + it->addr_offset)) - goto unmap; + memcpy(map + it->addr_offset, files[i].data, files[i].size); printf("%s: %zu bytes at +0x%06x\n", it->filename, files[i].size, it->addr_offset); } @@ -182,8 +182,7 @@ int main(int argc, char **argv) munmap(map, info.map_size); out: for (int i = 0; i < UCODE_MAX_ITEMS; i++) - if (files[i].fd >= 0) - close(files[i].fd); + free(files[i].data); close(fd); return ret; } From ed1321c32551d83d1cd75cb1be846d4d178f3c39 Mon Sep 17 00:00:00 2001 From: Dylan Corrales Date: Fri, 6 Nov 2026 01:10:41 -0500 Subject: [PATCH 3/3] ambarella-osdrv-s3l: load_ambarella: load_ucode now comes with the package 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. --- .../ambarella-osdrv-s3l/files/script/load_ambarella | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/general/package/legacy/ambarella-osdrv-s3l/files/script/load_ambarella b/general/package/legacy/ambarella-osdrv-s3l/files/script/load_ambarella index 0a9fed01f2..c51e468b70 100755 --- a/general/package/legacy/ambarella-osdrv-s3l/files/script/load_ambarella +++ b/general/package/legacy/ambarella-osdrv-s3l/files/script/load_ambarella @@ -7,11 +7,10 @@ # them and the nodes have to be made here. The major comes from /proc/devices; # the minors are fixed. # -# The microcode (orccode.bin, orcme.bin, default_binary.bin) and the load_ucode -# tool are Ambarella's and are not part of this package, and the stock tool is -# a glibc armhf binary that will not run on this musl image. Without them the -# drivers load and the DSP does not start, so no video. Drop working ones in -# place and this script uses them. +# The microcode (orccode.bin, orcme.bin, default_binary.bin) is Ambarella's and +# is not part of this package; load_ucode, which puts it in place, is built from +# the package's src/. Without the microcode the drivers load and the DSP does +# not start, so no video. Drop it in /lib/firmware and this script loads it. vendor=$(ipcinfo -v) ucode_dir=/lib/firmware