diff --git a/tools/aurora-sep/recipes/mesa-m3/PKGBUILD b/tools/aurora-sep/recipes/mesa-m3/PKGBUILD index 2c80497680e0af..d00a5bffee226a 100644 --- a/tools/aurora-sep/recipes/mesa-m3/PKGBUILD +++ b/tools/aurora-sep/recipes/mesa-m3/PKGBUILD @@ -5,12 +5,14 @@ # the exact j613-25g83-hal200 token, firmware26.6.2 handoff and runtime HAL200/USC3. # The J615 shares that profile experimentally: the session hook admits it only # with /chosen asahi,j615-25g83-experimental="1" (share/mesa-m3/native25-boards). +# Where /opt/mesa-m3 is not used, Hyprland sessions on J613, J615 and J516S keep +# a system Mesa in /usr that mesa-m3-system-check admits, not software rendering. # The package builds both sources sequentially and checks compiler definitions, # generated kernels, queue sandbox behavior, TLS loading and native MRT clears. # No profile selection substitutes another Mac's firmware or calibration. pkgname=mesa-m3 pkgver=26.1.4.m3.2 -pkgrel=8 +pkgrel=9 _commit=7ff89a4da387bd561b78efd5f09c98ca8ae18538 pkgdesc="Mesa in /opt/mesa-m3 for the M3 Pro (G15S) and M3 (G15G) GPUs; used by uwsm sessions on T6030, and on T8122 when opted in" arch=('aarch64') @@ -85,7 +87,7 @@ sha256sums=('7ed24969985dce91f089d34dbe1c55cbcb8c95765e3e5bc0f268d27fbead2222' '10abda78df16e650a77443f50cc46a15e418c42d4d9680c6a61616b232c9bdef' '6c98fab6f9d70449bd7f5258c34eac773836908e060ce5bef86dc5b0ce595e61' '826016665cd0424a614b67add921c0c79c3c698a4d104ea5712518449b75f27c' - '9cbc38f4bee27ab72d4a288f2741ab1eeb70112ca473a5c98a9695cdfcbe56f7' + '8abf5fcf736b24dad81ffe6a10e137ded8727c004f17d9d723c27e04141b717f' '6144af8b2e0f04ea615e8c4149e69c45ac49e630c46b2a58f0d1095d977798b2' '530948fc1a124de21c030a497e2ef5351742ba4ad4653484fe30fd1697ad7141' '644cf4a5ab57b7badec75843e2899b68d41490c4851d6885227180d6090c25ea' @@ -94,10 +96,10 @@ sha256sums=('7ed24969985dce91f089d34dbe1c55cbcb8c95765e3e5bc0f268d27fbead2222' '04fae9bf32a09a574b6ed9ab9d7afb97340df252b9269ed789303e8561a61a21' '96c3101c15c73d26cd3086bc8ac4e8eb35d42300d495ca0ffd2bd03960ef3332' '9c7c3050d3ec15fce80a6e59d099567b396f1844a43695aa4377e02ac696f566' - '2a15cfdbc160b78e5a3a8e99d5898d40a54db28592f1a4ce04a600990a731f87' + '17a3c74a34681cb202767de73cfbe62c3bcbb93a16093cd058eaa955cd8776ea' '7eaae7de828e15208fccc84db2b910f38bb1288176419f7af7f4675b5c542ce4' 'c888e9dce99e8379aa21e9c5895cbe1b7e879a9319bca144193eddc66f9d4051' - 'e80947130044c2dc3671c24c7bff4f523dcc033f7e96f71e334d89a663e2742e') + '6ea402187cd93305315e7a0fae0012eaead15083916985b1e32ee1cc6cf9469c') # The qualified M3 Pro build's generated kernel files and libclc. build() stops # when one of ours differs. libagx_shaders.c is compared through kernel-digest.py: diff --git a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-confirm b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-confirm index c54676d8f84eb9..2360287ac5c80c 100755 --- a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-confirm +++ b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-confirm @@ -1,8 +1,9 @@ #!/bin/sh # SPDX-License-Identifier: MIT # mesa-m3-session-confirm [--settle SECONDS]: write the success marker of this -# boot's login with /opt/mesa-m3, but only on live evidence that its -# compositor is up and serving. Run by mesa-m3-session-confirm.service after +# boot's login with /opt/mesa-m3 (or with the system Mesa that the session hook +# admitted automatically), but only on live evidence that its compositor is up +# and serving. Run by mesa-m3-session-confirm.service after # graphical-session.target (reaching the target alone proves nothing). # # The attempt (${XDG_STATE_HOME:-~/.local/state}/mesa-m3/attempt, written by diff --git a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-env b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-env index a90c3b5d2bc143..c5ec443d47a23b 100644 --- a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-env +++ b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-session-env @@ -49,9 +49,10 @@ # search failed) # gpu-fault the kernel log shows an M3 GPU or display fault this boot # (on a T8122 also a failed first-job verdict of its start) -# previous-failed a login with /opt/mesa-m3 earlier this boot has no success -# marker: its compositor was not seen alive and answering -# on its Wayland socket after the settle time (see +# previous-failed a login with /opt/mesa-m3 (or with the system Mesa admitted +# automatically) earlier this boot has no success marker: +# its compositor was not seen alive and answering on its +# Wayland socket after the settle time (see # mesa-m3-session-confirm.service). The attempt and the # marker are in ${XDG_STATE_HOME:-~/.local/state}/mesa-m3, # which a logout does not clear (/run/user/ can go @@ -60,20 +61,38 @@ # M1, M2, the M3 Max and anything else are left alone. # # When it does not use /opt/mesa-m3 but an M3 render node is there and this -# user can open it, the system Mesa would be refused by the kernel (it is not -# M3-aware), and a compositor that renders through an EGL device on that node +# user can open it, a system Mesa that is not M3-aware would be refused by the +# kernel, and a compositor that renders through an EGL device on that node # (Hyprland) would stop at start. For every inactive reason except user-setup # it therefore exports LIBGL_ALWAYS_SOFTWARE=1 (and MESA_M3_FALLBACK=software, # which marks it as this hook's): the session renders in software. For # user-setup it exports nothing: the session's own Mesa setup, which it uses, # decides. The state file says which (fallback=software|none). # -# A file opt-out (/etc/mesa-m3/disable or the user's mesa-m3/disable) on a -# J613 (T8122) or a J516S (T6030) is the exception: when the same node, -# display, kernel-log, user-setup and same-boot attempt checks pass and -# mesa-m3-system-check admits /usr, the session keeps system Mesa -# (fallback=none, no new attempt). The whole check runs in its own sh and is -# refused after 20 seconds. mesa_m3=off stays on the software fallback. +# A system Mesa on /usr that drives the GPU natively is the exception, on a +# J613 or J615 (T8122, the 13- and 15-inch M3 Air) or a J516S (T6030) in a +# Hyprland session. It is checked only where the gate above ends inactive, +# except not-supported and user-setup (the session's own Mesa setup still +# decides): an active /opt/mesa-m3 is never replaced. When the node, display, +# kernel-log, user-setup (--system-provider) and same-boot attempt checks pass +# and mesa-m3-system-check admits /usr, the session keeps the system Mesa +# (fallback=none) instead of the software fallback. The whole check runs in +# its own sh and is refused after 20 seconds. mesa_m3=off and nomodeset refuse +# it, and any refusal leaves the gate's decision and its fallback exactly as +# above. Its state: +# system-mesa no opt-out: the system Mesa was admitted automatically. +# Its attempt is recorded first, as for active, so a login +# whose compositor is not confirmed (mesa-m3-session-confirm) +# turns the next login of this boot into the software +# fallback instead of a login loop. When the attempt +# cannot be written, the gate's decision and fallback +# stay, and the detail says the attempt was not recorded. +# opt-out a file opt-out (/etc/mesa-m3/disable or the user's +# mesa-m3/disable) whose system Mesa was admitted: as +# before, no new attempt. +# Without a file opt-out, the 25G83 native profile stays on its own gate: an +# /etc/mesa-m3/t8122-profile selection, or a node with the +# apple,j613-25g83-gpu-handoff property, is not admitted automatically. # # Each login writes /run/user//mesa-m3-session.state (atomically, 0644, # key=value lines, first line schema=aurora.mesa-m3-session/2) and logs one @@ -255,13 +274,14 @@ mesa_m3_gate() { # opt-out uses before it will leave the system Mesa in place. _mm3_klog=$(_mm3_kernel_log) || { echo "$_mm3_klog"; return 1; } - # A login that chose /opt/mesa-m3 earlier this boot must have a success - # marker bound to it (same boot_id, session_id and hook_run_id), written by - # mesa-m3-session-confirm.service only after its compositor was alive and - # answering on its Wayland socket after the settle time. Otherwise this + # A login that chose /opt/mesa-m3 (or the system Mesa automatically) earlier + # this boot must have a success marker bound to it (same boot_id, + # session_id and hook_run_id), written by mesa-m3-session-confirm.service + # only after its compositor was alive and answering on its Wayland socket + # after the settle time. Otherwise this # login stays off it: no login loop when the compositor cannot run on it. if ! _mm3_legacy_attempt_clear "$_mm3_root"; then - echo "previous-failed the last login with /opt/mesa-m3 this boot (hook_run_id ${_mm3_v:-unknown}) has no success marker: its compositor was not seen alive and answering, so this login renders in software; to try the GPU again: rm $_mm3_att, then log out and in (or reboot)" + echo "previous-failed the last GPU login this boot (hook_run_id ${_mm3_v:-unknown}) has no success marker: its compositor was not seen alive and answering, so this login renders in software; to try the GPU again: rm $_mm3_att, then log out and in (or reboot)" return 1 fi @@ -361,9 +381,9 @@ _mm3_kernel_log() { return 0 } -# Return 0 when this boot has no unmatched /opt/mesa-m3 attempt. Return 1 -# when an attempt for this boot_id has no success marker bound to its -# hook_run_id and session_id. Never creates, deletes or rewrites either file. +# Return 0 when this boot has no unmatched attempt (of /opt/mesa-m3 or of an +# automatic system Mesa). Return 1 when an attempt for this boot_id has no +# success marker bound to its hook_run_id and session_id. Never creates, deletes or rewrites either file. # On failure, _mm3_att and _mm3_v are the attempt path and its hook_run_id. _mm3_legacy_attempt_clear() { _mm3_att="$1${XDG_STATE_HOME:-$HOME/.local/state}/mesa-m3/attempt" @@ -385,27 +405,33 @@ _mm3_legacy_attempt_clear() { return 0 } -# File opt-out on J613/T8122 or J516S/T6030 may keep the system Mesa. Prints -# one detail line and returns 0 only when every check below passes. A helper -# exit of 0 is not enough on its own. Does not write an attempt or touch a -# marker. mesa_m3=off is not a file opt-out. +# The system Mesa on J613 or J615 (T8122) or J516S (T6030), automatically or +# after a file opt-out. Prints one detail line and returns 0 only when every +# check below passes. A helper exit of 0 is not enough on its own. Does not +# write an attempt or touch a marker (the caller records an automatic one). +# mesa_m3=off and nomodeset refuse it; mesa_m3=off is not a file opt-out. _mm3_system_admit() { case ${XDG_CURRENT_DESKTOP:-} in Hyprland|hyprland) ;; *) return 1 ;; esac _mm3_cmdline=$(cat "$1/proc/cmdline" 2>/dev/null) || return 1 case " $_mm3_cmdline " in *" mesa_m3=off "*|*" nomodeset "*) return 1 ;; esac + _mm3_auto=0 if [ ! -e "$1/etc/mesa-m3/disable" ] && [ ! -e "$1${XDG_CONFIG_HOME:-$HOME/.config}/mesa-m3/disable" ]; then - return 1 + # Automatic: an explicit 25G83 native profile selection keeps its gate. + _mm3_auto=1 + [ ! -e "$1/etc/mesa-m3/t8122-profile" ] || return 1 fi _mm3_dt=$(tr '\0' '\n' 2>/dev/null < "$1/proc/device-tree/compatible") _mm3_soc=$(_mm3_soc_of "$1") _mm3_chip= _mm3_identity=$(printf '%s\n' "$_mm3_dt" | grep -E '^apple,j') _mm3_socs=$(printf '%s\n' "$_mm3_dt" | grep -E '^apple,t') + # Exactly one board and one SoC compatible. The 13-inch (J613) and 15-inch + # (J615) M3 Air pass the same T8122 checks; mesa-m3-system-check repeats them. case "$_mm3_soc:$_mm3_identity:$_mm3_socs" in - t8122:apple,j613:apple,t8122) _mm3_chip=t8122 ;; + t8122:apple,j613:apple,t8122 | t8122:apple,j615:apple,t8122) _mm3_chip=t8122 ;; t6030:apple,j516s:apple,t6030) _mm3_chip=t6030 ;; *) return 1 ;; esac @@ -422,6 +448,10 @@ _mm3_system_admit() { _mm3_drv=$(basename "$(readlink -f "$_mm3_node/device/driver" 2>/dev/null)" 2>/dev/null) [ "$_mm3_drv" = asahi ] || return 1 tr '\0' '\n' 2>/dev/null < "$_mm3_node/device/of_node/compatible" | grep -qx "apple,agx-$_mm3_chip" || return 1 + # A node handed off for the 25G83 firmware belongs to the native profile. + if [ "$_mm3_auto" -eq 1 ] && [ -e "$_mm3_node/device/of_node/apple,j613-25g83-gpu-handoff" ]; then + return 1 + fi _mm3_dev="$1/dev/dri/${_mm3_node##*/}" [ -r "$_mm3_dev" ] && [ -w "$_mm3_dev" ] || return 1 _mm3_dcp=$(_mm3_native_display "$1" "$_mm3_chip") || return 1 @@ -448,7 +478,7 @@ _mm3_system_admit() { timeout -k 2 5 env -i PATH=/usr/bin:/bin "$_mm3_helper" \ "/dev/dri/${_mm3_node##*/}" "$_mm3_chip" >/dev/null 2>&1 [ "$?" -eq 0 ] || return 1 - printf '%s\n' "system Mesa on /usr admits /dev/dri/${_mm3_node##*/} ($_mm3_chip); /opt/mesa-m3 stays inactive" + printf '%s\n' "system Mesa on /usr admits /dev/dri/${_mm3_node##*/} ($_mm3_chip, $_mm3_identity); /opt/mesa-m3 stays inactive" } # _mm3_system_admit in its own sh, under one timeout for the whole check. A @@ -461,18 +491,24 @@ _mm3_system_admit_bounded() { sh "$_mm3_self" "${1:-}" "$_mm3_p" } -# _mm3_state ROOT DECISION REASON DETAIL FALLBACK: write the state file and the journal -# line. A value holding a newline or carriage return is refused. -_mm3_state() { +# _mm3_login_ids ROOT: this login's uid, session id and boot id, as the state +# file and the attempt record them. +_mm3_login_ids() { _mm3_uid=$(id -u) _mm3_sid=${XDG_SESSION_ID:-} [ -n "$_mm3_sid" ] || _mm3_sid=$(loginctl show-user "$_mm3_uid" -p Display --value 2>/dev/null) + _mm3_boot= + [ -r "$1/proc/sys/kernel/random/boot_id" ] && IFS= read -r _mm3_boot < "$1/proc/sys/kernel/random/boot_id" +} + +# _mm3_state ROOT DECISION REASON DETAIL FALLBACK: write the state file and the journal +# line. A value holding a newline or carriage return is refused. +_mm3_state() { + _mm3_login_ids "$1" _mm3_inv=$(systemctl show -p InvocationID --value "user@$_mm3_uid.service" 2>/dev/null) _mm3_ver= [ -r "$1$_mm3_p/share/mesa-m3/package-version" ] && IFS= read -r _mm3_ver < "$1$_mm3_p/share/mesa-m3/package-version" - _mm3_boot= - [ -r "$1/proc/sys/kernel/random/boot_id" ] && IFS= read -r _mm3_boot < "$1/proc/sys/kernel/random/boot_id" _mm3_cr=$(printf '\r') _mm3_nl=' ' @@ -501,7 +537,8 @@ _mm3_state() { return 1 } -# _mm3_attempt ROOT: record that this login uses /opt/mesa-m3, in +# _mm3_attempt ROOT: record that this login uses /opt/mesa-m3 (or the system +# Mesa admitted automatically), in # ${XDG_STATE_HOME:-~/.local/state}/mesa-m3/attempt (kept across a logout; a # later boot's gate ignores it by boot_id). It counts as failed until # mesa-m3-session-confirm.service writes a success marker beside it with the @@ -533,7 +570,47 @@ mesa_m3_session_env() { [ -r /proc/sys/kernel/random/uuid ] && IFS= read -r _mm3_run < /proc/sys/kernel/random/uuid _mm3_run=${_mm3_run:-unknown} [ "${_mm3_decision%% *}" = not-supported ] || export MESA_M3_HOOK_RUN_ID="$_mm3_run" - if [ "$_mm3_on" -eq 0 ]; then + # The system Mesa only where the gate ends inactive (see the header): never + # instead of an active /opt/mesa-m3, and not where this is neither a T6030 + # nor a T8122 or the session's own Mesa setup chose. A refusal leaves the + # gate's decision and fallback as they were. + _mm3_admitted=0 + _mm3_extra= + if [ "$_mm3_on" -ne 0 ]; then + case ${_mm3_decision%% *} in + not-supported | user-setup) ;; + *) + _mm3_sys=$(_mm3_system_admit_bounded "${1:-}") + [ "$?" -ne 0 ] || _mm3_admitted=1 ;; + esac + fi + # Automatic: the attempt comes first. Without it no unconfirmed login could + # send the next one to software, so a failed write keeps the fallback. + if [ "$_mm3_admitted" -eq 1 ] && [ "${_mm3_decision%% *}" != opt-out ]; then + _mm3_login_ids "${1:-}" + if ! _mm3_attempt "${1:-}"; then + _mm3_admitted=0 + _mm3_extra="; system Mesa admitted, but its attempt not recorded in ${XDG_STATE_HOME:-$HOME/.local/state}/mesa-m3, so this login keeps the fallback" + fi + fi + if [ "$_mm3_admitted" -eq 1 ]; then + # Only the pair this hook itself exports. Anything else in the + # environment, including a user's own LIBGL_ALWAYS_SOFTWARE without + # this marker, stays. + if [ "${LIBGL_ALWAYS_SOFTWARE:-}" = 1 ] && [ "${MESA_M3_FALLBACK:-}" = software ]; then + unset LIBGL_ALWAYS_SOFTWARE + unset MESA_M3_FALLBACK + fi + if [ "${_mm3_decision%% *}" = opt-out ]; then + # A file opt-out: as before, no new attempt. + _mm3_state "${1:-}" inactive opt-out "${_mm3_decision#* }; $_mm3_sys" none + else + # Automatic: its attempt is already recorded, so a compositor that + # is not confirmed sends the next login of this boot to the + # software fallback instead of looping. + _mm3_state "${1:-}" inactive system-mesa "$_mm3_sys; the /opt/mesa-m3 gate said: $_mm3_decision" none + fi + elif [ "$_mm3_on" -eq 0 ]; then case ":${LD_LIBRARY_PATH:-}:" in *":$_mm3_p/lib:"*) ;; *) export LD_LIBRARY_PATH="$_mm3_p/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ;; @@ -562,31 +639,14 @@ mesa_m3_session_env() { _mm3_attempt "${1:-}" else # Never the system Mesa on an M3 render node it can open (Hyprland - # would stop at start), except where this session's own setup chose, - # or a file opt-out whose system-Mesa handoff passed. + # would stop at start), except where this session's own setup chose. _mm3_fb=none - _mm3_detail=${_mm3_decision#* } - _mm3_admitted=0 - if [ "${_mm3_decision%% *}" = opt-out ]; then - _mm3_sys=$(_mm3_system_admit_bounded "${1:-}") - if [ "$?" -eq 0 ]; then - _mm3_admitted=1 - _mm3_detail="$_mm3_detail; $_mm3_sys" - # Only the pair this hook itself exports. Anything else in the - # environment, including a user's own LIBGL_ALWAYS_SOFTWARE - # without this marker, stays. - if [ "${LIBGL_ALWAYS_SOFTWARE:-}" = 1 ] && [ "${MESA_M3_FALLBACK:-}" = software ]; then - unset LIBGL_ALWAYS_SOFTWARE - unset MESA_M3_FALLBACK - fi - fi - fi - if [ "$_mm3_admitted" -eq 0 ] && [ "${_mm3_decision%% *}" != user-setup ] && _mm3_node_open "${1:-}"; then + if [ "${_mm3_decision%% *}" != user-setup ] && _mm3_node_open "${1:-}"; then export LIBGL_ALWAYS_SOFTWARE=1 export MESA_M3_FALLBACK=software _mm3_fb=software fi - _mm3_state "${1:-}" inactive "${_mm3_decision%% *}" "$_mm3_detail" "$_mm3_fb" + _mm3_state "${1:-}" inactive "${_mm3_decision%% *}" "${_mm3_decision#* }$_mm3_extra" "$_mm3_fb" fi } @@ -594,7 +654,7 @@ if [ -z "${MESA_M3_SESSION_ENV_NO_RUN:-}" ]; then mesa_m3_session_env unset -f mesa_m3_gate mesa_m3_session_env _mm3_init _mm3_state _mm3_node_open _mm3_attempt \ _mm3_soc_of _mm3_25g83_board _mm3_native_display _mm3_kernel_log \ - _mm3_legacy_attempt_clear _mm3_system_admit _mm3_system_admit_bounded + _mm3_legacy_attempt_clear _mm3_system_admit _mm3_system_admit_bounded _mm3_login_ids fi unset _mm3_root _mm3_p _mm3_nodes _mm3_node _mm3_n _mm3_drv _mm3_dcp _mm3_c _mm3_dev _mm3_f _mm3_v \ _mm3_val _mm3_ifs _mm3_d _mm3_out _mm3_rc _mm3_decision _mm3_on \ @@ -602,4 +662,4 @@ unset _mm3_root _mm3_p _mm3_nodes _mm3_node _mm3_n _mm3_drv _mm3_dcp _mm3_c _mm3 _mm3_body _mm3_kv _mm3_dir _mm3_tmp _mm3_fb _mm3_run _mm3_att _mm3_ok _mm3_faults _mm3_ign _mm3_igd \ _mm3_soc _mm3_name _mm3_dt _mm3_base _mm3_profile _mm3_requested _mm3_files _mm3_gate_ifs _mm3_board _mm3_note \ _mm3_klog _mm3_sys _mm3_admitted _mm3_detail _mm3_helper _mm3_chip _mm3_identity _mm3_socs _mm3_cmdline \ - _mm3_self + _mm3_self _mm3_auto _mm3_extra diff --git a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-system-check.c b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-system-check.c index 8f6177750196d5..a80c435cc0808b 100644 --- a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-system-check.c +++ b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-system-check.c @@ -15,12 +15,24 @@ #include #include +/* The boards whose system Mesa the session hook may admit: the 13-inch (J613) + * and 15-inch (J615) M3 Air on the T8122, the J516S on the T6030. + */ +static bool +board_name_matches(const char *value, const char *soc) +{ + if (!strcmp(soc, "t8122")) + return !strcmp(value, "apple,j613") || !strcmp(value, "apple,j615"); + if (!strcmp(soc, "t6030")) + return !strcmp(value, "apple,j516s"); + return false; +} + static bool board_compat_matches(const char *bytes, size_t length, const char *soc) { - const char *board = !strcmp(soc, "t8122") ? "apple,j613" : - !strcmp(soc, "t6030") ? "apple,j516s" : NULL; - if (!board || !length || bytes[length - 1] != '\0') + if ((strcmp(soc, "t8122") && strcmp(soc, "t6030")) || + !length || bytes[length - 1] != '\0') return false; unsigned boards = 0, socs = 0; size_t offset = 0; @@ -31,7 +43,7 @@ board_compat_matches(const char *bytes, size_t length, const char *soc) return false; const char *value = bytes + offset; if (!strncmp(value, "apple,j", 7)) { - if (strcmp(value, board)) + if (!board_name_matches(value, soc)) return false; boards++; } else if (!strncmp(value, "apple,t", 7)) { diff --git a/tools/aurora-sep/recipes/mesa-m3/mesa-m3.install b/tools/aurora-sep/recipes/mesa-m3/mesa-m3.install index a19e70799264e0..f658e212a3d0ed 100644 --- a/tools/aurora-sep/recipes/mesa-m3/mesa-m3.install +++ b/tools/aurora-sep/recipes/mesa-m3/mesa-m3.install @@ -9,6 +9,9 @@ post_install() { echo "mesa-m3: takes effect at the next login to a uwsm session on an M3 Pro (T6030)," echo "mesa-m3: and on an M3 (T8122) only with /etc/mesa-m3/t8122-gpu-experiment: its GPU" echo "mesa-m3: is experimental (the installer writes the file for --m3-gpu-experiment)." + echo "mesa-m3: Where /opt/mesa-m3 is not used, a Hyprland session on a 13- or 15-inch" + echo "mesa-m3: M3 Air (J613, J615) or a J516S keeps a system Mesa in /usr that drives" + echo "mesa-m3: the GPU, instead of rendering in software; no marker file is needed." echo "mesa-m3: It does nothing on other machines. Each login's decision is in" echo "mesa-m3: /run/user//mesa-m3-session.state and the journal (tag mesa-m3)." _render_note diff --git a/tools/aurora-sep/recipes/mesa-m3/tests/SYSTEM-HANDOFF.md b/tools/aurora-sep/recipes/mesa-m3/tests/SYSTEM-HANDOFF.md index c5b7c978049197..fdb6f87d6b7f83 100644 --- a/tools/aurora-sep/recipes/mesa-m3/tests/SYSTEM-HANDOFF.md +++ b/tools/aurora-sep/recipes/mesa-m3/tests/SYSTEM-HANDOFF.md @@ -26,20 +26,54 @@ scanner refuses instead of holding the login. The helper keeps its own 5-second timeout. A failed check keeps the existing software fallback. `mesa_m3=off` and -`nomodeset` remain recovery choices. Other boards, including J615, remain -outside this system handoff. Explicit legacy profile selection is unchanged. -No opt-in flags, attempt records or success markers are added or removed. +`nomodeset` remain recovery choices. Other boards remain outside this system +handoff. Explicit legacy profile selection is unchanged. Only the exact software-fallback pair previously exported by this hook can be cleared. The state remains `inactive`, `reason=opt-out`, `fallback=none` when system Mesa admission succeeds. +## J615 and automatic selection + +The 15-inch Air (J615, T8122) is admitted with the same checks as the J613, in +the hook and in `mesa-m3-system-check`. A root that names both Airs, or one of +them twice, is refused. + +The file opt-out is no longer required. In a Hyprland session on J613, J615 +or J516S, the hook runs the same bounded admission where its `/opt/mesa-m3` +gate ends inactive, except `not-supported` and `user-setup` (the session's +own Mesa setup still decides). An active `/opt/mesa-m3` is never replaced: +moving those sessions to system Mesa would need a maintainer opt-in. When +admission passes, the session keeps system Mesa instead of the software +fallback. The state is `inactive`, `reason=system-mesa`, `fallback=none`; its +detail names the admitted board and the gate's own decision. Any refusal +leaves the gate's decision and fallback exactly as before. + +An automatic admission records its attempt first, as an `/opt/mesa-m3` login +does. `mesa-m3-session-confirm` confirms it in the same way, and an +unconfirmed login sends the next login of that boot to the software fallback +instead of a login loop. When the attempt cannot be written, the admission +is dropped: the gate's decision and fallback stay, and the detail says the +attempt was not recorded. The file opt-out path still writes no attempt. + +Without a file opt-out, the 25G83 native profile stays on its own gate: an +`/etc/mesa-m3/t8122-profile` selection, or a render node with +`apple,j613-25g83-gpu-handoff`, is not admitted automatically. + The helper proves only that the system EGL device and a context open. It does -not allocate GBM buffers, render or present. A system-Mesa login that passes -admission and then fails later is not recorded: this path writes no attempt, -so the legacy success-marker check cannot see it, and the next login is -admitted again. Recover with `mesa_m3=off` on the kernel command line or by -removing the opt-out file. Separate system-Mesa attempt and success markers -would make that recovery automatic. +not allocate GBM buffers, render or present. + +Recovery after a system-Mesa login that passes admission and then fails: + +- Automatic (`reason=system-mesa`): the unconfirmed attempt sends the next + login of the same boot to software. To retry the GPU in that boot, remove + `${XDG_STATE_HOME:-~/.local/state}/mesa-m3/attempt` and log in again; a + reboot makes the previous attempt inapplicable. To stay in software, boot + with `mesa_m3=off`. +- File opt-out (`reason=opt-out`): this path writes no attempt, so the + success-marker check cannot see the failure and the next login is admitted + again. To force software rendering immediately, boot with `mesa_m3=off`. + Removing the opt-out file restores normal selection, which may retry the + system Mesa automatically. Run the host checks from this directory (a writable TMPDIR may be needed): diff --git a/tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh b/tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh index f10616501ea707..c77df77f6afe52 100755 --- a/tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh +++ b/tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh @@ -676,6 +676,7 @@ rm "$J516S/etc/mesa-m3/t8122-profile" system_root() { case "$2" in air) _hd="apple,j613 apple,t8122"; _hg=$G15G ;; + air15) _hd="apple,j615 apple,t8122"; _hg=$G15G ;; *) _hd="apple,j516s apple,t6030"; _hg=$G15S ;; esac _hr=$(mkroot "handoff-$1" "$_hd" "$_hg" yes 1 yes) @@ -741,11 +742,21 @@ rm "$HRA/etc/mesa-m3/disable" mkdir -p "$HRA$T/home/.config/mesa-m3" : > "$HRA$T/home/.config/mesa-m3/disable" handoff "$HRA" none yes +if grep -q 'detail=.*admits /dev/dri/renderD128 (t8122, apple,j613)' "$HRA/run/user/$UID_/mesa-m3-session.state"; then + echo 'ok handoff: the state names the admitted board' +else echo 'FAIL handoff: board in the state detail'; fail=1; fi if [ "$(cat "$HRA/opt/mesa-m3/libexec/system-check.args")" = "$(printf '/dev/dri/renderD128\nt8122')" ] && grep -qx 'PATH=/usr/bin:/bin' "$HRA/opt/mesa-m3/libexec/system-check.env" && [ -z "$(grep -vE '^(PATH=/usr/bin:/bin|PWD=.*|SHLVL=[0-9]+|_=.*)$' "$HRA/opt/mesa-m3/libexec/system-check.env")" ]; then echo 'ok handoff: helper receives exact device/SoC and empty selector environment' else echo 'FAIL handoff: helper argv/environment'; fail=1; fi +# The 15-inch Air (J615) passes the same file opt-out handoff as the J613. +HRJ=$(system_root optout-j615 air15) +handoff "$HRJ" none yes +if [ "$(cat "$HRJ/opt/mesa-m3/libexec/system-check.args")" = "$(printf '/dev/dri/renderD128\nt8122')" ] && + grep -q 'detail=.*admits /dev/dri/renderD128 (t8122, apple,j615)' "$HRJ/run/user/$UID_/mesa-m3-session.state"; then + echo 'ok handoff: J615 helper receives the T8122 node and the state names apple,j615' +else echo 'FAIL handoff: J615 helper argv or state'; fail=1; fi HRP=$(system_root root-optout pro) handoff "$HRP" none yes handoff "$HRP" none yes LIBGL_ALWAYS_SOFTWARE=1 MESA_M3_FALLBACK=software @@ -775,7 +786,7 @@ for journal in noperm error match bank dcp g15g verdict; do HR=$(system_root "journal-$journal" pro) handoff "$HR" software no FAKE_JOURNAL="$journal" done -for dt in 'apple,j615 apple,t8122' 'apple,j514s apple,t6030' \ +for dt in 'apple,j615 apple,t6030' 'apple,j514s apple,t6030' \ 'apple,j614s apple,t6030' 'apple,j613 apple,j615 apple,t8122' \ 'apple,j613 apple,t6030' 'apple,j516s apple,t6030 apple,t8122'; do HR=$(system_root "board-$(printf '%s' "$dt" | tr ' ,' '--')" pro) @@ -785,6 +796,15 @@ for dt in 'apple,j615 apple,t8122' 'apple,j514s apple,t6030' \ case "$dt" in *t8122*) nul apple,agx-t8122 apple,agx-t6030 > "$HR/sys/devices/platform/gpu0/of_node/compatible" ;; esac handoff "$HR" software no done +# On a T8122 GPU and display, any board but the two Airs is refused, and so +# is a root naming both of them or one twice. +for dt in 'apple,j504 apple,t8122' 'apple,j615s apple,t8122' 'apple,j516s apple,t8122' \ + 'apple,j615 apple,j613 apple,t8122' 'apple,j615 apple,j615 apple,t8122' \ + 'apple,j615 apple,t8122 apple,t8122'; do + HR=$(system_root "air-board-$(printf '%s' "$dt" | tr ' ,' '--')" air15) + nul $dt > "$HR/proc/device-tree/compatible" + handoff "$HR" software no +done HR=$(system_root no-display pro) rm "$HR/sys/class/drm/card2/device" handoff "$HR" software no @@ -845,5 +865,197 @@ rm "$HOK" sed -i 's/boot_id=boot-a/boot_id=old-boot/' "$HATT" handoff "$HR" none yes +# Automatic system Mesa: no opt-out file. On J613, J615 and J516S a Hyprland +# session whose /opt/mesa-m3 gate ends inactive keeps the system Mesa when +# every check passes (reason system-mesa, fallback none, an attempt recorded +# first for the loop guard). An active gate is never replaced. Every refusal +# leaves the gate's decision and fallback. +auto_root() { # NAME air|air15|pro + _ar=$(system_root "auto-$1" "$2") + rm "$_ar/etc/mesa-m3/disable" + echo "$_ar" +} +# auto_run ROOT [VAR=value...]: one hook run under $TSH; prints +# "DECISION REASON FALLBACK helper=yes|no attempt=new|unchanged|none sw=.. legacy=.."; +# the run's exports and state are left in $T/auto.out. +auto_run() { + _ar=$1; shift + rm -f "$_ar/opt/mesa-m3/libexec/system-check.args" "$_ar/run/user/$UID_/mesa-m3-session.state" "$T/log" + _aa="$_ar$T/home/.local/state/mesa-m3/attempt" + _ab=none + [ ! -f "$_aa" ] || _ab=$(sha256sum "$_aa" | cut -d' ' -f1) + env -i PATH="$T/bin:/usr/bin:/bin" HOME="$T/home" FAKE_LOG="$T/log" XDG_SESSION_ID=5 \ + XDG_CURRENT_DESKTOP=Hyprland "$@" "$TSH" -c ' + MESA_M3_SESSION_ENV_NO_RUN=1 . "$1" + mesa_m3_session_env "$2" + printf "software=%s\nmarker=%s\nlegacy=%s\nrun=%s\n" "${LIBGL_ALWAYS_SOFTWARE:-}" "${MESA_M3_FALLBACK:-}" "${MESA_M3_SESSION:-}" "${MESA_M3_HOOK_RUN_ID:-}" + cat "$2/run/user/$(id -u)/mesa-m3-session.state" + ' sh "$ENVFILE" "$_ar" > "$T/auto.out" 2>&1 + _aaf=none + [ ! -f "$_aa" ] || _aaf=$(sha256sum "$_aa" | cut -d' ' -f1) + if [ "$_aaf" = none ]; then _aat=none + elif [ "$_aaf" = "$_ab" ]; then _aat=unchanged + elif grep -qx "hook_run_id=$(sed -n 's/^run=//p' "$T/auto.out")" "$_aa"; then _aat=new + else _aat=foreign; fi + _ac=no + [ ! -e "$_ar/opt/mesa-m3/libexec/system-check.args" ] || _ac=yes + echo "$(sed -n 's/^decision=//p' "$T/auto.out") $(sed -n 's/^reason=//p' "$T/auto.out") $(sed -n 's/^fallback=//p' "$T/auto.out") helper=$_ac attempt=$_aat sw=$(sed -n 's/^software=//p' "$T/auto.out") legacy=$(sed -n 's/^legacy=//p' "$T/auto.out")" +} +expect_auto() { # NAME "WANT" ROOT [VAR=value...] + _en=$1 _ew=$2 + shift 2 + _eg=$(auto_run "$@") + if [ "$_eg" = "$_ew" ]; then echo "ok auto $_en: $_eg" + else echo "FAIL auto $_en: got '$_eg', want '$_ew'"; sed 's/^/ /' "$T/auto.out"; fail=1; fi +} +auto_fresh() { rm -f "$1$T/home/.local/state/mesa-m3/attempt" "$1$T/home/.local/state/mesa-m3/session-ok"; } +SYS='inactive system-mesa none helper=yes attempt=new sw= legacy=' +# The 13- and 15-inch Air without the T8122 experiment marker: the gate says +# experimental, which used to mean the software fallback. +AJ613=$(auto_root j613 air) +expect_auto "J613 without opt-out" "$SYS" "$AJ613" +grep -qx 'detail=system Mesa on /usr admits /dev/dri/renderD128 (t8122, apple,j613); /opt/mesa-m3 stays inactive; the /opt/mesa-m3 gate said: experimental .*' "$T/auto.out" && + echo "ok auto J613: the state names the system Mesa, the board and the gate's own decision" || + { echo "FAIL auto J613 detail: $(grep '^detail=' "$T/auto.out")"; fail=1; } +AJ615=$(auto_root j615 air15) +expect_auto "J615 without opt-out" "$SYS" "$AJ615" +if [ "$(cat "$AJ615/opt/mesa-m3/libexec/system-check.args")" = "$(printf '/dev/dri/renderD128\nt8122')" ] && + [ -z "$(grep -vE '^(PATH=/usr/bin:/bin|PWD=.*|SHLVL=[0-9]+|_=.*)$' "$AJ615/opt/mesa-m3/libexec/system-check.env")" ] && + grep -q '^detail=system Mesa on /usr admits /dev/dri/renderD128 (t8122, apple,j615);' "$T/auto.out"; then + echo "ok auto J615: helper receives the T8122 node and an empty environment; the state names apple,j615" +else echo "FAIL auto J615: helper argv/environment or detail"; fail=1; fi +# The attempt is bound to this login exactly as an active one is, so +# mesa-m3-session-confirm can confirm it (see test-confirm.sh). +_arun=$(sed -n 's/^run=//p' "$T/auto.out") +if [ "$(head -n 1 "$AJ615$T/home/.local/state/mesa-m3/attempt")" = schema=aurora.mesa-m3-attempt/2 ] && + grep -qx 'boot_id=boot-a' "$AJ615$T/home/.local/state/mesa-m3/attempt" && + grep -qx 'session_id=5' "$AJ615$T/home/.local/state/mesa-m3/attempt" && + grep -qx "hook_run_id=$_arun" "$T/auto.out"; then + echo "ok auto J615: attempt bound to boot_id, session_id and the exported hook_run_id (= state)" +else echo "FAIL auto J615 attempt: $(cat "$AJ615$T/home/.local/state/mesa-m3/attempt")"; fail=1; fi +check_state "system-mesa (J615)" inactive system-mesa none "$(auto_fresh "$AJ615"; state "$AJ615" 5)" +auto_fresh "$AJ615" +# The leftover software pair of an earlier login is the hook's own: cleared. +# A user's own LIBGL_ALWAYS_SOFTWARE is a user setup (below). +expect_auto "J615 clears its own leftover pair" "$SYS" "$AJ615" LIBGL_ALWAYS_SOFTWARE=1 MESA_M3_FALLBACK=software +auto_fresh "$AJ615" +# An active /opt/mesa-m3 is never replaced: the J613 opted in to the +# experiment and the J516S keep it, and the helper is not run. +LEGACY='active active none helper=no attempt=new sw= legacy=/opt/mesa-m3' +mkdir -p "$AJ613/etc/mesa-m3"; : > "$AJ613/etc/mesa-m3/t8122-gpu-experiment" +auto_fresh "$AJ613" +expect_auto "J613 opted in: /opt stays active" "$LEGACY" "$AJ613" +rm "$AJ613/etc/mesa-m3/t8122-gpu-experiment"; auto_fresh "$AJ613" +AJ516A=$(auto_root j516s-active pro) +expect_auto "J516S: /opt stays active" "$LEGACY" "$AJ516A" +# A J516S whose /opt/mesa-m3 gate ends inactive (incomplete prefix) gets the +# system Mesa instead of software. +AJ516=$(auto_root j516s pro); rm "$AJ516/opt/mesa-m3/lib/libvulkan_asahi.so" +expect_auto "J516S, /opt incomplete" "$SYS" "$AJ516" +grep -q '^detail=system Mesa on /usr admits /dev/dri/renderD128 (t6030, apple,j516s);.*gate said: incomplete-prefix' "$T/auto.out" && + echo "ok auto J516S: the state names t6030, apple,j516s and the gate's verdict" || { echo "FAIL auto J516S detail"; fail=1; } +auto_fresh "$AJ516" +# Exactly the run id exported: none of /opt/mesa-m3's variables, no fallback. +exs=$(env -i PATH="$T/bin:/usr/bin:/bin" HOME="$T/home" FAKE_LOG="$T/log" XDG_CURRENT_DESKTOP=Hyprland LD_LIBRARY_PATH=/x "$TSH" -c ' + MESA_M3_SESSION_ENV_NO_RUN=1 . "$1"; unset MESA_M3_SESSION_ENV_NO_RUN + env | sort > "$HOME/b7"; mesa_m3_session_env "$2" >/dev/null; env | sort > "$HOME/a7" + diff "$HOME/b7" "$HOME/a7" | grep "^[<>]" | grep -v -E "^[<>] (_=|SHLVL=)" | sed "s/^> //"' sh "$ENVFILE" "$AJ516" | + sed -E 's/^MESA_M3_HOOK_RUN_ID=[0-9a-f-]{36}$/MESA_M3_HOOK_RUN_ID=/') +[ "$exs" = "MESA_M3_HOOK_RUN_ID=" ] && echo "ok auto J516S: only the run id exported, LD_LIBRARY_PATH untouched" || + { echo "FAIL auto J516S exports: $exs"; fail=1; } +auto_fresh "$AJ516" +# No login loop: an automatic login whose compositor was not confirmed sends +# the next login of this boot to the software fallback; a matching success +# marker lets the system Mesa in again, replacing the older confirmed attempt; +# another boot's attempt does not count. +expect_auto "J615 first login" "$SYS" "$AJ615" +_arun=$(sed -n 's/^run=//p' "$T/auto.out") +expect_auto "J615 after an unconfirmed login" "inactive experimental software helper=no attempt=unchanged sw=1 legacy=" "$AJ615" +printf 'schema=aurora.mesa-m3-session-ok/1\nboot_id=boot-a\nsession_id=5\nhook_run_id=%s\n' "$_arun" > "$AJ615$T/home/.local/state/mesa-m3/session-ok" +expect_auto "J615 replaces an older confirmed attempt" "$SYS" "$AJ615" +grep -qx "hook_run_id=$_arun" "$AJ615$T/home/.local/state/mesa-m3/attempt" && + { echo "FAIL auto J615: the older attempt was kept"; fail=1; } || echo "ok auto J615: the older confirmed attempt is gone" +echo boot-b > "$AJ615/proc/sys/kernel/random/boot_id" +expect_auto "J615 after another boot's unconfirmed login" "$SYS" "$AJ615" +echo boot-a > "$AJ615/proc/sys/kernel/random/boot_id"; auto_fresh "$AJ615" +expect_auto "J516S first login" "$SYS" "$AJ516" +expect_auto "J516S after an unconfirmed login" "inactive incomplete-prefix software helper=no attempt=unchanged sw=1 legacy=" "$AJ516" +auto_fresh "$AJ516" +# The attempt comes first: when it cannot be written, the admission is dropped, +# the gate's software fallback stays and the state says why. +R=$(auto_root j615-attempt-fails air15); AD="$R$T/home/.local/state/mesa-m3" +mkdir -p "$AD"; chmod 500 "$AD" +if [ -w "$AD" ]; then echo "skip attempt-fails (running as root)"; else + expect_auto "J615 attempt not writable" "inactive experimental software helper=yes attempt=none sw=1 legacy=" "$R" + grep -q '^detail=the M3 (T8122) GPU is experimental.*; system Mesa admitted, but its attempt not recorded in ' "$T/auto.out" && + echo "ok auto J615: the state says the attempt was not recorded" || { echo "FAIL auto J615 attempt-fails detail: $(grep '^detail=' "$T/auto.out")"; fail=1; } + check_state "attempt not recorded (J615)" inactive experimental software "$(state "$R" 5)" + # An older confirmed attempt in that directory stays as it was. + chmod 700 "$AD" + printf 'schema=aurora.mesa-m3-attempt/2\nboot_id=boot-a\nsession_id=5\nhook_run_id=old-run\n' > "$AD/attempt" + printf 'schema=aurora.mesa-m3-session-ok/1\nboot_id=boot-a\nsession_id=5\nhook_run_id=old-run\n' > "$AD/session-ok" + chmod 500 "$AD" + expect_auto "J615 attempt not writable over a confirmed one" "inactive experimental software helper=yes attempt=unchanged sw=1 legacy=" "$R" +fi +chmod 700 "$AD" +# Fail closed: each refusal keeps today's decision and fallback exactly. +GATEX='inactive experimental software helper=no attempt=none sw=1 legacy=' +expect_auto "J615 mesa_m3=off" "inactive opt-out software helper=no attempt=none sw=1 legacy=" "$(R=$(auto_root j615-off air15); echo 'root=UUID=x rw mesa_m3=off quiet' > "$R/proc/cmdline"; echo "$R")" +expect_auto "J615 nomodeset" "$GATEX" "$(R=$(auto_root j615-nomodeset air15); echo 'root=UUID=x rw nomodeset quiet' > "$R/proc/cmdline"; echo "$R")" +expect_auto "J516S mesa_m3=off" "inactive opt-out software helper=no attempt=none sw=1 legacy=" "$(R=$(auto_root j516-off pro); echo 'root=UUID=x rw mesa_m3=off quiet' > "$R/proc/cmdline"; echo "$R")" +R=$(auto_root j615-helper-fails air15); : > "$R/opt/mesa-m3/libexec/system-check.fail" +expect_auto "J615 helper refuses" "inactive experimental software helper=yes attempt=none sw=1 legacy=" "$R" +R=$(auto_root j615-helper-stalls air15); : > "$R/opt/mesa-m3/libexec/system-check.sleep" +expect_auto "J615 helper times out" "inactive experimental software helper=yes attempt=none sw=1 legacy=" "$R" +R=$(auto_root j615-helper-missing air15); rm "$R/opt/mesa-m3/libexec/mesa-m3-system-check" +expect_auto "J615 helper missing" "$GATEX" "$R" +R=$(auto_root j516-helper-fails pro); : > "$R/opt/mesa-m3/libexec/system-check.fail"; rm "$R/opt/mesa-m3/lib/libvulkan_asahi.so" +expect_auto "J516S helper refuses" "inactive incomplete-prefix software helper=yes attempt=none sw=1 legacy=" "$R" +# The J615 without its nodes: no render node (GPU not started), no display, +# another GPU, two nodes. +expect_auto "J615 without a render node" "inactive experimental none helper=no attempt=none sw= legacy=" \ + "$(R=$(auto_root j615-nonode air15); rm -r "$R/sys/class/drm/renderD128"; echo "$R")" +expect_auto "J615 without the display" "$GATEX" "$(R=$(auto_root j615-nodcp air15); rm "$R/sys/class/drm/card2/device"; echo "$R")" +expect_auto "J615 with the T6030 display" "$GATEX" \ + "$(R=$(auto_root j615-dcps air15); nul apple,t6030-display-subsystem > "$R/sys/devices/platform/display/of_node/compatible"; echo "$R")" +expect_auto "J615 with two render nodes" "inactive experimental software helper=no attempt=none sw=1 legacy=" \ + "$(R=$(auto_root j615-twonodes air15); mkdir "$R/sys/class/drm/renderD129"; echo "$R")" +expect_auto "J615 with the T6030 GPU" "inactive experimental none helper=no attempt=none sw= legacy=" \ + "$(R=$(auto_root j615-g15s air15); nul $G15S > "$R/sys/devices/platform/gpu0/of_node/compatible"; echo "$R")" +# Unknown or ambiguous boards: the gate's decision stands. +expect_auto "unknown T8122 board (J504)" "$GATEX" "$(R=$(auto_root j504 air15); nul apple,j504 apple,t8122 > "$R/proc/device-tree/compatible"; echo "$R")" +expect_auto "both Air boards" "$GATEX" "$(R=$(auto_root j613j615 air15); nul apple,j613 apple,j615 apple,t8122 > "$R/proc/device-tree/compatible"; echo "$R")" +expect_auto "14-inch M3 Pro (J514S): /opt as before" "active active none helper=no attempt=new sw= legacy=/opt/mesa-m3" \ + "$(R=$(auto_root j514s pro); nul apple,j514s apple,t6030 > "$R/proc/device-tree/compatible"; echo "$R")" +# The 25G83 native profile keeps its own gate: an explicit selection, or a +# node handed off for that firmware without one. +expect_auto "J613 with an explicit t8122-profile" "$GATEX" "$(R=$(auto_root j613-profile air); echo j613-25g83-hal200 > "$R/etc/mesa-m3/t8122-profile"; echo "$R")" +expect_auto "J615 with the 25G83 GPU handoff" "$GATEX" \ + "$(R=$(auto_root j615-hal200 air15); printf '\000\000\000\001' > "$R/sys/devices/platform/gpu0/of_node/apple,j613-25g83-gpu-handoff"; echo "$R")" +# Kernel log, desktop and user setup. +for journal in noperm error g15g verdict dcp; do + expect_auto "J615 journal $journal" "$GATEX" "$(auto_root "j615-journal-$journal" air15)" FAKE_JOURNAL="$journal" +done +expect_auto "J615 in a sway session" "$GATEX" "$AJ615" XDG_CURRENT_DESKTOP=sway +auto_fresh "$AJ615" +expect_auto "J615 with LIBGL_ALWAYS_SOFTWARE of the user's" "$GATEX" "$AJ615" LIBGL_ALWAYS_SOFTWARE=1 +auto_fresh "$AJ615" +R=$(auto_root j516-user-mesa pro); P="$T/home/mesa" +mkdir -p "$R$T/home/.config/uwsm" "$R$P/lib"; : > "$R$P/lib/libgallium-26.1.4.so" +printf 'export LIBGL_DRIVERS_PATH=%s/lib/dri\n' "$P" > "$R$T/home/.config/uwsm/env-hyprland" +expect_auto "J516S whose user chose a Mesa" "inactive user-setup none helper=no attempt=none sw= legacy=" "$R" +R=$(auto_root j615-late-software air15); mkdir -p "$R$T/home/.config/uwsm" +printf 'export LIBGL_ALWAYS_SOFTWARE+=1\n' > "$R$T/home/.config/uwsm/env" +expect_auto "J615 with a late append override" "$GATEX" "$R" +R=$(auto_root j615-no-hook air15); rm "$R/opt/mesa-m3/libexec/mesa-m3-session-env" +expect_auto "J615 without the installed hook" "$GATEX" "$R" +# A stalled journal is bounded (20 s, then 2 s to kill) on the automatic path. +R=$(auto_root j615-stall air15) +_hstart=$(date +%s) +expect_auto "J615 stalled journal" "$GATEX" "$R" FAKE_JOURNAL=stall +_hspent=$(( $(date +%s) - _hstart )) +if [ "$_hspent" -le 30 ]; then echo "ok auto J615 stalled journal: refused after ${_hspent}s" +else echo "FAIL auto J615 stalled journal: took ${_hspent}s"; fail=1; fi + [ "$fail" = 0 ] && echo "ALL PASS" || echo "SOME FAILED" exit "$fail" diff --git a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c index 1aa86befdcdba2..2acf61e6e0f666 100644 --- a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c +++ b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c @@ -13,10 +13,21 @@ int main(void) CHECK(true, "t8122", "apple,j613\0apple,t8122\0apple,arm-platform\0"); CHECK(true, "t6030", "apple,j516s\0apple,t6030\0apple,arm-platform\0"); CHECK(true, "t6030", "apple,arm-platform\0apple,t6030\0apple,j516s\0"); - CHECK(false, "t8122", "apple,j615\0apple,t8122\0"); + CHECK(true, "t8122", "apple,j615\0apple,t8122\0apple,arm-platform\0"); + CHECK(true, "t8122", "apple,arm-platform\0apple,t8122\0apple,j615\0"); + CHECK(false, "t6030", "apple,j615\0apple,t6030\0"); + CHECK(false, "t8122", "apple,j615\0apple,j615\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j615\0apple,t8122\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j615\0"); + CHECK(false, "t8122", "apple,j615\0apple,t8122"); + CHECK(false, "t8122", "apple,j616\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j615s\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j504\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j516s\0apple,t8122\0"); CHECK(false, "t6030", "apple,j514s\0apple,t6030\0"); CHECK(false, "t6030", "apple,j516s\0apple,t6031\0"); CHECK(false, "t8122", "apple,j613\0apple,j615\0apple,t8122\0"); + CHECK(false, "t8122", "apple,j615\0apple,j613\0apple,t8122\0"); CHECK(false, "t8122", "apple,j613\0apple,j613\0apple,t8122\0"); CHECK(false, "t6030", "apple,j516s\0apple,t6030\0apple,t6030\0"); CHECK(false, "t6030", "apple,j516s\0apple,t6030\0apple,t8122\0"); @@ -27,6 +38,6 @@ int main(void) CHECK(false, "t6030", "apple,j516s\0"); CHECK(false, "t8140", "apple,j516s\0apple,t6030\0"); CHECK(false, "t6030", ""); - puts("17 board parser controls passed"); + puts("28 board parser controls passed"); return 0; } diff --git a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py index e528270b7c9444..4bfc444327f05e 100644 --- a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py +++ b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """The system-provider decision must see legacy selectors as overrides.""" from pathlib import Path +import shutil import subprocess import tempfile import unittest @@ -227,5 +228,168 @@ def test_missing_file_does_not_mask_unresolved_choice(self): self.assertNotEqual(result.returncode, 0, result.stdout) +class SystemAdmissionTest(unittest.TestCase): + """_mm3_system_admit: which boards and nodes may keep the system Mesa. + + The helper is a stub that records its arguments; its own board parser is + covered by test-system-board.c. Without a file opt-out the admission is + automatic; the same checks apply either way. + """ + + BOARDS = {"j613": ("apple,j613", "apple,t8122"), + "j615": ("apple,j615", "apple,t8122"), + "j516s": ("apple,j516s", "apple,t6030")} + + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.base = Path(self.temporary.name) + self.bin = self.base / "bin" + self.bin.mkdir() + journal = self.bin / "journalctl" + journal.write_text('#!/bin/sh\n[ -z "${FAKE_FAULT:-}" ] || echo "$FAKE_FAULT"\n' + 'echo "asahi 292400000.gpu: M3: firmware ready"\n') + journal.chmod(0o755) + + def make_root(self, board="j615", node=True, display=True): + compatibles = self.BOARDS.get(board, (board, "apple,t8122")) + soc = compatibles[-1].split(",")[1] + root = self.base / ("root-" + board) + (root / "proc/device-tree").mkdir(parents=True) + (root / "proc/cmdline").write_text("root=UUID=x rw quiet\n") + (root / "proc/device-tree/compatible").write_bytes( + b"".join(c.encode() + b"\0" for c in (*compatibles, "apple,arm-platform"))) + driver = root / "sys/bus/platform/drivers/asahi" + driver.mkdir(parents=True) + (root / "sys/class/drm").mkdir(parents=True) + (root / "dev/dri").mkdir(parents=True) + if node: + gpu = root / "sys/devices/platform/gpu0" + (gpu / "of_node").mkdir(parents=True) + (gpu / "of_node/compatible").write_bytes(b"apple,agx-" + soc.encode() + b"\0") + (gpu / "driver").symlink_to(driver) + (root / "sys/class/drm/renderD128").mkdir() + (root / "sys/class/drm/renderD128/device").symlink_to(gpu) + (root / "dev/dri/renderD128").touch(mode=0o600) + if display: + dcp = root / "sys/devices/platform/display" + (dcp / "of_node").mkdir(parents=True) + (dcp / "of_node/compatible").write_bytes( + b"apple," + soc.encode() + b"-display-subsystem\0") + (root / "sys/class/drm/card2").mkdir() + (root / "sys/class/drm/card2/device").symlink_to(dcp) + prefix = root / "opt/mesa-m3" + (prefix / "libexec").mkdir(parents=True) + (prefix / "share/mesa-m3").mkdir(parents=True) + (prefix / "lib").mkdir() + (prefix / "lib/libgallium-26.1.4.so").touch() + shutil.copy(RECIPE / "mesa-m3-user-setup", prefix / "libexec/mesa-m3-user-setup") + shutil.copy(RECIPE / "user-setup.list", prefix / "share/mesa-m3/user-setup.list") + helper = prefix / "libexec/mesa-m3-system-check" + helper.write_text('#!/bin/sh\nprintf "%s\\n" "$@" > "${0%/*}/args"\n' + '[ ! -e "${0%/*}/refuse" ]\n') + helper.chmod(0o755) + (root / "home/tester/.config").mkdir(parents=True) + return root + + def admit(self, root, **environment): + hook = RECIPE / "mesa-m3-session-env" + return subprocess.run( + ["sh", "-c", 'MESA_M3_SESSION_ENV_NO_RUN=1 . "$1" && _mm3_p=/opt/mesa-m3 && ' + '_mm3_system_admit "$2"', "sh", str(hook), str(root)], + env={"PATH": str(self.bin) + ":/usr/bin:/bin", "HOME": "/home/tester", + "XDG_CURRENT_DESKTOP": "Hyprland", **environment}, + capture_output=True, text=True, timeout=30) + + def helper_args(self, root): + path = root / "opt/mesa-m3/libexec/args" + return path.read_text().split() if path.exists() else None + + def test_supported_boards_are_admitted_without_opt_out(self): + for board, chip in (("j613", "t8122"), ("j615", "t8122"), ("j516s", "t6030")): + with self.subTest(board=board): + root = self.make_root(board) + result = self.admit(root) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn(f"({chip}, apple,{board})", result.stdout) + self.assertEqual(self.helper_args(root), ["/dev/dri/renderD128", chip]) + + def test_file_opt_out_still_admits(self): + root = self.make_root("j615") + (root / "etc/mesa-m3").mkdir(parents=True) + (root / "etc/mesa-m3/disable").touch() + self.assertEqual(self.admit(root).returncode, 0) + + def test_unknown_or_ambiguous_boards_are_refused(self): + for board in ("apple,j504", "apple,j615s", "apple,j516s", "apple,j614s"): + with self.subTest(board=board): + root = self.make_root(board) + self.assertEqual(self.admit(root).returncode, 1) + self.assertIsNone(self.helper_args(root)) + root = self.make_root("j615") + (root / "proc/device-tree/compatible").write_bytes(b"apple,j613\0apple,j615\0apple,t8122\0") + self.assertEqual(self.admit(root).returncode, 1) + self.assertIsNone(self.helper_args(root)) + + def test_j615_without_its_nodes_is_refused(self): + for node, display in ((False, True), (True, False)): + with self.subTest(node=node, display=display): + root = self.make_root("j615", node=node, display=display) + self.assertEqual(self.admit(root).returncode, 1) + self.assertIsNone(self.helper_args(root)) + shutil.rmtree(root) + + def test_kernel_command_line_refuses(self): + for flag in ("mesa_m3=off", "nomodeset"): + with self.subTest(flag=flag): + root = self.make_root("j615") + (root / "proc/cmdline").write_text(f"root=UUID=x rw {flag} quiet\n") + self.assertEqual(self.admit(root).returncode, 1) + self.assertIsNone(self.helper_args(root)) + shutil.rmtree(root) + + def test_helper_refusal_is_final(self): + root = self.make_root("j615") + (root / "opt/mesa-m3/libexec/refuse").touch() + result = self.admit(root) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertEqual(self.helper_args(root), ["/dev/dri/renderD128", "t8122"]) + + def test_kernel_fault_and_other_desktops_refuse(self): + root = self.make_root("j615") + self.assertEqual(self.admit(root, FAKE_FAULT="asahi: M3 G15G: firmware crashed").returncode, 1) + self.assertEqual(self.admit(root, XDG_CURRENT_DESKTOP="sway").returncode, 1) + self.assertIsNone(self.helper_args(root)) + + def test_native_25g83_profile_stays_on_its_gate_without_opt_out(self): + root = self.make_root("j613") + (root / "etc/mesa-m3").mkdir(parents=True) + (root / "etc/mesa-m3/t8122-profile").write_text("j613-25g83-hal200\n") + self.assertEqual(self.admit(root).returncode, 1) + (root / "etc/mesa-m3/disable").touch() + self.assertEqual(self.admit(root).returncode, 0) + root = self.make_root("j615") + handoff = root / "sys/devices/platform/gpu0/of_node/apple,j613-25g83-gpu-handoff" + handoff.write_bytes(b"\0\0\0\1") + self.assertEqual(self.admit(root).returncode, 1) + (root / "etc/mesa-m3").mkdir(parents=True) + (root / "etc/mesa-m3/disable").touch() + self.assertEqual(self.admit(root).returncode, 0) + + def test_unconfirmed_attempt_of_this_boot_refuses(self): + root = self.make_root("j615") + (root / "proc/sys/kernel/random").mkdir(parents=True) + (root / "proc/sys/kernel/random/boot_id").write_text("boot-a\n") + state = root / "home/tester/.local/state/mesa-m3" + state.mkdir(parents=True) + (state / "attempt").write_text("schema=aurora.mesa-m3-attempt/2\nboot_id=boot-a\n" + "session_id=5\nhook_run_id=run-1\n") + self.assertEqual(self.admit(root).returncode, 1) + (state / "session-ok").write_text("schema=aurora.mesa-m3-session-ok/1\nboot_id=boot-a\n" + "session_id=5\nhook_run_id=run-1\n") + self.assertEqual(self.admit(root).returncode, 0) + + if __name__ == "__main__": unittest.main()