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