root/sys/dev/apple/smc/smc_sysctl.c
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2026 Abdelkader Boudih <dragonflybsd@seuros.com>
 *
 * Ported from FreeBSD's asmc(4) driver.
 *
 * DragonFlyBSD port: adapted for kmalloc/kfree, lockmgr 2-arg form,
 * taskqueue_start_threads ncpu arg, sys/bus_resource.h, acpica paths.
 * Added MMIO (T2) backend support.
 */

/*
 * Apple SMC — sysctl handlers.
 *
 * All SYSCTL_HANDLER_ARGS callbacks: fan speed, temperature, SMS axes,
 * keyboard backlight, battery charge limit, system state keys, and
 * the optional raw-key debug interface.
 */

#include "smc.h"

#include <machine/specialreg.h>
#include <machine/cpufunc.h>

static int
apple_smc_key_check_writable(device_t dev, const char *key)
{
        uint8_t attrs;

        if (apple_smc_key_getattrs(dev, key, &attrs) == 0 &&
            (attrs & ASMC_ATTR_WRITABLE) == 0) {
                device_printf(dev, "%s: attrs=0x%02x, not writable\n",
                    key, attrs);
                return (EPERM);
        }
        return (0);
}

/*
 * Module-scope cached keyboard backlight level.  Shared across all device
 * instances so suspend/resume can restore the last requested brightness even
 * though the resume path only has a device_t and not sysctl request context.
 */
static unsigned int light_control = 0;

/* Retrieve the cached module-level brightness value for resume restore. */
unsigned int
apple_smc_get_light_control(void)
{
        return (light_control);
}

int
apple_smc_mb_sysctl_fanspeed(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        int fan = arg2;
        int32_t v;

        v = apple_smc_fan_getvalue(dev, ASMC_KEY_FANSPEED, fan);
        return (sysctl_handle_int(oidp, &v, 0, req));
}

int
apple_smc_mb_sysctl_fanid(SYSCTL_HANDLER_ARGS)
{
        uint8_t buf[16];
        device_t dev = (device_t)arg1;
        int fan = arg2;
        char *desc;

        desc = apple_smc_fan_getstring(dev, ASMC_KEY_FANID, fan,
            buf, sizeof(buf));
        if (desc == NULL) {
                return (EIO);
        }
        return (sysctl_handle_string(oidp, desc, 0, req));
}

int
apple_smc_mb_sysctl_fansafespeed(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        int fan = arg2;
        int32_t v;

        v = apple_smc_fan_getvalue(dev, ASMC_KEY_FANSAFESPEED, fan);
        return (sysctl_handle_int(oidp, &v, 0, req));
}

/*
 * Fan speed keys that support both read and write.
 * Index into this table is packed in arg2 bits [15:8].
 */
static const char *apple_smc_fan_rw_keys[] = {
        ASMC_KEY_FANMINSPEED,           /* 0 */
        ASMC_KEY_FANMAXSPEED,           /* 1 */
        ASMC_KEY_FANTARGETSPEED,        /* 2 */
};

int
apple_smc_mb_sysctl_fanrw(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        int key_idx = (arg2 >> 8) & 0xFF;
        int fan = arg2 & 0xFF;
        const char *key;
        int error;
        int32_t v;

        if (key_idx >= (int)nitems(apple_smc_fan_rw_keys))
                return (EINVAL);
        key = apple_smc_fan_rw_keys[key_idx];

        v = apple_smc_fan_getvalue(dev, key, fan);
        error = sysctl_handle_int(oidp, &v, 0, req);
        if (error == 0 && req->newptr != NULL) {
                error = apple_smc_fan_setvalue(dev, key, fan, v);
        }
        return (error);
}

