#!/bin/sh
# tlp-stat - display power saving details
#
# Copyright (c) 2023 Thomas Koch <linrunner at gmx.net> and others.
# SPDX-License-Identifier: GPL-2.0-or-later

# --- Source libraries

for lib in /usr/share/tlp/tlp-func-base /usr/share/tlp/func.d/[0-9][0-9]* /usr/share/tlp/func.d/tlp-func-stat; do
    # shellcheck disable=SC1090
    . "$lib" || exit 70
done

# --- Constants

readonly TLPUSB=/usr/share/tlp/tlp-usblist
readonly TLPPCI=/usr/share/tlp/tlp-pcilist

readonly JOURNALCTL=journalctl

readonly ASPM=/sys/module/pcie_aspm/parameters/policy
readonly EFID=/sys/firmware/efi
readonly NMIWD=/proc/sys/kernel/nmi_watchdog
readonly OSRELEASE=/etc/os-release
readonly SWITCHEROO=/sys/kernel/debug/vgaswitcheroo/switch
readonly WQPE=/sys/module/workqueue/parameters/power_efficient

readonly CORETEMP_DIRS="
/sys/devices/platform/coretemp.0
/sys/devices/platform/coretemp.0/hwmon/hwmon*"
readonly HWMONFAN_DIRS="
/sys/class/hwmon/hwmon*/device
/sys/class/hwmon/hwmon*"
readonly IBMFAN=/proc/acpi/ibm/fan
readonly IBMTHERMAL=/proc/acpi/ibm/thermal

readonly DEBUGLOG=/var/log/debug

# --- Variables

needs_root_priv=
show_all=1
show_bat=0
show_conf=0
show_disk=0
show_graf=0
show_pcie=0
show_pev=0
show_proc=0
show_psup=0
show_rfkill=0
show_system=0
show_temp=0
show_trace=0
show_udev=0
show_usb=0
show_verbose=0
show_warn=0

# --- Functions

parse_args () { # parse command-line -- $@: arguments to parse

    # iterate arguments until delimiter '--' reached
    while [ $# -gt 0 ]; do
        case "$1" in
            "-b"|"--battery")
                show_all=0
                show_bat=1
                needs_root_priv=1
                ;;

            "-c"|"--config")
                show_all=0
                show_conf=1
                : ${needs_root_priv:=0}
                ;;

            "--cdiff")
                show_all=0
                show_cdiff=1
                : ${needs_root_priv:=0}
                ;;

            "-d"|"--disk")
                show_all=0
                show_disk=1
                needs_root_priv=1
                ;;

            "-e"|"--pcie")
                show_all=0
                show_pcie=1
                : ${needs_root_priv:=0}
                ;;

            "-g"|"--graphics")
                show_all=0
                show_graf=1
                needs_root_priv=1
                ;;

            "-p"|"--processor")
                show_all=0
                show_proc=1
                needs_root_priv=1
                ;;

            "-r"|"--rfkill")
                show_all=0
                show_rfkill=1
                : ${needs_root_priv:=0}
                ;;

            "-s"|"--system")
                show_all=0
                show_system=1
                : ${needs_root_priv:=0}
                ;;

            "-t"|"--temp")
                show_all=0
                show_temp=1
                : ${needs_root_priv:=0}
                ;;

            "-u"|"--usb")
                show_all=0
                show_usb=1
                : ${needs_root_priv:=0}
                ;;

            "-v"|"--verbose")
                show_verbose=1
                ;;

            "-w"|"--warn")
                show_all=0
                show_warn=1
                : ${needs_root_priv:=1}
                ;;

            "-P"|"--pev")
                show_all=0
                show_pev=1
                needs_root_priv=1
                ;;

            "--psup")
                show_all=0
                show_psup=1
                : ${needs_root_priv:=0}
                ;;

            "-T"|"--trace")
                show_all=0
                show_trace=1
                needs_root_priv=1
                ;;

            "--udev")
                show_all=0
                show_udev=1
                : ${needs_root_priv:=0}
                ;;

            "--") # config values follow --> quit loop
                break
                ;;

            *)
                echo "Usage: tlp-stat [ -b | --battery   | -c | --config    |"
                echo "                  -d | --disk      | -e | --pcie      |"
                echo "                  -g | --graphics  | -p | --processor |"
                echo "                  -r | --rfkill    | -s | --system    |"
                echo "                  -t | --temp      | -u | --usb       |"
                echo "                  -w | --warn      | -v | --verbose   |"
                echo "                     | --cdiff     |    | --pev       |"
                echo "                  -P | --psup      | -T | --trace     |"
                echo "                     | --udev ]"
                do_exit 3
                ;;
        esac

        shift # next argument
    done # while arguments

    return 0
}

