root/usr.sbin/powerd/powerd.c
/*
 * Copyright (c) 2010,2016 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * The powerd daemon :
 * - Monitor the cpu load and adjusts cpu and cpu power domain
 *   performance accordingly.
 * - Monitor battery life.  Alarm alerts and shutdown the machine
 *   if battery life goes low.
 */

#define _KERNEL_STRUCTURES
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/kinfo.h>
#include <sys/queue.h>
#include <sys/soundcard.h>
#include <sys/sensors.h>
#include <sys/time.h>
#include <sys/cpumask.h>
#include <machine/cpufunc.h>
#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>

#include "alert1.h"

#define MAXDOM          MAXCPU  /* worst case, 1 cpu per domain */

#define MAXFREQ         64
#define CST_STRLEN      16

#define NFREQ_MONPERF   0x0001
#define NFREQ_ADJPERF   0x0002
#define NFREQ_CPUTEMP   0x0004

#define NFREQ_ALL       (NFREQ_MONPERF | NFREQ_ADJPERF | NFREQ_CPUTEMP)

struct cpu_pwrdom {
        TAILQ_ENTRY(cpu_pwrdom) dom_link;
        int                     dom_id;
        int                     dom_ncpus;
        cpumask_t               dom_cpumask;
};

struct cpu_state {
        double                  cpu_qavg;
        double                  cpu_uavg;       /* used for speeding up */
        double                  cpu_davg;       /* used for slowing down */
        int                     cpu_limit;
        int                     cpu_count;
        char                    cpu_name[8];
};

static void usage(void);
static void get_ncpus(void);
static void mon_cputemp(void);

/* usched cpumask */
static void get_uschedcpus(void);
static void set_uschedcpus(void);

/* perfbias(4) */
static int has_perfbias(void);
static void set_perfbias(int, int);

/* acpi(4) P-state */
static void acpi_getcpufreq_str(int, int *, int *);
static int acpi_getcpufreq_bin(int, int *, int *);
static void acpi_get_cpufreq(int, int *, int *);
static void acpi_set_cpufreq(int, int);
static int acpi_get_cpupwrdom(void);

/* mwait C-state hint */
static int probe_cstate(void);
static void set_cstate(int, int);

/* Performance monitoring */
static void init_perf(void);
static void mon_perf(double);
static void adj_perf(cpumask_t, cpumask_t);
static void adj_cpu_pwrdom(int, int);
static void adj_cpu_perf(int, int);
static void get_cputime(double);
static int get_nstate(struct cpu_state *, double);
static void add_spare_cpus(const cpumask_t, int);
static void restore_perf(void);
static void set_global_freq(int freq);

/* Battery monitoring */
static int has_battery(void);
static int mon_battery(void);
static void low_battery_alert(int);

/* Backlight */
static void restore_backlight(void);

/* Runtime states for performance monitoring */
static int global_pcpu_limit;
static struct cpu_state pcpu_state[MAXCPU];
static struct cpu_state global_cpu_state;
static cpumask_t cpu_used;              /* cpus w/ high perf */
static cpumask_t cpu_pwrdom_used;       /* cpu power domains w/ high perf */
static cpumask_t usched_cpu_used;       /* cpus for usched */

/* Constants */
static cpumask_t cpu_pwrdom_mask;       /* usable cpu power domains */
static int cpu2pwrdom[MAXCPU];          /* cpu to cpu power domain map */
static struct cpu_pwrdom *cpu_pwrdomain[MAXDOM];
static int NCpus;                       /* # of cpus */
static char orig_global_cx[CST_STRLEN];
static char cpu_perf_cx[CST_STRLEN];
static int cpu_perf_cxlen;
static char cpu_idle_cx[CST_STRLEN];
static int cpu_idle_cxlen;
static int FreqAry[MAXFREQ];
static int NFreq;
static int NFreqChanged = NFREQ_ALL;
static int SavedPXGlobal;

static int DebugOpt;
static int TurboOpt = 1;
static int PowerFd;
static int Hysteresis = 10;     /* percentage */
static double TriggerUp = 0.25; /* single-cpu load to force max freq */
static double TriggerDown;      /* load per cpu to force the min freq */
static int HasPerfbias = 0;
static int AdjustCpuFreq = 1;
static int AdjustCstate = 0;
static int HighestCpuFreq;
static int LowestCpuFreq;
static int AdjustUsched = 1;

static int AdjustCpuFreqOverride;

static volatile int stopped;

/* Battery life monitoring */
static int BatLifeMin = 2;      /* shutdown the box, if low on battery life */
static struct timespec BatLifePrevT;
static int BatLifePollIntvl = 5; /* unit: sec */
static struct timespec BatShutdownStartT;
static int BatShutdownLinger = -1;
static int BatShutdownLingerSet = 60; /* unit: sec */
static int BatShutdownLingerCnt;
static int BatShutdownAudioAlert = 1;
static int MinTemp = 75;
static int MaxTemp = 85;
static int BackLightPct = 100;
static int OldBackLightLevel;
static int BackLightDown;

static void sigintr(int signo);

