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 <[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.
+ */
+
+#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 <[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.
+ */
+
+#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 <[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.
+ */
+
+#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 <[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.
+.\"
+.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 <[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.
+ */
+
+#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, >gt);
+ 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);
+}