#!/bin/sh
# tlp-stat - display power saving details
#
# Copyright (c) 2024 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 DMIDECODE=dmidecode
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 ACPITEMP_DIR="/sys/devices/virtual/thermal"
readonly ACPITEMP_GLOB="
/sys/devices/virtual/thermal/thermal_zone*"
readonly CORETEMP_DIR="/sys/devices/platform/coretemp.0"
readonly CORETEMP_GLOB="
/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
show_mode=0
show_version=0
quiet=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
                ;;

            "-m"|"--mode")
                show_all=0
                show_mode=1
                quiet=1
                : ${needs_root_priv:=0}
                ;;

            "-p"|"--processor")
                show_all=0
                show_proc=1
                : ${needs_root_priv:=0}
                ;;

            "-q"|"--quiet")
                quiet=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}
                ;;

            "--version")
                show_all=0
                show_version=1
                quiet=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  | -m | --mode      |"
                echo "                  -p | --processor | -q | --quiet     |"
                echo "                  -r | --rfkill    | -s | --system    |"
                echo "                  -t | --temp      | -u | --usb       |"
                echo "                  -w | --warn      | -v | --verbose   |"
                echo "                     | --version   |    | --cdiff     |"
                echo "                     | --pev       | -P | --psup      |"
                echo "                  -T | --trace     |    | --udev      |"
                echo "                ]"
                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 "$@"
cprintf_init

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
fi
get_sys_power_supply

if [ "$quiet" = "0" ]; then
    echo "--- TLP $TLPVER --------------------------------------------"
    echo
fi

# --- show version
if [ "$show_version" = "1" ]; then
    print_version
fi # show_version

# --- 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_cdiff

# --- show opmode
if [ "$show_mode" = "1" ]; then
    printf "%s\n" "$(print_saved_powerstate)"
fi # show_mode

# --- show system info and status
if [ "$show_system" = "1" ] || [ "$show_all" = "1" ] ; then
    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
        # simulate arbitrary model
        model="$X_SIMULATE_MODEL"
    fi

    echo "+++ System Info"
    vendor="$(read_dmi sys_vendor)"        && printf "System         = %s\n" "$vendor $model"
    bios="$(read_dmi bios_version)"        && printf "BIOS           = %s\n" "$bios"
    ecfw="$(read_dmi ec_firmware_release)" && printf "EC Firmware    = %s\n" "$ecfw"

    # --- 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

    # --- 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

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

    # --- 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

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

    # --- actual opmode
    printf "Mode           = %s\n" "$(print_saved_powerstate)"

    # ---- 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
        cprintf "" "Warning: TLP's power saving will not apply on boot because the conflicting $PPD_SERVICE is active.\n" 1>&2
        echo
    fi

    # -- warning if l-m-t detected
    check_laptop_mode_tools
fi # show_system

# --- show cpu info
if [ "$show_proc" = "1" ] || [ "$show_all" = "1" ]; then
    [ "$quiet" = "0" ] || [ "$show_all" = "1" ] && echo "+++ Processor"

    # check EPP availability
    if [ -f "${CPUD}/cpu0/cpufreq/energy_performance_preference" ]; then
        epp=1
    else
        epp=0
    fi

    if [ "$quiet" = "0" ]; then
        if [ "$X_IGNORE_PROCCPU" != "1" ]; then
            cpu_mod="$(sed -rn 's/model name[ \t]+: (.+)/\1/p' /proc/cpuinfo | head -1)"
        else
            cpu_mod=""
        fi
        if [ -z "$cpu_mod" ] && test_root && cmd_exists $DMIDECODE ; then
            cpu_mod="$(dmidecode -t 4 2> /dev/null | sed -rn 's/\s*Version:\s+(.+)/\1/p')"
        fi
        if [ -n "$cpu_mod" ]; then
            printf "CPU model = %s\n\n" "$cpu_mod"
        fi

        # -- scaling gov and freq info
        cnt=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"
                    fi
                    if [ -f "$cpuf/energy_performance_available_preferences" ]; then
                        printparm "%s = ##%s##" "$cpuf/energy_performance_available_preferences"
                    fi

                    if { supports_intel_cpu_epb && [ "$epp" -eq 0 ]; } || [ "$X_SHOW_EPB" = "1" ] ; then
                        # CPU supports EPB: use native kernel API (5.2 and later)
                        printparm_epb "${CPUD}/cpu${cn}/power/energy_perf_bias"
                    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 # for cn

        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
    fi # !quiet

    if check_intel_pstate; then
        # Intel P-state
        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
        # AMD P-state
        printparm "%-54s = ##%s##" "$AMD_PSTATED/status"
        if [ -f "$CPU_BOOST_ALL_CTRL" ]; then
            printparm "%-54s = ##%d##" "$CPU_BOOST_ALL_CTRL" "not available"
        fi

    elif [ -f "$CPU_BOOST_ALL_CTRL" ]; then
        # turbo boost
        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

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

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

    # --- platform profile
    if [ "$quiet" = "0" ] || [ "$show_all" = "1" ] ; then
        printf "\n+++ Platform Profile\n"
    fi
    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

