#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpuhelper.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/msgport2.h>
#include <sys/cpu_topology.h>
#include "acpi.h"
#include "acpivar.h"
#include "acpi_cpu.h"
#include "acpi_cpu_pstate.h"
#define ACPI_NPSTATE_MAX 32
#define ACPI_PSS_PX_NENTRY 6
#define ACPI_PSD_COORD_SWALL 0xfc
#define ACPI_PSD_COORD_SWANY 0xfd
#define ACPI_PSD_COORD_HWALL 0xfe
#define ACPI_PSD_COORD_VALID(coord) \
((coord) == ACPI_PSD_COORD_SWALL || \
(coord) == ACPI_PSD_COORD_SWANY || \
(coord) == ACPI_PSD_COORD_HWALL)
struct acpi_pst_softc;
LIST_HEAD(acpi_pst_list, acpi_pst_softc);
struct acpi_pst_chmsg {
struct cpuhelper_msg ch_msg;
const struct acpi_pst_res *ch_ctrl;
const struct acpi_pst_res *ch_status;
};
struct acpi_pst_domain {
uint32_t pd_dom;
uint32_t pd_coord;
uint32_t pd_nproc;
LIST_ENTRY(acpi_pst_domain) pd_link;
uint32_t pd_flags;
struct lwkt_serialize pd_serialize;
int pd_state;
struct acpi_pst_list pd_pstlist;
struct sysctl_ctx_list pd_sysctl_ctx;
struct sysctl_oid *pd_sysctl_tree;
};
LIST_HEAD(acpi_pst_domlist, acpi_pst_domain);
#define ACPI_PSTDOM_FLAG_STUB 0x1
#define ACPI_PSTDOM_FLAG_DEAD 0x2
#define ACPI_PSTDOM_FLAG_INT 0x4
struct acpi_pst_softc {
device_t pst_dev;
struct acpi_cpu_softc *pst_parent;
struct acpi_pst_domain *pst_domain;
struct acpi_pst_res pst_creg;
struct acpi_pst_res pst_sreg;
int pst_state;
int pst_cpuid;
uint32_t pst_flags;
ACPI_HANDLE pst_handle;
LIST_ENTRY(acpi_pst_softc) pst_link;
};
#define ACPI_PST_FLAG_PPC 0x1
#define ACPI_PST_FLAG_PDL 0x2
static int acpi_pst_probe(device_t dev);
static int acpi_pst_attach(device_t dev);
static void acpi_pst_notify(device_t dev);
static void acpi_pst_postattach(void *);
static struct acpi_pst_domain *
acpi_pst_domain_create_int(device_t, uint32_t);
static struct acpi_pst_domain *
acpi_pst_domain_create_pkg(device_t, ACPI_OBJECT *);
static struct acpi_pst_domain *
acpi_pst_domain_find(uint32_t);
static struct acpi_pst_domain *
acpi_pst_domain_alloc(uint32_t, uint32_t, uint32_t);
static void acpi_pst_domain_set_pstate_locked(struct acpi_pst_domain *,
int, int *);
static void acpi_pst_domain_set_pstate(struct acpi_pst_domain *, int,
int *);
static void acpi_pst_domain_check_nproc(device_t, struct acpi_pst_domain *);
static void acpi_pst_global_set_pstate(int);
static void acpi_pst_global_fixup_pstate(void);
static int acpi_pst_check_csr(struct acpi_pst_softc *);
static int acpi_pst_check_pstates(struct acpi_pst_softc *);
static int acpi_pst_init(struct acpi_pst_softc *);
static int acpi_pst_set_pstate(struct acpi_pst_softc *,
const struct acpi_pstate *);
static const struct acpi_pstate *
acpi_pst_get_pstate(struct acpi_pst_softc *);
static int acpi_pst_alloc_resource(device_t, ACPI_OBJECT *, int,
struct acpi_pst_res *);
static int acpi_pst_eval_ppc(struct acpi_pst_softc *, int *);
static int acpi_pst_eval_pdl(struct acpi_pst_softc *, int *);
static void acpi_pst_check_csr_handler(struct cpuhelper_msg *);
static void acpi_pst_check_pstates_handler(struct cpuhelper_msg *);
static void acpi_pst_init_handler(struct cpuhelper_msg *);
static void acpi_pst_set_pstate_handler(struct cpuhelper_msg *);
static void acpi_pst_get_pstate_handler(struct cpuhelper_msg *);
static int acpi_pst_sysctl_freqs(SYSCTL_HANDLER_ARGS);
static int acpi_pst_sysctl_freqs_bin(SYSCTL_HANDLER_ARGS);
static int acpi_pst_sysctl_power(SYSCTL_HANDLER_ARGS);
static int acpi_pst_sysctl_members(SYSCTL_HANDLER_ARGS);
static int acpi_pst_sysctl_select(SYSCTL_HANDLER_ARGS);
static int acpi_pst_sysctl_global(SYSCTL_HANDLER_ARGS);
static struct acpi_pst_domlist acpi_pst_domains =
LIST_HEAD_INITIALIZER(acpi_pst_domains);
static int acpi_pst_domain_id;
static int acpi_pst_global_state;
static int acpi_pstate_start = -1;
static int acpi_pstate_count;
static int acpi_npstates;
static struct acpi_pstate *acpi_pstates;
static const struct acpi_pst_md *acpi_pst_md;
static int acpi_pst_ppc = -1;
TUNABLE_INT("hw.acpi.cpu.pst.ppc", &acpi_pst_ppc);
static int acpi_pst_pdl = -1;
TUNABLE_INT("hw.acpi.cpu.pst.pdl", &acpi_pst_pdl);
static int acpi_pst_ht_reuse_domain = 1;
TUNABLE_INT("hw.acpi.cpu.pst.ht_reuse_domain", &acpi_pst_ht_reuse_domain);
static int acpi_pst_force_pkg_domain = 0;
TUNABLE_INT("hw.acpi.cpu.pst.force_pkg_domain", &acpi_pst_force_pkg_domain);
static int acpi_pst_handle_notify = 1;
TUNABLE_INT("hw.acpi.cpu.pst.handle_notify", &acpi_pst_handle_notify);
static int acpi_pst_intel_pkg_domain = 1;
TUNABLE_INT("hw.acpi.cpu.pst.intel_pkg_domain", &acpi_pst_intel_pkg_domain);
static device_method_t acpi_pst_methods[] = {
DEVMETHOD(device_probe, acpi_pst_probe),
DEVMETHOD(device_attach, acpi_pst_attach),
DEVMETHOD(device_detach, bus_generic_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(bus_add_child, bus_generic_add_child),
DEVMETHOD(bus_print_child, bus_generic_print_child),
DEVMETHOD(bus_read_ivar, bus_generic_read_ivar),
DEVMETHOD(bus_write_ivar, bus_generic_write_ivar),
DEVMETHOD(bus_get_resource_list, bus_generic_get_resource_list),
DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource),
DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource),
DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource),
DEVMETHOD(bus_release_resource, bus_generic_release_resource),
DEVMETHOD(bus_driver_added, bus_generic_driver_added),
DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
DEVMETHOD_END
};
static driver_t acpi_pst_driver = {
"cpu_pst",
acpi_pst_methods,
sizeof(struct acpi_pst_softc),
.gpri = KOBJ_GPRI_ACPI+2
};
static devclass_t acpi_pst_devclass;
DRIVER_MODULE(cpu_pst, cpu, acpi_pst_driver, acpi_pst_devclass, NULL, NULL);
MODULE_DEPEND(cpu_pst, acpi, 1, 1, 1);
static __inline int
acpi_pst_freq2index(int freq)
{
int i;
for (i = 0; i < acpi_npstates; ++i) {
if (acpi_pstates[i].st_freq == freq)
return i;
}
return -1;
}
static int
acpi_pst_probe(device_t dev)
{
ACPI_BUFFER buf;
ACPI_HANDLE handle;
ACPI_STATUS status;
ACPI_OBJECT *obj;
if (acpi_disabled("cpu_pst") ||
acpi_get_type(dev) != ACPI_TYPE_PROCESSOR)
return ENXIO;
if (acpi_pst_md == NULL)
acpi_pst_md = acpi_pst_md_probe();
handle = acpi_get_handle(dev);
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(handle, "_PSD", NULL, &buf);
if (!ACPI_FAILURE(status)) {
AcpiOsFree((ACPI_OBJECT *)buf.Pointer);
goto done;
}
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(handle, "_PCT", NULL, &buf);
if (ACPI_FAILURE(status)) {
if (bootverbose) {
device_printf(dev, "Can't get _PCT package - %s\n",
AcpiFormatException(status));
}
return ENXIO;
}
obj = (ACPI_OBJECT *)buf.Pointer;
if (!ACPI_PKG_VALID_EQ(obj, 2)) {
device_printf(dev, "Invalid _PCT package\n");
AcpiOsFree(obj);
return ENXIO;
}
AcpiOsFree(obj);
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(handle, "_PSS", NULL, &buf);
if (ACPI_FAILURE(status)) {
device_printf(dev, "Can't get _PSS package - %s\n",
AcpiFormatException(status));
return ENXIO;
}
obj = (ACPI_OBJECT *)buf.Pointer;
if (!ACPI_PKG_VALID(obj, 1)) {
device_printf(dev, "Invalid _PSS package\n");
AcpiOsFree(obj);
return ENXIO;
}
AcpiOsFree(obj);
done:
device_set_desc(dev, "ACPI CPU P-State");
return 0;
}
static int
acpi_pst_attach(device_t dev)
{
struct acpi_pst_softc *sc = device_get_softc(dev);
struct acpi_pst_domain *dom = NULL;
ACPI_BUFFER buf;
ACPI_STATUS status;
ACPI_OBJECT *obj;
struct acpi_pstate *pstate, *p;
int i, npstate, error, sstart, scount;
sc->pst_dev = dev;
sc->pst_parent = device_get_softc(device_get_parent(dev));
sc->pst_handle = acpi_get_handle(dev);
sc->pst_cpuid = acpi_get_magic(dev);
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->pst_handle, "_PSD", NULL, &buf);
if (!ACPI_FAILURE(status)) {
obj = (ACPI_OBJECT *)buf.Pointer;
if (acpi_pst_domain_id > 0) {
device_printf(dev, "Missing _PSD for certain CPUs\n");
AcpiOsFree(obj);
return ENXIO;
}
acpi_pst_domain_id = -1;
if (ACPI_PKG_VALID_EQ(obj, 1)) {
dom = acpi_pst_domain_create_pkg(dev,
&obj->Package.Elements[0]);
if (dom == NULL) {
AcpiOsFree(obj);
return ENXIO;
}
} else {
if (obj->Type != ACPI_TYPE_INTEGER) {
device_printf(dev,
"Invalid _PSD package, Type 0x%x\n",
obj->Type);
AcpiOsFree(obj);
return ENXIO;
} else {
device_printf(dev, "Integer _PSD %ju\n",
(uintmax_t)obj->Integer.Value);
dom = acpi_pst_domain_create_int(dev,
obj->Integer.Value);
if (dom == NULL) {
AcpiOsFree(obj);
return ENXIO;
}
}
}
AcpiOsFree(buf.Pointer);
} else {
if (acpi_pst_domain_id < 0) {
device_printf(dev, "Missing _PSD for cpu%d\n",
sc->pst_cpuid);
return ENXIO;
}
dom = acpi_pst_domain_alloc(acpi_pst_domain_id,
ACPI_PSD_COORD_SWANY, 1);
dom->pd_flags |= ACPI_PSTDOM_FLAG_STUB;
++acpi_pst_domain_id;
}
acpi_pst_domain_check_nproc(dev, dom);
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->pst_handle, "_PCT", NULL, &buf);
if (ACPI_FAILURE(status)) {
struct acpi_pst_softc *pst;
pst = LIST_FIRST(&dom->pd_pstlist);
if (pst == NULL) {
cpumask_t mask;
mask = get_cpumask_from_level(sc->pst_cpuid,
CORE_LEVEL);
if (CPUMASK_TESTNZERO(mask)) {
struct acpi_pst_domain *dom1;
LIST_FOREACH(dom1, &acpi_pst_domains, pd_link) {
LIST_FOREACH(pst, &dom1->pd_pstlist,
pst_link) {
if (CPUMASK_TESTBIT(mask,
pst->pst_cpuid))
break;
}
if (pst != NULL)
break;
}
if (pst != NULL && acpi_pst_ht_reuse_domain) {
device_printf(dev, "Destroy domain%u, "
"reuse domain%u\n",
dom->pd_dom, dom1->pd_dom);
LIST_REMOVE(dom, pd_link);
kfree(dom, M_DEVBUF);
dom = dom1;
acpi_pst_domain_check_nproc(dev, dom);
}
}
}
if (pst != NULL &&
pst->pst_creg.pr_res == NULL &&
pst->pst_creg.pr_rid == 0 &&
pst->pst_creg.pr_gas.SpaceId ==
ACPI_ADR_SPACE_FIXED_HARDWARE &&
pst->pst_sreg.pr_res == NULL &&
pst->pst_sreg.pr_rid == 0 &&
pst->pst_sreg.pr_gas.SpaceId ==
ACPI_ADR_SPACE_FIXED_HARDWARE) {
sc->pst_creg = pst->pst_creg;
sc->pst_sreg = pst->pst_sreg;
device_printf(dev,
"No _PCT; reuse %s control/status regs\n",
device_get_nameunit(pst->pst_dev));
goto fetch_pss;
}
device_printf(dev, "Can't get _PCT package - %s\n",
AcpiFormatException(status));
return ENXIO;
}
obj = (ACPI_OBJECT *)buf.Pointer;
if (!ACPI_PKG_VALID_EQ(obj, 2)) {
device_printf(dev, "Invalid _PCT package\n");
AcpiOsFree(obj);
return ENXIO;
}
error = acpi_pst_alloc_resource(dev, obj, 0, &sc->pst_creg);
if (error) {
AcpiOsFree(obj);
return error;
}
if (bootverbose) {
device_printf(dev, "control reg %d %jx\n",
sc->pst_creg.pr_gas.SpaceId,
(uintmax_t)sc->pst_creg.pr_gas.Address);
}
error = acpi_pst_alloc_resource(dev, obj, 1, &sc->pst_sreg);
if (error) {
AcpiOsFree(obj);
return error;
}
if (bootverbose) {
device_printf(dev, "status reg %d %jx\n",
sc->pst_sreg.pr_gas.SpaceId,
(uintmax_t)sc->pst_sreg.pr_gas.Address);
}
AcpiOsFree(obj);
fetch_pss:
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->pst_handle, "_PSS", NULL, &buf);
if (ACPI_FAILURE(status)) {
if (acpi_npstates > 0 && acpi_pstates != NULL) {
device_printf(dev, "No _PSS\n");
goto fetch_ppc;
}
device_printf(dev, "Can't get _PSS package - %s\n",
AcpiFormatException(status));
return ENXIO;
}
obj = (ACPI_OBJECT *)buf.Pointer;
if (!ACPI_PKG_VALID(obj, 1)) {
device_printf(dev, "Invalid _PSS package\n");
AcpiOsFree(obj);
return ENXIO;
}
npstate = obj->Package.Count;
if (npstate > ACPI_NPSTATE_MAX) {
device_printf(dev, "Too many P-States, %d->%d\n",
npstate, ACPI_NPSTATE_MAX);
npstate = ACPI_NPSTATE_MAX;
}
if (acpi_pstates != NULL && acpi_npstates != npstate) {
device_printf(dev, "Inconsistent # of P-States "
"cross Processor objects\n");
AcpiOsFree(obj);
return ENXIO;
}
pstate = kmalloc(sizeof(*pstate) * npstate, M_TEMP, M_WAITOK);
for (i = 0, p = pstate; i < npstate; ++i, ++p) {
ACPI_OBJECT *pkg;
uint32_t *ptr[ACPI_PSS_PX_NENTRY] = {
&p->st_freq, &p->st_power, &p->st_xsit_lat,
&p->st_bm_lat, &p->st_cval, &p->st_sval
};
int j;
pkg = &obj->Package.Elements[i];
if (!ACPI_PKG_VALID(pkg, ACPI_PSS_PX_NENTRY)) {
device_printf(dev, "Invalud _PSS P%d\n", i);
AcpiOsFree(obj);
kfree(pstate, M_TEMP);
return ENXIO;
}
for (j = 0; j < ACPI_PSS_PX_NENTRY; ++j) {
if (acpi_PkgInt32(pkg, j, ptr[j]) != 0) {
device_printf(dev, "Can't extract "
"_PSS P%d %dth entry\n", i, j);
AcpiOsFree(obj);
kfree(pstate, M_TEMP);
return ENXIO;
}
}
if (p->st_freq & 0x80000000) {
device_printf(dev, "Invalid _PSS P%d freq: 0x%08x\n",
i, p->st_freq);
AcpiOsFree(obj);
kfree(pstate, M_TEMP);
return ENXIO;
}
}
AcpiOsFree(obj);
if (acpi_pstates == NULL) {
acpi_pstates = pstate;
acpi_npstates = npstate;
pstate = NULL;
if (bootverbose) {
for (i = 0; i < acpi_npstates; ++i) {
device_printf(dev,
"freq %u, pwr %u, xlat %u, blat %u, "
"cv %08x, sv %08x\n",
acpi_pstates[i].st_freq,
acpi_pstates[i].st_power,
acpi_pstates[i].st_xsit_lat,
acpi_pstates[i].st_bm_lat,
acpi_pstates[i].st_cval,
acpi_pstates[i].st_sval);
}
}
} else {
if (memcmp(pstate, acpi_pstates,
sizeof(*pstate) * npstate) != 0) {
device_printf(dev, "Inconsistent _PSS "
"cross Processor objects\n");
#if 0
kfree(pstate, M_TEMP);
return ENXIO;
#endif
}
kfree(pstate, M_TEMP);
}
fetch_ppc:
sstart = 0;
if (acpi_pst_ppc >= 0) {
if (acpi_pst_ppc < acpi_npstates) {
if (bootverbose) {
device_printf(dev, "_PPC override %d\n",
acpi_pst_ppc);
}
sstart = acpi_pst_ppc;
goto proc_ppc;
} else {
device_printf(dev, "Invalid _PPC override %d, "
"must be less than %d\n", acpi_pst_ppc,
acpi_npstates);
}
}
error = acpi_pst_eval_ppc(sc, &sstart);
if (error && error != ENOENT)
return error;
else if (!error)
sc->pst_flags |= ACPI_PST_FLAG_PPC;
proc_ppc:
if (acpi_pstate_start < 0) {
acpi_pstate_start = sstart;
} else if (acpi_pstate_start != sstart) {
device_printf(dev, "_PPC mismatch, was %d, now %d\n",
acpi_pstate_start, sstart);
if (acpi_pstate_start < sstart) {
device_printf(dev, "_PPC %d -> %d\n",
acpi_pstate_start, sstart);
acpi_pstate_start = sstart;
}
}
scount = acpi_npstates;
if (acpi_pst_pdl >= 0) {
if (acpi_pst_pdl < acpi_npstates) {
if (bootverbose) {
device_printf(dev, "_PDL override %d\n",
acpi_pst_pdl);
}
scount = acpi_pst_pdl + 1;
goto proc_pdl;
} else {
device_printf(dev, "Invalid _PDL override %d, "
"must be less than %d\n", acpi_pst_pdl,
acpi_npstates);
}
}
error = acpi_pst_eval_pdl(sc, &scount);
if (error && error != ENOENT)
return error;
else if (!error)
sc->pst_flags |= ACPI_PST_FLAG_PDL;
proc_pdl:
if (acpi_pstate_count == 0) {
acpi_pstate_count = scount;
} else if (acpi_pstate_count != scount) {
device_printf(dev, "_PDL mismatch, was %d, now %d\n",
acpi_pstate_count, scount);
if (acpi_pstate_count > scount) {
device_printf(dev, "_PDL %d -> %d\n",
acpi_pstate_count, scount);
acpi_pstate_count = scount;
}
}
if (LIST_EMPTY(&dom->pd_pstlist) &&
(acpi_pst_force_pkg_domain ||
(cpu_vendor_id == CPU_VENDOR_INTEL &&
acpi_pst_intel_pkg_domain))) {
cpumask_t mask;
mask = get_cpumask_from_level(sc->pst_cpuid, CHIP_LEVEL);
if (CPUMASK_TESTNZERO(mask)) {
struct acpi_pst_softc *pst = NULL;
struct acpi_pst_domain *dom1;
LIST_FOREACH(dom1, &acpi_pst_domains, pd_link) {
LIST_FOREACH(pst, &dom1->pd_pstlist,
pst_link) {
if (CPUMASK_TESTBIT(mask,
pst->pst_cpuid))
break;
}
if (pst != NULL)
break;
}
if (pst != NULL &&
memcmp(&pst->pst_creg, &sc->pst_creg,
sizeof(sc->pst_creg)) == 0 &&
memcmp(&pst->pst_sreg, &sc->pst_sreg,
sizeof(sc->pst_sreg)) == 0) {
device_printf(dev, "Destroy domain%u, "
"force pkg domain%u\n",
dom->pd_dom, dom1->pd_dom);
LIST_REMOVE(dom, pd_link);
kfree(dom, M_DEVBUF);
dom = dom1;
acpi_pst_domain_check_nproc(dev, dom);
}
}
}
sc->pst_domain = dom;
LIST_INSERT_HEAD(&dom->pd_pstlist, sc, pst_link);
if (device_get_unit(dev) == 0)
AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_pst_postattach, NULL);
if (sc->pst_flags & (ACPI_PST_FLAG_PPC | ACPI_PST_FLAG_PDL))
sc->pst_parent->cpu_pst_notify = acpi_pst_notify;
return 0;
}
static struct acpi_pst_domain *
acpi_pst_domain_create_pkg(device_t dev, ACPI_OBJECT *obj)
{
struct acpi_pst_domain *dom;
uint32_t val, domain, coord, nproc;
if (!ACPI_PKG_VALID_EQ(obj, 5)) {
device_printf(dev, "Invalid _PSD package\n");
return NULL;
}
if (acpi_PkgInt32(obj, 0, &val) != 0 || val != 5) {
device_printf(dev, "Invalid _PSD NumberOfEntries\n");
return NULL;
}
if (acpi_PkgInt32(obj, 1, &val) != 0 || val != 0) {
device_printf(dev, "Invalid _PSD Revision\n");
return NULL;
}
if (acpi_PkgInt32(obj, 2, &domain) != 0 ||
acpi_PkgInt32(obj, 3, &coord) != 0 ||
acpi_PkgInt32(obj, 4, &nproc) != 0) {
device_printf(dev, "Can't extract _PSD package\n");
return NULL;
}
if (!ACPI_PSD_COORD_VALID(coord)) {
device_printf(dev, "Invalid _PSD CoordType (%#x)\n", coord);
return NULL;
}
if (nproc > MAXCPU) {
if (coord == ACPI_PSD_COORD_SWALL) {
device_printf(dev, "Unsupported _PSD NumProcessors "
"(%d)\n", nproc);
return NULL;
}
} else if (nproc == 0) {
device_printf(dev, "_PSD NumProcessors are zero\n");
return NULL;
}
dom = acpi_pst_domain_find(domain);
if (dom != NULL) {
if (dom->pd_flags & ACPI_PSTDOM_FLAG_INT) {
device_printf(dev, "Mixed Integer _PSD and "
"Package _PSD\n");
return NULL;
}
if (dom->pd_coord != coord) {
device_printf(dev, "Inconsistent _PSD coord "
"information cross Processor objects\n");
return NULL;
}
if (dom->pd_nproc != nproc) {
device_printf(dev, "Inconsistent _PSD nproc "
"information cross Processor objects\n");
}
return dom;
}
dom = acpi_pst_domain_alloc(domain, coord, nproc);
if (bootverbose) {
device_printf(dev, "create pkg domain%u, coord %#x\n",
dom->pd_dom, dom->pd_coord);
}
return dom;
}
static struct acpi_pst_domain *
acpi_pst_domain_create_int(device_t dev, uint32_t domain)
{
struct acpi_pst_domain *dom;
dom = acpi_pst_domain_find(domain);
if (dom != NULL) {
if ((dom->pd_flags & ACPI_PSTDOM_FLAG_INT) == 0) {
device_printf(dev, "Mixed Package _PSD and "
"Integer _PSD\n");
return NULL;
}
KKASSERT(dom->pd_coord == ACPI_PSD_COORD_SWALL);
dom->pd_nproc++;
return dom;
}
dom = acpi_pst_domain_alloc(domain, ACPI_PSD_COORD_SWALL, 1);
dom->pd_flags |= ACPI_PSTDOM_FLAG_INT;
if (bootverbose)
device_printf(dev, "create int domain%u\n", dom->pd_dom);
return dom;
}
static struct acpi_pst_domain *
acpi_pst_domain_find(uint32_t domain)
{
struct acpi_pst_domain *dom;
LIST_FOREACH(dom, &acpi_pst_domains, pd_link) {
if (dom->pd_flags & ACPI_PSTDOM_FLAG_STUB)
continue;
if (dom->pd_dom == domain)
return dom;
}
return NULL;
}
static struct acpi_pst_domain *
acpi_pst_domain_alloc(uint32_t domain, uint32_t coord, uint32_t nproc)
{
struct acpi_pst_domain *dom;
dom = kmalloc(sizeof(*dom), M_DEVBUF, M_WAITOK | M_ZERO);
dom->pd_dom = domain;
dom->pd_coord = coord;
dom->pd_nproc = nproc;
LIST_INIT(&dom->pd_pstlist);
lwkt_serialize_init(&dom->pd_serialize);
LIST_INSERT_HEAD(&acpi_pst_domains, dom, pd_link);
return dom;
}
static void
acpi_pst_domain_set_pstate_locked(struct acpi_pst_domain *dom, int i, int *global)
{
const struct acpi_pstate *pstate;
struct acpi_pst_softc *sc;
int done, error;
ASSERT_SERIALIZED(&dom->pd_serialize);
KKASSERT(i >= 0 && i < acpi_npstates);
pstate = &acpi_pstates[i];
done = 0;
LIST_FOREACH(sc, &dom->pd_pstlist, pst_link) {
if (!done) {
error = acpi_pst_set_pstate(sc, pstate);
if (error) {
device_printf(sc->pst_dev, "can't set "
"freq %d\n", pstate->st_freq);
}
if (dom->pd_coord == ACPI_PSD_COORD_SWANY)
done = 1;
}
sc->pst_state = i;
}
dom->pd_state = i;
if (global != NULL)
*global = i;
}
static void
acpi_pst_domain_set_pstate(struct acpi_pst_domain *dom, int i, int *global)
{
lwkt_serialize_enter(&dom->pd_serialize);
acpi_pst_domain_set_pstate_locked(dom, i, global);
lwkt_serialize_exit(&dom->pd_serialize);
}
static void
acpi_pst_global_set_pstate(int i)
{
struct acpi_pst_domain *dom;
int *global = &acpi_pst_global_state;
LIST_FOREACH(dom, &acpi_pst_domains, pd_link) {
if (dom->pd_flags & ACPI_PSTDOM_FLAG_DEAD)
continue;
acpi_pst_domain_set_pstate(dom, i, global);
global = NULL;
}
}
static void
acpi_pst_global_fixup_pstate(void)
{
struct acpi_pst_domain *dom;
int *global = &acpi_pst_global_state;
int sstart, scount;
sstart = acpi_pstate_start;
scount = acpi_pstate_count;
LIST_FOREACH(dom, &acpi_pst_domains, pd_link) {
int i = -1;
if (dom->pd_flags & ACPI_PSTDOM_FLAG_DEAD)
continue;
lwkt_serialize_enter(&dom->pd_serialize);
if (global != NULL) {
if (*global < sstart)
*global = sstart;
else if (*global >= scount)
*global = scount - 1;
global = NULL;
}
if (dom->pd_state < sstart)
i = sstart;
else if (dom->pd_state >= scount)
i = scount - 1;
if (i >= 0)
acpi_pst_domain_set_pstate_locked(dom, i, NULL);
lwkt_serialize_exit(&dom->pd_serialize);
}
}
static void
acpi_pst_postattach(void *arg __unused)
{
struct acpi_pst_domain *dom;
struct acpi_cpu_softc *cpu;
device_t *devices;
int i, ndevices, error, has_domain;
devices = NULL;
ndevices = 0;
error = devclass_get_devices(acpi_pst_devclass, &devices, &ndevices);
if (error)
return;
if (ndevices == 0)
return;
cpu = NULL;
for (i = 0; i < ndevices; ++i) {
cpu = device_get_softc(device_get_parent(devices[i]));
if (cpu->glob_sysctl_tree != NULL)
break;
}
kfree(devices, M_TEMP);
KKASSERT(cpu != NULL);
if (acpi_pst_md == NULL)
kprintf("ACPI: no P-State CPU driver\n");
has_domain = 0;
LIST_FOREACH(dom, &acpi_pst_domains, pd_link) {
struct acpi_pst_softc *sc;
char buf[32];
dom->pd_state = acpi_pstate_start;
i = 0;
LIST_FOREACH(sc, &dom->pd_pstlist, pst_link) {
sc->pst_state = acpi_pstate_start;
++i;
}
if (i != dom->pd_nproc) {
KKASSERT(i < dom->pd_nproc);
kprintf("ACPI: domain%u misses processors, "
"should be %d, got %d\n", dom->pd_dom,
dom->pd_nproc, i);
if (dom->pd_coord == ACPI_PSD_COORD_SWALL) {
dom->pd_flags |= ACPI_PSTDOM_FLAG_DEAD;
continue;
}
dom->pd_nproc = i;
}
LIST_FOREACH(sc, &dom->pd_pstlist, pst_link) {
error = acpi_pst_check_csr(sc);
if (error)
break;
error = acpi_pst_check_pstates(sc);
if (error)
break;
}
if (sc != NULL) {
kprintf("ACPI: domain%u P-State configuration "
"check failed\n", dom->pd_dom);
dom->pd_flags |= ACPI_PSTDOM_FLAG_DEAD;
continue;
}
LIST_FOREACH(sc, &dom->pd_pstlist, pst_link) {
error = acpi_pst_init(sc);
if (error)
break;
}
if (sc != NULL) {
kprintf("ACPI: domain%u P-State initialization "
"check failed\n", dom->pd_dom);
dom->pd_flags |= ACPI_PSTDOM_FLAG_DEAD;
continue;
}
has_domain = 1;
ksnprintf(buf, sizeof(buf), "px_dom%u", dom->pd_dom);
sysctl_ctx_init(&dom->pd_sysctl_ctx);
dom->pd_sysctl_tree =
SYSCTL_ADD_NODE(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(cpu->glob_sysctl_tree),
OID_AUTO, buf, CTLFLAG_RD, 0,
"P-State domain");
if (dom->pd_sysctl_tree == NULL) {
kprintf("ACPI: Can't create sysctl tree for domain%u",
dom->pd_dom);
continue;
}
SYSCTL_ADD_PROC(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(dom->pd_sysctl_tree),
OID_AUTO, "available",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP,
dom, 0, acpi_pst_sysctl_freqs, "A",
"available frequencies");
SYSCTL_ADD_PROC(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(dom->pd_sysctl_tree),
OID_AUTO, "avail",
CTLTYPE_OPAQUE | CTLFLAG_RD,
dom, 0, acpi_pst_sysctl_freqs_bin, "IU",
"available frequencies");
SYSCTL_ADD_PROC(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(dom->pd_sysctl_tree),
OID_AUTO, "power",
CTLTYPE_OPAQUE | CTLFLAG_RD,
dom, 0, acpi_pst_sysctl_power, "IU",
"power of available frequencies");
SYSCTL_ADD_PROC(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(dom->pd_sysctl_tree),
OID_AUTO, "members",
CTLTYPE_STRING | CTLFLAG_RD,
dom, 0, acpi_pst_sysctl_members, "A",
"member cpus");
if (acpi_pst_md != NULL &&
acpi_pst_md->pmd_set_pstate != NULL) {
SYSCTL_ADD_PROC(&dom->pd_sysctl_ctx,
SYSCTL_CHILDREN(dom->pd_sysctl_tree),
OID_AUTO, "select",
CTLTYPE_UINT | CTLFLAG_RW,
dom, 0, acpi_pst_sysctl_select,
"IU", "select freq");
}
}
if (has_domain && acpi_pst_md != NULL &&
acpi_pst_md->pmd_set_pstate != NULL) {
SYSCTL_ADD_PROC(&cpu->glob_sysctl_ctx,
SYSCTL_CHILDREN(cpu->glob_sysctl_tree),
OID_AUTO, "px_global",
CTLTYPE_UINT | CTLFLAG_RW,
NULL, 0, acpi_pst_sysctl_global,
"IU", "select freq for all domains");
SYSCTL_ADD_INT(&cpu->glob_sysctl_ctx,
SYSCTL_CHILDREN(cpu->glob_sysctl_tree),
OID_AUTO, "px_handle_notify", CTLFLAG_RW,
&acpi_pst_handle_notify, 0,
"handle type 0x80 notify");
acpi_pst_global_set_pstate(acpi_pstate_start);
}
}
static int
acpi_pst_sysctl_freqs(SYSCTL_HANDLER_ARGS)
{
int i, error, sstart, scount;
error = 0;
sstart = acpi_pstate_start;
scount = acpi_pstate_count;
for (i = 0; i < acpi_npstates; ++i) {
if (error == 0 && i)
error = SYSCTL_OUT(req, " ", 1);
if (error == 0) {
const char *pat;
char buf[32];
if (i < sstart || i >= scount)
pat = "(%u)";
else
pat = "%u";
ksnprintf(buf, sizeof(buf), pat,
acpi_pstates[i].st_freq);
error = SYSCTL_OUT(req, buf, strlen(buf));
}
}
return error;
}
static int
acpi_pst_sysctl_freqs_bin(SYSCTL_HANDLER_ARGS)
{
uint32_t freqs[ACPI_NPSTATE_MAX];
int cnt, i, sstart, scount;
sstart = acpi_pstate_start;
scount = acpi_pstate_count;
cnt = scount - sstart;
for (i = 0; i < cnt; ++i)
freqs[i] = acpi_pstates[sstart + i].st_freq;
return sysctl_handle_opaque(oidp, freqs, cnt * sizeof(freqs[0]), req);
}
static int
acpi_pst_sysctl_power(SYSCTL_HANDLER_ARGS)
{
uint32_t power[ACPI_NPSTATE_MAX];
int cnt, i, sstart, scount;
sstart = acpi_pstate_start;
scount = acpi_pstate_count;
cnt = scount - sstart;
for (i = 0; i < cnt; ++i)
power[i] = acpi_pstates[sstart + i].st_power;
return sysctl_handle_opaque(oidp, power, cnt * sizeof(power[0]), req);
}
static int
acpi_pst_sysctl_members(SYSCTL_HANDLER_ARGS)
{
struct acpi_pst_domain *dom = arg1;
struct acpi_pst_softc *sc;
int loop, error;
loop = error = 0;
LIST_FOREACH(sc, &dom->pd_pstlist, pst_link) {
char buf[32];
if (error == 0 && loop)
error = SYSCTL_OUT(req, " ", 1);
if (error == 0) {
ksnprintf(buf, sizeof(buf), "cpu%d", sc->pst_cpuid);
error = SYSCTL_OUT(req, buf, strlen(buf));
}
if (error == 0 && acpi_pst_md && acpi_pst_md->pmd_get_pstate) {
const struct acpi_pstate *pstate;
const char *str;
pstate = acpi_pst_get_pstate(sc);
if (pstate == NULL) {
str = "(*)";
} else {
ksnprintf(buf, sizeof(buf), "(%d)",
pstate->st_freq);
str = buf;
}
error = SYSCTL_OUT(req, str, strlen(str));
}
++loop;
}
return error;
}
static int
acpi_pst_sysctl_select(SYSCTL_HANDLER_ARGS)
{
struct acpi_pst_domain *dom = arg1;
int error, i, freq;
KKASSERT(dom->pd_state >= 0 && dom->pd_state < acpi_npstates);
freq = acpi_pstates[dom->pd_state].st_freq;
error = sysctl_handle_int(oidp, &freq, 0, req);
if (error || req->newptr == NULL)
return error;
i = acpi_pst_freq2index(freq);
if (i < 0)
return EINVAL;
acpi_pst_domain_set_pstate(dom, i, NULL);
return 0;
}
static int
acpi_pst_sysctl_global(SYSCTL_HANDLER_ARGS)
{
int error, i, freq;
KKASSERT(acpi_pst_global_state >= 0 &&
acpi_pst_global_state < acpi_npstates);
freq = acpi_pstates[acpi_pst_global_state].st_freq;
error = sysctl_handle_int(oidp, &freq, 0, req);
if (error || req->newptr == NULL)
return error;
i = acpi_pst_freq2index(freq);
if (i < 0)
return EINVAL;
acpi_pst_global_set_pstate(i);
return 0;
}
static void
acpi_pst_check_csr_handler(struct cpuhelper_msg *msg)
{
struct acpi_pst_chmsg *pmsg = (struct acpi_pst_chmsg *)msg;
int error;
error = acpi_pst_md->pmd_check_csr(pmsg->ch_ctrl, pmsg->ch_status);
cpuhelper_replymsg(msg, error);
}
static int
acpi_pst_check_csr(struct acpi_pst_softc *sc)
{
struct acpi_pst_chmsg msg;
if (acpi_pst_md == NULL)
return 0;
cpuhelper_initmsg(&msg.ch_msg, &curthread->td_msgport,
acpi_pst_check_csr_handler, NULL, MSGF_PRIORITY);
msg.ch_ctrl = &sc->pst_creg;
msg.ch_status = &sc->pst_sreg;
return (cpuhelper_domsg(&msg.ch_msg, sc->pst_cpuid));
}
static void
acpi_pst_check_pstates_handler(struct cpuhelper_msg *msg)
{
int error;
error = acpi_pst_md->pmd_check_pstates(acpi_pstates, acpi_npstates);
cpuhelper_replymsg(msg, error);
}
static int
acpi_pst_check_pstates(struct acpi_pst_softc *sc)
{
struct cpuhelper_msg msg;
if (acpi_pst_md == NULL)
return 0;
cpuhelper_initmsg(&msg, &curthread->td_msgport,
acpi_pst_check_pstates_handler, NULL, MSGF_PRIORITY);
return (cpuhelper_domsg(&msg, sc->pst_cpuid));
}
static void
acpi_pst_init_handler(struct cpuhelper_msg *msg)
{
struct acpi_pst_chmsg *pmsg = (struct acpi_pst_chmsg *)msg;
int error;
error = acpi_pst_md->pmd_init(pmsg->ch_ctrl, pmsg->ch_status);
cpuhelper_replymsg(msg, error);
}
static int
acpi_pst_init(struct acpi_pst_softc *sc)
{
struct acpi_pst_chmsg msg;
if (acpi_pst_md == NULL)
return 0;
cpuhelper_initmsg(&msg.ch_msg, &curthread->td_msgport,
acpi_pst_init_handler, NULL, MSGF_PRIORITY);
msg.ch_ctrl = &sc->pst_creg;
msg.ch_status = &sc->pst_sreg;
return (cpuhelper_domsg(&msg.ch_msg, sc->pst_cpuid));
}
static void
acpi_pst_set_pstate_handler(struct cpuhelper_msg *msg)
{
struct acpi_pst_chmsg *pmsg = (struct acpi_pst_chmsg *)msg;
int error;
error = acpi_pst_md->pmd_set_pstate(pmsg->ch_ctrl, pmsg->ch_status,
msg->ch_cbarg);
cpuhelper_replymsg(msg, error);
}
static int
acpi_pst_set_pstate(struct acpi_pst_softc *sc, const struct acpi_pstate *pstate)
{
struct acpi_pst_chmsg msg;
KKASSERT(acpi_pst_md != NULL);
cpuhelper_initmsg(&msg.ch_msg, &curthread->td_msgport,
acpi_pst_set_pstate_handler, __DECONST(void *, pstate),
MSGF_PRIORITY);
msg.ch_ctrl = &sc->pst_creg;
msg.ch_status = &sc->pst_sreg;
return (cpuhelper_domsg(&msg.ch_msg, sc->pst_cpuid));
}
static void
acpi_pst_get_pstate_handler(struct cpuhelper_msg *msg)
{
struct acpi_pst_chmsg *pmsg = (struct acpi_pst_chmsg *)msg;
const struct acpi_pstate *pstate;
pstate = acpi_pst_md->pmd_get_pstate(pmsg->ch_status, acpi_pstates,
acpi_npstates);
msg->ch_cbarg = __DECONST(void *, pstate);
cpuhelper_replymsg(msg, 0);
}
static const struct acpi_pstate *
acpi_pst_get_pstate(struct acpi_pst_softc *sc)
{
struct acpi_pst_chmsg msg;
if (acpi_pst_md == NULL)
return 0;
cpuhelper_initmsg(&msg.ch_msg, &curthread->td_msgport,
acpi_pst_get_pstate_handler, NULL, MSGF_PRIORITY);
msg.ch_status = &sc->pst_sreg;
cpuhelper_domsg(&msg.ch_msg, sc->pst_cpuid);
return (msg.ch_msg.ch_cbarg);
}
static int
acpi_pst_alloc_resource(device_t dev, ACPI_OBJECT *obj, int idx,
struct acpi_pst_res *res)
{
struct acpi_pst_softc *sc = device_get_softc(dev);
int error, type;
error = acpi_PkgRawGas(obj, idx, &res->pr_gas);
if (error)
return error;
res->pr_rid = sc->pst_parent->cpu_next_rid;
acpi_bus_alloc_gas(dev, &type, &res->pr_rid, &res->pr_gas, &res->pr_res, 0);
if (res->pr_res != NULL) {
sc->pst_parent->cpu_next_rid++;
res->pr_bt = rman_get_bustag(res->pr_res);
res->pr_bh = rman_get_bushandle(res->pr_res);
} else {
res->pr_rid = 0;
}
return 0;
}
static void
acpi_pst_domain_check_nproc(device_t dev, struct acpi_pst_domain *dom)
{
struct acpi_pst_softc *pst;
int i;
i = 0;
LIST_FOREACH(pst, &dom->pd_pstlist, pst_link)
++i;
if (i == dom->pd_nproc) {
if (bootverbose) {
device_printf(dev, "domain%u already contains %d "
"P-States\n", dom->pd_dom, dom->pd_nproc);
}
dom->pd_nproc++;
}
KKASSERT(i < dom->pd_nproc);
}
static int
acpi_pst_eval_ppc(struct acpi_pst_softc *sc, int *sstart)
{
ACPI_BUFFER buf;
ACPI_STATUS status;
ACPI_OBJECT *obj;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->pst_handle, "_PPC", NULL, &buf);
if (!ACPI_FAILURE(status)) {
ACPI_OBJECT_LIST arglist;
ACPI_OBJECT arg[2];
obj = (ACPI_OBJECT *)buf.Pointer;
if (obj->Type == ACPI_TYPE_INTEGER) {
if (obj->Integer.Value >= acpi_npstates) {
if (bootverbose) {
device_printf(sc->pst_dev,
"Invalid _PPC value %ju, "
"npstates %d\n",
(uintmax_t)obj->Integer.Value,
acpi_npstates);
}
*sstart = 0;
} else {
*sstart = obj->Integer.Value;
if (bootverbose) {
device_printf(sc->pst_dev, "_PPC %d\n",
*sstart);
}
}
} else {
device_printf(sc->pst_dev, "Invalid _PPC object\n");
AcpiOsFree(obj);
return ENXIO;
}
AcpiOsFree(obj);
arglist.Pointer = arg;
arglist.Count = 2;
arg[0].Type = ACPI_TYPE_INTEGER;
arg[0].Integer.Value = 0x80;
arg[1].Type = ACPI_TYPE_INTEGER;
arg[1].Integer.Value = 0;
AcpiEvaluateObject(sc->pst_handle, "_OST", &arglist, NULL);
return 0;
}
return ENOENT;
}
static int
acpi_pst_eval_pdl(struct acpi_pst_softc *sc, int *scount)
{
ACPI_BUFFER buf;
ACPI_STATUS status;
ACPI_OBJECT *obj;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->pst_handle, "_PDL", NULL, &buf);
if (!ACPI_FAILURE(status)) {
obj = (ACPI_OBJECT *)buf.Pointer;
if (obj->Type == ACPI_TYPE_INTEGER) {
if (obj->Integer.Value >= acpi_npstates) {
device_printf(sc->pst_dev,
"Invalid _PDL value\n");
AcpiOsFree(obj);
return ENXIO;
}
if (obj->Integer.Value >= acpi_pstate_start) {
*scount = obj->Integer.Value + 1;
if (bootverbose) {
device_printf(sc->pst_dev, "_PDL %d\n",
*scount);
}
} else {
device_printf(sc->pst_dev, "conflict _PDL %ju "
"and _PPC %d, ignore\n",
(uintmax_t)obj->Integer.Value,
acpi_pstate_start);
}
} else {
device_printf(sc->pst_dev, "Invalid _PDL object\n");
AcpiOsFree(obj);
return ENXIO;
}
AcpiOsFree(obj);
return 0;
}
return ENOENT;
}
static void
acpi_pst_notify(device_t dev)
{
struct acpi_pst_softc *sc = device_get_softc(dev);
boolean_t fixup = FALSE;
if (!acpi_pst_handle_notify)
return;
if (sc->pst_flags & ACPI_PST_FLAG_PPC) {
int sstart = acpi_pstate_start;
acpi_pst_eval_ppc(sc, &sstart);
if (acpi_pstate_start != sstart && sc->pst_cpuid == 0) {
acpi_pstate_start = sstart;
fixup = TRUE;
}
}
if (sc->pst_flags & ACPI_PST_FLAG_PDL) {
int scount = acpi_pstate_count;
acpi_pst_eval_pdl(sc, &scount);
if (acpi_pstate_count != scount && sc->pst_cpuid == 0) {
acpi_pstate_count = scount;
fixup = TRUE;
}
}
if (fixup && acpi_pst_md != NULL &&
acpi_pst_md->pmd_set_pstate != NULL)
acpi_pst_global_fixup_pstate();
}