Hi,

I recently decided to replace MacOSX on my iMac11,3 27" with OpenBSD.
During the MacOSX times I had to replace the broken HDD with a new SSD.
The new SSD didn't offer pins to attach the sensor cable back, which
was previously attached to the HDD, so I left it loose.  This caused
the HDD fan to spin up to maximum over time.  On MacOSX there are some
fan control programs with which I could control the fan speed.  I
almost forgot about that "fix" over time, but installing OpenBSD on
that machine remembered me of it - You can't overhear it :-)

That made me play around with asmc(4), where I noticed you can not
only read the fan properties, but also set them.  I initially was
thinking to make sysctl(8) able to set fan speeds, but what I could
see is that the fan framework there seems to be designed to read
only.  Instead of poking around further in sysctl(8), I had the idea
to create a small fan(4) framework layer, which can be controlled
through a device by using a fanctl(8) user-land program.

I don't know if there are other fan sensor controllers which would
allow fan properties control.  If yes, they potentially could attach
to the same fan framework.  If not, the fan framework probably doesn't
make much sense only to be used by asmc(4).

Some example:

        ...
        asmc0 at acpi0: SMC_ (smc-piketon) addr 0x300/0x20: rev 1.59f559, 297
        keys
        fan0 at asmc0
        ...

        bigmac# sysctl | grep fan
        hw.sensors.asmc0.fan0=998 RPM (ODD, right mid rear)
        hw.sensors.asmc0.fan1=3677 RPM (HDD, center mid rear)
        hw.sensors.asmc0.fan2=939 RPM (CPU, left lower rear)

        bigmac# ./fanctl
        driver=asmc0
        fan0.id=ODD, right mid rear
        fan0.act=999 RPM
        fan0.min=1000 RPM
        fan0.max=3800 RPM
        fan0.saf=0 RPM
        fan0.tgt=1000 RPM
        fan1.id=HDD, center mid rear
        fan1.act=3693 RPM
        fan1.min=1100 RPM
        fan1.max=5500 RPM
        fan1.saf=0 RPM
        fan1.tgt=3700 RPM
        fan2.id=CPU, left lower rear
        fan2.act=939 RPM
        fan2.min=940 RPM
        fan2.max=2100 RPM
        fan2.saf=0 RPM
        fan2.tgt=940 RPM

        bigmac# ./fanctl fan1.act
        fan1.act=3729
        bigmac# ./fanctl fan1.max 
        fan1.max=5500

        bigmac# ./fanctl fan1.max=1000
        fan1.max: 5500 -> 1000

        *silence* :-)

        bigmac# ./fanctl fan1.act 
        fan1.act=1002

Attached a very initial coding, but basically works for me.

Maybe the idea is totally stupid, and the use cases are very
rare.  I don't know.  Therefore I would appreciate some feedback.

Thanks,
Marcus


Index: etc/MAKEDEV.common
===================================================================
RCS file: /cvs/src/etc/MAKEDEV.common,v
retrieving revision 1.111
diff -u -p -u -p -r1.111 MAKEDEV.common
--- etc/MAKEDEV.common  6 Jul 2020 06:11:26 -0000       1.111
+++ etc/MAKEDEV.common  27 Nov 2020 16:13:39 -0000
@@ -170,6 +170,7 @@ target(all, bpf)dnl
 target(all, kcov)dnl
 target(all, dt)dnl
 target(all, kstat)dnl
