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