tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
ACPI fan
Hi,
below you will find a small driver for controlling ACPI fans. A
sysctl-solution with "knobs" is hardly optimal and in a way the whole driver
is redundant.
However, I have a laptop that has problems with fans and overheating. The
driver provides a manual solution for these immediate problems (at least I
feel more comfortable doing longer debugging sessions with the ability to
turn the fan on). For the record: instead of acpitz(4), fans and thermal
zones seem to be handled directly by the EC.
P.S.
I noticed (too late) that Jared had already written this years ago, so
nothing new here, but someone with similar problems might benefit from this.
* * *
/* $NetBSD: acpi_fan.c,v 0.1 2009/04/12 11:03:00 $ */
/*-
* Copyright (c) 2009 Jukka Ruohonen <jruohonen%iki.fi@localhost>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: acpi_fan.c,v 0.1 2009/04/12 11:03:03 $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
struct acpifan_softc {
struct acpi_devnode *sc_node;
struct sysctllog *sc_log;
device_t sc_dev;
};
static int acpifan_match(device_t, cfdata_t, void *);
static void acpifan_attach(device_t, device_t, void *);
static ACPI_STATUS acpifan_get_state(ACPI_HANDLE, int *);
static ACPI_STATUS acpifan_get_status(ACPI_HANDLE, int *);
static bool acpifan_sysctl_init(device_t);
static int acpifan_sysctl_helper(SYSCTLFN_PROTO);
CFATTACH_DECL_NEW(acpifan, sizeof(struct acpifan_softc),
acpifan_match, acpifan_attach, NULL, NULL);
static const char * const fan_hid[] = {
"PNP0C0B",
NULL
};
static int
acpifan_match(device_t parent, cfdata_t match, void *aux)
{
struct acpi_attach_args *aa = aux;
if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
return 0;
return acpi_match_hid(aa->aa_node->ad_devinfo, fan_hid);
}
static void
acpifan_attach(device_t parent, device_t self, void *aux)
{
struct acpifan_softc *sc = device_private(self);
struct acpi_attach_args *aa = aux;
ACPI_STATUS rv;
sc->sc_dev = self;
sc->sc_node = aa->aa_node;
rv = acpifan_get_state(sc->sc_node->ad_handle, NULL);
if (ACPI_FAILURE(rv)) {
aprint_normal(": failed to get state: %s\n",
AcpiFormatException(rv));
return;
}
aprint_naive(": ACPI Fan\n");
aprint_normal(": ACPI Fan\n");
if (acpifan_sysctl_init(self) != true)
aprint_error_dev(self, "failed to initialize sysctl\n");
if (pmf_device_register(self, NULL, NULL) != true)
aprint_error_dev(self, "failed to register power handler\n");
}
static ACPI_STATUS
acpifan_get_state(ACPI_HANDLE hdl, int *state)
{
ACPI_OBJECT *pobj, *eobj;
ACPI_BUFFER buf;
ACPI_STATUS rv;
uint8_t i, n;
rv = acpi_eval_struct(hdl, "_PR0", &buf);
if (ACPI_FAILURE(rv))
goto out;
pobj = buf.Pointer;
if (pobj->Package.Count > 0xFF) {
rv = AE_LIMIT;
goto out;
}
n = pobj->Package.Count;
if (pobj->Type != ACPI_TYPE_PACKAGE || n == 0) {
rv = AE_BAD_DATA;
goto out;
}
eobj = pobj->Package.Elements;
for (i = 0; i < n; ++i) {
rv = acpifan_get_status(eobj->Reference.Handle, state);
if (ACPI_FAILURE(rv))
goto out;
/*
* If and only if all resources are "on",
* the whole package is assumed to be "on".
*/
if (state != NULL && *state != ACPI_STA_POW_ON)
break;
}
out:
if (buf.Pointer != NULL)
AcpiOsFree(buf.Pointer);
return rv;
}
static ACPI_STATUS
acpifan_get_status(ACPI_HANDLE hdl, int *state)
{
ACPI_INTEGER buf;
ACPI_STATUS rv;
rv = acpi_eval_integer(hdl, "_STA", &buf);
if (ACPI_FAILURE(rv))
return rv;
if (buf != ACPI_STA_POW_OFF && buf != ACPI_STA_POW_ON)
return AE_BAD_DATA;
if (state != NULL)
*state = buf;
return AE_OK;
}
static bool
acpifan_sysctl_init(device_t self)
{
struct acpifan_softc *sc = device_private(self);
const struct sysctlnode *cnode;
const struct sysctlnode *rnode;
const char *ptr;
if ((ptr = strstr(device_xname(self), "fan")) == NULL)
return false;
if (sysctl_createv(&sc->sc_log, 0, NULL, NULL,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw",
NULL, NULL, 0, NULL, 0, CTL_HW, CTL_EOL) != 0)
return false;
if (sysctl_createv(&sc->sc_log, 0, NULL, &rnode,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi",
NULL, NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL) != 0)
return false;
if (sysctl_createv(&sc->sc_log, 0, &rnode, &cnode,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE, CTLTYPE_INT, ptr,
SYSCTL_DESCR("fan control"), acpifan_sysctl_helper,
0, sc, 0, CTL_CREATE, CTL_EOL) != 0)
return false;
return true;
}
static int
acpifan_sysctl_helper(SYSCTLFN_ARGS)
{
struct acpifan_softc *sc;
struct sysctlnode node;
int error, state, t;
ACPI_STATUS rv;
sc = rnode->sysctl_data;
rv = acpifan_get_state(sc->sc_node->ad_handle, &t);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(sc->sc_dev, "failed to get state: %s\n",
AcpiFormatException(rv));
return EIO;
}
node = *rnode;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
switch (t) {
case ACPI_STA_POW_ON:
state = ACPI_STATE_D0;
break;
case ACPI_STA_POW_OFF:
state = ACPI_STATE_D3;
break;
default:
return EINVAL;
}
rv = acpi_pwr_switch_consumer(sc->sc_node->ad_handle, state);
if (ACPI_FAILURE(rv) && rv != AE_BAD_PARAMETER) {
aprint_error_dev(sc->sc_dev, "failed to set state: %s\n",
AcpiFormatException(rv));
return EIO;
}
return 0;
}
Home |
Main Index |
Thread Index |
Old Index