From 5cda91672cf34a415771d7e2f2e63bb2653f81f3 Mon Sep 17 00:00:00 2001 From: Antoine Viallon Date: Sun, 10 Apr 2022 12:43:36 +0200 Subject: [PATCH] [Laptop] Add option to force PCIe ASPM --- laptop.nix | 20 ++ packages/aspm_enable/default.nix | 38 ++++ packages/aspm_enable/src/aspm_enable.sh | 267 ++++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 packages/aspm_enable/default.nix create mode 100755 packages/aspm_enable/src/aspm_enable.sh diff --git a/laptop.nix b/laptop.nix index 9c642df..a84c689 100644 --- a/laptop.nix +++ b/laptop.nix @@ -16,6 +16,9 @@ in { description = "Change service used to manage power consumption on laptop"; type = types.enum [ "tlp" "power-profiles-daemon" false ]; }; + tweaks = { + pcieAspmForce = mkEnableOption "hardcore tweaks to power consumption. Warning: Might be dangerous to use."; + }; }; config = mkIf cfg.enable { @@ -31,6 +34,23 @@ in { # Les power consumption against some performance "workqueue.power_efficient" = ""; nohz = "on"; + + pcie_aspm = mkIf cfg.tweaks.pcieAspmForce "force"; + }; + + + systemd.services.aspm-force-enable = let + aspm_enable = pkgs.callPackage ./packages/aspm_enable { }; + in { + serviceConfig = { + ExecStart = [ + "${aspm_enable}/bin/aspm_enable" + ]; + Type = "simple"; + }; + wantedBy = [ "multi-user.target" ]; + description = "Force-enable PCIe ASPM"; + enable = cfg.tweaks.pcieAspmForce; }; services.tlp.enable = (cfg.power-manager == "tlp"); diff --git a/packages/aspm_enable/default.nix b/packages/aspm_enable/default.nix new file mode 100644 index 0000000..67e26db --- /dev/null +++ b/packages/aspm_enable/default.nix @@ -0,0 +1,38 @@ +{lib +,bc +,pciutils +,gnugrep +,coreutils +,bash +,writeText +,stdenv +,substituteAll +}: +with lib; +stdenv.mkDerivation rec { + pname = "aspm_enable"; + version = "2022-04-09"; + + src = ./src; + + installPhase = '' + mkdir -p $out/bin; + cp -v aspm_enable.sh $out/bin/aspm_enable + substituteInPlace $out/bin/aspm_enable \ + --replace bc ${bc}/bin/bc \ + --replace lspci ${pciutils}/bin/lspci \ + --replace setpci ${pciutils}/bin/setpci \ + --replace grep ${gnugrep}/bin/grep; + substituteAllInPlace $out/bin/aspm_enable; + ''; + + buildInputs = [ pciutils bc coreutils gnugrep ]; + + meta = { + description = "A program to forcibly enable PCIe ASPM for compatible devices"; + homepage = "https://wireless.wiki.kernel.org/en/users/Documentation/ASPM"; + license = licenses.gpl3Plus; + patforms = [ "x86_64-linux" "i686-linux" "aarch64-linux" "mipsel-linux" ]; + maintainers = with maintainers; [ ]; + }; +} diff --git a/packages/aspm_enable/src/aspm_enable.sh b/packages/aspm_enable/src/aspm_enable.sh new file mode 100755 index 0000000..aa4dcff --- /dev/null +++ b/packages/aspm_enable/src/aspm_enable.sh @@ -0,0 +1,267 @@ +#!/usr/bin/env bash +# Copyright (c) 2010-2013 Luis R. Rodriguez +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# ASPM Tuning script +# +# This script lets you enable ASPM on your devices in case your BIOS +# does not have it enabled for some reason. If your BIOS does not have +# it enabled it is usually for a good reason so you should only use this if +# you know what you are doing. Typically you would only need to enable +# ASPM manually when doing development and using a card that typically +# is not present on a laptop, or using the cardbus slot. The BIOS typically +# disables ASPM for foreign cards and on the cardbus slot. Check also +# if you may need to do other things than what is below on your vendor +# documentation. +# +# To use this script You will need for now to at least query your device +# PCI endpoint and root complex addresses using the convention output by +# lspci: []:[].[] +# +# For example: +# +# 03:00.0 Network controller: Atheros Communications Inc. AR9300 Wireless LAN adaptor (rev 01 +# 00:1c.1 PCI bridge: Intel Corporation 82801H (ICH8 Family) PCI Express Port 2 (rev 03) +# +# The root complex for the endpoint can be found using lspci -t +# +# For more details refer to: +# +# http://wireless.kernel.org/en/users/Documentation/ASPM + +# We'll only enable the last 2 bits by using a mask +# of :3 to setpci, this will ensure we keep the existing +# values on the byte. +# +# Hex Binary Meaning +# ------------------------- +# 0 0b00 L0 only +# 1 0b01 L0s only +# 2 0b10 L1 only +# 3 0b11 L1 and L0s + +function aspm_setting_to_string() +{ + case $1 in + 0) + echo -e "\t${BLUE}L0 only${NORMAL}, ${RED}ASPM disabled${NORMAL}" + ;; + 1) + ;; + 2) + echo -e "\t${GREEN}L1 only${NORMAL}" + ;; + 3) + echo -e "\t${GREEN}L1 and L0s${NORMAL}" + ;; + *) + echo -e "\t${RED}Invalid${NORMAL}" + ;; + esac +} + + +################################################################### +# Do not edit below here unless you are sending me a patch +################################################################### +# +# TODO: patches are welcomed to me until we submit to to +# PCI Utilities upstream. +# +# This can be improved by in this order: +# +# * Accept arguments for endpoint and root complex address, and +# desired ASPM settings +# * Look for your ASPM capabilities by quering your +# LnkCap register first. Use these values to let you +# select whether you want to enable only L1 or L1 & L0s +# * Searching for your root complex for you +# * Search for your PCI device by using the driver +# * Disable your driver and ask to reboot ? +# * Rewrite in C +# * Write ncurses interface [ wishlist ] +# * Write GTK/QT interface [ wishlist ] +# * Submit upstream as aspm.c to the PCI Utilities, which are +# maintained by Martin Mares + +# Pretty colors +GREEN="\033[01;32m" +YELLOW="\033[01;33m" +NORMAL="\033[00m" +BLUE="\033[34m" +RED="\033[31m" +PURPLE="\033[35m" +CYAN="\033[36m" +UNDERLINE="\033[02m" + +# we can surely read the spec to get a better value +MAX_SEARCH=50 + +function enable_aspm() { + + ENDPOINT="$1" + shift 1 + ASPM_SETTING="${1:-3}" + shift 1 + + SEARCH_COUNT=1 + ASPM_BYTE_ADDRESS="INVALID" + + ENDPOINT_PRESENT=$(lspci | grep -c "$ENDPOINT") + + if [[ $(id -u) != 0 ]]; then + echo "This needs to be run as root" + exit 1 + fi + + if [[ $ENDPOINT_PRESENT -eq 0 ]]; then + echo "Endpoint $ENDPOINT is not present" + exit + fi + + # XXX: lspci -s some_device_not_existing does not return positive + # if the device does not exist, fix this upstream + function device_present() + { + + PRESENT=$(lspci | grep -c "$1") + COMPLAINT="${RED}not present${NORMAL}" + + if [[ $PRESENT -eq 0 ]]; then + if [[ $2 != "present" ]]; then + COMPLAINT="${RED}disappeared${NORMAL}" + fi + + echo -e "Device ${BLUE}${1}${NORMAL} $COMPLAINT" + return 1 + fi + return 0 + } + + function find_aspm_byte_address() + { + device_present $ENDPOINT present + if [[ $? -ne 0 ]]; then + exit + fi + + SEARCH=$(setpci -s $1 34.b) + # We know on the first search $SEARCH will not be + # 10 but this simplifies the implementation. + while [[ $SEARCH != 10 && $SEARCH_COUNT -le $MAX_SEARCH ]]; do + END_SEARCH=$(setpci -s $1 ${SEARCH}.b) + + # Convert hex digits to uppercase for bc + SEARCH_UPPER=$(printf "%X" 0x${SEARCH}) + + if [[ $END_SEARCH = 10 ]]; then + ASPM_BYTE_ADDRESS=$(echo "obase=16; ibase=16; $SEARCH_UPPER + 10" | bc) + break + fi + + SEARCH=$(echo "obase=16; ibase=16; $SEARCH + 1" | bc) + SEARCH=$(setpci -s $1 ${SEARCH}.b) + + ((SEARCH_COUNT++)) + done + + if [[ $SEARCH_COUNT -ge $MAX_SEARCH ]]; then + echo -e "Long loop while looking for ASPM word for $1" + return 1 + fi + return 0 + } + + function enable_aspm_byte() + { + device_present $1 present + if [[ $? -ne 0 ]]; then + exit + fi + + find_aspm_byte_address $1 + if [[ $? -ne 0 ]]; then + return 1 + fi + + ASPM_BYTE_HEX=$(setpci -s $1 ${ASPM_BYTE_ADDRESS}.b) + ASPM_BYTE_HEX=$(printf "%X" 0x${ASPM_BYTE_HEX}) + # setpci doesn't support a mask on the query yet, only on the set, + # so to verify a setting on a mask we have no other optoin but + # to do do this stuff ourselves. + DESIRED_ASPM_BYTE_HEX=$(printf "%X" $(( (0x${ASPM_BYTE_HEX} & ~0x7) |0x${ASPM_SETTING}))) + + if [[ $ASPM_BYTE_ADDRESS = "INVALID" ]]; then + echo -e "No ASPM byte could be found for $(lspci -s $1)" + return + fi + + echo -e "$(lspci -s $1)" + echo -en "\t${YELLOW}0x${ASPM_BYTE_ADDRESS}${NORMAL} : ${CYAN}0x${ASPM_BYTE_HEX}${GREEN} --> ${BLUE}0x${DESIRED_ASPM_BYTE_HEX}${NORMAL} ... " + + device_present $1 present + if [[ $? -ne 0 ]]; then + exit + fi + + # Avoid setting if already set + if [[ $ASPM_BYTE_HEX = $DESIRED_ASPM_BYTE_HEX ]]; then + echo -e "[${GREEN}SUCESS${NORMAL}] (${GREEN}already set${NORMAL})" + aspm_setting_to_string $ASPM_SETTING + return 0 + fi + + # This only writes the last 3 bits + setpci -s $1 ${ASPM_BYTE_ADDRESS}.b=${ASPM_SETTING}:3 + + sleep 3 + + ACTUAL_ASPM_BYTE_HEX=$(setpci -s $1 ${ASPM_BYTE_ADDRESS}.b) + ACTUAL_ASPM_BYTE_HEX=$(printf "%X" 0x${ACTUAL_ASPM_BYTE_HEX}) + + # Do not retry this if it failed, if it failed to set. + # Likey if it failed its a good reason and you should look + # into that. + if [[ $ACTUAL_ASPM_BYTE_HEX != $DESIRED_ASPM_BYTE_HEX ]]; then + echo -e "\t[${RED}FAIL${NORMAL}] (0x${ACTUAL_ASPM_BYTE_HEX})" + return 1 + fi + + echo -e "\t[${GREEN}SUCCESS]${NORMAL}]" + aspm_setting_to_string $ASPM_SETTING + + return 0 + } + + device_present $ENDPOINT not_sure + if [[ $? -ne 0 ]]; then + exit + fi + + enable_aspm_byte $ENDPOINT +} + +function enable_aspm_all() { + ENDPOINTS=($(lspci | cut -d " " -f 1)) + for ENDPOINT in "${ENDPOINTS[@]}"; do + if lspci -s $ENDPOINT -vnvn | grep -q ASPM; then + enable_aspm $ENDPOINT 3 + else + echo -e "${CYAN}$ENDPOINT${NORMAL} doesn't support ASPM" + fi + done +} + +enable_aspm_all +