int
main(int ac, char **av)
{
        double srt;
        double pollrate;
        int ch;
        int lowest;
        int highest;
        char buf[64];
        int monbat;
        char *p2;

        srt = 8.0;      /* time for samples - 8 seconds */
        pollrate = 1.0; /* polling rate in seconds */

        while ((ch = getopt(ac, av, "b:cdefh:l:p:r:tu:B:H:L:P:QT:U")) != -1) {
                switch(ch) {
                case 'b':
                        BackLightPct = strtol(optarg, NULL, 10);
                        break;
                case 'c':
                        AdjustCstate = 1;
                        break;
                case 'd':
                        DebugOpt = 1;
                        break;
                case 'e':
                        HasPerfbias = 1;
                        break;
                case 'f':
                        AdjustCpuFreq = 0;
                        break;
                case 'h':
                        HighestCpuFreq = strtol(optarg, NULL, 10);
                        break;
                case 'l':
                        LowestCpuFreq = strtol(optarg, NULL, 10);
                        break;
                case 'p':
                        Hysteresis = (int)strtol(optarg, NULL, 10);
                        break;
                case 'r':
                        pollrate = strtod(optarg, NULL);
                        break;
                case 't':
                        TurboOpt = 0;
                        break;
                case 'u':
                        TriggerUp = (double)strtol(optarg, NULL, 10) / 100;
                        break;
                case 'B':
                        BatLifeMin = strtol(optarg, NULL, 10);
                        break;
                case 'H':
                        MaxTemp = strtol(optarg, &p2, 0);
                        if (*p2 == ':') {
                                MinTemp = MaxTemp;
                                MaxTemp = strtol(p2 + 1, NULL, 0);
                        } else {
                                MinTemp = MaxTemp * 9 / 10;
                        }
                        break;
                case 'L':
                        BatShutdownLingerSet = strtol(optarg, NULL, 10);
                        if (BatShutdownLingerSet < 0)
                                BatShutdownLingerSet = 0;
                        break;
                case 'P':
                        BatLifePollIntvl = strtol(optarg, NULL, 10);
                        break;
                case 'Q':
                        BatShutdownAudioAlert = 0;
                        break;
                case 'T':
                        srt = strtod(optarg, NULL);
                        break;
                case 'U':
                        AdjustUsched = 0;
                        break;
                default:
                        usage();
                        /* NOT REACHED */
                }
        }
        ac -= optind;
        av += optind;

        setlinebuf(stdout);

        /* Get number of cpus */
        get_ncpus();

        /* Seed FreqAry[] */
        acpi_get_cpufreq(0, &lowest, &highest);

        if (Hysteresis < 0 || Hysteresis > 99) {
                fprintf(stderr, "Invalid hysteresis value\n");
                exit(1);
        }

        if (TriggerUp < 0 || TriggerUp > 1) {
                fprintf(stderr, "Invalid load limit value\n");
                exit(1);
        }

        if (BackLightPct > 100 || BackLightPct <= 0) {
                fprintf(stderr, "Invalid backlight setting, ignore\n");
                BackLightPct = 100;
        }

        TriggerDown = TriggerUp - (TriggerUp * (double) Hysteresis / 100);

        /*
         * Make sure powerd is not already running.
         */
        PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
        if (PowerFd < 0) {
                fprintf(stderr,
                        "Cannot create /var/run/powerd.pid, "
                        "continuing anyway\n");
        } else {
                ssize_t r;
                pid_t pid = -1;

                r = read(PowerFd, buf, sizeof(buf) - 1);
                if (r > 0) {
                        buf[r] = 0;
                        pid = strtol(buf, NULL, 0);
                }
                if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
                        if (pid > 0) {
                                kill(pid, SIGTERM);
                                flock(PowerFd, LOCK_EX);
                                fprintf(stderr, "restarting powerd\n");
                        } else {
                                fprintf(stderr,
                                        "powerd is already running, "
                                        "unable to kill pid for restart\n");
                                exit(1);
                        }
                }
                lseek(PowerFd, 0L, 0);
        }

        /*
         * Demonize and set pid
         */
        if (DebugOpt == 0) {
                daemon(0, 0);
                openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
        }

        if (PowerFd >= 0) {
                ftruncate(PowerFd, 0);
                snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
                write(PowerFd, buf, strlen(buf));
        }

        /* Do we need to monitor battery life? */
        if (BatLifePollIntvl <= 0)
                monbat = 0;
        else
                monbat = has_battery();

        /* Do we have perfbias(4)? */
        if (HasPerfbias)
                HasPerfbias = has_perfbias();

        /* Could we adjust C-state? */
        if (AdjustCstate)
                AdjustCstate = probe_cstate();

        /*
         * Wait hw.acpi.cpu.px_dom* sysctl to be created by kernel.
         *
         * Since hw.acpi.cpu.px_dom* creation is queued into ACPI
         * taskqueue and ACPI taskqueue is shared across various
         * ACPI modules, any delay in other modules may cause
         * hw.acpi.cpu.px_dom* to be created at quite a later time
         * (e.g. cmbat module's task could take quite a lot of time).
         */
        for (;;) {
                /* Prime delta cputime calculation. */
                get_cputime(pollrate);

                /* Wait for all cpus to appear */
                if (acpi_get_cpupwrdom())
                        break;
                usleep((int)(pollrate * 1000000.0));
        }

        /*
         * Catch some signals so that max performance could be restored.
         */
        signal(SIGINT, sigintr);
        signal(SIGTERM, sigintr);

        /* Initialize performance states */
        init_perf();

        srt = srt / pollrate;   /* convert to sample count */
        if (DebugOpt)
                printf("samples for downgrading: %5.2f\n", srt);

        /*
         * Monitoring loop
         */
        while (!stopped) {
                /*
                 * Monitor performance
                 */
                get_cputime(pollrate);
                mon_cputemp();
                mon_perf(srt);

                /*
                 * Monitor battery
                 */
                if (monbat)
                        monbat = mon_battery();

                usleep((int)(pollrate * 1000000.0));
        }

        /*
         * Set to maximum performance if killed.
         */
        syslog(LOG_INFO, "killed, setting max and exiting");
        if (SavedPXGlobal)
                set_global_freq(SavedPXGlobal);
        restore_perf();
        restore_backlight();

        exit(0);
}

static void
sigintr(int signo __unused)
{
        stopped = 1;
}

/*
 * Figure out the cpu power domains.
 */