# --- show temperatures
if [ "$show_temp" = "1" ] || [ "$show_all" = "1" ]; then
    if [ "$quiet" = "0" ] || [ "$show_all" = "1" ]; then
        echo "+++ Temperatures"
    fi

    tempshown=0
    if [ -f "$IBMTHERMAL" ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then
        # use ThinkPad-specific sysfile
        echo "$IBMTHERMAL = $(read_sysf $IBMTHERMAL | cut -f2  ) [°C]"
        tempshown=1
    elif [ -d "$CORETEMP_DIR" ] && [ "$X_IGNORE_CORETEMP" != "1" ]; then
        # use coretemp sensors
        cmax=0
        # find max value of all CPU packages (just in case there are several)
        # temp1_input is "package"
        for sens in $(glob_files '/temp1_input' "$CORETEMP_GLOB"); 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);'
            tempshown=1
        fi
    elif [ -d "$ACPITEMP_DIR" ] && [ "$X_IGNORE_ACPITEMP" != "1" ]; then
        # use ACPI thermal zone sensors for CPUs
        cmax=0
        # find max value of all CPUs (just in case there are several)
        for sens in $(glob_files '/temp' "$ACPITEMP_GLOB"); do
            if [ "$(read_sysf "${sens%temp}type")" = "acpitz" ]; then
                ctemp=$(read_sysval "$sens")
                [ "$ctemp" -gt "$cmax" ] && cmax=$ctemp
            fi
        done
        if [ "$cmax" -gt 0 ]; then
            perl -e 'printf ("CPU temp               = %5d [°C]\n", '"$cmax"' / 1000.0);'
            tempshown=1
        fi
    fi
    if [ "$tempshown" -eq "0" ]; then
        # no CPU sensor detected
        printf "CPU temp               = (not available)\n"
    fi

    # --- fan speed
    fanshown=0
    if [ -f $IBMFAN ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then
        # use thinkpad-specific sysfile
        awk '$1 ~ /speed:/ { printf "'$IBMFAN'     = %5d [/min]\n", $2 }' $IBMFAN
        fanshown=1
    elif [ "$X_IGNORE_HWMONFAN" != "1" ]; then
        # use hwmon
        # 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}"
                fanshown=1
                printf "Fan speed (%s)       = %5d [/min]\n" \
                    "${fan_name}" "${fan_speed}"
            fi
        done
    fi
    if [ "$fanshown" -eq "0" ]; then
        printf "Fan speed              = (not available)\n"
    fi
    if [ "$quiet" = "0" ] || [ "$show_all" = "1" ]; then
        echo
    fi
fi # show_temp

# --- show laptop-mode, dirty buffers params
if [ "$show_all" = "1" ]; then
    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

# --- show disk info
if [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then
    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

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

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

    # -- docks
    cnt=0
    for dock in $DOCK_GLOB; 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

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

    # --- gpu data for all drivers
    show_gpu_data
fi # show_graf

# --- show rfkill state
if [ "$show_rfkill" = "1" ] || [ "$show_all" = "1" ]; then
    echo "+++ Wireless"
    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

    # --- 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

    # --- wifi
    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

    # --- wwan
    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

# --- show sound power mode
if [ "$show_all" = "1" ]; then
    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

# --- show pcie info
if [ "$show_pcie" = "1" ] || [ "$show_all" = "1" ]; then
    # --- 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

    # --- 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
        cecho "Error: missing subcommand $TLPPCI." 1>&2
    fi
    echo
fi # show_pcie

# --- show usb autosuspend
if [ "$show_usb" = "1" ] || [ "$show_all" = "1" ]; then
    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
        cecho "Error: missing subcommand $TLPUSB." 1>&2
    fi
    echo
fi # show_usb

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

# --- show warnings
if [ "$show_warn" = "1" ] || [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then
    # 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

# -- show recommendations
if [ "$show_bat" = "1" ] || [ "$show_system" = "1" ] || [ "$show_all" = "1" ]; then
    # battery plugin specific recommendations
    if [ "$show_bat" = "1" ] || [ "$show_all" = "1" ]; then
        reout="$(batdrv_recommendations)"
        if [ -n "$reout" ]; then
            reout="$reout\n"
        fi
    else
        reout=""
    fi

    # other recommendations
    if [ "$show_system" = "1" ] || [ "$show_all" = "1" ]; then
        if check_ppd_active; then
            reout="${reout}Uninstall power-profiles-daemon or invoke 'systemctl mask $PPD_SERVICE' to ensure the full functionality of TLP\n"
        fi
    fi
    if [ "$show_all" = "1" ]; then
        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
        cecho "+++ Recommendations" "notice"
        # shellcheck disable=SC2059
        # don't change to %s, $reout contains blanks and \n!
        cecho "$(printf "$reout" | sed -r 's/^/\* /')" "notice"
        echo
    fi
fi # show_all

# --- show udev power_supply events
if [ "$show_pev" = "1" ]; then
    # 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

# --- show power_supply diagnostic
if [ "$show_psup" = "1" ]; then
    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

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

# --- show debug log
if [ "$show_trace" = "1" ]; then
    # 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
            cecho "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
