#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/globaldata.h>
#include <machine/md_var.h>
#include <machine/cpufunc.h>
#include <machine/cpufreq.h>
#include <machine/cputypes.h>
#include <machine/specialreg.h>
#include "acpi.h"
#include "acpi_cpu_pstate.h"
#define AMD_APMI_HWPSTATE 0x80
#define AMD_MSR_PSTATE_CSR_MASK 0x7ULL
#define AMD1X_MSR_PSTATE_CTL 0xc0010062
#define AMD1X_MSR_PSTATE_ST 0xc0010063
#define AMD_MSR_PSTATE_EN 0x8000000000000000ULL
#define AMD1X_MSR_PSTATE_START 0xc0010064
#define AMD10_MSR_PSTATE_COUNT 5
#define AMD11_MSR_PSTATE_COUNT 8
#define AMD0F_PST_CTL_FID(cval) (((cval) >> 0) & 0x3f)
#define AMD0F_PST_CTL_VID(cval) (((cval) >> 6) & 0x1f)
#define AMD0F_PST_CTL_VST(cval) (((cval) >> 11) & 0x7f)
#define AMD0F_PST_CTL_MVS(cval) (((cval) >> 18) & 0x3)
#define AMD0F_PST_CTL_PLLTIME(cval) (((cval) >> 20) & 0x7f)
#define AMD0F_PST_CTL_RVO(cval) (((cval) >> 28) & 0x3)
#define AMD0F_PST_CTL_IRT(cval) (((cval) >> 30) & 0x3)
#define AMD0F_PST_ST_FID(sval) (((sval) >> 0) & 0x3f)
#define AMD0F_PST_ST_VID(sval) (((sval) >> 6) & 0x3f)
#define INTEL_MSR_MISC_ENABLE 0x1a0
#define INTEL_MSR_MISC_EST_EN 0x10000ULL
#define INTEL_MSR_PERF_STATUS 0x198
#define INTEL_MSR_PERF_CTL 0x199
#define INTEL_MSR_PERF_MASK 0xffffULL
static const struct acpi_pst_md *
acpi_pst_amd_probe(void);
static int acpi_pst_amd_check_csr(const struct acpi_pst_res *,
const struct acpi_pst_res *);
static int acpi_pst_amd1x_check_pstates1(const struct acpi_pstate *, int,
uint32_t, uint32_t);
static int acpi_pst_amd1x_check_pstates(const struct acpi_pstate *, int);
static int acpi_pst_amd0f_check_pstates(const struct acpi_pstate *, int);
static int acpi_pst_amd_init(const struct acpi_pst_res *,
const struct acpi_pst_res *);
static int acpi_pst_amd1x_set_pstate(const struct acpi_pst_res *,
const struct acpi_pst_res *, const struct acpi_pstate *);
static int acpi_pst_amd0f_set_pstate(const struct acpi_pst_res *,
const struct acpi_pst_res *, const struct acpi_pstate *);
static const struct acpi_pstate *
acpi_pst_amd1x_get_pstate(const struct acpi_pst_res *,
const struct acpi_pstate *, int);
static const struct acpi_pstate *
acpi_pst_amd0f_get_pstate(const struct acpi_pst_res *,
const struct acpi_pstate *, int);
static const struct acpi_pst_md *
acpi_pst_intel_probe(void);
static int acpi_pst_intel_check_csr(const struct acpi_pst_res *,
const struct acpi_pst_res *);
static int acpi_pst_intel_check_pstates(const struct acpi_pstate *, int);
static int acpi_pst_intel_init(const struct acpi_pst_res *,
const struct acpi_pst_res *);
static int acpi_pst_intel_set_pstate(const struct acpi_pst_res *,
const struct acpi_pst_res *, const struct acpi_pstate *);
static const struct acpi_pstate *
acpi_pst_intel_get_pstate(const struct acpi_pst_res *,
const struct acpi_pstate *, int);
static int acpi_pst_md_gas_asz(const ACPI_GENERIC_ADDRESS *);
static int acpi_pst_md_gas_verify(const ACPI_GENERIC_ADDRESS *);
static uint32_t acpi_pst_md_res_read(const struct acpi_pst_res *);
static void acpi_pst_md_res_write(const struct acpi_pst_res *, uint32_t);
static const struct acpi_pst_md acpi_pst_amd1x = {
.pmd_check_csr = acpi_pst_amd_check_csr,
.pmd_check_pstates = acpi_pst_amd1x_check_pstates,
.pmd_init = acpi_pst_amd_init,
.pmd_set_pstate = acpi_pst_amd1x_set_pstate,
.pmd_get_pstate = acpi_pst_amd1x_get_pstate
};
static const struct acpi_pst_md acpi_pst_amd0f = {
.pmd_check_csr = acpi_pst_amd_check_csr,
.pmd_check_pstates = acpi_pst_amd0f_check_pstates,
.pmd_init = acpi_pst_amd_init,
.pmd_set_pstate = acpi_pst_amd0f_set_pstate,
.pmd_get_pstate = acpi_pst_amd0f_get_pstate
};
static const struct acpi_pst_md acpi_pst_intel = {
.pmd_check_csr = acpi_pst_intel_check_csr,
.pmd_check_pstates = acpi_pst_intel_check_pstates,
.pmd_init = acpi_pst_intel_init,
.pmd_set_pstate = acpi_pst_intel_set_pstate,
.pmd_get_pstate = acpi_pst_intel_get_pstate
};
static int acpi_pst_stringent_check = 1;
TUNABLE_INT("hw.acpi.cpu.pstate.strigent_check", &acpi_pst_stringent_check);
static int acpi_pst_amd1x_msr_pstate_count = AMD10_MSR_PSTATE_COUNT;
const struct acpi_pst_md *
acpi_pst_md_probe(void)
{
if (cpu_vendor_id == CPU_VENDOR_AMD)
return acpi_pst_amd_probe();
else if (cpu_vendor_id == CPU_VENDOR_INTEL)
return acpi_pst_intel_probe();
return NULL;
}
static const struct acpi_pst_md *
acpi_pst_amd_probe(void)
{
uint32_t regs[4];
if (CPUID_TO_FAMILY(cpu_id) < 0xf)
return NULL;
if (cpu_exthigh < 0x80000007)
return NULL;
do_cpuid(0x80000007, regs);
if (CPUID_TO_FAMILY(cpu_id) == 0xf) {
if ((regs[3] & 0x06) == 0x06)
return &acpi_pst_amd0f;
} else if (CPUID_TO_FAMILY(cpu_id) >= 0x10) {
if (CPUID_TO_FAMILY(cpu_id) >= 0x11) {
acpi_pst_amd1x_msr_pstate_count =
AMD11_MSR_PSTATE_COUNT;
}
if (regs[3] & 0x80)
return &acpi_pst_amd1x;
}
return NULL;
}
static int
acpi_pst_amd_check_csr(const struct acpi_pst_res *ctrl,
const struct acpi_pst_res *status)
{
if (ctrl->pr_gas.SpaceId != ACPI_ADR_SPACE_FIXED_HARDWARE) {
kprintf("cpu%d: Invalid P-State control register\n", mycpuid);
return EINVAL;
}
if (status->pr_gas.SpaceId != ACPI_ADR_SPACE_FIXED_HARDWARE) {
kprintf("cpu%d: Invalid P-State status register\n", mycpuid);
return EINVAL;
}
return 0;
}
static int
acpi_pst_amd1x_check_pstates1(const struct acpi_pstate *pstates, int npstates,
uint32_t msr_start, uint32_t msr_end)
{
int i;
for (i = 0; i < npstates; ++i) {
uint64_t pstate;
uint32_t msr;
msr = msr_start +
(pstates[i].st_cval & AMD_MSR_PSTATE_CSR_MASK);
if (msr >= msr_end) {
kprintf("cpu%d: MSR P-State register %#08x "
"does not exist\n", mycpuid, msr);
return EINVAL;
}
pstate = rdmsr(msr);
if ((pstate & AMD_MSR_PSTATE_EN) == 0) {
kprintf("cpu%d: MSR P-State register %#08x "
"is not enabled\n", mycpuid, msr);
return EINVAL;
}
}
return 0;
}
static int
acpi_pst_amd1x_check_pstates(const struct acpi_pstate *pstates, int npstates)
{
if (npstates > acpi_pst_amd1x_msr_pstate_count) {
kprintf("cpu%d: only %d P-states are allowed\n", mycpuid,
acpi_pst_amd1x_msr_pstate_count);
return EINVAL;
}
return acpi_pst_amd1x_check_pstates1(pstates, npstates,
AMD1X_MSR_PSTATE_START,
AMD1X_MSR_PSTATE_START + acpi_pst_amd1x_msr_pstate_count);
}
static int
acpi_pst_amd1x_set_pstate(const struct acpi_pst_res *ctrl __unused,
const struct acpi_pst_res *status __unused,
const struct acpi_pstate *pstate)
{
uint64_t cval;
cval = pstate->st_cval & AMD_MSR_PSTATE_CSR_MASK;
wrmsr(AMD1X_MSR_PSTATE_CTL, cval);
return 0;
}
static const struct acpi_pstate *
acpi_pst_amd1x_get_pstate(const struct acpi_pst_res *status __unused,
const struct acpi_pstate *pstates, int npstates)
{
uint64_t sval;
int i;
sval = rdmsr(AMD1X_MSR_PSTATE_ST) & AMD_MSR_PSTATE_CSR_MASK;
for (i = 0; i < npstates; ++i) {
if ((pstates[i].st_sval & AMD_MSR_PSTATE_CSR_MASK) == sval)
return &pstates[i];
}
return NULL;
}
static int
acpi_pst_amd0f_check_pstates(const struct acpi_pstate *pstates, int npstates)
{
struct amd0f_fidvid fv_max, fv_min;
int i;
amd0f_fidvid_limit(&fv_min, &fv_max);
if (fv_min.fid == fv_max.fid && fv_min.vid == fv_max.vid) {
kprintf("cpu%d: only one P-State is supported\n", mycpuid);
if (acpi_pst_stringent_check)
return EOPNOTSUPP;
}
for (i = 0; i < npstates; ++i) {
const struct acpi_pstate *p = &pstates[i];
uint32_t fid, vid, mvs, rvo;
int mvs_mv, rvo_mv;
fid = AMD0F_PST_CTL_FID(p->st_cval);
vid = AMD0F_PST_CTL_VID(p->st_cval);
if (i == 0) {
if (vid != fv_max.vid) {
kprintf("cpu%d: max VID mismatch "
"real %u, lim %d\n", mycpuid,
vid, fv_max.vid);
}
if (fid != fv_max.fid) {
kprintf("cpu%d: max FID mismatch "
"real %u, lim %d\n", mycpuid,
fid, fv_max.fid);
}
} else if (i == npstates - 1) {
if (vid != fv_min.vid) {
kprintf("cpu%d: min VID mismatch "
"real %u, lim %d\n", mycpuid,
vid, fv_min.vid);
}
if (fid != fv_min.fid) {
kprintf("cpu%d: min FID mismatch "
"real %u, lim %d\n", mycpuid,
fid, fv_min.fid);
}
} else {
if (fid >= fv_max.fid || fid < (fv_min.fid + 0x8)) {
kprintf("cpu%d: Invalid FID %#x, "
"out [%#x, %#x]\n", mycpuid, fid,
fv_min.fid + 0x8, fv_max.fid);
if (acpi_pst_stringent_check)
return EINVAL;
}
if (vid < fv_max.vid || vid > fv_min.vid) {
kprintf("cpu%d: Invalid VID %#x, "
"in [%#x, %#x]\n", mycpuid, vid,
fv_max.vid, fv_min.vid);
if (acpi_pst_stringent_check)
return EINVAL;
}
}
mvs = AMD0F_PST_CTL_MVS(p->st_cval);
rvo = AMD0F_PST_CTL_RVO(p->st_cval);
if (mvs != 0) {
kprintf("cpu%d: Invalid MVS %#x\n", mycpuid, mvs);
return EINVAL;
}
mvs_mv = 25 * (1 << mvs);
rvo_mv = 25 * rvo;
if (rvo_mv % mvs_mv != 0) {
kprintf("cpu%d: Invalid MVS/RVO (%#x/%#x)\n",
mycpuid, mvs, rvo);
return EINVAL;
}
}
return 0;
}
static int
acpi_pst_amd0f_set_pstate(const struct acpi_pst_res *ctrl __unused,
const struct acpi_pst_res *status __unused,
const struct acpi_pstate *pstate)
{
struct amd0f_fidvid fv;
struct amd0f_xsit xsit;
fv.fid = AMD0F_PST_CTL_FID(pstate->st_cval);
fv.vid = AMD0F_PST_CTL_VID(pstate->st_cval);
xsit.rvo = AMD0F_PST_CTL_RVO(pstate->st_cval);
xsit.mvs = AMD0F_PST_CTL_MVS(pstate->st_cval);
xsit.vst = AMD0F_PST_CTL_VST(pstate->st_cval);
xsit.pll_time = AMD0F_PST_CTL_PLLTIME(pstate->st_cval);
xsit.irt = AMD0F_PST_CTL_IRT(pstate->st_cval);
return amd0f_set_fidvid(&fv, &xsit);
}
static const struct acpi_pstate *
acpi_pst_amd0f_get_pstate(const struct acpi_pst_res *status __unused,
const struct acpi_pstate *pstates, int npstates)
{
struct amd0f_fidvid fv;
int error, i;
error = amd0f_get_fidvid(&fv);
if (error)
return NULL;
for (i = 0; i < npstates; ++i) {
const struct acpi_pstate *p = &pstates[i];
if (fv.fid == AMD0F_PST_ST_FID(p->st_sval) &&
fv.vid == AMD0F_PST_ST_VID(p->st_sval))
return p;
}
return NULL;
}
static int
acpi_pst_amd_init(const struct acpi_pst_res *ctrl __unused,
const struct acpi_pst_res *status __unused)
{
return 0;
}
static const struct acpi_pst_md *
acpi_pst_intel_probe(void)
{
if ((cpu_feature2 & CPUID2_EST) == 0)
return NULL;
if (CPUID_TO_FAMILY(cpu_id) >= 0xf || CPUID_TO_FAMILY(cpu_id) == 0x6)
return &acpi_pst_intel;
return NULL;
}
static int
acpi_pst_intel_check_csr(const struct acpi_pst_res *ctrl,
const struct acpi_pst_res *status)
{
int error;
if (ctrl->pr_gas.SpaceId != status->pr_gas.SpaceId) {
kprintf("cpu%d: P-State control(%d)/status(%d) registers have "
"different SpaceId", mycpuid,
ctrl->pr_gas.SpaceId, status->pr_gas.SpaceId);
return EINVAL;
}
switch (ctrl->pr_gas.SpaceId) {
case ACPI_ADR_SPACE_FIXED_HARDWARE:
if (ctrl->pr_res != NULL || status->pr_res != NULL) {
kprintf("cpu%d: Allocated resource for fixed hardware "
"registers\n", mycpuid);
return EINVAL;
}
break;
case ACPI_ADR_SPACE_SYSTEM_IO:
if (ctrl->pr_res == NULL) {
kprintf("cpu%d: ioport allocation failed for control "
"register\n", mycpuid);
return ENXIO;
}
error = acpi_pst_md_gas_verify(&ctrl->pr_gas);
if (error) {
kprintf("cpu%d: Invalid control register GAS\n",
mycpuid);
return error;
}
if (status->pr_res == NULL) {
kprintf("cpu%d: ioport allocation failed for status "
"register\n", mycpuid);
return ENXIO;
}
error = acpi_pst_md_gas_verify(&status->pr_gas);
if (error) {
kprintf("cpu%d: Invalid status register GAS\n",
mycpuid);
return error;
}
break;
default:
kprintf("cpu%d: Invalid P-State control/status register "
"SpaceId %d\n", mycpuid, ctrl->pr_gas.SpaceId);
return EOPNOTSUPP;
}
return 0;
}
static int
acpi_pst_intel_check_pstates(const struct acpi_pstate *pstates __unused,
int npstates __unused)
{
return 0;
}
static int
acpi_pst_intel_init(const struct acpi_pst_res *ctrl __unused,
const struct acpi_pst_res *status __unused)
{
uint64_t misc_enable;
if (CPUID_TO_FAMILY(cpu_id) == 0xf ||
(CPUID_TO_FAMILY(cpu_id) == 0x6 && CPUID_TO_MODEL(cpu_id) < 0xd)) {
return 0;
}
misc_enable = rdmsr(INTEL_MSR_MISC_ENABLE);
if ((misc_enable & INTEL_MSR_MISC_EST_EN) == 0) {
misc_enable |= INTEL_MSR_MISC_EST_EN;
wrmsr(INTEL_MSR_MISC_ENABLE, misc_enable);
misc_enable = rdmsr(INTEL_MSR_MISC_ENABLE);
if ((misc_enable & INTEL_MSR_MISC_EST_EN) == 0) {
kprintf("cpu%d: Can't enable EST\n", mycpuid);
return EIO;
}
}
return 0;
}
static int
acpi_pst_intel_set_pstate(const struct acpi_pst_res *ctrl,
const struct acpi_pst_res *status __unused,
const struct acpi_pstate *pstate)
{
if (ctrl->pr_res != NULL) {
acpi_pst_md_res_write(ctrl, pstate->st_cval);
} else {
uint64_t ctl;
ctl = rdmsr(INTEL_MSR_PERF_CTL);
ctl &= ~INTEL_MSR_PERF_MASK;
ctl |= (pstate->st_cval & INTEL_MSR_PERF_MASK);
wrmsr(INTEL_MSR_PERF_CTL, ctl);
}
return 0;
}
static const struct acpi_pstate *
acpi_pst_intel_get_pstate(const struct acpi_pst_res *status,
const struct acpi_pstate *pstates, int npstates)
{
int i;
if (status->pr_res != NULL) {
uint32_t st;
st = acpi_pst_md_res_read(status);
for (i = 0; i < npstates; ++i) {
if (pstates[i].st_sval == st)
return &pstates[i];
}
} else {
uint64_t sval;
sval = rdmsr(INTEL_MSR_PERF_STATUS) & INTEL_MSR_PERF_MASK;
for (i = 0; i < npstates; ++i) {
if ((pstates[i].st_sval & INTEL_MSR_PERF_MASK) == sval)
return &pstates[i];
}
}
return NULL;
}
static int
acpi_pst_md_gas_asz(const ACPI_GENERIC_ADDRESS *gas)
{
int asz;
if (gas->AccessWidth != 0)
asz = gas->AccessWidth;
else
asz = gas->BitWidth / NBBY;
switch (asz) {
case 1:
case 2:
case 4:
break;
default:
asz = 0;
break;
}
return asz;
}
static int
acpi_pst_md_gas_verify(const ACPI_GENERIC_ADDRESS *gas)
{
int reg, end, asz;
if (gas->BitOffset % NBBY != 0)
return EINVAL;
end = gas->BitWidth / NBBY;
reg = gas->BitOffset / NBBY;
if (reg >= end)
return EINVAL;
asz = acpi_pst_md_gas_asz(gas);
if (asz == 0)
return EINVAL;
if (reg + asz > end)
return EINVAL;
return 0;
}
static uint32_t
acpi_pst_md_res_read(const struct acpi_pst_res *res)
{
int asz, reg;
KKASSERT(res->pr_res != NULL);
asz = acpi_pst_md_gas_asz(&res->pr_gas);
reg = res->pr_gas.BitOffset / NBBY;
switch (asz) {
case 1:
return bus_space_read_1(res->pr_bt, res->pr_bh, reg);
case 2:
return bus_space_read_2(res->pr_bt, res->pr_bh, reg);
case 4:
return bus_space_read_4(res->pr_bt, res->pr_bh, reg);
}
panic("unsupported access width %d", asz);
return 0;
}
static void
acpi_pst_md_res_write(const struct acpi_pst_res *res, uint32_t val)
{
int asz, reg;
KKASSERT(res->pr_res != NULL);
asz = acpi_pst_md_gas_asz(&res->pr_gas);
reg = res->pr_gas.BitOffset / NBBY;
switch (asz) {
case 1:
bus_space_write_1(res->pr_bt, res->pr_bh, reg, val);
break;
case 2:
bus_space_write_2(res->pr_bt, res->pr_bh, reg, val);
break;
case 4:
bus_space_write_4(res->pr_bt, res->pr_bh, reg, val);
break;
default:
panic("unsupported access width %d", asz);
}
}