static int
acpi_get_cpupwrdom(void)
{
        struct cpu_pwrdom *dom;
        cpumask_t pwrdom_mask;
        char buf[64];
        char members[1024];
        char *str;
        size_t msize;
        int n, i, ncpu = 0, dom_id;

        memset(cpu2pwrdom, 0, sizeof(cpu2pwrdom));
        memset(cpu_pwrdomain, 0, sizeof(cpu_pwrdomain));
        CPUMASK_ASSZERO(cpu_pwrdom_mask);

        for (i = 0; i < MAXDOM; ++i) {
                snprintf(buf, sizeof(buf),
                         "hw.acpi.cpu.px_dom%d.available", i);
                if (sysctlbyname(buf, NULL, NULL, NULL, 0) < 0)
                        continue;

                dom = calloc(1, sizeof(*dom));
                dom->dom_id = i;

                if (cpu_pwrdomain[i] != NULL) {
                        fprintf(stderr, "cpu power domain %d exists\n", i);
                        exit(1);
                }
                cpu_pwrdomain[i] = dom;
                CPUMASK_ORBIT(cpu_pwrdom_mask, i);
        }
        pwrdom_mask = cpu_pwrdom_mask;

        while (CPUMASK_TESTNZERO(pwrdom_mask)) {
                dom_id = BSFCPUMASK(pwrdom_mask);
                CPUMASK_NANDBIT(pwrdom_mask, dom_id);
                dom = cpu_pwrdomain[dom_id];

                CPUMASK_ASSZERO(dom->dom_cpumask);

                snprintf(buf, sizeof(buf),
                         "hw.acpi.cpu.px_dom%d.members", dom->dom_id);
                msize = sizeof(members);
                if (sysctlbyname(buf, members, &msize, NULL, 0) < 0) {
                        cpu_pwrdomain[dom_id] = NULL;
                        free(dom);
                        continue;
                }

                members[msize] = 0;
                for (str = strtok(members, " "); str; str = strtok(NULL, " ")) {
                        n = -1;
                        sscanf(str, "cpu%d", &n);
                        if (n >= 0) {
                                ++ncpu;
                                ++dom->dom_ncpus;
                                CPUMASK_ORBIT(dom->dom_cpumask, n);
                                cpu2pwrdom[n] = dom->dom_id;
                        }
                }
                if (dom->dom_ncpus == 0) {
                        cpu_pwrdomain[dom_id] = NULL;
                        free(dom);
                        continue;
                }
                if (DebugOpt) {
                        printf("dom%d cpumask: ", dom->dom_id);
                        for (i = 0; i < (int)NELEM(dom->dom_cpumask.ary); ++i) {
                                printf("%jx ",
                                    (uintmax_t)dom->dom_cpumask.ary[i]);
                        }
                        printf("\n");
                }
        }

        if (ncpu != NCpus) {
                if (DebugOpt)
                        printf("Found %d cpus, expecting %d\n", ncpu, NCpus);

                pwrdom_mask = cpu_pwrdom_mask;
                while (CPUMASK_TESTNZERO(pwrdom_mask)) {
                        dom_id = BSFCPUMASK(pwrdom_mask);
                        CPUMASK_NANDBIT(pwrdom_mask, dom_id);
                        dom = cpu_pwrdomain[dom_id];
                        if (dom != NULL)
                                free(dom);
                }
                return 0;
        }
        return 1;
}

/*
 * Save per-cpu load and sum of per-cpu load.
 */
static void
get_cputime(double pollrate)
{
        static struct kinfo_cputime ocpu_time[MAXCPU];
        static struct kinfo_cputime ncpu_time[MAXCPU];
        size_t slen;
        int ncpu;
        int cpu;
        uint64_t delta;

        bcopy(ncpu_time, ocpu_time, sizeof(struct kinfo_cputime) * NCpus);

        slen = sizeof(ncpu_time);
        if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
                fprintf(stderr, "kern.cputime sysctl not available\n");
                exit(1);
        }
        ncpu = slen / sizeof(ncpu_time[0]);

        delta = 0;
        for (cpu = 0; cpu < ncpu; ++cpu) {
                uint64_t d;

                d = (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
                     ncpu_time[cpu].cp_nice + ncpu_time[cpu].cp_intr) -
                    (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
                     ocpu_time[cpu].cp_nice + ocpu_time[cpu].cp_intr);
                pcpu_state[cpu].cpu_qavg = (double)d / (pollrate * 1000000.0);

                delta += d;
        }
        global_cpu_state.cpu_qavg = (double)delta / (pollrate * 1000000.0);
}

static void
acpi_getcpufreq_str(int dom_id, int *highest0, int *lowest0)
{
        char buf[256], sysid[64];
        size_t buflen;
        char *ptr;
        int v, highest, lowest;
        int freqidx;

        /*
         * Retrieve availability list
         */
        snprintf(sysid, sizeof(sysid),
                 "hw.acpi.cpu.px_dom%d.available", dom_id);
        buflen = sizeof(buf) - 1;
        if (sysctlbyname(sysid, buf, &buflen, NULL, 0) < 0)
                return;
        buf[buflen] = 0;

        /*
         * Parse out the highest and lowest cpu frequencies
         */
        ptr = buf;
        highest = lowest = 0;
        freqidx = 0;
        while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
                if ((lowest == 0 || lowest > v) &&
                    (LowestCpuFreq <= 0 || v >= LowestCpuFreq))
                        lowest = v;
                if ((highest == 0 || highest < v) &&
                    (HighestCpuFreq <= 0 || v <= HighestCpuFreq))
                        highest = v;
                /* 
                 * Detect turbo mode
                 */
                if (!TurboOpt && highest - v == 1)
                        highest = v;
                ++freqidx;
        }

        /*
         * Frequency array
         */
        if (freqidx > MAXFREQ)
                freqidx = MAXFREQ;
        if (NFreq != freqidx) {
                NFreq = freqidx;
                NFreqChanged = NFREQ_ALL;
        }
        ptr = buf;
        while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
                if (freqidx == 0)
                        break;
                if (FreqAry[freqidx - 1] != v)
                        NFreqChanged = NFREQ_ALL;
                FreqAry[--freqidx] = v;
        }

        *highest0 = highest;
        *lowest0 = lowest;
}

