Commit Graph
18 Commits
Author SHA1 Message Date
OpenIPC AI teamandGitHub c98adedbeb apfpv: stop writing outgoing.enabled, which majestic no longer has (#158)
`outgoing.enabled` was one switch for the whole list of places a camera
publishes to. Each destination has carried its own since
OpenIPC/majestic#701, and the section switch was removed rather than kept
as a second gate — so this line writes a key nothing reads. majestic
strips it from the tree on load and the next save drops it from the file.

The `.outgoing.server` line below it stays and is untouched: `cli` edits
majestic.yaml directly, majestic converts that address into a row of its
destination list on load, and the camera publishes exactly as before.
There is no way to write a list element through yaml-cli — its path
validator rejects the brackets — so the single-address spelling is the one
this script must keep using.
2026-09-14 12:28:45 +03:00
OpenIPC AI teamandGitHub ba182ac589 excludes: strip two sensors these boards cannot use, and a dead f2fs entry (#155)
sensor_sc3336_mipi.ko, sensor_os03b10_mipi.ko and sc3336.bin arrived in
sigmastar-osdrv-sensors after the ssc333 exclude lists were written, so
meari-speed-6s (JXF37) and babysense-see-hd-ip206 (GC2053) have been shipping
both sensors they cannot use. No board in the README table uses SC3336 or
OS03B10.

Scoped to those two devices on purpose. The other nine infinity6b0 profiles
carry the same two files, but ssc333_lite_tp-link-tapo-c110-v26 has "?" in the
sensor column and stripping a sensor blob from a board whose sensor nobody has
recorded is not a safe guess.

f2fs.ko has not been built since F2FS was switched off upstream
(# CONFIG_F2FS_FS is not set in hi3518ev200.generic.config), so five
hi3518ev200_lite lists have been asking the rootfs script to delete a file that
is never there, and every build prints "1 of 55 entries matched no file" for
it. The warning is how a stale exclude gets noticed, so it should not be
permanently on.

Measured by rebuilding the shipped nightly-20260909-e0a643f images with the
build's own squashfs parameters (the unmodified tree reproduces them
byte-exactly): meari 5108 -> 5092 KB, babysense 5104 -> 5092 KB.
2026-09-12 07:39:49 +03:00
OpenIPC AI teamandGitHub 2a26e86e45 gk7205v200_otg: stop the UVC gadget freeing its own video device (#149)
Tearing down a UVC gadget that userspace still has open corrupts the V4L2 core:
the node number leaks (the gadget climbs /dev/video0 -> video1 -> video2 across
role switches), and once the kernel is tainted by the WARN that goes with it, a
NULL dereference follows with v4l2_device_release on the backtrace.

struct uvc_device embeds its struct video_device and uvc_free() kfree()s the
lot, but the V4L2 core holds a reference to that video_device for as long as
/dev/videoN is open. Reference-count the structure instead, which is what the
driver's own "TODO reference counting" has been asking for.

Verified on the bench: the gadget returns to /dev/video0 every time instead of
climbing, and no WARN or Oops after repeated switching.
2026-09-06 14:40:07 +03:00
OpenIPC AI teamandGitHub 0df089bf61 gk7205v200_otg: take the role scripts from firmware's package (#148)
Follow-up to OpenIPC/firmware#2370, which moves `usb-mode`,
`uvc-gadget-setup` and `S72usbmode` into a selectable `usb-dual-role`
package. Nothing about them was device-specific; they were only here because
this is where the OTG image was first put together.

So this profile stops carrying its own copies and selects the package instead.
Keeping both would be worse than untidy: firmware installs them in /usr/sbin
and this profile installed them in /usr/bin, so a build with both would put two
versions of each script on the camera and let PATH order decide which ran --
with the copy here being the older, missing the bus settling, the UDC guard and
the idempotent apply that came with the move.

MERGE AFTER the firmware change, not before: until that lands there is no
package to select, and a build from this branch ships no role scripts at all.

What stays here is what is genuinely particular to this board: the defconfig,
the kernel config, and the dwc3/uvc patches.
2026-09-06 14:38:10 +03:00
OpenIPC AI teamandGitHub f86ea4f4a6 gk7205v200_otg: settle the USB role before xHCI probes (#147)
The OTG profile could not see USB devices in host mode. On a board with a
UVC webcam on the USB tail, `lsusb` showed only the two root hubs, while the
same board running a plain lite image enumerated the webcam normally.

The controller was in the host role and the host controller was running --
mode file "host", xhci-hcd bound, USBCMD 0x5, PORTSC1 port-power set -- and
the port still never reported a connect (PORTSC1 0x00000280, CCS clear).

dr_mode = "otg" makes dwc3_core_init() program GCTL.PRTCAPDIR = OTG(3), and
dwc3_core_init_mode() registers xhci-hcd on top of that. This 4.9 dwc3 has no
working OTG role-detection block, so xHCI probes, resets the controller and
brings up its root hubs while the core is in a role it cannot drive. The port
is then dead for the life of that probe.

S72usbmode's `usb-mode host` did not repair it and could not: writing the
debugfs mode file reaches dwc3_set_mode(), which flips GCTL.PRTCAPDIR and
nothing else, leaving the already-probed xHCI with the port state machine it
built in OTG. It also skipped the bind, because xhci-hcd was already bound.

Fix it where the ordering is: patch dwc3_core_init() to program
PRTCAPDIR = HOST for the OTG case, so xHCI probes into a role the core can
drive. dwc3_gadget_init() only registers a UDC and does not care, and
`usb-mode device` re-aims the controller itself when it takes the port.

usb-mode's host path also learns to re-probe an xHCI that was bound in some
other role, which is what makes a newer rootfs recover on an older kernel.
2026-09-06 09:57:08 +03:00
OpenIPC AI teamandGitHub bac7c697b2 xm-85h50ai: turn on majestic's autofocus out of the box (#142)
The first camera with working autofocus on HiSilicon: the customizer
enables majestic's contrast AF engine (widgetii/majestic#522), which
reads the ISP's BE AF zone statistics and drives this board's XM zoom
block over the same UART the pad uses, serialised by the same lock.
The engine defaults to off everywhere — a lens without a focus motor
must not grow an /autofocus endpoint — so enabling it in the profile
of the board it was proven on is the whole change; port and speed
defaults already match. The WebUI grows an AF button and books a
settle-gated pass after each zoom step (majestic-webui#265).

Verified end to end on the lab unit: from total blur to a tack-sharp
frame, fv 30 -> 5126 against a 5606 peak in 21s.
2026-08-31 17:37:24 +03:00
OpenIPC AI teamandGitHub 3921b342f3 Add hi3516ev300_lite_xm-85h50ai device profile (XiongMai, motorized zoom+focus) (#141)
XiongMai's 85H50AI: hi3516ev300 + imx335 with a motorized zoom/focus
module whose MCU shares /dev/ttyAMA0 with the serial console at 115200,
speaking the XM near-Pelco protocol (majestic-webui's btzoom-xm).
Proven on a lab unit driven end to end through the WebUI
(majestic-webui#227).

The defconfig is a byte-copy of firmware's hi3516ev300_lite_defconfig.
customizer.sh (first boot) sets sensor imx335, the upgrade URL, and the
PTZ trio — ptz_control pelco-xm, ptz_port /dev/ttyAMA0, ptz_caps
'zoom focus' (majestic-webui#261) — and deletes the getty line from
/etc/inittab: the MCU owns the console UART. The edit lands in the
overlay and heals itself (an upgrade that wipes the overlay wipes
/etc/custom.ok too, so the script re-runs). S01printk quiets runtime
kernel messages that would drip onto the PTZ wire. The required
hi3516ev300_lite excludes list prunes every non-imx335 sensor blob,
generated from the live lite image so no entry is stale on arrival.
2026-08-31 16:00:28 +03:00
OpenIPC AI teamandGitHub b2267404b9 customizer: gpio_motors becomes ptz_gpio, with ptz_control naming the method (#140)
Completes the #227 PTZ env rename (majestic-webui#232, firmware#2341) in
the last place still writing the old name: the three customizer.sh files
carrying 'fw_setenv gpio_motors ...' now write ptz_gpio instead. The
gpio-motors utility reads ptz_gpio first and falls back to gpio_motors
since firmware#2341, and it is the only consumer of the variable anywhere
in the org, so the rename is mechanical.

Each device also gains 'fw_setenv ptz_control gpio' — not part of the
rename, but required by it: the WebUI dropped its auto-detection from a
bare pin list (unset ptz_control now means no PTZ, per flyrouter's ruling
on the same issue; majestic-webui#255). Without the method named, these
devices would keep their motors but lose their pads. In
t23_lite_jooan-a6m-u the pin line was commented out, so both new lines
are commented there too.
2026-08-31 14:29:27 +03:00
OpenIPC AI teamandGitHub 2b619b9e76 publish with an explicit release credential, and prove it before building (#138)
* publish with an explicit release credential, and prove it before building

This repo was next in line for the failure OpenIPC/firmware hit on
2026-08-29. Moving the nightly off `schedule:` onto a dispatch changes
the identity the run executes as, and the default GITHUB_TOKEN's
release-write goes with it: a scheduled run executes as the repository's
scheduling identity, a dispatched one as whoever dispatched it, and for
an app/OAuth-backed identity the releases API answers 403 "Resource not
accessible by integration".

It does so even though the job declares `contents: write` and the run
log confirms the token was granted it, which is why nothing in the
workflow file looks wrong. firmware's run 33273333359 built 102 boards
over two hours before finding out. This repo builds 107 devices and
publishes the same way -- `gh release create` plus a PATCH of
refs/tags/latest -- so it would have failed identically the first time
the train reached it. It never got that far only because the train
correctly refused to advance past firmware's failure.

The publish step takes RELEASE_TOKEN instead of the default token, and
preflight proves that credential can create a release before the matrix
is allowed to spend hours on it, by creating a draft release and
deleting it: invisible, no tag, two API calls.

Asserting the secret is merely non-empty would not have caught the
firmware incident. The token that failed existed and was simply refused,
so the probe has to attempt the write.

* probe: survive re-runs and clean up after itself

Review caught a real defect in the credential probe. It deleted its
draft with a single fail-fast call, so an interrupted or cancelled job
left the draft behind -- and because github.run_id is stable across
re-runs, that leftover would collide with its own re-run and report a
perfectly good credential as broken. A guard that fails wrongly is worse
than no guard, because it blocks good runs and teaches people to ignore
it.

Three changes. The probe tag now carries run_attempt as well as run_id,
so a re-run can never collide with its own leftovers. Deletion moved
into a trap, so a cancelled job still cleans up. And a best-effort sweep
removes probe drafts stranded by earlier runs, because cleanup.yml does
not know this tag pattern and they would otherwise accumulate forever.

The sweep is capped at the 10 most recent releases and swallows every
error. That cap is not arbitrary: listing 100 releases in these repos
returns HTTP 504 because each carries hundreds of assets, measured at
~2s for 10 and ~7s for 30. Housekeeping must never be the thing that
fails a build.

Verified end to end against the live repo with a real token: creates the
draft, reports "release credential OK", the trap removes it, no tag is
created and no published release is touched. Both failure paths still
fail loudly -- unset token, and a credential the API refuses.

* probe: retry the write, and do not narrate garbage while sweeping

Review caught that the probe treated any one-shot POST failure as a bad
credential. GitHub answers 403 for secondary rate limiting as well as
for refusal -- the publish step retries six times for exactly that
reason -- and these repos return 504 on ordinary release listings often
enough that one was hit while writing this change. A blip would have
failed the nightly with "the token is wrong", sending whoever read it
off to reissue a credential that was never the problem.

The POST now retries three times with 5s and 10s backoff, and the error
says "after 3 attempts ... if this is not a transient API error". Three
rather than the publish step`s six because the value here is failing
fast: a genuine misconfiguration still surfaces in well under a minute
instead of after the whole matrix.

Testing the retry then exposed a second defect, in the sweep added by
the previous commit. gh writes its error body to STDOUT, so a failed
list feeds the cleanup loop lines of JSON instead of release ids, and
the log filled with `removing stranded probe draft {"status": "401"}`.
The deletes were harmless -- they simply failed -- but a step whose
entire purpose is to be believed must not narrate confident nonsense.
Only numeric ids are acted on now.

Verified against the live repo: happy path creates the draft, reports
OK, the trap removes it, nothing is left behind; a refused credential
retries three times and fails with the honest message; a failing list
produces zero bogus removal lines.
2026-08-30 11:00:05 +03:00
OpenIPC AI teamandGitHub 96fc55698c Take the nightly off GitHub's scheduler (#137)
This is the last stage of a three-stage release train — widgetii/majestic
publishes the majestic tarball, OpenIPC/firmware builds the images, this builds
from that tree. Each stage was its own GitHub cron sized against a guess at the
previous stage's runtime: 17:25, 22:30 and 03:00 UTC.

From 2026-08-26 GitHub began delivering scheduled events hours late and
independently per repo, and this repository was the worst affected of the three
— +10h38m and +11h49m on successive nights, a 03:00 slot sitting in the deepest
part of the congestion band. On the 08-28 cycle that made this stage run FIRST,
at 14:49, ahead of both the repositories it is supposed to follow. It went
green, as it always would: no stage checks that its inputs are newer than the
ones it used last time.

The delay is not ours and not fixable here: for every late run
`created_at == run_started_at` to the second, so all of it sits upstream of run
creation, inside GitHub's dispatcher. Moving the cron off the top of the hour
and pushing a commit to resync the schedule were both tried on the majestic
side; neither changed anything.

The ordering moves to the host that runs majestic's self-hosted runners, which
dispatches each stage in turn and waits for it to succeed before starting the
next.

Unlike majestic and firmware this needed no gate change, and the preflight
comment now says why: those two skip when nothing has changed and keyed that
skip on the event name, which a dispatch would have made unreachable. This one
deliberately always builds, so there was no event-name condition to go stale.

The clone guard rail keeps its logic and loses half its argument: an inherited
workflow can no longer run nightly and unattended on a stranger's Actions bill,
but it can still do so on every internal PR.
2026-08-29 23:09:15 +03:00
OpenIPC AI teamandGitHub 9df259cb6f ci: build the KC110 and the Tapo TC70 v3 instead of skipping them (#132)
Both devices have had a complete devices/<dir>/ in this tree for a long time and had
never once been built. #120 froze that state rather than changing it -- it put the 7
defconfigs that were in no master.yml matrix into NOT_BUILT so that a device falling out
of CI would be a test failure instead of a silent skip. Freezing was right for a change
whose job was to not alter what gets built; it was never meant to be permanent for
devices whose only problem is that nobody added the matrix line.

Two contributors did try to add that line. #111 (Tapo TC70 v3, July) and #108 (KC110,
June) both append to the device list in master.yml, which is where the registry lived
when they were written. It does not live there any more -- the matrix is read off the
tree -- so both PRs are merge conflicts now, and the devices they asked for are exactly
the two entries removed here. This is their CI half, landed the way the selector wants
it. #108 also carries a BR2_OPENIPC_MAJESTIC="ultimate" line and a vendor
libsns_ov2735.so for the no-video bug; those are unaffected and stay with that PR.

The two self-test cases that used t31_lite_tp-link-tapo-tc70-v3 as their example of an
unbuilt device move to t31_lite_xiaomi-mjsxj05hl, which is still in NOT_BUILT. The
overlay case also switches to a path that exists, since the device it now names has no
etc/inittab.

Neither device had ever been through a build, so this PR was where we found out. That
was affordable exactly once: a change to ci-matrix.py cannot narrow -- it is what does
the narrowing -- so this ran the full matrix and proved both in the same run. Both are
green:

    Firmware (hi3518ev200_ultimate_tplink-kasa-kc110)   SUCCESS
    Firmware (t31_lite_tp-link-tapo-tc70-v3)            SUCCESS
2026-08-27 21:24:20 +03:00
OpenIPC AI teamandGitHub 5e2d58f644 fpv: give the 8812eu its own gk7205v200 build (#133)
#61 asks for 8812eu support on gk7205v200 FPV. #70 and the first version of this PR
both tried to satisfy it by adding BR2_PACKAGE_RTL88X2EU_OPENIPC=y next to the existing
BR2_PACKAGE_RTL8812AU_OPENIPC=y. CI built that and it does not fit:

    - uImage:          [1988KB/2048KB]
    - rootfs.squashfs: [5504KB/5120KB]
    -- size exceeded by: 384KB

Two heavy kernel Wi-Fi drivers do not go in one 8M image. The driver itself is fine --
8812eu.ko links and installs -- it is purely flash budget, and there is no 384KB of
slack in a 5MB rootfs that was already at its cap.

The tree had already said so. Every defconfig here that ships both drivers is 16M: the
ssc338q, ssc30kq, ssc377qe and ssc378qe fpv/apfpv/rubyfpv targets. Every 8M one ships
8812au alone: gk7205v200/v210/v300 fpv and venc, hi3516ev200_fpv, hi3516ev300_fpv. The
"missing" 8812eu on the Goke configs was never an oversight; it is what 8M allows.

So this is a separate build. gk7205v200_fpv_8812eu is gk7205v200_fpv with one line
changed, the driver swapped rather than added:

    -BR2_PACKAGE_RTL8812AU_OPENIPC=y
    +BR2_PACKAGE_RTL88X2EU_OPENIPC=y

The existing gk7205v200_fpv is untouched, so nobody on an 8812au adapter is affected.
It builds with room to spare -- rootfs 5024KB/5120KB, 96KB headroom.

Nothing else had to move for it:

  - Registration is automatic; the defconfig is the registration. --self-test passes at
    108 devices and the diff narrows to exactly this target, so its own build proved it.
  - Excludes resolve as ${OPENIPC_SOC_MODEL}_${OPENIPC_VARIANT}.list in firmware's
    rootfs_script.sh, and VARIANT stays "fpv", so it reuses gk7205v200_fpv.list. That
    list strips neither driver.
  - Release assets do not collide. master.yml keys on the underscore count:
    gk7205v200_fpv is COMMON=1 and uploads the canonical openipc.gk7205v200-nor-fpv.tgz,
    this one is COMMON=2 and is renamed to gk7205v200_fpv_8812eu-nor.tgz. Same for its
    sizes.json.

Only gk7205v200, which is what #61 asked for. The other four 8M FPV targets have the
same constraint and could take the same treatment if anyone asks.

Closes #70.
2026-08-27 21:21:47 +03:00
OpenIPC AI teamandGitHub 82ab2e7a32 docs: stop telling contributors to register a device in master.yml (#134)
Step 6 of "Adding a new device" still said to add `- <device>` to the matrix in
.github/workflows/master.yml, and called that matrix "the *only* build registry".
Neither has been true since #120 moved the matrix into .github/scripts/ci-matrix.py,
where it is derived from the tree rather than listed. master.yml has no device list
at all.

Not a hypothetical staleness: #111 (Tapo TC70 v3) and #108 (KC110) each append a line
to a block that no longer exists, so both are merge conflicts rather than the one-line
changes they were written as. Their authors did the documented thing.

So step 6 is now the opposite instruction -- there is nothing to add, and adding it is
the mistake. It also names NOT_BUILT, since that is the one list a contributor could
reasonably go looking for, and says to leave it alone.

Two adjacent claims went stale with the same change: the master.yml bullet described a
cron-and-dispatch workflow with no pull_request trigger, and nothing mentioned
ci-matrix.py at all -- including that --self-test runs in the Select devices job and
will fail a PR that puts the tree and the selector out of step.

A second correction, caught by Qodo in review: "opening the PR is what gets the device
built" holds only for a non-draft PR. classify() returns needs_build=False for a draft
and self_test() asserts it; master.yml lists ready_for_review in its triggers precisely
because a PR opened as a draft would otherwise never build once marked ready. Left as
written, a contributor could open a draft, see no build, and read that as the device
being fine. The ci:full label is documented for the same reason -- it is the other
contributor-facing lever over the matrix.
2026-08-27 20:54:43 +03:00
OpenIPC AI teamandGitHub 6fef62c0f3 ci: watch for drift from firmware instead of finding it by accident (#129)
* ci: watch for drift from firmware instead of finding it by accident

builder.sh copies devices/<item>/* over a fresh firmware clone, so a device
directory that ships its own copy of a firmware file replaces it outright. A
fleet-wide change in firmware reaches every device that only references a path
and none that ships its own copy, silently, because nothing in either repository
is looking. One comment on OpenIPC/firmware#2308 turned up three instances of
that in an afternoon: 13 board configs that missed the CONFIG_VT sweep (#126),
97 defconfigs that kept BR2_PACKAGE_JSONFILTER after firmware retired it and put
three devices over their rootfs cap (#128), and excludes lists naming files that
no longer exist (firmware#2313 reports those at build time).

The trigger is the design. Every one of those was caused by a commit in
firmware while this repository sat untouched, so a pull_request check would
never have fired for any of them -- nobody opens a builder PR when firmware
changes. firmware-drift.yml runs on a schedule and files an issue rather than
failing: drift someone else introduced is not a reason to redden a nightly that
is otherwise fine. Only the self-test gates a merge here, out of lint.yml.

Two checks, because the two kinds of drift are not the same shape.

Shadowed files: comparing content is useless, a builder copy is *supposed* to
differ. What matters is whether firmware's version moved since a human last
reconciled them, so each of the 13 entries pins the blob it was reconciled
against. Re-pinning is the act of looking again. The mapping is hand-authored --
nothing can infer that gk7205v200.generic-fpv derives from gk7205v200.generic.

Defconfig symbols: nothing here copies a firmware defconfig, so pinning does not
apply. Every BR2_PACKAGE_*=y is resolved against buildroot, firmware and this
tree and sorted into "resolves nowhere" (a dead line kconfig ignores) and
"resolves, but no firmware defconfig selects it" (needs an allowlist entry). An
allowlist entry can also fence a symbol to the devices that need it; without
that, writing JSONFILTER down as builder-only for devices/apfpv would equally
bless it creeping back onto 95 unrelated defconfigs, which is #128 verbatim.

Replayed against the three regressions, each reproduced by hand on a clean tree:

    firmware moves a shadowed config
      -> br-ext-chip-goke/board/gk7205v200/gk7205v200.generic.config moved in
         firmware since this copy was reconciled (2026-08-25).
         builder copy: devices/common/.../gk7205v200.generic-fpv.config
         pinned 7450c6803f6d, firmware now f97dce84c0f8
         firmware commits since:
           85233f0e kernel: drop CONFIG_VT and the busybox applets ... (#2308)

    jsonfilter creeps back onto a non-apfpv device
      -> BR2_PACKAGE_JSONFILTER is allowlisted only for apfpv/*/configs/*_defconfig,
         but 1 other defconfig(s) select it.

    firmware retires a symbol builder still selects
      -> BR2_PACKAGE_LIBUBOX is selected here but by no firmware defconfig.

Resolving against buildroot is not optional and getting it wrong is how a
checker lies. Buildroot is vendored by neither repository, so a first pass over
OpenIPC packages alone called HOSTAPD, IW, PHP, UHTTPD, LIBZIP and BWM_NG dead;
all six are upstream. Matching only "Config.in" then called PHP_EXT_ZIP dead,
because buildroot declares php's extensions in package/php/Config.ext. Without
--buildroot that half is skipped and says so rather than inventing findings.

Two symbols really do resolve nowhere today -- BR2_PACKAGE_APFPV_GREG on the
four greg-generic devices and BR2_PACKAGE_WIFIBROADCAST_EXT in devices/common.
Both are recorded as known-dead and reported as a notice on every run rather
than removed: whether those devices still want the packages is a maintainer's
call, not a side effect of adding a check.

The three new paths are classified in ci-matrix.py so this PR and every future
re-pin cost no device builds. Without that a blob update is a 107-device run.

* ci: discover shadowed files instead of listing them by hand

Qodo caught the shadow list omitting the device-local t21 and t31 kernel configs.
It was right, and the hole was much bigger than the five it named: a hand-written
list of 13 board configs against 93 same-path shadows covering 35 firmware paths.
The missing ones are not incidental -- load_goke, load_hisilicon, load_sigmastar
and four vendor .mk files are all shadowed by some device, and those are exactly
the shared files a fleet-wide fix lands in. firmware regression-tests
load_hisilicon's os_mem_size derivation; two devices here ship their own copy of
that script and would not have seen the fix.

Listing them by hand was the wrong shape. builder.sh copies devices/<item>/*
over the firmware clone, so any file at a path firmware also has replaces it --
which makes same-path shadows discoverable rather than something to remember.
discover_shadows() walks devices/ and reports anything sitting on a firmware
path with no entry in firmware-drift.json, so the list cannot silently rot and a
device added tomorrow cannot quietly introduce an unreconciled shadow.

The manifest goes from 13 entries to 101: the 88 discovered ones, plus the 13
hand-authored entries that discovery cannot find because the name differs from
the file they replace -- gk7205v200.generic-fpv shadows gk7205v200.generic, and
nothing can infer that.

Two self-test cases for the new path: a same-path shadow with no entry is
reported, and an entry silences it. Verified against the tree by dropping the
load_hisilicon entries, which surfaces both devices immediately.
2026-08-26 01:27:10 +03:00
OpenIPC AI teamandGitHub a0418c2987 kernel: drop CONFIG_VT from the device configs that shadow firmware's (#126)
builder.sh copies devices/<item>/* over the firmware checkout before make, so a
device directory that carries its own board .config replaces firmware's copy
outright. OpenIPC/firmware #2308 and #2309 turned CONFIG_VT and CONFIG_INPUT off
across the fleet; the 13 board configs here were never touched by that, so 21
device builds carried on shipping the virtual-terminal layer -- /dev/tty0,
/dev/tty1..63, the keyboard translation tables, console selection and screen
code -- for a monitor and keyboard no camera has. flyrouter pointed this gap out
on #2308.

The transformation is not re-derived here. For each file the symbol-level delta
was taken straight out of firmware's history (85233f0e^..HEAD) for the config it
shadows and replayed, so these land on exactly the state that was reviewed and
proven on hardware there. Every affected symbol in every copy still held
firmware's pre-sweep value, so nothing here was a deliberate customisation --
they were simply stale.

Eleven of the thirteen also needed CONFIG_EXPERT=y first. In drivers/tty/Kconfig
the symbol reads

    config VT
        bool "Virtual terminal" if EXPERT
        select INPUT
        default y

so its prompt exists only under EXPERT, and without it olddefconfig finds the
symbol invisible and forces it back to its default -- "# CONFIG_VT is not set"
on its own does nothing.

Two exceptions:

  * gk7102c keeps CONFIG_INPUT. It sets CONFIG_INPUT_EVDEV=y and firmware's
    gk710x, which it shadows, keeps INPUT for the same reason. Only VT moves.

  * CONFIG_INPUT_LEDS=m is dropped on the six FPV/OTG copies. It is the
    input-to-LED bridge, lives inside `if INPUT`, and firmware never carried it,
    so olddefconfig would have dropped it silently -- stated here so the file
    matches the kernel it generates. CONFIG_LEDS_CLASS is a separate symbol in
    drivers/leds and is untouched: the FPV status LEDs are unaffected.

Nothing consumes VT on any of these. FRAMEBUFFER_CONSOLE is its only in-kernel
consumer and no config in this repository sets it at =y or =m, so the sole
console driver compiled in was DUMMY_CONSOLE and writes to /dev/tty0 went
nowhere. The ingenic t21/t31/t40 copies already matched firmware -- VT off,
INPUT kept for their GPIO keys -- and are left alone.

Verified by generating the kernel config the way builder.sh does: copy
devices/common over a firmware checkout at master and run the configure step,
before and after. gk7205v200_fpv, comparing the two generated
build/linux-custom/.config files symbol by symbol:

    turned OFF (8)   CONSOLE_TRANSLATIONS DUMMY_CONSOLE HW_CONSOLE INPUT
                     INPUT_LEDS VT VT_CONSOLE VT_CONSOLE_SLEEP
    turned ON  (1)   EXPERT
    value changed    none

    LEDS_CLASS=m, TTY, UNIX98_PTYS, SERIAL_AMBA_PL011_CONSOLE, USB_GADGET,
    CFG80211, MAC80211, KALLSYMS, COREDUMP, NAMESPACES, ELF_CORE,
    CORE_DUMP_DEFAULT_ELF_HEADERS, DEBUG_KERNEL, DEBUG_MEMORY_INIT: unchanged.

That last line is the check #2308 missed on its first pass: EXPERT carries
`select DEBUG_KERNEL` and unhides a block of on-by-default options, which there
silently moved COREDUMP and friends. DEBUG_KERNEL was already y in these
configs, so nothing else moved.

Building the kernel both ways in that same tree, on the OpenIPC/linux goke
tarball these devices use:

    gk7205v200_fpv  uImage  2071368 -> 2036040   -35328 bytes (-1.71%)
                    zImage  2058872 -> 2023544   -35328 bytes (-1.72%)

which lines up with the -34680 bytes firmware measured on gk7205v200_lite for
the same delta. drivers/input/input-leds.ko is built in the before tree and is
not built after.
2026-08-25 21:25:13 +03:00
OpenIPC AI teamandGitHub 7bfcab91ff ci: stop a NOT_BUILT device widening the matrix to all of them (#127)
NOT_BUILT is documented as the opt-out that narrows to nothing, and a change to
one of those devices' defconfigs does exactly that. Anything else in the same
directory did the opposite: targets_in() only ever returns devices CI builds, so
a directory holding nothing but opted-out devices came back empty and fell
through to the "is in no directory CI builds" widen. Touching a device CI
deliberately skips cost a full 107-device run.

    $ echo devices/gk7205v200_rubyfpv_generic/br-ext-chip-goke/board/\
gk7205v200/gk7205v200.generic-fpv.config | python3 .github/scripts/ci-matrix.py --stdin
    ci-matrix: 107/107 devices (needs_build=True) --- ... is in no directory CI builds

    $ echo devices/gk7205v200_rubyfpv_generic/br-ext-chip-goke/configs/\
gk7205v200_rubyfpv_generic_defconfig | python3 .github/scripts/ci-matrix.py --stdin
    ci-matrix: 0/107 devices (needs_build=False) --- nothing that reaches a build

Same device, same opt-out, opposite answers. #126 touches three NOT_BUILT
devices and is building all 107 because of it.

The empty result was carrying two meanings: "every device here is opted out" and
"this directory is not one I know". Only the second should widen. devices_in()
answers the second question -- it reads directory_of, which is built from every
defconfig in the tree rather than the built subset -- so an unrecognised
directory still widens, which is the half of the rule worth keeping.

Three self-test cases, one per meaning: a kernel config and an overlay file in a
NOT_BUILT device reach nothing, and a devices/ directory with no defconfig at
all still widens. With this, #126's own file list narrows from 107 to 23.
2026-08-25 21:08:13 +03:00
OpenIPC AI teamandGitHub a5882d5e80 package: stop selecting jsonfilter, mirroring firmware #2304 (#128)
OpenIPC/firmware dropped BR2_PACKAGE_JSONFILTER from every one of its
defconfigs in #2304, once the WebUI stopped calling it. builder's defconfigs are
its own files, so 97 of them kept selecting it. jsonfilter selects JSON_C and
LIBUBOX, and on a device that enables neither uqmi-openipc nor wifibroadcast-ng
nothing else selects libubox, so it and jsonfilter shipped for nothing.

That is what has had three devices over their rootfs cap:

    hi3518ev200_lite_switcam-hs303      rootfs 5124KB/5120KB   -4KB
    ssc333_lite_babysense-see-hd-ip206  rootfs 5124KB/5120KB   -4KB
    ssc333_lite_meari-speed-6s          rootfs 5128KB/5120KB   -8KB

all three red on the nightly, meari since 2026-08-24. Bisecting meari across
firmware revisions lands on #2301, which added 408 bytes to the comment-stripped
sysupgrade script -- these images had no headroom left at all, and 408 bytes of
a genuine bug fix was enough to push one over a 4KB padding step. The fix is to
give them headroom, not to give back the bug fix.

Measured on ssc333_lite_meari-speed-6s, built the way builder.sh builds it --
devices/<item>/ copied over a firmware checkout at master, clean tree both times:

    before   rootfs.squashfs 5128KB/5120KB   size exceeded by: 8KB
    after    rootfs.squashfs 5096KB/5120KB   24KB spare

-32KB, from /usr/bin/jsonfilter (18068 raw) and /usr/lib/libubox.so (30404 raw).
json-c does NOT go: majestic lists it in MAJESTIC_DEPENDENCIES, so buildroot
builds and installs it whether or not BR2_PACKAGE_JSON_C is set. Only the two
files above leave the image.

Two consumers genuinely need jsonfilter and both keep it:

  * devices/apfpv's own etc/init.d/S99msposd reads majestic's config.json
    through it, and ssc338q_apfpv and ssc378qe_apfpv do not enable
    wifibroadcast-ng. Those two keep the symbol, with the reason written above
    the line -- the same undeclared-runtime-dependency trap firmware #2305 had
    to fix, and the reason this is 95 defconfigs and not 97.

  * every other FPV device enables wifibroadcast-ng, whose Config.in selects
    BR2_PACKAGE_JSONFILTER (added by #2305) for the same script. They keep it
    without saying so.

LTE devices keep libubox through uqmi-openipc, which selects it independently.

Nothing else in this repository calls jsonfilter: grepping devices/ and
package/ finds only that one S99msposd line, and firmware's own tree has no
caller left outside wifibroadcast-ng.
2026-08-25 20:29:47 +03:00
OpenIPC AI teamandGitHub 6fcd657a00 ci: publish the devices that did build when the matrix goes red (#125)
* ci: publish the devices that did build when the matrix goes red

This morning's nightly built 97 of 98 devices. ssc333_lite_meari-speed-6s
failed, and that one device cost every device its release: there is no
nightly-20260824-* release, and `nightly` and `latest` still carry the
23rd's images. 2026-08-21 went the same way on two failures out of 98 --
no nightly-20260821-* either.

The publish job is documented as best-effort -- "publish whatever the
matrix produced even if some devices failed" -- and its condition already
lists 'failure' as an accepted matrix result. It never got that far. A job
whose `needs` failed is skipped by default, and an `if:` overrides that
only when it CONTAINS a status function; reading needs.buildroot.result
inside contains() does not count. So the job was skipped outright, never
evaluated, and reported "skipped" rather than anything red.

This has been true since #121 consolidated the release writes into a
single writer to stop the concurrent-upload rate limits. That change was
right -- before it each device uploaded from its own matrix job, so a
failing device took down only itself. Both nightlies that have failed
since then lost their whole release this way.

`!cancelled()` rather than `always()`: the contains() already drops a
cancelled or skipped matrix, but a run cancelled after a green matrix
would still start uploading under always(), and a half-written nightly is
what the single-writer design exists to avoid.

The gate stays red on a partial matrix; that is unchanged. What changes is
that a red run now still delivers the images it built. Devices that failed
keep their previous image on nightly/latest, which the ci-gate already
assumed; the new warning in Collect assets says so on the run rather than
leaving it to be inferred.

Same fix as OpenIPC/firmware#2303 -- the two publish jobs share a common
ancestor and carried the identical gate.

Verification: master.yml parses; lint-workflow-shell.py checks 23 run
blocks clean (and its own self-test passes); ci-matrix.py --self-test ok.
Publish condition audited against every path -- PR, no-op schedule, clone
guard, cancelled run, failed select and all-devices-failed all still skip;
only "matrix failed, some devices built" changes from skip to publish.

* ci: finish the sentence in the partial-publish warning

The line ended on "for every device that did not", leaving the reader to
supply "build" from the clause before it. It is an operator-facing log
line on the one run where somebody is trying to work out what shipped, so
say it in full and point at where the failed devices are listed. builder
has no Build summary job, so the run's own job list is that place.

Raised by Qodo on #125.
2026-08-24 14:13:56 +03:00