Subject: Re: ACPI-CA v. 20050309
To: Vincent <10.50@free.fr>
From: Quentin Garnier <cube@cubidou.net>
List: tech-kern
Date: 03/26/2005 13:51:29
--6749iDyxihQ87e9v
Content-Type: multipart/mixed; boundary="35rzUHtxHARXKmYf"
Content-Disposition: inline


--35rzUHtxHARXKmYf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On Wed, Mar 23, 2005 at 11:18:32AM +0100, Vincent wrote:
[...]
> I was also planning to write a driver for the processor object, by the wa=
y!

I had a take at this last night, maybe because I had read too much of the
ACPI dump of my laptop.

I came up with the attached code, which doesn't do much except listing all
the supported performance states obtained with the _PSS method.  It
dynamically adjusts the range of supported performance states through the
Notify mechanism.

However, it doesn't allow the user to change the performance state yet,
for reasons I'll state later.

Here's what gets printed on the console with that driver:

acpiproc0 at acpi0: ACPI CPU
acpiproc0: 10 processor states:
acpiproc0: 0 ->  1600 MHz,  32000 mW,    10 mus,   110 mus
acpiproc0: 1 ->  1600 MHz,  32000 mW,    10 mus,   110 mus
acpiproc0: 2 ->  1600 MHz,  32000 mW,    10 mus,   110 mus
acpiproc0: 3 ->  1600 MHz,  32000 mW,    10 mus,   110 mus
acpiproc0: 4 ->  1600 MHz,  32000 mW,    10 mus,   110 mus
acpiproc0: 5 ->  1400 MHz,  28000 mW,    10 mus,   110 mus
acpiproc0: 6 ->  1200 MHz,  24000 mW,    10 mus,   110 mus
acpiproc0: 7 ->  1000 MHz,  20000 mW,    10 mus,   110 mus
acpiproc0: 8 ->   800 MHz,  16000 mW,    10 mus,   110 mus
acpiproc0: 9 ->   600 MHz,  12000 mW,    10 mus,   110 mus
acpiproc0: states 4-9 available

Then, later, when I un-plug and re-plug the AC adapter:

acpiproc0: PPC changed, states 5-9 available
acpiproc0: received unknown notify message 0x81

acpiproc0: PPC changed, states 4-9 available
acpiproc0: received unknown notify message 0x81

The driver also adds a sysctl node with all the information.

Now, for the things the driver doesn't do:

 o first, it should be accessing _PDC (or rather _OSC when it exists)
   to indicate what the system is able to do.  I only had a quick look
   at the Intel docs about that (as it is vendor-dependent), but it is
   not the most important thing right now,

 o there is a C-state change notification that the driver doesn't
   handle.  I haven't looked very closely at all the C-state stuff, so
   while I guess there's something we could do, I don't know what yet,

 o it doesn't even look at the performance throttling objects (ENXIO on
   my laptop),

 o it doesn't offer an interface to change the performance state.

The performance state can be changed thanks to the data contained in
the _PCT object.  However, the ACPI-CA code (neither the one in the
tree or the latest version) does not offer an API to parse Register()
resources.

Reading the ASL code, though, on my laptop, changing the CPU speed is
a matter of doing outw(0xb2, 0x0n86), and checking the success with
inb(0xb3) =3D=3D 0x0n (n being the performance state number).

The structure is rather easy to parse and documented in the ACPI spec,
but in any case it would be redundant with Enhanced SpeedStep.  OTOH,
I'd like to see an ACPI dump of a PowerNow! based computer, it should
have a similar interface which would allow unification.

My main objective with this would be to provide an interface with the
ThermalZone stuff, mostly to support the passive cooling features
(there are other things we want to do better in acpitz(4), such as
what to do when the temperature reaches _HOT or _CRT...).

Ah, insomnia.

--=20
Quentin Garnier - cube@cubidou.net - cube@NetBSD.org
"When I find the controls, I'll go where I like, I'll know where I want
to be, but maybe for now I'll stay right here on a silent sea."
KT Tunstall, Silent Sea, Eye to the Telescope, 2004.

