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.

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)?
o Error handling in _attach...if we can't make a task, do we leave the
  device in a borked state?

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.

-dv

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);
+
        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)))
+               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.
+ */
+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