static int
acpi_getcpufreq_bin(int dom_id, int *highest0, int *lowest0)
{
        char sysid[64];
        size_t freqlen;
        int freqcnt, i;
        int freqary[MAXFREQ];

        /*
         * Retrieve availability list
         */
        snprintf(sysid, sizeof(sysid), "hw.acpi.cpu.px_dom%d.avail", dom_id);
        freqlen = sizeof(FreqAry);
        bzero(freqary, sizeof(freqary));
        if (sysctlbyname(sysid, freqary, &freqlen, NULL, 0) < 0)
                return 0;

        freqcnt = freqlen / sizeof(freqary[0]);
        if (NFreq != freqcnt) {
                NFreq = freqcnt;
                NFreqChanged = NFREQ_ALL;
        }
        if (bcmp(freqary, FreqAry, sizeof(FreqAry)) != 0)
                NFreqChanged = NFREQ_ALL;
        bcopy(freqary, FreqAry, sizeof(FreqAry));
        if (freqcnt == 0)
                return 0;

        for (i = freqcnt - 1; i >= 0; --i) {
                *lowest0 = FreqAry[i];
                if (LowestCpuFreq <= 0 || *lowest0 >= LowestCpuFreq)
                        break;
        }

        i = 0;
        *highest0 = FreqAry[0];
        if (!TurboOpt && freqcnt > 1 && FreqAry[0] - FreqAry[1] == 1) {
                i = 1;
                *highest0 = FreqAry[1];
        }
        for (; i < freqcnt; ++i) {
                if (HighestCpuFreq <= 0 || *highest0 <= HighestCpuFreq)
                        break;
                *highest0 = FreqAry[i];
        }
        return 1;
}

static void
acpi_get_cpufreq(int dom_id, int *highest, int *lowest)
{
        *highest = 0;
        *lowest = 0;

        if (acpi_getcpufreq_bin(dom_id, highest, lowest))
                return;
        acpi_getcpufreq_str(dom_id, highest, lowest);
}

static
void
usage(void)
{
        fprintf(stderr, "usage: powerd [-cdeftQU] [-p hysteresis] "
            "[-h highest_freq] [-l lowest_freq] "
            "[-r poll_interval] [-u trigger_up] "
            "[-B min_battery_life] [-L low_battery_linger] "
            "[-P battery_poll_interval] [-T sample_interval] "
            "[-b backlight]\n");
        exit(1);
}

#define BAT_SYSCTL_TIME_MAX     50000000 /* unit: nanosecond */

static int
has_battery(void)
{
        struct timespec s, e;
        size_t len;
        int val;

        clock_gettime(CLOCK_MONOTONIC_FAST, &s);
        BatLifePrevT = s;

        len = sizeof(val);
        if (sysctlbyname("hw.acpi.acline", &val, &len, NULL, 0) < 0) {
                /* No AC line information */
                return 0;
        }
        clock_gettime(CLOCK_MONOTONIC_FAST, &e);

        timespecsub(&e, &s, &e);
        if (e.tv_sec > 0 || e.tv_nsec > BAT_SYSCTL_TIME_MAX) {
                /* hw.acpi.acline takes to long to be useful */
                syslog(LOG_NOTICE, "hw.acpi.acline takes too long");
                return 0;
        }

        clock_gettime(CLOCK_MONOTONIC_FAST, &s);
        len = sizeof(val);
        if (sysctlbyname("hw.acpi.battery.life", &val, &len, NULL, 0) < 0) {
                /* No battery life */
                return 0;
        }
        clock_gettime(CLOCK_MONOTONIC_FAST, &e);

        timespecsub(&e, &s, &e);
        if (e.tv_sec > 0 || e.tv_nsec > BAT_SYSCTL_TIME_MAX) {
                /* hw.acpi.battery.life takes to long to be useful */
                syslog(LOG_NOTICE, "hw.acpi.battery.life takes too long");
                return 0;
        }
        return 1;
}

static void
low_battery_alert(int life)
{
        int fmt, stereo, freq;
        int fd;

        syslog(LOG_ALERT, "low battery life %d%%, please plugin AC line, #%d",
            life, BatShutdownLingerCnt);
        ++BatShutdownLingerCnt;

        if (!BatShutdownAudioAlert)
                return;

        fd = open("/dev/dsp", O_WRONLY);
        if (fd < 0)
                return;

        fmt = AFMT_S16_LE;
        if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt, sizeof(fmt)) < 0)
                goto done;

        stereo = 0;
        if (ioctl(fd, SNDCTL_DSP_STEREO, &stereo, sizeof(stereo)) < 0)
                goto done;

        freq = 44100;
        if (ioctl(fd, SNDCTL_DSP_SPEED, &freq, sizeof(freq)) < 0)
                goto done;

        write(fd, alert1, sizeof(alert1));
        write(fd, alert1, sizeof(alert1));

done:
        close(fd);
}

static int
mon_battery(void)
{
        struct timespec cur, ts;
        int acline, life;
        size_t len;

        clock_gettime(CLOCK_MONOTONIC_FAST, &cur);
        timespecsub(&cur, &BatLifePrevT, &ts);
        if (ts.tv_sec < BatLifePollIntvl)
                return 1;
        BatLifePrevT = cur;

        len = sizeof(acline);
        if (sysctlbyname("hw.acpi.acline", &acline, &len, NULL, 0) < 0)
                return 1;
        if (acline) {
                BatShutdownLinger = -1;
                BatShutdownLingerCnt = 0;
                restore_backlight();
                return 1;
        }

        if (!BackLightDown && BackLightPct != 100) {
                int backlight_max, backlight;

                len = sizeof(backlight_max);
                if (sysctlbyname("hw.backlight_max", &backlight_max, &len,
                    NULL, 0) < 0) {
                        /* No more backlight adjustment */
                        BackLightPct = 100;
                        goto after_backlight;
                }

                len = sizeof(OldBackLightLevel);
                if (sysctlbyname("hw.backlight_level", &OldBackLightLevel, &len,
                    NULL, 0) < 0) {
                        /* No more backlight adjustment */
                        BackLightPct = 100;
                        goto after_backlight;
                }

                backlight = (backlight_max * BackLightPct) / 100;
                if (backlight >= OldBackLightLevel) {
                        /* No more backlight adjustment */
                        BackLightPct = 100;
                        goto after_backlight;
                }

                if (sysctlbyname("hw.backlight_level", NULL, NULL,
                    &backlight, sizeof(backlight)) < 0) {
                        /* No more backlight adjustment */
                        BackLightPct = 100;
                        goto after_backlight;
                }
                BackLightDown = 1;
        }
after_backlight:

        len = sizeof(life);
        if (sysctlbyname("hw.acpi.battery.life", &life, &len, NULL, 0) < 0)
                return 1;

        if (BatShutdownLinger > 0) {
                timespecsub(&cur, &BatShutdownStartT, &ts);
                if (ts.tv_sec > BatShutdownLinger)
                        BatShutdownLinger = 0;
        }

        if (life <= BatLifeMin) {
                if (BatShutdownLinger == 0 || BatShutdownLingerSet == 0) {
                        syslog(LOG_ALERT, "low battery life %d%%, "
                            "shutting down", life);
                        if (vfork() == 0)
                                execlp("poweroff", "poweroff", NULL);
                        return 0;
                } else if (BatShutdownLinger < 0) {
                        BatShutdownLinger = BatShutdownLingerSet;
                        BatShutdownStartT = cur;
                }
                low_battery_alert(life);
        }
        return 1;
}

