From 3f880a8ba7b60c7570473f958bb56fb3fd77ef8b Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:12:37 +0100 Subject: [PATCH 01/72] chore: adopt product feature architecture --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index f8dd4611..60fe8e7e 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit f8dd4611c9060d380ffff601f4446e89e237ffc4 +Subproject commit 60fe8e7e38f6637b859eaebcdea3536bf3a5ed91 From ea863223c8c28897ed3d4ec05111ec54935c9731 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:13:40 +0100 Subject: [PATCH 02/72] chore: drop Improv from Jaguar screen --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 60fe8e7e..acb7875c 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 60fe8e7e38f6637b859eaebcdea3536bf3a5ed91 +Subproject commit acb7875c66765a30793b62ee2e9101da3fe266f0 From ea75849a681c05bb203bce9d82ada8bc8a7d6347 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:25:18 +0100 Subject: [PATCH 03/72] chore: pin explicit product software selection --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index acb7875c..033bb6bc 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit acb7875c66765a30793b62ee2e9101da3fe266f0 +Subproject commit 033bb6bc3455a6fb5aa6bf147243945622332f92 From 6897dc34c039eb98a2bf94d329c30fe81b1f2de1 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:34:12 +0100 Subject: [PATCH 04/72] waydroid: validate required distro capabilities --- recipes-support/waydroid/waydroid.bb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 759aed6c..c3543d61 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -45,7 +45,12 @@ inherit pkgconfig #inherit webos_app #inherit webos_filesystem_paths #inherit webos_systemd -inherit systemd +inherit features_check systemd + +# Product configuration selects the provider-neutral `android-container` +# bundle. The distro layer expands that bundle to these implementation +# prerequisites; fail early if Waydroid is pulled into an incomplete image. +REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl" WEBOS_SYSTEMD_SERVICE = "waydroid-init.service waydroid-container.service" From acb634749aad18b73997a41ba302101f70aa276f Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:35:08 +0100 Subject: [PATCH 05/72] chore: update product feature documentation --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 033bb6bc..1dc4ce86 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 033bb6bc3455a6fb5aa6bf147243945622332f92 +Subproject commit 1dc4ce8699639d5610c6ebff3d2f25e93d33e729 From 172486f651074abf7f0c70c63717a77bf546031e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:38:33 +0100 Subject: [PATCH 06/72] feat(imx95): support canonical Android container build --- kas/lmp-imx95-frdm-evk-smoke.yml | 70 +++++++++++++++++++ meta-dynamicdevices-bsp | 2 +- recipes-containers/lxc/lxc_git.bbappend | 3 + .../waydroid/python3-gbinder_git.bb | 8 +-- recipes-support/waydroid/waydroid.bb | 6 ++ 5 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 kas/lmp-imx95-frdm-evk-smoke.yml create mode 100644 recipes-containers/lxc/lxc_git.bbappend diff --git a/kas/lmp-imx95-frdm-evk-smoke.yml b/kas/lmp-imx95-frdm-evk-smoke.yml new file mode 100644 index 00000000..3b978314 --- /dev/null +++ b/kas/lmp-imx95-frdm-evk-smoke.yml @@ -0,0 +1,70 @@ +# Local Android-container smoke configuration for FRDM-IMX95 (LPDDR4x). + +header: + version: 14 + includes: + - base.yml + - bsp.yml + - dynamicdevices.yml + +distro: lmp-dynamicdevices +target: lmp-factory-image +machine: imx95-frdm-evk + +repos: + meta-imx: + url: https://github.com/nxp-imx/meta-imx.git + branch: scarthgap-6.6.52-2.2.1 + path: build/layers/meta-imx + layers: + meta-imx-bsp: + + meta-freescale: + url: https://github.com/lmp-mirrors/meta-freescale + commit: 281202125dee44b45ddd56822e43af9c007e4d3d + path: build/layers/meta-freescale + + meta-freescale-3rdparty: + url: https://github.com/lmp-mirrors/meta-freescale-3rdparty + commit: 70c83e96c7f75e73245cb77f1b0cada9ed4bbc6d + path: build/layers/meta-freescale-3rdparty + + meta-lmp: + url: https://github.com/foundriesio/meta-lmp + commit: 4dffdff79b4df49c683c9a7faea406595cb7e9ca + path: build/layers/meta-lmp + layers: + meta-lmp-base: + meta-lmp-bsp: + +local_conf_header: + product-features: | + DD_PRODUCT_FEATURES = "android-container" + + imx95-smoke: | + ACCEPT_FSL_EULA = "1" + LOCAL_DEVELOPMENT_BUILD = "1" + DEV_MODE = "1" + NXP_WIFI_SECURE_FIRMWARE = "0" + + UBOOT_SIGN_ENABLE = "0" + UBOOT_SPL_SIGN_ENABLE = "0" + TF_A_SIGN_ENABLE = "0" + OPTEE_TA_SIGN_ENABLE = "0" + UEFI_SIGN_ENABLE = "0" + MODSIGN = "0" + MODSIGN_ENABLE = "0" + SIGN_ENABLE = "0" + + SIGNING_UBOOT_SIGN_KEY = "${TOPDIR}/bitbake.lock" + SIGNING_UBOOT_SIGN_CRT = "${TOPDIR}/bitbake.lock" + SIGNING_UBOOT_SPL_SIGN_KEY = "${TOPDIR}/bitbake.lock" + SIGNING_UBOOT_SPL_SIGN_CRT = "${TOPDIR}/bitbake.lock" + SIGNING_UEFI_SIGN_KEY = "${TOPDIR}/bitbake.lock" + SIGNING_UEFI_SIGN_CRT = "${TOPDIR}/bitbake.lock" + SIGNING_MODSIGN_PRIVKEY = "${TOPDIR}/bitbake.lock" + SIGNING_MODSIGN_X509 = "${TOPDIR}/bitbake.lock" + TF_A_SIGN_KEY_PATH = "${TOPDIR}/bitbake.lock" + + INHERIT:remove = "sign_rpm create-spdx spdx" + CREATE_SPDX:forcevariable = "0" diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 869a3db5..d1a4ecfe 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 869a3db5b729efad77c13a9117f5fa5a4b91d4ab +Subproject commit d1a4ecfead152f84798b73c7f40cf5b33ab85406 diff --git a/recipes-containers/lxc/lxc_git.bbappend b/recipes-containers/lxc/lxc_git.bbappend new file mode 100644 index 00000000..ec6c536c --- /dev/null +++ b/recipes-containers/lxc/lxc_git.bbappend @@ -0,0 +1,3 @@ +# clang ThinLTO cannot use the default ld.bfd linker selected by this build. +# Waydroid needs LXC, so disable Meson's LTO switch for this dependency. +EXTRA_OEMESON:append = " -Db_lto=false" diff --git a/recipes-support/waydroid/python3-gbinder_git.bb b/recipes-support/waydroid/python3-gbinder_git.bb index 038e7997..2e0307c8 100644 --- a/recipes-support/waydroid/python3-gbinder_git.bb +++ b/recipes-support/waydroid/python3-gbinder_git.bb @@ -7,9 +7,10 @@ LICENSE = "GPL-3.0-only" SECTION = "devel/python" LIC_FILES_CHKSUM = "file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464" -# We're stuck @ 1.1.1 untill we are at cython3, build breaks with https://github.com/waydroid/gbinder-python/commit/4d8cb8f56da9e8159ea1b2ef76ddfa0253563db7 -PV = "1.1.1+git${SRCPV}" -SRCREV = "990c3007eeac3e015fb38aecd76dd010b4b75a1e" +# 1.1.1 fails with the Cython 3 toolchain used by Scarthgap. The bullseye +# 1.1.2 tip includes the required noexcept declarations. +PV = "1.1.2+git${SRCPV}" +SRCREV = "5089d76d4cd958cedda0028ffd752c25508dd382" SRC_URI = "git://github.com/waydroid/gbinder-python.git;branch=bullseye;protocol=https \ file://0001-setup.py-Migrate-away-from-deprecated-distutils.core.patch \ " @@ -26,4 +27,3 @@ SETUPTOOLS_BUILD_ARGS = "sdist --cython" inherit setuptools3 pkgconfig BBCLASSEXTEND = "native" - diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index c3543d61..ed8f347d 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -40,6 +40,7 @@ COMPATIBLE_MACHINE:pinetab2 = "(.*)" COMPATIBLE_MACHINE:mido-halium = "(.*)" COMPATIBLE_MACHINE:tissot = "(.*)" COMPATIBLE_MACHINE:imx8mm-lpddr4-evk = "(.*)" +COMPATIBLE_MACHINE:imx95-frdm-evk = "(.*)" inherit pkgconfig #inherit webos_app @@ -90,6 +91,11 @@ do_install:append:raspberrypi4-64() { install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh } +do_install:append:imx95-frdm-evk() { + install -Dm644 -t "${D}${sysconfdir}" "${WORKDIR}/gbinder.conf" + install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh +} + FILES:${PN} += " \ ${sysconfdir} \ ${libdir} \ From 9ba96f9b8728a309282946d9c3c9a3051cdeab12 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:48:31 +0100 Subject: [PATCH 07/72] bsp: provide i.MX development memtool --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index d1a4ecfe..fb1e978f 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit d1a4ecfead152f84798b73c7f40cf5b33ab85406 +Subproject commit fb1e978f5d03204923cf6ef2f6f9a7672c5f8727 From b82b36649706e9eb64351e0d3a8f3e0658311f91 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:50:57 +0100 Subject: [PATCH 08/72] fix(android): require Vulkan capability --- meta-dynamicdevices-distro | 2 +- recipes-support/waydroid/waydroid.bb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 1dc4ce86..9d202543 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 1dc4ce8699639d5610c6ebff3d2f25e93d33e729 +Subproject commit 9d20254350032254f19febdb42cb8f2eb55743da diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index ed8f347d..15708cdd 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -51,7 +51,7 @@ inherit features_check systemd # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation # prerequisites; fail early if Waydroid is pulled into an incomplete image. -REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl" +REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl vulkan" WEBOS_SYSTEMD_SERVICE = "waydroid-init.service waydroid-container.service" From fdbfbb471f3dfd10bd649b4df705a03dad1f0431 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 11:54:27 +0100 Subject: [PATCH 09/72] feat(screen): stage isolated Qt 6.8 build config --- kas/dynamicdevices.yml | 5 +++++ kas/qt-screen.yml | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 kas/qt-screen.yml diff --git a/kas/dynamicdevices.yml b/kas/dynamicdevices.yml index 818c2c6a..5d97464e 100644 --- a/kas/dynamicdevices.yml +++ b/kas/dynamicdevices.yml @@ -10,6 +10,11 @@ repos: meta-dynamicdevices-distro: path: meta-dynamicdevices-distro + meta-qt6: + url: https://github.com/YoeDistro/meta-qt6.git + path: build/layers/meta-qt6 + commit: d6e576a7d75d0371602e93124c4e2f1c539be8f3 + meta-rust-bin: url: https://github.com/rust-embedded/meta-rust-bin.git path: build/layers/meta-rust-bin diff --git a/kas/qt-screen.yml b/kas/qt-screen.yml new file mode 100644 index 00000000..bad82d14 --- /dev/null +++ b/kas/qt-screen.yml @@ -0,0 +1,14 @@ +header: + version: 14 + includes: + - lmp-dynamicdevices-dev.yml + +machine: imx8mm-jaguar-screen +distro: lmp-dynamicdevices-headless + +local_conf_header: + qt-screen-prototype: | + SCREEN_QT_DEMO = "1" + QT_GIT_PROTOCOL = "https" + DISTRO_FEATURES:append = " wayland opengl" + BBMASK += "meta-dynamicdevices-distro/recipes-graphics/godot/godot_.*[.]bbappend" From 85f566a7e6173d1855e9dcaa2a078d1b49d63281 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:06:51 +0100 Subject: [PATCH 10/72] distro: adopt provider-neutral display feature --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 9d202543..2da1ebea 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 9d20254350032254f19febdb42cb8f2eb55743da +Subproject commit 2da1ebeaaef0ad7ef07123873bad9190eb72e40a From 1f15370346c72da310da6cea529b8008d4f807b4 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:09:36 +0100 Subject: [PATCH 11/72] distro: integrate explicit Jaguar screen stack --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 2da1ebea..67a80cc5 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 2da1ebeaaef0ad7ef07123873bad9190eb72e40a +Subproject commit 67a80cc505701cd2e66598919552c999bc7bc2f7 From 499378661064ab9b292e44505a380b7d6ae7c53e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:11:31 +0100 Subject: [PATCH 12/72] bsp: integrate Jaguar screen hardware support --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index fb1e978f..077803f4 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit fb1e978f5d03204923cf6ef2f6f9a7672c5f8727 +Subproject commit 077803f45767c3b3d2794c75d2f9fb002abc9c46 From f2383b41b38a2b4353c8be78cb0b82e74169ecd7 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:15:13 +0100 Subject: [PATCH 13/72] bsp: complete Jaguar screen device-tree inputs --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 077803f4..3e2d741a 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 077803f45767c3b3d2794c75d2f9fb002abc9c46 +Subproject commit 3e2d741a78298b7ab6f6c8ec9c8f987cde84e1b1 From 1fc551be3d0ed5cc19188d17f6c55b2d6b773279 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:20:48 +0100 Subject: [PATCH 14/72] kas: pin Godot runtime layer --- kas/base.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kas/base.yml b/kas/base.yml index 0f4bc79d..e897b044 100644 --- a/kas/base.yml +++ b/kas/base.yml @@ -66,6 +66,11 @@ repos: commit: eeee54cfa3c51c1fd99604a0d5f173096bdcf1da path: build/layers/meta-tensorflow + meta-godot: + url: https://github.com/active-esl/meta-godot.git + commit: 58670332d8ae0c37e4ac0df884fede37ad4661bc + path: build/layers/meta-godot + openembedded-core: url: https://github.com/lmp-mirrors/openembedded-core commit: 7a59dc5ee6edd9596e87c2fbcd1f2594c06b3d1b From beeebf2cfe8b7f65703d1a6dcb81bf38a70e6945 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 12:29:58 +0100 Subject: [PATCH 15/72] kas: pin Flutter runtime layer --- kas/base.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kas/base.yml b/kas/base.yml index e897b044..1dd352f4 100644 --- a/kas/base.yml +++ b/kas/base.yml @@ -71,6 +71,11 @@ repos: commit: 58670332d8ae0c37e4ac0df884fede37ad4661bc path: build/layers/meta-godot + meta-flutter: + url: https://github.com/meta-flutter/meta-flutter.git + commit: 34a3d4eb3a36b8c9c2af9c4ef2b75986d821fa3f + path: build/layers/meta-flutter + openembedded-core: url: https://github.com/lmp-mirrors/openembedded-core commit: 7a59dc5ee6edd9596e87c2fbcd1f2594c06b3d1b From 78a26e26e3b3010b10e5c046b334fd5be4fb408f Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 13:03:57 +0100 Subject: [PATCH 16/72] distro: migrate AESL onto canonical policy --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 67a80cc5..fcf5d2ea 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 67a80cc505701cd2e66598919552c999bc7bc2f7 +Subproject commit fcf5d2ea7112847297267d9fa433adaf501cc656 From 8e2e57639d65d292ea4aa6385fc0370bb87f2a76 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 13:04:35 +0100 Subject: [PATCH 17/72] docs: use canonical distro in current configuration --- docs/CRA-Compliance-Guide.md | 2 +- docs/investigations/IMX93_SECURE_BOOT_REPORT.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CRA-Compliance-Guide.md b/docs/CRA-Compliance-Guide.md index be56c199..6efe199a 100644 --- a/docs/CRA-Compliance-Guide.md +++ b/docs/CRA-Compliance-Guide.md @@ -125,7 +125,7 @@ Each audit event generates a comprehensive JSON report: The CRA compliance system is built into the distro layer and automatically enabled: ```bash -# In meta-dynamicdevices-distro/conf/distro/lmp-dynamicdevices-headless.conf +# In meta-dynamicdevices-distro/conf/distro/lmp-dynamicdevices.conf DISTRO_FEATURES:append = " cra-audit" ``` diff --git a/docs/investigations/IMX93_SECURE_BOOT_REPORT.md b/docs/investigations/IMX93_SECURE_BOOT_REPORT.md index b587b403..6c09c81e 100644 --- a/docs/investigations/IMX93_SECURE_BOOT_REPORT.md +++ b/docs/investigations/IMX93_SECURE_BOOT_REPORT.md @@ -19,7 +19,8 @@ refs/heads/main-imx93-jaguar-eink: machines: - imx93-jaguar-eink params: - DISTRO: lmp-dynamicdevices-headless + DISTRO: lmp-dynamicdevices + DD_PRODUCT_FEATURES: "improv" # Enable secure boot and image signing for production UBOOT_SIGN_ENABLE: "1" TF_A_SIGN_ENABLE: "1" From d8c70e64d9660a34312b1f2c6158d8d54ea7e988 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 13:05:25 +0100 Subject: [PATCH 18/72] docs: document final compatibility retirement gate --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index fcf5d2ea..ff18dc8f 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit fcf5d2ea7112847297267d9fa433adaf501cc656 +Subproject commit ff18dc8f595b30ab4c638055c39e2832ceb09dde From 854bcdd2ba58486f44731f3083b085ba84779013 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 13:09:29 +0100 Subject: [PATCH 19/72] distro: preserve effective product feature parity --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index ff18dc8f..32db1564 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit ff18dc8f595b30ab4c638055c39e2832ceb09dde +Subproject commit 32db15643409dc32422928b0ecd79124a93df6c7 From 637a8ce1fe2e4b3bb09df195d852cd710802a7c3 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 13:19:16 +0100 Subject: [PATCH 20/72] distro: retain Waydroid Vulkan compatibility --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 32db1564..93a18b37 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 32db15643409dc32422928b0ecd79124a93df6c7 +Subproject commit 93a18b3727ff7df991e2cc7e064cd862cfa871a0 From ae52f4097187af27f98dc2a013e7cd0fb001e81d Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:00:01 +0100 Subject: [PATCH 21/72] docs: record product feature migration evidence --- docs/product-feature-migration-validation.md | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/product-feature-migration-validation.md diff --git a/docs/product-feature-migration-validation.md b/docs/product-feature-migration-validation.md new file mode 100644 index 00000000..34898c20 --- /dev/null +++ b/docs/product-feature-migration-validation.md @@ -0,0 +1,58 @@ +# Product-feature migration validation + +Validation date: 2026-09-02 + +This records local evidence for migration to the canonical +`lmp-dynamicdevices` distro. It is not approval to publish factory pins or to +retire compatibility distro files. + +## Effective feature parity + +| Product | Legacy selection | Canonical `DD_PRODUCT_FEATURES` | Result | +| --- | --- | --- | --- | +| Jaguar Sentai | `lmp-dynamicdevices-headless` | `improv` | Same effective `DISTRO_FEATURES` token set | +| Jaguar DT510 | `lmp-dynamicdevices-headless` | `improv usb-gadget` | Same effective token set; legacy comments mention ALSA but the effective configuration removes it | +| Jaguar Screen | legacy screen-enabled headless configuration | `display flutter godot` | Same effective display/UI/audio token set; deliberately no Improv | +| Android/Waydroid products | `lmp-dynamicdevices-headless-waydroid` | `android-container` | Preserves Wayland, OpenGL, PulseAudio and ALSA; adds Vulkan because the current Waydroid recipe requires it | + +Headless operation was also checked with an empty feature selection. It omits +display, UI, audio-runtime, Android and Improv software features. Hardware +capabilities such as Bluetooth remain in `MACHINE_FEATURES` and do not select +product software. + +## Compatibility checks + +- Unknown product-feature names fail at `ConfigParsed`. +- `display`, `flutter` and `godot` fail unless the machine declares + `display-multimedia`. +- Selecting `display` on `imx8mm-jaguar-sentai` was verified to fail. +- Standalone `audio` was verified to expand to ALSA and PulseAudio. +- `imx8mm-jaguar-screen` declares `display-multimedia` in the BSP. + +## Dependency proofs + +- `imx8mm-jaguar-screen`, `display flutter godot`: + `bitbake -n lmp-factory-image` completed all 9,463 tasks successfully. +- `imx95-frdm-evk`, `android-container`: + `bitbake -n lmp-factory-image` completed all 7,722 tasks successfully. +- The Android check also proved that removing Vulkan makes `waydroid` + unbuildable because its recipe lists Vulkan in `REQUIRED_DISTRO_FEATURES`. + +No prior rootfs package manifests were present in the local worktrees, so a +package-by-package manifest comparison is not claimed. Effective feature-set +comparison and complete dry-run dependency graphs are the available local +evidence. + +## Rollout gates still required + +1. Publish the distro, BSP, superproject, Foundries Factory Definition and + AESL runner/config branches in dependency order. +2. Update public/private manifest pins to the published commits. Private-source + pin changes and CI triggers require the physical hardware-key touch. +3. Build deployable images and retain their rootfs/package manifests as the new + baselines. +4. Boot and exercise the Screen and Android targets on hardware, including + display/touch, Flutter, Godot, Waydroid, audio where selected, OTA identity + and rollback-sensitive behavior. +5. Confirm no live factory, manifest or local build consumer names a legacy + distro; only then delete the compatibility distro files. From cc67ca13a9ab7816796d7634f05db24a5b6d4420 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:00:50 +0100 Subject: [PATCH 22/72] docs: record coordinated rollout candidates --- docs/product-feature-migration-validation.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/product-feature-migration-validation.md b/docs/product-feature-migration-validation.md index 34898c20..03cd89ae 100644 --- a/docs/product-feature-migration-validation.md +++ b/docs/product-feature-migration-validation.md @@ -45,6 +45,19 @@ evidence. ## Rollout gates still required +Validated local rollout candidates (all worktrees clean when recorded): + +| Repository/worktree | Commit | +| --- | --- | +| `meta-dynamicdevices-bsp` | `3e2d741a7829` | +| `meta-dynamicdevices-distro` | `93a18b3727ff` | +| Foundries `ci-scripts` | `53e954882c97` | +| AESL `factory-core-ci` | `33b25f2d8b6e` | +| AESL Factory Definition | `379727319ef4` | + +Publish dependencies before their consumers: distro and BSP first, +superproject next, then Foundries/AESL configuration and manifest pins. + 1. Publish the distro, BSP, superproject, Foundries Factory Definition and AESL runner/config branches in dependency order. 2. Update public/private manifest pins to the published commits. Private-source From 847be211550b65ceda59ab4f7fd7a5cf0bc99f25 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:04:36 +0100 Subject: [PATCH 23/72] distro: preserve screen rotation in rollout candidate --- docs/product-feature-migration-validation.md | 2 +- meta-dynamicdevices-distro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/product-feature-migration-validation.md b/docs/product-feature-migration-validation.md index 03cd89ae..b8dc2dcb 100644 --- a/docs/product-feature-migration-validation.md +++ b/docs/product-feature-migration-validation.md @@ -50,7 +50,7 @@ Validated local rollout candidates (all worktrees clean when recorded): | Repository/worktree | Commit | | --- | --- | | `meta-dynamicdevices-bsp` | `3e2d741a7829` | -| `meta-dynamicdevices-distro` | `93a18b3727ff` | +| `meta-dynamicdevices-distro` | `ea70a12` | | Foundries `ci-scripts` | `53e954882c97` | | AESL `factory-core-ci` | `33b25f2d8b6e` | | AESL Factory Definition | `379727319ef4` | diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 93a18b37..ea70a121 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 93a18b3727ff7df991e2cc7e064cd862cfa871a0 +Subproject commit ea70a121f03accd3679e4a1dda3c986125f3385e From b9e6221b55a4d603cd22fcf50e05ae27889fbeb5 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:20:48 +0100 Subject: [PATCH 24/72] bsp: include proven imx95 boot chain --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 3e2d741a..b615d396 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 3e2d741a78298b7ab6f6c8ec9c8f987cde84e1b1 +Subproject commit b615d396e219e91af4aca3c495530622ef185331 From de62d6940a4b93bcf14f5f83c7ef7cfe534c3c0b Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:22:47 +0100 Subject: [PATCH 25/72] bsp: preserve disabled screen camera graph --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index b615d396..c1868e70 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit b615d396e219e91af4aca3c495530622ef185331 +Subproject commit c1868e70e8e2da0d9651bd8b6dd6b5b09f8df4aa From d1d2651ca14a08864db7e1b13c44ef1e733a5c85 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:29:54 +0100 Subject: [PATCH 26/72] distro: retain Waydroid migration alias --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index ea70a121..e8cbf006 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit ea70a121f03accd3679e4a1dda3c986125f3385e +Subproject commit e8cbf0061fea392829138e760045afc631b7c73f From 8f6ceae04eabd16c01d07f19ca4d25ca1b2ac167 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 14:39:41 +0100 Subject: [PATCH 27/72] docs: record manifest migration coverage --- docs/product-feature-migration-validation.md | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/product-feature-migration-validation.md b/docs/product-feature-migration-validation.md index b8dc2dcb..6529c106 100644 --- a/docs/product-feature-migration-validation.md +++ b/docs/product-feature-migration-validation.md @@ -49,8 +49,9 @@ Validated local rollout candidates (all worktrees clean when recorded): | Repository/worktree | Commit | | --- | --- | -| `meta-dynamicdevices-bsp` | `3e2d741a7829` | -| `meta-dynamicdevices-distro` | `ea70a12` | +| `meta-dynamicdevices-bsp` | `c1868e70e8e2` | +| `meta-dynamicdevices-distro` | `e8cbf0061fea` | +| `meta-dynamicdevices` superproject | `d1d2651ca14a` | | Foundries `ci-scripts` | `53e954882c97` | | AESL `factory-core-ci` | `33b25f2d8b6e` | | AESL Factory Definition | `379727319ef4` | @@ -60,8 +61,7 @@ superproject next, then Foundries/AESL configuration and manifest pins. 1. Publish the distro, BSP, superproject, Foundries Factory Definition and AESL runner/config branches in dependency order. -2. Update public/private manifest pins to the published commits. Private-source - pin changes and CI triggers require the physical hardware-key touch. +2. Update public/private manifest pins to the published commits. 3. Build deployable images and retain their rootfs/package manifests as the new baselines. 4. Boot and exercise the Screen and Android targets on hardware, including @@ -69,3 +69,21 @@ superproject next, then Foundries/AESL configuration and manifest pins. and rollback-sensitive behavior. 5. Confirm no live factory, manifest or local build consumer names a legacy distro; only then delete the compatibility distro files. + +## Foundries manifest consumer audit + +The Factory Definition references 19 branch names. Sixteen currently exist in +the Foundries manifest repository. Canonical-stack pin migrations are live for +`main-imx95-frdm-devel` and `main-jaguar-screen`; validated local rollout +commits are prepared for the other fourteen existing branches. + +The following configured branch names do not exist in the manifest repository +and therefore cannot be migrated or built as named: + +- `imx8mm-jaguar-handheld-5in` +- `imx8mm-jaguar-handheld-7in` +- `main-rpi5` + +`main-jaguar-handheld` does exist, but it does not match either configured +handheld branch name. These stale/mismatched Factory Definition entries must be +resolved before the final no-legacy-consumer gate can pass. From d08ce97a959d314c17db78e4d6a7409d549a6c01 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 15:02:20 +0100 Subject: [PATCH 28/72] feat(display): allow screenshots in dev images --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index e8cbf006..b5e1fc94 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit e8cbf0061fea392829138e760045afc631b7c73f +Subproject commit b5e1fc94a79fa7d3808170106ee1e84eecd9872f From 83aa5996d72fe26880e2cb48a06450c94696c659 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 15:18:46 +0100 Subject: [PATCH 29/72] feat(screen): reconcile Qt prototype with product features --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index b5e1fc94..2a258527 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit b5e1fc94a79fa7d3808170106ee1e84eecd9872f +Subproject commit 2a2585278b92506d4df5032b63d5c3a778e0832e From ffbe791d541818eb55d7fe44d147a86ad4e2af2e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 15:50:46 +0100 Subject: [PATCH 30/72] feat(screen): restore branded boot and demo startup --- meta-dynamicdevices-bsp | 2 +- meta-dynamicdevices-distro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index c1868e70..240fd2bb 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit c1868e70e8e2da0d9651bd8b6dd6b5b09f8df4aa +Subproject commit 240fd2bbb1afb9735280241574cab3cced3fdece diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index b5e1fc94..f191e4d7 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit b5e1fc94a79fa7d3808170106ee1e84eecd9872f +Subproject commit f191e4d7f078de100f850787eadbe4133316c2cc From f53a759e6c2bfba16870723fa1c2ee90ec4a93d9 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 17:33:26 +0100 Subject: [PATCH 31/72] bsp: correct Linux splash rotation --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 240fd2bb..50889425 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 240fd2bbb1afb9735280241574cab3cced3fdece +Subproject commit 50889425055b34eb6574ed5edc5550ccea9a1c96 From f413b98fb7acfbe3e4b641d4ac1720e5cc22aaa7 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 2 Sep 2026 17:58:34 +0100 Subject: [PATCH 32/72] bsp: enable maintainable U-Boot screen profiles --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 50889425..e7d8db0d 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 50889425055b34eb6574ed5edc5550ccea9a1c96 +Subproject commit e7d8db0d3a42998158215bb082380c54f10c6d38 From 92f4191e5e28789f1a82ba951a5c4b66beb11b22 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Thu, 3 Sep 2026 10:17:55 +0100 Subject: [PATCH 33/72] fix(godot3): pin final libatomic link fix --- kas/base.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kas/base.yml b/kas/base.yml index 1dd352f4..28d8836c 100644 --- a/kas/base.yml +++ b/kas/base.yml @@ -68,7 +68,7 @@ repos: meta-godot: url: https://github.com/active-esl/meta-godot.git - commit: 58670332d8ae0c37e4ac0df884fede37ad4661bc + commit: 3d28782b820f8c07eab54595fdcd188424096c26 path: build/layers/meta-godot meta-flutter: From 139935c5c0a5c56827183539d0106ceb8ceae4d1 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Thu, 3 Sep 2026 15:17:55 +0100 Subject: [PATCH 34/72] build: pin reconciled Godot touch and atomic fixes --- kas/base.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kas/base.yml b/kas/base.yml index 28d8836c..34077ef8 100644 --- a/kas/base.yml +++ b/kas/base.yml @@ -68,7 +68,7 @@ repos: meta-godot: url: https://github.com/active-esl/meta-godot.git - commit: 3d28782b820f8c07eab54595fdcd188424096c26 + commit: 5396659eed1cb6a0192a9928a35c8fc57f57d1c8 path: build/layers/meta-godot meta-flutter: From cc512e30975cb9d0a7019a13e261cec6f8115ff0 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Thu, 3 Sep 2026 17:11:04 +0100 Subject: [PATCH 35/72] screen: make Qt image proof reproducible --- kas/qt-screen.yml | 4 ++++ meta-dynamicdevices-distro | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/kas/qt-screen.yml b/kas/qt-screen.yml index bad82d14..fc7c5f31 100644 --- a/kas/qt-screen.yml +++ b/kas/qt-screen.yml @@ -12,3 +12,7 @@ local_conf_header: QT_GIT_PROTOCOL = "https" DISTRO_FEATURES:append = " wayland opengl" BBMASK += "meta-dynamicdevices-distro/recipes-graphics/godot/godot_.*[.]bbappend" + # Local proof builds require a syntactically valid module-signing identity. + # These files are throwaway build/conf artifacts and are never committed. + MODSIGN_PRIVKEY:forcevariable = "${TOPDIR}/conf/factory-keys/modsign.key" + MODSIGN_X509:forcevariable = "${TOPDIR}/conf/factory-keys/modsign.crt" diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index e1e24d2e..5537e441 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit e1e24d2ede6909726b589874d7c8025ebabb0fe5 +Subproject commit 5537e4413ead0ce4e4bafda14b82a392530b3f78 From 7f2efe8b04d50d38b8013c6471315e5c2894ae83 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 09:44:31 +0100 Subject: [PATCH 36/72] screen: consume Qt product feature --- kas/qt-screen.yml | 1 - meta-dynamicdevices-distro | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/kas/qt-screen.yml b/kas/qt-screen.yml index fc7c5f31..be5cc3e4 100644 --- a/kas/qt-screen.yml +++ b/kas/qt-screen.yml @@ -8,7 +8,6 @@ distro: lmp-dynamicdevices-headless local_conf_header: qt-screen-prototype: | - SCREEN_QT_DEMO = "1" QT_GIT_PROTOCOL = "https" DISTRO_FEATURES:append = " wayland opengl" BBMASK += "meta-dynamicdevices-distro/recipes-graphics/godot/godot_.*[.]bbappend" diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 5537e441..9701abe8 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 5537e4413ead0ce4e4bafda14b82a392530b3f78 +Subproject commit 9701abe8753e1ae0c7e3d24404db2ebd041bb9ba From 79c750d42e0cc64c5b2bdf3cd0bc0b89162fd8c3 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 10:03:32 +0100 Subject: [PATCH 37/72] screen: keep Godot demos opt-in --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 9701abe8..ef0bb9b0 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 9701abe8753e1ae0c7e3d24404db2ebd041bb9ba +Subproject commit ef0bb9b09a72480e30507e9807d684aea8e98400 From 1af5896e6067b484b33d1f00a0b7cae291de4d86 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 10:32:59 +0100 Subject: [PATCH 38/72] kas: test Qt without injected graphics features --- kas/qt-screen.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/kas/qt-screen.yml b/kas/qt-screen.yml index be5cc3e4..bf514df3 100644 --- a/kas/qt-screen.yml +++ b/kas/qt-screen.yml @@ -9,7 +9,6 @@ distro: lmp-dynamicdevices-headless local_conf_header: qt-screen-prototype: | QT_GIT_PROTOCOL = "https" - DISTRO_FEATURES:append = " wayland opengl" BBMASK += "meta-dynamicdevices-distro/recipes-graphics/godot/godot_.*[.]bbappend" # Local proof builds require a syntactically valid module-signing identity. # These files are throwaway build/conf artifacts and are never committed. From e6ba155a4dc04ce1c3cfae1fd7c50513042d5aaa Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 11:45:54 +0100 Subject: [PATCH 39/72] feat(waydroid): provision Android images outside OSTree --- recipes-support/waydroid/waydroid.bb | 18 ++++++++++++++++++ .../waydroid/waydroid/waydroid-image-provision | 18 ++++++++++++++++++ .../waydroid/waydroid-image-provision.service | 17 +++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 recipes-support/waydroid/waydroid/waydroid-image-provision create mode 100644 recipes-support/waydroid/waydroid/waydroid-image-provision.service diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 15708cdd..71ce9f2d 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -24,6 +24,8 @@ RRECOMMENDS:${PN} += "\ SRC_URI = "git://github.com/herrie82/waydroid.git;branch=herrie/luneos;protocol=https \ file://gbinder.conf \ file://waydroid-net.sh \ + file://waydroid-image-provision \ + file://waydroid-image-provision.service \ " S = "${WORKDIR}/git" @@ -40,6 +42,7 @@ COMPATIBLE_MACHINE:pinetab2 = "(.*)" COMPATIBLE_MACHINE:mido-halium = "(.*)" COMPATIBLE_MACHINE:tissot = "(.*)" COMPATIBLE_MACHINE:imx8mm-lpddr4-evk = "(.*)" +COMPATIBLE_MACHINE:imx8mm-jaguar-screen = "(.*)" COMPATIBLE_MACHINE:imx95-frdm-evk = "(.*)" inherit pkgconfig @@ -48,6 +51,9 @@ inherit pkgconfig #inherit webos_systemd inherit features_check systemd +SYSTEMD_SERVICE:${PN}:imx8mm-jaguar-screen = "waydroid-image-provision.service" +SYSTEMD_AUTO_ENABLE:${PN}:imx8mm-jaguar-screen = "enable" + # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation # prerequisites; fail early if Waydroid is pulled into an incomplete image. @@ -63,6 +69,13 @@ do_install() { make install_luneos DESTDIR=${D} } +do_install:append() { + install -Dm0755 ${WORKDIR}/waydroid-image-provision \ + ${D}${libexecdir}/waydroid-image-provision + install -Dm0644 ${WORKDIR}/waydroid-image-provision.service \ + ${D}${systemd_system_unitdir}/waydroid-image-provision.service +} + # Provided by libgbinder already for Halium devices, but necessary to add for non-Halium devices. do_install:append:pinephone() { @@ -86,6 +99,11 @@ do_install:append:imx8mm-lpddr4-evk() { install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh } +do_install:append:imx8mm-jaguar-screen() { + install -Dm644 -t "${D}${sysconfdir}" "${WORKDIR}/gbinder.conf" + install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh +} + do_install:append:raspberrypi4-64() { install -Dm644 -t "${D}${sysconfdir}" "${WORKDIR}/gbinder.conf" install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision new file mode 100644 index 00000000..7af9103d --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -0,0 +1,18 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu + +images_dir=/var/lib/waydroid/images +config=/var/lib/waydroid/waydroid.cfg + +if [ -s "${images_dir}/system.img" ] && [ -s "${images_dir}/vendor.img" ]; then + exit 0 +fi + +# An interrupted first initialization can leave configuration claiming a +# channel revision whose image was never fully installed. Let Waydroid build +# that configuration again before its checksum-verified channel download. +rm -f "${config}" + +exec /usr/bin/waydroid init diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision.service b/recipes-support/waydroid/waydroid/waydroid-image-provision.service new file mode 100644 index 00000000..a3e69843 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision.service @@ -0,0 +1,17 @@ +[Unit] +Description=Provision Waydroid Android images into persistent storage +Wants=network-online.target +After=network-online.target dbus.service +Before=waydroid-container.service +StartLimitIntervalSec=0 + +[Service] +Type=oneshot +ExecStart=/usr/libexec/waydroid-image-provision +RemainAfterExit=yes +Restart=on-failure +RestartSec=60 +TimeoutStartSec=infinity + +[Install] +WantedBy=multi-user.target From dc9b73c5b7d673981b5da3005845c58d88afb79a Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:06:36 +0100 Subject: [PATCH 40/72] docs: record Jaguar screen galcore rollback release --- ...guar-screen-galcore-baseline-2026.09.04.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md diff --git a/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md new file mode 100644 index 00000000..849c8013 --- /dev/null +++ b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md @@ -0,0 +1,42 @@ +# Jaguar Screen galcore rollback baseline — 2026-09-04 + +This release preserves the last i.MX8MM Jaguar Screen configuration before the +Waydroid graphics stack is changed from NXP galcore/imx-gpu-viv to Mesa +Etnaviv. + +## Release identity + +- Release tag: `jaguar-screen-galcore-baseline-2026.09.04` +- Foundries factory: `dynamic-devices` +- Machine: `imx8mm-jaguar-screen` +- Hardware-lab device: `imx8mm-jaguar-screen-2210a09dab86563` +- Hardware rollback target: Foundries target `2838` +- BSP submodule: `88b8e2706814622476f0eac962935f4888df7af7` +- Distro submodule: `ef0bb9b09a72480e30507e9807d684aea8e98400` + +The manifest and CI repositories carry the same release tag. Their tagged +revisions are the authoritative source assembly and build configuration. + +## Known state + +- The host display is operational through `imx-drm` with NXP's proprietary + `galcore`, `imx-gpu-viv`, EGL/GBM and Weston G2D renderer stack. +- Waydroid 1.4.2 and its Android 13 system/vendor images are installed on the + hardware target. +- Binder support and the Android container start correctly. +- Waydroid graphics do **not** boot to a usable Android UI in this release. + The generic Android vendor image uses Mesa Etnaviv, which cannot allocate + buffers through the host's galcore DRM device. SurfaceFlinger aborts with + `Failed to allocate buffer` / `output buffer not gpu writeable`. +- Android images are provisioned into `/var/lib/waydroid/images` rather than + included in the OSTree payload. + +## Rollback + +Prefer an OTA rollback of the hardware-lab device to Foundries target `2838`. +For a source rollback, check out this tag in `meta-dynamicdevices`, +`lmp-manifest`, and `ci-scripts`, then build the tagged manifest without any +Etnaviv experiment commits. + +Do not describe target 2838 as an Etnaviv or accelerated-Waydroid target: it is +the deliberately retained galcore baseline. From 2615af59c36ac27fcb5bedc91344d1c2550dafc3 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:16:08 +0100 Subject: [PATCH 41/72] fix(waydroid): require Etnaviv on Jaguar screen --- recipes-support/waydroid/waydroid.bb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 71ce9f2d..feb4a3c5 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -57,7 +57,8 @@ SYSTEMD_AUTO_ENABLE:${PN}:imx8mm-jaguar-screen = "enable" # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation # prerequisites; fail early if Waydroid is pulled into an incomplete image. -REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl vulkan" +REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl" +REQUIRED_DISTRO_FEATURES:append:imx8mm-jaguar-screen = " etnaviv" WEBOS_SYSTEMD_SERVICE = "waydroid-init.service waydroid-container.service" From 0c49d6cfa54d794107286e32a8a4701927bbf102 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:16:20 +0100 Subject: [PATCH 42/72] feat(android): wire Etnaviv graphics submodules --- meta-dynamicdevices-bsp | 2 +- meta-dynamicdevices-distro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index 88b8e270..ebd7eb08 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit 88b8e2706814622476f0eac962935f4888df7af7 +Subproject commit ebd7eb08e809722754111a36feed1d2e7d259c88 diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index ef0bb9b0..83373444 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit ef0bb9b09a72480e30507e9807d684aea8e98400 +Subproject commit 8337344463c66ce5fa10dab191754cb9a852566b From ef6e16ad665672fc08f2ff763c54f6be8d32a464 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:34:16 +0100 Subject: [PATCH 43/72] docs: record built galcore rollback target --- docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md index 849c8013..57e7f09d 100644 --- a/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md +++ b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md @@ -11,6 +11,8 @@ Etnaviv. - Machine: `imx8mm-jaguar-screen` - Hardware-lab device: `imx8mm-jaguar-screen-2210a09dab86563` - Hardware rollback target: Foundries target `2838` +- Tagged source release build: Foundries target `2840` +- Target 2840 OSTree: `0008cdaca80b08c524d6db29df02c6c168382ea9e8de07814083380a04d36ea2` - BSP submodule: `88b8e2706814622476f0eac962935f4888df7af7` - Distro submodule: `ef0bb9b09a72480e30507e9807d684aea8e98400` @@ -33,7 +35,9 @@ revisions are the authoritative source assembly and build configuration. ## Rollback -Prefer an OTA rollback of the hardware-lab device to Foundries target `2838`. +Prefer an OTA rollback of the hardware-lab device to Foundries target `2840`, +which was built from the tagged manifest commit. Target `2838` remains the +known-running pre-release hardware fallback. For a source rollback, check out this tag in `meta-dynamicdevices`, `lmp-manifest`, and `ci-scripts`, then build the tagged manifest without any Etnaviv experiment commits. From e81a5cb91e5bf418709e498eac01a2650e910c6b Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:40:05 +0100 Subject: [PATCH 44/72] fix(android): retain Etnaviv feature expansion --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 83373444..c87787ad 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 8337344463c66ce5fa10dab191754cb9a852566b +Subproject commit c87787adfc8da71c0789c49f9318e21da20c6813 From c4424c7ca3a2c421774167dbb560576172c7be7c Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:46:29 +0100 Subject: [PATCH 45/72] docs: record authoritative rollback layer pins --- .../releases/jaguar-screen-galcore-baseline-2026.09.04.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md index 57e7f09d..95e44566 100644 --- a/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md +++ b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md @@ -13,8 +13,12 @@ Etnaviv. - Hardware rollback target: Foundries target `2838` - Tagged source release build: Foundries target `2840` - Target 2840 OSTree: `0008cdaca80b08c524d6db29df02c6c168382ea9e8de07814083380a04d36ea2` -- BSP submodule: `88b8e2706814622476f0eac962935f4888df7af7` -- Distro submodule: `ef0bb9b09a72480e30507e9807d684aea8e98400` +- Manifest-pinned BSP: `6ca2f503c0310cb6605890f09b68158cf3b0cf22` +- Manifest-pinned distro: `84566da924d5f04ddaf1714b785ad6cd2625ef45` + +The top-level repository also records Git submodule links, but Foundries repo +sync treats the explicit BSP and distro projects in `dynamic-devices.xml` as +authoritative. Use the manifest-pinned revisions above for reproduction. The manifest and CI repositories carry the same release tag. Their tagged revisions are the authoritative source assembly and build configuration. From 01ad181e127c811a6e96f93d91c55c5c52e96bb6 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 4 Sep 2026 12:53:21 +0100 Subject: [PATCH 46/72] fix(android): update Mesa Etnaviv provider policy --- meta-dynamicdevices-distro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index c87787ad..5c84b589 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit c87787adfc8da71c0789c49f9318e21da20c6813 +Subproject commit 5c84b589fc1c306120efb7da99df1e2aef781202 From f8e5ddc5a4c030102badf70acb44971355baa0c8 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:02:30 +0100 Subject: [PATCH 47/72] feat(screen): add 2GB Waydroid memory controls --- docs/waydroid-imx8mm-2gb-memory.md | 26 ++++++++++++++ recipes-support/waydroid/waydroid.bb | 16 +++++++++ .../waydroid/waydroid-container-2gb.conf | 13 +++++++ .../waydroid/waydroid-memory-headroom | 19 ++++++++++ .../waydroid/waydroid/waydroid-zram | 36 +++++++++++++++++++ .../waydroid/waydroid/waydroid-zram.service | 14 ++++++++ 6 files changed, 124 insertions(+) create mode 100644 docs/waydroid-imx8mm-2gb-memory.md create mode 100644 recipes-support/waydroid/waydroid/waydroid-container-2gb.conf create mode 100644 recipes-support/waydroid/waydroid/waydroid-memory-headroom create mode 100644 recipes-support/waydroid/waydroid/waydroid-zram create mode 100644 recipes-support/waydroid/waydroid/waydroid-zram.service diff --git a/docs/waydroid-imx8mm-2gb-memory.md b/docs/waydroid-imx8mm-2gb-memory.md new file mode 100644 index 00000000..746f7aa0 --- /dev/null +++ b/docs/waydroid-imx8mm-2gb-memory.md @@ -0,0 +1,26 @@ +# Waydroid memory profile for i.MX8MM 2 GB + +The `imx8mm-jaguar-screen` Waydroid image enables a 768 MiB LZ4 zram swap +device before the container starts. The container has a provisional +`MemoryHigh` of 1200 MiB and `MemoryMax` of 1500 MiB. + +These values are starting controls, not proof of capacity. On the shipping +image, run: + +```sh +/usr/libexec/waydroid-memory-headroom +``` + +Collect results at idle, after kiosk launch, during video playback, during an +application update and after repeated application restarts. Also check the +kernel journal for OOM kills and zram writeback failures. + +Resource gates under the target workload: + +- green: container peak below 70% of `MemoryMax` and host available memory + above 30%; +- amber: either reaches 70%; +- red: either reaches 85%, `memory.events` reports `oom`/`oom_kill`, or the + kernel OOM killer runs. + +Adjust `MemoryHigh`, `MemoryMax` or zram size only from recorded board data. diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index feb4a3c5..7f617e45 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -27,6 +27,12 @@ SRC_URI = "git://github.com/herrie82/waydroid.git;branch=herrie/luneos;protocol= file://waydroid-image-provision \ file://waydroid-image-provision.service \ " +SRC_URI:append:imx8mm-jaguar-screen = " \ + file://waydroid-zram \ + file://waydroid-zram.service \ + file://waydroid-container-2gb.conf \ + file://waydroid-memory-headroom \ +" S = "${WORKDIR}/git" # Needs quite new kernel (probably >= 3.18) and from LuneOS supported machines @@ -52,8 +58,11 @@ inherit pkgconfig inherit features_check systemd SYSTEMD_SERVICE:${PN}:imx8mm-jaguar-screen = "waydroid-image-provision.service" +SYSTEMD_SERVICE:${PN}:append:imx8mm-jaguar-screen = " waydroid-zram.service" SYSTEMD_AUTO_ENABLE:${PN}:imx8mm-jaguar-screen = "enable" +RDEPENDS:${PN}:append:imx8mm-jaguar-screen = " kmod util-linux-mkswap util-linux-swaponoff" + # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation # prerequisites; fail early if Waydroid is pulled into an incomplete image. @@ -103,6 +112,13 @@ do_install:append:imx8mm-lpddr4-evk() { do_install:append:imx8mm-jaguar-screen() { install -Dm644 -t "${D}${sysconfdir}" "${WORKDIR}/gbinder.conf" install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh + install -Dm0755 ${WORKDIR}/waydroid-zram ${D}${libexecdir}/waydroid-zram + install -Dm0755 ${WORKDIR}/waydroid-memory-headroom ${D}${libexecdir}/waydroid-memory-headroom + install -Dm0644 ${WORKDIR}/waydroid-zram.service \ + ${D}${systemd_system_unitdir}/waydroid-zram.service + install -d ${D}${systemd_system_unitdir}/waydroid-container.service.d + install -m 0644 ${WORKDIR}/waydroid-container-2gb.conf \ + ${D}${systemd_system_unitdir}/waydroid-container.service.d/20-memory-2gb.conf } do_install:append:raspberrypi4-64() { diff --git a/recipes-support/waydroid/waydroid/waydroid-container-2gb.conf b/recipes-support/waydroid/waydroid/waydroid-container-2gb.conf new file mode 100644 index 00000000..26d17cd3 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-container-2gb.conf @@ -0,0 +1,13 @@ +[Unit] +Requires=waydroid-zram.service +After=waydroid-zram.service + +[Service] +# Provisional limits for the 2 GB board. Validate under the shipping kiosk +# workload before release; MemoryHigh provides reclaim pressure before the hard +# cap while reserving roughly 512 MiB for Linux, display and update services. +MemoryAccounting=yes +MemoryHigh=1200M +MemoryMax=1500M +MemorySwapMax=768M +OOMPolicy=stop diff --git a/recipes-support/waydroid/waydroid/waydroid-memory-headroom b/recipes-support/waydroid/waydroid/waydroid-memory-headroom new file mode 100644 index 00000000..942c5a4f --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-memory-headroom @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu + +cgroup=/sys/fs/cgroup/system.slice/waydroid-container.service + +awk '/^(MemTotal|MemAvailable|SwapTotal|SwapFree):/ { print }' /proc/meminfo + +for item in memory.current memory.peak memory.high memory.max memory.swap.current memory.swap.max memory.events; do + if [ -r "${cgroup}/${item}" ]; then + printf '%s=' "${item}" + cat "${cgroup}/${item}" + fi +done + +if grep -q '^/dev/zram0[[:space:]]' /proc/swaps 2>/dev/null; then + echo "zram=active" +else + echo "zram=inactive" +fi diff --git a/recipes-support/waydroid/waydroid/waydroid-zram b/recipes-support/waydroid/waydroid/waydroid-zram new file mode 100644 index 00000000..0ccb8b4d --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-zram @@ -0,0 +1,36 @@ +#!/bin/sh +set -eu + +zram_device=/dev/zram0 +zram_size_bytes=805306368 + +stop_zram() { + if grep -q "^${zram_device}[[:space:]]" /proc/swaps 2>/dev/null; then + swapoff "${zram_device}" + fi + if [ -w /sys/block/zram0/reset ]; then + echo 1 > /sys/block/zram0/reset + fi +} + +if [ "${1:-start}" = stop ]; then + stop_zram + exit 0 +fi + +modprobe zram +[ -b "${zram_device}" ] || { + echo "zram device was not created" >&2 + exit 1 +} + +if grep -q "^${zram_device}[[:space:]]" /proc/swaps 2>/dev/null; then + exit 0 +fi + +if grep -qw lz4 /sys/block/zram0/comp_algorithm; then + echo lz4 > /sys/block/zram0/comp_algorithm +fi +echo "${zram_size_bytes}" > /sys/block/zram0/disksize +mkswap "${zram_device}" +swapon -p 100 "${zram_device}" diff --git a/recipes-support/waydroid/waydroid/waydroid-zram.service b/recipes-support/waydroid/waydroid/waydroid-zram.service new file mode 100644 index 00000000..1e1d9100 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-zram.service @@ -0,0 +1,14 @@ +[Unit] +Description=Compressed swap for the 2 GB Waydroid product +Documentation=https://github.com/DynamicDevices/meta-dynamicdevices +After=systemd-modules-load.service +Before=waydroid-container.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/libexec/waydroid-zram start +ExecStop=/usr/libexec/waydroid-zram stop + +[Install] +WantedBy=multi-user.target From 1cddb2865d7c0a257e10205cee49e41abd05505d Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:13:48 +0100 Subject: [PATCH 48/72] feat(waydroid): pin Android 16 host runtime --- .../waydroid/python3-gbinder_git.bb | 12 ++++---- recipes-support/waydroid/waydroid.bb | 28 +++++++++++++++---- .../waydroid/waydroid-luneos-appinfo.json | 11 ++++++++ .../waydroid/waydroid/waydroid-luneos.env | 2 ++ .../waydroid/waydroid/waydroid-luneos.sh | 3 ++ 5 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 recipes-support/waydroid/waydroid/waydroid-luneos-appinfo.json create mode 100644 recipes-support/waydroid/waydroid/waydroid-luneos.env create mode 100644 recipes-support/waydroid/waydroid/waydroid-luneos.sh diff --git a/recipes-support/waydroid/python3-gbinder_git.bb b/recipes-support/waydroid/python3-gbinder_git.bb index 2e0307c8..93f2f570 100644 --- a/recipes-support/waydroid/python3-gbinder_git.bb +++ b/recipes-support/waydroid/python3-gbinder_git.bb @@ -7,13 +7,11 @@ LICENSE = "GPL-3.0-only" SECTION = "devel/python" LIC_FILES_CHKSUM = "file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464" -# 1.1.1 fails with the Cython 3 toolchain used by Scarthgap. The bullseye -# 1.1.2 tip includes the required noexcept declarations. -PV = "1.1.2+git${SRCPV}" -SRCREV = "5089d76d4cd958cedda0028ffd752c25508dd382" -SRC_URI = "git://github.com/waydroid/gbinder-python.git;branch=bullseye;protocol=https \ - file://0001-setup.py-Migrate-away-from-deprecated-distutils.core.patch \ -" +# Waydroid 1.6.3 requires gbinder-python >= 1.3.0. Version 1.3.1 retains +# the Cython 3 noexcept fixes required by the Scarthgap toolchain. +PV = "1.3.1+git${SRCPV}" +SRCREV = "86b8feba4cacd0952b010d1c3af6a29a0c146ced" +SRC_URI = "git://github.com/waydroid/gbinder-python.git;branch=main;protocol=https" S = "${WORKDIR}/git" diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 7f617e45..100c0957 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -7,8 +7,8 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464" SECTION = "webos/support" -SRCREV = "41f309f4c185a2c716723c081274eb56eb9263ff" -SPV = "1.4.2" +SRCREV = "5b7e2e71be3f6bfaaaab3b461251dacaf1ce4991" +SPV = "1.6.3" PV = "${SPV}+git${SRCPV}" @@ -21,8 +21,11 @@ RRECOMMENDS:${PN} += "\ kernel-module-ashmem-linux \ " -SRC_URI = "git://github.com/herrie82/waydroid.git;branch=herrie/luneos;protocol=https \ +SRC_URI = "git://github.com/waydroid/waydroid.git;branch=main;protocol=https \ file://gbinder.conf \ + file://waydroid-luneos.env \ + file://waydroid-luneos-appinfo.json \ + file://waydroid-luneos.sh \ file://waydroid-net.sh \ file://waydroid-image-provision \ file://waydroid-image-provision.service \ @@ -73,10 +76,25 @@ WEBOS_SYSTEMD_SERVICE = "waydroid-init.service waydroid-container.service" CLEANBROKEN = "1" -EXTRA_OEMAKE = "SYSD_DIR=${systemd_system_unitdir} USE_NFTABLES="1" WAYDROID_VERSION=${SPV}" +EXTRA_OEMAKE = "PREFIX=${prefix} SYSCONFDIR=${sysconfdir} SYSD_DIR=${systemd_system_unitdir} USE_NFTABLES=1" do_install() { - make install_luneos DESTDIR=${D} + oe_runmake install DESTDIR=${D} + + # Keep the small webOS/LuneOS launcher integration out of the upstream + # source tree so that the maintained Waydroid release can remain pinned. + install -d ${D}${prefix}/palm/applications/id.waydro.container + install -d ${D}${sysconfdir}/id.waydro.Container + install -m 0644 ${S}/data/AppIcon.png \ + ${D}${prefix}/palm/applications/id.waydro.container/icon.png + install -m 0644 ${WORKDIR}/waydroid-luneos-appinfo.json \ + ${D}${prefix}/palm/applications/id.waydro.container/appinfo.json + sed -i -e 's:__VERSION__:${SPV}:g' \ + ${D}${prefix}/palm/applications/id.waydro.container/appinfo.json + install -m 0755 ${WORKDIR}/waydroid-luneos.sh \ + ${D}${prefix}/palm/applications/id.waydro.container/waydroid.sh + install -m 0644 ${WORKDIR}/waydroid-luneos.env \ + ${D}${sysconfdir}/id.waydro.Container/waydroid.env } do_install:append() { diff --git a/recipes-support/waydroid/waydroid/waydroid-luneos-appinfo.json b/recipes-support/waydroid/waydroid/waydroid-luneos-appinfo.json new file mode 100644 index 00000000..692646a1 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-luneos-appinfo.json @@ -0,0 +1,11 @@ +{ + "id": "id.waydro.container", + "title": "Waydroid", + "version": "__VERSION__", + "main": "waydroid.sh", + "params": "", + "type": "native", + "uiRevision": 2, + "vendorurl": "https://waydro.id", + "icon": "icon.png" +} diff --git a/recipes-support/waydroid/waydroid/waydroid-luneos.env b/recipes-support/waydroid/waydroid/waydroid-luneos.env new file mode 100644 index 00000000..71ab013d --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-luneos.env @@ -0,0 +1,2 @@ +XDG_RUNTIME_DIR=/tmp/luna-session +XDG_SESSION_TYPE=wayland diff --git a/recipes-support/waydroid/waydroid/waydroid-luneos.sh b/recipes-support/waydroid/waydroid/waydroid-luneos.sh new file mode 100644 index 00000000..9faf4e42 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-luneos.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /usr/bin/waydroid show-full-ui From 93e12176203fcb8c59884227a9e0be3e1ca5ca95 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:26:34 +0100 Subject: [PATCH 49/72] feat(waydroid): pin Etnaviv runtime graphics --- .../waydroid/waydroid-image-provision | 72 ++++++++++++++++--- scripts/validation/waydroid-gpu-provision.sh | 39 ++++++++++ 2 files changed, 102 insertions(+), 9 deletions(-) create mode 100755 scripts/validation/waydroid-gpu-provision.sh diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index 7af9103d..f2d4eb29 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -3,16 +3,70 @@ set -eu -images_dir=/var/lib/waydroid/images -config=/var/lib/waydroid/waydroid.cfg +images_dir=${WAYDROID_IMAGES_DIR:-/var/lib/waydroid/images} +config=${WAYDROID_CONFIG:-/var/lib/waydroid/waydroid.cfg} +sys_drm_dir=${WAYDROID_SYS_DRM_DIR:-/sys/class/drm} +dev_dri_dir=${WAYDROID_DEV_DRI_DIR:-/dev/dri} +waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} -if [ -s "${images_dir}/system.img" ] && [ -s "${images_dir}/vendor.img" ]; then - exit 0 +if ! [ -s "${images_dir}/system.img" ] || ! [ -s "${images_dir}/vendor.img" ]; then + # An interrupted first initialization can leave configuration claiming a + # channel revision whose image was never fully installed. Let Waydroid + # build that configuration again before its checksum-verified download. + rm -f "${config}" + "${waydroid_bin}" init fi -# An interrupted first initialization can leave configuration claiming a -# channel revision whose image was never fully installed. Let Waydroid build -# that configuration again before its checksum-verified channel download. -rm -f "${config}" +render_node= +for sys_node in "${sys_drm_dir}"/renderD*; do + [ -r "${sys_node}/device/uevent" ] || continue + if grep -Fqx 'DRIVER=etnaviv' "${sys_node}/device/uevent"; then + candidate="${dev_dri_dir}/${sys_node##*/}" + if [ -c "${candidate}" ] || [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" = 1 ]; then + render_node=${candidate} + break + fi + fi +done -exec /usr/bin/waydroid init +if [ -z "${render_node}" ]; then + echo "No Etnaviv DRM render node is available; refusing GPU fallback" >&2 + exit 1 +fi + +# Waydroid otherwise selects the first DRM node and may advertise a software +# or unrelated GPU. Persist an exact, reviewable Android graphics contract. +python3 - "${config}" "${render_node}" <<'PY' +import configparser +import os +import sys + +path, render_node = sys.argv[1:] +cfg = configparser.ConfigParser(interpolation=None) +cfg.optionxform = str +cfg.read(path) + +if not cfg.has_section("waydroid"): + cfg.add_section("waydroid") +cfg.set("waydroid", "drm_device", render_node) + +if not cfg.has_section("properties"): + cfg.add_section("properties") +properties = { + "gralloc.gbm.device": render_node, + "ro.hardware.egl": "mesa", + "ro.hardware.gralloc": "minigbm_gbm_mesa", + "ro.hardware.hwcomposer": "waydroid", + "ro.opengles.version": "196609", +} +for key, value in properties.items(): + cfg.set("properties", key, value) +cfg.remove_option("properties", "ro.hardware.vulkan") + +tmp = path + ".tmp" +with open(tmp, "w", encoding="utf-8") as output: + cfg.write(output) +os.replace(tmp, path) +PY + +echo "Pinned Waydroid graphics to Etnaviv node ${render_node}" diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh new file mode 100755 index 00000000..b0c77268 --- /dev/null +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +provision=${repo_root}/recipes-support/waydroid/waydroid/waydroid-image-provision +test_root=$(mktemp -d /tmp/waydroid-gpu-provision.XXXXXX) +trap 'rm -rf "${test_root}"' EXIT HUP INT TERM + +mkdir -p "${test_root}/images" \ + "${test_root}/sys/class/drm/renderD128/device" \ + "${test_root}/dev/dri" +printf 'image\n' > "${test_root}/images/system.img" +printf 'image\n' > "${test_root}/images/vendor.img" +printf 'DRIVER=etnaviv\n' > "${test_root}/sys/class/drm/renderD128/device/uevent" +printf '[waydroid]\narch = arm64\n\n[properties]\nro.hardware.vulkan = lvp\n' \ + > "${test_root}/waydroid.cfg" + +WAYDROID_IMAGES_DIR=${test_root}/images \ +WAYDROID_CONFIG=${test_root}/waydroid.cfg \ +WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ +WAYDROID_DEV_DRI_DIR=${test_root}/dev/dri \ +WAYDROID_ALLOW_FAKE_DRM=1 \ + sh "${provision}" + +config=${test_root}/waydroid.cfg +grep -Fqx 'drm_device = '"${test_root}"'/dev/dri/renderD128' "${config}" +grep -Fqx 'gralloc.gbm.device = '"${test_root}"'/dev/dri/renderD128' "${config}" +grep -Fqx 'ro.hardware.egl = mesa' "${config}" +grep -Fqx 'ro.hardware.gralloc = minigbm_gbm_mesa' "${config}" +grep -Fqx 'ro.hardware.hwcomposer = waydroid' "${config}" +grep -Fqx 'ro.opengles.version = 196609' "${config}" +if grep -Fq 'ro.hardware.vulkan' "${config}"; then + echo 'Vulkan fallback was not removed' >&2 + exit 1 +fi + +echo 'Waydroid Etnaviv GPU provisioning: passed' From f1eedf800d219c283ec15a2df379daba42614fb5 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:36:35 +0100 Subject: [PATCH 50/72] security(waydroid): restrict GPU device passthrough --- recipes-support/waydroid/waydroid.bb | 1 + ...xc-limit-graphics-device-permissions.patch | 103 ++++++++++++++++++ .../waydroid/waydroid-image-provision | 25 ++++- scripts/validation/waydroid-gpu-provision.sh | 7 +- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 100c0957..51b0a7ff 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -22,6 +22,7 @@ RRECOMMENDS:${PN} += "\ " SRC_URI = "git://github.com/waydroid/waydroid.git;branch=main;protocol=https \ + file://0001-lxc-limit-graphics-device-permissions.patch \ file://gbinder.conf \ file://waydroid-luneos.env \ file://waydroid-luneos-appinfo.json \ diff --git a/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch new file mode 100644 index 00000000..79ccff93 --- /dev/null +++ b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch @@ -0,0 +1,103 @@ +From 4e294bc169a3455c8d50bf1ff5c1636ae8d3825f Mon Sep 17 00:00:00 2001 +From: Active ESL +Date: Mon, 7 Sep 2026 15:00:00 +0100 +Subject: [PATCH] lxc: limit graphics device permissions + +Do not chmod every DRM render node and DMA heap on the host to 0777. The +configured DRM node is the only render node exposed by lxc.py, so apply +permissions to that node alone. Permit products to provide a semicolon-separated +DMA heap allowlist and use 0666, without meaningless execute bits, for the +selected character devices. + +The empty dma_heap_devices setting retains upstream discovery for products +which have not opted into an allowlist. + +Signed-off-by: Active ESL +--- + tools/actions/container_manager.py | 15 ++++++++++----- + tools/helpers/lxc.py | 23 ++++++++++++++++++++++- + 2 files changed, 32 insertions(+), 6 deletions(-) + +diff --git a/tools/actions/container_manager.py b/tools/actions/container_manager.py +index 6e088bb..ad71ad8 100644 +--- a/tools/actions/container_manager.py ++++ b/tools/actions/container_manager.py +@@ -66,6 +66,7 @@ class DbusContainerManager(dbus.service.Object): + return {} + + def set_permissions(args, perm_list=None, mode="777"): ++ use_default_list = perm_list is None + def chmod(path, mode): + if os.path.exists(path): + command = ["chmod", mode, "-R", path] +@@ -92,18 +93,22 @@ def set_permissions(args, perm_list=None, mode="777"): + "/dev/ion", + ] + +- # DRM render nodes +- perm_list.extend(glob.glob("/dev/dri/renderD*")) + # Framebuffers + perm_list.extend(glob.glob("/dev/fb*")) + # Videos + perm_list.extend(glob.glob("/dev/video*")) +- # DMA-BUF Heaps +- perm_list.extend(glob.glob("/dev/dma_heap/*")) +- + for path in perm_list: + chmod(path, mode) + ++ if use_default_list: ++ # The container receives only the selected render node. Do not relax ++ # permissions on unrelated host GPUs or add execute bits to devices. ++ render, _ = helpers.gpu.getDriNode(args) ++ if render: ++ chmod(render, "666") ++ for path in helpers.lxc.get_dma_heap_devices(args): ++ chmod(path, "666") ++ + def start(args): + mainloop = GLib.MainLoop() + +diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py +index 1b6ff49..937cd52 100644 +--- a/tools/helpers/lxc.py ++++ b/tools/helpers/lxc.py +@@ -37,6 +37,27 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): + nodes.append(entry) + return True + ++def get_dma_heap_devices(args): ++ """Return configured DMA heaps, rejecting paths outside /dev/dma_heap.""" ++ cfg = tools.config.load(args) ++ configured = cfg["waydroid"].get("dma_heap_devices", "").strip() ++ if not configured: ++ return sorted(glob.glob("/dev/dma_heap/*")) ++ ++ devices = [] ++ for item in configured.split(";"): ++ path = os.path.normpath(item.strip()) ++ if (not path.startswith("/dev/dma_heap/") or ++ os.path.dirname(path) != "/dev/dma_heap" or ++ any(char in path for char in ["\n", "\r"])): ++ logging.warning("Ignoring invalid DMA heap path: %s", item) ++ continue ++ if os.path.exists(path): ++ devices.append(path) ++ else: ++ logging.warning("Configured DMA heap does not exist: %s", path) ++ return devices ++ + def generate_nodes_lxc_config(args): + nodes = [] + def make_entry(src, dist=None, mnt_type="none", options="bind,create=file,optional 0 0", check=True): +@@ -68,7 +89,7 @@ def generate_nodes_lxc_config(args): + make_entry(n) + for n in glob.glob("/dev/video*"): + make_entry(n) +- for n in glob.glob("/dev/dma_heap/*"): ++ for n in get_dma_heap_devices(args): + make_entry(n) + + # Binder dev nodes +-- +2.43.0 diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index f2d4eb29..8edfa6d0 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -34,14 +34,34 @@ if [ -z "${render_node}" ]; then exit 1 fi +dma_heap_devices= +separator= +have_system_heap=false +for heap_name in system system-uncached linux,cma; do + candidate=/dev/dma_heap/${heap_name} + test_candidate=${WAYDROID_DEV_DMA_HEAP_DIR:-/dev/dma_heap}/${heap_name} + if [ -c "${test_candidate}" ] || { + [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" = 1 ] && [ -e "${test_candidate}" ] + }; then + dma_heap_devices=${dma_heap_devices}${separator}${candidate} + separator=';' + [ "${heap_name}" = system ] && have_system_heap=true + fi +done + +if [ "${have_system_heap}" != true ]; then + echo "The required /dev/dma_heap/system device is unavailable" >&2 + exit 1 +fi + # Waydroid otherwise selects the first DRM node and may advertise a software # or unrelated GPU. Persist an exact, reviewable Android graphics contract. -python3 - "${config}" "${render_node}" <<'PY' +python3 - "${config}" "${render_node}" "${dma_heap_devices}" <<'PY' import configparser import os import sys -path, render_node = sys.argv[1:] +path, render_node, dma_heap_devices = sys.argv[1:] cfg = configparser.ConfigParser(interpolation=None) cfg.optionxform = str cfg.read(path) @@ -49,6 +69,7 @@ cfg.read(path) if not cfg.has_section("waydroid"): cfg.add_section("waydroid") cfg.set("waydroid", "drm_device", render_node) +cfg.set("waydroid", "dma_heap_devices", dma_heap_devices) if not cfg.has_section("properties"): cfg.add_section("properties") diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index b0c77268..1090c57a 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -10,10 +10,13 @@ trap 'rm -rf "${test_root}"' EXIT HUP INT TERM mkdir -p "${test_root}/images" \ "${test_root}/sys/class/drm/renderD128/device" \ - "${test_root}/dev/dri" + "${test_root}/dev/dri" \ + "${test_root}/dev/dma_heap" printf 'image\n' > "${test_root}/images/system.img" printf 'image\n' > "${test_root}/images/vendor.img" printf 'DRIVER=etnaviv\n' > "${test_root}/sys/class/drm/renderD128/device/uevent" +printf 'heap\n' > "${test_root}/dev/dma_heap/system" +printf 'heap\n' > "${test_root}/dev/dma_heap/linux,cma" printf '[waydroid]\narch = arm64\n\n[properties]\nro.hardware.vulkan = lvp\n' \ > "${test_root}/waydroid.cfg" @@ -21,11 +24,13 @@ WAYDROID_IMAGES_DIR=${test_root}/images \ WAYDROID_CONFIG=${test_root}/waydroid.cfg \ WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ WAYDROID_DEV_DRI_DIR=${test_root}/dev/dri \ +WAYDROID_DEV_DMA_HEAP_DIR=${test_root}/dev/dma_heap \ WAYDROID_ALLOW_FAKE_DRM=1 \ sh "${provision}" config=${test_root}/waydroid.cfg grep -Fqx 'drm_device = '"${test_root}"'/dev/dri/renderD128' "${config}" +grep -Fqx 'dma_heap_devices = /dev/dma_heap/system;/dev/dma_heap/linux,cma' "${config}" grep -Fqx 'gralloc.gbm.device = '"${test_root}"'/dev/dri/renderD128' "${config}" grep -Fqx 'ro.hardware.egl = mesa' "${config}" grep -Fqx 'ro.hardware.gralloc = minigbm_gbm_mesa' "${config}" From 4c239a2e0b90c889a2f65e7a75380f288ca82a4f Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:46:37 +0100 Subject: [PATCH 51/72] security(waydroid): allowlist V4L2 decoder nodes --- ...xc-limit-graphics-device-permissions.patch | 51 +++++++++++++++---- .../waydroid/waydroid-image-provision | 27 +++++++++- scripts/validation/waydroid-gpu-provision.sh | 8 ++- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch index 79ccff93..62fe36d5 100644 --- a/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch +++ b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch @@ -1,7 +1,7 @@ From 4e294bc169a3455c8d50bf1ff5c1636ae8d3825f Mon Sep 17 00:00:00 2001 From: Active ESL Date: Mon, 7 Sep 2026 15:00:00 +0100 -Subject: [PATCH] lxc: limit graphics device permissions +Subject: [PATCH] lxc: limit graphics and video device permissions Do not chmod every DRM render node and DMA heap on the host to 0777. The configured DRM node is the only render node exposed by lxc.py, so apply @@ -9,14 +9,17 @@ permissions to that node alone. Permit products to provide a semicolon-separated DMA heap allowlist and use 0666, without meaningless execute bits, for the selected character devices. -The empty dma_heap_devices setting retains upstream discovery for products -which have not opted into an allowlist. +Apply the same allowlist model to V4L2 nodes so a hardware decoder does not +grant the Android container access to unrelated cameras or capture devices. + +Empty dma_heap_devices and video_devices settings retain upstream discovery +for products which have not opted into an allowlist. Signed-off-by: Active ESL --- - tools/actions/container_manager.py | 15 ++++++++++----- - tools/helpers/lxc.py | 23 ++++++++++++++++++++++- - 2 files changed, 32 insertions(+), 6 deletions(-) + tools/actions/container_manager.py | 19 ++++++++++++------- + tools/helpers/lxc.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/tools/actions/container_manager.py b/tools/actions/container_manager.py index 6e088bb..ad71ad8 100644 @@ -38,8 +41,8 @@ index 6e088bb..ad71ad8 100644 - perm_list.extend(glob.glob("/dev/dri/renderD*")) # Framebuffers perm_list.extend(glob.glob("/dev/fb*")) - # Videos - perm_list.extend(glob.glob("/dev/video*")) +- # Videos +- perm_list.extend(glob.glob("/dev/video*")) - # DMA-BUF Heaps - perm_list.extend(glob.glob("/dev/dma_heap/*")) - @@ -54,6 +57,8 @@ index 6e088bb..ad71ad8 100644 + chmod(render, "666") + for path in helpers.lxc.get_dma_heap_devices(args): + chmod(path, "666") ++ for path in helpers.lxc.get_video_devices(args): ++ chmod(path, "666") + def start(args): mainloop = GLib.MainLoop() @@ -62,7 +67,7 @@ diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py index 1b6ff49..937cd52 100644 --- a/tools/helpers/lxc.py +++ b/tools/helpers/lxc.py -@@ -37,6 +37,27 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): +@@ -37,6 +37,48 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): nodes.append(entry) return True @@ -86,18 +91,42 @@ index 1b6ff49..937cd52 100644 + else: + logging.warning("Configured DMA heap does not exist: %s", path) + return devices ++ ++def get_video_devices(args): ++ """Return configured V4L2 nodes, rejecting paths outside /dev/videoN.""" ++ cfg = tools.config.load(args) ++ configured = cfg["waydroid"].get("video_devices", "").strip() ++ if not configured: ++ return sorted(glob.glob("/dev/video*")) ++ ++ devices = [] ++ for item in configured.split(";"): ++ path = os.path.normpath(item.strip()) ++ name = os.path.basename(path) ++ if (os.path.dirname(path) != "/dev" or not name.startswith("video") or ++ not name[5:].isdigit() or any(char in path for char in ["\n", "\r"])): ++ logging.warning("Ignoring invalid V4L2 device path: %s", item) ++ continue ++ if os.path.exists(path): ++ devices.append(path) ++ else: ++ logging.warning("Configured V4L2 device does not exist: %s", path) ++ return devices + def generate_nodes_lxc_config(args): nodes = [] def make_entry(src, dist=None, mnt_type="none", options="bind,create=file,optional 0 0", check=True): -@@ -68,7 +89,7 @@ def generate_nodes_lxc_config(args): +@@ -66,9 +108,9 @@ def generate_nodes_lxc_config(args): + for n in glob.glob("/dev/graphics/fb*"): make_entry(n) - for n in glob.glob("/dev/video*"): +- for n in glob.glob("/dev/video*"): ++ for n in get_video_devices(args): make_entry(n) - for n in glob.glob("/dev/dma_heap/*"): + for n in get_dma_heap_devices(args): make_entry(n) # Binder dev nodes + make_entry("/dev/" + args.BINDER_DRIVER, "dev/binder", check=False) -- 2.43.0 diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index 8edfa6d0..82cb20da 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -7,6 +7,8 @@ images_dir=${WAYDROID_IMAGES_DIR:-/var/lib/waydroid/images} config=${WAYDROID_CONFIG:-/var/lib/waydroid/waydroid.cfg} sys_drm_dir=${WAYDROID_SYS_DRM_DIR:-/sys/class/drm} dev_dri_dir=${WAYDROID_DEV_DRI_DIR:-/dev/dri} +sys_video_dir=${WAYDROID_SYS_VIDEO_DIR:-/sys/class/video4linux} +dev_video_dir=${WAYDROID_DEV_VIDEO_DIR:-/dev} waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} if ! [ -s "${images_dir}/system.img" ] || ! [ -s "${images_dir}/vendor.img" ]; then @@ -54,14 +56,34 @@ if [ "${have_system_heap}" != true ]; then exit 1 fi +video_devices= +separator= +for sys_node in "${sys_video_dir}"/video*; do + [ -r "${sys_node}/name" ] || continue + grep -Fqx 'vsi_v4l2dec' "${sys_node}/name" || continue + node_name=${sys_node##*/} + test_candidate=${dev_video_dir}/${node_name} + if [ -c "${test_candidate}" ] || { + [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" = 1 ] && [ -e "${test_candidate}" ] + }; then + video_devices=${video_devices}${separator}/dev/${node_name} + separator=';' + fi +done + +if [ -z "${video_devices}" ]; then + echo "No vsi_v4l2dec device is available; refusing unrelated V4L2 nodes" >&2 + exit 1 +fi + # Waydroid otherwise selects the first DRM node and may advertise a software # or unrelated GPU. Persist an exact, reviewable Android graphics contract. -python3 - "${config}" "${render_node}" "${dma_heap_devices}" <<'PY' +python3 - "${config}" "${render_node}" "${dma_heap_devices}" "${video_devices}" <<'PY' import configparser import os import sys -path, render_node, dma_heap_devices = sys.argv[1:] +path, render_node, dma_heap_devices, video_devices = sys.argv[1:] cfg = configparser.ConfigParser(interpolation=None) cfg.optionxform = str cfg.read(path) @@ -70,6 +92,7 @@ if not cfg.has_section("waydroid"): cfg.add_section("waydroid") cfg.set("waydroid", "drm_device", render_node) cfg.set("waydroid", "dma_heap_devices", dma_heap_devices) +cfg.set("waydroid", "video_devices", video_devices) if not cfg.has_section("properties"): cfg.add_section("properties") diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 1090c57a..4ef19d36 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -10,6 +10,7 @@ trap 'rm -rf "${test_root}"' EXIT HUP INT TERM mkdir -p "${test_root}/images" \ "${test_root}/sys/class/drm/renderD128/device" \ + "${test_root}/sys/class/video4linux/video2" \ "${test_root}/dev/dri" \ "${test_root}/dev/dma_heap" printf 'image\n' > "${test_root}/images/system.img" @@ -17,6 +18,8 @@ printf 'image\n' > "${test_root}/images/vendor.img" printf 'DRIVER=etnaviv\n' > "${test_root}/sys/class/drm/renderD128/device/uevent" printf 'heap\n' > "${test_root}/dev/dma_heap/system" printf 'heap\n' > "${test_root}/dev/dma_heap/linux,cma" +printf 'vsi_v4l2dec\n' > "${test_root}/sys/class/video4linux/video2/name" +printf 'video\n' > "${test_root}/dev/video2" printf '[waydroid]\narch = arm64\n\n[properties]\nro.hardware.vulkan = lvp\n' \ > "${test_root}/waydroid.cfg" @@ -25,12 +28,15 @@ WAYDROID_CONFIG=${test_root}/waydroid.cfg \ WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ WAYDROID_DEV_DRI_DIR=${test_root}/dev/dri \ WAYDROID_DEV_DMA_HEAP_DIR=${test_root}/dev/dma_heap \ +WAYDROID_SYS_VIDEO_DIR=${test_root}/sys/class/video4linux \ +WAYDROID_DEV_VIDEO_DIR=${test_root}/dev \ WAYDROID_ALLOW_FAKE_DRM=1 \ sh "${provision}" config=${test_root}/waydroid.cfg grep -Fqx 'drm_device = '"${test_root}"'/dev/dri/renderD128' "${config}" grep -Fqx 'dma_heap_devices = /dev/dma_heap/system;/dev/dma_heap/linux,cma' "${config}" +grep -Fqx 'video_devices = /dev/video2' "${config}" grep -Fqx 'gralloc.gbm.device = '"${test_root}"'/dev/dri/renderD128' "${config}" grep -Fqx 'ro.hardware.egl = mesa' "${config}" grep -Fqx 'ro.hardware.gralloc = minigbm_gbm_mesa' "${config}" @@ -41,4 +47,4 @@ if grep -Fq 'ro.hardware.vulkan' "${config}"; then exit 1 fi -echo 'Waydroid Etnaviv GPU provisioning: passed' +echo 'Waydroid Etnaviv GPU and V4L2 provisioning: passed' From 95548d5d9228ffd4978ca172cb344a0471af3d08 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:50:44 +0100 Subject: [PATCH 52/72] test(waydroid): add acceleration evidence checks --- recipes-support/waydroid/waydroid.bb | 2 + .../waydroid/waydroid-acceleration-check | 94 +++++++++++++++++++ .../waydroid/waydroid-memory-headroom | 43 ++++++++- scripts/validation/waydroid-gpu-provision.sh | 28 ++++++ 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 recipes-support/waydroid/waydroid/waydroid-acceleration-check diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 51b0a7ff..5527805b 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -36,6 +36,7 @@ SRC_URI:append:imx8mm-jaguar-screen = " \ file://waydroid-zram.service \ file://waydroid-container-2gb.conf \ file://waydroid-memory-headroom \ + file://waydroid-acceleration-check \ " S = "${WORKDIR}/git" @@ -133,6 +134,7 @@ do_install:append:imx8mm-jaguar-screen() { install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh install -Dm0755 ${WORKDIR}/waydroid-zram ${D}${libexecdir}/waydroid-zram install -Dm0755 ${WORKDIR}/waydroid-memory-headroom ${D}${libexecdir}/waydroid-memory-headroom + install -Dm0755 ${WORKDIR}/waydroid-acceleration-check ${D}${libexecdir}/waydroid-acceleration-check install -Dm0644 ${WORKDIR}/waydroid-zram.service \ ${D}${systemd_system_unitdir}/waydroid-zram.service install -d ${D}${systemd_system_unitdir}/waydroid-container.service.d diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check new file mode 100644 index 00000000..b81e882a --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -0,0 +1,94 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu + +config=${WAYDROID_CONFIG:-/var/lib/waydroid/waydroid.cfg} +nodes=${WAYDROID_LXC_NODES:-/var/lib/waydroid/lxc/waydroid/config_nodes} +sys_drm_dir=${WAYDROID_SYS_DRM_DIR:-/sys/class/drm} +waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} +failed=0 + +pass() { echo "PASS: $*"; } +fail() { echo "FAIL: $*" >&2; failed=1; } + +config_value() { + key=$1 + awk -F= -v wanted="${key}" ' + { + key = $1 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) + if (key == wanted) { + value = substr($0, index($0, "=") + 1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + print value + } + } + ' "${config}" | tail -1 +} + +[ -r "${config}" ] || { echo "FAIL: ${config} is unavailable" >&2; exit 1; } + +render=$(config_value drm_device) +if { [ -c "${render}" ] || [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" = 1 ]; } \ + && grep -Fqx 'DRIVER=etnaviv' "${sys_drm_dir}/${render##*/}/device/uevent"; then + pass "configured render node ${render} is Etnaviv" +else + fail "configured render node is not an Etnaviv character device: ${render:-unset}" +fi + +for expected in \ + 'ro.hardware.egl=mesa' \ + 'ro.hardware.gralloc=minigbm_gbm_mesa' \ + 'ro.hardware.hwcomposer=waydroid' \ + 'ro.opengles.version=196609'; do + key=${expected%%=*} + value=${expected#*=} + [ "$(config_value "${key}")" = "${value}" ] && pass "${expected}" || fail "missing ${expected}" +done + +if [ -n "$(config_value ro.hardware.vulkan)" ]; then + fail 'Vulkan is configured without an i.MX8MM hardware driver' +else + pass 'Vulkan override is absent' +fi + +if [ -r "${nodes}" ]; then + render_mounts=$(grep -c '/dev/dri/renderD' "${nodes}" || true) + [ "${render_mounts}" -eq 1 ] && grep -Fq "${render}" "${nodes}" \ + && pass 'only the selected DRM render node is mounted' \ + || fail 'DRM render-node mount is not exact' + if grep '/dev/dma_heap/' "${nodes}" | grep -Ev '/dev/dma_heap/(system|system-uncached|linux,cma)([[:space:]]|$)' >/dev/null; then + fail 'an unapproved DMA heap is mounted' + else + pass 'DMA heap mounts match the product allowlist' + fi + video_mounts=$(grep -c '/dev/video' "${nodes}" || true) + [ "${video_mounts}" -ge 1 ] && pass 'allowlisted V4L2 decoder node is mounted' \ + || fail 'V4L2 decoder node is not mounted' +else + fail "${nodes} is unavailable; initialize Waydroid first" +fi + +if ! "${waydroid_bin}" status 2>/dev/null | grep -Fq 'RUNNING'; then + fail 'Waydroid container is not running' +else + pass 'Waydroid container is running' + [ "$("${waydroid_bin}" shell getprop ro.hardware.egl 2>/dev/null)" = mesa ] \ + && pass 'Android selected Mesa EGL' || fail 'Android did not select Mesa EGL' + [ "$("${waydroid_bin}" shell getprop ro.hardware.gralloc 2>/dev/null)" = minigbm_gbm_mesa ] \ + && pass 'Android selected minigbm GBM Mesa' || fail 'Android did not select minigbm GBM Mesa' + [ -z "$("${waydroid_bin}" shell getprop ro.hardware.vulkan 2>/dev/null)" ] \ + && pass 'Android does not expose a Vulkan HAL' || fail 'Android exposes a Vulkan HAL' + if "${waydroid_bin}" shell pm list features 2>/dev/null | grep -Fq 'android.hardware.vulkan'; then + fail 'Android advertises a Vulkan feature' + else + pass 'Android does not advertise Vulkan features' + fi + "${waydroid_bin}" shell dumpsys SurfaceFlinger 2>/dev/null | grep -Eiq 'etnaviv|GC7000|Vivante' \ + && pass 'SurfaceFlinger reports the i.MX8MM GPU' || fail 'SurfaceFlinger lacks Etnaviv/GC7000 evidence' + "${waydroid_bin}" shell dumpsys media.codec 2>/dev/null | grep -Fq 'c2.v4l2.avc.decoder' \ + && pass 'V4L2 H.264 Codec2 component is registered' || fail 'V4L2 H.264 Codec2 component is absent' +fi + +exit "${failed}" diff --git a/recipes-support/waydroid/waydroid/waydroid-memory-headroom b/recipes-support/waydroid/waydroid/waydroid-memory-headroom index 942c5a4f..431252a9 100644 --- a/recipes-support/waydroid/waydroid/waydroid-memory-headroom +++ b/recipes-support/waydroid/waydroid/waydroid-memory-headroom @@ -3,9 +3,11 @@ set -eu cgroup=/sys/fs/cgroup/system.slice/waydroid-container.service -awk '/^(MemTotal|MemAvailable|SwapTotal|SwapFree):/ { print }' /proc/meminfo +echo '[memory]' +awk '/^(MemTotal|MemAvailable|SwapTotal|SwapFree|CmaTotal|CmaFree):/ { print }' /proc/meminfo -for item in memory.current memory.peak memory.high memory.max memory.swap.current memory.swap.max memory.events; do +echo '[cgroup]' +for item in memory.current memory.peak memory.high memory.max memory.swap.current memory.swap.max memory.events cpu.stat pids.current pids.peak; do if [ -r "${cgroup}/${item}" ]; then printf '%s=' "${item}" cat "${cgroup}/${item}" @@ -14,6 +16,43 @@ done if grep -q '^/dev/zram0[[:space:]]' /proc/swaps 2>/dev/null; then echo "zram=active" + for item in comp_algorithm disksize mm_stat; do + if [ -r "/sys/block/zram0/${item}" ]; then + printf 'zram.%s=' "${item}" + cat "/sys/block/zram0/${item}" + fi + done else echo "zram=inactive" fi + +echo '[pressure]' +for resource in cpu memory io; do + if [ -r "/proc/pressure/${resource}" ]; then + sed "s/^/${resource}.pressure=/" "/proc/pressure/${resource}" + fi +done + +echo '[gpu-devfreq]' +for devfreq in /sys/class/devfreq/*; do + [ -d "${devfreq}" ] || continue + name=$(cat "${devfreq}/name" 2>/dev/null || basename "${devfreq}") + case "${name}" in + *gpu*|*GPU*|*etnaviv*|*galcore*) ;; + *) continue ;; + esac + echo "gpu.name=${name}" + for item in cur_freq min_freq max_freq available_frequencies governor; do + if [ -r "${devfreq}/${item}" ]; then + printf 'gpu.%s=' "${item}" + cat "${devfreq}/${item}" + fi + done +done + +echo '[thermal]' +for zone in /sys/class/thermal/thermal_zone*; do + [ -r "${zone}/temp" ] || continue + printf '%s.%s=' "${zone##*/}" "$(cat "${zone}/type" 2>/dev/null || echo unknown)" + cat "${zone}/temp" +done diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 4ef19d36..1a6b53e8 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -47,4 +47,32 @@ if grep -Fq 'ro.hardware.vulkan' "${config}"; then exit 1 fi +printf '%s\n' \ + "lxc.mount.entry = ${test_root}/dev/dri/renderD128 dev/dri/renderD128 none bind,create=file 0 0" \ + 'lxc.mount.entry = /dev/dma_heap/system dev/dma_heap/system none bind,create=file 0 0' \ + 'lxc.mount.entry = /dev/dma_heap/linux,cma dev/dma_heap/linux,cma none bind,create=file 0 0' \ + 'lxc.mount.entry = /dev/video2 dev/video2 none bind,create=file 0 0' \ + > "${test_root}/config_nodes" + +printf '%s\n' \ + '#!/bin/sh' \ + 'case "$*" in' \ + ' status) echo "Container: RUNNING" ;;' \ + ' "shell getprop ro.hardware.egl") echo mesa ;;' \ + ' "shell getprop ro.hardware.gralloc") echo minigbm_gbm_mesa ;;' \ + ' "shell getprop ro.hardware.vulkan") : ;;' \ + ' "shell pm list features") echo feature:android.hardware.opengles.aep ;;' \ + ' "shell dumpsys SurfaceFlinger") echo "GLES: Mesa etnaviv GC7000Lite" ;;' \ + ' "shell dumpsys media.codec") echo c2.v4l2.avc.decoder ;;' \ + ' *) exit 1 ;;' \ + 'esac' > "${test_root}/waydroid" +chmod 0755 "${test_root}/waydroid" + +WAYDROID_CONFIG=${config} \ +WAYDROID_LXC_NODES=${test_root}/config_nodes \ +WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ +WAYDROID_BIN=${test_root}/waydroid \ +WAYDROID_ALLOW_FAKE_DEVICES=1 \ + sh "${repo_root}/recipes-support/waydroid/waydroid/waydroid-acceleration-check" + echo 'Waydroid Etnaviv GPU and V4L2 provisioning: passed' From aad8bf57046c091c1b26a5fcac3ce062c5c1c67c Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 14:52:41 +0100 Subject: [PATCH 53/72] build(screen): pin Waydroid DMA heap kernel support --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index ebd7eb08..b8e1cb23 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit ebd7eb08e809722754111a36feed1d2e7d259c88 +Subproject commit b8e1cb23890d792ff2325d970c17e4863c4d2088 From d679e9948ca98e6575374e34d71afdd8cce904ef Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 15:24:21 +0100 Subject: [PATCH 54/72] feat(media): select NXP decoder by V4L2 capability --- recipes-support/waydroid/waydroid.bb | 2 + .../waydroid/waydroid-image-provision | 31 +++--- .../waydroid/waydroid/waydroid-v4l2-probe | 98 +++++++++++++++++++ 3 files changed, 117 insertions(+), 14 deletions(-) create mode 100755 recipes-support/waydroid/waydroid/waydroid-v4l2-probe diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 5527805b..f8d828b2 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -37,6 +37,7 @@ SRC_URI:append:imx8mm-jaguar-screen = " \ file://waydroid-container-2gb.conf \ file://waydroid-memory-headroom \ file://waydroid-acceleration-check \ + file://waydroid-v4l2-probe \ " S = "${WORKDIR}/git" @@ -135,6 +136,7 @@ do_install:append:imx8mm-jaguar-screen() { install -Dm0755 ${WORKDIR}/waydroid-zram ${D}${libexecdir}/waydroid-zram install -Dm0755 ${WORKDIR}/waydroid-memory-headroom ${D}${libexecdir}/waydroid-memory-headroom install -Dm0755 ${WORKDIR}/waydroid-acceleration-check ${D}${libexecdir}/waydroid-acceleration-check + install -Dm0755 ${WORKDIR}/waydroid-v4l2-probe ${D}${libexecdir}/waydroid-v4l2-probe install -Dm0644 ${WORKDIR}/waydroid-zram.service \ ${D}${systemd_system_unitdir}/waydroid-zram.service install -d ${D}${systemd_system_unitdir}/waydroid-container.service.d diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index 82cb20da..e05edb86 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -10,6 +10,7 @@ dev_dri_dir=${WAYDROID_DEV_DRI_DIR:-/dev/dri} sys_video_dir=${WAYDROID_SYS_VIDEO_DIR:-/sys/class/video4linux} dev_video_dir=${WAYDROID_DEV_VIDEO_DIR:-/dev} waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} +v4l2_probe=${WAYDROID_V4L2_PROBE:-/usr/libexec/waydroid-v4l2-probe} if ! [ -s "${images_dir}/system.img" ] || ! [ -s "${images_dir}/vendor.img" ]; then # An interrupted first initialization can leave configuration claiming a @@ -57,22 +58,24 @@ if [ "${have_system_heap}" != true ]; then fi video_devices= -separator= -for sys_node in "${sys_video_dir}"/video*; do - [ -r "${sys_node}/name" ] || continue - grep -Fqx 'vsi_v4l2dec' "${sys_node}/name" || continue - node_name=${sys_node##*/} - test_candidate=${dev_video_dir}/${node_name} - if [ -c "${test_candidate}" ] || { - [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" = 1 ] && [ -e "${test_candidate}" ] - }; then - video_devices=${video_devices}${separator}/dev/${node_name} - separator=';' - fi -done +if [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" = 1 ]; then + for sys_node in "${sys_video_dir}"/video*; do + [ -r "${sys_node}/name" ] || continue + grep -Fqx 'vsi_v4l2dec' "${sys_node}/name" || continue + node_name=${sys_node##*/} + [ -e "${dev_video_dir}/${node_name}" ] && video_devices=/dev/${node_name} + done +else + # The NXP 6.6 driver leaves /sys/class/video4linux/video*/name empty. Query + # the ABI instead: accept only a vsiv4l2 stateful M2M streaming device + # whose encoded-input queue advertises H.264. This excludes the camera and + # the VSI encoder without relying on unstable /dev/video numbering. + video_devices=$("${v4l2_probe}" --device-dir "${dev_video_dir}" --select-h264-decoder) \ + || video_devices= +fi if [ -z "${video_devices}" ]; then - echo "No vsi_v4l2dec device is available; refusing unrelated V4L2 nodes" >&2 + echo "No H.264-capable NXP vsiv4l2 decoder is available; refusing unrelated V4L2 nodes" >&2 exit 1 fi diff --git a/recipes-support/waydroid/waydroid/waydroid-v4l2-probe b/recipes-support/waydroid/waydroid/waydroid-v4l2-probe new file mode 100755 index 00000000..5ecfe62f --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-v4l2-probe @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Report V4L2 mem2mem devices or select NXP's H.264 decoder.""" + +import argparse +import fcntl +import glob +import json +import os +import struct + +VIDIOC_QUERYCAP = 0x80685600 +VIDIOC_ENUM_FMT = 0xC0405602 +V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 +V4L2_BUF_TYPE_VIDEO_OUTPUT = 2 +V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9 +V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10 +V4L2_CAP_VIDEO_M2M_MPLANE = 0x00004000 +V4L2_CAP_VIDEO_M2M = 0x00008000 +V4L2_CAP_STREAMING = 0x04000000 +V4L2_CAP_DEVICE_CAPS = 0x80000000 +V4L2_PIX_FMT_H264 = struct.unpack(" Date: Mon, 7 Sep 2026 15:25:04 +0100 Subject: [PATCH 55/72] build: pin stateful V4L2 decoder BSP --- meta-dynamicdevices-bsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index b8e1cb23..b7c4300a 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit b8e1cb23890d792ff2325d970c17e4863c4d2088 +Subproject commit b7c4300af2333e5c561d9fead9c62e8207927d11 From 0e37923a52a60f1838aca6d54b5cf9282edd0688 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 15:40:24 +0100 Subject: [PATCH 56/72] waydroid: supervise NXP V4L2 daemon --- recipes-support/waydroid/waydroid.bb | 7 +++++-- .../waydroid/waydroid-acceleration-check | 18 ++++++++++++++++-- .../waydroid/waydroid/waydroid-image-provision | 8 ++++++++ .../waydroid/waydroid-image-provision.service | 3 ++- .../waydroid/waydroid-vsidaemon.service | 14 ++++++++++++++ 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 recipes-support/waydroid/waydroid/waydroid-vsidaemon.service diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index f8d828b2..67dec732 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -38,6 +38,7 @@ SRC_URI:append:imx8mm-jaguar-screen = " \ file://waydroid-memory-headroom \ file://waydroid-acceleration-check \ file://waydroid-v4l2-probe \ + file://waydroid-vsidaemon.service \ " S = "${WORKDIR}/git" @@ -64,10 +65,10 @@ inherit pkgconfig inherit features_check systemd SYSTEMD_SERVICE:${PN}:imx8mm-jaguar-screen = "waydroid-image-provision.service" -SYSTEMD_SERVICE:${PN}:append:imx8mm-jaguar-screen = " waydroid-zram.service" +SYSTEMD_SERVICE:${PN}:append:imx8mm-jaguar-screen = " waydroid-zram.service waydroid-vsidaemon.service" SYSTEMD_AUTO_ENABLE:${PN}:imx8mm-jaguar-screen = "enable" -RDEPENDS:${PN}:append:imx8mm-jaguar-screen = " kmod util-linux-mkswap util-linux-swaponoff" +RDEPENDS:${PN}:append:imx8mm-jaguar-screen = " kmod util-linux-mkswap util-linux-swaponoff imx-vpu-hantro" # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation @@ -139,6 +140,8 @@ do_install:append:imx8mm-jaguar-screen() { install -Dm0755 ${WORKDIR}/waydroid-v4l2-probe ${D}${libexecdir}/waydroid-v4l2-probe install -Dm0644 ${WORKDIR}/waydroid-zram.service \ ${D}${systemd_system_unitdir}/waydroid-zram.service + install -Dm0644 ${WORKDIR}/waydroid-vsidaemon.service \ + ${D}${systemd_system_unitdir}/waydroid-vsidaemon.service install -d ${D}${systemd_system_unitdir}/waydroid-container.service.d install -m 0644 ${WORKDIR}/waydroid-container-2gb.conf \ ${D}${systemd_system_unitdir}/waydroid-container.service.d/20-memory-2gb.conf diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check index b81e882a..015a7714 100644 --- a/recipes-support/waydroid/waydroid/waydroid-acceleration-check +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -30,6 +30,7 @@ config_value() { [ -r "${config}" ] || { echo "FAIL: ${config} is unavailable" >&2; exit 1; } render=$(config_value drm_device) +video=$(config_value video_devices) if { [ -c "${render}" ] || [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" = 1 ]; } \ && grep -Fqx 'DRIVER=etnaviv' "${sys_drm_dir}/${render##*/}/device/uevent"; then pass "configured render node ${render} is Etnaviv" @@ -64,12 +65,25 @@ if [ -r "${nodes}" ]; then pass 'DMA heap mounts match the product allowlist' fi video_mounts=$(grep -c '/dev/video' "${nodes}" || true) - [ "${video_mounts}" -ge 1 ] && pass 'allowlisted V4L2 decoder node is mounted' \ - || fail 'V4L2 decoder node is not mounted' + [ "${video_mounts}" -eq 1 ] && grep -Fq "${video}" "${nodes}" \ + && pass 'only the capability-selected V4L2 decoder node is mounted' \ + || fail 'V4L2 decoder-node mount is not exact' else fail "${nodes} is unavailable; initialize Waydroid first" fi +if [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" = 1 ]; then + pass 'V4L2 daemon and DAC checks skipped for fixture devices' +else + systemctl is-active --quiet waydroid-vsidaemon.service \ + && pass 'NXP Hantro userspace daemon is running' \ + || fail 'NXP Hantro userspace daemon is not running' + [ -c "${video}" ] && [ "$(stat -c %g "${video}")" = 1013 ] \ + && [ "$(stat -c %a "${video}")" = 660 ] \ + && pass 'decoder DAC is restricted to root and Android AID_MEDIA' \ + || fail 'decoder DAC is not root:1013 mode 0660' +fi + if ! "${waydroid_bin}" status 2>/dev/null | grep -Fq 'RUNNING'; then fail 'Waydroid container is not running' else diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index e05edb86..2d46620e 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -79,6 +79,14 @@ if [ -z "${video_devices}" ]; then exit 1 fi +if [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" != 1 ]; then + # The Android V4L2 Codec2 service runs as AID_MEDIA (stable numeric GID + # 1013). Grant that service access to only the capability-selected decoder; + # the camera and encoder are neither changed nor mounted into the container. + chown 0:1013 "${video_devices}" + chmod 0660 "${video_devices}" +fi + # Waydroid otherwise selects the first DRM node and may advertise a software # or unrelated GPU. Persist an exact, reviewable Android graphics contract. python3 - "${config}" "${render_node}" "${dma_heap_devices}" "${video_devices}" <<'PY' diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision.service b/recipes-support/waydroid/waydroid/waydroid-image-provision.service index a3e69843..92534cb4 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision.service +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision.service @@ -1,7 +1,8 @@ [Unit] Description=Provision Waydroid Android images into persistent storage Wants=network-online.target -After=network-online.target dbus.service +Requires=waydroid-vsidaemon.service +After=network-online.target dbus.service waydroid-vsidaemon.service Before=waydroid-container.service StartLimitIntervalSec=0 diff --git a/recipes-support/waydroid/waydroid/waydroid-vsidaemon.service b/recipes-support/waydroid/waydroid/waydroid-vsidaemon.service new file mode 100644 index 00000000..50c79c15 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-vsidaemon.service @@ -0,0 +1,14 @@ +[Unit] +Description=NXP Hantro V4L2 userspace daemon for Waydroid +After=systemd-modules-load.service +ConditionPathExists=/dev/vsi_daemon_ctrl + +[Service] +Type=simple +ExecStart=/usr/bin/vsidaemon +Restart=on-failure +RestartSec=2 +TimeoutStopSec=10 + +[Install] +WantedBy=multi-user.target From 24a30e56ba8d07d716886e2861e7eb0a91c5bc5e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 15:57:07 +0100 Subject: [PATCH 57/72] waydroid: preserve configured preinstalled images --- .../waydroid/waydroid-image-provision | 20 ++++++++++++++++++- scripts/validation/waydroid-gpu-provision.sh | 5 ++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index 2d46620e..132a6d55 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -3,7 +3,6 @@ set -eu -images_dir=${WAYDROID_IMAGES_DIR:-/var/lib/waydroid/images} config=${WAYDROID_CONFIG:-/var/lib/waydroid/waydroid.cfg} sys_drm_dir=${WAYDROID_SYS_DRM_DIR:-/sys/class/drm} dev_dri_dir=${WAYDROID_DEV_DRI_DIR:-/dev/dri} @@ -12,12 +11,31 @@ dev_video_dir=${WAYDROID_DEV_VIDEO_DIR:-/dev} waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} v4l2_probe=${WAYDROID_V4L2_PROBE:-/usr/libexec/waydroid-v4l2-probe} +configured_images_dir() { + python3 - "${config}" <<'PY' +import configparser +import sys + +cfg = configparser.ConfigParser(interpolation=None) +cfg.read(sys.argv[1]) +print(cfg.get("waydroid", "images_path", fallback="/var/lib/waydroid/images")) +PY +} + +images_dir=${WAYDROID_IMAGES_DIR:-$(configured_images_dir)} if ! [ -s "${images_dir}/system.img" ] || ! [ -s "${images_dir}/vendor.img" ]; then # An interrupted first initialization can leave configuration claiming a # channel revision whose image was never fully installed. Let Waydroid # build that configuration again before its checksum-verified download. rm -f "${config}" "${waydroid_bin}" init + if [ -z "${WAYDROID_IMAGES_DIR:-}" ]; then + images_dir=$(configured_images_dir) + fi + if ! [ -s "${images_dir}/system.img" ] || ! [ -s "${images_dir}/vendor.img" ]; then + echo "Waydroid initialization did not provide complete images in ${images_dir}" >&2 + exit 1 + fi fi render_node= diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 1a6b53e8..95080cfe 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -20,10 +20,9 @@ printf 'heap\n' > "${test_root}/dev/dma_heap/system" printf 'heap\n' > "${test_root}/dev/dma_heap/linux,cma" printf 'vsi_v4l2dec\n' > "${test_root}/sys/class/video4linux/video2/name" printf 'video\n' > "${test_root}/dev/video2" -printf '[waydroid]\narch = arm64\n\n[properties]\nro.hardware.vulkan = lvp\n' \ - > "${test_root}/waydroid.cfg" +printf '[waydroid]\narch = arm64\nimages_path = %s/images\n\n[properties]\nro.hardware.vulkan = lvp\n' \ + "${test_root}" > "${test_root}/waydroid.cfg" -WAYDROID_IMAGES_DIR=${test_root}/images \ WAYDROID_CONFIG=${test_root}/waydroid.cfg \ WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ WAYDROID_DEV_DRI_DIR=${test_root}/dev/dri \ From 95e4488de2fba3a20b3d89fbed2700c2cc5e4adc Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 16:07:05 +0100 Subject: [PATCH 58/72] waydroid: require pinned Android 16 image evidence --- .../workflows/build-waydroid-imx8mm-aesl.yml | 71 +++++++++++++++++ docs/waydroid-android16-build.md | 27 +++++++ kas/waydroid-imx8mm-aesl.yml | 34 +++++++++ recipes-support/waydroid/waydroid-data.bb | 76 +++++++++++++++++-- 4 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/build-waydroid-imx8mm-aesl.yml create mode 100644 docs/waydroid-android16-build.md create mode 100644 kas/waydroid-imx8mm-aesl.yml diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml new file mode 100644 index 00000000..83634beb --- /dev/null +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -0,0 +1,71 @@ +name: Build AESL Android 16 Waydroid host image + +on: + workflow_dispatch: + inputs: + android_artifact_dir: + description: Reviewed Android artifact root under /yocto/android-16-artifacts + required: true + type: string + +permissions: + contents: read + +concurrency: + group: build-waydroid-imx8mm-aesl + cancel-in-progress: false + +jobs: + build: + runs-on: [self-hosted, esl-proxmox] + timeout-minutes: 720 + container: + image: dynamicdevices/yocto-ci-build@sha256:be170373625e77a6235a0bb3efb84d00be736221782d5d716e88aafd1b965794 + options: --privileged --volume /yocto:/yocto + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Validate Android evidence and CI storage + env: + AESL_ANDROID_ARTIFACT_DIR: ${{ inputs.android_artifact_dir }} + run: | + case "${AESL_ANDROID_ARTIFACT_DIR}" in + /yocto/android-16-artifacts/*) ;; + *) echo "Android artifact must be under /yocto/android-16-artifacts" >&2; exit 1 ;; + esac + test -s "${AESL_ANDROID_ARTIFACT_DIR}/waydroid-images.inc" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/source-manifest.xml" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/build-info.json" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/system.img" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/vendor.img" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/sbom.spdx.json" + (cd "${AESL_ANDROID_ARTIFACT_DIR}" && sha256sum --check --strict SHA256SUMS) + install -d /yocto/waydroid-host/kas-work /yocto/waydroid-host/kas-build + available_kib=$(df --output=avail /yocto | tail -1 | tr -d ' ') + minimum_kib=$((300 * 1024 * 1024)) + test "${available_kib}" -ge "${minimum_kib}" + df -h / /yocto + + - name: Build pinned i.MX8MM host image + env: + AESL_ANDROID_ARTIFACT_DIR: ${{ inputs.android_artifact_dir }} + AESL_YOCTO_WORK_ROOT: /yocto/waydroid-host + KAS_WORK_DIR: /yocto/waydroid-host/kas-work + KAS_BUILD_DIR: /yocto/waydroid-host/kas-build + run: kas build kas/waydroid-imx8mm-aesl.yml + + - name: Verify deploy evidence + run: | + deploy=/yocto/waydroid-host/tmp/deploy/images/imx8mm-jaguar-screen + test -d "${deploy}" + find "${deploy}" -maxdepth 1 -type f -printf '%f\n' | sort + find "${deploy}" -maxdepth 1 -type f -name '*.manifest' -print -quit | grep -q . + + - uses: actions/upload-artifact@v4 + with: + name: aesl-waydroid-imx8mm-host-${{ github.run_id }}-${{ github.run_attempt }} + path: /yocto/waydroid-host/tmp/deploy/images/imx8mm-jaguar-screen + if-no-files-found: error + retention-days: 30 diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md new file mode 100644 index 00000000..4ec4831e --- /dev/null +++ b/docs/waydroid-android16-build.md @@ -0,0 +1,27 @@ +# AESL Android 16 Waydroid host build + +The i.MX8MM product deliberately refuses to reuse the recipe's legacy +LineageOS 18.1 payload. Build the reviewed LineageOS 23.2 manifest first; its +artifact root contains raw `imx8mm/system.img`, `imx8mm/vendor.img`, the SPDX +JSON SBOM, immutable source manifest, build metadata, and a generated +`waydroid-images.inc` containing their exact hashes. + +All persistent source trees, downloads, caches, build outputs, and large CI +artifacts must remain on the dedicated `/yocto` volume. On the self-hosted +runner, build the host image with: + +```sh +AESL_ANDROID_ARTIFACT_DIR=/yocto/android-16-artifacts/RUN-ATTEMPT \ +AESL_YOCTO_WORK_ROOT=/yocto/waydroid-host \ +KAS_WORK_DIR=/yocto/waydroid-host/kas-work \ +KAS_BUILD_DIR=/yocto/waydroid-host/kas-build \ + kas build kas/waydroid-imx8mm-aesl.yml +``` + +The KAS profile rejects artifact and work paths outside `/yocto`; `waydroid-data` +rejects absent or malformed SHA-256 pins and installs the SBOM, source lock, +and build metadata as release evidence alongside the chunked images. + +CI runs this inside the digest-pinned Yocto build container with `/yocto` +mounted at the same absolute path. Do not substitute `kas-container` without +also arranging that mount and explicitly forwarding the AESL variables. diff --git a/kas/waydroid-imx8mm-aesl.yml b/kas/waydroid-imx8mm-aesl.yml new file mode 100644 index 00000000..9225587b --- /dev/null +++ b/kas/waydroid-imx8mm-aesl.yml @@ -0,0 +1,34 @@ +# AESL Android 16 / Waydroid proof image for the 2 GB Jaguar Screen. +# +# AESL_ANDROID_ARTIFACT_DIR must name the reviewed Android build artifact root +# under /yocto. It contains waydroid-images.inc, source-manifest.xml, +# build-info.json, and the imx8mm image/SBOM directory. + +header: + version: 14 + includes: + - lmp-dynamicdevices-dev.yml + +machine: imx8mm-jaguar-screen +distro: lmp-dynamicdevices +target: lmp-factory-image + +local_conf_header: + aesl-waydroid-android16: | + DD_PRODUCT_FEATURES = "android-container" + AESL_ANDROID_ARTIFACT_DIR = "${@os.getenv('AESL_ANDROID_ARTIFACT_DIR', '')}" + AESL_YOCTO_WORK_ROOT = "${@os.getenv('AESL_YOCTO_WORK_ROOT', '/yocto/waydroid-host')}" + DL_DIR = "${AESL_YOCTO_WORK_ROOT}/downloads" + SSTATE_DIR = "${AESL_YOCTO_WORK_ROOT}/sstate-cache" + TMPDIR = "${AESL_YOCTO_WORK_ROOT}/tmp" + FILESEXTRAPATHS:prepend:pn-waydroid-data := "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm:${AESL_ANDROID_ARTIFACT_DIR}:" + require ${AESL_ANDROID_ARTIFACT_DIR}/waydroid-images.inc + + python () { + artifact_dir = d.getVar("AESL_ANDROID_ARTIFACT_DIR") or "" + work_root = d.getVar("AESL_YOCTO_WORK_ROOT") or "" + if not artifact_dir.startswith("/yocto/"): + bb.fatal("AESL_ANDROID_ARTIFACT_DIR must be an absolute path under /yocto") + if not work_root.startswith("/yocto/"): + bb.fatal("AESL_YOCTO_WORK_ROOT must be an absolute path under /yocto") + } diff --git a/recipes-support/waydroid/waydroid-data.bb b/recipes-support/waydroid/waydroid-data.bb index 6f60249f..368b6ab5 100644 --- a/recipes-support/waydroid/waydroid-data.bb +++ b/recipes-support/waydroid/waydroid-data.bb @@ -1,10 +1,13 @@ SUMMARY = "Waydroid uses a container-based approach to boot a full Android system" DESCRIPTION = "Android image file for Waydroid" -# this isn't very clear, there is no information in build.anbox.io and it surely doesn't -# cover all components included in this built image, e.g. -# https://aur.archlinux.org/packages/waydroid-image says Apache license +# This is the packaging recipe's licence, not a claim that every component in +# a prebuilt Android filesystem has one licence. The AESL Android 16 payload +# carries its SPDX SBOM and immutable source manifest as installed evidence. LICENSE = "BSD-3-Clause" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/BSD-3-Clause;md5=550794465ba0ec5312d6919e203a55f9" +LICENSE:imx8mm-jaguar-screen = "CLOSED" +LIC_FILES_CHKSUM:imx8mm-jaguar-screen = "" +PV:imx8mm-jaguar-screen = "16.0+lineage23.2" # works only for following 4 archs COMPATIBLE_MACHINE ?= "(^$)" @@ -27,12 +30,52 @@ SHA256SUM_VENDOR:aarch64 = "e67f0d92907bd74083f1f83da701609c94c4cdbd8ba7c662c27d SHA256SUM_VENDOR:halium = "cd5b1394f35c97c0284f365e52588eecd7b89b6aa28624aefca55aff509143e5" +# The Android image workflow emits waydroid-images.inc with these exact +# values. The consuming KAS configuration must also prepend the artifact's +# imx8mm and root directories to FILESEXTRAPATHS for this recipe. +AESL_WAYDROID_SYSTEM_SHA256 ?= "" +AESL_WAYDROID_VENDOR_SHA256 ?= "" +AESL_WAYDROID_SBOM_SHA256 ?= "" +AESL_WAYDROID_SOURCE_MANIFEST_SHA256 ?= "" +AESL_WAYDROID_BUILD_INFO_SHA256 ?= "" + +SHA256SUM_SYSTEM:imx8mm-jaguar-screen = "${AESL_WAYDROID_SYSTEM_SHA256}" +SHA256SUM_VENDOR:imx8mm-jaguar-screen = "${AESL_WAYDROID_VENDOR_SHA256}" + SRC_URI = "https://sourceforge.net/projects/waydroid/files/images/system/lineage/${WAYDROID_ARCH}/${WAYDROID_SYSTEM_IMAGE};name=system \ https://sourceforge.net/projects/waydroid/files/images/vendor/${WAYDROID_ARCH}/${WAYDROID_VENDOR_IMAGE};name=vendor \ " +SRC_URI:imx8mm-jaguar-screen = " \ + file://system.img;name=system \ + file://vendor.img;name=vendor \ + file://sbom.spdx.json;name=sbom \ + file://source-manifest.xml;name=source-manifest \ + file://build-info.json;name=build-info \ +" SRC_URI[system.sha256sum] = "${SHA256SUM_SYSTEM}" SRC_URI[vendor.sha256sum] = "${SHA256SUM_VENDOR}" +SRC_URI[sbom.sha256sum] = "${AESL_WAYDROID_SBOM_SHA256}" +SRC_URI[source-manifest.sha256sum] = "${AESL_WAYDROID_SOURCE_MANIFEST_SHA256}" +SRC_URI[build-info.sha256sum] = "${AESL_WAYDROID_BUILD_INFO_SHA256}" + +python __anonymous() { + if d.getVar("MACHINE") != "imx8mm-jaguar-screen": + return + if "waydroid" not in (d.getVar("DISTRO_FEATURES") or "").split(): + return + required = ( + "AESL_WAYDROID_SYSTEM_SHA256", + "AESL_WAYDROID_VENDOR_SHA256", + "AESL_WAYDROID_SBOM_SHA256", + "AESL_WAYDROID_SOURCE_MANIFEST_SHA256", + "AESL_WAYDROID_BUILD_INFO_SHA256", + ) + for name in required: + value = d.getVar(name) or "" + if len(value) != 64 or any(c not in "0123456789abcdef" for c in value): + bb.fatal("%s must be supplied by the reviewed Android 16 artifact include" % name) +} do_install() { install -dm755 "${D}/usr/share/waydroid-extra/images" @@ -41,16 +84,31 @@ do_install() { split -b100M -d ${WORKDIR}/system.img ${WORKDIR}/system.img. split -b100M -d ${WORKDIR}/vendor.img ${WORKDIR}/vendor.img. - # makepkg have extracted the zips + # The upstream archives unpack to these names; the AESL Android 16 inputs + # are already raw images. Both paths are chunked to keep OSTree objects + # manageable on constrained devices. for f in ${WORKDIR}/system.img.*; do \ - install -m 0644 $f "${D}/usr/share/waydroid-extra/images"; \ + install -m 0644 "$f" "${D}/usr/share/waydroid-extra/images"; \ done for f in ${WORKDIR}/vendor.img.*; do \ - install -m 0644 $f "${D}/usr/share/waydroid-extra/images"; \ + install -m 0644 "$f" "${D}/usr/share/waydroid-extra/images"; \ done } -FILES:${PN} += "/usr/share/waydroid-extra/images" +do_install:append:imx8mm-jaguar-screen() { + install -dm0755 "${D}/usr/share/waydroid-extra/evidence" + install -m 0644 "${WORKDIR}/sbom.spdx.json" \ + "${D}/usr/share/waydroid-extra/evidence/sbom.spdx.json" + install -m 0644 "${WORKDIR}/source-manifest.xml" \ + "${D}/usr/share/waydroid-extra/evidence/source-manifest.xml" + install -m 0644 "${WORKDIR}/build-info.json" \ + "${D}/usr/share/waydroid-extra/evidence/build-info.json" + printf '%s system.img\n%s vendor.img\n' \ + "${AESL_WAYDROID_SYSTEM_SHA256}" "${AESL_WAYDROID_VENDOR_SHA256}" \ + > "${D}/usr/share/waydroid-extra/evidence/IMAGE_SHA256SUMS" +} + +FILES:${PN} += "/usr/share/waydroid-extra/images /usr/share/waydroid-extra/evidence" pkg_postinst_ontarget:${PN} () { #!/bin/sh @@ -60,6 +118,10 @@ pkg_postinst_ontarget:${PN} () { # rm /usr/share/waydroid-extra/images/system.img.* cat /usr/share/waydroid-extra/images/vendor.img.* > /etc/waydroid-extra/images/vendor.img # rm /usr/share/waydroid-extra/images/vendor.img.* + if [ -s /usr/share/waydroid-extra/evidence/IMAGE_SHA256SUMS ]; then + (cd /etc/waydroid-extra/images && \ + sha256sum -c /usr/share/waydroid-extra/evidence/IMAGE_SHA256SUMS) + fi } # QA Skip Justification: Waydroid packages pre-built Android system images From 04c09f246f4c5feab82af24b0ff740cf09154d9b Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 16:38:58 +0100 Subject: [PATCH 59/72] security: enforce Waydroid host device isolation --- docs/waydroid-android16-build.md | 27 ++++ meta-dynamicdevices-bsp | 2 +- meta-dynamicdevices-distro | 2 +- recipes-support/waydroid/waydroid.bb | 6 +- ...xc-limit-graphics-device-permissions.patch | 125 ++++++++++-------- .../waydroid/waydroid-acceleration-check | 71 +++++++++- .../waydroid/waydroid-image-provision | 13 ++ .../waydroid/waydroid-image-provision.service | 4 +- scripts/validation/waydroid-gpu-provision.sh | 1 + 9 files changed, 185 insertions(+), 66 deletions(-) diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index 4ec4831e..05c414de 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -25,3 +25,30 @@ and build metadata as release evidence alongside the chunked images. CI runs this inside the digest-pinned Yocto build container with `/yocto` mounted at the same absolute path. Do not substitute `kas-container` without also arranging that mount and explicitly forwarding the AESL variables. + +## Host container security gate + +The `android-container` product feature also enables the AppArmor distro +feature. The Jaguar kernel builds AppArmor into its ordered LSM list, and the +Waydroid recipe installs all three upstream profiles in enforce mode. Image +provisioning waits for `apparmor.service`; if the `lxc-waydroid` profile is not +loaded, the generated LXC configuration remains unconfined and the runtime +verification must fail. + +The patched, pinned Waydroid 1.6.3 runtime writes a deny-by-default LXC device +policy. It then grants `rwm` only to fixed pseudo devices and the exact major +and minor numbers of bind-mounted character devices. The product provisioner +separately limits mounts to the chosen Etnaviv render node, approved DMA heaps, +and the capability-selected NXP decoder. The GPU and heaps are `root:1003` +(`AID_GRAPHICS`) mode `0660`; the decoder is `root:1013` (`AID_MEDIA`) mode +`0660`. No block-device or wildcard device grant is generated. + +On the target, run: + +```sh +sudo /usr/libexec/waydroid-acceleration-check +``` + +This is a release gate: it checks AppArmor is enabled and enforcing, the LXC +profile is selected, the device policy is deny-by-default with exact rules, +and the Android GPU/Vulkan/Codec2 runtime state matches the product contract. diff --git a/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index b7c4300a..0cac9583 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit b7c4300af2333e5c561d9fead9c62e8207927d11 +Subproject commit 0cac9583d3b3f6d195ca614d96d1e7a63d01e183 diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 5c84b589..3e430ec1 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 5c84b589fc1c306120efb7da99df1e2aef781202 +Subproject commit 3e430ec1f36108d2b17505ae284fca3b050a7f80 diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 67dec732..a728423f 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -68,13 +68,13 @@ SYSTEMD_SERVICE:${PN}:imx8mm-jaguar-screen = "waydroid-image-provision.service" SYSTEMD_SERVICE:${PN}:append:imx8mm-jaguar-screen = " waydroid-zram.service waydroid-vsidaemon.service" SYSTEMD_AUTO_ENABLE:${PN}:imx8mm-jaguar-screen = "enable" -RDEPENDS:${PN}:append:imx8mm-jaguar-screen = " kmod util-linux-mkswap util-linux-swaponoff imx-vpu-hantro" +RDEPENDS:${PN}:append:imx8mm-jaguar-screen = " apparmor kmod util-linux-mkswap util-linux-swaponoff imx-vpu-hantro" # Product configuration selects the provider-neutral `android-container` # bundle. The distro layer expands that bundle to these implementation # prerequisites; fail early if Waydroid is pulled into an incomplete image. REQUIRED_DISTRO_FEATURES = "waydroid wayland opengl" -REQUIRED_DISTRO_FEATURES:append:imx8mm-jaguar-screen = " etnaviv" +REQUIRED_DISTRO_FEATURES:append:imx8mm-jaguar-screen = " apparmor etnaviv" WEBOS_SYSTEMD_SERVICE = "waydroid-init.service waydroid-container.service" @@ -83,7 +83,7 @@ CLEANBROKEN = "1" EXTRA_OEMAKE = "PREFIX=${prefix} SYSCONFDIR=${sysconfdir} SYSD_DIR=${systemd_system_unitdir} USE_NFTABLES=1" do_install() { - oe_runmake install DESTDIR=${D} + oe_runmake install install_apparmor DESTDIR=${D} # Keep the small webOS/LuneOS launcher integration out of the upstream # source tree so that the maintained Waydroid release can remain pinned. diff --git a/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch index 62fe36d5..09c8a2b6 100644 --- a/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch +++ b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch @@ -1,76 +1,83 @@ -From 4e294bc169a3455c8d50bf1ff5c1636ae8d3825f Mon Sep 17 00:00:00 2001 +From b800aa3663bb259d0f8445155d8cd01996e5eef9 Mon Sep 17 00:00:00 2001 From: Active ESL -Date: Mon, 7 Sep 2026 15:00:00 +0100 -Subject: [PATCH] lxc: limit graphics and video device permissions +Date: Mon, 7 Sep 2026 16:26:44 +0100 +Subject: [PATCH] lxc: enforce exact graphics and video device policy -Do not chmod every DRM render node and DMA heap on the host to 0777. The -configured DRM node is the only render node exposed by lxc.py, so apply -permissions to that node alone. Permit products to provide a semicolon-separated -DMA heap allowlist and use 0666, without meaningless execute bits, for the -selected character devices. - -Apply the same allowlist model to V4L2 nodes so a hardware decoder does not -grant the Android container access to unrelated cameras or capture devices. - -Empty dma_heap_devices and video_devices settings retain upstream discovery -for products which have not opted into an allowlist. - -Signed-off-by: Active ESL --- - tools/actions/container_manager.py | 19 ++++++++++++------- - tools/helpers/lxc.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- - 2 files changed, 56 insertions(+), 9 deletions(-) + data/configs/apparmor_profiles/adbd | 3 +- + data/configs/apparmor_profiles/android_app | 2 +- + data/configs/apparmor_profiles/lxc-waydroid | 3 +- + tools/actions/container_manager.py | 19 +++--- + tools/helpers/lxc.py | 65 ++++++++++++++++++++- + 5 files changed, 77 insertions(+), 15 deletions(-) +diff --git a/data/configs/apparmor_profiles/adbd b/data/configs/apparmor_profiles/adbd +index 2ce14e6..5e969e5 100644 +--- a/data/configs/apparmor_profiles/adbd ++++ b/data/configs/apparmor_profiles/adbd +@@ -1 +1 @@ +-profile adbd flags=(attach_disconnected,mediate_deleted,complain) { ++profile adbd flags=(attach_disconnected,mediate_deleted) { +@@ -56 +55,0 @@ profile adbd flags=(attach_disconnected,mediate_deleted,complain) { +- +diff --git a/data/configs/apparmor_profiles/android_app b/data/configs/apparmor_profiles/android_app +index 2f4e35e..5d567fd 100644 +--- a/data/configs/apparmor_profiles/android_app ++++ b/data/configs/apparmor_profiles/android_app +@@ -1 +1 @@ +-profile android_app flags=(attach_disconnected, complain, mediate_deleted) { ++profile android_app flags=(attach_disconnected, mediate_deleted) { +diff --git a/data/configs/apparmor_profiles/lxc-waydroid b/data/configs/apparmor_profiles/lxc-waydroid +index e17d709..3f9e52f 100644 +--- a/data/configs/apparmor_profiles/lxc-waydroid ++++ b/data/configs/apparmor_profiles/lxc-waydroid +@@ -1 +1 @@ +-profile lxc-waydroid flags=(attach_disconnected, complain, mediate_deleted) { ++profile lxc-waydroid flags=(attach_disconnected, mediate_deleted) { +@@ -68 +67,0 @@ profile lxc-waydroid flags=(attach_disconnected, complain, mediate_deleted) { +- diff --git a/tools/actions/container_manager.py b/tools/actions/container_manager.py -index 6e088bb..ad71ad8 100644 +index 6e088bb..7c781c3 100644 --- a/tools/actions/container_manager.py +++ b/tools/actions/container_manager.py -@@ -66,6 +66,7 @@ class DbusContainerManager(dbus.service.Object): - return {} - - def set_permissions(args, perm_list=None, mode="777"): +@@ -68,0 +69 @@ def set_permissions(args, perm_list=None, mode="777"): + use_default_list = perm_list is None - def chmod(path, mode): - if os.path.exists(path): - command = ["chmod", mode, "-R", path] -@@ -92,18 +93,22 @@ def set_permissions(args, perm_list=None, mode="777"): - "/dev/ion", - ] - +@@ -95,2 +95,0 @@ def set_permissions(args, perm_list=None, mode="777"): - # DRM render nodes - perm_list.extend(glob.glob("/dev/dri/renderD*")) - # Framebuffers - perm_list.extend(glob.glob("/dev/fb*")) +@@ -99,5 +97,0 @@ def set_permissions(args, perm_list=None, mode="777"): - # Videos - perm_list.extend(glob.glob("/dev/video*")) - # DMA-BUF Heaps - perm_list.extend(glob.glob("/dev/dma_heap/*")) - - for path in perm_list: - chmod(path, mode) - +@@ -106,0 +101,11 @@ def set_permissions(args, perm_list=None, mode="777"): + if use_default_list: + # The container receives only the selected render node. Do not relax + # permissions on unrelated host GPUs or add execute bits to devices. + render, _ = helpers.gpu.getDriNode(args) + if render: -+ chmod(render, "666") ++ chmod(render, "660") + for path in helpers.lxc.get_dma_heap_devices(args): -+ chmod(path, "666") ++ chmod(path, "660") + for path in helpers.lxc.get_video_devices(args): -+ chmod(path, "666") ++ chmod(path, "660") + - def start(args): - mainloop = GLib.MainLoop() - diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py -index 1b6ff49..937cd52 100644 +index 1b6ff49..3dcc5cd 100644 --- a/tools/helpers/lxc.py +++ b/tools/helpers/lxc.py -@@ -37,6 +37,48 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): - nodes.append(entry) - return True - +@@ -37,0 +38,9 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): ++ try: ++ device = os.stat(src) ++ except (FileNotFoundError, TypeError): ++ return True ++ if stat.S_ISCHR(device.st_mode): ++ controller = ("lxc.cgroup2" if os.path.exists("/sys/fs/cgroup/cgroup.controllers") ++ else "lxc.cgroup") ++ nodes.append("{}.devices.allow = c {}:{} rwm".format( ++ controller, os.major(device.st_rdev), os.minor(device.st_rdev))) +@@ -39,0 +49,42 @@ def add_node_entry(nodes, src, dist, mnt_type, options, check): +def get_dma_heap_devices(args): + """Return configured DMA heaps, rejecting paths outside /dev/dma_heap.""" + cfg = tools.config.load(args) @@ -113,20 +120,22 @@ index 1b6ff49..937cd52 100644 + logging.warning("Configured V4L2 device does not exist: %s", path) + return devices + - def generate_nodes_lxc_config(args): - nodes = [] - def make_entry(src, dist=None, mnt_type="none", options="bind,create=file,optional 0 0", check=True): -@@ -66,9 +108,9 @@ def generate_nodes_lxc_config(args): - for n in glob.glob("/dev/graphics/fb*"): - make_entry(n) +@@ -41 +92,9 @@ def generate_nodes_lxc_config(args): +- nodes = [] ++ controller = ("lxc.cgroup2" if os.path.exists("/sys/fs/cgroup/cgroup.controllers") ++ else "lxc.cgroup") ++ nodes = ["{}.devices.deny = a".format(controller)] ++ # Android creates these standard pseudo devices after LXC mounts its /dev ++ # tmpfs. Permit only their fixed majors/minors plus the devpts range. ++ for device in ("c 1:3 rwm", "c 1:5 rwm", "c 1:7 rwm", "c 1:8 rwm", ++ "c 1:9 rwm", "c 5:0 rwm", "c 5:1 rwm", "c 5:2 rwm", ++ "c 136:* rwm"): ++ nodes.append("{}.devices.allow = {}".format(controller, device)) +@@ -69 +128 @@ def generate_nodes_lxc_config(args): - for n in glob.glob("/dev/video*"): + for n in get_video_devices(args): - make_entry(n) +@@ -71 +130 @@ def generate_nodes_lxc_config(args): - for n in glob.glob("/dev/dma_heap/*"): + for n in get_dma_heap_devices(args): - make_entry(n) - - # Binder dev nodes - make_entry("/dev/" + args.BINDER_DRIVER, "dev/binder", check=False) -- 2.43.0 diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check index 015a7714..f45721a7 100644 --- a/recipes-support/waydroid/waydroid/waydroid-acceleration-check +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -5,6 +5,8 @@ set -eu config=${WAYDROID_CONFIG:-/var/lib/waydroid/waydroid.cfg} nodes=${WAYDROID_LXC_NODES:-/var/lib/waydroid/lxc/waydroid/config_nodes} +lxc_config=${WAYDROID_LXC_CONFIG:-/var/lib/waydroid/lxc/waydroid/config} +apparmor_profiles=${WAYDROID_APPARMOR_PROFILES:-/sys/kernel/security/apparmor/profiles} sys_drm_dir=${WAYDROID_SYS_DRM_DIR:-/sys/class/drm} waydroid_bin=${WAYDROID_BIN:-/usr/bin/waydroid} failed=0 @@ -12,6 +14,36 @@ failed=0 pass() { echo "PASS: $*"; } fail() { echo "FAIL: $*" >&2; failed=1; } +verify_char_device_rule() { + device=$1 + label=$2 + if [ ! -c "${device}" ]; then + fail "${label} is not a character device: ${device}" + return + fi + set -- $(stat -c '%t %T' "${device}") + major=$(printf '%d' "0x$1") + minor=$(printf '%d' "0x$2") + if grep -Fqx "${cgroup_controller}.devices.allow = c ${major}:${minor} rwm" "${nodes}"; then + pass "${label} has an exact cgroup device rule (${major}:${minor})" + else + fail "${label} lacks an exact cgroup device rule (${major}:${minor})" + fi +} + +verify_device_dac() { + device=$1 + gid=$2 + label=$3 + if [ -c "${device}" ] && [ "$(stat -c %u "${device}")" = 0 ] \ + && [ "$(stat -c %g "${device}")" = "${gid}" ] \ + && [ "$(stat -c %a "${device}")" = 660 ]; then + pass "${label} DAC is root:${gid} mode 0660" + else + fail "${label} DAC is not root:${gid} mode 0660" + fi +} + config_value() { key=$1 awk -F= -v wanted="${key}" ' @@ -55,6 +87,15 @@ else fi if [ -r "${nodes}" ]; then + deny_count=$(grep -Ec '^lxc\.cgroup2?\.devices\.deny = a$' "${nodes}" || true) + if [ "${deny_count}" -eq 1 ] \ + && ! grep -Eq '^lxc\.cgroup2?\.devices\.allow = a([[:space:]]|$)' "${nodes}"; then + pass 'LXC device policy denies by default and has no wildcard allow' + else + fail 'LXC device policy is not deny-by-default' + fi + cgroup_controller=$(grep -E '^lxc\.cgroup2?\.devices\.deny = a$' "${nodes}" \ + | head -1 | sed 's/\.devices\.deny.*//') render_mounts=$(grep -c '/dev/dri/renderD' "${nodes}" || true) [ "${render_mounts}" -eq 1 ] && grep -Fq "${render}" "${nodes}" \ && pass 'only the selected DRM render node is mounted' \ @@ -68,13 +109,41 @@ if [ -r "${nodes}" ]; then [ "${video_mounts}" -eq 1 ] && grep -Fq "${video}" "${nodes}" \ && pass 'only the capability-selected V4L2 decoder node is mounted' \ || fail 'V4L2 decoder-node mount is not exact' + + if [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" != 1 ] && [ -n "${cgroup_controller}" ]; then + verify_char_device_rule "${render}" 'DRM render node' + verify_device_dac "${render}" 1003 'DRM render node' + old_ifs=${IFS} + IFS=';' + for device in $(config_value dma_heap_devices); do + verify_char_device_rule "${device}" 'DMA heap' + verify_device_dac "${device}" 1003 'DMA heap' + done + IFS=${old_ifs} + verify_char_device_rule "${video}" 'V4L2 decoder node' + fi else fail "${nodes} is unavailable; initialize Waydroid first" fi if [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" = 1 ]; then - pass 'V4L2 daemon and DAC checks skipped for fixture devices' + pass 'AppArmor, exact device-rule, V4L2 daemon and DAC checks skipped for fixture devices' else + [ -r /sys/module/apparmor/parameters/enabled ] \ + && grep -Fqx Y /sys/module/apparmor/parameters/enabled \ + && pass 'host AppArmor LSM is enabled' \ + || fail 'host AppArmor LSM is not enabled' + [ -r "${lxc_config}" ] \ + && grep -Fqx 'lxc.apparmor.profile = lxc-waydroid' "${lxc_config}" \ + && ! grep -Eq '^lxc\.(aa_profile|apparmor\.profile) = unconfined$' "${lxc_config}" \ + && pass 'Waydroid selects the enforcing AppArmor profile' \ + || fail 'Waydroid is not configured with its AppArmor profile' + for profile in lxc-waydroid adbd android_app; do + [ -r "${apparmor_profiles}" ] \ + && grep -Fq "${profile} (enforce)" "${apparmor_profiles}" \ + && pass "Waydroid AppArmor profile ${profile} is enforcing" \ + || fail "Waydroid AppArmor profile ${profile} is not enforcing" + done systemctl is-active --quiet waydroid-vsidaemon.service \ && pass 'NXP Hantro userspace daemon is running' \ || fail 'NXP Hantro userspace daemon is not running' diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision index 132a6d55..2867e6e0 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -98,6 +98,19 @@ if [ -z "${video_devices}" ]; then fi if [ "${WAYDROID_ALLOW_FAKE_DRM:-0}" != 1 ]; then + # The Android compositor and minigbm allocator use AID_GRAPHICS (stable + # numeric GID 1003). Restrict the selected GPU and DMA heaps to that group + # rather than making host accelerator devices world-accessible. + chown 0:1003 "${render_node}" + chmod 0660 "${render_node}" + old_ifs=${IFS} + IFS=';' + for dma_heap in ${dma_heap_devices}; do + chown 0:1003 "${dma_heap}" + chmod 0660 "${dma_heap}" + done + IFS=${old_ifs} + # The Android V4L2 Codec2 service runs as AID_MEDIA (stable numeric GID # 1013). Grant that service access to only the capability-selected decoder; # the camera and encoder are neither changed nor mounted into the container. diff --git a/recipes-support/waydroid/waydroid/waydroid-image-provision.service b/recipes-support/waydroid/waydroid/waydroid-image-provision.service index 92534cb4..c4ba8798 100644 --- a/recipes-support/waydroid/waydroid/waydroid-image-provision.service +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision.service @@ -1,8 +1,8 @@ [Unit] Description=Provision Waydroid Android images into persistent storage Wants=network-online.target -Requires=waydroid-vsidaemon.service -After=network-online.target dbus.service waydroid-vsidaemon.service +Requires=apparmor.service waydroid-vsidaemon.service +After=network-online.target dbus.service apparmor.service waydroid-vsidaemon.service Before=waydroid-container.service StartLimitIntervalSec=0 diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 95080cfe..87818ca0 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -47,6 +47,7 @@ if grep -Fq 'ro.hardware.vulkan' "${config}"; then fi printf '%s\n' \ + 'lxc.cgroup2.devices.deny = a' \ "lxc.mount.entry = ${test_root}/dev/dri/renderD128 dev/dri/renderD128 none bind,create=file 0 0" \ 'lxc.mount.entry = /dev/dma_heap/system dev/dma_heap/system none bind,create=file 0 0' \ 'lxc.mount.entry = /dev/dma_heap/linux,cma dev/dma_heap/linux,cma none bind,create=file 0 0' \ From 99b1a84f5205866910ec8ba94b0484c1b805ee41 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 16:43:48 +0100 Subject: [PATCH 60/72] test: record Waydroid board acceleration evidence --- docs/waydroid-android16-build.md | 16 +++ recipes-support/waydroid/waydroid.bb | 2 + .../waydroid/waydroid/waydroid-board-evidence | 126 ++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 recipes-support/waydroid/waydroid/waydroid-board-evidence diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index 05c414de..27c29f00 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -52,3 +52,19 @@ sudo /usr/libexec/waydroid-acceleration-check This is a release gate: it checks AppArmor is enabled and enforcing, the LXC profile is selected, the device policy is deny-by-default with exact rules, and the Android GPU/Vulkan/Codec2 runtime state matches the product contract. + +Record the complete target snapshots and restart proof with: + +```sh +sudo /usr/libexec/waydroid-board-evidence capture +sudo /usr/libexec/waydroid-board-evidence restart +``` + +Suspend/resume is a two-stage test so the evidence survives the SSH session +dropping during suspend: + +```sh +sudo /usr/libexec/waydroid-board-evidence suspend-prepare /var/log/waydroid-validation/suspend-1 +# Suspend and wake the board using the product wake source. +sudo /usr/libexec/waydroid-board-evidence suspend-verify /var/log/waydroid-validation/suspend-1 +``` diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index a728423f..7bf72403 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -36,6 +36,7 @@ SRC_URI:append:imx8mm-jaguar-screen = " \ file://waydroid-zram.service \ file://waydroid-container-2gb.conf \ file://waydroid-memory-headroom \ + file://waydroid-board-evidence \ file://waydroid-acceleration-check \ file://waydroid-v4l2-probe \ file://waydroid-vsidaemon.service \ @@ -136,6 +137,7 @@ do_install:append:imx8mm-jaguar-screen() { install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh install -Dm0755 ${WORKDIR}/waydroid-zram ${D}${libexecdir}/waydroid-zram install -Dm0755 ${WORKDIR}/waydroid-memory-headroom ${D}${libexecdir}/waydroid-memory-headroom + install -Dm0755 ${WORKDIR}/waydroid-board-evidence ${D}${libexecdir}/waydroid-board-evidence install -Dm0755 ${WORKDIR}/waydroid-acceleration-check ${D}${libexecdir}/waydroid-acceleration-check install -Dm0755 ${WORKDIR}/waydroid-v4l2-probe ${D}${libexecdir}/waydroid-v4l2-probe install -Dm0644 ${WORKDIR}/waydroid-zram.service \ diff --git a/recipes-support/waydroid/waydroid/waydroid-board-evidence b/recipes-support/waydroid/waydroid/waydroid-board-evidence new file mode 100644 index 00000000..7415804a --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-board-evidence @@ -0,0 +1,126 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu +umask 027 + +mode=${1:-capture} +output=${2:-/var/log/waydroid-validation/$(date -u +%Y%m%dT%H%M%SZ)} +failed=0 + +mark_fail() { + echo "FAIL: $*" >&2 + failed=1 +} + +capture_all() { + destination=$1 + mkdir -p "${destination}" + + { + echo "captured_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "boot_id=$(cat /proc/sys/kernel/random/boot_id)" + echo "kernel=$(uname -srvmo)" + echo "uptime_seconds=$(cut -d' ' -f1 /proc/uptime)" + [ -r /etc/os-release ] && cat /etc/os-release + } > "${destination}/host.txt" + + /usr/libexec/waydroid-memory-headroom > "${destination}/memory.txt" 2>&1 \ + || mark_fail 'memory snapshot failed' + /usr/libexec/waydroid-acceleration-check > "${destination}/acceleration.txt" 2>&1 \ + || mark_fail 'acceleration gate failed' + + systemctl show waydroid-container.service waydroid-vsidaemon.service \ + -p Id -p ActiveState -p SubState -p MainPID -p ControlGroup \ + > "${destination}/services.txt" 2>&1 || mark_fail 'service snapshot failed' + journalctl -b --no-pager -n 200 \ + -u waydroid-container.service -u waydroid-vsidaemon.service \ + > "${destination}/service-journal.txt" 2>&1 || true + + { + stat -c '%n uid=%u gid=%g mode=%a type=%t:%T' \ + /dev/dri/renderD* /dev/dma_heap/* /dev/video* 2>/dev/null || true + for node in /sys/class/drm/renderD*/device/uevent; do + [ -r "${node}" ] || continue + echo "[${node}]" + cat "${node}" + done + } > "${destination}/accelerator-devices.txt" + + { + for property in ro.build.fingerprint ro.build.version.release \ + ro.build.version.security_patch ro.hardware.egl \ + ro.hardware.gralloc ro.hardware.vulkan; do + printf '%s=' "${property}" + waydroid shell getprop "${property}" 2>/dev/null || true + done + waydroid shell pm list features 2>/dev/null \ + | grep -E 'opengles|vulkan' || true + waydroid shell dumpsys SurfaceFlinger 2>/dev/null \ + | grep -Ei 'GLES|etnaviv|GC7000|Vivante|refresh|fps' | head -80 || true + waydroid shell dumpsys media.codec 2>/dev/null \ + | grep -E 'c2\.v4l2|decoder' | head -120 || true + } > "${destination}/android.txt" + waydroid shell dumpsys SurfaceFlinger --latency \ + > "${destination}/surfaceflinger-latency.txt" 2>&1 || true + + if [ -d /usr/share/waydroid-extra/evidence ]; then + (cd /usr/share/waydroid-extra/evidence && sha256sum *) \ + > "${destination}/installed-evidence-sha256.txt" 2>&1 \ + || mark_fail 'installed Android evidence hashing failed' + else + mark_fail 'installed Android evidence directory is absent' + fi +} + +case "${mode}" in + capture) + capture_all "${output}" + ;; + restart) + capture_all "${output}/before" + systemctl restart waydroid-container.service \ + || mark_fail 'container restart command failed' + running=false + attempts=0 + while [ "${attempts}" -lt 90 ]; do + if waydroid status 2>/dev/null | grep -Fq 'Container: RUNNING'; then + running=true + break + fi + attempts=$((attempts + 1)) + sleep 1 + done + [ "${running}" = true ] || mark_fail 'container did not recover within 90 seconds' + capture_all "${output}/after" + ;; + suspend-prepare) + mkdir -p "${output}" + cat /proc/sys/kernel/random/boot_id > "${output}/boot-id" + date +%s > "${output}/started-at" + capture_all "${output}/before" + echo "Prepared ${output}; suspend the board, wake it, then run:" + echo "$0 suspend-verify ${output}" + ;; + suspend-verify) + [ -r "${output}/boot-id" ] && [ -r "${output}/started-at" ] \ + || { echo "Missing suspend preparation state in ${output}" >&2; exit 2; } + [ "$(cat "${output}/boot-id")" = "$(cat /proc/sys/kernel/random/boot_id)" ] \ + || mark_fail 'board rebooted instead of resuming the same kernel' + journalctl -k --no-pager --since "@$(cat "${output}/started-at")" \ + > "${output}/suspend-kernel-journal.txt" 2>&1 || true + grep -Eiq 'PM: suspend entry|suspending system' "${output}/suspend-kernel-journal.txt" \ + || mark_fail 'kernel suspend entry was not recorded' + grep -Eiq 'PM: suspend exit|resume from suspend|Finishing wakeup' \ + "${output}/suspend-kernel-journal.txt" \ + || mark_fail 'kernel resume was not recorded' + capture_all "${output}/after" + ;; + *) + echo "Usage: $0 {capture|restart|suspend-prepare|suspend-verify} [output-directory]" >&2 + exit 2 + ;; +esac + +echo "evidence=${output}" +exit "${failed}" From 5395da1d00ed3885ea4f4c2840727fd15fd4b5d3 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 7 Sep 2026 16:47:23 +0100 Subject: [PATCH 61/72] compliance: emit host SPDX and licence evidence --- .github/workflows/build-waydroid-imx8mm-aesl.yml | 1 + docs/waydroid-android16-build.md | 4 ++++ kas/waydroid-imx8mm-aesl.yml | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml index 83634beb..a2bcd81b 100644 --- a/.github/workflows/build-waydroid-imx8mm-aesl.yml +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -62,6 +62,7 @@ jobs: test -d "${deploy}" find "${deploy}" -maxdepth 1 -type f -printf '%f\n' | sort find "${deploy}" -maxdepth 1 -type f -name '*.manifest' -print -quit | grep -q . + find "${deploy}" -maxdepth 1 -type f -name '*.spdx.tar.zst' -print -quit | grep -q . - uses: actions/upload-artifact@v4 with: diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index 27c29f00..94ea88db 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -21,6 +21,10 @@ KAS_BUILD_DIR=/yocto/waydroid-host/kas-build \ The KAS profile rejects artifact and work paths outside `/yocto`; `waydroid-data` rejects absent or malformed SHA-256 pins and installs the SBOM, source lock, and build metadata as release evidence alongside the chunked images. +The proof profile also re-enables OpenEmbedded SPDX generation and copies the +host package licence manifest and licence texts into the image. CI fails unless +the deploy directory contains the host image manifest and SPDX archive, so the +Android SBOM is not mistaken for a complete product SBOM. CI runs this inside the digest-pinned Yocto build container with `/yocto` mounted at the same absolute path. Do not substitute `kas-container` without diff --git a/kas/waydroid-imx8mm-aesl.yml b/kas/waydroid-imx8mm-aesl.yml index 9225587b..49271468 100644 --- a/kas/waydroid-imx8mm-aesl.yml +++ b/kas/waydroid-imx8mm-aesl.yml @@ -24,6 +24,19 @@ local_conf_header: FILESEXTRAPATHS:prepend:pn-waydroid-data := "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm:${AESL_ANDROID_ARTIFACT_DIR}:" require ${AESL_ANDROID_ARTIFACT_DIR}/waydroid-images.inc + # The base development profile suppresses SPDX for speed. This CRA proof + # image must describe the complete Linux host as well as carrying the + # Android SPDX document installed by waydroid-data. + CREATE_SPDX:forcevariable = "1" + INHERIT:remove = "spdx" + INHERIT:append = " create-spdx" + SPDX_PRETTY = "1" + SPDX_INCLUDE_SOURCES = "0" + SPDX_ARCHIVE_PACKAGED = "0" + SPDX_ARCHIVE_SOURCES = "0" + COPY_LIC_MANIFEST = "1" + COPY_LIC_DIRS = "1" + python () { artifact_dir = d.getVar("AESL_ANDROID_ARTIFACT_DIR") or "" work_root = d.getVar("AESL_YOCTO_WORK_ROOT") or "" From cc2865363b2f5972808a02b86cef93082cb3323e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 10:55:43 +0100 Subject: [PATCH 62/72] test(waydroid): harden release evidence gates --- .../waydroid/waydroid-acceleration-check | 117 ++++++++++++------ .../waydroid/waydroid/waydroid-board-evidence | 8 +- 2 files changed, 82 insertions(+), 43 deletions(-) diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check index f45721a7..d3eb64bf 100644 --- a/recipes-support/waydroid/waydroid/waydroid-acceleration-check +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -21,9 +21,11 @@ verify_char_device_rule() { fail "${label} is not a character device: ${device}" return fi - set -- $(stat -c '%t %T' "${device}") - major=$(printf '%d' "0x$1") - minor=$(printf '%d' "0x$2") + device_numbers=$(stat -c '%t %T' "${device}") + major_hex=${device_numbers% *} + minor_hex=${device_numbers#* } + major=$(printf '%d' "0x${major_hex}") + minor=$(printf '%d' "0x${minor_hex}") if grep -Fqx "${cgroup_controller}.devices.allow = c ${major}:${minor} rwm" "${nodes}"; then pass "${label} has an exact cgroup device rule (${major}:${minor})" else @@ -77,7 +79,11 @@ for expected in \ 'ro.opengles.version=196609'; do key=${expected%%=*} value=${expected#*=} - [ "$(config_value "${key}")" = "${value}" ] && pass "${expected}" || fail "missing ${expected}" + if [ "$(config_value "${key}")" = "${value}" ]; then + pass "${expected}" + else + fail "missing ${expected}" + fi done if [ -n "$(config_value ro.hardware.vulkan)" ]; then @@ -97,18 +103,22 @@ if [ -r "${nodes}" ]; then cgroup_controller=$(grep -E '^lxc\.cgroup2?\.devices\.deny = a$' "${nodes}" \ | head -1 | sed 's/\.devices\.deny.*//') render_mounts=$(grep -c '/dev/dri/renderD' "${nodes}" || true) - [ "${render_mounts}" -eq 1 ] && grep -Fq "${render}" "${nodes}" \ - && pass 'only the selected DRM render node is mounted' \ - || fail 'DRM render-node mount is not exact' + if [ "${render_mounts}" -eq 1 ] && grep -Fq "${render}" "${nodes}"; then + pass 'only the selected DRM render node is mounted' + else + fail 'DRM render-node mount is not exact' + fi if grep '/dev/dma_heap/' "${nodes}" | grep -Ev '/dev/dma_heap/(system|system-uncached|linux,cma)([[:space:]]|$)' >/dev/null; then fail 'an unapproved DMA heap is mounted' else pass 'DMA heap mounts match the product allowlist' fi video_mounts=$(grep -c '/dev/video' "${nodes}" || true) - [ "${video_mounts}" -eq 1 ] && grep -Fq "${video}" "${nodes}" \ - && pass 'only the capability-selected V4L2 decoder node is mounted' \ - || fail 'V4L2 decoder-node mount is not exact' + if [ "${video_mounts}" -eq 1 ] && grep -Fq "${video}" "${nodes}"; then + pass 'only the capability-selected V4L2 decoder node is mounted' + else + fail 'V4L2 decoder-node mount is not exact' + fi if [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" != 1 ] && [ -n "${cgroup_controller}" ]; then verify_char_device_rule "${render}" 'DRM render node' @@ -129,49 +139,76 @@ fi if [ "${WAYDROID_ALLOW_FAKE_DEVICES:-0}" = 1 ]; then pass 'AppArmor, exact device-rule, V4L2 daemon and DAC checks skipped for fixture devices' else - [ -r /sys/module/apparmor/parameters/enabled ] \ - && grep -Fqx Y /sys/module/apparmor/parameters/enabled \ - && pass 'host AppArmor LSM is enabled' \ - || fail 'host AppArmor LSM is not enabled' - [ -r "${lxc_config}" ] \ - && grep -Fqx 'lxc.apparmor.profile = lxc-waydroid' "${lxc_config}" \ - && ! grep -Eq '^lxc\.(aa_profile|apparmor\.profile) = unconfined$' "${lxc_config}" \ - && pass 'Waydroid selects the enforcing AppArmor profile' \ - || fail 'Waydroid is not configured with its AppArmor profile' + if [ -r /sys/module/apparmor/parameters/enabled ] \ + && grep -Fqx Y /sys/module/apparmor/parameters/enabled; then + pass 'host AppArmor LSM is enabled' + else + fail 'host AppArmor LSM is not enabled' + fi + if [ -r "${lxc_config}" ] \ + && grep -Fqx 'lxc.apparmor.profile = lxc-waydroid' "${lxc_config}" \ + && ! grep -Eq '^lxc\.(aa_profile|apparmor\.profile) = unconfined$' "${lxc_config}"; then + pass 'Waydroid selects the enforcing AppArmor profile' + else + fail 'Waydroid is not configured with its AppArmor profile' + fi for profile in lxc-waydroid adbd android_app; do - [ -r "${apparmor_profiles}" ] \ - && grep -Fq "${profile} (enforce)" "${apparmor_profiles}" \ - && pass "Waydroid AppArmor profile ${profile} is enforcing" \ - || fail "Waydroid AppArmor profile ${profile} is not enforcing" + if [ -r "${apparmor_profiles}" ] \ + && grep -Fq "${profile} (enforce)" "${apparmor_profiles}"; then + pass "Waydroid AppArmor profile ${profile} is enforcing" + else + fail "Waydroid AppArmor profile ${profile} is not enforcing" + fi done - systemctl is-active --quiet waydroid-vsidaemon.service \ - && pass 'NXP Hantro userspace daemon is running' \ - || fail 'NXP Hantro userspace daemon is not running' - [ -c "${video}" ] && [ "$(stat -c %g "${video}")" = 1013 ] \ - && [ "$(stat -c %a "${video}")" = 660 ] \ - && pass 'decoder DAC is restricted to root and Android AID_MEDIA' \ - || fail 'decoder DAC is not root:1013 mode 0660' + if systemctl is-active --quiet waydroid-vsidaemon.service; then + pass 'NXP Hantro userspace daemon is running' + else + fail 'NXP Hantro userspace daemon is not running' + fi + if [ -c "${video}" ] && [ "$(stat -c %g "${video}")" = 1013 ] \ + && [ "$(stat -c %a "${video}")" = 660 ]; then + pass 'decoder DAC is restricted to root and Android AID_MEDIA' + else + fail 'decoder DAC is not root:1013 mode 0660' + fi fi if ! "${waydroid_bin}" status 2>/dev/null | grep -Fq 'RUNNING'; then fail 'Waydroid container is not running' else pass 'Waydroid container is running' - [ "$("${waydroid_bin}" shell getprop ro.hardware.egl 2>/dev/null)" = mesa ] \ - && pass 'Android selected Mesa EGL' || fail 'Android did not select Mesa EGL' - [ "$("${waydroid_bin}" shell getprop ro.hardware.gralloc 2>/dev/null)" = minigbm_gbm_mesa ] \ - && pass 'Android selected minigbm GBM Mesa' || fail 'Android did not select minigbm GBM Mesa' - [ -z "$("${waydroid_bin}" shell getprop ro.hardware.vulkan 2>/dev/null)" ] \ - && pass 'Android does not expose a Vulkan HAL' || fail 'Android exposes a Vulkan HAL' + if [ "$("${waydroid_bin}" shell getprop ro.hardware.egl 2>/dev/null)" = mesa ]; then + pass 'Android selected Mesa EGL' + else + fail 'Android did not select Mesa EGL' + fi + if [ "$("${waydroid_bin}" shell getprop ro.hardware.gralloc 2>/dev/null)" = minigbm_gbm_mesa ]; then + pass 'Android selected minigbm GBM Mesa' + else + fail 'Android did not select minigbm GBM Mesa' + fi + if [ -z "$("${waydroid_bin}" shell getprop ro.hardware.vulkan 2>/dev/null)" ]; then + pass 'Android does not expose a Vulkan HAL' + else + fail 'Android exposes a Vulkan HAL' + fi if "${waydroid_bin}" shell pm list features 2>/dev/null | grep -Fq 'android.hardware.vulkan'; then fail 'Android advertises a Vulkan feature' else pass 'Android does not advertise Vulkan features' fi - "${waydroid_bin}" shell dumpsys SurfaceFlinger 2>/dev/null | grep -Eiq 'etnaviv|GC7000|Vivante' \ - && pass 'SurfaceFlinger reports the i.MX8MM GPU' || fail 'SurfaceFlinger lacks Etnaviv/GC7000 evidence' - "${waydroid_bin}" shell dumpsys media.codec 2>/dev/null | grep -Fq 'c2.v4l2.avc.decoder' \ - && pass 'V4L2 H.264 Codec2 component is registered' || fail 'V4L2 H.264 Codec2 component is absent' + if "${waydroid_bin}" shell dumpsys SurfaceFlinger 2>/dev/null \ + | grep -Eiq 'etnaviv|GC7000|Vivante'; then + pass 'SurfaceFlinger reports the i.MX8MM GPU' + else + fail 'SurfaceFlinger lacks Etnaviv/GC7000 evidence' + fi + if "${waydroid_bin}" shell dumpsys media.codec 2>/dev/null \ + | grep -Fq 'c2.v4l2.avc.decoder'; then + pass 'V4L2 H.264 Codec2 component is registered' + else + fail 'V4L2 H.264 Codec2 component is absent' + fi fi exit "${failed}" diff --git a/recipes-support/waydroid/waydroid/waydroid-board-evidence b/recipes-support/waydroid/waydroid/waydroid-board-evidence index 7415804a..6c2c1611 100644 --- a/recipes-support/waydroid/waydroid/waydroid-board-evidence +++ b/recipes-support/waydroid/waydroid/waydroid-board-evidence @@ -65,7 +65,7 @@ capture_all() { > "${destination}/surfaceflinger-latency.txt" 2>&1 || true if [ -d /usr/share/waydroid-extra/evidence ]; then - (cd /usr/share/waydroid-extra/evidence && sha256sum *) \ + (cd /usr/share/waydroid-extra/evidence && sha256sum -- ./*) \ > "${destination}/installed-evidence-sha256.txt" 2>&1 \ || mark_fail 'installed Android evidence hashing failed' else @@ -103,8 +103,10 @@ case "${mode}" in echo "$0 suspend-verify ${output}" ;; suspend-verify) - [ -r "${output}/boot-id" ] && [ -r "${output}/started-at" ] \ - || { echo "Missing suspend preparation state in ${output}" >&2; exit 2; } + if [ ! -r "${output}/boot-id" ] || [ ! -r "${output}/started-at" ]; then + echo "Missing suspend preparation state in ${output}" >&2 + exit 2 + fi [ "$(cat "${output}/boot-id")" = "$(cat /proc/sys/kernel/random/boot_id)" ] \ || mark_fail 'board rebooted instead of resuming the same kernel' journalctl -k --no-pager --since "@$(cat "${output}/started-at")" \ From a88603bbea48f62abc79488502dd8d8e0c362a8a Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 11:01:50 +0100 Subject: [PATCH 63/72] test(waydroid): prove acceleration gate rejects unsafe paths --- scripts/validation/waydroid-gpu-provision.sh | 35 +++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 87818ca0..1bf73add 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -3,7 +3,7 @@ set -eu -repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd) provision=${repo_root}/recipes-support/waydroid/waydroid/waydroid-image-provision test_root=$(mktemp -d /tmp/waydroid-gpu-provision.XXXXXX) trap 'rm -rf "${test_root}"' EXIT HUP INT TERM @@ -75,4 +75,37 @@ WAYDROID_BIN=${test_root}/waydroid \ WAYDROID_ALLOW_FAKE_DEVICES=1 \ sh "${repo_root}/recipes-support/waydroid/waydroid/waydroid-acceleration-check" +run_gate() { + WAYDROID_CONFIG=${config} \ + WAYDROID_LXC_NODES=${test_root}/config_nodes \ + WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ + WAYDROID_BIN=${test_root}/waydroid \ + WAYDROID_ALLOW_FAKE_DEVICES=1 \ + sh "${repo_root}/recipes-support/waydroid/waydroid/waydroid-acceleration-check" +} + +expect_gate_failure() { + description=$1 + if run_gate > "${test_root}/negative-test.log" 2>&1; then + echo "Release gate accepted ${description}" >&2 + cat "${test_root}/negative-test.log" >&2 + exit 1 + fi +} + +cp "${config}" "${test_root}/waydroid.cfg.good" +cp "${test_root}/config_nodes" "${test_root}/config_nodes.good" + +printf 'ro.hardware.vulkan = lvp\n' >> "${config}" +expect_gate_failure 'a software Vulkan HAL override' +cp "${test_root}/waydroid.cfg.good" "${config}" + +printf 'DRIVER=vgem\n' > "${test_root}/sys/class/drm/renderD128/device/uevent" +expect_gate_failure 'a non-Etnaviv render node' +printf 'DRIVER=etnaviv\n' > "${test_root}/sys/class/drm/renderD128/device/uevent" + +printf 'lxc.cgroup2.devices.allow = a\n' >> "${test_root}/config_nodes" +expect_gate_failure 'wildcard LXC device access' +cp "${test_root}/config_nodes.good" "${test_root}/config_nodes" + echo 'Waydroid Etnaviv GPU and V4L2 provisioning: passed' From a9e49522eab38dd33a4788346141164c0d24890e Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 11:07:39 +0100 Subject: [PATCH 64/72] test(waydroid): measure CPU and storage headroom --- docs/waydroid-imx8mm-2gb-memory.md | 17 +++++--- .../waydroid/waydroid-memory-headroom | 42 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/waydroid-imx8mm-2gb-memory.md b/docs/waydroid-imx8mm-2gb-memory.md index 746f7aa0..37271bbc 100644 --- a/docs/waydroid-imx8mm-2gb-memory.md +++ b/docs/waydroid-imx8mm-2gb-memory.md @@ -12,15 +12,20 @@ image, run: ``` Collect results at idle, after kiosk launch, during video playback, during an -application update and after repeated application restarts. Also check the -kernel journal for OOM kills and zram writeback failures. +application update and after repeated application restarts. The report takes a +two-second CPU sample and records the host filesystems that contain Waydroid's +state and images. Also check the kernel journal for OOM kills and zram +writeback failures. Resource gates under the target workload: - green: container peak below 70% of `MemoryMax` and host available memory - above 30%; -- amber: either reaches 70%; -- red: either reaches 85%, `memory.events` reports `oom`/`oom_kill`, or the - kernel OOM killer runs. + above 30%, CPU busy below 70% with idle above 30%, and relevant filesystems + below 70%; +- amber: RAM or filesystem use reaches 70%, CPU busy reaches 70%, or CPU idle + falls to 30%; +- red: RAM or filesystem use reaches 85%, CPU busy reaches 85%, CPU idle falls + to 15%, `memory.events` reports `oom`/`oom_kill`, or the kernel OOM killer + runs. Adjust `MemoryHigh`, `MemoryMax` or zram size only from recorded board data. diff --git a/recipes-support/waydroid/waydroid/waydroid-memory-headroom b/recipes-support/waydroid/waydroid/waydroid-memory-headroom index 431252a9..05ed1baa 100644 --- a/recipes-support/waydroid/waydroid/waydroid-memory-headroom +++ b/recipes-support/waydroid/waydroid/waydroid-memory-headroom @@ -1,4 +1,5 @@ #!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only set -eu cgroup=/sys/fs/cgroup/system.slice/waydroid-container.service @@ -33,6 +34,47 @@ for resource in cpu memory io; do fi done +echo '[cpu]' +cpu_sample() { + awk '/^cpu / { + total = 0 + for (field = 2; field <= NF; field++) total += $field + idle = $5 + $6 + printf "%.0f:%.0f\n", total, idle + exit + }' /proc/stat +} +cpu_before=$(cpu_sample) +sample_seconds=${WAYDROID_CPU_SAMPLE_SECONDS:-2} +sleep "${sample_seconds}" +cpu_after=$(cpu_sample) +total_before=${cpu_before%%:*} +idle_before=${cpu_before#*:} +total_after=${cpu_after%%:*} +idle_after=${cpu_after#*:} +total_delta=$((total_after - total_before)) +idle_delta=$((idle_after - idle_before)) +if [ "${total_delta}" -gt 0 ]; then + idle_pct=$((idle_delta * 100 / total_delta)) + printf 'cpu.sample_seconds=%s\n' "${sample_seconds}" + printf 'cpu.idle_pct=%s\n' "${idle_pct}" + printf 'cpu.busy_pct=%s\n' "$((100 - idle_pct))" +fi +printf 'cpu.loadavg=' +cat /proc/loadavg +printf 'cpu.count=' +getconf _NPROCESSORS_ONLN + +echo '[storage]' +for path in / /var/lib/waydroid /etc/waydroid-extra/images; do + [ -e "${path}" ] || continue + df -Pk "${path}" | awk -v path="${path}" 'NR == 2 { + gsub(/%/, "", $5) + printf "storage.path=%s size_kib=%s used_kib=%s available_kib=%s used_pct=%s mount=%s\n", \ + path, $2, $3, $4, $5, $6 + }' +done + echo '[gpu-devfreq]' for devfreq in /sys/class/devfreq/*; do [ -d "${devfreq}" ] || continue From 50fb7f9ef35eed2514781f9bb91fe1027fdb5f00 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 11:13:57 +0100 Subject: [PATCH 65/72] test(waydroid): summarize frame-rate headroom --- docs/waydroid-imx8mm-2gb-memory.md | 6 +++ recipes-support/waydroid/waydroid.bb | 2 + .../waydroid/waydroid/waydroid-board-evidence | 4 ++ .../waydroid/waydroid/waydroid-frame-headroom | 48 +++++++++++++++++++ scripts/validation/waydroid-gpu-provision.sh | 15 ++++++ 5 files changed, 75 insertions(+) create mode 100755 recipes-support/waydroid/waydroid/waydroid-frame-headroom diff --git a/docs/waydroid-imx8mm-2gb-memory.md b/docs/waydroid-imx8mm-2gb-memory.md index 37271bbc..5def0bf1 100644 --- a/docs/waydroid-imx8mm-2gb-memory.md +++ b/docs/waydroid-imx8mm-2gb-memory.md @@ -17,6 +17,12 @@ two-second CPU sample and records the host filesystems that contain Waydroid's state and images. Also check the kernel journal for OOM kills and zram writeback failures. +`waydroid-board-evidence capture` also records raw SurfaceFlinger presentation +timestamps and summarizes the target refresh rate, effective frame rate, +missed refresh intervals and worst frame gap. Capture this during steady-state +shipping kiosk animation and video playback; an idle surface is not a valid +frame-rate headroom measurement. + Resource gates under the target workload: - green: container peak below 70% of `MemoryMax` and host available memory diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 7bf72403..ce9dea01 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -36,6 +36,7 @@ SRC_URI:append:imx8mm-jaguar-screen = " \ file://waydroid-zram.service \ file://waydroid-container-2gb.conf \ file://waydroid-memory-headroom \ + file://waydroid-frame-headroom \ file://waydroid-board-evidence \ file://waydroid-acceleration-check \ file://waydroid-v4l2-probe \ @@ -137,6 +138,7 @@ do_install:append:imx8mm-jaguar-screen() { install -m 755 ${WORKDIR}/waydroid-net.sh ${D}/usr/lib/waydroid/data/scripts/waydroid-net.sh install -Dm0755 ${WORKDIR}/waydroid-zram ${D}${libexecdir}/waydroid-zram install -Dm0755 ${WORKDIR}/waydroid-memory-headroom ${D}${libexecdir}/waydroid-memory-headroom + install -Dm0755 ${WORKDIR}/waydroid-frame-headroom ${D}${libexecdir}/waydroid-frame-headroom install -Dm0755 ${WORKDIR}/waydroid-board-evidence ${D}${libexecdir}/waydroid-board-evidence install -Dm0755 ${WORKDIR}/waydroid-acceleration-check ${D}${libexecdir}/waydroid-acceleration-check install -Dm0755 ${WORKDIR}/waydroid-v4l2-probe ${D}${libexecdir}/waydroid-v4l2-probe diff --git a/recipes-support/waydroid/waydroid/waydroid-board-evidence b/recipes-support/waydroid/waydroid/waydroid-board-evidence index 6c2c1611..b761c62b 100644 --- a/recipes-support/waydroid/waydroid/waydroid-board-evidence +++ b/recipes-support/waydroid/waydroid/waydroid-board-evidence @@ -63,6 +63,10 @@ capture_all() { } > "${destination}/android.txt" waydroid shell dumpsys SurfaceFlinger --latency \ > "${destination}/surfaceflinger-latency.txt" 2>&1 || true + /usr/libexec/waydroid-frame-headroom \ + "${destination}/surfaceflinger-latency.txt" \ + > "${destination}/frame-headroom.txt" 2>&1 \ + || mark_fail 'SurfaceFlinger frame-headroom summary failed' if [ -d /usr/share/waydroid-extra/evidence ]; then (cd /usr/share/waydroid-extra/evidence && sha256sum -- ./*) \ diff --git a/recipes-support/waydroid/waydroid/waydroid-frame-headroom b/recipes-support/waydroid/waydroid/waydroid-frame-headroom new file mode 100755 index 00000000..7239dd3a --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-frame-headroom @@ -0,0 +1,48 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu + +input=${1:--} + +awk ' + NR == 1 { + refresh_ns = $1 + 0 + next + } + $2 ~ /^[0-9]+$/ { + presented_ns = $2 + 0 + if (presented_ns <= 0 || presented_ns >= 9.0e18) + next + if (previous_ns > 0) { + delta_ns = presented_ns - previous_ns + if (delta_ns > 0) { + intervals++ + total_ns += delta_ns + if (delta_ns > worst_ns) + worst_ns = delta_ns + if (refresh_ns > 0 && delta_ns > refresh_ns * 1.5) + missed_intervals++ + } + } + previous_ns = presented_ns + } + END { + if (refresh_ns <= 0) { + print "frame.error=invalid_refresh_period" + exit 2 + } + printf "frame.refresh_period_ns=%.0f\n", refresh_ns + printf "frame.target_fps=%.2f\n", 1000000000 / refresh_ns + printf "frame.intervals=%d\n", intervals + if (intervals == 0) { + print "frame.error=no_presented_intervals" + exit 3 + } + printf "frame.effective_fps=%.2f\n", 1000000000 * intervals / total_ns + printf "frame.missed_intervals=%d\n", missed_intervals + printf "frame.missed_intervals_pct=%.2f\n", 100 * missed_intervals / intervals + printf "frame.worst_interval_ns=%.0f\n", worst_ns + printf "frame.worst_refresh_periods=%.2f\n", worst_ns / refresh_ns + } +' "${input}" diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 1bf73add..2f00468a 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -108,4 +108,19 @@ printf 'lxc.cgroup2.devices.allow = a\n' >> "${test_root}/config_nodes" expect_gate_failure 'wildcard LXC device access' cp "${test_root}/config_nodes.good" "${test_root}/config_nodes" +cat > "${test_root}/surfaceflinger-latency.txt" <<'LATENCY' +16666666 +990000000 1000000000 1001000000 +1006666666 1016666666 1017666666 +1023333332 1033333332 1034333332 +1056666664 1066666664 1067666664 +LATENCY +sh "${repo_root}/recipes-support/waydroid/waydroid/waydroid-frame-headroom" \ + "${test_root}/surfaceflinger-latency.txt" > "${test_root}/frame-headroom.txt" +grep -Fqx 'frame.target_fps=60.00' "${test_root}/frame-headroom.txt" +grep -Fqx 'frame.intervals=3' "${test_root}/frame-headroom.txt" +grep -Fqx 'frame.effective_fps=45.00' "${test_root}/frame-headroom.txt" +grep -Fqx 'frame.missed_intervals=1' "${test_root}/frame-headroom.txt" +grep -Fqx 'frame.worst_refresh_periods=2.00' "${test_root}/frame-headroom.txt" + echo 'Waydroid Etnaviv GPU and V4L2 provisioning: passed' From 7ee3ae61f84737b2fdacb976c95df47265686575 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 11:51:43 +0100 Subject: [PATCH 66/72] build: gate host images on Android release provenance --- .../workflows/build-waydroid-imx8mm-aesl.yml | 12 +++++ docs/waydroid-android16-build.md | 6 +++ .../validate-waydroid-build-info.py | 52 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100755 scripts/validation/validate-waydroid-build-info.py diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml index a2bcd81b..c0f09247 100644 --- a/.github/workflows/build-waydroid-imx8mm-aesl.yml +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -7,6 +7,14 @@ on: description: Reviewed Android artifact root under /yocto/android-16-artifacts required: true type: string + release_mode: + description: Integration accepts userdebug; production requires production-gated Android user provenance + required: true + default: integration + type: choice + options: + - integration + - production permissions: contents: read @@ -30,6 +38,7 @@ jobs: - name: Validate Android evidence and CI storage env: AESL_ANDROID_ARTIFACT_DIR: ${{ inputs.android_artifact_dir }} + AESL_RELEASE_MODE: ${{ inputs.release_mode }} run: | case "${AESL_ANDROID_ARTIFACT_DIR}" in /yocto/android-16-artifacts/*) ;; @@ -41,6 +50,9 @@ jobs: test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/system.img" test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/vendor.img" test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/sbom.spdx.json" + python3 scripts/validation/validate-waydroid-build-info.py \ + "${AESL_ANDROID_ARTIFACT_DIR}/build-info.json" \ + --release-mode "${AESL_RELEASE_MODE}" (cd "${AESL_ANDROID_ARTIFACT_DIR}" && sha256sum --check --strict SHA256SUMS) install -d /yocto/waydroid-host/kas-work /yocto/waydroid-host/kas-build available_kib=$(df --output=avail /yocto | tail -1 | tr -d ' ') diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index 94ea88db..42c00dbd 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -26,6 +26,12 @@ host package licence manifest and licence texts into the image. CI fails unless the deploy directory contains the host image manifest and SPDX archive, so the Android SBOM is not mistaken for a complete product SBOM. +The host workflow defaults to `integration` mode so a reviewed Android +`userdebug` artifact can be exercised on the board. Select `production` only +with an Android `user` artifact whose `build-info.json` records both production +release class and the blocking SELinux production gate; the workflow rejects +missing or integration-only provenance. + CI runs this inside the digest-pinned Yocto build container with `/yocto` mounted at the same absolute path. Do not substitute `kas-container` without also arranging that mount and explicitly forwarding the AESL variables. diff --git a/scripts/validation/validate-waydroid-build-info.py b/scripts/validation/validate-waydroid-build-info.py new file mode 100755 index 00000000..61aaf8ef --- /dev/null +++ b/scripts/validation/validate-waydroid-build-info.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Validate Android build provenance before a Waydroid host build.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("build_info", type=Path) + parser.add_argument( + "--release-mode", choices=("integration", "production"), required=True + ) + args = parser.parse_args() + + info = json.loads(args.build_info.read_text(encoding="utf-8")) + targets = info.get("targets", []) + imx8mm_targets = [ + target + for target in targets + if "lineage_waydroid_aesl_2gb_arm64_only-bp4a-" in target + ] + if len(imx8mm_targets) != 1: + raise SystemExit("build-info must contain exactly one AESL i.MX8MM target") + if not imx8mm_targets[0].endswith(("-user", "-userdebug")): + raise SystemExit("build-info contains an unrecognised i.MX8MM variant") + + if args.release_mode == "production": + required = { + "imx8mm_variant": "user", + "release_class": "production", + "selinux_gate": "production", + } + for key, expected in required.items(): + if info.get(key) != expected: + raise SystemExit( + f"production host build requires build-info {key}={expected}" + ) + if not imx8mm_targets[0].endswith("-user"): + raise SystemExit("production host build rejects Android userdebug") + + print( + f"Android provenance accepted for {args.release_mode}: {imx8mm_targets[0]}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 59f974e77476e9a12a744b305c58fe97974cd0c2 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 12:04:22 +0100 Subject: [PATCH 67/72] compliance: install Android NOTICE licence evidence --- .github/workflows/build-waydroid-imx8mm-aesl.yml | 8 ++++++++ docs/waydroid-android16-build.md | 5 ++++- recipes-support/waydroid/waydroid-data.bb | 11 +++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml index c0f09247..04eca969 100644 --- a/.github/workflows/build-waydroid-imx8mm-aesl.yml +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -50,6 +50,14 @@ jobs: test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/system.img" test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/vendor.img" test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/sbom.spdx.json" + if [ "${AESL_RELEASE_MODE}" = production ]; then + test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/NOTICE-system.xml.gz" + test -s "${AESL_ANDROID_ARTIFACT_DIR}/imx8mm/NOTICE-vendor.xml.gz" + grep -Eq '^AESL_WAYDROID_SYSTEM_NOTICE_SHA256 = "[0-9a-f]{64}"$' \ + "${AESL_ANDROID_ARTIFACT_DIR}/waydroid-images.inc" + grep -Eq '^AESL_WAYDROID_VENDOR_NOTICE_SHA256 = "[0-9a-f]{64}"$' \ + "${AESL_ANDROID_ARTIFACT_DIR}/waydroid-images.inc" + fi python3 scripts/validation/validate-waydroid-build-info.py \ "${AESL_ANDROID_ARTIFACT_DIR}/build-info.json" \ --release-mode "${AESL_RELEASE_MODE}" diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index 42c00dbd..fc176ed7 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -30,7 +30,10 @@ The host workflow defaults to `integration` mode so a reviewed Android `userdebug` artifact can be exercised on the board. Select `production` only with an Android `user` artifact whose `build-info.json` records both production release class and the blocking SELinux production gate; the workflow rejects -missing or integration-only provenance. +missing or integration-only provenance. Production also requires the Android +system and vendor NOTICE archives. Their generated pins are consumed by +`waydroid-data` and the archives are installed beside the Android SBOM and +source-lock evidence. CI runs this inside the digest-pinned Yocto build container with `/yocto` mounted at the same absolute path. Do not substitute `kas-container` without diff --git a/recipes-support/waydroid/waydroid-data.bb b/recipes-support/waydroid/waydroid-data.bb index 368b6ab5..a3c010df 100644 --- a/recipes-support/waydroid/waydroid-data.bb +++ b/recipes-support/waydroid/waydroid-data.bb @@ -36,6 +36,8 @@ SHA256SUM_VENDOR:halium = "cd5b1394f35c97c0284f365e52588eecd7b89b6aa28624aefca55 AESL_WAYDROID_SYSTEM_SHA256 ?= "" AESL_WAYDROID_VENDOR_SHA256 ?= "" AESL_WAYDROID_SBOM_SHA256 ?= "" +AESL_WAYDROID_SYSTEM_NOTICE_SHA256 ?= "" +AESL_WAYDROID_VENDOR_NOTICE_SHA256 ?= "" AESL_WAYDROID_SOURCE_MANIFEST_SHA256 ?= "" AESL_WAYDROID_BUILD_INFO_SHA256 ?= "" @@ -52,10 +54,13 @@ SRC_URI:imx8mm-jaguar-screen = " \ file://source-manifest.xml;name=source-manifest \ file://build-info.json;name=build-info \ " +SRC_URI:append:imx8mm-jaguar-screen = "${@' file://NOTICE-system.xml.gz;name=system-notice' if d.getVar('AESL_WAYDROID_SYSTEM_NOTICE_SHA256') else ''}${@' file://NOTICE-vendor.xml.gz;name=vendor-notice' if d.getVar('AESL_WAYDROID_VENDOR_NOTICE_SHA256') else ''}" SRC_URI[system.sha256sum] = "${SHA256SUM_SYSTEM}" SRC_URI[vendor.sha256sum] = "${SHA256SUM_VENDOR}" SRC_URI[sbom.sha256sum] = "${AESL_WAYDROID_SBOM_SHA256}" +SRC_URI[system-notice.sha256sum] = "${AESL_WAYDROID_SYSTEM_NOTICE_SHA256}" +SRC_URI[vendor-notice.sha256sum] = "${AESL_WAYDROID_VENDOR_NOTICE_SHA256}" SRC_URI[source-manifest.sha256sum] = "${AESL_WAYDROID_SOURCE_MANIFEST_SHA256}" SRC_URI[build-info.sha256sum] = "${AESL_WAYDROID_BUILD_INFO_SHA256}" @@ -103,6 +108,12 @@ do_install:append:imx8mm-jaguar-screen() { "${D}/usr/share/waydroid-extra/evidence/source-manifest.xml" install -m 0644 "${WORKDIR}/build-info.json" \ "${D}/usr/share/waydroid-extra/evidence/build-info.json" + for notice in NOTICE-system.xml.gz NOTICE-vendor.xml.gz; do + if [ -s "${WORKDIR}/${notice}" ]; then + install -m 0644 "${WORKDIR}/${notice}" \ + "${D}/usr/share/waydroid-extra/evidence/${notice}" + fi + done printf '%s system.img\n%s vendor.img\n' \ "${AESL_WAYDROID_SYSTEM_SHA256}" "${AESL_WAYDROID_VENDOR_SHA256}" \ > "${D}/usr/share/waydroid-extra/evidence/IMAGE_SHA256SUMS" From 188a0ea343d635405762bde7bd24636f8e4e96ee Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 15:11:21 +0100 Subject: [PATCH 68/72] test(waydroid): accept tabbed restart status --- recipes-support/waydroid/waydroid/waydroid-board-evidence | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recipes-support/waydroid/waydroid/waydroid-board-evidence b/recipes-support/waydroid/waydroid/waydroid-board-evidence index b761c62b..956526ae 100644 --- a/recipes-support/waydroid/waydroid/waydroid-board-evidence +++ b/recipes-support/waydroid/waydroid/waydroid-board-evidence @@ -88,7 +88,8 @@ case "${mode}" in running=false attempts=0 while [ "${attempts}" -lt 90 ]; do - if waydroid status 2>/dev/null | grep -Fq 'Container: RUNNING'; then + if waydroid status 2>/dev/null \ + | grep -Eq 'Container:[[:space:]]+RUNNING'; then running=true break fi From d00b71816d1749a99a9014b15fa2d32ef6e38c81 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Wed, 9 Sep 2026 15:13:43 +0100 Subject: [PATCH 69/72] test(waydroid): reject software GPU renderers --- .../waydroid/waydroid/waydroid-acceleration-check | 6 +++++- scripts/validation/waydroid-gpu-provision.sh | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check index d3eb64bf..3a224250 100644 --- a/recipes-support/waydroid/waydroid/waydroid-acceleration-check +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -197,7 +197,11 @@ else else pass 'Android does not advertise Vulkan features' fi - if "${waydroid_bin}" shell dumpsys SurfaceFlinger 2>/dev/null \ + surfaceflinger=$("${waydroid_bin}" shell dumpsys SurfaceFlinger 2>/dev/null || true) + if printf '%s\n' "${surfaceflinger}" \ + | grep -Eiq 'swiftshader|llvmpipe|softpipe'; then + fail 'SurfaceFlinger reports a software renderer' + elif printf '%s\n' "${surfaceflinger}" \ | grep -Eiq 'etnaviv|GC7000|Vivante'; then pass 'SurfaceFlinger reports the i.MX8MM GPU' else diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh index 2f00468a..fba959ad 100755 --- a/scripts/validation/waydroid-gpu-provision.sh +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -54,6 +54,8 @@ printf '%s\n' \ 'lxc.mount.entry = /dev/video2 dev/video2 none bind,create=file 0 0' \ > "${test_root}/config_nodes" +# The parameter expansion below belongs in the generated fixture script. +# shellcheck disable=SC2016 printf '%s\n' \ '#!/bin/sh' \ 'case "$*" in' \ @@ -62,7 +64,7 @@ printf '%s\n' \ ' "shell getprop ro.hardware.gralloc") echo minigbm_gbm_mesa ;;' \ ' "shell getprop ro.hardware.vulkan") : ;;' \ ' "shell pm list features") echo feature:android.hardware.opengles.aep ;;' \ - ' "shell dumpsys SurfaceFlinger") echo "GLES: Mesa etnaviv GC7000Lite" ;;' \ + ' "shell dumpsys SurfaceFlinger") echo "${WAYDROID_TEST_RENDERER:-GLES: Mesa etnaviv GC7000Lite}" ;;' \ ' "shell dumpsys media.codec") echo c2.v4l2.avc.decoder ;;' \ ' *) exit 1 ;;' \ 'esac' > "${test_root}/waydroid" @@ -76,6 +78,7 @@ WAYDROID_ALLOW_FAKE_DEVICES=1 \ sh "${repo_root}/recipes-support/waydroid/waydroid/waydroid-acceleration-check" run_gate() { + WAYDROID_TEST_RENDERER=${WAYDROID_TEST_RENDERER:-} \ WAYDROID_CONFIG=${config} \ WAYDROID_LXC_NODES=${test_root}/config_nodes \ WAYDROID_SYS_DRM_DIR=${test_root}/sys/class/drm \ @@ -108,6 +111,9 @@ printf 'lxc.cgroup2.devices.allow = a\n' >> "${test_root}/config_nodes" expect_gate_failure 'wildcard LXC device access' cp "${test_root}/config_nodes.good" "${test_root}/config_nodes" +WAYDROID_TEST_RENDERER='GLES: llvmpipe etnaviv compatibility shim' \ + expect_gate_failure 'a software SurfaceFlinger renderer' + cat > "${test_root}/surfaceflinger-latency.txt" <<'LATENCY' 16666666 990000000 1000000000 1001000000 From 9894ac3d1f22d956b13b624785526aa9dcb93a65 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Thu, 10 Sep 2026 18:31:22 +0100 Subject: [PATCH 70/72] waydroid: provide Android 16 metadata storage --- recipes-support/waydroid/waydroid.bb | 1 + ...xc-provide-writable-android-metadata.patch | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 recipes-support/waydroid/waydroid/0002-lxc-provide-writable-android-metadata.patch diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index ce9dea01..ade91e54 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -23,6 +23,7 @@ RRECOMMENDS:${PN} += "\ SRC_URI = "git://github.com/waydroid/waydroid.git;branch=main;protocol=https \ file://0001-lxc-limit-graphics-device-permissions.patch \ + file://0002-lxc-provide-writable-android-metadata.patch \ file://gbinder.conf \ file://waydroid-luneos.env \ file://waydroid-luneos-appinfo.json \ diff --git a/recipes-support/waydroid/waydroid/0002-lxc-provide-writable-android-metadata.patch b/recipes-support/waydroid/waydroid/0002-lxc-provide-writable-android-metadata.patch new file mode 100644 index 00000000..6bb2692c --- /dev/null +++ b/recipes-support/waydroid/waydroid/0002-lxc-provide-writable-android-metadata.patch @@ -0,0 +1,41 @@ +From 8cb1011fe0ee850422365730ed80466cffbdbd34 Mon Sep 17 00:00:00 2001 +From: Active ESL +Date: Thu, 10 Sep 2026 19:10:00 +0100 +Subject: [PATCH] lxc: provide writable Android metadata storage + +Android 16 selects the new aconfig storage whenever /metadata exists. The +directory supplied by the read-only system image is not writable, so aconfigd +cannot populate its maps. Manifest parsing then treats enabled flags as +missing while generated framework callers still see their compiled values. + +Bind a small persistent host directory over /metadata before Android starts. +This matches the separate writable metadata partition available on a normal +Android device and prevents early system_server failures such as the missing +RANGING permission/app-op mapping. +--- + tools/helpers/lxc.py | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py +index 3dcc5cd..018d223 100644 +--- a/tools/helpers/lxc.py ++++ b/tools/helpers/lxc.py +@@ -154,6 +154,16 @@ def generate_nodes_lxc_config(args): + make_entry("tmpfs", "var", "tmpfs", "nodev 0 0", False) + make_entry("tmpfs", "run", "tmpfs", "nodev 0 0", False) + ++ # Android 16 aconfigd needs persistent writable storage under /metadata. ++ # Without this bind, the mountpoint comes from the read-only system image ++ # and framework manifest flags can disagree with their generated callers. ++ metadata_path = os.path.join(tools.config.defaults["work"], "metadata") ++ os.makedirs(metadata_path, mode=0o770, exist_ok=True) ++ os.chown(metadata_path, 0, 1000) # Android AID_SYSTEM ++ os.chmod(metadata_path, 0o770) ++ make_entry(metadata_path, "metadata", ++ options="rbind,create=dir 0 0") ++ + # NFC config + make_entry("/system/etc/libnfc-nci.conf", options="bind,optional 0 0") + +-- +2.43.0 From aee5aac1758936d5e998a415ad268adf692c2360 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Fri, 11 Sep 2026 16:51:58 +0100 Subject: [PATCH 71/72] fix(waydroid): align host Binder runtime with Android 16 --- docs/waydroid-android16-build.md | 8 +++ recipes-support/libgbinder/libgbinder.bb | 9 ++-- recipes-support/libglibutil/libglibutil.bb | 7 +-- recipes-support/waydroid/waydroid.bb | 1 + ...-use-Android-16-interface-descriptor.patch | 51 +++++++++++++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 recipes-support/waydroid/waydroid/0003-platform-use-Android-16-interface-descriptor.patch diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md index fc176ed7..3b15a3ba 100644 --- a/docs/waydroid-android16-build.md +++ b/docs/waydroid-android16-build.md @@ -66,6 +66,14 @@ This is a release gate: it checks AppArmor is enabled and enforcing, the LXC profile is selected, the device policy is deny-by-default with exact rules, and the Android GPU/Vulkan/Codec2 runtime state matches the product contract. +The Android 16 Binder contract is pinned independently of the Android images: +Waydroid 1.6.3 selects the AIDL6 service-manager protocol for API 36, +libgbinder 1.1.52 is fixed at +`e906afcffbfa51b7fbefe042a13b933d9e8dfdd9`, and libglibutil 1.0.82 is fixed +at `cccc4aa8f1745096f6feb66da7883b35055d9423`. The Waydroid recipe carries a +narrow compatibility patch which selects `id.waydro.waydroid.IPlatform` only +for AIDL6, retaining the legacy descriptor for older Android images. + Record the complete target snapshots and restart proof with: ```sh diff --git a/recipes-support/libgbinder/libgbinder.bb b/recipes-support/libgbinder/libgbinder.bb index bfc46f01..7beaafbc 100644 --- a/recipes-support/libgbinder/libgbinder.bb +++ b/recipes-support/libgbinder/libgbinder.bb @@ -9,13 +9,16 @@ DEPENDS = "glib-2.0 libglibutil" inherit pkgconfig -SRC_URI = "git://github.com/mer-hybris/libgbinder.git;branch=master;protocol=https \ +SRC_URI = "git://github.com/mer-hybris/libgbinder.git;nobranch=1;protocol=https \ file://gbinder.conf \ " S = "${WORKDIR}/git" -PV = "1.1.35" -SRCREV = "e3f705c4cc6b820d8885b565fc7995e02dd196b3" +# First libgbinder release with Android API 36 / AIDL6 service-manager +# support. This is the exact revision proven by the Framework Android 16 +# runtime validation; do not follow the moving master branch. +PV = "1.1.52" +SRCREV = "e906afcffbfa51b7fbefe042a13b933d9e8dfdd9" EXTRA_OEMAKE = "KEEP_SYMBOLS=1" PARALLEL_MAKE = "" diff --git a/recipes-support/libglibutil/libglibutil.bb b/recipes-support/libglibutil/libglibutil.bb index 7d04a511..90abb835 100644 --- a/recipes-support/libglibutil/libglibutil.bb +++ b/recipes-support/libglibutil/libglibutil.bb @@ -9,11 +9,12 @@ DEPENDS = "glib-2.0" inherit pkgconfig -SRC_URI = "git://github.com/sailfishos/libglibutil.git;protocol=https;branch=master" +SRC_URI = "git://github.com/sailfishos/libglibutil.git;protocol=https;nobranch=1" S = "${WORKDIR}/git" -PV = "1.0.75-1+git${SRCPV}" -SRCREV = "4e110017fd4f852a3b1e5616baf111813be9fe92" +# Exact dependency revision used by the validated Android 16 host runtime. +PV = "1.0.82" +SRCREV = "cccc4aa8f1745096f6feb66da7883b35055d9423" EXTRA_OEMAKE = "KEEP_SYMBOLS=1" PARALLEL_MAKE = "" diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index ade91e54..1b3302e9 100644 --- a/recipes-support/waydroid/waydroid.bb +++ b/recipes-support/waydroid/waydroid.bb @@ -24,6 +24,7 @@ RRECOMMENDS:${PN} += "\ SRC_URI = "git://github.com/waydroid/waydroid.git;branch=main;protocol=https \ file://0001-lxc-limit-graphics-device-permissions.patch \ file://0002-lxc-provide-writable-android-metadata.patch \ + file://0003-platform-use-Android-16-interface-descriptor.patch \ file://gbinder.conf \ file://waydroid-luneos.env \ file://waydroid-luneos-appinfo.json \ diff --git a/recipes-support/waydroid/waydroid/0003-platform-use-Android-16-interface-descriptor.patch b/recipes-support/waydroid/waydroid/0003-platform-use-Android-16-interface-descriptor.patch new file mode 100644 index 00000000..0f1bc0a6 --- /dev/null +++ b/recipes-support/waydroid/waydroid/0003-platform-use-Android-16-interface-descriptor.patch @@ -0,0 +1,51 @@ +From 37bda348b9023b3ff445f1730e242ed917034a2a Mon Sep 17 00:00:00 2001 +From: Active ESL +Date: Fri, 11 Sep 2026 16:00:00 +0100 +Subject: [PATCH] platform: use Android 16 interface descriptor + +LineageOS 23.2 exposes the Waydroid platform service as +id.waydro.waydroid.IPlatform. Older Android releases use the legacy +lineageos.waydroid.IPlatform descriptor. + +Waydroid 1.6.3 already selects the AIDL6 service-manager protocol for API 36. +Use that selected protocol to choose the matching platform descriptor while +retaining compatibility with older images. +--- + tools/interfaces/IPlatform.py | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/tools/interfaces/IPlatform.py b/tools/interfaces/IPlatform.py +index b10e908..e0a41e8 100644 +--- a/tools/interfaces/IPlatform.py ++++ b/tools/interfaces/IPlatform.py +@@ -7,6 +7,7 @@ import signal + + + INTERFACE = "lineageos.waydroid.IPlatform" ++ANDROID_16_INTERFACE = "id.waydro.waydroid.IPlatform" + SERVICE_NAME = "waydroidplatform" + + TRANSACTION_getprop = 1 +@@ -24,7 +25,7 @@ TRANSACTION_launchIntent = 13 + + class IPlatform: +- def __init__(self, remote): +- self.client = gbinder.Client(remote, INTERFACE) ++ def __init__(self, remote, interface=INTERFACE): ++ self.client = gbinder.Client(remote, interface) + + def getprop(self, arg1, arg2): + request = self.client.new_request() +@@ -321,7 +322,9 @@ def get_service(args): + else: + return None + +- return IPlatform(remote) ++ interface = (ANDROID_16_INTERFACE if args.SERVICE_MANAGER_PROTOCOL == "aidl6" ++ else INTERFACE) ++ return IPlatform(remote, interface) + + # Like ServiceManager.wait() but can be interrupted + def wait_for_manager(sm): +-- +2.43.0 From 65f6c43f0739b09ebcf5ba951a204051be456f39 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 14 Sep 2026 10:56:47 +0100 Subject: [PATCH 72/72] ci: update Waydroid workflow actions Use Node 24-compatible action majors before exposing the Android 16 host build on the default branch. Assisted-by: OpenAI Codex --- .github/workflows/build-waydroid-imx8mm-aesl.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml index 04eca969..0519e416 100644 --- a/.github/workflows/build-waydroid-imx8mm-aesl.yml +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -31,7 +31,7 @@ jobs: image: dynamicdevices/yocto-ci-build@sha256:be170373625e77a6235a0bb3efb84d00be736221782d5d716e88aafd1b965794 options: --privileged --volume /yocto:/yocto steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: submodules: recursive @@ -84,7 +84,7 @@ jobs: find "${deploy}" -maxdepth 1 -type f -name '*.manifest' -print -quit | grep -q . find "${deploy}" -maxdepth 1 -type f -name '*.spdx.tar.zst' -print -quit | grep -q . - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: aesl-waydroid-imx8mm-host-${{ github.run_id }}-${{ github.run_attempt }} path: /yocto/waydroid-host/tmp/deploy/images/imx8mm-jaguar-screen