int
apple_smc_mb_sysctl_fanmanual(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int fan = arg2;
        int error;
        int32_t v;
        uint8_t buf[2];
        uint16_t val;
        char fmkey[5];

        ksnprintf(fmkey, sizeof(fmkey), ASMC_KEY_FANMANUAL_T2, fan);
        if (sc->sc_is_t2 &&
            apple_smc_key_getinfo(dev, fmkey, NULL, NULL) == 0) {
                error = apple_smc_key_read(dev, fmkey, buf, 1);
                if (error != 0) {
                        return (error);
                }
                v = buf[0] ? 1 : 0;
                error = sysctl_handle_int(oidp, &v, 0, req);
                if (error == 0 && req->newptr != NULL) {
                        if (v != 0 && v != 1) {
                                return (EINVAL);
                        }
                        buf[0] = (uint8_t)v;
                        error = apple_smc_key_write(dev, fmkey, buf, 1);
                }
                return (error);
        }

        error = apple_smc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf));
        if (error != 0) {
                return (error);
        }

        val = (buf[0] << 8) | buf[1];
        v = (val >> fan) & 0x01;
        error = sysctl_handle_int(oidp, &v, 0, req);
        if (error == 0 && req->newptr != NULL) {
                if (v != 0 && v != 1) {
                        return (EINVAL);
                }
                error = apple_smc_key_read(dev, ASMC_KEY_FANMANUAL,
                    buf, sizeof(buf));
                if (error == 0) {
                        val = (buf[0] << 8) | buf[1];
                        if (v) {
                                val |= (1 << fan);
                        } else {
                                val &= ~(1 << fan);
                        }
                        buf[0] = val >> 8;
                        buf[1] = val & 0xff;
                        error = apple_smc_key_write(dev, ASMC_KEY_FANMANUAL,
                            buf, sizeof(buf));
                }
        }
        return (error);
}

int
apple_smc_temp_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int error;
        int val;

        if (arg2 < 0 || arg2 >= sc->sc_temp_count) {
                return (EINVAL);
        }
        error = apple_smc_sensor_read(dev, sc->sc_temp_sensors[arg2], &val);
        if (error != 0) {
                return (error);
        }
        return (sysctl_handle_int(oidp, &val, 0, req));
}

int
apple_smc_dts_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int error, val;

        if (arg2 < 0 || arg2 >= sc->sc_dts_count)
                return (EINVAL);
        error = apple_smc_sensor_read(dev, sc->sc_dts_sensors[arg2], &val);
        if (error != 0)
                return (error);
        /* Sentinel check: a disconnected key can return 0x8x00 at runtime too. */
        if (val <= -120000)
                return (ENODEV);
        /* DTS value is a negative offset from Tj,max; convert to absolute. */
        val = sc->sc_tj_max * 1000 + val;
        return (sysctl_handle_int(oidp, &val, 0, req));
}

int
apple_smc_sensor_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int error, val;
        int sensor_type = (arg2 >> 8) & 0xFF;
        int sensor_idx  = arg2 & 0xFF;
        const char *key = NULL;

        switch (sensor_type) {
        case 'V':
                if (sensor_idx < sc->sc_voltage_count) {
                        key = sc->sc_voltage_sensors[sensor_idx];
                }
                break;
        case 'I':
                if (sensor_idx < sc->sc_current_count) {
                        key = sc->sc_current_sensors[sensor_idx];
                }
                break;
        case 'P':
                if (sensor_idx < sc->sc_power_count) {
                        key = sc->sc_power_sensors[sensor_idx];
                }
                break;
        case 'L':
                if (sensor_idx < sc->sc_light_count) {
                        key = sc->sc_light_sensors[sensor_idx];
                }
                break;
        default:
                return (EINVAL);
        }
        if (key == NULL) {
                return (ENOENT);
        }

        error = apple_smc_sensor_read(dev, key, &val);
        if (error != 0) {
                return (error);
        }
        return (sysctl_handle_int(oidp, &val, 0, req));
}

static int
apple_smc_sms_axis(device_t dev, const char *key,
    struct sysctl_oid *oidp, struct sysctl_req *req)
{
        int16_t val;
        int32_t v;
        int error;