static void
get_ncpus(void)
{
        size_t slen;

        slen = sizeof(NCpus);
        if (sysctlbyname("hw.ncpu", &NCpus, &slen, NULL, 0) < 0)
                err(1, "sysctlbyname hw.ncpu failed");
        if (DebugOpt)
                printf("hw.ncpu %d\n", NCpus);
}

static void
get_uschedcpus(void)
{
        size_t slen;

        slen = sizeof(usched_cpu_used);
        if (sysctlbyname("kern.usched_global_cpumask", &usched_cpu_used, &slen,
            NULL, 0) < 0)
                err(1, "sysctlbyname kern.usched_global_cpumask failed");
        if (DebugOpt) {
                int i;

                printf("usched cpumask was: ");
                for (i = 0; i < (int)NELEM(usched_cpu_used.ary); ++i)
                        printf("%jx ", (uintmax_t)usched_cpu_used.ary[i]);
                printf("\n");
        }
}

static void
set_uschedcpus(void)
{
        if (DebugOpt) {
                int i;

                printf("usched cpumask: ");
                for (i = 0; i < (int)NELEM(usched_cpu_used.ary); ++i) {
                        printf("%jx ",
                            (uintmax_t)usched_cpu_used.ary[i]);
                }
                printf("\n");
        }
        sysctlbyname("kern.usched_global_cpumask", NULL, 0,
            &usched_cpu_used, sizeof(usched_cpu_used));
}

static int
has_perfbias(void)
{
        size_t len;
        int hint;

        len = sizeof(hint);
        if (sysctlbyname("machdep.perfbias0.hint", &hint, &len, NULL, 0) < 0)
                return 0;
        return 1;
}

static void
set_perfbias(int cpu, int inc)
{
        int hint = inc ? 0 : 15;
        char sysid[64];

        if (DebugOpt)
                printf("cpu%d set perfbias hint %d\n", cpu, hint);
        snprintf(sysid, sizeof(sysid), "machdep.perfbias%d.hint", cpu);
        sysctlbyname(sysid, NULL, NULL, &hint, sizeof(hint));
}

static void
init_perf(void)
{
        struct cpu_state *state;
        int cpu;

        /* Get usched cpumask */
        get_uschedcpus();

        /*
         * Assume everything are used and are maxed out, before we
         * start.
         */
        CPUMASK_ASSBMASK(cpu_used, NCpus);
        cpu_pwrdom_used = cpu_pwrdom_mask;
        global_pcpu_limit = NCpus;

        for (cpu = 0; cpu < NCpus; ++cpu) {
                state = &pcpu_state[cpu];

                state->cpu_uavg = 0.0;
                state->cpu_davg = 0.0;
                state->cpu_limit = 1;
                state->cpu_count = 1;
                snprintf(state->cpu_name, sizeof(state->cpu_name), "cpu%d",
                    cpu);
        }

        state = &global_cpu_state;
        state->cpu_uavg = 0.0;
        state->cpu_davg = 0.0;
        state->cpu_limit = NCpus;
        state->cpu_count = NCpus;
        strlcpy(state->cpu_name, "global", sizeof(state->cpu_name));
}

static int
get_nstate(struct cpu_state *state, double srt)
{
        int ustate, dstate, nstate;

        /* speeding up */
        state->cpu_uavg = (state->cpu_uavg * 2.0 + state->cpu_qavg) / 3.0;
        /* slowing down */
        state->cpu_davg = (state->cpu_davg * srt + state->cpu_qavg) / (srt + 1);
        if (state->cpu_davg < state->cpu_uavg)
                state->cpu_davg = state->cpu_uavg;

        ustate = state->cpu_uavg / TriggerUp;
        if (ustate < state->cpu_limit)
                ustate = state->cpu_uavg / TriggerDown;
        dstate = state->cpu_davg / TriggerUp;
        if (dstate < state->cpu_limit)
                dstate = state->cpu_davg / TriggerDown;

        nstate = (ustate > dstate) ? ustate : dstate;
        if (nstate > state->cpu_count)
                nstate = state->cpu_count;

        if (DebugOpt) {
                printf("%s qavg=%5.2f uavg=%5.2f davg=%5.2f "
                    "%2d ncpus=%d\n", state->cpu_name,
                    state->cpu_qavg, state->cpu_uavg, state->cpu_davg,
                    state->cpu_limit, nstate);
        }
        return nstate;
}

