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;
+ }
+}