        error = apple_smc_sms_read(dev, key, &val);
        if (error != 0) {
                return (error);
        }
        v = (int32_t)val;
        return (sysctl_handle_int(oidp, &v, 0, req));
}

static const char *apple_smc_sms_keys[] = {
        ASMC_KEY_SMS_X,         /* 0 */
        ASMC_KEY_SMS_Y,         /* 1 */
        ASMC_KEY_SMS_Z,         /* 2 */
};

int
apple_smc_mb_sysctl_sms(SYSCTL_HANDLER_ARGS)
{
        if (arg2 < 0 || arg2 >= (int)nitems(apple_smc_sms_keys))
                return (EINVAL);
        return (apple_smc_sms_axis((device_t)arg1, apple_smc_sms_keys[arg2],
            oidp, req));
}

static int
apple_smc_light_sensor(device_t dev, const char *key,
    struct sysctl_oid *oidp, struct sysctl_req *req)
{
        uint8_t buf[6];
        int error;
        int32_t v;

        error = apple_smc_key_read(dev, key, buf, sizeof(buf));
        if (error != 0) {
                return (error);
        }
        v = buf[2];
        return (sysctl_handle_int(oidp, &v, 0, req));
}

static const char *apple_smc_light_keys[] = {
        ASMC_KEY_LIGHTLEFT,     /* 0 */
        ASMC_KEY_LIGHTRIGHT,    /* 1 */
};

int
apple_smc_mbp_sysctl_light(SYSCTL_HANDLER_ARGS)
{
        if (arg2 < 0 || arg2 >= (int)nitems(apple_smc_light_keys))
                return (EINVAL);
        return (apple_smc_light_sensor((device_t)arg1,
            apple_smc_light_keys[arg2], oidp, req));
}

int
apple_smc_mbp_sysctl_light_left_10byte(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[10];
        uint32_t v;
        int error;

        error = apple_smc_key_read(dev, ASMC_KEY_LIGHTLEFT, buf, sizeof(buf));
        if (error != 0) {
                return (error);
        }
        v = be32dec(&buf[6]);
        v = v >> 8;
        if (v > 255) {
                v = 255;
        }
        return (sysctl_handle_int(oidp, &v, 0, req));
}

int
apple_smc_mbp_sysctl_light_control(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        uint8_t buf[2];
        int error, v;

        v = light_control;
        error = sysctl_handle_int(oidp, &v, 0, req);
        if (error == 0 && req->newptr != NULL) {
                if (v < 0 || v > 255) {
                        return (EINVAL);
                }
                light_control = v;
                sc->sc_kbd_bkl_level = v * 100 / 255;
                buf[0] = light_control;
                buf[1] = 0x00;
                error = apple_smc_key_write(dev, ASMC_KEY_LIGHTVALUE, buf,
                    sizeof(buf));
        }
        return (error);
}

/*
 * Generic RW sysctl for single-byte flag keys (0 or 1).
 * arg1 = device_t, arg2 = pointer to 4-char SMC key name.
 */
int
apple_smc_flag_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        const char *key = (const char *)(intptr_t)arg2;
        uint8_t raw;
        int val, error;

        if (apple_smc_key_read(dev, key, &raw, 1) != 0)
                return (EIO);
        val = (raw != 0) ? 1 : 0;
        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error != 0 || req->newptr == NULL)
                return (error);
        if (val != 0 && val != 1)
                return (EINVAL);
        raw = (uint8_t)val;
        return (apple_smc_key_write(dev, key, &raw, 1) != 0 ? EIO : 0);
}

int
apple_smc_bclm_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t bclm;
        int val, error;

        if (apple_smc_key_read(dev, ASMC_KEY_BCLM, &bclm, 1) != 0) {
                return (EIO);
        }
        val = (int)bclm;
        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error != 0 || req->newptr == NULL) {
                return (error);
        }
        if (val < 0 || val > 100) {
                return (EINVAL);
        }
        bclm = (uint8_t)val;
        return (apple_smc_key_write(dev, ASMC_KEY_BCLM, &bclm, 1) != 0 ?
            EIO : 0);
}

