Message ID | 1353431124-27557-1-git-send-email-colin.king@canonical.com |
---|---|
State | Accepted |
Headers | show |
On 11/21/2012 01:05 AM, Colin King wrote: > From: Colin Ian King <colin.king@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@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); > Acked-by: Alex Hung <alex.hung@canonical.com>
On Wed, Nov 21, 2012 at 1:05 AM, Colin King <colin.king@canonical.com> wrote: > From: Colin Ian King <colin.king@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@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 > Acked-by: Keng-Yu Lin <kengyu@canonical.com>
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);