Created
December 30, 2025 00:55
-
-
Save Entrpi/6a300bbacfcee4983a45c9661d1dfb15 to your computer and use it in GitHub Desktop.
Orion O6 - Minimum Power Testing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Minimum power test script for Radxa Orion O6 / CIX Sky1 | |
| # Modular design with individual power controls | |
| # Run as root. | |
| set -e | |
| #============================================================================= | |
| # Configuration | |
| #============================================================================= | |
| DDR_LP="/sys/devices/platform/cix_ddr_lp/on" | |
| CPU_IPA="/sys/devices/platform/83bf0300.cix_cpu_ipa/cpu_power" | |
| CI700_DEVFREQ="/sys/class/devfreq/cix_bus_ci700" | |
| NI700_DEVFREQ="/sys/class/devfreq/cix_bus_ni700" | |
| USB_CONTROLLERS="9010000 9080000 90f0000 9160000 91d0000 91e0000 9260000 9290000 92c0000 92f0000" | |
| DISPLAY_CONTROLLERS="14010000 14080000 140f0000 14160000 141d0000" | |
| # PCIe root ports for 5GbE R8126 controllers | |
| PCIE_5GBE_PORTS="a0d0000.pcie a0e0000.pcie" | |
| # PCIe root port for WiFi (RTL8852BE) | |
| PCIE_WIFI_PORT="a0c0000.pcie" | |
| # PCIe root port for NVMe | |
| PCIE_NVME_PORT="a070000.pcie" | |
| # Ramdisk location - MUST be on existing tmpfs (not under root fs) | |
| RAMROOT="/run/ramroot" | |
| #============================================================================= | |
| # Utility Functions | |
| #============================================================================= | |
| hold_for() { | |
| local secs=$1 label=$2 | |
| echo ">>> $label - hold ${secs}s ($(date '+%H:%M:%S'))" | |
| for i in $(seq $secs -1 1); do | |
| printf "\r %3d seconds..." $i | |
| sleep 1 | |
| done | |
| echo "" | |
| } | |
| #============================================================================= | |
| # Individual Power Controls (on/off functions) | |
| #============================================================================= | |
| # --- GDM / Desktop --- | |
| ctl_gdm() { | |
| case $1 in | |
| off) | |
| systemctl stop gdm 2>/dev/null || true | |
| pkill -9 gnome-shell 2>/dev/null || true | |
| pkill -9 Xwayland 2>/dev/null || true | |
| pkill -9 gsd- 2>/dev/null || true | |
| sleep 2 | |
| echo " GDM/desktop: stopped" | |
| ;; | |
| on) | |
| systemctl start gdm 2>/dev/null || true | |
| echo " GDM/desktop: started" | |
| ;; | |
| esac | |
| } | |
| # --- GPU (panthor) --- | |
| ctl_gpu() { | |
| case $1 in | |
| off) | |
| rmmod panthor 2>/dev/null && echo " GPU: unloaded" || echo " GPU: already unloaded" | |
| ;; | |
| on) | |
| modprobe panthor 2>/dev/null || true | |
| if [ ! -e "/sys/devices/platform/soc@0/15000000.gpu/driver" ]; then | |
| echo "15000000.gpu" > /sys/bus/platform/drivers/panthor/bind 2>/dev/null || true | |
| fi | |
| echo " GPU: loaded" | |
| ;; | |
| esac | |
| } | |
| # --- Display (DPU + DP TX) --- | |
| ctl_display() { | |
| case $1 in | |
| off) | |
| rmmod trilin_dpsub 2>/dev/null || true | |
| rmmod linlon_dp 2>/dev/null || true | |
| echo " Display: unloaded" | |
| ;; | |
| on) | |
| modprobe linlon_dp 2>/dev/null || true | |
| modprobe trilin_dpsub 2>/dev/null || true | |
| for disp in $DISPLAY_CONTROLLERS; do | |
| if [ ! -e "/sys/bus/platform/drivers/linlondp/${disp}.disp-controller" ]; then | |
| echo "${disp}.disp-controller" > /sys/bus/platform/drivers/linlondp/bind 2>/dev/null || true | |
| fi | |
| done | |
| echo " Display: loaded" | |
| ;; | |
| esac | |
| } | |
| # --- WiFi --- | |
| ctl_wifi() { | |
| local deep=${2:-false} # "deep" = also remove PCI device | |
| case $1 in | |
| off) | |
| /usr/sbin/rfkill block wifi 2>/dev/null || true | |
| ip link set wlp97s0 down 2>/dev/null || true | |
| rmmod rtw89_8852be rtw89_8852b rtw89_8852b_common rtw89_pci rtw89_core 2>/dev/null || true | |
| rmmod mac80211 cfg80211 2>/dev/null || true | |
| if [ "$deep" = "deep" ]; then | |
| # Remove WiFi PCI device (RTL8852BE = 10ec:b852) | |
| for dev in /sys/bus/pci/devices/*; do | |
| vendor=$(cat "$dev/vendor" 2>/dev/null) | |
| device=$(cat "$dev/device" 2>/dev/null) | |
| if [ "$vendor" = "0x10ec" ] && [ "$device" = "0xb852" ]; then | |
| echo 1 > "$dev/remove" 2>/dev/null || true | |
| echo " WiFi: disabled + PCI device removed" | |
| return | |
| fi | |
| done | |
| echo " WiFi: disabled (device already removed)" | |
| else | |
| echo " WiFi: disabled" | |
| fi | |
| ;; | |
| on) | |
| # Rescan to restore removed device | |
| echo 1 > /sys/bus/pci/rescan 2>/dev/null || true | |
| sleep 1 | |
| modprobe rtw89_8852be 2>/dev/null || true | |
| /usr/sbin/rfkill unblock wifi 2>/dev/null || true | |
| echo " WiFi: enabled" | |
| ;; | |
| esac | |
| } | |
| # --- Bluetooth --- | |
| ctl_bluetooth() { | |
| case $1 in | |
| off) | |
| /usr/sbin/rfkill block bluetooth 2>/dev/null || true | |
| rmmod btusb btrtl btintel btbcm btmtk 2>/dev/null || true | |
| echo " Bluetooth: disabled" | |
| ;; | |
| on) | |
| modprobe btusb 2>/dev/null || true | |
| /usr/sbin/rfkill unblock bluetooth 2>/dev/null || true | |
| echo " Bluetooth: enabled" | |
| ;; | |
| esac | |
| } | |
| # --- USB Controllers --- | |
| ctl_usb() { | |
| local keep=${2:-} # optional controller to keep | |
| case $1 in | |
| off) | |
| for usb in $USB_CONTROLLERS; do | |
| [ "$usb" = "$keep" ] && continue | |
| if [ -e "/sys/bus/platform/drivers/cdns-usbssp/${usb}.usb-controller" ]; then | |
| echo "${usb}.usb-controller" > /sys/bus/platform/drivers/cdns-usbssp/unbind 2>/dev/null || true | |
| fi | |
| done | |
| echo " USB: unbound${keep:+ (kept $keep)}" | |
| ;; | |
| on) | |
| for usb in $USB_CONTROLLERS; do | |
| if [ ! -e "/sys/bus/platform/drivers/cdns-usbssp/${usb}.usb-controller" ]; then | |
| echo "${usb}.usb-controller" > /sys/bus/platform/drivers/cdns-usbssp/bind 2>/dev/null || true | |
| fi | |
| done | |
| echo " USB: bound" | |
| ;; | |
| esac | |
| } | |
| # --- Audio --- | |
| ctl_audio() { | |
| case $1 in | |
| off) | |
| rmmod snd_hda_codec_alc269 snd_hda_scodec_component snd_hda_codec_realtek_lib 2>/dev/null || true | |
| rmmod snd_hda_codec_generic snd_hda_cix_ipbloq snd_hda_codec snd_hda_core 2>/dev/null || true | |
| rmmod snd_soc_hdmi_codec snd_soc_sky1_card snd_soc_cdns_i2s_mc snd_soc_core 2>/dev/null || true | |
| rmmod snd_compress snd_pcm_dmaengine snd_pcm snd_timer snd soundcore 2>/dev/null || true | |
| if [ -e "/sys/bus/platform/drivers/arm-dma350/7010000.dma-controller" ]; then | |
| echo "7010000.dma-controller" > /sys/bus/platform/drivers/arm-dma350/unbind 2>/dev/null || true | |
| fi | |
| echo " Audio: unloaded" | |
| ;; | |
| on) | |
| modprobe snd_hda_cix_ipbloq 2>/dev/null || true | |
| echo " Audio: loaded (may need reboot)" | |
| ;; | |
| esac | |
| } | |
| # --- PCIe ASPM --- | |
| ctl_pcie_aspm() { | |
| case $1 in | |
| off) # powersupersave | |
| echo powersupersave > /sys/module/pcie_aspm/parameters/policy 2>/dev/null || true | |
| for d in /sys/bus/pci/devices/*/power/control; do | |
| echo auto > "$d" 2>/dev/null || true | |
| done | |
| # L1 substates | |
| setpci -s 0001:90:00.0 0x908.b=0x0f 2>/dev/null || true | |
| setpci -s 0001:91:00.0 0x230.b=0x0f 2>/dev/null || true | |
| setpci -s 0000:60:00.0 0x908.b=0x0f 2>/dev/null || true | |
| setpci -s 0002:30:00.0 0x908.b=0x0f 2>/dev/null || true | |
| setpci -s 0002:31:00.0 0x22c.b=0x0f 2>/dev/null || true | |
| setpci -s 0003:00:00.0 0x908.b=0x0f 2>/dev/null || true | |
| setpci -s 0003:01:00.0 0x22c.b=0x0f 2>/dev/null || true | |
| echo " PCIe ASPM: powersupersave + L1 substates" | |
| ;; | |
| on) # default | |
| echo default > /sys/module/pcie_aspm/parameters/policy 2>/dev/null || true | |
| echo " PCIe ASPM: default" | |
| ;; | |
| esac | |
| } | |
| # --- DDR Low Power --- | |
| # Note: "on" = LP enabled (low power), "off" = LP disabled (full performance) | |
| ctl_ddr_lp() { | |
| case $1 in | |
| on) # enable LP mode (low power) | |
| [ -f "$DDR_LP" ] && echo 1 > "$DDR_LP" | |
| echo " DDR LP: enabled (low power)" | |
| ;; | |
| off) # disable LP mode (full performance) | |
| [ -f "$DDR_LP" ] && echo 0 > "$DDR_LP" | |
| echo " DDR LP: disabled (full performance)" | |
| ;; | |
| esac | |
| } | |
| # --- Bus Frequency --- | |
| ctl_bus_freq() { | |
| local mode=$1 # min or max | |
| [ ! -d "$CI700_DEVFREQ" ] && return | |
| [ ! -d "$NI700_DEVFREQ" ] && return | |
| local ci_freqs=$(cat "$CI700_DEVFREQ/available_frequencies") | |
| local ni_freqs=$(cat "$NI700_DEVFREQ/available_frequencies") | |
| if [ "$mode" = "min" ]; then | |
| local ci_freq=$(echo $ci_freqs | awk '{print $1}') | |
| local ni_freq=$(echo $ni_freqs | awk '{print $1}') | |
| else | |
| local ci_freq=$(echo $ci_freqs | awk '{print $NF}') | |
| local ni_freq=$(echo $ni_freqs | awk '{print $NF}') | |
| fi | |
| echo "$ci_freq" > "$CI700_DEVFREQ/min_freq" | |
| echo "$ci_freq" > "$CI700_DEVFREQ/max_freq" | |
| echo "$ni_freq" > "$NI700_DEVFREQ/min_freq" | |
| echo "$ni_freq" > "$NI700_DEVFREQ/max_freq" | |
| echo " Bus freq: CI-700=$((ci_freq/1000000))MHz NI-700=$((ni_freq/1000000))MHz" | |
| } | |
| # --- CPU --- | |
| ctl_cpu() { | |
| case $1 in | |
| off) # powersave, single core | |
| for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| echo powersave > "$cpu" 2>/dev/null || true | |
| done | |
| for cpu in /sys/devices/system/cpu/cpu{1..11}/online; do | |
| echo 0 > "$cpu" 2>/dev/null || true | |
| done | |
| echo " CPU: powersave, 1 core online" | |
| ;; | |
| on) # schedutil, all cores | |
| for cpu in /sys/devices/system/cpu/cpu{1..11}/online; do | |
| echo 1 > "$cpu" 2>/dev/null || true | |
| done | |
| for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| echo schedutil > "$cpu" 2>/dev/null || true | |
| done | |
| for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do | |
| max=$(cat $(dirname $cpu)/scaling_available_frequencies 2>/dev/null | awk '{print $NF}') | |
| [ -n "$max" ] && echo "$max" > "$cpu" 2>/dev/null || true | |
| done | |
| echo " CPU: schedutil, all cores online" | |
| ;; | |
| esac | |
| } | |
| # --- Misc modules --- | |
| ctl_misc() { | |
| case $1 in | |
| off) | |
| rmmod goodix_ts 2>/dev/null || true | |
| rmmod cix_dsp_rproc 2>/dev/null || true | |
| echo " Misc: unloaded (touchscreen, DSP)" | |
| ;; | |
| on) | |
| modprobe goodix_ts 2>/dev/null || true | |
| modprobe cix_dsp_rproc 2>/dev/null || true | |
| echo " Misc: loaded" | |
| ;; | |
| esac | |
| } | |
| # --- 5GbE R8126 (remove PCI devices) --- | |
| # Note: We use PCI device removal since sky1-pcie driver lacks bind/unbind sysfs. | |
| # To restore after removal, need to rescan the bus or reboot. | |
| ctl_5gbe() { | |
| case $1 in | |
| off) | |
| # Find and remove R8126 devices (vendor:device = 10ec:8126) | |
| for dev in /sys/bus/pci/devices/*; do | |
| vendor=$(cat "$dev/vendor" 2>/dev/null) | |
| device=$(cat "$dev/device" 2>/dev/null) | |
| if [ "$vendor" = "0x10ec" ] && [ "$device" = "0x8126" ]; then | |
| echo 1 > "$dev/remove" 2>/dev/null || true | |
| echo " Removed $(basename $dev)" | |
| fi | |
| done | |
| echo " 5GbE: R8126 devices removed from bus" | |
| ;; | |
| on) | |
| # Rescan PCIe buses to re-enumerate removed devices | |
| echo 1 > /sys/bus/pci/rescan 2>/dev/null || true | |
| echo " 5GbE: PCIe bus rescanned" | |
| ;; | |
| esac | |
| } | |
| # --- PMU (Performance Monitoring Units - not needed for headless) --- | |
| # NOTE: cix_pmu is used by devfreq for bus frequency scaling. | |
| # We must stop devfreq polling before unloading it, or the kernel crashes. | |
| ctl_pmu() { | |
| case $1 in | |
| off) | |
| # Stop devfreq polling first to prevent use-after-free crash | |
| for gov in "$CI700_DEVFREQ/governor" "$NI700_DEVFREQ/governor"; do | |
| [ -f "$gov" ] && echo "userspace" > "$gov" 2>/dev/null || true | |
| done | |
| sleep 0.5 # Let devfreq workqueue drain | |
| rmmod arm_cmn arm_dsu_pmu 2>/dev/null || true | |
| # cix_pmu is too dangerous to unload - tied to devfreq events | |
| # rmmod cix_pmu 2>/dev/null || true | |
| echo " PMU: unloaded (arm_cmn, arm_dsu_pmu) - cix_pmu kept for devfreq" | |
| ;; | |
| on) | |
| modprobe arm_cmn 2>/dev/null || true | |
| modprobe arm_dsu_pmu 2>/dev/null || true | |
| # Restore devfreq governor | |
| for gov in "$CI700_DEVFREQ/governor" "$NI700_DEVFREQ/governor"; do | |
| [ -f "$gov" ] && echo "simple_ondemand" > "$gov" 2>/dev/null || true | |
| done | |
| echo " PMU: loaded" | |
| ;; | |
| esac | |
| } | |
| #============================================================================= | |
| # High-Level Commands | |
| #============================================================================= | |
| usage() { | |
| echo "Usage: $0 <command> [options]" | |
| echo "" | |
| echo "Commands:" | |
| echo " status - Show current power-related settings" | |
| echo " baseline - Set known HIGH power state for measurement" | |
| echo " apply - Apply power savings (keeps display)" | |
| echo " headless - Maximum power savings (serial only!)" | |
| echo " restore - Restore normal operation" | |
| echo " benchmark - Run memory bandwidth test at different bus frequencies" | |
| echo " powertest [secs] - Bus frequency power test (default 30s holds)" | |
| echo " powersteps [secs] - Step-by-step power test (default 20s holds)" | |
| echo " ramtest [secs] - Pivot to ramdisk, disable NVMe (default 60s hold)" | |
| echo "" | |
| echo "Individual controls (for testing):" | |
| echo " ctl <name> <on|off> - Control individual subsystem" | |
| echo " Names: gdm, gpu, display, wifi, bluetooth, usb, audio, pcie, ddr, bus, cpu, misc, 5gbe, pmu" | |
| exit 1 | |
| } | |
| show_status() { | |
| echo "=== Power Status ===" | |
| echo "" | |
| echo "CPU: $(cat /sys/devices/system/cpu/online) online, $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null) governor" | |
| if [ -d "$CI700_DEVFREQ" ]; then | |
| echo "CI-700: $(( $(cat $CI700_DEVFREQ/cur_freq) / 1000000 )) MHz" | |
| fi | |
| if [ -d "$NI700_DEVFREQ" ]; then | |
| echo "NI-700: $(( $(cat $NI700_DEVFREQ/cur_freq) / 1000000 )) MHz" | |
| fi | |
| [ -f "$DDR_LP" ] && echo "DDR LP: $([ $(cat $DDR_LP) = 1 ] && echo enabled || echo disabled)" | |
| echo "PCIe ASPM: $(cat /sys/module/pcie_aspm/parameters/policy)" | |
| echo "" | |
| echo "GPU: $([ -e /sys/devices/platform/soc@0/15000000.gpu/driver ] && echo bound || echo unbound)" | |
| echo "Display: $(lsmod | grep -q linlon_dp && echo loaded || echo unloaded)" | |
| echo "WiFi: $(/usr/sbin/rfkill list wifi 2>/dev/null | grep -q 'Soft blocked: yes' && echo blocked || echo unblocked)" | |
| echo "Bluetooth: $(/usr/sbin/rfkill list bluetooth 2>/dev/null | grep -q 'Soft blocked: yes' && echo blocked || echo unblocked)" | |
| local usb_bound=0 | |
| for usb in $USB_CONTROLLERS; do | |
| [ -e "/sys/bus/platform/drivers/cdns-usbssp/${usb}.usb-controller" ] && usb_bound=$((usb_bound + 1)) | |
| done | |
| echo "USB: $usb_bound/10 controllers bound" | |
| } | |
| set_baseline() { | |
| echo "=== Setting HIGH Power Baseline ===" | |
| echo "" | |
| echo "This sets all subsystems to maximum power state" | |
| echo "for accurate power savings measurements." | |
| echo "" | |
| # Ensure all CPUs online and at max performance | |
| echo "CPUs: all online, performance governor..." | |
| for cpu in /sys/devices/system/cpu/cpu{1..11}/online; do | |
| echo 1 > "$cpu" 2>/dev/null || true | |
| done | |
| for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| echo performance > "$cpu" 2>/dev/null || true | |
| done | |
| for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do | |
| max=$(cat $(dirname $cpu)/scaling_available_frequencies 2>/dev/null | awk '{print $NF}') | |
| [ -n "$max" ] && echo "$max" > "$cpu" 2>/dev/null || true | |
| done | |
| # Bus frequencies to MAX | |
| echo "Bus freq: MAX..." | |
| ctl_bus_freq max | |
| # DDR LP disabled (full performance) | |
| echo "DDR LP: disabled (full performance)..." | |
| ctl_ddr_lp off | |
| # PCIe ASPM off (default policy) | |
| echo "PCIe ASPM: default..." | |
| ctl_pcie_aspm on | |
| # Ensure all USB controllers bound | |
| echo "USB: binding all controllers..." | |
| ctl_usb on | |
| # Ensure display/GPU loaded | |
| echo "Display/GPU: loading..." | |
| ctl_display on | |
| ctl_gpu on | |
| # WiFi/BT unblocked | |
| echo "WiFi/Bluetooth: unblocking..." | |
| ctl_wifi on | |
| ctl_bluetooth on | |
| echo "" | |
| echo "=== Baseline Set ===" | |
| show_status | |
| echo "" | |
| echo "System is now at MAXIMUM power state." | |
| echo "Wait 10-20s for power to stabilize before measuring." | |
| } | |
| apply_power_savings() { | |
| local headless=${1:-false} | |
| local skip_confirm=${2:-false} | |
| echo "=== Applying Power Savings ===" | |
| [ "$headless" = "true" ] && echo "MODE: HEADLESS" || echo "MODE: WITH DISPLAY" | |
| if [ "$skip_confirm" != "true" ]; then | |
| read -p "Continue? [y/N] " -n 1 -r | |
| echo | |
| [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 | |
| fi | |
| ctl_gdm off | |
| [ "$headless" = "true" ] && ctl_gpu off | |
| [ "$headless" = "true" ] && ctl_display off | |
| [ "$headless" = "true" ] && ctl_wifi off deep || ctl_wifi off | |
| ctl_bluetooth off | |
| [ "$headless" = "true" ] && ctl_usb off || ctl_usb off 9260000 | |
| ctl_audio off | |
| ctl_5gbe off | |
| ctl_pmu off | |
| ctl_pcie_aspm off | |
| ctl_ddr_lp on | |
| ctl_bus_freq min | |
| ctl_cpu off | |
| ctl_misc off | |
| echo "" | |
| echo "=== MINIMUM POWER MODE ACTIVE ===" | |
| echo "To restore: $0 restore" | |
| } | |
| restore_normal() { | |
| echo "=== Restoring Normal Operation ===" | |
| ctl_cpu on | |
| ctl_bus_freq max | |
| ctl_ddr_lp on # Keep LP enabled - it's automatic | |
| ctl_pcie_aspm on | |
| ctl_pmu on | |
| ctl_5gbe on | |
| ctl_misc on | |
| ctl_audio on | |
| ctl_usb on | |
| ctl_bluetooth on | |
| ctl_wifi on | |
| ctl_display on | |
| ctl_gpu on | |
| ctl_gdm on | |
| echo "" | |
| echo "=== Normal Operation Restored ===" | |
| } | |
| powersteps() { | |
| local hold=${1:-20} | |
| echo "==========================================" | |
| echo "=== Step-by-Step Power Measurement ===" | |
| echo "==========================================" | |
| echo "Hold time: ${hold}s per step" | |
| echo "" | |
| echo "This will:" | |
| echo " 1. Set HIGH power baseline (buses MAX, DDR LP off, all CPUs)" | |
| echo " 2. Step through each power saving with ${hold}s holds" | |
| echo "" | |
| read -p "Continue? [y/N] " -n 1 -r | |
| echo | |
| [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 | |
| # Set known high-power baseline first | |
| set_baseline | |
| echo "" | |
| hold_for $hold "BASELINE (max power)" | |
| ctl_gdm off | |
| hold_for $hold "GDM stopped" | |
| ctl_gpu off | |
| hold_for $hold "GPU unloaded" | |
| ctl_display off | |
| hold_for $hold "Display unloaded" | |
| ctl_wifi off deep | |
| hold_for $hold "WiFi disabled + PCIe unbound" | |
| ctl_bluetooth off | |
| hold_for $hold "Bluetooth disabled" | |
| ctl_usb off | |
| hold_for $hold "USB unbound" | |
| ctl_audio off | |
| hold_for $hold "Audio unloaded" | |
| ctl_5gbe off | |
| hold_for $hold "5GbE PCIe ports unbound" | |
| ctl_pmu off | |
| hold_for $hold "PMU modules unloaded" | |
| ctl_pcie_aspm off | |
| hold_for $hold "PCIe ASPM powersupersave" | |
| ctl_ddr_lp on | |
| hold_for $hold "DDR LP enabled" | |
| ctl_bus_freq min | |
| hold_for $hold "Bus freq MIN" | |
| ctl_cpu off | |
| hold_for $hold "CPU powersave + 1 core" | |
| echo "" | |
| echo "=== All steps complete ===" | |
| echo "System at minimum power. To restore: $0 restore" | |
| } | |
| powertest() { | |
| local duration=${1:-30} | |
| echo "=== Bus Frequency Power Test ===" | |
| echo "Duration: ${duration}s per phase" | |
| read -p "Continue? [y/N] " -n 1 -r | |
| echo | |
| [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 | |
| apply_power_savings true true | |
| echo "" | |
| echo "Stabilizing for 10s..." | |
| sleep 10 | |
| [ ! -d "$CI700_DEVFREQ" ] && { echo "ERROR: devfreq not available"; exit 1; } | |
| local ci_freqs=$(cat "$CI700_DEVFREQ/available_frequencies") | |
| local ni_freqs=$(cat "$NI700_DEVFREQ/available_frequencies") | |
| local ci_min=$(echo $ci_freqs | awk '{print $1}') | |
| local ci_max=$(echo $ci_freqs | awk '{print $NF}') | |
| local ni_min=$(echo $ni_freqs | awk '{print $1}') | |
| local ni_max=$(echo $ni_freqs | awk '{print $NF}') | |
| trap 'echo "Interrupted"; restore_normal; exit 0' INT TERM | |
| ctl_bus_freq min | |
| hold_for $duration "Phase 1: MIN/MIN" | |
| ctl_bus_freq max | |
| hold_for $duration "Phase 2: MAX/MAX" | |
| echo "$ci_max" > "$CI700_DEVFREQ/min_freq"; echo "$ci_max" > "$CI700_DEVFREQ/max_freq" | |
| echo "$ni_min" > "$NI700_DEVFREQ/min_freq"; echo "$ni_min" > "$NI700_DEVFREQ/max_freq" | |
| hold_for $duration "Phase 3: CI MAX, NI MIN" | |
| echo "$ci_min" > "$CI700_DEVFREQ/min_freq"; echo "$ci_min" > "$CI700_DEVFREQ/max_freq" | |
| echo "$ni_max" > "$NI700_DEVFREQ/min_freq"; echo "$ni_max" > "$NI700_DEVFREQ/max_freq" | |
| hold_for $duration "Phase 4: CI MIN, NI MAX" | |
| restore_normal | |
| echo "=== Power test complete ===" | |
| } | |
| benchmark() { | |
| echo "=== Memory Bandwidth Benchmark ===" | |
| [ ! -d "$CI700_DEVFREQ" ] && { echo "ERROR: devfreq not available"; exit 1; } | |
| local ci_freqs=$(cat "$CI700_DEVFREQ/available_frequencies") | |
| local ni_freqs=$(cat "$NI700_DEVFREQ/available_frequencies") | |
| local ci_min=$(echo $ci_freqs | awk '{print $1}') | |
| local ci_max=$(echo $ci_freqs | awk '{print $NF}') | |
| local ni_min=$(echo $ni_freqs | awk '{print $1}') | |
| local ni_max=$(echo $ni_freqs | awk '{print $NF}') | |
| run_bw() { | |
| sync; echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true | |
| dd if=/dev/zero of=/dev/null bs=1M count=2048 2>&1 | tail -1 | grep -oP '[\d.]+\s*[GM]B/s' || echo "N/A" | |
| } | |
| echo "" | |
| echo "MAX frequencies:" | |
| ctl_bus_freq max | |
| sleep 1 | |
| echo " Bandwidth: $(run_bw)" | |
| echo "" | |
| echo "MIN frequencies:" | |
| ctl_bus_freq min | |
| sleep 1 | |
| echo " Bandwidth: $(run_bw)" | |
| ctl_bus_freq max | |
| echo "" | |
| echo "=== Benchmark complete ===" | |
| } | |
| #============================================================================= | |
| # Ramdisk Power Test (pivot root, disable NVMe) | |
| #============================================================================= | |
| setup_ramroot() { | |
| echo "Setting up ramdisk root..." | |
| # Use existing tmpfs (/run is already tmpfs) | |
| # Don't mount a new one - just create directory | |
| mkdir -p "$RAMROOT" | |
| # Create directory structure | |
| mkdir -p "$RAMROOT"/{bin,sbin,proc,sys,dev,tmp,oldroot} | |
| mkdir -p "$RAMROOT"/sys/bus/platform/drivers/sky1-pcie | |
| mkdir -p "$RAMROOT"/sys/class/devfreq | |
| mkdir -p "$RAMROOT"/sys/devices/system/cpu | |
| mkdir -p "$RAMROOT"/sys/devices/platform | |
| # Copy busybox and create symlinks | |
| cp /bin/busybox "$RAMROOT/bin/" | |
| for cmd in sh ash cat echo sleep ls mkdir mount umount sync \ | |
| grep awk sed tr head tail printf date seq chroot; do | |
| ln -sf busybox "$RAMROOT/bin/$cmd" | |
| done | |
| # Copy this script | |
| cp "$0" "$RAMROOT/bin/power.sh" | |
| chmod +x "$RAMROOT/bin/power.sh" | |
| # Create restore script | |
| cat > "$RAMROOT/bin/restore.sh" << 'RESTORE_EOF' | |
| #!/bin/busybox sh | |
| # Restore NVMe and pivot back to real root | |
| echo "=== Restoring NVMe and root filesystem ===" | |
| # Rescan PCI bus to re-enumerate NVMe | |
| echo " Rescanning PCI bus..." | |
| echo 1 > /sys/bus/pci/rescan 2>/dev/null | |
| sleep 2 | |
| # Wait for NVMe to appear | |
| echo " Waiting for NVMe..." | |
| for i in $(seq 1 15); do | |
| [ -b /dev/nvme0n1p3 ] && break | |
| sleep 1 | |
| echo " $i..." | |
| done | |
| if [ ! -b /dev/nvme0n1p3 ]; then | |
| echo "ERROR: NVMe device not found!" | |
| echo "Try: echo 1 > /sys/bus/pci/rescan" | |
| echo "Then: mount /dev/nvme0n1p3 /oldroot" | |
| exit 1 | |
| fi | |
| # Mount old root | |
| mount /dev/nvme0n1p3 /oldroot | |
| if [ $? -ne 0 ]; then | |
| echo "ERROR: Failed to mount root filesystem!" | |
| exit 1 | |
| fi | |
| echo " Root filesystem mounted" | |
| # Pivot back | |
| cd /oldroot | |
| mount --move /sys /oldroot/sys | |
| mount --move /proc /oldroot/proc | |
| mount --move /dev /oldroot/dev | |
| pivot_root . tmp/ramroot | |
| exec chroot . /bin/sh -c 'umount /tmp/ramroot 2>/dev/null; exec /bin/bash' | |
| RESTORE_EOF | |
| chmod +x "$RAMROOT/bin/restore.sh" | |
| echo " Ramdisk ready at $RAMROOT" | |
| } | |
| pivot_to_ramroot() { | |
| echo "Switching to ramdisk environment..." | |
| # Sync filesystems | |
| sync | |
| # We don't do a full pivot_root (complex requirements). | |
| # Instead, we'll lazy unmount root and remove NVMe. | |
| # The kernel keeps pages in memory until all refs are gone. | |
| # Make sure we're running from ramdisk copy | |
| if [ ! -f "$RAMROOT/bin/busybox" ]; then | |
| echo "ERROR: Ramdisk not set up properly" | |
| return 1 | |
| fi | |
| # Pre-cache binaries we'll need after unmount | |
| # This loads them into page cache so they stay available | |
| date +%s >/dev/null 2>&1 | |
| seq 1 1 >/dev/null 2>&1 | |
| printf "" 2>&1 | |
| sleep 0.01 2>&1 | |
| lspci >/dev/null 2>&1 | |
| # Export for subshells | |
| export PATH="$RAMROOT/bin:$PATH" | |
| export RAMROOT | |
| echo " Ramdisk environment ready" | |
| echo " Binaries pre-cached for post-unmount operation" | |
| } | |
| stop_background_services() { | |
| echo "Aggressively stopping all services and freezing system..." | |
| # Mask services so systemd won't try to restart them | |
| systemctl mask --runtime systemd-journald rpcbind nfs-server \ | |
| irqbalance cron cups avahi-daemon ModemManager NetworkManager \ | |
| wpa_supplicant polkit accounts-daemon udisks2 colord thermald \ | |
| packagekit rsyslog 2>/dev/null || true | |
| # Stop all non-essential services | |
| systemctl stop irqbalance cron cups rpcbind avahi-daemon \ | |
| ModemManager NetworkManager wpa_supplicant polkit \ | |
| accounts-daemon udisks2 colord thermald packagekit \ | |
| nfs-server nfs-mountd nfs-idmapd 2>/dev/null || true | |
| # Stop logging | |
| systemctl stop systemd-journald rsyslog 2>/dev/null || true | |
| # Kill absolutely everything except essential processes | |
| # Get our own PID and parent PIDs to avoid killing ourselves | |
| local my_pid=$$ | |
| local my_ppid=$(ps -o ppid= -p $my_pid | tr -d ' ') | |
| # Kill all user processes | |
| pkill -9 -u radxa 2>/dev/null || true | |
| # Kill specific troublemakers | |
| pkill -9 gvfsd gvfs dconf at-spi ibus evolution tracker gmain 2>/dev/null || true | |
| pkill -9 -f "/usr/lib" 2>/dev/null || true | |
| # Send SIGSTOP to all processes except our shell and kernel threads | |
| echo " Freezing remaining processes..." | |
| for pid in $(ps -eo pid= | tr -d ' '); do | |
| # Skip kernel threads (ppid=2), our process, and PID 1 | |
| [ "$pid" = "1" ] && continue | |
| [ "$pid" = "2" ] && continue | |
| [ "$pid" = "$my_pid" ] && continue | |
| [ "$pid" = "$my_ppid" ] && continue | |
| ppid=$(ps -o ppid= -p $pid 2>/dev/null | tr -d ' ') | |
| [ "$ppid" = "2" ] && continue # kernel thread | |
| kill -STOP $pid 2>/dev/null || true | |
| done | |
| echo " All processes frozen" | |
| } | |
| unmount_nvme() { | |
| echo "Unmounting NVMe filesystem..." | |
| # Sync and drop caches first | |
| sync | |
| echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true | |
| # Lazy unmount the old root | |
| umount -l /oldroot 2>/dev/null || true | |
| echo " NVMe unmounted" | |
| } | |
| unbind_nvme_pcie() { | |
| echo "Removing NVMe from PCI bus..." | |
| # Find and remove NVMe device (class 0108 = Non-Volatile memory controller) | |
| for dev in /sys/bus/pci/devices/*; do | |
| class=$(cat "$dev/class" 2>/dev/null) | |
| # Class 0x010802 = NVMe controller | |
| if [ "${class:0:6}" = "0x0108" ]; then | |
| devname=$(basename $dev) | |
| echo 1 > "$dev/remove" 2>/dev/null || true | |
| echo " NVMe removed: $devname" | |
| return | |
| fi | |
| done | |
| echo " NVMe device not found (already removed?)" | |
| } | |
| ramtest() { | |
| local duration=${1:-60} | |
| echo "==========================================" | |
| echo "=== RAMDISK POWER TEST ===" | |
| echo "==========================================" | |
| echo "" | |
| echo "This will:" | |
| echo " 1. Apply all power savings (headless mode)" | |
| echo " 2. Lazy-unmount root filesystem" | |
| echo " 3. Remove NVMe from PCI bus" | |
| echo " 4. Hold for ${duration}s for measurement" | |
| echo " 5. Restore NVMe (rescan PCI bus)" | |
| echo "" | |
| echo "WARNING: This is experimental! Have serial console ready." | |
| echo " If something goes wrong, you'll need to reboot." | |
| echo "" | |
| read -p "Continue? [y/N] " -n 1 -r | |
| echo | |
| [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 | |
| # Step 1: Setup ramdisk while we still have full system | |
| setup_ramroot | |
| # Step 2: Apply power savings (except NVMe-related) | |
| echo "" | |
| echo "=== Applying power savings ===" | |
| ctl_gdm off | |
| ctl_gpu off | |
| ctl_display off | |
| ctl_wifi off deep | |
| ctl_bluetooth off | |
| ctl_usb off | |
| ctl_audio off | |
| ctl_5gbe off | |
| ctl_pmu off | |
| ctl_pcie_aspm off | |
| ctl_ddr_lp on | |
| ctl_bus_freq min | |
| ctl_cpu off | |
| ctl_misc off | |
| echo "" | |
| echo "=== Preparing ramdisk ===" | |
| pivot_to_ramroot | |
| echo "" | |
| echo "=== Stopping background services ===" | |
| stop_background_services | |
| echo "" | |
| echo "=== Disabling NVMe ===" | |
| unmount_nvme | |
| unbind_nvme_pcie | |
| echo "" | |
| echo "==========================================" | |
| echo "=== MINIMUM POWER - NVMe REMOVED ===" | |
| echo "==========================================" | |
| echo "All subsystems disabled including NVMe." | |
| echo "Root filesystem lazy-unmounted (cached in RAM)." | |
| echo "" | |
| echo ">>> Measure power now!" | |
| echo "" | |
| echo "Press ENTER to restore NVMe and exit..." | |
| read dummy | |
| echo "" | |
| echo "=== Restoring NVMe ===" | |
| echo 1 > /sys/bus/pci/rescan 2>/dev/null || true | |
| # Use bash built-in for delay (read with timeout) | |
| read -t 3 dummy 2>/dev/null || true | |
| # NVMe should be back after rescan | |
| echo " PCI rescan complete" | |
| echo "" | |
| echo "==========================================" | |
| echo "Test complete. Please REBOOT to restore system." | |
| echo "==========================================" | |
| } | |
| #============================================================================= | |
| # Main | |
| #============================================================================= | |
| case "${1:-}" in | |
| status) show_status ;; | |
| baseline) set_baseline ;; | |
| apply) apply_power_savings false ;; | |
| headless) apply_power_savings true ;; | |
| restore) restore_normal ;; | |
| benchmark) benchmark ;; | |
| powertest) powertest "${2:-30}" ;; | |
| powersteps) powersteps "${2:-20}" ;; | |
| ramtest) ramtest "${2:-60}" ;; | |
| ctl) | |
| { [ -z "$2" ] || [ -z "$3" ]; } && { echo "Usage: $0 ctl <name> <on|off>"; exit 1; } | |
| case "$2" in | |
| gdm) ctl_gdm "$3" ;; | |
| gpu) ctl_gpu "$3" ;; | |
| display) ctl_display "$3" ;; | |
| wifi) ctl_wifi "$3" ;; | |
| bluetooth) ctl_bluetooth "$3" ;; | |
| usb) ctl_usb "$3" ;; | |
| audio) ctl_audio "$3" ;; | |
| pcie) ctl_pcie_aspm "$3" ;; | |
| ddr) ctl_ddr_lp "$3" ;; | |
| bus) ctl_bus_freq "$3" ;; # min/max instead of on/off | |
| cpu) ctl_cpu "$3" ;; | |
| misc) ctl_misc "$3" ;; | |
| 5gbe) ctl_5gbe "$3" ;; | |
| pmu) ctl_pmu "$3" ;; | |
| *) echo "Unknown: $2"; exit 1 ;; | |
| esac | |
| ;; | |
| *) usage ;; | |
| esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment