> From: Dave Voutila <[email protected]>
> Date: Tue, 15 Feb 2022 09:11:54 -0500
> Content-Type: text/plain
> 
> My Microsoft Surface Go 3 is full of lovely oddities where Microsoft
> decided to ignore using standard ACPI paradigms. One is the way thermal
> zones and trip points are exposed: acpitz(4) can read zones, but there's
> no trip points expressed!
> 
> As a result, a simple `make -j4` to build a kernel puts my Go 3 into a
> boosted clock speed and rapidly overheats. The firmware kills the
> machine. (This is the model with the Intel i3-10100Y.)
> 
> Microsoft implemented a new device (MSHW0189) with methods for reading
> thermal zones as well as requesting trip points. Thank you, Redmond, for
> being special.
> 
> The below diff is ready for testing. I've been using it with success for
> a few days. Currently it:
> 
> 1. Exposes thermal zones as sensors, similar to acpitz(4), albeit using
>    a sensor task and not acpi polling.
> 
> 2. Uses driver-provided trip points for preventing critical overheating
>    by throttling the cpu via cpu_setperf. (On the Go 3 this ends up
>    leveraging the existing cpu_setperf, which is Intel's SpeedStep.)
> 
> 3. The driver's objective is to maximize performance while keeping
>    temperature at a "safe" level. Power minimization isn't a current
>    objective. "Safe" has been determined through experimentation.
> 
> 4. It prevents acpitz(4) from attaching on a Surface Go 3 and, for any
>    system without defined hot/critical zones, it skips hijacking
>    cpu_setperf. (Without this, we race against acpitz to see who becomes
>    the controller of cpu_setperf logic.)
> 
> Feedback is welcome, but I am still cleaning things up a bit. I haven't
> seen any Surface Go 3's in our dmesg@ log so I wanted to get this out
> widely early in case there are folks with them.

I won't point out too many style nits then ;)

But we avoid static on functions in the kernel if we can.

> Questions / Areas I'm working through:
> 
> o Possible race conditions between sensor tasks and acpi tasks.
> o Tuning task intervals.
> o Finding another device other than a Surface Go 3 that uses MSHW0189
> o Naming...surfacetz(4)?

The name is fine ;)

> o Error handling in _attach...if we can't make a task, do we leave the
>   device in a borked state?

If we can't register the sensors task at the point where this driver
attaches, we have bigger problems...

As long as you don't call sensordev_install() you'll just have a dead
device that doesn't do anything.  That's fine.

> 
> Note: the "algorithm" for throttling or relaxing the restrictions on
> the cpu is crude. It may be overly aggressive or not aggressive
> enough. My testing has primarily been with `make -j4` across the tree as
> that's what was killing my device.

It may be worth looking at building in some hysteresis; don't "relax"
until you've dropped a bit below the trip temperature.