static void
mon_perf(double srt)
{
        cpumask_t ocpu_used, ocpu_pwrdom_used;
        int pnstate = 0, nstate;
        int cpu;

        /*
         * Find cpus requiring performance and their cooresponding power
         * domains.  Save the number of cpus requiring performance in
         * pnstate.
         */
        ocpu_used = cpu_used;
        ocpu_pwrdom_used = cpu_pwrdom_used;

        CPUMASK_ASSZERO(cpu_used);
        CPUMASK_ASSZERO(cpu_pwrdom_used);

        for (cpu = 0; cpu < NCpus; ++cpu) {
                struct cpu_state *state = &pcpu_state[cpu];
                int s;

                s = get_nstate(state, srt);
                if (s) {
                        CPUMASK_ORBIT(cpu_used, cpu);
                        CPUMASK_ORBIT(cpu_pwrdom_used, cpu2pwrdom[cpu]);
                }
                pnstate += s;

                state->cpu_limit = s;
        }

        /*
         * Calculate nstate, the number of cpus we wish to run at max
         * performance.
         */
        nstate = get_nstate(&global_cpu_state, srt);

        if (nstate == global_cpu_state.cpu_limit &&
            (NFreqChanged & NFREQ_MONPERF) == 0 &&
            (pnstate == global_pcpu_limit || nstate > pnstate)) {
                /* Nothing changed; keep the sets */
                cpu_used = ocpu_used;
                cpu_pwrdom_used = ocpu_pwrdom_used;

                global_pcpu_limit = pnstate;
                return;
        }
        NFreqChanged &= ~NFREQ_MONPERF;
        global_pcpu_limit = pnstate;

        if (nstate > pnstate) {
                /*
                 * Add spare cpus to meet global performance requirement.
                 */
                add_spare_cpus(ocpu_used, nstate - pnstate);
        }

        global_cpu_state.cpu_limit = nstate;

        /*
         * Adjust cpu and cpu power domain performance
         */
        adj_perf(ocpu_used, ocpu_pwrdom_used);
}

static void
add_spare_cpus(const cpumask_t ocpu_used, int ncpu)
{
        cpumask_t saved_pwrdom, xcpu_used;
        int done = 0, cpu;

        /*
         * Find more cpus in the previous cpu set.
         */
        xcpu_used = cpu_used;
        CPUMASK_XORMASK(xcpu_used, ocpu_used);
        while (CPUMASK_TESTNZERO(xcpu_used)) {
                cpu = BSFCPUMASK(xcpu_used);
                CPUMASK_NANDBIT(xcpu_used, cpu);

                if (CPUMASK_TESTBIT(ocpu_used, cpu)) {
                        CPUMASK_ORBIT(cpu_pwrdom_used, cpu2pwrdom[cpu]);
                        CPUMASK_ORBIT(cpu_used, cpu);
                        --ncpu;
                        if (ncpu == 0)
                                return;
                }
        }

        /*
         * Find more cpus in the used cpu power domains.
         */
        saved_pwrdom = cpu_pwrdom_used;
again:
        while (CPUMASK_TESTNZERO(saved_pwrdom)) {
                cpumask_t unused_cpumask;
                int dom;

                dom = BSFCPUMASK(saved_pwrdom);
                CPUMASK_NANDBIT(saved_pwrdom, dom);

                unused_cpumask = cpu_pwrdomain[dom]->dom_cpumask;
                CPUMASK_NANDMASK(unused_cpumask, cpu_used);

                while (CPUMASK_TESTNZERO(unused_cpumask)) {
                        cpu = BSFCPUMASK(unused_cpumask);
                        CPUMASK_NANDBIT(unused_cpumask, cpu);

                        CPUMASK_ORBIT(cpu_pwrdom_used, dom);
                        CPUMASK_ORBIT(cpu_used, cpu);
                        --ncpu;
                        if (ncpu == 0)
                                return;
                }
        }
        if (!done) {
                done = 1;
                /*
                 * Find more cpus in unused cpu power domains
                 */
                saved_pwrdom = cpu_pwrdom_mask;
                CPUMASK_NANDMASK(saved_pwrdom, cpu_pwrdom_used);
                goto again;
        }
        if (DebugOpt)
                printf("%d cpus not found\n", ncpu);
}

static void
acpi_set_cpufreq(int dom, int inc)
{
        int lowest, highest, desired;
        char sysid[64];

        acpi_get_cpufreq(dom, &highest, &lowest);
        if (highest == 0 || lowest == 0)
                return;
        desired = inc ? highest : lowest;

        if (DebugOpt)
                printf("dom%d set frequency %d\n", dom, desired);
        snprintf(sysid, sizeof(sysid), "hw.acpi.cpu.px_dom%d.select", dom);
        sysctlbyname(sysid, NULL, NULL, &desired, sizeof(desired));
}

static void
adj_cpu_pwrdom(int dom, int inc)
{
        if (AdjustCpuFreq && (inc == 0 || AdjustCpuFreqOverride == 0))
                acpi_set_cpufreq(dom, inc);
}

static void
adj_cpu_perf(int cpu, int inc)
{
        if (DebugOpt) {
                if (inc)
                        printf("cpu%d increase perf\n", cpu);
                else
                        printf("cpu%d decrease perf\n", cpu);
        }

        if (HasPerfbias)
                set_perfbias(cpu, inc);
        if (AdjustCstate)
                set_cstate(cpu, inc);
}

static void
adj_perf(cpumask_t xcpu_used, cpumask_t xcpu_pwrdom_used)
{
        int cpu, inc;

        if (AdjustUsched) {
                cpumask_t old_usched_used;

                /*
                 * Set cpus requiring performance to the userland process
                 * scheduler.  Leave the rest of cpus unmapped.
                 */
                old_usched_used = usched_cpu_used;
                usched_cpu_used = cpu_used;
                if (CPUMASK_TESTZERO(usched_cpu_used))
                        CPUMASK_ORBIT(usched_cpu_used, 0);
                if (CPUMASK_CMPMASKNEQ(usched_cpu_used, old_usched_used))
                        set_uschedcpus();
        }

        /*
         * Adjust per-cpu performance for any cpus which changed.
         */
        CPUMASK_XORMASK(xcpu_used, cpu_used);
        if (NFreqChanged & NFREQ_ADJPERF)
                CPUMASK_ASSBMASK(xcpu_used, NCpus);
        while (CPUMASK_TESTNZERO(xcpu_used)) {
                cpu = BSFCPUMASK(xcpu_used);
                CPUMASK_NANDBIT(xcpu_used, cpu);

                if (CPUMASK_TESTBIT(cpu_used, cpu)) {
                        /* Increase cpu performance */
                        inc = 1;
                } else {
                        /* Decrease cpu performance */
                        inc = 0;
                }
                adj_cpu_perf(cpu, inc);
        }

        /*
         * Adjust cpu power domain performance.  This could affect
         * a set of cpus.
         */
        CPUMASK_XORMASK(xcpu_pwrdom_used, cpu_pwrdom_used);
        if (NFreqChanged & NFREQ_ADJPERF)
                CPUMASK_ASSBMASK(xcpu_pwrdom_used, NCpus);
        while (CPUMASK_TESTNZERO(xcpu_pwrdom_used)) {
                int dom;

                dom = BSFCPUMASK(xcpu_pwrdom_used);
                CPUMASK_NANDBIT(xcpu_pwrdom_used, dom);

                if (CPUMASK_TESTBIT(cpu_pwrdom_used, dom)) {
                        /* Increase cpu power domain performance */
                        inc = 1;
                } else {
                        /* Decrease cpu power domain performance */
                        inc = 0;
                }
                adj_cpu_pwrdom(dom, inc);
        }
        NFreqChanged &= ~NFREQ_ADJPERF;
}

