diff --git a/.github/workflows/build-waydroid-imx8mm-aesl.yml b/.github/workflows/build-waydroid-imx8mm-aesl.yml new file mode 100644 index 00000000..0519e416 --- /dev/null +++ b/.github/workflows/build-waydroid-imx8mm-aesl.yml @@ -0,0 +1,92 @@ +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 + 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 + +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@v7 + with: + submodules: recursive + + - 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/*) ;; + *) 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" + 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}" + (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 . + find "${deploy}" -maxdepth 1 -type f -name '*.spdx.tar.zst' -print -quit | grep -q . + + - 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 + if-no-files-found: error + retention-days: 30 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" diff --git a/docs/product-feature-migration-validation.md b/docs/product-feature-migration-validation.md new file mode 100644 index 00000000..6529c106 --- /dev/null +++ b/docs/product-feature-migration-validation.md @@ -0,0 +1,89 @@ +# 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 + +Validated local rollout candidates (all worktrees clean when recorded): + +| Repository/worktree | Commit | +| --- | --- | +| `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` | + +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. +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. + +## 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. 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..95e44566 --- /dev/null +++ b/docs/releases/jaguar-screen-galcore-baseline-2026.09.04.md @@ -0,0 +1,50 @@ +# 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` +- Tagged source release build: Foundries target `2840` +- Target 2840 OSTree: `0008cdaca80b08c524d6db29df02c6c168382ea9e8de07814083380a04d36ea2` +- 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. + +## 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 `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. + +Do not describe target 2838 as an Etnaviv or accelerated-Waydroid target: it is +the deliberately retained galcore baseline. diff --git a/docs/waydroid-android16-build.md b/docs/waydroid-android16-build.md new file mode 100644 index 00000000..3b15a3ba --- /dev/null +++ b/docs/waydroid-android16-build.md @@ -0,0 +1,91 @@ +# 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. +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. + +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. 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 +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. + +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 +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/docs/waydroid-imx8mm-2gb-memory.md b/docs/waydroid-imx8mm-2gb-memory.md new file mode 100644 index 00000000..5def0bf1 --- /dev/null +++ b/docs/waydroid-imx8mm-2gb-memory.md @@ -0,0 +1,37 @@ +# 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. 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. + +`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 + 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/kas/base.yml b/kas/base.yml index 0f4bc79d..34077ef8 100644 --- a/kas/base.yml +++ b/kas/base.yml @@ -66,6 +66,16 @@ repos: commit: eeee54cfa3c51c1fd99604a0d5f173096bdcf1da path: build/layers/meta-tensorflow + meta-godot: + url: https://github.com/active-esl/meta-godot.git + commit: 5396659eed1cb6a0192a9928a35c8fc57f57d1c8 + 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 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/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/kas/qt-screen.yml b/kas/qt-screen.yml new file mode 100644 index 00000000..bf514df3 --- /dev/null +++ b/kas/qt-screen.yml @@ -0,0 +1,16 @@ +header: + version: 14 + includes: + - lmp-dynamicdevices-dev.yml + +machine: imx8mm-jaguar-screen +distro: lmp-dynamicdevices-headless + +local_conf_header: + qt-screen-prototype: | + QT_GIT_PROTOCOL = "https" + 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/kas/waydroid-imx8mm-aesl.yml b/kas/waydroid-imx8mm-aesl.yml new file mode 100644 index 00000000..49271468 --- /dev/null +++ b/kas/waydroid-imx8mm-aesl.yml @@ -0,0 +1,47 @@ +# 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 + + # 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 "" + 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/meta-dynamicdevices-bsp b/meta-dynamicdevices-bsp index b926d4e9..da622c47 160000 --- a/meta-dynamicdevices-bsp +++ b/meta-dynamicdevices-bsp @@ -1 +1 @@ -Subproject commit b926d4e96532fb95c83984b154d7b2f72d82471e +Subproject commit da622c47486b60b764b7b363d0ff59096b3e4bf8 diff --git a/meta-dynamicdevices-distro b/meta-dynamicdevices-distro index 9807961b..3e430ec1 160000 --- a/meta-dynamicdevices-distro +++ b/meta-dynamicdevices-distro @@ -1 +1 @@ -Subproject commit 9807961bf74bb6c20eb60c0cd388b91b69883a41 +Subproject commit 3e430ec1f36108d2b17505ae284fca3b050a7f80 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/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/python3-gbinder_git.bb b/recipes-support/waydroid/python3-gbinder_git.bb index 038e7997..93f2f570 100644 --- a/recipes-support/waydroid/python3-gbinder_git.bb +++ b/recipes-support/waydroid/python3-gbinder_git.bb @@ -7,12 +7,11 @@ 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" -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" @@ -26,4 +25,3 @@ SETUPTOOLS_BUILD_ARGS = "sdist --cython" inherit setuptools3 pkgconfig BBCLASSEXTEND = "native" - diff --git a/recipes-support/waydroid/waydroid-data.bb b/recipes-support/waydroid/waydroid-data.bb index 6f60249f..a3c010df 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,57 @@ 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_SYSTEM_NOTICE_SHA256 ?= "" +AESL_WAYDROID_VENDOR_NOTICE_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: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}" + +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 +89,37 @@ 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 +} + +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" + 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" } -FILES:${PN} += "/usr/share/waydroid-extra/images" +FILES:${PN} += "/usr/share/waydroid-extra/images /usr/share/waydroid-extra/evidence" pkg_postinst_ontarget:${PN} () { #!/bin/sh @@ -60,6 +129,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 diff --git a/recipes-support/waydroid/waydroid.bb b/recipes-support/waydroid/waydroid.bb index 759aed6c..1b3302e9 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,9 +21,28 @@ 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://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 \ + file://waydroid-luneos.sh \ file://waydroid-net.sh \ + 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 \ + file://waydroid-frame-headroom \ + file://waydroid-board-evidence \ + file://waydroid-acceleration-check \ + file://waydroid-v4l2-probe \ + file://waydroid-vsidaemon.service \ " S = "${WORKDIR}/git" @@ -40,21 +59,57 @@ 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 #inherit webos_app #inherit webos_filesystem_paths #inherit webos_systemd -inherit systemd +inherit features_check systemd + +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 = " 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 = " apparmor etnaviv" 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 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. + 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() { + 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. @@ -80,11 +135,34 @@ 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 + 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 + 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 +} + 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 } +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} \ 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..09c8a2b6 --- /dev/null +++ b/recipes-support/waydroid/waydroid/0001-lxc-limit-graphics-device-permissions.patch @@ -0,0 +1,141 @@ +From b800aa3663bb259d0f8445155d8cd01996e5eef9 Mon Sep 17 00:00:00 2001 +From: Active ESL +Date: Mon, 7 Sep 2026 16:26:44 +0100 +Subject: [PATCH] lxc: enforce exact graphics and video device policy + +--- + 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..7c781c3 100644 +--- a/tools/actions/container_manager.py ++++ b/tools/actions/container_manager.py +@@ -68,0 +69 @@ def set_permissions(args, perm_list=None, mode="777"): ++ use_default_list = perm_list is None +@@ -95,2 +95,0 @@ def set_permissions(args, perm_list=None, mode="777"): +- # DRM render nodes +- perm_list.extend(glob.glob("/dev/dri/renderD*")) +@@ -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/*")) +- +@@ -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, "660") ++ for path in helpers.lxc.get_dma_heap_devices(args): ++ chmod(path, "660") ++ for path in helpers.lxc.get_video_devices(args): ++ chmod(path, "660") ++ +diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py +index 1b6ff49..3dcc5cd 100644 +--- a/tools/helpers/lxc.py ++++ b/tools/helpers/lxc.py +@@ -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) ++ 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 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 ++ +@@ -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): +@@ -71 +130 @@ def generate_nodes_lxc_config(args): +- for n in glob.glob("/dev/dma_heap/*"): ++ for n in get_dma_heap_devices(args): +-- +2.43.0 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 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 diff --git a/recipes-support/waydroid/waydroid/waydroid-acceleration-check b/recipes-support/waydroid/waydroid/waydroid-acceleration-check new file mode 100644 index 00000000..3a224250 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-acceleration-check @@ -0,0 +1,218 @@ +#!/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} +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 + +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 + 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 + 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}" ' + { + 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) +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" +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#*=} + if [ "$(config_value "${key}")" = "${value}" ]; then + pass "${expected}" + else + fail "missing ${expected}" + fi +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 + 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) + 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) + 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' + 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 'AppArmor, exact device-rule, V4L2 daemon and DAC checks skipped for fixture devices' +else + 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 + 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 + 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' + 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 + 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 + 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 new file mode 100644 index 00000000..956526ae --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-board-evidence @@ -0,0 +1,133 @@ +#!/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 + /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 -- ./*) \ + > "${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 -Eq 'Container:[[:space:]]+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) + 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")" \ + > "${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}" 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-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/recipes-support/waydroid/waydroid/waydroid-image-provision b/recipes-support/waydroid/waydroid/waydroid-image-provision new file mode 100644 index 00000000..2867e6e0 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision @@ -0,0 +1,158 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +set -eu + +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} +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= +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 + +if [ -z "${render_node}" ]; then + echo "No Etnaviv DRM render node is available; refusing GPU fallback" >&2 + 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 + +video_devices= +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 H.264-capable NXP vsiv4l2 decoder is available; refusing unrelated V4L2 nodes" >&2 + exit 1 +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. + 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' +import configparser +import os +import sys + +path, render_node, dma_heap_devices, video_devices = 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) +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") +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/recipes-support/waydroid/waydroid/waydroid-image-provision.service b/recipes-support/waydroid/waydroid/waydroid-image-provision.service new file mode 100644 index 00000000..c4ba8798 --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-image-provision.service @@ -0,0 +1,18 @@ +[Unit] +Description=Provision Waydroid Android images into persistent storage +Wants=network-online.target +Requires=apparmor.service waydroid-vsidaemon.service +After=network-online.target dbus.service apparmor.service waydroid-vsidaemon.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 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 diff --git a/recipes-support/waydroid/waydroid/waydroid-memory-headroom b/recipes-support/waydroid/waydroid/waydroid-memory-headroom new file mode 100644 index 00000000..05ed1baa --- /dev/null +++ b/recipes-support/waydroid/waydroid/waydroid-memory-headroom @@ -0,0 +1,100 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only +set -eu + +cgroup=/sys/fs/cgroup/system.slice/waydroid-container.service + +echo '[memory]' +awk '/^(MemTotal|MemAvailable|SwapTotal|SwapFree|CmaTotal|CmaFree):/ { print }' /proc/meminfo + +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}" + fi +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 '[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 + 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/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("/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 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()) diff --git a/scripts/validation/waydroid-gpu-provision.sh b/scripts/validation/waydroid-gpu-provision.sh new file mode 100755 index 00000000..fba959ad --- /dev/null +++ b/scripts/validation/waydroid-gpu-provision.sh @@ -0,0 +1,132 @@ +#!/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}/sys/class/video4linux/video2" \ + "${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 'vsi_v4l2dec\n' > "${test_root}/sys/class/video4linux/video2/name" +printf 'video\n' > "${test_root}/dev/video2" +printf '[waydroid]\narch = arm64\nimages_path = %s/images\n\n[properties]\nro.hardware.vulkan = lvp\n' \ + "${test_root}" > "${test_root}/waydroid.cfg" + +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}" +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 + +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' \ + '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' \ + ' 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 "${WAYDROID_TEST_RENDERER:-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" + +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 \ + 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" + +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 +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'