> diff refs/heads/master refs/heads/mshw0189
> blob - e3e424ca8c85c07ec43a6e1ed61f161c6a2238c1
> blob + db9313480da440b8f0f76d0433d84b494a8baf0c
> --- sys/arch/amd64/conf/GENERIC
> +++ sys/arch/amd64/conf/GENERIC
> @@ -81,6 +81,7 @@ asmc*               at acpi?        # Apple SMC
>  tpm*         at acpi?
>  acpihve*     at acpi?
>  acpisurface* at acpi?
> +surfacetz*   at acpi?
>  acpihid*     at acpi?
>  ipmi0                at acpi? disable
>  ccpmic*              at iic?
> blob - c4e166dd5390f4aa9a0225829b4f85f0c53bda41
> blob + 5c2d34979a1e7ba095c9f7a34a7cb12b5df3cef9
> --- sys/dev/acpi/acpitz.c
> +++ sys/dev/acpi/acpitz.c
> @@ -101,6 +101,12 @@ extern struct aml_node aml_root;
>  void
>  acpitz_init_perf(void *arg)
>  {
> +     struct acpitz_softc *sc = arg;
> +
> +     /* If there are no identified trip points, leave cpu_setperf alone. */
> +     if (sc->sc_psv == -1 && sc->sc_crt == -1 && sc->sc_hot == -1)
> +             return;
> +
>       if (acpitz_perflevel == -1)
>               acpitz_perflevel = perflevel;
> 
> @@ -181,6 +187,14 @@ acpitz_match(struct device *parent, void *match, void
>       if (aa->aaa_node->value->type != AML_OBJTYPE_THERMZONE)
>               return (0);
> 
> +     /*
> +      * Some Microsoft Surface devices provide their own "special" thermal
> +      * zone devices.
> +      */
> +     if (hw_vendor != NULL && strncmp(hw_vendor, "Microsoft", 9) == 0 &&
> +         hw_prod != NULL && strncmp(hw_prod, "Surface Go 3", 12) == 0)
> +             return (0);

I hate these types of checks.  What is the downside of having
acpitz(4) attach on these machines beyond duplicated temperature
sensors?

> +
>       return (1);
>  }
> 
> blob - f97eb6d4e3e698578502805659a2a251712be6b3
> blob + 964d2dd5f98dfef53a938af584577c6e80f6519d
> --- sys/dev/acpi/files.acpi
> +++ sys/dev/acpi/files.acpi
> @@ -238,6 +238,11 @@ device   acpisurface
>  attach       acpisurface at acpi
>  file dev/acpi/acpisurface.c          acpisurface
> 
> +# MSHW0189 Surface Thermal Zones
> +device  surfacetz
> +attach  surfacetz at acpi
> +file    dev/acpi/surfacetz.c            surfacetz
> +
>  # IPMI
>  attach       ipmi at acpi with ipmi_acpi
>  file dev/acpi/ipmi_acpi.c            ipmi_acpi
> blob - /dev/null
> blob + f668f0ea5ba9355517fbd8921a10ac499b14fae0 (mode 644)
> --- /dev/null
> +++ sys/dev/acpi/surfacetz.c
> @@ -0,0 +1,547 @@
> +
> +/*
> + * Copyright (c) 2022 Dave Voutila <[email protected]>
> + *
> + * Permission to use, copy, modify, and 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.
> + */
> +/*
> + * Microsoft Surface Thermal Zones
> + */
> +#include <sys/param.h>
> +#include <sys/proc.h>
> +#include <sys/signalvar.h>
> +#include <sys/systm.h>
> +#include <sys/types.h>
> +#include <sys/timeout.h>
> +#include <sys/kthread.h>
> +
> +#include <dev/acpi/acpireg.h>
> +#include <dev/acpi/acpivar.h>
> +#include <dev/acpi/acpidev.h>
> +#include <dev/acpi/amltypes.h>
> +#include <dev/acpi/dsdt.h>
> +
> +#include <sys/atomic.h>
> +#include <sys/sensors.h>
> +
> +#define MAX_SENSORS  16      /* Assumption */
> +#define PERFSTEP     10
> +#define COLD         0
> +#define WARM         49
> +#define HOT          56
> +#define CRITICAL     67
> +#define RELAX_DELAY  3       /* Guess */
> +
> +#define DK_TO_C(x)   ((x - 2732) / 10)
> +#define DK_TO_UK(x)  ((x * 100000) - 50000)
> +#define C_TO_DK(x)   ((x * 10) + 2732)
> +#define UK_TO_DK(x)  ((x + 50000) / 100000)
> +#define UK_TO_C(x)   (DK_TO_C(UK_TO_DK(x)))
> +#define SURFACETZ_DEBUG              /* XXX */
> +
> +#ifdef SURFACETZ_DEBUG
> +#define DPRINTF(x...)        do { printf(x); } while(0)
> +#define ASSERT_SENSOR(x)     KASSERT(x > 0 && x < MAX_SENSORS)
> +#else
> +#define DPRINTF(x...)
> +#define ASSERT_SENSOR(x)
> +#endif /* SURFACETZ_DEBUG */
> +
> +struct surfacetz_softc;              /* Forward declaration */
> +
> +struct surfacetz_arg {
> +     struct surfacetz_softc  *arg_sc;
> +     uint8_t                  arg_sensor;
> +};
> +
> +struct surfacetz_softc {
> +     struct device            sc_dev;
> +
> +     struct acpiec_softc     *sc_ec;
> +     struct acpi_softc       *sc_acpi;
> +     struct aml_node         *sc_devnode;
> +
> +     uint8_t                  sc_sensors;    /* Number of detected sensors */
> +     uint8_t                  sc_triplevel[MAX_SENSORS];
> +
> +     struct timeout           sc_timeout[MAX_SENSORS];
> +     struct surfacetz_arg     sc_timeout_arg[MAX_SENSORS];
> +
> +     struct ksensor           sc_ksensor[MAX_SENSORS];
> +     struct ksensordev        sc_ksensdev;
> +     struct sensor_task      *sc_ksensor_task;
> +};
> +
> +int          surfacetz_match(struct device *, void *, void *);
> +void         surfacetz_attach(struct device *, struct device *, void *);
> +
> +void         surfacetz_init_perf(void *);
> +int          surfacetz_notify(struct aml_node *, int, void *);
> +void         surfacetz_setperf(int);
> +
> +static void  add_sensor_task(void *);
> +static void  add_trip_task(struct surfacetz_softc *, uint8_t, uint8_t);
> +
> +static void  surfacetz_init(struct surfacetz_softc *);
> +static uint8_t       surfacetz_list_sensors(struct surfacetz_softc *);
> +static uint64_t      surfacetz_read_sensor(struct surfacetz_softc *, 
> uint8_t);
> +static void  surfacetz_set_trip(struct surfacetz_softc *, uint8_t, uint8_t);
> +static void  surfacetz_relax(void *);
> +static void  surfacetz_throttle(struct surfacetz_softc *, uint8_t, uint8_t);
> +static void  surfacetz_update_sensors(void *, int);
> +static void  surfacetz_update_trip(void *, int);
> +
> +void         (*surfacetz_cpu_setperf)(int);
> +static int   max_perflevel = -1;
> +static int   desired_perflevel = -1;
> +
> +extern void  (*cpu_setperf)(int);
> +extern int   perflevel;
> +
> +struct cfattach surfacetz_ca = {
> +     sizeof(struct surfacetz_softc), surfacetz_match, surfacetz_attach,
> +     NULL, NULL
> +};
> +
> +struct cfdriver surfacetz_cd = {
> +     NULL, "surfacetz", DV_DULL
> +};
> +
> +const char *surfacetz_hids[] = {
> +     "MSHW0189",
> +     NULL
> +};
> +
> +int
> +surfacetz_match(struct device *parent, void *match, void *aux)
> +{
> +     struct acpi_attach_args *aa = aux;
> +     struct cfdata *cf = match;
> +
> +     if (!acpi_matchhids(aa, surfacetz_hids, cf->cf_driver->cd_name))
> +             return (0);
> +
> +     return (1);
> +}
> +
> +void
> +surfacetz_attach(struct device *parent, struct device *self, void *aux)
> +{
> +     struct surfacetz_softc  *sc = (struct surfacetz_softc *)self;
> +     struct acpi_attach_args *aa = aux;
> +     uint8_t                  i, sensors = 0;
> +
> +     sc->sc_acpi = (struct acpi_softc *)parent;
> +     sc->sc_devnode = aa->aaa_node;
> +
> +     sensors = surfacetz_list_sensors(sc);
> +     sc->sc_sensors = sensors;
> +     memset(&sc->sc_triplevel, COLD, sizeof(sc->sc_triplevel));
> +
> +     memset(&sc->sc_timeout, 0, sizeof(sc->sc_timeout));
> +     memset(&sc->sc_timeout_arg, 0, sizeof(sc->sc_timeout_arg));
> +     for (i = 0; i < nitems(sc->sc_timeout); i++) {
> +             sc->sc_timeout_arg[i].arg_sc = sc;
> +             sc->sc_timeout_arg[i].arg_sensor = i + 1;       /* 1-indexed */
> +             timeout_set(&sc->sc_timeout[i], surfacetz_relax,
> +                 &sc->sc_timeout_arg[i]);
> +     }
> +
> +     memset(&sc->sc_ksensdev, 0, sizeof(sc->sc_ksensdev));
> +     strlcpy(sc->sc_ksensdev.xname, DEVNAME(sc),
> +         sizeof(sc->sc_ksensdev.xname));
> +
> +     memset(&sc->sc_ksensor, 0, sizeof(sc->sc_ksensor));
> +     for (i = 0; i < sc->sc_sensors; i++) {
> +             strlcpy(sc->sc_ksensor[i].desc, "zone temperature",
> +                 sizeof(sc->sc_ksensor[i].desc));
> +             sc->sc_ksensor[i].type = SENSOR_TEMP;
> +             sensor_attach(&sc->sc_ksensdev, &sc->sc_ksensor[i]);
> +     }
> +     if (!(sc->sc_ksensor_task = sensor_task_register(sc, add_sensor_task,
> +         2)))

Best to avoid assignments in if statements like that; especially when
it means you end up having to wrap the line anyway!

> +             printf("unable to register sensor task");
> +     else
> +             sensordev_install(&sc->sc_ksensdev);
> +
> +     printf("\n");
> +     surfacetz_init(sc);
> +
> +     kthread_create_deferred(surfacetz_init_perf, sc);
> +}
> +
> +void
> +surfacetz_init_perf(void *arg)
> +{
> +     struct surfacetz_softc  *sc = arg;
> +     int                      i;
> +
> +     if (max_perflevel == -1) {
> +             max_perflevel = 100;
> +
> +             /* Set initial trip points now that we're later in startup. */
> +             for (i = 1; i < sc->sc_sensors + 1; i++)
> +                     /* OK to call here as we're in attach mode. */
> +                     surfacetz_set_trip(sc, i, HOT);
> +     }
> +
> +     if (cpu_setperf != surfacetz_setperf) {
> +             surfacetz_cpu_setperf = cpu_setperf;
> +             cpu_setperf = surfacetz_setperf;
> +     }
> +}
> +
> +void
> +surfacetz_setperf(int level)
> +{
> +     if (level < 0 || level > 100)
> +             return;
> +
> +     atomic_swap_uint(&desired_perflevel, level);
> +
> +     DPRINTF("%s: desired=%d, max=%d\n", __func__, desired_perflevel,
> +         max_perflevel);
> +
> +     if (surfacetz_cpu_setperf) {
> +             if (desired_perflevel > max_perflevel)
> +                     surfacetz_cpu_setperf(max_perflevel);
> +             else
> +                     surfacetz_cpu_setperf(desired_perflevel);
> +     }
> +}
> +
> +static void
> +surfacetz_init(struct surfacetz_softc *sc)
> +{
> +     /* No need to poll as the MSHW0189 will notify us on trip. */
> +     aml_register_notify(sc->sc_devnode, NULL, surfacetz_notify, sc,
> +         ACPIDEV_NOPOLL);
> +
> +     /* Let the system run hot during boot. */
> +     for (int i = 1; i < sc->sc_sensors + 1; i++)
> +             surfacetz_set_trip(sc, i, CRITICAL);
> +}
> +
> +static uint8_t
> +surfacetz_list_sensors(struct surfacetz_softc *sc)
> +{
> +     int                     i, rc;
> +     int64_t                 temp = 0;
> +     uint8_t                 found = 0;
> +
> +     /*
> +      * LIST gives us a bitmask of sensors. Assume that the bitmask is
> +      * without gaps and starts with the first bit.
> +      */
> +     rc = aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "LIST", 0, NULL,
> +         &temp);
> +     if (rc) {
> +             printf(": found 0 sensors");
> +             return (0);
> +     }
> +
> +     for (i = 0; i < MAX_SENSORS; i++) {
> +             if (temp & (1 << i))
> +                     found++;
> +             else
> +                     break;
> +     }
> +
> +     if (i == MAX_SENSORS)
> +             printf(": %d sensors (maximum)", found);
> +     else
> +             printf(": %d sensors", found);
> +
> +     return (found);
> +}
> +
> +/*
> + * Call the MSHW0189 "TEMP" AML method for a given sensor index, returning 
> the
> + * temperature in decikelvin.
> + */
> +static uint64_t
> +surfacetz_read_sensor(struct surfacetz_softc *sc, uint8_t sensor)
> +{
> +     struct aml_value        val;
> +     int64_t                 decikelvin;
> +     int                     rc;
> +
> +     ASSERT_SENSOR(sensor);
> +     if (sensor < 1) {
> +             printf("%s: invalid sensor id %d\n", DEVNAME(sc), sensor);
> +             return (0);
> +     }
> +
> +     memset(&val, 0, sizeof(val));
> +     val.type = AML_OBJTYPE_INTEGER;
> +     val.v_integer = sensor;
> +     val.length = 1;
> +
> +     /* TEMP reports decikelvin, similar to standard ACPI thermal zones. */
> +     rc = aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "TEMP", 1, &val,
> +         &decikelvin);
> +     if (rc) {
> +             printf("%s: failed to read sensor %d\n", DEVNAME(sc), sensor);
> +             return (0);
> +     }
> +
> +     return (decikelvin);
> +}
> +
> +int
> +surfacetz_notify(struct aml_node *node, int notify_type, void *arg)
> +{
> +     struct surfacetz_softc  *sc = arg;
> +     uint8_t                  sensor, celcius;
> +
> +     /* MSHW0189 encodes the sensor index in the notification type. */
> +     if (notify_type & 0xC0) {
> +             sensor = (notify_type & 0x0F) + 1;
> +
> +             /* OK to read via AML here. */
> +             celcius = DK_TO_C(surfacetz_read_sensor(sc, sensor));
> +
> +             DPRINTF("%s: sensor %d tripped at %dC, temp %dC\n", DEVNAME(sc),
> +                 sensor, sc->sc_triplevel[sensor - 1], celcius);
> +             surfacetz_throttle(sc, sensor, celcius);
> +     } else
> +             DPRINTF("%s: unexpected notification type %.2x\n", DEVNAME(sc),
> +                 notify_type);
> +
> +     return (0);
> +}
> +
> +/*
> + * Set a trip point for a given sensor to a value in celcius.
> + */
> +static void
> +surfacetz_set_trip(struct surfacetz_softc *sc, uint8_t sensor, uint8_t 
> celcius)
> +{
> +     struct aml_value        values[2];
> +     int                     rc;
> +
> +     ASSERT_SENSOR(sensor);
> +     memset(&values, 0, sizeof(values));
> +
> +     /* Sensor Index */
> +     values[0].type = AML_OBJTYPE_INTEGER;
> +     values[0].v_integer = sensor;
> +     values[0].length = 1;
> +
> +     /* Trip point (in decikelvin) */
> +     values[1].type = AML_OBJTYPE_INTEGER;
> +     values[1].v_integer = C_TO_DK(celcius);
> +     values[1].length = 1;
> +
> +     /* XXX Lock around sc? */
> +     rc = aml_evalname(sc->sc_acpi, sc->sc_devnode, "TRIP", 2, values, NULL);
> +     if (rc == 0) {
> +             sc->sc_triplevel[sensor - 1] = celcius;
> +             DPRINTF("%s: set trip point for sensor %d to %dC\n",
> +                 DEVNAME(sc), sensor, sc->sc_triplevel[sensor - 1]);
> +     } else
> +             printf("%s: failed to set trip point for %d\n", DEVNAME(sc),
> +                 sensor);
> +}
> +
> +/*
> + * Down throttle the CPU to perform passive cooling.
> + *
> + * May be called from ACPI task threads and timeout threads.
> + */
> +static void
> +surfacetz_throttle(struct surfacetz_softc *sc, uint8_t sensor, uint8_t 
> celcius)
> +{
> +     uint8_t                 trip, nexttrip;
> +     int                     nextperf;
> +
> +     ASSERT_SENSOR(sensor);
> +     trip = sc->sc_triplevel[sensor - 1];
> +
> +     switch (trip) {
> +     case WARM:
> +             nexttrip = HOT;
> +             break;
> +     case HOT:
> +             nexttrip = CRITICAL;
> +             break;
> +     case CRITICAL:
> +             nexttrip = CRITICAL;
> +             if (celcius >= CRITICAL) {
> +                     printf("%s: critical temperature exceeded %dC, shutting"
> +                         " down\n", DEVNAME(sc), celcius);
> +                     prsignal(initprocess, SIGUSR2);
> +                     return;
> +             }
> +             break;
> +     default:
> +             printf("%s: invalid trip state %d\n", DEVNAME(sc), trip);
> +             return;
> +     }
> +
> +     /* We will race here, but max_perflevel won't be set invalidly. */
> +     nextperf = atomic_add_int_nv(&max_perflevel, -PERFSTEP);
> +     if (nextperf < 0)
> +             nextperf = 0;
> +     atomic_swap_uint(&max_perflevel, nextperf);
> +
> +     DPRINTF("%s: sensor %d throttling to %d\n", DEVNAME(sc), sensor,
> +         nextperf);
> +     surfacetz_setperf(desired_perflevel);
> +
> +     /* Move our trip point out a tick. Ok to call directly. */
> +     add_trip_task(sc, sensor, nexttrip);
> +
> +     /* Schedule a cooling timeout. */
> +     if (!timeout_add_sec(&sc->sc_timeout[sensor - 1], RELAX_DELAY))
> +             DPRINTF("%s: failed to add timeout\n", DEVNAME(sc));
> +}
> +
> +/*
> + * Try relaxing our throttled max performance based on if the sensor has 
> cooled.
> + *
> + * Called via a timeout, so cannot access AML directly. All AML-dependent
> + * functions need to be called indirectly via an ACPI task.
> + */