# --- MAIN
# read configuration; continue on error, no trace
read_config 0 1

parse_args "$@"
parse_args4config "$@"

add_sbin2path
: ${needs_root_priv:=1}

# inhibit trace output (unless forced)
# shellcheck disable=SC2034
[ "$X_TRACE_TLP_STAT" = "1" ] || _nodebug=1

# check prerequisites
if [ "$needs_root_priv" = "1" ]; then
    check_root
    # shellcheck disable=SC2086
    load_modules $MOD_MSR $MOD_TEMP
fi
get_sys_power_supply

echo "--- TLP $TLPVER --------------------------------------------"
echo

# --- show configuration
if [ "$show_conf" = "1" ] || [ "$show_all" = "1" ]; then
    echo "+++ Configured Settings:"
    $READCONFS --notrace
    echo
fi # show_conf

if [ "$show_cdiff" = "1" ]; then
    echo "+++ Configured Settings (only differences to defaults):"
    $READCONFS --notrace --cdiff
    echo
fi # show_conf

if [ "$show_system" = "1" ] || [ "$show_all" = "1" ] ; then
    # --- show system info
    # simulate arbitrary model
    if [ -z "$X_SIMULATE_MODEL" ]; then
        product_version="$(read_dmi product_version)"
        product_name="$(read_dmi product_name)"
        if printf '%s' "$product_name" | grep -E -q 'Think[Pp]ad'; then
            model="$product_name"
        else
            model="$product_version $product_name"
        fi
    else
        model="$X_SIMULATE_MODEL"
    fi
    echo "+++ System Info"
    echo "System         = $(read_dmi sys_vendor) $model"
    echo "BIOS           = $(read_dmi bios_version)"
    ecfw="$(read_dmi ec_firmware_release)" && printf "EC Firmware    = %s\n" "$ecfw"

    # --- show release & kernel info
    printf "OS Release     = "
    if ! sed -rn 's/PRETTY_NAME="(.*)"/\1/p' $OSRELEASE 2> /dev/null; then
        echo "unknown"
    fi
    echo "Kernel         = $(uname -r -m -v)"
    printparm "%-14s = %s" /proc/cmdline

    # --- show init system info
    if check_systemd; then
        echo "Init system    = systemd $(systemd --version 2> /dev/null | sed -rn 's/systemd ([0-9]+)/v\1/p')"
    elif check_upstart; then
        echo "Init system    = upstart"
    elif check_openrc; then
        echo "Init system    = openrc"
    else
        echo "Init system    = sysvinit"
    fi
    if [ -d $EFID ]; then
        echo "Boot mode      = UEFI"
    else
        echo "Boot mode      = BIOS (CSM, Legacy)"
    fi
    printf   "Suspend mode   = %s\n" "$(read_sysf "$SLEEPMODE" "(not available)")"
    print_selinux
    echo

    # --- show TLP status
    echo "+++ TLP Status"
    if check_tlp_enabled; then
        printf "State          = enabled\n"
    else
        printf "State          = disabled\n"
    fi

    # --- show RDW status
    if check_rdw_installed; then
        if ! check_tlp_enabled; then
            printf "RDW state      = disabled (TLP disabled)\n"
        elif check_run_flag "$RDW_KILL"; then
            printf "RDW state      = disabled\n"
        else
            printf "RDW state      = enabled\n"
        fi
    else
        printf "RDW state      = not installed\n"
    fi

    # --- show last invocation time
    printf "Last run       = %s\n" "$(print_file_modtime_and_age "$PWRRUNFILE")"

    # --- show actual power mode
    printf "Mode           = %s\n" "$(print_saved_powerstate)"

    # ---- show actual power source
    get_sys_power_supply
    case $? in
        0) printf "Power source   = AC\n" ;;
        1) printf "Power source   = battery\n" ;;
        *) printf "Power source   = unknown\n" ;;
    esac
    if check_ac_quirk "$model"; then
        echo "Notice: system may not detect AC/charger -- see: https://linrunner.de/faq/operation.html#faq-ac-quirk"
    fi
    echo

    # -- check systemd services
    check_services_activation_status

    # -- check for power-profiles-daemon
    if check_ppd_active; then
        echo_message "Warning: TLP's power saving will not apply on boot because the conflicting $PPD_SERVICE is active "`
                    `"--> Uninstall power-profiles-daemon or invoke 'systemctl mask $PPD_SERVICE to ensure the full functionality of TLP." "err"
        echo_message "" "err"
    fi

    # -- show warning if l-m-t detected
    check_laptop_mode_tools

fi # show_system

if [ "$show_proc" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show cpu info
    echo "+++ Processor"
    sed -rn 's/model name[ \t]+: (.+)/CPU model      = \1/p' /proc/cpuinfo | head -1
    echo

    # -- show scaling gov and freq info
    cnt=0
    epp=0
    cpu2nd=""
    cpulast=""
    for cn in $(glob_dirs '/cpu[0-9]*' "$CPUD" | sed 's/.*\/cpu//' | sort -n); do

        cpuf="${CPUD}/cpu${cn}/cpufreq"
        cpua="${CPUD}/cpu${cn}/acpi_cppc"
        if [ -f "$cpuf/scaling_driver" ]; then
            cpu="cpu${cn}"

            # show only cpu0, unless verbose mode is enabled
            if [ $cnt -eq 0 ] || [ "$show_verbose" = "1" ] ; then
                printparm "%-54s = ##%s##" "$cpuf/scaling_driver"
                printparm "%-54s = ##%s##" "$cpuf/scaling_governor"
                printparm "%s = ##%s##" "$cpuf/scaling_available_governors" _

                if [ -f "$cpuf/scaling_min_freq" ]; then
                    printf "%-54s = %8d [kHz]\n" "$cpuf/scaling_min_freq" "$(read_sysf "$cpuf/scaling_min_freq")"
                fi
                if [ -f "$cpuf/scaling_max_freq" ]; then
                    printf "%-54s = %8d [kHz]\n" "$cpuf/scaling_max_freq" "$(read_sysf "$cpuf/scaling_max_freq")"
                fi
                if [ -f "$cpuf/scaling_available_frequencies" ]; then
                    printf "%s = " "$cpuf/scaling_available_frequencies"
                    for freq in $(read_sysf "$cpuf/scaling_available_frequencies"); do
                        printf "%s " "$freq"
                    done
                    printf "[kHz]\n"
                fi
                if [ -f "$cpuf/cpuinfo_min_freq" ]; then
                    printf "%-54s = %8d [kHz]\n" "$cpuf/cpuinfo_min_freq" "$(read_sysf "$cpuf/cpuinfo_min_freq")"
                fi
                if [ -f "$cpuf/cpuinfo_max_freq" ]; then
                    printf "%-54s = %8d [kHz]\n" "$cpuf/cpuinfo_max_freq" "$(read_sysf "$cpuf/cpuinfo_max_freq")"
                fi
                if [ -f "$cpuf/bios_limit" ]; then
                    printf "%-54s = %8d [kHz]\n" "$cpuf/bios_limit" "$(read_sysf "$cpuf/bios_limit")"
                fi

                if [ -f "$cpuf/energy_performance_preference" ]; then
                    printparm "%s = ##%s## [EPP]" "$cpuf/energy_performance_preference"
                    epp=1
                fi
                if [ -f "$cpuf/energy_performance_available_preferences" ]; then
                    printparm "%s = ##%s##" "$cpuf/energy_performance_available_preferences"
                fi

                if [ $show_verbose -eq 1 ]; then
                    if [ -f "$cpuf/amd_pstate_highest_perf" ]; then
                        printf "%-70s = %8d [%%]\n" "$cpuf/amd_pstate_highest_perf" "$(read_sysf "$cpuf/amd_pstate_highest_perf")"
                    fi
                    if [ -f "$cpuf/amd_pstate_max_freq" ]; then
                        printf "%-70s = %8d [kHz]\n" "$cpuf/amd_pstate_max_freq" "$(read_sysf "$cpuf/amd_pstate_max_freq")"
                    fi
                    if [ -f "$cpuf/amd_pstate_lowest_nonlinear_freq" ]; then
                        printf "%-70s = %8d [kHz]\n" "$cpuf/amd_pstate_lowest_nonlinear_freq" "$(read_sysf "$cpuf/amd_pstate_lowest_nonlinear_freq")"
                    fi
                    if [ -f "$cpua/lowest_perf" ]; then
                        printf "%-61s = %5d\n" "$cpua/lowest_perf" "$(read_sysf "$cpua/lowest_perf")"
                    fi
                    if [ -f "$cpua/lowest_nonlinear_perf" ]; then
                        printf "%-61s = %5d\n" "$cpua/lowest_nonlinear_perf" "$(read_sysf "$cpua/lowest_nonlinear_perf")"
                    fi
                    if [ -f "$cpua/nominal_perf" ]; then
                        printf "%-61s = %5d\n" "$cpua/nominal_perf" "$(read_sysf "$cpua/nominal_perf")"
                    fi
                    if [ -f "$cpua/reference_perf" ]; then
                        printf "%-61s = %5d\n" "$cpua/reference_perf" "$(read_sysf "$cpua/reference_perf")"
                    fi
                    if [ -f "$cpua/highest_perf" ]; then
                        printf "%-61s = %5d\n" "$cpua/highest_perf" "$(read_sysf "$cpua/highest_perf")"
                    fi
                    if [ -f "$cpua/lowest_freq" ]; then
                        printf "%-61s = %5d [MHz]\n" "$cpua/lowest_freq" "$(read_sysf "$cpua/lowest_freq")"
                    fi
                    if [ -f "$cpua/nominal_freq" ]; then
                        printf "%-61s = %5d [MHz]\n" "$cpua/nominal_freq" "$(read_sysf "$cpua/nominal_freq")"
                    fi
                fi

                printf "\n"
            fi

            if [ $cnt -eq 1 ]; then
                # remember 2nd cpu core
                cpu2nd=$cpu
            else
                # remember last cpu core
                cpulast=$cpu
            fi
            cnt=$((cnt + 1))
        fi
    done

    if [ "$show_verbose" = "0" ] && [ $cnt -gt 1 ]; then
        printf "%s/%s..%s: omitted for clarity, use -v to show all\n\n" "$CPUD" "$cpu2nd" "$cpulast"
    fi

    if check_intel_pstate; then
        # show Intel P-state info
        printparm "%-54s = ##%s##"       "$INTEL_PSTATED/status"
        printparm "%-54s = ##%3d## [%%]" "$CPU_MIN_PERF_PCT"
        printparm "%-54s = ##%3d## [%%]" "$CPU_MAX_PERF_PCT"
        printparm "%-54s = ##%3d##"      "$CPU_TURBO_PSTATE"
        printparm "%-54s = ##%3d##"      "$INTEL_DYN_BOOST"
        printparm "%-54s = ##%3d## [%%]" "$INTEL_PSTATED/turbo_pct"
        printparm "%-54s = ##%3d##"      "$INTEL_PSTATED/num_pstates"

    elif check_amd_pstate; then
        # show AMD P-state info
        printparm "%-54s = ##%s##" "$AMD_PSTATED/status"
        printparm "%-54s = ##%s##" "$AMD_DYN_BOOST"
        if [ -f "$CPU_BOOST_ALL_CTRL" ]; then
            printparm "%-54s = ##%d##" "$CPU_BOOST_ALL_CTRL" "not available"
        fi

    elif [ -f "$CPU_BOOST_ALL_CTRL" ]; then
        # show turbo boost info
        boost=$(read_sysval "$CPU_BOOST_ALL_CTRL")

        # simple test for attribute "w" doesn't work, so actually write
        if write_sysf "$boost" "$CPU_BOOST_ALL_CTRL"; then
            printparm "%-54s = ##%d##" "$CPU_BOOST_ALL_CTRL"
        else
            printparm "%-54s = ##%d## (CPU not supported)" "$CPU_BOOST_ALL_CTRL"
        fi

    else
         printparm "%-54s = (not available)" "$CPU_BOOST_ALL_CTRL"
    fi

    # --- show Intel energy and performance bias hint (EPB)
    if check_intel_pstate; then
        if [ $epp -eq 0 ] || [ "$X_FORCE_EPB" = "1" ]; then
            # show only when HWP.EPP not present (or forced)
            if supports_intel_cpu_epb; then
                # CPU supports EPB: try native kernel API first (5.2 and later)
                cnt=0
                for cn in $(glob_dirs '/cpu[0-9]*' "$CPUD" | sed 's/.*\/cpu//' | sort -n); do
                    cpuf="${CPUD}/cpu${cn}/power/energy_perf_bias"
                    if [ -f "$cpuf" ]; then
                        # show only cpu0, unless verbose mode is enabled
                        if [ $cnt -eq 0 ] || [ "$show_verbose" = "1" ]; then
                            printparm_epb "$cpuf"
                        fi
                        cnt=$((cnt + 1))
                    fi
                done
                if [ $cnt -gt 0 ]; then
                    # native kernel API actually detected
                    printf "\n"
                else
                    # no native kernel API --> parse x86_energy_perf_policy output
                    if cmd_exists "$ENERGYPERF"; then
                        # check CPU support
                        eperf=$($ENERGYPERF -r 2> /dev/null)
                        case $? in
                            0)  if [ -n "$eperf" ]; then
                                    # parse x86_energy_perf_policy output:
                                    # - replace numbers with descriptive strings
                                    # - remove ":"
                                    # - indent and align
                                    $ENERGYPERF -r 2>/dev/null | grep -v 'HWP_' | \
                                        sed -r 's/://;
                                                s/(0x0000000000000000|EPB 0)/0 \(performance\)/;
                                                s/(0x0000000000000004|EPB 4)/4 \(balance_performance\)/;
                                                s/(0x0000000000000006|EPB 6)/6 \(default\)/;
                                                s/(0x0000000000000008|EPB 8)/8 \(balance_power\)/;
                                                s/(0x000000000000000f|EPB 15)/15 \(power\)/' | \
                                        awk '{ printf "x86_energy_perf_policy.%-31s = %s %s [EPB]\n", $1, $2, $3; }'
                                    printf "\n"
                                fi
                                ;;

                            2) printf "x86_energy_perf_policy: program for your kernel not installed.\n\n" ;;
                            *) printf "x86_energy_perf_policy: not available.\n\n" ;;
                        esac
                    else
                        echo "x86_energy_perf_policy: program not installed."
                        echo
                    fi
                fi
            else
                # CPU does not support EPB
                printf "Intel EPB: unsupported CPU.\n\n"
            fi
        fi
    fi

    # --- show workqueue power efficient status
    printparm "%-54s = ##%s##" "$WQPE"

    # --- show nmi watchdog
    printparm "%-54s = ##%d##" "$NMIWD"
    echo

    # --- show platform profile
    printf "+++ Platform Profile\n"
    printparm "%-54s = ##%s##" "$FWACPID/platform_profile"
    printparm "%-54s = ##%s##" "$FWACPID/platform_profile_choices"
    [ -d "$TPACPID" ] && printparm "%-54s = ##%s##" "$TPACPID/dytc_lapmode"
    printf "\n"

fi # show_proc

if [ "$show_temp" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show temperatures
    echo "+++ Temperatures"
    if [ -f "$IBMTHERMAL" ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then
        # use ThinkPad-specific sysfile
        echo "$IBMTHERMAL = $(read_sysf $IBMTHERMAL | cut -f2  ) [°C]"
    else
        # use coretemp sensors
        cmax=0
        # find max value of all packages (just in case there are several)
        # temp1_input is "Package"
        for sens in $(glob_files '/temp1_input' "$CORETEMP_DIRS"); do
            ctemp=$(read_sysval "$sens")
            [ "$ctemp" -gt "$cmax" ] && cmax=$ctemp
        done
        if [ "$cmax" -gt 0 ]; then
            perl -e 'printf ("CPU temp               = %5d [°C]\n", '"$cmax"' / 1000.0);'
        fi
    fi

    # --- show fan speed
    if [ -f $IBMFAN ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then
        # use thinkpad-specific sysfile
        awk '$1 ~ /speed:/ { printf "'$IBMFAN'     = %5d [/min]\n", $2 }' $IBMFAN
    else
        # use hwmon
        have_any_fan=
        # shellcheck disable=SC2086
        for fan in $(glob_files '/fan?*_input' $HWMONFAN_DIRS); do
            if fan_speed=$(read_sysval "$fan"); then
                fan_name="${fan##*/}"; fan_name="${fan_name%_input}"
                have_any_fan=y

                printf "Fan speed (%s)       = %5d [/min]\n" \
                    "${fan_name}" "${fan_speed}"
            fi
        done
        if [ -z "${have_any_fan}" ]; then
            printf "Fan speed              = (not available)\n"
        fi
    fi
    echo
fi # show_temp

if [ "$show_all" = "1" ]; then
    # --- show laptop-mode, dirty buffers params
    echo "+++ File System"
    printparm "%-38s = ##%5d##" /proc/sys/vm/laptop_mode
    printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_writeback_centisecs
    printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_expire_centisecs
    printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_ratio
    printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_background_ratio
    printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/age_buffer_centisecs _
    printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/xfssyncd_centisecs _
    printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/xfsbufd_centisecs _
    echo
fi # show_all

if [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show disk info
    echo "+++ Disks"
    # list for storage device iteration
    disklist="$DISK_DEVICES"
    # list for output: print "(disabled)" when empty
    diskstat="${DISK_DEVICES:-(disabled)}"
    printf "Devices = %s\n" "$diskstat"

    # iterate over list
    if [ -n "$disklist" ]; then
        for dev in $disklist; do # iterate all devices
            show_disk_data "$dev"
        done
    fi
    echo

    # --- show sata alpm mode
    if stat -t "${ALPMD}/link_power_management_policy" > /dev/null 2>&1; then
        echo "+++ AHCI Link Power Management (ALPM) :: SATA Links"
        for i in ${ALPMD} ; do
            printparm_ahci "$i/link_power_management_policy"
        done
        echo
    fi

    # --- show ahci runtime pm
    if stat -t "${AHCID}/power" > /dev/null 2>&1; then
        echo "+++ AHCI Port Runtime Power Management :: SATA/ATA Ports"
        for dev in ${AHCID}/power ; do
            printparm_ahci "$dev/control"
        done
        echo
    fi

    # -- show docks
    cnt=0
    for dock in $DOCKGLOB; do
        [ ! -d "$dock" ] && break # no dock/bay detected

        # dock/bay detected, print header
        [ $cnt -eq 0 ] && echo "+++ Docks and Device Bays"
        cnt=$((cnt+1))

        # get dock type
        { read -r dock_type < "$dock/type"; } 2>/dev/null

        # get dock state
        if check_is_docked; then
            # docked
            case $dock_type in
                ata_bay)      dock_state="drive present" ;;
                battery_bay)  dock_state="battery present" ;;
                dock_station) dock_state="docked" ;;

                *)  dock_state="docked"
                    dock_type="unknown"
                    ;;
            esac
        else
            # not docked
            case $dock_type in
                ata_bay)      dock_state="no drive (or powered off)" ;;
                battery_bay)  dock_state="no battery " ;;
                dock_station) dock_state="undocked" ;;

                *)  dock_state="undocked"
                    dock_type="unknown"
                    ;;
            esac
        fi

        # print dock data
        printf "%s: %-13s = %s\n" "$dock" "$dock_type" "$dock_state"
    done
    [ $cnt -gt 0 ] && echo
fi # show_disk

if [ "$show_graf" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show hybrid graphics switch (nouveau, radeon only)
    if [ -f $SWITCHEROO ]; then
        echo "+++ Hybrid Graphics Switch"
        printparm_ml "  " $SWITCHEROO
    fi

    # --- show GPU date for all drivers
    show_gpu_data

fi # show_graf

if [ "$show_rfkill" = "1" ] || [ "$show_all" = "1" ]; then
    echo "+++ Wireless"
    # --- show rfkill state
    for i in bluetooth nfc wifi wwan; do
        get_devc $i
        get_devs $i
        # shellcheck disable=SC2154
        echo_device_state "$i" "$_devs"
    done
    echo

    ifshown=0

    # --- show bluetooth
    get_bluetooth_ifaces
    # shellcheck disable=SC2154
    for iface in $_bifaces; do
        if [ -n "$iface" ]; then
            ifshown=1

            # get bluetooth driver
            get_bluetooth_driver "$iface"
            printf "%-30s: bluetooth, " "$iface($_btdrv)"
            if bluetooth_in_use "$iface"; then
                echo "connected"
            else
                echo "not connected"
            fi
        fi
    done

    # --- show wifi data
    get_wifi_ifaces
    # shellcheck disable=SC2154
    for iface in $_wifaces; do
        if [ -n "$iface" ]; then
            ifshown=1

            # get wifi power mgmt state
            if cmd_exists "$IW"; then
                wifipm=$($IW dev "$iface" get power_save 2> /dev/null | \
                    grep 'Power save' | \
                    sed -r 's/.*Power save: (on|off).*/\1/')
            else
                wifipm=""
            fi

            # get wifi driver
            get_wifi_driver "$iface"
            printf "%-30s: wifi, " "$iface($_wifidrv)"
            if wireless_in_use "$iface"; then
                printf "connected, "
            else
                printf "not connected, "
            fi
            printf "power management = "
            case $wifipm in
                on|off) printf "%s" "$wifipm" ;;
                *)      printf "unknown" ;;
            esac
            printf "\n"
        fi
    done

    # --- show wwan data
    get_wwan_ifaces
    # shellcheck disable=SC2154
    for iface in $_wanifaces; do
        if [ -n "$iface" ]; then
            ifshown=1

            # get wwan driver
            get_wwan_driver "$iface"

            printf "%-30s: wwan, " "$iface($_wwandrv)"
            if wireless_in_use "$iface"; then
                printf "connected"
            else
                printf "not connected"
            fi
            printf "\n"
        fi
    done
    [ "$ifshown" = "1" ] && echo