--35rzUHtxHARXKmYf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="acpi_proc.c"

/*	$NetBSD$	*/

/*
 *  Copyright (c) 2005 The NetBSD Foundation.
 *  All rights reserved.
 *
 *  This code is derived from software contributed to the NetBSD Foundation
 *   by Quentin Garnier.
 *
 *  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.
 *  3. All advertising materials mentioning features or use of this software
 *     must display the following acknowledgement:
 *         This product includes software developed by the NetBSD
 *         Foundation, Inc. and its contributors.
 *  4. Neither the name of The NetBSD Foundation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

#include <dev/acpi/acpica.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>

/*
 * Let's have reasonable maximum values.
 */
#define ACPIPROC_MAX_STATES	100
#define ACPIPROC_STRING_MAXLEN	64

static int	acpiproc_node;

static int	acpiproc_match(struct device *, struct cfdata *, void *);
static void	acpiproc_attach(struct device *, struct device *, void *);

SYSCTL_SETUP_PROTO(sysctl_acpiproc_setup);

struct acpiproc_power_state {
	ACPI_INTEGER pm_freq;
	ACPI_INTEGER pm_power;
	ACPI_INTEGER pm_translat;
	ACPI_INTEGER pm_bmlat;
	ACPI_INTEGER pm_control;
	ACPI_INTEGER pm_status;
	char pm_string[ACPIPROC_STRING_MAXLEN];
};

struct acpiproc_softc {
	struct device sc_dev;
	struct acpi_devnode *sc_node;
	uint32_t sc_nstates;
	uint32_t sc_navail;
	int sc_PPC_present;
	struct acpiproc_power_state *sc_states;
};

CFATTACH_DECL(acpiproc, sizeof(struct acpiproc_softc), acpiproc_match,
    acpiproc_attach, NULL, NULL);

static int	acpiproc_refresh_PPC(struct acpiproc_softc *);
static void	acpiproc_notify_handler(ACPI_HANDLE, UINT32, void *);

static int
acpiproc_match(struct device *parent, struct cfdata *match, void *aux)
{
	struct acpi_attach_args *aa = aux;

	if (aa->aa_node->ad_type == ACPI_TYPE_PROCESSOR)
		return 1;
	return 0;
}

