Date: Sat, 13 Dec 2003 13:12:03 -0800 (PST) From: Nate Lawson <nate@root.org> To: arch@freebsd.org Cc: acpi-jp@jp.freebsd.org Subject: Power profile script Message-ID: <20031213130351.N59162@root.org>
next in thread | raw e-mail | index | archive | help
I've implemented a script for changing power profiles in userland when you go on or off AC power. The defaults will just change the CPU idle states to the lowest power ones when off AC power. Throttling will stay at 100%. Of course, the user can override these in rc.conf. In the future, other power profile features will be maintained by the same script. You have to be running devd to use this. The special values LOW and HIGH refer to the lowest and highest performing settings for a given sysctl. So if a system has 8 throttling values, from 1 to 8, HIGH would be 8 and LOW would be 1. You can also specify a particular value ("2") if you know what you want. I'm mostly looking for style input on the /etc/power_profile script since I'm not familiar with our scripting guidelines. Note that it's called from devd (or manually by the user) and is not an rc.d boot-time thing. -Nate Index: etc/Makefile =================================================================== RCS file: /home/ncvs/src/etc/Makefile,v retrieving revision 1.322 diff -u -r1.322 Makefile --- etc/Makefile 2 Nov 2003 22:13:36 -0000 1.322 +++ etc/Makefile 13 Dec 2003 20:56:55 -0000 @@ -33,7 +33,7 @@ .endif # -rwxr-xr-x root:wheel, for the new cron root:wheel -BIN2= netstart pccard_ether rc.suspend rc.resume +BIN2= netstart pccard_ether power_profile rc.suspend rc.resume MTREE= BSD.include.dist BSD.local.dist BSD.root.dist BSD.usr.dist \ BSD.var.dist BSD.x11.dist BSD.x11-4.dist Index: etc/devd.conf =================================================================== RCS file: /home/ncvs/src/etc/devd.conf,v retrieving revision 1.9 diff -u -r1.9 devd.conf --- etc/devd.conf 25 Oct 2003 05:03:25 -0000 1.9 +++ etc/devd.conf 13 Dec 2003 20:59:14 -0000 @@ -70,6 +70,13 @@ # action "logger Unknown device: $pnpinfo $location $bus"; }; +# Switch power profiles when the AC line state changes +notify 10 { + match "system" "ACPI"; + match "subsystem" "ACAD"; + action "/etc/power_profile $notify"; +}; + /* EXAMPLES TO END OF FILE # The following might be an example of something that a vendor might Index: etc/power_profile =================================================================== RCS file: etc/power_profile diff -N etc/power_profile --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ etc/power_profile 13 Dec 2003 20:58:00 -0000 @@ -0,0 +1,87 @@ +#!/bin/sh - +# +# Modify the power profile based on AC line state. This script is +# usually called from devd(8). +# +# Arguments: 0x00 (AC offline, economy) or 0x01 (AC online, performance) +# +# $FreeBSD$ +# + +LOGGER="logger -t power_profile -p daemon.notice" + +# Set a given sysctl node to a value. +# +# Variables: +# $node: sysctl node to set with the new value +# $value: HIGH for the highest performance value, LOW for the best +# economy value, or the value itself. +# $highest_value: maximum value for this sysctl, when $value is "HIGH" +# $lowest_value: minimum value for this sysctl, when $value is "LOW" +# +sysctl_set () { + # Check if the node exists + if [ -z "`sysctl -n ${node} 2> /dev/null`" ]; then + return + fi + + # Get the new value, checking for special types HIGH or LOW + case ${value} in + [Hh][Ii][Gg][Hh]) + value=${highest_value} + ;; + [Ll][Oo][Ww]) + value=${lowest_value} + ;; + *) + ;; + esac + + # Set the desired value + [ -n "${value}" ] && sysctl ${node}=${value} +} + +# Pull in default values. +if [ -r /etc/defaults/rc.conf ]; then + . /etc/defaults/rc.conf + source_rc_confs +elif [ -r /etc/rc.conf ]; then + . /etc/rc.conf +fi + +if [ $# -ne 1 ]; then + echo "Usage: $0 [0x00|0x01]" + exit 1 +fi + +# Find the next state (performance or economy). +state=$1 +case ${state} in +0x01 | '') + ${LOGGER} "changed to 'performance'" + profile="performance" + ;; +0x00) + ${LOGGER} "changed to 'economy'" + profile="economy" + ;; +*) + echo "Usage: $0 [0x00|0x01]" + exit 1 +esac + +# Set the various sysctls based on the profile's values. +node="hw.acpi.cpu.cx_lowest" +highest_value=0 +lowest_value="`sysctl -n hw.acpi.cpu.cx_supported | \ + awk '{ print split($0, a) - 1 }' - 2> /dev/null`" +eval value=\$${profile}_cx_lowest +sysctl_set + +node="hw.acpi.cpu.throttle_state" +highest_value="`sysctl -n hw.acpi.cpu.throttle_max 2> /dev/null`" +lowest_value="1" +eval value=\$${profile}_throttle_state +sysctl_set + +exit 0 Index: etc/defaults/rc.conf =================================================================== RCS file: /home/ncvs/src/etc/defaults/rc.conf,v retrieving revision 1.191 diff -u -r1.191 rc.conf --- etc/defaults/rc.conf 28 Nov 2003 17:28:42 -0000 1.191 +++ etc/defaults/rc.conf 13 Dec 2003 20:42:24 -0000 @@ -441,6 +441,11 @@ devfs_rulesets="/etc/defaults/devfs.rules /etc/devfs.rules" # Files containing # devfs(8) rules. devfs_system_ruleset="" # The name of a ruleset to apply to /dev +performance_cx_lowest="HIGH" # Online CPU idle state +performance_throttle_state="HIGH" # Online throttling state +economy_cx_lowest="LOW" # Offline CPU idle state +economy_throttle_state="HIGH" # Offline throttling state + ############################################################## ### Jail Configuration ####################################### Index: sys/dev/acpica/acpi_cpu.c =================================================================== RCS file: /home/ncvs/src/sys/dev/acpica/acpi_cpu.c,v retrieving revision 1.26 diff -u -r1.26 acpi_cpu.c --- sys/dev/acpica/acpi_cpu.c 12 Dec 2003 19:42:16 -0000 1.26 +++ sys/dev/acpica/acpi_cpu.c 13 Dec 2003 04:27:46 -0000 @@ -134,10 +134,8 @@ static int cpu_idle_busy; /* Count of CPUs in acpi_cpu_idle. */ /* Values for sysctl. */ -static uint32_t cpu_current_state; -static uint32_t cpu_performance_state; -static uint32_t cpu_economy_state; -static uint32_t cpu_max_state; +static uint32_t cpu_throttle_state; +static uint32_t cpu_throttle_max; static int cpu_cx_lowest; static char cpu_cx_supported[64]; @@ -165,7 +163,6 @@ static void acpi_pm_ticksub(uint32_t *end, const uint32_t *start); static void acpi_cpu_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_cpu_quirks(struct acpi_cpu_softc *sc); -static void acpi_cpu_power_profile(void *arg); static int acpi_cpu_throttle_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_cpu_history_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_cpu_cx_lowest_sysctl(SYSCTL_HANDLER_ARGS); @@ -616,10 +613,6 @@ /* Get set of CPU devices */ devclass_get_devices(acpi_cpu_devclass, &cpu_devices, &cpu_ndevices); - /* Register performance profile change handler */ - EVENTHANDLER_REGISTER(power_profile_change, acpi_cpu_power_profile, - NULL, 0); - /* * Make sure all the processors' Cx counts match. We should probably * also check the contents of each. However, no known systems have @@ -647,60 +640,34 @@ static void acpi_cpu_startup_throttling() { - int cpu_temp_speed; ACPI_LOCK_DECL; /* Initialise throttling states */ - cpu_max_state = CPU_MAX_SPEED; - cpu_performance_state = cpu_max_state; - cpu_economy_state = cpu_performance_state / 2; - - /* 0 is 'reserved' */ - if (cpu_economy_state == 0) - cpu_economy_state++; - if (TUNABLE_INT_FETCH("hw.acpi.cpu.performance_speed", &cpu_temp_speed) && - cpu_temp_speed > 0 && cpu_temp_speed <= cpu_max_state) { - - cpu_performance_state = cpu_temp_speed; - } - if (TUNABLE_INT_FETCH("hw.acpi.cpu.economy_speed", &cpu_temp_speed) && - cpu_temp_speed > 0 && cpu_temp_speed <= cpu_max_state) { - - cpu_economy_state = cpu_temp_speed; - } + cpu_throttle_max = CPU_MAX_SPEED; + cpu_throttle_state = CPU_MAX_SPEED; SYSCTL_ADD_INT(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), - OID_AUTO, "max_speed", CTLFLAG_RD, - &cpu_max_state, 0, "maximum CPU speed"); - SYSCTL_ADD_INT(&acpi_cpu_sysctl_ctx, - SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), - OID_AUTO, "current_speed", CTLFLAG_RD, - &cpu_current_state, 0, "current CPU speed"); - SYSCTL_ADD_PROC(&acpi_cpu_sysctl_ctx, - SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), - OID_AUTO, "performance_speed", - CTLTYPE_INT | CTLFLAG_RW, &cpu_performance_state, - 0, acpi_cpu_throttle_sysctl, "I", ""); + OID_AUTO, "throttle_max", CTLFLAG_RD, + &cpu_throttle_max, 0, "maximum CPU speed"); SYSCTL_ADD_PROC(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), - OID_AUTO, "economy_speed", - CTLTYPE_INT | CTLFLAG_RW, &cpu_economy_state, - 0, acpi_cpu_throttle_sysctl, "I", ""); + OID_AUTO, "throttle_state", + CTLTYPE_INT | CTLFLAG_RW, &cpu_throttle_state, + 0, acpi_cpu_throttle_sysctl, "I", "current CPU speed"); /* If ACPI 2.0+, signal platform that we are taking over throttling. */ - if (cpu_pstate_cnt != 0) { - ACPI_LOCK; + ACPI_LOCK; + if (cpu_pstate_cnt != 0) AcpiOsWritePort(cpu_smi_cmd, cpu_pstate_cnt, 8); - ACPI_UNLOCK; - } - /* Set initial speed */ - acpi_cpu_power_profile(NULL); + /* Set initial speed to maximum. */ + acpi_cpu_throttle_set(cpu_throttle_max); + ACPI_UNLOCK; printf("acpi_cpu: throttling enabled, %d steps (100%% to %d.%d%%), " "currently %d.%d%%\n", CPU_MAX_SPEED, CPU_SPEED_PRINTABLE(1), - CPU_SPEED_PRINTABLE(cpu_current_state)); + CPU_SPEED_PRINTABLE(cpu_throttle_state)); } static void @@ -787,7 +754,7 @@ ACPI_VPRINT(sc->cpu_dev, acpi_device_get_parent_softc(sc->cpu_dev), "set speed to %d.%d%%\n", CPU_SPEED_PRINTABLE(speed)); } - cpu_current_state = speed; + cpu_throttle_state = speed; } /* @@ -1026,54 +993,14 @@ return (0); } -/* - * Power profile change hook. - * - * Uses the ACPI lock to avoid reentrancy. - */ -static void -acpi_cpu_power_profile(void *arg) -{ - int state; - uint32_t new; - ACPI_LOCK_DECL; - - state = power_profile_get_state(); - if (state != POWER_PROFILE_PERFORMANCE && state != POWER_PROFILE_ECONOMY) - return; - - ACPI_LOCK; - - switch (state) { - case POWER_PROFILE_PERFORMANCE: - new = cpu_performance_state; - break; - case POWER_PROFILE_ECONOMY: - new = cpu_economy_state; - break; - default: - new = cpu_current_state; - break; - } - - if (cpu_current_state != new) - acpi_cpu_throttle_set(new); - - ACPI_UNLOCK; -} - -/* - * Handle changes in the performance/ecomony CPU settings. - * - * Does not need the ACPI lock (although setting *argp should - * probably be atomic). - */ +/* Handle changes in the CPU throttling setting. */ static int acpi_cpu_throttle_sysctl(SYSCTL_HANDLER_ARGS) { uint32_t *argp; uint32_t arg; int error; + ACPI_LOCK_DECL; argp = (uint32_t *)oidp->oid_arg1; arg = *argp; @@ -1082,12 +1009,16 @@ /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); - if (arg < 1 || arg > cpu_max_state) + if (arg < 1 || arg > cpu_throttle_max) return (EINVAL); - /* Set new value and possibly switch */ - *argp = arg; - acpi_cpu_power_profile(NULL); + /* If throttling changed, notify the BIOS of the new rate. */ + ACPI_LOCK; + if (*argp != arg) { + *argp = arg; + acpi_cpu_throttle_set(arg); + } + ACPI_UNLOCK; return (0); } Index: share/man/man4/acpi.4 =================================================================== RCS file: /home/ncvs/src/share/man/man4/acpi.4,v retrieving revision 1.20 diff -u -r1.20 acpi.4 --- share/man/man4/acpi.4 19 Nov 2003 20:37:15 -0000 1.20 +++ share/man/man4/acpi.4 13 Dec 2003 05:04:22 -0000 @@ -328,12 +328,12 @@ .El .Sh SYSCTLS .Bl -tag -width indent -.It Va hw.acpi.cpu.performance_speed -Sets the speed of the CPU, if it supports multiple speeds, while in -the performance power profile. -.It Va hw.acpi.cpu.economy_speed -Sets the speed of the CPU, if it supports multiple speeds, while in -the economy power profile. +.It Va hw.acpi.cpu.throttle_max +Maximum value for CPU throttling, equal to 100% of the clock rate. +.It Va hw.acpi.cpu.throttle_state +Get or set the current throttling state, from 1 to +.Va hw.acpi.cpu.throttle_max . +This scales back the CPU clock rate and the corresponding power consumption. .It Va hw.acpi.cpu.cx_history Debugging information listing all sleep states and the number of long and short sleeps for each one.
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20031213130351.N59162>