mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-19 10:53:58 +00:00
Break out acpi throttling support into a new relative cpufreq driver,
acpi_throttle(4).
This commit is contained in:
parent
88c9b54c47
commit
7150dfc7e5
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=141414
384
sys/dev/acpica/acpi_throttle.c
Normal file
384
sys/dev/acpica/acpi_throttle.c
Normal file
@ -0,0 +1,384 @@
|
||||
/*-
|
||||
* Copyright (c) 2003-2005 Nate Lawson (SDG)
|
||||
* Copyright (c) 2001 Michael Smith
|
||||
* 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>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include "opt_acpi.h"
|
||||
#include <sys/param.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/cpu.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/rman.h>
|
||||
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include "acpi.h"
|
||||
#include <dev/acpica/acpivar.h>
|
||||
#include <dev/pci/pcivar.h>
|
||||
|
||||
#include "cpufreq_if.h"
|
||||
|
||||
/*
|
||||
* Throttling provides relative frequency control. It involves modulating
|
||||
* the clock so that the CPU is active for only a fraction of the normal
|
||||
* clock cycle. It does not change voltage and so is less efficient than
|
||||
* other mechanisms. Since it is relative, it can be used in addition to
|
||||
* absolute cpufreq drivers. We support the ACPI 2.0 specification.
|
||||
*/
|
||||
|
||||
struct acpi_throttle_softc {
|
||||
device_t cpu_dev;
|
||||
ACPI_HANDLE cpu_handle;
|
||||
uint32_t cpu_p_blk; /* ACPI P_BLK location */
|
||||
uint32_t cpu_p_blk_len; /* P_BLK length (must be 6). */
|
||||
struct resource *cpu_p_cnt; /* Throttling control register */
|
||||
int cpu_p_type; /* Resource type for cpu_p_cnt. */
|
||||
};
|
||||
|
||||
#define THR_GET_REG(reg) \
|
||||
(bus_space_read_4(rman_get_bustag((reg)), \
|
||||
rman_get_bushandle((reg)), 0))
|
||||
#define THR_SET_REG(reg, val) \
|
||||
(bus_space_write_4(rman_get_bustag((reg)), \
|
||||
rman_get_bushandle((reg)), 0, (val)))
|
||||
|
||||
/*
|
||||
* Speeds are stored in counts, from 1 to CPU_MAX_SPEED, and
|
||||
* reported to the user in hundredths of a percent.
|
||||
*/
|
||||
#define CPU_MAX_SPEED (1 << cpu_duty_width)
|
||||
#define CPU_SPEED_PERCENT(x) ((10000 * (x)) / CPU_MAX_SPEED)
|
||||
#define CPU_SPEED_PRINTABLE(x) (CPU_SPEED_PERCENT(x) / 10), \
|
||||
(CPU_SPEED_PERCENT(x) % 10)
|
||||
#define CPU_P_CNT_THT_EN (1<<4)
|
||||
#define CPU_QUIRK_NO_THROTTLE (1<<1) /* Throttling is not usable. */
|
||||
|
||||
#define PCI_VENDOR_INTEL 0x8086
|
||||
#define PCI_DEVICE_82371AB_3 0x7113 /* PIIX4 chipset for quirks. */
|
||||
#define PCI_REVISION_A_STEP 0
|
||||
#define PCI_REVISION_B_STEP 1
|
||||
|
||||
static uint32_t cpu_duty_offset; /* Offset in P_CNT of throttle val. */
|
||||
static uint32_t cpu_duty_width; /* Bit width of throttle value. */
|
||||
static uint32_t cpu_throttle_state; /* Current throttle setting. */
|
||||
static int thr_rid; /* Driver-wide resource id. */
|
||||
static int thr_quirks; /* Indicate any hardware bugs. */
|
||||
|
||||
static void acpi_throttle_identify(driver_t *driver, device_t parent);
|
||||
static int acpi_throttle_probe(device_t dev);
|
||||
static int acpi_throttle_attach(device_t dev);
|
||||
static int acpi_throttle_evaluate(struct acpi_throttle_softc *sc);
|
||||
static int acpi_throttle_quirks(struct acpi_throttle_softc *sc);
|
||||
static int acpi_thr_settings(device_t dev, struct cf_setting *sets,
|
||||
int *count, int *type);
|
||||
static int acpi_thr_set(device_t dev, const struct cf_setting *set);
|
||||
static int acpi_thr_get(device_t dev, struct cf_setting *set);
|
||||
|
||||
static device_method_t acpi_throttle_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_identify, acpi_throttle_identify),
|
||||
DEVMETHOD(device_probe, acpi_throttle_probe),
|
||||
DEVMETHOD(device_attach, acpi_throttle_attach),
|
||||
|
||||
/* cpufreq interface */
|
||||
DEVMETHOD(cpufreq_drv_set, acpi_thr_set),
|
||||
DEVMETHOD(cpufreq_drv_get, acpi_thr_get),
|
||||
DEVMETHOD(cpufreq_drv_settings, acpi_thr_settings),
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static driver_t acpi_throttle_driver = {
|
||||
"acpi_throttle",
|
||||
acpi_throttle_methods,
|
||||
sizeof(struct acpi_throttle_softc),
|
||||
};
|
||||
|
||||
static devclass_t acpi_throttle_devclass;
|
||||
DRIVER_MODULE(acpi_throttle, cpu, acpi_throttle_driver, acpi_throttle_devclass,
|
||||
0, 0);
|
||||
|
||||
static void
|
||||
acpi_throttle_identify(driver_t *driver, device_t parent)
|
||||
{
|
||||
|
||||
/* Make sure we're not being doubly invoked. */
|
||||
if (device_find_child(parent, "acpi_throttle", 0) != NULL)
|
||||
return;
|
||||
|
||||
/* Check for a valid duty width and parent CPU type. */
|
||||
if (acpi_get_handle(parent) == NULL)
|
||||
return;
|
||||
if (AcpiGbl_FADT->DutyWidth == 0 ||
|
||||
acpi_get_type(parent) != ACPI_TYPE_PROCESSOR)
|
||||
return;
|
||||
if (BUS_ADD_CHILD(parent, 0, "acpi_throttle", 0) == NULL)
|
||||
device_printf(parent, "acpi_throttle: add child failed\n");
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_throttle_probe(device_t dev)
|
||||
{
|
||||
|
||||
device_set_desc(dev, "ACPI CPU Throttling");
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_throttle_attach(device_t dev)
|
||||
{
|
||||
struct acpi_throttle_softc *sc;
|
||||
ACPI_BUFFER buf;
|
||||
ACPI_OBJECT*obj;
|
||||
ACPI_STATUS status;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
sc->cpu_dev = dev;
|
||||
sc->cpu_handle = acpi_get_handle(dev);
|
||||
|
||||
buf.Pointer = NULL;
|
||||
buf.Length = ACPI_ALLOCATE_BUFFER;
|
||||
status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
device_printf(dev, "attach failed to get Processor obj - %s\n",
|
||||
AcpiFormatException(status));
|
||||
return (ENXIO);
|
||||
}
|
||||
obj = (ACPI_OBJECT *)buf.Pointer;
|
||||
sc->cpu_p_blk = obj->Processor.PblkAddress;
|
||||
sc->cpu_p_blk_len = obj->Processor.PblkLength;
|
||||
AcpiOsFree(obj);
|
||||
|
||||
/* If this is the first device probed, check for quirks. */
|
||||
if (device_get_unit(dev) == 0)
|
||||
acpi_throttle_quirks(sc);
|
||||
|
||||
return (acpi_throttle_evaluate(sc));
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_throttle_evaluate(struct acpi_throttle_softc *sc)
|
||||
{
|
||||
uint32_t duty_end;
|
||||
ACPI_BUFFER buf;
|
||||
ACPI_OBJECT obj;
|
||||
ACPI_GENERIC_ADDRESS gas;
|
||||
ACPI_STATUS status;
|
||||
|
||||
/* Get throttling parameters from the FADT. 0 means not supported. */
|
||||
if (device_get_unit(sc->cpu_dev) == 0) {
|
||||
cpu_duty_offset = AcpiGbl_FADT->DutyOffset;
|
||||
cpu_duty_width = AcpiGbl_FADT->DutyWidth;
|
||||
}
|
||||
if (cpu_duty_width == 0 || (thr_quirks & CPU_QUIRK_NO_THROTTLE) != 0)
|
||||
return (ENXIO);
|
||||
|
||||
/* Validate the duty offset/width. */
|
||||
duty_end = cpu_duty_offset + cpu_duty_width - 1;
|
||||
if (duty_end > 31) {
|
||||
device_printf(sc->cpu_dev,
|
||||
"CLK_VAL field overflows P_CNT register\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
if (cpu_duty_offset <= 4 && duty_end >= 4) {
|
||||
device_printf(sc->cpu_dev,
|
||||
"CLK_VAL field overlaps THT_EN bit\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/*
|
||||
* If not present, fall back to using the processor's P_BLK to find
|
||||
* the P_CNT register.
|
||||
*
|
||||
* Note that some systems seem to duplicate the P_BLK pointer
|
||||
* across multiple CPUs, so not getting the resource is not fatal.
|
||||
*/
|
||||
buf.Pointer = &obj;
|
||||
buf.Length = sizeof(obj);
|
||||
status = AcpiEvaluateObject(sc->cpu_handle, "_PTC", NULL, &buf);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
if (obj.Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3) {
|
||||
device_printf(sc->cpu_dev, "_PTC buffer too small\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
memcpy(&gas, obj.Buffer.Pointer + 3, sizeof(gas));
|
||||
acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid,
|
||||
&gas, &sc->cpu_p_cnt);
|
||||
if (sc->cpu_p_cnt != NULL && bootverbose) {
|
||||
device_printf(sc->cpu_dev, "P_CNT from _PTC %#jx\n",
|
||||
gas.Address);
|
||||
}
|
||||
}
|
||||
|
||||
/* If _PTC not present or other failure, try the P_BLK. */
|
||||
if (sc->cpu_p_cnt == NULL) {
|
||||
/*
|
||||
* The spec says P_BLK must be 6 bytes long. However, some
|
||||
* systems use it to indicate a fractional set of features
|
||||
* present so we take anything >= 4.
|
||||
*/
|
||||
if (sc->cpu_p_blk_len < 4)
|
||||
return (ENXIO);
|
||||
gas.Address = sc->cpu_p_blk;
|
||||
gas.AddressSpaceId = ACPI_ADR_SPACE_SYSTEM_IO;
|
||||
gas.RegisterBitWidth = 32;
|
||||
acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid,
|
||||
&gas, &sc->cpu_p_cnt);
|
||||
if (sc->cpu_p_cnt != NULL) {
|
||||
if (bootverbose)
|
||||
device_printf(sc->cpu_dev,
|
||||
"P_CNT from P_BLK %#x\n", sc->cpu_p_blk);
|
||||
} else {
|
||||
device_printf(sc->cpu_dev, "failed to attach P_CNT\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
}
|
||||
thr_rid++;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_throttle_quirks(struct acpi_throttle_softc *sc)
|
||||
{
|
||||
device_t acpi_dev;
|
||||
|
||||
/* Look for various quirks of the PIIX4 part. */
|
||||
acpi_dev = pci_find_device(PCI_VENDOR_INTEL, PCI_DEVICE_82371AB_3);
|
||||
if (acpi_dev) {
|
||||
switch (pci_get_revid(acpi_dev)) {
|
||||
/*
|
||||
* Disable throttling control on PIIX4 A and B-step.
|
||||
* See specification changes #13 ("Manual Throttle Duty Cycle")
|
||||
* and #14 ("Enabling and Disabling Manual Throttle"), plus
|
||||
* erratum #5 ("STPCLK# Deassertion Time") from the January
|
||||
* 2002 PIIX4 specification update. Note that few (if any)
|
||||
* mobile systems ever used this part.
|
||||
*/
|
||||
case PCI_REVISION_A_STEP:
|
||||
case PCI_REVISION_B_STEP:
|
||||
thr_quirks |= CPU_QUIRK_NO_THROTTLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_thr_settings(device_t dev, struct cf_setting *sets, int *count, int *type)
|
||||
{
|
||||
struct acpi_throttle_softc *sc;
|
||||
int i, speed;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
if (sets == NULL || count == NULL)
|
||||
return (EINVAL);
|
||||
if (*count < CPU_MAX_SPEED)
|
||||
return (ENOMEM);
|
||||
|
||||
/* Return a list of valid settings for this driver. */
|
||||
memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * CPU_MAX_SPEED);
|
||||
for (i = 0, speed = CPU_MAX_SPEED; speed != 0; i++, speed--) {
|
||||
sets[i].freq = CPU_SPEED_PERCENT(speed);
|
||||
sets[i].dev = dev;
|
||||
}
|
||||
*count = CPU_MAX_SPEED;
|
||||
*type = CPUFREQ_TYPE_RELATIVE;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_thr_set(device_t dev, const struct cf_setting *set)
|
||||
{
|
||||
struct acpi_throttle_softc *sc;
|
||||
uint32_t clk_val, p_cnt, speed;
|
||||
|
||||
if (set == NULL)
|
||||
return (EINVAL);
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
/*
|
||||
* Validate requested state converts to a duty cycle that is an
|
||||
* integer from [1 .. CPU_MAX_SPEED].
|
||||
*/
|
||||
speed = set->freq * CPU_MAX_SPEED / 10000;
|
||||
if (speed * 10000 != set->freq * CPU_MAX_SPEED ||
|
||||
speed < 1 || speed > CPU_MAX_SPEED)
|
||||
return (EINVAL);
|
||||
|
||||
/* If we're at this setting, don't bother applying it again. */
|
||||
if (speed == cpu_throttle_state)
|
||||
return (0);
|
||||
|
||||
/* Get the current P_CNT value and disable throttling */
|
||||
p_cnt = THR_GET_REG(sc->cpu_p_cnt);
|
||||
p_cnt &= ~CPU_P_CNT_THT_EN;
|
||||
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
|
||||
|
||||
/* If we're at maximum speed, that's all */
|
||||
if (speed < CPU_MAX_SPEED) {
|
||||
/* Mask the old CLK_VAL off and OR in the new value */
|
||||
clk_val = (CPU_MAX_SPEED - 1) << cpu_duty_offset;
|
||||
p_cnt &= ~clk_val;
|
||||
p_cnt |= (speed << cpu_duty_offset);
|
||||
|
||||
/* Write the new P_CNT value and then enable throttling */
|
||||
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
|
||||
p_cnt |= CPU_P_CNT_THT_EN;
|
||||
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
|
||||
}
|
||||
cpu_throttle_state = speed;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
acpi_thr_get(device_t dev, struct cf_setting *set)
|
||||
{
|
||||
struct acpi_throttle_softc *sc;
|
||||
uint32_t p_cnt, clk_val;
|
||||
|
||||
if (set == NULL)
|
||||
return (EINVAL);
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
/* Get the current throttling setting from P_CNT. */
|
||||
p_cnt = THR_GET_REG(sc->cpu_p_cnt);
|
||||
clk_val = (p_cnt >> cpu_duty_offset) & (CPU_MAX_SPEED - 1);
|
||||
|
||||
memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set));
|
||||
set->freq = CPU_SPEED_PERCENT(clk_val);
|
||||
set->dev = dev;
|
||||
|
||||
return (0);
|
||||
}
|
Loading…
Reference in New Issue
Block a user