|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: GPL-2.0 |
| 4 | +# Copyright (c) 2026 Igor Velkov |
| 5 | +# This file is a part of the Armbian Build Framework https://github.com/armbian/build/ |
| 6 | +# |
| 7 | +# Enable Magic SysRq through the serial console for headless boards. Without |
| 8 | +# this, kernel hangs (NFS deadlocks, ATA error-handler corner cases on Helios64, |
| 9 | +# etc.) leave you with no way out except the hardware-watchdog timeout or a |
| 10 | +# physical reset. With it, an operator on the serial console can sync, |
| 11 | +# remount-RO and reboot from any kernel state where interrupts still flow. |
| 12 | +# |
| 13 | +# This extension only enables the operator-console *surface*. To make the |
| 14 | +# tracebacks and KGDB sessions reachable through that surface actually |
| 15 | +# informative (rather than streams of hex addresses), pair with the |
| 16 | +# `kernel-debug-tiers` extension which adds BTF, hung-task detection, |
| 17 | +# pstore/ramoops, and KGDB symbol resolution. |
| 18 | +# |
| 19 | +# Mainline kernels ship CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE empty by default, |
| 20 | +# which disables BREAK-triggered SysRq entirely. From lib/Kconfig.debug: |
| 21 | +# "If unsure, leave an empty string and the option will not be enabled." |
| 22 | +# (https://elixir.bootlin.com/linux/latest/source/lib/Kconfig.debug#L698) |
| 23 | +# The serial driver requires a non-empty sequence after BREAK so that random |
| 24 | +# line noise cannot trigger a reboot. This extension fills that in with a |
| 25 | +# deliberate sequence that is vanishingly unlikely in normal serial traffic. |
| 26 | +# |
| 27 | +# Usage — add to your userpatches config (e.g. `userpatches/config-my.conf`): |
| 28 | +# |
| 29 | +# enable_extension "sysrq-serial-trigger" |
| 30 | +# |
| 31 | +# Or pass on the build CLI for a one-off run: |
| 32 | +# |
| 33 | +# ./compile.sh build BOARD=helios64 BRANCH=edge \ |
| 34 | +# ENABLE_EXTENSIONS=sysrq-serial-trigger |
| 35 | +# |
| 36 | +# Operator workflow (picocom default escape Ctrl-A): |
| 37 | +# Ctrl-A \ (send BREAK) |
| 38 | +# sysrq (the magic sequence, default value of SYSRQ_SERIAL_SEQUENCE) |
| 39 | +# b (or any other SysRq command — see Documentation/admin-guide/sysrq.rst) |
| 40 | +# |
| 41 | +# To experiment with a different sequence, override SYSRQ_SERIAL_SEQUENCE |
| 42 | +# from the build CLI; pick something that does not occur in normal output of |
| 43 | +# anything you care about (logging, dialog, etc.). |
| 44 | + |
| 45 | +function custom_kernel_config__sysrq_serial_trigger() { |
| 46 | + # Default "sysrq": printable, easy to type, and passes through Bash/sed without |
| 47 | + # escaping. Kernel matches bytes verbatim against serial input after BREAK. |
| 48 | + # `:-` substitutes only when SYSRQ_SERIAL_SEQUENCE is unset; an explicit |
| 49 | + # empty value would otherwise silently write MAGIC_SYSRQ_SERIAL_SEQUENCE="" |
| 50 | + # which lib/Kconfig.debug documents as disabling BREAK-triggered SysRq |
| 51 | + # entirely. Refuse empty loudly so the operator fixes the config rather |
| 52 | + # than getting a silently-disabled SysRq path. |
| 53 | + declare seq="${SYSRQ_SERIAL_SEQUENCE:-sysrq}" |
| 54 | + if [[ -z "${seq}" ]]; then |
| 55 | + exit_with_error "${EXTENSION}: SYSRQ_SERIAL_SEQUENCE must not be empty" \ |
| 56 | + "empty would write MAGIC_SYSRQ_SERIAL_SEQUENCE=\"\" and disable BREAK-triggered SysRq; pick any non-empty sequence (default: SYSRQ_SERIAL_SEQUENCE=sysrq)" |
| 57 | + fi |
| 58 | + display_alert "${EXTENSION}: enabling SysRq over serial" "sequence='${seq}' after BREAK" "info" |
| 59 | + opts_y+=("MAGIC_SYSRQ" "MAGIC_SYSRQ_SERIAL") |
| 60 | + # 1 = SYSRQ_ENABLE_ALL (kernel special-case, not bit 1); enables all SysRq |
| 61 | + # commands regardless of the runtime kernel.sysrq sysctl value at boot. |
| 62 | + # shellcheck disable=SC2034 # opts_val is read by armbian_kernel_config_apply_opts_from_arrays |
| 63 | + opts_val["MAGIC_SYSRQ_DEFAULT_ENABLE"]="1" |
| 64 | + # Armbian has no opts_str[] equivalent for string kconfig options — opts_val[] |
| 65 | + # dispatches via --set-val which truncates strings to "" (it is designed for |
| 66 | + # numeric/hex values only). Use kernel_config_set_string directly; it calls |
| 67 | + # --set-str and registers the value in kernel_config_modifying_hashes. |
| 68 | + # Two-phase guard: kernel_config_set_string requires an unpacked source tree; |
| 69 | + # in the hash-only phase (.config absent) add to hashes manually instead. |
| 70 | + if [[ -f .config ]]; then |
| 71 | + kernel_config_set_string "MAGIC_SYSRQ_SERIAL_SEQUENCE" "${seq}" |
| 72 | + else |
| 73 | + kernel_config_modifying_hashes+=("MAGIC_SYSRQ_SERIAL_SEQUENCE=\"${seq}\"") |
| 74 | + fi |
| 75 | +} |
| 76 | + |
| 77 | +# U-Boot autoboot timing — give the operator a real chance to interrupt. |
| 78 | +# Default Armbian u-boot prints "Hit any key to stop autoboot: 0" — i.e. |
| 79 | +# a 1-second window that's effectively unusable. BOOTDELAY=5 gives a 5-second |
| 80 | +# countdown; AUTOBOOT_NEVER_TIMEOUT means a single keypress freezes the |
| 81 | +# countdown entirely so the operator can take their time after that. The |
| 82 | +# trade-off is +5s on every cold boot — acceptable for headless servers, |
| 83 | +# probably annoying for kiosks. Comment out this whole function to revert. |
| 84 | +function post_config_uboot_target__sysrq_serial_uboot_autoboot() { |
| 85 | + display_alert "${EXTENSION}: u-boot BOOTDELAY=5 + AUTOBOOT_NEVER_TIMEOUT" "give the operator 5s to grab the prompt; pause forever after first keypress" "info" |
| 86 | + # `scripts/config` is u-boot's own kbuild helper (same script as Linux uses). |
| 87 | + # It handles "is not set" → enabled and value-overrides correctly without |
| 88 | + # depending on which form the line currently takes. |
| 89 | + run_host_command_logged ./scripts/config --set-val CONFIG_BOOTDELAY 5 |
| 90 | + run_host_command_logged ./scripts/config --enable CONFIG_AUTOBOOT_NEVER_TIMEOUT |
| 91 | +} |
| 92 | + |
| 93 | +# Distro defaults (Debian/Ubuntu /usr/lib/sysctl.d/55-magic-sysrq.conf) cap |
| 94 | +# kernel.sysrq at 176 — enough for `s`/`u`/`b` but not `t`/`m`/`f`/`w` which |
| 95 | +# are the actually useful debug commands when a kernel is misbehaving. A |
| 96 | +# headless box with serial-console SysRq is exactly the case where the full |
| 97 | +# set should be available, so override to 1 (all functions). The 60- prefix |
| 98 | +# beats the 55- distro file in sysctl.d lexical order. |
| 99 | +function post_customize_image__sysrq_serial_trigger_userland() { |
| 100 | + declare conf="${SDCARD}/etc/sysctl.d/60-armbian-sysrq.conf" |
| 101 | + display_alert "${EXTENSION}: enabling full kernel.sysrq" "${conf##*/} (kernel.sysrq=1)" "info" |
| 102 | + cat > "${conf}" <<- 'SYSCTL_CONF' |
| 103 | + # Installed by the Armbian sysrq-serial-trigger extension. |
| 104 | + # Overrides /usr/lib/sysctl.d/55-magic-sysrq.conf (default 176) |
| 105 | + # to enable the full SysRq command set — needed for serial-console |
| 106 | + # debugging (process dumps, blocked-task list, OOM kill, etc.). |
| 107 | + kernel.sysrq = 1 |
| 108 | + SYSCTL_CONF |
| 109 | +} |
0 commit comments