static const char *
apple_smc_cause_str(int8_t cause)
{
        switch (cause) {
        case -128: return "power-loss";
        case -127: return "software-powerdown";
        case -125: return "thermtrip";
        case -50:  return "overtemp-shutdown";
        case -20:  return "watchdog";
        case -15:  return "battery-low";
        case 0:    return "unknown";
        case 1:    return "overtemp-sleep";
        case 3:    return "power-button";
        case 5:    return "good-shutdown";
        default:   return NULL;
        }
}

static const char *apple_smc_cause_keys[] = {
        ASMC_KEY_MSSD,          /* 0 */
        ASMC_KEY_MSSP,          /* 1 */
};

int
apple_smc_cause_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        const char *key, *desc;
        int8_t cause;
        char buf[48];

        if (arg2 < 0 || arg2 >= (int)nitems(apple_smc_cause_keys))
                return (EINVAL);
        key = apple_smc_cause_keys[arg2];

        if (apple_smc_key_read(dev, key, (uint8_t *)&cause, 1) != 0)
                return (EIO);
        desc = apple_smc_cause_str(cause);
        if (desc)
                ksnprintf(buf, sizeof(buf), "%d (%s)", (int)cause, desc);
        else
                ksnprintf(buf, sizeof(buf), "%d", (int)cause);
        return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
}

int
apple_smc_msal_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t msal; char buf[80];

        if (apple_smc_key_read(dev, ASMC_KEY_MSAL, &msal, 1) != 0) {
                return (EIO);
        }
        /*
         * Bits 0x04, 0x10 and 0x20 are present in observed hardware values but
         * are not documented here yet, so report the raw byte and decode only the
         * known flags.
         */
        ksnprintf(buf, sizeof(buf),
            "0x%02x (tss=%d therm_valid=%d calib_valid=%d prochot=%d plimits=%d)",
            msal,
            (msal & 0x01) != 0, (msal & 0x02) != 0,
            (msal & 0x08) != 0, (msal & 0x40) != 0, (msal & 0x80) != 0);
        return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
}

int
apple_smc_clkt_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[4]; uint32_t secs;

        if (apple_smc_key_read(dev, ASMC_KEY_CLKT, buf, 4) != 0) {
                return (EIO);
        }
        secs = be32dec(buf);
        return (sysctl_handle_32(oidp, &secs, 0, req));
}

int
apple_smc_clwk_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[2];
        uint32_t secs;
        int error;

        if (apple_smc_key_read(dev, ASMC_KEY_CLWK, buf, 2) != 0)
                return (EIO);

        /* CLWK is ui16 BE; 0 = disabled. */
        secs = be16dec(buf);
        error = sysctl_handle_32(oidp, &secs, 0, req);
        if (error != 0 || req->newptr == NULL)
                return (error);

        if (secs >= 86400)
                return (EINVAL);

        error = apple_smc_key_check_writable(dev, ASMC_KEY_CLWK);
        if (error != 0)
                return (error);

        be16enc(buf, (uint16_t)secs);
        if (apple_smc_key_write(dev, ASMC_KEY_CLWK, buf, 2) != 0)
                return (EIO);

        /* Verify write; retry as LE if readback mismatches. */
        if (apple_smc_key_read(dev, ASMC_KEY_CLWK, buf, 2) == 0) {
                uint16_t readback = be16dec(buf);
                if (readback != (uint16_t)secs) {
                        le16enc(buf, (uint16_t)secs);
                        return (apple_smc_key_write(dev, ASMC_KEY_CLWK,
                            buf, 2) != 0 ? EIO : 0);
                }
        }
        return (0);
}