static void
acpiproc_attach(struct device *parent, struct device *self, void *aux)
{
	struct acpiproc_softc *sc = (struct acpiproc_softc *)self;
	struct acpi_attach_args *aa = aux;
	ACPI_STATUS rv;
	ACPI_BUFFER buf;
	ACPI_OBJECT *p, *s, *e;
	struct sysctlnode *node;
	int i, n1, n2, error;
	char state_name[7]; /* state85 is the maximum */
	struct sysctllog *clog = NULL;

	aprint_normal(": ACPI CPU\n");
	sc->sc_node = aa->aa_node;

	rv = acpi_eval_struct(sc->sc_node->ad_handle, "_PSS", &buf);
	if (ACPI_FAILURE(rv)) {
		aprint_error("%s: no Processor Performance Control support\n",
		    sc->sc_dev.dv_xname);
		return;
	}

	p = (ACPI_OBJECT *)buf.Pointer;
	if (p->Type != ACPI_TYPE_PACKAGE) {
		aprint_error("%s: _PSS: wrong data type returned (%d)\n",
		    sc->sc_dev.dv_xname, p->Type);
		goto out;
	}

	if (p->Package.Count == 0) {
		aprint_error("%s: no available processor state\n",
		    sc->sc_dev.dv_xname);
		goto out;
	}
	if (p->Package.Count > ACPIPROC_MAX_STATES) {
		aprint_error("%s: too many processor states\n",
		    sc->sc_dev.dv_xname);
		goto out;
	}

	if ((error = sysctl_createv(&clog, 0, NULL, &node, 0,
	    CTLTYPE_NODE, sc->sc_dev.dv_xname, NULL, NULL, 0, NULL, 0,
	    CTL_MACHDEP, acpiproc_node, CTL_CREATE, CTL_EOL)) != 0) {
		aprint_error("%s: root node creation error (%d)\n",
		    sc->sc_dev.dv_xname, error);
		goto out;
	}
	n1 = node->sysctl_num;

	aprint_normal("%s: %d processor states:\n", sc->sc_dev.dv_xname,
	    p->Package.Count);

	sc->sc_nstates = p->Package.Count - 1;
	sc->sc_states = malloc(p->Package.Count *
	    sizeof(struct acpiproc_power_state), M_DEVBUF, M_NOWAIT);
	if (sc->sc_states == NULL) {
		aprint_error("%s: failed to allocate memory\n",
		    sc->sc_dev.dv_xname);
		goto out1;
	}

	if ((error = sysctl_createv(&clog, 0, NULL, NULL, CTLFLAG_IMMEDIATE,
	    CTLTYPE_INT, "perf_nstates", NULL, NULL, sc->sc_nstates, NULL, 0,
	    CTL_MACHDEP, acpiproc_node, n1, CTL_CREATE, CTL_EOL)) != 0) {
		aprint_error("%s: 'perf_nstates' node creation error (%d)\n",
		    sc->sc_dev.dv_xname, error);
		goto out2;
	}

	if ((error = sysctl_createv(&clog, 0, NULL, &node, 0, CTLTYPE_NODE,
	    "perf_states", NULL, NULL, 0, NULL, 0,
	    CTL_MACHDEP, acpiproc_node, n1, CTL_CREATE, CTL_EOL)) != 0) {
		aprint_error("%s: 'perf_states' node creation error (%d)\n",
		    sc->sc_dev.dv_xname, error);
		goto out2;
	}
	n2 = node->sysctl_num;

	for (i = 0, s = p->Package.Elements; i < p->Package.Count; i++, s++) {
		if (s->Type != ACPI_TYPE_PACKAGE) {
			aprint_error("%s: broken PSS table at element %d\n",
			    sc->sc_dev.dv_xname, i);
			goto out2;
		}

		e = s->Package.Elements;

		snprintf(&sc->sc_states[i].pm_string[0],
		    ACPIPROC_STRING_MAXLEN, "%5" PRIu64 " MHz, %6" PRIu64
		    " mW, %5" PRIu64 " mus, %5" PRIu64 " mus",
		    e[0].Integer.Value, e[1].Integer.Value, e[2].Integer.Value,
		    e[3].Integer.Value);
		aprint_normal("%s: %d -> %s\n", sc->sc_dev.dv_xname, i,
		    sc->sc_states[i].pm_string);

		snprintf(state_name, sizeof(state_name), "state%u", i);
		if ((error = sysctl_createv(&clog, 0, NULL, NULL, 0,
		    CTLTYPE_STRING, state_name, NULL, NULL, 0,
		    sc->sc_states[i].pm_string, ACPIPROC_STRING_MAXLEN,
		    CTL_MACHDEP, acpiproc_node, n1, n2, CTL_CREATE,
		    CTL_EOL)) != 0) {
			aprint_error("%s: state %d node creation error (%d)\n",
			    sc->sc_dev.dv_xname, i, error);
			goto out2;
		}

		sc->sc_states[i].pm_freq = e[0].Integer.Value;
		sc->sc_states[i].pm_power = e[1].Integer.Value;
		sc->sc_states[i].pm_translat = e[2].Integer.Value;
		sc->sc_states[i].pm_bmlat = e[3].Integer.Value;
		sc->sc_states[i].pm_control = e[4].Integer.Value;
		sc->sc_states[i].pm_status = e[5].Integer.Value;
	}

	if (acpiproc_refresh_PPC(sc) != 0)
		sc->sc_PPC_present = 0;
	else {
		sc->sc_PPC_present = 1;
		aprint_normal("%s: states %u-%u available\n",
		    sc->sc_dev.dv_xname, sc->sc_navail, sc->sc_nstates);

		if ((error = sysctl_createv(&clog, 0, NULL, NULL, 0,
		    CTLTYPE_INT, "perf_navail", NULL, NULL, 0, &sc->sc_navail, 0,
		    CTL_MACHDEP, acpiproc_node, n1, CTL_CREATE, CTL_EOL)) != 0) {
			aprint_error("%s: 'perf_navail' node creation error (%d)\n",
			    sc->sc_dev.dv_xname, error);
			goto out2;
		}

		rv = AcpiInstallNotifyHandler(sc->sc_node->ad_handle,
		    ACPI_DEVICE_NOTIFY, acpiproc_notify_handler, sc);
	}

	AcpiOsFree(buf.Pointer);
	return;
out2:
	free(sc->sc_states, M_DEVBUF);
out1:
	sysctl_teardown(&clog);
out:
	AcpiOsFree(buf.Pointer);
}

