#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/uuid.h>
#include <sys/kdb.h>
#include <machine/_inttypes.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <dev/acpica/acpivar.h>
#define _COMPONENT ACPI_SPMC
ACPI_MODULE_NAME("SPMC")
static SYSCTL_NODE(_debug_acpi, OID_AUTO, spmc, CTLFLAG_RD | CTLFLAG_MPSAFE,
NULL, "SPMC debugging");
static char *spmc_ids[] = {
"PNP0D80",
NULL
};
enum intel_dsm_index {
DSM_ENUM_FUNCTIONS = 0,
DSM_GET_DEVICE_CONSTRAINTS = 1,
DSM_GET_CRASH_DUMP_DEVICE = 2,
DSM_DISPLAY_OFF_NOTIF = 3,
DSM_DISPLAY_ON_NOTIF = 4,
DSM_ENTRY_NOTIF = 5,
DSM_EXIT_NOTIF = 6,
DSM_MODERN_ENTRY_NOTIF = 7,
DSM_MODERN_EXIT_NOTIF = 8,
};
enum amd_dsm_index {
AMD_DSM_ENUM_FUNCTIONS = 0,
AMD_DSM_GET_DEVICE_CONSTRAINTS = 1,
AMD_DSM_ENTRY_NOTIF = 2,
AMD_DSM_EXIT_NOTIF = 3,
AMD_DSM_DISPLAY_OFF_NOTIF = 4,
AMD_DSM_DISPLAY_ON_NOTIF = 5,
};
enum dsm_set_flags {
DSM_SET_INTEL = 1 << 0,
DSM_SET_MS = 1 << 1,
DSM_SET_AMD = 1 << 2,
};
struct dsm_set {
enum dsm_set_flags flag;
const char *name;
int revision;
struct uuid uuid;
uint64_t dsms_expected;
};
static struct dsm_set intel_dsm_set = {
.flag = DSM_SET_INTEL,
.name = "Intel",
.revision = 0,
.uuid = {
0xc4eb40a0, 0x6cd2, 0x11e2, 0xbc, 0xfd,
{0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66},
},
.dsms_expected = DSM_GET_DEVICE_CONSTRAINTS | DSM_DISPLAY_OFF_NOTIF |
DSM_DISPLAY_ON_NOTIF | DSM_ENTRY_NOTIF | DSM_EXIT_NOTIF,
};
SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, intel_dsm_revision, CTLFLAG_RW,
&intel_dsm_set.revision, 0,
"Revision to use when evaluating Intel SPMC DSMs");
static struct dsm_set ms_dsm_set = {
.flag = DSM_SET_MS,
.name = "Microsoft",
.revision = 0,
.uuid = {
0x11e00d56, 0xce64, 0x47ce, 0x83, 0x7b,
{0x1f, 0x89, 0x8f, 0x9a, 0xa4, 0x61},
},
.dsms_expected = DSM_DISPLAY_OFF_NOTIF | DSM_DISPLAY_ON_NOTIF |
DSM_ENTRY_NOTIF | DSM_EXIT_NOTIF | DSM_MODERN_ENTRY_NOTIF |
DSM_MODERN_EXIT_NOTIF,
};
static struct dsm_set amd_dsm_set = {
.flag = DSM_SET_AMD,
.name = "AMD",
.revision = 2,
.uuid = {
0xe3f32452, 0xfebc, 0x43ce, 0x90, 0x39,
{0x93, 0x21, 0x22, 0xd3, 0x77, 0x21},
},
.dsms_expected = AMD_DSM_GET_DEVICE_CONSTRAINTS | AMD_DSM_ENTRY_NOTIF |
AMD_DSM_EXIT_NOTIF | AMD_DSM_DISPLAY_OFF_NOTIF |
AMD_DSM_DISPLAY_ON_NOTIF,
};
SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, amd_dsm_revision, CTLFLAG_RW,
&amd_dsm_set.revision, 0, "Revision to use when evaluating AMD SPMC DSMs");
union dsm_index {
int i;
enum intel_dsm_index regular;
enum amd_dsm_index amd;
};
struct acpi_spmc_constraint {
bool enabled;
char *name;
int min_d_state;
ACPI_HANDLE handle;
uint64_t lpi_uid;
uint64_t min_dev_specific_state;
uint64_t function_states;
};
struct acpi_spmc_softc {
device_t dev;
ACPI_HANDLE handle;
ACPI_OBJECT *obj;
enum dsm_set_flags dsm_sets;
bool constraints_populated;
size_t constraint_count;
struct acpi_spmc_constraint *constraints;
};
static void acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc,
ACPI_HANDLE handle, struct dsm_set *dsm_set);
static int acpi_spmc_get_constraints(device_t dev);
static void acpi_spmc_free_constraints(struct acpi_spmc_softc *sc);
static int
acpi_spmc_probe(device_t dev)
{
char *name;
ACPI_HANDLE handle;
struct acpi_spmc_softc *sc;
if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("spmc"))
return (ENXIO);
if (ACPI_ID_PROBE(device_get_parent(dev), dev, spmc_ids, &name) > 0)
return (ENXIO);
handle = acpi_get_handle(dev);
if (handle == NULL)
return (ENXIO);
sc = device_get_softc(dev);
sc->dsm_sets = 0;
acpi_spmc_check_dsm_set(sc, handle, &intel_dsm_set);
acpi_spmc_check_dsm_set(sc, handle, &ms_dsm_set);
acpi_spmc_check_dsm_set(sc, handle, &amd_dsm_set);
if (sc->dsm_sets == 0)
return (ENXIO);
device_set_descf(dev, "Low Power S0 Idle (DSM sets 0x%x)",
sc->dsm_sets);
return (0);
}
static int
acpi_spmc_attach(device_t dev)
{
struct acpi_spmc_softc *sc;
sc = device_get_softc(dev);
sc->dev = dev;
sc->handle = acpi_get_handle(dev);
if (sc->handle == NULL)
return (ENXIO);
sc->constraints_populated = false;
sc->constraint_count = 0;
sc->constraints = NULL;
acpi_spmc_get_constraints(sc->dev);
return (0);
}
static int
acpi_spmc_detach(device_t dev)
{
acpi_spmc_free_constraints(device_get_softc(dev));
return (0);
}
static void
acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, ACPI_HANDLE handle,
struct dsm_set *dsm_set)
{
const uint64_t dsms_supported = acpi_DSMQuery(handle,
(uint8_t *)&dsm_set->uuid, dsm_set->revision);
if ((dsms_supported & 1) == 0)
return;
if ((dsms_supported & dsm_set->dsms_expected)
!= dsm_set->dsms_expected) {
device_printf(sc->dev, "DSM set %s does not support expected "
"DSMs (%#" PRIx64 " vs %#" PRIx64 "). "
"Some methods may fail.\n",
dsm_set->name, dsms_supported, dsm_set->dsms_expected);
}
sc->dsm_sets |= dsm_set->flag;
}
static void
acpi_spmc_free_constraints(struct acpi_spmc_softc *sc)
{
if (sc->constraints == NULL)
return;
for (size_t i = 0; i < sc->constraint_count; i++) {
if (sc->constraints[i].name != NULL)
free(sc->constraints[i].name, M_TEMP);
}
free(sc->constraints, M_TEMP);
sc->constraints = NULL;
}
static int
acpi_spmc_get_constraints_spec(struct acpi_spmc_softc *sc, ACPI_OBJECT *object)
{
struct acpi_spmc_constraint *constraint;
int revision;
ACPI_OBJECT *constraint_obj;
ACPI_OBJECT *name_obj;
ACPI_OBJECT *detail;
ACPI_OBJECT *constraint_package;
KASSERT(sc->constraints_populated == false,
("constraints already populated"));
sc->constraint_count = object->Package.Count;
sc->constraints = malloc(sc->constraint_count * sizeof *sc->constraints,
M_TEMP, M_WAITOK | M_ZERO);
for (size_t i = 0; i < object->Package.Count; i++) {
constraint_obj = &object->Package.Elements[i];
constraint = &sc->constraints[i];
constraint->enabled =
constraint_obj->Package.Elements[1].Integer.Value;
name_obj = &constraint_obj->Package.Elements[0];
constraint->name = strdup(name_obj->String.Pointer, M_TEMP);
if (constraint->name == NULL) {
acpi_spmc_free_constraints(sc);
return (ENOMEM);
}
revision = constraint_obj->Package.Elements[0].Integer.Value;
if (revision != 0) {
device_printf(sc->dev, "Unknown revision %d for "
"device constraint detail package\n", revision);
sc->constraint_count--;
continue;
}
detail = &constraint_obj->Package.Elements[2];
constraint_package = &detail->Package.Elements[1];
constraint->lpi_uid =
constraint_package->Package.Elements[0].Integer.Value;
constraint->min_d_state =
constraint_package->Package.Elements[1].Integer.Value;
constraint->min_dev_specific_state =
constraint_package->Package.Elements[2].Integer.Value;
}
sc->constraints_populated = true;
return (0);
}
static int
acpi_spmc_get_constraints_amd(struct acpi_spmc_softc *sc, ACPI_OBJECT *object)
{
size_t constraint_count;
ACPI_OBJECT *constraint_obj;
ACPI_OBJECT *constraints;
struct acpi_spmc_constraint *constraint;
ACPI_OBJECT *name_obj;
KASSERT(sc->constraints_populated == false,
("constraints already populated"));
constraint_count = object->Package.Elements[1].Integer.Value;
constraints = &object->Package.Elements[2];
if (constraints->Package.Count != constraint_count) {
device_printf(sc->dev, "constraint count mismatch (%d to %zu)\n",
constraints->Package.Count, constraint_count);
return (ENXIO);
}
sc->constraint_count = constraint_count;
sc->constraints = malloc(constraint_count * sizeof *sc->constraints,
M_TEMP, M_WAITOK | M_ZERO);
for (size_t i = 0; i < constraint_count; i++) {
constraint_obj = &constraints->Package.Elements[i];
if (constraint_obj->Package.Count != 4) {
device_printf(sc->dev, "constraint %zu has %d elements\n",
i, constraint_obj->Package.Count);
acpi_spmc_free_constraints(sc);
return (ENXIO);
}
constraint = &sc->constraints[i];
constraint->enabled =
constraint_obj->Package.Elements[0].Integer.Value;
name_obj = &constraint_obj->Package.Elements[1];
constraint->name = strdup(name_obj->String.Pointer, M_TEMP);
if (constraint->name == NULL) {
acpi_spmc_free_constraints(sc);
return (ENOMEM);
}
constraint->function_states =
constraint_obj->Package.Elements[2].Integer.Value;
constraint->min_d_state =
constraint_obj->Package.Elements[3].Integer.Value;
}
sc->constraints_populated = true;
return (0);
}
static int
acpi_spmc_get_constraints(device_t dev)
{
struct acpi_spmc_softc *sc;
union dsm_index dsm_index;
struct dsm_set *dsm_set;
ACPI_STATUS status;
ACPI_BUFFER result;
ACPI_OBJECT *object;
bool is_amd;
int rv;
struct acpi_spmc_constraint *constraint;
sc = device_get_softc(dev);
if (sc->constraints_populated)
return (0);
is_amd = (sc->dsm_sets & DSM_SET_AMD) != 0;
if (is_amd) {
dsm_set = &amd_dsm_set;
dsm_index.amd = AMD_DSM_GET_DEVICE_CONSTRAINTS;
} else {
dsm_set = &intel_dsm_set;
dsm_index.regular = DSM_GET_DEVICE_CONSTRAINTS;
}
status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid,
dsm_set->revision, dsm_index.i, NULL, &result,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status)) {
device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n",
__func__, dsm_set->name, dsm_index.i, dsm_set->revision);
return (ENXIO);
}
object = (ACPI_OBJECT *)result.Pointer;
if (is_amd)
rv = acpi_spmc_get_constraints_amd(sc, object);
else
rv = acpi_spmc_get_constraints_spec(sc, object);
AcpiOsFree(object);
if (rv != 0)
return (rv);
for (size_t i = 0; i < sc->constraint_count; i++) {
constraint = &sc->constraints[i];
status = acpi_GetHandleInScope(sc->handle,
__DECONST(char *, constraint->name), &constraint->handle);
if (ACPI_FAILURE(status)) {
device_printf(dev, "failed to get handle for %s\n",
constraint->name);
constraint->handle = NULL;
}
}
return (0);
}
static void
acpi_spmc_check_constraints(struct acpi_spmc_softc *sc)
{
bool violation = false;
KASSERT(sc->constraints_populated, ("constraints not populated"));
for (size_t i = 0; i < sc->constraint_count; i++) {
struct acpi_spmc_constraint *constraint = &sc->constraints[i];
if (!constraint->enabled)
continue;
if (constraint->handle == NULL)
continue;
ACPI_STATUS status = acpi_GetHandleInScope(sc->handle,
__DECONST(char *, constraint->name), &constraint->handle);
if (ACPI_FAILURE(status)) {
device_printf(sc->dev, "failed to get handle for %s\n",
constraint->name);
constraint->handle = NULL;
}
if (constraint->handle == NULL)
continue;
#ifdef notyet
int d_state;
if (ACPI_FAILURE(acpi_pwr_get_state(constraint->handle, &d_state)))
continue;
if (d_state < constraint->min_d_state) {
device_printf(sc->dev, "constraint for device %s"
" violated (minimum D-state required was %s, actual"
" D-state is %s), might fail to enter LPI state\n",
constraint->name,
acpi_d_state_to_str(constraint->min_d_state),
acpi_d_state_to_str(d_state));
violation = true;
}
#endif
}
if (!violation)
device_printf(sc->dev,
"all device power constraints respected!\n");
}
static void
acpi_spmc_run_dsm(device_t dev, struct dsm_set *dsm_set, int index)
{
struct acpi_spmc_softc *sc;
ACPI_STATUS status;
ACPI_BUFFER result;
sc = device_get_softc(dev);
status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid,
dsm_set->revision, index, NULL, &result, ACPI_TYPE_ANY);
if (ACPI_FAILURE(status)) {
device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n",
__func__, dsm_set->name, index, dsm_set->revision);
return;
}
AcpiOsFree(result.Pointer);
}
static void
acpi_spmc_display_off_notif(device_t dev)
{
struct acpi_spmc_softc *sc = device_get_softc(dev);
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_OFF_NOTIF);
if ((sc->dsm_sets & DSM_SET_MS) != 0)
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_OFF_NOTIF);
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_OFF_NOTIF);
}
static void
acpi_spmc_display_on_notif(device_t dev)
{
struct acpi_spmc_softc *sc = device_get_softc(dev);
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_ON_NOTIF);
if ((sc->dsm_sets & DSM_SET_MS) != 0)
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_ON_NOTIF);
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_ON_NOTIF);
}
static void
acpi_spmc_entry_notif(device_t dev)
{
struct acpi_spmc_softc *sc = device_get_softc(dev);
acpi_spmc_check_constraints(sc);
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_ENTRY_NOTIF);
if ((sc->dsm_sets & DSM_SET_MS) != 0) {
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_ENTRY_NOTIF);
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_ENTRY_NOTIF);
}
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_ENTRY_NOTIF);
}
static void
acpi_spmc_exit_notif(device_t dev)
{
struct acpi_spmc_softc *sc = device_get_softc(dev);
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_EXIT_NOTIF);
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_EXIT_NOTIF);
if ((sc->dsm_sets & DSM_SET_MS) != 0) {
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_EXIT_NOTIF);
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_EXIT_NOTIF);
}
}
static int
acpi_spmc_suspend(device_t dev)
{
acpi_spmc_display_off_notif(dev);
acpi_spmc_entry_notif(dev);
return (0);
}
static int
acpi_spmc_resume(device_t dev)
{
acpi_spmc_exit_notif(dev);
acpi_spmc_display_on_notif(dev);
return (0);
}
static device_method_t acpi_spmc_methods[] = {
DEVMETHOD(device_probe, acpi_spmc_probe),
DEVMETHOD(device_attach, acpi_spmc_attach),
DEVMETHOD(device_detach, acpi_spmc_detach),
DEVMETHOD_END
};
static driver_t acpi_spmc_driver = {
"acpi_spmc",
acpi_spmc_methods,
sizeof(struct acpi_spmc_softc),
};
DRIVER_MODULE_ORDERED(acpi_spmc, acpi, acpi_spmc_driver, NULL, NULL, SI_ORDER_ANY);
MODULE_DEPEND(acpi_spmc, acpi, 1, 1, 1);