int
apple_smc_msps_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[2]; uint32_t state;

        if (apple_smc_key_read(dev, ASMC_KEY_MSPS, buf, 2) != 0) {
                return (EIO);
        }
        state = ((uint32_t)buf[0] << 8) | buf[1];
        return (sysctl_handle_32(oidp, &state, 0, req));
}

int
apple_smc_rplt_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[9]; char name[9];

        memset(buf, 0, sizeof(buf));
        if (apple_smc_key_read(dev, ASMC_KEY_RPLT, buf, 8) != 0) {
                return (EIO);
        }
        memcpy(name, buf, 8);
        name[8] = '\0';
        return (sysctl_handle_string(oidp, name, sizeof(name), req));
}

int
apple_smc_rgen_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t gen; uint32_t val;

        if (apple_smc_key_read(dev, ASMC_KEY_RGEN, &gen, 1) != 0) {
                return (EIO);
        }
        val = gen;
        return (sysctl_handle_32(oidp, &val, 0, req));
}

int
apple_smc_raw_key_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        char newkey[ASMC_KEYLEN + 1];
        uint8_t keylen;
        int error;

        strlcpy(newkey, sc->sc_rawkey, sizeof(newkey));
        error = sysctl_handle_string(oidp, newkey, sizeof(newkey), req);
        if (error || req->newptr == NULL) {
                return (error);
        }
        if (strlen(newkey) != ASMC_KEYLEN) {
                return (EINVAL);
        }
        if (apple_smc_key_getinfo(dev, newkey, &keylen, sc->sc_rawtype) != 0) {
                return (ENOENT);
        }
        if (keylen > ASMC_MAXVAL) {
                keylen = ASMC_MAXVAL;
        }
        strlcpy(sc->sc_rawkey, newkey, sizeof(sc->sc_rawkey));
        sc->sc_rawlen = keylen;
        memset(sc->sc_rawval, 0, sizeof(sc->sc_rawval));
        /* Read may fail; allow setting key for subsequent write. */
        apple_smc_key_read(dev, sc->sc_rawkey, sc->sc_rawval,
            sc->sc_rawlen);
        return (0);
}

int
apple_smc_raw_value_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        char hexbuf[ASMC_MAXVAL * 2 + 1];
        int error, i;

        if (sc->sc_rawkey[0] != '\0') {
                error = apple_smc_key_read(dev, sc->sc_rawkey, sc->sc_rawval,
                    sc->sc_rawlen > 0 ? sc->sc_rawlen : ASMC_MAXVAL);
                if (error != 0) {
                        return (error);
                }
        }

        for (i = 0; i < sc->sc_rawlen && i < ASMC_MAXVAL; i++) {
                ksnprintf(hexbuf + i * 2, 3, "%02x", sc->sc_rawval[i]);
        }
        hexbuf[i * 2] = '\0';

        error = sysctl_handle_string(oidp, hexbuf, sizeof(hexbuf), req);
        if (error || req->newptr == NULL) {
                return (error);
        }
        if (sc->sc_rawkey[0] == '\0') {
                return (EINVAL);
        }

        memset(sc->sc_rawval, 0, sizeof(sc->sc_rawval));
        for (i = 0; i < sc->sc_rawlen && hexbuf[i*2] && hexbuf[i*2+1]; i++) {
                unsigned int val;
                char tmp[3] = { hexbuf[i*2], hexbuf[i*2+1], 0 };

                if (ksscanf(tmp, "%02x", &val) == 1) {
                        sc->sc_rawval[i] = (uint8_t)val;
                }
        }
        return (apple_smc_key_write(dev, sc->sc_rawkey,
            sc->sc_rawval, sc->sc_rawlen) != 0 ? EIO : 0);
}

int
apple_smc_raw_len_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int v = sc->sc_rawlen;
        return (sysctl_handle_int(oidp, &v, 0, req));
}

int
apple_smc_raw_type_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        return (sysctl_handle_string(oidp, sc->sc_rawtype,
            sizeof(sc->sc_rawtype), req));
}

