[PATCH] acpi: pcc: New test to sanity check PCC, (LP: #863175)
Colin King
colin.king at canonical.com
Tue Nov 20 17:05:24 UTC 2012
From: Colin Ian King <colin.king at canonical.com>
This adds a test to sanity check the Processor Clocking Control (PCC).
PCC seems to be found on just a few HP Proliant machines at the moment,
for example:
HP ProLiant DL380 G6
HP ProLiant DL380 G7
HP ProLiant BL460c G7
HP ProLiant BL460c G7
and probably a bunch more HP kit besides. I've only found 4
different machines that have the PCCH out of ~6000 kernel related
bugs filed in LaunchPad, so this really is for a small handful
of machines.
This test evaluates PCCH and sanity checks the data structures.
We can go a bit further and determine the PCC Header and memory
map this and sanity check this too, however, for this initial
version I have disabled this functionality because I've not got
any hardware to test this on and also I am concerned that reading
an incorrectly specified header region may create some issues.
I will enable this once I can test this on real hardware.
For more information on PCC, see:
http://acpica.org/download/Processor-Clocking-Control-v1p0.pdf
Signed-off-by: Colin Ian King <colin.king at canonical.com>
---
src/Makefile.am | 1 +
src/acpi/pcc/pcc.c | 469 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 470 insertions(+)
create mode 100644 src/acpi/pcc/pcc.c
diff --git a/src/Makefile.am b/src/Makefile.am
index e82c7a9..478c522 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -36,6 +36,7 @@ fwts_SOURCES = main.c \
acpi/lid/lid.c \
acpi/powerbutton/powerbutton.c \
acpi/wmi/wmi.c \
+ acpi/pcc/pcc.c \
bios/ebda_region/ebda_region.c \
bios/ebdadump/ebdadump.c \
bios/mtrr/mtrr.c \
diff --git a/src/acpi/pcc/pcc.c b/src/acpi/pcc/pcc.c
new file mode 100644
index 0000000..c3b6a38
--- /dev/null
+++ b/src/acpi/pcc/pcc.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2010-2012 Canonical
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include "fwts.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+/* acpica headers */
+#include "acpi.h"
+#include "fwts_acpi_method.h"
+
+/*
+ * This test does some sanity checking on the PCC interface,
+ * see http://acpica.org/download/Processor-Clocking-Control-v1p0.pdf
+ */
+
+#define PCC_HDR_SIGNATURE 0x24504343 /* $PCC */
+
+/*
+ * For the moment, we turn this off as I am concerned that reads of this region
+ * may cause issues.
+ */
+#define CHECK_PCC_HDR 0
+
+typedef struct {
+ uint8_t descriptor;
+ uint8_t length;
+ uint8_t space_id;
+ uint8_t resource_usage;
+ uint8_t type_specific;
+ uint64_t granularity;
+ uint64_t minimum;
+ uint64_t maximum;
+ uint64_t translation_offset;
+ uint64_t address_length;
+} __attribute__ ((packed)) fwts_pcc_memory_resource;
+
+typedef struct {
+ uint8_t descriptor;
+ uint16_t length;
+ uint8_t space_id;
+ uint8_t bit_width;
+ uint8_t bit_offset;
+ uint8_t access_size;
+ uint64_t address;
+} __attribute__ ((packed)) fwts_pcc_register_resource;
+
+typedef struct {
+ uint32_t signature;
+ uint16_t length;
+ uint8_t major;
+ uint8_t minor;
+ uint32_t features;
+ uint16_t command;
+ uint16_t status;
+ uint32_t latency;
+ uint32_t minimum_time;
+ uint32_t maximum_time;
+ uint32_t nominal;
+ uint32_t throttled_frequency;
+ uint32_t minimum_frequency;
+} __attribute__ ((packed)) fwts_pcc_header;
+
+/*
+ * pcc_init()
+ * initialize ACPI
+ */
+static int pcc_init(fwts_framework *fw)
+{
+ if (fwts_method_init(fw) != FWTS_OK)
+ return FWTS_ERROR;
+
+ return FWTS_OK;
+}
+
+/*
+ * pcc_deinit
+ * de-intialize ACPI
+ */
+static int pcc_deinit(fwts_framework *fw)
+{
+ return fwts_method_deinit(fw);
+}
+
+#if CHECK_PCC_HDR
+static void pcc_check_pcc_header(
+ fwts_framework *fw,
+ uint64_t addr,
+ uint64_t length,
+ bool *failed)
+{
+ fwts_pcc_header *hdr;
+
+ hdr = (fwts_pcc_header *)fwts_mmap((off_t)addr, (size_t)length);
+ if (hdr == NULL) {
+ fwts_log_info(fw, "Failed to memory map PCC header 0x%" PRIx64
+ "..0x%" PRIx64 ".", addr, addr + length);
+ return;
+ }
+
+ fwts_log_info_verbatum(fw, "PCC header at 0x%" PRIx64 ".", addr);
+ fwts_log_info_verbatum(fw, " Signature: 0x%" PRIx32, hdr->signature);
+ fwts_log_info_verbatum(fw, " Length: 0x%" PRIx16, hdr->length);
+ fwts_log_info_verbatum(fw, " Major: 0x%" PRIx8, hdr->major);
+ fwts_log_info_verbatum(fw, " Minor: 0x%" PRIx8, hdr->minor);
+ fwts_log_info_verbatum(fw, " Features: 0x%" PRIx32, hdr->features);
+ fwts_log_info_verbatum(fw, " Commend: 0x%" PRIx16, hdr->command);
+ fwts_log_info_verbatum(fw, " Status: 0x%" PRIx16, hdr->status);
+ fwts_log_info_verbatum(fw, " Latency: 0x%" PRIx32, hdr->latency);
+ fwts_log_info_verbatum(fw, " Minimum Time: 0x%" PRIx32, hdr->minimum_time);
+ fwts_log_info_verbatum(fw, " Maximum Time: 0x%" PRIx32, hdr->maximum_time);
+ fwts_log_info_verbatum(fw, " Nominal: 0x%" PRIx32, hdr->nominal);
+ fwts_log_info_verbatum(fw, " Throttled Freq.: 0x%" PRIx32, hdr->throttled_frequency);
+ fwts_log_info_verbatum(fw, " Minimum Freq.: 0x%" PRIx32, hdr->minimum_frequency);
+
+ fwts_munmap(hdr, (size_t)length);
+ fwts_log_nl(fw);
+
+ if (hdr->signature != PCC_HDR_SIGNATURE) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHdrSignatureError",
+ "The PCC Header Signature is not a valid PCC signature, was expecting "
+ "0x%" PRIx32 " ($PCC), got instead 0x%" PRIx32,
+ PCC_HDR_SIGNATURE, hdr->signature);
+ *failed = true;
+ }
+}
+#endif
+
+static void pcc_check_shared_memory_region(
+ fwts_framework *fw,
+ const char *name,
+ ACPI_OBJECT *pcc_obj,
+ bool *failed)
+{
+ fwts_pcc_memory_resource *pcc_mr;
+
+ if (pcc_obj->Type != ACPI_TYPE_BUFFER) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementZeroNotBuffer",
+ "PCCH object %s returned a package with element zero "
+ "was not an ACPI_BUFFER. This does not conform to the "
+ "PCC specification.", name);
+ *failed = true;
+ return;
+ }
+
+ if (pcc_obj->Buffer.Pointer == NULL) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementZeroBufferNull",
+ "PCCH object %s returned a package with element zero "
+ "which was an ACPI_BUFFER, however, the buffer pointer "
+ "was NULL. This does not conform to the PCC "
+ "specification.", name);
+ *failed = true;
+ return;
+ }
+
+ if (pcc_obj->Buffer.Length < sizeof(fwts_pcc_memory_resource)) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHMemoryResourceIllegalSize",
+ "PCCH object %s returned a PCC memory resource buffer "
+ "which was the wrong size. Got %" PRIu32 " bytes, "
+ "expected %zu bytes.", name,
+ pcc_obj->Buffer.Length, sizeof(fwts_pcc_memory_resource));
+ *failed = true;
+ return;
+ }
+
+ pcc_mr = (fwts_pcc_memory_resource *)pcc_obj->Buffer.Pointer;
+
+ fwts_log_info_verbatum(fw, "PCC Memory Resource (Shared Memory Region) for %s:", name);
+ fwts_log_info_verbatum(fw, " Descriptor: 0x%" PRIx8, pcc_mr->descriptor);
+ fwts_log_info_verbatum(fw, " Length: 0x%" PRIx8, pcc_mr->length);
+ fwts_log_info_verbatum(fw, " Space ID: 0x%" PRIx8, pcc_mr->space_id);
+ fwts_log_info_verbatum(fw, " Resource Usage: 0x%" PRIx8, pcc_mr->resource_usage);
+ fwts_log_info_verbatum(fw, " Type Specific: 0x%" PRIx8, pcc_mr->type_specific);
+ fwts_log_info_verbatum(fw, " Minimum: 0x%" PRIx64, pcc_mr->minimum);
+ fwts_log_info_verbatum(fw, " Maximum: 0x%" PRIx64, pcc_mr->maximum);
+ fwts_log_info_verbatum(fw, " Translation Offset: 0x%" PRIx64, pcc_mr->translation_offset);
+ fwts_log_info_verbatum(fw, " Address Length: 0x%" PRIx64, pcc_mr->address_length);
+
+ if (pcc_mr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryResourceSpaceIdWrongType",
+ "PCC Memory Resource Space ID is of the wrong type, got 0x%" PRIx8
+ ", expected to get type 0x%" PRIx8 " (ACPI_ADR_SPACE_SYSTEM_MEMORY).",
+ pcc_mr->space_id, ACPI_ADR_SPACE_SYSTEM_MEMORY);
+ *failed = true;
+ }
+
+ if (pcc_mr->length == 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryResourceAddrLength",
+ "PCC Memory Resource Address Length is zero, this is clearly incorrect.");
+ *failed = true;
+ }
+
+ if (pcc_mr->minimum & 4095) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryNotPageAligned",
+ "PCC Memory Resource Minumum Address is not page aligned. It must "
+ "start on a 4K page boundary.");
+ *failed = true;
+ }
+
+ /* TODO: We should also check if the region is in the e820 region too */
+
+ if (pcc_mr->minimum == 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryResourceMinAddr",
+ "PCC Memory Resource Minimum Address is zero, this is clearly incorrect.");
+ *failed = true;
+ }
+
+ if (pcc_mr->maximum == 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryResourceMaxAddr",
+ "PCC Memory Resource Maximum Address is zero, this is clearly incorrect.");
+ *failed = true;
+ }
+
+ if (pcc_mr->minimum >= pcc_mr->maximum) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCMemoryResourceMinMaxAddrError",
+ "PCC Memory Resource Minimum Address should be less than "
+ "the Maximum Address: Min: 0x%" PRIx64 ", Max: 0x%" PRIx64,
+ pcc_mr->minimum, pcc_mr->maximum);
+ *failed = true;
+ }
+
+ fwts_log_nl(fw);
+
+#if CHECK_PCC_HDR
+ pcc_check_pcc_header(fw, pcc_mr->minimum, pcc_mr->length, failed);
+#endif
+}
+
+static void pcc_check_doorbell_address(
+ fwts_framework *fw,
+ const char *name,
+ ACPI_OBJECT *pcc_obj,
+ bool *failed)
+{
+ fwts_pcc_register_resource *pcc_rr;
+
+ if (pcc_obj->Type != ACPI_TYPE_BUFFER) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementOneNotBuffer",
+ "PCCH object %s returned a package with element zero "
+ "was not an ACPI_BUFFER. This does not conform to the "
+ "PCC specification.", name);
+ *failed = true;
+ return;
+ }
+
+ if (pcc_obj->Buffer.Pointer == NULL) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementOneBufferNull",
+ "PCCH object %s returned a package with element one "
+ "which was an ACPI_BUFFER, however, the buffer pointer "
+ "was NULL. This does not conform to the PCC "
+ "specification.", name);
+ *failed = true;
+ return;
+ }
+
+ if (pcc_obj->Buffer.Length < sizeof(fwts_pcc_register_resource)) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHRegisterResourceIllegalSize",
+ "PCCH object %s returned a PCC register resource buffer "
+ "which was the wrong size. Got %" PRIu32 " bytes, "
+ "expected %zu bytes.", name,
+ pcc_obj->Buffer.Length, sizeof(fwts_pcc_register_resource));
+ *failed = true;
+ return;
+ }
+
+ pcc_rr = (fwts_pcc_register_resource *)pcc_obj->Buffer.Pointer;
+
+ fwts_log_info_verbatum(fw, "PCC Register Resource (Doorbell) for %s:", name);
+ fwts_log_info_verbatum(fw, " Descriptor: 0x%" PRIx8, pcc_rr->descriptor);
+ fwts_log_info_verbatum(fw, " Length: 0x%" PRIx8, pcc_rr->length);
+ fwts_log_info_verbatum(fw, " Space ID: 0x%" PRIx8, pcc_rr->space_id);
+ fwts_log_info_verbatum(fw, " Bit Width: 0x%" PRIx8, pcc_rr->bit_width);
+ fwts_log_info_verbatum(fw, " Bit Offset: 0x%" PRIx8, pcc_rr->bit_offset);
+ fwts_log_info_verbatum(fw, " Access Size: 0x%" PRIx8, pcc_rr->access_size);
+ fwts_log_info_verbatum(fw, " Address: 0x%" PRIx64, pcc_rr->address);
+
+ if (pcc_rr->space_id != ACPI_ADR_SPACE_SYSTEM_IO) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCRegisterResourceSpaceIdWrongType",
+ "PCC Register Resource Space ID is of the wrong type, got 0x%" PRIx8
+ ", expected to get type 0x%" PRIx8 " (ACPI_ADR_SPACE_SYSTEM_IO).",
+ pcc_rr->space_id, ACPI_ADR_SPACE_SYSTEM_IO);
+ *failed = true;
+ }
+
+ if ((pcc_rr->bit_width < 1) || (pcc_rr->bit_width > 32)) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCRegisterResourceBitWidthError",
+ "PCC Register Resource Bit Width is incorrect, got 0x%" PRIx8,
+ pcc_rr->bit_width);
+ *failed = true;
+ }
+
+ if (pcc_rr->address == 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCRegisterResourceAddressZero",
+ "PCC Register Resource Address is incorrect, got 0x%" PRIx64,
+ pcc_rr->address);
+ *failed = true;
+ }
+
+ fwts_log_nl(fw);
+}
+
+static void pcc_check_doorbell_preserve_mask(
+ fwts_framework *fw,
+ const char *name,
+ ACPI_OBJECT *pcc_obj,
+ bool *failed)
+{
+ if (pcc_obj->Type != ACPI_TYPE_INTEGER) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementTwoNotInteger",
+ "PCCH object %s returned a package with element two "
+ "was not an ACPI_INTEGER. This does not conform to the "
+ "PCC specification.", name);
+ *failed = true;
+ return;
+ }
+
+ fwts_log_info_verbatum(fw, "PCC Doorbell Preserve Mask for %s:", name);
+ fwts_log_info_verbatum(fw, " Preserve Mask: 0x%" PRIx64, pcc_obj->Integer.Value);
+ fwts_log_nl(fw);
+}
+
+static void pcc_check_doorbell_write_mask(
+ fwts_framework *fw,
+ const char *name,
+ ACPI_OBJECT *pcc_obj,
+ bool *failed)
+{
+ if (pcc_obj->Type != ACPI_TYPE_INTEGER) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHElementTwoNotInteger",
+ "PCCH object %s returned a package with element three "
+ "was not an ACPI_INTEGER. This does not conform to the "
+ "PCC specification.", name);
+ *failed = true;
+ return;
+ }
+
+ fwts_log_info_verbatum(fw, "PCC Doorbell Write Mask for %s:", name);
+ fwts_log_info_verbatum(fw, " Write Mask: 0x%" PRIx64, pcc_obj->Integer.Value);
+ fwts_log_nl(fw);
+
+ if (pcc_obj->Integer.Value == 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCDoorBellWriteMaskZero",
+ "PCC Doorbell Write Mask is incorrect, got 0x%" PRIx64,
+ pcc_obj->Integer.Value);
+ *failed = true;
+ }
+}
+
+static void pcc_check_buffer(
+ fwts_framework *fw,
+ const char *name,
+ ACPI_BUFFER *buf)
+{
+ ACPI_OBJECT *obj;
+ bool failed = false;
+
+ if (buf->Pointer == NULL) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHNullPointer",
+ "PCCH object %s returned a NULL pointer when evaluated.", name);
+ return;
+ }
+
+ obj = buf->Pointer;
+
+ if (obj->Type != ACPI_TYPE_PACKAGE) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHNotPackage",
+ "PCCH object %s did not return an ACPI_PACKAGE when evaluated.", name);
+ return;
+ }
+
+ if (obj->Package.Count != 4) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCHExpectedTwoElements",
+ "PCCH object %s did not return an ACPI_PACKAGE "
+ "that contained two elements, got %" PRIu32 " instead.",
+ name, obj->Package.Count);
+ return;
+ }
+
+ pcc_check_shared_memory_region(fw, name, &obj->Package.Elements[0], &failed);
+ pcc_check_doorbell_address(fw, name, &obj->Package.Elements[1], &failed);
+ pcc_check_doorbell_preserve_mask(fw, name, &obj->Package.Elements[2], &failed);
+ pcc_check_doorbell_write_mask(fw, name, &obj->Package.Elements[3], &failed);
+
+ if (!failed)
+ fwts_passed(fw, "PCC pased; %s returned sane looking data structures.", name);
+}
+
+static int pcc_test1(fwts_framework *fw)
+{
+ ACPI_BUFFER buf;
+ ACPI_STATUS ret;
+ fwts_list_link *item;
+ fwts_list *pccs;
+ static char *name = "PCCH";
+ size_t name_len = strlen(name);
+ int count = 0;
+
+ fwts_log_info(fw,
+ "This test checks the sanity of the Processor Clocking Control "
+ "as found on some HP ProLiant machines. Most computers do not "
+ "use this interface to control the CPU clock frequency, so this "
+ "test will be skipped.");
+ fwts_log_nl(fw);
+
+ if ((pccs = fwts_method_get_names()) != NULL) {
+ fwts_list_foreach(item, pccs) {
+ char *pcc_name = fwts_list_data(char*, item);
+ size_t len = strlen(pcc_name);
+
+ if (strncmp(name, pcc_name + len - name_len, name_len) == 0) {
+ ret = fwts_method_evaluate(fw, pcc_name, NULL, &buf);
+ if (ACPI_FAILURE(ret) == AE_OK) {
+ pcc_check_buffer(fw, pcc_name, &buf);
+ count++;
+
+ if (buf.Length && buf.Pointer)
+ free(buf.Pointer);
+ }
+ }
+ }
+ }
+
+ if (count > 1) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCCTooManyPCCHObjects",
+ "The firmware contains too many PCCH objects, expected 1, got %d.", count);
+ }
+
+ /* Nothing found, this is not an error */
+ if (count == 0) {
+ fwts_log_info(fw, "This machine does not use Processor Clocking Control (PCC).");
+ fwts_infoonly(fw);
+ }
+
+ return FWTS_OK;
+}
+
+/* Just one big test */
+static fwts_framework_minor_test pcc_tests[] = {
+ { pcc_test1, "Check PCCH." },
+ { NULL, NULL }
+};
+
+static fwts_framework_ops pcc_ops = {
+ .description = "Processor Clocking Control (PCC) Test.",
+ .init = pcc_init,
+ .deinit = pcc_deinit,
+ .minor_tests = pcc_tests
+};
+
+FWTS_REGISTER(pcc, &pcc_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH);
--
1.7.10.4
More information about the fwts-devel
mailing list