static void
restore_perf(void)
{
        cpumask_t ocpu_used, ocpu_pwrdom_used;

        /* Remove highest cpu frequency limitation */
        HighestCpuFreq = 0;

        ocpu_used = cpu_used;
        ocpu_pwrdom_used = cpu_pwrdom_used;

        /* Max out all cpus and cpu power domains performance */
        CPUMASK_ASSBMASK(cpu_used, NCpus);
        cpu_pwrdom_used = cpu_pwrdom_mask;

        adj_perf(ocpu_used, ocpu_pwrdom_used);

        if (AdjustCstate) {
                /*
                 * Restore the original mwait C-state
                 */
                if (DebugOpt)
                        printf("global set cstate %s\n", orig_global_cx);
                sysctlbyname("machdep.mwait.CX.idle", NULL, NULL,
                    orig_global_cx, strlen(orig_global_cx) + 1);
        }
}

static int
probe_cstate(void)
{
        char cx_supported[1024];
        const char *target;
        char *ptr;
        int idle_hlt, deep = 1;
        size_t len;

        len = sizeof(idle_hlt);
        if (sysctlbyname("machdep.cpu_idle_hlt", &idle_hlt, &len, NULL, 0) < 0)
                return 0;
        if (idle_hlt != 1)
                return 0;

        len = sizeof(cx_supported);
        if (sysctlbyname("machdep.mwait.CX.supported", cx_supported, &len,
            NULL, 0) < 0)
                return 0;

        len = sizeof(orig_global_cx);
        if (sysctlbyname("machdep.mwait.CX.idle", orig_global_cx, &len,
            NULL, 0) < 0)
                return 0;

        strlcpy(cpu_perf_cx, "AUTODEEP", sizeof(cpu_perf_cx));
        cpu_perf_cxlen = strlen(cpu_perf_cx) + 1;
        if (sysctlbyname("machdep.mwait.CX.idle", NULL, NULL,
            cpu_perf_cx, cpu_perf_cxlen) < 0) {
                /* AUTODEEP is not supported; try AUTO */
                deep = 0;
                strlcpy(cpu_perf_cx, "AUTO", sizeof(cpu_perf_cx));
                cpu_perf_cxlen = strlen(cpu_perf_cx) + 1;
                if (sysctlbyname("machdep.mwait.CX.idle", NULL, NULL,
                    cpu_perf_cx, cpu_perf_cxlen) < 0)
                        return 0;
        }

        if (!deep)
                target = "C2/0";
        else
                target = NULL;
        for (ptr = strtok(cx_supported, " "); ptr != NULL;
             ptr = strtok(NULL, " ")) {
                if (target == NULL ||
                    (target != NULL && strcmp(ptr, target) == 0)) {
                        strlcpy(cpu_idle_cx, ptr, sizeof(cpu_idle_cx));
                        cpu_idle_cxlen = strlen(cpu_idle_cx) + 1;
                        if (target != NULL)
                                break;
                }
        }
        if (cpu_idle_cxlen == 0)
                return 0;

        if (DebugOpt) {
                printf("cstate orig %s, perf %s, idle %s\n",
                    orig_global_cx, cpu_perf_cx, cpu_idle_cx);
        }
        return 1;
}

static void
set_cstate(int cpu, int inc)
{
        const char *cst;
        char sysid[64];
        size_t len;

        if (inc) {
                cst = cpu_perf_cx;
                len = cpu_perf_cxlen;
        } else {
                cst = cpu_idle_cx;
                len = cpu_idle_cxlen;
        }

        if (DebugOpt)
                printf("cpu%d set cstate %s\n", cpu, cst);
        snprintf(sysid, sizeof(sysid), "machdep.mwait.CX.idle%d", cpu);
        sysctlbyname(sysid, NULL, NULL, cst, len);
}

static void
restore_backlight(void)
{
        if (BackLightDown) {
                BackLightDown = 0;
                sysctlbyname("hw.backlight_level", NULL, NULL,
                    &OldBackLightLevel, sizeof(OldBackLightLevel));
        }
}

/*
 * get_cputemp() / mon_cputemp()
 *
 * This enforces the maximum cpu frequency based on temperature
 * verses MinTemp and MaxTemp.
 */
