From 0138a95ebac947f56c39ff2a148d3380ad5db1cc Mon Sep 17 00:00:00 2001 From: Yongcong Du Date: Sun, 19 Aug 2012 19:30:23 +0800 Subject: [PATCH] cpuidle: implement acpi cpuidle driver we prefer intel native cpuidle driver on newer intel platforms -- sandybridge or later. we'll fall back to acpi cpuidle driver for other platforms. Signed-off-by: Fredrik Holmqvist --- .../power/x86_cpuidle/acpi_cpuidle.cpp | 411 +++++++++++++++++- 1 file changed, 410 insertions(+), 1 deletion(-) diff --git a/src/add-ons/kernel/drivers/power/x86_cpuidle/acpi_cpuidle.cpp b/src/add-ons/kernel/drivers/power/x86_cpuidle/acpi_cpuidle.cpp index 8064d506e0..fb4095cb87 100644 --- a/src/add-ons/kernel/drivers/power/x86_cpuidle/acpi_cpuidle.cpp +++ b/src/add-ons/kernel/drivers/power/x86_cpuidle/acpi_cpuidle.cpp @@ -19,27 +19,433 @@ #include #include +#include #include "x86_cpuidle.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) + +// Bus Master check required +#define ACPI_PDC_GAS_BM (1 << 1) + +#define ACPI_CSTATE_HALT 0x1 +#define ACPI_CSTATE_SYSIO 0x2 +#define ACPI_CSTATE_FFH 0x3 + +// Bus Master Check +#define ACPI_FLAG_C_BM (1 << 0) +// Bus master arbitration +#define ACPI_FLAG_C_ARB (1 << 1) + +// Copied from acpica's actypes.h, where's the best place to put? +#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 "drivers/power/x86_cpuidle/acpi/driver_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_cpuidle_driver_info { device_node *node; acpi_device_module_info *acpi; acpi_device acpi_cookie; + uint32 flags; +}; + +struct acpi_cstate_info { + uint32 address; + uint8 skip_bm_sts; + uint8 method; + uint8 type; }; static device_manager_info *sDeviceManager; static acpi_cpuidle_driver_info *acpi_processor[B_MAX_CPU_COUNT]; +static CpuidleModuleInfo *sIdle; +static CpuidleDevice sAcpiDevice; +static acpi_module_info *sAcpi; + + +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.data.buffer.length = sizeof(cap); + obj.data.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) +{ + // guid for intel platform + 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].data.buffer.length = sizeof(uuid); + obj[0].data.buffer.buffer = uuid; + obj[1].object_type = ACPI_TYPE_INTEGER; + obj[1].data.integer = ACPI_PDC_REVID; + obj[2].object_type = ACPI_TYPE_INTEGER; + obj[2].data.integer = sizeof(cap)/sizeof(cap[0]); + obj[3].object_type = ACPI_TYPE_BUFFER; + obj[3].data.buffer.length = sizeof(cap); + obj[3].data.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->data.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(CpuidleCstate *cState) +{ + cpu_ent *cpu = get_cpu_struct(); + if (cpu->invoke_scheduler) + return; + + x86_monitor((void *)&cpu->invoke_scheduler, 0, 0); + if (!cpu->invoke_scheduler) + x86_mwait((unsigned long)cState->pData, 1); +} + + +static inline void +acpi_cstate_halt(void) +{ + cpu_ent *cpu = get_cpu_struct(); + if (cpu->invoke_scheduler) + return; + asm("hlt"); +} + + +static void +acpi_cstate_enter(CpuidleCstate *cState) +{ + acpi_cstate_info *ci = (acpi_cstate_info *)cState->pData; + if (ci->method == ACPI_CSTATE_FFH) + acpi_cstate_ffh_enter(cState); + else if (ci->method == ACPI_CSTATE_SYSIO) + in8(ci->address); + else + acpi_cstate_halt(); +} + + +static int32 +acpi_cstate_idle(int32 state, CpuidleDevice *device) +{ + CpuidleCstate *cState = &device->cStates[state]; + acpi_cstate_info *ci = (acpi_cstate_info *)cState->pData; + if (!ci->skip_bm_sts) { + // we fall back to C1 if there's bus master activity + if (acpi_cstate_bm_check()) + state = 1; + } + if (ci->type != ACPI_STATE_C3) + acpi_cstate_enter(cState); + + // set BM_RLD for Bus Master to activity to wake the system from C3 + // With Newer chipsets BM_RLD is a NOP Since DMA is automatically handled + // during C3 State + acpi_cpuidle_driver_info *pi = acpi_processor[smp_get_current_cpu()]; + if (pi->flags & ACPI_FLAG_C_BM) + sAcpi->write_bit_register(ACPI_BITREG_BUS_MASTER_RLD, 1); + + // disable bus master arbitration during C3 + if (pi->flags & ACPI_FLAG_C_ARB) + sAcpi->write_bit_register(ACPI_BITREG_ARB_DISABLE, 1); + + acpi_cstate_enter(cState); + + // clear BM_RLD and re-enable the arbiter + 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); + + return state; +} + + +static status_t +acpi_cstate_add(acpi_object_type *object, CpuidleCstate *cState) +{ + acpi_cstate_info *ci = (acpi_cstate_info *)malloc(sizeof(acpi_cstate_info)); + if (!ci) + return B_NO_MEMORY; + + if (object->object_type != ACPI_TYPE_PACKAGE) { + dprintf("invalid _CST object\n"); + return B_ERROR; + } + + if (object->data.package.count != 4) { + dprintf("invalid _CST number\n"); + return B_ERROR; + } + + // type + acpi_object_type * pointer = &object->data.package.objects[1]; + if (pointer->object_type != ACPI_TYPE_INTEGER) { + dprintf("invalid _CST elem type\n"); + return B_ERROR; + } + uint32 n = pointer->data.integer; + if (n < 1 || n > 3) { + dprintf("invalid _CST elem value\n"); + return B_ERROR; + } + ci->type = n; + dprintf("C%" B_PRId32 "\n", n); + snprintf(cState->name, sizeof(cState->name), "C%"B_PRId32, n); + + // Latency + pointer = &object->data.package.objects[2]; + if (pointer->object_type != ACPI_TYPE_INTEGER) { + dprintf("invalid _CST elem type\n"); + return B_ERROR; + } + n = pointer->data.integer; + cState->latency = n; + dprintf("Latency: %" B_PRId32 "\n", n); + + // power + pointer = &object->data.package.objects[3]; + if (pointer->object_type != ACPI_TYPE_INTEGER) { + dprintf("invalid _CST elem type\n"); + return B_ERROR; + } + n = pointer->data.integer; + dprintf("power: %" B_PRId32 "\n", n); + + // register + pointer = &object->data.package.objects[0]; + if (pointer->object_type != ACPI_TYPE_BUFFER) { + dprintf("invalid _CST elem type\n"); + return B_ERROR; + } + if (pointer->data.buffer.length < 15) { + dprintf("invalid _CST elem length\n"); + return B_ERROR; + } + + struct acpicpu_reg *reg = (struct acpicpu_reg *)pointer->data.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; + + // skip checking BM_STS if ACPI_PDC_GAS_BM is cleared + cpu_ent *cpu = get_cpu_struct(); + if ((cpu->arch.vendor == VENDOR_INTEL) && + !(reg->reg_accesssize & ACPI_PDC_GAS_BM)) + ci->skip_bm_sts = 1; + break; + } + default: + dprintf("invalid spaceid %" B_PRId8 "\n", reg->reg_spaceid); + break; + } + cState->pData = ci; + cState->EnterIdle = acpi_cstate_idle; + + return B_OK; +} + + +static void +acpi_cstate_quirks(acpi_cpuidle_driver_info *device) +{ + cpu_ent *cpu = get_cpu_struct(); + // Calculated Model Value: M = (Extended Model << 4) + Model + uint32 model = (cpu->arch.extended_model << 4) + cpu->arch.model; + + // On all recent Intel platforms, ARB_DIS is not necessary + 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) +{ + // _PDC is deprecated in the ACPI 3.0, we will try _OSC firstly + // and fall back to _PDC if _OSC fail + status_t status = acpi_eval_osc(device); + if (status != B_OK) + status = acpi_eval_pdc(device); + if (status != B_OK) { + dprintf("faile 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->data.package.count < 2) + dprintf("invalid _CST count\n"); + + acpi_object_type *pointer = object->data.package.objects; + if (pointer[0].object_type != ACPI_TYPE_INTEGER) + dprintf("invalid _CST type 2\n"); + uint32 n = pointer[0].data.integer; + if (n != object->data.package.count - 1) + dprintf("invalid _CST count 2\n"); + if (n > 8) + dprintf("_CST has too many states\n"); + dprintf("cpuidle found %" B_PRId32 " cstates\n", n); + uint32 count = 1; + for (uint32 i = 1; i <= n; i++) { + pointer = &object->data.package.objects[i]; + if (acpi_cstate_add(pointer, &sAcpiDevice.cStates[count]) == B_OK) + ++count; + } + sAcpiDevice.cStateCount = count; + free(buffer.pointer); + + // TODO we assume BM is a must and ARB_DIS is always available + device->flags |= ACPI_FLAG_C_ARB | ACPI_FLAG_C_BM; + + acpi_cstate_quirks(device); + + return B_OK; +} static status_t acpi_cpuidle_init(void) { dprintf("acpi_cpuidle_init\n"); + if (get_module(B_CPUIDLE_MODULE_NAME, (module_info**)&sIdle) != B_OK) + return B_ERROR; + if (get_module(B_ACPI_MODULE_NAME, (module_info**)&sAcpi) != B_OK) + return B_ERROR; - return B_ERROR; + for (int32 i = 0; i < smp_get_num_cpus(); i++) + if (acpi_cpuidle_setup(acpi_processor[i]) != B_OK) + return B_ERROR; + + status_t status = sIdle->AddDevice(&sAcpiDevice); + if (status == B_OK) + dprintf("using acpi idle\n"); + else + put_module(B_CPUIDLE_MODULE_NAME); + return status; } @@ -147,6 +553,9 @@ static void acpi_cpuidle_uninit_driver(void *driverCookie) { dprintf("acpi_cpuidle_uninit_driver"); + put_module(B_CPUIDLE_MODULE_NAME); + if (sAcpi) + put_module(B_ACPI_MODULE_NAME); acpi_cpuidle_driver_info *device = (acpi_cpuidle_driver_info *)driverCookie; free(device); }