SYSCTL_SETUP(sysctl_acpiproc_setup, "sysctl machdep.acpiproc subtree setup")
{
	struct sysctlnode *node;

	if (sysctl_createv(clog, 0, NULL, NULL,
	    CTLFLAG_PERMANENT,
	    CTLTYPE_NODE, "machdep", NULL,
	    NULL, 0, NULL, 0,
	    CTL_MACHDEP, CTL_EOL) != 0)
		return;
	if (sysctl_createv(clog, 0, NULL, &node,
	    CTLFLAG_PERMANENT,
	    CTLTYPE_NODE, "acpiproc", NULL,
	    NULL, 0, NULL, 0,
	    CTL_MACHDEP, CTL_CREATE, CTL_EOL) != 0)
		return;
	acpiproc_node = node->sysctl_num;
}

static int
acpiproc_refresh_PPC(struct acpiproc_softc *sc)
{
	ACPI_INTEGER ai;
	ACPI_STATUS rv;

	rv = acpi_eval_integer(sc->sc_node->ad_handle, "_PPC", &ai);
	if (ACPI_FAILURE(rv)) {
		aprint_error("%s: no _PPC object or invalid _PPC object\n",
		    sc->sc_dev.dv_xname);
		return ENXIO;
	}

	sc->sc_navail = (uint32_t)ai;

	return 0;
}

static void
acpiproc_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context)
{
	struct acpiproc_softc *sc = context;

	switch (notify) {
	case ACPI_NOTIFY_PerformancePresentCapabilitiesChanged:
		acpiproc_refresh_PPC(sc);
		aprint_normal("%s: PPC changed, states %u-%u available\n",
		    sc->sc_dev.dv_xname, sc->sc_navail, sc->sc_nstates);
		break;
	default:
		aprint_error("%s: received unknown notify message 0x%x\n",
		    sc->sc_dev.dv_xname, notify);
	}
}

--35rzUHtxHARXKmYf--

--6749iDyxihQ87e9v
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (NetBSD)

iQEVAwUBQkVa0dgoQloHrPnoAQIVNggAgW0AEV/3ym6XTDC1YFVIhaTPR80SHji+
gPZoe0BNIZuYfUk+fAqvT0LxAR7ijduZOPKmk4sG3s6C2H4MRDW/gjN4fzlcwIra
S5gJ/yQHkJ/nOptacAF2h7ytyPz8r7+TzttRn5LZ0yDQwGpFptL+taRu4cJczKoh
78ei75X1sfh5Hgl48rKkul15FDgUNjWe9AFzUlHayGm+I8kMOA3apBLd/sWvl1tc
7NW1HwDm99Szujpw2CVjQqxorLt8fq3ldTf77FF7U+rQNWFAYCGE4Xkxzud86Axa
5BuJZF4kHZInZ4q4iO3VWR+cNrgq2+s8yXeGV6XDYwsTEYxyfIvXyQ==
=jIHJ
-----END PGP SIGNATURE-----

--6749iDyxihQ87e9v--