fi # show_rfkill

if [ "$show_all" = "1" ]; then
    # --- show sound power mode
    echo "+++ Audio"
    if [ -d /sys/module/snd_hda_intel ]; then
        printparm "%-58s = ##%s##" "/sys/module/snd_hda_intel/parameters/power_save"
        printparm "%-58s = ##%s##" "/sys/module/snd_hda_intel/parameters/power_save_controller"
    fi
    if [ -d /sys/module/snd_ac97_codec ]; then
        printparm "%s = ##%s##" "/sys/module/snd_ac97_codec/parameters/power_save"
    fi
    echo
fi # show_all

if [ "$show_pcie" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show pcie aspm state
    echo "+++ PCIe Active State Power Management"
    if [ -f $ASPM ]; then
        pol=$(read_sysf $ASPM | sed -r 's/[[:space:]]+$//')
        apol=$(printf "%s" "$pol" | sed -r 's/.*\[(.*)\].*/\1/')
        if write_sysf "$apol" $ASPM; then
            echo "$ASPM = $pol"
        else
            echo "$ASPM = $pol (using BIOS preferences)"
        fi
    else
        echo "$ASPM = (not available)"
    fi
    echo

    # -- show runtime pm
    echo "+++ PCIe Runtime Power Management"
    echo "Enable devices    = ${RUNTIME_PM_ENABLE:-(disabled)}"
    echo "Disable devices   = ${RUNTIME_PM_DISABLE:-(disabled)}"
    echo "Device denylist   = ${RUNTIME_PM_DENYLIST:=(disabled)}"
    echo "Driver denylist   = ${RUNTIME_PM_DRIVER_DENYLIST:-(disabled)}"
    echo

    if cmd_exists $TLPPCI; then
        if [ $show_verbose -eq 1 ]; then
            $TLPPCI --verbose
        else
            $TLPPCI
        fi
    else
        echo "Error: missing subcommand $TLPPCI." 1>&2
    fi
    echo
fi # show_pcie

if [ "$show_usb" = "1" ] || [ "$show_all" = "1" ]; then
    # -- show usb autosuspend
    echo "+++ USB"
    if [ "$USB_AUTOSUSPEND" = "1" ]; then
        echo "Autosuspend       = enabled"
    else
        echo "Autosuspend       = disabled"
    fi
    echo "Device allowlist  = ${USB_ALLOWLIST:=(not configured)}"
    echo "Device denylist   = ${USB_DENYLIST:=(not configured)}"
    if [ "${USB_EXCLUDE_AUDIO:-0}" = "1" ]; then
        echo "Exclude audio     = enabled"
    else
        echo "Exclude audio     = disabled"
    fi
    if [ "${USB_EXCLUDE_BTUSB:-0}" = "1" ]; then
        echo "Exclude bluetooth = enabled"
    else
        echo "Exclude bluetooth = disabled"
    fi
    if [ "${USB_EXCLUDE_PHONE:-0}" = "1" ]; then
        echo "Exclude phones    = enabled"
    else
        echo "Exclude phones    = disabled"
    fi
    if [ "${USB_EXCLUDE_PRINTER:-0}" = "1" ]; then
        echo "Exclude printers  = enabled"
    else
        echo "Exclude printers  = disabled"
    fi
    if [ "${USB_EXCLUDE_WWAN:-1}" = "1" ]; then
        echo "Exclude WWAN      = enabled"
    else
        echo "Exclude WWAN      = disabled"
    fi
    echo

    if cmd_exists $TLPUSB; then
        if [ $show_verbose -eq 1 ]; then
            $TLPUSB --verbose
        else
            $TLPUSB
        fi
    else
        echo "Error: missing subcommand $TLPUSB." 1>&2
    fi
    echo

fi # show_usb

if [ "$show_bat" = "1" ] || [ "$show_all" = "1" ]; then
    select_batdrv
    batdrv_show_battery_data $show_verbose
fi # show_bat

if [ "$show_warn" = "1" ] || [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then
    # --- show warnings
    # ata errors (possibly) caused by SATA_LINKPWR_ON_AC/BAT != max_performance
    ecnt=$(check_ata_errors)
    if [ "$ecnt" -ne 0 ]; then
        echo "+++ Warnings"
        printf "* Kernel log shows ata errors (%d) possibly caused by the configuration\n" "$ecnt"
        printf "  SATA_LINKPWR_ON_AC/BAT=min_power or medium_power.\n"
        printf "  Consider using medium_power or max_performance instead.\n"
        printf "  See the FAQ: https://linrunner.de/en/tlp/docs/tlp-faq.html#warnings\n"
        printf "  Details:\n"
        dmesg | grep -E -A 5 "${RE_ATA_ERROR}"
        echo
    elif [ "$show_warn" = "1" ]; then
        echo "No warnings detected."
        echo ""
    fi

fi # show_warn

if [ "$show_bat" = "1" ] || [ "$show_all" = "1" ]; then
    # -- show recommendations

    # battery plugin specific recommendations
    reout="$(batdrv_recommendations)"

    if [ "$show_all" = "1" ]; then
        # add other recommendations
        cmd_exists ethtool  || reout="${reout}Install ethtool to disable Wake-on-LAN\n"
        cmd_exists smartctl || reout="${reout}Install smartmontools for disk drive health info\n"
    fi

    if [ -n "$reout" ]; then
        echo "+++ Recommendations"
        # shellcheck disable=SC2059
        # don't change to %s, $reout contains blanks and \n!
        printf "$reout" | sed -r 's/^/\* /'
        echo
    fi

fi # show_all

if [ "$show_pev" = "1" ]; then
    # --- show udev power_supply events

    # check for udevadm
    if cmd_exists "$UDEVADM"; then
        echo "+++ Monitor power supply events -- cancel with ^C"
        echo
        $UDEVADM monitor --udev --property --subsystem-match=power_supply
    fi
fi # show_pev

if [ "$show_psup" = "1" ]; then
    # --- show power_supply diagnostic
    printf "+++ Power supply diagnostic\n"
    for ps in /sys/class/power_supply/*; do
        # shellcheck disable=SC2063
        printparm "%s: ##%s##" "$ps/type"
        printparm "%s: ##%s##" "$ps/usb_type"
        printparm "%s: ##%s##" "$ps/online"
        printparm "%s: ##%s##" "$ps/voltage_max"
        printparm "%s: ##%s##" "$ps/voltage_min"
        printparm "%s: ##%s##" "$ps/voltage_now"
        printparm "%s: ##%s##" "$ps/present"
        printparm "%s: ##%s##" "$ps/charge_control_start_threshold"
        printparm "%s: ##%s##" "$ps/charge_control_end_threshold"
        printparm "%s: ##%s##" "$ps/status"
        printparm "%s: ##%s##" "$ps/device/path"
    done
    printf "\n+++ udev diagnostic\n"
    check_udev_rule_ps
fi # show_psup

if [ "$show_udev" = "1" ]; then
    # --- show udev diagnostic
    printf "+++ udev diagnostic\n"
    check_udev_rule_ps
    check_udev_rule_usb
fi # show_sup

if [ "$show_trace" = "1" ]; then
    # --- show debug log

    # check for systemd journal
    jdone=0
    if cmd_exists $JOURNALCTL; then
        # retrieve trace output from journal, rc=1 if journald has no data available
        if [ $show_verbose -eq 1 ]; then
            # verbose: show all output
            $JOURNALCTL -p debug --no-pager SYSLOG_IDENTIFIER=tlp 2> /dev/null && jdone=1
        else
            # non-verbose: show output since last reboot only
            $JOURNALCTL -p debug --no-pager SYSLOG_IDENTIFIER=tlp -b 2> /dev/null && jdone=1
        fi
    fi

    if [ "$jdone" = "0"  ]; then
        # no journald data available --> retrieve trace output from logfile
        if [ -f $DEBUGLOG ]; then
            grep 'tlp\[' $DEBUGLOG
        else
            echo "Error: $DEBUGLOG does not exist." 1>&2
            echo 1>&2
            echo "Solution: create an rsyslog conffile /etc/rsyslog.d/90-debug.conf with the following contents" 1>&2
            echo " *.=debug;\\" 1>&2
            echo " mail,authpriv,cron.none;\\" 1>&2
            echo " local0,local1,local3,local4,\\" 1>&2
            echo " local5,local6,local7.none    -/var/log/debug" 1>&2
            echo "and restart the rsyslog daemon." 1>&2
            echo 1>&2
        fi
    fi
fi # show_trace

do_exit 0
