linux-stable/drivers/acpi/processor_pdc.c
Roger Pau Monne 073828e954 ACPI: processor: Fix evaluating _PDC method when running as Xen dom0
In ACPI systems, the OS can direct power management, as opposed to the
firmware.  This OS-directed Power Management is called OSPM.  Part of
telling the firmware that the OS going to direct power management is
making ACPI "_PDC" (Processor Driver Capabilities) calls.  These _PDC
methods must be evaluated for every processor object.  If these _PDC
calls are not completed for every processor it can lead to
inconsistency and later failures in things like the CPU frequency
driver.

In a Xen system, the dom0 kernel is responsible for system-wide power
management.  The dom0 kernel is in charge of OSPM.  However, the
number of CPUs available to dom0 can be different than the number of
CPUs physically present on the system.

This leads to a problem: the dom0 kernel needs to evaluate _PDC for
all the processors, but it can't always see them.

In dom0 kernels, ignore the existing ACPI method for determining if a
processor is physically present because it might not be accurate.
Instead, ask the hypervisor for this information.

Fix this by introducing a custom function to use when running as Xen
dom0 in order to check whether a processor object matches a CPU that's
online.  Such checking is done using the existing information fetched
by the Xen pCPU subsystem, extending it to also store the ACPI ID.

This ensures that _PDC method gets evaluated for all physically online
CPUs, regardless of the number of CPUs made available to dom0.

Fixes: 5d554a7bb0 ("ACPI: processor: add internal processor_physically_present()")
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2023-03-22 19:36:31 +01:00

212 lines
4.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005 Intel Corporation
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
*
* Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
* - Added _PDC for platforms with Intel CPUs
*/
#define pr_fmt(fmt) "ACPI: " fmt
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include <acpi/processor.h>
#include <xen/xen.h>
#include "internal.h"
static bool __init processor_physically_present(acpi_handle handle)
{
int cpuid, type;
u32 acpi_id;
acpi_status status;
acpi_object_type acpi_type;
unsigned long long tmp;
union acpi_object object = { 0 };
struct acpi_buffer buffer = { sizeof(union acpi_object), &object };
status = acpi_get_type(handle, &acpi_type);
if (ACPI_FAILURE(status))
return false;
switch (acpi_type) {
case ACPI_TYPE_PROCESSOR:
status = acpi_evaluate_object(handle, NULL, NULL, &buffer);
if (ACPI_FAILURE(status))
return false;
acpi_id = object.processor.proc_id;
break;
case ACPI_TYPE_DEVICE:
status = acpi_evaluate_integer(handle, "_UID", NULL, &tmp);
if (ACPI_FAILURE(status))
return false;
acpi_id = tmp;
break;
default:
return false;
}
if (xen_initial_domain())
/*
* When running as a Xen dom0 the number of processors Linux
* sees can be different from the real number of processors on
* the system, and we still need to execute _PDC for all of
* them.
*/
return xen_processor_present(acpi_id);
type = (acpi_type == ACPI_TYPE_DEVICE) ? 1 : 0;
cpuid = acpi_get_cpuid(handle, type, acpi_id);
return !invalid_logical_cpuid(cpuid);
}
static void acpi_set_pdc_bits(u32 *buf)
{
buf[0] = ACPI_PDC_REVISION_ID;
buf[1] = 1;
/* Enable coordination with firmware's _TSD info */
buf[2] = ACPI_PDC_SMP_T_SWCOORD;
/* Twiddle arch-specific bits needed for _PDC */
arch_acpi_set_pdc_bits(buf);
}
static struct acpi_object_list *acpi_processor_alloc_pdc(void)
{
struct acpi_object_list *obj_list;
union acpi_object *obj;
u32 *buf;
/* allocate and initialize pdc. It will be used later. */
obj_list = kmalloc(sizeof(struct acpi_object_list), GFP_KERNEL);
if (!obj_list)
goto out;
obj = kmalloc(sizeof(union acpi_object), GFP_KERNEL);
if (!obj) {
kfree(obj_list);
goto out;
}
buf = kmalloc(12, GFP_KERNEL);
if (!buf) {
kfree(obj);
kfree(obj_list);
goto out;
}
acpi_set_pdc_bits(buf);
obj->type = ACPI_TYPE_BUFFER;
obj->buffer.length = 12;
obj->buffer.pointer = (u8 *) buf;
obj_list->count = 1;
obj_list->pointer = obj;
return obj_list;
out:
pr_err("Memory allocation error\n");
return NULL;
}
/*
* _PDC is required for a BIOS-OS handshake for most of the newer
* ACPI processor features.
*/
static acpi_status
acpi_processor_eval_pdc(acpi_handle handle, struct acpi_object_list *pdc_in)
{
acpi_status status = AE_OK;
if (boot_option_idle_override == IDLE_NOMWAIT) {
/*
* If mwait is disabled for CPU C-states, the C2C3_FFH access
* mode will be disabled in the parameter of _PDC object.
* Of course C1_FFH access mode will also be disabled.
*/
union acpi_object *obj;
u32 *buffer = NULL;
obj = pdc_in->pointer;
buffer = (u32 *)(obj->buffer.pointer);
buffer[2] &= ~(ACPI_PDC_C_C2C3_FFH | ACPI_PDC_C_C1_FFH);
}
status = acpi_evaluate_object(handle, "_PDC", pdc_in, NULL);
if (ACPI_FAILURE(status))
acpi_handle_debug(handle,
"Could not evaluate _PDC, using legacy perf control\n");
return status;
}
void acpi_processor_set_pdc(acpi_handle handle)
{
struct acpi_object_list *obj_list;
if (arch_has_acpi_pdc() == false)
return;
obj_list = acpi_processor_alloc_pdc();
if (!obj_list)
return;
acpi_processor_eval_pdc(handle, obj_list);
kfree(obj_list->pointer->buffer.pointer);
kfree(obj_list->pointer);
kfree(obj_list);
}
static acpi_status __init
early_init_pdc(acpi_handle handle, u32 lvl, void *context, void **rv)
{
if (processor_physically_present(handle) == false)
return AE_OK;
acpi_processor_set_pdc(handle);
return AE_OK;
}
static int __init set_no_mwait(const struct dmi_system_id *id)
{
pr_notice("%s detected - disabling mwait for CPU C-states\n",
id->ident);
boot_option_idle_override = IDLE_NOMWAIT;
return 0;
}
static const struct dmi_system_id processor_idle_dmi_table[] __initconst = {
{
set_no_mwait, "Extensa 5220", {
DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_VERSION, "0100"),
DMI_MATCH(DMI_BOARD_NAME, "Columbia") }, NULL},
{},
};
static void __init processor_dmi_check(void)
{
/*
* Check whether the system is DMI table. If yes, OSPM
* should not use mwait for CPU-states.
*/
dmi_check_system(processor_idle_dmi_table);
}
void __init acpi_early_processor_set_pdc(void)
{
processor_dmi_check();
acpi_walk_namespace(ACPI_TYPE_PROCESSOR, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX,
early_init_pdc, NULL, NULL, NULL);
acpi_get_devices(ACPI_PROCESSOR_DEVICE_HID, early_init_pdc, NULL, NULL);
}