Did you consider using timeout_set_proc(9)?

> +static void
> +surfacetz_relax(void *arg)
> +{
> +     struct surfacetz_arg    *_arg = arg;
> +     struct surfacetz_softc  *sc = _arg->arg_sc;
> +     uint8_t                  celcius, triplevel, sensor = _arg->arg_sensor;
> +     int                      nextperf;
> +
> +     ASSERT_SENSOR(sensor);
> +     DPRINTF("%s: trying to relax sensor %d\n", DEVNAME(sc), sensor);
> +
> +     /* Use our latest sensor reading. */
> +     celcius = UK_TO_C(sc->sc_ksensor[sensor - 1].value);
> +     if (celcius < 1) {
> +             DPRINTF("%s: %s invalid temperature\n", DEVNAME(sc), __func__);
> +             return;
> +     }
> +     triplevel = sc->sc_triplevel[sensor - 1];
> +
> +     /* Are we still above the trip level? We need to throttle more. */
> +     if (celcius >= triplevel) {
> +             DPRINTF("%s: still too warm (%dC)\n", DEVNAME(sc), celcius);
> +             surfacetz_throttle(sc, sensor, celcius);
> +             return;
> +     }
> +
> +     /* We've cooled off */
> +     switch (triplevel) {
> +     case CRITICAL:
> +             if (celcius >= HOT) {
> +                     /* We're still running hot. Throttle more. */
> +                     DPRINTF("%s: sensor %d still hot, throttling\n",
> +                         DEVNAME(sc), sensor);
> +                     surfacetz_throttle(sc, sensor, celcius);
> +             } else if (WARM <= celcius && celcius < HOT) {
> +                     /* Reset trip point and let out some slack. */
> +                     DPRINTF("%s: sensor %d warm, moving a tick\n",
> +                         DEVNAME(sc), sensor);
> +
> +                     nextperf = atomic_add_int_nv(&max_perflevel, PERFSTEP);
> +                     if (nextperf > 100)
> +                             nextperf = 100;
> +                     atomic_swap_uint(&max_perflevel, nextperf);
> +
> +                     surfacetz_setperf(desired_perflevel);
> +                     add_trip_task(sc, sensor, HOT);
> +                     if (!timeout_add_sec(&sc->sc_timeout[sensor - 1],
> +                             RELAX_DELAY))
> +                         DPRINTF("%s: failed to add timeout\n", DEVNAME(sc));
> +             } else {
> +                     /* We've cooled down. Reset perf. */
> +                     DPRINTF("%s: sensor %d cooled\n", DEVNAME(sc), sensor);
> +                     atomic_swap_uint(&max_perflevel, 100);
> +                     surfacetz_setperf(desired_perflevel);
> +                     add_trip_task(sc, sensor, HOT);
> +             }
> +             break;
> +     case HOT:
> +             if (celcius >= WARM) {
> +                     /* Reset trip point and let out some slack. */
> +                     DPRINTF("%s: sensor %d warm, moving a tick\n",
> +                         DEVNAME(sc), sensor);
> +
> +                     nextperf = atomic_add_int_nv(&max_perflevel, PERFSTEP);
> +                     if (nextperf > 100)
> +                             nextperf = 100;
> +                     atomic_swap_uint(&max_perflevel, nextperf);
> +
> +                     surfacetz_setperf(desired_perflevel);
> +                     add_trip_task(sc, sensor, HOT);
> +                     if (!timeout_add_sec(&sc->sc_timeout[sensor - 1],
> +                             RELAX_DELAY))
> +                         DPRINTF("%s: failed to add timeout\n", DEVNAME(sc));
> +             } else {
> +                     /* We've cooled off. */
> +                     DPRINTF("%s: sensor %d cooled\n", DEVNAME(sc), sensor);
> +                     atomic_swap_uint(&max_perflevel, 100);
> +                     surfacetz_setperf(desired_perflevel);
> +             }
> +             break;
> +     default:
> +             DPRINTF("%s: invalid trip point (%d) for %s\n", __func__,
> +                 triplevel, DEVNAME(sc));
> +     }
> +}
> +
> +static void
> +add_sensor_task(void *arg)
> +{
> +     struct surfacetz_softc  *sc = arg;
> +
> +     acpi_addtask(sc->sc_acpi, surfacetz_update_sensors, sc, 0);
> +     acpi_wakeup(sc->sc_acpi);
> +}
> +
> +static void
> +add_trip_task(struct surfacetz_softc *sc, uint8_t sensor, uint8_t triplevel)
> +{
> +     ASSERT_SENSOR(sensor);
> +
> +     acpi_addtask(sc->sc_acpi, surfacetz_update_trip,
> +         &sc->sc_timeout_arg[sensor - 1], triplevel);
> +     acpi_wakeup(sc->sc_acpi);
> +}
> +
> +static void
> +surfacetz_update_trip(void *arg0, int arg1)
> +{
> +     struct surfacetz_arg    *arg = arg0;
> +     struct surfacetz_softc  *sc = arg->arg_sc;
> +     uint8_t                  sensor = arg->arg_sensor, triplevel = arg1;
> +
> +     ASSERT_SENSOR(sensor);
> +     surfacetz_set_trip(sc, sensor, triplevel);
> +}
> +
> +static void
> +surfacetz_update_sensors(void *arg0, int arg1)
> +{
> +     struct surfacetz_softc  *sc = arg0;
> +     int                      i;
> +     uint64_t                 ukelvin;
> +
> +     for (i = 1; i < sc->sc_sensors + 1; i++) {
> +             ukelvin = DK_TO_UK(surfacetz_read_sensor(sc, i));
> +             if (ukelvin > 0) {
> +                     sc->sc_ksensor[i - 1].value = ukelvin;
> +                     sc->sc_ksensor[i - 1].flags &= ~SENSOR_FINVALID;
> +             } else
> +                     sc->sc_ksensor[i - 1].flags |= SENSOR_FINVALID;
> +     }
> +}
> 
> 

Reply via email to