#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ACPI.h>
#include <Drivers.h>
#include <Errors.h>
#include <KernelExport.h>
#include <arch_system_info.h>
#include <cpu.h>
#include <cpuidle.h>
#include <smp.h>
#include <thread.h>
#include "x86_mwait.h"
#define ACPI_PDC_REVID 0x1
#define ACPI_OSC_QUERY (1 << 0)
#define ACPI_PDC_P_FFH (1 << 0)
#define ACPI_PDC_C_C1_HALT (1 << 1)
#define ACPI_PDC_T_FFH (1 << 2)
#define ACPI_PDC_SMP_C1PT (1 << 3)
#define ACPI_PDC_SMP_C2C3 (1 << 4)
#define ACPI_PDC_SMP_P_SW (1 << 5)
#define ACPI_PDC_SMP_C_SW (1 << 6)
#define ACPI_PDC_SMP_T_SW (1 << 7)
#define ACPI_PDC_C_C1_FFH (1 << 8)
#define ACPI_PDC_C_C2C3_FFH (1 << 9)
#define ACPI_PDC_P_HWCOORD (1 << 11)
#define ACPI_PDC_GAS_BM (1 << 1)
#define ACPI_CSTATE_HALT 0x1
#define ACPI_CSTATE_SYSIO 0x2
#define ACPI_CSTATE_FFH 0x3
#define ACPI_FLAG_C_BM (1 << 0)
#define ACPI_FLAG_C_ARB (1 << 1)
#define ACPI_BITREG_BUS_MASTER_STATUS 0x01
#define ACPI_BITREG_BUS_MASTER_RLD 0x0F
#define ACPI_BITREG_ARB_DISABLE 0x13
#define ACPI_STATE_C0 (uint8) 0
#define ACPI_STATE_C1 (uint8) 1
#define ACPI_STATE_C2 (uint8) 2
#define ACPI_STATE_C3 (uint8) 3
#define ACPI_C_STATES_MAX ACPI_STATE_C3
#define ACPI_C_STATE_COUNT 4
#define ACPI_CPUIDLE_MODULE_NAME CPUIDLE_MODULES_PREFIX "/x86_acpi_cstates/v1"
struct acpicpu_reg {
uint8 reg_desc;
uint16 reg_reslen;
uint8 reg_spaceid;
uint8 reg_bitwidth;
uint8 reg_bitoffset;
uint8 reg_accesssize;
uint64 reg_addr;
} __attribute__((packed));
struct acpi_cstate_info {
char name[B_OS_NAME_LENGTH];
uint32 latency;
uint32 address;
uint8 skip_bm_sts;
uint8 method;
uint8 type;
};
struct acpi_cpuidle_driver_info {
device_node *processor;
acpi_device_module_info *acpi;
acpi_device acpi_cookie;
uint32 flags;
#define MAX_CSTATES 8
int32 state_count;
acpi_cstate_info states[MAX_CSTATES];
};
static acpi_cpuidle_driver_info *sAcpiProcessor[SMP_MAX_CPUS];
static device_manager_info *sDeviceManager;
static acpi_module_info *sAcpi;
static int32 sStateIndex = -1;
static status_t
acpi_eval_pdc(acpi_cpuidle_driver_info *device)
{
acpi_objects arg;
acpi_object_type obj;
uint32 cap[3];
arg.count = 1;
arg.pointer = &obj;
cap[0] = 1;
cap[1] = 1;
cap[2] = ACPI_PDC_C_C1_HALT | ACPI_PDC_SMP_C1PT | ACPI_PDC_SMP_C2C3;
cap[2] |= ACPI_PDC_SMP_P_SW | ACPI_PDC_SMP_C_SW | ACPI_PDC_SMP_T_SW;
cap[2] |= ACPI_PDC_C_C1_FFH | ACPI_PDC_C_C2C3_FFH;
cap[2] |= ACPI_PDC_SMP_T_SW | ACPI_PDC_P_FFH | ACPI_PDC_P_HWCOORD
| ACPI_PDC_T_FFH;
obj.object_type = ACPI_TYPE_BUFFER;
obj.buffer.length = sizeof(cap);
obj.buffer.buffer = cap;
status_t status = device->acpi->evaluate_method(device->acpi_cookie, "_PDC",
&arg, NULL);
return status;
}
static status_t
acpi_eval_osc(acpi_cpuidle_driver_info *device)
{
dprintf("%s@%p\n", __func__, device->acpi_cookie);
static uint8 uuid[] = {
0x16, 0xA6, 0x77, 0x40, 0x0C, 0x29, 0xBE, 0x47,
0x9E, 0xBD, 0xD8, 0x70, 0x58, 0x71, 0x39, 0x53
};
uint32 cap[2];
cap[0] = 0;
cap[1] = ACPI_PDC_C_C1_HALT | ACPI_PDC_SMP_C1PT | ACPI_PDC_SMP_C2C3;
cap[1] |= ACPI_PDC_SMP_P_SW | ACPI_PDC_SMP_C_SW | ACPI_PDC_SMP_T_SW;
cap[1] |= ACPI_PDC_C_C1_FFH | ACPI_PDC_C_C2C3_FFH;
cap[1] |= ACPI_PDC_SMP_T_SW | ACPI_PDC_P_FFH | ACPI_PDC_P_HWCOORD
| ACPI_PDC_T_FFH;
acpi_objects arg;
acpi_object_type obj[4];
arg.count = 4;
arg.pointer = obj;
obj[0].object_type = ACPI_TYPE_BUFFER;
obj[0].buffer.length = sizeof(uuid);
obj[0].buffer.buffer = uuid;
obj[1].object_type = ACPI_TYPE_INTEGER;
obj[1].integer.integer = ACPI_PDC_REVID;
obj[2].object_type = ACPI_TYPE_INTEGER;
obj[2].integer.integer = sizeof(cap)/sizeof(cap[0]);
obj[3].object_type = ACPI_TYPE_BUFFER;
obj[3].buffer.length = sizeof(cap);
obj[3].buffer.buffer = (void *)cap;
acpi_data buf;
buf.pointer = NULL;
buf.length = ACPI_ALLOCATE_LOCAL_BUFFER;
status_t status = device->acpi->evaluate_method(device->acpi_cookie, "_OSC",
&arg, &buf);
if (status != B_OK)
return status;
acpi_object_type *osc = (acpi_object_type *)buf.pointer;
if (osc->object_type != ACPI_TYPE_BUFFER)
return B_BAD_TYPE;
if (osc->buffer.length != sizeof(cap))
return B_BUFFER_OVERFLOW;
return status;
}
static inline bool
acpi_cstate_bm_check(void)
{
uint32 val;
sAcpi->read_bit_register(ACPI_BITREG_BUS_MASTER_STATUS, &val);
if (!val)
return false;
sAcpi->write_bit_register(ACPI_BITREG_BUS_MASTER_STATUS, 1);
return true;
}
static inline void
acpi_cstate_ffh_enter(acpi_cstate_info *ci)
{
int dummy;
x86_monitor(&dummy, 0, 0);
x86_mwait((ci->type << 4), MWAIT_INTERRUPTS_BREAK);
}
static inline void
acpi_cstate_halt(void)
{
gCpuIdleFunc();
}
static void
acpi_cstate_enter(acpi_cstate_info *ci)
{
if (ci->method == ACPI_CSTATE_FFH)
acpi_cstate_ffh_enter(ci);
else if (ci->method == ACPI_CSTATE_SYSIO)
in8(ci->address);
else
acpi_cstate_halt();
}
static void
acpi_cstate_set_scheduler_mode(scheduler_mode mode)
{
int maxState;
if (mode == SCHEDULER_MODE_POWER_SAVING)
maxState = ACPI_STATE_C3;
else
maxState = ACPI_STATE_C1;
acpi_cpuidle_driver_info *pi = sAcpiProcessor[0];
int32 index = -1;
for (int i = 0; i < pi->state_count; i++) {
if (pi->states[i].type > maxState)
break;
index = i;
}
sStateIndex = index;
}
static void
acpi_cstate_idle()
{
Thread* thread = thread_get_current_thread();
if (thread->pinned_to_cpu <= 0 || thread->post_interrupt_callback != NULL)
panic("invalid thread state");
acpi_cpuidle_driver_info *pi = sAcpiProcessor[smp_get_current_cpu()];
acpi_cstate_info *ci = NULL;
int32 stateIndex = -1;
struct IdlingState {
jmp_buf reset_jump;
int32 preparing;
} idlingState;
idlingState.preparing = 1;
int result = setjmp(idlingState.reset_jump);
if (result != 0) {
enable_interrupts();
if (ci->type == ACPI_STATE_C3)
goto C3_out;
thread->post_interrupt_callback = NULL;
return;
}
thread->post_interrupt_data = &idlingState;
thread->post_interrupt_callback = [](void* state) {
thread_get_current_thread()->post_interrupt_callback = NULL;
IdlingState* idlingState = (IdlingState*)state;
if (idlingState->preparing == 1) {
idlingState->preparing = 0;
return;
}
longjmp(idlingState->reset_jump, EINTR);
};
if ((stateIndex = sStateIndex) < 0) {
thread->post_interrupt_callback = NULL;
acpi_cstate_halt();
return;
}
ci = &pi->states[stateIndex];
if (!ci->skip_bm_sts) {
if (acpi_cstate_bm_check())
ci = &pi->states[0];
}
if (ci->type != ACPI_STATE_C3) {
if (atomic_test_and_set(&idlingState.preparing, 0, 1) == 1)
acpi_cstate_enter(ci);
thread->post_interrupt_callback = NULL;
return;
}
if (pi->flags & ACPI_FLAG_C_BM)
sAcpi->write_bit_register(ACPI_BITREG_BUS_MASTER_RLD, 1);
if (pi->flags & ACPI_FLAG_C_ARB)
sAcpi->write_bit_register(ACPI_BITREG_ARB_DISABLE, 1);
if (atomic_test_and_set(&idlingState.preparing, 0, 1) == 1)
acpi_cstate_enter(ci);
C3_out:
thread->post_interrupt_callback = NULL;
if (pi->flags & ACPI_FLAG_C_BM)
sAcpi->write_bit_register(ACPI_BITREG_BUS_MASTER_RLD, 0);
if (pi->flags & ACPI_FLAG_C_ARB)
sAcpi->write_bit_register(ACPI_BITREG_ARB_DISABLE, 0);
}
static void
acpi_cstate_wait(int32* variable, int32 test)
{
arch_cpu_pause();
}
static status_t
acpi_cstate_add(acpi_object_type *object, acpi_cstate_info *ci)
{
if (object->object_type != ACPI_TYPE_PACKAGE) {
dprintf("invalid _CST object\n");
return B_ERROR;
}
if (object->package.count != 4) {
dprintf("invalid _CST number\n");
return B_ERROR;
}
acpi_object_type * pointer = &object->package.objects[1];
if (pointer->object_type != ACPI_TYPE_INTEGER) {
dprintf("invalid _CST elem type\n");
return B_ERROR;
}
uint32 n = pointer->integer.integer;
if (n < 1 || n > 3) {
dprintf("invalid _CST elem value\n");
return B_ERROR;
}
ci->type = n;
dprintf("C%" B_PRId32 " ", n);
snprintf(ci->name, sizeof(ci->name), "C%" B_PRId32, n);
pointer = &object->package.objects[2];
if (pointer->object_type != ACPI_TYPE_INTEGER) {
dprintf("invalid _CST elem type\n");
return B_ERROR;
}
n = pointer->integer.integer;
ci->latency = n;
dprintf("latency: %" B_PRId32 ", ", n);
pointer = &object->package.objects[3];
if (pointer->object_type != ACPI_TYPE_INTEGER) {
dprintf("invalid _CST elem type\n");
return B_ERROR;
}
n = pointer->integer.integer;
dprintf("power: %" B_PRId32 ", ", n);
pointer = &object->package.objects[0];
if (pointer->object_type != ACPI_TYPE_BUFFER) {
dprintf("invalid _CST elem type\n");
return B_ERROR;
}
if (pointer->buffer.length < 15) {
dprintf("invalid _CST elem length\n");
return B_ERROR;
}
struct acpicpu_reg *reg = (struct acpicpu_reg *)pointer->buffer.buffer;
switch (reg->reg_spaceid) {
case ACPI_ADR_SPACE_SYSTEM_IO:
dprintf("IO method\n");
if (reg->reg_addr == 0) {
dprintf("illegal address\n");
return B_ERROR;
}
if (reg->reg_bitwidth != 8) {
dprintf("invalid source length\n");
return B_ERROR;
}
ci->address = reg->reg_addr;
ci->method = ACPI_CSTATE_SYSIO;
break;
case ACPI_ADR_SPACE_FIXED_HARDWARE:
{
dprintf("FFH method\n");
ci->method = ACPI_CSTATE_FFH;
ci->address = reg->reg_addr;
cpu_ent *cpu = get_cpu_struct();
if (cpu->arch.vendor == VENDOR_INTEL &&
(reg->reg_accesssize & ACPI_PDC_GAS_BM) == 0)
ci->skip_bm_sts = 1;
break;
}
default:
dprintf("invalid spaceid %" B_PRId8 "\n", reg->reg_spaceid);
break;
}
return B_OK;
}
static void
acpi_cstate_quirks(acpi_cpuidle_driver_info *device)
{
cpu_ent *cpu = get_cpu_struct();
uint32 model = (cpu->arch.extended_model << 4) + cpu->arch.model;
if (cpu->arch.vendor != VENDOR_INTEL)
return;
if (cpu->arch.family > 0xf || (cpu->arch.family == 6 && model >= 0xf))
device->flags &= ~ACPI_FLAG_C_ARB;
}
static status_t
acpi_cpuidle_setup(acpi_cpuidle_driver_info *device)
{
status_t status = acpi_eval_osc(device);
if (status != B_OK)
status = acpi_eval_pdc(device);
if (status != B_OK) {
dprintf("failed to eval _OSC and _PDC\n");
return status;
}
acpi_data buffer;
buffer.pointer = NULL;
buffer.length = ACPI_ALLOCATE_BUFFER;
dprintf("evaluate _CST @%p\n", device->acpi_cookie);
status = device->acpi->evaluate_method(device->acpi_cookie, "_CST", NULL,
&buffer);
if (status != B_OK) {
dprintf("failed to get _CST\n");
return B_IO_ERROR;
}
acpi_object_type *object = (acpi_object_type *)buffer.pointer;
if (object->object_type != ACPI_TYPE_PACKAGE)
dprintf("invalid _CST type\n");
if (object->package.count < 2)
dprintf("invalid _CST count\n");
acpi_object_type *pointer = object->package.objects;
if (pointer[0].object_type != ACPI_TYPE_INTEGER)
dprintf("invalid _CST type 2\n");
uint32 n = pointer[0].integer.integer;
if (n != object->package.count - 1)
dprintf("invalid _CST count 2\n");
if (n > MAX_CSTATES) {
dprintf("_CST has too many states\n");
n = MAX_CSTATES;
}
dprintf("cpuidle found %" B_PRId32 " cstates\n", n);
uint32 count = 0;
for (uint32 i = 1; i <= n; i++) {
pointer = &object->package.objects[i];
if (acpi_cstate_add(pointer, &device->states[count]) == B_OK)
++count;
}
device->state_count = count;
free(buffer.pointer);
device->flags |= ACPI_FLAG_C_ARB | ACPI_FLAG_C_BM;
acpi_cstate_quirks(device);
return B_OK;
}
static status_t
acpi_processor_init(acpi_cpuidle_driver_info *device)
{
acpi_data buffer;
buffer.pointer = NULL;
buffer.length = ACPI_ALLOCATE_BUFFER;
status_t status = device->acpi->evaluate_method(device->acpi_cookie, NULL,
NULL, &buffer);
if (status != B_OK) {
dprintf("failed to get processor obj\n");
return status;
}
acpi_object_type *tmpObject = (acpi_object_type *)buffer.pointer;
const uint32 processor_cpu_id = tmpObject->processor.cpu_id;
free(buffer.pointer);
int32 cpuIndex = -1;
for (int32 i = 0; i < smp_get_num_cpus(); i++) {
cpu_ent* cpu = &gCPU[i];
if ((uint32)cpu->arch.acpi_processor_id == processor_cpu_id) {
cpuIndex = i;
break;
}
}
if (cpuIndex < 0) {
dprintf("can't find matching cpu_ent for acpi cpu %" B_PRId32 "\n",
processor_cpu_id);
return B_ERROR;
}
dprintf("acpi cpu %" B_PRId32 " maps to cpu %" B_PRId32 "\n",
processor_cpu_id, cpuIndex);
sAcpiProcessor[cpuIndex] = device;
return status;
}
static void
acpi_cpuidle_uninit()
{
for (size_t i = 0; i < B_COUNT_OF(sAcpiProcessor); i++) {
if (sAcpiProcessor[i] == NULL)
continue;
sDeviceManager->put_node(sAcpiProcessor[i]->processor);
free(sAcpiProcessor[i]);
sAcpiProcessor[i] = NULL;
}
}
static status_t
acpi_cpuidle_init()
{
if (x86_check_feature(IA32_FEATURE_EXT_HYPERVISOR, FEATURE_EXT))
return B_ERROR;
device_node* root = sDeviceManager->get_root_node();
status_t status = B_OK;
int32 processors = 0;
device_node* processor = NULL;
while (true) {
device_attr acpiAttrs[] = {
{ B_DEVICE_BUS, B_STRING_TYPE, { .string = "acpi" }},
{ ACPI_DEVICE_TYPE_ITEM, B_UINT32_TYPE, { .ui32 = ACPI_TYPE_PROCESSOR }},
{ NULL }
};
if (sDeviceManager->find_child_node(root, acpiAttrs, &processor) != B_OK)
break;
acpi_cpuidle_driver_info *device;
device = (acpi_cpuidle_driver_info *)calloc(1, sizeof(*device));
if (device == NULL) {
sDeviceManager->put_node(processor);
status = B_NO_MEMORY;
break;
}
device->processor = processor;
sDeviceManager->get_driver(processor, (driver_module_info **)&device->acpi,
(void **)&device->acpi_cookie);
status = acpi_processor_init(device);
if (status != B_OK) {
sDeviceManager->put_node(processor);
free(device);
status = B_OK;
continue;
}
processors++;
}
sDeviceManager->put_node(root);
if (status == B_OK && processors != smp_get_num_cpus()) {
dprintf("can't use x86 ACPI idle: missing %" B_PRId32 " processor objects\n",
smp_get_num_cpus() - processors);
status = B_NOT_SUPPORTED;
}
for (int32 i = 0; status == B_OK && i < smp_get_num_cpus(); i++)
status = acpi_cpuidle_setup(sAcpiProcessor[i]);
if (status == B_OK) {
acpi_cstate_set_scheduler_mode(SCHEDULER_MODE_LOW_LATENCY);
dprintf("using x86 ACPI idle\n");
}
if (status != B_OK)
acpi_cpuidle_uninit();
return status;
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
return acpi_cpuidle_init();
case B_MODULE_UNINIT:
acpi_cpuidle_uninit();
return B_OK;
}
return B_ERROR;
}
static cpuidle_module_info sAcpiidleModule = {
{
ACPI_CPUIDLE_MODULE_NAME,
0,
std_ops,
},
0.2f,
acpi_cstate_set_scheduler_mode,
acpi_cstate_idle,
acpi_cstate_wait
};
module_info *modules[] = {
(module_info *)&sAcpiidleModule,
NULL
};
module_dependency module_dependencies[] = {
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager },
{ B_ACPI_MODULE_NAME, (module_info **)&sAcpi },
{}
};