int
apple_smc_raw_index_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int idx = -1, error;
        char keyname[ASMC_KEYLEN + 1];
        uint8_t keylen;

        error = sysctl_handle_int(oidp, &idx, 0, req);
        if (error || req->newptr == NULL)
                return (error);
        if (idx < 0 || idx >= sc->sc_nkeys)
                return (EINVAL);

        error = apple_smc_key_dump_by_index(dev, idx, keyname,
            sc->sc_rawtype, &keylen);
        if (error != 0)
                return (EIO);
        strlcpy(sc->sc_rawkey, keyname, sizeof(sc->sc_rawkey));
        sc->sc_rawlen = keylen;
        memset(sc->sc_rawval, 0, sizeof(sc->sc_rawval));
        apple_smc_key_read(dev, sc->sc_rawkey, sc->sc_rawval, sc->sc_rawlen);
        return (0);
}

/* G3AO sysctl handler. */
int
apple_smc_g3ao_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[4];
        uint32_t ts;
        int error;

        if (apple_smc_key_read(dev, ASMC_KEY_G3AO, buf, 4) != 0)
                return (EIO);
        ts = be32dec(buf);
        error = sysctl_handle_32(oidp, &ts, 0, req);
        if (error != 0 || req->newptr == NULL)
                return (error);

        error = apple_smc_key_check_writable(dev, ASMC_KEY_G3AO);
        if (error != 0)
                return (error);

        be32enc(buf, ts);
        return (apple_smc_key_write(dev, ASMC_KEY_G3AO, buf, 4) != 0 ?
            EIO : 0);
}


/* AUWT sysctl handler. */
int
apple_smc_auwt_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[2];
        uint32_t secs;
        int error;

        if (apple_smc_key_read(dev, ASMC_KEY_AUWT, buf, 2) != 0)
                return (EIO);
        secs = be16dec(buf);
        error = sysctl_handle_32(oidp, &secs, 0, req);
        if (error != 0 || req->newptr == NULL)
                return (error);
        if (secs > 65535)
                return (EINVAL);
        be16enc(buf, (uint16_t)secs);
        return (apple_smc_key_write(dev, ASMC_KEY_AUWT, buf, 2) != 0 ?
            EIO : 0);
}

/* DPBR sysctl handler. */
int
apple_smc_dpbr_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t buf[2];
        uint32_t val;

        if (apple_smc_key_read(dev, ASMC_KEY_DPBR, buf, 2) != 0)
                return (EIO);
        val = be16dec(buf);
        return (sysctl_handle_32(oidp, &val, 0, req));
}

/* ENV0 sysctl handler. */
int
apple_smc_env0_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        uint8_t val;
        uint32_t v;

        if (apple_smc_key_read(dev, ASMC_KEY_ENV0, &val, 1) != 0)
                return (EIO);
        v = val;
        return (sysctl_handle_32(oidp, &v, 0, req));
}


int
apple_smc_prochot_override_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = (device_t)arg1;
        struct apple_smc_softc *sc = device_get_softc(dev);
        int val, error;
        uint64_t msr;

        val = sc->sc_prochot_override;
        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error || req->newptr == NULL)
                return (error);

        if (val != 0 && val != 1)
                return (EINVAL);
        if (val == sc->sc_prochot_override)
                return (0);

        if (rdmsr_safe(MSR_IA32_POWER_CTL, &msr) != 0)
                return (EIO);

        if (val)
                msr &= ~(uint64_t)IA32_POWER_CTL_BD_PROCHOT;
        else
                msr |= IA32_POWER_CTL_BD_PROCHOT;

        if (wrmsr_safe(MSR_IA32_POWER_CTL, msr) != 0)
                return (EIO);

        if (!val) {
                if (sc->sc_thermal_running) {
                        sc->sc_thermal_enabled = 0;
                        apple_smc_thermal_fans_to_max(dev);
                }
                apple_smc_fan_release_manual(dev);
        }

        sc->sc_prochot_override = val;
        return (0);
}