static int
get_cputemp(void)
{
        char sysid[64];
        struct sensor sensor;
        size_t sensor_size;
        int t;
        int mt = -1;
        int n;

        for (n = 0; ; ++n) {
                t = 0;
                snprintf(sysid, sizeof(sysid),
                         "hw.sensors.cpu_node%d.temp0", n);
                sensor_size = sizeof(sensor);
                if (sysctlbyname(sysid, &sensor, &sensor_size, NULL, 0) < 0)
                        break;
                t = -1;
                if ((sensor.flags & (SENSOR_FINVALID | SENSOR_FUNKNOWN)) == 0) {
                        t = (int)((sensor.value - 273150000) / 1000000);
                        if (mt < t)
                                mt = t;
                }
        }
        if (n)
                return mt;

        /*
         * Missing nodeN for some reason, try cpuN.
         */
        for (n = 0; ; ++n) {
                t = 0;
                snprintf(sysid, sizeof(sysid),
                         "hw.sensors.cpu%d.temp0", n);
                sensor_size = sizeof(sensor);
                if (sysctlbyname(sysid, &sensor, &sensor_size, NULL, 0) < 0)
                        break;
                t = -1;
                if ((sensor.flags & (SENSOR_FINVALID | SENSOR_FUNKNOWN)) == 0) {
                        t = (int)((sensor.value - 273150000) / 1000000);
                        if (mt < t)
                                mt = t;
                }
        }
        return mt;
}

static void
set_global_freq(int freq)
{
        if (freq > 0)
                sysctlbyname("hw.acpi.cpu.px_global",
                             NULL, NULL, &freq, sizeof(freq));
}

static int
get_global_freq(void)
{
        int freq;
        size_t freq_size;

        freq = -1;
        freq_size = sizeof(freq);
        sysctlbyname("hw.acpi.cpu.px_global", &freq, &freq_size, NULL, 0);

        return freq;
}

static void
mon_cputemp(void)
{
        static int last_temp = -1;
        static int last_idx = -1;
        int temp = get_cputemp();
        int idx;
        int lowest;
        int highest;
        static int CurPXGlobal __unused;

        /*
         * Reseed FreqAry, it can change w/AC power state
         */
        acpi_get_cpufreq(0, &lowest, &highest);

        /*
         * Some cpu frequency steps can cause large shifts in cpu temperature,
         * creating an oscillation that min-maxes the temperature in a way
         * that is not desireable.  To deal with this, we impose an exponential
         * average for any temperature change.
         *
         * We have to do this in both directions, otherwise (in particular)
         * laptop fan responsiveness and temperature sensor response times
         * can create major frequency oscillations.
         */
        if (last_temp < 0 || (NFreqChanged & NFREQ_CPUTEMP)) {
                NFreqChanged &= ~NFREQ_CPUTEMP;
                last_temp = temp << 8;
        } else if (temp < last_temp) {
                last_temp = (last_temp * 15 + (temp << 8)) / 16;
                if (DebugOpt) {
                        printf("Falling temp %d (use %d)\n",
                                temp, (last_temp >> 8));
                }
        } else {
                last_temp = (last_temp * 15 + (temp << 8)) / 16;
                if (DebugOpt) {
                        printf("Rising temp %d (use %d)\n",
                                temp, (last_temp >> 8));
                }
        }
        temp = last_temp >> 8;

        /*
         * CPU Temp not available or available frequencies not yet
         * probed.
         */
        if (DebugOpt)
                printf("Temp %d {%d-%d} NFreq=%d)\n",
                       temp, MinTemp, MaxTemp, NFreq);
        if (temp <= 0)
                return;
        if (NFreq == 0)
                return;

        /*
         * Return to normal operation if under the minimum
         */
        if (temp <= MinTemp) {
                if (AdjustCpuFreqOverride) {
                        AdjustCpuFreqOverride = 0;
                        CurPXGlobal = 0;
                        NFreqChanged = NFREQ_ALL;
                        last_idx = -1;
                        syslog(LOG_ALERT,
                               "Temp below %d, returning to normal operation",
                               MinTemp);
                        if (SavedPXGlobal)
                                set_global_freq(SavedPXGlobal);
                }
                return;
        }

        /*
         * Hysteresis before entering temperature control mode
         */
        if (AdjustCpuFreqOverride == 0 &&
            temp <= MinTemp + (MaxTemp - MinTemp) / 10 + 1) {
                return;
        }

        /*
         * Override frequency controls (except for idle -> lowest)
         */
        if (AdjustCpuFreqOverride == 0) {
                AdjustCpuFreqOverride = 1;
                SavedPXGlobal = get_global_freq();
                CurPXGlobal = 0;
                NFreqChanged = NFREQ_ALL;
                last_idx = -1;
                syslog(LOG_ALERT,
                       "Temp %d {%d-%d}, entering temperature control mode",
                       temp, MinTemp, MaxTemp);
        }
        if (temp > MaxTemp + (MaxTemp - MinTemp) / 10 + 1) {
                syslog(LOG_ALERT,
                       "Temp %d {%d-%d}, TOO HOT!!!",
                       temp, MinTemp, MaxTemp);
        }
        idx = (temp - MinTemp) * NFreq / (MaxTemp - MinTemp);
        if (idx < 0 || idx >= NFreq)    /* overtemp */
                idx = NFreq - 1;

        /*
         * Limit frequency shifts to single steps in both directions.
         * Some fans react very quickly, this will reduce oscillations.
         */
        if (DebugOpt)
                printf("Temp index %d (use %d)\n", idx, last_idx);
        if (last_idx >= 0 && idx < last_idx)
                idx = last_idx - 1;
        else if (last_idx >= 0 && idx > last_idx)
                idx = last_idx + 1;
        last_idx = idx;

        /*
         * One last thing, make sure our frequency adheres to
         * HighestCpuFreq.  However, override LowestCpuFreq for
         * temperature control purposes.
         */
        while (HighestCpuFreq > 0 && idx < NFreq &&
               FreqAry[idx] > HighestCpuFreq) {
                ++idx;
        }
#if 0
        /*
         * Currently ignore LowestCpuFreq if temp control thinks it
         * needs to go lower
         */
        while (LowestCpuFreq > 0 && idx > 0 &&
               FreqAry[idx] < LowestCpuFreq) {
                --idx;
        }
#endif

        if (FreqAry[idx] != CurPXGlobal) {
                CurPXGlobal = FreqAry[idx];

#if 0
                /* this can get noisy so don't log for now */
                syslog(LOG_ALERT,
                       "Temp %d {%d-%d}, set frequency %d",
                       temp, MinTemp, MaxTemp, CurPXGlobal);
#endif
        }
        set_global_freq(CurPXGlobal);
}