+target(all, fan, 0)dnl
 dnl
 _mkdev(all, {-all-}, {-dnl
 show_target(all)dnl
@@ -535,3 +536,6 @@ __devitem(dt, dt, Dynamic Tracer)dnl
 _mkdev(dt, dt, {-M dt c major_dt_c 0 600-})dnl
 __devitem(kstat, kstat, Kernel Statistics)dnl
 _mkdev(kstat, kstat, {-M kstat c major_kstat_c 0 640-})dnl
+__devitem(fan, fan*, Fan Management Framework)dnl
+_mkdev(fan, fan*, {-M fan$U c major_fan_c $U 600
+       MKlist[${#MKlist[*]}]=";[ -e fan ] || ln -s fan$U fan"-})dnl
Index: etc/etc.amd64/MAKEDEV.md
===================================================================
RCS file: /cvs/src/etc/etc.amd64/MAKEDEV.md,v
retrieving revision 1.76
diff -u -p -u -p -r1.76 MAKEDEV.md
--- etc/etc.amd64/MAKEDEV.md    6 Jul 2020 06:11:26 -0000       1.76
+++ etc/etc.amd64/MAKEDEV.md    27 Nov 2020 16:13:39 -0000
@@ -98,6 +98,7 @@ _DEV(vscsi, 89)
 _DEV(pvbus, 95)
 _DEV(switch, 97)
 _DEV(kstat, 51)
+_DEV(fan, 48)
 dnl
 divert(__mddivert)dnl
 dnl
Index: sys/arch/amd64/amd64/conf.c
===================================================================
RCS file: /cvs/src/sys/arch/amd64/amd64/conf.c,v
retrieving revision 1.72
diff -u -p -u -p -r1.72 conf.c
--- sys/arch/amd64/amd64/conf.c 7 Oct 2020 13:37:33 -0000       1.72
+++ sys/arch/amd64/amd64/conf.c 27 Nov 2020 16:13:53 -0000
@@ -173,6 +173,7 @@ cdev_decl(pci);
 #include "pvbus.h"
 #include "ipmi.h"
 #include "switch.h"
+#include "fan.h"
 
 struct cdevsw  cdevsw[] =
 {
@@ -229,7 +230,7 @@ struct cdevsw       cdevsw[] =
        cdev_random_init(1,random),     /* 45: random data source */
        cdev_ocis_init(NPCTR,pctr),     /* 46: performance counters */
        cdev_disk_init(NRD,rd),         /* 47: ram disk driver */
-       cdev_notdef(),                  /* 48 */
+       cdev_fan_init(NFAN,fan),        /* 48: Fan Management */
        cdev_bktr_init(NBKTR,bktr),     /* 49: Bt848 video capture device */
        cdev_ksyms_init(NKSYMS,ksyms),  /* 50: Kernel symbols device */
        cdev_kstat_init(NKSTAT,kstat),  /* 51: Kernel statistics */
Index: sys/arch/amd64/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/amd64/conf/GENERIC,v
retrieving revision 1.495
diff -u -p -u -p -r1.495 GENERIC
--- sys/arch/amd64/conf/GENERIC 15 Nov 2020 16:47:12 -0000      1.495
+++ sys/arch/amd64/conf/GENERIC 27 Nov 2020 16:13:53 -0000
@@ -72,6 +72,7 @@ acpicbkbd*    at acpi?
 acpials*       at acpi?
 abl*           at acpi?        # Apple Backlight
 asmc*          at acpi?        # Apple SMC
+fan*           at asmc?        # Fan Management Framework
 tpm*           at acpi?
 acpihve*       at acpi?
 acpisurface*   at acpi?
Index: sys/conf/files
===================================================================
RCS file: /cvs/src/sys/conf/files,v
retrieving revision 1.692
diff -u -p -u -p -r1.692 files
--- sys/conf/files      17 Nov 2020 14:30:13 -0000      1.692
+++ sys/conf/files      27 Nov 2020 16:13:54 -0000
@@ -21,6 +21,7 @@ define        gpiobus {}
 define onewirebus {}
 define video {}
 define intrmap {}
+define fan {}
 
 # filesystem firmware loading attribute
 define firmload
@@ -34,6 +35,9 @@ define pt2254a
 device video
 attach video at video
 
+device fan
+attach fan at fan
+
 # audio and midi devices, attaches to audio hardware driver
 device audio
 attach audio at audio
@@ -664,6 +668,7 @@ file dev/mulaw.c                    audio                   
needs-flag
 file dev/vnd.c                         vnd                     needs-flag
 file dev/rnd.c
 file dev/video.c                       video                   needs-flag
+file dev/fan.c                         fan                     needs-flag
 file isofs/cd9660/cd9660_bmap.c                cd9660
 file isofs/cd9660/cd9660_lookup.c      cd9660
 file isofs/cd9660/cd9660_node.c                cd9660
Index: sys/dev/fan.c
===================================================================
RCS file: sys/dev/fan.c
diff -N sys/dev/fan.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/dev/fan.c       27 Nov 2020 16:13:54 -0000
@@ -0,0 +1,229 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Marcus Glocker <mgloc...@openbsd.org>
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <sys/poll.h>
+#include <sys/device.h>
+#include <sys/vnode.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/fanio.h>
+#include <dev/fan_if.h>
+
+struct fan_softc {
+       struct device            dev;
+       void                    *hw_hdl;
+       struct fan_hw_if        *hw_if;         /* driver functions */
+#define FAN_OPEN 0x1
+       uint8_t                  sc_open;
+};
+
+int    fan_match(struct device *, void *, void *);
+void   fan_attach(struct device*, struct device *, void *);
+int    fan_detach(struct device *, int);
+int    fan_activate(struct device *, int);
+int    fan_print(void *, const char *);
+
+const struct cfattach fan_ca = {
+       sizeof(struct fan_softc), fan_match, fan_attach, fan_detach,
+       fan_activate
+};
+
+struct cfdriver fan_cd = {
+       NULL, "fan", DV_DULL
+};
+
+int
+fan_match(struct device *parent, void *match, void *aux)
+{
+       return 1;
+}
+
+void
+fan_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct fan_softc *sc = (void *)self;
+       struct fan_attach_args *sa = aux;
+
+       printf("\n");
+       sc->hw_if = sa->hwif;
+       sc->hw_hdl = sa->hdl;
+}
+
+int
+fan_detach(struct device *self, int flags)
+{
+       return 0;
+}
+
+int
+fan_activate(struct device *self, int act)
+{
+        //struct fan_softc *sc = (struct fan_softc *)self;
+
+        return 0;
+}
+
+int
+fan_submatch(struct device *parent, void *match, void *aux)
+{
+       struct cfdata *cf = match;
+
+       return (cf->cf_driver == &fan_cd);
+}
+
+struct device *
+fan_attach_mi(struct fan_hw_if *rhwp, void *hdlp, struct device *dev)
+{
+       struct fan_attach_args arg;
+
+       arg.hwif = rhwp;
+       arg.hdl = hdlp;
+       return config_found_sm(dev, &arg, fan_print, fan_submatch);
+}
+
+int
+fan_print(void *aux, const char *pnp)
+{
+       if (pnp != NULL)
+               printf("fan at %s", pnp);
+
+       return UNCONF;
+}
+
+int
+fanopen(dev_t dev, int flags, int fmt, struct proc *p)
+{
+       struct fan_softc *sc;
+       int unit;
+
+       printf("fanopen\n");
+
+       unit = FAN_UNIT(dev);
+       sc = fan_cd.cd_devs[unit];
+       if (unit >= fan_cd.cd_ndevs || sc == NULL || sc->hw_if == NULL)
+               return (ENXIO);
+
+       if (sc->sc_open & FAN_OPEN)
+               return EBUSY;
+
+       sc->sc_open |= FAN_OPEN;
+
+       if (sc->hw_if->open != NULL)
+               return (sc->hw_if->open(sc->hw_hdl));
+
+       return 0;
+}
+
+int
+fanclose(dev_t dev, int flags, int fmt, struct proc *p)
+{
+       struct fan_softc *sc;
+       int unit, r;
+
+       printf("fanclose\n");
+
+       unit = FAN_UNIT(dev);
+       sc = fan_cd.cd_devs[unit];
+       if (sc == NULL)
+               return ENXIO;
+
+       if (sc->hw_if->close != NULL)
+               r = sc->hw_if->close(sc->hw_hdl);
+
+       sc->sc_open &= ~FAN_OPEN;
+
+       return r;
+}
+
+int
+fanioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p)
+{
+       struct fan_softc *sc;
+       int unit, error;
+
+       printf("fanioctl\n");
+
+       unit = FAN_UNIT(dev);
+       sc = fan_cd.cd_devs[unit];
+       if (sc == NULL)
+               return ENXIO;
+
+       error = EOPNOTSUPP;
+       switch (cmd) {
+       case FANIOC_QUERY_DRV:
+               if (sc->hw_if->query_drv)
+                       error = sc->hw_if->query_drv(sc->hw_hdl,
+                           (struct fan_query_drv *)data);
+               break;
+       case FANIOC_QUERY_FAN:
+               if (sc->hw_if->query_fan)
+                       error = sc->hw_if->query_fan(sc->hw_hdl,
+                           (struct fan_query_fan *)data);
+               break;
+       case FANIOC_G_ACT:
+               if (sc->hw_if->g_act)
+                       error = sc->hw_if->g_act(sc->hw_hdl,
+                           (struct fan_g_act *)data);
+               break;
+       case FANIOC_G_MIN:
+               if (sc->hw_if->g_min)
+                       error = sc->hw_if->g_min(sc->hw_hdl,
+                           (struct fan_g_min *)data);
+               break;
+       case FANIOC_G_MAX:
+               if (sc->hw_if->g_max)
+                       error = sc->hw_if->g_max(sc->hw_hdl,
+                           (struct fan_g_max *)data);
+               break;
+       case FANIOC_G_SAF:
+               if (sc->hw_if->g_saf)
+                       error = sc->hw_if->g_saf(sc->hw_hdl,
+                           (struct fan_g_saf *)data);
+               break;
+       case FANIOC_G_TGT:
+               if (sc->hw_if->g_tgt)
+                       error = sc->hw_if->g_tgt(sc->hw_hdl,
+                           (struct fan_g_tgt *)data);
+               break;
+       case FANIOC_S_MIN:
+               if (sc->hw_if->s_min)
+                       error = sc->hw_if->s_min(sc->hw_hdl,
+                           (struct fan_s_min *)data);
+               break;
+       case FANIOC_S_MAX:
+               if (sc->hw_if->s_max)
+                       error = sc->hw_if->s_max(sc->hw_hdl,
+                           (struct fan_s_max *)data);
+               break;
+       case FANIOC_S_TGT:
+               if (sc->hw_if->s_tgt)
+                       error = sc->hw_if->s_tgt(sc->hw_hdl,
+                           (struct fan_s_tgt *)data);
+               break;
+       default:
+               error = ENOTTY;
+       }
+
+       return error;
+}
Index: sys/dev/fan_if.h
===================================================================
RCS file: sys/dev/fan_if.h
diff -N sys/dev/fan_if.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/dev/fan_if.h    27 Nov 2020 16:13:54 -0000
@@ -0,0 +1,51 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Marcus Glocker <mgloc...@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _SYS_DEV_FAN_IF_H
+#define _SYS_DEV_FAN_IF_H
+
+#define FAN_UNIT(x) (minor(x))
+
+struct fan_hw_if {
+       /* open */
+       int (*open)(void *);
+
+       /* close */
+       int (*close)(void *);
+
+       /* ioctl */
+       int (*query_drv)(void *, struct fan_query_drv *);
+       int (*query_fan)(void *, struct fan_query_fan *);
+       int (*g_act)(void *, struct fan_g_act *);
+       int (*g_min)(void *, struct fan_g_min *);
+       int (*g_max)(void *, struct fan_g_max *);
+       int (*g_saf)(void *, struct fan_g_saf *);
+       int (*g_tgt)(void *, struct fan_g_tgt *);
+       int (*s_min)(void *, struct fan_s_min *);
+       int (*s_max)(void *, struct fan_s_max *);
+       int (*s_tgt)(void *, struct fan_s_tgt *);
+};
+
+struct fan_attach_args {
+       void *hwif;
+       void *hdl;
+};
+
+struct device *fan_attach_mi(struct fan_hw_if *, void *, struct device *);
+
+#endif /* _SYS_DEV_FAN_IF_H */
Index: sys/dev/acpi/asmc.c
===================================================================
RCS file: /cvs/src/sys/dev/acpi/asmc.c,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 asmc.c
--- sys/dev/acpi/asmc.c 13 Sep 2020 14:11:28 -0000      1.2
+++ sys/dev/acpi/asmc.c 27 Nov 2020 16:13:54 -0000
@@ -26,6 +26,7 @@
 #include <sys/rwlock.h>
 #include <sys/task.h>
 #include <sys/sensors.h>
+#include <sys/fanio.h>
 
 #include <machine/bus.h>
 
@@ -35,6 +36,7 @@
 #include <dev/acpi/dsdt.h>
 
 #include <dev/wscons/wsconsio.h>
+#include <dev/fan_if.h>
 
 #define ASMC_DATA      0x00    /* SMC data port offset */
 #define ASMC_COMMAND   0x04    /* SMC command port offset */
@@ -66,6 +68,7 @@ struct asmc_prod {
 
 struct asmc_softc {
        struct device            sc_dev;
+       struct device           *sc_fandev;
 
        struct acpi_softc       *sc_acpi;
        struct aml_node         *sc_devnode;
@@ -105,6 +108,19 @@ int        asmc_set_backlight(struct wskbd_back
 extern int (*wskbd_get_backlight)(struct wskbd_backlight *);
 extern int (*wskbd_set_backlight)(struct wskbd_backlight *);
 
+int    asmc_open(void *);
+int    asmc_close(void *);
+int    asmc_query_drv(void *, struct fan_query_drv *);
+int    asmc_query_fan(void *, struct fan_query_fan *);
+int    asmc_g_act(void *, struct fan_g_act *);
+int    asmc_g_min(void *, struct fan_g_min *);
+int    asmc_g_max(void *, struct fan_g_max *);
+int    asmc_g_saf(void *, struct fan_g_saf *);
+int    asmc_g_tgt(void *, struct fan_g_tgt *);
+int    asmc_s_min(void *, struct fan_s_min *);
+int    asmc_s_max(void *, struct fan_s_max *);
+int    asmc_s_tgt(void *, struct fan_s_tgt *);
+
 const struct cfattach asmc_ca = {
        sizeof(struct asmc_softc), asmc_match, asmc_attach, NULL, asmc_activate
 };
@@ -113,6 +129,21 @@ struct cfdriver asmc_cd = {
        NULL, "asmc", DV_DULL
 };
 
+struct fan_hw_if asmc_hw_if = {
+       asmc_open,
+       asmc_close,
+       asmc_query_drv,
+       asmc_query_fan,
+       asmc_g_act,
+       asmc_g_min,
+       asmc_g_max,
+       asmc_g_saf,
+       asmc_g_tgt,
+       asmc_s_min,
+       asmc_s_max,
+       asmc_s_tgt
+};
+
 const char *asmc_hids[] = {
        "APP0001", NULL
 };
@@ -348,6 +379,8 @@ asmc_attach(struct device *parent, struc
                return;
        }
        sensordev_install(&sc->sc_sensor_dev);
+
+       sc->sc_fandev = fan_attach_mi(&asmc_hw_if, sc, &sc->sc_dev);
 }
 
 int
@@ -738,4 +771,223 @@ asmc_update(void *arg)
                if (!(sc->sc_sensor_motion[i].flags & SENSOR_FINVALID))
                        asmc_motion(sc, i, 0);
 #endif
+}
+
+/*
+ * ASMC_KEY_FANCOUNT           "FNum"  RO; 1 byte
+ * ASMC_KEY_FANMANUAL          "FS! "  RW; 2 bytes
+ * ASMC_KEY_FANID              "F%dID" RO; 16 bytes
+ * ASMC_KEY_FANSPEED           "F%dAc" RO; 2 bytes
+ * ASMC_KEY_FANMINSPEED                "F%dMn" RO; 2 bytes
+ * ASMC_KEY_FANMAXSPEED                "F%dMx" RO; 2 bytes
+ * ASMC_KEY_FANSAFESPEED       "F%dSf" RO; 2 bytes
+ * ASMC_KEY_FANTARGETSPEED     "F%dTg" RW; 2 bytes
+ */
+int
+asmc_open(void *addr)
+{
+       struct asmc_softc *sc = addr;
+
+       printf("asmc_open\n");
+
+       if (sc->sc_nfans <= 0)
+               return ENOTSUP;
+
+        return 0;
+}
+
+int
+asmc_close(void *addr)
+{
+       printf("asmc_close\n");
+
+       return 0;
+}
+
+int
+asmc_query_drv(void *v, struct fan_query_drv *qd)
+{
+       struct asmc_softc *sc = v;
+
+       printf("asmc_nfans\n");
+
+       strlcpy(qd->id, sc->sc_dev.dv_xname, sizeof(qd->id));
+       qd->nfans = sc->sc_nfans;
+
+       return 0;
+}
+
+int
+asmc_query_fan(void *v, struct fan_query_fan *qfan)
+{
+       struct asmc_softc *sc = v;
+       char key[5];
+       uint8_t buf[17];
+
+       printf("asmc_query\n");
+
+       strlcpy(qfan->id, sc->sc_sensor_fan[qfan->idx].desc,
+           sizeof(qfan->id));
+
+       qfan->rpm_act = sc->sc_sensor_fan[qfan->idx].value;
+
+       snprintf(key, sizeof(key), "F%dMn", qfan->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               qfan->rpm_min = 0;
+       else
+               qfan->rpm_min = asmc_rpm(buf);
+
+       snprintf(key, sizeof(key), "F%dMx", qfan->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               qfan->rpm_max = 0;
+       else
+               qfan->rpm_max = asmc_rpm(buf);
+
+       snprintf(key, sizeof(key), "F%dSf", qfan->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               qfan->rpm_saf = 0;
+       else
+               qfan->rpm_saf = asmc_rpm(buf);
+
+       snprintf(key, sizeof(key), "F%dTg", qfan->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               qfan->rpm_tgt = 0;
+       else
+               qfan->rpm_tgt = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_g_act(void *v, struct fan_g_act *g_act)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+
+       snprintf(key, sizeof(key), "F%dAc", g_act->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               g_act->rpm = 0;
+       else
+               g_act->rpm = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_g_min(void *v, struct fan_g_min *g_min)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+
+       snprintf(key, sizeof(key), "F%dMn", g_min->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               g_min->rpm = 0;
+       else
+               g_min->rpm = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_g_max(void *v, struct fan_g_max *g_max)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+
+       snprintf(key, sizeof(key), "F%dMx", g_max->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               g_max->rpm = 0;
+       else
+               g_max->rpm = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_g_saf(void *v, struct fan_g_saf *g_saf)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+
+       snprintf(key, sizeof(key), "F%dSf", g_saf->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               g_saf->rpm = 0;
+       else
+               g_saf->rpm = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_g_tgt(void *v, struct fan_g_tgt *g_tgt)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+
+       snprintf(key, sizeof(key), "F%dTg", g_tgt->idx);
+       if (asmc_try(sc, ASMC_READ, key, buf, 2))
+               g_tgt->rpm = 0;
+       else
+               g_tgt->rpm = asmc_rpm(buf);
+
+       return 0;
+}
+
+int
+asmc_s_min(void *v, struct fan_s_min *s_min)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+       int rpm, r;
+
+       rpm = s_min->rpm * 4;
+       buf[0] = rpm >> 8;
+       buf[1] = rpm;
+       snprintf(key, sizeof(key), "F%dMn", s_min->idx);
+       if ((r = asmc_try(sc, ASMC_WRITE, key, buf, 2)))
+               return r;
+
+       return 0;
+}
+
+int
+asmc_s_max(void *v, struct fan_s_max *s_max)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+       int rpm, r;
+
+       rpm = s_max->rpm * 4;
+       buf[0] = rpm >> 8;
+       buf[1] = rpm;
+       snprintf(key, sizeof(key), "F%dMx", s_max->idx);
+       if ((r = asmc_try(sc, ASMC_WRITE, key, buf, 2)))
+               return r;
+
+       return 0;
+}
+
+int
+asmc_s_tgt(void *v, struct fan_s_tgt *s_tgt)
+{
+       struct asmc_softc *sc = v;
+       char buf[2];
+       char key[5];
+       int rpm, r;
+
+       rpm = s_tgt->rpm * 4;
+       buf[0] = rpm >> 8;
+       buf[1] = rpm;
+       snprintf(key, sizeof(key), "F%dTg", s_tgt->idx);
+       if ((r = asmc_try(sc, ASMC_WRITE, key, buf, 2)))
+               return r;
+
+       return 0;
 }
Index: sys/dev/acpi/files.acpi
===================================================================
RCS file: /cvs/src/sys/dev/acpi/files.acpi,v
retrieving revision 1.61
diff -u -p -u -p -r1.61 files.acpi
--- sys/dev/acpi/files.acpi     17 Nov 2020 14:31:59 -0000      1.61
+++ sys/dev/acpi/files.acpi     27 Nov 2020 16:13:54 -0000
@@ -91,7 +91,7 @@ attach        abl at acpi
 file   dev/acpi/abl.c                  abl
 
 # Apple System Management Controller (SMC)
-device asmc
+device asmc: fan
 attach asmc at acpi
 file   dev/acpi/asmc.c                 asmc
 
Index: sys/sys/conf.h
===================================================================
RCS file: /cvs/src/sys/sys/conf.h,v
retrieving revision 1.155
diff -u -p -u -p -r1.155 conf.h
--- sys/sys/conf.h      6 Jul 2020 04:11:26 -0000       1.155
+++ sys/sys/conf.h      27 Nov 2020 16:13:54 -0000
@@ -499,6 +499,13 @@ extern struct cdevsw cdevsw[];
 
 #endif
 
+/* open, close, ioctl */
+#define cdev_fan_init(c,n) { \
+       dev_init(c,n,open), dev_init(c,n,close), (dev_type_read((*))) enodev, \
+       (dev_type_write((*))) enodev, dev_init(c,n,ioctl), \
+       (dev_type_stop((*))) enodev, 0, selfalse, \
+       (dev_type_mmap((*))) enodev }
+
 /*
  * Line discipline switch table
  */
@@ -635,6 +642,7 @@ cdev_decl(fuse);
 cdev_decl(pvbus);
 cdev_decl(ipmi);
 cdev_decl(kcov);
+cdev_decl(fan);
 
 #endif
 
Index: sys/sys/fanio.h
===================================================================
RCS file: sys/sys/fanio.h
diff -N sys/sys/fanio.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/sys/fanio.h     27 Nov 2020 16:13:54 -0000
@@ -0,0 +1,88 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Marcus Glocker <mgloc...@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _SYS_FANIO_H_
+#define _SYS_FANIO_H_
+
+struct fan_query_drv {
+       char id[32];
+       int nfans;
+};
+
+struct fan_query_fan {
+       int idx;
+       char id[32];
+       int rpm_act;
+       int rpm_min;
+       int rpm_max;
+       int rpm_saf;
+       int rpm_tgt;
+};
+
+struct fan_g_act {
+       int idx;
+       int rpm;
+};
+
+struct fan_g_min {
+       int idx;
+       int rpm;
+};
+
+struct fan_g_max {
+       int idx;
+       int rpm;
+};
+
+struct fan_g_saf {
+       int idx;
+       int rpm;
+};
+
+struct fan_g_tgt {
+       int idx;
+       int rpm;
+};
+
+struct fan_s_min {
+       int idx;
+       int rpm;
+};
+
+struct fan_s_max {
+       int idx;
+       int rpm;
+};
+
+struct fan_s_tgt {
+       int idx;
+       int rpm;
+};
+
+#define FANIOC_QUERY_DRV        _IOR('V', 0, struct fan_query_drv)
+#define FANIOC_QUERY_FAN       _IOWR('V', 1, struct fan_query_fan)
+#define FANIOC_G_ACT           _IOWR('V', 2, struct fan_g_act)
+#define FANIOC_G_MIN           _IOWR('V', 3, struct fan_g_min)
+#define FANIOC_G_MAX           _IOWR('V', 4, struct fan_g_max)
+#define FANIOC_G_SAF           _IOWR('V', 5, struct fan_g_saf)
+#define FANIOC_G_TGT           _IOWR('V', 6, struct fan_g_tgt)
+#define FANIOC_S_MIN           _IOWR('V', 7, struct fan_s_min)
+#define FANIOC_S_MAX           _IOWR('V', 8, struct fan_s_max)
+#define FANIOC_S_TGT           _IOWR('V', 9, struct fan_s_tgt)
+
+#endif /* _SYS_FANIO_H */


--- /dev/null   Fri Nov 27 17:15:07 2020
+++ sbin/fanctl/Makefile        Tue Nov 24 07:48:05 2020
@@ -0,0 +1,6 @@
+#      $OpenBSD$
+
+PROG=  fanctl
+MAN=   fanctl.8
+
+.include <bsd.prog.mk>
--- /dev/null   Fri Nov 27 17:15:16 2020
+++ sbin/fanctl/fanctl.8        Fri Nov 27 16:56:35 2020
@@ -0,0 +1,17 @@
+.\"     $OpenBSD$
+.\"
+.\" Copyright (c) 2020 Marcus Glocker <mgloc...@openbsd.org>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate$
--- /dev/null   Fri Nov 27 17:15:21 2020
+++ sbin/fanctl/fanctl.c        Fri Nov 27 17:05:02 2020
@@ -0,0 +1,270 @@
+/*      $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Marcus Glocker <mgloc...@openbsd.org>
+ *
+ * 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.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/fanio.h>
+
+#define DEVICE "/dev/fan0"
+
+void   usage(void);
+int    printall(int);
+void   parse(char *);
+void   getvalue0(char *);
+void   getvalue1(char *);
+void   setvalue(char *, int);
+
+int    aflag, fd;
+
+int
+main(int argc, char *argv[])
+{
+       int ch, r;
+       char *dev = NULL;
+
+       while ((ch = getopt(argc, argv, "af:")) != -1) {
+               switch (ch) {
+               case 'a':
+                       aflag = 1;
+                       break;
+               case 'f':
+                       dev = optarg;
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (dev == NULL)
+               dev = strdup(DEVICE);
+       if (dev == NULL)
+               err(1, "strdup");
+       fd = open(dev, O_RDWR);
+       if (fd == -1)
+               err(1, "open");
+
+       if (argc == 0 || aflag)
+               r = printall(fd);
+
+       if (aflag) {
+               close(fd);
+               return r;
+       }
+
+       for (; *argv != NULL; ++argv)
+               parse(*argv);
+
+       close(fd);
+       return r;
+}
+
+void
+usage(void)
+{
+       fprintf(stderr, "usage: fanctl [-a] [-f file]\n");
+       exit(1);
+}
+
+int
+printall(int fd)
+{
+       int i, r, nfans;
+       struct fan_query_drv qd;
+       struct fan_query_fan qf;
+
+       r = ioctl(fd, FANIOC_QUERY_DRV, &qd);
+       if (r == -1)
+               err(1, "ioctl");
+       if (qd.nfans < 1)
+               return -1;
+
+       printf("driver=%s\n", qd.id);
+
+       for (i = 0; i < qd.nfans; i++) {
+               memset(&qf, 0, sizeof(struct fan_query_fan));
+               qf.idx = i;
+               r = ioctl(fd, FANIOC_QUERY_FAN, &qf);
+               if (r == -1)
+                       return -1;
+               printf("fan%d.id=%s\n", i, qf.id);
+               printf("fan%d.act=%d RPM\n", i, qf.rpm_act);
+               printf("fan%d.min=%d RPM\n", i, qf.rpm_min);
+               printf("fan%d.max=%d RPM\n", i, qf.rpm_max);
+               printf("fan%d.saf=%d RPM\n", i, qf.rpm_saf);
+               printf("fan%d.tgt=%d RPM\n", i, qf.rpm_tgt);
+       }
+
+       return 0;
+}
+
+void
+parse(char *string)
+{
+       char *key, *val;
+       const char *errstr;
+       int valn;
+
+       key = strdup(string);
+       val = strchr(key, '=');
+       if (val == NULL) {
+               if (strchr(key, '.') == NULL)
+                       getvalue0(key);
+               else
+                       getvalue1(key);
+               return;
+       }
+       *val = '\0';
+       val++;
+
+       valn = strtonum(val, 0, 10000, &errstr);
+       if (errstr != NULL) {
+               warnx("%s: %s", key, errstr);
+               free(key);
+               return;
+       }
+
+       setvalue(key, valn);
+
+       free(key);
+}
+
+void
+getvalue0(char *key)
+{
+       struct fan_query_drv qd;
+       char val[32];
+
+       if (!strcmp(key, "driver")) {
+               ioctl(fd, FANIOC_QUERY_DRV, &qd);
+               strlcpy(val, qd.id, sizeof(val));
+       }
+
+       printf("%s=%s\n", key, val);
+}
+
+void
+getvalue1(char *key)
+{
+       struct fan_query_fan qf;
+       struct fan_g_act gact;
+       struct fan_g_min gmin;
+       struct fan_g_max gmax;
+       struct fan_g_saf gsaf;
+       struct fan_g_tgt gtgt;
+       char *fan, *type;
+       char fanno[3];
+       const char *errstr;
+       int fann, val;
+
+       fan = strdup(key);
+       type = strchr(fan, '.');
+       if (type == NULL) {
+               printf("what\n");
+               return;
+       }
+       *type = '\0';
+       type++;
+
+       strlcpy(fanno, fan+3, sizeof(fanno));
+       fann = strtonum(fanno, 0, 99, &errstr);
+
+       if (!strcmp(type, "id")) {
+               qf.idx = fann;
+               ioctl(fd, FANIOC_QUERY_FAN, &qf);
+               printf("%s=%s\n", key, qf.id);
+               free(fan);
+               return;
+       } else if (!strcmp(type, "act")) {
+               gact.idx = fann;
+               gact.rpm = 0;
+               ioctl(fd, FANIOC_G_ACT, &gact);
+               val = gact.rpm; 
+       } else if (!strcmp(type, "min")) {
+               gmin.idx = fann;
+               gmin.rpm = 0;
+               ioctl(fd, FANIOC_G_MIN, &gmin);
+               val = gmin.rpm;
+       } else if (!strcmp(type, "max")) {
+               gmax.idx = fann;
+               gmax.rpm = 0;
+               ioctl(fd, FANIOC_G_MAX, &gmax);
+               val = gmax.rpm;
+       } else if (!strcmp(type, "saf")) {
+               gsaf.idx = fann;
+               gsaf.rpm = 0;
+               ioctl(fd, FANIOC_G_SAF, &gsaf);
+               val = gsaf.rpm;
+       } else if (!strcmp(type, "tgt")) {
+               gtgt.idx = fann;
+               gtgt.rpm = 0;
+               ioctl(fd, FANIOC_G_TGT, &gtgt);
+               val = gtgt.rpm;
+       } else {
+               printf("fanctl: %s: unknown fan speed\n", type);
+               free(fan);
+               return;
+       }
+
+       printf("%s=%d\n", key, val);
+
+       free(fan);
+}
+
+void
+setvalue(char *key, int val)
+{
+       struct fan_s_max sm;
+       struct fan_g_max gm;
+       char *fan, *type;
+       char fanno[3];
+       const char *errstr;
+       int fann;
+
+       fan = strdup(key);
+       type = strchr(fan, '.');
+       if (type == NULL) {
+               printf("what\n");
+               return;
+       }
+       *type = '\0';
+       type++;
+
+       strlcpy(fanno, fan+3, sizeof(fanno));
+       fann = strtonum(fanno, 0, 99, &errstr);
+
+       if (!strcmp(type, "max")) {
+               gm.idx = fann;
+               gm.rpm = 0;
+               ioctl(fd, FANIOC_G_MAX, &gm);
+
+               sm.idx = fann;
+               sm.rpm = val;
+               ioctl(fd, FANIOC_S_MAX, &sm);
+               printf("%s: %d -> %d\n", key, gm.rpm, val);
+       }
+
+       free(fan);
+}

Reply via email to